自托管 · 多租户 · 短链服务

每一次跳转,
皆为裁决。

Janus 把短链服务装进你自己的服务器:租户带自己的域名进来,Caddy 为每个已激活域名自动签发并续期 HTTPS,访问热路径上零 DB 查询地完成一次规则裁决。

  • Go 1.26 · Gin
  • Vue 3 · TypeScript
  • PostgreSQL 16
  • Caddy 2 · on-demand TLS
  • AGPL-3.0

热路径

一次访问,六步之间完成裁决。

从 Host 定位到落下一行访问明细,全过程发生在跳转热路径上。三条硬约束守在这里:零 DB 查询、零写放大、fail-open。

  1. 1
    定位域名

    用 Host 头映射到 active 域名;未命中直接 404,并且不记明细。

    Host → domain
  2. 2
    定位短链

    短码找到短链与目标池;同一短码在不同域名下可指向不同目标。

    code → link + targets
  3. 3
    可用性检查

    停用 / 已逻辑删除 / 无可用目标 / 落地页文件缺失,一律 404,并记一行 outcome=failed 明细。

    availability
  4. 4
    规则求值

    短链开启规则时按 priority 升序求值,首条命中即裁决;规则快照按租户整份缓存在内存,零 DB 查询。

    first match wins
  5. 5
    跳转或落地页

    跳转型从目标池轮询选一个 → 302(默认)或 301;落地页型先到落地页,点击后再进目标。

    round-robin → 302
  6. 6
    落访问明细

    动作 / 结果 / IP / User-Agent / 来源 / 语言 / 国家 / 命中规则,一行一次访问。

    visit row
零 DB 查询规则集合来自按租户缓存的内存快照,没有规则时连访客画像都不构造。
零写放大规则命中不 UPDATE 任何计数表,只多写本次访问那一行明细。
fail-open快照加载失败或求值 panic 一律按「未命中」继续,风控不把线上短链打成 500。

核心能力

该有的都有,且都在你自己的机器上。

补一条不依赖外部服务的短链基础设施:域名、证书、规则、明细,闭环自洽。

自带域名 + 自动 HTTPS

自有域名加进来做 DNS 激活校验,通过后由 Caddy on-demand TLS 自动签发 Let's Encrypt 证书并续期;平台域名配一条泛解析,每个租户自动获得 <slug>.<平台域名>。

on-demand TLS · 重试 5min / 最长 72h

多租户隔离

租户之间的域名、短链、规则、落地页完全隔离;部署者持有平台管理员角色,可治理全平台。

Casbin RBAC · tenant / superadmin

规则引擎

13 个可求值访客字段 × 14 个运算符,条件组可嵌套;裁决为放行 / 改写目标 / 404 / 限流,按 priority 升序首条命中即裁决。

visual 条件树 或 Expr 表达式

多目标轮询

一条短链可配多个目标 URL,访问时按轮询选择其一;跳转方式临时 302 或永久 301,每条独立配置。

round-robin · 302 / 301

落地页型短链

落地页来源二选一:填外部地址,或上传含 index.html 的 zip 由平台托管。每条短链一份 JS SDK,按钮点击自动绑定并回传点击。

/<code>/sdk.js · /<code>/click

访问明细与地理统计

每次访问落一行:动作 / 结果 / IP / UA / 来源 / 语言 / 国家。总览走专用聚合端点,含 KPI、CTR、失败与风险拦截分布,以及离线 ip2region 驱动的世界地图。

?range=today|3d|7d|30d|1y

给脚本用的 Bearer JWT API

会话 cookie 之外,另发 24 小时 Bearer JWT 供脚本 / CLI 使用——header 认证,免疫 CSRF;配套限流、bcrypt 密码与统一 { code, message, details } 错误响应。

POST /api/auth/token → Authorization: Bearer …

自定义错误页

租户全局或单条规则专属地自定义 404 / 429 页面,决议优先级为规则专属 → 租户全局 → 系统内置;错误页带 CSP sandbox 与 nosniff。

rule → tenant → default

规则引擎

访客画像进,裁决结果出。

选一个访客画像,看规则引擎怎么判。规则按 priority 升序求值,首条命中即裁决;取不到数据恒不命中——取反写法也不例外。

country
devtype
ipattr
  1. P10devtype in [bot]throttle
  2. P20country in [CN, US]pass
  3. P30ipattr in [private]notfound
302 pass · 继续原跳转

未命中任何规则 → 走原跳转流程,从目标池轮询选一个,302 到目标。

注意:country 取不到值时,`country not_in [CN]` 这类取反条件同样不命中——否则「拦截非中国访客」会变成拦截所有人。

架构

一条 compose 命令,一套自洽的拓扑。

前后端分离:后端镜像内无任何前端文件,前端独立构建为 nginx 镜像——改前端不必重打后端。

Caddy80/443 · on-demand TLS
nginx(web 镜像)SPA + 静态资源
Go 后端/api/* · /{code} · /internal/caddy/*
PostgreSQL 16唯一持久状态

技术栈

Go 1.26 gin gorm casbin expr-lang golang-jwt/v5 ip2region Vue 3.5 TypeScript Vite 6 Tailwind CSS 4 Reka UI PostgreSQL 16 Caddy 2 Let's Encrypt

后端启动顺序:加载配置 → 连接 Postgres → 执行初始化脚本 → 初始化超管 → 启动后台 worker → 监听 HTTP。后台 worker 负责 DNS 重试、证书探活、访问记录与 token 清理。

邮件可插拔:配置 SMTP 走真实发送;未配置时用控制台 mailer 把邮件内容打印到后端日志,便于无外网环境开发。

开始自托管

把你的短链,收回自己手里。

单服务器部署:Go 后端 + Vue 3 SPA + PostgreSQL 16 + Caddy,一条命令全部跑起来。唯一的外部依赖是 Let's Encrypt 与可选的 SMTP。

AGPL-3.0 自托管 多租户隔离 自动 HTTPS 零 DB 查询热路径