(function () { Vue.component('vue-item', { props: ['jsondata', 'theme'], template: '#item-template' }) Vue.component('vue-outer', { props: ['jsondata', 'isend', 'path', 'theme'], template: '#outer-template' }) Vue.component('vue-expand', { props: [], template: '#expand-template' }) Vue.component('vue-val', { props: ['field', 'val', 'isend', 'path', 'theme'], template: '#val-template' }) Vue.use({ install: function (Vue, options) { // 判断数据类型 Vue.prototype.getTyp = function (val) { return toString.call(val).split(']')[0].split(' ')[1] } // 判断是否是对象或者数组,以对下级进行渲染 Vue.prototype.isObjectArr = function (val) { return ['Object', 'Array'].indexOf(this.getTyp(val)) > -1 } // 折叠 Vue.prototype.fold = function ($event) { var target = Vue.prototype.expandTarget($event) target.siblings('svg').show() target.hide().parent().siblings('.expand-view').hide() target.parent().siblings('.fold-view').show() } // 展开 Vue.prototype.expand = function ($event) { var target = Vue.prototype.expandTarget($event) target.siblings('svg').show() target.hide().parent().siblings('.expand-view').show() target.parent().siblings('.fold-view').hide() } //获取展开折叠的target Vue.prototype.expandTarget = function ($event) { switch($event.target.tagName.toLowerCase()) { case 'use': return $($event.target).parent() case 'label': return $($event.target).closest('.fold-view').siblings('.expand-wraper').find('.icon-square-plus').first() default: return $($event.target) } } // 格式化值 Vue.prototype.formatVal = function (val) { switch(Vue.prototype.getTyp(val)) { case 'String': return '"' + val + '"' case 'Null': return 'null' default: return val } } // 判断值是否是链接 Vue.prototype.isaLink = function (val) { return /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(val) } // 计算对象的长度 Vue.prototype.objLength = function (obj) { return Object.keys(obj).length } /**渲染 JSON key:value 项 * @author TommyLemon * @param val * @param key * @return {boolean} */ Vue.prototype.onRenderJSONItem = function (val, key, path) { if (isSingle || key == null) { return true } if (key == '_$_this_$_') { // return true return false } var method = App.getMethod(); var isRestful = ! JSONObject.isAPIJSONPath(method); try { if (val instanceof Array) { if (val[0] instanceof Object && (val[0] instanceof Array == false)) { // && JSONObject.isArrayKey(key, null, isRestful)) { // alert('onRenderJSONItem key = ' + key + '; val = ' + JSON.stringify(val)) var ckey = key.substring(0, key.lastIndexOf('[]')); var aliaIndex = ckey.indexOf(':'); var objName = aliaIndex < 0 ? ckey : ckey.substring(0, aliaIndex); var firstIndex = objName.indexOf('-'); var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); for (var i = 0; i < val.length; i++) { var cPath = (StringUtil.isEmpty(path, false) ? '' : path + '/') + key; if (JSONObject.isTableKey(firstKey, val, isRestful)) { // var newVal = JSON.parse(JSON.stringify(val[i])) var newVal = {} for (var k in val[i]) { newVal[k] = val[i][k] //提升性能 delete val[i][k] } val[i]._$_this_$_ = JSON.stringify({ path: cPath + '/' + i, table: firstKey }) for (var k in newVal) { val[i][k] = newVal[k] } } else { this.onRenderJSONItem(val[i], '' + i, cPath); } // this.$children[i]._$_this_$_ = key // alert('this.$children[i]._$_this_$_ = ' + this.$children[i]._$_this_$_) } } } else if (val instanceof Object) { var aliaIndex = key.indexOf(':'); var objName = aliaIndex < 0 ? key : key.substring(0, aliaIndex); // var newVal = JSON.parse(JSON.stringify(val)) var newVal = {} for (var k in val) { newVal[k] = val[k] //提升性能 delete val[k] } val._$_this_$_ = JSON.stringify({ path: (StringUtil.isEmpty(path, false) ? '' : path + '/') + key, table: JSONObject.isTableKey(objName, val, isRestful) ? objName : null }) for (var k in newVal) { val[k] = newVal[k] } // val = Object.assign({ _$_this_$_: objName }, val) //解决多显示一个逗号 , // this._$_this_$_ = key TODO 不影响 JSON 的方式,直接在组件读写属性 // alert('this._$_this_$_ = ' + this._$_this_$_) } } catch (e) { alert('onRenderJSONItem try { ... } catch (e) {\n' + e.message) } return true } /**显示 Response JSON 的注释 * @author TommyLemon * @param val * @param key * @param $event */ Vue.prototype.setResponseHint = function (val, key, $event) { console.log('setResponseHint') this.$refs.responseKey.setAttribute('data-hint', isSingle ? '' : this.getResponseHint(val, key, $event)); } /**获取 Response JSON 的注释 * 方案一: * 拿到父组件的 key,逐层向下传递 * 问题:拿不到爷爷组件 "Comment[]": [ { "id": 1, "content": "content1" }, { "id": 2 }... ] * * 方案二: * 改写 jsonon 的 refKey 为 key0/key1/.../refKey * 问题:遍历,改 key;容易和特殊情况下返回的同样格式的字段冲突 * * 方案三: * 改写 jsonon 的结构,val 里加 .path 或 $.path 之类的隐藏字段 * 问题:遍历,改 key;容易和特殊情况下返回的同样格式的字段冲突 * * @author TommyLemon * @param val * @param key * @param $event */ Vue.prototype.getResponseHint = function (val, key, $event) { // alert('setResponseHint key = ' + key + '; val = ' + JSON.stringify(val)) var s = '' try { var standardObj = null; try { var currentItem = App.isTestCaseShow ? App.remotes[App.currentDocIndex] : App.currentRemoteItem; standardObj = JSON.parse(((currentItem || {}).TestRecord || {}).standard); } catch (e3) { log(e3) } var path = null var table = null var column = null var method = App.isTestCaseShow ? ((App.currentRemoteItem || {}).Document || {}).url : App.getMethod(); var isRestful = ! JSONObject.isAPIJSONPath(method); if (val instanceof Object && (val instanceof Array == false)) { var parent = $event.currentTarget.parentElement.parentElement var valString = parent.textContent // alert('valString = ' + valString) var i = valString.indexOf('"_$_this_$_": "') if (i >= 0) { valString = valString.substring(i + '"_$_this_$_": "'.length) i = valString.indexOf('}"') if (i >= 0) { valString = valString.substring(0, i + 1) // alert('valString = ' + valString) var _$_this_$_ = JSON.parse(valString) || {} path = _$_this_$_.path table = _$_this_$_.table } var aliaIndex = key == null ? -1 : key.indexOf(':'); var objName = aliaIndex < 0 ? key : key.substring(0, aliaIndex); if (JSONObject.isTableKey(objName, val, isRestful)) { table = objName } else if (JSONObject.isTableKey(table, val, isRestful)) { column = key } // alert('path = ' + path + '; table = ' + table + '; column = ' + column) } } else { var parent = $event.currentTarget.parentElement.parentElement var valString = parent.textContent // alert('valString = ' + valString) var i = valString.indexOf('"_$_this_$_": "') if (i >= 0) { valString = valString.substring(i + '"_$_this_$_": "'.length) i = valString.indexOf('}"') if (i >= 0) { valString = valString.substring(0, i + 1) // alert('valString = ' + valString) var _$_this_$_ = JSON.parse(valString) || {} path = _$_this_$_.path table = _$_this_$_.table } } if (val instanceof Array && JSONObject.isArrayKey(key, val, isRestful)) { var key2 = key == null ? null : key.substring(0, key.lastIndexOf('[]')); var aliaIndex = key2 == null ? -1 : key2.indexOf(':'); var objName = aliaIndex < 0 ? key2 : key2.substring(0, aliaIndex); var firstIndex = objName == null ? -1 : objName.indexOf('-'); var firstKey = firstIndex < 0 ? objName : objName.substring(0, firstIndex); // alert('key = ' + key + '; firstKey = ' + firstKey + '; firstIndex = ' + firstIndex) if (JSONObject.isTableKey(firstKey, null, isRestful)) { table = firstKey var s0 = ''; if (firstIndex > 0) { objName = objName.substring(firstIndex + 1); firstIndex = objName.indexOf('-'); column = firstIndex < 0 ? objName : objName.substring(0, firstIndex) var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + key; var c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, column, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); // this.getResponseHint({}, table, $event s0 = column + (StringUtil.isEmpty(c, true) ? '' : ': ' + c) } var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + (StringUtil.isEmpty(column) ? key : column); var c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, isRestful ? key : null, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); s = (StringUtil.isEmpty(path) ? '' : path + '/') + key + ' 中 ' + ( StringUtil.isEmpty(c, true) ? '' : table + ': ' + c + ((StringUtil.isEmpty(s0, true) ? '' : ' - ' + s0) ) ); return s; } //导致 key[] 的 hint 显示为 key[]key[] else { // s = (StringUtil.isEmpty(path) ? '' : path + '/') + key // } } else { if (isRestful || JSONObject.isTableKey(table)) { column = key } // alert('path = ' + path + '; table = ' + table + '; column = ' + column) } } // alert('setResponseHint table = ' + table + '; column = ' + column) var pathUri = (StringUtil.isEmpty(path) ? '' : path + '/') + key; var c = CodeUtil.getCommentFromDoc(docObj == null ? null : docObj['[]'], table, isRestful ? key : column, method, App.database, App.language, true, false, pathUri.split('/'), isRestful, val, true, standardObj); s += pathUri + (StringUtil.isEmpty(c, true) ? '' : ': ' + c) } catch (e) { s += '\n' + e.message } return s; } } }) var DEBUG = false var initJson = {} // 主题 [key, String, Number, Boolean, Null, link-link, link-hover] var themes = [ ['#92278f', '#3ab54a', '#25aae2', '#f3934e', '#f34e5c', '#717171'], ['rgb(19, 158, 170)', '#cf9f19', '#ec4040', '#7cc500', 'rgb(211, 118, 126)', 'rgb(15, 189, 170)'], ['#886', '#25aae2', '#e60fc2', '#f43041', 'rgb(180, 83, 244)', 'rgb(148, 164, 13)'], ['rgb(97, 97, 102)', '#cf4c74', '#20a0d5', '#cd1bc4', '#c1b8b9', 'rgb(25, 8, 174)'] ] // APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< function getRequestFromURL(url_) { var url = url_ || window.location.search; var index = url == null ? -1 : url.indexOf("?") if(index < 0) { //判断是否有参数 return null; } var theRequest = null; var str = url.substring(index + 1); //从第一个字符开始 因为第0个是?号 获取所有除问号的所有符串 var arr = str.split("&"); //截除“&”生成一个数组 var len = arr == null ? 0 : arr.length; for(var i = 0; i < len; i++) { var part = arr[i]; var ind = part == null ? -1 : part.indexOf("="); if (ind <= 0) { continue } if (theRequest == null) { theRequest = {}; } var v = decodeURIComponent(part.substring(ind+1)); try { v = JSON.parse(v) } catch (e) {} theRequest[part.substring(0, ind)] = v; } return theRequest; } var PLATFORM_POSTMAN = 'POSTMAN' var PLATFORM_SWAGGER = 'SWAGGER' var PLATFORM_YAPI = 'YAPI' var PLATFORM_RAP = 'RAP' var REQUEST_TYPE_PARAM = 'PARAM' // GET ?a=1&b=c&key=value var REQUEST_TYPE_FORM = 'FORM' // POST x-www-form-urlencoded var REQUEST_TYPE_DATA = 'DATA' // POST form-data var REQUEST_TYPE_JSON = 'JSON' // POST application/json var REQUEST_TYPE_GRPC = 'GRPC' // POST application/json var CONTENT_TYPE_MAP = { // 'PARAM': 'plain/text', 'FORM': 'x-www-form-urlencoded', 'DATA': 'form-data', 'JSON': 'application/json', 'GRPC': 'application/json', } var CONTENT_VALUE_TYPE_MAP = { 'plain/text': 'JSON', 'x-www-form-urlencoded': 'FORM', 'form-data': 'DATA', 'application/json': 'JSON' } var IGNORE_HEADERS = ['status code', 'remote address', 'referrer policy', 'connection', 'content-length' , 'content-type', 'date', 'keep-alive', 'proxy-connection', 'set-cookie', 'vary', 'accept', 'cache-control', 'dnt' , 'host', 'origin', 'pragma', 'referer', 'user-agent'] var RANDOM_DB = 'RANDOM_DB' var RANDOM_IN = 'RANDOM_IN' var RANDOM_INT = 'RANDOM_INT' var RANDOM_NUM = 'RANDOM_NUM' var RANDOM_STR = 'RANDOM_STR' var ORDER_DB = 'ORDER_DB' var ORDER_IN = 'ORDER_IN' var ORDER_INT = 'ORDER_INT' var ORDER_MAP = {} function randomInt(min, max) { return randomNum(min, max, 0); } function randomNum(min, max, precision) { // 0 居然也会转成 Number.MIN_SAFE_INTEGER !!! // start = start || Number.MIN_SAFE_INTEGER // end = end || Number.MAX_SAFE_INTEGER if (min == null) { min = Number.MIN_SAFE_INTEGER } if (max == null) { max = Number.MAX_SAFE_INTEGER } if (precision == null) { precision = 2 } return + ((max - min)*Math.random() + min).toFixed(precision); } function randomStr(minLength, maxLength, availableChars) { return 'Ab_Cd' + randomNum(); } function randomIn(...args) { return args == null || args.length <= 0 ? null : args[randomInt(0, args.length - 1)]; } function orderInt(desc, index, min, max) { if (min == null) { min = Number.MIN_SAFE_INTEGER } if (max == null) { max = Number.MAX_SAFE_INTEGER } if (desc) { return max - index%(max - min + 1) } return min + index%(max - min + 1) } function orderIn(desc, index, ...args) { // alert('orderIn index = ' + index + '; args = ' + JSON.stringify(args)); index = index || 0; return args == null || args.length <= index ? null : args[desc ? args.length - index : index]; } function getOrderIndex(randomId, line, argCount) { // alert('randomId = ' + randomId + '; line = ' + line + '; argCount = ' + argCount); // alert('ORDER_MAP = ' + JSON.stringify(ORDER_MAP, null, ' ')); if (randomId == null) { randomId = 0; } if (ORDER_MAP == null) { ORDER_MAP = {}; } if (ORDER_MAP[randomId] == null) { ORDER_MAP[randomId] = {}; } var orderIndex = ORDER_MAP[randomId][line]; // alert('orderIndex = ' + orderIndex) if (orderIndex == null || orderIndex < -1) { orderIndex = -1; } orderIndex ++ orderIndex = argCount == null || argCount <= 0 ? orderIndex : orderIndex%argCount; ORDER_MAP[randomId][line] = orderIndex; // alert('orderIndex = ' + orderIndex) // alert('ORDER_MAP = ' + JSON.stringify(ORDER_MAP, null, ' ')); return orderIndex; } //这些全局变量不能放在data中,否则会报undefined错误 var baseUrl var inputted var handler var docObj var doc var output var isSingle = true var doneCount // APIJSON >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> var App = new Vue({ el: '#app', data: { baseview: 'formater', view: 'output', jsoncon: JSON.stringify(initJson), jsonhtml: initJson, compressStr: '', error: {}, requestVersion: 3, requestCount: 1, urlComment: '关联查询 Comment.userId = User.id', historys: [], history: {name: '请求0'}, remotes: [], locals: [], testCases: [], randoms: [], randomSubs: [], account: '13000082001', password: '123456', accounts: [ { 'isLoggedIn': false, 'name': '测试账号1', 'phone': '13000082001', 'password': '123456' }, { 'isLoggedIn': false, 'name': '测试账号2', 'phone': '13000082002', 'password': '123456' }, { 'isLoggedIn': false, 'name': '测试账号3', 'phone': '13000082003', 'password': '123456' } ], currentAccountIndex: 0, currentDocIndex: -1, currentRandomIndex: -1, currentRandomSubIndex: -1, tests: { '-1':{}, '0':{}, '1':{}, '2': {} }, crossProcess: '交叉账号:已关闭', testProcess: '机器学习:已关闭', randomTestTitle: '参数注入 Random Test', testRandomCount: 1, testRandomProcess: '', compareColor: '#0000', isRandomTest: false, isDelayShow: false, isSaveShow: false, isExportShow: false, isExportRandom: false, isTestCaseShow: false, isHeaderShow: false, isRandomShow: true, // 默认展示 isRandomListShow: false, isRandomSubListShow: false, isRandomEditable: false, isLoginShow: false, isConfigShow: false, isDeleteShow: false, currentDocItem: {}, currentRemoteItem: { "Document": { "id": 1560244940013 , "userId": 82001 , "testAccountId": 82001 , "version": 3 , "name": "测试查询" , "type": "JSON" , "url": "/get" , "date": "2019-06-11 17:22:20.0", // 导致清空文本后,在说明文档后面重叠显示这个绿色注释 "detail": ` // 以上 JSON 文本支持 JSON5 格式。清空文本内容可查看规则。 // 注释可省略。行注释前必须有两个空格;段注释必须在 JSON 下方。 // // ## 快捷键 // Ctrl + I 或 Command + I 格式化 JSON // // #### 右上角设置项 > 预览请求输入框,显示对应的预览效果` }, "TestRecord": { "id": 1615135440014 , "userId": 82001 , "documentId": 1560244940013 } }, currentRandomItem: {}, isAdminOperation: false, loginType: 'login', isExportRemote: false, isRegister: false, isCrossEnabled: false, isMLEnabled: false, isDelegateEnabled: true, isPreviewEnabled: false, isEncodeEnabled: true, isEditResponse: false, isLocalShow: false, uploadTotal: 0, uploadDoneCount: 0, uploadFailCount: 0, exTxt: { name: 'APIJSON测试', button: '保存', index: 0 }, themes: themes, checkedTheme: 0, isExpand: true, User: { id: 0, name: '', head: '' }, Privacy: { id: 0, balance: null //点击更新提示需要判空 0.00 }, type: REQUEST_TYPE_JSON, types: [ REQUEST_TYPE_PARAM, REQUEST_TYPE_JSON], // 很多人喜欢用 GET 接口测试,默认的 JSON 看不懂 , REQUEST_TYPE_FORM, REQUEST_TYPE_DATA, REQUEST_TYPE_GRPC ], //默认展示 host: '', database: 'MYSQL', // 查文档必须,除非后端提供默认配置接口 // 用后端默认的,避免用户总是没有配置就问为什么没有生成文档和注释 'MYSQL',// 'POSTGRESQL', schema: 'sys', // 查文档必须,除非后端提供默认配置接口 // 用后端默认的,避免用户总是没有配置就问为什么没有生成文档和注释 'sys', server: 'http://localhost:8080', // 'http://apijson.cn:9090', // Chrome 90+ 跨域问题非常难搞,开发模式启动都不行了 'http://apijson.org:9090', //apijson.cn // server: 'http://47.74.39.68:9090', // apijson.org thirdParty: 'SWAGGER /v2/api-docs', //apijson.cn // thirdParty: 'RAP /repository/joined /repository/get', // thirdParty: 'YAPI /api/interface/list_menu /api/interface/get', language: CodeUtil.LANGUAGE_KOTLIN, header: {}, page: 0, count: 100, search: '', testCasePage: 0, testCaseCount: 50, testCaseSearch: '', randomPage: 0, randomCount: 50, randomSearch: '', randomSubPage: 0, randomSubCount: 50, randomSubSearch: '' }, methods: { // 全部展开 expandAll: function () { if (this.view != 'code') { alert('请先获取正确的JSON Response!') return } $('.icon-square-min').show() $('.icon-square-plus').hide() $('.expand-view').show() $('.fold-view').hide() this.isExpand = true; }, // 全部折叠 collapseAll: function () { if (this.view != 'code') { alert('请先获取正确的JSON Response!') return } $('.icon-square-min').hide() $('.icon-square-plus').show() $('.expand-view').hide() $('.fold-view').show() this.isExpand = false; }, // diff diffTwo: function () { var oldJSON = {} var newJSON = {} this.view = 'code' try { oldJSON = jsonlint.parse(this.jsoncon) } catch (ex) { this.view = 'error' this.error = { msg: '原 JSON 解析错误\r\n' + ex.message } return } try { newJSON = jsonlint.parse(this.jsoncon) } catch (ex) { this.view = 'error' this.error = { msg: '新 JSON 解析错误\r\n' + ex.message } return } var base = difflib.stringAsLines(JSON.stringify(oldJSON, '', 4)) var newtxt = difflib.stringAsLines(JSON.stringify(newJSON, '', 4)) var sm = new difflib.SequenceMatcher(base, newtxt) var opcodes = sm.get_opcodes() $('#diffoutput').empty().append(diffview.buildView({ baseTextLines: base, newTextLines: newtxt, opcodes: opcodes, baseTextName: '原 JSON', newTextName: '新 JSON', contextSize: 2, viewType: 0 })) }, baseViewToDiff: function () { this.baseview = 'diff' this.diffTwo() }, // 回到格式化视图 baseViewToFormater: function () { this.baseview = 'formater' this.view = 'code' this.showJsonView() }, // 根据json内容变化格式化视图 showJsonView: function () { if (this.baseview === 'diff') { return } try { if (this.jsoncon.trim() === '') { this.view = 'empty' } else { this.view = 'code' if (isSingle) { this.jsonhtml = jsonlint.parse(this.jsoncon) } else { this.jsonhtml = Object.assign({ _$_this_$_: JSON.stringify({ path: null, table: null }) }, jsonlint.parse(this.jsoncon)) } } } catch (ex) { this.view = 'error' this.error = { msg: ex.message } } }, showUrl: function (isAdminOperation, branchUrl) { if (StringUtil.isEmpty(this.host, true)) { //显示(可编辑)URL Host if (isAdminOperation != true) { baseUrl = this.getBaseUrl() } vUrl.value = (isAdminOperation ? this.server : baseUrl) + branchUrl } else { //隐藏(固定)URL Host if (isAdminOperation) { this.host = this.server } vUrl.value = branchUrl } vUrlComment.value = isSingle || StringUtil.isEmpty(this.urlComment, true) ? '' : vUrl.value + CodeUtil.getComment(this.urlComment, false, ' ') + ' - ' + (this.requestVersion > 0 ? 'V' + this.requestVersion : 'V*'); }, //设置基地址 setBaseUrl: function () { if (StringUtil.isEmpty(this.host, true) != true) { return } // 重新拉取文档 var bu = this.getBaseUrl() if (baseUrl != bu) { baseUrl = bu; doc = null //这个是本地的数据库字典及非开放请求文档 this.saveCache('', 'URL_BASE', baseUrl) //已换成固定的管理系统URL // this.remotes = [] // var index = baseUrl.indexOf(':') //http://localhost:8080 // this.server = (index < 0 ? baseUrl : baseUrl.substring(0, baseUrl)) + ':9090' } }, getUrl: function () { var url = StringUtil.get(this.host) + new String(vUrl.value) return url.replace(/ /g, '') }, //获取基地址 getBaseUrl: function () { var url = new String(vUrl.value).trim() var length = this.getBaseUrlLength(url) url = length <= 0 ? '' : url.substring(0, length) return url == '' ? URL_BASE : url }, //获取基地址长度,以://后的第一个/分割baseUrl和method getBaseUrlLength: function (url_) { var url = StringUtil.trim(url_) var index = url.indexOf(' ') if (index >= 0) { return index + 1 } index = url.indexOf('://') return index < 0 ? 0 : index + 3 + url.substring(index + 3).indexOf('/') }, //获取操作方法 getMethod: function () { var url = new String(vUrl.value).trim() var index = this.getBaseUrlLength(url) url = index <= 0 ? url : url.substring(index) index = url.indexOf("?") if (index >= 0) { url = url.substring(0, index) } return url.startsWith('/') ? url.substring(1) : url }, //获取请求的tag getTag: function () { var req = null; try { req = this.getRequest(vInput.value); } catch (e) { log('main.getTag', 'try { req = this.getRequest(vInput.value); \n } catch (e) {\n' + e.message) } return req == null ? null : req.tag }, getRequest: function (json, defaultValue, isRaw) { // JSON5 兜底,减少修改范围 , isSingle) { var s = isRaw != true && isSingle ? this.switchQuote(json) : json; // this.toDoubleJSON(json, defaultValue); if (StringUtil.isEmpty(s, true)) { return defaultValue } try { return jsonlint.parse(s); } catch (e) { log('main.getRequest', 'try { return jsonlint.parse(s); \n } catch (e2) {\n' + e.message) log('main.getRequest', 'return jsonlint.parse(this.removeComment(s));') return JSON5.parse(s); // jsonlint.parse(this.removeComment(s)); } }, getExtraComment: function(json) { var it = json != null ? json : StringUtil.trim(vInput.value); var start = it.lastIndexOf('\n\/*'); var end = it.lastIndexOf('\n*\/'); return start < 0 || end <= start ? null : it.substring(start + '\n\/*'.length, end); }, getHeader: function (text) { var header = {} var hs = StringUtil.isEmpty(text, true) ? null : StringUtil.split(text, '\n') if (hs != null && hs.length > 0) { var item for (var i = 0; i < hs.length; i++) { item = hs[i] || '' // 解决整体 trim 后第一行 // 被当成正常的 key 路径而不是注释 var index = StringUtil.trim(item).startsWith('//') ? 0 : item.lastIndexOf(' //') // 不加空格会导致 http:// 被截断 ('//') //这里只支持单行注释,不用 removeComment 那种带多行的去注释方式 var item2 = index < 0 ? item : item.substring(0, index) item2 = item2.trim() if (item2.length <= 0) { continue; } index = item2.indexOf(':') if (index <= 0) { throw new Error('请求头 Request Header 输入错误!请按照每行 key: value 的格式输入,不要有多余的换行或空格!' + '\n错误位置: 第 ' + (i + 1) + ' 行' + '\n错误文本: ' + item) } var val = item2.substring(index + 1, item2.length) var ind = val.indexOf('(') //一定要有函数是为了避免里面是一个简短单词和 APIAuto 代码中变量冲突 if (ind > 0 && val.indexOf(')') > ind) { //不从 0 开始是为了保证是函数,且不是 (1) 这种单纯限制作用域的括号 try { val = eval(val) } catch (e) { this.log("getHeader if (hs != null && hs.length > 0) { ... if (ind > 0 && val.indexOf(')') > ind) { ... try { val = eval(val) } catch (e) = " + e.message) } } header[StringUtil.trim(item2.substring(0, index))] = val } } return header }, // 分享 APIAuto 特有链接,打开即可还原分享人的 JSON 参数、设置项、搜索关键词、分页数量及页码等配置 shareLink: function (isRandom) { var jsonStr = null if (this.isTestCaseShow != true) { try { jsonStr = JSON.stringify(encode(JSON.parse(vInput.value))) } catch (e) { // 可能包含注释 log(e) jsonStr = encode(StringUtil.trim(vInput.value)) } } // URL 太长导致打不开标签 var settingStr = null try { settingStr = JSON.stringify({ requestVersion: this.requestVersion, requestCount: this.requestCount, isTestCaseShow: this.isTestCaseShow, // isHeaderShow: this.isHeaderShow, // isRandomShow: this.isRandomShow, isRandomListShow: this.isRandomShow ? this.isRandomListShow : undefined, isRandomSubListShow: this.isRandomListShow ? this.isRandomSubListShow : undefined, // isRandomEditable: this.isRandomEditable, isCrossEnabled: this.isCrossEnabled, isMLEnabled: this.isMLEnabled, isDelegateEnabled: this.isDelegateEnabled, isPreviewEnabled: this.isPreviewEnabled, isEncodeEnabled: this.isEncodeEnabled, isEditResponse: this.isEditResponse, isLocalShow: this.isTestCaseShow ? this.isLocalShow : undefined, page: this.page, count: this.count, testCasePage: this.testCasePage, testCaseCount: this.testCaseCount, testRandomCount: this.testRandomCount, randomPage: this.randomPage, randomCount: this.randomCount, randomSubPage: this.randomSubPage, randomSubCount: this.randomSubCount, host: StringUtil.isEmpty(this.host, true) ? undefined : encodeURIComponent(this.host), search: StringUtil.isEmpty(this.search, true) ? undefined : encodeURIComponent(this.search), testCaseSearch: StringUtil.isEmpty(this.testCaseSearch, true) ? undefined : this.testCaseSearch, randomSearch: StringUtil.isEmpty(this.randomSearch, true) ? undefined : encodeURIComponent(this.randomSearch), randomSubSearch: StringUtil.isEmpty(this.randomSubSearch, true) ? undefined : encodeURIComponent(this.randomSubSearch) }) } catch (e){ log(e) } var headerStr = this.isTestCaseShow || StringUtil.isEmpty(vHeader.value, true) ? null : encodeURIComponent(StringUtil.trim(vHeader.value)) var randomStr = this.isTestCaseShow || StringUtil.isEmpty(vRandom.value, true) ? null : encodeURIComponent(StringUtil.trim(vRandom.value)) var href = window.location.href || 'http://apijson.cn/api' var ind = href == null ? -1 : href.indexOf('?') // url 后带参数只能 encodeURIComponent // 实测 561059 长度的 URL 都支持,只是输入框显示长度约为 2000 window.open((ind < 0 ? href : href.substring(0, ind)) + (this.view != 'code' ? "?send=false" : (isRandom ? "?send=random" : "?send=true")) + "&type=" + StringUtil.trim(this.type) + "&url=" + encodeURIComponent(StringUtil.trim(vUrl.value)) + (StringUtil.isEmpty(jsonStr, true) ? '' : "&json=" + jsonStr) + (StringUtil.isEmpty(headerStr, true) ? '' : "&header=" + headerStr) + (StringUtil.isEmpty(randomStr, true) ? '' : "&random=" + randomStr) + (StringUtil.isEmpty(settingStr, true) ? '' : "&setting=" + settingStr) ) }, // 显示保存弹窗 showSave: function (show) { if (show) { if (this.isTestCaseShow) { alert('请先输入请求内容!') return } var tag = this.getTag() this.history.name = (this.urlComment || this.getMethod() + (StringUtil.isEmpty(tag, true) ? '' : ' ' + tag)) + ' ' + this.formatTime() //不自定义名称的都是临时的,不需要时间太详细 } this.isSaveShow = show }, // 显示导出弹窗 showExport: function (show, isRemote, isRandom) { if (show) { if (isRemote) { //共享测试用例 this.isExportRandom = isRandom // if (isRandom != true) { // 分享搜索关键词和分页信息也挺好 } && this.isTestCaseShow != true) { // 没有拿到列表,没用 // setTimeout(function () { // App.shareLink(App.isRandomTest) // }, 1000) // } if (this.isTestCaseShow) { alert('请先输入请求内容!') return } if (this.view == 'error') { // this.view != 'code') { alert('发现错误,请输入正确的内容!') // alert('请先测试请求,确保是正确可用的!') return } if (isRandom) { this.exTxt.name = '随机配置 ' + this.formatDateTime() } else { if (this.isEditResponse) { this.isExportRemote = isRemote this.exportTxt() return } // var tag = this.getTag() this.exTxt.name = this.urlComment || '' // 避免偷懒不输入名称 this.getMethod() + (StringUtil.isEmpty(tag, true) ? '' : ' ' + tag) } } else { //下载到本地 if (this.isTestCaseShow) { //文档 this.exTxt.name = 'APIJSON自动化文档 ' + this.formatDateTime() } else if (this.view == 'markdown' || this.view == 'output') { var suffix switch (this.language) { case CodeUtil.LANGUAGE_KOTLIN: suffix = '.kt'; break; case CodeUtil.LANGUAGE_JAVA: suffix = '.java'; break; case CodeUtil.LANGUAGE_C_SHARP: suffix = '.cs'; break; case CodeUtil.LANGUAGE_SWIFT: suffix = '.swift'; break; case CodeUtil.LANGUAGE_OBJECTIVE_C: suffix = '.h'; break; case CodeUtil.LANGUAGE_GO: suffix = '.go'; break; case CodeUtil.LANGUAGE_C_PLUS_PLUS: suffix = '.cpp'; break; case CodeUtil.LANGUAGE_TYPE_SCRIPT: suffix = '.ts'; break; case CodeUtil.LANGUAGE_JAVA_SCRIPT: suffix = '.js'; break; case CodeUtil.LANGUAGE_PHP: suffix = '.php'; break; case CodeUtil.LANGUAGE_PYTHON: suffix = '.py'; break; default: suffix = '.java'; break; } this.exTxt.name = 'User' + suffix alert('自动生成模型代码,可填类名后缀:\n' + 'Kotlin.kt, Java.java, Swift.swift, Objective-C.m, C#.cs, Go.go,' + '\nTypeScript.ts, JavaScript.js, PHP.php, Python.py, C++.cpp'); } else { this.exTxt.name = 'APIJSON测试 ' + this.getMethod() + ' ' + this.formatDateTime() } } } this.isExportShow = show this.isExportRemote = isRemote }, // 显示配置弹窗 showConfig: function (show, index) { this.isConfigShow = false if (this.isTestCaseShow) { if (index == 3 || index == 4 || index == 5 || index == 10) { this.showTestCase(false, false) } } if (show) { this.exTxt.button = index == 8 ? '上传' : '切换' this.exTxt.index = index switch (index) { case 0: case 1: case 2: case 6: case 7: case 8: this.exTxt.name = index == 0 ? this.database : (index == 1 ? this.schema : (index == 2 ? this.language : (index == 6 ? this.server : (index == 8 ? this.thirdParty : (this.types || []).join())))) this.isConfigShow = true if (index == 0) { alert('可填数据库:\nMYSQL,POSTGRESQL,SQLSERVER,ORACLE,DB2,SQLITE') } else if (index == 2) { alert('自动生成代码,可填语言:\nKotlin,Java,Swift,Objective-C,C#,Go,\nTypeScript,JavaScript,PHP,Python,C++') } else if (index == 7) { alert('多个类型用 , 隔开,可填类型:\nPARAM(GET ?a=1&b=c&key=value),\nJSON(POST application/json),\nFORM(POST x-www-form-urlencoded),\nDATA(POST form-data),\nGRPC(POST application/json 需要 GRPC 服务开启反射)') } else if (index == 8) { this.isHeaderShow = true alert('例如:\nSWAGGER http://apijson.cn:8080/v2/api-docs\nSWAGGER /v2/api-docs // 省略 Host\nSWAGGER / // 省略 Host 和 分支 URL\nRAP /repository/joined /repository/get\nYAPI /api/interface/list_menu /api/interface/get') try { this.getThirdPartyApiList(this.thirdParty, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { CodeUtil.thirdParty = platform var data = err != null ? null : (res || {}).data; var code = data == null ? null : data.errCode || data.errcode || data.err_code if (err != null || (code != null && code != 0)) { App.isHeaderShow = true App.isRandomShow = false alert('请把 YApi/Rap/Swagger 等网站的有效 Cookie 粘贴到请求头 Request Header 输入框后再试!') } App.onResponse(url_, res, err) return false }, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { var data = (res || {}).data var apiMap = CodeUtil.thirdPartyApiMap || {} if (platform == PLATFORM_POSTMAN) { alert('尚未开发 ' + PLATFORM_POSTMAN) return true } else if (platform == PLATFORM_SWAGGER) { var apis = data == null ? null : data.paths if (apis != null) { // var i = 0 for (var url in apis) { var item = apis[url] apiMap[url] = item.post || item.get || item.put || item.delete } } } else if (platform == PLATFORM_RAP) { } else if (platform == PLATFORM_YAPI) { var api = (data || {}).data var url = api == null || api.path == null ? null : StringUtil.noBlank(api.path).replace(/\/\//g, '/') if (StringUtil.isEmpty(url, true)) { return } var typeAndParam = App.parseYApiTypeAndParam(api) var name = StringUtil.trim(api.username) + ': ' + StringUtil.trim(api.title) apiMap[url] = { name: name, request: typeAndParam.param, response: api.res_body == null ? null : JSON.parse(api.res_body), detail: name + '\n' + (api.up_time == null ? '' : (typeof api.up_time != 'number' ? api.up_time : new Date(1000*api.up_time).toLocaleString())) + '\nhttp://apijson.cn/yapi/project/1/interface/api/' + api._id + '\n\n' + (StringUtil.isEmpty(api.markdown, true) ? StringUtil.trim(api.description) : api.markdown.trim().replace(/\\_/g, '_')) } } else { alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') return true } CodeUtil.thirdPartyApiMap = apiMap App.saveCache(App.thirdParty, 'thirdPartyApiMap', apiMap); return true }) } catch (e) { console.log('created try { ' + '\nthis.User = this.getCache(this.server, User) || {}' + '\n} catch (e) {\n' + e.message) } } break case 3: this.host = this.getBaseUrl() this.showUrl(false, new String(vUrl.value).substring(this.host.length)) //没必要导致必须重新获取 Response,this.onChange(false) break case 4: this.isHeaderShow = show this.saveCache('', 'isHeaderShow', show) break case 5: this.isRandomShow = show this.saveCache('', 'isRandomShow', show) break case 9: this.isDelegateEnabled = show this.saveCache('', 'isDelegateEnabled', show) break case 10: this.isPreviewEnabled = show this.saveCache('', 'isPreviewEnabled', show) this.onChange(false) break case 12: this.isEncodeEnabled = show this.saveCache('', 'isEncodeEnabled', show) break case 11: var did = ((this.currentRemoteItem || {}).Document || {}).id if (did == null) { alert('请先选择一个已上传的用例!') return } this.isEditResponse = show // this.saveCache('', 'isEditResponse', show) vInput.value = ((this.view != 'code' || StringUtil.isEmpty(this.jsoncon, true) ? null : this.jsoncon) || (this.currentRemoteItem.TestRecord || {}).response) || '' vHeader.value = (this.currentRemoteItem.TestRecord || {}).header || '' this.isTestCaseShow = false this.onChange(false) break } } else if (index == 3) { var host = StringUtil.get(this.host) var branch = new String(vUrl.value) this.host = '' vUrl.value = host + branch //保证 showUrl 里拿到的 baseUrl = this.host (http://apijson.cn:8080/put /balance) this.setBaseUrl() //保证自动化测试等拿到的 baseUrl 是最新的 this.showUrl(false, branch) //没必要导致必须重新获取 Response,this.onChange(false) } else if (index == 4) { this.isHeaderShow = show this.saveCache('', 'isHeaderShow', show) } else if (index == 5) { this.isRandomShow = show this.saveCache('', 'isRandomShow', show) } else if (index == 9) { this.isDelegateEnabled = show this.saveCache('', 'isDelegateEnabled', show) } else if (index == 10) { this.isPreviewEnabled = show this.saveCache('', 'isPreviewEnabled', show) // vRequestMarkdown.innerHTML = '' } else if (index == 12) { this.isEncodeEnabled = show this.saveCache('', 'isEncodeEnabled', show) } else if (index == 11) { this.isEditResponse = show // this.saveCache('', 'isEditResponse', show) vInput.value = (this.currentRemoteItem.Document || {}).request || '' vHeader.value = (this.currentRemoteItem.Document || {}).header || '' this.isTestCaseShow = false this.onChange(false) } }, // 显示删除弹窗 showDelete: function (show, item, index, isRandom) { this.isDeleteShow = show this.isDeleteRandom = isRandom this.exTxt.name = '请输入' + (isRandom ? '随机配置' : '接口') + '名来确认' if (isRandom) { this.currentRandomItem = Object.assign(item, { index: index }) } else { this.currentDocItem = Object.assign(item, { index: index }) } }, // 删除接口文档 deleteDoc: function () { var isDeleteRandom = this.isDeleteRandom var item = (isDeleteRandom ? this.currentRandomItem : this.currentDocItem) || {} var doc = (isDeleteRandom ? item.Random : item.Document) || {} var type = isDeleteRandom ? '随机配置' : '接口' if (doc.id == null) { alert('未选择' + type + '或' + type + '不存在!') return } if (doc.name != this.exTxt.name) { alert('输入的' + type + '名和要删除的' + type + '名不匹配!') return } this.showDelete(false, {}) this.isTestCaseShow = false this.isRandomListShow = false var url = this.server + '/delete' var req = isDeleteRandom ? { format: false, 'Random': { 'id': doc.id }, 'tag': 'Random' } : { format: false, 'Document': { 'id': doc.id }, 'tag': 'Document' } this.request(true, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data || {} if (isDeleteRandom) { if (rpObj.Random != null && rpObj.Random.code == CODE_SUCCESS) { if (((item.Random || {}).toId || 0) <= 0) { App.randoms.splice(item.index, 1) } else { App.randomSubs.splice(item.index, 1) } // App.showRandomList(true, App.currentRemoteItem) } } else { if (rpObj.Document != null && rpObj.Document.code == CODE_SUCCESS) { App.remotes.splice(item.index, 1) App.showTestCase(true, App.isLocalShow) } } }) }, // 保存当前的JSON save: function () { if (this.history.name.trim() === '') { Helper.alert('名称不能为空!', 'danger') return } var val = { name: this.history.name, type: this.type, url: '/' + this.getMethod(), request: inputted, response: this.jsoncon, header: vHeader.value, random: vRandom.value } var key = String(Date.now()) localforage.setItem(key, val, function (err, value) { Helper.alert('保存成功!', 'success') App.showSave(false) val.key = key App.historys.push(val) }) }, // 清空本地历史 clearLocal: function () { this.locals.splice(0, this.locals.length) //UI无反应 this.locals = [] this.saveCache('', 'locals', []) }, // 删除已保存的 remove: function (item, index, isRemote, isRandom) { if (isRemote == null || isRemote == false) { //null != false localforage.removeItem(item.key, function () { App.historys.splice(index, 1) }) } else { if (this.isLocalShow) { this.locals.splice(index, 1) this.saveCache('', 'locals', this.locals) return } if (isRandom && (((item || {}).Random || {}).id || 0) <= 0) { this.randomSubs.splice(index, 1) return } this.showDelete(true, item, index, isRandom) } }, // 根据参数注入用例恢复数据 restoreRandom: function (index, item) { this.currentRandomItem = item this.isRandomListShow = false this.isRandomSubListShow = false var random = (item || {}).Random || {} this.randomTestTitle = random.name this.testRandomCount = random.count vRandom.value = StringUtil.get(random.config) var response = ((item || {}).TestRecord || {}).response if (StringUtil.isEmpty(response, true) == false) { this.jsoncon = StringUtil.trim(response) this.view = 'code' } }, // 根据测试用例/历史记录恢复数据 restoreRemoteAndTest: function (index, item) { this.restoreRemote(index, item, true) }, // 根据测试用例/历史记录恢复数据 restoreRemote: function (index, item, test) { this.currentDocIndex = index this.currentRemoteItem = item this.restore((item || {}).Document, ((item || {}).TestRecord || {}).response, true, test) }, // 根据历史恢复数据 restore: function (item, response, isRemote, test) { this.isEditResponse = false item = item || {} // localforage.getItem(item.key || '', function (err, value) { var branch = new String(item.url || '/get') if (branch.startsWith('/') == false) { branch = '/' + branch } this.type = item.type; this.urlComment = item.name; this.requestVersion = item.version; this.showUrl(false, branch) this.showTestCase(false, this.isLocalShow) vInput.value = StringUtil.get(item.request) vHeader.value = StringUtil.get(item.header) vRandom.value = StringUtil.get(item.random) this.onChange(false) if (isRemote) { this.randoms = [] this.showRandomList(this.isRandomListShow, item) } if (test) { this.send(false) } else { if (StringUtil.isEmpty(response, true) == false) { setTimeout(function () { App.jsoncon = StringUtil.trim(response) App.view = 'code' }, 500) } } // }) }, // 获取所有保存的json listHistory: function () { localforage.iterate(function (value, key, iterationNumber) { if (key[0] !== '#') { value.key = key App.historys.push(value) } if (key === '#theme') { // 设置默认主题 App.checkedTheme = value } }) }, // 导出文本 exportTxt: function () { this.isExportShow = false if (this.isExportRemote == false) { //下载到本地 if (this.isTestCaseShow) { //文档 saveTextAs('# ' + this.exTxt.name + '\n主页: https://github.com/Tencent/APIJSON' + '\n\nBASE_URL: ' + this.getBaseUrl() + '\n\n\n## 测试用例(Markdown格式,可用工具预览) \n\n' + this.getDoc4TestCase() + '\n\n\n\n\n\n\n\n## 文档(Markdown格式,可用工具预览) \n\n' + doc , this.exTxt.name + '.txt') } else if (this.view == 'markdown' || this.view == 'output') { //model var clazz = StringUtil.trim(this.exTxt.name) var txt = '' //配合下面 +=,实现注释判断,一次全生成,方便测试 if (clazz.endsWith('.java')) { txt += CodeUtil.parseJavaBean(docObj, clazz.substring(0, clazz.length - 5), this.database) } else if (clazz.endsWith('.swift')) { txt += CodeUtil.parseSwiftStruct(docObj, clazz.substring(0, clazz.length - 6), this.database) } else if (clazz.endsWith('.kt')) { txt += CodeUtil.parseKotlinDataClass(docObj, clazz.substring(0, clazz.length - 3), this.database) } else if (clazz.endsWith('.m')) { txt += CodeUtil.parseObjectiveCEntity(docObj, clazz.substring(0, clazz.length - 2), this.database) } else if (clazz.endsWith('.cs')) { txt += CodeUtil.parseCSharpEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) } else if (clazz.endsWith('.php')) { txt += CodeUtil.parsePHPEntity(docObj, clazz.substring(0, clazz.length - 4), this.database) } else if (clazz.endsWith('.go')) { txt += CodeUtil.parseGoEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) } else if (clazz.endsWith('.cpp')) { txt += CodeUtil.parseCppStruct(docObj, clazz.substring(0, clazz.length - 4), this.database) } else if (clazz.endsWith('.js')) { txt += CodeUtil.parseJavaScriptEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) } else if (clazz.endsWith('.ts')) { txt += CodeUtil.parseTypeScriptEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) } else if (clazz.endsWith('.py')) { txt += CodeUtil.parsePythonEntity(docObj, clazz.substring(0, clazz.length - 3), this.database) } else { alert('请正确输入对应语言的类名后缀!') } if (StringUtil.isEmpty(txt, true)) { alert('找不到 ' + clazz + ' 对应的表!请检查数据库中是否存在!\n如果不存在,请重新输入存在的表;\n如果存在,请刷新网页后重试。') return } saveTextAs(txt, clazz) } else { var res = JSON.parse(this.jsoncon) res = this.removeDebugInfo(res) var s = '' switch (this.language) { case CodeUtil.LANGUAGE_KOTLIN: s += '(Kotlin):\n\n' + CodeUtil.parseKotlinResponse('', res, 0, false, ! isSingle) break; case CodeUtil.LANGUAGE_JAVA: s += '(Java):\n\n' + CodeUtil.parseJavaResponse('', res, 0, false, ! isSingle) break; case CodeUtil.LANGUAGE_C_SHARP: s += '(C#):\n\n' + CodeUtil.parseCSharpResponse('', res, 0) break; case CodeUtil.LANGUAGE_SWIFT: s += '(Swift):\n\n' + CodeUtil.parseSwiftResponse('', res, 0, isSingle) break; case CodeUtil.LANGUAGE_OBJECTIVE_C: s += '(Objective-C):\n\n' + CodeUtil.parseObjectiveCResponse('', res, 0) break; case CodeUtil.LANGUAGE_GO: s += '(Go):\n\n' + CodeUtil.parseGoResponse('', res, 0) break; case CodeUtil.LANGUAGE_C_PLUS_PLUS: s += '(C++):\n\n' + CodeUtil.parseCppResponse('', res, 0, isSingle) break; case CodeUtil.LANGUAGE_TYPE_SCRIPT: s += '(TypeScript):\n\n' + CodeUtil.parseTypeScriptResponse('', res, 0, isSingle) break; case CodeUtil.LANGUAGE_JAVA_SCRIPT: s += '(JavaScript):\n\n' + CodeUtil.parseJavaScriptResponse('', res, 0, isSingle) break; case CodeUtil.LANGUAGE_PHP: s += '(PHP):\n\n' + CodeUtil.parsePHPResponse('', res, 0, isSingle) break; case CodeUtil.LANGUAGE_PYTHON: s += '(Python):\n\n' + CodeUtil.parsePythonResponse('', res, 0, isSingle) break; default: s += ':\n没有生成代码,可能生成代码(封装,解析)的语言配置错误。 \n'; break; } saveTextAs('# ' + this.exTxt.name + '\n主页: https://github.com/Tencent/APIJSON' + '\n\n\nURL: ' + StringUtil.get(vUrl.value) + '\n\n\nHeader:\n' + StringUtil.get(vHeader.value) + '\n\n\nRequest:\n' + StringUtil.get(vInput.value) + '\n\n\nResponse:\n' + StringUtil.get(this.jsoncon) + '\n\n\n## 解析 Response 的代码' + s , this.exTxt.name + '.txt') } } else { //上传到远程服务器 var id = this.User == null ? null : this.User.id if (id == null || id <= 0) { alert('请先登录!') return } var isExportRandom = this.isExportRandom var isEditResponse = this.isEditResponse if (isExportRandom != true && StringUtil.isEmpty(this.exTxt.name, true)) { alert('请输入接口名!') return } var doc = (this.currentRemoteItem || {}).Document || {} var tr = (this.currentRemoteItem || {}).TestRecord || {} var did = doc.id if (isExportRandom && did == null) { alert('请先共享测试用例!') return } this.isTestCaseShow = false var currentAccountId = this.getCurrentAccountId() var currentResponse = this.view != 'code' || StringUtil.isEmpty(this.jsoncon, true) ? {} : this.removeDebugInfo(JSON.parse(this.jsoncon)); var after = isSingle ? this.switchQuote(inputted) : inputted; // this.toDoubleJSON(inputted); var inputObj = this.getRequest(after, {}); var commentObj = null; if (isExportRandom != true) { var m = this.getMethod(); var commentStddObj = null try { commentStddObj = JSON.parse(isEditResponse ? tr.standard : doc.standard); } catch(e) { log(e) } var code_ = inputObj.code inputObj.code = null // delete inputObj.code commentObj = JSONResponse.updateStandard(commentStddObj, inputObj); CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], m, this.database, this.language, isEditResponse != true, commentObj, true); inputObj.code = code_ } var code = currentResponse.code; var thrw = currentResponse.throw; delete currentResponse.code; //code必须一致 delete currentResponse.throw; //throw必须一致 var isML = this.isMLEnabled; var stddObj = isML ? JSONResponse.updateStandard({}, currentResponse) : {}; stddObj.code = code; stddObj.throw = thrw; currentResponse.code = code; currentResponse.throw = thrw; var url = this.server + (isExportRandom || isEditResponse || did == null ? '/post' : '/put') var req = isExportRandom ? { format: false, 'Random': { toId: 0, documentId: did, count: this.requestCount, name: this.exTxt.name, config: vRandom.value }, 'TestRecord': { 'response': JSON.stringify(currentResponse), 'standard': isML ? JSON.stringify(stddObj) : null }, 'tag': 'Random' } : { format: false, 'Document': isEditResponse ? null : { 'id': did == null ? undefined : did, 'testAccountId': currentAccountId, 'name': this.exTxt.name, 'type': this.type, 'url': '/' + this.getMethod(), 'request': JSON.stringify(inputObj, null, ' '), 'standard': JSON.stringify(commentObj, null, ' '), 'header': vHeader.value, 'detail': this.getExtraComment() || ((this.currentRemoteItem || {}).Document || {}).detail, }, 'TestRecord': isEditResponse != true && did != null ? null : { 'documentId': isEditResponse ? did : undefined, 'randomId': 0, 'host': this.getBaseUrl(), 'testAccountId': currentAccountId, 'response': JSON.stringify(isEditResponse ? inputObj : currentResponse), 'standard': isML || isEditResponse ? JSON.stringify(isEditResponse ? commentObj : stddObj) : undefined, // 没必要,直接都在请求中说明,查看也方便 'detail': (isEditResponse ? this.getExtraComment() : null) || ((this.currentRemoteItem || {}).TestRecord || {}).detail, }, 'tag': isEditResponse ? 'TestRecord' : 'Document' } this.request(true, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data || {} if (isExportRandom) { if (rpObj.code == CODE_SUCCESS) { App.randoms = [] App.showRandomList(true, (App.currentRemoteItem || {}).Document) } } else { var isPut = url.indexOf('/put') >= 0 if (rpObj.code != CODE_SUCCESS) { if (isPut) { // 修改失败就转为新增 App.currentRemoteItem = null; alert('修改失败,请重试(自动转为新增)!' + StringUtil.trim(rpObj.msg)) } } else { App.remotes = [] App.showTestCase(true, false) if (isPut) { // 修改失败就转为新增 alert('修改成功') return } //自动生成随机配置(遍历 JSON,对所有可变值生成配置,排除 @key, key@, key() 等固定值) var req = App.getRequest(vInput.value, {}) var config = StringUtil.trim(App.newRandomConfig(null, '', req)) if (config == '') { return; } App.request(true, REQUEST_TYPE_JSON, App.server + '/post', { format: false, 'Random': { documentId: rpObj.Document.id, count: App.requestCount, name: '默认配置(上传测试用例时自动生成)', config: config }, TestRecord: { host: App.getBaseUrl(), response: '' }, 'tag': 'Random' }, {}, function (url, res, err) { if (res.data != null && res.data.Random != null && res.data.Random.code == CODE_SUCCESS) { alert('已自动生成并上传随机配置:\n' + config) App.isRandomListShow = true } else { alert('已自动生成,但上传以下随机配置失败:\n' + config) vRandom.value = config } App.onResponse(url, res, err) }) } } }) } }, newRandomConfig: function (path, key, value) { if (key == null) { return '' } if (path == '' && (key == 'tag' || key == 'version' || key == 'format')) { return '' } var config = '' var childPath = path == null || path == '' ? key : path + '/' + key var prefix = '\n' + childPath + ': ' if (value instanceof Array) { var val if (value.length <= 0) { val = '' } else { if (value.length <= 1) { val = ', ' + JSON.stringify(value) } else if (value.length <= 2) { val = ', ' + JSON.stringify([value[0]]) + ', ' + JSON.stringify([value[1]]) + ', ' + JSON.stringify(value) } else { val = ', ' + JSON.stringify([value[0]]) + ', ' + JSON.stringify([value[value.length - 1]]) + ', ' + JSON.stringify([value[Math.floor(value.length / 2)]]) + ', ' + JSON.stringify(value) } } config += prefix + 'ORDER_IN(undefined, null, []' + val + ')' } else if (value instanceof Object) { for(var k in value) { var v = value[k] var isAPIJSONArray = v instanceof Object && v instanceof Array == false && k.startsWith('@') == false && (k.endsWith('[]') || k.endsWith('@')) if (isAPIJSONArray) { if (k.endsWith('@')) { delete v.from delete v.range } prefix = '\n' + (childPath == null || childPath == '' ? '' : childPath + '/') + k + '/' if (v.hasOwnProperty('page')) { config += prefix + 'page: ' + 'ORDER_INT(0, 10)' delete v.page } if (v.hasOwnProperty('count')) { config += prefix + 'count: ' + 'ORDER_IN(undefined, null, 0, 1, 5, 10, 20' + ([0, 1, 5, 10, 20].indexOf(v.count) >= 0 ? ')' : ', ' + v.count + ')') delete v.count } if (v.hasOwnProperty('query')) { config += prefix + 'query: ' + 'ORDER_IN(undefined, null, 0, 1, 2)' delete v.query } } config += this.newRandomConfig(childPath, k, v) } } else { //自定义关键词 if (key.startsWith('@')) { return config } if (typeof value == 'boolean') { config += prefix + 'ORDER_IN(undefined, null, false, true)' } else if (typeof value == 'number') { var isId = key == 'id' || key.endsWith('Id') || key.endsWith('_id') || key.endsWith('_ID') if (isId) { config += prefix + 'ORDER_IN(undefined, null, ' + value + ')' if (value >= 1000000000) { //PHP 等语言默认精确到秒 1000000000000) { config += '\n // 可替代上面的 ' + prefix.substring(1) + 'RANDOM_INT(' + Math.round(0.9 * value) + ', ' + Math.round(1.1 * value) + ')' } else { config += '\n // 可替代上面的 ' + prefix.substring(1) + 'RANDOM_INT(1, ' + (10 * value) + ')' } } else { var valStr = String(value) var dotIndex = valStr.indexOf('.') var hasDot = dotIndex >= 0 var keep = dotIndex < 0 ? 2 : valStr.length - dotIndex - 1 if (value < 0) { config += prefix + (hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(' + (100 * value) + (hasDot ? ', 0, ' + keep + ')' : ', 0)') } else if (value > 0 && value < 1) { // 0-1 比例 config += prefix + 'RANDOM_NUM(0, 1, ' + keep + ')' } else if ((hasDot && value > 0 && value <= 100) || (hasDot != true && value > 5 && value <= 100)) { // 10% 百分比 config += prefix + (hasDot ? 'RANDOM_NUM(0, 100, ' + keep + ')' : 'RANDOM_INT(0, 100)') } else { config += prefix + (dotIndex < 0 && value <= 10 ? 'ORDER_INT(0, 10)' : ((hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(0, ' + 100 * value + (hasDot ? ', ' + keep + ')' : ')')) ) var hasDot = String(value).indexOf('.') >= 0 if (value < 0) { config += '\n // 可替代上面的 ' + prefix.substring(1) + (hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(' + (100 * value) + ', 0)' } else if (value > 0 && value < 1) { // 0-1 比例 config += '\n // 可替代上面的 ' + prefix.substring(1) + 'RANDOM_NUM(0, 1)' } else if (value >= 0 && value <= 100) { // 10% 百分比 config += '\n // 可替代上面的 ' + prefix.substring(1) + 'RANDOM_INT(0, 100)' } else { config += '\n // 可替代上面的 ' + prefix.substring(1) + (hasDot != true && value < 10 ? 'ORDER_INT(0, 9)' : ((hasDot ? 'RANDOM_NUM' : 'RANDOM_INT') + '(0, ' + 100 * value + ')')) } } } } else if (typeof value == 'string') { //引用赋值 || 远程函数 || 匹配条件范围 if (key.endsWith('@') || key.endsWith('()') || key.endsWith('{}')) { return config } config += prefix + 'ORDER_IN(undefined, null, ""' + (value == '' ? ')' : ', "' + value + '")') } else { config += prefix + 'ORDER_IN(undefined, null' + (value == null ? ')' : ', ' + JSON.stringify(value) + ')') } } return config }, // 保存配置 saveConfig: function () { this.isConfigShow = this.exTxt.index == 8 switch (this.exTxt.index) { case 0: this.database = CodeUtil.database = this.exTxt.name this.saveCache('', 'database', this.database) doc = null var item = this.accounts[this.currentAccountIndex] item.isLoggedIn = false this.onClickAccount(this.currentAccountIndex, item) break case 1: this.schema = CodeUtil.schema = this.exTxt.name this.saveCache('', 'schema', this.schema) doc = null var item = this.accounts[this.currentAccountIndex] item.isLoggedIn = false this.onClickAccount(this.currentAccountIndex, item) break case 2: this.language = CodeUtil.language = this.exTxt.name this.saveCache('', 'language', this.language) doc = null this.onChange(false) break case 6: this.server = this.exTxt.name this.saveCache('', 'server', this.server) this.logout(true) break case 7: this.types = StringUtil.split(this.exTxt.name) this.saveCache('', 'types', this.types) break case 8: this.getThirdPartyApiList(this.exTxt.name, function (platform, docUrl, listUrl, itemUrl, url_, res, err) { var jsonData = (res || {}).data var isJSONData = jsonData instanceof Object if (isJSONData == false) { //后面是 URL 才存储;是 JSON 数据则不存储 App.thirdParty = thirdParty App.saveCache('', 'thirdParty', App.thirdParty) } const header = App.getHeader(vHeader.value) if (platform == PLATFORM_POSTMAN) { alert('尚未开发 ' + PLATFORM_POSTMAN) } else if (platform == PLATFORM_SWAGGER) { var swaggerCallback = function (url_, res, err) { if (App.isSyncing) { alert('正在同步,请等待完成') return } App.isSyncing = true App.onResponse(url_, res, err) var apis = (res.data || {}).paths if (apis == null) { // || apis.length <= 0) { App.isSyncing = false alert('没有查到 Swagger 文档!请开启跨域代理,并检查 URL 是否正确!') return } App.exTxt.button = '...' App.uploadTotal = 0 // apis.length || 0 App.uploadDoneCount = 0 App.uploadFailCount = 0 var item // var i = 0 for (var url in apis) { item = apis[url] //导致 url 全都是一样的 setTimeout(function () { if (App.uploadSwaggerApi(url, item, 'get') || App.uploadSwaggerApi(url, item, 'post') || App.uploadSwaggerApi(url, item, 'put') || App.uploadSwaggerApi(url, item, 'delete') ) {} // }, 100*i) // i ++ } } if (isJSONData) { swaggerCallback(docUrl, { data: jsonData }, null) } else { App.request(false, REQUEST_TYPE_PARAM, docUrl, {}, header, swaggerCallback) } } else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI) { var isRap = platform == PLATFORM_RAP var itemCallback = function (url, res, err) { try { App.onResponse(url, res, err) } catch (e) {} var data = res.data == null ? null : res.data.data if (isRap) { var modules = data == null ? null : data.modules if (modules != null) { for (var i = 0; i < modules.length; i++) { var it = modules[i] || {} var interfaces = it.interfaces || [] for (var j = 0; j < interfaces.length; j++) { App.uploadRapApi(interfaces[j]) } } } } else { App.uploadYApi(data) } } if (isJSONData) { itemCallback(itemUrl, { data: jsonData }, null) } else { App.request(false, REQUEST_TYPE_PARAM, listUrl, {}, header, function (url_, res, err) { if (App.isSyncing) { alert('正在同步,请等待完成') return } App.isSyncing = true App.onResponse(url_, res, err) var apis = (res.data || {}).data if (apis == null) { // || apis.length <= 0) { App.isSyncing = false alert('没有查到 ' + (isRap ? 'Rap' : 'YApi') + ' 文档!请开启跨域代理,并检查 URL 是否正确!') return } App.exTxt.button = '...' App.uploadTotal = 0 // apis.length || 0 App.uploadDoneCount = 0 App.uploadFailCount = 0 var item for (var url in apis) { item = apis[url] || {} var list = (isRap ? [ { _id: item.id } ] : (item == null ? null : item.list)) || [] for (let i1 = 0; i1 < list.length; i1++) { var listItem1 = list[i1] if (listItem1 == null || listItem1._id == null) { App.log('listItem1 == null || listItem1._id == null >> continue') continue } App.request(false, REQUEST_TYPE_PARAM, itemUrl + '?id=' + listItem1._id, {}, header, itemCallback) } } }) } } else { alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') } return true }) break } }, getThirdPartyApiList: function (thirdParty, listCallback, itemCallback) { this.parseThirdParty(thirdParty, function (platform, jsonData, docUrl, listUrl, itemUrl) { var isJSONData = jsonData instanceof Object const header = App.getHeader(vHeader.value) if (platform == PLATFORM_POSTMAN) { alert('尚未开发 ' + PLATFORM_POSTMAN) } else if (platform == PLATFORM_SWAGGER) { if (isJSONData) { listCallback(platform, docUrl, listUrl, itemUrl, itemUrl, { data: jsonData }, null) } else { App.request(false, REQUEST_TYPE_PARAM, docUrl, {}, header, function (url_, res, err) { if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, url_, res, err)) { return } if (itemCallback != null) { itemCallback(platform, docUrl, listUrl, itemUrl, itemUrl, res, err) } }) } } else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI) { var isRap = platform == PLATFORM_RAP if (isJSONData) { if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, listUrl, {data: [jsonData]}, null)) { return } if (itemCallback != null) { itemCallback(platform, docUrl, listUrl, itemUrl, itemUrl, {data: jsonData}, null) } } else { App.request(false, REQUEST_TYPE_PARAM, listUrl, {}, header, function (url_, res, err) { if (listCallback != null && listCallback(platform, docUrl, listUrl, itemUrl, url_, res, err)) { return } var apis = (res.data || {}).data if (apis == null) { // || apis.length <= 0) { alert('没有查到 ' + (isRap ? 'Rap' : 'YApi') + ' 文档!请开启跨域代理,并检查 URL 是否正确!YApi/Rap/Swagger 网站的 Cookie 必须粘贴到请求头 Request Header 输入框!') return } var item for (var url in apis) { item = apis[url] || {} var list = (isRap ? [ { _id: item.id } ] : (item == null ? null : item.list)) || [] for (let i1 = 0; i1 < list.length; i1++) { var listItem1 = list[i1] if (listItem1 == null || listItem1._id == null) { App.log('listItem1 == null || listItem1._id == null >> continue') continue } // var p = listItem1.path == null ? null : StringUtil.noBlank(listItem1.path).replace(/\/\//g, '/') // if (p == null) { // continue // } App.request(false, REQUEST_TYPE_PARAM, itemUrl + '?id=' + listItem1._id, {}, header, function (url_, res, err) { if (itemCallback != null) { itemCallback(platform, docUrl, listUrl, itemUrl, url_, res, err) } }) } } }) } } else { alert('第三方平台只支持 Postman, Swagger, Rap, YApi !') } }) }, parseThirdParty: function (thirdParty, callback) { var tp = StringUtil.trim(thirdParty) var index = tp.indexOf(' ') var platform = index < 0 ? PLATFORM_SWAGGER : tp.substring(0, index).toUpperCase() var docUrl = index <= 0 ? tp.trim() : tp.substring(index + 1).trim() var jsonData = null try { jsonData = JSON.parse(docUrl) } catch (e) {} var host = this.getBaseUrl() var listUrl = null var itemUrl = null if (platform == PLATFORM_SWAGGER) { if (docUrl == '/') { docUrl += 'v2/api-docs' } if (docUrl.startsWith('/')) { docUrl = host + docUrl } } else if (platform == PLATFORM_RAP || platform == PLATFORM_YAPI) { var isRap = platform == PLATFORM_RAP index = docUrl.indexOf(' ') listUrl = index < 0 ? docUrl + (isRap ? '/repository/joined' : '/api/interface/list_menu') : docUrl.substring(0, index).trim() itemUrl = index < 0 ? docUrl + (isRap ? '/repository/get' : '/api/interface/get') : docUrl.substring(index + 1).trim() if (listUrl.startsWith('/')) { listUrl = host + listUrl } if (itemUrl.startsWith('/')) { itemUrl = host + itemUrl } } callback(platform, jsonData, docUrl, listUrl, itemUrl) }, /**上传 Swagger API * @param url * @param docItem * @param method * @param callback */ uploadSwaggerApi: function(url, docItem, method) { method = method || 'get' var api = docItem == null ? null : docItem[method] if (api == null) { log('postApi', 'api == null >> return') this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount return false } this.uploadTotal ++ var parameters = api.parameters || [] var parameters2 = [] if (parameters != null && parameters.length > 0) { for (var k = 0; k < parameters.length; k++) { var paraItem = parameters[k] || {} var name = paraItem.name || '' if (name == 'mock') { continue } parameters2.push(paraItem) } } return this.uploadThirdPartyApi(method == 'get' ? REQUEST_TYPE_PARAM : REQUEST_TYPE_JSON , api.summary, url, parameters2, api.headers, api.description) }, /**上传 Rap API * @param docItem */ uploadRapApi: function(docItem) { var api = docItem if (api == null) { log('postApi', 'api == null >> return') this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount return false } this.uploadTotal ++ var type switch ((api.summary || {}).requestParamsType || '') { case 'QUERY_PARAMS': type = REQUEST_TYPE_PARAM break case 'BODY_PARAMS': switch ((api.summary || {}).bodyOption || '') { case 'FORM_DATA': type = REQUEST_TYPE_DATA break case 'FORM_URLENCODED': type = REQUEST_TYPE_FORM break // case 'RAW': //JSON default: type = REQUEST_TYPE_JSON break } break default: type = REQUEST_TYPE_JSON break } var header = '' var parameters = api.properties var parameters2 = [] if (parameters != null && parameters.length > 0) { for (var k = 0; k < parameters.length; k++) { var paraItem = parameters[k] || {} var name = paraItem.name || '' if (StringUtil.isEmpty(name, true) || paraItem.scope != 'request') { continue } var val = paraItem.value if (paraItem.pos == 1) { //header header += (k <= 0 ? '' : '\n') + name + ': ' + (val == null ? '' : val) + (StringUtil.isEmpty(paraItem.description, true) ? '' : ' // ' + paraItem.description) continue } //转成和 Swagger 一样的字段及格式 paraItem.type = paraItem.type == 'Number' ? 'integer' : StringUtil.toLowerCase(paraItem.type) paraItem.default = val parameters2.push(paraItem) } } return this.uploadThirdPartyApi(type, api.name, api.url, parameters2, header, api.description) }, /**上传 YApi * @param docItem */ uploadYApi: function(docItem) { var api = docItem if (api == null) { log('postApi', 'api == null >> return') this.exTxt.button = 'All:' + this.uploadTotal + '\nDone:' + this.uploadDoneCount + '\nFail:' + this.uploadFailCount return false } this.uploadTotal++ var headers = api.req_headers || [] var header = '' for (var i = 0; i < headers.length; i ++) { var item = headers[i]; var name = item == null ? null : item.name if (name == null) { continue } header += (i <= 0 ? '' : '\n') + name + ': ' + item.value + (StringUtil.isEmpty(item.description, true) ? '' : ' // ' + item.description) } var typeAndParam = this.parseYApiTypeAndParam(api) return this.uploadThirdPartyApi( typeAndParam.type, api.title, api.path, typeAndParam.param, header , (StringUtil.trim(api.username) + ': ' + StringUtil.trim(api.title) + '\n' + (api.up_time == null ? '' : (typeof api.up_time != 'number' ? api.up_time : new Date(1000*api.up_time).toLocaleString())) + '\nhttp://apijson.cn/yapi/project/1/interface/api/' + api._id + '\n\n' + (StringUtil.isEmpty(api.markdown, true) ? StringUtil.trim(api.description) : api.markdown.trim().replace(/\\_/g, '_'))) , api.username ) }, parseYApiTypeAndParam: function (api) { if (api == null) { return {} } var type var parameters switch (api.req_body_type || '') { case 'form': type = REQUEST_TYPE_FORM parameters = api.req_body_form break case 'data': type = REQUEST_TYPE_DATA parameters = api.req_params break case 'query': type = REQUEST_TYPE_PARAM parameters = api.req_query break default: type = REQUEST_TYPE_JSON parameters = api.req_body_other == null ? null : JSON.parse(api.req_body_other) var params = parameters.properties || {} var required = parameters.required || [] var newParams = [] for (var k in params) { //TODO 递归里面的子项 var item = params[k] item.name = k item.required = required.indexOf(k) >= 0 newParams.push(item) } parameters = newParams break } var parameters2 = [] if (parameters != null && parameters.length > 0) { //过滤掉无效的,避免多拼接 , 导致 req 不是合法 JSON for (var k = 0; k < parameters.length; k++) { var paraItem = parameters[k] || {} var name = paraItem.name || '' if (StringUtil.isEmpty(name, true)) { continue } //转成和 Swagger 一样的字段及格式 paraItem.url = paraItem.path var val = (paraItem.mock || {}).mock if (val == null && type == 'array') { val = [] var it = paraItem.items || {} var v = it == null ? null : (it.mock || {}).mock val.push(v) } paraItem.default = val parameters2.push(paraItem) } } return { type: type, param: parameters2 } }, //上传第三方平台的 API 至 APIAuto uploadThirdPartyApi: function(type, name, url, parameters, header, description, creator) { var req = '{' if (parameters != null && parameters.length > 0) { for (var k = 0; k < parameters.length; k++) { var paraItem = parameters[k] || {} var n = paraItem.name || '' //传进来前已过滤,这里只是避免万一为 null 导致后面崩溃 var t = paraItem.type || '' var val = paraItem.default if (val == undefined) { if (t == 'boolean') { val = 'true' } if (t == 'integer') { val = n == 'pageSize' ? '10' : '1' } else if (t == 'string') { val = '""' } else if (t == 'object') { val = '{}' } else if (t == 'array') { val = '[]' } else { var suffix = n.length >= 3 ? n.substring(n.length - 3).toLowerCase() : null if (suffix == 'dto') { val = '{}' } else { val = 'null' } } } else if (typeof val == 'string' && (StringUtil.isEmpty(t, true) || t == 'string')) { val = '"' + val.replace(/"/g, '\\"') + '"' } else if (val instanceof Object) { val = JSON.stringify(val, null, ' ') } req += '\n "' + n + '": ' + val + (k < parameters.length - 1 ? ',' : '') + ' // ' + (paraItem.required ? '必填。 ' : '') + StringUtil.trim(paraItem.description) } } req += '\n}' if (StringUtil.isEmpty(description, true) == false) { req += '\n\n/**\n\n' + StringUtil.trim(description).replace(/\*\//g, '* /') + '\n\n*/' } var currentAccountId = this.getCurrentAccountId() this.request(true, REQUEST_TYPE_JSON, this.server + '/post', { format: false, 'Document': { 'creator': creator, 'testAccountId': currentAccountId, 'type': type, 'name': StringUtil.get(name), 'url': url, 'request': req, 'header': StringUtil.isEmpty(header, true) ? null : StringUtil.trim(header) }, 'TestRecord': { 'randomId': 0, 'host': this.getBaseUrl(), 'testAccountId': currentAccountId, 'response': '' }, 'tag': 'Document' }, {}, function (url, res, err) { //太卡 App.onResponse(url, res, err) if (res.data != null && res.data.Document != null && res.data.Document.code == CODE_SUCCESS) { App.uploadDoneCount ++ } else { App.uploadFailCount ++ } App.exTxt.button = 'All:' + App.uploadTotal + '\nDone:' + App.uploadDoneCount + '\nFail:' + App.uploadFailCount if (App.uploadDoneCount + App.uploadFailCount >= App.uploadTotal) { alert('导入完成') App.isSyncing = false App.showTestCase(false, false) App.remotes = [] App.showTestCase(true, false) } }) return true }, // 切换主题 switchTheme: function (index) { this.checkedTheme = index localforage.setItem('#theme', index) }, // APIJSON <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< //格式化日期 formatDate: function (date) { if (date == null) { date = new Date() } return date.getFullYear() + '-' + this.fillZero(date.getMonth() + 1) + '-' + this.fillZero(date.getDate()) }, //格式化时间 formatTime: function (date) { if (date == null) { date = new Date() } return this.fillZero(date.getHours()) + ':' + this.fillZero(date.getMinutes()) }, formatDateTime: function (date) { if (date == null) { date = new Date() } return this.formatDate(date) + ' ' + this.formatTime(date) }, //填充0 fillZero: function (num, n) { if (num == null) { num = 0 } if (n == null || n <= 0) { n = 2 } var len = num.toString().length; while(len < n) { num = "0" + num; len++; } return num; }, onClickAccount: function (index, item, callback) { this.isTestCaseShow = false if (this.currentAccountIndex == index) { if (item == null) { if (callback != null) { callback(false, index) } } else { this.setRememberLogin(item.remember) this.account = item.phone this.password = item.password if (item.isLoggedIn) { //logout FIXME 没法自定义退出,浏览器默认根据url来管理session的 this.logout(false, function (url, res, err) { App.onResponse(url, res, err) item.isLoggedIn = false App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) if (callback != null) { callback(false, index, err) } }); } else { //login this.login(false, function (url, res, err) { App.onResponse(url, res, err) var data = res.data || {} var user = data.code == CODE_SUCCESS ? data.user : null if (user == null) { if (callback != null) { callback(false, index, err) } } else { item.name = user.name item.remember = data.remember item.isLoggedIn = true App.accounts[App.currentAccountIndex] = item App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) if (callback != null) { callback(true, index, err) } } }); } } return; } //退出当前账号 var c = this.currentAccountIndex var it = c == null || this.accounts == null ? null : this.accounts[c]; if (it != null) { //切换 BASE_URL后 it = undefined 导致UI操作无法继续 it.isLoggedIn = false //异步导致账号错位 this.onClickAccount(c, this.accounts[c]) } //切换到这个tab this.currentAccountIndex = index //目前还没做到同一标签页下测试账号切换后,session也跟着切换,所以干脆每次切换tab就重新登录 if (item != null) { item.isLoggedIn = false this.onClickAccount(index, item, callback) } else { if (callback != null) { callback(false, index) } } }, removeAccountTab: function () { if (this.accounts.length <= 1) { alert('至少要 1 个测试账号!') return } this.accounts.splice(this.currentAccountIndex, 1) if (this.currentAccountIndex >= this.accounts.length) { this.currentAccountIndex = this.accounts.length - 1 } this.saveCache(this.getBaseUrl(), 'currentAccountIndex', this.currentAccountIndex) this.saveCache(this.getBaseUrl(), 'accounts', this.accounts) }, addAccountTab: function () { this.showLogin(true, false) }, //显示远程的测试用例文档 showTestCase: function (show, isLocal) { this.isTestCaseShow = show this.isLocalShow = isLocal vOutput.value = show ? '' : (output || '') this.showDoc() if (isLocal) { this.testCases = this.locals || [] return } this.testCases = this.remotes || [] if (show) { var testCases = this.testCases var allCount = testCases == null ? 0 : testCases.length if (allCount > 0) { var accountIndex = (this.accounts[this.currentAccountIndex] || {}).isLoggedIn ? this.currentAccountIndex : -1 this.currentAccountIndex = accountIndex //解决 onTestResponse 用 -1 存进去, handleTest 用 currentAccountIndex 取出来为空 var tests = this.tests[String(accountIndex)] || {} if (tests != null && $.isEmptyObject(tests) != true) { for (var i = 0; i < allCount; i++) { var item = testCases[i] if (item == null) { continue } var d = item.Document || {} this.compareResponse(allCount, testCases, i, item, (tests[d.id] || {})[0], false, accountIndex, true) } } return; } this.isTestCaseShow = false var types = this.types var search = StringUtil.isEmpty(this.testCaseSearch, true) ? null : '%' + StringUtil.trim(this.testCaseSearch) + '%' var url = this.server + '/get' var req = { format: false, '[]': { 'count': this.testCaseCount || 100, //200 条测试直接卡死 0, 'page': this.testCasePage || 0, 'Document': { '@order': 'version-,date-', 'userId': this.User.id, 'name$': search, 'url$': search, '@combine': search == null ? null : 'name$,url$', 'type{}': types == null || types.length <= 0 ? null : types }, 'TestRecord': { 'documentId@': '/Document/id', 'userId': this.User.id, 'testAccountId': this.getCurrentAccountId(), 'randomId': 0, '@order': 'date-', '@column': 'id,userId,documentId,duration,minDuration,maxDuration,response' + (this.isMLEnabled ? ',standard' : ''), '@having': this.isMLEnabled ? 'length(standard)>2' : null //用 MySQL 5.6 '@having': this.isMLEnabled ? 'json_length(standard)>0' : null } }, '@role': 'LOGIN' } this.onChange(false) this.request(true, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data if (rpObj != null && rpObj.code === CODE_SUCCESS) { App.isTestCaseShow = true App.isLocalShow = false App.testCases = App.remotes = rpObj['[]'] vOutput.value = show ? '' : (output || '') App.showDoc() //App.onChange(false) } }) } }, //显示远程的随机配置文档 showRandomList: function (show, item, isSub) { this.isRandomEditable = false this.isRandomListShow = show && ! isSub this.isRandomSubListShow = show && isSub if (! isSub) { this.randomSubs = [] } vOutput.value = show ? '' : (output || '') this.showDoc() this.randoms = this.randoms || [] if (show && this.isRandomShow && this.randoms.length <= 0 && item != null && item.id != null) { this.isRandomListShow = false var subSearch = StringUtil.isEmpty(this.randomSubSearch, true) ? null : '%' + StringUtil.trim(this.randomSubSearch) + '%' var search = isSub ? subSearch : (StringUtil.isEmpty(this.randomSearch, true) ? null : '%' + StringUtil.trim(this.randomSearch) + '%') var url = this.server + '/get' var req = { '[]': { 'count': (isSub ? this.randomSubCount : this.randomCount) || 100, 'page': (isSub ? this.randomSubPage : this.randomPage) || 0, 'Random': { 'toId': isSub ? item.id : 0, 'documentId': isSub ? null : item.id, '@order': "date-", 'name$': search }, 'TestRecord': { 'randomId@': '/Random/id', 'testAccountId': this.getCurrentAccountId(), 'host': this.getBaseUrl(), '@order': 'date-' }, '[]': isSub ? null : { 'count': this.randomSubCount || 100, 'page': this.randomSubPage || 0, 'Random': { 'toId@': '[]/Random/id', 'documentId': item.id, '@order': "date-", 'name$': subSearch }, 'TestRecord': { 'randomId@': '/Random/id', 'testAccountId': this.getCurrentAccountId(), 'host': this.getBaseUrl(), '@order': 'date-' } } } } this.onChange(false) this.request(true, REQUEST_TYPE_JSON, url, req, {}, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data if (rpObj != null && rpObj.code === CODE_SUCCESS) { App.isRandomListShow = ! isSub App.isRandomSubListShow = isSub if (isSub) { if (App.currentRandomItem == null) { App.currentRandomItem = {} } App.randomSubs = App.currentRandomItem.subs = App.currentRandomItem['[]'] = rpObj['[]'] } else { App.randoms = rpObj['[]'] } vOutput.value = show ? '' : (output || '') App.showDoc() //App.onChange(false) } }) } }, // 设置文档 showDoc: function () { if (this.setDoc(doc) == false) { this.getDoc(function (d) { App.setDoc(d); }); } }, saveCache: function (url, key, value) { var cache = this.getCache(url); cache[key] = value localStorage.setItem('APIAuto:' + url, JSON.stringify(cache)) }, getCache: function (url, key, defaultValue) { var cache = localStorage.getItem('APIAuto:' + url) try { cache = JSON.parse(cache) } catch(e) { this.log('login this.send >> try { cache = JSON.parse(cache) } catch(e) {\n' + e.message) } cache = cache || {} var val = key == null ? cache : cache[key] return val == null && defaultValue != null ? defaultValue : val }, /**登录确认 */ confirm: function () { switch (this.loginType) { case 'login': this.login(this.isAdminOperation) break case 'register': this.register(this.isAdminOperation) break case 'forget': this.resetPassword(this.isAdminOperation) break } }, showLogin: function (show, isAdmin) { this.isLoginShow = show this.isAdminOperation = isAdmin if (show != true) { return } var user = isAdmin ? this.User : null // add account this.accounts[this.currentAccountIndex] // alert("showLogin isAdmin = " + isAdmin + "; user = \n" + JSON.stringify(user, null, ' ')) if (user == null || StringUtil.isEmpty(user.phone, true)) { user = { phone: '13000082001', password: '123456' } } this.setRememberLogin(user.remember) this.account = user.phone this.password = user.password }, setRememberLogin(remember) { vRemember.checked = remember || false }, getCurrentAccount: function() { return this.accounts == null ? null : this.accounts[this.currentAccountIndex] }, getCurrentAccountId: function() { var a = this.getCurrentAccount() return a != null && a.isLoggedIn ? a.id : null }, /**登录 */ login: function (isAdminOperation, callback) { this.isLoginShow = false this.isEditResponse = false const req = { type: 0, // 登录方式,非必须 0-密码 1-验证码 phone: this.account, password: this.password, version: 1, // 全局默认版本号,非必须 remember: vRemember.checked, format: false, defaults: isAdminOperation ? undefined : { '@database': StringUtil.isEmpty(this.database, true) ? undefined : this.database, '@schema': StringUtil.isEmpty(this.schema, true) ? undefined : this.schema } } if (isAdminOperation) { this.request(isAdminOperation, REQUEST_TYPE_JSON, this.server + '/login', req, {}, function (url, res, err) { if (callback) { callback(url, res, err) return } var rpObj = res.data || {} if (rpObj.code != CODE_SUCCESS) { alert('登录失败,请检查网络后重试。\n' + rpObj.msg + '\n详细信息可在浏览器控制台查看。') App.onResponse(url, res, err) } else { var user = rpObj.user || {} if (user.id > 0) { user.remember = rpObj.remember user.phone = req.phone user.password = req.password App.User = user } //保存User到缓存 App.saveCache(App.server, 'User', user) if (App.currentAccountIndex == null || App.currentAccountIndex < 0) { App.currentAccountIndex = 0 } var item = App.accounts[App.currentAccountIndex] item.isLoggedIn = false App.onClickAccount(App.currentAccountIndex, item) //自动登录测试账号 if (user.id > 0) { App.showTestCase(true, false) } } }) } else { if (callback == null) { var item for (var i in this.accounts) { item = this.accounts[i] if (item != null && req.phone == item.phone) { alert(req.phone + ' 已在测试账号中!') // this.currentAccountIndex = i item.remember = vRemember.checked this.onClickAccount(i, item) return } } } this.showUrl(isAdminOperation, '/login') vInput.value = JSON.stringify(req, null, ' ') this.type = REQUEST_TYPE_JSON this.showTestCase(false, this.isLocalShow) this.onChange(false) this.send(isAdminOperation, function (url, res, err) { if (callback) { callback(url, res, err) return } App.onResponse(url, res, err) //由login按钮触发,不能通过callback回调来实现以下功能 var data = res.data || {} if (data.code == CODE_SUCCESS) { var user = data.user || {} App.accounts.push({ isLoggedIn: true, id: user.id, name: user.name, phone: req.phone, password: req.password, remember: data.remember }) var lastItem = App.accounts[App.currentAccountIndex] if (lastItem != null) { lastItem.isLoggedIn = false } App.currentAccountIndex = App.accounts.length - 1 App.saveCache(App.getBaseUrl(), 'currentAccountIndex', App.currentAccountIndex) App.saveCache(App.getBaseUrl(), 'accounts', App.accounts) } }) } }, /**注册 */ register: function (isAdminOperation) { this.showUrl(isAdminOperation, '/register') vInput.value = JSON.stringify( { Privacy: { phone: this.account, _password: this.password }, User: { name: 'APIJSONUser' }, verify: vVerify.value }, null, ' ') this.showTestCase(false, false) this.onChange(false) this.send(isAdminOperation, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data if (rpObj != null && rpObj.code === CODE_SUCCESS) { alert('注册成功') var privacy = rpObj.Privacy || {} App.account = privacy.phone App.loginType = 'login' } }) }, /**重置密码 */ resetPassword: function (isAdminOperation) { this.showUrl(isAdminOperation, '/put/password') vInput.value = JSON.stringify( { verify: vVerify.value, Privacy: { phone: this.account, _password: this.password } }, null, ' ') this.showTestCase(false, this.isLocalShow) this.onChange(false) this.send(isAdminOperation, function (url, res, err) { App.onResponse(url, res, err) var rpObj = res.data if (rpObj != null && rpObj.code === CODE_SUCCESS) { alert('重置密码成功') var privacy = rpObj.Privacy || {} App.account = privacy.phone App.loginType = 'login' } }) }, /**退出 */ logout: function (isAdminOperation, callback) { this.isEditResponse = false var req = {} if (isAdminOperation) { // alert('logout isAdminOperation this.saveCache(this.server, User, {})') this.delegateId = null this.saveCache(this.server, 'delegateId', null) this.saveCache(this.server, 'User', {}) } // alert('logout isAdminOperation = ' + isAdminOperation + '; url = ' + url) if (isAdminOperation) { this.request(isAdminOperation, REQUEST_TYPE_JSON, this.server + '/logout', req, {}, function (url, res, err) { if (callback) { callback(url, res, err) return } // alert('logout clear admin ') App.clearUser() App.onResponse(url, res, err) App.showTestCase(false, App.isLocalShow) }) } else { this.showUrl(isAdminOperation, '/logout') vInput.value = JSON.stringify(req, null, ' ') this.type = REQUEST_TYPE_JSON this.showTestCase(false, this.isLocalShow) this.onChange(false) this.send(isAdminOperation, callback) } }, /**获取验证码 */ getVerify: function (isAdminOperation) { this.showUrl(isAdminOperation, '/post/verify') var type = this.loginType == 'login' ? 0 : (this.loginType == 'register' ? 1 : 2) vInput.value = JSON.stringify( { type: type, phone: this.account }, null, ' ') this.showTestCase(false, this.isLocalShow) this.onChange(false) this.send(isAdminOperation, function (url, res, err) { App.onResponse(url, res, err) var data = res.data || {} var obj = data.code == CODE_SUCCESS ? data.verify : null var verify = obj == null ? null : obj.verify if (verify != null) { //FIXME isEmpty校验时居然在verify=null! StringUtil.isEmpty(verify, true) == false) { vVerify.value = verify } }) }, clearUser: function () { this.User.id = 0 this.Privacy = {} this.remotes = [] // 导致刚登录成功就马上退出 this.delegateId = null this.saveCache(this.server, 'User', this.User) //应该用lastBaseUrl,baseUrl应随watch输入变化重新获取 // this.saveCache(this.server, 'delegateId', this.delegateId) //应该用lastBaseUrl,baseUrl应随watch输入变化重新获取 }, /**计时回调 */ onHandle: function (before) { this.isDelayShow = false if (inputted != before) { clearTimeout(handler); return; } this.view = 'output'; vComment.value = ''; vWarning.value = ''; // vUrlComment.value = ''; vOutput.value = 'resolving...'; //格式化输入代码 try { try { this.header = this.getHeader(vHeader.value) } catch (e2) { this.isHeaderShow = true vHeader.select() throw new Error(e2.message) } before = StringUtil.trim(before); var afterObj; var after; var code = ''; if (StringUtil.isEmpty(before)) { afterObj = {}; after = ''; } else { before = StringUtil.trim(before); // this.toDoubleJSON(StringUtil.trim(before)); log('onHandle before = \n' + before); var json = isSingle ? this.switchQuote(before) : before; try { afterObj = jsonlint.parse(json); after = JSON.stringify(afterObj, null, " "); before = isSingle ? this.switchQuote(after) : after; } catch (e) { log('main.onHandle', 'try { return jsonlint.parse(before); \n } catch (e) {\n' + e.message) log('main.onHandle', 'return jsonlint.parse(this.removeComment(before));') try { afterObj = JSON5.parse(json); // jsonlint.parse(this.removeComment(before)); after = JSON.stringify(afterObj, null, " "); } catch (e2) { throw new Error('请求 JSON 格式错误!请检查并编辑请求!\n\n如果JSON中有注释,请 手动删除 或 点击左边的 \'/" 按钮 来去掉。\n\n' + e.message + '\n\n' + e2.message) } } //关键词let在IE和Safari上不兼容 if (this.isEditResponse != true) { try { code = this.getCode(after); //必须在before还是用 " 时使用,后面用会因为解析 ' 导致失败 } catch (e) { code = '\n\n\n建议:\n使用其它浏览器,例如 谷歌Chrome、火狐FireFox 或者 微软Edge, 因为这样能自动生成请求代码.' + '\nError:\n' + e.message + '\n\n\n'; } } var selectionStart = vInput.selectionStart var selectionEnd = vInput.selectionEnd vInput.value = before + '\n\n\n ' + ' \n'; //解决遮挡 vInput.selectionStart = selectionStart vInput.selectionEnd = selectionEnd vInput.setSelectionRange(selectionStart, selectionEnd) } vSend.disabled = false; if (this.isEditResponse != true) { vOutput.value = output = '登录后点 ↑ 上方左侧最后图标按钮可查看用例列表,点上方右侧中间图标按钮可上传用例并且添加到列表中 ↑ \nOK,请点左上方 [发送请求] 按钮来测试。[点击这里查看视频教程](https://i.youku.com/i/UNTg1NzI1MjQ4MA==/videos?spm=a2hzp.8244740.0.0)' + code; this.showDoc() } var docKey = this.isEditResponse ? 'TestRecord' : 'Document'; var detail = ((this.currentRemoteItem || {})[docKey] || {}).detail; var extraComment = this.getExtraComment() try { var standardObj = null; try { standardObj = JSON.parse(((this.currentRemoteItem || {})[docKey] || {}).standard); } catch (e3) { log(e3) } var m = this.getMethod(); var w = isSingle || this.isEditResponse ? '' : StringUtil.trim(CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], m, this.database, this.language, this.isEditResponse != true, standardObj, null, true)); var c = isSingle ? '' : StringUtil.trim(CodeUtil.parseComment(after, docObj == null ? null : docObj['[]'], m, this.database, this.language, this.isEditResponse != true, standardObj)); //TODO 统计行数,补全到一致 vInput.value.lineNumbers if (isSingle != true) { if (afterObj.tag == null) { m = m == null ? 'GET' : m.toUpperCase() if (['GETS', 'HEADS', 'POST', 'PUT', 'DELETE'].indexOf(m) >= 0) { w += ' ! 非开放请求必须设置 tag !例如 "tag": "User"' c += ' ! 非开放请求必须设置 tag !例如 "tag": "User"' } } if (StringUtil.isEmpty(detail, true)) { c += extraComment == null ? '' : ('\n\n/*' + extraComment + '\n*/'); } else { c += '\n\n/*' + (extraComment == null ? '' : extraComment + '\n\n') + detail + '\n*/'; } } vWarning.value = w + '\n\n\n ' + ' \n'; //解决遮挡 vComment.value = c + '\n\n\n ' + ' \n'; //解决遮挡 vUrlComment.value = isSingle || StringUtil.isEmpty(this.urlComment, true) ? '' : vUrl.value + CodeUtil.getComment(this.urlComment, false, ' ') + ' - ' + (this.requestVersion > 0 ? 'V' + this.requestVersion : 'V*'); if (! isSingle) { var method = this.getMethod(); // m 已经 toUpperCase 了 var isRestful = ! JSONObject.isAPIJSONPath(method); if (isRestful != true) { method = method.toUpperCase(); } var apiMap = isRestful ? CodeUtil.thirdPartyApiMap : null; var api = apiMap == null ? null : apiMap['/' + method]; var name = api == null ? null : api.name; if (StringUtil.isEmpty(name, true) == false) { this.urlComment = name; vUrlComment.value = vUrl.value + CodeUtil.getComment(this.urlComment, false, ' ') } } onScrollChanged() onURLScrollChanged() } catch (e) { log('onHandle try { vComment.value = CodeUtil.parseComment >> } catch (e) {\n' + e.message); } if (this.isPreviewEnabled) { try { // 去掉前面的 JSON var raw = StringUtil.trim(isSingle ? vInput.value : vComment.value); var start = raw.lastIndexOf('\n\/*') var end = raw.lastIndexOf('\n*\/') var ct = start < 0 || end <= start ? '' : StringUtil.trim(raw.substring(start + '\n\/*'.length, end)) markdownToHTML('```js\n' + (start < 0 || end <= start ? raw : raw.substring(0, start)) + '\n```\n' + (StringUtil.isEmpty(ct, true) ? '' : ct + '\n\n```js\n' + ct + '\n```\n'), true); } catch (e3) { log(e3) } } if (this.isEditResponse) { this.view = 'code'; this.jsoncon = after } } catch(e) { log(e) vSend.disabled = true this.view = 'error' this.error = { msg: e.message } } }, /**输入内容改变 */ onChange: function (delay) { this.setBaseUrl(); inputted = new String(vInput.value); vComment.value = ''; vWarning.value = ''; // vUrlComment.value = ''; clearTimeout(handler); this.isDelayShow = delay; if (delay) { handler = setTimeout(function () { App.onHandle(inputted); }, 2000); } else { this.onHandle(inputted); } }, /**单双引号切换 */ transfer: function () { isSingle = ! isSingle; vInput.value = this.switchQuote(vInput.value); this.isTestCaseShow = false // // 删除注释 <<<<<<<<<<<<<<<<<<<<< // // var input = this.removeComment(vInput.value); // if (vInput.value != input) { // vInput.value = input // } // // // 删除注释 >>>>>>>>>>>>>>>>>>>>> this.onChange(false); }, /**获取显示的请求类型名称 */ getTypeName: function (type) { var ts = this.types var t = type || REQUEST_TYPE_JSON if (ts == null || ts.length <= 1 || (ts.length <= 2 && ts.indexOf(REQUEST_TYPE_PARAM) >= 0 && ts.indexOf(REQUEST_TYPE_GRPC) < 0)) { return t == REQUEST_TYPE_PARAM ? 'GET' : 'POST' } return t }, /**请求类型切换 */ changeType: function () { var count = this.types == null ? 0 : this.types.length if (count > 1) { var index = this.types.indexOf(this.type) index++; this.type = this.types[index % count] CodeUtil.type = this.type; } var url = StringUtil.get(vUrl.value) var index = url.indexOf('?') if (index >= 0) { var paramObj = getRequestFromURL(url.substring(index)) vUrl.value = url.substring(0, index) if (paramObj != null && $.isEmptyObject(paramObj) == false) { var originVal = this.getRequest(vInput.value, {}); var isConflict = false; if ($.isEmptyObject(originVal) == false) { for (var k in paramObj) { if (originVal.hasOwnProperty(k)) { isConflict = true; break; } } } if (isConflict) { vInput.value = JSON.stringify(paramObj, null, ' ') + '\n\n// FIXME 从 URL 上的参数转换过来,需要与下面原来的字段合并为一个 JSON:\n\n' + StringUtil.get(vInput.value) } else { vInput.value = JSON.stringify(Object.assign(originVal, paramObj), null, ' ') } } clearTimeout(handler) //解决 vUrl.value 和 vInput.value 变化导致刷新,而且会把 vInput.value 重置,加上下面 onChange 再刷新就卡死了 } this.onChange(false); }, /** * 删除注释 */ removeComment: function (json) { var reg = /("([^\\\"]*(\\.)?)*")|('([^\\\']*(\\.)?)*')|(\/{2,}.*?(\r|\n))|(\/\*(\n|.)*?\*\/)/g // 正则表达式 try { return new String(json).replace(reg, function(word) { // 去除注释后的文本 return /^\/{2,}/.test(word) || /^\/\*/.test(word) ? "" : word; }) } catch (e) { log('transfer delete comment in json >> catch \n' + e.message); } return json; }, showAndSend: function (branchUrl, req, isAdminOperation, callback) { this.showUrl(isAdminOperation, branchUrl) vInput.value = JSON.stringify(req, null, ' ') this.showTestCase(false, this.isLocalShow) this.onChange(false) this.send(isAdminOperation, callback) }, /**发送请求 */ send: function(isAdminOperation, callback) { if (this.isTestCaseShow) { alert('请先输入请求内容!') return } if (StringUtil.isEmpty(this.host, true)) { if (StringUtil.get(vUrl.value).startsWith('http://') != true && StringUtil.get(vUrl.value).startsWith('https://') != true) { alert('URL 缺少 http:// 或 https:// 前缀,可能不完整或不合法,\n可能使用同域的 Host,很可能访问出错!') } } else { if (StringUtil.get(vUrl.value).indexOf('://') >= 0) { alert('URL Host 已经隐藏(固定) 为 \n' + this.host + ' \n将会自动在前面补全,导致 URL 不合法访问出错!\n如果要改 Host,右上角设置 > 显示(编辑)URL Host') } } this.onHandle(vInput.value) clearTimeout(handler) if (this.isEditResponse) { this.onChange(false) return } var header try { header = this.getHeader(vHeader.value) } catch (e) { // alert(e.message) return } var req = this.getRequest(vInput.value, {}) var url = this.getUrl() vOutput.value = "requesting... \nURL = " + url this.view = 'output'; this.setBaseUrl() this.request(isAdminOperation, this.type, url, req, isAdminOperation ? {} : header, callback) this.locals = this.locals || [] if (this.locals.length >= 1000) { //最多1000条,太多会很卡 this.locals.splice(999, this.locals.length - 999) } var method = this.getMethod() this.locals.unshift({ 'Document': { 'userId': this.User.id, 'name': this.formatDateTime() + ' ' + (this.urlComment || StringUtil.trim(req.tag)), 'type': this.type, 'url': '/' + method, 'request': JSON.stringify(req, null, ' '), 'header': vHeader.value } }) this.saveCache('', 'locals', this.locals) }, //请求 request: function (isAdminOperation, type, url, req, header, callback) { type = type || REQUEST_TYPE_JSON url = StringUtil.noBlank(url) var isDelegate = (isAdminOperation == false && this.isDelegateEnabled) || (isAdminOperation && url.indexOf('://apijson.cn:9090') > 0) if (header != null && header.Cookie != null) { if (isDelegate) { header['Set-Cookie'] = header.Cookie delete header.Cookie } else { document.cookie = header.Cookie } } if (isDelegate && this.delegateId != null && (header == null || header['Apijson-Delegate-Id'] == null)) { if (header == null) { header = {}; } header['Apijson-Delegate-Id'] = this.delegateId } // axios.defaults.withcredentials = true axios({ method: (type == REQUEST_TYPE_PARAM ? 'get' : 'post'), url: (isDelegate ? ( this.server + '/delegate?' + (type == REQUEST_TYPE_GRPC ? '$_type=GRPC&' : '') + (StringUtil.isEmpty(this.delegateId, true) ? '' : '$_delegate_id=' + this.delegateId + '&') + '$_delegate_url=' + encodeURIComponent(url) ) : ( this.isEncodeEnabled ? encodeURI(url) : url ) ), params: (type == REQUEST_TYPE_PARAM || type == REQUEST_TYPE_FORM ? req : null), data: (type == REQUEST_TYPE_JSON || type == REQUEST_TYPE_GRPC ? req : (type == REQUEST_TYPE_DATA ? toFormData(req) : null)), headers: header, //Accept-Encoding(HTTP Header 大小写不敏感,SpringBoot 接收后自动转小写)可能导致 Response 乱码 withCredentials: true, //Cookie 必须要 type == REQUEST_TYPE_JSON // crossDomain: true }) .then(function (res) { res = res || {} if (isDelegate) { var hs = res.headers || {} var delegateId = hs['Apijson-Delegate-Id'] || hs['apijson-delegate-id'] if (delegateId != null && delegateId != App.delegateId) { App.delegateId = delegateId App.saveCache(App.server, 'delegateId', delegateId) } } //any one of then callback throw error will cause it calls then(null) // if ((res.config || {}).method == 'options') { // return // } log('send >> success:\n' + JSON.stringify(res, null, ' ')) //未登录,清空缓存 if (res.data != null && res.data.code == 407) { // alert('request res.data != null && res.data.code == 407 >> isAdminOperation = ' + isAdminOperation) if (isAdminOperation) { // alert('request App.User = {} App.server = ' + App.server) App.clearUser() } else { // alert('request App.accounts[App.currentAccountIndex].isLoggedIn = false ') if (App.accounts[App.currentAccountIndex] != null) { App.accounts[App.currentAccountIndex].isLoggedIn = false } } } if (callback != null) { callback(url, res, null) return } App.onResponse(url, res, null) }) .catch(function (err) { log('send >> error:\n' + err) if (isAdminOperation) { App.delegateId = null } if (callback != null) { callback(url, {}, err) return } App.onResponse(url, {}, err) }) }, /**请求回调 */ onResponse: function (url, res, err) { if (res == null) { res = {} } log('onResponse url = ' + url + '\nerr = ' + err + '\nres = \n' + JSON.stringify(res)) if (err != null) { vOutput.value = "Response:\nurl = " + url + "\nerror = " + err.message; } else { var data = res.data || {} if (isSingle && data.code == CODE_SUCCESS) { //不格式化错误的结果 data = JSONResponse.formatObject(data); } this.jsoncon = JSON.stringify(data, null, ' '); this.view = 'code'; vOutput.value = ''; // 会导致断言用了这个 // if (this.currentRemoteItem == null) { // this.currentRemoteItem = {} // } // if (this.currentRemoteItem.TestRecord == null) { // this.currentRemoteItem.TestRecord = {} // } // this.currentRemoteItem.TestRecord.response = data } }, /**处理复制事件 * @param event */ doOnCopy: function(event) { var target = event.target; var selectionStart = target.selectionStart; var selectionEnd = target.selectionEnd; if (target == vUrl) { try { var contentType = CONTENT_TYPE_MAP[this.type]; var json = this.getRequest(vInput.value) var header = this.getHeader(vHeader.value); var headerStr = ''; if (header != null) { for (var k in header) { var v = header[k]; headerStr += '\n' + k + ': ' + StringUtil.get(v); } } console.log('复制时自动转换:\n' + `Request URL: ` + vUrl.value + ` Request Method: ` + (this.type == REQUEST_TYPE_PARAM ? 'GET' : 'POST') + (StringUtil.isEmpty(contentType, true) ? '' : ` Content-Type: ` + contentType) + (StringUtil.isEmpty(headerStr, true) ? '' : headerStr) + '\n\n' + JSON.stringify(json)); } catch (e) { log(e) } } else if (target == vHeader || target == vRandom) { // key: value 转 { "key": value } if (selectionStart < 0 || selectionStart <= selectionEnd) { try { var selection = selectionStart < 0 ? target.value : StringUtil.get(target.value).substring(selectionStart, selectionEnd); var lines = StringUtil.split(selection, '\n'); var json = {}; for (var i = 0; i < lines.length; i ++) { var l = StringUtil.trim(lines[i]) || ''; if (l.startsWith('//')) { continue; } var ind = l.lastIndexOf(' //'); l = ind < 0 ? l : StringUtil.trim(l.substring(0, ind)); ind = l.indexOf(':'); if (ind >= 0) { var left = target == vHeader ? StringUtil.trim(l.substring(0, ind)) : l.substring(0, ind); json[left] = StringUtil.trim(l.substring(ind + 1)); } } if (Object.keys(json).length > 0) { var txt = JSON.stringify(json) console.log('复制时自动转换:\n' + txt) navigator.clipboard.writeText(selection + '\n\n' + txt); alert('复制内容最后拼接了,控制台 Console 也打印了:\n' + txt); } } catch (e) { log(e) } } } }, /**处理粘贴事件 * @param event */ doOnPaste: function(event) { var paste = (event.clipboardData || window.clipboardData || navigator.clipboard).getData('text'); var target = event.target; var selectionStart = target.selectionStart; var selectionEnd = target.selectionEnd; if (StringUtil.isNotEmpty(paste, true) && (StringUtil.isEmpty(target.value, true) || selectionStart <= 0 && selectionEnd >= StringUtil.get(target.value).length)) { if (target == vUrl) { // TODO 把 Chrome 或 Charles 等抓到的 Response Header 和 Content 自动粘贴到 vUrl, vHeader try { if (paste.trim().indexOf('\n') > 0) { // 解决正常的 URL 都粘贴不了 var contentStart = 0; var lines = StringUtil.split(paste, '\n'); var header = ''; for (var i = 0; i < lines.length; i++) { var l = StringUtil.trim(lines[i]); var ind = l.indexOf(':'); var left = ind < 0 ? '' : StringUtil.trim(l.substring(0, ind)); if (/^[a-zA-Z0-9\- ]+$/g.test(left)) { var lowerKey = left.toLowerCase(); var value = l.substring(ind + 1).trim(); if (lowerKey == 'host') { this.setBaseUrl(value.endsWith(':443') ? 'https://' + value.substring(0, value.length - ':443'.length) : 'http://' + value); event.preventDefault(); } else if (lowerKey == 'request method') { value = value.toUpperCase(); this.type = value == 'GET' ? 'PARAM' : (value == 'POST' ? 'JSON' : value); event.preventDefault(); } else if (lowerKey == 'content-type') { var type = vType.value != 'JSON' ? null : CONTENT_VALUE_TYPE_MAP[value]; if (StringUtil.isEmpty(type, true) != true) { this.type = type; event.preventDefault(); } } else if (lowerKey == 'request url') { vUrl.value = value; event.preventDefault(); } else if (StringUtil.isEmpty(lowerKey, true) || lowerKey.startsWith('accept-') || lowerKey.startsWith('access-control-') || IGNORE_HEADERS.indexOf(lowerKey) >= 0) { // 忽略 } else { header += '\n' + left + ': ' + StringUtil.trim(l.substring(ind + 1)); } contentStart += lines[i].length + 1; } else { if (ind <= 0 || StringUtil.isEmpty(l) || l.startsWith('HTTP/') || l.startsWith('HTTPS/')) { // HTTP/1.1 200 contentStart += lines[i].length + 1; continue; } var ind = l.indexOf(' '); var m = ind < 0 ? '' : StringUtil.trim(l.substring(0, ind)); if (APIJSON_METHODS.indexOf(m.toLowerCase()) >= 0) { // POST /gets HTTP/1.1 contentStart += lines[i].length + 1; var t = m.toUpperCase() this.type = t == 'GET' ? 'PARAM' : (t == 'POST' ? 'JSON' : t); l = l.substring(ind).trim(); ind = l.indexOf(' '); var url = ind < 0 ? l : l.substring(0, ind); if (url.length > 0 && url != '/') { vUrl.value = this.getBaseUrl() + (url.startsWith('/') ? url : '/' + url); } event.preventDefault(); continue; } var content = StringUtil.trim(paste.substring(contentStart)); var json = null; try { json = JSON5.parse(content); // { "a":1, "b": "c" } } catch (e) { log(e) try { json = getRequestFromURL('?' + content); // a=1&b=c } catch (e2) { log(e2) } } vInput.value = json == null || Object.keys(json).length < 0 ? '' : JSON.stringify(json, null, ' '); event.preventDefault(); break; } } if (StringUtil.isEmpty(header, true) != true) { vHeader.value = StringUtil.trim(header); event.preventDefault(); } } } catch (e) { log(e) } } else if (target == vHeader || target == vRandom) { // { "key": value } 转 key: value try { var json = JSON5.parse(paste); var newStr = ''; for (var k in json) { var v = json[k]; if (v instanceof Object || v instanceof Array) { v = JSON.stringify(v); } newStr += '\n' + k + ': ' + (target != vHeader && typeof v == 'string' ? "'" + v.replaceAll("'", "\\'") + "'" : StringUtil.get(v)); } target.value = StringUtil.trim(newStr); event.preventDefault(); } catch (e) { log(e) } } else if (target == vInput) { // key: value 转 { "key": value } try { try { JSON5.parse(paste); // 正常的 JSON 就不用转了 } catch (e) { var lines = StringUtil.split(paste, '\n'); var json = {}; for (var i = 0; i < lines.length; i++) { var l = StringUtil.trim(lines[i]) || ''; if (l.startsWith('//')) { continue; } var ind = l.lastIndexOf(' //'); l = ind < 0 ? l : StringUtil.trim(l.substring(0, ind)); ind = l.indexOf(':'); if (ind >= 0) { var left = target == vHeader ? StringUtil.trim(l.substring(0, ind)) : l.substring(0, ind); if (left.indexOf('=') >= 0 || left.indexOf('&') >= 0) { try { json = getRequestFromURL('?' + paste); if (Object.keys(json).length > 0) { break; } } catch (e2) { log(e) } } json[left] = StringUtil.trim(l.substring(ind + 1)); } } if (Object.keys(json).length <= 0) { json = getRequestFromURL('?' + paste); } if (Object.keys(json).length > 0) { vInput.value = JSON.stringify(json, null, ' '); event.preventDefault(); } } } catch (e) { log(e) } } } }, /**处理按键事件 * @param event */ doOnKeyUp: function (event, type, isFilter, item) { var keyCode = event.keyCode ? event.keyCode : (event.which ? event.which : event.charCode); var obj = event.srcElement ? event.srcElement : event.target; if ($(obj).attr('id') == 'vUrl') { vUrlComment.value = '' this.currentDocItem = null this.currentRemoteItem = null } if (keyCode == 13) { // enter if (isFilter) { this.onFilterChange(type) return } if (type == null) { this.send(false); return } if (type == 'random' || type == 'randomSub') { var r = item == null ? null : item.Random if (r == null || r.id == null) { alert('请选择有效的选项!item.Random.id == null !') return } //修改 Random 的 count this.request(true, REQUEST_TYPE_JSON, this.server + '/put', { Random: { id: r.id, count: r.count, name: r.name }, tag: 'Random' }, {}, function (url, res, err) { var isOk = (res.data || {}).code == CODE_SUCCESS var msg = isOk ? '' : ('\nmsg: ' + StringUtil.get((res.data || {}).msg)) if (err != null) { msg += '\nerr: ' + err.msg } alert('修改' + (isOk ? '成功' : '失败') + '!\ncount: ' + r.count + '\nname: ' + r.name + msg ) App.isRandomEditable = !isOk }) return } } else { if (isFilter) { return } if (type == 'random' || type == 'randomSub') { this.isRandomEditable = true return } if (type == 'document' || type == 'testCase') { return } this.urlComment = ''; this.requestVersion = ''; this.onChange(true); } }, pageDown: function(type) { type = type || '' var page switch (type) { case 'testCase': page = this.testCasePage break case 'random': page = this.randomPage break case 'randomSub': page = this.randomSubPage break default: page = this.page break } if (page == null) { page = 0 } if (page > 0) { page -- switch (type) { case 'testCase': this.testCasePage = page break case 'random': this.randomPage = page break case 'randomSub': this.randomSubPage = page break default: this.page = page break } this.onFilterChange(type) } }, pageUp: function(type) { type = type || '' switch (type) { case 'testCase': this.testCasePage ++ break case 'random': this.randomPage ++ break case 'randomSub': this.randomSubPage ++ break default: this.page ++ break } this.onFilterChange(type) }, onFilterChange: function(type) { type = type || '' switch (type) { case 'testCase': this.saveCache(this.server, 'testCasePage', this.testCasePage) this.saveCache(this.server, 'testCaseCount', this.testCaseCount) this.remotes = null this.showTestCase(true, false) break case 'random': this.saveCache(this.server, 'randomPage', this.randomPage) this.saveCache(this.server, 'randomCount', this.randomCount) this.randoms = null this.showRandomList(true, (this.currentRemoteItem || {}).Document, false) break case 'randomSub': this.saveCache(this.server, 'randomSubPage', this.randomSubPage) this.saveCache(this.server, 'randomSubCount', this.randomSubCount) this.randomSubs = null this.showRandomList(true, (this.currentRemoteItem || {}).Random, true) break default: docObj = null doc = null this.saveCache(this.server, 'page', this.page) this.saveCache(this.server, 'count', this.count) // this.saveCache(this.server, 'docObj', null) // this.saveCache(this.server, 'doc', null) this.onChange(false) //虽然性能更好,但长时间没反应,用户会觉得未生效 // this.getDoc(function (d) { // // vOutput.value = 'resolving...'; // App.setDoc(d) // App.onChange(false) // }); break } }, /**转为请求代码 * @param rq */ getCode: function (rq) { var s = '\n\n\n### 请求代码(自动生成) \n'; switch (this.language) { case CodeUtil.LANGUAGE_KOTLIN: s += '\n#### <= Android-Kotlin: 空对象用 HashMap<String, Any>(),空数组用 ArrayList<Any>()\n' + '```kotlin \n' + CodeUtil.parseKotlinRequest(null, JSON.parse(rq), 0, isSingle, false, false, this.type, this.getBaseUrl(), '/' + this.getMethod(), this.urlComment) + '\n ``` \n注:对象 {} 用 mapOf("key": value),数组 [] 用 listOf(value0, value1)\n'; break; case CodeUtil.LANGUAGE_JAVA: s += '\n#### <= Android-Java: 同名变量需要重命名' + ' \n ```java \n' + StringUtil.trim(CodeUtil.parseJavaRequest(null, JSON.parse(rq), 0, isSingle, false, false, this.type, '/' + this.getMethod(), this.urlComment)) + '\n ``` \n注:' + (isSingle ? '用了 APIJSON 的 JSONRequest, JSONResponse 类,也可使用其它类封装,只要 JSON 有序就行\n' : 'LinkedHashMap<>() 可替换为 fastjson 的 JSONObject(true) 等有序JSON构造方法\n'); var serverCode = CodeUtil.parseJavaServer(this.type, '/' + this.getMethod(), this.database, this.schema, JSON.parse(rq), isSingle); if (StringUtil.isEmpty(serverCode, true) != true) { s += '\n#### <= Server-Java: RESTful 等非 APIJSON 规范的 API' + ' \n ```java \n' + serverCode + '\n ``` \n注:' + (isSingle ? '分页和排序用了 Mybatis-PageHelper,如不需要可在生成代码基础上修改\n' : '使用 SSM(Spring + SpringMVC + Mybatis) 框架 \n'); } break; case CodeUtil.LANGUAGE_C_SHARP: s += '\n#### <= Unity3D-C\#: 键值对用 {"key", value}' + '\n ```csharp \n' + CodeUtil.parseCSharpRequest(null, JSON.parse(rq), 0) + '\n ``` \n注:对象 {} 用 new JObject{{"key", value}},数组 [] 用 new JArray{value0, value1}\n'; break; case CodeUtil.LANGUAGE_SWIFT: s += '\n#### <= iOS-Swift: 空对象用 [ : ]' + '\n ```swift \n' + CodeUtil.parseSwiftRequest(null, JSON.parse(rq), 0) + '\n ``` \n注:对象 {} 用 ["key": value],数组 [] 用 [value0, value1]\n'; break; case CodeUtil.LANGUAGE_OBJECTIVE_C: s += '\n#### <= iOS-Objective-C \n ```objective-c \n' + CodeUtil.parseObjectiveCRequest(null, JSON.parse(rq)) + '\n ``` \n'; break; case CodeUtil.LANGUAGE_GO: s += '\n#### <= Web-Go: 对象 key: value 会被强制排序,每个 key: value 最后都要加逗号 ","' + ' \n ```go \n' + CodeUtil.parseGoRequest(null, JSON.parse(rq), 0) + '\n ``` \n注:对象 {} 用 map[string]interface{} {"key": value},数组 [] 用 []interface{} {value0, value1}\n'; break; case CodeUtil.LANGUAGE_C_PLUS_PLUS: s += '\n#### <= Web-C++: 使用 RapidJSON' + ' \n ```cpp \n' + StringUtil.trim(CodeUtil.parseCppRequest(null, JSON.parse(rq), 0, isSingle)) + '\n ``` \n注:std::string 类型值需要判断 RAPIDJSON_HAS_STDSTRING\n'; break; case CodeUtil.LANGUAGE_PHP: s += '\n#### <= Web-PHP: 空对象用 (object) ' + (isSingle ? '[]' : 'array()') + ' \n ```php \n' + CodeUtil.parsePHPRequest(null, JSON.parse(rq), 0, isSingle) + '\n ``` \n注:对象 {} 用 ' + (isSingle ? '[\'key\' => value]' : 'array("key" => value)') + ',数组 [] 用 ' + (isSingle ? '[value0, value1]\n' : 'array(value0, value1)\n'); break; case CodeUtil.LANGUAGE_PYTHON: s += '\n#### <= Web-Python: 注释符用 \'\#\'' + ' \n ```python \n' + CodeUtil.parsePythonRequest(null, JSON.parse(rq), 0, isSingle, vInput.value) + '\n ``` \n注:关键词转换 null: None, false: False, true: True'; break; //以下都不需要解析,直接用左侧的 JSON case CodeUtil.LANGUAGE_TYPE_SCRIPT: case CodeUtil.LANGUAGE_JAVA_SCRIPT: //case CodeUtil.LANGUAGE_PYTHON: s += '\n#### <= Web-JavaScript/TypeScript: 和左边的请求 JSON 一样 \n'; break; default: s += '\n没有生成代码,可能生成代码(封装,解析)的语言配置错误。\n'; break; } if (((this.User || {}).id || 0) > 0) { s += '\n\n#### 开放源码 ' + '\nAPIJSON 接口测试: https://github.com/TommyLemon/APIAuto ' + '\nAPIJSON 单元测试: https://github.com/TommyLemon/UnitAuto ' + '\nAPIJSON 中文文档: https://github.com/vincentCheng/apijson-doc ' + '\nAPIJSON 英文文档: https://github.com/ruoranw/APIJSONdocs ' + '\nAPIJSON 官方网站: https://github.com/APIJSON/apijson.cn ' + '\nAPIJSON -Java版: https://github.com/Tencent/APIJSON ' + '\nAPIJSON - C# 版: https://github.com/liaozb/APIJSON.NET ' + '\nAPIJSON - Go 版: https://github.com/j2go/apijson-go ' + '\nAPIJSON - PHP版: https://github.com/kvnZero/hyperf-APIJSON ' + '\nAPIJSON -Node版: https://github.com/kevinaskin/apijson-node ' + '\nAPIJSON -Python: https://github.com/zhangchunlin/uliweb-apijson ' + '\n感谢热心的作者们的贡献,GitHub 右上角点 ⭐Star 支持下他们吧 ^_^'; } return s; }, /**显示文档 * @param d **/ setDoc: function (d) { if (d == null) { //解决死循环 || d == '') { return false; } doc = d; vOutput.value += ( '\n\n\n## 文档 \n\n 通用文档见 [APIJSON通用文档](https://github.com/Tencent/APIJSON/blob/master/Document.md#3.2) \n### 数据字典\n自动查数据库表和字段属性来生成 \n\n' + d + '
APIAuto-机器学习 HTTP 接口工具'
+ '
机器学习零代码测试、生成代码与静态检查、生成文档与光标悬浮注释'
+ '
由 APIAuto(前端网页工具), APIJSON(后端接口服务) 等提供技术支持'
+ '
遵循 Apache-2.0 开源协议'
+ '
Copyright © 2016-' + new Date().getFullYear() + ' Tommy Lemon'
+ '
粤ICP备18005508号-1'
+ '