feat(verification-plugin): support tencent sms (#1382)
* feat: support tencent sms * feat: complete tencent sms * refactor: clean * feat: improve error message
This commit is contained in:
parent
ddb174db1b
commit
b88adc5c11
@ -12,7 +12,8 @@
|
||||
"@nocobase/actions": "0.9.0-alpha.2",
|
||||
"@nocobase/resourcer": "0.9.0-alpha.2",
|
||||
"@nocobase/server": "0.9.0-alpha.2",
|
||||
"@nocobase/utils": "0.9.0-alpha.2"
|
||||
"@nocobase/utils": "0.9.0-alpha.2",
|
||||
"tencentcloud-sdk-nodejs": "^4.0.525"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nocobase/test": "0.9.0-alpha.2"
|
||||
|
@ -10,4 +10,11 @@ export default {
|
||||
'Endpoint': '接入点',
|
||||
'Sign': '签名',
|
||||
'Template code': '模板代码',
|
||||
|
||||
'Secret Id': 'Secret Id',
|
||||
'Secret Key': 'Secret Key',
|
||||
'Region': '地域',
|
||||
'Sign name': '短信签名内容',
|
||||
'Sms sdk app id': '短信应用 ID',
|
||||
'Template Id': '短信模板 ID'
|
||||
};
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
import { Registry } from "@nocobase/utils/client";
|
||||
import SMSAliyun from './sms-aliyun';
|
||||
import SMSTencent from './sms-tencent';
|
||||
|
||||
const providerTypes: Registry<ISchema> = new Registry();
|
||||
|
||||
providerTypes.register('sms-aliyun', SMSAliyun);
|
||||
providerTypes.register('sms-tencent', SMSTencent);
|
||||
|
||||
export default providerTypes;
|
||||
|
@ -0,0 +1,52 @@
|
||||
import { ISchema } from '@formily/react';
|
||||
|
||||
import { NAMESPACE } from '../locale';
|
||||
|
||||
export default {
|
||||
type: 'object',
|
||||
properties: {
|
||||
secretId: {
|
||||
title: `{{t("Secret Id", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
secretKey: {
|
||||
title: `{{t("Secret Key", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Password',
|
||||
},
|
||||
region: {
|
||||
title: `{{t("Region", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
endpoint: {
|
||||
title: `{{t("Endpoint", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
default: 'sms.tencentcloudapi.com',
|
||||
},
|
||||
SignName: {
|
||||
title: `{{t("Sign name", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
SmsSdkAppId: {
|
||||
title: `{{t("Sms sdk app id", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
},
|
||||
TemplateId: {
|
||||
title: `{{t("Template Id", { ns: "${NAMESPACE}" })}}`,
|
||||
type: 'string',
|
||||
'x-decorator': 'FormItem',
|
||||
'x-component': 'Input',
|
||||
}
|
||||
}
|
||||
} as ISchema;
|
@ -38,6 +38,7 @@ const collection = {
|
||||
required: true,
|
||||
enum: [
|
||||
{ label: `{{t("Aliyun SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-aliyun' },
|
||||
{ label: `{{t("Tencent SMS", { ns: "${NAMESPACE}" })}}`, value: 'sms-tencent' },
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -19,12 +19,7 @@ export async function create(context: Context, next: Next) {
|
||||
return context.throw(400, 'Invalid action type');
|
||||
}
|
||||
|
||||
const ProviderRepo = context.db.getRepository('verifications_providers');
|
||||
const providerItem = await ProviderRepo.findOne({
|
||||
filter: {
|
||||
default: true
|
||||
}
|
||||
});
|
||||
const providerItem = await plugin.getDefault()
|
||||
if (!providerItem) {
|
||||
console.error(`[verification] no provider for action (${values.type}) provided`);
|
||||
return context.throw(500);
|
||||
@ -32,7 +27,7 @@ export async function create(context: Context, next: Next) {
|
||||
|
||||
const receiver = interceptor.getReceiver(context);
|
||||
if (!receiver) {
|
||||
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', {ns: namespace }) });
|
||||
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', { ns: namespace }) });
|
||||
}
|
||||
const VerificationModel = context.db.getModel('verifications');
|
||||
const record = await VerificationModel.findOne({
|
||||
@ -70,7 +65,7 @@ export async function create(context: Context, next: Next) {
|
||||
switch (error.name) {
|
||||
case 'InvalidReceiver':
|
||||
// TODO: message should consider email and other providers, maybe use "receiver"
|
||||
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', {ns: namespace })});
|
||||
return context.throw(400, { code: 'InvalidReceiver', message: context.t('Not a valid cellphone number, please re-enter', { ns: namespace }) });
|
||||
case 'RateLimit':
|
||||
return context.throw(429, context.t('You are trying so frequently, please slow down', { ns: namespace }));
|
||||
default:
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const PROVIDER_TYPE_SMS_ALIYUN = 'sms-aliyun';
|
||||
export const PROVIDER_TYPE_SMS_TENCENT = 'sms-tencent';
|
||||
|
||||
export const CODE_STATUS_UNUSED = 0;
|
||||
export const CODE_STATUS_USED = 1;
|
||||
|
@ -3,5 +3,5 @@ export default {
|
||||
'Not a valid cellphone number, please re-enter': '不是有效的手机号,请重新输入',
|
||||
"Please don't retry in {{time}} seconds": '请 {{time}} 秒后再试',
|
||||
'You are trying so frequently, please slow down': '您的操作太频繁,请稍后再试',
|
||||
'Verification code is invalid': '无效的验证码'
|
||||
'Verification code is invalid': '无效的验证码',
|
||||
};
|
||||
|
@ -3,25 +3,26 @@ import path from 'path';
|
||||
import { requireModule } from '@nocobase/utils';
|
||||
|
||||
import Plugin from '../Plugin';
|
||||
import { PROVIDER_TYPE_SMS_ALIYUN } from '../constants';
|
||||
import { PROVIDER_TYPE_SMS_ALIYUN, PROVIDER_TYPE_SMS_TENCENT } from '../constants';
|
||||
|
||||
|
||||
|
||||
export class Provider {
|
||||
constructor(protected plugin: Plugin, protected options) {}
|
||||
constructor(protected plugin: Plugin, protected options) { }
|
||||
|
||||
async send(receiver: string, data: { [key: string]: any }): Promise<any>{}
|
||||
async send(receiver: string, data: { [key: string]: any }): Promise<any> { }
|
||||
}
|
||||
|
||||
interface Providers {
|
||||
[key: string]: typeof Provider
|
||||
}
|
||||
|
||||
export default function(plugin: Plugin, more: Providers = {}) {
|
||||
export default function (plugin: Plugin, more: Providers = {}) {
|
||||
const { providers } = plugin;
|
||||
|
||||
const natives = [
|
||||
PROVIDER_TYPE_SMS_ALIYUN
|
||||
PROVIDER_TYPE_SMS_ALIYUN,
|
||||
PROVIDER_TYPE_SMS_TENCENT
|
||||
].reduce((result, key) => Object.assign(result, {
|
||||
[key]: requireModule(path.isAbsolute(key) ? key : path.join(__dirname, key)) as typeof Provider
|
||||
}), {} as Providers);
|
||||
|
@ -0,0 +1,57 @@
|
||||
import { Provider } from '.';
|
||||
import * as tencentcloud from "tencentcloud-sdk-nodejs"
|
||||
|
||||
// 导入对应产品模块的client models。
|
||||
const smsClient = tencentcloud.sms.v20210111.Client;
|
||||
|
||||
export default class extends Provider {
|
||||
client: InstanceType<typeof smsClient>;
|
||||
|
||||
constructor(plugin, options) {
|
||||
super(plugin, options);
|
||||
|
||||
const { secretId, secretKey, region, endpoint } = this.options;
|
||||
|
||||
|
||||
/* 实例化要请求产品(以sms为例)的client对象 */
|
||||
this.client = new smsClient({
|
||||
credential: {
|
||||
secretId,
|
||||
secretKey
|
||||
},
|
||||
region,
|
||||
profile: {
|
||||
httpProfile: {
|
||||
endpoint
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async send(phoneNumbers, data: { code: string }) {
|
||||
const { SignName, TemplateId, SmsSdkAppId } = this.options
|
||||
const result = await this.client.SendSms({
|
||||
PhoneNumberSet: [phoneNumbers],
|
||||
SignName,
|
||||
TemplateId,
|
||||
SmsSdkAppId,
|
||||
TemplateParamSet: [data.code]
|
||||
})
|
||||
|
||||
const errCode = result.SendStatusSet[0].Code
|
||||
const error = new Error(`${errCode}:${result.SendStatusSet[0].Message}`)
|
||||
switch (errCode) {
|
||||
case 'Ok':
|
||||
return result.RequestId
|
||||
case 'InvalidParameterValue.IncorrectPhoneNumber':
|
||||
error.name = 'InvalidReceiver'
|
||||
case 'LimitExceeded.DeliveryFrequencyLimit':
|
||||
case 'LimitExceeded.PhoneNumberDailyLimit':
|
||||
case 'LimitExceeded.PhoneNumberThirtySecondLimit':
|
||||
case 'LimitExceeded.PhoneNumberOneHourLimit':
|
||||
error.name = 'RateLimit'
|
||||
}
|
||||
throw error
|
||||
}
|
||||
}
|
44
yarn.lock
44
yarn.lock
@ -6442,7 +6442,14 @@
|
||||
resolved "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc"
|
||||
integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==
|
||||
|
||||
"@types/react-dom@^16.9.8", "@types/react-dom@^17.0.0":
|
||||
"@types/react-dom@^16.9.8":
|
||||
version "16.9.17"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.17.tgz#29100cbcc422d7b7dba7de24bb906de56680dd34"
|
||||
integrity sha512-qSRyxEsrm5btPXnowDOs5jSkgT8ldAA0j6Qp+otHUh+xHzy3sXmgNfyhucZjAjkgpdAUw9rJe0QRtX/l+yaS4g==
|
||||
dependencies:
|
||||
"@types/react" "^16"
|
||||
|
||||
"@types/react-dom@^17.0.0":
|
||||
version "17.0.11"
|
||||
resolved "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.11.tgz#e1eadc3c5e86bdb5f7684e00274ae228e7bcc466"
|
||||
integrity sha512-f96K3k+24RaLGVu/Y2Ng3e1EbZ8/cVJvypZWd7cy0ofCBaf2lcM46xNhycMZ2xGwbBjRql7hOlZ+e2WlJ5MH3Q==
|
||||
@ -6502,7 +6509,7 @@
|
||||
"@types/history" "*"
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^16.9.43", "@types/react@^17.0.0":
|
||||
"@types/react@*", "@types/react@>=16.9.11", "@types/react@^17.0.0":
|
||||
version "17.0.34"
|
||||
resolved "https://registry.npmjs.org/@types/react/-/react-17.0.34.tgz#797b66d359b692e3f19991b6b07e4b0c706c0102"
|
||||
integrity sha512-46FEGrMjc2+8XhHXILr+3+/sTe3OfzSPU9YGKILLrUYbQ1CLQC9Daqo1KzENGXAWwrFwiY0l4ZbF20gRvgpWTg==
|
||||
@ -6511,6 +6518,15 @@
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/react@^16", "@types/react@^16.9.43":
|
||||
version "16.14.34"
|
||||
resolved "https://registry.yarnpkg.com/@types/react/-/react-16.14.34.tgz#d129324ffda312044e1c47aab18696e4ed493282"
|
||||
integrity sha512-b99nWeGGReLh6aKBppghVqp93dFJtgtDOzc8NXM6hewD8PQ2zZG5kBLgbx+VJr7Q7WBMjHxaIl3dwpwwPIUgyA==
|
||||
dependencies:
|
||||
"@types/prop-types" "*"
|
||||
"@types/scheduler" "*"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@types/resolve@1.17.1":
|
||||
version "1.17.1"
|
||||
resolved "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
|
||||
@ -17453,6 +17469,13 @@ node-fetch@2.6.7, node-fetch@^2.6.7:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.2.0:
|
||||
version "2.6.8"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.8.tgz#a68d30b162bc1d8fd71a367e81b997e1f4d4937e"
|
||||
integrity sha512-RZ6dBYuj8dRSfxpUSu+NsdF1dpPpluJxwOp+6IoDp/sH2QNDSvurYsAa+F1WxY2RjA1iP93xhcsUoYbF2XBqVg==
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.6"
|
||||
resolved "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.6.tgz#1751a7c01834e8e1697758732e9efb6eeadfaf89"
|
||||
@ -23241,6 +23264,18 @@ temp@^0.8.1:
|
||||
dependencies:
|
||||
rimraf "~2.6.2"
|
||||
|
||||
tencentcloud-sdk-nodejs@^4.0.525:
|
||||
version "4.0.525"
|
||||
resolved "https://registry.yarnpkg.com/tencentcloud-sdk-nodejs/-/tencentcloud-sdk-nodejs-4.0.525.tgz#b0b6d6caf6de5a5aeeda4f1f172ea7ecc447b9be"
|
||||
integrity sha512-Ym7DfnWsxtXpAd0sL3108dykO7q4hyTxrLeumPMHnzndGGWDqEcj6UMaZsylBOigYb2i423oJ5ePyPkDP5ot1g==
|
||||
dependencies:
|
||||
form-data "^3.0.0"
|
||||
get-stream "^6.0.0"
|
||||
https-proxy-agent "^5.0.0"
|
||||
is-stream "^2.0.0"
|
||||
node-fetch "^2.2.0"
|
||||
tslib "1.13.0"
|
||||
|
||||
term-size@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.npmjs.org/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
|
||||
@ -23714,6 +23749,11 @@ tsconfig@^7.0.0:
|
||||
strip-bom "^3.0.0"
|
||||
strip-json-comments "^2.0.0"
|
||||
|
||||
tslib@1.13.0:
|
||||
version "1.13.0"
|
||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.13.0.tgz#c881e13cc7015894ed914862d276436fa9a47043"
|
||||
integrity sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==
|
||||
|
||||
tslib@1.9.3:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||
|
Loading…
Reference in New Issue
Block a user