No description
  • Go 69.6%
  • TypeScript 26.2%
  • Shell 3.3%
  • CSS 0.3%
  • Dockerfile 0.3%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
xiaoman1221 6324e141c1 feat: 签到随机区间、支付与用户组购买、记录状态同步
签到
- 奖励改为闭区间 [checkin_min, checkin_max] 内随机取值(crypto/rand),
  两端相等即固定奖励;区间配反由 pointsPolicy 夹取、接口直接 400
- 旧固定值 checkin_reward 不再参与运行,由 MigratePointsCheckin 在启动时
  幂等地搬进区间并清零 —— 积分是决定用户能否用解析的经济参数,
  不该在升级时被换成默认值
- 管理端在「积分管理 → 规则设置」配置区间;个人中心按 points_enabled 显隐签到卡

支付(internal/payment,与 dns/oauth 同构的插件注册表)
- 内置 manual(人工确认,无需商户资质)与 alipay(电脑网站支付,RSA2 签名
  与验签在 internal/sign/alipay.go)
- 凭据按账号加密存在 provider_accounts(kind=payment),同一个渠道可配多个
  商户;回调地址因此带账号 ID,验签必须用下单时那个账号的凭据
- 回调是公开接口:必须验签、必须比对金额(签名只证明来源,不证明金额
  没被改小)、失败一律 4xx 且不发权益;人工渠道的 ParseNotify 恒返回错误,
  它的确认入口在管理端
- 金额全程用整数分,只在驱动内部换算(支付宝要元且两位小数),
  centsToYuan/yuanToCents 刻意不经过 float64
- 订单号随机不可枚举;下单只创建待支付订单,不改动用户组

用户组购买与分配
- 新增 Order 模型(状态机 pending→paid/closed)与订单生命周期:
  超时关闭、管理员确认收款、关闭订单
- 权益发放只在一处 settleOrderPaid,用条件更新(WHERE status <> 'paid')
  认领订单:回调重放、管理员与回调撞车都只发一次
- 续期从原到期时间往后接,提前续费不丢剩余天数
- 到期降级两道防线:ExpireUserGroups 每小时把数据改对,entitlementsFor
  的判断兜住任务间隔;退回默认组的权益而非全局设置
- 后台分配用户组时按组时长自动写到期时间,group_days 可显式指定

记录状态同步(internal/service/sync.go)
- 每小时一轮:重试失败的删除、回收已过期的记录、核对活跃记录在服务商侧
  是否仍然存在(不存在则标记 missing 并保留参数以便重建)
- 查询能力做成可选接口 dns.RecordChecker;mock 现在记着自己创建过哪些
  记录,MockForgetRecord 可模拟带外删除
- 用户可对缺失记录点「重建」,走与积分恢复同一条 recreateRecord(备注不丢)
- 与积分结算共用 recordOpMu:两者都会对同一条记录调用服务商

修复
- enabled=false 被静默忽略:带 default:true 标签的布尔字段在 Create 时
  零值被跳过、数据库默认值覆盖管理员的"创建时停用",用户组/主域名/
  服务商账号三处创建路径受影响;实测 Select("*") 与显式列名都绕不过,
  改为创建后定向更新
- payment.Payment 缺 JSON tag,会序列化成 URL/QRCode/Manual 而前端读
  url/qr_code/manual,支付分支会全部落到兜底提示
- OAuth 绑定入口改返回 JSON {url} 而不是 302:浏览器顶层导航不带
  Authorization 头,原先那条路径完全不可用

前端
- 新增「套餐与订单」(用户)与「订单管理」(管理员)两个页面
- 解析列表支持「解析缺失」徽章与重建;个人中心与用户管理展示套餐到期
- 系统设置有「记录维护 → 立即同步一轮」

契约与验证
- openapi.yaml 补齐 10 个新接口与 6 个 schema;新增 scripts/check-openapi.cjs
  校验路径/schema/引用完整性(文档页直接渲染它,缩进错误会让整页空白
  却不触发任何编译失败)
- smoke.sh 扩到 213 项断言,新增支付全链路、订单筛选与关闭、人工渠道
  回调拒绝、记录同步与重建拒绝路径

验证:go build/vet/test 全绿(新增 18 项单测),前端 tsc+vite 构建通过,
冒烟 213/213,契约校验通过,浏览器实测下单→确认收款→权益与到期时间→
远端丢失→标记缺失→重建恢复的完整链路。
2026-09-20 12:18:17 +08:00
cmd/sdsd feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
internal feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
scripts feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
web feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
.dockerignore feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
.gitattributes feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
.gitignore feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
CLAUDE.md feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00
config.example.yaml feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
docker-compose.yml feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
docker-entrypoint.sh feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
Dockerfile feat: 管理端合并为三页、登录渠道可选、Docker 支持,并修复签名与迁移缺陷 2026-09-18 07:42:14 +08:00
go.mod feat: 分层重构 + 平台化能力(多服务商、凭据加密、短信/邮件通知) 2026-09-17 21:30:55 +08:00
go.sum feat: 分层重构 + 平台化能力(多服务商、凭据加密、短信/邮件通知) 2026-09-17 21:30:55 +08:00
LICENSE feat: 分层重构 + 平台化能力(多服务商、凭据加密、短信/邮件通知) 2026-09-17 21:30:55 +08:00
README.md feat: 签到随机区间、支付与用户组购买、记录状态同步 2026-09-20 12:18:17 +08:00

SDS (Subdomain Distribution System)

Go Report Card License

SDSGo 是一个基于 Go (Golang) 开发的高性能、高并发的二级域名自动分发与管理系统。通过对接主流云服务商的 DNS API,实现用户自助申请、秒级解析、自动化管理的完整闭环。

🌟 核心特性

  • 多服务商支持: 插件化设计,已内置 13 家云解析适配器 —— 阿里云、腾讯云 DNSPod、华为云、百度智能云、Cloudflare、Route 53、GoDaddy、Namecheap、Gandi、DNSimple、DigitalOcean、Vultr、Linode。同一个适配器可以配置多个账号,主域名绑定到具体账号。
  • 第三方登录: 支持 GitHub / Google / 微软 / 微信 / QQ / 微博 / 支付宝官方直连,以及彩虹聚合登录、OAuthGo 两个聚合平台;聚合账号可以逐个勾选开放哪些渠道。
  • 记录备注: 用户可给每条解析记录填备注。服务商支持时一并写到远端(Cloudflare 的 comment、华为云/百度云的 description、腾讯云 DNSPod 的 Remark、阿里云需额外一次调用),不支持则只存本地。
  • 用户组与配额: 按组控制记录数上限与单条记录时长上限;区分免费组与付费组。
  • 积分系统: 每日签到(奖励为可配置的随机区间)、兑换码与管理员充值获得积分;按「1 积分维持 N 条记录 × M 天」周期结算,余额不足的用户解析自动暂停(远端记录删除)、补足后自动恢复,全程有积分流水可审计。管理端独立页面管理用户积分、批量生成兑换码与结算规则。
  • 套餐购买与支付: 用户组可标价出售,用户自助下单、支付成功后自动生效(到期回到默认组);内置人工确认收款(线下转账,无需商户资质)与支付宝电脑网站支付两个渠道,凭据按账号加密存储;管理端订单管理可筛选、确认收款与关闭订单。
  • 记录状态同步: 每小时核对每条解析记录在服务商侧是否仍然存在,回收已过期的记录、重试失败的删除;被带外删除的记录会被标记出来,用户可一键按原参数重建。
  • 安全与合规: 内置违禁词与保留前缀过滤,前缀按"包含"与"完全匹配"两种规则拦截;服务商凭据用 AES-GCM 加密存储,主密钥留在数据库之外。
  • 轻量与易部署: 编译后仅一个二进制文件,前端通过 go:embed 打进同一个文件;支持 Docker 一键部署。

尚未实现(README 的历史描述里有,但代码中没有):go-webauthn、OIDC。

🛠️ 技术栈

后端:Go 1.26 + Gin、GORM + SQLite(单文件零依赖部署,纯 Go 驱动无 CGO)、JWT(HS256)

前端:React 18 + TypeScript + Vite、Tailwind CSS v4 + Radix 无头原语(组件手写,非 shadcn CLI 生成)、Zustand、React Router

🚀 快速开始

1. 克隆仓库

git clone https://github.com/xiaoman1221/SDSGo.git
cd SDSGo

2. 配置文件

复制并修改配置文件(支持 JSON 或 YAML):

cp config.example.yaml config.yaml

配置项分两类:config.yaml 只放基础设施(监听地址、数据库路径、JWT 密钥、凭据加密主密钥、对外地址);其余可运行期调整的(站点名、注册开关、用户组权益、服务商凭据、邮件与短信)都在后台管理界面里改,存数据库。

配置文件不存在时不会报错,会用内置默认值启动(监听 0.0.0.0:8080、库在 data/sds.db)—— 对外提供服务前务必换掉默认的 jwt.secret。

3. 本地运行

go run cmd/sdsd/main.go

首次启动会自动建表、创建默认用户组、把首位注册的用户设为超级管理员。

4. 生产编译

必须先构建前端 —— 前端产物通过 go:embed 打进二进制,跳过这一步会嵌入上一次的旧产物:

cd web && npm install && npm run build && cd ..
go build -ldflags="-s -w" -o sdsd cmd/sdsd/main.go
./sdsd

🐳 Docker 部署

镜像采用三阶段构建(Node 构建前端 → Go 编译二进制 → Alpine 运行), 运行时以非 root 用户执行,最终镜像里只有一个静态编译的二进制。 仓库根目录提供了 Dockerfile 与 docker-compose.yml。

用 compose 启动(推荐)

cp config.example.yaml config.yaml   # 然后务必修改 jwt.secret
mkdir -p data
docker compose up -d --build

数据落在 ./data,配置文件只读挂载进容器。

用 docker run 启动

docker build -t sdsgo:latest .

docker run -d \
  --name sdsgo \
  --restart unless-stopped \
  -p 8080:8080 \
  -v "$PWD/data:/app/data" \
  -v "$PWD/config.yaml:/app/config.yaml:ro" \
  -e TZ=Asia/Shanghai \
  -e SDS_MASTER_KEY="$(openssl rand -base64 32)" \
  sdsgo:latest

几个必须知道的点

事项 说明
数据卷必须持久化 /app/data 里同时躺着 sds.db(全部数据)和 master.key(服务商凭据的加密主密钥)。主密钥丢了,已保存的凭据就再也解不开 —— 明文只在提交表单的那一瞬间存在过。
config.yaml 必须先存在 挂载一个不存在的文件时,Docker 会在宿主机上创建一个同名空目录,应用读不出内容便静默回落到内置默认值 —— 其中包括公开的默认 jwt.secret。
改端口要同时改两处 容器内监听哪个端口由 config.yaml 的 server.port 决定,compose 里的 ports 映射要跟着改。
/app/data 的属主会被自动修正 入口脚本以 root 起步、chown 之后立刻 su-exec 降权到 uid 10001(page 默认的 bind mount 属主是 root,不修正的话 SQLite 建不出库文件)。不想让容器以 root 起步,见 compose 里 user: 那段注释。
反向代理 + 第三方登录 回调地址默认由 X-Forwarded-* 推导,生产环境建议在 config.yaml 里显式配置 server.public_base_url。

镜像自带 ca-certificates(所有云解析 API 都走 HTTPS,缺了会全线 x509 失败) 与 tzdata;健康检查走 /api/v1/health,数据库不可达时该接口返回 503, 因此它能真实反映服务可用性。stop_grace_period 设为 20s —— 应用收到 SIGTERM 后最多用 10s 等在途请求跑完,Docker 默认的 10s 正好卡在边界上。

📂 项目结构

SDSGo/
├── cmd/sdsd/            # 主入口
├── internal/
│   ├── config/          # 配置加载(YAML / JSON,含内置默认值)
│   ├── database/        # GORM + SQLite 初始化与自动迁移
│   ├── model/           # 数据模型(User / Subdomain / ProviderAccount …)
│   ├── handler/         # HTTP API(注册、登录、子域名管理、健康检查)
│   ├── middleware/      # Gin 中间件(CORS)
│   ├── service/         # 全部业务逻辑
│   ├── dns/             # 云解析服务商适配器(16 家)
│   ├── oauth/           # 第三方登录适配器
│   ├── notify/          # 邮件与短信发送
│   ├── sign/            # 各云厂商的签名实现
│   ├── secret/          # 服务商凭据的 AES-GCM 加解密
│   ├── sensitive/       # 敏感词与保留前缀
│   └── webui/           # go:embed 嵌入前端构建产物(单二进制部署)
├── web/                 # 前端(React 18 + TS + Vite + Tailwind + Zustand)
│   └── src/             # 构建产物输出到 ../internal/webui/dist
├── scripts/smoke.sh     # API 冒烟测试脚本
├── Dockerfile           # 三阶段构建:前端 → 二进制 → Alpine 运行时
├── docker-compose.yml   # 单机编排(数据卷、健康检查、优雅关闭)
├── docker-entrypoint.sh # 修正数据卷属主并降权执行
├── .dockerignore        # 构建上下文排除清单
└── config.example.yaml  # 配置文件示例

🤝 参与贡献

欢迎提交 Issue 和 Pull Request!

  1. Fork 本仓库
  2. 创建您的特性分支 (git checkout -b feature/AmazingFeature)
  3. 提交您的修改 (git commit -m 'Add some AmazingFeature')
  4. 推送到分支 (git push origin feature/AmazingFeature)
  5. 开启一个 Pull Request

📄 开源协议

本项目基于 MIT License 协议开源。