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
});
}
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>
<template v-if="formState.public.smsLoginEnabled">
<a-form-item label="短信提供商" :name="['private', 'sms', 'type']">
<a-select v-model:value="formState.private.sms.type">
<a-select-option value="aliyun">阿里云</a-select-option>
<a-select v-model:value="formState.private.sms.type" @change="loadTypeDefine">
<a-select-option value="aliyun">阿里云短信</a-select-option>
<a-select-option value="yfysms">易发云短信</a-select-option>
</a-select>
</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="短信测试">
<div class="flex">
<a-input v-model:value="testMobile" placeholder="输入测试手机号" />
@ -67,8 +59,8 @@
</template>
<script setup lang="tsx">
import { reactive, ref } from "vue";
import { SysSettings } from "/@/views/sys/settings/api";
import { reactive, ref, Ref } from "vue";
import { GetSmsTypeDefine, SysSettings } from "/@/views/sys/settings/api";
import * as api from "/@/views/sys/settings/api";
import { merge } from "lodash-es";
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() {
const data: any = await api.SysSettingsGet();
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 { http, logger, simpleNanoId } from '@certd/basic';
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());
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 { ISmsService, PluginInputs, SmsPluginCtx } from './api.js';
import { AliyunAccess, AliyunClient } from '@certd/plugin-lib';
export type AliyunSmsConfig = {
accessId: string;
regionId: string;
signName: string;
codeTemplateId: string;
};
@ -11,27 +11,31 @@ export type AliyunSmsConfig = {
export class AliyunSmsService implements ISmsService {
static getDefine() {
return {
name: 'aliyun-sms',
name: 'aliyun',
desc: '阿里云短信服务',
input: {
accessId: {
title: '阿里云授权',
component: {
name: 'access-selector',
from: 'aliyun',
type: 'aliyun',
},
required: true,
},
regionId: {
title: '接入点',
required: true,
},
signName: {
title: '签名',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
codeTemplateId: {
title: '验证码模板Id',
component: {
name: 'a-input',
vModel: 'value',
},
required: true,
},
} as PluginInputs<AliyunSmsConfig>,

View File

@ -1,12 +1,25 @@
import { AliyunSmsService } from './aliyun-sms.js';
import { YfySmsService } from './yfy-sms.js';
export class SmsServiceFactory {
static createSmsService(type: string) {
const cls = this.GetClassByType(type);
return new cls();
}
static GetClassByType(type: string) {
switch (type) {
case 'aliyun':
return new AliyunSmsService();
return AliyunSmsService;
case 'yfysms':
return YfySmsService;
default:
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}`);
}
}
}