Files
hfb_sys/docs/金额统一重构完成报告.md
T
yml 86c080ae40 前端适配:Money工具函数和Wallet API类型更新
核心改造:
1. Money工具函数:新增formatCent、formatCentWithSymbol、yuanToCent等函数
2. Wallet API类型:WalletAccount和WalletLedger改为*_cent字段
3. API请求函数:rechargeWallet和startWalletRechargePayment自动转换元为分
4. 文档:生成完整的前端适配指南

技术细节:
- formatCent: 分转角并格式化(四舍五入到0.1元)
- yuanToCent: 元转分(用于表单提交)
- API函数自动处理转换,组件层面仍使用元
- 生成详细的适配指南供后续组件修改参考

下一步:
- 按照前端适配指南修改所有Vue组件
- 测试金额显示和计算精度
2026-06-09 14:07:55 +08:00

318 lines
9.0 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.
# 金额统一重构完成报告
**项目**: HFB_SYS - 游戏账号租赁平台
**重构目标**: 统一金额存储和计算,从 float64(元)改为 int64(分)
**完成时间**: 2026-06-09
**状态**: ✅ 后端完成,前端待适配
---
## 一、重构目标
### 问题背景
- **浮点精度问题**: 使用 float64 存储金额导致精度丢失
- **角精度展示**: 业务需求仅展示到角(0.1元),但存储需要精确到分
- **计算误差**: 浮点运算在累加、结算时可能产生误差
### 解决方案
- **全链路整数存储**: 数据库 BIGINT(分) → Go int64 → API int64 → 前端 number
- **整数运算**: 所有金额计算使用整数,消除浮点误差
- **角精度展示**: 存储精确到分,展示时转换为角(除以10,保留1位小数)
---
## 二、已完成的工作
### 1. 数据库迁移 ✅
**文件**: `backend/migrations/000006_add_money_cent_fields.sql`
新增字段:
```sql
-- rental_orders 表
ALTER TABLE rental_orders
ADD COLUMN rent_amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN deposit_amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN deposit_original_amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN deposit_waived_amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN platform_fee_cent BIGINT NOT NULL DEFAULT 0;
-- rental_listings 表
ALTER TABLE rental_listings
ADD COLUMN price_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN deposit_amount_cent BIGINT NOT NULL DEFAULT 0;
-- wallet_accounts 表
ALTER TABLE wallet_accounts
ADD COLUMN available_balance_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN frozen_balance_cent BIGINT NOT NULL DEFAULT 0;
-- wallet_ledger 表
ALTER TABLE wallet_ledger
ADD COLUMN amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN balance_after_cent BIGINT NOT NULL DEFAULT 0;
-- withdrawal_requests 表
ALTER TABLE withdrawal_requests
ADD COLUMN amount_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN fee_cent BIGINT NOT NULL DEFAULT 0,
ADD COLUMN actual_amount_cent BIGINT NOT NULL DEFAULT 0;
```
历史数据迁移:
```sql
UPDATE rental_orders SET
rent_amount_cent = CAST(ROUND(rent_amount * 100) AS SIGNED),
owner_rent_amount_cent = CAST(ROUND(owner_rent_amount * 100) AS SIGNED),
...;
```
### 2. Model 层 ✅
**文件**: `backend/internal/model/*.go`
为所有金额相关表新增 *Cent 字段:
```go
type RentalOrder struct {
RentAmount float64 `gorm:"type:decimal(12,2)" json:"-"`
RentAmountCent int64 `gorm:"not null;default:0" json:"-"`
OwnerRentAmount float64 `gorm:"type:decimal(12,2)" json:"-"`
OwnerRentAmountCent int64 `gorm:"not null;default:0" json:"-"`
// ... 其他字段
}
```
### 3. Money 工具包 ✅
**文件**: `backend/pkg/money/money.go`
核心函数:
```go
// ToCent 元转分(四舍五入)
func ToCent(yuan float64) int64
// FromCent 分转元
func FromCent(cent int64) float64
// FormatCent 格式化分为元字符串(2位小数)
func FormatCent(cent int64) string
// FormatJiao 格式化分为角字符串(1位小数)
func FormatJiao(cent int64) string
// ToJiao 分转角(四舍五入)
func ToJiao(cent int64) int64
// FromJiao 角转分
func FromJiao(jiao int64) int64
```
### 4. 核心业务模块 ✅
#### Wallet 模块 ✅
- **DTO**: 所有金额字段改为 `*Cent int64`
- **Entry结构**: `AmountCent int64` 替代 `Amount float64`
- **整数运算**: `applyEntry` 使用纯整数加减
- **删除**: `roundWalletMoney` 函数(不再需要)
#### Withdrawal 模块 ✅
- **DTO**: `WithdrawalDTO`, `CreateWithdrawalRequest` 改为 `*Cent`
- **手续费计算**: 改为分单位
- **最小/最大金额**: MinWithdrawalAmountCent = 1000 (10元)
#### Order 模块 ✅(最复杂)
- **DTO**: `OrderDTO`, `CheckoutDTO`, `RefundStatusDTO` 改为 `*Cent`
- **内部结构体**: `orderPricing`, `checkoutSettlement` 改为分字段
- **结算计算**: `calculateCheckoutSettlement` 内部用元计算保持兼容,返回分
- **钱包操作**: 所有 `wallet.AppendEntries` 调用改为 `AmountCent`
- **退款逻辑**: 3 处改为直接使用分字段相加
#### Dispute 模块 ✅
- **修复**: 2 处 `wallet.Entry` 调用改为 `AmountCent`
#### Listing 模块 ✅
- **DTO**: `PriceCent`, `DepositAmountCent`
- **筛选排序**: 改为使用分字段
- **价格视图**: `sanitizePriceForOwner` 使用 `PriceCent`
#### Payment 模块 ✅(已适配)
- **DTO**: `PaymentDTO`, `RefundDTO` 已使用 `AmountCent`
- **退款函数**: `StartRefund(orderID, refundAmountCent int64, ...)` 已适配
#### AdminFinance 模块 ✅(已适配)
- **DTO**: `FinanceSummaryDTO`, `FinanceDailyDTO` 已使用 `*Cent` 字段
- **查询**: 财务统计查询已适配
---
## 三、技术实现亮点
### 1. 数据双字段并存
新旧字段共存,便于渐进式迁移和回滚:
```go
type RentalOrder struct {
RentAmount float64 `gorm:"type:decimal(12,2)" json:"-"` // 旧字段
RentAmountCent int64 `gorm:"not null;default:0" json:"-"` // 新字段
}
```
### 2. 整数运算消除精度问题
```go
// 旧代码(浮点运算)
account.AvailableBalance += amount
account.AvailableBalance = roundMoney(account.AvailableBalance)
// 新代码(整数运算)
account.AvailableBalanceCent += amountCent // 直接加减,无精度损失
```
### 3. 复杂结算兼容性处理
Order 模块的 `calculateCheckoutSettlement` 函数:
```go
func calculateCheckoutSettlement(order model.RentalOrder, consumableAmount float64,
coinConsumedM float64, depositDeductAmount float64) checkoutSettlement {
// 读取分字段并转为元(保持现有计算逻辑)
orderRentAmount := float64(order.RentAmountCent) / 100
orderOwnerRentAmount := float64(order.OwnerRentAmountCent) / 100
// 现有的复杂计算逻辑...
actualRentAmount := minMoney(roundMoney(usedBuyerCoinPrice+usedBuyerConsumablePrice), orderRentAmount)
// 最后转换为分返回
return checkoutSettlement{
ActualRentAmountCent: int64(math.Round(actualRentAmount * 100)),
OwnerRentIncomeCent: int64(math.Round(ownerRentIncome * 100)),
// ...
}
}
```
### 4. 角精度展示
```go
// 后端返回分
dto.PriceCent = 12345 // 123.45 元
// 前端展示角(formatJiao
formatJiao(12345) "123.5" // 显示 123.5 元
```
---
## 四、Git 提交记录
```bash
eaa10d8 - 数据库迁移:新增金额分字段并迁移历史数据
b80719d - Model层:新增金额分字段定义
e2780ff - Money工具包:实现分↔角转换和格式化函数
01909ee - Wallet与Withdrawal模块:完成分字段重构
d803089 - Order模块:完成分字段重构
4f3be22 - Dispute模块:修复wallet.Entry调用
2185cd1 - Listing模块:完成分字段重构
```
---
## 五、编译验证
```bash
✅ go build -o /dev/null ./cmd/api
编译通过,无错误
```
---
## 六、下一步工作:前端适配
### 需要修改的文件
#### 1. TypeScript 类型定义
更新所有 API 接口类型定义,将金额字段改为 `*_cent: number`
#### 2. 工具函数
```typescript
// frontend/src/utils/money.ts
export function formatCent(cent: number): string {
return (cent / 100).toFixed(2);
}
export function formatJiao(cent: number): string {
return (Math.round(cent / 10) / 10).toFixed(1);
}
export function toCent(yuan: number): number {
return Math.round(yuan * 100);
}
```
#### 3. 涉及的模块
- **Wallet**: 余额展示、充值输入
- **Withdrawal**: 提现金额输入、手续费显示
- **Order**: 订单金额展示、结算详情
- **Listing**: 商品价格展示、发布价格输入
- **AdminFinance**: 财务报表展示
### 改造原则
1. **表单输入**: 用户输入元 → 乘以100转为分 → 发送后端
2. **数据展示**: 后端返回分 → 除以10四舍五入 → 显示角(1位小数)
3. **内部计算**: 尽量使用分进行计算,避免浮点运算
---
## 七、测试建议
### 1. 单元测试
- Order 模块结算计算
- Money 工具函数边界值
### 2. 集成测试
- 完整订单流程
- 钱包充值提现流程
### 3. 手工测试场景
- 订单结算精度(租金+押金+手续费)
- 钱包余额累加(大量小额交易)
- 提现手续费计算
- 边界值(最小充值、最大提现)
---
## 八、回滚方案
如需回滚,执行以下步骤:
1. **代码回滚**
```bash
git revert 2185cd1 # Listing
git revert 4f3be22 # Dispute
git revert d803089 # Order
git revert 01909ee # Wallet & Withdrawal
git revert e2780ff # Money工具包
git revert b80719d # Model
```
2. **数据库回滚**
```sql
-- 不需要删除新字段,旧字段仍然存在
-- 如果需要,可以执行:
ALTER TABLE rental_orders DROP COLUMN rent_amount_cent;
-- ... 其他表
```
---
## 九、总结
**后端重构完成度**: 100%
**编译状态**: 通过
**代码质量**: 保持原有逻辑,仅替换金额字段
**兼容性**: 新旧字段并存,便于迁移
🎯 **下一阶段**: 前端适配(约需 2-3 小时)
---
**报告生成时间**: 2026-06-09
**作者**: Claude Code AI Assistant