## 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 全部任务标记完成