This commit is contained in:
tanghc
2020-11-10 18:18:04 +08:00
parent bf42779369
commit 7f6eee4c07
27 changed files with 685 additions and 83 deletions

View File

@@ -155,10 +155,6 @@ export default {
type: Object,
default: () => {}
},
uri: {
type: String,
default: ''
},
urlProd: {
type: String,
default: ''

View File

@@ -1,31 +1,61 @@
<template>
<div class="doc-debug">
<h2>{{ docInfo.summary }}</h2>
<el-table
:data="[{ methodLabel: '接口名(method)', methodValue: docInfo.name, versionLabel: '版本号(version)', versionValue: docInfo.version }]"
border
:cell-style="baseInfoCellStyle"
:show-header="false"
<el-form
ref="configForm"
size="mini"
:model="configFormData"
:rules="configFormRules"
label-width="120px"
>
<el-table-column prop="methodLabel" align="center" width="130">
<template slot-scope="scope"><span class="api-info">{{ scope.row.methodLabel }}</span></template>
</el-table-column>
<el-table-column prop="methodValue" />
<el-table-column prop="versionLabel" align="center" width="130">
<template slot-scope="scope"><span class="api-info">{{ scope.row.versionLabel }}</span></template>
</el-table-column>
<el-table-column prop="versionValue" width="120" />
</el-table>
<h3>接口描述</h3>
<div class="doc-overview">{{ docInfo.description || docInfo.title }}</div>
<h3>请求方法</h3>
<div class="doc-request-method">
{{ docInfo.httpMethodList && docInfo.httpMethodList.join(' / ').toUpperCase() }}
</div>
<el-form-item prop="url" label="网关地址">
<el-input v-model="configFormData.url" clearable />
</el-form-item>
<el-form-item prop="appKey" label="AppId">
<el-input v-model="configFormData.appKey" clearable />
</el-form-item>
<el-form-item prop="privateKey" label="应用私钥">
<el-input v-model="configFormData.privateKey" clearable @change="onPrivateKeyChange" />
</el-form-item>
<el-form-item label="token">
<el-input v-model="configFormData.token" clearable />
</el-form-item>
</el-form>
<h2>请求参数</h2>
<parameter-table :data="docInfo.requestParameters" :editable="true" />
<parameter-table-edit ref="paramTableRef" :data="docInfo.requestParameters" />
<!-- 多文件选择 -->
<el-upload
v-show="docInfo.multiple"
action=""
:multiple="true"
:auto-upload="false"
style="width: 500px;margin-top: 10px"
:on-remove="(file, fileList) => onSelectMultiFile(file, fileList)"
:on-change="(file, fileList) => onSelectMultiFile(file, fileList)"
>
<el-button slot="trigger" type="primary" size="mini">上传多个文件</el-button>
</el-upload>
<br/>
<el-form
size="mini"
>
<el-form-item label="HttpMethod">
<el-radio-group v-model="httpMethod">
<el-radio v-for="method in docInfo.httpMethodList" :key="method" :label="method">{{ method.toUpperCase() }}</el-radio>
</el-radio-group>
</el-form-item>
</el-form>
<el-button type="primary" @click="send">发送请求</el-button>
<el-tabs v-show="resultShow">
<el-tabs v-model="resultActive" type="card">
<el-tab-pane label="请求信息" name="reqInfo">
<el-input v-model="reqInfo" type="textarea" :rows="10" readonly />
</el-tab-pane>
<el-tab-pane label="请求结果" name="resultContent">
<el-input v-model="resultContent" type="textarea" :rows="16" readonly />
</el-tab-pane>
</el-tabs>
</el-tabs>
</div>
</template>
<style>
@@ -36,20 +66,21 @@
.doc-debug .cell .el-form-item {margin-bottom: 0;}
</style>
<script>
import ParameterTable from '@/components/ParameterTable'
import ParameterTableEdit from '@/components/ParameterTableEdit'
const privateKeyStoreKey = 'sop.sendbox.privateKey'
export default {
name: 'Docdebug',
components: { ParameterTable },
components: { ParameterTableEdit },
props: {
item: {
type: Object,
default: () => {}
},
uri: {
appId: {
type: String,
default: ''
},
urlProd: {
gatewayUrl: {
type: String,
default: ''
}
@@ -63,31 +94,192 @@ export default {
return { padding: '5px 0' }
}
},
configFormRules: {
appKey: [
{ required: true, message: '请填写AppId', trigger: 'blur' }
],
privateKey: [
{ required: true, message: '请填写应用私钥', trigger: 'blur' }
],
url: [
{ required: true, message: '请填写URL', trigger: 'blur' }
]
},
configFormData: {
url: '',
appKey: '',
privateKey: '',
token: ''
},
httpMethod: '',
docInfo: {
summary: '',
name: '',
version: '',
multiple: false,
uploadRequest: false,
httpMethodList: [],
requestParameters: [],
responseParameters: [],
bizCodes: []
}
},
uploadFiles: [],
resultActive: 'resultContent',
resultShow: false,
reqInfo: '',
resultContent: ''
}
},
watch: {
item(newVal) {
this.initItem(newVal)
},
appId(newVal) {
this.configFormData.appKey = newVal
},
gatewayUrl(url) {
this.configFormData.url = url
}
},
created() {
const privateKey = this.getAttr(privateKeyStoreKey)
if (privateKey) {
this.configFormData.privateKey = privateKey
}
},
methods: {
send() {
this.$refs.configForm.validate(valid => {
if (valid) {
// 验证表格参数
const promiseRequestArr = this.validateTable(this.docInfo.requestParameters, ['req_form_example_'])
Promise.all(promiseRequestArr).then(validArr => {
// 到这里来表示全部内容校验通过
this.doSend()
}).catch((e) => {
// this.tipError('请完善表单内容')
}) // 加上这个控制台不会报Uncaught (in promise)
}
})
},
doSend() {
const bizContent = this.buildParamData(this.docInfo.requestParameters)
const data = {
gatewayUrl: this.configFormData.url,
appId: this.configFormData.appKey,
privateKey: this.configFormData.privateKey,
token: this.configFormData.token,
method: this.docInfo.name,
version: this.docInfo.version,
httpMethod: this.httpMethod,
bizContent: JSON.stringify(bizContent)
}
const files = this.buildFiles(this.docInfo.requestParameters)
const isForm = this.httpMethod.toUpperCase() === 'POST'
this.request(this.httpMethod, '/sandbox/test_v2', data, {}, false, isForm, files, function(error, response) {
this.resultShow = true
this.resultActive = 'resultContent'
const status = response.statusCode || response.status
if (!error && status === 200) {
this.successHandler(response)
} else {
console.log(error)
this.$message.error('请求异常,请查看日志')
}
})
},
validateTable: function(arr, refPrefixArr) {
const $refs = this.$refs.paramTableRef.$refs
let promiseArr = []
for (let i = 0; i < arr.length; i++) {
const row = arr[i]
const id = row.id
refPrefixArr.forEach(refPrefix => {
const ref = $refs[refPrefix + id]
if (ref) {
promiseArr.push(ref.validate())
}
})
const children = arr[i].children
if (children && children.length > 0) {
const childrenPromiseArr = this.validateTable(children, refPrefixArr)
promiseArr = promiseArr.concat(childrenPromiseArr)
}
}
return promiseArr
},
successHandler(response) {
this.setReqInfo(response)
this.setRespInfo(response)
},
setReqInfo(response) {
const headers = response.headers
if (headers) {
const html = []
html.push('【请求参数】:' + decodeURIComponent(headers['sendbox-params']))
html.push('【待签名内容】:' + decodeURIComponent(headers['sendbox-beforesign']))
html.push('【签名(sign)】:' + decodeURIComponent(headers['sendbox-sign']))
this.reqInfo = html.join('\r\n')
}
},
setRespInfo(response) {
const headers = response.headers
const targetHeadersString = headers['target-response-headers'] || '{}'
const targetHeaders = JSON.parse(targetHeadersString)
const contentType = targetHeaders['content-type'] || ''
const contentDisposition = targetHeaders['content-disposition'] || ''
if (contentType.indexOf('stream') > -1 || contentDisposition.indexOf('attachment') > -1) {
const filename = this.getDispositionFilename(contentDisposition)
this.downloadFile(filename, response.raw)
} else {
const body = response.body || response.data
this.resultContent = JSON.stringify(body, null, 4)
}
},
downloadFile(filename, buffer) {
const url = window.URL.createObjectURL(new Blob([buffer]))
const link = document.createElement('a')
link.href = url
link.setAttribute('download', filename)
document.body.appendChild(link)
link.click()
},
getDispositionFilename(disposition) {
const dispositionArr = disposition.split(';')
for (let i = 0; i < dispositionArr.length; i++) {
const item = dispositionArr[i].trim()
// filename="xx"
if (item.toLowerCase().startsWith('filename')) {
const result = item.match(new RegExp('filename="(.*?)"', 'i'))
return result ? result[1] : ''
}
}
},
resetResult() {
this.reqInfo = ''
this.resultContent = ''
this.resultShow = false
},
initItem(item) {
this.setData(item)
if (item) {
this.setData(item)
}
},
setData: function(data) {
this.resetResult()
this.docInfo = data
this.httpMethod = this.docInfo.httpMethodList[0]
},
onSelectMultiFile(file, fileList) {
const files = []
fileList.forEach(file => {
const rawFile = file.raw
files.push(rawFile)
})
this.uploadFiles = files
},
onPrivateKeyChange() {
this.setAttr(privateKeyStoreKey, this.configFormData.privateKey)
},
buildParamData: function(params) {
const responseJson = {}
@@ -117,6 +309,22 @@ export default {
return Object.values(responseJson)[0]
}
return responseJson
},
buildFiles(params) {
const files = []
for (let i = 0; i < params.length; i++) {
const row = params[i]
// 处理文件上传
const fileConfig = row.__file__
if (fileConfig) {
files.push(fileConfig)
}
}
// 全局上传
if (this.uploadFiles.length > 0) {
files.push({ name: 'file', files: this.uploadFiles })
}
return files
}
}
}

View File

@@ -50,16 +50,11 @@
label="示例值"
>
<template slot-scope="scope">
<div v-if="editable">
<div v-if="scope.row.type === 'enum'">
{{ (scope.row.enums || []).join('') }}
</div>
<div v-else>
<div v-if="scope.row.type === 'enum'">
{{ (scope.row.enums || []).join('') }}
</div>
<div v-else>
{{ scope.row.paramExample }}
</div>
{{ scope.row.paramExample }}
</div>
</template>
</el-table-column>
@@ -74,10 +69,6 @@ export default {
type: Array,
default: () => []
},
editable: {
type: Boolean,
default: false
},
tree: {
type: Boolean,
default: true

View File

@@ -0,0 +1,121 @@
<template>
<el-table
:data="data"
border
row-key="id"
default-expand-all
:tree-props="{ children: 'refs', hasChildren: 'hasChildren' }"
:cell-style="cellStyleSmall()"
:header-cell-style="headCellStyleSmall()"
empty-text="无参数"
>
<el-table-column
prop="name"
label="名称"
width="250"
>
<template slot-scope="scope">
<span :class="{ 'required': scope.row.required}">{{ scope.row.name }}</span>
</template>
</el-table-column>
<el-table-column
prop="type"
label="类型"
width="100"
>
<template slot-scope="scope">
<span>{{ scope.row.type }}</span>
<span v-show="scope.row.type === 'array' && scope.row.elementType">
<el-tooltip effect="dark" :content="`元素类型:${scope.row.elementType}`" placement="top">
<i class="el-icon-info"></i>
</el-tooltip>
</span>
</template>
</el-table-column>
<el-table-column
prop="paramExample"
label="参数值"
>
<template slot-scope="scope">
<el-form
v-if="scope.row.type !== 'object'"
:ref="'req_form_example_' + scope.row.id"
:model="scope.row"
:rules="buildParamRules(scope.row)"
size="mini"
style="display: inline-block"
>
<el-form-item
prop="paramExample"
label-width="0"
class="table-control"
>
<el-upload
v-if="scope.row.type === 'file' || scope.row.elementType === 'file'"
action=""
:multiple="false"
:auto-upload="false"
:on-change="(file, fileList) => onSelectFile(file, fileList, scope.row)"
:on-remove="(file, fileList) => onSelectFile(file, fileList, scope.row)"
>
<el-button slot="trigger" class="choose-file" type="primary">选择文件</el-button>
</el-upload>
<el-input v-else v-model="scope.row.paramExample" placeholder="参数值" clearable />
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column
prop="description"
label="描述"
/>
</el-table>
</template>
<style>
.table-control .el-form-item__error {
position: inherit;
}
span.required:before {
content: '*';
color: #F56C6C;
margin-right: 4px;
}
</style>
<script>
export default {
name: 'ParameterTableEdit',
props: {
data: {
type: Array,
default: () => []
},
tree: {
type: Boolean,
default: true
}
},
methods: {
buildParamRules(row) {
const rules = []
if (row.required && row.type !== 'file') {
rules.push({ required: true, message: '请填写参数值', trigger: 'blur' })
}
const max = parseInt(row.maxLength)
if (max) {
rules.push({ max: max, message: `长度不超过 ${max} 个字符`, trigger: 'blur' })
}
return {
paramExample: rules
}
},
onSelectFile(f, fileList, row) {
const files = []
fileList.forEach(file => {
const rawFile = file.raw
files.push(rawFile)
})
row.__file__ = { name: row.name, files: files }
}
}
}
</script>

View File

@@ -5,9 +5,6 @@ Vue.use(Router)
/* Layout */
import Layout from '@/layout'
const _import = require('@/router/_import_' + process.env.NODE_ENV)
const menuKey = 'route-menus'
/**
* Note: sub-menus only appear when route children.length >= 1

View File

@@ -55,6 +55,43 @@ Object.assign(Vue.prototype, {
that.doResponse(error, response, callback, errorCallback)
})
},
request(method, uri, data, headers, isJson, isForm, files, callback) {
// 如果是文件上传使用axiosneedle上传文件不完美不支持一个name对应多个文件
if (files && files.length > 0) {
this.doMultipart(uri, data, files, headers, callback)
return
}
const that = this
if (isForm) {
headers['Content-Type'] = 'application/x-www-form-urlencoded'
}
needle.request(method, baseURL + uri, data, {
// 设置header
headers: headers,
json: isJson
}, (error, response) => {
callback.call(that, error, response)
})
},
doMultipart(uri, data, files, headers, callback) {
const that = this
const formData = new FormData()
files.forEach(fileConfig => {
fileConfig.files.forEach(file => {
formData.append(fileConfig.name, file)
})
})
for (const name in data) {
formData.append(name, data[name])
}
client.post(uri, formData, {
headers: headers
}).then(function(response) {
callback.call(that, null, response)
}).catch(function(error) {
callback.call(that, error, null)
})
},
doResponse(error, response, callback, errorCallback) {
// 成功
if (!error && response.statusCode === 200) {
@@ -226,9 +263,6 @@ Object.assign(Vue.prototype, {
goLogin() {
removeToken()
this.$router.replace({ path: `/login` })
setTimeout(function() {
location.reload()
}, 200)
},
goRoute: function(path) {
this.$router.push({ path: path })

View File

@@ -24,12 +24,25 @@
/>
</el-aside>
<el-main style="padding-top: 0">
<doc-view
v-show="item"
:item="item"
:url-prod="docVO.urlProd"
uri="/portal/isv/getDocItem"
/>
<el-tabs>
<el-tabs v-show="item" v-model="active" type="card">
<el-tab-pane name="info">
<span slot="label"><i class="el-icon-document"></i> 接口信息</span>
<doc-view
:item="item"
:url-prod="docVO.urlProd"
/>
</el-tab-pane>
<el-tab-pane name="debug">
<span slot="label"><i class="el-icon-position"></i> 接口调试</span>
<docdebug
:item="item"
:app-id="docVO.appId"
:gateway-url="docVO.gatewayUrl"
/>
</el-tab-pane>
</el-tabs>
</el-tabs>
</el-main>
</el-container>
</div>
@@ -38,13 +51,16 @@
<script>
import 'mavon-editor/dist/css/index.css'
import docView from '@/components/DocView'
import docdebug from '@/components/Docdebug'
export default {
components: { docView },
components: { docView, docdebug },
data() {
return {
active: 'info',
docVO: {
appId: '',
gatewayUrl: '',
urlProd: '',
urlTest: '',
menuProjects: []