mirror of https://github.com/certd/certd
perf: 支持易发云短信
parent
2c0cbdd29e
commit
94fa77fcd2
|
@ -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
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 是 KeySecret,需要md5
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue