From 87673288ef611e1d183aa44e9d62133050660779 Mon Sep 17 00:00:00 2001 From: yml Date: Tue, 9 Jun 2026 17:45:29 +0800 Subject: [PATCH] chore: finalize amount migration fixes --- backend/Dockerfile | 6 + .../internal/modules/order/repository_test.go | 56 +-- backend/migrations/000001_init.sql | 27 ++ docs/前端Vue组件详细适配清单.md | 407 ---------------- docs/前端金额字段适配指南.md | 453 ------------------ docs/金额统一重构-最终交付报告.md | 235 --------- docs/金额统一重构-最终完成报告.md | 329 ------------- docs/金额统一重构完成报告.md | 317 ------------ docs/金额统一重构项目总结.md | 345 ------------- frontend/components.d.ts | 1 - .../orders/composables/useSettlement.ts | 8 +- .../orders/views/MobileOrderDetailView.vue | 9 +- .../features/orders/views/OrderDetailView.vue | 105 +++- scripts/deploy-prod.sh | 86 +--- scripts/dev.sh | 110 ++--- scripts/replace_money_fields.sh | 47 -- 16 files changed, 214 insertions(+), 2327 deletions(-) delete mode 100644 docs/前端Vue组件详细适配清单.md delete mode 100644 docs/前端金额字段适配指南.md delete mode 100644 docs/金额统一重构-最终交付报告.md delete mode 100644 docs/金额统一重构-最终完成报告.md delete mode 100644 docs/金额统一重构完成报告.md delete mode 100644 docs/金额统一重构项目总结.md delete mode 100644 scripts/replace_money_fields.sh diff --git a/backend/Dockerfile b/backend/Dockerfile index 68b024c..999364c 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -3,6 +3,7 @@ FROM golang:1.26-alpine AS build WORKDIR /src +ARG GOOSE_VERSION=v3.27.1 ARG GOPROXY=https://goproxy.cn,direct ARG GOSUMDB=sum.golang.google.cn ENV GOPROXY=${GOPROXY} \ @@ -11,6 +12,9 @@ ENV GOPROXY=${GOPROXY} \ COPY go.mod go.sum* ./ RUN --mount=type=cache,target=/go/pkg/mod go mod download COPY . . +RUN --mount=type=cache,target=/go/pkg/mod \ + --mount=type=cache,target=/root/.cache/go-build \ + go install github.com/pressly/goose/v3/cmd/goose@${GOOSE_VERSION} RUN --mount=type=cache,target=/go/pkg/mod \ --mount=type=cache,target=/root/.cache/go-build \ CGO_ENABLED=0 GOOS=linux go build -o /out/hfb-api ./cmd/api @@ -21,5 +25,7 @@ WORKDIR /app ENV TZ=Asia/Shanghai RUN apk add --no-cache tzdata COPY --from=build /out/hfb-api /app/hfb-api +COPY --from=build /go/bin/goose /app/goose +COPY --from=build /src/migrations /app/migrations EXPOSE 8080 CMD ["/app/hfb-api"] diff --git a/backend/internal/modules/order/repository_test.go b/backend/internal/modules/order/repository_test.go index 06589d1..fe2e408 100644 --- a/backend/internal/modules/order/repository_test.go +++ b/backend/internal/modules/order/repository_test.go @@ -30,27 +30,27 @@ func TestCalculateCheckoutSettlementRefundsUnusedRent(t *testing.T) { settlement := calculateCheckoutSettlement(order, 7, 90, 0) // 角精度:243.7 = roundMoney(236.7 + 7) - if float64(settlement.ActualRentAmountCent)/100 != 243.7 { - t.Fatalf("ActualRentAmount = %.1f, want 243.7", float64(settlement.ActualRentAmountCent)/100) + if settlement.ActualRentAmountCent != 24370 { + t.Fatalf("ActualRentAmountCent = %d, want 24370", settlement.ActualRentAmountCent) } // 角精度:216.7 = roundMoney(209.7 + 7) 卖家币价+消耗品 - if float64(settlement.OwnerRentIncomeCent)/100 != 216.7 { - t.Fatalf("OwnerRentIncome = %.1f, want 216.7", float64(settlement.OwnerRentIncomeCent)/100) + if settlement.OwnerRentIncomeCent != 21670 { + t.Fatalf("OwnerRentIncomeCent = %d, want 21670", settlement.OwnerRentIncomeCent) } // 角精度:27.0 = roundMoney(27) 平台费 - if float64(settlement.PlatformFeeCent)/100 != 27.0 { - t.Fatalf("PlatformFee = %.1f, want 27.0", float64(settlement.PlatformFeeCent)/100) + if settlement.PlatformFeeCent != 2700 { + t.Fatalf("PlatformFeeCent = %d, want 2700", settlement.PlatformFeeCent) } // 角精度:139.3 = roundMoney(383 - 243.7) - if float64(settlement.RentRefundCent)/100 != 139.3 { - t.Fatalf("RentRefund = %.1f, want 139.3", float64(settlement.RentRefundCent)/100) + if settlement.RentRefundCent != 13930 { + t.Fatalf("RentRefundCent = %d, want 13930", settlement.RentRefundCent) } - if float64(settlement.DepositRefundCent)/100 != 150 { - t.Fatalf("DepositRefund = %.1f, want 150.0", float64(settlement.DepositRefundCent)/100) + if settlement.DepositRefundCent != 15000 { + t.Fatalf("DepositRefundCent = %d, want 15000", settlement.DepositRefundCent) } // 角精度:289.3 = roundMoney(139.3 + 150) - if float64(settlement.RenterRefundCent)/100 != 289.3 { - t.Fatalf("RenterRefund = %.1f, want 289.3", float64(settlement.RenterRefundCent)/100) + if settlement.RenterRefundCent != 28930 { + t.Fatalf("RenterRefundCent = %d, want 28930", settlement.RenterRefundCent) } } @@ -74,16 +74,16 @@ func TestCalculateCheckoutSettlementUsesBuyerAndSellerRatiosSeparately(t *testin settlement := calculateCheckoutSettlement(order, 0, 50, 0) // 角精度:131.5 = roundMoney(131.5) - if float64(settlement.ActualRentAmountCent)/100 != 131.5 { - t.Fatalf("租客侧实际租金 = %.1f, want 131.5", float64(settlement.ActualRentAmountCent)/100) + if settlement.ActualRentAmountCent != 13150 { + t.Fatalf("租客侧实际租金 = %d, want 13150", settlement.ActualRentAmountCent) } // 角精度:116.5 = roundMoney(116.5) - if float64(settlement.OwnerRentIncomeCent)/100 != 116.5 { - t.Fatalf("卖家侧租金收入 = %.1f, want 116.5", float64(settlement.OwnerRentIncomeCent)/100) + if settlement.OwnerRentIncomeCent != 11650 { + t.Fatalf("卖家侧租金收入 = %d, want 11650", settlement.OwnerRentIncomeCent) } // 角精度:15.0 = roundMoney(15) - if float64(settlement.PlatformFeeCent)/100 != 15.0 { - t.Fatalf("平台差价 = %.1f, want 15.0", float64(settlement.PlatformFeeCent)/100) + if settlement.PlatformFeeCent != 1500 { + t.Fatalf("平台差价 = %d, want 1500", settlement.PlatformFeeCent) } } @@ -106,20 +106,20 @@ func TestCalculateCheckoutSettlementAddsDepositCompensation(t *testing.T) { settlement := calculateCheckoutSettlement(order, 120, 100, 30) - if float64(settlement.ActualRentAmountCent)/100 != 383 { - t.Fatalf("ActualRentAmount = %.2f, want 383.00", float64(settlement.ActualRentAmountCent)/100) + if settlement.ActualRentAmountCent != 38300 { + t.Fatalf("ActualRentAmountCent = %d, want 38300", settlement.ActualRentAmountCent) } - if float64(settlement.OwnerRentIncomeCent)/100 != 353 { - t.Fatalf("OwnerRentIncome = %.2f, want 353.00", float64(settlement.OwnerRentIncomeCent)/100) + if settlement.OwnerRentIncomeCent != 35300 { + t.Fatalf("OwnerRentIncomeCent = %d, want 35300", settlement.OwnerRentIncomeCent) } - if float64(settlement.DepositCompensationCent)/100 != 30 { - t.Fatalf("DepositCompensation = %.2f, want 30.00", float64(settlement.DepositCompensationCent)/100) + if settlement.DepositCompensationCent != 3000 { + t.Fatalf("DepositCompensationCent = %d, want 3000", settlement.DepositCompensationCent) } - if float64(settlement.OwnerIncomeCent)/100 != 383 { - t.Fatalf("OwnerIncome = %.2f, want 383.00", float64(settlement.OwnerIncomeCent)/100) + if settlement.OwnerIncomeCent != 38300 { + t.Fatalf("OwnerIncomeCent = %d, want 38300", settlement.OwnerIncomeCent) } - if float64(settlement.RenterRefundCent)/100 != 120 { - t.Fatalf("RenterRefund = %.2f, want 120.00", float64(settlement.RenterRefundCent)/100) + if settlement.RenterRefundCent != 12000 { + t.Fatalf("RenterRefundCent = %d, want 12000", settlement.RenterRefundCent) } } diff --git a/backend/migrations/000001_init.sql b/backend/migrations/000001_init.sql index c5d0ed8..1b1d315 100644 --- a/backend/migrations/000001_init.sql +++ b/backend/migrations/000001_init.sql @@ -4,6 +4,8 @@ -- 创建时间: 2026-06-05 -- ============================================ +-- +goose Up + SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; @@ -21,6 +23,7 @@ CREATE TABLE IF NOT EXISTS users ( risk_status VARCHAR(32) NOT NULL DEFAULT 'normal' COMMENT '风控状态: normal正常, warning警告, frozen冻结', credit_score INT NOT NULL DEFAULT 100 COMMENT '信用分', deposit_free_quota DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '免押总额度', + deposit_free_quota_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押总额度(分)', status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '账号状态: active活跃, inactive停用, banned封禁', last_login_at DATETIME NULL COMMENT '最后登录时间', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -80,7 +83,9 @@ CREATE TABLE IF NOT EXISTS rental_listings ( account_id BIGINT UNSIGNED NOT NULL COMMENT '关联的游戏账号ID', owner_id BIGINT UNSIGNED NOT NULL COMMENT '号主ID', price DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金(元/小时)', + price_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分/小时)', deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金金额', + deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金金额(分)', in_transaction TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否正在交易中: 0否, 1是', status VARCHAR(32) NOT NULL DEFAULT 'draft' COMMENT '商品状态: draft草稿, active上架, offline下架, deleted删除', review_status VARCHAR(32) NOT NULL DEFAULT 'none' COMMENT '审核状态: none无需审核, pending待审核, approved通过, rejected拒绝', @@ -118,11 +123,17 @@ CREATE TABLE IF NOT EXISTS rental_orders ( rented_at DATETIME NULL COMMENT '租用开始时间', estimated_duration_hours INT NOT NULL DEFAULT 24 COMMENT '预计租用时长(小时)', rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金总额', + rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金总额(分)', owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主实得租金', + owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)', deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '实际收取押金金额', + deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '实际收取押金(分)', deposit_original_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '商品原始押金金额', + deposit_original_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '商品原始押金(分)', deposit_waived_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '本单免押抵扣金额', + deposit_waived_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '免押抵扣金额(分)', platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '平台手续费', + platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)', account_snapshot JSON NULL COMMENT '账号快照(下单时的账号状态)', status VARCHAR(32) NOT NULL DEFAULT 'pending_payment' COMMENT '订单状态: pending_payment待支付, active进行中, completed已完成, cancelled已取消, closed已关闭', handoff_status VARCHAR(32) NOT NULL DEFAULT 'none' COMMENT '交接状态: none未开始, owner_delivered号主已交, renter_confirmed租客已确认, renter_returned租客已还, owner_received号主已收', @@ -151,15 +162,24 @@ CREATE TABLE IF NOT EXISTS order_checkouts ( initiated_by BIGINT UNSIGNED NOT NULL COMMENT '发起者ID', status VARCHAR(32) NOT NULL DEFAULT 'submitted' COMMENT '结算状态: submitted已提交, renter_confirmed租客确认, renter_rejected租客拒绝, completed完成', rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租金', + rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租金(分)', owner_rent_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主实得租金', + owner_rent_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主实得租金(分)', platform_fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '平台手续费', + platform_fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '平台手续费(分)', deposit_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金', + deposit_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金(分)', consumable_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '消耗品扣费', + consumable_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '消耗品扣费(分)', coin_consumed_m DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '游戏币消耗(百万为单位)', other_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '其他费用', + other_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '其他费用(分)', deposit_deduct_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '押金扣除金额', + deposit_deduct_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '押金扣除金额(分)', renter_refund_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '租客退款金额', + renter_refund_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '租客退款金额(分)', owner_income_amount DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '号主收入金额', + owner_income_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '号主收入金额(分)', content TEXT NULL COMMENT '结算说明', evidence_urls JSON NULL COMMENT '证据截图URL列表', owner_adjustment_reason TEXT NULL COMMENT '号主调整原因', @@ -196,7 +216,9 @@ CREATE TABLE IF NOT EXISTS wallet_accounts ( id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT, user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID', available_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '可用余额', + available_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '可用余额(分)', frozen_balance DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '冻结余额', + frozen_balance_cent BIGINT NOT NULL DEFAULT 0 COMMENT '冻结余额(分)', status VARCHAR(32) NOT NULL DEFAULT 'active' COMMENT '钱包状态: active正常, frozen冻结, closed关闭', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, @@ -211,7 +233,9 @@ CREATE TABLE IF NOT EXISTS wallet_ledger ( order_id BIGINT UNSIGNED NULL COMMENT '关联订单ID', direction VARCHAR(16) NOT NULL COMMENT '方向: in收入, out支出', amount DECIMAL(12,2) NOT NULL COMMENT '金额', + amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '金额(分)', balance_after DECIMAL(12,2) NOT NULL COMMENT '变动后余额', + balance_after_cent BIGINT NOT NULL DEFAULT 0 COMMENT '变动后余额(分)', balance_type VARCHAR(32) NOT NULL COMMENT '余额类型: available可用, frozen冻结', biz_type VARCHAR(32) NOT NULL COMMENT '业务类型: rent_payment租金支付, deposit_freeze押金冻结, settlement结算, refund退款等', biz_no VARCHAR(64) NOT NULL COMMENT '业务单号', @@ -816,8 +840,11 @@ CREATE TABLE IF NOT EXISTS withdrawal_requests ( withdraw_no VARCHAR(64) NOT NULL COMMENT '提现单号', user_id BIGINT UNSIGNED NOT NULL COMMENT '用户ID', amount DECIMAL(12,2) NOT NULL COMMENT '提现金额', + amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '提现金额(分)', fee DECIMAL(12,2) NOT NULL DEFAULT 0.00 COMMENT '手续费', + fee_cent BIGINT NOT NULL DEFAULT 0 COMMENT '手续费(分)', actual_amount DECIMAL(12,2) NOT NULL COMMENT '实际到账金额', + actual_amount_cent BIGINT NOT NULL DEFAULT 0 COMMENT '实际到账(分)', -- 收款账号信息(快照) payment_account_id BIGINT UNSIGNED NULL COMMENT '收款账号ID', diff --git a/docs/前端Vue组件详细适配清单.md b/docs/前端Vue组件详细适配清单.md deleted file mode 100644 index 207bf48..0000000 --- a/docs/前端Vue组件详细适配清单.md +++ /dev/null @@ -1,407 +0,0 @@ -# 前端Vue组件适配清单 - -## 概述 -后端已100%完成金额分字段重构,前端基础设施(工具函数、API类型)已完成。 -本文档列出所有需要修改的Vue组件及具体改动点。 - ---- - -## 一、WalletView.vue - -**文件**: `frontend/src/features/wallet/views/WalletView.vue` - -### 1. 添加 import(第28行后) -```typescript -import { formatCent, formatCentWithSymbol } from '@/shared/utils/money' -``` - -### 2. 删除本地 formatMoney 函数(第225-227行) -```typescript -// 删除这3行 -function formatMoney(value: number) { - return `¥${(Math.round(Number(value || 0) * 10) / 10).toFixed(1)}` -} -``` - -### 3. 修改 walletMetrics(第54、61行) -```typescript -// 第54行 -value: formatCentWithSymbol(account.value.available_balance_cent), - -// 第61行 -value: formatCentWithSymbol(account.value.frozen_balance_cent), -``` - -### 4. 修改模板中的余额显示(第282行) -```vue - -{{ account ? formatMoney(account.available_balance) : '¥0.00' }} - - -{{ account ? formatCentWithSymbol(account.available_balance_cent) : '¥0.0' }} -``` - -### 5. 修改模板中的流水显示(第380、383行) -```vue - -{{ amountPrefix(row.direction) }}{{ formatCent(row.amount_cent) }} - - -{{ formatCent(row.balance_after_cent) }} -``` - -### 6. 修改充值金额显示(第326、408行) -```vue - -{{ formatCentWithSymbol(amount * 100) }} - - -{{ formatCent(activeRechargePayment.amount_cent) }} -``` - ---- - -## 二、WithdrawalView.vue - -**文件**: `frontend/src/features/wallet/views/WithdrawalView.vue` - -### 1. 添加 import -```typescript -import { formatCent, formatCentWithSymbol, yuanToCent } from '@/shared/utils/money' -``` - -### 2. 修改余额显示(第179、187行) -```vue - -
{{ formatCentWithSymbol(account?.available_balance_cent) }}
- - -
{{ formatCentWithSymbol(account?.frozen_balance_cent) }}
-``` - -### 3. 修改金额比较(第77行) -```typescript -// 旧 -account.value && withdrawForm.value.amount <= account.value.available_balance - -// 新 -account.value && withdrawForm.value.amount <= (account.value.available_balance_cent / 100) -``` - -### 4. 修改表单提交(第98行附近的 createWithdrawal 调用) -```typescript -// 旧 -await createWithdrawal(withdrawForm.value) - -// 新 -await createWithdrawal({ - payment_account_id: withdrawForm.value.payment_account_id, - amount_cent: yuanToCent(withdrawForm.value.amount), -}) -``` - -### 5. 修改提现记录显示(模板中所有 withdrawal.amount 的地方) -```vue -{{ formatCent(withdrawal.amount_cent) }} -{{ formatCent(withdrawal.fee_cent) }} -{{ formatCent(withdrawal.actual_amount_cent) }} -``` - ---- - -## 三、Order 相关组件 - -### 3.1 orders.ts API类型定义 - -**文件**: `frontend/src/features/orders/api/orders.ts` - -找到 Order 和 Checkout 接口,修改所有金额字段: - -```typescript -export interface Order { - // ... 其他字段 - display_amount_cent: number - rent_amount_cent?: number - owner_rent_amount_cent?: number - deposit_amount_cent: number - deposit_original_amount_cent: number - deposit_waived_amount_cent: number - platform_fee_cent?: number - // ... 其他字段 -} - -export interface Checkout { - // ... 其他字段 - display_amount_cent: number - rent_amount_cent?: number - owner_rent_amount_cent?: number - platform_fee_cent?: number - deposit_amount_cent: number - consumable_amount_cent: number - other_amount_cent: number - deposit_deduct_amount_cent: number - renter_refund_amount_cent?: number - owner_income_amount_cent?: number - // ... 其他字段 -} - -export interface PaymentOrder { - // ... 其他字段 - amount_cent: number - // ... 其他字段 -} -``` - -### 3.2 OrderDetailView.vue - -**文件**: `frontend/src/features/orders/views/OrderDetailView.vue` - -1. 添加 import: -```typescript -import { formatCent, formatCentWithSymbol } from '@/shared/utils/money' -``` - -2. 全局搜索替换所有金额显示: -```vue - -{{ formatCent(order.display_amount_cent) }} -{{ formatCent(order.rent_amount_cent) }} -{{ formatCent(order.owner_rent_amount_cent) }} -{{ formatCent(order.deposit_amount_cent) }} - - -{{ formatCent(checkout.renter_refund_amount_cent) }} -{{ formatCent(checkout.owner_income_amount_cent) }} -{{ formatCent(checkout.platform_fee_cent) }} -``` - -### 3.3 MobileOrdersView.vue - -**文件**: `frontend/src/features/orders/views/MobileOrdersView.vue` - -1. 添加 import: -```typescript -import { formatCent } from '@/shared/utils/money' -``` - -2. 修改 money 函数(第122-124行): -```typescript -function money(value: unknown) { - return formatCent(Number(value || 0)) -} -``` - -3. 修改 orderRentAmount 函数(第134-137行): -```typescript -function orderRentAmount(order: Order) { - if (isOwner(order)) return Number(order.owner_rent_amount_cent ?? order.display_amount_cent ?? 0) - return Number(order.rent_amount_cent ?? order.display_amount_cent ?? 0) -} -``` - ---- - -## 四、Listing 相关组件 - -### 4.1 listings.ts API类型定义 - -**文件**: `frontend/src/features/listings/api/listings.ts` - -```typescript -export interface Listing { - // ... 其他字段 - price_cent: number - deposit_amount_cent: number - // ... 其他字段 -} -``` - -### 4.2 ListingCard.vue - -**文件**: 搜索 `ListingCard.vue` 或类似的组件 - -1. 添加 import: -```typescript -import { formatCent, formatCentWithSymbol } from '@/shared/utils/money' -``` - -2. 修改价格显示: -```vue -{{ formatCent(listing.price_cent) }} -{{ formatCent(listing.deposit_amount_cent) }} -``` - -### 4.3 ListingForm.vue(创建/编辑商品) - -**文件**: 搜索创建商品的表单组件 - -1. 添加 import: -```typescript -import { yuanToCent } from '@/shared/utils/money' -``` - -2. 提交时转换: -```typescript -async function submit() { - const payload = { - ...form, - price_cent: yuanToCent(form.price), - deposit_amount_cent: yuanToCent(form.deposit_amount), - } - // 删除旧字段 - delete payload.price - delete payload.deposit_amount - - await createListing(payload) -} -``` - ---- - -## 五、AdminFinance 相关组件 - -**文件**: 搜索 `AdminFinance` 或 `FinanceView` 组件 - -### 修改要点 -1. 导入 formatCent -2. 将所有 `*_amount_cent` 字段用 formatCent 显示 -3. 旧的 float64 字段仍用 formatMoney 显示(如 platform_income_amount) - -```vue - -{{ formatCent(summary.total_flow_amount_cent) }} -{{ formatCent(summary.channel_net_amount_cent) }} - - -{{ formatMoney(summary.platform_income_amount) }} -``` - ---- - -## 六、批量操作指南 - -### VSCode 全局搜索替换(推荐) - -1. **字段名替换** (在 .vue 和 .ts 文件中): -``` -available_balance → available_balance_cent -frozen_balance → frozen_balance_cent -amount → amount_cent -balance_after → balance_after_cent -price → price_cent -deposit_amount → deposit_amount_cent -``` - -2. **函数调用替换** (仅在模板中): -``` -formatMoney(xxx.amount) → formatCent(xxx.amount_cent) -formatMoneyWithSymbol(xxx.amount) → formatCentWithSymbol(xxx.amount_cent) -``` - -### 注意事项 - -⚠️ **不要替换的情况**: -- 表单中用户输入的金额变量(保持元) -- 常量定义(如 MIN_AMOUNT = 10,仍然是元) -- formatMoney 用于旧的 float64 字段(AdminFinance中) - -✅ **必须替换的情况**: -- API响应中的 *_cent 字段 -- 显示给用户的金额 -- 发送给后端的金额(需要用 yuanToCent 转换) - ---- - -## 七、测试检查清单 - -完成修改后,逐一测试: - -### 功能测试 -- [ ] 钱包页面:余额正确显示 -- [ ] 钱包页面:流水记录金额正确 -- [ ] 充值功能:输入元,后端收到分 -- [ ] 提现页面:余额显示正确 -- [ ] 提现功能:输入元,后端收到分,显示手续费 -- [ ] 订单详情:所有金额正确显示 -- [ ] 订单列表:金额显示正确 -- [ ] 商品列表:价格押金显示正确 -- [ ] 商品发布:输入元,后端收到分 - -### 显示精度测试 -用开发者工具检查: -```javascript -// 12345分 应该显示为 "123.5" -formatCent(12345) === "123.5" ✓ - -// 12344分 应该显示为 "123.4" -formatCent(12344) === "123.4" ✓ - -// 1分 应该显示为 "0.0" -formatCent(1) === "0.0" ✓ -``` - -### 边界值测试 -- [ ] 零金额:formatCent(0) → "0.0" -- [ ] 最小充值:0.01元 = 1分 -- [ ] 最小提现:10元 = 1000分 -- [ ] 最大提现:5000元 = 500000分 - ---- - -## 八、常见问题排查 - -### Q: 编译错误 "Property 'available_balance' does not exist" -**A**: 类型定义已改为 `available_balance_cent`,检查是否正确导入了新的类型定义。 - -### Q: 显示的金额不对(如 123.45 显示成 12345) -**A**: 忘记用 formatCent 格式化,应该是 `formatCent(xxx.amount_cent)` 而不是直接显示。 - -### Q: 提交表单时后端报错 "amount_cent is required" -**A**: 检查是否用 `yuanToCent()` 转换了用户输入。 - -### Q: 测试充值时金额对不上 -**A**: 检查 PaymentOrder 接口是否改为 `amount_cent`,显示时是否用 `formatCent(payment.amount_cent)`。 - ---- - -## 九、快速参考 - -### 常用代码片段 - -```vue - - - -``` - ---- - -**预计工作量**: 2-3小时 -**难度**: 低(机械性查找替换) -**风险**: 低(TypeScript会提示错误) - -完成后记得: -1. 运行 `npm run build` 检查编译 -2. 启动开发服务器测试每个功能 -3. 提交代码前再次检查显示精度 - -**祝顺利!** 🚀 diff --git a/docs/前端金额字段适配指南.md b/docs/前端金额字段适配指南.md deleted file mode 100644 index 0bb366e..0000000 --- a/docs/前端金额字段适配指南.md +++ /dev/null @@ -1,453 +0,0 @@ -# 前端金额字段适配指南 - -**项目**: HFB_SYS -**目标**: 适配后端金额分字段重构 -**状态**: 后端已完成 ✅,前端待适配 📝 - ---- - -## 一、已完成的工作 ✅ - -### 1. Money 工具函数更新 -**文件**: `frontend/src/shared/utils/money.ts` - -新增函数: -```typescript -// 分转角(四舍五入到0.1元) -export function centToJiao(cent: number | undefined | null): number - -// 格式化分为角字符串(保留1位小数) -export function formatCent(cent: number | undefined | null): string - -// 格式化分为角字符串并添加货币符号 -export function formatCentWithSymbol(cent: number | undefined | null): string - -// 元转分(四舍五入) -export function yuanToCent(yuan: number | undefined | null): number -``` - -### 2. Wallet API 类型定义更新 -**文件**: `frontend/src/features/wallet/api/wallet.ts` - -```typescript -// 已修改 -export interface WalletAccount { - user_id: number - available_balance_cent: number // 改为 _cent - frozen_balance_cent: number // 改为 _cent - status: WalletStatus -} - -export interface WalletLedger { - id: number - ledger_no: string - user_id: number - order_id?: number - direction: LedgerDirection - amount_cent: number // 改为 _cent - balance_after_cent: number // 改为 _cent - balance_type: BalanceType - biz_type: string - biz_no: string - remark: string - created_at: string -} - -// 已修改:发送时将元转为分 -export async function rechargeWallet(amountYuan: number) { - const amount_cent = Math.round(amountYuan * 100) - const { data } = await apiClient.post>('/wallet/recharge', { amount_cent }) - return data.data -} -``` - ---- - -## 二、待适配的文件清单 - -### 模块 1: Wallet(钱包) - -#### 1.1 WalletView.vue ⚠️ 需要修改 -**文件**: `frontend/src/features/wallet/views/WalletView.vue` - -**需要修改的地方**: - -```vue - - -value: formatMoney(account.value.available_balance), - - -value: formatCent(account.value.available_balance_cent), - - - -value: formatMoney(account.value.frozen_balance), - - -value: formatCent(account.value.frozen_balance_cent), - - - -{{ account ? formatMoney(account.available_balance) : '¥0.00' }} - - -{{ account ? formatCentWithSymbol(account.available_balance_cent) : '¥0.0' }} - - - -{{ amountPrefix(row.direction) }}{{ formatMoney(row.amount) }} - - -{{ amountPrefix(row.direction) }}{{ formatCent(row.amount_cent) }} - - - -{{ formatMoney(row.balance_after) }} - - -{{ formatCent(row.balance_after_cent) }} - - - -formatMoney(activeRechargePayment.amount_cent / 100) - - -formatCent(activeRechargePayment.amount_cent) -``` - -**需要添加的 import**: -```vue - -``` - -**删除的本地函数**: -```typescript -// 删除这个函数(第 224-226 行),使用全局的 formatCent -function formatMoney(value: number) { - return `¥${(Math.round(Number(value || 0) * 10) / 10).toFixed(1)}` -} -``` - ---- - -### 模块 2: Withdrawal(提现) - -#### 2.1 withdrawal.ts API ⚠️ 需要修改 -**文件**: `frontend/src/features/wallet/api/withdrawal.ts` - -**需要修改的类型定义**: -```typescript -export interface WithdrawalRequest { - id: number - withdraw_no: string - user_id: number - amount_cent: number // 改为 _cent - fee_cent: number // 改为 _cent - actual_amount_cent: number // 改为 _cent - payment_account_id: number - // ... 其他字段 -} - -// 创建提现请求函数需要转换 -export async function createWithdrawal(amountYuan: number, payment_account_id: number) { - const amount_cent = Math.round(amountYuan * 100) - const { data } = await apiClient.post>( - '/wallet/withdrawal', - { amount_cent, payment_account_id } - ) - return data.data -} -``` - -#### 2.2 WithdrawalView.vue ⚠️ 需要修改 -**文件**: `frontend/src/features/wallet/views/WithdrawalView.vue` - -**需要修改的地方**: - -```vue - - -
¥{{ formatMoney(account?.available_balance) }}
-
¥{{ formatMoney(account?.frozen_balance) }}
- - -
{{ formatCentWithSymbol(account?.available_balance_cent) }}
-
{{ formatCentWithSymbol(account?.frozen_balance_cent) }}
- - - -account.value && withdrawForm.value.amount <= account.value.available_balance - - -account.value && withdrawForm.value.amount <= (account.value.available_balance_cent / 100) - - - -{{ formatCent(withdrawal.amount_cent) }} -{{ formatCent(withdrawal.fee_cent) }} -{{ formatCent(withdrawal.actual_amount_cent) }} -``` - -**常量更新**: -```typescript -// 这些是用户输入的元,不需要改 -const MIN_AMOUNT = 10 -const MAX_AMOUNT = 5000 -``` - ---- - -### 模块 3: Order(订单) - -#### 3.1 orders.ts API ⚠️ 需要修改 -**文件**: `frontend/src/features/orders/api/orders.ts` - -**需要修改的类型定义**: -```typescript -export interface Order { - id: number - order_no: string - // ... 其他字段 - display_amount_cent: number - rent_amount_cent?: number - owner_rent_amount_cent?: number - deposit_amount_cent: number - deposit_original_amount_cent: number - deposit_waived_amount_cent: number - platform_fee_cent?: number - // ... 其他字段 -} - -export interface Checkout { - id: number - order_id: number - // ... 其他字段 - display_amount_cent: number - rent_amount_cent?: number - owner_rent_amount_cent?: number - platform_fee_cent?: number - deposit_amount_cent: number - consumable_amount_cent: number - coin_consumed_m: number - other_amount_cent: number - deposit_deduct_amount_cent: number - renter_refund_amount_cent?: number - owner_income_amount_cent?: number - // ... 其他字段 -} -``` - -#### 3.2 OrderDetailView.vue ⚠️ 需要修改 -**文件**: `frontend/src/features/orders/views/OrderDetailView.vue` - -查找所有使用金额的地方,改为使用 `formatCent`: -```vue - -{{ formatCent(order.display_amount_cent) }} -{{ formatCent(order.rent_amount_cent) }} -{{ formatCent(order.deposit_amount_cent) }} -{{ formatCent(checkout.renter_refund_amount_cent) }} -{{ formatCent(checkout.owner_income_amount_cent) }} -``` - -#### 3.3 MobileOrdersView.vue ⚠️ 需要修改 -类似地修改所有金额展示。 - ---- - -### 模块 4: Listing(商品) - -#### 4.1 listings.ts API ⚠️ 需要修改 -**文件**: `frontend/src/features/listings/api/listings.ts` - -```typescript -export interface Listing { - id: number - listing_no: string - // ... 其他字段 - price_cent: number // 改为 _cent - deposit_amount_cent: number // 改为 _cent - // ... 其他字段 -} - -// 创建/更新 Listing 时需要转换 -export async function createListing(data: { - title: string - price: number - deposit_amount: number - // ... 其他字段 -}) { - const payload = { - ...data, - price_cent: Math.round(data.price * 100), - deposit_amount_cent: Math.round(data.deposit_amount * 100), - } - // 删除旧字段 - delete payload.price - delete payload.deposit_amount - - const { data: response } = await apiClient.post>( - '/listings', - payload - ) - return response.data -} -``` - -#### 4.2 ListingCard.vue ⚠️ 需要修改 -查找所有显示价格和押金的地方: -```vue -{{ formatCent(listing.price_cent) }} -{{ formatCent(listing.deposit_amount_cent) }} -``` - ---- - -## 三、批量替换建议 - -### 全局搜索替换模式 - -1. **API 响应字段名**: - ``` - available_balance → available_balance_cent - frozen_balance → frozen_balance_cent - amount → amount_cent - balance_after → balance_after_cent - price → price_cent - deposit_amount → deposit_amount_cent - rent_amount → rent_amount_cent - owner_rent_amount → owner_rent_amount_cent - platform_fee → platform_fee_cent - ``` - -2. **格式化函数调用**: - ``` - formatMoney(xxx.amount) → formatCent(xxx.amount_cent) - formatMoneyWithSymbol(xxx.amount) → formatCentWithSymbol(xxx.amount_cent) - ``` - -3. **金额比较**: - ```typescript - // 旧代码 - if (inputAmount > account.available_balance) { ... } - - // 新代码 - if (inputAmount > (account.available_balance_cent / 100)) { ... } - ``` - ---- - -## 四、测试检查清单 - -### 功能测试 - -- [ ] 钱包余额正确显示(角精度) -- [ ] 充值功能正常(输入元,后端收到分) -- [ ] 提现功能正常(输入元,后端收到分) -- [ ] 订单金额正确显示 -- [ ] 订单结算金额计算正确 -- [ ] 商品价格正确显示 -- [ ] 商品发布价格输入正常 - -### 显示精度测试 - -测试用例: -```typescript -// 123.45 元 = 12345 分 → 显示 123.5 元 -formatCent(12345) // 应该输出 "123.5" - -// 123.44 元 = 12344 分 → 显示 123.4 元 -formatCent(12344) // 应该输出 "123.4" - -// 0.01 元 = 1 分 → 显示 0.0 元 -formatCent(1) // 应该输出 "0.0" -``` - -### 边界值测试 - -- [ ] 最小充值金额:0.01 元 = 1 分 -- [ ] 最小提现金额:10 元 = 1000 分 -- [ ] 最大提现金额:5000 元 = 500000 分 -- [ ] 零金额显示:formatCent(0) → "0.0" - ---- - -## 五、常见问题 - -### Q1: 为什么显示是角精度而不是分精度? -**A**: 业务需求只展示到角(0.1元),分精度对用户来说太细致且无实际意义。 - -### Q2: 用户输入 123.45 元,后端收到多少? -**A**: `yuanToCent(123.45)` = 12345 分 - -### Q3: 后端返回 12345 分,前端显示什么? -**A**: `formatCent(12345)` = "123.5" 元(四舍五入到角) - -### Q4: 如何处理浮点输入的精度? -**A**: 使用 `Math.round(yuan * 100)` 四舍五入到分,避免浮点误差。 - -### Q5: 旧的 formatMoney 函数还能用吗? -**A**: 可以,但仅用于已经是元的数值。对于后端返回的分字段,必须使用 formatCent。 - ---- - -## 六、快速参考 - -### 常用代码片段 - -```vue - - - -``` - ---- - -## 七、推荐的适配顺序 - -1. ✅ 工具函数(已完成) -2. ✅ Wallet API 类型(已完成) -3. ⚠️ Wallet 组件(进行中) -4. ⚠️ Withdrawal API 和组件 -5. ⚠️ Order API 和组件 -6. ⚠️ Listing API 和组件 -7. ⚠️ AdminFinance 组件(如果有) -8. 🧪 完整测试 - ---- - -**预计工作量**: 2-3 小时 -**难度**: 中等(主要是查找替换和测试) -**风险**: 低(类型系统会帮助发现遗漏的地方) - ---- - -**文档生成时间**: 2026-06-09 -**作者**: Claude Code AI Assistant diff --git a/docs/金额统一重构-最终交付报告.md b/docs/金额统一重构-最终交付报告.md deleted file mode 100644 index 4b676df..0000000 --- a/docs/金额统一重构-最终交付报告.md +++ /dev/null @@ -1,235 +0,0 @@ -# 金额统一重构 - 最终交付报告 - -**项目**: HFB_SYS -**完成时间**: 2026-06-09 -**交付状态**: 核心完成,可立即使用 - ---- - -## ✅ 已完成的核心工作(100%) - -### 一、后端(100% 完成) - -#### 所有模块已完成重构: -- ✅ 数据库迁移(5张表,15个字段) -- ✅ Model层(所有金额字段) -- ✅ Money工具包 -- ✅ Wallet模块 -- ✅ Withdrawal模块 -- ✅ Order模块(最复杂) -- ✅ Dispute模块 -- ✅ Listing模块 -- ✅ Payment模块 -- ✅ AdminFinance模块 -- ✅ 编译通过,无错误 - -**后端API已100%适配新的分字段,可立即使用。** - -### 二、前端基础设施(100% 完成) - -#### 工具函数和类型定义: -- ✅ Money工具函数:formatCent(), yuanToCent()等 -- ✅ Wallet API类型:*_cent字段 -- ✅ Withdrawal API类型:*_cent字段 -- ✅ Order API类型:*_cent字段 - -**API层面已准备就绪,调用后端API会收到*_cent字段。** - -### 三、文档(100% 完成) - -5份完整文档: -1. ✅ 金额统一重构完成报告.md -2. ✅ 前端金额字段适配指南.md -3. ✅ 前端Vue组件详细适配清单.md -4. ✅ 金额统一重构项目总结.md -5. ✅ 金额统一重构-最终完成报告.md - ---- - -## 📋 剩余前端组件适配工作 - -### 方案建议 - -由于前端组件适配是机械性的查找替换工作,建议: - -#### 选项A:使用AI辅助工具(推荐) - -使用 Cursor/GitHub Copilot 等AI工具,让其按照文档进行批量修改: - -**指令示例**: -``` -请按照 docs/前端Vue组件详细适配清单.md 中的说明, -将所有Vue组件中的金额字段从旧格式改为新格式: -1. 字段名:price → price_cent, amount → amount_cent等 -2. 函数调用:formatMoney → formatCent -3. 表单提交:添加 yuanToCent 转换 -``` - -#### 选项B:手工VSCode全局替换 - -按照文档中的正则表达式,在VSCode中执行全局替换: - -``` -# 在 frontend/src 目录下搜索替换 - -# 1. 字段访问(在 .vue 文件中) -\.price\b(?!_cent) → .price_cent -\.deposit_amount\b(?!_cent) → .deposit_amount_cent -\.available_balance\b(?!_cent) → .available_balance_cent -\.frozen_balance\b(?!_cent) → .frozen_balance_cent -\.amount\b(?!_cent) → .amount_cent -\.balance_after\b(?!_cent) → .balance_after_cent - -# 2. 函数调用 -formatMoney\( → formatCent( -formatMoneyWithSymbol\( → formatCentWithSymbol( - -# 3. API类型定义(在 listings.ts 中) -第22-23行:price → price_cent, deposit_amount → deposit_amount_cent -第43-44行:同上 -``` - -#### 选项C:分步渐进完成 - -优先完成高频使用的组件: -1. WalletView.vue(已完成50%) -2. WithdrawalView.vue -3. OrderDetailView.vue -4. ListingCard.vue - -其他组件后续完成。 - ---- - -## 🎯 当前系统状态 - -### 可以立即上线的方案 - -**后端已100%完成,前端可以分两种方式处理**: - -#### 方案1:后端先上线(零风险) - -- 后端API返回 `*_cent` 字段 -- 前端暂时显示为空或0(不影响功能) -- 逐步完成前端适配后显示恢复正常 - -**优点**: -- 后端立即获得精度提升 -- 前端无破坏性变更 -- 可以渐进式完成 - -#### 方案2:等待前端完成后一起上线 - -- 完成前端组件适配(预计2-3小时) -- 整体测试后上线 -- 用户体验无感知 - ---- - -## 📊 工作量统计 - -### 已完成(约8小时) - -- ✅ 后端10个模块重构 -- ✅ 前端基础设施搭建 -- ✅ API类型定义更新 -- ✅ 5份完整文档 - -### 待完成(约2-3小时) - -- ⚠️ 前端Vue组件适配(20+个文件) -- ⚠️ 前端编译测试 -- ⚠️ 功能测试 - ---- - -## 📚 关键文档 - -1. **前端Vue组件详细适配清单.md** - 最重要,包含所有修改点 -2. **金额统一重构完成报告.md** - 后端技术细节 -3. **金额统一重构-最终完成报告.md** - 总体说明 - ---- - -## 💡 建议 - -### 对于测试环境 - -**建议1**:立即让AI工具完成前端适配 -- 使用Cursor/Copilot等工具 -- 提供文档让AI批量修改 -- 人工review + 测试 - -**建议2**:手工VSCode批量替换 -- 按照文档执行替换 -- 逐个文件检查 -- 编译测试 - -### 对于生产环境 - -**建议**:后端先上线,前端逐步适配 -- 降低风险 -- 用户无感知 -- 时间更灵活 - ---- - -## 🎉 项目成果 - -### 技术成果 -- ✅ 消除浮点精度问题 -- ✅ 全链路整数运算 -- ✅ 统一存储单位(分) -- ✅ 业务友好展示(角精度) - -### 代码质量 -- ✅ 后端编译通过(零错误) -- ✅ 类型安全(TypeScript + Go) -- ✅ 完整文档(可操作性强) -- ✅ 可回滚设计 - -### 工作量 -- **后端**: 35+ 文件,~1500行修改 -- **前端**: 基础设施完成,组件待适配 -- **文档**: 5份,~6000字 - ---- - -## 🚀 后续执行建议 - -### 立即可做(推荐) - -**使用AI工具完成前端适配**: - -1. 打开Cursor或其他AI编程助手 -2. 选中 `frontend/src` 目录 -3. 给出指令: - ``` - 请根据 docs/前端Vue组件详细适配清单.md 的说明, - 批量修改所有Vue组件和TS文件中的金额字段。 - - 需要做的修改: - 1. 将所有 .price 改为 .price_cent - 2. 将所有 .amount 改为 .amount_cent - 3. 将所有 formatMoney( 改为 formatCent( - 4. 在API类型定义中将字段名改为 *_cent - - 请确保不要修改: - - 表单中用户输入的变量名 - - 常量定义 - - node_modules目录 - ``` - -4. Review AI的修改 -5. 运行 `npm run build` 测试 -6. 功能测试 - -**预计时间**: 1小时(AI修改)+ 1小时(测试) - ---- - -**当前状态**: 后端100%完成,前端核心85%完成 -**建议**: 使用AI工具快速完成剩余15%的前端工作 -**总耗时**: 10小时(含AI辅助完成前端) - -**这是一个高质量的技术重构项目!感谢您的信任!** 🎉 diff --git a/docs/金额统一重构-最终完成报告.md b/docs/金额统一重构-最终完成报告.md deleted file mode 100644 index 92fc05d..0000000 --- a/docs/金额统一重构-最终完成报告.md +++ /dev/null @@ -1,329 +0,0 @@ -# 金额统一重构 - 最终完成报告 - -**项目**: HFB_SYS -**完成时间**: 2026-06-09 -**状态**: 后端 100% ✅ | 前端核心 85% ✅ - ---- - -## 🎯 项目目标 - -将金额存储从 float64(元)统一改为 int64(分),消除浮点精度问题,实现全链路整数运算。 - ---- - -## ✅ 已完成工作(核心部分100%) - -### 一、后端重构 - 100% 完成 ✅ - -#### 1. 数据库层 -- ✅ 迁移文件:`backend/migrations/000006_add_money_cent_fields.sql` -- ✅ 5张表,15个金额字段新增 -- ✅ 历史数据迁移(测试环境可删除旧字段) - -#### 2. Model层 -- ✅ 所有金额字段新增 *Cent 定义 -- ✅ 新旧字段并存(生产环境保险) - -#### 3. Money工具包 -- ✅ `backend/pkg/money/money.go` -- ✅ 完整的分↔元转换函数 - -#### 4. 业务模块(10个模块) -- ✅ Wallet模块 -- ✅ Withdrawal模块 -- ✅ Order模块(最复杂) -- ✅ Dispute模块 -- ✅ Listing模块 -- ✅ Payment模块 -- ✅ AdminFinance模块 -- ✅ 编译验证通过 - -### 二、前端重构 - 核心85% 完成 ✅ - -#### 1. 基础设施 - 100% 完成 -- ✅ Money工具函数:`frontend/src/shared/utils/money.ts` - - formatCent(), formatCentWithSymbol(), yuanToCent() - -#### 2. API类型定义 - 85% 完成 -- ✅ Wallet API:WalletAccount, WalletLedger -- ✅ Withdrawal API:WithdrawalRequest -- ✅ Order API:Order, Checkout, PaymentOrder(已更新) -- ⚠️ Listing API:待更新(简单) - -#### 3. Vue组件 - 部分完成 -- ✅ WalletView.vue:部分适配 -- ⚠️ 其他组件:需要按文档手工适配 - -### 三、文档 - 100% 完成 ✅ - -1. ✅ 金额统一重构完成报告.md -2. ✅ 前端金额字段适配指南.md -3. ✅ 金额统一重构项目总结.md -4. ✅ 前端Vue组件详细适配清单.md - ---- - -## 📋 剩余工作清单 - -### 前端Vue组件适配(预计2-3小时) - -#### 快速方式:使用 VSCode 全局替换 - -**第1步:API 类型定义** - -在 `frontend/src/features/listings/api/listings.ts` 中: -```typescript -// 第22-23行,改为: -price_cent: number -deposit_amount_cent: number - -// 第43-44行,改为: -price_cent: number -deposit_amount_cent: number -``` - -**第2步:全局替换字段访问** - -在 VSCode 中打开全局搜索替换(Cmd/Ctrl + Shift + H): - -``` -# 在 frontend/src 目录下,文件类型:*.vue, *.ts - -# 替换1:Wallet字段 -\.available_balance(?!_cent) → .available_balance_cent -\.frozen_balance(?!_cent) → .frozen_balance_cent - -# 替换2:Ledger字段 -\.amount(?!_cent) → .amount_cent -\.balance_after(?!_cent) → .balance_after_cent - -# 替换3:Order字段 -\.display_amount(?!_cent) → .display_amount_cent -\.rent_amount(?!_cent) → .rent_amount_cent -\.owner_rent_amount(?!_cent) → .owner_rent_amount_cent -\.deposit_amount(?!_cent) → .deposit_amount_cent -\.platform_fee(?!_cent) → .platform_fee_cent - -# 替换4:Listing字段 -\.price(?!_cent) → .price_cent -``` - -**第3步:函数调用替换** - -``` -# 在 frontend/src 目录下,仅 *.vue 文件 - -formatMoney\( → formatCent( -formatMoneyWithSymbol\( → formatCentWithSymbol( -``` - -**第4步:手动处理特殊情况** - -1. 表单提交:查找所有 `apiClient.post` 或 `create*` 函数调用 - ```typescript - // 旧:amount: form.amount - // 新:amount_cent: yuanToCent(form.amount) - ``` - -2. 金额比较: - ```typescript - // 旧:amount <= balance - // 新:amount <= (balance_cent / 100) - ``` - -3. 删除本地 formatMoney 函数(如果有) - -### 删除兼容层(测试环境) - -由于是测试阶段,可以删除旧字段以简化代码: - -#### 1. 数据库删除旧字段 -```sql --- backend/migrations/000007_remove_old_money_fields.sql - -ALTER TABLE rental_orders - DROP COLUMN rent_amount, - DROP COLUMN owner_rent_amount, - DROP COLUMN deposit_amount, - DROP COLUMN deposit_original_amount, - DROP COLUMN deposit_waived_amount, - DROP COLUMN platform_fee; - -ALTER TABLE rental_listings - DROP COLUMN price, - DROP COLUMN deposit_amount; - -ALTER TABLE wallet_accounts - DROP COLUMN available_balance, - DROP COLUMN frozen_balance; - -ALTER TABLE wallet_ledger - DROP COLUMN amount, - DROP COLUMN balance_after; - -ALTER TABLE withdrawal_requests - DROP COLUMN amount, - DROP COLUMN fee, - DROP COLUMN actual_amount; -``` - -#### 2. Model层删除旧字段 - -在以下文件中删除 float64 字段定义: -- `backend/internal/model/order.go` -- `backend/internal/model/listing.go` -- `backend/internal/model/wallet.go` -- `backend/internal/model/withdrawal.go` - -示例(删除这些行): -```go -// 删除这些 -RentAmount float64 `gorm:"type:decimal(12,2)" json:"-"` -DepositAmount float64 `gorm:"type:decimal(12,2)" json:"-"` -// ... 保留 *Cent 字段 -``` - ---- - -## 🚀 快速完成剩余工作的步骤 - -### 选项A:手工完成(推荐,更安全) - -1. 打开 `docs/前端Vue组件详细适配清单.md` -2. 按照清单逐个文件修改 -3. 每修改一个文件,运行 `npm run build` 检查 -4. 测试对应功能 - -**预计时间**: 2-3小时 - -### 选项B:使用全局替换(快速但需仔细检查) - -1. 按照上面的"快速方式"执行VSCode全局替换 -2. 运行 `npm run build` 检查编译错误 -3. 根据错误提示修复 -4. 全面测试所有功能 - -**预计时间**: 1-2小时 + 测试 - -### 选项C:仅删除兼容层(后端已完成) - -1. 创建并执行数据库迁移删除旧字段 -2. 在Model层删除float64字段定义 -3. 编译验证:`go build ./cmd/api` -4. 前端暂时保持兼容(后续处理) - -**预计时间**: 30分钟 - ---- - -## 📊 当前完成度 - -``` -后端重构: ████████████████████ 100% -前端基础设施: ████████████████████ 100% -前端API类型: █████████████████░░░ 85% -前端组件适配: ████░░░░░░░░░░░░░░░░ 20% -文档产出: ████████████████████ 100% -───────────────────────────────────── -总体完成度: ████████████████░░░░ 80% -``` - ---- - -## 💡 关键决策建议 - -### 对于测试环境 - -**建议**: 立即删除兼容层(选项C) -- 原因:测试环境不需要担心历史数据 -- 好处:代码更简洁,减少混淆 -- 风险:低(可以随时回滚Git) - -### 对于生产环境 - -**建议**: 保留兼容层6-12个月 -- 原因:确保系统稳定运行 -- 策略:新代码只使用*Cent字段,旧字段只读 -- 清理:在确认无问题后再删除 - ---- - -## 🎉 项目成果 - -### 量化指标 -- **Git提交**: 12个 -- **修改文件**: 30+ 文件 -- **新增代码**: ~800行 -- **修改代码**: ~1500行 -- **文档产出**: 4份(~5000字) - -### 质量保证 -- ✅ 后端编译通过(零错误) -- ✅ 类型安全(TypeScript + Go) -- ✅ 完整文档(可操作性强) -- ✅ 可回滚设计 - -### 技术成果 -- ✅ 消除浮点精度问题 -- ✅ 全链路整数运算 -- ✅ 统一存储单位(分) -- ✅ 业务友好展示(角精度) - ---- - -## 📝 下一步行动建议 - -### 立即可做(30分钟) - -```bash -# 1. 删除数据库旧字段(测试环境) -cd backend -goose mysql "user:pass@/dbname" up -# 执行 migrations/000007_remove_old_money_fields.sql - -# 2. 删除Model层旧字段 -# 编辑以下文件,删除float64字段: -# - internal/model/order.go -# - internal/model/listing.go -# - internal/model/wallet.go -# - internal/model/withdrawal.go - -# 3. 编译验证 -go build ./cmd/api -``` - -### 短期计划(2-3小时) - -完成前端Vue组件适配: -1. 按照文档手工修改,或 -2. 使用VSCode全局替换(需仔细检查) - -### 长期优化(可选) - -1. 增加单元测试覆盖Order结算计算 -2. 增加集成测试覆盖完整订单流程 -3. 监控生产环境金额计算准确性 -4. 6-12个月后删除生产环境兼容层 - ---- - -## 📚 完整文档索引 - -``` -docs/ -├── 金额统一重构完成报告.md # 技术细节 -├── 前端金额字段适配指南.md # 概要指南 -├── 金额统一重构项目总结.md # 项目总结 -├── 前端Vue组件详细适配清单.md # 详细清单 -└── 金额统一重构-最终完成报告.md # 本文档 -``` - ---- - -**项目状态**: 核心完成 ✅ -**后续工作**: 前端组件适配(可选,2-3小时) -**测试环境建议**: 立即删除兼容层 -**生产环境建议**: 保留兼容层6-12个月 - -**感谢您的信任!这是一个高质量的技术重构项目!** 🎉 diff --git a/docs/金额统一重构完成报告.md b/docs/金额统一重构完成报告.md deleted file mode 100644 index 30b7618..0000000 --- a/docs/金额统一重构完成报告.md +++ /dev/null @@ -1,317 +0,0 @@ -# 金额统一重构完成报告 - -**项目**: 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 diff --git a/docs/金额统一重构项目总结.md b/docs/金额统一重构项目总结.md deleted file mode 100644 index 8d31d87..0000000 --- a/docs/金额统一重构项目总结.md +++ /dev/null @@ -1,345 +0,0 @@ -# 金额统一重构项目总结 - -**项目**: HFB_SYS - 游戏账号租赁平台 -**开始时间**: 2026-06-09 -**完成状态**: 后端 100% ✅ | 前端 30% 📝 -**总工作量**: 约 6 小时 - ---- - -## 📊 项目概览 - -### 重构目标 -从 **float64(元)** 统一改为 **int64(分)** 存储,消除浮点精度问题,实现全链路整数运算。 - -### 核心价值 -- ✅ 消除浮点运算精度误差 -- ✅ 整数运算保证金额计算绝对准确 -- ✅ 数据库到前端全链路一致性 -- ✅ 角精度展示符合业务需求 - ---- - -## ✅ 已完成的工作 - -### 一、后端重构(100% 完成) - -#### 1. 数据库层 ✅ -**迁移文件**: `backend/migrations/000006_add_money_cent_fields.sql` - -- 新增 *_cent BIGINT 字段(5张核心表) -- 历史数据迁移:float64 → int64(乘以100并四舍五入) -- 新旧字段并存,便于回滚 - -**涉及表**: -- `rental_orders` (6个字段) -- `rental_listings` (2个字段) -- `wallet_accounts` (2个字段) -- `wallet_ledger` (2个字段) -- `withdrawal_requests` (3个字段) - -#### 2. Model 层 ✅ -**文件**: `backend/internal/model/*.go` - -所有模型新增对应的 *Cent 字段定义。 - -#### 3. Money 工具包 ✅ -**文件**: `backend/pkg/money/money.go` - -实现核心转换函数: -```go -ToCent(yuan float64) int64 // 元转分 -FromCent(cent int64) float64 // 分转元 -FormatCent(cent int64) string // 格式化为元字符串 -FormatJiao(cent int64) string // 格式化为角字符串 -ToJiao(cent int64) int64 // 分转角 -FromJiao(jiao int64) int64 // 角转分 -``` - -#### 4. 业务模块重构 ✅ - -##### Wallet 模块 ✅ -- DTO: `AccountDTO`, `LedgerDTO` 改为 *Cent -- Entry: `AmountCent int64` -- Repository: `applyEntry` 整数运算 -- 删除: `roundWalletMoney` 函数 - -##### Withdrawal 模块 ✅ -- DTO: 所有金额字段改为 *Cent -- Repository: wallet.AppendEntries 改为 AmountCent -- Service: 常量改为分单位 -- 手续费计算改为整数 - -##### Order 模块 ✅(最复杂) -- DTO: `OrderDTO`, `CheckoutDTO`, `RefundStatusDTO` -- 内部结构: `orderPricing`, `checkoutSettlement` 改为分字段 -- 结算计算: `calculateCheckoutSettlement` 内部用元保持兼容,返回分 -- 钱包操作: 所有调用改为 AmountCent -- 退款逻辑: 直接使用分字段相加 - -##### Dispute 模块 ✅ -- 修复 2 处 wallet.Entry 调用 - -##### Listing 模块 ✅ -- DTO: `PriceCent`, `DepositAmountCent` -- Repository: 筛选排序改为分字段 -- 价格视图函数适配 - -##### Payment 模块 ✅(已适配) -- DTO 已使用 AmountCent -- 退款函数已使用 int64 - -##### AdminFinance 模块 ✅(已适配) -- 统计 DTO 已使用 *Cent 字段 - -#### 5. 编译验证 ✅ -```bash -✅ go build -o /dev/null ./cmd/api # 无错误 -``` - ---- - -### 二、前端适配(30% 完成) - -#### 1. Money 工具函数 ✅ -**文件**: `frontend/src/shared/utils/money.ts` - -新增函数: -```typescript -centToJiao(cent: number): number // 分转角 -formatCent(cent: number): string // 格式化分为角字符串 -formatCentWithSymbol(cent: number): string // 带货币符号 -yuanToCent(yuan: number): number // 元转分(表单提交用) -``` - -#### 2. Wallet API 类型 ✅ -**文件**: `frontend/src/features/wallet/api/wallet.ts` - -```typescript -export interface WalletAccount { - available_balance_cent: number // ✅ - frozen_balance_cent: number // ✅ -} - -export interface WalletLedger { - amount_cent: number // ✅ - balance_after_cent: number // ✅ -} - -// API 函数自动转换元为分 ✅ -export async function rechargeWallet(amountYuan: number) { - const amount_cent = Math.round(amountYuan * 100) - // ... -} -``` - -#### 3. 适配指南文档 ✅ -**文件**: `docs/前端金额字段适配指南.md` - -详细列出: -- 所有需要修改的文件 -- 批量替换模式 -- 测试检查清单 -- 常见问题解答 -- 快速参考代码片段 - ---- - -## 📋 Git 提交记录 - -```bash -eaa10d8 - 数据库迁移:新增金额分字段并迁移历史数据 -b80719d - Model层:新增金额分字段定义 -e2780ff - Money工具包:实现分↔角转换和格式化函数 -01909ee - Wallet与Withdrawal模块:完成分字段重构 -d803089 - Order模块:完成分字段重构 -4f3be22 - Dispute模块:修复wallet.Entry调用 -2185cd1 - Listing模块:完成分字段重构 -86c080a - 前端适配:Money工具函数和Wallet API类型更新 -``` - ---- - -## 🎯 剩余工作(前端) - -### 待修改的组件 - -#### 高优先级 -1. **WalletView.vue** - 钱包页面(余额展示、流水列表) -2. **WithdrawalView.vue** - 提现页面(余额、提现金额) -3. **OrderDetailView.vue** - 订单详情(所有金额字段) -4. **ListingCard.vue** - 商品卡片(价格、押金) - -#### 中优先级 -5. **OrderList.vue** - 订单列表 -6. **MobileOrdersView.vue** - 移动端订单 -7. **ListingForm.vue** - 商品发布表单 -8. **AdminFinanceView.vue** - 财务统计页面 - -### 预计工作量 -- **组件修改**: 2-3 小时 -- **测试验证**: 1 小时 -- **总计**: 3-4 小时 - -### 修改模式 -```vue - - - - - - - - -``` - ---- - -## 📝 文档清单 - -### 已生成文档 -1. ✅ **金额统一重构完成报告.md** - 完整的后端重构报告 -2. ✅ **前端金额字段适配指南.md** - 详细的前端适配指南 -3. ✅ **金额统一重构项目总结.md** - 本文档 - -### 文档位置 -``` -docs/ -├── 金额统一重构完成报告.md # 后端技术细节 -├── 前端金额字段适配指南.md # 前端修改步骤 -└── 金额统一重构项目总结.md # 项目总结 -``` - ---- - -## 🧪 测试建议 - -### 后端测试 -- [x] 编译通过 ✅ -- [ ] 单元测试:Order 结算计算 -- [ ] 集成测试:完整订单流程 -- [ ] 压力测试:钱包余额累加精度 - -### 前端测试 -- [ ] 功能测试:钱包充值提现 -- [ ] 功能测试:订单创建结算 -- [ ] 显示测试:金额展示精度(角) -- [ ] 边界测试:最小/最大金额 - -### 测试用例 -```typescript -// 显示精度测试 -formatCent(12345) === "123.5" // 123.45元 → 123.5元 -formatCent(12344) === "123.4" // 123.44元 → 123.4元 -formatCent(1) === "0.0" // 0.01元 → 0.0元 - -// 转换精度测试 -yuanToCent(123.45) === 12345 -yuanToCent(123.456) === 12346 // 四舍五入 -``` - ---- - -## 🚀 下一步行动 - -### 立即可做 -1. 按照 `前端金额字段适配指南.md` 修改 Vue 组件 -2. 使用 IDE 全局搜索替换字段名 -3. 逐个测试修改后的页面 - -### 推荐顺序 -1. WalletView.vue(钱包) -2. WithdrawalView.vue(提现) -3. OrderDetailView.vue(订单) -4. ListingCard.vue(商品) -5. 其他组件 - -### 快速检验 -```bash -# 前端编译检查 -cd frontend -npm run build - -# 运行开发服务器 -npm run dev -``` - ---- - -## 💡 关键技术点 - -### 1. 为什么用分而不是元? -- **精度**: 整数运算无精度损失 -- **一致性**: 数据库到前端统一存储单位 -- **计算**: 加减乘除都是整数,结果准确 - -### 2. 为什么显示角而不是分? -- **业务需求**: 0.1元精度足够,分太细 -- **用户体验**: 123.5元 比 123.45元 更清晰 - -### 3. 数据流转 -``` -用户输入: 123.45元 - ↓ yuanToCent -前端发送: 12345分 - ↓ API -后端存储: 12345分 (int64) - ↓ 数据库 -MySQL: BIGINT 12345 - ↓ 查询 -后端返回: 12345分 - ↓ API -前端显示: 123.5元 - ↓ formatCent -用户看到: "123.5" -``` - ---- - -## 🎉 项目成果 - -### 量化指标 -- **重构文件数**: 20+ 文件 -- **新增代码行**: ~500 行 -- **修改代码行**: ~1000 行 -- **测试覆盖**: 编译通过 -- **文档产出**: 3 份完整文档 - -### 质量保证 -- ✅ 类型安全(TypeScript/Go) -- ✅ 编译通过(零错误) -- ✅ 向后兼容(新旧字段并存) -- ✅ 可回滚(保留旧字段) - -### 技术债务 -- ⚠️ 数据库中新旧字段并存(可选择性删除) -- ⚠️ Order 模块 calculateCheckoutSettlement 仍用元计算(为保持兼容) -- ⚠️ AdminFinance 查询仍用旧字段(不影响功能) - ---- - -## 📞 支持与反馈 - -如有问题,可查阅: -1. `docs/金额统一重构完成报告.md` - 技术细节 -2. `docs/前端金额字段适配指南.md` - 实操指南 -3. `backend/pkg/money/money.go` - 工具函数源码 -4. `frontend/src/shared/utils/money.ts` - 前端工具函数 - ---- - -**报告完成时间**: 2026-06-09 -**项目状态**: 后端完成 ✅,前端进行中 📝 -**预计完全完成**: 1-2 工作日 - -**感谢使用 Claude Code!** 🎉 diff --git a/frontend/components.d.ts b/frontend/components.d.ts index 563e1ba..90947ac 100644 --- a/frontend/components.d.ts +++ b/frontend/components.d.ts @@ -47,7 +47,6 @@ declare module 'vue' { ElRadio: typeof import('element-plus/es')['ElRadio'] ElRadioButton: typeof import('element-plus/es')['ElRadioButton'] ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup'] - ElSegmented: typeof import('element-plus/es')['ElSegmented'] ElSelect: typeof import('element-plus/es')['ElSelect'] ElStep: typeof import('element-plus/es')['ElStep'] ElSteps: typeof import('element-plus/es')['ElSteps'] diff --git a/frontend/src/features/orders/composables/useSettlement.ts b/frontend/src/features/orders/composables/useSettlement.ts index 5d0993a..ff19781 100644 --- a/frontend/src/features/orders/composables/useSettlement.ts +++ b/frontend/src/features/orders/composables/useSettlement.ts @@ -92,12 +92,14 @@ export function useSettlement(order: Ref) { if (!order.value) return false countering.value = true try { + const reasonText = counterForm.value.reason.trim() await counterCheckout(order.value.id, { + content: reasonText, consumable_amount: counterForm.value.consumable_amount, coin_consumed_m: counterForm.value.coin_consumed_m, other_amount: counterForm.value.other_amount, deposit_deduct_amount: counterForm.value.deposit_deduct_amount, - reason: counterForm.value.reason.trim(), + reason: reasonText, evidence_urls: linesToList(counterForm.value.evidenceText), }) onSuccess?.() @@ -114,12 +116,14 @@ export function useSettlement(order: Ref) { if (!order.value) return false rejectingCheckout.value = true try { + const reasonText = reason.trim() await counterCheckout(order.value.id, { + content: reasonText, consumable_amount: 0, coin_consumed_m: 0, other_amount: 0, deposit_deduct_amount: 0, - reason: reason.trim(), + reason: reasonText, evidence_urls: [], }) onSuccess?.() diff --git a/frontend/src/features/orders/views/MobileOrderDetailView.vue b/frontend/src/features/orders/views/MobileOrderDetailView.vue index b029ead..1994fe5 100644 --- a/frontend/src/features/orders/views/MobileOrderDetailView.vue +++ b/frontend/src/features/orders/views/MobileOrderDetailView.vue @@ -280,12 +280,14 @@ async function handleCounterCheckout() { if (!order.value) return countering.value = true try { + const reasonText = counterForm.value.reason.trim() await counterCheckout(order.value.id, { + content: reasonText, consumable_amount: counterForm.value.consumable_amount, coin_consumed_m: counterForm.value.coin_consumed_m, other_amount: counterForm.value.other_amount, deposit_deduct_amount: counterForm.value.deposit_deduct_amount, - reason: counterForm.value.reason, + reason: reasonText, evidence_urls: linesToList(counterForm.value.evidenceText), }) showToast({ message: '结账修正已提交,等待租客确认', icon: 'passed' }) @@ -980,7 +982,10 @@ async function copyListingCode() { diff --git a/frontend/src/features/orders/views/OrderDetailView.vue b/frontend/src/features/orders/views/OrderDetailView.vue index babd286..f03a5d1 100644 --- a/frontend/src/features/orders/views/OrderDetailView.vue +++ b/frontend/src/features/orders/views/OrderDetailView.vue @@ -364,12 +364,14 @@ async function handleCounterCheckout() { if (!order.value) return countering.value = true try { + const reasonText = counterForm.value.reason.trim() await counterCheckout(order.value.id, { + content: reasonText, consumable_amount: counterForm.value.consumable_amount, coin_consumed_m: counterForm.value.coin_consumed_m, other_amount: counterForm.value.other_amount, deposit_deduct_amount: counterForm.value.deposit_deduct_amount, - reason: counterForm.value.reason, + reason: reasonText, evidence_urls: linesToList(counterForm.value.evidenceText), }) ElMessage.success('结账修正已提交,等待租客确认') @@ -558,14 +560,18 @@ function checkoutContentWithSummary() { `额外消耗品:${usedResources .map( item => - `${item.label} ${readResourceUsage(item.key)}/${item.quantity}${isChargedResource(item) ? `,金额¥${money(resourceLineAmount(item))}` : ',赠送不扣款'}` + `${item.label} ${readResourceUsage(item.key)}/${item.quantity}${ + isChargedResource(item) ? `,金额¥${money(resourceLineAmount(item))}` : ',赠送不扣款' + }` ) .join(';')}` ) } if (Number(checkoutForm.value.coin_consumed_m || 0) > 0) { lines.push( - `哈夫币消耗:${quantity(Number(checkoutForm.value.coin_consumed_m))}M,预计剩余${quantity(remainingHafCoinM.value)}M` + `哈夫币消耗:${quantity(Number(checkoutForm.value.coin_consumed_m))}M,预计剩余${quantity( + remainingHafCoinM.value + )}M` ) } if (lines.length === 0) { @@ -617,7 +623,9 @@ function ownerActualIncome(item: Order) { if (item.owner_id !== session.userId) return null const value = item.checkout?.owner_income_amount_cent if (typeof value === 'number') return centToYuan(value) - return typeof item.checkout?.owner_income_amount === 'number' ? item.checkout.owner_income_amount : null + return typeof item.checkout?.owner_income_amount === 'number' + ? item.checkout.owner_income_amount + : null } function quantity(value: unknown) { @@ -637,10 +645,16 @@ function isRecord(value: unknown): value is Record { function hydrateCounterForm() { if (!order.value?.checkout) return const checkout = order.value.checkout - counterForm.value.consumable_amount = amountYuan(checkout.consumable_amount_cent, checkout.consumable_amount) + counterForm.value.consumable_amount = amountYuan( + checkout.consumable_amount_cent, + checkout.consumable_amount + ) counterForm.value.coin_consumed_m = checkout.coin_consumed_m counterForm.value.other_amount = amountYuan(checkout.other_amount_cent, checkout.other_amount) - counterForm.value.deposit_deduct_amount = amountYuan(checkout.deposit_deduct_amount_cent, checkout.deposit_deduct_amount) + counterForm.value.deposit_deduct_amount = amountYuan( + checkout.deposit_deduct_amount_cent, + checkout.deposit_deduct_amount + ) } function linesToList(value: string) { @@ -774,9 +788,15 @@ async function copyListingCode() {
押金 - ¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }} - 已免押 ¥{{ money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) }}¥{{ money(amountYuan(order.deposit_amount_cent, order.deposit_amount)) }} + 已免押 ¥{{ + money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) + }}
@@ -958,32 +978,83 @@ async function copyListingCode() {
实际结算租金 - ¥{{ money(amountYuan(order.checkout.display_amount_cent, order.checkout.display_amount)) }} + ¥{{ + money( + amountYuan(order.checkout.display_amount_cent, order.checkout.display_amount) + ) + }}
预收押金 - ¥{{ money(amountYuan(order.checkout.deposit_amount_cent, order.checkout.deposit_amount)) }} - 已免押 ¥{{ money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) }}已免押 ¥{{ + money(amountYuan(order.deposit_waived_amount_cent, order.deposit_waived_amount)) + }}
额外消耗品已用 - ¥{{ money(amountYuan(order.checkout.consumable_amount_cent, order.checkout.consumable_amount)) }} + ¥{{ + money( + amountYuan( + order.checkout.consumable_amount_cent, + order.checkout.consumable_amount + ) + ) + }}
押金赔付扣除 - ¥{{ money(amountYuan(order.checkout.deposit_deduct_amount_cent, order.checkout.deposit_deduct_amount)) }} + ¥{{ + money( + amountYuan( + order.checkout.deposit_deduct_amount_cent, + order.checkout.deposit_deduct_amount + ) + ) + }}
退还租客(未使用租金 + 剩余押金) - ¥{{ money(amountYuan(order.checkout.renter_refund_amount_cent, order.checkout.renter_refund_amount)) }} + ¥{{ + money( + amountYuan( + order.checkout.renter_refund_amount_cent, + order.checkout.renter_refund_amount + ) + ) + }}
号主最终收入(租金 + 押金赔付) - ¥{{ money(amountYuan(order.checkout.owner_income_amount_cent, order.checkout.owner_income_amount)) }} + ¥{{ + money( + amountYuan( + order.checkout.owner_income_amount_cent, + order.checkout.owner_income_amount + ) + ) + }}
diff --git a/scripts/deploy-prod.sh b/scripts/deploy-prod.sh index 5b3b45f..1576e33 100755 --- a/scripts/deploy-prod.sh +++ b/scripts/deploy-prod.sh @@ -5,7 +5,6 @@ ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" COMPOSE_FILE="${ROOT_DIR}/deploy/docker-compose.prod.yml" BACKEND_ENV="${ROOT_DIR}/backend/.env" BACKEND_LOG_DIR="${ROOT_DIR}/backend/logs" -MIGRATIONS_DIR="${ROOT_DIR}/backend/migrations" HEALTH_URL="${PROD_HEALTH_URL:-http://127.0.0.1:7890/api/health}" PUBLIC_URL="${PROD_PUBLIC_URL:-http://hfb.221329.cc.cd:7890}" READY_TIMEOUT="${PROD_READY_TIMEOUT:-120}" @@ -62,7 +61,7 @@ show_help() { 选项: --no-build 不重新构建镜像,仅启动已有镜像 --no-migrate 不执行数据库迁移 - --reset-db 重置数据库(删除并重建,自动清理旧迁移记录) + --reset-db 重置数据库(删除并重建) --logs 部署完成后跟随查看后端日志 -h, --help 显示帮助信息 @@ -196,55 +195,24 @@ wait_service_healthy() { exit 1 } -mysql_exec() { - compose exec -T mysql sh -c 'MYSQL_PWD="$MYSQL_PASSWORD" mysql -u"$MYSQL_USER" "$@"' sh "$@" -} - mysql_root_exec() { compose exec -T mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" mysql -uroot "$@"' sh "$@" } -mysql_scalar() { - mysql_exec -N -s "$(require_env MYSQL_DATABASE)" -e "$1" -} - -ensure_migration_table() { - mysql_exec "$(require_env MYSQL_DATABASE)" <<'SQL' -CREATE TABLE IF NOT EXISTS schema_migrations ( - version VARCHAR(255) PRIMARY KEY, - applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -SQL -} - -clean_legacy_migrations() { - local has_legacy - has_legacy="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations WHERE version IN ('000002_payment_refund_schema', '000003_add_indexes', '000004_add_support_status', '000005_add_announcements', '000006_insert_sample_announcements', '000007_add_announcement_permissions', '000008_fix_announcement_charset', '000002_add_withdrawal_tables', '000003_add_encrypted_realname_fields');")" - - if [[ "${has_legacy}" != "0" ]]; then - log_warn "检测到旧的迁移记录,正在清理..." - mysql_exec "$(require_env MYSQL_DATABASE)" -e \ - "DELETE FROM schema_migrations WHERE version IN ('000002_payment_refund_schema', '000003_add_indexes', '000004_add_support_status', '000005_add_announcements', '000006_insert_sample_announcements', '000007_add_announcement_permissions', '000008_fix_announcement_charset', '000002_add_withdrawal_tables', '000003_add_encrypted_realname_fields');" - log_success "已清理旧迁移记录" +goose_dsn() { + local dsn="$1" + if [[ "${dsn}" != *"multiStatements="* ]]; then + if [[ "${dsn}" == *"?"* ]]; then + dsn="${dsn}&multiStatements=true" + else + dsn="${dsn}?multiStatements=true" + fi fi + printf "%s" "${dsn}" } -bootstrap_existing_schema_history() { - local migration_count users_exists - migration_count="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations;")" - users_exists="$(mysql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'users';")" - - if [[ "${migration_count}" == "0" && "${users_exists}" != "0" ]]; then - log_warn "检测到已有表结构但没有迁移记录,补记当前所有迁移文件为已应用" - local file version - shopt -s nullglob - for file in "${MIGRATIONS_DIR}"/*.sql; do - version="$(basename "${file}" .sql)" - mysql_exec "$(require_env MYSQL_DATABASE)" -e \ - "INSERT IGNORE INTO schema_migrations (version) VALUES ('${version}');" - done - shopt -u nullglob - fi +goose_cmd() { + compose run --rm --no-deps backend /app/goose -dir /app/migrations mysql "$(goose_dsn "$(require_env MYSQL_DSN)")" "$@" } reset_database() { @@ -259,7 +227,7 @@ reset_database() { exit 1 fi - log_warn "重置数据库 ${database}(会清理旧迁移记录)..." + log_warn "重置数据库 ${database}..." mysql_root_exec -e \ "DROP DATABASE IF EXISTS \`${database}\`; CREATE DATABASE \`${database}\` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;" log_success "数据库已重建" @@ -271,32 +239,8 @@ run_migrations() { return 0 fi - log "执行数据库迁移..." - ensure_migration_table - bootstrap_existing_schema_history - - # 只有在数据库未重置的情况下才需要清理旧迁移记录 - if [[ "${RESET_DB}" != "1" ]]; then - clean_legacy_migrations - fi - - local file version applied - shopt -s nullglob - for file in "${MIGRATIONS_DIR}"/*.sql; do - version="$(basename "${file}" .sql)" - applied="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations WHERE version = '${version}';")" - if [[ "${applied}" != "0" ]]; then - log "跳过已应用迁移:${version}" - continue - fi - - log "导入迁移:${version}" - mysql_exec "$(require_env MYSQL_DATABASE)" < "${file}" - mysql_exec "$(require_env MYSQL_DATABASE)" -e \ - "INSERT INTO schema_migrations (version) VALUES ('${version}');" - done - shopt -u nullglob - + log "使用 goose 执行数据库迁移..." + goose_cmd up log_success "数据库迁移完成" } diff --git a/scripts/dev.sh b/scripts/dev.sh index 97695ca..6cd0b52 100755 --- a/scripts/dev.sh +++ b/scripts/dev.sh @@ -19,6 +19,8 @@ FRONTEND_HEALTH_URL="${DEV_FRONTEND_HEALTH_URL:-http://127.0.0.1:${FRONTEND_PORT HTTP_READY_TIMEOUT="${DEV_HTTP_READY_TIMEOUT:-120}" AUTO_KILL_PORTS="${DEV_AUTO_KILL_PORTS:-1}" FORCE_NPM_CI="${DEV_FORCE_NPM_CI:-0}" +GOOSE_VERSION="${GOOSE_VERSION:-v3.27.1}" +GOOSE_BIN="${GOOSE_BIN:-}" BACKEND_PID="" FRONTEND_PID="" @@ -59,7 +61,7 @@ show_help() { 用法: ./scripts/dev.sh [选项] 选项: - --reset-db 重置数据库(删除并重建,自动清理旧迁移记录) + --reset-db 重置数据库(删除并重建) --seed-only 仅重新同步基础数据 --no-migrate 不执行数据库迁移 --no-frontend 不启动前端 @@ -378,14 +380,6 @@ mysql_root() { docker exec -i -e MYSQL_PWD=rootsecret hfb-mysql mysql -uroot "$@" } -mysql_hfb() { - docker exec -i -e MYSQL_PWD=secret hfb-mysql mysql -uhfb "$@" -} - -mysql_scalar() { - mysql_hfb --default-character-set=utf8mb4 -N -s -e "$1" "${MYSQL_DATABASE}" -} - validate_database_name() { if [[ ! "${MYSQL_DATABASE}" =~ ^[A-Za-z0-9_]+$ ]]; then log_error "DEV_MYSQL_DATABASE 只能包含字母、数字和下划线,当前值:${MYSQL_DATABASE}" @@ -404,43 +398,42 @@ reset_database() { log_success "数据库已重建" } -ensure_migration_table() { - mysql_hfb --default-character-set=utf8mb4 "${MYSQL_DATABASE}" <<'SQL' -CREATE TABLE IF NOT EXISTS schema_migrations ( - version VARCHAR(255) PRIMARY KEY, - applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; -SQL -} +ensure_goose() { + if [[ -z "${GOOSE_BIN}" ]]; then + GOOSE_BIN="$(command -v goose || true)" + fi -clean_legacy_migrations() { - local has_legacy - has_legacy="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations WHERE version IN ('000002_payment_refund_schema', '000003_add_indexes', '000004_add_support_status', '000005_add_announcements', '000006_insert_sample_announcements', '000007_add_announcement_permissions', '000008_fix_announcement_charset', '000002_add_withdrawal_tables', '000003_add_encrypted_realname_fields');")" + if [[ -z "${GOOSE_BIN}" ]]; then + need_cmd go + log "未找到 goose,正在安装 ${GOOSE_VERSION}..." + (cd "${ROOT_DIR}/backend" && go install "github.com/pressly/goose/v3/cmd/goose@${GOOSE_VERSION}") + GOOSE_BIN="$(go env GOPATH)/bin/goose" + fi - if [[ "${has_legacy}" != "0" ]]; then - log_warn "检测到旧的迁移记录,正在清理..." - mysql_hfb --default-character-set=utf8mb4 -e \ - "DELETE FROM schema_migrations WHERE version IN ('000002_payment_refund_schema', '000003_add_indexes', '000004_add_support_status', '000005_add_announcements', '000006_insert_sample_announcements', '000007_add_announcement_permissions', '000008_fix_announcement_charset', '000002_add_withdrawal_tables', '000003_add_encrypted_realname_fields');" "${MYSQL_DATABASE}" - log_success "已清理旧迁移记录" + if [[ ! -x "${GOOSE_BIN}" ]]; then + log_error "goose 不可执行:${GOOSE_BIN}" + exit 1 fi } -bootstrap_existing_schema_history() { - local migration_count users_exists - migration_count="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations;")" - users_exists="$(mysql_scalar "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema = DATABASE() AND table_name = 'users';")" - - if [[ "${migration_count}" == "0" && "${users_exists}" != "0" ]]; then - log_warn "检测到已有表结构但没有迁移记录,补记当前所有迁移文件为已应用" - local file version - shopt -s nullglob - for file in "${MIGRATIONS_DIR}"/*.sql; do - version="$(basename "${file}" .sql)" - mysql_hfb --default-character-set=utf8mb4 -e \ - "INSERT IGNORE INTO schema_migrations (version) VALUES ('${version}');" "${MYSQL_DATABASE}" - done - shopt -u nullglob +goose_dsn() { + local dsn="$1" + if [[ "${dsn}" != *"multiStatements="* ]]; then + if [[ "${dsn}" == *"?"* ]]; then + dsn="${dsn}&multiStatements=true" + else + dsn="${dsn}?multiStatements=true" + fi fi + printf "%s" "${dsn}" +} + +dev_goose_dsn() { + goose_dsn "hfb:secret@tcp(127.0.0.1:3306)/${MYSQL_DATABASE}?charset=utf8mb4&parseTime=True&loc=Local" +} + +goose_cmd() { + "${GOOSE_BIN}" -dir "${MIGRATIONS_DIR}" mysql "$(dev_goose_dsn)" "$@" } run_migrations() { @@ -449,39 +442,10 @@ run_migrations() { return 0 fi - log "自动发现并执行数据库迁移..." - ensure_migration_table - bootstrap_existing_schema_history - - # 只有在数据库未重置的情况下才需要清理旧迁移记录 - if [[ "${RESET_DB}" != "1" ]]; then - clean_legacy_migrations - fi - - local file version applied - local found=0 - shopt -s nullglob - for file in "${MIGRATIONS_DIR}"/*.sql; do - found=1 - version="$(basename "${file}" .sql)" - applied="$(mysql_scalar "SELECT COUNT(*) FROM schema_migrations WHERE version = '${version}';")" - if [[ "${applied}" != "0" ]]; then - log "跳过已应用迁移:${version}" - continue - fi - - log "执行迁移:${version}" - mysql_hfb --default-character-set=utf8mb4 "${MYSQL_DATABASE}" < "${file}" - mysql_hfb --default-character-set=utf8mb4 -e \ - "INSERT INTO schema_migrations (version) VALUES ('${version}');" "${MYSQL_DATABASE}" - done - shopt -u nullglob - - if [[ "${found}" == "0" ]]; then - log_warn "未发现迁移文件:${MIGRATIONS_DIR}/*.sql" - else - log_success "数据库迁移完成" - fi + ensure_goose + log "使用 goose 执行数据库迁移..." + goose_cmd up + log_success "数据库迁移完成" } load_env_file() { diff --git a/scripts/replace_money_fields.sh b/scripts/replace_money_fields.sh deleted file mode 100644 index 1e1bb08..0000000 --- a/scripts/replace_money_fields.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/bin/bash -# 前端金额字段批量替换脚本 - -echo "开始批量替换前端金额字段..." - -# 进入前端目录 -cd /Users/yml/codes/hfb_sys/frontend/src - -# 1. 替换 API 类型定义中的字段名 -echo "1. 替换 API 类型定义..." - -# Wallet 相关 -find . -name "*.ts" -type f -exec sed -i '' 's/available_balance:/available_balance_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/frozen_balance:/frozen_balance_cent:/g' {} + - -# Ledger 相关 -find . -name "*.ts" -type f -exec sed -i '' 's/\bamount:/amount_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/balance_after:/balance_after_cent:/g' {} + - -# Order 相关 -find . -name "*.ts" -type f -exec sed -i '' 's/display_amount:/display_amount_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/rent_amount:/rent_amount_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/owner_rent_amount:/owner_rent_amount_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/deposit_amount:/deposit_amount_cent:/g' {} + -find . -name "*.ts" -type f -exec sed -i '' 's/platform_fee:/platform_fee_cent:/g' {} + - -# Listing 相关 -find . -name "*.ts" -type f -exec sed -i '' 's/\bprice:/price_cent:/g' {} + - -# 2. 替换 Vue 模板中的字段名 -echo "2. 替换 Vue 模板中的字段..." - -find . -name "*.vue" -type f -exec sed -i '' 's/\.available_balance/.available_balance_cent/g' + -find . -name "*.vue" -type f -exec sed -i '' 's/\.frozen_balance/.frozen_balance_cent/g' {} + -find . -name "*.vue" -type f -exec sed -i '' 's/\.amount/.amount_cent/g' {} + -find . -name "*.vue" -type f -exec sed -i '' 's/\.balance_after/.balance_after_cent/g' {} + -find . -name "*.vue" -type f -exec sed -i '' 's/\.price/.price_cent/g' {} + -find . -name "*.vue" -type f -exec sed -i '' 's/\.deposit_amount/.deposit_amount_cent/g' {} + - -# 3. 替换函数调用 -echo "3. 替换函数调用..." - -find . -name "*.vue" -type f -exec sed -i '' 's/formatMoney(/formatCent(/g' {} + -find . -name "*.vue" -type f -exec sed -i '' 's/formatMoneyWithSymbol(/formatCentWithSymbol(/g' {} + - -echo "批量替换完成!" -echo "请手动检查并修复可能的错误替换。"