perf: 支持易发云短信

pull/265/head
xiaojunnuo 2024-12-02 15:11:29 +08:00
parent 2c0cbdd29e
commit 94fa77fcd2
6 changed files with 195 additions and 24 deletions

View File

@ -85,3 +85,13 @@ export async function TestSms(data: any) {
data data
}); });
} }
export async function GetSmsTypeDefine(type: string) {
return await request({
url: apiPrefix + "/getSmsTypeDefine",
method: "post",
data: {
type
}
});
}

View File

@ -32,23 +32,15 @@
</a-form-item> </a-form-item>
<template v-if="formState.public.smsLoginEnabled"> <template v-if="formState.public.smsLoginEnabled">
<a-form-item label="短信提供商" :name="['private', 'sms', 'type']"> <a-form-item label="短信提供商" :name="['private', 'sms', 'type']">
<a-select v-model:value="formState.private.sms.type"> <a-select v-model:value="formState.private.sms.type" @change="loadTypeDefine">
<a-select-option value="aliyun">阿里云</a-select-option> <a-select-option value="aliyun">阿里云短信</a-select-option>
<a-select-option value="yfysms">易发云短信</a-select-option>
</a-select> </a-select>
</a-form-item> </a-form-item>
<template v-for="item of smsTypeDefineInputs" :key="item.simpleKey">
<fs-form-item v-model="formState.private.sms.config[item.simpleKey]" :path="'private.sms.config' + item.key" :item="item" />
</template>
<a-form-item label="阿里云授权" :name="['private', 'sms', 'config', 'accessId']" :rules="rules.required">
<access-selector v-model="formState.private.sms.config.accessId" />
</a-form-item>
<a-form-item label="短信签名" :name="['private', 'sms', 'config', 'signName']" :rules="rules.required">
<a-input v-model:value="formState.private.sms.config.signName" />
</a-form-item>
<a-form-item label="验证码模版ID" :name="['private', 'sms', 'config', 'codeTemplateId']" :rules="rules.required">
<a-input v-model:value="formState.private.sms.config.codeTemplateId" />
<div class="helper">需要配置一个变量为{code}的验证码模版</div>
</a-form-item>
<a-form-item label="短信测试"> <a-form-item label="短信测试">
<div class="flex"> <div class="flex">
<a-input v-model:value="testMobile" placeholder="输入测试手机号" /> <a-input v-model:value="testMobile" placeholder="输入测试手机号" />
@ -67,8 +59,8 @@
</template> </template>
<script setup lang="tsx"> <script setup lang="tsx">
import { reactive, ref } from "vue"; import { reactive, ref, Ref } from "vue";
import { SysSettings } from "/@/views/sys/settings/api"; import { GetSmsTypeDefine, SysSettings } from "/@/views/sys/settings/api";
import * as api from "/@/views/sys/settings/api"; import * as api from "/@/views/sys/settings/api";
import { merge } from "lodash-es"; import { merge } from "lodash-es";
import { useSettingStore } from "/@/store/modules/settings"; import { useSettingStore } from "/@/store/modules/settings";
@ -121,6 +113,36 @@ const rules = {
} }
}; };
const smsTypeDefineInputs: Ref = ref({});
async function loadTypeDefine(type: string) {
const define: any = await api.GetSmsTypeDefine(type);
const keys = Object.keys(define.input);
const inputs: any = {};
keys.forEach((key) => {
const value = define.input[key];
value.simpleKey = key;
value.key = "private.sms.config." + key;
if (!value.component) {
value.component = {
name: "a-input"
};
}
if (!value.component.name) {
value.component.vModel = "value";
}
if (!value.rules) {
value.rules = [];
}
if (value.required) {
value.rules.push(rules.required);
}
inputs[key] = define.input[key];
});
smsTypeDefineInputs.value = inputs;
}
loadTypeDefine("aliyun");
async function loadSysSettings() { async function loadSysSettings() {
const data: any = await api.SysSettingsGet(); const data: any = await api.SysSettingsGet();
merge(formState, data); merge(formState, data);

View File

@ -7,6 +7,7 @@ import { UserSettingsService } from '../../../modules/mine/service/user-settings
import { getEmailSettings } from '../../../modules/sys/settings/fix.js'; import { getEmailSettings } from '../../../modules/sys/settings/fix.js';
import { http, logger, simpleNanoId } from '@certd/basic'; import { http, logger, simpleNanoId } from '@certd/basic';
import { CodeService } from '../../../modules/basic/service/code-service.js'; import { CodeService } from '../../../modules/basic/service/code-service.js';
import { SmsServiceFactory } from '../../../modules/basic/sms/factory.js';
/** /**
*/ */
@ -157,4 +158,9 @@ export class SysSettingsController extends CrudController<SysSettingsService> {
await this.codeService.sendSmsCode(body.phoneCode, body.mobile, simpleNanoId()); await this.codeService.sendSmsCode(body.phoneCode, body.mobile, simpleNanoId());
return this.ok({}); return this.ok({});
} }
@Post('/getSmsTypeDefine', { summary: 'sys:settings:view' })
async getSmsTypeDefine(@Body('type') type: string) {
return this.ok(SmsServiceFactory.getDefine(type));
}
} }

View File

@ -1,9 +1,9 @@
import { logger } from '@certd/basic'; import { logger } from '@certd/basic';
import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js'; import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
import { AliyunAccess, AliyunClient } from '@certd/plugin-lib'; import { AliyunAccess, AliyunClient } from '@certd/plugin-lib';
export type AliyunSmsConfig = { export type AliyunSmsConfig = {
accessId: string; accessId: string;
regionId: string;
signName: string; signName: string;
codeTemplateId: string; codeTemplateId: string;
}; };
@ -11,27 +11,31 @@ export type AliyunSmsConfig = {
export class AliyunSmsService implements ISmsService { export class AliyunSmsService implements ISmsService {
static getDefine() { static getDefine() {
return { return {
name: 'aliyun-sms', name: 'aliyun',
desc: '阿里云短信服务', desc: '阿里云短信服务',
input: { input: {
accessId: { accessId: {
title: '阿里云授权', title: '阿里云授权',
component: { component: {
name: 'access-selector', name: 'access-selector',
from: 'aliyun', type: 'aliyun',
}, },
required: true, required: true,
}, },
regionId: {
title: '接入点',
required: true,
},
signName: { signName: {
title: '签名', title: '签名',
component: {
name: 'a-input',
vModel: 'value',
},
required: true, required: true,
}, },
codeTemplateId: { codeTemplateId: {
title: '验证码模板Id', title: '验证码模板Id',
component: {
name: 'a-input',
vModel: 'value',
},
required: true, required: true,
}, },
} as PluginInputs<AliyunSmsConfig>, } as PluginInputs<AliyunSmsConfig>,

View File

@ -1,12 +1,25 @@
import { AliyunSmsService } from './aliyun-sms.js'; import { AliyunSmsService } from './aliyun-sms.js';
import { YfySmsService } from './yfy-sms.js';
export class SmsServiceFactory { export class SmsServiceFactory {
static createSmsService(type: string) { static createSmsService(type: string) {
const cls = this.GetClassByType(type);
return new cls();
}
static GetClassByType(type: string) {
switch (type) { switch (type) {
case 'aliyun': case 'aliyun':
return new AliyunSmsService(); return AliyunSmsService;
case 'yfysms':
return YfySmsService;
default: default:
throw new Error('不支持的短信服务类型'); throw new Error('不支持的短信服务类型');
} }
} }
static getDefine(type: string) {
const cls = this.GetClassByType(type);
return cls.getDefine();
}
} }

View File

@ -0,0 +1,116 @@
import { http, utils } from '@certd/basic';
import { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
import { YfySmsAccess } from '@certd/plugin-plus';
export type YfySmsConfig = {
accessId: string;
signName: string;
};
export class YfySmsService implements ISmsService {
static getDefine() {
return {
name: 'yfysms',
desc: '易发云短信',
input: {
accessId: {
title: '易发云短信授权',
component: {
name: 'access-selector',
type: 'yfysms',
},
required: true,
},
signName: {
title: '签名',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
} as PluginInputs<YfySmsConfig>,
};
}
ctx: SmsPluginCtx<YfySmsConfig>;
setCtx(ctx: any) {
this.ctx = ctx;
}
async sendSmsCode(opts: { mobile: string; code: string; phoneCode: string }) {
const { mobile, code } = opts;
const access = await this.ctx.accessService.getById<YfySmsAccess>(this.ctx.config.accessId);
const res = await http.request({
url: 'http://sms.yfyidc.cn/sms/',
method: 'post',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
data: {
/**
* u KeyID
* p KeySecretmd5
* m
* c
*/
u: access.keyId,
p: utils.hash.md5(access.keySecret),
m: mobile,
c: `${this.ctx.config.signName}】您的验证码是${code}。如非本人操作,请忽略本短信`,
},
});
if (res !== 0) {
/**
* 1
* 2
* 3 KEY
* 4
* 5
* 6
* 7
* 8
* 9
* 10
*/
let message = '';
switch (res) {
case 1:
message = '余额不足';
break;
case 2:
message = '用户不存在';
break;
case 3:
message = 'KEY错误';
break;
case 4:
message = '发送失败';
break;
case 5:
message = '签名不存在';
break;
case 6:
message = '签名审核未通过';
break;
case 7:
message = '当前发信短信已达到上限';
break;
case 8:
message = '有违规词';
break;
case 9:
message = '用户已封禁';
break;
case 10:
message = '未实名认证';
break;
default:
message = '未知错误';
}
throw new Error(`发送短信失败:${message}`);
}
}
}