new-api/docs/superpowers/specs/2026-05-20-运营能力补全-审阅版.md
zizi 059512f6ef docs: add operations capability implementation plan
Document the six-module operations roadmap and executable task breakdown for promo codes, announcements, channel monitoring, rebates, concurrency controls, and domestic payments.
2026-05-20 12:57:40 +08:00

29 KiB
Raw Blame History

new-api 运营能力补全 — 6 模块设计方案

日期2026-05-20 状态:待审阅


概览

实施顺序

# 模块 优先级 复杂度 预计工时
1 优惠码 P0 1-2d
2 公告系统 P1 2-3d
3 渠道监控 P1 2-3d
4 邀请返利 P2 2-3d
5 账号并发 P2 2-3d
6 国内支付 P0 3-5d

技术栈

  • Go 1.25 + Gin + GORM
  • 数据库MySQL/PostgreSQL/SQLite 三兼容
  • 缓存Redis
  • 参考实现sub2api (/Users/qingse/Sync/local-git/sub2api/)

模块 1优惠码Promo Code

背景

new-api 现状有兑换码Redemption系统支持额度兑换和订阅兑换也有充值金额折扣阶梯AmountDiscount但无独立优惠码系统。运营需要「充值/订阅时输入优惠码获得额外赠送」的能力。

目标与非目标

目标

  • 管理员创建/管理优惠码(固定加赠额度)
  • 用户充值或订阅时输入优惠码,校验通过后获得额外额度加赠
  • 每人每码仅可用一次,支持使用次数上限和过期时间
  • 可设最低充值/消费门槛

非目标

  • 不实现百分比折扣(本次只做固定加赠)
  • 不实现满减阶梯
  • 不做优惠码和现有兑换码的合并

数据模型

promo_codes新表

字段 类型 约束 说明
id uint PK, AUTO_INCREMENT
code varchar(32) UNIQUE, NOT NULL 优惠码,大写
bonus_amount int NOT NULL, DEFAULT 0 加赠额度(美元分)
max_uses int NOT NULL, DEFAULT 0 最大使用次数0=不限制
used_count int NOT NULL, DEFAULT 0 已使用次数
status tinyint NOT NULL, DEFAULT 1 1=启用, 0=禁用
min_recharge_amount int NOT NULL, DEFAULT 0 最低充值/消费门槛美元分0=无门槛
expires_at timestamp NULL 过期时间NULL=永不过期
notes varchar(255) DEFAULT '' 内部备注
created_at timestamp NOT NULL
updated_at timestamp NOT NULL

promo_code_usages新表

字段 类型 约束 说明
id uint PK, AUTO_INCREMENT
promo_code_id int FK, NOT NULL 关联 promo_codes
user_id int FK, NOT NULL 关联 users
used_at timestamp NOT NULL 使用时间

唯一索引:(promo_code_id, user_id)

API

管理端

GET    /api/admin/promo-codes              # 列表(分页,支持搜索、状态筛选)
POST   /api/admin/promo-codes              # 创建
GET    /api/admin/promo-codes/:id          # 详情
PUT    /api/admin/promo-codes/:id          # 编辑(已启用且有使用的码不可改 bonus_amount
DELETE /api/admin/promo-codes/:id          # 删除(软删除或仅禁用)
GET    /api/admin/promo-codes/:id/usages   # 使用记录分页

用户端

POST   /api/promo-codes/validate           # 校验优惠码

请求:

{
  "code": "SUMMER2024",
  "amount": 1000   // 计划充值/消费金额(美元分),用于校验最低门槛
}

响应(有效):

{
  "valid": true,
  "promo_code_id": 1,
  "bonus_amount": 200,
  "code": "SUMMER2024"
}

响应(无效):

{
  "valid": false,
  "reason": "expired"  // expired / disabled / max_uses_reached / already_used / below_min_recharge
}

业务流程

充值页 → 用户输入码 → POST /validate → 
  有效:展示"加赠 $2.00" → 用户确认支付 →
  支付成功回调 → Recharge() 中:
    1. 执行业务充值(加额度)
    2. 查询已校验的 promo_code
    3. 检查 (promo_code_id, user_id) 未使用
    4. 增加 bonus_amount 额度
    5. 记录 usage + 更新 used_count
    6. 记录日志 Log{Type: "topup", Content: "promo_code:SUMMER2024 bonus:200"}

和现有兑换码的关系

兑换码 Redemption 优惠码 PromoCode
触发方式 独立兑换(POST /api/redemption 支付时附带
类型 额度/订阅 固定加赠
依赖 不依赖支付 依赖支付订单完成
redemptions promo_codes + promo_code_usages

互不影响,独立运作。

文件变更清单

文件 操作 说明
model/promo_code.go 新增 GORM 模型 + 表创建 + CRUD 方法
model/promo_code_usage.go 新增 GORM 模型
controller/promo_code.go 新增 管理端 handler
controller/promo_code_user.go 新增 用户端 validate handler
router/admin.go 修改 加路由
router/api.go 修改 加路由
service/topup.goservice/payment.go 修改 Recharge 完成时处理优惠码加赠

验证方式

  1. 创建优惠码 → GET 列表可见
  2. 用户 A 使用优惠码充值 → 额度增加含 bonus → usages 有记录
  3. 用户 A 再次使用同一码 → /validate 返回 already_used
  4. 达到 max_uses 后 → /validate 返回 max_uses_reached
  5. 过期后 → /validate 返回 expired
  6. 充值金额不满足门槛 → /validate 返回 below_min_recharge
  7. MySQL / PostgreSQL / SQLite 三库建表通过

模块 2公告系统

背景

new-api 现状:公告以 JSON 字符串存在 ConsoleSetting.Announcements 字段,通过 /api/status 下发。无已读追踪,无状态管理,无定时发布。运营需要更精细的公告管理能力。

目标与非目标

目标

  • 独立 announcements 表,支持 Markdown 内容
  • 状态机draft → active → archived
  • 已读追踪(announcement_reads
  • 定时发布starts_at / ends_at
  • 弹窗/静默两种通知模式
  • json_rules JSON 字段,预留定向规则

非目标

  • 不做复杂的规则引擎(定向规则暂不走解析,存 JSON 备用)
  • 不做站内通知中心 (in-app notification center)
  • 不删除旧 JSON 存储方式(兼容期)

数据模型

announcements新表

字段 类型 约束 说明
id uint PK
title varchar(255) NOT NULL 标题
content text NOT NULL 内容Markdown
content_html text 渲染后的 HTML服务端预渲染
status varchar(16) NOT NULL, DEFAULT 'draft' draft / active / archived
notify_mode varchar(16) NOT NULL, DEFAULT 'silent' silent / popup
json_rules text JSON 字符串,定向展示规则(预留)
starts_at timestamp NULL 开始展示时间
ends_at timestamp NULL 结束展示时间
created_at timestamp NOT NULL
updated_at timestamp NOT NULL
created_by int 创建人 user_id

announcement_reads新表

字段 类型 约束 说明
id uint PK
announcement_id int FK, NOT NULL
user_id int FK, NOT NULL
read_at timestamp NOT NULL 首次阅读时间

唯一索引:(announcement_id, user_id)

API

管理端

GET    /api/admin/announcements                          # 列表(分页,状态筛选)
POST   /api/admin/announcements                          # 创建
GET    /api/admin/announcements/:id                      # 详情
PUT    /api/admin/announcements/:id                      # 编辑
DELETE /api/admin/announcements/:id                      # 删除
GET    /api/admin/announcements/:id/read-status           # 已读/未读用户列表

用户端

GET    /api/announcements                                # 当前可见公告status=active, 在时间范围内)
GET    /api/announcements?unread=true                    # 仅未读
POST   /api/announcements/:id/read                       # 标记已读

路由兼容

/api/status 中同步返回公告(从新表查 active 公告),保持现有前端兼容。用 ConsoleSetting.AnnouncementsEnabled 总开关。

文件变更清单

文件 操作 说明
model/announcement.go 新增 GORM 模型 + 表创建 + CRUD
model/announcement_read.go 新增 GORM 模型
controller/announcement.go 新增 管理端 handler
controller/announcement_user.go 新增 用户端 handler
controller/misc.go 修改 /api/status 公告来源切换到新表
router/admin.go 修改 加管理端路由
router/api.go 修改 加用户端路由

验证方式

  1. 创建公告(draft) → 用户端 GET 不可见
  2. 状态改为 active → 用户端可见
  3. 用户 A 标记已读 → announcement_reads 有记录
  4. 用户 A GET ?unread=true → 该公告不再出现
  5. starts_at 未来时间 → 未到时间不可见
  6. ends_at 过去 → 不可见
  7. 状态改为 archived → 不可见
  8. notify_mode=popup → 前端根据该字段弹出(前端配合)

模块 3渠道监控

背景

new-api 现状:MonitorSetting 有 auto-test-channel 和 auto-disable-channel 功能,但检查结果无独立存储,无历史查询,无请求模板,无日报汇总。运维需要可追溯的监控数据。

目标与非目标

目标

  • channel_monitors 表管理监控配置(多模型、间隔、超时、请求模板)
  • channel_monitor_history 表记录每次检查结果
  • channel_monitor_daily_rollup 日报汇总(各模型可用率)
  • 支持自定义请求模板列表(用户可配,内置 20 个兜底)
  • admin CRUD + 手动触发检查 + 历史查询
  • SSRF 校验(禁止内网地址)

非目标

  • 不替换现有 auto-test / auto-disable 机制(共存,后续可合并)
  • 不做实时告警推送(只做数据采集和展示,告警后续加)
  • 用户端只读(不需要用户创建 monitor

数据模型

channel_monitors新表

字段 类型 约束 说明
id uint PK
name varchar(128) NOT NULL 监控名称
channel_id int FK 关联 channels0=不限
target_url varchar(512) NOT NULL 目标 URL
primary_model varchar(64) NOT NULL 主测模型
extra_models text JSON 数组,额外测试模型
check_interval int NOT NULL, DEFAULT 300 检查间隔15-3600
timeout int NOT NULL, DEFAULT 30 超时(秒)
request_templates text JSON 数组,自定义请求模板
custom_headers text JSON 对象,自定义请求头
custom_body text 自定义请求体raw string
status tinyint NOT NULL, DEFAULT 1 1=启用, 0=禁用
created_at / updated_at timestamp

channel_monitor_history新表

字段 类型 约束 说明
id uint PK
monitor_id int FK, NOT NULL
channel_id int FK
model_name varchar(64) NOT NULL 测试的模型
status varchar(16) NOT NULL pass / degraded / failed / error
status_code int HTTP 状态码
latency_ms int 响应延迟(毫秒)
error_message text 错误信息
request_body text 发送的请求内容
response_snippet text 响应片段(前 500 字符)
checked_at timestamp NOT NULL

数据保留:原始记录保留 7 天(定时清理 job

channel_monitor_daily_rollup新表

字段 类型 约束 说明
id uint PK
monitor_id int FK, NOT NULL
channel_id int FK
model_name varchar(64) NOT NULL
date date NOT NULL 汇总日期
total_checks int NOT NULL, DEFAULT 0 检查总数
pass_count int NOT NULL, DEFAULT 0 通过次数
degraded_count int NOT NULL, DEFAULT 0 降级次数
failed_count int NOT NULL, DEFAULT 0 失败次数
avg_latency_ms int 平均延迟

唯一索引:(monitor_id, channel_id, model_name, date)

API

管理端

GET    /api/admin/channel-monitors                    # 列表
POST   /api/admin/channel-monitors                    # 创建
GET    /api/admin/channel-monitors/:id                # 详情
PUT    /api/admin/channel-monitors/:id                # 编辑
DELETE /api/admin/channel-monitors/:id                # 删除
POST   /api/admin/channel-monitors/:id/run            # 手动触发一次检查
GET    /api/admin/channel-monitors/:id/history        # 历史记录(按时间范围、模型筛选)
GET    /api/admin/channel-monitors/:id/rollup         # 日报(按日期范围)

用户端

GET    /api/channel-monitors/:id/status                # 公开状态页(可选)

内置请求模板

内置 20 个简短英文问题覆盖不同主题general、coding、math、translation、science每次检查随机抽取 1 个。用户可以自定义模板列表覆盖默认。

示例4/20

"Explain what a binary tree is in one sentence."
"Write a simple Python function to check if a number is prime."
"What is 15 percent of 200? Return only the number."
"Translate 'Hello, how are you?' into French."

Runner 设计

后台 goroutine每分钟扫描启用的 monitor检查各 monitor 的 last_checked_at + check_interval < now() 则触发检查。检查流程:

  1. 随机选一个请求模板
  2. 构造 HTTP 请求 → 发送到 target_url
  3. SSRF 校验(禁止内网 IP
  4. 记录 latency_ms、status_code
  5. 判断状态pass200, latency < 阈值) / degraded200, latency >= 阈值) / failed非 200 或无有效响应) / error连接失败
  6. 写入 history
  7. 每小时汇总一次到 daily_rollup

文件变更清单

文件 操作 说明
model/channel_monitor.go 新增 monitor 模型
model/channel_monitor_history.go 新增 history 模型
model/channel_monitor_daily_rollup.go 新增 rollup 模型
service/channel_monitor_runner.go 新增 后台 runner
service/channel_monitor_checker.go 新增 单次检查逻辑
service/channel_monitor_ssrf.go 新增 SSRF 校验
controller/channel_monitor.go 新增 管理端 handler
router/admin.go 修改 加路由

验证方式

  1. 创建 monitor → GET 列表可见
  2. POST /run → history 有新记录 → 状态正确
  3. 等一个间隔周期 → 自动触发检查 → history 有新记录
  4. 检查 daily_rollup 定时汇总正确
  5. SSRFtarget_url 指向 127.0.0.1 → 检查失败且记录 error
  6. 模型不可用 → 状态为 failed
  7. 自定义模板列表生效 → 请求体使用自定义模板内容

模块 4邀请返利增强

背景

new-api 现状有基础邀请系统:AffCode(邀请码)、AffCount(邀请数)、AffQuota(待提取返利额度)、AffHistoryQuota(累计返利)、InviterId(邀请人 ID。返利逻辑在 model/user.goInsert()inviteUser() 中,使用全局 QuotaForInviter/QuotaForInvitee 常量。

缺失

  • 无独立返利记录表(只有 Log 表 Type=system 的日志)
  • 无从用户级返利比例覆盖
  • 无冻结期机制
  • 无管理面板

目标与非目标

目标

  • rebate_records 表:记录每次返利发生
  • 全局返利比例 + per-user 返利比例覆盖
  • 冻结期机制:返利入账后冻结 N 天,解冻后才可提取
  • 管理面板:邀请概览、邀请记录、返利记录、转账记录、批量改比例

非目标

  • 不改变现有邀请注册逻辑AffCode 生成、InviterId 绑定)
  • 不做多级分销(只做一级)
  • 不做返利自动转入余额(保持用户手动 TransferAffQuotaToQuota

数据模型

现有字段增强users 表)

新增字段:

字段 类型 说明
aff_rebate_rate_percent int per-user 返利比例覆盖百分比0=使用全局默认
aff_rebate_frozen_days int per-user 冻结期覆盖0=使用全局默认

rebate_records新表

字段 类型 约束 说明
id uint PK
inviter_id int FK, NOT NULL 邀请人
invitee_id int FK, NOT NULL 被邀请人
order_id int FK 关联 top_ups 或 subscription_orders
order_type varchar(16) NOT NULL topup / subscription
order_amount int NOT NULL 订单金额(美元分)
rebate_amount int NOT NULL 返利金额(美元分)
rate_percent int NOT NULL 使用的返利比例
status varchar(16) NOT NULL, DEFAULT 'frozen' frozen / released
frozen_until timestamp 冻结到期时间
released_at timestamp 解冻时间
created_at timestamp NOT NULL

全局配置option 表)

Key 说明
AffRebateRatePercent 全局默认返利比例(百分比)
AffRebateFrozenDays 全局默认冻结天数
AffRebateEnabled 返利总开关

API

管理端

GET    /api/admin/affiliates/overview                     # 概览:总邀请数、返利总额、待解冻总额
GET    /api/admin/affiliates/invites                       # 邀请记录(分页,按时间/用户筛选)
GET    /api/admin/affiliates/rebates                       # 返利记录(分页,按状态/时间/用户筛选)
GET    /api/admin/affiliates/transfers                     # 转账/提取记录
GET    /api/admin/affiliates/users                         # 用户返利设置列表
GET    /api/admin/affiliates/users/:user_id/overview       # 单用户概览
PUT    /api/admin/affiliates/users/:user_id                # 更新单用户返利比例/冻结期
POST   /api/admin/affiliates/users/batch-rate              # 批量更新返利比例
DELETE /api/admin/affiliates/users/:user_id                # 清除用户特殊设置(回退到全局默认)
PUT    /api/admin/affiliates/config                        # 更新全局配置

返利流程变更

现有 inviteUser() 逻辑增强:

inviteUser(inviterId, invitee):
  1. inviter.AffCount += 1
  2. invitee.Quota += QuotaForInvitee  // 不变
  3. 不在注册时给返利,改为在 invitee 首次充值完成后触发

onRechargeComplete(inviteeUser):
  如果 inviteeUser.InviterId > 0 且是首次充值:
    rate = inviteeUser.Inviter.AffRebateRatePercent > 0
      ? inviteeUser.Inviter.AffRebateRatePercent
      : Option.AffRebateRatePercent
    rebate = orderAmount * rate / 100
    frozenDays = inviteeUser.Inviter.AffRebateFrozenDays > 0
      ? inviteeUser.Inviter.AffRebateFrozenDays
      : Option.AffRebateFrozenDays

    inviter.AffQuota += rebate      // 入账到冻结额度
    inviter.AffHistoryQuota += rebate

    insert rebate_record {
      status: frozenDays > 0 ? 'frozen' : 'released',
      frozen_until: now + frozenDays
    }

定时任务每小时releaseExpiredRebates()
  扫描 status=frozen AND frozen_until <= now()
  → status=released, released_at=now
  → inviter.AffQuota 不变(已在入账时加过),用户可正常提取

文件变更清单

文件 操作 说明
model/rebate_record.go 新增 返利记录模型
model/user.go 修改 加 aff_rebate_rate_percent / aff_rebate_frozen_days 字段
model/option.go 修改 加 AffRebateRatePercent / AffRebateFrozenDays / AffRebateEnabled
service/rebate.go 新增 返利计算、冻结/解冻逻辑
controller/affiliate.go 新增 管理端 handler
controller/topup_*.go 修改 Recharge 完成时触发 rebate 检查
router/admin.go 修改 加路由

验证方式

  1. 用户 A 邀请用户 B → B 注册后 InviterId=A
  2. B 首次充值 → rebate_records 有新记录 → 状态 frozen
  3. A 的 AffQuota 增加对应金额
  4. 冻结到期 → 状态变 released
  5. A 手动提取 → AffQuota 减少 + 额度增加
  6. 修改 A 的返利比例为 X% → B 下次充值按 X% 返利
  7. 批量更新接口修改 10 个用户返利比例 → 全部生效
  8. 关闭返利开关 → 充值不触发返利

模块 5账号并发控制

背景

new-api 现状有:全局 API 限流、模型级限流、channel polling 锁。但没有 per-user 或 per-upstream-account 的并发控制。每个上游 API Key 有其并发上限,超过后要么排队要么拒绝。

目标与非目标

目标

  • per-user 并发限制(可配,默认不限制)
  • per-upstream-accountchannel key并发槽位管理
  • Redis 原子计数实现
  • 超时等待队列(可选,不直接拒绝)
  • 管理面板实时并发监控
  • admin 可调整单个用户的并发数

非目标

  • 不替换现有 rate-limit 体系(并发和速率是两个维度)
  • 不做模型级并发隔离(先用 channel key 粒度)
  • 不做跨节点排队(单节点等待队列即可)

数据模型

users 表新增字段

字段 类型 默认值 说明
concurrency_limit int 0 per-user 最大并发数0=不限制
rpm_limit int 0 per-user 每分钟最大请求数0=不限制

channels 表新增字段

字段 类型 默认值 说明
concurrency_limit int 0 该 channel key 最大并发数0=不限制

Redis 数据结构

user:concurrency:{user_id}        → int (当前并发数)
channel:concurrency:{channel_id}  → int (当前并发数)
user:rpm:{user_id}:{minute}       → int (当前分钟请求数)

流程

relay 请求 → middleware:
  1. user_concurrency_limit > 0:
    INCR user:concurrency:{user_id}
    若 > limit → DECR → 返回 429或进入等待队列
  2. rpm_limit > 0:
    INCR user:rpm:{user_id}:{current_minute}
    若 > limit → 返回 429
  3. 选择 channel → channel.concurrent_limit > 0:
    INCR channel:concurrency:{channel_id}
    若 > limit → DECR → 换 channel 或 429
  4. relay 完成(成功或失败)→ defer DECR

等待队列(可选):
  若希望排队而非直接拒绝:
  - 用 Redis BLPOP 等待,设置 timeout如 30s
  - 获到槽位后执行

API

管理端

GET    /api/admin/ops/concurrency               # 全局实时并发 snapshot
GET    /api/admin/ops/concurrency/users          # per-user 当前并发排行
GET    /api/admin/ops/concurrency/channels       # per-channel 当前并发
PUT    /api/admin/users/:id/concurrency          # 修改单用户并发/RPM 限制

文件变更清单

文件 操作 说明
model/user.go 修改 加 concurrency_limit / rpm_limit
model/channel.go 修改 加 concurrency_limit
service/concurrency.go 新增 Redis 并发槽位管理
middleware/concurrency.go 新增 relay 前并发检查
controller/ops_concurrency.go 新增 管理端 handler
router/relay.gorouter/api.go 修改 relay 路由注入 middleware
router/admin.go 修改 加管理路由

验证方式

  1. 设用户 A concurrency_limit=2 → 同时发 3 个请求 → 第 3 个 429
  2. 设 channel concurrency_limit=5 → 同时发 6 个 → 第 6 个换 channel 或 429
  3. GET concurrency snapshot → 显示当前各用户/channel 并发数
  4. 请求完成后 → Redis 计数器自动 DECR
  5. 设 rpm_limit=10 → 1 分钟内 11 个请求 → 第 11 个 429
  6. Redis 宕机 → 降级跳过并发检查(不阻塞)
  7. concurrency_limit=0 → 不检查

模块 6国内支付直连

背景

new-api 现有支付Epay易支付聚合、Stripe、Creem、Waffo。都是国际渠道或聚合渠道没有支付宝/微信直连。需要新增直连接入以降低支付费率、扩展国内用户群。

目标与非目标

目标

  • 统一 Provider 接口抽象
  • 支付宝直连WAP/PC 支付)
  • 微信直连Native/H5/JSAPI
  • Webhook 回调验签 + 订单完成
  • 新渠道用 Provider 接口,老渠道不动

非目标

  • 不重构 Stripe/Creem/Waffo/Epay 到 Provider 接口
  • 不做 EasyPay 聚合(后续再加)
  • 不做分账、退款(第一期只做支付)

支付 Provider 接口设计

// payment/types.go

type PaymentRequest struct {
    OrderID     string
    Amount      int    // 美元分
    Currency    string // USD / CNY
    Description string
    ReturnURL   string // 支付完成跳转
    NotifyURL   string // webhook 地址
    ClientIP    string
    Metadata    map[string]string
    // WxPay specific
    TradeType   string // NATIVE / H5 / JSAPI
    OpenID      string // JSAPI 必填
}

type PaymentResponse struct {
    ProviderOrderID string // 上游订单号
    PaymentURL      string // 用户跳转支付 URLH5/WAP
    QRCode          string // 扫码支付 URLNative
    RawResponse     string // 上游原始响应
}

type OrderStatus struct {
    ProviderOrderID string
    Status          string // pending / success / failed / closed
    Amount          int
    Currency        string
    PaidAt          *time.Time
}

type Provider interface {
    Name() string
    CreatePayment(ctx context.Context, req *PaymentRequest) (*PaymentResponse, error)
    QueryOrder(ctx context.Context, providerOrderID string) (*OrderStatus, error)
    VerifyNotification(ctx context.Context, body []byte, headers map[string]string) (*OrderStatus, error)
    // Refund 第一期空实现
    Refund(ctx context.Context, providerOrderID string, amount int) error
}

支付宝直连

配置项(setting/payment_alipay.go

  • AppID
  • 应用私钥 (PKCS8)
  • 支付宝公钥
  • 签约的产品码 (FAST_INSTANT_TRADE_PAY)
  • 支付模式WAP / PC

流程:

POST /api/payment/alipay/create
  → 签名参数
  → 返回支付宝支付 URL
  → 用户跳转 → 支付宝页面 → 支付完成
  → 支付宝 POST notify → VerifyNotification() 验签
  → 订单完成 → 加额度

验签逻辑:支付宝 SDK 提供 RSA 签名验证。

微信直连

配置项(setting/payment_wxpay.go

  • AppID
  • MchID (商户号)
  • APIv3 Key
  • 商户私钥 (PEM)
  • 商户证书序列号
  • 支付模式NATIVE / H5 / JSAPI

流程:

POST /api/payment/wxpay/create
  → 调用微信统一下单 APIv3
  → NATIVE: 返回 code_url (二维码)
  → H5: 返回 h5_url (跳转链接)
  → 用户支付 → 微信 POST notify → VerifyNotification() 验签
  → 订单完成 → 加额度

验签逻辑:微信 APIv3 使用平台证书公钥验证回调签名,回调体是加密 JSON需先解密再验签。

数据库

复现现有 top_ups 表,字段已够用:

  • payment_method: 新增值 alipay_direct, wxpay_direct
  • payment_provider: 沿用 trade_no 存上游订单号
  • 可能需在 top_ups 加 provider_payload JSON 字段存上游原始响应(和 subscription_orders 对齐)

API

新 Provider 风格的支付路由:

POST   /api/payment/alipay/create          # 创建支付宝订单
GET    /api/payment/alipay/return           # 支付宝同步回调(跳转)
POST   /api/payment/webhook/alipay         # 支付宝异步通知
POST   /api/payment/webhook/wxpay          # 微信异步通知
POST   /api/payment/wxpay/create           # 创建微信订单

文件变更清单

文件 操作 说明
payment/types.go 新增 Provider 接口 + 请求/响应类型
payment/provider/alipay.go 新增 支付宝 Provider 实现
payment/provider/wxpay.go 新增 微信 Provider 实现
payment/crypto.go 新增 配置敏感字段 AES 加解密
setting/payment_alipay.go 新增 支付宝配置项
setting/payment_wxpay.go 新增 微信配置项
controller/payment_alipay.go 新增 支付宝 handler
controller/payment_wxpay.go 新增 微信 handler
model/option.go 修改 加配置 key 注册
model/topup.go 修改 加 provider_payload 字段
router/api.go 修改 加支付路由
go.mod 修改 加支付宝/微信 SDK 依赖

验证方式

  1. 配置支付宝沙箱 → 创建订单 → 收到支付 URL → 沙箱支付 → webhook 回调 → 额度增加
  2. 配置微信沙箱 → 创建 Native 订单 → 生成二维码 → 扫码支付 → webhook 回调 → 额度增加
  3. 错误私钥 → 创建订单失败 → 不 crash
  4. 伪造 webhook → 验签失败 → 拒绝
  5. 重复 webhook → 幂等处理top_ups 状态已 complete 则直接返回成功)
  6. 金额不一致 → 不完成订单

附录 A跨模块关注点

数据库兼容

所有新表使用 GORM AutoMigrate不手写 SQL DDL确保 MySQL/PostgreSQL/SQLite 三库兼容。

注意:

  • JSON 字段MySQL json / PostgreSQL jsonb / SQLite text,用 GORM type:text + serializer:json 统一
  • 唯一索引:迁移时检查存在再创建
  • 外键SQLite 默认不启用,用 GORM tag 但不在代码层强制执行

路由冲突

6 个模块不共享路由前缀不会冲突。admin 路由统一加在 router/admin.go

前端

本次设计只覆盖后端 API。前端React web/default/需要对应页面但不在此文档范围——API 设计已确保前端可直接对接。

测试策略

每个模块:

  1. 单元测试model CRUD + service 核心逻辑
  2. 集成测试controller → service → model 全链路
  3. 手动验证:按各模块验证方式执行

附录 B风险与兼容性

风险 影响 缓解
新表迁移失败 部分模块不可用 所有模块独立开关,回滚单模块不影响其他
返利逻辑改动影响现有充值 充值中断 Recharge 中 rebate 逻辑用 defer recover 包住
支付 SDK 引入体积大 构建变大 仅模块 6 引入,不影响前 5 个模块
并发控制 Redis 故障 请求被拒 Redis 不可用时跳过并发检查
JSON 存储和独立表共存 公告/优惠码新旧数据不一致 模块 2 新表优先,旧 JSON 兼容读