diff --git a/sop-sdk/sdk-nodejs/aa.txt b/sop-sdk/sdk-nodejs/aa.txt deleted file mode 100644 index 524527cb..00000000 --- a/sop-sdk/sdk-nodejs/aa.txt +++ /dev/null @@ -1 +0,0 @@ -hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/bb.txt b/sop-sdk/sdk-nodejs/bb.txt deleted file mode 100644 index 85f8e329..00000000 --- a/sop-sdk/sdk-nodejs/bb.txt +++ /dev/null @@ -1 +0,0 @@ -文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/common/BaseRequest.js b/sop-sdk/sdk-nodejs/common/BaseRequest.js new file mode 100644 index 00000000..255f8870 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/BaseRequest.js @@ -0,0 +1,93 @@ +const isArray = require('isarray'); +/** + * 请求类父类 + */ +module.exports = class BaseRequest { + constructor() { + this.bizModel = {}; + + this.files = undefined; + + // 用于文件上传时强制转换成POST_FILE请求 + this.__forceRequestType__ = undefined; + + this.checkOverride(); + } + + /** + * 校验子类是否已重写相关方法 + * */ + checkOverride() { + try { + this.getMethod(); + this.getVersion(); + this.getRequestType(); + } catch (error) { + throw error; + } + } + + 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; + } +}; diff --git a/sop-sdk/sdk-nodejs/common/Class.js b/sop-sdk/sdk-nodejs/common/Class.js deleted file mode 100644 index 6068fc48..00000000 --- a/sop-sdk/sdk-nodejs/common/Class.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * 面相对象辅助类,可实现类的创建,继承,方法重写 - * -
- //------------------------- - // JS类的创建,继承 - //------------------------- - - // 例子1:------------------------- - // 创建一个父类 - var Person = Class.create({ - // 构造函数 - init:function(option){ - this.name = option.name; - } - ,getName:function() { - return this.name; - } - }); - - // 声明类实例 - var Jim = new Person({name:'Jim'}); - console.log('Jim name:' + Jim.getName()) - - //例子2:------------------------- - - // 创建一个类,继承Person类,并重写getName - var Man = Class.create({ - init:function(option) { - this._super(option);// 调用父类构造函数 - this.age = option.age; - } - // 重写父类方法 - ,getName:function() { - // 调用父类的getName() - var name = this._super(); - return '我重写了getName方法:{'+name+'}'; - } - },Person); - - var man = new Man({name:'Tom',age:22}); - console.log('man name:' + man.getName()) - - console.log('Jim instanceof Person: ' + (Jim instanceof Person)); - console.log('man instanceof Person: ' + (man instanceof Person)); -- * - */ -exports.Class = (function () { - // ------Class Creation------ - var initializing = false, - fnTest = /xyz/.test(function () { - xyz; - }) ? /\b_super\b/ : /.*/; - - // The base Class implementation (does nothing) - this.Class = function () { - }; - - // Create a new Class that inherits from this class - Class.extend = function (prop) { - var _super = this.prototype; - - // Instantiate a base class (but only create the instance, - // don't run the init constructor) - initializing = true; - var prototype = new this(); - initializing = false; - - // Copy the properties over onto the new prototype - for (var name in prop) { - // Check if we're overwriting an existing function - prototype[name] = typeof prop[name] == 'function' && typeof _super[name] == 'function' && fnTest.test(prop[name]) ? (function (name, fn) { - return function () { - var tmp = this._super; - - // Add a new ._super() method that is the same method - // but on the super-class - this._super = _super[name]; - - // The method only need to be bound temporarily, so we - // remove it when we're done executing - var ret = fn.apply(this, arguments); - this._super = tmp; - - return ret; - }; - })(name, prop[name]) : prop[name]; - } - - // The dummy class constructor - function Class() { - // All construction is actually done in the init method - if (!initializing && this.init) this.init.apply(this, arguments); - } - - // Populate our constructed prototype object - Class.prototype = prototype; - - // Enforce the constructor to be what we expect - Class.prototype.constructor = Class; - - // And make this class extendable - Class.extend = arguments.callee; - - return Class; - };// ------Class Creation end------ - - - return { - /** - * 创建一个类 - * @param option 类方法,json数据 - * @param parentClass 父类 - */ - create: function (option, parentClass) { - if (!parentClass) { - parentClass = Class; - } - return parentClass.extend(option); - } - }; - -})(); diff --git a/sop-sdk/sdk-nodejs/common/OpenClient.js b/sop-sdk/sdk-nodejs/common/OpenClient.js index 2397021c..5c79178a 100644 --- a/sop-sdk/sdk-nodejs/common/OpenClient.js +++ b/sop-sdk/sdk-nodejs/common/OpenClient.js @@ -1,184 +1,213 @@ -const needle = require('needle'); +const axios = require('axios'); const moment = require('moment'); +const qs = require('qs'); -const {Class} = require('./Class'); -const {RequestType} = require('./RequestType'); -const {SignUtil} = require('./SignUtil'); -const {BaseRequest} = require('../request/BaseRequest'); +const RequestType = require('./RequestType'); +const SignUtil = require('./SignUtil'); +const BaseRequest = require('./BaseRequest'); + +const IS_RUN_IN_BROWSER = typeof window !== 'undefined' && this === window; const HEADERS = {'Accept-Encoding': 'identity'}; -const getHeaders = function (headers) { - if (!headers) { - return HEADERS; +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, customOptions = {}) => { + const params = buildParams(instance, request, token); + const {url} = instance; + let {headers} = customOptions; + const config = { + url, + method: 'POST', + params: undefined, + data: undefined + }; + headers = getHeaders(headers); + const requestType = request.getRealRequestType(); + switch (requestType) { + case RequestType.GET: { + config.method = 'GET'; + config.params = params; + } + break; + case RequestType.POST_FORM: { + headers = Object.assign(headers, { + 'Content-Type': 'application/x-www-form-urlencoded' + }); + config.data = qs.stringify(params); + } + break; + case RequestType.POST_JSON: { + config.data = params; + } + break; + case RequestType.POST_FILE: { + let formData; + if (IS_RUN_IN_BROWSER) { + formData = new window.FormData(); + (request.files || []).forEach(({name, path}) => { + formData.append(name, path, { + contentType: 'application/octet-stream' + }); + }); + } else { + const fs = require('fs'); + const fd = require('form-data'); + formData = new fd(); + (request.files || []).forEach(({name, path}) => { + formData.append(name, fs.createReadStream(path), { + contentType: 'application/octet-stream' + }); + }); + headers = Object.assign(headers, formData.getHeaders()); + } + Object.keys(params).forEach(key => { + const value = params[key]; + if (!(typeof key === 'undefined' || typeof value === 'undefined')) { + formData.append(key, params[key]); + } + }); + config.data = formData; + } + break; + default: { + callback(parseResponse(new Error('request.getRequestType()类型不正确'), undefined, request)); + return; + } } - for (const key in HEADERS) { - headers[key] = HEADERS[key]; + try { + config['headers'] = headers; + const response = await axios.request(config); + callback(parseResponse(undefined, response, request)); + } catch (error) { + callback(parseResponse(error, undefined, request)); } - return headers; }; -const OpenClient = Class.create({ +module.exports = class OpenClient { /** * 初始化客户端 * @param appId 应用ID * @param privateKey 应用私钥,2048位,PKCS8 * @param url 请求url */ - init: function (appId, privateKey, url) { - this.appId = appId; - this.privateKey = privateKey; - this.url = url; - }, + constructor(appId, privateKey, url) { + this.appId = appId || ''; + this.privateKey = privateKey || ''; + this.url = url || ''; + } + + setAppId(appId) { + this.appId = appId || ''; + return this; + } + + setPrivateKey(privateKey) { + this.privateKey = privateKey || ''; + return this; + } + + setUrl(url) { + this.url = url || ''; + return this; + } + /** * 发送请求 * @param request 请求类 * @param callback 回调函数,参数json(undefined则使用executeSync) + * @param options 自定义参数,如headers */ - execute: function (request, callback) { + execute(request, callback, options) { if (typeof callback == 'function') { - this.executeToken(request, null, callback); + return this.executeToken(request, null, callback, options); } else { - return this.executeSync(request); + return this.executeSync(request, options); } - }, + } + /** * 发送同步请求 * @param request 请求类 + * @param options 自定义参数,如headers * */ - executeSync: function (request) { - return new Promise((resolve) => { - this.execute(request, res => { + executeSync(request, options) { + return new Promise(async (resolve) => { + await this.execute(request, res => { resolve(res); - }); + }, options); }); - }, + } + /** * 发送请求 * @param request 请求类 * @param token token * @param callback 回调函数,参数json(undefined则使用executeTokenSync) + * @param options 自定义参数,如headers */ - executeToken: function (request, token, callback) { + async executeToken(request, token, callback, options) { if (!(request instanceof BaseRequest)) { throw 'request类未继承BaseRequest'; } if (typeof callback == 'function') { - const requestType = request.getRequestType(); - if (request.files) { - this._postFile(request, callback); - } else { - switch (requestType) { - case RequestType.GET: - this._get(request, callback); - break; - case RequestType.POST_FORM: - this._postForm(request, callback); - break; - case RequestType.POST_JSON: - this._postJson(request, callback); - break; - case RequestType.POST_FILE: - this._postFile(request, callback); - break; - default: { - throw 'request.getRequestType()类型不正确'; - } - } + 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: function (request, token) { - return new Promise((resolve) => { - this.executeToken(request, token, res => { + executeTokenSync(request, token, options) { + return new Promise(async (resolve) => { + await this.executeToken(request, token, res => { resolve(res); - }); - }); - }, - _get: function (request, callback) { - const allParams = this._buildParams(request); - const that = this; - // needle.request(method, url, data[, options][, callback]) - needle.request('GET', this.url, allParams, { - headers: getHeaders() - }, function (error, response) { - callback(that._parseResponse(error, response, request)); - }); - }, - _postForm: function (request, callback) { - const allParams = this._buildParams(request); - const that = this; - needle.request('POST', this.url, allParams, { - headers: getHeaders({ - 'Content-Type': 'application/x-www-form-urlencoded' - }) - }, function (error, response) { - callback(that._parseResponse(error, response, request)); - }); - }, - _postJson: function (request, callback) { - const allParams = this._buildParams(request); - const that = this; - needle.request('POST', this.url, allParams, { - headers: getHeaders(), json: true - }, function (error, response) { - callback(that._parseResponse(error, response, request)); - }); - }, - _postFile: function (request, callback) { - const allParams = this._buildParams(request); - const files = request.files; - files.forEach(row => { - // 设置成{ file: row.path, content_type: 'application/octet-stream' }格式 - // needle会认为是上传文件 - allParams[row.name] = {file: row.path, content_type: 'application/octet-stream'}; - }); - const that = this; - needle.request('POST', this.url, allParams, { - headers: getHeaders(), multipart: true - }, function (error, response) { - callback(that._parseResponse(error, response, request)); + }, options); }); - }, - _parseResponse: function (error, response, request) { - if (!error && response.statusCode === 200) { - return request.parseResponse(response.body); - } else { - // throw '请求异常:' + error - return { // 重新封装请求异常回调,以防中断 - msg: '请求异常', - code: '502', - sub_msg: `${error}`, - sub_code: 'isv.invalid-server' - }; - } - }, - _buildParams: function (request, token) { - const allParams = { - 'app_id': this.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; - } - // 创建签名 - const sign = SignUtil.createSign(allParams, this.privateKey, 'RSA2'); - allParams.sign = sign; - return allParams; } -}); -module.exports = OpenClient; +}; diff --git a/sop-sdk/sdk-nodejs/common/RequestType.js b/sop-sdk/sdk-nodejs/common/RequestType.js index 717d99f5..f745548d 100644 --- a/sop-sdk/sdk-nodejs/common/RequestType.js +++ b/sop-sdk/sdk-nodejs/common/RequestType.js @@ -1,4 +1,4 @@ -exports.RequestType = { +module.exports = { GET: 'GET', POST_FORM: 'POST_FORM', POST_JSON: 'POST_JSON', diff --git a/sop-sdk/sdk-nodejs/common/SignUtil.js b/sop-sdk/sdk-nodejs/common/SignUtil.js index 4c141676..98e2ceb6 100644 --- a/sop-sdk/sdk-nodejs/common/SignUtil.js +++ b/sop-sdk/sdk-nodejs/common/SignUtil.js @@ -11,7 +11,7 @@ const PEM_END = '\n-----END PRIVATE KEY-----'; /** * rsa签名参考:https://www.jianshu.com/p/145eab95322c */ -exports.SignUtil = { +module.exports = { /** * 创建签名 * @param params 请求参数 @@ -68,22 +68,21 @@ exports.SignUtil = { */ getSignContent: function (params) { const paramNames = []; - // 获取对象中的Key - paramNames.push(...Object.keys(params || {}) - // 过滤无效的KeyValue - .filter(paramName => { - // 参数名不为undefined且参数值不为undefined - return !(typeof paramName === undefined || typeof params[paramName] === undefined); - })); + for (const key in params) { + paramNames.push(key); + } paramNames.sort(); - // 合成签名字符串 - const paramNameValue = paramNames.map(paramName => { - const val = params[paramName]; - return `${paramName}=${val}`; - }); + 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('&'); } }; diff --git a/sop-sdk/sdk-nodejs/main.js b/sop-sdk/sdk-nodejs/main.js index d4dcd3e5..766285c4 100644 --- a/sop-sdk/sdk-nodejs/main.js +++ b/sop-sdk/sdk-nodejs/main.js @@ -1,6 +1,6 @@ const OpenClient = require('./common/OpenClient'); -const {StoryGetRequest} = require('./request/StoryGetRequest'); +const StoryGetRequest = require('./request/StoryGetRequest'); // 应用ID const appId = '2019032617262200001'; @@ -10,61 +10,35 @@ const privateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqW const url = 'http://localhost:8081'; // 创建客户端 -const openClient = new OpenClient(appId, privateKey, url); +const openClient = new OpenClient().setUrl(url).setAppId(appId).setPrivateKey(privateKey); -function test() { +(async () => { // 创建请求 const request = new StoryGetRequest(); // 设置业务参数 - request.bizModel = { + const bizModel = { id: 111, name: 'jim' }; + request.setBizModel(bizModel); // 添加上传文件 - // request.files = [ + // 批量添加 + // const files = [ // // name: 表单名称,path:文件全路径 - // {name: 'file1', path: `${__dirname}/aa.txt`}, - // {name: 'file2', path: `${__dirname}/bb.txt`} - // ] - - openClient.execute(request, data => { - console.log('异步请求'); - // 成功 - if (!data.sub_code) { - console.log('成功', data); - } else { - console.error('失败', data); - } - }); - - // 使用Promise进行封装 - openClient.executeSync(request).then(data => { - console.log('同步请求-Promise'); - // 成功 - if (!data.sub_code) { - console.log('成功', data); - } else { - console.error('失败', data); - } - }); - - // 使用Async/Await进行封装 - async function syncRequest() { - const data = await openClient.execute(request); - console.log('同步请求-Async/Await'); - // 成功 - if (!data.sub_code) { - console.log('成功', data); - } else { - console.error('失败', data); - } + // {name: 'file1', path: `${__dirname}/main.js`}, + // {name: 'file2', path: `${__dirname}/readme.md`} + // ]; + // request.setFiles(files); + // // 单个添加 + // request.addFile('file3', `${__dirname}/package.json`); + + const data = await openClient.executeSync(request); + // 成功 + if (!data.sub_code) { + console.log('成功', data); + } else { + console.error('失败', data); } - - syncRequest(); -} - - -test(); - +})(); diff --git a/sop-sdk/sdk-nodejs/package.json b/sop-sdk/sdk-nodejs/package.json index d1611bd4..db3bac8e 100644 --- a/sop-sdk/sdk-nodejs/package.json +++ b/sop-sdk/sdk-nodejs/package.json @@ -1,6 +1,6 @@ { "name": "sdk-nodejs", - "version": "1.0.0", + "version": "1.1.0", "description": "", "main": "index.js", "scripts": { @@ -10,8 +10,11 @@ "author": "", "license": "ISC", "dependencies": { + "axios": "^0.21.1", + "form-data": "^4.0.0", + "isarray": "^2.0.5", "jsrsasign": "^8.0.19", "moment": "^2.27.0", - "needle": "^2.5.0" + "qs": "^6.10.1" } } diff --git a/sop-sdk/sdk-nodejs/request/BaseRequest.js b/sop-sdk/sdk-nodejs/request/BaseRequest.js deleted file mode 100644 index 53c36f2f..00000000 --- a/sop-sdk/sdk-nodejs/request/BaseRequest.js +++ /dev/null @@ -1,48 +0,0 @@ -const {Class} = require('../common/Class'); - -/** - * 请求类父类 - */ -exports.BaseRequest = Class.create({ - init: function () { - this.bizModel = {}; - /* - [ - {name: 'file1', path: 'd:/dd/1.txt'}, - {name: 'file2', path: 'd:/dd/2.txt'} - ] - */ - this.files = []; - }, - /** - * 返回接口名称 - */ - getMethod: function () { - throw `未实现BaseRequest类getMethod()方法`; - }, - /** - * 返回版本号 - */ - getVersion: function () { - throw '未实现BaseRequest类getVersion()方法'; - }, - /** - * 返回请求类型,使用RequestType.js - */ - getRequestType: function () { - throw '未实现BaseRequest类getRequestType()方法'; - }, - /** - * 解析返回结果,子类可以覆盖实现 - * @param responseData 服务器返回内容 - * @returns 返回结果 - */ - parseResponse: function (responseData) { - let data = responseData['error_response']; - if (!data) { - const dataNodeName = this.getMethod().replace(/\./g, '_') + '_response'; - data = responseData[dataNodeName]; - } - return data; - } -}); diff --git a/sop-sdk/sdk-nodejs/request/StoryGetRequest.js b/sop-sdk/sdk-nodejs/request/StoryGetRequest.js index ea5f51bb..243084a7 100644 --- a/sop-sdk/sdk-nodejs/request/StoryGetRequest.js +++ b/sop-sdk/sdk-nodejs/request/StoryGetRequest.js @@ -1,22 +1,19 @@ -const {Class} = require('../common/Class'); -const {RequestType} = require('../common/RequestType'); -const {BaseRequest} = require('./BaseRequest'); +const BaseRequest = require('../common/BaseRequest'); +const RequestType = require('../common/RequestType'); /** * 创建一个请求类,继承BaseRequest,重写三个函数 */ -const StoryGetRequest = Class.create({ - - getMethod: function () { +module.exports = class StoryGetRequest extends BaseRequest { + getMethod() { return 'story.get'; - }, - getVersion: function () { - return '1.0'; - }, - getRequestType: function () { - return RequestType.GET; } -}, BaseRequest); // 继承BaseRequest + getVersion() { + return '1.0'; + } -module.exports.StoryGetRequest = StoryGetRequest; + getRequestType() { + return RequestType.GET; + } +}; diff --git a/sop-sdk/sdk-nodejs/testClass.js b/sop-sdk/sdk-nodejs/testClass.js deleted file mode 100644 index 866fe62a..00000000 --- a/sop-sdk/sdk-nodejs/testClass.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * 演示JS面相对象,包括类的创建,继承,方法重写 - * 运行:node testClass.js - */ - -const {Class} = require('./common/Class'); - -function testClass() { - //------------------------- - // JS类的创建,继承 - //------------------------- - - // 例子1:------------------------- - // 创建一个父类 - var Person = Class.create({ - // 构造函数 - init: function (option) { - this.name = option.name; - } - , getName: function () { - return this.name; - } - }); - - // 声明类实例 - var Jim = new Person({name: 'Jim'}); - console.log('Jim name:' + Jim.getName()); - - //例子2:------------------------- - - // 创建一个类,继承Person类,并重写getName - var Man = Class.create({ - init: function (option) { - this._super(option);// 调用父类构造函数 - this.age = option.age; - } - // 重写父类方法 - , getName: function () { - // 调用父类的getName() - var name = this._super(); - return '我重写了getName方法:{' + name + '}'; - } - }, Person); - - var man = new Man({name: 'Tom', age: 22}); - console.log('man name:' + man.getName()); - - console.log('Jim instanceof Person: ' + (Jim instanceof Person)); - console.log('man instanceof Person: ' + (man instanceof Person)); -} - -testClass();