mirror of
https://github.com/certd/certd.git
synced 2026-07-14 01:17:31 +08:00
feat: 支持审计日志,操作日志
This commit is contained in:
@@ -1,437 +0,0 @@
|
||||
# 审计日志功能实施计划(MVP 版本)
|
||||
|
||||
## 一、摘要
|
||||
|
||||
为 Certd 系统增加基础审计日志功能,记录用户关键操作(流水线增删改/执行、授权管理、站点监控、通知设置、API密钥、用户管理、角色权限、项目管理、系统设置等),支持管理员和用户分别在管理后台和用户端查看操作日志。
|
||||
|
||||
**关键发现**:`cd_audit_log` 表已通过数据库迁移 v10038 创建,`AuditLogEntity` 实体已定义,字段完整可直接使用,写入和查询功能完全未实现。
|
||||
|
||||
> **MVP 范围**:仅实现简要记录(谁在何时对什么做了什么),流水线差异对比(diffContent)后续版本再补充。
|
||||
|
||||
## 二、当前状态分析
|
||||
|
||||
### 已存在的基础设施
|
||||
|
||||
| 组件 | 路径 | 状态 |
|
||||
|------|------|------|
|
||||
| 审计日志表 | `cd_audit_log`(迁移 v10038__admin_mode.sql) | ✅ 已创建 |
|
||||
| 审计日志实体 | `packages/ui/certd-server/src/modules/sys/enterprise/entity/audit-log.ts` | ✅ 已定义 |
|
||||
| BaseService 钩子 | `BaseService.modifyAfter(data)` | ✅ 已预留(空实现) |
|
||||
|
||||
### 审计日志表字段
|
||||
|
||||
| 字段 | 类型 | 说明 |
|
||||
|------|------|------|
|
||||
| `id` | number (PK) | 主键 |
|
||||
| `userId` | number | 操作人ID |
|
||||
| `userName` | string(128) | 操作人用户名 |
|
||||
| `projectId` | number | 项目ID(企业模式) |
|
||||
| `projectName` | string(512) | 项目名称 |
|
||||
| `type` | string(128) | 操作模块类型 |
|
||||
| `action` | string(128) | 操作动作 |
|
||||
| `content` | text | 操作内容摘要描述 |
|
||||
| `ipAddress` | string(128) | 操作人IP |
|
||||
| `createTime` | Date | 创建时间 |
|
||||
| `updateTime` | Date | 修改时间 |
|
||||
|
||||
**无新增字段**,直接使用现有表结构。
|
||||
|
||||
### `ctx.user` 可用字段
|
||||
|
||||
JWT 登录路径:`{ id, username, roles }`
|
||||
OpenKey 认证路径:`{ id, roles, projectId }`(**无 username**)
|
||||
|
||||
> **注意**:审计时若需 username 而 ctx.user.username 不存在,需要通过 userId 查询 UserEntity 获取。
|
||||
|
||||
## 三、需求范围
|
||||
|
||||
### 审计类型(type)与动作(action)
|
||||
|
||||
| 序号 | 模块 | type | action | 说明 |
|
||||
|------|------|------|--------|------|
|
||||
| 1 | 流水线 | `pipeline` | `add`/`update`/`delete`/`execute`/`cancel`/`batchDelete`/`batchUpdate` | 记录流水线的增删改、手动执行、取消、批量操作 |
|
||||
| 2 | 授权管理 | `access` | `add`/`update`/`delete` | 记录云厂商授权凭据的增删改 |
|
||||
| 3 | 站点监控 | `monitor` | `add`/`update`/`delete`/`batchDelete` | 记录站点证书监控的增删改 |
|
||||
| 4 | 通知设置 | `notification` | `add`/`update`/`delete` | 记录通知配置的增删改 |
|
||||
| 5 | API密钥 | `openKey` | `add`/`update`/`delete` | 记录开放接口密钥的增删改 |
|
||||
| 6 | 用户管理 | `user` | `add`/`update`/`delete` | 记录系统管理员对用户的增删改 |
|
||||
| 7 | 角色管理 | `role` | `add`/`update`/`delete` | 记录角色的增删改 |
|
||||
| 8 | 权限管理 | `permission` | `add`/`update`/`delete` | 记录权限的增删改 |
|
||||
| 9 | 项目管理 | `project` | `add`/`update`/`delete`/`disable` | 记录项目的增删改/启停 |
|
||||
| 10 | 系统设置 | `settings` | `update` | 记录系统配置的修改 |
|
||||
|
||||
### 关键策略
|
||||
|
||||
- **存储**:仅数据库(使用已有 `cd_audit_log` 表,无新增字段/迁移)
|
||||
- **粒度**:简要记录(谁对什么做了什么),无差异对比
|
||||
- **保留策略**:按 90 天自动清理(硬编码,不提供配置界面,后续迭代再补配置)
|
||||
- **前端**:管理后台(`/sys/audit` 查看全部) + 用户端(`/certd/audit` 仅查看自己的)
|
||||
|
||||
## 四、实施方案
|
||||
|
||||
### 4.1 后端:新增文件
|
||||
|
||||
#### 4.1.1 AuditService
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/modules/sys/enterprise/service/audit-service.ts`
|
||||
|
||||
```typescript
|
||||
@Provide()
|
||||
export class AuditService {
|
||||
@Inject()
|
||||
dataSourceManager: TypeORMDataSourceManager;
|
||||
|
||||
getRepository(): Repository<AuditLogEntity> {
|
||||
return this.dataSourceManager.getDataSource('default').getRepository(AuditLogEntity);
|
||||
}
|
||||
|
||||
// 写入审计日志
|
||||
async log(params: {
|
||||
userId: number;
|
||||
type: string;
|
||||
action: string;
|
||||
content: string;
|
||||
username?: string;
|
||||
projectId?: number;
|
||||
projectName?: string;
|
||||
ipAddress?: string;
|
||||
}): Promise<void>;
|
||||
|
||||
// 分页查询
|
||||
async page(pageReq: PageReq<AuditLogEntity>): Promise<PageResult>;
|
||||
|
||||
// 删除指定天数之前的日志
|
||||
async cleanExpired(retentionDays: number): Promise<number>;
|
||||
}
|
||||
```
|
||||
|
||||
**关键实现细节**:
|
||||
- `log()` 中若 `username` 为空,通过 `userId` 查 `UserEntity` 获取,查询失败也写入(username留空)
|
||||
- `log()` 用 try-catch 包裹,写入失败仅 log error,不影响主业务
|
||||
- `page()` 不做额外过滤,由 Controller 层控制 userId 过滤逻辑
|
||||
- `cleanExpired()` 直接 DELETE WHERE `create_time < now - retentionDays`
|
||||
|
||||
#### 4.1.2 审计日志 Controller(管理后台)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/sys/audit/audit-controller.ts`
|
||||
|
||||
路由前缀:`/api/sys/audit`
|
||||
|
||||
```typescript
|
||||
@Provide()
|
||||
@ApiTags(['audit'])
|
||||
@Controller('/api/sys/audit')
|
||||
export class SysAuditLogController {
|
||||
@Inject()
|
||||
auditService: AuditService;
|
||||
|
||||
// POST /page - 分页查询(不过滤 userId,支持 type/action/userId/时间范围 筛选)
|
||||
// POST /delete - 删除单条日志
|
||||
// POST /clean - 手动清理过期日志
|
||||
}
|
||||
```
|
||||
|
||||
权限:`sys:settings:view`(查看)/ `sys:settings:edit`(删除/清理)
|
||||
|
||||
#### 4.1.3 审计日志 Controller(用户端)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/audit/audit-controller.ts`
|
||||
|
||||
路由前缀:`/api/pi/audit`
|
||||
|
||||
```typescript
|
||||
@Provide()
|
||||
@ApiTags(['audit'])
|
||||
@Controller('/api/pi/audit')
|
||||
export class AuditLogController extends BaseController {
|
||||
@Inject()
|
||||
auditService: AuditService;
|
||||
|
||||
// POST /page - 分页查询(自动过滤当前 userId)
|
||||
}
|
||||
```
|
||||
|
||||
权限:`authOnly`
|
||||
|
||||
#### 4.1.4 审计日志清理 Cron 任务
|
||||
|
||||
在 `AuditService` 中注册 cron:每天凌晨 3:00 执行,保留 90 天。
|
||||
|
||||
> 清理注册方式参考 `PipelineService` 中的 `this.cron.register()` 模式。
|
||||
|
||||
### 4.2 后端:修改现有文件
|
||||
|
||||
在以下 Controller 中注入 `AuditService` 并在关键方法中调用 `this.auditService.log(...)`。
|
||||
|
||||
#### 4.2.1 PipelineController(流水线)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/pipeline/pipeline-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `save` (新增, bean.id ≤ 0) | `add` | `创建了流水线「{title}」(ID:{id})` |
|
||||
| `save` (修改, bean.id > 0) | `update` | `修改了流水线「{title}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了流水线(ID:{id})` |
|
||||
| `trigger` | `execute` | `手动执行了流水线「{title}」(ID:{id})` |
|
||||
| `cancel` | `cancel` | `取消了流水线执行(historyId:{historyId})` |
|
||||
| `batchDelete` | `batchDelete` | `批量删除了{count}条流水线` |
|
||||
| `batchUpdateGroup` | `batchUpdate` | `批量修改了{count}条流水线分组` |
|
||||
| `batchUpdateTrigger` | `batchUpdate` | `批量修改了{count}条流水线触发器` |
|
||||
| `batchUpdateNotification` | `batchUpdate` | `批量修改了{count}条流水线通知` |
|
||||
| `batchUpdateCertApplyOptions` | `batchUpdate` | `批量修改了{count}条流水线证书申请配置` |
|
||||
| `batchRerun` | `execute` | `批量重新执行了{count}条流水线` |
|
||||
| `batchTransfer` | `batchUpdate` | `批量迁移了{count}条流水线` |
|
||||
| `refreshWebhookKey` | `update` | `刷新了流水线(ID:{id})的Webhook密钥` |
|
||||
|
||||
**接入方式**:在方法末尾、返回 `this.ok(...)` 之前调用 `this.auditService.log(...)`。对 `save` 方法,根据 `bean.id > 0` 区分 add/update。对 `delete`/`trigger`/`cancel` 等方法,由于方法签名限制,content 中尽量使用已有变量,查不到标题就用 ID 描述。
|
||||
|
||||
#### 4.2.2 AccessController(授权管理)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/pipeline/access-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了授权「{name}」(ID:{id}, 类型:{type})` |
|
||||
| `update` | `update` | `修改了授权「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了授权(ID:{id})` |
|
||||
|
||||
#### 4.2.3 SiteInfoController(站点监控)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/monitor/site-info-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了站点监控「{name}」(ID:{id}, 域名:{domain})` |
|
||||
| `update` | `update` | `修改了站点监控「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了站点监控(ID:{id})` |
|
||||
| `batchDelete` | `batchDelete` | `批量删除了{count}条站点监控` |
|
||||
|
||||
#### 4.2.4 NotificationController(通知设置)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/pipeline/notification-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了通知配置「{name}」(ID:{id}, 类型:{type})` |
|
||||
| `update` | `update` | `修改了通知配置「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了通知配置(ID:{id})` |
|
||||
|
||||
#### 4.2.5 OpenKeyController(API密钥)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/user/open/open-key-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了API密钥(ID:{id}, scope:{scope})` |
|
||||
| `update` | `update` | `修改了API密钥(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了API密钥(ID:{id})` |
|
||||
|
||||
#### 4.2.6 UserController(用户管理 - 管理后台)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/sys/authority/user-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了用户「{username}」(ID:{id})` |
|
||||
| `update` | `update` | `修改了用户「{username}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了用户(ID:{id})` |
|
||||
|
||||
#### 4.2.7 RoleController(角色管理 - 管理后台)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/sys/authority/role-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了角色「{name}」(ID:{id})` |
|
||||
| `update` | `update` | `修改了角色「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了角色(ID:{id})` |
|
||||
|
||||
#### 4.2.8 PermissionController(权限管理 - 管理后台)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/sys/authority/permission-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了权限「{name}」(ID:{id})` |
|
||||
| `update` | `update` | `修改了权限「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了权限(ID:{id})` |
|
||||
|
||||
#### 4.2.9 ProjectController(项目管理 - 管理后台)
|
||||
|
||||
**文件**:`packages/ui/certd-server/src/controller/sys/enterprise/project-controller.ts`
|
||||
|
||||
| 方法 | 审计 action | content 模板 |
|
||||
|------|------------|-------------|
|
||||
| `add` | `add` | `新增了项目「{name}」(ID:{id})` |
|
||||
| `update` | `update` | `修改了项目「{name}」(ID:{id})` |
|
||||
| `delete` | `delete` | `删除了项目(ID:{id})` |
|
||||
| `setDisabled` | `disable` | `{启用\|禁用了}项目「{name}」(ID:{id})` |
|
||||
|
||||
#### 4.2.10 系统设置 Controller
|
||||
|
||||
- **SysSettingsController**:`save` → `type=settings, action=update, content=修改了系统设置`
|
||||
- **SysSafeSettingsController**:`save` → `type=settings, action=update, content=修改了安全设置`
|
||||
|
||||
### 4.3 后端:数据库迁移
|
||||
|
||||
**无需新增迁移**。使用已有 `cd_audit_log` 表,无新增字段。
|
||||
|
||||
### 4.4 后端:通用辅助
|
||||
|
||||
#### 4.4.1 审计日志提取 helper
|
||||
|
||||
创建一个轻量的提取函数,减少 Controller 中的重复代码:
|
||||
|
||||
```typescript
|
||||
// 在 controller 中各方法调用示例
|
||||
async add(@Body(ALL) bean: XxxEntity) {
|
||||
const result = await this.service.add(bean);
|
||||
await this.auditService.log({
|
||||
userId: this.getUserId(),
|
||||
username: this.ctx.user?.username,
|
||||
type: 'xxx',
|
||||
action: 'add',
|
||||
content: `新增了xxx「${bean.name}」(ID:${result.id})`,
|
||||
ipAddress: this.ctx.ip,
|
||||
});
|
||||
return this.ok(result);
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 前端:新增文件
|
||||
|
||||
#### 4.5.1 用户端审计日志页
|
||||
|
||||
```
|
||||
packages/ui/certd-client/src/views/certd/audit/
|
||||
├── api.ts
|
||||
├── crud.tsx
|
||||
└── index.vue
|
||||
```
|
||||
|
||||
- **路由**:`/certd/audit`
|
||||
- **菜单位置**:放在「设置」子菜单中
|
||||
- **权限**:`authOnly`
|
||||
- **功能**:展示当前用户的操作日志,支持按 type/action/时间范围 筛选
|
||||
- **列**:操作时间、操作类型(type)、动作(action)、内容(content)、IP地址
|
||||
|
||||
#### 4.5.2 管理后台审计日志页
|
||||
|
||||
```
|
||||
packages/ui/certd-client/src/views/sys/audit/
|
||||
├── api.ts
|
||||
├── crud.tsx
|
||||
└── index.vue
|
||||
```
|
||||
|
||||
- **路由**:`/sys/audit`
|
||||
- **菜单位置**:放在「系统管理」菜单中
|
||||
- **权限**:`sys:settings:view`
|
||||
- **功能**:展示所有用户的操作日志,支持按 userId/type/action/时间范围 筛选
|
||||
- **列**:操作时间、操作人(userName)、操作类型(type)、动作(action)、内容(content)、IP地址
|
||||
- **额外操作**:删除单条、手动清理过期日志按钮(需 `sys:settings:edit` 权限)
|
||||
|
||||
#### 4.5.3 路由配置
|
||||
|
||||
**修改** `packages/ui/certd-client/src/router/source/modules/certd.ts`,在 settings children 中添加:
|
||||
|
||||
```typescript
|
||||
{
|
||||
title: "certd.auditLog",
|
||||
name: "AuditLog",
|
||||
path: "/certd/audit",
|
||||
component: "/certd/audit/index.vue",
|
||||
meta: {
|
||||
icon: "ion:document-text-outline",
|
||||
auth: true,
|
||||
keepAlive: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
**修改** `packages/ui/certd-client/src/router/source/modules/sys.ts`,在 SysRoot children 中添加:
|
||||
|
||||
```typescript
|
||||
{
|
||||
title: "certd.sysResources.auditLog",
|
||||
name: "SysAuditLog",
|
||||
path: "/sys/audit",
|
||||
component: "/sys/audit/index.vue",
|
||||
meta: {
|
||||
icon: "ion:document-text-outline",
|
||||
permission: "sys:settings:view",
|
||||
keepAlive: true,
|
||||
auth: true,
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.5.4 国际化
|
||||
|
||||
在 `packages/ui/certd-client/src/locales/` 的语言包中添加:
|
||||
|
||||
```json
|
||||
{
|
||||
"certd.auditLog": "操作日志",
|
||||
"certd.sysResources.auditLog": "操作日志",
|
||||
"audit.type": "操作类型",
|
||||
"audit.action": "操作动作",
|
||||
"audit.content": "内容",
|
||||
"audit.ipAddress": "IP地址",
|
||||
"audit.userName": "操作人",
|
||||
"audit.createTime": "操作时间",
|
||||
"audit.cleanExpired": "清理过期日志"
|
||||
}
|
||||
```
|
||||
|
||||
## 五、实现顺序
|
||||
|
||||
1. **Phase 1:AuditService**
|
||||
- 创建 `AuditService`(`log`、`page`、`cleanExpired` 方法)
|
||||
- 注册审计日志清理 cron(默认保留 90 天)
|
||||
|
||||
2. **Phase 2:Controller 接入**
|
||||
- `SysAuditLogController` + `AuditLogController`(日志查询接口)
|
||||
- 逐个 Controller 接入审计日志:
|
||||
- PipelineController
|
||||
- AccessController
|
||||
- SiteInfoController
|
||||
- NotificationController
|
||||
- OpenKeyController
|
||||
- 管理后台 User/Role/Permission/Project/Settings Controllers
|
||||
|
||||
3. **Phase 3:前端**
|
||||
- 用户端 `/certd/audit` 页面
|
||||
- 管理后台 `/sys/audit` 页面
|
||||
- 路由 + 国际化
|
||||
|
||||
4. **Phase 4:测试**
|
||||
- AuditService 单元测试
|
||||
- 验证各模块操作是否正确写入日志
|
||||
|
||||
## 六、验证步骤
|
||||
|
||||
### 后端
|
||||
|
||||
1. 运行单元测试:`cd packages/ui/certd-server && npm run test:unit`
|
||||
2. 手动验证:
|
||||
- 创建/修改/删除流水线 → 检查 `cd_audit_log` 表
|
||||
- 手动触发流水线 → 检查 execute 记录
|
||||
- 增删改授权/监控/通知/密钥 → 检查对应记录
|
||||
- 管理后台用户/角色/权限/项目/设置操作 → 检查记录
|
||||
|
||||
### 前端
|
||||
|
||||
1. 用户端 `/certd/audit` → 仅显示当前用户日志
|
||||
2. 管理后台 `/sys/audit` → 显示全部日志,支持筛选和清理
|
||||
3. 运行 ESLint/Prettier
|
||||
|
||||
## 七、注意事项
|
||||
|
||||
1. **不记录敏感数据**:content 中不记录密码、密钥等敏感信息
|
||||
2. **不记录查询操作**:仅记录变更操作(add/update/delete/execute),不记录 page/list/info
|
||||
3. **异常不影响主流程**:审计日志写入失败用 try-catch 包裹,仅 log error
|
||||
4. **username 兼容性**:JWT 路径下 `ctx.user.username` 有值;OpenKey 路径下需从 UserService 查询
|
||||
5. **事务隔离**:审计日志独立写入,避免主业务事务回滚时丢失
|
||||
6. **批量操作**:批量操作一条审计记录记录概要信息,不为每个对象单独记录
|
||||
7. **后续迭代预留**:diffContent 字段和系统设置中的审计配置项在后续版本补充,本次不做迁移
|
||||
@@ -1,412 +0,0 @@
|
||||
# 插件依赖按需加载方案
|
||||
|
||||
## 背景与目标
|
||||
|
||||
### 当前问题
|
||||
- `packages/ui/certd-server/node_modules` 包含 50+ 个插件的所有依赖,体积庞大
|
||||
- 大量云厂商 SDK(AWS、阿里云、腾讯云、华为云等)只在特定插件中使用
|
||||
- 用户通常只使用少数几个插件,但必须安装所有依赖
|
||||
|
||||
### 目标
|
||||
实现依赖的按需下载和加载:
|
||||
1. 插件依赖独立管理,不占用主 `node_modules` 空间
|
||||
2. 只有当用户首次使用某插件时,才动态下载该插件需要的依赖
|
||||
3. 依赖安装完成后,通过 `await import()` 从独立路径加载
|
||||
4. 保持现有插件代码的最小改动
|
||||
|
||||
## 当前架构分析
|
||||
|
||||
### 插件加载机制
|
||||
- 插件位于 `packages/ui/certd-server/src/plugins/` 下(50+ 个插件目录)
|
||||
- `AutoLoadPlugins` 类在启动时扫描 `dist/plugins` 目录并动态导入
|
||||
- 插件注册到不同的 registry:`accessRegistry`, `pluginRegistry`, `dnsProviderRegistry` 等
|
||||
- 插件代码已经使用 `await import()` 进行懒加载(如 `await import("@aws-sdk/client-acm")`)
|
||||
|
||||
### 重型依赖分布
|
||||
从 `packages/ui/certd-server/package.json` 分析,以下依赖体积大且仅特定插件使用:
|
||||
|
||||
**云厂商 SDK(按插件分组):**
|
||||
- **AWS 插件**:`@aws-sdk/client-acm`, `@aws-sdk/client-cloudfront`, `@aws-sdk/client-iam`, `@aws-sdk/client-route-53`, `@aws-sdk/client-s3`, `@aws-sdk/client-sts`
|
||||
- **阿里云插件**:`@alicloud/openapi-client`, `@alicloud/pop-core`, `@alicloud/tea-typescript`, `@alicloud/fc20230330` 等
|
||||
- **腾讯云插件**:`tencentcloud-sdk-nodejs`, `cos-nodejs-sdk-v5`
|
||||
- **华为云插件**:`@huaweicloud/huaweicloud-sdk-cdn`, `@huaweicloud/huaweicloud-sdk-core` 等
|
||||
- **Azure 插件**:`@azure/arm-dns`, `@azure/identity`
|
||||
- **Google Cloud 插件**:`@google-cloud/dns`, `@google-cloud/publicca`
|
||||
- **火山引擎插件**:`@volcengine/openapi`, `@volcengine/tos-sdk`
|
||||
|
||||
**网络/工具库:**
|
||||
- `ssh2`, `socks`, `socks-proxy-agent`(SSH 相关插件)
|
||||
- `ali-oss`, `qiniu`, `basic-ftp`(存储/传输插件)
|
||||
- `nodemailer`(邮件通知插件)
|
||||
|
||||
**通用依赖(保留在主 package.json):**
|
||||
- `@midwayjs/*` 系列(框架核心)
|
||||
- `@certd/*` 系列(项目内部包)
|
||||
- `axios`, `lodash-es`, `dayjs`, `js-yaml` 等基础工具
|
||||
|
||||
## 设计方案
|
||||
|
||||
### 架构概览
|
||||
|
||||
```
|
||||
packages/ui/certd-server/
|
||||
├── package.json # 主依赖(框架、通用工具)
|
||||
├── node_modules/ # 主依赖安装目录
|
||||
├── optional-deps/ # 新增:可选依赖管理目录
|
||||
│ ├── package.json # 可选依赖总配置(用于 pnpm install)
|
||||
│ ├── pnpm-lock.yaml # 可选依赖锁文件
|
||||
│ └── node_modules/ # 可选依赖安装目录
|
||||
├── src/
|
||||
│ └── modules/
|
||||
│ └── dependency/ # 新增:依赖管理模块
|
||||
│ ├── dependency-manager.ts # 核心:依赖管理器
|
||||
│ ├── dependency-registry.ts # 依赖注册表(插件 -> 依赖映射)
|
||||
│ └── types.ts # 类型定义
|
||||
```
|
||||
|
||||
### 核心组件
|
||||
|
||||
#### 1. 依赖管理器(DependencyManager)
|
||||
|
||||
**职责:**
|
||||
- 检查依赖是否已安装
|
||||
- 动态执行 `pnpm install` 安装缺失依赖
|
||||
- 提供从 `optional-deps/node_modules` 加载依赖的方法
|
||||
- 并发控制:避免多个插件同时触发安装
|
||||
|
||||
**关键方法:**
|
||||
```typescript
|
||||
class DependencyManager {
|
||||
// 确保依赖已安装,返回依赖模块
|
||||
async ensureAndImport<T>(packageName: string): Promise<T>
|
||||
|
||||
// 检查依赖是否已安装
|
||||
async isInstalled(packageName: string): Promise<boolean>
|
||||
|
||||
// 安装依赖(带锁,避免并发)
|
||||
async installDependencies(packages: string[]): Promise<void>
|
||||
|
||||
// 从 optional-deps/node_modules 加载依赖
|
||||
async loadModule<T>(packageName: string): Promise<T>
|
||||
}
|
||||
```
|
||||
|
||||
**实现要点:**
|
||||
- 使用文件锁(如 `proper-lockfile`)防止并发安装
|
||||
- 安装前检查 `optional-deps/node_modules/{packageName}` 是否存在
|
||||
- 安装命令:`pnpm install --dir optional-deps --ignore-workspace`
|
||||
- 加载时使用绝对路径:`import('file:///absolute/path/to/optional-deps/node_modules/package')`
|
||||
|
||||
#### 2. 依赖注册表(DependencyRegistry)
|
||||
|
||||
**职责:**
|
||||
- 维护插件名称到依赖列表的映射
|
||||
- 提供依赖查询接口
|
||||
|
||||
**数据结构:**
|
||||
```typescript
|
||||
interface PluginDependencyConfig {
|
||||
pluginName: string;
|
||||
dependencies: {
|
||||
packageName: string;
|
||||
version: string;
|
||||
optional?: boolean; // 是否可选(安装失败不阻塞)
|
||||
}[];
|
||||
}
|
||||
|
||||
// 示例注册
|
||||
dependencyRegistry.register('plugin-aws', [
|
||||
{ packageName: '@aws-sdk/client-acm', version: '^3.964.0' },
|
||||
{ packageName: '@aws-sdk/client-cloudfront', version: '^3.964.0' },
|
||||
{ packageName: '@aws-sdk/client-route-53', version: '^3.964.0' },
|
||||
]);
|
||||
```
|
||||
|
||||
#### 3. 插件集成
|
||||
|
||||
**改造现有插件代码:**
|
||||
|
||||
改造前(`plugin-aws/libs/aws-client.ts`):
|
||||
```typescript
|
||||
const { ACMClient, ImportCertificateCommand } = await import("@aws-sdk/client-acm");
|
||||
```
|
||||
|
||||
改造后:
|
||||
```typescript
|
||||
import { DependencyManager } from "../../../modules/dependency/dependency-manager.js";
|
||||
|
||||
const depManager = new DependencyManager();
|
||||
const { ACMClient, ImportCertificateCommand } = await depManager.ensureAndImport("@aws-sdk/client-acm");
|
||||
```
|
||||
|
||||
**简化方案(推荐):**
|
||||
|
||||
创建辅助函数,减少改动量:
|
||||
```typescript
|
||||
// src/modules/dependency/import-helper.ts
|
||||
export async function importOptionalDep<T>(packageName: string): Promise<T> {
|
||||
const depManager = new DependencyManager();
|
||||
return await depManager.ensureAndImport<T>(packageName);
|
||||
}
|
||||
|
||||
// 插件中使用
|
||||
import { importOptionalDep } from "../../../modules/dependency/import-helper.js";
|
||||
const { ACMClient } = await importOptionalDep("@aws-sdk/client-acm");
|
||||
```
|
||||
|
||||
### 实施步骤
|
||||
|
||||
#### 阶段一:基础设施搭建
|
||||
1. 创建 `optional-deps/` 目录结构
|
||||
2. 生成 `optional-deps/package.json`(包含所有可选依赖)
|
||||
3. 实现 `DependencyManager` 核心逻辑
|
||||
4. 实现依赖安装锁机制
|
||||
5. 编写单元测试
|
||||
|
||||
#### 阶段二:依赖迁移
|
||||
6. 从主 `package.json` 移除可选依赖
|
||||
7. 将依赖添加到 `optional-deps/package.json`
|
||||
8. 创建依赖注册表,映射插件到依赖
|
||||
|
||||
#### 阶段三:插件改造
|
||||
9. 创建 `import-helper.ts` 辅助函数
|
||||
10. 逐步改造插件代码,使用 `importOptionalDep` 加载依赖
|
||||
11. 优先改造重型依赖(AWS、阿里云、腾讯云等)
|
||||
|
||||
#### 阶段四:测试与优化
|
||||
12. 端到端测试:验证依赖按需安装和加载
|
||||
13. 性能优化:缓存已加载的模块
|
||||
14. 错误处理:安装失败时的降级策略
|
||||
15. 文档:编写使用说明和迁移指南
|
||||
|
||||
## 关键技术决策
|
||||
|
||||
### 1. 依赖分组策略
|
||||
**选择:按插件分组**
|
||||
- 每个插件声明自己需要的依赖
|
||||
- 优点:职责清晰,易于维护
|
||||
- 缺点:可能有重复依赖(但 pnpm 会去重)
|
||||
|
||||
**备选:按功能分组**
|
||||
- 将依赖按功能分组(如 "aws-deps", "aliyun-deps")
|
||||
- 优点:更细粒度控制
|
||||
- 缺点:增加复杂度
|
||||
|
||||
### 2. 安装触发时机
|
||||
**选择:首次使用时触发**
|
||||
- 在插件的 `execute()` 或 `getClient()` 方法中触发安装
|
||||
- 优点:真正的按需加载
|
||||
- 缺点:首次使用有延迟
|
||||
|
||||
**备选:启动时预检查**
|
||||
- 启动时扫描启用的插件,预安装依赖
|
||||
- 优点:避免运行时延迟
|
||||
- 缺点:可能安装不需要的依赖
|
||||
|
||||
### 3. 依赖路径解析
|
||||
**选择:使用绝对路径 + `file://` 协议**
|
||||
```typescript
|
||||
const modulePath = path.resolve(__dirname, '../../optional-deps/node_modules', packageName);
|
||||
return await import(`file://${modulePath}/index.js`);
|
||||
```
|
||||
|
||||
**原因:**
|
||||
- Node.js ESM 要求明确的 URL 格式
|
||||
- 避免模块解析冲突
|
||||
|
||||
### 4. 并发控制
|
||||
**选择:文件锁 + 内存锁双重保护**
|
||||
- 使用 `proper-lockfile` 锁定 `optional-deps/` 目录
|
||||
- 内存中使用 `Map` 记录正在安装的依赖
|
||||
- 避免多个插件同时触发安装
|
||||
|
||||
### 5. 错误处理
|
||||
**策略:**
|
||||
- 安装失败时记录日志,抛出明确的错误信息
|
||||
- 提供手动安装命令提示:`请运行: cd optional-deps && pnpm install`
|
||||
- 支持降级:某些非核心依赖安装失败时,插件可以部分功能可用
|
||||
|
||||
## 验证方案
|
||||
|
||||
### 单元测试
|
||||
1. 测试 `DependencyManager.isInstalled()` 正确检测依赖状态
|
||||
2. 测试 `DependencyManager.installDependencies()` 成功安装依赖
|
||||
3. 测试并发安装时的锁机制
|
||||
4. 测试从 `optional-deps/node_modules` 加载模块
|
||||
|
||||
### 集成测试
|
||||
1. 清空 `optional-deps/node_modules`
|
||||
2. 启动服务,验证不触发安装
|
||||
3. 调用 AWS 插件,验证触发安装并成功加载
|
||||
4. 再次调用,验证不重复安装
|
||||
5. 验证主 `node_modules` 体积减少
|
||||
|
||||
### 性能测试
|
||||
1. 测量首次安装依赖的耗时
|
||||
2. 测量后续加载的耗时(应该与正常 import 相近)
|
||||
3. 对比改造前后的 `node_modules` 大小
|
||||
|
||||
## 风险与挑战
|
||||
|
||||
### 1. 首次使用延迟
|
||||
**风险:** 用户首次使用插件时需要等待依赖安装(可能几十秒)
|
||||
**缓解:**
|
||||
- 在 UI 上显示安装进度
|
||||
- 提供预安装命令:`pnpm run install-optional-deps`
|
||||
- 文档说明首次使用会有延迟
|
||||
|
||||
### 2. 离线环境
|
||||
**风险:** 离线环境无法下载依赖
|
||||
**缓解:**
|
||||
- 提供完整安装包(包含所有可选依赖)
|
||||
- 支持手动复制 `node_modules`
|
||||
|
||||
### 3. 版本冲突
|
||||
**风险:** 可选依赖与主依赖版本冲突
|
||||
**缓解:**
|
||||
- 使用 `--ignore-workspace` 隔离安装
|
||||
- 定期同步主依赖版本
|
||||
|
||||
### 4. TypeScript 类型
|
||||
**风险:** 动态导入的类型推断
|
||||
**缓解:**
|
||||
- 保留 `@types/*` 在主 `devDependencies`
|
||||
- 使用泛型和类型断言
|
||||
|
||||
## 预期收益
|
||||
|
||||
1. **空间节省:** 主 `node_modules` 体积减少 60-70%(估算)
|
||||
2. **安装速度:** 初始 `pnpm install` 速度提升 3-5 倍
|
||||
3. **用户体验:** 不使用的插件不占用空间,按需加载
|
||||
4. **维护性:** 依赖分组清晰,易于管理
|
||||
|
||||
## 后续优化
|
||||
|
||||
1. **依赖预热:** 在后台预安装常用插件依赖
|
||||
2. **依赖缓存:** 支持从 CDN 或本地缓存安装
|
||||
3. **依赖更新:** 提供命令批量更新可选依赖
|
||||
4. **插件市场:** 支持从远程下载插件及其依赖配置
|
||||
|
||||
## 附录:依赖分类清单
|
||||
|
||||
### 可选依赖(迁移到 optional-deps/package.json)
|
||||
|
||||
**AWS 相关(plugin-aws, plugin-aws-cn):**
|
||||
```json
|
||||
{
|
||||
"@aws-sdk/client-acm": "^3.964.0",
|
||||
"@aws-sdk/client-cloudfront": "^3.964.0",
|
||||
"@aws-sdk/client-iam": "^3.964.0",
|
||||
"@aws-sdk/client-route-53": "^3.964.0",
|
||||
"@aws-sdk/client-s3": "^3.964.0",
|
||||
"@aws-sdk/client-sts": "^3.990.0"
|
||||
}
|
||||
```
|
||||
|
||||
**阿里云相关(plugin-aliyun, plugin-lib/aliyun):**
|
||||
```json
|
||||
{
|
||||
"@alicloud/fc20230330": "^4.1.7",
|
||||
"@alicloud/openapi-client": "^0.4.12",
|
||||
"@alicloud/openapi-util": "^0.3.2",
|
||||
"@alicloud/pop-core": "^1.7.10",
|
||||
"@alicloud/sts-sdk": "^1.0.2",
|
||||
"@alicloud/tea-typescript": "^1.8.0",
|
||||
"@alicloud/tea-util": "^1.4.10",
|
||||
"ali-oss": "^6.21.0"
|
||||
}
|
||||
```
|
||||
|
||||
**腾讯云相关(plugin-tencent, plugin-lib/tencent):**
|
||||
```json
|
||||
{
|
||||
"tencentcloud-sdk-nodejs": "^4.1.112",
|
||||
"cos-nodejs-sdk-v5": "^2.14.6"
|
||||
}
|
||||
```
|
||||
|
||||
**华为云相关(plugin-huawei):**
|
||||
```json
|
||||
{
|
||||
"@huaweicloud/huaweicloud-sdk-cdn": "3.1.185",
|
||||
"@huaweicloud/huaweicloud-sdk-core": "3.1.185",
|
||||
"@huaweicloud/huaweicloud-sdk-elb": "3.1.185",
|
||||
"@huaweicloud/huaweicloud-sdk-iam": "3.1.185",
|
||||
"esdk-obs-nodejs": "^3.25.6"
|
||||
}
|
||||
```
|
||||
|
||||
**Azure 相关(plugin-azure):**
|
||||
```json
|
||||
{
|
||||
"@azure/arm-dns": "^5.1.0",
|
||||
"@azure/identity": "^4.13.1"
|
||||
}
|
||||
```
|
||||
|
||||
**Google Cloud 相关(plugin-google, plugin-cert/google):**
|
||||
```json
|
||||
{
|
||||
"@google-cloud/dns": "^5.3.1",
|
||||
"@google-cloud/publicca": "^1.3.0"
|
||||
}
|
||||
```
|
||||
|
||||
**火山引擎相关(plugin-volcengine):**
|
||||
```json
|
||||
{
|
||||
"@volcengine/openapi": "^1.28.1",
|
||||
"@volcengine/tos-sdk": "^2.9.1"
|
||||
}
|
||||
```
|
||||
|
||||
**SSH/网络相关(plugin-host, plugin-lib/ssh):**
|
||||
```json
|
||||
{
|
||||
"ssh2": "^1.17.0",
|
||||
"socks": "^2.8.3",
|
||||
"socks-proxy-agent": "^8.0.4",
|
||||
"basic-ftp": "^5.0.5"
|
||||
}
|
||||
```
|
||||
|
||||
**其他存储/传输(plugin-qiniu, plugin-lib/qiniu):**
|
||||
```json
|
||||
{
|
||||
"qiniu": "^7.12.0"
|
||||
}
|
||||
```
|
||||
|
||||
**邮件通知(plugin-notification/email):**
|
||||
```json
|
||||
{
|
||||
"nodemailer": "^6.9.16"
|
||||
}
|
||||
```
|
||||
|
||||
### 主依赖(保留在主 package.json)
|
||||
|
||||
**框架核心:**
|
||||
- `@midwayjs/*` 系列
|
||||
- `@koa/cors`
|
||||
- `typeorm`, `better-sqlite3`, `mysql2`, `pg`
|
||||
|
||||
**项目内部包:**
|
||||
- `@certd/*` 系列
|
||||
|
||||
**通用工具:**
|
||||
- `axios`, `lodash-es`, `dayjs`, `js-yaml`
|
||||
- `crypto-js`, `jsonwebtoken`, `bcryptjs`
|
||||
- `reflect-metadata`, `uuid`, `nanoid`
|
||||
- 等等
|
||||
|
||||
## 总结
|
||||
|
||||
本方案通过引入独立的可选依赖管理机制,实现了插件依赖的按需下载和加载。核心思路是:
|
||||
|
||||
1. **隔离管理:** 在 `optional-deps/` 目录下维护独立的 `package.json` 和 `node_modules`
|
||||
2. **动态安装:** 通过 `DependencyManager` 在首次使用时触发 `pnpm install`
|
||||
3. **路径加载:** 使用绝对路径从独立目录加载依赖模块
|
||||
4. **最小改动:** 通过辅助函数 `importOptionalDep` 简化插件代码改造
|
||||
|
||||
该方案可以显著减少主 `node_modules` 体积,提升初始安装速度,同时保持现有架构的兼容性和可维护性。
|
||||
Reference in New Issue
Block a user