Skip to content

Repository files navigation

RelayQR

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 与 Docker Compose。复制环境文件并至少修改以下两项:

cp .env.example .env
DOMAIN=qr.example.com
SESSION_SECRET=请替换为至少32字节的随机字符串

启动服务:

docker compose up -d --build

Compose 只启动 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、移动网络和数据更新频率均可能影响准确性;无法解析的属地在开启筛选时按不通过处理。

License

GNU General Public License Version 3 © 2026 Mazha0309

About

Self-hosted dynamic QR code manager with per-code redirect controls

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages