---
URL: "https://elysia-admin.top/"
LLMS_URL: "https://elysia-admin.top/\\index.md"
title: "Elysia Admin - 基于 ElysiaJS + Vue3 的全栈后台管理系统"
description: "Elysia Admin 是一套基于 Bun + ElysiaJS + DrizzleORM + Vue3 的高性能全栈后台管理系统,开箱即用,内置 RBAC 权限、JWT 认证、定时任务、队列等企业级功能。"
---
---
title: Elysia Admin - 基于 ElysiaJS + Vue3 的全栈后台管理系统
description: Elysia Admin 是一套基于 Bun + ElysiaJS + DrizzleORM + Vue3 的高性能全栈后台管理系统,开箱即用,内置 RBAC 权限、JWT 认证、定时任务、队列等企业级功能。
---
---
URL: "https://elysia-admin.top/architecture/commands.html"
LLMS_URL: "https://elysia-admin.top/\\architecture\\commands.md"
title: "内置命令 - Elysia Admin 架构"
description: "详细说明 Elysia Admin 项目内置的所有命令,包括开发启动、生产构建、数据库操作及 Docker 管理命令的使用方法。"
---
---
title: 内置命令 - Elysia Admin 架构
description: 详细说明 Elysia Admin 项目内置的所有命令,包括开发启动、生产构建、数据库操作及 Docker 管理命令的使用方法。
---
# 内置命令
项目脚本定义在 `server/package.json`(后端)和 `admin/package.json`(前端)。下文 **未特别说明时,均在 `server/` 目录执行**。
## 命令速查
### 后端 server/
| 命令 | 作用 |
|------|------|
| `bun run dev` | 开发:HTTP 主进程(watch + 新模块自动重载) |
| `bun run dev:plain` | 开发:仅原生 `bun --watch`(无新模块目录监听) |
| `bun run dev:workers` | 开发:构建 Processor 后启动 Worker |
| `bun run dev:all` | 开发:主进程 + Worker 同时启动 |
| `bun run dev:debug` | 调试:inspect 6499,供 IDE Attach |
| `bun run dev:debug:browser` | 调试:推荐,配合 debug.bun.sh |
| `bun run dev:debug:watch` | 调试 + 热重载(易卡顿,慎用) |
| `bun run build` | 生产构建(主进程 + Worker + Processor) |
| `bun run build:processors` | 仅重建队列 Processor |
| `bun run start` | 生产:直接启动 `dist/index.js` |
| `bun run test` | 单元测试 |
| `bun run test:watch` | 测试 watch 模式 |
| `bun run test:coverage` | 测试 + 覆盖率 |
| `bun run create:module` | 脚手架:后端 CRUD 模块 |
| `bun run create:page` | 脚手架:前端 CRUD 页面 |
| `bun run db:push` | Schema 推送到数据库 |
| `bun run db:pull` | 从数据库拉取 Schema |
| `bun run gen:crypto-keys` | 生成 RSA 密钥对并写入 yaml 的 `crypto` 段 |
| `bun run docker:build` | 构建 Docker 镜像(含 dist 全量产物) |
| `bun run docker:run` | 运行容器 |
| `bun run docker:stop` / `docker:rm` / `docker:logs` | 容器管理 |
### 前端 admin/
| 命令 | 作用 |
|------|------|
| `pnpm dev` | Vite 开发服务器(默认 3006) |
| `pnpm build` | 生产构建 → `admin/dist/` |
| `pnpm serve` | 预览构建结果 |
## 开发启动
### dev
只启动 HTTP 主进程。入口为 `script/dev-watch-modules.ts`:内部仍以 `bun --watch src/index.ts` 跑服务,并额外轮询 `src/modules` 目录集合。新增或删除模块目录且写盘稳定约 5 秒后,会 touch 已在模块图中的 `src/modules/index.ts`,触发 `bun --watch` 自行重载,从而无需手动重启即可访问新接口。已有模块的 `route.ts` / `handle.ts` / `dto.ts` 改动仍走原生热重载。日常写接口用这个即可。
```bash
cd server
bun run dev
```
### dev:plain
逃生口:行为与原先直接 `bun --watch src/index.ts` 一致,不监听新模块目录。排查守护脚本问题时可用。
```bash
bun run dev:plain
```
### dev:workers
先执行 `build:processors`,再启动 Worker 进程消费队列、跑定时任务。只用接口、不碰队列时可以不开。
```bash
bun run dev:workers
```
### dev:all
主进程和 Worker 一起启,日志带 `[server]` / `[workers]` 前缀,`Ctrl+C` 同时退出。全栈联调、测队列时推荐。
```bash
bun run dev:all
```
使用队列或定时任务时,至少保证 Worker 在跑。详见 [队列](/guide/queue)。
> `package.json` 中开发类脚本已内置 `NODE_ENV=development`、构建类脚本已内置 `NODE_ENV=production`,一般无需在命令行前手动指定环境变量。
## 调试
日常开发用 `bun run dev`;需要断点再用下面命令。
| 场景 | 命令 |
|------|------|
| 正常开发 | `bun run dev` |
| 浏览器断点(推荐) | `bun run dev:debug:browser` + [debug.bun.sh](https://debug.bun.sh) |
| Cursor / VS Code Attach | `bun run dev:debug`,F5 连 6499 端口 |
```mermaid
flowchart LR
A[bun run dev] --> N[日常开发]
B[dev:debug:browser] --> D[debug.bun.sh 断点]
C[dev:debug + F5] --> I[IDE Attach 较慢]
```
### dev:debug
`--inspect=6499` 启动,不含 watch。配合 VS Code 任务 `Server: dev:debug` 或 launch **Debug Server (IDE, 较慢)**。
```bash
cd server
bun run dev:debug
```
### dev:debug:browser
Bun 官方推荐的调试方式,比 IDE Attach 流畅。启动后把终端里的 `https://debug.bun.sh/#localhost:6499/...` 复制到浏览器,在 Sources 里下断点。
```bash
bun run dev:debug:browser
```
也可通过 **Terminal → Run Task → Server: dev:debug (browser, 推荐)** 启动。
### dev:debug:watch
同时开 inspect 和 watch,调试时容易卡顿或反复重连,仅在「边改代码边调试」时用。
**使用方式:**
```bash [bun]
cd server
bun run dev:debug:watch
```
服务已手动启动时,可用 **Attach Server (6499)** 配置连接。
## 构建
### build
生产环境完整构建:主进程、Worker、所有 Processor、静态资源、`production.yaml`、`ecosystem.config.cjs`。
```bash
cd server
bun run build
```
产物目录 `dist/`:
```
dist/
├── index.js # HTTP 主进程
├── workers.js # Worker 进程
├── ecosystem.config.cjs # PM2 配置
├── production.yaml # 生产配置(部署前请修改)
├── public/ # 前端静态资源(若已复制 admin 构建产物)
├── dist/cjs/ # BullMQ 沙箱 bootstrap
└── processors/
├── system-cron.js
├── flow-buffer.js
└── trade-order.js
```
部署说明见 [部署](/guide/deploy)。
### build:processors
只重建 `processors/*.js` 和 `dist/cjs/`。改了 `processor.ts` 或 `worker-sandbox/*.ts` 后执行,再重启 Worker。
```bash
bun run build:processors
```
## 测试
测试在 `server/test/`,`test/preload.ts` 会把配置指向 `development.yaml`(`bun test` 默认 `NODE_ENV=test`,否则会找不存在的 `test.yaml`)。
当前覆盖:纯函数、校验、树结构、时间、`CreateQueryBuilder` 等;**不含** handle 层、真实 DB/Redis、网络请求。
```bash
cd server
bun run test
bun run test:watch
bun run test:coverage
```
## 模块脚手架
在 **`server/`** 执行。依赖 `server/database/schema/` 里**已存在**的 Drizzle 表;字典、菜单、handoff SQL 仍需人工或 AI 补充。完整流程见 [AI 开发指南](/guide/ai-guide)。
### create:module
生成 `server/src/modules/{group}-{name}/` 下的 `route.ts`、`dto.ts`、`handle.ts`。
```bash
cd server
bun run create:module business-goods --tag 商品管理
```
| 参数 | 说明 |
|------|------|
| `--tag` | 模块中文名(API 文档分组) |
| `--schema business_goods` | 指定 schema 文件名,默认由 slug 推导 |
| `--dry-run` | 仅预览,不写文件 |
### create:page
生成前端 CRUD 五件套到 `admin/`(types、api、views)。脚本仍在 server 侧运行。
```bash
bun run create:page business goods --tag 商品管理
```
参数与 `create:module` 相同。`business goods` 对应路由目录 `admin/src/views/business/goods/`。
### 推荐流程
```bash
cd server
bun run create:module business-goods --tag 商品管理
bun run create:page business goods --tag 商品管理
# 再用 AI 补业务校验、字典、菜单与 server/database/sql/business-goods-init.sql
```
## 数据库
**仅建议在开发环境使用。**
```bash
# 将本地 schema 推送到 PostgreSQL
bun run db:push
# 从数据库拉取结构到本地 schema
bun run db:pull
```
表结构说明见 [数据库设计](./database)。业务查询写法见 [数据库操作](/guide/database-operation)。
## 请求加解密密钥
在 **`server/`** 执行。脚本生成 RSA-2048 密钥对(私钥 PKCS8、公钥 SPKI),写入 `server/src/config/` 下对应环境的 yaml,只替换 `crypto` 段的 `privateKey` / `publicKey`。`crypto.enabled` 保持原值,需要启用时再手动改成 `true`。协议与前后端开关见 [请求加解密](/guide/crypto)。
### gen:crypto-keys
```bash
cd server
bun run gen:crypto-keys
bun run gen:crypto-keys production
bun run gen:crypto-keys production --force
```
| 参数 | 说明 |
|------|------|
| (省略) | 写入 `development.yaml` |
| `production` | 写入 `production.yaml` |
| `--force` | 已有密钥时强制覆盖 |
目标文件里必须已经有 `crypto:` 段,否则脚本直接退出。`privateKey` 已有内容且未加 `--force` 时也会拒绝写入:覆盖后,用旧私钥解不开的报文无法再恢复。私钥只留在服务端 yaml 中,不要提交到仓库。生产环境建议先生成密钥,确认 `enabled` 后再打开加密。
## Docker
在 `server/` 目录,构建并运行含 HTTP + Worker 的完整 dist 镜像(单容器双进程)。
```bash
bun run docker:build # 构建镜像 hnq1/elysia-admin:latest
bun run docker:run # 启动容器,映射 3000
bun run docker:stop # 停止
bun run docker:rm # 删除容器
bun run docker:logs # 查看日志
```
Compose 多服务示例见 [部署](/guide/deploy)。
## 工作区任务
打开 `elysia-admin.code-workspace` 后,**Terminal → Run Task** 常用项:
| 任务 | 说明 |
|------|------|
| `Dev: Full Stack` | 前后端一起启 |
| `Server: dev` | 仅后端 |
| `Admin: dev` | 仅前端 |
| `Server: dev:debug (browser, 推荐)` | 浏览器调试 |
| `Server: dev:debug` | IDE 调试 |
---
URL: "https://elysia-admin.top/architecture/database.html"
LLMS_URL: "https://elysia-admin.top/\\architecture\\database.md"
title: "数据库设计 - Elysia Admin 架构"
description: "详细介绍 Elysia Admin 的数据库设计,包括系统管理、监控管理、业务管理各模块的表结构、字段说明、关联关系及索引设计最佳实践。"
---
---
title: 数据库设计 - Elysia Admin 架构
description: 详细介绍 Elysia Admin 的数据库设计,包括系统管理、监控管理、业务管理各模块的表结构、字段说明、关联关系及索引设计最佳实践。
---
# 数据库设计
本文介绍 Elysia Admin 的 PostgreSQL 表结构设计。Schema 源码在 `server/database/schema/`,以 Drizzle 定义为准;本文便于快速查阅表职责、字段含义和表间关系。
日常 CRUD 与查询写法见 [数据库操作](/guide/database-operation);新建业务表规范可参考 `.ai/AI_SCHEMA_GUIDE.md`。
## 技术栈
| 组件 | 选型 |
|------|------|
| 数据库 | PostgreSQL >= 16 |
| ORM | [Drizzle ORM](https://orm.drizzle.team/) |
| 数据库驱动 | [postgres](https://github.com/porsager/postgres)(轻量级 PG 驱动) |
| 缓存 | Redis >= 6([Ioredis](https://ioredis.org/),会话、缓存与队列共用) |
| 迁移 | Drizzle Kit(`bun run db:push` / `db:pull`,见下方常用命令) |
## 设计原则
- **BaseSchema**:业务表统一继承基础审计字段(创建/更新人、时间、软删、备注)
- **软删除**:`delFlag = true` 标记删除,查询时默认过滤
- **主键**:系统用户用 UUID;其余多数表用 `bigserial` 自增
- **外键**:Drizzle schema 中声明 `references`,保证引用完整性
- **命名**:字段驼峰(代码侧)对应数据库下划线列名
- **状态**:启用/禁用常用 `boolean` 的 `status`;业务流程状态常用 `varchar` + 字典
## BaseSchema
所有业务表通过 `...BaseSchema` 展开以下字段(定义见 `server/database/base-schema.ts`):
| 字段 | 类型 | 说明 | 默认 |
|------|------|------|------|
| createTime | timestamp | 创建时间 | now() |
| createBy | varchar(36) | 创建人 ID(UUID 字符串) | null |
| updateTime | timestamp | 更新时间 | null |
| updateBy | varchar(36) | 更新人 ID | null |
| delFlag | boolean | 软删除标志 | false |
| remark | varchar(255) | 备注 | null |
下文各表字段表**不含** BaseSchema 重复列,实际表结构均包含上述字段。
## 模块总览
```mermaid
flowchart TB
subgraph 系统 system
U[system_user]
R[system_role]
M[system_menu]
D[system_dict]
N[system_notice]
end
subgraph 监控 monitor
J[monitor_job]
end
subgraph 业务 business
BM[business_merchant]
BO[business_orders]
BP[business_payments]
end
U --> R
R --> M
BO --> BM
BO --> BP
```
| 模块 | 表数量 | 主要职责 |
|------|--------|----------|
| 系统管理 | 17 | RBAC、字典、日志、存储、IP 黑名单、通知公告、站点部署 |
| 监控管理 | 1 | 定时任务配置 |
| 业务管理 | 5 | 商户、订单、支付、退款 |
## 系统管理
### system_user 用户表
存储后台账号。主键 `userId` 为 UUID。
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| userId | uuid | PK | 用户 ID |
| username | varchar(64) | NOT NULL,部分唯一索引 `WHERE del_flag = false` | 登录名。已软删行不占用用户名 |
| password | varchar(255) | NOT NULL | 密码(加密) |
| nickname | varchar(64) | | 昵称 |
| email | varchar(64) | | 邮箱 |
| phone | varchar(11) | | 手机 |
| sex | varchar(1) | | 0 未知 / 1 男 / 2 女 |
| avatar | varchar(255) | | 头像 URL |
| deptId | bigint | FK | 部门 ID |
| status | boolean | | 启用 / 禁用 |
关联:多对一 `system_dept`;多对多 `system_role`(经 `system_user_role`)。
用户名唯一性由部分唯一索引 `system_user_username_unique`(`WHERE del_flag = false`)保证。从列级 `UNIQUE` 升级时,先去掉旧约束,再执行 `bun run db:push` 或手工建该索引。
### system_role 角色表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| roleId | bigserial | PK | 角色 ID |
| roleName | varchar(64) | NOT NULL | 角色名称 |
| roleCode | varchar(64) | NOT NULL | 角色编码 |
| sort | integer | | 排序 |
| status | boolean | | 状态 |
关联:多对多用户、菜单(经关联表)。
### system_user_role 用户角色关联
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| userId | uuid | FK | 用户 ID |
| roleId | bigint | FK | 角色 ID |
### system_menu 菜单表
树形菜单与前端路由配置,支持外链、iframe、KeepAlive、固定标签等。
| 字段 | 类型 | 说明 |
|------|------|------|
| menuId | bigserial PK | 菜单 ID |
| path / name / component | varchar | 路由路径、名称、组件 |
| title / icon | varchar | 标题、图标 |
| showBadge / showTextBadge | | 徽章 |
| isHide / isHideTab | boolean | 隐藏菜单 / 标签 |
| link / isIframe / isFullPage | | 外链、内嵌、全屏 |
| keepAlive / fixedTab / activePath | | 缓存、固定标签、激活路径 |
| sort / status / parentId | | 排序、状态、父级 |
### system_menu_btn 菜单按钮
| 字段 | 类型 | 说明 |
|------|------|------|
| btnId | bigserial PK | 按钮 ID |
| menuId | bigint FK | 所属菜单 |
| title | varchar | 按钮名 |
| permission | varchar | 权限标识,如 `system:user:create` |
| sort / status | | 排序、状态 |
### system_role_menu 角色菜单关联
| 字段 | 类型 | 说明 |
|------|------|------|
| roleId | bigint FK | 角色 |
| menuId | bigint FK | 菜单 |
| menuBtnId | bigint FK | 按钮(可选) |
### RBAC 关系图
用户通过角色获得菜单与按钮权限:
```mermaid
erDiagram
system_user ||--o{ system_user_role : has
system_role ||--o{ system_user_role : has
system_role ||--o{ system_role_menu : grants
system_menu ||--o{ system_role_menu : on
system_menu ||--o{ system_menu_btn : contains
system_user }o--|| system_dept : belongs_to
```
### system_dept 部门表
树形组织架构,`parentId` 自关联。
| 字段 | 类型 | 说明 |
|------|------|------|
| deptId | bigserial PK | 部门 ID |
| deptName | varchar(64) | 部门名称 |
| parentId | bigint | 父部门 |
| sort / status | | 排序、状态 |
### system_api 接口表
记录 API 路径与方法,用于权限与文档。
| 字段 | 类型 | 说明 |
|------|------|------|
| apiId | bigserial PK | API ID |
| apiName | varchar(64) | 名称 |
| apiPath | varchar(255) | 路径 |
| apiMethod | varchar(10) | GET / POST 等 |
| status | boolean | 状态 |
### system_dict_type / system_dict_data 字典
类型表定义 `dictType`,数据表存 label/value,前端 `useDictStore` 读取。
**system_dict_type**
| 字段 | 说明 |
|------|------|
| dictId | PK |
| dictName / dictType | 名称、类型编码(唯一) |
| status | 状态 |
**system_dict_data**
| 字段 | 说明 |
|------|------|
| dictCode | PK |
| dictType | 关联类型 |
| dictLabel / dictValue | 显示文本、实际值 |
| dictSort | 排序 |
| tagType / customClass | 标签样式 |
| status | 状态 |
### system_login_log 登录日志
| 字段 | 说明 |
|------|------|
| logId | PK |
| loginType / loginName | 登录方式、用户名 |
| clientType / clientPlatform | 客户端 |
| ipaddr / loginLocation | IP、地理位置 |
| userAgent / os | UA、系统 |
| message / status | 提示、成功与否 |
### system_oper_log 操作日志
中间件 `AddOperLog` 写入,路由 `meta.isLog: true` 时记录。
| 字段 | 说明 |
|------|------|
| operId | PK |
| title / action | 模块、操作名 |
| requestMethod | HTTP 方法 |
| userId / operName | 操作人 |
| operUrl / operIp / operLocation | URL、IP、地点 |
| operParam / jsonResult | 请求参数、响应摘要 |
| costTime | 耗时(ms) |
| status | 是否成功 |
### system_ip_black IP 黑名单
| 字段 | 说明 |
|------|------|
| ipBlackId | PK |
| ipAddress | IP(唯一) |
| status | 是否生效 |
### system_storage 存储配置
对象存储(OSS / COS / S3 / RustFS 等)连接信息,详见 [文件存储](/guide/storage)。
| 字段 | 说明 |
|------|------|
| storageId | PK |
| name | 配置名称(唯一) |
| region / endpoint / bucket | 区域、端点、桶 |
| accessKey / secretKey | 密钥 |
| status | 是否启用(同时仅一条可为 true) |
### system_notice 通知公告
后台「系统管理 → 通知公告」维护的系统公告,内容存 HTML(富文本编辑器)。
| 字段 | 说明 |
|------|------|
| noticeId | PK |
| title | 公告标题 |
| noticeType | 通知类型,字典 `system_notice_type` |
| content | 公告正文(HTML) |
| status | 发布状态(启用 / 停用) |
| sort | 排序 |
Handoff SQL:`server/database/sql/system-notice-init.sql`(菜单与按钮权限)。
### system_deploy_site 站点
后台「站点部署」保存的 Nginx 站点。未删除行的域名唯一(部分唯一索引 `uk_system_deploy_site_server_name`)。用法见 [站点部署](/guide/site-deploy)。
| 字段 | 说明 |
|------|------|
| siteId | PK |
| name | 站点名 |
| serverName | 域名 |
| wwwRoot / webRoot / certDir | 网站根目录、HTTP-01 校验目录、证书目录 |
| email | 续签联系邮箱 |
| sslAutoRenew / reloadNginx | 自动续签、续签后是否 `nginx -s reload` |
| useBaota / httpRedirect | 是否按宝塔目录生成配置、HTTP 是否跳 HTTPS |
| locations | 反代与静态路径(jsonb) |
| certExpireAt / lastRenewAt / lastRenewMsg | 证书到期时间、最近续签时间与结果 |
### system_deploy_cert 证书记录
每次申请或续签写一条记录。同一站点仅一条 `isCurrent = true` 且未删除的记录(部分唯一索引 `uk_system_deploy_cert_current`)。
| 字段 | 说明 |
|------|------|
| certId | PK |
| siteId | 所属站点 |
| issuer / serialNumber / fingerprint | 颁发者、序列号、SHA-256 指纹 |
| notBefore / notAfter | 生效与到期时间 |
| success / isCurrent | 是否成功、是否当前证书 |
| renewSource | `manual` 立即续签,或 `cron` 定时任务 |
| message | 结果说明 |
## 监控管理
### monitor_job 定时任务
与 `system-cron-queue`、后台「定时任务」页配合。`jobName` 须与 `worker-sandbox` 注册名一致。
| 字段 | 类型 | 说明 |
|------|------|------|
| jobId | bigserial PK | 任务 ID |
| jobName | varchar(64) UNIQUE | 任务名称 |
| jobCron | varchar(64) | Cron 表达式 |
| jobArgs | varchar(256) | JSON 数组参数 |
| status | boolean | 是否启用 |
详见 [定时任务](/guide/cron)。
## 业务管理
支付示例模块:商户 → 订单 → 支付流水 → 退款。
```mermaid
erDiagram
business_merchant ||--o{ business_merchant_configs : has
business_merchant ||--o{ business_orders : receives
business_orders ||--o{ business_payments : pays
business_orders ||--o{ business_refund : refunds
business_payments ||--o{ business_refund : from
```
### business_merchant 商户表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigserial PK | 商户 ID |
| name | varchar(100) | 商户名称 |
| status | boolean | 状态 |
### business_merchant_configs 商户支付配置
一条记录对应一个商户的一种支付渠道,供 `Pay(channel, platform)` 使用。
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigserial PK | 配置 ID |
| merchantId | bigint FK | 商户 |
| channel | varchar(20) | alipay / wechat / paypal |
| appId / mchId | varchar | 应用 ID、微信商户号 |
| privateKey / publicKey | text | 密钥 PEM |
| config | jsonb | notifyUrl、apiV3Key 等扩展 |
| status | boolean | 是否启用 |
详见 [支付集成](/guide/payment)。
### business_orders 订单表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigserial PK | 订单 ID |
| orderNo | varchar(64) UNIQUE | 订单号 |
| userId | varchar(64) | 下单用户(UUID 字符串) |
| merchantId | bigint FK | 商户 |
| title / description | varchar | 标题、描述 |
| amount | decimal(10,2) | 金额 |
| currency | varchar(10) | 默认 CNY |
| status | varchar(20) | 字典 `system_orders_status` |
| expireTime | timestamp | 过期时间 |
| extra | jsonb | 商品列表等扩展 |
订单状态(字典 `system_orders_status`):
| 值 | 含义 |
|----|------|
| 0 | 待支付 |
| 1 | 已支付 |
| 2 | 已取消 |
| 3 | 已过期 |
| 4 | 已退款 |
### business_payments 支付流水
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigserial PK | 支付 ID |
| orderId | bigint FK | 订单 ID |
| orderNo | varchar(64) UNIQUE FK | 订单号 |
| merchantConfigId | bigint FK | 商户配置 |
| paymentNo | varchar(64) UNIQUE | 本地支付单号 |
| platform | varchar(20) | 终端:app / h5 / mini / pc |
| paymentMethod | varchar(20) | 渠道:alipay / wechat / paypal |
| amount | decimal(10,2) | 实付金额 |
| status | varchar(20) | 字典 `system_pay_status` |
| thirdTradeNo | varchar(100) | 第三方交易号 |
| extra | jsonb | 回调原始数据等 |
支付状态(`system_pay_status`):0 待支付、1 成功、2 失败、3 已关闭。
### business_refund 退款表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | bigserial PK | 退款 ID |
| orderId | bigint FK | 订单 |
| paymentId | bigint FK | 支付流水 |
| refundNo | varchar(64) UNIQUE | 退款单号 |
| amount | decimal(10,2) | 退款金额 |
| status | varchar(20) | 字典 `system_refund_status` |
| thirdRefundNo | varchar(100) | 第三方退款号 |
| extra | jsonb | 扩展 |
退款状态:0 退款中、1 成功、2 失败。
## 索引建议
常用查询字段建议加索引(列名为数据库下划线形式):
```sql
-- 用户
CREATE INDEX idx_user_username ON system_user(username);
CREATE INDEX idx_user_dept ON system_user(dept_id);
CREATE INDEX idx_user_status ON system_user(status);
-- 菜单
CREATE INDEX idx_menu_parent ON system_menu(parent_id);
CREATE INDEX idx_menu_status ON system_menu(status);
-- 订单
CREATE INDEX idx_order_no ON business_orders(order_no);
CREATE INDEX idx_order_user ON business_orders(user_id);
CREATE INDEX idx_order_merchant ON business_orders(merchant_id);
CREATE INDEX idx_order_status ON business_orders(status);
CREATE INDEX idx_order_create_time ON business_orders(create_time);
-- 支付
CREATE INDEX idx_payment_order ON business_payments(order_id);
CREATE INDEX idx_payment_no ON business_payments(payment_no);
CREATE INDEX idx_payment_third_trade ON business_payments(third_trade_no);
-- 日志
CREATE INDEX idx_login_log_time ON system_login_log(create_time);
CREATE INDEX idx_oper_log_time ON system_oper_log(create_time);
CREATE INDEX idx_oper_log_user ON system_oper_log(user_id);
```
Drizzle `db:push` 不会自动创建上述索引,可按需在 schema 或迁移 SQL 中补充。
## 常用命令
在 `server/` 目录执行:
```bash
# 将 schema 推送到数据库(开发常用)
bun run db:push
# 从数据库拉取结构到本地 schema
bun run db:pull
```
使用 Drizzle Kit 迁移工作流时:
```bash
bun drizzle-kit generate
bun drizzle-kit migrate
```
初始化 SQL 脚本在 `server/database/sql/`,新模块菜单权限可合并进 `{模块}-init.sql` 手动执行。
## 性能建议
- 列表查询走 `FindPage` + `CreateQueryBuilder`,避免一次拉全表
- 热点字典、配置用 Redis `WithCache`,见 [缓存](/guide/cache)
- 日志表定期归档或清理
- 联表查询注意 N+1,优先 `FindPageWithJoin` 或批量查询
- 连接池大小按并发调优,勿过大占满数据库连接
## 相关文档
- [数据库操作](/guide/database-operation)
- [缓存](/guide/cache)
- [支付集成](/guide/payment)
- [定时任务](/guide/cron)
---
URL: "https://elysia-admin.top/architecture/structure.html"
LLMS_URL: "https://elysia-admin.top/\\architecture\\structure.md"
title: "项目结构 - Elysia Admin 架构"
description: "详细说明 Elysia Admin 的整体目录结构,包括前端 Vue3 模块划分和后端 ElysiaJS 分层架构设计,帮助开发者快速了解项目组织方式。"
---
---
title: 项目结构 - Elysia Admin 架构
description: 详细说明 Elysia Admin 的整体目录结构,包括前端 Vue3 模块划分和后端 ElysiaJS 分层架构设计,帮助开发者快速了解项目组织方式。
---
# 项目结构
这里将详细说明 `Elysia Admin` 的整体结构,包括前端和后端的目录组织、文件功能及模块划分,能够帮助开发人员快速了解项目架构。
## 整体结构
```
elysia-admin/
├── admin/ # Vue 3 前端
├── server/ # Elysia + Bun 后端
├── .ai/ # AI 开发指南(模块 SOP、代码示例等)
├── .cursor/ # Cursor 规则与 Skill
├── .claude/ # Claude 规则
├── .codex/ # Codex 配置
├── .kiro/ # Kiro Steering
├── .qoder/ # Qoder 规则
├── .trae/ # Trae 规则
├── .vscode/ # VS Code 工作区配置
├── AGENTS.md # 项目级 AI 指引
├── elysia-admin.code-workspace
├── LICENSE
├── README.md
└── .gitignore
```
## 前端结构
前端采用 `Vue 3` 框架构建,遵循模块化、组件化的设计原则。
```
├── src
│ ├── api # API 接口相关代码
│ │ ├── auth.ts # 认证相关 API 接口定义(登录、注册、用户信息等)
│ │ └── system-manage.ts # 系统管理相关 API 接口定义(菜单、用户、角色管理等)
│ ├── App.vue # Vue 根组件,定义应用全局结构和入口
│ ├── assets # 静态资源目录
│ │ ├── images # 图片资源目录
│ │ ├── styles # 全局样式文件
│ │ │ ├── core # 核心样式(系统级样式)
│ │ │ ├── custom # 自定义样式(业务级样式)
│ │ │ └── index.scss # 样式入口文件
│ │ └── svg # SVG 相关资源
│ │ └── loading.ts # 加载动画 SVG 定义
│ ├── components # 组件目录
│ │ ├── business # 业务组件(业务相关自定义组件)
│ │ │ └── comment-widget # 评论组件
│ │ └── core # 核心组件(系统级通用组件库)
│ │ ├── banners # 横幅组件
│ │ ├── base # 基础组件
│ │ ├── cards # 卡片组件
│ │ ├── charts # 图表组件
│ │ ├── forms # 表单组件
│ │ ├── layouts # 布局组件
│ │ ├── media # 媒体组件
│ │ ├── others # 其他组件
│ │ ├── tables # 表格组件
│ │ ├── text-effect # 文本特效组件
│ │ ├── theme # 主题相关组件
│ │ ├── views # 视图组件
│ │ └── widget # 小部件组件
│ ├── config # 项目配置目录
│ │ ├── assets # 静态资源配置
│ │ │ └── images.ts # 图片资源路径配置
│ │ ├── modules # 模块化配置
│ │ │ ├── component.ts # 组件配置
│ │ │ ├── fastEnter.ts # 快捷入口配置
│ │ │ ├── festival.ts # 节日/活动配置
│ │ │ └── headerBar.ts # 顶部栏配置
│ │ ├── index.ts # 配置入口文件
│ │ └── setting.ts # 系统设置配置
│ ├── directives # Vue 自定义指令
│ │ ├── business # 业务指令
│ │ │ ├── highlight.ts # 高亮指令
│ │ │ └── ripple.ts # 波纹效果指令
│ │ ├── core # 核心指令
│ │ │ ├── auth.ts # 认证指令
│ │ │ └── roles.ts # 角色权限指令
│ │ └── index.ts # 指令入口文件
│ ├── enums # 枚举定义
│ │ ├── appEnum.ts # 应用级枚举(主题类型、语言类型等)
│ │ └── formEnum.ts # 表单相关枚举(表单状态、验证规则等)
│ ├── env.d.ts # TypeScript 环境声明文件
│ ├── hooks # Vue 3 Composable 函数(可复用逻辑)
│ │ ├── core # 核心 Hooks
│ │ │ ├── useAppMode.ts # 应用模式相关逻辑
│ │ │ ├── useAuth.ts # 认证相关逻辑
│ │ │ ├── useCeremony.ts # 节日/仪式相关逻辑
│ │ │ ├── useChart.ts # 图表相关逻辑
│ │ │ ├── useCommon.ts # 通用逻辑
│ │ │ ├── useFastEnter.ts # 快捷入口逻辑
│ │ │ ├── useHeaderBar.ts # 顶部栏逻辑
│ │ │ ├── useLayoutHeight.ts # 布局高度计算逻辑
│ │ │ ├── useTable.ts # 表格逻辑
│ │ │ ├── useTableColumns.ts # 表格列配置逻辑
│ │ │ ├── useTableHeight.ts # 表格高度计算逻辑
│ │ │ └── useTheme.ts # 主题切换逻辑
│ │ └── index.ts # Hooks 入口文件
│ ├── locales # 国际化(i18n)资源
│ │ ├── index.ts # 国际化入口文件
│ │ └── langs # 多语言文件
│ │ ├── en.json # 英文语言包
│ │ └── zh.json # 中文语言包
│ ├── main.ts # 项目主入口文件
│ ├── mock # Mock 数据目录
│ │ ├── json # JSON 格式 Mock 数据
│ │ │ └── chinaMap.json # 中国地图数据
│ │ ├── temp # 临时 Mock 数据
│ │ │ ├── articleList.ts # 文章列表数据
│ │ │ ├── commentDetail.ts # 评论详情数据
│ │ │ ├── commentList.ts # 评论列表数据
│ │ │ └── formData.ts # 表单数据
│ │ └── upgrade # 更新日志数据
│ │ └── changeLog.ts # 变更日志数据
│ ├── plugins # 插件配置
│ │ ├── echarts.ts # ECharts 图表库配置
│ │ └── index.ts # 插件入口文件
│ ├── router # Vue Router 路由相关代码
│ │ ├── core # 路由核心功能
│ │ │ ├── ComponentLoader.ts # 组件加载器
│ │ │ ├── IframeRouteManager.ts # Iframe 路由管理器
│ │ │ ├── MenuProcessor.ts # 菜单处理器
│ │ │ ├── RouteRegistry.ts # 路由注册器
│ │ │ ├── RouteTransformer.ts # 路由转换器
│ │ │ ├── RouteValidator.ts # 路由验证器
│ │ │ └── index.ts # 核心功能入口
│ │ ├── guards # 路由守卫
│ │ │ ├── afterEach.ts # 全局后置守卫
│ │ │ └── beforeEach.ts # 全局前置守卫
│ │ ├── modules # 路由模块定义
│ │ │ ├── article.ts # 文章模块路由
│ │ │ ├── dashboard.ts # 仪表盘路由
│ │ │ ├── examples.ts # 示例页面路由
│ │ │ ├── exception.ts # 异常页面路由
│ │ │ ├── help.ts # 帮助页面路由
│ │ │ ├── index.ts # 路由模块入口
│ │ │ ├── result.ts # 结果页面路由
│ │ │ ├── safeguard.ts # 安全防护路由
│ │ │ ├── system.ts # 系统管理路由
│ │ │ ├── template.ts # 模板页面路由
│ │ │ └── widgets.ts # 小组件路由
│ │ ├── routes # 路由配置
│ │ │ ├── asyncRoutes.ts # 异步路由(动态路由)
│ │ │ └── staticRoutes.ts # 静态路由(固定路由)
│ │ ├── index.ts # 路由主入口
│ │ └── routesAlias.ts # 路由别名定义
│ ├── store # Pinia 状态管理
│ │ ├── modules # 状态管理模块
│ │ │ ├── menu.ts # 菜单状态管理
│ │ │ ├── setting.ts # 设置状态管理
│ │ │ ├── table.ts # 表格状态管理
│ │ │ ├── user.ts # 用户状态管理
│ │ │ └── worktab.ts # 工作标签页状态管理
│ │ └── index.ts # Pinia 入口文件
│ ├── types # TypeScript 类型定义
│ │ ├── api # API 相关类型
│ │ │ └── api.d.ts # API 接口类型定义
│ │ ├── common # 通用类型定义
│ │ │ ├── index.ts # 通用类型入口
│ │ │ └── response.ts # 响应类型定义
│ │ ├── component # 组件相关类型
│ │ │ ├── chart.ts # 图表组件类型
│ │ │ └── index.ts # 组件类型入口
│ │ ├── config # 配置相关类型
│ │ │ └── index.ts # 配置类型定义
│ │ ├── import # 自动导入类型声明
│ │ │ ├── auto-imports.d.ts # 自动导入的函数类型
│ │ │ └── components.d.ts # 自动导入的组件类型
│ │ ├── router # 路由相关类型
│ │ │ └── index.ts # 路由类型定义
│ │ ├── store # 状态管理相关类型
│ │ │ └── index.ts # Store 类型定义
│ │ └── index.ts # 类型定义总入口
│ ├── utils # 工具函数目录
│ │ ├── constants # 常量定义
│ │ │ ├── index.ts # 常量入口
│ │ │ └── links.ts # 链接常量
│ │ ├── form # 表单相关工具
│ │ │ ├── index.ts # 表单工具入口
│ │ │ ├── responsive.ts # 响应式表单工具
│ │ │ └── validator.ts # 表单验证工具
│ │ ├── http # HTTP 请求工具
│ │ │ ├── error.ts # 错误处理
│ │ │ ├── index.ts # HTTP 工具入口
│ │ │ └── status.ts # 状态码处理
│ │ ├── navigation # 导航相关工具
│ │ │ ├── index.ts # 导航工具入口
│ │ │ ├── jump.ts # 页面跳转工具
│ │ │ ├── route.ts # 路由工具
│ │ │ └── worktab.ts # 工作标签页工具
│ │ ├── storage # 存储相关工具
│ │ │ ├── index.ts # 存储工具入口
│ │ │ ├── storage-config.ts # 存储配置
│ │ │ ├── storage-key-manager.ts # 存储键管理
│ │ │ └── storage.ts # 存储工具实现
│ │ ├── sys # 系统相关工具
│ │ │ ├── console.ts # 控制台工具
│ │ │ ├── error-handle.ts # 错误处理
│ │ │ ├── index.ts # 系统工具入口
│ │ │ ├── mittBus.ts # 事件总线
│ │ │ └── upgrade.ts # 升级相关工具
│ │ ├── table # 表格相关工具
│ │ │ ├── tableCache.ts # 表格缓存
│ │ │ ├── tableConfig.ts # 表格配置
│ │ │ └── tableUtils.ts # 表格工具函数
│ │ ├── ui # UI 相关工具
│ │ │ ├── animation.ts # 动画工具
│ │ │ ├── colors.ts # 颜色工具
│ │ │ ├── emojo.ts # 表情工具
│ │ │ ├── index.ts # UI 工具入口
│ │ │ ├── loading.ts # 加载动画工具
│ │ │ └── tabs.ts # 标签页工具
│ │ ├── index.ts # 工具函数总入口
│ │ └── router.ts # 路由工具函数
│ └── views # 页面组件目录(含 views/dev/sql 开发 SQL 控制台,仅开发环境使用)
├── tsconfig.json # TypeScript 配置文件
├── .env # 环境变量配置文件
├── .env.development # 开发环境变量配置文件
├── .env.production # 生产环境变量配置文件
├── .gitattributes # Git 属性文件
├── .gitignore # Git 忽略文件
├── .prettierignore # Prettier 忽略文件
├── .prettierrc # Prettier 配置文件
├── .stylelintignore # Stylelint 忽略文件
├── .stylelintrc # Stylelint 配置文件
├── commitlint.config.cjs # Commitlint 配置文件
├── eslint.config.mjs # ESLint 配置文件
├── index.html # HTML 入口文件
├── package.json # 项目依赖配置文件
├── pnpm-lock.yaml # pnpm 锁定文件
└── vite.config.ts # Vite 配置文件
```
## 后端结构
后端采用 `Elysia.js` 框架构建,遵循分层架构设计。
```
├── database # 数据库定义
│ ├── schema # 业务表 Schema
│ ├── sql # 初始化 SQL、模块 handoff SQL
│ └── base-schema.ts # 公共字段定义
├── script # 构建 / 注册表 / 种子脚本
├── test # 单元测试
│ ├── core
│ ├── shared
│ └── types
├── public # 静态资源
├── src
│ ├── config # 环境配置
│ ├── constants # 枚举与字典常量
│ ├── core # 核心基础设施
│ │ └── database # PG / Redis / Repository / 事务
│ ├── infrastructure # 外部集成
│ │ ├── clients # 第三方客户端
│ │ │ └── payment # 支付客户端
│ │ │ └── providers # alipay / paypal / wechat / crypto
│ │ ├── cron # 进程内定时任务
│ │ ├── queue # BullMQ 队列
│ │ │ ├── config
│ │ │ ├── core
│ │ │ ├── runtime
│ │ │ └── queues # flow-buffer / system-cron / trade-order
│ │ └── storage # 对象存储
│ │ └── providers # cos / kodo / oss / rustfs
│ ├── middleware # 中间件
│ │ └── guards # 认证 / 权限 / IP 黑名单 / 限流
│ ├── modules # 业务模块
│ │ ├── auth # 认证
│ │ ├── business-merchant # 商户管理
│ │ ├── business-orders # 订单管理
│ │ ├── business-payments # 支付管理
│ │ ├── business-refund # 退款管理
│ │ ├── crypto # 加解密公钥(请求/响应加密开关)
│ │ ├── dev-sql # 开发 SQL 控制台(仅 development,生产不注册)
│ │ ├── monitor-cache # 缓存监控
│ │ ├── monitor-job # 定时任务监控
│ │ ├── monitor-online # 在线用户监控
│ │ ├── system-api # API 接口管理
│ │ ├── system-dept # 部门管理
│ │ ├── system-deploy # 站点部署(Nginx 配置与证书)
│ │ ├── system-dict # 字典管理
│ │ ├── system-ip-black # IP 黑名单
│ │ ├── system-login-log # 登录日志
│ │ ├── system-menu # 菜单管理
│ │ ├── system-notice # 通知公告
│ │ ├── system-oper-log # 操作日志
│ │ ├── system-role # 角色管理
│ │ ├── system-storage # 存储配置
│ │ └── system-user # 用户管理
│ ├── worker-sandbox # 队列沙箱任务注册
│ ├── shared # 共享工具函数
│ ├── types # 类型定义
│ ├── app.ts # 应用入口
│ └── index.ts # 总入口
├── Dockerfile
├── drizzle.config.ts
├── package.json
├── bun.lock
├── tsconfig.json
└── README.md
```
---
URL: "https://elysia-admin.top/ecosystem/business-packages.html"
LLMS_URL: "https://elysia-admin.top/\\ecosystem\\business-packages.md"
title: "业务包 - Elysia Admin"
description: "社区贡献的可复用业务模块集合,快速查找并复用常见业务实现,减少重复造轮子。"
layout: false
sidebar: false
navbar: false
---
---
title: 业务包 - Elysia Admin
description: 社区贡献的可复用业务模块集合,快速查找并复用常见业务实现,减少重复造轮子。
layout: false
sidebar: false
navbar: false
---
---
URL: "https://elysia-admin.top/ecosystem/related-links.html"
LLMS_URL: "https://elysia-admin.top/\\ecosystem\\related-links.md"
title: "相关链接 - Elysia Admin"
description: "Elysia Admin 开发与维护涉及的核心技术栈参考链接,包括 ElysiaJS、Bun、DrizzleORM、BullMQ、PostgreSQL、Redis、Docker 等官方文档。"
---
---
title: 相关链接 - Elysia Admin
description: Elysia Admin 开发与维护涉及的核心技术栈参考链接,包括 ElysiaJS、Bun、DrizzleORM、BullMQ、PostgreSQL、Redis、Docker 等官方文档。
---
# 相关链接
本章为 `Elysia Admin` 开发与维护过程中涉及的核心技术栈、工具及规范提供权威参考。
## 官方文档
权威的框架与运行时手册,涵盖核心 API 与最佳实践。
1. **Art Design Pro Documentation**
- URL: [https://www.artd.pro/docs/zh/](https://www.artd.pro/docs/zh/)
- 摘要: 基于 Ant Design Vue 的中后台前端解决方案,提供丰富的组件与页面模板。
- 访问日期: 2026-04-09
2. **Bun Official Documentation**
- URL: [https://bun.sh/docs](https://bun.sh/docs)
- 摘要: Bun 运行时官方文档,包含高性能 HTTP 服务、包管理及测试运行器的详细说明。
- 访问日期: 2026-04-09
3. **ElysiaJS Official Documentation**
- URL: [https://elysiajs.com/](https://elysiajs.com/)
- 摘要: ElysiaJS 框架文档,专注于 TypeScript 深度优化与极高性能的 Web 路由实现。
- 访问日期: 2026-04-09
4. **BullMQ Documentation**
- URL: [https://docs.bullmq.io/](https://docs.bullmq.io/)
- 摘要: BullMQ 是一个基于 Redis 构建,专为 Node.js 和 Python 设计的高性能、分布式消息队列与任务批处理库。
- 访问日期: 2026-04-15
## 数据库与缓存
数据持久化与性能优化的技术支柱。
1. **Drizzle ORM Documentation**
- URL: [https://orm.drizzle.team/docs/overview](https://orm.drizzle.team/docs/overview)
- 摘要: 轻量级、类型安全的 TypeScript ORM,支持 PostgreSQL 与 Drizzle Kit 迁移工具。
- 访问日期: 2026-04-09
2. **PostgreSQL Official Manual**
- URL: [https://www.postgresql.org/docs/current/index.html](https://www.postgresql.org/docs/current/index.html)
- 摘要: 全球领先的开源关系型数据库官方手册,提供详尽的 SQL 参考与配置指南。
- 访问日期: 2026-04-09
3. **Redis Documentation**
- URL: [https://redis.io/docs/latest/](https://redis.io/docs/latest/)
- 摘要: 高性能内存数据库,本项目用于缓存加速、分布式锁及限流机制。
- 访问日期: 2026-04-09
## 存储与工具
提升开发效率与系统可用性的辅助设施。
1. **Docker Documentation**
- URL: [https://docs.docker.com/](https://docs.docker.com/)
- 摘要: 容器化部署官方文档,涵盖镜像构建、多容器编排及生产环境配置。
- 访问日期: 2026-04-09
2. **RustFS Repository**
- URL: [https://github.com/Maurice-Yu/RustFS](https://github.com/Maurice-Yu/RustFS)
- 摘要: 兼容 S3 协议的高性能开源对象存储,项目默认推荐的私有部署存储方案。
- 访问日期: 2026-04-09
## 开发规范与社区
保持代码质量与获取最新动态的渠道。
1. **Bun Discord Community**
- URL: [https://bun.sh/discord](https://bun.sh/discord)
- 摘要: Bun 官方活跃社区,实时获取运行时更新及技术解答。
- 访问日期: 2026-04-09
2. **ElysiaJS GitHub Discussions**
- URL: [https://github.com/elysiajs/elysia/discussions](https://github.com/elysiajs/elysia/discussions)
- 摘要: Elysia 官方论坛,涵盖功能提案、疑难排查及生态插件讨论。
- 访问日期: 2026-04-09
3. **TypeScript Handbook**
- URL: [https://www.typescriptlang.org/docs/handbook/intro.html](https://www.typescriptlang.org/docs/handbook/intro.html)
- 摘要: 现代 TypeScript 语法与类型系统全手册,项目类型安全的基础参考。
- 访问日期: 2026-04-09
## 扩展阅读
深入底层原理与行业最佳实践。
1. **RESTful API Design Best Practices**
- URL: [https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design](https://learn.microsoft.com/en-us/azure/architecture/best-practices/api-design)
- 摘要: 微软 Azure 团队总结的 API 设计最佳实践,确保接口简洁且易于维护。
- 访问日期: 2026-04-09
2. **S3-compatible Storage Compatibility List**
- URL: [https://github.com/gaul/s3proxy/wiki/S3-Compatibility-List](https://github.com/gaul/s3proxy/wiki/S3-Compatibility-List)
- 摘要: 各类 S3 兼容存储方案的深度对比,辅助进行存储服务选型。
- 访问日期: 2026-04-09
---
URL: "https://elysia-admin.top/guide/ai-guide.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\ai-guide.md"
title: "AI 开发指南"
description: "介绍 Cursor、Qoder、Trae、Kiro、Codex、Claude Code 的项目规则与 `.ai/` 规范文档,配合提示词让 AI 按 Elysia Admin 风格生成前后端模块代码。"
---
---
title: AI 开发指南
description: 介绍 Cursor、Qoder、Trae、Kiro、Codex、Claude Code 的项目规则与 `.ai/` 规范文档,配合提示词让 AI 按 Elysia Admin 风格生成前后端模块代码。
---
# AI 开发指南
在本仓库里用 AI 做业务模块,你不需要每次粘贴长篇模板。打开项目后,IDE 会自动加载规则;你只需发一条合适的提示词,AI 就会按 Elysia Admin 的目录结构和代码风格来生成。
## 工作原理
项目根目录下有多套 Agent 规则(Cursor、Qoder、Trae、Kiro、Codex、Claude Code),语义一致。AI 对话时会读取这些规则,以及 `.ai/` 里的工作流与代码模板。
```mermaid
flowchart LR
A[你发提示词] --> B[IDE 加载规则]
B --> C[AI 读 .ai/ 规范]
C --> D[生成代码 + SQL 文件]
D --> E[你手动执行 handoff SQL / 可选 db:push]
```
提示词里带上触发语,更容易匹配项目 Skill:
- `按 module dev workflow` 或 `走完整 SOP`
- 脚手架相关:`先用脚手架` / `脚手架已生成`
- 需要菜单时:`含菜单权限和 handoff SQL`
- 多模块 / 完整项目:`拆分任务` / `分阶段执行` / `大任务拆解` / `完整项目规划`
## 我该从哪开始
| 场景 | 做法 |
|------|------|
| Schema 已建好,要标准 CRUD | 先跑脚手架,再用 AI 做增量(见下节) |
| 从零生成整个模块 | 用「一句话」提示词,AI 会建议是否先跑脚手架 |
| 字段多、只改前端或后端 | 用「结构化」提示词,分块写清需求 |
| 多个业务模块 / 子系统 | 先出 `feature-tasks` 任务包,你选阶段后再写代码(见「大任务分阶段」) |
| 用 VS Code 本地开发 | 打开 `elysia-admin.code-workspace`,任务 `Dev: Full Stack` 启前后端 |
| 用 ChatGPT / Claude 网页版 | 见文末「网页版 AI」 |
| 查规范文件 | 见「规范文档」 |
## 大任务分阶段
当需求一次点名**多个业务模块**或完整子系统时,不要直接开写代码。先让 AI 按 `.ai/AI_PHASED_TASKS.md` 拆成可交接的任务包,你选定某一阶段(或说「继续 / 下一个」)后再实现。
**何时用:** 多模块 / 子系统 / 你说了 `拆分任务`、`分阶段执行`、`大任务拆解`、`完整项目规划` 等。
**何时不用:** 单个业务模块、单点修 bug、局部增强 —— 仍走 Skill / 脚手架 / `AI_MODULE_WORKFLOW`。
任务包写在**当前 IDE** 的配置目录下(不要落到仓库根):
| IDE | 目录 |
|-----|------|
| Cursor | `.cursor/feature-tasks/{slug}/` |
| Claude Code | `.claude/feature-tasks/{slug}/` |
| Codex | `.codex/feature-tasks/{slug}/` |
| Qoder | `.qoder/feature-tasks/{slug}/` |
| Trae | `.trae/feature-tasks/{slug}/` |
| Kiro | `.kiro/feature-tasks/{slug}/` |
每个特性一个 `kebab-case` slug,目录内含 `CHECKLIST.md`(总清单)与 `01-*.md`、`02-*.md`…(分阶段详情)。同一功能只写在一个 IDE 目录,不要跨工具双写。
## 脚手架 + AI
单表标准 CRUD、且 Drizzle Schema 已存在时,推荐先跑脚手架,再让 AI 补业务逻辑和排版。Agent 细则见 `.ai/AI_MODULE_SCAFFOLD.md`。
在 `server/` 目录执行:
```bash
cd server
bun run create:module business-goods --tag 商品管理
bun run create:page business goods --tag 商品管理
```
可选参数:`--schema business_goods` 指定表名,`--dry-run` 仅预览不写文件。完整说明见 [内置命令 - 模块脚手架](/architecture/commands#模块脚手架命令)。
脚手架会生成后端 `route.ts` / `dto.ts` / `handle.ts` 骨架,以及前端 types、api、views。接下来把下面「脚手架之后」的提示词发给 AI,让它补校验、表格排版、字典和 handoff SQL。
最后由你在本机**手动执行** `server/database/sql/business-goods-init.sql`,并在后台把菜单 component 配成 `/business/goods/index`。
若 AI 修改了 Drizzle Schema,它可能会询问是否代跑 `bun run db:push`(在 `server/` 目录)。同意后偏好会写入 `.ai/dev-preferences.local.md`(不提交 Git),后续默认可代跑。

## 提示词
示例模块均为 `business-goods`。复制后把模块名、字段、父菜单换成你的业务。
### 脚手架之后
适合 Schema 已有、标准 CRUD。先跑脚手架,AI 只补增量,省 token。
```
business-goods 已执行 create:module 和 create:page,请按 AI_MODULE_SCAFFOLD 增量规范:
1. handle 补价格/库存等业务校验;
2. 表格和弹窗按 AI_PAGE_QUALITY 排版,status 接字典;
3. 输出 server/database/sql/business-goods-init.sql(菜单+按钮权限)。
不要重写 route/dto/api 骨架。
```
### 一句话
日常最省事。AI 会在计划里补默认值;无表时会先问你,不会擅自改 drizzle。
```
按 module dev workflow 做商品管理(business-goods):名称、价格、库存、备注;全栈 CRUD,要菜单权限、字典和 server/database/sql/business-goods-init.sql。
若 Schema 已有,请先建议运行 create:module + create:page。
```
### 两句补充
需要指定父菜单或字段类型时用。减少 AI 猜测。
```
business-goods 全栈模块,字段 name/price/stock/remark,status 用 boolean。
表若没有先给 schema 方案;菜单挂「业务管理」(/business)下;输出 handoff SQL。
```
### 结构化
字段很多、或只要前端 / 只要后端时用。
**仅后端**
```
按 module dev workflow,仅后端:商品管理模块 business-goods
- 字段:name、price、stock、remark、status(boolean)
- 标准 CRUD(创建、列表、详情、更新、软删除)
- 登录认证 + 操作日志;无需定时任务
- 暂不需要前端和 handoff SQL(或:需要 SQL 补菜单权限)
```
**仅前端**
```
按 module dev workflow,仅前端:business/goods 商品管理页
- types/api/business-goods.d.ts、api/business/goods.ts
- index.vue + goods-search.vue + goods-dialog.vue
- 遵循 AI_PAGE_QUALITY:art-full-height、useDictStore,勿硬编码业务枚举
- 权限与后端 business:goods:* 一致
```
**全栈(字段写全)**
```
按 module dev workflow,全栈商品管理 business-goods:
【后端】server/src/modules/business-goods/
- 表:goodsId、name、price、stock、status(boolean)、remark;主表 + BaseSchema
- 标准 CRUD;meta.permission:business:goods:create|query|update|delete
【前端】admin/src/
- types/api/business-goods.d.ts、api/business/goods.ts
- views/business/goods/(index + goods-search + goods-dialog)
【运维】
- 父菜单:/business(业务管理)
- 字典:若无 `goods_status` 则在 SQL 中补(`dict_type` 短且语义化,优先 2–3 段;status 用 boolean 可省略字典)
- 生成 server/database/sql/business-goods-init.sql(菜单/按钮/角色,MCP 查库优先)
```
### 常见变体
| 需求 | 在提示词里加上 |
|------|----------------|
| 只要代码,不要 SQL | `暂不需要菜单和 handoff SQL` |
| 只补菜单权限 | `补 business-goods 菜单权限 SQL,查库后写入 handoff 文件` |
| 表已存在 | `schema 已有 business_goods,不要改 drizzle` |
| 允许 AI 代跑 db:push | `允许 AI 在 server/ 执行 db:push`(或同意后会写入本地偏好文件) |
| 要定时任务 | `需要 task.ts`(见下节说明) |
定时 / 队列任务在 `task.ts` 定义函数,于 `server/src/worker-sandbox/` 注册。详见 [定时任务](./cron)、[队列](./queue)。
## AI 会交付什么
发完提示词,AI 通常按下面顺序产出。无表时会先提案 Schema,**等你同意**再改 drizzle。
| 产出 | 说明 |
|------|------|
| 计划 | 5 行内说明 + 待改文件清单 |
| Schema | 查 `server/database/schema/`;无表则提案,**须你同意**后再改 drizzle |
| `db:push` | Schema 变更后,AI 可能询问是否代跑(见 `.ai/dev-preferences.local.example.md`) |
| 代码 | 后端四文件;全栈含前端 types / api / views |
| 字典 | 对齐 `system_dict_*` 或写进 handoff SQL |
| SQL 文件 | `server/database/sql/{模块}-init.sql`(**仅生成文件,须你手动执行**) |
| 前端 | `art-full-height`、`useDictStore`、弹窗排版 |
| 说明 | MCP 查库摘要,或「未连 Postgres MCP」 |
AI **不会**擅自 `git add` / `commit`,也**不会**用脚本、psql、MCP 写入等方式代跑 handoff SQL。提交与 SQL 执行均由你自己完成。
配置 Postgres MCP 后,菜单和字典 SQL 更准确。查运行时数据(dict、menu、权限)**优先 MCP 只读**;**不要**让 AI 读 `server/database/sql/pg.sql`(全库备份,可能与当前库不一致)。配置与纪律见上方 **MCP 入口** 或 [MCP 服务](./mcp),Agent 细则见 `.ai/AI_MCP_SETUP.md`。
## 规范文档
`.ai/` 目录存放 AI 工作流与代码模板。工作流文档为英文(便于 Agent 理解);本页与提示词为中文,给人阅读。
| 文件 | 用途 |
|------|------|
| `AI_MODULE_WORKFLOW.md` | 主流程:Schema → 脚手架 → 代码 → SQL |
| `AI_MODULE_SCAFFOLD.md` | `create:module` + `create:page` 细则 |
| `AI_PHASED_TASKS.md` | 多模块 / 完整项目分阶段任务包 |
| `AI_CODE_EXAMPLES.md` | 代码模板**索引**(按任务再打开对应详版) |
| `AI_CODE_EXAMPLES_BACKEND.md` | 后端 `dto` / `handle` / `route` 模板 |
| `AI_CODE_EXAMPLES_FRONTEND.md` | 前端 types / api / views 模板 |
| `AI_PAGE_QUALITY.md` | 列表、搜索、字典、排版 |
| `AI_UI_LAYOUT.md` | 弹窗、抽屉表单布局 |
| `AI_HANDOFF_SQL.md` | 合并 SQL、菜单查库 |
| `AI_SCHEMA_GUIDE.md` | 建表规范、`db:push` 流程 |
| `AI_MCP_SETUP.md` | Postgres、Chrome DevTools MCP |
| `AI_CONTEXT_CAPSULE.md` | 一页速查 |
| `dev-preferences.local.example.md` | 本地偏好示例(如允许 AI 代跑 `db:push`) |
Handoff SQL 输出路径:`server/database/sql/{模块名}-init.sql`(**开发者手动执行**)
Cursor Skill:`.cursor/skills/elysia-module-dev/SKILL.md`
Qoder Skill:`.qoder/skills/elysia-module-dev/SKILL.md`
## IDE 规则
多处 Agent 规则与 `.cursor/rules/` 语义一致。改架构或读文件纪律时,请同步 Cursor、Qoder、Trae、Kiro、Codex、Claude Code(各目录 README 有说明)。
### Cursor
打开仓库即可,无需额外配置。
| 文件 | 何时生效 | 作用 |
|------|----------|------|
| `general.mdc` | 始终 | 架构、依赖方向、读文件纪律 |
| `backend.mdc` | 编辑 `server/src/**` | 后端模块、repository、`db:push` |
| `frontend.mdc` | 编辑 `admin/src/**` | 前端页面、useTable |
Skill:`.cursor/skills/elysia-module-dev/SKILL.md`
### Qoder
与 Cursor 语义对齐,规则在 `.qoder/` 目录。
| 文件 | 对应 Cursor | 作用 |
|------|-------------|------|
| `.qoder/rules/general.md` | `general.mdc` | 始终生效 |
| `.qoder/rules/backend.md` | `backend.mdc` | 编辑 `server/src/**` |
| `.qoder/rules/frontend.md` | `frontend.mdc` | 编辑 `admin/src/**` |
| `.qoder/skills/elysia-module-dev/SKILL.md` | Cursor Skill | 全栈模块开发 Skill |
说明见 `.qoder/README.md`。根目录 `AGENTS.md` 也会被 Qoder 识别,但 `.qoder/rules/` 优先级更高。
### Trae
| 文件 | 对应 Cursor |
|------|-------------|
| `.trae/rules/general.md` | `general.mdc` |
| `.trae/rules/backend.md` | `backend.mdc` |
| `.trae/rules/frontend.md` | `frontend.mdc` |
### Kiro
| 文件 | 作用 |
|------|------|
| `product.md` / `structure.md` / `tech.md` | 始终:产品、目录、技术栈 |
| `frontend-vue.md` / `backend-elysia.md` | 编辑前后端时加载 |
| `ai-ops-supplement.md` | 工作流、MCP、SQL、排版 |
通过 `#[[file:.ai/...]]` 引用大文档,避免 steering 里重复贴模板。
### Codex
| 文件 | 对应 Cursor | 作用 |
|------|-------------|------|
| `AGENTS.md`(仓库根) | `general.mdc` | 全局架构、模块触发语 |
| `server/AGENTS.md` | `backend.mdc` | 在 `server/` 下工作时叠加 |
| `admin/AGENTS.md` | `frontend.mdc` | 在 `admin/` 下工作时叠加 |
Codex 从 Git 根向当前目录逐级合并 `AGENTS.md`。验证:`codex --print-instructions`。说明见 `.codex/README.md`。
### Claude Code
| 文件 | 对应 Cursor | 作用 |
|------|-------------|------|
| `.claude/CLAUDE.md` | — | 项目入口、命令、`.ai/` 索引 |
| `.claude/rules/general.md` | `general.mdc` | 始终生效 |
| `.claude/rules/backend.md` | `backend.mdc` | 编辑 `server/src/**` 时懒加载 |
| `.claude/rules/frontend.md` | `frontend.mdc` | 编辑 `admin/src/**` 时懒加载 |
说明见 `.claude/README.md`。个人偏好可写 `CLAUDE.local.md`(不提交)。
### VS Code 编辑器
| 文件 | 作用 |
|------|------|
| `.vscode/settings.json` | 格式化、ESLint、Prettier |
| `.vscode/extensions.json` | 推荐 Volar、ESLint、Prettier、Bun |
| `.vscode/tasks.json` | `Server: dev`、`Admin: dev`、`Dev: Full Stack` |
| `.vscode/launch.json` | Bun 调试 `server/src/index.ts` |
| `elysia-admin.code-workspace` | 多根工作区(admin / server / docs) |
## 网页版 AI
未在本仓库 IDE 中打开项目、无法自动加载规则时,适用 ChatGPT、Claude 网页版等。
先把 `.ai/README.md` 贴进对话,再按需贴 `AI_MODULE_WORKFLOW.md`、`AI_CODE_EXAMPLES.md`。然后贴下面短前缀和你的需求,**不要**再贴 100+ 行内联代码。
```
# elysia-admin 约束摘要
- 前后端分离:Vue3 + Elysia/Bun;模块 server/src/modules/{group}-{name}/(dto/handle/route/task)
- 代码模板以 AI_CODE_EXAMPLES.md 为准;handle 不要包 try/catch,业务错误用 BaseResultData.fail(4xx, msg)
- 前端:useTable + ArtSearchBar + ArtForm;art-full-height;业务枚举用 useDictStore
- 全栈:查 schema → 代码 → server/database/sql/{module}-init.sql;改表须先征得同意
- handoff SQL 仅生成文件,开发者手动执行;禁止 AI 用脚本/psql/MCP 代跑
- 查 dict/menu 优先 Postgres MCP;禁止读 server/database/sql/pg.sql(备份,可能过期)
- Schema 变更后 AI 可询问是否代跑 db:push;同意写入 .ai/dev-preferences.local.md
- 按 AI_MODULE_WORKFLOW 的交付格式输出
```
示例需求:
```
按 module dev workflow,创建 business-goods 全栈:goodsId、name、price、stock、status、remark;
含菜单权限与 server/database/sql/business-goods-init.sql。
```
无法读仓库时,至少粘贴 **`AI_CODE_EXAMPLES.md` 全文** 后再发需求。
## 你需要手动做的
- 在本机**手动执行** **`server/database/sql/{模块}-init.sql`**(AI 只生成文件,不会代跑;Postgres MCP 对 AI 一般只读)
- Schema 变更后,若未授权 AI 代跑,请在 `server/` 自行执行 **`bun run db:push`**
- 确认 AI 计划里的默认值是否符合业务(尤其「一句话」提示词)
- Git 提交由**你自己**完成
### 数据库相关纪律
| 事项 | 说明 |
|------|------|
| Handoff SQL | 只能你手动在本地/测试库执行;AI 禁止写临时代码、psql、MCP 写入来代跑 |
| 查 dict / menu | 优先 Postgres MCP 只读;**勿**用 `server/database/sql/pg.sql`(备份快照,可能与现库不一致) |
| `db:push` 偏好 | 首次 AI 会询问;同意后写入 `.ai/dev-preferences.local.md`(gitignore),后续可默认代跑 |
AI 读文件有纪律:新建模块只读一个参考(后端 `system-api/`、前端 `system/user/`);改现有文件只读该文件;不读 `node_modules/`、`dist/`、`server/database/sql/pg.sql`。跑过脚手架后,只读生成文件和当前 Schema 即可。
列表导出优先用 `ArtExcelExport`,不必单独为导出造后端(除非业务需要)。
---
URL: "https://elysia-admin.top/guide/cache.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\cache.md"
title: "缓存操作 - Elysia Admin 指南"
description: "介绍 Elysia Admin 中封装的 Redis 缓存操作方法,包括基础的 Set/Get/Del,以及防缓存击穿的 WithCache、CacheInsert、CacheUpdate 等组合操作。"
---
---
title: 缓存操作 - Elysia Admin 指南
description: 介绍 Elysia Admin 中封装的 Redis 缓存操作方法,包括基础的 Set/Get/Del,以及防缓存击穿的 WithCache、CacheInsert、CacheUpdate 等组合操作。
---
# 缓存操作
项目通过 Redis 做数据缓存,减少重复查库。基础读写走 `@/core/database/redis`,带防击穿和列表增量维护的组合操作走 `@/core/cache`。
缓存键建议用 `CacheEnum`(`@/constants/enum`)统一管理,避免硬编码字符串散落各处。字典、部门树等模块已按此方式使用。
```mermaid
flowchart LR
H[handle.ts] --> R[redis Set/Get/Del]
H --> C[WithCache 等组合函数]
C --> R
R --> Redis[(Redis)]
```
## 基础 API
从 `@/core/database/redis` 导入。值会自动 JSON 序列化 / 反序列化。
| 函数 | 用途 |
|------|------|
| `Set(key, value, expire?)` | 写入缓存,`expire` 为秒;不传则保留已有 TTL |
| `SetMulti(items)` | 批量写入,`items` 为 `{ key, value, expire? }[]` |
| `Get(key)` | 读取,不存在返回 `null` |
| `Del(key \| keys)` | 删除单个或批量 |
| `Keys(pattern)` | 按前缀或通配符查找键 |
```ts
import { Set, Get, Del } from '@/core/database/redis';
await Set('user:1', { name: 'John', age: 30 }, 60);
const user = await Get('user:1');
await Del(['user:1', 'user:2']);
```
```ts
import { SetMulti } from '@/core/database/redis';
await SetMulti([
{ key: 'user:1', value: { name: 'John' }, expire: 60 },
{ key: 'user:2', value: { name: 'Jane' }, expire: 60 },
]);
```
业务数据变更后记得主动 `Del` 相关键。例如字典模块在增删改后会清除 `CacheEnum.DICT_TYPE` 和对应的 `DICT_DATA` 键,避免读到过期数据。
## WithCache
`WithCache` 封装「先读缓存,未命中再查库并回写」的逻辑,并用分布式锁防止热点键过期时大量请求同时穿透到数据库。
```mermaid
flowchart TD
A[读 Redis] -->|命中| Z[返回缓存]
A -->|未命中| B[尝试获取锁]
B -->|成功| C[双重检查缓存]
C --> D[执行 dbQueryFn]
D --> E[写入 Redis]
E --> Z
B -->|失败| F[短暂等待重试]
F -->|读到缓存| Z
F -->|仍无缓存| G[直接查库]
G --> Z
```
```ts [handle.ts]
import { WithCache } from '@/core/cache';
import { CacheEnum } from '@/constants/enum';
import { CreateQueryBuilder, FindAll } from '@/core/database/repository';
import { systemDictTypeSchema } from '@database/schema/system_dict';
const data = await WithCache(
CacheEnum.DICT_TYPE,
async () => {
const where = CreateQueryBuilder(systemDictTypeSchema).eq('delFlag', false).build();
return await FindAll(systemDictTypeSchema, where);
},
300, // 过期秒数,默认取 config.app.baseCacheTime
);
```
| 参数 | 说明 | 默认 |
|------|------|------|
| `cacheKey` | Redis 键 | — |
| `dbQueryFn` | 缓存未命中时执行的查库函数 | — |
| `expire` | 过期时间(秒) | `config.app.baseCacheTime` |
| `lockTtl` | 分布式锁 TTL(秒) | 10 |
| `retryTimes` | 抢锁失败重试次数 | 3 |
| `retryDelay` | 重试间隔(毫秒) | 100 |
获锁方回写缓存后,其他等待的请求会通过双重检查或重试读到新值;重试超时则直接查库兜底。
## 列表缓存维护
`CacheInsert`、`CacheUpdate`、`CacheDelete` 用于在**不整键失效**的前提下,增量维护已缓存的**数组**数据。适合列表类缓存;结构复杂或一致性要求高时,直接 `Del` 后让 `WithCache` 重建更稳妥。
从 `@/core/cache` 导入:
| 函数 | 用途 |
|------|------|
| `CacheInsert(cacheKey, data)` | 向数组末尾追加一项;缓存不存在则新建 `[data]` |
| `CacheUpdate(cacheKey, key, data)` | 按 `key` 字段匹配并合并更新;无匹配项则追加 |
| `CacheDelete(cacheKey, key, values)` | 按 `key` 字段值数组过滤删除;删空则移除整个键 |
```ts
import { CacheInsert, CacheUpdate, CacheDelete } from '@/core/cache';
// 缓存结构: [{ id: 1, name: 'A' }, { id: 2, name: 'B' }]
await CacheInsert('users:list', { id: 3, name: 'New User' });
await CacheUpdate('users:list', 'id', { id: 1, name: 'Updated', age: 25 });
await CacheDelete('users:list', 'id', [1, 2]);
```
三个函数均返回 `boolean` 表示是否操作成功,内部异常会记日志并返回 `false`。
## 使用建议
- 键名走 `CacheEnum`,新增业务缓存时在 `constants/enum` 里补常量
- 写操作后主动失效相关键,不要依赖 TTL 兜底业务一致性
- 高频读、低频写的数据适合 `WithCache`;写多读少的场景慎用列表增量维护
- 后台「系统监控 → 缓存管理」可查看 `CacheEnum` 中注册的缓存键(见 `monitor-cache` 模块)
---
URL: "https://elysia-admin.top/guide/cron.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\cron.md"
title: "定时任务 - Elysia Admin 指南"
description: "详细介绍 Elysia Admin 中基于 BullMQ Sandboxed Processors 的定时任务创建、注册、构建及后台管理配置流程,支持多实例高可用部署。"
---
---
title: 定时任务 - Elysia Admin 指南
description: 详细介绍 Elysia Admin 中基于 BullMQ Sandboxed Processors 的定时任务创建、注册、构建及后台管理配置流程,支持多实例高可用部署。
---
# 定时任务
定时任务用于按 Cron 表达式周期执行业务逻辑,例如清理过期数据、同步外部状态。系统基于 **BullMQ Sandboxed Processors** 实现:每个任务在独立子进程中运行,不会阻塞主进程的 HTTP 事件循环。
整体链路如下。后台配置的任务名、Cron 和参数会写入队列调度;Worker 消费后,经 `worker-sandbox` 注册表找到 `task.ts` 里的函数并执行。
```mermaid
flowchart TD
subgraph 代码侧
T[task.ts 定义函数] --> SB[worker-sandbox 注册]
SB --> P[processor 子进程]
end
subgraph 运行侧
UI[后台定时任务] --> Q[BullMQ 队列]
Q --> W[Worker 消费]
W --> P
P --> Fn[执行业务函数]
end
```
与 [队列](./queue) 里的异步任务共用同一套 Redis 与 Worker 模型,区别只是触发来源:定时任务由 Cron 调度,异步任务由业务代码 `addJob` 投递。
## 任务函数
在业务模块的 `task.ts` 中定义并导出任务函数。参数顺序和类型需与后台配置的「任务参数」一致。
```ts [task.ts]
// server/src/modules/monitor-job/task.ts
import { logger } from '@/shared/logger';
export function jobDemo(name: string, age?: number, isActive?: boolean) {
logger.info(`jobDemo 执行 - 姓名: ${name}, 年龄: ${age ?? '未提供'}, 状态: ${isActive ? '激活' : '未激活'}`);
}
```
`task.ts` 里写纯函数即可,复杂业务可调用同模块的 `handle.ts`,避免在 processor 里重复逻辑。
## 沙箱注册
任务函数写好后,必须在 `worker-sandbox` 里注册,Worker 子进程才能按名称找到它。
**不要**在 `processor.ts` 里直接 `import '@/modules/...'`,以保持 infrastructure 与 modules 的依赖边界。
```ts [system-cron-tasks.ts]
// server/src/worker-sandbox/system-cron-tasks.ts
import { jobDemo } from '@/modules/monitor-job/task';
import type { TaskFn } from '@/infrastructure/queue/core/processor-utils';
export function registerSystemCronSandboxTasks(
register: (name: string, fn: TaskFn) => void,
): void {
// 第一个参数必须与后台「任务名称」完全一致
register('测试任务', jobDemo);
}
```
`processor.ts` 只负责调用注册函数,不关心具体业务:
```ts [processor.ts]
// server/src/infrastructure/queue/queues/system-cron/processor.ts
import type { SandboxedJob } from 'bullmq';
import { registerSystemCronSandboxTasks } from '@/worker-sandbox/system-cron-tasks';
import { createTaskRegistry, parseArgs } from '../../core/processor-utils';
const { register, get } = createTaskRegistry();
registerSystemCronSandboxTasks(register);
export default async function processor(job: SandboxedJob) {
const { taskName, jobArgs } = job.data;
const taskFn = get(taskName);
if (!taskFn) throw new Error(`[SystemCron] 未找到任务: ${taskName}`);
await taskFn(...parseArgs(jobArgs));
return { success: true, taskName };
}
```
Processor 运行在独立子进程中,可以正常使用数据库、Redis 等工具,但无法访问主进程的内存单例。子进程会自行初始化所需连接。
## 构建与启动
修改 `worker-sandbox/*.ts` 或 `processor.ts` 后,需要重新构建 processor 才能生效。
```bash
cd server
bun run build:processors
```
开发环境单独启动 Worker:
```bash
bun run dev:workers
```
前后端和 Worker 一起启动时,可用 `bun run dev:all`。
## 后台配置
代码注册完成后,在后台添加调度配置。路径:**系统监控 → 定时任务**。

| 字段 | 说明 |
|------|------|
| 任务名称 | 与 `register('测试任务', ...)` 的第一个参数**完全一致**,全局唯一 |
| Cron 表达式 | 执行频率,可用 [在线工具](https://tool.lu/crontab/) 校验 |
| 任务参数 | JSON 数组,如 `["张三", 25, true]`,顺序和类型对应函数参数 |
提交后把任务状态设为「开启」,系统会按 Cron 表达式自动触发。
## 部署注意
至少需要一个 Worker 进程在运行,否则任务只会积压在 Redis 中,不会被消费。生产环境通常用 PM2 单独管理 Worker,与 HTTP 服务分开部署。
多实例部署时,BullMQ 通过 Redis 保证同一触发时刻只有一个 Worker 消费任务,一般不需要额外的分布式锁。Worker 崩溃后由进程管理器重启;未完成的任务数据持久化在 Redis 中,重启后可恢复。
---
URL: "https://elysia-admin.top/guide/crypto.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\crypto.md"
title: "请求加解密 - Elysia Admin 指南"
description: "介绍 Elysia Admin 的请求与响应加解密:RSA-OAEP 包裹 AES-256-GCM、前后端开关、密钥生成,以及明文兼容与日志还原。"
---
---
title: 请求加解密 - Elysia Admin 指南
description: 介绍 Elysia Admin 的请求与响应加解密:RSA-OAEP 包裹 AES-256-GCM、前后端开关、密钥生成,以及明文兼容与日志还原。
---
# 请求加解密
接口可以在传输层之外再包一层应用层加密:每次请求用随机 AES-256-GCM 密钥加密 JSON,再用服务端 RSA 公钥把这把密钥交给后端。浏览器抓包时看到的是密文;没有加密头的客户端仍按明文访问。
实现位于 `server/src/middleware/crypto.ts`、`server/src/shared/crypto-envelope.ts` 与 `admin/src/utils/crypto.ts`。`crypto.enabled` 为 false 时中间件不注册。
```mermaid
flowchart LR
FE[前端生成 AES 密钥] --> RSA[RSA-OAEP 加密密钥]
RSA --> H[请求头 X-Encrypt-Key]
FE --> AES[AES-GCM 加密 JSON]
AES --> Body["请求体 data"]
H --> BE[后端私钥解出 AES 密钥]
Body --> BE
BE --> Handle[校验与 handle 看到明文]
Handle --> Out["响应 encrypted + data"]
```
## 开关
两边都打开才会加密。任一关闭时,管理端自动退回明文。
| 位置 | 配置 | 作用 |
|------|------|------|
| 后端 `development.yaml` / `production.yaml` | `crypto.enabled` | 为 `true` 才注册中间件,并允许下发公钥 |
| 后端同一段 | `crypto.privateKey` / `crypto.publicKey` | PKCS8 私钥与 SPKI 公钥(PEM) |
| 前端 `admin/.env*` | `VITE_API_ENCRYPT` | 为 `true` 才去拉公钥并加密 |
开发配置里 `crypto.enabled` 默认为 true,生产配置默认为 false。公钥接口是 `GET /api/crypto/public-key`(`ipRateLimit: 60:30`,无需登录),返回 `{ enabled, publicKey }`。前端只拉取一次;`enabled` 为 false、没有公钥或请求失败时,后续请求保持明文。
## 生成密钥
在 `server/` 下执行。脚本只替换 `crypto` 段的 `privateKey` / `publicKey`,不改当前的 `enabled`。
```bash
bun run gen:crypto-keys
bun run gen:crypto-keys production
bun run gen:crypto-keys production --force
```
已有密钥时不加 `--force` 会拒绝覆盖,避免旧密钥加密的报文无法再解。私钥只放在服务端 yaml 中,不要提交到仓库。PEM 也可以手工写成 YAML 块,或把换行写成 `\n`。
生产环境开启前:先生成密钥,再把 `crypto.enabled` 改为 `true`,并把前端 `VITE_API_ENCRYPT` 设为 `true` 后重新构建。配置结构见 [部署](/guide/deploy) 的部署前准备。
## 协议
算法为 RSA-OAEP(SHA-256)+ AES-256-GCM。IV 为 12 字节,和密文拼在一起再做 Base64。
1. 前端用 Web Crypto 生成本次 AES 密钥,公钥加密后放入请求头 `X-Encrypt-Key`。
2. JSON 请求体改为 `{ "data": "" }`。GET 等没有 JSON 体的请求只带请求头,响应仍可加密。
3. 后端在 `onParse` 里解密,下游 DTO 校验和 `handle` 拿到的是明文对象。
4. JSON 对象响应加密为 `{ "encrypted": true, "data": "" }`,并设置响应头 `x-encrypted: 1`。
5. 解密失败返回 HTTP 400,文案为「请求解密失败」。
文件上传(`FormData` 或非 JSON 体)和 `blob` / `arraybuffer` / `stream` 响应不加密。刷新 Token 的独立请求走同一套信封。
## 明文与日志
没有 `X-Encrypt-Key` 的请求原样进入后续中间件,第三方调用和本地调试可以继续发 JSON。
响应加密发生在 `mapResponse`。随后的 `onAfterResponse` 会把上下文里的 `response` 还原成加密前的业务对象,操作日志、请求日志和限流记录仍能读取 `response.code` 等字段。守卫链的位置见 [中间件](/guide/middleware)。
---
URL: "https://elysia-admin.top/guide/database-operation.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\database-operation.md"
title: "数据库操作 - Elysia Admin 指南"
description: "介绍 Elysia Admin 封装的 PostgreSQL 数据库操作方法,包括增删改查、条件构造器、联表查询、分页查询及事务管理的完整使用指南。"
---
---
title: 数据库操作 - Elysia Admin 指南
description: 介绍 Elysia Admin 封装的 PostgreSQL 数据库操作方法,包括增删改查、条件构造器、联表查询、分页查询及事务管理的完整使用指南。
---
# 数据库操作
业务模块的 `handle.ts` 通过 `@/core/database/repository` 访问 PostgreSQL,底层基于 Drizzle ORM。你一般不需要手写 SQL,用封装好的增删改查和 `CreateQueryBuilder` 即可覆盖大部分场景。
下文示例统一使用 `AppContext`(`import type { AppContext } from '@/types/app-context'`),与中间件注入的 `ctx.body`、`ctx.query`、`ctx.params`、`ctx.user` 一致。接口入参校验见 [参数验证](./parameter-validation)。
```mermaid
flowchart TD
H[handle.ts] --> QB[CreateQueryBuilder]
H --> R[repository 函数]
QB --> R
R --> PG[Drizzle / PostgreSQL]
```
## API 速查
所有函数均从 `@/core/database/repository` 导入。
| 函数 | 用途 |
|------|------|
| `InsertOne` | 插入,不返回记录 |
| `InsertOneAndRes` | 插入,返回新记录 |
| `InsertMany` | 批量插入 |
| `FindOneByKey` | 按主键查单条 |
| `FindAll` | 查全部(不分页) |
| `FindPage` | 分页查询 |
| `FindAllWithJoin` | 联表查全部 |
| `FindPageWithJoin` | 联表分页 |
| `UpdateByKey` | 按主键更新,不返回 |
| `UpdateByKeyAndRes` | 按主键更新,返回记录 |
| `SoftDeleteByKeys` | 软删除(`delFlag = true`) |
| `HardDelete` | 按 where 条件硬删除 |
| `HardDeleteByKeys` | 按主键数组硬删除 |
| `CreateQueryBuilder` | 链式构造 where 条件 |
传入 `ctx` 时,封装会自动填充 `createBy` / `updateBy`(表里有对应字段时)。用 `customData` 时把 `ctx` 设为 `null`。
## 插入
```ts [handle.ts]
import type { AppContext } from '@/types/app-context';
import { InsertOneAndRes } from '@/core/database/repository';
import { BaseResultData } from '@/core/result';
import { businessMerchantSchema } from '@database/schema/business_merchant';
export async function create(ctx: AppContext) {
const res = await InsertOneAndRes(businessMerchantSchema, ctx);
return BaseResultData.ok(res);
}
```
只需写入、不关心返回值时用 `InsertOne`。批量导入用 `InsertMany(schema, ctx, dataArray)`。
## 查询条件
### QueryBuilder 方法
| 方法 | 说明 | 示例 |
|------|------|------|
| `eq` | 等于 | `eq('status', true)` |
| `ne` | 不等于 | `ne('type', 'x')` |
| `in` / `notIn` | 在 / 不在数组中 | `in('id', [1, 2, 3])` |
| `like` / `ilike` | 模糊匹配(区分 / 不区分大小写) | `like('name', keyword)` |
| `notLike` / `notIlike` | 不包含 | `notLike('name', 'test')` |
| `leftLike` / `rightLike` | 左 / 右模糊 | `rightLike('code', 'A')` |
| `gt` / `gte` / `lt` / `lte` | 比较 | `gte('amount', 0)` |
| `between` / `notBetween` | 区间 | `between('age', 18, 60)` |
| `dateRange` | 日期范围 | `dateRange('createTime', start, end)` |
| `isNull` / `isNotNull` | 空值判断 | `isNull('deletedAt')` |
| `or` / `not` | 组合条件 | `or()` / `not()` |
| `join` | 联表 | 见下文 |
| `custom` | 自定义 SQL 片段 | `custom(sql)` |
值为 `undefined`、`null` 或空字符串时,对应条件会自动跳过,不必手动判断。
### 构造 where
```ts
const where = CreateQueryBuilder(businessMerchantSchema)
.eq('delFlag', false)
.eq('status', status)
.dateRange('createTime', startTime, endTime)
.build();
```
等价于:
```sql
WHERE del_flag = false
AND status = $status
AND create_time BETWEEN $start AND $end
```
## 查询
### 按主键
```ts [handle.ts]
import { FindOneByKey } from '@/core/database/repository';
export async function findOne(ctx: AppContext) {
const res = await FindOneByKey(businessMerchantSchema, 'id', ctx.params.id);
return BaseResultData.ok(res);
}
```
### 列表与分页
不分页:
```ts
const where = CreateQueryBuilder(systemDictTypeSchema).eq('delFlag', false).build();
const list = await FindAll(systemDictTypeSchema, where);
```
分页(`pageNum`、`pageSize` 等通常来自 `ctx.query`,与 `BaseListQueryDto` 对齐):
```ts [handle.ts]
import { CreateQueryBuilder, FindPage } from '@/core/database/repository';
export async function findList(ctx: AppContext) {
const { pageNum = 1, pageSize = 10, orderByColumn = 'createTime', sortRule = 'desc', startTime, endTime, status } = ctx.query;
const where = CreateQueryBuilder(businessMerchantSchema)
.eq('delFlag', false)
.eq('status', status)
.dateRange('createTime', startTime, endTime)
.build();
const res = await FindPage(businessMerchantSchema, where, { pageNum, pageSize, orderByColumn, sortRule });
return BaseResultData.ok(res);
}
```
返回的 `res` 结构为 `{ list, total }`。
### 联表查询
通过 `.join()` 把关联表数据挂到主表字段上,再用 `FindAllWithJoin` 或 `FindPageWithJoin` 查询。
```ts [handle.ts]
import { eq } from 'drizzle-orm';
import { CreateQueryBuilder, FindAllWithJoin } from '@/core/database/repository';
import { ListToTree } from '@/shared/tree';
import { systemMenuSchema } from '@database/schema/system_menu';
import { systemMenuBtnSchema } from '@database/schema/system_menu_btn';
export async function findTree(ctx: AppContext) {
const builder = CreateQueryBuilder(systemMenuSchema)
.eq('delFlag', false)
.join({
joinSchema: systemMenuBtnSchema,
fieldName: 'authList',
foreignKey: 'menuId',
primaryKey: 'menuId',
defaultValue: [],
where: eq(systemMenuBtnSchema.delFlag, false),
multiple: true,
});
const data = await FindAllWithJoin(systemMenuSchema, builder);
const tree = ListToTree(data, {
idKey: 'menuId',
parentKey: 'parentId',
childrenKey: 'children',
rootValue: 0,
sortKey: 'sort',
});
return BaseResultData.ok(tree);
}
```
联表分页与上面类似,把 `FindAllWithJoin` 换成 `FindPageWithJoin`,并传入分页参数。
`join` 常用字段:
| 字段 | 说明 |
|------|------|
| `joinSchema` | 关联表 schema |
| `fieldName` | 挂到主表记录上的属性名 |
| `foreignKey` / `primaryKey` | 关联键 |
| `multiple` | `true` 为一对多,`false` 为一对一 |
| `where` | 关联表的额外过滤 |
| `defaultValue` | 无关联数据时的默认值 |
## 更新
```ts [handle.ts]
import { UpdateByKeyAndRes } from '@/core/database/repository';
import { systemDeptSchema } from '@database/schema/system_dept';
export async function update(ctx: AppContext) {
const res = await UpdateByKeyAndRes(systemDeptSchema, 'deptId', ctx);
return BaseResultData.ok(res);
}
```
传入 `ctx` 时,`ctx.body` 中的主键用于定位记录,其余字段写入更新;`updateTime` 和 `updateBy` 会自动处理。日期字符串经 `ParseDateFields` 转为 `Date`(见 [参数验证](./parameter-validation.md))。
## 删除
业务表默认用软删除,关联表、中间表常用硬删除。
**软删除** — 将 `delFlag` 标为 `true`,`ctx.params.ids` 为主键列表(路由通常为 `/:ids`):
```ts [handle.ts]
import { SoftDeleteByKeys } from '@/core/database/repository';
export async function remove(ctx: AppContext) {
await SoftDeleteByKeys(systemApiSchema, 'apiId', ctx);
return BaseResultData.ok();
}
```
**硬删除** — 物理移除记录:
```ts
import { eq } from 'drizzle-orm';
import { HardDelete, HardDeleteByKeys } from '@/core/database/repository';
// 按条件
await HardDelete(systemRoleMenuSchema, eq(systemRoleMenuSchema.roleId, roleId));
// 按主键数组
await HardDeleteByKeys(systemRoleMenuSchema, 'roleId', [1, 2, 3]);
```
## 事务
多表写入需要原子性时,用 `@/core/database/transaction`。事务内抛出错误会自动回滚。
### RunTransaction
最常用的写法。回调参数 `tx` 与 Drizzle 事务对象一致,可直接 `insert` / `update` / `select`。
```ts
import { RunTransaction } from '@/core/database/transaction';
import { systemUserSchema } from '@database/schema/system_user';
import { systemRoleSchema } from '@database/schema/system_role';
const result = await RunTransaction(async (tx) => {
const user = await tx.insert(systemUserSchema).values({
userName: 'admin',
nickName: 'Administrator',
}).returning();
const role = await tx.insert(systemRoleSchema).values({
roleName: 'admin',
roleKey: 'admin',
roleSort: 1,
}).returning();
return { user: user[0], role: role[0] };
});
```
### 隔离级别与只读
第二个参数可传配置:
```ts
// 隔离级别:read uncommitted | read committed | repeatable read | serializable
await RunTransaction(async (tx) => { /* ... */ }, { isolationLevel: 'serializable' });
// 只读事务
await RunTransaction(async (tx) => {
return await tx.select().from(systemUserSchema);
}, { accessMode: 'read only' });
```
### 构建器与钩子
需要链式配置隔离级别、只读模式或生命周期钩子时,用 `CreateTransaction`:
```ts
import { CreateTransaction } from '@/core/database/transaction';
const result = await CreateTransaction<{ userId: number }>()
.isolation('serializable')
.readOnly()
.onBegin(() => console.log('事务开始'))
.onCommit(() => console.log('提交成功'))
.onRollback((err) => console.error('已回滚', err))
.execute(async (tx) => {
const user = await tx.insert(systemUserSchema).values({ userName: 'builder' }).returning();
return { userId: user[0].userId };
})
.run();
```
`RunTransaction` 也支持 `onBegin`、`onCommit`、`onRollback`、`onError` 钩子,适合提交后清缓存、回滚时记日志等场景。
### 批量与嵌套
`CreateBatchTransaction` 可串行或并行跑多个**独立**事务:
```ts
import { CreateBatchTransaction } from '@/core/database/transaction';
const results = await CreateBatchTransaction()
.add('创建用户', async (tx) => tx.insert(systemUserSchema).values({ userName: 'u1' }).returning())
.add('创建角色', async (tx) => tx.insert(systemRoleSchema).values({ roleName: 'r1' }).returning())
.runAll(); // 串行
// .runAllParallel() // 并行,互不影响
```
可复用函数用 `WithTransaction`,传入已有 `tx` 时加入同一事务,否则自动开新事务:
```ts
import { WithTransaction, TransactionContext } from '@/core/database/transaction';
import pg from '@/core/database/pg';
async function createUser(name: string, tx?: TransactionContext) {
return WithTransaction(tx ?? pg, async (t) => {
return t.insert(systemUserSchema).values({ userName: name }).returning();
});
}
async function createRole(name: string, tx?: TransactionContext) {
return WithTransaction(tx ?? pg, async (t) => {
return t.insert(systemRoleSchema).values({ roleName: name }).returning();
});
}
// 共享同一事务
await RunTransaction(async (tx) => {
await createUser('john', tx);
await createRole('admin', tx);
});
// 各自独立事务
await createUser('jane');
```
## 参考
- [Drizzle ORM 事务](https://orm.drizzle.org.cn/docs/transactions)
- [PostgreSQL 事务](https://www.runoob.com/postgresql/postgresql-transaction.html)
---
URL: "https://elysia-admin.top/guide/deploy.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\deploy.md"
title: "部署 - Elysia Admin 指南"
description: "介绍 Elysia Admin 的多种生产环境部署方案,包括 Docker 容器化部署、PM2 进程管理部署及宝塔面板部署,附安全建议与故障排查指南。"
---
---
title: 部署 - Elysia Admin 指南
description: 介绍 Elysia Admin 的多种生产环境部署方案,包括 Docker 容器化部署、PM2 进程管理部署及宝塔面板部署,附安全建议与故障排查指南。
---
# 部署
生产环境需要 PostgreSQL、Redis,以及(若使用队列/定时任务)独立运行的 Worker 进程。本文介绍 Docker 与 PM2 两种常见方式,以及宝塔面板上的操作要点。
::: tip 站点部署
后台还可以维护本机 Nginx 站点:生成配置、用 Let's Encrypt 申请和续签证书。见 [站点部署](/guide/site-deploy)。
:::
```mermaid
flowchart LR
subgraph 服务器
PM2[PM2] --> Main[index.js HTTP]
PM2 --> Worker[workers.js 队列]
end
Main --> PG[(PostgreSQL)]
Main --> Redis[(Redis)]
Worker --> PG
Worker --> Redis
```
## 部署前准备
无论哪种方式,上线前请先改好配置。
**后端** — 编辑 `server/src/config/production.yaml`:
| 配置项 | 说明 |
|--------|------|
| `app.port` / `app.prefix` | 服务端口与 API 前缀(默认 `/api`) |
| `app.corsOrigin` | 前端访问域名,生产环境务必改成你的域名 |
| `database.*` | PostgreSQL 连接信息 |
| `redis.*` | Redis 连接信息 |
| `jwt.*.secret` | 生产环境请换成随机强密钥 |
| `crypto.enabled` / `crypto.privateKey` / `crypto.publicKey` | 请求加解密。生产默认关闭。开启前在 `server/` 执行 `bun run gen:crypto-keys production` 写入密钥,详见 [请求加解密](/guide/crypto) |
**前端(前后端同机部署时)**
在 `admin/` 配置 `.env.production` 的 `VITE_API_BASE_URL`,构建后把产物放进 `server/public/`:
```bash
cd admin
pnpm install
pnpm build
# 将 admin/dist/ 内文件复制到 server/public/
```
然后执行 `server` 的 `bun run build`,`public/` 会一并打进 `dist/public/`。
**前端(前后端分离时)**
`admin/dist/` 部署到 Nginx / CDN,只需保证 `VITE_API_BASE_URL` 指向后端地址,`production.yaml` 的 `corsOrigin` 包含前端域名。
**依赖服务**
- PostgreSQL >= 16,库表已初始化(开发环境 `bun db:push` 或执行项目 SQL)
- Redis >= 6,队列与缓存依赖它
### 生产启动顺序与双栈探测
生产环境(`NODE_ENV=production`)主进程启动顺序为:
1. **双栈出网探测**(`shared/dual-stack-probe`):探测 IPv4 / IPv6 可达性,并据此设置 DNS `resultOrder`
2. 并行连接 PostgreSQL(`EnsurePg`)与 Redis(`ConnectRedis`)
3. 初始化队列
4. 创建应用并开始监听
若探测判定双栈半残或完全不可达,进程会**失败退出**,避免连库/连 Redis 卡在错误协议栈上。内网或特殊网络若探测误判,可设置环境变量 `SKIP_NETWORK_PROBE=1` 跳过探测并强制按 IPv4 策略启动。进程关闭时会一并关闭队列连接。
## Docker 部署
适合已有容器编排经验的团队。镜像在 `server/Dockerfile`:build 阶段在容器内执行 `bun run build`,运行阶段为 `debian:bookworm-slim` + Bun 二进制(非 `--compile` 单文件),**单容器内同时启动 HTTP 与 Worker**(`WORKDIR` 为 `/app/dist`)。
**环境要求:** Docker;可选 Docker Compose。
进入 `server/` 目录:
```bash
cd server
```
构建镜像:
```bash
bun run docker:build
# 等价于
docker build -t hnq1/elysia-admin:latest .
```
运行容器(需挂载生产配置,建议加 `--init` 改善信号转发):
```bash
bun run docker:run
# 或手动指定配置卷
docker run -d \
--name elysia-admin \
--init \
-p 3000:3000 \
-e NODE_ENV=production \
-e CONFIG_PATH=/app/dist/production.yaml \
-v /path/to/production.yaml:/app/dist/production.yaml \
hnq1/elysia-admin:latest
```
### Docker Compose
本地或单机一键拉起应用 + PostgreSQL + Redis 时,可在 `server/` 下创建 `docker-compose.yml`:
```yaml
version: '3.8'
services:
app:
image: hnq1/elysia-admin:latest
container_name: elysia-admin
restart: unless-stopped
init: true
ports:
- "3000:3000"
environment:
- NODE_ENV=production
- CONFIG_PATH=/app/dist/production.yaml
volumes:
- ./production.yaml:/app/dist/production.yaml
depends_on:
- postgres
- redis
postgres:
image: postgres:16-alpine
container_name: elysia-admin-db
restart: unless-stopped
environment:
- POSTGRES_DB=elysia_admin
- POSTGRES_USER=admin
- POSTGRES_PASSWORD=your_password
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
redis:
image: redis:7-alpine
container_name: elysia-admin-redis
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
```
启动:
```bash
docker-compose up -d
```
常用命令:
```bash
# 查看日志
bun run docker:logs
docker logs -f elysia-admin
# 停止 / 删除
bun run docker:stop
bun run docker:rm
# 重启
docker restart elysia-admin
```
## PM2 部署
推荐大多数 VPS 场景。项目构建后会生成 **主进程 + Worker 进程** 两份入口,PM2 同时托管,HTTP 与队列互不影响。
**环境要求:** Node.js >= 22、Bun、PM2;服务器已装好 PostgreSQL 与 Redis。
```bash
npm i -g pm2 bun
```
### 本地构建
在开发机或 CI 上进入 `server/` 执行生产构建:
```bash
cd server
bun run build
```
构建完成后 `dist/` 目录结构如下:
```
dist/
├── public/ # 前端静态资源(若已复制 admin 构建产物)
├── ecosystem.config.cjs # PM2 配置(appId 来自 production.yaml)
├── index.js # 主进程:HTTP + 静态资源
├── workers.js # Worker:队列消费 + 定时任务
├── dist/cjs/ # BullMQ 沙箱 bootstrap(Worker 依赖,不可缺少)
├── processors/ # 各队列 Processor
│ ├── system-cron.js
│ ├── flow-buffer.js
│ └── trade-order.js
└── production.yaml # 生产配置(部署前请按服务器环境修改)
```
### 上传与启动
将 `dist/` **里面的所有文件**上传到服务器目录(例如 `/www/wwwroot/elysia-admin`),不是上传 `dist` 文件夹本身。服务器上目录应与上图一致。
上传后编辑服务器上的 `production.yaml`,填入真实的数据库、Redis、域名等配置。
```bash
cd /www/wwwroot/elysia-admin
pm2 start ecosystem.config.cjs
```
PM2 会启动两个进程(名称来自 `production.yaml` 的 `app.id`,默认类似 `Elysia-Admin`):
| 进程 | 入口 | 职责 |
|------|------|------|
| `{appId}` | `index.js` | HTTP API、静态页面、Bull Board |
| `{appId}-workers` | `workers.js` | 队列消费、定时任务 |
两个进程都要在跑,否则任务会积压在 Redis。详见 [队列](./queue)。
### PM2 常用命令
```bash
pm2 status
pm2 logs Elysia-Admin
pm2 logs Elysia-Admin-workers
pm2 restart all
pm2 restart Elysia-Admin
pm2 restart Elysia-Admin-workers
pm2 stop all
pm2 delete all
pm2 save # 保存进程列表
pm2 startup # 开机自启
pm2 monit # 资源监控
```
进程名以你 `production.yaml` 里 `app.id` 为准,上表仅为示例。
## 宝塔面板
适合习惯图形界面的运维。推荐宝塔 >= 11.0.0。
从 [宝塔官网](https://www.bt.cn/new/download.html) 安装面板后:
**安装运行环境**
在软件商店安装 PostgreSQL(>= 16)、Redis(>= 6)、Nginx(反向代理用)。
**安装 Node 与 Bun**
进入「网站 → Node 项目」,安装 Node.js(推荐 v22+)。终端执行:
```bash
npm i -g bun pm2
```
**上传构建产物**
与 PM2 部署相同:本地 `bun run build` 后,把 `dist/` 内文件上传到例如 `/www/wwwroot/elysia-admin`,修改 `production.yaml`。
**数据库连接**

**添加 Node 项目**
在「Node 项目」中新建项目,指向 PM2 配置文件:

也可在 SSH 终端直接:
```bash
cd /www/wwwroot/elysia-admin
pm2 start ecosystem.config.cjs
```
Nginx 反代时,把域名指到 `app.port`,静态资源可由后端 `public/` 直接提供,或单独配置 `admin` 构建产物。
## 部署后检查
| 检查项 | 做法 |
|--------|------|
| 站点可访问 | 浏览器打开你的域名,能加载登录页 |
| 双进程正常 | `pm2 status` 显示 main + workers 均为 online |
| 日志无报错 | `pm2 logs` 或 `docker logs` 查看启动错误 |
| 数据库 / Redis | 登录、字典等接口正常;缓存监控有数据 |
| API | 调几个关键接口(登录、列表)确认 200 |
| 队列 | Admin → 系统监控 → 队列监控,确认 Worker 在消费 |
## 安全建议
- 修改默认端口,不要直接把 3000 暴露公网;对外只开 80 / 443
- 配置防火墙,数据库和 Redis 不要对公网开放
- Nginx 配置 HTTPS,强制跳转
- `production.yaml` 中 JWT secret、数据库密码使用强随机值
- 定期备份 PostgreSQL 与重要文件
- 管理后台、Bull Board 建议限制 IP 或加额外鉴权
## 故障排查
| 现象 | 排查方向 |
|------|----------|
| 端口无法访问 | `netstat -tunlp \| grep 3000` 看是否监听;检查防火墙 |
| 启动即退出 | `pm2 logs` 看报错;常见为 `production.yaml` 数据库连不上,或生产双栈探测失败(见上文「生产启动顺序与双栈探测」;可临时 `SKIP_NETWORK_PROBE=1`) |
| 登录 401 / CORS | 检查 `corsOrigin` 是否包含前端域名 |
| 定时 / 队列不执行 | Worker 进程是否 online;`dist/cjs/`、`processors/` 是否完整上传 |
| Processor 报错 | 是否忘记在本地重新 `bun run build` 后再上传 |
| 静态页 404 | `public/` 是否包含前端构建产物 |
仍无法解决可查看 [常见问题](/other/faq) 或在 Gitee 提交 Issue。
---
URL: "https://elysia-admin.top/guide/first-api.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\first-api.md"
title: "第一个接口 - Elysia Admin 指南"
description: "学习如何在 Elysia Admin 中创建第一个 API 接口,包括模块创建、路由定义、DTO 参数校验、处理函数实现及完整代码整合流程。"
---
---
title: 第一个接口 - Elysia Admin 指南
description: 学习如何在 Elysia Admin 中创建第一个 API 接口,包括模块创建、路由定义、DTO 参数校验、处理函数实现及完整代码整合流程。
---
# 第一个接口
本章将详细介绍如何在 `Elysia Admin` 项目中创建第一个 API 接口,包括接口定义、参数校验、响应处理等完整流程;下文按「模块目录 → 路由 → DTO → 处理函数 → 整合」的顺序编写,最后一节会用示意图归纳各文件的协作关系。
## 创建业务模块
首先,在 `/server/src/modules/` 目录下创建业务模块目录,例如: `/server/src/modules/business-goods/`。
**命名规范:**
- `business` 作为业务模块的前缀,用于区分不同类型的模块
- `goods` 作为业务模块的具体名称,描述模块的业务领域
## 定义接口路由
在 `/server/src/modules/business-goods/` 目录下创建 `route.ts` 文件,用于定义接口路由配置。
```ts [route.ts]
import type { IRouteModule } from "@/types/route";
const BusinessGoodsModule: IRouteModule = {
// 业务模块分类名称(用于 API 文档分组)
tags: '商品管理',
// 接口列表
routes: [
{
url: '/business/goods', // 接口路径
method: 'post', // 接口 HTTP 方法
summary: '创建商品', // 接口描述(将显示在 API 文档中)
handle: () => {}, // 接口处理函数(暂时为空实现)
// 接口元数据配置
meta: {
isAuth: true, // 是否需要登录认证
isLog: true, // 是否记录操作日志
ipRateLimit: '60:2', // IP 限流配置(60秒内最多2次请求)
permission: 'business:goods:create', // 权限配置
},
}
],
};
export default BusinessGoodsModule;
```
保存文件后,接口会自动注册到后端服务中。在开发环境下,API 文档也会实时更新。
## 创建数据传输对象 (DTO)
`DTO`(Data Transfer Object)用于校验前端传递的参数是否符合要求,以及确保后端响应的数据类型正确。若参数或响应数据不符合定义,系统会自动返回错误响应。
在 `/server/src/modules/business-goods/` 目录下创建 `dto.ts` 文件:
```ts [dto.ts]
import { t } from 'elysia';
import { BaseResultDto } from '@/types/dto';
/**
* 创建商品请求参数校验
*/
export const CreateDto = {
body: t.Object({
name: t.String({ description: '商品名称', error: '商品名称格式错误', minLength: 5 }),
price: t.Number({ description: '商品价格', error: '商品价格格式错误', min: 0 }),
stock: t.Number({ description: '商品库存', error: '商品库存格式错误', min: 0 }),
remark: t.Optional(t.String({ description: '商品备注' })),
}),
};
```
## 实现接口处理函数
在 `/server/src/modules/business-goods/` 目录下创建 `handle.ts` 文件,用于实现接口的业务逻辑:
```ts [handle.ts]
import type { AppContext } from '@/types/app-context';
import { BaseResultData } from '@/core/result';
/**
* 创建商品接口处理函数
* @param ctx 请求上下文(含 user、routeInfo 等)
* @returns 标准响应格式
*/
export async function create(ctx: AppContext) {
const data = ctx.body;
// 这里可以实现具体的业务逻辑,如数据库保存操作
return BaseResultData.ok();
}
```
## 整合代码
将创建的文件进行整合,更新 `route.ts` 文件以引用 DTO 和处理函数:
::: code-group
```ts [route.ts]
import type { IRouteModule } from "@/types/route";
import { CreateDto } from "./dto";
import { create } from "./handle";
const BusinessGoodsModule: IRouteModule = {
// 业务模块分类名称
tags: '商品管理',
// 接口列表
routes: [
{
url: '/business/goods', // 接口路径
method: 'post', // 接口方法
summary: '创建商品', // 接口描述
dto: CreateDto, // 接口参数校验
handle: create, // 接口处理函数
// 接口元数据
meta: {
isAuth: true, // 是否登录后才能访问
isLog: true, // 是否记录日志
ipRateLimit: '60:2', // IP 限流配置(60s 内最多 2 次请求)
permission: 'business:goods:create', // 权限配置
},
}
],
};
export default BusinessGoodsModule;
```
```ts [dto.ts]
import { t } from 'elysia';
import { BaseResultDto } from '@/types/dto';
export const CreateDto = {
body: t.Object({
name: t.String({ description: '商品名称', error: '商品名称格式错误', minLength: 5 }),
price: t.Number({ description: '商品价格', error: '商品价格格式错误', min: 0 }),
stock: t.Number({ description: '商品库存', error: '商品库存格式错误', min: 0 }),
remark: t.Optional(t.String({ description: '商品备注' })),
}),
};
```
```ts [handle.ts]
import type { AppContext } from '@/types/app-context';
import { BaseResultData } from '@/core/result';
export async function create(ctx: AppContext) {
const data = ctx.body;
// 保存操作
return BaseResultData.ok();
}
```
:::
## 模块文件协作
完成以上五步后,`route.ts`、`dto.ts`、`handle.ts` 三个文件已在同一个模块目录中各司其职。约定在 `server/src/modules/` 下按领域建目录(如 `business-goods`);`route.ts` 会被启动流程扫描并自动注册到应用(开发环境逐模块加载,失败会 warn 并跳过;生产环境使用预生成的 `route-registry.generated.ts`),因此不必手写「把路由挂到 app」的样板代码。下面的示意图归纳了「谁参与注册、谁负责校验与业务」的协作关系:
```mermaid
flowchart LR
M[modules/business-xxx/] --> R[route.ts
路由与 meta]
M --> D[dto.ts
入参/出参校验]
M --> H[handle.ts 等
业务逻辑]
R --> Auto[启动扫描注册]
```
通过以上步骤,我们成功创建了一个具备以下特性的商品创建接口:
- **模块化设计**:代码结构清晰,职责分明
- **登录认证**:通过 `isAuth: true` 确保只有登录用户可以访问
- **参数校验**:使用 DTO 验证请求参数的合法性
- **操作日志**:通过 `isLog: true` 记录接口调用情况
- **接口限流**:通过 `ipRateLimit` 防止恶意请求
- **权限控制**:通过 `permission` 配置实现细粒度的权限管理
## 按环境注册模块(stages)
`IRouteModule` 支持可选字段 `stages`,用于控制模块在哪些环境注册与打进生产包:
| 写法 | 行为 |
|------|------|
| 省略 `stages` | `development` + `production` 两端都注册 / 打包 |
| `stages: ['development']` | 仅开发环境加载(如内置 `dev-sql` SQL 控制台) |
| `stages: ['production']` | 仅生产环境 |
| `stages: []` | 永不加载 |
必须使用**字面量数组**(如 `stages: ['development']`),供 `script/generate-registry.ts` 用正则解析;不要写成变量或计算值。生产构建只收录含 `production` 的模块;运行时 `LoadRouteModules` 也会按规范化后的 `NODE_ENV`(`production` vs 其余 → `development`)过滤。
示例(仅开发环境可用的模块):
```ts [route.ts]
import type { IRouteModule } from '@/types/route'
const DevOnlyModule: IRouteModule = {
tags: '开发工具',
stages: ['development'],
routes: [
{
url: '/dev/example',
method: 'get',
summary: '仅开发环境',
handle: () => ({ ok: true }),
meta: { isAuth: true },
},
],
}
export default DevOnlyModule
```
---
URL: "https://elysia-admin.top/guide/mcp.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\mcp.md"
title: "MCP 服务"
description: "Elysia Admin 官方 MCP(elysia-admin-mcp)源码活索引与 PostgreSQL MCP(pg-mcp-server)数据库只读查询的配置、工具清单与使用纪律。"
---
---
title: MCP 服务
description: Elysia Admin 官方 MCP(elysia-admin-mcp)源码活索引与 PostgreSQL MCP(pg-mcp-server)数据库只读查询的配置、工具清单与使用纪律。
---
# MCP 服务
用 AI 做业务模块时,除了项目自带的 rules 与 `.ai/` 规范,还可以接入两个 MCP 服务,分别提供**源码事实**与**运行时事实**:
| MCP | 包名 | 职责 |
|-----|------|------|
| 官方 MCP | `elysia-admin-mcp` | 组件 props、Hook 签名、后端方法、Drizzle 表结构、模块权限码 |
| 数据库 MCP | `pg-mcp-server` | 只读查询 dict / menu / 权限等运行时数据 |
两者互补,不重复:官方 MCP 不连数据库;数据库 MCP 不扫源码。规范与流程仍由 `.ai/`、`AGENTS.md` 负责。
```mermaid
flowchart LR
subgraph sourceFacts [源码事实]
A[elysia-admin-mcp]
A --> A1[组件 / Hook / 工具]
A --> A2[后端方法 / 表结构]
A --> A3[模块权限码]
end
subgraph runtimeFacts [运行时事实]
B[pg-mcp-server]
B --> B1[system_dict_*]
B --> B2[system_menu]
B --> B3[system_role_menu]
end
C[AI 生成代码与 SQL] --> A
C --> B
```
## 一、官方 MCP(elysia-admin-mcp)
[Elysia Admin MCP](https://www.npmjs.com/package/elysia-admin-mcp) 是项目的**源码活索引** MCP 服务器。它实时扫描前端与后端源码(默认 `admin/` 与 `server/`,可通过环境变量自定义),把组件 props、后端方法签名、Drizzle 表结构等**必须与代码同步的事实**变成可查询接口。
### 定位
- **镜头而非仓库**:只索引源码事实,约定/流程/文档的家仍在 `.ai/` 与 `AGENTS.md`
- **只读安全**:不连业务数据库、不执行 SQL、不写文件
- **落盘缓存**:源码未变时秒级启动(mtime 指纹失效)
- **中文检索**:内置高频后端方法中文别名,搜「分页」「事务」「软删」直接命中
### 工具清单
分 3 组,共 14 个工具。每个工具返回结果均带 `path`,便于 AI 跳转核实。
#### A. 统一检索
| 工具 | 用途 |
|------|------|
| `search_asset` | 全局模糊搜索组件/Hook/工具/后端方法/表/模块,不确定「有没有现成的 X」时的第一站 |
#### B. 前端源码索引
| 工具 | 用途 |
|------|------|
| `list_components` | 列出 Art 组件清单(可按分类过滤) |
| `get_component` | 获取组件 props/emits/slots + 最小用法示例 |
| `get_hook` | 获取 `useTable`/`useAuth` 等 Hook 的参数与返回值 |
| `get_util` | 获取 `request`/`validator` 等工具导出成员 |
| `list_api_types` | 列出 `Api.*` 类型命名空间 |
| `get_api_type` | 获取某命名空间的 ListItem/List/SearchParams 类型 |
#### C. 后端源码索引
| 工具 | 用途 |
|------|------|
| `list_backend_methods` | 按 core/shared/infrastructure 列出方法(重点方法优先) |
| `get_backend_method` | 获取 `FindPage`/`WithTransaction` 等方法签名 |
| `get_infra_capability` | 获取 payment/storage/queue/cron/mail 入口用法 |
| `list_modules` | 列出模块及权限码数量 |
| `get_module` | 获取模块路由、权限码与文件构成 |
| `list_schemas` | 列出 Drizzle 表名 |
| `get_schema` | 获取表字段、类型、主键与软删标记 |
### 客户端接入
所有客户端均需设置环境变量 `ELYSIA_ADMIN_ROOT` 指向你的 Elysia Admin 项目根目录。若前端/后端目录名非 `admin/` / `server/`,可额外设置 `ELYSIA_ADMIN_DIR` / `ELYSIA_SERVER_DIR`。
#### Cursor
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"elysia-admin": {
"command": "npx",
"args": ["-y", "elysia-admin-mcp"],
"env": {
"ELYSIA_ADMIN_ROOT": "D:/path/to/elysia-admin",
"ELYSIA_ADMIN_DIR": "",
"ELYSIA_SERVER_DIR": ""
}
}
}
}
```
> `ELYSIA_ADMIN_DIR` / `ELYSIA_SERVER_DIR` 留空即使用默认 `admin/` / `server/`。填相对名(如 `"webapp"`)会拼到 `ELYSIA_ADMIN_ROOT` 下;填绝对路径则直接使用(可在 root 之外)。
> 本地开发时可将 `command` 改为 `node`、`args` 指向本仓库 `dist/index.js`。
#### Claude Code
```bash
claude mcp add elysia-admin -- npx -y elysia-admin-mcp
# 并在环境或配置中设置 ELYSIA_ADMIN_ROOT(可选 ELYSIA_ADMIN_DIR / ELYSIA_SERVER_DIR)
```
#### Codex
`~/.codex/config.toml`:
```toml
[mcp_servers.elysia-admin]
command = "npx"
args = ["-y", "elysia-admin-mcp"]
[mcp_servers.elysia-admin.env]
ELYSIA_ADMIN_ROOT = "D:/path/to/elysia-admin"
# ELYSIA_ADMIN_DIR = "webapp" # 可选,相对 root
# ELYSIA_SERVER_DIR = "D:/other/repo/backend" # 可选,绝对路径
```
#### Qoder / Kiro / Trae
在各自的 MCP 配置中添加 stdio 服务,`command` / `args` / `env` 字段与 Cursor 一致。
### 环境变量
| 变量 | 说明 |
|------|------|
| `ELYSIA_ADMIN_ROOT` | Elysia Admin 项目根目录。未设置时从 cwd 向上自动查找(需含 `admin/` 与 `server/`);若两个自定义目录均为绝对路径,则 root 回退为 cwd |
| `ELYSIA_ADMIN_DIR` | 前端代码目录(可选)。为空 → `{root}/admin`;相对名 → `{root}/{name}`;绝对路径 → 直接使用 |
| `ELYSIA_SERVER_DIR` | 后端代码目录(可选)。为空 → `{root}/server`;相对名 → `{root}/{name}`;绝对路径 → 直接使用 |
| `ELYSIA_MCP_NO_CACHE` | 设为 `1` 强制重建索引,忽略缓存(等价于 `--no-cache`) |
## 二、数据库 MCP(pg-mcp-server)
[pg-mcp-server](https://github.com/ericzakariasson/pg-mcp-server) 是 PostgreSQL 的 MCP 服务器,让 AI 通过受控接口**只读**查询数据库,用于核对 dict、menu、权限等运行时数据。
### Cursor 配置
```json
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["--yes", "pg-mcp-server", "--transport", "stdio"],
"env": {
"DATABASE_URL": "postgresql://postgres:postgres@localhost:5432/postgres"
}
}
}
}
```
> 请将 `DATABASE_URL` 替换为你本机或测试环境的 PostgreSQL 连接串。
### 配置项
| 变量 | 说明 |
|------|------|
| `DATABASE_URL` | PostgreSQL 连接串(必填) |
| `DANGEROUSLY_ALLOW_WRITE_OPS` | 启用写入(默认 `false`,**建议保持只读**) |
| `DEBUG` | 启用调试日志(默认 `false`) |
| `PG_SSL_ROOT_CERT` | 可选 TLS CA 证书路径(如云数据库) |
### 工具与资源
**工具**
| 工具 | 用途 |
|------|------|
| `query` | 执行 SQL 查询,例如 `{ "sql": "SELECT * FROM system_menu LIMIT 10" }` |
**资源**
| 资源 URI | 用途 |
|----------|------|
| `postgres://tables` | 列出所有表 |
| `postgres://table/{schema}/{table}` | 获取表结构与示例数据 |
### 只读纪律
与项目 `.ai/AI_MCP_SETUP.md` 对齐:
| 事项 | 说明 |
|------|------|
| 查 dict / menu / 权限 | **优先** Postgres MCP 只读查询 |
| 禁止读备份 | **不要**让 AI 读 `server/database/sql/pg.sql`(全库备份,可能与现库不一致) |
| 禁止 MCP 代跑 SQL | handoff SQL(`server/database/sql/{模块}-init.sql`)仍由**开发者手动执行** |
| 禁止 MCP 写入 | 不要用 MCP 做 DDL、迁移或业务写入;`DANGEROUSLY_ALLOW_WRITE_OPS` 保持 `false` |
| Schema 真相源 | Drizzle schema 文件仍是表结构权威,MCP 用于核对运行时数据 |
**只读用途示例:**
- 核对表与列是否与 schema 文件一致
- 查询 `system_dict_type` / `system_dict_data` 获取业务字典
- 查询 `system_menu` 获取父菜单 `menu_id`、`MAX(sort)`、重复 `permission`
- 查询 `system_role` 用于 `system_role_menu` 插入
**MCP 不可用时**,AI 应声明「Postgres MCP 未连接」,并在 handoff SQL 中优先用 `SELECT ... INTO` / CTE 子查询,仍输出完整 SQL 文件。
## 三、推荐使用顺序
```mermaid
flowchart TD
S1[1. elysia-admin-mcp 查源码事实] --> S2[2. pg-mcp-server 只读查运行时数据]
S2 --> S3[3. 生成后端 / 前端代码]
S3 --> S4[4. 输出 handoff SQL 文件]
S4 --> S5[5. 开发者手动执行 SQL]
```
1. **源码事实**(`elysia-admin-mcp`)—— 查组件、Hook、后端方法、表结构、权限码
2. **运行时事实**(`pg-mcp-server` 只读)—— 查 dict、menu、角色权限行
3. **生成代码** —— 后端 `handle.ts` → 前端 views
4. **输出 SQL** —— `server/database/sql/{模块}-init.sql`(仅生成文件)
5. **手动执行** —— 开发者在本机执行 handoff SQL
## 相关
- [AI 开发指南](./ai-guide) —— 提示词、脚手架与 AI 交付物
- [数据库操作](./database-operation) —— repository 与 QueryBuilder 用法
- 仓库内 `.ai/AI_MCP_SETUP.md` —— Agent 侧 MCP 纪律(英文)
---
URL: "https://elysia-admin.top/guide/middleware.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\middleware.md"
title: "中间件 - Elysia Admin 指南"
description: "介绍 Elysia Admin 中间件的使用方式,包括请求加解密、请求预处理(IP黑名单、认证、限流、权限)和响应后处理(操作日志、响应时间统计)的实现方法。"
---
---
title: 中间件 - Elysia Admin 指南
description: 介绍 Elysia Admin 中间件的使用方式,包括请求加解密、请求预处理(IP黑名单、认证、限流、权限)和响应后处理(操作日志、响应时间统计)的实现方法。
---
# 中间件
中间件在请求生命周期的不同阶段注入通用逻辑:进入业务 `handle` 之前按需解密、做鉴权与限流,响应发出之前按需加密,响应发出之后写操作日志。代码集中在 `server/src/middleware/`。`app.ts` 里先挂 `CryptoMiddleware`,再挂 `GlobalMiddleware` 和 `GlobalResponseMiddleware`。
路由上的 `meta` 字段(`isAuth`、`permission`、`ipRateLimit` 等)决定哪些守卫会生效,配置方式见 [第一个接口](./first-api)。加解密开关与报文格式见 [请求加解密](./crypto)。
```mermaid
flowchart TD
Req[HTTP 请求] --> C[CryptoMiddleware 按需解密]
C --> R[onRequest 记录 startTime]
R --> B[onBeforeHandle 守卫链]
B --> H[handle.ts 业务逻辑]
H --> Enc[CryptoMiddleware 按需加密响应]
Enc --> A[onAfterResponse 还原明文并写日志]
B -->|守卫返回错误| Err[直接响应,跳过 handle]
```
任一守卫返回错误响应时,后续 `handle` 不会执行。排查「接口没进业务逻辑」时,可先确认认证、权限或限流是否在中途拦截。
## 请求加解密
`CryptoMiddleware` 在 `crypto.enabled` 为 true 时注册,挂在全局守卫之前。
1. `onRequest`:请求头带 `X-Encrypt-Key` 时,用 RSA 私钥解出本次 AES 密钥。
2. `onParse`:JSON 体为 `{ data }` 时解密成对象,下游校验与 `handle` 看到的是明文。
3. `mapResponse`:本次请求有 AES 密钥且响应是 JSON 对象时,加密成 `{ encrypted: true, data }`。
4. `onAfterResponse`:把上下文里的 `response` 还原成加密前的业务对象,后面的操作日志才能读到 `response.code`。
没有加密头的请求不经过解密。协议、密钥生成和前端开关见 [请求加解密](./crypto)。
## 请求预处理
`onBeforeHandle` 里按顺序执行守卫。守卫返回 `BaseResultData.fail(...)` 时,`executeGuard` 会抛出并中断请求。
```ts [middleware/index.ts]
import { Elysia } from 'elysia';
import config from '@/config';
import { AnalysisRoute } from './analysis';
import { AuthGuard } from './guards/auth';
import { PermissionGuard } from './guards/permission';
import { IpBlackGuard } from './guards/ipblack';
import { ApiGuard } from './guards/api';
import { IpRateLimitGuard } from './guards/ipratelimit';
const { guard } = config;
export function GlobalMiddleware(app: Elysia) {
app.onBeforeHandle(async (ctx) => {
if (guard.ipBlacklist) await executeGuard(IpBlackGuard, ctx);
if (guard.apiSwitch) await executeGuard(ApiGuard, ctx);
await executeGuard(AnalysisRoute, ctx);
await executeGuard(AuthGuard, ctx);
await executeGuard(IpRateLimitGuard, ctx);
await executeGuard(PermissionGuard, ctx);
});
}
```
| 守卫 | 作用 | 触发条件 |
|------|------|----------|
| `IpBlackGuard` | IP 黑名单拦截 | `config.guard.ipBlacklist` 开启 |
| `ApiGuard` | API 熔断开关 | `config.guard.apiSwitch` 开启 |
| `AnalysisRoute` | 解析当前路由,注入 `routeInfo`、`routeKey` | 始终执行 |
| `AuthGuard` | JWT 认证,注入 `ctx.user` | 路由 `meta.isAuth: true` |
| `IpRateLimitGuard` | IP 限流 | 路由配置了 `meta.ipRateLimit` |
| `PermissionGuard` | 按钮权限校验 | 路由配置了 `meta.permission` |
自定义守卫放在 `middleware/guards/` 下,按同样方式 `executeGuard` 接入即可。
## 响应后处理
`onRequest` 记录请求开始时间,`onAfterResponse` 在响应完成后执行副作用,不阻塞返回。
```ts [middleware/index.ts]
import { logger } from '@/shared/logger';
import { AddOperLog } from '@/modules/system-oper-log/handle';
import { IpRateLimitRecord } from './guards/ipratelimit';
export function GlobalResponseMiddleware(app: Elysia) {
app.onRequest((ctx) => {
ctx.startTime = Date.now();
});
app.onAfterResponse(async (ctx) => {
process.env.NODE_ENV !== 'production' && logger.logRequest(ctx);
await AddOperLog(ctx);
await IpRateLimitRecord(ctx);
});
}
```
| 钩子 | 时机 | 典型用途 |
|------|------|----------|
| `onRequest` | 请求刚进入 | 记录 `startTime` |
| `onAfterResponse` | 响应已发出 | 操作日志、限流计数、开发环境请求日志 |
路由 `meta.isLog: true` 时,`AddOperLog` 会把本次请求写入操作日志表。
## 在 handle 里读上下文
`handle.ts` 使用 `AppContext` 类型(`@/types/app-context`),直接访问中间件注入的字段,无需 `(ctx as any)`。
```ts [handle.ts]
import type { AppContext } from '@/types/app-context';
import { GetClientIp } from '@/shared/ip';
import { BaseResultData } from '@/core/result';
export async function handleRequest(ctx: AppContext) {
const startTime = ctx.startTime; // onRequest
const user = ctx.user; // AuthGuard
const routeInfo = ctx.routeInfo; // AnalysisRoute
const routeKey = ctx.routeKey;
const ip = ctx.ip ?? GetClientIp(ctx); // IpBlackGuard 等
// 业务逻辑...
return BaseResultData.ok();
}
```
常用注入字段:
| 字段 | 来源 | 说明 |
|------|------|------|
| `routeInfo` | `AnalysisRoute` | 当前路由的 `tags`、`summary`、`meta` |
| `routeKey` | `AnalysisRoute` | 路由唯一键,如 `GET:/api/system/user/list` |
| `user` | `AuthGuard` | 当前登录用户(含 `permissions`) |
| `startTime` | `onRequest` | 请求开始时间戳 |
| `ip` | IP 相关守卫 | 客户端 IP |
## 错误处理
全局异常由 `middleware/error-handler.ts` 的 `ConfigureErrorHandler` 统一捕获,在 `app.ts` 中与上述中间件一并注册。业务层用 `BaseResultData.fail(code, msg)` 返回错误即可,不必在 `handle` 里包 `try/catch`。
---
URL: "https://elysia-admin.top/guide/parameter-validation.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\parameter-validation.md"
title: "参数验证 - Elysia Admin 指南"
description: "介绍 Elysia Admin 中的接口参数校验方案,包括 BaseResultDto、BaseListQueryDto、CrudDto 生成器及常用工具函数的使用方法。"
---
---
title: 参数验证 - Elysia Admin 指南
description: 介绍 Elysia Admin 中的接口参数校验方案,包括 BaseResultDto、BaseListQueryDto、CrudDto 生成器及常用工具函数的使用方法。
---
# 参数验证
每个业务模块的 `dto.ts` 负责两件事:校验前端传来的 `body` / `query` / `params`,以及声明接口成功时的响应结构。校验不通过时,Elysia 会自动返回错误,不会进入 `handle.ts`。
`query`、`params`、`body` 的基础写法基于 Elysia 内置的 TypeBox,这里不展开。需要时可直接查阅官方文档:
- [Elysia 参数校验](https://elysiajs.com/essential/validation.html)
- [Elysia TypeBox](https://elysiajs.com/validation/typebox.html)
下文介绍项目封装的 DTO 工具,以及标准 CRUD 模块里最常见的写法。
## 在模块里怎么用
`dto.ts` 定义校验规则,`route.ts` 通过 `dto` 字段引用;服务启动时注册到 Elysia。请求到达后,先按规则校验参数,通过才进入 `handle.ts`。
```mermaid
flowchart TD
subgraph 启动时
D[dto.ts 定义 ListDto] --> R[route.ts 绑定 dto]
R --> Reg[注册到 Elysia]
end
subgraph 请求时
Req[HTTP 请求] --> Valid{参数校验}
Valid -->|通过| H[handle.ts]
Valid -->|不通过| Err[自动返回错误]
end
Reg -.-> Valid
```
`dto.ts` 导出规则,`route.ts` 只负责绑定:
```ts [dto.ts]
import { t } from 'elysia';
import { CrudDto } from '@/types/dto';
import { InsertBusinessGoods, SelectBusinessGoods } from '@database/schema/business_goods';
export const CreateDto = CrudDto.create(
InsertBusinessGoods,
SelectBusinessGoods,
['name', 'price']
);
export const ListDto = CrudDto.list(SelectBusinessGoods, {
name: t.Optional(t.String({ description: '商品名称' })),
});
```
```ts [route.ts]
import { CreateDto, ListDto } from './dto';
import { create, findList } from './handle';
const routes = [
{ url: '/business/goods', method: 'post', dto: CreateDto, handle: create, /* meta... */ },
{ url: '/business/goods/list', method: 'get', dto: ListDto, handle: findList, /* meta... */ },
];
```
更完整的模块搭建流程见 [第一个接口](./first-api)。
## 响应格式
项目接口统一返回 `{ code, msg, data }`。`BaseResultDto` 和 `BaseResultListDto` 用来在 OpenAPI 文档里声明这个结构,并约束 `data` 的类型。
**单条数据**
```json
{
"code": 200,
"msg": "success",
"data": {
"goodsId": 1,
"name": "示例商品",
"price": 99
}
}
```
**分页列表**
```json
{
"code": 200,
"msg": "success",
"data": {
"list": [
{ "goodsId": 1, "name": "示例商品" }
],
"total": 100
}
}
```
`handle.ts` 里用 `BaseResultData.ok(data)` 返回,格式会自动对齐 DTO 声明。
## BaseResultDto
声明「成功时 `data` 是某个对象」的响应。常与 `body` / `query` 写在同一个导出对象里。
```ts [dto.ts]
import { t } from 'elysia';
import { BaseResultDto } from '@/types/dto';
export const DetailDto = {
...BaseResultDto(t.Object({
goodsId: t.Number(),
name: t.String(),
price: t.Number(),
})),
};
```
`CrudDto.create` / `update` / `findOne` 内部已自动带上 `BaseResultDto`,一般不需要手写。
## BaseResultListDto
声明分页列表响应,`data.list` 为数组,`data.total` 为总数。
```ts [dto.ts]
import { t } from 'elysia';
import { BaseResultListDto } from '@/types/dto';
export const CustomListDto = {
query: t.Object({
pageNum: t.Number({ default: 1 }),
pageSize: t.Number({ default: 10 }),
keyword: t.Optional(t.String()),
}),
...BaseResultListDto(t.Object({
goodsId: t.Number(),
name: t.String(),
})),
};
```
`CrudDto.list` 已内置 `BaseResultListDto`,标准列表接口优先用它。
## BaseListQueryDto
列表查询的 `query` 基座,自带分页、排序、时间范围字段,再叠加业务筛选条件。
内置字段:
| 字段 | 说明 | 默认 |
|------|------|------|
| `pageNum` | 页码 | 1 |
| `pageSize` | 每页条数 | 10 |
| `orderByColumn` | 排序字段 | 可选 |
| `sortRule` | 排序规则 | 可选 |
| `startTime` | 开始时间 | 可选 |
| `endTime` | 结束时间 | 可选 |
```ts [dto.ts]
import { t } from 'elysia';
import { BaseListQueryDto } from '@/types/dto';
export const SearchDto = {
query: BaseListQueryDto({
name: t.Optional(t.String({ description: '商品名称' })),
status: t.Optional(t.Boolean({ description: '状态' })),
}),
};
```
`handle.ts` 里可直接解构 `ctx.query`,分页字段已经过校验。
## CrudDto
基于 Drizzle 的 `InsertXxx` / `SelectXxx` schema 快速生成标准 CRUD 的 DTO。新建模块时脚手架会生成骨架,你通常只需调整必填字段和额外查询项。
| 方法 | 用途 | 生成内容 |
|------|------|----------|
| `create` | 新增 | `body` + 单条响应 |
| `update` | 更新 | `body`(主键必填,其余可选)+ 单条响应 |
| `list` | 分页列表 | `query`(含分页)+ 列表响应 |
| `findAll` | 全量查询 | `query` + 数组响应 |
| `findOne` | 单条详情 | 单条响应 |
### create
指定哪些字段必填,其余字段从 `insertSchema` 自动变为可选。
```ts [dto.ts]
import { CrudDto } from '@/types/dto';
import { InsertBusinessGoods, SelectBusinessGoods } from '@database/schema/business_goods';
export const CreateDto = CrudDto.create(
InsertBusinessGoods,
SelectBusinessGoods,
['name', 'price', 'stock']
);
```
### update
主键必填,其他字段可选。日期字段 `createTime` / `updateTime` 接受 ISO 字符串。
```ts [dto.ts]
export const UpdateDto = CrudDto.update(SelectBusinessGoods, 'goodsId');
// 需要额外字段时传入第四个参数
export const UpdateDto = CrudDto.update(SelectBusinessGoods, 'goodsId', {
tags: t.Optional(t.Array(t.String())),
});
```
### list
分页查询,在 `BaseListQueryDto` 上叠加筛选字段。
```ts [dto.ts]
export const ListDto = CrudDto.list(SelectBusinessGoods, {
name: t.Optional(t.String({ description: '商品名称' })),
status: t.Optional(t.Boolean({ description: '状态' })),
});
```
敏感字段(如密码)可在传入前用 `t.Omit` 去掉:
```ts [dto.ts]
export const ListDto = CrudDto.list(
t.Omit(SelectSystemUser, ['password']),
{ username: t.Optional(t.String()) }
);
```
### findAll
不分页,返回全部匹配数据。`query` 只包含你传入的筛选字段。
```ts [dto.ts]
export const FindAllDto = CrudDto.findAll(SelectBusinessGoods, {
status: t.Optional(t.Boolean({ description: '状态' })),
});
```
### findOne
详情接口,只声明响应结构。路径参数 `id` 由路由 `url` 定义,不在 DTO 里重复写。
```ts [dto.ts]
export const FindOneDto = CrudDto.findOne(SelectBusinessGoods);
```

## 工具函数
### CreateUpdateDto
`CrudDto.update` 内部使用。需要手写更新 DTO、又不想逐个字段标 `Optional` 时,可以直接用它:主键必填,其余字段可选,日期字段按字符串处理。
```ts [dto.ts]
import { CreateUpdateDto } from '@/types/dto';
import { SelectBusinessGoods } from '@database/schema/business_goods';
export const CustomUpdateDto = {
body: CreateUpdateDto(SelectBusinessGoods, 'goodsId'),
};
```
### ParseDateFields
前端传来的 `createTime` / `updateTime` 是字符串,写入数据库前转成 `Date`。
```ts [handle.ts]
import { ParseDateFields } from '@/types/dto';
export async function update(ctx: AppContext) {
const data = ParseDateFields(ctx.body);
// data.createTime / data.updateTime 已是 Date 对象
}
```
## 非标准接口
不是所有接口都走 `CrudDto`。登录、改密码、自定义业务动作等,直接在 `dto.ts` 手写 `body` / `query` 即可。
```ts [dto.ts]
export const ChangePasswordDto = {
body: t.Object({
oldPassword: t.String({ description: '旧密码' }),
newPassword: t.String({ description: '新密码' }),
}),
};
```
```ts [route.ts]
{ url: '/system/user/password', method: 'put', dto: ChangePasswordDto, handle: updatePassword, meta: { /* ... */ } }
```
校验规则和响应声明保持在 `dto.ts`,`route.ts` 只做绑定——这和标准 CRUD 的做法一致。
---
URL: "https://elysia-admin.top/guide/payment.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\payment.md"
title: "支付集成 - Elysia Admin 指南"
description: "介绍 Elysia Admin 中支付宝、微信支付、PayPal 的统一支付适配层,包括 Pay 路由、商户配置、下单查询退款与异步回调,以及与商户配置表、业务支付模块的配合方式。"
---
---
title: 支付集成 - Elysia Admin 指南
description: 介绍 Elysia Admin 中支付宝、微信支付、PayPal 的统一支付适配层,包括 Pay 路由、商户配置、下单查询退款与异步回调,以及与商户配置表、业务支付模块的配合方式。
---
# 支付集成
对接支付宝、微信、PayPal 时,各家的 API、签名、回调格式都不一样。项目在 `server/src/infrastructure/clients/payment/` 封装了统一入口 `Pay(channel, platform)`,用「渠道 + 终端」路由到具体实现,对外暴露同一套 `create` / `query` / `refund` / `notify` / `notifySuccess` 方法。
类型定义在 `server/src/types/pay.ts`。业务模块 `business-payments` 已从 `business_merchant_configs` 读商户配置并调用 `Pay`,可作为参考实现。
```mermaid
flowchart LR
Biz[handle.ts] --> Pay["Pay(channel, platform)"]
Pay --> Reg[registry 解析]
Reg --> AP[alipay/*]
Reg --> WC[wechat/*]
Reg --> PP[paypal/*]
```
## 渠道与终端
`channel` 为支付渠道,`platform` 为终端类型。只有下表中打勾的组合才能调用,否则会抛出「不支持的支付组合」。
| channel | app | h5 | mini | pc |
|---------|:---:|:--:|:----:|:--:|
| alipay | ✅ | ✅ | ✅ | ✅ |
| wechat | ✅ | ✅ | ✅ | ✅ |
| paypal | ❌ | ✅ | ❌ | ✅ |
TypeScript 类型:`PaymentChannel`、`PaymentPlatform`(`@/types/pay`)。
## 快速入门
```ts
import { Pay } from '@/infrastructure/clients/payment';
import type { MerchantConfig } from '@/types/pay';
const client = Pay('alipay', 'mini');
```
| 方法 | 作用 |
|------|------|
| `create` | 发起支付,返回本地 `paymentNo` 与前端调起参数 `payload` |
| `query` | 主动查询支付状态 |
| `refund` | 发起退款 |
| `notify` | 解析异步回调(验签、解密) |
| `notifySuccess` | 返回各平台要求的成功应答字符串 |
各方法第一个参数均为 `MerchantConfig`,第二个为业务入参(`PaymentCreateParams`、`QueryParams` 等)。
## 商户配置
`MerchantConfig` 定义在 `@/types/pay`。私钥与密钥只存服务端,不要下发前端。
| 字段 | 说明 |
|------|------|
| `appId` | 支付宝/微信 AppID,或 PayPal Client ID |
| `mchId` | 微信商户号 |
| `privateKey` | 商户私钥 PEM,或 PayPal Client Secret |
| `publicKey` | 支付宝公钥 / 微信平台证书公钥(回调验签) |
| `config` | 扩展 JSON,各渠道专用字段见下文 |
多商户场景:从 `business_merchant_configs` 按 `merchantId`、`channel` 查出记录,把 `app_id`、`mch_id`、`private_key`、`public_key`、`config` 映射为 `MerchantConfig` 传入 `Pay`。
**支付宝私钥**:支持 PKCS#8(`BEGIN PRIVATE KEY`)与 PKCS#1(`BEGIN RSA PRIVATE KEY`);纯 Base64 无 PEM 头时会按 PKCS#1 自动补全。
**微信验签**:除顶层 `publicKey` 外,可在 `config.platformCerts` 按证书序列号维护多把平台公钥,对应回调头 `Wechatpay-Serial`,便于证书轮换。
### 支付宝 config
`buildAlipayRequest` 会读取 `notifyUrl`、`returnUrl`,至少配置异步通知地址。
```json
{
"notifyUrl": "https://your.domain/api/business/payments/notify",
"returnUrl": "https://your.domain/h5/pay/result"
}
```
- `notifyUrl`:支付结果异步通知,需外网 HTTPS 可访问
- `returnUrl`:同步跳转页(PC/H5 支付完成后回跳);纯 App API 可为空
若业务 `create` 入参里也传了 `notifyUrl` / `returnUrl`,请与库中配置保持一致,避免网关公共参数与下单入参矛盾。
### 微信 config
V3 接口**必须**有 `serialNo` 和 `apiV3Key`,缺省会直接报错。
```json
{
"serialNo": "7132D72A000000XXXXXXXXXXXXXXXXXX",
"apiV3Key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"refundNotifyUrl": "https://your.domain/api/business/payments/refund-notify",
"platformCerts": {
"序列号A": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----",
"序列号B": "-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----"
}
}
```
- `serialNo`:商户 API 证书序列号,用于请求头 `Authorization`
- `apiV3Key`:32 字节 APIv3 密钥,解密回调 `resource` 密文
- `refundNotifyUrl`:可选,退款结果通知
- `platformCerts`:可选,按序列号选公钥验签;未命中时用顶层 `publicKey`
下单 body 核心字段(`appid`、`mchid`、`out_trade_no`、`notify_url`、`amount`)不会被 `extra` 覆盖;`extra` 仅用于补充字段(如小程序 `openid`)。
### PayPal config
REST 用 `appId` + `privateKey`(Client ID / Secret)。回调验签依赖 Webhook ID。
```json
{
"webhookId": "1AB23C45DE6789012"
}
```
注意:
- `webhookId` 缺省将无法完成回调验签
- 当前 `providers/paypal/base.ts` 默认 API 根地址为**沙箱** `api-m.sandbox.paypal.com`;生产环境需改为 `https://api-m.paypal.com`,并使用 Live 密钥
- `PaymentCreateParams.notifyUrl` **不会**传给 PayPal 下单;支付结果靠 PayPal 后台 Webhook + 本项目 `notify` + `webhookId`
## 发起支付 create
```ts
const result = await Pay('alipay', 'mini').create(merchantConfig, {
orderNo: 'ORDER_20240101_001',
title: '商品名称',
description: '商品描述', // 可选
amount: '99.00', // 字符串,单位元;微信会安全转为分
currency: 'CNY', // 可选;PayPal 常用 USD
notifyUrl: 'https://your.domain/pay/notify',
returnUrl: 'https://your.domain/pay/return', // 可选
extra: {},
});
```
返回值 `PaymentCreateResult`:
| 字段 | 说明 |
|------|------|
| `paymentNo` | 本地支付单号,建议写入支付流水表 |
| `thirdTradeNo` | 部分渠道下单阶段即返回第三方单号 |
| `payload` | 交给前端调起支付,形态随渠道/终端变化 |
`paymentNo` 可选传入:业务已有流水号时带上;否则服务端生成。支付宝各端、微信各端、PayPal H5/PC 均支持。
### extra 常见必填
| 渠道 / 终端 | 字段 | 说明 |
|-------------|------|------|
| alipay + mini | `extra.buyerId` | 买家支付宝用户标识 |
| wechat + mini | `extra.openid` | 用户 openid,缺省报错 |
| wechat + h5 | `extra.clientIp` | 用户出口 IP,建议填写 |
| paypal + h5 | `extra.cancelUrl` | 取消支付跳转;不传则用 `returnUrl` |
### payload 形态摘要
| 终端 | 常见 payload |
|------|----------------|
| 支付宝 App | 原生 SDK 订单串 |
| 支付宝 H5 / PC | `{ payUrl }` |
| 支付宝小程序 | `{ tradeNo }` → `my.tradePay` |
| 微信 App / 小程序 | App 调起参数 / `wx.requestPayment` 参数 |
| 微信 H5 | `{ h5Url }` |
| 微信 PC | `{ codeUrl }` 扫码 |
| PayPal H5 | `{ approveUrl, orderId }` |
| PayPal PC | `{ orderId }` 供 JS SDK |
## 查询 query
```ts
const result = await Pay('wechat', 'mini').query(merchantConfig, {
paymentNo: 'LOCAL_PAYMENT_NO',
thirdTradeNo: '4200001...', // 依渠道含义不同
});
```
`result.status`:`'pending' | 'success' | 'failed' | 'closed'`。成功时关注 `paidAt`、`thirdTradeNo`。
| 渠道 | `thirdTradeNo` |
|------|----------------|
| 支付宝 / 微信 | 可选,有助于精确查询 |
| PayPal | **必填**,须为 PayPal Order ID(`payload.orderId`),不能仅用本地 `paymentNo` |
## 退款 refund
```ts
const result = await Pay('alipay', 'app').refund(merchantConfig, {
orderNo: 'ORDER_20240101_001',
paymentNo: 'LOCAL_PAYMENT_NO',
thirdTradeNo: '2024...',
refundNo: 'REFUND_20240101_001',
amount: '10.00',
totalAmount: '99.00',
reason: '用户申请退款',
extra: {
// PayPal:captureId 必填;currency 建议与捕获币种一致
},
});
```
- 微信:金额字符串解析为元再转分,避免浮点误差
- PayPal:`extra.captureId` 必填;`extra.currency` 建议填写,缺省 USD
## 异步回调
支付网关 POST 到你配置的 `notifyUrl`。路由里读取**原始 body** 和**请求头**,交给 `notify` 验签解析;业务校验金额与幂等后更新订单,用 `notifySuccess()` 返回值作为 HTTP 响应体。
微信特别注意:必须用原始 body 字符串,不要用已 `JSON.parse` 再 `stringify` 的对象代替。
```ts
// 推荐:从 request 读原始 body(适配所有渠道)
const rawBody = await request.text();
const headers: Record = {};
request.headers.forEach((v, k) => { headers[k] = v; });
const client = Pay('wechat', 'mini');
try {
const result = await client.notify(merchantConfig, { rawBody, headers });
if (result.status === 'success') {
// 更新订单、支付流水(注意幂等与金额校验)
}
return new Response(client.notifySuccess(), { status: 200 });
} catch {
return new Response('fail', { status: 400 });
}
```
成功响应格式(`notifySuccess()`):
| 渠道 | 返回值 |
|------|--------|
| alipay | `success` |
| wechat | `{"code":"SUCCESS","message":"成功"}` |
| paypal | 空字符串 `''`(HTTP 2xx 即可) |
验签失败应返回非 2xx,以便渠道按策略重试。
```mermaid
sequenceDiagram
participant GW as 支付网关
participant API as 业务路由
participant Pay as Pay(...).notify
GW->>API: POST 原始 body + headers
API->>Pay: notify(merchantConfig, ...)
Pay->>Pay: 验签 / 解密
Pay-->>API: NotifyResult
API->>GW: 200 + notifySuccess()
```
### PayPal 回调补充
- 验签依赖 `paypal-*` 请求头,适配层对大小写不敏感
- 支付成功类事件以 `PAYMENT.CAPTURE.*` 为主;`thirdTradeNo` 一般为 Capture ID
- 需要 Order ID 时看 `extra.paypalOrderId`
- `orderNo` 来自 `custom_id` / `invoice_id`;`paymentNo` 与 `reference_id` 对齐
## 完整示例:支付宝小程序
```ts
import { Pay } from '@/infrastructure/clients/payment';
import type { MerchantConfig } from '@/types/pay';
const merchantConfig: MerchantConfig = {
appId: '2021000000000000',
privateKey: '-----BEGIN PRIVATE KEY-----\n...\n-----END PRIVATE KEY-----',
publicKey: '-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----',
config: {
notifyUrl: 'https://your.domain/pay/notify/alipay',
returnUrl: 'https://your.domain/pay/return',
},
};
const { paymentNo, payload } = await Pay('alipay', 'mini').create(merchantConfig, {
orderNo: order.orderNo,
title: order.title,
amount: order.amount,
notifyUrl: 'https://your.domain/pay/notify/alipay',
extra: { buyerId: user.alipayUid },
});
// 1. 将 paymentNo 写入 business_payments 流水表
// 2. 将 payload.tradeNo 返回小程序,调用 my.tradePay({ tradeNO: payload.tradeNo })
// 回调路由(原始 body):
// const result = await Pay('alipay', 'mini').notify(merchantConfig, { rawBody, headers });
// return Pay('alipay', 'mini').notifySuccess();
```
业务侧完整流程(订单校验、商户配置、事务写流水)见 `server/src/modules/business-payments/handle.ts` 的 `payOrder` 与 `payOrderNotify`。
## 扩展新渠道
在 `server/src/infrastructure/clients/payment/providers/` 下新增目录,实现 `IPaymentProvider`(`@/types/pay`)。
在 `payment/index.ts` 的 `registry` 注册 `channel -> platform -> provider` 映射。
在 `server/src/types/pay.ts` 扩展 `PaymentChannel` 联合类型。
完成后即可 `Pay('新渠道', '某平台')` 获得统一调用方式。
## 常见问题
- **不支持的支付组合**:对照渠道终端表,PayPal 不支持 app/mini
- **微信 API 报错缺 serialNo / apiV3Key**:检查 `config` JSON 是否完整
- **回调验签失败**:微信用原始 body;支付宝公钥是否配对;PayPal `webhookId` 是否与后台一致
- **PayPal 沙箱/生产混用**:沙箱密钥不能打生产网关
- **金额不一致**:支付金额以数据库订单为准,不要用前端传来的金额(见 `business-payments` 注释)
---
URL: "https://elysia-admin.top/guide/queue.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\queue.md"
title: "队列 - Elysia Admin 指南"
description: "详细介绍 Elysia Admin 基于 BullMQ 的队列系统,包括任务投递、延迟任务、优先级、幂等投递、自定义队列、重试策略、限流控制及多实例高可用部署。"
---
---
title: 队列 - Elysia Admin 指南
description: 详细介绍 Elysia Admin 基于 BullMQ 的队列系统,包括任务投递、延迟任务、优先级、幂等投递、自定义队列、重试策略、限流控制及多实例高可用部署。
---
# 队列
有些业务不适合在 HTTP 请求里同步做完,比如发邮件、订单超时取消、批量导出。这类任务可以丢进队列,由后台 Worker 异步执行,接口快速返回,用户不用干等。
项目基于 **BullMQ Sandboxed Processors** 实现:主进程负责接收请求并投递任务;Redis 存队列;独立 Worker 进程取任务后,在**子进程**里跑 Processor,避免阻塞 HTTP 事件循环。
定时任务也走同一套设施(`system-cron-queue`),区别只是触发方式。沙箱注册与后台配置见 [定时任务](./cron)。
## 架构概览
先把进程边界搞清楚,后面自定义队列时才知道文件该写在哪。
```mermaid
flowchart TD
subgraph 主进程 server/src/index.ts
HTTP[HTTP 请求] --> AddJob[queueManager.addJob]
AddJob --> Redis[(Redis)]
end
subgraph Worker 进程 runtime/worker.ts
Redis --> Worker[Worker 消费]
Worker --> Spawn[spawn 子进程]
Spawn --> Proc[dist/processors/*.js]
end
subgraph 子进程内
Proc --> Sandbox[worker-sandbox 任务注册]
Sandbox --> Biz[业务函数 / handle]
end
Biz --> DB[(数据库 / Redis)]
```
三层隔离:
| 层级 | 说明 |
|------|------|
| 主进程 vs Worker | Worker 挂了,网站照常响应 |
| Worker vs Processor 子进程 | 某个任务崩溃,不影响同队列其他任务 |
| 子进程互相隔离 | 耗时计算不会卡住 Worker 事件循环 |
关键目录:
| 路径 | 作用 |
|------|------|
| `infrastructure/queue/queues/*/queue.ts` | 注册队列(主进程加载) |
| `infrastructure/queue/queues/*/worker.ts` | 注册 Worker(Worker 进程加载) |
| `infrastructure/queue/queues/*/processor.ts` | 子进程入口,构建到 `dist/processors/` |
| `worker-sandbox/*-tasks.ts` | 业务函数注册,可 import `modules` |
| `runtime/app.ts` / `runtime/worker.ts` | 汇总 import 各队列 |
## 启动服务
本地开发至少要有一个 Worker 在跑,否则任务只会堆在 Redis 里没人消费。
```bash
cd server
# 推荐:HTTP + Worker 一起启
bun run dev:all
# 或分开启(调试 Worker 时常用)
bun run dev # 仅主进程
bun run dev:workers # 仅 Worker(内部会先执行 build:processors)
```
改动了 `processor.ts` 或 `worker-sandbox/*.ts` 后,必须重新构建 Processor,否则子进程跑的仍是旧代码:
```bash
bun run build:processors
# 然后重启 Worker(dev:workers 或 PM2)
```
## 投递任务
所有投递通过 `queueManager` 完成,在 `handle.ts`、定时逻辑等主进程代码里均可调用。
```ts
import { queueManager } from '@/infrastructure/queue';
```
项目内置三个队列,可以先对照理解再自定义:
| 队列名 | 用途 | Processor 模式 |
|--------|------|----------------|
| `system-cron-queue` | 定时任务 | `taskName` + `jobArgs` |
| `flow-buffer-queue` | 延迟/缓冲类业务 | 按 `job.name` 分发 |
| `trade-order-queue` | 订单示例(演示用) | 直接读 `job.data` |
### 基本投递
最常用的写法,把业务数据放进 `job.data`,Worker 在子进程里读取。
```ts
await queueManager.addJob('trade-order-queue', {
orderId: 'ORD-001',
action: 'pay',
orderData: { amount: 100 },
});
```
`trade-order` 的 Processor 会根据 `action` 分支处理(`create` / `pay` / `cancel` / `refund`),可在 `queues/trade-order/processor.ts` 查看示例。
### 延迟任务
`delay` 单位为毫秒。适合「下单后 30 分钟未支付自动取消」这类场景。
```ts
// 30 分钟后执行
await queueManager.addJob('trade-order-queue', {
orderId: 'ORD-001',
action: 'cancel',
}, {
delay: 30 * 60 * 1000,
});
```
项目里订单模块的真实写法(`flow-buffer-queue`,按 `job.name` 分发):
```ts
// server/src/modules/business-orders/handle.ts(节选)
const queue = queueManager.getQueue('flow-buffer-queue')!;
await queue.add('订单超时处理', { orderNo }, { delay: timeout });
```
这里的 `'订单超时处理'` 必须与 `worker-sandbox/flow-buffer-tasks.ts` 里 `register('订单超时处理', ...)` 的名称一致。
### 优先级
数字**越小**优先级**越高**,`1` 为最高。多任务积压时,高优先级先被消费。
```ts
await queueManager.addJob('flow-buffer-queue', data, {
priority: 1,
});
```
### 幂等投递
传入 `jobId` 后,相同 ID 的任务不会重复入队。适合防止用户重复点击、接口重试导致重复扣款。
```ts
const orderId = 'ORD-001';
await queueManager.addJob('trade-order-queue', data, {
jobId: `pay-${orderId}`,
});
```
### 批量投递
一次塞多条任务,比循环 `addJob` 更高效。
```ts
await queueManager.addBulkJobs('flow-buffer-queue', [
{ data: { action: 'write', payload: { userId: 1 } } },
{ data: { action: 'write', payload: { userId: 2 } } },
{ data: { action: 'write', payload: { userId: 3 } } },
]);
```
### 控制保留数量
避免 Redis 里堆积过多历史任务记录。
```ts
await queueManager.addJob('system-cron-queue', data, {
removeOnComplete: 100, // 完成后只留最近 100 条
removeOnFail: 200, // 失败后只留最近 200 条
});
```
## 定时调度
除了后台「定时任务」页面配置外,也可以在代码里用 `schedule` 注册 Cron。规则存在 Redis 中,服务重启后自动恢复,不用每次启动重新注册。
```ts
import { queueManager, schedule, removeSchedule } from '@/infrastructure/queue';
const queue = queueManager.getQueue('system-cron-queue')!;
// 注册:每天凌晨 2 点
await schedule(queue, 'daily-cleanup', {
cron: '0 2 * * *',
data: {
taskName: 'cleanup', // 对应 worker-sandbox 注册名
jobArgs: JSON.stringify([30]), // 传给任务函数的参数数组
},
});
// 移除
await removeSchedule(queue, 'daily-cleanup', '0 2 * * *');
```
常用 Cron 表达式:
| 表达式 | 说明 |
|--------|------|
| `* * * * *` | 每分钟 |
| `*/5 * * * *` | 每 5 分钟 |
| `0 * * * *` | 每小时整点 |
| `0 2 * * *` | 每天凌晨 2 点 |
| `0 2 * * 1` | 每周一凌晨 2 点 |
| `0 2 1 * *` | 每月 1 日凌晨 2 点 |
建议用 [Cron 在线工具](https://tool.lu/crontab/) 校验。`taskName` 与 `worker-sandbox` 注册、后台表单「任务名称」须完全一致,详见 [定时任务](./cron)。
## 自定义队列
下面以新增 `email-notify-queue`(邮件通知)为例,从零走一遍。自定义队列本质是**新增一组文件 + 改三处注册 + 构建**。
整体 checklist:
```
queues/email-notify/queue.ts ← 注册队列
queues/email-notify/processor.ts ← 子进程逻辑
queues/email-notify/worker.ts ← 注册 Worker
worker-sandbox/email-notify-tasks.ts ← 业务函数(可 import modules)
runtime/app.ts ← import queue
runtime/worker.ts ← import worker
```
### 注册队列
主进程启动时加载,用于 `addJob` 投递。
```ts
// server/src/infrastructure/queue/queues/email-notify/queue.ts
import { queueManager } from '../../core';
export default queueManager.registerQueue({
name: 'email-notify-queue',
description: '邮件通知队列',
});
```
### 沙箱任务
**不要**在 `processor.ts` 里直接 `import '@/modules/...'`。业务函数写在 `worker-sandbox`,保持 infrastructure 与 modules 的依赖边界(与定时任务相同)。
```ts
// server/src/worker-sandbox/email-notify-tasks.ts
import type { TaskFn } from '@/infrastructure/queue/core/processor-utils';
async function sendEmail(to: string, subject: string, body: string) {
console.log(`发送邮件到 ${to}: ${subject}`);
// 实际项目可 import 模块 handle:import { sendMail } from '@/modules/xxx/handle';
}
export function registerEmailNotifySandboxTasks(
register: (name: string, fn: TaskFn) => void,
): void {
register('sendEmail', sendEmail as TaskFn);
}
```
### Processor
构建后输出到 `dist/processors/email-notify.js`,Worker 以沙箱方式 spawn 执行。
```ts
// server/src/infrastructure/queue/queues/email-notify/processor.ts
import type { SandboxedJob } from 'bullmq';
import { registerEmailNotifySandboxTasks } from '@/worker-sandbox/email-notify-tasks';
import { createTaskRegistry, parseArgs } from '../../core/processor-utils';
const { register, get } = createTaskRegistry();
registerEmailNotifySandboxTasks(register);
export default async function processor(job: SandboxedJob) {
const { taskName, jobArgs } = job.data;
const taskFn = get(taskName);
if (!taskFn) throw new Error(`未找到任务: ${taskName}`);
await taskFn(...parseArgs(jobArgs));
return { success: true, taskName };
}
```
子进程可以正常连数据库、Redis,但**拿不到**主进程内存里的单例(如已建立的连接对象),会在子进程内重新初始化。
### Worker
指定并发数、限流等运行参数。
```ts
// server/src/infrastructure/queue/queues/email-notify/worker.ts
import path from 'path';
import { queueManager, RateLimitPresets } from '../../core';
const processorFile = path.resolve(process.cwd(), 'dist/processors/email-notify.js');
queueManager.registerWorker({
queueName: 'email-notify-queue',
processor: processorFile,
options: {
concurrency: 5, // 同时处理 5 个任务
...RateLimitPresets.low, // 每秒最多 5 个(见下文「限流」)
},
});
```
### 挂载与构建
`runtime/app.ts` 增加:
```ts
import '../queues/email-notify/queue';
```
`runtime/worker.ts` 增加:
```ts
import '../queues/email-notify/worker';
```
新增 `queues/{name}/processor.ts` 后,`discoverProcessorEntries()` 会在 `bun run build` / `bun run build:processors` 时自动纳入构建,无需改构建脚本。
构建并投递测试任务:
```bash
bun run build:processors
bun run dev:workers
```
```ts
await queueManager.addJob('email-notify-queue', {
taskName: 'sendEmail',
jobArgs: JSON.stringify(['user@example.com', '验证码', '您的验证码是 123456']),
});
```
到 Bull Board 面板确认任务从 waiting → completed。
### 任务数据格式
两种 Processor 模式**不要混用**,新手最容易在这里踩坑:
**模式 A:`taskName` + `jobArgs`**
用于 `system-cron-queue` 和上文 `email-notify` 自定义队列。
```ts
// 投递
await queueManager.addJob('email-notify-queue', {
taskName: 'sendEmail',
jobArgs: JSON.stringify(['a@b.com', '标题', '正文']),
});
// Processor 内
const { taskName, jobArgs } = job.data;
const taskFn = get(taskName);
await taskFn(...parseArgs(jobArgs));
```
**模式 B:按 `job.name` 分发**
用于 `flow-buffer-queue`。投递时第一个参数是任务名,`data` 整包传给函数。
```ts
// 投递(注意用 queue.add,第一个参数是 job.name)
const queue = queueManager.getQueue('flow-buffer-queue')!;
await queue.add('订单超时处理', { orderNo: 'xxx' }, { delay: 60000 });
// Processor 内(flow-buffer/processor.ts)
const taskFn = get(job.name);
await taskFn(job.data);
```
| 队列 | 投递方式 | Processor 取任务 |
|------|----------|------------------|
| `system-cron-queue` | `addJob` + `taskName` | `job.data.taskName` |
| 自定义(email-notify 示例) | 同上 | 同上 |
| `flow-buffer-queue` | `queue.add(name, data)` | `job.name` |
| `trade-order-queue` | `addJob` + 业务字段 | 直接读 `job.data` |
## 重试策略
任务失败时 BullMQ 会自动重试。投递时通过选项控制策略,也可用项目预设。
```ts
import { RetryPresets, buildRetry } from '@/infrastructure/queue';
// 预设:指数退避,最多 5 次
await queueManager.addJob('trade-order-queue', data, {
...RetryPresets.aggressive,
});
```
| 预设 | 策略 | 次数 | 初始延迟 |
|------|------|------|----------|
| `RetryPresets.none` | 不重试 | 1 | — |
| `RetryPresets.standard` | 固定间隔 | 3 | 2s |
| `RetryPresets.aggressive` | 指数退避 | 5 | 1s |
自定义:
```ts
// 固定间隔:失败后等 5 秒重试,最多 3 次
await queueManager.addJob('email-notify-queue', data, {
...buildRetry({ attempts: 3, strategy: 'fixed', delay: 5000 }),
});
// 指数退避:1s → 2s → 4s → 8s,最多 4 次
await queueManager.addJob('trade-order-queue', data, {
...buildRetry({ attempts: 4, strategy: 'exponential', delay: 1000 }),
});
```
对外部 API(发邮件、发短信)建议配合 `RateLimitPresets.low`,避免重试风暴把第三方打挂。
## 限流控制
限流在 **Worker 注册时**配置,控制单位时间内最多处理多少个任务,与投递时的 `addJob` 无关。
```ts
import { RateLimitPresets, buildRateLimit } from '@/infrastructure/queue';
queueManager.registerWorker({
queueName: 'email-notify-queue',
processor: processorFile,
options: {
concurrency: 5, // 同时并发处理的任务数
...RateLimitPresets.low, // 每秒最多 5 个
},
});
```
| 预设 | 每秒上限 | 适用场景 |
|------|----------|----------|
| `RateLimitPresets.low` | 5 | 邮件、短信等外部 API |
| `RateLimitPresets.medium` | 20 | 普通业务处理 |
| `RateLimitPresets.high` | 100 | 高并发写入缓冲 |
自定义:
```ts
options: {
concurrency: 10,
...buildRateLimit({ max: 50, duration: 2000 }), // 2 秒内最多 50 个
}
```
`concurrency` 是「同时跑几个」,`limiter` 是「单位时间最多跑几个」,两者同时生效。发邮件队列通常 `concurrency` 略高、`limiter` 偏低。
## Processor 开发
### 日志与进度
在 Bull Board 里可查看任务日志和进度条,调试时很有用。
```ts
export default async function processor(job: SandboxedJob) {
await job.log('开始处理...');
await job.updateProgress(30);
// 业务逻辑...
await job.updateProgress(100);
await job.log('处理完成');
return { success: true };
}
```
### 子进程里投递新任务
子进程**不能**用主进程的 `queueManager` 单例,需要自行创建 `Queue`:
```ts
import { Queue } from 'bullmq';
import Redis from 'ioredis';
import { getQueueEnvConfig } from '../../config/env';
const cfg = getQueueEnvConfig();
const conn = new Redis({
host: cfg.redis.host,
port: cfg.redis.port,
maxRetriesPerRequest: null,
});
// 注意队列名带 appId 前缀
const notifyQueue = new Queue(`${cfg.appId}-email-notify-queue`, { connection: conn });
await notifyQueue.add('sendEmail', {
taskName: 'sendEmail',
jobArgs: JSON.stringify(['user@example.com', '通知', '处理完成']),
});
```
### 修改后记得构建
```bash
bun run build:processors
```
## 监控面板
登录 Admin 后台,进入 **系统监控 → 队列监控**(路径 `/monitor/bullmq`)。
面板支持:
- 查看各队列 waiting / active / completed / failed / delayed 统计
- 按状态浏览任务、查看详情与运行日志
- 延迟任务倒计时与立即执行(promote)
- 单任务 / 批量重试失败任务、删除、清空 completed/failed
- 可选 3 秒 / 5 秒自动刷新
任务一直停在 waiting,优先检查 Worker 是否在跑(`bun run dev:workers` 或 PM2 里的 worker 进程)。
## 部署与高可用
- BullMQ 通过 Redis 协调,同一任务只会被一个 Worker 消费,多实例部署一般不需要额外分布式锁
- 建议 HTTP 与 Worker 分开用 PM2 管理;Worker 崩溃重启不影响 API
- 任务数据在 Redis 中持久化,重启后未完成的任务会继续被消费
## 常见问题
- **任务不执行**:Worker 没启动,或改了 Processor 没 `build:processors`
- **未找到任务**:`taskName` / `job.name` 与 `worker-sandbox` 注册名不一致
- **两种数据格式混用**:`flow-buffer` 用 `job.name`,cron 和自定义队列用 `taskName`,见上文对照表
- **子进程报错连不上 DB**:子进程会自行初始化连接,检查环境变量与 Redis/Postgres 是否可达
## 参考
- [BullMQ 官方文档](https://docs.bullmq.io/)
- [Sandboxed Processors](https://docs.bullmq.io/guide/workers/sandboxed-processors)
- [Repeatable Jobs](https://docs.bullmq.io/guide/jobs/repeatable)
- [Rate Limiting](https://docs.bullmq.io/guide/rate-limiting)
- [Retrying Jobs](https://docs.bullmq.io/guide/retrying-failing-jobs)
- [Cron 表达式工具](https://tool.lu/crontab/)
- [ioredis](https://github.com/redis/ioredis)
---
URL: "https://elysia-admin.top/guide/site-deploy.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\site-deploy.md"
title: "站点部署 - Elysia Admin 指南"
description: "介绍后台站点部署:维护域名与反代路径、预览 Nginx 配置、用 Let's Encrypt HTTP-01 申请与续签证书。"
---
---
title: 站点部署 - Elysia Admin 指南
description: 介绍后台站点部署:维护域名与反代路径、预览 Nginx 配置、用 Let's Encrypt HTTP-01 申请与续签证书。
---
# 站点部署
后台「站点部署」用来维护本机 Nginx 站点:保存域名与反代路径,预览配置文本,并用 Let's Encrypt 申请证书。把 Elysia Admin 自身用 Docker 或 PM2 跑起来,见 [部署](/guide/deploy)。
代码在 `server/src/modules/system-deploy/`。站点表 `system_deploy_site`、证书记录表 `system_deploy_cert`,字段见 [数据库设计](/architecture/database)。
```mermaid
flowchart TD
Form[保存站点] --> Preview[预览 Nginx 配置]
Preview --> Panel[自行写入面板或 vhost]
Form --> Renew[立即续签或定时续签]
Renew --> ACME[HTTP-01 校验]
ACME --> Pem[写入 fullchain.pem 与 privkey.pem]
Pem --> Reload[可选 nginx -s reload]
```
## 前置条件
证书申请和 Nginx 重载都在**运行后端的那台机器**上执行。
- 本机可执行 `openssl`(生成账户密钥与证书请求)和 `nginx`(仅在开启自动重载时调用 `nginx -s reload`)
- 域名的 80 端口能访问到该机,Let's Encrypt 用 HTTP-01 读取 `/.well-known/acme-challenge/`
- 进程对网站校验目录、证书目录可写
- 定时续签依赖 Worker。任务名必须是「站点证书续签」,种子数据中的 Cron 为 `15 3 * * *`(每天 03:15)。库里没有这条任务时,在「定时任务」里按该名称新增并启用。详见 [定时任务](/guide/cron)
ACME 账户密钥写在进程工作目录的 `storage/certs/account.pem`,账户 URL 写在同目录的 `account.url`。
## 站点字段
未删除站点的域名唯一。站点名只允许字母、数字、下划线和连字符,且以字母或数字开头(宝塔路径会拼进文件名)。
| 字段 | 说明 |
|------|------|
| 站点名 `name` | 用于宝塔 include、日志文件名 |
| 域名 `serverName` | Nginx `server_name` |
| 网站根目录 `wwwRoot` | 静态站点(管理端等)默认 `root` |
| 校验目录 `webRoot` | HTTP-01 文件根目录,默认进程工作目录 |
| 证书目录 `certDir` | 默认 `storage/certs/<域名>`,其下为 `fullchain.pem`、`privkey.pem` |
| 邮箱 | 开启自动续签时必填,作为 ACME 账户联系邮箱 |
| 自动续签 `sslAutoRenew` | 交给定时任务扫描 |
| 自动重载 `reloadNginx` | 续签成功后执行 `nginx -s reload`;关闭时需要自己到面板重载 |
| 宝塔目录 `useBaota` | 默认开启。配置中带宝塔 `vhost` include 与 `/www/wwwlogs/` 日志路径 |
| HTTP 跳 HTTPS `httpRedirect` | 仅出现在含证书的那份配置里;`/.well-known/` 不跳转 |
| 反代 `locations` | 路径列表,见下一节 |
校验文件的绝对路径是 `/.well-known/acme-challenge`。预览接口会把 `challengeDir`、`certDir` 一并返回,详情和列表卡片也会带上解析后的绝对路径。
## 反代路径
未传 `locations` 时使用四条预设;传了数组则按数组保存,可以是空数组。
| kind | 默认 | 行为 |
|------|------|------|
| `home` | `/` → `127.0.0.1:4002` | 反代 |
| `admin` | `/admin`,静态目录取 `wwwRoot` | `try_files` 回退到 `index.html` |
| `api` | `/api` → `127.0.0.1:4003` | 反代 |
| `rustfs` | `/zqwz/` → `127.0.0.1:9000`,上传限制 `50m` | 反代,`Host` 使用 `$host` |
| `custom` | 自填路径 | `proxy` 反代到端口,或 `static` 指向目录 |
路径以 `/` 开头,不能含 `..` 和换行等特殊字符,同一站点内不能重复。更长的路径写在更前面,避免被短前缀截走。RustFS 只反代到本机,不要把 9000 端口暴露到公网。
若当前启用的存储配置名称是 `RustFS`,站点表单通过 `GET /api/system/storage/rustfs-bucket` 读取桶名,用作默认反代路径。存储配置见 [文件存储](/guide/storage)。
## 预览配置
`POST /system/deploy/preview/:id` 只返回文本,不写 Nginx 文件。一次给出两份:
- `httpOnly`:只监听 80,申请证书前先让校验目录可访问
- `ssl`:额外监听 `443 ssl` 与 `443 quic`,引用证书目录里的 `fullchain.pem`、`privkey.pem`
把对应文本放到面板或 vhost 后,由你自己重载 Nginx。宝塔开关打开时,配置会 include:
- `/www/server/panel/vhost/nginx/extension/<站点名>/*.conf`
- `/www/server/panel/vhost/nginx/well-known/<站点名>.conf`
- `/www/server/panel/vhost/rewrite/node_<站点名>.conf`
这些文件不存在时,Nginx 检测会失败,需要在面板里先有同名站点,或关掉「宝塔目录」再预览。
## 申请与续签
签发走 Let's Encrypt 生产目录 `https://acme-v02.api.letsencrypt.org/directory`。每次续签都新建订单,有效期以返回的证书为准。成功后原子替换 `fullchain.pem` 与 `privkey.pem`(失败会留住旧文件),并写入 `system_deploy_cert`。同一站点只有一条 `isCurrent = true` 的未删除记录。
立即续签是 `POST /system/deploy/renew/:id`。定时任务调用同一逻辑,但只处理同时满足这两点的站点:`sslAutoRenew` 为 true,且没有 `certExpireAt` 或距离到期不足 30 天。
手动续签与定时扫描共用 Redis 锁 `deploy-cert-renew`(约 10 分钟)。锁被占用时,手动接口返回「正在续签,请稍后再试」,定时任务直接跳过。续签失败会把原因写到站点的「最近续签结果」和证书记录,不中断其他站点。
开启了自动重载且续签成功时,才会执行 `nginx -s reload`。命令不存在或返回非 0 时,接口仍视为证书已更新,文案里会提示到面板手动重载。
## 权限
路由都要求登录,权限码如下。
| 权限 | 接口 |
|------|------|
| `system:deploy:query` | 列表、全部、详情 |
| `system:deploy:create` | 创建 |
| `system:deploy:update` | 更新 |
| `system:deploy:delete` | 软删除 |
| `system:deploy:preview` | 生成配置 |
| `system:deploy:renew` | 立即续签 |
| `system:deploy:cert` | 证书记录 |
---
URL: "https://elysia-admin.top/guide/storage.html"
LLMS_URL: "https://elysia-admin.top/\\guide\\storage.md"
title: "文件存储 - Elysia Admin 指南"
description: "介绍 Elysia Admin 的文件存储方案,包括 RustFS 安装、S3 对象存储配置、预签名 URL 直传流程,以及 Web 端和 uni-app 小程序端的上传实现。"
---
---
title: 文件存储 - Elysia Admin 指南
description: 介绍 Elysia Admin 的文件存储方案,包括 RustFS 安装、S3 对象存储配置、预签名 URL 直传流程,以及 Web 端和 uni-app 小程序端的上传实现。
---
# 文件存储
项目通过 **预签名 URL** 实现文件直传:客户端先把文件 PUT 到对象存储,后端只负责签发短时 URL,不经手文件流。
支持所有兼容 S3 协议的服务,包括 RustFS(自建)、阿里云 OSS、腾讯云 COS、七牛 Kodo 等。无法采购云服务时,可用 Docker 快速部署 RustFS。
## 为什么用对象存储
文件存本地磁盘会带来迁移成本高、难横向扩展、占用后端带宽和磁盘 I/O、缺少容灾与生命周期管理等问题。对象存储把这些能力交给专用服务,后端只保存文件 URL。
## 部署 RustFS
RustFS 是兼容 S3 的开源对象存储,Docker 一键启动:
```bash
docker run -d --name rustfs_container --user root \
-p 9000:9000 -p 9001:9001 \
-v /mnt/rustfs/data:/data \
-e RUSTFS_ACCESS_KEY=rustfsadmin \
-e RUSTFS_SECRET_KEY=rustfsadmin \
-e RUSTFS_CONSOLE_ENABLE=true \
rustfs/rustfs:latest \
--address :9000 --console-enable \
--access-key rustfsadmin --secret-key rustfsadmin /data
```
| 端口 | 用途 |
|------|------|
| 9000 | S3 API,上传与下载 |
| 9001 | Web 管理控制台 |
默认凭据:`rustfsadmin` / `rustfsadmin`。生产环境务必修改 Access Key 和 Secret Key。
## 后台配置
登录后台,进入 **系统管理 → 存储配置**,填写 Endpoint、Bucket、Access Key、Secret Key 等信息后启用。

注意:
- 系统内置主流服务商模板,可快速填写
- 同一时间只能启用一种存储配置;启用新配置会自动禁用旧的
- 站点部署在填写 RustFS 反代路径时,会请求 `GET /api/system/storage/rustfs-bucket`,读取当前启用且名称为 `RustFS` 的桶名。详见 [站点部署](/guide/site-deploy)
| 字段 | 说明 |
|------|------|
| 名称 | 供应商标识,如 RustFS、OSS、COS |
| Region | 服务区域;RustFS 可填 `us-east-1` |
| Endpoint | API 访问地址,不含 bucket 前缀(OSS 由代码自动拼接) |
| Bucket | 存储桶名称 |
| Access Key / Secret Key | 访问密钥 |
### 配置示例
**RustFS**
```json
{
"name": "RustFS",
"region": "us-east-1",
"endpoint": "http://192.168.1.100:9000",
"bucket": "public",
"access_key": "rustfsadmin",
"secret_key": "rustfsadmin"
}
```
**腾讯云 COS**
```json
{
"name": "COS",
"region": "ap-nanjing",
"endpoint": "cos.ap-nanjing.myqcloud.com",
"bucket": "my-bucket-1234567890",
"access_key": "AKIDxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"secret_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
`bucket` 格式为 `<桶名>-`,密钥在「访问管理 → API 密钥管理」获取。
**阿里云 OSS**
```json
{
"name": "OSS",
"region": "cn-hangzhou",
"endpoint": "oss-cn-hangzhou.aliyuncs.com",
"bucket": "my-bucket",
"access_key": "LTAI5txxxxxxxxxxxxxxxxxx",
"secret_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
`endpoint` 不含桶名前缀,代码内部会拼成 `.`。
**七牛 Kodo**
```json
{
"name": "Kodo",
"region": "",
"endpoint": "https://your-bucket.qiniudn.com",
"bucket": "my-kodo-bucket",
"access_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"secret_key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
`endpoint` 填存储桶访问域名,`region` 可留空。
## 上传流程
```mermaid
flowchart TD
A[选择文件] --> B[GET /api/system/storage/presign]
B --> C[返回预签名 URL]
C --> D[客户端 PUT 直传对象存储]
D --> E[取 URL 去掉查询参数]
E --> F[写入业务表单]
```
后端接口 `GET /api/system/storage/presign?fileName=xxx` 会读取当前启用的存储配置,调用 `StorageService.getPresignedUrl` 签发 URL。前端封装为 `fetchGeneratePresign`(`admin/src/api/system/storage.ts`),`ArtForm` 上传组件已内置这套流程。
直传的优势:不占后端带宽、支持大文件、后端只做轻量签名。
## Web 端
浏览器用原生 `fetch` 即可完成,与 `ArtForm` 中的实现一致:
```ts
import { fetchGeneratePresign } from '@/api/system/storage'
const presignUrl = await fetchGeneratePresign({ fileName: file.name })
const response = await fetch(presignUrl, {
method: 'PUT',
body: file,
headers: { 'Content-Type': file.type || 'application/octet-stream' },
})
// 持久化地址:去掉签名查询参数
const fileUrl = presignUrl.split('?')[0]
```
## uni-app / 小程序
小程序环境不能直接把 `File` 对象 PUT 出去,需要先用 `getFileSystemManager` 读出 `ArrayBuffer`,再调用后端获取预签名 URL,最后用 `uni.request` 以 `PUT` 上传。下面是一份可直接复制使用的完整示例,`generatePreSignUrl` 需对接你的 presign 接口(与 Web 端 `fetchGeneratePresign` 等价)。
```ts [ts]
/**
* 通过文件名称检测文件类型返回对应的 Content-Type
*/
function getContentTypeByFileName(fileName: string) {
const ext = fileName.split('.').pop()?.toLowerCase();
if (!ext || ext === fileName) return 'application/octet-stream';
switch (ext) {
case 'jpg':
case 'jpeg': return 'image/jpeg'
case 'png': return 'image/png'
case 'webp': return 'image/webp';
case 'svg': return 'image/svg+xml';
case 'gif': return 'image/gif'
case 'mp4': return 'video/mp4'
case 'mp3': return 'audio/mpeg';
case 'avi': return 'video/x-msvideo'
case 'mov': return 'video/mov'
case 'pdf': return 'application/pdf'
case 'docx': return 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
case 'xlsx': return 'application/vnd.ms-excel'
case 'txt': return 'text/plain'
case 'csv': return 'text/csv'
case 'wav': return 'audio/wav';
case 'ogg': return 'audio/ogg';
case 'html': return 'text/html';
case 'css': return 'text/css';
case 'js': return 'application/javascript';
case 'json': return 'application/json'
case 'xml': return 'application/xml'
case 'zip': return 'application/zip'
case 'rar': return 'application/x-rar-compressed'
case '7z': return 'application/x-7z-compressed'
case 'tar': return 'application/x-tar'
case 'gz': return 'application/x-gzip'
default: return 'application/octet-stream'
}
}
/**
* 通过 url 获取文件名称
*/
function getFileNameByUrl(url: string) {
let fileName = url.split('/').pop() || ''
const ext = fileName.split('.').pop()
fileName = fileName.slice(0, 10)
fileName = fileName.replace(/[^\w]/g, '_')
return fileName + (ext ? '.' + ext : '')
}
/** 文件读成arrayBuffer */
const fsm = uni.getFileSystemManager();
async function readFileAsBuffer(url: string): Promise {
return new Promise((resolve, reject) => {
fsm.readFile({
filePath: url,
success(res) {
resolve(res.data as ArrayBuffer)
},
fail(err) {
console.error('文件读取失败:', err)
reject(err)
},
})
})
}
/** S3预签名单文件上传 */
export function runOne(url: string): Promise {
return new Promise(async (resolve, reject) => {
const fileName = getFileNameByUrl(url)
const contentType = getContentTypeByFileName(fileName)
const arrayBuffer = await readFileAsBuffer(url)
const preSignUrl = await generatePreSignUrl(fileName)
const fileUrl = preSignUrl.split('?')[0]
uni.request({
url: preSignUrl,
method: 'PUT',
header: { 'Content-Type': contentType, },
data: arrayBuffer,
success(res) {
if (res.statusCode === 200) resolve(fileUrl)
},
fail(err) {
console.error('上传文件失败:', err)
reject(err)
},
})
})
}
/**
* 简易版通用S3预签名文件上传
*/
export function uploadFileToS3() {
/** 图片上传 */
function image(options?: any): Promise {
return new Promise((resolve, reject) => {
uni.chooseImage({
success: async (result: UniApp.ChooseImageSuccessCallbackResult) => {
const paths = result.tempFilePaths
const tempFilePaths: string[] = typeof paths === 'string' ? [paths] : [...paths]
const fileUrls = await Promise.all(tempFilePaths.map(runOne))
const formate = fileUrls.length > 1 ? fileUrls : fileUrls[0]
resolve(formate)
},
fail(err: UniApp.GeneralCallbackResult) {
console.error("选择图片失败:", err)
reject(err)
},
...options,
})
})
}
/** 视频上传 */
function video(options?: any): Promise {
return new Promise((resolve, reject) => {
uni.chooseVideo({
success: async (result: UniApp.ChooseVideoSuccess) => {
const fileUrl = await runOne(result.tempFilePath)
resolve(fileUrl)
},
fail(err: UniApp.GeneralCallbackResult) {
console.error("选择视频失败:", err)
reject(err)
},
...options,
})
})
}
// #ifdef MP-WEIXIN
/** 文件上传 */
function file(options?: any): Promise {
return new Promise((resolve, reject) => {
wx.chooseMessageFile({
success: async (result) => {
const tempFilePaths = result.tempFiles.map(item => item.path)
const fileUrls = await Promise.all(tempFilePaths.map(runOne))
const formate = fileUrls?.length > 1 ? fileUrls : fileUrls[0]
resolve(formate)
},
fail(err) {
console.error("选择文件失败:", err)
reject(err)
},
...options,
})
})
}
// #endif
return {
image,
video,
// #ifdef MP-WEIXIN
file,
// #endif
}
}
```
使用方式:
```ts
const uploader = uploadFileToS3()
const imageUrl = await uploader.image()
const videoUrl = await uploader.video()
// 微信小程序
const fileUrl = await uploader.file()
```
## 常见问题
- **文件名**:上传前过滤特殊字符,或用 UUID 重命名,避免部分环境兼容问题
- **时效**:预签名 URL 通常 15–60 分钟有效,拿到后尽快上传
- **CORS**:跨域 PUT 失败时,检查对象存储控制台的 CORS 规则
- **Content-Type**:务必与文件类型一致,否则浏览器可能强制下载而非预览
- **微信小程序域名**:若出现 `request:fail url not in domain list`,说明请求域名未加入白名单。登录 [微信公众平台](https://mp.weixin.qq.com/) → **开发管理 → 开发设置 → 服务器域名**,将后端 API 域名(presign 接口)和对象存储 Endpoint 域名(PUT 上传)添加到 **request 合法域名**
---
URL: "https://elysia-admin.top/other/faq.html"
LLMS_URL: "https://elysia-admin.top/\\other\\faq.md"
title: "常见问题 - Elysia Admin"
description: "解答 Elysia Admin 使用过程中的常见问题,包括 PM2 集群模式限制、多实例部署方案及 Nginx 负载均衡配置。"
---
---
title: 常见问题 - Elysia Admin
description: 解答 Elysia Admin 使用过程中的常见问题,包括 PM2 集群模式限制、多实例部署方案及 Nginx 负载均衡配置。
---
# 常见问题
## 为什么不再支持二进制打包?
从引入 BullMQ Sandboxed Processors 之后,二进制打包就不再可行了。主要原因有两个:
- 第一,`config/index.ts` 通过 `readFileSync` 读取 `production.yaml`,二进制文件没有文件系统,读不到外部文件。
- 第二,也是更根本的问题,Sandboxed 模式下 BullMQ 会用 `child_process.spawn` 启动独立子进程来执行每个任务,而二进制包里没有 `bun` 运行时,也没有 `processors/` 目录,子进程根本无法启动。
如果放弃 Sandboxed 模式改回函数模式,二进制是可以打包的,但会失去进程隔离的优势。综合考虑,直接使用 PM2 部署是更合适的方案,稳定性和功能都更完整。
## 为什么无法使用PM2的cluster(集群模式)?
`PM2` 的集群模式并不是自己发明了多进程管理,而是深度依赖 `Node.js` 原生的 `cluster` 模块。当你设置 `exec_mode: 'cluster'` 时,`PM2` 实际上是在调用 `Node.js` 的内部 API 来分叉进程并共享端口(TCP 句柄)。
但是我们的 `Elysia Admin` 项目完全依赖 `Bun` 。 `Bun` 虽然兼容了很多 `Node.js` API,但它并没有完全实现 cluster 模块(或者说实现方式与 Node 不同)。
因此你需要手动多实例:
```js [ecosystem.config.cjs]
module.exports = {
apps: [
{
name: 'Elysia-Admin-1',
script: 'bun',
args: 'run index.js',
interpreter: 'node',
exec_mode: 'fork', // 必须 fork
env: {
PORT: 3001, // 指定不同端口
NODE_ENV: 'production'
}
},
{
name: 'Elysia-Admin-2',
script: 'bun',
args: 'run index.js',
interpreter: 'node',
exec_mode: 'fork',
env: {
PORT: 3002, // 指定不同端口
NODE_ENV: 'production'
}
},
// 可以继续加 3, 4...
]
};
```
```txt [nginx]
http {
upstream elysia_app {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
# 负载均衡策略,默认是轮询
}
server {
listen 80;
location / {
proxy_pass http://elysia_app;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
```
## 支付回调收不到通知,是什么原因?
支付网关(支付宝、微信、PayPal)的异步通知是由**网关主动 POST 到你的服务器**,因此回调地址必须满足以下条件:
- **外网可访问**:本地开发环境(如 `localhost`)无法接收回调,需使用内网穿透工具(如 [ngrok](https://ngrok.com/))或直接部署到公网服务器。
- **使用 HTTPS**:微信支付和支付宝生产环境均要求回调地址为 HTTPS。
- **及时返回 2xx**:网关在收到 2xx 之前会按策略重试,务必在完成业务处理后用 `notifySuccess()` 的返回值作为响应体,并返回 HTTP 200。
## 微信支付调用报错:serialNo 缺失
微信支付 V3 接口要求在请求头 `Authorization` 中携带商户 API 证书序列号。若报错提示 `serialNo` 缺失,请检查商户配置 `MerchantConfig.config.serialNo` 是否已正确填写。
```json
{
"serialNo": "7132D72A000000XXXXXXXXXXXXXXXXXX",
"apiV3Key": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
```
`serialNo` 可在微信商户平台「账户中心 → API 安全 → 管理证书」页面查看;`apiV3Key` 在同页面「APIv3 密钥」处设置。**两者缺一不可,缺省会导致所有微信支付接口调用失败。**
## PayPal 为什么默认连接的是沙箱环境?
`providers/paypal/base.ts` 中 API 根地址默认指向 `api-m.sandbox.paypal.com`(沙箱),便于本地开发调试。上线生产环境前,需将地址改为 `https://api-m.paypal.com`,并使用 **Live** 应用的 Client ID / Secret;沙箱与生产密钥**不可混用**,否则会导致鉴权失败。
## 如何做路由拦截与接口中间件?
路由注册与全局守卫分两处,按需求改对应文件即可:
| 需求 | 文件 | 说明 |
| :--- | :--- | :--- |
| 路由拦截 / 注册策略 | [`server/src/modules/index.ts`](../../server/src/modules/index.ts) | `RegisterRoutes()` 自动加载各模块 `route.ts`,按 `meta.isAuth` 拆分为公共路由与需登录路由;需登录的路由统一走 `app.guard` 校验 `authorization` 请求头。若要调整**哪些路由需要登录、如何分组挂载**,在此修改。 |
| 接口中间件(守卫链) | [`server/src/middleware/index.ts`](../../server/src/middleware/index.ts) | `GlobalMiddleware` 在 `onBeforeHandle` 中依次执行:IP 黑名单 → API 熔断 → 路由分析 → 登录认证 → IP 限流 → 权限校验;`GlobalResponseMiddleware` 负责请求日志与操作日志。新增或调整**请求进入业务前**的拦截,在此修改。 |
`CreateApp()`([`server/src/app.ts`](../../server/src/app.ts))会先挂载全局中间件,再调用 `RegisterRoutes()` 注册业务路由。单条路由还可通过 `route.ts` 的 `meta`(如 `isAuth`、`permission`、`ipRateLimit`)配合守卫生效。
---
URL: "https://elysia-admin.top/other/social.html"
LLMS_URL: "https://elysia-admin.top/\\other\\social.md"
title: "找到我们 - Elysia Admin"
description: "加入 Elysia Admin 社区,通过微信或 QQ 群与作者及其他开发者交流,获取最新动态与技术支持。"
---
---
title: 找到我们 - Elysia Admin
description: 加入 Elysia Admin 社区,通过微信或 QQ 群与作者及其他开发者交流,获取最新动态与技术支持。
---
# 找到我们
有问题想聊?遇到 bug 想反馈?或者只是想认识一下同道中人?欢迎加入我们的社区,大家一起搞事情。
## 微信
有商务合作意向、或者遇到问题想直接跟作者沟通,扫码加微信就行,备注 `Elysia Admin` 方便通过。
## QQ 群
日常技术交流、问题反馈都在群里,欢迎加入。
| 群名 | 群号 |
| --- | --- |
| elysia-admin 1群 | `527636808` |
感谢支持本项目的朋友,名单见 [鸣谢](/other/sponsors)。
## 说在最后
社区氛围很重要,进群之后多交流、多分享,有好的想法或者发现了问题都可以直接说。
项目是开源的,欢迎提 PR 和 Issue,一起把它做得更好。
---
URL: "https://elysia-admin.top/other/sponsors.html"
LLMS_URL: "https://elysia-admin.top/\\other\\sponsors.md"
title: "感谢支持 - Elysia Admin"
description: "感谢自愿打赏与支持 Elysia Admin 开源项目的朋友们,你们的鼓励是项目持续维护的动力。"
---
---
title: 感谢支持 - Elysia Admin
description: 感谢自愿打赏与支持 Elysia Admin 开源项目的朋友们,你们的鼓励是项目持续维护的动力。
---
# 感谢支持
`Elysia Admin` 是开源项目,维护与文档更新离不开社区朋友的鼓励。以下为自愿打赏支持的朋友,排名不分先后,在此一并致谢。
::: info
暂不接受陌生人打赏。如需支持项目,请先 [添加作者微信](/other/social#微信) 后再进行打赏。
:::
暂无鸣谢名单。
---
URL: "https://elysia-admin.top/start/change-log.html"
LLMS_URL: "https://elysia-admin.top/\\start\\change-log.md"
title: "更新日志 - Elysia Admin"
description: "Elysia Admin 版本更新记录,包含各版本新增功能、问题修复及重要变更说明。"
---
---
title: 更新日志 - Elysia Admin
description: Elysia Admin 版本更新记录,包含各版本新增功能、问题修复及重要变更说明。
---
# 更新日志
## v1
::: timeline v1.5.10(2026-10-22)
- 新增了请求/响应加解密:`crypto.enabled` 为 true 时注册 `CryptoMiddleware`。请求头 `X-Encrypt-Key` 用 RSA-OAEP(SHA-256)解出本次 AES-256-GCM 密钥,JSON 体 `{ data: Base64(iv 12 字节 + 密文) }` 解密后再进入校验;响应对称为 `{ encrypted: true, data }`,并带 `x-encrypted: 1`。没有该头的请求仍走明文。加密后的 `onAfterResponse` 会还原明文,操作日志仍能读取 `response.code`。前端仅当 `VITE_API_ENCRYPT=true` 且 `GET /api/crypto/public-key` 返回启用和公钥时才加密,否则自动退回明文;文件上传与二进制响应跳过。密钥用 `bun run gen:crypto-keys` 写入 yaml(生产环境加参数 `production`,覆盖加 `--force`),私钥不要提交到仓库。详见 [请求加解密](/guide/crypto)。
- 修复了软删除后用户名无法再次使用的问题:`system_user.username` 由列级 `UNIQUE` 改为部分唯一索引 `system_user_username_unique WHERE del_flag = false`;`RegisterUser` 只在未删除行中查重。已有库升级时需去掉旧的列级唯一约束,再同步该索引(`bun run db:push` 或手工 DDL)。
- 新增了站点部署:后台维护站点(域名、宝塔目录开关、反代 location:首页 / 管理端 / API / RustFS / 自定义),可预览仅 HTTP 与含证书的两套 Nginx 配置;本机 `openssl` 走 Let's Encrypt HTTP-01 签发 `fullchain.pem` / `privkey.pem`。支持立即续签;定时任务「站点证书续签」处理已开启自动续签、且没有证书或剩余不足 30 天的站点,可选执行 `nginx -s reload`。Redis 锁避免并发续签,未删除站点的域名唯一。另增 `GET /api/system/storage/rustfs-bucket`,供站点表单读取当前启用的 RustFS 桶名。详见 [站点部署](/guide/site-deploy)。
- 修复了富文本标题被样式压平的问题:WangEditor 编辑区补回 h1–h6 字号与字重,加粗改为 700,标题下拉不再用 `!important` 统一字号。通知公告弹窗在 `reset` 之后调用 `clearValidate`,避免选择器误报校验。
:::
::: timeline v1.5.9(2026-10-06)
- 修复了登录等热路径对 Redis 全库 SCAN 的依赖:refresh / 在线用户改为 SET 索引维护,登录、登出、踢人、改密按用户清理,不再 `Keys` 扫库;`Keys` 仅留给缓存监控等低频运维接口。
- 增强了 refresh 索引升级兼容:升级前散落的 refresh key 在该用户首次清理时 SCAN 一次并写入迁移标记,之后走索引,避免反复全库扫描。
- 修复了生产构建临时 stub 残留:`runUnifiedBuild` / `runProcessorBuild` 在 `finally` 中清理 stub 目录,构建失败也会删临时入口,降低目录被占用导致的二次构建异常。
- 优化了开发 SQL 控制台对显式事务脚本的支持:含 `BEGIN`/`COMMIT` 时剥离关键字,事务体走 `pgClient.begin`,其后语句再 `unsafe`,避免连接池禁止 `unsafe` 发送事务关键字。
- 修复了菜单保存漏传全屏页字段:提交时补上 `isFullPage`,全屏页配置编辑后不再丢失。
:::
::: timeline v1.5.8(2026-09-17)
- 新增了开发环境 SQL 控制台:后端 `dev-sql` 模块(`POST /dev/sql/execute`,`stages: ['development']`,需登录);前端 `admin/src/views/dev/sql/` 支持执行任意 SQL、返回列/行与耗时,SELECT 超 1000 行截断;生产构建与注册不会打进该模块。
- 新增了路由模块级 `stages`(`'development' | 'production'`):缺省两端都注册;须字面量数组供 `generateRegistry` 解析;`stages: []` 永不加载;生产包仅收录含 `production` 的模块;脚手架模板与后端示例/规则已同步说明。
- 修复了生产环境双栈出网半残导致启动异常:新增 `dual-stack-probe`,生产启动前探测 IPv4/IPv6,按结果设置 DNS `resultOrder`;半残/不可达则失败退出;`SKIP_NETWORK_PROBE=1` 可跳过并强制 IPv4。
- 优化了启动顺序:先探测(生产)→ 并行 `EnsurePg` / `ConnectRedis` → 再初始化队列 → 再建应用监听;关闭时一并关闭队列,降低 Redis/队列过早连上的问题。
- 修复了 WangEditor 图片/视频上传:改为走对象存储预签名上传,与现有 Storage 上传链路一致。
- 增强了 AI 大任务协作规范:新增 `.ai/AI_PHASED_TASKS.md`(多模块/完整项目分阶段拆解);各 IDE 规则与 Skill 增加触发与目录约定(`.cursor|claude|codex|qoder|trae|kiro/feature-tasks/`)。
:::
::: timeline v1.5.7(2026-09-08)
- 修复了分页排序不稳定的问题:`FindPage` / `FindAll` 在主排序字段外追加主键升序作为 tie-breaker,避免并列值下 `LIMIT/OFFSET` 漏行或重复。
- 优化了字典数据列表筛选:`system-dict` 数据列表对 `dictLabel` / `dictType` 由模糊 `like` 改为精确 `eq`,与类型下拉、标签精确匹配场景一致。
- 优化了用户管理页视觉与信息密度:列表改为头像/首字母 + 昵称/账号/邮箱一体展示,状态改为启停胶囊,时间拆分日期与时刻;新增/编辑弹窗采用自定义标题区与更清晰的提交文案,并去掉入场过渡动画以免干扰操作。
- 新增了前端版本更新检测:生产环境以 `assets` 下带 hash 的 js/css 为指纹,在路由切换与页签切回前台时比对远端 `index.html`;发现新版本弹出「立即刷新 / 稍后」,懒加载 chunk 失效时自动刷新兜底,开发环境自动关闭。
:::
::: timeline v1.5.6(2026-08-25)
- 优化了列表关键字模糊查询:`QueryBuilder` 的 `like` / `ilike` / `notLike` / `notIlike` / `leftLike` / `rightLike` 在拼接 SQL 前统一对字符串关键字执行 `trim()`,避免首尾空格导致查不到结果;纯空格输入视为空并跳过条件,各模块 `handle.ts` 无需改动。
- 增强了 AI 开发规范:英文规则文件(`.ai` / IDE rules)新增或修改正文须用英文(仅本次编辑,不批量汉英重写);新建 `dict_type` 须短且语义化(优先 2–3 段如 `goods_status`),可复用则不新建,纯布尔启停勿建字典;同步 Cursor / Claude / Qoder / Trae / Kiro / `AGENTS.md` 与 `guide/ai-guide.md`。
:::
::: timeline v1.5.5(2026-08-08)
- 优化了开发态热重载:`bun run dev` 改为由 `script/dev-watch-modules.ts` 守护;轮询 `src/modules` 目录集合,新增/删除模块且写盘稳定约 5 秒后 touch 已在模块图中的 `src/modules/index.ts`,让 `bun --watch` 自行重载,无需手动重启即可访问新接口。
- 增强了慢写兜底:新模块目录出现后的 30 秒内若继续补写 `handle.ts` / `dto.ts` 等,会再触发一次重载,避免脚手架或 AI 分批落盘时模块被静默跳过。
- 新增了逃生命令 `bun run dev:plain`,行为与原先直接 `bun --watch src/index.ts` 一致;`dev:all` 继续走 `dev`,自动受益。
- 新增了业务编号生成工具 `core/biz-no.ts`:基于 Redis 日递增序号,格式为前缀 + 日期 + 序号(如 `ORD20260808000123`);订单号 / 支付单号 / 退款单号由 UUID 改为 `GenerateBizNo`,前缀常量 `BizNoPrefix` 放在 `constants/enum.ts`。
- 优化了对象存储上传文件名:预签名 `put` 时由 `StorageService` 统一生成短对象键(日期目录 + 18 位短 UUID + 扩展名,如 `20260808/0197f2c8a1b34d5e9f.png`),不再拼接毫秒时间戳与原始中文文件名;四个 provider 仅负责签名,`get` 仍透传原 key,历史文件可正常访问。
- 修复了前端 `tsconfig.json` 的弃用告警:`moduleResolution` 由已废弃的 `node`(node10)改为 Vite 场景适用的 `bundler`,不再需要 `ignoreDeprecations` 静音;因新解析模式会遵守依赖包 `exports` 字段,同步在 `paths` 中为 `@wangeditor/editor-for-vue` 指向其声明文件,避免退化为隐式 `any`。
- 修复了重置密码接口的前端类型与后端契约不一致:`Api.Auth.ResetPasswordParams.uid` 由 `string` 改为 `number`,与 `auth/dto.ts` 中 `t.Number()` 对齐;`admin` 全量 `vue-tsc --noEmit` 已零错误。
:::
::: timeline v1.5.4(2026-07-29)
- 增强了 AI 开发规范:软删除场景下唯一性校验须覆盖已删记录,新表唯一约束改用部分唯一索引 `WHERE del_flag = false`;表单须前后端双校验;`handle.ts` 导出函数须写 JSDoc;实体下拉走缓存 `/options` 接口,禁止用分页 `/list`;响应 DTO 须声明完整字段以免被 Elysia 剥离;明确 **禁止读写** `server/database/sql/pg.sql`(仅作备份快照)。
- 同步了各 IDE Agent 规则与 Skill(Cursor / Claude / Qoder / Trae / Kiro / Codex `AGENTS.md`),并补齐 `.ai` 模板索引、生成规则与前端硬规则中的对应条目。
- 优化了模块脚手架产出:`create:module` 生成的 `handle.ts` 五个导出函数自动带中文 JSDoc;`create:page` 生成的表格列、搜索/弹窗表单标签、placeholder 与校验 `message` 改用 Schema 行尾注释解析出的中文标签,开箱与后端 `fieldLabels` 对齐。
:::
::: timeline v1.5.3(2026-07-22)
- 增强了生产构建安全:`bun run build` 复制 `production.yaml` 到 `dist` 后,自动为 `jwt.accessToken.secret` / `jwt.refreshToken.secret` 注入 ≥32 字节随机串(`base64url`),源码配置不变;每次重新打包会轮换密钥,旧 token 失效。
- 修复了登录失败可枚举用户名的问题:账号不存在与密码错误统一返回 `400` + 用户名或密码错误,与忘记密码接口的防探测思路一致。
- 修复了改密 / 重置后会话未失效的问题:新增 `InvalidateUserSession`,在 `SetUserPassword`、禁用用户、软删用户时清理 `ONLINE_USER` / refresh / 菜单缓存;登出与在线用户强退复用同一逻辑。
- 增强了 Refresh Token 重放检测:轮换时写入 `refreshUsed` 墓碑;已作废的 refresh 再被使用时调用 `InvalidateUserSession` 整族吊销,并返回 `401`「会话已失效,请重新登录」。
- 优化了密码哈希性能:`BcryptHash` / `BcryptCompare` 由 `hashSync` / `verifySync` 改为异步 `Bun.password.hash` / `verify`,避免并发登录阻塞事件循环。
- 优化了操作日志落库:`onAfterResponse` 中 `AddOperLog` 改为投递专用 BullMQ 队列 `system-oper-log-queue`,由 Worker 异步 `InsertOne`,降低请求尾延迟并减轻与请求共用连接池的争抢。
- 增强了 API 安全响应头:新增 `SecurityHeadersMiddleware`,统一下发 `X-Content-Type-Options` / `X-Frame-Options` / `CSP frame-ancestors` / `Referrer-Policy`;HSTS 由配置项 `app.hsts` 控制(开发关、生产开,需 HTTPS / 前置 TLS)。
- 优化了鉴权 Redis 压力:`AuthGuard` 对 `ONLINE_USER` 增加 2s 进程内 L1;登出/强退/改密与角色权限刷新时清本机缓存(多实例他机最多滞后约 2s)。
- 优化了角色权限变更:按 `roleId` 查受影响用户,只删 `USER_PERM` / 菜单 / L1,不再 `Keys(ONLINE_USER+*)`;鉴权与 `findPerm` 按需加载权限。
- 修复了列表缓存写路径竞态:IP 黑名单改为写库后 `Del(IP_BLACK)`,读继续 `WithCache`;移除无锁的 `CacheInsert` / `CacheUpdate` / `CacheDelete`。
- 增强了列表分页边界:`FindPage` 统一 clamp(`pageNum≥1`,`pageSize` 默认 10、上限 100);`BaseListQueryDto` 同步 `minimum` / `maximum` 校验。
:::
::: timeline v1.5.2(2026-07-21)
- 新增了 AI 后端「跨表数据访问」规范:跨表须调用所属模块 `handle.ts` 导出的 PascalCase 函数,禁止直接引入他模块 schema;路由函数小写驼峰,跨模块导出 PascalCase。
- 优化了生产构建的 chunk 输出:`Bun.build` 的 chunk 命名改为 `chunks/chunk-[hash].[ext]`,打包产物统一落在 `dist/chunks/` 目录,清理时可整文件夹删除,不再散落于 `dist/` 根目录。
- 修复了 chunk 移入子目录后的路径解析:`src/config/index.ts` 与 `src/app.ts` 改用 `getDistRoot()` 定位 dist 根目录,避免 `import.meta.dirname` 指向 `dist/chunks/` 导致 `production.yaml` 与静态资源 `public/` 读取失败。
:::
::: timeline v1.5.1(2026-07-19)
- 重构了服务端日志:自研 `console` Logger 替换为 [pino](https://github.com/pinojs/pino);开发环境用 `pino-pretty` 彩色输出,生产环境按日写入 `dist/logs/YYYYMMDD/`(主进程 `app.log` / `error.log` / `http.log`,Worker / 沙箱 Processor 为 `worker.log` / `worker-error.log`)。
- 新增了配置项 `log.level`、`log.showRequestParams`(`development.yaml` / `production.yaml`):控制日志级别,以及 HTTP 访问日志是否记录 query / body / params。
- 修复了 BullMQ 沙箱 Processor 定时任务日志落到 PM2 stdout 的问题:识别 `dist/processors/*.js` 与 `dist/dist/cjs` 入口,日志目录锚定 `dist/logs`,子进程继承 `APP_ROLE=worker`。
- 优化了日志分流:`warn` / `error` 写入错误文件,`info` 保留在 app/worker;请求校验失败仅记短消息,不再整包 dump 进 `app.log`(详情以 `http.log` 状态码为准)。
- 增强了启动失败可观测性:配置校验失败、未捕获异常与启动失败通过 `appendFatalLog` 同步写入当天 `error.log` / `worker-error.log`;PM2 `ecosystem` 不再采集 stdout/stderr,排查直接看 `dist/logs/YYYYMMDD/`。
- 调整了 TypeScript 配置:移除已弃用的 `baseUrl`,`paths` 改为相对 tsconfig 的显式映射(含 `database/*`、`script/*`)。
- 修复了在线用户列表接口响应校验失败:`monitor-online` 的 `ListDto` 将 `username` / `email` / `phone` / `sex` / `avatar` 改为可选且允许 `null`,与 Redis 中实际在线用户字段一致。
:::
::: timeline v1.5.0(2026-07-11)
- 新增了 [MCP 服务](/guide/mcp) 指南:覆盖官方 `elysia-admin-mcp`(源码活索引,14 个工具)与 `pg-mcp-server`(PostgreSQL 只读查询)的职责边界、Cursor / Claude Code / Codex 接入配置、环境变量与只读纪律;与 `.ai/AI_MCP_SETUP.md` 对齐。
:::
::: timeline v1.4.14(2026-07-08)
- 新增了登录日志与操作日志列表筛选:后端 `system-login-log` / `system-oper-log` 扩展 `ListDto` 与 `QueryBuilder`(用户/IP/模块/操作/请求方式/用户类型/状态及 `createTime` 日期范围);前端拆分 `loginlog-search.vue`、`operlog-search.vue` 搜索栏,字典下拉与日期快捷范围与列表页联动。
- 优化了 `CrudDto.create` 与模块脚手架:`fieldLabels` / `fieldTypes` 为必填字段自动生成中文 `error` 校验文案;`create:module` 模板从 Schema 字段 `comment` 写入 DTO,避免校验失败时前端收到 JSON 而非可读提示。
- 重构了 AI 代码模板与 Token 预算:`.ai/AI_CODE_EXAMPLES.md` 拆为索引 + `AI_CODE_EXAMPLES_BACKEND.md` / `AI_CODE_EXAMPLES_FRONTEND.md`,各 IDE Agent 规则统一「按任务只读对应章节、单轮最多 2–3 份 .ai 文档」等读文件纪律;`dto.ts` 校验须含中文 `error` 写入全局规则。
- 新增了 Qoder IDE 项目配置(`.qoder/rules/`、`elysia-module-dev` Skill、`settings.json`),与 Cursor / Claude / Trae / Kiro / Codex 规则语义对齐;根目录 `AGENTS.md` 与分层 rules 说明见 `.qoder/README.md`。
- 新增了开发者本地偏好示例 `.ai/dev-preferences.local.example.md`:复制为 gitignore 的 `dev-preferences.local.md` 并设置 `db_push: allowed` 后,AI 可在 schema 变更后自动执行 `bun run db:push`。
:::
::: timeline v1.4.13(2026-07-03)
- 重构了 Docker 镜像构建:改为多阶段执行 `bun run build`,运行镜像仅包含完整 `dist/`(`index.js`、`workers.js`、`processors/`、`dist/cjs/`、`production.yaml`、`public/`);单容器同时启动 HTTP 与 Worker,不再使用 `--compile` 单文件 + distroless;新增 `server/.dockerignore` 并收窄 build 阶段 COPY(仅 `script/`、`src/`、`database/schema`、`public/` 等必要文件)。
- 优化了生产构建:`script/build.ts` 合并为单次 `Bun.build`,index / Worker / 全部 Processor 共享 chunk,减少重复打包与构建时间;stub 入口由 `script/build-shared.ts` 临时生成于 `script/.build-entries/`。
- 统一了 Processor 增量构建:`script/build-processors.ts` 与生产 build 共用 `runProcessorBuild`、`copyBullmqCjs`(BullMQ 沙箱 bootstrap 统一输出至 `dist/dist/cjs/`,修正 dev 原先写到 `dist/cjs/` 的路径偏差)。
- 拆分了启动种子数据:`script/seed.prod.ts`(API 同步 + 定时任务恢复)、`script/seed.dev.ts`(空库执行 `pg.sql`);`src/index.ts` 生产路径仅静态加载 prod,dev 动态 import,减小生产 bundle 体积。
- 移除了无效的 `task-registry.generated.ts` 构建:任务恢复仍由 `task-registry.ts` 的 `RegisterAllTasks()` 从数据库加载,不再在 build 时扫描 `modules/*/task.ts`。
- 更新了 [部署指南](/guide/deploy) Docker 章节与 [内置命令](/architecture/commands) Docker 说明:反映单容器双进程、配置挂载路径与 `--init` 建议。
- 更新了 [队列指南](/guide/queue) 自定义队列 checklist:移除向 `build.ts` / `build-processors.ts` 手动追加 processor 列表的过时步骤(已由 `discoverProcessorEntries()` 自动发现)。
:::
::: timeline v1.4.12(2026-07-01)
- 移除了服务端 `zod` 依赖:配置校验改为 Elysia `t`(TypeBox)schema + `Value.Check`,与路由 DTO 校验栈统一;`IConfig` 类型改为 `Static`。
- 优化了生产构建体积:主进程与 Worker、多个 Processor 分别启用 `bun build --minify --splitting` 共享 chunk;相较独立单文件打包,主进程 + Worker 与 Processor 区 JS 总体积显著下降(部署时需连同 `dist/` 根目录及 `dist/processors/` 下的 `chunk-*.js` 一并上传)。
- 新增了 Processor 构建自动发现:`discoverProcessorEntries()` 扫描 `queues/*/processor.ts`,新增沙箱队列无需再改 `build.ts` / `build-processors.ts` 硬编码列表;构建参数抽取至 `script/build-shared.ts`。
- 调整了 Docker 镜像构建:编译参数由 `--minify-whitespace --minify-syntax` 改为完整 `--minify`。
- 简化了本地开发与构建命令:`package.json` 中 `dev` / `dev:debug*` / `dev:workers` 内置 `NODE_ENV=development`,`build` / `build:processors` / `start` 内置 `NODE_ENV=production`;新增 `bun run start` 生产直启 `dist/index.js`,无需再在命令行前手动设环境变量。
:::
::: timeline v1.4.11(2026-06-23)
- 修复了生产环境打包部署后静态资源路径错误:`staticPlugin` 原先使用 `resolve(import.meta.dirname, '../public')`,打包后 `index.js` 位于部署目录(如 `/www/wwwroot/elysia-admin`)时实际解析到上一级 `public/`(如 `/www/wwwroot/public`),与 `build` 复制到 `dist/public` 的布局不一致,启动时报 `ENOENT: no such file or directory`;生产环境改为同目录 `public`,开发环境保持 `../public` 指向 `server/public`。
- 修复了未登录调用登出接口可能误删缓存的问题:`logout` 在无 `userId` 时直接返回成功,避免 `undefined` 拼入 Redis key 前缀。
:::
::: timeline v1.4.10(2026-06-16)
- 新增了系统管理「通知公告」菜单及前后端 CRUD,通知类型复用字典 `system_notice_type`,内容支持富文本编辑;
- 新增了仪表盘「分析页」「电商页」示例(`dashboard/analysis`、`dashboard/ecommerce`),含销售趋势、热门商品、访客洞察等演示模块,种子数据补充对应菜单;
- 修复了系统用户批量删除对 uuid 主键无效的问题:`SoftDeleteByKeys` 原先对 `ctx.params.ids` 一律 `.map(Number)`,uuid 会变成 `NaN` 导致软删除失效;现按主键列 `dataType` 区分,`number` 仍转数字、`string`(含 uuid)保留原值;`system-user` 的 `remove` 改走 repository 抽象,移除 handle 内直连 `pg` 的写法。
:::
::: timeline v1.4.9(2026-06-12)
- 修复了后台 CRUD 弹窗「编辑后再新增」时表单数据未清理的问题:`initFormData` 使用 `Object.assign(formData, { ...row })` 会把列表行的主键(如 `deptId`、`apiId`)与审计字段挂到 reactive 表单上,切换新增后仍随 create 请求提交导致接口报错。
- 统一改为 `getDefaultFormData()` 打开弹窗时先重置默认值、删除可能残留的审计字段、编辑模式再显式赋值;已修复部门(`dept-dialog`)、接口(`api-dialog`)、角色(`role-edit-dialog`)、IP 黑名单(`blacklist-dialog`)、定时任务(`job-dialog`)、存储配置(`storage-dialog`)共 6 个弹窗;字典、用户、菜单等页面此前已采用相同模式无需改动。
- 修复了系统用户新增时备注未入库的问题:`system-user` 模块 `CreateDto` 未声明 `remark`(及 `avatar`、`status`),Elysia 校验请求体时会丢弃未定义字段;编辑接口基于 `SelectSystemUser` 含 `remark` 故不受影响。已在 `CreateDto` 补全上述字段。
- 修复了 `/api/system/menu/simple` 只勾选子菜单时子菜单丢失的问题:`handleMenuListToTree` 在父节点未授权时会丢弃孤儿节点;现于 `loadUserMenuTree` 自动补全祖先链,未直接授权的父级合成 `isHide`,并增加孤儿挂 root 兜底。
- 修复了只勾选按钮权限时侧边栏误显示所属菜单的问题:`GetRoleMenuIdsAndBtnIds` 原先将按钮行的 `menuId` 一并计入可见菜单;现 `menuIds` 仅取 `menuBtnId` 为 null 的纯菜单授权行,按钮权限仍通过 `menuBtnIds` 与 API 权限字符串正常生效。
- 修复了角色权限弹窗仍展示已禁用菜单与按钮的问题:`/api/system/menu/tree` 原先仅过滤 `delFlag`;新增查询参数 `enabledOnly`,角色权限分配时传 `true` 仅返回 `status=true` 的菜单与按钮,菜单管理页保持展示全部记录。
:::
::: timeline v1.4.8(2026-06-10)
- 新增了模块脚手架:在 `server/` 下执行 `bun run create:module` 生成后端 `route.ts` / `dto.ts` / `handle.ts`,`bun run create:page` 生成后台 types、api、views 标准 CRUD 骨架;依赖已有 Drizzle Schema,支持 `--schema`、`--dry-run`。
- 新增了脚手架脚本与模板(`server/script/create-module.ts`、`create-page.ts`、`script/scaffold/`)及 `server/test/script/scaffold.test.ts` 单测。
- 新增了 `.ai/AI_MODULE_SCAFFOLD.md`,说明脚手架使用场景、命令示例及跑完后 AI 增量职责(业务逻辑、表格/弹窗排版、字典与 handoff SQL)。
- 更新了 AI 模块工作流(`.ai/AI_MODULE_WORKFLOW.md` Step 2.5)、`.ai/README.md`、`.ai/AI_CONTEXT_CAPSULE.md` 与 Cursor Skill(`.cursor/skills/elysia-module-dev/`):标准单表 CRUD 优先脚手架,再让 AI 做增量。
- 同步更新了 Cursor / Codex / Claude Code / Trae / Kiro 的 Agent 规则(`AGENTS.md`、`server/AGENTS.md`、`admin/AGENTS.md` 等),各 IDE 均可识别 `create:module` + `create:page` 推荐流程。
- 更新了 [AI 开发指南](/guide/ai-guide):新增「推荐流程(脚手架 + AI)」与档0 提示词(脚手架已生成后的增量开发)。
- 补充了 [内置命令](/architecture/commands)「模块脚手架命令」章节。
- 优化了 BullMQ Redis 键前缀:Queue / Worker 使用 `app.id` 作为 `prefix`(如 `Elysia-Admin:flow-buffer-queue:meta`),替代默认 `bull:` + 队列名拼 `appId` 的旧形态;多应用共用 Redis 时仍按应用隔离,旧 `bull:*` 键需手动清理。
- 新增了内置队列监控,替代原 Bull Board 独立面板:移除 `@bull-board/api`、`@bull-board/elysia` 依赖及 `bull-board-ui.ts`,监控能力统一纳入 Admin 后台与 RBAC。
- 新增了后端 `monitor-queue` 模块(`server/src/modules/monitor-queue/`):队列列表、按状态分页查任务、任务详情与日志、单/批量重试、删除、清空 completed/failed、暂停/恢复、延迟任务立即执行(promote);权限码 `monitor:queue:query` / `retry` / `delete` / `clean` / `pause` / `promote`。
- 新增了前端队列监控页(`admin/src/views/monitor/bullmq/`):三栏布局(队列列表 / 任务列表 / 任务详情),支持延迟倒计时、运行日志、3 秒 / 5 秒自动刷新;配套 `api/monitor/queue.ts` 与 `types/api/monitor-queue.d.ts`。
- 扩展了 `QueueManager`:注册队列时记录 `description` 元数据,对外提供 `getQueueMeta` / `getAllQueueMeta` 供监控接口使用;`closeAll` 时同步清空内部 Map。
- 更新了种子数据 `server/database/sql/pg.sql`:补充「系统监控 → 队列监控」菜单(`/monitor/bullmq`)及按钮权限;启动横幅移除 Bull Board 地址提示。
- 更新了 [队列指南](/guide/queue) 与 [部署指南](/guide/deploy) 监控章节:入口改为 Admin 后台,说明新面板能力与权限要求。
- 修复了生产环境经 Nginx 反代后登录日志 IP 全为 `127.0.0.1`:`production.yaml` 默认开启 `trustProxy` 并配置 `trustedProxyCidrs`(`127.0.0.1` / `::1`),在受信代理场景下正确解析 `X-Forwarded-For` / `X-Real-IP`(需反代层传递对应头)。
- 为 `server/src/config/schema.ts` 各配置段与字段补充了 JSDoc 注释,说明用途、单位及可被环境变量覆盖的项。
:::
::: timeline v1.4.7(2026-06-09)
- 重构了后端错误处理:HTTP 路由 handler 移除重复的 `try/catch`,未捕获异常统一由全局 `onError` 收口;服务端日志保留完整 `stack`,客户端仍返回通用 500 文案。
- 新增了 `server-error` 工具(`getHttpStatus` / `getPgErrorCode` / `logServerError`),支持业务 `httpStatus`(如 409)、PostgreSQL 唯一约束 `23505` 等在全局层映射。
- 调整了 `BaseResultData.fail`:仅对业务主动返回的字符串 500 打日志,避免与 `onError` 重复记录异常对象。
- 拆分了应用启动配置:`ConfigureErrorHandler` → `middleware/error-handler.ts`,`ConfigureOpenAPI` → `infrastructure/openapi.ts`,`ConfigureBullMQUI` → `infrastructure/queue/bull-board-ui.ts`,`app.ts` 仅保留 `CreateApp` 组装逻辑。
- 更新了 AI 代码模板(`.ai/AI_CODE_EXAMPLES.md`):标准 CRUD handler 不再生成通用 `try/catch`;补充 `useDictStore` 字典字段示例。
- 新增了 AI 模块开发工作流(`.ai/AI_MODULE_WORKFLOW.md` 及 Schema / Handoff SQL / MCP / 页面排版子指南),Handoff SQL 统一输出至 `server/database/sql/{模块名}-init.sql`。
- 新增了 Cursor 项目 Skill(`.cursor/skills/elysia-module-dev/`),9 步 checklist 对齐 Schema → 代码 → 字典 → 前端 → SQL 交付;AI 规范文档(`.ai/`、`.cursor/rules/`)面向 Agent 统一为英文。
- 新增了 `.ai/AI_PAGE_QUALITY.md`(列表页 `art-full-height`、`useDictStore`、弹窗排版等整页质量规范)。
- 同步更新了 Trae / Kiro steering 与 `.cursor/rules/general.mdc`(模块开发触发语、Git 只读、MCP 查库优先)。
- 重写了 [AI 开发指南](/guide/ai-guide.html):三档提示词(一句话 / 两句 / 结构化)、网页版短前缀指引,移除过时内联代码模板。
- 新增了 OpenAI Codex 分层指令(根目录及 `server/`、`admin/` 的 `AGENTS.md`)与 `.codex/README.md`。
- 新增了 Claude Code 配置(`.claude/CLAUDE.md`、`.claude/rules/`),规则语义与 Cursor / Trae 对齐。
- 新增了 VS Code / Cursor 开发配置(`.vscode/`:settings、extensions、tasks、launch)及多根工作区 `elysia-admin.code-workspace`。
- 新增了后端调试命令:`dev:debug`(IDE Attach 6499)、`dev:debug:browser`(推荐,配合 [debug.bun.sh](https://debug.bun.sh))、`dev:debug:watch`(调试 + 热重载,默认不推荐)。
- 新增了 `server/script/dev-debug.ts` 调试启动脚本,终端提示浏览器断点流程。
- 调整了 `.vscode/launch.json`:改用内置 Node 调试器 Attach 6499,替代原 `type: bun`(Cursor / Trae 下扩展调试适配器常不可用);`.vscode/tasks.json` 补充 `Server: dev:debug` 相关任务。
- 补充了 [内置命令](/architecture/commands.html) 文档:独立「调试命令」「构建命令」章节,说明命令与编辑器 Tasks / Launch 配套用法。
:::
::: timeline v1.4.6(2026-06-08)
- 新增了启动期 Zod strict 校验 `*.yaml` 配置,缺字段、类型错误或未知 key 时 fail-fast 并输出可读错误。
- 修复了 PostgreSQL 连接池 `connect_timeout` 配置未生效(原误读 `connection_timeout`)。
- 修复了 PM2 生产部署下队列 Worker 沙箱 processor 子进程找不到 `production.yaml` 的问题(`CONFIG_PATH` 误解析为 `dist/dist/` 路径)。
- 修复了用户管理删除用户失败的问题。
- 修复了用户弹窗「编辑 → 新增」切换时表单未完整重置、异步详情回填错乱的问题(Gitee IJRSXJ)。
- 修复了登录后菜单/权限数据为空时路由初始化失败,500 页无法返回登录页的问题;菜单校验失败时自动登出并提示,异常页支持「重新登录」兜底。
:::
::: timeline v1.4.5(2026-06-01)
- 修复了字典类型弹窗在「编辑 → 新增」切换时表单未完整重置,偶发提交 400 的问题。
- 更新了字典类型编码修改逻辑:同步级联更新下属字典数据的 `dictType`,并清理类型/数据相关 Redis 缓存;前端同步刷新右侧字典值列表与当前选中类型。
- 新增了 `sha256WithRsa`(`RSA-SHA256`)供 v3 请求签名与 JSAPI 调起签名使用,与 OpenSSL 算法名对齐。
- 优化了 `buildWechatOrderBody`,仅白名单透传 `attach`、`goods_tag`、`time_expire` 等文档允许的可选字段,避免将 `openid`、`clientIp` 等由各渠道自行处理的字段误写入通用下单 body。
- 更新了微信支付回调验签,统一使用 `RSA-SHA256`(`crypto.createVerify`),与微信 v3 平台证书验签规范一致。
- 接入了 Bun 内置 `bun test`(`test` / `test:watch` / `test:coverage`),新增 `server/test/` 纯函数与 `QueryBuilder` 单元测试基线。
- 补充了架构文档测试命令说明([内置命令 - 测试命令](/architecture/commands.html#测试命令))。
:::
::: timeline v1.4.4(2026-05-21)
- 修复了在环境中同时存在 Node.js v24 时,`@bull-board/elysia` 顶层静态 import 触发 CJS `require()` 加载含 Top-Level Await 的 ESM 模块而报错的问题:将 `@bull-board/api`、`@bull-board/elysia` 及 `queues` 的 import 由顶层静态引入改为 `configureBullMQUI` 函数内部的动态 `await import()`。
- 文档:补充了存储配置指南中各服务商(RustFS、COS、OSS、Kodo)的后端配置字段示例。
:::
::: timeline v1.4.3(2026-05-20)
- 将系统用户主键 `userId` 由自增数字改为 UUID 字符串,避免管理端与小程序等多套用户表在 Redis 在线会话、令牌等场景下 ID 冲突;同步调整 `createBy`/`updateBy` 及关联表、前后端类型定义。
- 修复了在线用户强退无效的问题:强退接口不再将 UUID 转为数字,并同步清除刷新令牌与菜单缓存。
- 优化了在线监控页:强退当前登录账号后自动执行前端登出。
:::
::: timeline v1.4.2(2026-05-18)
- 修复了菜单删除或禁用后左侧栏仍显示的问题:用户菜单查询增加 `delFlag`、`status` 过滤,菜单变更后自动清除 Redis 菜单缓存。
- 优化了菜单管理页:保存或删除后自动刷新侧边栏动态路由(后端权限模式)。
:::
::: timeline v1.4.1(2026-05-16)
- 规范了 HTTP 路由注册方式,并收紧路由、接口处理函数的类型定义,便于 IDE 提示与排查。
- 优化了路由自动加载:某个业务模块加载失败时会在日志中明确提示,不再静默跳过。
- 优化了队列 Worker 与业务模块的耦合方式:沙箱任务统一登记,基础设施层不再直接引用业务代码。
- 新增了请求上下文类型 `AppContext`,业务接口可 typed 访问当前用户、路由权限等信息,减少 `(ctx as any)` 写法。
- 生产构建时预生成的路由列表带类型信息,TypeScript 检查更准确。
- 更新了后端开发规范与工程配置,与当前目录划分(业务类型 / 声明文件 / 沙箱任务)保持一致。
- 调整了定时任务与 Redis 分布式锁的代码归属,与「工具层不依赖核心层」的架构约定一致。
:::
::: timeline v1.4.0(2026-05-15)
- 优化了订单管理前后端功能。
- 新增了支付调试能力。
- 优化了服务端启动日志的版本提示。
- 更新了项目文档与 AI 开发指引。
> 自 2026-05-15 起,版本号按功能域重新对齐。原文档中的 v1.2.2 对应 v1.3.2,当前最新版为 v1.4.2。
:::
::: timeline v1.3.2(2026-05-13)
- 新增了服务端预编译能力。
- 新增了公开注册 `allowPublicRegister` 与 `ALLOW_PUBLIC_REGISTER` 开关,生产配置默认关闭。
- 修复了注册假成功(未向上抛出异常)与重置密码操作顺序不一致的问题。
- 优化了 IP 获取逻辑,仅在 `trustProxy` 受信代理场景下解析 `X-Forwarded-For`。
- 增加了 IP 归属地查询超时与降级处理,失败不再影响登录流程。
- IP 路由限流改为 Redis 原子计数,消除高并发下的「先读后写」竞态。
- 修复了 `BaseResultData.fail` 在 500 状态码下错误日志拼接问题。
- 优化了 `WithCache`,现在可正确缓存 `0`、`false`、空数组等合法结果。
- 统一了 CORS/静态资源配置。
- 规范了 `SIGINT`/`SIGTERM` 服务关闭顺序,移除重复的信号处理。
- 为 `IConfig` 补充了针对 `app.listen` 返回值类型的 TypeScript 修复。
:::
::: timeline v1.3.1(2026-05-12)
- 新增了退款接口。
- 新增了 AI 开发规范文件。
- 修复了 `WithCache` 缓存命中判断缺陷(空数组和对象类型缓存永远未命中问题)。
- 修复了权限守卫读取路径错误,细粒度接口权限校验现已正确生效。
- 修复了 `GetMenuPermissionByRoleIds` 和 `GetRoleMenuIdsAndBtnIds` 在角色无按钮权限时触发全表扫描,导致权限数据返回异常的严重 Bug。
- 优化了 `FindPage` 分页查询,使用窗口函数 `COUNT(*) OVER()` 将常规分页由 2 次 DB 查询合并为 1 次。
- 优化了认证守卫,去除了 JWT 重复解析(移除冗余的 `ParseToken` 调用)。
- 优化了 IP 黑名单缓存,SQL 层直接过滤 `status=true` 数据,减少缓存体积。
- 修复了角色权限变更时 `forEach(async)` 并发无序问题,改为串行处理确保稳定性。
:::
::: timeline v1.3.0(2026-05-10)
- 新增了支付适配层与路由分发。
- 新增了支付宝 PC / H5 / App / 小程序各端接入,并修复了各端支付与回调问题。
- 新增了微信支付、PayPal 多渠道接入。
- 新增了 PC 端支付完成处理与异步通知回调校验优化。
- 同步更新了支付相关文档。
:::
::: timeline v1.2.0(2026-04-18)
- 修复了配置角色权限后菜单不更新等问题。
- 移除了 `build:binary` 命令,具体原因可在 [常见问题](/other/faq.md) 中查看。
- 优化了创建订单接口。
- 修复了生产环境 Worker 处理器路径错误。
:::
::: timeline v1.1.0(2026-04-15)
- 新增了 `BullMQ`,添加了队列功能。
- 基于 `BullMQ` 重写了定时任务功能。
:::
::: timeline v1.0.3(2026-04-10)
- 扩充了项目文档体系(快速开始、架构、中间件等章节)。
- 调整了 `server` 中的部分接口目录。
:::
::: timeline v1.0.2(2026-04-01)
- 新增了订单模块初版接口。
:::
::: timeline v1.0.1(2026-03-24)
- 新增了业务模块。
- 规范化了部分代码结构。
- 修复了数据库迁移交互不显示的问题。
:::
::: timeline v1.0.0(2026-03-04)
- 整个系统前后端全部可用。
- 完成了认证、字典、菜单/角色/部门、API 管理、种子数据等核心模块。
- 新增了四重守卫与全局中间件、登录日志、操作日志。
- 新增了存储/文件上传、定时任务(Redis 分布式锁)。
- 新增了缓存模块、接口限流、登录次数限制。
- 启用了 YAML 配置,优化了 Drizzle ORM 配置。
- 修复了 PM2 在线部署等问题。
:::
## v0
::: timeline v0.0.0(2025-12-12)
- 创建项目,第一次提交。
> [CSDN文章链接:](https://blog.csdn.net/weixin_63443072/article/details/158653317?spm=1001.2014.3001.5501)
>
> [掘金文章链接:](https://juejin.cn/post/7612957140249362482)
:::
---
URL: "https://elysia-admin.top/start/introduction.html"
LLMS_URL: "https://elysia-admin.top/\\start\\introduction.md"
title: "介绍 - Elysia Admin"
description: "了解 Elysia Admin 是什么、核心特色、技术选型及内置功能,快速掌握这套基于 ElysiaJS + Vue3 的全栈后台管理解决方案。"
---
---
title: 介绍 - Elysia Admin
description: 了解 Elysia Admin 是什么、核心特色、技术选型及内置功能,快速掌握这套基于 ElysiaJS + Vue3 的全栈后台管理解决方案。
---
# 介绍
Elysia Admin
一个基于 [ElysiaJS](https://elysiajs.com/) + [Art Design Pro](https://www.artd.pro/docs/zh/) 的现代化全栈后台管理系统。
## Elysia Admin 是什么?
`Elysia Admin` (elysia-admin)是一套基于 `ElysiaJS` 后端与 `Vue 3` 前端的全栈解决方案。我们致力于解决国内 `Node.js` 开发者在构建后台系统时的痛点,提供一套开箱即用的高性能、高颜值管理模板。
本项目的核心特色在于其独特的后端架构:我们彻底告别了繁琐且难以维护的链式调用,创新性地引入了类似 `Vue Router` 的配置化写法。这种模块化的路由配置方式,不仅让代码逻辑更加清晰直观,更完美契合了国内开发者的思维习惯,让你能更专注于业务逻辑,充分释放 `Bun` 运行时的强大性能潜力。

## 什么时候使用?
如果你符合以下任何一种情况,`Elysia Admin` 将是你的最佳选择:
- 全栈TypeScript拥趸:追求端到端的类型安全,希望前后端共享类型定义,消除类型不一致带来的 Bug。
- 资源受限环境:部署服务器配置有限,或追求极致性价比,需要一款内存占用极低(~80MB)的高性能框架。
- 拒绝链式调用:厌倦了 `Elysia` 传统的链式 API 写法,渴望像 `Vue Router` 一样通过配置式路由来管理接口,让代码结构清晰、易于维护。
- 追求开发效率:希望降低学习成本,拒绝重复造轮子,需要一套开箱即用、规范化的全栈脚手架。
## 在线体验
- 官网地址:
- 演示地址:
- 账号:`admin`
- 密码:`123456` (建议仅用于体验,勿修改核心数据)
- 代码下载:
- gitee仓库:
- github仓库:
**提示**:演示环境为公共测试服,请勿进行破坏性操作。
## 主要特征
本项目不仅是一个脚手架,更是一套经过优化的企业级解决方案:
- 前端基于 `Art Design Pro` 构建,继承了其所有现代化特性。提供精美、响应式的界面设计,开箱即用的高级组件。
- 创新实现后端 **接口与定时任务的自动注册机制**。无需繁琐的手动引入,系统自动扫描并加载模块,极大降低了项目配置的复杂度,让开发更专注于业务逻辑。
- 从数据库到前端全程 `TypeScript` 覆盖。后端集成 `DrizzleORM`,利用其强大的类型推导能力,实现数据库操作与 API 响应的端到端类型安全,拒绝 `any`。
- 区别于传统的链式调用,采用类似 `Vue Router` 的 **配置式路由架构**。将接口路径、方法、权限与处理函数解耦,结构清晰,逻辑分明,完美契合国内开发者的工程化思维。
## 技术选型
本项目严格遵循现代化全栈开发标准,采用高性能、类型安全的技术栈构建。
### 单仓目录与运行时
仓库内与日常开发最相关的是 **admin**(Vue 管理端)、**server**(Elysia 接口服务)以及文档 **docs**;
线上典型路径是浏览器访问 **admin**,通过 **HTTP** 调用 **server**,由后者访问 **PostgreSQL** 做持久化、访问 **Redis** 做会话、缓存与队列等。
下面示意图把「界面 → API → 数据」与下一小节「环境依赖」里的 PostgreSQL、Redis、Bun 等条目对齐;本地开发则多为「前端 dev server + 后端监听端口」,与图中所述依赖方向一致。
```mermaid
flowchart TB
subgraph Client["前端 admin"]
V[Vue 3 + Vite]
end
subgraph Server["后端 server"]
E[ElysiaJS on Bun]
end
subgraph Data["数据与中间件"]
PG[(PostgreSQL)]
RD[(Redis)]
end
V <-->|HTTP API| E
E --> PG
E --> RD
```
若单独启动队列 **Worker** 消费任务,通常仍通过同一 **Redis** 与 HTTP 服务协作,详见 [队列指南](/guide/queue)。
### 环境依赖
**必须** :
- [`Node.JS >= 22.0.0`](https://nodejs.org/)
- [`Bun >= 1.2.21`](https://bun.com/)
- [`Redis >= 6`](https://redis.io/)
- [`PostgreSQL >= 16`](https://www.postgresql.org/)
**可选** :
- [`Nvm`](https://github.com/nvm-sh/nvm)
- [`Pnpm`](https://pnpm.io/)
- [`RustFS`](https://rustfs.com.cn/)
- [`Docker`](https://www.docker.com/)
- [`PM2`](https://pm2.keymetrics.io/)
### 前端技术
- 框架核心:[`Vue 3`](https://vuejs.org/) + [`TypeScript`](https://www.typescriptlang.org/) (端到端类型安全)
- 构建工具:[`Vite`](https://vitejs.dev/) (极速热更新)
- 状态管理:[`Pinia`](https://pinia.vuejs.org/) (Vue 官方推荐)
- 路由系统:[`Vue Router`](https://router.vuejs.org/zh/)
- UI 组件库:[`Element Plus`](https://element-plus.org/zh-CN/) + [`Tailwind CSS`](https://tailwindcss.com/) (原子化 CSS 辅助)
- 样式处理:[`Scss`](https://scss-lang.com/) (增强样式可维护性)
### 后端依赖
- 核心框架:[`ElysiaJS`](https://elysiajs.com/) (Bun 生态高性能框架)
- 数据持久层:[`Drizzle ORM`](https://orm.drizzle.team/) (类型推断优先,比传统 ORM 更轻量)
- 数据库驱动:[`Postgres`](https://github.com/porsager/postgres) (轻量级 PG 驱动) + [`Ioredis`](https://ioredis.org/)
- 工具库:
- [`Jose`](https://github.com/panva/jose):JWT/OIDC 标准加密处理。
- [`Croner`](https://github.com/hexagon/croner):轻量级定时任务调度。
- [`nodemailer`](https://nodemailer.com/):邮件发送服务。
- [`BullMQ`](https://bullmq.io/):任务队列处理。
## 内置功能
### 认证与权限
- 全面认证:支持用户注册、登录、忘记密码及密码修改。采用双 Token (Access/Refresh) 认证机制,确保持续会话的安全性。
- 用户管理:完善的用户 CRUD 功能,支持查看用户详情、分配角色及所属部门。
- 角色管理:标准的 RBAC 模型,支持自定义角色名称、描述及细粒度的权限分配。
- 菜单管理:前后端联动的动态菜单系统。支持配置名称、路径、图标及权限标识,实现菜单与权限的实时同步。
- 部门管理:可视化的组织架构管理,支持多层级部门结构与详细描述。
- 字典管理:系统级数据字典维护,支持名称、类型、值的动态配置,极大提升业务开发的灵活性。
- 通知公告:系统管理下的公告维护,通知类型复用数据字典,内容支持富文本编辑。
- 接口熔断:支持动态修改接口状态。无需重启服务即可实现接口的灰度发布或紧急下线,运维响应更从容。
- 防爆破与黑名单:内置 IP 黑名单机制。登录接口集成防爆破逻辑,多次验证失败自动拉黑 IP,有效防御恶意攻击。
- 在线用户监控:实时查看当前在线用户列表(含用户名、IP、状态),支持管理员强制下线指定用户。
- 日志审计:完善的日志系统,包含登录日志与操作日志。支持在服务端灵活配置记录级别,满足合规审计需求。
- 存储配置 (S3):支持配置各类 S3 兼容存储(如 RustFS、阿里云 OSS、腾讯云 COS 等)。采用后端生成签名 URL、前端直传模式,完全不占用应用服务器带宽。
- 缓存数据管理:可视化的存储列表查看器。支持在线查看、调试及修改系统缓存数据,极大便利了开发调试与问题排查。
- 请求加解密:可选的 RSA-OAEP + AES-GCM 信封。前后端同时开启后,JSON 接口以密文传输;未带加密头的请求仍走明文。详见 [请求加解密](/guide/crypto)。
### 系统与工具
- 定时任务调度:内置可视化任务管理面板。支持 Cron 表达式配置,可动态启动或暂停任务,轻松处理周期性业务。
- 自动化接口文档:后端自动生成交互式 API 文档。开发调试无需借助第三方工具,浏览器即可查看与测试接口。
- 站点与证书:后台维护域名和反代路径,预览 Nginx 配置,并通过 Let's Encrypt 申请、续签证书。详见 [站点部署](/guide/site-deploy)。
### 支付模块
- 多渠道支付接入:统一适配层 `Pay(channel, platform)` 支持支付宝、微信支付、PayPal,按渠道 + 终端自动路由到对应实现。
- 商户配置管理:支持多商户、多渠道配置,密钥仅存储在服务端,不下发前端。
- 下单 / 查询 / 退款:统一接口签名,各渠道差异由适配层屏蔽,业务侧只需调用 `create`、`query`、`refund`。
- 异步回调验签:内置各渠道验签与解密逻辑,业务层只需处理 `NotifyResult`,无需关心原始报文格式。
## 源码分支
| 名称 | 描述 | 地址 | 进度 |
| --- | --- | --- | --- |
| `master` | 主分支,包含最新版本的代码 | [https://gitee.com/nian-qian/elysia-admin/tree/master](https://gitee.com/nian-qian/elysia-admin/tree/master) | `已完成` |
---
URL: "https://elysia-admin.top/start/quick-start.html"
LLMS_URL: "https://elysia-admin.top/\\start\\quick-start.md"
title: "快速开始 - Elysia Admin"
description: "Elysia Admin 快速启动指南,包含前置依赖安装、源码下载、数据库创建、项目配置及运行步骤,帮助你快速搭建开发环境。"
---
---
title: 快速开始 - Elysia Admin
description: Elysia Admin 快速启动指南,包含前置依赖安装、源码下载、数据库创建、项目配置及运行步骤,帮助你快速搭建开发环境。
---
# 快速开始
本章将提供 `Elysia Admin` 项目的快速启动指南,帮助您快速搭建开发环境并运行项目。
## 前置依赖
> **重要:** 请确保已安装以下环境或软件,否则可能导致项目依赖安装失败或启动异常。
- [`Node.js >= 22.0.0`](https://nodejs.org/)
- [`Bun >= 1.2.21`](https://bun.com/)
- [`Redis >= 6`](https://redis.io/)
- [`PostgreSQL >= 16`](https://www.postgresql.org/)
## 下载源码
::: code-group
```bash [gitee]
git clone https://gitee.com/nian-qian/elysia-admin.git
```
```bash [github]
git clone https://github.com/meishanlaoyao/elysia-admin.git
```
:::
## 启动项目
本项目前端使用 `pnpm` 管理依赖,后端使用 `bun` 运行项目。
### 安装依赖
::: code-group
```bash [bun]
# 安装后端依赖
bun install
```
```bash [pnpm]
# 安装前端依赖
pnpm install
```
:::
### 创建数据库
首先需要在 PostgreSQL 中创建一个数据库,以下使用 `Navicat Premium 17` 工具演示数据库创建过程:

> **注意:** 数据库创建完成后即可关闭。在 `开发环境` 下,启动后端服务时会自动初始化数据库;在 `生产环境` 下不会自动初始化数据库,以防止误操作线上已有的数据库。
### 配置项目
#### 后端配置
后端配置文件位于 `/server/src/config/` 目录下,请根据实际部署环境修改对应配置文件:
- `development.yaml`:开发环境配置
- `production.yaml`:生产环境配置
#### 前端配置
前端配置文件位于 `/admin/` 目录下,请根据实际部署环境修改对应配置文件:
- `env.development`:开发环境配置
- `env.production`:生产环境配置
### 运行项目
::: code-group
```bash [bun]
# 运行后端项目
bun dev
```
```bash [pnpm]
# 运行前端项目
pnpm dev
```
:::
后端启动成功后,控制台会输出以下信息:
```bash [terminal]
============================================================
🚀 Elysia-Admin 启动成功
============================================================
服务地址: http://localhost:3000/api
API文档: http://localhost:3000/api/openapi
OpenAPI JSON: http://localhost:3000/api/openapi/json
启动时间: 2026/4/6 11:17:38
运行环境: development
Bun版本: 1.3.8
进程ID: 25360
============================================================
```
前端项目启动后会自动打开浏览器,默认访问地址为:。
### 问题排查
1. 若在启动过程中遇到问题,可通过顶部搜索框搜索相关解决方案。
2. 若问题仍未解决,请访问代码仓库平台创建 issue,我们会及时处理。