Repository: ymm-tech/gods-pen-server Branch: master Commit: 907172f65ff0 Files: 105 Total size: 296.6 KB Directory structure: gitextract_t7mniftx/ ├── .dockerignore ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .gitmodules ├── CHANGELOG.md ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── app/ │ ├── controller/ │ │ ├── category.js │ │ ├── component.js │ │ ├── group.js │ │ ├── groupUser.js │ │ ├── home.js │ │ ├── interface.js │ │ ├── kaptcha.js │ │ ├── mock.js │ │ ├── notice.js │ │ ├── ossupload.js │ │ ├── pages.js │ │ ├── project.js │ │ ├── projectUser.js │ │ ├── proxy.js │ │ ├── resources.js │ │ ├── statistics.js │ │ ├── tags.js │ │ ├── template.js │ │ ├── test.js │ │ └── user.js │ ├── extend/ │ │ ├── application.js │ │ ├── context.js │ │ ├── dingd.js │ │ ├── email/ │ │ │ └── tpl/ │ │ │ ├── active.html │ │ │ ├── notice.html │ │ │ └── password.html │ │ ├── helper.js │ │ ├── noticeMessage.js │ │ ├── token.js │ │ └── tools.js │ ├── middleware/ │ │ ├── login_handler.js │ │ └── response_handler.js │ ├── model/ │ │ ├── category.js │ │ ├── component.js │ │ ├── component_use.js │ │ ├── group.js │ │ ├── group_project.js │ │ ├── group_user.js │ │ ├── history_interface.js │ │ ├── interface.js │ │ ├── interface_draf.js │ │ ├── interface_log.js │ │ ├── interface_user.js │ │ ├── pages.js │ │ ├── pages_history.js │ │ ├── project.js │ │ ├── project_data.js │ │ ├── resTagsRel.js │ │ ├── resources.js │ │ ├── tag.js │ │ ├── tag_interface.js │ │ ├── tags.js │ │ ├── template.js │ │ ├── user.js │ │ ├── user_grade.js │ │ ├── user_login.js │ │ ├── user_login_log.js │ │ ├── user_notice.js │ │ ├── user_notice_type.js │ │ ├── user_project.js │ │ └── valid_code.js │ ├── router.js │ ├── schedule/ │ │ ├── es_delete.js │ │ ├── es_report.js │ │ ├── pu_uv_ratio.js │ │ └── pvcount.js │ └── service/ │ ├── base.js │ ├── category.js │ ├── component.js │ ├── group.js │ ├── groupUser.js │ ├── kaptcha.js │ ├── mock.js │ ├── notice.js │ ├── pages.js │ ├── project.js │ ├── projectUser.js │ ├── resources.js │ ├── statistics.js │ ├── tags.js │ ├── template.js │ └── user.js ├── app.js ├── config/ │ ├── config.app.js │ ├── config.default.js │ ├── config.dev.js │ ├── config.production.js │ └── plugin.js ├── config-parser.js ├── docker-entrypoint.sh ├── index.js ├── package.json ├── process.json ├── sql/ │ └── init.sql └── sub-build/ └── .gitkeep ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ logs/ npm-debug.log node_modules/ coverage/ .idea/ run/ .DS_Store *.swp .vscode/ config/config.dev.js config/config.docker.js .git dist/ sub/**/node_modules/ sub/**/dist/ config.yaml config/config.dev.js.bak config/config.local.js ================================================ FILE: .eslintignore ================================================ coverage ================================================ FILE: .eslintrc.js ================================================ module.exports = { root: true, parserOptions: { sourceType: 'module' }, // https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style extends: 'standard', // required to lint *.vue files plugins: [ 'html' ], // add your custom rules here 'rules': { "no-multi-spaces": 0, "no-eval": 0, "no-new": 0, // allow paren-less arrow functions 'arrow-parens': 0, // allow debugger during development 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, "eqeqeq": 0, // 必须使用全等 "comma-dangle": [ 2, "never" ] //定义数组或对象最后多余的逗号 } } ================================================ FILE: .gitignore ================================================ logs/ npm-debug.log node_modules/ coverage/ .idea/ run/ .DS_Store *.swp .vscode/ config/config.docker.js .travis.yml appveyor.yml config.yaml config/config.dev.js.bak config/config.local.js memo.md ================================================ FILE: .gitmodules ================================================ [submodule "sub/gods-pen-admin"] path = sub/gods-pen-admin url = https://github.com/ymm-tech/gods-pen-admin.git [submodule "sub/gods-pen"] path = sub/gods-pen url = https://github.com/ymm-tech/gods-pen.git ================================================ FILE: CHANGELOG.md ================================================ ## V1.0.0 - 新增flutter 布局 去除动画等 - 页面类型 - 后台设置/更新类型 - 编辑器组件分类型显示 - 编辑器样式设置面板分类型展示 - 组件类型 - 创建时可选择组件类型 - 被正确筛选展示在同类型页面的组件列表 - 手动package.json修改类型 0 web 1 flutter - cli - 指定发布环境 -e - 指定token -t - 组件类型 - 权限 - flutter页面 编辑器开发者以上可保存 - flutter页面 编辑器管理者以上可发布 - flutter页面 历史记录开发者以上可恢复到草稿 - flutter页面 历史记录管理者以上可覆盖发布 - 其他页面无限制,(ml-server可配置) - 画布 - 编辑器支持画布切换 - tview支持不同画布渲染 - nostack模式 - nostack/stack 正常切换 - 子组件stacked 展示正常 - 拖动,方向键等操作符合直觉 - childLimit/leaf - 点击添加组件,组件个数被限制/或禁止 - 拖动添加组件,组件个数被限制/或禁止 - 组件树中移动(入),组件个数被限制/或禁止 - 预览样式/pc端tview样式 - 脚本/组合组件/页面模板优化tag交互 - 脚本保存 - 编辑器内发布,默认私有,可至后台设为公有 - 列表内发布,默认私有,可至后台设为公有 - 后台数据 - 新增团队和项目维度的统计数据、uvpv、页面数 - 新增指定时间内单页面累计独立访客数统计 - hi there - 根节点支持脚本 - 样式换新 - fixed - 编辑器内更优雅的(伪)fixed实现,不闪屏 - pc预览内更优雅的(伪)fixed实现 - bugfix - 开发者布局下,脚本面板未提前打开时点击脚本不能自动打开,且手动切换后,显示空白 - object类型的输入框不显示 - 组件设置过margin时,拖动组件位置计算错误,不符合直觉 - 组件为fixed定位时,通过bottom、right定位,一碰就变成left、top定位,实际显示不能以底部、右侧对齐 ================================================ FILE: Dockerfile ================================================ FROM node:10.13 COPY . /app WORKDIR /app RUN npm install pm2@3.5.0 -g --registry=https://registry.npm.taobao.org RUN make install-lib RUN chmod +x /app/docker-entrypoint.sh ONBUILD COPY ./config.yaml /app ONBUILD RUN node ./config-parser.js ONBUILD RUN make build-all ENTRYPOINT [ "./docker-entrypoint.sh" ] CMD ["pm2-runtime", "start", "process.json", "--env=docker"] EXPOSE 7051 ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2020 Full Truck Alliance Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # 路径配置 admin = sub/gods-pen-admin editor = sub/gods-pen # 安装依赖 install-lib: install-server install-admin install-editor @yarn cache clean && echo '所有依赖安装完成' install-server: @yarn --pure-lockfile --production && echo 'server依赖安装完成' install-admin: @cd $(admin) && yarn --pure-lockfile && echo 'admin依赖安装完成' install-editor: @cd $(editor) && yarn --pure-lockfile && echo 'editor依赖安装完成' # 构建后台 build-admin: cd $(admin) && npm run build-docker && cp -rf ./dist/. ../../sub-build/ # 构建编辑器(含终端页面) build-editor: cd $(editor) && npm run editor:build-docker && npm run client:build-docker && cp -rf ./dist/. ../../sub-build/ # 清理文件 clear-sub: -rm -rf sub # 依次构建 build-all: build-admin build-editor clear-sub @echo '构建完成' # 启动服务 run-server: pm2 start process.json --no-daemon --env=docker # 初始化子模块 init-sub: git submodule update --init --recursive # 更新子模块 update-sub: git submodule update # 构建镜像 docker-build: @read -p '输入镜像tag:' version; docker image build -t godspen\:$$version . ================================================ FILE: README.md ================================================ # 码良服务端

License

## :house: 官网 官网: https://godspen.ymm56.com/ 使用手册: https://godspen.ymm56.com/doc/cookbook/introduce.html 在线体验: https://godspen.ymm56.com/admin/#/home 私有部署: https://godspen.ymm56.com/doc/cookbook/install.html ![](https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/access/ymm_1539588655850.png)

:point_right: `喜欢别忘了加star支持我们,你的支持是我们坚持的动力` :point_left:

## 项目构成 码良系统由3个项目构成,分别是 [gods-pen-server](https://github.com/ymm-tech/gods-pen-server) 码良服务端、 [gods-pen-admin](https://github.com/ymm-tech/gods-pen-admin) 码良管理后台以及于7月份就已经开源的 [gods-pen](https://github.com/ymm-tech/gods-pen) 码良编辑器。 ## 详细部署文档 https://godspen.ymm56.com/doc/cookbook/source.html ## 配置说明 码良依赖 es、redis、mysql、邮件、oss服务,因此需要配置这些服务的信息 config/ 文件夹下存放了开发配置和生产配置 ### 前期准备 除过 mysql 以外,其他服务都开箱即可使用,无需进行初始化之类的操作 **mysql 需要使用 sql/init.sql 来初始化表结构和表数据** ### 开发配置 本地开发时,使用的是配置文件为 config/config.dev.js ### 生成配置 服务器部署时,使用的是配置文件为 config/config.production.js ## 开发 开发 ```bash npm run dev ``` debug(在vscode中端点调试) ```bash npm run debug ``` ## 部署 启动服务 ```bash npm run serve ``` 终止服务 ```bash npm run stop ``` 查看日志 ```bash tail $HOME/logs/master-stdout.log -n 500 -f # stdout tail $HOME/logs/master-stderr.log -n 500 -f # stderr ``` ================================================ FILE: app/controller/category.js ================================================ 'use strict' module.exports = app => { class CategoryController extends app.Controller { * list () { const { ctx } = this const searchRule = {} ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.category.list(ctx.request.body) } * save () { const { ctx } = this const searchRule = { id: { type: 'int', required: false, allowEmpty: true }, name: { type: 'string', max: 20, required: true }, type: { type: 'int', max: 5, required: true }, // desc: { // type: 'string', // max: 64, // allowEmpty: true // } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.category.save(ctx.request.body) } * delete () { const { ctx } = this const createRule = { id: { type: 'int', required: true } } ctx.validate(createRule) yield ctx.service.category.delete(ctx.request.body.id) } } return CategoryController } ================================================ FILE: app/controller/component.js ================================================ 'use strict' module.exports = app => { class ComponentController extends app.Controller { * find () { const { ctx } = this const searchRule = { name: { type: 'string', } } ctx.validate(searchRule) console.log('validate success') var obj = ctx.request.body obj.name = obj.name.replace(/\%/gi, '') if (!obj.name) { throw this.ctx.getError({ msg: '搜索条件为空' }) } obj.uid = ctx.request.uid obj.like = false ctx.body = yield ctx.service.component.list(obj) } * searchByName () { const { ctx } = this const searchRule = {} ctx.validate(searchRule) console.log('validate success') var obj = ctx.request.body obj.name = obj.name.replace(/\%/gi, '') obj.uid = ctx.request.uid obj.like = true ctx.body = yield ctx.service.component.list(obj) } * searchAllStatusByName () { const { ctx } = this const searchRule = {} ctx.validate(searchRule) console.log('validate success') var obj = ctx.request.body obj.name = obj.name.replace(/\%/gi, '') obj.uid = ctx.request.uid obj.like = true obj.status = 'all' ctx.body = yield ctx.service.component.list(obj) } * info () { const { ctx } = this const searchRule = { id: { type: 'string' } } ctx.validate(searchRule, ctx.query) var obj = ctx.query obj.uid = ctx.request.uid ctx.body = yield ctx.service.component.info(obj) } * updata () { const { ctx } = this const searchRule = { desc: { type: 'string', required: true }, visibilitylevel: { type: 'int', required: true }, } ctx.validate(searchRule) var obj = ctx.request.body obj.userId = ctx.request.uid ctx.body = yield ctx.service.component.updata(obj) } * save () { const { ctx } = this const searchRule = { version: { type: 'string', required: true }, name: { type: 'string', required: true }, desc: { type: 'string', required: true }, path: { type: 'string', required: true }, visibilitylevel: { type: 'int', required: true }, } ctx.validate(searchRule) var obj = ctx.request.body obj.userId = ctx.request.uid ctx.body = yield ctx.service.component.save(obj) } /** * 组件使用过一次 */ * useone () { const { ctx } = this const searchRule = { id: { type: 'number', required: true } } ctx.validate(searchRule) var obj = ctx.request.body obj.userId = ctx.request.uid ctx.body = yield ctx.service.component.useone(obj) } * delete () { const { ctx } = this const createRule = { id: { type: 'int', required: true } } ctx.validate(createRule) var obj = ctx.request.body obj.uid = ctx.request.uid yield ctx.service.component.delete(obj) } async import () { const { ctx } = this const searchRule = { id: { type: 'int', required: true }, token: { type: 'string', required: true }, } ctx.validate(searchRule) const obj = ctx.request.body const userId = ctx.request.uid const result = await ctx.service.component.import(obj, userId) ctx.body = result } } return ComponentController } ================================================ FILE: app/controller/group.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { * add () { const { ctx } = this; const createRule = { description: { type: 'string' }, name: { type: 'string', required: true, allowEmpty: false }, }; // 1.校验参数 ctx.validate(createRule); let groupInfo = { description: ctx.request.body.description, logo: ctx.request.body.logo, name: ctx.request.body.name, uid: ctx.request.uid, } ctx.body = yield ctx.service.group.add(groupInfo); } * delete () { const { ctx } = this; const createRule = { id: { type: 'number', required: true } }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = {}; obj.groupId = ctx.request.body.id; obj.uid = ctx.request.uid; yield ctx.service.group.delete(obj); } * list () { const { ctx } = this; // type: 0 || 1 => 0表示所有 1表示我创建的 const createRule = { type: { type: 'string', required: true }, count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = {}; obj.type = ctx.query.type; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.group.list(obj); } * update () { const { ctx } = this; const createRule = { id: { type: 'number', required: true, min: 1 } }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = ctx.request.body; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.group.update(obj); } * info () { const { ctx } = this; // type: 0 || 1 => 0表示所有 1表示我创建的 const createRule = { id: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); const obj = {}; obj.groupId = ctx.query.id; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.group.info(obj); } } return controller; }; ================================================ FILE: app/controller/groupUser.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { * add () { const { ctx } = this; const createRule = { groupId: { type: 'string', required: true }, userId: { type: 'array', required: true }, role: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule); const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.groupUser.add(obj); } * delete () { const { ctx } = this; const createRule = { groupId: { type: 'string', required: true }, userId: { type: 'number', required: true }, }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.groupUser.delete(obj); } * list () { const { ctx } = this; const createRule = { id: { type: 'string', required: true }, count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule,ctx.query); // 查询数据返回 const obj = {}; obj.groupId = ctx.query.id; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.groupUser.list(obj); } * update () { const { ctx } = this; const createRule = { // groupId: { type: 'string', required: true }, userId: { type: 'string', required: true, min: 1 }, role: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.groupUser.update(obj); } } return controller; }; ================================================ FILE: app/controller/home.js ================================================ 'use strict'; module.exports = app => { class HomeController extends app.Controller { * index() { this.ctx.body = 'hi, egg'; this.redirect(`http://cn.bing.com/search?q=1`); } } return HomeController; }; ================================================ FILE: app/controller/interface.js ================================================ 'use strict' module.exports = app => { class HomeController extends app.Controller { * add () { const { ctx } = this const createRule = { method: { type: 'string', required: true, allowEmpty: false }, name: { type: 'string', required: true, allowEmpty: false, max: 50 }, path: { type: 'string', required: true, allowEmpty: false }, projectId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:插入接口数据 const id = yield ctx.service.interface.add(params) ctx.body = { id } } * delete () { const { ctx } = this const createRule = { apiId: { type: 'number', required: true, min: 1 } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:删除接口数据 yield ctx.service.interface.delete(params) } * update () { const { ctx } = this const createRule = { request: { type: 'string' }, response: { type: 'string' }, method: { type: 'string', required: true, allowEmpty: false }, name: { type: 'string', required: true, allowEmpty: false, max: 50 }, path: { type: 'string', required: true, allowEmpty: false }, apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:修改接口数据 yield ctx.service.interface.update(params) } * updataInterfaceMock () { const { ctx } = this const createRule = { content: { type: 'string' }, apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:修改接口数据 yield ctx.service.interface.updataInterfaceMock(params) } /** * 对外的草稿同步接口 */ * apiUpload () { const { ctx } = this const createRule = { projectId: { type: 'number' }, token: { type: 'string' } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body console.log(params) // 2:修改接口数据 yield ctx.service.interface.draf(params) } /** * 查询某项目ID同步的api列表 */ * getApiByProjectId () { const { ctx } = this const createRule = { projectId: { type: 'string' } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body console.log(params) // 2:修改接口数据 const listApis = yield ctx.service.interface.searchApiByProjectId(params) ctx.body = listApis } * batchUpdate () { const { ctx } = this const createRule = { apis: { type: 'array' }, projectId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:批量修改接口数据 yield ctx.service.interface.batchUpdate(params) } * getDetailDrafById () { const { ctx } = this const createRule = { id: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.query) let params = ctx.query // 2:查询接口数据 const api = yield ctx.service.interface.getDetailDrafById(params) ctx.body = api } * draftList () { const { ctx } = this const createRule = { projectId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.query) let params = ctx.query // 2:查询接口数据 const listApis = yield ctx.service.interface.draftList(params) ctx.body = listApis } /** * 废弃接口文档 * @param {*} obj */ * deprecated (obj) { const { ctx } = this const createRule = { id: { type: 'number', required: true }, deprecated: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.request.body) let params = ctx.request.body const listApis = yield ctx.service.interface.deprecated(params) ctx.body = listApis } * list () { const { ctx } = this const createRule = { projectId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.query) let params = ctx.query // 2:查询接口数据 const listApis = yield ctx.service.interface.list(params) ctx.body = listApis } * getInterfaceInfo () { const { ctx } = this const createRule = { apiId: { type: 'string', required: true }, type: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.query) let params = ctx.query params.uid = ctx.request.uid // 2:查询接口数据 const apiInfo = yield ctx.service.interface.getInterfaceInfo(params) ctx.body = apiInfo } * addApiTag () { const { ctx } = this const createRule = { tagName: { type: 'string', required: true, max: 50 }, apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:插入接口数据 const id = yield ctx.service.interface.addApiTag(params) ctx.body = { tagId: id } } * requestRelease () { const { ctx } = this const createRule = { apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:发布接口 yield ctx.service.interface.requestRelease(params) } * getApproveList () { const { ctx } = this let params = {} params.uid = ctx.request.uid // 2:查询接口数据 const listApis = yield ctx.service.interface.getApproveList(params) ctx.body = listApis } * deleteApiTag () { const { ctx } = this const createRule = { tagId: { type: 'number', required: true }, apiId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:删除接口标签 yield ctx.service.interface.deleteApiTag(params) } * auditApi () { const { ctx } = this const createRule = { status: { type: 'number', required: true }, apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:审核接口标签 yield ctx.service.interface.auditApi(params) } * addTag () { const { ctx } = this const createRule = { projectId: { type: 'number', required: true }, name: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:审核接口标签 const id = yield ctx.service.interface.addTag(params) ctx.body = { id } } * updateTag () { const { ctx } = this const createRule = { tagId: { type: 'number', required: true }, name: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:审核接口标签 yield ctx.service.interface.updateTag(params) ctx.body = { id: params.tagId, name: params.name } } * deleteTag () { const { ctx } = this const createRule = { tagId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:删除标签 yield ctx.service.interface.deleteTag(params) } * getTags () { const { ctx } = this const createRule = { projectId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.request.query) let params = ctx.request.query params.uid = ctx.request.uid // 2:删除标签 const tags = yield ctx.service.interface.getTags(params) ctx.body = { tags } } * getPersons () { const { ctx } = this const createRule = { apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.request.query) let params = ctx.request.query params.uid = ctx.request.uid // 2:接口关注人列表 const persons = yield ctx.service.interface.getPersons(params) ctx.body = { persons } } * addPerson () { const { ctx } = this const createRule = { apiId: { type: 'number', required: true }, userId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:关注接口 yield ctx.service.interface.addPerson(params) } * deletePerson () { const { ctx } = this const createRule = { apiId: { type: 'number', required: true }, userId: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:删除关注人 yield ctx.service.interface.deletePerson(params) } * addData () { const { ctx } = this const createRule = { projectId: { type: 'number', required: true }, name: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:审核接口标签 const id = yield ctx.service.interface.addData(params) ctx.body = { id } } * updateData () { const { ctx } = this const createRule = { id: { type: 'number', required: true }, name: { type: 'string', required: true }, content: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:审核接口标签 yield ctx.service.interface.updateData(params) ctx.body = { id: params.id, name: params.name, content: params.content } } * deleteData () { const { ctx } = this const createRule = { id: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:删除标签 yield ctx.service.interface.deleteData(params) } * getDatas () { const { ctx } = this const createRule = { projectId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.request.query) let params = ctx.request.query params.uid = ctx.request.uid // 2:删除标签 const datas = yield ctx.service.interface.getDatas(params) ctx.body = { datas } } * getHistoryList () { const { ctx } = this const createRule = { apiId: { type: 'string', required: true } } // 1.校验参数 ctx.validate(createRule, ctx.query) let params = ctx.query // 2:查询接口数据 const listApis = yield ctx.service.interface.getHistoryList(params) ctx.body = listApis } * updateHistoryStatus () { const { ctx } = this const createRule = { id: { type: 'number', required: true }, type: { type: 'number', required: true }, status: { type: 'number', required: true } } // 1.校验参数 ctx.validate(createRule) let params = ctx.request.body params.uid = ctx.request.uid // 2:修改文档状态 yield ctx.service.interface.updateHistoryStatus(params) } } return HomeController } ================================================ FILE: app/controller/kaptcha.js ================================================ module.exports = app => { class HomeController extends app.Controller { * init (ctx) { const info = { img: '', } info.key = (parseInt((Math.random() * 9000)) + 1000) + ''; info.img = yield ctx.service.kaptcha.getCaptcha(info.key) ctx.session.kaptcha = info.key; this.ctx.body = { img: info.img }; } } return HomeController; }; ================================================ FILE: app/controller/mock.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { * infoOption () { const { ctx } = this; ctx.noWarp = true ctx.body = '' ctx.status = 200 } * info () { const { ctx } = this; let info = {}; if (ctx.params[ 0 ] && ctx.params[ 1 ]) { if (/^\d*$/.test(ctx.params[ 0 ])) { info.id = ctx.params[ 0 ]; info.path = ctx.params[ 1 ]; } } info.type = ctx.request.method.toLowerCase(); // 获取请求类型 ctx.noWarp = true; ctx.body = yield ctx.service.mock.info(info); } * insertOrUpdate () { const { ctx } = this; const createRule = { mockRequest: { type: 'string' }, type: { type: 'number', required: true }, apiId: { type: 'number' }, }; // 1.校验参数 ctx.validate(createRule); const mockInfo = ctx.request.body; mockInfo.uid = ctx.request.uid; ctx.body = yield ctx.service.mock.add(mockInfo); } } return controller; }; ================================================ FILE: app/controller/notice.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { // 拉取最新20条未读消息 * pullMessage () { const { ctx } = this; const obj = {}; obj.uid = ctx.request.uid; obj.readStatus = 1; ctx.body = yield ctx.service.notice.pullMessage(obj); } // 获取站内信消息列表 * listMessage () { const { ctx } = this; const createRule = { type: { type: 'string' }, status: { type: 'string' }, count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = ctx.query; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.notice.listMessage(obj); } // 修改站内信读取状态 * changeReadStatus () { const { ctx } = this; const obj = ctx.request.body; yield ctx.service.notice.changeReadStatus(obj); } // 获取站内信详情 * getMessageInfo () { const { ctx } = this; const obj = ctx.request.query; ctx.body = yield ctx.service.notice.getMessageInfo(obj); } // 获取站内信消息数 * getMessageNums () { const { ctx } = this; const obj = {}; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.notice.getMessageNums(obj); } // 获取消息设置详情 * getNoticeType () { const { ctx } = this; const obj = ctx.request.query; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.notice.getNoticeType(obj); } // 修改消息设置详情 * updateNoticeType () { const { ctx } = this; const obj = ctx.request.body; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.notice.updateNoticeType(obj); } } return controller; }; ================================================ FILE: app/controller/ossupload.js ================================================ const OSS = require('ali-oss') const co = require('co') const sendToWormhole = require('stream-wormhole'); const mime = require('mime'); module.exports = app => { let ossClient = new OSS({ region: app.config.oss.region, accessKeyId: app.config.oss.accessKeyId, accessKeySecret: app.config.oss.accessKeySecret, bucket: app.config.oss.bucket, }) class OssUploadController extends app.Controller { async uploadByUrls () { const { ctx } = this const rule = { urls: { type: 'array', itemType: 'string', rule: { format: /^https?:\/\/.+$/ }, required: true } } ctx.validate(rule) let {urls} = ctx.request.body let transHeaders = ctx.request.headers let status = {} await Promise.all( urls.map(url => { return co(function * () { let assetStream try { assetStream = yield ctx.curl(url, { method: 'get', headers: Object.assign({ 'user-agent': transHeaders['user-agent'], 'accept-encoding': transHeaders['accept-encoding'], 'accept': transHeaders['accept'], }), timeout: 50000, streaming: true }) } catch (e) { console.log(e) } let fileName = url.replace(/\?.+$/, '').split('/').slice(-1)[0] if (!assetStream) { status[fileName] = 0 return } try { let result = yield ossClient.putStream(fileName, assetStream.res) console.log(result) status[fileName] = 1 } catch (e) { console.log(e) status[fileName] = 0 } }) }) ).catch(e => { console.log(e) }) ctx.body = status } /** * f = new FormData() * f.append('files', File) * f.append('files', File) // 支持多个文件 * f.append('base64', 'data:image/png;base64,xxxxxx') * f.append('base64', 'data:image/png;base64,xxxxxx') // 支持多个base64 * */ async uploadFile () { const { ctx } = this const parts = await ctx.multipart() let part let files = [] while ((part = await parts()) != null) { let valid, ext, filedata, result = {} switch (part.length && part.length > 0 ? part[0] : part.fieldname) { case 'files': if (valid = part.filename) { filedata = part ext = mime.getExtension(part.mime) } break case 'base64': if (valid = part[1].match(/^data:(.+);base64,(.+)$/)) { ext = mime.getExtension(valid[1]) filedata = Buffer.from(valid[2], 'base64') } break } if (valid) { try { result = await upload(filedata, [Date.now(), (Math.random() + 1) * 1000000000 | 0].map(v => v.toString(16)).join('') + (ext ? `.${ext}` : '')) } catch (e) { console.log(e) await sendToWormhole(part) } files.push({ originName: part.filename || '', mime: part.mime || '', name: result.name, path: result.path, }) } } ctx.body = files async function upload (filedata, fileName) { let result await co(function * () { result = yield ossClient.put(fileName, filedata) }) return { name: result && result.name, path: result && result.url.replace(/^http(?!s)/, 'https') } } } } return OssUploadController } ================================================ FILE: app/controller/pages.js ================================================ 'use strict' const co = require('co') const PSD = require('psd') const fs = require('fs') const path = require('path') const request = require('request') const rimraf = require('rimraf') const sendToWormhole = require('stream-wormhole') const OSS = require('ali-oss') module.exports = app => { class PagesController extends app.Controller { /** * 记录页面pv */ * pv() { const { ctx } = this var obj = ctx.request.body obj.pageKey = 'key_' + obj.pageKey this.ctx.helper.tools.TSDB.putMetric( 'h5.page.maliang.pv' + obj.pageKey, new Date().getTime(), 1, { uuid: obj.uuid } ) } * pvuv() { const { ctx } = this var obj = ctx.request.body obj.pageKey = 'key_' + obj.pageKey var myTsdbClient = this.ctx.helper.tools.TSDB var getUv = function (array) { var dps = {} for (let index = 0; index < array.length; index++) { let element = array[index]; for (const key in element.dps) { if (dps[key]) { dps[key] += 1 } else { dps[key] = 1 } } } return dps } console.log('获取pv' + obj.pageKey) let pv = yield myTsdbClient.query( obj.startTime, obj.endTime, [{ "aggregator": "sum", "metric": "h5.page.maliang.pv" + obj.pageKey, filters: [], downsample: myTsdbClient.composeDownsampleString('sum', obj.step) }] ) console.log('获取pv完成') if (pv.error || !pv[0]) { pv = {} } else { pv = pv[0].dps } console.log('获取uv' + obj.pageKey) // let uv = yield myTsdbClient.query( // obj.startTime, // obj.endTime, [ { // "aggregator": "sum", // "metric": "h5.page.maliang.pv" + obj.pageKey, // filters: [ { // "type": "wildcard", // "tagk": "uuid", // "filter": "*", // "groupBy": true // } ], // downsample: myTsdbClient.composeDownsampleString('sum', obj.step) // } ] // ) // if (uv.error) { // uv = {} // } else { // uv = getUv(uv) // } ctx.body = { pv: pv, uv: {} } } * save() { const { ctx } = this const searchRule = { name: { type: 'string', max: 50, required: true, allowEmpty: false } } ctx.validate(searchRule) console.log('validate success') var obj = ctx.request.body delete obj.updateTime delete obj.createTime if (obj.id) { const role = yield ctx.service.base.getUserRole(ctx.request.uid, obj.projectId) var permissionLimit = this.app.config.permissionLimit[obj.type] || {} if (permissionLimit.save && !(role <= permissionLimit.save)) throw this.ctx.getError({ msg: '您缺少“开发者”或更高权限,无法保存,请联系该页面管理人员添加权限' }) } ctx.body = yield ctx.service.pages.save(obj) } * list() { const { ctx } = this const searchRule = { projectId: { type: 'int', required: true, allowEmpty: false }, status: { type: 'int', required: false, allowEmpty: true } } ctx.validate(searchRule) const query = ctx.request.body; query.uid = ctx.request.uid; ctx.body = yield ctx.service.pages.list(query) } * publiclist() { const { ctx } = this const searchRule = {} ctx.validate(searchRule) ctx.body = yield ctx.service.pages.publiclist(ctx.request.body) } * info() { const { ctx } = this const createRule = { id: { type: 'string', required: true } } ctx.validate(createRule) var body = ctx.request.body body.uid = ctx.request.uid; var info = yield ctx.service.pages.info(body) ctx.body = info } async detail() { const { ctx } = this const createRule = { pageKey: { type: 'string', required: true }, scene: { type: 'string', required: false, } } ctx.validate(createRule) var body = ctx.request.body var retval var fromCache var role switch (body.scene) { case 'render': retval = await this.ctx.app.getCache(body.pageKey) // string fromCache = !ctx.helper.tools.isEmpty(retval) && !ctx.helper.tools.isEmptyObject(retval) if (fromCache) break case 'preview': case 'copy': case 'edit': case 'history': case 'history_preview': retval = await co(function* () { return yield ctx.service.pages.detail(ctx.request.body) }) if (['render', 'preview', 'history_preview'].includes(body.scene)) { retval.content = ctx.helper.tools.nodeTreeScriptTransform(retval.content) } if (body.scene == 'edit') { role = await co(function* () { return yield ctx.service.base.getUserRole(ctx.request.uid, retval.projectId) }) if (role >= 4) throw ctx.getError({ status: 403, msg: '您没有此页面的权限' }) } if (body.scene == 'render') { this.ctx.app.setCache(body.pageKey, retval) } } if (fromCache) { ctx.noWarp = 1 ctx.body = `{"code":1,"msg":"success","data":${retval}}` console.info(`从缓存中获取详情 ${body.scene}`) } else { ctx.body = retval console.info(`从数据库中获取详情 ${body.scene}`) } } *changeStatus() { const { ctx } = this const searchRule = { id: { type: 'int', required: true, allowEmpty: false }, status: { type: 'int', required: true, allowEmpty: false } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.pages.changeStatus(ctx.request.body) } * setHomePage() { const { ctx } = this const searchRule = { id: { type: 'int', required: true, allowEmpty: false } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.pages.setHomePage(ctx.request.body) } * delete() { const { ctx } = this const createRule = { id: { type: 'int', required: true } } ctx.validate(createRule) const query = { id: ctx.request.body.id, uid: ctx.request.uid } yield ctx.service.pages.delete(query) } * publish() { const { ctx } = this const createRule = { id: { type: 'int', required: true }, projectId: { type: 'int', required: true }, pageKey: { type: 'string', required: true }, content: { type: 'string', required: true, allowEmpty: false } } ctx.validate(createRule) var body = ctx.request.body body.uid = ctx.request.uid; const role = yield ctx.service.base.getUserRole(ctx.request.uid, body.projectId) var permissionLimit = this.app.config.permissionLimit[body.type] || {} if (permissionLimit.publish && !(role <= permissionLimit.publish)) throw this.ctx.getError({ msg: '您缺少“管理员”或更高权限,无法发布,请联系该页面管理人员' }) yield ctx.service.pages.publish(body) this.ctx.app.delCache(body.pageKey) // 更新缓存 实际是清除缓存,待下次请求自动进行缓存 } * count() { const { ctx } = this var num = yield ctx.service.pages.count() ctx.body = { count: num } } * history() { const { ctx } = this const searchRule = { pageId: { type: 'string', required: true, allowEmpty: false } } ctx.validate(searchRule, ctx.query) var body = ctx.query body.uid = ctx.request.uid ctx.body = yield ctx.service.pages.history(body) } * getNameBykeys() { const { ctx } = this let rule = { ids: { type: 'array', itemType: 'string', } } ctx.validate(rule) let names = yield ctx.service.pages.getNameBykeys(ctx.request.body) ctx.body = { names } } * historyDelete() { const { ctx } = this const searchRule = { id: { type: 'string', required: true, allowEmpty: false } } ctx.validate(searchRule, ctx.query) var body = ctx.query body.uid = ctx.request.uid ctx.body = yield ctx.service.pages.deleteHistory(body) } * historyPublish() { const { ctx } = this const createRule = { id: { type: 'int', required: true }, projectId: { type: 'int', required: true }, type: { type: 'int', required: false }, pageKey: { type: 'string', required: true }, content: { type: 'string', required: true, allowEmpty: false } } ctx.validate(createRule) var body = ctx.request.body body.uid = ctx.request.uid; const role = yield ctx.service.base.getUserRole(ctx.request.uid, body.projectId) var permissionLimit = this.app.config.permissionLimit[body.type] || {} if (permissionLimit.publish && !(role <= permissionLimit.publish)) throw this.ctx.getError({ msg: '您缺少“管理员”或更高权限,无法发布,请联系该页面管理人员' }) yield ctx.service.pages.historyPublish(body) this.ctx.app.delCache(body.pageKey) // 更新缓存 实际是清除缓存,待下次请求自动进行缓存 } * historyToDraft() { const { ctx } = this const createRule = { id: { type: 'int', required: true }, projectId: { type: 'int', required: true }, type: { type: 'int', required: false }, pageKey: { type: 'string', required: true }, content: { type: 'string', required: true, allowEmpty: false } } ctx.validate(createRule) var body = ctx.request.body body.uid = ctx.request.uid; const role = yield ctx.service.base.getUserRole(ctx.request.uid, body.projectId) var permissionLimit = this.app.config.permissionLimit[body.type] || {} if (permissionLimit.save && !(role <= permissionLimit.save)) throw this.ctx.getError({ msg: '您缺少“开发者”或更高权限,无法保存,请联系该页面管理人员添加权限' }) yield ctx.service.pages.historyToDraft(body) } * updateFork() { const createRule = { id: { type: 'int', required: true }, } const ctx = this.ctx ctx.validate(createRule) yield ctx.service.pages.updateFork(ctx.request.body) } /** * psd 文件解析 */ async psdToPage() { const { ctx } = this; let currentPathDir = `psd_image` const SERVER_PATH = './' fs.existsSync(path.join(SERVER_PATH, currentPathDir)) || fs.mkdirSync(path.join(SERVER_PATH, currentPathDir)) const query = ctx.request.body || {} const type = /^https?:\/\//.test(query.url) ? 'url' : 'file' if (type === 'url' && !ctx.helper.tools.isSafeUrl(query.url)) { ctx.body = 'hello world' return } let stream = type === 'url' ? request(query.url) : await ctx.getFileStream() let filename = type === 'url' ? (query.url.match(/\/([^/]+)$/) || [ '', Date.now().toString(32) + Math.random().toString(32).slice(2, 4) ])[1] : stream.filename // stream对象也包含了文件名,大小等基本信息 // 创建文件写入路径 let target = path.join(SERVER_PATH, currentPathDir + '/' + filename) const result = await new Promise((resolve, reject) => { // 创建文件写入流 const remoteFileStrem = fs.createWriteStream(target) let errFlag const onerror = (err) => { errFlag = true sendToWormhole(stream) remoteFileStrem.destroy() console.error(err) reject(err) } // url 读取流校验 if (type === 'url') { stream = stream.on('response', (response) => { const isPsd = response.headers['content-type'] === 'image/vnd.adobe.photoshop' if (!isPsd) onerror(new Error('不是 psd 资源')) }) .on('error', err => onerror(err)) } // 以管道方式写入流 stream.pipe(remoteFileStrem) // 监听error事件 remoteFileStrem.on('error', err => onerror(err)) // 监听写入完成事件 remoteFileStrem.on('finish', () => { if (errFlag) return resolve({ filename, name: type === 'url' ? filename : stream.fields.name }) }) }) console.log(result) if (!result.filename) return false let psd = await PSD.open(path.join(SERVER_PATH, currentPathDir + '/' + filename)) let descendantsList = psd.tree().descendants() descendantsList.reverse() let psdSourceList = [] for (var i = 0; i < descendantsList.length; i++) { if (descendantsList[i].isGroup()) continue if (!descendantsList[i].visible) continue try { await descendantsList[i].saveAsPng(path.join(SERVER_PATH, currentPathDir + `/${i}.png`)) const fName = `ml/psd-img/${[Date.now(), (Math.random() + 1) * 1000000000 | 0].map(v => v.toString(16)).join('')}.png` let src = await this.upload(fs.createReadStream(SERVER_PATH + `psd_image/${i}.png`), fName) console.log('src', src) psdSourceList.push({ ...descendantsList[i].export(), type: 'picture', // imageSrc: SERVER_PATH + `psd_image/${i}.png`, // path: binaryToBase(a) src }) rimraf(SERVER_PATH + `psd_image/${i}.png`, function (err) { // 删除当前目录下的 test.txt console.log(err) }) } catch (e) { // 转换不出来的图层先忽 console.log(e) continue } } rimraf(SERVER_PATH + `psd_image/`, function (err) { // 删除当前目录 // console.log(err) }); ctx.body = { elements: psdSourceList, document: psd.tree().export().document } } async upload(filedata, fileName) { let ossClient = new OSS({ region: app.config.oss.region, accessKeyId: app.config.oss.accessKeyId, accessKeySecret: app.config.oss.accessKeySecret, bucket: app.config.oss.bucket, }) let result await co(function* () { result = yield ossClient.put(fileName, filedata) }) // console.log('result', result) return (result && result.url.replace(/^http(?!s)/, 'https')) || '' } async featuringPages () { const createRule = { month: { type: 'int', }, } const ctx = this.ctx ctx.validate(createRule) const list = await ctx.service.pages.featuringPages({ monthBefore: ctx.request.body.month }) ctx.body = list } async updateFeatured () { const createRule = { id: { type: 'int', required: false, }, value: { type: 'int', required: true, }, key: { type: 'string', required: false, } } const ctx = this.ctx ctx.validate(createRule) const res = ctx.request.body || {} const result = await ctx.service.pages.updateFeatured({ id: res.id, uid: ctx.request.uid, value: res.value, key: res.key }) ctx.body = { success: result } } } return PagesController } ================================================ FILE: app/controller/project.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { * add () { const { ctx } = this; const createRule = { name: { type: 'string', required: true, allowEmpty: false }, groupId: { type: 'number' }, }; // 1.校验参数 ctx.validate(createRule); const projectInfo = ctx.request.body; projectInfo.uid = ctx.request.uid; ctx.body = yield ctx.service.project.add(projectInfo); } * delete () { const { ctx } = this; const createRule = { id: { type: 'number', required: true } }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = {}; obj.projectId = ctx.request.body.id; obj.uid = ctx.request.uid; yield ctx.service.project.delete(obj); } * list () { const { ctx } = this; const createRule = { count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = {}; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.project.list(obj); } * update () { const { ctx } = this; const createRule = { name: { type: 'string', required: true, allowEmpty: false }, groupId: { type: 'number' }, id: { type: 'number' }, }; // 1.校验参数 ctx.validate(createRule); const projectInfo = ctx.request.body; projectInfo.uid = ctx.request.uid; yield ctx.service.project.update(projectInfo); } * info () { const { ctx } = this; const createRule = { id: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); const obj = {}; obj.projectId = ctx.query.id; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.project.info(obj); } * groupProject () { const { ctx } = this; const createRule = { count: { type: 'string' }, start: { type: 'string' }, groupId: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = ctx.query; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.project.groupProjects(obj); } * favorateProject () { const { ctx } = this; const createRule = { id: { type: 'number', required: true, }, }; // 1.校验参数 ctx.validate(createRule); const projectInfo = ctx.request.body; projectInfo.uid = ctx.request.uid; yield ctx.service.project.favorateProject(projectInfo); } * cancelFavorateProject () { const { ctx } = this; const createRule = { id: { type: 'number', required: true, }, }; // 1.校验参数 ctx.validate(createRule); const projectInfo = ctx.request.body; projectInfo.uid = ctx.request.uid; yield ctx.service.project.cancelFavorateProject(projectInfo); } * getFavorateProject () { const { ctx } = this; const createRule = { count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = {}; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.project.getFavorateProject(obj); } } return controller; }; ================================================ FILE: app/controller/projectUser.js ================================================ 'use strict'; module.exports = app => { class controller extends app.Controller { * add () { const { ctx } = this; const createRule = { projectId: { type: 'string', required: true }, userId: { type: 'array', required: true }, role: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule); const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.projectUser.add(obj); } * delete () { const { ctx } = this; const createRule = { projectId: { type: 'string', required: true }, userId: { type: 'number', required: true }, }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.projectUser.delete(obj); } * list () { const { ctx } = this; const createRule = { projectId: { type: 'string', required: true }, count: { type: 'string' }, start: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule, ctx.query); // 查询数据返回 const obj = {}; obj.projectId = ctx.query.projectId; obj.uid = ctx.request.uid; ctx.body = yield ctx.service.projectUser.list(obj); } * update () { const { ctx } = this; const createRule = { projectId: { type: 'string', required: true }, userId: { type: 'string', required: true, min: 1 }, role: { type: 'string', required: true }, }; // 1.校验参数 ctx.validate(createRule); // 2:验证参数 const obj = ctx.request.body; obj.uid = ctx.request.uid; yield ctx.service.projectUser.update(obj); } } return controller; }; ================================================ FILE: app/controller/proxy.js ================================================ const fs = require('fs') const Controller = require('egg').Controller const ua = require('random-ua') const mime = require('mime') const sendToWormhole = require('stream-wormhole') const util = require('util') const request = util.promisify(require('request')) class ProxyController extends Controller { async imgCorsProxy () { const { ctx } = this const url = ctx.query['url'] const responseType = ctx.query['responseType'] if (!ctx.helper.tools.isSafeUrl(url)) { ctx.body = 'hello world' return } let useStream = responseType == 'blob' const result = await ctx.curl(url, { method: 'GET', streaming: useStream }) const contentType = result.res.headers['content-type'] const isImage = /^image\//.test(contentType) if (!isImage) { if (useStream) sendToWormhole(result.res) ctx.body = `"${url}" is not a image resource` return } if (useStream) { ctx.type = contentType ctx.body = result.res } else { ctx.body = `data:${contentType};base64,${Buffer.from( result.res.data, 'binary' ).toString('base64')}` } ctx.set('cache-control', 'max-age=31536000') } async transparentProxy () { const { ctx } = this const rule = { url: { type: 'string', required: true }, headers: { type: 'object', }, method: { type: 'string' }, body: { type: 'object', } } ctx.validate(rule) let {url, headers, method, body} = ctx.request.body let transHeaders = ctx.request.headers const result = await ctx.curl(url, { method: (method || 'get').toUpperCase(), data: body, headers: Object.assign({ 'user-agent': transHeaders['user-agent'], 'accept-encoding': transHeaders['accept-encoding'], 'accept': transHeaders['accept'], 'referer': transHeaders['referer'], }, headers), timeout: 5000, streaming: true }) Object.entries(result.res.headers).map(([key, val]) => { if (key == 'access-control-allow-origin' || key == 'content-security-policy') return ctx.set(key, val) }) ctx.body = result.res } async word2html () { const { ctx } = this const part = ctx.request.files && ctx.request.files[0] || {} if (part.fieldname !== 'file' || !['docx', 'doc'].includes(mime.getExtension(part.mime))) { ctx.cleanupRequestFiles() throw this.ctx.getError({ msg: '仅支持docx或者doc类型文件' }) } const options = { 'method': 'POST', 'url': 'https://s6.aconvert.com/convert/convert-batch-win.php', 'headers': { 'Origin': 'https://www.aconvert.com', 'Accept-Language': 'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7', 'User-Agent': ua.generate(), 'Content-Type': 'multipart/form-data', 'Accept': '*/*', 'Referer': 'https://www.aconvert.com/cn/document/doc-to-html/', 'Connection': 'keep-alive', 'DNT': '1' }, formData: { 'file': fs.createReadStream(part.filepath), 'targetformat': 'html', 'code': '86000', 'filelocation': 'local' } } const result = await request(options) .then(({body}) => JSON.parse(body)) .catch(e => console.error(e)) if (!result || !result.filename) { ctx.cleanupRequestFiles() throw this.ctx.getError({ msg: '文档解析失败' }) } ctx.cleanupRequestFiles() const html = await ctx.curl(`https://s6.aconvert.com/convert/p3r68-cdx67/${result.filename}`, {dataType: 'text'}) .then(v => v.data) .catch(e => { console.error(e) return '' }) ctx.body = { html } } } module.exports = ProxyController ================================================ FILE: app/controller/resources.js ================================================ 'use strict' module.exports = app => { class ResourcesController extends app.Controller { * info () { const { ctx } = this const searchRule = { id: { type: 'string' } } ctx.validate(searchRule, ctx.query) var obj = ctx.query obj.uid = ctx.request.uid ctx.body = yield ctx.service.resources.info(obj) } * list () { const { ctx } = this const searchRule = { name: { type: 'string', max: 50, required: false, allowEmpty: true }, categoryId: { type: 'int', required: false, allowEmpty: true }, page: { type: 'int', required: false, allowEmpty: true }, pageSize: { type: 'int', required: false, allowEmpty: true } } ctx.validate(searchRule) console.log('validate success') var obj = ctx.request.body obj.uid = ctx.request.uid ctx.body = yield ctx.service.resources.list(obj) } * save () { const { ctx } = this const searchRule = { id: { type: 'int', required: false, allowEmpty: true }, name: { type: 'string', allowEmpty: true, required: false }, icon: { type: 'string', allowEmpty: true, required: false }, content: { type: 'string', allowEmpty: true, required: false }, tags: { type: 'array', required: false } } ctx.validate(searchRule) var obj = ctx.request.body obj.uid = ctx.request.uid ctx.body = yield ctx.service.resources.save(obj) } * delete () { const { ctx } = this const createRule = { id: { type: 'int', required: true } } ctx.validate(createRule) var obj = ctx.request.body obj.uid = ctx.request.uid yield ctx.service.resources.delete(obj) } * addUseCount () { const { ctx } = this const rule = { id: { type: 'int', required: true } } ctx.validate(rule) var obj = ctx.request.body ctx.body = yield ctx.service.resources.addUseCount(obj) } } return ResourcesController } ================================================ FILE: app/controller/statistics.js ================================================ const Controller = require('egg').Controller const co = require('co') class StatisticsController extends Controller { async report () { const ctx = this.ctx const query = ctx.request.query /* 允许的 query构造,除此以外被放到 extras 字段上 ** { ** finger_print: 'xxxxx', env: duration: ** app_id: 'tview', ** page_id: "page_7", ** action: "pageview", utm_campaign ** label: "red", html: '' // 代码片段 ** } */ let FIELDS = ['finger_print', 'env', 'app_id', 'page_id', 'action', 'utm_campaign', 'label', 'html', 'duration', '_t'] let filteredQuery = Object.keys(query) .filter(k => FIELDS.indexOf(k) > -1) .reduce((o, k) => { if (k != '_t' && k != 'duration') o[k] = query[k] if (k == 'duration') o['durations'] = +query[k] || 0 // 类型冲突,重建索引太慢,只能换个字段名了 delete query[k] return o }, {}) let report = { referrer: ctx.get('referrer'), create_time: Date.now(), ip: ctx.ip || null, ua: ctx.get('User-Agent'), ...filteredQuery } if (Object.keys(query).length > 0) { report.extras = JSON.stringify(query) } if (report.action === 'pageview' && report.app_id === 'tview') { this.app.addPV('report') } ctx.service.statistics.report(report) ctx.status = 200 ctx.body = JSON.stringify(report, null, 2) } async getPUV () { const ctx = this.ctx let rule = { pageId: { type: 'string', required: false }, pageIds: { type: 'array', required: false }, // default tview appId: { type: 'string', required: false }, timePeriod: { type: 'array', itemType: 'int', }, interval: { type: 'string', required: false } } ctx.validate(rule) let result = await ctx.service.statistics.getPUV(ctx.request.body) ctx.body = result } async getPages () { const ctx = this.ctx let rule = { timePeriod: { type: 'array', itemType: 'int', }, // default tview appId: { type: 'string', required: false }, size: { type: 'int', required: false } } ctx.validate(rule) let result = await ctx.service.statistics.getPages(ctx.request.body) ctx.body = result } async getUtms () { const ctx = this.ctx let rule = { timePeriod: { type: 'array', itemType: 'int', }, pageId: { type: 'string', required: true }, // default tview appId: { type: 'string', required: false }, size: { type: 'int', required: false } } ctx.validate(rule) let result = await ctx.service.statistics.getUtms(ctx.request.body) ctx.body = result } async getViewTime () { const ctx = this.ctx let rule = { appId: { type: 'string', required: true }, pageId: { type: 'string', allowEmpty: true, required: false }, timePeriod: { type: 'array', itemType: 'int', }, utm: { type: 'string', allowEmpty: true, required: false }, avgonly: { type: 'int', allowEmpty: true, required: false } } ctx.validate(rule) let result = await ctx.service.statistics.getViewTime(ctx.request.body) ctx.body = result } async getActions () { const ctx = this.ctx let rule = { // default tview appId: { type: 'string', required: false }, pageId: { type: 'string', required: true }, timePeriod: { type: 'array', itemType: 'int', }, size: { type: 'int', required: false } } ctx.validate(rule) let result = await ctx.service.statistics.getActions(ctx.request.body) ctx.body = result } async actionTrack () { const ctx = this.ctx let rule = { // default tview appId: { type: 'string', required: false }, pageId: { type: 'string', required: true }, timePeriod: { type: 'array', itemType: 'int', }, size: { type: 'int', required: false }, interval: { type: 'string', required: false } } ctx.validate(rule) let result = await ctx.service.statistics.actionTrack(ctx.request.body) ctx.body = result } async getTodayOutline () { const ctx = this.ctx let rule = { appId: { type: 'string', required: true } } ctx.validate(rule) let result = await ctx.service.statistics.getTodayOutline(ctx.request.body) ctx.body = result } async getProjectReport () { const ctx = this.ctx let rule = { projectId: { type: 'int', required: true }, timePeriod: { type: 'array', itemType: 'int', }, interval: { type: 'string', required: false } } ctx.validate(rule) let params = ctx.request.body let pages = await co(function * () { return yield ctx.service.pages.list(params) }) let pageKeys = pages.map(p => p.key) let puv if (!pageKeys || !pageKeys.length) puv = {sum: {}, histogram: []} else puv = await ctx.service.statistics.getPUV({pageIds: pageKeys, timePeriod: params.timePeriod, interval: params.interval}) ctx.body = Object.assign({count: pages.length}, puv) } async getGroupReport () { const ctx = this.ctx let rule = { groupId: { type: 'number', required: true }, timePeriod: { type: 'array', itemType: 'int', }, interval: { type: 'string', required: false } } ctx.validate(rule) let params = ctx.request.body let projects = await co(function * () { return yield ctx.service.project.groupProjects(params) }) let projectIds = projects.map(p => p.id) params.projectId = projectIds let pages = await co(function * () { return yield ctx.service.pages.list(params) }) let pageKeys = pages.map(p => p.key) let puv if (!pageKeys || !pageKeys.length) puv = {sum: {}, histogram: []} else puv = await ctx.service.statistics.getPUV({pageIds: pageKeys, timePeriod: params.timePeriod, interval: params.interval}) ctx.body = Object.assign({pageCount: pages.length, projectCount: projects.length}, puv) } } module.exports = StatisticsController ================================================ FILE: app/controller/tags.js ================================================ 'use strict' module.exports = app => { class TagsController extends app.Controller { * list () { const { ctx } = this const searchRule = { name: { type: 'string', max: 50, required: false, allowEmpty: true }, categoryId: { type: 'int', required: false, allowEmpty: true } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.tags.list(ctx.request.body) } * add () { const { ctx } = this const searchRule = { name: { type: 'string', max: 50, required: false, allowEmpty: true }, categoryId: { type: 'int', required: false, allowEmpty: true } } ctx.validate(searchRule) ctx.body = yield ctx.service.tags.add(ctx.request.body) } /** * 组件使用过一次 */ * useone () { const { ctx } = this const searchRule = { id: { type: 'number', required: true } } ctx.validate(searchRule) var obj = ctx.request.body obj.userId = ctx.request.uid ctx.body = yield ctx.service.tags.useone(obj) } } return TagsController } ================================================ FILE: app/controller/template.js ================================================ 'use strict' module.exports = app => { class TemplateController extends app.Controller { * list () { const { ctx } = this const searchRule = { id: { type: 'int', max: 5, required: false, allowEmpty: true }, name: { type: 'string', max: 20, allowEmpty: true, required: false }, categoryId: { type: 'int', max: 5, required: false, allowEmpty: true } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.template.list(ctx.request.body) } * detail () { const { ctx } = this const searchRule = { id: { type: 'int', required: true } } ctx.validate(searchRule) ctx.body = yield ctx.service.template.detail(ctx.request.body) } * save () { const { ctx } = this const searchRule = { id: { type: 'int', required: false, allowEmpty: true }, name: { type: 'string', max: 20, required: true }, categoryId: { type: 'int', max: 5, allowEmpty: true, required: false }, content: { type: 'string', allowEmpty: true }, // image: { // type: 'string', // allowEmpty: true, // required: false // }, // desc: { // type: 'string', // max: 64, // required: false, // allowEmpty: true // } } ctx.validate(searchRule) console.log('validate success') ctx.body = yield ctx.service.template.save(ctx.request.body) } * delete () { const { ctx } = this const createRule = { id: { type: 'int', required: true } } ctx.validate(createRule) yield ctx.service.template.delete(ctx.request.body.id) } } return TemplateController } ================================================ FILE: app/controller/test.js ================================================ 'use strict'; module.exports = app => { class HomeController extends app.Controller { * index() { this.ctx.body = 'hi, egg'; } } return HomeController; }; ================================================ FILE: app/controller/user.js ================================================ 'use strict'; var OSS = require('ali-oss'); var moment = require('moment') var crypto = require('crypto') const co = require('co') module.exports = app => { function getAdminUrl (ctx, path) { if (/https?:\/\//.test(app.config.ADMIN_PATH)) return `${app.config.ADMIN_PATH.replace(/\/$/g, '')}/${path}` let protocol = 'http://' const domain = (({header, host}) => { let referer = header.referer if (/^https/.test(referer)) protocol = 'https://' let d = host try { d = referer.replace(/^(https?:)?\/\//, '').split('/')[0] } catch (e) { console.log(e) } return d })(ctx) return `${protocol}${domain}/${app.config.ADMIN_PATH.replace(/^\/|\/$/g, '')}/${path}`; } class HomeController extends app.Controller { * login () { const { ctx } = this; const createRule = { account: { type: 'string' }, kaptcha: { type: 'string' }, password: { type: 'string' }, }; // 1.校验参数 ctx.validate(createRule); // 2:校验验证码 if (ctx.session.kaptcha != ctx.request.body.kaptcha) { throw ctx.getError({ msg: '验证码不正确' }); } // 3:校验用户名及密码 yield ctx.service.user.login(ctx.request.body); } * oauthCode () { const { ctx } = this; const createRule = { channel: { type: 'string', required: true, allowEmpty: false }, }; // 1.校验参数 ctx.validate(createRule); let url = '' if (ctx.request.body.channel === 'github') { url = `https://github.com/login/oauth/authorize?client_id=${app.config.github.clientId}&redirect_uri=${getAdminUrl(ctx, 'login.html?from=github')}` } if (!url) { throw ctx.getError({ msg: '暂不支持该第三方登录' }); return } console.log(url) // 2.跳转第三方登录页面 this.ctx.body = { redirect: url } } async oauthLogin () { const { ctx } = this; const createRule = { channel: { type: 'string', required: true, allowEmpty: false }, code: { type: 'string', required: true, allowEmpty: false } }; let params = ctx.request.body // 1.校验参数 ctx.validate(createRule); let user = {} if (params.channel == 'github') { console.log('a') // 获取token let accessToken = await app.curl('https://github.com/login/oauth/access_token', { method: 'POST', data: { client_id: app.config.github.clientId, client_secret:app.config.github.clientSecret, code: params.code, redirect_uri: '', state: '' } }); let token = '' try { token = accessToken.data.toString().match(/access_token=([^&]+)/)[1] } catch (e) { throw ctx.getError({ msg: '第三方账号授权失败' }); } if (!token) { throw ctx.getError({ msg: '第三方账号授权失败' }); } let info = (await app.curl(`https://api.github.com/user?access_token=${token}`, { method: 'GET', dataType: 'json', headers: { Authorization: token }, }) || {}).data user = { oauth: `github_${info.id}`, name: info.name, email: info.email } } // 3:登录 await co(function * () { yield ctx.service.user.oauthLogin(user); }) } * register () { const { ctx } = this; const registerRule = { email: 'email', password: { type: 'string', required: true, allowEmpty: false }, kaptcha: { type: 'string', min: 4, max: 4 }, name: { type: 'string', min: 2, max: 32 }, }; ctx.validate(registerRule); // 2:校验验证码 if (ctx.session.kaptcha != ctx.request.body.kaptcha) { throw ctx.getError({ msg: '验证码不正确' }); } let obj = ctx.request.body; obj.emailStatus = 1; yield ctx.service.user.register(obj); } * activeEmail () { const { ctx } = this; const activeEmailRule = { code: { type: 'string', required: true, allowEmpty: false } }; ctx.validate(activeEmailRule, ctx.request.body); // 解析code码 const showCode = ctx.helper.tools.decrypt(ctx.request.body.code, app.config.appConfig.desKey); if (!ctx.helper.tools.isEmpty(showCode)) { const params = {}; const index = showCode.indexOf('||'); params.email = showCode.substr(0, index); params.code = showCode.substr(index + 2); const message = yield ctx.service.user.activeEmail(params); if (ctx.helper.tools.isEmpty(message)) { // 激活成功,跳转制定页面 this.ctx.redirect(getAdminUrl(this.ctx, 'index.html')); } else { ctx.body = message; } } else { throw ctx.getError({ msg: '参数不合法' }); } } * sendActiveEmail () { const { ctx } = this; if (ctx.request.uid > 0) { yield ctx.service.user.sendActiveEmail(ctx.request.uid); } else { throw ctx.getError({ msg: '您的登录已失效' }); } } // 忘记密码,发送邮件 * sendEmail () { const { ctx } = this; const sendEmailRule = { email: 'email' }; // 1.校验参数 ctx.validate(sendEmailRule); yield ctx.service.user.sendEmail(ctx.request.body.email); } * info () { const { ctx } = this; if (ctx.request.uid > 0) { let info = yield ctx.service.user.info(ctx.request.uid); if (ctx.query.uid > 0 && info.emailStatus !== 2) throw ctx.getError({ msg: '您的账户未激活,暂不能查看其他项目成员的信息' }); if (ctx.query.uid > 0) info = yield ctx.service.user.info(ctx.query.uid); const user = {}; user.name = info.name; user.userId = info.id; user.photo = info.photo; user.telephone = info.telephone; user.email = info.email; user.role = info.role; user.security = ctx.query.uid > 0 ? '' : info.security; user.emailStatus = info.emailStatus; user.projectNums = info.projectCount; ctx.body = user; } else { throw ctx.getError({ msg: '您的登录已失效' }); } } * logout () { this.ctx.cookies.set(this.app.config.appConfig.accessTokenKey, '', { maxAge: 0 }); } * edit () { const { ctx } = this; const editRule = { name: { type: 'string', required: true, allowEmpty: false, min: 2, max: 32 }, telephone: { type: 'string', allowEmpty: true, min: 0, max: 11 }, email: { type: 'string', allowEmpty: true }, }; // 1.校验参数 ctx.validate(editRule); yield ctx.service.user.edit(ctx.request.body); } * updatePassword () { const { ctx } = this; const passwordRule = { targetPassword: { type: 'string', required: true, allowEmpty: false }, password: { type: 'string', required: true, allowEmpty: false }, }; // 1.校验参数 ctx.validate(passwordRule); ctx.request.body.uid = ctx.request.uid yield ctx.service.user.updatePassword(ctx.request.body); } // 忘记密码,重置接口 * newUpdatePassword () { const { ctx } = this; const passwordRule = { code: { type: 'string', required: true, allowEmpty: false }, }; ctx.validate(passwordRule, ctx.request.body); // 解析code码 const showCode = ctx.helper.tools.decrypt(ctx.request.body.code, app.config.appConfig.desKey); if (!ctx.helper.tools.isEmpty(showCode)) { const params = {}; const index = showCode.indexOf('||'); params.email = showCode.substr(0, index); params.code = showCode.substr(index + 2); params.password = ctx.request.body.password yield ctx.service.user.newUpdatePassword(params); } else { throw ctx.getError({ msg: '参数不合法' }); } } * forgetPassword () { const { ctx } = this; const forgetPasswordRule = { email: 'email' }; // 1.校验参数 ctx.validate(forgetPasswordRule); yield ctx.service.user.forgetPassword(ctx.request.body); } * search () { const { ctx } = this; const searchRule = { key: { type: 'string', required: true, max: 50 } }; // 1.校验参数 ctx.validate(searchRule, ctx.query); const data = yield ctx.service.user.search(ctx.query); ctx.body = data; } * getTocken () { console.log('访问ip:', this.ctx.ip) const valid = this.ctx.helper.tools.ossConfigValid(app.config.oss) if (!valid) throw this.ctx.getError({ msg: '您未正确配置 oss 服务的相关字段,无法使用对象存储服务' }) var expire_syncpoint = new Date().getTime() + 60 * 1000 var policyToken = { accessKeyId: app.config.oss.accessKeyId, accessKeySecret: app.config.oss.accessKeySecret, host: app.config.oss.host, expire_time: moment(expire_syncpoint).toISOString(), upload_dir: `${app.config.oss.bucket}/` } console.log(policyToken) var policy_dict = {} policy_dict[ 'expiration' ] = policyToken.expire_time var condition_array = [] var array_item = [] array_item.push('starts-with'); array_item.push('$key'); array_item.push(policyToken.upload_dir); condition_array.push(array_item) policy_dict[ 'conditions' ] = condition_array var policy = JSON.stringify(policy_dict) var policy_encode = new Buffer(policy).toString('base64') console.log(policy_encode) var sign_result = crypto.createHmac('sha1', policyToken.accessKeySecret).update(policy_encode).digest().toString('base64'); console.log(sign_result) var token_dict = {} token_dict[ 'accessid' ] = policyToken.accessKeyId token_dict[ 'host' ] = policyToken.host token_dict[ 'policy' ] = policy_encode token_dict[ 'signature' ] = sign_result token_dict[ 'expire' ] = expire_syncpoint token_dict[ 'dir' ] = policyToken.upload_dir token_dict[ 'ns' ] = (this.ctx.request.uid + 10000).toString(16) // namespace const { ctx } = this; ctx.body = token_dict; } } return HomeController; }; ================================================ FILE: app/extend/application.js ================================================ const zlib = require('zlib') const Redis = require('ioredis') const REDIS = Symbol('REDIS#INSTANCE') const request = require("request") const throttle = require("lodash/throttle") const nodemailer = require('nodemailer') const nunjucks = require('nunjucks') nunjucks.configure(__dirname + '/email/tpl', { autoescape: true }) function initMailSender (config) { return nodemailer.createTransport({ host: config.host, port: config.port, secure: config.secure, auth: { user: config.user, pass: config.pass } }) } function strZip (str) { str = typeof str === 'string' ? str : JSON.stringify(str) return new Promise((resolve, reject) => { zlib.gzip(str, (err, result) => { if (err) reject(err) else resolve(result) }) }) } function strUnzip (buf) { return new Promise((resolve, reject) => { zlib.gunzip(buf, (err, result) => { if (err) reject(err) else resolve(result.toString()) }) }) } function initRedis (config, errorCallback) { var mode = config instanceof Array ? 'cluster' : 'single' var redis if (mode == 'cluster') { redis = new Redis.Cluster(config, { keyPrefix: 'godspen:', }) } else { redis = new Redis(Object.assign({ keyPrefix: 'godspen:', }, config)) } redis.on('error', (err) => { console.error(err) typeof errorCallback === 'function' && errorCallback(err) }) return redis } module.exports = { get redis () { if (!this[REDIS]) { this[REDIS] = initRedis(this.config.redis, throttle((err) => { this.DDNotify(`${err.name} \n\n ${err.message} \n\n ${err.stack} \n\n ${err.lastNodeError}`, 'redis 异常') }, 60000)) } return this[REDIS] }, async getCache (key) { if (!key) return let cache try { cache = await this.redis.getBuffer(key) if (!cache) return null cache = await strUnzip(cache) } catch (e) { console.error(e) } return cache }, async setCache (key, val) { if (!key) return let flag try { val = await strZip(val) flag = await this.redis.set(key, val, 'EX', 86400) } catch (e) { console.error(e) } return flag === 'OK' }, async delCache (key) { if (!key) return let flag try { flag = await this.redis.del(key) } catch (e) { console.error(e) } return Boolean(flag) }, addPV (from) { let pvkey = `pv_from_${from}` // report or detail if (!this[pvkey] || typeof this[pvkey] !== 'number') this[pvkey] = 1 else this[pvkey] += 1 }, getPV () { return { pv_from_report: this.pv_from_report, pv_from_detail: this.pv_from_detail, } }, DDNotify (msg, title, at) { if (!this.config.dingding) return var options = { method: 'POST', url: 'https://oapi.dingtalk.com/robot/send', qs: { access_token: this.config.dingding}, headers: { 'cache-control': 'no-cache', 'Content-Type': 'application/json' }, body: JSON.stringify({ 'msgtype': 'markdown', 'markdown': { 'title': title, 'text': `${title} @${at || 'all'} \n${msg}` }, 'at': { 'atMobiles': at ? [at] : [], 'isAtAll': false } }) } request(options, function (error, response, body) { if (error) console.error(error) console.log(body) }) }, /** * 发送邮件给指定用户 * send({ * receivers: [ '258137678@qq.com' ], * tplName: 'password', * data: { * name:'xingm', * password:'密码', * } *}) * @param {array} receivers 接受邮件的邮箱地址,一个数组 * @param {string} tplName 模板名称。见tpl文件夹下面的模板名称。不用写后缀.html * @param {object} data 模板数据 * @param {string} subject 邮件主题,可不填写 * @return {Promise} 返回一个promise */ sendMail: function mailSend ({ receivers, tplName, data, subject }) { if (!mailSend.transporter) mailSend.transporter = initMailSender(this.config.mail || {}) let mailOptions = { from: this.config.mail.user.indexOf('<') > -1 ? this.config.mail.user : `码良noreply <${this.config.mail.user}>`, // sender address to: receivers.join(','), // list of receivers subject: subject || '通知', // Subject line text: '', html: '' // html body } mailOptions.html = nunjucks.render(`${tplName}.html`, data) let promise = new Promise(function (resolve, reject) { mailSend.transporter.sendMail(mailOptions, function (error, info) { if (error) { reject(error) } else { resolve(info) } }) }) return promise } } ================================================ FILE: app/extend/context.js ================================================ class ERROR extends Error { constructor ({ code, status, msg, error }) { super(msg) this.code = code || 500 this.status = status || 200 this.msg = msg || '服务器异常,请稍后重试' this.error = error } } module.exports = { /** * 获取一个错误对象 * @param {string} code code码 * @param {string} status 状态值 * @param {string} msg 错误消息 * @return {ERROR} 错误对象 */ getError ({ code, status, msg, e }) { // this 就是 ctx 对象,在其中可以调用 ctx 上的其他方法,或访问属性 console.log(e) return new ERROR({ code, status, msg, e }) } } ================================================ FILE: app/extend/dingd.js ================================================ /** * */ 'use strict' module.exports = { * sendNotice (data) { try { const projectInfo = yield this.ctx.model.Project.findOne({ where: { id: data.projectId } }) console.log('ddproject', projectInfo) let webhook = projectInfo.ddwebhook if (webhook) { let info = { msgtype: 'markdown', markdown: data.markdown, at: data.at } console.log(info) const result = yield this.ctx.curl(webhook, { // 必须指定 method method: 'POST', // 通过 contentType 告诉 HttpClient 以 JSON 格式发送 contentType: 'json', data: info, // 明确告诉 HttpClient 以 JSON 格式处理返回的响应 body dataType: 'json' }) console.log(result) return result } } catch (error) { console.log('钉钉发送消息失败', error) } } } ================================================ FILE: app/extend/email/tpl/active.html ================================================

尊敬的{{name}},您好:

点击链接即可激活您的码良账户

{{urlcontent}}

为保障您的帐号安全,请在24小时内点击该链接,您也可以将链接复制到浏览器地址栏访问。如果您并未尝试激活邮箱,请忽略本邮件,由此给您带来的不便请谅解。

本邮件由码良自动发出,请勿直接回复!

================================================ FILE: app/extend/email/tpl/notice.html ================================================

{{content}}

本邮件由码良自动发出,请勿直接回复!

================================================ FILE: app/extend/email/tpl/password.html ================================================

尊敬的{{name}},您好:

点击链接即可重置您的码良账户密码

{{urlcontent}}

为保障您的帐号安全,请在24小时内点击该链接,您也可以将链接复制到浏览器地址栏访问。如果您并未尝试重置密码,请忽略本邮件,由此给您带来的不便请谅解。

本邮件由码良自动发出,请勿直接回复!

================================================ FILE: app/extend/helper.js ================================================ const token = require('./token'); const tools = require('./tools'); const dingd = require('./dingd'); module.exports = { dingd, token, tools, }; ================================================ FILE: app/extend/noticeMessage.js ================================================ module.exports = { GROUP_ADD_USER: { code: 101, title: '{{userName}}已加入项目组《{{groupName}}》', content: '{{userName}}已加入项目组《{{groupName}}》', }, GROUP_DELETE_USER: { code: 102, title: '{{userName}}已被移除项目组《{{groupName}}》', content: '{{userName}}您已被移除项目组《{{groupName}}》', }, GROUP_ROLE_USER: { code: 103, title: '{{userName}}对项目组《{{groupName}}》的权限变更为{{role}}', content: '{{userName}}对项目组《{{groupName}}》的权限变更为{{role}}', }, GROUP_DELETE: { code: 104, title: '{{userName}}已删除项目组《{{groupName}}》', content: '{{userName}}已删除项目组《{{groupName}}》', }, PROJECT_ADD_USER: { code: 201, title: '{{userName}}已加入项目《{{projectName}}》', content: '{{userName}}已加入项目《{{projectName}}》', }, PROJECT_DELETE_USER: { code: 202, title: '{{userName}}已离开项目《{{projectName}}》', content: '{{userName}}已离开项目《{{projectName}}》', }, PROJECT_ROLE_USER: { code: 203, title: '{{userName}}在项目《{{projectName}}》内的权限变更为{{role}}', content: '{{userName}}在项目《{{projectName}}》内的权限变更为{{role}}', }, PROJECT_DELETE: { code: 204, title: '{{userName}}删除项目《{{projectName}}》', content: '{{userName}}删除项目《{{projectName}}》', }, API_DELETE: { code: 301, title: '{{userName}}已删除接口【{{interfaceName}}】', content: '{{userName}}已删除接口【{{interfaceName}}】', }, API_PUBLISH_RElEASE: { code: 302, title: '{{userName}}申请发布接口【{{interfaceName}}】', content: '{{userName}}申请发布接口【{{interfaceName}}】,申请备注:{{remark}}', }, API_UPDATE: { code: 303, title: '{{userName}}已修改接口【{{interfaceName}}】', content: '{{userName}}已修改接口【{{interfaceName}}】', }, API_AUDIT: { code: 304, title: '接口【{{interfaceName}}】申请发布审核{{version}}', content: '接口【{{interfaceName}}】申请发布审核{{version}},审核备注:{{remark}}', }, API_UPDATE_STATUS: { code: 305, title: '接口【{{interfaceName}}】被{{userName}} 修改 {{facet}} 调试结果为: {{result}}', content: '接口【{{interfaceName}}】被{{userName}} 修改 {{facet}} 调试结果为: {{result}}', }, demo: { title: '申请发布接口', content: '## 申请人:{{userName}}\n' + '> 接口名称:{{interfaceName}}\n' + '> 接口路径:{{interfacePath}}\n' + '> ![screenshot](http://image.jpg)\n' + '> ## 10点20分发布 [天气](http://www.thinkpage.cn/) \n', }, API_DING_PUBLISH_RElEASE: { title: '申请发布接口', content: '## 申请信息\n\n' + '> 申请人:{{userName}}\n\n' + '> 接口名称:{{interfaceName}}\n\n' + '> 接口路径:{{interfacePath}}\n\n' + '> ## [点击查看详情]({{url}}) \n\n' + '## 备注 \n\n' + '> {{remark}}\n\n' }, API_DING_API_AUDIT: { title: '接口文档变动', content: '## 变动信息 {{status}}\n\n' + '> 接口名称:{{interfaceName}}\n\n' + '> 接口路径:{{interfacePath}}\n\n' + '> ## [点击查看详情]({{url}}) \n\n' + '## 备注 \n\n' + '> {{remark}}\n\n' }, }; ================================================ FILE: app/extend/token.js ================================================ const defaultsp1 = '@#$*'; const defaultsp2 = '$%^&'; const tools = require('./tools'); module.exports = { decryptToken(token) { const tokenConfig = {}; const accessToken = tools.decrypt(token, this.app.config.appConfig.desKey); const index = accessToken.indexOf(defaultsp1); const index2 = accessToken.indexOf(defaultsp2); if (index === -1 || index2 === -1) return tokenConfig; tokenConfig.uid = accessToken.substring(0, index); tokenConfig.date = accessToken.substring(index + defaultsp1.length, index2); tokenConfig.token = accessToken; tokenConfig.password = accessToken.substring(index2 + defaultsp2.length); return tokenConfig; }, getToken() { let accesToken = this.cookies.get(this.app.config.appConfig.accessTokenKey); if (tools.isEmpty(accesToken)) { // 从header取值 accesToken = this.headers[ this.app.config.appConfig.accessTokenKey ]; } return accesToken; }, /** * 生成token,规则 = encode("uid + sp1 + date + sp2 + password", deskey),sp1和sp2为固定值,deskey为加密key * @param {string} uid 用户id * @param {string} password 用户密码 */ setToken(uid, password) { const date = new Date().getTime(); let token = uid + defaultsp1 + date + defaultsp2 + password; token = tools.encrypt(token, this.app.config.appConfig.desKey); this.ctx.cookies.set(this.app.config.appConfig.accessTokenKey, token, { maxAge: this.app.config.appConfig.accessTokenKeyTime, }); }, parseCookie(cookie) { var cookies = {}; if (!cookie) { return cookie; } var list = cookie.split(';'); for (var i = 0; i < list.length; i++) { var pair = list[i].split('='); cookies[pair[0].trim()] = pair[1]; } return cookies; }, }; ================================================ FILE: app/extend/tools.js ================================================ /** * */ 'use strict' const crypto = require('crypto') const CryptoJs = require('crypto-js') const Mock = require('mockjs') var TsdbClient = require('opentsdb-node-client') const babel = require("babel-core") const urlParser = require('url') module.exports = { TSDB:(function(){ return new TsdbClient({ host: 'http://10.111.12.101', port: '30583' }) })(), /** * 加密 * @param {string} str 加密字符串 * @param {string} key 加密秘钥 * @return {*} 加密后的字符串 */ encrypt (str, key) { const cipher = crypto.createCipheriv('des-ecb', key, '') cipher.setAutoPadding(true) let ciph = cipher.update(str, 'utf8', 'base64') ciph += cipher.final('base64') return ciph }, /** * dec解密 * @param {string} str 加密字符串 * @param {string} key 加密秘钥 * @return {*} 解密后的字符串 */ decrypt (str, key) { str = decodeURIComponent(str) const decipher = crypto.createDecipheriv('des-ecb', key, '') decipher.setAutoPadding(true) let txt = decipher.update(str, 'base64', 'utf8') txt += decipher.final('utf8') return txt }, /** * md5 加密 * @param {string} str 加密字符串 * @return {*} 加密后的字符串 */ md5 (str) { const hash = crypto.createHash('sha256') hash.update(str) return hash.digest('hex') }, /** * 校验字符串货对象为空 * @param {string} str 加密字符串 * @return {boolean} 校验结果 */ isEmpty (str) { if (str === '' || str === null || str === undefined) { return true } return false }, isEmptyObject (obj) { return typeof obj == 'object' && JSON.stringify(obj) === '{}' }, /** * 修改版本号 * @param {string} str 原版本号 * @return {string} 新的版本号 */ getNewVersion (str) { if (str === '' || str === null || str === undefined) { return str } let version = str.replace(/\./g, '') - 0 version = version + 1 + '' version = version.split('').join('.') return version }, /** * 对给定的json数据拆解成为可以 * @param {object} data 数据 * @return {*} 返回mock数据 */ jsonToMock (data) { let info = {} let work = function (data, info) { for (let key in data) { let value = data[ key ] info[ value.name ] = value.mock // 数组处理 if (value.type == 'object') { info[ value.name + value.mock ] = {} work(value.child, info[ value.name + value.mock ]) } else if (value.type.indexOf('array') != -1) { info[ value.name ] = [] let type = value.type.replace('array(', '').replace(')', '') let arrayLength = value.mock - 0 for (let i = 0; i < arrayLength; i++) { if (type == 'object') { let data1 = {} info[ value.name ].push(data1) work(value.child, data1) } else if (type == 'array') { console.log('array no deal') } else if (type == 'number') { info[ value.name ].push(Mock.mock('@integer')) } else if (type == 'string') { info[ value.name ].push(Mock.mock('@string')) } else if (type == 'boolean') { info[ value.name ].push(Mock.mock('@boolean')) } } } } } work(data, info) return Mock.mock(info) }, /** * 获取项目组权限 * @param {number} role 权限key * @return {string} 权限值 */ getGroupRole (role) { let result = '' switch (role) { case 1: result = '创建者' break case 2: result = '管理者' break case 3: result = '组成员' break default: break } return result }, getSSOInfo (session) { /** * RSA最大解密密文大小 */ let MAX_DECRYPT_BLOCK = 128 /** * 公钥解密 * @param date * @returns {string} */ function publicDecrypt (date) { // 得到私钥 let publicPem = `-----BEGIN PUBLIC KEY----- MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCClI7NJ+4YeSiVjp6cy5R6x9zGRSdrX06ZrEXy kDvqBAus+luQvkfcpfU5mLgMeNEjyvTK5Om74fm0NDsoLZ6Y7xeDARgEFMoQ4h1M8cReDszFUUPV uBAU3akQpGYd7wLMMY+ND7/Hn8GjDk5qyjwlBKUsG6/bJ4hlzej9xORE5wIDAQAB -----END PUBLIC KEY-----`; // 替换你自己的路径 let publicKey = publicPem.toString() // 经过base64编码的密文转成buf let buf = new Buffer(date, 'base64') // buf转byte数组 // let inputLen = bytes(buf, "base64"); let inputLen = buf.byteLength // 密文 let bufs = [] // 开始长度 let offSet = 0 // 结束长度 let endOffSet = MAX_DECRYPT_BLOCK // 分段加密 while (inputLen - offSet > 0) { if (inputLen - offSet > MAX_DECRYPT_BLOCK) { let bufTmp = buf.slice(offSet, endOffSet) bufs.push(crypto.publicDecrypt({ key: publicKey, padding: crypto.RSA_PKCS1_PADDING }, bufTmp)) } else { let bufTmp = buf.slice(offSet, inputLen) bufs.push(crypto.publicDecrypt({ key: publicKey, padding: crypto.RSA_PKCS1_PADDING }, bufTmp)) } offSet += MAX_DECRYPT_BLOCK endOffSet += MAX_DECRYPT_BLOCK } let result = Buffer.concat(bufs).toString() console.log(result) return result } let tinfo = publicDecrypt(session) || '' tinfo = tinfo.split(',') if (tinfo.length == 3) { return { id: tinfo[ 0 ], session: tinfo[ 1 ], createTime: tinfo[ 2 ] } } return {} }, getSSOHeader (appid, sec) { // 定义加/解密的 key(key都放这里了, 加密还有啥意义!^_^) const initKey = sec /** * 定义加密函数 * @param {string} data - 需要加密的数据, 传过来前先进行 JSON.stringify(data) * @param {string} key - 加密使用的 key */ const aesEncrypt = (data, key) => { /** * CipherOption, 加密的一些选项: * mode: 加密模式, 可取值(CBC, CFB, CTR, CTRGladman, OFB, ECB), 都在 CryptoJS.mode 对象下 * padding: 填充方式, 可取值(Pkcs7, AnsiX923, Iso10126, Iso97971, ZeroPadding, NoPadding), 都在 CryptoJS.pad 对象下 * iv: 偏移量, mode === ECB 时, 不需要 iv * 返回的是一个加密对象 */ const cipher = CryptoJs.AES.encrypt(data, key, { mode: CryptoJs.mode.ECB, padding: CryptoJs.pad.Pkcs7, iv: '' }) // 将加密后的数据转换成 Base64 const base64Cipher = cipher.ciphertext.toString(CryptoJs.enc.Base64) // 处理 Android 某些低版的BUG return base64Cipher } // 获取填充后的key const key = CryptoJs.enc.Utf8.parse(initKey) // 定义需要加密的数据 const data = `${new Date() - 0}#${ parseInt(Math.random() * 100000)}` console.log(data) // 调用加密函数 const encrypted = aesEncrypt(data, key) return `${appid}@${encrypted}` }, /** * 获取项目权限 * @param {number} role 权限key * @return {string} 权限值 */ getProjectRole (role) { let result = '' switch (role) { case 1: result = '创建者' break case 2: result = '管理者' break case 3: result = '开发者' break case 4: result = '项目成员' break default: break } return result }, /** * 获取项目权限 * @param {String} tree 节点树 json * @return {string} child 子节点数组的key */ nodeTreeScriptTransform (tree, child = 'child') { if (!tree) return '' let parsingtree try { parsingtree = JSON.parse(tree) parsingtree = scriptTransform(parsingtree) parsingtree = JSON.stringify(parsingtree) } catch (e) { parsingtree = tree console.error('节点树es6语法转换出错了', e) } return parsingtree function scriptTransform (node) { if (node.script) { var script = toString.call(node.script) == toString.call([]) ? node.script : [{content: node.script, name: '脚本'}] node.script = script.filter(s => s && s.content).map(s => { s.content = wrapper(s.content) return s }).map(s => { s.content = transform(s.content) return s }).map(s => { s.content = deWrapper(s.content) return s }) } if (node[child] && node[child].length) { node[child] = node[child].map(scriptTransform) } return node } function transform (script) { return babel.transform(script, { minified: false, babelrc: false, presets: ['es2015', 'es2017', 'stage-0'], }).code } function wrapper (script) { return `function tmmmmmmmmmmmmmmmmp (vm){ ${script} }` } function deWrapper (script) { script = script.replace(/^['"]use strict['"];((?:.|[\n\r])*)function\s*tmmmmmmmmmmmmmmmmp\s*\(vm\)\s*\{((?:.|[\n\r])*)\}$/m, '$1\n$2').replace(/[\r\n]+/g, '\n') return script } }, /** ** ** 时间格式化 **/ timeFormat (time, format = 'yyyy/mm/dd') { if (!time instanceof Date) return '' let o = { 'y+': time.getFullYear(), // 年份 'M+': time.getMonth() + 1, // 月份 'd+': time.getDate(), // 日 'h+': time.getHours(), // 小时 'm+': time.getMinutes(), // 分 's+': time.getSeconds(), // 秒 'q+': Math.floor((time.getMonth() + 3) / 3), // 季度 'S': time.getMilliseconds() // 毫秒 } for (let key of Object.keys(o)) { format = format.replace(new RegExp(`(${key})`), (m, p) => { let val = `00${o[key]}` return val.slice(val.length - p.length) }) } return format }, ossConfigValid (config = {}) { const isInvalid = op => typeof op !== 'string' || op.trim() === '' || /<[^<>]+>/.test(op) return !(isInvalid(config.accessKeyId) || isInvalid(config.accessKeySecret) || isInvalid(config.host) || isInvalid(config.bucket) || isInvalid(config.region)) }, isSafeUrl (url) { if (!url || typeof url !== 'string') return false const urlInfo = urlParser.parse(url) const isSafeUrl = urlInfo && /^https?:$/.test(urlInfo.protocol) && !/^[\d.:]+$/.test(urlInfo.host) && urlInfo.hostname != 'localhost' return isSafeUrl }, } ================================================ FILE: app/middleware/login_handler.js ================================================ module.exports = () => { return function* (next) { // 校验登录信息 const accessToken = this.helper.token.getToken.call(this); var clikey = this.headers['clikey'] if(clikey){ // 请求数据库校验用户uid const user = yield this.model.UserLogin.findOne({ where: { security: clikey }, }); if (this.helper.tools.isEmpty(user)) throw this.getError({ status: 403, msg: '请求不合法' }); this.request.uid = user.userId; } else { if (this.helper.tools.isEmpty(accessToken)) throw this.getError({ status: 401, msg: '您的登录已失效,请重新登录' }) const tokenConfig = this.helper.token.decryptToken.call(this, accessToken); if (this.helper.tools.isEmpty(tokenConfig.token) || this.helper.tools.isEmpty(tokenConfig.date) || this.helper.tools.isEmpty(tokenConfig.uid) || this.helper.tools.isEmpty(tokenConfig.password)) throw this.getError({ status: 403, msg: '请求不合法' }); // 校验过期时间 if (new Date().getTime() - tokenConfig.date > this.app.config.appConfig.accessTokenKeyTime) { throw this.getError({ status: 401, msg: '您的登录已失效,请重新登录' }); } // 请求数据库校验用户uid const user = yield this.model.UserLogin.findOne({ where: { userId: tokenConfig.uid }, }); if (this.helper.tools.isEmpty(user) || user.password !== tokenConfig.password) throw this.getError({ status: 403, msg: '请求不合法' }); this.request.uid = tokenConfig.uid; } yield next; }; }; ================================================ FILE: app/middleware/response_handler.js ================================================ const throttle = require("lodash/throttle") module.exports = (options, app) => { var notify = throttle((err, {url, req}) => { try { req = JSON.stringify(req) } catch (e) { console.log(`try JSON.stringify ${req} error`, e) } app.DDNotify(`url: ${url} \n\n request: ${req} \n\n ${err.name} \n\n ${err.message} \n\n ${err.stack}`, '接口请求异常') }, 30000) return function* (next) { try { console.log('params:', this.href, this.request.body, this.query); yield next; const result = this.body; // 包装返回参数 this.body = this.noWarp ? result : { code: 1, msg: 'success', data: result, } } catch (err) { // 所有的异常都在 app 上触发一个 error 事件,框架会记录一条错误日志 console.error('RESPONSE:ERROR', err); this.status = err.status; this.body = { code: err.code || 500, msg: this.helper.tools.isEmpty(err.msg) ? err.message : err.msg, }; // notify(err, {url: this.href, req: this.request && this.request.body}) } }; }; ================================================ FILE: app/model/category.js ================================================ 'use strict'; module.exports = app => { return app.model.define('category', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, desc: { field: 'desc', type: app.Sequelize.STRING }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_category', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/component.js ================================================ 'use strict'; module.exports = app => { var component = app.model.define('component', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, version: { field: 'version', type: app.Sequelize.STRING }, path: { field: 'path', type: app.Sequelize.STRING }, desc: { field: 'desc', type: app.Sequelize.STRING }, userId: { field: 'user_id', type: app.Sequelize.INTEGER }, useNumber: { field: 'usenumber', type: app.Sequelize.INTEGER }, status: { field: 'status', type: app.Sequelize.INTEGER }, isnew: { field: 'isnew', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER}, visibilitylevel: { field: 'visibilitylevel', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_component', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); return component }; ================================================ FILE: app/model/component_use.js ================================================ 'use strict'; module.exports = app => { return app.model.define('ComponentUse', { cid: { field: 'cid', type: app.Sequelize.INTEGER }, useNumber: { field: 'usenumber', type: app.Sequelize.INTEGER }, love: { field: 'love', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_component_use', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/group.js ================================================ module.exports = app => { return app.model.define('group', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, createUserId: { field: 'create_user_id', type: app.Sequelize.BIGINT }, description: { field: 'description', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, logo: { field: 'logo', type: app.Sequelize.STRING }, type: { field: 'type', type: app.Sequelize.INTEGER }, projectCount: { field: 'project_count', type: app.Sequelize.INTEGER }, userCount: { field: 'user_count', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_group', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/group_project.js ================================================ module.exports = app => { return app.model.define('groupProject', { groupId: { field: 'group_id', type: app.Sequelize.BIGINT, primaryKey: true }, projectId: { field: 'project_id', type: app.Sequelize.BIGINT, primaryKey: true, }, status: { field: 'status', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_group_and_project', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/group_user.js ================================================ module.exports = app => { return app.model.define('groupUser', { groupId: { field: 'group_id', type: app.Sequelize.BIGINT, primaryKey: true }, userId: { field: 'user_id', type: app.Sequelize.BIGINT, primaryKey: true }, status: { field: 'status', type: app.Sequelize.INTEGER }, role: { field: 'role', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_group_and_user', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/history_interface.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 下午3:47 * To change this template use File | Settings | File Templates. */ 'use strict' module.exports = app => { return app.model.define('historyInterface', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, createUserId: { field: 'create_user_id', type: app.Sequelize.BIGINT }, description: { field: 'description', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, path: { field: 'path', type: app.Sequelize.STRING }, request: { field: 'request_content', type: app.Sequelize.STRING }, response: { field: 'response_content', type: app.Sequelize.STRING }, mockResponse: { field: 'mock_response', type: app.Sequelize.STRING }, projectId: { field: 'project_id', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, version: { field: 'version', type: app.Sequelize.STRING }, interfaceId: { field: 'interface_id', type: app.Sequelize.INTEGER }, lastedVersion: { field: 'lasted_version', type: app.Sequelize.INTEGER }, modifyUserId: { field: 'modify_user_id', type: app.Sequelize.INTEGER }, aduitRemark: { field: 'aduit_remark', type: app.Sequelize.STRING }, endStatus: { field: 'end_status', type: app.Sequelize.INTEGER }, deprecated: { field: 'deprecated', type: app.Sequelize.INTEGER }, frontStatus: { field: 'front_status', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_history_interface', createdAt: 'createTime', updatedAt: 'updateTime' }, { classMethods: {} }) } ================================================ FILE: app/model/interface.js ================================================ module.exports = app => { return app.model.define('interface', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, createUserId: { field: 'create_user_id', type: app.Sequelize.BIGINT }, description: { field: 'description', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, path: { field: 'path', type: app.Sequelize.STRING }, request: { field: 'request_content', type: app.Sequelize.STRING }, response: { field: 'response_content', type: app.Sequelize.STRING }, projectId: { field: 'project_id', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, versionInterfaceId: { field: 'version_interface_id', type: app.Sequelize.INTEGER }, modifyUserId: { field: 'modify_user_id', type: app.Sequelize.INTEGER }, publishStatus: { field: 'publish_status', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_interface', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/interface_draf.js ================================================ module.exports = app => { return app.model.define('interfaceDraf', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, projectId: { field: 'project_id', type: app.Sequelize.BIGINT }, apis: { field: 'apis', type: app.Sequelize.STRING }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_interface_draf', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/interface_log.js ================================================ module.exports = app => { return app.model.define('interfaceLog', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, remark: { field: 'remark', type: app.Sequelize.STRING }, uid: { field: 'uid', type: app.Sequelize.BIGINT }, interfaceId: { field: 'interface_id', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_interface_log', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/interface_user.js ================================================ module.exports = app => { return app.model.define('interfaceUser', { userId: { field: 'user_id', type: app.Sequelize.BIGINT, primaryKey: true }, interfaceId: { field: 'interface_id', type: app.Sequelize.INTEGER, primaryKey: true }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_interface_user', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/pages.js ================================================ 'use strict'; module.exports = app => { return app.model.define('pages', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, key: { field: 'key', type: app.Sequelize.STRING }, name: { field: 'name', type: app.Sequelize.STRING }, image: { field: 'image', type: app.Sequelize.STRING }, desc: { field: 'desc', type: app.Sequelize.STRING }, content: { field: 'content', type: app.Sequelize.STRING }, draft: { field: 'draft', type: app.Sequelize.STRING }, projectId: { field: 'project_id', type: app.Sequelize.INTEGER }, status: { field: 'status', type: app.Sequelize.INTEGER }, featured: { field: 'featured', type: app.Sequelize.INTEGER }, visibilitylevel: { field: 'visibilitylevel', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, // 页面类型,默认0,普通页面;1,flutter 页面; updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, fork: { field: 'fork', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_pages', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/pages_history.js ================================================ 'use strict'; module.exports = app => { return app.model.define('pagesHistory', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true }, content: { field: 'content', type: app.Sequelize.STRING }, userId: { field: 'user_id', type: app.Sequelize.INTEGER }, pageId: { field: 'page_id', type: app.Sequelize.INTEGER }, status: { field: 'status', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_pages_history', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/project.js ================================================ module.exports = app => { return app.model.define('project', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, key: { field: 'key', type: app.Sequelize.STRING }, createUserId: { field: 'create_user_id', type: app.Sequelize.BIGINT }, image: { field: 'image', type: app.Sequelize.STRING }, desc: { field: 'desc', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, visibilitylevel: { field: 'visibilitylevel', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_project', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/project_data.js ================================================ module.exports = app => { return app.model.define('projectData', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, projectId: { field: 'project_id', type: app.Sequelize.BIGINT }, status: { field: 'status', type: app.Sequelize.INTEGER }, name: { field: 'name', type: app.Sequelize.STRING }, remark: { field: 'remark', type: app.Sequelize.STRING }, content: { field: 'content', type: app.Sequelize.STRING }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_project_data', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/resTagsRel.js ================================================ 'use strict'; module.exports = app => { return app.model.define('resTagsRel', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, rid: { field: 'rid', type: app.Sequelize.INTEGER }, tid: { field: 'tid', type: app.Sequelize.INTEGER }, cid: { field: 'cid', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_res_tags_rel', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/resources.js ================================================ 'use strict'; module.exports = app => { return app.model.define('resources', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, icon: { field: 'icon', type: app.Sequelize.STRING }, content: { field: 'content', type: app.Sequelize.STRING }, categoryId: { field: 'category_id', type: app.Sequelize.INTEGER }, userId: { field: 'user_id', type: app.Sequelize.INTEGER }, status: { field: 'status', type: app.Sequelize.INTEGER }, desc: { field: 'desc', type: app.Sequelize.STRING }, visibilitylevel: { field: 'visibilitylevel', type: app.Sequelize.INTEGER }, useCount: { field: 'use_count', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_resources', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/tag.js ================================================ /** * Created with WebStorm. * User: star * Email:258137678@qq.com * Date: 2017/5/3 * Time: 下午3:47 * To change this template use File | Settings | File Templates. */ 'use strict' module.exports = app => { return app.model.define('tag', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, projectId: { field: 'project_id', type: app.Sequelize.INTEGER }, name: { field: 'name', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_tag', createdAt: 'createTime', updatedAt: 'updateTime' }, { classMethods: {} }) } ================================================ FILE: app/model/tag_interface.js ================================================ module.exports = app => { return app.model.define('tagInterface', { tagId: { field: 'tag_id', type: app.Sequelize.BIGINT, primaryKey: true }, interfaceId: { field: 'interface_id', type: app.Sequelize.INTEGER, primaryKey: true }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_tag_interface', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/tags.js ================================================ 'use strict'; module.exports = app => { return app.model.define('tags', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, categoryId: { field: 'category_id', type: app.Sequelize.INTEGER }, status: { field: 'status', type: app.Sequelize.INTEGER }, useNumber: { field: 'usenumber', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_tags', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/template.js ================================================ 'use strict'; module.exports = app => { return app.model.define('template', { id: { field: 'id', type: app.Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, name: { field: 'name', type: app.Sequelize.STRING }, categoryId: { field: 'category_id', type: app.Sequelize.INTEGER }, content: { field: 'content', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, desc: { field: 'desc', type: app.Sequelize.STRING }, image: { field: 'image', type: app.Sequelize.STRING }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT } }, { timestamps: true, tableName: 'tb_template', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user.js ================================================ module.exports = app => { return app.model.define('user', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, email: { field: 'email', type: app.Sequelize.STRING }, emailStatus: { field: 'email_status', type: app.Sequelize.INTEGER }, name: { field: 'name', type: app.Sequelize.STRING }, telephone: { field: 'telephone', type: app.Sequelize.STRING }, photo: { field: 'photo', type: app.Sequelize.STRING }, projectCount: { field: 'project_count', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, role: { field: 'role', type: app.Sequelize.INTEGER }, oauth: { field: 'oauth', type: app.Sequelize.STRING } }, { timestamps: true, tableName: 'tb_user', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_grade.js ================================================ module.exports = app => { return app.model.define('userGrade', { userId: { field: 'user_id', type: app.Sequelize.BIGINT, primaryKey: true }, projectNum: { field: 'project_num', type: app.Sequelize.INTEGER }, groupNum: { field: 'group_num', type: app.Sequelize.INTEGER }, interfaceNum: { field: 'interface_num', type: app.Sequelize.INTEGER }, favorateProjectNum: { field: 'favorate_project_num', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_user_grade', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_login.js ================================================ module.exports = app => { return app.model.define('userLogin', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, password: { field: 'password', type: app.Sequelize.STRING }, userId: { field: 'user_id', type: app.Sequelize.BIGINT }, email: { field: 'email', type: app.Sequelize.STRING }, status: { field: 'status', type: app.Sequelize.INTEGER }, lastIp: { field: 'last_ip', type: app.Sequelize.STRING }, lastLogin: { field: 'last_login', type: app.Sequelize.DATE }, ssoUid: { field: 'sso_uid', type: app.Sequelize.BIGINT }, security: { field: 'security', type: app.Sequelize.STRING }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_user_login', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_login_log.js ================================================ module.exports = app => { return app.model.define('userLoginLog', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, userId: { field: 'user_id', type: app.Sequelize.BIGINT }, ip: { field: 'ip', type: app.Sequelize.STRING }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_login_log', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_notice.js ================================================ module.exports = app => { return app.model.define('userNotice', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, createUserId: { field: 'create_user_id', type: app.Sequelize.BIGINT }, userId: { field: 'user_id', type: app.Sequelize.BIGINT }, content: { field: 'content', type: app.Sequelize.STRING }, title: { field: 'title', type: app.Sequelize.STRING }, readStatus: { field: 'read_status', type: app.Sequelize.INTEGER }, type: { field: 'type', type: app.Sequelize.INTEGER }, joinId: { field: 'join_id', type: app.Sequelize.BIGINT }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_user_notice', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_notice_type.js ================================================ module.exports = app => { return app.model.define('userNoticeType', { userId: { field: 'user_id', type: app.Sequelize.BIGINT, primaryKey: true }, type: { field: 'type', type: app.Sequelize.INTEGER, primaryKey: true }, messageNotice: { field: 'message_notice', type: app.Sequelize.INTEGER }, emailNotice: { field: 'email_notice', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_user_notice_type', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/user_project.js ================================================ module.exports = app => { return app.model.define('userProject', { projectId: { field: 'project_id', type: app.Sequelize.BIGINT, primaryKey: true }, userId: { field: 'user_id', type: app.Sequelize.BIGINT, primaryKey: true, }, status: { field: 'status', type: app.Sequelize.INTEGER }, role: { field: 'role', type: app.Sequelize.INTEGER }, favor: { field: 'is_favor', type: app.Sequelize.INTEGER }, updateTime: { field: 'update_time', type: app.Sequelize.BIGINT }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_user_and_project', createdAt: 'createTime', updatedAt: 'updateTime', }, { classMethods: {}, }); }; ================================================ FILE: app/model/valid_code.js ================================================ module.exports = app => { return app.model.define('validCode', { id: { field: 'id', type: app.Sequelize.BIGINT, primaryKey: true, autoIncrement: true }, code: { field: 'code', type: app.Sequelize.STRING }, userId: { field: 'user_id', type: app.Sequelize.BIGINT }, email: { field: 'email', type: app.Sequelize.STRING }, expireTime: { field: 'expire_time', type: app.Sequelize.DATE }, createTime: { field: 'create_time', type: app.Sequelize.BIGINT }, }, { timestamps: true, tableName: 'tb_valid_code', createdAt: 'createTime', updatedAt: false, }, { classMethods: {}, }); }; ================================================ FILE: app/router.js ================================================ 'use strict' module.exports = app => { console.log(app.config) app.router.prefix('/' + app.config.API_PATH.replace(/^\/+|\/+$/g, '')) app.post(`/users/login`, 'user.login') app.post(`/users/register`, 'user.register') app.post(`/users/activeEmail`, 'user.activeEmail') app.get(`/users/sendActiveEmail`, 'user.sendActiveEmail') app.post(`/users/sendEmail`, 'user.sendEmail') app.get(`/users/info`, 'user.info') app.post(`/users/logout`, 'user.logout') app.put(`/users/edit`, 'user.edit') app.put(`/users/updatePassword`, 'user.updatePassword') app.post(`/users/newUpdatePassword`, 'user.newUpdatePassword') app.post(`/users/forgetPassword`, 'user.forgetPassword') app.get(`/users/search`, 'user.search') app.get(`/upload/getTocken`, 'user.getTocken') app.get(`/kaptcha/init`, 'kaptcha.init') // 第三方登录 app.post(`/users/oauthCode`, 'user.oauthCode') app.post(`/users/oauthLogin`, 'user.oauthLogin') // 组件列表 app.post(`/component/useone`, 'component.useone') // 组件使用次数增加 app.post(`/component/searchByName`, 'component.searchByName') // 我的分组列表 app.post(`/component/searchAllStatusByName`, 'component.searchAllStatusByName') // 我的分组列表 app.post(`/component/find`, 'component.find') // 我的分组列表 app.post(`/component/add`, 'component.save') app.get(`/component/info`, 'component.info') // 获取组件信息 app.post(`/component/updata`, 'component.updata') // 更新组件信息 app.post(`/component/delete`, 'component.delete') // 更新组件信息 app.post(`/component/import`, 'component.import') // 组件导入 // 分组管理 app.delete(`/project/group`, 'group.delete') app.put(`/project/group`, 'group.update') app.get(`/project/group`, 'group.list') // 我的分组列表 app.get(`/project/groupinfo`, 'group.info') // 特定分组详情 app.post(`/project/group`, 'group.add') // 分组成员管理 app.post(`/project/groupuser`, 'groupUser.add') app.delete(`/project/groupuser`, 'groupUser.delete') app.put(`/project/groupuser`, 'groupUser.update') app.get(`/project/groupuser`, 'groupUser.list') // 项目管理 app.delete(`/project/project`, 'project.delete') app.put(`/project/project`, 'project.update') app.get(`/project/project`, 'project.list') // 我的项目列表 app.get(`/project/projectinfo`, 'project.info') // 特定项目详情 app.post(`/project/project`, 'project.add') app.get(`/project/groupproject`, 'project.groupProject') // 某分组项目列表 app.post(`/project/favorateproject`, 'project.favorateProject') app.delete(`/project/favorateproject`, 'project.cancelFavorateProject') app.get(`/project/favorateproject`, 'project.getFavorateProject') // 项目成员管理 app.post(`/project/projectuser`, 'projectUser.add') app.delete(`/project/projectuser`, 'projectUser.delete') app.put(`/project/projectuser`, 'projectUser.update') app.get(`/project/projectuser`, 'projectUser.list') // pages app.post(`/editor/pages/pvuv`, 'pages.pvuv') app.post(`/editor/pages/pv`, 'pages.pv') app.get(`/editor/pages/publiclist`, 'pages.publiclist') app.post(`/editor/pages/publiclist`, 'pages.publiclist') app.post(`/editor/pages/list`, 'pages.list') app.post(`/editor/pages/save`, 'pages.save') app.post(`/editor/pages/delete`, 'pages.delete') app.post(`/editor/pages/info`, 'pages.info') app.post(`/editor/pages/detail`, 'pages.detail') app.post(`/editor/pages/editor-detail`, 'pages.detail') // 编辑起调用这个,走登陆判断 app.post(`/editor/pages/change-status`, 'pages.changeStatus') app.post(`/editor/pages/set-home-page`, 'pages.setHomePage') app.post(`/editor/pages/publish`, 'pages.publish') app.get(`/editor/pages/count`, 'pages.count') app.get(`/editor/pages/history`, 'pages.history') app.get(`/editor/pages/history-delete`, 'pages.historyDelete') app.post(`/editor/pages/history-publish`, 'pages.historyPublish') app.post(`/editor/pages/history-to-draft`, 'pages.historyToDraft') app.post(`/editor/pages/update-fork`, 'pages.updateFork') app.post(`/editor/pages/psd-to-page`, 'pages.psdToPage') app.post(`/editor/pages/featuring`, 'pages.featuringPages') app.post(`/editor/pages/update-featured`, 'pages.updateFeatured') // names app.post(`/editor/pages/getNameBykeys`, 'pages.getNameBykeys') // template app.post(`/editor/template/list`, 'template.list') app.post(`/editor/template/save`, 'template.save') app.post(`/editor/template/delete`, 'template.delete') app.post(`/editor/template/detail`, 'template.detail') // category app.post(`/editor/category/list`, 'category.list') app.post(`/editor/category/save`, 'category.save') app.post(`/editor/category/delete`, 'category.delete') // resources app.post(`/editor/resources/list`, 'resources.list') app.get(`/editor/resources/info`, 'resources.info') app.post(`/editor/resources/save`, 'resources.save') app.post(`/editor/resources/delete`, 'resources.delete') app.post(`/editor/resources/addUseCount`, 'resources.addUseCount') // tags app.post(`/editor/tags/list`, 'tags.list') app.post(`/editor/tags/add`, 'tags.add') app.post(`/editor/tags/useone`, 'tags.useone') app.get(`/test`, 'home.index') // proxy // img-proxy app.get(`/proxy/imgCors`, 'proxy.imgCorsProxy') // img-proxy alias app.get(`/cors-proxy`, 'proxy.imgCorsProxy') // transparent-proxy app.post(`/proxy/transparent`, 'proxy.transparentProxy') // 数据统计 // es app.get(`/statistics/report`, 'statistics.report') app.post(`/statistics/getPUV`, 'statistics.getPUV') app.post(`/statistics/getPages`, 'statistics.getPages') app.post(`/statistics/getActions`, 'statistics.getActions') app.post(`/statistics/actionTrack`, 'statistics.actionTrack') app.post(`/statistics/getTodayOutline`, 'statistics.getTodayOutline') app.post(`/statistics/getUtms`, 'statistics.getUtms') app.post(`/statistics/getViewTime`, 'statistics.getViewTime') app.post(`/statistics/getProjectReport`, 'statistics.getProjectReport') app.post(`/statistics/getGroupReport`, 'statistics.getGroupReport') // ossupload app.post(`/ossupload/uploadByUrls`, 'ossupload.uploadByUrls') app.post(`/ossupload/uploadFile`, 'ossupload.uploadFile') // word to html app.post(`/transform/word2html`, 'proxy.word2html') } ================================================ FILE: app/schedule/es_delete.js ================================================ module.exports = { schedule: { cron: '0 0 1 * *', // 每月1号 type: 'worker', // 随机一个 worker 执行 }, cronOptions: { tz: 'Asia/Shanghai' }, disable: false, async task(ctx) { console.info('开始清理6个月前的日志') let status = await ctx.service.statistics.deleteIndices({monthBefore: 6}) console.info('日志清理完成', status) } } ================================================ FILE: app/schedule/es_report.js ================================================ module.exports = { schedule: { interval: '1m', // 1分钟间隔 type: 'all', }, async task(ctx) { console.info('开始执行上报打点日志定时任务') let status = await ctx.service.statistics.addDocs() console.info('结束执行上报打点日志定时任务,操作', status ? '成功' : '失败') } } ================================================ FILE: app/schedule/pu_uv_ratio.js ================================================ const PUV_RATIO_THRESHOLD = 1.6 // 小时 pv/uv 平均1.34 最大值3.72 取1.6为告警阈值 module.exports = app => { return { schedule: { // interval: '1m', cron: '0 0 * * * *', // 每小时一次 type: 'worker', }, disable: 0, cronOptions: { tz: 'Asia/Shanghai' }, async task(ctx) { let {sum: {pv, uv}} = await ctx.service.statistics.getPUV({pageId: '', timePeriod: [Date.now() - 3600000], utm: '', interval: 'hour', appId: 'tview'}) let ratio = pv / uv if (ratio >= PUV_RATIO_THRESHOLD) { app.DDNotify( `最近一小时码良页面 \n\n 访问量 ${pv} \n\n 访问人数 ${uv} \n\n 比值 ${ratio.toFixed(2)} \n\n 注意排查是否流量异常`, '码良访问量/访问人数比超限告警') } } } } ================================================ FILE: app/schedule/pvcount.js ================================================ module.exports = app => { return { schedule: { // interval: '30s', cron: '0 0 */1 * *', // 每天零点 type: 'all', }, disable: true, cronOptions: { tz: 'Asia/Shanghai' }, async task(ctx) { console.info('page_count_log', app.getPV()) } } } ================================================ FILE: app/service/base.js ================================================ module.exports = app => { class BaseService extends app.Service { * getUserRole (uid, projectId) { let role = 100; uid = uid || (this.ctx.request && this.ctx.request.uid); let userProject = yield this.ctx.model.UserProject.findOne({ where: { userId: uid, projectId, status: 1 } }); if (userProject) role = userProject.role; // 查询是否有分组权限 const group = yield this.ctx.model.GroupProject.findOne({ where: { projectId, status: 1 } }); if (group) { // 查询分组权限 const groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: uid, groupId: group.groupId, status: 1 } }); if (groupUser) role = role < groupUser.role ? role : groupUser.role; } else { role = 100; } return role; } * sendNotice (uids, notice, noticeType) { if (!uids || uids.length == 0 || noticeType < 0 || !notice) return; for (let i = 0; i < uids.length; i++) { let uid = uids[ i ]; // 查询用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: uid } }); if (!user) continue; // 查询用户的通知设置 const userNotice = yield this.ctx.model.UserNoticeType.findOne({ where: { userId: uid, type: noticeType } }); if (!userNotice) continue; if (userNotice.messageNotice == 1) { // 发送站内信 notice.userId = uid; yield this.ctx.model.UserNotice.create(notice); } if (userNotice.emailNotice == 1) { // 发送邮件 this.app.sendMail({ receivers: [ user.email ], tplName: 'notice', data: { content: notice.content, }, callback () {}, subject: notice.title }); } } } } return BaseService; }; ================================================ FILE: app/service/category.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 上午9:30 * To change this template use File | Settings | File Templates. */ var md5 = require('md5'); 'use strict' module.exports = app => { class Category extends app.Service { * list (query) { console.log('service start --list') let _where = { status: (query.status == undefined) ? 1 : query.status, } if (query.type) { _where.type = { $in: query.type } } return yield this.ctx.model.Category.findAll({ where: _where }); } * save (query) { try { var _item = { name: query.name, type: query.type, desc: query.desc, status: (query.status == undefined) ? 1 : query.status, } if (query.id) { // 编辑 // 参数验证,1判断是否存在该id,2是否重名 if (!(yield this.ctx.model.Category.findOne({ where: { status: 1, id: query.id } }))) throw this.ctx.getError({ msg: `不存在id为${query.id}的类型` }); if (yield this.ctx.model.Category.findOne({ where: { name: query.name, status: 1, type: query.type, id: { $ne: query.id } } })) throw this.ctx.getError({ msg: `已经存在名称为${query.name}的类型` }); yield this.ctx.model.Category.update(_item, { where: { id: query.id } }) return { id: query.id } } else { // 参数验证 是否重名 if (yield this.ctx.model.Category.findOne({ where: { type: query.type, name: query.name, status: 1 } })) throw this.ctx.getError({ msg: `已经存在名称为${query.name}的类型` }); var newItem = yield this.ctx.model.Category.create(_item) return { id: newItem.id } } } catch (e) { throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * delete (id) { var item = yield this.ctx.model.Category.findOne({ where: { id: id } }) if (item) { yield this.ctx.model.Category.update({ status: 0 }, { where: { id: id } }); return true } } } return Category } ================================================ FILE: app/service/component.js ================================================ const co = require('co') const OSS = require('ali-oss') const request = require('request') var JSZip = require("jszip") function setConstVar (str, { publicpath, namespace, name, version }) { str = str.replace(/__OSS_BUCKET__/g, publicpath) str = str.replace(/__NAMESPACE__/g, namespace) str = str.replace(/__NAME__/g, name) str = str.replace(/__VERSION__/g, version) return str } function normalizeName (name = '') { return String(name).replace(/[-_\s]+(\w)/g, (m, p) => p.toUpperCase()) } async function genManifest (pkgfile, uid) { const pkg = JSON.parse(await pkgfile.async('text')) const ns = normalizeName(pkg.author || (uid + 10000).toString(16)) return { namespace: ns, name: normalizeName(pkg.name), type: +pkg.type || 0, description: pkg.description, version: pkg.version, visibilitylevel: Number(!pkg.private), tags: (pkg.tags || []).filter(t => !isNaN(t)).map(t => ({id: t})) } } module.exports = app => { const ossClient = new OSS({ region: app.config.oss.region, accessKeyId: app.config.oss.accessKeyId, accessKeySecret: app.config.oss.accessKeySecret, bucket: app.config.oss.bucket, }) const OSS_HOST = app.config.oss.host async function downloadZip (id, token) { const downloadApi = app.config.zipDownloadApi const options = { 'method': 'POST', 'url': downloadApi, 'headers': { 'clikey': token, 'Content-Type': 'application/json' }, encoding: null, body: JSON.stringify({id: String(id)}) } return await new Promise((resolve, reject) => { request(options, (error, response, body) => { if(error || response.statusCode !== 200) reject(error || new Error('资源下载错误')) resolve(body) }) }) } async function ossUpload (files = [], manifest) { const DIR = process.env.EGG_SERVER_ENV !== 'production' ? 'components-test' : 'components' let mainFile for (let file of files) { const name = file.name const filepath = `${manifest.namespace}/${manifest.name}/${manifest.version}/${name.replace(/^dist[\/]/, '')}` let data if (/(index|editor)\.js$/.test(name)) { data = await file.async('text') data = setConstVar(data, { namespace: manifest.namespace, publicpath: `${OSS_HOST}/${DIR}/`, name: manifest.name, version: manifest.version }) } else { data = await file.async('nodebuffer') } const res = await upload(filepath, data) console.log(`${res.name} 上传成功: ${res.path}`) if (/index\.js$/.test(name)) mainFile = res.path } return mainFile async function upload (filepath, data) { if (typeof data === 'string') data = Buffer.from(data) let result = await co(function * () { return yield ossClient.put(`${DIR}/${filepath}`, data) }) return { name: result && result.name, path: result && result.url.replace(/^http(?!s)/, 'https') } } } class Component extends app.Service { * info (obj) { var item = yield this.ctx.model.Component.findOne({ where: { id: obj.id, userId: obj.uid, isnew: 1 } }) if (!item || !item.dataValues) return item console.log(item) let tags = yield this.ctx.model.query(`select t.id, t.name from tb_res_tags_rel rel left join tb_tags t on t.id=rel.tid where rel.rid =:id and cid=3 `, { type: 'SELECT', replacements: obj }) item.dataValues.tags = tags return item } * list (query) { var where = {} if (query.like) { if (query.name) { where[ '$and' ] = { '$or': [ { name: { $like: `%${query.name}%` } }, { desc: { $like: `%${query.name}%` } } ] } } where.isnew = 1 if (query.type !== undefined && query.type !== null) where.type = query.type if (query.onlyMine) { where.userId = +query.uid } else { where[ '$or' ] = [ { userId: query.uid }, { visibilitylevel: 1 } ] } } else { query.name && (where.name = query.name) query.version && (where.version = query.version) } if (query.status != 'all') { where.status = 1 } this.ctx.model.Component.hasOne(this.ctx.model.ComponentUse, { foreignKey: 'cid' }) if (query.tags && query.tags.length) { let tagrefs = yield this.ctx.model.ResTagsRel.findAll({ where: { tid: {'$or': query.tags.map(t => t.id || t)} } }) let cid = Array.from(new Set((tagrefs || []).map(t => t.rid))) if (!cid || !cid.length) return {list: []} where.id = {'$or': cid} } var list = yield this.ctx.model.Component.findAll({ where: where, include: [ { required: false, model: this.ctx.model.ComponentUse } ], order: [ [ this.ctx.model.ComponentUse, 'usenumber', 'DESC' ] ], limit: query.limit || 100 }) return { list } } * updata (query) { console.log(query) let t = yield app.model.transaction(); try { var _item = query yield this.ctx.model.Component.update({ desc: query.desc, visibilitylevel: query.visibilitylevel, }, { where: { name: query.name, userId: query.userId } }, { transaction: t }) // 移除标签和资源的关系 yield this.ctx.model.query(` DELETE FROM tb_res_tags_rel WHERE rid=:id and cid=3; `, { type: 'BULKDELETE', transaction: t, replacements: query }); query.tags = query.tags || [] for (let tag of query.tags) { yield this.ctx.service.tags.useone({ id: tag.id }) // 绑定标签和资源的关系 yield this.ctx.model.ResTagsRel.create({ rid: query.id, tid: tag.id, cid: 3 }, { transaction: t }) } yield t.commit(); return { id: query.id } } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * save (query) { let t = yield app.model.transaction(); try { var _item = query // 如果没有用户信息,资源设为公共资源 if (!query.userId) { _item.visibilitylevel = 1 } _item.status = 1 _item.isnew = 1 // 查找最新的元素 var currComponent = yield this.ctx.model.Component.findOne({ where: { isnew: 1, name: query.name } }) yield this.ctx.model.Component.update({ isnew: 0 }, { where: { isnew: 1, name: query.name } }, { transaction: t }) var newItem = yield this.ctx.model.Component.create(_item, { transaction: t }) query.id = newItem.id if (currComponent) { var componentUse = yield this.ctx.model.ComponentUse.findOne({ where: { cid: currComponent.id } }, { transaction: t }) // 使用量记录,如果有记录则加一,没有的话。初始化创建一个 if (componentUse) { yield this.ctx.model.ComponentUse.update({ cid: newItem.id }, { where: { cid: currComponent.id } }, { transaction: t }) } else { yield this.ctx.model.ComponentUse.create({ cid: newItem.id, useNumber: currComponent.useNumber }, { transaction: t }) } // 更新tag标签 // 移除标签和资源的关系 var _query = { id: newItem.id, rid: currComponent.id } yield this.ctx.model.query(`update tb_res_tags_rel set rid=:id WHERE rid=:rid and cid=3; `, { transaction: t, replacements: _query }); } else { // 首次添加时,生成标签 query.tags = query.tags || [] for (let tag of query.tags) { yield this.ctx.service.tags.useone({ id: tag.id }) // 绑定标签和资源的关系 yield this.ctx.model.ResTagsRel.create({ rid: query.id, tid: tag.id, cid: 3 }, { transaction: t }) } } yield t.commit(); return { id: query.id } } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * useone (query) { let t = yield app.model.transaction(); try { var component = yield this.ctx.model.Component.findOne({ where: { id: query.id, status: 1 } }, { transaction: t }) if (component) { var useNum = component.useNumber + 1 yield this.ctx.model.Component.update({ useNumber: useNum }, { where: { id: query.id } }, { transaction: t }) var componentUse = yield this.ctx.model.ComponentUse.findOne({ where: { cid: component.id } }, { transaction: t }) // 使用量记录,如果有记录则加一,没有的话。初始化创建一个 if (componentUse) { yield this.ctx.model.ComponentUse.update({ useNumber: componentUse.useNumber + 1 }, { where: { cid: componentUse.cid } }, { transaction: t }) } else { yield this.ctx.model.ComponentUse.create({ cid: component.id, useNumber: 1 }, { transaction: t }) } } else { throw this.ctx.getError({ msg: '不纯在该组件' }); } yield t.commit(); return { id: query.id } } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * delete (obj) { var item = yield this.ctx.model.Component.findOne({ where: { id: obj.id, userId: obj.uid } }) if (item) { yield this.ctx.model.Component.update({ status: obj.status || 0 }, { where: { id: obj.id, userId: obj.uid } }); return true } else { throw this.ctx.getError({ msg: `该资源不可删除或者不是你上传的资源` }); } } async import ({id, token}, userId) { const valid = this.ctx.helper.tools.ossConfigValid(app.config.oss) if (!valid) throw this.ctx.getError({ msg: '您未正确配置 oss 服务的相关字段,无法使用对象存储服务' }) const that = this const zipBuffer = await downloadZip(id, token).catch(e => (console.error(e), null)) if (!zipBuffer) return const zip = await JSZip.loadAsync(zipBuffer) const manifest = await genManifest(zip.files['package.json'], userId) const exists = await co(function * () { return yield that.list({ name: `${manifest.namespace}/${manifest.name}`, version: manifest.version, uid: userId, like: false }) }) if (exists && exists.list && exists.list.length > 0) { throw this.ctx.getError({ msg: '组件库已存在相同名称和版本的组件,终止导入', }) } const files = Object.keys(zip.files).filter(k => k !== 'package.json' && k !== 'install.js').map(k => zip.files[k]) const mainFile = await ossUpload(files, manifest) const res = await co(function * () { return yield that.save({ userId: userId, name: `${manifest.namespace}/${manifest.name}`, version: manifest.version, desc: manifest.description, type: manifest.type, tags: manifest.tags, path: mainFile.replace(/^http:/, 'https:'), visibilitylevel: manifest.visibilitylevel }) }) return res } } return Component } ================================================ FILE: app/service/group.js ================================================ const nunjucks = require('nunjucks'); const noticeMessage = require('../extend/noticeMessage'); module.exports = app => { class Group extends app.Service { * delete (obj) { // 检查用户与组的权限 let groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId } }); if (!groupUser) throw this.ctx.getError({ msg: '无权限操作' }); if (groupUser.role !== app.config.appConfig.roleOwner || groupUser.status !== 1) { throw this.ctx.getError({ msg: '无操作权限' }); } // 检查分组是否存在 let group = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); if (!group) throw this.ctx.getError({ msg: '分组不存在' }); // 软删除分组信息 let t = yield app.model.transaction(); try { group = {}; group.status = 0; yield this.ctx.model.Group.update(group, { where: { id: obj.groupId }, transaction: t }); // delete all group-user relation yield this.ctx.model.GroupUser.update({ status: 0 }, { where: { groupId: obj.groupId }, transaction: t }); // delete all prioject relation const groupProjects = yield this.ctx.model.GroupProject.findAll({ where: { status: 1, groupId: obj.groupId } }); if (groupProjects && groupProjects.length > 0) { // delete group all projects let projectIds = []; groupProjects.forEach(function(e) { // 组合相应的项目信息 projectIds.push(e.projectId); }); yield this.ctx.model.GroupProject.update({ status: 0 }, { where: { projectId: { in: projectIds } } }); yield this.ctx.model.Project.update({ status: 0 }, { where: { id: { in: projectIds } } }); // delete all project usre relation yield this.ctx.model.UserProject.update({ status: 0 }, { where: { projectId: { in: projectIds } } }); // delete all interface const interfaces = yield this.ctx.model.Interface.findAll({ where: { status: 1, projectId: { in: projectIds } } }); let interfaceIds = []; interfaces.forEach(function(e) { // 组合相应的项目信息 interfaceIds.push(e.id); }); yield this.ctx.model.Interface.update({ status: 2 }, { where: { id: { in: interfaceIds } } }); // delete all interface usre relation yield this.ctx.model.InterfaceUser.update({ status: 2 }, { where: { interfaceId: { in: interfaceIds } } }); } // 发送消息删除组 let uids = yield this.ctx.model.query(`select user_id from tb_group_and_user where group_id=:groupId and status=1 and user_id !=:uid`, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: obj.uid } }); // 获取分组信息 const groupInfo = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); const title = nunjucks.renderString(noticeMessage.GROUP_DELETE.title, { userName: user.name, groupName: groupInfo.name }); const content = nunjucks.renderString(noticeMessage.GROUP_DELETE.content, { userName: user.name, groupName: groupInfo.name }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.GROUP_DELETE.code; notice.joinId = group.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeGroup); yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常' }); } } * add (group) { // 限制分组名不能重复 const groupPro = yield this.ctx.model.Group.findAll({ where: { name: group.name, status: 1 } }); if (groupPro && groupPro.length > 0) { throw this.ctx.getError({ msg: '该名称的分组已存在' }); } // 校验用户创建组的最大数 const groupNums = yield this.ctx.model.GroupUser.count({ where: { userId: group.uid, status: 1 } }); const userGrade = yield this.ctx.model.UserGrade.findOne({ where: { userId: group.uid } }); if (userGrade && userGrade.groupNum <= groupNums) { throw this.ctx.getError({ msg: '分组数目超限' }); } // 插入分组数据 group.createUserId = group.uid; group.status = 1; group.type = 1; group.projectCount = 0; group.userCount = 1; let t = yield app.model.transaction(); try { // 创建组 let groupInfo = yield this.ctx.model.Group.create(group, { transaction: t }); // 创建组合用户的关系 yield this.ctx.model.GroupUser.create({ groupId: groupInfo.id, userId: group.uid, status: 1, role: app.config.appConfig.roleOwner, }, { transaction: t }); yield t.commit(); let groupPro = {}; groupPro.description = group.description; groupPro.name = group.name; groupPro.logo = group.logo; groupPro.id = groupInfo.id; groupPro.role = app.config.appConfig.roleOwner; return groupPro; } catch (e) { yield t.rollback(); console.log(e); throw this.ctx.getError({ msg: '服务器异常', error: e }); } } * list (obj) { // 组合当前用户的信息 let wheres = {}; wheres.userId = obj.uid; wheres.status = 1; if (obj.type === 1) { wheres.role = app.config.appConfig.roleOwner; } const groupUsers = yield this.ctx.model.GroupUser.findAll({ where: wheres }); if (!groupUsers) return null; let groupIds = []; let tempGroupUser = {}; groupUsers.forEach(function(e) { // 组合相应的分组信息 groupIds.push(e.groupId); tempGroupUser[e.groupId] = e; }); const groups = yield this.ctx.model.Group.findAll({ attributes: [ 'id', 'name', 'description', 'logo', 'createTime' ], where: { status: 1, id: { in: groupIds } } }); groups.forEach(function(el) { el.dataValues.role = tempGroupUser[el.id].role; }); return groups; } * update (obj) { // 检查用户与组的权限 let groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.id } }); if (!groupUser) throw this.ctx.getError({ msg: '无权限操作' }); if (groupUser.role !== app.config.appConfig.roleOwner || groupUser.status !== 1) { throw this.ctx.getError({ msg: '无操作权限' }); } // 检查分组是否存在 let group = yield this.ctx.model.Group.findOne({ where: { id: obj.id } }); if (!group) throw this.ctx.getError({ msg: '分组不存在' }); const groupPro = this.ctx.model.Group.findAll({ where: { name: obj.name, status: 1, id: { $ne: obj.id } } }); if (groupPro && groupPro.length > 0) { throw this.ctx.getError({ msg: '该名称的分组已存在' }); } // 修改分组信息 group = {}; group.description = obj.description; group.name = obj.name; group.logo = obj.logo; console.log(group); yield this.ctx.model.Group.update(group, { where: { id: obj.id } }); let groupInfoPro = {}; groupInfoPro.description = obj.description; groupInfoPro.name = obj.name; groupInfoPro.logo = obj.logo; groupInfoPro.id = obj.id; groupInfoPro.role = groupUser.role; return groupInfoPro; } * info (obj) { // 检查用户与组的权限 let groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId } }); if (!groupUser) throw this.ctx.getError({ msg: '无权限查看' }); if (groupUser.status !== 1) { throw this.ctx.getError({ msg: '无权限查看' }); } const groupInfo = yield this.ctx.model.Group.findOne({ attributes: [ 'id', 'name', 'description', 'logo', 'createTime' ], where: { status: 1, id: obj.groupId } }); groupInfo.dataValues.role = groupUser.role; return groupInfo; } } return Group; }; ================================================ FILE: app/service/groupUser.js ================================================ const moment = require('moment'); const nunjucks = require('nunjucks'); const noticeMessage = require('../extend/noticeMessage'); module.exports = app => { class GroupUser extends app.Service { * add (obj) { // 校验用户权限 if (obj.role != app.config.appConfig.roleOwner && obj.role != app.config.appConfig.roleMaster && obj.role != app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '参数有误' }); } let groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId, status: 1 } }); if (!groupUser || (groupUser.role != app.config.appConfig.roleOwner)) { throw this.ctx.getError({ msg: '无权限操作' }); } const userIds = obj.userId; for (let i = 0; i < userIds.length; i++) { const userId = userIds[i]; if (userId == obj.uid) continue; // 查询是否已插入 groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId, groupId: obj.groupId } }); if (!groupUser) { // 插入,数量加1 let t = yield app.model.transaction(); try { yield this.ctx.model.GroupUser.create({ userId, groupId: obj.groupId, role: obj.role, status: 1 }, { transaction: t }); yield this.ctx.model.query(`update tb_group set user_count=user_count+1,update_time='${moment().utcOffset(0).format('YYYY-MM-DD HH:mm:ss')}' where id=:groupId and user_count+1>0 `, { transaction: t, replacements: obj }); // 用户加入成功,发送消息通知 let uids = yield this.ctx.model.query(`select user_id from tb_group_and_user where group_id=:groupId and user_id!=:uid and status=1`, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: userId } }); // 获取分组信息 const group = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); const title = nunjucks.renderString(noticeMessage.GROUP_ADD_USER.title, { userName: user.name, groupName: group.name }); const content = nunjucks.renderString(noticeMessage.GROUP_ADD_USER.content, { userName: user.name, groupName: group.name }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.GROUP_ADD_USER.code; notice.joinId = group.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeGroup); yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常', error: e }); } } else { // 修改信息 yield this.ctx.model.GroupUser.update({ role: obj.role, status: 1 }, { where: { userId, groupId: obj.groupId } }); // 发送消息修改项目组内的权限 let uids = yield this.ctx.model.query(`select user_id from tb_group_and_user where group_id=:groupId and user_id!=:uid and status=1`, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: userId } }); // 获取分组信息 const group = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); const title = nunjucks.renderString(noticeMessage.GROUP_ROLE_USER.title, { userName: user.name, groupName: group.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const content = nunjucks.renderString(noticeMessage.GROUP_ROLE_USER.content, { userName: user.name, groupName: group.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.GROUP_ROLE_USER.code; notice.joinId = group.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeGroup); } } } * delete (obj) { // 查询是否已插入 const groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.userId, groupId: obj.groupId } }); if (!groupUser) { throw this.ctx.getError({ msg: '当前用户不存在' }); } else { // 校验用户权限 const opGroupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId, status: 1 } }); if ((!opGroupUser || (opGroupUser.role != app.config.appConfig.roleOwner)) && obj.uid != groupUser.userId) { throw this.ctx.getError({ msg: '无权限操作' }); } if (groupUser.role == app.config.appConfig.roleOwner) { // 查询是否存在其他的owner const userOwners = yield this.ctx.model.GroupUser.count({ where: { groupId: obj.groupId, status: 1, role: app.config.appConfig.roleOwner } }); if (userOwners < 2) throw this.ctx.getError({ msg: '当前只有一位owner用户无法删除' }); } // 存在删除信息 let t = yield app.model.transaction(); try { yield this.ctx.model.GroupUser.update({ status: 0 }, { where: { userId: obj.userId, groupId: obj.groupId } }); yield this.ctx.model.query(`update tb_group set user_count=user_count-1,update_time='${moment().utcOffset(0).format('YYYY-MM-DD HH:mm:ss')}' where id=:groupId and user_count-1>0 `, { transaction: t, replacements: obj }); // 发送消息删除项目组成员 let uids = yield this.ctx.model.query(`select user_id from tb_group_and_user where group_id=:groupId and status=1 and user_id!=:uid `, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: obj.userId } }); // 获取分组信息 const group = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); const title = nunjucks.renderString(noticeMessage.GROUP_DELETE_USER.title, { userName: user.name, groupName: group.name }); const content = nunjucks.renderString(noticeMessage.GROUP_DELETE_USER.content, { userName: user.name, groupName: group.name }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.GROUP_DELETE_USER.code; notice.joinId = group.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeGroup); yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常', error: e }); } } } * list (obj) { // 组合当前用户的信息 let wheres = {}; wheres.groupId = obj.groupId; wheres.status = 1; const groupUsers = yield this.ctx.model.GroupUser.findAll({ where: wheres }); if (!groupUsers) return null; let userIds = []; let tempGroupUser = {}; groupUsers.forEach(function(e) { // 组合相应的分组信息 userIds.push(e.userId); tempGroupUser[e.userId] = e; }); const users = yield this.ctx.model.User.findAll({ attributes: [ 'id', 'name', 'photo' ], where: { id: { in: userIds } } }); users.forEach(function(el) { el.dataValues.role = tempGroupUser[el.id].role; el.dataValues.userId = el.id; delete el.dataValues.id; }); return users; } * update (obj) { // 校验用户权限 if (obj.role != app.config.appConfig.roleOwner && obj.role != app.config.appConfig.roleMaster && obj.role != app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '参数有误' }); } // 查询是否已插入 const groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.userId, groupId: obj.groupId, status: 1 } }); if (!groupUser) { // 不存在 throw this.ctx.getError({ msg: '当前用户不存在' }); } else { // 存在则修改 // 校验用户权限 const opGroupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId, status: 1 } }); if (!opGroupUser || (opGroupUser.role != app.config.appConfig.roleOwner)) { throw this.ctx.getError({ msg: '无权限操作' }); } if (groupUser.role == app.config.appConfig.roleOwner) { if (obj.role != app.config.appConfig.roleOwner) { // 查询是否存在其他的owner const userOwners = yield this.ctx.model.GroupUser.count({ where: { groupId: obj.groupId, status: 1, role: app.config.appConfig.roleOwner } }); if (userOwners < 2) throw this.ctx.getError({ msg: '当前只有一位owner用户无法修改为其他权限' }); } } yield this.ctx.model.GroupUser.update({ role: obj.role, status: 1 }, { where: { userId: obj.userId, groupId: obj.groupId } }); // 发送消息修改项目组内的权限 let uids = yield this.ctx.model.query(`select user_id from tb_group_and_user where group_id=:groupId and status=1`, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: obj.userId } }); // 获取分组信息 const group = yield this.ctx.model.Group.findOne({ where: { id: obj.groupId } }); const title = nunjucks.renderString(noticeMessage.GROUP_ROLE_USER.title, { userName: user.name, groupName: group.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const content = nunjucks.renderString(noticeMessage.GROUP_ROLE_USER.content, { userName: user.name, groupName: group.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.GROUP_ROLE_USER.code; notice.joinId = group.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeGroup); } } } return GroupUser; }; ================================================ FILE: app/service/kaptcha.js ================================================ const captchapng = require('captchapng'); module.exports = app => { class Kaptcha extends app.Service { * getCaptcha(key) { const p = new captchapng(80, 30, key); // width,height,numeric captcha p.color(0, 0, 0, 0); // First color: background (red, green, blue, alpha) p.color(80, 80, 80, 255); // Second color: paint (red, green, blue, alpha) const img = p.getBase64(); return img } } return Kaptcha; }; ================================================ FILE: app/service/mock.js ================================================ module.exports = app => { class Mock extends app.Service { * info (obj) { // 查询项目 // 查询项目对应接口信息 // 生成mock数据 console.log('---------') let shortPath = obj.path.replace(/^\//, '').replace(/\/$/, '') console.log(shortPath) const interfaceInfos = yield this.ctx.model.HistoryInterface.findAll({ where: { projectId: obj.id, path: { $in: [ shortPath, '/' + shortPath, shortPath + '/', '/' + shortPath + '/' ] }, status: 1 } }); let interfaceInfo = null interfaceInfos.forEach((value) => { if (value.type == obj.type) { interfaceInfo = value } }) if (interfaceInfo) { if(interfaceInfo.mockResponse){ return JSON.parse(interfaceInfo.mockResponse) } if (!interfaceInfo.response) throw this.ctx.getError({ msg: '无响应值' }); return this.ctx.helper.tools.jsonToMock(JSON.parse(interfaceInfo.response)); } else { if (interfaceInfos.length == 0) { throw this.ctx.getError({ msg: '接口不存在' }); } else { throw this.ctx.getError({ msg: '接口访问类型不匹配,尝试用 ' + interfaceInfos[ 0 ].type }); } } } * add (obj) { const mockInfo = yield this.ctx.model.Mock.findOne({ where: { interfaceId: obj.apiId, type: obj.type } }); if (mockInfo) { // 修改 yield this.ctx.model.Mock.update({ mockRequest: obj.mockRequest }, { where: { interfaceId: obj.apiId, type: obj.type } }); } else { // 添加 yield this.ctx.model.Mock.create({ mockRequest: obj.mockRequest, interfaceId: obj.apiId, type: obj.type }); } } } return Mock; }; ================================================ FILE: app/service/notice.js ================================================ const moment = require('moment'); module.exports = app => { class ProjectUser extends app.Service { * pullMessage (obj) { const data = yield this.ctx.model.query(`select id,create_user_id as userId,title,content,type,join_id as joinId,read_status as readStatus,create_time as time from tb_user_notice where user_id=${obj.uid} and read_status=${obj.readStatus} order by id desc limit 20`, { type: 'SELECT' }); if (data && data.length > 0) { for (let i = 0; i < data.length; i++) { const temp = data[ i ]; const user = yield this.ctx.model.User.findOne({ where: { id: temp.userId } }); temp.userName = user.name; temp.userPhoto = user.photo; } } return data; } * listMessage (obj) { let sql = 'select title,id,create_user_id as userId,content,read_status as readStatus,type,join_id as joinId,create_time as time from tb_user_notice where '; if (obj.status == 1) { sql = sql + ' read_status = 1 and '; } if (obj.status == 2) { sql = sql + ' read_status = 1 and '; } if (obj.startId > 0) { sql = sql + ' id < ' + obj.start + ' and '; } if (obj.type == 1) { sql = sql + ' type <= 200 and '; } if (obj.type == 2) { sql = sql + ' type > 200 and type <= 300 and '; } if (obj.type == 3) { sql = sql + ' type > 300 and type <= 400 and '; } sql = sql + ' user_id=' + obj.uid + ' order by id desc limit ' + obj.count; const data = yield this.ctx.model.query(sql, { type: 'SELECT' }); if (data && data.length > 0) { for (let i = 0; i < data.length; i++) { const temp = data[ i ]; const user = yield this.ctx.model.User.findOne({ where: { id: temp.userId } }); temp.userName = user.name; temp.userPhoto = user.photo; } } return data; } * changeReadStatus (obj) { yield this.ctx.model.query(` update tb_user_notice set read_status=2,update_time='${moment().utcOffset(0).format('YYYY-MM-DD HH:mm:ss')}' where id=${obj.id} `); } * getMessageInfo (obj) { const data = yield this.ctx.model.query(`select id,create_user_id as userId,title,content,type,join_id as joinId,read_status as readStatus,create_time as time from tb_user_notice where id=${obj.id}`, { type: 'SELECT' }) return data[0]; } * getMessageNums (obj) { const data = yield this.ctx.model.query(`select count(id) as total,IFNULL(sum(case when type <= 200 then 1 else 0 end),0) as groups,IFNULL(sum(case when type > 200 and type <= 300 then 1 else 0 end),0) as project,IFNULL(sum(case when type > 300 and type <= 400 then 1 else 0 end),0) as api from tb_user_notice where user_id=${obj.uid} and read_status = 1 `, { type: 'SELECT' }) return data[0]; } * getNoticeType (obj) { const data = yield this.ctx.model.query(`select type,message_notice as messageNotice,email_notice as emailNotice from tb_user_notice_type where user_id=${obj.uid}`, { type: 'SELECT' }) return data[0]; } * updateNoticeType (obj) { let sql = ' update tb_user_notice_type set '; if (obj.noticeType == 'message') { sql = sql + 'message_notice = ' + obj.noticeType + ' , update_time=' + moment().utcOffset(0).format('YYYY-MM-DD HH:mm:ss'); } else if (obj.noticeType == 'email') { sql = sql + 'email_notice = ' + obj.noticeType + ' , update_time=' + moment().utcOffset(0).format('YYYY-MM-DD HH:mm:ss'); } sql = sql + 'where user_id=' + obj.uid + ' and type=' + obj.categoryType; yield this.ctx.model.query(sql); } } return ProjectUser; }; ================================================ FILE: app/service/pages.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 上午9:30 * To change this template use File | Settings | File Templates. */ var md5 = require('md5') 'use strict' module.exports = app => { class Pages extends app.Service { * save (query) { try { var _item = { name: query.name, projectId: query.projectId, draft: query.content || '', desc: query.desc, image: query.image, key: query.key, type: query.type || 0, fork: query.fork, visibilitylevel: query.visibilitylevel || 0 // 默认公共页面 } if (query.id) { // 编辑 // 参数验证,1判断是否存在该id,2是否重名 var pageInfo = yield this.ctx.model.Pages.findOne({ where: { status: 1, id: query.id } }) var keyPage = yield this.ctx.model.Pages.findOne({ where: { key: _item.key, status: { $ne: 0 } } }) if (keyPage && keyPage.id != query.id) { throw this.ctx.getError({ msg: `新输入的页面的key:${query.key}已经存在,重新输入` }) } if (pageInfo) { yield this.ctx.model.Pages.update(_item, { where: { id: query.id } }) } else { throw this.ctx.getError({ msg: `不存在id为${query.id}的页面信息` }) } } else { // 新建页面 fork使用数为0 _item.fork = 0 // 页面不存在。生成页面信息 _item.status = 1 // 生成唯一的页面 key _item.key = this.ctx.helper.tools.md5(_item.projectId + '_' + _item.name + (new Date().getTime())) // 截取10位,然后随机做大小写切换 _item.key = _item.key.substring(0, 10).replace(/./gi, function (a) { if (parseInt(Math.random() * 10) % 3 == 0) { return a.toUpperCase() } else { return a.toLowerCase() } }) // 参数验证 是否重名 let oldPage = yield this.ctx.model.Pages.findOne({ where: { projectId: query.projectId, key: _item.key, status: { $ne: 0 } } }) if (oldPage) throw this.ctx.getError({ msg: `已经存在key为:${_item.key}的页面信息,重新提交` }) if (query.publishNow) { _item.content = _item.draft _item.draft = '' } var newItem = yield this.ctx.model.Pages.create(_item) query.id = newItem.id } // 移除标签和资源的关系 yield this.ctx.model.query(` DELETE FROM tb_res_tags_rel WHERE rid=:id and cid=1; `, { type: 'BULKDELETE', replacements: query }); // 对标签关系进行添加 query.tags = query.tags || [] for (let tag of query.tags) { yield this.ctx.service.tags.useone({ id: tag.id }) // 绑定标签和资源的关系 yield this.ctx.model.ResTagsRel.create({ rid: query.id, tid: tag.id, cid: 1 }) } return { id: query.id } } catch (e) { console.log(e) throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }) } } * list (query) { console.log('service start --list', query, query.status == undefined) if (!query.projectId) return [] // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(query.uid, query.projectId); if (role == 100) { throw this.ctx.getError({ msg: '无操作权限' }); } let projectIds = (query.projectId instanceof Array ? query.projectId : [query.projectId]).filter(id => id && id > 0) if (!projectIds.length) return [] let _where = { projectId: { '$or': projectIds }, status: (query.status == undefined) ? { $ne: 0 } : query.status } let list = yield this.ctx.model.Pages.findAll({ where: _where, order: 'create_time DESC' }) if (list && list.length) { list = list.map(item => { var it = item.dataValues || {} if (it.id) { try { let content = it.draft || it.content it.psdList = JSON.parse(content).psdList || [] } catch (error) { it.psdList = [] } it.isPublish = !it.draft delete it.content delete it.draft } return it }) } return list } * publiclist (query) { console.log('service start --list', query, query.status == undefined) var sqlQuery = '' if (query.name) { query.name = `%${query.name}%` sqlQuery += ` and pages.name like :name`; } if (query.tags && query.tags.length != 0) { sqlQuery += ` and t.id in (${query.tags.map(val => val.id).join(',')})` // query.tagsStr = query.tags.map(val => val.id).join(',') // sqlQuery += ` and t.id in (:tagsStr)` } let list = yield this.ctx.model.query(`SELECT distinct pages.id, pages.key, pages.name, pages.fork, pages.image, pages.desc, pages.draft, pages.type, pages.project_id AS projectId, pages.status, pages.visibilitylevel, pages.update_time AS updateTime, pages.create_time AS createTime FROM tb_pages AS pages left join tb_res_tags_rel rel on pages.id=rel.rid left join tb_tags t on rel.tid=t.id WHERE ${!query.featured ? '' : 'pages.featured = 1 AND'} pages.visibilitylevel = 1 AND pages.status = 1 ${sqlQuery} `,{type:'SELECT', replacements: query}) // if (list && list.length) { // list = list.map(item => { // var it = item.dataValues || {} // if (it.id) { // it.isPublish = !it.draft // delete it.draft // } // return it // }) // } // console.log(list) return list || [] } /** * 查询页面内容 * 1:公共页面直接提供查询 * 2:非公共页面只有当改页面所在的项目属于该用户才可见,否则提示权限不够 * @param query * @return {*} */ * info (query) { let page = yield this.ctx.model.Pages.findOne({ where: { id: query.id } }) // 查询当前用户的角色 if (page) { const role = yield this.ctx.service.base.getUserRole(query.uid, page.projectId) page.dataValues.role = role // 查询标签 let tags = yield this.ctx.model.query(`select t.id, t.name from tb_res_tags_rel rel left join tb_tags t on t.id=rel.tid where rel.rid =:id and cid=1 `, { type: 'SELECT', replacements: query }) page.dataValues.tags = tags } return page } * detail (query) { let info = (yield this.ctx.model.Pages.findOne({ where: { key: query.pageKey, status: { $ne: 0 } } })) || {} info = info.dataValues || {} if (info.id) { if (['edit', 'preview', 'copy'].includes(query.scene)) { info.content = info.draft || info.content } info.isPublish = !info.draft delete info.draft } // 如果有历史记录id 则查询历史记录并设置到content里面 if (query.historyid) { let history = yield this.ctx.model.PagesHistory.findOne({ where: { id: query.historyid } }) if (history) { info.content = history.content info.historyId = query.historyid } } return info } * setHomePage (query) { try { // 设置当前项为"首页" yield this.ctx.model.Pages.update({ isHomePage: 1 }, { where: { id: query.id } }) var pageInfo = yield this.ctx.model.Pages.findOne({ where: { id: query.id } }) // 将其他页面的isHomePage 设置为0 yield this.ctx.model.Pages.update({ isHomePage: 0 }, { where: { projectId: pageInfo.projectId, id: { $ne: query.id } } }) return null } catch (e) { throw this.ctx.getError({ msg: e.msg || `操作失败`, error: e }) } } * changeStatus (query) { try { // 参数验证,1判断是否存在该id,2是否重名 if (!(yield this.ctx.model.Pages.findOne({ where: { id: query.id } }))) throw this.ctx.getError({ msg: `不存在id为${query.id}的页面` }) yield this.ctx.model.Pages.update({ status: query.status }, { where: { id: query.id } }) return null } catch (e) { throw this.ctx.getError({ msg: e.msg || `操作失败`, error: e }) } } * delete (query) { var item = yield this.ctx.model.Pages.findOne({ where: { id: query.id } }) if (item) { // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(query.uid, item.projectId); if (role > 2) { throw this.ctx.getError({ msg: '您缺少“管理员”或更高权限,无法删除,请联系该页面管理人员' }); } yield this.ctx.model.Pages.update({ status: 0 }, { where: { id: query.id } }) return true } } * publish (query) { // 插入发布 yield this.ctx.model.Pages.update({ content: query.content, draft: '' }, { where: { id: query.id } }) // 插入历史记录 yield this.ctx.model.PagesHistory.create({ content: query.content, userId: query.uid, pageId: query.id }) return true } * count () { return yield this.ctx.model.Pages.count({ where: { key: { $ne: null } } }) } * history (query) { let _where = { pageId: query.pageId, status: (query.status == undefined) ? 1 : query.status } let list = yield this.ctx.model.query(`select ph.id, ph.content, ph.page_id as pageId, ph.create_time as createTime, u.id as userId, u.name as userName, u.photo as userPhoto from tb_pages_history ph left join tb_user u on u.id=ph.user_id where page_id=:pageId and status=:status order by createTime desc `, { type: 'SELECT', replacements: _where }); return list } * getNameBykeys ({ ids }) { if (!ids || !ids.length) return [] let ctx = this.ctx let where = { $or: ids.map(id => ({ key: id })) } let names = yield ctx.model.Pages.findAll({ where, attributes: [ 'name', 'key', 'id', 'desc' ] }) return names } * deleteHistory (query) { var item = yield this.ctx.model.PagesHistory.findOne({ where: { id: query.id } }) console.log(item) if (item) { yield item.destroy(); return true } } * historyPublish (query) { // 插入发布 yield this.ctx.model.Pages.update({ content: query.content, draft: query.content }, { where: { id: query.id } }) // 插入历史记录 // yield this.ctx.model.PagesHistory.create({ // content: query.content, // userId: query.uid, // pageId: query.id // }) return true } * historyToDraft (query) { yield this.ctx.model.Pages.update({ draft: query.content }, { where: { id: query.id } }) return true } * updateFork (query) { yield this.ctx.model.Pages.findOne({where: { id: query.id }}).then(function(page) { return page.increment([ 'fork' ], {by: 1, silent: true}) }) return true } async featuringPages ({ monthBefore = 3}) { const date = (() => { const ts = new Date(Date.now() - monthBefore * 86400000 * 30) return `${ts.getFullYear()}-${ts.getMonth() + 1}-${ts.getDate()}` })() const list = await this.ctx.model.Pages.findAll({ attributes: ['id', 'key', 'name', 'image', 'desc', 'fork', 'featured'], limit: 50, // order: ['updateTime', 'DESC'], where: { updateTime: { '$gte': date }, visibilitylevel: 1, featured: 0, status: 1, } }) .then((pages = []) => pages.map(v => v.dataValues)) return list } async updateFeatured ({ value = 2, id, uid, key = '' }) { const role = await this.ctx.service.user.getUserRole({ uid }) if (role !== 1) throw this.ctx.getError({msg: '用户无权限'}) if (!/^\w{3,}$/.test(key) && !/^\d+$/.test(id)) throw this.ctx.getError({msg: '未提供参数 id 或者 key'}) await this.ctx.model.Pages.update({ featured: value }, { where: /^\w{3,}$/.test(key) ? { key } : { id } }) return true } } return Pages } ================================================ FILE: app/service/project.js ================================================ const nunjucks = require('nunjucks'); const noticeMessage = require('../extend/noticeMessage'); module.exports = app => { class Project extends app.Service { * delete (obj) { // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.projectId); if (role != app.config.appConfig.roleOwner) { throw this.ctx.getError({ msg: '无操作权限' }); } // 检查项目是否存在 let projectTemp = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); if (!projectTemp) throw this.ctx.getError({ msg: '项目不存在' }); // 软删除分组信息 let t = yield app.model.transaction(); try { let project1 = {}; project1.status = 0; yield this.ctx.model.Project.update(project1, { where: { id: obj.projectId }, transaction: t }); // delete all group-project relation yield this.ctx.model.GroupProject.update({ status: 0 }, { where: { projectId: obj.projectId }, transaction: t }); yield this.ctx.model.UserProject.update({ status: 0 }, { where: { projectId: obj.projectId }, transaction: t }); // delete all page const interfaces = yield this.ctx.model.Pages.findAll({ where: { status: 1, projectId: obj.projectId } }); if (interfaces && interfaces.length > 0) { let interfaceIds = []; interfaces.forEach(function (e) { // 组合相应的项目信息 interfaceIds.push(e.id); }); yield this.ctx.model.Pages.update({ status: 0 }, { where: { id: { in: interfaceIds } } }); } yield t.commit(); } catch (e) { yield t.rollback(); console.log(e) throw this.ctx.getError({ msg: '服务器异常', e }); } } * add (obj) { // 校验分组的权限 const groupUser = yield this.ctx.model.GroupUser.findOne({ where: { userId: obj.uid, groupId: obj.groupId, status: 1 } }); if (!groupUser) throw this.ctx.getError({ msg: '无权限操作' }); if (groupUser.role > app.config.appConfig.roleMaster) { throw this.ctx.getError({ msg: '无操作权限' }); } // 校验用户创建项目的最大数 const projectNums = this.ctx.model.UserProject.count({ where: { userId: obj.uid, status: 1 } }); const userGrade = this.ctx.model.UserGrade.findOne({ where: { userId: obj.uid } }); if (userGrade && userGrade.groupNum <= projectNums) { throw this.ctx.getError({ msg: '项目数目超限' }); } // 插入项目数据 const project = obj; project.createUserId = obj.uid; project.status = 1; project.key = 'projectdefault' let t = yield app.model.transaction(); try { // 创建项目 let projectInfo = yield this.ctx.model.Project.create(project, { transaction: t }); // 创建项目与用户的关系 // yield this.ctx.model.UserProject.create({ // projectId: projectInfo.id, // userId: project.uid, // status: 1, // role: app.config.appConfig.roleOwner, // favor: 0 // }, { transaction: t }); // 创建项目与组关系 yield this.ctx.model.GroupProject.create({ projectId: projectInfo.id, groupId: project.groupId, status: 1, }, { transaction: t }); yield t.commit(); return projectInfo.id; } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常', error: e }); } } * list (obj) { // 组合当前用户的信息 let wheres = {}; wheres.userId = obj.uid; wheres.status = 1; const userProjects = yield this.ctx.model.UserProject.findAll({ where: wheres }); if (!userProjects) return null; let projectIds = []; let userIds = []; let projectMap = {}; let groupMap = {}; let userMap = {}; let groupProjectMap = {}; userProjects.forEach(function (e) { // 组合相应的分组信息 projectIds.push(e.projectId); }); // 查询本人创建的项目 const ownerProjects = yield this.ctx.model.Project.findAll({ where: { status: 1, createUserId: obj.uid } }); if (ownerProjects && ownerProjects.length > 0) { for (let i = 0; i < ownerProjects.length; i++) { let e = ownerProjects[ i ]; // 组合相应的分组信息 if (projectIds.indexOf(e.id) == -1) { projectIds.push(e.id); // userProjects也增加,查询用户在组里的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, e.id); userProjects.push({ projectId: e.id, role }); } } } // 查询用户参与的组 const tempGroupIds = []; const groupUsers = yield this.ctx.model.GroupUser.findAll({ where: { status: 1, userId: obj.uid } }); if (groupUsers && groupUsers.length > 0) { for (let i = 0; i < groupUsers.length; i++) { let el = groupUsers[ i ]; tempGroupIds.push(el.groupId); } } if (tempGroupIds && tempGroupIds.length > 0) { const projectInfos = yield this.ctx.model.GroupProject.findAll({ where: { status: 1, groupId: { in: tempGroupIds } } }); if (projectInfos && projectInfos.length > 0) { for (let i = 0; i < projectInfos.length; i++) { let et = projectInfos[ i ]; // 组合相应的分组信息 if (projectIds.indexOf(et.projectId) == -1) { projectIds.push(et.projectId); // userProjects也增加,查询用户在组里的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, et.projectId); userProjects.push({ projectId: et.projectId, role }); } } } } const projects = yield this.ctx.model.Project.findAll({ where: { status: 1, id: { in: projectIds } } }); if (!projects) return null; projects.forEach(function (e) { // 组合相应的信息 projectMap[ e.id ] = e; userIds.push(e.createUserId); }); // 查询分组信息 const groupProjects = yield this.ctx.model.GroupProject.findAll({ where: { status: 1, projectId: { in: projectIds } } }); if (!groupProjects) return null; let groupIds = []; groupProjects.forEach(function (e) { // 组合相应的分组信息 groupIds.push(e.groupId); groupProjectMap[ e.projectId ] = e; }); const groups = yield this.ctx.model.Group.findAll({ where: { status: 1, id: { in: groupIds } } }); if (!groups) return null; groups.forEach(function (e) { // 组合相应的信息 groupMap[ e.id ] = e; }); // 查询用户信息 const users = yield this.ctx.model.User.findAll({ where: { id: { in: userIds } } }); if (!users) return null; users.forEach(function (e) { // 组合相应的信息 userMap[ e.id ] = e; }); let result = []; userProjects.forEach(function (e) { // 组合相应的分组信息 const temp = {}; temp.id = e.projectId; if (projectMap[ e.projectId ]) { temp.projectName = projectMap[ e.projectId ].name; temp.image = projectMap[ e.projectId ].image; temp.role = e.role; temp.desc = projectMap[ e.projectId ].desc; temp.groupId = groupProjectMap[ e.projectId ].groupId; temp.groupName = groupMap[ temp.groupId ].name; temp.creatorId = projectMap[ e.projectId ].createUserId; temp.creatorName = userMap[ temp.creatorId ].name; temp.createTime = projectMap[ e.projectId ].createTime; result.push(temp); } }); return result; } * update (obj) { // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.id); if (role != app.config.appConfig.roleOwner) { throw this.ctx.getError({ msg: '无操作权限' }); } // 查询项目与组关系 const groupProject = yield this.ctx.model.GroupProject.findOne({ where: { projectId: obj.id } }); if (!groupProject) throw this.ctx.getError({ msg: '项目对应分组不存在' }); // 检查项目是否存在 let oldProject = yield this.ctx.model.Project.findOne({ where: { id: obj.id } }); if (!oldProject) throw this.ctx.getError({ msg: '项目不存在' }); // 修改项目信息 var project = {} project.desc = obj.desc; project.name = obj.name; project.image = obj.image; project.status = oldProject.status; project.visibilitylevel = obj.visibilitylevel; yield this.ctx.model.Project.update(project, { where: { id: obj.id } }); yield this.ctx.model.GroupProject.update({ groupId: obj.groupId }, { where: { projectId: obj.id } }); } * info (obj) { // 检查用户与项目的权限 const groupProject = yield this.ctx.model.GroupProject.findOne({ where: { projectId: obj.projectId } }); if (!groupProject) throw this.ctx.getError({ msg: '项目对应分组不存在' }); const result = {}; // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.projectId); if (role == 100) { throw this.ctx.getError({ msg: '无操作权限' }); } // 组合数据 const project = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); const group = yield this.ctx.model.Group.findOne({ where: { id: groupProject.groupId } }); const user = yield this.ctx.model.User.findOne({ where: { id: project.createUserId } }); result.id = obj.projectId; result.projectName = project.name; result.image = project.image; result.role = role; result.desc = project.desc; result.visibilitylevel = project.visibilitylevel; result.groupId = group.id; result.groupName = group.name; result.creatorId = project.createUserId; result.creatorName = user.name; result.createTime = project.createTime; result.security = project.security; result.ddwebhook = project.ddwebhook; return result; } * groupProjects (obj) { // 查询用户与组的信息 const groupUser = yield this.ctx.model.GroupUser.findOne({ where: { status: 1, groupId: obj.groupId, userId: obj.uid } }); if (!groupUser) this.ctx.getError({ msg: '无权限操作' }); const groupProjects = yield this.ctx.model.GroupProject.findAll({ where: { status: 1, groupId: obj.groupId } }); if (!groupProjects) return null; let projectIds = []; let userIds = []; let userMap = {}; let groupProjectMap = {}; let userProjectMap = {}; groupProjects.forEach(function (e) { // 组合相应的分组信息 groupProjectMap[ e.projectId ] = e; projectIds.push(e.projectId); }); const projects = yield this.ctx.model.Project.findAll({ where: { status: 1, id: { in: projectIds } }, order: 'update_time desc' }); if (!projects) return null; projects.forEach(function (e) { userIds.push(e.createUserId); }); // 查询用户信息 const users = yield this.ctx.model.User.findAll({ where: { id: { in: userIds } } }); if (!users) return null; users.forEach(function (e) { // 组合相应的信息 userMap[ e.id ] = e; }); const userProjects = yield this.ctx.model.UserProject.findAll({ where: { status: 1, userId: obj.uid, projectId: { in: projectIds } } }); userProjects.forEach(function (e) { // 组合相应的信息 userProjectMap[ e.projectId ] = e; }); let result = []; for (let i = 0; i < projects.length; i++) { let e = projects[ i ]; // 组合相应的分组信息 const temp = {}; temp.id = e.id; temp.projectName = e.name; temp.image = e.image; // 检查用户与项目的权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, e.id); temp.role = role; temp.desc = e.desc; temp.creatorId = e.createUserId; temp.creatorName = userMap[ e.createUserId ].name; temp.createTime = e.createTime; result.push(temp); } return result; } * favorateProject (obj) { // 校验用户关注项目的最大数 const favorProjectrojectNums = this.ctx.model.UserProject.count({ where: { userId: obj.uid, status: 1, favor: 1 } }); const userGrade = this.ctx.model.UserGrade.findOne({ where: { userId: obj.uid } }); if (!userGrade && userGrade.favorateProjectNum <= favorProjectrojectNums) { throw this.ctx.getError({ msg: '关注项目数目超限' }); } // 判断用户是否已关注 const userProject = this.ctx.model.UserProject.findOne({ where: { userId: obj.uid, projectId: obj.id } }); if (!userProject || userProject.status != 1) { throw this.ctx.getError({ msg: '项目不存在' }); } if (userProject && userProject.status == 1 && userProject.favor == 1) { return ''; } this.ctx.model.UserProject.update({ status: 1, favor: 1 }, { where: { userId: obj.uid, projectId: obj.id } }); } * cancelFavorateProject (obj) { // 判断用户是否已关注 const userProject = this.ctx.model.UserProject.findOne({ where: { userId: obj.uid, projectId: obj.id } }); if (!userProject || userProject.status == 0) { throw this.ctx.getError({ msg: '项目不存在' }); } if (userProject && userProject.favor != 1) { return ''; } this.ctx.model.UserProject.update({ favor: 0 }, { where: { userId: obj.uid, projectId: obj.id } }); } * getFavorateProject (obj) { // 组合当前用户的信息 let wheres = {}; wheres.userId = obj.uid; wheres.status = 1; const userProjects = yield this.ctx.model.UserProject.findAll({ where: wheres }); if (!userProjects) return null; let projectIds = []; let userIds = []; let projectMap = {}; let userMap = {}; userProjects.forEach(function (e) { // 组合相应的分组信息 projectIds.push(e.projectId); }); const projects = yield this.ctx.model.Project.findAll({ where: { status: 1, id: { in: projectIds } } }); if (!projects) return null; projects.forEach(function (e) { // 组合相应的信息 projectMap[ e.id ] = e; userIds.push(e.createUserId); }); // 查询用户信息 const users = yield this.ctx.model.User.findAll({ where: { id: { in: userIds } } }); if (!users) return null; users.forEach(function (e) { // 组合相应的信息 userMap[ e.id ] = e; }); let result = []; userProjects.forEach(function (e) { // 组合相应的分组信息 const temp = {}; temp.id = e.projectId; temp.projectName = projectMap[ e.projectId ].name; temp.image = projectMap[ e.projectId ].image; temp.role = e.role; temp.desc = projectMap[ e.projectId ].desc; temp.creatorId = projectMap[ e.projectId ].createUserId; temp.creatorName = userMap[ temp.creatorId ].name; temp.createTime = projectMap[ e.projectId ].createTime; result.push(temp); }); return result; } } return Project; }; ================================================ FILE: app/service/projectUser.js ================================================ const nunjucks = require('nunjucks'); const noticeMessage = require('../extend/noticeMessage'); module.exports = app => { class ProjectUser extends app.Service { * add (obj) { // 校验用户权限 if (obj.role != app.config.appConfig.roleDev && obj.role != app.config.appConfig.roleGest && obj.role != app.config.appConfig.roleMaster) { throw this.ctx.getError({ msg: '参数有误' }); } const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.projectId); if (role > app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '无权限操作' }); } const userIds = obj.userId; for (let i = 0; i < userIds.length; i++) { const userId = userIds[i]; // 查询是否已插入 const userProject = yield this.ctx.model.UserProject.findOne({ where: { userId, projectId: obj.projectId } }); if (!userProject) { // 插入 yield this.ctx.model.UserProject.create({ userId, projectId: obj.projectId, role: obj.role, status: 1 }); // 添加项目成员成功,发送消息通知 let uids = yield this.ctx.model.query(`select DISTINCT user_id from tb_user_and_project where project_id=:projectId and user_id!=:uid and status =1 `, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: userId } }); // 获取项目信息 const project = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); const title = nunjucks.renderString(noticeMessage.PROJECT_ADD_USER.title, { userName: user.name, projectName: project.name }); const content = nunjucks.renderString(noticeMessage.PROJECT_ADD_USER.content, { userName: user.name, projectName: project.name }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.PROJECT_ADD_USER.code; notice.joinId = project.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeProject); } else { // 修改信息 yield this.ctx.model.UserProject.update({ role: obj.role, status: 1 }, { where: { userId, projectId: obj.projectId } }); // 添加项目成员修改权限成功,发送消息通知 let uids = yield this.ctx.model.query(`select DISTINCT user_id from tb_user_and_project where project_id=:projectId and user_id!=:uid and status =1 `, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: userId } }); // 获取项目信息 const project = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); const title = nunjucks.renderString(noticeMessage.PROJECT_ROLE_USER.title, { userName: user.name, projectName: project.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const content = nunjucks.renderString(noticeMessage.PROJECT_ROLE_USER.content, { userName: user.name, projectName: project.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.PROJECT_ROLE_USER.code; notice.joinId = project.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeProject); } } } * delete (obj) { // 查询是否已插入 let projectUser = yield this.ctx.model.UserProject.findOne({ where: { userId: obj.userId, projectId: obj.projectId } }); if (!projectUser) { throw this.ctx.getError({ msg: '当前用户不存在' }); } else { // 校验用户权限 const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.projectId); if (role > app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '无权限操作' }); } // 存在删除信息 let t = yield app.model.transaction(); try { yield this.ctx.model.UserProject.update({ status: 0 }, { where: { userId: obj.userId, projectId: obj.projectId } }); // 删除项目成员成功,发送消息通知 let uids = yield this.ctx.model.query(`select DISTINCT user_id from tb_user_and_project where project_id=:projectId and status =1 and user_id != :uid `, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: obj.userId } }); // 获取项目信息 const project = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); const title = nunjucks.renderString(noticeMessage.PROJECT_DELETE_USER.title, { userName: user.name, projectName: project.name }); const content = nunjucks.renderString(noticeMessage.PROJECT_DELETE_USER.content, { userName: user.name, projectName: project.name }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.PROJECT_DELETE_USER.code; notice.joinId = project.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeProject); yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常', error: e }); } } } * list (obj) { // 组合当前用户的信息 let wheres = {}; wheres.projectId = obj.projectId; wheres.status = 1; const projectUsers = yield this.ctx.model.UserProject.findAll({ where: wheres }); if (!projectUsers) return null; let userIds = []; let tempGroupUser = {}; projectUsers.forEach(function(e) { // 组合相应的分组信息 userIds.push(e.userId); tempGroupUser[e.userId] = e; }); const users = yield this.ctx.model.User.findAll({ attributes: [ 'id', 'name', 'photo' ], where: { id: { in: userIds } } }); users.forEach(function(el) { el.dataValues.role = tempGroupUser[el.id].role; el.dataValues.userId = el.id; delete el.dataValues.id; }); return users; } * update (obj) { // 校验用户权限 if (obj.role != app.config.appConfig.roleMaster && obj.role != app.config.appConfig.roleGest && obj.role != app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '参数有误' }); } // 查询是否已插入 const projectUser = yield this.ctx.model.UserProject.findOne({ where: { userId: obj.userId, projectId: obj.projectId, status: 1 } }); if (!projectUser) { // 不存在 throw this.ctx.getError({ msg: '当前用户不存在' }); } else { const role = yield this.ctx.service.base.getUserRole(obj.uid, obj.projectId); if (role > app.config.appConfig.roleDev) { throw this.ctx.getError({ msg: '无权限操作' }); } // 存在则修改 yield this.ctx.model.UserProject.update({ role: obj.role, status: 1 }, { where: { userId: obj.userId, projectId: obj.projectId } }); // 项目成员修改权限成功,发送消息通知 let uids = yield this.ctx.model.query(`select DISTINCT user_id from tb_user_and_project where project_id=:projectId and status =1 `, { type: 'SELECT', replacements: obj }); uids = uids.map((value) => value.user_id); // 获取用户信息 const user = yield this.ctx.model.User.findOne({ where: { id: obj.userId } }); // 获取项目信息 const project = yield this.ctx.model.Project.findOne({ where: { id: obj.projectId } }); const title = nunjucks.renderString(noticeMessage.PROJECT_ROLE_USER.title, { userName: user.name, projectName: project.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const content = nunjucks.renderString(noticeMessage.PROJECT_ROLE_USER.content, { userName: user.name, projectName: project.name, role: this.ctx.helper.tools.getGroupRole(obj.role - 0) }); const notice = {}; notice.createUserId = obj.uid; notice.content = content; notice.title = title; notice.readStatus = 1; notice.type = noticeMessage.PROJECT_ROLE_USER.code; notice.joinId = project.id; yield this.ctx.service.base.sendNotice(uids, notice, app.config.appConfig.noticeTypeProject); } } } return ProjectUser; }; ================================================ FILE: app/service/resources.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 上午9:30 * To change this template use File | Settings | File Templates. */ var md5 = require('md5'); 'use strict' module.exports = app => { class Resources extends app.Service { * info (obj) { var item = yield this.ctx.model.Resources.findOne({ where: { id: obj.id, userId: obj.uid } }) var query = { id: obj.id, categoryId: item.categoryId } let tags = yield this.ctx.model.query(`select t.id, t.name from tb_res_tags_rel rel left join tb_tags t on t.id=rel.tid where rel.rid =:id and cid=:categoryId `, { type: 'SELECT', replacements: query }) item.dataValues.tags = tags return item } * list (query) { // 查询条件 var sqlQuery = `(r.visibilitylevel=1 or r.user_id=:uid ) and r.status=1` if (query.categoryId) { sqlQuery = sqlQuery + ` and r.category_id=:categoryId`; } if (query.name) { query.name = `%${query.name}%` sqlQuery = sqlQuery + ` and r.name like :name`; } if (query.tags && query.tags.length != 0) { sqlQuery += ` and t.id in (${query.tags.map(val => val.id).join(',')})` // query.tagsStr = `${query.tags.map(val => val.id).join(',')}` // sqlQuery += ` and t.id in (:tagsStr)` } // 分页计算 let offset = 0, page = +query.page || 1, pageSize = +query.pageSize || 10 if (query.page < 1) query.page = 1 if (query.pageSize < 1) query.pageSize = 10 offset = (page - 1) * pageSize || 0 // 查询语句 var sql = `SELECT GROUP_CONCAT(t.name,',',t.id SEPARATOR ';') as tags, r.user_id as userId, r.icon, r.desc, r.id,r.name,r.content,r.category_id as categoryId,r.create_time as createTime,r.update_time as updateTime from tb_resources r left join tb_res_tags_rel rel on r.id=rel.rid left join tb_tags t on rel.tid=t.id where ${sqlQuery} GROUP BY r.id ORDER BY r.create_time DESC LIMIT ${offset}, ${pageSize};` // 查询总条数 var total = yield this.ctx.model.query(`SELECT count(r.id) as counts from tb_resources r left join tb_res_tags_rel rel on r.id=rel.rid left join tb_tags t on rel.tid=t.id where ${sqlQuery} GROUP BY r.id `, { type: 'SELECT', replacements: query }); // 查询列表数据 var list = yield this.ctx.model.query(sql, { type: 'SELECT', replacements: query }) || [] list = list.map(it => { it.tags = it.tags ? ((it.tags.split(';') || []).map(t => { var ts = t.split(',') || [] return { id: ts[ 1 ], name: ts[ 0 ] } })) : [] return it }) return { total: total.length, list }; } * save (query) { let t = yield app.model.transaction(); try { var _item = { name: query.name, categoryId: query.categoryId, content: query.content, userId: query.uid, desc: query.desc, icon: query.icon, visibilitylevel: query.visibilitylevel } // 如果没有用户信息,资源设为公共资源 if (!query.uid) { _item.visibilitylevel = 1 } // 资源信息保存或修改 if (query.id) { // 编辑 if (!(yield this.ctx.model.Resources.findOne({ where: { status: 1, id: query.id } }))) throw this.ctx.getError({ msg: `不存在id为${query.id}的资源` }); yield this.ctx.model.Resources.update(_item, { where: { id: query.id } }, { transaction: t }) } else { _item.status = 1 var newItem = yield this.ctx.model.Resources.create(_item, { transaction: t }) query.id = newItem.id } // 移除标签和资源的关系 yield this.ctx.model.query(` DELETE FROM tb_res_tags_rel WHERE rid=:id and cid=:categoryId; `, { type: 'BULKDELETE', transaction: t, replacements: query }); query.tags = query.tags || [] console.info(query.tags) for (let tag of query.tags) { yield this.ctx.service.tags.useone({ id: tag.id }) // 绑定标签和资源的关系 yield this.ctx.model.ResTagsRel.create({ rid: query.id, tid: tag.id, cid: query.categoryId }, { transaction: t }) } yield t.commit(); return { id: query.id } } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * delete (obj) { var item = yield this.ctx.model.Resources.findOne({ where: { id: obj.id, userId: obj.uid } }) if (item) { yield this.ctx.model.Resources.update({ status: 0 }, { where: { id: obj.id, userId: obj.uid } }); return true } else { throw this.ctx.getError({ msg: `该资源不可删除或者不是你上传的资源` }); } } * addUseCount (obj) { var item = yield this.ctx.model.Resources.findOne({ where: { id: obj.id } }) if (item) { var useCount = (item.useCount || 0) + 1 yield this.ctx.model.Resources.update({ useCount }, { where: { id: obj.id } }); return { useCount, id: item.id } } else { throw this.ctx.getError({ msg: '未查询到此资源' }); } } } return Resources } ================================================ FILE: app/service/statistics.js ================================================ const es = require('elasticsearch') const throttle = require("lodash/throttle") module.exports = app => { const ES_SERVER = app.config.es.host const ES_INDEX = app.config.es.index const ES_TYPE = app.config.es.type const esClient = new es.Client({ host: ES_SERVER, apiVersion: '5.6', log: process.env.NODE_ENV === 'production' ? 'error' : 'trace' }) var notify = throttle((err) => { app.DDNotify(`${err.name} \n\n ${err.message} \n\n ${err.stack}`, 'ES请求异常') }, 30000) let reportsQueue = [] class Statistics extends app.Service { report (doc) { if (doc instanceof Array) { reportsQueue = reportsQueue.concat(doc) } else { reportsQueue.push(doc) } } async addDoc (doc) { let {ctx} = this let res = {created: false} let nowTime = new Date() let indices = ES_INDEX + ctx.helper.tools.timeFormat(nowTime, '_yyyy_MM') try { await this.createIndex(indices) res = await esClient.index({ index: indices, type: ES_TYPE, body: doc }) } catch (e) { console.error(e) notify(e) } return res.created } async addDocs (docs) { let fromQuene = false if (!docs || !docs.length) { fromQuene = true docs = reportsQueue.slice(0) } let {ctx} = this if (toString.call(docs) !== toString.call(docs)) { console.warn('addDocs: docs is not a Array, canceled') return true } let res = {errors: true} let nowTime = new Date() let indices = ES_INDEX + ctx.helper.tools.timeFormat(nowTime, '_yyyy_MM') let orderedDocs = docs.filter(d => d && d.create_time > 0).map(d => [ { index: { _index: indices, _type: ES_TYPE } }, d // the document to index ]) if (!orderedDocs.length) { console.warn('addDocs: no valid docs be added') return true } let orders = orderedDocs.reduce((o, d) => o.concat(d), []) try { await this.createIndex(indices) res = await esClient.bulk({ body: orders }) if (fromQuene) reportsQueue = [] } catch (e) { console.error(e) notify(e) } return !res.errors } async search ({filter, timePeriod = [], size = 10, aggs, mustNot} = {}) { let {ctx} = this let res = {} let timeRange let patterns = filter if (timePeriod.length > 0) { timeRange = ['gte', 'lte'] .map((t, i) => [t, timePeriod[i]]) .reduce((o, t) => { if (t[1] > 0) o[t[0]] = t[1] return o }, {}) patterns.push({range: { create_time: timeRange }}) } let body = { query: { bool: { filter: patterns } }, aggs, size } if (mustNot) body.query.bool.must_not = mustNot try { res = await esClient.search({ index: ES_INDEX + '*', body, }) } catch (e) { console.error(e) notify(e) } return res } async getPUV ({pageId, pageIds, timePeriod, utm, interval = 'day', appId = 'tview'}) { if (['day', 'hour', 'week'].indexOf(interval) < 0) interval = 'day' let {hits, aggregations} = await this.search( { filter: (() => { var li = [ {term: { 'action.keyword': 'pageview' }}, {term: { 'app_id.keyword': appId }}, ] if (pageIds && pageIds.length) li.push({terms: { 'page_id.keyword': pageIds }}) else if (pageId) li.push({term: { 'page_id.keyword': pageId }}) if (utm && utm.length >= 1 && utm !== 'N/A') li.push({term: { 'utm_campaign.keyword': utm }}) return li })(), timePeriod, mustNot: utm === 'N/A' ? { exists: { field: 'utm_campaign' } } : null, size: 0, aggs: { histogram: { date_histogram: { field: 'create_time', time_zone: "+08:00", interval: interval, }, aggs: { distinct_user: { cardinality: { script: { lang: 'painless', inline: 'doc["page_id.keyword"].value + "_" + doc["finger_print.keyword"].value' } } } } }, distinct_user: { sum_bucket: { buckets_path: 'histogram>distinct_user' } }, all_user: { cardinality: { script: { lang: 'painless', inline: 'doc["page_id.keyword"].value + "_" + doc["finger_print.keyword"].value' } } }, } } ) return { sum: { pv: hits && hits.total || 0, uv: aggregations && aggregations['distinct_user'].value, user: aggregations && aggregations['all_user'].value, }, histogram: (aggregations && aggregations.histogram.buckets || []).map(h => { return { dateStr: h.key_as_string, date: h.key, pv: h.doc_count, uv: h.distinct_user && h.distinct_user.value } }) } } async getPages ({appId, timePeriod, size = 20}) { let {aggregations} = await this.search( { filter: [ {term: { 'action.keyword': 'pageview' }}, {term: { 'app_id.keyword': appId }}, ], timePeriod, size: 0, aggs: { page_count: { cardinality: { field : "page_id.keyword" } }, page_list: { terms: { field: 'page_id.keyword', size: size }, aggs: { distinct_user: { cardinality: { field: "finger_print.keyword" } } } } } } ) return { count: aggregations && aggregations.page_count.value || 0, list: aggregations && aggregations.page_list.buckets.map(p => { return { key: p.key, pv: p.doc_count, uv: p.distinct_user.value || 0 } }) } } async getActions ({appId, pageId, utm, timePeriod, size = 20}) { let {aggregations} = await this.search( { filter: (() => { var li = [ {term: { 'app_id.keyword': appId }}, {term: { 'page_id.keyword': pageId }}, ] if (utm && utm.length >= 1 && utm !== 'N/A') li.push({term: { 'utm_campaign.keyword': utm }}) return li })(), timePeriod, mustNot: utm === 'N/A' ? { exists: { field: 'utm_campaign' } } : null, size: 0, aggs: { action_count: { cardinality: { field : "action.keyword" } }, action_list: { terms: { field: 'action.keyword', size: size } } } } ) return { count: aggregations && aggregations.action_count.value || 0, list: aggregations && aggregations.action_list.buckets.map(p => { return { action: p.key, count: p.doc_count } }) } } async getViewTime ({appId, pageId, utm, timePeriod, interval, avgonly}) { if (['day', 'hour', 'week'].indexOf(interval) < 0) interval = 'day' let {aggregations} = await this.search( { filter: (() => { var li = [ {term: { 'app_id.keyword': appId }}, {term: { 'action.keyword': 'PV_TIME' }}, {range: { durations: { lte: 3600, gte: -0.1 } }} // 防止越界 half_float 65504 ] if (pageId && pageId.length >= 1) li.push({term: { 'page_id.keyword': pageId }}) if (utm && utm.length >= 1 && utm !== 'N/A') li.push({term: { 'utm_campaign.keyword': utm }}) return li })(), timePeriod, mustNot: utm === 'N/A' ? { exists: { field: 'utm_campaign' } } : null, size: 0, aggs: (() => { var option = { avarage: { avg : { field: 'durations', missing: 0 } }, } if (!avgonly) { option.histogram = { date_histogram: { field: 'create_time', time_zone: '+08:00', interval: interval, }, aggs: { avarage: { avg : { field: 'durations', missing: 0 } }, } } } return option })() } ) return { avarage: aggregations && (+aggregations.avarage.value | 0) || 0, list: aggregations && aggregations.histogram && aggregations.histogram.buckets.map(p => { return { dateStr: p.key_as_string, date: p.key, avarage: +p.avarage.value | 0 } }) } } async getUtms ({appId, pageId, timePeriod, size = 20}) { let {aggregations} = await this.search( { filter: [ {term: { 'app_id.keyword': appId }}, {term: { 'page_id.keyword': pageId }}, ], timePeriod, size: 0, aggs: { utm_count: { cardinality: { field : "utm_campaign.keyword" } }, utm_list: { terms: { field: 'utm_campaign.keyword', size, missing: 'N/A' } } } } ) return { count: aggregations && aggregations.utm_count.value || 0, list: aggregations && aggregations.utm_list.buckets.map(p => { return { utm: p.key, count: p.doc_count } }) } } async getTodayOutline ({appId = 'tview'}) { let now = Date.now() let todayStart = now - (now + 8 * 3600000) % 86400000 let yesterdayNow = now - 86400000 let yesterdayStart = todayStart - 86400000 let todaySearch = this.search( { filter: [ {term: { 'action.keyword': 'pageview' }}, {term: { 'app_id.keyword': appId }}, ], timePeriod: [yesterdayStart, now], size: 0, aggs: [['yesterday', yesterdayStart, todayStart], ['today', todayStart, now], ['yesterdayNow', yesterdayStart, yesterdayNow]] .reduce((o, i) => { var [key, start, end] = i o[key] = { filter: { range: { create_time: {gte: start, lte: end} } }, aggs: { distinct_user: { cardinality: { script: { lang: 'painless', inline: 'doc["page_id.keyword"].value + "_" + doc["finger_print.keyword"].value' } } } } } return o },{}) } ) let historySearch = this.search( { filter: [ {term: { 'action.keyword': 'pageview' }}, {term: { 'app_id.keyword': appId }}, ], timePeriod: [0, now], size: 0, aggs: { histogram: { date_histogram: { field: 'create_time', time_zone: '+08:00', interval: 'day', }, aggs: { distinct_user: { cardinality: { script: { lang: 'painless', inline: 'doc["page_id.keyword"].value + "_" + doc["finger_print.keyword"].value' } } } } }, maxuv: { max_bucket: { buckets_path: 'histogram>distinct_user' } }, avguv: { avg_bucket: { gap_policy: 'skip', buckets_path: 'histogram>distinct_user' } }, maxpv: { max_bucket: { buckets_path: 'histogram._count' } }, avgpv: { avg_bucket: { gap_policy: 'skip', buckets_path: 'histogram._count' } } } } ) let [{aggregations: todayData} = {}, {aggregations: historyData} = {}] = await Promise.all([todaySearch, historySearch]) var table = [ ...[['today', '今日'], ['yesterday', '昨日'], ['yesterdayNow', '昨日此时']].map(i => { var [key, name] = i if (!todayData[key]) return {} return {key, name, pv: todayData[key].doc_count, uv: todayData[key].distinct_user.value} }), ...[['avg', '每日平均'], ['max', '历史峰值']].map(i => { var [key, name] = i var o = {key, name, pv: historyData[`${key}pv`].value, uv: historyData[`${key}uv`].value} if (key === 'max') { ;['maxpv', 'maxuv'].reduce((o, i) => { var dates = historyData[i].keys o[`${i}Date`] = dates && dates[dates.length - 1] || '' return o }, o) } return o }) ] return table } async actionTrack ({pageId, action, utm, timePeriod, size = 0, interval = 'day', appId = 'tview'}) { if (['day', 'hour', 'week'].indexOf(interval) < 0) interval = 'day' let {hits, aggregations} = await this.search( { filter: (() => { var li = [ {term: { 'action.keyword': action }}, {term: { 'app_id.keyword': appId }}, {term: { 'page_id.keyword': pageId }}, ] if (utm && utm.length >= 1 && utm !== 'N/A') li.push({term: { 'utm_campaign.keyword': utm }}) return li })(), timePeriod, mustNot: utm === 'N/A' ? { exists: { field: 'utm_campaign' } } : null, size, aggs: { distinct_user: { sum_bucket: { buckets_path: 'histogram>distinct_user' } }, histogram: { date_histogram: { field: 'create_time', time_zone: "+08:00", interval: interval, }, aggs: { distinct_user: { cardinality: { field: "finger_print.keyword" } } } }, labels: { terms: { field: 'label.keyword' } } } } ) return { sum: { count: hits && hits.total || 0, distinct_user: aggregations && aggregations['distinct_user'].value }, list: hits && hits.hits.map(h => h._source), histogram: (aggregations && aggregations.histogram.buckets || []).map(h => { return { dateStr: h.key_as_string, date: h.key, count: h.doc_count, distinct_user: h.distinct_user.value } }), labels: (aggregations && aggregations.labels.buckets || []).map(h => { return { label: h.key, count: h.doc_count } }) } } async createIndex (indices) { if (!this.createIndex.created) this.createIndex.created = {} if (this.createIndex.created[indices]) return true let exists = await esClient.indices.exists({index: indices}) if (!exists) { let result result = await esClient.indices.create({index: indices}) result = await esClient.indices.putMapping({ index: indices, type: ES_TYPE, body: { properties: { create_time: { type: 'date', format: 'yyy/MM/dd HH:mm:ss||yyyy/MM/dd||epoch_millis' }, ip: { type: 'ip' }, durations: { type: 'half_float' } } } }) console.info('create indices', result) } this.createIndex.created[indices] = 1 } async deleteIndices ({monthBefore = 6} = {}) { let ctx = this.ctx if (monthBefore <= 0) { console.warn('monthBefore less than 1, cannot do that') return -1 } if (monthBefore > 11) { console.warn('monthBefore greater than 12, cannot do that') return -2 } let date = new Date() let endYear = date.getFullYear() let endMonth = date.getMonth() + 1 let indices = Array.apply(null, {length: 12}).map((_, i) => { let m = i - 12 + Number(endMonth) let y = (m < 0 ? -1 : 0) + Number(endYear) return `${ES_INDEX}_${y}_${`0${(m + 12) % 12 + 1}`.slice(-2)}` }) let result result = await Promise.all(indices.slice(0, 12 - monthBefore).map(async (i) => { let exists = await esClient.indices.exists({index: i}) let result = '' if (exists) result = await esClient.indices.delete({index: i}) return { index: i, exists, result } })) console.info('delete indices', result) return result } } return Statistics } ================================================ FILE: app/service/tags.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 上午9:30 * To change this template use File | Settings | File Templates. */ var md5 = require('md5') 'use strict' module.exports = app => { class Tags extends app.Service { * list (query) { let _where = { status: 1 } if (query.categoryId) _where.categoryId = query.categoryId if (query.name) _where.name = { $like: `%${query.name || ''}%` } return yield this.ctx.model.Tags.findAll({ where: _where, attributes: [ 'id', 'name' ], order: [ [ 'useNumber', 'DESC' ] ] }); } * add (query) { var item = yield this.ctx.model.Tags.findOne({ where: { name: query.name, categoryId: query.categoryId } }) if (item) { return item } else { var newItem = yield this.ctx.model.Tags.create({ name: query.name, categoryId: query.categoryId }) return newItem } } * useone (query) { var component = yield this.ctx.model.Tags.findOne({ where: { id: query.id, status: 1 } }) if (component) { var useNum = component.useNumber + 1 yield this.ctx.model.Tags.update({ useNumber: useNum }, { where: { id: query.id } }) } return { id: query.id } } } return Tags } ================================================ FILE: app/service/template.js ================================================ /** * Created with WebStorm. * User: kevan * Email:258137678@qq.com * Date: 2017/5/3 * Time: 上午9:30 * To change this template use File | Settings | File Templates. */ var md5 = require('md5'); 'use strict' module.exports = app => { class Template extends app.Service { * list (query) { console.log('service start --list') let _where = { status: (query.status == undefined) ? 1 : query.status, name: { $like: `%${query.name || ''}%` }, } if (query.id) _where.id = query.id if (query.categoryId) _where.categoryId = query.categoryId return yield this.ctx.model.Template.findAll({ where: _where}); } * detail (query) { return yield this.ctx.model.Template.findOne({ where: { id: query.id } }) } * save (query) { try { var _item = { name: query.name, categoryId: query.categoryId, content: query.content, desc: query.desc, status: (query.status == undefined) ? 1 : query.status, image: query.image } if (query.id) { // 编辑 // 参数验证,1判断是否存在该id,2是否重名 if (!(yield this.ctx.model.Template.findOne({where: { status: 1, id: query.id }}))) throw this.ctx.getError({ msg: `不存在id为${query.id}的模板` }); if (yield this.ctx.model.Template.findOne({where: {name: query.name, status: 1, id: { $ne: query.id }}})) throw this.ctx.getError({ msg: `已经存在名称为${query.name}的模板` }); yield this.ctx.model.Template.update(_item, { where: { id: query.id } }) return { id: query.id } } else { // 参数验证 是否重名 if (yield this.ctx.model.Template.findOne({where: {name: query.name, status: 1}})) throw this.ctx.getError({ msg: `已经存在名称为${query.name}的模板` }); var newItem = yield this.ctx.model.Template.create(_item) return { id: newItem.id } } } catch (e) { throw this.ctx.getError({ msg: e.msg || `${query.id ? '更新失败' : '新增失败'}`, error: e }); } } * delete (id) { var item = yield this.ctx.model.Template.findOne({ where: { id: id } }) if (item) { yield this.ctx.model.Template.update({ status: 0 }, { where: { id: id } }); return true } } } return Template } ================================================ FILE: app/service/user.js ================================================ module.exports = app => { function getAdminUrl (ctx, path) { if (/https?:\/\//.test(app.config.ADMIN_PATH)) return `${app.config.ADMIN_PATH.replace(/\/$/g, '')}/${path}` let protocol = 'http://' const domain = (({header, host}) => { let referer = header.referer if (/^https/.test(referer)) protocol = 'https://' let d = host try { d = referer.replace(/^(https?:)?\/\//, '').split('/')[0] } catch (e) { console.log(e) } return d })(ctx) return `${protocol}${domain}/${app.config.ADMIN_PATH.replace(/^\/|\/$/g, '')}/${path}`; } class User extends app.Service { * login (obj) { // 获取验证码比对 if (this.ctx.session.kaptcha != obj.kaptcha) { throw this.ctx.getError({ msg: '验证码错误' }) } const user = yield this.ctx.model.UserLogin.findOne({ where: { email: obj.account }, }); if (user) { if (user.password === this.ctx.helper.tools.md5(obj.password)) { // 插入登录日志 let userLoginLog = {}; userLoginLog.userId = user.id; userLoginLog.ip = this.ctx.ip; userLoginLog = yield this.ctx.model.UserLoginLog.create(userLoginLog); // 修改登录时间及IP const userLogin = {}; userLogin.lastIp = this.ctx.ip; userLogin.lastLogin = new Date(); yield this.ctx.model.UserLogin.update(userLogin, { where: { userId: user.id } }); this.ctx.helper.token.setToken.call(this, user.userId, user.password); } else { throw this.ctx.getError({ msg: '密码不正确' }); } } else { throw this.ctx.getError({ msg: '不存在该用户' }); } } * oauthLogin (obj) { const user = yield this.ctx.model.User.findOne({ where: { oauth: obj.oauth } }); if (user) { const loginInfo = yield this.ctx.model.UserLogin.findOne({ where: { userId: user.id } }) // 插入登录日志 let userLoginLog = {}; userLoginLog.userId = user.id; userLoginLog.ip = this.ctx.ip; userLoginLog = yield this.ctx.model.UserLoginLog.create(userLoginLog); // 修改登录时间及IP const userLogin = {}; userLogin.lastIp = this.ctx.ip; userLogin.lastLogin = new Date(); yield this.ctx.model.UserLogin.update(userLogin, { where: { userId: user.id } }); this.ctx.helper.token.setToken.call(this, user.id, loginInfo.password || this.ctx.helper.tools.md5(user.oauth)); } else { let t = yield app.model.transaction(); let userInfo = {} try { // 插入用户表 userInfo.oauth = obj.oauth userInfo.name = obj.name userInfo.email = obj.email userInfo.emailStatus = 2 userInfo.projectCount = 0 userInfo.telephone = obj.telephone || '' userInfo = yield this.ctx.model.User.create(userInfo, { transaction: t }); if (userInfo.id > 0) { // UserLogin const userLogin = {}; userLogin.userId = userInfo.id; userLogin.password = this.ctx.helper.tools.md5(userInfo.oauth); userLogin.status = 1; userLogin.lastIp = this.ctx.ip; userLogin.lastLogin = new Date(); yield this.ctx.model.UserLogin.create(userLogin, { transaction: t }); // UserGrade const userGrade = {}; userGrade.userId = userInfo.id; userGrade.projectNum = app.config.appConfig.projectNumber; userGrade.groupNum = app.config.appConfig.groupNumber; userGrade.interfaceNum = app.config.appConfig.interfaceNumber; userGrade.favorateProjectNum = app.config.appConfig.favorateProjectNumber; yield this.ctx.model.UserGrade.create(userGrade, { transaction: t }); // UserNoticeType const userNotcieType = {}; userNotcieType.userId = userInfo.id; userNotcieType.type = 1; userNotcieType.messageNotice = 1; userNotcieType.emailNotice = 2; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); userNotcieType.type = 2; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); userNotcieType.type = 3; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); this.ctx.helper.token.setToken.call(this, userInfo.id, userLogin.password); } yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常,请稍后重试', e }); } } } * register (obj) { const user = yield this.ctx.model.UserLogin.findOne({ where: { email: obj.email }, }); if (user) { throw this.ctx.getError({ msg: '用户名已存在' }); } // 保存用户账户 let userInfo = {}; // 事务中处理 let t = yield app.model.transaction(); try { userInfo.email = obj.email; userInfo.name = obj.name; userInfo.emailStatus = obj.emailStatus; userInfo.projectCount = 0; userInfo.telephone = obj.telephone || ''; userInfo = yield this.ctx.model.User.create(userInfo, { transaction: t }); if (userInfo.id > 0) { const userLogin = {}; userLogin.userId = userInfo.id; userLogin.password = this.ctx.helper.tools.md5(obj.password); userLogin.email = obj.email; userLogin.status = 1; userLogin.lastIp = this.ctx.ip; userLogin.ssoUid = obj.ssoUid || 0;//映射SSO_UID userLogin.lastLogin = new Date(); yield this.ctx.model.UserLogin.create(userLogin, { transaction: t }); const userGrade = {}; userGrade.userId = userInfo.id; userGrade.projectNum = app.config.appConfig.projectNumber; userGrade.groupNum = app.config.appConfig.groupNumber; userGrade.interfaceNum = app.config.appConfig.interfaceNumber; userGrade.favorateProjectNum = app.config.appConfig.favorateProjectNumber; yield this.ctx.model.UserGrade.create(userGrade, { transaction: t }); const userNotcieType = {}; userNotcieType.userId = userInfo.id; userNotcieType.type = 1; userNotcieType.messageNotice = 1; userNotcieType.emailNotice = 2; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); userNotcieType.type = 2; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); userNotcieType.type = 3; yield this.ctx.model.UserNoticeType.create(userNotcieType, { transaction: t }); this.ctx.helper.token.setToken.call(this, userInfo.id, userLogin.password); } yield t.commit(); } catch (e) { yield t.rollback(); throw this.ctx.getError({ msg: '服务器异常,请稍后重试', e }); } if (obj.emailStatus == 1) { let code = this.ctx.helper.tools.md5(obj.email + new Date().getTime()); code = code.substring(code.length - 10); const showCode = this.ctx.helper.tools.encrypt(obj.email + '||' + code, app.config.appConfig.desKey); const url = getAdminUrl(this.ctx, `emailActive.html?code=${encodeURIComponent(showCode)}`) this.app.sendMail({ receivers: [ obj.email ], tplName: 'active', data: { name: obj.email, urlcontent: url, url }, subject: '【码良】邮箱激活' }).then(() => { const validCode = {}; validCode.userId = userInfo.id; validCode.code = code; validCode.email = obj.email; validCode.expireTime = new Date(new Date().getTime() + app.config.appConfig.emailActiveTime); this.ctx.model.ValidCode.create(validCode).then() }); } return userInfo.id; } * activeEmail (obj) { const user = yield this.ctx.model.User.findOne({ where: { email: obj.email }, }); if (user && user.emailStatus === 2) { return '您的账户已经成功激活,请勿重复激活'; } const validCode = yield this.ctx.model.ValidCode.findOne({ where: { code: obj.code }, }); if (!validCode) { return '激活码不合法'; } if (validCode.expireTime.getTime() < new Date().getTime()) { return '激活码已过期,请登入账户重新激活'; } // 修改邮箱激活状态 const userUpadte = {}; userUpadte.emailStatus = 2; yield this.ctx.model.User.update(userUpadte, { where: { id: validCode.userId } }); return userUpadte.emailStatus } * sendActiveEmail (uid) { const user = yield this.ctx.model.User.findById(uid); if (!user) { throw this.ctx.getError({ msg: '账户不存在' }); } if (user.emailStatus === 2) { throw this.ctx.getError({ msg: '邮箱已激活' }); } // 发送激活邮件 let code = this.ctx.helper.tools.md5(user.email + new Date().getTime()); code = code.substring(code.length - 10); const showCode = this.ctx.helper.tools.encrypt(user.email + '||' + code, app.config.appConfig.desKey); const url = getAdminUrl(this.ctx, `emailActive.html?code=${encodeURIComponent(showCode)}`) this.app.sendMail({ receivers: [ user.email ], tplName: 'active', data: { name: user.email, urlcontent: url, url }, subject: '【码良】邮箱激活' }).then(() => { const validCode = {}; validCode.userId = user.id; validCode.code = code; validCode.email = user.email; validCode.expireTime = new Date(new Date().getTime() + app.config.appConfig.emailActiveTime); this.ctx.model.ValidCode.create(validCode); }); } // 忘记密码,发送邮件 * sendEmail (email) { const user = yield this.ctx.model.User.findOne({ where: { email: email } }); if (!user) { throw this.ctx.getError({ msg: '账户不存在' }); } // 发送激活邮件 let code = this.ctx.helper.tools.md5(user.email + new Date().getTime()); code = code.substring(code.length - 10); const showCode = this.ctx.helper.tools.encrypt(user.email + '||' + code, app.config.appConfig.desKey); const url = getAdminUrl(this.ctx, `updatePassword.html?code=${encodeURIComponent(showCode)}`) this.app.sendMail({ receivers: [ user.email ], tplName: 'password', data: { name: user.email, urlcontent: url, url }, subject: '【码良】密码重置' }).then(() => { const validCode = {}; validCode.userId = user.id; validCode.code = code; validCode.email = user.email; validCode.expireTime = new Date(new Date().getTime() + app.config.appConfig.emailActiveTime); this.ctx.model.ValidCode.create(validCode).then() }); } * info (uid) { const user = yield this.ctx.model.User.findById(uid); const userLogin = yield this.ctx.model.UserLogin.findOne({ where: { userId: uid } }); user.security = userLogin.security if (!user) { throw this.ctx.getError({ status: 401, msg: '账户不存在' }); } return user; } * edit (obj) { yield this.ctx.model.User.update(obj, { where: { id: this.ctx.request.uid } }); if (obj.security) { yield this.ctx.model.UserLogin.update({ security: obj.security }, { where: { userId: this.ctx.request.uid } }); } } * updatePassword (obj) { const userLogin = yield this.ctx.model.UserLogin.findOne({ where: { userId: obj.uid }, }); if (!userLogin) { throw this.ctx.getError({ status: 401, msg: '账户不存在' }); } if (userLogin.password !== this.ctx.helper.tools.md5(obj.password)) { throw this.ctx.getError({ msg: '原密码不正确' }); } const updateUserLogin = {}; updateUserLogin.password = this.ctx.helper.tools.md5(obj.targetPassword); yield this.ctx.model.UserLogin.update(updateUserLogin, { where: { userId: obj.uid } }); } // 重置密码接口 * newUpdatePassword (obj) { const userLogin = yield this.ctx.model.UserLogin.findOne({ where: { email: obj.email }, }); if (!userLogin) { throw this.ctx.getError({ status: 401, msg: '账户不存在' }); } const validCode = yield this.ctx.model.ValidCode.findOne({ where: { code: obj.code }, }); if (!validCode) { return '激活码不合法'; } if (validCode.expireTime.getTime() < new Date().getTime()) { return '激活码已过期,请登入账户重新激活'; } // 修改邮箱激活状态 const userUpadte = {}; userUpadte.password = this.ctx.helper.tools.md5(obj.password); yield this.ctx.model.UserLogin.update(userUpadte, { where: { email: userLogin.email } }); this.ctx.helper.token.setToken.call(this, userLogin.userId, userUpadte.password); // return ''; } * forgetPassword (obj) { const userLogin = yield this.ctx.model.UserLogin.findOne({ where: { email: obj.email }, }); if (!userLogin) { throw this.ctx.getError({ status: 401, msg: '账户不存在' }); } // 生成新密码 let password = this.ctx.helper.tools.md5(obj.email + '_' + new Date().getTime()); password = password.substring(password.length - 8); this.app.sendMail({ receivers: [ obj.email ], tplName: 'password', data: { name: obj.email, password, }, callback () { const updateUserLogin = {}; updateUserLogin.password = this.ctx.helper.tools.md5(this.ctx.helper.tools.md5(password)); this.ctx.model.UserLogin.update(updateUserLogin, { where: { userId: obj.uid } }); }, subject: '【码良】密码重置' }); } * search (obj) { let queryData = { attributes: [ 'id', 'name', 'photo', 'email' ], where: { $or: [ { name: { $like: '%' + obj.key + '%' } }, { email: { $like: '%' + obj.key + '%' } } ] }, limit: 10 } if (this.ctx.helper.tools.isEmpty(obj.key)) { delete queryData.where; } const data = yield this.ctx.model.User.findAll(queryData); return data; } async getUserRole ({uid = null}) { if (uid === null || uid === '') return null return this.ctx.model.User.findOne({ where: { id: uid }, attributes: ['id', 'role'] }) .then(({ dataValues = {}} = {}) => dataValues.role) .catch(e => console.error(e)) } } return User; }; ================================================ FILE: app.js ================================================ module.exports = app => { console.log('hello world') } ================================================ FILE: config/config.app.js ================================================ module.exports = { accessTokenKey: 'maliangtoken', // cookies的key信息 accessTokenKeyTime: 86400 * 1000, // cookies的key有效期,毫秒数 desKey: '2871sjks', // des加密的key emailActiveTime: 86400 * 1000, // 邮件激活过期时间,单位毫秒 projectNumber: 10, // 默认可创建项目数 groupNumber: 5, // 默认可创建分组数 interfaceNumber: 20, // 默认可创建接口数 favorateProjectNumber: 5, // 默认可关注的项目数 roleOwner: 1, // 角色所有者 roleMaster: 2, // 角色管理者 roleDev: 3, // 角色操作者 roleGest: 4, // 游客 noticeTypeGroup: 1, // 消息类型组域 noticeTypeProject: 2, // 消息类型项目域 noticeTypeApi: 3, // 消息类型接口域 } ================================================ FILE: config/config.default.js ================================================ const appConfig = require('./config.app') module.exports = appInfo => { let config = {} config.appConfig = appConfig config.proxy = true config.keys = appInfo.name + '_1493285411479_6518' // Cookie 秘钥 config.bodyParser = { jsonLimit: '100mb', formLimit: '100mb', } // mutipart formdata config.multipart = { fileModeMatch: /transform\/word2html$/, fieldSize: '10mb', fields: 20, fileSize: '10mb', files: 20, fileExtensions: [ '.csv', '.txt', '.psd', '.doc', '.docx' ] } // 自定义 middleware config.middleware = ['responseHandler', 'loginHandler'] config.responseHandler = { ignore(ctx) { const arr = [ '/cors-proxy', '/proxy/*', '/statistics/report' ] return arr.some(v => { const reg = new RegExp(ctx.router.opts.prefix + v, 'i') return reg.test(ctx.path) }) } } config.loginHandler = { ignore(ctx) { const arr = [ '/editor/pages/pv', '/editor/pages/detail', '/editor/tags/list', '/kaptcha/init', '/users/login', '/users/register', '/users/sendEmail', '/users/newUpdatePassword', '/users/oauthCode', '/users/oauthLogin', '/proxy/*', '/cors-proxy', '/users/activeEmail', '/statistics/report', '/ossupload/uploadFile' ] return arr.some(v => { const reg = new RegExp(ctx.router.opts.prefix + v, 'i') return reg.test(ctx.path) }) } } config.static = { prefix: '/public/' } config.security = { csrf: { enable: false }, domainWhiteList: [] } config.cors = { origin: (ctx) => { console.log(ctx) let transHeaders = ctx.request.headers return transHeaders['origin'] || '*' }, credentials: true, allowHeaders: 'Range, authorization,Authorization, Content-Disposition, Content-Length, Content-MD5, Content-Type, X-Requested-With, X-File-Name,ysession,clikey', maxAge: 7200000 } config.permissionLimit = { 0: { // 普通页面 save: 3, publish: 2 }, 1: { // flutter save: 3, publish: 2 } } config.zipDownloadApi = 'https://godspen.ymm56.com/shop/api/resources/down' config.VIEW_PATH = 'view' config.ADMIN_PATH = 'admin' config.EDITOR_PATH = 'editor' config.API_PATH = 'api' return config } ================================================ FILE: config/config.dev.js ================================================ /* * * 开发环境配置 * 下面是码良依赖的各种服务,请务必逐一配置 * */ module.exports = { sequelize: { dialect: 'mysql', port: '3306', database: 'godspen_db', host: '', username: '', password: '' }, oss: { accessKeyId: '', accessKeySecret: '', host: '', // eg. https://xxxxxx.oss-cn-hangzhou.aliyuncs.com' bucket:'', region: '' // eg. oss-cn-hangzhou }, es: { index: 'godspen', type: 'doc', host: '', }, // redis 单节点 redis: { host: '', port: 6379 }, // redis 集群 // redis: [ // {host: '', port: 6390}, // {host: '', port: 6391}, // {host: '', port: 6390}, // ], mail: { host: '', port: 465, secure: true, // 端口号为465时,请设置为true user: 'xxx@xx.com', pass: '', }, ADMIN_PATH: '/' // 开发环境下的一级路径,也可以为全路径 http://127.0.0.1:8567/ } ================================================ FILE: config/config.production.js ================================================ /* * * 生产环境配置 * 下面是码良依赖的各种服务,请务必逐一配置 * */ module.exports = { sequelize: { dialect: 'mysql', port: '3306', database: 'godspen_db', host: '', username: '', password: '' }, oss: { accessKeyId: '', accessKeySecret: '', host: '', // eg. https://xxxxxx.oss-cn-hangzhou.aliyuncs.com' bucket:'', region: '' // eg. oss-cn-hangzhou }, es: { index: 'godspen', type: 'doc', host: '', }, // redis 单节点 redis: { host: '', port: 6379 }, // redis 集群 // redis: [ // {host: '', port: 6390}, // {host: '', port: 6391}, // {host: '', port: 6390}, // ], mail: { host: '', port: 465, secure: true, // 端口号为465时,请设置为true user: 'xxx@xx.com', pass: '', } } ================================================ FILE: config/plugin.js ================================================ exports.static = true exports.cors = { enable: true, package: 'egg-cors', }; exports.validate = { package: 'egg-validate', } exports.sequelize = { enable: true, package: 'egg-sequelize', } ================================================ FILE: config-parser.js ================================================ const fs = require('fs') const path = require('path') const yaml = require('js-yaml') const CLIENT_KEYS = ['VIEW_PATH','ADMIN_PATH','EDITOR_PATH','API_PATH','EDITOR_TITLE','ADMIN_TITLE', 'BAIDU_TONGJI', 'github'] const REQUIRED_KEYS = ['sequelize', 'oss', 'es', 'redis', 'mail'] const SEQUELIZE_REQUIRED = ['database', 'host', 'username', 'password'] const OSS_REQUIRED = ['accessKeyId', 'accessKeySecret', 'host', 'bucket'] const ES_REQUIRED = ['host'] const REDIS_REQUIRED = ['host', 'port'] const MAIL_REQUIRED = ['host', 'port', 'user', 'pass'] const CLIENT_CONFIG_DIR = process.env.CLIENT_CONFIG_DIR || './sub/gods-pen-admin/src/config,./sub/gods-pen/src/config' const SERVER_CONFIG_DIR = process.env.SERVER_CONFIG_DIR || './config' function parseConfig () { let config try { config = yaml.safeLoad(fs.readFileSync(path.resolve(__dirname, './config.yaml'), 'utf8')) } catch (e) { console.error(e) process.exit() } return config } function validConfig (config) { if (!REQUIRED_KEYS.every(k => !!config[k])) { console.error('配置不全,请确保配置了所有必须字段:', REQUIRED_KEYS) process.exit() } for (let key of REQUIRED_KEYS) { let val = config[key] switch (key) { case 'sequelize': if (!SEQUELIZE_REQUIRED.every(k => !!val[k])) { console.error('sequelize 配置字段不全', SEQUELIZE_REQUIRED) process.exit() } val['port'] = val['port'] || '3306' val['dialect'] = val['dialect'] || 'mysql' break case 'oss': if (!OSS_REQUIRED.every(k => !!val[k])) { console.error('oss 配置字段不全', OSS_REQUIRED) process.exit() } val['region'] = val['region'] || val['host'].replace(/^(https?:)?\/\//, '').split('.').filter(v => v.indexOf('oss-') == 0)[0] if (!val['region']) { console.error('oss.region 解析失败') process.exit() } break case 'es': if (!ES_REQUIRED.every(k => !!val[k])) { console.error('es 配置字段不全', ES_REQUIRED) process.exit() } val['type'] = val['type'] || 'doc' val['index'] = val['index'] || 'godspen' break case 'redis': if (!REDIS_REQUIRED.every(k => !!(val[0] || val || {})[k])) { console.error('redis 配置字段不全', REDIS_REQUIRED) process.exit() } break case 'mail': if (!MAIL_REQUIRED.every(k => !!val[k])) { console.error('mail 配置字段不全', MAIL_REQUIRED) process.exit() } break } } } function rewriteConfig (config) { let clientConfig = CLIENT_KEYS.reduce((o, k) => { o[k] = config[k] return o }, {}) let serverConfig = REQUIRED_KEYS.reduce((o, k) => { o[k] = config[k] return o }, Object.assign({}, clientConfig)) ;CLIENT_CONFIG_DIR.split(',') .map(p => path.resolve(__dirname, p, 'docker.js')) .map(p => fs.writeFileSync(p, `module.exports = ${JSON.stringify(clientConfig, null, 2)}`, 'utf-8')) ;[SERVER_CONFIG_DIR].map(p => path.resolve(__dirname, p, 'config.docker.js')) .map(p => fs.writeFileSync(p, `module.exports = ${JSON.stringify(serverConfig, null, 2)}`, 'utf-8')) } ;(function () { console.log('开始解析配置文件') let config = parseConfig() validConfig(config) rewriteConfig(config) console.log('配置文件解析分发完成') })() ================================================ FILE: docker-entrypoint.sh ================================================ #!/bin/sh #if [ -d '/app/static-html' -a "`ls -A /app/static-html`" != "" ]; then if [ $1 != 'pm2-runtime' -a $1 != 'pm2' ]; then # pass echo 'pass' else echo 'copy file' cp -rf /app/sub-build/. /app/static-html/ fi exec "$@" ================================================ FILE: index.js ================================================ 'use strict' require('egg').startCluster({ baseDir: __dirname, port: process.env.PORT || 7051 }) ================================================ FILE: package.json ================================================ { "name": "gods-pen-server", "version": "1.0.0", "description": "码良服务端", "private": false, "dependencies": { "ali-oss": "^5.2.0", "babel-core": "6.26.0", "babel-plugin-transform-runtime": "^6.23.0", "babel-preset-es2015": "^6.24.1", "babel-preset-es2017": "^6.24.1", "babel-preset-stage-0": "^6.24.1", "captchapng": "^0.0.1", "co": "^4.6.0", "crypto-js": "^3.1.9-1", "egg": "2.26.0", "egg-cors": "^1.1.0", "egg-multipart": "^2.4.0", "egg-mysql": "^3.0.0", "egg-security": "2.5.0", "egg-sequelize": "2.0.2", "egg-validate": "^1.0.0", "elasticsearch": "^15.1.1", "ioredis": "^4.6.2", "js-yaml": "^3.13.1", "jszip": "^3.2.2", "md5": "^2.2.1", "mime": "^2.4.0", "mockjs": "^1.0.1-beta3", "moment": "^2.18.1", "nodemailer": "^4.0.1", "nunjucks": "^3.0.0", "opentsdb-node-client": "^1.0.1", "psd": "^3.2.0", "random-ua": "^0.0.6", "rimraf": "^3.0.0", "stream-wormhole": "^1.1.0" }, "devDependencies": { "autod": "^2.8.0", "egg-bin": "^3.3.0", "egg-ci": "^1.6.0", "egg-mock": "^3.20.0", "egg-scripts": "^2.11.1", "eslint": "^3.19.0", "eslint-config-egg": "^3.2.0", "webstorm-disable-index": "^1.1.2" }, "engines": { "node": ">=6.0.0" }, "scripts": { "dev": "cross-env EGG_SERVER_ENV=dev egg-bin dev --port 7051", "debug": "cross-env EGG_SERVER_ENV=dev egg-bin debug --port 7051", "lint": "eslint .", "serve": "cross-env EGG_SERVER_ENV=production NODE_ENV=production egg-scripts start --port=7051 --title=gods-pen-server --daemon && echo", "stop": "egg-scripts stop --title=gods-pen-server" }, "repository": { "type": "git", "url": "" }, "license": "MIT" } ================================================ FILE: process.json ================================================ { "script": "index.js", "name":"godspen", "log_date_format": "YYYY-MM-DD HH:mm:ss", "env": { "NODE_ENV": "development", "PORT":"7051", "EGG_SERVER_ENV":"dev" }, "env_docker": { "NODE_ENV": "production", "PORT":"7051", "EGG_SERVER_ENV":"docker" }, "env_prod": { "NODE_ENV": "production", "PORT":"7051", "EGG_SERVER_ENV":"production" } } ================================================ FILE: sql/init.sql ================================================ -- MySQL dump 10.13 Distrib 5.7.27, for Linux (x86_64) -- -- Host: localhost Database: godspen_db -- ------------------------------------------------------ -- Server version 5.7.27 /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; /*!40101 SET NAMES utf8 */; /*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */; /*!40103 SET TIME_ZONE='+00:00' */; /*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */; /*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */; /*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */; /*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */; -- -- Current Database: `godspen_db` -- CREATE DATABASE /*!32312 IF NOT EXISTS*/ `godspen_db` /*!40100 DEFAULT CHARACTER SET latin1 */; USE `godspen_db`; -- -- Table structure for table `tb_category` -- DROP TABLE IF EXISTS `tb_category`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_category` ( `id` int(10) NOT NULL COMMENT '类别id', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '类别名', `type` int(10) NOT NULL COMMENT '分类所属(1:项目 2:模板 3:资源)', `status` int(5) NOT NULL DEFAULT '1' COMMENT '项目状态(0删除,1正常,2禁用)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '类别创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '类别更改时间', `desc` varchar(255) DEFAULT '' COMMENT '类别描述', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_category` -- LOCK TABLES `tb_category` WRITE; /*!40000 ALTER TABLE `tb_category` DISABLE KEYS */; INSERT INTO `tb_category` VALUES (1,'page',1,0,'2017-12-18 17:46:00','2018-09-11 15:54:16','页面'),(2,'combinedcomponent',3,1,'2017-12-18 17:47:02','2018-09-11 15:54:21','组合组件'),(3,'component',2,1,'2017-12-19 10:17:02','2018-09-11 15:55:10','组件'),(4,'image',3,1,'2018-01-18 15:02:48','2018-09-11 15:54:29','图片'),(5,'audio',3,1,'2018-01-18 15:02:58','2018-09-11 15:54:33','音频'),(6,'script',3,1,'2018-02-08 17:43:14','2018-09-11 15:54:35','脚本'),(7,'video',3,1,'2018-03-02 16:56:04','2018-09-11 15:54:39','视频'); /*!40000 ALTER TABLE `tb_category` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_company` -- DROP TABLE IF EXISTS `tb_company`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_company` ( `id` bigint(11) NOT NULL, `name` varchar(32) DEFAULT '', `address` varchar(64) DEFAULT '', `status` tinyint(1) DEFAULT '1', `update_time` bigint(20) unsigned NOT NULL DEFAULT '0', `create_time` bigint(20) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_company` -- LOCK TABLES `tb_company` WRITE; /*!40000 ALTER TABLE `tb_company` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_company` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_company_and_user` -- DROP TABLE IF EXISTS `tb_company_and_user`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_company_and_user` ( `user_id` bigint(20) NOT NULL, `company_id` bigint(20) NOT NULL, `status` tinyint(1) DEFAULT '1', `update_time` bigint(20) unsigned NOT NULL DEFAULT '0', `create_time` bigint(20) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`user_id`,`company_id`), KEY `company_id` (`company_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_company_and_user` -- LOCK TABLES `tb_company_and_user` WRITE; /*!40000 ALTER TABLE `tb_company_and_user` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_company_and_user` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_component` -- DROP TABLE IF EXISTS `tb_component`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_component` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(50) NOT NULL COMMENT '资源名称', `path` text NOT NULL COMMENT '资源内容(url、脚本)', `user_id` bigint(10) DEFAULT NULL COMMENT '用户id,外键', `version` varchar(15) NOT NULL COMMENT '版本号', `visibilitylevel` int(5) NOT NULL DEFAULT '1' COMMENT '显示状态(0私有,1公共开放)', `status` int(11) unsigned DEFAULT '1' COMMENT '删除状态', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `desc` varchar(200) DEFAULT NULL COMMENT '简单描述', `isnew` int(11) DEFAULT NULL COMMENT '是否是最新版本 1:是 0:否', `usenumber` bigint(20) unsigned DEFAULT '0' COMMENT '使用量', `type` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '组件类型,默认0,普通组件;1,flutter 组件;', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='组件列表'; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_component` -- LOCK TABLES `tb_component` WRITE; /*!40000 ALTER TABLE `tb_component` DISABLE KEYS */; INSERT INTO `tb_component` VALUES (1,'truck/image','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/truck/image/0.1.7/index.js',1,'0.1.7',1,1,'2019-01-03 10:40:07','2019-04-22 07:04:07','图片',1,0,0),(2,'truck/text','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/truck/text/0.1.7/index.js',1,'0.1.7',1,1,'2018-11-15 05:34:09','2019-04-22 07:04:07','文本',1,0,0),(3,'truck/button','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/truck/button/0.1.6/index.js',1,'0.1.6',1,1,'2018-11-15 05:23:00','2019-04-22 07:04:07','按钮',1,0,0),(4,'truck/emptyContainer','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/emptyContainer/1.0.2/index.js',1,'1.0.2',1,1,'2018-06-11 11:02:19','2019-04-22 07:04:07','空容器节点',1,0,0),(5,'truck/richtext','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/richtext/1.1.5/index.js',1,'1.1.5',1,1,'2018-06-29 03:38:42','2019-04-22 07:04:07','富文本',1,0,0),(6,'truck/PageContainer','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/PageContainer/1.0.2/index.js',1,'1.0.2',1,1,'2018-06-11 11:01:25','2019-04-22 07:04:07','页面容器组件',1,0,0),(7,'truck/ListContainer','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/ListContainer/0.1.5/index.js',1,'0.1.5',1,1,'2018-07-30 07:52:35','2019-04-22 07:05:04','列表容器',1,0,0),(8,'truck/video','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/video/0.1.7/index.js',1,'0.1.7',1,1,'2018-06-12 00:42:50','2019-04-22 07:05:04','视频组件,用于播放视频',1,0,0),(9,'truck/audio','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/truck/audio/0.1.3/index.js',1,'0.1.3',1,1,'2018-06-12 00:46:47','2019-04-22 07:05:04','音频组件',1,0,0),(10,'truck/drumPad','https://ymm-maliang.oss-cn-hangzhou.aliyuncs.com/ymm-maliang/truck/drumPad/1.0.1/index.js',1,'1.0.1',1,1,'2018-12-27 07:43:39','2019-04-22 07:05:04','格子块,n行n列的块状区域,每个块有两个状态',1,0,0); /*!40000 ALTER TABLE `tb_component` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_component_use` -- DROP TABLE IF EXISTS `tb_component_use`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_component_use` ( `cid` bigint(10) NOT NULL COMMENT '用户id,外键', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', `usenumber` bigint(20) unsigned DEFAULT '0' COMMENT '使用量', `love` bigint(20) unsigned DEFAULT '0' COMMENT '点赞数', `id` bigint(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 ROW_FORMAT=DYNAMIC COMMENT='组件列表'; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_component_use` -- LOCK TABLES `tb_component_use` WRITE; /*!40000 ALTER TABLE `tb_component_use` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_component_use` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_favorate_project` -- DROP TABLE IF EXISTS `tb_favorate_project`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_favorate_project` ( `project_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, `status` tinyint(1) DEFAULT '1', `update_time` bigint(20) unsigned NOT NULL DEFAULT '0', `create_time` bigint(20) unsigned NOT NULL DEFAULT '0', PRIMARY KEY (`project_id`,`user_id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_favorate_project` -- LOCK TABLES `tb_favorate_project` WRITE; /*!40000 ALTER TABLE `tb_favorate_project` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_favorate_project` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_group` -- DROP TABLE IF EXISTS `tb_group`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_group` ( `id` bigint(11) NOT NULL AUTO_INCREMENT, `create_user_id` bigint(20) DEFAULT '0', `name` varchar(32) DEFAULT '', `status` tinyint(1) DEFAULT '1', `type` tinyint(1) DEFAULT '1' COMMENT '项目组类型, 1默认, 2新建', `description` varchar(100) DEFAULT '', `logo` varchar(200) DEFAULT '', `project_count` int(5) NOT NULL DEFAULT '0', `user_count` int(5) NOT NULL DEFAULT '0', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_group` -- LOCK TABLES `tb_group` WRITE; /*!40000 ALTER TABLE `tb_group` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_group` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_group_and_project` -- DROP TABLE IF EXISTS `tb_group_and_project`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_group_and_project` ( `project_id` bigint(20) NOT NULL, `group_id` bigint(20) NOT NULL, `status` tinyint(1) DEFAULT '1', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`project_id`,`group_id`), KEY `group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_group_and_project` -- LOCK TABLES `tb_group_and_project` WRITE; /*!40000 ALTER TABLE `tb_group_and_project` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_group_and_project` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_group_and_user` -- DROP TABLE IF EXISTS `tb_group_and_user`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_group_and_user` ( `user_id` bigint(20) NOT NULL, `group_id` bigint(20) NOT NULL, `status` tinyint(1) DEFAULT '1', `role` tinyint(1) DEFAULT '1' COMMENT '用户在项目组中的角色, 1创建者, 2管理员, 3组员', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`user_id`,`group_id`), KEY `group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_group_and_user` -- LOCK TABLES `tb_group_and_user` WRITE; /*!40000 ALTER TABLE `tb_group_and_user` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_group_and_user` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_kaptcha` -- DROP TABLE IF EXISTS `tb_kaptcha`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_kaptcha` ( `id` int(11) NOT NULL AUTO_INCREMENT, `deskey` varchar(11) DEFAULT NULL COMMENT '验证key', `code` varchar(255) DEFAULT NULL COMMENT '验证码', `expire_time` bigint(20) DEFAULT NULL COMMENT '过期时间', `create_time` bigint(20) DEFAULT NULL COMMENT '创建时间', PRIMARY KEY (`id`), KEY `tk` (`create_time`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_kaptcha` -- LOCK TABLES `tb_kaptcha` WRITE; /*!40000 ALTER TABLE `tb_kaptcha` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_kaptcha` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_login_log` -- DROP TABLE IF EXISTS `tb_login_log`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_login_log` ( `id` int(5) unsigned NOT NULL AUTO_INCREMENT, `user_id` bigint(11) NOT NULL, `ip` varchar(255) DEFAULT NULL COMMENT '登录IP地址', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) USING BTREE, KEY `INDEX_UPDATE_TIME` (`update_time`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_login_log` -- LOCK TABLES `tb_login_log` WRITE; /*!40000 ALTER TABLE `tb_login_log` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_login_log` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_login_token` -- DROP TABLE IF EXISTS `tb_login_token`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_login_token` ( `id` int(11) NOT NULL AUTO_INCREMENT, `user_id` bigint(11) DEFAULT NULL COMMENT '用户ID', `get_time` varchar(255) DEFAULT NULL COMMENT '获取时间', `expire_time` varchar(255) DEFAULT NULL COMMENT '过期时间', `token` varchar(1023) DEFAULT NULL, `expires_in` int(20) NOT NULL COMMENT '有效期,单位秒', PRIMARY KEY (`id`), KEY `tk` (`token`(255)) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_login_token` -- LOCK TABLES `tb_login_token` WRITE; /*!40000 ALTER TABLE `tb_login_token` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_login_token` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_mock` -- DROP TABLE IF EXISTS `tb_mock`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_mock` ( `interface_id` bigint(11) NOT NULL COMMENT '接口Id(历史接口或草稿接口)', `type` tinyint(4) NOT NULL COMMENT '接口类型:1-历史接口 2-草稿接口', `mock_request` text COMMENT '请求的mock规则', `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间', `update_time` timestamp NULL DEFAULT NULL COMMENT '修改时间', PRIMARY KEY (`interface_id`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_mock` -- LOCK TABLES `tb_mock` WRITE; /*!40000 ALTER TABLE `tb_mock` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_mock` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_pages` -- DROP TABLE IF EXISTS `tb_pages`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_pages` ( `id` bigint(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '页面id, hash值', `key` varchar(50) DEFAULT NULL COMMENT '页面key', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '页面名', `image` varchar(500) DEFAULT NULL COMMENT '页面logo图片地址', `desc` varchar(500) DEFAULT '' COMMENT '页面描述', `content` mediumtext COMMENT '页面json数据', `draft` mediumtext COMMENT '页面json数据-草稿', `project_id` bigint(10) DEFAULT NULL COMMENT '项目id,外键', `is_home_page` int(11) NOT NULL DEFAULT '0' COMMENT '是否为首页 (0否 1是)', `status` int(5) NOT NULL DEFAULT '1' COMMENT '页面状态(0删除,1正常,2禁用)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '页面创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '页面更改时间', `visibilitylevel` int(5) NOT NULL DEFAULT '1' COMMENT '显示状态(0私有,1公共开放)', `type` tinyint(1) unsigned zerofill NOT NULL DEFAULT '0' COMMENT '页面类型,默认0,普通页面;1,flutter 页面;', `fork` int(1) unsigned zerofill NOT NULL DEFAULT '0' COMMENT '页面fork数量', `featured` int(1) DEFAULT '0' COMMENT '加精 0 未处理 1 精选 2 一般 ', PRIMARY KEY (`id`) USING BTREE, UNIQUE KEY `Index` (`key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='abc'; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_pages` -- LOCK TABLES `tb_pages` WRITE; /*!40000 ALTER TABLE `tb_pages` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_pages` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_pages_history` -- DROP TABLE IF EXISTS `tb_pages_history`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_pages_history` ( `id` bigint(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '页面id, hash值', `content` mediumtext COMMENT '页面json数据', `page_id` bigint(10) DEFAULT NULL COMMENT '页面id,外键', `status` int(5) NOT NULL DEFAULT '1' COMMENT '历史状态(0删除,1正常)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '页面创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '页面更改时间', `user_id` bigint(10) DEFAULT NULL COMMENT '操作人id', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='页面历史记录'; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_pages_history` -- LOCK TABLES `tb_pages_history` WRITE; /*!40000 ALTER TABLE `tb_pages_history` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_pages_history` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_project` -- DROP TABLE IF EXISTS `tb_project`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_project` ( `id` bigint(10) NOT NULL AUTO_INCREMENT COMMENT '项目id, hash值', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '项目名', `key` varchar(50) NOT NULL DEFAULT '' COMMENT ' ', `category_id` int(10) DEFAULT NULL COMMENT '项目的类别(外链,与tb_category相关联)', `desc` varchar(500) DEFAULT '' COMMENT '项目描述', `image` varchar(500) DEFAULT '' COMMENT '项目logo图片地址', `status` int(5) NOT NULL DEFAULT '1' COMMENT '项目状态(0删除,1正常,2禁用)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '项目创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '项目更改时间', `visibilitylevel` int(5) NOT NULL DEFAULT '1' COMMENT '显示状态(0私有,1公共开放)', `create_user_id` bigint(20) DEFAULT '0', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_project` -- LOCK TABLES `tb_project` WRITE; /*!40000 ALTER TABLE `tb_project` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_project` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_res_tags_rel` -- DROP TABLE IF EXISTS `tb_res_tags_rel`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_res_tags_rel` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `rid` bigint(20) NOT NULL COMMENT '资源表外键', `tid` bigint(20) DEFAULT NULL COMMENT '标签表外键', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `cid` bigint(20) DEFAULT NULL COMMENT '标签分类id', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_res_tags_rel` -- LOCK TABLES `tb_res_tags_rel` WRITE; /*!40000 ALTER TABLE `tb_res_tags_rel` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_res_tags_rel` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_resources` -- DROP TABLE IF EXISTS `tb_resources`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_resources` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `category_id` int(11) NOT NULL COMMENT '分类', `name` varchar(50) DEFAULT NULL COMMENT '资源名称', `content` text COMMENT '资源内容(url、脚本)', `status` int(11) DEFAULT NULL COMMENT '1正常 0删除', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `visibilitylevel` int(5) NOT NULL DEFAULT '1' COMMENT '显示状态(0私有,1公共开放)', `user_id` bigint(10) DEFAULT NULL COMMENT '用户id,外键', `icon` varchar(200) DEFAULT NULL COMMENT '对应icon图标', `use_count` bigint(10) DEFAULT '0' COMMENT '使用量', `desc` text COMMENT '资源内容(url、脚本)', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_resources` -- LOCK TABLES `tb_resources` WRITE; /*!40000 ALTER TABLE `tb_resources` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_resources` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_role` -- DROP TABLE IF EXISTS `tb_role`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_role` ( `id` int(4) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `name` varchar(32) NOT NULL COMMENT '角色名称', `alias_number` varchar(32) NOT NULL COMMENT '编号', `remark` varchar(100) DEFAULT NULL COMMENT '备注信息', `del_status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '删除状态:1-未删除 2-已删除', `create_time` bigint(20) unsigned NOT NULL COMMENT '创建时间', `update_time` bigint(20) unsigned NOT NULL COMMENT '修改时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色信息表'; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_role` -- LOCK TABLES `tb_role` WRITE; /*!40000 ALTER TABLE `tb_role` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_role` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_tags` -- DROP TABLE IF EXISTS `tb_tags`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_tags` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `name` varchar(50) NOT NULL COMMENT '资源名称', `category_id` int(11) NOT NULL COMMENT '分类', `status` int(11) NOT NULL DEFAULT '1' COMMENT '1正常 0删除', `create_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `update_time` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, `usenumber` int(11) NOT NULL DEFAULT '1' COMMENT '使用次数', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=21 DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Table structure for table `tb_template` -- DROP TABLE IF EXISTS `tb_template`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_template` ( `id` int(10) NOT NULL AUTO_INCREMENT COMMENT '模板id', `name` varchar(50) NOT NULL DEFAULT '' COMMENT '模板名', `category_id` int(10) NOT NULL COMMENT '模板的类别(外链,与tb_category相关联)', `content` text COMMENT '模板内容', `status` int(5) NOT NULL DEFAULT '1' COMMENT '模板状态(0删除,1正常,2禁用)', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '模板创建时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '模板更改时间', `desc` varchar(255) DEFAULT '' COMMENT '模板描述', `image` varchar(255) DEFAULT '' COMMENT '模板图片', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_template` -- LOCK TABLES `tb_template` WRITE; /*!40000 ALTER TABLE `tb_template` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_template` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user` -- DROP TABLE IF EXISTS `tb_user`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `email` varchar(64) DEFAULT '', `email_status` tinyint(4) DEFAULT '1' COMMENT '邮箱激活状态 1-未激活 2-已激活', `name` varchar(255) DEFAULT NULL COMMENT '姓名', `telephone` varchar(16) DEFAULT '', `photo` varchar(100) DEFAULT NULL COMMENT '头像地址', `project_count` int(11) DEFAULT '0', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `oauth` varchar(64) DEFAULT NULL COMMENT '第三方登录鉴权id:渠道_id', `role` tinyint(1) DEFAULT '0' COMMENT '权限 0 普通用户 1 管理员 其他未定可扩展', PRIMARY KEY (`id`), KEY `searchEmail` (`email`) USING BTREE, KEY `searchName` (`name`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user` -- LOCK TABLES `tb_user` WRITE; /*!40000 ALTER TABLE `tb_user` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user_and_project` -- DROP TABLE IF EXISTS `tb_user_and_project`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user_and_project` ( `project_id` bigint(20) NOT NULL, `user_id` bigint(20) NOT NULL, `status` tinyint(1) DEFAULT '1', `role` tinyint(1) DEFAULT '1' COMMENT '用户在项目中的角色, 1Owner, 2Master, 3Dev, 4Guest', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `is_favor` tinyint(1) DEFAULT '0' COMMENT '是否关注,0未关注,1已关注', PRIMARY KEY (`project_id`,`user_id`), KEY `user_id` (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user_and_project` -- LOCK TABLES `tb_user_and_project` WRITE; /*!40000 ALTER TABLE `tb_user_and_project` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user_and_project` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user_grade` -- DROP TABLE IF EXISTS `tb_user_grade`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user_grade` ( `user_id` bigint(11) unsigned NOT NULL COMMENT '用户ID,为主键', `project_num` int(4) DEFAULT NULL COMMENT '剩余可创建的项目数', `group_num` int(4) DEFAULT NULL COMMENT '剩余可创建的组数目', `interface_num` int(4) DEFAULT NULL COMMENT '剩余可创建的接口数', `favorate_project_num` int(11) DEFAULT NULL COMMENT '关注项目数', `create_time` timestamp NULL DEFAULT NULL, `update_time` timestamp NULL DEFAULT NULL, PRIMARY KEY (`user_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user_grade` -- LOCK TABLES `tb_user_grade` WRITE; /*!40000 ALTER TABLE `tb_user_grade` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user_grade` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user_login` -- DROP TABLE IF EXISTS `tb_user_login`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user_login` ( `id` bigint(11) unsigned NOT NULL AUTO_INCREMENT, `password` varchar(64) DEFAULT '', `user_id` bigint(11) DEFAULT NULL COMMENT '用户信息表ID', `email` varchar(50) DEFAULT '', `status` tinyint(1) DEFAULT '1' COMMENT '是否可用状态:1-可用 2-不可用', `last_ip` varchar(20) DEFAULT NULL COMMENT '上次登录IP', `last_login` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '上次登录时间', `sso_uid` bigint(20) DEFAULT '0' COMMENT 'SSO登录映射用户ID', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间', `security` varchar(64) DEFAULT '' COMMENT '秘钥信息', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user_login` -- LOCK TABLES `tb_user_login` WRITE; /*!40000 ALTER TABLE `tb_user_login` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user_login` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user_notice` -- DROP TABLE IF EXISTS `tb_user_notice`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user_notice` ( `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `create_user_id` bigint(11) DEFAULT NULL COMMENT '通知创建者ID', `user_id` bigint(11) DEFAULT NULL COMMENT '用户ID', `content` varchar(255) DEFAULT NULL COMMENT '消息内容', `title` varchar(50) DEFAULT NULL COMMENT '消息标题', `read_status` tinyint(1) DEFAULT '1' COMMENT '读取状态:1-未读 2-已读取', `type` int(4) DEFAULT NULL COMMENT '消息类型 :101-用户添加组 102-组移除用户 103-组内用户权限变动 201-用户添加项目 202-项目移除成员 203-项目内成员权限变动 301-删除接口 302-接口发布申请 303-接口修改 304-审核接口', `join_id` bigint(11) DEFAULT NULL COMMENT '关联ID(取值 项目ID、分组ID、接口ID、审核日志ID)', `create_time` timestamp NULL DEFAULT NULL, `update_time` timestamp NULL DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user_notice` -- LOCK TABLES `tb_user_notice` WRITE; /*!40000 ALTER TABLE `tb_user_notice` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user_notice` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_user_notice_type` -- DROP TABLE IF EXISTS `tb_user_notice_type`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_user_notice_type` ( `user_id` bigint(11) NOT NULL COMMENT '用户ID', `type` tinyint(1) NOT NULL COMMENT '类别:1-项目域 2-接口域', `message_notice` tinyint(1) DEFAULT '1' COMMENT '站内信是否开启通知:1-开启 2-不开启', `email_notice` tinyint(1) DEFAULT '2' COMMENT '邮件通知:1-开启 2-不开启', `create_time` timestamp NULL DEFAULT NULL, `update_time` timestamp NULL DEFAULT NULL, PRIMARY KEY (`user_id`,`type`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_user_notice_type` -- LOCK TABLES `tb_user_notice_type` WRITE; /*!40000 ALTER TABLE `tb_user_notice_type` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_user_notice_type` ENABLE KEYS */; UNLOCK TABLES; -- -- Table structure for table `tb_valid_code` -- DROP TABLE IF EXISTS `tb_valid_code`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `tb_valid_code` ( `id` int(4) NOT NULL AUTO_INCREMENT, `user_id` bigint(11) DEFAULT NULL, `email` varchar(255) DEFAULT NULL COMMENT '邮箱', `code` varchar(255) DEFAULT NULL COMMENT '验证码', `create_time` timestamp NULL DEFAULT NULL COMMENT '创建时间', `expire_time` timestamp NULL DEFAULT NULL COMMENT '过期时间', PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8; /*!40101 SET character_set_client = @saved_cs_client */; -- -- Dumping data for table `tb_valid_code` -- LOCK TABLES `tb_valid_code` WRITE; /*!40000 ALTER TABLE `tb_valid_code` DISABLE KEYS */; /*!40000 ALTER TABLE `tb_valid_code` ENABLE KEYS */; UNLOCK TABLES; /*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */; /*!40101 SET SQL_MODE=@OLD_SQL_MODE */; /*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */; /*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; /*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */; -- Dump completed on 2019-11-01 8:29:43 ================================================ FILE: sub-build/.gitkeep ================================================