├── .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 |
2 |
3 |
4 |
5 |
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 |
2 |
6 |
7 |
8 |
53 |
76 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/MockData.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
19 |
21 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/Param.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{param.key}}
6 |
7 | {{param.type}}[{{param.items.type}}]
8 | {{param.required ? '是' : '否'}}
9 | {{param.maxLength ? param.maxLength : ''}}
10 |
11 | {{param.example !== undefined ? param.example : '无'}}
12 |
13 |
14 |
15 |
16 |
17 |
18 |
68 |
82 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/Params.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
20 |
21 |
30 |
31 |
32 |
33 |
34 |
79 |
103 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/ParamsTable.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | 参数名
6 |
7 | 类型
8 | 是否必填
9 | 最大长度
10 |
11 | 示例
12 |
13 |
14 |
15 |
23 |
24 |
25 |
26 |
57 |
121 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/Schema.vue:
--------------------------------------------------------------------------------
1 |
2 |
28 |
29 |
30 |
78 |
117 |
--------------------------------------------------------------------------------
/client/src/components/ApiDoc/Schemas.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
12 |
13 |
14 |
51 |
--------------------------------------------------------------------------------
/client/src/components/common/CopyField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 复制
7 |
8 |
9 |
10 |
40 |
--------------------------------------------------------------------------------
/client/src/components/common/CreateGroup.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
11 |
12 |
13 |
52 |
57 |
--------------------------------------------------------------------------------
/client/src/components/common/Header/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
36 |
37 |
38 |
71 |
110 |
--------------------------------------------------------------------------------
/client/src/components/common/InputFinder.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{sd.name}}
5 | x
6 |
7 |
11 |
12 |
13 |
14 |
47 |
84 |
--------------------------------------------------------------------------------
/client/src/components/common/Layout.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 |
37 |
--------------------------------------------------------------------------------
/client/src/components/common/Pagination.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
40 |
--------------------------------------------------------------------------------
/client/src/components/common/QuillEditor.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
76 |
88 |
--------------------------------------------------------------------------------
/client/src/components/common/SearchInput.vue:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
52 |
--------------------------------------------------------------------------------
/client/src/components/common/SearchUser.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
44 |
--------------------------------------------------------------------------------
/client/src/components/common/Simditor/index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
60 |
72 |
--------------------------------------------------------------------------------
/client/src/components/common/UserSelector.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
18 | {{getUserLabel(item)}}
19 |
20 |
21 |
22 |
23 |
62 |
67 |
--------------------------------------------------------------------------------
/client/src/components/common/charts/Line.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | 折线图
4 |
5 |
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 |
2 |
3 |
4 |
5 | {{item.type}}:
6 | {{ item.label }}
7 |
8 |
9 |
13 |
14 |
15 |
16 |
66 |
67 |
78 |
--------------------------------------------------------------------------------
/client/src/components/stat/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
15 |
38 |
--------------------------------------------------------------------------------
/client/src/components/stat/Mock.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
11 |
13 |
14 |
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 |
2 |
3 |
4 | 接口管理系统登录中...
5 |
6 |
7 |
8 |
9 |
45 |
47 |
--------------------------------------------------------------------------------
/client/src/views/auth/FindPass.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 找回密码
5 |
6 |
7 |
8 |
9 | 发送邮件
10 | --> 登录
11 |
12 |
13 |
14 |
15 |
16 |
57 |
59 |
--------------------------------------------------------------------------------
/client/src/views/auth/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
10 |
75 |
--------------------------------------------------------------------------------
/client/src/views/auth/Login.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 接口管理系统
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | 登录
13 | 找回密码
14 | --> 注册
15 |
16 |
17 |
18 |
19 |
20 |
78 |
80 |
--------------------------------------------------------------------------------
/client/src/views/auth/Register.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 账号注册
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 注册
19 | --> 登录
20 |
21 |
22 |
23 |
24 |
25 |
86 |
88 |
--------------------------------------------------------------------------------
/client/src/views/diff/Api.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
7 | {{api.operatorName}} 修改于:
8 |
9 |
13 |
14 |
15 |
16 |
20 |
无数据
22 |
23 |
24 |
63 |
85 |
--------------------------------------------------------------------------------
/client/src/views/diff/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
86 |
101 |
--------------------------------------------------------------------------------
/client/src/views/document/ApiContent.vue:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
43 |
45 |
--------------------------------------------------------------------------------
/client/src/views/document/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
61 |
63 |
--------------------------------------------------------------------------------
/client/src/views/document/Overview.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
11 |
--------------------------------------------------------------------------------
/client/src/views/edit/ApiAuthor.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
7 |
8 |
20 |
25 |
--------------------------------------------------------------------------------
/client/src/views/edit/ApiHistory.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 | {{record.data.modifiedTime | dateFormat('YYYY-MM-DD H:mm')}}
5 | {{record.operatorName}}
6 | 加载
7 |
8 |
9 |
10 |
11 |
56 |
82 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/DescBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
27 |
32 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/ResultBox.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
10 |
11 |
59 |
64 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/SettingField.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
{{title}}
4 |
5 | {{fullscreen ? 'Esc' : '全屏'}}
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
56 |
128 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/response/Config.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
11 |
12 |
16 |
17 |
18 |
19 |
20 |
21 | ms
22 |
23 |
24 |
25 |
26 |
27 |
65 |
92 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/response/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
77 |
105 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/response/StatusSetting.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
46 |
55 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/schema/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
10 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
75 |
117 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/schema/params/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
67 |
--------------------------------------------------------------------------------
/client/src/views/edit/apiBox/schema/params/ParamFill.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
17 |
18 |
19 |
20 |
21 |
51 |
53 |
--------------------------------------------------------------------------------
/client/src/views/list/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
26 |
--------------------------------------------------------------------------------
/client/src/views/list/components/GroupList.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | {{getParent(scope.row.parent)}}
8 |
9 |
10 |
11 |
12 | {{ row.creator ? row.creator.name : '未知' }}
13 |
14 |
15 |
16 |
17 | 接口列表
18 | 查看文档
19 |
20 |
21 |
22 |
23 |
24 |
25 |
56 |
--------------------------------------------------------------------------------
/client/src/views/list/components/PageNav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
11 |
12 |
13 |
40 |
--------------------------------------------------------------------------------
/client/src/views/manage/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
25 |
--------------------------------------------------------------------------------
/client/src/views/manage/Nav.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
17 |
18 |
19 |
20 |
24 |
--------------------------------------------------------------------------------
/client/src/views/manage/api/ApiStatus.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | 正常
7 | 不再维护
8 | 已下线
9 |
10 |
11 |
12 |
16 |
17 |
18 |
19 |
50 |
--------------------------------------------------------------------------------
/client/src/views/manage/api/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | 我创建的 API
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
15 |
103 |
109 |
--------------------------------------------------------------------------------
/client/src/views/manage/group/GroupControl.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | api 管理
5 | 编辑
6 | 删除
7 |
8 | 文档
9 | 认领
10 |
11 |
12 |
13 |
74 |
76 |
--------------------------------------------------------------------------------
/client/src/views/manage/profile/Index.vue:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
14 |
35 |
44 |
--------------------------------------------------------------------------------
/client/src/views/manage/profile/Menu.vue:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
40 |
109 |
--------------------------------------------------------------------------------
/client/src/views/manage/profile/UserItem.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | {{user[item.key]}}
5 | 修改
6 |
7 |
8 |
12 |
15 |
16 | 保存
17 | 取消
18 |
19 |
20 |
21 |
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 |
--------------------------------------------------------------------------------