短信验证绑定日志内容
This commit is contained in:
@@ -3,7 +3,7 @@ import Dysmsapi20170525, { SendSmsRequest } from '@alicloud/dysmsapi20170525';
|
||||
import * as OpenApi from '@alicloud/openapi-client';
|
||||
import { loadAppConfig } from '@dukang/shared-types';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { ISmsProvider } from './sms.interface';
|
||||
import type { ISmsProvider, SmsActorRef, SmsSendResult } from './sms.interface';
|
||||
import { SmsCodeStore } from './sms-code.store';
|
||||
|
||||
function serializeSmsResponseBody(body: unknown): Record<string, string> | undefined {
|
||||
@@ -19,6 +19,10 @@ function serializeSmsResponseBody(body: unknown): Record<string, string> | undef
|
||||
return Object.keys(out).length ? out : undefined;
|
||||
}
|
||||
|
||||
function maskPhone(phone: string) {
|
||||
return `${phone.slice(0, 3)}****${phone.slice(-4)}`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SmsAliyunProvider implements ISmsProvider {
|
||||
private readonly logger = new Logger(SmsAliyunProvider.name);
|
||||
@@ -54,8 +58,9 @@ export class SmsAliyunProvider implements ISmsProvider {
|
||||
return this.client;
|
||||
}
|
||||
|
||||
async send(phone: string, scene: string): Promise<void> {
|
||||
async send(phone: string, scene: string, actorRef?: SmsActorRef): Promise<SmsSendResult> {
|
||||
const code = await this.smsCodeStore.generateAndStore(phone, scene);
|
||||
const masked = maskPhone(phone);
|
||||
const request = new SendSmsRequest({
|
||||
phoneNumbers: phone,
|
||||
signName: this.config.aliyunSmsSignName,
|
||||
@@ -63,43 +68,53 @@ export class SmsAliyunProvider implements ISmsProvider {
|
||||
templateParam: JSON.stringify({ code }),
|
||||
});
|
||||
|
||||
let logged = false;
|
||||
const baseLog = {
|
||||
provider: 'SMS' as const,
|
||||
scene,
|
||||
refType: actorRef?.refType,
|
||||
refId: actorRef?.refId,
|
||||
requestBody: {
|
||||
phone: masked,
|
||||
templateCode: this.config.aliyunSmsTemplateCode,
|
||||
signName: this.config.aliyunSmsSignName,
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await this.getClient().sendSms(request);
|
||||
const bizId = response.body?.bizId ?? undefined;
|
||||
const ok = response.body?.code === 'OK';
|
||||
const responseBody = serializeSmsResponseBody(response.body);
|
||||
await this.prisma.logThirdParty.create({
|
||||
const log = await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'SMS',
|
||||
scene,
|
||||
requestBody: { phone, templateCode: this.config.aliyunSmsTemplateCode, signName: this.config.aliyunSmsSignName },
|
||||
...baseLog,
|
||||
responseBody,
|
||||
externalNo: bizId,
|
||||
status: ok ? 'SUCCESS' : 'FAILED',
|
||||
errorMessage: ok ? undefined : response.body?.message ?? 'SMS send failed',
|
||||
},
|
||||
});
|
||||
logged = true;
|
||||
if (!ok) {
|
||||
throw new Error(response.body?.message ?? '短信发送失败');
|
||||
if (ok) {
|
||||
this.logger.log(`Aliyun SMS sent to ${masked} scene=${scene} bizId=${bizId ?? '-'}`);
|
||||
} else {
|
||||
this.logger.warn(`Aliyun SMS failed for ${masked} scene=${scene}: ${response.body?.message}`);
|
||||
}
|
||||
this.logger.log(`Aliyun SMS sent to ${phone.slice(0, 3)}****${phone.slice(-4)} scene=${scene} bizId=${bizId ?? '-'}`);
|
||||
return {
|
||||
logId: log.id,
|
||||
ok,
|
||||
errorMessage: ok ? undefined : response.body?.message ?? '短信发送失败',
|
||||
};
|
||||
} catch (err) {
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
this.logger.error(`Aliyun SMS send failed: ${message}`);
|
||||
if (!logged) {
|
||||
await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'SMS',
|
||||
scene,
|
||||
requestBody: { phone, templateCode: this.config.aliyunSmsTemplateCode, signName: this.config.aliyunSmsSignName },
|
||||
status: 'FAILED',
|
||||
errorMessage: message.slice(0, 512),
|
||||
},
|
||||
});
|
||||
}
|
||||
throw err;
|
||||
const log = await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
...baseLog,
|
||||
status: 'FAILED',
|
||||
errorMessage: message.slice(0, 512),
|
||||
},
|
||||
});
|
||||
return { logId: log.id, ok: false, errorMessage: message };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
export type SmsActorRef = {
|
||||
refType: string;
|
||||
refId: bigint;
|
||||
};
|
||||
|
||||
export type SmsSendResult = {
|
||||
logId: bigint;
|
||||
ok: boolean;
|
||||
errorMessage?: string;
|
||||
};
|
||||
|
||||
export interface ISmsProvider {
|
||||
send(phone: string, scene: string): Promise<void>;
|
||||
send(phone: string, scene: string, actorRef?: SmsActorRef): Promise<SmsSendResult>;
|
||||
verify(phone: string, code: string, scene: string): Promise<void>;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { PrismaService } from '../../common/prisma/prisma.module';
|
||||
import { ISmsProvider } from './sms.interface';
|
||||
import type { ISmsProvider, SmsActorRef, SmsSendResult } from './sms.interface';
|
||||
import { SmsCodeStore } from './sms-code.store';
|
||||
|
||||
function maskPhone(phone: string) {
|
||||
return `${phone.slice(0, 3)}****${phone.slice(-4)}`;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class SmsMockProvider implements ISmsProvider {
|
||||
private readonly logger = new Logger(SmsMockProvider.name);
|
||||
@@ -12,20 +16,23 @@ export class SmsMockProvider implements ISmsProvider {
|
||||
private readonly prisma: PrismaService,
|
||||
) {}
|
||||
|
||||
async send(phone: string, scene: string): Promise<void> {
|
||||
async send(phone: string, scene: string, actorRef?: SmsActorRef): Promise<SmsSendResult> {
|
||||
const code = await this.smsCodeStore.generateAndStore(phone, scene);
|
||||
const masked = `${phone.slice(0, 3)}****${phone.slice(-4)}`;
|
||||
const masked = maskPhone(phone);
|
||||
this.logger.log(`Mock SMS → ${masked} scene=${scene} code=${code}`);
|
||||
|
||||
await this.prisma.logThirdParty.create({
|
||||
const log = await this.prisma.logThirdParty.create({
|
||||
data: {
|
||||
provider: 'SMS',
|
||||
scene,
|
||||
refType: actorRef?.refType,
|
||||
refId: actorRef?.refId,
|
||||
requestBody: { phone: masked, mode: 'MOCK', scene },
|
||||
responseBody: { mock: true, hint: 'use MOCK_SMS_CODE or check server log' },
|
||||
status: 'SUCCESS',
|
||||
},
|
||||
});
|
||||
return { logId: log.id, ok: true };
|
||||
}
|
||||
|
||||
async verify(phone: string, code: string, scene: string): Promise<void> {
|
||||
|
||||
Reference in New Issue
Block a user