diff --git a/package.json b/package.json index 893f60f..6eeff0c 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,13 @@ "description": "一个项目用COOL就够了", "private": true, "dependencies": { - "@alicloud/pop-core": "^1.7.12", "@cool-midway/cloud": "^6.0.0", "@cool-midway/core": "^6.0.6", "@cool-midway/file": "^6.0.1", "@cool-midway/iot": "^6.0.0", "@cool-midway/pay": "^6.0.0", "@cool-midway/rpc": "^6.0.1", + "@cool-midway/sms": "^6.0.1", "@cool-midway/task": "^6.0.0", "@midwayjs/bootstrap": "^3.11.4", "@midwayjs/cache": "^3.11.5", @@ -36,6 +36,7 @@ "mysql2": "^3.2.4", "svg-captcha": "^1.4.0", "svg2png-wasm": "^1.3.4", + "tencentcloud-sdk-nodejs": "^4.0.607", "typeorm": "^0.3.15", "uuid": "^9.0.0" }, diff --git a/packages/sms/.editorconfig b/packages/sms/.editorconfig new file mode 100644 index 0000000..4c7f8a8 --- /dev/null +++ b/packages/sms/.editorconfig @@ -0,0 +1,11 @@ +# 🎨 editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/packages/sms/.eslintrc.json b/packages/sms/.eslintrc.json new file mode 100644 index 0000000..aad9755 --- /dev/null +++ b/packages/sms/.eslintrc.json @@ -0,0 +1,28 @@ +{ + "extends": "./node_modules/mwts/", + "ignorePatterns": [ + "node_modules", + "dist", + "test", + "jest.config.js", + "typings", + "public/**/**", + "view/**/**" + ], + "env": { + "jest": true + }, + "rules": { + "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/no-unused-vars": "off", + "@typescript-eslint/ban-ts-comment": "off", + "node/no-extraneous-import": "off", + "no-empty": "off", + "node/no-extraneous-require": "off", + "eqeqeq": "off", + "node/no-unsupported-features/node-builtins": "off", + "@typescript-eslint/ban-types": "off", + "no-control-regex": "off", + "prefer-const": "off" + } +} \ No newline at end of file diff --git a/packages/sms/.gitignore b/packages/sms/.gitignore new file mode 100644 index 0000000..13bc3a6 --- /dev/null +++ b/packages/sms/.gitignore @@ -0,0 +1,15 @@ +logs/ +npm-debug.log +yarn-error.log +node_modules/ +package-lock.json +yarn.lock +coverage/ +dist/ +.idea/ +run/ +.DS_Store +*.sw* +*.un~ +.tsbuildinfo +.tsbuildinfo.* diff --git a/packages/sms/.prettierrc.js b/packages/sms/.prettierrc.js new file mode 100644 index 0000000..b964930 --- /dev/null +++ b/packages/sms/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('mwts/.prettierrc.json') +} diff --git a/packages/sms/index.d.ts b/packages/sms/index.d.ts new file mode 100644 index 0000000..e0065de --- /dev/null +++ b/packages/sms/index.d.ts @@ -0,0 +1,10 @@ +export * from './dist/index'; + +declare module '@midwayjs/core/dist/interface' { + interface MidwayConfig { + book?: PowerPartial<{ + a: number; + b: string; + }>; + } +} diff --git a/packages/sms/jest.config.js b/packages/sms/jest.config.js new file mode 100644 index 0000000..82a8b85 --- /dev/null +++ b/packages/sms/jest.config.js @@ -0,0 +1,7 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/test/fixtures'], + coveragePathIgnorePatterns: ['/test/'], + setupFilesAfterEnv: ['./jest.setup.js'] +}; diff --git a/packages/sms/jest.setup.js b/packages/sms/jest.setup.js new file mode 100644 index 0000000..1399c91 --- /dev/null +++ b/packages/sms/jest.setup.js @@ -0,0 +1 @@ +jest.setTimeout(30000); diff --git a/packages/sms/package.json b/packages/sms/package.json new file mode 100644 index 0000000..7ea0c13 --- /dev/null +++ b/packages/sms/package.json @@ -0,0 +1,49 @@ +{ + "name": "@cool-midway/sms", + "version": "6.0.1", + "description": "cool-js.com 短信", + "main": "dist/index.js", + "typings": "index.d.ts", + "scripts": { + "build": "cross-env midway-bin build -c", + "test": "cross-env midway-bin test --ts", + "cov": "cross-env midway-bin cov --ts", + "lint": "mwts check", + "lint:fix": "mwts fix" + }, + "keywords": [ + "cool", + "cool-admin", + "cooljs" + ], + "author": "COOL", + "readme": "README.md", + "files": [ + "dist/**/*.js", + "dist/**/*.d.ts", + "index.d.ts" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://cool-js.com" + }, + "devDependencies": { + "@cool-midway/core": "^6.0.0", + "@midwayjs/cli": "^2.0.9", + "@midwayjs/core": "^3.9.0", + "@midwayjs/decorator": "^3.9.0", + "@midwayjs/mock": "^3.9.0", + "@types/jest": "^29.2.5", + "@types/node": "^18.11.18", + "cross-env": "^7.0.3", + "jest": "^29.3.1", + "mwts": "^1.3.0", + "ts-jest": "^29.0.3", + "typescript": "^4.9.4" + }, + "dependencies": { + "@alicloud/pop-core": "^1.7.13", + "tencentcloud-sdk-nodejs": "^4.0.607" + } +} diff --git a/packages/sms/src/ali.ts b/packages/sms/src/ali.ts new file mode 100644 index 0000000..a2f4e6f --- /dev/null +++ b/packages/sms/src/ali.ts @@ -0,0 +1,64 @@ +import * as Core from '@alicloud/pop-core'; +import { Config, Provide } from "@midwayjs/core"; +import { CoolSmsAliConfig } from './interface'; +import { CoolCommException } from '@cool-midway/core'; + +/** + * 阿里云短信 + */ +@Provide() +export class SmsAli { + @Config('cool.sms.ali') + config: CoolSmsAliConfig; + + /** + * 配置 + * @param config + */ + setConfig(config: CoolSmsAliConfig) { + this.config = config; + } + + /** + * 发送短信 + * @param phone 手机号 + * @param params 参数 + * @param config signName 签名 template 模板 + * @returns + */ + async send(phone, params: { + [key: string]: string; + }, config?: { + signName: string; + template: string; + }) { + const { accessKeyId, accessKeySecret } = this.config; + if (!accessKeyId || !accessKeyId) { + throw new CoolCommException('请配置阿里云短信'); + } + if (!config) { + config = { + signName: this.config.signName, + template: this.config.template, + }; + } + const client = new Core({ + accessKeyId, + accessKeySecret, + endpoint: 'https://dysmsapi.aliyuncs.com', + // endpoint: 'https://cs.cn-hangzhou.aliyuncs.com', + apiVersion: '2017-05-25', + // apiVersion: '2018-04-18', + }); + const data = { + RegionId: 'cn-shanghai', + PhoneNumbers: phone, + signName: config.signName, + templateCode: config.template, + TemplateParam: JSON.stringify(params), + }; + return await client.request('SendSms', data, { + method: 'POST', + }); + } +} \ No newline at end of file diff --git a/packages/sms/src/config/config.default.ts b/packages/sms/src/config/config.default.ts new file mode 100644 index 0000000..2a8c538 --- /dev/null +++ b/packages/sms/src/config/config.default.ts @@ -0,0 +1,5 @@ +export const customKey = { + a: 1, + b: 'hello', + }; + \ No newline at end of file diff --git a/packages/sms/src/configuration.ts b/packages/sms/src/configuration.ts new file mode 100644 index 0000000..d968e7f --- /dev/null +++ b/packages/sms/src/configuration.ts @@ -0,0 +1,17 @@ +import { Configuration } from '@midwayjs/decorator'; +import * as DefaultConfig from './config/config.default'; +import { IMidwayContainer } from '@midwayjs/core'; + +@Configuration({ + namespace: 'cool:sms', + importConfigs: [ + { + default: DefaultConfig, + }, + ], +}) +export class CoolPayConfiguration { + async onReady(container: IMidwayContainer) { + // TODO something + } +} diff --git a/packages/sms/src/index.ts b/packages/sms/src/index.ts new file mode 100644 index 0000000..3856a5c --- /dev/null +++ b/packages/sms/src/index.ts @@ -0,0 +1,9 @@ +export { CoolPayConfiguration as Configuration } from './configuration'; + +export * from './interface'; + +export * from './ali'; +export * from './yp'; +export * from './tx'; + +export * from './sms'; diff --git a/packages/sms/src/interface.ts b/packages/sms/src/interface.ts new file mode 100644 index 0000000..cad710a --- /dev/null +++ b/packages/sms/src/interface.ts @@ -0,0 +1,80 @@ +export interface CoolSmsConfig { + /** + * 阿里云短信配置 + */ + ali: CoolSmsAliConfig; + /** + * 腾讯云短信配置 + */ + tx: CoolTxConfig; + /** + * 云片短信配置 + */ + yp: CoolYpConfig; +} + +/** + * 阿里云配置 + */ +export interface CoolSmsAliConfig { + /** + * 阿里云accessKeyId + */ + accessKeyId: string; + /** + * 阿里云accessKeySecret + */ + accessKeySecret: string; + /** + * 签名,非必填,调用时可以传入 + */ + signName?: string; + /** + * 模板,非必填,调用时可以传入 + */ + template?: string; +} + +/** + * 腾讯云配置 + */ +export interface CoolTxConfig { + /** + * 应用ID + */ + appId: string; + /** + * 腾讯云secretId + */ + secretId: string; + /** + * 腾讯云secretKey + */ + secretKey: string; + /** + * 签名,非必填,调用时可以传入 + */ + signName?: string; + /** + * 模板,非必填,调用时可以传入 + */ + template?: string; +} + +/** + * 云片短信配置 + */ +export interface CoolYpConfig { + /** + * 云片apikey + */ + apikey: string; + /** + * 签名,非必填,调用时可以传入 + */ + signName?: string; + /** + * 模板,非必填,调用时可以传入 + */ + template?: string; +} diff --git a/packages/sms/src/package.json b/packages/sms/src/package.json new file mode 100644 index 0000000..45b3eb3 --- /dev/null +++ b/packages/sms/src/package.json @@ -0,0 +1,49 @@ +{ + "name": "@cool-midway/sms", + "version": "6.0.1", + "description": "cool-js.com 短信", + "main": "index.js", + "typings": "index.d.ts", + "scripts": { + "build": "cross-env midway-bin build -c", + "test": "cross-env midway-bin test --ts", + "cov": "cross-env midway-bin cov --ts", + "lint": "mwts check", + "lint:fix": "mwts fix" + }, + "keywords": [ + "cool", + "cool-admin", + "cooljs" + ], + "author": "COOL", + "readme": "README.md", + "files": [ + "**/*.js", + "**/*.d.ts", + "index.d.ts" + ], + "license": "MIT", + "repository": { + "type": "git", + "url": "https://cool-js.com" + }, + "devDependencies": { + "@cool-midway/core": "^6.0.0", + "@midwayjs/cli": "^2.0.9", + "@midwayjs/core": "^3.9.0", + "@midwayjs/decorator": "^3.9.0", + "@midwayjs/mock": "^3.9.0", + "@types/jest": "^29.2.5", + "@types/node": "^18.11.18", + "cross-env": "^7.0.3", + "jest": "^29.3.1", + "mwts": "^1.3.0", + "ts-jest": "^29.0.3", + "typescript": "^4.9.4" + }, + "dependencies": { + "@alicloud/pop-core": "^1.7.13", + "tencentcloud-sdk-nodejs": "^4.0.607" + } +} diff --git a/packages/sms/src/sms.ts b/packages/sms/src/sms.ts new file mode 100644 index 0000000..2ce7b3d --- /dev/null +++ b/packages/sms/src/sms.ts @@ -0,0 +1,83 @@ +import { Config, Inject, Provide } from "@midwayjs/core"; +import { SmsYp } from "./yp"; +import { SmsAli } from "./ali"; +import { SmsTx } from "./tx"; +import { CoolSmsConfig } from "./interface"; + +@Provide() +export class CoolSms { + @Inject() + smsYp: SmsYp + + @Inject() + smsAli: SmsAli + + @Inject() + smsTx: SmsTx + + @Config('cool.sms') + config: CoolSmsConfig; + + /** + * 配置 + * @param config + */ + setConfig(config: CoolSmsConfig) { + this.smsYp.setConfig(config.yp); + this.smsAli.setConfig(config.ali); + this.smsTx.setConfig(config.tx); + this.config = config; + } + + /** + * 发送验证码 模板字段名为:code + * @param phone + * @param config + */ + async sendCode(phone, config?: { + signName: string; + template: string; + }) { + const code = this.generateNumber(); + let params = { + code + } + await this.send(phone, this.config.tx ? [code] : params, config) + return code; + } + + /** + * 发送短信 + * @param phone + * @param params + * @param config + * @returns + */ + async send(phone: string, params: any, config?: { + signName: string; + template: string; + }) { + if (this.config.ali) { + return await this.smsAli.send(phone, params, config); + } + if (this.config.tx) { + return await this.smsTx.send(phone, params, config); + } + if (this.config.yp) { + return await this.smsYp.send(phone, params, config); + } + return true; + } + + /** + * 生成验证码 + */ + generateNumber(digits = 4) { + if (digits <= 0) { + return 0; + } + const min = Math.pow(10, digits - 1); + const max = Math.pow(10, digits) - 1; + return Math.floor(Math.random() * (max - min + 1)) + min; + } +} \ No newline at end of file diff --git a/packages/sms/src/tx.ts b/packages/sms/src/tx.ts new file mode 100644 index 0000000..1d3e9e1 --- /dev/null +++ b/packages/sms/src/tx.ts @@ -0,0 +1,70 @@ +import { Config, Provide } from "@midwayjs/core"; +import { CoolTxConfig } from './interface'; +import * as tencentcloud from "tencentcloud-sdk-nodejs"; +import { CoolCommException } from "@cool-midway/core"; + +/** + * 腾讯云短信 + */ +@Provide() +export class SmsTx { + @Config('cool.sms.tx') + config: CoolTxConfig; + + /** + * 配置 + * @param config + */ + setConfig(config: CoolTxConfig) { + this.config = config; + } + + /** + * 发送短信 + * @param phone 手机号 + * @param params 参数 + * @param config signName 签名 template 模板 + * @returns + */ + async send(phone: string, params: string[], config?: { + signName: string; + template: string; + }) { + const { appId, secretId, secretKey } = this.config; + if(!config) { + config = { + signName: this.config.signName, + template: this.config.template, + }; + } + if(!appId || !secretId || !secretKey) { + throw new CoolCommException('请配置腾讯云短信'); + } + const smsClient = tencentcloud.sms.v20210111.Client; + + const client = new smsClient({ + credential: { + secretId, + secretKey, + }, + region: 'ap-guangzhou', + profile: { + signMethod: 'HmacSHA256', + httpProfile: { + reqMethod: 'POST', + reqTimeout: 30, + endpoint: 'sms.tencentcloudapi.com', + }, + }, + }); + + const data = { + SmsSdkAppId: appId, + SignName: config.signName, + TemplateId: config.template, + TemplateParamSet: params, + PhoneNumberSet: [`+86${phone}`], + }; + return client.SendSms(data); + } +} \ No newline at end of file diff --git a/packages/sms/src/yp.ts b/packages/sms/src/yp.ts new file mode 100644 index 0000000..01ffc66 --- /dev/null +++ b/packages/sms/src/yp.ts @@ -0,0 +1,86 @@ +import { Config, Provide } from "@midwayjs/core"; +import { CoolYpConfig } from "./interface"; +import { CoolCommException } from "@cool-midway/core"; +import axios from 'axios'; + +/** + * 云片短信 + */ +@Provide() +export class SmsYp { + @Config('cool.sms.yp') + config: CoolYpConfig; + + /** + * 配置 + * @param config + */ + setConfig(config: CoolYpConfig) { + this.config = config; + } + + /** + * 发送短信 + * @param phones 手机号 数组,需要加国家码如 ["+8612345678901"] + * @param params 参数 + * @param config signName 签名 template 模板 + * @returns + */ + async send(phones: string, params: { + [key: string]: string; + }, config?: { + signName: string; + template: string; + }) { + const { apikey } = this.config; + if (!config) { + config = { + signName: this.config.signName, + template: this.config.template, + }; + } + if (!apikey) { + throw new CoolCommException('请配置云片短信'); + } + + const headers = { + Accept: 'application/json;charset=utf-8', + 'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8', + }; + const data = { + apikey: apikey, + mobile: phones, + tpl_id: config.template, + tpl_value: this.smsTplValue(params), + }; + const result = await axios.post( + 'https://sms.yunpian.com/v2/sms/tpl_single_send.json', + data, + { headers } + ); + if (result.data.code === 0) { + return true; + } + return false; + } + + /** + * 获得短信模板值 + * @param obj + * @returns + */ + protected smsTplValue(obj) { + const urlParams = []; + + for (let key in obj) { + // eslint-disable-next-line no-prototype-builtins + if (obj.hasOwnProperty(key)) { + const encodedKey = encodeURIComponent(`#${key}#`); + const encodedValue = encodeURIComponent(obj[key]); + urlParams.push(`${encodedKey}=${encodedValue}`); + } + } + + return urlParams.join('&'); + } +} \ No newline at end of file diff --git a/packages/sms/test/index.test.ts b/packages/sms/test/index.test.ts new file mode 100644 index 0000000..a6c75a1 --- /dev/null +++ b/packages/sms/test/index.test.ts @@ -0,0 +1,14 @@ +import { createLightApp } from '@midwayjs/mock'; +import * as custom from '../src'; + +describe('/test/index.test.ts', () => { + it('test component', async () => { + const app = await createLightApp('', { + imports: [ + custom + ] + }); + const bookService = await app.getApplicationContext().getAsync(custom.BookService); + expect(await bookService.getBookById()).toEqual('hello world'); + }); +}); diff --git a/packages/sms/tsconfig.json b/packages/sms/tsconfig.json new file mode 100644 index 0000000..f01e1d2 --- /dev/null +++ b/packages/sms/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":false, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "skipLibCheck": true, + "noImplicitReturns": false, + "pretty": true, + "declaration": true, + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +} diff --git a/src/configuration.ts b/src/configuration.ts index 2019271..3129a76 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -11,6 +11,7 @@ import * as localTask from '@midwayjs/task'; import * as cool from '@cool-midway/core'; import * as cloud from '@cool-midway/cloud'; import * as file from '@cool-midway/file'; +// import * as sms from '@cool-midway/sms'; // import * as rpc from '@cool-midway/rpc'; // import * as task from '@cool-midway/task'; // import * as pay from '@cool-midway/pay'; @@ -46,6 +47,8 @@ import * as file from '@cool-midway/file'; // pay, // 物联网开发,如MQTT支持等 // iot, + // 短信 + // sms { component: info, enabledEnvironment: ['local'], diff --git a/src/modules/user/service/sms.ts b/src/modules/user/service/sms.ts index c2816c7..57c13a9 100644 --- a/src/modules/user/service/sms.ts +++ b/src/modules/user/service/sms.ts @@ -1,8 +1,8 @@ import { Provide, Config, Inject } from '@midwayjs/decorator'; import { BaseService, CoolCommException } from '@cool-midway/core'; import * as _ from 'lodash'; -import * as Core from '@alicloud/pop-core'; import { CacheManager } from '@midwayjs/cache'; +import { CoolSms } from '@cool-midway/sms'; /** * 描述 @@ -16,19 +16,17 @@ export class UserSmsService extends BaseService { @Inject() cacheManager: CacheManager; + @Inject() + coolSms: CoolSms; + /** * 发送验证码 * @param phone */ async sendSms(phone) { try { - const TemplateParam = { code: _.random(1000, 9999) }; - await this.send(phone, TemplateParam); - this.cacheManager.set( - `sms:${phone}`, - TemplateParam.code, - this.config.timeout - ); + const code = await this.coolSms.sendCode(phone); + this.cacheManager.set(`sms:${phone}`, code, this.config.timeout); } catch (error) { throw new CoolCommException('发送过于频繁,请稍后再试'); } @@ -47,33 +45,4 @@ export class UserSmsService extends BaseService { } return false; } - - /** - * 发送短信 - * @param phone - * @param templateCode - * @param template - */ - async send(phone, TemplateParam) { - const { signName, accessKeyId, accessKeySecret, templateCode } = - this.config; - const client = new Core({ - accessKeyId, - accessKeySecret, - endpoint: 'https://dysmsapi.aliyuncs.com', - // endpoint: 'https://cs.cn-hangzhou.aliyuncs.com', - apiVersion: '2017-05-25', - // apiVersion: '2018-04-18', - }); - const params = { - RegionId: 'cn-shanghai', - PhoneNumbers: phone, - signName, - templateCode, - TemplateParam: JSON.stringify(TemplateParam), - }; - return await client.request('SendSms', params, { - method: 'POST', - }); - } }