├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .npmignore ├── .npmrc ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── README.md ├── client ├── Application.js ├── common.js ├── components │ ├── AceEditor │ │ ├── AceEditor.js │ │ ├── AceEditor.scss │ │ └── mockEditor.js │ ├── AuthenticatedComponent.js │ ├── Breadcrumb │ │ ├── Breadcrumb.js │ │ └── Breadcrumb.scss │ ├── CaseEnv │ │ ├── index.js │ │ └── index.scss │ ├── EasyDragSort │ │ └── EasyDragSort.js │ ├── ErrMsg │ │ ├── ErrMsg.js │ │ └── ErrMsg.scss │ ├── Footer │ │ ├── Footer.js │ │ └── Footer.scss │ ├── GuideBtns │ │ └── GuideBtns.js │ ├── Header │ │ ├── Header.js │ │ ├── Header.scss │ │ └── Search │ │ │ ├── Search.js │ │ │ └── Search.scss │ ├── Intro │ │ ├── Intro.js │ │ └── Intro.scss │ ├── Label │ │ ├── Label.js │ │ └── Label.scss │ ├── Loading │ │ ├── Loading.js │ │ └── Loading.scss │ ├── LogoSVG │ │ └── index.js │ ├── MockDoc │ │ ├── MockDoc.js │ │ └── MockDoc.scss │ ├── ModalPostman │ │ ├── MethodsList.js │ │ ├── MockList.js │ │ ├── VariablesSelect.js │ │ ├── index.js │ │ └── index.scss │ ├── MyPopConfirm │ │ └── MyPopConfirm.js │ ├── Notify │ │ └── Notify.js │ ├── Postman │ │ ├── CheckCrossInstall.js │ │ ├── Postman.js │ │ └── Postman.scss │ ├── ProjectCard │ │ ├── ProjectCard.js │ │ └── ProjectCard.scss │ ├── SchemaTable │ │ ├── SchemaTable.js │ │ └── index.scss │ ├── Subnav │ │ ├── Subnav.js │ │ └── Subnav.scss │ ├── TimeLine │ │ ├── TimeLine.js │ │ └── TimeLine.scss │ ├── UsernameAutoComplete │ │ └── UsernameAutoComplete.js │ └── index.js ├── constants │ └── variable.js ├── containers │ ├── AddProject │ │ ├── AddProject.js │ │ └── Addproject.scss │ ├── DevTools │ │ └── DevTools.js │ ├── Follows │ │ ├── Follows.js │ │ └── Follows.scss │ ├── Group │ │ ├── Group.js │ │ ├── Group.scss │ │ ├── GroupList │ │ │ ├── GroupList.js │ │ │ └── GroupList.scss │ │ ├── GroupLog │ │ │ └── GroupLog.js │ │ ├── GroupSetting │ │ │ ├── GroupSetting.js │ │ │ └── GroupSetting.scss │ │ ├── MemberList │ │ │ ├── MemberList.js │ │ │ └── MemberList.scss │ │ └── ProjectList │ │ │ ├── ProjectList.js │ │ │ ├── ProjectList.scss │ │ │ └── UpDateModal.js │ ├── Home │ │ ├── Home.js │ │ └── Home.scss │ ├── Login │ │ ├── Login.js │ │ ├── Login.scss │ │ ├── LoginContainer.js │ │ ├── LoginWrap.js │ │ └── Reg.js │ ├── News │ │ ├── News.js │ │ ├── News.scss │ │ ├── NewsList │ │ │ └── NewsList.js │ │ └── NewsTimeline │ │ │ └── NewsTimeline.js │ ├── Project │ │ ├── Activity │ │ │ ├── Activity.js │ │ │ └── Activity.scss │ │ ├── Interface │ │ │ ├── Interface.js │ │ │ ├── InterfaceCol │ │ │ │ ├── CaseReport.js │ │ │ │ ├── ImportInterface.js │ │ │ │ ├── InterfaceCaseContent.js │ │ │ │ ├── InterfaceCaseContent.scss │ │ │ │ ├── InterfaceColContent.js │ │ │ │ ├── InterfaceColMenu.js │ │ │ │ └── InterfaceColMenu.scss │ │ │ ├── InterfaceList │ │ │ │ ├── AddInterfaceCatForm.js │ │ │ │ ├── AddInterfaceForm.js │ │ │ │ ├── Edit.js │ │ │ │ ├── Edit.scss │ │ │ │ ├── InterfaceContent.js │ │ │ │ ├── InterfaceEditForm.js │ │ │ │ ├── InterfaceList.js │ │ │ │ ├── InterfaceMenu.js │ │ │ │ ├── Run │ │ │ │ │ ├── AddColModal.js │ │ │ │ │ ├── Run.js │ │ │ │ │ └── Run.scss │ │ │ │ ├── View.js │ │ │ │ ├── View.scss │ │ │ │ ├── editor.css │ │ │ │ └── interfaceMenu.scss │ │ │ └── interface.scss │ │ ├── Project.js │ │ └── Setting │ │ │ ├── ProjectData │ │ │ ├── ProjectData.js │ │ │ └── ProjectData.scss │ │ │ ├── ProjectEnv │ │ │ ├── ProjectEnvContent.js │ │ │ ├── index.js │ │ │ └── index.scss │ │ │ ├── ProjectMember │ │ │ └── ProjectMember.js │ │ │ ├── ProjectMessage │ │ │ ├── ProjectMessage.js │ │ │ ├── ProjectTag.js │ │ │ └── ProjectTag.scss │ │ │ ├── ProjectMock │ │ │ └── index.js │ │ │ ├── ProjectRequest │ │ │ ├── ProjectRequest.js │ │ │ └── project-request.scss │ │ │ ├── ProjectToken │ │ │ ├── ProjectToken.js │ │ │ └── ProjectToken.scss │ │ │ ├── Setting.js │ │ │ └── Setting.scss │ ├── User │ │ ├── List.js │ │ ├── Profile.js │ │ ├── User.js │ │ └── index.scss │ └── index.js ├── font │ ├── QIconLab.eot │ ├── QIconLab.svg │ ├── QIconLab.ttf │ └── QIconLab.woff ├── images │ └── loading.gif ├── index.js ├── plugin.js ├── reducer │ ├── create.js │ ├── middleware │ │ └── messageMiddleware.js │ └── modules │ │ ├── addInterface.js │ │ ├── follow.js │ │ ├── group.js │ │ ├── interface.js │ │ ├── interfaceCol.js │ │ ├── menu.js │ │ ├── mockCol.js │ │ ├── news.js │ │ ├── project.js │ │ ├── reducer.js │ │ └── user.js └── styles │ ├── common.scss │ ├── mixin.scss │ ├── public-sass.scss │ └── theme.less ├── common ├── HandleImportData.js ├── config.js ├── createContext.js ├── diff-view.js ├── formats.js ├── lib.js ├── markdown.js ├── mergeJsonSchema.js ├── mock-extra.js ├── plugin.js ├── postmanLib.js ├── power-string.js ├── schema-transformTo-table.js ├── tui-editor │ └── dist │ │ ├── tui-editor-2x.png │ │ ├── tui-editor-Editor-all.min.js │ │ ├── tui-editor-Editor.min.js │ │ ├── tui-editor-Viewer-all.min.js │ │ ├── tui-editor-Viewer.min.js │ │ ├── tui-editor-contents.min.css │ │ ├── tui-editor-extChart.min.js │ │ ├── tui-editor-extColorSyntax.min.js │ │ ├── tui-editor-extScrollSync.min.js │ │ ├── tui-editor-extTable.min.js │ │ ├── tui-editor-extUML.min.js │ │ ├── tui-editor.min.css │ │ └── tui-editor.png └── utils.js ├── config_example.json ├── docs ├── NAV.md ├── devops │ ├── SUMMARY.md │ ├── index.md │ └── ldap.png ├── documents │ ├── 2019-01-15-14-05-46.png │ ├── CHANGELOG.md │ ├── SUMMARY.md │ ├── adv_mock.md │ ├── api.md │ ├── case-col.png │ ├── case.md │ ├── clone-project.png │ ├── clone-project.png.png │ ├── data.md │ ├── export-data.md │ ├── export-data.png │ ├── images │ │ ├── autoTest.png │ │ ├── autoTestResult.png │ │ ├── baseSet.png │ │ ├── bianlif.jpeg │ │ ├── case_add.jpg │ │ ├── case_add_modal.jpg │ │ ├── case_col_add.jpg │ │ ├── case_col_add_modal.jpg │ │ ├── case_list.jpg │ │ ├── charles.png │ │ ├── dbbmklogo.jpg │ │ ├── elong.jpeg │ │ ├── favicon.ico │ │ ├── getQuery.png │ │ ├── interface_add.png │ │ ├── interface_add_cat.png │ │ ├── interface_run.png │ │ ├── interface_run_valid.png │ │ ├── intro_page_1.png │ │ ├── intro_page_2.png │ │ ├── intro_page_3.png │ │ ├── jd.jpeg │ │ ├── jsonSchema.png │ │ ├── kuaishou.jpeg │ │ ├── lianjia.jpeg │ │ ├── logo_header22@2x.png │ │ ├── logo_header@2x.png │ │ ├── mock-strice.png │ │ ├── mock-strice2.png │ │ ├── mock-strice3.png │ │ ├── mock.jpg │ │ ├── project-remove.png │ │ ├── project_add_view.png │ │ ├── project_group.png │ │ ├── project_list.png │ │ ├── requestSet.png │ │ ├── schema-mock-1.png │ │ ├── schema-mock-2.png │ │ ├── shouqian.png │ │ ├── show.jpeg │ │ ├── usage │ │ │ ├── add-group.png │ │ │ ├── adv-mock-case1.png │ │ │ ├── adv-mock-case3.png │ │ │ ├── adv-mock-case4-new.png │ │ │ ├── adv-mock-case4.png │ │ │ ├── adv-mock-case5-new.png │ │ │ ├── adv-mock-case5.png │ │ │ ├── adv-mock.jpg │ │ │ ├── api_add_btn.png │ │ │ ├── api_add_panel.png │ │ │ ├── api_res.png │ │ │ ├── case-edit.jpg │ │ │ ├── case-list.gif │ │ │ ├── case-list.jpg │ │ │ ├── case_key_body_json.png │ │ │ ├── case_key_list.png │ │ │ ├── case_key_query.png │ │ │ ├── case_key_res.png │ │ │ ├── case_key_res_query.png │ │ │ ├── chrome-1.jpg │ │ │ ├── chrome-2.jpg │ │ │ ├── chrome-3.jpg │ │ │ ├── chrome-4.jpg │ │ │ ├── chrome-5.jpg │ │ │ ├── chrome-6.jpg │ │ │ ├── hover.png │ │ │ ├── index.png │ │ │ ├── json-schema-demo.jpg │ │ │ ├── json-schema-mock.jpg │ │ │ ├── login.png │ │ │ ├── manage_ask.png │ │ │ ├── manage_ask_group.png │ │ │ ├── manage_find_manager.png │ │ │ ├── manage_find_project_owner.png │ │ │ ├── manage_intro_group.png │ │ │ ├── mock-demo.jpg │ │ │ ├── modal-postman-tips.png │ │ │ ├── modal-postman.gif │ │ │ ├── postman-1.jpg │ │ │ ├── postman-2.jpg │ │ │ ├── postman-3.jpg │ │ │ ├── project-message.png │ │ │ ├── project.png │ │ │ ├── projectCopy.png │ │ │ ├── project_add.png │ │ │ ├── project_add_panel.png │ │ │ ├── project_copy_ok.png │ │ │ ├── project_setting.png │ │ │ ├── project_setting_env.png │ │ │ ├── project_setting_global.png │ │ │ ├── project_setting_logo.png │ │ │ └── user.png │ │ ├── vip.jpeg │ │ ├── xuetangx.jpg │ │ ├── ykit.jpg │ │ └── yonyou.jpg │ ├── import-case.png │ ├── import-json-data.png │ ├── index.md │ ├── manage.md │ ├── mock.md │ ├── plugin-dev.md │ ├── plugin-hooks.md │ ├── plugin-index.md │ ├── plugin-list.md │ ├── project.md │ ├── qa.md │ ├── quickstart.md │ ├── redev.md │ └── test-case.png ├── index.jsx ├── openapi-doc.html ├── openapi.html └── web.css ├── exts ├── yapi-plugin-advanced-mock │ ├── AdvMock.js │ ├── MockCol │ │ ├── CaseDesModal.js │ │ ├── CaseDesModal.scss │ │ ├── MockCol.js │ │ └── mockColReducer.js │ ├── advMockModel.js │ ├── caseModel.js │ ├── client.js │ ├── controller.js │ ├── index.js │ └── server.js ├── yapi-plugin-export-data │ ├── .sass-cache │ │ ├── 3f3d6afdb6793d0137c849fff0c1eee600369f02 │ │ │ └── defaultTheme.scssc │ │ └── 8d7dcd37f61732054a9966925c8cce5b23a209ac │ │ │ └── defaultTheme.scssc │ ├── client.js │ ├── controller.js │ ├── defaultTheme.css │ ├── defaultTheme.css.map │ ├── defaultTheme.js │ ├── defaultTheme.scss │ ├── index.js │ └── server.js ├── yapi-plugin-export-swagger2-data │ ├── client.js │ ├── controller.js │ ├── index.js │ └── server.js ├── yapi-plugin-gen-services │ ├── .sass-cache │ │ ├── 3f3d6afdb6793d0137c849fff0c1eee600369f02 │ │ │ └── defaultTheme.scssc │ │ └── 8d7dcd37f61732054a9966925c8cce5b23a209ac │ │ │ └── defaultTheme.scssc │ ├── Services │ │ ├── Services.js │ │ └── Services.scss │ ├── client.js │ ├── controller.js │ ├── defaultTheme.css │ ├── defaultTheme.css.map │ ├── defaultTheme.js │ ├── defaultTheme.scss │ ├── index.js │ └── server.js ├── yapi-plugin-import-har │ ├── client.js │ └── index.js ├── yapi-plugin-import-postman │ ├── client.js │ └── index.js ├── yapi-plugin-import-swagger │ ├── client.js │ ├── index.js │ ├── run.js │ └── server.js ├── yapi-plugin-import-yapi-json │ ├── client.js │ └── index.js ├── yapi-plugin-statistics │ ├── client.js │ ├── controller.js │ ├── index.js │ ├── server.js │ ├── statisMockModel.js │ ├── statisticsClientPage │ │ ├── StatisChart.js │ │ ├── StatisTable.js │ │ ├── index.js │ │ └── index.scss │ ├── test.js │ └── util.js ├── yapi-plugin-swagger-auto-sync │ ├── client.js │ ├── controller │ │ └── syncController.js │ ├── index.js │ ├── interfaceSyncUtils.js │ ├── server.js │ ├── swaggerAutoSync │ │ └── swaggerAutoSync.js │ └── syncModel.js ├── yapi-plugin-test │ ├── client.js │ └── index.js └── yapi-plugin-wiki │ ├── client.js │ ├── controller.js │ ├── index.js │ ├── server.js │ ├── util.js │ ├── wikiModel.js │ └── wikiPage │ ├── Editor.js │ ├── View.js │ ├── index.js │ └── index.scss ├── nodemon.json ├── npm-publish.js ├── package-lock.json ├── package.json ├── plugin.json ├── server ├── app.js ├── controllers │ ├── base.js │ ├── follow.js │ ├── group.js │ ├── interface.js │ ├── interfaceCol.js │ ├── log.js │ ├── open.js │ ├── project.js │ ├── test.js │ └── user.js ├── install.js ├── middleware │ └── mockServer.js ├── models │ ├── avatar.js │ ├── base.js │ ├── follow.js │ ├── group.js │ ├── interface.js │ ├── interfaceCase.js │ ├── interfaceCat.js │ ├── interfaceCol.js │ ├── log.js │ ├── project.js │ ├── storage.js │ ├── token.js │ └── user.js ├── plugin.js ├── router.js ├── utils │ ├── commons.js │ ├── db.js │ ├── initConfig.js │ ├── ldap.js │ ├── mongoose-auto-increment.js │ ├── notice.js │ ├── reportHtml │ │ ├── defaultTheme.css │ │ ├── defaultTheme.js │ │ └── index.js │ ├── sandbox.js │ ├── storage.js │ └── token.js ├── websocket.js └── yapi.js ├── static ├── attachment │ └── cross-request.zip ├── dev.html ├── iconfont │ ├── iconfont.eot │ ├── iconfont.svg │ ├── iconfont.ttf │ └── iconfont.woff ├── image │ ├── avatar-1.png │ ├── avatar.png │ ├── demo-img@1x.jpg │ ├── demo-img@2x.jpg │ └── favicon.png ├── index.html └── prd │ ├── assets.js │ ├── index@4bf34ac5d0d400162c8b.css │ ├── index@4bf34ac5d0d400162c8b.css.gz │ ├── index@4bf34ac5d0d400162c8b.js │ ├── index@4bf34ac5d0d400162c8b.js.gz │ ├── lib2@b606a4c30ccd185aa92f.js │ ├── lib2@b606a4c30ccd185aa92f.js.gz │ ├── lib3@bae7dadd0771f0f60dd8.js │ ├── lib3@bae7dadd0771f0f60dd8.js.gz │ ├── lib@ff671d00820016606eda.js │ ├── lib@ff671d00820016606eda.js.gz │ └── manifest@f2f4bd774d6c221b3d5f.js ├── test ├── common │ ├── common.test.js │ └── mergeJsonSchema.test.js ├── lib.test.js ├── mock-extra.test.js └── server │ ├── commons.test.js │ └── mockServer.test.js ├── webpack.alias.js ├── yapi-base-flow.jpg ├── ydoc.js ├── ydocfile.js └── ykit.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | /logs 3 | .git/ -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/* 2 | common/json-schema-mockjs.js -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | "browser": true, 4 | "commonjs": true, 5 | "es6": true, 6 | "node": true 7 | }, 8 | extends: ["eslint:recommended", "plugin:react/recommended"], 9 | parser: "babel-eslint", 10 | parserOptions: { 11 | "ecmaFeatures": { 12 | "jsx": true 13 | }, 14 | "sourceType": "module" 15 | }, 16 | plugins: [ 17 | "react", 18 | "import" 19 | ], 20 | rules: { 21 | "indent": ["off", 2], 22 | "react/display-name": ["off"], 23 | "react/jsx-indent": ["error", 2], 24 | "comma-dangle": ["error", "never"], 25 | "no-console": ["off"], 26 | "import/no-unresolved": ["off"], 27 | "react/no-find-dom-node": ["off"], 28 | "no-empty": ["off"] 29 | // "react/no-unescaped-entities": 0 30 | }, 31 | settings:{ 32 | "react": { 33 | "version": "detect" 34 | } 35 | } 36 | }; 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## 版本号 2 | ~ 3 | 4 | ## 什么问题 5 | ~ 6 | 7 | ## 如何复现此问题 8 | ~ 9 | 10 | ## 什么浏览器 11 | ~ 12 | 13 | ## 什么系统(Linux, Windows, macOS) 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # kdiff3 ignore 2 | *.orig 3 | 4 | # maven ignore 5 | target/ 6 | 7 | # eclipse ignore 8 | .settings/ 9 | .project 10 | .classpath 11 | .history 12 | 13 | # idea ignore 14 | .idea/ 15 | *.ipr 16 | *.iml 17 | *.iws 18 | 19 | # temp ignore 20 | *.log 21 | *.cache 22 | *.diff 23 | *.patch 24 | *.tmp 25 | 26 | # system ignore 27 | .DS_Store 28 | Thumbs.db 29 | 30 | # package ignore (optional) 31 | # *.jar 32 | # *.war 33 | # *.zip 34 | # *.tar 35 | # *.tar.gz 36 | 37 | node_modules/ 38 | runtime/ 39 | /prd/ 40 | /dev/ 41 | .tags 42 | .tags1 43 | tsconfig.json 44 | client/plugin-module.js 45 | .vscode 46 | /iconfont 47 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /docs 2 | /test 3 | /static/doc 4 | /iconfont 5 | /ydoc.js 6 | /ydocfile.js 7 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | sass_binary_site=https://npm.taobao.org/mirrors/node-sass/ 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12-alpine 2 | ENV TZ="Asia/Shanghai" 3 | # 使用阿里云镜像 4 | RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories 5 | WORKDIR /yapi/vendors 6 | COPY . /yapi/vendors/ 7 | 8 | RUN apk add --no-cache wget python make && cd /yapi/vendors && npm set strict-ssl false && npm install --production --registry https://registry.npm.taobao.org 9 | 10 | EXPOSE 3000 11 | ENTRYPOINT ["node"] 12 | -------------------------------------------------------------------------------- /client/components/AceEditor/AceEditor.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import mockEditor from './mockEditor'; 3 | import PropTypes from 'prop-types'; 4 | import './AceEditor.scss'; 5 | 6 | const ModeMap = { 7 | javascript: 'ace/mode/javascript', 8 | json: 'ace/mode/json', 9 | text: 'ace/mode/text', 10 | xml: 'ace/mode/xml', 11 | html: 'ace/mode/html' 12 | }; 13 | 14 | const defaultStyle = { width: '100%', height: '200px' }; 15 | 16 | function getMode(mode) { 17 | return ModeMap[mode] || ModeMap.text; 18 | } 19 | 20 | class AceEditor extends React.PureComponent { 21 | constructor(props) { 22 | super(props); 23 | } 24 | 25 | static propTypes = { 26 | data: PropTypes.any, 27 | onChange: PropTypes.func, 28 | className: PropTypes.string, 29 | mode: PropTypes.string, //enum[json, text, javascript], default is javascript 30 | readOnly: PropTypes.bool, 31 | callback: PropTypes.func, 32 | style: PropTypes.object, 33 | fullScreen: PropTypes.bool, 34 | insertCode: PropTypes.func 35 | }; 36 | 37 | componentDidMount() { 38 | this.editor = mockEditor({ 39 | container: this.editorElement, 40 | data: this.props.data, 41 | onChange: this.props.onChange, 42 | readOnly: this.props.readOnly, 43 | fullScreen: this.props.fullScreen 44 | }); 45 | let mode = this.props.mode || 'javascript'; 46 | this.editor.editor.getSession().setMode(getMode(mode)); 47 | if (typeof this.props.callback === 'function') { 48 | this.props.callback(this.editor.editor); 49 | } 50 | } 51 | 52 | UNSAFE_componentWillReceiveProps(nextProps) { 53 | if (!this.editor) { 54 | return; 55 | } 56 | if (nextProps.data !== this.props.data && this.editor.getValue() !== nextProps.data) { 57 | this.editor.setValue(nextProps.data); 58 | let mode = nextProps.mode || 'javascript'; 59 | this.editor.editor.getSession().setMode(getMode(mode)); 60 | this.editor.editor.clearSelection(); 61 | } 62 | } 63 | 64 | render() { 65 | return ( 66 |
{ 70 | this.editorElement = editor; 71 | }} 72 | /> 73 | ); 74 | } 75 | } 76 | 77 | export default AceEditor; 78 | -------------------------------------------------------------------------------- /client/components/AceEditor/AceEditor.scss: -------------------------------------------------------------------------------- 1 | .ace_editor.fullScreen { 2 | height: auto; 3 | width: auto; 4 | border: 0; 5 | margin: 0; 6 | position: fixed !important; 7 | top: 0; 8 | bottom: 0; 9 | left: 0; 10 | right: 0; 11 | z-index: 1000008; 12 | } 13 | 14 | .ace_editor .ace_print-margin{ 15 | visibility: hidden !important 16 | } 17 | 18 | .fullScreen { 19 | overflow: hidden 20 | } 21 | 22 | .ace_editor.ace-xcode { 23 | background-color: #f5f5f5; 24 | color: #000000; 25 | } -------------------------------------------------------------------------------- /client/components/AuthenticatedComponent.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { changeMenuItem } from '../reducer/modules/menu'; 5 | 6 | export function requireAuthentication(Component) { 7 | return @connect( 8 | state => { 9 | return { 10 | isAuthenticated: state.user.isLogin 11 | }; 12 | }, 13 | { 14 | changeMenuItem 15 | } 16 | ) 17 | class AuthenticatedComponent extends React.PureComponent { 18 | constructor(props) { 19 | super(props); 20 | } 21 | static propTypes = { 22 | isAuthenticated: PropTypes.bool, 23 | location: PropTypes.object, 24 | dispatch: PropTypes.func, 25 | history: PropTypes.object, 26 | changeMenuItem: PropTypes.func 27 | }; 28 | UNSAFE_componentWillMount() { 29 | this.checkAuth(); 30 | } 31 | UNSAFE_componentWillReceiveProps() { 32 | this.checkAuth(); 33 | } 34 | checkAuth() { 35 | if (!this.props.isAuthenticated) { 36 | this.props.history.push('/'); 37 | this.props.changeMenuItem('/'); 38 | } 39 | } 40 | render() { 41 | return
{this.props.isAuthenticated ? : null}
; 42 | } 43 | }; 44 | } 45 | -------------------------------------------------------------------------------- /client/components/Breadcrumb/Breadcrumb.js: -------------------------------------------------------------------------------- 1 | import './Breadcrumb.scss'; 2 | import { withRouter } from 'react-router-dom'; 3 | import { Breadcrumb } from 'antd'; 4 | import PropTypes from 'prop-types'; 5 | import React, { PureComponent as Component } from 'react'; 6 | import { connect } from 'react-redux'; 7 | import { Link } from 'react-router-dom'; 8 | 9 | @connect(state => { 10 | return { 11 | breadcrumb: state.user.breadcrumb 12 | }; 13 | }) 14 | @withRouter 15 | export default class BreadcrumbNavigation extends Component { 16 | constructor(props) { 17 | super(props); 18 | } 19 | 20 | static propTypes = { 21 | breadcrumb: PropTypes.array 22 | }; 23 | 24 | render() { 25 | const getItem = this.props.breadcrumb.map((item, index) => { 26 | if (item.href) { 27 | return ( 28 | 29 | {item.name} 30 | 31 | ); 32 | } else { 33 | return {item.name}; 34 | } 35 | }); 36 | return ( 37 |
38 | {getItem} 39 |
40 | ); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /client/components/Breadcrumb/Breadcrumb.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixin.scss'; 2 | 3 | .breadcrumb-container { 4 | .ant-breadcrumb { 5 | font-size: 16px; 6 | float: left; 7 | color: #fff; 8 | padding-left: 16px; 9 | line-height: unset; 10 | 11 | } 12 | .ant-breadcrumb a { 13 | color: #fff; 14 | &:hover { 15 | color: $color-blue 16 | } 17 | } 18 | .ant-breadcrumb > span:last-child { 19 | color: #fff; 20 | font-weight: normal; 21 | } 22 | .ant-breadcrumb-separator { 23 | color: #fff; 24 | font-weight: normal; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /client/components/CaseEnv/index.scss: -------------------------------------------------------------------------------- 1 | .case-env { 2 | .label { 3 | // width: 100%; 4 | text-align: right; 5 | padding-right: 8px; 6 | overflow: hidden; 7 | text-overflow: ellipsis; 8 | white-space: nowrap; 9 | } 10 | .label:after{ 11 | content: ":"; 12 | margin: 0 8px 0 2px; 13 | position: relative; 14 | top: -.5px; 15 | } 16 | .label-name { 17 | overflow: hidden; 18 | text-overflow: ellipsis; 19 | white-space: nowrap; 20 | } 21 | 22 | .env-item { 23 | margin-bottom: 16px; 24 | } 25 | } -------------------------------------------------------------------------------- /client/components/ErrMsg/ErrMsg.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Icon } from 'antd'; 4 | import './ErrMsg.scss'; 5 | import { withRouter } from 'react-router'; 6 | 7 | /** 8 | * 错误信息提示 9 | * 10 | * @component ErrMsg 11 | * @examplelanguage js 12 | * 13 | * * 错误信息提示组件 14 | * * 错误信息提示组件 15 | * 16 | * 17 | */ 18 | 19 | /** 20 | * 标题 21 | * 一般用于描述错误信息名称 22 | * @property title 23 | * @type string 24 | * @description 一般用于描述错误信息名称 25 | * @returns {object} 26 | */ 27 | @withRouter 28 | class ErrMsg extends Component { 29 | constructor(props) { 30 | super(props); 31 | } 32 | 33 | static propTypes = { 34 | type: PropTypes.string, 35 | history: PropTypes.object, 36 | title: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 37 | desc: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), 38 | opration: PropTypes.oneOfType([PropTypes.string, PropTypes.object]) 39 | }; 40 | 41 | render() { 42 | let { type, title, desc, opration } = this.props; 43 | let icon = 'frown-o'; 44 | if (type) { 45 | switch (type) { 46 | case 'noFollow': 47 | title = '你还没有关注项目呢'; 48 | desc = ( 49 | 50 | 先去 this.props.history.push('/group')}>“项目广场” 逛逛吧, 51 | 那里可以添加关注。 52 | 53 | ); 54 | break; 55 | case 'noInterface': 56 | title = '该项目还没有接口呢'; 57 | desc = '在左侧 “接口列表” 中添加接口'; 58 | break; 59 | case 'noMemberInProject': 60 | title = '该项目还没有成员呢'; 61 | break; 62 | case 'noMemberInGroup': 63 | title = '该分组还没有成员呢'; 64 | break; 65 | case 'noProject': 66 | title = '该分组还没有项目呢'; 67 | desc = 请点击右上角添加项目按钮新建项目; 68 | break; 69 | case 'noData': 70 | title = '暂无数据'; 71 | desc = '先去别处逛逛吧'; 72 | break; 73 | case 'noChange': 74 | title = '没有改动'; 75 | desc = '该操作未改动 Api 数据'; 76 | icon = 'meh-o'; 77 | break; 78 | default: 79 | console.log('default'); 80 | } 81 | } 82 | return ( 83 |
84 | 85 |

{title}

86 |

{desc}

87 |

{opration}

88 |
89 | ); 90 | } 91 | } 92 | 93 | export default ErrMsg; 94 | -------------------------------------------------------------------------------- /client/components/ErrMsg/ErrMsg.scss: -------------------------------------------------------------------------------- 1 | .err-msg { 2 | text-align: center; 3 | font-size: .14rem; 4 | line-height: 2; 5 | margin-bottom: .24rem; 6 | color: rgba(13, 27, 62, 0.43); 7 | .icon { 8 | font-size: .6rem; 9 | margin-bottom: .08rem; 10 | } 11 | .title { 12 | font-size: .18rem; 13 | } 14 | .desc { 15 | 16 | } 17 | .opration { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /client/components/Footer/Footer.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/common.scss'; 2 | @import '../../styles/mixin.scss'; 3 | 4 | .footer-wrapper{ 5 | height: 2.4rem; 6 | width: 100%; 7 | background-color: $color-bg-dark; 8 | overflow: hidden; 9 | position: relative; 10 | z-index: 0; 11 | } 12 | 13 | .footer-container{ 14 | margin: 0 auto !important; 15 | padding: .48rem .24rem; 16 | max-width: 12.2rem; 17 | .icon { 18 | font-size: .16rem; 19 | margin-right: .08rem; 20 | } 21 | .title { 22 | color: #8898aa; 23 | font-size: .14rem; 24 | margin-bottom: .08rem; 25 | } 26 | .link { 27 | font-size: .14rem; 28 | font-weight: 200; 29 | color: #8898aa; 30 | line-height: .3rem; 31 | transition: color .2s; 32 | &:hover { 33 | color: $color-bg-gray; 34 | } 35 | } 36 | } 37 | .footItem{ 38 | padding: 24px 2%; 39 | width: 25%; 40 | float: left; 41 | div{ 42 | margin: 6px 0; 43 | } 44 | a{ 45 | font-weight: 200; 46 | color: #b3bdc1; 47 | &:hover{ 48 | color: white; 49 | } 50 | } 51 | } 52 | .copyRight{ 53 | padding: 24px 2%; 54 | width: 25%; 55 | float: left; 56 | font-size: 13px; 57 | text-indent: 1em; 58 | h4{ 59 | font-size: 14px; 60 | margin: 0 auto 13px; 61 | font-weight: 500; 62 | position: relative; 63 | text-indent: 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/components/GuideBtns/GuideBtns.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button } from 'antd'; 4 | import { connect } from 'react-redux'; 5 | import { changeStudyTip, finishStudy } from '../../reducer/modules/user.js'; 6 | 7 | @connect( 8 | null, 9 | { 10 | changeStudyTip, 11 | finishStudy 12 | } 13 | ) 14 | class GuideBtns extends Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | 19 | static propTypes = { 20 | changeStudyTip: PropTypes.func, 21 | finishStudy: PropTypes.func, 22 | isLast: PropTypes.bool 23 | }; 24 | 25 | // 点击下一步 26 | nextStep = () => { 27 | this.props.changeStudyTip(); 28 | if (this.props.isLast) { 29 | this.props.finishStudy(); 30 | } 31 | }; 32 | 33 | // 点击退出指引 34 | exitGuide = () => { 35 | this.props.finishStudy(); 36 | }; 37 | 38 | render() { 39 | return ( 40 |
41 | 44 | 47 |
48 | ); 49 | } 50 | } 51 | export default GuideBtns; 52 | -------------------------------------------------------------------------------- /client/components/Header/Search/Search.scss: -------------------------------------------------------------------------------- 1 | $color-grey:#979DA7; 2 | 3 | .search-wrapper{ 4 | cursor: auto; 5 | .search-input{ 6 | width: 2rem; 7 | } 8 | .srch-icon{ 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /client/components/Intro/Intro.scss: -------------------------------------------------------------------------------- 1 | $imgUrl: "../../../static/image/"; 2 | $color-grey: #E5E5E5; 3 | $color-blue: #2395f1; 4 | $color-white: #fff; 5 | 6 | .intro-container{ 7 | .imgWrapper{ 8 | height: 100%; 9 | width: 50%; 10 | overflow: hidden; 11 | position: absolute; 12 | left: 0; 13 | } 14 | .textWrapper{ 15 | display: block; 16 | width: 50%; 17 | height: 150px; 18 | vertical-align: top; 19 | position: absolute; 20 | margin: auto; 21 | right: 0; 22 | } 23 | .des-container{ 24 | padding-left: .15rem; 25 | .des-title{ 26 | font-size: .24rem; 27 | margin-bottom: .1rem; 28 | } 29 | .des-detail{ 30 | font-size: .15rem; 31 | margin-bottom: .2rem; 32 | } 33 | .des-switch{ 34 | .switch-content{ 35 | float: left; 36 | width: 50%; 37 | max-height: .85rem; 38 | font-size: .14rem; 39 | padding: .1rem .15rem .1rem 0; 40 | div{ 41 | float: left; 42 | } 43 | .icon-switch{ 44 | height: .4rem; 45 | width: .4rem; 46 | border-radius: .02rem; 47 | background-color: $color-blue; 48 | margin-right: .1rem; 49 | color: $color-white; 50 | display: flex; 51 | align-items: center; 52 | justify-content: center; 53 | font-size: .18rem; 54 | } 55 | .text-switch{ 56 | width: calc(100% - .65rem); 57 | } 58 | } 59 | } 60 | } 61 | .img-container{ 62 | height: 100%; 63 | width: 100%; 64 | padding-right: .15rem; 65 | //background-image: url("#{$imgUrl}demo-img.png"); 66 | img{ 67 | height: 100%; 68 | width: 100%; 69 | border: .01rem solid $color-grey; 70 | box-shadow : 0 0 3px 1px $color-grey; 71 | border-radius: .04rem; 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /client/components/Label/Label.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { Icon, Input, Tooltip } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import './Label.scss'; 5 | 6 | export default class Label extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.state = { 10 | inputShow: false, 11 | inputValue: '' 12 | }; 13 | } 14 | static propTypes = { 15 | onChange: PropTypes.func, 16 | desc: PropTypes.string, 17 | cat_name: PropTypes.string 18 | }; 19 | toggle = () => { 20 | this.setState({ inputShow: !this.state.inputShow }); 21 | }; 22 | handleChange = event => { 23 | this.setState({ inputValue: event.target.value }); 24 | }; 25 | UNSAFE_componentWillReceiveProps(nextProps) { 26 | if (this.props.desc === nextProps.desc) { 27 | this.setState({ 28 | inputShow: false 29 | }); 30 | } 31 | } 32 | render() { 33 | return ( 34 |
35 | {this.props.desc && ( 36 |
37 | {!this.state.inputShow ? ( 38 |
39 |

40 | {this.props.desc}    41 | 42 | 43 | 44 |

45 |
46 | ) : ( 47 |
48 | 49 | { 52 | this.props.onChange(this.state.inputValue); 53 | this.toggle(); 54 | }} 55 | type="check" 56 | /> 57 | 58 |
59 | )} 60 |
61 | )} 62 |
63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /client/components/Label/Label.scss: -------------------------------------------------------------------------------- 1 | .component-label { 2 | p { 3 | padding: 3px 7px; 4 | &:hover { 5 | .interface-delete-icon { 6 | display: inline-block; 7 | } 8 | } 9 | .interface-delete-icon { 10 | display: none; 11 | } 12 | } 13 | .interface-delete-icon { 14 | &:hover { 15 | color: #2395f1; 16 | cursor: pointer; 17 | } 18 | } 19 | .label-input-wrapper { 20 | input { 21 | width: 30%; 22 | } 23 | .interface-delete-icon { 24 | font-size: 1.4em; 25 | margin-left: 10px; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/components/Loading/Loading.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import './Loading.scss'; 4 | 5 | export default class Loading extends React.PureComponent { 6 | static defaultProps = { 7 | visible: false 8 | }; 9 | static propTypes = { 10 | visible: PropTypes.bool 11 | }; 12 | constructor(props) { 13 | super(props); 14 | this.state = { show: props.visible }; 15 | } 16 | UNSAFE_componentWillReceiveProps(nextProps) { 17 | this.setState({ show: nextProps.visible }); 18 | } 19 | render() { 20 | return ( 21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /client/components/Loading/Loading.scss: -------------------------------------------------------------------------------- 1 | $ball-color:#30a1f2; 2 | $ball-size:.15rem; 3 | $ball-margin:.02rem; 4 | $loader-radius:.25rem; 5 | 6 | @function delay($interval, $count, $index) { 7 | @return ($index * $interval) - ($interval * $count); 8 | } 9 | 10 | @keyframes ball-spin-fade-loader { 11 | 50% { 12 | opacity: 0.3; 13 | transform: scale(0.4); 14 | } 15 | 100% { 16 | opacity: 1; 17 | transform: scale(1); 18 | } 19 | } 20 | @mixin ball-spin-fade-loader($n:8,$start:1){ 21 | @for $i from $start through $n { 22 | > div:nth-child(#{$i}) { 23 | $quarter: ($loader-radius/2) + ($loader-radius/5.5); 24 | 25 | @if $i == 1 { 26 | top: $loader-radius; 27 | left: 0; 28 | } @else if $i == 2 { 29 | top: $quarter; 30 | left: $quarter; 31 | } @else if $i == 3 { 32 | top: 0; 33 | left: $loader-radius; 34 | } @else if $i == 4 { 35 | top: -$quarter; 36 | left: $quarter; 37 | } @else if $i == 5 { 38 | top: -$loader-radius; 39 | left: 0; 40 | } @else if $i == 6 { 41 | top: -$quarter; 42 | left: -$quarter; 43 | } @else if $i == 7 { 44 | top: 0; 45 | left: -$loader-radius; 46 | } @else if $i == 8 { 47 | top: $quarter; 48 | left: -$quarter; 49 | } 50 | animation: ball-spin-fade-loader 1s delay(0.12s, $n, $i - 1) infinite linear; 51 | } 52 | } 53 | } 54 | 55 | .loading-box{ 56 | align-items: center; 57 | justify-content: center; 58 | position: fixed; 59 | left: 0; 60 | right: 0; 61 | top: 0; 62 | bottom: 0; 63 | z-index: 9999; 64 | &-bg{ 65 | position: absolute; 66 | left: 0; 67 | top: 0; 68 | height: 100%; 69 | width: 100%; 70 | background: rgba(255,255,255,.7); 71 | } 72 | &-inner{ 73 | @include ball-spin-fade-loader(); 74 | position: relative; 75 | >div{ 76 | position: absolute; 77 | width: $ball-size; 78 | height: $ball-size; 79 | border-radius: 50%; 80 | margin: $ball-margin; 81 | background-color: $ball-color; 82 | animation-fill-mode: both; 83 | } 84 | } 85 | } 86 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /client/components/MockDoc/MockDoc.scss: -------------------------------------------------------------------------------- 1 | .MockDoc{ 2 | background-color: #F6F6F6; 3 | // padding: 16px; 4 | border:16px solid #F6F6F6; 5 | overflow: auto; 6 | width: 500px; 7 | height: 500px; 8 | word-break:keep-all; /* 不换行 */ 9 | white-space:nowrap; 10 | .jsonItem{ 11 | font-size: 14px; 12 | width: auto; 13 | 14 | } 15 | .jsonitemNum{ 16 | margin-right: 16px; 17 | display: inline-block; 18 | width: 25px; 19 | border-right: 1px solid gray; 20 | } 21 | .valueLight{ 22 | color: #108ee9; 23 | } 24 | } 25 | .spaces{ 26 | display: inline-block; 27 | width: 30px; 28 | } 29 | .keymes{ 30 | margin-left: 20px; 31 | } -------------------------------------------------------------------------------- /client/components/ModalPostman/MockList.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Row, Input } from 'antd'; 4 | import constants from '../../constants/variable.js'; 5 | const wordList = constants.MOCK_SOURCE; 6 | const Search = Input.Search; 7 | 8 | class MockList extends Component { 9 | static propTypes = { 10 | click: PropTypes.func, 11 | clickValue: PropTypes.string 12 | }; 13 | 14 | constructor(props) { 15 | super(props); 16 | this.state = { 17 | filter: '', 18 | list: [] 19 | }; 20 | } 21 | 22 | componentDidMount() { 23 | this.setState({ 24 | list: wordList 25 | }); 26 | } 27 | 28 | onFilter = e => { 29 | const list = wordList.filter(item => { 30 | return item.mock.indexOf(e.target.value) !== -1; 31 | }); 32 | this.setState({ 33 | filter: e.target.value, 34 | list: list 35 | }); 36 | }; 37 | 38 | render() { 39 | const { list, filter } = this.state; 40 | const { click, clickValue } = this.props; 41 | return ( 42 |
43 | 49 | {list.map((item, index) => { 50 | return ( 51 | click(item.mock)} 57 | > 58 | {item.mock} 59 | 60 | ); 61 | })} 62 |
63 | ); 64 | } 65 | } 66 | 67 | export default MockList; 68 | -------------------------------------------------------------------------------- /client/components/MyPopConfirm/MyPopConfirm.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import { Modal, Button } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | 5 | // 嵌入到 BrowserRouter 内部,覆盖掉默认的 window.confirm 6 | // http://reacttraining.cn/web/api/BrowserRouter/getUserConfirmation-func 7 | class MyPopConfirm extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | visible: true 12 | }; 13 | } 14 | static propTypes = { 15 | msg: PropTypes.string, 16 | callback: PropTypes.func 17 | }; 18 | 19 | yes = () => { 20 | this.props.callback(true); 21 | this.setState({ visible: false }); 22 | } 23 | 24 | no = () => { 25 | this.props.callback(false); 26 | this.setState({ visible: false }); 27 | } 28 | 29 | UNSAFE_componentWillReceiveProps() { 30 | this.setState({ visible: true }); 31 | } 32 | 33 | render() { 34 | if (!this.state.visible) { 35 | return null; 36 | } 37 | return (取 消, 43 | 44 | ]} 45 | > 46 |

{this.props.msg}

47 |
); 48 | } 49 | } 50 | 51 | export default MyPopConfirm; 52 | -------------------------------------------------------------------------------- /client/components/Notify/Notify.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import axios from 'axios'; 3 | import { Alert, message } from 'antd'; 4 | 5 | export default class Notify extends Component { 6 | constructor(props) { 7 | super(props); 8 | this.state = { 9 | newVersion: process.env.version, 10 | version: process.env.version 11 | }; 12 | } 13 | 14 | componentDidMount() { 15 | axios.get('https://www.easy-mock.com/mock/5c2851e3d84c733cb500c3b9/yapi/versions').then(req => { 16 | if (req.status === 200) { 17 | this.setState({ newVersion: req.data.data[0] }); 18 | } else { 19 | message.error('无法获取新版本信息!'); 20 | } 21 | }); 22 | } 23 | 24 | render() { 25 | const isShow = this.state.newVersion !== this.state.version; 26 | return ( 27 |
28 | {isShow && ( 29 | 32 | 当前版本是:{this.state.version}  可升级到: {this.state.newVersion} 33 |     34 | 38 | 版本详情 39 | 40 |
41 | } 42 | banner 43 | closable 44 | type="info" 45 | /> 46 | )} 47 |
48 | ); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /client/components/Postman/CheckCrossInstall.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | 5 | exports.initCrossRequest = function (fn) { 6 | let startTime = 0; 7 | let _crossRequest = setInterval(() => { 8 | startTime += 500; 9 | if (startTime > 5000) { 10 | clearInterval(_crossRequest); 11 | } 12 | if (window.crossRequest) { 13 | clearInterval(_crossRequest); 14 | fn(true); 15 | } else { 16 | fn(false); 17 | } 18 | }, 500); 19 | return _crossRequest; 20 | }; 21 | 22 | CheckCrossInstall.propTypes = { 23 | hasPlugin: PropTypes.bool 24 | }; 25 | 26 | function CheckCrossInstall(props) { 27 | const hasPlugin = props.hasPlugin; 28 | return ( 29 |
30 | {hasPlugin ? ( 31 | '' 32 | ) : ( 33 | 36 | 重要:当前的接口测试服务,需安装免费测试增强插件,仅支持 chrome 37 | 浏览器,选择下面任意一种安装方式: 38 | {/* */} 46 | 52 |
53 | } 54 | type="warning" 55 | /> 56 | )} 57 |
58 | ); 59 | } 60 | 61 | export default CheckCrossInstall; 62 | -------------------------------------------------------------------------------- /client/components/SchemaTable/index.scss: -------------------------------------------------------------------------------- 1 | .table-desc { 2 | white-space: pre-wrap; 3 | } -------------------------------------------------------------------------------- /client/components/Subnav/Subnav.js: -------------------------------------------------------------------------------- 1 | import './Subnav.scss'; 2 | import React, { PureComponent as Component } from 'react'; 3 | import { Link } from 'react-router-dom'; 4 | import PropTypes from 'prop-types'; 5 | import { Menu } from 'antd'; 6 | 7 | class Subnav extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | static propTypes = { 13 | data: PropTypes.array, 14 | default: PropTypes.string 15 | }; 16 | 17 | render() { 18 | return ( 19 |
20 | 26 | {this.props.data.map((item, index) => { 27 | // 若导航标题为两个字,则自动在中间加个空格 28 | if (item.name.length === 2) { 29 | item.name = item.name[0] + ' ' + item.name[1]; 30 | } 31 | return ( 32 | 33 | {this.props.data[index].name} 34 | 35 | ); 36 | })} 37 | 38 |
39 | ); 40 | } 41 | } 42 | 43 | export default Subnav; 44 | -------------------------------------------------------------------------------- /client/components/Subnav/Subnav.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/common.scss'; 2 | 3 | .m-subnav { 4 | margin-bottom: .24rem; 5 | background-color: #fff; 6 | box-shadow: 0 0 .04rem rgba(0, 0, 0, .08); 7 | font-size: .14rem; 8 | border: none; 9 | .ant-menu { 10 | font-size: unset; 11 | } 12 | .m-subnav-menu { 13 | border: none; 14 | padding: 0 .24rem; 15 | .item { 16 | line-height: .54rem; 17 | padding: 0 .36rem; 18 | font-weight: normal; 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /client/components/index.js: -------------------------------------------------------------------------------- 1 | import Breadcrumb from './Breadcrumb/Breadcrumb.js'; 2 | import Footer from './Footer/Footer.js'; 3 | import Header from './Header/Header.js'; 4 | import Intro from './Intro/Intro.js'; 5 | import Loading from './Loading/Loading.js'; 6 | import ProjectCard from './ProjectCard/ProjectCard.js'; 7 | import Subnav from './Subnav/Subnav.js'; 8 | import Postman from './Postman/Postman'; 9 | 10 | export { Breadcrumb, Footer, Header, Intro, Loading, ProjectCard, Subnav, Postman }; 11 | -------------------------------------------------------------------------------- /client/containers/AddProject/Addproject.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/common.scss'; 2 | 3 | .m-container { 4 | margin: .24rem auto !important; 5 | padding: .24rem !important; 6 | background-color: #fff; 7 | } 8 | 9 | .form-item { 10 | margin-bottom: .16rem; 11 | } 12 | 13 | .breakline { 14 | margin-top: .18rem; 15 | margin-bottom: .18rem; 16 | border: 0; 17 | border-top: 1px solid #eeeeee; 18 | } 19 | 20 | 21 | .radio { 22 | font-weight: 600; 23 | } 24 | 25 | .radio-desc { 26 | margin-left: .22rem; 27 | position: relative; 28 | font-weight: normal; 29 | top: -.08rem; 30 | color: #919191; 31 | } 32 | -------------------------------------------------------------------------------- /client/containers/DevTools/DevTools.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { createDevTools } from 'redux-devtools'; 3 | import LogMonitor from 'redux-devtools-log-monitor'; 4 | import DockMonitor from 'redux-devtools-dock-monitor'; 5 | 6 | 7 | module.exports = createDevTools( 8 | 13 | 14 | 15 | ); -------------------------------------------------------------------------------- /client/containers/Follows/Follows.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/common.scss'; 2 | @import '../../styles/mixin.scss'; 3 | 4 | 5 | .follow-box{ 6 | padding: 24px; 7 | background-color: #fff; 8 | margin-top: .24rem; 9 | } 10 | -------------------------------------------------------------------------------- /client/containers/Group/Group.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixin.scss'; 2 | 3 | .g-doc { 4 | @include row-width-limit; 5 | margin: 0 auto .24rem; 6 | } 7 | .news-box .news-timeline .ant-timeline-item .ant-timeline-item-content{ 8 | min-width: 300px !important; 9 | width: 75% !important; 10 | } -------------------------------------------------------------------------------- /client/containers/Group/GroupLog/GroupLog.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import TimeTree from '../../../components/TimeLine/TimeLine'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | // import { Button } from 'antd' 6 | @connect(state => { 7 | return { 8 | uid: state.user.uid + '', 9 | curGroupId: state.group.currGroup._id 10 | }; 11 | }) 12 | class GroupLog extends Component { 13 | constructor(props) { 14 | super(props); 15 | } 16 | static propTypes = { 17 | uid: PropTypes.string, 18 | match: PropTypes.object, 19 | curGroupId: PropTypes.number 20 | }; 21 | render() { 22 | return ( 23 |
24 |
25 | 26 |
27 |
28 | ); 29 | } 30 | } 31 | 32 | export default GroupLog; 33 | -------------------------------------------------------------------------------- /client/containers/Group/GroupSetting/GroupSetting.scss: -------------------------------------------------------------------------------- 1 | .panel-group { 2 | .row { 3 | // display: flex; 4 | // align-items: center; 5 | margin-bottom: .24rem; 6 | } 7 | .save { 8 | margin-top: .48rem 9 | } 10 | .left { 11 | flex: 100px 0 1; 12 | text-align: right; 13 | } 14 | .right { 15 | flex: 830px 0 1; 16 | } 17 | .label { 18 | text-align: right; 19 | } 20 | .save-button{ 21 | text-align: center; 22 | } 23 | 24 | .custom-field-rule{ 25 | padding-top: 4px; 26 | position: absolute; 27 | color: #f5222d ; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /client/containers/Group/MemberList/MemberList.scss: -------------------------------------------------------------------------------- 1 | @import '../../../styles/mixin.scss'; 2 | 3 | .m-panel{ 4 | background-color: #fff; 5 | padding: 24px; 6 | min-height: 4.68rem; 7 | margin-top: 0; 8 | // box-shadow: $box-shadow-panel; 9 | } 10 | 11 | .m-tab { 12 | overflow: inherit !important; 13 | } 14 | 15 | .btn-container { 16 | text-align: right; 17 | } 18 | 19 | .modal-input { 20 | display: flex; 21 | align-items: center; 22 | margin-bottom: .24rem; 23 | .label { 24 | text-align: right; 25 | } 26 | .select { 27 | width: 1.2rem; 28 | } 29 | } 30 | 31 | .member-opration { 32 | text-align: right; 33 | .select { 34 | width: 1.2rem; 35 | } 36 | .btn-danger { 37 | margin-left: .08rem; 38 | // background-color: transparent; 39 | border-color: transparent; 40 | } 41 | } 42 | 43 | .m-user { 44 | display: flex; 45 | align-items: center; 46 | .m-user-img { 47 | width: .32rem; 48 | height: .32rem; 49 | border-radius: .04rem; 50 | } 51 | .m-user-name { 52 | margin-left: 8px; 53 | } 54 | 55 | } 56 | .usernamelabel,.usernameauth{ 57 | line-height: 36px; 58 | } 59 | -------------------------------------------------------------------------------- /client/containers/Group/ProjectList/ProjectList.scss: -------------------------------------------------------------------------------- 1 | .ant-tabs-bar { 2 | border-bottom: 1px solid transparent; 3 | margin-bottom: 0; 4 | } 5 | 6 | 7 | .m-panel{ 8 | background-color: #fff; 9 | padding: 24px; 10 | min-height: 4.68rem; 11 | margin-top: 0; 12 | } 13 | 14 | .project-list{ 15 | .project-list-header{ 16 | background: #eee; 17 | height: 64px; 18 | line-height: 40px; 19 | border-radius: 4px; 20 | text-align: right; 21 | padding: 0 10px; 22 | font-weight: bold; 23 | margin-bottom: 15px; 24 | display: flex; 25 | align-items: center; 26 | color: rgba(39, 56, 72, 0.85); 27 | font-weight: 500; 28 | } 29 | .owner-type{ 30 | // padding: 10px; 31 | font-size: 15px; 32 | // background-color: #eee; 33 | // border-radius: 4px; 34 | // margin-bottom: 15px; 35 | font-weight: 400; 36 | margin-bottom: 0.16rem; 37 | border-left: 3px solid #2395f1; 38 | padding-left: 8px; 39 | } 40 | } 41 | 42 | 43 | 44 | .ant-input-group-wrapper { 45 | width: 100%; 46 | } 47 | 48 | .dynamic-delete-button { 49 | cursor: pointer; 50 | position: relative; 51 | top: 4px; 52 | font-size: 24px; 53 | color: #999; 54 | transition: all .3s; 55 | } 56 | .dynamic-delete-button:hover { 57 | color: #777; 58 | } 59 | .dynamic-delete-button[disabled] { 60 | cursor: not-allowed; 61 | opacity: 0.5; 62 | } 63 | -------------------------------------------------------------------------------- /client/containers/Login/Login.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/common.scss'; 2 | 3 | // .login-body { 4 | // background-color: #fff; 5 | // } 6 | .login-container { 7 | padding-bottom: .6rem; 8 | } 9 | 10 | .login-form-button { 11 | background-image: linear-gradient(to right, #6d69fe 0%, #48a0fa 100%) !important; 12 | border: none !important; 13 | margin-top: .2rem; 14 | width: 100%; 15 | } 16 | 17 | .ant-form-item { 18 | margin-bottom: .1rem; 19 | } 20 | 21 | .qsso-breakline{ 22 | display: flex; 23 | align-items: center; 24 | color: #6d7c90; 25 | margin: .2rem auto; 26 | &:before, &:after{ 27 | content: ""; 28 | display: inline-block; 29 | height: .02rem; 30 | flex: 1; 31 | border-top: .01rem solid #6d7c90; 32 | } 33 | .qsso-breakword{ 34 | padding: 0 .1rem; 35 | } 36 | } 37 | 38 | .card-login { 39 | margin-top: 1.6rem; 40 | margin-bottom: 1.6rem; 41 | border-radius: .04rem; 42 | position: relative; 43 | .login-logo { 44 | font-size: 0; 45 | position: absolute; 46 | left: 50%; 47 | top: 0; 48 | background-image: linear-gradient(-20deg, #21d4fd 0%, #b721ff 100%); 49 | transform: translate(-50%, -50%); 50 | padding: .16rem; 51 | border-radius: 50%; 52 | box-shadow: 0 4px 6px rgba(50,50,93,.11), 0 1px 3px rgba(0,0,0,.08); 53 | } 54 | .login-title { 55 | text-align: center; 56 | padding-top: .5rem; 57 | font-size: .4rem; 58 | font-weight: 200; 59 | color: #2e2e5a; 60 | } 61 | .svg { 62 | animation: spin 5s linear infinite; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /client/containers/Login/LoginContainer.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import Login from './LoginWrap'; 3 | import { Row, Col, Card } from 'antd'; 4 | import LogoSVG from '../../components/LogoSVG/index.js'; 5 | 6 | class LoginContainer extends Component { 7 | render() { 8 | return ( 9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |

YApi Pro

22 |
23 | 24 |
25 | 26 |
27 | 28 |
29 |
30 |
31 |
32 | ); 33 | } 34 | } 35 | 36 | export default LoginContainer; 37 | -------------------------------------------------------------------------------- /client/containers/Login/LoginWrap.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { Tabs } from 'antd'; 5 | import LoginForm from './Login'; 6 | import RegForm from './Reg'; 7 | import './Login.scss'; 8 | const TabPane = Tabs.TabPane; 9 | 10 | @connect(state => ({ 11 | loginWrapActiveKey: state.user.loginWrapActiveKey, 12 | canRegister: state.user.canRegister 13 | })) 14 | export default class LoginWrap extends Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | 19 | static propTypes = { 20 | form: PropTypes.object, 21 | loginWrapActiveKey: PropTypes.string, 22 | canRegister: PropTypes.bool 23 | }; 24 | 25 | render() { 26 | const { loginWrapActiveKey, canRegister } = this.props; 27 | {/** show only login when register is disabled */} 28 | return ( 29 | 34 | 35 | 36 | 37 | 38 | {canRegister ? :
管理员已禁止注册,请联系管理员
} 39 |
40 |
41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /client/containers/News/News.js: -------------------------------------------------------------------------------- 1 | import './News.scss'; 2 | import React, { PureComponent as Component } from 'react'; 3 | import NewsTimeline from './NewsTimeline/NewsTimeline'; 4 | import { connect } from 'react-redux'; 5 | import PropTypes from 'prop-types'; 6 | import Breadcrumb from '../../components/Breadcrumb/Breadcrumb'; 7 | import { Button } from 'antd'; 8 | import { getMockUrl } from '../../reducer/modules/news.js'; 9 | import Subnav from '../../components/Subnav/Subnav.js'; 10 | 11 | @connect( 12 | state => { 13 | return { 14 | uid: state.user.uid + '' 15 | }; 16 | }, 17 | { 18 | getMockUrl: getMockUrl 19 | } 20 | ) 21 | class News extends Component { 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | mockURL: '' 26 | }; 27 | } 28 | static propTypes = { 29 | uid: PropTypes.string, 30 | getMockUrl: PropTypes.func 31 | }; 32 | UNSAFE_componentWillMount() { 33 | //const that = this; 34 | // this.props.getMockUrl(2724).then(function(data){ 35 | // const { prd_host, basepath, protocol } = data.payload.data.data; 36 | // const mockURL = `${protocol}://${prd_host}${basepath}/{path}`; 37 | // that.setState({ 38 | // mockURL: mockURL 39 | // }) 40 | // }) 41 | } 42 | render() { 43 | return ( 44 |
45 | 62 |
63 |
64 |
65 | 66 |
67 | Mock地址: 68 |

{this.state.mockURL}

69 | 70 |
71 |
72 | 73 |
74 |
75 |
76 | ); 77 | } 78 | } 79 | 80 | export default News; 81 | -------------------------------------------------------------------------------- /client/containers/News/News.scss: -------------------------------------------------------------------------------- 1 | @import '../../styles/mixin.scss'; 2 | 3 | .news-box { 4 | @include row-width-limit; 5 | 6 | display: -webkit-box; 7 | -webkit-box-flex: 1; 8 | margin: 0px auto 0 auto; 9 | font-size: 0.14rem; 10 | background: #FFF; 11 | display: block; 12 | 13 | .news-timeline{ 14 | padding: 24px; 15 | padding-left: 125px; 16 | color: #6b6c6d; 17 | .ant-timeline-item{ 18 | min-height: 60px; 19 | } 20 | .ant-timeline-item-head{ 21 | width: 30px; 22 | height: 30px; 23 | left: -8px; 24 | top: -4px; 25 | border-color:#e1e3e4; 26 | } 27 | .logusername{ 28 | color: #4eaef3; 29 | padding: 0px 16px 0px 8px; 30 | cursor: pointer; 31 | } 32 | .logtype{ 33 | padding-right: 16px; 34 | } 35 | .logtime{ 36 | padding-right: 16px; 37 | 38 | } 39 | .logcontent{ 40 | display: block; 41 | padding-left: 8px; 42 | line-height: 24px; 43 | } 44 | .logoTimeago{ 45 | position: absolute; 46 | left: -80px; 47 | top: 5px; 48 | color: #c0c1c1; 49 | } 50 | .logbidden{ 51 | color: #c0c1c1; 52 | cursor: default; 53 | line-height: 30px; 54 | padding-left: 30px; 55 | } 56 | .loggetMore{ 57 | line-height: 30px; 58 | padding-left: 30px; 59 | color: #4eaef3; 60 | } 61 | } 62 | .logHead{ 63 | height: 80px; 64 | width: 100%; 65 | border-bottom: 1px solid #e9e9e9; 66 | padding: 24px 0px; 67 | overflow: hidden; 68 | .breadcrumb-container{ 69 | float: left; 70 | min-width:100px; 71 | } 72 | .Mockurl{ 73 | width: 500px; 74 | float: right; 75 | color: #7b7b7b; 76 | >span{ 77 | float: left; 78 | line-height: 30px; 79 | } 80 | p{ 81 | width: 60%; 82 | display: inline-block; 83 | position: relative; 84 | padding: 4px 7px; 85 | height: 28px; 86 | cursor: text; 87 | font-size: 13px; 88 | color: rgba(0,0,0,.65); 89 | background-color: #fff; 90 | background-image: none; 91 | border: 1px solid #d9d9d9; 92 | -webkit-transition: all .3s; 93 | transition: all .3s; 94 | overflow-x:auto; 95 | } 96 | button{ 97 | float: right; 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /client/containers/News/NewsList/NewsList.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { Menu } from 'antd'; 5 | import { fetchNewsData } from '../../../reducer/modules/news.js'; 6 | 7 | const logList = [ 8 | { 9 | name: '用户' 10 | }, 11 | { 12 | name: '分组' 13 | }, 14 | { 15 | name: '接口' 16 | }, 17 | { 18 | name: '项目' 19 | } 20 | ]; 21 | @connect( 22 | state => { 23 | // console.log(state); 24 | return { 25 | uid: state.user.uid + '', 26 | newsData: state.news.newsData 27 | }; 28 | }, 29 | { 30 | fetchNewsData 31 | } 32 | ) 33 | class NewsList extends Component { 34 | static propTypes = { 35 | fetchNewsData: PropTypes.func, 36 | setLoading: PropTypes.func, 37 | uid: PropTypes.string 38 | }; 39 | 40 | constructor(props) { 41 | super(props); 42 | this.state = { 43 | selectedKeys: 0 44 | }; 45 | } 46 | getLogData(e) { 47 | // page,size,logId 48 | // console.log(e.key); 49 | this.setState({ 50 | selectedKeys: +e.key 51 | }); 52 | const that = this; 53 | this.props.setLoading(true); 54 | this.props.fetchNewsData(+this.props.uid, 0, 5).then(function() { 55 | that.props.setLoading(false); 56 | }); 57 | } 58 | render() { 59 | return ( 60 |
61 |

日志类型

62 | 67 | {logList.map((item, i) => { 68 | return ( 69 | 70 | {item.name} 71 | 72 | ); 73 | })} 74 | 75 |
76 | ); 77 | } 78 | } 79 | 80 | export default NewsList; 81 | -------------------------------------------------------------------------------- /client/containers/Project/Activity/Activity.js: -------------------------------------------------------------------------------- 1 | import './Activity.scss'; 2 | import React, { PureComponent as Component } from 'react'; 3 | import TimeTree from '../../../components/TimeLine/TimeLine'; 4 | import { connect } from 'react-redux'; 5 | import PropTypes from 'prop-types'; 6 | import { Button } from 'antd'; 7 | @connect(state => { 8 | return { 9 | uid: state.user.uid + '', 10 | curdata: state.inter.curdata, 11 | currProject: state.project.currProject 12 | }; 13 | }) 14 | class Activity extends Component { 15 | constructor(props) { 16 | super(props); 17 | } 18 | static propTypes = { 19 | uid: PropTypes.string, 20 | getMockUrl: PropTypes.func, 21 | match: PropTypes.object, 22 | curdata: PropTypes.object, 23 | currProject: PropTypes.object 24 | }; 25 | render() { 26 | let { currProject } = this.props; 27 | return ( 28 |
29 |
30 |
31 | {/**/} 32 |
33 |

高效、易用、可部署的API管理平台

34 |
35 |
36 | Mock地址: 37 |

38 | {location.protocol + 39 | '//' + 40 | location.hostname + 41 | (location.port !== '' ? ':' + location.port : '') + 42 | `/mock/${currProject._id}${currProject.basepath}/yourPath`} 43 |

44 | 49 |
50 |
51 | 52 |
53 |
54 | ); 55 | } 56 | } 57 | 58 | export default Activity; 59 | -------------------------------------------------------------------------------- /client/containers/Project/Activity/Activity.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/containers/Project/Interface/InterfaceCol/InterfaceCaseContent.scss: -------------------------------------------------------------------------------- 1 | .case-content { 2 | padding: 6px 0; 3 | .case-title { 4 | display: flex; 5 | .case-name { 6 | margin-left: 8px; 7 | border-radius: 4px; 8 | border: 1px solid transparent; 9 | padding: 0 5px; 10 | background-color: #eee; 11 | font-size: 20px; 12 | flex-grow: 1; 13 | cursor: pointer; 14 | } 15 | .case-name:hover { 16 | color: rgba(0,0,0,.65); 17 | border: 1px solid #d9d9d9; 18 | } 19 | .edit-case-name { 20 | margin-left: 8px; 21 | display: flex; 22 | flex-grow: 1; 23 | } 24 | .inter-link { 25 | flex-basis: 50px; 26 | position: relative; 27 | } 28 | .inter-link .text { 29 | position: absolute; 30 | bottom: 4px; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /client/containers/Project/Interface/InterfaceList/AddInterfaceCatForm.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Form, Input, Button } from 'antd'; 4 | const FormItem = Form.Item; 5 | function hasErrors(fieldsError) { 6 | return Object.keys(fieldsError).some(field => fieldsError[field]); 7 | } 8 | class AddInterfaceForm extends Component { 9 | static propTypes = { 10 | form: PropTypes.object, 11 | onSubmit: PropTypes.func, 12 | onCancel: PropTypes.func, 13 | catdata: PropTypes.object 14 | }; 15 | handleSubmit = e => { 16 | e.preventDefault(); 17 | this.props.form.validateFields((err, values) => { 18 | if (!err) { 19 | this.props.onSubmit(values); 20 | } 21 | }); 22 | }; 23 | 24 | render() { 25 | const { getFieldDecorator, getFieldsError } = this.props.form; 26 | const formItemLayout = { 27 | labelCol: { 28 | xs: { span: 24 }, 29 | sm: { span: 6 } 30 | }, 31 | wrapperCol: { 32 | xs: { span: 24 }, 33 | sm: { span: 14 } 34 | } 35 | }; 36 | 37 | return ( 38 |
39 | 40 | {getFieldDecorator('name', { 41 | rules: [ 42 | { 43 | required: true, 44 | message: '请输入分类名称!' 45 | } 46 | ], 47 | initialValue: this.props.catdata ? this.props.catdata.name || null : null 48 | })()} 49 | 50 | 51 | {getFieldDecorator('desc', { 52 | initialValue: this.props.catdata ? this.props.catdata.desc || null : null 53 | })()} 54 | 55 | 56 | 57 | 60 | 63 | 64 |
65 | ); 66 | } 67 | } 68 | 69 | export default Form.create()(AddInterfaceForm); 70 | -------------------------------------------------------------------------------- /client/containers/Project/Interface/InterfaceList/Run/Run.scss: -------------------------------------------------------------------------------- 1 | 2 | .add-col-modal { 3 | .col-list { 4 | height: 200px; 5 | overflow: auto; 6 | margin: 7px 0 16px 0; 7 | background: #eaeaea; 8 | .col-item { 9 | padding: 7px 10px 7px 10px; 10 | } 11 | .col-item:hover { 12 | background: #fa0; 13 | } 14 | .col-item.selected { 15 | background: #2395f1; 16 | color: rgba(255, 255, 255, 1); 17 | } 18 | } 19 | } -------------------------------------------------------------------------------- /client/containers/Project/Interface/InterfaceList/interfaceMenu.scss: -------------------------------------------------------------------------------- 1 | .tree-wrappper{ 2 | min-height: 500px; 3 | overflow-y: scroll; 4 | } -------------------------------------------------------------------------------- /client/containers/Project/Setting/ProjectData/ProjectData.scss: -------------------------------------------------------------------------------- 1 | .postman-dataImport{ 2 | display: flex; 3 | .dataImportCon{ 4 | min-width: 304px; 5 | background-color: #ececec; 6 | padding: 16px; 7 | border-radius: 4px; 8 | 9 | .ant-upload-drag{ 10 | padding: 16px; 11 | background-color: white; 12 | } 13 | .dataImportTile{ 14 | color: #2395f1; 15 | padding: 16px 0px; 16 | font-weight: 500; 17 | width: 100%; 18 | .ant-select{ 19 | width: 100%; 20 | } 21 | } 22 | 23 | .dataExport{ 24 | padding-bottom: 16px; 25 | font-weight: 500; 26 | width: 100%; 27 | } 28 | 29 | .dataSync{ 30 | padding-top: 16px; 31 | font-weight: 500; 32 | width: 100%; 33 | .label{ 34 | padding-right: 8px; 35 | width: 115px; 36 | display: inline-block; 37 | } 38 | .label:after { 39 | content: ":"; 40 | margin: 0 8px 0 2px; 41 | position: relative; 42 | top: -.5px; 43 | } 44 | } 45 | 46 | .import-content { 47 | margin-top: 16px; 48 | height: 180px; 49 | } 50 | 51 | .url-import-content { 52 | text-align: center; 53 | .url-btn{ 54 | margin-top: 16px; 55 | } 56 | } 57 | 58 | .export-content{ 59 | text-align: center; 60 | } 61 | .export-desc{ 62 | padding-bottom: 15px; 63 | } 64 | .export-button{ 65 | width: 100px; 66 | height:35px; 67 | } 68 | 69 | .wiki-btn { 70 | margin-left: 8px; 71 | } 72 | } 73 | } 74 | 75 | .postman-dataImport-modal{ 76 | 77 | .postman-dataImport-modal-content{ 78 | max-height: 600px; 79 | min-width: 534px; 80 | overflow-y: scroll; 81 | padding-top: 8px; 82 | 83 | } 84 | .postman-dataImport-show-diff{ 85 | padding: 4px 0; 86 | 87 | } 88 | 89 | .info{ 90 | font-weight: 400; 91 | font-size: 16px; 92 | padding-top: 24px; 93 | } 94 | 95 | } 96 | 97 | .dataImport-confirm{ 98 | .ant-modal-content .ant-confirm-btns{ 99 | margin-top: 10px; 100 | } 101 | } 102 | 103 | 104 | -------------------------------------------------------------------------------- /client/containers/Project/Setting/ProjectEnv/index.scss: -------------------------------------------------------------------------------- 1 | .m-env-panel { 2 | // padding-top: 8px; 3 | min-height: 4.68rem; 4 | margin-top: 0; 5 | background-color: #fff; 6 | } 7 | 8 | .project-env{ 9 | min-height: 4.68rem; 10 | .env-icon-style{ 11 | display: flex; 12 | justify-content: space-between; 13 | align-items: center; 14 | .anticon{ 15 | font-size: 15px; 16 | } 17 | } 18 | 19 | .menu-item{ 20 | padding: 0 16px; 21 | font-size: 13px; 22 | line-height: 42px; 23 | height: 42px; 24 | overflow: hidden; 25 | text-overflow: ellipsis; 26 | cursor: pointer; 27 | 28 | } 29 | 30 | .menu-item-checked{ 31 | background-color: #eef7fe; 32 | color: #2395f1; 33 | font-size: 14px; 34 | margin-right: -1px; 35 | border-right: 2px solid #2395f1; 36 | } 37 | 38 | .first-menu-item{ 39 | background-color: #eceef1; 40 | } 41 | 42 | .delete{ 43 | font-size: 20px; 44 | top: 8px; 45 | } 46 | 47 | .env-content{ 48 | border-left: 1px solid #ccc; 49 | } 50 | 51 | .ant-menu-item-disabled{ 52 | cursor: pointer; 53 | } 54 | 55 | .m-empty-prompt{ 56 | display: flex; 57 | height: 400px; 58 | span{ 59 | margin: auto; 60 | font-size: 16px; 61 | .anticon { 62 | padding-right: 16px; 63 | font-size: 24px; 64 | } 65 | } 66 | } 67 | 68 | .env-label{ 69 | padding-bottom: 8px; 70 | a { 71 | color: #636363; 72 | } 73 | } 74 | 75 | .env-last-row { 76 | display: none; 77 | } 78 | 79 | .env-name{ 80 | width: 150px; 81 | overflow: hidden; 82 | text-overflow: ellipsis; 83 | } 84 | .btnwrap-changeproject { 85 | text-align: center; 86 | padding: .16rem 0; 87 | background: #fff; 88 | background-color: #fff; 89 | margin: 0 -.4rem; 90 | background-size: 4px 4px; 91 | } 92 | 93 | } -------------------------------------------------------------------------------- /client/containers/Project/Setting/ProjectMessage/ProjectTag.scss: -------------------------------------------------------------------------------- 1 | 2 | .project-tag { 3 | .item-name { 4 | margin-right: 16px; 5 | } 6 | 7 | .delete { 8 | font-size: 16px; 9 | } 10 | 11 | .tag-item { 12 | margin-bottom: 8px; 13 | } 14 | 15 | .tag-last-row { 16 | display: none; 17 | } 18 | } -------------------------------------------------------------------------------- /client/containers/Project/Setting/ProjectRequest/project-request.scss: -------------------------------------------------------------------------------- 1 | .project-request{ 2 | background: #fff; 3 | padding: 15px; 4 | 5 | .request-editor{ 6 | min-height: 300px; 7 | margin-top: 10px; 8 | background-color: #eee; 9 | } 10 | } -------------------------------------------------------------------------------- /client/containers/Project/Setting/ProjectToken/ProjectToken.scss: -------------------------------------------------------------------------------- 1 | .project-token { 2 | background: #fff; 3 | padding: 15px; 4 | min-height: 4.68rem; 5 | 6 | .token{ 7 | padding: 16px; 8 | font-size: 16px; 9 | font-weight: 500; 10 | 11 | } 12 | 13 | .token-message{ 14 | padding: 8px; 15 | margin-right: 8px; 16 | background-color: #f5f5f5; 17 | 18 | } 19 | 20 | .open-api{ 21 | margin-top: 10px; 22 | margin-left: 20px; 23 | li{ 24 | margin-bottom: 10px; 25 | } 26 | } 27 | 28 | .message{ 29 | padding: 16px 0 0 16px; 30 | font-size: 14px; 31 | } 32 | 33 | .token-title{ 34 | font-size: 16px; 35 | // background-color: #eee; 36 | // border-radius: 4px; 37 | // margin-bottom: 15px; 38 | font-weight: 400; 39 | margin-bottom: 0.16rem; 40 | border-left: 3px solid #2395f1; 41 | padding-left: 8px; 42 | } 43 | 44 | .blockquote{ 45 | border-left: 4px solid #ff561b; 46 | // background-color: #f8f8f8; 47 | padding: .12rem .24rem; 48 | position: relative; 49 | margin-left: 16px; 50 | } 51 | 52 | .blockquote:before { 53 | content: '!'; 54 | display: block; 55 | position: absolute; 56 | left: -.12rem; 57 | top: .12rem; 58 | width: 20px; 59 | height: 20px; 60 | background-color: #ff561b; 61 | color: #fff; 62 | border-radius: 50%; 63 | text-align: center; 64 | font-family: 'Dosis', 'Source Sans Pro', 'Helvetica Neue', Arial, sans-serif; 65 | } 66 | 67 | .token-btn{ 68 | margin-right: 8px; 69 | } 70 | } -------------------------------------------------------------------------------- /client/containers/Project/Setting/Setting.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react'; 2 | import { Tabs } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | import ProjectMessage from './ProjectMessage/ProjectMessage.js'; 5 | import ProjectEnv from './ProjectEnv/index.js'; 6 | import ProjectRequest from './ProjectRequest/ProjectRequest'; 7 | import ProjectToken from './ProjectToken/ProjectToken'; 8 | import ProjectMock from './ProjectMock/index.js'; 9 | import { connect } from 'react-redux'; 10 | const TabPane = Tabs.TabPane; 11 | const plugin = require('client/plugin.js'); 12 | 13 | const routers = {} 14 | 15 | import './Setting.scss'; 16 | 17 | @connect(state => { 18 | return { 19 | curProjectRole: state.project.currProject.role 20 | }; 21 | }) 22 | class Setting extends Component { 23 | static propTypes = { 24 | match: PropTypes.object, 25 | curProjectRole: PropTypes.string 26 | }; 27 | render() { 28 | const id = this.props.match.params.id; 29 | plugin.emitHook('sub_setting_nav', routers); 30 | return ( 31 |
32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | {this.props.curProjectRole !== 'guest' ? ( 43 | 44 | 45 | 46 | ) : null} 47 | 48 | 49 | 50 | {Object.keys(routers).map(key=>{ 51 | const C = routers[key].component; 52 | return 53 | 54 | 55 | })} 56 | 57 |
58 | ); 59 | } 60 | } 61 | 62 | export default Setting; 63 | -------------------------------------------------------------------------------- /client/containers/User/User.js: -------------------------------------------------------------------------------- 1 | import './index.scss'; 2 | import React, { PureComponent as Component } from 'react'; 3 | import { connect } from 'react-redux'; 4 | import { Route } from 'react-router-dom'; 5 | import List from './List.js'; 6 | import PropTypes from 'prop-types'; 7 | import Profile from './Profile.js'; 8 | import { Row } from 'antd'; 9 | @connect( 10 | state => { 11 | return { 12 | curUid: state.user.uid, 13 | userType: state.user.type, 14 | role: state.user.role 15 | }; 16 | }, 17 | {} 18 | ) 19 | class User extends Component { 20 | static propTypes = { 21 | match: PropTypes.object, 22 | curUid: PropTypes.number, 23 | userType: PropTypes.string, 24 | role: PropTypes.string 25 | }; 26 | 27 | constructor(props) { 28 | super(props); 29 | } 30 | 31 | render() { 32 | return ( 33 |
34 |
35 | 36 | 37 | 38 | 39 |
40 |
41 | ); 42 | } 43 | } 44 | 45 | export default User; 46 | -------------------------------------------------------------------------------- /client/containers/index.js: -------------------------------------------------------------------------------- 1 | import Header from '../components/Header/Header.js'; 2 | import Home from './Home/Home.js'; 3 | import Login from './Login/LoginContainer.js'; 4 | import Group from './Group/Group.js'; 5 | import Project from './Project/Project.js'; 6 | import Follows from './Follows/Follows.js'; 7 | import AddProject from './AddProject/AddProject.js'; 8 | 9 | export { Header, Home, Login, Group, Project, Follows, AddProject }; 10 | -------------------------------------------------------------------------------- /client/font/QIconLab.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/client/font/QIconLab.eot -------------------------------------------------------------------------------- /client/font/QIconLab.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/client/font/QIconLab.ttf -------------------------------------------------------------------------------- /client/font/QIconLab.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/client/font/QIconLab.woff -------------------------------------------------------------------------------- /client/images/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/client/images/loading.gif -------------------------------------------------------------------------------- /client/index.js: -------------------------------------------------------------------------------- 1 | import './styles/common.scss'; 2 | import './styles/theme.less'; 3 | import { LocaleProvider } from 'antd'; 4 | import './plugin'; 5 | import React from 'react'; 6 | import ReactDOM from 'react-dom'; 7 | import App from './Application'; 8 | import { Provider } from 'react-redux'; 9 | import createStore from './reducer/create'; 10 | 11 | // 由于 antd 组件的默认文案是英文,所以需要修改为中文 12 | import zhCN from 'antd/lib/locale-provider/zh_CN'; 13 | 14 | const store = createStore(); 15 | 16 | ReactDOM.render( 17 | 18 | 19 | 20 | 21 | , 22 | document.getElementById('yapi') 23 | ); 24 | -------------------------------------------------------------------------------- /client/reducer/create.js: -------------------------------------------------------------------------------- 1 | import { createStore as _createStore, applyMiddleware } from 'redux'; 2 | import promiseMiddleware from 'redux-promise'; 3 | import messageMiddleware from './middleware/messageMiddleware'; 4 | import reducer from './modules/reducer'; 5 | 6 | export default function createStore(initialState = {}) { 7 | const middleware = [promiseMiddleware, messageMiddleware]; 8 | 9 | let finalCreateStore; 10 | //if (process.env.NODE_ENV === 'production') { 11 | finalCreateStore = applyMiddleware(...middleware)(_createStore); 12 | // } else { 13 | // finalCreateStore = compose( 14 | // applyMiddleware(...middleware), 15 | // window.devToolsExtension ? window.devToolsExtension() : require('../containers/DevTools/DevTools').instrument() 16 | // )(_createStore); 17 | // } 18 | 19 | const store = finalCreateStore(reducer, initialState); 20 | 21 | return store; 22 | } 23 | -------------------------------------------------------------------------------- /client/reducer/middleware/messageMiddleware.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | export default () => next => action => { 4 | if (!action) { 5 | return; 6 | } 7 | if (action.error) { 8 | message.error((action.payload && action.payload.message) || '服务器错误'); 9 | } else if ( 10 | action.payload && 11 | action.payload.data && 12 | action.payload.data.errcode && 13 | action.payload.data.errcode !== 40011 14 | ) { 15 | message.error(action.payload.data.errmsg); 16 | throw new Error(action.payload.data.errmsg); 17 | } 18 | return next(action); 19 | }; 20 | -------------------------------------------------------------------------------- /client/reducer/modules/follow.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // Actions 4 | const GET_FOLLOW_LIST = 'yapi/follow/GET_FOLLOW_LIST'; 5 | const DEL_FOLLOW = 'yapi/follow/DEL_FOLLOW'; 6 | const ADD_FOLLOW = 'yapi/follow/ADD_FOLLOW'; 7 | 8 | // Reducer 9 | const initialState = { 10 | data: [] 11 | }; 12 | 13 | export default (state = initialState, action) => { 14 | if (action.type === GET_FOLLOW_LIST) { 15 | return { 16 | ...state, 17 | data: action.payload.data.data 18 | }; 19 | } else { 20 | return state; 21 | } 22 | }; 23 | 24 | // 获取关注列表 25 | export function getFollowList(uid) { 26 | return { 27 | type: GET_FOLLOW_LIST, 28 | payload: axios.get('/api/follow/list', { 29 | params: { uid } 30 | }) 31 | }; 32 | } 33 | 34 | // 添加关注 35 | export function addFollow(param) { 36 | return { 37 | type: ADD_FOLLOW, 38 | payload: axios.post('/api/follow/add', param) 39 | }; 40 | } 41 | 42 | // 删除关注 43 | export function delFollow(id) { 44 | return { 45 | type: DEL_FOLLOW, 46 | payload: axios.post('/api/follow/del', { projectid: id }) 47 | }; 48 | } 49 | -------------------------------------------------------------------------------- /client/reducer/modules/menu.js: -------------------------------------------------------------------------------- 1 | // Actions 2 | const CHANGE_MENU_ITEM = 'yapi/menu/CHANGE_MENU_ITEM'; 3 | 4 | // Reducer 5 | const initialState = { 6 | curKey: '/' + window.location.hash.split('/')[1] 7 | }; 8 | 9 | export default (state = initialState, action) => { 10 | if (action.type === CHANGE_MENU_ITEM) { 11 | return { 12 | ...state, 13 | curKey: action.data 14 | }; 15 | } else { 16 | return state; 17 | } 18 | }; 19 | 20 | // Action Creators 21 | export function changeMenuItem(curKey) { 22 | return { 23 | type: CHANGE_MENU_ITEM, 24 | data: curKey 25 | }; 26 | } 27 | -------------------------------------------------------------------------------- /client/reducer/modules/mockCol.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // Actions 4 | const FETCH_MOCK_COL = 'yapi/mockCol/FETCH_MOCK_COL'; 5 | 6 | // Reducer 7 | const initialState = { 8 | list: [] 9 | }; 10 | 11 | export default (state = initialState, action) => { 12 | switch (action.type) { 13 | case FETCH_MOCK_COL: 14 | return { 15 | ...state, 16 | list: action.payload.data 17 | }; 18 | default: 19 | return state; 20 | } 21 | }; 22 | 23 | // Action Creators 24 | export async function fetchMockCol(interfaceId) { 25 | let result = await axios.get('/api/plugin/advmock/case/list?interface_id=' + interfaceId); 26 | return { 27 | type: FETCH_MOCK_COL, 28 | payload: result.data 29 | }; 30 | } 31 | -------------------------------------------------------------------------------- /client/reducer/modules/reducer.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import user from './user.js'; 3 | import group from './group.js'; 4 | import project from './project.js'; 5 | import inter from './interface.js'; 6 | import interfaceCol from './interfaceCol.js'; 7 | import news from './news.js'; 8 | import addInterface from './addInterface.js'; 9 | import menu from './menu.js'; 10 | import follow from './follow.js'; 11 | 12 | import { emitHook } from 'client/plugin.js'; 13 | 14 | const reducerModules = { 15 | group, 16 | user, 17 | inter, 18 | interfaceCol, 19 | project, 20 | news, 21 | addInterface, 22 | menu, 23 | follow 24 | }; 25 | emitHook('add_reducer', reducerModules); 26 | 27 | export default combineReducers(reducerModules); 28 | -------------------------------------------------------------------------------- /client/styles/mixin.scss: -------------------------------------------------------------------------------- 1 | $color-white: rgba(255,255,255, .85); 2 | $color-white-secondary: rgba(255,255,255, .67); 3 | $color-bg-gray: #ececec; 4 | $color-blue: #2395f1; 5 | $color-blue-deeper: #34495E; 6 | $color-grey-deep: #929aac; 7 | $color-black-light: #404040; 8 | $color-bg-dark: #32363a; // 背景色 - header 用的深蓝色 9 | $box-shadow-panel: 0 2px 4px 0 rgba(0, 0, 0, 0.2); 10 | 11 | $color-text-dark: rgba(13, 27, 62, 0.65); 12 | 13 | $color-antd-green: #00a854; 14 | $color-antd-yellow: #ffbf00; 15 | $color-antd-red: #f56a00; 16 | $color-antd-pink: #f5317f; 17 | $color-antd-cyan: #00a2ae; 18 | $color-antd-gray: #bfbfbf; 19 | $color-antd-purple: #7265e6; 20 | 21 | @mixin row-width-limit { 22 | max-width: 12.2rem; 23 | min-width: 10.2rem; 24 | padding: 0 .24rem; 25 | } 26 | 27 | @mixin wrap-width-limit { 28 | min-width: 10.2rem; 29 | } 30 | -------------------------------------------------------------------------------- /client/styles/public-sass.scss: -------------------------------------------------------------------------------- 1 | $commonUrl: @import '../../styles/common.scss'; 2 | -------------------------------------------------------------------------------- /common/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | exts: [{ 3 | name: 'import-postman' 4 | },{ 5 | name: 'import-har' 6 | },{ 7 | name: 'advanced-mock' 8 | },{ 9 | name: 'import-swagger' 10 | },{ 11 | name: 'statistics' 12 | },{ 13 | name: 'export-data' 14 | },{ 15 | name: 'gen-services' 16 | },{ 17 | name: 'export-swagger2-data' 18 | },{ 19 | name: 'import-yapi-json' 20 | },{ 21 | name: 'wiki' 22 | }, { 23 | name: 'swagger-auto-sync' 24 | } 25 | // { 26 | // name: 'test' 27 | // } 28 | ] 29 | } -------------------------------------------------------------------------------- /common/createContext.js: -------------------------------------------------------------------------------- 1 | module.exports = function (uid, projectId,interfaceId) { 2 | if(!uid || !projectId || !interfaceId){ 3 | console.error('uid projectId interfaceId 不能为空', uid, projectId,interfaceId) 4 | } 5 | 6 | /** 7 | * 统一转换为number 8 | */ 9 | return { 10 | uid: +uid, 11 | projectId: +projectId, 12 | interfaceId: +interfaceId 13 | } 14 | } -------------------------------------------------------------------------------- /common/formats.js: -------------------------------------------------------------------------------- 1 | const formats = [ 2 | { 3 | name: 'url', 4 | title: 'url' 5 | }, 6 | { 7 | name: 'domain', 8 | title: '域名' 9 | }, 10 | { 11 | name: 'ip', 12 | title: 'ipv4 地址' 13 | }, 14 | { 15 | name: 'id' 16 | }, 17 | { 18 | name: 'guid' 19 | }, 20 | { 21 | name: 'now' 22 | }, 23 | { 24 | name: 'timestamp' 25 | }, 26 | { 27 | name: 'date' 28 | }, 29 | { 30 | name: 'time' 31 | }, 32 | { 33 | name: 'datetime' 34 | }, 35 | { 36 | name: 'image', 37 | title: '图片链接' 38 | }, 39 | { 40 | name: 'imageData', 41 | title: '图片' 42 | }, 43 | { 44 | name: 'email', 45 | title: '邮箱' 46 | }, 47 | { 48 | name: 'paragraph', 49 | title: '段落' 50 | }, 51 | { 52 | name: 'sentence', 53 | title: '句子' 54 | }, 55 | { 56 | name: 'word', 57 | title: '单词' 58 | }, 59 | { 60 | name: 'title', 61 | title: '标题' 62 | }, 63 | { 64 | name: 'name', 65 | title: '姓名' 66 | }, 67 | { 68 | name: 'region', 69 | title: '地区' 70 | }, 71 | { 72 | name: 'province', 73 | title: '省份' 74 | }, 75 | { 76 | name: 'city', 77 | title: '城市名' 78 | }, 79 | { 80 | name: 'county', 81 | title: '国家' 82 | }, 83 | { 84 | name: 'mobile', 85 | title: '手机号' 86 | }, 87 | { 88 | name: 'cparagraph', 89 | title: '中文本' 90 | }, 91 | { 92 | name: 'cname', 93 | title: '中文姓名' 94 | }, 95 | { 96 | title: '中文标题', 97 | name: 'ctitle' 98 | } 99 | ]; 100 | 101 | module.exports = formats; 102 | -------------------------------------------------------------------------------- /common/lib.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | 3 | function isObj(object) { 4 | return ( 5 | object && 6 | typeof object == 'object' && 7 | Object.prototype.toString.call(object).toLowerCase() == '[object object]' 8 | ); 9 | } 10 | 11 | function isArray(object) { 12 | return object && typeof object == 'object' && object.constructor == Array; 13 | } 14 | 15 | function getLength(object) { 16 | return Object.keys(object).length; 17 | } 18 | 19 | function Compare(objA, objB) { 20 | if (!isObj(objA) && !isObj(objB)) { 21 | if (isArray(objA) && isArray(objB)) { 22 | return CompareArray(objA, objB, true); 23 | } 24 | return objA == objB; 25 | } 26 | if (!isObj(objA) || !isObj(objB)) return false; 27 | if (getLength(objA) != getLength(objB)) return false; 28 | return CompareObj(objA, objB, true); 29 | } 30 | 31 | function CompareArray(objA, objB, flag) { 32 | if (objA.length != objB.length) return false; 33 | for (let i in objB) { 34 | if (!Compare(objA[i], objB[i])) { 35 | flag = false; 36 | break; 37 | } 38 | } 39 | 40 | return flag; 41 | } 42 | 43 | function CompareObj(objA, objB, flag) { 44 | for (var key in objA) { 45 | if (!flag) break; 46 | if (!objB.hasOwnProperty(key)) { 47 | flag = false; 48 | break; 49 | } 50 | if (!isArray(objA[key])) { 51 | if (objB[key] != objA[key]) { 52 | flag = false; 53 | break; 54 | } 55 | } else { 56 | if (!isArray(objB[key])) { 57 | flag = false; 58 | break; 59 | } 60 | var oA = objA[key], 61 | oB = objB[key]; 62 | if (oA.length != oB.length) { 63 | flag = false; 64 | break; 65 | } 66 | for (var k in oA) { 67 | if (!flag) break; 68 | flag = CompareObj(oA[k], oB[k], flag); 69 | } 70 | } 71 | } 72 | return flag; 73 | } 74 | 75 | exports.jsonEqual = Compare; 76 | 77 | exports.isDeepMatch = function(obj, properties) { 78 | 79 | if (!properties || typeof properties !== 'object' || Object.keys(properties).length === 0) { 80 | return true; 81 | } 82 | 83 | if (!obj || typeof obj !== 'object' || Object.keys(obj).length === 0) { 84 | return false; 85 | } 86 | 87 | let match = true; 88 | let keys = Object.keys(properties) 89 | for (let index=0; index< keys.length; index++) { 90 | let i = keys[index]; 91 | if (!Compare(obj[i], properties[i])) { 92 | match = false; 93 | break; 94 | } 95 | } 96 | return match; 97 | }; 98 | -------------------------------------------------------------------------------- /common/mergeJsonSchema.js: -------------------------------------------------------------------------------- 1 | function isPlainObject(obj) { 2 | return obj ? typeof obj === 'object' && Object.getPrototypeOf(obj) === Object.prototype : false; 3 | } 4 | 5 | function handleProperties(sourceProperties, mergeProperties){ 6 | if(!isPlainObject(mergeProperties)){ 7 | return mergeProperties 8 | } 9 | if(! isPlainObject(sourceProperties)){ 10 | return mergeProperties 11 | } 12 | Object.keys(mergeProperties).forEach(key=>{ 13 | mergeProperties[key]= handleSchema(sourceProperties[key], mergeProperties[key]) 14 | }) 15 | return mergeProperties; 16 | } 17 | 18 | 19 | function handleSchema(source, merge){ 20 | if(!isPlainObject(source)) return merge; 21 | if(!isPlainObject(merge)) return merge; 22 | let result = {} 23 | Object.assign(result, source, merge) 24 | if(merge.type === 'object'){ 25 | result.properties = handleProperties(source.properties, merge.properties); 26 | }else if(merge.type === 'array'){ 27 | result.items = handleSchema(source.items, merge.items); 28 | } 29 | return result; 30 | } 31 | 32 | module.exports = function(sourceJsonSchema, mergeJsonSchema){ 33 | return handleSchema(sourceJsonSchema, mergeJsonSchema) 34 | } -------------------------------------------------------------------------------- /common/plugin.js: -------------------------------------------------------------------------------- 1 | const _ = require('underscore'); 2 | 3 | function getPluginConfig(name, type) { 4 | let pluginConfig; 5 | if (type === 'ext') { 6 | pluginConfig = require('../exts/yapi-plugin-' + name); 7 | } else { 8 | pluginConfig = require('yapi-plugin-' + name); 9 | } 10 | 11 | if (!pluginConfig || typeof pluginConfig !== 'object') { 12 | throw new Error(`Plugin ${name} Config 配置错误,请检查 yapi-plugin-${name}/index.js`); 13 | } 14 | 15 | return { 16 | server: pluginConfig.server, 17 | client: pluginConfig.client 18 | } 19 | } 20 | 21 | 22 | /** 23 | * type @string enum[plugin, ext] plugin是外部插件,ext是内部插件 24 | */ 25 | exports.initPlugins = function (plugins, type) { 26 | if (!plugins) { 27 | return []; 28 | } 29 | if (typeof plugins !== 'object' || !Array.isArray(plugins)) { 30 | throw new Error('插件配置有误,请检查', plugins); 31 | } 32 | 33 | plugins = plugins.map(item => { 34 | let pluginConfig; 35 | if (item && typeof item === 'string') { 36 | pluginConfig = getPluginConfig(item, type); 37 | return Object.assign({}, pluginConfig, { name: item, enable: true }) 38 | } else if (item && typeof item === 'object') { 39 | pluginConfig = getPluginConfig(item.name, type); 40 | return Object.assign({}, 41 | pluginConfig, 42 | { 43 | name: item.name, 44 | options: item.options, 45 | enable: item.enable === false ? false : true 46 | }) 47 | } 48 | }) 49 | plugins = plugins.filter(item => { 50 | return item.enable === true && (item.server || item.client) 51 | }) 52 | 53 | return _.uniq(plugins, item => item.name) 54 | } -------------------------------------------------------------------------------- /common/tui-editor/dist/tui-editor-2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/common/tui-editor/dist/tui-editor-2x.png -------------------------------------------------------------------------------- /common/tui-editor/dist/tui-editor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/common/tui-editor/dist/tui-editor.png -------------------------------------------------------------------------------- /config_example.json: -------------------------------------------------------------------------------- 1 | { 2 | "port": "3000", 3 | "adminAccount": "admin@admin.com", 4 | "timeout":120000, 5 | "db": { 6 | "servername": "127.0.0.1", 7 | "DATABASE": "yapi", 8 | "port": 27017, 9 | "user": "test1", 10 | "pass": "test1", 11 | "authSource": "" 12 | }, 13 | "mail": { 14 | "enable": true, 15 | "host": "smtp.163.com", 16 | "port": 465, 17 | "from": "***@163.com", 18 | "auth": { 19 | "user": "***@163.com", 20 | "pass": "*****" 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /docs/NAV.md: -------------------------------------------------------------------------------- 1 | # YApi 2 | ![logo](documents/images/logo_header@2x.png) 3 | 4 | 5 | * [教程](documents/index.md) 6 | * [内网部署](devops/index.md) 7 | * [开放Api](openapi.html) -------------------------------------------------------------------------------- /docs/devops/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # 内网部署 2 | 3 | * [安装](index.md#安装) 4 | * [服务器管理](index.md#服务器管理) 5 | * [升级](index.md#升级) 6 | 7 | --- 8 | 9 | * [mongodb集群](index.md#mongodb集群) 10 | * [配置邮箱](index.md#配置邮箱) 11 | * [配置LDAP登录](index.md#配置LDAP登录) 12 | * [禁止注册](index.md#禁止注册) 13 | * [版本通知](index.md#版本通知) 14 | -------------------------------------------------------------------------------- /docs/devops/ldap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/devops/ldap.png -------------------------------------------------------------------------------- /docs/documents/2019-01-15-14-05-46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/2019-01-15-14-05-46.png -------------------------------------------------------------------------------- /docs/documents/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | !!!include(CHANGELOG.md)!!! -------------------------------------------------------------------------------- /docs/documents/SUMMARY.md: -------------------------------------------------------------------------------- 1 | # YApi 2 | 3 | ### 快速上手 4 | 5 | * [认识YApi](index.md) 6 | * [创建第一个API](quickstart.md) 7 | 8 | ### 进阶篇 9 | * [权限](manage.md) 10 | * [项目操作](project.md) 11 | * [基本设置](project.md#基本设置) 12 | * [新建项目](project.md#新建项目) 13 | * [修改项目](project.md#修改项目) 14 | * [项目迁移](project.md#项目迁移) 15 | * [项目拷贝](project.md#项目拷贝) 16 | * [配置环境](project.md#配置环境) 17 | * [请求配置](project.md#请求配置) 18 | * [token配置](project.md#token) 19 | * [全局mock](project.md#全局mock) 20 | * [接口操作](api.md) 21 | * [接口设置](api.md#接口配置) 22 | * [接口运行](api.md#接口运行) 23 | * [数据Mock](mock.md) 24 | * [方式1. mockjs](mock.md#方式1. mockjs) 25 | * [方式2. json-schema](mock.md#方式2. json-schema) 26 | * [如何使用](mock.md#如何使用 Mock) 27 | * [严格模式](mock.md#mock请求严格模式) 28 | * [高级Mock](adv_mock.md) 29 | * [Mock 期望](adv_mock.md#Mock 期望) 30 | * [自定义脚本](adv_mock.md#自定义 Mock 脚本) 31 | * [自动化测试](case.md) 32 | * [第一步,测试集合](case.md#第一步,测试集合) 33 | * [第二步,编辑测试用例](case.md#第二步,编辑测试用例) 34 | * [第三步,运行自动化测试](case.md#第三步,运行自动化测试) 35 | * [断言脚本公共变量](case.md#断言脚本公共变量) 36 | * [服务端自动化测试](case.md#服务端自动化测试) 37 | * [数据导入](data.md) 38 | * [Postman 数据导入](data.md#Postman 数据导入) 39 | * [HAR 数据导入](data.md#HAR 数据导入) 40 | * [Swagger 数据导入](data.md#Swagger 数据导入) 41 | * [JSON 数据导入](data.md#YApi接口JSON数据导入) 42 | * [通过命令行导入接口数据](data.md#通过命令行导入接口数据) 43 | * [数据导出](export-data.md) 44 | 45 | 46 | ### 自定义 47 | * [插件](plugin-index.md) 48 | * [插件开发](plugin-dev.md) 49 | * [插件列表](plugin-list.md) 50 | * [钩子](plugin-hooks.md) 51 | * [二次开发](redev.md) 52 | 53 | --- 54 | * [常见问题解答](qa.md) 55 | * [版本记录](CHANGELOG.md) 56 | 57 | 58 | -------------------------------------------------------------------------------- /docs/documents/api.md: -------------------------------------------------------------------------------- 1 | # 接口设置 2 | 进入项目页,可以看到项目下的所有接口,需要注意的是,YApi有 `接口集合` 和 `测试集合` 两个概念。 3 | 4 | - `接口集合` 将接口进行分类,使接口结构更清晰,一个接口只能属于一个集合,且不允许与其他接口重名。 5 | - `测试集合` 为了方便我们测试接口,`测试集合` 将若干接口组合在一起,在这里一个接口可以属于不同集合。 6 | 7 | ## 接口配置 8 | 9 | [新建接口](./quickstart.md#新建接口) 后,点击新添加的接口,右侧可以看到接口的预览信息,点击右侧的 `编辑` Tab项进入编辑面板。 10 | 11 | 在该面板中你可以看到接口的基本信息(接口名称、分类、路径),除此以外,你还可以完善以下接口信息: 12 | 13 | ### 基本设置 14 | 15 | - 接口路径:可以更改 HTTP 请求方式,并且支持 restful 动态路由,例如 /api/{id}/{name}, id和name是动态参数 16 | - 选择分类:可以更改接口所在分类 17 | - 状态:用于标识接口是否开发完成。 18 | - Tag:用于标识接口tag信息(v1.3.23+),在接口list页可以根据tag过滤接口 19 | 20 | 21 | 22 | ### 请求参数设置 23 | 24 | - Query参数: 接口 url 的查询字符串,点击『添加Query参数』按钮来添加参数,可以通过拖动来交换参数位置 25 | - 请求Body:http 请求 body 部分,如果http请求方式是 post, put 等请求方式时会有 req_body 部分。req_body_type 形式有4种,分别是 form, json, file 和 raw 。 26 | - Headers: http 请求头字段,在 req_body 形式是 form 格式下会在 header 中自动生成 'Content-Type application/x-www-form-urlencoded',其他3种格式也会自动生成不同 header 27 | 28 | 29 | 30 | ### 返回数据设置 31 | 32 | - 返回数据分为 `json` & `raw` 两种形式。基于 mockjs (具体使用方法详见[Mock 介绍](./mock.md))和 json5,使用注释方式写参数说明。 为了方便数据编写可以按F9来使用全局编辑 33 | - 选择json-schema 则进入了 json 结构可视化编辑器形式, 数据以 json schema 格式解析 快速入门 Json Schema 。 34 | 35 | 36 | 37 | 38 | 39 | ### 备注 & 其他 40 | - 接口描述: 用简短的文字描述接口的作用。 41 | - 邮件通知:开启后将此次接口的改动以邮件的形式发送至项目组所有成员和关注该项目的成员(邮件默认情况下自动开启) 42 | - 开放接口:默认为关闭状态,用户可以在 数据导出 时选择只导出公开接口 43 | 44 | 45 | ## 接口运行 46 | 接口运行功能,是用来测试真实接口的,类似『Postman』的功能。 47 | 48 | 点击运行 tab ,可进入到接口测试页面,首先安装『chrome crossRequest』扩展,才可正常使用此功能。 49 | 50 | 点击保存按钮可把当前接口保存到测试集,方便下次调试。 51 | 52 | > 安装完插件记得刷新页面 53 | 54 | 55 | 56 | ### 接口返回数据验证 57 | 58 | 版本 v1.3.22 新增返回数据验证功能, 如果接口的返回数据格式为json schema 在接口运行时会对接口返回数据和定义数据格式进行校验 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /docs/documents/case-col.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/case-col.png -------------------------------------------------------------------------------- /docs/documents/clone-project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/clone-project.png -------------------------------------------------------------------------------- /docs/documents/clone-project.png.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/clone-project.png.png -------------------------------------------------------------------------------- /docs/documents/export-data.md: -------------------------------------------------------------------------------- 1 | # 数据导出 2 | 为了方便开发者将接口数据分析给第三方或其他使用者,YApi 内置了方便易用的接口数据导出功能。 3 | 4 | 5 | ## 使用教程 6 | 7 | 在 项目 -> 数据管理,选择需要导出的数据方式,一共有三种导出方式,html,markdown,json。然后点击导出按钮,将会下载数据文件。 8 | 9 | ![](export-data.png) 10 | 11 | -------------------------------------------------------------------------------- /docs/documents/export-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/export-data.png -------------------------------------------------------------------------------- /docs/documents/images/autoTest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/autoTest.png -------------------------------------------------------------------------------- /docs/documents/images/autoTestResult.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/autoTestResult.png -------------------------------------------------------------------------------- /docs/documents/images/baseSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/baseSet.png -------------------------------------------------------------------------------- /docs/documents/images/bianlif.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/bianlif.jpeg -------------------------------------------------------------------------------- /docs/documents/images/case_add.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/case_add.jpg -------------------------------------------------------------------------------- /docs/documents/images/case_add_modal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/case_add_modal.jpg -------------------------------------------------------------------------------- /docs/documents/images/case_col_add.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/case_col_add.jpg -------------------------------------------------------------------------------- /docs/documents/images/case_col_add_modal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/case_col_add_modal.jpg -------------------------------------------------------------------------------- /docs/documents/images/case_list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/case_list.jpg -------------------------------------------------------------------------------- /docs/documents/images/charles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/charles.png -------------------------------------------------------------------------------- /docs/documents/images/dbbmklogo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/dbbmklogo.jpg -------------------------------------------------------------------------------- /docs/documents/images/elong.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/elong.jpeg -------------------------------------------------------------------------------- /docs/documents/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/favicon.ico -------------------------------------------------------------------------------- /docs/documents/images/getQuery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/getQuery.png -------------------------------------------------------------------------------- /docs/documents/images/interface_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/interface_add.png -------------------------------------------------------------------------------- /docs/documents/images/interface_add_cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/interface_add_cat.png -------------------------------------------------------------------------------- /docs/documents/images/interface_run.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/interface_run.png -------------------------------------------------------------------------------- /docs/documents/images/interface_run_valid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/interface_run_valid.png -------------------------------------------------------------------------------- /docs/documents/images/intro_page_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/intro_page_1.png -------------------------------------------------------------------------------- /docs/documents/images/intro_page_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/intro_page_2.png -------------------------------------------------------------------------------- /docs/documents/images/intro_page_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/intro_page_3.png -------------------------------------------------------------------------------- /docs/documents/images/jd.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/jd.jpeg -------------------------------------------------------------------------------- /docs/documents/images/jsonSchema.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/jsonSchema.png -------------------------------------------------------------------------------- /docs/documents/images/kuaishou.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/kuaishou.jpeg -------------------------------------------------------------------------------- /docs/documents/images/lianjia.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/lianjia.jpeg -------------------------------------------------------------------------------- /docs/documents/images/logo_header22@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/logo_header22@2x.png -------------------------------------------------------------------------------- /docs/documents/images/logo_header@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/logo_header@2x.png -------------------------------------------------------------------------------- /docs/documents/images/mock-strice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/mock-strice.png -------------------------------------------------------------------------------- /docs/documents/images/mock-strice2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/mock-strice2.png -------------------------------------------------------------------------------- /docs/documents/images/mock-strice3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/mock-strice3.png -------------------------------------------------------------------------------- /docs/documents/images/mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/mock.jpg -------------------------------------------------------------------------------- /docs/documents/images/project-remove.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/project-remove.png -------------------------------------------------------------------------------- /docs/documents/images/project_add_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/project_add_view.png -------------------------------------------------------------------------------- /docs/documents/images/project_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/project_group.png -------------------------------------------------------------------------------- /docs/documents/images/project_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/project_list.png -------------------------------------------------------------------------------- /docs/documents/images/requestSet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/requestSet.png -------------------------------------------------------------------------------- /docs/documents/images/schema-mock-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/schema-mock-1.png -------------------------------------------------------------------------------- /docs/documents/images/schema-mock-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/schema-mock-2.png -------------------------------------------------------------------------------- /docs/documents/images/shouqian.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/shouqian.png -------------------------------------------------------------------------------- /docs/documents/images/show.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/show.jpeg -------------------------------------------------------------------------------- /docs/documents/images/usage/add-group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/add-group.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case1.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case3.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case4-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case4-new.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case4.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case5-new.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case5-new.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock-case5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock-case5.png -------------------------------------------------------------------------------- /docs/documents/images/usage/adv-mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/adv-mock.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/api_add_btn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/api_add_btn.png -------------------------------------------------------------------------------- /docs/documents/images/usage/api_add_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/api_add_panel.png -------------------------------------------------------------------------------- /docs/documents/images/usage/api_res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/api_res.png -------------------------------------------------------------------------------- /docs/documents/images/usage/case-edit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case-edit.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/case-list.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case-list.gif -------------------------------------------------------------------------------- /docs/documents/images/usage/case-list.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case-list.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/case_key_body_json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case_key_body_json.png -------------------------------------------------------------------------------- /docs/documents/images/usage/case_key_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case_key_list.png -------------------------------------------------------------------------------- /docs/documents/images/usage/case_key_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case_key_query.png -------------------------------------------------------------------------------- /docs/documents/images/usage/case_key_res.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case_key_res.png -------------------------------------------------------------------------------- /docs/documents/images/usage/case_key_res_query.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/case_key_res_query.png -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-1.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-2.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-3.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-4.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-5.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-5.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/chrome-6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/chrome-6.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/hover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/hover.png -------------------------------------------------------------------------------- /docs/documents/images/usage/index.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/index.png -------------------------------------------------------------------------------- /docs/documents/images/usage/json-schema-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/json-schema-demo.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/json-schema-mock.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/json-schema-mock.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/login.png -------------------------------------------------------------------------------- /docs/documents/images/usage/manage_ask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/manage_ask.png -------------------------------------------------------------------------------- /docs/documents/images/usage/manage_ask_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/manage_ask_group.png -------------------------------------------------------------------------------- /docs/documents/images/usage/manage_find_manager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/manage_find_manager.png -------------------------------------------------------------------------------- /docs/documents/images/usage/manage_find_project_owner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/manage_find_project_owner.png -------------------------------------------------------------------------------- /docs/documents/images/usage/manage_intro_group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/manage_intro_group.png -------------------------------------------------------------------------------- /docs/documents/images/usage/mock-demo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/mock-demo.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/modal-postman-tips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/modal-postman-tips.png -------------------------------------------------------------------------------- /docs/documents/images/usage/modal-postman.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/modal-postman.gif -------------------------------------------------------------------------------- /docs/documents/images/usage/postman-1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/postman-1.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/postman-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/postman-2.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/postman-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/postman-3.jpg -------------------------------------------------------------------------------- /docs/documents/images/usage/project-message.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project-message.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project.png -------------------------------------------------------------------------------- /docs/documents/images/usage/projectCopy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/projectCopy.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_add.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_add.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_add_panel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_add_panel.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_copy_ok.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_copy_ok.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_setting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_setting.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_setting_env.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_setting_env.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_setting_global.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_setting_global.png -------------------------------------------------------------------------------- /docs/documents/images/usage/project_setting_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/project_setting_logo.png -------------------------------------------------------------------------------- /docs/documents/images/usage/user.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/usage/user.png -------------------------------------------------------------------------------- /docs/documents/images/vip.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/vip.jpeg -------------------------------------------------------------------------------- /docs/documents/images/xuetangx.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/xuetangx.jpg -------------------------------------------------------------------------------- /docs/documents/images/ykit.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/ykit.jpg -------------------------------------------------------------------------------- /docs/documents/images/yonyou.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/images/yonyou.jpg -------------------------------------------------------------------------------- /docs/documents/import-case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/import-case.png -------------------------------------------------------------------------------- /docs/documents/import-json-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/import-json-data.png -------------------------------------------------------------------------------- /docs/documents/index.md: -------------------------------------------------------------------------------- 1 | # YApi 2 | 在开始使用 YApi 之前,我们先来熟悉一下 YApi 的网站结构,这将让你快速了解YApi。 3 | 4 | ## 登录与注册 5 | 想要使用 YApi ,首先要注册账号。 6 | 7 | 8 | 9 | ## 首页 10 | 11 | 登录后进入首页,首页展示了分组与项目。 12 | 13 | 此时你作为新用户,没有任何分组与项目的权限,因此只能搜索、浏览 “公开项目” 的接口,如果在首页找不到任何项目,请联系管理员将你加入对应项目。 14 | 15 | 1首页头部展示了当前所在的位置、搜索框、新建项目、查看文档和用户信息。 16 | 17 | 2首页左侧展示分组信息,“分组”是“项目”的集合,只有超级管理员可以管理分组。 18 | 19 | 3首页右侧是分组下的项目和成员列表,点击左侧的某个分组,右侧会出现该分组下的项目和成员信息。 20 | 21 | 4点击项目右上角的星星即可关注项目,关注的项目可以在“我的关注”页面查看。 22 | 23 | 24 | 25 | ## 项目页 26 | 27 | 点击一个项目,进入项目页,项目页展示了属于该项目的全部接口,并提供项目、接口的全部操作。 28 | 29 | 此时你作为新用户,只能浏览接口信息,不可以编辑项目或接口,如果需要编辑,请联系管理员将你加入该项目。 30 | 31 | 1项目页左侧的 “接口列表” 展示了该项目下的所有接口,右侧默认显示该项目下所有接口的列表。 32 | 33 | 2点击左侧的某个接口,右侧会出现“预览”、“编辑”和“运行”。 34 | 35 | 3点击左侧的 “测试集合” 使用[测试集](./case.md)功能。 36 | 37 | 4点击二级导航的“设置”,项目组长即可编辑项目信息和管理成员列表。 38 | 39 | 5点击二级导航的“动态”,即可查看项目的操作日志。 40 | 41 | 42 | 43 | ## 个人中心 44 | 鼠标移动到右上角的用户头像或用户名上,即可点击“个人中心”查看个人信息。 45 | 46 | 47 | 48 | 在个人信息页面可以查看并修改自己的用户名、密码等信息。 49 | 50 | 51 | -------------------------------------------------------------------------------- /docs/documents/manage.md: -------------------------------------------------------------------------------- 1 | # 权限权利 2 | 3 | 接口管理的逻辑较为复杂,操作频率高,层层审批将严重拖慢生产效率,因此传统的金字塔管理模式并不适用。 4 | 5 | YApi 将扁平化管理模式的思想引入到产品的权限管理中,`超级管理员` 拥有最高的权限,并将权限分配给若干 `组长`,`超级管理员` 只需管理`组长` 即可,实际上管理YAPI各大分组与项目的是“`组长`”。`组长`对分组或项目负责,一般由BU负责人/项目负责人担任。 6 | 7 | ## 认识组长 8 | 9 | 组长分为 `分组组长` 与 `项目组长`,他们的关系就是 一个分组内有若干 `分组组长` ,这些 `分组组长` 在创建项目时就可以指定 `项目组长`。因此他们在职责上的区别就在于 `分组组长` 对分组负责 `项目组长` 对项目负责,二者其他具体区别如下: 10 | 11 | 12 | 13 | `分组组长` 的权限包括修改分组、删除分组、创建分组下的项目。一般来说,`分组组长` 只需要对项目负责,将项目的操作任务安排给 `项目组长` 处理即可。 14 | 15 | `项目组长` 只属于某一个项目因此它无法操作项目所属分组,但拥有项目的全部权限,`项目组长` 是 YApi 的基层管理者,承担了 YApi 绝大部分的日常管理工作。 16 | 17 | ## 创建分组 18 | 只有 `超级管理员` 有权限创建分组 19 | 20 | 21 | 22 | ## 创建项目 23 | 成为 `分组成员`,即可在分组中创建项目。 24 | 25 | 26 | 27 | > 想成为 `分组组长` ,在分组成员列表中找到 `分组组长`,联系 `分组组长` 将你设置为分组组长。 28 | 29 | [怎样联系组长?](./qa.md#q1-怎样联系组长?) 30 | 31 | ## 权限列表 32 | 33 | 新用户未加入项目或分组时,我们称为 `“游客”`。 34 | 35 | ### 项目权限 36 | 37 | 38 | | 操作 | 游客 | 项目开发者 | 项目组长 | 超级管理员 | 39 | | :-------------- | :------------: | :------------: | :------------: | :------------: | 40 | | 浏览公开项目与接口 | ✓ | ✓ | ✓ | ✓ | 41 | | 浏览私有项目与接口 | | ✓ | ✓ | ✓ | 42 | | 编辑项目信息 | | ✓ | ✓ | ✓ | 43 | | 新建接口 | | ✓ | ✓ | ✓ | 44 | | 编辑接口 | | ✓ | ✓ | ✓ | 45 | | 编辑项目头像 | | | ✓ | ✓ | 46 | | 删除项目 | | | ✓ | ✓ | 47 | 48 | 49 | ### 分组权限 50 | 51 | | 操作 | 游客 | 分组开发者 | 分组组长 | 超级管理员 | 52 | | :-------------- | :------------: | :------------: | :------------: | :------------: | 53 | | 浏览分组 | ✓ | ✓ | ✓ | ✓ | 54 | | 在分组中新建项目 | | ✓ | ✓ | ✓ | 55 | | 编辑分组信息 | | | ✓ | ✓ | 56 | | 管理分组成员 | | | ✓ | ✓ | 57 | | 删除分组 | | | ✓ | ✓ | 58 | -------------------------------------------------------------------------------- /docs/documents/plugin-dev.md: -------------------------------------------------------------------------------- 1 | ## 运行开发服务器 2 | ```bash 3 | npm install 4 | npm install -g ykit #依赖 ykit 5 | npm run dev #启动开发服务器 6 | ``` 7 | 8 | ## 加载插件 9 | 在config.json plugins配置项,加入 demo 插件, 10 | ```json 11 | { 12 | "port": "3000", 13 | "db": { 14 | "servername": "127.0.0.1", 15 | "DATABASE": "yapi" 16 | }, 17 | ... 18 | "plugins": [{ 19 | "name": "demo", 20 | "options": {} 21 | }] 22 | } 23 | ``` 24 | 25 | ## 初始化目录 26 | 27 | 可参考 项目vendors/exts 目录下的插件 28 | 29 | 在 vendors/node_modules 下新建 yapi-plugin-demo 目录和 npm init,最后生成的目录结构如下 30 | ``` 31 | yapi-plugin-demo 32 | client.js //客户端入口文件 33 | server.js //服务端入口文件 34 | packjson.json //插件依赖管理 35 | index.js //插件配置文件 36 | ``` 37 | 38 | ## index.js 配置说明 39 | ``` 40 | server: true // 如果为true,表名该插件需要经过后端服务器加载 41 | client: true // 如果为true,表名该插件需要经过前端编译 42 | ``` 43 | 44 | ## server.js 45 | 在server.js 需要导出一个 function ,例如: module.exports = function(options){} 46 | 47 | options 可在 config.json 配置 48 | ### 绑定钩子 49 | ``` 50 | this.bindHook(hookname, listener) //绑定钩子 51 | hookname //钩子名 52 | listener //监听函数,可以是普通函数,也可以是 asyncFunction 53 | ``` 54 | 55 | ### 如何使用 YApi vendors/server 目录下的模块 56 | 可以直接 require vendors 目录下的模块,注意:后端 node 不能使用 import关键字,只能使用 require 57 | 例如: require('yapi') 58 | 59 | 60 | 61 | ### controller 和 model 62 | 新增 controller 需要继承 baseController(controller/base.js) 63 | 64 | 新增 model 需要继承 baseModel(model/base.js) 65 | 66 | ## client.js 67 | ### 绑定钩子(同后端 server.js ) 68 | ``` 69 | this.bindHook(hookname, listener) //绑定钩子 70 | hookname //钩子名 71 | listener //监听函数,可以是普通函数,也可以是 asyncFunction 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/documents/plugin-index.md: -------------------------------------------------------------------------------- 1 | ## 安装 2 | 假设插件名为:yapi-plugin-demo,安装方法如下: 3 | ``` 4 | cd {项目目录} 5 | yapi plugin yapi-plugin-demo 6 | ``` 7 | 8 | ## 卸载插件 9 | 假设插件名为:yapi-plugin-demo,卸载方法如下: 10 | ``` 11 | cd {项目目录} 12 | yapi unplugin yapi-plugin-demo 13 | ``` -------------------------------------------------------------------------------- /docs/documents/plugin-list.md: -------------------------------------------------------------------------------- 1 | ## 怎么分享我的插件? 2 | Fork [yapi-pro](https://github.com/yapi-pro/yapi), 然后修改 docs/documents/plugin-list.md, 修改完成后请 Pull-Request. 3 | 4 | ## 插件列表 5 | 6 | * [dingding](https://github.com/zgs225/yapi-plugin-dding) 钉钉机器人推送插件 7 | * [qsso](https://github.com/ymfe/yapi-plugin-qsso) sso 第三方登录 8 | * [import-rap](https://github.com/wxxcarl/yapi-plugin-import-rap) 从rap中导入项目 9 | * [export-docx-data](https://github.com/inceptiongt/Yapi-plugin-export-docx-data) 数据导出docx文档 10 | * [import-swagger-customize](https://github.com/follow-my-heart/yapi-plugin-import-swagger-customize) 导入指定swagger接口 -------------------------------------------------------------------------------- /docs/documents/qa.md: -------------------------------------------------------------------------------- 1 | # 常见问题解答 2 | 3 | 本页面罗列了大家使用 YApi 时遇到的常见问题. 4 | 5 | 如果没有找到您要的答案,请联系管理员. 6 | 7 | ## Q1 怎样联系组长? 8 | 9 | 组长分为 `分组组长` 和 `项目组长`: 10 | - 分组组长:选择首页左侧的分组,点击右侧面板的 `成员列表`,成员右侧显示着 `组长/开发者` 的权限信息。 11 | 12 | - 项目组长: 点击项目页的 `设置` - `成员列表`,成员右侧显示着 `组长/开发者` 的权限信息。 13 | 14 | 15 | ## Q2 怎么快速迁移旧项目? 16 | 17 | 第一步. 使用 Chrome 浏览器开发者工具录制功能 18 | 19 | 第二步 录制当前项目所有请求,导出到 har 文件 20 | 21 | 第三步 将Har数据导入到 YApi 平台 22 | 23 | 具体使用方法请参考 YApi 文档 24 | 25 | ## Q3 忘记密码怎么办? 26 | 27 | 请联系 `超级管理员` ,只有超级管理员能重置密码。 28 | 29 | ## Q4 发现了 Bug 怎么办? 30 | 31 | 请反馈到 Github,功能性的问题我们会在一周内修复,并在每周一发布新的版本 Tag. 32 | 33 | ## Q5 部署不成功怎么办? 34 | 35 | 1. 确保 node 版本=> 7.6,请运行 node -v 查看版本号 36 | 2. 确保 mongodb 版本 => 2.6,请运行 mongo --version 查看版本号 37 | 3. 确保安装了 npm, 运行 npm -v 查看版本号 38 | 4. 确保安装了 git,运行 git --version 查看版本号 39 | 40 | 确认版本号没问题,请删除原有的安装文件和数据库,重新安装。 41 | 如果还是无法安装,请不要选择最新的版本,可选择上一个版本或上上一个版本等,最新版本出问题的概率会比较大。 42 | 43 | ## Q6 部署YApi遇到mongodb认证问题? 44 | 45 | * mongodb3.03以上开启认证,解决程序认证连接报错以及第三方客户端无法认证问题 46 | -------------------------------------------------------------------------------- /docs/documents/quickstart.md: -------------------------------------------------------------------------------- 1 | # 如何创建接口? 2 | 3 | 把大象装进冰箱分几步?三步:把冰箱门打开,把大象装进去,关门,搞定~ 4 | 5 | 新建接口分几步?也是三步: 6 | 7 | * [获取权限](#获取权限) 8 | * [找到一个项目](#选择项目) 9 | * [新建接口](#新建接口) 10 | 11 | 搞定~ 12 | 13 | 14 | ## 获取权限 15 | 16 | 新用户登录拥有 `个人空间` 分组下的全部权限,个人空间分组仅自己可见,因此可以在这里任意试用 YApi 的功能。 17 | 18 | 除此以外没有任何项目或分组的权限,只能浏览已存在分组下面的公开项目。 19 | 20 | 如果找不到想找的项目,可能是尚未成为项目成员,此时应联系 `项目组长` 将你加入该项目。 21 | 22 | [怎样联系组长?](./qa.md#Q__怎样联系组长?) 23 | 24 | 25 | 26 | - 想创建分组,请看: [创建分组](./manage.md#创建分组) 27 | - 想创建项目,请看: [创建项目](./manage.md#创建项目) 28 | 29 | > 想了解更多权限信息,请查看[权限列表](./manage.md#权限列表) 30 | 31 | ## 选择项目 32 | 33 | - 如果你已经登录,会在首页右侧看到一些项目 (可以在左侧的分组列表切换分组来查看不同分组下的项目)。 34 | 35 | - 点击一个项目,进入该项目的详情页。 36 | 37 | 图片名称 38 | 39 | ## 新建接口 40 | 41 | - 点击左侧接口分组右侧的菜单按钮,选择 `添加接口`,或者点击接口列表右上角的 `添加接口`。 42 | 43 | 44 | 45 | - 选择接口分类,输入接口名称和接口路径,点击 `提交`。 46 | 47 | 48 | 49 | - 恭喜你!创建了第一个YApi的接口,你可以看到在左侧看到接口名称,右侧有该接口的信息预览。 50 | 51 | 52 | -------------------------------------------------------------------------------- /docs/documents/redev.md: -------------------------------------------------------------------------------- 1 | ## 安装YApi 2 | 3 | 1.创建工程目录 4 | 5 | ```bash 6 | mkdir yapi && cd yapi 7 | git clone https://github.com/yapi-pro/yapi.git vendors --depth=1 # 或者下载 zip 包解压到 vendors 目录 8 | ``` 9 | 10 | 2.修改配置 11 | 12 | ```bash 13 | cp vendors/config_example.json ./config.json # 复制完成后请修改相关配置 14 | vi ./config.json 15 | ``` 16 | 17 | 配置如下,主要配置 MongoDB 数据库,以及 Admin 账号。 18 | 19 | ```json 20 | { 21 | "port": "3011", 22 | "adminAccount": "admin@admin.com", 23 | "db": { 24 | "servername": "127.0.0.1", 25 | "DATABASE": "yapi", 26 | "port": 27017, 27 | "user": "yapi", 28 | "pass": "yapi123" 29 | }, 30 | "mail": { 31 | "enable": true, 32 | "host": "smtp.163.com", 33 | "port": 465, 34 | "from": "***@163.com", 35 | "auth": { 36 | "user": "***@163.com", 37 | "pass": "*****" 38 | } 39 | } 40 | } 41 | ``` 42 | > db.user 和 db.pass 是 mongodb 的用户名和密码,如果没有开启 mongo 认证功能,请删除这两个选项。 43 | 44 | 3.安装依赖 45 | 46 | ```bash 47 | cd vendors 48 | npm install --registry https://registry.npm.taobao.org # 安装依赖 49 | ``` 50 | 51 | 4.初始化 52 | 53 | ```bash 54 | npm run install-server # 安装程序会初始化数据库索引和管理员账号,管理员账号名可在 config.json 配置 55 | # 默认输出 56 | # 初始化管理员账号成功,账号名:"admin@admin.com",密码:"yapi.pro" 57 | ``` 58 | 59 | 5.启动开发机 60 | 61 | ```bash 62 | npm run dev 63 | # 启动服务器后,请访问 127.0.0.1:{config.json配置的端口},初次运行会有个编译的过程,请耐心等候 64 | # 127.0.0.1:3011 65 | ``` 66 | 67 | 目录结构 68 | 69 | ``` 70 | |-- config.json 71 | |-- init.lock 72 | |-- log 73 | `-- vendors 74 | |-- CHANGELOG.md 75 | |-- LICENSE 76 | |-- README.md 77 | |-- client 78 | |-- common 79 | |-- config_example.json 80 | |-- doc 81 | |-- exts 82 | |-- nodemon.json 83 | |-- npm-debug.log 84 | |-- package.json 85 | |-- plugin.json 86 | |-- server 87 | |-- static 88 | |-- test 89 | |-- webpack.alias.js 90 | |-- yapi-base-flow.jpg 91 | |-- ydocfile.js 92 | `-- ykit.config.js 93 | ``` 94 | 95 | 96 | 97 | ## 技术栈说明 98 | 99 | 后端: koa mongoose 100 | 101 | 前端: react redux 102 | 103 | ## 启动开发环境服务器 104 | 105 | ```bash 106 | cd vendors 107 | npm run dev 108 | # 启动服务器后,请访问 127.0.0.1:{config.json配置的端口},初次运行会有个编译的过程,请耐心等候 109 | ``` 110 | 111 | ## 启动生产环境服务器 112 | 113 | ```bash 114 | cd vendors 115 | ykit pack -m 116 | node server/app.js 117 | ``` -------------------------------------------------------------------------------- /docs/documents/test-case.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/docs/documents/test-case.png -------------------------------------------------------------------------------- /docs/index.jsx: -------------------------------------------------------------------------------- 1 | --- 2 | banner: 3 | name: 'YApi' 4 | desc: '旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API' 5 | btns: 6 | - { name: '开始', href: './documents/index.html', primary: true } 7 | - { name: 'Github >', href: 'https://github.com/yapi-pro/yapi' } 8 | features: 9 | - { name: '权限管理', desc: 'YApi 成熟的团队管理扁平化项目权限配置满足各类企业的需求' } 10 | - { name: '可视化接口管理', desc: '基于 websocket 的多人协作接口编辑功能和类 postman 测试工具,让多人协作成倍提升开发效率' } 11 | - { name: 'Mock Server', desc: '易用的 Mock Server,再也不用担心 mock 数据的生成了' } 12 | - { name: '自动化测试', desc: '完善的接口自动化测试,保证数据的正确性' } 13 | - { name: '数据导入', desc: '支持导入 swagger, postman, har 数据格式,方便迁移旧项目'} 14 | - { name: '插件机制', desc: '强大的插件机制,满足各类业务需求'} 15 | 16 | footer: 17 | copyRight: 18 | name: 'YMFE Team' 19 | href: 'https://ymfe.org/' 20 | links: 21 | 团队网址: 22 | - { name: 'YMFE', href: 'https://ymfe.org/' } 23 | - { name: 'YMFE Blog', href: 'https://blog.ymfe.org/' } 24 | Git仓库: 25 | - { name: 'Github', href: 'https://github.com/yapi-pro/yapi' } 26 | - { name: 'Github Issue', href: 'https://github.com/yapi-pro/yapi/issues' } 27 | 28 | --- 29 | { 30 | (function(){ 31 | banner.caption = '当前版本: v' + props.config.version 32 | return
33 | 34 |
35 |
36 | })() 37 | } 38 | -------------------------------------------------------------------------------- /docs/openapi.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/web.css: -------------------------------------------------------------------------------- 1 | img{ 2 | margin:16px 3 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/MockCol/CaseDesModal.scss: -------------------------------------------------------------------------------- 1 | .case-des-modal { 2 | .ant-modal-body { 3 | max-height: 520px; 4 | overflow-y: scroll; 5 | } 6 | .ip-filter .ip>.ant-form-item, .ip-filter .ip-switch>.ant-form-item { 7 | margin-bottom: 0; 8 | } 9 | .headers>.ant-form-item, .paramsArr>.ant-form-item, .params-form>.ant-form-item { 10 | margin-bottom: 0; 11 | } 12 | .sub-title { 13 | clear: both; 14 | font-weight: normal; 15 | margin-top: .48rem; 16 | margin-bottom: .16rem; 17 | border-left: 3px solid #2395f1; 18 | padding-left: 8px; 19 | } 20 | 21 | .pretty-editor{ 22 | min-height: 300px; 23 | border: 1px solid #d9d9d9; 24 | border-radius: 4px; 25 | } 26 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/MockCol/mockColReducer.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | import { message } from 'antd' 4 | 5 | // Actions 6 | const FETCH_MOCK_COL = 'yapi/mockCol/FETCH_MOCK_COL'; 7 | 8 | // Reducer 9 | const initialState = { 10 | list: [] 11 | } 12 | 13 | export default (state = initialState, action) => { 14 | switch (action.type) { 15 | case FETCH_MOCK_COL: 16 | return { 17 | ...state, 18 | list: action.payload.data 19 | } 20 | default: 21 | return state 22 | } 23 | } 24 | 25 | // Action Creators 26 | export async function fetchMockCol(interfaceId) { 27 | let result = await axios.get('/api/plugin/advmock/case/list?interface_id=' + interfaceId); 28 | if(result.errcode !==0 ){ 29 | message.error(result.errmsg); 30 | 31 | } 32 | return { 33 | type: FETCH_MOCK_COL, 34 | payload: result.data 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/advMockModel.js: -------------------------------------------------------------------------------- 1 | const yapi = require('yapi.js'); 2 | const baseModel = require('models/base.js'); 3 | 4 | class advMockModel extends baseModel { 5 | getName() { 6 | return 'adv_mock'; 7 | } 8 | 9 | getSchema() { 10 | return { 11 | interface_id: { type: Number, required: true }, 12 | project_id: {type: Number, required: true}, 13 | enable: {type: Boolean, default: false}, 14 | mock_script: String, 15 | uid: String, 16 | up_time: Number 17 | }; 18 | } 19 | 20 | get(interface_id) { 21 | 22 | return this.model.findOne({ 23 | interface_id: interface_id 24 | }); 25 | } 26 | 27 | delByInterfaceId(interface_id) { 28 | return this.model.remove({ 29 | interface_id: interface_id 30 | }); 31 | } 32 | 33 | delByProjectId(project_id){ 34 | return this.model.remove({ 35 | project_id: project_id 36 | }) 37 | } 38 | 39 | save(data) { 40 | data.up_time = yapi.commons.time(); 41 | let m = new this.model(data); 42 | return m.save(); 43 | } 44 | 45 | up(data) { 46 | data.up_time = yapi.commons.time(); 47 | return this.model.update({ 48 | interface_id: data.interface_id 49 | }, { 50 | uid: data.uid, 51 | up_time: data.up_time, 52 | mock_script: data.mock_script, 53 | enable: data.enable 54 | }, { 55 | upsert: true 56 | }) 57 | } 58 | 59 | } 60 | 61 | module.exports = advMockModel; -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/caseModel.js: -------------------------------------------------------------------------------- 1 | const yapi = require('yapi.js'); 2 | const baseModel = require('models/base.js'); 3 | const mongoose = require('mongoose'); 4 | 5 | class caseModel extends baseModel { 6 | getName() { 7 | return 'adv_mock_case'; 8 | } 9 | 10 | getSchema() { 11 | return { 12 | interface_id: { type: Number, required: true }, 13 | project_id: {type: Number, required: true}, 14 | ip: {type: String}, 15 | ip_enable: {type: Boolean, default: false}, 16 | name: {type: String, required: true}, 17 | code: {type: Number, default: 200}, 18 | delay: {type: Number, default: 0}, 19 | headers: [{ 20 | name: {type: String, required: true}, 21 | value: {type: String} 22 | }], 23 | params: mongoose.Schema.Types.Mixed, 24 | uid: String, 25 | up_time: Number, 26 | res_body: {type: String, required: true}, 27 | case_enable: {type: Boolean, default: true} 28 | }; 29 | } 30 | 31 | get(data) { 32 | return this.model.findOne(data); 33 | } 34 | 35 | list(id){ 36 | return this.model.find({ 37 | interface_id: id 38 | }) 39 | } 40 | 41 | delByInterfaceId(interface_id) { 42 | return this.model.remove({ 43 | interface_id: interface_id 44 | }); 45 | } 46 | 47 | delByProjectId(project_id){ 48 | return this.model.remove({ 49 | project_id: project_id 50 | }) 51 | } 52 | 53 | save(data) { 54 | data.up_time = yapi.commons.time(); 55 | let m = new this.model(data); 56 | return m.save(); 57 | } 58 | 59 | up(data) { 60 | let id = data.id; 61 | delete data.id; 62 | data.up_time = yapi.commons.time(); 63 | return this.model.update({ 64 | _id: id 65 | }, data) 66 | } 67 | 68 | del(id){ 69 | return this.model.remove({ 70 | _id: id 71 | }) 72 | } 73 | 74 | } 75 | 76 | module.exports = caseModel; -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/client.js: -------------------------------------------------------------------------------- 1 | import AdvMock from './AdvMock' 2 | import mockCol from './MockCol/mockColReducer.js' 3 | 4 | module.exports = function(){ 5 | this.bindHook('interface_tab', function(tabs){ 6 | tabs.advMock = { 7 | name: '高级Mock', 8 | component: AdvMock 9 | } 10 | }) 11 | this.bindHook('add_reducer', function(reducerModules){ 12 | reducerModules.mockCol = mockCol; 13 | }) 14 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-advanced-mock/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true, 4 | httpCodes: [ 5 | 100,101,102,200,201,202,203,204,205,206,207,208,226,300,301,302,303,304,305,307,308,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,422,423,424,426,428,429,431,500,501,502,503,504,505,506,507,508,510,511 6 | ] 7 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/.sass-cache/3f3d6afdb6793d0137c849fff0c1eee600369f02/defaultTheme.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/exts/yapi-plugin-export-data/.sass-cache/3f3d6afdb6793d0137c849fff0c1eee600369f02/defaultTheme.scssc -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/.sass-cache/8d7dcd37f61732054a9966925c8cce5b23a209ac/defaultTheme.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/exts/yapi-plugin-export-data/.sass-cache/8d7dcd37f61732054a9966925c8cce5b23a209ac/defaultTheme.scssc -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/client.js: -------------------------------------------------------------------------------- 1 | // import {message} from 'antd' 2 | 3 | function exportData(exportDataModule, pid) { 4 | exportDataModule.html = { 5 | name: 'html', 6 | route: `/api/plugin/export?type=html&pid=${pid}`, 7 | desc: '导出项目接口文档为 html 文件' 8 | }; 9 | (exportDataModule.markdown = { 10 | name: 'markdown', 11 | route: `/api/plugin/export?type=markdown&pid=${pid}`, 12 | desc: '导出项目接口文档为 markdown 文件' 13 | }), 14 | (exportDataModule.json = { 15 | name: 'json', 16 | route: `/api/plugin/export?type=json&pid=${pid}`, 17 | desc: '导出项目接口文档为 json 文件,可使用该文件导入接口数据' 18 | }); 19 | // exportDataModule.pdf = { 20 | // name: 'pdf', 21 | // route: `/api/plugin/export?type=pdf&pid=${pid}`, 22 | // desc: '导出项目接口文档为 pdf 文件' 23 | // } 24 | } 25 | 26 | module.exports = function() { 27 | this.bindHook('export_data', exportData); 28 | }; 29 | -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/defaultTheme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const sysPath = require('path'); 3 | const css = fs.readFileSync(sysPath.join(__dirname, './defaultTheme.css')); 4 | module.exports = ''; -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-export-data/server.js: -------------------------------------------------------------------------------- 1 | const controller = require('./controller'); 2 | 3 | // const mongoose = require('mongoose'); 4 | // const _ = require('underscore'); 5 | 6 | module.exports = function(){ 7 | this.bindHook('add_router', function(addRouter){ 8 | addRouter({ 9 | controller: controller, 10 | method: 'get', 11 | path: 'export', 12 | action: 'exportData' 13 | }) 14 | }) 15 | 16 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-export-swagger2-data/client.js: -------------------------------------------------------------------------------- 1 | function exportData(exportDataModule, pid) { 2 | exportDataModule.swaggerjson = { 3 | name: 'swaggerjson', 4 | route: `/api/plugin/exportSwagger?type=OpenAPIV2&pid=${pid}`, 5 | desc: '导出项目接口文档为(Swagger 2.0)Json文件' 6 | }; 7 | } 8 | 9 | module.exports = function() { 10 | this.bindHook('export_data', exportData); 11 | }; -------------------------------------------------------------------------------- /exts/yapi-plugin-export-swagger2-data/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-export-swagger2-data/server.js: -------------------------------------------------------------------------------- 1 | const exportSwaggerController = require('./controller'); 2 | 3 | module.exports = function(){ 4 | this.bindHook('add_router', function(addRouter){ 5 | addRouter({ 6 | controller: exportSwaggerController, 7 | method: 'get', 8 | path: 'exportSwagger', 9 | action: 'exportData' 10 | }) 11 | }) 12 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/.sass-cache/3f3d6afdb6793d0137c849fff0c1eee600369f02/defaultTheme.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/exts/yapi-plugin-gen-services/.sass-cache/3f3d6afdb6793d0137c849fff0c1eee600369f02/defaultTheme.scssc -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/.sass-cache/8d7dcd37f61732054a9966925c8cce5b23a209ac/defaultTheme.scssc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/exts/yapi-plugin-gen-services/.sass-cache/8d7dcd37f61732054a9966925c8cce5b23a209ac/defaultTheme.scssc -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/Services/Services.js: -------------------------------------------------------------------------------- 1 | import React, { PureComponent as Component } from 'react' 2 | import PropTypes from 'prop-types' 3 | import { connect } from 'react-redux'; 4 | import { getToken } from '../../../client/reducer/modules/project.js' 5 | 6 | 7 | import './Services.scss'; 8 | 9 | @connect( 10 | state => { 11 | return { 12 | token: state.project.token 13 | } 14 | }, 15 | { 16 | getToken 17 | } 18 | ) 19 | export default class Services extends Component { 20 | static propTypes = { 21 | projectId: PropTypes.string, 22 | token: PropTypes.string, 23 | getToken: PropTypes.func 24 | } 25 | 26 | async componentDidMount() { 27 | const id = this.props.projectId; 28 | await this.props.getToken(id); 29 | 30 | } 31 | render () { 32 | const id = this.props.projectId; 33 | return ( 34 |
35 |
36 |
37 |
安装工具
38 |
{`
39 |   npm i sm2tsservice -D
40 |   `}
41 |
配置【3.2.0及以上版本】
42 |
{`
43 |   touch json2service.json
44 |   `}
45 |
{`
46 |   {
47 |     "url": "yapi-swagger.json",
48 |     "remoteUrl": "${location.protocol}//${location.hostname}${location.port ? `:${location.port}` : ''}/api/open/plugin/export-full?type=json&pid=${id}&status=all&token=${this.props.token}",
49 |     "type": "yapi",
50 |     "swaggerParser": {}
51 |   }
52 |   `}
53 |             
54 |
配置【3.2.0以下版本】
55 |
{`
56 |   touch json2service.json
57 |   `}
58 |
{`
59 |   {
60 |     "url": "${location.protocol}//${location.hostname}${location.port ? `:${location.port}` : ''}/api/open/plugin/export-full?type=json&pid=${id}&status=all&token=${this.props.token}",
61 |     "type": "yapi",
62 |     "swaggerParser": {}
63 |   }
64 |   `}
65 |             
66 |
生成services代码
67 |
{`
68 |   (./node_modules/.bin/)sm2tsservice --clear
69 |   `}
70 |
71 | 更多说明 sm2tsservice 72 |
73 |
74 | ); 75 | } 76 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/Services/Services.scss: -------------------------------------------------------------------------------- 1 | .project-services { 2 | margin: 0; 3 | pre { 4 | background: #efefef; 5 | } 6 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/client.js: -------------------------------------------------------------------------------- 1 | import Services from './Services/Services.js'; 2 | 3 | function genServices(routers) { 4 | routers['services'] = { 5 | name: '生成 ts services', 6 | component: Services 7 | } 8 | } 9 | 10 | module.exports = function() { 11 | this.bindHook('sub_setting_nav', genServices); 12 | }; 13 | -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/defaultTheme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const sysPath = require('path'); 3 | const css = fs.readFileSync(sysPath.join(__dirname, './defaultTheme.css')); 4 | module.exports = ''; -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-gen-services/server.js: -------------------------------------------------------------------------------- 1 | const controller = require('./controller'); 2 | 3 | // const mongoose = require('mongoose'); 4 | // const _ = require('underscore'); 5 | 6 | module.exports = function(){ 7 | this.bindHook('add_router', function(addRouter){ 8 | // @feat: serives 9 | addRouter({ 10 | controller: controller, 11 | method: 'get', 12 | prefix: '/open', 13 | path: 'export-full', 14 | action: 'exportFullData' 15 | }); 16 | }) 17 | 18 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-import-har/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: false, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-import-postman/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: false, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-import-swagger/client.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | import run from './run'; 3 | 4 | module.exports = function() { 5 | this.bindHook('import_data', function(importDataModule) { 6 | if (!importDataModule || typeof importDataModule !== 'object') { 7 | console.error('importDataModule 参数Must be Object Type'); 8 | return null; 9 | } 10 | importDataModule.swagger = { 11 | name: 'Swagger', 12 | run: async function(res) { 13 | try { 14 | return await run(res); 15 | } catch (err) { 16 | console.error(err); 17 | message.error('解析失败'); 18 | } 19 | }, 20 | desc: `

Swagger数据导入( 支持 v2.0+ )

21 |

22 | 通过命令行导入接口数据 23 |

24 | ` 25 | }; 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /exts/yapi-plugin-import-swagger/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-import-swagger/server.js: -------------------------------------------------------------------------------- 1 | module.exports = function(){ 2 | this.bindHook('import_data', function(importDataModule){ 3 | importDataModule.swagger = async (res)=>{ 4 | try{ 5 | return await require('./run.js')(res) 6 | }catch(err){ 7 | this.commons.log(err, 'error') 8 | return false; 9 | } 10 | } 11 | }) 12 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-import-yapi-json/client.js: -------------------------------------------------------------------------------- 1 | import { message } from 'antd'; 2 | 3 | function importData(importDataModule) { 4 | async function run(res) { 5 | try { 6 | let interfaceData = { apis: [], cats: [] }; 7 | res = JSON.parse(res); 8 | res.forEach(item => { 9 | interfaceData.cats.push({ 10 | name: item.name, 11 | desc: item.desc 12 | }); 13 | item.list.forEach(api => { 14 | api.catname = item.name; 15 | }); 16 | interfaceData.apis = interfaceData.apis.concat(item.list); 17 | }); 18 | return interfaceData; 19 | } catch (e) { 20 | console.error(e); 21 | message.error('数据格式有误'); 22 | } 23 | } 24 | 25 | if (!importDataModule || typeof importDataModule !== 'object') { 26 | console.error('importDataModule 参数Must be Object Type'); 27 | return null; 28 | } 29 | 30 | importDataModule.json = { 31 | name: 'json', 32 | run: run, 33 | desc: 'YApi接口 json数据导入' 34 | }; 35 | } 36 | 37 | module.exports = function() { 38 | this.bindHook('import_data', importData); 39 | }; 40 | -------------------------------------------------------------------------------- /exts/yapi-plugin-import-yapi-json/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: false, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/client.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gxl.gao on 2017/10/24. 3 | */ 4 | import StatisticsPage from './statisticsClientPage/index' 5 | 6 | module.exports = function () { 7 | this.bindHook('header_menu', function (menu) { 8 | menu.statisticsPage = { 9 | path: '/statistic', 10 | name: '系统信息', 11 | icon: 'bar-chart', 12 | adminFlag: true 13 | } 14 | }) 15 | this.bindHook('app_route', function (app) { 16 | app.statisticsPage = { 17 | path: '/statistic', 18 | component: StatisticsPage 19 | } 20 | }) 21 | 22 | 23 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gxl.gao on 2017/10/24. 3 | */ 4 | module.exports = { 5 | server: true, 6 | client: true, 7 | httpCodes: [ 8 | 100,101,102,200,201,202,203,204,205,206,207,208,226,300,301,302,303,304,305,307,308,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,422,423,424,426,428,429,431,500,501,502,503,504,505,506,507,508,510,511 9 | ] 10 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/server.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gxl.gao on 2017/10/24. 3 | */ 4 | const yapi = require('yapi.js'); 5 | const mongoose = require('mongoose'); 6 | const controller = require('./controller'); 7 | const statisModel = require('./statisMockModel.js'); 8 | const commons = require('./util.js'); 9 | 10 | module.exports = function() { 11 | yapi.connect.then(function() { 12 | let Col = mongoose.connection.db.collection('statis_mock'); 13 | Col.createIndex({ 14 | interface_id: 1 15 | }); 16 | Col.createIndex({ 17 | project_id: 1 18 | }); 19 | Col.createIndex({ 20 | group_id: 1 21 | }); 22 | Col.createIndex({ 23 | time: 1 24 | }); 25 | Col.createIndex({ 26 | date: 1 27 | }); 28 | }); 29 | 30 | this.bindHook('add_router', function(addRouter) { 31 | addRouter({ 32 | controller: controller, 33 | method: 'get', 34 | path: 'statismock/count', 35 | action: 'getStatisCount' 36 | }); 37 | 38 | addRouter({ 39 | controller: controller, 40 | method: 'get', 41 | path: 'statismock/get', 42 | action: 'getMockDateList' 43 | }); 44 | addRouter({ 45 | controller: controller, 46 | method: 'get', 47 | path: 'statismock/get_system_status', 48 | action: 'getSystemStatus' 49 | }); 50 | addRouter({ 51 | controller: controller, 52 | method: 'get', 53 | path: 'statismock/group_data_statis', 54 | action: 'groupDataStatis' 55 | }); 56 | }); 57 | 58 | // MockServer生成mock数据后触发 59 | this.bindHook('mock_after', function(context) { 60 | let interfaceId = context.interfaceData._id; 61 | let projectId = context.projectData._id; 62 | let groupId = context.projectData.group_id; 63 | //let ip = context.ctx.originalUrl; 64 | let ip = yapi.commons.getIp(context.ctx); 65 | 66 | let data = { 67 | interface_id: interfaceId, 68 | project_id: projectId, 69 | group_id: groupId, 70 | time: yapi.commons.time(), 71 | ip: ip, 72 | date: commons.formatYMD(new Date()) 73 | }; 74 | let inst = yapi.getInst(statisModel); 75 | 76 | try { 77 | inst.save(data).then(); 78 | } catch (e) { 79 | yapi.commons.log('mockStatisError', e); 80 | } 81 | }); 82 | }; 83 | -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/statisMockModel.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gxl.gao on 2017/10/24. 3 | */ 4 | const yapi = require('yapi.js'); 5 | const baseModel = require('models/base.js'); 6 | 7 | class statisMockModel extends baseModel { 8 | getName() { 9 | return 'statis_mock'; 10 | } 11 | 12 | getSchema() { 13 | return { 14 | interface_id: { type: Number, required: true }, 15 | project_id: { type: Number, required: true }, 16 | group_id: { type: Number, required: true }, 17 | time: Number, //'时间戳' 18 | ip: String, 19 | date: String 20 | }; 21 | } 22 | 23 | countByGroupId(id){ 24 | return this.model.countDocuments({ 25 | group_id: id 26 | }) 27 | } 28 | 29 | save(data) { 30 | let m = new this.model(data); 31 | return m.save(); 32 | } 33 | 34 | getTotalCount() { 35 | return this.model.countDocuments({}); 36 | } 37 | 38 | async getDayCount(timeInterval) { 39 | let end = timeInterval[1]; 40 | let start = timeInterval[0]; 41 | let data = []; 42 | const cursor = this.model.aggregate([ 43 | { 44 | $match: { 45 | date: { $gt: start, $lte: end } 46 | } 47 | }, 48 | { 49 | $group: { 50 | _id: '$date', //$region is the column name in collection 51 | count: { $sum: 1 } 52 | } 53 | }, 54 | { 55 | $sort: { _id: 1 } 56 | } 57 | ]).cursor({}).exec(); 58 | await cursor.eachAsync(doc => data.push(doc)); 59 | return data; 60 | 61 | } 62 | 63 | list() { 64 | return this.model.find({}).select('date').exec(); 65 | } 66 | 67 | up(id, data) { 68 | data.up_time = yapi.commons.time(); 69 | return this.model.updateOne({ 70 | _id: id 71 | }, data, { runValidators: true }); 72 | } 73 | } 74 | 75 | module.exports = statisMockModel; -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/statisticsClientPage/StatisChart.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by gxl.gao on 2017/10/25. 3 | */ 4 | import React, { Component } from 'react'; 5 | // import PropTypes from 'prop-types' 6 | import axios from 'axios'; 7 | import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; 8 | import { Spin } from 'antd'; 9 | class StatisChart extends Component { 10 | static propTypes = {}; 11 | 12 | constructor(props) { 13 | super(props); 14 | this.state = { 15 | showLoading: true, 16 | chartDate: { 17 | mockCount: 0, 18 | mockDateList: [] 19 | } 20 | }; 21 | } 22 | 23 | UNSAFE_componentWillMount() { 24 | this.getMockData(); 25 | } 26 | 27 | // 获取mock 请求次数信息 28 | async getMockData() { 29 | let result = await axios.get('/api/plugin/statismock/get'); 30 | if (result.data.errcode === 0) { 31 | let mockStatisData = result.data.data; 32 | this.setState({ 33 | showLoading: false, 34 | chartDate: { ...mockStatisData } 35 | }); 36 | } 37 | } 38 | 39 | render() { 40 | const width = 1050; 41 | const { mockCount, mockDateList } = this.state.chartDate; 42 | 43 | return ( 44 |
45 | 46 |
47 |

mock 接口访问总数为:{mockCount.toLocaleString()}

48 |
49 | 55 | 56 | 57 | 58 | 59 | 60 | 67 | 68 |
69 |
过去3个月mock接口调用情况
70 |
71 |
72 |
73 | ); 74 | } 75 | } 76 | 77 | export default StatisChart; 78 | -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/statisticsClientPage/StatisTable.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Table } from 'antd'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const columns = [ 6 | { 7 | title: 'Group', 8 | dataIndex: 'name', 9 | key: 'name' 10 | }, 11 | { 12 | title: '项目', 13 | dataIndex: 'project', 14 | key: 'project' 15 | }, 16 | { 17 | title: '接口', 18 | dataIndex: 'interface', 19 | key: 'interface' 20 | }, 21 | { 22 | title: 'mock数据', 23 | dataIndex: 'mock', 24 | key: 'mock' 25 | } 26 | ]; 27 | 28 | const StatisTable = props => { 29 | const { dataSource } = props; 30 | return ( 31 |
32 |

分组数据详情

33 | 39 | 40 | ); 41 | }; 42 | 43 | StatisTable.propTypes = { 44 | dataSource: PropTypes.array 45 | }; 46 | 47 | export default StatisTable; 48 | -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/statisticsClientPage/index.scss: -------------------------------------------------------------------------------- 1 | @import '../../../client/styles/mixin'; 2 | 3 | .g-statistic { 4 | @include row-width-limit; 5 | margin: 0 auto .24rem; 6 | margin-top: 24px; 7 | min-width: 11.2rem; 8 | 9 | 10 | .content { 11 | -webkit-box-flex: 1; 12 | padding: 24px; 13 | width:100%; 14 | background: #fff; 15 | min-height: 5rem; 16 | // overflow-x: scroll; 17 | } 18 | .m-row { 19 | border-bottom: 1px solid #f0f0f0; 20 | padding: 16px 0; 21 | } 22 | 23 | .m-row-table { 24 | padding-top: 16px 25 | 26 | } 27 | 28 | .statis-table { 29 | margin-left: 16px; 30 | } 31 | 32 | .m-help { 33 | margin-left: 5px; 34 | border-radius: 12px; 35 | color: #2395f1; 36 | } 37 | 38 | .gutter-row { 39 | padding-left: 24px; 40 | border-left: 1px solid #f0f0f0; 41 | } 42 | 43 | .gutter-row:first-child { 44 | border-left: 0 45 | } 46 | 47 | .gutter-box { 48 | margin-top: 8px; 49 | //margin-bottom: 16px; 50 | //margin: 8px 0 16px; 51 | } 52 | 53 | .statis-chart-content { 54 | margin-top: 8px; 55 | } 56 | 57 | .statis-title{ 58 | padding: 8px 8px 24px; 59 | } 60 | 61 | .statis-chart{ 62 | margin:0 auto; 63 | text-align: center; 64 | } 65 | 66 | .statis-footer{ 67 | margin:16px 0; 68 | text-align: center; 69 | width: 1050px; 70 | } 71 | 72 | .title{ 73 | font-size: 16px; 74 | font-weight: 400; 75 | margin-bottom: 0.16rem; 76 | border-left: 3px solid #2395f1; 77 | padding-left: 8px; 78 | } 79 | 80 | .system-content{ 81 | margin-bottom: 16px; 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /exts/yapi-plugin-statistics/test.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs-extra'); 2 | const yapi = require('../../server/yapi.js'); 3 | const commons = require('../../server/utils/commons'); 4 | const dbModule = require('../../server/utils/db.js'); 5 | const userModel = require('../../server/models/user.js'); 6 | const mongoose = require('mongoose'); 7 | 8 | yapi.commons = commons; 9 | yapi.connect = dbModule.connect(); 10 | 11 | const convert2Decimal = num => (num > 9 ? num : `0${num}`); 12 | const formatYMD = (val, joinStr = '-') => { 13 | let date = val; 14 | if (typeof val !== 'object') { 15 | val = val * 1000; 16 | date = new Date(val); 17 | } 18 | return `${[ 19 | date.getFullYear(), 20 | convert2Decimal(date.getMonth() + 1), 21 | convert2Decimal(date.getDate()) 22 | ].join(joinStr)}`; 23 | }; 24 | 25 | function run() { 26 | let time = yapi.commons.time() - 10000000; 27 | let data = i => { 28 | time = time - yapi.commons.rand(10000, 1000000); 29 | return { 30 | interface_id: 94, 31 | project_id: 25, 32 | group_id: 19, 33 | time: time, 34 | ip: '1.1.1.1', 35 | date: formatYMD(time) 36 | }; 37 | }; 38 | 39 | yapi.connect 40 | .then(function() { 41 | let logCol = mongoose.connection.db.collection('statis_mock'); 42 | let arr = []; 43 | for (let i = 0; i < 11; i++) { 44 | if (arr.length >= 5) { 45 | logCol.insert(arr); 46 | arr = []; 47 | } 48 | arr.push(data(i)); 49 | } 50 | }) 51 | .catch(function(err) { 52 | throw new Error(err.message); 53 | }); 54 | } 55 | 56 | run(); 57 | -------------------------------------------------------------------------------- /exts/yapi-plugin-swagger-auto-sync/client.js: -------------------------------------------------------------------------------- 1 | import swaggerAutoSync from './swaggerAutoSync/swaggerAutoSync.js' 2 | 3 | function hander(routers) { 4 | routers.test = { 5 | name: 'Swagger自动同步', 6 | component: swaggerAutoSync 7 | }; 8 | } 9 | 10 | module.exports = function() { 11 | this.bindHook('sub_setting_nav', hander); 12 | }; 13 | -------------------------------------------------------------------------------- /exts/yapi-plugin-swagger-auto-sync/controller/syncController.js: -------------------------------------------------------------------------------- 1 | const baseController = require('controllers/base.js'); 2 | const yapi = require('yapi.js'); 3 | const syncModel = require('../syncModel.js'); 4 | const projectModel = require('models/project.js'); 5 | const interfaceSyncUtils = require('../interfaceSyncUtils.js') 6 | 7 | class syncController extends baseController { 8 | constructor(ctx) { 9 | super(ctx); 10 | this.syncModel = yapi.getInst(syncModel); 11 | this.projectModel = yapi.getInst(projectModel); 12 | this.interfaceSyncUtils = yapi.getInst(interfaceSyncUtils); 13 | } 14 | 15 | /** 16 | * 保存定时任务 17 | * @param {*} ctx 18 | */ 19 | async upSync(ctx) { 20 | let requestBody = ctx.request.body; 21 | let projectId = requestBody.project_id; 22 | if (!projectId) { 23 | return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); 24 | } 25 | 26 | if ((await this.checkAuth(projectId, 'project', 'edit')) !== true) { 27 | return (ctx.body = yapi.commons.resReturn(null, 405, '没有权限')); 28 | } 29 | 30 | let result; 31 | if (requestBody.id) { 32 | result = await this.syncModel.up(requestBody); 33 | } else { 34 | result = await this.syncModel.save(requestBody); 35 | } 36 | 37 | //操作定时任务 38 | if (requestBody.is_sync_open) { 39 | this.interfaceSyncUtils.addSyncJob(projectId, requestBody.sync_cron, requestBody.sync_json_url, requestBody.sync_mode, requestBody.uid); 40 | } else { 41 | this.interfaceSyncUtils.deleteSyncJob(projectId); 42 | } 43 | return (ctx.body = yapi.commons.resReturn(result)); 44 | } 45 | 46 | /** 47 | * 查询定时任务 48 | * @param {*} ctx 49 | */ 50 | async getSync(ctx) { 51 | let projectId = ctx.query.project_id; 52 | if (!projectId) { 53 | return (ctx.body = yapi.commons.resReturn(null, 408, '缺少项目Id')); 54 | } 55 | let result = await this.syncModel.getByProjectId(projectId); 56 | return (ctx.body = yapi.commons.resReturn(result)); 57 | } 58 | 59 | } 60 | 61 | 62 | module.exports = syncController; -------------------------------------------------------------------------------- /exts/yapi-plugin-swagger-auto-sync/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-swagger-auto-sync/server.js: -------------------------------------------------------------------------------- 1 | const controller = require('./controller/syncController.js'); 2 | const yapi =require('yapi.js'); 3 | const interfaceSyncUtils = require('./interfaceSyncUtils.js'); 4 | 5 | module.exports = function () { 6 | yapi.getInst(interfaceSyncUtils); 7 | 8 | this.bindHook('add_router', function (addRouter) { 9 | addRouter({ 10 | controller: controller, 11 | method: 'get', 12 | path: 'autoSync/get', 13 | action: 'getSync' 14 | }); 15 | addRouter({ 16 | controller: controller, 17 | method: 'post', 18 | path: 'autoSync/save', 19 | action: 'upSync' 20 | }); 21 | }); 22 | 23 | }; -------------------------------------------------------------------------------- /exts/yapi-plugin-swagger-auto-sync/syncModel.js: -------------------------------------------------------------------------------- 1 | const yapi = require('yapi.js'); 2 | const baseModel = require('models/base.js'); 3 | const mongoose = require('mongoose'); 4 | 5 | class syncModel extends baseModel { 6 | getName() { 7 | return 'interface_auto_sync'; 8 | } 9 | 10 | getSchema() { 11 | return { 12 | uid: { type: Number}, 13 | project_id: { type: Number, required: true }, 14 | //是否开启自动同步 15 | is_sync_open: { type: Boolean, default: false }, 16 | //自动同步定时任务的cron表达式 17 | sync_cron: String, 18 | //自动同步获取json的url 19 | sync_json_url: String, 20 | //接口合并模式 good,nomarl等等 意思也就是智能合并,完全覆盖等 21 | sync_mode: String, 22 | //上次成功同步接口时间, 23 | last_sync_time: Number, 24 | //上次同步的swagger 文档内容 25 | old_swagger_content: String, 26 | add_time: Number, 27 | up_time: Number, 28 | }; 29 | } 30 | 31 | getByProjectId(id) { 32 | return this.model.findOne({ 33 | project_id: id 34 | }) 35 | } 36 | 37 | delByProjectId(project_id){ 38 | return this.model.remove({ 39 | project_id: project_id 40 | }) 41 | } 42 | 43 | save(data) { 44 | data.up_time = yapi.commons.time(); 45 | let m = new this.model(data); 46 | return m.save(); 47 | } 48 | 49 | listAll() { 50 | return this.model 51 | .find({}) 52 | .select( 53 | '_id uid project_id add_time up_time is_sync_open sync_cron sync_json_url sync_mode old_swagger_content last_sync_time' 54 | ) 55 | .sort({ _id: -1 }) 56 | .exec(); 57 | } 58 | 59 | up(data) { 60 | let id = data.id; 61 | delete data.id; 62 | data.up_time = yapi.commons.time(); 63 | return this.model.update({ 64 | _id: id 65 | }, data) 66 | } 67 | 68 | upById(id, data) { 69 | delete data.id; 70 | data.up_time = yapi.commons.time(); 71 | return this.model.update({ 72 | _id: id 73 | }, data) 74 | } 75 | 76 | del(id){ 77 | return this.model.remove({ 78 | _id: id 79 | }) 80 | } 81 | 82 | delByProjectId(projectId){ 83 | return this.model.remove({ 84 | project_id: projectId 85 | }) 86 | } 87 | 88 | } 89 | 90 | module.exports = syncModel; -------------------------------------------------------------------------------- /exts/yapi-plugin-test/client.js: -------------------------------------------------------------------------------- 1 | function hander(routers) { 2 | routers.test = { 3 | name: 'test', 4 | component: ()=> 'hello world.' 5 | }; 6 | } 7 | 8 | module.exports = function() { 9 | this.bindHook('sub_setting_nav', hander); 10 | }; 11 | -------------------------------------------------------------------------------- /exts/yapi-plugin-test/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: false, 3 | client: true 4 | } -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/client.js: -------------------------------------------------------------------------------- 1 | import WikiPage from './wikiPage/index'; 2 | // const WikiPage = require('./wikiPage/index') 3 | 4 | module.exports = function() { 5 | this.bindHook('sub_nav', function(app) { 6 | app.wiki = { 7 | name: 'Wiki', 8 | path: '/project/:id/wiki', 9 | component: WikiPage 10 | }; 11 | }); 12 | }; 13 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | server: true, 3 | client: true, 4 | httpCodes: [ 5 | 100, 6 | 101, 7 | 102, 8 | 200, 9 | 201, 10 | 202, 11 | 203, 12 | 204, 13 | 205, 14 | 206, 15 | 207, 16 | 208, 17 | 226, 18 | 300, 19 | 301, 20 | 302, 21 | 303, 22 | 304, 23 | 305, 24 | 307, 25 | 308, 26 | 400, 27 | 401, 28 | 402, 29 | 403, 30 | 404, 31 | 405, 32 | 406, 33 | 407, 34 | 408, 35 | 409, 36 | 410, 37 | 411, 38 | 412, 39 | 413, 40 | 414, 41 | 415, 42 | 416, 43 | 417, 44 | 418, 45 | 422, 46 | 423, 47 | 424, 48 | 426, 49 | 428, 50 | 429, 51 | 431, 52 | 500, 53 | 501, 54 | 502, 55 | 503, 56 | 504, 57 | 505, 58 | 506, 59 | 507, 60 | 508, 61 | 510, 62 | 511 63 | ] 64 | }; 65 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/server.js: -------------------------------------------------------------------------------- 1 | const yapi = require('yapi.js'); 2 | const mongoose = require('mongoose'); 3 | const controller = require('./controller'); 4 | 5 | module.exports = function() { 6 | yapi.connect.then(function() { 7 | let Col = mongoose.connection.db.collection('wiki'); 8 | Col.createIndex({ 9 | project_id: 1 10 | }); 11 | }); 12 | 13 | this.bindHook('add_router', function(addRouter) { 14 | addRouter({ 15 | // 获取wiki信息 16 | controller: controller, 17 | method: 'get', 18 | path: 'wiki_desc/get', 19 | action: 'getWikiDesc' 20 | }); 21 | 22 | addRouter({ 23 | // 更新wiki信息 24 | controller: controller, 25 | method: 'post', 26 | path: 'wiki_desc/up', 27 | action: 'uplodaWikiDesc' 28 | }); 29 | }); 30 | 31 | this.bindHook('add_ws_router', function(wsRouter) { 32 | wsRouter({ 33 | controller: controller, 34 | method: 'get', 35 | path: 'wiki_desc/solve_conflict', 36 | action: 'wikiConflict' 37 | }); 38 | }); 39 | }; 40 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/util.js: -------------------------------------------------------------------------------- 1 | // 时间 2 | const convert2Decimal = num => (num > 9 ? num : `0${num}`); 3 | 4 | /** 5 | * 格式化 年、月、日、时、分、秒 6 | * @param val {Object or String or Number} 日期对象 或是可new Date的对象或时间戳 7 | * @return {String} 2017-01-20 20:00:00 8 | */ 9 | exports.formatDate = val => { 10 | let date = val; 11 | if (typeof val !== 'object') { 12 | date = new Date(val); 13 | } 14 | return `${[ 15 | date.getFullYear(), 16 | convert2Decimal(date.getMonth() + 1), 17 | convert2Decimal(date.getDate()) 18 | ].join('-')} ${[ 19 | convert2Decimal(date.getHours()), 20 | convert2Decimal(date.getMinutes()), 21 | convert2Decimal(date.getSeconds()) 22 | ].join(':')}`; 23 | }; 24 | 25 | // const json5_parse = require('../client/common.js').json5_parse; 26 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/wikiModel.js: -------------------------------------------------------------------------------- 1 | const yapi = require('yapi.js'); 2 | const baseModel = require('models/base.js'); 3 | 4 | class statisMockModel extends baseModel { 5 | getName() { 6 | return 'wiki'; 7 | } 8 | 9 | getSchema() { 10 | return { 11 | project_id: { type: Number, required: true }, 12 | username: String, 13 | uid: { type: Number, required: true }, 14 | edit_uid: { type: Number, default: 0 }, 15 | desc: String, 16 | markdown: String, 17 | add_time: Number, 18 | up_time: Number 19 | }; 20 | } 21 | 22 | save(data) { 23 | let m = new this.model(data); 24 | return m.save(); 25 | } 26 | 27 | get(project_id) { 28 | return this.model 29 | .findOne({ 30 | project_id: project_id 31 | }) 32 | .exec(); 33 | } 34 | 35 | up(id, data) { 36 | return this.model.update( 37 | { 38 | _id: id 39 | }, 40 | data, 41 | { runValidators: true } 42 | ); 43 | } 44 | 45 | upEditUid(id, uid) { 46 | return this.model.update( 47 | { 48 | _id: id 49 | }, 50 | { edit_uid: uid }, 51 | { runValidators: true } 52 | ); 53 | } 54 | } 55 | 56 | module.exports = statisMockModel; 57 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/wikiPage/Editor.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button, Checkbox } from 'antd'; 4 | import Editor from 'common/tui-editor/dist/tui-editor-Editor-all.min.js'; 5 | require('common/tui-editor/dist/tui-editor.min.css'); // editor ui 6 | require('common/tui-editor/dist/tui-editor-contents.min.css'); // editor content 7 | class WikiEditor extends Component { 8 | constructor(props) { 9 | super(props); 10 | } 11 | 12 | static propTypes = { 13 | isConflict: PropTypes.bool, 14 | onUpload: PropTypes.func, 15 | onCancel: PropTypes.func, 16 | notice: PropTypes.bool, 17 | onEmailNotice: PropTypes.func, 18 | desc: PropTypes.string 19 | }; 20 | 21 | componentDidMount() { 22 | this.editor = new Editor({ 23 | el: document.querySelector('#desc'), 24 | initialEditType: 'wysiwyg', 25 | height: '500px', 26 | initialValue: this.props.desc 27 | }); 28 | } 29 | 30 | onUpload = () => { 31 | let desc = this.editor.getHtml(); 32 | let markdown = this.editor.getMarkdown(); 33 | this.props.onUpload(desc, markdown); 34 | }; 35 | 36 | render() { 37 | const { isConflict, onCancel, notice, onEmailNotice } = this.props; 38 | return ( 39 |
40 |
45 |
46 | 55 | 58 | 59 | 通知相关人员 60 | 61 |
62 |
63 | ); 64 | } 65 | } 66 | 67 | export default WikiEditor; 68 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/wikiPage/View.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import PropTypes from 'prop-types'; 3 | import { Button } from 'antd'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | const WikiView = props => { 7 | const { editorEable, onEditor, uid, username, editorTime, desc } = props; 8 | return ( 9 |
10 |
11 | 14 | {username && ( 15 |
16 | 由{' '} 17 | 18 | {username} 19 | {' '} 20 | 修改于 {editorTime} 21 |
22 | )} 23 |
24 |
28 |
29 | ); 30 | }; 31 | 32 | WikiView.propTypes = { 33 | editorEable: PropTypes.bool, 34 | onEditor: PropTypes.func, 35 | uid: PropTypes.number, 36 | username: PropTypes.string, 37 | editorTime: PropTypes.string, 38 | desc: PropTypes.string 39 | }; 40 | 41 | export default WikiView; 42 | -------------------------------------------------------------------------------- /exts/yapi-plugin-wiki/wikiPage/index.scss: -------------------------------------------------------------------------------- 1 | .wiki-content { 2 | .wiki-user { 3 | padding-top: 8px; 4 | } 5 | 6 | .wiki-editor { 7 | padding-top: 16px; 8 | } 9 | 10 | .upload-btn { 11 | margin-right: 16px; 12 | } 13 | .wiki-conflict { 14 | text-align: center; 15 | font-size: 14px; 16 | padding-top: 10px; 17 | } 18 | 19 | .wiki-up { 20 | text-align: right; 21 | padding-top: 16px; 22 | } 23 | 24 | .wiki-title { 25 | padding-bottom: 16px; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["server/", "common/", "plugins", "exts/"] 3 | } -------------------------------------------------------------------------------- /npm-publish.js: -------------------------------------------------------------------------------- 1 | const shell = require ('shelljs'); 2 | const packageJson = require ('./package.json'); 3 | 4 | const version = "v" + packageJson.version; 5 | 6 | if(!version){ 7 | console.error('version 不能为空') 8 | process.exit(1) 9 | } 10 | 11 | shell.exec ('cnpm i'); 12 | shell.exec ('cnpm run build-client'); 13 | shell.exec ('git add .'); 14 | shell.exec ('git commit -a -m "chore: update static file"'); 15 | 16 | console.log ('exec: git pull origin master'); 17 | shell.exec ('git pull origin master'); 18 | let a = shell.exec (`git tag |grep ${version} |wc -l`); 19 | if (a && parseInt (a) > 0) { 20 | shell.exec ('git tag -d ' + version); 21 | shell.exec ('git push origin :' + version); 22 | } 23 | shell.exec ('git tag ' + version); 24 | shell.exec ('git push origin ' + version); 25 | 26 | console.log('git push success', version) 27 | 28 | console.log('正在执行npm发布') 29 | shell.exec('npm publish --registry https://registry.npmjs.org/') 30 | -------------------------------------------------------------------------------- /plugin.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "yapi-plugin-qsso", 4 | "url": "https://github.com/ymfe/yapi-plugin-qsso", 5 | "desc": "qunar 专用 sso 第三方登录" 6 | }, 7 | { 8 | "title": "yapi-plugin-import-openapi", 9 | "url": "https://github.com/yuu2lee4/yapi-plugin-import-openapi", 10 | "desc": "openapi导入插件(仅支持3.0)" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /server/app.js: -------------------------------------------------------------------------------- 1 | process.env.NODE_PATH = __dirname; 2 | require('module').Module._initPaths(); 3 | 4 | const yapi = require('./yapi.js'); 5 | const commons = require('./utils/commons'); 6 | yapi.commons = commons; 7 | const dbModule = require('./utils/db.js'); 8 | yapi.connect = dbModule.connect(); 9 | const mockServer = require('./middleware/mockServer.js'); 10 | require('./plugin.js'); 11 | const websockify = require('koa-websocket'); 12 | const websocket = require('./websocket.js'); 13 | const storageCreator = require('./utils/storage') 14 | require('./utils/notice') 15 | 16 | const Koa = require('koa'); 17 | const koaStatic = require('koa-static'); 18 | // const bodyParser = require('koa-bodyparser'); 19 | const koaBody = require('koa-body'); 20 | const router = require('./router.js'); 21 | 22 | global.storageCreator = storageCreator; 23 | let indexFile = process.argv[2] === 'dev' ? 'dev.html' : 'index.html'; 24 | 25 | const app = websockify(new Koa()); 26 | app.proxy = true; 27 | yapi.app = app; 28 | 29 | // app.use(bodyParser({multipart: true})); 30 | app.use(koaBody({ multipart: true, jsonLimit: '2mb', formLimit: '1mb', textLimit: '1mb' })); 31 | app.use(mockServer); 32 | app.use(router.routes()); 33 | app.use(router.allowedMethods()); 34 | 35 | websocket(app); 36 | 37 | app.use(async (ctx, next) => { 38 | if (/^\/(?!api)[a-zA-Z0-9\/\-_]*$/.test(ctx.path)) { 39 | ctx.path = '/'; 40 | await next(); 41 | } else { 42 | await next(); 43 | } 44 | }); 45 | 46 | app.use(async (ctx, next) => { 47 | if (ctx.path.indexOf('/prd') === 0) { 48 | ctx.set('Cache-Control', 'max-age=8640000000'); 49 | if (yapi.commons.fileExist(yapi.path.join(yapi.WEBROOT, 'static', ctx.path + '.gz'))) { 50 | ctx.set('Content-Encoding', 'gzip'); 51 | ctx.path = ctx.path + '.gz'; 52 | } 53 | } 54 | await next(); 55 | }); 56 | 57 | 58 | app.use(koaStatic(yapi.path.join(yapi.WEBROOT, 'static'), { index: indexFile, gzip: true })); 59 | 60 | 61 | const server = app.listen(yapi.WEBCONFIG.port); 62 | 63 | server.setTimeout(yapi.WEBCONFIG.timeout); 64 | 65 | commons.log( 66 | `服务已启动,请打开下面链接访问: \nhttp://127.0.0.1${ 67 | yapi.WEBCONFIG.port == '80' ? '' : ':' + yapi.WEBCONFIG.port 68 | }/` 69 | ); 70 | -------------------------------------------------------------------------------- /server/models/avatar.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi.js'); 2 | const baseModel = require('./base.js'); 3 | 4 | class avatarModel extends baseModel { 5 | getName() { 6 | return 'avatar'; 7 | } 8 | 9 | getSchema() { 10 | return { 11 | uid: { type: Number, required: true }, 12 | basecode: String, 13 | type: String 14 | }; 15 | } 16 | 17 | get(uid) { 18 | return this.model.findOne({ 19 | uid: uid 20 | }); 21 | } 22 | 23 | up(uid, basecode, type) { 24 | return this.model.update( 25 | { 26 | uid: uid 27 | }, 28 | { 29 | type: type, 30 | basecode: basecode 31 | }, 32 | { 33 | upsert: true 34 | } 35 | ); 36 | } 37 | } 38 | 39 | module.exports = avatarModel; 40 | -------------------------------------------------------------------------------- /server/models/base.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi.js'); 2 | const mongoose = require('mongoose'); 3 | const autoIncrement = require('../utils/mongoose-auto-increment'); 4 | 5 | /** 6 | * 所有的model都需要继承baseModel, 且需要 getSchema和getName方法,不然会报错 7 | */ 8 | 9 | class baseModel { 10 | constructor() { 11 | this.schema = new mongoose.Schema(this.getSchema()); 12 | this.name = this.getName(); 13 | 14 | if (this.isNeedAutoIncrement() === true) { 15 | this.schema.plugin(autoIncrement.plugin, { 16 | model: this.name, 17 | field: this.getPrimaryKey(), 18 | startAt: 11, 19 | incrementBy: yapi.commons.rand(1, 10) 20 | }); 21 | } 22 | 23 | this.model = yapi.db(this.name, this.schema); 24 | } 25 | 26 | isNeedAutoIncrement() { 27 | return true; 28 | } 29 | 30 | /** 31 | * 可通过覆盖此方法生成其他自增字段 32 | */ 33 | getPrimaryKey() { 34 | return '_id'; 35 | } 36 | 37 | /** 38 | * 获取collection的schema结构 39 | */ 40 | getSchema() { 41 | yapi.commons.log('Model Class need getSchema function', 'error'); 42 | } 43 | 44 | getName() { 45 | yapi.commons.log('Model Class need name', 'error'); 46 | } 47 | } 48 | 49 | module.exports = baseModel; 50 | -------------------------------------------------------------------------------- /server/models/follow.js: -------------------------------------------------------------------------------- 1 | const baseModel = require('./base.js'); 2 | 3 | class followModel extends baseModel { 4 | getName() { 5 | return 'follow'; 6 | } 7 | 8 | getSchema() { 9 | return { 10 | uid: { type: Number, required: true }, 11 | projectid: { type: Number, required: true }, 12 | projectname: { type: String, required: true }, 13 | icon: String, 14 | color: String 15 | }; 16 | } 17 | 18 | /** 19 | * @param {Number} uid 用户id 20 | * @param {Number} projectid 项目id 21 | * @param {String} projectname 项目名 22 | * @param {String} icon 项目图标 23 | */ 24 | 25 | save(data) { 26 | //关注 27 | let saveData = { 28 | uid: data.uid, 29 | projectid: data.projectid, 30 | projectname: data.projectname, 31 | icon: data.icon, 32 | color: data.color 33 | }; 34 | let follow = new this.model(saveData); 35 | return follow.save(); 36 | } 37 | 38 | del(projectid, uid) { 39 | return this.model.remove({ 40 | projectid: projectid, 41 | uid: uid 42 | }); 43 | } 44 | 45 | list(uid) { 46 | return this.model 47 | .find({ 48 | uid: uid 49 | }) 50 | .exec(); 51 | } 52 | 53 | listByProjectId(projectid) { 54 | return this.model.find({ 55 | projectid: projectid 56 | }); 57 | } 58 | 59 | checkProjectRepeat(uid, projectid) { 60 | return this.model.countDocuments({ 61 | uid: uid, 62 | projectid: projectid 63 | }); 64 | } 65 | 66 | updateById(id, typeid, data) { 67 | return this.model.update( 68 | { 69 | uid: id, 70 | projectid: typeid 71 | }, 72 | data, 73 | { runValidators: true } 74 | ); 75 | } 76 | } 77 | 78 | module.exports = followModel; 79 | -------------------------------------------------------------------------------- /server/models/interfaceCat.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi.js'); 2 | const baseModel = require('./base.js'); 3 | 4 | /** 5 | * 接口分类 6 | */ 7 | class interfaceCat extends baseModel { 8 | getName() { 9 | return 'interface_cat'; 10 | } 11 | 12 | getSchema() { 13 | return { 14 | name: { type: String, required: true }, 15 | uid: { type: Number, required: true }, 16 | project_id: { type: Number, required: true }, 17 | desc: String, 18 | add_time: Number, 19 | up_time: Number, 20 | index: { type: Number, default: 0 } 21 | }; 22 | } 23 | 24 | save(data) { 25 | let m = new this.model(data); 26 | return m.save(); 27 | } 28 | 29 | get(id) { 30 | return this.model 31 | .findOne({ 32 | _id: id 33 | }) 34 | .exec(); 35 | } 36 | 37 | checkRepeat(name) { 38 | return this.model.countDocuments({ 39 | name: name 40 | }); 41 | } 42 | 43 | list(project_id) { 44 | return this.model 45 | .find({ 46 | project_id: project_id 47 | }) 48 | .sort({ index: 1 }) 49 | .exec(); 50 | } 51 | 52 | del(id) { 53 | return this.model.remove({ 54 | _id: id 55 | }); 56 | } 57 | 58 | delByProjectId(id) { 59 | return this.model.remove({ 60 | project_id: id 61 | }); 62 | } 63 | 64 | up(id, data) { 65 | data.up_time = yapi.commons.time(); 66 | return this.model.update( 67 | { 68 | _id: id 69 | }, 70 | data 71 | ); 72 | } 73 | 74 | upCatIndex(id, index) { 75 | return this.model.update( 76 | { 77 | _id: id 78 | }, 79 | { 80 | index: index 81 | } 82 | ); 83 | } 84 | } 85 | 86 | module.exports = interfaceCat; 87 | -------------------------------------------------------------------------------- /server/models/storage.js: -------------------------------------------------------------------------------- 1 | const baseModel = require('./base.js'); 2 | const mongoose = require('mongoose'); 3 | 4 | class stroageModel extends baseModel { 5 | constructor() { 6 | super() 7 | let storageCol = mongoose.connection.db.collection('storage'); 8 | storageCol.createIndex( 9 | { 10 | key: 1 11 | }, 12 | { 13 | unique: true 14 | } 15 | ); 16 | } 17 | 18 | getName() { 19 | return 'storage'; 20 | } 21 | 22 | getSchema() { 23 | return { 24 | key: { type: Number, required: true }, 25 | data: { 26 | type: String, 27 | default: '' 28 | } //用于原始数据存储 29 | }; 30 | } 31 | save(key, data = {}, isInsert = false) { 32 | 33 | let saveData = { 34 | key, 35 | data: JSON.stringify(data, null, 2) 36 | }; 37 | if(isInsert){ 38 | let r = new this.model(saveData); 39 | return r.save(); 40 | } 41 | return this.model.updateOne({ 42 | key 43 | }, saveData) 44 | } 45 | 46 | del(key) { 47 | return this.model.remove({ 48 | key 49 | }); 50 | } 51 | 52 | get(key) { 53 | return this.model 54 | .findOne({ 55 | key 56 | }) 57 | .exec().then(data => { 58 | this.save(key, {}) 59 | if (!data) return null; 60 | data = data.toObject().data; 61 | try { 62 | return JSON.parse(data) 63 | } catch (e) { 64 | return {} 65 | } 66 | }); 67 | } 68 | } 69 | 70 | module.exports = stroageModel; 71 | -------------------------------------------------------------------------------- /server/models/token.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi.js'); 2 | const baseModel = require('./base.js'); 3 | 4 | class tokenModel extends baseModel { 5 | getName() { 6 | return 'token'; 7 | } 8 | 9 | getSchema() { 10 | return { 11 | project_id: { type: Number, required: true }, 12 | token: String 13 | }; 14 | } 15 | 16 | save(data) { 17 | let m = new this.model(data); 18 | return m.save(); 19 | } 20 | 21 | get(project_id) { 22 | return this.model.findOne({ 23 | project_id: project_id 24 | }); 25 | } 26 | 27 | findId(token) { 28 | return this.model 29 | .findOne({ 30 | token: token 31 | }) 32 | .select('project_id') 33 | .exec(); 34 | } 35 | 36 | up(project_id, token) { 37 | return this.model.update( 38 | { 39 | project_id: project_id 40 | }, 41 | { 42 | token: token 43 | } 44 | ); 45 | } 46 | } 47 | 48 | module.exports = tokenModel; 49 | -------------------------------------------------------------------------------- /server/utils/db.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const yapi = require('../yapi.js'); 3 | const autoIncrement = require('./mongoose-auto-increment'); 4 | 5 | function model(model, schema) { 6 | if (schema instanceof mongoose.Schema === false) { 7 | schema = new mongoose.Schema(schema); 8 | } 9 | 10 | schema.set('autoIndex', false); 11 | 12 | return mongoose.model(model, schema, model); 13 | } 14 | 15 | function connect(callback) { 16 | mongoose.Promise = global.Promise; 17 | mongoose.set('useNewUrlParser', true); 18 | mongoose.set('useFindAndModify', false); 19 | mongoose.set('useCreateIndex', true); 20 | 21 | let config = yapi.WEBCONFIG; 22 | let options = {useNewUrlParser: true, useCreateIndex: true, useUnifiedTopology: true}; 23 | 24 | if (config.db.user) { 25 | options.user = config.db.user; 26 | options.pass = config.db.pass; 27 | } 28 | 29 | options = Object.assign({}, options, config.db.options) 30 | 31 | var connectString = ''; 32 | 33 | if(config.db.connectString){ 34 | connectString = config.db.connectString; 35 | }else{ 36 | connectString = `mongodb://${config.db.servername}:${config.db.port}/${config.db.DATABASE}`; 37 | if (config.db.authSource) { 38 | connectString = connectString + `?authSource=${config.db.authSource}`; 39 | } 40 | } 41 | 42 | let db = mongoose.connect( 43 | connectString, 44 | options, 45 | function(err) { 46 | if (err) { 47 | yapi.commons.log(err + ', mongodb Authentication failed', 'error'); 48 | } 49 | } 50 | ); 51 | 52 | db.then( 53 | function() { 54 | yapi.commons.log('mongodb load success...'); 55 | 56 | if (typeof callback === 'function') { 57 | callback.call(db); 58 | } 59 | }, 60 | function(err) { 61 | yapi.commons.log(err + 'mongodb connect error', 'error'); 62 | } 63 | ); 64 | 65 | autoIncrement.initialize(db); 66 | return db; 67 | } 68 | 69 | yapi.db = model; 70 | 71 | module.exports = { 72 | model: model, 73 | connect: connect 74 | }; 75 | -------------------------------------------------------------------------------- /server/utils/initConfig.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const config = require('../config.js'); 4 | 5 | let runtimePath = config.runtime_path; 6 | fs.ensureDirSync(runtimePath); 7 | fs.ensureDirSync(path.join(runtimePath, 'log')); 8 | let configPath = path.join(runtimePath, 'config.json'); 9 | 10 | fs.writeFileSync(configPath, 11 | JSON.stringify(config, null, '\t'), 12 | { encoding: 'utf8' } 13 | ); -------------------------------------------------------------------------------- /server/utils/notice.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi.js'); 2 | 3 | function arrUnique(arr1, arr2) { 4 | let arr = arr1.concat(arr2); 5 | let res = arr.filter(function(item, index, arr) { 6 | return arr.indexOf(item) === index; 7 | }); 8 | return res; 9 | } 10 | 11 | const noticeObj = { 12 | mail: { 13 | title: '邮件', 14 | hander: (emails, title, content)=>{ 15 | yapi.commons.sendMail({ 16 | to: emails, 17 | contents: content, 18 | subject: title 19 | }); 20 | } 21 | } 22 | } 23 | 24 | yapi.emitHook('addNotice', noticeObj) 25 | 26 | yapi.commons.sendNotice = async function(projectId, data) { 27 | const projectModel = require('../models/project.js'); 28 | const userModel = require('../models/user.js'); 29 | const followModel = require('../models/follow.js'); 30 | 31 | const followInst = yapi.getInst(followModel); 32 | const userInst = yapi.getInst(userModel); 33 | const projectInst = yapi.getInst(projectModel); 34 | const list = await followInst.listByProjectId(projectId); 35 | const starUsers = list.map(item => item.uid); 36 | 37 | const projectList = await projectInst.get(projectId); 38 | const projectMenbers = projectList.members 39 | .filter(item => item.email_notice) 40 | .map(item => item.uid); 41 | 42 | const users = arrUnique(projectMenbers, starUsers); 43 | const usersInfo = await userInst.findByUids(users); 44 | const emails = usersInfo.map(item => item.email).join(','); 45 | 46 | try { 47 | Object.keys(noticeObj).forEach(key=>{ 48 | let noticeItem = noticeObj[key]; 49 | try{ 50 | noticeItem.hander(emails, data.title, data.content) 51 | }catch(err){ 52 | yapi.commons.log('发送' + (noticeItem.title || key) + '失败' + err.message, 'error') 53 | } 54 | }) 55 | // yapi.commons.sendMail({ 56 | // to: emails, 57 | // contents: data.content, 58 | // subject: data.title 59 | // }); 60 | } catch (e) { 61 | yapi.commons.log('发送失败:' + e, 'error'); 62 | } 63 | }; -------------------------------------------------------------------------------- /server/utils/reportHtml/defaultTheme.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const sysPath = require('path'); 3 | const css = fs.readFileSync(sysPath.join(__dirname, './defaultTheme.css')); 4 | module.exports = ''; 5 | -------------------------------------------------------------------------------- /server/utils/sandbox.js: -------------------------------------------------------------------------------- 1 | const Safeify = require('safeify').default; 2 | 3 | module.exports = async function sandboxFn(context, script) { 4 | // 创建 safeify 实例 5 | const safeVm = new Safeify({ 6 | timeout: 3000, 7 | asyncTimeout: 60000 8 | }) 9 | 10 | // 执行动态代码 11 | const result = await safeVm.run(script, context) 12 | 13 | // 释放资源 14 | safeVm.destroy() 15 | return result 16 | } 17 | -------------------------------------------------------------------------------- /server/utils/storage.js: -------------------------------------------------------------------------------- 1 | module.exports = function storageCreator(id) { 2 | const storageModel = require('../models/storage.js'); 3 | const yapi = require('../yapi.js'); 4 | const defaultData = {} 5 | return { 6 | getItem: async (name = '') => { 7 | let inst = yapi.getInst(storageModel); 8 | let data = await inst.get(id); 9 | data = data || defaultData; 10 | if (name) return data[name]; 11 | return data; 12 | }, 13 | setItem: async (name, value) => { 14 | let inst = yapi.getInst(storageModel); 15 | let curData = await inst.get(id); 16 | let data = curData || defaultData; 17 | let result; 18 | data[name] = value; 19 | if(!curData){ 20 | result = await inst.save(id, data, true) 21 | }else{ 22 | result = await inst.save(id, data, false) 23 | } 24 | 25 | return result; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /server/utils/token.js: -------------------------------------------------------------------------------- 1 | const yapi = require('../yapi') 2 | 3 | const crypto = require('crypto'); 4 | 5 | /* 6 | 下面是使用加密算法 7 | */ 8 | 9 | // 创建加密算法 10 | const aseEncode = function(data, password) { 11 | 12 | // 如下方法使用指定的算法与密码来创建cipher对象 13 | const cipher = crypto.createCipher('aes192', password); 14 | 15 | // 使用该对象的update方法来指定需要被加密的数据 16 | let crypted = cipher.update(data, 'utf-8', 'hex'); 17 | 18 | crypted += cipher.final('hex'); 19 | 20 | return crypted; 21 | }; 22 | 23 | // 创建解密算法 24 | const aseDecode = function(data, password) { 25 | /* 26 | 该方法使用指定的算法与密码来创建 decipher对象, 第一个算法必须与加密数据时所使用的算法保持一致; 27 | 第二个参数用于指定解密时所使用的密码,其参数值为一个二进制格式的字符串或一个Buffer对象,该密码同样必须与加密该数据时所使用的密码保持一致 28 | */ 29 | const decipher = crypto.createDecipher('aes192', password); 30 | 31 | /* 32 | 第一个参数为一个Buffer对象或一个字符串,用于指定需要被解密的数据 33 | 第二个参数用于指定被解密数据所使用的编码格式,可指定的参数值为 'hex', 'binary', 'base64'等, 34 | 第三个参数用于指定输出解密数据时使用的编码格式,可选参数值为 'utf-8', 'ascii' 或 'binary'; 35 | */ 36 | let decrypted = decipher.update(data, 'hex', 'utf-8'); 37 | 38 | decrypted += decipher.final('utf-8'); 39 | return decrypted; 40 | }; 41 | 42 | const defaultSalt = 'abcde'; 43 | 44 | exports.getToken = function getToken(token, uid){ 45 | if(!token)throw new Error('token 不能为空') 46 | yapi.WEBCONFIG.passsalt = yapi.WEBCONFIG.passsalt || defaultSalt; 47 | return aseEncode(uid + '|' + token, yapi.WEBCONFIG.passsalt) 48 | } 49 | 50 | exports.parseToken = function parseToken(token){ 51 | if(!token)throw new Error('token 不能为空') 52 | yapi.WEBCONFIG.passsalt = yapi.WEBCONFIG.passsalt || defaultSalt; 53 | let tokens; 54 | try{ 55 | tokens = aseDecode(token, yapi.WEBCONFIG.passsalt) 56 | }catch(e){} 57 | if(tokens && typeof tokens === 'string' && tokens.indexOf('|') > 0){ 58 | tokens = tokens.split('|') 59 | return { 60 | uid: tokens[0], 61 | projectToken: tokens[1] 62 | } 63 | } 64 | return false; 65 | 66 | } 67 | 68 | -------------------------------------------------------------------------------- /server/websocket.js: -------------------------------------------------------------------------------- 1 | const koaRouter = require('koa-router'); 2 | const interfaceController = require('./controllers/interface.js'); 3 | const yapi = require('./yapi.js'); 4 | 5 | const router = koaRouter(); 6 | const { createAction } = require("./utils/commons.js") 7 | 8 | let pluginsRouterPath = []; 9 | 10 | 11 | function addPluginRouter(config) { 12 | if (!config.path || !config.controller || !config.action) { 13 | throw new Error('Plugin Route config Error'); 14 | } 15 | let method = config.method || 'GET'; 16 | let routerPath = '/ws_plugin/' + config.path; 17 | if (pluginsRouterPath.indexOf(routerPath) > -1) { 18 | throw new Error('Plugin Route path conflict, please try rename the path') 19 | } 20 | pluginsRouterPath.push(routerPath); 21 | createAction(router, "/api", config.controller, config.action, routerPath, method, true); 22 | } 23 | 24 | 25 | function websocket(app) { 26 | createAction(router, "/api", interfaceController, "solveConflict", "/interface/solve_conflict", "get") 27 | 28 | yapi.emitHookSync('add_ws_router', addPluginRouter); 29 | 30 | 31 | app.ws.use(router.routes()) 32 | app.ws.use(router.allowedMethods()); 33 | app.ws.use(function (ctx, next) { 34 | return ctx.websocket.send(JSON.stringify({ 35 | errcode: 404, 36 | errmsg: 'No Fount.' 37 | })); 38 | }); 39 | } 40 | 41 | module.exports = websocket -------------------------------------------------------------------------------- /server/yapi.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs-extra'); 3 | const nodemailer = require('nodemailer'); 4 | const config = require('../../config.json'); 5 | 6 | let insts = new Map(); 7 | let mail; 8 | 9 | const WEBROOT = path.resolve(__dirname, '..'); //路径 10 | const WEBROOT_SERVER = __dirname; 11 | const WEBROOT_RUNTIME = path.resolve(__dirname, '../..'); 12 | const WEBROOT_LOG = path.join(WEBROOT_RUNTIME, 'log'); 13 | const WEBCONFIG = config; 14 | 15 | fs.ensureDirSync(WEBROOT_LOG); 16 | 17 | if (WEBCONFIG.mail && WEBCONFIG.mail.enable) { 18 | mail = nodemailer.createTransport(WEBCONFIG.mail); 19 | } 20 | 21 | /** 22 | * 获取一个model实例,如果不存在则创建一个新的返回 23 | * @param {*} m class 24 | * @example 25 | * yapi.getInst(groupModel, arg1, arg2) 26 | */ 27 | function getInst(m, ...args) { 28 | if (!insts.get(m)) { 29 | insts.set(m, new m(args)); 30 | } 31 | return insts.get(m); 32 | } 33 | 34 | function delInst(m) { 35 | try { 36 | insts.delete(m); 37 | } catch (err) { 38 | console.error(err); // eslint-disable-line 39 | } 40 | } 41 | 42 | 43 | let r = { 44 | fs: fs, 45 | path: path, 46 | WEBROOT: WEBROOT, 47 | WEBROOT_SERVER: WEBROOT_SERVER, 48 | WEBROOT_RUNTIME: WEBROOT_RUNTIME, 49 | WEBROOT_LOG: WEBROOT_LOG, 50 | WEBCONFIG: WEBCONFIG, 51 | getInst: getInst, 52 | delInst: delInst, 53 | getInsts: insts 54 | }; 55 | if (mail) r.mail = mail; 56 | module.exports = r; -------------------------------------------------------------------------------- /static/attachment/cross-request.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/attachment/cross-request.zip -------------------------------------------------------------------------------- /static/dev.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | YApi Pro-高效、易用、功能强大的可视化接口管理平台 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /static/iconfont/iconfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/iconfont/iconfont.eot -------------------------------------------------------------------------------- /static/iconfont/iconfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/iconfont/iconfont.ttf -------------------------------------------------------------------------------- /static/iconfont/iconfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/iconfont/iconfont.woff -------------------------------------------------------------------------------- /static/image/avatar-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/image/avatar-1.png -------------------------------------------------------------------------------- /static/image/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/image/avatar.png -------------------------------------------------------------------------------- /static/image/demo-img@1x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/image/demo-img@1x.jpg -------------------------------------------------------------------------------- /static/image/demo-img@2x.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/image/demo-img@2x.jpg -------------------------------------------------------------------------------- /static/image/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/image/favicon.png -------------------------------------------------------------------------------- /static/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | YApi Pro-高效、易用、功能强大的可视化接口管理平台 9 | 10 | 13 | 14 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 26 | 29 | 32 | 35 | 36 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/prd/assets.js: -------------------------------------------------------------------------------- 1 | window.WEBPACK_ASSETS = {"manifest":{"js":"manifest@f2f4bd774d6c221b3d5f.js"},"index.js":{"js":"index@4bf34ac5d0d400162c8b.js","css":"index@4bf34ac5d0d400162c8b.css"},"lib":{"js":"lib@ff671d00820016606eda.js"},"lib2":{"js":"lib2@b606a4c30ccd185aa92f.js"},"lib3":{"js":"lib3@bae7dadd0771f0f60dd8.js"},"":{"gz":["lib@ff671d00820016606eda.js.gz","lib2@b606a4c30ccd185aa92f.js.gz","index@4bf34ac5d0d400162c8b.css.gz","lib3@bae7dadd0771f0f60dd8.js.gz","index@4bf34ac5d0d400162c8b.js.gz"]}} -------------------------------------------------------------------------------- /static/prd/index@4bf34ac5d0d400162c8b.css.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/prd/index@4bf34ac5d0d400162c8b.css.gz -------------------------------------------------------------------------------- /static/prd/index@4bf34ac5d0d400162c8b.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/prd/index@4bf34ac5d0d400162c8b.js.gz -------------------------------------------------------------------------------- /static/prd/lib2@b606a4c30ccd185aa92f.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/prd/lib2@b606a4c30ccd185aa92f.js.gz -------------------------------------------------------------------------------- /static/prd/lib3@bae7dadd0771f0f60dd8.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/prd/lib3@bae7dadd0771f0f60dd8.js.gz -------------------------------------------------------------------------------- /static/prd/lib@ff671d00820016606eda.js.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/static/prd/lib@ff671d00820016606eda.js.gz -------------------------------------------------------------------------------- /static/prd/manifest@f2f4bd774d6c221b3d5f.js: -------------------------------------------------------------------------------- 1 | !function(e){function t(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return e[n].call(i.exports,i,i.exports,t),i.loaded=!0,i.exports}var n=window.webpackJsonp;window.webpackJsonp=function(s,o){for(var u,f,l=0,c=[];l{ 6 | let data = '@string ${body.a}'; 7 | t.is(mockExtra(data), '@string ${body.a}'); 8 | let data2 = { 9 | a:'@string', 10 | b:{ 11 | t:'${body.a}' 12 | } 13 | } 14 | t.deepEqual(mockExtra(data2,{ 15 | body: { 16 | a: 3 17 | } 18 | }), { 19 | a:'@string', 20 | b:{ 21 | t:3 22 | } 23 | }, 'message'); 24 | 25 | //test object 26 | let data3 = { 27 | a:'@string', 28 | b:{ 29 | t:'${body}' 30 | } 31 | } 32 | t.deepEqual(mockExtra(data3,{ 33 | body: { 34 | a: 3, 35 | t: 5 36 | } 37 | }), { 38 | a:'@string', 39 | b:{ 40 | t:{ 41 | a: 3, 42 | t: 5 43 | } 44 | } 45 | }, 'message'); 46 | 47 | //test array 48 | let data4 = { 49 | a:'@string', 50 | b:{ 51 | t:'${query.arr}' 52 | } 53 | } 54 | 55 | t.deepEqual(mockExtra(data4, {query: { 56 | arr: [1,2,3] 57 | }}), { 58 | a: '@string', 59 | b:{ 60 | t: [1,2,3] 61 | } 62 | 63 | }, 'message'); 64 | 65 | //test var 66 | let data5 = { 67 | a:'@string', 68 | b:{ 69 | t:'${ttt.arr}' 70 | } 71 | } 72 | 73 | t.deepEqual(mockExtra(data5, {ttt: { 74 | arr: [1,2,3] 75 | }}), { 76 | a: '@string', 77 | b:{ 78 | t: [1,2,3] 79 | } 80 | 81 | }, 'message'); 82 | 83 | //test var 84 | let data6 = { 85 | a:'@string', 86 | b:{ 87 | "ttt|regexp":'a|b' 88 | } 89 | } 90 | 91 | //test regexp 92 | t.deepEqual(mockExtra(data6, {ttt: { 93 | arr: [1,2,3] 94 | }}), { 95 | a: '@string', 96 | b:{ 97 | ttt: /a|b/ 98 | } 99 | 100 | }, 'message'); 101 | 102 | 103 | 104 | 105 | 106 | }) 107 | 108 | -------------------------------------------------------------------------------- /test/server/commons.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | import { 3 | ltrim, 4 | rtrim, 5 | trim, 6 | handleParams, 7 | verifyPath, 8 | sandbox, 9 | handleVarPath 10 | } from '../../server/utils/commons.js'; 11 | 12 | test('trim', t => { 13 | t.is(trim(" a b ksjdfk "), 'a b ksjdfk'); 14 | t.is(trim(1), '1') 15 | }); 16 | 17 | test('ltrim', t => { 18 | t.is(ltrim(" a b ksjdfk "), 'a b ksjdfk '); 19 | t.is(ltrim(1), '1') 20 | }); 21 | 22 | test('rtrim', t => { 23 | t.is(rtrim(" a b ksjdfk "), ' a b ksjdfk'); 24 | t.is(rtrim(1), '1') 25 | }); 26 | 27 | test('handleParams', t=>{ 28 | t.deepEqual(handleParams({ 29 | a: ' s k ', 30 | b: " a123456 " 31 | }, { 32 | a: 'string', 33 | b: 'number' 34 | }), { 35 | a: 's k', 36 | b: 0 37 | }) 38 | }) 39 | 40 | test('verifyPath', t=>{ 41 | t.false(verifyPath('a/b')); 42 | t.true(verifyPath('/a:b/t/.api/k_-/tt')) 43 | t.true(verifyPath('/a:b/t/.api/k_-/tt/')) 44 | }) 45 | 46 | test('sandbox', t=>{ 47 | t.deepEqual(sandbox({ 48 | a: 1 49 | }, 'a=2'), {a : 2}); 50 | }) 51 | 52 | test('handleVarPath', t=>{ 53 | let result = []; 54 | let pathname = '/a/:id' 55 | handleVarPath(pathname, result); 56 | 57 | t.deepEqual(result, [{ 58 | name: 'id', 59 | desc: '' 60 | }]) 61 | }) 62 | 63 | test('handleVarPath2', t=>{ 64 | let result = []; 65 | let pathname = '/a/{id}' 66 | handleVarPath(pathname, result); 67 | 68 | t.deepEqual(result, [{ 69 | name: 'id', 70 | desc: '' 71 | }]) 72 | }) 73 | 74 | test('handleVarPath4', t=>{ 75 | let result = []; 76 | let pathname = '/a/id={id}/tt/:sub/kk' 77 | handleVarPath(pathname, result); 78 | 79 | t.deepEqual(result, [{ 80 | name: 'sub', 81 | desc: '' 82 | }, { 83 | name: 'id', 84 | desc: '' 85 | }]) 86 | }) -------------------------------------------------------------------------------- /test/server/mockServer.test.js: -------------------------------------------------------------------------------- 1 | import test from 'ava'; 2 | const rewire = require("rewire"); 3 | const mockServer = rewire('../../server/middleware/mockServer.js'); 4 | const matchApi = mockServer.__get__('matchApi'); 5 | 6 | 7 | test('matchApi', t => { 8 | const apiRule = '/user/:username'; 9 | t.truthy(matchApi('/user/tom', apiRule)); 10 | t.truthy(matchApi('/user/111$$%#$##$#2222222222!!!!!!!', apiRule)) 11 | t.false(matchApi('/user/a/', apiRule)) 12 | t.false(matchApi('/use/a', apiRule)) 13 | 14 | const apiRule_2 = '/user/:username/kk'; 15 | t.truthy(matchApi('/user/aa/kk', apiRule_2)); 16 | t.truthy(matchApi('/user/!!!###kksdjfks***/kk', apiRule_2)); 17 | t.false(matchApi('/user/aa/aa', apiRule_2)); 18 | 19 | const apiRule_3 = '/user/:sdfsdfj/ttt/:sdkfjkj'; 20 | t.truthy(matchApi('/user/a/ttt/b', apiRule_3)); 21 | t.false(matchApi('/user/a/ttt2/b', apiRule_3)) 22 | 23 | const apiRule_4 = '/user/{aaa}/ttt/{bbbb}'; 24 | t.truthy(matchApi('/user/a/ttt/b', apiRule_4)); 25 | t.false(matchApi('/user/a/ttt2/b', apiRule_4)) 26 | 27 | const apiRule_5 = '/user/{aaa}/ttt/{bbbb}'; 28 | let r5 = matchApi('/user/ac/ttt/bd', apiRule_5); 29 | t.deepEqual(r5, { 30 | aaa: 'ac', 31 | bbbb: 'bd', 32 | __weight: 2 33 | }); 34 | 35 | const apiRule_6 = '/user/a1={aaa}/ttt/b1={bbbb}'; 36 | let r6 = matchApi('/user/a1=aaa/ttt/b1=111q', apiRule_6); 37 | t.deepEqual(r6, { 38 | aaa: 'aaa', 39 | bbbb: '111q', 40 | __weight: 2 41 | }); 42 | 43 | 44 | const apiRule_7 = '/user/a1={aaa}/ttt/b1={bbbb}/xxx/yyy'; 45 | let r7 = matchApi('/user/a1=aaa/ttt/b1=111q/xxx/yyy', apiRule_7); 46 | t.deepEqual(r7, { 47 | aaa: 'aaa', 48 | bbbb: '111q', 49 | __weight: 4 50 | }); 51 | 52 | }); 53 | -------------------------------------------------------------------------------- /webpack.alias.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | resolve: { 5 | alias: { 6 | 'common': path.resolve(__dirname, 'common'), 7 | 'client': path.resolve(__dirname, 'client') 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /yapi-base-flow.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yapi-pro/yapi/3bd89c207c1689a2a27a01c495c3c33220fc5d34/yapi-base-flow.jpg -------------------------------------------------------------------------------- /ydoc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "title": "YApi 接口管理平台", 3 | "keywords": "api管理,接口管理,接口文档,api文档", 4 | "author": "ymfe", 5 | "description": "YApi 是高效、易用、功能强大的 api 管理平台,旨在为开发、产品、测试人员提供更优雅的接口管理服务。可以帮助开发者轻松创建、发布、维护 API,YApi 还为用户提供了优秀的交互体验,开发人员只需利用平台提供的接口数据写入工具以及简单的点击操作就可以实现接口的管理", 6 | "plugins": ["search", "img-view"], 7 | "dist": "static/doc", 8 | "pluginsConfig": { 9 | "import-asset": { 10 | "css": "web.css" 11 | } 12 | }, 13 | version: require('./package.json').version, 14 | markdownIt: function(md){ 15 | md.use(require('markdown-it-include'), __dirname) 16 | } 17 | } 18 | --------------------------------------------------------------------------------