├── .gitattributes ├── .gitignore ├── LICENSE ├── README.en.md ├── README.md ├── client ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .postcssrc.js ├── CHANGELOG.md ├── README.md ├── build │ ├── build.js │ ├── check-versions.js │ ├── dev-client.js │ ├── dev-server.js │ ├── utils.js │ ├── vue-loader.conf.js │ ├── webpack.base.conf.js │ ├── webpack.dev.conf.js │ └── webpack.prod.conf.js ├── config │ ├── build_url.js │ ├── dev.env.js │ ├── index.js │ └── prod.env.js ├── index.html ├── package.json ├── src │ ├── App.vue │ ├── assets │ │ ├── logo.png │ │ └── texture.png │ ├── components │ │ ├── Admin.vue │ │ ├── ApiDoc │ │ │ ├── Index.vue │ │ │ ├── MockData.vue │ │ │ ├── Param.vue │ │ │ ├── Params.vue │ │ │ ├── ParamsTable.vue │ │ │ ├── Schema.vue │ │ │ └── Schemas.vue │ │ ├── common │ │ │ ├── CopyField.vue │ │ │ ├── CreateGroup.vue │ │ │ ├── Header │ │ │ │ ├── Search.vue │ │ │ │ └── index.vue │ │ │ ├── InputFinder.vue │ │ │ ├── Layout.vue │ │ │ ├── Pagination.vue │ │ │ ├── QuillEditor.vue │ │ │ ├── SearchInput.vue │ │ │ ├── SearchUser.vue │ │ │ ├── Simditor │ │ │ │ └── index.vue │ │ │ ├── UserSelector.vue │ │ │ ├── charts │ │ │ │ └── Line.vue │ │ │ ├── importJson │ │ │ │ ├── ConfigModel.vue │ │ │ │ ├── Index.vue │ │ │ │ └── Success.vue │ │ │ ├── index.js │ │ │ └── jsonEditor │ │ │ │ └── Index.vue │ │ ├── log │ │ │ └── ChangeLog.vue │ │ └── stat │ │ │ ├── Index.vue │ │ │ └── Mock.vue │ ├── config │ │ └── api.js │ ├── directive │ │ ├── drag.js │ │ ├── index.js │ │ ├── scroll.js │ │ ├── side-bar.js │ │ └── stop-default-enter.js │ ├── filter │ │ └── index.js │ ├── main.js │ ├── model │ │ ├── api.js │ │ ├── param.js │ │ ├── request.js │ │ ├── response.js │ │ └── schema.js │ ├── router │ │ ├── config.js │ │ └── index.js │ ├── store │ │ ├── actions.js │ │ ├── getters.js │ │ ├── index.js │ │ ├── modules │ │ │ └── document.js │ │ ├── mutations.js │ │ └── state.js │ ├── style │ │ ├── index.css │ │ ├── material-icons │ │ │ ├── MaterialIcons-Regular.woff2 │ │ │ └── index.css │ │ └── reset.css │ ├── util │ │ ├── apiInitData.js │ │ ├── buildApiResponse.js │ │ ├── buildSchemaFromExample.js │ │ ├── catchError.js │ │ ├── copyToClickBoard.js │ │ ├── download.js │ │ ├── getUrlAllParams.js │ │ ├── importApiMockerDoc.js │ │ ├── importEasyMockHtml.js │ │ ├── importHar.js │ │ ├── importRap.js │ │ ├── importSwagger.js │ │ ├── importYapiJson.js │ │ ├── index.js │ │ ├── jsonDiff.js │ │ ├── requestHeaders.js │ │ ├── swaggerUtilSet.js │ │ └── validateApi.js │ └── views │ │ ├── auth │ │ ├── DxyLogin.vue │ │ ├── FindPass.vue │ │ ├── Index.vue │ │ ├── Login.vue │ │ ├── Register.vue │ │ └── ResetPass.vue │ │ ├── diff │ │ ├── Api.vue │ │ └── Index.vue │ │ ├── document │ │ ├── ApiContent.vue │ │ ├── ApiList.vue │ │ ├── GroupContent.vue │ │ ├── Index.vue │ │ └── Overview.vue │ │ ├── edit │ │ ├── ApiAuthor.vue │ │ ├── ApiHistory.vue │ │ ├── ApiInfo.vue │ │ ├── Index.vue │ │ └── apiBox │ │ │ ├── DescBox.vue │ │ │ ├── Index.vue │ │ │ ├── PostmanKiller.vue │ │ │ ├── ResultBox.vue │ │ │ ├── SettingField.vue │ │ │ ├── request │ │ │ └── Index.vue │ │ │ ├── response │ │ │ ├── Config.vue │ │ │ ├── Index.vue │ │ │ ├── Status.vue │ │ │ └── StatusSetting.vue │ │ │ └── schema │ │ │ ├── Example.vue │ │ │ ├── Index.vue │ │ │ └── params │ │ │ ├── Index.vue │ │ │ ├── Param.vue │ │ │ ├── ParamFill.vue │ │ │ ├── ParamSet.vue │ │ │ └── Params.vue │ │ ├── list │ │ ├── Content.vue │ │ ├── Index.vue │ │ ├── Search.vue │ │ └── components │ │ │ ├── ApiList.vue │ │ │ ├── GroupList.vue │ │ │ ├── Menu.vue │ │ │ ├── MoveApiDialog.vue │ │ │ ├── MoveDialog.vue │ │ │ └── PageNav.vue │ │ └── manage │ │ ├── Index.vue │ │ ├── Nav.vue │ │ ├── api │ │ ├── ApiAuthority.vue │ │ ├── ApiManage.vue │ │ ├── ApiStatus.vue │ │ └── Index.vue │ │ ├── group │ │ ├── GroupControl.vue │ │ ├── GroupEdit.vue │ │ └── Index.vue │ │ └── profile │ │ ├── Index.vue │ │ ├── Menu.vue │ │ ├── Password.vue │ │ └── UserItem.vue └── static │ ├── .gitkeep │ └── favorite.ico ├── docs ├── authority.md ├── build.sh ├── edit.md ├── faq.md ├── group.md ├── intro.md ├── keymap.md ├── list.md ├── mock.md ├── proxy.md ├── push.md └── readme.md ├── ecosystem.config.js ├── makefile └── server ├── .autod.conf.js ├── .babelrc ├── .eslintignore ├── .eslintrc ├── .gitattributes ├── .gitignore ├── .gitlab-ci.yml ├── .npmrc ├── .travis.yml ├── CHANGELOG.md ├── README.md ├── README.zh-CN.md ├── agent.js ├── app.js ├── app ├── controller │ ├── abstract.js │ ├── api.js │ ├── authority.js │ ├── client.js │ ├── group.js │ ├── history.js │ ├── home.js │ ├── stat.js │ └── user.js ├── middleware │ ├── api_stat.js │ ├── auth.js │ └── credentials.js ├── model │ ├── api.js │ ├── api_authority.js │ ├── api_history.js │ ├── api_stat.js │ ├── group.js │ ├── team.js │ └── user.js ├── public │ ├── api.js │ ├── importOrigin.js │ ├── importSwagger.js │ ├── param.js │ ├── schema.js │ ├── swaggerUtilSet.js │ └── util.js ├── router.js ├── service │ ├── api.js │ ├── api_authority.js │ ├── api_history.js │ ├── cache.js │ ├── cookie.js │ ├── email.js │ ├── group.js │ ├── stat.js │ ├── ticket.js │ └── user.js └── view │ └── index.tpl ├── appveyor.yml ├── config ├── config.default.js ├── config.local.js ├── config.prod.js ├── core.default.js ├── manager.default.js └── plugin.js ├── constants ├── authority.js ├── index.js └── permission.js ├── ecosystem.config.js ├── index.js ├── makefile ├── package.json ├── sql-scripts └── api_stat-add-create_day.js └── test ├── .setup.js └── app └── controller └── api.test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | ~* 3 | dist/ 4 | npm-debug* 5 | db/ 6 | package-lock.json 7 | server/appveyor.yml 8 | server/.travis.yml 9 | -------------------------------------------------------------------------------- /client/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { "modules": false }], 4 | "stage-2" 5 | ], 6 | "plugins": ["transform-runtime"], 7 | "comments": false, 8 | "env": { 9 | "develop": { 10 | "presets": ["env", "stage-2"], 11 | "plugins": [ "istanbul" ] 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /client/.eslintignore: -------------------------------------------------------------------------------- 1 | build/*.js 2 | config/*.js 3 | -------------------------------------------------------------------------------- /client/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // http://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | extends: 'standard', 13 | // required to lint *.vue files 14 | plugins: [ 15 | 'html' 16 | ], 17 | // add your custom rules here 18 | 'rules': { 19 | // 取消promise必须返回error类型 20 | 'prefer-promise-reject-errors': 'off', 21 | // allow debugger during development 22 | 'no-debugger': process.env.NODE_ENV === 'development' ? 0 : 2, 23 | // 'linebreak-style': ['error', process.env.NODE_ENV === 'prod' ? 'unix' : 'windows'], // windows机器eslint换行问题,得加该行 24 | 'arrow-body-style': ['error', 'as-needed'] 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | dist/ 4 | .vscode/ 5 | .idea/ 6 | ~* 7 | npm-debug* 8 | package-lock.json 9 | yarn-error.log 10 | -------------------------------------------------------------------------------- /client/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserlist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /client/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # api-mocker-clent changelog 2 | 3 | ## 1.3.1 4 | 5 | * 升级moment版本,规避moment已知的正则安全漏洞 6 | * 修复接口对比时接口名与接口方法未正确展示 7 | 8 | ## 1.3.2 9 | 10 | * feat: 接口列表跳转默认跳转到文档模式,小图标改为编辑模式 11 | * fix: 接口列表名称太长显示不下 12 | * fix: 文档模式,左侧菜单滚动到当前项 13 | * fix: 接口添加返回状态时,添加复制操作 14 | * fix: 编辑模式保存后 3s 内不能重复保存 15 | * fix: 编辑模式下更便捷的切换到文档模式 16 | 17 | ## 1.3.3 18 | 19 | * feat: 添加了 example 的验证,在创建/更新接口时,example 必须按照 schema 规则书写!!! 20 | * fix: 修改 example 中输入小数问题。 21 | * feat: 添加组管理移交到其他人下。 22 | * fix: 修改 api 权限,选择为指定人员时包含组管理员。 23 | * fix: schema 生成 example 时,切换数据格式生成数据格式不切换的问题。 24 | * fix: get 请求 query 中参数为 number 类型,但非必填,发送空字符串错误问题。 25 | 26 | ## 1.3.4 27 | 28 | * feat: 组管理员可以修改组下所有 api 的设置 29 | * fix: example 的校验去掉了对空数组的校验 30 | * feat: 添加超级管理员 31 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # client 2 | 3 | > api-mocker-client 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For detailed explanation on how things work, checkout the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /client/build/build.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | // process.env.NODE_ENV = 'production' 4 | 5 | var ora = require('ora') 6 | var rm = require('rimraf') 7 | var path = require('path') 8 | var chalk = require('chalk') 9 | var webpack = require('webpack') 10 | var config = require('../config') 11 | var webpackConfig = require('./webpack.prod.conf') 12 | 13 | var spinner = ora('building for production...') 14 | spinner.start() 15 | 16 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 17 | if (err) throw err 18 | webpack(webpackConfig, function (err, stats) { 19 | spinner.stop() 20 | if (err) throw err 21 | process.stdout.write(stats.toString({ 22 | colors: true, 23 | modules: false, 24 | children: false, 25 | chunks: false, 26 | chunkModules: false 27 | }) + '\n\n') 28 | 29 | console.log(chalk.cyan(' Build complete.\n')) 30 | console.log(chalk.yellow( 31 | ' Tip: built files are meant to be served over an HTTP server.\n' + 32 | ' Opening index.html over file:// won\'t work.\n' 33 | )) 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /client/build/check-versions.js: -------------------------------------------------------------------------------- 1 | var chalk = require('chalk') 2 | var semver = require('semver') 3 | var packageConfig = require('../package.json') 4 | 5 | function exec (cmd) { 6 | return require('child_process').execSync(cmd).toString().trim() 7 | } 8 | 9 | var versionRequirements = [ 10 | { 11 | name: 'node', 12 | currentVersion: semver.clean(process.version), 13 | versionRequirement: packageConfig.engines.node 14 | }, 15 | { 16 | name: 'npm', 17 | currentVersion: exec('npm --version'), 18 | versionRequirement: packageConfig.engines.npm 19 | } 20 | ] 21 | 22 | module.exports = function () { 23 | var warnings = [] 24 | for (var i = 0; i < versionRequirements.length; i++) { 25 | var mod = versionRequirements[i] 26 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 27 | warnings.push(mod.name + ': ' + 28 | chalk.red(mod.currentVersion) + ' should be ' + 29 | chalk.green(mod.versionRequirement) 30 | ) 31 | } 32 | } 33 | 34 | if (warnings.length) { 35 | console.log('') 36 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 37 | console.log() 38 | for (var i = 0; i < warnings.length; i++) { 39 | var warning = warnings[i] 40 | console.log(' ' + warning) 41 | } 42 | console.log() 43 | process.exit(1) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /client/build/dev-client.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | require('eventsource-polyfill') 3 | var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true') 4 | 5 | hotClient.subscribe(function (event) { 6 | if (event.action === 'reload') { 7 | window.location.reload() 8 | } 9 | }) 10 | -------------------------------------------------------------------------------- /client/build/dev-server.js: -------------------------------------------------------------------------------- 1 | require('./check-versions')() 2 | 3 | var config = require('../config') 4 | if (!process.env.NODE_ENV) { 5 | process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) 6 | } 7 | 8 | var opn = require('opn') 9 | var path = require('path') 10 | var express = require('express') 11 | var webpack = require('webpack') 12 | var proxyMiddleware = require('http-proxy-middleware') 13 | var webpackConfig = require('./webpack.dev.conf') 14 | 15 | // default port where dev server listens for incoming traffic 16 | var port = process.env.PORT || config.dev.port 17 | // automatically open browser, if not set will be false 18 | var autoOpenBrowser = !!config.dev.autoOpenBrowser 19 | // Define HTTP proxies to your custom API backend 20 | // https://github.com/chimurai/http-proxy-middleware 21 | var proxyTable = config.dev.proxyTable 22 | 23 | var app = express() 24 | var compiler = webpack(webpackConfig) 25 | 26 | var devMiddleware = require('webpack-dev-middleware')(compiler, { 27 | publicPath: webpackConfig.output.publicPath, 28 | quiet: true 29 | }) 30 | 31 | var hotMiddleware = require('webpack-hot-middleware')(compiler, { 32 | log: () => {} 33 | }) 34 | // force page reload when html-webpack-plugin template changes 35 | compiler.plugin('compilation', function (compilation) { 36 | compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { 37 | // hotMiddleware.publish({ action: 'reload' }) 38 | cb() 39 | }) 40 | }) 41 | 42 | // proxy api requests 43 | Object.keys(proxyTable).forEach(function (context) { 44 | var options = proxyTable[context] 45 | if (typeof options === 'string') { 46 | options = { target: options } 47 | } 48 | app.use(proxyMiddleware(options.filter || context, options)) 49 | }) 50 | 51 | // handle fallback for HTML5 history API 52 | app.use(require('connect-history-api-fallback')()) 53 | 54 | // serve webpack bundle output 55 | app.use(devMiddleware) 56 | 57 | // enable hot-reload and state-preserving 58 | // compilation error display 59 | app.use(hotMiddleware) 60 | 61 | // serve pure static assets 62 | var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) 63 | app.use(staticPath, express.static('./static')) 64 | 65 | var uri = 'http://localhost:' + port 66 | 67 | var _resolve 68 | var readyPromise = new Promise(resolve => { 69 | _resolve = resolve 70 | }) 71 | 72 | console.log('> Starting dev server...') 73 | devMiddleware.waitUntilValid(() => { 74 | console.log('> Listening at ' + uri + '\n') 75 | // when env is testing, don't need open it 76 | if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { 77 | opn(uri) 78 | } 79 | _resolve() 80 | }) 81 | 82 | var server = app.listen(port) 83 | 84 | module.exports = { 85 | ready: readyPromise, 86 | close: () => { 87 | server.close() 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /client/build/utils.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var config = require('../config') 3 | var ExtractTextPlugin = require('extract-text-webpack-plugin') 4 | 5 | exports.assetsPath = function (_path) { 6 | var assetsSubDirectory = process.env.NODE_ENV === 'development' 7 | ? config.dev.assetsSubDirectory 8 | : config.build.assetsSubDirectory 9 | return path.posix.join(assetsSubDirectory, _path) 10 | } 11 | 12 | exports.cssLoaders = function (options) { 13 | options = options || {} 14 | 15 | var cssLoader = { 16 | loader: 'css-loader', 17 | options: { 18 | minimize: process.env.NODE_ENV !== 'development', 19 | sourceMap: options.sourceMap 20 | } 21 | } 22 | 23 | // generate loader string to be used with extract text plugin 24 | function generateLoaders (loader, loaderOptions) { 25 | var loaders = [cssLoader] 26 | if (loader) { 27 | loaders.push({ 28 | loader: loader + '-loader', 29 | options: Object.assign({}, loaderOptions, { 30 | sourceMap: options.sourceMap 31 | }) 32 | }) 33 | } 34 | 35 | // Extract CSS when that option is specified 36 | // (which is the case during production build) 37 | if (options.extract) { 38 | return ExtractTextPlugin.extract({ 39 | use: loaders, 40 | fallback: 'vue-style-loader' 41 | }) 42 | } else { 43 | return ['vue-style-loader'].concat(loaders) 44 | } 45 | } 46 | 47 | // http://vuejs.github.io/vue-loader/en/configurations/extract-css.html 48 | return { 49 | css: generateLoaders(), 50 | postcss: generateLoaders(), 51 | less: generateLoaders('less'), 52 | sass: generateLoaders('sass', { indentedSyntax: true }), 53 | scss: generateLoaders('sass'), 54 | stylus: generateLoaders('stylus'), 55 | styl: generateLoaders('stylus') 56 | } 57 | } 58 | 59 | // Generate loaders for standalone style files (outside of .vue) 60 | exports.styleLoaders = function (options) { 61 | var output = [] 62 | var loaders = exports.cssLoaders(options) 63 | for (var extension in loaders) { 64 | var loader = loaders[extension] 65 | output.push({ 66 | test: new RegExp('\\.' + extension + '$'), 67 | use: loader 68 | }) 69 | } 70 | return output 71 | } 72 | -------------------------------------------------------------------------------- /client/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var config = require('../config') 3 | var isProduction = process.env.NODE_ENV === 'production' || process.env.NODE_ENV === 'test' 4 | 5 | module.exports = { 6 | loaders: utils.cssLoaders({ 7 | sourceMap: isProduction 8 | ? config.build.productionSourceMap 9 | : config.dev.cssSourceMap, 10 | extract: isProduction 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /client/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | var path = require('path') 2 | var utils = require('./utils') 3 | var config = require('../config') 4 | var vueLoaderConfig = require('./vue-loader.conf') 5 | 6 | function resolve (dir) { 7 | return path.join(__dirname, '..', dir) 8 | } 9 | 10 | module.exports = { 11 | entry: { 12 | app: './src/main.js' 13 | }, 14 | output: { 15 | path: config.build.assetsRoot, 16 | filename: '[name].js', 17 | publicPath: process.env.NODE_ENV === 'development' ? config.dev.assetsPublicPath : config.build.assetsPublicPath 18 | }, 19 | resolve: { 20 | extensions: ['.js', '.vue', '.json'], 21 | alias: { 22 | 'vue$': 'vue/dist/vue.esm.js', 23 | '@': resolve('src') 24 | } 25 | }, 26 | module: { 27 | rules: [ 28 | { 29 | test: /\.(js|vue)$/, 30 | loader: 'eslint-loader', 31 | enforce: 'pre', 32 | include: [resolve('src'), resolve('test')], 33 | options: { 34 | formatter: require('eslint-friendly-formatter') 35 | } 36 | }, 37 | { 38 | test: /\.vue$/, 39 | loader: 'vue-loader', 40 | options: vueLoaderConfig 41 | }, 42 | { 43 | test: /\.js$/, 44 | loader: 'babel-loader', 45 | include: [resolve('src'), resolve('test')] 46 | }, 47 | { 48 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 49 | loader: 'url-loader', 50 | query: { 51 | limit: 10000, 52 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 53 | } 54 | }, 55 | { 56 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 57 | loader: 'url-loader', 58 | query: { 59 | limit: 10000, 60 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 61 | } 62 | } 63 | ] 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | var utils = require('./utils') 2 | var webpack = require('webpack') 3 | var config = require('../config') 4 | var merge = require('webpack-merge') 5 | var baseWebpackConfig = require('./webpack.base.conf') 6 | var HtmlWebpackPlugin = require('html-webpack-plugin') 7 | var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 8 | var CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin') 9 | 10 | // add hot-reload related code to entry chunks 11 | Object.keys(baseWebpackConfig.entry).forEach(function (name) { 12 | baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name]) 13 | }) 14 | 15 | module.exports = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap }) 18 | }, 19 | // cheap-module-eval-source-map is faster for development 20 | devtool: '#cheap-module-eval-source-map', 21 | plugins: [ 22 | // 作用域提升 23 | new webpack.optimize.ModuleConcatenationPlugin(), 24 | // 避免引入全部的moment locales包 25 | new webpack.ContextReplacementPlugin( 26 | /moment[/\\]locale$/, 27 | /zh-cn/ 28 | ), 29 | // 区分文件大小写 30 | new CaseSensitivePathsPlugin(), 31 | new webpack.DefinePlugin({ 32 | 'process.env': config.dev.env 33 | }), 34 | // https://github.com/glenjamin/webpack-hot-middleware#installation--usage 35 | new webpack.HotModuleReplacementPlugin(), 36 | new webpack.NoEmitOnErrorsPlugin(), 37 | // https://github.com/ampedandwired/html-webpack-plugin 38 | new HtmlWebpackPlugin({ 39 | filename: 'index.html', 40 | template: 'index.html', 41 | inject: true 42 | }), 43 | new FriendlyErrorsPlugin() 44 | ] 45 | }) 46 | -------------------------------------------------------------------------------- /client/config/build_url.js: -------------------------------------------------------------------------------- 1 | var buildUrl = '/' 2 | if(process.env.NODE_ENV === 'test') { 3 | buildUrl += '_develop' 4 | } 5 | 6 | module.exports = buildUrl 7 | -------------------------------------------------------------------------------- /client/config/dev.env.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | NODE_ENV: '"development"' 3 | } 4 | -------------------------------------------------------------------------------- /client/config/index.js: -------------------------------------------------------------------------------- 1 | // see http://vuejs-templates.github.io/webpack for documentation. 2 | var path = require('path') 3 | var buildUrl = require('./build_url') 4 | 5 | module.exports = { 6 | appName: 'DXY API Mocker', 7 | docsUrl: 'https://github.com/DXY-F2E/api-mocker/tree/master/docs#api-mocker', 8 | build: { 9 | env: require('./prod.env'), 10 | index: path.resolve(__dirname, '../dist/index.html'), 11 | assetsRoot: path.resolve(__dirname, '../dist'), 12 | assetsSubDirectory: 'static', 13 | assetsPublicPath: buildUrl, 14 | productionSourceMap: false, 15 | // Gzip off by default as many popular static hosts such as 16 | // Surge or Netlify already gzip all static assets for you. 17 | // Before setting to `true`, make sure to: 18 | // npm install --save-dev compression-webpack-plugin 19 | productionGzip: true, 20 | productionGzipExtensions: ['js', 'css'], 21 | // Run the build command with an extra argument to 22 | // View the bundle analyzer report after build finishes: 23 | // `npm run build --report` 24 | // Set to `true` or `false` to always turn it on or off 25 | bundleAnalyzerReport: process.env.npm_config_report, 26 | // 服务端接口的根路径 27 | serverRoot: 'mock-api' 28 | }, 29 | dev: { 30 | env: require('./dev.env'), 31 | port: 8080, 32 | autoOpenBrowser: true, 33 | assetsSubDirectory: 'static', 34 | assetsPublicPath: '/', 35 | proxyTable: {}, 36 | // CSS Sourcemaps off by default because relative paths are "buggy" 37 | // with this option, according to the CSS-Loader README 38 | // (https://github.com/webpack/css-loader#sourcemaps) 39 | // In our experience, they generally work as expected, 40 | // just be aware of this issue when enabling this option. 41 | cssSourceMap: false, 42 | serverRoot: '127.0.0.1:7001' 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /client/config/prod.env.js: -------------------------------------------------------------------------------- 1 | var isTest = process.env.NODE_ENV === "test" 2 | var isPro = process.env.NODE_ENV === "production" 3 | var NODE_ENV = '' 4 | if (isTest) { 5 | NODE_ENV = '"test"' 6 | }else if(isPro) { 7 | NODE_ENV = '"production"' 8 | } 9 | 10 | module.exports = { 11 | NODE_ENV: NODE_ENV 12 | } 13 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Api Mocker 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "version": "1.3.2", 4 | "description": "api-mocker-client", 5 | "author": "zhangfx", 6 | "license": "GPL-3.0", 7 | "private": true, 8 | "scripts": { 9 | "dev": "NODE_ENV=development node build/dev-server.js", 10 | "build": "NODE_ENV=production node build/build.js", 11 | "lint": "eslint --ext .js,.vue src", 12 | "fix": "eslint --fix --ext .js,.vue src", 13 | "build:test": "NODE_ENV=test node build/build.js" 14 | }, 15 | "dependencies": { 16 | "axios": "0.15.3", 17 | "brace": "0.10.0", 18 | "deepmerge": "^3.1.0", 19 | "echarts": "3.6.1", 20 | "element-ui": "^2.3.9", 21 | "fmp-tti": "^1.1.4", 22 | "fs-extra": "^3.0.1", 23 | "lodash": "^4.17.14", 24 | "mocker-dsl-core": "^0.1.6", 25 | "mockjs": "1.0.1-beta3", 26 | "moment": "2.21.0", 27 | "prismjs": "1.6.0", 28 | "ramda": "0.23.0", 29 | "sha.js": "2.4.9", 30 | "sha1": "1.1.1", 31 | "simditor": "2.3.18", 32 | "vue": "^2.5.2", 33 | "vue-router": "2.5.3", 34 | "vuex": "2.2.1" 35 | }, 36 | "devDependencies": { 37 | "autoprefixer": "^6.7.2", 38 | "babel-core": "^6.22.1", 39 | "babel-eslint": "^7.1.1", 40 | "babel-loader": "^7.1.1", 41 | "babel-plugin-transform-runtime": "^6.22.0", 42 | "babel-preset-env": "^1.2.1", 43 | "babel-preset-stage-2": "^6.22.0", 44 | "babel-register": "^6.22.0", 45 | "case-sensitive-paths-webpack-plugin": "2.1.1", 46 | "chalk": "^1.1.3", 47 | "compression-webpack-plugin": "0.3.2", 48 | "connect-history-api-fallback": "^1.3.0", 49 | "copy-webpack-plugin": "^4.0.1", 50 | "css-loader": "^0.26.1", 51 | "eslint": "^3.14.1", 52 | "eslint-config-standard": "10.2.1", 53 | "eslint-friendly-formatter": "^2.0.7", 54 | "eslint-loader": "^1.6.1", 55 | "eslint-plugin-html": "^2.0.0", 56 | "eslint-plugin-import": "2.7.0", 57 | "eslint-plugin-node": "5.1.1", 58 | "eslint-plugin-promise": "3.5.0", 59 | "eslint-plugin-standard": "3.0.1", 60 | "eventsource-polyfill": "^0.9.6", 61 | "express": "^4.14.1", 62 | "file-loader": "^0.10.0", 63 | "friendly-errors-webpack-plugin": "^1.1.3", 64 | "function-bind": "^1.1.0", 65 | "html-webpack-plugin": "^2.28.0", 66 | "http-proxy-middleware": "^0.17.3", 67 | "less": "2.7.2", 68 | "less-loader": "^4.0.4", 69 | "opn": "^4.0.2", 70 | "optimize-css-assets-webpack-plugin": "^1.3.0", 71 | "ora": "^1.1.0", 72 | "rimraf": "^2.6.0", 73 | "semver": "^5.3.0", 74 | "url-loader": "^0.5.7", 75 | "vue-loader": "^11.1.4", 76 | "vue-style-loader": "^2.0.0", 77 | "vue-template-compiler": "^2.5.2", 78 | "extract-text-webpack-plugin": "^3.0.0", 79 | "webpack": "^3.5.5", 80 | "webpack-bundle-analyzer": "^2.2.1", 81 | "webpack-dev-middleware": "^1.10.0", 82 | "webpack-hot-middleware": "^2.16.1", 83 | "webpack-merge": "^4.1.0" 84 | }, 85 | "engines": { 86 | "node": ">= 4.0.0", 87 | "npm": ">= 3.0.0" 88 | }, 89 | "browserslist": [ 90 | "> 1%", 91 | "last 2 versions", 92 | "not ie <= 8" 93 | ] 94 | } 95 | -------------------------------------------------------------------------------- /client/src/App.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 12 | 13 | 26 | -------------------------------------------------------------------------------- /client/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/src/assets/logo.png -------------------------------------------------------------------------------- /client/src/assets/texture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/src/assets/texture.png -------------------------------------------------------------------------------- /client/src/components/Admin.vue: -------------------------------------------------------------------------------- 1 | 7 | 8 | 53 | 76 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/MockData.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 19 | 21 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/Param.vue: -------------------------------------------------------------------------------- 1 | 17 | 18 | 68 | 82 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/Params.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 79 | 103 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/ParamsTable.vue: -------------------------------------------------------------------------------- 1 | 25 | 26 | 57 | 121 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/Schema.vue: -------------------------------------------------------------------------------- 1 | 29 | 30 | 78 | 117 | -------------------------------------------------------------------------------- /client/src/components/ApiDoc/Schemas.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 51 | -------------------------------------------------------------------------------- /client/src/components/common/CopyField.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 40 | -------------------------------------------------------------------------------- /client/src/components/common/CreateGroup.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 52 | 57 | -------------------------------------------------------------------------------- /client/src/components/common/Header/index.vue: -------------------------------------------------------------------------------- 1 | 37 | 38 | 71 | 110 | -------------------------------------------------------------------------------- /client/src/components/common/InputFinder.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 47 | 84 | -------------------------------------------------------------------------------- /client/src/components/common/Layout.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 17 | 18 | 37 | -------------------------------------------------------------------------------- /client/src/components/common/Pagination.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /client/src/components/common/QuillEditor.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 76 | 88 | -------------------------------------------------------------------------------- /client/src/components/common/SearchInput.vue: -------------------------------------------------------------------------------- 1 | 13 | 52 | -------------------------------------------------------------------------------- /client/src/components/common/SearchUser.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 44 | -------------------------------------------------------------------------------- /client/src/components/common/Simditor/index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 60 | 72 | -------------------------------------------------------------------------------- /client/src/components/common/UserSelector.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 62 | 67 | -------------------------------------------------------------------------------- /client/src/components/common/charts/Line.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 62 | 68 | -------------------------------------------------------------------------------- /client/src/components/common/index.js: -------------------------------------------------------------------------------- 1 | // 注册通用组件到全局 2 | import Vue from 'vue' 3 | import Layout from './Layout.vue' 4 | 5 | Vue.component('Layout', Layout) 6 | -------------------------------------------------------------------------------- /client/src/components/log/ChangeLog.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 66 | 67 | 78 | -------------------------------------------------------------------------------- /client/src/components/stat/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 15 | 38 | -------------------------------------------------------------------------------- /client/src/components/stat/Mock.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 84 | 93 | -------------------------------------------------------------------------------- /client/src/config/api.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda' 2 | import { getDomain } from '../util' 3 | 4 | const domain = getDomain() 5 | 6 | export default R.map((url) => `${domain}${url}`)({ 7 | MOVE_APIS: '/server/api/moveApis', 8 | GROUPS_ALL: '/server/group/all', 9 | GROUPS: '/server/group', 10 | GROUP: '/server/group/:groupId', 11 | QUERY_GROUP: '/server/group/query/:groupId', 12 | APIS: '/server/api', 13 | GROUP_APIS: '/server/api/:groupId', 14 | GROUP_APIS_COPY: '/server/api/copy/:groupId', 15 | GROUP_EXPORT: '/server/group/export/:groupId', 16 | GROUP_FOLLOWER: '/server/group/follower/:groupId', 17 | GROUP_IMPORT_APIS: '/server/group/import/:groupId', 18 | API: '/server/api/:groupId/:apiId', 19 | API_EXPORT: '/server/api/export', 20 | API_HISTORY: '/server/history/api/:apiId', 21 | API_AUTHORITY: '/server/authority/api/:apiId', 22 | API_FOLLOWER: '/server/api/follower/:apiId', 23 | API_STATUS: '/server/api/status/:apiId', 24 | USER: '/auth/user', 25 | USER_SEARCH: '/server/user/search', 26 | USER_FAVORITE: '/server/user/favorites/:groupId', 27 | PROFILE: '/server/user', 28 | STAT: '/server/stat', 29 | LOCAL_DEBUG: '/server/localDebug' 30 | }) 31 | -------------------------------------------------------------------------------- /client/src/directive/drag.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @param {HTMLElement} el - 要放置的 dom 节点 4 | * @param {Function} onChange - 修改完后的回调 5 | */ 6 | function setDragLine (el, onChange) { 7 | if (window.getComputedStyle(el).position === 'static') el.style.position = 'relative' 8 | let line = document.createElement('div') 9 | line.style.position = 'absolute' 10 | line.style.right = '-5px' 11 | line.style.top = 0 12 | line.style.height = '100%' 13 | line.style.width = '10px' 14 | line.style.cursor = 'col-resize' 15 | line.style.backgroundColor = 'transparent' 16 | 17 | line.addEventListener('mousedown', e => { 18 | document.documentElement.style.cursor = 'col-resize' 19 | document.documentElement.style.pointerEvents = 'none' 20 | function onmousemove (e) { 21 | el.style.width = `${e.clientX - el.offsetLeft + 5}px` 22 | } 23 | function onmouseup (e) { 24 | onChange(e.clientX - el.offsetLeft + 5) 25 | document.documentElement.style.cursor = '' 26 | document.documentElement.style.pointerEvents = '' 27 | document.onmousemove = null 28 | document.onmouseup = null 29 | } 30 | document.onmousemove = onmousemove 31 | document.onmouseup = onmouseup 32 | 33 | return false 34 | }) 35 | 36 | el.appendChild(line) 37 | el.addEventListener('scroll', e => { 38 | line.style.top = el.scrollTop + 'px' 39 | }) 40 | } 41 | 42 | export default { 43 | /** 44 | * dom 插入父节点时调用 45 | * @param {HTMLElement} el - 当前 dom 节点 46 | * @param {any} binding - vue 自定义指令绑定的数据 47 | */ 48 | inserted (el, binding) { 49 | let option = binding.value || {} 50 | let { value, onChange } = option 51 | if (value) el.style.width = `${value}px` 52 | setDragLine(el, onChange) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /client/src/directive/index.js: -------------------------------------------------------------------------------- 1 | import sideBar from './side-bar' 2 | import stopDefaultEnter from './stop-default-enter' 3 | import drag from './drag' 4 | import scroll from './scroll' 5 | export default { 6 | install (Vue) { 7 | Vue.directive('sideBar', sideBar) 8 | Vue.directive('stopDefaultEnter', stopDefaultEnter) 9 | Vue.directive('drag', drag) 10 | Vue.directive('scroll', scroll) 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /client/src/directive/scroll.js: -------------------------------------------------------------------------------- 1 | function scrollIntoView (el, isScroll) { 2 | if (!isScroll) return 3 | el.scrollIntoViewIfNeeded() 4 | } 5 | 6 | export default { 7 | /** 8 | * dom 插入父节点时调用 9 | * @param {HTMLElement} el - 当前 dom 节点 10 | * @param {any} binding - vue 自定义指令绑定的数据 11 | */ 12 | inserted (el, binding) { 13 | scrollIntoView(el, binding.value) 14 | }, 15 | /** 16 | * 绑定的数据修改时调用 17 | * @param {HTMLElement} el - 当前 dom 节点 18 | * @param {any} binding - vue 自定义指令绑定的数据 19 | */ 20 | update (el, binding) { 21 | scrollIntoView(el, binding.value) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /client/src/directive/side-bar.js: -------------------------------------------------------------------------------- 1 | // 75-K, 66-B, 默认 按下 ctrl + k + b 隐藏侧边栏 2 | const switchSideBar = (el) => { 3 | if (el.clientWidth) { 4 | el.style.display = 'none' 5 | } else { 6 | el.style.display = 'block' 7 | } 8 | } 9 | const funcMap = {} 10 | const matchLength = (mapKeys, keys) => mapKeys.filter((k, i) => k === keys[i]).length 11 | const matchKey = mapKeys => { 12 | let codes = [] 13 | return e => { 14 | const key = e.keyCode 15 | if ((e.ctrlKey || e.metaKey) && !!mapKeys.find(k => key === k)) { 16 | codes.push(key) 17 | const length = matchLength(mapKeys, codes) 18 | if (length === mapKeys.length) { 19 | codes = [] 20 | return true 21 | } 22 | if (length === codes.length) { 23 | return false 24 | } 25 | codes = [] 26 | return false 27 | } else { 28 | codes = [] 29 | return false 30 | } 31 | } 32 | } 33 | const getEleId = el => el.id || 'defalut' 34 | export default { 35 | inserted (el, { value = [75, 66] }) { 36 | const elId = getEleId(el) 37 | const isMatchKey = matchKey(value) 38 | const onKeydown = el => e => isMatchKey(e) && switchSideBar(el) 39 | funcMap[elId] = onKeydown(el) 40 | document.addEventListener('keydown', funcMap[elId]) 41 | }, 42 | unbind (el) { 43 | const elId = getEleId(el) 44 | document.removeEventListener('keydown', funcMap[elId]) 45 | delete funcMap[elId] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /client/src/directive/stop-default-enter.js: -------------------------------------------------------------------------------- 1 | export default { 2 | bind (el) { 3 | el.onkeydown = e => { 4 | if (e.keyCode === 13) { 5 | return false 6 | } 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /client/src/filter/index.js: -------------------------------------------------------------------------------- 1 | import moment from 'moment' 2 | const filter = {} 3 | const statusMap = { 4 | 1: '正常', 5 | 2: '不再维护', 6 | 3: '已下线' 7 | } 8 | filter.install = (Vue) => { 9 | Vue.filter('dateFormat', (date, format = 'YYYY-MM-DD H:mm:ss') => { 10 | // 若是时间戳字符串则转换为时间戳 11 | const d = isFinite(date) ? Number(date) : date 12 | return moment(d).format(format) 13 | }) 14 | Vue.filter('reverse', (array) => array.reverse()) 15 | Vue.filter('status', (status) => statusMap[status]) 16 | } 17 | export default filter 18 | -------------------------------------------------------------------------------- /client/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import Vuex from 'vuex' 5 | import App from './App' 6 | import store from './store' 7 | import filter from './filter' 8 | import directive from './directive' 9 | import router from './router' 10 | import ElementUI from 'element-ui' 11 | // 引入全局通用组件 12 | import './components/common' 13 | import axios from 'axios' 14 | import '@/style/index.css' 15 | import 'element-ui/lib/theme-chalk/index.css' 16 | 17 | Vue.config.productionTip = false 18 | Vue.use(filter) 19 | Vue.use(directive) 20 | Vue.use(ElementUI, { size: 'medium' }) 21 | Vue.use(Vuex) 22 | Vue.prototype.$http = axios 23 | 24 | if (!window.localStorage.getItem('_sessionId')) { 25 | const s = 'ABCDEFGHIJKLMN' 26 | const a2f = s => f => i => s[f(i)] 27 | const randomChar = length => () => Math.floor(Math.random() * 1000) % length 28 | const randomString = Array.prototype.map.call(s, a2f(s)(randomChar(s.length))).join('') 29 | window.localStorage.setItem('_sessionId', randomString) 30 | } 31 | try { 32 | if (module.hot) { 33 | module.hot.accept(e => { 34 | console.log('HMR ERR', e) 35 | window.location.reload() 36 | }) 37 | } 38 | } catch (e) { 39 | console.log('error', e) 40 | } 41 | /* eslint-disable no-new */ 42 | new Vue({ 43 | el: '#app', 44 | store, 45 | router, 46 | template: '', 47 | components: { 48 | App 49 | } 50 | }) 51 | -------------------------------------------------------------------------------- /client/src/model/api.js: -------------------------------------------------------------------------------- 1 | // import Request from './request' 2 | // import Response from './response' 3 | import Schema from './schema' 4 | /** 接口文档 模型 5 | * 主要要素 6 | * name 文档名称 7 | * proxy 代理信息 8 | * desc 文档描述 9 | * request 请求( 10 | * method 请求方法 11 | * prodUrl 生产环境地址 12 | * devUrl 开发环境地址 13 | * body 请求体 Schema 14 | * query 请求参数 Schema 15 | * headers 请求头 Schema 16 | * ) 17 | * response 响应 ( 18 | * status_code 状态码 19 | * status_text 状态内容 20 | * body 响应体 Schema 21 | * ) 22 | */ 23 | 24 | function initData () { 25 | return { 26 | prodUrl: null, 27 | devUrl: null, 28 | name: '', 29 | group: '', 30 | desc: null, 31 | creator: null, 32 | manager: null, 33 | follower: [], 34 | options: { 35 | proxy: { 36 | mode: 0 37 | }, 38 | response: [new Schema()], 39 | responseIndex: 0, 40 | headers: { 41 | example: null, 42 | params: [] 43 | }, 44 | method: 'get', 45 | delay: 0, 46 | examples: { 47 | query: null, 48 | body: null, 49 | path: null 50 | }, 51 | params: { 52 | query: [], 53 | body: [], 54 | path: [] 55 | } 56 | } 57 | } 58 | } 59 | export default initData 60 | -------------------------------------------------------------------------------- /client/src/model/param.js: -------------------------------------------------------------------------------- 1 | // 参数(属性)列表 模型 2 | class Param { 3 | constructor (initParam = {}) { 4 | const { key = null, type = 'string', required = true, comment = null } = initParam 5 | this.key = key 6 | this.type = type 7 | this.required = required 8 | this.comment = comment 9 | } 10 | } 11 | 12 | export default Param 13 | -------------------------------------------------------------------------------- /client/src/model/request.js: -------------------------------------------------------------------------------- 1 | import Schema from './schema' 2 | /** 接口请求 模型 3 | * method 请求方法, 4 | * prodUrl 生产环境地址 5 | * devUrl 开发环境地址 6 | * body 请求体 7 | * query 请求参数 8 | * headers 请求头 9 | */ 10 | class Request { 11 | constructor (reqDoc) { 12 | const { method, prodUrl, devUrl, body, query, headers } = reqDoc 13 | this.method = method 14 | this.prodUrl = prodUrl 15 | this.devUrl = devUrl 16 | this.body = new Schema(body) 17 | this.query = new Schema(query) 18 | this.headers = new Schema(headers) 19 | } 20 | } 21 | 22 | export default Request 23 | -------------------------------------------------------------------------------- /client/src/model/response.js: -------------------------------------------------------------------------------- 1 | import Schema from './schema' 2 | /** 接口请求 模型 3 | * code 状态码 4 | * text 状态内容 5 | * body 响应体 Schema 6 | */ 7 | class Response { 8 | constructor (resDoc) { 9 | const { code, text, body } = resDoc 10 | this.code = code 11 | this.text = text 12 | this.body = new Schema(body) 13 | } 14 | } 15 | 16 | export default Response 17 | -------------------------------------------------------------------------------- /client/src/model/schema.js: -------------------------------------------------------------------------------- 1 | import Param from './param' 2 | 3 | // 字段结构 模型 4 | class Schema { 5 | constructor (index = 1) { 6 | this.status = 200 7 | this.statusText = `status${index}` 8 | this.example = null 9 | this.params = [new Param()] 10 | } 11 | } 12 | 13 | export default Schema 14 | -------------------------------------------------------------------------------- /client/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import VueRouter from 'vue-router' 3 | import routes from './config' 4 | Vue.use(VueRouter) 5 | 6 | // 路由跳转置顶 7 | const scrollBehavior = (to, from, savedPosition) => { 8 | if (savedPosition) { 9 | return savedPosition 10 | } else { 11 | return { 12 | x: 0, 13 | y: 0 14 | } 15 | } 16 | } 17 | 18 | const router = new VueRouter({ 19 | routes, 20 | scrollBehavior 21 | }) 22 | 23 | export default router 24 | -------------------------------------------------------------------------------- /client/src/store/getters.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/src/store/getters.js -------------------------------------------------------------------------------- /client/src/store/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Vuex from 'vuex' 3 | import actions from './actions' 4 | import mutations from './mutations' 5 | import getters from './getters' 6 | import state from './state' 7 | 8 | import doc from './modules/document' 9 | 10 | Vue.use(Vuex) 11 | 12 | const debug = process.env.NODE_ENV === 'development' 13 | 14 | export default new Vuex.Store({ 15 | modules: { 16 | doc 17 | }, 18 | state, 19 | actions, 20 | mutations, 21 | getters, 22 | strict: debug 23 | }) 24 | -------------------------------------------------------------------------------- /client/src/store/mutations.js: -------------------------------------------------------------------------------- 1 | import R from 'ramda' 2 | import Vue from 'vue' 3 | 4 | const mutations = { 5 | FETCH_GROUPS_SUCCESS (state, groups) { 6 | state.groups = groups 7 | }, 8 | SET_CUR_GROUP (state, group) { 9 | state.curGroup = group 10 | }, 11 | SEARCH_KEYWORD (state, { q }) { 12 | state.search.keyword = q 13 | }, 14 | SEARCH_GROUPS_SUCCESS (state, listData) { 15 | state.search.groupList = listData 16 | }, 17 | SEARCH_APIS_SUCCESS (state, listData) { 18 | state.search.apiList = listData 19 | }, 20 | SEARCH_HISTORY_LIST_ADD (state, keyword) { 21 | let {searchHistoryList} = state.search 22 | if (searchHistoryList.length >= 20) { 23 | searchHistoryList.pop(searchHistoryList.length - 1) 24 | } 25 | searchHistoryList.unshift(keyword) 26 | state.search.searchHistoryList = searchHistoryList 27 | }, 28 | SEARCH_HISTORY_LIST_DELETE (state, index) { 29 | let {searchHistoryList} = state.search 30 | searchHistoryList.splice(index, 1) 31 | state.search.searchHistoryList = searchHistoryList 32 | }, 33 | SET_SEARCH_HISTORY_LIST (state, searchHistoryList) { 34 | state.search.searchHistoryList = searchHistoryList 35 | }, 36 | INSERT_APIS (state, apis) { 37 | state.apiList = apis.concat(state.apiList) 38 | }, 39 | INSERT_API (state, api) { 40 | state.apiList.unshift(api) 41 | }, 42 | FETCH_SUCCESS (state, data) { 43 | state.apiList = data.resources 44 | state.apiPage = data.pages 45 | state.apiListSuccess = true 46 | state.apiListLoading = false 47 | }, 48 | FETCH_BEGIN (state) { 49 | state.apiListLoading = true 50 | }, 51 | FETCH_FAILED (state) { 52 | state.apiListLoading = false 53 | state.apiListSuccess = false 54 | }, 55 | UPDATE_GROUP (state, group) { 56 | const index = R.findIndex(g => g._id === group._id)(state.groups) 57 | Vue.set(state.groups, index, group) 58 | }, 59 | CREATE_GROUP_SUCCESS (state, data) { 60 | state.groups.unshift(data) 61 | }, 62 | SET_GROUP_DETAIL (state, data) { 63 | state.groupDetail = data || {} 64 | }, 65 | GET_GROUP_API (state, data) { 66 | state.apiList = data 67 | }, 68 | UPDATE_API_PAGE (state, data) { 69 | state.apiPage = data 70 | }, 71 | DELETE_API (state, apiIdx) { 72 | state.apiList.splice(apiIdx, 1) 73 | }, 74 | UPDATE_REQ_PARAMS (state, { type, params, value }) { 75 | state.reqParams[type] = { 76 | params, 77 | value 78 | } 79 | }, 80 | SET_USER (state, user) { 81 | state.user = user 82 | }, 83 | UPDATE_PREVIEW_APIS (state, data) { 84 | state.previewApis = data 85 | }, 86 | UPDATE_WINDOW_WIDTH (state, width) { 87 | state.windowWidth = width 88 | }, 89 | SET_ALL_USERS (state, users) { 90 | state.allUsers = users 91 | } 92 | } 93 | export default mutations 94 | -------------------------------------------------------------------------------- /client/src/store/state.js: -------------------------------------------------------------------------------- 1 | import { getDomain } from '@/util' 2 | 3 | const domain = getDomain() 4 | /** 5 | * 全局的状态维护 6 | */ 7 | const state = { 8 | user: null, 9 | // 搜索结果 10 | search: { 11 | keyword: '', 12 | searchHistoryList: [], 13 | groupList: { 14 | resources: [] 15 | }, 16 | apiList: { 17 | resources: [] 18 | } 19 | }, 20 | groups: [], 21 | curGroup: null, 22 | apiList: [], 23 | apiListLoading: false, 24 | apiListSuccess: true, 25 | serverRoot: domain, 26 | windowWidth: 0, 27 | allUsers: [], 28 | groupDetail: {} 29 | } 30 | 31 | export default state 32 | -------------------------------------------------------------------------------- /client/src/style/index.css: -------------------------------------------------------------------------------- 1 | @import './reset.css'; 2 | @import './material-icons/index.css'; 3 | 4 | .line-through { 5 | text-decoration: line-through; 6 | } -------------------------------------------------------------------------------- /client/src/style/material-icons/MaterialIcons-Regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/src/style/material-icons/MaterialIcons-Regular.woff2 -------------------------------------------------------------------------------- /client/src/style/material-icons/index.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Material Icons'; 3 | font-style: normal; 4 | font-weight: 400; 5 | /* For IE6-8 */ 6 | src: local('Material Icons'), 7 | local('MaterialIcons-Regular'), 8 | url(MaterialIcons-Regular.woff2) format('woff2'); 9 | } 10 | 11 | .material-icons { 12 | font-family: 'Material Icons'; 13 | font-weight: normal; 14 | font-style: normal; 15 | font-size: 24px; 16 | /* Preferred icon size */ 17 | display: inline-block; 18 | line-height: 1; 19 | text-transform: none; 20 | letter-spacing: normal; 21 | word-wrap: normal; 22 | white-space: nowrap; 23 | direction: ltr; 24 | 25 | /* Support for all WebKit browsers. */ 26 | -webkit-font-smoothing: antialiased; 27 | /* Support for Safari and Chrome. */ 28 | text-rendering: optimizeLegibility; 29 | 30 | /* Support for Firefox. */ 31 | -moz-osx-font-smoothing: grayscale; 32 | 33 | /* Support for IE. */ 34 | font-feature-settings: 'liga'; 35 | } 36 | -------------------------------------------------------------------------------- /client/src/style/reset.css: -------------------------------------------------------------------------------- 1 | html, 2 | body, 3 | div, 4 | span, 5 | applet, 6 | object, 7 | iframe, 8 | h1, 9 | h2, 10 | h3, 11 | h4, 12 | h5, 13 | h6, 14 | p, 15 | blockquote, 16 | pre, 17 | a, 18 | abbr, 19 | acronym, 20 | address, 21 | big, 22 | cite, 23 | code, 24 | del, 25 | dfn, 26 | em, 27 | img, 28 | ins, 29 | kbd, 30 | q, 31 | s, 32 | samp, 33 | small, 34 | strike, 35 | strong, 36 | sub, 37 | sup, 38 | tt, 39 | var, 40 | b, 41 | u, 42 | i, 43 | center, 44 | dl, 45 | dt, 46 | dd, 47 | ol, 48 | ul, 49 | li, 50 | fieldset, 51 | form, 52 | label, 53 | legend, 54 | table, 55 | caption, 56 | tbody, 57 | tfoot, 58 | thead, 59 | tr, 60 | th, 61 | td, 62 | article, 63 | aside, 64 | canvas, 65 | details, 66 | embed, 67 | figure, 68 | figcaption, 69 | footer, 70 | header, 71 | hgroup, 72 | menu, 73 | nav, 74 | output, 75 | ruby, 76 | section, 77 | summary, 78 | time, 79 | mark, 80 | audio, 81 | video { 82 | margin: 0; 83 | padding: 0; 84 | } 85 | 86 | html, 87 | body { 88 | font-size: 10px; 89 | } 90 | 91 | body { 92 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif !important; 93 | color: #303133; 94 | 95 | /* -webkit-font-smoothing: antialiased; 96 | -moz-osx-font-smoothing: grayscale; */ 97 | font-size: 1.4rem; 98 | } 99 | 100 | input, 101 | select, 102 | textarea { 103 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif !important; 104 | } 105 | 106 | .table th { 107 | font-family: "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif !important; 108 | } 109 | 110 | /* HTML5 display-role reset for older browsers */ 111 | 112 | article, 113 | aside, 114 | details, 115 | figcaption, 116 | figure, 117 | footer, 118 | header, 119 | hgroup, 120 | menu, 121 | nav, 122 | section { 123 | display: block; 124 | } 125 | 126 | body { 127 | /* line-height: 1.25; */ 128 | } 129 | 130 | ol, 131 | ul { 132 | list-style: none; 133 | } 134 | 135 | blockquote, 136 | q { 137 | quotes: none; 138 | } 139 | 140 | a { 141 | text-decoration: none; 142 | } 143 | 144 | blockquote::before, 145 | blockquote::after, 146 | q::before, 147 | q::after { 148 | content: ''; 149 | content: none; 150 | } 151 | 152 | table { 153 | border-collapse: collapse; 154 | border-spacing: 0; 155 | } 156 | 157 | /* 默认placeholder颜色 */ 158 | ::-webkit-input-placeholder { 159 | color: #c0c4cc; 160 | } 161 | 162 | .pagination { 163 | height: 100px; 164 | display: flex; 165 | justify-content: center; 166 | align-items: center; 167 | } 168 | -------------------------------------------------------------------------------- /client/src/util/apiInitData.js: -------------------------------------------------------------------------------- 1 | import Schema from '@/model/schema' 2 | function initData () { 3 | return { 4 | prodUrl: null, 5 | devUrl: null, 6 | name: '', 7 | group: '', 8 | desc: null, 9 | creator: null, 10 | manager: null, 11 | follower: [], 12 | options: { 13 | proxy: { 14 | mode: 0 15 | }, 16 | response: [new Schema()], 17 | responseIndex: 0, 18 | headers: { 19 | example: null, 20 | params: [] 21 | }, 22 | method: 'get', 23 | delay: 0, 24 | examples: { 25 | query: null, 26 | body: null, 27 | path: null 28 | }, 29 | params: { 30 | query: [], 31 | body: [], 32 | path: [] 33 | } 34 | } 35 | } 36 | } 37 | export default initData 38 | -------------------------------------------------------------------------------- /client/src/util/buildApiResponse.js: -------------------------------------------------------------------------------- 1 | import buildSchemaFromExample from './buildSchemaFromExample' 2 | export default (api) => { 3 | if (api.options.response) { 4 | return api 5 | } 6 | api.options.response = [buildSchemaFromExample(api.dsl)] 7 | return api 8 | } 9 | -------------------------------------------------------------------------------- /client/src/util/buildSchemaFromExample.js: -------------------------------------------------------------------------------- 1 | import Param from '@/model/param' 2 | import R from 'ramda' 3 | import * as deepmerge from 'deepmerge' 4 | 5 | function findParam (params, key) { 6 | if (!params || !params.length) { 7 | return null 8 | } 9 | return params.find(p => p.key === key) 10 | } 11 | function buildParams (json, oldParams) { 12 | const params = [] 13 | for (const key in json) { 14 | const jsonValue = json[key] 15 | const type = typeof jsonValue 16 | // null, {}, [] 都属于空,属性则为选填 17 | // const required = !(R.isEmpty(jsonValue) || jsonValue === null) 18 | const oldParam = findParam(oldParams, key) 19 | const param = new Param({ 20 | ...(oldParam || {}), 21 | key, 22 | type, 23 | required: false 24 | }) 25 | if (type === 'object' && jsonValue instanceof Array) { 26 | param.type = 'array' 27 | param.items = { type: typeof jsonValue[0] } 28 | if (param.items.type === 'object') { 29 | param.items.params = buildArrayParams(jsonValue, oldParam && oldParam.items && oldParam.items.params) 30 | } else { 31 | param.example = jsonValue 32 | } 33 | } else if (type === 'object') { 34 | param.params = buildParams(jsonValue, oldParam && oldParam.params) 35 | } else { 36 | param.example = jsonValue 37 | } 38 | params.push(param) 39 | } 40 | // 保证在页面上呈现一个可填项 41 | if (params.length === 0) { 42 | params.push(new Param()) 43 | } 44 | return params 45 | } 46 | 47 | function buildArrayParams (data = [], params) { 48 | let allObject = data.filter(item => !R.isEmpty(item)).reduce((result, currentObject) => deepmerge(result, currentObject)) 49 | return buildParams(allObject, params) 50 | } 51 | 52 | export default (json, oldParams = null, statusText = 'status1', status = 200) => { 53 | const schema = { 54 | status, 55 | statusText, 56 | example: json, 57 | params: [] 58 | } 59 | schema.params = buildParams(json, oldParams) 60 | return schema 61 | } 62 | -------------------------------------------------------------------------------- /client/src/util/catchError.js: -------------------------------------------------------------------------------- 1 | export default (err) => { 2 | if (err.response && err.response.status === 401) { 3 | // window.location.href = '#/login' 4 | } 5 | if (!err.response) { 6 | return Promise.reject({ 7 | msg: '请求无响应' 8 | }) 9 | } 10 | // throw err; 11 | return Promise.reject({ 12 | response: err.response, 13 | statusCode: err.response.status, 14 | statusText: err.response.statusText, 15 | msg: err.response.data.message 16 | }) 17 | } 18 | -------------------------------------------------------------------------------- /client/src/util/copyToClickBoard.js: -------------------------------------------------------------------------------- 1 | 2 | function copy (content, callback) { 3 | const input = document.createElement('input') 4 | input.setAttribute('readonly', 'readonly') 5 | input.setAttribute('value', content) 6 | document.body.appendChild(input) 7 | input.select() 8 | if (document.execCommand('copy')) { 9 | document.execCommand('copy') 10 | callback() 11 | } 12 | } 13 | 14 | export default copy 15 | -------------------------------------------------------------------------------- /client/src/util/download.js: -------------------------------------------------------------------------------- 1 | export default function (res, fileName) { 2 | let dataString = JSON.stringify(res, null, 2) 3 | let blob = new Blob([dataString], {type: 'application/json'}) 4 | let a = document.createElement('a') 5 | a.download = `${fileName}.json` 6 | a.href = URL.createObjectURL(blob) 7 | document.body.appendChild(a) 8 | a.click() 9 | document.body.removeChild(a) 10 | } 11 | -------------------------------------------------------------------------------- /client/src/util/getUrlAllParams.js: -------------------------------------------------------------------------------- 1 | export default (str = '') => { 2 | let res = {} 3 | let paramsArr = str.split('&') 4 | paramsArr.forEach((paramsPair) => { 5 | let keyValuePair = paramsPair.split('=') 6 | let key = keyValuePair[0] 7 | let value = keyValuePair[1] 8 | res[key] = value 9 | }) 10 | return res 11 | } 12 | -------------------------------------------------------------------------------- /client/src/util/importApiMockerDoc.js: -------------------------------------------------------------------------------- 1 | function buildApisFormApiMockerDoc (json, group) { 2 | let {resources = []} = json 3 | resources.forEach((api) => { 4 | api.group = group._id 5 | }) 6 | return resources 7 | } 8 | 9 | export default buildApisFormApiMockerDoc 10 | -------------------------------------------------------------------------------- /client/src/util/importEasyMockHtml.js: -------------------------------------------------------------------------------- 1 | import Api from '@/model/api' 2 | import { buildReqParams, buildResponse } from './swaggerUtilSet' 3 | /** 4 | * 从esay mock接口中构建apis 5 | * @param {html} String 页面接口返回的页面字符串 6 | * @param {group} Obejct 群组信息 7 | * @param {param} Obejct 用户输入信息 8 | * @return {[type]} Array 构建完成之后组件弹窗 9 | */ 10 | function buildApisFromEasyMockHtml (html, group, param) { 11 | let jsonString = html.split('window.__INITIAL_STATE__=')[1].split(' { 22 | let {url, method, description, parameters, response_model} = item 23 | const requestList = JSON.parse(parameters) || [] 24 | const responseList = JSON.parse(response_model) || [] 25 | 26 | const api = Api() 27 | 28 | api.url = url 29 | api.devUrl = `${devUrl}${url}` 30 | api.prodUrl = `${prodUrl}${url}` 31 | api.name = description 32 | api.desc = description 33 | api.group = group._id 34 | api.options.method = method 35 | api.options.params = buildReqParams(api.options.params, requestList, {}) 36 | api.options.response = buildResponse(responseList, {}) 37 | 38 | return api 39 | }) 40 | 41 | return easyMockApis 42 | } catch (e) { 43 | console.log(e) 44 | } 45 | } 46 | 47 | export default buildApisFromEasyMockHtml 48 | -------------------------------------------------------------------------------- /client/src/util/importHar.js: -------------------------------------------------------------------------------- 1 | import Api from '@/model/api' 2 | import { buildSchemaFromExample } from '@/util' 3 | 4 | function extractParamsAndExample (list) { 5 | let example = {} 6 | let params = [] 7 | 8 | list.forEach((item) => { 9 | let {name, value, comment} = item 10 | example[name] = value 11 | params.push({ 12 | key: name, 13 | example: value, 14 | type: typeof value, 15 | comment 16 | }) 17 | }) 18 | return {params, example} 19 | } 20 | 21 | function buildReqParams (request, status, statusText) { 22 | let {headers = [], queryString = [], postData} = request 23 | let examples = {path: null, body: null, query: null} 24 | let params = {path: [], body: [], query: []} 25 | 26 | let optionHeaders = extractParamsAndExample(headers) 27 | 28 | if (queryString.length) { 29 | let queryResult = extractParamsAndExample(queryString) 30 | examples.query = queryResult.example 31 | params.query = queryResult.params 32 | } 33 | 34 | if (postData) { 35 | let {text = '', encoding} = postData 36 | if (text) { 37 | if (encoding === 'base64') { 38 | text = window.atob(text) 39 | } 40 | let example = JSON.parse(text) 41 | let bodyResult = buildSchemaFromExample(example, {}, statusText, status) 42 | example.body = bodyResult.example 43 | params.body = bodyResult.params 44 | } 45 | } 46 | return {examples, params, optionHeaders} 47 | } 48 | 49 | function buildResponse (response, status, statusText) { 50 | let {content} = response 51 | let {text, encoding} = content 52 | let example = {} 53 | if (encoding === 'base64') { 54 | text = window.atob(text) 55 | } 56 | example = JSON.parse(text) 57 | return [buildSchemaFromExample(example, {}, statusText, status)] 58 | } 59 | 60 | function buildApisFormHar (json, group, param) { 61 | let { log } = json 62 | let { entries = [] } = log 63 | let {devUrl, prodUrl} = param 64 | 65 | let result = entries.map((entry, index) => { 66 | const api = Api() 67 | 68 | let {request, response} = entry 69 | let {method, url} = request 70 | let {pathname} = new URL(url) 71 | let {status, statusText} = response 72 | 73 | api.name = `Api-form-har_${index}` 74 | api.url = pathname 75 | api.devUrl = `${devUrl}${pathname}` 76 | api.prodUrl = `${prodUrl}${pathname}` 77 | api.group = group._id 78 | api.modifiedTime = api.createTime = new Date().getTime() 79 | 80 | api.options.method = method 81 | let {examples, params, optionHeaders} = buildReqParams(request, status, statusText) 82 | api.options.headers = optionHeaders 83 | api.options.params = params 84 | api.options.examples = examples 85 | api.options.response = buildResponse(response, status, statusText) 86 | 87 | return api 88 | }) 89 | 90 | return result 91 | } 92 | 93 | export default buildApisFormHar 94 | -------------------------------------------------------------------------------- /client/src/util/importRap.js: -------------------------------------------------------------------------------- 1 | import Api from '@/model/api' 2 | 3 | function buildReqParams (params, parameterList, methodType) { 4 | methodType = Number(methodType) 5 | const newParams = buildParams(parameterList) 6 | if (methodType === 1) { 7 | params.query = newParams 8 | } else { 9 | params.body = newParams 10 | } 11 | return params 12 | } 13 | function buildResponse (parameterList) { 14 | return [{ 15 | status: 200, 16 | statusText: 'status1', 17 | example: null, 18 | params: buildParams(parameterList) 19 | }] 20 | } 21 | function buildParams (parameterList) { 22 | return parameterList.map(p => { 23 | const param = { 24 | key: p.identifier, 25 | type: p.dataType, 26 | required: true, 27 | comment: p.name 28 | } 29 | if (p.remark.indexOf('@mock') === 0) { 30 | param.example = p.remark.replace('@mock=', '') 31 | } else { 32 | param.comment += ` ${p.remark}` 33 | } 34 | if (param.type === 'object') { 35 | param.params = buildParams(p.parameterList) 36 | } 37 | if (param.type.indexOf('array') >= 0) { 38 | param.items = { 39 | type: param.type.replace('array<', '').replace('>', '') 40 | } 41 | param.params = buildParams(p.parameterList) 42 | param.type = 'array' 43 | } 44 | return param 45 | }) 46 | } 47 | 48 | function buildApisFormRap (json, group, param) { 49 | let methods = ['get', 'post', 'put', 'delete'] 50 | let {devUrl, prodUrl} = param 51 | let moduleList = [] 52 | if (json.modelJSON) { 53 | moduleList = JSON.parse(json.modelJSON).moduleList 54 | } else if (json.projectData) { 55 | moduleList = json.projectData.moduleList 56 | } else { 57 | // $message.error('json格式错误') 58 | return 59 | } 60 | const apis = [] 61 | moduleList.forEach(module => { 62 | module.pageList.forEach(page => { 63 | page.actionList.forEach(action => { 64 | window.console.log(action) 65 | let {requestUrl} = action 66 | const apiName = `${module.name}-${page.name}-${action.name}` 67 | const api = Api() 68 | api.name = apiName 69 | api.desc = action.description 70 | 71 | api.devUrl = `${devUrl}${requestUrl}` 72 | api.prodUrl = `${prodUrl}${requestUrl}` 73 | 74 | api.group = group._id 75 | const requestType = Number(action.requestType) 76 | api.options.method = methods[requestType - 1] 77 | api.options.params = buildReqParams(api.options.params, action.requestParameterList, requestType) 78 | api.options.response = buildResponse(action.responseParameterList) 79 | apis.push(api) 80 | }) 81 | }) 82 | }) 83 | 84 | return apis 85 | } 86 | 87 | export default buildApisFormRap 88 | -------------------------------------------------------------------------------- /client/src/util/importSwagger.js: -------------------------------------------------------------------------------- 1 | import Api from '@/model/api' 2 | import { buildReqParams, buildResponse } from './swaggerUtilSet' 3 | /** 4 | * 从swagger json配置文件中构建apis 5 | * @param {json} json json对象 6 | * @param {group} Obejct 群组信息 7 | * @param {param} Obejct 用户输入信息 8 | * @return {[type]} Obejct 构建完成之后组件弹窗 9 | */ 10 | function buildApisFormSwagger (json, group, param) { 11 | let {devUrl, prodUrl} = param 12 | // swagger 导出 JSON 格式 13 | /* 14 | * swagger: 版本 15 | * info: { 16 | * version: 1.0 接口版本 17 | * title: '' 接口名字 18 | * license: '' 许可证 19 | * } 20 | * host: '' 21 | * basePath: '' 22 | * tags: { } 23 | * paths: { 24 | * "路径": { 25 | * 'get': { 26 | * } 27 | * } 28 | * } 29 | */ 30 | let { 31 | info = {}, 32 | paths = {} 33 | } = json || {} 34 | let _definitions = json.definitions || {} 35 | 36 | let { title = '' } = info 37 | 38 | const swaggerApis = [] 39 | 40 | for (let [key, value] of Object.entries(paths)) { 41 | try { 42 | for (let method in value) { 43 | const methodValue = value[method] 44 | const { summary, tags = [] } = methodValue || {} 45 | const api = Api() 46 | 47 | api.url = key 48 | api.devUrl = `${devUrl}${key}` 49 | api.prodUrl = `${prodUrl}${key}` 50 | api.name = `${title}-${tags.join(',')}-${summary}` 51 | api.desc = summary 52 | api.group = group._id 53 | api.options.method = method 54 | api.options.params = buildReqParams(api.options.params, methodValue.parameters || [], _definitions) 55 | api.options.response = buildResponse(methodValue.responses, _definitions) 56 | 57 | swaggerApis.push(api) 58 | } 59 | } catch (e) { 60 | console.log(`${key} API 有误 无法解析`) 61 | console.warn(e) 62 | } 63 | } 64 | 65 | return swaggerApis 66 | } 67 | 68 | export default buildApisFormSwagger 69 | -------------------------------------------------------------------------------- /client/src/util/index.js: -------------------------------------------------------------------------------- 1 | import config from '../../config' 2 | export { default as buildExampleFromSchema } from 'mocker-dsl-core/lib/buildExampleFromSchema' 3 | export { default as buildSchemaFromExample } from './buildSchemaFromExample' 4 | export { default as buildApiResponse } from './buildApiResponse' 5 | export { default as validateApi } from './validateApi' 6 | export { default as catchError } from './catchError' 7 | export { default as getUrlAllParams } from './getUrlAllParams' 8 | 9 | export function getDomain () { 10 | const protocol = window.location.href.indexOf('https') === 0 ? 'https://' : 'http://' 11 | return process.env.NODE_ENV === 'development' ? protocol + config.dev.serverRoot : config.build.serverRoot 12 | } 13 | 14 | export function buildRestUrl (baseUrl, params) { 15 | for (const key in params) { 16 | const placeholder = `:${key}` 17 | if (baseUrl.indexOf(placeholder) === -1) { 18 | baseUrl += `/${params[key]}` 19 | } else { 20 | baseUrl = baseUrl.replace(placeholder, params[key]) 21 | } 22 | } 23 | return baseUrl 24 | } 25 | 26 | export function debounce (fun, interval) { 27 | let timer = -1 28 | return function (...args) { 29 | clearTimeout(timer) 30 | timer = setTimeout(() => { 31 | fun.apply(this, args) 32 | }, interval) 33 | } 34 | } 35 | 36 | export function throttle (fn, gapTime) { 37 | let lastTime = null 38 | 39 | return function () { 40 | let now = +new Date() 41 | if (now - lastTime > gapTime || !lastTime) { 42 | fn() 43 | lastTime = now 44 | } 45 | } 46 | } 47 | 48 | export function urlJoin (...args) { 49 | if (args.length > 0) { 50 | const strs = args.map((arg, index) => { 51 | let str = arg 52 | // 去掉头部/ 53 | if (str && index !== 0 && str.startsWith('/')) str = str.substr(1, str.length - 1) 54 | // 去掉尾部/ 55 | if (str && index !== args.length - 1 && str.endsWith('/')) str = str.substr(0, str.length - 1) 56 | return str 57 | }) 58 | return strs.join('/') 59 | } 60 | return '' 61 | } 62 | -------------------------------------------------------------------------------- /client/src/util/requestHeaders.js: -------------------------------------------------------------------------------- 1 | const headersList = ['Accept', 'Accept-Charset', 'Accept-Encoding', 'Accept-Language', 'Accept-Datetime', 'Accept-Ranges', 'Authorization', 'Cache-Control', 'Connection', 'Cookie', 'Content-Disposition', 'Content-Length', 'Content-MD5', 'Content-Range', 'Content-Type', 'Date', 'DNT', 'Expect', 'From', 'Front-End-Https', 'Host', 'If-Match', 'If-Modified-Since', 'If-None-Match', 'If-Range', 'If-Unmodified-Since', 'Max-Forwards', 'Origin', 'Pragma', 'Proxy-Authorization', 'Proxy-Connection', 'Range', 'Referer', 'TE', 'User-Agent', 'Upgrade', 'Via', 'Warning', 'X-Requested-With', 'X-Forwarded-For', 'X-Forwarded-Host', 'X-Forwarded-Proto', 'X-Http-Method-Override', 'X-ATT-DeviceId', 'X-Wap-Profile', 'X-UIDH', 'X-Csrf-Token', 'X-Frame-Options', 'X-XSS-Protection', 'X-Content-Type-Options', 'X-Powered-By'] 2 | 3 | export default headersList 4 | -------------------------------------------------------------------------------- /client/src/util/validateApi.js: -------------------------------------------------------------------------------- 1 | function isEmpty (val) { 2 | return !val || val.trim() === '' 3 | } 4 | export default (state) => { 5 | // const regex = new RegExp(/^((ht|f)tps?):\/\/[\w-]+(\.[\w-]+)+([\w\-.,@?^=%&:/~+#]*[\w\-@?^=%&~+#])?$/); 6 | const api = state.api 7 | let rs = {} 8 | if (isEmpty(api.name)) { 9 | rs = { 10 | success: false, 11 | msg: '接口名不能为空' 12 | } 13 | } else if (isEmpty(api.group)) { 14 | rs = { 15 | success: false, 16 | msg: '接口分组不能为空' 17 | } 18 | } else if (!state.dslStatus.success) { 19 | rs = state.dslStatus 20 | } else { 21 | rs = { 22 | success: true 23 | } 24 | } 25 | return new Promise((resolve, reject) => { 26 | if (rs.success) { 27 | resolve(rs) 28 | } else { 29 | reject(rs) 30 | } 31 | }) 32 | } 33 | -------------------------------------------------------------------------------- /client/src/views/auth/DxyLogin.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 45 | 47 | -------------------------------------------------------------------------------- /client/src/views/auth/FindPass.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 57 | 59 | -------------------------------------------------------------------------------- /client/src/views/auth/Index.vue: -------------------------------------------------------------------------------- 1 | 6 | 10 | 75 | -------------------------------------------------------------------------------- /client/src/views/auth/Login.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 78 | 80 | -------------------------------------------------------------------------------- /client/src/views/auth/Register.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 86 | 88 | -------------------------------------------------------------------------------- /client/src/views/diff/Api.vue: -------------------------------------------------------------------------------- 1 | 24 | 63 | 85 | -------------------------------------------------------------------------------- /client/src/views/diff/Index.vue: -------------------------------------------------------------------------------- 1 | 20 | 86 | 101 | -------------------------------------------------------------------------------- /client/src/views/document/ApiContent.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 43 | 45 | -------------------------------------------------------------------------------- /client/src/views/document/Index.vue: -------------------------------------------------------------------------------- 1 | 9 | 61 | 63 | -------------------------------------------------------------------------------- /client/src/views/document/Overview.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | -------------------------------------------------------------------------------- /client/src/views/edit/ApiAuthor.vue: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 25 | -------------------------------------------------------------------------------- /client/src/views/edit/ApiHistory.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 56 | 82 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/DescBox.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 27 | 32 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/ResultBox.vue: -------------------------------------------------------------------------------- 1 | 10 | 11 | 59 | 64 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/SettingField.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 56 | 128 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/response/Config.vue: -------------------------------------------------------------------------------- 1 | 26 | 27 | 65 | 92 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/response/Index.vue: -------------------------------------------------------------------------------- 1 | 21 | 22 | 77 | 105 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/response/StatusSetting.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 46 | 55 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/schema/Index.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 75 | 117 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/schema/params/Index.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 67 | -------------------------------------------------------------------------------- /client/src/views/edit/apiBox/schema/params/ParamFill.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 51 | 53 | -------------------------------------------------------------------------------- /client/src/views/list/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 26 | -------------------------------------------------------------------------------- /client/src/views/list/components/GroupList.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 56 | -------------------------------------------------------------------------------- /client/src/views/list/components/PageNav.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 40 | -------------------------------------------------------------------------------- /client/src/views/manage/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 25 | -------------------------------------------------------------------------------- /client/src/views/manage/Nav.vue: -------------------------------------------------------------------------------- 1 | 19 | 20 | 24 | -------------------------------------------------------------------------------- /client/src/views/manage/api/ApiStatus.vue: -------------------------------------------------------------------------------- 1 | 18 | 19 | 50 | -------------------------------------------------------------------------------- /client/src/views/manage/api/Index.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 103 | 109 | -------------------------------------------------------------------------------- /client/src/views/manage/group/GroupControl.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 74 | 76 | -------------------------------------------------------------------------------- /client/src/views/manage/profile/Index.vue: -------------------------------------------------------------------------------- 1 | 13 | 14 | 35 | 44 | -------------------------------------------------------------------------------- /client/src/views/manage/profile/Menu.vue: -------------------------------------------------------------------------------- 1 | 11 | 12 | 40 | 109 | -------------------------------------------------------------------------------- /client/src/views/manage/profile/UserItem.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 80 | 102 | -------------------------------------------------------------------------------- /client/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/static/.gitkeep -------------------------------------------------------------------------------- /client/static/favorite.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DXY-F2E/api-mocker/603193f39155f65f1b7ecb465c512bf0f4d0e5b3/client/static/favorite.ico -------------------------------------------------------------------------------- /docs/authority.md: -------------------------------------------------------------------------------- 1 | ## 关于权限 2 | 3 | * 接口创建者即接口管理员 4 | * 分组创建者即分组管理员 5 | * 只有管理员能做权限修改 6 | * 权限分为可读权限与可写权限 7 | * 创建的接口与分组默认对所有人可读可写 8 | * 用户可以成为多个组的组内成员 9 | * 管理员目前不能更改,将来会安排需求完善 10 | -------------------------------------------------------------------------------- /docs/build.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # clear readme.md 4 | : > ./readme.md 5 | 6 | # merge all doc.md to readme.md doc by this order 7 | for md in intro edit mock proxy group authority list push keymap faq 8 | do 9 | ( cat ${md}.md; echo ) >> ./readme.md 10 | done 11 | 12 | echo "build readme.md done" 13 | -------------------------------------------------------------------------------- /docs/edit.md: -------------------------------------------------------------------------------- 1 | ## 创建/编辑 接口 2 | 3 | ### 必需字段 4 | 5 | * 接口名称; 6 | * 接口分组,若无,可根据页面提示创建分组; 7 | * 请求类型,默认选择`GET`; 8 | 9 | ### 请求约定 - Request 10 | 11 | 请求约定分为三类请求参数与一个请求头: 12 | 13 | * Body: 提交的JSON或者Form数据,若请求类型选择`GET`,则不显示此参数约定 14 | * Query: url参数,例如:https://www.dxy.cn?param=hi 15 | * Path: RESTful风格的url参数,例如:https://www.dxy.cn/:param1/:param2 16 | * Header: 请求头的约定 17 | 18 | ### 响应约定 19 | 20 | * 返回结果可以设置多个,并且自定义对应的名称与状态码,此状态码应该与真实接口状态码保持一致。 21 | * 支持模拟网络延迟 22 | * 如若设置了多个返回值,可以指定返回结果,或者随机结果 23 | 24 | 25 | ### 关于参数结构(Schema)填写 26 | 27 | 1. 参数属性包括:参数名(key),参数类型,是否必填,备注,示例值(example)。 28 | 2. 基本参数类型有:`string`,`number`,`boolean`,`object`,`array`。 29 | 3. Body参数的数据类型,另多一个`file`类型,但目前不做校验支持,只在文档起约定显示作用。 30 | 4. Query参数与Header参数只支持`string`类型,因为url与请求头中的数据,都是字符串。 31 | 5. 如若参数类型选择对象或者对象数组(`array[object]`),则可以再选择子一级参数。 32 | 6. 暂不支持嵌套数组,若选择数组,那么数组里每个值类型都应该一样。 33 | 34 | 为方便大家理解`Schema`,以及与`Example`的关系,附上 `Schema`的整体json结构。 35 | ```javascript 36 | Param = { 37 | key: String, // 参数名 38 | type: String, // 参数类型,string|number|boolean|object|array 39 | comment: String, // 备注 40 | example: String, // 参数示例值 41 | items: { // 当参数类型为array时,此字段生效 42 | type: String // 数组的参数类型 43 | params: Array[Param] // 当数组参数类型为object类型时,此字段生效,意义为数组内对象的参数模型 44 | } 45 | } 46 | Schema = { 47 | example: Object, 48 | params: Array[Param] 49 | } 50 | ``` 51 | 52 | ### 关于Example 53 | 54 | * 每个参数结构体(Schema)都有对应的Example。代表这个参数结构体的示例值。系统支持example与schema的相互转换。 55 | * schema生成example根据mock规则生成,具体可看mock一章。 56 | * example生成schema,会自动判断数据类型,并把值作为参数的example。如若值为null,{},则会认为是选填对象,若为[],则为选填数组。 57 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | ## 其他常见问题 2 | 3 | **Q: 请求Mock URL出现404 not found。** 4 | 5 | A: 请先确认请求协议是否正确。为严格定义接口,系统会严格判断请求协议。若发起请求类型不对,接口会返回404。 6 | 7 | **Q: query参数类型只有string,number,boolean?** 8 | 9 | A: Query参数本质都是字符串,接口为了适应实际需求,还另外加了number与boolean类型的校验,但不支持object与array,若有类似以批量id查询的接口,请以字符串加分隔符的格式传输。 10 | 11 | **Q: 如何删除接口?** 12 | 13 | A: 鼠标移动到右上角账户名,选择接口管理,可以删除接口。但仅限于接口管理员有权限(接口创建者默认为接口管理员),分组同上。 14 | 15 | **若有其他问题,欢迎提[Issues](http://gitlab.dxy.net/f2e/api-mocker/issues)或者PR** 16 | -------------------------------------------------------------------------------- /docs/group.md: -------------------------------------------------------------------------------- 1 | ## 关于分组 2 | 3 | * 系统目前只有一级分组 4 | * 接口必须属于某一且唯一分组 5 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | # API Mocker 2 | API Mocker,不仅仅是mock。最初我们只是想做一个mock server。后来为了提高前后端协同开发效率,不断的演变成如今的接口管理系统。 3 | 4 | 下面介绍下如何使用该系统。 5 | -------------------------------------------------------------------------------- /docs/keymap.md: -------------------------------------------------------------------------------- 1 | ## 快捷键 2 | 3 | 为了编辑方便,系统有部分快捷键,其中: 4 | 5 | * 保存接口:`ctrl + s` 或者 `cmd + s` 6 | * 退出全屏:`ESC` 7 | -------------------------------------------------------------------------------- /docs/list.md: -------------------------------------------------------------------------------- 1 | ## 列表与搜索 2 | 3 | * 列表页左侧栏为用户所有可见分组 4 | * 列表页中间为分组内所有可见接口,默认为系统全部可见接口 5 | * 目前能按接口名称、线上地址、测试地址、管理员名字和接口ID来搜索。((接口ID为mock url中最后一截ObjectId字符串) 6 | -------------------------------------------------------------------------------- /docs/mock.md: -------------------------------------------------------------------------------- 1 | ## 关于Mock 2 | 3 | ### mock规则 4 | 系统引用了[Mock.js](http://mockjs.com/),拥有了mock随机假数据的能力。但本系统强调接口管理本身,在假数据上不过分引导与强调。 5 | 6 | 如若设置了`Response`的`Example`,则会返回`Example`的值,如果当中写了[Mock.js](http://mockjs.com/examples.html)的语法,则会生成对应的数据。例如 7 | 8 | ```javascript 9 | // example值为: 10 | { 11 | "status|1-2": true, 12 | "number|1-100": 100 13 | } 14 | // 则会生成 15 | { 16 | "status": false, 17 | "number": 40 18 | } 19 | // 或 20 | { 21 | "status": true, 22 | "number": 99 23 | } 24 | // 或其他随机值 25 | ``` 26 | 27 | 如果没有设置`Response`的`Example`,则会根据对应`Schema`产生mock数据。 28 | 29 | 1. 生成mock数据时,会优先使用`schema`参数的示例值。 30 | 2. 若示例值未填写,则`String`类型参数mock数据为`"value"`,`Boolean`与`Number`会随机 31 | 32 | ```javascript 33 | // schema 34 | { 35 | success: { 36 | type: Boolean, 37 | example: true 38 | }, 39 | counts: { 40 | type: Number 41 | }, 42 | items: [{ 43 | id: { 44 | type: String, 45 | example: "e9da9ae33va9f" 46 | }, 47 | name: { 48 | type: String 49 | } 50 | }] 51 | } 52 | // 会生成 53 | { 54 | "success": true, 55 | "counts": 345, 56 | "items": [{ 57 | "id": "e9da9ae33va9f", 58 | "name": "value" 59 | }] 60 | } 61 | ``` 62 | 63 | ### 建议 64 | * 如果对于假数据随机性要求不高,不建议写mock语法,也不需要填写`Schema`的`Example`,系统会自动生成假数据。 65 | 66 | 67 | ### 设置返回 68 | * api-mocker 可以为一个 mock 接口配置多种 response。可以在系统中切换,也可以在请求时带上 query 参数 __api_mock_status__ 来指定返回配置 response 中的哪个(__api_mock_status__ 为索引值)。 69 | 当两者都配置时以 __api_mock_status__ 参数为准。 -------------------------------------------------------------------------------- /docs/proxy.md: -------------------------------------------------------------------------------- 1 | ## 关于代理 2 | 3 | api-mocker支持代理mock请求到**线上地址**或者**测试地址**,前提是已经在接口设置中配置对应的地址链接,且为绝对路径。 4 | 5 | ### 开启代理的两种方法 6 | 7 | 1. 在接口编辑页左侧的 [**代理转发**] 选项勾选对应的转发规则。 8 | 2. 可以在请求Mock URL时,带上query参数来设置: 9 | - 转发线上:{ mockURL }?_mockProxyStatus=1 10 | - 转发测试:{ mockURL }?_mockProxyStatus=2 11 | 12 | ### 代理鉴权 13 | 14 | 有些接口需要cookie鉴权,而mock地址与线上、测试地址并非同个域名,所以cookie没办法共通。为此api-mocker提供一个配置:mock请求的请求头若设置了字段`api-cookie`,则会使用此字段的值作为代理请求的`cookie`。 15 | 16 | 故开发阶段可以将某个已鉴权cookie,写至mock请求头的`api-cookie`字段,如示例: 17 | ```javascript 18 | $.ajax({ 19 | url: mockUrl, 20 | method: 'get', 21 | headers: { 22 | 'api-cookie': 'your cookie of authentication' 23 | } 24 | }) 25 | ``` 26 | 27 | 亦或者直接手动将该`cookie`设置到mock请求的域名下,mock服务端会将当前域名的cookie转发到代理请求地址。 28 | 29 | > 注:若请求头设置了`api-cookie`,则不会再使用mock请求地址的原本cookie。 30 | -------------------------------------------------------------------------------- /docs/push.md: -------------------------------------------------------------------------------- 1 | ## 订阅与推送 2 | 3 | * 支持按接口订阅or分组订阅; 4 | * 接口创建者默认订阅接口,其他人需到接口文档页自己订阅; 5 | * 接口发生修改时,推送提醒,连续1小时内的变化不会连续推送(可配置,详情请查阅配置相关的文档); 6 | * 订阅的分组内接口发生修改、创建或删除时,推送提醒; 7 | * 推送到订阅者注册账户的邮箱; 8 | * 系统的推送邮箱需要配置,请注意由于不同邮件商安全策略不同,请自行调试。 9 | -------------------------------------------------------------------------------- /ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [ 3 | { 4 | name: 'api-mocker', 5 | script: './server/index.js', 6 | cwd: '/var/www/api-mocker/server/', 7 | env_production: { 8 | NODE_ENV: 'production', 9 | EGG_SERVER_ENV: "prod", 10 | PORT: 7001 11 | } 12 | } 13 | ] 14 | }; 15 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: prerequ-program client server prod_server prod_client 4 | 5 | 6 | install:|prerequ-program 7 | 8 | define require_install 9 | if test "$(shell which $(1))" = ""; \ 10 | then \ 11 | brew install $(2); \ 12 | else \ 13 | echo $(1) is exists. skip install; \ 14 | fi 15 | endef 16 | 17 | prerequ-program: 18 | @$(call require_install,mongod,mongo) 19 | mkdir -p ./db/ 20 | if [ "${shell pgrep mongod}" = "" ]; then mongod --bind_ip 127.0.0.1 --fork --dbpath ./db/ --logpath ./db/mongod.log; fi 21 | @echo "start mongod success!" 22 | 23 | # 开发模式 24 | server: 25 | cd server && npm install && npm run dev 26 | 27 | client: 28 | cd client && npm install && npm run dev 29 | 30 | # 生产模式 31 | prod_server: 32 | cd server && npm install && npm start 33 | 34 | prod_client: 35 | cd client && npm install && npm run build 36 | -------------------------------------------------------------------------------- /server/.autod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = { 4 | write: true, 5 | prefix: '^', 6 | plugin: 'autod-egg', 7 | test: [ 8 | 'test', 9 | 'benchmark', 10 | ], 11 | dep: [ 12 | ], 13 | devdep: [ 14 | 'egg-ci', 15 | 'egg-bin', 16 | 'autod', 17 | 'eslint', 18 | 'supertest', 19 | 'autod-egg', 20 | 'webstorm-disable-index', 21 | ], 22 | exclude: [ 23 | './test/fixtures', 24 | './dist', 25 | ], 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /server/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | presets: ['es2015'] 3 | } 4 | -------------------------------------------------------------------------------- /server/.eslintignore: -------------------------------------------------------------------------------- 1 | test/fixtures 2 | test/app/controller 3 | coverage 4 | sql-scripts/ 5 | config/core.default.js 6 | config/manager.default.js 7 | -------------------------------------------------------------------------------- /server/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "standard" 4 | // "eslint-config-egg" 5 | ], 6 | "rules": { 7 | "new-cap": "warn" 8 | }, 9 | "globals": { 10 | "app": true, 11 | "request": true, 12 | "mm": true, 13 | "mock": true, 14 | "assert": true, 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/.gitattributes: -------------------------------------------------------------------------------- 1 | *.vue linguist-language=JavaScript 2 | -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | npm-debug.log 3 | node_modules/ 4 | coverage/ 5 | .idea/ 6 | .vscode/ 7 | run/ 8 | db/ 9 | .DS_Store 10 | *.swp 11 | package-lock.json 12 | appveyor.yml 13 | .travis.yml -------------------------------------------------------------------------------- /server/.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | stages: 2 | - build 3 | - deploy 4 | - deploy-ams 5 | 6 | variables: 7 | PKG: api-mocker-server 8 | 9 | 10 | # stage build - 安装 npm 依赖 11 | install dependencies: 12 | stage: build 13 | tags: 14 | - f2e-runner 15 | - docker 16 | only: 17 | - develop 18 | - master 19 | except: 20 | changes: 21 | - "*.md" 22 | cache: 23 | key: "${CI_PROJECT_PATH}-${CI_COMMIT_REF_NAME}" 24 | paths: 25 | - node_modules/ 26 | policy: pull-push # 拉取 npm 依赖缓存、更新 npm 依赖 & 推送 npm 依赖缓存 27 | script: 28 | - npm config set registry https://registry.npm.taobao.org/ 29 | - npm config set production false 30 | - npm i 31 | - tar zcf ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz * .eslint* # 打代码包,做为 artifacts 32 | - md5sum ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz > ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz.md5 33 | artifacts: 34 | paths: 35 | - ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz 36 | - ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz.md5 37 | expire_in: 1 week 38 | 39 | # stage deploy - 在测试环境(192.168.202.216 机器)上部署应用 40 | deploy to development environment: 41 | stage: deploy 42 | tags: 43 | - f2e-runner 44 | - bash # 部署使用 shell executor,此 runner 就在目标机器 216 上 45 | only: 46 | - develop # 针对 develop 分支,部署测试环境 47 | except: 48 | changes: 49 | - "*.md" 50 | variables: 51 | GIT_STRATEGY: none 52 | dependencies: 53 | - install dependencies 54 | script: 55 | - tar xf ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz 56 | - ./bin/appctl.sh ${PKG} ${CI_COMMIT_SHA:0:8} 57 | 58 | # stage deploy-ams - 在生产环境(生产机)上部署应用 59 | deploy-ams to production environment: 60 | stage: deploy-ams 61 | tags: 62 | - f2e-prd 63 | - bash 64 | only: 65 | - master 66 | except: 67 | changes: 68 | - "*.md" 69 | variables: 70 | GIT_STRATEGY: none 71 | dependencies: 72 | - install dependencies 73 | script: 74 | - mv ${PKG}-${CI_COMMIT_SHA:0:8}.tar.gz* /var/cache/deploy/ 75 | - sudo /opt/bin/deploy-f2e-api-mocker-server ${PKG} ${CI_COMMIT_SHA:0:8} 76 | -------------------------------------------------------------------------------- /server/.npmrc: -------------------------------------------------------------------------------- 1 | # 设置使用淘宝镜像地址 2 | registry=https://registry.npm.taobao.org 3 | 4 | # 设置一些二进制文件镜像地址 5 | disturl=https://npm.taobao.org/dist 6 | chromedriver-cdnurl=https://npm.taobao.org/mirrors/chromedriver 7 | couchbase-binary-host-mirror=https://npm.taobao.org/mirrors/couchbase/v{version} 8 | debug-binary-host-mirror=https://npm.taobao.org/mirrors/node-inspector 9 | electron-mirror=https://npm.taobao.org/mirrors/electron/ 10 | flow-bin-binary-host-mirror=https://npm.taobao.org/mirrors/flow/v 11 | fse-binary-host-mirror=https://npm.taobao.org/mirrors/fsevents 12 | fuse-bindings-binary-host-mirror=https://npm.taobao.org/mirrors/fuse-bindings/v{version} 13 | git4win-mirror=https://npm.taobao.org/mirrors/git-for-windows 14 | gl-binary-host-mirror=https://npm.taobao.org/mirrors/gl/v{version} 15 | grpc-node-binary-host-mirror=https://npm.taobao.org/mirrors 16 | hackrf-binary-host-mirror=https://npm.taobao.org/mirrors/hackrf/v{version} 17 | leveldown-binary-host-mirror=https://npm.taobao.org/mirrors/leveldown/v{version} 18 | leveldown-hyper-binary-host-mirror=https://npm.taobao.org/mirrors/leveldown-hyper/v{version} 19 | mknod-binary-host-mirror=https://npm.taobao.org/mirrors/mknod/v{version} 20 | node-sqlite3-binary-host-mirror=https://npm.taobao.org/mirrors 21 | node-tk5-binary-host-mirror=https://npm.taobao.org/mirrors/node-tk5/v{version} 22 | nodegit-binary-host-mirror=https://npm.taobao.org/mirrors/nodegit/v{version}/ 23 | operadriver-cdnurl=https://npm.taobao.org/mirrors/operadriver 24 | phantomjs-cdnurl=https://npm.taobao.org/mirrors/phantomjs 25 | profiler-binary-host-mirror=https://npm.taobao.org/mirrors/node-inspector/ 26 | puppeteer-download-host=https://npm.taobao.org/mirrors 27 | python-mirror=https://npm.taobao.org/mirrors/python 28 | rabin-binary-host-mirror=https://npm.taobao.org/mirrors/rabin/v{version} 29 | sass-binary-site=https://npm.taobao.org/mirrors/node-sass 30 | sodium-prebuilt-binary-host-mirror=https://npm.taobao.org/mirrors/sodium-prebuilt/v{version} 31 | sqlite3-binary-site=https://npm.taobao.org/mirrors/sqlite3 32 | utf-8-validate-binary-host-mirror=https://npm.taobao.org/mirrors/utf-8-validate/v{version} 33 | utp-native-binary-host-mirror=https://npm.taobao.org/mirrors/utp-native/v{version} 34 | zmq-prebuilt-binary-host-mirror=https://npm.taobao.org/mirrors/zmq-prebuilt/v{version} 35 | phantomjs_cdnurl = https://npm.taobao.org/mirrors/phantomjs/ 36 | -------------------------------------------------------------------------------- /server/.travis.yml: -------------------------------------------------------------------------------- 1 | 2 | language: node_js 3 | node_js: 4 | - '8' 5 | before_install: 6 | - npm i npminstall -g 7 | install: 8 | - npminstall 9 | script: 10 | - npm run ci 11 | after_script: 12 | - npminstall codecov && codecov 13 | -------------------------------------------------------------------------------- /server/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # api-mocker-server changelog 2 | 3 | ## 1.3.1 4 | 5 | * 升级moment版本,规避moment已知的正则安全漏洞 6 | * 增加通过prodUrl或devUrl来请求mock 7 | * 优化冗余的代码 8 | -------------------------------------------------------------------------------- /server/README.md: -------------------------------------------------------------------------------- 1 | # server 2 | 3 | api-mocker-server 4 | 5 | ## QuickStart 6 | 7 | 8 | 9 | see [egg docs][egg] for more detail. 10 | 11 | ### Development 12 | ```shell 13 | $ npm install 14 | $ npm run dev 15 | $ open http://localhost:7001/news 16 | ``` 17 | 18 | ### Deploy 19 | 20 | Use `EGG_SERVER_ENV=prod` to enable prod mode 21 | 22 | ```shell 23 | $ EGG_SERVER_ENV=prod npm start 24 | ``` 25 | 26 | ### npm scripts 27 | 28 | - Use `npm run lint` to check code style. 29 | - Use `npm test` to run unit test. 30 | - Use `npm run autod` to auto detect dependencies upgrade, see [autod](https://www.npmjs.com/package/autod) for more detail. 31 | 32 | 33 | [egg]: https://eggjs.org -------------------------------------------------------------------------------- /server/README.zh-CN.md: -------------------------------------------------------------------------------- 1 | # server 2 | 3 | api-mocker-server 4 | 5 | ## 快速入门 6 | 7 | 8 | 9 | 如需进一步了解,参见 [egg 文档][egg]。 10 | 11 | ### 本地开发 12 | ```bash 13 | $ npm install 14 | $ npm run dev 15 | $ open http://localhost:7001/ 16 | ``` 17 | 18 | ### 本地调试 19 | $ npm run debug 20 | 21 | ### 部署 22 | 23 | 线上正式环境用 `EGG_SERVER_ENV=prod` 来启动。 24 | 25 | ```bash 26 | $ EGG_SERVER_ENV=prod npm start 27 | ``` 28 | 29 | ### 单元测试 30 | - [egg-bin] 内置了 [mocha], [thunk-mocha], [power-assert], [istanbul] 等框架,让你可以专注于写单元测试,无需理会配套工具。 31 | - 断言库非常推荐使用 [power-assert]。 32 | - 具体参见 [egg 文档 -单元测试](https://eggjs.org/zh-cn/core/unittest)。 33 | 34 | ### 内置指令 35 | 36 | - 使用 `npm run lint` 来做代码风格检查。 37 | - 使用 `npm test` 来执行单元测试。 38 | - 使用 `npm run autod` 来自动检测依赖更新,详细参见 [autod](https://www.npmjs.com/package/autod) 。 39 | 40 | 41 | [egg]: https://eggjs.org -------------------------------------------------------------------------------- /server/agent.js: -------------------------------------------------------------------------------- 1 | module.exports = agent => { 2 | agent.messenger.once('egg-ready', () => { 3 | agent.messenger.sendToApp('refresh_timestamp', Date.now()) 4 | }) 5 | 6 | agent.messenger.on('refresh', () => { 7 | agent.messenger.sendToApp('refresh_timestamp', Date.now()) 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | const md5 = require('blueimp-md5') 2 | const core = require('./config/core.default') 3 | const { md5Key } = core() 4 | const manager = require('./config/manager.default') 5 | const managerConfig = { ...manager, password: md5(manager.password, md5Key) } 6 | 7 | module.exports = app => { 8 | app.messenger.on('refresh_timestamp', (timeStamp) => { 9 | app.timeStamp = timeStamp 10 | }) 11 | app.beforeStart(async () => { 12 | const ctx = app.createAnonymousContext() 13 | const { password } = managerConfig 14 | try { 15 | delete managerConfig.password 16 | let initManager = await ctx.model.User.findOne(managerConfig) 17 | if (initManager) { 18 | console.log('super manager has existed!') 19 | } else { 20 | managerConfig.password = password 21 | await ctx.model.User.create(managerConfig) 22 | console.log(`super manager user create success!`) 23 | } 24 | } catch (err) { 25 | console.warn(`super manager user create fail! \n`, err) 26 | } 27 | }) 28 | // 数字校验-允许提交字符串格式的数字 29 | app.validator.addRule('unstrict_number', (rule, value) => { 30 | if (value && !isNaN(value)) { 31 | value = Number(value) 32 | } 33 | if (typeof value !== 'number') { 34 | return 'should be a number' 35 | } 36 | }) 37 | app.validator.addRule('unstrict_boolean', (rule, value) => { 38 | if (typeof value === 'boolean') return 39 | if (value === 'false' || value === 'true') return 40 | return 'should be a boolean' 41 | }) 42 | } 43 | -------------------------------------------------------------------------------- /server/app/controller/abstract.js: -------------------------------------------------------------------------------- 1 | const Controller = require('egg').Controller 2 | class AbstractController extends Controller { 3 | success (data) { 4 | this.ctx.status = 200 5 | this.ctx.body = data 6 | } 7 | fail (msg) { 8 | // 弃用 9 | this.ctx.body = { 10 | success: false, 11 | msg 12 | } 13 | } 14 | error (data, code = 403) { 15 | if (typeof data === 'string') { 16 | this.ctx.throw(code, data) 17 | } else { 18 | this.ctx.throw(data.code || 403, data.msg || data.message) 19 | } 20 | } 21 | notFound (msg) { 22 | msg = msg || 'not found' 23 | this.ctx.throw(404, msg) 24 | } 25 | } 26 | module.exports = AbstractController 27 | -------------------------------------------------------------------------------- /server/app/controller/authority.js: -------------------------------------------------------------------------------- 1 | const AbstractController = require('./abstract') 2 | 3 | class AuthorityController extends AbstractController { 4 | async modifyApi () { 5 | const { apiId } = this.ctx.params 6 | const { operation } = this.ctx.request.body 7 | const authority = { operation } 8 | 9 | const isManager = await this.service.api.isManager(apiId) 10 | if (!isManager) { 11 | this.error('无权操作') 12 | } 13 | 14 | const rs = await this.service.apiAuthority.update(apiId, authority) 15 | if (!rs) { 16 | this.error('更新失败') 17 | } else { 18 | this.success('更新成功') 19 | } 20 | } 21 | async getApi () { 22 | const { apiId } = this.ctx.params 23 | const authority = (await this.service.apiAuthority.get(apiId)) || this.ctx.model.ApiAuthority() 24 | authority.apiId = apiId 25 | this.success(authority) 26 | } 27 | } 28 | module.exports = AuthorityController 29 | -------------------------------------------------------------------------------- /server/app/controller/history.js: -------------------------------------------------------------------------------- 1 | const AbstractController = require('./abstract') 2 | 3 | class HistoryController extends AbstractController { 4 | async getApi () { 5 | const { apiId } = this.ctx.params 6 | this.ctx.body = await this.service.apiHistory.get(apiId) 7 | this.ctx.status = 200 8 | } 9 | } 10 | 11 | module.exports = HistoryController 12 | -------------------------------------------------------------------------------- /server/app/controller/home.js: -------------------------------------------------------------------------------- 1 | const AbstractController = require('./abstract') 2 | 3 | class HomeController extends AbstractController { 4 | async index () { 5 | const { NODE_ENV } = this.app.config.env 6 | const { timeStamp } = this.app 7 | const assetUrl = NODE_ENV === 'development' ? '_develop' : '' 8 | await this.ctx.render('index.tpl', { 9 | assetUrl, 10 | timeStamp 11 | }) 12 | } 13 | } 14 | 15 | module.exports = HomeController 16 | -------------------------------------------------------------------------------- /server/app/controller/stat.js: -------------------------------------------------------------------------------- 1 | const AbstractController = require('./abstract') 2 | 3 | class StatController extends AbstractController { 4 | async mock () { 5 | const { start, end } = this.ctx.query 6 | this.ctx.body = await this.service.stat.getMockStat(start, end) 7 | } 8 | } 9 | 10 | module.exports = StatController 11 | -------------------------------------------------------------------------------- /server/app/middleware/api_stat.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return async function apiStat (ctx, next) { 3 | const { id } = ctx.params 4 | try { 5 | await next() 6 | ctx.service.stat.requestApi(id, true) 7 | } catch (err) { 8 | ctx.status = err.status >= 100 ? err.status : 500 9 | ctx.body = err 10 | ctx.service.stat.requestApi(id, false, err.message) 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /server/app/middleware/auth.js: -------------------------------------------------------------------------------- 1 | module.exports = options => { 2 | return async function auth (ctx, next) { 3 | let url = ctx.url 4 | if (url.indexOf('?') > 0) { 5 | url = url.substr(0, url.indexOf('?')) 6 | } 7 | if (url.indexOf('server') >= 0 && url.indexOf('client') < 0) { 8 | const user = ctx.service.cookie.getUser() 9 | if (user) { 10 | const findUser = await ctx.model.User.findById(user._id) 11 | // document object => object 12 | ctx.authUser = JSON.parse(JSON.stringify(findUser)) 13 | // 超级管理员 14 | if (user.isManager) { 15 | ctx.isManager = true 16 | } 17 | 18 | await next() 19 | } else { 20 | ctx.status = 401 21 | } 22 | } else { 23 | await next() 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /server/app/middleware/credentials.js: -------------------------------------------------------------------------------- 1 | module.exports = () => { 2 | return async function credentials (ctx, next) { 3 | ctx.set('Access-Control-Allow-Credentials', 'true') 4 | await next() 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /server/app/model/api.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose 3 | const { ObjectId } = mongoose.Schema.Types 4 | 5 | const ApiSchema = mongoose.Schema({ 6 | // 接口文档主要内容 7 | name: { 8 | type: String, 9 | unique: false 10 | }, 11 | desc: String, 12 | prodUrl: String, 13 | devUrl: String, 14 | url: String, 15 | options: { 16 | method: String, 17 | proxy: { 18 | type: Object, 19 | default: { 20 | mode: 0 21 | } 22 | }, 23 | headers: { 24 | example: {}, 25 | params: [] 26 | }, 27 | params: {}, 28 | examples: { 29 | type: Object, 30 | default: { 31 | query: null, 32 | body: null, 33 | path: null 34 | } 35 | }, 36 | response: {}, 37 | responseIndex: { 38 | type: Number, 39 | default: 0 40 | }, 41 | delay: Number 42 | }, 43 | // 以下为管理文档所需信息 44 | group: { 45 | type: ObjectId, 46 | ref: 'group', 47 | index: true 48 | }, 49 | creator: { // 创建者 50 | type: ObjectId, 51 | required: true, 52 | ref: 'user' 53 | }, 54 | manager: { // 管理员 55 | type: ObjectId, 56 | required: true, 57 | ref: 'user' 58 | }, 59 | follower: [{ // 订阅者 60 | type: ObjectId, 61 | ref: 'user' 62 | }], 63 | createTime: { 64 | type: String, 65 | default: Date.now 66 | }, 67 | modifiedTime: { 68 | type: String, 69 | default: Date.now 70 | }, 71 | isDeleted: { 72 | type: Boolean, 73 | default: false 74 | }, 75 | status: { // 接口状态,1:正常,2:不再维护,3:已下线 76 | type: Number, 77 | default: 1 78 | } 79 | }) 80 | 81 | return mongoose.model('Api', ApiSchema) 82 | } 83 | -------------------------------------------------------------------------------- /server/app/model/api_authority.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose 3 | const { ObjectId } = mongoose.Schema.Types 4 | const ApiAuthoritySchema = mongoose.Schema({ 5 | apiId: { 6 | type: ObjectId, 7 | unique: true, 8 | ref: 'api' 9 | }, 10 | operation: { // 编辑权限 11 | mode: { 12 | type: Number, 13 | default: 0 // 0 - 所有人, 1 - 组内人员 2 - 指定人员 14 | }, 15 | operator: { 16 | type: [ ObjectId ], 17 | default: [] 18 | } 19 | }, 20 | createTime: { 21 | type: Date, 22 | default: Date.now 23 | }, 24 | modifiedTime: { 25 | type: Date, 26 | default: Date.now 27 | } 28 | }) 29 | 30 | return mongoose.model('ApiAuthority', ApiAuthoritySchema) 31 | } 32 | -------------------------------------------------------------------------------- /server/app/model/api_history.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose 3 | const { ObjectId } = mongoose.Schema.Types 4 | const ApiHistorySchema = mongoose.Schema({ 5 | apiId: { 6 | type: ObjectId, 7 | unique: true, 8 | ref: 'api' 9 | }, 10 | records: [{ 11 | operator: ObjectId, 12 | operatorName: String, 13 | createTime: { 14 | type: Date, 15 | default: Date.now 16 | }, 17 | data: Object 18 | }], 19 | createTime: { 20 | type: Date, 21 | default: Date.now 22 | }, 23 | updateTime: { 24 | type: Date, 25 | default: Date.now 26 | } 27 | }) 28 | 29 | return mongoose.model('ApiHistory', ApiHistorySchema) 30 | } 31 | -------------------------------------------------------------------------------- /server/app/model/api_stat.js: -------------------------------------------------------------------------------- 1 | const moment = require('moment') 2 | module.exports = app => { 3 | const mongoose = app.mongoose 4 | const { ObjectId } = mongoose.Schema.Types 5 | // Api 数据统计表 6 | const ApiStatSchema = mongoose.Schema({ 7 | apiId: { 8 | type: ObjectId, 9 | ref: 'api' 10 | }, 11 | behavior: { 12 | type: Number, 13 | required: true 14 | }, 15 | result: { 16 | status: Boolean, 17 | msg: String 18 | }, 19 | user: ObjectId, 20 | createDay: { 21 | type: String, 22 | default: () => moment().format('YYYY-MM-DD') 23 | }, 24 | createTime: { 25 | type: Date, 26 | default: Date.now 27 | } 28 | }) 29 | 30 | return mongoose.model('ApiStat', ApiStatSchema) 31 | } 32 | -------------------------------------------------------------------------------- /server/app/model/group.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose 3 | const { ObjectId } = mongoose.Schema.Types 4 | const GroupSchema = new mongoose.Schema({ 5 | teamId: { 6 | type: ObjectId, 7 | required: false 8 | }, 9 | creator: { 10 | type: ObjectId, 11 | required: true 12 | }, 13 | manager: { 14 | type: ObjectId, 15 | required: true 16 | }, 17 | // 订阅者 18 | follower: [ 19 | { 20 | type: ObjectId, 21 | ref: 'user' 22 | } 23 | ], 24 | member: [ ObjectId ], 25 | operation: { 26 | type: Number, 27 | default: 0 // 0 - 所有人可操作,1 - 组内成员可操作 28 | }, 29 | privacy: { 30 | type: Number, 31 | default: 0 // 0 - 所有人可见, 1 - 组内成员可见, 3 - 仅自己可以 32 | }, 33 | name: { 34 | type: String, 35 | required: true 36 | // unique: true 37 | }, 38 | token: { 39 | type: String, 40 | default: '' 41 | }, 42 | level: { // 分组的层级,默认第一级,预留字段,暂时无用。 43 | type: Number, 44 | required: true, 45 | default: 1 46 | }, 47 | createTime: { 48 | type: String, 49 | default: Date.now 50 | }, 51 | modifiedTime: { 52 | type: String, 53 | default: Date.now 54 | }, 55 | // prefix: String, 56 | devPrefix: String, 57 | prodPrefix: String, 58 | // 父group 59 | pGroup: { 60 | type: ObjectId, 61 | ref: 'group', 62 | index: true 63 | }, 64 | desc: String, // 分组描述,预留字段,暂时无用。 65 | isDeleted: { 66 | type: Boolean, 67 | default: false 68 | } 69 | }) 70 | return mongoose.model('Group', GroupSchema) 71 | } 72 | -------------------------------------------------------------------------------- /server/app/model/team.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 团队;目前无用,可设计为分组的外层 3 | */ 4 | module.exports = app => { 5 | const mongoose = app.mongoose 6 | const { ObjectId } = mongoose.Schema.Types 7 | const TeamSchema = new mongoose.Schema({ 8 | name: { 9 | type: String, 10 | unique: true 11 | }, 12 | creator: { 13 | type: ObjectId, 14 | required: true 15 | }, 16 | manager: { 17 | type: ObjectId, 18 | required: true 19 | }, 20 | operator: [ ObjectId ], 21 | createTime: { 22 | type: Date, 23 | default: Date.now 24 | }, 25 | modifiedTime: { 26 | type: Date, 27 | default: Date.now 28 | }, 29 | isDeleted: { 30 | type: Boolean, 31 | default: false 32 | } 33 | }) 34 | return mongoose.model('Team', TeamSchema) 35 | } 36 | -------------------------------------------------------------------------------- /server/app/model/user.js: -------------------------------------------------------------------------------- 1 | module.exports = app => { 2 | const mongoose = app.mongoose 3 | const { ObjectId } = mongoose.Schema.Types 4 | 5 | const UserSchema = new mongoose.Schema({ 6 | email: { 7 | type: String, 8 | unique: true, 9 | required: true 10 | }, 11 | password: { 12 | type: String, 13 | required: true 14 | }, 15 | name: { 16 | type: String, 17 | required: true 18 | }, 19 | createTime: { 20 | type: Date, 21 | default: Date.now 22 | }, 23 | modifiedTime: { 24 | type: Date, 25 | default: Date.now 26 | }, 27 | isDeleted: { 28 | type: Boolean, 29 | default: false 30 | }, 31 | isManager: { 32 | type: Boolean, 33 | default: false 34 | }, 35 | teamId: [ObjectId], 36 | // 收藏夹 37 | favorites: [ 38 | { 39 | type: ObjectId, 40 | ref: 'Group' 41 | } 42 | ] 43 | }) 44 | return mongoose.model('User', UserSchema) 45 | } 46 | -------------------------------------------------------------------------------- /server/app/public/api.js: -------------------------------------------------------------------------------- 1 | // import Request from './request' 2 | // import Response from './response' 3 | const Schema = require('./schema') 4 | /** 接口文档 模型 5 | * 主要要素 6 | * name 文档名称 7 | * proxy 代理信息 8 | * desc 文档描述 9 | * request 请求( 10 | * method 请求方法 11 | * prodUrl 生产环境地址 12 | * devUrl 开发环境地址 13 | * body 请求体 Schema 14 | * query 请求参数 Schema 15 | * headers 请求头 Schema 16 | * ) 17 | * response 响应 ( 18 | * status_code 状态码 19 | * status_text 状态内容 20 | * body 响应体 Schema 21 | * ) 22 | */ 23 | 24 | function initData () { 25 | return { 26 | prodUrl: null, 27 | devUrl: null, 28 | name: '', 29 | group: '', 30 | desc: null, 31 | creator: null, 32 | manager: null, 33 | follower: [], 34 | options: { 35 | proxy: { 36 | mode: 0 37 | }, 38 | response: [new Schema()], 39 | responseIndex: 0, 40 | headers: { 41 | example: null, 42 | params: [] 43 | }, 44 | method: 'get', 45 | delay: 0, 46 | examples: { 47 | query: null, 48 | body: null, 49 | path: null 50 | }, 51 | params: { 52 | query: [], 53 | body: [], 54 | path: [] 55 | } 56 | } 57 | } 58 | } 59 | 60 | module.exports = initData 61 | -------------------------------------------------------------------------------- /server/app/public/importOrigin.js: -------------------------------------------------------------------------------- 1 | function buildApisFormJson (json, group) { 2 | return json.map(i => ({...i, group: group._id})) 3 | } 4 | 5 | module.exports = buildApisFormJson 6 | -------------------------------------------------------------------------------- /server/app/public/importSwagger.js: -------------------------------------------------------------------------------- 1 | const Api = require('./api') 2 | const { buildReqParams, buildResponse } = require('./swaggerUtilSet') 3 | /** 4 | * 从swagger json配置文件中构建apis 5 | * @param {json} json json对象 6 | * @param {group} Obejct 群组信息 7 | * @param {param} Obejct 用户输入信息 8 | * @return {[type]} Obejct 构建完成之后组件弹窗 9 | */ 10 | function buildApisFormSwagger (json, group, param) { 11 | let {devUrl = '', prodUrl = ''} = param 12 | // swagger 导出 JSON 格式 13 | /* 14 | * swagger: 版本 15 | * info: { 16 | * version: 1.0 接口版本 17 | * title: '' 接口名字 18 | * license: '' 许可证 19 | * } 20 | * host: '' 21 | * basePath: '' 22 | * tags: { } 23 | * paths: { 24 | * "路径": { 25 | * 'get': { 26 | * } 27 | * } 28 | * } 29 | */ 30 | let { 31 | info = {}, 32 | paths = {} 33 | } = json || {} 34 | let _definitions = json.definitions || {} 35 | 36 | let { title = '' } = info 37 | 38 | const swaggerApis = [] 39 | 40 | for (let [key, value] of Object.entries(paths)) { 41 | try { 42 | for (let method in value) { 43 | const methodValue = value[method] 44 | const { summary, tags = [] } = methodValue || {} 45 | const api = Api() 46 | 47 | api.url = key 48 | api.devUrl = `${devUrl}${key}` 49 | api.prodUrl = `${prodUrl}${key}` 50 | api.name = `${title ? `${title}-` : ''}${tags.join(',') ? `${tags.join(',')}-` : ''}${summary}` 51 | api.desc = summary 52 | api.group = group._id 53 | api.options.method = method 54 | api.options.params = buildReqParams(api.options.params, methodValue.parameters || [], _definitions) 55 | api.options.response = buildResponse(methodValue.responses, _definitions) 56 | 57 | swaggerApis.push(api) 58 | } 59 | } catch (e) { 60 | console.log(`${key} API 有误 无法解析`) 61 | console.warn(e) 62 | } 63 | } 64 | 65 | return swaggerApis 66 | } 67 | 68 | module.exports = buildApisFormSwagger 69 | -------------------------------------------------------------------------------- /server/app/public/param.js: -------------------------------------------------------------------------------- 1 | // 参数(属性)列表 模型 2 | class Param { 3 | constructor (initParam = {}) { 4 | const { key = null, type = 'string', required = true, comment = null } = initParam 5 | this.key = key 6 | this.type = type 7 | this.required = required 8 | this.comment = comment 9 | } 10 | } 11 | 12 | module.exports = Param 13 | -------------------------------------------------------------------------------- /server/app/public/schema.js: -------------------------------------------------------------------------------- 1 | const Param = require('./param') 2 | 3 | // 字段结构 模型 4 | class Schema { 5 | constructor (index = 1) { 6 | this.status = 200 7 | this.statusText = `status${index}` 8 | this.example = null 9 | this.params = [new Param()] 10 | } 11 | } 12 | 13 | module.exports = Schema 14 | -------------------------------------------------------------------------------- /server/app/public/util.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 分词逻辑 3 | * @param {*} q 4 | */ 5 | function parseWord (q) { 6 | let result = [] 7 | 8 | let parseResult = q.split(' ') 9 | // 只允许字母数字汉字和下划线并去重 10 | const filterCondition = new RegExp(/^[a-zA-Z0-9_\u4e00-\u9fa5]+$/) 11 | parseResult.map((word) => { 12 | filterCondition.test(word) && result.push(word) 13 | }) 14 | result = [...new Set(result)] 15 | 16 | return result 17 | } 18 | 19 | module.exports = { 20 | parseWord 21 | } 22 | -------------------------------------------------------------------------------- /server/app/service/api_authority.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | const { authority } = require('../../constants') 3 | const { 4 | OPERATION_ALL, 5 | OPERATION_MEMBER, 6 | OPERATION_DESIGNEE 7 | } = authority 8 | 9 | class ApiAuthority extends Service { 10 | update (apiId, authority) { 11 | authority = (typeof authority === 'object') ? authority : {} 12 | authority.modifiedTime = Date.now() 13 | return this.ctx.model.ApiAuthority.findOneAndUpdate({ 14 | apiId 15 | }, authority, { 16 | setDefaultsOnInsert: true, 17 | new: true, 18 | upsert: true 19 | }) 20 | } 21 | get (apiId) { 22 | return this.ctx.model.ApiAuthority.findOne({ 23 | apiId 24 | }) 25 | } 26 | isWritable (authority, group, authId) { 27 | if (!authority) { 28 | return { status: true } 29 | } 30 | const { mode, operator } = authority.operation 31 | switch (mode) { 32 | case OPERATION_ALL: 33 | return { status: true } 34 | case OPERATION_MEMBER: 35 | return { 36 | status: !!group.member.find(m => m.toString() === authId), 37 | msg: '仅组内成员可操作' 38 | } 39 | case OPERATION_DESIGNEE: 40 | return { 41 | status: !!(operator.find(o => o.toString() === authId) || (group.manager.toString() === authId)), 42 | msg: '仅指定人员可操作' 43 | } 44 | default: 45 | return { status: true } 46 | } 47 | } 48 | } 49 | 50 | module.exports = ApiAuthority 51 | -------------------------------------------------------------------------------- /server/app/service/api_history.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | 3 | class ApiHistory extends Service { 4 | get (apiId) { 5 | return this.ctx.model.ApiHistory.findOne({ 6 | apiId 7 | }) 8 | } 9 | create (api) { 10 | return this.ctx.model.ApiHistory({ 11 | apiId: api._id, 12 | data: api 13 | }).save() 14 | } 15 | push (api) { 16 | const { _id, name } = this.ctx.authUser 17 | const record = { 18 | data: api, 19 | operator: _id, 20 | operatorName: name 21 | } 22 | return this.ctx.model.ApiHistory.findOneAndUpdate({ 23 | apiId: api._id 24 | }, { 25 | updateTime: Date.now(), 26 | $push: { 27 | records: { 28 | $each: [ record ], 29 | $sort: { createTime: 1 }, 30 | $slice: -5 31 | } 32 | } 33 | }, { 34 | setDefaultsOnInsert: true, 35 | new: true, 36 | upsert: true 37 | }) 38 | } 39 | } 40 | 41 | module.exports = ApiHistory 42 | -------------------------------------------------------------------------------- /server/app/service/cache.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 缓存service,可作为验证码服务,因为多进程非共同内存空间,故目前无用。 3 | */ 4 | const LRU = require('lru-cache') 5 | const DEFAULT_MAX_AGE = 1 * 1000 * 60 6 | const options = { 7 | max: 500, 8 | // length: function (n, key) { return n * 2 + key.length }, 9 | // dispose: function (key, n) { n.close() }, 10 | maxAge: DEFAULT_MAX_AGE 11 | } 12 | const Cache = LRU(options) 13 | 14 | const Service = require('egg').Service 15 | 16 | class CacheService extends Service { 17 | create (key, value, maxAge = DEFAULT_MAX_AGE) { 18 | return Cache.set(key, value, maxAge) 19 | } 20 | verifyCodeCache (key, length, maxAge = DEFAULT_MAX_AGE) { 21 | const code = Array.from({ length }, () => Math.ceil(Math.random() * 9)).join('') 22 | return this.create(key, code, maxAge) && code 23 | } 24 | get (key) { 25 | return Cache.get(key) 26 | } 27 | del (key) { 28 | return Cache.del(key) 29 | } 30 | has (key) { 31 | return Cache.has(key) 32 | } 33 | } 34 | 35 | module.exports = CacheService 36 | -------------------------------------------------------------------------------- /server/app/service/cookie.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | 3 | class Cookie extends Service { 4 | set (key, value, config = {}) { 5 | // cookie有效期,一个月 6 | const expires = new Date(Date.now() + 1000 * 60 * 60 * 24 * 30) 7 | this.ctx.cookies.set(key, value, Object.assign({ 8 | expires, 9 | overwrite: true, 10 | secure: false, 11 | encrypt: true // 加密传输 12 | }, config)) 13 | } 14 | get (key) { 15 | return this.ctx.cookies.get(key, { 16 | overwrite: true, 17 | encrypt: true 18 | }) 19 | } 20 | setUser (user) { 21 | this.set('mockerUser', JSON.stringify(user)) 22 | } 23 | getUser () { 24 | try { 25 | return JSON.parse(this.get('mockerUser')) 26 | } catch (e) { 27 | return null 28 | } 29 | } 30 | clearUser (user) { 31 | this.set('mockerUser', '', { 32 | expires: Date.now() 33 | }) 34 | } 35 | } 36 | 37 | module.exports = Cookie 38 | -------------------------------------------------------------------------------- /server/app/service/email.js: -------------------------------------------------------------------------------- 1 | const nodemailer = require('nodemailer') 2 | const Service = require('egg').Service 3 | 4 | class Email extends Service { 5 | constructor (...args) { 6 | super(...args) 7 | this.transporter = nodemailer.createTransport(this.config.transporter) 8 | } 9 | sent (to, subject, html) { 10 | const { auth, appName } = this.config.transporter 11 | const mailOptions = { 12 | from: `${appName} <${auth.user}>`, 13 | to, 14 | subject, 15 | html 16 | } 17 | return this.transporter.sendMail(mailOptions).catch(error => { 18 | this.ctx.logger.info('Message %s sent error: %s', error) 19 | return error 20 | }) 21 | } 22 | resetPassword (verifyCode, user) { 23 | const html = ` 24 | 重设密码 25 |

账户名:${user.name}

26 |

验证码:${verifyCode}

27 | ` 28 | return this.sent(user.email, 'Api Mocker 找回密码', html) 29 | } 30 | passwordTicket (ticket, user) { 31 | const url = `${this.config.clientRoot}/#/reset-pass?ticket=${ticket}` 32 | const html = ` 33 | 找回密码 34 |

账户名:${user.name}

35 |

链接:${url}

36 |

提示:如果无法链接点击,请把链接直接复制到浏览器地址栏中访问

37 | ` 38 | return this.sent(user.email, 'Api Mocker 找回密码', html) 39 | } 40 | getApiDocUrl (api) { 41 | return `${this.config.clientRoot}/#/doc/${api.group}/${api._id}` 42 | } 43 | notifyApiCreate (group, api, users) { 44 | const html = ` 45 | API:${api.name} 46 |

分组:${group.name}

47 |

创建者:${this.ctx.authUser.name}

48 |

链接地址:${this.getApiDocUrl(api)}

49 | ` 50 | users.forEach(user => this.sent(user.email, 'Api Mocker 接口新增提醒', html)) 51 | } 52 | notifyApiDelete (group, api, users) { 53 | const html = ` 54 | API:${api.name} 55 |

分组:${group.name}

56 |

删除者:${this.ctx.authUser.name}

57 |

链接地址:${this.getApiDocUrl(api)}

58 | ` 59 | users.forEach(user => this.sent(user.email, 'Api Mocker 接口删除提醒', html)) 60 | } 61 | notifyApiChange (api, users) { 62 | const html = ` 63 | API:${api.name} 64 |

修改者:${this.ctx.authUser.name}

65 |

链接地址:${this.getApiDocUrl(api)}

66 | ` 67 | users.forEach(user => this.sent(user.email, 'Api Mocker 接口变动提醒', html)) 68 | } 69 | } 70 | 71 | module.exports = Email 72 | -------------------------------------------------------------------------------- /server/app/service/stat.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | 3 | const API_BEHAVIOR_MOCK = 1 // 数据统计行为->请求mock数据 4 | 5 | class Stat extends Service { 6 | // 保存数据方法异步执行 7 | saveApiStat (apiId, behavior, result) { 8 | return this.ctx.model.ApiStat({ 9 | apiId, 10 | behavior, 11 | result 12 | }).save() 13 | } 14 | requestApi (apiId, status, msg) { 15 | return this.saveApiStat(apiId, API_BEHAVIOR_MOCK, { 16 | status, 17 | msg 18 | }) 19 | } 20 | getMockStat (start, end) { 21 | return this.ctx.model.ApiStat.aggregate([ 22 | { 23 | $match: { 24 | behavior: API_BEHAVIOR_MOCK, 25 | createDay: { 26 | $gte: start, 27 | $lte: end 28 | } 29 | } 30 | }, 31 | { 32 | $group: { 33 | _id: '$createDay', 34 | count: { $sum: 1 } 35 | } 36 | }, 37 | { $sort: { _id: 1 } } 38 | ]) 39 | } 40 | } 41 | 42 | module.exports = Stat 43 | -------------------------------------------------------------------------------- /server/app/service/ticket.js: -------------------------------------------------------------------------------- 1 | const Crypto = require('crypto') 2 | const Service = require('egg').Service 3 | 4 | class Ticket extends Service { 5 | create (id, act, maxAge = 15 * 60 * 1000) { 6 | const Cipher = Crypto.createCipher('aes192', 'a password') 7 | const ticket = JSON.stringify({ 8 | id, 9 | act, 10 | maxAge, 11 | date: new Date() 12 | }) 13 | return Cipher.update(ticket, 'utf8', 'hex') + Cipher.final('hex') 14 | } 15 | check (ticket, act, id, modifiedTime) { 16 | const Decipher = Crypto.createDecipher('aes192', 'a password') 17 | let rs 18 | try { 19 | rs = Decipher.update(ticket, 'hex', 'utf8') + Decipher.final('utf8') 20 | rs = JSON.parse(rs) 21 | } catch (err) { 22 | return { success: false, msg: '未知ticket' } 23 | } 24 | const expires = +new Date(rs.date) + rs.maxAge 25 | if (expires < Date.now()) { 26 | return { success: false, msg: 'ticket过期' } 27 | } 28 | if (act !== rs.act) { 29 | return { success: false, msg: 'ticket错误' } 30 | } 31 | if (id && id !== rs.id) { 32 | return { success: false, msg: 'ticket有误' } 33 | } 34 | if (modifiedTime && modifiedTime > new Date(rs.date)) { 35 | return { success: false, msg: 'ticket失效' } 36 | } 37 | return { success: true, data: rs } 38 | } 39 | } 40 | 41 | module.exports = Ticket 42 | -------------------------------------------------------------------------------- /server/app/service/user.js: -------------------------------------------------------------------------------- 1 | const Service = require('egg').Service 2 | const md5 = require('blueimp-md5') 3 | 4 | class UserService extends Service { 5 | async create (user) { 6 | return (await this.ctx.model.User({ 7 | email: user.email, 8 | password: md5(user.password, this.config.md5Key), 9 | name: user.name 10 | }).save()).toObject() 11 | } 12 | getByEmail (email) { 13 | return this.ctx.model.User.findOne({ 14 | email 15 | }).lean() 16 | } 17 | getById (id) { 18 | return this.ctx.model.User.findOne({ 19 | _id: id 20 | }) 21 | } 22 | getByIds (ids) { 23 | return this.ctx.model.User.find({ 24 | _id: { 25 | $in: ids 26 | } 27 | }) 28 | } 29 | find (q) { 30 | const reg = new RegExp(`.*${q}.*`, 'i') 31 | return this.ctx.model.User.find({ 32 | isDeleted: false, 33 | $or: [ 34 | { name: reg }, 35 | { email: reg } 36 | ] 37 | }) 38 | } 39 | updatePassword (email, password) { 40 | return this.ctx.model.User.findOneAndUpdate({ 41 | email 42 | }, { 43 | password: md5(password, this.config.md5Key), 44 | modifiedTime: new Date() 45 | }, { new: true }).lean() 46 | } 47 | updatePasswordByOldPassword (oldPassword, newPassword) { 48 | return this.ctx.model.User.findOneAndUpdate({ 49 | _id: this.ctx.authUser._id, 50 | password: md5(oldPassword, this.config.md5Key) 51 | }, { 52 | password: md5(newPassword, this.config.md5Key), 53 | modifiedTime: new Date() 54 | }, { new: true }).lean() 55 | } 56 | // 修改用户信息 57 | update (user) { 58 | const authId = this.ctx.authUser._id 59 | return this.ctx.model.User.findOneAndUpdate({ 60 | _id: authId 61 | }, { 62 | name: user.name, 63 | email: user.email, 64 | modifiedTime: new Date() 65 | }, { new: true }).lean() 66 | } 67 | } 68 | 69 | module.exports = UserService 70 | -------------------------------------------------------------------------------- /server/app/view/index.tpl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | Api Mocker 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /server/appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | - nodejs_version: '8' 4 | 5 | install: 6 | - ps: Install-Product node $env:nodejs_version 7 | - npm i npminstall && node_modules\.bin\npminstall 8 | 9 | test_script: 10 | - node --version 11 | - npm --version 12 | - npm run test 13 | 14 | build: off 15 | -------------------------------------------------------------------------------- /server/config/config.default.js: -------------------------------------------------------------------------------- 1 | const core = require('./core.default') 2 | const coreConfig = core() 3 | 4 | module.exports = appInfo => { 5 | const config = { 6 | bodyParser: { 7 | jsonLimit: '5mb' // 不能再大了,再大接口实在太不合理了 8 | }, 9 | keys: `${appInfo.name}_1490324849354_6879`, 10 | // 允许跨域携带cookie 11 | cors: { 12 | credentials: true 13 | }, 14 | security: { 15 | csrf: { 16 | enable: false 17 | }, 18 | domainWhiteList: [ '*' ] 19 | }, 20 | middleware: ['auth'], 21 | // 邮件推送间隔 22 | pushInterval: { 23 | // 一个小时内修改api不会连续推送 24 | api: 1000 * 60 * 60 25 | }, 26 | httpclient: { 27 | request: { 28 | timeout: 1000 * 60 * 5 29 | } 30 | }, 31 | multipart: { 32 | fileSize: '500Mb', 33 | mode: 'stream' 34 | }, 35 | view: { 36 | defaultViewEngine: 'nunjucks', 37 | mapping: { 38 | '.tpl': 'nunjucks' 39 | } 40 | }, 41 | ...coreConfig 42 | } 43 | return config 44 | } 45 | -------------------------------------------------------------------------------- /server/config/config.local.js: -------------------------------------------------------------------------------- 1 | module.exports = appInfo => { 2 | return { 3 | clientRoot: 'http://localhost:8080' 4 | } 5 | } -------------------------------------------------------------------------------- /server/config/config.prod.js: -------------------------------------------------------------------------------- 1 | module.exports = appInfo => { 2 | return { 3 | clientRoot: 'http://xxx/mock' 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /server/config/core.default.js: -------------------------------------------------------------------------------- 1 | // mongo 数据库配置 2 | module.exports = config => ({ 3 | // mongo 配置 4 | mongoose: { 5 | url: 'mongodb://127.0.0.1/api-mock' 6 | }, 7 | // 密码加密的key 8 | md5Key: 'xxx', 9 | // 发送邮件配置 10 | transporter: { 11 | appName: 'Api Mocker', 12 | host: 'smtp.exmail.qq.com', 13 | secure: true, 14 | port: 465, 15 | auth: { 16 | user: 'xxx@xxx.cn', 17 | pass: 'xxx' 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /server/config/manager.default.js: -------------------------------------------------------------------------------- 1 | // 初始化创建超级管理员 2 | module.exports = { 3 | email: 'xxx@xxx.cn', 4 | password: 'xxx', 5 | name: '超级管理员', 6 | isManager: true 7 | } -------------------------------------------------------------------------------- /server/config/plugin.js: -------------------------------------------------------------------------------- 1 | 2 | exports.security = false 3 | 4 | exports.cors = { 5 | enable: true, 6 | package: 'egg-cors' 7 | } 8 | 9 | exports.mongoose = { 10 | enable: true, 11 | package: 'egg-mongoose' 12 | } 13 | 14 | exports.validate = { 15 | package: 'egg-validate' 16 | } 17 | 18 | exports.nunjucks = { 19 | enable: true, 20 | package: 'egg-view-nunjucks' 21 | } 22 | 23 | exports.multipart = { 24 | package: 'egg-multipart' 25 | } 26 | 27 | -------------------------------------------------------------------------------- /server/constants/authority.js: -------------------------------------------------------------------------------- 1 | /* ***** */ 2 | /* 隐私性 */ 3 | /* ***** */ 4 | 5 | /* 所有人 */ 6 | exports.PRIVACY_ALL = 0 7 | 8 | /* 组员 */ 9 | exports.PRIVACY_MEMBER = 1 10 | 11 | /* 指定人员 */ 12 | exports.PRIVACY_DESIGNEE = 2 13 | 14 | /* 仅自己 */ 15 | exports.PRIVACY_SELF = 3 16 | 17 | /* ******* */ 18 | /* 操作权限 */ 19 | /* ******* */ 20 | 21 | /* 所有人 */ 22 | exports.OPERATION_ALL = 0 23 | 24 | /* 组员 */ 25 | exports.OPERATION_MEMBER = 1 26 | 27 | /* 指定人员 */ 28 | exports.OPERATION_DESIGNEE = 2 29 | 30 | /* 仅自己 */ 31 | exports.OPERATION_SELF = 3 32 | -------------------------------------------------------------------------------- /server/constants/index.js: -------------------------------------------------------------------------------- 1 | const authority = require('./authority') 2 | const permission = require('./permission') 3 | 4 | module.exports = { 5 | authority, 6 | permission 7 | } 8 | -------------------------------------------------------------------------------- /server/constants/permission.js: -------------------------------------------------------------------------------- 1 | /* 不可见的 */ 2 | exports.INVISIBLE = 0 3 | 4 | /* 只读的 */ 5 | exports.READONLY = 1 6 | 7 | /* 可写的 */ 8 | exports.WRITABLE = 2 9 | 10 | /* 无限制 */ 11 | exports.ROOT = 3 12 | -------------------------------------------------------------------------------- /server/ecosystem.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | apps: [{ 3 | name: 'api-mocker-server', 4 | script: 'index.js', 5 | env_test: { 6 | NODE_ENV: 'development', 7 | EGG_SERVER_ENV: 'test' 8 | }, 9 | env_production: { 10 | NODE_ENV: 'production', 11 | EGG_SERVER_ENV: 'prod' 12 | } 13 | }] 14 | } 15 | -------------------------------------------------------------------------------- /server/index.js: -------------------------------------------------------------------------------- 1 | // const path = require('path') 2 | process.env.NODE_ENV = 'production' 3 | 4 | require('egg').startCluster({ 5 | // 若需要https服务,请取消注释,并配置好证书文件 6 | https: false, 7 | baseDir: __dirname, 8 | workers: 4, 9 | port: 7001 // default to 7001 10 | }) 11 | -------------------------------------------------------------------------------- /server/makefile: -------------------------------------------------------------------------------- 1 | SHELL := /bin/bash 2 | 3 | .PHONY: prerequ-program client server prod_server prod_client 4 | 5 | 6 | install:|prerequ-program 7 | 8 | define require_install 9 | if test "$(shell which $(1))" = ""; \ 10 | then \ 11 | brew install $(2); \ 12 | else \ 13 | echo $(1) is exists. skip install; \ 14 | fi 15 | endef 16 | 17 | prerequ-program: 18 | @$(call require_install,mongod,mongo) 19 | mkdir -p ./db/ 20 | if [ "${shell pgrep mongod}" = "" ]; then mongod --bind_ip 127.0.0.1 --fork --dbpath ./db/ --logpath ./db/mongod.log; fi 21 | @echo "start mongod success!" 22 | 23 | # 开发模式 24 | server: 25 | cd server && npm install && npm run dev 26 | 27 | client: 28 | cd client && npm install && npm run dev 29 | 30 | # 生产模式 31 | prod_server: 32 | cd server && npm install && npm start 33 | 34 | prod_client: 35 | cd client && npm install && npm run build 36 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.3.1", 4 | "description": "api-mocker-server", 5 | "private": true, 6 | "dependencies": { 7 | "axios": "^0.21.0", 8 | "blueimp-md5": "^2.10.0", 9 | "egg": "^2.0.0", 10 | "egg-cors": "^2.0.0", 11 | "egg-mongoose": "2.2.1", 12 | "egg-multipart": "^2.10.1", 13 | "egg-validate": "^1.0.0", 14 | "egg-view-nunjucks": "^2.2.0", 15 | "http-assert": "^1.3.0", 16 | "lodash": "^4.17.14", 17 | "lru-cache": "^4.1.1", 18 | "mocker-dsl-core": "^0.1.2", 19 | "moment": "^2.21.0", 20 | "mongoose": "^4.13.5", 21 | "nodemailer": "^4.4.0", 22 | "path-to-regexp": "^2.1.0", 23 | "ramda": "^0.25.0", 24 | "stream-wormhole": "^1.1.0" 25 | }, 26 | "devDependencies": { 27 | "autod": "^3.0.1", 28 | "autod-egg": "^1.0.0", 29 | "egg-bin": "^4.3.6", 30 | "egg-ci": "^1.8.0", 31 | "egg-mock": "^3.13.1", 32 | "eslint": "^4.12.1", 33 | "eslint-config-standard": "10.2.1", 34 | "eslint-plugin-import": "2.7.0", 35 | "eslint-plugin-node": "5.1.1", 36 | "eslint-plugin-promise": "3.5.0", 37 | "eslint-plugin-standard": "3.0.1", 38 | "pm2": "2.4.2", 39 | "supertest": "^3.0.0", 40 | "webstorm-disable-index": "^1.2.0" 41 | }, 42 | "engines": { 43 | "node": ">=8.0.0" 44 | }, 45 | "scripts": { 46 | "dev": "egg-bin dev", 47 | "pm2": "pm2 start ecosystem.config.js", 48 | "pm2-test": "pm2 start ecosystem.config.js --env test", 49 | "start": "node index.js", 50 | "test": "npm run test-local", 51 | "fix": "npm run lint -- --fix", 52 | "test-local": "egg-bin test", 53 | "cov": "egg-bin cov", 54 | "lint": "eslint .", 55 | "ci": "npm run lint && npm run cov", 56 | "autod": "autod", 57 | "debug": "egg-bin debug" 58 | }, 59 | "ci": { 60 | "version": "8" 61 | }, 62 | "repository": { 63 | "type": "git", 64 | "url": "git@github.com:DXY-F2E/api-mocker.git" 65 | }, 66 | "author": "zhangfx", 67 | "license": "GPL-3.0" 68 | } 69 | -------------------------------------------------------------------------------- /server/sql-scripts/api_stat-add-create_day.js: -------------------------------------------------------------------------------- 1 | // mongo v3.4 2 | 3 | db.apistats.aggregate( 4 | [ 5 | { $addFields: { 6 | createDay: { $dateToString: { format: '%Y-%m-%d', date: '$createTime' } } 7 | } }, 8 | { $out: 'apistats' } 9 | ] 10 | ) 11 | -------------------------------------------------------------------------------- /server/test/.setup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const mm = require('egg-mock'); 4 | 5 | global.mm = global.mock = mm; 6 | global.request = require('supertest'); 7 | global.assert = require('assert'); 8 | 9 | let app; 10 | before(() => { 11 | app = global.app = mm.app(); 12 | return app.ready(); 13 | }); 14 | 15 | after(() => app.close()); 16 | -------------------------------------------------------------------------------- /server/test/app/controller/api.test.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | const { app, mock, assert } = require('egg-mock/bootstrap'); 3 | 4 | describe('test/app/controller/api.test.js', () => { 5 | 6 | it('should get /', () => app 7 | .httpRequest() 8 | .get('/server/api') 9 | .expect(401)) 10 | 11 | }) 12 | --------------------------------------------------------------------------------