import React, { Component } from 'react' import PropTypes from 'prop-types' import Mock from 'mockjs' import { Button, Input, Select, Alert, Spin, Icon, Collapse, Tooltip, message, AutoComplete, Switch } from 'antd' import { autobind } from 'core-decorators'; import constants from '../../constants/variable.js' import mockEditor from '../../containers/Project/Interface/InterfaceList/mockEditor' import URL from 'url'; const MockExtra = require('common/mock-extra.js') import './Postman.scss'; import json5 from 'json5' import { handleMockWord } from '../../common.js' function json_parse(data) { try { return json5.parse(data) } catch (e) { return data } } function isJsonData(headers) { if (!headers || typeof headers !== 'object') return false; let isResJson = false; Object.keys(headers).map(key => { if (/content-type/i.test(key) && /application\/json/i.test(headers[key])) { isResJson = true; } }) return isResJson; } const wordList = constants.MOCK_SOURCE; const mockDataSource = wordList.map(item => { return {item.mock}   随机{item.name} }); // const { TextArea } = Input; const InputGroup = Input.Group; const Option = Select.Option; const Panel = Collapse.Panel; const HTTP_METHOD = constants.HTTP_METHOD; export default class Run extends Component { static propTypes = { data: PropTypes.object, save: PropTypes.func, saveTip: PropTypes.string, type: PropTypes.string } state = { res: null, resHeader: null, method: 'GET', domains: [], pathname: '', query: [], bodyForm: [], headers: [], caseEnv: '', bodyType: '', bodyOther: '', loading: false, validRes: [], hasPlugin: true, test_status: null, resMockTest: true, resStatusCode: null, resStatusText: '' } constructor(props) { super(props) } componentWillMount() { let startTime = 0; this.interval = setInterval(() => { startTime += 500; if (startTime > 5000) { clearInterval(this.interval); } if (window.crossRequest) { clearInterval(this.interval); this.setState({ hasPlugin: true }) } else { this.setState({ hasPlugin: false }) } }, 500) this.getInterfaceState() } componentWillUnmount() { clearInterval(this.interval) } componentWillReceiveProps(nextProps) { if (nextProps.data._id !== this.props.data._id) { this.getInterfaceState(nextProps) } } componentDidMount() { const { bodyType } = this.state; if (bodyType && bodyType !== 'file' && bodyType !== 'form') { this.loadBodyEditor() } } @autobind getInterfaceState(nextProps) { const props = nextProps || this.props; const { data, type } = props; const { method = '', path: url = '', req_headers = [], req_body_type, req_query = [], req_params = [], req_body_other = '', req_body_form = [], basepath = '', env = [], case_env = '', test_status = '', test_res_body = '', test_report = [], test_res_header = '', mock_verify = true } = data; // case 任意编辑 pathname,不管项目的 basepath const pathname = (type === 'inter' ? (basepath + url) : url).replace(/\/+/g, '/'); // let hasContentType = false; // req_headers.forEach(headerItem => { // // TODO 'Content-Type' 排除大小写不同格式影响 // if (headerItem.name === 'Content-Type'){ // hasContentType = true; // headerItem.value = headerItem.value || 'application/x-www-form-urlencoded'; // } // }) // if (!hasContentType) { // req_headers.push({name: 'Content-Type', value: 'application/x-www-form-urlencoded'}); // } // const domains = env.concat(); // if (domain && !env.find(item => item.domain === domain)) { // domains.push({name: 'default', domain}) // } this.setState({ method, domains: env.concat(), pathParam: req_params.concat(), pathname, query: req_query.concat(), bodyForm: req_body_form.concat(), headers: req_headers.concat(), bodyOther: req_body_other, caseEnv: case_env || (env[0] && env[0].name), bodyType: req_body_type || 'form', loading: false, test_status: test_status, validRes: test_report, res: test_res_body, resHeader: test_res_header, resMockTest: mock_verify }, () => { if (req_body_type && req_body_type !== 'file' && req_body_type !== 'form') { this.loadBodyEditor() } if (test_res_body) { this.bindAceEditor(); } }); } @autobind reqRealInterface() { if (this.state.loading) { this.setState({ loading: false }) return; } const { headers, bodyForm, pathParam, bodyOther, caseEnv, domains, method, pathname, query, bodyType } = this.state; const urlObj = URL.parse(domains.find(item => item.name === caseEnv).domain); let path = pathname pathParam.forEach(item => { path = path.replace(`:${item.name}`, item.value || `:${item.name}`); }); if(urlObj.pathname){ if(urlObj.pathname[urlObj.pathname.length - 1] !== '/'){ urlObj.pathname += '/' } } const href = URL.format({ protocol: urlObj.protocol || 'http', host: urlObj.host, pathname: urlObj.pathname ? URL.resolve(urlObj.pathname, '.' + path) : path, query: this.getQueryObj(query) }); this.setState({ loading: true }) let that = this; window.crossRequest({ url: href, method, headers: this.getHeadersObj(headers), data: bodyType === 'form' ? this.arrToObj(bodyForm) : bodyOther, files: bodyType === 'form' ? this.getFiles(bodyForm) : {}, file: bodyType === 'file' ? 'single-file' : null, success: (res, header, third) => { console.log('suc', third); this.setState({ resStatusCode: third.res.status, resStatusText: third.res.statusText }) try { if (isJsonData(header)) { res = json_parse(res); } const { res_body, res_body_type } = that.props.data; let validRes = []; let query = {}; that.state.query.forEach(item => { query[item.name] = item.value; }) let body = {}; if (that.state.bodyType === 'form') { that.state.bodyForm.forEach(item => { body[item.name] = item.value; }) } else if (that.state.bodyType === 'json') { body = json_parse(that.state.bodyOther); } if (res_body && res_body_type === 'json' && typeof res === 'object') { let tpl = MockExtra(json_parse(res_body), { query: query, body: body }) validRes = Mock.valid(tpl, res) } if (Array.isArray(validRes) && validRes.length > 0) { message.warn('请求完成, 返回数据跟接口定义不匹配'); validRes = validRes.map(item => { return item.message }) that.setState({ res, resHeader: header, validRes, test_status: 'invalid' }) } else if (Array.isArray(validRes) && validRes.length === 0) { message.success('请求完成'); that.setState({ res, resHeader: header, validRes: ['验证通过'], test_status: 'ok' }) } that.setState({ loading: false }) that.bindAceEditor() } catch (e) { console.error(e.message) } }, error: (err, header, third) => { console.log('err', third); this.setState({ resStatusCode: third.res.status, resStatusText: third.res.statusText }) try { err = json_parse(err); } catch (e) { console.log(e) } message.error(err || '请求异常') that.setState({ res: err || '请求失败', resHeader: header, validRes: [], test_status: 'error' }) that.setState({ loading: false }) that.bindAceEditor() } }) } // @autobind // changeDomain(value) { // this.setState({ currDomain: value }); // } @autobind selectDomain(value) { this.setState({ caseEnv: value }); } @autobind changeHeader(v, index, isName) { const headers = json_parse(JSON.stringify(this.state.headers)); if (isName) { headers[index].name = v; } else { headers[index].value = v; } this.setState({ headers }); } @autobind addHeader() { const { headers } = this.state; this.setState({ headers: headers.concat([{ name: '', value: '' }]) }) } @autobind deleteHeader(index) { const { headers } = this.state; this.setState({ headers: headers.filter((item, i) => +index !== +i) }); } @autobind setContentType() { // const headersObj = this.getHeadersObj(this.state.headers); // headersObj['Content-Type'] = type; // this.setState({ headers: this.objToArr(headersObj) }) } @autobind changeQuery(v, index, isKey) { const query = json_parse(JSON.stringify(this.state.query)); if (isKey) { query[index].name = v; } else { query[index].value = v; } this.setState({ query }); } @autobind addQuery() { const { query } = this.state; this.setState({ query: query.concat([{ name: '', value: '' }]) }) } @autobind deleteQuery(index) { const { query } = this.state; this.setState({ query: query.filter((item, i) => +index !== +i) }); } @autobind changePathParam(v, index, isKey) { const pathParam = JSON.parse(JSON.stringify(this.state.pathParam)); const name = pathParam[index].name; let newPathname = this.state.pathname; if (isKey) { if (!name && v) { newPathname += `/:${v}`; } else { newPathname = newPathname.replace(`/:${name}`, v ? `/:${v}` : '') } pathParam[index].name = v; } else { pathParam[index].value = v; } this.setState({ pathParam, pathname: newPathname }); } @autobind addPathParam() { const { pathParam } = this.state; this.setState({ pathParam: pathParam.concat([{ name: '', value: '' }]) }) } @autobind deletePathParam(index) { const { pathParam } = this.state; const name = pathParam[index].name; const newPathname = this.state.pathname.replace(`/:${name}`, ''); this.setState({ pathParam: pathParam.filter((item, i) => +index !== +i), pathname: newPathname }); } @autobind changeBody(v, index) { const bodyForm = json_parse(JSON.stringify(this.state.bodyForm)); if (bodyForm[index].type === 'file') { bodyForm[index].value = 'file_' + index } else { bodyForm[index].value = v } this.setState({ bodyForm }); } @autobind addBody() { const { bodyForm } = this.state; this.setState({ bodyForm: bodyForm.concat([{ name: '', value: '', type: 'text' }]) }) } @autobind deleteBody(index) { const { bodyForm } = this.state; this.setState({ bodyForm: bodyForm.filter((item, i) => +index !== +i) }); } @autobind changeMethod(value) { this.setState({ method: value }); } @autobind changePath(e) { const path = e.target.value; const urlObj = URL.parse(path, true); this.setState({ query: this.objToArr(urlObj.query), pathname: urlObj.pathname }) } @autobind changeBodyType(value) { this.setState({ bodyType: value }, () => { if (value !== 'file' && value !== 'form') { this.loadBodyEditor() } }) } // hasCrossRequestPlugin() { // const dom = document.getElementById('y-request'); // return dom.getAttribute('key') === 'yapi'; // } objToArr(obj, key, value) { const keyName = key || 'name'; const valueName = value || 'value'; const arr = [] Object.keys(obj).forEach((_key) => { if (_key) { arr.push({ [keyName]: _key, [valueName]: obj[_key] }); } }) return arr; } arrToObj(arr) { const obj = {}; arr.forEach(item => { if (item.name && item.type !== 'file') { obj[item.name] = handleMockWord(item.value); } }) return obj; } getFiles(bodyForm) { const files = {}; bodyForm.forEach(item => { if (item.name && item.type === 'file') { files[item.name] = item.value } }) return files; } getQueryObj(query) { const queryObj = {}; query.forEach(item => { if (item.name) { queryObj[item.name] = handleMockWord(item.value); } }) return queryObj; } getHeadersObj(headers) { const headersObj = {}; headers.forEach(item => { if (item.name && item.value) { headersObj[item.name] = item.value; } }) return headersObj; } bindAceEditor = () => { mockEditor({ container: 'res-body-pretty', data: this.state.res, readOnly: true, onChange: function () { } }) mockEditor({ container: 'res-headers-pretty', data: this.state.resHeader, readOnly: true, onChange: function () { } }) } loadBodyEditor = () => { const that = this; setTimeout(function () { mockEditor({ container: 'body-other-edit', data: that.state.bodyOther, onChange: function (d) { if (d.format !== true) return false; that.setState({ bodyOther: d.text }) } }) }, 0); } @autobind fileChange(e, index) { console.log(e) console.log(index) } @autobind onTestSwitched(checked) { this.setState({ resMockTest: checked }); } render() { const { method, domains, pathParam, pathname, query, headers, bodyForm, caseEnv, bodyType, resHeader, loading, validRes } = this.state; HTTP_METHOD[method] = HTTP_METHOD[method] || {} const hasPlugin = this.state.hasPlugin; let isResJson = isJsonData(resHeader); let path = pathname; pathParam.forEach(item => { path = path.replace(`:${item.name}`, item.value || `:${item.name}`); }); const search = decodeURIComponent(URL.format({ query: this.getQueryObj(query) })); let validResView; validResView = validRes.map((item, index) => { return
{item}
}) return (
{hasPlugin ? '' : {/* 温馨提示:当前正在使用接口测试服务,请安装我们为您免费提供的测试增强插件 (该插件可支持任何 chrome 内核的浏览器) */} 重要:当前的接口测试服务,需安装免费测试增强插件 (支持所有 webkit 内核),选择下面任意一种安装方式:
[Google 商店获取(需翻墙)]
[手动下载] zip 文件解压后将 crx 文件拖入到 chrome://extensions/ [详细安装教程]
} type="warning" /> }

请求部分 

{ if (hasPlugin) { return '发送请求' } else { return '请安装cross-request插件' } })()}>
{ pathParam.map((item, index) => { return (
this.changePathParam(e, index, true)} className="key" /> = this.changePathParam(e, index)} className="value" dataSource={mockDataSource} placeholder="参数值" optionLabelProp="value" /> this.deletePathParam(index)} />
) }) }
{ query.map((item, index) => { return (
this.changeQuery(e, index, true)} className="key" /> = this.changeQuery(e, index)} className="value" dataSource={mockDataSource} placeholder="参数值" optionLabelProp="value" /> this.deleteQuery(index)} />
) }) }
{ headers.map((item, index) => { return (
this.changeHeader(e, index, true)} className="key" /> = this.changeHeader(e, index)} className="value" dataSource={mockDataSource} placeholder="参数值" optionLabelProp="value" /> this.deleteHeader(index)} />
) }) }
BODY
} key="3" className={HTTP_METHOD[method].request_body ? 'POST' : 'hidden'} >
{ HTTP_METHOD[method].request_body && bodyType === 'form' &&
{ bodyForm.map((item, index) => { return (
this.changeBody(e, index, 'key')} className="key" /> [ ] = {item.type === 'file' ? this.changeBody(e, index, 'value')} multiple className="value" /> : this.changeBody(e, index, 'value')} className="value" dataSource={mockDataSource} placeholder="参数值" optionLabelProp="value" /> } this.deleteBody(index)} />
) }) }
} { HTTP_METHOD[method].request_body && bodyType === 'file' &&
} {/* method !== 'POST' &&
GET 请求没有 BODY。
*/}

返回结果

= 200 && this.state.resStatusCode < 400 && !this.state.loading) ? 'success' : 'fail')}>{this.state.resStatusCode + ' ' + this.state.resStatusText}

Headers

1

Body

{this.state.res && this.state.res.toString()}

发送请求后在这里查看返回结果。

数据结构验证

{(isResJson && this.state.resMockTest) ? validResView :

若开启此功能,则发送请求后在这里查看验证结果。

数据结构验证在接口编辑页面配置,YApi 将根据 Response body 验证请求返回的结果。

}
) } }