From 8cea0e7998c94c12c1d5db98f32ac663f8c2b418 Mon Sep 17 00:00:00 2001 From: hailin Date: Wed, 10 Dec 2025 11:11:36 -0800 Subject: [PATCH] fix(reward): change sourceOrderId to sourceOrderNo string type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes BigInt conversion error when consuming planting.order.paid events. The orderId from planting-service is a string format like PLT1765391584505Q0Q6QD, not a numeric value. Changes: - reward-source.vo.ts: sourceOrderId (bigint) → sourceOrderNo (string) - reward-calculation.service.ts: updated all method signatures - reward-application.service.ts: updated distributeRewards params - event-consumer.controller.ts: removed BigInt() conversion - prisma schema: changed column type to VarChar(50) - mapper/repository: updated field names accordingly - Removed incompatible prisma.config.ts (Prisma 7.x format) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 --- .../services/reward-service/prisma.config.ts | 14 ----- .../migration.sql | 15 +++++ .../reward-service/prisma/schema.prisma | 4 +- .../services/reward-application.service.ts | 6 +- .../reward-ledger-entry.aggregate.ts | 4 +- .../src/domain/events/reward-created.event.ts | 2 +- ...eward-ledger-entry.repository.interface.ts | 2 +- .../services/reward-calculation.service.ts | 62 +++++++++---------- .../domain/value-objects/reward-source.vo.ts | 8 +-- .../kafka/event-consumer.controller.ts | 2 +- .../mappers/reward-ledger-entry.mapper.ts | 4 +- .../reward-ledger-entry.repository.impl.ts | 6 +- 12 files changed, 65 insertions(+), 64 deletions(-) delete mode 100644 backend/services/reward-service/prisma.config.ts create mode 100644 backend/services/reward-service/prisma/migrations/20241210000000_change_source_order_id_to_string/migration.sql diff --git a/backend/services/reward-service/prisma.config.ts b/backend/services/reward-service/prisma.config.ts deleted file mode 100644 index dfa4671b..00000000 --- a/backend/services/reward-service/prisma.config.ts +++ /dev/null @@ -1,14 +0,0 @@ -// This file was generated by Prisma and assumes you have installed the following: -// npm install --save-dev prisma dotenv -import "dotenv/config"; -import { defineConfig, env } from "prisma/config"; - -export default defineConfig({ - schema: "prisma/schema.prisma", - migrations: { - path: "prisma/migrations", - }, - datasource: { - url: env("DATABASE_URL"), - }, -}); diff --git a/backend/services/reward-service/prisma/migrations/20241210000000_change_source_order_id_to_string/migration.sql b/backend/services/reward-service/prisma/migrations/20241210000000_change_source_order_id_to_string/migration.sql new file mode 100644 index 00000000..aaf0be48 --- /dev/null +++ b/backend/services/reward-service/prisma/migrations/20241210000000_change_source_order_id_to_string/migration.sql @@ -0,0 +1,15 @@ +-- AlterColumn: Change source_order_id from BIGINT to VARCHAR(50) +-- This is needed because planting-service sends orderNo as string (e.g., PLT1765391584505Q0Q6QD) + +-- Step 1: Drop the existing index +DROP INDEX IF EXISTS "idx_source_order"; + +-- Step 2: Rename the old column +ALTER TABLE "reward_ledger_entries" RENAME COLUMN "source_order_id" TO "source_order_no"; + +-- Step 3: Change the column type from BIGINT to VARCHAR(50) +ALTER TABLE "reward_ledger_entries" + ALTER COLUMN "source_order_no" TYPE VARCHAR(50) USING "source_order_no"::VARCHAR(50); + +-- Step 4: Recreate the index on the new column +CREATE INDEX "idx_source_order" ON "reward_ledger_entries"("source_order_no"); diff --git a/backend/services/reward-service/prisma/schema.prisma b/backend/services/reward-service/prisma/schema.prisma index a61435b2..4af8c7ac 100644 --- a/backend/services/reward-service/prisma/schema.prisma +++ b/backend/services/reward-service/prisma/schema.prisma @@ -16,7 +16,7 @@ model RewardLedgerEntry { userId BigInt @map("user_id") // 接收奖励的用户ID // === 奖励来源 === - sourceOrderId BigInt @map("source_order_id") // 来源认种订单ID + sourceOrderNo String @map("source_order_no") @db.VarChar(50) // 来源认种订单号(字符串格式如PLT1765391584505Q0Q6QD) sourceUserId BigInt @map("source_user_id") // 触发奖励的用户ID(认种者) rightType String @map("right_type") @db.VarChar(50) // 权益类型 @@ -40,7 +40,7 @@ model RewardLedgerEntry { @@map("reward_ledger_entries") @@index([userId, rewardStatus], name: "idx_user_status") @@index([userId, createdAt(sort: Desc)], name: "idx_user_created") - @@index([sourceOrderId], name: "idx_source_order") + @@index([sourceOrderNo], name: "idx_source_order") @@index([sourceUserId], name: "idx_source_user") @@index([rightType], name: "idx_right_type") @@index([rewardStatus], name: "idx_status") diff --git a/backend/services/reward-service/src/application/services/reward-application.service.ts b/backend/services/reward-service/src/application/services/reward-application.service.ts index d8f5d9ca..7980ce8b 100644 --- a/backend/services/reward-service/src/application/services/reward-application.service.ts +++ b/backend/services/reward-service/src/application/services/reward-application.service.ts @@ -35,13 +35,13 @@ export class RewardApplicationService { * 分配奖励 (响应认种订单支付成功事件) */ async distributeRewards(params: { - sourceOrderId: bigint; + sourceOrderNo: string; // 订单号是字符串格式如 PLT1765391584505Q0Q6QD sourceUserId: bigint; treeCount: number; provinceCode: string; cityCode: string; }): Promise { - this.logger.log(`Distributing rewards for order ${params.sourceOrderId}`); + this.logger.log(`Distributing rewards for order ${params.sourceOrderNo}`); // 1. 计算所有奖励 const rewards = await this.rewardCalculationService.calculateRewards(params); @@ -76,7 +76,7 @@ export class RewardApplicationService { reward.clearDomainEvents(); } - this.logger.log(`Distributed ${rewards.length} rewards for order ${params.sourceOrderId}`); + this.logger.log(`Distributed ${rewards.length} rewards for order ${params.sourceOrderNo}`); } /** diff --git a/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts index c3d560ee..ee171173 100644 --- a/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts +++ b/backend/services/reward-service/src/domain/aggregates/reward-ledger-entry/reward-ledger-entry.aggregate.ts @@ -106,7 +106,7 @@ export class RewardLedgerEntry { entry._domainEvents.push(new RewardCreatedEvent({ entryId: entry._id?.toString() || 'temp', userId: entry._userId.toString(), - sourceOrderId: entry._rewardSource.sourceOrderId.toString(), + sourceOrderNo: entry._rewardSource.sourceOrderNo, sourceUserId: entry._rewardSource.sourceUserId.toString(), rightType: entry._rewardSource.rightType, usdtAmount: entry._usdtAmount.amount, @@ -143,7 +143,7 @@ export class RewardLedgerEntry { entry._domainEvents.push(new RewardCreatedEvent({ entryId: entry._id?.toString() || 'temp', userId: entry._userId.toString(), - sourceOrderId: entry._rewardSource.sourceOrderId.toString(), + sourceOrderNo: entry._rewardSource.sourceOrderNo, sourceUserId: entry._rewardSource.sourceUserId.toString(), rightType: entry._rewardSource.rightType, usdtAmount: entry._usdtAmount.amount, diff --git a/backend/services/reward-service/src/domain/events/reward-created.event.ts b/backend/services/reward-service/src/domain/events/reward-created.event.ts index 1b0d1002..f715d8a9 100644 --- a/backend/services/reward-service/src/domain/events/reward-created.event.ts +++ b/backend/services/reward-service/src/domain/events/reward-created.event.ts @@ -5,7 +5,7 @@ import { RewardStatus } from '../value-objects/reward-status.enum'; export interface RewardCreatedPayload { entryId: string; userId: string; - sourceOrderId: string; + sourceOrderNo: string; sourceUserId: string; rightType: RightType; usdtAmount: number; diff --git a/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts b/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts index e69b52e8..2287fd1d 100644 --- a/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts +++ b/backend/services/reward-service/src/domain/repositories/reward-ledger-entry.repository.interface.ts @@ -19,7 +19,7 @@ export interface IRewardLedgerEntryRepository { findPendingByUserId(userId: bigint): Promise; findSettleableByUserId(userId: bigint): Promise; findExpiredPending(beforeDate: Date): Promise; - findBySourceOrderId(sourceOrderId: bigint): Promise; + findBySourceOrderNo(sourceOrderNo: string): Promise; countByUserId(userId: bigint, status?: RewardStatus): Promise; } diff --git a/backend/services/reward-service/src/domain/services/reward-calculation.service.ts b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts index d6c6ca7c..c501d929 100644 --- a/backend/services/reward-service/src/domain/services/reward-calculation.service.ts +++ b/backend/services/reward-service/src/domain/services/reward-calculation.service.ts @@ -51,7 +51,7 @@ export class RewardCalculationService { * 总计 2199 USDT = 400 + 300 + 9 + 800 + 500 + 15 + 20 + 35 + 40 + 80 */ async calculateRewards(params: { - sourceOrderId: bigint; + sourceOrderNo: string; // 订单号是字符串格式 sourceUserId: bigint; treeCount: number; provinceCode: string; @@ -65,7 +65,7 @@ export class RewardCalculationService { // 1. 成本费 (400 USDT) const costFeeReward = this.calculateCostFee( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -73,7 +73,7 @@ export class RewardCalculationService { // 2. 运营费 (300 USDT) const operationFeeReward = this.calculateOperationFee( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -81,7 +81,7 @@ export class RewardCalculationService { // 3. 总部社区基础费 (9 USDT) const headquartersBaseFeeReward = this.calculateHeadquartersBaseFee( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -89,7 +89,7 @@ export class RewardCalculationService { // 4. RWAD底池注入 (800 USDT) const rwadPoolReward = this.calculateRwadPoolInjection( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -101,7 +101,7 @@ export class RewardCalculationService { // 5. 分享权益 (500 USDT) const shareRewards = await this.calculateShareRights( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -109,7 +109,7 @@ export class RewardCalculationService { // 6. 省团队权益 (20 USDT) const provinceTeamReward = await this.calculateProvinceTeamRight( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.provinceCode, params.treeCount, @@ -118,7 +118,7 @@ export class RewardCalculationService { // 7. 省区域权益 (15 USDT + 1%算力) const provinceAreaReward = this.calculateProvinceAreaRight( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.provinceCode, params.treeCount, @@ -127,7 +127,7 @@ export class RewardCalculationService { // 8. 市团队权益 (40 USDT) const cityTeamReward = await this.calculateCityTeamRight( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.cityCode, params.treeCount, @@ -136,7 +136,7 @@ export class RewardCalculationService { // 9. 市区域权益 (35 USDT + 2%算力) const cityAreaReward = this.calculateCityAreaRight( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.cityCode, params.treeCount, @@ -145,7 +145,7 @@ export class RewardCalculationService { // 10. 社区权益 (80 USDT) const communityReward = await this.calculateCommunityRight( - params.sourceOrderId, + params.sourceOrderNo, params.sourceUserId, params.treeCount, ); @@ -163,7 +163,7 @@ export class RewardCalculationService { * 分配至指定成本账户 */ private calculateCostFee( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): RewardLedgerEntry { @@ -173,7 +173,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.COST_FEE, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -191,7 +191,7 @@ export class RewardCalculationService { * 分配至指定运营账户 */ private calculateOperationFee( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): RewardLedgerEntry { @@ -201,7 +201,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.OPERATION_FEE, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -219,7 +219,7 @@ export class RewardCalculationService { * 分配至总部社区账户 */ private calculateHeadquartersBaseFee( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): RewardLedgerEntry { @@ -229,7 +229,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.HEADQUARTERS_BASE_FEE, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -247,7 +247,7 @@ export class RewardCalculationService { * 注入RWAD 1号底池 */ private calculateRwadPoolInjection( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): RewardLedgerEntry { @@ -257,7 +257,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.RWAD_POOL_INJECTION, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -278,7 +278,7 @@ export class RewardCalculationService { * 计算分享权益 (500 USDT) */ private async calculateShareRights( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): Promise { @@ -288,7 +288,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.SHARE_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -333,7 +333,7 @@ export class RewardCalculationService { * 计算省团队权益 (20 USDT) */ private async calculateProvinceTeamRight( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, provinceCode: string, treeCount: number, @@ -344,7 +344,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.PROVINCE_TEAM_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -377,7 +377,7 @@ export class RewardCalculationService { * 计算省区域权益 (15 USDT + 1%算力) */ private calculateProvinceAreaRight( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, provinceCode: string, treeCount: number, @@ -388,7 +388,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.PROVINCE_AREA_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -408,7 +408,7 @@ export class RewardCalculationService { * 计算市团队权益 (40 USDT) */ private async calculateCityTeamRight( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, cityCode: string, treeCount: number, @@ -419,7 +419,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.CITY_TEAM_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -452,7 +452,7 @@ export class RewardCalculationService { * 计算市区域权益 (35 USDT + 2%算力) */ private calculateCityAreaRight( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, cityCode: string, treeCount: number, @@ -463,7 +463,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.CITY_AREA_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); @@ -483,7 +483,7 @@ export class RewardCalculationService { * 计算社区权益 (80 USDT) */ private async calculateCommunityRight( - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, treeCount: number, ): Promise { @@ -493,7 +493,7 @@ export class RewardCalculationService { const rewardSource = RewardSource.create( RightType.COMMUNITY_RIGHT, - sourceOrderId, + sourceOrderNo, sourceUserId, ); diff --git a/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts b/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts index 4a2fdadb..6ded5ed3 100644 --- a/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts +++ b/backend/services/reward-service/src/domain/value-objects/reward-source.vo.ts @@ -3,22 +3,22 @@ import { RightType } from './right-type.enum'; export class RewardSource { private constructor( public readonly rightType: RightType, - public readonly sourceOrderId: bigint, + public readonly sourceOrderNo: string, // 订单号是字符串格式如 PLT1765391584505Q0Q6QD public readonly sourceUserId: bigint, ) {} static create( rightType: RightType, - sourceOrderId: bigint, + sourceOrderNo: string, sourceUserId: bigint, ): RewardSource { - return new RewardSource(rightType, sourceOrderId, sourceUserId); + return new RewardSource(rightType, sourceOrderNo, sourceUserId); } equals(other: RewardSource): boolean { return ( this.rightType === other.rightType && - this.sourceOrderId === other.sourceOrderId && + this.sourceOrderNo === other.sourceOrderNo && this.sourceUserId === other.sourceUserId ); } diff --git a/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts b/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts index 7ff03f3f..f2a80ac3 100644 --- a/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts +++ b/backend/services/reward-service/src/infrastructure/kafka/event-consumer.controller.ts @@ -61,7 +61,7 @@ export class EventConsumerController { try { // 1. 计算并分配奖励 await this.rewardService.distributeRewards({ - sourceOrderId: BigInt(eventData.orderId), + sourceOrderNo: eventData.orderId, // orderId 实际是 orderNo 字符串格式 sourceUserId: BigInt(eventData.userId), treeCount: eventData.treeCount, provinceCode: eventData.provinceCode, diff --git a/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts index 27d2ebaf..27dd7cab 100644 --- a/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts +++ b/backend/services/reward-service/src/infrastructure/persistence/mappers/reward-ledger-entry.mapper.ts @@ -11,7 +11,7 @@ export class RewardLedgerEntryMapper { userId: raw.userId, rewardSource: RewardSource.create( raw.rightType as RightType, - raw.sourceOrderId, + raw.sourceOrderNo, raw.sourceUserId, ), usdtAmount: Number(raw.usdtAmount), @@ -30,7 +30,7 @@ export class RewardLedgerEntryMapper { return { id: entry.id || undefined, userId: entry.userId, - sourceOrderId: entry.rewardSource.sourceOrderId, + sourceOrderNo: entry.rewardSource.sourceOrderNo, sourceUserId: entry.rewardSource.sourceUserId, rightType: entry.rewardSource.rightType, usdtAmount: new Prisma.Decimal(entry.usdtAmount.amount), diff --git a/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts index 70f38b6c..94cc35c4 100644 --- a/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts +++ b/backend/services/reward-service/src/infrastructure/persistence/repositories/reward-ledger-entry.repository.impl.ts @@ -29,7 +29,7 @@ export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryReposi const created = await this.prisma.rewardLedgerEntry.create({ data: { userId: data.userId, - sourceOrderId: data.sourceOrderId, + sourceOrderNo: data.sourceOrderNo, sourceUserId: data.sourceUserId, rightType: data.rightType, usdtAmount: data.usdtAmount, @@ -137,9 +137,9 @@ export class RewardLedgerEntryRepositoryImpl implements IRewardLedgerEntryReposi return rawList.map(RewardLedgerEntryMapper.toDomain); } - async findBySourceOrderId(sourceOrderId: bigint): Promise { + async findBySourceOrderNo(sourceOrderNo: string): Promise { const rawList = await this.prisma.rewardLedgerEntry.findMany({ - where: { sourceOrderId }, + where: { sourceOrderNo }, }); return rawList.map(RewardLedgerEntryMapper.toDomain);