diff --git a/.gitignore b/.gitignore index ccedd9ff..3fda4751 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,32 @@ # IntelliJ project files +.vscode/ +node_modules/ +npm-debug.log +yarn-error.log +yarn.lock +package-lock.json +/.idea/ +*/**/dist +*/**/pnpm-lock.yaml +*/**/stats.html .idea *.iml out gen -node_modules/ -packages/*/test/*-private.js +/test/*.private.* + +/*.log + +/packages/ui/*/.idea + +/packages/ui/*/node_modules + +/packages/*/node_modules +/packages/ui/certd-server/tmp/ +/packages/ui/certd-ui/dist/ +/other +/dev-sidecar-test +/packages/core/certd/yarn.lock +/packages/test +/test/own +/pnpm-lock.yaml diff --git a/packages/ui/certd-server/.dockerignore b/packages/ui/certd-server/.dockerignore new file mode 100644 index 00000000..6eb6e80d --- /dev/null +++ b/packages/ui/certd-server/.dockerignore @@ -0,0 +1,16 @@ +logs/ +npm-debug.log +yarn-error.log +node_modules/ +package-lock.json +yarn.lock +coverage/ +!dist/ +.idea/ +run/ +.DS_Store +*.sw* +*.un~ +.tsbuildinfo +.tsbuildinfo.* +/data/db.sqlite diff --git a/packages/ui/certd-server/.editorconfig b/packages/ui/certd-server/.editorconfig new file mode 100644 index 00000000..4c7f8a8e --- /dev/null +++ b/packages/ui/certd-server/.editorconfig @@ -0,0 +1,11 @@ +# 🎨 editorconfig.org + +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_style = space +indent_size = 2 +trim_trailing_whitespace = true +insert_final_newline = true \ No newline at end of file diff --git a/packages/ui/certd-server/.eslintrc.json b/packages/ui/certd-server/.eslintrc.json new file mode 100644 index 00000000..8d20e22b --- /dev/null +++ b/packages/ui/certd-server/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": "./node_modules/mwts/", + "ignorePatterns": ["node_modules", "dist", "test", "jest.config.js", "typings"], + "env": { + "jest": true + } +} diff --git a/packages/ui/certd-server/.gitignore b/packages/ui/certd-server/.gitignore new file mode 100644 index 00000000..d881af0a --- /dev/null +++ b/packages/ui/certd-server/.gitignore @@ -0,0 +1,17 @@ +logs/ +npm-debug.log +yarn-error.log +node_modules/ +package-lock.json +yarn.lock +coverage/ +dist/ +.idea/ +run/ +.DS_Store +*.sw* +*.un~ +.tsbuildinfo +.tsbuildinfo.* +/data/db.sqlite +/pnpm-lock.yaml diff --git a/packages/ui/certd-server/.prettierrc.js b/packages/ui/certd-server/.prettierrc.js new file mode 100644 index 00000000..b964930f --- /dev/null +++ b/packages/ui/certd-server/.prettierrc.js @@ -0,0 +1,3 @@ +module.exports = { + ...require('mwts/.prettierrc.json') +} diff --git a/packages/ui/certd-server/Dockerfile b/packages/ui/certd-server/Dockerfile new file mode 100644 index 00000000..52140734 --- /dev/null +++ b/packages/ui/certd-server/Dockerfile @@ -0,0 +1,15 @@ +FROM registry.cn-shenzhen.aliyuncs.com/greper/node:15.8.0-alpine + +WORKDIR /home + +COPY . . +# 如果各公司有自己的私有源,可以替换registry地址 +#RUN npm install --registry=https://registry.npmmirror.com +RUN npm install -g cnpm +RUN cnpm install +RUN npm run build:preview + +# 如果端口更换,这边可以更新一下 +EXPOSE 7001 + +CMD ["npm", "run", "online:preview"] diff --git a/packages/ui/certd-server/LICENSE b/packages/ui/certd-server/LICENSE new file mode 100644 index 00000000..e9513c2e --- /dev/null +++ b/packages/ui/certd-server/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 fast-crud + +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. diff --git a/packages/ui/certd-server/README.md b/packages/ui/certd-server/README.md new file mode 100644 index 00000000..4b38c776 --- /dev/null +++ b/packages/ui/certd-server/README.md @@ -0,0 +1,41 @@ +# fast-server-js + +base on midway + +## QuickStart + +> nodejs需要16以上 + + + + +### Development + +```bash +$ npm i +# 如果遇到sqlite安装失败时 +# 建议使用cnpm +# npm install -g cnpm +# cnpm install +$ npm run dev +$ open http://localhost:7001/ +``` + +### Deploy + +```bash +$ npm start +``` + +### npm scripts + +- Use `npm run lint` to check code style. +- Use `npm test` to run unit test. + + +[midway]: https://midwayjs.org + +see [midway docs][ https://midwayjs.org] for more detail. + + + diff --git a/packages/ui/certd-server/README.zh-CN.md b/packages/ui/certd-server/README.zh-CN.md new file mode 100644 index 00000000..54b99c0e --- /dev/null +++ b/packages/ui/certd-server/README.zh-CN.md @@ -0,0 +1,30 @@ +# fast-server-js + +## 快速入门 + + + +如需进一步了解,参见 [midway 文档][midway]。 + +### 本地开发 + +```bash +$ npm i +$ npm run dev +$ open http://localhost:7001/ +``` + +### 部署 + +```bash +$ npm start +``` + +### 内置指令 + +- 使用 `npm run lint` 来做代码风格检查。 +- 使用 `npm test` 来执行单元测试。 + + +[midway]: https://midwayjs.org + diff --git a/packages/ui/certd-server/bootstrap.js b/packages/ui/certd-server/bootstrap.js new file mode 100644 index 00000000..d0421214 --- /dev/null +++ b/packages/ui/certd-server/bootstrap.js @@ -0,0 +1,7 @@ +const WebFramework = require('@midwayjs/koa').Framework; +const web = new WebFramework().configure({ + port: 7001, +}); + +const { Bootstrap } = require('@midwayjs/bootstrap'); +Bootstrap.load(web).run(); diff --git a/packages/ui/certd-server/db/migration/v00001__init.sql b/packages/ui/certd-server/db/migration/v00001__init.sql new file mode 100644 index 00000000..b8f169c3 --- /dev/null +++ b/packages/ui/certd-server/db/migration/v00001__init.sql @@ -0,0 +1,77 @@ +-- 表:sys_permission +CREATE TABLE "sys_permission" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "title" varchar(100) NOT NULL, "permission" varchar(100), "parent_id" integer NOT NULL DEFAULT (-1), "sort" integer NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (1, '系统管理', 'sys', -1, 1, 1, 1624085863636); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (2, '权限管理', 'sys:auth', 1, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (3, '用户管理', 'sys:auth:user', 2, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (4, '查看', 'sys:auth:user:view', 3, 100, 1, 1624189112333); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (5, '权限管理', 'sys:auth:per', 2, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (6, '查看', 'sys:auth:per:view', 5, 100, 1, 1624189161317); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (7, '角色管理', 'sys:auth:role', 2, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (8, '查看', 'sys:auth:role:view', 7, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (9, '修改', 'sys:auth:user:edit', 3, 300, 1, 1624189127688); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (10, '删除', 'sys:auth:user:remove', 3, 400, 1, 1624189133184); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (11, '添加', 'sys:auth:user:add', 3, 200, 1, 1624189142576); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (12, '修改', 'sys:auth:role:edit', 7, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (13, '删除', 'sys:auth:role:remove', 7, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (14, '添加', 'sys:auth:role:add', 7, 1, 1, 1); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (15, '修改', 'sys:auth:per:edit', 5, 300, 1, 1624189308837); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (16, '删除', 'sys:auth:per:remove', 5, 400, 1, 1624189256926); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (17, '添加', 'sys:auth:per:add', 5, 200, 1, 1624189283766); +INSERT INTO sys_permission (id, title, permission, parent_id, sort, create_time, update_time) VALUES (18,'授权','sys:auth:role:authz',7,100,1,1624335712144); + + + +-- 表:sys_role +CREATE TABLE "sys_role" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "name" varchar(100) NOT NULL, "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)); +INSERT INTO sys_role (id, name, create_time, update_time) VALUES (1, '管理员', 1, 1623749138537); +INSERT INTO sys_role (id, name, create_time, update_time) VALUES (2, '只读角色', 1, 1623749138537); + +-- 表:sys_role_permission +CREATE TABLE "sys_role_permission" ("role_id" integer NOT NULL, "permission_id" integer NOT NULL, PRIMARY KEY ("role_id", "permission_id")); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 1); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 2); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 3); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 4); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 5); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 6); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 7); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 8); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 9); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 10); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 11); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 12); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 13); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 14); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 15); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 16); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 17); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, 18); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (1, -1); + +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 4); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 6); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 8); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 1); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 2); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 3); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 5); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, 7); +INSERT INTO sys_role_permission (role_id, permission_id) VALUES (2, -1); + +-- 表:sys_user +CREATE TABLE "sys_user" ("id" integer PRIMARY KEY AUTOINCREMENT NOT NULL, "username" varchar(100) NOT NULL, "password" varchar(50) NOT NULL, "nick_name" varchar(50), "avatar" varchar(255), "phone_code" varchar(20), "mobile" varchar(20), "email" varchar(100),"remark" varchar(100), "status" integer NOT NULL DEFAULT (1), "create_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP), "update_time" datetime NOT NULL DEFAULT (CURRENT_TIMESTAMP)); +INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (1, 'admin', 'e10adc3949ba59abbe56e057f20f883e', 'admin', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,NULL); +INSERT INTO sys_user (id, username, password, nick_name, avatar, phone_code, mobile, email, status, create_time, update_time,remark) VALUES (2, 'readonly', 'e10adc3949ba59abbe56e057f20f883e', '只读用户', NULL, NULL, NULL, NULL, 1, 2011123132, 123132,'密码:123456'); + +-- 表:sys_user_role +CREATE TABLE "sys_user_role" ("role_id" integer NOT NULL, "user_id" integer NOT NULL, PRIMARY KEY ("role_id", "user_id")); +INSERT INTO sys_user_role (role_id, user_id) VALUES (1, 1); +INSERT INTO sys_user_role (role_id, user_id) VALUES (2, 2); + +-- 索引:IDX_223de54d6badbe43a5490450c3 +CREATE UNIQUE INDEX "IDX_223de54d6badbe43a5490450c3" ON "sys_role" ("name"); + +-- 索引:IDX_9e7164b2f1ea1348bc0eb0a7da +CREATE UNIQUE INDEX "IDX_9e7164b2f1ea1348bc0eb0a7da" ON "sys_user" ("username"); + + diff --git a/packages/ui/certd-server/db/migration/v00002__for_pre.sql b/packages/ui/certd-server/db/migration/v00002__for_pre.sql new file mode 100644 index 00000000..708d7aed --- /dev/null +++ b/packages/ui/certd-server/db/migration/v00002__for_pre.sql @@ -0,0 +1,4 @@ +-- for preview 限制演示环境的数据修改 +update sqlite_sequence set seq = 1000 where name = 'sys_user' ; +update sqlite_sequence set seq = 1000 where name = 'sys_permission' ; +update sqlite_sequence set seq = 1000 where name = 'sys_role' ; diff --git a/packages/ui/certd-server/jest.config.js b/packages/ui/certd-server/jest.config.js new file mode 100644 index 00000000..c5bd388a --- /dev/null +++ b/packages/ui/certd-server/jest.config.js @@ -0,0 +1,6 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + testPathIgnorePatterns: ['/test/fixtures'], + coveragePathIgnorePatterns: ['/test/'], +}; \ No newline at end of file diff --git a/packages/ui/certd-server/package.json b/packages/ui/certd-server/package.json new file mode 100644 index 00000000..610c5be3 --- /dev/null +++ b/packages/ui/certd-server/package.json @@ -0,0 +1,82 @@ +{ + "name": "@fast-crud/fs-server-js", + "version": "0.2.0", + "description": "fast-server base midway", + "private": true, + "scripts": { + "start": "NODE_ENV=production node ./bootstrap.js", + "online": "NODE_ENV=production node ./bootstrap.js", + "online:preview": "NODE_ENV=preview node ./bootstrap.js", + "dev": "cross-env NODE_ENV=local midway-bin dev --ts", + "dev:preview": "cross-env NODE_ENV=preview midway-bin dev --ts", + "test": "midway-bin test --ts", + "cov": "midway-bin cov --ts", + "lint": "mwts check", + "lint:fix": "mwts fix", + "ci": "npm run cov", + "build": "midway-bin build -c", + "build:preview": "cross-env NODE_ENV=preview midway-bin build -c", + "check": "luckyeye", + "mig": "typeorm migration:create -n name" + }, + "dependencies": { + "midway-flyway-js": "^3.0.0", + "@koa/cors": "^3.1.0", + "@midwayjs/bootstrap": "^3.0.0", + "@midwayjs/cache": "^3.0.0", + "@midwayjs/cli": "^1.2.38", + "@midwayjs/core": "^3.0.0", + "@midwayjs/decorator": "^3.0.0", + "@midwayjs/koa": "^3.0.0", + "@midwayjs/logger": "^2.17.0", + "@midwayjs/typeorm": "^3.5.3", + "@midwayjs/validate": "^3.0.0", + "@types/cache-manager": "^3.4.0", + "typeorm": "^0.3.10", + "cache-manager": "^3.4.3", + "dayjs": "^1.10.5", + "glob": "^7.1.7", + "jsonwebtoken": "^8.5.1", + "koa-bodyparser": "^4.3.0", + "lodash": "^4.17.21", + "log4js": "^6.3.0", + "md5": "^2.3.0", + "sqlite3": "^5.1.2", + "svg-captcha": "^1.4.0", + "@alicloud/pop-core": "^1.7.10" + }, + "devDependencies": { + "@midwayjs/cli": "^1.0.0", + "@midwayjs/luckyeye": "^1.0.0", + "@midwayjs/mock": "^3.0.0", + "@midwayjs/orm": "^3.0.0", + "@types/jest": "^26.0.10", + "@types/koa-bodyparser": "^4.3.0", + "@types/node": "14", + "cross-env": "^6.0.0", + "jest": "^26.4.0", + "mwts": "^1.0.5", + "ts-jest": "^26.2.0", + "typescript": "^4.0.0", + "ts-node": "^10.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + + "midway-bin-clean": [ + ".vscode/.tsbuildinfo", + "dist" + ], + "midway-luckyeye": { + "packages": [ + "midway_v2" + ] + }, + "repository": { + "type": "git", + "url": "https://github.com/fast-crud/fast-server-js" + }, + "author": "Greper", + "license": "MIT" +} diff --git a/packages/ui/certd-server/src/basic/base-controller.ts b/packages/ui/certd-server/src/basic/base-controller.ts new file mode 100644 index 00000000..38a5d76b --- /dev/null +++ b/packages/ui/certd-server/src/basic/base-controller.ts @@ -0,0 +1,33 @@ +import { Inject } from '@midwayjs/decorator'; +import { Context } from '@midwayjs/koa'; +import { Constants } from './constants'; + +export abstract class BaseController { + @Inject() + ctx: Context; + + /** + * 成功返回 + * @param data 返回数据 + */ + ok(data) { + const res = { + ...Constants.res.success, + data: undefined, + }; + if (data) { + res.data = data; + } + return res; + } + /** + * 失败返回 + * @param message + */ + fail(msg, code) { + return { + code: code ? code : Constants.res.error.code, + msg: msg ? msg : Constants.res.error.code, + }; + } +} diff --git a/packages/ui/certd-server/src/basic/base-service.ts b/packages/ui/certd-server/src/basic/base-service.ts new file mode 100644 index 00000000..077838a3 --- /dev/null +++ b/packages/ui/certd-server/src/basic/base-service.ts @@ -0,0 +1,150 @@ +import { Inject } from '@midwayjs/decorator'; +import { ValidateException } from './exception/validation-exception'; +import * as _ from 'lodash'; +import { Context } from '@midwayjs/koa'; +/** + * 服务基类 + */ +export abstract class BaseService { + @Inject() + ctx: Context; + + abstract getRepository(); + + /** + * 获得单个ID + * @param id ID + * @param infoIgnoreProperty 忽略返回属性 + */ + async info(id, infoIgnoreProperty?) { + if (!id) { + throw new ValidateException('id不能为空'); + } + const info = await this.getRepository().findOne({ + where:{ id } + }); + if (info && infoIgnoreProperty) { + for (const property of infoIgnoreProperty) { + delete info[property]; + } + } + return info; + } + + /** + * 非分页查询 + * @param option 查询配置 + */ + async find(options) { + return await this.getRepository().find(options); + } + + /** + * 删除 + * @param ids 删除的ID集合 如:[1,2,3] 或者 1,2,3 + */ + async delete(ids) { + if (ids instanceof Array) { + await this.getRepository().delete(ids); + } else if (typeof ids === 'string') { + await this.getRepository().delete(ids.split(',')); + } else { + //ids是一个condition + await this.getRepository().delete(ids); + } + await this.modifyAfter(ids); + } + + /** + * 新增|修改 + * @param param 数据 + */ + async addOrUpdate(param) { + await this.getRepository().save(param); + } + + /** + * 新增 + * @param param 数据 + */ + async add(param) { + const now = new Date().getTime(); + param.createTime = now; + param.updateTime = now; + await this.addOrUpdate(param); + await this.modifyAfter(param); + return { + id: param.id, + }; + } + + /** + * 修改 + * @param param 数据 + */ + async update(param) { + if (!param.id) throw new ValidateException('no id'); + param.updateTime = new Date().getTime(); + await this.addOrUpdate(param); + await this.modifyAfter(param); + } + + /** + * 新增|修改|删除 之后的操作 + * @param data 对应数据 + */ + async modifyAfter(data) {} + + /** + * 分页查询 + * @param query 查询条件 bean + * @param page + * @param order + * @param buildQuery + */ + async page(query, page = { offset: 0, limit: 20 }, order, buildQuery) { + if (page.offset == null) { + page.offset = 0; + } + if (page.limit == null) { + page.limit = 20; + } + const qb = this.getRepository().createQueryBuilder('main'); + if (order && order.prop) { + qb.orderBy('main.' + order.prop, order.asc ? 'ASC' : 'DESC'); + } else { + qb.orderBy('id', 'DESC'); + } + qb.offset(page.offset).limit(page.limit); + //根据bean query + if (query) { + let whereSql = ''; + let index = 0; + _.forEach(query, (value, key) => { + if (!value) { + return; + } + if (index !== 0) { + whereSql += ' and '; + } + whereSql += ` main.${key} = :${key} `; + index++; + }); + if (index > 0) { + qb.where(whereSql, query); + } + } + //自定义query + if (buildQuery) { + buildQuery(qb); + } + const list = await qb.getMany(); + const total = await qb.getCount(); + return { + records: list, + total, + offset: page.offset, + limit: page.limit, + }; + } +} diff --git a/packages/ui/certd-server/src/basic/constants.ts b/packages/ui/certd-server/src/basic/constants.ts new file mode 100644 index 00000000..992f13af --- /dev/null +++ b/packages/ui/certd-server/src/basic/constants.ts @@ -0,0 +1,28 @@ +export const Constants = { + res: { + error: { + code: 1, + message: 'error', + }, + success: { + code: 0, + message: 'success', + }, + validation: { + code: 10, + message: '参数错误', + }, + auth: { + code: 401, + message: '您还未登录或token已过期', + }, + permission: { + code: 402, + message: '您没有权限', + }, + preview: { + code: 10001, + message: '对不起,预览环境不允许修改此数据', + }, + }, +}; diff --git a/packages/ui/certd-server/src/basic/crud-controller.ts b/packages/ui/certd-server/src/basic/crud-controller.ts new file mode 100644 index 00000000..c9bb15d5 --- /dev/null +++ b/packages/ui/certd-server/src/basic/crud-controller.ts @@ -0,0 +1,50 @@ +import { ALL, Body, Post, Query } from '@midwayjs/decorator'; +import { BaseService } from './base-service'; +import { BaseController } from './base-controller'; + +export abstract class CrudController< + T extends BaseService +> extends BaseController { + abstract getService(); + + @Post('/page') + async page( + @Body(ALL) + body + ) { + const pageRet = await this.getService().page( + body?.query, + body?.page, + body?.sort, + null + ); + return this.ok(pageRet); + } + + @Post('/add') + async add( + @Body(ALL) + bean + ) { + const id = await this.getService().add(bean); + return this.ok(id); + } + + @Post('/update') + async update( + @Body(ALL) + bean + ) { + await this.getService().update(bean); + return this.ok(null); + } + @Post('/delete') + async delete( + @Query('id') + id + ) { + await this.getService().delete([id]); + return this.ok(null); + } +} + diff --git a/packages/ui/certd-server/src/basic/enum-item.ts b/packages/ui/certd-server/src/basic/enum-item.ts new file mode 100644 index 00000000..a7e4a4a8 --- /dev/null +++ b/packages/ui/certd-server/src/basic/enum-item.ts @@ -0,0 +1,11 @@ +export class EnumItem { + value: string; + label: string; + color: string; + + constructor(value, label, color) { + this.value = value; + this.label = label; + this.color = color; + } +} diff --git a/packages/ui/certd-server/src/basic/exception/auth-exception.ts b/packages/ui/certd-server/src/basic/exception/auth-exception.ts new file mode 100644 index 00000000..92e82a4d --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/auth-exception.ts @@ -0,0 +1,14 @@ +import { Constants } from '../constants'; +import { BaseException } from './base-exception'; +/** + * 授权异常 + */ +export class AuthException extends BaseException { + constructor(message) { + super( + 'AuthException', + Constants.res.auth.code, + message ? message : Constants.res.auth.message + ); + } +} diff --git a/packages/ui/certd-server/src/basic/exception/base-exception.ts b/packages/ui/certd-server/src/basic/exception/base-exception.ts new file mode 100644 index 00000000..a4e89604 --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/base-exception.ts @@ -0,0 +1,11 @@ +/** + * 异常基类 + */ +export class BaseException extends Error { + status: number; + constructor(name, code, message) { + super(message); + this.name = name; + this.status = code; + } +} diff --git a/packages/ui/certd-server/src/basic/exception/common-exception.ts b/packages/ui/certd-server/src/basic/exception/common-exception.ts new file mode 100644 index 00000000..f4afa91e --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/common-exception.ts @@ -0,0 +1,14 @@ +import { Constants } from '../constants'; +import { BaseException } from './base-exception'; +/** + * 通用异常 + */ +export class CommonException extends BaseException { + constructor(message) { + super( + 'CommonException', + Constants.res.error.code, + message ? message : Constants.res.error.message + ); + } +} diff --git a/packages/ui/certd-server/src/basic/exception/permission-exception.ts b/packages/ui/certd-server/src/basic/exception/permission-exception.ts new file mode 100644 index 00000000..edeb2b7e --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/permission-exception.ts @@ -0,0 +1,14 @@ +import { Constants } from '../constants'; +import { BaseException } from './base-exception'; +/** + * 授权异常 + */ +export class PermissionException extends BaseException { + constructor(message) { + super( + 'PermissionException', + Constants.res.permission.code, + message ? message : Constants.res.permission.message + ); + } +} diff --git a/packages/ui/certd-server/src/basic/exception/preview-exception.ts b/packages/ui/certd-server/src/basic/exception/preview-exception.ts new file mode 100644 index 00000000..2abfc48e --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/preview-exception.ts @@ -0,0 +1,14 @@ +import { Constants } from '../constants'; +import { BaseException } from './base-exception'; +/** + * 预览模式 + */ +export class PreviewException extends BaseException { + constructor(message) { + super( + 'PreviewException', + Constants.res.preview.code, + message ? message : Constants.res.preview.message + ); + } +} diff --git a/packages/ui/certd-server/src/basic/exception/validation-exception.ts b/packages/ui/certd-server/src/basic/exception/validation-exception.ts new file mode 100644 index 00000000..d595ff71 --- /dev/null +++ b/packages/ui/certd-server/src/basic/exception/validation-exception.ts @@ -0,0 +1,14 @@ +import { Constants } from '../constants'; +import { BaseException } from './base-exception'; +/** + * 校验异常 + */ +export class ValidateException extends BaseException { + constructor(message) { + super( + 'ValidateException', + Constants.res.validation.code, + message ? message : Constants.res.validation.message + ); + } +} diff --git a/packages/ui/certd-server/src/basic/result.ts b/packages/ui/certd-server/src/basic/result.ts new file mode 100644 index 00000000..ea102244 --- /dev/null +++ b/packages/ui/certd-server/src/basic/result.ts @@ -0,0 +1,18 @@ +export class Result { + code: number; + msg: string; + data: T; + constructor(code, msg, data?) { + this.code = code; + this.msg = msg; + this.data = data; + } + + static error(code = 1, msg) { + return new Result(code, msg, null); + } + + static success(msg, data?) { + return new Result(0, msg, data); + } +} diff --git a/packages/ui/certd-server/src/config/config.default.ts b/packages/ui/certd-server/src/config/config.default.ts new file mode 100644 index 00000000..a44a9989 --- /dev/null +++ b/packages/ui/certd-server/src/config/config.default.ts @@ -0,0 +1,56 @@ +import { join } from 'path'; +import {FlywayHistory} from "midway-flyway-js/dist/entity"; + + +import { MidwayConfig } from '@midwayjs/core'; + +export default { + // use for cookie sign key, should change to your own and keep security + keys: 'greper-is-666', + koa: { + port: 7001, + }, + /** + * 演示环境 + */ + preview :{ + enabled: false, + }, + + /** + * 数据库 + */ + typeorm : { + dataSource: { + default: { + /** + * 单数据库实例 + */ + type: 'sqlite', + database: join(__dirname, '../../data/db.sqlite'), + synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true + logging: true, + + // 配置实体模型 或者 entities: '/entity', + entities: ['/modules/authority/entity/*',FlywayHistory], + } + } + }, + /** + * 自动升级数据库脚本 + */ + flyway : { + scriptDir:join(__dirname, '../../db/migration'), + }, + + biz : { + jwt: { + secret: 'greper-is-666', + expire: 7 * 24 * 60, //单位秒 + }, + auth: { + ignoreUrls: ['/', '/api/login', '/api/register'], + }, + } + +} as MidwayConfig; diff --git a/packages/ui/certd-server/src/config/config.preview.ts b/packages/ui/certd-server/src/config/config.preview.ts new file mode 100644 index 00000000..aa4bc6aa --- /dev/null +++ b/packages/ui/certd-server/src/config/config.preview.ts @@ -0,0 +1,10 @@ +import { MidwayConfig } from '@midwayjs/core'; + +export default { + /** + * 演示环境 + */ + preview: { + enabled: true, + } +} as MidwayConfig; diff --git a/packages/ui/certd-server/src/config/config.production.ts b/packages/ui/certd-server/src/config/config.production.ts new file mode 100644 index 00000000..aa4bc6aa --- /dev/null +++ b/packages/ui/certd-server/src/config/config.production.ts @@ -0,0 +1,10 @@ +import { MidwayConfig } from '@midwayjs/core'; + +export default { + /** + * 演示环境 + */ + preview: { + enabled: true, + } +} as MidwayConfig; diff --git a/packages/ui/certd-server/src/configuration.ts b/packages/ui/certd-server/src/configuration.ts new file mode 100644 index 00000000..390d73c7 --- /dev/null +++ b/packages/ui/certd-server/src/configuration.ts @@ -0,0 +1,59 @@ +import * as validateComp from '@midwayjs/validate'; +import * as productionConfig from './config/config.production'; +import * as previewConfig from './config/config.preview'; +import * as defaultConfig from './config/config.default'; +import { Configuration, App } from '@midwayjs/decorator'; +import * as koa from '@midwayjs/koa'; +import * as bodyParser from 'koa-bodyparser'; +import * as orm from '@midwayjs/typeorm'; +import * as cache from '@midwayjs/cache'; +import * as cors from '@koa/cors'; +import { join } from 'path'; +import * as flyway from 'midway-flyway-js'; +import {ReportMiddleware} from "./middleware/report"; +import {GlobalExceptionMiddleware} from "./middleware/global-exception"; +import {PreviewMiddleware} from "./middleware/preview"; +import {AuthorityMiddleware} from "./middleware/authority"; +@Configuration({ + imports: [koa, orm, cache, flyway,validateComp], + importConfigs: [ + { + default: defaultConfig, + preview: previewConfig, + production: productionConfig, + }, + ], +}) +export class ContainerConfiguration {} +@Configuration({ + conflictCheck: true, + importConfigs: [join(__dirname, './config')], +}) +export class ContainerLifeCycle { + @App() + app: koa.Application; + + async onReady() { + //跨域 + this.app.use( + cors({ + origin: '*', + }) + ); + // bodyparser options see https://github.com/koajs/bodyparser + this.app.use(bodyParser()); + //请求日志打印 + + this.app.useMiddleware([ + + ReportMiddleware, + //统一异常处理 + GlobalExceptionMiddleware, + //预览模式限制修改id<1000的数据 + PreviewMiddleware, + //授权处理 + AuthorityMiddleware + ]) + } +} + diff --git a/packages/ui/certd-server/src/controller/home.ts b/packages/ui/certd-server/src/controller/home.ts new file mode 100644 index 00000000..6aa5a9a7 --- /dev/null +++ b/packages/ui/certd-server/src/controller/home.ts @@ -0,0 +1,10 @@ +import { Controller, Get, Provide } from '@midwayjs/decorator'; + +@Provide() +@Controller('/') +export class HomeController { + @Get('/') + async home(): Promise { + return 'Hello Midwayjs!'; + } +} diff --git a/packages/ui/certd-server/src/middleware/authority.ts b/packages/ui/certd-server/src/middleware/authority.ts new file mode 100644 index 00000000..ff23c6a5 --- /dev/null +++ b/packages/ui/certd-server/src/middleware/authority.ts @@ -0,0 +1,48 @@ +import { Config, Provide } from '@midwayjs/decorator'; +import { + IWebMiddleware, + IMidwayKoaContext, + NextFunction +} from '@midwayjs/koa'; +import * as _ from 'lodash'; +import * as jwt from 'jsonwebtoken'; +import { Constants } from '../basic/constants'; + +/** + * 权限校验 + */ +@Provide() +export class AuthorityMiddleware implements IWebMiddleware { + @Config('biz.jwt.secret') + private secret: string; + @Config('biz.auth.ignoreUrls') + private ignoreUrls: string[]; + + resolve() { + return async (ctx: IMidwayKoaContext, next: NextFunction) => { + const { url } = ctx; + const token = ctx.get('Authorization'); + // 路由地址为 admin前缀的 需要权限校验 + // console.log('ctx', ctx); + const queryIndex = url.indexOf('?'); + let uri = url; + if (queryIndex >= 0) { + uri = url.substring(0, queryIndex); + } + const yes = this.ignoreUrls.includes(uri); + if (yes) { + await next(); + return; + } + + try { + ctx.user = jwt.verify(token, this.secret); + } catch (err) { + ctx.status = 401; + ctx.body = Constants.res.auth; + return; + } + await next(); + }; + } +} diff --git a/packages/ui/certd-server/src/middleware/global-exception.ts b/packages/ui/certd-server/src/middleware/global-exception.ts new file mode 100644 index 00000000..5c842feb --- /dev/null +++ b/packages/ui/certd-server/src/middleware/global-exception.ts @@ -0,0 +1,27 @@ +import { Provide } from '@midwayjs/decorator'; +import { + IWebMiddleware, + IMidwayKoaContext, + NextFunction, +} from '@midwayjs/koa'; +import { logger } from '../utils/logger'; +import { Result } from '../basic/result'; + +@Provide() +export class GlobalExceptionMiddleware implements IWebMiddleware { + resolve() { + return async (ctx: IMidwayKoaContext, next: NextFunction) => { + const { url } = ctx; + const startTime = Date.now(); + logger.info('请求开始:', url); + try { + await next(); + logger.info('请求完成', url, Date.now() - startTime + 'ms'); + } catch (err) { + logger.error('请求异常:', url, Date.now() - startTime + 'ms', err); + ctx.status = 200; + ctx.body = Result.error(err.code != null ? err.code : 1, err.message); + } + }; + } +} diff --git a/packages/ui/certd-server/src/middleware/preview.ts b/packages/ui/certd-server/src/middleware/preview.ts new file mode 100644 index 00000000..53910d74 --- /dev/null +++ b/packages/ui/certd-server/src/middleware/preview.ts @@ -0,0 +1,50 @@ +import { Config, Provide } from '@midwayjs/decorator'; +import { + IMidwayKoaContext, + NextFunction, + IWebMiddleware, +} from '@midwayjs/koa'; +import { PreviewException } from '../basic/exception/preview-exception'; + +/** + * 预览模式 + */ +@Provide() +export class PreviewMiddleware implements IWebMiddleware { + @Config('preview.enabled') + private preview: boolean; + + resolve() { + return async (ctx: IMidwayKoaContext, next: NextFunction) => { + if (!this.preview) { + await next(); + return; + } + let { url, request } = ctx; + const body: any = request.body; + let id = body.id || request.query.id; + const roleId = body.roleId; + if (id == null && roleId != null) { + id = roleId; + } + if (id != null && typeof id === 'string') { + id = parseInt(id); + } + if (url.indexOf('?') !== -1) { + url = url.substring(0, url.indexOf('?')); + } + const isModify = + url.endsWith('update') || + url.endsWith('delete') || + url.endsWith('authz'); + const isPreviewId = id < 1000; + if (this.preview && isModify && isPreviewId) { + throw new PreviewException( + '对不起,预览环境不允许修改此数据,如需体验请添加新数据' + ); + } + await next(); + return; + }; + } +} diff --git a/packages/ui/certd-server/src/middleware/report.ts b/packages/ui/certd-server/src/middleware/report.ts new file mode 100644 index 00000000..a866150e --- /dev/null +++ b/packages/ui/certd-server/src/middleware/report.ts @@ -0,0 +1,28 @@ +import { Provide } from '@midwayjs/decorator'; +import { + IWebMiddleware, + IMidwayKoaContext, + NextFunction, +} from '@midwayjs/koa'; +import { logger } from '../utils/logger'; + +@Provide() +export class ReportMiddleware implements IWebMiddleware { + resolve() { + return async (ctx: IMidwayKoaContext, next: NextFunction) => { + const { url } = ctx; + logger.info('请求开始:', url); + const startTime = Date.now(); + await next(); + if (ctx.status !== 200) { + logger.error( + '请求失败:', + url, + ctx.status, + Date.now() - startTime + 'ms' + ); + } + logger.info('请求完成:', url, ctx.status, Date.now() - startTime + 'ms'); + }; + } +} diff --git a/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts new file mode 100644 index 00000000..2ef2a7af --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/controller/permission-controller.ts @@ -0,0 +1,63 @@ +import { + ALL, + Body, + Controller, + Inject, + Post, + Provide, + Query, +} from '@midwayjs/decorator'; +import { CrudController } from '../../../basic/crud-controller'; +import { PermissionService } from '../service/permission-service'; + +/** + * 权限资源 + */ +@Provide() +@Controller('/api/sys/authority/permission') +export class PermissionController extends CrudController { + @Inject() + service: PermissionService; + + getService() { + return this.service; + } + + @Post('/page') + async page( + @Body(ALL) + body + ) { + return await super.page(body); + } + + @Post('/add') + async add( + @Body(ALL) + bean + ) { + return await super.add(bean); + } + + @Post('/update') + async update( + @Body(ALL) + bean + ) { + return await super.update(bean); + } + @Post('/delete') + async delete( + @Query('id') + id + ) { + return await super.delete(id); + } + + @Post('/tree') + async tree() { + const tree = await this.service.tree({}); + return this.ok(tree); + } +} + diff --git a/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts new file mode 100644 index 00000000..df0d4122 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/controller/role-controller.ts @@ -0,0 +1,96 @@ +import { + ALL, + Body, + Controller, + Inject, + Post, + Provide, + Query, +} from '@midwayjs/decorator'; +import { CrudController } from '../../../basic/crud-controller'; +import { RoleService } from '../service/role-service'; + +/** + * 系统用户 + */ +@Provide() +@Controller('/api/sys/authority/role') +export class RoleController extends CrudController { + @Inject() + service: RoleService; + + getService() { + return this.service; + } + + @Post('/page') + async page( + @Body(ALL) + body + ) { + return await super.page(body); + } + + @Post('/list') + async list() { + const ret = await this.service.find({}); + return this.ok(ret); + } + + @Post('/add') + async add( + @Body(ALL) + bean + ) { + return await super.add(bean); + } + + @Post('/update') + async update( + @Body(ALL) + bean + ) { + return await super.update(bean); + } + @Post('/delete') + async delete( + @Query('id') + id + ) { + return await super.delete(id); + } + + @Post('/getPermissionTree') + async getPermissionTree( + @Query('id') + id + ) { + const ret = await this.service.getPermissionTreeByRoleId(id); + return this.ok(ret); + } + + @Post('/getPermissionIds') + async getPermissionIds( + @Query('id') + id + ) { + const ret = await this.service.getPermissionIdsByRoleId(id); + return this.ok(ret); + } + + /** + * 给角色授予权限 + * @param id + */ + @Post('/authz') + async authz( + @Body('roleId') + roleId, + @Body('permissionIds') + permissionIds + ) { + await this.service.authz(roleId, permissionIds); + return this.ok(null); + } +} + diff --git a/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts new file mode 100644 index 00000000..9c579584 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/controller/user-controller.ts @@ -0,0 +1,119 @@ +import { + Provide, + Controller, + Post, + Inject, + Body, + Query, + ALL, +} from '@midwayjs/decorator'; +import { UserService } from '../service/user-service'; +import { CrudController } from '../../../basic/crud-controller'; +import { RoleService } from '../service/role-service'; +import { PermissionService } from '../service/permission-service'; + +/** + * 系统用户 + */ +@Provide() +@Controller('/api/sys/authority/user') +export class UserController extends CrudController { + @Inject() + service: UserService; + + @Inject() + roleService: RoleService; + @Inject() + permissionService: PermissionService; + + getService() { + return this.service; + } + + @Post('/page') + async page( + @Body(ALL) + body + ) { + const ret = await super.page(body); + + const users = ret.data.records; + + //获取roles + const userIds = users.map((item) => item.id); + const userRoles = await this.roleService.getByUserIds(userIds); + const userRolesMap = new Map(); + for (const ur of userRoles) { + let roles = userRolesMap.get(ur.userId); + if (roles == null) { + roles = []; + userRolesMap.set(ur.userId, roles); + } + roles.push(ur.roleId); + } + + for (const record of users) { + //withRoles + record.roles = userRolesMap.get(record.id); + //删除密码字段 + delete record.password; + } + + return ret; + } + + @Post('/add') + async add( + @Body(ALL) + bean + ) { + return await super.add(bean); + } + + @Post('/update') + async update( + @Body(ALL) + bean + ) { + return await super.update(bean); + } + @Post('/delete') + async delete( + @Query('id') + id + ) { + return await super.delete(id); + } + + /** + * 当前登录用户的个人信息 + */ + @Post('/mine') + public async mine() { + const id = this.ctx.user.id; + const info = await this.service.info(id, ['password']); + return this.ok(info); + } + + /** + * 当前登录用户的权限列表 + */ + @Post('/permissions') + public async permissions() { + const id = this.ctx.user.id; + const permissions = await this.service.getUserPermissions(id); + return this.ok(permissions); + } + + /** + * 当前登录用户的权限树形列表 + */ + @Post('/permissionTree') + public async permissionTree() { + const id = this.ctx.user.id; + const permissions = await this.service.getUserPermissions(id); + const tree = this.permissionService.buildTree(permissions); + return this.ok(tree); + } +} + diff --git a/packages/ui/certd-server/src/modules/authority/entity/permission.ts b/packages/ui/certd-server/src/modules/authority/entity/permission.ts new file mode 100644 index 00000000..6fa91e18 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/entity/permission.ts @@ -0,0 +1,40 @@ +import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm'; + +/** + * 权限 + */ +@Entity('sys_permission') +export class PermissionEntity { + @PrimaryGeneratedColumn() + id: number; + @Column({ comment: '标题', length: 100 }) + title: string; + /** + * 权限代码 + * 示例:sys:user:read + */ + @Column({ comment: '权限代码', length: 100, nullable: true }) + permission: string; + + @Column({ name: 'parent_id', comment: '父节点ID', default: -1 }) + parentId: number; + + @Column({ comment: '排序号' }) + sort: number; + + @Column({ + name: 'create_time', + comment: '创建时间', + default: () => 'CURRENT_TIMESTAMP', + }) + createTime: Date; + @Column({ + name: 'update_time', + comment: '修改时间', + default: () => 'CURRENT_TIMESTAMP', + }) + updateTime: Date; + + // @ManyToMany(type => RoleEntity, res => res.permissions) + // roles: RoleEntity[]; +} diff --git a/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts b/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts new file mode 100644 index 00000000..85ac0c73 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/entity/role-permission.ts @@ -0,0 +1,12 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +/** + * 角色权限多对多 + */ +@Entity('sys_role_permission') +export class RolePermissionEntity { + @PrimaryColumn({ name: 'role_id' }) + roleId: number; + @PrimaryColumn({ name: 'permission_id' }) + permissionId: number; +} diff --git a/packages/ui/certd-server/src/modules/authority/entity/role.ts b/packages/ui/certd-server/src/modules/authority/entity/role.ts new file mode 100644 index 00000000..2aecfb74 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/entity/role.ts @@ -0,0 +1,43 @@ +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; + +/** + * 角色 + */ +@Entity('sys_role') +export class RoleEntity { + @PrimaryGeneratedColumn() + id: number; + @Index({ unique: true }) + @Column({ comment: '角色名称', length: 100 }) + name: string; + + @Column({ + name: 'create_time', + comment: '创建时间', + default: () => 'CURRENT_TIMESTAMP', + }) + createTime: Date; + @Column({ + name: 'update_time', + comment: '修改时间', + default: () => 'CURRENT_TIMESTAMP', + }) + updateTime: Date; + + // @ManyToMany(type => PermissionEntity, res => res.roles) + // @JoinTable({ + // name: 'sys_role_resources', + // joinColumn: { + // name: 'roleId', + // referencedColumnName: 'id', + // }, + // inverseJoinColumn: { + // name: 'resourceId', + // referencedColumnName: 'id', + // }, + // }) + // resources: PermissionEntity[]; + + // @ManyToMany(type => UserEntity, res => res.roles) + // users: UserEntity[]; +} diff --git a/packages/ui/certd-server/src/modules/authority/entity/user-role.ts b/packages/ui/certd-server/src/modules/authority/entity/user-role.ts new file mode 100644 index 00000000..533bf644 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/entity/user-role.ts @@ -0,0 +1,12 @@ +import { Entity, PrimaryColumn } from 'typeorm'; + +/** + * 用户角色多对多 + */ +@Entity('sys_user_role') +export class UserRoleEntity { + @PrimaryColumn({ name: 'role_id' }) + roleId: number; + @PrimaryColumn({ name: 'user_id' }) + userId: number; +} diff --git a/packages/ui/certd-server/src/modules/authority/entity/user.ts b/packages/ui/certd-server/src/modules/authority/entity/user.ts new file mode 100644 index 00000000..59824583 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/entity/user.ts @@ -0,0 +1,63 @@ +import { Column, Entity, Index, PrimaryGeneratedColumn } from 'typeorm'; + +/** + * 系统用户 + */ +@Entity('sys_user') +export class UserEntity { + @PrimaryGeneratedColumn() + id: number; + @Index({ unique: true }) + @Column({ comment: '用户名', length: 100 }) + username: string; + + @Column({ comment: '密码', length: 100 }) + password: string; + + @Column({ name: 'nick_name', comment: '昵称', length: 100, nullable: true }) + nickName: string; + + @Column({ comment: '头像', length: 255, nullable: true }) + avatar: string; + + @Column({ name: 'phone_code', comment: '区号', length: 20, nullable: true }) + phoneCode: string; + + @Column({ comment: '手机', length: 20, nullable: true }) + mobile: string; + + @Column({ comment: '邮箱', length: 50, nullable: true }) + email: string; + + @Column({ comment: '备注', length: 100, nullable: true }) + remark: string; + + @Column({ comment: '状态 0:禁用 1:启用', default: 1, type: 'int' }) + status: number; + @Column({ + name: 'create_time', + comment: '创建时间', + default: () => 'CURRENT_TIMESTAMP', + }) + createTime: Date; + @Column({ + name: 'update_time', + comment: '修改时间', + default: () => 'CURRENT_TIMESTAMP', + }) + updateTime: Date; + + // @ManyToMany(type => RoleEntity, res => res.users) + // @JoinTable({ + // name: 'sys_user_roles', + // joinColumn: { + // name: 'userId', + // referencedColumnName: 'id', + // }, + // inverseJoinColumn: { + // name: 'roleId', + // referencedColumnName: 'id', + // }, + // }) + // roles: RoleEntity[]; +} diff --git a/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts b/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts new file mode 100644 index 00000000..cf3411ec --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/enums/ResourceTypeEnum.ts @@ -0,0 +1,17 @@ +import { EnumItem } from '../../../basic/enum-item'; +import * as _ from 'lodash'; +class ResourceTypes { + MENU = new EnumItem('menu', '菜单', 'blue'); + BTN = new EnumItem('btn', '按钮', 'green'); + ROUTE = new EnumItem('route', '路由', 'red'); + + names() { + const list = []; + _.forEach(this, (item, key) => { + list.push(item); + }); + return list; + } +} + +export const ResourceTypeEnum = new ResourceTypes(); diff --git a/packages/ui/certd-server/src/modules/authority/service/permission-service.ts b/packages/ui/certd-server/src/modules/authority/service/permission-service.ts new file mode 100644 index 00000000..1015ddc1 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/service/permission-service.ts @@ -0,0 +1,52 @@ +import { Provide } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { BaseService } from '../../../basic/base-service'; +import { PermissionEntity } from '../entity/permission'; + +/** + * 权限资源 + */ +@Provide() +export class PermissionService extends BaseService { + @InjectEntityModel(PermissionEntity) + repository: Repository; + + getRepository() { + return this.repository; + } + + async tree(options: any = {}) { + if (options.order == null) { + options.order = { + sort: 'ASC', + }; + } + const list = await this.find(options); + return this.buildTree(list); + } + + buildTree(list: any) { + const idMap = {}; + const root = []; + for (const item of list) { + idMap[item.id] = item; + if (item.parentId == null || item.parentId <= 0) { + root.push(item); + } + } + + for (const item of list) { + if (item.parentId > 0) { + const parent = idMap[item.parentId]; + if (parent) { + if (parent.children == null) { + parent.children = []; + } + parent.children.push(item); + } + } + } + return root; + } +} diff --git a/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts b/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts new file mode 100644 index 00000000..81da1cc1 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/service/role-permission-service.ts @@ -0,0 +1,18 @@ +import { Provide } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { BaseService } from '../../../basic/base-service'; +import { RolePermissionEntity } from '../entity/role-permission'; + +/** + * 角色->权限 + */ +@Provide() +export class RolePermissionService extends BaseService { + @InjectEntityModel(RolePermissionEntity) + repository: Repository; + + getRepository() { + return this.repository; + } +} diff --git a/packages/ui/certd-server/src/modules/authority/service/role-service.ts b/packages/ui/certd-server/src/modules/authority/service/role-service.ts new file mode 100644 index 00000000..4831f1a4 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/service/role-service.ts @@ -0,0 +1,101 @@ +import { Inject, Provide } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { In, Repository } from 'typeorm'; +import { BaseService } from '../../../basic/base-service'; +import { RoleEntity } from '../entity/role'; +import { UserRoleService } from './user-role-service'; +import { RolePermissionEntity } from '../entity/role-permission'; +import { PermissionService } from './permission-service'; +import * as _ from 'lodash'; +import { RolePermissionService } from './role-permission-service'; +/** + * 角色 + */ +@Provide() +export class RoleService extends BaseService { + @InjectEntityModel(RoleEntity) + repository: Repository; + @Inject() + userRoleService: UserRoleService; + @Inject() + permissionService: PermissionService; + @Inject() + rolePermissionService: RolePermissionService; + + getRepository() { + return this.repository; + } + + async getRoleIdsByUserId(id: any) { + const userRoles = await this.userRoleService.find({ + where: { userId: id }, + }); + return userRoles.map(item => item.roleId); + } + async getByUserIds(ids: any) { + return await this.userRoleService.find({ + where: { + userId: In(ids), + }, + }); + } + + async getPermissionByRoleIds(roleIds: any) { + return await this.permissionService.repository + .createQueryBuilder('permission') + .innerJoinAndSelect( + RolePermissionEntity, + 'rp', + 'rp.permissionId = permission.id and rp.roleId in (:...roleIds)', + { roleIds } + ) + .getMany(); + } + + async addRoles(userId: number, roles) { + if (roles == null || roles.length === 0) { + return; + } + for (const roleId of roles) { + await this.userRoleService.add({ + userId, + roleId, + }); + } + } + + async updateRoles(userId, roles) { + if (roles == null) { + return; + } + const oldRoleIds = await this.getRoleIdsByUserId(userId); + if (_.xor(roles, oldRoleIds).length === 0) { + //如果两个数组相等,则不修改 + return; + } + //先删除所有 + await this.userRoleService.delete({ userId }); + //再添加 + await this.addRoles(userId, roles); + } + + async getPermissionTreeByRoleId(id: any) { + const list = await this.getPermissionByRoleIds([id]); + return this.permissionService.buildTree(list); + } + + async getPermissionIdsByRoleId(id: any) { + const list = await this.getPermissionByRoleIds([id]); + return list.map(item => item.id); + } + + async authz(roleId: any, permissionIds: any) { + await this.rolePermissionService.delete({ roleId }); + for (const permissionId of permissionIds) { + await this.rolePermissionService.add({ + roleId, + permissionId, + }); + } + } +} diff --git a/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts b/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts new file mode 100644 index 00000000..333c7251 --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/service/user-role-service.ts @@ -0,0 +1,18 @@ +import { Provide } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { BaseService } from '../../../basic/base-service'; +import { UserRoleEntity } from '../entity/user-role'; + +/** + * 用户->角色 + */ +@Provide() +export class UserRoleService extends BaseService { + @InjectEntityModel(UserRoleEntity) + repository: Repository; + + getRepository() { + return this.repository; + } +} diff --git a/packages/ui/certd-server/src/modules/authority/service/user-service.ts b/packages/ui/certd-server/src/modules/authority/service/user-service.ts new file mode 100644 index 00000000..696d938a --- /dev/null +++ b/packages/ui/certd-server/src/modules/authority/service/user-service.ts @@ -0,0 +1,113 @@ +import { Inject, Provide } from '@midwayjs/decorator'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { UserEntity } from '../entity/user'; +import * as _ from 'lodash'; +import * as md5 from 'md5'; +import { CommonException } from '../../../basic/exception/common-exception'; +import { BaseService } from '../../../basic/base-service'; +import { logger } from '../../../utils/logger'; +import { RoleService } from './role-service'; +import { PermissionService } from './permission-service'; +import { UserRoleService } from './user-role-service'; + +/** + * 系统用户 + */ +@Provide() +export class UserService extends BaseService { + @InjectEntityModel(UserEntity) + repository: Repository; + @Inject() + roleService: RoleService; + @Inject() + permissionService: PermissionService; + @Inject() + userRoleService: UserRoleService; + + getRepository() { + return this.repository; + } + + /** + * 获得个人信息 + */ + async mine() { + const info = await this.repository.findOne({ + where: { + id: this.ctx.user.id, + } + }); + delete info.password; + return info; + } + + /** + * 新增 + * @param param + */ + async add(param) { + const exists = await this.repository.findOne({ + where:{ + username: param.username, + } + }); + if (!_.isEmpty(exists)) { + throw new CommonException('用户名已经存在'); + } + const password = param.password ?? '123456'; + param.password = md5(password); // 默认密码 建议未改密码不能登陆 + await super.add(param); + //添加角色 + if (param.roles && param.roles.length > 0) { + await this.roleService.addRoles(param.id, param.roles); + } + return param.id; + } + + /** + * 修改 + * @param param 数据 + */ + async update(param) { + if (param.id == null) { + throw new CommonException('id不能为空'); + } + const userInfo = await this.repository.findOne({ + where:{ id: param.id } + }); + if (!userInfo) { + throw new CommonException('用户不存在'); + } + + delete param.username; + if (!_.isEmpty(param.password)) { + param.password = md5(param.password); + } else { + delete param.password; + } + await super.update(param); + await this.roleService.updateRoles(param.id, param.roles); + } + + async findOne(param) { + return this.repository.findOne({ + where:param + }); + } + + checkPassword(rawPassword: any, md5Password: any) { + logger.info('md5', md5('123456')); + return md5(rawPassword) === md5Password; + } + + /** + * 获取用户的菜单资源列表 + * @param id + */ + async getUserPermissions(id: any) { + const roleIds = await this.roleService.getRoleIdsByUserId(id); + + return await this.roleService.getPermissionByRoleIds(roleIds); + } +} diff --git a/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts b/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts new file mode 100644 index 00000000..bd2eff18 --- /dev/null +++ b/packages/ui/certd-server/src/modules/basic/controller/basic-controller.ts @@ -0,0 +1,56 @@ +import { Rule,RuleType } from '@midwayjs/validate'; +import { ALL, Inject } from '@midwayjs/decorator'; +import { Body } from '@midwayjs/decorator'; +import { Controller, Post, Provide } from '@midwayjs/decorator'; +import { BaseController } from '../../../basic/base-controller'; +import { CodeService } from '../service/code-service'; +export class SmsCodeReq { + @Rule(RuleType.number().required()) + phoneCode: number; + + @Rule(RuleType.string().required()) + mobile: string; + + @Rule(RuleType.string().required().max(10)) + randomStr: string; + + @Rule(RuleType.number().required().max(4)) + imgCode: string; +} + +// const enumsMap = {}; +// glob('src/modules/**/enums/*.ts', {}, (err, matches) => { +// console.log('matched', matches); +// for (const filePath of matches) { +// const module = require('/' + filePath); +// console.log('modules', module); +// } +// }); + +/** + */ +@Provide() +@Controller('/api/basic') +export class BasicController extends BaseController { + @Inject() + codeService: CodeService; + @Post('/sendSmsCode') + public sendSmsCode( + @Body(ALL) + body: SmsCodeReq + ) { + // 设置缓存内容 + return this.ok(null); + } + + @Post('/captcha') + public async getCaptcha( + @Body() + randomStr + ) { + console.assert(randomStr < 10, 'randomStr 过长'); + const captcha = await this.codeService.generateCaptcha(randomStr); + return this.ok(captcha.data); + } +} + diff --git a/packages/ui/certd-server/src/modules/basic/service/code-service.ts b/packages/ui/certd-server/src/modules/basic/service/code-service.ts new file mode 100644 index 00000000..cb306af6 --- /dev/null +++ b/packages/ui/certd-server/src/modules/basic/service/code-service.ts @@ -0,0 +1,57 @@ +import { Inject, Provide } from '@midwayjs/decorator'; +import { CacheManager } from '@midwayjs/cache'; +const svgCaptcha = require('svg-captcha'); + +// {data: '', text: 'abcd'} +/** + */ +@Provide() +export class CodeService { + @Inject() + cache: CacheManager; // 依赖注入CacheManager + + /** + */ + async generateCaptcha(randomStr) { + console.assert(randomStr < 10, 'randomStr 过长'); + const c = svgCaptcha.create(); + //{data: '', text: 'abcd'} + const imgCode = c.text; // = RandomUtil.randomStr(4, true); + await this.cache.set('imgCode:' + randomStr, imgCode, { + ttl: 2 * 60 * 1000, //过期时间 2分钟 + }); + return c; + } + + async getCaptchaText(randomStr) { + return await this.cache.get('imgCode:' + randomStr); + } + + async removeCaptcha(randomStr) { + await this.cache.del('imgCode:' + randomStr); + } + + async checkCaptcha(randomStr, userCaptcha) { + const code = await this.getCaptchaText(randomStr); + if (code == null) { + throw new Error('验证码已过期'); + } + if (code !== userCaptcha) { + throw new Error('验证码不正确'); + } + return true; + } + /** + */ + async sendSms(phoneCode, mobile, smsCode) { + console.assert(phoneCode != null && mobile != null, '手机号不能为空'); + console.assert(smsCode != null, '验证码不能为空'); + } + + /** + * loginBySmsCode + */ + async loginBySmsCode(user, smsCode) { + console.assert(user.mobile != null, '手机号不能为空'); + } +} diff --git a/packages/ui/certd-server/src/modules/login/controller/login-controller.ts b/packages/ui/certd-server/src/modules/login/controller/login-controller.ts new file mode 100644 index 00000000..13fb65d9 --- /dev/null +++ b/packages/ui/certd-server/src/modules/login/controller/login-controller.ts @@ -0,0 +1,31 @@ +import { + Body, + Controller, + Inject, + Post, + Provide, + ALL, +} from '@midwayjs/decorator'; +import { LoginService } from '../service/login-service'; +import { BaseController } from '../../../basic/base-controller'; + +/** + */ +@Provide() +@Controller('/api/') +export class LoginController extends BaseController { + @Inject() + loginService: LoginService; + @Post('/login') + public async login( + @Body(ALL) + user + ) { + const token = await this.loginService.login(user); + return this.ok(token); + } + + @Post('/logout') + public logout() {} +} + diff --git a/packages/ui/certd-server/src/modules/login/service/login-service.ts b/packages/ui/certd-server/src/modules/login/service/login-service.ts new file mode 100644 index 00000000..85ceaee5 --- /dev/null +++ b/packages/ui/certd-server/src/modules/login/service/login-service.ts @@ -0,0 +1,52 @@ +import { Config, Inject, Provide } from '@midwayjs/decorator'; +import { UserService } from '../../authority/service/user-service'; +import * as jwt from 'jsonwebtoken'; +import { CommonException } from '../../../basic/exception/common-exception'; + +/** + * 系统用户 + */ +@Provide() +export class LoginService { + @Inject() + userService: UserService; + @Config('biz.jwt') + private jwt: any; + + /** + * login + */ + async login(user) { + console.assert(user.username != null, '用户名不能为空'); + const info = await this.userService.findOne({ username: user.username }); + if (info == null) { + throw new CommonException('用户名或密码错误'); + } + const right = this.userService.checkPassword(user.password, info.password); + if (!right) { + throw new CommonException('用户名或密码错误'); + } + + return this.generateToken(info); + } + + /** + * 生成token + * @param user 用户对象 + */ + async generateToken(user) { + const tokenInfo = { + username: user.username, + id: user.id, + }; + const expire = this.jwt.expire; + const token = jwt.sign(tokenInfo, this.jwt.secret, { + expiresIn: expire, + }); + + return { + token, + expire, + }; + } +} diff --git a/packages/ui/certd-server/src/utils/logger.ts b/packages/ui/certd-server/src/utils/logger.ts new file mode 100644 index 00000000..d4fea9ea --- /dev/null +++ b/packages/ui/certd-server/src/utils/logger.ts @@ -0,0 +1,12 @@ +const log4js = require('log4js'); +const level = process.env.NODE_ENV === 'development' ? 'debug' : 'info'; +const path = require('path'); +const filename = path.join('/logs/server.log'); +log4js.configure({ + appenders: { + std: { type: 'stdout', level: 'debug' }, + file: { type: 'file', pattern: 'yyyy-MM-dd', daysToKeep: 3, filename }, + }, + categories: { default: { appenders: ['std'], level } }, +}); +export const logger = log4js.getLogger('fast'); diff --git a/packages/ui/certd-server/src/utils/random.ts b/packages/ui/certd-server/src/utils/random.ts new file mode 100644 index 00000000..6be6f6e2 --- /dev/null +++ b/packages/ui/certd-server/src/utils/random.ts @@ -0,0 +1,43 @@ +const numbers = '0123456789'; +const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'; +const specials = '~!@#$%^*()_+-=[]{}|;:,./<>?'; + +/** + * Generate random string + * @param {Number} length + * @param {Object} options + */ +function randomStr(length, options) { + length || (length = 8); + options || (options = {}); + + let chars = ''; + let result = ''; + + if (options === true) { + chars = numbers + letters; + } else if (typeof options === 'string') { + chars = options; + } else { + if (options.numbers !== false) { + chars += typeof options.numbers === 'string' ? options.numbers : numbers; + } + + if (options.letters !== false) { + chars += typeof options.letters === 'string' ? options.letters : letters; + } + + if (options.specials) { + chars += + typeof options.specials === 'string' ? options.specials : specials; + } + } + + while (length > 0) { + length--; + result += chars[Math.floor(Math.random() * chars.length)]; + } + return result; +} + +export const RandomUtil = { randomStr }; diff --git a/packages/ui/certd-server/test/controller/api.test.ts b/packages/ui/certd-server/test/controller/api.test.ts new file mode 100644 index 00000000..28dcfd03 --- /dev/null +++ b/packages/ui/certd-server/test/controller/api.test.ts @@ -0,0 +1,25 @@ +import { createApp, close, createHttpRequest } from '@midwayjs/mock'; +import { Framework } from '@midwayjs/koa'; +import * as assert from 'assert'; + +describe('test/controller/home.test.ts', () => { + + it('should POST /api/get_user', async () => { + // create app + const app = await createApp(); + + // make request + const result = await createHttpRequest(app).post('/api/get_user').query({ uid: 123 }); + + // use expect by jest + expect(result.status).toBe(200); + expect(result.body.message).toBe('OK'); + + // or use assert + assert.deepStrictEqual(result.status, 200); + assert.deepStrictEqual(result.body.data.uid, '123'); + + // close app + await close(app); + }); +}); diff --git a/packages/ui/certd-server/test/controller/home.test.ts b/packages/ui/certd-server/test/controller/home.test.ts new file mode 100644 index 00000000..2e957889 --- /dev/null +++ b/packages/ui/certd-server/test/controller/home.test.ts @@ -0,0 +1,26 @@ +import { createApp, close, createHttpRequest } from '@midwayjs/mock'; +import { Framework } from '@midwayjs/koa'; +import * as assert from 'assert'; + +describe('test/controller/home.test.ts', () => { + + it('should GET /', async () => { + // create app + const app = await createApp(); + + // make request + const result = await createHttpRequest(app).get('/'); + + // use expect by jest + expect(result.status).toBe(200); + expect(result.text).toBe('Hello Midwayjs!'); + + // or use assert + assert.deepStrictEqual(result.status, 200); + assert.deepStrictEqual(result.text, 'Hello Midwayjs!'); + + // close app + await close(app); + }); + +}); diff --git a/packages/ui/certd-server/tsconfig.json b/packages/ui/certd-server/tsconfig.json new file mode 100644 index 00000000..8553a7c5 --- /dev/null +++ b/packages/ui/certd-server/tsconfig.json @@ -0,0 +1,24 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "ES2018", + "module": "commonjs", + "moduleResolution": "node", + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "inlineSourceMap":true, + "noImplicitThis": true, + "noUnusedLocals": true, + "stripInternal": true, + "skipLibCheck": false, + "pretty": true, + "declaration": true, + "typeRoots": [ "./typings", "./node_modules/@types"], + "outDir": "dist" + }, + "exclude": [ + "dist", + "node_modules", + "test" + ] +}