RelayQR 是一个自托管的动态二维码管理平台。二维码本身只保存 RelayQR 的固定短链接;你可以在后台随时更换实际目标,无需重新印刷二维码。
- 多用户独立账号,用户名和密码登录,不依赖邮件服务;忘记密码时管理员可在后台重置,生成一次性临时密码,首次登录必须修改。
- 每个账号可创建任意多个随机短码,短码删除后永不复用。
- 粘贴目标地址,或上传二维码图片:浏览器本地解析目标,原图默认保存到自托管服务器。
- 每个活码可独立开启 Fallback 二维码展示页,并选择同时展示目标链接或只展示已上传的二维码原图。
- 每个 Fallback 可开启访问条件:按允许的 IP 国家/省/城市筛选,并组合最多 10 道单选题或填空题;全部通过后才显示目标内容。
- 保存完整目标历史,可将旧目标恢复为新的当前版本。
- 每个活码独立控制是否允许跳转;暂停时必须填写面向扫码者的说明。
- 支持 HTTP(S)、微信链接和自定义 App 协议;拒绝可执行或本地危险协议。
- 二维码支持透明/纯色背景、自定义前景色、方块/圆角/圆点模块和独立定位角样式、可调静区、顶部/底部文字(自动换行、最多三行、可选圆角半透明背景)和可调大小的中心图标。
- 导出 1000 × 1000 PNG 或 SVG,两种格式渲染结果一致。
- 统计累计扫描、日期趋势、设备类别、来源域名和 IP 属地,并在后台查看最近 100 次访问的完整 IP 明细。
- 管理员中心可授权或取消管理员、按成员筛选修改记录,并实时监控请求、错误率、响应时间、CPU、内存、磁盘和数据库状态。
- SQLite 单文件数据与 Docker 自托管。
要求 Node.js 22 或更高版本。
cp .env.example .env
npm install
npm run dev浏览器打开 http://localhost:5173。Vite 会把 /api 和 /r 请求代理到 http://localhost:3000。
常用命令:
npm run typecheck
npm test
npm run build
npm start生产构建后,Fastify 会从同一个端口提供前端、API 和公开短链接。
本机需要 Docker 与 Docker Compose。复制环境文件并至少修改以下两项:
cp .env.example .envDOMAIN=qr.example.com
SESSION_SECRET=请替换为至少32字节的随机字符串启动服务:
docker compose up -d --buildCompose 只启动 RelayQR,并将端口绑定到宿主机的 127.0.0.1:3000,外网无法直接绕过 HTTPS 访问。部署前必须把域名的 A/AAAA 记录解析到服务器,并在宿主机 Caddy 中加入:
qr.example.com {
reverse_proxy 127.0.0.1:3000
}将示例域名替换为 .env 中的 DOMAIN,然后执行 sudo systemctl reload caddy。服务器防火墙或云安全组需要放行 TCP 80 和 TCP 443。
业务数据保存在 relayqr_data 卷中。升级或迁移前应备份该数据卷;HTTPS 证书继续由宿主机现有 Caddy 管理。
建议通过 Caddy、Nginx 或其他反向代理提供 HTTPS。PUBLIC_BASE_URL 一旦用于印刷二维码,不应随意更换域名;服务器必须长期保留 /r/:slug 路由。
| 变量 | 默认值 | 说明 |
|---|---|---|
PORT |
3000 |
服务监听端口 |
HOST |
0.0.0.0 |
服务监听地址 |
DOMAIN |
qr.example.com |
Docker HTTPS 部署使用的公网域名 |
PUBLIC_BASE_URL |
http://localhost:3000 |
生成固定二维码时使用的公网根地址 |
DATA_DIR |
./data |
SQLite、图标与上传二维码原图的持久化目录 |
SESSION_SECRET |
仅开发默认值 | 生产环境必填的会话密钥 |
SESSION_TTL_DAYS |
30 |
登录有效天数 |
REGISTRATION_ENABLED |
true |
新数据库的注册默认值;管理员在后台保存设置后以后台设置为准 |
TRUST_PROXY |
false |
反向代理部署时设为 true |
- HTTP(S) 目标返回
302,并带有Cache-Control: no-store,防止浏览器或 CDN 缓存旧目标。 - 启用 Fallback 且已上传二维码原图时返回展示页,可配置同时展示目标链接或只展示原图;关闭开关即恢复直接跳转。
- 启用访问条件时,服务端先校验 IP 属地,再校验全部题目;任一条件不通过都不会把目标内容或二维码图片返回给访客。
- 验证通过后签发与活码、访客 IP 和当前门禁配置绑定的 15 分钟 HttpOnly 凭证;直接猜测二维码图片地址会被拒绝。
- 自定义协议先返回即时打开页,再尝试唤起对应 App;浏览器或 App 内置浏览器仍可能阻止深链。
- 暂停状态返回说明页,不包含目标地址,也不会执行跳转;该次访问仍计入扫描统计。
- 删除状态永久返回
410 Gone,短码不会分配给其他活码。
微信群、支付码或其他第三方二维码能否最终使用,仍取决于对应平台的有效期、人数限制、风控和客户端协议。RelayQR 负责更换入口目标,不会绕过第三方平台规则。
- 升级已有数据库时,最早创建的账号会自动成为首位管理员;全新部署时,第一个注册的账号成为管理员。
- 管理员可以向其他成员授予或取消管理员权限,系统不允许取消最后一名管理员。
- 成员忘记密码时,管理员可以验证自己的密码后为其重置:生成 16 位一次性临时随机密码(仅展示一次),该成员所有设备会被退出登录,使用临时密码登录后必须修改密码才能进入控制台;重置操作写入修改记录,管理员不能重置自己的密码。
- 管理员可以查看并按成员筛选全部活码;输入当前管理员密码后,可在 10 分钟授权期内代成员修改目标、状态、样式、Fallback 和访问条件等完整配置。授权按单个活码生效,密码不会保存,操作记录管理员账号与 IP;普通成员仍只能访问自己的活码。
- 管理员可以实时开启或关闭新账号注册;设置保存到 SQLite 并在重启后保留,登录页会同步显示注册状态。
- 修改记录覆盖活码创建、名称/样式、目标、跳转、Fallback、访问条件、图标、二维码原图和密码修改;不记录密码、题目答案或目标正文等敏感请求内容。
- 审计从该功能部署后开始记录,旧操作不会被反向补齐。
- 服务器监控每 5 秒刷新。容器部署时,主机名和进程指标对应 RelayQR 容器,磁盘指标对应
/data数据卷所在文件系统。
- 密码通过 Node.js
scrypt加盐哈希。 - 会话使用随机令牌和 HttpOnly、SameSite Cookie;数据库只保存令牌哈希。
- 登录与注册接口有独立速率限制,所有接口均有全局速率限制。
- 管理员接口在服务端校验实时角色,隐藏前端入口不能代替权限控制;管理员授权变更本身也会写入审计记录。
- 图标限制为 1.5 MB、二维码原图限制为 8 MB;均仅支持 PNG、JPEG 或 WebP,并校验文件签名。
- 扫描统计保存时间、完整 IP、离线解析属地、设备类别和来源域名,不保存完整 User-Agent。IP 地址属于个人信息,部署者应依法告知访客、限制后台访问并制定删除/保留策略。
- IP 属地使用内置 ip2region IPv4 离线数据解析,不会把访客 IP 发给外部定位接口。代理、VPN、移动网络和数据更新频率均可能影响准确性;无法解析的属地在开启筛选时按不通过处理。