├── .cursor └── rules │ ├── authority.mdc │ ├── cache.mdc │ ├── controller.mdc │ ├── db.mdc │ ├── event.mdc │ ├── exception.mdc │ ├── module.mdc │ ├── service.mdc │ ├── socket.mdc │ ├── task.mdc │ └── tenant.mdc ├── .cursorrules ├── .editorconfig ├── .eslintrc.json ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .vscode ├── config.code-snippets ├── controller.code-snippets ├── entity.code-snippets ├── event.code-snippets ├── launch.json ├── middleware.code-snippets ├── queue.code-snippets └── service.code-snippets ├── Dockerfile ├── LICENSE ├── README.md ├── bootstrap.js ├── docker-compose.yml ├── jest.config.js ├── package.json ├── public ├── Thumbs.db ├── css │ └── welcome.css ├── favicon.ico ├── index.html ├── js │ └── welcome.js └── swagger │ ├── LICENSE │ ├── NOTICE │ ├── README.md │ ├── absolute-path.js │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── index.css │ ├── index.html │ ├── index.js │ ├── oauth2-redirect.html │ ├── package.json │ ├── swagger-initializer.js │ ├── swagger-ui-bundle.js │ ├── swagger-ui-bundle.js.map │ ├── swagger-ui-es-bundle-core.js │ ├── swagger-ui-es-bundle-core.js.map │ ├── swagger-ui-es-bundle.js │ ├── swagger-ui-es-bundle.js.map │ ├── swagger-ui-standalone-preset.js │ ├── swagger-ui-standalone-preset.js.map │ ├── swagger-ui.css │ ├── swagger-ui.css.map │ ├── swagger-ui.js │ └── swagger-ui.js.map ├── src ├── comm │ ├── path.ts │ ├── port.ts │ └── utils.ts ├── config │ ├── config.default.ts │ ├── config.local.ts │ └── config.prod.ts ├── configuration.ts ├── entities.ts ├── interface.ts └── modules │ ├── base │ ├── config.ts │ ├── controller │ │ ├── admin │ │ │ ├── coding.ts │ │ │ ├── comm.ts │ │ │ ├── open.ts │ │ │ └── sys │ │ │ │ ├── department.ts │ │ │ │ ├── log.ts │ │ │ │ ├── menu.ts │ │ │ │ ├── param.ts │ │ │ │ ├── role.ts │ │ │ │ └── user.ts │ │ └── app │ │ │ ├── README.md │ │ │ └── comm.ts │ ├── db.json │ ├── db │ │ └── tenant.ts │ ├── dto │ │ └── login.ts │ ├── entity │ │ ├── base.ts │ │ └── sys │ │ │ ├── conf.ts │ │ │ ├── department.ts │ │ │ ├── log.ts │ │ │ ├── menu.ts │ │ │ ├── param.ts │ │ │ ├── role.ts │ │ │ ├── role_department.ts │ │ │ ├── role_menu.ts │ │ │ ├── user.ts │ │ │ └── user_role.ts │ ├── event │ │ ├── app.ts │ │ └── menu.ts │ ├── job │ │ └── log.ts │ ├── menu.json │ ├── middleware │ │ ├── authority.ts │ │ ├── log.ts │ │ └── translate.ts │ └── service │ │ ├── coding.ts │ │ ├── sys │ │ ├── conf.ts │ │ ├── data.ts │ │ ├── department.ts │ │ ├── log.ts │ │ ├── login.ts │ │ ├── menu.ts │ │ ├── param.ts │ │ ├── perms.ts │ │ ├── role.ts │ │ └── user.ts │ │ └── translate.ts │ ├── demo │ ├── config.ts │ ├── controller │ │ ├── admin │ │ │ ├── goods.ts │ │ │ └── tenant.ts │ │ └── open │ │ │ ├── cache.ts │ │ │ ├── event.ts │ │ │ ├── goods.ts │ │ │ ├── i18n.ts │ │ │ ├── plugin.ts │ │ │ ├── queue.ts │ │ │ ├── rpc.ts │ │ │ ├── sse.ts │ │ │ ├── tenant.ts │ │ │ └── transaction.ts │ ├── entity │ │ └── goods.ts │ ├── event │ │ └── comm.ts │ ├── queue │ │ ├── comm.ts │ │ ├── getter.ts │ │ └── single.ts │ └── service │ │ ├── cache.ts │ │ ├── goods.ts │ │ ├── i18n.ts │ │ ├── rpc.ts │ │ ├── tenant.ts │ │ └── transaction.ts │ ├── dict │ ├── config.ts │ ├── controller │ │ ├── admin │ │ │ ├── info.ts │ │ │ └── type.ts │ │ └── app │ │ │ └── info.ts │ ├── db.json │ ├── entity │ │ ├── info.ts │ │ └── type.ts │ └── service │ │ ├── info.ts │ │ └── type.ts │ ├── plugin │ ├── config.ts │ ├── controller │ │ └── admin │ │ │ └── info.ts │ ├── entity │ │ └── info.ts │ ├── event │ │ ├── app.ts │ │ └── init.ts │ ├── hooks │ │ ├── base.ts │ │ └── upload │ │ │ ├── index.ts │ │ │ └── interface.ts │ ├── interface.ts │ └── service │ │ ├── center.ts │ │ ├── info.ts │ │ └── types.ts │ ├── recycle │ ├── config.ts │ ├── controller │ │ └── admin │ │ │ └── data.ts │ ├── entity │ │ └── data.ts │ ├── event │ │ └── data.ts │ ├── schedule │ │ └── data.ts │ └── service │ │ └── data.ts │ ├── space │ ├── config.ts │ ├── controller │ │ ├── admin │ │ │ ├── info.ts │ │ │ └── type.ts │ │ └── 说明.md │ ├── entity │ │ ├── info.ts │ │ └── type.ts │ └── service │ │ ├── info.ts │ │ └── type.ts │ ├── swagger │ ├── builder.ts │ ├── config.ts │ ├── controller │ │ └── index.ts │ └── event │ │ └── app.ts │ ├── task │ ├── config.ts │ ├── controller │ │ └── admin │ │ │ └── info.ts │ ├── db.json │ ├── entity │ │ ├── info.ts │ │ └── log.ts │ ├── event │ │ └── comm.ts │ ├── middleware │ │ └── task.ts │ ├── queue │ │ └── task.ts │ └── service │ │ ├── bull.ts │ │ ├── demo.ts │ │ ├── info.ts │ │ └── local.ts │ └── user │ ├── config.ts │ ├── controller │ ├── admin │ │ ├── address.ts │ │ └── info.ts │ └── app │ │ ├── address.ts │ │ ├── comm.ts │ │ ├── info.ts │ │ └── login.ts │ ├── entity │ ├── address.ts │ ├── info.ts │ └── wx.ts │ ├── middleware │ └── app.ts │ └── service │ ├── address.ts │ ├── info.ts │ ├── login.ts │ ├── sms.ts │ └── wx.ts ├── test └── README.md ├── tsconfig.json └── typings ├── plugin.d.ts └── upload.d.ts /.cursor/rules/cache.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 缓存(Cache) 3 | globs: 4 | --- 5 | # 缓存 6 | 7 | 为了方便开发者进行缓存操作的组件,它有利于改善项目的性能。它为我们提供了一个数据中心以便进行高效的数据访问。 8 | 9 | ::: 10 | 11 | ## 使用 12 | 13 | ```ts 14 | import { InjectClient, Provide } from '@midwayjs/core'; 15 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 16 | 17 | @Provide() 18 | export class UserService { 19 | 20 | @InjectClient(CachingFactory, 'default') 21 | cache: MidwayCache; 22 | 23 | async invoke(name: string, value: string) { 24 | // 设置缓存 25 | await this.cache.set(name, value); 26 | // 获取缓存 27 | const data = await this.cache.get(name); 28 | // ... 29 | } 30 | } 31 | ``` 32 | 33 | ## 换成 Redis (v7.1 版本) 34 | 35 | 安装依赖,具体可以查看@midwayjs cache 36 | 37 | ```bash 38 | pnpm i cache-manager-ioredis-yet --save 39 | ``` 40 | 41 | `src/config/config.default.ts` 42 | 43 | ```ts 44 | import { CoolFileConfig, MODETYPE } from "@cool-midway/file"; 45 | import { MidwayConfig } from "@midwayjs/core"; 46 | // redis缓存 47 | import { redisStore } from "cache-manager-ioredis-yet"; 48 | 49 | export default { 50 | // Redis缓存 51 | cacheManager: { 52 | clients: { 53 | default: { 54 | store: redisStore, 55 | options: { 56 | port: 6379, 57 | host: "127.0.0.1", 58 | password: "", 59 | ttl: 0, 60 | db: 0, 61 | }, 62 | }, 63 | }, 64 | }, 65 | } as unknown as MidwayConfig; 66 | ``` 67 | 68 | ## 换成 Redis (以往版本) 69 | 70 | ```bash 71 | pnpm i cache-manager-ioredis --save 72 | ``` 73 | 74 | `src/config/config.default.ts` 75 | 76 | ```ts 77 | import { CoolFileConfig, MODETYPE } from "@cool-midway/file"; 78 | import { MidwayConfig } from "@midwayjs/core"; 79 | // redis缓存 80 | import * as redisStore from "cache-manager-ioredis"; 81 | 82 | export default { 83 | // Redis缓存 84 | cache: { 85 | store: redisStore, 86 | options: { 87 | port: 6379, 88 | host: "127.0.0.1", 89 | password: "", 90 | db: 0, 91 | keyPrefix: "cool:", 92 | ttl: null, 93 | }, 94 | }, 95 | } as unknown as MidwayConfig; 96 | ``` 97 | 98 | ## 使用 99 | 100 | `src/modules/demo/controller/open/cache.ts` 101 | 102 | ```ts 103 | import { DemoCacheService } from "../../service/cache"; 104 | import { Inject, Post, Provide, Get, InjectClient } from "@midwayjs/core"; 105 | import { CoolController, BaseController } from "@cool-midway/core"; 106 | import { CachingFactory, MidwayCache } from "@midwayjs/cache-manager"; 107 | 108 | /** 109 | * 缓存 110 | */ 111 | @Provide() 112 | @CoolController() 113 | export class AppDemoCacheController extends BaseController { 114 | @InjectClient(CachingFactory, "default") 115 | midwayCache: MidwayCache; 116 | 117 | @Inject() 118 | demoCacheService: DemoCacheService; 119 | 120 | /** 121 | * 设置缓存 122 | * @returns 123 | */ 124 | @Post("/set") 125 | async set() { 126 | await this.midwayCache.set("a", 1); 127 | // 缓存10秒 128 | await this.midwayCache.set("a", 1, 10 * 1000); 129 | return this.ok(await this.midwayCache.get("a")); 130 | } 131 | 132 | /** 133 | * 获得缓存 134 | * @returns 135 | */ 136 | @Get("/get") 137 | async get() { 138 | return this.ok(await this.demoCacheService.get()); 139 | } 140 | } 141 | ``` 142 | 143 | ## 方法缓存 144 | 145 | 有些业务场景,我们并不希望每次请求接口都需要操作数据库,如:今日推荐、上个月排行榜等,数据存储在 redis 146 | 147 | 框架提供了 `@CoolCache` 方法装饰器,方法设置缓存,让代码更优雅 148 | 149 | `src/modules/demo/service/cache.ts` 150 | 151 | ```ts 152 | import { Provide } from "@midwayjs/core"; 153 | import { CoolCache } from "@cool-midway/core"; 154 | 155 | /** 156 | * 缓存 157 | */ 158 | @Provide() 159 | export class DemoCacheService { 160 | // 数据缓存5秒 161 | @CoolCache(5000) 162 | async get() { 163 | console.log("执行方法"); 164 | return { 165 | a: 1, 166 | b: 2, 167 | }; 168 | } 169 | } 170 | ``` 171 | 172 | ::: warning 173 | service 主要是处理业务逻辑,`@CoolCache`应该要在 service 中使用,不要在 controller 等其他位置使用 174 | ::: 175 | -------------------------------------------------------------------------------- /.cursor/rules/event.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 事件(Event) 3 | globs: 4 | --- 5 | # 事件(Event) 6 | 7 | 事件是开发过程中经常使用到的功能,我们经常利用它来做一些解耦的操作。如:更新了用户信息,其他需要更新相关信息的操作自行监听更新等 8 | 9 | ## 新建监听 10 | 11 | ```ts 12 | import { Provide, Scope, ScopeEnum } from "@midwayjs/core"; 13 | import { CoolEvent, Event } from "@cool-midway/core"; 14 | 15 | /** 16 | * 接收事件 17 | */ 18 | @CoolEvent() 19 | export class DemoEvent { 20 | /** 21 | * 根据事件名接收事件 22 | * @param msg 23 | * @param a 24 | */ 25 | @Event("updateUser") 26 | async updateUser(msg, a) { 27 | console.log("ImEvent", "updateUser", msg, a); 28 | } 29 | } 30 | ``` 31 | 32 | ## 发送事件 33 | 34 | ```ts 35 | import { Get, Inject, Provide } from "@midwayjs/core"; 36 | import { 37 | CoolController, 38 | BaseController, 39 | CoolEventManager, 40 | } from "@cool-midway/core"; 41 | 42 | /** 43 | * 事件 44 | */ 45 | @CoolController() 46 | export class DemoEventController extends BaseController { 47 | @Inject() 48 | coolEventManager: CoolEventManager; 49 | 50 | /** 51 | * 发送事件 52 | */ 53 | @Get("/send") 54 | public async send() { 55 | this.coolEventManager.emit("updateUser", { a: 1 }, 12); 56 | } 57 | } 58 | ``` 59 | 60 | ## 多进程通信 61 | 62 | 当你的项目利用如`pm2`等工具部署为 cluster 模式的时候,你的项目会有多个进程,这时候你的事件监听和发送只会在当前进程内有效,如果你需要触发到所有或者随机一个进程,需要使用多进程通信,这里我们提供了一个简单的方式来实现多进程通信。 63 | 64 | 需要根据你的业务需求来使用该功能!!! 65 | 66 | ```ts 67 | import { Get, Inject, Provide } from "@midwayjs/core"; 68 | import { 69 | CoolController, 70 | BaseController, 71 | CoolEventManager, 72 | } from "@cool-midway/core"; 73 | 74 | /** 75 | * 事件 76 | */ 77 | @Provide() 78 | @CoolController() 79 | export class DemoEventController extends BaseController { 80 | @Inject() 81 | coolEventManager: CoolEventManager; 82 | 83 | @Post("/global", { summary: "全局事件,多进程都有效" }) 84 | async global() { 85 | await this.coolEventManager.globalEmit("demo", false, { a: 2 }, 1); 86 | return this.ok(); 87 | } 88 | } 89 | ``` 90 | 91 | **globalEmit** 92 | 93 | ```ts 94 | /** 95 | * 发送全局事件 96 | * @param event 事件 97 | * @param random 是否随机一个 98 | * @param args 参数 99 | * @returns 100 | */ 101 | globalEmit(event: string, random?: boolean, ...args: any[]) 102 | ``` 103 | -------------------------------------------------------------------------------- /.cursor/rules/exception.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 异常处理(Exception) 3 | globs: 4 | --- 5 | # 异常处理 6 | 7 | 框架自带有: `CoolCommException` 8 | 9 | ## 通用异常 10 | 11 | CoolCommException 12 | 13 | 返回码: 1001 14 | 15 | 返回消息:comm fail 16 | 17 | 用法: 18 | 19 | ```ts 20 | // 可以自定义返回消息 21 | throw new CoolCommException('用户不存在~'); 22 | ``` -------------------------------------------------------------------------------- /.cursor/rules/socket.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 即时通讯(Socket) 3 | globs: 4 | --- 5 | # 即时通讯(Socket) 6 | 7 | `cool-admin`即时通讯功能基于[Socket.io(v4)](https://socket.io/docs/v4)开发,[midwayjs 官方 Socket.io 文档](http://midwayjs.org/docs/extensions/socketio) 8 | 9 | ## 配置 10 | 11 | `configuration.ts` 12 | 13 | ```ts 14 | import * as socketio from "@midwayjs/socketio"; 15 | 16 | @Configuration({ 17 | imports: [ 18 | // socketio http://www.midwayjs.org/docs/extensions/socketio 19 | socketio, 20 | ], 21 | importConfigs: [join(__dirname, "./config")], 22 | }) 23 | export class ContainerLifeCycle { 24 | @App() 25 | app: koa.Application; 26 | 27 | async onReady() {} 28 | } 29 | ``` 30 | 31 | ## 配置`config/config.default.ts` 32 | 33 | 需要配置 redis 适配器,让进程之间能够进行通讯 34 | 35 | ```ts 36 | import { CoolConfig, MODETYPE } from "@cool-midway/core"; 37 | import { MidwayConfig } from "@midwayjs/core"; 38 | import * as fsStore from "@cool-midway/cache-manager-fs-hash"; 39 | import { createAdapter } from "@socket.io/redis-adapter"; 40 | // @ts-ignore 41 | import Redis from "ioredis"; 42 | 43 | const redis = { 44 | host: "127.0.0.1", 45 | port: 6379, 46 | password: "", 47 | db: 0, 48 | }; 49 | 50 | const pubClient = new Redis(redis); 51 | const subClient = pubClient.duplicate(); 52 | 53 | export default { 54 | // ... 55 | // socketio 56 | socketIO: { 57 | upgrades: ["websocket"], // 可升级的协议 58 | adapter: createAdapter(pubClient, subClient), 59 | }, 60 | } as MidwayConfig; 61 | ``` 62 | 63 | ## 服务端 64 | 65 | ```ts 66 | import { 67 | WSController, 68 | OnWSConnection, 69 | Inject, 70 | OnWSMessage, 71 | } from "@midwayjs/core"; 72 | import { Context } from "@midwayjs/socketio"; 73 | /** 74 | * 测试 75 | */ 76 | @WSController("/") 77 | export class HelloController { 78 | @Inject() 79 | ctx: Context; 80 | 81 | // 客户端连接 82 | @OnWSConnection() 83 | async onConnectionMethod() { 84 | console.log("on client connect", this.ctx.id); 85 | console.log("参数", this.ctx.handshake.query); 86 | this.ctx.emit("data", "连接成功"); 87 | } 88 | 89 | // 消息事件 90 | @OnWSMessage("myEvent") 91 | async gotMessage(data) { 92 | console.log("on data got", this.ctx.id, data); 93 | } 94 | } 95 | ``` 96 | 97 | ## 客户端 98 | 99 | ```ts 100 | const io = require("socket.io-client"); 101 | 102 | const socket = io("http://127.0.0.1:8001", { 103 | auth: { 104 | token: "xxx", 105 | }, 106 | }); 107 | 108 | socket.on("data", (msg) => { 109 | console.log("服务端消息", msg); 110 | }); 111 | ``` 112 | 113 | ## 注意事项 114 | 115 | 如果部署为多线程的,为了让进程之间能够进行通讯,需要配置 redis 适配器,[配置方式](http://midwayjs.org/docs/extensions/socketio#%E9%85%8D%E7%BD%AE-redis-%E9%80%82%E9%85%8D%E5%99%A8) 116 | 117 | ```ts 118 | // src/config/config.default 119 | import { createRedisAdapter } from "@midwayjs/socketio"; 120 | 121 | export default { 122 | // ... 123 | socketIO: { 124 | adapter: createRedisAdapter({ host: "127.0.0.1", port: 6379 }), 125 | }, 126 | }; 127 | ``` 128 | -------------------------------------------------------------------------------- /.cursor/rules/tenant.mdc: -------------------------------------------------------------------------------- 1 | --- 2 | description: 多租户(Tenant) 3 | globs: 4 | --- 5 | # 多租户(v8.0新增) 6 | 7 | 多租户(Multi-tenancy)是一种软件架构模式,允许单个应用实例服务多个租户(客户组织)。每个租户的数据是相互隔离的,但共享同一个应用程序代码和基础设施。 8 | 9 | 10 | ## 主要特点 11 | 12 | - **数据隔离**: 确保不同租户之间的数据严格分离,互不可见 13 | - **资源共享**: 多个租户共享同一套应用程序代码和基础设施 14 | - **独立配置**: 每个租户可以有自己的个性化配置和定制化需求 15 | - **成本优化**: 通过资源共享降低运营和维护成本 16 | 17 | ## 实现 18 | 19 | ### 1、数据隔离 20 | 21 | 多租户的数据隔离有许多种方案,但最为常见的是以列进行隔离的方式。Cool Admin 通过在`BaseEntity`中加入指定的列(租户ID `tenantId`)对数据进行隔离。 22 | 23 | ::: tip 小贴士 24 | 25 | v8.0之后,`BaseEntity`已经从`cool-midway/core`中移动至`src/modules/base/entity/base.ts`,方便开发者扩展定制 26 | 27 | ::: 28 | 29 | 30 | `src/modules/base/entity/base.ts` 31 | ```ts 32 | import { 33 | Index, 34 | UpdateDateColumn, 35 | CreateDateColumn, 36 | PrimaryGeneratedColumn, 37 | Column, 38 | } from 'typeorm'; 39 | import { CoolBaseEntity } from '@cool-midway/core'; 40 | 41 | /** 42 | * 实体基类 43 | */ 44 | export abstract class BaseEntity extends CoolBaseEntity { 45 | // 默认自增 46 | @PrimaryGeneratedColumn('increment', { 47 | comment: 'ID', 48 | }) 49 | id: number; 50 | 51 | @Index() 52 | @CreateDateColumn({ comment: '创建时间' }) 53 | createTime: Date; 54 | 55 | @Index() 56 | @UpdateDateColumn({ comment: '更新时间' }) 57 | updateTime: Date; 58 | 59 | @Index() 60 | @Column({ comment: '租户ID', nullable: true }) 61 | tenantId: number; 62 | } 63 | 64 | ``` 65 | 66 | ### 2、条件注入 67 | 68 | Cool 改造了 `typeorm`的 `Subscriber`,新增了以下四种监听: 69 | 70 | ```ts 71 | /** 72 | * 当进行select的QueryBuilder构建之后触发 73 | */ 74 | afterSelectQueryBuilder?(queryBuilder: SelectQueryBuilder): void; 75 | 76 | /** 77 | * 当进行insert的QueryBuilder构建之后触发 78 | */ 79 | afterInsertQueryBuilder?(queryBuilder: InsertQueryBuilder): void; 80 | 81 | /** 82 | * 当进行update的QueryBuilder构建之后触发 83 | */ 84 | afterUpdateQueryBuilder?(queryBuilder: UpdateQueryBuilder): void; 85 | 86 | /** 87 | * 当进行delete的QueryBuilder构建之后触发 88 | */ 89 | afterDeleteQueryBuilder?(queryBuilder: DeleteQueryBuilder): void; 90 | ``` 91 | 92 | 在`src/modules/base/db/tenant.ts`中,通过`tenantId`进行条件注入,从而实现数据隔离。 93 | 94 | ## 使用 95 | 96 | ### 1、开启多租户 97 | 98 | 框架默认关闭多租户,需要手动开启,在`src/config/config.default.ts`中开启多租户 99 | 100 | ```ts 101 | cool: { 102 | // 是否开启多租户 103 | tenant: { 104 | // 是否开启多租户 105 | enable: true, 106 | // 需要过滤多租户的url, 支持通配符,如/admin/**/* 表示admin模块下的所有接口都进行多租户过滤 107 | urls: [], 108 | }, 109 | } 110 | ``` 111 | tenant 112 | ### 2、代码中使用 113 | 114 | 只要开启了多租户,并配置了`urls`,那么框架会自动注入`tenantId`,开发者原本的代码不需要做任何修改,框架会自动进行数据隔离。 115 | 116 | #### Controller 117 | 118 | @CoolController的`add`、`delete`、`update`、`info`、`list`、`page`方法都支持过滤多租户。 119 | 120 | 121 | #### Service 122 | 123 | `Service`中使用多租户,以下是一个完整的示例,包含有效和无效的情况,开发者需要结合实际业务进行选择。 124 | 125 | ```ts 126 | import { Inject, Provide } from '@midwayjs/core'; 127 | import { BaseService } from '@cool-midway/core'; 128 | import { InjectEntityModel } from '@midwayjs/typeorm'; 129 | import { Repository } from 'typeorm'; 130 | import { DemoGoodsEntity } from '../entity/goods'; 131 | import { UserInfoEntity } from '../../user/entity/info'; 132 | import { noTenant } from '../../base/db/tenant'; 133 | 134 | /** 135 | * 商品服务 136 | */ 137 | @Provide() 138 | export class DemoTenantService extends BaseService { 139 | @InjectEntityModel(DemoGoodsEntity) 140 | demoGoodsEntity: Repository; 141 | 142 | @Inject() 143 | ctx; 144 | 145 | /** 146 | * 使用多租户 147 | */ 148 | async use() { 149 | await this.demoGoodsEntity.createQueryBuilder().getMany(); 150 | await this.demoGoodsEntity.find(); 151 | } 152 | 153 | /** 154 | * 不使用多租户(局部不使用) 155 | */ 156 | async noUse() { 157 | // 过滤多租户 158 | await this.demoGoodsEntity.createQueryBuilder().getMany(); 159 | // 被noTenant包裹,不会过滤多租户 160 | await noTenant(this.ctx, async () => { 161 | return await this.demoGoodsEntity.createQueryBuilder().getMany(); 162 | }); 163 | // 过滤多租户 164 | await this.demoGoodsEntity.find(); 165 | } 166 | 167 | /** 168 | * 无效多租户 169 | */ 170 | async invalid() { 171 | // 自定义sql,不进行多租户过滤 172 | await this.nativeQuery('select * from demo_goods'); 173 | // 自定义分页sql,进行多租户过滤 174 | await this.sqlRenderPage('select * from demo_goods', {}); 175 | } 176 | } 177 | 178 | ``` -------------------------------------------------------------------------------- /.cursorrules: -------------------------------------------------------------------------------- 1 | # 项目背景 2 | - 数据库:MySQL、Sqlite、Postgres、Typeorm(0.3.20版本, 不使用外键方式,如@ManyToOne、@OneToMany等) 3 | - 语言:TypeScript、JavaScript、CommonJS 4 | - 框架:Koa.js、midway.js、cool-admin-midway 5 | - 项目版本:8.x 6 | 7 | # 目录 8 | 项目目录: 9 | ├── .vscode(代码片段,根据关键字可以快速地生成代码) 10 | ├── public(静态资源文件,如js、css或者上传的文件) 11 | ├── src 12 | │ └── comm(通用库) 13 | │ └── modules(项目模块) 14 | │ └── config 15 | │ │ └── config.default.ts(默认配置,不区分环境,都生效) 16 | │ │ └── config.local.ts(本地开发配置,对应npm run dev) 17 | │ │ └── config.prod.ts(生产环境配置,对应npm run start) 18 | │ └── configuration.ts(midway的配置文件) 19 | │ └── welcome.ts(环境的controller) 20 | │ └── interface.ts(类型声明) 21 | ├── package.json(依赖管理,项目信息) 22 | ├── bootstrap.js(生产环境启动入口文件,可借助pm2等工具多进程启动) 23 | └── ... 24 | 25 | 模块目录 26 | ├── modules 27 | │ └── base(基础的权限管理系统) 28 | │ │ └── controller(api接口) 29 | │ │ └── dto(参数校验) 30 | │ │ └── entity(实体类) 31 | │ │ └── middleware(中间件) 32 | │ │ └── schedule(定时任务) 33 | │ │ └── service(服务,写业务逻辑) 34 | │ │ └── config.ts(必须,模块的配置) 35 | │ │ └── db.json(可选,初始化该模块的数据) 36 | │ │ └── menu.json(可选,初始化该模块的菜单) 37 | 38 | # 其它 39 | - 始终使用中文回复,包括代码注释等 40 | - `@midwayjs/decorator`,已弃用,使用`@midwayjs/core` 41 | - 不要使用自定义sql来操作数据库,而是使用typeorm的api,统计相关的可以考虑使用原生sql 42 | - Controller中不允许重写`add`、`delete`、`update`、`info`、`list`、`page`方法 43 | - Controller不需要加@Provide()注解 44 | - page接口关联表查询一般写在Controller的pageQueryOp中,尽量不要使用自定义sql 45 | - Entity字段使用驼峰命名,如:studentNo 46 | - Entity不允许使用@ManyToOne、@OneToMany等外键关系 47 | - Entity的BaseEntity引用固定为:`import { BaseEntity } from '../../base/entity/base';`,禁止修改层级 48 | - 创建api接口时,不要多层级如:`/student/detail`,改为`/studentDetail`,用驼峰法; 49 | - 本项目是版本8.x,所有代码都需要按照新的写法进行编写,如Entity字典的配置 50 | - 文件的命名不要使用驼峰法,而是使用下划线法,如:student_info.entity.ts,另外禁止太啰嗦,比如:student模块下的学生信息,不要写成:student_info, 而是写成info.ts,班级信息:class.ts,不要写成student_class.ts 51 | - 创建模块代码需要读取.cursor/rules的module.mdc、controller.mdc、service.mdc、db.mdc,其它的rules根据需要进行参考 -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # 🎨 editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | indent_style = space 9 | indent_size = 2 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/mwts/", 3 | "ignorePatterns": [ 4 | "node_modules", 5 | "dist", 6 | "test", 7 | "jest.config.js", 8 | "typings", 9 | "public/**/**", 10 | "view/**/**", 11 | "packages" 12 | ], 13 | "env": { 14 | "jest": true 15 | }, 16 | "rules": { 17 | "@typescript-eslint/explicit-module-boundary-types": "off", 18 | "@typescript-eslint/no-unused-vars": "off", 19 | "@typescript-eslint/ban-ts-comment": "off", 20 | "node/no-extraneous-import": "off", 21 | "no-empty": "off", 22 | "node/no-extraneous-require": "off", 23 | "node/no-unpublished-import": "off", 24 | "eqeqeq": "off", 25 | "node/no-unsupported-features/node-builtins": "off", 26 | "@typescript-eslint/ban-types": "off", 27 | "no-control-regex": "off", 28 | "prefer-const": "off" 29 | } 30 | } -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.js text eol=lf 2 | *.json text eol=lf 3 | *.ts text eol=lf 4 | *.code-snippets text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | logs/ 2 | cache/ 3 | npm-debug.log 4 | yarn-error.log 5 | node_modules/ 6 | package-lock.json 7 | yarn.lock 8 | coverage/ 9 | dist/ 10 | .idea/ 11 | run/ 12 | build/ 13 | .DS_Store 14 | launch.json 15 | *.sw* 16 | *.un~ 17 | .tsbuildinfo 18 | .tsbuildinfo.* 19 | data/* 20 | pnpm-lock.yaml 21 | public/uploads/* 22 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | ...require('mwts/.prettierrc.json') 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/config.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "config": { 3 | "prefix": "config", 4 | "body": [ 5 | "import { ModuleConfig } from '@cool-midway/core';", 6 | "", 7 | "/**", 8 | " * 模块配置", 9 | " */", 10 | "export default () => {", 11 | " return {", 12 | " // 模块名称", 13 | " name: 'xxx',", 14 | " // 模块描述", 15 | " description: 'xxx',", 16 | " // 中间件,只对本模块有效", 17 | " middlewares: [],", 18 | " // 中间件,全局有效", 19 | " globalMiddlewares: [],", 20 | " // 模块加载顺序,默认为0,值越大越优先加载", 21 | " order: 0,", 22 | " } as ModuleConfig;", 23 | "};", 24 | "" 25 | ], 26 | "description": "cool-admin config代码片段" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.vscode/controller.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "controller": { 3 | "prefix": "controller", 4 | "body": [ 5 | "import { CoolController, BaseController } from '@cool-midway/core';", 6 | "", 7 | "/**", 8 | " * 描述", 9 | " */", 10 | "@CoolController({", 11 | " api: ['add', 'delete', 'update', 'info', 'list', 'page'],", 12 | " entity: 实体,", 13 | "})", 14 | "export class XxxController extends BaseController {}", 15 | "" 16 | ], 17 | "description": "cool-admin controller代码片段" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.vscode/entity.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "entity": { 3 | "prefix": "entity", 4 | "body": [ 5 | "import { BaseEntity } from '../../base/entity/base';", 6 | "import { Column, Entity } from 'typeorm';", 7 | "", 8 | "/**", 9 | " * 描述", 10 | " */", 11 | "@Entity('xxx_xxx_xxx')", 12 | "export class XxxEntity extends BaseEntity {", 13 | " @Column({ comment: '描述' })", 14 | " xxx: string;", 15 | "}", 16 | "" 17 | ], 18 | "description": "cool-admin entity代码片段" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /.vscode/event.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "event": { 3 | "prefix": "event", 4 | "body": [ 5 | "import { CoolEvent, Event } from '@cool-midway/core';", 6 | "", 7 | "/**", 8 | " * 接收事件", 9 | " */", 10 | "@CoolEvent()", 11 | "export class xxxEvent {", 12 | " @Event('updateUser')", 13 | " async updateUser(msg, a) {", 14 | " console.log('ImEvent', 'updateUser', msg, a);", 15 | " }", 16 | "}", 17 | "" 18 | ], 19 | "description": "cool-admin event代码片段" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [{ 4 | "name": "Cool Admin", 5 | "type": "node", 6 | "request": "launch", 7 | "cwd": "${workspaceRoot}", 8 | "runtimeExecutable": "npm", 9 | "windows": { 10 | "runtimeExecutable": "npm.cmd" 11 | }, 12 | "runtimeArgs": [ 13 | "run", 14 | "dev" 15 | ], 16 | "env": { 17 | "NODE_ENV": "local" 18 | }, 19 | "console": "integratedTerminal", 20 | "restart": true, 21 | "autoAttachChildProcesses": true 22 | }] 23 | } 24 | -------------------------------------------------------------------------------- /.vscode/middleware.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "middleware": { 3 | "prefix": "middleware", 4 | "body": [ 5 | "import { Middleware } from '@midwayjs/core';", 6 | "import { NextFunction, Context } from '@midwayjs/koa';", 7 | "import { IMiddleware } from '@midwayjs/core';", 8 | "", 9 | "/**", 10 | " * 描述", 11 | " */", 12 | "@Middleware()", 13 | "export class XxxMiddleware implements IMiddleware {", 14 | " resolve() {", 15 | " return async (ctx: Context, next: NextFunction) => {", 16 | " // 控制器前执行的逻辑", 17 | " const startTime = Date.now();", 18 | " // 执行下一个 Web 中间件,最后执行到控制器", 19 | " await next();", 20 | " // 控制器之后执行的逻辑", 21 | " console.log(Date.now() - startTime);", 22 | " };", 23 | " }", 24 | "}", 25 | "" 26 | ], 27 | "description": "cool-admin middleware代码片段" 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.vscode/queue.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "queue": { 3 | "prefix": "queue", 4 | "body": [ 5 | "import { BaseCoolQueue, CoolQueue } from '@cool-midway/task';", 6 | "", 7 | "/**", 8 | " * 队列", 9 | " */", 10 | "@CoolQueue()", 11 | "export abstract class xxxQueue extends BaseCoolQueue {", 12 | " async data(job: any, done: any) {", 13 | " console.log('收到的数据', job.data);", 14 | " done();", 15 | " }", 16 | "}", 17 | "" 18 | ], 19 | "description": "cool-admin service代码片段" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /.vscode/service.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "service": { 3 | "prefix": "service", 4 | "body": [ 5 | "import { Init, Provide } from '@midwayjs/core';", 6 | "import { BaseService } from '@cool-midway/core';", 7 | "import { InjectEntityModel } from '@midwayjs/typeorm';", 8 | "import { Repository } from 'typeorm';", 9 | "", 10 | "/**", 11 | " * 描述", 12 | " */", 13 | "@Provide()", 14 | "export class XxxService extends BaseService {", 15 | " @InjectEntityModel(实体)", 16 | " xxxEntity: Repository<实体>;", 17 | "" 18 | " @Init()" 19 | " async init() {", 20 | " await super.init();", 21 | " this.setEntity(this.xxxEntity);", 22 | " }", 23 | "", 24 | " /**", 25 | " * 描述", 26 | " */", 27 | " async xxx() {}", 28 | "}", 29 | "" 30 | ], 31 | "description": "cool-admin service代码片段" 32 | } 33 | } -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM node:lts-alpine 3 | 4 | WORKDIR /app 5 | 6 | # 配置alpine国内镜像加速 7 | RUN sed -i "s@http://dl-cdn.alpinelinux.org/@https://repo.huaweicloud.com/@g" /etc/apk/repositories 8 | 9 | # 安装tzdata,默认的alpine基础镜像不包含时区组件,安装后可通过TZ环境变量配置时区 10 | RUN apk add --no-cache tzdata 11 | 12 | # 设置时区为中国东八区,这里的配置可以被docker-compose.yml或docker run时指定的时区覆盖 13 | ENV TZ="Asia/Shanghai" 14 | 15 | # 如果各公司有自己的私有源,可以替换registry地址,如使用官方源注释下一行 16 | RUN npm config set registry https://registry.npmmirror.com 17 | 18 | # 复制package.json 19 | COPY package.json ./package.json 20 | # 安装依赖 21 | RUN npm install 22 | # 构建项目 23 | COPY . . 24 | RUN npm run build 25 | # 删除开发期依赖 26 | RUN rm -rf node_modules && rm package-lock.json 27 | # 安装生产环境依赖 28 | RUN npm install 29 | 30 | # 如果端口更换,这边可以更新一下 31 | EXPOSE 8001 32 | 33 | CMD ["npm", "run", "start"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2025] [厦门闪酷科技开发有限公司] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | --- 24 | 25 | MIT 许可证 26 | 27 | 版权所有 (c) [2025] [厦门闪酷科技开发有限公司] 28 | 29 | 特此免费授予获得本软件及相关文档文件(“软件”)副本的任何人无限制地处理本软件的权限,包括但不限于使用、复制、修改、合并、发布、分发、再许可和/或销售软件的副本,并允许软件提供给其的人员这样做,但须符合以下条件: 30 | 31 | 上述版权声明和本许可声明应包含在软件的所有副本或主要部分中。 32 | 33 | 本软件按“原样”提供,不提供任何明示或暗示的担保,包括但不限于对适销性、特定用途适用性和非侵权的担保。在任何情况下,作者或版权持有人均不对因软件或软件使用或其他交易而产生的任何索赔、损害或其他责任负责,无论是在合同诉讼、侵权诉讼或其他诉讼中。 -------------------------------------------------------------------------------- /bootstrap.js: -------------------------------------------------------------------------------- 1 | const { Bootstrap } = require('@midwayjs/bootstrap'); 2 | 3 | // 显式以组件方式引入用户代码 4 | Bootstrap.configure({ 5 | // 这里引用的是编译后的入口,本地开发不走这个文件 6 | // eslint-disable-next-line node/no-unpublished-require 7 | imports: require('./dist/index'), 8 | // 禁用依赖注入的目录扫描 9 | moduleDetector: false, 10 | }).run(); 11 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | # 本地数据库环境 2 | # 数据存放在当前目录下的 data里 3 | # 推荐使用安装了docker扩展的vscode打开目录 在本文件上右键可以快速启动,停止 4 | # 如不需要相关容器开机自启动,可注释掉 restart: always 5 | # 如遇端口冲突 可调整ports下 :前面的端口号 6 | version: "3.1" 7 | 8 | services: 9 | coolDB: 10 | image: mysql 11 | command: 12 | --default-authentication-plugin=mysql_native_password 13 | --sql_mode=STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION 14 | --group_concat_max_len=102400 15 | restart: always 16 | volumes: 17 | - ./data/mysql/:/var/lib/mysql/ 18 | environment: 19 | TZ: Asia/Shanghai # 指定时区 20 | MYSQL_ROOT_PASSWORD: "123456" # 配置root用户密码 21 | MYSQL_DATABASE: "cool" # 业务库名 22 | MYSQL_USER: "root" # 业务库用户名 23 | MYSQL_PASSWORD: "123456" # 业务库密码 24 | networks: 25 | - cool 26 | ports: 27 | - 3306:3306 28 | 29 | coolRedis: 30 | image: redis 31 | #command: --requirepass "12345678" # redis库密码,不需要密码注释本行 32 | restart: always 33 | environment: 34 | TZ: Asia/Shanghai # 指定时区 35 | volumes: 36 | - ./data/redis/:/data/ 37 | networks: 38 | - cool 39 | ports: 40 | - 6379:6379 41 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | testPathIgnorePatterns: ['/test/fixtures'], 5 | coveragePathIgnorePatterns: ['/test/'], 6 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cool-admin", 3 | "version": "8.0.0", 4 | "description": "一个很酷的Ai快速开发框架", 5 | "private": true, 6 | "dependencies": { 7 | "@cool-midway/core": "8.0.4", 8 | "@cool-midway/rpc": "8.0.1", 9 | "@cool-midway/task": "8.0.2", 10 | "@midwayjs/bootstrap": "^3.20.3", 11 | "@midwayjs/cache-manager": "^3.20.3", 12 | "@midwayjs/core": "^3.20.3", 13 | "@midwayjs/cron": "^3.20.3", 14 | "@midwayjs/cross-domain": "^3.20.3", 15 | "@midwayjs/info": "^3.20.3", 16 | "@midwayjs/koa": "^3.20.3", 17 | "@midwayjs/logger": "^3.4.2", 18 | "@midwayjs/static-file": "^3.20.3", 19 | "@midwayjs/typeorm": "^3.20.3", 20 | "@midwayjs/upload": "^3.20.3", 21 | "@midwayjs/validate": "^3.20.3", 22 | "adm-zip": "^0.5.16", 23 | "axios": "^1.8.4", 24 | "cron": "^4.1.3", 25 | "download": "^8.0.0", 26 | "jsonwebtoken": "^9.0.2", 27 | "lodash": "^4.17.21", 28 | "md5": "^2.3.0", 29 | "moment": "^2.30.1", 30 | "mysql2": "^3.14.0", 31 | "svg-captcha": "^1.4.0", 32 | "tslib": "^2.8.1", 33 | "typeorm": "npm:@cool-midway/typeorm@0.3.20", 34 | "uuid": "^11.1.0", 35 | "ws": "^8.18.1" 36 | }, 37 | "devDependencies": { 38 | "@midwayjs/bundle-helper": "^1.3.0", 39 | "@midwayjs/mock": "^3.20.3", 40 | "@types/jest": "^29.5.14", 41 | "@types/node": "22", 42 | "@yao-pkg/pkg": "^6.3.2", 43 | "cross-env": "^7.0.3", 44 | "jest": "^29.7.0", 45 | "mwts": "^1.3.0", 46 | "mwtsc": "^1.15.1", 47 | "rimraf": "^6.0.1", 48 | "ts-jest": "^29.3.0", 49 | "typescript": "~5.8.2" 50 | }, 51 | "engines": { 52 | "node": ">=18.0.0" 53 | }, 54 | "scripts": { 55 | "start": "NODE_ENV=production node ./bootstrap.js", 56 | "dev": "rimraf src/index.ts && cool check && cross-env NODE_ENV=local mwtsc --cleanOutDir --watch --run @midwayjs/mock/app.js --keepalive", 57 | "test": "cross-env NODE_ENV=unittest jest", 58 | "cov": "jest --coverage", 59 | "lint": "mwts check", 60 | "lint:fix": "mwts fix", 61 | "ci": "npm run cov", 62 | "build": "cool entity && bundle && mwtsc --cleanOutDir", 63 | "build:obfuscate": "cool entity && bundle && mwtsc --cleanOutDir && cool obfuscate", 64 | "pkg": "rimraf build && mkdir build && npm run build && pkg . -d > build/pkg.log", 65 | "pm2:start": "pm2 start ./bootstrap.js -i 1 --name cool-admin", 66 | "pm2:stop": "pm2 stop cool-admin & pm2 delete cool-admin" 67 | }, 68 | "bin": "./bootstrap.js", 69 | "pkg": { 70 | "scripts": [ 71 | "dist/**/*", 72 | "node_modules/axios/dist/node/*" 73 | ], 74 | "assets": [ 75 | "public/**/*", 76 | "typings/**/*", 77 | "src/locales/**/*" 78 | ], 79 | "targets": [ 80 | "node20-win-x64" 81 | ], 82 | "outputPath": "build" 83 | }, 84 | "repository": { 85 | "type": "git", 86 | "url": "https://cool-js.com" 87 | }, 88 | "author": "COOL", 89 | "license": "MIT" 90 | } 91 | -------------------------------------------------------------------------------- /public/Thumbs.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cool-team-official/cool-admin-midway/dd12136386d62e719430405c5a9830e2959385f8/public/Thumbs.db -------------------------------------------------------------------------------- /public/css/welcome.css: -------------------------------------------------------------------------------- 1 | body { 2 | display: flex; 3 | min-height: 100vh; 4 | margin: 0; 5 | justify-content: center; 6 | align-items: center; 7 | text-align: center; 8 | background: #222; 9 | overflow-y: hidden; 10 | } 11 | 12 | @keyframes fadeIn { 13 | from { 14 | opacity: 0; 15 | } 16 | to { 17 | opacity: 1; 18 | } 19 | } 20 | 21 | .footer-bar { 22 | position: fixed; 23 | bottom: 0; 24 | left: 0; 25 | right: 0; 26 | color: #6ee1f5; 27 | padding: 10px 0 20px 0; 28 | text-align: center; 29 | opacity: 0; 30 | animation: fadeIn 5s forwards; 31 | background: #222; 32 | } 33 | 34 | .link { 35 | color: #6ee1f5; 36 | } 37 | 38 | .reveal { 39 | position: relative; 40 | display: flex; 41 | color: #6ee1f5; 42 | font-size: 2em; 43 | font-family: Raleway, sans-serif; 44 | letter-spacing: 3px; 45 | text-transform: uppercase; 46 | white-space: pre; 47 | } 48 | .reveal span { 49 | opacity: 0; 50 | transform: scale(0); 51 | animation: fadeIn 2.4s forwards; 52 | } 53 | .reveal::before, .reveal::after { 54 | position: absolute; 55 | content: ""; 56 | top: 0; 57 | bottom: 0; 58 | width: 2px; 59 | height: 100%; 60 | background: white; 61 | opacity: 0; 62 | transform: scale(0); 63 | } 64 | .reveal::before { 65 | left: 50%; 66 | animation: slideLeft 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards; 67 | } 68 | .reveal::after { 69 | right: 50%; 70 | animation: slideRight 1.5s cubic-bezier(0.7, -0.6, 0.3, 1.5) forwards; 71 | } 72 | 73 | @keyframes fadeIn { 74 | to { 75 | opacity: 1; 76 | transform: scale(1); 77 | } 78 | } 79 | @keyframes slideLeft { 80 | to { 81 | left: -6%; 82 | opacity: 1; 83 | transform: scale(0.9); 84 | } 85 | } 86 | @keyframes slideRight { 87 | to { 88 | right: -6%; 89 | opacity: 1; 90 | transform: scale(0.9); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cool-team-official/cool-admin-midway/dd12136386d62e719430405c5a9830e2959385f8/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | COOL-AMIND 一个很酷的后台权限管理系统 9 | 10 | 11 | 12 | 13 | 14 | 15 |
HELLO COOL-ADMIN AI快速开发框架
16 | 17 | 18 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /public/js/welcome.js: -------------------------------------------------------------------------------- 1 | const duration = 0.8; 2 | const delay = 0.3; 3 | // eslint-disable-next-line no-undef 4 | const revealText = document.querySelector('.reveal'); 5 | const letters = revealText.textContent.split(''); 6 | revealText.textContent = ''; 7 | const middle = letters.filter(e => e !== ' ').length / 2; 8 | letters.forEach((letter, i) => { 9 | // eslint-disable-next-line no-undef 10 | const span = document.createElement('span'); 11 | span.textContent = letter; 12 | span.style.animationDelay = `${delay + Math.abs(i - middle) * 0.1}s`; 13 | revealText.append(span); 14 | }); 15 | -------------------------------------------------------------------------------- /public/swagger/NOTICE: -------------------------------------------------------------------------------- 1 | swagger-ui 2 | Copyright 2020-2021 SmartBear Software Inc. 3 | -------------------------------------------------------------------------------- /public/swagger/README.md: -------------------------------------------------------------------------------- 1 | # Swagger UI Dist 2 | [![NPM version](https://badge.fury.io/js/swagger-ui-dist.svg)](http://badge.fury.io/js/swagger-ui-dist) 3 | 4 | # API 5 | 6 | This module, `swagger-ui-dist`, exposes Swagger-UI's entire dist folder as a dependency-free npm module. 7 | Use `swagger-ui` instead, if you'd like to have npm install dependencies for you. 8 | 9 | `SwaggerUIBundle` and `SwaggerUIStandalonePreset` can be imported: 10 | ```javascript 11 | import { SwaggerUIBundle, SwaggerUIStandalonePreset } from "swagger-ui-dist" 12 | ``` 13 | 14 | To get an absolute path to this directory for static file serving, use the exported `getAbsoluteFSPath` method: 15 | 16 | ```javascript 17 | const swaggerUiAssetPath = require("swagger-ui-dist").getAbsoluteFSPath() 18 | 19 | // then instantiate server that serves files from the swaggerUiAssetPath 20 | ``` 21 | 22 | For anything else, check the [Swagger-UI](https://github.com/swagger-api/swagger-ui) repository. 23 | -------------------------------------------------------------------------------- /public/swagger/absolute-path.js: -------------------------------------------------------------------------------- 1 | /* 2 | * getAbsoluteFSPath 3 | * @return {string} When run in NodeJS env, returns the absolute path to the current directory 4 | * When run outside of NodeJS, will return an error message 5 | */ 6 | const getAbsoluteFSPath = function () { 7 | // detect whether we are running in a browser or nodejs 8 | if (typeof module !== "undefined" && module.exports) { 9 | return require("path").resolve(__dirname) 10 | } 11 | throw new Error('getAbsoluteFSPath can only be called within a Nodejs environment'); 12 | } 13 | 14 | module.exports = getAbsoluteFSPath 15 | -------------------------------------------------------------------------------- /public/swagger/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cool-team-official/cool-admin-midway/dd12136386d62e719430405c5a9830e2959385f8/public/swagger/favicon-16x16.png -------------------------------------------------------------------------------- /public/swagger/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cool-team-official/cool-admin-midway/dd12136386d62e719430405c5a9830e2959385f8/public/swagger/favicon-32x32.png -------------------------------------------------------------------------------- /public/swagger/index.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | overflow: -moz-scrollbars-vertical; 4 | overflow-y: scroll; 5 | } 6 | 7 | *, 8 | *:before, 9 | *:after { 10 | box-sizing: inherit; 11 | } 12 | 13 | body { 14 | margin: 0; 15 | background: #fafafa; 16 | } 17 | -------------------------------------------------------------------------------- /public/swagger/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Swagger UI 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /public/swagger/index.js: -------------------------------------------------------------------------------- 1 | try { 2 | module.exports.SwaggerUIBundle = require("./swagger-ui-bundle.js") 3 | module.exports.SwaggerUIStandalonePreset = require("./swagger-ui-standalone-preset.js") 4 | } catch(e) { 5 | // swallow the error if there's a problem loading the assets. 6 | // allows this module to support providing the assets for browserish contexts, 7 | // without exploding in a Node context. 8 | // 9 | // see https://github.com/swagger-api/swagger-ui/issues/3291#issuecomment-311195388 10 | // for more information. 11 | } 12 | 13 | // `absolutePath` and `getAbsoluteFSPath` are both here because at one point, 14 | // we documented having one and actually implemented the other. 15 | // They were both retained so we don't break anyone's code. 16 | module.exports.absolutePath = require("./absolute-path.js") 17 | module.exports.getAbsoluteFSPath = require("./absolute-path.js") 18 | -------------------------------------------------------------------------------- /public/swagger/oauth2-redirect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Swagger UI: OAuth2 Redirect 5 | 6 | 7 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /public/swagger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "swagger-ui-dist", 3 | "version": "5.10.0", 4 | "main": "index.js", 5 | "repository": "git@github.com:swagger-api/swagger-ui.git", 6 | "contributors": [ 7 | "(in alphabetical order)", 8 | "Anna Bodnia ", 9 | "Buu Nguyen ", 10 | "Josh Ponelat ", 11 | "Kyle Shockey ", 12 | "Robert Barnwell ", 13 | "Sahar Jafari " 14 | ], 15 | "license": "Apache-2.0", 16 | "dependencies": {}, 17 | "devDependencies": {} 18 | } 19 | -------------------------------------------------------------------------------- /public/swagger/swagger-initializer.js: -------------------------------------------------------------------------------- 1 | window.onload = function() { 2 | // 3 | 4 | // the following lines will be replaced by docker/configurator, when it runs in a docker-container 5 | window.ui = SwaggerUIBundle({ 6 | url: "/swagger/json", 7 | dom_id: '#swagger-ui', 8 | deepLinking: true, 9 | presets: [ 10 | SwaggerUIBundle.presets.apis, 11 | SwaggerUIStandalonePreset 12 | ], 13 | plugins: [ 14 | SwaggerUIBundle.plugins.DownloadUrl 15 | ], 16 | layout: "StandaloneLayout" 17 | }); 18 | 19 | // 20 | }; 21 | -------------------------------------------------------------------------------- /src/comm/path.ts: -------------------------------------------------------------------------------- 1 | import * as path from 'path'; 2 | import * as os from 'os'; 3 | import * as md5 from 'md5'; 4 | import * as fs from 'fs'; 5 | 6 | /** 7 | * 获得配置文件中的 keys 8 | * @returns 9 | */ 10 | const getKeys = () => { 11 | const configFile = path.join(__dirname, '../config/config.default.js'); 12 | const configContent = fs.readFileSync(configFile, 'utf8'); 13 | const keys = configContent.match(/keys: '([^']+)'/)?.[1]; 14 | return keys; 15 | }; 16 | 17 | /** 18 | * 项目数据目录 19 | * @returns 20 | */ 21 | export const pDataPath = () => { 22 | const dirPath = path.join(os.homedir(), '.cool-admin', md5(getKeys())); 23 | if (!fs.existsSync(dirPath)) { 24 | fs.mkdirSync(dirPath, { recursive: true }); 25 | } 26 | return dirPath; 27 | }; 28 | 29 | /** 30 | * 上传目录 31 | * @returns 32 | */ 33 | export const pUploadPath = () => { 34 | const uploadPath = path.join(pDataPath(), 'upload'); 35 | if (!fs.existsSync(uploadPath)) { 36 | fs.mkdirSync(uploadPath, { recursive: true }); 37 | } 38 | return uploadPath; 39 | }; 40 | 41 | /** 42 | * 插件目录 43 | * @returns 44 | */ 45 | export const pPluginPath = () => { 46 | const pluginPath = path.join(pDataPath(), 'plugin'); 47 | if (!fs.existsSync(pluginPath)) { 48 | fs.mkdirSync(pluginPath, { recursive: true }); 49 | } 50 | return pluginPath; 51 | }; 52 | 53 | /** 54 | * sqlite 数据库文件 55 | */ 56 | export const pSqlitePath = () => { 57 | return path.join(pDataPath(), 'cool.sqlite'); 58 | }; 59 | 60 | /** 61 | * 缓存目录 62 | * @returns 63 | */ 64 | export const pCachePath = () => { 65 | return path.join(pDataPath(), 'cache'); 66 | }; 67 | -------------------------------------------------------------------------------- /src/comm/port.ts: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | 3 | /** 4 | * 同步检查端口是否可用(通过系统命令) 5 | * @param {number} port - 要检查的端口 6 | * @returns {boolean} - 是否可用 7 | */ 8 | function isPortAvailableSync(port: number): boolean { 9 | try { 10 | if (process.platform === 'win32') { 11 | // Windows 使用 netstat 检查端口,排除 TIME_WAIT 状态 12 | const result = execSync(`netstat -ano | findstr :${port}`, { 13 | encoding: 'utf-8', 14 | }); 15 | // 如果端口只处于 TIME_WAIT 状态,则认为端口可用 16 | return !result || result.toLowerCase().includes('time_wait'); 17 | } else { 18 | // Linux/Mac 使用 lsof 检查端口,只检查 LISTEN 状态 19 | const result = execSync(`lsof -i :${port} -sTCP:LISTEN`, { 20 | encoding: 'utf-8', 21 | }); 22 | return !result; 23 | } 24 | } catch (error) { 25 | // 命令执行失败,端口可用 26 | return true; 27 | } 28 | } 29 | 30 | /** 31 | * 查找可用端口(同步) 32 | * @param {number} startPort - 起始端口 33 | * @returns {number} - 可用的端口 34 | */ 35 | export function availablePort(startPort: number): number { 36 | if (!process['pkg']) return startPort; 37 | let port = startPort; 38 | while (port <= 8010) { 39 | if (isPortAvailableSync(port)) { 40 | if (port !== startPort) { 41 | console.warn( 42 | '\x1b[33m%s\x1b[0m', 43 | `Port ${startPort} is occupied, using port ${port}` 44 | ); 45 | } 46 | return port; 47 | } 48 | port++; 49 | } 50 | return 8001; 51 | } 52 | -------------------------------------------------------------------------------- /src/config/config.default.ts: -------------------------------------------------------------------------------- 1 | import { CoolConfig } from '@cool-midway/core'; 2 | import { MidwayConfig } from '@midwayjs/core'; 3 | import { CoolCacheStore } from '@cool-midway/core'; 4 | import * as path from 'path'; 5 | import { pCachePath, pUploadPath } from '../comm/path'; 6 | import { availablePort } from '../comm/port'; 7 | 8 | // redis缓存 9 | // import { redisStore } from 'cache-manager-ioredis-yet'; 10 | 11 | export default { 12 | // 确保每个项目唯一,项目首次启动会自动生成 13 | keys: 'cool-admin-keys-xxxxxx', 14 | koa: { 15 | port: availablePort(8001), 16 | }, 17 | // 开启异步上下文管理 18 | asyncContextManager: { 19 | enable: true, 20 | }, 21 | // 静态文件配置 22 | staticFile: { 23 | buffer: true, 24 | dirs: { 25 | default: { 26 | prefix: '/', 27 | dir: path.join(__dirname, '..', '..', 'public'), 28 | }, 29 | static: { 30 | prefix: '/upload', 31 | dir: pUploadPath(), 32 | }, 33 | }, 34 | }, 35 | // 文件上传 36 | upload: { 37 | fileSize: '200mb', 38 | whitelist: null, 39 | }, 40 | // 缓存 可切换成其他缓存如:redis http://www.midwayjs.org/docs/extensions/caching 41 | cacheManager: { 42 | clients: { 43 | default: { 44 | store: CoolCacheStore, 45 | options: { 46 | path: pCachePath(), 47 | ttl: 0, 48 | }, 49 | }, 50 | }, 51 | }, 52 | // cacheManager: { 53 | // clients: { 54 | // default: { 55 | // store: redisStore, 56 | // options: { 57 | // port: 6379, 58 | // host: '127.0.0.1', 59 | // password: '', 60 | // ttl: 0, 61 | // db: 0, 62 | // }, 63 | // }, 64 | // }, 65 | // }, 66 | cool: { 67 | // 已经插件化,本地文件上传查看 plugin/config.ts,其他云存储查看对应插件的使用 68 | file: {}, 69 | // 是否开启多租户 70 | tenant: { 71 | // 是否开启多租户 72 | enable: false, 73 | // 需要过滤多租户的url, 支持通配符, 如/admin/**/* 表示admin模块下的所有接口都进行多租户过滤 74 | urls: [], 75 | }, 76 | // 国际化配置 77 | i18n: { 78 | // 是否开启 79 | enable: false, 80 | // 语言 81 | languages: ['zh-cn', 'zh-tw', 'en'], 82 | }, 83 | // crud配置 84 | crud: { 85 | // 插入模式,save不会校验字段(允许传入不存在的字段),insert会校验字段 86 | upsert: 'save', 87 | // 软删除 88 | softDelete: true, 89 | }, 90 | } as CoolConfig, 91 | } as MidwayConfig; 92 | -------------------------------------------------------------------------------- /src/config/config.local.ts: -------------------------------------------------------------------------------- 1 | import { CoolConfig } from '@cool-midway/core'; 2 | import { MidwayConfig } from '@midwayjs/core'; 3 | import { TenantSubscriber } from '../modules/base/db/tenant'; 4 | 5 | /** 6 | * 本地开发 npm run dev 读取的配置文件 7 | */ 8 | export default { 9 | typeorm: { 10 | dataSource: { 11 | default: { 12 | type: 'mysql', 13 | host: '127.0.0.1', 14 | port: 3306, 15 | username: 'root', 16 | password: '123456', 17 | database: 'cool', 18 | // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 19 | synchronize: true, 20 | // 打印日志 21 | logging: false, 22 | // 字符集 23 | charset: 'utf8mb4', 24 | // 是否开启缓存 25 | cache: true, 26 | // 实体路径 27 | entities: ['**/modules/*/entity'], 28 | // 订阅者 29 | subscribers: [TenantSubscriber], 30 | }, 31 | }, 32 | }, 33 | cool: { 34 | // 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息 35 | eps: true, 36 | // 是否自动导入模块数据库 37 | initDB: true, 38 | // 判断是否初始化的方式 39 | initJudge: 'db', 40 | // 是否自动导入模块菜单 41 | initMenu: true, 42 | } as CoolConfig, 43 | } as MidwayConfig; 44 | -------------------------------------------------------------------------------- /src/config/config.prod.ts: -------------------------------------------------------------------------------- 1 | import { CoolConfig } from '@cool-midway/core'; 2 | import { MidwayConfig } from '@midwayjs/core'; 3 | import { entities } from '../entities'; 4 | import { TenantSubscriber } from '../modules/base/db/tenant'; 5 | 6 | /** 7 | * 本地开发 npm run prod 读取的配置文件 8 | */ 9 | export default { 10 | typeorm: { 11 | dataSource: { 12 | default: { 13 | type: 'mysql', 14 | host: '127.0.0.1', 15 | port: 3306, 16 | username: 'root', 17 | password: '123456', 18 | database: 'cool', 19 | // 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失 20 | synchronize: false, 21 | // 打印日志 22 | logging: false, 23 | // 字符集 24 | charset: 'utf8mb4', 25 | // 是否开启缓存 26 | cache: true, 27 | // 实体路径 28 | entities, 29 | // 订阅者 30 | subscribers: [TenantSubscriber], 31 | }, 32 | }, 33 | }, 34 | cool: { 35 | // 实体与路径,跟生成代码、前端请求、swagger文档相关 注意:线上不建议开启,以免暴露敏感信息 36 | eps: false, 37 | // 是否自动导入模块数据库 38 | initDB: false, 39 | // 判断是否初始化的方式 40 | initJudge: 'db', 41 | // 是否自动导入模块菜单 42 | initMenu: false, 43 | } as CoolConfig, 44 | } as MidwayConfig; 45 | -------------------------------------------------------------------------------- /src/configuration.ts: -------------------------------------------------------------------------------- 1 | import * as orm from '@midwayjs/typeorm'; 2 | import { 3 | Configuration, 4 | App, 5 | IMidwayApplication, 6 | Inject, 7 | ILogger, 8 | MidwayWebRouterService, 9 | } from '@midwayjs/core'; 10 | import * as koa from '@midwayjs/koa'; 11 | // import * as crossDomain from '@midwayjs/cross-domain'; 12 | import * as validate from '@midwayjs/validate'; 13 | import * as info from '@midwayjs/info'; 14 | import * as staticFile from '@midwayjs/static-file'; 15 | import * as cron from '@midwayjs/cron'; 16 | import * as DefaultConfig from './config/config.default'; 17 | import * as LocalConfig from './config/config.local'; 18 | import * as ProdConfig from './config/config.prod'; 19 | import * as cool from '@cool-midway/core'; 20 | import * as upload from '@midwayjs/upload'; 21 | // import * as task from '@cool-midway/task'; 22 | // import * as rpc from '@cool-midway/rpc'; 23 | 24 | @Configuration({ 25 | imports: [ 26 | // https://koajs.com/ 27 | koa, 28 | // 是否开启跨域(注:顺序不能乱放!!!) http://www.midwayjs.org/docs/extensions/cross_domain 29 | // crossDomain, 30 | // 静态文件托管 https://midwayjs.org/docs/extensions/static_file 31 | staticFile, 32 | // orm https://midwayjs.org/docs/extensions/orm 33 | orm, 34 | // 参数验证 https://midwayjs.org/docs/extensions/validate 35 | validate, 36 | // 本地任务 http://www.midwayjs.org/docs/extensions/cron 37 | cron, 38 | // 文件上传 39 | upload, 40 | // cool-admin 官方组件 https://cool-js.com 41 | cool, 42 | // rpc 微服务 远程调用 43 | // rpc, 44 | // 任务与队列 45 | // task, 46 | { 47 | component: info, 48 | enabledEnvironment: ['local', 'prod'], 49 | }, 50 | ], 51 | importConfigs: [ 52 | { 53 | default: DefaultConfig, 54 | local: LocalConfig, 55 | prod: ProdConfig, 56 | }, 57 | ], 58 | }) 59 | export class MainConfiguration { 60 | @App() 61 | app: IMidwayApplication; 62 | 63 | @Inject() 64 | webRouterService: MidwayWebRouterService; 65 | 66 | @Inject() 67 | logger: ILogger; 68 | 69 | async onReady() {} 70 | } 71 | -------------------------------------------------------------------------------- /src/entities.ts: -------------------------------------------------------------------------------- 1 | // 自动生成的文件,请勿手动修改 2 | export const entities = []; 3 | -------------------------------------------------------------------------------- /src/interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @description User-Service parameters 3 | */ 4 | export interface IUserOptions { 5 | uid: number; 6 | } 7 | -------------------------------------------------------------------------------- /src/modules/base/config.ts: -------------------------------------------------------------------------------- 1 | import { BaseLogMiddleware } from './middleware/log'; 2 | import { BaseAuthorityMiddleware } from './middleware/authority'; 3 | import { ModuleConfig } from '@cool-midway/core'; 4 | import { BaseTranslateMiddleware } from './middleware/translate'; 5 | 6 | /** 7 | * 模块的配置 8 | */ 9 | export default () => { 10 | return { 11 | // 模块名称 12 | name: '权限管理', 13 | // 模块描述 14 | description: '基础的权限管理功能,包括登录,权限校验', 15 | // 中间件 16 | globalMiddlewares: [ 17 | BaseTranslateMiddleware, 18 | BaseAuthorityMiddleware, 19 | BaseLogMiddleware, 20 | ], 21 | // 模块加载顺序,默认为0,值越大越优先加载 22 | order: 10, 23 | // app参数配置允许读取的key 24 | allowKeys: [], 25 | // jwt 生成解密token的 26 | jwt: { 27 | // 单点登录 28 | sso: false, 29 | // 注意: 最好重新修改,防止破解 30 | secret: 'cool-admin-xxxxxx', 31 | // token 32 | token: { 33 | // 2小时过期,需要用刷新token 34 | expire: 2 * 3600, 35 | // 15天内,如果没操作过就需要重新登录 36 | refreshExpire: 24 * 3600 * 15, 37 | }, 38 | }, 39 | } as ModuleConfig; 40 | }; 41 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/coding.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { Body, Get, Inject, Post } from '@midwayjs/core'; 3 | import { BaseCodingService } from '../../service/coding'; 4 | 5 | /** 6 | * Ai编码 7 | */ 8 | @CoolController() 9 | export class AdminCodingController extends BaseController { 10 | @Inject() 11 | baseCodingService: BaseCodingService; 12 | 13 | @Get('/getModuleTree', { summary: '获取模块目录结构' }) 14 | async getModuleTree() { 15 | return this.ok(await this.baseCodingService.getModuleTree()); 16 | } 17 | 18 | @Post('/createCode', { summary: '创建代码' }) 19 | async createCode( 20 | @Body('codes') 21 | codes: { 22 | path: string; 23 | content: string; 24 | }[] 25 | ) { 26 | this.baseCodingService.createCode(codes); 27 | return this.ok(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/comm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BaseController, 3 | CoolController, 4 | CoolTag, 5 | CoolUrlTag, 6 | TagTypes, 7 | } from '@cool-midway/core'; 8 | import { ALL, Body, Get, Inject, Post, Provide } from '@midwayjs/core'; 9 | import { Context } from '@midwayjs/koa'; 10 | import { PluginService } from '../../../plugin/service/info'; 11 | import { BaseSysUserEntity } from '../../entity/sys/user'; 12 | import { BaseSysLoginService } from '../../service/sys/login'; 13 | import { BaseSysPermsService } from '../../service/sys/perms'; 14 | import { BaseSysUserService } from '../../service/sys/user'; 15 | 16 | /** 17 | * Base 通用接口 一般写不需要权限过滤的接口 18 | */ 19 | @CoolUrlTag() 20 | @Provide() 21 | @CoolController() 22 | export class BaseCommController extends BaseController { 23 | @Inject() 24 | baseSysUserService: BaseSysUserService; 25 | 26 | @Inject() 27 | baseSysPermsService: BaseSysPermsService; 28 | 29 | @Inject() 30 | baseSysLoginService: BaseSysLoginService; 31 | 32 | @Inject() 33 | ctx: Context; 34 | 35 | @Inject() 36 | pluginService: PluginService; 37 | 38 | /** 39 | * 获得个人信息 40 | */ 41 | @Get('/person', { summary: '个人信息' }) 42 | async person() { 43 | return this.ok( 44 | await this.baseSysUserService.person(this.ctx.admin?.userId) 45 | ); 46 | } 47 | 48 | /** 49 | * 修改个人信息 50 | */ 51 | @Post('/personUpdate', { summary: '修改个人信息' }) 52 | async personUpdate(@Body(ALL) user: BaseSysUserEntity) { 53 | await this.baseSysUserService.personUpdate(user); 54 | return this.ok(); 55 | } 56 | 57 | /** 58 | * 权限菜单 59 | */ 60 | @Get('/permmenu', { summary: '权限与菜单' }) 61 | async permmenu() { 62 | return this.ok( 63 | await this.baseSysPermsService.permmenu(this.ctx.admin.roleIds) 64 | ); 65 | } 66 | 67 | /** 68 | * 文件上传 69 | */ 70 | @Post('/upload', { summary: '文件上传' }) 71 | async upload() { 72 | const file = await this.pluginService.getInstance('upload'); 73 | return this.ok(await file.upload(this.ctx)); 74 | } 75 | 76 | /** 77 | * 文件上传模式,本地或者云存储 78 | */ 79 | @Get('/uploadMode', { summary: '文件上传模式' }) 80 | async uploadMode() { 81 | const file = await this.pluginService.getInstance('upload'); 82 | return this.ok(await file.getMode()); 83 | } 84 | 85 | /** 86 | * 退出 87 | */ 88 | @Post('/logout', { summary: '退出' }) 89 | async logout() { 90 | await this.baseSysLoginService.logout(); 91 | return this.ok(); 92 | } 93 | 94 | @CoolTag(TagTypes.IGNORE_TOKEN) 95 | @Get('/program', { summary: '编程' }) 96 | async program() { 97 | return this.ok('Node'); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/open.ts: -------------------------------------------------------------------------------- 1 | import { Provide, Body, Inject, Post, Get, Query } from '@midwayjs/core'; 2 | import { 3 | CoolController, 4 | BaseController, 5 | CoolEps, 6 | CoolUrlTag, 7 | CoolTag, 8 | TagTypes, 9 | RESCODE, 10 | } from '@cool-midway/core'; 11 | import { LoginDTO } from '../../dto/login'; 12 | import { BaseSysLoginService } from '../../service/sys/login'; 13 | import { BaseSysParamService } from '../../service/sys/param'; 14 | import { Context } from '@midwayjs/koa'; 15 | import { Validate } from '@midwayjs/validate'; 16 | 17 | /** 18 | * 不需要登录的后台接口 19 | */ 20 | @Provide() 21 | @CoolController({ description: '开放接口' }) 22 | @CoolUrlTag() 23 | export class BaseOpenController extends BaseController { 24 | @Inject() 25 | baseSysLoginService: BaseSysLoginService; 26 | 27 | @Inject() 28 | baseSysParamService: BaseSysParamService; 29 | 30 | @Inject() 31 | ctx: Context; 32 | 33 | @Inject() 34 | eps: CoolEps; 35 | 36 | /** 37 | * 实体信息与路径 38 | * @returns 39 | */ 40 | @CoolTag(TagTypes.IGNORE_TOKEN) 41 | @Get('/eps', { summary: '实体信息与路径' }) 42 | public async getEps() { 43 | return this.ok(this.eps.admin); 44 | } 45 | 46 | /** 47 | * 根据配置参数key获得网页内容(富文本) 48 | */ 49 | @CoolTag(TagTypes.IGNORE_TOKEN) 50 | @Get('/html', { summary: '获得网页内容的参数值' }) 51 | async htmlByKey(@Query('key') key: string) { 52 | this.ctx.body = await this.baseSysParamService.htmlByKey(key); 53 | } 54 | 55 | /** 56 | * 登录 57 | * @param login 58 | */ 59 | @CoolTag(TagTypes.IGNORE_TOKEN) 60 | @Post('/login', { summary: '登录' }) 61 | @Validate() 62 | async login(@Body() login: LoginDTO) { 63 | return this.ok(await this.baseSysLoginService.login(login)); 64 | } 65 | 66 | /** 67 | * 获得验证码 68 | */ 69 | @CoolTag(TagTypes.IGNORE_TOKEN) 70 | @Get('/captcha', { summary: '验证码' }) 71 | async captcha( 72 | @Query('width') width: number, 73 | @Query('height') height: number, 74 | @Query('color') color: string 75 | ) { 76 | return this.ok( 77 | await this.baseSysLoginService.captcha(width, height, color) 78 | ); 79 | } 80 | 81 | /** 82 | * 刷新token 83 | */ 84 | @CoolTag(TagTypes.IGNORE_TOKEN) 85 | @Get('/refreshToken', { summary: '刷新token' }) 86 | async refreshToken(@Query('refreshToken') refreshToken: string) { 87 | try { 88 | const token = await this.baseSysLoginService.refreshToken(refreshToken); 89 | return this.ok(token); 90 | } catch (e) { 91 | this.ctx.status = 401; 92 | this.ctx.body = { 93 | code: RESCODE.COMMFAIL, 94 | message: '登录失效~', 95 | }; 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/department.ts: -------------------------------------------------------------------------------- 1 | import { ALL, Body, Inject, Post, Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { BaseSysDepartmentEntity } from '../../../entity/sys/department'; 4 | import { BaseSysDepartmentService } from '../../../service/sys/department'; 5 | 6 | /** 7 | * 部门 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'list'], 12 | entity: BaseSysDepartmentEntity, 13 | service: BaseSysDepartmentService, 14 | insertParam: ctx => { 15 | return { 16 | userId: ctx.admin.userId, 17 | }; 18 | }, 19 | }) 20 | export class BaseDepartmentController extends BaseController { 21 | @Inject() 22 | baseDepartmentService: BaseSysDepartmentService; 23 | 24 | /** 25 | * 部门排序 26 | */ 27 | @Post('/order', { summary: '排序' }) 28 | async order(@Body(ALL) params: any) { 29 | await this.baseDepartmentService.order(params); 30 | return this.ok(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/log.ts: -------------------------------------------------------------------------------- 1 | import { Provide, Post, Inject, Body, Get } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { BaseSysLogEntity } from '../../../entity/sys/log'; 4 | import { BaseSysUserEntity } from '../../../entity/sys/user'; 5 | import { BaseSysConfService } from '../../../service/sys/conf'; 6 | import { BaseSysLogService } from '../../../service/sys/log'; 7 | 8 | /** 9 | * 系统日志 10 | */ 11 | @Provide() 12 | @CoolController({ 13 | api: ['page'], 14 | entity: BaseSysLogEntity, 15 | urlTag: { 16 | name: 'a', 17 | url: ['add'], 18 | }, 19 | pageQueryOp: { 20 | keyWordLikeFields: ['b.name', 'a.action', 'a.ip'], 21 | select: ['a.*', 'b.name'], 22 | join: [ 23 | { 24 | entity: BaseSysUserEntity, 25 | alias: 'b', 26 | condition: 'a.userId = b.id', 27 | type: 'leftJoin', 28 | }, 29 | ], 30 | }, 31 | }) 32 | export class BaseSysLogController extends BaseController { 33 | @Inject() 34 | baseSysLogService: BaseSysLogService; 35 | 36 | @Inject() 37 | baseSysConfService: BaseSysConfService; 38 | 39 | /** 40 | * 清空日志 41 | */ 42 | @Post('/clear', { summary: '清理' }) 43 | public async clear() { 44 | await this.baseSysLogService.clear(true); 45 | return this.ok(); 46 | } 47 | 48 | /** 49 | * 设置日志保存时间 50 | */ 51 | @Post('/setKeep', { summary: '日志保存时间' }) 52 | public async setKeep(@Body('value') value: number) { 53 | await this.baseSysConfService.updateVaule('logKeep', value); 54 | return this.ok(); 55 | } 56 | 57 | /** 58 | * 获得日志保存时间 59 | */ 60 | @Get('/getKeep', { summary: '获得日志保存时间' }) 61 | public async getKeep() { 62 | return this.ok(await this.baseSysConfService.getValue('logKeep')); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/menu.ts: -------------------------------------------------------------------------------- 1 | import { Body, Inject, Post, Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { BaseSysMenuEntity } from '../../../entity/sys/menu'; 4 | import { BaseSysMenuService } from '../../../service/sys/menu'; 5 | 6 | /** 7 | * 菜单 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: BaseSysMenuEntity, 13 | service: BaseSysMenuService, 14 | }) 15 | export class BaseSysMenuController extends BaseController { 16 | @Inject() 17 | baseSysMenuService: BaseSysMenuService; 18 | 19 | @Post('/parse', { summary: '解析' }) 20 | async parse( 21 | @Body('entity') entity: string, 22 | @Body('controller') controller: string, 23 | @Body('module') module: string 24 | ) { 25 | return this.ok( 26 | await this.baseSysMenuService.parse(entity, controller, module) 27 | ); 28 | } 29 | 30 | @Post('/create', { summary: '创建代码' }) 31 | async create(@Body() body) { 32 | await this.baseSysMenuService.create(body); 33 | return this.ok(); 34 | } 35 | 36 | @Post('/export', { summary: '导出' }) 37 | async export(@Body('ids') ids: number[]) { 38 | return this.ok(await this.baseSysMenuService.export(ids)); 39 | } 40 | 41 | @Post('/import', { summary: '导入' }) 42 | async import(@Body('menus') menus: any[]) { 43 | await this.baseSysMenuService.import(menus); 44 | return this.ok(); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/param.ts: -------------------------------------------------------------------------------- 1 | import { Get, Inject, Provide, Query } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { BaseSysParamEntity } from '../../../entity/sys/param'; 4 | import { BaseSysParamService } from '../../../service/sys/param'; 5 | import { Context } from '@midwayjs/koa'; 6 | 7 | /** 8 | * 参数配置 9 | */ 10 | @Provide() 11 | @CoolController({ 12 | api: ['add', 'delete', 'update', 'info', 'page'], 13 | entity: BaseSysParamEntity, 14 | service: BaseSysParamService, 15 | pageQueryOp: { 16 | keyWordLikeFields: ['name', 'keyName'], 17 | fieldEq: ['dataType'], 18 | }, 19 | }) 20 | export class BaseSysParamController extends BaseController { 21 | @Inject() 22 | baseSysParamService: BaseSysParamService; 23 | 24 | @Inject() 25 | ctx: Context; 26 | 27 | /** 28 | * 根据配置参数key获得网页内容(富文本) 29 | */ 30 | @Get('/html', { summary: '获得网页内容的参数值' }) 31 | async htmlByKey(@Query('key') key: string) { 32 | this.ctx.body = await this.baseSysParamService.htmlByKey(key); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/role.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { Context } from 'vm'; 4 | import { BaseSysRoleEntity } from '../../../entity/sys/role'; 5 | import { BaseSysRoleService } from '../../../service/sys/role'; 6 | 7 | /** 8 | * 系统角色 9 | */ 10 | @Provide() 11 | @CoolController({ 12 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 13 | entity: BaseSysRoleEntity, 14 | service: BaseSysRoleService, 15 | // 新增的时候插入当前用户ID 16 | insertParam: async (ctx: Context) => { 17 | return { 18 | userId: ctx.admin.userId, 19 | }; 20 | }, 21 | pageQueryOp: { 22 | keyWordLikeFields: ['a.name', 'a.label'], 23 | where: async (ctx: Context) => { 24 | const { userId, roleIds, username } = ctx.admin; 25 | return [ 26 | // 超级管理员的角色不展示 27 | ['a.label != :label', { label: 'admin' }], 28 | // 如果不是超管,只能看到自己新建的或者自己有的角色 29 | [ 30 | `(a.userId=:userId or a.id in (${roleIds.join(',')}))`, 31 | { userId }, 32 | username !== 'admin', 33 | ], 34 | ]; 35 | }, 36 | }, 37 | }) 38 | export class BaseSysRoleController extends BaseController {} 39 | -------------------------------------------------------------------------------- /src/modules/base/controller/admin/sys/user.ts: -------------------------------------------------------------------------------- 1 | import { Body, Inject, Post, Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { BaseSysUserEntity } from '../../../entity/sys/user'; 4 | import { BaseSysUserService } from '../../../service/sys/user'; 5 | 6 | /** 7 | * 系统用户 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: BaseSysUserEntity, 13 | service: BaseSysUserService, 14 | insertParam: ctx => { 15 | return { 16 | userId: ctx.admin.userId, 17 | }; 18 | }, 19 | }) 20 | export class BaseSysUserController extends BaseController { 21 | @Inject() 22 | baseSysUserService: BaseSysUserService; 23 | 24 | /** 25 | * 移动部门 26 | */ 27 | @Post('/move', { summary: '移动部门' }) 28 | async move( 29 | @Body('departmentId') departmentId: number, 30 | @Body('userIds') userIds: [] 31 | ) { 32 | await this.baseSysUserService.move(departmentId, userIds); 33 | return this.ok(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/base/controller/app/README.md: -------------------------------------------------------------------------------- 1 | 这里写对外的api接口 -------------------------------------------------------------------------------- /src/modules/base/controller/app/comm.ts: -------------------------------------------------------------------------------- 1 | import { Provide, Inject, Get, Post, Query, Config } from '@midwayjs/core'; 2 | import { 3 | CoolController, 4 | BaseController, 5 | CoolEps, 6 | TagTypes, 7 | CoolUrlTag, 8 | CoolTag, 9 | } from '@cool-midway/core'; 10 | import { Context } from '@midwayjs/koa'; 11 | import { BaseSysParamService } from '../../service/sys/param'; 12 | import { PluginService } from '../../../plugin/service/info'; 13 | 14 | /** 15 | * 不需要登录的后台接口 16 | */ 17 | @CoolUrlTag() 18 | @Provide() 19 | @CoolController() 20 | export class BaseAppCommController extends BaseController { 21 | @Inject() 22 | pluginService: PluginService; 23 | 24 | @Inject() 25 | ctx: Context; 26 | 27 | @Config('module.base.allowKeys') 28 | allowKeys: string[]; 29 | 30 | @Inject() 31 | eps: CoolEps; 32 | 33 | @Inject() 34 | baseSysParamService: BaseSysParamService; 35 | 36 | @CoolTag(TagTypes.IGNORE_TOKEN) 37 | @Get('/param', { summary: '参数配置' }) 38 | async param(@Query('key') key: string) { 39 | if (!this.allowKeys.includes(key)) { 40 | return this.fail('非法操作'); 41 | } 42 | return this.ok(await this.baseSysParamService.dataByKey(key)); 43 | } 44 | 45 | /** 46 | * 实体信息与路径 47 | * @returns 48 | */ 49 | @CoolTag(TagTypes.IGNORE_TOKEN) 50 | @Get('/eps', { summary: '实体信息与路径' }) 51 | public async getEps() { 52 | return this.ok(this.eps.app); 53 | } 54 | 55 | /** 56 | * 文件上传 57 | */ 58 | @Post('/upload', { summary: '文件上传' }) 59 | async upload() { 60 | const file = await this.pluginService.getInstance('upload'); 61 | return this.ok(await file.upload(this.ctx)); 62 | } 63 | 64 | /** 65 | * 文件上传模式,本地或者云存储 66 | */ 67 | @Get('/uploadMode', { summary: '文件上传模式' }) 68 | async uploadMode() { 69 | const file = await this.pluginService.getInstance('upload'); 70 | return this.ok(await file.getMode()); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/modules/base/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "base_sys_param": [ 3 | { 4 | "keyName": "rich", 5 | "name": "富文本参数", 6 | "data": "

这是一个富文本

xxx

xxxxxxxxxx


", 7 | "dataType": 1, 8 | "remark": null 9 | }, 10 | { 11 | "keyName": "json", 12 | "name": "JSON参数", 13 | "data": "{\n \"code\": 111233\n}", 14 | "dataType": 0, 15 | "remark": null 16 | }, 17 | { 18 | "keyName": "file", 19 | "name": "文件", 20 | "data": "", 21 | "dataType": 2, 22 | "remark": null 23 | }, 24 | { 25 | "keyName": "text", 26 | "name": "测试", 27 | "data": "这是一段字符串", 28 | "dataType": 0, 29 | "remark": null 30 | } 31 | ], 32 | "base_sys_conf": [ 33 | { 34 | "cKey": "logKeep", 35 | "cValue": "31" 36 | }, 37 | { 38 | "cKey": "recycleKeep", 39 | "cValue": "31" 40 | } 41 | ], 42 | "base_sys_department": [ 43 | { 44 | "id": 1, 45 | "name": "COOL", 46 | "parentId": null, 47 | "orderNum": 0 48 | }, 49 | { 50 | "id": 11, 51 | "name": "开发", 52 | "parentId": 12, 53 | "orderNum": 2 54 | }, 55 | { 56 | "id": 12, 57 | "name": "测试", 58 | "parentId": 1, 59 | "orderNum": 1 60 | }, 61 | { 62 | "id": 13, 63 | "name": "游客", 64 | "parentId": 1, 65 | "orderNum": 3 66 | } 67 | ], 68 | "base_sys_role": [ 69 | { 70 | "id": 1, 71 | "userId": "1", 72 | "name": "超管", 73 | "label": "admin", 74 | "remark": "最高权限的角色", 75 | "relevance": 1, 76 | "menuIdList": "null", 77 | "departmentIdList": "null" 78 | } 79 | ], 80 | "base_sys_user": [ 81 | { 82 | "id": 1, 83 | "departmentId": 1, 84 | "name": "超级管理员", 85 | "username": "admin", 86 | "password": "e10adc3949ba59abbe56e057f20f883e", 87 | "passwordV": 7, 88 | "nickName": "管理员", 89 | "phone": "18000000000", 90 | "email": "team@cool-js.com", 91 | "status": 1, 92 | "remark": "拥有最高权限的用户", 93 | "socketId": null 94 | } 95 | ], 96 | "base_sys_user_role": [ 97 | { 98 | "userId": 1, 99 | "roleId": 1 100 | } 101 | ] 102 | } -------------------------------------------------------------------------------- /src/modules/base/dto/login.ts: -------------------------------------------------------------------------------- 1 | import { Rule, RuleType } from '@midwayjs/validate'; 2 | /** 3 | * 登录参数校验 4 | */ 5 | export class LoginDTO { 6 | // 用户名 7 | @Rule(RuleType.string().required()) 8 | username: string; 9 | 10 | // 密码 11 | @Rule(RuleType.string().required()) 12 | password: string; 13 | 14 | // 验证码ID 15 | @Rule(RuleType.string().required()) 16 | captchaId: string; 17 | 18 | // 验证码 19 | @Rule(RuleType.required()) 20 | verifyCode: number; 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/base/entity/base.ts: -------------------------------------------------------------------------------- 1 | import { Index, PrimaryGeneratedColumn, Column } from 'typeorm'; 2 | import * as moment from 'moment'; 3 | import { CoolBaseEntity } from '@cool-midway/core'; 4 | 5 | /** 6 | * 时间转换器 7 | */ 8 | export const transformerTime = { 9 | to(value) { 10 | return value 11 | ? moment(value).format('YYYY-MM-DD HH:mm:ss') 12 | : moment().format('YYYY-MM-DD HH:mm:ss'); 13 | }, 14 | from(value) { 15 | return value; 16 | }, 17 | }; 18 | 19 | /** 20 | * Json转换器 21 | */ 22 | export const transformerJson = { 23 | to: value => value, 24 | from: value => { 25 | // 确保从数据库返回的是对象 26 | if (typeof value === 'string') { 27 | try { 28 | return JSON.parse(value); 29 | } catch (e) { 30 | return value; 31 | } 32 | } 33 | return value; 34 | }, 35 | }; 36 | /** 37 | * 实体基类 38 | */ 39 | export abstract class BaseEntity extends CoolBaseEntity { 40 | // 默认自增 41 | @PrimaryGeneratedColumn('increment', { 42 | comment: 'ID', 43 | }) 44 | id: number; 45 | 46 | @Index() 47 | @Column({ 48 | comment: '创建时间', 49 | type: 'varchar', 50 | transformer: transformerTime, 51 | }) 52 | createTime: Date; 53 | 54 | @Index() 55 | @Column({ 56 | comment: '更新时间', 57 | type: 'varchar', 58 | transformer: transformerTime, 59 | }) 60 | updateTime: Date; 61 | 62 | @Index() 63 | @Column({ comment: '租户ID', nullable: true }) 64 | tenantId: number; 65 | } 66 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/conf.ts: -------------------------------------------------------------------------------- 1 | import { Column, Index, Entity } from 'typeorm'; 2 | import { BaseEntity } from '../base'; 3 | 4 | /** 5 | * 系统配置 6 | */ 7 | @Entity('base_sys_conf') 8 | export class BaseSysConfEntity extends BaseEntity { 9 | @Index({ unique: true }) 10 | @Column({ comment: '配置键' }) 11 | cKey: string; 12 | 13 | @Column({ comment: '配置值' }) 14 | cValue: string; 15 | } 16 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/department.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Entity, Index } from 'typeorm'; 3 | 4 | /** 5 | * 部门 6 | */ 7 | @Entity('base_sys_department') 8 | export class BaseSysDepartmentEntity extends BaseEntity { 9 | @Column({ comment: '部门名称' }) 10 | name: string; 11 | 12 | @Index() 13 | @Column({ comment: '创建者ID', nullable: true }) 14 | userId: number; 15 | 16 | @Column({ comment: '上级部门ID', nullable: true }) 17 | parentId: number; 18 | 19 | @Column({ comment: '排序', default: 0 }) 20 | orderNum: number; 21 | // 父菜单名称 22 | parentName: string; 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/log.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, transformerJson } from '../base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 系统日志 6 | */ 7 | @Entity('base_sys_log') 8 | export class BaseSysLogEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '用户ID', nullable: true }) 11 | userId: number; 12 | 13 | @Index() 14 | @Column({ comment: '行为' }) 15 | action: string; 16 | 17 | @Index() 18 | @Column({ comment: 'ip', nullable: true }) 19 | ip: string; 20 | 21 | @Column({ 22 | comment: '参数', 23 | nullable: true, 24 | type: 'json', 25 | transformer: transformerJson, 26 | }) 27 | params: string; 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/menu.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 菜单 6 | */ 7 | @Entity('base_sys_menu') 8 | export class BaseSysMenuEntity extends BaseEntity { 9 | @Column({ comment: '父菜单ID', nullable: true }) 10 | parentId: number; 11 | 12 | @Column({ comment: '菜单名称' }) 13 | name: string; 14 | 15 | @Column({ comment: '菜单地址', nullable: true }) 16 | router: string; 17 | 18 | @Column({ comment: '权限标识', type: 'text', nullable: true }) 19 | perms: string; 20 | 21 | @Column({ 22 | comment: '类型 0-目录 1-菜单 2-按钮', 23 | default: 0, 24 | }) 25 | type: number; 26 | 27 | @Column({ comment: '图标', nullable: true }) 28 | icon: string; 29 | 30 | @Column({ comment: '排序', default: 0 }) 31 | orderNum: number; 32 | 33 | @Column({ comment: '视图地址', nullable: true }) 34 | viewPath: string; 35 | 36 | @Column({ comment: '路由缓存', default: true }) 37 | keepAlive: boolean; 38 | 39 | @Column({ comment: '是否显示', default: true }) 40 | isShow: boolean; 41 | 42 | // 父菜单名称 43 | parentName: string; 44 | 45 | // 子菜单 46 | childMenus: any; 47 | } 48 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/param.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 参数配置 6 | */ 7 | @Entity('base_sys_param') 8 | export class BaseSysParamEntity extends BaseEntity { 9 | @Index({ unique: true }) 10 | @Column({ comment: '键' }) 11 | keyName: string; 12 | 13 | @Column({ comment: '名称' }) 14 | name: string; 15 | 16 | @Column({ comment: '数据', type: 'text' }) 17 | data: string; 18 | 19 | @Column({ 20 | comment: '数据类型 0-字符串 1-富文本 2-文件 ', 21 | default: 0, 22 | }) 23 | dataType: number; 24 | 25 | @Column({ comment: '备注', nullable: true }) 26 | remark: string; 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/role.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, transformerJson } from '../base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 角色 6 | */ 7 | @Entity('base_sys_role') 8 | export class BaseSysRoleEntity extends BaseEntity { 9 | @Column({ comment: '用户ID' }) 10 | userId: string; 11 | 12 | @Index({ unique: true }) 13 | @Column({ comment: '名称' }) 14 | name: string; 15 | 16 | @Index({ unique: true }) 17 | @Column({ comment: '角色标签', nullable: true, length: 50 }) 18 | label: string; 19 | 20 | @Column({ comment: '备注', nullable: true }) 21 | remark: string; 22 | 23 | @Column({ comment: '数据权限是否关联上下级', default: false }) 24 | relevance: boolean; 25 | 26 | @Column({ comment: '菜单权限', type: 'json', transformer: transformerJson }) 27 | menuIdList: number[]; 28 | 29 | @Column({ comment: '部门权限', type: 'json', transformer: transformerJson }) 30 | departmentIdList: number[]; 31 | } 32 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/role_department.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 角色部门 6 | */ 7 | @Entity('base_sys_role_department') 8 | export class BaseSysRoleDepartmentEntity extends BaseEntity { 9 | @Column({ comment: '角色ID' }) 10 | roleId: number; 11 | 12 | @Column({ comment: '部门ID' }) 13 | departmentId: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/role_menu.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 角色菜单 6 | */ 7 | @Entity('base_sys_role_menu') 8 | export class BaseSysRoleMenuEntity extends BaseEntity { 9 | @Column({ comment: '角色ID' }) 10 | roleId: number; 11 | 12 | @Column({ comment: '菜单ID' }) 13 | menuId: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/user.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 系统用户 6 | */ 7 | @Entity('base_sys_user') 8 | export class BaseSysUserEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '部门ID', nullable: true }) 11 | departmentId: number; 12 | 13 | @Index() 14 | @Column({ comment: '创建者ID', nullable: true }) 15 | userId: number; 16 | 17 | @Column({ comment: '姓名', nullable: true }) 18 | name: string; 19 | 20 | @Index({ unique: true }) 21 | @Column({ comment: '用户名', length: 100 }) 22 | username: string; 23 | 24 | @Column({ comment: '密码' }) 25 | password: string; 26 | 27 | @Column({ 28 | comment: '密码版本, 作用是改完密码,让原来的token失效', 29 | default: 1, 30 | }) 31 | passwordV: number; 32 | 33 | @Column({ comment: '昵称', nullable: true }) 34 | nickName: string; 35 | 36 | @Column({ comment: '头像', nullable: true }) 37 | headImg: string; 38 | 39 | @Index() 40 | @Column({ comment: '手机', nullable: true, length: 20 }) 41 | phone: string; 42 | 43 | @Column({ comment: '邮箱', nullable: true }) 44 | email: string; 45 | 46 | @Column({ comment: '备注', nullable: true }) 47 | remark: string; 48 | 49 | @Column({ comment: '状态 0-禁用 1-启用', default: 1 }) 50 | status: number; 51 | // 部门名称 52 | departmentName: string; 53 | // 角色ID列表 54 | roleIdList: number[]; 55 | 56 | @Column({ comment: 'socketId', nullable: true }) 57 | socketId: string; 58 | } 59 | -------------------------------------------------------------------------------- /src/modules/base/entity/sys/user_role.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 用户角色 6 | */ 7 | @Entity('base_sys_user_role') 8 | export class BaseSysUserRoleEntity extends BaseEntity { 9 | @Column({ comment: '用户ID' }) 10 | userId: number; 11 | 12 | @Column({ comment: '角色ID' }) 13 | roleId: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/base/event/app.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, Event } from '@cool-midway/core'; 2 | import { App, ILogger, IMidwayApplication, Inject } from '@midwayjs/core'; 3 | 4 | /** 5 | * 接收事件 6 | */ 7 | @CoolEvent() 8 | export class BaseAppEvent { 9 | @App() 10 | app: IMidwayApplication; 11 | 12 | @Inject() 13 | logger: ILogger; 14 | 15 | @Event('onServerReady') 16 | async onServerReady() { 17 | if (!process['pkg']) return; 18 | const port = this.app.getConfig('koa.port') || 8001; 19 | this.logger.info(`Server is running at http://127.0.0.1:${port}`); 20 | const url = `http://127.0.0.1:${port}`; 21 | 22 | // 使用 child_process 打开浏览器 23 | const { exec } = require('child_process'); 24 | let command; 25 | 26 | switch (process.platform) { 27 | case 'darwin': // macOS 28 | command = `open ${url}`; 29 | break; 30 | case 'win32': // Windows 31 | command = `start ${url}`; 32 | break; 33 | default: // Linux 34 | command = `xdg-open ${url}`; 35 | break; 36 | } 37 | 38 | console.log('url=>', url); 39 | exec(command, (error: any) => { 40 | if (!error) { 41 | this.logger.info(`Application has opened in browser at ${url}`); 42 | } 43 | }); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/modules/base/event/menu.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, CoolEventManager, Event } from '@cool-midway/core'; 2 | import { BaseSysMenuService } from '../service/sys/menu'; 3 | import { 4 | App, 5 | ILogger, 6 | IMidwayApplication, 7 | Inject, 8 | Logger, 9 | } from '@midwayjs/core'; 10 | import { BaseTranslateService } from '../service/translate'; 11 | 12 | /** 13 | * 导入菜单 14 | */ 15 | @CoolEvent() 16 | export class BaseMenuEvent { 17 | @Logger() 18 | coreLogger: ILogger; 19 | 20 | @Inject() 21 | baseSysMenuService: BaseSysMenuService; 22 | 23 | @App() 24 | app: IMidwayApplication; 25 | 26 | @Inject() 27 | coolEventManager: CoolEventManager; 28 | 29 | @Inject() 30 | baseTranslateService: BaseTranslateService; 31 | 32 | @Event('onMenuImport') 33 | async onMenuImport(datas) { 34 | for (const module in datas) { 35 | await this.baseSysMenuService.import(datas[module]); 36 | this.coreLogger.info( 37 | '\x1B[36m [cool:module:base] midwayjs cool module base import [' + 38 | module + 39 | '] module menu success \x1B[0m' 40 | ); 41 | } 42 | this.coolEventManager.emit('onMenuInit', {}); 43 | this.baseTranslateService.check(); 44 | } 45 | 46 | @Event('onServerReady') 47 | async onServerReady() { 48 | this.baseTranslateService.loadTranslations(); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/base/job/log.ts: -------------------------------------------------------------------------------- 1 | import { Job, IJob } from '@midwayjs/cron'; 2 | import { FORMAT, ILogger, Inject } from '@midwayjs/core'; 3 | import { BaseSysLogService } from '../service/sys/log'; 4 | 5 | /** 6 | * 日志定时任务 7 | */ 8 | @Job({ 9 | cronTime: FORMAT.CRONTAB.EVERY_DAY, 10 | start: true, 11 | }) 12 | export class BaseLogJob implements IJob { 13 | @Inject() 14 | baseSysLogService: BaseSysLogService; 15 | 16 | @Inject() 17 | logger: ILogger; 18 | 19 | async onTick() { 20 | this.logger.info('清除日志定时任务开始执行'); 21 | const startTime = Date.now(); 22 | await this.baseSysLogService.clear(); 23 | this.logger.info(`清除日志定时任务结束,耗时:${Date.now() - startTime}ms`); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/base/middleware/authority.ts: -------------------------------------------------------------------------------- 1 | import { App, Config, Inject, Middleware } from '@midwayjs/core'; 2 | import * as _ from 'lodash'; 3 | import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core'; 4 | import * as jwt from 'jsonwebtoken'; 5 | import { NextFunction, Context } from '@midwayjs/koa'; 6 | import { 7 | IMiddleware, 8 | IMidwayApplication, 9 | Init, 10 | InjectClient, 11 | } from '@midwayjs/core'; 12 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 13 | import { Utils } from '../../../comm/utils'; 14 | 15 | /** 16 | * 权限校验 17 | */ 18 | @Middleware() 19 | export class BaseAuthorityMiddleware 20 | implements IMiddleware 21 | { 22 | @Config('koa.globalPrefix') 23 | prefix; 24 | 25 | @Config('module.base') 26 | jwtConfig; 27 | 28 | @InjectClient(CachingFactory, 'default') 29 | midwayCache: MidwayCache; 30 | 31 | @Inject() 32 | coolUrlTagData: CoolUrlTagData; 33 | 34 | @App() 35 | app: IMidwayApplication; 36 | 37 | @Inject() 38 | utils: Utils; 39 | 40 | ignoreUrls: string[] = []; 41 | 42 | @Init() 43 | async init() { 44 | this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'admin'); 45 | } 46 | 47 | resolve() { 48 | return async (ctx: Context, next: NextFunction) => { 49 | let statusCode = 200; 50 | let { url } = ctx; 51 | url = url.replace(this.prefix, '').split('?')[0]; 52 | const token = ctx.get('Authorization'); 53 | const adminUrl = '/admin/'; 54 | // 路由地址为 admin前缀的 需要权限校验 55 | if (_.startsWith(url, adminUrl)) { 56 | try { 57 | ctx.admin = jwt.verify(token, this.jwtConfig.jwt.secret); 58 | if (ctx.admin.isRefresh) { 59 | ctx.status = 401; 60 | throw new CoolCommException('登录失效~', ctx.status); 61 | } 62 | } catch (error) {} 63 | // 使用matchUrl方法来检查URL是否应该被忽略 64 | const isIgnored = this.ignoreUrls.some(pattern => 65 | this.utils.matchUrl(pattern, url) 66 | ); 67 | if (isIgnored) { 68 | await next(); 69 | return; 70 | } 71 | if (ctx.admin) { 72 | const rToken = await this.midwayCache.get( 73 | `admin:token:${ctx.admin.userId}` 74 | ); 75 | // 判断密码版本是否正确 76 | const passwordV = await this.midwayCache.get( 77 | `admin:passwordVersion:${ctx.admin.userId}` 78 | ); 79 | if (passwordV != ctx.admin.passwordVersion) { 80 | throw new CoolCommException('登录失效~', 401); 81 | } 82 | // 超管拥有所有权限 83 | if (ctx.admin.username == 'admin' && !ctx.admin.isRefresh) { 84 | if (rToken !== token && this.jwtConfig.jwt.sso) { 85 | throw new CoolCommException('登录失效~', 401); 86 | } else { 87 | await next(); 88 | return; 89 | } 90 | } 91 | // 要登录每个人都有权限的接口 92 | if ( 93 | new RegExp(`^${adminUrl}?.*/comm/`).test(url) || 94 | // 字典接口 95 | url == '/admin/dict/info/data' 96 | ) { 97 | await next(); 98 | return; 99 | } 100 | // 如果传的token是refreshToken则校验失败 101 | if (ctx.admin.isRefresh) { 102 | throw new CoolCommException('登录失效~', 401); 103 | } 104 | if (!rToken) { 105 | throw new CoolCommException('登录失效或无权限访问~', 401); 106 | } 107 | if (rToken !== token && this.jwtConfig.jwt.sso) { 108 | statusCode = 401; 109 | } else { 110 | let perms: string[] = await this.midwayCache.get( 111 | `admin:perms:${ctx.admin.userId}` 112 | ); 113 | if (!_.isEmpty(perms)) { 114 | perms = perms.map(e => { 115 | return e.replace(/:/g, '/'); 116 | }); 117 | if (!perms.includes(url.split('?')[0].replace('/admin/', ''))) { 118 | statusCode = 403; 119 | } 120 | } else { 121 | statusCode = 403; 122 | } 123 | } 124 | } else { 125 | statusCode = 401; 126 | } 127 | if (statusCode > 200) { 128 | throw new CoolCommException('登录失效或无权限访问~', statusCode); 129 | } 130 | } 131 | await next(); 132 | }; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/modules/base/middleware/log.ts: -------------------------------------------------------------------------------- 1 | import { Middleware } from '@midwayjs/core'; 2 | import * as _ from 'lodash'; 3 | import { NextFunction, Context } from '@midwayjs/koa'; 4 | import { IMiddleware } from '@midwayjs/core'; 5 | import { BaseSysLogService } from '../service/sys/log'; 6 | 7 | /** 8 | * 日志中间件 9 | */ 10 | @Middleware() 11 | export class BaseLogMiddleware implements IMiddleware { 12 | resolve() { 13 | return async (ctx: Context, next: NextFunction) => { 14 | const baseSysLogService = await ctx.requestContext.getAsync( 15 | BaseSysLogService 16 | ); 17 | baseSysLogService.record( 18 | ctx, 19 | ctx.url, 20 | ctx.req.method === 'GET' ? ctx.request.query : ctx.request.body, 21 | ctx.admin ? ctx.admin.userId : null 22 | ); 23 | await next(); 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/base/middleware/translate.ts: -------------------------------------------------------------------------------- 1 | import { Config, ILogger, Middleware } from '@midwayjs/core'; 2 | import { NextFunction, Context } from '@midwayjs/koa'; 3 | import { IMiddleware, Inject } from '@midwayjs/core'; 4 | import { BaseTranslateService } from '../service/translate'; 5 | import * as _ from 'lodash'; 6 | import { RESCODE } from '@cool-midway/core'; 7 | /** 8 | * 翻译中间件 9 | */ 10 | @Middleware() 11 | export class BaseTranslateMiddleware 12 | implements IMiddleware 13 | { 14 | @Inject() 15 | baseTranslateService: BaseTranslateService; 16 | 17 | @Inject() 18 | logger: ILogger; 19 | 20 | @Config('cool.i18n') 21 | config: { 22 | /** 是否开启 */ 23 | enable: boolean; 24 | /** 语言 */ 25 | languages: string[]; 26 | /** 翻译服务 */ 27 | serviceUrl?: string; 28 | }; 29 | 30 | resolve() { 31 | return async (ctx, next: NextFunction) => { 32 | const url = ctx.url; 33 | const language = ctx.get('language'); 34 | let data; 35 | try { 36 | data = await next(); 37 | } catch (error) { 38 | this.logger.error(error); 39 | // 处理翻译消息 40 | if (error.name == 'CoolCommException') { 41 | if (error.message && error.message !== 'success') { 42 | ctx.status = error.statusCode || 200; 43 | ctx.body = { 44 | code: RESCODE.COMMFAIL, 45 | message: await this.baseTranslateService.translate( 46 | 'msg', 47 | language, 48 | error.message 49 | ), 50 | }; 51 | return; 52 | } 53 | } 54 | ctx.status = 200; 55 | ctx.body = { 56 | code: RESCODE.COMMFAIL, 57 | message: error.message, 58 | }; 59 | return; 60 | } 61 | if (!this.config.enable) { 62 | return; 63 | } 64 | // 处理菜单翻译 65 | if (url == '/admin/base/comm/permmenu') { 66 | for (const menu of data.data.menus) { 67 | if (menu.name) { 68 | menu.name = await this.baseTranslateService.translate( 69 | 'menu', 70 | language, 71 | menu.name 72 | ); 73 | } 74 | } 75 | } 76 | if (url == '/admin/base/sys/menu/list') { 77 | for (const menu of data.data) { 78 | if (menu.name) { 79 | menu.name = await this.baseTranslateService.translate( 80 | 'menu', 81 | language, 82 | menu.name 83 | ); 84 | } 85 | } 86 | } 87 | // 处理字典翻译 88 | if (url == '/admin/dict/info/list') { 89 | for (const dict of data.data) { 90 | dict.name = await this.baseTranslateService.translate( 91 | 'dict:info', 92 | language, 93 | dict.name 94 | ); 95 | } 96 | } 97 | if (url == '/admin/dict/type/page') { 98 | for (const dict of data.data.list) { 99 | dict.name = await this.baseTranslateService.translate( 100 | 'dict:type', 101 | language, 102 | dict.name 103 | ); 104 | } 105 | } 106 | if (url == '/admin/dict/info/data' || url == '/app/dict/info/data') { 107 | for (const key in data.data) { 108 | for (const item of data.data[key]) { 109 | item.name = await this.baseTranslateService.translate( 110 | 'dict:info', 111 | language, 112 | item.name 113 | ); 114 | } 115 | } 116 | } 117 | }; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /src/modules/base/service/coding.ts: -------------------------------------------------------------------------------- 1 | import { App, IMidwayApplication, Init, Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | 6 | /** 7 | * Ai编码 8 | */ 9 | @Provide() 10 | export class BaseCodingService extends BaseService { 11 | @App() 12 | app: IMidwayApplication; 13 | 14 | /** 15 | * 获得模块目录结构 16 | */ 17 | async getModuleTree() { 18 | if (this.app.getEnv() !== 'local') { 19 | return []; 20 | } 21 | 22 | const moduleDir = await this.app.getBaseDir(); 23 | const modulesPath = path.join(moduleDir, 'modules'); 24 | // 返回modules下有多少个模块 25 | const modules = fs.readdirSync(modulesPath); 26 | return modules.filter(module => module !== '.DS_Store'); 27 | } 28 | 29 | /** 30 | * 创建代码 31 | * @param codes 代码 32 | */ 33 | async createCode( 34 | codes: { 35 | path: string; 36 | content: string; 37 | }[] 38 | ) { 39 | if (this.app.getEnv() !== 'local') { 40 | throw new Error('只能在开发环境下创建代码'); 41 | } 42 | 43 | const moduleDir = this.app.getAppDir(); 44 | 45 | for (const code of codes) { 46 | // 格式化代码内容 47 | const formattedContent = await this.formatContent(code.content); 48 | 49 | // 获取完整的文件路径 50 | const filePath = path.join(moduleDir, code.path); 51 | 52 | // 确保目录存在 53 | const dirPath = path.dirname(filePath); 54 | if (!fs.existsSync(dirPath)) { 55 | fs.mkdirSync(dirPath, { recursive: true }); 56 | } 57 | 58 | // 写入文件 59 | fs.writeFileSync(filePath, formattedContent, 'utf8'); 60 | } 61 | } 62 | 63 | /** 64 | * 格式化内容 65 | * @param content 66 | */ 67 | async formatContent(content: string) { 68 | // 使用prettier格式化内容 69 | const prettier = require('prettier'); 70 | return prettier.format(content, { 71 | parser: 'typescript', 72 | singleQuote: true, 73 | trailingComma: 'all', 74 | bracketSpacing: true, 75 | arrowParens: 'avoid', 76 | printWidth: 80, 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/conf.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { Repository } from 'typeorm'; 5 | import { BaseSysConfEntity } from '../../entity/sys/conf'; 6 | 7 | /** 8 | * 系统配置 9 | */ 10 | @Provide() 11 | export class BaseSysConfService extends BaseService { 12 | @InjectEntityModel(BaseSysConfEntity) 13 | baseSysConfEntity: Repository; 14 | 15 | /** 16 | * 获得配置参数值 17 | * @param key 18 | */ 19 | async getValue(key) { 20 | const conf = await this.baseSysConfEntity.findOneBy({ cKey: key }); 21 | if (conf) { 22 | return conf.cValue; 23 | } 24 | } 25 | 26 | /** 27 | * 更新配置参数 28 | * @param cKey 29 | * @param cValue 30 | */ 31 | async updateVaule(cKey, cValue) { 32 | await this.baseSysConfEntity 33 | .createQueryBuilder() 34 | .update() 35 | .where({ cKey }) 36 | .set({ cKey, cValue }) 37 | .execute(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/data.ts: -------------------------------------------------------------------------------- 1 | import { DataSource } from 'typeorm'; 2 | 3 | export class TempDataSource extends DataSource { 4 | /** 5 | * 重新构造元数据 6 | */ 7 | async buildMetadatas() { 8 | await super.buildMetadatas(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/department.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { In, Repository } from 'typeorm'; 5 | import { BaseSysDepartmentEntity } from '../../entity/sys/department'; 6 | import * as _ from 'lodash'; 7 | import { BaseSysRoleDepartmentEntity } from '../../entity/sys/role_department'; 8 | import { BaseSysPermsService } from './perms'; 9 | import { BaseSysUserEntity } from '../../entity/sys/user'; 10 | 11 | /** 12 | * 描述 13 | */ 14 | @Provide() 15 | export class BaseSysDepartmentService extends BaseService { 16 | @InjectEntityModel(BaseSysDepartmentEntity) 17 | baseSysDepartmentEntity: Repository; 18 | 19 | @InjectEntityModel(BaseSysUserEntity) 20 | baseSysUserEntity: Repository; 21 | 22 | @InjectEntityModel(BaseSysRoleDepartmentEntity) 23 | baseSysRoleDepartmentEntity: Repository; 24 | 25 | @Inject() 26 | baseSysPermsService: BaseSysPermsService; 27 | 28 | @Inject() 29 | ctx; 30 | 31 | /** 32 | * 获得部门菜单 33 | */ 34 | async list() { 35 | // 部门权限 36 | const permsDepartmentArr = await this.baseSysPermsService.departmentIds( 37 | this.ctx.admin.userId 38 | ); 39 | 40 | // 过滤部门权限 41 | const find = this.baseSysDepartmentEntity.createQueryBuilder('a'); 42 | if (this.ctx.admin.username !== 'admin') { 43 | find.andWhere('a.id in (:...ids)', { 44 | ids: !_.isEmpty(permsDepartmentArr) ? permsDepartmentArr : [null], 45 | }); 46 | find.orWhere('a.userId = :userId', { userId: this.ctx.admin.userId }); 47 | } 48 | find.addOrderBy('a.orderNum', 'ASC'); 49 | const departments: BaseSysDepartmentEntity[] = await find.getMany(); 50 | 51 | if (!_.isEmpty(departments)) { 52 | departments.forEach(e => { 53 | const parentMenu = departments.filter(m => { 54 | e.parentId = parseInt(e.parentId + ''); 55 | if (e.parentId == m.id) { 56 | return m.name; 57 | } 58 | }); 59 | if (!_.isEmpty(parentMenu)) { 60 | e.parentName = parentMenu[0].name; 61 | } 62 | }); 63 | } 64 | return departments; 65 | } 66 | 67 | /** 68 | * 根据多个ID获得部门权限信息 69 | * @param {[]} roleIds 数组 70 | * @param isAdmin 是否超管 71 | */ 72 | async getByRoleIds(roleIds: number[], isAdmin) { 73 | if (!_.isEmpty(roleIds)) { 74 | if (isAdmin) { 75 | const result = await this.baseSysDepartmentEntity.find(); 76 | return result.map(e => { 77 | return e.id; 78 | }); 79 | } 80 | const result = await this.baseSysRoleDepartmentEntity 81 | .createQueryBuilder('a') 82 | .where('a.roleId in (:...roleIds)', { roleIds }) 83 | .getMany(); 84 | if (!_.isEmpty(result)) { 85 | return _.uniq( 86 | result.map(e => { 87 | return e.departmentId; 88 | }) 89 | ); 90 | } 91 | } 92 | return []; 93 | } 94 | 95 | /** 96 | * 部门排序 97 | * @param params 98 | */ 99 | async order(params) { 100 | for (const e of params) { 101 | await this.baseSysDepartmentEntity.update(e.id, e); 102 | } 103 | } 104 | 105 | /** 106 | * 删除 107 | */ 108 | async delete(ids: number[]) { 109 | const { deleteUser } = this.ctx.request.body; 110 | await super.delete(ids); 111 | if (deleteUser) { 112 | await this.baseSysUserEntity.delete({ departmentId: In(ids) }); 113 | } else { 114 | const topDepartment = await this.baseSysDepartmentEntity 115 | .createQueryBuilder('a') 116 | .where('a.parentId is null') 117 | .getOne(); 118 | if (topDepartment) { 119 | await this.baseSysUserEntity.update( 120 | { departmentId: In(ids) }, 121 | { departmentId: topDepartment.id } 122 | ); 123 | } 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/log.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { LessThan, Repository } from 'typeorm'; 5 | import * as _ from 'lodash'; 6 | import { BaseSysLogEntity } from '../../entity/sys/log'; 7 | import * as moment from 'moment'; 8 | import { Utils } from '../../../../comm/utils'; 9 | import { BaseSysConfService } from './conf'; 10 | import { Context } from '@midwayjs/koa'; 11 | 12 | /** 13 | * 描述 14 | */ 15 | @Provide() 16 | export class BaseSysLogService extends BaseService { 17 | @Inject() 18 | ctx; 19 | 20 | @Inject() 21 | utils: Utils; 22 | 23 | @InjectEntityModel(BaseSysLogEntity) 24 | baseSysLogEntity: Repository; 25 | 26 | @Inject() 27 | baseSysConfService: BaseSysConfService; 28 | 29 | /** 30 | * 记录 31 | * @param url URL地址 32 | * @param params 参数 33 | * @param userId 用户ID 34 | */ 35 | async record(context: Context, url, params, userId) { 36 | const ip = await this.utils.getReqIP(context); 37 | const sysLog = new BaseSysLogEntity(); 38 | sysLog.userId = userId; 39 | sysLog.ip = typeof ip === 'string' ? ip : ip.join(','); 40 | sysLog.action = url.split('?')[0]; 41 | sysLog.params = params; 42 | await this.baseSysLogEntity.insert(sysLog); 43 | } 44 | 45 | /** 46 | * 日志 47 | * @param isAll 是否清除全部 48 | */ 49 | async clear(isAll?) { 50 | if (isAll) { 51 | await this.baseSysLogEntity.clear(); 52 | return; 53 | } 54 | const keepDay = await this.baseSysConfService.getValue('logKeep'); 55 | if (keepDay) { 56 | const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate(); 57 | await this.baseSysLogEntity.delete({ createTime: LessThan(beforeDate) }); 58 | } else { 59 | await this.baseSysLogEntity.clear(); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/param.ts: -------------------------------------------------------------------------------- 1 | import { InjectClient, Provide } from '@midwayjs/core'; 2 | import { BaseService, CoolCommException } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { Not, Repository } from 'typeorm'; 5 | import { BaseSysParamEntity } from '../../entity/sys/param'; 6 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 7 | 8 | /** 9 | * 参数配置 10 | */ 11 | @Provide() 12 | export class BaseSysParamService extends BaseService { 13 | @InjectEntityModel(BaseSysParamEntity) 14 | baseSysParamEntity: Repository; 15 | 16 | @InjectClient(CachingFactory, 'default') 17 | midwayCache: MidwayCache; 18 | 19 | /** 20 | * 根据key获得对应的参数 21 | * @param key 22 | */ 23 | async dataByKey(key) { 24 | let result: any = await this.midwayCache.get(`param:${key}`); 25 | if (!result) { 26 | result = await this.baseSysParamEntity.findOneBy({ keyName: key }); 27 | this.midwayCache.set(`param:${key}`, result); 28 | } 29 | if (result) { 30 | if (result.dataType == 0) { 31 | try { 32 | return JSON.parse(result.data); 33 | } catch (error) { 34 | return result.data; 35 | } 36 | } 37 | if (result.dataType == 1) { 38 | return result.data; 39 | } 40 | if (result.dataType == 2) { 41 | return result.data.split(','); 42 | } 43 | } 44 | return; 45 | } 46 | 47 | /** 48 | * 信息 49 | * @param id 50 | * @param infoIgnoreProperty 51 | * @returns 52 | */ 53 | async info(id: any, infoIgnoreProperty?: string[]): Promise { 54 | const info = await super.info(id, infoIgnoreProperty); 55 | try { 56 | info.data = JSON.parse(info.data.replace(/{/g, '[').replace(/}/g, ']')); 57 | } catch (error) { 58 | info.data = info.data; 59 | } 60 | return info; 61 | } 62 | 63 | /** 64 | * 根据key获得对应的网页数据 65 | * @param key 66 | */ 67 | async htmlByKey(key) { 68 | let html = '@title@content'; 69 | let result: any = await this.midwayCache.get(`param:${key}`); 70 | if (result) { 71 | html = html 72 | .replace('@content', result.data) 73 | .replace('@title', result.name); 74 | } else { 75 | html = html.replace('@content', 'key notfound'); 76 | } 77 | return html; 78 | } 79 | 80 | /** 81 | * 添加或者修改 82 | * @param param 83 | */ 84 | async addOrUpdate(param: any, type): Promise { 85 | if (type == 2) { 86 | param.data = JSON.stringify(param.data.replace()); 87 | } 88 | const find = { 89 | keyName: param.keyName, 90 | }; 91 | if (param.id) { 92 | find['id'] = Not(param.id); 93 | } 94 | const check = await this.baseSysParamEntity.findOneBy(find); 95 | if (check) { 96 | throw new CoolCommException('存在相同的keyName'); 97 | } 98 | await super.addOrUpdate(param, type); 99 | } 100 | 101 | /** 102 | * 重新初始化缓存 103 | */ 104 | async modifyAfter() { 105 | const params = await this.baseSysParamEntity.find(); 106 | for (const param of params) { 107 | await this.midwayCache.set(`param:${param.keyName}`, param); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/perms.ts: -------------------------------------------------------------------------------- 1 | import { Inject, InjectClient, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { BaseSysMenuService } from './menu'; 4 | import { BaseSysRoleService } from './role'; 5 | import { BaseSysDepartmentService } from './department'; 6 | import { Context } from '@midwayjs/koa'; 7 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 8 | import { BaseSysRoleEntity } from '../../entity/sys/role'; 9 | import { In, Repository } from 'typeorm'; 10 | import { InjectEntityModel } from '@midwayjs/typeorm'; 11 | 12 | /** 13 | * 权限 14 | */ 15 | @Provide() 16 | export class BaseSysPermsService extends BaseService { 17 | @InjectClient(CachingFactory, 'default') 18 | midwayCache: MidwayCache; 19 | 20 | @Inject() 21 | baseSysMenuService: BaseSysMenuService; 22 | 23 | @Inject() 24 | baseSysRoleService: BaseSysRoleService; 25 | 26 | @Inject() 27 | baseSysDepartmentService: BaseSysDepartmentService; 28 | 29 | @InjectEntityModel(BaseSysRoleEntity) 30 | baseSysRoleEntity: Repository; 31 | 32 | @Inject() 33 | ctx: Context; 34 | base: any; 35 | 36 | /** 37 | * 刷新权限 38 | * @param userId 用户ID 39 | */ 40 | async refreshPerms(userId) { 41 | const roleIds = await this.baseSysRoleService.getByUser(userId); 42 | const perms = await this.baseSysMenuService.getPerms(roleIds); 43 | await this.midwayCache.set(`admin:perms:${userId}`, perms); 44 | // 更新部门权限 45 | const departments = await this.baseSysDepartmentService.getByRoleIds( 46 | roleIds, 47 | await this.isAdmin(roleIds) 48 | ); 49 | await this.midwayCache.set(`admin:department:${userId}`, departments); 50 | } 51 | 52 | /** 53 | * 根据角色判断是不是超管 54 | * @param roleIds 55 | */ 56 | async isAdmin(roleIds: number[]) { 57 | const roles = await this.baseSysRoleEntity.findBy({ id: In(roleIds) }); 58 | const roleLabels = roles.map(item => item.label); 59 | return roleLabels.includes('admin'); 60 | } 61 | 62 | /** 63 | * 获得权限菜单 64 | * @param roleIds 65 | */ 66 | async permmenu(roleIds: number[]) { 67 | const perms = await this.baseSysMenuService.getPerms(roleIds); 68 | const menus = await this.baseSysMenuService.getMenus( 69 | roleIds, 70 | this.ctx.admin.username === 'admin' 71 | ); 72 | return { perms, menus }; 73 | } 74 | 75 | /** 76 | * 根据用户ID获得部门权限 77 | * @param userId 78 | * @return 部门ID数组 79 | */ 80 | async departmentIds(userId: number) { 81 | const department: any = await this.midwayCache.get( 82 | `admin:department:${userId}` 83 | ); 84 | if (department) { 85 | return department; 86 | } else { 87 | return []; 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/modules/base/service/sys/role.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { Repository } from 'typeorm'; 5 | import { BaseSysRoleEntity } from '../../entity/sys/role'; 6 | import { BaseSysUserRoleEntity } from '../../entity/sys/user_role'; 7 | import * as _ from 'lodash'; 8 | import { BaseSysRoleMenuEntity } from '../../entity/sys/role_menu'; 9 | import { BaseSysRoleDepartmentEntity } from '../../entity/sys/role_department'; 10 | import { BaseSysPermsService } from './perms'; 11 | import { Brackets } from 'typeorm'; 12 | 13 | /** 14 | * 角色 15 | */ 16 | @Provide() 17 | export class BaseSysRoleService extends BaseService { 18 | @InjectEntityModel(BaseSysRoleEntity) 19 | baseSysRoleEntity: Repository; 20 | 21 | @InjectEntityModel(BaseSysUserRoleEntity) 22 | baseSysUserRoleEntity: Repository; 23 | 24 | @InjectEntityModel(BaseSysRoleMenuEntity) 25 | baseSysRoleMenuEntity: Repository; 26 | 27 | @InjectEntityModel(BaseSysRoleDepartmentEntity) 28 | baseSysRoleDepartmentEntity: Repository; 29 | 30 | @Inject() 31 | baseSysPermsService: BaseSysPermsService; 32 | 33 | @Inject() 34 | ctx; 35 | 36 | /** 37 | * 根据用户ID获得所有用户角色 38 | * @param userId 39 | */ 40 | async getByUser(userId: number): Promise { 41 | const userRole = await this.baseSysUserRoleEntity.findBy({ userId }); 42 | if (!_.isEmpty(userRole)) { 43 | return userRole.map(e => { 44 | return e.roleId; 45 | }); 46 | } 47 | return []; 48 | } 49 | 50 | /** 51 | * 52 | * @param param 53 | */ 54 | async modifyAfter(param) { 55 | if (param.id) { 56 | this.updatePerms(param.id, param.menuIdList, param.departmentIdList); 57 | } 58 | } 59 | 60 | /** 61 | * 更新权限 62 | * @param roleId 63 | * @param menuIdList 64 | * @param departmentIds 65 | */ 66 | async updatePerms(roleId, menuIdList?, departmentIds = []) { 67 | // 更新菜单权限 68 | await this.baseSysRoleMenuEntity.delete({ roleId }); 69 | await Promise.all( 70 | menuIdList.map(async e => { 71 | return await this.baseSysRoleMenuEntity.save({ roleId, menuId: e }); 72 | }) 73 | ); 74 | // 更新部门权限 75 | await this.baseSysRoleDepartmentEntity.delete({ roleId }); 76 | await Promise.all( 77 | departmentIds.map(async e => { 78 | return await this.baseSysRoleDepartmentEntity.save({ 79 | roleId, 80 | departmentId: e, 81 | }); 82 | }) 83 | ); 84 | // 刷新权限 85 | const userRoles = await this.baseSysUserRoleEntity.findBy({ roleId }); 86 | for (const userRole of userRoles) { 87 | await this.baseSysPermsService.refreshPerms(userRole.userId); 88 | } 89 | } 90 | 91 | /** 92 | * 角色信息 93 | * @param id 94 | */ 95 | async info(id) { 96 | const info = await this.baseSysRoleEntity.findOneBy({ id }); 97 | if (info) { 98 | const menus = await this.baseSysRoleMenuEntity.findBy( 99 | id !== 1 ? { roleId: id } : {} 100 | ); 101 | const menuIdList = menus.map(e => { 102 | return parseInt(e.menuId + ''); 103 | }); 104 | const departments = await this.baseSysRoleDepartmentEntity.findBy( 105 | id !== 1 ? { roleId: id } : {} 106 | ); 107 | const departmentIdList = departments.map(e => { 108 | return parseInt(e.departmentId + ''); 109 | }); 110 | return { 111 | ...info, 112 | menuIdList, 113 | departmentIdList, 114 | }; 115 | } 116 | return {}; 117 | } 118 | 119 | async list() { 120 | return this.baseSysRoleEntity 121 | .createQueryBuilder('a') 122 | .where( 123 | new Brackets(qb => { 124 | qb.where('a.id !=:id', { id: 1 }); // 超级管理员的角色不展示 125 | // 如果不是超管,只能看到自己新建的或者自己有的角色 126 | if (this.ctx.admin.username !== 'admin') { 127 | qb.andWhere('(a.userId=:userId or a.id in (:...roleId))', { 128 | userId: this.ctx.admin.userId, 129 | roleId: this.ctx.admin.roleIds, 130 | }); 131 | } 132 | }) 133 | ) 134 | .getMany(); 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/modules/demo/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default () => { 7 | return { 8 | // 模块名称 9 | name: 'demo模块', 10 | // 模块描述 11 | description: '演示用', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | } as ModuleConfig; 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/demo/controller/admin/goods.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { DemoGoodsEntity } from '../../entity/goods'; 3 | import { UserInfoEntity } from '../../../user/entity/info'; 4 | import { DemoGoodsService } from '../../service/goods'; 5 | 6 | /** 7 | * 商品模块-商品信息 8 | */ 9 | @CoolController({ 10 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 11 | entity: DemoGoodsEntity, 12 | service: DemoGoodsService, 13 | pageQueryOp: { 14 | keyWordLikeFields: ['a.description'], 15 | fieldEq: ['a.status'], 16 | fieldLike: ['a.title'], 17 | select: ['a.*', 'b.nickName as userName'], 18 | join: [ 19 | { 20 | entity: UserInfoEntity, 21 | alias: 'b', 22 | condition: 'a.id = b.id', 23 | }, 24 | ], 25 | }, 26 | }) 27 | export class AdminDemoGoodsController extends BaseController {} 28 | -------------------------------------------------------------------------------- /src/modules/demo/controller/admin/tenant.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { DemoGoodsEntity } from '../../entity/goods'; 3 | import { DemoTenantService } from '../../service/tenant'; 4 | 5 | /** 6 | * 多租户 7 | */ 8 | @CoolController({ 9 | serviceApis: [ 10 | 'use', 11 | { 12 | method: 'noUse', 13 | summary: '不使用多租户', 14 | }, 15 | { 16 | method: 'noTenant', 17 | summary: '局部不使用多租户', 18 | }, 19 | ], 20 | entity: DemoGoodsEntity, 21 | service: DemoTenantService, 22 | }) 23 | export class AdminDemoTenantController extends BaseController {} 24 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/cache.ts: -------------------------------------------------------------------------------- 1 | import { DemoCacheService } from '../../service/cache'; 2 | import { Inject, Post, Provide, Get, InjectClient } from '@midwayjs/core'; 3 | import { CoolController, BaseController } from '@cool-midway/core'; 4 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 5 | 6 | /** 7 | * 缓存 8 | */ 9 | @CoolController() 10 | export class OpenDemoCacheController extends BaseController { 11 | @InjectClient(CachingFactory, 'default') 12 | midwayCache: MidwayCache; 13 | 14 | @Inject() 15 | demoCacheService: DemoCacheService; 16 | 17 | /** 18 | * 设置缓存 19 | * @returns 20 | */ 21 | @Post('/set', { summary: '设置缓存' }) 22 | async set() { 23 | await this.midwayCache.set('a', 1); 24 | // 缓存10秒 25 | await this.midwayCache.set('a', 1, 10 * 1000); 26 | return this.ok(await this.midwayCache.get('a')); 27 | } 28 | 29 | /** 30 | * 获得缓存 31 | * @returns 32 | */ 33 | @Get('/get', { summary: '获得缓存' }) 34 | async get() { 35 | return this.ok(await this.demoCacheService.get()); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/event.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Post } from '@midwayjs/core'; 2 | import { 3 | CoolController, 4 | BaseController, 5 | CoolEventManager, 6 | } from '@cool-midway/core'; 7 | 8 | /** 9 | * 事件 10 | */ 11 | @CoolController() 12 | export class OpenDemoEventController extends BaseController { 13 | @Inject() 14 | coolEventManager: CoolEventManager; 15 | 16 | @Post('/comm', { summary: '普通事件,本进程生效' }) 17 | async comm() { 18 | await this.coolEventManager.emit('demo', { a: 2 }, 1); 19 | return this.ok(); 20 | } 21 | 22 | @Post('/global', { summary: '全局事件,多进程都有效' }) 23 | async global() { 24 | await this.coolEventManager.globalEmit('demo', false, { a: 2 }, 1); 25 | return this.ok(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/goods.ts: -------------------------------------------------------------------------------- 1 | import { DemoGoodsService } from '../../service/goods'; 2 | import { DemoGoodsEntity } from '../../entity/goods'; 3 | import { Body, Inject, Post } from '@midwayjs/core'; 4 | import { CoolController, BaseController } from '@cool-midway/core'; 5 | import { InjectEntityModel } from '@midwayjs/typeorm'; 6 | import { Repository } from 'typeorm'; 7 | 8 | /** 9 | * 测试 10 | */ 11 | @CoolController({ 12 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 13 | entity: DemoGoodsEntity, 14 | service: DemoGoodsService, 15 | pageQueryOp: { 16 | fieldLike: ['title'], 17 | }, 18 | }) 19 | export class OpenDemoGoodsController extends BaseController { 20 | @InjectEntityModel(DemoGoodsEntity) 21 | demoGoodsEntity: Repository; 22 | 23 | @Inject() 24 | demoGoodsService: DemoGoodsService; 25 | 26 | @Post('/sqlPage', { summary: 'sql分页查询' }) 27 | async sqlPage(@Body() query) { 28 | return this.ok(await this.demoGoodsService.sqlPage(query)); 29 | } 30 | 31 | @Post('/entityPage', { summary: 'entity分页查询' }) 32 | async entityPage(@Body() query) { 33 | return this.ok(await this.demoGoodsService.entityPage(query)); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/i18n.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { DemoI18nService } from '../../service/i18n'; 3 | 4 | /** 5 | * 国际化 6 | */ 7 | @CoolController({ 8 | serviceApis: [ 9 | { 10 | method: 'en', 11 | summary: '翻译成英文', 12 | }, 13 | { 14 | method: 'tw', 15 | summary: '翻译成繁体', 16 | }, 17 | ], 18 | service: DemoI18nService, 19 | }) 20 | export class DemoI18nController extends BaseController {} 21 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/plugin.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { PluginService } from '../../../plugin/service/info'; 3 | import { Get, Inject } from '@midwayjs/core'; 4 | 5 | /** 6 | * 插件 7 | */ 8 | @CoolController() 9 | export class OpenDemoPluginController extends BaseController { 10 | @Inject() 11 | pluginService: PluginService; 12 | 13 | @Get('/invoke', { summary: '调用插件' }) 14 | async invoke() { 15 | // 获取插件实例 16 | const instance: any = await this.pluginService.getInstance('ollama'); 17 | // 调用chat 18 | const messages = [ 19 | { role: 'system', content: '你叫小酷,是一个智能助理' }, 20 | { role: 'user', content: '写一个1000字的关于春天的文章' }, 21 | ]; 22 | for (let i = 0; i < 3; i++) { 23 | instance.chat(messages, { stream: true }, res => { 24 | console.log(i, res.content); 25 | }); 26 | } 27 | return this.ok(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/queue.ts: -------------------------------------------------------------------------------- 1 | import { Get, Inject, Post, Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { DemoCommQueue } from '../../queue/comm'; 4 | import { DemoGetterQueue } from '../../queue/getter'; 5 | 6 | /** 7 | * 队列 8 | */ 9 | @CoolController() 10 | export class OpenDemoQueueController extends BaseController { 11 | // 普通队列 12 | @Inject() 13 | demoCommQueue: DemoCommQueue; 14 | 15 | // 主动消费队列 16 | @Inject() 17 | demoGetterQueue: DemoGetterQueue; 18 | 19 | /** 20 | * 发送数据到队列 21 | */ 22 | @Post('/add', { summary: '发送队列数据' }) 23 | async queue() { 24 | this.demoCommQueue.add({ a: 2 }); 25 | return this.ok(); 26 | } 27 | 28 | @Post('/addGetter') 29 | async addGetter() { 30 | await this.demoGetterQueue.add({ a: new Date() }); 31 | return this.ok(); 32 | } 33 | 34 | /** 35 | * 获得队列中的数据,只有当队列类型为getter时有效 36 | */ 37 | @Get('/getter') 38 | async getter() { 39 | const job = await this.demoGetterQueue.getters.getJobs( 40 | ['wait'], 41 | 0, 42 | 0, 43 | true 44 | ); 45 | // 获得完将数据从队列移除 46 | await job[0]?.remove(); 47 | return this.ok(job[0]?.data); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/rpc.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide, Get } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { DemoRpcService } from '../../service/rpc'; 4 | 5 | /** 6 | * 远程RPC调用 7 | */ 8 | @CoolController() 9 | export class OpenDemoRpcController extends BaseController { 10 | @Inject() 11 | demoRpcService: DemoRpcService; 12 | 13 | @Get('/call', { summary: '远程调用' }) 14 | async call() { 15 | return this.ok(await this.demoRpcService.call()); 16 | } 17 | 18 | @Get('/event', { summary: '集群事件' }) 19 | async event() { 20 | await this.demoRpcService.event(); 21 | return this.ok(); 22 | } 23 | 24 | @Get('/transaction', { summary: '分布式事务' }) 25 | async transaction() { 26 | await this.demoRpcService.transaction({ a: 1 }); 27 | return this.ok(); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/sse.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { Get, Inject } from '@midwayjs/core'; 3 | import { PluginService } from '../../../plugin/service/info'; 4 | import { PassThrough } from 'stream'; 5 | import { IMidwayKoaContext } from '@midwayjs/koa'; 6 | 7 | /** 8 | * 事件流 服务端主动推送 9 | */ 10 | @CoolController() 11 | export class OpenDemoSSEController extends BaseController { 12 | @Inject() 13 | ctx: IMidwayKoaContext; 14 | 15 | @Inject() 16 | pluginService: PluginService; 17 | 18 | @Get('/call', { summary: '事件流 服务端主动推送' }) 19 | async call() { 20 | // 设置响应头 21 | this.ctx.set('Content-Type', 'text/event-stream'); 22 | this.ctx.set('Cache-Control', 'no-cache'); 23 | this.ctx.set('Connection', 'keep-alive'); 24 | 25 | const stream = new PassThrough(); 26 | 27 | // 发送数据 28 | const send = (data: any) => { 29 | stream.write(`data: ${JSON.stringify(data)}\n\n`); 30 | }; 31 | 32 | // 获取插件实例 33 | const instance: any = await this.pluginService.getInstance('ollama'); 34 | // 调用chat 35 | const messages = [ 36 | { role: 'system', content: '你叫小酷,是个编程助手' }, 37 | { role: 'user', content: '用js写个Hello World' }, 38 | ]; 39 | instance.chat(messages, { stream: true }, res => { 40 | send(res); 41 | if (res.isEnd) { 42 | this.ctx.res.end(); 43 | } 44 | }); 45 | 46 | this.ctx.status = 200; 47 | this.ctx.body = stream; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/tenant.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { DemoGoodsEntity } from '../../entity/goods'; 3 | import { DemoTenantService } from '../../service/tenant'; 4 | 5 | /** 6 | * 多租户 7 | */ 8 | @CoolController({ 9 | api: [], 10 | entity: DemoGoodsEntity, 11 | service: DemoTenantService, 12 | }) 13 | export class OpenDemoTenantController extends BaseController {} 14 | -------------------------------------------------------------------------------- /src/modules/demo/controller/open/transaction.ts: -------------------------------------------------------------------------------- 1 | import { DemoGoodsEntity } from '../../entity/goods'; 2 | import { Provide } from '@midwayjs/core'; 3 | import { CoolController, BaseController } from '@cool-midway/core'; 4 | import { DemoTransactionService } from '../../service/transaction'; 5 | 6 | /** 7 | * 事务 8 | */ 9 | @CoolController({ 10 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 11 | entity: DemoGoodsEntity, 12 | service: DemoTransactionService, 13 | }) 14 | export class OpenDemoTransactionController extends BaseController {} 15 | -------------------------------------------------------------------------------- /src/modules/demo/entity/goods.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, transformerJson } from '../../base/entity/base'; 2 | import { Column, Entity, Index } from 'typeorm'; 3 | 4 | /** 5 | * 商品模块-商品信息 6 | */ 7 | @Entity('demo_goods') 8 | export class DemoGoodsEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '标题', length: 50 }) 11 | title: string; 12 | 13 | @Column({ 14 | comment: '价格', 15 | type: 'decimal', 16 | precision: 5, 17 | scale: 2, 18 | }) 19 | price: number; 20 | 21 | @Column({ comment: '描述', nullable: true }) 22 | description: string; 23 | 24 | @Column({ comment: '主图', nullable: true }) 25 | mainImage: string; 26 | 27 | @Column({ comment: '分类', dict: 'goodsType' }) 28 | type: number; 29 | 30 | @Column({ comment: '状态', dict: ['禁用', '启用'], default: 1 }) 31 | status: number; 32 | 33 | @Column({ 34 | comment: '示例图', 35 | nullable: true, 36 | type: 'json', 37 | transformer: transformerJson, 38 | }) 39 | exampleImages: string[]; 40 | 41 | @Column({ comment: '库存', default: 0 }) 42 | stock: number; 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/demo/event/comm.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, Event } from '@cool-midway/core'; 2 | import { EVENT_PLUGIN_READY } from '../../plugin/service/center'; 3 | 4 | /** 5 | * 普通事件 6 | */ 7 | @CoolEvent() 8 | export class DemoCommEvent { 9 | /** 10 | * 根据事件名接收事件 11 | * @param msg 12 | * @param a 13 | */ 14 | @Event('demo') 15 | async demo(msg, a) { 16 | console.log(`comm当前进程的ID是: ${process.pid}`); 17 | console.log('comm收到消息', msg, a); 18 | } 19 | 20 | /** 21 | * 插件已就绪 22 | */ 23 | @Event(EVENT_PLUGIN_READY) 24 | async pluginReady() { 25 | // TODO 插件已就绪 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/modules/demo/queue/comm.ts: -------------------------------------------------------------------------------- 1 | import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; 2 | import { IMidwayApplication } from '@midwayjs/core'; 3 | import { App } from '@midwayjs/core'; 4 | 5 | /** 6 | * 普通队列 7 | */ 8 | @CoolQueue() 9 | export class DemoCommQueue extends BaseCoolQueue { 10 | @App() 11 | app: IMidwayApplication; 12 | 13 | async data(job: any, done: any): Promise { 14 | // 这边可以执行定时任务具体的业务或队列的业务 15 | console.log('数据', job.data); 16 | // 抛出错误 可以让队列重试,默认重试5次 17 | //throw new Error('错误'); 18 | done(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/demo/queue/getter.ts: -------------------------------------------------------------------------------- 1 | import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; 2 | 3 | /** 4 | * 主动消费队列 5 | */ 6 | @CoolQueue({ type: 'getter' }) 7 | export class DemoGetterQueue extends BaseCoolQueue {} 8 | -------------------------------------------------------------------------------- /src/modules/demo/queue/single.ts: -------------------------------------------------------------------------------- 1 | import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; 2 | import { IMidwayApplication } from '@midwayjs/core'; 3 | import { App } from '@midwayjs/core'; 4 | 5 | /** 6 | * 单例队列,cluster 或 集群模式下 只会有一个实例消费数据 7 | */ 8 | @CoolQueue({ type: 'single' }) 9 | export class DemoSingleQueue extends BaseCoolQueue { 10 | @App() 11 | app: IMidwayApplication; 12 | 13 | async data(job: any, done: any): Promise { 14 | // 这边可以执行定时任务具体的业务或队列的业务 15 | console.log('数据', job.data); 16 | // 抛出错误 可以让队列重试,默认重试5次 17 | //throw new Error('错误'); 18 | done(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/modules/demo/service/cache.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { CoolCache } from '@cool-midway/core'; 3 | 4 | /** 5 | * 缓存 6 | */ 7 | @Provide() 8 | export class DemoCacheService { 9 | // 数据缓存5秒 10 | @CoolCache(5000) 11 | async get() { 12 | console.log('执行方法'); 13 | return { 14 | a: 1, 15 | b: 2, 16 | }; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/demo/service/goods.ts: -------------------------------------------------------------------------------- 1 | import { DemoGoodsEntity } from './../entity/goods'; 2 | import { Inject, Provide } from '@midwayjs/core'; 3 | import { BaseService } from '@cool-midway/core'; 4 | import { InjectEntityModel } from '@midwayjs/typeorm'; 5 | import { Repository } from 'typeorm'; 6 | 7 | /** 8 | * 商品示例 9 | */ 10 | @Provide() 11 | export class DemoGoodsService extends BaseService { 12 | @InjectEntityModel(DemoGoodsEntity) 13 | demoGoodsEntity: Repository; 14 | 15 | @Inject() 16 | ctx; 17 | 18 | /** 19 | * 执行sql分页 20 | */ 21 | async sqlPage(query) { 22 | await this.demoGoodsEntity.save({ 23 | id: 1, 24 | title: '标题', 25 | price: 99.0, 26 | description: '商品描述', 27 | mainImage: 'https://cool-js.com/logo.png', 28 | }); 29 | return this.sqlRenderPage( 30 | 'select * from demo_goods ORDER BY id ASC', 31 | query, 32 | false 33 | ); 34 | } 35 | 36 | /** 37 | * 执行entity分页 38 | */ 39 | async entityPage(query) { 40 | const find = this.demoGoodsEntity.createQueryBuilder(); 41 | return this.entityRenderPage(find, query); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/modules/demo/service/i18n.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide } from '@midwayjs/core'; 2 | import { BaseTranslateService } from '../../base/service/translate'; 3 | 4 | /** 5 | * 国际化服务 6 | */ 7 | @Provide() 8 | export class DemoI18nService { 9 | @Inject() 10 | translate: BaseTranslateService; 11 | 12 | /** 13 | * 翻译成英文 14 | */ 15 | async en() { 16 | const value = this.translate.comm('一个很Cool的框架')['en']; 17 | console.log(value); 18 | return value; 19 | } 20 | 21 | /** 22 | * 翻译成繁体 23 | */ 24 | async tw() { 25 | const value = this.translate.comm('一个很Cool的框架')['zh-tw']; 26 | console.log(value); 27 | return value; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/modules/demo/service/rpc.ts: -------------------------------------------------------------------------------- 1 | import { App, Provide } from '@midwayjs/core'; 2 | import { DemoGoodsEntity } from '../entity/goods'; 3 | import { IMidwayApplication, Inject } from '@midwayjs/core'; 4 | import { 5 | BaseRpcService, 6 | CoolRpc, 7 | CoolRpcService, 8 | CoolRpcTransaction, 9 | } from '@cool-midway/rpc'; 10 | import { QueryRunner } from 'typeorm'; 11 | 12 | @Provide() 13 | @CoolRpcService({ 14 | entity: DemoGoodsEntity, 15 | method: ['info', 'add', 'page'], 16 | }) 17 | export class DemoRpcService extends BaseRpcService { 18 | @App() 19 | app: IMidwayApplication; 20 | 21 | @Inject() 22 | rpc: CoolRpc; 23 | 24 | /** 25 | * 远程调用 26 | * @returns 27 | */ 28 | async call() { 29 | return await this.rpc.call('goods', 'demoGoodsService', 'test', { 30 | a: 1, 31 | }); 32 | } 33 | 34 | /** 35 | * 集群事件 36 | */ 37 | async event() { 38 | this.rpc.event('test', { a: 1 }); 39 | } 40 | 41 | async info(params) { 42 | return params; 43 | } 44 | async getUser() { 45 | return { 46 | uid: '123', 47 | username: 'mockedName', 48 | phone: '12345678901', 49 | email: 'xxx.xxx@xxx.com', 50 | }; 51 | } 52 | 53 | @CoolRpcTransaction() 54 | async transaction(params, rpcTransactionId?, queryRunner?: QueryRunner) { 55 | console.log('获得的参数', params); 56 | const data = { 57 | title: '商品标题', 58 | pic: 'https://xxx', 59 | price: 99.0, 60 | type: 1, 61 | }; 62 | await queryRunner.manager.save(DemoGoodsEntity, data); 63 | 64 | // 将事务id传给调用的远程服务方法 65 | await this.rpc.call('goods', 'demoGoodsService', 'transaction', { 66 | rpcTransactionId, 67 | ...params, 68 | }); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/modules/demo/service/tenant.ts: -------------------------------------------------------------------------------- 1 | import { Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { Repository } from 'typeorm'; 5 | import { DemoGoodsEntity } from '../entity/goods'; 6 | import { noTenant } from '../../base/db/tenant'; 7 | 8 | /** 9 | * 商品服务 10 | */ 11 | @Provide() 12 | export class DemoTenantService extends BaseService { 13 | @InjectEntityModel(DemoGoodsEntity) 14 | demoGoodsEntity: Repository; 15 | 16 | @Inject() 17 | ctx; 18 | 19 | /** 20 | * 使用多租户 21 | */ 22 | async use() { 23 | await this.demoGoodsEntity.createQueryBuilder().getMany(); 24 | await this.demoGoodsEntity.find(); 25 | } 26 | 27 | /** 28 | * 不使用多租户(局部不使用) 29 | */ 30 | async noUse() { 31 | // 过滤多租户 32 | await this.demoGoodsEntity.createQueryBuilder().getMany(); 33 | // 被noTenant包裹,不会过滤多租户 34 | await noTenant(this.ctx, async () => { 35 | return await this.demoGoodsEntity.createQueryBuilder().getMany(); 36 | }); 37 | // 过滤多租户 38 | await this.demoGoodsEntity.find(); 39 | } 40 | 41 | /** 42 | * 无效多租户 43 | */ 44 | async invalid() { 45 | // 自定义sql,不进行多租户过滤 46 | await this.nativeQuery('select * from demo_goods'); 47 | // 自定义分页sql,不进行多租户过滤 48 | await this.sqlRenderPage('select * from demo_goods'); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/modules/demo/service/transaction.ts: -------------------------------------------------------------------------------- 1 | import { DemoGoodsEntity } from './../entity/goods'; 2 | import { Provide } from '@midwayjs/core'; 3 | import { BaseService, CoolTransaction } from '@cool-midway/core'; 4 | import { QueryRunner } from 'typeorm'; 5 | 6 | /** 7 | * 操作事务 8 | */ 9 | @Provide() 10 | export class DemoTransactionService extends BaseService { 11 | /** 12 | * 事务操作 13 | */ 14 | @CoolTransaction({ 15 | connectionName: 'default', 16 | }) 17 | async add(param, queryRunner?: QueryRunner) { 18 | await queryRunner.manager.insert(DemoGoodsEntity, param); 19 | return { 20 | id: param.id, 21 | }; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/modules/dict/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default () => { 7 | return { 8 | // 模块名称 9 | name: '字典管理', 10 | // 模块描述 11 | description: '数据字典等', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | } as ModuleConfig; 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/dict/controller/admin/info.ts: -------------------------------------------------------------------------------- 1 | import { DictInfoEntity } from './../../entity/info'; 2 | import { Body, Get, Inject, Post, Provide } from '@midwayjs/core'; 3 | import { 4 | CoolController, 5 | BaseController, 6 | CoolTag, 7 | TagTypes, 8 | } from '@cool-midway/core'; 9 | import { DictInfoService } from '../../service/info'; 10 | 11 | /** 12 | * 字典信息 13 | */ 14 | @Provide() 15 | @CoolController({ 16 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 17 | entity: DictInfoEntity, 18 | service: DictInfoService, 19 | listQueryOp: { 20 | fieldEq: ['typeId'], 21 | keyWordLikeFields: ['name'], 22 | addOrderBy: { 23 | createTime: 'ASC', 24 | }, 25 | }, 26 | }) 27 | export class AdminDictInfoController extends BaseController { 28 | @Inject() 29 | dictInfoService: DictInfoService; 30 | 31 | @Post('/data', { summary: '获得字典数据' }) 32 | async data(@Body('types') types: string[] = []) { 33 | return this.ok(await this.dictInfoService.data(types)); 34 | } 35 | 36 | @CoolTag(TagTypes.IGNORE_TOKEN) 37 | @Get('/types', { summary: '获得所有字典类型' }) 38 | async types() { 39 | return this.ok(await this.dictInfoService.types()); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/dict/controller/admin/type.ts: -------------------------------------------------------------------------------- 1 | import { DictTypeEntity } from './../../entity/type'; 2 | import { Provide } from '@midwayjs/core'; 3 | import { CoolController, BaseController } from '@cool-midway/core'; 4 | import { DictTypeService } from '../../service/type'; 5 | 6 | /** 7 | * 字典类型 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: DictTypeEntity, 13 | service: DictTypeService, 14 | listQueryOp: { 15 | keyWordLikeFields: ['name'], 16 | }, 17 | }) 18 | export class AdminDictTypeController extends BaseController {} 19 | -------------------------------------------------------------------------------- /src/modules/dict/controller/app/info.ts: -------------------------------------------------------------------------------- 1 | import { Body, Get, Inject, Post, Provide } from '@midwayjs/core'; 2 | import { 3 | CoolController, 4 | BaseController, 5 | CoolUrlTag, 6 | TagTypes, 7 | CoolTag, 8 | } from '@cool-midway/core'; 9 | import { DictInfoService } from '../../service/info'; 10 | 11 | /** 12 | * 字典信息 13 | */ 14 | @Provide() 15 | @CoolController() 16 | @CoolUrlTag() 17 | export class AppDictInfoController extends BaseController { 18 | @Inject() 19 | dictInfoService: DictInfoService; 20 | 21 | @CoolTag(TagTypes.IGNORE_TOKEN) 22 | @Post('/data', { summary: '获得字典数据' }) 23 | async data(@Body('types') types: string[] = []) { 24 | return this.ok(await this.dictInfoService.data(types)); 25 | } 26 | 27 | @CoolTag(TagTypes.IGNORE_TOKEN) 28 | @Get('/types', { summary: '获得所有字典类型' }) 29 | async types() { 30 | return this.ok(await this.dictInfoService.types()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/dict/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "dict_info": [ 3 | { 4 | "id": 21, 5 | "typeId": 19, 6 | "name": "COOL", 7 | "orderNum": 1, 8 | "remark": null, 9 | "parentId": null, 10 | "value": "cool" 11 | }, 12 | { 13 | "id": 22, 14 | "typeId": 19, 15 | "name": "闪酷", 16 | "orderNum": 2, 17 | "remark": null, 18 | "parentId": null, 19 | "value": "https://show.cool-admin.com/api/public/uploads/20230308/c731b0cba84046268b10edbbcf36f948_315c243a448e1369fa145c5ea3f020da.gif" 20 | }, 21 | { 22 | "id": 23, 23 | "typeId": 20, 24 | "name": "法师", 25 | "orderNum": 1, 26 | "remark": null, 27 | "parentId": null, 28 | "value": "4" 29 | }, 30 | { 31 | "id": 24, 32 | "typeId": 20, 33 | "name": "战士", 34 | "orderNum": 2, 35 | "remark": null, 36 | "parentId": null, 37 | "value": "3" 38 | }, 39 | { 40 | "id": 25, 41 | "typeId": 20, 42 | "name": "坦克", 43 | "orderNum": 3, 44 | "remark": null, 45 | "parentId": null, 46 | "value": "2" 47 | }, 48 | { 49 | "id": 26, 50 | "typeId": 20, 51 | "name": "刺客", 52 | "orderNum": 4, 53 | "remark": null, 54 | "parentId": null, 55 | "value": "1" 56 | }, 57 | { 58 | "id": 27, 59 | "typeId": 20, 60 | "name": "射手", 61 | "orderNum": 5, 62 | "remark": null, 63 | "parentId": null, 64 | "value": "0" 65 | }, 66 | { 67 | "id": 30, 68 | "typeId": 20, 69 | "name": "幻影刺客", 70 | "orderNum": 1, 71 | "remark": null, 72 | "parentId": 26, 73 | "value": "5" 74 | } 75 | ], 76 | "dict_type": [ 77 | { 78 | "id": 19, 79 | "name": "品牌", 80 | "key": "brand" 81 | }, 82 | { 83 | "id": 20, 84 | "name": "职业", 85 | "key": "occupation" 86 | } 87 | ] 88 | } -------------------------------------------------------------------------------- /src/modules/dict/entity/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 字典信息 6 | */ 7 | @Entity('dict_info') 8 | export class DictInfoEntity extends BaseEntity { 9 | @Column({ comment: '类型ID' }) 10 | typeId: number; 11 | 12 | @Column({ comment: '名称' }) 13 | name: string; 14 | 15 | @Column({ comment: '值', nullable: true }) 16 | value: string; 17 | 18 | @Column({ comment: '排序', default: 0 }) 19 | orderNum: number; 20 | 21 | @Column({ comment: '备注', nullable: true }) 22 | remark: string; 23 | 24 | @Column({ comment: '父ID', default: null }) 25 | parentId: number; 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/dict/entity/type.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 字典类别 6 | */ 7 | @Entity('dict_type') 8 | export class DictTypeEntity extends BaseEntity { 9 | @Column({ comment: '名称' }) 10 | name: string; 11 | 12 | @Column({ comment: '标识' }) 13 | key: string; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/dict/service/info.ts: -------------------------------------------------------------------------------- 1 | import { DictTypeEntity } from './../entity/type'; 2 | import { DictInfoEntity } from './../entity/info'; 3 | import { Config, Provide } from '@midwayjs/core'; 4 | import { BaseService } from '@cool-midway/core'; 5 | import { InjectEntityModel } from '@midwayjs/typeorm'; 6 | import { Repository, In } from 'typeorm'; 7 | import * as _ from 'lodash'; 8 | 9 | /** 10 | * 字典信息 11 | */ 12 | @Provide() 13 | export class DictInfoService extends BaseService { 14 | @InjectEntityModel(DictInfoEntity) 15 | dictInfoEntity: Repository; 16 | 17 | @InjectEntityModel(DictTypeEntity) 18 | dictTypeEntity: Repository; 19 | 20 | @Config('typeorm.dataSource.default.type') 21 | ormType: string; 22 | 23 | /** 24 | * 获得字典数据 25 | * @param types 26 | */ 27 | async data(types: string[]) { 28 | const result = {}; 29 | let typeData = await this.dictTypeEntity.find(); 30 | if (!_.isEmpty(types)) { 31 | typeData = await this.dictTypeEntity.findBy({ key: In(types) }); 32 | } 33 | if (_.isEmpty(typeData)) { 34 | return {}; 35 | } 36 | const data = await this.dictInfoEntity 37 | .createQueryBuilder('a') 38 | .select([ 39 | 'a.id', 40 | 'a.name', 41 | 'a.typeId', 42 | 'a.parentId', 43 | 'a.orderNum', 44 | 'a.value', 45 | ]) 46 | .where('a.typeId in(:...typeIds)', { 47 | typeIds: typeData.map(e => { 48 | return e.id; 49 | }), 50 | }) 51 | .orderBy('a.orderNum', 'ASC') 52 | .addOrderBy('a.createTime', 'ASC') 53 | .getMany(); 54 | for (const item of typeData) { 55 | result[item.key] = _.filter(data, { typeId: item.id }).map(e => { 56 | const value = e.value ? Number(e.value) : e.value; 57 | return { 58 | ...e, 59 | // @ts-ignore 60 | value: isNaN(value) ? e.value : value, 61 | }; 62 | }); 63 | } 64 | return result; 65 | } 66 | 67 | /** 68 | * 获得字典key 69 | * @returns 70 | */ 71 | async types() { 72 | return await this.dictTypeEntity.find(); 73 | } 74 | 75 | /** 76 | * 获得单个或多个字典值 77 | * @param value 字典值或字典值数组 78 | * @param key 字典类型 79 | * @returns 80 | */ 81 | async getValues(value: string | string[], key: string) { 82 | // 获取字典类型 83 | const type = await this.dictTypeEntity.findOneBy({ key }); 84 | if (!type) { 85 | return null; // 或者适当的错误处理 86 | } 87 | 88 | // 根据typeId获取所有相关的字典信息 89 | const dictValues = await this.dictInfoEntity.find({ 90 | where: { typeId: type.id }, 91 | }); 92 | 93 | // 如果value是字符串,直接查找 94 | if (typeof value === 'string') { 95 | return this.findValueInDictValues(value, dictValues); 96 | } 97 | 98 | // 如果value是数组,遍历数组,对每个元素进行查找 99 | return value.map(val => this.findValueInDictValues(val, dictValues)); 100 | } 101 | 102 | /** 103 | * 在字典值数组中查找指定的值 104 | * @param value 要查找的值 105 | * @param dictValues 字典值数组 106 | * @returns 107 | */ 108 | findValueInDictValues(value: string, dictValues: any[]) { 109 | let result = dictValues.find(dictValue => dictValue.value === value); 110 | if (!result) { 111 | result = dictValues.find(dictValue => dictValue.id === parseInt(value)); 112 | } 113 | return result ? result.name : null; // 或者适当的错误处理 114 | } 115 | 116 | /** 117 | * 修改之后 118 | * @param data 119 | * @param type 120 | */ 121 | async modifyAfter(data: any, type: 'delete' | 'update' | 'add') { 122 | if (type === 'delete') { 123 | for (const id of data) { 124 | await this.delChildDict(id); 125 | } 126 | } 127 | } 128 | 129 | /** 130 | * 删除子字典 131 | * @param id 132 | */ 133 | private async delChildDict(id) { 134 | const delDict = await this.dictInfoEntity.findBy({ parentId: id }); 135 | if (_.isEmpty(delDict)) { 136 | return; 137 | } 138 | const delDictIds = delDict.map(e => { 139 | return e.id; 140 | }); 141 | await this.dictInfoEntity.delete(delDictIds); 142 | for (const dictId of delDictIds) { 143 | await this.delChildDict(dictId); 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /src/modules/dict/service/type.ts: -------------------------------------------------------------------------------- 1 | import { DictInfoEntity } from './../entity/info'; 2 | import { Provide } from '@midwayjs/core'; 3 | import { BaseService } from '@cool-midway/core'; 4 | import { InjectEntityModel } from '@midwayjs/typeorm'; 5 | import { Repository, In } from 'typeorm'; 6 | 7 | /** 8 | * 描述 9 | */ 10 | @Provide() 11 | export class DictTypeService extends BaseService { 12 | @InjectEntityModel(DictInfoEntity) 13 | dictInfoEntity: Repository; 14 | 15 | /** 16 | * 删除 17 | * @param ids 18 | */ 19 | async delete(ids) { 20 | await super.delete(ids); 21 | await this.dictInfoEntity.delete({ 22 | typeId: In(ids), 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/plugin/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default options => { 7 | return { 8 | // 模块名称 9 | name: '插件模块', 10 | // 模块描述 11 | description: '插件查看、安装、卸载、配置等', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | // 基础插件配置 19 | hooks: { 20 | // 文件上传 21 | upload: { 22 | // 地址前缀 23 | domain: `http://127.0.0.1:${options?.app?.getConfig('koa.port')}`, 24 | }, 25 | }, 26 | } as ModuleConfig; 27 | }; 28 | -------------------------------------------------------------------------------- /src/modules/plugin/controller/admin/info.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CoolController, 3 | BaseController, 4 | CoolTag, 5 | CoolUrlTag, 6 | TagTypes, 7 | } from '@cool-midway/core'; 8 | import { PluginInfoEntity } from '../../entity/info'; 9 | import { Body, Fields, Files, Inject, Post } from '@midwayjs/core'; 10 | import { PluginService } from '../../service/info'; 11 | 12 | /** 13 | * 插件信息 14 | */ 15 | @CoolUrlTag({ 16 | key: TagTypes.IGNORE_TOKEN, 17 | value: [], 18 | }) 19 | @CoolController({ 20 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 21 | entity: PluginInfoEntity, 22 | service: PluginService, 23 | pageQueryOp: { 24 | select: [ 25 | 'a.id', 26 | 'a.name', 27 | 'a.keyName', 28 | 'a.hook', 29 | 'a.version', 30 | 'a.status', 31 | 'a.readme', 32 | 'a.author', 33 | 'a.logo', 34 | 'a.description', 35 | 'a.pluginJson', 36 | 'a.config', 37 | 'a.createTime', 38 | 'a.updateTime', 39 | ], 40 | addOrderBy: { 41 | id: 'DESC', 42 | }, 43 | }, 44 | }) 45 | export class AdminPluginInfoController extends BaseController { 46 | @Inject() 47 | pluginService: PluginService; 48 | 49 | @CoolTag(TagTypes.IGNORE_TOKEN) 50 | @Post('/install', { summary: '安装插件' }) 51 | async install(@Files() files, @Fields() fields) { 52 | return this.ok( 53 | await this.pluginService.install(files[0].data, fields.force) 54 | ); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/plugin/entity/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, transformerJson } from '../../base/entity/base'; 2 | import { Column, Entity, Index } from 'typeorm'; 3 | 4 | /** 5 | * 插件信息 6 | */ 7 | @Entity('plugin_info') 8 | export class PluginInfoEntity extends BaseEntity { 9 | @Column({ comment: '名称' }) 10 | name: string; 11 | 12 | @Column({ comment: '简介' }) 13 | description: string; 14 | 15 | @Index() 16 | @Column({ comment: 'Key名' }) 17 | keyName: string; 18 | 19 | @Column({ comment: 'Hook' }) 20 | hook: string; 21 | 22 | @Column({ comment: '描述', type: 'text' }) 23 | readme: string; 24 | 25 | @Column({ comment: '版本' }) 26 | version: string; 27 | 28 | @Column({ comment: 'Logo(base64)', type: 'text', nullable: true }) 29 | logo: string; 30 | 31 | @Column({ comment: '作者' }) 32 | author: string; 33 | 34 | @Column({ comment: '状态 0-禁用 1-启用', default: 0 }) 35 | status: number; 36 | 37 | @Column({ comment: '内容', type: 'json', transformer: transformerJson }) 38 | content: { 39 | type: 'comm' | 'module'; 40 | data: string; 41 | }; 42 | 43 | @Column({ comment: 'ts内容', type: 'json', transformer: transformerJson }) 44 | tsContent: { 45 | type: 'ts'; 46 | data: string; 47 | }; 48 | 49 | @Column({ 50 | comment: '插件的plugin.json', 51 | type: 'json', 52 | transformer: transformerJson, 53 | nullable: true, 54 | }) 55 | pluginJson: any; 56 | 57 | @Column({ 58 | comment: '配置', 59 | type: 'json', 60 | transformer: transformerJson, 61 | nullable: true, 62 | }) 63 | config: any; 64 | } 65 | -------------------------------------------------------------------------------- /src/modules/plugin/event/app.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, Event } from '@cool-midway/core'; 2 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 3 | import { 4 | App, 5 | Config, 6 | ILogger, 7 | Inject, 8 | InjectClient, 9 | Logger, 10 | } from '@midwayjs/core'; 11 | import { IMidwayKoaApplication } from '@midwayjs/koa'; 12 | import { PLUGIN_CACHE_KEY, PluginCenterService } from '../service/center'; 13 | import { PluginTypesService } from '../service/types'; 14 | 15 | /** 16 | * 插件事件 17 | */ 18 | @CoolEvent() 19 | export class PluginAppEvent { 20 | @Logger() 21 | coreLogger: ILogger; 22 | 23 | @Config('module') 24 | config; 25 | 26 | @App() 27 | app: IMidwayKoaApplication; 28 | 29 | @InjectClient(CachingFactory, 'default') 30 | midwayCache: MidwayCache; 31 | 32 | @Inject() 33 | pluginCenterService: PluginCenterService; 34 | 35 | @Inject() 36 | pluginTypesService: PluginTypesService; 37 | 38 | @Event('onServerReady') 39 | async onServerReady() { 40 | await this.midwayCache.set(PLUGIN_CACHE_KEY, []); 41 | this.pluginCenterService.init(); 42 | // this.pluginTypesService.reGenerate(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/modules/plugin/event/init.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, Event } from '@cool-midway/core'; 2 | import { Inject } from '@midwayjs/core'; 3 | import { PluginCenterService } from '../service/center'; 4 | 5 | // 插件初始化全局事件 6 | export const GLOBAL_EVENT_PLUGIN_INIT = 'globalPluginInit'; 7 | // 插件移除全局事件 8 | export const GLOBAL_EVENT_PLUGIN_REMOVE = 'globalPluginRemove'; 9 | 10 | /** 11 | * 接收事件 12 | */ 13 | @CoolEvent() 14 | export class PluginInitEvent { 15 | @Inject() 16 | pluginCenterService: PluginCenterService; 17 | 18 | /** 19 | * 插件初始化事件,某个插件重新初始化 20 | * @param key 21 | */ 22 | @Event(GLOBAL_EVENT_PLUGIN_INIT) 23 | async globalPluginInit(key: string) { 24 | await this.pluginCenterService.initOne(key); 25 | } 26 | 27 | /** 28 | * 插件移除或者关闭事件 29 | * @param key 30 | * @param isHook 31 | */ 32 | @Event(GLOBAL_EVENT_PLUGIN_REMOVE) 33 | async globalPluginRemove(key: string, isHook: boolean) { 34 | await this.pluginCenterService.remove(key, isHook); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/modules/plugin/hooks/base.ts: -------------------------------------------------------------------------------- 1 | import { IMidwayContext, IMidwayApplication } from '@midwayjs/core'; 2 | import { PluginInfo } from '../interface'; 3 | 4 | /** 5 | * hook基类 6 | */ 7 | export class BasePluginHook { 8 | /** 请求上下文,用到此项无法本地调试,需安装到cool-admin中才能调试 */ 9 | ctx: IMidwayContext; 10 | /** 应用实例,用到此项无法本地调试,需安装到cool-admin中才能调试 */ 11 | app: IMidwayApplication; 12 | /** 插件信息 */ 13 | pluginInfo: PluginInfo; 14 | /** 15 | * 初始化 16 | */ 17 | async init( 18 | pluginInfo: PluginInfo, 19 | ctx?: IMidwayContext, 20 | app?: IMidwayApplication 21 | ) { 22 | this.pluginInfo = pluginInfo; 23 | this.ctx = ctx; 24 | this.app = app; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/plugin/hooks/upload/index.ts: -------------------------------------------------------------------------------- 1 | import { BaseUpload, MODETYPE } from './interface'; 2 | import { BasePluginHook } from '../base'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import * as moment from 'moment'; 6 | import { v1 as uuid } from 'uuid'; 7 | import { CoolCommException } from '@cool-midway/core'; 8 | import * as _ from 'lodash'; 9 | import { pUploadPath } from '../../../../comm/path'; 10 | 11 | /** 12 | * 文件上传 13 | */ 14 | export class CoolPlugin extends BasePluginHook implements BaseUpload { 15 | /** 16 | * 获得上传模式 17 | * @returns 18 | */ 19 | async getMode() { 20 | return { 21 | mode: MODETYPE.LOCAL, 22 | type: MODETYPE.LOCAL, 23 | }; 24 | } 25 | 26 | /** 27 | * 获得原始操作对象 28 | * @returns 29 | */ 30 | async getMetaFileObj() { 31 | return; 32 | } 33 | 34 | /** 35 | * 下载并上传 36 | * @param url 37 | * @param fileName 38 | */ 39 | async downAndUpload(url: string, fileName?: string) { 40 | const { domain } = this.pluginInfo.config; 41 | // 从url获取扩展名 42 | const extend = path.extname(url); 43 | const download = require('download'); 44 | // 数据 45 | const data = url.includes('http') 46 | ? await download(url) 47 | : fs.readFileSync(url); 48 | // 创建文件夹 49 | const dirPath = path.join(pUploadPath(), `${moment().format('YYYYMMDD')}`); 50 | if (!fs.existsSync(dirPath)) { 51 | fs.mkdirSync(dirPath, { recursive: true }); 52 | } 53 | const uuidStr = uuid(); 54 | const name = `${moment().format('YYYYMMDD')}/${ 55 | fileName ? fileName : uuidStr + extend 56 | }`; 57 | fs.writeFileSync( 58 | `${dirPath}/${fileName ? fileName : uuid() + extend}`, 59 | data 60 | ); 61 | return `${domain}/upload/${name}`; 62 | } 63 | 64 | /** 65 | * 指定Key(路径)上传,本地文件上传到存储服务 66 | * @param filePath 文件路径 67 | * @param key 路径一致会覆盖源文件 68 | */ 69 | async uploadWithKey(filePath: any, key: any) { 70 | const { domain } = this.pluginInfo.config; 71 | const data = fs.readFileSync(filePath); 72 | fs.writeFileSync(path.join(this.app.getBaseDir(), '..', key), data); 73 | return domain + key; 74 | } 75 | 76 | /** 77 | * 上传文件 78 | * @param ctx 79 | * @param key 文件路径 80 | */ 81 | async upload(ctx: any) { 82 | const { domain } = this.pluginInfo.config; 83 | try { 84 | const { key } = ctx.fields; 85 | if ( 86 | key && 87 | (key.includes('..') || 88 | key.includes('./') || 89 | key.includes('\\') || 90 | key.includes('//')) 91 | ) { 92 | throw new CoolCommException('非法的key值'); 93 | } 94 | if (_.isEmpty(ctx.files)) { 95 | throw new CoolCommException('上传文件为空'); 96 | } 97 | const basePath = pUploadPath(); 98 | 99 | const file = ctx.files[0]; 100 | const extension = file.filename.split('.').pop(); 101 | const name = 102 | moment().format('YYYYMMDD') + '/' + (key || `${uuid()}.${extension}`); 103 | const target = path.join(basePath, name); 104 | const dirPath = path.join(basePath, moment().format('YYYYMMDD')); 105 | if (!fs.existsSync(dirPath)) { 106 | fs.mkdirSync(dirPath); 107 | } 108 | const data = fs.readFileSync(file.data); 109 | fs.writeFileSync(target, data); 110 | return domain + '/upload/' + name; 111 | } catch (err) { 112 | console.error(err); 113 | throw new CoolCommException('上传失败' + err.message); 114 | } 115 | } 116 | } 117 | 118 | // 导出插件实例, Plugin名称不可修改 119 | export const Plugin = CoolPlugin; 120 | -------------------------------------------------------------------------------- /src/modules/plugin/hooks/upload/interface.ts: -------------------------------------------------------------------------------- 1 | // 模式 2 | export enum MODETYPE { 3 | // 本地 4 | LOCAL = 'local', 5 | // 云存储 6 | CLOUD = 'cloud', 7 | // 其他 8 | OTHER = 'other', 9 | } 10 | 11 | /** 12 | * 上传模式 13 | */ 14 | export interface Mode { 15 | // 模式 16 | mode: MODETYPE; 17 | // 类型 18 | type: string; 19 | } 20 | 21 | /** 22 | * 文件上传 23 | */ 24 | export interface BaseUpload { 25 | /** 26 | * 获得上传模式 27 | */ 28 | getMode(): Promise; 29 | 30 | /** 31 | * 获得原始操作对象 32 | * @returns 33 | */ 34 | getMetaFileObj(): Promise; 35 | 36 | /** 37 | * 下载并上传 38 | * @param url 39 | * @param fileName 文件名 40 | */ 41 | downAndUpload(url: string, fileName?: string): Promise; 42 | 43 | /** 44 | * 指定Key(路径)上传,本地文件上传到存储服务 45 | * @param filePath 文件路径 46 | * @param key 路径一致会覆盖源文件 47 | */ 48 | uploadWithKey(filePath, key): Promise; 49 | 50 | /** 51 | * 上传文件 52 | * @param ctx 53 | * @param key 文件路径 54 | */ 55 | upload(ctx): Promise; 56 | } 57 | -------------------------------------------------------------------------------- /src/modules/plugin/interface.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 插件信息 3 | */ 4 | export interface PluginInfo { 5 | /** 名称 */ 6 | name?: string; 7 | /** 唯一标识 */ 8 | key?: string; 9 | /** 钩子 */ 10 | hook?: string; 11 | /** 是否单例 */ 12 | singleton?: boolean; 13 | /** 版本 */ 14 | version?: string; 15 | /** 描述 */ 16 | description?: string; 17 | /** 作者 */ 18 | author?: string; 19 | /** logo */ 20 | logo?: string; 21 | /** README 使用说明 */ 22 | readme?: string; 23 | /** 配置 */ 24 | config?: any; 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/recycle/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default () => { 7 | return { 8 | // 模块名称 9 | name: '数据回收', 10 | // 模块描述 11 | description: '收集被删除的数据,管理和恢复', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | } as ModuleConfig; 19 | }; 20 | -------------------------------------------------------------------------------- /src/modules/recycle/controller/admin/data.ts: -------------------------------------------------------------------------------- 1 | import { BaseSysUserEntity } from './../../../base/entity/sys/user'; 2 | import { RecycleDataEntity } from './../../entity/data'; 3 | import { Body, Inject, Post, Provide } from '@midwayjs/core'; 4 | import { CoolController, BaseController } from '@cool-midway/core'; 5 | import { RecycleDataService } from '../../service/data'; 6 | 7 | /** 8 | * 数据回收 9 | */ 10 | @Provide() 11 | @CoolController({ 12 | api: ['info', 'page'], 13 | entity: RecycleDataEntity, 14 | pageQueryOp: { 15 | keyWordLikeFields: ['b.name', 'a.url'], 16 | select: ['a.*', 'b.name as userName'], 17 | join: [ 18 | { 19 | entity: BaseSysUserEntity, 20 | alias: 'b', 21 | condition: 'a.userId = b.id', 22 | }, 23 | ], 24 | }, 25 | }) 26 | export class AdminRecycleDataController extends BaseController { 27 | @Inject() 28 | recycleDataService: RecycleDataService; 29 | 30 | @Post('/restore', { summary: '恢复数据' }) 31 | async restore(@Body('ids') ids: number[]) { 32 | await this.recycleDataService.restore(ids); 33 | return this.ok(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/recycle/entity/data.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity, transformerJson } from '../../base/entity/base'; 2 | import { Entity, Column, Index } from 'typeorm'; 3 | 4 | /** 5 | * 数据回收站 软删除的时候数据会回收到该表 6 | */ 7 | @Entity('recycle_data') 8 | export class RecycleDataEntity extends BaseEntity { 9 | @Column({ comment: '表', type: 'json', transformer: transformerJson }) 10 | entityInfo: { 11 | // 数据源名称 12 | dataSourceName: string; 13 | // entity 14 | entity: string; 15 | }; 16 | 17 | @Index() 18 | @Column({ comment: '操作人', nullable: true }) 19 | userId: number; 20 | 21 | @Column({ 22 | comment: '被删除的数据', 23 | type: 'json', 24 | transformer: transformerJson, 25 | }) 26 | data: object[]; 27 | 28 | @Column({ comment: '请求的接口', nullable: true }) 29 | url: string; 30 | 31 | @Column({ 32 | comment: '请求参数', 33 | nullable: true, 34 | type: 'json', 35 | transformer: transformerJson, 36 | }) 37 | params: string; 38 | 39 | @Column({ comment: '删除数据条数', default: 1 }) 40 | count: number; 41 | } 42 | -------------------------------------------------------------------------------- /src/modules/recycle/event/data.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, EVENT, Event } from '@cool-midway/core'; 2 | import { Inject } from '@midwayjs/core'; 3 | import { RecycleDataService } from '../service/data'; 4 | 5 | /** 6 | * 接受数据事件 7 | */ 8 | @CoolEvent() 9 | export class RecycleDataEvent { 10 | @Inject() 11 | recycleDataService: RecycleDataService; 12 | 13 | /** 14 | * 数据被删除 15 | * @param params 16 | */ 17 | @Event(EVENT.SOFT_DELETE) 18 | async softDelete(params) { 19 | await this.recycleDataService.record(params); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/modules/recycle/schedule/data.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Provide, 3 | Inject, 4 | CommonSchedule, 5 | TaskLocal, 6 | FORMAT, 7 | } from '@midwayjs/core'; 8 | import { ILogger } from '@midwayjs/logger'; 9 | import { RecycleDataService } from '../service/data'; 10 | 11 | /** 12 | * 数据定时清除定时任务 13 | */ 14 | @Provide() 15 | export class BaseRecycleSchedule implements CommonSchedule { 16 | @Inject() 17 | recycleDataService: RecycleDataService; 18 | 19 | @Inject() 20 | logger: ILogger; 21 | 22 | // 定时执行的具体任务 23 | @TaskLocal(FORMAT.CRONTAB.EVERY_DAY) 24 | async exec() { 25 | this.logger.info('清除回收站数据定时任务开始执行'); 26 | const startTime = Date.now(); 27 | await this.recycleDataService.clear(); 28 | this.logger.info( 29 | `清除回收站数据定时任务结束,耗时:${Date.now() - startTime}ms` 30 | ); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/modules/recycle/service/data.ts: -------------------------------------------------------------------------------- 1 | import { RecycleDataEntity } from './../entity/data'; 2 | import { Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; 3 | import { BaseService } from '@cool-midway/core'; 4 | import { InjectEntityModel, TypeORMDataSourceManager } from '@midwayjs/typeorm'; 5 | import { LessThan, Repository } from 'typeorm'; 6 | import * as _ from 'lodash'; 7 | import * as moment from 'moment'; 8 | import { BaseSysConfService } from '../../base/service/sys/conf'; 9 | 10 | /** 11 | * 数据回收 12 | */ 13 | @Provide() 14 | @Scope(ScopeEnum.Request, { allowDowngrade: true }) 15 | export class RecycleDataService extends BaseService { 16 | @InjectEntityModel(RecycleDataEntity) 17 | recycleDataEntity: Repository; 18 | 19 | @Inject() 20 | typeORMDataSourceManager: TypeORMDataSourceManager; 21 | 22 | @Inject() 23 | baseSysConfService: BaseSysConfService; 24 | 25 | /** 26 | * 恢复数据 27 | * @param ids 28 | */ 29 | async restore(ids: number[]) { 30 | for (const id of ids) { 31 | const info = await this.recycleDataEntity.findOneBy({ id }); 32 | if (!info) { 33 | continue; 34 | } 35 | let entityModel = this.typeORMDataSourceManager 36 | .getDataSource(info.entityInfo.dataSourceName) 37 | .getRepository(info.entityInfo.entity); 38 | await entityModel.save(info.data); 39 | await this.recycleDataEntity.delete(id); 40 | } 41 | } 42 | 43 | /** 44 | * 记录数据 45 | * @param params 46 | */ 47 | async record(params) { 48 | const { ctx, data, entity } = params; 49 | if (!ctx?.req) return; 50 | const dataSourceName = 51 | this.typeORMDataSourceManager.getDataSourceNameByModel(entity.target); 52 | const url = ctx?.url; 53 | await this.recycleDataEntity.save({ 54 | entityInfo: { 55 | dataSourceName, 56 | entity: entity.target.name, 57 | }, 58 | url, 59 | params: 60 | ctx?.req.method === 'GET' ? ctx?.request.query : ctx?.request.body, 61 | data, 62 | count: data.length, 63 | userId: _.startsWith(url, '/admin/') ? ctx?.admin.userId : ctx?.user?.id, 64 | }); 65 | } 66 | 67 | /** 68 | * 日志 69 | * @param isAll 是否清除全部 70 | */ 71 | async clear(isAll?) { 72 | if (isAll) { 73 | await this.recycleDataEntity.clear(); 74 | return; 75 | } 76 | const keepDay = await this.baseSysConfService.getValue('recycleKeep'); 77 | if (keepDay) { 78 | const beforeDate = moment().add(-keepDay, 'days').startOf('day').toDate(); 79 | await this.recycleDataEntity.delete({ createTime: LessThan(beforeDate) }); 80 | } else { 81 | await this.recycleDataEntity.clear(); 82 | } 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/modules/space/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default () => { 7 | return { 8 | // 模块名称 9 | name: '文件空间', 10 | // 模块描述 11 | description: '上传和管理文件资源', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | // wps的配置 19 | wps: { 20 | // 这是个测试的appId,会有水印 21 | appId: 'SX20230111NDUAGQ', 22 | }, 23 | } as ModuleConfig; 24 | }; 25 | -------------------------------------------------------------------------------- /src/modules/space/controller/admin/info.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { SpaceInfoEntity } from '../../entity/info'; 4 | import { SpaceInfoService } from '../../service/info'; 5 | 6 | /** 7 | * 图片空间信息 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: SpaceInfoEntity, 13 | service: SpaceInfoService, 14 | pageQueryOp: { 15 | fieldEq: ['type', 'classifyId'], 16 | }, 17 | }) 18 | export class BaseAppSpaceInfoController extends BaseController {} 19 | -------------------------------------------------------------------------------- /src/modules/space/controller/admin/type.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { SpaceTypeEntity } from '../../entity/type'; 4 | import { SpaceTypeService } from '../../service/type'; 5 | 6 | /** 7 | * 空间分类 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: SpaceTypeEntity, 13 | service: SpaceTypeService, 14 | }) 15 | export class BaseAppSpaceTypeController extends BaseController {} 16 | -------------------------------------------------------------------------------- /src/modules/space/controller/说明.md: -------------------------------------------------------------------------------- 1 | 编写接口 -------------------------------------------------------------------------------- /src/modules/space/entity/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 文件空间信息 6 | */ 7 | @Entity('space_info') 8 | export class SpaceInfoEntity extends BaseEntity { 9 | @Column({ comment: '地址' }) 10 | url: string; 11 | 12 | @Column({ comment: '类型' }) 13 | type: string; 14 | 15 | @Column({ comment: '分类ID', nullable: true }) 16 | classifyId: number; 17 | 18 | @Index() 19 | @Column({ comment: '文件id' }) 20 | fileId: string; 21 | 22 | @Column({ comment: '文件名' }) 23 | name: string; 24 | 25 | @Column({ comment: '文件大小' }) 26 | size: number; 27 | 28 | @Column({ comment: '文档版本', default: 1 }) 29 | version: number; 30 | 31 | @Column({ comment: '文件位置' }) 32 | key: string; 33 | } 34 | -------------------------------------------------------------------------------- /src/modules/space/entity/type.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 图片空间信息分类 6 | */ 7 | @Entity('space_type') 8 | export class SpaceTypeEntity extends BaseEntity { 9 | @Column({ comment: '类别名称' }) 10 | name: string; 11 | 12 | @Column({ comment: '父分类ID', nullable: true }) 13 | parentId: number; 14 | } 15 | -------------------------------------------------------------------------------- /src/modules/space/service/info.ts: -------------------------------------------------------------------------------- 1 | import { SpaceInfoEntity } from './../entity/info'; 2 | import { Inject, Provide } from '@midwayjs/core'; 3 | import { BaseService, MODETYPE } from '@cool-midway/core'; 4 | import { InjectEntityModel } from '@midwayjs/typeorm'; 5 | import { Repository } from 'typeorm'; 6 | import { PluginService } from '../../plugin/service/info'; 7 | 8 | /** 9 | * 文件信息 10 | */ 11 | @Provide() 12 | export class SpaceInfoService extends BaseService { 13 | @InjectEntityModel(SpaceInfoEntity) 14 | spaceInfoEntity: Repository; 15 | 16 | @Inject() 17 | pluginService: PluginService; 18 | 19 | /** 20 | * 新增 21 | */ 22 | async add(param) { 23 | const result = await this.pluginService.invoke('upload', 'getMode'); 24 | const config = await this.pluginService.getConfig('upload'); 25 | if (result.mode == MODETYPE.LOCAL) { 26 | param.key = param.url.replace(config.domain, ''); 27 | } 28 | return super.add(param); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/space/service/type.ts: -------------------------------------------------------------------------------- 1 | import { Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { In, Repository } from 'typeorm'; 5 | import { SpaceTypeEntity } from '../entity/type'; 6 | import { SpaceInfoEntity } from '../entity/info'; 7 | 8 | /** 9 | * 文件分类 10 | */ 11 | @Provide() 12 | export class SpaceTypeService extends BaseService { 13 | @InjectEntityModel(SpaceTypeEntity) 14 | spaceTypeEntity: Repository; 15 | 16 | @InjectEntityModel(SpaceInfoEntity) 17 | spaceInfoEntity: Repository; 18 | 19 | /** 20 | * 删除 21 | * @param ids 22 | */ 23 | async delete(ids: any) { 24 | await super.delete(ids); 25 | // 删除该分类下的文件信息 26 | await this.spaceInfoEntity.delete({ classifyId: In(ids) }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/swagger/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | 3 | /** 4 | * 模块配置 5 | */ 6 | export default ({ app }) => { 7 | return { 8 | // 模块名称 9 | name: 'Swagger', 10 | // 模块描述 11 | description: '处理和生成swagger文档', 12 | // 中间件,只对本模块有效 13 | middlewares: [], 14 | // 中间件,全局有效 15 | globalMiddlewares: [], 16 | // 模块加载顺序,默认为0,值越大越优先加载 17 | order: 0, 18 | // swagger基本配置 19 | base: { 20 | openapi: '3.1.0', 21 | info: { 22 | title: 'Cool Admin 在线API文档', 23 | version: '8.x', 24 | description: '本文档是由Cool Admin内部自动构建完成', 25 | contact: { 26 | name: '开发文档', 27 | url: 'https://cool-js.com', 28 | }, 29 | }, 30 | // 请求地址 31 | servers: [ 32 | { 33 | url: `http://127.0.0.1:${app?.getConfig('koa.port') || 8001}`, 34 | description: '本地后台地址', 35 | }, 36 | ], 37 | paths: {}, 38 | components: { 39 | schemas: {}, 40 | securitySchemes: { 41 | ApiKeyAuth: { 42 | type: 'apiKey', 43 | name: 'Authorization', 44 | in: 'header', 45 | }, 46 | }, 47 | }, 48 | }, 49 | } as ModuleConfig; 50 | }; 51 | -------------------------------------------------------------------------------- /src/modules/swagger/controller/index.ts: -------------------------------------------------------------------------------- 1 | import { Config, Controller, Get, Inject } from '@midwayjs/core'; 2 | import { Context } from '@midwayjs/koa'; 3 | import { SwaggerBuilder } from '../builder'; 4 | import { BaseController } from '@cool-midway/core'; 5 | 6 | /** 7 | * 欢迎界面 8 | */ 9 | @Controller('/swagger') 10 | export class SwaggerIndexController extends BaseController { 11 | @Inject() 12 | ctx: Context; 13 | 14 | @Inject() 15 | swaggerBuilder: SwaggerBuilder; 16 | 17 | @Config('cool.eps') 18 | epsConfig: boolean; 19 | 20 | @Get('/', { summary: 'swagger界面' }) 21 | public async index() { 22 | if (!this.epsConfig) { 23 | return this.fail('Eps未开启'); 24 | } 25 | await this.ctx.render('swagger', {}); 26 | } 27 | 28 | @Get('/json', { summary: '获得Swagger JSON数据' }) 29 | public async json() { 30 | if (!this.epsConfig) { 31 | return this.fail('Eps未开启'); 32 | } 33 | return this.swaggerBuilder.json; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/modules/swagger/event/app.ts: -------------------------------------------------------------------------------- 1 | import { CoolEvent, Event } from '@cool-midway/core'; 2 | import { App, ILogger, Inject, Logger } from '@midwayjs/core'; 3 | import { IMidwayKoaApplication } from '@midwayjs/koa'; 4 | import { SwaggerBuilder } from '../builder'; 5 | 6 | /** 7 | * 修改jwt.secret 8 | */ 9 | @CoolEvent() 10 | export class SwaggerAppEvent { 11 | @Logger() 12 | coreLogger: ILogger; 13 | 14 | @App() 15 | app: IMidwayKoaApplication; 16 | 17 | @Inject() 18 | swaggerBuilder: SwaggerBuilder; 19 | 20 | @Event('onServerReady') 21 | async onServerReady() { 22 | this.swaggerBuilder.init().then(() => { 23 | this.coreLogger.info( 24 | '\x1B[36m [cool:module:swagger] midwayjs cool module swagger build success\x1B[0m' 25 | ); 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/modules/task/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | import { TaskMiddleware } from './middleware/task'; 3 | 4 | /** 5 | * 模块配置 6 | */ 7 | export default () => { 8 | return { 9 | // 模块名称 10 | name: '任务调度', 11 | // 模块描述 12 | description: '任务调度模块,支持分布式任务,由redis整个集群的任务', 13 | // 中间件 14 | middlewares: [TaskMiddleware], 15 | // 模块加载顺序,默认为0,值越大越优先加载 16 | order: 0, 17 | // 日志 18 | log: { 19 | // 日志保留时间,单位天 20 | keepDays: 20, 21 | }, 22 | } as ModuleConfig; 23 | }; 24 | -------------------------------------------------------------------------------- /src/modules/task/controller/admin/info.ts: -------------------------------------------------------------------------------- 1 | import { Body, Get, Inject, Post, Provide, Query } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { TaskInfoEntity } from '../../entity/info'; 4 | import { TaskInfoService } from '../../service/info'; 5 | 6 | /** 7 | * 任务 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'page'], 12 | entity: TaskInfoEntity, 13 | service: TaskInfoService, 14 | before: ctx => { 15 | ctx.request.body.limit = ctx.request.body.repeatCount; 16 | }, 17 | pageQueryOp: { 18 | fieldEq: ['status', 'type'], 19 | }, 20 | }) 21 | export class TaskInfoController extends BaseController { 22 | @Inject() 23 | taskInfoService: TaskInfoService; 24 | 25 | /** 26 | * 手动执行一次 27 | */ 28 | @Post('/once', { summary: '执行一次' }) 29 | async once(@Body('id') id: number) { 30 | await this.taskInfoService.once(id); 31 | this.ok(); 32 | } 33 | 34 | /** 35 | * 暂停任务 36 | */ 37 | @Post('/stop', { summary: '停止' }) 38 | async stop(@Body('id') id: number) { 39 | await this.taskInfoService.stop(id); 40 | this.ok(); 41 | } 42 | 43 | /** 44 | * 开始任务 45 | */ 46 | @Post('/start', { summary: '开始' }) 47 | async start(@Body('id') id: number, @Body('type') type: number) { 48 | await this.taskInfoService.start(id, type); 49 | this.ok(); 50 | } 51 | 52 | /** 53 | * 日志 54 | */ 55 | @Get('/log', { summary: '日志' }) 56 | async log(@Query() params: any) { 57 | return this.ok(await this.taskInfoService.log(params)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/modules/task/db.json: -------------------------------------------------------------------------------- 1 | { 2 | "task_info": [ 3 | { 4 | "id": 1, 5 | "jobId": null, 6 | "repeatConf": null, 7 | "name": "每秒执行一次", 8 | "cron": null, 9 | "limit": null, 10 | "every": 1000, 11 | "remark": null, 12 | "status": 0, 13 | "startDate": null, 14 | "endDate": null, 15 | "data": null, 16 | "service": "taskDemoService.test(1,2)", 17 | "type": 1, 18 | "nextRunTime": null, 19 | "taskType": 1 20 | }, 21 | { 22 | "id": 2, 23 | "jobId": null, 24 | "repeatConf": null, 25 | "name": "cron任务,5秒执行一次", 26 | "cron": "0/5 * * * * * ", 27 | "limit": null, 28 | "every": null, 29 | "remark": null, 30 | "status": 0, 31 | "startDate": null, 32 | "endDate": null, 33 | "data": null, 34 | "service": "taskDemoService.test()", 35 | "type": 1, 36 | "nextRunTime": null, 37 | "taskType": 0 38 | } 39 | ] 40 | } -------------------------------------------------------------------------------- /src/modules/task/entity/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 任务信息 6 | */ 7 | @Entity('task_info') 8 | export class TaskInfoEntity extends BaseEntity { 9 | @Column({ comment: '任务ID', nullable: true }) 10 | jobId: string; 11 | 12 | @Column({ comment: '任务配置', nullable: true, length: 1000 }) 13 | repeatConf: string; 14 | 15 | @Column({ comment: '名称' }) 16 | name: string; 17 | 18 | @Column({ comment: 'cron', nullable: true }) 19 | cron: string; 20 | 21 | @Column({ comment: '最大执行次数 不传为无限次', nullable: true }) 22 | limit: number; 23 | 24 | @Column({ 25 | comment: '每间隔多少毫秒执行一次 如果cron设置了 这项设置就无效', 26 | nullable: true, 27 | }) 28 | every: number; 29 | 30 | @Column({ comment: '备注', nullable: true }) 31 | remark: string; 32 | 33 | @Column({ comment: '状态 0-停止 1-运行', default: 1 }) 34 | status: number; 35 | 36 | @Column({ comment: '开始时间', nullable: true }) 37 | startDate: Date; 38 | 39 | @Column({ comment: '结束时间', nullable: true }) 40 | endDate: Date; 41 | 42 | @Column({ comment: '数据', nullable: true }) 43 | data: string; 44 | 45 | @Column({ comment: '执行的service实例ID', nullable: true }) 46 | service: string; 47 | 48 | @Column({ comment: '状态 0-系统 1-用户', default: 0 }) 49 | type: number; 50 | 51 | @Column({ comment: '下一次执行时间', nullable: true }) 52 | nextRunTime: Date; 53 | 54 | @Column({ comment: '状态 0-cron 1-时间间隔', default: 0 }) 55 | taskType: number; 56 | 57 | @Column({ nullable: true }) 58 | lastExecuteTime: Date; 59 | 60 | @Column({ nullable: true }) 61 | lockExpireTime: Date; 62 | } 63 | -------------------------------------------------------------------------------- /src/modules/task/entity/log.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Index, Entity } from 'typeorm'; 3 | 4 | /** 5 | * 任务日志 6 | */ 7 | @Entity('task_log') 8 | export class TaskLogEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '任务ID', nullable: true }) 11 | taskId: number; 12 | 13 | @Column({ comment: '状态 0-失败 1-成功', default: 0 }) 14 | status: number; 15 | 16 | @Column({ comment: '详情描述', nullable: true, type: 'text' }) 17 | detail: string; 18 | } 19 | -------------------------------------------------------------------------------- /src/modules/task/event/comm.ts: -------------------------------------------------------------------------------- 1 | import { Inject } from '@midwayjs/core'; 2 | import { CoolEvent, Event } from '@cool-midway/core'; 3 | import { TaskInfoService } from '../service/info'; 4 | import { TaskLocalService } from '../service/local'; 5 | 6 | /** 7 | * 应用事件 8 | */ 9 | @CoolEvent() 10 | export class TaskCommEvent { 11 | @Inject() 12 | taskInfoService: TaskInfoService; 13 | 14 | @Inject() 15 | taskLocalService: TaskLocalService; 16 | 17 | @Event('onServerReady') 18 | async onServerReady() { 19 | this.taskInfoService.initTask(); 20 | } 21 | 22 | @Event() 23 | async onLocalTaskStop(jobId) { 24 | this.taskLocalService.stopByJobId(jobId); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/modules/task/middleware/task.ts: -------------------------------------------------------------------------------- 1 | import { CoolCommException } from '@cool-midway/core'; 2 | import { Inject, Middleware } from '@midwayjs/core'; 3 | import { NextFunction, Context } from '@midwayjs/koa'; 4 | import { IMiddleware } from '@midwayjs/core'; 5 | import { TaskInfoQueue } from '../queue/task'; 6 | import { TaskInfoService } from '../service/info'; 7 | 8 | /** 9 | * 任务中间件 10 | */ 11 | @Middleware() 12 | export class TaskMiddleware implements IMiddleware { 13 | @Inject() 14 | taskInfoQueue: TaskInfoQueue; 15 | 16 | @Inject() 17 | taskInfoService: TaskInfoService; 18 | 19 | resolve() { 20 | return async (ctx: Context, next: NextFunction) => { 21 | const urls = ctx.url.split('/'); 22 | const type = await this.taskInfoService.initType(); 23 | if ( 24 | ['add', 'update', 'once', 'stop', 'start'].includes( 25 | urls[urls.length - 1] 26 | ) && 27 | type == 'bull' 28 | ) { 29 | if (!this.taskInfoQueue.metaQueue) { 30 | throw new CoolCommException( 31 | 'task插件未启用或redis配置错误或redis版本过低(>=6.x)' 32 | ); 33 | } 34 | } 35 | await next(); 36 | }; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/modules/task/queue/task.ts: -------------------------------------------------------------------------------- 1 | import { App, Inject } from '@midwayjs/core'; 2 | import { BaseCoolQueue, CoolQueue } from '@cool-midway/task'; 3 | import { TaskBullService } from '../service/bull'; 4 | import { IMidwayApplication } from '@midwayjs/core'; 5 | 6 | /** 7 | * 任务 8 | */ 9 | @CoolQueue() 10 | export abstract class TaskInfoQueue extends BaseCoolQueue { 11 | @App() 12 | app: IMidwayApplication; 13 | 14 | @Inject() 15 | taskBullService: TaskBullService; 16 | 17 | async data(job, done: any): Promise { 18 | try { 19 | const result = await this.taskBullService.invokeService(job.data.service); 20 | this.taskBullService.record(job.data, 1, JSON.stringify(result)); 21 | } catch (error) { 22 | this.taskBullService.record(job.data, 0, error.message); 23 | } 24 | if (!job.data.isOnce) { 25 | this.taskBullService.updateNextRunTime(job.data.jobId); 26 | this.taskBullService.updateStatus(job.data.id); 27 | } 28 | done(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/modules/task/service/demo.ts: -------------------------------------------------------------------------------- 1 | import { Logger, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { ILogger } from '@midwayjs/logger'; 4 | 5 | /** 6 | * 描述 7 | */ 8 | @Provide() 9 | export class TaskDemoService extends BaseService { 10 | @Logger() 11 | logger: ILogger; 12 | /** 13 | * 描述 14 | */ 15 | async test(a, b) { 16 | this.logger.info('我被调用了', a, b); 17 | return '任务执行成功'; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/modules/task/service/info.ts: -------------------------------------------------------------------------------- 1 | import { App, Init, Inject, Provide, Scope, ScopeEnum } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import { Repository } from 'typeorm'; 5 | import { TaskInfoEntity } from '../entity/info'; 6 | import * as _ from 'lodash'; 7 | import { IMidwayApplication } from '@midwayjs/core'; 8 | import { CoolQueueHandle } from '@cool-midway/task'; 9 | import { TaskBullService } from './bull'; 10 | import { TaskLocalService } from './local'; 11 | import { TaskLogEntity } from '../entity/log'; 12 | /** 13 | * 任务 14 | */ 15 | @Provide() 16 | @Scope(ScopeEnum.Request, { allowDowngrade: true }) 17 | export class TaskInfoService extends BaseService { 18 | @InjectEntityModel(TaskInfoEntity) 19 | taskInfoEntity: Repository; 20 | 21 | @InjectEntityModel(TaskLogEntity) 22 | taskLogEntity: Repository; 23 | 24 | type: 'local' | 'bull' = 'local'; 25 | 26 | @App() 27 | app: IMidwayApplication; 28 | 29 | @Inject() 30 | taskBullService: TaskBullService; 31 | 32 | @Inject() 33 | taskLocalService: TaskLocalService; 34 | 35 | @Init() 36 | async init() { 37 | await super.init(); 38 | await this.initType(); 39 | this.setEntity(this.taskInfoEntity); 40 | } 41 | 42 | /** 43 | * 初始化任务类型 44 | */ 45 | async initType() { 46 | try { 47 | const check = await this.app 48 | .getApplicationContext() 49 | .getAsync(CoolQueueHandle); 50 | if (check) { 51 | this.type = 'bull'; 52 | } else { 53 | this.type = 'local'; 54 | } 55 | } catch (e) { 56 | this.type = 'local'; 57 | } 58 | return this.type; 59 | } 60 | 61 | /** 62 | * 停止任务 63 | * @param id 64 | */ 65 | async stop(id) { 66 | this.type === 'bull' 67 | ? await this.taskBullService.stop(id) 68 | : await this.taskLocalService.stop(id); 69 | } 70 | 71 | /** 72 | * 开始任务 73 | * @param id 74 | * @param type 75 | */ 76 | async start(id, type?) { 77 | this.type === 'bull' 78 | ? await this.taskBullService.start(id) 79 | : await this.taskLocalService.start(id, type); 80 | } 81 | /** 82 | * 手动执行一次 83 | * @param id 84 | */ 85 | async once(id) { 86 | this.type === 'bull' 87 | ? this.taskBullService.once(id) 88 | : this.taskLocalService.once(id); 89 | } 90 | /** 91 | * 检查任务是否存在 92 | * @param jobId 93 | */ 94 | async exist(jobId) { 95 | this.type === 'bull' 96 | ? this.taskBullService.exist(jobId) 97 | : this.taskLocalService.exist(jobId); 98 | } 99 | /** 100 | * 新增或修改 101 | * @param params 102 | */ 103 | async addOrUpdate(params) { 104 | this.type === 'bull' 105 | ? this.taskBullService.addOrUpdate(params) 106 | : this.taskLocalService.addOrUpdate(params); 107 | } 108 | /** 109 | * 删除 110 | * @param ids 111 | */ 112 | async delete(ids) { 113 | this.type === 'bull' 114 | ? this.taskBullService.delete(ids) 115 | : this.taskLocalService.delete(ids); 116 | } 117 | /** 118 | * 任务日志 119 | * @param query 120 | */ 121 | async log(query) { 122 | const { id, status } = query; 123 | const find = await this.taskLogEntity 124 | .createQueryBuilder('a') 125 | .select(['a.*', 'b.name as taskName']) 126 | .leftJoin(TaskInfoEntity, 'b', 'a.taskId = b.id') 127 | .where('a.taskId = :id', { id }); 128 | if (status || status == 0) { 129 | find.andWhere('a.status = :status', { status }); 130 | } 131 | return await this.entityRenderPage(find, query); 132 | } 133 | 134 | /** 135 | * 初始化任务 136 | */ 137 | async initTask() { 138 | this.type === 'bull' 139 | ? this.taskBullService.initTask() 140 | : this.taskLocalService.initTask(); 141 | } 142 | 143 | /** 144 | * 详情 145 | * @param id 146 | * @returns 147 | */ 148 | async info(id: any): Promise { 149 | this.type === 'bull' 150 | ? this.taskBullService.info(id) 151 | : this.taskLocalService.info(id); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /src/modules/user/config.ts: -------------------------------------------------------------------------------- 1 | import { ModuleConfig } from '@cool-midway/core'; 2 | import { UserMiddleware } from './middleware/app'; 3 | 4 | /** 5 | * 模块配置 6 | */ 7 | export default () => { 8 | return { 9 | // 模块名称 10 | name: '用户模块', 11 | // 模块描述 12 | description: 'APP、小程序、公众号等用户', 13 | // 中间件,只对本模块有效 14 | middlewares: [], 15 | // 中间件,全局有效 16 | globalMiddlewares: [UserMiddleware], 17 | // 模块加载顺序,默认为0,值越大越优先加载 18 | order: 0, 19 | // 短信 20 | sms: { 21 | // 验证码有效期,单位秒 22 | timeout: 60 * 3, 23 | }, 24 | // jwt 25 | jwt: { 26 | // token 过期时间,单位秒 27 | expire: 60 * 60 * 24, 28 | // 刷新token 过期时间,单位秒 29 | refreshExpire: 60 * 60 * 24 * 30, 30 | // jwt 秘钥 31 | secret: 'cool-app-xxxxxx', 32 | }, 33 | } as ModuleConfig; 34 | }; 35 | -------------------------------------------------------------------------------- /src/modules/user/controller/admin/address.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { UserAddressEntity } from '../../entity/address'; 3 | import { UserAddressService } from '../../service/address'; 4 | 5 | /** 6 | * 用户-地址 7 | */ 8 | @CoolController({ 9 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 10 | entity: UserAddressEntity, 11 | service: UserAddressService, 12 | }) 13 | export class AdminUserAddressesController extends BaseController {} 14 | -------------------------------------------------------------------------------- /src/modules/user/controller/admin/info.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { UserInfoEntity } from '../../entity/info'; 3 | 4 | /** 5 | * 用户信息 6 | */ 7 | @CoolController({ 8 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 9 | entity: UserInfoEntity, 10 | pageQueryOp: { 11 | fieldEq: ['a.status', 'a.gender', 'a.loginType'], 12 | keyWordLikeFields: ['a.nickName', 'a.phone'], 13 | }, 14 | }) 15 | export class AdminUserInfoController extends BaseController {} 16 | -------------------------------------------------------------------------------- /src/modules/user/controller/app/address.ts: -------------------------------------------------------------------------------- 1 | import { Get, Inject, Provide } from '@midwayjs/core'; 2 | import { CoolController, BaseController } from '@cool-midway/core'; 3 | import { UserAddressEntity } from '../../entity/address'; 4 | import { UserAddressService } from '../../service/address'; 5 | 6 | /** 7 | * 地址 8 | */ 9 | @Provide() 10 | @CoolController({ 11 | api: ['add', 'delete', 'update', 'info', 'list', 'page'], 12 | entity: UserAddressEntity, 13 | service: UserAddressService, 14 | insertParam: ctx => { 15 | return { 16 | userId: ctx.user.id, 17 | }; 18 | }, 19 | pageQueryOp: { 20 | where: async ctx => { 21 | return [['userId =:userId', { userId: ctx.user.id }]]; 22 | }, 23 | addOrderBy: { 24 | isDefault: 'DESC', 25 | }, 26 | }, 27 | }) 28 | export class AppUserAddressController extends BaseController { 29 | @Inject() 30 | userAddressService: UserAddressService; 31 | 32 | @Inject() 33 | ctx; 34 | 35 | @Get('/default', { summary: '默认地址' }) 36 | async default() { 37 | return this.ok(await this.userAddressService.default(this.ctx.user.id)); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/modules/user/controller/app/comm.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CoolController, 3 | BaseController, 4 | CoolUrlTag, 5 | TagTypes, 6 | CoolTag, 7 | } from '@cool-midway/core'; 8 | import { Body, Inject, Post } from '@midwayjs/core'; 9 | import { UserWxService } from '../../service/wx'; 10 | 11 | /** 12 | * 通用 13 | */ 14 | @CoolUrlTag() 15 | @CoolController() 16 | export class UserCommController extends BaseController { 17 | @Inject() 18 | userWxService: UserWxService; 19 | 20 | @CoolTag(TagTypes.IGNORE_TOKEN) 21 | @Post('/wxMpConfig', { summary: '获取微信公众号配置' }) 22 | public async getWxMpConfig(@Body('url') url: string) { 23 | return this.ok(await this.userWxService.getWxMpConfig(url)); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/modules/user/controller/app/info.ts: -------------------------------------------------------------------------------- 1 | import { CoolController, BaseController } from '@cool-midway/core'; 2 | import { Body, Get, Inject, Post } from '@midwayjs/core'; 3 | import { UserInfoService } from '../../service/info'; 4 | import { UserInfoEntity } from '../../entity/info'; 5 | 6 | /** 7 | * 用户信息 8 | */ 9 | @CoolController({ 10 | api: [], 11 | entity: UserInfoEntity, 12 | }) 13 | export class AppUserInfoController extends BaseController { 14 | @Inject() 15 | ctx; 16 | 17 | @Inject() 18 | userInfoService: UserInfoService; 19 | 20 | @Get('/person', { summary: '获取用户信息' }) 21 | async person() { 22 | return this.ok(await this.userInfoService.person(this.ctx.user.id)); 23 | } 24 | 25 | @Post('/updatePerson', { summary: '更新用户信息' }) 26 | async updatePerson(@Body() body) { 27 | return this.ok( 28 | await this.userInfoService.updatePerson(this.ctx.user.id, body) 29 | ); 30 | } 31 | 32 | @Post('/updatePassword', { summary: '更新用户密码' }) 33 | async updatePassword( 34 | @Body('password') password: string, 35 | @Body('code') code: string 36 | ) { 37 | await this.userInfoService.updatePassword(this.ctx.user.id, password, code); 38 | return this.ok(); 39 | } 40 | 41 | @Post('/logoff', { summary: '注销' }) 42 | async logoff() { 43 | await this.userInfoService.logoff(this.ctx.user.id); 44 | return this.ok(); 45 | } 46 | 47 | @Post('/bindPhone', { summary: '绑定手机号' }) 48 | async bindPhone(@Body('phone') phone: string, @Body('code') code: string) { 49 | await this.userInfoService.bindPhone(this.ctx.user.id, phone, code); 50 | return this.ok(); 51 | } 52 | 53 | @Post('/miniPhone', { summary: '绑定小程序手机号' }) 54 | async miniPhone(@Body() body) { 55 | const { code, encryptedData, iv } = body; 56 | return this.ok( 57 | await this.userInfoService.miniPhone( 58 | this.ctx.user.id, 59 | code, 60 | encryptedData, 61 | iv 62 | ) 63 | ); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/modules/user/controller/app/login.ts: -------------------------------------------------------------------------------- 1 | import { 2 | CoolController, 3 | BaseController, 4 | CoolUrlTag, 5 | TagTypes, 6 | CoolTag, 7 | } from '@cool-midway/core'; 8 | import { Body, Get, Inject, Post, Query } from '@midwayjs/core'; 9 | import { UserLoginService } from '../../service/login'; 10 | import { BaseSysLoginService } from '../../../base/service/sys/login'; 11 | 12 | /** 13 | * 登录 14 | */ 15 | @CoolUrlTag() 16 | @CoolController() 17 | export class AppUserLoginController extends BaseController { 18 | @Inject() 19 | userLoginService: UserLoginService; 20 | 21 | @Inject() 22 | baseSysLoginService: BaseSysLoginService; 23 | 24 | @CoolTag(TagTypes.IGNORE_TOKEN) 25 | @Post('/mini', { summary: '小程序登录' }) 26 | async mini(@Body() body) { 27 | const { code, encryptedData, iv } = body; 28 | return this.ok(await this.userLoginService.mini(code, encryptedData, iv)); 29 | } 30 | 31 | @CoolTag(TagTypes.IGNORE_TOKEN) 32 | @Post('/mp', { summary: '公众号登录' }) 33 | async mp(@Body('code') code: string) { 34 | return this.ok(await this.userLoginService.mp(code)); 35 | } 36 | 37 | @CoolTag(TagTypes.IGNORE_TOKEN) 38 | @Post('/wxApp', { summary: '微信APP授权登录' }) 39 | async app(@Body('code') code: string) { 40 | return this.ok(await this.userLoginService.wxApp(code)); 41 | } 42 | 43 | @CoolTag(TagTypes.IGNORE_TOKEN) 44 | @Post('/phone', { summary: '手机号登录' }) 45 | async phone(@Body('phone') phone: string, @Body('smsCode') smsCode: string) { 46 | return this.ok(await this.userLoginService.phoneVerifyCode(phone, smsCode)); 47 | } 48 | 49 | @CoolTag(TagTypes.IGNORE_TOKEN) 50 | @Post('/uniPhone', { summary: '一键手机号登录' }) 51 | async uniPhone( 52 | @Body('access_token') access_token: string, 53 | @Body('openid') openid: string, 54 | @Body('appId') appId: string 55 | ) { 56 | return this.ok( 57 | await this.userLoginService.uniPhone(access_token, openid, appId) 58 | ); 59 | } 60 | 61 | @CoolTag(TagTypes.IGNORE_TOKEN) 62 | @Post('/miniPhone', { summary: '绑定小程序手机号' }) 63 | async miniPhone(@Body() body) { 64 | const { code, encryptedData, iv } = body; 65 | return this.ok( 66 | await this.userLoginService.miniPhone(code, encryptedData, iv) 67 | ); 68 | } 69 | 70 | @CoolTag(TagTypes.IGNORE_TOKEN) 71 | @Get('/captcha', { summary: '图片验证码' }) 72 | async captcha( 73 | @Query('width') width: number, 74 | @Query('height') height: number, 75 | @Query('color') color: string 76 | ) { 77 | return this.ok( 78 | await this.baseSysLoginService.captcha(width, height, color) 79 | ); 80 | } 81 | 82 | @CoolTag(TagTypes.IGNORE_TOKEN) 83 | @Post('/smsCode', { summary: '验证码' }) 84 | async smsCode( 85 | @Body('phone') phone: string, 86 | @Body('captchaId') captchaId: string, 87 | @Body('code') code: string 88 | ) { 89 | return this.ok(await this.userLoginService.smsCode(phone, captchaId, code)); 90 | } 91 | 92 | @CoolTag(TagTypes.IGNORE_TOKEN) 93 | @Post('/refreshToken', { summary: '刷新token' }) 94 | public async refreshToken(@Body('refreshToken') refreshToken) { 95 | return this.ok(await this.userLoginService.refreshToken(refreshToken)); 96 | } 97 | 98 | @CoolTag(TagTypes.IGNORE_TOKEN) 99 | @Post('/password', { summary: '密码登录' }) 100 | async password( 101 | @Body('phone') phone: string, 102 | @Body('password') password: string 103 | ) { 104 | return this.ok(await this.userLoginService.password(phone, password)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/modules/user/entity/address.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Entity, Column, Index } from 'typeorm'; 3 | 4 | /** 5 | * 用户模块-收货地址 6 | */ 7 | @Entity('user_address') 8 | export class UserAddressEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '用户ID' }) 11 | userId: number; 12 | 13 | @Column({ comment: '联系人' }) 14 | contact: string; 15 | 16 | @Index() 17 | @Column({ comment: '手机号', length: 11 }) 18 | phone: string; 19 | 20 | @Column({ comment: '省' }) 21 | province: string; 22 | 23 | @Column({ comment: '市' }) 24 | city: string; 25 | 26 | @Column({ comment: '区' }) 27 | district: string; 28 | 29 | @Column({ comment: '地址' }) 30 | address: string; 31 | 32 | @Column({ comment: '是否默认', default: false }) 33 | isDefault: boolean; 34 | } 35 | -------------------------------------------------------------------------------- /src/modules/user/entity/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity, Index } from 'typeorm'; 3 | 4 | /** 5 | * 用户信息 6 | */ 7 | @Entity('user_info') 8 | export class UserInfoEntity extends BaseEntity { 9 | @Index({ unique: true }) 10 | @Column({ comment: '登录唯一ID', nullable: true }) 11 | unionid: string; 12 | 13 | @Column({ comment: '头像', nullable: true }) 14 | avatarUrl: string; 15 | 16 | @Column({ comment: '昵称', nullable: true }) 17 | nickName: string; 18 | 19 | @Index({ unique: true }) 20 | @Column({ comment: '手机号', nullable: true }) 21 | phone: string; 22 | 23 | @Column({ comment: '性别', dict: ['未知', '男', '女'], default: 0 }) 24 | gender: number; 25 | 26 | @Column({ comment: '状态', dict: ['禁用', '正常', '已注销'], default: 1 }) 27 | status: number; 28 | 29 | @Column({ comment: '登录方式', dict: ['小程序', '公众号', 'H5'], default: 0 }) 30 | loginType: number; 31 | 32 | @Column({ comment: '密码', nullable: true }) 33 | password: string; 34 | 35 | @Column({ comment: '介绍', type: 'text', nullable: true }) 36 | description: string; 37 | } 38 | -------------------------------------------------------------------------------- /src/modules/user/entity/wx.ts: -------------------------------------------------------------------------------- 1 | import { BaseEntity } from '../../base/entity/base'; 2 | import { Column, Entity, Index } from 'typeorm'; 3 | 4 | /** 5 | * 微信用户 6 | */ 7 | @Entity('user_wx') 8 | export class UserWxEntity extends BaseEntity { 9 | @Index() 10 | @Column({ comment: '微信unionid', nullable: true }) 11 | unionid: string; 12 | 13 | @Index() 14 | @Column({ comment: '微信openid' }) 15 | openid: string; 16 | 17 | @Column({ comment: '头像', nullable: true }) 18 | avatarUrl: string; 19 | 20 | @Column({ comment: '昵称', nullable: true }) 21 | nickName: string; 22 | 23 | @Column({ comment: '性别 0-未知 1-男 2-女', default: 0 }) 24 | gender: number; 25 | 26 | @Column({ comment: '语言', nullable: true }) 27 | language: string; 28 | 29 | @Column({ comment: '城市', nullable: true }) 30 | city: string; 31 | 32 | @Column({ comment: '省份', nullable: true }) 33 | province: string; 34 | 35 | @Column({ comment: '国家', nullable: true }) 36 | country: string; 37 | 38 | @Column({ comment: '类型 0-小程序 1-公众号 2-H5 3-APP', default: 0 }) 39 | type: number; 40 | } 41 | -------------------------------------------------------------------------------- /src/modules/user/middleware/app.ts: -------------------------------------------------------------------------------- 1 | import { ALL, Config, Middleware } from '@midwayjs/core'; 2 | import { NextFunction, Context } from '@midwayjs/koa'; 3 | import { IMiddleware, Init, Inject } from '@midwayjs/core'; 4 | import * as jwt from 'jsonwebtoken'; 5 | import * as _ from 'lodash'; 6 | import { CoolCommException, CoolUrlTagData, TagTypes } from '@cool-midway/core'; 7 | import { Utils } from '../../../comm/utils'; 8 | 9 | /** 10 | * 用户 11 | */ 12 | @Middleware() 13 | export class UserMiddleware implements IMiddleware { 14 | @Config(ALL) 15 | coolConfig; 16 | 17 | @Inject() 18 | coolUrlTagData: CoolUrlTagData; 19 | 20 | @Config('module.user.jwt') 21 | jwtConfig; 22 | 23 | ignoreUrls: string[] = []; 24 | 25 | @Config('koa.globalPrefix') 26 | prefix; 27 | 28 | @Inject() 29 | utils: Utils; 30 | 31 | @Init() 32 | async init() { 33 | this.ignoreUrls = this.coolUrlTagData.byKey(TagTypes.IGNORE_TOKEN, 'app'); 34 | } 35 | 36 | resolve() { 37 | return async (ctx: Context, next: NextFunction) => { 38 | let { url } = ctx; 39 | url = url.replace(this.prefix, '').split('?')[0]; 40 | if (_.startsWith(url, '/app/')) { 41 | const token = ctx.get('Authorization'); 42 | try { 43 | ctx.user = jwt.verify(token, this.jwtConfig.secret); 44 | 45 | if (ctx.user.isRefresh) { 46 | throw new CoolCommException('登录失效~'); 47 | } 48 | } catch (error) {} 49 | // 使用matchUrl方法来检查URL是否应该被忽略 50 | const isIgnored = this.ignoreUrls.some(pattern => 51 | this.utils.matchUrl(pattern, url) 52 | ); 53 | if (isIgnored) { 54 | await next(); 55 | return; 56 | } else { 57 | if (!ctx.user) { 58 | ctx.status = 401; 59 | throw new CoolCommException('登录失效~'); 60 | } 61 | } 62 | } 63 | await next(); 64 | }; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/modules/user/service/address.ts: -------------------------------------------------------------------------------- 1 | import { Init, Inject, Provide } from '@midwayjs/core'; 2 | import { BaseService } from '@cool-midway/core'; 3 | import { Equal, Repository } from 'typeorm'; 4 | import { UserAddressEntity } from '../entity/address'; 5 | import { InjectEntityModel } from '@midwayjs/typeorm'; 6 | 7 | /** 8 | * 地址 9 | */ 10 | @Provide() 11 | export class UserAddressService extends BaseService { 12 | @InjectEntityModel(UserAddressEntity) 13 | userAddressEntity: Repository; 14 | 15 | @Inject() 16 | ctx; 17 | 18 | @Init() 19 | async init() { 20 | await super.init(); 21 | this.setEntity(this.userAddressEntity); 22 | } 23 | 24 | /** 25 | * 列表信息 26 | */ 27 | async list() { 28 | return this.userAddressEntity 29 | .createQueryBuilder() 30 | .where('userId = :userId ', { userId: this.ctx.user.id }) 31 | .addOrderBy('isDefault', 'DESC') 32 | .getMany(); 33 | } 34 | 35 | /** 36 | * 修改之后 37 | * @param data 38 | * @param type 39 | */ 40 | async modifyAfter(data: any, type: 'add' | 'update' | 'delete') { 41 | if (type == 'add' || type == 'update') { 42 | if (data.isDefault) { 43 | await this.userAddressEntity 44 | .createQueryBuilder() 45 | .update() 46 | .set({ isDefault: false }) 47 | .where('userId = :userId ', { userId: this.ctx.user.id }) 48 | .andWhere('id != :id', { id: data.id }) 49 | .execute(); 50 | } 51 | } 52 | } 53 | 54 | /** 55 | * 默认地址 56 | */ 57 | async default(userId) { 58 | return await this.userAddressEntity.findOneBy({ 59 | userId: Equal(userId), 60 | isDefault: true, 61 | }); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/modules/user/service/info.ts: -------------------------------------------------------------------------------- 1 | import { BaseService, CoolCommException } from '@cool-midway/core'; 2 | import { Inject, Provide } from '@midwayjs/core'; 3 | import { InjectEntityModel } from '@midwayjs/typeorm'; 4 | import * as md5 from 'md5'; 5 | import { Equal, Repository } from 'typeorm'; 6 | import { v1 as uuid } from 'uuid'; 7 | import { PluginService } from '../../plugin/service/info'; 8 | import { UserInfoEntity } from '../entity/info'; 9 | import { UserSmsService } from './sms'; 10 | import { UserWxService } from './wx'; 11 | 12 | /** 13 | * 用户信息 14 | */ 15 | @Provide() 16 | export class UserInfoService extends BaseService { 17 | @InjectEntityModel(UserInfoEntity) 18 | userInfoEntity: Repository; 19 | 20 | @Inject() 21 | pluginService: PluginService; 22 | 23 | @Inject() 24 | userSmsService: UserSmsService; 25 | 26 | @Inject() 27 | userWxService: UserWxService; 28 | 29 | /** 30 | * 绑定小程序手机号 31 | * @param userId 32 | * @param code 33 | * @param encryptedData 34 | * @param iv 35 | */ 36 | async miniPhone(userId: number, code: any, encryptedData: any, iv: any) { 37 | const phone = await this.userWxService.miniPhone(code, encryptedData, iv); 38 | await this.userInfoEntity.update({ id: Equal(userId) }, { phone }); 39 | return phone; 40 | } 41 | 42 | /** 43 | * 获取用户信息 44 | * @param id 45 | * @returns 46 | */ 47 | async person(id) { 48 | const info = await this.userInfoEntity.findOneBy({ id: Equal(id) }); 49 | delete info.password; 50 | return info; 51 | } 52 | 53 | /** 54 | * 注销 55 | * @param userId 56 | */ 57 | async logoff(userId: number) { 58 | await this.userInfoEntity.update( 59 | { id: userId }, 60 | { 61 | status: 2, 62 | phone: null, 63 | unionid: null, 64 | nickName: `已注销-00${userId}`, 65 | avatarUrl: null, 66 | } 67 | ); 68 | } 69 | 70 | /** 71 | * 更新用户信息 72 | * @param id 73 | * @param param 74 | * @returns 75 | */ 76 | async updatePerson(id, param) { 77 | const info = await this.person(id); 78 | if (!info) throw new CoolCommException('用户不存在'); 79 | try { 80 | // 修改了头像要重新处理 81 | if (param.avatarUrl && info.avatarUrl != param.avatarUrl) { 82 | const file = await this.pluginService.getInstance('upload'); 83 | param.avatarUrl = await file.downAndUpload( 84 | param.avatarUrl, 85 | uuid() + '.png' 86 | ); 87 | } 88 | } catch (err) {} 89 | try { 90 | return await this.userInfoEntity.update({ id }, param); 91 | } catch (err) { 92 | throw new CoolCommException('更新失败,参数错误或者手机号已存在'); 93 | } 94 | } 95 | 96 | /** 97 | * 更新密码 98 | * @param userId 99 | * @param password 100 | * @param 验证码 101 | */ 102 | async updatePassword(userId, password, code) { 103 | const user = await this.userInfoEntity.findOneBy({ id: userId }); 104 | const check = await this.userSmsService.checkCode(user.phone, code); 105 | if (!check) { 106 | throw new CoolCommException('验证码错误'); 107 | } 108 | await this.userInfoEntity.update(user.id, { password: md5(password) }); 109 | } 110 | 111 | /** 112 | * 绑定手机号 113 | * @param userId 114 | * @param phone 115 | * @param code 116 | */ 117 | async bindPhone(userId, phone, code) { 118 | const check = await this.userSmsService.checkCode(phone, code); 119 | if (!check) { 120 | throw new CoolCommException('验证码错误'); 121 | } 122 | await this.userInfoEntity.update({ id: userId }, { phone }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/modules/user/service/sms.ts: -------------------------------------------------------------------------------- 1 | import { Provide, Config, Inject, Init, InjectClient } from '@midwayjs/core'; 2 | import { BaseService, CoolCommException } from '@cool-midway/core'; 3 | import * as _ from 'lodash'; 4 | import { CachingFactory, MidwayCache } from '@midwayjs/cache-manager'; 5 | import { PluginService } from '../../plugin/service/info'; 6 | 7 | /** 8 | * 描述 9 | */ 10 | @Provide() 11 | export class UserSmsService extends BaseService { 12 | // 获得模块的配置信息 13 | @Config('module.user.sms') 14 | config; 15 | 16 | @InjectClient(CachingFactory, 'default') 17 | midwayCache: MidwayCache; 18 | 19 | @Inject() 20 | pluginService: PluginService; 21 | 22 | plugin; 23 | 24 | @Init() 25 | async init() { 26 | for (const key of ['sms-tx', 'sms-ali']) { 27 | try { 28 | this.plugin = await this.pluginService.getInstance(key); 29 | if (this.plugin) { 30 | this.config.pluginKey = key; 31 | break; 32 | } 33 | } catch (e) { 34 | continue; 35 | } 36 | } 37 | } 38 | 39 | /** 40 | * 发送验证码 41 | * @param phone 42 | */ 43 | async sendSms(phone) { 44 | // 随机四位验证码 45 | const code = _.random(1000, 9999); 46 | const pluginKey = this.config.pluginKey; 47 | if (!this.plugin) 48 | throw new CoolCommException( 49 | '未配置短信插件,请到插件市场下载安装配置:https://cool-js.com/plugin?keyWord=短信' 50 | ); 51 | try { 52 | if (pluginKey == 'sms-tx') { 53 | await this.plugin.send([phone], [code]); 54 | } 55 | if (pluginKey == 'sms-ali') { 56 | await this.plugin.send([phone], { 57 | code, 58 | }); 59 | } 60 | this.midwayCache.set(`sms:${phone}`, code, this.config.timeout * 1000); 61 | } catch (error) { 62 | throw new CoolCommException('发送过于频繁,请稍后再试'); 63 | } 64 | } 65 | 66 | /** 67 | * 验证验证码 68 | * @param phone 69 | * @param code 70 | * @returns 71 | */ 72 | async checkCode(phone, code) { 73 | const cacheCode = await this.midwayCache.get(`sms:${phone}`); 74 | if (code && cacheCode == code) { 75 | return true; 76 | } 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /test/README.md: -------------------------------------------------------------------------------- 1 | # 测试方式 2 | 3 | 考虑到cool-admin采用了自动化路由技术,它与官方集成的jest测试工具并不兼容。为确保测试环境与实际的开发环境保持一致,我们并不推荐使用jest进行测试。 4 | 5 | # 自动化测试API工具 6 | 7 | 我们为您推荐以下的自动化API测试工具: 8 | 9 | - [Apifox](https://apifox.com/) 10 | - [ApiPost](https://www.apipost.cn/) 11 | 12 | 同时这些工具也方便写API接口文档,更加灵活有用 -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": true, 3 | "compilerOptions": { 4 | "target": "es2018", 5 | "module": "commonjs", 6 | "moduleResolution": "node", 7 | "experimentalDecorators": true, 8 | "emitDecoratorMetadata": true, 9 | "inlineSourceMap": true, 10 | "noImplicitThis": true, 11 | "noUnusedLocals": false, 12 | "stripInternal": true, 13 | "skipLibCheck": true, 14 | "resolveJsonModule": true, 15 | "pretty": true, 16 | "declaration": false, 17 | "noImplicitAny": false, 18 | "typeRoots": [ 19 | "typings", 20 | "./node_modules/@types", 21 | ], 22 | "outDir": "dist", 23 | "rootDir": "src", 24 | }, 25 | "exclude": [ 26 | "dist", 27 | "node_modules", 28 | "test", 29 | ], 30 | } 31 | -------------------------------------------------------------------------------- /typings/plugin.d.ts: -------------------------------------------------------------------------------- 1 | import { BaseUpload, MODETYPE } from './upload'; 2 | type AnyString = string & {}; 3 | /** 4 | * 插件类型声明 5 | */ 6 | interface PluginMap { 7 | upload: BaseUpload; 8 | } 9 | -------------------------------------------------------------------------------- /typings/upload.d.ts: -------------------------------------------------------------------------------- 1 | // 模式 2 | export enum MODETYPE { 3 | // 本地 4 | LOCAL = 'local', 5 | // 云存储 6 | CLOUD = 'cloud', 7 | // 其他 8 | OTHER = 'other', 9 | } 10 | 11 | /** 12 | * 上传模式 13 | */ 14 | export interface Mode { 15 | // 模式 16 | mode: MODETYPE; 17 | // 类型 18 | type: string; 19 | } 20 | 21 | /** 22 | * 文件上传 23 | */ 24 | export interface BaseUpload { 25 | /** 26 | * 获得上传模式 27 | */ 28 | getMode(): Promise; 29 | 30 | /** 31 | * 获得原始操作对象 32 | * @returns 33 | */ 34 | getMetaFileObj(): Promise; 35 | 36 | /** 37 | * 下载并上传 38 | * @param url 39 | * @param fileName 文件名 40 | */ 41 | downAndUpload(url: string, fileName?: string): Promise; 42 | 43 | /** 44 | * 指定Key(路径)上传,本地文件上传到存储服务 45 | * @param filePath 文件路径 46 | * @param key 路径一致会覆盖源文件 47 | */ 48 | uploadWithKey(filePath, key): Promise; 49 | 50 | /** 51 | * 上传文件 52 | * @param ctx 53 | * @param key 文件路径 54 | */ 55 | upload(ctx): Promise; 56 | } 57 | --------------------------------------------------------------------------------