- Go 69.6%
- TypeScript 26.2%
- Shell 3.3%
- CSS 0.3%
- Dockerfile 0.3%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
签到
- 奖励改为闭区间 [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,契约校验通过,浏览器实测下单→确认收款→权益与到期时间→
远端丢失→标记缺失→重建恢复的完整链路。
|
||
| cmd/sdsd | ||
| internal | ||
| scripts | ||
| web | ||
| .dockerignore | ||
| .gitattributes | ||
| .gitignore | ||
| CLAUDE.md | ||
| config.example.yaml | ||
| docker-compose.yml | ||
| docker-entrypoint.sh | ||
| Dockerfile | ||
| go.mod | ||
| go.sum | ||
| LICENSE | ||
| README.md | ||
SDS (Subdomain Distribution System)
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!
- Fork 本仓库
- 创建您的特性分支 (
git checkout -b feature/AmazingFeature) - 提交您的修改 (
git commit -m 'Add some AmazingFeature') - 推送到分支 (
git push origin feature/AmazingFeature) - 开启一个 Pull Request
📄 开源协议
本项目基于 MIT License 协议开源。