From b88adc5c1136788c1b870d4149769538552ead25 Mon Sep 17 00:00:00 2001 From: Dunqing Date: Mon, 30 Jan 2023 21:16:03 +0800 Subject: [PATCH] feat(verification-plugin): support tencent sms (#1382) * feat: support tencent sms * feat: complete tencent sms * refactor: clean * feat: improve error message --- packages/plugins/verification/package.json | 3 +- .../verification/src/client/locale/zh-CN.ts | 7 +++ .../src/client/providerTypes/index.ts | 2 + .../src/client/providerTypes/sms-tencent.ts | 52 +++++++++++++++++ .../src/client/schemas/providers.ts | 1 + .../src/server/actions/verifications.ts | 11 +--- .../verification/src/server/constants.ts | 1 + .../verification/src/server/locale/zh-CN.ts | 2 +- .../src/server/providers/index.ts | 11 ++-- .../src/server/providers/sms-tencent.ts | 57 +++++++++++++++++++ yarn.lock | 44 +++++++++++++- 11 files changed, 174 insertions(+), 17 deletions(-) create mode 100644 packages/plugins/verification/src/client/providerTypes/sms-tencent.ts create mode 100644 packages/plugins/verification/src/server/providers/sms-tencent.ts diff --git a/packages/plugins/verification/package.json b/packages/plugins/verification/package.json index 8cc172f410..9b8d138506 100644 --- a/packages/plugins/verification/package.json +++ b/packages/plugins/verification/package.json @@ -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" diff --git a/packages/plugins/verification/src/client/locale/zh-CN.ts b/packages/plugins/verification/src/client/locale/zh-CN.ts index e1ba7540bd..d5d8dcaac9 100644 --- a/packages/plugins/verification/src/client/locale/zh-CN.ts +++ b/packages/plugins/verification/src/client/locale/zh-CN.ts @@ -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' }; diff --git a/packages/plugins/verification/src/client/providerTypes/index.ts b/packages/plugins/verification/src/client/providerTypes/index.ts index ad9628e0b8..c9fdafaca5 100644 --- a/packages/plugins/verification/src/client/providerTypes/index.ts +++ b/packages/plugins/verification/src/client/providerTypes/index.ts @@ -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 = new Registry(); providerTypes.register('sms-aliyun', SMSAliyun); +providerTypes.register('sms-tencent', SMSTencent); export default providerTypes; diff --git a/packages/plugins/verification/src/client/providerTypes/sms-tencent.ts b/packages/plugins/verification/src/client/providerTypes/sms-tencent.ts new file mode 100644 index 0000000000..139da826e1 --- /dev/null +++ b/packages/plugins/verification/src/client/providerTypes/sms-tencent.ts @@ -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; diff --git a/packages/plugins/verification/src/client/schemas/providers.ts b/packages/plugins/verification/src/client/schemas/providers.ts index 9f59533deb..4de319101e 100644 --- a/packages/plugins/verification/src/client/schemas/providers.ts +++ b/packages/plugins/verification/src/client/schemas/providers.ts @@ -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' }, ], }, }, diff --git a/packages/plugins/verification/src/server/actions/verifications.ts b/packages/plugins/verification/src/server/actions/verifications.ts index 055e1763e6..d553c9f8b9 100644 --- a/packages/plugins/verification/src/server/actions/verifications.ts +++ b/packages/plugins/verification/src/server/actions/verifications.ts @@ -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: diff --git a/packages/plugins/verification/src/server/constants.ts b/packages/plugins/verification/src/server/constants.ts index 25cfdb8a66..5d4c030a85 100644 --- a/packages/plugins/verification/src/server/constants.ts +++ b/packages/plugins/verification/src/server/constants.ts @@ -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; diff --git a/packages/plugins/verification/src/server/locale/zh-CN.ts b/packages/plugins/verification/src/server/locale/zh-CN.ts index a05b96826e..eb4a7b6c29 100644 --- a/packages/plugins/verification/src/server/locale/zh-CN.ts +++ b/packages/plugins/verification/src/server/locale/zh-CN.ts @@ -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': '无效的验证码', }; diff --git a/packages/plugins/verification/src/server/providers/index.ts b/packages/plugins/verification/src/server/providers/index.ts index 42676be692..a820b3bdec 100644 --- a/packages/plugins/verification/src/server/providers/index.ts +++ b/packages/plugins/verification/src/server/providers/index.ts @@ -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{} + async send(receiver: string, data: { [key: string]: any }): Promise { } } 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); diff --git a/packages/plugins/verification/src/server/providers/sms-tencent.ts b/packages/plugins/verification/src/server/providers/sms-tencent.ts new file mode 100644 index 0000000000..dfdb40d6a8 --- /dev/null +++ b/packages/plugins/verification/src/server/providers/sms-tencent.ts @@ -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; + + 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 + } +} diff --git a/yarn.lock b/yarn.lock index 507885c036..0a6b0c3d8a 100644 --- a/yarn.lock +++ b/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"