feat(reporting-service): 新增多种活动事件类型
扩展仪表板"最近活动"功能,新增以下活动类型: 活动类型新增: - kyc_submitted: KYC认证提交 - kyc_approved: KYC认证通过 - kyc_rejected: KYC认证拒绝 - contract_signed: 合同签署 - deposit: 充值到账 - withdrawal: 提现成功 - benefit_applied: 权益申请 监听的 Kafka Topics: - identity.KYCSubmitted - identity.KYCApproved - identity.KYCRejected - contract.signed - blockchain.deposit.credited - wallet.withdrawals (仅处理 completed 事件) - authorization.benefit.applied 所有新增事件处理器均使用幂等创建,防止重复记录。 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
65bd6a857f
commit
38fff077dd
|
|
@ -4,11 +4,18 @@
|
||||||
export const SYSTEM_ACTIVITY_REPOSITORY = Symbol('SYSTEM_ACTIVITY_REPOSITORY');
|
export const SYSTEM_ACTIVITY_REPOSITORY = Symbol('SYSTEM_ACTIVITY_REPOSITORY');
|
||||||
|
|
||||||
export type ActivityType =
|
export type ActivityType =
|
||||||
| 'user_register'
|
| 'user_register' // 用户注册
|
||||||
| 'company_authorization'
|
| 'planting_order' // 认种订单
|
||||||
| 'planting_order'
|
| 'contract_signed' // 合同签署
|
||||||
| 'system_update'
|
| 'kyc_submitted' // KYC提交
|
||||||
| 'report_generated';
|
| 'kyc_approved' // KYC通过
|
||||||
|
| 'kyc_rejected' // KYC拒绝
|
||||||
|
| 'deposit' // 充值
|
||||||
|
| 'withdrawal' // 提现
|
||||||
|
| 'company_authorization' // 公司授权
|
||||||
|
| 'benefit_applied' // 权益申请
|
||||||
|
| 'system_update' // 系统更新
|
||||||
|
| 'report_generated'; // 报表生成
|
||||||
|
|
||||||
export interface SystemActivityData {
|
export interface SystemActivityData {
|
||||||
id?: bigint;
|
id?: bigint;
|
||||||
|
|
|
||||||
|
|
@ -405,4 +405,370 @@ export class ActivityEventConsumerController {
|
||||||
}
|
}
|
||||||
return phone.slice(0, 3) + '****' + phone.slice(-4);
|
return phone.slice(0, 3) + '****' + phone.slice(-4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// [2026-01-08] 新增:更多活动事件消费者
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 KYC 提交事件 (identity-service)
|
||||||
|
* Topic: identity.KYCSubmitted
|
||||||
|
*/
|
||||||
|
@MessagePattern('identity.KYCSubmitted')
|
||||||
|
async handleKYCSubmitted(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
eventId: string;
|
||||||
|
eventType: string;
|
||||||
|
aggregateId: string;
|
||||||
|
occurredAt: string;
|
||||||
|
payload: {
|
||||||
|
userId: string;
|
||||||
|
accountSequence?: string;
|
||||||
|
realName?: string;
|
||||||
|
idNumber?: string;
|
||||||
|
submittedAt: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received identity.KYCSubmitted event`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { payload, aggregateId } = message;
|
||||||
|
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'kyc_submitted' as ActivityType,
|
||||||
|
title: 'KYC认证提交',
|
||||||
|
description: `用户提交了实名认证申请`,
|
||||||
|
icon: '📋',
|
||||||
|
relatedUserId: BigInt(payload.userId),
|
||||||
|
relatedEntityType: 'kyc',
|
||||||
|
relatedEntityId: `kyc_submit_${aggregateId}`,
|
||||||
|
metadata: {
|
||||||
|
accountSequence: payload.accountSequence,
|
||||||
|
submittedAt: payload.submittedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for KYC submission: ${payload.userId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate KYC submission event: ${payload.userId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording KYC submission activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 KYC 通过事件 (identity-service)
|
||||||
|
* Topic: identity.KYCApproved
|
||||||
|
*/
|
||||||
|
@MessagePattern('identity.KYCApproved')
|
||||||
|
async handleKYCApproved(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
eventId: string;
|
||||||
|
eventType: string;
|
||||||
|
aggregateId: string;
|
||||||
|
occurredAt: string;
|
||||||
|
payload: {
|
||||||
|
userId: string;
|
||||||
|
accountSequence?: string;
|
||||||
|
approvedAt: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received identity.KYCApproved event`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { payload, aggregateId } = message;
|
||||||
|
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'kyc_approved' as ActivityType,
|
||||||
|
title: 'KYC认证通过',
|
||||||
|
description: `用户实名认证已通过`,
|
||||||
|
icon: '✅',
|
||||||
|
relatedUserId: BigInt(payload.userId),
|
||||||
|
relatedEntityType: 'kyc',
|
||||||
|
relatedEntityId: `kyc_approve_${aggregateId}`,
|
||||||
|
metadata: {
|
||||||
|
accountSequence: payload.accountSequence,
|
||||||
|
approvedAt: payload.approvedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for KYC approval: ${payload.userId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate KYC approval event: ${payload.userId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording KYC approval activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听 KYC 拒绝事件 (identity-service)
|
||||||
|
* Topic: identity.KYCRejected
|
||||||
|
*/
|
||||||
|
@MessagePattern('identity.KYCRejected')
|
||||||
|
async handleKYCRejected(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
eventId: string;
|
||||||
|
eventType: string;
|
||||||
|
aggregateId: string;
|
||||||
|
occurredAt: string;
|
||||||
|
payload: {
|
||||||
|
userId: string;
|
||||||
|
accountSequence?: string;
|
||||||
|
reason?: string;
|
||||||
|
rejectedAt: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received identity.KYCRejected event`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { payload, aggregateId } = message;
|
||||||
|
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'kyc_rejected' as ActivityType,
|
||||||
|
title: 'KYC认证拒绝',
|
||||||
|
description: `用户实名认证被拒绝`,
|
||||||
|
icon: '❌',
|
||||||
|
relatedUserId: BigInt(payload.userId),
|
||||||
|
relatedEntityType: 'kyc',
|
||||||
|
relatedEntityId: `kyc_reject_${aggregateId}`,
|
||||||
|
metadata: {
|
||||||
|
accountSequence: payload.accountSequence,
|
||||||
|
reason: payload.reason,
|
||||||
|
rejectedAt: payload.rejectedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for KYC rejection: ${payload.userId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate KYC rejection event: ${payload.userId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording KYC rejection activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听合同签署事件 (planting-service)
|
||||||
|
* Topic: contract.signed
|
||||||
|
*/
|
||||||
|
@MessagePattern('contract.signed')
|
||||||
|
async handleContractSigned(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
orderNo: string;
|
||||||
|
userId: string;
|
||||||
|
accountSequence: string;
|
||||||
|
treeCount: number;
|
||||||
|
totalAmount: number;
|
||||||
|
provinceCode: string;
|
||||||
|
cityCode: string;
|
||||||
|
signedAt: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received contract.signed event: ${message.orderNo}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'contract_signed' as ActivityType,
|
||||||
|
title: '合同签署',
|
||||||
|
description: `用户签署了 ${message.treeCount} 棵榴莲树认种合同`,
|
||||||
|
icon: '📝',
|
||||||
|
relatedUserId: BigInt(message.userId),
|
||||||
|
relatedEntityType: 'contract',
|
||||||
|
relatedEntityId: message.orderNo,
|
||||||
|
metadata: {
|
||||||
|
orderNo: message.orderNo,
|
||||||
|
accountSequence: message.accountSequence,
|
||||||
|
treeCount: message.treeCount,
|
||||||
|
totalAmount: message.totalAmount,
|
||||||
|
provinceCode: message.provinceCode,
|
||||||
|
cityCode: message.cityCode,
|
||||||
|
signedAt: message.signedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for contract signing: ${message.orderNo}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate contract signing event: ${message.orderNo}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording contract signing activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听充值到账事件 (blockchain-service -> wallet-service)
|
||||||
|
* Topic: blockchain.deposit.credited
|
||||||
|
*/
|
||||||
|
@MessagePattern('blockchain.deposit.credited')
|
||||||
|
async handleDepositCredited(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
depositId: string;
|
||||||
|
userId: string;
|
||||||
|
accountSequence?: string;
|
||||||
|
amount: string;
|
||||||
|
assetType: string;
|
||||||
|
txHash: string;
|
||||||
|
creditedAt: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received blockchain.deposit.credited event: ${message.depositId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'deposit' as ActivityType,
|
||||||
|
title: '充值到账',
|
||||||
|
description: `用户充值 ${message.amount} ${message.assetType}`,
|
||||||
|
icon: '💰',
|
||||||
|
relatedUserId: BigInt(message.userId),
|
||||||
|
relatedEntityType: 'deposit',
|
||||||
|
relatedEntityId: message.depositId,
|
||||||
|
metadata: {
|
||||||
|
depositId: message.depositId,
|
||||||
|
accountSequence: message.accountSequence,
|
||||||
|
amount: message.amount,
|
||||||
|
assetType: message.assetType,
|
||||||
|
txHash: message.txHash,
|
||||||
|
creditedAt: message.creditedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for deposit: ${message.depositId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate deposit event: ${message.depositId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording deposit activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听提现完成事件 (wallet-service)
|
||||||
|
* Topic: wallet.withdrawals (eventType: wallet.withdrawal.completed)
|
||||||
|
*/
|
||||||
|
@MessagePattern('wallet.withdrawals')
|
||||||
|
async handleWithdrawalEvent(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
eventType: string;
|
||||||
|
withdrawalId: string;
|
||||||
|
userId: string;
|
||||||
|
accountSequence?: string;
|
||||||
|
amount: string;
|
||||||
|
assetType: string;
|
||||||
|
status: string;
|
||||||
|
txHash?: string;
|
||||||
|
completedAt?: string;
|
||||||
|
requestedAt?: string;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
// 只处理提现完成事件
|
||||||
|
if (message.eventType !== 'wallet.withdrawal.completed') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.log(`Received wallet.withdrawal.completed event: ${message.withdrawalId}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'withdrawal' as ActivityType,
|
||||||
|
title: '提现成功',
|
||||||
|
description: `用户提现 ${message.amount} ${message.assetType}`,
|
||||||
|
icon: '💸',
|
||||||
|
relatedUserId: BigInt(message.userId),
|
||||||
|
relatedEntityType: 'withdrawal',
|
||||||
|
relatedEntityId: message.withdrawalId,
|
||||||
|
metadata: {
|
||||||
|
withdrawalId: message.withdrawalId,
|
||||||
|
accountSequence: message.accountSequence,
|
||||||
|
amount: message.amount,
|
||||||
|
assetType: message.assetType,
|
||||||
|
txHash: message.txHash,
|
||||||
|
completedAt: message.completedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for withdrawal: ${message.withdrawalId}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate withdrawal event: ${message.withdrawalId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording withdrawal activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听权益申请事件 (authorization-service)
|
||||||
|
* Topic: authorization-events (eventType 包含 benefit 或 applied)
|
||||||
|
* 注意:此处理器复用 authorization-events topic,在 handleAuthorizationEvent 之外单独处理权益相关事件
|
||||||
|
*/
|
||||||
|
@MessagePattern('authorization.benefit.applied')
|
||||||
|
async handleBenefitApplied(
|
||||||
|
@Payload()
|
||||||
|
message: {
|
||||||
|
eventId: string;
|
||||||
|
eventType: string;
|
||||||
|
aggregateId: string;
|
||||||
|
occurredAt: string;
|
||||||
|
payload: {
|
||||||
|
authorizationId: string;
|
||||||
|
userId: string;
|
||||||
|
accountSequence: string;
|
||||||
|
roleType: string;
|
||||||
|
regionCode: string;
|
||||||
|
regionName: string;
|
||||||
|
benefitType?: string;
|
||||||
|
appliedAt: string;
|
||||||
|
};
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
this.logger.log(`Received authorization.benefit.applied event`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { payload, aggregateId } = message;
|
||||||
|
const roleTypeLabel = this.getRoleTypeLabel(payload.roleType);
|
||||||
|
|
||||||
|
const created = await this.activityRepo.createIfNotExists({
|
||||||
|
activityType: 'benefit_applied' as ActivityType,
|
||||||
|
title: '权益申请',
|
||||||
|
description: `${payload.regionName} ${roleTypeLabel} 申请权益`,
|
||||||
|
icon: '🎁',
|
||||||
|
relatedUserId: BigInt(payload.userId),
|
||||||
|
relatedEntityType: 'benefit',
|
||||||
|
relatedEntityId: `benefit_${aggregateId}`,
|
||||||
|
metadata: {
|
||||||
|
authorizationId: payload.authorizationId,
|
||||||
|
accountSequence: payload.accountSequence,
|
||||||
|
roleType: payload.roleType,
|
||||||
|
regionCode: payload.regionCode,
|
||||||
|
regionName: payload.regionName,
|
||||||
|
benefitType: payload.benefitType,
|
||||||
|
appliedAt: payload.appliedAt,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (created) {
|
||||||
|
this.logger.log(`Activity recorded for benefit application: ${payload.accountSequence}`);
|
||||||
|
} else {
|
||||||
|
this.logger.log(`Skipped duplicate benefit application event: ${aggregateId}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(`Error recording benefit application activity:`, error);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue