diff --git a/sop-sdk/sdk-nodejs/.gitignore b/sop-sdk/sdk-nodejs/.gitignore new file mode 100644 index 00000000..9ad28d23 --- /dev/null +++ b/sop-sdk/sdk-nodejs/.gitignore @@ -0,0 +1,16 @@ +.DS_Store +node_modules/ +dist/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +package-lock.json +tests/**/coverage/ + +# Editor directories and files +.idea +.vscode +*.suo +*.ntvs* +*.njsproj +*.sln diff --git a/sop-sdk/sdk-nodejs/aa.txt b/sop-sdk/sdk-nodejs/aa.txt new file mode 100644 index 00000000..524527cb --- /dev/null +++ b/sop-sdk/sdk-nodejs/aa.txt @@ -0,0 +1 @@ +hello你好123 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/bb.txt b/sop-sdk/sdk-nodejs/bb.txt new file mode 100644 index 00000000..85f8e329 --- /dev/null +++ b/sop-sdk/sdk-nodejs/bb.txt @@ -0,0 +1 @@ +文件bb的内容 \ No newline at end of file diff --git a/sop-sdk/sdk-nodejs/common/Class.js b/sop-sdk/sdk-nodejs/common/Class.js new file mode 100644 index 00000000..e066def1 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/Class.js @@ -0,0 +1,125 @@ +/** + * 面相对象辅助类,可实现类的创建,继承,方法重写 + * +
+ //------------------------- + // 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 new file mode 100644 index 00000000..1707ddf9 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/OpenClient.js @@ -0,0 +1,151 @@ +const needle = require('needle') +const moment = require('moment') + +const {Class} = require("./Class"); +const {RequestType} = require('./RequestType') +const {SignUtil} = require('./SignUtil') +const {BaseRequest} = require('../request/BaseRequest') + +const HEADERS = {'Accept-Encoding': 'identity'} + +const getHeaders = function (headers) { + if (!headers) { + return HEADERS + } + for (const key in HEADERS) { + headers[key] = HEADERS[key]; + } + return headers +} + +const OpenClient = Class.create({ + /** + * 初始化客户端 + * @param appId 应用ID + * @param privateKey 应用私钥,2048位,PKCS8 + * @param url 请求url + */ + init: function (appId, privateKey, url) { + this.appId = appId; + this.privateKey = privateKey; + this.url = url; + }, + /** + * 发送请求 + * @param request 请求类 + * @param callback 回调函数,参数json + */ + execute: function (request, callback) { + this.executeToken(request, null, callback) + }, + /** + * 发送请求 + * @param request 请求类 + * @param token token + * @param callback 回调函数,参数json + */ + executeToken: function (request, token, callback) { + if (!(request instanceof BaseRequest)) { + throw 'request类未继承BaseRequest' + } + 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()类型不正确' + } + } + } + }, + _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)) + }); + }, + _parseResponse: function (error, response, request) { + if (!error && response.statusCode === 200) { + return request.parseResponse(response.body) + } else { + throw '请求异常:' + error + } + }, + _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() + } + const bizModel = request.bizModel + + for (const key in bizModel) { + allParams[key] = bizModel[key] + } + + 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 new file mode 100644 index 00000000..e64db59d --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/RequestType.js @@ -0,0 +1,6 @@ +exports.RequestType = { + GET: 'GET', + POST_FORM: 'POST_FORM', + POST_JSON: 'POST_JSON', + POST_FILE: 'POST_FILE' +} diff --git a/sop-sdk/sdk-nodejs/common/SignUtil.js b/sop-sdk/sdk-nodejs/common/SignUtil.js new file mode 100644 index 00000000..03d757a6 --- /dev/null +++ b/sop-sdk/sdk-nodejs/common/SignUtil.js @@ -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('&') + } +} diff --git a/sop-sdk/sdk-nodejs/main.js b/sop-sdk/sdk-nodejs/main.js new file mode 100644 index 00000000..4745e990 --- /dev/null +++ b/sop-sdk/sdk-nodejs/main.js @@ -0,0 +1,43 @@ +const OpenClient = require('./common/OpenClient') + +const {StoryGetRequest} = require('./request/StoryGetRequest') + +// 应用ID +const appId = '2019032617262200001' +// 应用私钥,2048位,PKCS8 +const privateKey = 'MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCXJv1pQFqWNA/++OYEV7WYXwexZK/J8LY1OWlP9X0T6wHFOvxNKRvMkJ5544SbgsJpVcvRDPrcxmhPbi/sAhdO4x2PiPKIz9Yni2OtYCCeaiE056B+e1O2jXoLeXbfi9fPivJZkxH/tb4xfLkH3bA8ZAQnQsoXA0SguykMRZntF0TndUfvDrLqwhlR8r5iRdZLB6F8o8qXH6UPDfNEnf/K8wX5T4EB1b8x8QJ7Ua4GcIUqeUxGHdQpzNbJdaQvoi06lgccmL+PHzminkFYON7alj1CjDN833j7QMHdPtS9l7B67fOU/p2LAAkPMtoVBfxQt9aFj7B8rEhGCz02iJIBAgMBAAECggEARqOuIpY0v6WtJBfmR3lGIOOokLrhfJrGTLF8CiZMQha+SRJ7/wOLPlsH9SbjPlopyViTXCuYwbzn2tdABigkBHYXxpDV6CJZjzmRZ+FY3S/0POlTFElGojYUJ3CooWiVfyUMhdg5vSuOq0oCny53woFrf32zPHYGiKdvU5Djku1onbDU0Lw8w+5tguuEZ76kZ/lUcccGy5978FFmYpzY/65RHCpvLiLqYyWTtaNT1aQ/9pw4jX9HO9NfdJ9gYFK8r/2f36ZE4hxluAfeOXQfRC/WhPmiw/ReUhxPznG/WgKaa/OaRtAx3inbQ+JuCND7uuKeRe4osP2jLPHPP6AUwQKBgQDUNu3BkLoKaimjGOjCTAwtp71g1oo+k5/uEInAo7lyEwpV0EuUMwLA/HCqUgR4K9pyYV+Oyb8d6f0+Hz0BMD92I2pqlXrD7xV2WzDvyXM3s63NvorRooKcyfd9i6ccMjAyTR2qfLkxv0hlbBbsPHz4BbU63xhTJp3Ghi0/ey/1HQKBgQC2VsgqC6ykfSidZUNLmQZe3J0p/Qf9VLkfrQ+xaHapOs6AzDU2H2osuysqXTLJHsGfrwVaTs00ER2z8ljTJPBUtNtOLrwNRlvgdnzyVAKHfOgDBGwJgiwpeE9voB1oAV/mXqSaUWNnuwlOIhvQEBwekqNyWvhLqC7nCAIhj3yvNQKBgQCqYbeec56LAhWP903Zwcj9VvG7sESqXUhIkUqoOkuIBTWFFIm54QLTA1tJxDQGb98heoCIWf5x/A3xNI98RsqNBX5JON6qNWjb7/dobitti3t99v/ptDp9u8JTMC7penoryLKK0Ty3bkan95Kn9SC42YxaSghzqkt+uvfVQgiNGQKBgGxU6P2aDAt6VNwWosHSe+d2WWXt8IZBhO9d6dn0f7ORvcjmCqNKTNGgrkewMZEuVcliueJquR47IROdY8qmwqcBAN7Vg2K7r7CPlTKAWTRYMJxCT1Hi5gwJb+CZF3+IeYqsJk2NF2s0w5WJTE70k1BSvQsfIzAIDz2yE1oPHvwVAoGAA6e+xQkVH4fMEph55RJIZ5goI4Y76BSvt2N5OKZKd4HtaV+eIhM3SDsVYRLIm9ZquJHMiZQGyUGnsvrKL6AAVNK7eQZCRDk9KQz+0GKOGqku0nOZjUbAu6A2/vtXAaAuFSFx1rUQVVjFulLexkXR3KcztL1Qu2k5pB6Si0K/uwQ=' +// 接口url +const url = 'http://localhost:8081' + +// 创建客户端 +const openClient = new OpenClient(appId, privateKey, url) + +function test() { + // 创建请求 + const request = new StoryGetRequest() + + // 设置业务参数 + request.bizModel = { + name: 'jim' + } + + // 添加上传文件 + // request.files = [ + // // name: 表单名称,path:文件全路径 + // {name: 'file1', path: `${__dirname}/aa.txt`}, + // {name: 'file2', path: `${__dirname}/bb.txt`} + // ] + + openClient.execute(request, data => { + // 成功 + if (!data.sub_code) { + console.log('成功', data); + } else { + console.error('失败', data) + } + }) +} + + +test() + diff --git a/sop-sdk/sdk-nodejs/package.json b/sop-sdk/sdk-nodejs/package.json new file mode 100644 index 00000000..d1611bd4 --- /dev/null +++ b/sop-sdk/sdk-nodejs/package.json @@ -0,0 +1,17 @@ +{ + "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": { + "jsrsasign": "^8.0.19", + "moment": "^2.27.0", + "needle": "^2.5.0" + } +} diff --git a/sop-sdk/sdk-nodejs/readme.md b/sop-sdk/sdk-nodejs/readme.md new file mode 100644 index 00000000..79b852e5 --- /dev/null +++ b/sop-sdk/sdk-nodejs/readme.md @@ -0,0 +1,10 @@ +- 执行`npm install --registry=https://registry.npm.taobao.org` + +- 执行`node main.js`进行测试 + +## 封装步骤 + +1. 新建request类,继承BaseRequest,参考`StoryGetRequest.js` + +2. 调用接口,参考`main.js` + diff --git a/sop-sdk/sdk-nodejs/request/BaseRequest.js b/sop-sdk/sdk-nodejs/request/BaseRequest.js new file mode 100644 index 00000000..7720828b --- /dev/null +++ b/sop-sdk/sdk-nodejs/request/BaseRequest.js @@ -0,0 +1,48 @@ +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 new file mode 100644 index 00000000..fb66c2f6 --- /dev/null +++ b/sop-sdk/sdk-nodejs/request/StoryGetRequest.js @@ -0,0 +1,22 @@ +const {Class} = require("../common/Class"); +const {RequestType} = require("../common/RequestType"); +const {BaseRequest} = require('./BaseRequest') + +/** + * 创建一个请求类,继承BaseRequest,重写三个函数 + */ +const StoryGetRequest = Class.create({ + + getMethod: function () { + return "story.get" + }, + getVersion: function () { + return "1.0" + }, + getRequestType: function () { + return RequestType.GET + } + +}, BaseRequest) // 继承BaseRequest + +module.exports.StoryGetRequest = StoryGetRequest diff --git a/sop-sdk/sdk-nodejs/testClass.js b/sop-sdk/sdk-nodejs/testClass.js new file mode 100644 index 00000000..0e2fe2a8 --- /dev/null +++ b/sop-sdk/sdk-nodejs/testClass.js @@ -0,0 +1,52 @@ +/** + * 演示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()