From 54cb1beb39275413ea577d0e10d48a63565c9b1f Mon Sep 17 00:00:00 2001 From: filesite Date: Thu, 2 May 2024 15:08:37 +0000 Subject: [PATCH] aliyun sms send done --- README.md | 35 ++++++++++++++++++++++- common.js | 26 +++++++++++++++++ conf/config.js | 4 ++- package.json | 5 +++- router_aliyun.js | 70 +++++++++++++++++++++++++++++++++++++++++++++ server.js | 52 +++++++++++++++++++++++++++++++++ test/aliyun.test.js | 31 ++++++++++++++++++++ 7 files changed, 220 insertions(+), 3 deletions(-) create mode 100644 common.js create mode 100644 router_aliyun.js create mode 100644 server.js diff --git a/README.md b/README.md index b2f11da..78b8b7c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,36 @@ # service-3rd -Service implement api or sdk of 3rd party. \ No newline at end of file +Service implement api or sdk of 3rd party. + +实现第三方平台接口的公用服务,通过API调用。 + +## 已对接功能 + +* 阿里云的短信发送接口 + + +## 接口参数签名方法 + +将所有参数按字母排序之后转换成JSON字符串(注意需保持斜杠/和unicode字符串不转义),最后再拼接上token计算MD5值。 + +示例如下: +``` +var token = 'hello world'; //config.js里配置的密钥 +var params = { //参数示例 + "b": 2, + "a": 1, + "t": 234343 +}; + +var sortObj = function(obj) { //参数排序方法 + return Object.keys(obj).sort().reduce(function(result, key) { + result[key] = obj[key]; + return result; + }, {}); +}; + +//1. 排序参数 +var sortedParams = sortObj(params); +//2. 计算MD5值 +var sign = md5( JSON.stringify(sortedParams) + token ); +``` diff --git a/common.js b/common.js new file mode 100644 index 0000000..5717e92 --- /dev/null +++ b/common.js @@ -0,0 +1,26 @@ +'use strict'; + +const md5 = require('md5'); + +class Common { + static sortDict(obj) { //dict按key排序 + return Object.keys(obj).sort().reduce(function(result, key) { + result[key] = obj[key]; + return result; + }, {}); + } + + static sign(params, token) { //对参数做MD5签名 + return md5( JSON.stringify(Common.sortDict(params)) + token ); + } + + static isPhoneNumber(number) { + return /^1[3-9][0-9]{9}$/i.test(number); + } + + static isVerifyCode(code) { + return /^[0-9]{4}$/i.test(code); + } +} + +exports.default = Common; \ No newline at end of file diff --git a/conf/config.js b/conf/config.js index af91c10..2a70e32 100644 --- a/conf/config.js +++ b/conf/config.js @@ -13,7 +13,6 @@ exports.getCustomConfigs = async function(configFile) { let data = null; let customConfigFile = resolve(configFile); - console.log(customConfigFile); if (fs.existsSync(customConfigFile)) { try { const contents = await readFile(customConfigFile, { encoding: 'utf8' }); @@ -29,6 +28,9 @@ exports.getCustomConfigs = async function(configFile) { }; exports.default = { + "secret": "Api调用的密钥", + + //阿里云API调用配置 "ALIBABA_CLOUD_ACCESS_KEY_ID": myAccessKey, "ALIBABA_CLOUD_ACCESS_KEY_SECRET": myAccessSecret, }; \ No newline at end of file diff --git a/package.json b/package.json index b0ac25f..55b13d6 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,9 @@ "node": ">=8.x" }, "dependencies": { - "@alicloud/dysmsapi20170525": "^2.0.24" + "@alicloud/dysmsapi20170525": "^2.0.24", + "express": "^4.18.2", + "md5": "^2.3.0", + "axios": "^1.3.3" } } diff --git a/router_aliyun.js b/router_aliyun.js new file mode 100644 index 0000000..fd0070e --- /dev/null +++ b/router_aliyun.js @@ -0,0 +1,70 @@ +'use strict'; + +/** + * Express router of aliyun/ + */ + +const express = require('express'); +const {default: common} = require('./common.js'); + +const {default: aliyunSmsClient} = require('./aliyunSmsClient'); +const {default: defaultConfig, getCustomConfigs: getCustomConfigs} = require('./conf/config'); + + + +const router = express.Router(); + +//发送注册验证码短信接口 +/** + * @phoneNumber: 手机号码 + * @codeNumber: 4位数字的验证码 + * @action: ['register', 'login'] + * @sign: 签名 + */ +router.post('/sendverifycode', async (req, res) => { + let phoneNumber = req.body.phoneNumber; + let codeNumber = req.body.codeNumber; + let action = req.body.action ? req.body.action : 'register'; + let sign = req.body.sign; + + let data = {code: 0, message: ''}; + let myConfig = await getCustomConfigs('./conf/custom_config.json'); + + if (!phoneNumber || !codeNumber || !sign) { + data.message = '参数不能为空'; + }else if (common.isPhoneNumber(phoneNumber) == false) { + data.message = '手机号码格式错误'; + }else if (common.isVerifyCode(codeNumber) == false) { + data.message = '验证码格式错误,必须是4位数的数字'; + }else { + let paramsCheck = {}; + for (const key in req.body) { + if (key != 'sign') { + paramsCheck[key] = req.body[key]; + } + } + + let mySign = common.sign(paramsCheck, myConfig.secret); + if (mySign.toLowerCase() != sign.toLowerCase()) { + data.message = `签名 ${sign} 不匹配,请确保密钥正确及签名方法跟文档一致`; + } + } + + if (!data.message) { + let signName = 'Ta荐', + templateCode = action == 'register' ? 'SMS_465895580' : 'SMS_465915662', + templateParam = `{"code":"${codeNumber}"}`; + + let sended = await aliyunSmsClient.send(myConfig, signName, templateCode, templateParam, phoneNumber); + if (sended) { + data.code = 1; + data.message = '验证码发送成功'; + }else { + data.message = '验证码发送失败,请稍后重试'; + } + } + + return res.status(200).json(data); +}); + +exports.default = router; \ No newline at end of file diff --git a/server.js b/server.js new file mode 100644 index 0000000..288935e --- /dev/null +++ b/server.js @@ -0,0 +1,52 @@ +'use strict'; + +/** + * Main program of service of 3rd party + **/ + +const express = require('express'); +const bodyParser = require('body-parser'); +const {default: aliyunRouter} = require('./router_aliyun.js'); + +const app = express(); + +//Express behind proxies +app.set('trust proxy', true); +app.disable('x-powered-by'); + +//Serving static files +app.use(express.static('public')); + +// parse application/x-www-form-urlencoded +app.use(bodyParser.urlencoded({ limit: '2mb', extended: false })) +// parse application/json +app.use(bodyParser.json({ limit: '2mb' })) + +app.get('/', (req, res) => { + return res.send('Welcome to @filesite/service-3rd'); +}); + +//阿里云相关接口调用 +app.use('/aliyun', aliyunRouter); + +//error handler +app.use((err, req, res, next) => { + if (res.headersSent) { + return next(err); + } + + console.error('Request error in @filesite/service-3rd: %s', err.stack); + + var statusCode = 500; + if (typeof(err.statusCode) != 'undefined' && err.statusCode) { + statusCode = err.statusCode; + } + return res.status(statusCode).send(err.message); +}) + +// Listen to the App Engine-specified port, or 8080 otherwise +const PORT = process.env.PORT || 8081; +const HOST = '127.0.0.1'; +app.listen(PORT, HOST, async () => { + console.log('Server listening on port %s...', PORT); +}); diff --git a/test/aliyun.test.js b/test/aliyun.test.js index f3a9f72..f01641f 100644 --- a/test/aliyun.test.js +++ b/test/aliyun.test.js @@ -2,10 +2,17 @@ const test = require('node:test'); const assert = require('node:assert'); +const axios = require('axios'); +const {default: common} = require('../common.js'); const {default: aliyunSmsClient} = require('../aliyunSmsClient'); const {default: defaultConfig, getCustomConfigs: getCustomConfigs} = require('../conf/config'); +const axiosConfig = { + timeout: 5000, + proxy: false +}; + test('Custom config load test', async (t) => { console.log('Config data', defaultConfig); assert.equal(defaultConfig.ALIBABA_CLOUD_ACCESS_KEY_ID, '你的AccessKey ID'); @@ -21,6 +28,7 @@ test('AliyunSmsClient test', async (t) => { assert.ok(client); + /* let signName = 'Ta荐', templateCode = 'SMS_465915662', templateParam = '{"code":"2345"}', @@ -29,4 +37,27 @@ test('AliyunSmsClient test', async (t) => { let sended = await aliyunSmsClient.send(myConfig, signName, templateCode, templateParam, phoneNumber); assert.equal(sended, true); + */ }); + +test('Aliyun api test', async (t) => { + let myConfig = await getCustomConfigs('./conf/custom_config.json'); + + let api = 'http://127.0.0.1:8081/aliyun/sendverifycode'; + let params = { + phoneNumber: '13168946847', + codeNumber: '8866', + action: 'register' + }; + + let sign = common.sign(params, myConfig.secret); + assert.ok(myConfig); + assert.ok(sign); + + params.sign = sign; + const response = await axios.post(api, params, axiosConfig); + console.log(response.data); + + assert.equal(response.status, 200); + assert.equal(response.data.code, 1); +}); \ No newline at end of file