docs: 归档 asset-wallet-interface OpenSpec 提案,更新卡钱包 spec

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-16 23:30:48 +08:00
parent 63ca12393b
commit f3297f0529
8 changed files with 927 additions and 148 deletions

View File

@@ -0,0 +1,157 @@
## 1. 数据库迁移(先行)
- [x] 1.1 创建迁移文件:`tb_card_wallet``tb_asset_wallet``tb_card_wallet_transaction``tb_asset_wallet_transaction``tb_card_recharge_record``tb_asset_recharge_record`(三张表在同一个迁移文件中完成)
- [x] 1.2 创建迁移文件:`tb_asset_wallet_transaction.reference_id (bigint, nullable)``reference_no (varchar 50, nullable)`ALTER TABLE RENAME COLUMN + ALTER COLUMN TYPE
- [x] 1.3 执行全部迁移,使用 PostgreSQL MCP 确认三张表改名成功,`reference_no` 字段类型为 varchar(50)
## 2. Model 层全量重命名
- [x] 2.1 重命名 `internal/model/card_wallet.go``internal/model/asset_wallet.go`,文件内所有类型改名:
- `CardWallet``AssetWallet``TableName()` 返回 `"tb_asset_wallet"`
- `CardWalletTransaction``AssetWalletTransaction``TableName()` 返回 `"tb_asset_wallet_transaction"`
- `CardRechargeRecord``AssetRechargeRecord``TableName()` 返回 `"tb_asset_recharge_record"`
- [x] 2.2 更新 `AssetWalletTransaction` 结构体字段:`CardWalletID uint json:"card_wallet_id"``AssetWalletID uint json:"asset_wallet_id"`GORM column tag 同步更新为 `column:asset_wallet_id``ReferenceID *uint json:"reference_id,omitempty"``ReferenceNo *string json:"reference_no,omitempty"`GORM column tag 改为 `column:reference_no;type:varchar(50)`
- [x] 2.3 更新 `AssetRechargeRecord` 结构体字段:`CardWalletID uint json:"card_wallet_id"``AssetWalletID uint json:"asset_wallet_id"`GORM column tag 同步更新)
- [x] 2.4 运行 `go build ./...` 确认 Model 层无编译错误
## 3. Store 层全量重命名
- [x] 3.1 重命名 `internal/store/postgres/card_wallet_store.go``asset_wallet_store.go`,类型 `CardWalletStore``AssetWalletStore`,构造函数 `NewCardWalletStore``NewAssetWalletStore`,方法内 `model.CardWallet``model.AssetWallet`
- [x] 3.2 重命名 `internal/store/postgres/card_wallet_transaction_store.go``asset_wallet_transaction_store.go`,类型 `CardWalletTransactionStore``AssetWalletTransactionStore`,构造函数及方法内 Model 引用同步更新
- [x] 3.3 重命名 `internal/store/postgres/card_recharge_store.go``asset_recharge_store.go`,类型 `CardRechargeStore``AssetRechargeStore`,构造函数及方法内 Model 引用同步更新
- [x] 3.4 运行 `go build ./...` 确认 Store 层无编译错误
## 4. Bootstrap 层更新
- [x] 4.1 更新 `internal/bootstrap/stores.go`:字段名 `CardWallet``AssetWallet``CardWalletTransaction``AssetWalletTransaction``CardRecharge``AssetRecharge`;构造函数调用同步更新为 `NewAssetWalletStore``NewAssetWalletTransactionStore``NewAssetRechargeStore`
- [x] 4.2 更新 `internal/bootstrap/services.go`:依赖注入中所有 `s.CardWallet*` 引用改为 `s.AssetWallet*``s.CardRecharge` 改为 `s.AssetRecharge`
- [x] 4.3 运行 `go build ./...` 确认 bootstrap 层无编译错误
## 5. 常量层更新
- [x] 5.1 更新 `pkg/constants/redis.go``RedisCardWalletBalanceKey``RedisAssetWalletBalanceKey`,函数体不变,仅函数名改变
- [x] 5.2 全局搜索 `RedisCardWalletBalanceKey` 调用处card_wallet_store.go替换为 `RedisAssetWalletBalanceKey`
## 6. Service 层适配order service
- [x] 6.1 更新 `internal/service/order/service.go`:结构体字段 `cardWalletStore *postgres.CardWalletStore``assetWalletStore *postgres.AssetWalletStore`,构造函数参数及所有调用点同步更新
- [x] 6.2 在 `WalletPay` 卡钱包支付路径(`resourceType != "shop"` 分支)中,扣款成功后在同一事务内补写 `AssetWalletTransaction` 扣款流水:
- 在事务内扣款前记录 `balanceBefore = wallet.Balance`
- 扣款成功(`RowsAffected == 1`)后,`INSERT` 一条 `AssetWalletTransaction``AssetWalletID=wallet.ID``ResourceType=resourceType``ResourceID=resourceID``UserID=buyerID``TransactionType="deduct"``Amount=-order.TotalAmount``BalanceBefore=balanceBefore``BalanceAfter=balanceBefore-order.TotalAmount``Status=1``ReferenceType=strPtr("order")``ReferenceNo=&order.OrderNo``Remark=strPtr("钱包支付套餐")``ShopIDTag=wallet.ShopIDTag``EnterpriseIDTag=wallet.EnterpriseIDTag`
- [x] 6.3 运行 `go build ./...` 确认 order service 无编译错误
## 7. Service 层适配recharge service
- [x] 7.1 更新 `internal/service/recharge/service.go`:结构体字段及构造函数 `cardWalletStore``assetWalletStore``cardWalletTransactionStore``assetWalletTransactionStore`;所有 Model 引用 `model.CardWallet*``model.AssetWallet*`
- [x] 7.2 更新充值回调写入流水记录处(约第 320 行):`ReferenceID: &recharge.ID``ReferenceNo: &recharge.RechargeNo`(同时删除原 `ReferenceID` 字段赋值)
- [x] 7.3 运行 `go build ./...` 确认 recharge service 无编译错误
## 8. DTO 新增
- [x] 8.1 新建 `internal/model/dto/asset_wallet_dto.go`,定义以下 DTO含所有字段及 `description` tag
**AssetWalletResponse**(钱包概况响应):
- `wallet_id uint``resource_type string``resource_id uint`
- `balance int64``frozen_balance int64``available_balance int64`
- `currency string``status int``status_text string`
- `created_at time.Time``updated_at time.Time`
**AssetWalletTransactionListRequest**(流水列表请求,查询参数):
- `page int`(默认 1`page_size int`(默认 20最大 100
- `transaction_type *string`可选oneof=recharge deduct refund
- `start_time *time.Time`(可选)、`end_time *time.Time`(可选)
**AssetWalletTransactionItem**(单条流水):
- `id uint`
- `transaction_type string``transaction_type_text string`
- `amount int64``balance_before int64``balance_after int64`
- `reference_type *string``reference_no *string`
- `remark *string`
- `created_at time.Time`
**AssetWalletTransactionListResponse**(流水列表响应):
- `list []*AssetWalletTransactionItem`
- `total int64``page int``page_size int``total_pages int`
- [x] 8.2 运行 `lsp_diagnostics` 确认 DTO 无错误
## 9. AssetWalletService 新增
- [x] 9.1 新建 `internal/service/asset_wallet/service.go`,定义 `Service` 结构体,依赖注入:`AssetWalletStore``AssetWalletTransactionStore`
- [x] 9.2 实现 `GetWallet(ctx, assetType string, assetID uint) (*dto.AssetWalletResponse, error)`
-`assetType``card`/`device`)映射到 `resourceType``iot_card`/`device`
- 调用 `AssetWalletStore.GetByResourceTypeAndID(ctx, resourceType, assetID)`
- 组装 `AssetWalletResponse`(计算 `available_balance = balance - frozen_balance`,翻译 `status_text`
- 钱包不存在时返回 `errors.New(errors.CodeNotFound, "该资产暂无钱包记录")`
- [x] 9.3 实现 `ListTransactions(ctx, assetType string, assetID uint, req *dto.AssetWalletTransactionListRequest) (*dto.AssetWalletTransactionListResponse, error)`
-`assetType` 映射为 `resourceType`
- 调用 `AssetWalletTransactionStore.ListByResourceID(ctx, resourceType, assetID, offset, limit)``CountByResourceID` 获取分页数据
- 组装响应:翻译 `transaction_type_text`recharge→充值 / deduct→扣款 / refund→退款计算 `total_pages`
- 如有 `transaction_type` 过滤参数,在 Store 层新增对应过滤方法(或在 Service 层 in-memory 过滤——推荐 Store 层)
- [x] 9.4 运行 `lsp_diagnostics` 确认 Service 无错误
## 10. Store 层新增查询方法
- [x] 10.1 在 `AssetWalletTransactionStore` 中新增 `ListByResourceIDWithFilter(ctx, resourceType string, resourceID uint, transactionType *string, startTime, endTime *time.Time, offset, limit int) ([]*model.AssetWalletTransaction, error)` 方法,支持 `transaction_type`、时间范围过滤,应用 `ApplyShopTagFilter` 数据权限
- [x] 10.2 在 `AssetWalletTransactionStore` 中新增 `CountByResourceIDWithFilter(ctx, resourceType string, resourceID uint, transactionType *string, startTime, endTime *time.Time) (int64, error)` 方法
- [x] 10.3 运行 `lsp_diagnostics` 确认 Store 无错误
## 11. AssetWalletHandler 新增
- [x] 11.1 新建 `internal/handler/admin/asset_wallet.go`,定义 `AssetWalletHandler` 结构体(依赖 `*assetWalletSvc.Service`),实现两个 Handler 方法:
**GetWallet**`GET /api/admin/assets/:asset_type/:id/wallet`
- 检查企业账号:`user_type == UserTypeEnterprise` → 返回 403
- 解析路径参数 `asset_type`(校验为 `card``device`)和 `id`(校验为正整数)
- 调用 `assetWalletSvc.GetWallet(ctx, assetType, id)` → 返回 `response.Success`
**ListTransactions**`GET /api/admin/assets/:asset_type/:id/wallet/transactions`
- 检查企业账号:同上
- 解析路径参数和查询参数(`QueryParser` 绑定 `AssetWalletTransactionListRequest`
- 参数验证:`page_size` 最大 100`transaction_type` 需为合法枚举值
- 调用 `assetWalletSvc.ListTransactions(ctx, assetType, id, &req)` → 返回 `response.Success`
- [x] 11.2 运行 `lsp_diagnostics` 确认 Handler 无编译错误
## 12. 路由注册
- [x] 12.1 在 `internal/routes/asset.go``registerAssetRoutes` 函数末尾追加两条路由(需传入 `*admin.AssetWalletHandler` 参数):
```go
Register(assets, doc, groupPath, "GET", "/:asset_type/:id/wallet", walletHandler.GetWallet, RouteSpec{
Summary: "资产钱包概况",
Description: "查询指定卡或设备的钱包余额概况。企业账号禁止调用。",
Tags: []string{"资产管理"},
Input: new(dto.AssetTypeIDRequest),
Output: new(dto.AssetWalletResponse),
Auth: true,
})
Register(assets, doc, groupPath, "GET", "/:asset_type/:id/wallet/transactions", walletHandler.ListTransactions, RouteSpec{
Summary: "资产钱包流水列表",
Description: "分页查询指定资产的钱包收支流水,含充值/扣款来源编号。企业账号禁止调用。",
Tags: []string{"资产管理"},
Input: new(dto.AssetWalletTransactionListRequest),
Output: new(dto.AssetWalletTransactionListResponse),
Auth: true,
})
```
- [x] 12.2 更新 `registerAssetRoutes` 函数签名,增加 `walletHandler *admin.AssetWalletHandler` 参数
- [x] 12.3 更新 `internal/routes/routes.go` 中 `registerAssetRoutes` 的调用处,传入 `AssetWalletHandler`
## 13. Bootstrap 层注册新 Handler/Service
- [x] 13.1 更新 `internal/bootstrap/types.go``Handlers` 结构体新增 `AssetWallet *admin.AssetWalletHandler``Services` 结构体新增 `AssetWallet *assetWalletSvc.Service`(如需独立 service 包则导入)
- [x] 13.2 更新 `internal/bootstrap/services.go`:实例化 `assetWalletSvc.New(s.AssetWallet, s.AssetWalletTransaction)`
- [x] 13.3 更新 `internal/bootstrap/handlers.go`:实例化 `admin.NewAssetWalletHandler(svcs.AssetWallet)`
- [x] 13.4 更新 `cmd/api/docs.go` 和 `cmd/gendocs/main.go``handlers.AssetWallet = admin.NewAssetWalletHandler(nil)`(文档生成器注册)
- [x] 13.5 运行 `go build ./...` 全量确认无编译错误
## 14. 文档和最终验收
- [x] 14.1 运行 `go run cmd/gendocs/main.go` 确认两个新接口(资产钱包概况、资产钱包流水列表)出现在 OpenAPI 文档中
- [x] 14.2 使用 PostgreSQL MCP 验证三张表改名成功:`tb_asset_wallet`、`tb_asset_wallet_transaction`(含 `reference_no varchar(50)` 字段)、`tb_asset_recharge_record`
- [x] 14.3 使用 PostgreSQL MCP 或 curl 验证H5 充值接口 `GET /api/h5/wallets/recharges/:id` 响应中 `wallet_id` 字段仍正常返回
- [x] 14.4 运行 `go build ./...` 全量确认无编译错误
- [x] 14.5 tasks.md 全部任务标记完成