mirror of
https://github.com/cool-team-official/cool-admin-midway.git
synced 2025-12-11 00:22:49 +00:00
配置
This commit is contained in:
parent
657f009408
commit
f004db2058
286
.cursorrules
Normal file
286
.cursorrules
Normal file
@ -0,0 +1,286 @@
|
||||
# 项目背景
|
||||
- 数据库:MySQL、Sqlite、Postgres、Typeorm
|
||||
- 语言:TypeScript、JavaScript、CommonJS
|
||||
- 框架:Koa.js、midway.js、cool-admin-midway
|
||||
- 项目版本:8.x
|
||||
|
||||
# 目录
|
||||
项目目录:
|
||||
├── .vscode(代码片段,根据关键字可以快速地生成代码)
|
||||
├── public(静态资源文件,如js、css或者上传的文件)
|
||||
├── src
|
||||
│ └── comm(通用库)
|
||||
│ └── modules(项目模块)
|
||||
│ └── config
|
||||
│ │ └── config.default.ts(默认配置,不区分环境,都生效)
|
||||
│ │ └── config.local.ts(本地开发配置,对应npm run dev)
|
||||
│ │ └── config.prod.ts(生产环境配置,对应npm run start)
|
||||
│ └── configuration.ts(midway的配置文件)
|
||||
│ └── welcome.ts(环境的controller)
|
||||
│ └── interface.ts(类型声明)
|
||||
├── package.json(依赖管理,项目信息)
|
||||
├── bootstrap.js(生产环境启动入口文件,可借助pm2等工具多进程启动)
|
||||
└── ...
|
||||
|
||||
模块目录
|
||||
├── modules
|
||||
│ └── base(基础的权限管理系统)
|
||||
│ │ └── controller(api接口)
|
||||
│ │ └── dto(参数校验)
|
||||
│ │ └── entity(实体类)
|
||||
│ │ └── middleware(中间件)
|
||||
│ │ └── schedule(定时任务)
|
||||
│ │ └── service(服务,写业务逻辑)
|
||||
│ │ └── config.ts(必须,模块的配置)
|
||||
│ │ └── db.json(可选,初始化该模块的数据)
|
||||
│ │ └── menu.json(可选,初始化该模块的菜单)
|
||||
|
||||
# 核心
|
||||
|
||||
- Controller
|
||||
|
||||
```ts
|
||||
import { Get, Provide } from "@midwayjs/core";
|
||||
import { CoolController, BaseController } from "@cool-midway/core";
|
||||
import { BaseSysUserEntity } from "../../../base/entity/sys/user";
|
||||
import { DemoAppGoodsEntity } from "../../entity/goods";
|
||||
|
||||
/**
|
||||
* 商品
|
||||
*/
|
||||
@Provide()
|
||||
@CoolController({
|
||||
// 添加通用CRUD接口
|
||||
api: ["add", "delete", "update", "info", "list", "page"],
|
||||
// 设置表实体
|
||||
entity: DemoAppGoodsEntity,
|
||||
// 向表插入当前登录用户ID
|
||||
insertParam: (ctx) => {
|
||||
return {
|
||||
// 获得当前登录的后台用户ID,需要请求头传Authorization参数
|
||||
userId: ctx.admin.userId,
|
||||
};
|
||||
},
|
||||
// 操作crud之前做的事情 @cool-midway/core@3.2.14 新增
|
||||
before: (ctx) => {
|
||||
// 将前端的数据转JSON格式存数据库
|
||||
const { data } = ctx.request.body;
|
||||
ctx.request.body.data = JSON.stringify(data);
|
||||
},
|
||||
// info接口忽略价格字段
|
||||
infoIgnoreProperty: ["a.price"],
|
||||
// 分页查询配置
|
||||
pageQueryOp: {
|
||||
// 让title字段支持模糊查询
|
||||
keyWordLikeFields: ["a.title"],
|
||||
// 让type字段支持筛选,请求筛选字段与表字段一致是情况
|
||||
fieldEq: ["a.type"],
|
||||
// 多表关联,请求筛选字段与表字段不一致的情况
|
||||
fieldEq: [{ column: "a.id", requestParam: "id" }],
|
||||
// 指定返回字段,注意多表查询这个是必要的,否则会出现重复字段的问题
|
||||
select: ["a.*", "b.name", "a.name AS userName"],
|
||||
// 4.x置为过时 改用 join 关联表用户表
|
||||
leftJoin: [
|
||||
{
|
||||
entity: BaseSysUserEntity,
|
||||
alias: "b",
|
||||
condition: "a.userId = b.id",
|
||||
},
|
||||
],
|
||||
// 4.x新增
|
||||
join: [
|
||||
{
|
||||
entity: BaseSysUserEntity,
|
||||
alias: "b",
|
||||
condition: "a.userId = b.id",
|
||||
type: "innerJoin",
|
||||
},
|
||||
],
|
||||
// 4.x 新增 追加其他条件
|
||||
extend: async (find: SelectQueryBuilder<DemoGoodsEntity>) => {
|
||||
find.groupBy("a.id");
|
||||
},
|
||||
// 增加其他条件
|
||||
where: async (ctx) => {
|
||||
// 获取body参数
|
||||
const { a } = ctx.request.body;
|
||||
return [
|
||||
// 价格大于90
|
||||
["a.price > :price", { price: 90.0 }],
|
||||
// 满足条件才会执行
|
||||
["a.price > :price", { price: 90.0 }, "条件"],
|
||||
// 多个条件一起
|
||||
[
|
||||
"(a.price = :price or a.userId = :userId)",
|
||||
{ price: 90.0, userId: ctx.admin.userId },
|
||||
],
|
||||
];
|
||||
},
|
||||
// 可选,添加排序,默认按createTime Desc排序
|
||||
addOrderBy: {
|
||||
price: "desc",
|
||||
},
|
||||
},
|
||||
})
|
||||
export class DemoAppGoodsController extends BaseController {
|
||||
/**
|
||||
* 其他接口
|
||||
*/
|
||||
@Get("/other")
|
||||
async other() {
|
||||
return this.ok("hello, cool-admin!!!");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
注意:
|
||||
- CoolController的entity,alias 为 "a";
|
||||
- 如果是多表查询,必须设置 select 参数,否则会出现重复字段的错误,因为每个表都继承了 BaseEntity,至少都有 id、createTime、updateTime 三个相同的字段;
|
||||
- keyWordLikeFields、fieldEq等配置哪个字段,都需要有对应的别名;
|
||||
|
||||
- Entity
|
||||
|
||||
```ts
|
||||
// BaseEntity的路径是固定,不能修改
|
||||
import { BaseEntity } from '../../base/entity/base';
|
||||
import { Column, Entity, Index } from 'typeorm';
|
||||
|
||||
/**
|
||||
* demo模块-用户信息
|
||||
*/
|
||||
// 表名必须包含模块固定格式:模块_,
|
||||
@Entity('demo_user_info')
|
||||
// DemoUserInfoEntity是模块+表名+Entity
|
||||
export class DemoUserInfoEntity extends BaseEntity {
|
||||
@Index()
|
||||
@Column({ comment: '手机号', length: 11 })
|
||||
phone: string;
|
||||
|
||||
@Index({ unique: true })
|
||||
@Column({ comment: '身份证', length: 50 })
|
||||
idCard: string;
|
||||
|
||||
// 生日只需要精确到哪一天,所以type:'date',如果需要精确到时分秒,应为'datetime'
|
||||
@Column({ comment: '生日', type: 'date' })
|
||||
birthday: Date;
|
||||
|
||||
@Column({ comment: '状态', dict: ['禁用', '启用'], default: 1 })
|
||||
status: number;
|
||||
|
||||
@Column({ comment: '分类', dict: ['普通', '会员', '超级会员'], default: 0, type: 'tinyint' })
|
||||
type: number;
|
||||
|
||||
// 由于labels的类型是一个数组,所以Column中的type类型必须得是'json'
|
||||
@Column({ comment: '标签', nullable: true, type: 'json' })
|
||||
labels: string[];
|
||||
|
||||
@Column({
|
||||
comment: '余额',
|
||||
type: 'decimal',
|
||||
precision: 12,
|
||||
scale: 2,
|
||||
})
|
||||
balance: number;
|
||||
|
||||
@Column({ comment: '备注', nullable: true })
|
||||
remark: string;
|
||||
|
||||
@Column({ comment: '简介', type: 'text', nullable: true })
|
||||
summary: string;
|
||||
|
||||
@Column({ comment: '省', length: 50 })
|
||||
province: string;
|
||||
|
||||
@Column({ comment: '市', length: 50 })
|
||||
city: string;
|
||||
|
||||
@Column({ comment: '区', length: 50 })
|
||||
district: string;
|
||||
|
||||
@Column({ comment: '详细地址', length: 255 })
|
||||
address: string;
|
||||
}
|
||||
```
|
||||
注意:
|
||||
- 禁止使用外键,如ManyToOne、JoinColumn等;
|
||||
- comment需要简短,如班级表的名称不要叫班级名称,直接叫名称;
|
||||
- dict如果遇到可选项如:状态、类型等需要配置;
|
||||
- BaseEntity的路径是固定,不能修改;
|
||||
|
||||
- Service
|
||||
|
||||
```ts
|
||||
import { Init, Provide } from '@midwayjs/core';
|
||||
import { BaseService } from '@cool-midway/core';
|
||||
import { InjectEntityModel } from '@midwayjs/typeorm';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
/**
|
||||
* 描述
|
||||
*/
|
||||
@Provide()
|
||||
export class XxxService extends BaseService {
|
||||
@InjectEntityModel(实体)
|
||||
xxxEntity: Repository<实体>;
|
||||
|
||||
// 获得ctx
|
||||
@Inject()
|
||||
ctx;
|
||||
|
||||
@Init()
|
||||
async init() {
|
||||
await super.init();
|
||||
this.setEntity(this.xxxEntity);
|
||||
}
|
||||
|
||||
/**
|
||||
* 其它方法
|
||||
*/
|
||||
async xxx() {}
|
||||
}
|
||||
```
|
||||
|
||||
- 自动路由
|
||||
|
||||
规则: /controller 文件夹下的文件夹名或者文件名/模块文件夹名/方法名
|
||||
// 模块目录
|
||||
├── modules
|
||||
│ └── demo(模块名)
|
||||
│ │ └── controller(api接口)
|
||||
│ │ │ └── app(参数校验)
|
||||
│ │ │ │ └── goods.ts(商品的controller)
|
||||
│ │ │ └── pay.ts(支付的controller)
|
||||
│ │ └── config.ts(必须,模块的配置)
|
||||
│ │ └── init.sql(可选,初始化该模块的sql)
|
||||
|
||||
生成的路由前缀为: /pay/demo/xxx(具体的方法)与/app/demo/goods/xxx(具体的方法)
|
||||
|
||||
- config.ts
|
||||
|
||||
```ts
|
||||
import { ModuleConfig } from '@cool-midway/core';
|
||||
|
||||
/**
|
||||
* 模块配置
|
||||
*/
|
||||
export default () => {
|
||||
return {
|
||||
// 模块名称
|
||||
name: 'xxx',
|
||||
// 模块描述
|
||||
description: 'xxx',
|
||||
// 中间件,只对本模块有效
|
||||
middlewares: [],
|
||||
// 中间件,全局有效
|
||||
globalMiddlewares: [],
|
||||
// 模块加载顺序,默认为0,值越大越优先加载
|
||||
order: 0,
|
||||
} as ModuleConfig;
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
# 其它
|
||||
- 根据需要进行必要的关联表查询;
|
||||
- 禁止出现import 但是没有使用的情况;
|
||||
- 所有代码需有类级注释;
|
||||
@ -19,10 +19,10 @@
|
||||
"@midwayjs/typeorm": "^3.20.0",
|
||||
"@midwayjs/upload": "^3.20.0",
|
||||
"@midwayjs/validate": "^3.20.0",
|
||||
"@midwayjs/view-ejs": "^3.20.0",
|
||||
"adm-zip": "^0.5.16",
|
||||
"axios": "^1.7.9",
|
||||
"cron": "^3.5.0",
|
||||
"deasync": "^0.1.30",
|
||||
"jsonwebtoken": "^9.0.2",
|
||||
"lodash": "^4.17.21",
|
||||
"md5": "^2.3.0",
|
||||
@ -75,11 +75,10 @@
|
||||
],
|
||||
"assets": [
|
||||
"public/**/*",
|
||||
"typings/**/*",
|
||||
"node_modules/sqlite3/build/Release/node_sqlite3.node"
|
||||
"typings/**/*"
|
||||
],
|
||||
"targets": [
|
||||
"node20-win-x64"
|
||||
"node20-macos-arm64"
|
||||
],
|
||||
"outputPath": "build"
|
||||
},
|
||||
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@ -53,9 +53,6 @@ importers:
|
||||
'@midwayjs/validate':
|
||||
specifier: ^3.20.0
|
||||
version: 3.20.0
|
||||
'@midwayjs/view-ejs':
|
||||
specifier: ^3.20.0
|
||||
version: 3.20.0
|
||||
adm-zip:
|
||||
specifier: ^0.5.16
|
||||
version: 0.5.16
|
||||
@ -65,6 +62,9 @@ importers:
|
||||
cron:
|
||||
specifier: ^3.5.0
|
||||
version: 3.5.0
|
||||
deasync:
|
||||
specifier: ^0.1.30
|
||||
version: 0.1.30
|
||||
jsonwebtoken:
|
||||
specifier: ^9.0.2
|
||||
version: 9.0.2
|
||||
@ -740,14 +740,6 @@ packages:
|
||||
resolution: {integrity: sha512-LCMna/wAz4LDRKyMQh8Uoh+2W4qhdNJ4frAX5gzrxIUbCN5uTbBpmqqYSLA/j24ItSye4htSdQ7r+H9WM+LUIw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@midwayjs/view-ejs@3.20.0':
|
||||
resolution: {integrity: sha512-HyFAeE6UqmmY7mWDeMsTKXYOARBIAL4Ce9l28hjJudJxTokf514l9OeRpPyWCb6hwMJf/AKgqyYZBcGFLiXhNA==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@midwayjs/view@3.20.0':
|
||||
resolution: {integrity: sha512-rb8hrrfjnA0t7HFoiVb9JqCKzB/OULDbVRBy2gDy2EcnN1da7RtX7m7VU8hCAkqC7ClGDOOhghaYigmp/iVn6A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
|
||||
resolution: {integrity: sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==}
|
||||
cpu: [arm64]
|
||||
@ -1646,6 +1638,10 @@ packages:
|
||||
dayjs@1.8.36:
|
||||
resolution: {integrity: sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw==}
|
||||
|
||||
deasync@0.1.30:
|
||||
resolution: {integrity: sha512-OaAjvEQuQ9tJsKG4oHO9nV1UHTwb2Qc2+fadB0VeVtD0Z9wiG1XPGLJ4W3aLhAoQSYTaLROFRbd5X20Dkzf7MQ==}
|
||||
engines: {node: '>=0.11.0'}
|
||||
|
||||
debug@3.2.7:
|
||||
resolution: {integrity: sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==}
|
||||
peerDependencies:
|
||||
@ -3401,6 +3397,9 @@ packages:
|
||||
node-abort-controller@3.1.1:
|
||||
resolution: {integrity: sha512-AGK2yQKIjRuqnc6VkX2Xj5d+QW8xZ87pa1UK6yA6ouUyuxfHuMP6umE5QK7UmTeOAymo+Zx1Fxiuw9rVx8taHQ==}
|
||||
|
||||
node-addon-api@1.7.2:
|
||||
resolution: {integrity: sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg==}
|
||||
|
||||
node-addon-api@7.1.1:
|
||||
resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
|
||||
|
||||
@ -5432,13 +5431,6 @@ snapshots:
|
||||
'@midwayjs/i18n': 3.20.0
|
||||
joi: 17.13.3
|
||||
|
||||
'@midwayjs/view-ejs@3.20.0':
|
||||
dependencies:
|
||||
'@midwayjs/view': 3.20.0
|
||||
ejs: 3.1.10
|
||||
|
||||
'@midwayjs/view@3.20.0': {}
|
||||
|
||||
'@msgpackr-extract/msgpackr-extract-darwin-arm64@3.0.3':
|
||||
optional: true
|
||||
|
||||
@ -6511,6 +6503,11 @@ snapshots:
|
||||
|
||||
dayjs@1.8.36: {}
|
||||
|
||||
deasync@0.1.30:
|
||||
dependencies:
|
||||
bindings: 1.5.0
|
||||
node-addon-api: 1.7.2
|
||||
|
||||
debug@3.2.7:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@ -8507,6 +8504,8 @@ snapshots:
|
||||
|
||||
node-abort-controller@3.1.1: {}
|
||||
|
||||
node-addon-api@1.7.2: {}
|
||||
|
||||
node-addon-api@7.1.1: {}
|
||||
|
||||
node-fetch@2.7.0(encoding@0.1.13):
|
||||
|
||||
49
src/comm/port.ts
Normal file
49
src/comm/port.ts
Normal file
@ -0,0 +1,49 @@
|
||||
const net = require('net');
|
||||
const deasync = require('deasync');
|
||||
|
||||
/**
|
||||
* 同步检查端口是否可用
|
||||
* @param {number} port - 要检查的端口
|
||||
* @returns {boolean} - 是否可用
|
||||
*/
|
||||
function isPortAvailableSync(port) {
|
||||
let available = false;
|
||||
let checked = false; // 新增标志变量,表示检查是否完成
|
||||
const server = net.createServer();
|
||||
|
||||
server.once('error', err => {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
checked = true; // 标记检查完成
|
||||
server.close();
|
||||
}
|
||||
});
|
||||
|
||||
server.once('listening', () => {
|
||||
available = true; // 标记端口可用
|
||||
checked = true; // 标记检查完成
|
||||
server.close();
|
||||
});
|
||||
|
||||
server.listen(port);
|
||||
|
||||
// 阻塞直到检查完成(checked === true)
|
||||
deasync.loopWhile(() => !checked);
|
||||
return available;
|
||||
}
|
||||
|
||||
/**
|
||||
* 查找可用端口(同步)
|
||||
* @param {number} startPort - 起始端口
|
||||
* @returns {number} - 可用的端口
|
||||
*/
|
||||
export function checkPort(startPort: number) {
|
||||
let port = startPort;
|
||||
while (port <= 65535) {
|
||||
if (isPortAvailableSync(port)) {
|
||||
console.log(`Valid port: ${port}`);
|
||||
return port;
|
||||
}
|
||||
port++;
|
||||
}
|
||||
throw new Error('No available port found');
|
||||
}
|
||||
@ -3,6 +3,7 @@ import { MidwayConfig } from '@midwayjs/core';
|
||||
import { CoolCacheStore } from '@cool-midway/core';
|
||||
import * as path from 'path';
|
||||
import { pCachePath, pUploadPath } from '../comm/path';
|
||||
import { checkPort } from '../comm/port';
|
||||
|
||||
// redis缓存
|
||||
// import { redisStore } from 'cache-manager-ioredis-yet';
|
||||
@ -11,7 +12,7 @@ export default {
|
||||
// 确保每个项目唯一,项目首次启动会自动生成
|
||||
keys: '576848ea-bb0c-4c0c-ac95-c8602ef908b5',
|
||||
koa: {
|
||||
port: 8001,
|
||||
port: checkPort(8001),
|
||||
},
|
||||
// 开启异步上下文管理
|
||||
asyncContextManager: {
|
||||
|
||||
@ -17,7 +17,7 @@ export default {
|
||||
// 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
|
||||
synchronize: true,
|
||||
// 打印日志
|
||||
logging: true,
|
||||
logging: false,
|
||||
// 实体路径
|
||||
entities: ['**/modules/*/entity'],
|
||||
// 订阅者
|
||||
|
||||
@ -16,7 +16,7 @@ export default {
|
||||
// 自动建表 注意:线上部署的时候不要使用,有可能导致数据丢失
|
||||
synchronize: true,
|
||||
// 打印日志
|
||||
logging: true,
|
||||
logging: false,
|
||||
// 实体路径
|
||||
entities,
|
||||
// 订阅者
|
||||
|
||||
44
src/modules/base/event/app.ts
Normal file
44
src/modules/base/event/app.ts
Normal file
@ -0,0 +1,44 @@
|
||||
import { CoolEvent, Event } from '@cool-midway/core';
|
||||
import { App, ILogger, IMidwayApplication, Inject } from '@midwayjs/core';
|
||||
|
||||
/**
|
||||
* 接收事件
|
||||
*/
|
||||
@CoolEvent()
|
||||
export class BaseAppEvent {
|
||||
@App()
|
||||
app: IMidwayApplication;
|
||||
|
||||
@Inject()
|
||||
logger: ILogger;
|
||||
|
||||
@Event('onServerReady')
|
||||
async onServerReady() {
|
||||
if (!process['pkg']) return;
|
||||
const port = this.app.getConfig('koa.port') || 8001;
|
||||
this.logger.info(`Server is running at http://127.0.0.1:${port}`);
|
||||
const url = `http://127.0.0.1:${port}`;
|
||||
|
||||
// 使用 child_process 打开浏览器
|
||||
const { exec } = require('child_process');
|
||||
let command;
|
||||
|
||||
switch (process.platform) {
|
||||
case 'darwin': // macOS
|
||||
command = `open ${url}`;
|
||||
break;
|
||||
case 'win32': // Windows
|
||||
command = `start ${url}`;
|
||||
break;
|
||||
default: // Linux
|
||||
command = `xdg-open ${url}`;
|
||||
break;
|
||||
}
|
||||
|
||||
exec(command, (error: any) => {
|
||||
if (!error) {
|
||||
this.logger.info(`Application has opened in browser at ${url}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user