| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- const yapi = require('../yapi.js');
- const projectModel = require('../models/project.js');
- const interfaceModel = require('../models/interface.js');
- const mockExtra = require('../../common/mock-extra.js');
- const { schemaValidator } = require('../../common/utils.js');
- const _ = require('underscore');
- const Mock = require('mockjs');
- const variable = require('../../client/constants/variable.js')
- /**
- *
- * @param {*} apiPath /user/tom
- * @param {*} apiRule /user/:username
- */
- function matchApi(apiPath, apiRule) {
- let apiRules = apiRule.split('/');
- let apiPaths = apiPath.split('/');
- let pathParams = {
- __weight: 0
- };
- if (apiPaths.length !== apiRules.length) {
- return false;
- }
- for (let i = 0; i < apiRules.length; i++) {
- if (apiRules[i]) {
- apiRules[i] = apiRules[i].trim();
- } else {
- continue;
- }
- if (
- apiRules[i].length > 2 &&
- apiRules[i][0] === '{' &&
- apiRules[i][apiRules[i].length - 1] === '}'
- ) {
- pathParams[apiRules[i].substr(1, apiRules[i].length - 2)] = apiPaths[i];
- } else if (apiRules[i].indexOf(':') === 0) {
- pathParams[apiRules[i].substr(1)] = apiPaths[i];
- } else if (
- apiRules[i].length > 2 &&
- apiRules[i].indexOf('{') > -1 &&
- apiRules[i].indexOf('}') > -1
- ) {
- let params = [];
- apiRules[i] = apiRules[i].replace(/\{(.+?)\}/g, function(src, match) {
- params.push(match);
- return '([^\\/\\s]+)';
- });
- apiRules[i] = new RegExp(apiRules[i]);
- if (!apiRules[i].test(apiPaths[i])) {
- return false;
- }
- let matchs = apiPaths[i].match(apiRules[i]);
- params.forEach((item, index) => {
- pathParams[item] = matchs[index + 1];
- });
- } else {
- if (apiRules[i] !== apiPaths[i]) {
- return false;
- }else{
- pathParams.__weight++;
- }
- }
- }
- return pathParams;
- }
- function parseCookie(str) {
- if (!str || typeof str !== 'string') {
- return str;
- }
- if (str.split(';')[0]) {
- let c = str.split(';')[0].split('=');
- return { name: c[0], value: c[1] || '' };
- }
- return null;
- }
- function handleCorsRequest(ctx) {
- let header = ctx.request.header;
- ctx.set('Access-Control-Allow-Origin', header.origin);
- ctx.set('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, HEADER, PATCH, OPTIONS');
- ctx.set('Access-Control-Allow-Headers', header['access-control-request-headers']);
- ctx.set('Access-Control-Allow-Credentials', true);
- ctx.set('Access-Control-Max-Age', 1728000);
- ctx.body = 'ok';
- }
- // 必填字段是否填写好
- function mockValidator(interfaceData, ctx) {
- let i,
- j,
- l,
- len,
- noRequiredArr = [];
- let method = interfaceData.method.toUpperCase() || 'GET';
- // query 判断
- for (i = 0, l = interfaceData.req_query.length; i < l; i++) {
- let curQuery = interfaceData.req_query[i];
- if (curQuery && typeof curQuery === 'object' && curQuery.required === '1') {
- if (!ctx.query[curQuery.name]) {
- noRequiredArr.push(curQuery.name);
- }
- }
- }
- // form 表单判断
- if (variable.HTTP_METHOD[method].request_body && interfaceData.req_body_type === 'form') {
- for (j = 0, len = interfaceData.req_body_form.length; j < len; j++) {
- let curForm = interfaceData.req_body_form[j];
- if (curForm && typeof curForm === 'object' && curForm.required === '1') {
- if (
- ctx.request.body[curForm.name] ||
- (ctx.request.body.fields && ctx.request.body.fields[curForm.name]) ||
- (ctx.request.body.files && ctx.request.body.files[curForm.name])
- ) {
- continue;
- }
- noRequiredArr.push(curForm.name);
- }
- }
- }
- let validResult;
- // json schema 判断
- if (variable.HTTP_METHOD[method].request_body && interfaceData.req_body_type === 'json' && interfaceData.req_body_is_json_schema === true) {
- const schema = yapi.commons.json_parse(interfaceData.req_body_other);
- const params = yapi.commons.json_parse(ctx.request.body);
- validResult = schemaValidator(schema, params);
- }
- if (noRequiredArr.length > 0 || (validResult && !validResult.valid)) {
- let message = `错误信息:`;
- message += noRequiredArr.length > 0 ? `缺少必须字段 ${noRequiredArr.join(',')} ` : '';
- message += validResult && !validResult.valid ? `shema 验证请求参数 ${validResult.message}` : '';
- return {
- valid: false,
- message
- };
- }
- return { valid: true };
- }
- module.exports = async (ctx, next) => {
- // no used variable 'hostname' & 'config'
- // let hostname = ctx.hostname;
- // let config = yapi.WEBCONFIG;
- let path = ctx.path;
- let header = ctx.request.header;
- if (path.indexOf('/mock/') !== 0) {
- if (next) await next();
- return true;
- }
- let paths = path.split('/');
- let projectId = paths[2];
- paths.splice(0, 3);
- path = '/' + paths.join('/');
- ctx.set('Access-Control-Allow-Origin', header.origin);
- ctx.set('Access-Control-Allow-Credentials', true);
- // ctx.set('Access-Control-Allow-Origin', '*');
- if (!projectId) {
- return (ctx.body = yapi.commons.resReturn(null, 400, 'projectId不能为空'));
- }
- let projectInst = yapi.getInst(projectModel),
- project;
- try {
- project = await projectInst.get(projectId);
- } catch (e) {
- return (ctx.body = yapi.commons.resReturn(null, 403, e.message));
- }
- if (!project) {
- return (ctx.body = yapi.commons.resReturn(null, 400, '不存在的项目'));
- }
- let interfaceData, newpath;
- let interfaceInst = yapi.getInst(interfaceModel);
- try {
- newpath = path.substr(project.basepath.length);
- interfaceData = await interfaceInst.getByPath(project._id, newpath, ctx.method);
- let queryPathInterfaceData = await interfaceInst.getByQueryPath(project._id, newpath, ctx.method);
- //处理query_path情况 url 中有 ?params=xxx
- if (!interfaceData || interfaceData.length != queryPathInterfaceData.length) {
- let i,
- l,
- j,
- len,
- curQuery,
- match = false;
- for (i = 0, l = queryPathInterfaceData.length; i < l; i++) {
- match = false;
- let currentInterfaceData = queryPathInterfaceData[i];
- curQuery = currentInterfaceData.query_path;
- if (!curQuery || typeof curQuery !== 'object' || !curQuery.path) {
- continue;
- }
- for (j = 0, len = curQuery.params.length; j < len; j++) {
- if (ctx.query[curQuery.params[j].name] !== curQuery.params[j].value) {
- continue;
- }
- if (j === len - 1) {
- match = true;
- }
- }
- if (match) {
- interfaceData = [currentInterfaceData];
- break;
- }
- // if (i === l - 1) {
- // interfaceData = [];
- // }
- }
- }
- //处理动态路由
- if (!interfaceData || interfaceData.length === 0) {
- let newData = await interfaceInst.getVar(project._id, ctx.method);
- let findInterface;
- let weight = 0;
- _.each(newData, item => {
- let m = matchApi(newpath, item.path);
- if (m !== false) {
- if(m.__weight >= weight){
- findInterface = item;
- }
- delete m.__weight;
- ctx.request.query = Object.assign(m, ctx.request.query);
- return true;
- }
- return false;
- });
- if (!findInterface) {
- //非正常跨域预检请求回应
- if (ctx.method === 'OPTIONS' && ctx.request.header['access-control-request-method']) {
- return handleCorsRequest(ctx);
- }
- return (ctx.body = yapi.commons.resReturn(
- null,
- 404,
- `不存在的api, 当前请求path为 ${newpath}, 请求方法为 ${
- ctx.method
- } ,请确认是否定义此请求。`
- ));
- }
- interfaceData = [await interfaceInst.get(findInterface._id)];
- }
- if (interfaceData.length > 1) {
- return (ctx.body = yapi.commons.resReturn(null, 405, '存在多个api,请检查数据库'));
- } else {
- interfaceData = interfaceData[0];
- }
- // 必填字段是否填写好
- if (project.strice) {
- const validResult = mockValidator(interfaceData, ctx);
- if (!validResult.valid) {
- return (ctx.body = yapi.commons.resReturn(
- null,
- 404,
- `接口字段验证不通过, ${validResult.message}`
- ));
- }
- }
- let res;
- // mock 返回值处理
- res = interfaceData.res_body;
- try {
- if (interfaceData.res_body_type === 'json') {
- if (interfaceData.res_body_is_json_schema === true) {
- //json-schema
- const schema = yapi.commons.json_parse(interfaceData.res_body);
- res = yapi.commons.schemaToJson(schema, {
- alwaysFakeOptionals: true
- });
- } else {
- // console.log('header', ctx.request.header['content-type'].indexOf('multipart/form-data'))
- // 处理 format-data
- if (
- _.isString(ctx.request.header['content-type']) &&
- ctx.request.header['content-type'].indexOf('multipart/form-data') > -1
- ) {
- ctx.request.body = ctx.request.body.fields;
- }
- // console.log('body', ctx.request.body)
- res = mockExtra(yapi.commons.json_parse(interfaceData.res_body), {
- query: ctx.request.query,
- body: ctx.request.body,
- params: Object.assign({}, ctx.request.query, ctx.request.body)
- });
- // console.log('res',res)
- }
- try {
- res = Mock.mock(res);
- } catch (e) {
- console.log('err', e.message);
- yapi.commons.log(e, 'error');
- }
- }
- let context = {
- projectData: project,
- interfaceData: interfaceData,
- ctx: ctx,
- mockJson: res,
- resHeader: {},
- httpCode: 200,
- delay: 0
- };
- if (project.is_mock_open && project.project_mock_script) {
- // 项目层面的mock脚本解析
- let script = project.project_mock_script;
- yapi.commons.handleMockScript(script, context);
- }
- await yapi.emitHook('mock_after', context);
- let handleMock = new Promise(resolve => {
- setTimeout(() => {
- resolve(true);
- }, context.delay);
- });
- await handleMock;
- if (context.resHeader && typeof context.resHeader === 'object') {
- for (let i in context.resHeader) {
- let cookie;
- if (i === 'Set-Cookie') {
- if (context.resHeader[i] && typeof context.resHeader[i] === 'string') {
- cookie = parseCookie(context.resHeader[i]);
- if (cookie && typeof cookie === 'object') {
- ctx.cookies.set(cookie.name, cookie.value, {
- maxAge: 864000000,
- httpOnly: false
- });
- }
- } else if (context.resHeader[i] && Array.isArray(context.resHeader[i])) {
- context.resHeader[i].forEach(item => {
- cookie = parseCookie(item);
- if (cookie && typeof cookie === 'object') {
- ctx.cookies.set(cookie.name, cookie.value, {
- maxAge: 864000000,
- httpOnly: false
- });
- }
- });
- }
- } else {
- ctx.set(i, context.resHeader[i]);
- }
- }
- }
- ctx.status = context.httpCode;
- ctx.body = context.mockJson;
- return;
- } catch (e) {
- yapi.commons.log(e, 'error');
- return (ctx.body = {
- errcode: 400,
- errmsg: '解析出错,请检查。Error: ' + e.message,
- data: null
- });
- }
- } catch (e) {
- yapi.commons.log(e, 'error');
- return (ctx.body = yapi.commons.resReturn(null, 409, e.message));
- }
- };
|