# 整型金额迁移计划 — 禁止多精度类型 > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** 消除全栈所有 decimal/double 类型,使用整型(分/千分比)替代,前端统一做展示转换 **架构:** 分 4 阶段推进。P0 核心电商(Product/ProductOrder)→ P1 套餐/活动/服务(GuidePackage/Activity/ServiceContent)→ P2 支付/佣金/会员(PaymentOrder/Commission/Membership)→ P3 评分/配置/先天属性(Teacher rating/Energy ratio/Zodiac/Bazi)。数据库逐表迁移(新增 INT 列→迁移数据→删除旧 DECIMAL 列→重命名)。 **核心原则:** - 金额以 **分** 存储(`1元 = 100`),前端展示 `(cents / 100).toFixed(2)` - 百分比以 **千分比** 存储(`23.5% = 235`),前端展示 `(permillage / 10) + '%'` - 评分以 **百分制整数** 存储(`4.5 = 450`),前端展示 `(value / 100).toFixed(1)` - 比例/权重以 **万分比** 存储(`0.15 = 1500`),计算时除以 10000 - DTO 层字段名不变,类型统一为 `Integer`,前端做转换 **Tech Stack:** Java 8, Spring Boot 2.7, MyBatis-Plus, MySQL 8.0, uni-app Vue 2 --- ## 文件改动总览 | 文件 | 操作 | 任务 | 优先级 | |------|------|------|--------| | `Product.java` | 修改 | T1 | P0 | | `ProductDTO.java` | 修改 | T1 | P0 | | `ProductOrder.java` | 修改 | T2 | P0 | | `ProductOrderDTO.java` | 修改 | T2 | P0 | | `ProductOrderService.java` | 修改 | T2 | P0 | | `utils/format.js` | **新建** | T3 | P0 | | 前端价格展示位置(6 处) | 修改 | T3 | P0 | | `DatabaseInitializer.java` | 修改 | T4 | P0 | | `AdminCommissionController.java` | 修改 | T4 | P0 | | SQL 迁移脚本 | 新增 | T4 | P0 | | `Activity.java` / `ActivityDTO.java` | 修改 | T5 | P1 | | `ServiceContent.java` / DTOs | 修改 | T5 | P1 | | `GuidePackage.java` / DTOs | 修改 | T5 | P1 | | 套餐/服务相关 Service | 修改 | T5 | P1 | | `PaymentOrder.java` / `PaymentOrderDTO.java` | 修改 | T6 | P2 | | `CommissionRecord.java` / DTOs | 修改 | T6 | P2 | | `CommissionService.java` | 修改 | T6 | P2 | | `WithdrawalRequest.java` / `WithdrawalService.java` | 修改 | T6 | P2 | | `MembershipLevel.java` / `MembershipService.java` | 修改 | T6 | P2 | | `Guide.java` / `Teacher.java` rating | 修改 | T7 | P3 | | `EnergyDimensionConfig.java` / `EnergySourceConfig.java` | 修改 | T7 | P3 | | `ZodiacConfig.java` / `BaziConfig.java` / `BloodTypeConfig.java` | 修改 | T8 | P3 | | `FamilyMemberAttributes.java` | 修改 | T8 | P3 | | `FamilyMemberAttributeService.java` | 修改 | T8 | P3 | --- ## Phase 0 — 工具函数 (P0) ### Task 0: 新建前端金额格式化工具函数 **Files:** - Create: `cfc-frontend/utils/format.js` - [ ] **Step 1: 创建 format.js** ```javascript /** * 金额格式化工具 * 后端统一使用整型(分)存储金额,前端在此转换展示 */ /** * 分 → 元,保留2位小数 * @param {number|null} cents 金额(分) * @returns {string|null} "12.50" 或 null */ export function formatPrice(cents) { if (cents == null || cents === undefined) return null return (cents / 100).toFixed(2) } /** * 分 → 元,带 ¥ 前缀 * @param {number|null} cents 金额(分) * @returns {string} "¥12.50" 或 "登录查看" */ export function formatPriceWithSymbol(cents) { if (cents == null || cents === undefined) return '登录查看' return '¥' + (cents / 100).toFixed(2) } /** * 千分比 → 百分比字符串 * @param {number|null} permillage 千分比(235 = 23.5%) * @returns {string} "23.5%" 或 "--" */ export function formatRate(permillage) { if (permillage == null || permillage === undefined) return '--' return (permillage / 10) + '%' } /** * 百分制评分 → 展示值 * @param {number|null} value 百分制(450 = 4.5分) * @param {number} fixed 小数位数 * @returns {string} "4.5" 或 "--" */ export function formatScore(value, fixed) { if (fixed === undefined) fixed = 1 if (value == null || value === undefined) return '--' return (value / 100).toFixed(fixed) } ``` - [ ] **Step 2: 编译验证** 运行 `lsp_diagnostics` 确认无语法错误。 --- ## Phase 1 — 核心电商金额 (P0 🔴) ### Task 1: Product 实体 + DTO — BigDecimal → Integer(分) **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/Product.java` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductDTO.java` - [ ] **Step 1: Product.java 字段类型修改** ```java // 修改前 import java.math.BigDecimal; // ... private BigDecimal price; private BigDecimal memberPrice; private BigDecimal profitRate; // 修改后 — 删除 import java.math.BigDecimal; // ... private Integer price; // 价格(分),展示时 /100 private Integer memberPrice; // 会员价(分) private Integer profitRate; // 利润率(千分比),23.5% → 235 ``` - [ ] **Step 2: ProductDTO.java 同步修改** ```java // 删除 import java.math.BigDecimal; private Integer price; private Integer memberPrice; private Integer profitRate; ``` 检查 DTO 中的转换逻辑(如有 `d.price = p.getPrice()` 等),类型改为 `Integer` 后赋值不变(`Integer` → `Integer` 无需转换)。 - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS`(仅改 entity + dto 无编译依赖错误) --- ### Task 2: ProductOrder 实体 + DTO + Service — 整型金额 **Files:** - Modify: `cfc-backend/src/main/java/com/etotem/cfc/entity/ProductOrder.java` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/dto/ProductOrderDTO.java` - Modify: `cfc-backend/src/main/java/com/etotem/cfc/service/ProductOrderService.java` - [ ] **Step 1: ProductOrder.java 字段类型修改** ```java // 删除 import java.math.BigDecimal; private Integer unitPrice; // 单价(分) private Integer totalAmount; // 总价(分) ``` - [ ] **Step 2: ProductOrderDTO.java 同步修改** ```java // 删除 import java.math.BigDecimal; private Integer unitPrice; private Integer totalAmount; ``` - [ ] **Step 3: ProductOrderService.java 计算逻辑更新** ```java // 修改前 (约第65行): order.setTotalAmount(product.getPrice().multiply(BigDecimal.valueOf(dto.getQuantity()))); // 修改后: order.setUnitPrice(product.getPrice()); order.setTotalAmount(product.getPrice() * dto.getQuantity()); ``` - [ ] **Step 4: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` --- ### Task 3: 前端价格展示 — 全部改用 formatPriceWithSymbol **Files:** - Modify: `cfc-frontend/pages/index/index.vue`(未登录态商品列表) - Modify: `cfc-frontend/components/DimensionProducts.vue` - Modify: `cfc-frontend/pages/body/index.vue`(如有直接价格渲染) - Modify: `cfc-frontend/pages/mind/index.vue`(如有直接价格渲染) - Modify: `cfc-frontend/pages/action/index.vue`(如有直接价格渲染) - Modify: `cfc-frontend/pages/shop/index.vue`(商城价格) - [ ] **Step 1: 在 utils/api.js 或相关位置引入 format 工具** 在 `main.js` 中全局注册(或在各页面分别 import): ```javascript // main.js import { formatPriceWithSymbol, formatPrice, formatRate } from '@/utils/format' // 挂载到 Vue 原型,所有组件可通过 this.formatPriceWithSymbol() 访问 Vue.prototype.formatPriceWithSymbol = formatPriceWithSymbol Vue.prototype.formatPrice = formatPrice Vue.prototype.formatRate = formatRate ``` - [ ] **Step 2: 逐文件替换价格渲染** 对所有前端文件中形如 `¥{{ item.price }}` 的渲染,改为 `{{ formatPriceWithSymbol(item.price) }}`。 **搜索清单(grep 查找 `¥{{` 或 `¥ ` 在 .vue 文件中):** ```bash grep -rn '¥' cfc-frontend/pages/ cfc-frontend/components/ --include='*.vue' ``` 每个匹配位置都需修改: ```vue ¥{{ item.price }} {{ formatPriceWithSymbol(item.price) }} ``` - [ ] **Step 3: 检查 v-if 条件** ```vue ¥{{ item.price }} {{ formatPriceWithSymbol(item.price) }} 会员{{ formatPriceWithSymbol(item.memberPrice) }} ``` - [ ] **Step 4: LSP 诊断** 运行 `lsp_diagnostics` 确认无语法错误。 --- ### Task 4: 后端 Controller + DatabaseInitializer + SQL 迁移 **Files:** - Modify: `cfc-backend/.../controller/product/ProductController.java` - Modify: `cfc-backend/.../config/DatabaseInitializer.java` - Modify: `cfc-backend/.../controller/admin/AdminCommissionController.java` - Create: `cfc-backend/src/main/resources/db/migration/V20260615__integer_price.sql` - [ ] **Step 1: ProductController 价格置 null 逻辑** ```java // 游客隐藏价格逻辑不变,类型已改为 Integer public Result list(@RequestBody(required = false) Map params, @RequestAttribute(value = "userId", required = false) Long userId) { // ... 正常查询逻辑 List productList = ...; if (userId == null) { for (ProductDTO dto : productList) { dto.setPrice(null); dto.setMemberPrice(null); } } return Result.success(productList); } ``` 类型可赋值 `null`,逻辑无需改动(只需确认编译通过)。 - [ ] **Step 2: DatabaseInitializer — BigDecimal → Integer** 在 `DatabaseInitializer.java` 中找到种子数据插入(约第 982-986 行): ```java // 修改前: insertProductSeed("亲子关系工作坊", "...", "activity", new java.math.BigDecimal("388.00"), "action", "all"); // 修改后: 388.00元 = 38800分 insertProductSeed("亲子关系工作坊", "...", "activity", 38800, "action", "all"); ``` 检查 `insertProductSeed` 方法签名,将参数类型从 `java.math.BigDecimal` 改为 `Integer`(或确认重载版本)。 ```java // 方法签名修改前: private void insertProductSeed(String name, String desc, String type, java.math.BigDecimal price, String domain, String budget) { // 方法签名修改后: private void insertProductSeed(String name, String desc, String type, Integer price, String domain, String budget) { ``` 所有种子数据价格转换: | 原值 | 分 | |------|---| | 388.00 | 38800 | | 199.00 | 19900 | | 599.00 | 59900 | | 99.00 | 9900 | | 500.00 | 50000 | - [ ] **Step 3: AdminCommissionController 整型化** ```java // 修改前 (约第61-71行): BigDecimal profitRate = params.get("profitRate") != null ? new BigDecimal(params.get("profitRate").toString()) : null; if (productId == null || profitRate == null) { return Result.error("productId和profitRate不能为空"); } // ... product.setProfitRate(profitRate); // 修改后: Integer profitRate = params.get("profitRate") != null ? Integer.valueOf(params.get("profitRate").toString()) : null; if (productId == null || profitRate == null) { return Result.error("productId和profitRate不能为空"); } // ... product.setProfitRate(profitRate); ``` - [ ] **Step 4: 数据库迁移 SQL** ```sql -- V20260615__integer_price.sql -- ======================================== -- Phase 1: 核心电商表 -- ======================================== -- products: price, member_price, profit_rate ALTER TABLE products ADD COLUMN price_int INT DEFAULT 0 COMMENT '价格(分)', ADD COLUMN member_price_int INT DEFAULT 0 COMMENT '会员价(分)', ADD COLUMN profit_rate_int INT DEFAULT 0 COMMENT '利润率(千分比)'; UPDATE products SET price_int = ROUND(price * 100); UPDATE products SET member_price_int = ROUND(member_price * 100); UPDATE products SET profit_rate_int = ROUND(profit_rate * 10); ALTER TABLE products DROP COLUMN price, DROP COLUMN member_price, DROP COLUMN profit_rate; ALTER TABLE products CHANGE COLUMN price_int price INT DEFAULT 0 COMMENT '价格(分)', CHANGE COLUMN member_price_int member_price INT DEFAULT 0 COMMENT '会员价(分)', CHANGE COLUMN profit_rate_int profit_rate INT DEFAULT 0 COMMENT '利润率(千分比)'; -- product_orders: unit_price, total_amount ALTER TABLE product_orders ADD COLUMN unit_price_int INT DEFAULT 0 COMMENT '单价(分)', ADD COLUMN total_amount_int INT DEFAULT 0 COMMENT '总价(分)'; UPDATE product_orders SET unit_price_int = ROUND(unit_price * 100); UPDATE product_orders SET total_amount_int = ROUND(total_amount * 100); ALTER TABLE product_orders DROP COLUMN unit_price, DROP COLUMN total_amount; ALTER TABLE product_orders CHANGE COLUMN unit_price_int unit_price INT DEFAULT 0 COMMENT '单价(分)', CHANGE COLUMN total_amount_int total_amount INT DEFAULT 0 COMMENT '总价(分)'; ``` - [ ] **Step 5: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` --- ## Phase 2 — 活动/套餐/服务价格 (P1 🟠) ### Task 5: Activity + ServiceContent + GuidePackage 整型化 **Files:** - Modify: `cfc-backend/.../entity/Activity.java`(price) - Modify: `cfc-backend/.../dto/ActivityDTO.java`(price) - Modify: `cfc-backend/.../entity/ServiceContent.java`(price) - Modify: `cfc-backend/.../entity/GuidePackage.java`(price, commissionRate) - Modify: `cfc-backend/.../entity/GuidePackageTemplate.java`(referencePrice, minPrice) - Modify: `cfc-backend/.../entity/TaskTemplatePackage.java`(price, platformFee) - Modify: `cfc-backend/.../entity/Guide.java`(commissionRate) - Modify: `cfc-backend/.../entity/GuideFamily.java`(servicePrice) - Modify: `cfc-backend/.../entity/GuideApplication.java`(paymentAmount) - Modify: 上述实体对应的所有 DTO 文件 - Modify: 相关 Controller/Service(调整 `new BigDecimal()` → `Integer.valueOf()`) - [ ] **Step 1: 实体字段逐类修改** 每个实体执行同样的模式: ```java // Activity.java — 例 // 删除 import java.math.BigDecimal; private Integer price; // 价格(分) ``` DTO 同步修改,注意 DTO 中的转换赋值不变(`Integer` ↔ `Integer`)。 - [ ] **Step 2: Controller 中 BigDecimal 构造替换** 搜索所有 Controller 中 `new BigDecimal(params.get("price").toString())` 模式: ```bash grep -rn 'new BigDecimal' cfc-backend/src/main/java/com/etotem/cfc/controller/ ``` 将每个匹配位置改为 `Integer.valueOf()` 或 `Integer.parseInt()`: ```java // 修改前: java.math.BigDecimal price = new java.math.BigDecimal(params.get("price").toString()); // 修改后: Integer price = params.get("price") != null ? Integer.valueOf(params.get("price").toString()) : null; ``` - [ ] **Step 3: Service 中的金额计算调整** 搜索 Service 中使用 `BigDecimal` 进行金额计算的代码: ```bash grep -rn '\.multiply\|\.divide\|\.add\|\.subtract\|new BigDecimal' cfc-backend/src/main/java/com/etotem/cfc/service/ --include='*.java' ``` 需要重点调整的位置: **TaskTemplatePackageService.java:** ```java // 修改前: pkg.setPlatformFee(dto.getPrice().multiply(new BigDecimal("0.10"))); // 修改后: 平台服务费 = 价格 × 10%(结果以分存储) pkg.setPlatformFee(dto.getPrice() * 10 / 100); ``` **GuidePackageService.java:** ```java // 方法签名: // 旧: boolean addGuidePackage(Long guideId, Long packageId, BigDecimal price, BigDecimal commissionRate) // 新: boolean addGuidePackage(Long guideId, Long packageId, Integer price, Integer commissionRate) ``` **BindInviteService.java:** ```java // 旧: gf.setServicePrice(BigDecimal.ZERO); // 新: gf.setServicePrice(0); ``` - [ ] **Step 4: 数据库迁移 SQL** ```sql -- Phase 2 SQL -- activities.price ALTER TABLE activity ADD COLUMN price_int INT DEFAULT 0; UPDATE activity SET price_int = ROUND(price * 100); ALTER TABLE activity DROP COLUMN price; ALTER TABLE activity CHANGE COLUMN price_int price INT DEFAULT 0; -- guide_packages.price, commission_rate ALTER TABLE guide_packages ADD COLUMN price_int INT DEFAULT 0, ADD COLUMN commission_rate_int INT DEFAULT 0; UPDATE guide_packages SET price_int = ROUND(price * 100), commission_rate_int = ROUND(commission_rate * 1000); ALTER TABLE guide_packages DROP COLUMN price, DROP COLUMN commission_rate; ALTER TABLE guide_packages CHANGE COLUMN price_int price INT DEFAULT 0, CHANGE COLUMN commission_rate_int commission_rate INT DEFAULT 0; -- 后续表同理... ``` - [ ] **Step 5: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` --- ## Phase 3 — 支付/佣金/会员 (P2 🟡) ### Task 6: PaymentOrder + Commission + Membership + Withdrawal **Files:** - Modify: `PaymentOrder.java`, `CommissionRecord.java`, `WithdrawalRequest.java`, `MembershipLevel.java`, `FamilyMembership.java`, `FinanceCheckin.java`, `InsurancePolicy.java` - Modify: 上述实体的 DTO - Modify: `CommissionService.java`(整型化全部计算逻辑) - Modify: `MembershipService.java` - Modify: `WithdrawalService.java` - Modify: `PackagePaymentService.java` - Modify: `PackagePurchaseService.java` > **注意:** Phase 3 涉及复杂的佣金计算(CommissionService 约 470 行,大量 BigDecimal 计算),**改造风险较高**。建议的改造策略: > 1. 所有金额字段改为整型(分) > 2. 百分比/费率字段改为万分比(1% = 100bps) > 3. 计算时:`amount * rate_bps / 10000`(整型除法,避免精度问题) > 4. 顺序:先改 Entity/DTO 保证编译通过 → 再逐个 Service 改造 → 最后改测试 - [ ] **Step 1: Entity + DTO 逐类修改** 按 Task 1/2 的模式,逐个文件将 `BigDecimal` 字段改为 `Integer`。 - [ ] **Step 2: 佣金计算逻辑重构(CommissionService.java)** 将浮动百分比改为 BPS(基点)模式: ```java // 旧模式:BigDecimal 乘除 private static final BigDecimal PROFIT_COMMISSION_RATE = new BigDecimal("0.30"); l1Amount = orderAmount.multiply(profitRate).divide(new BigDecimal("100")); // 新模式:整型 BPS(基点) private static final int PROFIT_COMMISSION_RATE_BPS = 3000; // 30% // 返利 = 订单金额(分) × 利润率(千分比) × 佣金比例(bps) / 10000 / 1000 // 简化: l1Amount = orderAmount * profitRatePermillage * rateBps / 10000000 ``` > 注意:CommissionService 的计算涉及多层乘除,整型化后需特别注意溢出(用 `long` 中间值)。建议先在 `CommissionService.java` 中引入 `SafeMath` 辅助方法。 - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` - [ ] **Step 4: 数据库迁移 SQL** ```sql -- Phase 3: payment_orders.amount ALTER TABLE payment_orders ADD COLUMN amount_int INT DEFAULT 0; UPDATE payment_orders SET amount_int = ROUND(amount * 100); ALTER TABLE payment_orders DROP COLUMN amount; ALTER TABLE payment_orders CHANGE COLUMN amount_int amount INT DEFAULT 0; -- commission_records.order_amount, profit_rate, commission_amount ALTER TABLE commission_records ADD COLUMN order_amount_int INT DEFAULT 0, ADD COLUMN profit_rate_int INT DEFAULT 0, ADD COLUMN commission_amount_int INT DEFAULT 0; UPDATE commission_records SET order_amount_int = ROUND(order_amount * 100), profit_rate_int = ROUND(profit_rate * 1000), commission_amount_int = ROUND(commission_amount * 100); ALTER TABLE commission_records DROP COLUMN order_amount, DROP COLUMN profit_rate, DROP COLUMN commission_amount; ALTER TABLE commission_records CHANGE COLUMN order_amount_int order_amount INT DEFAULT 0, CHANGE COLUMN profit_rate_int profit_rate INT DEFAULT 0, CHANGE COLUMN commission_amount_int commission_amount INT DEFAULT 0; -- 其他表同理... ``` --- ## Phase 4 — 评分/配置/先天属性 (P3 🟢) ### Task 7: Teacher rating + Energy 配置 **Files:** - Modify: `Teacher.java`, `TeacherDTO.java`(rating: 百分制整数, 4.5 = 450) - Modify: `EnergyDimensionConfig.java`(weight: 万分比, 0.25 = 2500) - Modify: `EnergySourceConfig.java`(ratio: 万分比) - Modify: `EnergyService.java`(计算逻辑整型化) - Modify: `AssessmentPointsConfig.java`(pointsValueYuan: 分) - Modify: `AssessmentService.java` - [ ] **Step 1: 实体字段整型化** ```java // Teacher.java // 旧: private java.math.BigDecimal rating; // 评分 // 新: private Integer rating; // 评分(百分制),4.5分 → 450 // EnergyDimensionConfig.java // 旧: private BigDecimal weight; // 新: private Integer weight; // 权重(万分比),0.25 → 2500 // EnergySourceConfig.java // 旧: private BigDecimal ratio; // 新: private Integer ratio; // 比例(万分比) // AssessmentPointsConfig.java // 旧: private BigDecimal pointsValueYuan; // 新: private Integer pointsValueYuan; // 分值(分) ``` - [ ] **Step 2: EnergyService 计算逻辑重构** `EnergyService.java` 中的 `resolveDimensionRatios` 和 `calculateAllocations` 使用 `BigDecimal` 进行比例计算: ```java // 旧: Map ratios = ...; // BigDecimal totalRatio = ratios.values().stream().reduce(BigDecimal.ZERO, BigDecimal::add); BigDecimal share = BigDecimal.valueOf(total).multiply(entry.getValue()).divide(totalRatio, 0, RoundingMode.DOWN); // 新: Map ratios = ...; // long totalRatio = ratios.values().stream().mapToLong(Integer::longValue).sum(); long share = (long) total * entry.getValue() / totalRatio; // 整型运算 ``` - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` --- ### Task 8: 先天属性 (Zodiac/Bazi/BloodType/FamilyMemberAttributes) **Files:** - Modify: `ZodiacConfig.java`, `BaziConfig.java`, `BloodTypeConfig.java`(所有 `BigDecimal` → `Integer` 万分比) - Modify: `FamilyMemberAttributes.java`(mindBaseScore/wisdomBaseScore: 百分制整数) - Modify: `FamilyMemberAttributeService.java`(`calcInnateScore` 整型化) - [ ] **Step 1: 配置实体整型化** ```java // ZodiacConfig.java / BaziConfig.java / BloodTypeConfig.java // 旧: private BigDecimal mindBase; // 基数 0.85 // 新: private Integer mindBase; // 万分比基数,0.85 → 8500 private Integer wisdomBase; // 万分比 private Integer bodyModifier; // 万分比 private Integer actionModifier; private Integer wealthModifier; ``` - [ ] **Step 2: FamilyMemberAttributeService 计算重构** `calcInnateScore` 方法中大量使用 `BigDecimal` 乘除: ```java // 旧: BigDecimal bloodScore = BigDecimal.ZERO; BigDecimal result = zodiacScore.multiply(BigDecimal.valueOf(0.5)) .add(baziScore.multiply(BigDecimal.valueOf(0.3))) .add(bloodScore.multiply(BigDecimal.valueOf(0.2))); // 新: 权重使用千分比,结果使用百分制整数 // zodiac占比50% = 500‰, bazi 30% = 300‰, blood 20% = 200‰ int result = (zodiacScore * 500 + baziScore * 300 + bloodScore * 200) / 1000; ``` - [ ] **Step 3: 编译验证** ```bash cd cfc-backend && mvn clean compile -q ``` 预期:`BUILD SUCCESS` --- ## 验证总表 | # | 验证项 | 预期 | 检查方法 | |---|--------|------|----------| | 1 | 后端编译 | BUILD SUCCESS | `mvn clean compile -q` | | 2 | entity 中无 BigDecimal | 0 个 import java.math.BigDecimal | `grep -r 'import java.math.BigDecimal' src/main/java/com/etotem/cfc/entity/ \| wc -l` | | 3 | dto 中无 BigDecimal | 0 个 import | `grep -r 'import java.math.BigDecimal' src/main/java/com/etotem/cfc/dto/ \| wc -l` | | 4 | 前端无 ¥{{ 直接渲染 | 0 个匹配 | `grep -rn '¥{{' cfc-frontend/pages/ cfc-frontend/components/` | | 5 | 金额展示使用 formatPriceWithSymbol | 所有 ¥ 前缀位置使用函数 | 人工审查 | | 6 | 数据库无 DECIMAL 列 | 0 个 DECIMAL | `SHOW COLUMNS FROM products;` 等 | | 7 | 端到端价格展示 | 商品价格显示 ¥388.00 | 小程序截图对比 | | 8 | 端到端游客价格 | 显示"登录查看" | 未登录状态浏览 | --- ## 外置参考 — 全部 BigDecimal 出现位置 执行以下命令确认最终清理状态: ```bash # 后端全部 BigDecimal 引用(期望最终为 0) grep -rn 'import java.math.BigDecimal' cfc-backend/src/main/java/ --include='*.java' # 前端全部 ¥ 渲染(期望最终只剩通过 formatPriceWithSymbol 的调用) grep -rn '¥' cfc-frontend/pages/ cfc-frontend/components/ --include='*.vue' ``` ## 迁移风险矩阵 | 风险 | 影响范围 | 缓解措施 | |------|----------|----------| | Integer 溢出(金额*BPS 计算) | CommissionService | 中间值使用 `long`/`Long`,关键点加注释 | | 精度损失(除不尽) | EnergyService 分配 | 大数优先分配,余数补到最大维度 | | seed 数据价格不对 | DatabaseInitializer | 逐行双检(388.00→38800) | | 前端漏改 | 所有 Tab 页 | `grep ¥{{` + 人工审查 | | 历史数据迁移失败 | 生产库 | 迁移前备份,执行后 SELECT 抽样对比 |