parent
							
								
									aaef05525e
								
							
						
					
					
						commit
						764d492449
					
				| @ -0,0 +1,183 @@ | ||||
| const axios = require('axios'); | ||||
| const formData = require('form-data'); | ||||
| const moment = require('moment'); | ||||
| const qs = require('qs'); | ||||
| 
 | ||||
| const {RequestType} = require('./RequestType'); | ||||
| const {SignUtil} = require('./SignUtil'); | ||||
| const {BaseRequest} = require('../request/BaseRequest'); | ||||
| 
 | ||||
| const HEADERS = {'Accept-Encoding': 'identity'}; | ||||
| 
 | ||||
| const getHeaders = (headers = {}) => { | ||||
|     return Object.assign({}, headers, HEADERS); | ||||
| }; | ||||
| 
 | ||||
| const parseResponse = (error, response, request) => { | ||||
|     if (!error && response.status === 200) { | ||||
|         return request.parseResponse(response.data); | ||||
|     } else { | ||||
|         return { // 重新封装请求异常回调,以防中断
 | ||||
|             msg: '请求异常', | ||||
|             code: '502', | ||||
|             sub_msg: `${error}`, | ||||
|             sub_code: 'isv.invalid-server' | ||||
|         }; | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| const buildParams = (instance, request, token) => { | ||||
|     const {appId, privateKey} = instance; | ||||
|     const allParams = { | ||||
|         'app_id': appId, | ||||
|         'method': request.getMethod(), | ||||
|         'charset': 'UTF-8', | ||||
|         'sign_type': 'RSA2', | ||||
|         'timestamp': moment().format('YYYY-MM-DD HH:mm:ss'), | ||||
|         'version': request.getVersion(), | ||||
|         'biz_content': JSON.stringify(request.bizModel) | ||||
|     }; | ||||
| 
 | ||||
|     if (token) { | ||||
|         allParams['app_auth_token'] = token; | ||||
|     } | ||||
|     // 创建签名
 | ||||
|     allParams.sign = SignUtil.createSign(allParams, privateKey, 'RSA2'); | ||||
|     return allParams; | ||||
| }; | ||||
| 
 | ||||
| const executeRequest = async (instance = {}, request, token, callback, {headers}) => { | ||||
|     const params = buildParams(instance, request, token); | ||||
|     const {url} = instance; | ||||
|     const options = { | ||||
|         url, | ||||
|         method: 'POST', | ||||
|         params: undefined, | ||||
|         data: undefined | ||||
|     }; | ||||
|     headers = getHeaders(headers); | ||||
|     const requestType = request.getRequestType(); | ||||
|     switch (requestType) { | ||||
|         case RequestType.GET: { | ||||
|             options.method = 'GET'; | ||||
|             options.params = params; | ||||
|         } | ||||
|             break; | ||||
|         case RequestType.POST_FORM: { | ||||
|             headers = Object.assign(headers, { | ||||
|                 'Content-Type': 'application/x-www-form-urlencoded' | ||||
|             }); | ||||
|             options.data = qs.stringify(params); | ||||
|         } | ||||
|             break; | ||||
|         case RequestType.POST_JSON: { | ||||
|             options.data = params; | ||||
|         } | ||||
|             break; | ||||
|         case RequestType.POST_FILE: { | ||||
|             const formData = new formData(); | ||||
|             (request.files || []).forEach(({name, path}) => { | ||||
|                 formData.append(name, path, { | ||||
|                     contentType: 'application/octet-stream' | ||||
|                 }); | ||||
|             }); | ||||
|             Object.keys(params).forEach(key => { | ||||
|                 const value = params[key]; | ||||
|                 if (!(typeof key === 'undefined' || typeof value === 'undefined')) { | ||||
|                     formData.append(key, params[key]); | ||||
|                 } | ||||
|             }); | ||||
|             options.data = formData; | ||||
|         } | ||||
|             break; | ||||
|         default: { | ||||
|             callback(parseResponse(new Error('request.getRequestType()类型不正确'), undefined, request)); | ||||
|             return; | ||||
|         } | ||||
|     } | ||||
|     try { | ||||
|         options['headers'] = headers; | ||||
|         const response = await axios.request(options); | ||||
|         callback(parseResponse(undefined, response, request)); | ||||
|     } catch (error) { | ||||
|         callback(parseResponse(error, undefined, request)); | ||||
|     } | ||||
| }; | ||||
| 
 | ||||
| module.exports = class OpenClient { | ||||
|     /** | ||||
|      * 初始化客户端 | ||||
|      * @param appId 应用ID | ||||
|      * @param privateKey 应用私钥,2048位,PKCS8 | ||||
|      * @param url 请求url | ||||
|      */ | ||||
|     constructor(appId, privateKey, url) { | ||||
|         this.appId = appId; | ||||
|         this.privateKey = privateKey; | ||||
|         this.url = url; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 发送请求 | ||||
|      * @param request 请求类 | ||||
|      * @param callback 回调函数,参数json(undefined则使用executeSync) | ||||
|      * @param options 自定义参数,如headers | ||||
|      */ | ||||
|     execute(request, callback, options) { | ||||
|         if (typeof callback == 'function') { | ||||
|             return this.executeToken(request, null, callback, options); | ||||
|         } else { | ||||
|             return this.executeSync(request, options); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 发送同步请求 | ||||
|      * @param request 请求类 | ||||
|      * @param options 自定义参数,如headers | ||||
|      * */ | ||||
|     executeSync(request, options) { | ||||
|         return new Promise((resolve) => { | ||||
|             const _ = this.execute(request, res => { | ||||
|                 resolve(res); | ||||
|             }, options); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 发送请求 | ||||
|      * @param request 请求类 | ||||
|      * @param token token | ||||
|      * @param callback 回调函数,参数json(undefined则使用executeTokenSync) | ||||
|      * @param options 自定义参数,如headers | ||||
|      */ | ||||
|     async executeToken(request, token, callback, options) { | ||||
|         if (!(request instanceof BaseRequest)) { | ||||
|             throw 'request类未继承BaseRequest'; | ||||
|         } | ||||
|         if (typeof callback == 'function') { | ||||
|             const files = request.files; | ||||
|             if (files && files.length > 0) { | ||||
|                 request.setForceRequestType(RequestType.POST_FILE); | ||||
|             } | ||||
|             return await executeRequest(this, request, token, callback, options); | ||||
|         } else { | ||||
|             return this.executeTokenSync(request, token); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 发送同步请求 | ||||
|      * @param request 请求类 | ||||
|      * @param token token | ||||
|      * @param options 自定义参数,如headers | ||||
|      */ | ||||
|     executeTokenSync(request, token, options) { | ||||
|         return new Promise((resolve) => { | ||||
|             const _ = this.executeToken(request, token, res => { | ||||
|                 resolve(res); | ||||
|             }, options); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| }; | ||||
| @ -0,0 +1,6 @@ | ||||
| exports.RequestType = { | ||||
|     GET: 'GET', | ||||
|     POST_FORM: 'POST_FORM', | ||||
|     POST_JSON: 'POST_JSON', | ||||
|     POST_FILE: 'POST_FILE' | ||||
| }; | ||||
| @ -0,0 +1,88 @@ | ||||
| const {KJUR, hextob64} = require('jsrsasign'); | ||||
| 
 | ||||
| const HashMap = { | ||||
|     SHA256withRSA: 'SHA256withRSA', | ||||
|     SHA1withRSA: 'SHA1withRSA' | ||||
| }; | ||||
| 
 | ||||
| const PEM_BEGIN = '-----BEGIN PRIVATE KEY-----\n'; | ||||
| const PEM_END = '\n-----END PRIVATE KEY-----'; | ||||
| 
 | ||||
| /** | ||||
|  * rsa签名参考:https://www.jianshu.com/p/145eab95322c
 | ||||
|  */ | ||||
| exports.SignUtil = { | ||||
|     /** | ||||
|      * 创建签名 | ||||
|      * @param params 请求参数 | ||||
|      * @param privateKey 私钥,PKCS8 | ||||
|      * @param signType 签名类型,RSA,RSA2 | ||||
|      * @returns 返回签名内容 | ||||
|      */ | ||||
|     createSign(params, privateKey, signType) { | ||||
|         const content = this.getSignContent(params); | ||||
|         return this.sign(content, privateKey, signType); | ||||
|     }, | ||||
|     sign: function (content, privateKey, signType) { | ||||
|         if (signType.toUpperCase() === 'RSA') { | ||||
|             return this.rsaSign(content, privateKey, HashMap.SHA1withRSA); | ||||
|         } else if (signType.toUpperCase() === 'RSA2') { | ||||
|             return this.rsaSign(content, privateKey, HashMap.SHA256withRSA); | ||||
|         } else { | ||||
|             throw 'signType错误'; | ||||
|         } | ||||
|     }, | ||||
|     /** | ||||
|      * rsa签名 | ||||
|      * @param content 签名内容 | ||||
|      * @param privateKey 私钥 | ||||
|      * @param hash hash算法,SHA256withRSA,SHA1withRSA | ||||
|      * @returns 返回签名字符串,base64 | ||||
|      */ | ||||
|     rsaSign: function (content, privateKey, hash) { | ||||
|         privateKey = this._formatKey(privateKey); | ||||
|         // 创建 Signature 对象
 | ||||
|         const signature = new KJUR.crypto.Signature({ | ||||
|             alg: hash, | ||||
|             //!这里指定 私钥 pem!
 | ||||
|             prvkeypem: privateKey | ||||
|         }); | ||||
|         signature.updateString(content); | ||||
|         const signData = signature.sign(); | ||||
|         // 将内容转成base64
 | ||||
|         return hextob64(signData); | ||||
|     }, | ||||
|     _formatKey: function (key) { | ||||
|         if (!key.startsWith(PEM_BEGIN)) { | ||||
|             key = PEM_BEGIN + key; | ||||
|         } | ||||
|         if (!key.endsWith(PEM_END)) { | ||||
|             key = key + PEM_END; | ||||
|         } | ||||
|         return key; | ||||
|     }, | ||||
|     /** | ||||
|      * 获取签名内容 | ||||
|      * @param params 请求参数 | ||||
|      * @returns {string} | ||||
|      */ | ||||
|     getSignContent: function (params) { | ||||
|         const paramNames = []; | ||||
|         for (const key in params) { | ||||
|             paramNames.push(key); | ||||
|         } | ||||
| 
 | ||||
|         paramNames.sort(); | ||||
| 
 | ||||
|         const paramNameValue = []; | ||||
| 
 | ||||
|         for (let i = 0, len = paramNames.length; i < len; i++) { | ||||
|             const paramName = paramNames[i]; | ||||
|             const val = params[paramName]; | ||||
|             if (paramName && val) { | ||||
|                 paramNameValue.push(`${paramName}=${val}`); | ||||
|             } | ||||
|         } | ||||
|         return paramNameValue.join('&'); | ||||
|     } | ||||
| }; | ||||
| @ -0,0 +1,21 @@ | ||||
| { | ||||
|   "name": "sdk-nodejs", | ||||
|   "version": "1.0.0", | ||||
|   "description": "", | ||||
|   "main": "index.js", | ||||
|   "scripts": { | ||||
|     "test": "echo \"Error: no test specified\" && exit 1" | ||||
|   }, | ||||
|   "keywords": [], | ||||
|   "author": "", | ||||
|   "license": "ISC", | ||||
|   "dependencies": { | ||||
|     "axios": "^0.21.1", | ||||
|     "form-data": "^4.0.0", | ||||
|     "isarray": "^2.0.5", | ||||
|     "isobject": "^4.0.0", | ||||
|     "jsrsasign": "^8.0.19", | ||||
|     "moment": "^2.27.0", | ||||
|     "qs": "^6.10.1" | ||||
|   } | ||||
| } | ||||
| @ -0,0 +1,78 @@ | ||||
| const isArray = require('isarray'); | ||||
| /** | ||||
|  * 请求类父类 | ||||
|  */ | ||||
| exports.BaseRequest = class BaseRequest { | ||||
|     constructor() { | ||||
|         this.bizModel = {}; | ||||
| 
 | ||||
|         this.files = undefined; | ||||
| 
 | ||||
|         // 用于文件上传时强制转换成POST_FILE请求
 | ||||
|         this.__forceRequestType__ = undefined; | ||||
|     } | ||||
| 
 | ||||
|     setBizModel(biz = {}) { | ||||
|         this.bizModel = biz; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     setFiles(files) { | ||||
|         this.files = files; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     addFile({name, path}) { | ||||
|         if (name && path) { | ||||
|             if (!isArray(this.files)) { | ||||
|                 this.files = []; | ||||
|             } | ||||
|             this.files.push({name, path}); | ||||
|         } | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 返回接口名称 | ||||
|      */ | ||||
|     getMethod() { | ||||
|         throw `未实现BaseRequest类getMethod()方法`; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 返回版本号 | ||||
|      */ | ||||
|     getVersion() { | ||||
|         throw '未实现BaseRequest类getVersion()方法'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 返回请求类型,使用RequestType.js | ||||
|      */ | ||||
|     getRequestType() { | ||||
|         throw '未实现BaseRequest类getRequestType()方法'; | ||||
|     } | ||||
| 
 | ||||
|     setForceRequestType(type) { | ||||
|         this.__forceRequestType__ = type; | ||||
|         return this; | ||||
|     } | ||||
| 
 | ||||
|     getRealRequestType() { | ||||
|         return this.__forceRequestType__ || this.getRequestType(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 解析返回结果,子类可以覆盖实现 | ||||
|      * @param responseData 服务器返回内容 | ||||
|      * @returns 返回结果 | ||||
|      */ | ||||
|     parseResponse(responseData) { | ||||
|         let data = responseData['error_response']; | ||||
|         if (!data) { | ||||
|             const dataNodeName = this.getMethod().replace(/\./g, '_') + '_response'; | ||||
|             data = responseData[dataNodeName]; | ||||
|         } | ||||
|         return data; | ||||
|     } | ||||
| }; | ||||
					Loading…
					
					
				
		Reference in new issue