05-go-refactor-blueprint.md 11 KB

API Go 重构蓝图

本文档面向后续将 www/new_sdk/application/api 迁移到 Go 的落地设计,重点是模块边界、接口抽象、迁移顺序和风险控制。

1. 推荐目录结构

cmd/
  api/
    main.go
internal/
  bootstrap/              # 配置、DB、Redis、日志、依赖装配
  config/                 # app、game、payment、complex、third-party 配置
  http/
    router/               # 路由注册
    middleware/           # 协议解密、鉴权、会话、限流、日志
    handler/              # HTTP controller,保持薄层
    response/             # 统一响应、错误码、SDK 加密输出
  protocol/
    codec/                # AES/JSON/兼容 imeil 等字段
    sign/                 # MD5 sign、appid/key 校验
    token/                # auth_code/session token 编解码
  domain/
    account/              # 登录、注册、账号绑定、实名
    session/              # token、Redis 会话
    game/                 # 游戏、区服、角色、版本、开服表
    payment/              # 订单、支付渠道、回调、平台币
    complex/              # 联运/渠道适配器
    coupon/               # 优惠券
    gift/                 # 礼包、福利、客服中心
    mlbb/                 # MLBB 独立协议
    thirdparty/           # 对外数据 API
  repository/             # MySQL 仓储
  integration/
    payment/              # ali、wx、yunshanfu、dinpay、shande 等
    identity/             # 实名、未成年、防沉迷
    notify/               # CP 通知、钉钉告警
    dataapi/              # dy、yql 等外部接口
pkg/
  decimal/                # 金额处理封装,避免 float 误差

原则:HTTP 层只负责协议、参数和响应;业务规则沉到 domain service;MySQL/Redis 访问全部经 repository;第三方接口全部经 integration。

2. 中间件拆分

当前 controller\Api 承担了请求解密、签名、应用校验、返回加密、登录校验等多种职责。Go 版本建议拆成以下中间件:

RequestIDMiddleware
RecoverMiddleware
AccessLogMiddleware
SDKCodecMiddleware       # AES-128-ECB 解密 data、兼容 imeil、响应加密
AppAuthMiddleware        # appid、AppKey、MD5 sign 校验
SessionMiddleware        # auth_code/token 解析、Redis 会话校验
RateLimitMiddleware      # 按 appid、ip、用户或接口限流

保留兼容点:

  • 客户端参数名、路由名、错误码、加密方式先保持不变。
  • imeil 这类历史字段不要迁移时强行改名,可在 Go 内部归一成 imei
  • 未登录接口与需登录接口要由路由显式声明,不再依赖控制器父类隐式判断。

3. 核心接口设计

3.1 SDK 协议

type Codec interface {
    DecodeRequest(ctx context.Context, req *http.Request) (*SDKRequest, error)
    EncodeResponse(ctx context.Context, app AppConfig, body any) (*SDKResponse, error)
}

type Signer interface {
    Verify(params map[string]string, appKey string, sign string) error
}

type SessionVerifier interface {
    Verify(ctx context.Context, authCode string) (*Session, error)
    Issue(ctx context.Context, userID int64, appID string) (*Session, error)
    Revoke(ctx context.Context, authCode string) error
}

3.2 账号域

type AccountService interface {
    Login(ctx context.Context, cmd LoginCommand) (*LoginResult, error)
    Register(ctx context.Context, cmd RegisterCommand) (*RegisterResult, error)
    QuickRegister(ctx context.Context, cmd QuickRegisterCommand) (*RegisterResult, error)
    BindAccount(ctx context.Context, cmd BindAccountCommand) error
    ChangePassword(ctx context.Context, cmd ChangePasswordCommand) error
    SubmitIdentity(ctx context.Context, cmd IdentityCommand) (*IdentityResult, error)
}

账号迁移要特别关注同一用户在 MemberUser、token、实名、防沉迷、渠道用户标识之间的映射关系。

3.3 支付域

type PaymentProvider interface {
    CreatePayment(ctx context.Context, order PaymentOrder) (*PaymentIntent, error)
    VerifyNotify(ctx context.Context, req NotifyRequest) (*NormalizedNotify, error)
    SuccessResponse() HTTPResponse
    FailResponse(reason string) HTTPResponse
}

type OrderService interface {
    CreateGameOrder(ctx context.Context, cmd CreateOrderCommand) (*PaymentOrder, error)
    MarkPaid(ctx context.Context, notify NormalizedNotify) (*PaymentOrder, error)
    Cancel(ctx context.Context, orderNo string) error
    GetOrder(ctx context.Context, orderNo string) (*PaymentOrder, error)
    ListOrders(ctx context.Context, query OrderQuery) ([]PaymentOrder, error)
}

支付状态机建议固定为:

created -> paying -> paid -> delivering -> delivered
created -> canceled
paid/delivering -> deliver_failed -> delivering -> delivered

回调处理必须满足:

  • order_no 或第三方交易号做幂等。
  • 金额用整数分或 decimal,禁止使用 float 做最终判断。
  • 更新订单状态、发放资产、通知 CP 要么在一个事务里完成,要么使用事务加 outbox。
  • CP 通知失败不能导致第三方支付回调一直失败,应单独重试和告警。

3.4 联运渠道域

type ComplexAdapter interface {
    Channel() string
    CheckLogin(ctx context.Context, cmd ComplexLoginCommand, cfg ComplexGameConfig) (*ComplexLoginResult, error)
    VerifyPayNotify(ctx context.Context, req ComplexNotifyRequest, cfg ComplexGameConfig) (*NormalizedComplexNotify, error)
    SuccessResponse() HTTPResponse
    FailResponse(reason string) HTTPResponse
}

type SpecialParamProvider interface {
    SpecialParams(ctx context.Context, cmd SpecialParamCommand, cfg ComplexGameConfig) (map[string]any, error)
}

type H5PayVerifier interface {
    VerifyH5Pay(ctx context.Context, req ComplexH5PayRequest, cfg ComplexGameConfig) error
}

每个 complex/*.php 对应一个 Go adapter,先迁移高流量渠道。渠道差异只允许存在 adapter 内,业务层接收统一后的 NormalizedComplexNotify

4. 模块边界

Go 模块 承接现有逻辑 迁移重点
protocol controller\Api、签名、AES、响应格式 兼容老 SDK
account UserV1\User、实名相关控制器 token、实名、防沉迷
game GameRoleServerVersionOpenServer 游戏、区服、角色缓存
payment PayPayNotifyGamePayServicePayService 订单状态、幂等、金额
complex ComplexPayNotifyComplexcomplex/* 渠道适配器抽象
coupon CouponMemberCouponService 领取、使用、过期
gift GiftWelfareService 礼包码、福利、客服
mlbb api/controller/mlbb/*service/mlbb/* 独立路由和验签
thirdparty Third, DataApi, YqlApi, DyApi 外部数据接口隔离

5. 迁移阶段

阶段 0:确认流量与契约

  • 统计当前线上实际调用接口、渠道回调、支付渠道占比。
  • 为 AES、sign、token、登录、支付回调准备 golden case。
  • 将敏感配置从代码中迁出,统一进入环境变量或配置中心。

阶段 1:协议层先行

  • 在 Go 中实现 SDKCodecMiddlewareAppAuthMiddleware、统一错误码。
  • 使用历史请求样本做加解密、验签、响应格式测试。
  • 先不改业务,仅保证 Go 能读懂老 SDK 请求。

阶段 2:只读和低风险接口

  • 迁移版本、开服表、游戏信息、礼包列表、订单查询等低风险接口。
  • 通过网关或 Nginx 按接口灰度转发。
  • 开启新旧响应对比日志,确认字段兼容。

阶段 3:账号与会话

  • 迁移登录、注册、token 校验、实名接口。
  • Redis key 先兼容旧格式,后续再逐步改成 Go 命名规范。
  • 账号写入接口需要有回滚方案和完整审计日志。

阶段 4:支付核心

  • 先迁移订单创建,再迁移支付回调。
  • 每个支付渠道单独上线,保留 PHP 回调兜底。
  • 订单状态机和幂等逻辑必须在 Go 侧统一,禁止各渠道自行更新状态。

阶段 5:联运渠道

  • 选取 3 到 5 个高流量 complex 渠道优先迁移。
  • 每个渠道建立登录和支付回调契约测试。
  • adapter 上线后保留 PHP fallback,观察回调成功率和金额差异。

阶段 6:收敛与下线

  • 完成全量接口迁移后,冻结 PHP 新需求。
  • 清理重复 Redis key、废弃支付渠道、无流量接口。
  • 将 API 文档、错误码、渠道协议沉淀为 Go 项目内的 contract tests。

6. 测试策略

测试类型 重点
Golden tests AES、sign、auth_code、响应格式、历史字段兼容
Unit tests 账号规则、订单状态机、金额计算、渠道验签
Repository tests MySQL 查询、事务、唯一键、分页
Integration tests Redis 会话、支付 provider、实名 provider、CP 通知
Contract tests 每个 complex adapter 的登录与支付回调样本
Shadow tests 新旧接口响应对比,只记录不影响线上

支付和联运回调不建议直接双写。更稳妥的方式是先做回调样本重放和只读影子验证,确认后再灰度切流。

7. 运维与可观测性

  • 日志字段固定包含 request_idappiduser_idgame_idorder_nochannelroute
  • 支付回调、CP 通知、实名失败、渠道登录失败要有独立指标。
  • CP 通知使用队列或 outbox 重试,失败进入告警。
  • 第三方接口要设置超时、重试上限、熔断和降级响应。
  • 管理敏感配置时区分普通配置和 secret,代码仓库不再保存私钥、商户密钥、回调 token。

8. Go 技术建议

  • Web 框架可选 Gin、Chi 或 Hertz;如果团队偏简单稳定,优先 Chi/Gin。
  • ORM 可选 GORM 或 SQLBoiler;支付和订单建议显式事务,避免隐藏更新。
  • Redis 使用 go-redis。
  • 金额使用整数分或 shopspring/decimal。
  • 配置使用 Viper 或自研轻量 loader,但 secret 必须从环境变量或配置中心读取。
  • 错误码用枚举和集中映射,不在 handler 中散落字符串。

9. 优先级建议

优先级 内容 原因
P0 协议层、错误码、配置和日志 所有接口依赖
P0 支付订单状态机和幂等模型 风险最高
P1 登录、注册、会话、实名 核心用户链路
P1 高流量支付渠道 直接影响收入
P2 高流量 complex adapter 联运差异大
P2 游戏、礼包、优惠券、客服 业务完整性
P3 低流量 Third/Data API 可按需迁移

10. 落地判断

这个项目适合迁移到 Go,但不适合一次性重写。推荐采用“协议兼容 + 分域迁移 + 支付谨慎灰度”的方式推进。最关键的不是框架选择,而是把当前隐含在控制器、service、complex adapter 里的规则变成明确的接口、状态机、契约测试和可观测指标。