user.power,而应该统一走 AppBillingService 或 BaseBillingService。packages/core/src/modules/billing/
├── billing.module.ts
├── base-billing.service.ts
├── app-billing.service.ts
└── types.tsBillingModuleBaseBillingServiceAppBillingServiceBaseBillingServicepackages/api/src/modules/app.module.ts 已经导入了 BillingModule,所以大多数主程序 service 里可以直接注入:@Injectable()
export class ReportService {
constructor(private readonly appBillingService: AppBillingService) {}
}BaseBillingService 不只是改一个数字,它还会:user.poweraccount_loguser.power,会丢掉:BaseBillingService 提供的方法getUserPower(userId)const power = await this.appBillingService.getUserPower(userId);hasSufficientPower(userId, requiredAmount)const ok = await this.appBillingService.hasSufficientPower(userId, 10);
if (!ok) {
throw HttpErrorFactory.badRequest("积分不足");
}deductUserPower(options, entityManager?)addUserPower(options, entityManager?)@Injectable()
export class FeatureService {
constructor(private readonly appBillingService: AppBillingService) {}
async run(userId: string, taskNo: string) {
await this.appBillingService.deductUserPower({
userId,
amount: 3,
accountType: ACCOUNT_LOG_TYPE.CHAT_DEC,
source: {
type: ACCOUNT_LOG_SOURCE.CHAT,
source: "报告生成",
},
remark: "报告生成消耗",
associationNo: taskNo,
});
}
}await this.appBillingService.deductUserPower({
userId,
amount,
accountType,
source: {
type,
source: "来源描述",
},
remark: "备注",
associationNo: "关联单号",
associationUserId: "关联用户ID",
});userIdamountaccountTypesource.typesource.sourceremarkassociationNoassociationUserIdawait this.appBillingService.addUserPower({
userId,
amount,
accountType,
source: {
type,
source: "来源描述",
},
remark: "备注",
associationNo: "订单号",
expireAt,
subscriptionId,
});expireAtsubscriptionIdACCOUNT_LOG_SOURCE 与 ACCOUNT_LOG_TYPEACCOUNT_LOG_SOURCE.RECHARGE
ACCOUNT_LOG_SOURCE.SYSTEM
ACCOUNT_LOG_SOURCE.CHAT
ACCOUNT_LOG_SOURCE.AGENT_CHAT
ACCOUNT_LOG_SOURCE.PLUGIN
ACCOUNT_LOG_SOURCE.MEMBERSHIP_GIFT
ACCOUNT_LOG_SOURCE.CARD_KEY_REDEEMACCOUNT_LOG_TYPE.RECHARGE_INC
ACCOUNT_LOG_TYPE.RECHARGE_GIVE_INC
ACCOUNT_LOG_TYPE.RECHARGE_DEC
ACCOUNT_LOG_TYPE.SYSTEM_MANUAL_INC
ACCOUNT_LOG_TYPE.SYSTEM_MANUAL_DEC
ACCOUNT_LOG_TYPE.CHAT_DEC
ACCOUNT_LOG_TYPE.AGENT_CHAT_DEC
ACCOUNT_LOG_TYPE.AGENT_GUEST_CHAT_DEC
ACCOUNT_LOG_TYPE.PLUGIN_DEC
ACCOUNT_LOG_TYPE.MEMBERSHIP_GIFT_INC
ACCOUNT_LOG_TYPE.MEMBERSHIP_GIFT_DEC
ACCOUNT_LOG_TYPE.MEMBERSHIP_GIFT_EXPIRED
ACCOUNT_LOG_TYPE.CARD_KEY_REDEEM_INCACCOUNT_LOG_TYPEsource.type 选大类source.source 写清楚业务名,方便财务和运营排查chat-billing.handler.ts 和 agent-billing.ts 里都有。const currentPower = await this.appBillingService.getUserPower(userId);
const minRequired = Math.ceil((estimatedTokens / billingRule.tokens) * billingRule.power);
if (currentPower < minRequired) {
throw HttpErrorFactory.badRequest("积分不足,请充值后重试");
}await this.appBillingService.deductUserPower({
userId,
amount,
accountType: ACCOUNT_LOG_TYPE.CHAT_DEC,
source: {
type: ACCOUNT_LOG_SOURCE.CHAT,
source: "基本对话",
},
remark: "基本对话消耗",
associationNo: conversationId,
});user.service.ts 就是这么做的:await this.appBillingService.addUserPower({
userId,
amount: dto.amount,
accountType: ACCOUNT_LOG_TYPE.SYSTEM_MANUAL_INC,
source: {
type: ACCOUNT_LOG_SOURCE.SYSTEM,
source: "系统操作",
},
remark: "系统调整用户积分",
associationUserId: currentUser.id,
});deductUserPowerACCOUNT_LOG_TYPE.SYSTEM_MANUAL_DECawait this.userRepository.manager.transaction(async (entityManager) => {
await this.appBillingService.addUserPower(
{
userId: order.userId,
amount: power,
accountType: ACCOUNT_LOG_TYPE.RECHARGE_INC,
source: {
type: ACCOUNT_LOG_SOURCE.RECHARGE,
source: "用户充值",
},
remark: "充值成功",
associationNo: order.orderNo,
},
entityManager,
);
await entityManager.update(RechargeOrder, order.id, {
payStatus: 1,
payTime: new Date(),
});
});await this.appBillingService.addUserPower({
userId,
amount: giftPower,
accountType: ACCOUNT_LOG_TYPE.MEMBERSHIP_GIFT_INC,
source: {
type: ACCOUNT_LOG_SOURCE.MEMBERSHIP_GIFT,
source: "会员赠送",
},
remark: "会员周期赠送积分",
expireAt,
subscriptionId,
});BaseBillingService 在扣费时会优先扣:entityManager 传进去:await this.userRepository.manager.transaction(async (entityManager) => {
await this.appBillingService.deductUserPower(
{
userId,
amount: 5,
accountType: ACCOUNT_LOG_TYPE.CHAT_DEC,
source: {
type: ACCOUNT_LOG_SOURCE.CHAT,
source: "报告生成",
},
remark: "报告生成消耗",
associationNo: taskNo,
},
entityManager,
);
await entityManager.insert(TaskEntity, {
userId,
taskNo,
status: "done",
});
});user.power = user.power - xaccount_logassociationNo 尽量传业务单号,方便排查source.source 写清楚具体功能,不要只写“系统”