diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 index 857ff6a..4cafa26 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ logs/ +cache/ npm-debug.log yarn-error.log node_modules/ @@ -9,11 +10,11 @@ dist/ .idea/ run/ .DS_Store +launch.json *.sw* *.un~ .tsbuildinfo .tsbuildinfo.* -.audit -typings/ -public/uploads/ -cache/ +data/* +pnpm-lock.yaml +public/uploads/* diff --git a/.hintrc b/.hintrc new file mode 100644 index 0000000..82cba57 --- /dev/null +++ b/.hintrc @@ -0,0 +1,9 @@ +{ + "extends": [ + "development" + ], + "hints": { + "typescript-config/consistent-casing": "off", + "typescript-config/strict": "off" + } +} \ No newline at end of file diff --git a/.vscode/controller.code-snippets b/.vscode/controller.code-snippets index 3a7c846..13be753 100644 --- a/.vscode/controller.code-snippets +++ b/.vscode/controller.code-snippets @@ -8,7 +8,6 @@ "/**", " * 描述", " */", - "@Provide()", "@CoolController({", " api: ['add', 'delete', 'update', 'info', 'list', 'page'],", " entity: 实体,", diff --git a/.vscode/entity.code-snippets b/.vscode/entity.code-snippets index fa44c76..992a066 100644 --- a/.vscode/entity.code-snippets +++ b/.vscode/entity.code-snippets @@ -2,14 +2,13 @@ "entity": { "prefix": "entity", "body": [ - "import { EntityModel } from '@midwayjs/orm';", "import { BaseEntity } from '@cool-midway/core';", - "import { Column } from 'typeorm';", + "import { Column, Entity } from 'typeorm';", "", "/**", " * 描述", " */", - "@EntityModel('xxx_xxx_xxx')", + "@Entity('xxx_xxx_xxx')", "export class XxxEntity extends BaseEntity {", " @Column({ comment: '描述' })", " xxx: string;", diff --git a/.vscode/event.code-snippets b/.vscode/event.code-snippets index ed05fd8..dae6473 100644 --- a/.vscode/event.code-snippets +++ b/.vscode/event.code-snippets @@ -2,7 +2,6 @@ "event": { "prefix": "event", "body": [ - "import { Provide, Scope, ScopeEnum } from '@midwayjs/decorator';", "import { CoolEvent, Event } from '@cool-midway/core';", "", "/**", diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index f1f2b58..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - // 使用 IntelliSense 了解相关属性。 - // 悬停以查看现有属性的描述。 - // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "type": "pwa-node", - "request": "launch", - "name": "Launch Program", - "skipFiles": [ - "/**" - ], - "program": "${workspaceFolder}/bootstrap.js", - "preLaunchTask": "tsc: build - tsconfig.json", - "outFiles": [ - "${workspaceFolder}/dist/**/*.js" - ] - } - ] -} \ No newline at end of file diff --git a/.vscode/service.code-snippets b/.vscode/service.code-snippets index 5722f72..600a96e 100644 --- a/.vscode/service.code-snippets +++ b/.vscode/service.code-snippets @@ -4,7 +4,7 @@ "body": [ "import { Provide } from '@midwayjs/decorator';", "import { BaseService } from '@cool-midway/core';", - "import { InjectEntityModel } from '@midwayjs/orm';", + "import { InjectEntityModel } from '@midwayjs/typeorm';", "import { Repository } from 'typeorm';", "", "/**", diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 6f3a291..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "liveServer.settings.port": 5501 -} \ No newline at end of file diff --git a/LICENSE b/LICENSE index 83140ce..5a6831d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2021 cool-team-official +Copyright (c) 2023 cool-team-official Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md old mode 100755 new mode 100644 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..bf8115c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +# 本地数据库环境 +# 数据存放在当前目录下的 data里 +# 推荐使用安装了docker扩展的vscode打开目录 在本文件上右键可以快速启动,停止 +# 如不需要相关容器开机自启动,可注释掉 restart: always +# 如遇端口冲突 可调整ports下 :前面的端口号 +version: "3.1" + +services: + coolDB: + image: mysql + command: + --default-authentication-plugin=mysql_native_password + --sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION + --group_concat_max_len=102400 + restart: always + volumes: + - ./data/mysql/:/var/lib/mysql/ + environment: + TZ: Asia/Shanghai # 指定时区 + MYSQL_ROOT_PASSWORD: "123456" # 配置root用户密码 + MYSQL_DATABASE: "cool" # 业务库名 + MYSQL_USER: "root" # 业务库用户名 + MYSQL_PASSWORD: "123456" # 业务库密码 + ports: + - 3306:3306 + + coolRedis: + image: redis + #command: --requirepass "12345678" # redis库密码,不需要密码注释本行 + restart: always + environment: + TZ: Asia/Shanghai # 指定时区 + volumes: + - ./data/redis/:/data/ + ports: + - 6379:6379 diff --git a/package.json b/package.json old mode 100755 new mode 100644 index 9705d70..4f78e51 --- a/package.json +++ b/package.json @@ -1,61 +1,68 @@ { "name": "cool-admin", - "version": "5.0.0", - "description": "", + "version": "6.0.0", + "description": "一个项目用COOL就够了", "private": true, "dependencies": { - "@cool-midway/core": "^5.1.7", - "@cool-midway/es": "^5.0.2", - "@cool-midway/file": "^5.0.7", - "@cool-midway/pay": "^5.0.0", - "@cool-midway/rpc": "^5.0.1", - "@cool-midway/task": "^5.0.1", - "@midwayjs/bootstrap": "^3.2.0", - "@midwayjs/core": "^3.2.0", - "@midwayjs/decorator": "^3.1.6", - "@midwayjs/info": "^3.2.0", - "@midwayjs/koa": "^3.2.0", - "@midwayjs/logger": "^2.16.3", - "@midwayjs/orm": "^3.2.0", - "@midwayjs/socketio": "^3.2.0", - "@midwayjs/static-file": "^3.2.0", - "@midwayjs/task": "^3.2.2", - "@midwayjs/validate": "^3.2.0", - "@midwayjs/view-ejs": "^3.2.0", - "cfork": "^1.8.0", + "@cool-midway/cloud": "^6.0.0", + "@cool-midway/core": "^6.0.0", + "@cool-midway/file": "^6.0.0", + "@cool-midway/iot": "^6.0.0", + "@cool-midway/pay": "^6.0.0", + "@cool-midway/rpc": "^6.0.1", + "@cool-midway/task": "^6.0.0", + "@midwayjs/bootstrap": "^3.10.7", + "@midwayjs/cache": "^3.10.9", + "@midwayjs/core": "^3.10.7", + "@midwayjs/cross-domain": "^3.10.8", + "@midwayjs/decorator": "^3.10.7", + "@midwayjs/info": "^3.10.7", + "@midwayjs/koa": "^3.10.7", + "@midwayjs/logger": "^2.17.0", + "@midwayjs/static-file": "^3.10.8", + "@midwayjs/task": "^3.6.0", + "@midwayjs/typeorm": "^3.10.7", + "@midwayjs/validate": "^3.10.7", + "@midwayjs/view-ejs": "^3.10.7", + "cache-manager-fs-hash": "^1.0.0", "ipip-ipdb": "^0.6.0", - "jsonwebtoken": "^8.5.1", + "jsonwebtoken": "^9.0.0", + "lodash": "^4.17.21", + "md5": "^2.3.0", "mini-svg-data-uri": "^1.4.4", - "mysql2": "^2.3.3", + "moment": "^2.29.4", + "mysql2": "^3.1.2", "svg-captcha": "^1.4.0", - "typeorm": "0.2.45" + "typeorm": "^0.3.12", + "uuid": "^9.0.0" }, "devDependencies": { - "@midwayjs/cli": "1.3.12-beta.1", - "@midwayjs/mock": "^3.2.0", - "@types/jest": "^27.4.1", - "@types/koa": "^2.13.4", - "@types/node": "17", + "@midwayjs/cli": "^2.0.11", + "@midwayjs/mock": "^3.10.7", + "@types/jest": "^29.4.0", + "@types/koa": "^2.13.5", + "@types/node": "18", "cross-env": "^7.0.3", - "jest": "^27.5.1", + "jest": "^29.4.2", "mwts": "^1.3.0", - "swagger-ui-dist": "^4.9.1", - "ts-jest": "^27.1.4", - "typescript": "^4.6.3" + "ts-jest": "^29.0.5", + "typescript": "~4.9.5" }, "engines": { "node": ">=12.0.0" }, "scripts": { - "start_single": "NODE_ENV=prod node ./bootstrap.js", - "start": "NODE_ENV=prod node ./server.js", + "start": "NODE_ENV=production node ./bootstrap.js", "dev": "cross-env && cross-env NODE_ENV=local TS_NODE_TYPE_CHECK=false TS_NODE_TRANSPILE_ONLY=true 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": "midway-bin build -c", + "pm2:start": "pm2 start ./bootstrap.js -i max --name cool-admin", + "pm2:stop": "pm2 stop cool-admin & pm2 delete cool-admin", + "pm2:debug": "pm2-runtime start ./bootstrap.js -i max --name cool-admin" }, "midway-bin-clean": [ ".vscode/.tsbuildinfo", @@ -63,8 +70,8 @@ ], "repository": { "type": "git", - "url": "" + "url": "https://cool-js.com" }, - "author": "anonymous", + "author": "COOL", "license": "MIT" } diff --git a/public/favicon.ico b/public/favicon.ico index da11adc..a0c3086 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/server.js b/server.js deleted file mode 100644 index 8aea631..0000000 --- a/server.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -const cfork = require('cfork'); -const util = require('util'); -const path = require('path'); -const os = require('os'); - -// 获取 cpu 核数 -const cpuNumbers = os.cpus().length; - -cfork({ - exec: path.join(__dirname, './bootstrap.js'), - count: cpuNumbers, -}) - .on('fork', worker => { - console.warn( - '[%s] [worker:%d] new worker start', - Date(), - worker.process.pid - ); - }) - .on('disconnect', worker => { - console.warn( - '[%s] [master:%s] wroker:%s disconnect, exitedAfterDisconnect: %s, state: %s.', - Date(), - process.pid, - worker.process.pid, - worker.exitedAfterDisconnect, - worker.state - ); - }) - .on('exit', (worker, code, signal) => { - const exitCode = worker.process.exitCode; - const err = new Error( - util.format( - 'worker %s died (code: %s, signal: %s, exitedAfterDisconnect: %s, state: %s)', - worker.process.pid, - exitCode, - signal, - worker.exitedAfterDisconnect, - worker.state - ) - ); - err.name = 'WorkerDiedError'; - console.error( - '[%s] [master:%s] wroker exit: %s', - Date(), - process.pid, - err.stack - ); - }); diff --git a/src/config/config.default.ts b/src/config/config.default.ts index bf72c21..7fcf569 100644 --- a/src/config/config.default.ts +++ b/src/config/config.default.ts @@ -1,27 +1,25 @@ -import { CoolConfig } from '@cool-midway/core'; -import { MODETYPE } from '@cool-midway/file'; +import { CoolConfig, MODETYPE } from '@cool-midway/core'; import { MidwayConfig } from '@midwayjs/core'; -// import * as redisStore from 'cache-manager-ioredis'; import * as fsStore from 'cache-manager-fs-hash'; export default { - // 修改成你自己独有的key + // use for cookie sign key, should change to your own and keep security keys: 'cool-admin for node', koa: { port: 8001, }, - // 文件上传 - upload: { - fileSize: '200mb', - whitelist: null, - }, // 模板渲染 view: { mapping: { '.html': 'ejs', }, }, - // 本地缓存 + // 文件上传 + upload: { + fileSize: '200mb', + whitelist: null, + }, + // 缓存 可切换成其他缓存如:redis http://midwayjs.org/docs/extensions/cache cache: { store: fsStore, options: { @@ -29,34 +27,12 @@ export default { ttl: -1, }, }, - // redis缓存 - // cache: { - // store: redisStore, - // options: { - // host: '127.0.0.1', - // port: 6379, - // password: '', - // db: 1, - // ttl: null, - // }, - // }, - // cool配置 cool: { - // redis: { - // host: '127.0.0.1', - // port: 6379, - // db: 0, - // }, - // 是否自动导入数据库 file: { // 上传模式 本地上传或云存储 mode: MODETYPE.LOCAL, - // 本地上传 文件地址前缀,当且仅当mode为LOCAL时生效 + // 本地上传 文件地址前缀 domain: 'http://127.0.0.1:8001', }, } as CoolConfig, -} as - | MidwayConfig - | { - cache: any; - }; +} as MidwayConfig; diff --git a/src/config/config.local.ts b/src/config/config.local.ts index 67304ea..d4031e1 100644 --- a/src/config/config.local.ts +++ b/src/config/config.local.ts @@ -5,22 +5,35 @@ import { MidwayConfig } from '@midwayjs/core'; * 本地开发 npm run dev 读取的配置文件 */ export default { - orm: { - type: 'mysql', - host: '127.0.0.1', - port: 3306, - username: 'root', - password: '123456', - database: 'cool', - // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 - synchronize: true, - // 打印日志 - logging: true, - // 字符集 - charset: 'utf8mb4', + typeorm: { + dataSource: { + default: { + type: 'mysql', + host: '127.0.0.1', + port: 3306, + username: 'root', + password: '123456', + database: 'cool', + // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 + synchronize: true, + // 打印日志 + logging: false, + // 字符集 + charset: 'utf8mb4', + // 是否开启缓存 + cache: true, + // 实体路径 + entities: ['**/modules/*/entity'], + }, + }, }, cool: { // 是否自动导入数据库 initDB: true, + // crud配置 + crud: { + // 软删除 + softDelete: true, + }, } as CoolConfig, } as MidwayConfig; diff --git a/src/config/config.prod.ts b/src/config/config.prod.ts index 1c6ac62..cb87cb3 100644 --- a/src/config/config.prod.ts +++ b/src/config/config.prod.ts @@ -5,22 +5,30 @@ import { MidwayConfig } from '@midwayjs/core'; * 本地开发 npm run dev 读取的配置文件 */ export default { - orm: { - type: 'mysql', - host: '127.0.0.1', - port: 3306, - username: 'root', - password: '123456', - database: 'cool', - // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 - synchronize: false, - // 打印日志 - logging: false, - // 字符集 - charset: 'utf8mb4', + typeorm: { + dataSource: { + default: { + type: 'mysql', + host: '127.0.0.1', + port: 3306, + username: 'root', + password: '123456', + database: 'cool', + // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 + synchronize: false, + // 打印日志 + logging: false, + // 字符集 + charset: 'utf8mb4', + // 是否开启缓存 + cache: true, + // 实体路径 + entities: ['**/modules/*/entity'], + }, + }, }, cool: { - // 是否自动导入数据库 + // 是否自动导入数据库,生产环境不建议开,用本地的数据库手动初始化 initDB: false, } as CoolConfig, } as MidwayConfig; diff --git a/src/configuration.ts b/src/configuration.ts index 1bc45de..2019271 100644 --- a/src/configuration.ts +++ b/src/configuration.ts @@ -1,48 +1,51 @@ +import * as orm from '@midwayjs/typeorm'; import { Configuration, App } from '@midwayjs/decorator'; import * as koa from '@midwayjs/koa'; import * as validate from '@midwayjs/validate'; import * as info from '@midwayjs/info'; import { join } from 'path'; -import * as staticFile from '@midwayjs/static-file'; import * as view from '@midwayjs/view-ejs'; -import * as orm from '@midwayjs/orm'; -import * as cool from '@cool-midway/core'; -import * as file from '@cool-midway/file'; +import * as staticFile from '@midwayjs/static-file'; import * as localTask from '@midwayjs/task'; -// import * as socketio from '@midwayjs/socketio'; +// import * as crossDomain from '@midwayjs/cross-domain'; +import * as cool from '@cool-midway/core'; +import * as cloud from '@cool-midway/cloud'; +import * as file from '@cool-midway/file'; +// import * as rpc from '@cool-midway/rpc'; // import * as task from '@cool-midway/task'; // import * as pay from '@cool-midway/pay'; -// import * as es from '@cool-midway/es'; -// import * as rpc from '@cool-midway/rpc'; +// import * as iot from '@cool-midway/iot'; @Configuration({ imports: [ - // http://koajs.cn/ + // https://koajs.com/ koa, - // 参数验证 http://midwayjs.org/docs/extensions/validate - validate, - // 本地任务 http://midwayjs.org/docs/extensions/task - localTask, - // 模板渲染 http://midwayjs.org/docs/extensions/render + // 是否开启跨域(注:顺序不能乱放!!!) http://www.midwayjs.org/docs/extensions/cross_domain + // crossDomain, + // 模板渲染 https://midwayjs.org/docs/extensions/render view, - // 静态文件托管 http://midwayjs.org/docs/extensions/static_file + // 静态文件托管 https://midwayjs.org/docs/extensions/static_file staticFile, - // typeorm https://typeorm.io 打不开? https://typeorm.biunav.com/zh/ + // orm https://midwayjs.org/docs/extensions/orm orm, - // socketio http://www.midwayjs.org/docs/extensions/socketio - // socketio, - // cool-admin 官方组件 https://www.cool-js.com + // 参数验证 https://midwayjs.org/docs/extensions/validate + validate, + // 本地任务 http://midwayjs.org/docs/legacy/task + localTask, + // cool-admin 官方组件 https://cool-js.com cool, - // 文件上传 阿里云存储 腾讯云存储 七牛云存储 + // 文件上传 本地 阿里云存储 腾讯云存储 七牛云存储 file, - // 任务与队列 - // task, - // 支付 微信与支付宝 - // pay, - // elasticsearch - // es, // rpc 微服务 远程调用 // rpc, + // 任务与队列 + // task, + // cool-admin 云开发组件 + cloud, + // 支付(微信、支付宝) https://cool-js.com/admin/node/core/pay.html + // pay, + // 物联网开发,如MQTT支持等 + // iot, { component: info, enabledEnvironment: ['local'], diff --git a/src/modules/base/controller/admin/sys/menu.ts b/src/modules/base/controller/admin/sys/menu.ts index 297ac3b..8f5808f 100644 --- a/src/modules/base/controller/admin/sys/menu.ts +++ b/src/modules/base/controller/admin/sys/menu.ts @@ -1,4 +1,4 @@ -import { Inject, Provide } from '@midwayjs/decorator'; +import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; import { CoolController, BaseController } from '@cool-midway/core'; import { BaseSysMenuEntity } from '../../../entity/sys/menu'; import { BaseSysMenuService } from '../../../service/sys/menu'; @@ -15,4 +15,21 @@ import { BaseSysMenuService } from '../../../service/sys/menu'; export class BaseSysMenuController extends BaseController { @Inject() baseSysMenuService: BaseSysMenuService; + + @Post('/parse', { summary: '解析' }) + async parse( + @Body('entity') entity: string, + @Body('controller') controller: string, + @Body('module') module: string + ) { + return this.ok( + await this.baseSysMenuService.parse(entity, controller, module) + ); + } + + @Post('/create', { summary: '创建代码' }) + async create(@Body() body) { + await this.baseSysMenuService.create(body); + return this.ok(); + } } diff --git a/src/modules/base/entity/sys/conf.ts b/src/modules/base/entity/sys/conf.ts index cada1ef..fcfc5e1 100644 --- a/src/modules/base/entity/sys/conf.ts +++ b/src/modules/base/entity/sys/conf.ts @@ -1,11 +1,10 @@ -import { Column, Index } from 'typeorm'; -import { EntityModel } from '@midwayjs/orm'; +import { Column, Index, Entity } from 'typeorm'; import { BaseEntity } from '@cool-midway/core'; /** * 系统配置 */ -@EntityModel('base_sys_conf') +@Entity('base_sys_conf') export class BaseSysConfEntity extends BaseEntity { @Index({ unique: true }) @Column({ comment: '配置键' }) diff --git a/src/modules/base/entity/sys/department.ts b/src/modules/base/entity/sys/department.ts index edd688c..b2c2186 100644 --- a/src/modules/base/entity/sys/department.ts +++ b/src/modules/base/entity/sys/department.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 部门 */ -@EntityModel('base_sys_department') +@Entity('base_sys_department') export class BaseSysDepartmentEntity extends BaseEntity { @Column({ comment: '部门名称' }) name: string; diff --git a/src/modules/base/entity/sys/log.ts b/src/modules/base/entity/sys/log.ts index b0ca6d3..94dccaf 100644 --- a/src/modules/base/entity/sys/log.ts +++ b/src/modules/base/entity/sys/log.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column, Index } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 系统日志 */ -@EntityModel('base_sys_log') +@Entity('base_sys_log') export class BaseSysLogEntity extends BaseEntity { @Index() @Column({ comment: '用户ID', nullable: true, type: 'bigint' }) @@ -16,13 +15,13 @@ export class BaseSysLogEntity extends BaseEntity { action: string; @Index() - @Column({ comment: 'ip', nullable: true, length: 50 }) + @Column({ comment: 'ip', nullable: true }) ip: string; @Index() @Column({ comment: 'ip地址', nullable: true, length: 50 }) ipAddr: string; - @Column({ comment: '参数', nullable: true, type: 'text' }) + @Column({ comment: '参数', nullable: true, type: 'json' }) params: string; } diff --git a/src/modules/base/entity/sys/menu.ts b/src/modules/base/entity/sys/menu.ts index 7c7dd5b..3055162 100644 --- a/src/modules/base/entity/sys/menu.ts +++ b/src/modules/base/entity/sys/menu.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 菜单 */ -@EntityModel('base_sys_menu') +@Entity('base_sys_menu') export class BaseSysMenuEntity extends BaseEntity { @Column({ comment: '父菜单ID', type: 'bigint', nullable: true }) parentId: number; @@ -20,7 +19,7 @@ export class BaseSysMenuEntity extends BaseEntity { perms: string; @Column({ - comment: '类型 0:目录 1:菜单 2:按钮', + comment: '类型 0-目录 1-菜单 2-按钮', default: 0, type: 'tinyint', }) diff --git a/src/modules/base/entity/sys/param.ts b/src/modules/base/entity/sys/param.ts index 4110230..5937c79 100644 --- a/src/modules/base/entity/sys/param.ts +++ b/src/modules/base/entity/sys/param.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column, Index } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 参数配置 */ -@EntityModel('base_sys_param') +@Entity('base_sys_param') export class BaseSysParamEntity extends BaseEntity { @Index() @Column({ comment: '键位' }) diff --git a/src/modules/base/entity/sys/role.ts b/src/modules/base/entity/sys/role.ts index ad64ae9..2d6ed06 100644 --- a/src/modules/base/entity/sys/role.ts +++ b/src/modules/base/entity/sys/role.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column, Index } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 角色 */ -@EntityModel('base_sys_role') +@Entity('base_sys_role') export class BaseSysRoleEntity extends BaseEntity { @Column({ comment: '用户ID' }) userId: string; @@ -23,4 +22,10 @@ export class BaseSysRoleEntity extends BaseEntity { @Column({ comment: '数据权限是否关联上下级', default: 1 }) relevance: number; + + @Column({ comment: '菜单权限', type: 'json' }) + menuIdList: number[]; + + @Column({ comment: '部门权限', type: 'json' }) + departmentIdList: number[]; } diff --git a/src/modules/base/entity/sys/role_department.ts b/src/modules/base/entity/sys/role_department.ts index a923231..39afa99 100644 --- a/src/modules/base/entity/sys/role_department.ts +++ b/src/modules/base/entity/sys/role_department.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 角色部门 */ -@EntityModel('base_sys_role_department') +@Entity('base_sys_role_department') export class BaseSysRoleDepartmentEntity extends BaseEntity { @Column({ comment: '角色ID', type: 'bigint' }) roleId: number; diff --git a/src/modules/base/entity/sys/role_menu.ts b/src/modules/base/entity/sys/role_menu.ts index 5c3873b..d608177 100644 --- a/src/modules/base/entity/sys/role_menu.ts +++ b/src/modules/base/entity/sys/role_menu.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 角色菜单 */ -@EntityModel('base_sys_role_menu') +@Entity('base_sys_role_menu') export class BaseSysRoleMenuEntity extends BaseEntity { @Column({ comment: '角色ID', type: 'bigint' }) roleId: number; diff --git a/src/modules/base/entity/sys/user.ts b/src/modules/base/entity/sys/user.ts index a407e0a..db384cc 100644 --- a/src/modules/base/entity/sys/user.ts +++ b/src/modules/base/entity/sys/user.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column, Index } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 系统用户 */ -@EntityModel('base_sys_user') +@Entity('base_sys_user') export class BaseSysUserEntity extends BaseEntity { @Index() @Column({ comment: '部门ID', type: 'bigint', nullable: true }) diff --git a/src/modules/base/entity/sys/user_role.ts b/src/modules/base/entity/sys/user_role.ts index 382dcd1..e2e63ae 100644 --- a/src/modules/base/entity/sys/user_role.ts +++ b/src/modules/base/entity/sys/user_role.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 用户角色 */ -@EntityModel('base_sys_user_role') +@Entity('base_sys_user_role') export class BaseSysUserRoleEntity extends BaseEntity { @Column({ comment: '用户ID', type: 'bigint' }) userId: number; diff --git a/src/modules/base/init.sql b/src/modules/base/init.sql index 7b391b4..3802602 100644 --- a/src/modules/base/init.sql +++ b/src/modules/base/init.sql @@ -17,6 +17,45 @@ SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; +-- ---------------------------- +-- Table structure for base_app_space_info +-- ---------------------------- +DROP TABLE IF EXISTS `base_app_space_info`; +CREATE TABLE `base_app_space_info` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间', + `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间', + `url` varchar(255) NOT NULL COMMENT '地址', + `type` varchar(255) NOT NULL COMMENT '类型', + `classifyId` bigint(20) DEFAULT NULL COMMENT '分类ID', + PRIMARY KEY (`id`), + KEY `IDX_4aed04cbfa2ecdc01485b86e51` (`createTime`), + KEY `IDX_abd5de4a4895eb253a5cabb20f` (`updateTime`) +) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Table structure for base_app_space_type +-- ---------------------------- +DROP TABLE IF EXISTS `base_app_space_type`; +CREATE TABLE `base_app_space_type` ( + `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID', + `createTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) COMMENT '创建时间', + `updateTime` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6) COMMENT '更新时间', + `name` varchar(255) NOT NULL COMMENT '类别名称', + `parentId` tinyint(4) DEFAULT NULL COMMENT '父分类ID', + PRIMARY KEY (`id`), + KEY `IDX_5e8376603f89fdf3e7bb05103a` (`createTime`), + KEY `IDX_500ea9e8b2c5c08c9b86a0667e` (`updateTime`) +) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4; + +-- ---------------------------- +-- Records of base_app_space_type +-- ---------------------------- +BEGIN; +INSERT INTO `base_app_space_type` VALUES (1, '2021-02-26 14:07:48.867045', '2021-02-26 14:07:48.867045', 'a', NULL); +INSERT INTO `base_app_space_type` VALUES (2, '2021-02-26 14:07:52.285531', '2021-02-26 14:07:52.285531', 'b', NULL); +COMMIT; + -- ---------------------------- -- Table structure for base_sys_conf -- ---------------------------- @@ -133,7 +172,7 @@ INSERT INTO `base_sys_menu` VALUES (30, '2019-09-12 17:37:03.000000', '2021-03-0 INSERT INTO `base_sys_menu` VALUES (43, '2019-11-07 14:22:34.000000', '2021-03-08 23:02:51.000000', 45, 'crud 示例', '/crud', NULL, 1, 'icon-favor', 1, 'cool/modules/demo/views/crud.vue', 1, 1); INSERT INTO `base_sys_menu` VALUES (45, '2019-11-07 22:36:57.000000', '2019-11-11 15:21:10.000000', 1, '组件库', '/ui-lib', NULL, 0, 'icon-common', 2, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (47, '2019-11-08 09:35:08.000000', '2021-02-27 17:16:35.000000', NULL, '框架教程', '/tutorial', NULL, 0, 'icon-task', 4, NULL, 1, 1); -INSERT INTO `base_sys_menu` VALUES (48, '2019-11-08 09:35:53.000000', '2021-03-03 11:03:21.000000', 47, '文档', '/tutorial/doc', NULL, 1, 'icon-log', 0, 'https://cool-js.com', 1, 1); +INSERT INTO `base_sys_menu` VALUES (48, '2019-11-08 09:35:53.000000', '2021-03-03 11:03:21.000000', 47, '文档', '/tutorial/doc', NULL, 1, 'icon-log', 0, 'https://admin.cool-js.com', 1, 1); INSERT INTO `base_sys_menu` VALUES (49, '2019-11-09 22:11:13.000000', '2021-03-09 09:50:46.000000', 45, 'quill 富文本编辑器', '/editor-quill', NULL, 1, 'icon-favor', 2, 'cool/modules/demo/views/editor-quill.vue', 1, 1); INSERT INTO `base_sys_menu` VALUES (59, '2019-11-18 16:50:27.000000', '2019-11-18 16:50:27.000000', 97, '部门列表', NULL, 'base:sys:department:list', 2, NULL, 0, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (60, '2019-11-18 16:50:45.000000', '2019-11-18 16:50:45.000000', 97, '新增部门', NULL, 'base:sys:department:add', 2, NULL, 0, NULL, 1, 1); @@ -158,6 +197,14 @@ INSERT INTO `base_sys_menu` VALUES (99, '1900-01-20 14:14:22.823000', '1900-01-2 INSERT INTO `base_sys_menu` VALUES (100, '1900-01-20 14:14:33.973000', '1900-01-20 14:14:33.973000', 97, '修改', NULL, 'base:sys:user:delete,base:sys:user:update', 2, NULL, 0, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (101, '2021-01-12 14:14:51.000000', '2021-01-12 14:14:51.000000', 97, '查询', NULL, 'base:sys:user:page,base:sys:user:list,base:sys:user:info', 2, NULL, 0, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (105, '2021-01-21 10:42:55.000000', '2021-01-21 10:42:55.000000', 2, '监控管理', NULL, NULL, 0, 'icon-rank', 6, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (109, '2021-02-27 14:13:56.000000', '2021-02-27 17:09:19.000000', NULL, '插件管理', NULL, NULL, 0, 'icon-menu', 3, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (110, '2021-02-27 14:14:13.000000', '2021-03-08 23:01:30.000000', 109, '插件列表', '/plugin', NULL, 1, 'icon-menu', 0, 'cool/modules/base/views/plugin.vue', 1, 1); +INSERT INTO `base_sys_menu` VALUES (111, '2021-02-27 14:24:41.877000', '2021-02-27 14:24:41.877000', 110, '编辑', NULL, 'base:plugin:info:info,base:plugin:info:update', 2, NULL, 0, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (112, '2021-02-27 14:24:52.159000', '2021-02-27 14:24:52.159000', 110, '列表', NULL, 'base:plugin:info:list', 2, NULL, 0, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (113, '2021-02-27 14:25:02.066000', '2021-02-27 14:25:02.066000', 110, '删除', NULL, 'base:plugin:info:delete', 2, NULL, 0, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (114, '2021-02-27 16:36:59.322000', '2021-02-27 16:36:59.322000', 110, '保存配置', NULL, 'base:plugin:info:config', 2, NULL, 0, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (115, '2021-02-27 16:38:21.000000', '2021-02-27 18:18:22.000000', 110, '获取配置', NULL, 'base:plugin:info:getConfig', 2, NULL, 0, NULL, 1, 1); +INSERT INTO `base_sys_menu` VALUES (116, '2021-02-27 17:57:42.000000', '2021-02-27 18:19:35.000000', 110, '开启、关闭', NULL, 'base:plugin:info:enable', 2, NULL, 0, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (117, '2021-03-05 10:58:25.000000', '2021-03-05 10:58:25.000000', NULL, '任务管理', NULL, NULL, 0, 'icon-activity', 5, NULL, 1, 1); INSERT INTO `base_sys_menu` VALUES (118, '2021-03-05 10:59:42.000000', '2021-03-05 10:59:42.000000', 117, '任务列表', '/task', NULL, 1, 'icon-menu', 0, 'cool/modules/task/views/task.vue', 1, 1); INSERT INTO `base_sys_menu` VALUES (119, '2021-03-05 11:00:00.000000', '2021-03-05 11:00:00.000000', 118, '权限', NULL, 'task:info:page,task:info:list,task:info:info,task:info:add,task:info:delete,task:info:update,task:info:stop,task:info:start,task:info:once,task:info:log', 2, NULL, 0, NULL, 1, 1); diff --git a/src/modules/base/middleware/log.ts b/src/modules/base/middleware/log.ts index 295a3dc..2b3486e 100644 --- a/src/modules/base/middleware/log.ts +++ b/src/modules/base/middleware/log.ts @@ -16,7 +16,7 @@ export class BaseLogMiddleware implements IMiddleware { ); baseSysLogService.record( ctx, - ctx.url.split('?')[0], + ctx.url, ctx.req.method === 'GET' ? ctx.request.query : ctx.request.body, ctx.admin ? ctx.admin.userId : null ); diff --git a/src/modules/base/service/sys/conf.ts b/src/modules/base/service/sys/conf.ts index 0db684a..dbff81d 100644 --- a/src/modules/base/service/sys/conf.ts +++ b/src/modules/base/service/sys/conf.ts @@ -1,6 +1,6 @@ import { Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysConfEntity } from '../../entity/sys/conf'; @@ -17,7 +17,7 @@ export class BaseSysConfService extends BaseService { * @param key */ async getValue(key) { - const conf = await this.baseSysConfEntity.findOne({ cKey: key }); + const conf = await this.baseSysConfEntity.findOneBy({ cKey: key }); if (conf) { return conf.cValue; } diff --git a/src/modules/base/service/sys/data.ts b/src/modules/base/service/sys/data.ts new file mode 100644 index 0000000..42b03c9 --- /dev/null +++ b/src/modules/base/service/sys/data.ts @@ -0,0 +1,10 @@ +import { DataSource } from 'typeorm'; + +export class TempDataSource extends DataSource { + /** + * 重新构造元数据 + */ + async buildMetadatas() { + await super.buildMetadatas(); + } +} diff --git a/src/modules/base/service/sys/department.ts b/src/modules/base/service/sys/department.ts index 1177b98..512cbfb 100644 --- a/src/modules/base/service/sys/department.ts +++ b/src/modules/base/service/sys/department.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysDepartmentEntity } from '../../entity/sys/department'; import * as _ from 'lodash'; @@ -101,7 +101,7 @@ export class BaseSysDepartmentService extends BaseService { */ async delete(ids: number[]) { const { deleteUser } = this.ctx.request.body; - await this.baseSysDepartmentEntity.delete(ids); + await super.delete(ids); if (deleteUser) { await this.nativeQuery( 'delete from base_sys_user where departmentId in (?)', diff --git a/src/modules/base/service/sys/log.ts b/src/modules/base/service/sys/log.ts index bcf105b..5d92474 100644 --- a/src/modules/base/service/sys/log.ts +++ b/src/modules/base/service/sys/log.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import * as _ from 'lodash'; import { BaseSysLogEntity } from '../../entity/sys/log'; @@ -41,10 +41,8 @@ export class BaseSysLogService extends BaseService { for (const e of sysLog.ip.split(',')) ipAddrArr.push(await await this.utils.getIpAddr(context, e)); sysLog.ipAddr = ipAddrArr.join(','); - sysLog.action = url; - if (!_.isEmpty(params)) { - sysLog.params = JSON.stringify(params); - } + sysLog.action = url.split('?')[0]; + sysLog.params = params; await this.baseSysLogEntity.insert(sysLog); } diff --git a/src/modules/base/service/sys/login.ts b/src/modules/base/service/sys/login.ts index 32720ed..a1e3a2b 100644 --- a/src/modules/base/service/sys/login.ts +++ b/src/modules/base/service/sys/login.ts @@ -5,7 +5,7 @@ import * as svgCaptcha from 'svg-captcha'; import { v1 as uuid } from 'uuid'; import { BaseSysUserEntity } from '../../entity/sys/user'; import { Repository } from 'typeorm'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import * as md5 from 'md5'; import { BaseSysRoleService } from './role'; import * as _ from 'lodash'; @@ -51,7 +51,7 @@ export class BaseSysLoginService extends BaseService { // 校验验证码 const checkV = await this.captchaCheck(captchaId, verifyCode); if (checkV) { - const user = await this.baseSysUserEntity.findOne({ username }); + const user = await this.baseSysUserEntity.findOneBy({ username }); // 校验用户 if (user) { // 校验用户状态及密码 diff --git a/src/modules/base/service/sys/menu.ts b/src/modules/base/service/sys/menu.ts index 690040d..01f275c 100644 --- a/src/modules/base/service/sys/menu.ts +++ b/src/modules/base/service/sys/menu.ts @@ -1,11 +1,17 @@ -import { Inject, Provide } from '@midwayjs/decorator'; -import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { App, IMidwayApplication } from '@midwayjs/core'; +import { ALL, Config, Inject, Provide } from '@midwayjs/decorator'; +import { BaseService, CoolCommException } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysMenuEntity } from '../../entity/sys/menu'; import * as _ from 'lodash'; import { BaseSysPermsService } from './perms'; import { Context } from '@midwayjs/koa'; +import { TempDataSource } from './data'; +// eslint-disable-next-line node/no-unpublished-import +import * as ts from 'typescript'; +import * as fs from 'fs'; +import * as pathUtil from 'path'; /** * 菜单 @@ -21,6 +27,12 @@ export class BaseSysMenuService extends BaseService { @Inject() baseSysPermsService: BaseSysPermsService; + @Config(ALL) + config; + + @App() + app: IMidwayApplication; + /** * 获得所有菜单 */ @@ -131,7 +143,7 @@ export class BaseSysMenuService extends BaseService { */ private async delChildMenu(id) { await this.refreshPerms(id); - const delMenu = await this.baseSysMenuEntity.find({ parentId: id }); + const delMenu = await this.baseSysMenuEntity.findBy({ parentId: id }); if (_.isEmpty(delMenu)) { return; } @@ -162,4 +174,139 @@ export class BaseSysMenuService extends BaseService { } } } + + /** + * 解析实体和Controller + * @param entityString + * @param controller + * @param module + */ + async parse(entityString: string, controller: string, module: string) { + const tempDataSource = new TempDataSource({ + ...this.config.typeorm.dataSource.default, + entities: [], + }); + // 连接数据库 + await tempDataSource.initialize(); + const { newCode, className } = this.parseCode(entityString); + const code = ts.transpile( + `${newCode} + tempDataSource.options.entities.push(${className}) + `, + { + emitDecoratorMetadata: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2018, + removeComments: true, + } + ); + eval(code); + await tempDataSource.buildMetadatas(); + const columns = tempDataSource.getMetadata(className).columns; + await tempDataSource.destroy(); + const fileName = await this.fileName(controller); + return { + columns: columns.map(e => { + return { + propertyName: e.propertyName, + type: typeof e.type == 'string' ? e.type : e.type.name.toLowerCase(), + length: e.length, + comment: e.comment, + nullable: e.isNullable, + }; + }), + path: `/admin/${module}/${fileName}`, + }; + } + + /** + * 解析Entity类名 + * @param code + * @returns + */ + parseCode(code: string) { + try { + const oldClassName = code + .match('class(.*)extends')[1] + .replace(/\s*/g, ''); + const oldTableStart = code.indexOf('@Entity('); + const oldTableEnd = code.indexOf(')'); + + const oldTableName = code + .substring(oldTableStart + 9, oldTableEnd - 1) + .replace(/\s*/g, '') + // eslint-disable-next-line no-useless-escape + .replace(/\"/g, '') + // eslint-disable-next-line no-useless-escape + .replace(/\'/g, ''); + const className = `${oldClassName}TEMP`; + return { + newCode: code + .replace(oldClassName, className) + .replace(oldTableName, `func_${oldTableName}`), + className, + tableName: `func_${oldTableName}`, + }; + } catch (err) { + throw new CoolCommException('代码结构不正确,请检查'); + } + } + + /** + * 创建代码 + * @param body body + */ + async create(body) { + const { module, entity, controller } = body; + const basePath = this.app.getBaseDir(); + const fileName = await this.fileName(controller); + // 生成Entity + const entityPath = pathUtil.join( + basePath, + 'modules', + module, + 'entity', + `${fileName}.ts` + ); + this.createFile(entityPath, entity); + // // 生成Controller + const controllerPath = pathUtil.join( + basePath, + 'modules', + module, + 'controller', + 'admin', + `${fileName}.ts` + ); + this.createFile(controllerPath, controller); + } + + /** + * 找到文件名 + * @param controller + * @returns + */ + async fileName(controller: string) { + const regex = /import\s*{\s*\w+\s*}\s*from\s*'[^']*\/([\w-]+)';/; + const match = regex.exec(controller); + + if (match && match.length > 1) { + return match[1]; + } + + return null; + } + + /** + * 创建文件 + * @param filePath + * @param content + */ + async createFile(filePath: string, content: string) { + const folderPath = pathUtil.dirname(filePath); + if (!fs.existsSync(folderPath)) { + fs.mkdirSync(folderPath, { recursive: true }); + } + fs.writeFileSync(filePath, content); + } } diff --git a/src/modules/base/service/sys/param.ts b/src/modules/base/service/sys/param.ts index 9bfd735..8b88c28 100644 --- a/src/modules/base/service/sys/param.ts +++ b/src/modules/base/service/sys/param.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysParamEntity } from '../../entity/sys/param'; import { CacheManager } from '@midwayjs/cache'; @@ -23,7 +23,7 @@ export class BaseSysParamService extends BaseService { async dataByKey(key) { let result: any = await this.cacheManager.get(`param:${key}`); if (!result) { - result = await this.baseSysParamEntity.findOne({ keyName: key }); + result = await this.baseSysParamEntity.findOneBy({ keyName: key }); } if (result) { if (typeof result == 'string') { diff --git a/src/modules/base/service/sys/role.ts b/src/modules/base/service/sys/role.ts index f8f30c0..d42c99f 100644 --- a/src/modules/base/service/sys/role.ts +++ b/src/modules/base/service/sys/role.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysRoleEntity } from '../../entity/sys/role'; import { BaseSysUserRoleEntity } from '../../entity/sys/user_role'; @@ -38,7 +38,7 @@ export class BaseSysRoleService extends BaseService { * @param userId */ async getByUser(userId: number): Promise { - const userRole = await this.baseSysUserRoleEntity.find({ userId }); + const userRole = await this.baseSysUserRoleEntity.findBy({ userId }); if (!_.isEmpty(userRole)) { return userRole.map(e => { return e.roleId; @@ -75,7 +75,7 @@ export class BaseSysRoleService extends BaseService { await this.baseSysRoleDepartmentEntity.save({ roleId, departmentId }); } // 刷新权限 - const userRoles = await this.baseSysUserRoleEntity.find({ roleId }); + const userRoles = await this.baseSysUserRoleEntity.findBy({ roleId }); for (const userRole of userRoles) { await this.baseSysPermsService.refreshPerms(userRole.userId); } @@ -86,15 +86,15 @@ export class BaseSysRoleService extends BaseService { * @param id */ async info(id) { - const info = await this.baseSysRoleEntity.findOne({ id }); + const info = await this.baseSysRoleEntity.findOneBy({ id }); if (info) { - const menus = await this.baseSysRoleMenuEntity.find( + const menus = await this.baseSysRoleMenuEntity.findBy( id !== 1 ? { roleId: id } : {} ); const menuIdList = menus.map(e => { return parseInt(e.menuId + ''); }); - const departments = await this.baseSysRoleDepartmentEntity.find( + const departments = await this.baseSysRoleDepartmentEntity.findBy( id !== 1 ? { roleId: id } : {} ); const departmentIdList = departments.map(e => { diff --git a/src/modules/base/service/sys/user.ts b/src/modules/base/service/sys/user.ts index 126d086..3fd4e14 100644 --- a/src/modules/base/service/sys/user.ts +++ b/src/modules/base/service/sys/user.ts @@ -1,6 +1,6 @@ import { Inject, Provide } from '@midwayjs/decorator'; import { BaseService, CoolCommException } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { BaseSysUserEntity } from '../../entity/sys/user'; import { BaseSysPermsService } from './perms'; @@ -92,7 +92,7 @@ export class BaseSysUserService extends BaseService { * 获得个人信息 */ async person() { - const info = await this.baseSysUserEntity.findOne({ + const info = await this.baseSysUserEntity.findOneBy({ id: this.ctx.admin?.userId, }); delete info?.password; @@ -124,7 +124,7 @@ export class BaseSysUserService extends BaseService { * @param param */ async add(param) { - const exists = await this.baseSysUserEntity.findOne({ + const exists = await this.baseSysUserEntity.findOneBy({ username: param.username, }); if (!_.isEmpty(exists)) { @@ -141,12 +141,12 @@ export class BaseSysUserService extends BaseService { * @param id */ public async info(id) { - const info = await this.baseSysUserEntity.findOne({ id }); + const info = await this.baseSysUserEntity.findOneBy({ id }); const userRoles = await this.nativeQuery( 'select a.roleId from base_sys_user_role a where a.userId = ?', [id] ); - const department = await this.baseSysDepartmentEntity.findOne({ + const department = await this.baseSysDepartmentEntity.findOneBy({ id: info.departmentId, }); if (info) { @@ -172,7 +172,7 @@ export class BaseSysUserService extends BaseService { param.id = this.ctx.admin.userId; if (!_.isEmpty(param.password)) { param.password = md5(param.password); - const userInfo = await this.baseSysUserEntity.findOne({ id: param.id }); + const userInfo = await this.baseSysUserEntity.findOneBy({ id: param.id }); if (!userInfo) { throw new CoolCommException('用户不存在'); } @@ -197,7 +197,7 @@ export class BaseSysUserService extends BaseService { } if (!_.isEmpty(param.password)) { param.password = md5(param.password); - const userInfo = await this.baseSysUserEntity.findOne({ id: param.id }); + const userInfo = await this.baseSysUserEntity.findOneBy({ id: param.id }); if (!userInfo) { throw new CoolCommException('用户不存在'); } diff --git a/src/modules/cloud/config.ts b/src/modules/cloud/config.ts new file mode 100644 index 0000000..a9aa6cc --- /dev/null +++ b/src/modules/cloud/config.ts @@ -0,0 +1,19 @@ +import { ModuleConfig } from '@cool-midway/core'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '云服务', + // 模块描述 + description: '云函数、云数据库、云存储等', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + } as ModuleConfig; +}; diff --git a/src/modules/cloud/controller/admin/db.ts b/src/modules/cloud/controller/admin/db.ts new file mode 100644 index 0000000..e8ea723 --- /dev/null +++ b/src/modules/cloud/controller/admin/db.ts @@ -0,0 +1,34 @@ +import { CloudDBService } from './../../service/db'; +import { CloudDBEntity } from './../../entity/db'; +import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * 云数据库 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: CloudDBEntity, + service: CloudDBService, + pageQueryOp: { + fieldEq: ['status'], + keyWordLikeFields: ['name'], + }, +}) +export class CloudDBController extends BaseController { + @Inject() + cloudDBService: CloudDBService; + + @Post('/initEntity', { summary: '初始化Entity' }) + async initEntity() { + await this.cloudDBService.initEntity(); + return this.ok(); + } + + @Post('/data', { summary: '数据操作' }) + async data(@Body() body) { + const { id, method, params } = body; + return this.ok(await this.cloudDBService.data(id, method, params)); + } +} diff --git a/src/modules/cloud/controller/admin/func/info.ts b/src/modules/cloud/controller/admin/func/info.ts new file mode 100644 index 0000000..2a905cf --- /dev/null +++ b/src/modules/cloud/controller/admin/func/info.ts @@ -0,0 +1,31 @@ +import { CloudFuncService } from './../../../service/func'; +import { CloudFuncInfoEntity } from '../../../entity/func/info'; +import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { CloudReq } from '@cool-midway/cloud'; + +/** + * 云函数 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: CloudFuncInfoEntity, + pageQueryOp: { + keyWordLikeFields: ['name'], + fieldEq: ['status'], + }, +}) +export class AdminCloudFuncInfoController extends BaseController { + @Inject() + cloudFuncService: CloudFuncService; + + @Post('/invoke', { summary: '调用云函数' }) + async invoke( + @Body() req: CloudReq, + @Body('id') id: number, + @Body('content') content: string + ) { + return this.ok(await this.cloudFuncService.invoke(req, id, content)); + } +} diff --git a/src/modules/cloud/controller/admin/func/log.ts b/src/modules/cloud/controller/admin/func/log.ts new file mode 100644 index 0000000..9a9733c --- /dev/null +++ b/src/modules/cloud/controller/admin/func/log.ts @@ -0,0 +1,16 @@ +import { CloudFuncLogEntity } from './../../../entity/func/log'; +import { Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * 日志 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: CloudFuncLogEntity, + pageQueryOp: { + fieldEq: ['infoId'], + }, +}) +export class AdminCloudFuncLogController extends BaseController {} diff --git a/src/modules/cloud/controller/app/func.ts b/src/modules/cloud/controller/app/func.ts new file mode 100644 index 0000000..11ffca1 --- /dev/null +++ b/src/modules/cloud/controller/app/func.ts @@ -0,0 +1,12 @@ +import { Body, Post, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * 云函数 + */ +@Provide() +@CoolController() +export class AppCloudFuncController extends BaseController { + @Post('/invoke', { summary: '调用云函数' }) + async invoke(@Body() body) {} +} diff --git a/src/modules/cloud/entity/db.ts b/src/modules/cloud/entity/db.ts new file mode 100644 index 0000000..58cd4b7 --- /dev/null +++ b/src/modules/cloud/entity/db.ts @@ -0,0 +1,28 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 云数据库 + */ +@Entity('cloud_db') +export class CloudDBEntity extends BaseEntity { + @Column({ comment: '名称' }) + name: string; + + @Column({ comment: '说明', nullable: true }) + readme: string; + + @Column({ comment: '内容', type: 'text' }) + content: string; + + @Index({ unique: true }) + @Column({ comment: '类名', nullable: true }) + className: string; + + @Index({ unique: true }) + @Column({ comment: '表名', nullable: true }) + tableName: string; + + @Column({ comment: '状态 0-禁用 1-启用', default: 1 }) + status: number; +} diff --git a/src/modules/cloud/entity/func/info.ts b/src/modules/cloud/entity/func/info.ts new file mode 100644 index 0000000..48584c8 --- /dev/null +++ b/src/modules/cloud/entity/func/info.ts @@ -0,0 +1,20 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity } from 'typeorm'; + +/** + * 云函数 + */ +@Entity('cloud_func_info') +export class CloudFuncInfoEntity extends BaseEntity { + @Column({ comment: '名称' }) + name: string; + + @Column({ comment: '说明', nullable: true }) + readme: string; + + @Column({ comment: '内容', type: 'text' }) + content: string; + + @Column({ comment: '状态 0-禁用 1-启用', default: 1 }) + status: number; +} diff --git a/src/modules/cloud/entity/func/log.ts b/src/modules/cloud/entity/func/log.ts new file mode 100644 index 0000000..eddb736 --- /dev/null +++ b/src/modules/cloud/entity/func/log.ts @@ -0,0 +1,28 @@ +import { CloudReq } from '@cool-midway/cloud'; +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 云函数日志 + */ +@Entity('cloud_func_log') +export class CloudFuncLogEntity extends BaseEntity { + @Index() + @Column({ comment: '云函数ID' }) + infoId: number; + + @Column({ comment: '请求', type: 'json', nullable: true }) + request: CloudReq; + + @Column({ comment: '结果', type: 'json', nullable: true }) + result: string; + + @Column({ comment: '类型 0-失败 1-成功', default: 1, type: 'tinyint' }) + type: number; + + @Column({ comment: '异常信息', nullable: true, type: 'text' }) + error: string; + + @Column({ comment: '耗时(毫秒)', default: 0 }) + time: number; +} diff --git a/src/modules/cloud/event/app.ts b/src/modules/cloud/event/app.ts new file mode 100644 index 0000000..614f1cc --- /dev/null +++ b/src/modules/cloud/event/app.ts @@ -0,0 +1,21 @@ +import { IMidwayApplication } from '@midwayjs/core'; +import { CoolEvent, Event } from '@cool-midway/core'; +import { App } from '@midwayjs/decorator'; +import { CloudDBService } from '../service/db'; + +/** + * 应用事件 + */ +@CoolEvent() +export class AppEvent { + @App() + app: IMidwayApplication; + + @Event('onServerReady') + async onServerReady() { + const cloudDBService = await this.app + .getApplicationContext() + .getAsync(CloudDBService); + cloudDBService.initEntity(); + } +} diff --git a/src/modules/cloud/service/db.ts b/src/modules/cloud/service/db.ts new file mode 100644 index 0000000..4dcbe4e --- /dev/null +++ b/src/modules/cloud/service/db.ts @@ -0,0 +1,151 @@ +import { Config, Singleton } from '@midwayjs/core'; +import { CloudDBEntity } from './../entity/db'; +import { Inject, Provide } from '@midwayjs/decorator'; +import { BaseService, CoolCommException, CoolConfig } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { CoolCloudDb } from '@cool-midway/cloud'; +import * as _ from 'lodash'; + +/** + * 云数据库 + */ +@Provide() +@Singleton() +export class CloudDBService extends BaseService { + @InjectEntityModel(CloudDBEntity) + cloudDBEntity: Repository; + + @Inject() + coolCloudDb: CoolCloudDb; + + @Config('cool') + coolConfig: CoolConfig; + + /** + * 数据 + * @param id + * @param method + * @param params + * @returns + */ + async data(id, method, params: any = {}) { + const db = await this.cloudDBEntity.findOneBy({ id }); + const repository = await this.getRepository(db?.className); + if (method == 'add' || method == 'update') { + await repository.save(params); + return { + id: params.id, + }; + } + if (method == 'delete') { + await repository.delete(params.ids); + } + if (method == 'clear') { + await repository.clear(); + } + if (method == 'page') { + const { page = 1, size = this.coolConfig.crud.pageSize } = params; + const find = repository + .createQueryBuilder() + .offset((page - 1) * size) + .limit(size) + .orderBy('createTime', 'DESC'); + return { + list: await find.getMany(), + pagination: { + page: parseInt(page), + size: parseInt(size), + total: await find.getCount(), + }, + }; + } + } + + /** + * 获得数据操作实例 + * @param className + */ + async getRepository(className: string): Promise> { + const info = await this.cloudDBEntity.findOneBy({ + className, + }); + if (!info) { + throw new CoolCommException('云数据表不存在'); + } + return await this.coolCloudDb.getRepository(info.className); + } + + /** + * 新增 + * @param param + * @returns + */ + async addOrUpdate(param) { + const { tableName, className } = this.coolCloudDb.parseCode(param.content); + // 更新 + if (param.id) { + const old = await this.cloudDBEntity.findOneBy({ id: param.id }); + if (tableName != old.tableName) { + const check = await this.cloudDBEntity.findOneBy({ tableName }); + if (check) { + throw new CoolCommException('已存在相同的表名'); + } + } + if (className != old.className) { + const checkClass = await this.cloudDBEntity.findOneBy({ className }); + if (checkClass) { + throw new CoolCommException('已存在相同的类名'); + } + } + } else { + const check = await this.cloudDBEntity.findOneBy({ tableName }); + if (check) { + throw new CoolCommException('已存在相同的表名'); + } + const checkClass = await this.cloudDBEntity.findOneBy({ className }); + if (checkClass) { + throw new CoolCommException('已存在相同的类名'); + } + } + await super.addOrUpdate({ + ...param, + tableName, + className: className.replace('CLOUD', ''), + }); + } + + /** + * 初始化 + */ + async initEntity() { + const tables = await this.cloudDBEntity.findBy({ status: 1 }); + const tableNames = []; + for (const table of tables) { + const parseData = this.coolCloudDb.parseCode(table.content); + tableNames.push(parseData.tableName); + await this.coolCloudDb.createTable(table.content, true); + } + // 所有云函数表 + const allTables = ( + await this.coolCloudDb.coolDataSource.query( + "SELECT table_name from information_schema.columns where table_name like 'func_%' group by table_name" + ) + ).map(e => { + return e.TABLE_NAME; + }); + // 需要删除的云函数表 + const deleteTables = allTables.filter(e => { + return !tableNames.includes(e); + }); + if (!_.isEmpty(deleteTables)) { + await this.coolCloudDb.coolDataSource.query( + `drop table ${deleteTables.join(',')}` + ); + } + } + + async modifyAfter() { + await this.initEntity(); + } +} diff --git a/src/modules/cloud/service/func.ts b/src/modules/cloud/service/func.ts new file mode 100644 index 0000000..68a6cbf --- /dev/null +++ b/src/modules/cloud/service/func.ts @@ -0,0 +1,130 @@ +import { CloudFuncLogEntity } from './../entity/func/log'; +import { Config, IMidwayApplication } from '@midwayjs/core'; +import { CloudFuncInfoEntity } from './../entity/func/info'; +import { App, Provide, Inject } from '@midwayjs/decorator'; +import { BaseService, CoolConfig, CoolCommException } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import * as moment from 'moment'; + +// eslint-disable-next-line node/no-unpublished-import +import * as ts from 'typescript'; +import { Context } from '@midwayjs/koa'; +import { + CloudCrud, + CloudReq, + CoolCloudDb, + CoolCloudFunc, +} from '@cool-midway/cloud'; + +/** + * 云函数 + */ +@Provide() +export class CloudFuncService extends BaseService { + @InjectEntityModel(CloudFuncInfoEntity) + cloudFuncInfoEntity: Repository; + + @InjectEntityModel(CloudFuncLogEntity) + cloudFuncLogEntity: Repository; + + @App() + app: IMidwayApplication; + + @Inject() + ctx: Context; + + @Inject() + coolCloudDb: CoolCloudDb; + + @Inject() + coolCloudFunc: CoolCloudFunc; + + @Config('cool') + coolConfig: CoolConfig; + + /** + * 调用云函数 + * @param req + * @param id + * @param content 内容 调试的时候传过来 + * @returns + */ + async invoke(req: CloudReq, id: number, content?: string) { + const start = moment().valueOf(); + let funcInfo: CloudFuncInfoEntity; + if (id) { + funcInfo = await this.cloudFuncInfoEntity + .createQueryBuilder() + .cache(true) + .where({ id, status: 1 }) + .getOne(); + req.name = funcInfo?.name; + } else { + funcInfo = await this.cloudFuncInfoEntity + .createQueryBuilder() + .cache(true) + .where({ name: req.name, status: 1 }) + .getOne(); + } + if (!funcInfo) { + throw new CoolCommException('云函数不存在或被禁用'); + } + if (!req.method) { + throw new CoolCommException('调用方法不能为空'); + } + let result; + let func: CloudCrud; + const code = content ? content : funcInfo.content; + const className = this.coolCloudFunc.getClassName(code); + const newCode = ts.transpile( + `${code} + func = new ${className}(); + `, + { + emitDecoratorMetadata: true, + module: ts.ModuleKind.CommonJS, + target: ts.ScriptTarget.ES2018, + removeComments: true, + } + ); + const log = new CloudFuncLogEntity(); + try { + eval(newCode); + + func.ctx = this.ctx; + func.app = this.app; + func.coolCloudDb = this.coolCloudDb; + func.coolConfig = this.coolConfig; + await func.init(req); + const apis: string[] = func.curdOption.api || []; + // 判断是否可以执行6个通用方法 + if ( + ['add', 'delete', 'update', 'info', 'list', 'page'].includes( + req.method + ) && + !apis.includes(req.method) + ) { + throw new CoolCommException( + `${req.method} 方法未在curdOption.api 中配置` + ); + } + // result = func.add({ name: 'aa', age: 22, test2: 1 }); + result = await func[req.method](req.params); + } catch (error) { + log.error = error.message; + } + log.infoId = funcInfo.id; + log.request = req; + log.result = result; + log.type = log.error ? 0 : 1; + const end = moment().valueOf(); + log.time = end - start; + this.cloudFuncLogEntity.insert(log); + if (id) { + return log; + } else { + return result; + } + } +} diff --git a/src/modules/demo/config.ts b/src/modules/demo/config.ts index a12bf6b..a0f978c 100644 --- a/src/modules/demo/config.ts +++ b/src/modules/demo/config.ts @@ -1,5 +1,4 @@ import { ModuleConfig } from '@cool-midway/core'; -import { DemoMiddleware } from './middleware/demo'; /** * 模块配置 @@ -7,16 +6,14 @@ import { DemoMiddleware } from './middleware/demo'; export default () => { return { // 模块名称 - name: 'xxx', + name: 'demo模块', // 模块描述 - description: 'xxx', + description: '演示用', // 中间件,只对本模块有效 - middlewares: [DemoMiddleware], + middlewares: [], // 中间件,全局有效 globalMiddlewares: [], // 模块加载顺序,默认为0,值越大越优先加载 order: 0, - // 其他配置 - a: 1, } as ModuleConfig; }; diff --git a/src/modules/demo/controller/admin/goods.ts b/src/modules/demo/controller/admin/goods.ts index 7a0b626..2bf6fdb 100644 --- a/src/modules/demo/controller/admin/goods.ts +++ b/src/modules/demo/controller/admin/goods.ts @@ -1,11 +1,11 @@ +import { CoolController, BaseController } from '@cool-midway/core'; import { DemoGoodsEntity } from '../../entity/goods'; -import { BaseController, CoolController } from '@cool-midway/core'; /** - * 测试 + * 商品模块-商品信息 */ @CoolController({ - api: ['add', 'delete', 'update', 'info', 'page', 'list'], + api: ['add', 'delete', 'update', 'info', 'list', 'page'], entity: DemoGoodsEntity, }) -export class CoolGoodsController extends BaseController {} +export class AdminDemoGoodsController extends BaseController {} diff --git a/src/modules/demo/controller/app/cache.ts b/src/modules/demo/controller/app/cache.ts index 5f68b43..ee6313f 100644 --- a/src/modules/demo/controller/app/cache.ts +++ b/src/modules/demo/controller/app/cache.ts @@ -24,7 +24,7 @@ export class AppDemoCacheController extends BaseController { await this.cacheManager.set('a', 1); // 缓存10秒 await this.cacheManager.set('a', 1, { - ttl: 10, + ttl: 30, }); return this.ok(await this.cacheManager.get('a')); } diff --git a/src/modules/demo/controller/app/config.ts b/src/modules/demo/controller/app/config.ts deleted file mode 100644 index 61644e2..0000000 --- a/src/modules/demo/controller/app/config.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { Config, Get, Provide } from '@midwayjs/decorator'; -import { CoolController, BaseController } from '@cool-midway/core'; - -/** - * 配置 - */ -@Provide() -@CoolController() -export class DemoConfigController extends BaseController { - //获得模块配置,格式: module.模块名,模块文件夹名称,如demo - @Config('module.demo') - demoConfig; - - @Get('/get') - async get() { - return this.ok(this.demoConfig); - } -} diff --git a/src/modules/demo/controller/app/es.ts b/src/modules/demo/controller/app/es.ts deleted file mode 100644 index f171bca..0000000 --- a/src/modules/demo/controller/app/es.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Inject, Post, Provide } from '@midwayjs/decorator'; -import { CoolController, BaseController } from '@cool-midway/core'; -import { TestEsIndex } from '../../es/test'; -import { CoolElasticSearch } from '@cool-midway/es'; - -/** - * elasticsearch - */ -@Provide() -@CoolController() -export class AppDemoEsController extends BaseController { - @Inject() - testEsIndex: TestEsIndex; - - @Inject() - es: CoolElasticSearch; - - @Post('/test') - async test() { - // es 客户端实例 - this.es.client; - // 新增与修改 - await this.testEsIndex.upsert({ - name: '你好啊你是谁', - age: 18, - }); - return this.ok(await this.testEsIndex.find()); - } -} diff --git a/src/modules/demo/controller/app/event.ts b/src/modules/demo/controller/app/event.ts index 34d6add..6e053c5 100644 --- a/src/modules/demo/controller/app/event.ts +++ b/src/modules/demo/controller/app/event.ts @@ -1,4 +1,4 @@ -import { Inject, Post, Provide } from '@midwayjs/decorator'; +import { Inject, Post } from '@midwayjs/decorator'; import { CoolController, BaseController, @@ -8,7 +8,6 @@ import { /** * 事件 */ -@Provide() @CoolController() export class AppDemoEventController extends BaseController { @Inject() diff --git a/src/modules/demo/controller/app/file.ts b/src/modules/demo/controller/app/file.ts deleted file mode 100644 index 8ce2a83..0000000 --- a/src/modules/demo/controller/app/file.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { Get, Inject, Post, Provide } from '@midwayjs/decorator'; -import { CoolController, BaseController } from '@cool-midway/core'; -import { Context } from 'koa'; -import { CoolFile } from '@cool-midway/file'; - -/** - * 文件上传 - */ -@Provide() -@CoolController() -export class AppDemoFileController extends BaseController { - @Inject() - ctx: Context; - - @Inject() - file: CoolFile; - - @Post('/upload', { summary: '文件上传' }) - async uplod() { - return this.ok(await this.file.upload(this.ctx)); - } - - @Get('/uploadMode', { summary: '获得上传模式' }) - async uploadMode() { - return this.ok(await this.file.getMode()); - } - - @Post('/downAndUpload', { summary: '下载并上传' }) - async downAndUpload() { - return this.ok( - await this.file.downAndUpload('https://cool-js.com/admin/show.png') - ); - } -} diff --git a/src/modules/demo/controller/app/goods.ts b/src/modules/demo/controller/app/goods.ts index 7935ca0..790f998 100644 --- a/src/modules/demo/controller/app/goods.ts +++ b/src/modules/demo/controller/app/goods.ts @@ -1,13 +1,33 @@ -import { DemoGoodsEntity } from '../../entity/goods'; -import { BaseController, CoolController } from '@cool-midway/core'; import { DemoGoodsService } from '../../service/goods'; +import { DemoGoodsEntity } from '../../entity/goods'; +import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; /** * 测试 */ +@Provide() @CoolController({ - api: ['add', 'delete', 'update', 'info', 'page', 'list'], + api: ['add', 'delete', 'update', 'info', 'list', 'page'], entity: DemoGoodsEntity, service: DemoGoodsService, }) -export class CoolGoodsController extends BaseController {} +export class AppDemoGoodsController extends BaseController { + @InjectEntityModel(DemoGoodsEntity) + demoGoodsEntity: Repository; + + @Inject() + demoGoodsService: DemoGoodsService; + + @Post('/sqlPage', { summary: 'sql分页查询' }) + async sqlPage(@Body() query) { + return this.ok(await this.demoGoodsService.sqlPage(query)); + } + + @Post('/entityPage', { summary: 'entity分页查询' }) + async entityPage(@Body() query) { + return this.ok(await this.demoGoodsService.entityPage(query)); + } +} diff --git a/src/modules/demo/controller/app/pay.ts b/src/modules/demo/controller/app/pay.ts index f0cc784..3889dcc 100644 --- a/src/modules/demo/controller/app/pay.ts +++ b/src/modules/demo/controller/app/pay.ts @@ -1,16 +1,20 @@ -import { ALL, App, Body, Inject, Post, Provide } from '@midwayjs/decorator'; -import { CoolController, BaseController } from '@cool-midway/core'; -import { CoolWxPay, CoolAliPay } from '@cool-midway/pay'; -import { parseString } from 'xml2js'; -import { Context } from '@midwayjs/koa'; -import { IMidwayApplication } from '@midwayjs/core'; +import { Body, Config, Inject, Post, Provide } from '@midwayjs/decorator'; +import { + CoolController, + BaseController, + CoolWxPayConfig, + CoolAliPayConfig, +} from '@cool-midway/core'; +import { CoolAliPay, CoolWxPay } from '@cool-midway/pay'; +import AlipayFormData from 'alipay-sdk/lib/form'; +import { sign } from 'alipay-sdk/lib/util'; /** - * 支付示例 + * 微信支付 */ @Provide() @CoolController() -export class DemoPayController extends BaseController { +export class AppDemoPayController extends BaseController { // 微信支付 @Inject() wxPay: CoolWxPay; @@ -20,10 +24,13 @@ export class DemoPayController extends BaseController { aliPay: CoolAliPay; @Inject() - ctx: Context; + ctx; - @App() - app: IMidwayApplication; + @Config('cool.pay.wx') + wxPayConfig: CoolWxPayConfig; + + @Config('cool.pay.ali') + aliPayConfig: CoolAliPayConfig; /** * 微信扫码支付 @@ -31,78 +38,92 @@ export class DemoPayController extends BaseController { @Post('/wx') async wx() { const orderNum = await this.wxPay.createOrderNum(); - const data = await this.wxPay.getInstance().unifiedOrder({ + const params = { + description: '测试', out_trade_no: orderNum, - body: '测试微信支付', - total_fee: 1, - trade_type: 'NATIVE', - product_id: 'test001', - }); - return this.ok(data); + notify_url: this.wxPayConfig.notify_url, + amount: { + total: 1, + }, + scene_info: { + payer_client_ip: 'ip', + }, + }; + const result = await this.wxPay.getInstance().transactions_native(params); + return this.ok(result); } /** - * 微信支付通知回调 + * 微信支付回调通知 */ @Post('/wxNotify') - async wxNotify() { - let data = ''; - this.ctx.req.setEncoding('utf8'); - this.ctx.req.on('data', chunk => { - data += chunk; - }); - const results = await new Promise((resolve, reject) => { - this.ctx.req.on('end', () => { - parseString(data, { explicitArray: false }, async (err, json) => { - if (err) { - return reject('success'); - } - const checkSign = await this.wxPay.signVerify(json.xml); - if (checkSign && json.xml.result_code === 'SUCCESS') { - // 处理业务逻辑 - console.log('微信支付成功', json.xml); - return resolve(true); - } - return resolve(false); - }); - }); - }); - if (results) { - this.ctx.body = - 'OKSUCCESS'; + async wxNotify(@Body() body) { + const check = await this.wxPay.signVerify(this.ctx); + // 验签通过,处理业务逻辑 + if (check) { } } /** - * 支付宝app支付 + * 支付宝PC网站支付 * @returns */ - @Post('/alipay') - async alipay() { + @Post('/aliPc') + async aliPc() { const orderNum = await this.aliPay.createOrderNum(); - // app支付 - const params = await this.aliPay.getInstance().appPay({ - subject: '测试商品', - body: '测试商品描述', - outTradeId: orderNum, - timeout: '10m', - amount: '10.00', - goodsType: '0', + const formData = new AlipayFormData(); + // 调用 setMethod 并传入 get,会返回可以跳转到支付页面的 url + formData.setMethod('get'); + formData.addField('notifyUrl', this.aliPayConfig.notifyUrl); + formData.addField('bizContent', { + outTradeNo: orderNum, + productCode: 'FAST_INSTANT_TRADE_PAY', + totalAmount: '0.01', + subject: '商品', + body: '商品详情', }); - return this.ok(params); + // 返回支付链接 + const result = await this.aliPay + .getInstance() + .exec('alipay.trade.page.pay', {}, { formData }); + return this.ok(result); } /** - * 支付宝支付回调 + * 支付宝APP支付 + * @returns + */ + @Post('/aliApp') + async aliApp() { + const orderNum = await this.aliPay.createOrderNum(); + + // 返回支付链接 + const data = sign( + 'alipay.trade.app.pay', + { + notifyUrl: this.aliPayConfig.notifyUrl, + bizContent: { + subject: '商品标题', + totalAmount: '0.01', + outTradeNo: orderNum, + productCode: 'QUICK_MSECURITY_PAY', + }, + }, + this.aliPay.getInstance().config + ); + const payInfo = new URLSearchParams(data).toString(); + return this.ok(payInfo); + } + + /** + * 微信支付回调通知 */ @Post('/aliNotify') - async aliNotify(@Body(ALL) body: any) { - const { trade_status, out_trade_no } = body; + async aliNotify(@Body() body) { const check = await this.aliPay.signVerify(body); - if (check && trade_status === 'TRADE_SUCCESS') { - // 处理逻辑 - console.log('支付宝支付成功', out_trade_no); + // 验签通过,处理业务逻辑 + if (check) { } - this.ctx.body = 'success'; + return 'success'; } } diff --git a/src/modules/demo/controller/app/rpc.ts b/src/modules/demo/controller/app/rpc.ts index ef01f13..4a716d7 100644 --- a/src/modules/demo/controller/app/rpc.ts +++ b/src/modules/demo/controller/app/rpc.ts @@ -1,34 +1,28 @@ -import { Inject, Post, Provide } from '@midwayjs/decorator'; +import { Inject, Provide, Get } from '@midwayjs/decorator'; import { CoolController, BaseController } from '@cool-midway/core'; -import { CoolRpc } from '@cool-midway/rpc'; import { DemoRpcService } from '../../service/rpc'; /** - * 微服务 + * 远程RPC调用 */ @Provide() @CoolController() -export class DemoRpcController extends BaseController { - @Inject() - rpc: CoolRpc; - +export class AppDemoRpcController extends BaseController { @Inject() demoRpcService: DemoRpcService; - @Post('/call', { summary: '远程调用' }) + @Get('/call', { summary: '远程调用' }) async call() { - return this.ok( - await this.rpc.call('goods', 'demoGoodsService', 'test', { a: 1 }) - ); + return this.ok(await this.demoRpcService.call()); } - @Post('/event', { summary: '集群事件' }) + @Get('/event', { summary: '集群事件' }) async event() { - this.rpc.broadcastEvent('test', { a: 1 }); + await this.demoRpcService.event(); return this.ok(); } - @Post('/transaction', { summary: '分布式事务' }) + @Get('/transaction', { summary: '分布式事务' }) async transaction() { await this.demoRpcService.transaction({ a: 1 }); return this.ok(); diff --git a/src/modules/demo/controller/app/swagger.ts b/src/modules/demo/controller/app/swagger.ts deleted file mode 100644 index 2d967c0..0000000 --- a/src/modules/demo/controller/app/swagger.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { Param, Post, Provide } from '@midwayjs/decorator'; -import { CoolController, BaseController } from '@cool-midway/core'; - -/** - * swagger 文档 - */ -@Provide() -@CoolController(null, { - tagName: 'swagger demo', -}) -export class AppSwaggerController extends BaseController { - @Post('/create', { summary: '创建' }) - async create(@Param('id') id: number) { - return this.ok(id); - } -} diff --git a/src/modules/demo/controller/app/tag.ts b/src/modules/demo/controller/app/tag.ts deleted file mode 100644 index 841b91b..0000000 --- a/src/modules/demo/controller/app/tag.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { Get, Inject, Provide } from '@midwayjs/decorator'; -import { - CoolController, - BaseController, - CoolUrlTag, - TagTypes, - CoolUrlTagData, -} from '@cool-midway/core'; - -/** - * 测试给URL打标签 - */ -@Provide() -@CoolController({ - api: [], - entity: '', - pageQueryOp: () => {}, -}) -@CoolUrlTag({ - key: TagTypes.IGNORE_TOKEN, - value: ['add'], -}) -export class DemoAppTagController extends BaseController { - @Inject() - tag: CoolUrlTagData; - - /** - * 获得标签数据, 如可以标记忽略token的url,然后在中间件判断 - * @returns - */ - @Get('/data') - async data() { - return this.ok(this.tag.byKey(TagTypes.IGNORE_TOKEN)); - } -} diff --git a/src/modules/demo/controller/app/transaction.ts b/src/modules/demo/controller/app/transaction.ts new file mode 100644 index 0000000..5b37a6d --- /dev/null +++ b/src/modules/demo/controller/app/transaction.ts @@ -0,0 +1,15 @@ +import { DemoGoodsEntity } from './../../entity/goods'; +import { Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { DemoTransactionService } from '../../service/transaction'; + +/** + * 事务 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: DemoGoodsEntity, + service: DemoTransactionService, +}) +export class AppDemoTransactionController extends BaseController {} diff --git a/src/modules/demo/entity/goods-test.ts b/src/modules/demo/entity/goods-test.ts deleted file mode 100644 index e679294..0000000 --- a/src/modules/demo/entity/goods-test.ts +++ /dev/null @@ -1,23 +0,0 @@ -// import { EntityModel } from '@midwayjs/orm'; -// import { BaseEntity } from '@cool-midway/core'; -// import { Column } from 'typeorm'; - -// /** -// * 商品(多数据库连接) -// */ -// @EntityModel('demo_goods_1', { -// connectionName: 'test', -// }) -// export class DemoGoodsTestEntity extends BaseEntity { -// @Column({ comment: '标题' }) -// title: string; - -// @Column({ comment: '图片' }) -// pic: string; - -// @Column({ comment: '价格', type: 'decimal', precision: 5, scale: 2 }) -// price: number; - -// @Column({ comment: '分类', type: 'tinyint', default: 0 }) -// type: number; -// } diff --git a/src/modules/demo/entity/goods.ts b/src/modules/demo/entity/goods.ts index 30632ee..4667633 100644 --- a/src/modules/demo/entity/goods.ts +++ b/src/modules/demo/entity/goods.ts @@ -1,21 +1,32 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity, Index } from 'typeorm'; /** - * 商品 + * 商品模块-商品信息 */ -@EntityModel('demo_goods') +@Entity('demo_goods') export class DemoGoodsEntity extends BaseEntity { - @Column({ comment: '标题' }) + @Index() + @Column({ comment: '标题', length: 50 }) title: string; - @Column({ comment: '图片' }) - pic: string; - - @Column({ comment: '价格', type: 'decimal', precision: 5, scale: 2 }) + @Column({ + comment: '价格', + type: 'decimal', + precision: 5, + scale: 2, + }) price: number; - @Column({ comment: '分类 0-衣服 1-鞋子 2-裤子', type: 'tinyint', default: 0 }) - type: number; + @Column({ comment: '描述', nullable: true }) + description: string; + + @Column({ comment: '主图', nullable: true }) + mainImage: string; + + @Column({ comment: '示例图', nullable: true, type: 'json' }) + exampleImages: string[]; + + @Column({ comment: '库存', default: 0 }) + stock: number; } diff --git a/src/modules/demo/es/test.ts b/src/modules/demo/es/test.ts deleted file mode 100644 index 1fcc014..0000000 --- a/src/modules/demo/es/test.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { CoolEsIndex, ICoolEs, BaseEsIndex } from '@cool-midway/es'; - -/** - * 测试索引 - */ -@CoolEsIndex({ name: 'test', replicas: 0 }) -export class TestEsIndex extends BaseEsIndex implements ICoolEs { - indexInfo() { - return { - // 需要安装ik分词器 https://github.com/medcl/elasticsearch-analysis-ik - name: { - type: 'text', - analyzer: 'ik_max_word', - search_analyzer: 'ik_max_word', - fields: { - raw: { - type: 'keyword', - }, - }, - }, - age: { - type: 'long', - }, - }; - } -} diff --git a/src/modules/demo/event/demo.ts b/src/modules/demo/event/demo.ts index 8aff92a..6ffdb35 100644 --- a/src/modules/demo/event/demo.ts +++ b/src/modules/demo/event/demo.ts @@ -1,8 +1,11 @@ +import { Provide, Scope, ScopeEnum } from '@midwayjs/decorator'; import { CoolEvent, Event } from '@cool-midway/core'; /** * 接收事件 */ +@Provide() +@Scope(ScopeEnum.Singleton) @CoolEvent() export class DemoEvent { /** diff --git a/src/modules/demo/middleware/demo.ts b/src/modules/demo/middleware/demo.ts deleted file mode 100644 index a19ec9c..0000000 --- a/src/modules/demo/middleware/demo.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { CoolUrlTagData, TagTypes } from '@cool-midway/core'; -import { IMiddleware } from '@midwayjs/core'; -import { Inject, Middleware } from '@midwayjs/decorator'; -import { NextFunction, Context } from '@midwayjs/koa'; - -@Middleware() -export class DemoMiddleware implements IMiddleware { - @Inject() - tag: CoolUrlTagData; - - resolve() { - return async (ctx: Context, next: NextFunction) => { - const urls = this.tag.byKey(TagTypes.IGNORE_TOKEN); - - console.log('忽略token的URL数组', urls); - - // 控制器前执行的逻辑 - const startTime = Date.now(); - // 执行下一个 Web 中间件,最后执行到控制器 - // 这里可以拿到下一个中间件或者控制器的返回值 - const result = await next(); - // 控制器之后执行的逻辑 - console.log(Date.now() - startTime); - // 返回给上一个中间件的结果 - return result; - }; - } -} diff --git a/src/modules/demo/queue/single.ts b/src/modules/demo/queue/single.ts new file mode 100644 index 0000000..67bcc6b --- /dev/null +++ b/src/modules/demo/queue/single.ts @@ -0,0 +1,20 @@ +import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; +import { IMidwayApplication } from '@midwayjs/core'; +import { App } from '@midwayjs/decorator'; + +/** + * 单例队列,cluster 或 集群模式下 只会有一个实例消费数据 + */ +@CoolQueue({ type: 'single' }) +export class DemoSingleQueue extends BaseCoolQueue { + @App() + app: IMidwayApplication; + + async data(job: any, done: any): Promise { + // 这边可以执行定时任务具体的业务或队列的业务 + console.log('数据', job.data); + // 抛出错误 可以让队列重试,默认重试5次 + //throw new Error('错误'); + done(); + } +} diff --git a/src/modules/demo/service/goods.ts b/src/modules/demo/service/goods.ts index eaf324e..ce82a11 100644 --- a/src/modules/demo/service/goods.ts +++ b/src/modules/demo/service/goods.ts @@ -1,12 +1,33 @@ +import { DemoGoodsEntity } from './../entity/goods'; import { Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; /** - * 缓存 + * 商品示例 */ @Provide() export class DemoGoodsService extends BaseService { - async test() { - console.log('调用'); + @InjectEntityModel(DemoGoodsEntity) + demoGoodsEntity: Repository; + + /** + * 执行sql分页 + */ + async sqlPage(query) { + return this.sqlRenderPage( + 'select * from demo_goods ORDER BY id ASC', + query, + false + ); + } + + /** + * 执行entity分页 + */ + async entityPage(query) { + const find = this.demoGoodsEntity.createQueryBuilder(); + return this.entityRenderPage(find, query); } } diff --git a/src/modules/demo/service/rpc.ts b/src/modules/demo/service/rpc.ts index 4268966..e95ca39 100644 --- a/src/modules/demo/service/rpc.ts +++ b/src/modules/demo/service/rpc.ts @@ -1,6 +1,6 @@ -import { App, Inject, Provide } from '@midwayjs/decorator'; +import { App, Provide } from '@midwayjs/decorator'; import { DemoGoodsEntity } from '../entity/goods'; -import { IMidwayApplication } from '@midwayjs/core'; +import { IMidwayApplication, Inject } from '@midwayjs/core'; import { BaseRpcService, CoolRpc, @@ -22,12 +22,34 @@ export class DemoRpcService extends BaseRpcService { rpc: CoolRpc; /** - * 分布式事务 - * @param params 方法参数 - * @param rpcTransactionId 无需调用者传参, 本次事务的ID,ID会自动注入无需调用者传参 - * @param queryRunner 无需调用者传参,操作数据库,需要用queryRunner操作数据库,才能统一提交或回滚事务 + * 远程调用 + * @returns */ - // 注解启用分布式事务,参数可以指定事务类型 + async call() { + return await this.rpc.call('goods', 'demoGoodsService', 'test', { + a: 1, + }); + } + + /** + * 集群事件 + */ + async event() { + this.rpc.event('test', { a: 1 }); + } + + async info(params) { + return params; + } + async getUser() { + return { + uid: '123', + username: 'mockedName', + phone: '12345678901', + email: 'xxx.xxx@xxx.com', + }; + } + @CoolRpcTransaction() async transaction(params, rpcTransactionId?, queryRunner?: QueryRunner) { console.log('获得的参数', params); @@ -42,18 +64,7 @@ export class DemoRpcService extends BaseRpcService { // 将事务id传给调用的远程服务方法 await this.rpc.call('goods', 'demoGoodsService', 'transaction', { rpcTransactionId, + ...params, }); } - - async info(params) { - return params; - } - async getUser() { - return { - uid: '123', - username: 'mockedName', - phone: '12345678901', - email: 'xxx.xxx@xxx.com', - }; - } } diff --git a/src/modules/demo/service/transaction.ts b/src/modules/demo/service/transaction.ts new file mode 100644 index 0000000..3aa45e8 --- /dev/null +++ b/src/modules/demo/service/transaction.ts @@ -0,0 +1,23 @@ +import { DemoGoodsEntity } from './../entity/goods'; +import { Provide } from '@midwayjs/decorator'; +import { BaseService, CoolTransaction } from '@cool-midway/core'; +import { QueryRunner } from 'typeorm'; + +/** + * 操作事务 + */ +@Provide() +export class DemoTransactionService extends BaseService { + /** + * 事务操作 + */ + @CoolTransaction({ + connectionName: 'default', + }) + async add(param, queryRunner?: QueryRunner) { + await queryRunner.manager.insert(DemoGoodsEntity, param); + return { + id: param.id, + }; + } +} diff --git a/src/modules/demo/socket/hello.ts b/src/modules/demo/socket/hello.ts deleted file mode 100644 index 9260d74..0000000 --- a/src/modules/demo/socket/hello.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - WSController, - OnWSConnection, - Inject, - OnWSMessage, -} from '@midwayjs/decorator'; -import { Context } from '@midwayjs/socketio'; -/** - * 测试 - */ -@WSController('/') -export class HelloController { - @Inject() - ctx: Context; - - @OnWSConnection() - async onConnectionMethod() { - console.log('on client connect', this.ctx.id); - console.log('参数', this.ctx.handshake.query); - this.ctx.emit('data', '连接成功'); - } - - @OnWSMessage('myEvent') - async gotMessage(data) { - console.log('on data got', this.ctx.id, data); - } -} diff --git a/src/modules/dict/entity/info.ts b/src/modules/dict/entity/info.ts index 7af27c1..7197a14 100644 --- a/src/modules/dict/entity/info.ts +++ b/src/modules/dict/entity/info.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 字典信息 */ -@EntityModel('dict_info') +@Entity('dict_info') export class DictInfoEntity extends BaseEntity { @Column({ comment: '类型ID' }) typeId: number; diff --git a/src/modules/dict/entity/type.ts b/src/modules/dict/entity/type.ts index 61b3006..1b5d1b4 100644 --- a/src/modules/dict/entity/type.ts +++ b/src/modules/dict/entity/type.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 字典类别 */ -@EntityModel('dict_type') +@Entity('dict_type') export class DictTypeEntity extends BaseEntity { @Column({ comment: '名称' }) name: string; diff --git a/src/modules/dict/service/info.ts b/src/modules/dict/service/info.ts index 0c396a7..1254e17 100644 --- a/src/modules/dict/service/info.ts +++ b/src/modules/dict/service/info.ts @@ -2,7 +2,7 @@ import { DictTypeEntity } from './../entity/type'; import { DictInfoEntity } from './../entity/info'; import { Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository, In } from 'typeorm'; import * as _ from 'lodash'; @@ -33,7 +33,7 @@ export class DictInfoService extends BaseService { } const data = await this.dictInfoEntity .createQueryBuilder('a') - .select(['a.id', 'a.name', 'a.typeId', 'a.parentId']) + .select(['a.id', 'a.name', 'a.typeId', 'a.parentId', 'a.orderNum']) .where('typeId in(:typeIds)', { typeIds: typeData.map(e => { return e.id; @@ -54,7 +54,7 @@ export class DictInfoService extends BaseService { * @returns */ async value(infoId: number) { - const info = await this.dictInfoEntity.findOne({ id: infoId }); + const info = await this.dictInfoEntity.findOneBy({ id: infoId }); return info?.name; } @@ -64,7 +64,7 @@ export class DictInfoService extends BaseService { * @returns */ async values(infoIds: number[]) { - const infos = await this.dictInfoEntity.find({ id: In(infoIds) }); + const infos = await this.dictInfoEntity.findBy({ id: In(infoIds) }); return infos.map(e => { return e.name; }); @@ -88,7 +88,7 @@ export class DictInfoService extends BaseService { * @param id */ private async delChildDict(id) { - const delDict = await this.dictInfoEntity.find({ parentId: id }); + const delDict = await this.dictInfoEntity.findBy({ parentId: id }); if (_.isEmpty(delDict)) { return; } diff --git a/src/modules/dict/service/type.ts b/src/modules/dict/service/type.ts index 5f5c8d7..7ff4b9d 100644 --- a/src/modules/dict/service/type.ts +++ b/src/modules/dict/service/type.ts @@ -1,7 +1,7 @@ import { DictInfoEntity } from './../entity/info'; import { Provide } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository, In } from 'typeorm'; /** diff --git a/src/modules/iot/config.ts b/src/modules/iot/config.ts new file mode 100644 index 0000000..9c8796b --- /dev/null +++ b/src/modules/iot/config.ts @@ -0,0 +1,19 @@ +import { ModuleConfig } from '@cool-midway/core'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '物联网', + // 模块描述 + description: '物联网模块,主要提供物联交互,状态监测等', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + } as ModuleConfig; +}; diff --git a/src/modules/iot/controller/admin/device.ts b/src/modules/iot/controller/admin/device.ts new file mode 100644 index 0000000..081153b --- /dev/null +++ b/src/modules/iot/controller/admin/device.ts @@ -0,0 +1,17 @@ +import { IotDeviceEntity } from './../../entity/device'; +import { Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * 设备信息 + */ +@Provide() +@CoolController({ + api: ['add', 'delete', 'update', 'info', 'list', 'page'], + entity: IotDeviceEntity, + pageQueryOp: { + keyWordLikeFields: ['label', 'uniqueId'], + fieldEq: ['status'], + }, +}) +export class AdminIotDeviceController extends BaseController {} diff --git a/src/modules/iot/controller/admin/message.ts b/src/modules/iot/controller/admin/message.ts new file mode 100644 index 0000000..a6a81b7 --- /dev/null +++ b/src/modules/iot/controller/admin/message.ts @@ -0,0 +1,16 @@ +import { IotMessageEntity } from './../../entity/message'; +import { Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * 设备消息 + */ +@Provide() +@CoolController({ + api: ['page'], + entity: IotMessageEntity, + pageQueryOp: { + fieldEq: ['deviceId'], + }, +}) +export class AdminIotMessageController extends BaseController {} diff --git a/src/modules/iot/controller/admin/mqtt.ts b/src/modules/iot/controller/admin/mqtt.ts new file mode 100644 index 0000000..e3209a2 --- /dev/null +++ b/src/modules/iot/controller/admin/mqtt.ts @@ -0,0 +1,27 @@ +import { IotMqttService } from './../../service/mqtt'; +import { Provide, Get, Post, Body, Inject } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; + +/** + * MQTT相关 + */ +@Provide() +@CoolController() +export class AdminIotMqttController extends BaseController { + @Inject() + iotMqttService: IotMqttService; + + @Get('/config', { summary: 'MQTT配置信息' }) + async config() { + return this.ok(await this.iotMqttService.config()); + } + + @Post('/publish', { summary: '推送消息' }) + async publish( + @Body('uniqueId') uniqueId: string, + @Body('data') data: string + ) { + await this.iotMqttService.publish(uniqueId, data); + return this.ok(); + } +} diff --git a/src/modules/iot/controller/mqtt.ts b/src/modules/iot/controller/mqtt.ts new file mode 100644 index 0000000..afc7178 --- /dev/null +++ b/src/modules/iot/controller/mqtt.ts @@ -0,0 +1,19 @@ +import { Get, Inject, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { CoolMqttServe } from '@cool-midway/iot'; + +/** + * MQTT + */ +@Provide() +@CoolController() +export class IotMqttController extends BaseController { + @Inject() + coolMqttServe: CoolMqttServe; + + @Get('/publish') + async publish() { + await this.coolMqttServe.publish('presence', 'hello'); + return this.ok(); + } +} diff --git a/src/modules/iot/entity/device.ts b/src/modules/iot/entity/device.ts new file mode 100644 index 0000000..9f5ea8a --- /dev/null +++ b/src/modules/iot/entity/device.ts @@ -0,0 +1,25 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 设备 + */ +@Entity('iot_device') +export class IotDeviceEntity extends BaseEntity { + @Column({ comment: '图标', nullable: true }) + icon: string; + + @Column({ comment: '名称' }) + name: string; + + @Index({ unique: true }) + @Column({ comment: '设备唯一ID' }) + uniqueId: string; + + @Index() + @Column({ comment: '状态 0-离线 1-在线', type: 'tinyint', default: 0 }) + status: number; + + @Column({ comment: '客户端ID', nullable: true }) + clientId: string; +} diff --git a/src/modules/iot/entity/message.ts b/src/modules/iot/entity/message.ts new file mode 100644 index 0000000..5e4643d --- /dev/null +++ b/src/modules/iot/entity/message.ts @@ -0,0 +1,19 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Column, Entity, Index } from 'typeorm'; + +/** + * 设备消息 + */ +@Entity('iot_message') +export class IotMessageEntity extends BaseEntity { + @Index() + @Column({ comment: '设备ID' }) + deviceId: number; + + @Column({ comment: '数据' }) + data: string; + + @Index() + @Column({ comment: '类型 0-推送 1-接收', type: 'tinyint', default: 1 }) + type: number; +} diff --git a/src/modules/iot/event/app.ts b/src/modules/iot/event/app.ts new file mode 100644 index 0000000..159a285 --- /dev/null +++ b/src/modules/iot/event/app.ts @@ -0,0 +1,18 @@ +import { Inject } from '@midwayjs/core'; +import { CoolEvent, Event } from '@cool-midway/core'; +import { IotDeviceService } from '../service/device'; + +/** + * 应用事件 + */ +@CoolEvent() +export class AppEvent { + @Inject() + iotDeviceService: IotDeviceService; + + @Event('onServerReady') + async onServerReady() { + // 重置设备状态 + await this.iotDeviceService.resetStatus(); + } +} diff --git a/src/modules/iot/event/mqtt.ts b/src/modules/iot/event/mqtt.ts new file mode 100644 index 0000000..4b3692d --- /dev/null +++ b/src/modules/iot/event/mqtt.ts @@ -0,0 +1,86 @@ +import { IotMessageService } from './../service/message'; +import { IotDeviceService } from './../service/device'; +import { ILogger, Inject } from '@midwayjs/core'; +import { CoolMqtt, CoolMqttEvent, CoolMqttServe } from '@cool-midway/iot'; + +/** + * 应用事件 + */ +@CoolMqtt() +export class IotMQTTEvent { + @Inject() + iotDeviceService: IotDeviceService; + + @Inject() + iotMessageService: IotMessageService; + + @Inject() + logger: ILogger; + + @Inject() + coolMqttServe: CoolMqttServe; + + /** + * 客户端连接 + * @param client + */ + @CoolMqttEvent('client') + async client(client) { + this.logger.info('mqtt client event clientId:', client.id); + } + + /** + * 发送消息 + * @param packet + * @param client + */ + @CoolMqttEvent('publish') + async publish(packet, client) { + if (packet.cmd) { + // console.log(11); + await this.iotMessageService.record( + packet.topic, + packet.payload.toString(), + packet.properties?.contentType == 'push' ? 0 : 1 + ); + if ( + !packet.topic.includes('@admin') && + packet.properties?.contentType != 'push' + ) { + this.coolMqttServe.publish( + `${packet.topic}@admin`, + packet.payload.toString() + ); + } + } + } + + /** + * 订阅事件 注册设备 + * @param subscriptions + * @param client + */ + @CoolMqttEvent('subscribe') + async subscribe(subscriptions, client) { + await this.iotDeviceService.register(subscriptions[0].topic, client.id); + } + + /** + * 取消订阅 + * @param subscriptions + * @param client + */ + @CoolMqttEvent('unsubscribe') + async unsubscribe(subscriptions, client) { + await this.iotDeviceService.changeStatus(subscriptions[0], 0); + } + + /** + * 断开连接 + * @param client + */ + @CoolMqttEvent('clientDisconnect') + async clientDisconnect(client) { + this.logger.info('mqtt clientDisconnect event clientId:', client.id); + } +} diff --git a/src/modules/iot/service/device.ts b/src/modules/iot/service/device.ts new file mode 100644 index 0000000..aad4a6e --- /dev/null +++ b/src/modules/iot/service/device.ts @@ -0,0 +1,49 @@ +import { IotDeviceEntity } from './../entity/device'; +import { Provide, Scope, ScopeEnum } from '@midwayjs/decorator'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; + +/** + * 设备 + */ +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class IotDeviceService extends BaseService { + @InjectEntityModel(IotDeviceEntity) + iotDeviceEntity: Repository; + + /** + * 注册设备 + * @param uniqueId + * @param clientId + */ + async register(uniqueId: string, clientId: string) { + const info = await this.iotDeviceEntity.findOneBy({ uniqueId }); + if (info) { + await this.iotDeviceEntity.update({ uniqueId }, { status: 1, clientId }); + } else { + // await this.iotDeviceEntity.insert({ uniqueId, clientId, status: 1 }); + } + } + + /** + * 重置所有设备状态 + */ + async resetStatus() { + await this.iotDeviceEntity + .createQueryBuilder() + .update() + .set({ status: 0 }) + .execute(); + } + + /** + * 改变设备状态 + * @param uniqueId + * @param status + */ + async changeStatus(uniqueId: string, status: number) { + await this.iotDeviceEntity.update({ uniqueId }, { status }); + } +} diff --git a/src/modules/iot/service/message.ts b/src/modules/iot/service/message.ts new file mode 100644 index 0000000..c6000ce --- /dev/null +++ b/src/modules/iot/service/message.ts @@ -0,0 +1,32 @@ +import { IotMessageEntity } from './../entity/message'; +import { IotDeviceEntity } from './../entity/device'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import { Provide, Singleton } from '@midwayjs/core'; + +/** + * 消息 + */ +@Provide() +@Singleton() +export class IotMessageService extends BaseService { + @InjectEntityModel(IotDeviceEntity) + iotDeviceEntity: Repository; + + @InjectEntityModel(IotMessageEntity) + iotMessageEntity: Repository; + + /** + * 记录消息 + * @param uniqueId 设备唯一ID + * @param data 数据 + * @param type 类型 0-推送 1-接收 + */ + async record(uniqueId: string, data: string, type: number) { + const device = await this.iotDeviceEntity.findOneBy({ uniqueId }); + if (device) { + await this.iotMessageEntity.insert({ deviceId: device.id, data, type }); + } + } +} diff --git a/src/modules/iot/service/mqtt.ts b/src/modules/iot/service/mqtt.ts new file mode 100644 index 0000000..e9facbd --- /dev/null +++ b/src/modules/iot/service/mqtt.ts @@ -0,0 +1,43 @@ +import { CoolMqttServe } from '@cool-midway/iot'; +import { IotMessageEntity } from './../entity/message'; +import { Config, Inject, Provide } from '@midwayjs/decorator'; +import { BaseService, CoolIotConfig } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; + +/** + * MQTT + */ +@Provide() +export class IotMqttService extends BaseService { + @InjectEntityModel(IotMessageEntity) + iotMessageEntity: Repository; + + @Config('cool.iot') + coolIotConfig: CoolIotConfig; + + @Inject() + coolMqttServe: CoolMqttServe; + + /** + * 配置信息 + */ + async config() { + return { + port: this.coolIotConfig.port, + }; + } + + /** + * 推送消息 + * @param uniqueId 设备唯一ID + * @param data 推送数据 + */ + async publish(uniqueId: string, data: string) { + await this.coolMqttServe.publish(uniqueId, data, { + properties: { + contentType: 'push', + }, + }); + } +} diff --git a/src/modules/recycle/config.ts b/src/modules/recycle/config.ts new file mode 100644 index 0000000..3421900 --- /dev/null +++ b/src/modules/recycle/config.ts @@ -0,0 +1,19 @@ +import { ModuleConfig } from '@cool-midway/core'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '数据回收', + // 模块描述 + description: '收集被删除的数据,管理和恢复', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + } as ModuleConfig; +}; diff --git a/src/modules/recycle/controller/admin/data.ts b/src/modules/recycle/controller/admin/data.ts new file mode 100644 index 0000000..10810db --- /dev/null +++ b/src/modules/recycle/controller/admin/data.ts @@ -0,0 +1,34 @@ +import { BaseSysUserEntity } from './../../../base/entity/sys/user'; +import { RecycleDataEntity } from './../../entity/data'; +import { Body, Inject, Post, Provide } from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { RecycleDataService } from '../../service/data'; + +/** + * 数据回收 + */ +@Provide() +@CoolController({ + api: ['info', 'page'], + entity: RecycleDataEntity, + pageQueryOp: { + select: ['a.*', 'b.name as userName'], + join: [ + { + entity: BaseSysUserEntity, + alias: 'b', + condition: 'a.userId = b.id', + }, + ], + }, +}) +export class AdminRecycleDataController extends BaseController { + @Inject() + recycleDataService: RecycleDataService; + + @Post('/restore', { summary: '恢复数据' }) + async restore(@Body('ids') ids: number[]) { + await this.recycleDataService.restore(ids); + return this.ok(); + } +} diff --git a/src/modules/recycle/entity/data.ts b/src/modules/recycle/entity/data.ts new file mode 100644 index 0000000..e100488 --- /dev/null +++ b/src/modules/recycle/entity/data.ts @@ -0,0 +1,32 @@ +import { BaseEntity } from '@cool-midway/core'; +import { Entity, Column, Index } from 'typeorm'; + +/** + * 数据回收站 软删除的时候数据会回收到该表 + */ +@Entity('recycle_data') +export class RecycleDataEntity extends BaseEntity { + @Column({ comment: '表', type: 'json' }) + entityInfo: { + // 数据源名称 + dataSourceName: string; + // entity + entity: string; + }; + + @Index() + @Column({ comment: '操作人', nullable: true }) + userId: string; + + @Column({ comment: '被删除的数据', type: 'json' }) + data: object[]; + + @Column({ comment: '请求的接口', nullable: true }) + url: string; + + @Column({ comment: '请求参数', nullable: true, type: 'json' }) + params: string; + + @Column({ comment: '删除数据条数', default: 1 }) + count: number; +} diff --git a/src/modules/recycle/event/data.ts b/src/modules/recycle/event/data.ts new file mode 100644 index 0000000..5c96bab --- /dev/null +++ b/src/modules/recycle/event/data.ts @@ -0,0 +1,21 @@ +import { CoolEvent, EVENT, Event } from '@cool-midway/core'; +import { Inject } from '@midwayjs/core'; +import { RecycleDataService } from '../service/data'; + +/** + * 接受数据事件 + */ +@CoolEvent() +export class RecycleDataEvent { + @Inject() + recycleDataService: RecycleDataService; + + /** + * 数据被删除 + * @param params + */ + @Event(EVENT.SOFT_DELETE) + async softDelete(params) { + await this.recycleDataService.record(params); + } +} diff --git a/src/modules/recycle/init.sql b/src/modules/recycle/init.sql new file mode 100644 index 0000000..6cf45ae --- /dev/null +++ b/src/modules/recycle/init.sql @@ -0,0 +1,3 @@ +BEGIN; +INSERT INTO `base_sys_conf` VALUES (2, '2021-02-25 14:23:26.810981', '2021-02-25 14:23:26.810981', 'recycleKeep', '31'); +COMMIT; \ No newline at end of file diff --git a/src/modules/recycle/schedule/data.ts b/src/modules/recycle/schedule/data.ts new file mode 100644 index 0000000..5ac87b9 --- /dev/null +++ b/src/modules/recycle/schedule/data.ts @@ -0,0 +1,32 @@ +import { + Provide, + Inject, + CommonSchedule, + TaskLocal, + FORMAT, +} from '@midwayjs/decorator'; +import { ILogger } from '@midwayjs/logger'; +import { RecycleDataService } from '../service/data'; + +/** + * 数据定时清除定时任务 + */ +@Provide() +export class BaseRecycleSchedule implements CommonSchedule { + @Inject() + recycleDataService: RecycleDataService; + + @Inject() + logger: ILogger; + + // 定时执行的具体任务 + @TaskLocal(FORMAT.CRONTAB.EVERY_DAY) + async exec() { + this.logger.info('清除回收站数据定时任务开始执行'); + const startTime = Date.now(); + await this.recycleDataService.clear(); + this.logger.info( + `清除回收站数据定时任务结束,耗时:${Date.now() - startTime}ms` + ); + } +} diff --git a/src/modules/recycle/service/data.ts b/src/modules/recycle/service/data.ts new file mode 100644 index 0000000..2484246 --- /dev/null +++ b/src/modules/recycle/service/data.ts @@ -0,0 +1,89 @@ +import { RecycleDataEntity } from './../entity/data'; +import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/decorator'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; +import * as _ from 'lodash'; +import * as moment from 'moment'; +import { BaseSysConfService } from '../../base/service/sys/conf'; + +/** + * 数据回收 + */ +@Provide() +@Scope(ScopeEnum.Request, { allowDowngrade: true }) +export class RecycleDataService extends BaseService { + @InjectEntityModel(RecycleDataEntity) + recycleDataEntity: Repository; + + @Inject() + typeORMDataSourceManager: TypeORMDataSourceManager; + + @Inject() + baseSysConfService: BaseSysConfService; + + /** + * 恢复数据 + * @param ids + */ + async restore(ids: number[]) { + for (const id of ids) { + const info = await this.recycleDataEntity.findOneBy({ id }); + if (!info) { + continue; + } + let entityModel = this.typeORMDataSourceManager + .getDataSource(info.entityInfo.dataSourceName) + .getRepository(info.entityInfo.entity); + await entityModel.save(info.data); + await this.recycleDataEntity.delete(id); + } + } + + /** + * 记录数据 + * @param params + */ + async record(params) { + const { ctx, data, entity } = params; + const dataSourceName = + this.typeORMDataSourceManager.getDataSourceNameByModel(entity.target); + const url = ctx?.url; + await this.recycleDataEntity.save({ + entityInfo: { + dataSourceName, + entity: entity.target.name, + }, + url, + params: + ctx?.req.method === 'GET' ? ctx?.request.query : ctx?.request.body, + data, + count: data.length, + userId: _.startsWith(url, '/admin/') ? ctx?.admin.userId : ctx?.user?.id, + }); + } + + /** + * 日志 + * @param isAll 是否清除全部 + */ + async clear(isAll?) { + if (isAll) { + await this.recycleDataEntity.clear(); + return; + } + const keepDay = await this.baseSysConfService.getValue('recycleKeep'); + if (keepDay) { + const beforeDate = `${moment() + .add(-keepDay, 'days') + .format('YYYY-MM-DD')} 00:00:00`; + await this.recycleDataEntity + .createQueryBuilder() + .delete() + .where('createTime < :createTime', { createTime: beforeDate }) + .execute(); + } else { + await this.recycleDataEntity.clear(); + } + } +} diff --git a/src/modules/space/config.ts b/src/modules/space/config.ts index acd7973..10e7cff 100644 --- a/src/modules/space/config.ts +++ b/src/modules/space/config.ts @@ -15,5 +15,10 @@ export default () => { globalMiddlewares: [], // 模块加载顺序,默认为0,值越大越优先加载 order: 0, + // wps的配置 + wps: { + // 这是个测试的appId,会有水印 + appId: 'SX20230111NDUAGQ', + }, } as ModuleConfig; }; diff --git a/src/modules/space/controller/admin/info.ts b/src/modules/space/controller/admin/info.ts index 7520b73..767b2be 100644 --- a/src/modules/space/controller/admin/info.ts +++ b/src/modules/space/controller/admin/info.ts @@ -1,6 +1,7 @@ -import { Provide } from '@midwayjs/decorator'; +import { Config, Get, Provide } from '@midwayjs/decorator'; import { CoolController, BaseController } from '@cool-midway/core'; import { SpaceInfoEntity } from '../../entity/info'; +import { SpaceInfoService } from '../../service/info'; /** * 图片空间信息 @@ -9,8 +10,17 @@ import { SpaceInfoEntity } from '../../entity/info'; @CoolController({ api: ['add', 'delete', 'update', 'info', 'list', 'page'], entity: SpaceInfoEntity, + service: SpaceInfoService, pageQueryOp: { fieldEq: ['type', 'classifyId'], }, }) -export class BaseAppSpaceInfoController extends BaseController {} +export class BaseAppSpaceInfoController extends BaseController { + @Config('module.space.wps') + config; + + @Get('/getConfig', { summary: '获得WPS配置' }) + async getConfig() { + return this.ok({ appId: this.config.appId }); + } +} diff --git a/src/modules/space/controller/app/wps.ts b/src/modules/space/controller/app/wps.ts new file mode 100644 index 0000000..bea47d3 --- /dev/null +++ b/src/modules/space/controller/app/wps.ts @@ -0,0 +1,65 @@ +import { + ALL, + Body, + Files, + Get, + Inject, + Param, + Post, + Provide, + Query, +} from '@midwayjs/decorator'; +import { CoolController, BaseController } from '@cool-midway/core'; +import { SpaceWpsService } from '../../service/wps'; + +/** + * wps回调 + */ +@Provide() +@CoolController('/wps') +export class AppSpaceWpsController extends BaseController { + @Inject() + spaceWpsService: SpaceWpsService; + + @Get('/v3/3rd/files/:file_id', { summary: '获取文件信息' }) + async files(@Param('file_id') file_id: string) { + return await this.spaceWpsService.getFiles(file_id); + } + + @Get('/v3/3rd/files/:file_id/download', { summary: '获取文件下载地址' }) + async download(@Param('file_id') file_id: string) { + return await this.spaceWpsService.download(file_id); + } + + @Get('/v3/3rd/files/:file_id/permission', { summary: '获取文档用户权限' }) + async permission(@Param('file_id') file_id: string) { + return await this.spaceWpsService.permission(file_id); + } + + @Post('/v3/3rd/files/:file_id/upload', { summary: '文件上传' }) + async upload(@Param('file_id') file_id: string, @Files() files) { + return await this.spaceWpsService.upload(file_id, files); + } + + @Get('/v3/3rd/files/:file_id/upload/prepare', { summary: '准备上传阶段' }) + async uploadPrepare(@Param('file_id') file_id: string) { + return await this.spaceWpsService.uploadPrepare(file_id); + } + + @Post('/v3/3rd/files/:file_id/upload/address', { summary: '获取上传地址' }) + async uploadAddress(@Param('file_id') file_id: string, @Body(ALL) body) { + return await this.spaceWpsService.uploadAddress(file_id, body); + } + + @Post('/v3/3rd/files/:file_id/upload/complete', { + summary: '上传完成后,回调通知上传结果', + }) + async uploadComplete(@Param('file_id') file_id: string, @Body(ALL) body) { + return await this.spaceWpsService.uploadComplete(file_id, body); + } + + @Get('/v3/3rd/users', { summary: '用户信息' }) + async users(@Query('user_ids') user_ids: string[]) { + return await this.spaceWpsService.users(user_ids); + } +} diff --git a/src/modules/space/entity/info.ts b/src/modules/space/entity/info.ts index 3762f45..a8d80b8 100644 --- a/src/modules/space/entity/info.ts +++ b/src/modules/space/entity/info.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 文件空间信息 */ -@EntityModel('space_info') +@Entity('space_info') export class SpaceInfoEntity extends BaseEntity { @Column({ comment: '地址' }) url: string; @@ -15,4 +14,20 @@ export class SpaceInfoEntity extends BaseEntity { @Column({ comment: '分类ID', type: 'bigint', nullable: true }) classifyId: number; + + @Index() + @Column({ comment: '文件id' }) + fileId: string; + + @Column({ comment: '文件名' }) + name: string; + + @Column({ comment: '文件大小' }) + size: number; + + @Column({ comment: '文档版本', default: 1 }) + version: number; + + @Column({ comment: '文件位置' }) + key: string; } diff --git a/src/modules/space/entity/type.ts b/src/modules/space/entity/type.ts index 663f1bb..b554954 100644 --- a/src/modules/space/entity/type.ts +++ b/src/modules/space/entity/type.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 图片空间信息分类 */ -@EntityModel('space_type') +@Entity('space_type') export class SpaceTypeEntity extends BaseEntity { @Column({ comment: '类别名称' }) name: string; diff --git a/src/modules/space/service/info.ts b/src/modules/space/service/info.ts new file mode 100644 index 0000000..7ceca1e --- /dev/null +++ b/src/modules/space/service/info.ts @@ -0,0 +1,27 @@ +import { SpaceInfoEntity } from './../entity/info'; +import { Config, Provide } from '@midwayjs/decorator'; +import { BaseService, CoolFileConfig, MODETYPE } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { Repository } from 'typeorm'; + +/** + * 文件信息 + */ +@Provide() +export class SpaceInfoService extends BaseService { + @InjectEntityModel(SpaceInfoEntity) + spaceInfoEntity: Repository; + + @Config('cool.file') + config: CoolFileConfig; + + /** + * 新增 + */ + async add(param) { + if (this.config.mode == MODETYPE.LOCAL) { + param.key = param.url.replace(this.config.domain, ''); + } + return super.add(param); + } +} diff --git a/src/modules/space/service/wps.ts b/src/modules/space/service/wps.ts new file mode 100644 index 0000000..ae56474 --- /dev/null +++ b/src/modules/space/service/wps.ts @@ -0,0 +1,271 @@ +import { SpaceTypeEntity } from './../entity/type'; +import { SpaceInfoEntity } from './../entity/info'; +import { Config, Inject, Provide } from '@midwayjs/decorator'; +import { BaseService } from '@cool-midway/core'; +import { InjectEntityModel } from '@midwayjs/typeorm'; +import { In, Repository } from 'typeorm'; +import * as _ from 'lodash'; +import { BaseSysUserService } from '../../base/service/sys/user'; +import * as moment from 'moment'; +import * as fs from 'fs'; +import { CacheManager } from '@midwayjs/cache'; +import * as jwt from 'jsonwebtoken'; +import { BaseSysUserEntity } from '../../base/entity/sys/user'; +import { CoolFile } from '@cool-midway/file'; + +/** + * 使用wps在线文档 + */ +@Provide() +export class SpaceWpsService extends BaseService { + @InjectEntityModel(SpaceInfoEntity) + spaceInfoEntity: Repository; + + @InjectEntityModel(SpaceTypeEntity) + spaceTypeEntity: Repository; + + @Inject() + baseSysUserService: BaseSysUserService; + + @InjectEntityModel(BaseSysUserEntity) + baseSysUserEntity: Repository; + + @Inject() + ctx; + + @Inject() + coolFile: CoolFile; + + @Inject() + cacheManager: CacheManager; + + @Config('module.base') + jwtConfig; + + /** + * 根据filedId获取数据库文件信息 + * @param fileId + */ + async getFileInfo(fileId) { + return await this.spaceInfoEntity.findOneBy({ + fileId, + }); + } + + /** + * 校验文档权限 + */ + async verifyUser() { + const token = this.ctx.request.header['x-weboffice-token']; + let tokenData; + try { + tokenData = jwt.verify(token, this.jwtConfig.jwt.secret); + } catch (err) {} + let userInfo; + if (tokenData) { + userInfo = await this.baseSysUserEntity.findOneBy({ + id: tokenData.userId, + }); + } + if (_.isEmpty(userInfo)) { + throw new Error('用户不存在'); + } + return userInfo; + } + + /** + * 获得文件信息 + * @param file_id + */ + async getFiles(file_id: string) { + const userInfo = await this.verifyUser(); + const fileInfo = await this.getFileInfo(file_id); + if (_.isEmpty(fileInfo)) { + return { + code: '40004', + msg: '文档不存在', + }; + } + return { + code: 0, + data: { + id: fileInfo.fileId, + name: fileInfo.name, + version: fileInfo.version, + size: fileInfo.size, + create_time: moment(fileInfo.createTime).valueOf(), + modify_time: moment(fileInfo.updateTime).valueOf(), + creator_id: String(userInfo.id), + modifier_id: String(userInfo.id), + attrs: { _w_third_file_id: fileInfo.fileId }, + }, + }; + } + + /** + * 获取文件下载地址 + * @param file_id + * @returns + */ + async download(file_id: string) { + await this.verifyUser(); + const fileInfo = await this.getFileInfo(file_id); + return { + code: 0, + data: { + url: fileInfo.url, + }, + }; + } + + /** + * 获取文档用户权限 + * @param file_id + * @returns + */ + async permission(file_id: string) { + const userInfo = await this.verifyUser(); + return { + code: 0, + data: { + user_id: String(userInfo.id), + read: 1, + update: 1, + download: 1, + rename: 0, + history: 0, + copy: 1, + print: 1, + saveas: 1, + comment: 1, + }, + }; + } + + /** + * 文件上传 + * @param file_id + * @param files + * @param body + * @returns + */ + async upload(file_id: string, files) { + const userInfo = await this.verifyUser(); + const fileInfo = await this.getFileInfo(file_id); + const data = files[0].data; + const stat = fs.statSync(data); + await this.coolFile.uploadWithKey(files[0], fileInfo.key); + + fileInfo.version++; + fileInfo.size = stat.size; + await this.spaceInfoEntity.save(fileInfo); + return { + code: 0, + data: { + id: fileInfo.fileId, + name: fileInfo.name, + version: fileInfo.version, + size: fileInfo.size, + create_time: moment(fileInfo.createTime).valueOf(), + modify_time: moment(fileInfo.updateTime).valueOf(), + creator_id: String(userInfo.id), + modifier_id: String(userInfo.id), + }, + }; + } + + /** + * 用户信息 + * @param user_ids + * @returns + */ + async users(user_ids: string[]) { + const userInfos = await this.baseSysUserEntity.find({ + where: { + id: In(user_ids), + }, + }); + return { + code: 0, + data: userInfos.map(userInfo => { + return { + id: String(userInfo.id), + name: userInfo.name, + avatar_url: userInfo.headImg, + }; + }), + }; + } + + /** + * 准备上传阶段 + * @param file_id + * @returns + */ + async uploadPrepare(file_id: string) { + console.log('准备上传阶段:' + file_id); + return { + code: 0, + data: { + digest_types: ['sha1'], + }, + msg: '', + }; + } + + /** + * 获取上传地址 + * @param file_id + * @param body + * @returns + */ + async uploadAddress(file_id: string, body: any) { + console.log('获取上传地址:' + file_id); + console.log(body); + const fileInfo = await this.getFileInfo(file_id); + const uploadRes = await this.coolFile.downAndUpload( + body.url, + fileInfo.name + ); + if (!uploadRes) { + return { + code: '41001', + msg: '文件未正确上传', + }; + } + return { + code: 0, + data: { + method: 'PUT', + url: uploadRes, + }, + msg: '', + }; + } + + /** + * 上传完成后,回调通知上传结果 + * @param file_id + * @param body + * @returns + */ + async uploadComplete(file_id: string, body: any) { + console.log('上传完成后,回调通知上传结果:' + file_id); + console.log(body); + const userInfo = await this.baseSysUserService.person(); + const fileInfo = await this.getFileInfo(file_id); + return { + code: 0, + data: { + id: fileInfo.fileId, + name: fileInfo.name, + version: fileInfo.version, + size: fileInfo.size, + create_time: moment(fileInfo.createTime).valueOf(), + modify_time: moment(fileInfo.updateTime).valueOf(), + creator_id: String(userInfo.id), + modifier_id: String(userInfo.id), + }, + }; + } +} diff --git a/src/modules/task/entity/info.ts b/src/modules/task/entity/info.ts index 5efaca3..e7ddcd8 100644 --- a/src/modules/task/entity/info.ts +++ b/src/modules/task/entity/info.ts @@ -1,11 +1,10 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column } from 'typeorm'; +import { Column, Entity } from 'typeorm'; /** * 任务信息 */ -@EntityModel('task_info') +@Entity('task_info') export class TaskInfoEntity extends BaseEntity { @Column({ comment: '任务ID', nullable: true }) jobId: string; diff --git a/src/modules/task/entity/log.ts b/src/modules/task/entity/log.ts index 94ba97b..1d0caf5 100644 --- a/src/modules/task/entity/log.ts +++ b/src/modules/task/entity/log.ts @@ -1,17 +1,16 @@ -import { EntityModel } from '@midwayjs/orm'; import { BaseEntity } from '@cool-midway/core'; -import { Column, Index } from 'typeorm'; +import { Column, Index, Entity } from 'typeorm'; /** * 任务日志 */ -@EntityModel('task_log') +@Entity('task_log') export class TaskLogEntity extends BaseEntity { @Index() @Column({ comment: '任务ID', nullable: true, type: 'bigint' }) taskId: number; - @Column({ comment: '状态 0:失败 1:成功', default: 0, type: 'tinyint' }) + @Column({ comment: '状态 0-失败 1-成功', default: 0, type: 'tinyint' }) status: number; @Column({ comment: '详情描述', nullable: true, type: 'text' }) diff --git a/src/modules/task/event/app.ts b/src/modules/task/event/app.ts index 3245b58..6866027 100644 --- a/src/modules/task/event/app.ts +++ b/src/modules/task/event/app.ts @@ -1,6 +1,6 @@ +import { Inject } from '@midwayjs/core'; import { TaskInfoService } from './../service/info'; import { CoolEvent, Event } from '@cool-midway/core'; -import { Inject } from '@midwayjs/decorator'; /** * 应用事件 @@ -12,6 +12,6 @@ export class AppEvent { @Event('onServerReady') async onServerReady() { - this.taskInfoService.initTask(); + await this.taskInfoService.initTask(); } } diff --git a/src/modules/task/service/info.ts b/src/modules/task/service/info.ts index b8a2783..7514686 100644 --- a/src/modules/task/service/info.ts +++ b/src/modules/task/service/info.ts @@ -7,7 +7,7 @@ import { ScopeEnum, } from '@midwayjs/decorator'; import { BaseService } from '@cool-midway/core'; -import { InjectEntityModel } from '@midwayjs/orm'; +import { InjectEntityModel } from '@midwayjs/typeorm'; import { Repository } from 'typeorm'; import { TaskInfoEntity } from '../entity/info'; import { TaskLogEntity } from '../entity/log'; @@ -46,7 +46,7 @@ export class TaskInfoService extends BaseService { * @param id */ async stop(id) { - const task = await this.taskInfoEntity.findOne({ id }); + const task = await this.taskInfoEntity.findOneBy({ id }); if (task) { const result = await this.taskInfoQueue.getRepeatableJobs(); const job = _.find(result, { id: task.id + '' }); @@ -75,7 +75,7 @@ export class TaskInfoService extends BaseService { * @param type */ async start(id, type?) { - const task = await this.taskInfoEntity.findOne({ id }); + const task = await this.taskInfoEntity.findOneBy({ id }); task.status = 1; if (type || type == 0) { task.type = type; @@ -88,7 +88,7 @@ export class TaskInfoService extends BaseService { * @param id */ async once(id) { - const task = await this.taskInfoEntity.findOne({ id }); + const task = await this.taskInfoEntity.findOneBy({ id }); if (task) { await this.taskInfoQueue.add( { @@ -185,7 +185,7 @@ export class TaskInfoService extends BaseService { idArr = ids.split(','); } for (const id of idArr) { - const task = await this.taskInfoEntity.findOne({ id }); + const task = await this.taskInfoEntity.findOneBy({ id }); const exist = await this.exist(task.id); if (exist) { this.stop(task.id); @@ -247,7 +247,8 @@ export class TaskInfoService extends BaseService { */ async initTask() { setTimeout(async () => { - const runningTasks = await this.taskInfoEntity.find({ status: 1 }); + this.logger.info('init task....'); + const runningTasks = await this.taskInfoEntity.findBy({ status: 1 }); if (!_.isEmpty(runningTasks)) { for (const task of runningTasks) { const job = await this.exist(task.id); // 任务已存在就不添加 @@ -291,7 +292,7 @@ export class TaskInfoService extends BaseService { * @returns */ async info(id: any): Promise { - const info = await this.taskInfoEntity.findOne({ id }); + const info = await this.taskInfoEntity.findOneBy({ id }); return { ...info, repeatCount: info.limit, @@ -307,17 +308,16 @@ export class TaskInfoService extends BaseService { if (!job) { return; } - // @ts-ignore - const task = await this.taskInfoEntity.findOne({ id: job.id }); + const task = await this.taskInfoEntity.findOneBy({ id: job.id }); const nextTime = await this.getNextRunTime(task.id); if (task) { - // if (task.nextRunTime.getTime() == nextTime.getTime()) { - // task.status = 0; - // task.nextRunTime = nextTime; - // this.taskInfoQueue.removeRepeatableByKey(job.key); - // } else { - task.nextRunTime = nextTime; - // } + if (task.nextRunTime.getTime() == nextTime.getTime()) { + task.status = 0; + task.nextRunTime = nextTime; + this.taskInfoQueue.removeRepeatableByKey(job.key); + } else { + task.nextRunTime = nextTime; + } await this.taskInfoEntity.update(task.id, task); } } diff --git a/src/modules/user/config.ts b/src/modules/user/config.ts new file mode 100644 index 0000000..03293fe --- /dev/null +++ b/src/modules/user/config.ts @@ -0,0 +1,19 @@ +import { ModuleConfig } from '@cool-midway/core'; + +/** + * 模块配置 + */ +export default () => { + return { + // 模块名称 + name: '用户模块', + // 模块描述 + description: '小程序、app等客户端用户,与后台用户区别开', + // 中间件,只对本模块有效 + middlewares: [], + // 中间件,全局有效 + globalMiddlewares: [], + // 模块加载顺序,默认为0,值越大越优先加载 + order: 0, + } as ModuleConfig; +}; diff --git a/src/welcome.ts b/src/welcome.ts index 62dc944..508e9e9 100644 --- a/src/welcome.ts +++ b/src/welcome.ts @@ -1,5 +1,7 @@ -import { Controller, Get, Inject } from '@midwayjs/decorator'; -import { Context } from '@midwayjs/koa'; +import { CoolCloudDb } from '@cool-midway/cloud'; +import { App, Controller, Get, Inject } from '@midwayjs/decorator'; +import { Context, Application } from '@midwayjs/koa'; +import { TypeORMDataSourceManager } from '@midwayjs/typeorm'; /** * 欢迎界面 @@ -9,10 +11,19 @@ export class WelcomeController { @Inject() ctx: Context; + @App() + app: Application; + + @Inject() + typeORMDataSourceManager: TypeORMDataSourceManager; + + @Inject() + coolCloudDb: CoolCloudDb; + @Get('/') public async welcome() { await this.ctx.render('welcome', { - text: 'HELLO COOL-ADMIN 5.x 一个项目只用COOL就够了!!!', + text: 'HELLO COOL-ADMIN 6.x 一个项目用COOL就够了!!!', }); } } diff --git a/test/controller/api.test.ts b/test/controller/api.test.ts old mode 100755 new mode 100644 diff --git a/test/controller/home.test.ts b/test/controller/home.test.ts old mode 100755 new mode 100644 diff --git a/typings/app/index.d.ts b/typings/app/index.d.ts new file mode 100644 index 0000000..8ed2d4b --- /dev/null +++ b/typings/app/index.d.ts @@ -0,0 +1,6 @@ +// This file is created by egg-ts-helper +// Do not modify this file!!!!!!!!! +import 'egg'; +import '@midwayjs/web'; +export * from 'egg'; +export as namespace Egg; \ No newline at end of file diff --git a/typings/config/index.d.ts b/typings/config/index.d.ts new file mode 100644 index 0000000..d68bf9a --- /dev/null +++ b/typings/config/index.d.ts @@ -0,0 +1,35 @@ +// This file is created by egg-ts-helper +// Do not modify this file!!!!!!!!! +import 'egg'; +import '@midwayjs/web'; +import 'egg-onerror'; +import 'egg-session'; +import 'egg-i18n'; +import 'egg-watcher'; +import 'egg-multipart'; +import 'egg-security'; +import 'egg-development'; +import 'egg-logrotator'; +import 'egg-schedule'; +import 'egg-static'; +import 'egg-jsonp'; +import 'egg-view'; +import 'midway-schedule'; +import { EggPluginItem } from 'egg'; +declare module 'egg' { + interface EggPlugin { + 'onerror'?: EggPluginItem; + 'session'?: EggPluginItem; + 'i18n'?: EggPluginItem; + 'watcher'?: EggPluginItem; + 'multipart'?: EggPluginItem; + 'security'?: EggPluginItem; + 'development'?: EggPluginItem; + 'logrotator'?: EggPluginItem; + 'schedule'?: EggPluginItem; + 'static'?: EggPluginItem; + 'jsonp'?: EggPluginItem; + 'view'?: EggPluginItem; + 'schedulePlus'?: EggPluginItem; + } +} \ No newline at end of file diff --git a/typings/config/plugin.d.ts b/typings/config/plugin.d.ts new file mode 100644 index 0000000..d68bf9a --- /dev/null +++ b/typings/config/plugin.d.ts @@ -0,0 +1,35 @@ +// This file is created by egg-ts-helper +// Do not modify this file!!!!!!!!! +import 'egg'; +import '@midwayjs/web'; +import 'egg-onerror'; +import 'egg-session'; +import 'egg-i18n'; +import 'egg-watcher'; +import 'egg-multipart'; +import 'egg-security'; +import 'egg-development'; +import 'egg-logrotator'; +import 'egg-schedule'; +import 'egg-static'; +import 'egg-jsonp'; +import 'egg-view'; +import 'midway-schedule'; +import { EggPluginItem } from 'egg'; +declare module 'egg' { + interface EggPlugin { + 'onerror'?: EggPluginItem; + 'session'?: EggPluginItem; + 'i18n'?: EggPluginItem; + 'watcher'?: EggPluginItem; + 'multipart'?: EggPluginItem; + 'security'?: EggPluginItem; + 'development'?: EggPluginItem; + 'logrotator'?: EggPluginItem; + 'schedule'?: EggPluginItem; + 'static'?: EggPluginItem; + 'jsonp'?: EggPluginItem; + 'view'?: EggPluginItem; + 'schedulePlus'?: EggPluginItem; + } +} \ No newline at end of file diff --git a/view/welcome.html b/view/welcome.html index d5850b6..fc70487 100644 --- a/view/welcome.html +++ b/view/welcome.html @@ -7,8 +7,9 @@ COOL-AMIND 一个很酷的后台权限管理系统 - + +
<%= text %>