Files
junhong_cmp_fiber/.sisyphus/notepads/tech-debt-cleanup/learnings.md

230 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# tech-debt-cleanup 学习记录
## [2026-04-14] 初始化
### 项目规范关键点
- 所有注释必须使用中文
- Redis Key 必须通过函数生成(格式:`Redis{Module}{Purpose}Key(params...)`
- 错误码必须定义在 `pkg/errors/codes.go`
- 常量定义在 `pkg/constants/`
- 架构Handler → Service → Store → Model
- 禁止使用外键约束
- 禁止 Go 测试文件(*_test.go
### 关键文件路径
- 支付服务: `internal/service/order/service.go`, `internal/service/recharge/service.go`
- 轮询服务: `internal/service/polling/manual_trigger_service.go`, `alert_service.go`
- 轮询 Handler: `internal/handler/admin/polling_manual_trigger.go`
- 轮询 Store: `internal/store/postgres/polling_manual_trigger_store.go`
- 文档生成: `cmd/api/docs.go`, `cmd/gendocs/main.go`
- Redis Key: `pkg/constants/redis.go`
- 支付包: `pkg/payment/`
- 废弃 DTO: `internal/model/dto/enterprise_card_authorization_dto.go`
- 废弃任务: `internal/task/sync.go`
- 空文件: `internal/routes/recharge.go`
- Model: `internal/model/iot_card.go`, `internal/model/device.go`
## [2026-04-14] Task 3: 轮询状态常量提取
### 发现和注意点
1. **alert_service.go 的 "pending" 只有 1 处**`NotificationStatus: "pending"``triggerAlert()` 函数中,其他字符串如 `"sent"``"partial"``"failed"` 是通知发送状态(非轮询触发日志状态),不在替换范围内。
2. **Store 层 SQL 字符串处理**`polling_manual_trigger_store.go``GetRunning` 函数原本使用 SQL 字面量 `status IN ('pending', 'processing')`,改为 GORM 参数化查询 `status IN (?, ?)` 并传入常量,既消除硬编码又提升安全性。
3. **Store 层需要新增 import**`polling_manual_trigger_store.go` 原本未导入 `constants` 包,添加时注意 Go import 分组规范stdlib / 第三方 / 内部包)。
4. **constants 包在 handler 文件已存在**`polling_manual_trigger.go` 已导入 `pkg/constants`,无需额外修改 import。
5. **manual_trigger_service.go 共 6 处替换**3 处 `"processing"`triggerLog 创建、2 处 `"completed"`UpdateStatus 调用、1 处同时含 `"pending"` + `"processing"` + `"cancelled"` 的 CancelTrigger 状态检查。
## [2026-04-14] Task 4: 废弃代码清理
### 废弃别名实际引用情况
扫描 `internal/``pkg/` 后,真正使用废弃别名的只有 3 处(其他文件早已用新常量):
- `shop_commission/service.go:645``TransactionTypeWithdrawal` 替换为 `AgentTransactionTypeWithdrawal`
- `recharge/service.go:91,94,417,418``RechargeMinAmount/RechargeMaxAmount` 替换为 `AssetRechargeMinAmount/AssetRechargeMaxAmount`资产钱包充值场景最小1元/最大100000元
### CheckAndStopCard 无法删除
`stop_resume_service.go` 中的 `CheckAndStopCard` 在接口 `StopResumeServiceInterface``usage_service.go:20`)中声明,并在 `package_activation_handler.go`327、347行`usage_service.go`230、255行中活跃调用。任务描述要求"确认无调用后再删除",此方法有调用,故跳过删除。
### grep 验证注意事项
- `SyncHandler` 的 grep 命中了 `CommissionStatsSyncHandler`(合法),不是废弃的 DataSync handler。
- `CardWallet` 出现在 `data_scope.go:82` 的注释字符串里(非代码引用),不需要修改。
### 删除的代码统计
- `sync.go` 整个文件166行
- `handler.go` 3行syncHandler var + HandleFunc + logger.Info
- `constants.go` 1行TaskTypeDataSync
- `wallet.go` 86行149-234行废弃别名块
- `enterprise_card_authorization_dto.go` 22行DeviceBundle、DeviceBundleCard、AllocatedDevice
- `order/service.go` 约243行CreateLegacy 方法)
## [2026-04-14] Task 7+8: 常量清理和空文件删除
### 执行内容
**Task 7.1:删除 CodeExceedLimit 错误码**
- 删除常量定义:`CodeExceedLimit = 1055` (line 68)
- 删除 allErrorCodes 列表条目 (line 227)
- 删除 errorMessages 映射条目 (line 359)
- 验证grep 确认代码库中无任何引用
**Task 7.2:为预留常量添加 [预留] 注释**
- LadderType 系列(激活量、提货量、充值量):添加 `[预留] 用于分佣阶梯功能,待产品规划`
- ApprovalType/ApprovalStatus 系列(审批类型/状态):添加 `[预留] 用于审批流程功能,待产品规划`
- MerchantType 系列(支付宝、微信、银行卡):添加 `[预留] 用于未来商户管理功能,待产品规划`
- ReplacementStatus 系列(换卡申请状态):添加 `[预留] 用于换卡申请功能,待产品规划`
- ReplacementReason 系列(换货原因):添加 `[预留] 用于换卡原因管理功能,待产品规划`
**Task 8.1:删除空文件**
- 删除 `internal/routes/recharge.go`(仅包含 `package routes`
**Task 8.2:删除 postgres.go 的 AutoMigrate 注释块**
- 删除 lines 90-95 的注释掉的 AutoMigrate 代码块
**Task 8.3:更新 client_order/service.go 注释**
- 在 line 140 的实名认证检查注释前添加:`// [待确认] 业务是否需要实名认证检查?参见 tech-debt-cleanup 提案`
### 验证结果
-`go build ./cmd/api ./cmd/worker` 通过
-`go vet ./pkg/errors/ ./pkg/constants/ ./pkg/database/` 无报告
-`grep -r "CodeExceedLimit"` 无结果(完全删除)
- ✅ 所有预留常量已添加 [预留] 标记和产品规划说明
## [2026-04-14] Task 2: API 文档生成器补全
### 发现
- `BuildDocHandlers()` 缺少 3 个 Handler`ClientAuth``AdminAuth``AssetLifecycle`,它们在 `bootstrap/types.go` 中有定义,但从未加入文档生成器
- `AdminAuth``docs.go``gendocs/main.go` 中**完全未被设置**(连手动覆写都没有),这意味着 Admin Auth 相关路由一直缺失于 OpenAPI 文档
- `docs.go``gendocs/main.go` 的手动覆写列表9 行 `handlers.Xxx = ...`)中,大多数 handler 其实已经在 `BuildDocHandlers()` 中了,属于纯冗余
### 操作
1. `pkg/openapi/handlers.go`:按 types.go 字段顺序插入 `ClientAuth`PersonalCustomer 后)、`AdminAuth`ShopRole 后)、`AssetLifecycle`Asset 后)
2. `cmd/api/docs.go`删除所有手动覆写行9 行)+ 移除 `admin``apphandler` import
3. `cmd/gendocs/main.go`:同上
### 注意点
- `admin.NewAuthHandler` 需要 2 个 nil 参数authService + validator
- `app.NewClientAuthHandler` 需要 2 个 nil 参数service + logger
- `admin.NewAssetLifecycleHandler` 只需 1 个 nil 参数service 接口)
- 清理完 import 后 `go build` + `go vet` 均通过,无报错
## [2026-04-14] Task 5: Model 废弃字段清理
### 引用情况(超出预期的额外文件)
计划中列出的文件之外,还发现了额外引用:
- `internal/service/device/service.go` line 576-577`DeviceResponse` 构建中有引用(计划中未提及)
- `internal/service/recharge/service.go` line 31`ForceRechargeRequirement` struct 有 `FirstCommissionPaid` 字段
- `internal/service/recharge/service.go` line 458`result.FirstCommissionPaid = firstCommissionPaid` 赋值
### 关键判断recharge/service.go 的 firstCommissionPaid 变量
`checkForceRechargeRequirement` 中的 `firstCommissionPaid` 变量**不是废弃字段**
- 它读自 `card.IsFirstRechargeTriggeredBySeries(*seriesID)``device.IsFirstRechargeTriggeredBySeries(*seriesID)`(均是新 BySeries 方法)
- 该变量用于 `if firstCommissionPaid {` 的业务判断(已发放则跳过强充检查)
- 正确做法:只删 `ForceRechargeRequirement.FirstCommissionPaid` 字段 + `result.FirstCommissionPaid = firstCommissionPaid` 赋值,保留变量声明和 if 判断
### 迁移文件
- 新建 `migrations/000116_remove_legacy_commission_fields.up.sql`
- 新建 `migrations/000116_remove_legacy_commission_fields.down.sql`
### 验证结果
-`go build ./cmd/api ./cmd/worker` 通过,无任何错误
## [2026-04-14] Task 1: 支付配置动态加载
### 实现架构决策
#### 1. PaymentConfigLoader 位置
- 新建 `pkg/payment/loader.go`(包名 `payment`
- `pkg/` 下可以 import `internal/` 包(项目中已有先例:`pkg/wechat/config.go` import `internal/model`
#### 2. 缓存策略
- 不缓存 Payment 实例(含连接/状态,不可序列化)
- 缓存 **WechatConfig JSON 数据**key: `payment:config:{id}`TTL: 1 小时)
- 每次从缓存数据构建 Payment 实例(轻量操作,无 I/O
#### 3. v2 适配器模式
- `PaymentV2Service` 未实现完整 `PaymentServiceInterface`(缺少 H5/查单/关单/HandlePaymentNotify
- 通过 `v2PaymentAdapter` 包装,补全缺失方法(返回"不支持"错误)
- **不修改** `payment_v2.go` 原文件
#### 4. `appID` 来源
- `NewPaymentAppFromConfig` / `NewPaymentV2ServiceFromConfig` 均需要 `appID` 参数
- 使用 `wechatConfig.OaAppID`(公众号 AppID
#### 5. wechatCache 共享
- loader 在构造时通过 `wechat.NewRedisCache(rdb)` 创建一次,所有 LoadConfig 调用复用
- v3 Payment 实例构建需要此 cache用于 PowerWeChat SDK 内部 token 缓存)
#### 6. worker_services.go 也需更新
- `internal/bootstrap/worker_services.go` 里有另一处 `orderSvc.New` 调用(超时取消专用)
- 需额外传 `nil` 占位
### 关键文件变更
| 文件 | 变更内容 |
|------|---------|
| `pkg/constants/redis.go` | +`RedisPaymentConfigKey` |
| `pkg/payment/loader.go` | 新建:接口 + 实现 + v2 适配器 |
| `internal/service/order/service.go` | +`paymentLoader` 字段/参数,替换 2 处 TODO |
| `internal/service/recharge/service.go` | +`paymentLoader` 字段/参数,更新 1 处 TODO 注释 |
| `internal/bootstrap/services.go` | 创建 loader 实例,传入 Order/Recharge 构造函数 |
| `internal/bootstrap/worker_services.go` | `orderSvc.New` 补传 `nil` |
### 验证结果
-`go build ./cmd/api ./cmd/worker` 通过,无任何错误
-`go vet` 无报告
## [2026-04-14] Task 6: DTO _name 字段补全
### 概述
为所有 Response DTO 中的 `int` 类型状态字段补全对应 `string` 类型 `_name` 字段,并在 Service 层赋值。
### 常量映射函数(新增)
**`pkg/constants/constants.go`**
- `GetStatusName(status int) string` — 通用 0=禁用/1=启用
- `GetShelfStatusName(status int) string` — 1=上架/2=下架
- `GetExchangeStatusName(status int) string` — 换货单状态
**`pkg/constants/iot.go`**
- `GetIotCardStatusName` — 1=在库, 2=已分销, 3=已激活, 4=已停用
- `GetActivationStatusName` — 0=未激活, 1=已激活
- `GetRealNameStatusName` — 0=未实名, 1=已实名
- `GetNetworkStatusName` — 0=停机, 1=开机
- `GetCommissionRecordStatusName` — 1=已冻结/2=解冻中/3=已发放/4=已失效/99=待人工修正
- `GetOrderPaymentStatusName` — 1=待支付/2=已支付/3=已取消/4=已退款
- `GetOrderCommissionStatusName` — 1=待计算/2=已计算
- `GetRefundStatusName` — 1=待审批/2=已通过/3=已拒绝/4=已退回
### 模块处理情况
| 模块 | DTO 改动 | Service 赋值位置 | 备注 |
|------|---------|-----------------|------|
| Account | `AccountResponse` + `status_name` | `toAccountResponse()` | constants 已导入 |
| Shop | `ShopResponse` + `status_name` | Create/Update/ListShopResponses3处| constants 已导入 |
| IotCard | `StandaloneIotCardResponse` + 4个 `_name` 字段 | `toStandaloneResponse()` | status/activation/realname/network |
| Order | `OrderResponse` + `commission_status_name` | `buildOrderResponse()` | payment_status 已有 `_text` 字段 |
| Package | `PackageResponse` + `status_name`/`shelf_status_name` | `toResponse()` 末尾agent 覆盖后赋值) | my_package.go 也同步更新(不涉及 service|
| Refund | `RefundResponse` + `status_name` | `buildRefundResponse()` | constants 已导入 |
| Device | `DeviceCardBindingResponse` + `status_name` | `device/binding.go` | 新增 constants import |
| Asset | `AssetResolveResponse` + `status_name` | `buildDeviceResolveResponse` + `buildCardResolveResponse` | constants 已导入 |
| Commission | `CommissionRecordResponse` + `status_name` | 未找到 service 调用DTO 未被使用ShopCommissionRecordItem 已有 status_name| 仅 DTO 更新;同时修正了 description 错误 |
| Client Asset | `AssetInfoResponse` + 4个 `_name` 字段 | handler `client_asset.go`(直接赋值)| constants 已导入 |
| Client Order | `ClientOrderInfo/ListItem/DetailResponse` + `payment_status_name`; `ClientRechargeInfo` + `status_name` | `client_order/service.go` | 增加 `clientRechargeStatusName` 辅助函数 |
| MyPackage | `MyPackageResponse/DetailResponse` + `status_name`/`shelf_status_name`; `MySeriesAllocationResponse` + `status_name` | 未找到 service 调用,仅 DTO 更新 | |
### 关键发现
1. `CommissionRecordResponse.status` description 原先错误(写的 "1:已入账" 但常量是 "1:已冻结"),已修正为正确值
2. `ClientRechargeInfo.Status` 使用的是 `rechargeStatusToClientStatus()` 转换后的值0/1/2与 RechargeStatus* 常量不同
3. 部分 DTOMyPackage、CommissionRecord未被 service 层引用,仅更新 DTO 定义
4. `DeviceResponse``ImportTaskResponse` 已经有 `status_name``status_text` 字段,无需修改
### 验证结果
-`go build ./cmd/api ./cmd/worker` 通过,无任何错误