ethan-skill 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/index.js +1019 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +206 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/skills/27-tech-debt.d.ts +3 -0
- package/dist/skills/27-tech-debt.d.ts.map +1 -0
- package/dist/skills/27-tech-debt.js +149 -0
- package/dist/skills/27-tech-debt.js.map +1 -0
- package/dist/skills/28-api-mock.d.ts +3 -0
- package/dist/skills/28-api-mock.d.ts.map +1 -0
- package/dist/skills/28-api-mock.js +272 -0
- package/dist/skills/28-api-mock.js.map +1 -0
- package/dist/skills/29-data-migration.d.ts +3 -0
- package/dist/skills/29-data-migration.d.ts.map +1 -0
- package/dist/skills/29-data-migration.js +331 -0
- package/dist/skills/29-data-migration.js.map +1 -0
- package/dist/skills/30-llm-feature.d.ts +3 -0
- package/dist/skills/30-llm-feature.d.ts.map +1 -0
- package/dist/skills/30-llm-feature.js +328 -0
- package/dist/skills/30-llm-feature.js.map +1 -0
- package/dist/skills/31-threat-model.d.ts +3 -0
- package/dist/skills/31-threat-model.d.ts.map +1 -0
- package/dist/skills/31-threat-model.js +240 -0
- package/dist/skills/31-threat-model.js.map +1 -0
- package/dist/skills/32-green-code.d.ts +3 -0
- package/dist/skills/32-green-code.d.ts.map +1 -0
- package/dist/skills/32-green-code.js +346 -0
- package/dist/skills/32-green-code.js.map +1 -0
- package/dist/skills/33-service-catalog.d.ts +3 -0
- package/dist/skills/33-service-catalog.d.ts.map +1 -0
- package/dist/skills/33-service-catalog.js +334 -0
- package/dist/skills/33-service-catalog.js.map +1 -0
- package/dist/skills/34-mobile-review.d.ts +3 -0
- package/dist/skills/34-mobile-review.d.ts.map +1 -0
- package/dist/skills/34-mobile-review.js +390 -0
- package/dist/skills/34-mobile-review.js.map +1 -0
- package/dist/skills/35-data-pipeline.d.ts +3 -0
- package/dist/skills/35-data-pipeline.d.ts.map +1 -0
- package/dist/skills/35-data-pipeline.js +392 -0
- package/dist/skills/35-data-pipeline.js.map +1 -0
- package/dist/skills/36-ml-experiment.d.ts +3 -0
- package/dist/skills/36-ml-experiment.d.ts.map +1 -0
- package/dist/skills/36-ml-experiment.js +415 -0
- package/dist/skills/36-ml-experiment.js.map +1 -0
- package/dist/skills/index.d.ts +10 -0
- package/dist/skills/index.d.ts.map +1 -1
- package/dist/skills/index.js +41 -1
- package/dist/skills/index.js.map +1 -1
- package/dist/skills/pipeline.d.ts.map +1 -1
- package/dist/skills/pipeline.js +35 -0
- package/dist/skills/pipeline.js.map +1 -1
- package/dist/skills/skills.test.js +3 -3
- package/dist/skills/skills.test.js.map +1 -1
- package/package.json +1 -1
- package/rules/claude-code/CLAUDE.md +2963 -3
- package/rules/cline/.clinerules +2805 -2
- package/rules/codebuddy/CODEBUDDY.md +2913 -2
- package/rules/continue/.continuerules +2805 -2
- package/rules/copilot/copilot-instructions.md +2883 -2
- package/rules/cursor/.cursorrules +2952 -2
- package/rules/cursor/smart-flow.mdc +2952 -2
- package/rules/jetbrains/smart-flow.md +2883 -2
- package/rules/lingma/smart-flow.md +2904 -3
- package/rules/windsurf/.windsurf/rules/smart-flow.md +2884 -3
- package/rules/zed/smart-flow.rules +2794 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# Ethan v1.
|
|
1
|
+
# Ethan v1.12.0
|
|
2
2
|
|
|
3
|
-
> Auto-generated from src/skills/ | 2026-04-
|
|
3
|
+
> Auto-generated from src/skills/ | 2026-04-06T16:19:27.840Z
|
|
4
4
|
> Do not edit manually. Source: src/skills/
|
|
5
5
|
|
|
6
6
|
## Ethan
|
|
7
7
|
|
|
8
|
-
本文件配置了
|
|
8
|
+
本文件配置了 36 个标准化工作流节点(Skill)。当用户输入触发词时,严格按对应 Skill 的步骤执行,输出遵循各 Skill 的格式模板。
|
|
9
9
|
|
|
10
10
|
## 执行原则
|
|
11
11
|
|
|
@@ -4101,4 +4101,2964 @@ spec 中定义的场景或需求在代码中未体现:
|
|
|
4101
4101
|
|
|
4102
4102
|
---
|
|
4103
4103
|
|
|
4104
|
+
### 27. 技术债追踪 (`tech-debt`)
|
|
4105
|
+
|
|
4106
|
+
**描述**: 识别、量化、排序技术债,生成偿还路线图与预防机制
|
|
4107
|
+
|
|
4108
|
+
**触发词**: `技术债`, `tech debt`, `technical debt`, `技术债追踪`, `技术债偿还`, `code smell`, `代码腐化`, `@ethan tech-debt`, `/tech-debt`
|
|
4109
|
+
|
|
4110
|
+
**执行步骤**:
|
|
4111
|
+
|
|
4112
|
+
#### 1. 技术债识别与分类
|
|
4113
|
+
|
|
4114
|
+
扫描代码库,从以下维度识别技术债:
|
|
4115
|
+
|
|
4116
|
+
**四大技术债类型**
|
|
4117
|
+
|
|
4118
|
+
| 类型 | 检测手段 | 典型表现 |
|
|
4119
|
+
|------|---------|---------|
|
|
4120
|
+
| **代码债** | TODO/FIXME/HACK 注释统计 | 注释标记超过 50 个/千行 |
|
|
4121
|
+
| **设计债** | 圈复杂度 > 10,类长度 > 500 行 | 上帝类、面条代码、深层继承 |
|
|
4122
|
+
| **测试债** | 覆盖率 < 60%,测试缺失模块 | 核心模块无测试,回归靠手工 |
|
|
4123
|
+
| **依赖债** | 过时依赖、已弃用 API 调用 | 主版本落后 2+ 个版本 |
|
|
4124
|
+
|
|
4125
|
+
**识别命令参考**
|
|
4126
|
+
```bash
|
|
4127
|
+
# 统计 TODO/FIXME
|
|
4128
|
+
grep -rn "TODO\|FIXME\|HACK\|XXX" src/ --include="*.ts" | wc -l
|
|
4129
|
+
|
|
4130
|
+
# 查找超长文件
|
|
4131
|
+
find src -name "*.ts" | xargs wc -l | sort -rn | head -20
|
|
4132
|
+
|
|
4133
|
+
# 重复代码检测(需安装 jscpd)
|
|
4134
|
+
npx jscpd src/ --min-lines 5 --reporters json
|
|
4135
|
+
```
|
|
4136
|
+
|
|
4137
|
+
**输出**:技术债清单(分类 + 位置 + 初步评估)
|
|
4138
|
+
|
|
4139
|
+
#### 2. 债务量化评估
|
|
4140
|
+
|
|
4141
|
+
用"影响度 × 修复成本"矩阵评估每项技术债的优先级:
|
|
4142
|
+
|
|
4143
|
+
**评分维度**
|
|
4144
|
+
|
|
4145
|
+
| 维度 | 1分(低) | 3分(中) | 5分(高) |
|
|
4146
|
+
|------|---------|---------|---------|
|
|
4147
|
+
| **业务影响** | 边缘功能 | 常用功能 | 核心链路 |
|
|
4148
|
+
| **出错频率** | 极少触发 | 偶尔出现 | 频繁发生 |
|
|
4149
|
+
| **修复成本** | < 0.5天 | 0.5~2天 | > 2天 |
|
|
4150
|
+
| **扩散风险** | 独立模块 | 少量依赖 | 多处依赖 |
|
|
4151
|
+
|
|
4152
|
+
**优先级 = 影响度(业务影响 × 出错频率) ÷ 修复成本**
|
|
4153
|
+
|
|
4154
|
+
**T恤 Size 对照**
|
|
4155
|
+
- 🔴 **Critical(>8分)**:阻碍新功能开发,必须本 Sprint 处理
|
|
4156
|
+
- 🟠 **High(5-8分)**:影响团队效率,下个 Sprint 安排
|
|
4157
|
+
- 🟡 **Medium(3-5分)**:纳入季度技术改进计划
|
|
4158
|
+
- 🟢 **Low(<3分)**:有时间再处理,记录即可
|
|
4159
|
+
|
|
4160
|
+
**输出**:技术债优先级矩阵(标注 T恤 Size)
|
|
4161
|
+
|
|
4162
|
+
#### 3. 偿还路线图
|
|
4163
|
+
|
|
4164
|
+
按"高影响、低成本"优先原则,制定可落地的偿还计划:
|
|
4165
|
+
|
|
4166
|
+
**Sprint 规划原则**
|
|
4167
|
+
- 每个 Sprint 分配 **15-20% 时间**用于技术债偿还(债务预算)
|
|
4168
|
+
- Critical 债务:独立 Story,本 Sprint 必须完成
|
|
4169
|
+
- High 债务:与新功能并行,2 个 Sprint 内完成
|
|
4170
|
+
- Medium 债务:季度 Hackathon 集中处理
|
|
4171
|
+
|
|
4172
|
+
**路线图模板**
|
|
4173
|
+
```
|
|
4174
|
+
Sprint N(当前)
|
|
4175
|
+
🔴 [Critical] 拆分 UserService 上帝类 → 3个子服务 2天
|
|
4176
|
+
🔴 [Critical] 修复登录模块 XXX 标记的并发 Bug 1天
|
|
4177
|
+
|
|
4178
|
+
Sprint N+1
|
|
4179
|
+
🟠 [High] 提升 Payment 模块测试覆盖率至 80% 2天
|
|
4180
|
+
🟠 [High] 替换废弃的 axios v0.21 → v1.x 1天
|
|
4181
|
+
|
|
4182
|
+
Q+1 季度计划
|
|
4183
|
+
🟡 [Medium] 重构订单模块(圈复杂度>15的5个函数) 5天
|
|
4184
|
+
🟡 [Medium] 清理 87 个 TODO 注释并创建对应 Issue 3天
|
|
4185
|
+
```
|
|
4186
|
+
|
|
4187
|
+
**输出**:按 Sprint 排期的技术债偿还路线图
|
|
4188
|
+
|
|
4189
|
+
#### 4. 预防机制
|
|
4190
|
+
|
|
4191
|
+
建立长效机制,让技术债不再悄悄累积:
|
|
4192
|
+
|
|
4193
|
+
**自动化门禁(CI/CD 集成)**
|
|
4194
|
+
```yaml
|
|
4195
|
+
# .github/workflows/debt-check.yml
|
|
4196
|
+
- name: 技术债扫描
|
|
4197
|
+
run: |
|
|
4198
|
+
# TODO 数量检查
|
|
4199
|
+
TODO_COUNT=$(grep -rn "TODO\|FIXME" src/ | wc -l)
|
|
4200
|
+
if [ $TODO_COUNT -gt $TODO_THRESHOLD ]; then
|
|
4201
|
+
echo "❌ TODO 数量超过阈值: $TODO_COUNT > $TODO_THRESHOLD"
|
|
4202
|
+
exit 1
|
|
4203
|
+
fi
|
|
4204
|
+
# 圈复杂度检查(使用 complexity-report 或 ESLint)
|
|
4205
|
+
npx eslint src/ --rule '{"complexity": ["error", 10]}'
|
|
4206
|
+
```
|
|
4207
|
+
|
|
4208
|
+
**团队规范**
|
|
4209
|
+
- **Boy Scout 原则**:离开时让代码比你来时更干净(每次 PR 消灭 1 个技术债)
|
|
4210
|
+
- **TODO 转 Issue**:所有 TODO 必须关联 GitHub Issue,不得孤立存在
|
|
4211
|
+
- **债务预算制度**:每个 Sprint 强制预留 15% 时间偿还债务
|
|
4212
|
+
- **月度债务评审**:每月 Review 技术债清单,更新优先级
|
|
4213
|
+
|
|
4214
|
+
**技术债仪表盘指标**
|
|
4215
|
+
- TODO/FIXME 总数趋势(目标:每月下降 10%)
|
|
4216
|
+
- 平均圈复杂度(目标:< 8)
|
|
4217
|
+
- 测试覆盖率(目标:> 80%)
|
|
4218
|
+
- 依赖过时率(目标:0 个 Critical 漏洞)
|
|
4219
|
+
|
|
4220
|
+
**输出**:CI 门禁配置 + 团队规范文档 + 仪表盘指标定义
|
|
4221
|
+
|
|
4222
|
+
**输出格式**: 技术债地图(分级清单)+ 优先级矩阵(影响×成本)+ Sprint 偿还路线图 + CI 门禁配置 + 预防规范
|
|
4223
|
+
|
|
4224
|
+
**注意事项**:
|
|
4225
|
+
- 技术债不是"坏代码",是在特定时间压力下做出的有意识权衡,关键是要记录并计划偿还
|
|
4226
|
+
- 建议在项目初期就建立债务预算制度(15-20% Sprint 时间),而不是等到债务失控再处理
|
|
4227
|
+
- 优先处理"高影响低成本"的债务,避免在低价值债务上耗费资源
|
|
4228
|
+
- 技术债清单应纳入项目文档,让所有成员可见,避免只有架构师知道
|
|
4229
|
+
|
|
4230
|
+
---
|
|
4231
|
+
|
|
4232
|
+
### 28. API Mock 服务 (`api-mock`)
|
|
4233
|
+
|
|
4234
|
+
**描述**: 根据接口定义生成 MSW/JSON Server Mock 配置,支持动态数据与边界场景模拟
|
|
4235
|
+
|
|
4236
|
+
**触发词**: `api mock`, `mock service`, `mock 服务`, `接口 mock`, `msw`, `json server`, `mock 数据`, `接口模拟`, `@ethan api-mock`, `/api-mock`
|
|
4237
|
+
|
|
4238
|
+
**执行步骤**:
|
|
4239
|
+
|
|
4240
|
+
#### 1. 接口分析与 Mock 范围确定
|
|
4241
|
+
|
|
4242
|
+
分析需要 Mock 的接口,建立清单:
|
|
4243
|
+
|
|
4244
|
+
**接口信息收集**
|
|
4245
|
+
- OpenAPI/Swagger 文档路径(如 `api/swagger.json`)
|
|
4246
|
+
- 手工接口描述(方法、路径、请求/响应结构)
|
|
4247
|
+
- 需要模拟的特殊场景(401/403/500/超时/慢响应)
|
|
4248
|
+
|
|
4249
|
+
**Mock 范围分类**
|
|
4250
|
+
```
|
|
4251
|
+
📋 待 Mock 接口清单
|
|
4252
|
+
├── 认证接口
|
|
4253
|
+
│ ├── POST /api/auth/login ← 成功 / 密码错误 / 账号锁定
|
|
4254
|
+
│ └── POST /api/auth/refresh ← 成功 / token 过期
|
|
4255
|
+
├── 用户接口
|
|
4256
|
+
│ ├── GET /api/users ← 列表 / 空列表 / 分页
|
|
4257
|
+
│ └── GET /api/users/:id ← 成功 / 404
|
|
4258
|
+
└── 业务接口
|
|
4259
|
+
├── POST /api/orders ← 成功 / 库存不足 / 支付失败
|
|
4260
|
+
└── GET /api/orders?status=... ← 各状态过滤
|
|
4261
|
+
```
|
|
4262
|
+
|
|
4263
|
+
**输出**:接口 Mock 清单(含边界场景枚举)
|
|
4264
|
+
|
|
4265
|
+
#### 2. Mock 方案选型
|
|
4266
|
+
|
|
4267
|
+
根据项目特点选择最合适的 Mock 方案:
|
|
4268
|
+
|
|
4269
|
+
**选型矩阵**
|
|
4270
|
+
|
|
4271
|
+
| 方案 | 适用场景 | 优点 | 缺点 |
|
|
4272
|
+
|------|---------|------|------|
|
|
4273
|
+
| **MSW(推荐)** | React/Vue 前端项目 | 拦截真实网络请求,零侵入,支持 Jest/Vitest | 需要 Service Worker |
|
|
4274
|
+
| **JSON Server** | REST API 快速原型 | 零代码,自动 CRUD | 功能有限,不支持复杂逻辑 |
|
|
4275
|
+
| **Mirage.js** | Ember/复杂 SPA | 内置数据库,关系模型支持 | 包较大,配置复杂 |
|
|
4276
|
+
| **Nock** | Node.js 单元测试 | 精确控制 HTTP 请求 | 仅限 Node 环境 |
|
|
4277
|
+
|
|
4278
|
+
**推荐组合**:
|
|
4279
|
+
- 开发阶段:**MSW**(浏览器端拦截)
|
|
4280
|
+
- 单元测试:**MSW + @mswjs/data**(内存数据库)
|
|
4281
|
+
- 快速原型:**JSON Server**(5分钟启动)
|
|
4282
|
+
|
|
4283
|
+
**输出**:选型决策 + 安装命令
|
|
4284
|
+
|
|
4285
|
+
#### 3. MSW handlers 生成
|
|
4286
|
+
|
|
4287
|
+
生成 MSW(Mock Service Worker)拦截处理器:
|
|
4288
|
+
|
|
4289
|
+
**安装**
|
|
4290
|
+
```bash
|
|
4291
|
+
npm install msw --save-dev
|
|
4292
|
+
npx msw init public/ --save
|
|
4293
|
+
```
|
|
4294
|
+
|
|
4295
|
+
**handlers.ts 模板**
|
|
4296
|
+
```typescript
|
|
4297
|
+
// src/mocks/handlers.ts
|
|
4298
|
+
import { http, HttpResponse, delay } from 'msw';
|
|
4299
|
+
import { faker } from '@faker-js/faker';
|
|
4300
|
+
|
|
4301
|
+
export const handlers = [
|
|
4302
|
+
// ─── 认证接口 ──────────────────────────────────────
|
|
4303
|
+
http.post('/api/auth/login', async ({ request }) => {
|
|
4304
|
+
const { email, password } = await request.json() as any;
|
|
4305
|
+
|
|
4306
|
+
// 模拟特殊场景
|
|
4307
|
+
if (password === 'wrong') {
|
|
4308
|
+
return HttpResponse.json(
|
|
4309
|
+
{ code: 401, message: '密码错误' },
|
|
4310
|
+
{ status: 401 }
|
|
4311
|
+
);
|
|
4312
|
+
}
|
|
4313
|
+
if (email === 'locked@test.com') {
|
|
4314
|
+
return HttpResponse.json(
|
|
4315
|
+
{ code: 423, message: '账号已锁定,请联系管理员' },
|
|
4316
|
+
{ status: 423 }
|
|
4317
|
+
);
|
|
4318
|
+
}
|
|
4319
|
+
|
|
4320
|
+
// 正常响应
|
|
4321
|
+
return HttpResponse.json({
|
|
4322
|
+
token: faker.string.uuid(),
|
|
4323
|
+
user: { id: faker.string.uuid(), email, name: faker.person.fullName() },
|
|
4324
|
+
});
|
|
4325
|
+
}),
|
|
4326
|
+
|
|
4327
|
+
// ─── 用户列表(分页)──────────────────────────────
|
|
4328
|
+
http.get('/api/users', ({ request }) => {
|
|
4329
|
+
const url = new URL(request.url);
|
|
4330
|
+
const page = Number(url.searchParams.get('page') ?? 1);
|
|
4331
|
+
const pageSize = Number(url.searchParams.get('pageSize') ?? 10);
|
|
4332
|
+
|
|
4333
|
+
const total = 87;
|
|
4334
|
+
const items = Array.from({ length: Math.min(pageSize, total - (page - 1) * pageSize) }, () => ({
|
|
4335
|
+
id: faker.string.uuid(),
|
|
4336
|
+
name: faker.person.fullName(),
|
|
4337
|
+
email: faker.internet.email(),
|
|
4338
|
+
createdAt: faker.date.past().toISOString(),
|
|
4339
|
+
}));
|
|
4340
|
+
|
|
4341
|
+
return HttpResponse.json({ items, total, page, pageSize });
|
|
4342
|
+
}),
|
|
4343
|
+
|
|
4344
|
+
// ─── 慢响应模拟 ────────────────────────────────────
|
|
4345
|
+
http.get('/api/slow-endpoint', async () => {
|
|
4346
|
+
await delay(3000); // 模拟 3 秒延迟
|
|
4347
|
+
return HttpResponse.json({ data: 'slow response' });
|
|
4348
|
+
}),
|
|
4349
|
+
|
|
4350
|
+
// ─── 网络错误模拟 ──────────────────────────────────
|
|
4351
|
+
http.get('/api/network-error', () => {
|
|
4352
|
+
return HttpResponse.error(); // 模拟网络断开
|
|
4353
|
+
}),
|
|
4354
|
+
];
|
|
4355
|
+
```
|
|
4356
|
+
|
|
4357
|
+
**browser.ts(浏览器初始化)**
|
|
4358
|
+
```typescript
|
|
4359
|
+
// src/mocks/browser.ts
|
|
4360
|
+
import { setupWorker } from 'msw/browser';
|
|
4361
|
+
import { handlers } from './handlers';
|
|
4362
|
+
export const worker = setupWorker(...handlers);
|
|
4363
|
+
```
|
|
4364
|
+
|
|
4365
|
+
**server.ts(Node.js/测试环境)**
|
|
4366
|
+
```typescript
|
|
4367
|
+
// src/mocks/server.ts
|
|
4368
|
+
import { setupServer } from 'msw/node';
|
|
4369
|
+
import { handlers } from './handlers';
|
|
4370
|
+
export const server = setupServer(...handlers);
|
|
4371
|
+
```
|
|
4372
|
+
|
|
4373
|
+
**输出**:完整 handlers.ts + browser.ts + server.ts
|
|
4374
|
+
|
|
4375
|
+
#### 4. JSON Server 配置
|
|
4376
|
+
|
|
4377
|
+
生成 JSON Server 快速 REST Mock 配置:
|
|
4378
|
+
|
|
4379
|
+
**安装与启动**
|
|
4380
|
+
```bash
|
|
4381
|
+
npm install json-server --save-dev
|
|
4382
|
+
# 启动:json-server --watch db.json --port 3001 --routes routes.json
|
|
4383
|
+
```
|
|
4384
|
+
|
|
4385
|
+
**db.json(数据库)**
|
|
4386
|
+
```json
|
|
4387
|
+
{
|
|
4388
|
+
"users": [
|
|
4389
|
+
{ "id": "1", "name": "张三", "email": "zhang@test.com", "role": "admin" },
|
|
4390
|
+
{ "id": "2", "name": "李四", "email": "li@test.com", "role": "user" }
|
|
4391
|
+
],
|
|
4392
|
+
"orders": [
|
|
4393
|
+
{ "id": "101", "userId": "1", "status": "pending", "amount": 299.00, "createdAt": "2024-01-15" },
|
|
4394
|
+
{ "id": "102", "userId": "2", "status": "completed", "amount": 599.00, "createdAt": "2024-01-16" }
|
|
4395
|
+
],
|
|
4396
|
+
"products": [
|
|
4397
|
+
{ "id": "P001", "name": "商品A", "price": 99.00, "stock": 100 }
|
|
4398
|
+
]
|
|
4399
|
+
}
|
|
4400
|
+
```
|
|
4401
|
+
|
|
4402
|
+
**routes.json(路由重写)**
|
|
4403
|
+
```json
|
|
4404
|
+
{
|
|
4405
|
+
"/api/*": "/$1",
|
|
4406
|
+
"/api/v1/*": "/$1",
|
|
4407
|
+
"/api/users/:id/orders": "/orders?userId=:id"
|
|
4408
|
+
}
|
|
4409
|
+
```
|
|
4410
|
+
|
|
4411
|
+
**package.json 脚本**
|
|
4412
|
+
```json
|
|
4413
|
+
{
|
|
4414
|
+
"scripts": {
|
|
4415
|
+
"mock": "json-server --watch src/mocks/db.json --port 3001 --routes src/mocks/routes.json",
|
|
4416
|
+
"dev:mock": "concurrently \"npm run mock\" \"npm run dev\""
|
|
4417
|
+
}
|
|
4418
|
+
}
|
|
4419
|
+
```
|
|
4420
|
+
|
|
4421
|
+
**输出**:db.json + routes.json + npm 脚本
|
|
4422
|
+
|
|
4423
|
+
#### 5. 动态数据与边界场景策略
|
|
4424
|
+
|
|
4425
|
+
用 faker.js 生成真实感数据,覆盖各类边界场景:
|
|
4426
|
+
|
|
4427
|
+
**faker.js 常用生成器速查**
|
|
4428
|
+
```typescript
|
|
4429
|
+
import { faker } from '@faker-js/faker/locale/zh_CN'; // 中文数据
|
|
4430
|
+
|
|
4431
|
+
// 个人信息
|
|
4432
|
+
faker.person.fullName() // 王小明
|
|
4433
|
+
faker.internet.email() // user@example.com
|
|
4434
|
+
faker.phone.number() // 138-1234-5678
|
|
4435
|
+
faker.date.birthdate() // 生日
|
|
4436
|
+
|
|
4437
|
+
// 地址
|
|
4438
|
+
faker.location.city() // 上海
|
|
4439
|
+
faker.location.streetAddress() // 延安路 123 号
|
|
4440
|
+
|
|
4441
|
+
// 业务数据
|
|
4442
|
+
faker.string.uuid() // UUID
|
|
4443
|
+
faker.number.int({ min:1, max:100 }) // 随机整数
|
|
4444
|
+
faker.helpers.arrayElement(['pending', 'active', 'closed']) // 随机枚举
|
|
4445
|
+
faker.helpers.multiple(() => faker.person.fullName(), { count: 10 }) // 批量生成
|
|
4446
|
+
```
|
|
4447
|
+
|
|
4448
|
+
**边界场景 Checklist**
|
|
4449
|
+
- [ ] 空列表(返回 `{ items: [], total: 0 }`)
|
|
4450
|
+
- [ ] 单条数据(边界分页)
|
|
4451
|
+
- [ ] 超长字符串(名称 > 100 字符)
|
|
4452
|
+
- [ ] 特殊字符(`<script>`、SQL 注入字符串)
|
|
4453
|
+
- [ ] 极大数字(金额 = 999999999.99)
|
|
4454
|
+
- [ ] 时区边界(UTC+8 vs UTC)
|
|
4455
|
+
- [ ] 并发响应竞态(两个请求同时返回)
|
|
4456
|
+
- [ ] 401 token 过期 → 自动刷新
|
|
4457
|
+
- [ ] 503 服务不可用 → 降级 UI
|
|
4458
|
+
|
|
4459
|
+
**测试集成(Vitest)**
|
|
4460
|
+
```typescript
|
|
4461
|
+
// setupTests.ts
|
|
4462
|
+
import { beforeAll, afterEach, afterAll } from 'vitest';
|
|
4463
|
+
import { server } from './mocks/server';
|
|
4464
|
+
|
|
4465
|
+
beforeAll(() => server.listen({ onUnhandledRequest: 'warn' }));
|
|
4466
|
+
afterEach(() => server.resetHandlers()); // 每个测试后重置
|
|
4467
|
+
afterAll(() => server.close());
|
|
4468
|
+
```
|
|
4469
|
+
|
|
4470
|
+
**输出**:faker 数据工厂函数 + 边界场景 handlers + 测试集成配置
|
|
4471
|
+
|
|
4472
|
+
**输出格式**: Mock 方案选型报告 + MSW handlers.ts + JSON Server db.json/routes.json + faker 数据工厂 + 边界场景 Checklist + 测试集成配置
|
|
4473
|
+
|
|
4474
|
+
**注意事项**:
|
|
4475
|
+
- MSW 是目前最推荐的前端 Mock 方案,可在浏览器和 Node.js 两种环境无缝切换
|
|
4476
|
+
- Mock 数据应尽量真实(使用 faker.js),避免 "test"/"demo" 等无意义数据干扰开发体验
|
|
4477
|
+
- 边界场景的 Mock 与正常流程同等重要,建议用命名 handler(override)覆写特定测试场景
|
|
4478
|
+
- 生产环境应通过环境变量完全关闭 Mock,避免意外拦截真实请求
|
|
4479
|
+
|
|
4480
|
+
---
|
|
4481
|
+
|
|
4482
|
+
### 29. 数据迁移助手 (`data-migration`)
|
|
4483
|
+
|
|
4484
|
+
**描述**: 评估迁移风险,生成 UP/DOWN 双向脚本,制定零停机迁移与回滚方案
|
|
4485
|
+
|
|
4486
|
+
**触发词**: `数据迁移`, `data migration`, `schema migration`, `db migration`, `数据库迁移`, `migrate database`, `migration script`, `迁移脚本`, `@ethan data-migration`, `/data-migration`
|
|
4487
|
+
|
|
4488
|
+
**执行步骤**:
|
|
4489
|
+
|
|
4490
|
+
#### 1. 迁移评估
|
|
4491
|
+
|
|
4492
|
+
在编写任何脚本之前,先全面评估迁移的范围和风险:
|
|
4493
|
+
|
|
4494
|
+
**评估清单**
|
|
4495
|
+
|
|
4496
|
+
| 维度 | 问题 | 影响 |
|
|
4497
|
+
|------|------|------|
|
|
4498
|
+
| **数据量** | 涉及多少行/多少 GB? | 决定迁移时长和窗口 |
|
|
4499
|
+
| **Schema 变更** | 新增/修改/删除了哪些列? | 影响向后兼容性 |
|
|
4500
|
+
| **外键约束** | 是否有级联影响? | 需要临时禁用约束 |
|
|
4501
|
+
| **业务流量** | 高峰期 QPS 是多少? | 影响迁移策略选择 |
|
|
4502
|
+
| **停机容忍** | 是否接受停机?接受多长? | 决定是否需要零停机方案 |
|
|
4503
|
+
| **数据一致性** | 允许最终一致性还是强一致? | 影响双写策略 |
|
|
4504
|
+
|
|
4505
|
+
**Schema 变更对比**
|
|
4506
|
+
```sql
|
|
4507
|
+
-- 变更前
|
|
4508
|
+
CREATE TABLE users (
|
|
4509
|
+
id BIGINT PRIMARY KEY,
|
|
4510
|
+
username VARCHAR(50),
|
|
4511
|
+
created_at TIMESTAMP
|
|
4512
|
+
);
|
|
4513
|
+
|
|
4514
|
+
-- 变更后(新增 email 列,重命名 username → name)
|
|
4515
|
+
CREATE TABLE users (
|
|
4516
|
+
id BIGINT PRIMARY KEY,
|
|
4517
|
+
name VARCHAR(100), -- 原 username,扩大长度
|
|
4518
|
+
email VARCHAR(255), -- 新增,NOT NULL 需要默认值
|
|
4519
|
+
created_at TIMESTAMP,
|
|
4520
|
+
updated_at TIMESTAMP -- 新增审计列
|
|
4521
|
+
);
|
|
4522
|
+
```
|
|
4523
|
+
|
|
4524
|
+
**停机时间估算**
|
|
4525
|
+
```
|
|
4526
|
+
数据行数: 5,000,000
|
|
4527
|
+
迁移速度: ~10,000 行/秒(受磁盘 IO 限制)
|
|
4528
|
+
估算时长: 5,000,000 ÷ 10,000 = 500 秒 ≈ 8.3 分钟
|
|
4529
|
+
建议窗口: 低峰期(凌晨 2:00-4:00)
|
|
4530
|
+
```
|
|
4531
|
+
|
|
4532
|
+
**输出**:迁移评估报告(范围 + 风险 + 时长估算 + 策略选择)
|
|
4533
|
+
|
|
4534
|
+
#### 2. 迁移脚本生成(UP/DOWN)
|
|
4535
|
+
|
|
4536
|
+
生成可逆的双向迁移脚本:
|
|
4537
|
+
|
|
4538
|
+
**Knex.js 迁移模板(Node.js)**
|
|
4539
|
+
```typescript
|
|
4540
|
+
// migrations/20240115_add_email_rename_username.ts
|
|
4541
|
+
import { Knex } from 'knex';
|
|
4542
|
+
|
|
4543
|
+
export async function up(knex: Knex): Promise<void> {
|
|
4544
|
+
await knex.schema.alterTable('users', (table) => {
|
|
4545
|
+
// 1. 新增列(先加,允许 NULL,稍后回填)
|
|
4546
|
+
table.string('name', 100).nullable();
|
|
4547
|
+
table.string('email', 255).nullable();
|
|
4548
|
+
table.timestamp('updated_at').nullable();
|
|
4549
|
+
});
|
|
4550
|
+
|
|
4551
|
+
// 2. 数据回填(分批处理,避免锁表)
|
|
4552
|
+
const BATCH_SIZE = 1000;
|
|
4553
|
+
let offset = 0;
|
|
4554
|
+
while (true) {
|
|
4555
|
+
const rows = await knex('users')
|
|
4556
|
+
.select('id', 'username')
|
|
4557
|
+
.limit(BATCH_SIZE)
|
|
4558
|
+
.offset(offset);
|
|
4559
|
+
if (rows.length === 0) break;
|
|
4560
|
+
|
|
4561
|
+
await knex('users')
|
|
4562
|
+
.whereIn('id', rows.map((r) => r.id))
|
|
4563
|
+
.update((row: any) => ({ name: row.username }));
|
|
4564
|
+
|
|
4565
|
+
offset += BATCH_SIZE;
|
|
4566
|
+
}
|
|
4567
|
+
|
|
4568
|
+
// 3. 添加约束
|
|
4569
|
+
await knex.schema.alterTable('users', (table) => {
|
|
4570
|
+
table.string('name', 100).notNullable().alter();
|
|
4571
|
+
table.dropColumn('username'); // 危险操作!确认数据回填完成后执行
|
|
4572
|
+
});
|
|
4573
|
+
}
|
|
4574
|
+
|
|
4575
|
+
export async function down(knex: Knex): Promise<void> {
|
|
4576
|
+
// 回滚:恢复 username 列
|
|
4577
|
+
await knex.schema.alterTable('users', (table) => {
|
|
4578
|
+
table.string('username', 50).nullable();
|
|
4579
|
+
table.dropColumn('name');
|
|
4580
|
+
table.dropColumn('email');
|
|
4581
|
+
table.dropColumn('updated_at');
|
|
4582
|
+
});
|
|
4583
|
+
|
|
4584
|
+
// 回填 username(从备份表恢复)
|
|
4585
|
+
await knex.raw('UPDATE users u JOIN users_backup b ON u.id = b.id SET u.username = b.username');
|
|
4586
|
+
}
|
|
4587
|
+
```
|
|
4588
|
+
|
|
4589
|
+
**Flyway SQL 迁移模板**
|
|
4590
|
+
```sql
|
|
4591
|
+
-- V20240115__add_email_rename_username.sql
|
|
4592
|
+
BEGIN;
|
|
4593
|
+
|
|
4594
|
+
-- 阶段1:新增列
|
|
4595
|
+
ALTER TABLE users ADD COLUMN name VARCHAR(100);
|
|
4596
|
+
ALTER TABLE users ADD COLUMN email VARCHAR(255);
|
|
4597
|
+
ALTER TABLE users ADD COLUMN updated_at TIMESTAMP DEFAULT NOW();
|
|
4598
|
+
|
|
4599
|
+
-- 阶段2:数据回填
|
|
4600
|
+
UPDATE users SET name = username WHERE name IS NULL;
|
|
4601
|
+
|
|
4602
|
+
-- 阶段3:添加约束(回填完成后)
|
|
4603
|
+
ALTER TABLE users ALTER COLUMN name SET NOT NULL;
|
|
4604
|
+
CREATE INDEX CONCURRENTLY idx_users_email ON users(email);
|
|
4605
|
+
|
|
4606
|
+
COMMIT;
|
|
4607
|
+
```
|
|
4608
|
+
|
|
4609
|
+
**输出**:可执行的 UP/DOWN 迁移脚本
|
|
4610
|
+
|
|
4611
|
+
#### 3. 数据验证策略
|
|
4612
|
+
|
|
4613
|
+
迁移前后必须验证数据完整性:
|
|
4614
|
+
|
|
4615
|
+
**三阶段验证**
|
|
4616
|
+
|
|
4617
|
+
**① 迁移前基线(Baseline)**
|
|
4618
|
+
```sql
|
|
4619
|
+
-- 记录迁移前状态
|
|
4620
|
+
CREATE TABLE migration_baseline AS
|
|
4621
|
+
SELECT
|
|
4622
|
+
COUNT(*) as total_rows,
|
|
4623
|
+
COUNT(DISTINCT id) as unique_ids,
|
|
4624
|
+
SUM(CASE WHEN email IS NOT NULL THEN 1 ELSE 0 END) as non_null_email,
|
|
4625
|
+
MD5(STRING_AGG(id::text, ',' ORDER BY id)) as row_checksum
|
|
4626
|
+
FROM users;
|
|
4627
|
+
```
|
|
4628
|
+
|
|
4629
|
+
**② 迁移后验证(Post-Migration)**
|
|
4630
|
+
```sql
|
|
4631
|
+
-- 行数对比
|
|
4632
|
+
SELECT
|
|
4633
|
+
(SELECT COUNT(*) FROM users) as after_count,
|
|
4634
|
+
(SELECT total_rows FROM migration_baseline) as before_count,
|
|
4635
|
+
(SELECT COUNT(*) FROM users) - (SELECT total_rows FROM migration_baseline) as diff;
|
|
4636
|
+
|
|
4637
|
+
-- 关键字段空值检查
|
|
4638
|
+
SELECT COUNT(*) as null_names FROM users WHERE name IS NULL;
|
|
4639
|
+
SELECT COUNT(*) as null_emails FROM users WHERE email IS NULL;
|
|
4640
|
+
|
|
4641
|
+
-- 数据一致性抽样(对比10%数据)
|
|
4642
|
+
SELECT u.id, u.name, b.username
|
|
4643
|
+
FROM users u
|
|
4644
|
+
JOIN users_backup b ON u.id = b.id
|
|
4645
|
+
WHERE u.name != b.username
|
|
4646
|
+
LIMIT 100;
|
|
4647
|
+
```
|
|
4648
|
+
|
|
4649
|
+
**③ 业务验证(Smoke Test)**
|
|
4650
|
+
```bash
|
|
4651
|
+
# 验证核心业务接口
|
|
4652
|
+
curl -X POST /api/auth/login -d '{"email":"test@example.com","password":"xxx"}'
|
|
4653
|
+
curl -X GET /api/users/1
|
|
4654
|
+
curl -X POST /api/orders -d '{"userId":1,"items":[...]}'
|
|
4655
|
+
```
|
|
4656
|
+
|
|
4657
|
+
**验证通过标准**
|
|
4658
|
+
- [ ] 行数差异为 0(或符合预期的新增/删除)
|
|
4659
|
+
- [ ] 关键字段 NULL 率为 0
|
|
4660
|
+
- [ ] 数据抽样一致性 100%
|
|
4661
|
+
- [ ] Smoke Test 全部通过
|
|
4662
|
+
- [ ] 数据库慢查询无异常增加
|
|
4663
|
+
|
|
4664
|
+
**输出**:验证脚本套件 + 通过标准 Checklist
|
|
4665
|
+
|
|
4666
|
+
#### 4. 回滚方案
|
|
4667
|
+
|
|
4668
|
+
制定多层次回滚机制,确保任何阶段都可以安全撤退:
|
|
4669
|
+
|
|
4670
|
+
**三级回滚策略**
|
|
4671
|
+
|
|
4672
|
+
**Level 1:脚本级回滚(最快,< 5 分钟)**
|
|
4673
|
+
```bash
|
|
4674
|
+
# 直接执行 DOWN 脚本
|
|
4675
|
+
npx knex migrate:down
|
|
4676
|
+
# 或
|
|
4677
|
+
flyway undo
|
|
4678
|
+
```
|
|
4679
|
+
适用条件:迁移后立即发现问题,数据量小
|
|
4680
|
+
|
|
4681
|
+
**Level 2:备份恢复(中速,5-30 分钟)**
|
|
4682
|
+
```bash
|
|
4683
|
+
# 迁移前创建备份(必须!)
|
|
4684
|
+
pg_dump -h $DB_HOST -U $DB_USER -d $DB_NAME --table=users -f backup_users_$(date +%Y%m%d_%H%M%S).sql
|
|
4685
|
+
|
|
4686
|
+
# 恢复
|
|
4687
|
+
psql -h $DB_HOST -U $DB_USER -d $DB_NAME < backup_users_20240115_020000.sql
|
|
4688
|
+
```
|
|
4689
|
+
|
|
4690
|
+
**Level 3:蓝绿数据库(最可靠,但成本高)**
|
|
4691
|
+
```
|
|
4692
|
+
Green DB(新)→ 迁移成功 → 流量切到 Green
|
|
4693
|
+
Blue DB(旧)→ 保留 72 小时 → 确认无问题后关闭
|
|
4694
|
+
```
|
|
4695
|
+
|
|
4696
|
+
**回滚决策树**
|
|
4697
|
+
```
|
|
4698
|
+
迁移执行中...
|
|
4699
|
+
├── 数据验证失败?
|
|
4700
|
+
│ └── YES → 立即执行 Level 1 回滚(DOWN 脚本)
|
|
4701
|
+
├── 业务 Smoke Test 失败?
|
|
4702
|
+
│ └── YES → Level 1 回滚 → 分析根因
|
|
4703
|
+
└── 迁移后 24 小时内发现数据异常?
|
|
4704
|
+
└── YES → Level 2 回滚(备份恢复)→ 通知用户
|
|
4705
|
+
```
|
|
4706
|
+
|
|
4707
|
+
**输出**:多级回滚方案 + 回滚决策树 + 备份命令
|
|
4708
|
+
|
|
4709
|
+
#### 5. 零停机迁移四步法
|
|
4710
|
+
|
|
4711
|
+
对于无法停机的生产系统,使用"扩列→双写→切流→清旧"四步法:
|
|
4712
|
+
|
|
4713
|
+
**背景**:将 `username` 列重命名为 `name`,同时新增 `email` 列
|
|
4714
|
+
|
|
4715
|
+
**Step 1:扩列(向后兼容)**
|
|
4716
|
+
```sql
|
|
4717
|
+
-- 新增 name 和 email 列,保留 username(双列共存)
|
|
4718
|
+
ALTER TABLE users ADD COLUMN name VARCHAR(100);
|
|
4719
|
+
ALTER TABLE users ADD COLUMN email VARCHAR(255);
|
|
4720
|
+
-- 应用代码:只读 username,不写 name(暂不变更代码)
|
|
4721
|
+
```
|
|
4722
|
+
|
|
4723
|
+
**Step 2:双写 + 数据回填**
|
|
4724
|
+
```typescript
|
|
4725
|
+
// 应用代码更新:同时写入 username 和 name
|
|
4726
|
+
await db.users.update({
|
|
4727
|
+
where: { id },
|
|
4728
|
+
data: {
|
|
4729
|
+
username: newName, // 旧列
|
|
4730
|
+
name: newName, // 新列(同步写入)
|
|
4731
|
+
email: email,
|
|
4732
|
+
},
|
|
4733
|
+
});
|
|
4734
|
+
|
|
4735
|
+
// 异步回填历史数据(后台任务,不影响主流程)
|
|
4736
|
+
async function backfillNames() {
|
|
4737
|
+
let cursor = 0;
|
|
4738
|
+
while (true) {
|
|
4739
|
+
const rows = await db.users.findMany({
|
|
4740
|
+
where: { name: null },
|
|
4741
|
+
take: 1000,
|
|
4742
|
+
});
|
|
4743
|
+
if (rows.length === 0) break;
|
|
4744
|
+
await db.users.updateMany({
|
|
4745
|
+
where: { id: { in: rows.map(r => r.id) } },
|
|
4746
|
+
data: rows.map(r => ({ name: r.username })),
|
|
4747
|
+
});
|
|
4748
|
+
}
|
|
4749
|
+
}
|
|
4750
|
+
```
|
|
4751
|
+
|
|
4752
|
+
**Step 3:切流(读取切换到新列)**
|
|
4753
|
+
```typescript
|
|
4754
|
+
// 确认回填完成后,切换读取源
|
|
4755
|
+
// 应用代码:读 name,写 name(不再写 username)
|
|
4756
|
+
const user = await db.users.findUnique({
|
|
4757
|
+
select: { id: true, name: true, email: true }, // 不再 select username
|
|
4758
|
+
});
|
|
4759
|
+
```
|
|
4760
|
+
|
|
4761
|
+
**Step 4:清旧(移除废弃列)**
|
|
4762
|
+
```sql
|
|
4763
|
+
-- 确认应用代码已完全切换(观察 1-2 周)
|
|
4764
|
+
ALTER TABLE users DROP COLUMN username;
|
|
4765
|
+
-- 观察指标:慢查询、错误日志中是否有 username 相关错误
|
|
4766
|
+
```
|
|
4767
|
+
|
|
4768
|
+
**关键时间线**
|
|
4769
|
+
```
|
|
4770
|
+
Day 0: Step 1(扩列)→ 部署新版本(双写)
|
|
4771
|
+
Day 1: Step 2(确认双写正常,后台回填完成)
|
|
4772
|
+
Day 3: Step 3(切换读取到新列)→ 部署
|
|
4773
|
+
Day 14: Step 4(确认无问题,删除旧列)
|
|
4774
|
+
```
|
|
4775
|
+
|
|
4776
|
+
**输出**:零停机迁移四步代码示例 + 时间线 + 监控指标
|
|
4777
|
+
|
|
4778
|
+
**输出格式**: 迁移评估报告 + UP/DOWN 迁移脚本 + 数据验证 SQL 套件 + 多级回滚方案 + 零停机迁移四步代码示例
|
|
4779
|
+
|
|
4780
|
+
**注意事项**:
|
|
4781
|
+
- 数据迁移前必须备份,无论多紧急。备份是回滚的最后防线
|
|
4782
|
+
- 分批处理大表(BATCH_SIZE = 1000-5000),避免长事务和表锁
|
|
4783
|
+
- 生产迁移应先在测试环境全流程演练,记录实际耗时
|
|
4784
|
+
- 零停机迁移每个步骤之间至少观察 24 小时,确认监控指标正常再进行下一步
|
|
4785
|
+
- DROP COLUMN 是不可逆操作,在确认切换完成前绝对不要执行
|
|
4786
|
+
|
|
4787
|
+
---
|
|
4788
|
+
|
|
4789
|
+
### 30. LLM 功能设计助手 (`llm-feature`)
|
|
4790
|
+
|
|
4791
|
+
**描述**: 设计 RAG/Agent/生成类 LLM 功能,输出 Prompt 模板、评估框架与降级方案
|
|
4792
|
+
|
|
4793
|
+
**触发词**: `llm feature`, `ai feature`, `rag`, `llm 功能`, `ai 功能设计`, `prompt design`, `大模型功能`, `agent 设计`, `@ethan llm-feature`, `/llm-feature`
|
|
4794
|
+
|
|
4795
|
+
**执行步骤**:
|
|
4796
|
+
|
|
4797
|
+
#### 1. LLM 功能定位与场景分类
|
|
4798
|
+
|
|
4799
|
+
明确 LLM 功能的类型和边界,选择合适的架构模式:
|
|
4800
|
+
|
|
4801
|
+
**五大 LLM 场景类型**
|
|
4802
|
+
|
|
4803
|
+
| 场景 | 描述 | 典型案例 | 推荐架构 |
|
|
4804
|
+
|------|------|---------|---------|
|
|
4805
|
+
| **RAG(检索增强生成)** | 基于私有知识库回答 | 内部文档问答、客服 | 向量检索 + LLM |
|
|
4806
|
+
| **Agent(自主决策)** | 多步骤工具调用 | 代码 Agent、任务自动化 | LLM + Function Calling |
|
|
4807
|
+
| **生成** | 创作/摘要/翻译 | 文案生成、会议纪要 | 直接调用 + 结构化输出 |
|
|
4808
|
+
| **分类** | 意图/情感/标签 | 工单分类、评论分析 | Fine-tune 或 Few-shot |
|
|
4809
|
+
| **提取** | 信息抽取/NER | 合同关键信息、表单填充 | Structured Output + JSON Schema |
|
|
4810
|
+
|
|
4811
|
+
**功能定位问卷**
|
|
4812
|
+
```
|
|
4813
|
+
1. 用户问题的答案在哪里?
|
|
4814
|
+
- 模型已知知识 → 直接生成
|
|
4815
|
+
- 私有文档/数据库 → RAG 架构
|
|
4816
|
+
- 需要实时操作 → Agent 架构
|
|
4817
|
+
|
|
4818
|
+
2. 需要多步推理吗?
|
|
4819
|
+
- 是 → Agent(工具调用链)
|
|
4820
|
+
- 否 → 单次 Prompt
|
|
4821
|
+
|
|
4822
|
+
3. 输出格式是否严格?
|
|
4823
|
+
- 是 → JSON Mode / Structured Output
|
|
4824
|
+
- 否 → 自然语言生成
|
|
4825
|
+
|
|
4826
|
+
4. 延迟要求?
|
|
4827
|
+
- < 500ms → 小模型 + 缓存
|
|
4828
|
+
- < 3s → 标准调用
|
|
4829
|
+
- 可接受流式 → 流式输出
|
|
4830
|
+
```
|
|
4831
|
+
|
|
4832
|
+
**输出**:LLM 功能定位文档(场景类型 + 架构选型建议)
|
|
4833
|
+
|
|
4834
|
+
#### 2. RAG 架构设计
|
|
4835
|
+
|
|
4836
|
+
为知识库问答类功能设计高质量 RAG 架构:
|
|
4837
|
+
|
|
4838
|
+
**RAG 核心组件**
|
|
4839
|
+
```
|
|
4840
|
+
文档摄入管道:
|
|
4841
|
+
Raw Docs → Chunking → Embedding → Vector Store
|
|
4842
|
+
↓
|
|
4843
|
+
Metadata 过滤层(时间/来源/权限)
|
|
4844
|
+
|
|
4845
|
+
查询管道:
|
|
4846
|
+
User Query → Query 改写/扩展 → 向量检索 → Rerank → LLM 生成
|
|
4847
|
+
```
|
|
4848
|
+
|
|
4849
|
+
**Chunking 策略选型**
|
|
4850
|
+
```typescript
|
|
4851
|
+
// 固定大小(适合均匀文本)
|
|
4852
|
+
const CHUNK_SIZE = 512; // tokens
|
|
4853
|
+
const CHUNK_OVERLAP = 50; // 上下文保留
|
|
4854
|
+
|
|
4855
|
+
// 语义分块(适合结构化文档,推荐)
|
|
4856
|
+
// 按段落/标题边界切分
|
|
4857
|
+
function semanticChunk(text: string): string[] {
|
|
4858
|
+
return text.split(/
|
|
4859
|
+
#{1,3}s/); // Markdown 标题分块
|
|
4860
|
+
}
|
|
4861
|
+
|
|
4862
|
+
// 递归分块(LangChain RecursiveCharacterTextSplitter)
|
|
4863
|
+
// 优先按段落 → 句子 → 词语拆分
|
|
4864
|
+
```
|
|
4865
|
+
|
|
4866
|
+
**向量库选型矩阵**
|
|
4867
|
+
| 方案 | 适用规模 | 托管 | 特性 |
|
|
4868
|
+
|------|---------|------|------|
|
|
4869
|
+
| **Pinecone** | 100万+ | 云托管 | 生产级,自动扩容 |
|
|
4870
|
+
| **Weaviate** | 中大型 | 自托管/云 | 混合搜索,GraphQL |
|
|
4871
|
+
| **Chroma** | 开发/小型 | 本地 | 零配置,适合 PoC |
|
|
4872
|
+
| **pgvector** | 中型 | PostgreSQL | 复用现有 DB |
|
|
4873
|
+
|
|
4874
|
+
**Rerank 优化**
|
|
4875
|
+
```typescript
|
|
4876
|
+
// 两阶段检索:向量粗召回 → 精排重排序
|
|
4877
|
+
const candidates = await vectorStore.search(query, { topK: 20 });
|
|
4878
|
+
const reranked = await rerankModel.rank(query, candidates, { topN: 5 });
|
|
4879
|
+
// 推荐:Cohere Rerank / BGE-Reranker
|
|
4880
|
+
```
|
|
4881
|
+
|
|
4882
|
+
**输出**:RAG 架构图 + Chunking 策略 + 向量库选型建议
|
|
4883
|
+
|
|
4884
|
+
#### 3. Prompt 工程规范
|
|
4885
|
+
|
|
4886
|
+
制定高质量 Prompt 的设计规范:
|
|
4887
|
+
|
|
4888
|
+
**System Prompt 黄金结构**
|
|
4889
|
+
```
|
|
4890
|
+
[角色定义] 你是 {产品名} 的 {角色},专注于 {领域}。
|
|
4891
|
+
[能力边界] 你可以 {能力列表}。你不能/不应该 {限制列表}。
|
|
4892
|
+
[输出格式] 请按照以下格式回复:{格式规范}
|
|
4893
|
+
[行为准则] {特定规则,如:始终用中文回复/引用来源}
|
|
4894
|
+
```
|
|
4895
|
+
|
|
4896
|
+
**System Prompt 模板**
|
|
4897
|
+
```typescript
|
|
4898
|
+
const SYSTEM_PROMPT = `
|
|
4899
|
+
你是 Acme Corp 的智能客服助手,专注于解答产品使用和技术支持问题。
|
|
4900
|
+
|
|
4901
|
+
## 能力
|
|
4902
|
+
- 查询订单状态、退换货政策、产品规格
|
|
4903
|
+
- 引导用户完成常见操作流程
|
|
4904
|
+
- 基于知识库提供准确答案
|
|
4905
|
+
|
|
4906
|
+
## 限制
|
|
4907
|
+
- 不讨论竞争对手产品
|
|
4908
|
+
- 不提供具体价格承诺(引导至销售团队)
|
|
4909
|
+
- 不确定时明确说明,不猜测
|
|
4910
|
+
|
|
4911
|
+
## 输出规范
|
|
4912
|
+
- 语言:与用户保持一致(中/英文)
|
|
4913
|
+
- 长度:简洁清晰,避免超过 200 字
|
|
4914
|
+
- 引用:若基于文档回答,在末尾标注 [来源:{文档名}]
|
|
4915
|
+
`.trim();
|
|
4916
|
+
```
|
|
4917
|
+
|
|
4918
|
+
**Few-shot 示例设计**
|
|
4919
|
+
```typescript
|
|
4920
|
+
const FEW_SHOT_EXAMPLES = [
|
|
4921
|
+
{
|
|
4922
|
+
role: 'user',
|
|
4923
|
+
content: '我的订单 #12345 什么时候发货?'
|
|
4924
|
+
},
|
|
4925
|
+
{
|
|
4926
|
+
role: 'assistant',
|
|
4927
|
+
content: '您好!根据订单信息,#12345 已于昨天完成备货,预计今日发出,3-5个工作日送达。[来源:订单管理文档]'
|
|
4928
|
+
}
|
|
4929
|
+
];
|
|
4930
|
+
```
|
|
4931
|
+
|
|
4932
|
+
**关键参数建议**
|
|
4933
|
+
```typescript
|
|
4934
|
+
// 不同场景的推荐参数
|
|
4935
|
+
const PARAMS = {
|
|
4936
|
+
factual_qa: { temperature: 0.1, max_tokens: 500 }, // 事实问答,低随机性
|
|
4937
|
+
creative: { temperature: 0.8, max_tokens: 2000 }, // 创意生成
|
|
4938
|
+
classification:{ temperature: 0, max_tokens: 50 }, // 分类,确定性输出
|
|
4939
|
+
code_gen: { temperature: 0.2, max_tokens: 4000 }, // 代码生成
|
|
4940
|
+
};
|
|
4941
|
+
```
|
|
4942
|
+
|
|
4943
|
+
**Token 预算管理**
|
|
4944
|
+
```
|
|
4945
|
+
System Prompt: ~500 tokens (固定)
|
|
4946
|
+
RAG Context: ~2000 tokens (动态,按相关度截断)
|
|
4947
|
+
Conversation: ~1000 tokens (保留最近 N 轮)
|
|
4948
|
+
User Query: ~200 tokens (预留)
|
|
4949
|
+
Output: ~1000 tokens (max_tokens 控制)
|
|
4950
|
+
总计: ~4700 tokens < 8K context window
|
|
4951
|
+
```
|
|
4952
|
+
|
|
4953
|
+
**输出**:System Prompt 模板 + Few-shot 示例 + 参数配置建议
|
|
4954
|
+
|
|
4955
|
+
#### 4. LLM 评估方案
|
|
4956
|
+
|
|
4957
|
+
建立自动化 + 人工评估体系,持续监控 LLM 功能质量:
|
|
4958
|
+
|
|
4959
|
+
**三层评估框架**
|
|
4960
|
+
|
|
4961
|
+
**① 自动化 Evals(CI/CD 集成)**
|
|
4962
|
+
```typescript
|
|
4963
|
+
// 评估维度
|
|
4964
|
+
interface EvalResult {
|
|
4965
|
+
accuracy: number; // 答案正确率(对比 Golden Set)
|
|
4966
|
+
faithfulness: number; // 回答与上下文一致性(RAG 专用)
|
|
4967
|
+
relevance: number; // 答案与问题相关度
|
|
4968
|
+
latency_p95: number; // P95 响应时间(ms)
|
|
4969
|
+
cost_per_query: number; // 平均 token 成本($)
|
|
4970
|
+
}
|
|
4971
|
+
|
|
4972
|
+
// 自动评估示例(LLM-as-Judge)
|
|
4973
|
+
async function evalWithLLM(question: string, answer: string, context: string) {
|
|
4974
|
+
const judgePrompt = `
|
|
4975
|
+
问题:${question}
|
|
4976
|
+
检索上下文:${context}
|
|
4977
|
+
模型回答:${answer}
|
|
4978
|
+
|
|
4979
|
+
请评估回答的准确性(0-1)和忠实度(0-1),返回 JSON:
|
|
4980
|
+
{"accuracy": 0.x, "faithfulness": 0.x, "reason": "..."}
|
|
4981
|
+
`;
|
|
4982
|
+
return await llm.json(judgePrompt);
|
|
4983
|
+
}
|
|
4984
|
+
```
|
|
4985
|
+
|
|
4986
|
+
**② Golden Set 维护**
|
|
4987
|
+
```json
|
|
4988
|
+
// evals/golden-set.json
|
|
4989
|
+
[
|
|
4990
|
+
{
|
|
4991
|
+
"id": "Q001",
|
|
4992
|
+
"question": "退货政策是什么?",
|
|
4993
|
+
"expected_keywords": ["30天", "unopened", "退款"],
|
|
4994
|
+
"expected_source": "return-policy.md",
|
|
4995
|
+
"difficulty": "easy"
|
|
4996
|
+
}
|
|
4997
|
+
]
|
|
4998
|
+
```
|
|
4999
|
+
|
|
5000
|
+
**③ 人工评分(A/B 测试)**
|
|
5001
|
+
```
|
|
5002
|
+
评分维度(1-5分):
|
|
5003
|
+
- Helpful(有帮助): 回答解决了用户问题
|
|
5004
|
+
- Accurate(准确): 信息与事实一致
|
|
5005
|
+
- Safe(安全): 无有害/不当内容
|
|
5006
|
+
- Concise(简洁): 无冗余信息
|
|
5007
|
+
```
|
|
5008
|
+
|
|
5009
|
+
**评估指标基准**
|
|
5010
|
+
| 指标 | 可接受 | 良好 | 优秀 |
|
|
5011
|
+
|------|--------|------|------|
|
|
5012
|
+
| 准确率 | > 70% | > 85% | > 95% |
|
|
5013
|
+
| 忠实度 | > 75% | > 90% | > 97% |
|
|
5014
|
+
| P95 延迟 | < 5s | < 3s | < 1.5s |
|
|
5015
|
+
| 幻觉率 | < 15% | < 5% | < 2% |
|
|
5016
|
+
|
|
5017
|
+
**输出**:评估脚本 + Golden Set 模板 + A/B 测试方案
|
|
5018
|
+
|
|
5019
|
+
#### 5. 降级与安全策略
|
|
5020
|
+
|
|
5021
|
+
为 LLM 功能设计完善的降级和安全机制:
|
|
5022
|
+
|
|
5023
|
+
**五大风险与对策**
|
|
5024
|
+
|
|
5025
|
+
**① 幻觉控制**
|
|
5026
|
+
```typescript
|
|
5027
|
+
// RAG 置信度检查:若无相关文档,拒绝回答
|
|
5028
|
+
const MIN_SIMILARITY = 0.75;
|
|
5029
|
+
const docs = await vectorStore.search(query, { topK: 3 });
|
|
5030
|
+
if (docs[0].score < MIN_SIMILARITY) {
|
|
5031
|
+
return { answer: '抱歉,我没有找到相关信息,请联系人工客服。', sources: [] };
|
|
5032
|
+
}
|
|
5033
|
+
```
|
|
5034
|
+
|
|
5035
|
+
**② Fallback 降级链**
|
|
5036
|
+
```typescript
|
|
5037
|
+
async function robustLLMCall(prompt: string) {
|
|
5038
|
+
try {
|
|
5039
|
+
// 主模型(高质量)
|
|
5040
|
+
return await gpt4o.complete(prompt, { timeout: 5000 });
|
|
5041
|
+
} catch (primaryError) {
|
|
5042
|
+
try {
|
|
5043
|
+
// 降级到备用模型(更快更便宜)
|
|
5044
|
+
return await gpt4oMini.complete(prompt, { timeout: 3000 });
|
|
5045
|
+
} catch (fallbackError) {
|
|
5046
|
+
// 最终降级:静态规则/人工兜底
|
|
5047
|
+
return { answer: FALLBACK_MESSAGES[detectIntent(prompt)], fallback: true };
|
|
5048
|
+
}
|
|
5049
|
+
}
|
|
5050
|
+
}
|
|
5051
|
+
```
|
|
5052
|
+
|
|
5053
|
+
**③ 内容过滤**
|
|
5054
|
+
```typescript
|
|
5055
|
+
// 输入/输出双向过滤
|
|
5056
|
+
const BLOCKED_PATTERNS = [/如何.*伤害/, /制作.*武器/];
|
|
5057
|
+
function isSafe(text: string): boolean {
|
|
5058
|
+
return !BLOCKED_PATTERNS.some(p => p.test(text));
|
|
5059
|
+
}
|
|
5060
|
+
// 推荐:使用 OpenAI Moderation API / Azure Content Safety
|
|
5061
|
+
```
|
|
5062
|
+
|
|
5063
|
+
**④ 成本限制**
|
|
5064
|
+
```typescript
|
|
5065
|
+
// 每用户每日 Token 限额
|
|
5066
|
+
const DAILY_TOKEN_LIMIT = 50000;
|
|
5067
|
+
// 请求频率限制
|
|
5068
|
+
const RATE_LIMIT = { requests: 10, window: '1m' };
|
|
5069
|
+
// 输出截断
|
|
5070
|
+
const MAX_OUTPUT_TOKENS = 1000;
|
|
5071
|
+
```
|
|
5072
|
+
|
|
5073
|
+
**⑤ 可观测性**
|
|
5074
|
+
```typescript
|
|
5075
|
+
// 每次 LLM 调用必须记录
|
|
5076
|
+
logger.info('llm_call', {
|
|
5077
|
+
model, prompt_tokens, completion_tokens, latency_ms,
|
|
5078
|
+
user_id, session_id, feature_flag,
|
|
5079
|
+
is_fallback, safety_triggered,
|
|
5080
|
+
});
|
|
5081
|
+
```
|
|
5082
|
+
|
|
5083
|
+
**输出**:降级策略代码模板 + 内容安全配置 + 成本控制方案 + 可观测性埋点
|
|
5084
|
+
|
|
5085
|
+
**输出格式**: LLM 功能定位文档 + RAG 架构图 + System Prompt 模板 + 评估框架(Golden Set + 自动 Evals)+ 降级与安全策略代码
|
|
5086
|
+
|
|
5087
|
+
**注意事项**:
|
|
5088
|
+
- 先从小模型(GPT-4o mini/Claude Haiku)开始验证,确认方案可行再升级到大模型降低成本
|
|
5089
|
+
- RAG 的质量 80% 取决于 Chunking 策略和 Embedding 质量,而非 LLM 本身
|
|
5090
|
+
- Golden Set 至少需要 100 条覆盖核心场景的问答对,建立前先做 10 条快速验证
|
|
5091
|
+
- 幻觉是 RAG 的最大风险,必须设置相似度阈值,宁可说"不知道"也不要胡编
|
|
5092
|
+
- 生产环境必须监控 Token 消耗和每次调用成本,防止费用失控
|
|
5093
|
+
|
|
5094
|
+
---
|
|
5095
|
+
|
|
5096
|
+
### 31. 威胁建模 (`threat-model`)
|
|
5097
|
+
|
|
5098
|
+
**描述**: 运用 STRIDE 方法识别系统安全威胁,构建攻击树,制定缓解措施与残余风险评估
|
|
5099
|
+
|
|
5100
|
+
**触发词**: `威胁建模`, `threat model`, `threat modeling`, `stride`, `攻击面分析`, `security design`, `安全设计`, `攻击树`, `@ethan threat-model`, `/threat-model`
|
|
5101
|
+
|
|
5102
|
+
**执行步骤**:
|
|
5103
|
+
|
|
5104
|
+
#### 1. 系统边界定义
|
|
5105
|
+
|
|
5106
|
+
明确系统的信任边界、数据流和外部实体:
|
|
5107
|
+
|
|
5108
|
+
**系统分解步骤**
|
|
5109
|
+
|
|
5110
|
+
1. **识别外部实体**(系统外部的人/系统)
|
|
5111
|
+
- 终端用户(匿名用户、注册用户、管理员)
|
|
5112
|
+
- 外部系统(第三方 API、支付网关、邮件服务)
|
|
5113
|
+
- 内部其他服务(微服务间调用)
|
|
5114
|
+
|
|
5115
|
+
2. **绘制数据流图(DFD Level 0)**
|
|
5116
|
+
```
|
|
5117
|
+
[用户浏览器] ──HTTPS──> [Web 服务器] ──内网──> [应用服务器]
|
|
5118
|
+
│
|
|
5119
|
+
[数据库服务器]
|
|
5120
|
+
│
|
|
5121
|
+
[缓存 Redis]
|
|
5122
|
+
|
|
5123
|
+
信任边界(虚线):
|
|
5124
|
+
- 边界1:Internet / DMZ(用户浏览器 ↔ Web 服务器)
|
|
5125
|
+
- 边界2:DMZ / 内网(Web 服务器 ↔ 应用服务器)
|
|
5126
|
+
- 边界3:内网 / 数据层(应用服务器 ↔ DB/Cache)
|
|
5127
|
+
```
|
|
5128
|
+
|
|
5129
|
+
3. **数据资产分类**
|
|
5130
|
+
```
|
|
5131
|
+
🔴 高度敏感:用户密码、信用卡信息、身份证号
|
|
5132
|
+
🟠 敏感: 邮箱、手机号、订单信息、行为数据
|
|
5133
|
+
🟡 普通: 商品信息、公开内容、系统配置
|
|
5134
|
+
```
|
|
5135
|
+
|
|
5136
|
+
4. **威胁面列举**
|
|
5137
|
+
- 对外 API 端点数量:___
|
|
5138
|
+
- 需要认证的接口:___
|
|
5139
|
+
- 数据库直连点:___
|
|
5140
|
+
- 第三方 SDK 集成:___
|
|
5141
|
+
|
|
5142
|
+
**输出**:DFD 图 + 信任边界标注 + 数据资产清单
|
|
5143
|
+
|
|
5144
|
+
#### 2. STRIDE 威胁识别
|
|
5145
|
+
|
|
5146
|
+
用 STRIDE 框架对每个数据流逐一检查六类威胁:
|
|
5147
|
+
|
|
5148
|
+
**STRIDE 威胁矩阵**
|
|
5149
|
+
|
|
5150
|
+
| 威胁类型 | 缩写含义 | 核心问题 | 安全属性 |
|
|
5151
|
+
|---------|---------|---------|---------|
|
|
5152
|
+
| **Spoofing(仿冒)** | 攻击者伪装成合法用户/系统 | 系统如何验证身份? | 认证 |
|
|
5153
|
+
| **Tampering(篡改)** | 恶意修改数据或代码 | 数据完整性如何保证? | 完整性 |
|
|
5154
|
+
| **Repudiation(否认)** | 用户否认执行过某操作 | 是否有可信的操作记录? | 不可否认性 |
|
|
5155
|
+
| **Information Disclosure(信息泄露)** | 敏感数据暴露给未授权方 | 数据在传输/存储中是否加密? | 机密性 |
|
|
5156
|
+
| **Denial of Service(拒绝服务)** | 使系统无法响应合法请求 | 系统能承受多大压力? | 可用性 |
|
|
5157
|
+
| **Elevation of Privilege(权限提升)** | 获得超出授权的权限 | 权限检查是否严格? | 授权 |
|
|
5158
|
+
|
|
5159
|
+
**针对登录接口的 STRIDE 分析示例**
|
|
5160
|
+
|
|
5161
|
+
```
|
|
5162
|
+
接口:POST /api/auth/login(外部 → 应用服务器)
|
|
5163
|
+
|
|
5164
|
+
S - 仿冒:
|
|
5165
|
+
威胁:攻击者暴力破解或凭据填充(Credential Stuffing)
|
|
5166
|
+
影响:高(账号被盗)
|
|
5167
|
+
|
|
5168
|
+
T - 篡改:
|
|
5169
|
+
威胁:中间人攻击修改请求(非 HTTPS 场景)
|
|
5170
|
+
影响:高(凭据被劫持)
|
|
5171
|
+
|
|
5172
|
+
R - 否认:
|
|
5173
|
+
威胁:用户否认登录操作(无日志)
|
|
5174
|
+
影响:中(无法审计)
|
|
5175
|
+
|
|
5176
|
+
I - 信息泄露:
|
|
5177
|
+
威胁:错误信息泄露"用户不存在"vs"密码错误"
|
|
5178
|
+
影响:中(可枚举用户名)
|
|
5179
|
+
|
|
5180
|
+
D - 拒绝服务:
|
|
5181
|
+
威胁:大量登录请求耗尽服务器资源
|
|
5182
|
+
影响:高(服务不可用)
|
|
5183
|
+
|
|
5184
|
+
E - 权限提升:
|
|
5185
|
+
威胁:JWT 签名算法混淆(HS256 → None)
|
|
5186
|
+
影响:严重(绕过认证)
|
|
5187
|
+
```
|
|
5188
|
+
|
|
5189
|
+
**输出**:STRIDE 威胁识别矩阵(每个数据流 × 6 类威胁)
|
|
5190
|
+
|
|
5191
|
+
#### 3. 攻击树构建
|
|
5192
|
+
|
|
5193
|
+
为高风险威胁构建攻击树,量化攻击路径:
|
|
5194
|
+
|
|
5195
|
+
**攻击树符号**
|
|
5196
|
+
```
|
|
5197
|
+
目标(攻击根节点)
|
|
5198
|
+
├── AND: 所有子节点都需满足
|
|
5199
|
+
└── OR: 任意子节点满足即可(默认)
|
|
5200
|
+
```
|
|
5201
|
+
|
|
5202
|
+
**示例:攻击者获取用户数据攻击树**
|
|
5203
|
+
```
|
|
5204
|
+
[目标] 获取用户敏感数据
|
|
5205
|
+
├── OR
|
|
5206
|
+
│ ├── [路径1] 突破 Web 层
|
|
5207
|
+
│ │ ├── SQL 注入(概率: 中,影响: 严重)
|
|
5208
|
+
│ │ │ └── 防御:参数化查询 ✓
|
|
5209
|
+
│ │ └── XSS 窃取 Token(概率: 中,影响: 高)
|
|
5210
|
+
│ │ └── 防御:CSP + HttpOnly Cookie ✓
|
|
5211
|
+
│ ├── [路径2] 绕过认证
|
|
5212
|
+
│ │ ├── 暴力破解(概率: 高,影响: 高)
|
|
5213
|
+
│ │ │ └── 防御:Rate Limit + CAPTCHA ✓
|
|
5214
|
+
│ │ └── Token 伪造(概率: 低,影响: 严重)
|
|
5215
|
+
│ │ └── 防御:强签名算法(RS256)✓
|
|
5216
|
+
│ └── [路径3] 内部威胁
|
|
5217
|
+
│ └── 数据库直接访问(概率: 低,影响: 严重)
|
|
5218
|
+
│ └── 防御:最小权限 + 审计日志 ✓
|
|
5219
|
+
```
|
|
5220
|
+
|
|
5221
|
+
**风险评分(DREAD 模型)**
|
|
5222
|
+
```
|
|
5223
|
+
Damage(损害): 1-10
|
|
5224
|
+
Reproducibility(复现): 1-10
|
|
5225
|
+
Exploitability(利用难度): 1-10
|
|
5226
|
+
Affected Users(影响用户): 1-10
|
|
5227
|
+
Discoverability(发现难度): 1-10
|
|
5228
|
+
|
|
5229
|
+
总分 = (D+R+E+A+D) / 5
|
|
5230
|
+
8-10:严重 6-7.9:高 4-5.9:中 < 4:低
|
|
5231
|
+
```
|
|
5232
|
+
|
|
5233
|
+
**输出**:攻击树图 + DREAD 风险评分表
|
|
5234
|
+
|
|
5235
|
+
#### 4. 安全控制措施
|
|
5236
|
+
|
|
5237
|
+
为每个已识别威胁制定具体的缓解控制措施:
|
|
5238
|
+
|
|
5239
|
+
**STRIDE → 控制措施对照表**
|
|
5240
|
+
|
|
5241
|
+
| 威胁 | 控制措施 | 实现示例 |
|
|
5242
|
+
|------|---------|---------|
|
|
5243
|
+
| **仿冒** | 多因素认证、OAuth 2.0 | `TOTP + SMS OTP` |
|
|
5244
|
+
| **篡改** | HTTPS 全程加密、数字签名 | `TLS 1.3 + HMAC 请求签名` |
|
|
5245
|
+
| **否认** | 不可篡改审计日志 | `Append-only Log + 时间戳签名` |
|
|
5246
|
+
| **信息泄露** | 最小权限、数据脱敏、加密 | `AES-256 静态加密 + 字段脱敏` |
|
|
5247
|
+
| **DoS** | 限流、WAF、自动扩容 | `Rate Limit 10 req/s/IP + CDN` |
|
|
5248
|
+
| **权限提升** | RBAC、输入验证 | `JWT RS256 + 接口级鉴权` |
|
|
5249
|
+
|
|
5250
|
+
**优先级实现路线图**
|
|
5251
|
+
```
|
|
5252
|
+
P0(立即实现,影响上线):
|
|
5253
|
+
✅ HTTPS 强制(HSTS)
|
|
5254
|
+
✅ SQL 参数化查询
|
|
5255
|
+
✅ 密码 bcrypt 哈希(cost factor ≥ 12)
|
|
5256
|
+
✅ JWT 算法锁定(禁用 none/HS256→RS256)
|
|
5257
|
+
|
|
5258
|
+
P1(首个 Sprint 内):
|
|
5259
|
+
🔲 API 限流(100 req/min/user)
|
|
5260
|
+
🔲 账号锁定(5次失败锁定15分钟)
|
|
5261
|
+
🔲 安全响应头(CSP/X-Frame-Options/HSTS)
|
|
5262
|
+
🔲 审计日志(登录/权限变更/数据访问)
|
|
5263
|
+
|
|
5264
|
+
P2(次个 Sprint 内):
|
|
5265
|
+
🔲 多因素认证(MFA)
|
|
5266
|
+
🔲 字段级数据脱敏(API 响应)
|
|
5267
|
+
🔲 依赖漏洞扫描(npm audit / Snyk)
|
|
5268
|
+
🔲 渗透测试(OWASP Top 10 覆盖)
|
|
5269
|
+
```
|
|
5270
|
+
|
|
5271
|
+
**输出**:安全控制措施清单 + 实现优先级路线图
|
|
5272
|
+
|
|
5273
|
+
#### 5. 残余风险评估
|
|
5274
|
+
|
|
5275
|
+
评估实施控制措施后的剩余风险,制定接受/处置策略:
|
|
5276
|
+
|
|
5277
|
+
**四种风险处置方式**
|
|
5278
|
+
- **接受(Accept)**:风险可接受,记录并监控
|
|
5279
|
+
- **降低(Mitigate)**:实施控制措施降低概率/影响
|
|
5280
|
+
- **转移(Transfer)**:购买保险或外包给第三方(如 Cloudflare DDoS 防护)
|
|
5281
|
+
- **规避(Avoid)**:不实现该功能/移除风险点
|
|
5282
|
+
|
|
5283
|
+
**残余风险登记册模板**
|
|
5284
|
+
```
|
|
5285
|
+
| ID | 威胁描述 | 原始风险 | 控制措施 | 残余风险 | 处置策略 | 责任人 | 复查日期 |
|
|
5286
|
+
|----|---------|---------|---------|---------|---------|--------|---------|
|
|
5287
|
+
| T001 | 暴力破解密码 | 高(8) | Rate Limit + CAPTCHA | 低(2) | 接受 | @security | 2024-Q2 |
|
|
5288
|
+
| T002 | SQL注入 | 严重(9) | 参数化查询 | 极低(1) | 接受 | @backend | 2024-Q2 |
|
|
5289
|
+
| T003 | DDoS攻击 | 高(7) | Cloudflare | 低(3) | 转移 | @infra | 2024-Q2 |
|
|
5290
|
+
| T004 | 0-day漏洞 | 未知 | WAF + 快速响应 | 中(5) | 监控 | @security | 月度 |
|
|
5291
|
+
```
|
|
5292
|
+
|
|
5293
|
+
**安全评审 Checklist**
|
|
5294
|
+
- [ ] 所有 P0 控制措施已实现并测试
|
|
5295
|
+
- [ ] 威胁模型已经过安全团队评审
|
|
5296
|
+
- [ ] 高风险威胁已获得业务方正式接受签字
|
|
5297
|
+
- [ ] 残余风险已录入风险登记册
|
|
5298
|
+
- [ ] 设定下次威胁模型复审日期(建议每季度或重大变更后)
|
|
5299
|
+
|
|
5300
|
+
**输出**:残余风险登记册 + 风险接受声明 + 复审计划
|
|
5301
|
+
|
|
5302
|
+
**输出格式**: 威胁模型文档(DFD + 信任边界)+ STRIDE 威胁矩阵 + 攻击树图 + 安全控制措施路线图 + 残余风险登记册
|
|
5303
|
+
|
|
5304
|
+
**注意事项**:
|
|
5305
|
+
- 威胁建模应在设计阶段进行,而非开发完成后——越早发现安全问题修复成本越低
|
|
5306
|
+
- STRIDE 不是一次性工作,每次重大功能变更(新 API、新数据流)都应更新威胁模型
|
|
5307
|
+
- 攻击树帮助量化优先级,但不要追求完美——覆盖 OWASP Top 10 对大多数产品已足够
|
|
5308
|
+
- 内部人员威胁(离职员工、权限滥用)往往被忽视,建议在威胁建模中专门分析
|
|
5309
|
+
- 威胁建模输出文档应纳入安全合规证据库(SOC2/ISO27001 审计常见要求)
|
|
5310
|
+
|
|
5311
|
+
---
|
|
5312
|
+
|
|
5313
|
+
### 32. 绿色编码实践 (`green-code`)
|
|
5314
|
+
|
|
5315
|
+
**描述**: 识别代码能耗热点,优化算法复杂度与云资源效率,估算并降低软件碳排放
|
|
5316
|
+
|
|
5317
|
+
**触发词**: `绿色编码`, `green code`, `green software`, `carbon footprint`, `能耗优化`, `sustainable code`, `碳排放`, `节能`, `@ethan green-code`, `/green-code`
|
|
5318
|
+
|
|
5319
|
+
**执行步骤**:
|
|
5320
|
+
|
|
5321
|
+
#### 1. 能耗热点识别
|
|
5322
|
+
|
|
5323
|
+
扫描代码库,定位高能耗模式:
|
|
5324
|
+
|
|
5325
|
+
**六大能耗反模式**
|
|
5326
|
+
|
|
5327
|
+
| 反模式 | 表现 | 能耗级别 |
|
|
5328
|
+
|--------|------|---------|
|
|
5329
|
+
| **无效轮询** | `while(true) { check(); sleep(100) }` | 🔴 极高 |
|
|
5330
|
+
| **CPU 密集循环** | 嵌套循环处理大数据集 | 🔴 极高 |
|
|
5331
|
+
| **内存泄漏** | 对象无法被 GC 回收 | 🟠 高 |
|
|
5332
|
+
| **冗余网络请求** | 重复请求相同资源 | 🟠 高 |
|
|
5333
|
+
| **过度序列化** | 频繁 JSON.stringify 大对象 | 🟡 中 |
|
|
5334
|
+
| **阻塞 I/O** | 同步文件读写阻塞主线程 | 🟡 中 |
|
|
5335
|
+
|
|
5336
|
+
**识别工具命令**
|
|
5337
|
+
```bash
|
|
5338
|
+
# Node.js — CPU Profile
|
|
5339
|
+
node --prof app.js
|
|
5340
|
+
node --prof-process isolate-*.log > profile.txt
|
|
5341
|
+
|
|
5342
|
+
# 内存快照(Node.js)
|
|
5343
|
+
node --heap-prof app.js
|
|
5344
|
+
|
|
5345
|
+
# 前端 — Chrome DevTools
|
|
5346
|
+
# Performance 面板 → Record → 分析 Long Tasks(>50ms)
|
|
5347
|
+
|
|
5348
|
+
# Python — cProfile
|
|
5349
|
+
python -m cProfile -o output.prof app.py
|
|
5350
|
+
python -m pstats output.prof # 查看热点函数
|
|
5351
|
+
```
|
|
5352
|
+
|
|
5353
|
+
**无效轮询重构**
|
|
5354
|
+
```typescript
|
|
5355
|
+
// ❌ 能耗高:每 100ms 轮询
|
|
5356
|
+
while (true) {
|
|
5357
|
+
const status = await checkJobStatus(jobId);
|
|
5358
|
+
if (status === 'done') break;
|
|
5359
|
+
await sleep(100);
|
|
5360
|
+
}
|
|
5361
|
+
|
|
5362
|
+
// ✅ 绿色:指数退避 + 最大间隔
|
|
5363
|
+
async function pollWithBackoff(jobId: string) {
|
|
5364
|
+
let delay = 500;
|
|
5365
|
+
const MAX_DELAY = 30000;
|
|
5366
|
+
while (true) {
|
|
5367
|
+
const status = await checkJobStatus(jobId);
|
|
5368
|
+
if (status === 'done') return;
|
|
5369
|
+
await sleep(Math.min(delay, MAX_DELAY));
|
|
5370
|
+
delay *= 1.5;
|
|
5371
|
+
}
|
|
5372
|
+
}
|
|
5373
|
+
|
|
5374
|
+
// ✅ 更绿色:WebSocket / SSE(服务端推送,零轮询)
|
|
5375
|
+
const ws = new WebSocket('/api/jobs/status');
|
|
5376
|
+
ws.onmessage = (e) => handleUpdate(JSON.parse(e.data));
|
|
5377
|
+
```
|
|
5378
|
+
|
|
5379
|
+
**输出**:能耗热点清单(位置 + 类型 + 估算影响)
|
|
5380
|
+
|
|
5381
|
+
#### 2. 算法复杂度优化
|
|
5382
|
+
|
|
5383
|
+
用低复杂度算法替换高能耗实现:
|
|
5384
|
+
|
|
5385
|
+
**复杂度对碳排放的影响(1M 数据量对比)**
|
|
5386
|
+
```
|
|
5387
|
+
O(n²) → 10^12 操作 → 约 100W CPU 秒 🔴
|
|
5388
|
+
O(n log n) → 2×10^7 操作 → 约 20ms 🟢
|
|
5389
|
+
O(n) → 10^6 操作 → 约 1ms 🟢
|
|
5390
|
+
O(1) → 1 操作 → 即时 🟢
|
|
5391
|
+
```
|
|
5392
|
+
|
|
5393
|
+
**常见优化模式**
|
|
5394
|
+
|
|
5395
|
+
**① 嵌套循环 → 哈希查找**
|
|
5396
|
+
```typescript
|
|
5397
|
+
// ❌ O(n²) — 大数据量极度消耗 CPU
|
|
5398
|
+
function findMatches(users: User[], orders: Order[]) {
|
|
5399
|
+
return orders.filter(order =>
|
|
5400
|
+
users.some(user => user.id === order.userId) // O(n) per order
|
|
5401
|
+
);
|
|
5402
|
+
}
|
|
5403
|
+
|
|
5404
|
+
// ✅ O(n) — 预建索引
|
|
5405
|
+
function findMatchesOptimized(users: User[], orders: Order[]) {
|
|
5406
|
+
const userMap = new Map(users.map(u => [u.id, u])); // 一次性 O(n)
|
|
5407
|
+
return orders.filter(order => userMap.has(order.userId)); // O(1) per order
|
|
5408
|
+
}
|
|
5409
|
+
```
|
|
5410
|
+
|
|
5411
|
+
**② 重复计算 → 缓存/记忆化**
|
|
5412
|
+
```typescript
|
|
5413
|
+
// ❌ 每次调用重新计算
|
|
5414
|
+
function expensiveCalc(n: number) { /* O(2^n) */ }
|
|
5415
|
+
|
|
5416
|
+
// ✅ 记忆化缓存
|
|
5417
|
+
const memo = new Map<number, number>();
|
|
5418
|
+
function cachedCalc(n: number): number {
|
|
5419
|
+
if (memo.has(n)) return memo.get(n)!;
|
|
5420
|
+
const result = expensiveCalc(n);
|
|
5421
|
+
memo.set(n, result);
|
|
5422
|
+
return result;
|
|
5423
|
+
}
|
|
5424
|
+
```
|
|
5425
|
+
|
|
5426
|
+
**③ 大列表操作 → 流式处理**
|
|
5427
|
+
```typescript
|
|
5428
|
+
// ❌ 全量加载到内存
|
|
5429
|
+
const allRecords = await db.findAll(); // 可能 100MB
|
|
5430
|
+
processAll(allRecords);
|
|
5431
|
+
|
|
5432
|
+
// ✅ 流式分批处理
|
|
5433
|
+
const BATCH = 1000;
|
|
5434
|
+
for await (const batch of db.findInBatches(BATCH)) {
|
|
5435
|
+
await processBatch(batch);
|
|
5436
|
+
}
|
|
5437
|
+
```
|
|
5438
|
+
|
|
5439
|
+
**输出**:算法优化建议 + 重构前后复杂度对比
|
|
5440
|
+
|
|
5441
|
+
#### 3. 云资源效率优化
|
|
5442
|
+
|
|
5443
|
+
减少云资源浪费,降低运行能耗:
|
|
5444
|
+
|
|
5445
|
+
**云资源三大浪费来源**
|
|
5446
|
+
```
|
|
5447
|
+
1. Over-provisioning(过度配置):实际 CPU 利用率 < 20%,却购买了大型实例
|
|
5448
|
+
2. 僵尸资源:停止的 EC2、未挂载的 EBS 卷、空闲的 NAT 网关
|
|
5449
|
+
3. 低效架构:长时间运行的大实例 vs. Serverless 按需执行
|
|
5450
|
+
```
|
|
5451
|
+
|
|
5452
|
+
**Right-sizing 实践**
|
|
5453
|
+
```bash
|
|
5454
|
+
# AWS — 查看 CPU 利用率趋势(CloudWatch)
|
|
5455
|
+
aws cloudwatch get-metric-statistics --metric-name CPUUtilization --dimensions Name=InstanceId,Value=i-xxxx --start-time 2024-01-01T00:00:00 --end-time 2024-01-31T00:00:00 --period 86400 --statistics Average
|
|
5456
|
+
|
|
5457
|
+
# 建议:平均 CPU < 20% → 降低一档实例类型
|
|
5458
|
+
# m5.xlarge (4 vCPU) → m5.large (2 vCPU) = 节省 50% 成本和能耗
|
|
5459
|
+
```
|
|
5460
|
+
|
|
5461
|
+
**自动伸缩配置(节能关键)**
|
|
5462
|
+
```yaml
|
|
5463
|
+
# Kubernetes HPA — 按 CPU 自动伸缩
|
|
5464
|
+
apiVersion: autoscaling/v2
|
|
5465
|
+
kind: HorizontalPodAutoscaler
|
|
5466
|
+
metadata:
|
|
5467
|
+
name: api-server
|
|
5468
|
+
spec:
|
|
5469
|
+
minReplicas: 1 # 低峰期缩减到 1 个副本
|
|
5470
|
+
maxReplicas: 10
|
|
5471
|
+
metrics:
|
|
5472
|
+
- type: Resource
|
|
5473
|
+
resource:
|
|
5474
|
+
name: cpu
|
|
5475
|
+
target:
|
|
5476
|
+
type: Utilization
|
|
5477
|
+
averageUtilization: 60
|
|
5478
|
+
```
|
|
5479
|
+
|
|
5480
|
+
**Serverless 适用场景**
|
|
5481
|
+
```
|
|
5482
|
+
适合 Serverless(事件驱动,闲时零消耗):
|
|
5483
|
+
✅ 图片处理、文件转换
|
|
5484
|
+
✅ 定时任务、数据同步
|
|
5485
|
+
✅ Webhook 处理器
|
|
5486
|
+
|
|
5487
|
+
不适合 Serverless(冷启动延迟不可接受):
|
|
5488
|
+
❌ 实时游戏服务器
|
|
5489
|
+
❌ 高频低延迟 API(< 100ms SLA)
|
|
5490
|
+
```
|
|
5491
|
+
|
|
5492
|
+
**输出**:云资源优化建议(Right-sizing + 自动伸缩 + 架构选型)
|
|
5493
|
+
|
|
5494
|
+
#### 4. 前端绿色实践
|
|
5495
|
+
|
|
5496
|
+
减少前端资源消耗,降低设备端能耗:
|
|
5497
|
+
|
|
5498
|
+
**前端能耗五大优化**
|
|
5499
|
+
|
|
5500
|
+
**① JavaScript Bundle 瘦身**
|
|
5501
|
+
```bash
|
|
5502
|
+
# 分析 Bundle 大小
|
|
5503
|
+
npx webpack-bundle-analyzer stats.json
|
|
5504
|
+
npx vite-bundle-visualizer
|
|
5505
|
+
|
|
5506
|
+
# 目标:首屏 JS < 150KB (gzipped)
|
|
5507
|
+
```
|
|
5508
|
+
|
|
5509
|
+
```typescript
|
|
5510
|
+
// ✅ Tree-shaking(只导入用到的函数)
|
|
5511
|
+
import { debounce } from 'lodash-es'; // 不要 import _ from 'lodash'
|
|
5512
|
+
|
|
5513
|
+
// ✅ 动态导入(按需加载非关键模块)
|
|
5514
|
+
const HeavyChart = lazy(() => import('./HeavyChart'));
|
|
5515
|
+
|
|
5516
|
+
// ✅ 外部化大型库(CDN 缓存复用)
|
|
5517
|
+
// vite.config.ts
|
|
5518
|
+
build: { rollupOptions: { external: ['react', 'react-dom'] } }
|
|
5519
|
+
```
|
|
5520
|
+
|
|
5521
|
+
**② 图片优化**
|
|
5522
|
+
```html
|
|
5523
|
+
<!-- ✅ 现代格式 + 响应式 -->
|
|
5524
|
+
<picture>
|
|
5525
|
+
<source srcset="hero.avif" type="image/avif">
|
|
5526
|
+
<source srcset="hero.webp" type="image/webp">
|
|
5527
|
+
<img src="hero.jpg" width="800" height="600"
|
|
5528
|
+
loading="lazy" decoding="async" alt="...">
|
|
5529
|
+
</picture>
|
|
5530
|
+
<!-- WebP 比 JPEG 小 30%,AVIF 比 JPEG 小 50% -->
|
|
5531
|
+
```
|
|
5532
|
+
|
|
5533
|
+
**③ CSS 精简**
|
|
5534
|
+
```bash
|
|
5535
|
+
# PurgeCSS — 移除未使用的 CSS
|
|
5536
|
+
npx purgecss --css dist/*.css --content dist/*.html dist/*.js --output dist/
|
|
5537
|
+
```
|
|
5538
|
+
|
|
5539
|
+
**④ 减少不必要的动画**
|
|
5540
|
+
```css
|
|
5541
|
+
/* 尊重用户的省电模式偏好 */
|
|
5542
|
+
@media (prefers-reduced-motion: reduce) {
|
|
5543
|
+
*, *::before, *::after {
|
|
5544
|
+
animation-duration: 0.01ms !important;
|
|
5545
|
+
transition-duration: 0.01ms !important;
|
|
5546
|
+
}
|
|
5547
|
+
}
|
|
5548
|
+
```
|
|
5549
|
+
|
|
5550
|
+
**⑤ 高效 DOM 操作**
|
|
5551
|
+
```typescript
|
|
5552
|
+
// ❌ 频繁触发 Reflow
|
|
5553
|
+
items.forEach(item => {
|
|
5554
|
+
item.style.width = container.offsetWidth + 'px'; // 每次读写触发 Reflow
|
|
5555
|
+
});
|
|
5556
|
+
|
|
5557
|
+
// ✅ 批量读后批量写
|
|
5558
|
+
const width = container.offsetWidth; // 一次读取
|
|
5559
|
+
items.forEach(item => { item.style.width = width + 'px'; }); // 批量写
|
|
5560
|
+
```
|
|
5561
|
+
|
|
5562
|
+
**输出**:前端绿色实践清单 + Bundle 分析建议
|
|
5563
|
+
|
|
5564
|
+
#### 5. 碳排放估算
|
|
5565
|
+
|
|
5566
|
+
用 SCI(Software Carbon Intensity)公式量化软件碳排放:
|
|
5567
|
+
|
|
5568
|
+
**SCI 公式**
|
|
5569
|
+
```
|
|
5570
|
+
SCI = (E × I) + M
|
|
5571
|
+
|
|
5572
|
+
E = 软件消耗的电能(kWh)
|
|
5573
|
+
I = 电网碳强度(gCO₂eq/kWh)— 取决于服务器所在地区
|
|
5574
|
+
M = 硬件制造碳排放(均摊到使用寿命)
|
|
5575
|
+
```
|
|
5576
|
+
|
|
5577
|
+
**各地区电网碳强度参考**
|
|
5578
|
+
```
|
|
5579
|
+
地区 碳强度 (gCO₂eq/kWh)
|
|
5580
|
+
─────────────────────────────────
|
|
5581
|
+
西欧 / Nordic ~150-300 (可再生能源占比高)
|
|
5582
|
+
us-east-1 ~350-400
|
|
5583
|
+
ap-northeast-1 ~450-500 (日本,主要火电)
|
|
5584
|
+
cn-north-1 ~550-600 (中国,煤电为主)
|
|
5585
|
+
AWS GovCloud ~200 (承诺使用清洁能源)
|
|
5586
|
+
```
|
|
5587
|
+
|
|
5588
|
+
**API 服务碳排放估算示例**
|
|
5589
|
+
```
|
|
5590
|
+
假设:
|
|
5591
|
+
- API 服务器:4 vCPU,平均 40% 利用率
|
|
5592
|
+
- TDP(热设计功耗):100W(4 vCPU 服务器)
|
|
5593
|
+
- 实际功耗:100W × 40% × PUE(1.2) = 48W
|
|
5594
|
+
- 每月运行:48W × 730h = 35 kWh
|
|
5595
|
+
- 碳排放(us-east-1,380 gCO₂/kWh):
|
|
5596
|
+
35 × 380 = 13,300 gCO₂ = 13.3 kg CO₂/月
|
|
5597
|
+
|
|
5598
|
+
优化后(CPU 利用率从 40% 优化到 20% 后缩容):
|
|
5599
|
+
12W × 730h × 380 / 1000 = 3.3 kg CO₂/月
|
|
5600
|
+
节省:10 kg CO₂/月(减少 75%)
|
|
5601
|
+
```
|
|
5602
|
+
|
|
5603
|
+
**绿色目标设定**
|
|
5604
|
+
```
|
|
5605
|
+
当前基准: ___ gCO₂/1000 API 请求
|
|
5606
|
+
目标(3个月): 降低 30%
|
|
5607
|
+
目标(1年): 迁移到低碳区域(or 可再生能源数据中心)
|
|
5608
|
+
```
|
|
5609
|
+
|
|
5610
|
+
**Carbon.txt 声明**(向用户公示绿色承诺)
|
|
5611
|
+
```
|
|
5612
|
+
# /carbon.txt
|
|
5613
|
+
We are committed to reducing our software's carbon footprint.
|
|
5614
|
+
Hosting: AWS us-west-2 (powered by renewable energy)
|
|
5615
|
+
Current SCI: 15g CO₂eq per 1000 requests
|
|
5616
|
+
Target: < 10g CO₂eq per 1000 requests by 2025
|
|
5617
|
+
```
|
|
5618
|
+
|
|
5619
|
+
**输出**:碳排放基准报告 + SCI 计算表 + 减碳路线图
|
|
5620
|
+
|
|
5621
|
+
**输出格式**: 能耗热点报告 + 算法优化建议 + 云资源 Right-sizing 方案 + 前端绿色实践 Checklist + 碳排放估算(SCI)
|
|
5622
|
+
|
|
5623
|
+
**注意事项**:
|
|
5624
|
+
- 绿色编码和性能优化高度重合——节能的代码往往也是更快的代码,两者并不冲突
|
|
5625
|
+
- 将服务器迁移到使用可再生能源的区域(如 AWS us-west-2 Oregon)是降低碳排放最立竿见影的方式
|
|
5626
|
+
- 前端优化对碳排放影响巨大:每减少 1KB JS,每百万用户每年可节省约 0.5 kg CO₂
|
|
5627
|
+
- SCI 指标建议纳入团队 KPI,每季度度量,与其他工程指标(性能/可用性)并列追踪
|
|
5628
|
+
- 使用 AWS Customer Carbon Footprint Tool 或 Google Cloud Carbon Footprint 获取真实数据
|
|
5629
|
+
|
|
5630
|
+
---
|
|
5631
|
+
|
|
5632
|
+
### 33. 服务目录管理 (`service-catalog`)
|
|
5633
|
+
|
|
5634
|
+
**描述**: 建立标准化服务目录,生成 Backstage 兼容的 catalog-info.yaml,可视化服务依赖与健康评分
|
|
5635
|
+
|
|
5636
|
+
**触发词**: `服务目录`, `service catalog`, `service registry`, `backstage`, `内部开发者平台`, `idp`, `服务注册`, `@ethan service-catalog`, `/service-catalog`
|
|
5637
|
+
|
|
5638
|
+
**执行步骤**:
|
|
5639
|
+
|
|
5640
|
+
#### 1. 服务元数据采集
|
|
5641
|
+
|
|
5642
|
+
系统化收集每个服务的关键元数据:
|
|
5643
|
+
|
|
5644
|
+
**服务元数据清单**
|
|
5645
|
+
|
|
5646
|
+
```yaml
|
|
5647
|
+
# 必填字段
|
|
5648
|
+
name: 服务唯一标识(小写 + 连字符)
|
|
5649
|
+
display_name: 人类可读名称
|
|
5650
|
+
owner: 团队/个人(GitHub Team 或 email)
|
|
5651
|
+
tech_stack: 主要技术栈(Node.js/Go/Java 等)
|
|
5652
|
+
type: 服务类型(service/library/website/pipeline)
|
|
5653
|
+
|
|
5654
|
+
# 运维信息
|
|
5655
|
+
tier: 重要性等级(tier-1/tier-2/tier-3)
|
|
5656
|
+
sla: 可用性目标(99.9%/99.95%/99.99%)
|
|
5657
|
+
on_call: 值班联系方式(PagerDuty/Slack channel)
|
|
5658
|
+
runbook: 运维手册 URL
|
|
5659
|
+
|
|
5660
|
+
# 依赖关系
|
|
5661
|
+
dependencies: 依赖的其他服务列表
|
|
5662
|
+
consumers: 消费本服务的上游列表
|
|
5663
|
+
databases: 使用的数据库(类型 + 库名)
|
|
5664
|
+
external_apis: 调用的外部 API(如 Stripe/SendGrid)
|
|
5665
|
+
|
|
5666
|
+
# 文档与质量
|
|
5667
|
+
docs: 文档链接(Confluence/Notion)
|
|
5668
|
+
api_spec: OpenAPI/AsyncAPI 规范文件路径
|
|
5669
|
+
test_coverage: 当前测试覆盖率
|
|
5670
|
+
deployment_frequency: 发布频率(daily/weekly/monthly)
|
|
5671
|
+
```
|
|
5672
|
+
|
|
5673
|
+
**批量采集脚本**
|
|
5674
|
+
```bash
|
|
5675
|
+
#!/bin/bash
|
|
5676
|
+
# 扫描所有服务目录,检查是否有 catalog-info.yaml
|
|
5677
|
+
for dir in services/*/; do
|
|
5678
|
+
if [ ! -f "$dir/catalog-info.yaml" ]; then
|
|
5679
|
+
echo "❌ Missing: $dir/catalog-info.yaml"
|
|
5680
|
+
else
|
|
5681
|
+
echo "✅ Found: $dir/catalog-info.yaml"
|
|
5682
|
+
fi
|
|
5683
|
+
done
|
|
5684
|
+
```
|
|
5685
|
+
|
|
5686
|
+
**输出**:服务元数据采集表(所有服务 + 完整度评分)
|
|
5687
|
+
|
|
5688
|
+
#### 2. 生成 catalog-info.yaml
|
|
5689
|
+
|
|
5690
|
+
生成 Backstage/内部开发者平台兼容的服务定义文件:
|
|
5691
|
+
|
|
5692
|
+
**完整 catalog-info.yaml 模板**
|
|
5693
|
+
```yaml
|
|
5694
|
+
# services/order-service/catalog-info.yaml
|
|
5695
|
+
apiVersion: backstage.io/v1alpha1
|
|
5696
|
+
kind: Component
|
|
5697
|
+
metadata:
|
|
5698
|
+
name: order-service
|
|
5699
|
+
title: 订单服务
|
|
5700
|
+
description: 处理订单创建、支付和履约的核心服务
|
|
5701
|
+
tags:
|
|
5702
|
+
- nodejs
|
|
5703
|
+
- postgresql
|
|
5704
|
+
- kafka
|
|
5705
|
+
- tier-1
|
|
5706
|
+
annotations:
|
|
5707
|
+
# GitHub 集成
|
|
5708
|
+
github.com/project-slug: myorg/order-service
|
|
5709
|
+
# 监控集成
|
|
5710
|
+
prometheus.io/alert: 'order-service-alerts'
|
|
5711
|
+
grafana.com/dashboard: 'https://grafana.internal/d/orders'
|
|
5712
|
+
# 文档
|
|
5713
|
+
backstage.io/techdocs-ref: dir:.
|
|
5714
|
+
# PagerDuty
|
|
5715
|
+
pagerduty.com/service-id: P123456
|
|
5716
|
+
links:
|
|
5717
|
+
- url: https://runbook.internal/order-service
|
|
5718
|
+
title: Runbook
|
|
5719
|
+
icon: book
|
|
5720
|
+
- url: https://grafana.internal/d/orders
|
|
5721
|
+
title: Dashboard
|
|
5722
|
+
icon: dashboard
|
|
5723
|
+
|
|
5724
|
+
spec:
|
|
5725
|
+
type: service
|
|
5726
|
+
lifecycle: production # experimental | production | deprecated
|
|
5727
|
+
owner: group:backend-team
|
|
5728
|
+
system: ecommerce-platform
|
|
5729
|
+
|
|
5730
|
+
# 依赖声明
|
|
5731
|
+
dependsOn:
|
|
5732
|
+
- component:user-service
|
|
5733
|
+
- component:inventory-service
|
|
5734
|
+
- resource:orders-postgres
|
|
5735
|
+
- resource:payment-kafka-topic
|
|
5736
|
+
|
|
5737
|
+
# 对外提供的 API
|
|
5738
|
+
providesApis:
|
|
5739
|
+
- order-api-v2
|
|
5740
|
+
```
|
|
5741
|
+
|
|
5742
|
+
**API 定义(关联)**
|
|
5743
|
+
```yaml
|
|
5744
|
+
# order-api.yaml
|
|
5745
|
+
apiVersion: backstage.io/v1alpha1
|
|
5746
|
+
kind: API
|
|
5747
|
+
metadata:
|
|
5748
|
+
name: order-api-v2
|
|
5749
|
+
description: 订单服务 REST API v2
|
|
5750
|
+
spec:
|
|
5751
|
+
type: openapi
|
|
5752
|
+
lifecycle: production
|
|
5753
|
+
owner: group:backend-team
|
|
5754
|
+
definition:
|
|
5755
|
+
$text: ./openapi/order-api-v2.yaml
|
|
5756
|
+
```
|
|
5757
|
+
|
|
5758
|
+
**Resource 定义(数据库/队列)**
|
|
5759
|
+
```yaml
|
|
5760
|
+
apiVersion: backstage.io/v1alpha1
|
|
5761
|
+
kind: Resource
|
|
5762
|
+
metadata:
|
|
5763
|
+
name: orders-postgres
|
|
5764
|
+
description: 订单主数据库(PostgreSQL 14)
|
|
5765
|
+
spec:
|
|
5766
|
+
type: database
|
|
5767
|
+
owner: group:dba-team
|
|
5768
|
+
system: ecommerce-platform
|
|
5769
|
+
```
|
|
5770
|
+
|
|
5771
|
+
**输出**:catalog-info.yaml + API 定义文件 + Resource 定义文件
|
|
5772
|
+
|
|
5773
|
+
#### 3. 服务依赖关系图
|
|
5774
|
+
|
|
5775
|
+
可视化服务间的上下游依赖关系:
|
|
5776
|
+
|
|
5777
|
+
**Mermaid 依赖图生成**
|
|
5778
|
+
```typescript
|
|
5779
|
+
// scripts/gen-dependency-graph.ts
|
|
5780
|
+
function generateMermaid(services: Service[]): string {
|
|
5781
|
+
const lines = ['graph LR'];
|
|
5782
|
+
|
|
5783
|
+
for (const service of services) {
|
|
5784
|
+
for (const dep of service.dependencies) {
|
|
5785
|
+
lines.push(` ${service.name} --> ${dep}`);
|
|
5786
|
+
}
|
|
5787
|
+
}
|
|
5788
|
+
|
|
5789
|
+
return lines.join('\n');
|
|
5790
|
+
}
|
|
5791
|
+
|
|
5792
|
+
// 输出示例:
|
|
5793
|
+
// graph LR
|
|
5794
|
+
// order-service --> user-service
|
|
5795
|
+
// order-service --> inventory-service
|
|
5796
|
+
// order-service --> payment-service
|
|
5797
|
+
// api-gateway --> order-service
|
|
5798
|
+
// api-gateway --> user-service
|
|
5799
|
+
```
|
|
5800
|
+
|
|
5801
|
+
**关键依赖分析**
|
|
5802
|
+
```
|
|
5803
|
+
依赖分析报告
|
|
5804
|
+
════════════
|
|
5805
|
+
|
|
5806
|
+
高扇入服务(被依赖最多,故障影响最广):
|
|
5807
|
+
user-service 被 8 个服务依赖 ⚠️ 单点风险
|
|
5808
|
+
auth-service 被 12 个服务依赖 🔴 关键路径
|
|
5809
|
+
|
|
5810
|
+
高扇出服务(依赖最多,级联失败风险):
|
|
5811
|
+
order-service 依赖 6 个服务 ⚠️ 需熔断保护
|
|
5812
|
+
report-service 依赖 9 个服务 🔴 脆弱性高
|
|
5813
|
+
|
|
5814
|
+
循环依赖检测:
|
|
5815
|
+
✅ 未发现循环依赖
|
|
5816
|
+
|
|
5817
|
+
孤立服务(无依赖/无被依赖):
|
|
5818
|
+
legacy-batch-job ⚠️ 是否可下线?
|
|
5819
|
+
```
|
|
5820
|
+
|
|
5821
|
+
**依赖健康度 Checklist**
|
|
5822
|
+
- [ ] 高扇入服务(>5个依赖)配备了熔断器(Circuit Breaker)
|
|
5823
|
+
- [ ] 高扇出服务对所有依赖设置了超时(Timeout)
|
|
5824
|
+
- [ ] 关键路径服务有冗余/多活部署
|
|
5825
|
+
- [ ] 无循环依赖
|
|
5826
|
+
|
|
5827
|
+
**输出**:服务依赖关系图(Mermaid/DOT)+ 风险分析报告
|
|
5828
|
+
|
|
5829
|
+
#### 4. 服务健康评分
|
|
5830
|
+
|
|
5831
|
+
建立多维度服务健康评分体系,量化技术质量:
|
|
5832
|
+
|
|
5833
|
+
**服务健康评分卡(满分 100)**
|
|
5834
|
+
|
|
5835
|
+
```
|
|
5836
|
+
维度 权重 评分标准
|
|
5837
|
+
─────────────────────────────────────────
|
|
5838
|
+
📄 文档完整性 20分
|
|
5839
|
+
catalog-info.yaml 5分 (存在且完整)
|
|
5840
|
+
README.md 5分 (含部署、接口说明)
|
|
5841
|
+
API 规范 5分 (OpenAPI/AsyncAPI)
|
|
5842
|
+
Runbook 5分 (含常见故障处理)
|
|
5843
|
+
|
|
5844
|
+
🧪 测试质量 25分
|
|
5845
|
+
单元测试覆盖率 >80% 15分
|
|
5846
|
+
集成测试存在 10分
|
|
5847
|
+
|
|
5848
|
+
🚀 部署规范 25分
|
|
5849
|
+
CI/CD 流水线完整 10分
|
|
5850
|
+
容器化(Dockerfile) 5分
|
|
5851
|
+
健康检查接口 5分
|
|
5852
|
+
优雅关闭实现 5分
|
|
5853
|
+
|
|
5854
|
+
📊 可观测性 20分
|
|
5855
|
+
Metrics 暴露 8分 (/metrics endpoint)
|
|
5856
|
+
结构化日志 7分 (JSON 格式 + trace ID)
|
|
5857
|
+
告警规则配置 5分
|
|
5858
|
+
|
|
5859
|
+
🔒 安全合规 10分
|
|
5860
|
+
依赖漏洞扫描通过 5分 (npm audit/Snyk 0 High)
|
|
5861
|
+
密钥未硬编码 5分
|
|
5862
|
+
```
|
|
5863
|
+
|
|
5864
|
+
**评分等级**
|
|
5865
|
+
- 🟢 **优秀(90-100)**:可作为黄金路径参考
|
|
5866
|
+
- 🟡 **良好(70-89)**:有改进空间,下季度计划
|
|
5867
|
+
- 🟠 **待改进(50-69)**:需要专项提升
|
|
5868
|
+
- 🔴 **高风险(< 50)**:必须立即改进
|
|
5869
|
+
|
|
5870
|
+
**批量评分脚本示例**
|
|
5871
|
+
```bash
|
|
5872
|
+
#!/bin/bash
|
|
5873
|
+
# 检查服务健康评分关键项
|
|
5874
|
+
check_service() {
|
|
5875
|
+
local dir=$1
|
|
5876
|
+
local score=0
|
|
5877
|
+
|
|
5878
|
+
[ -f "$dir/catalog-info.yaml" ] && score=$((score+5))
|
|
5879
|
+
[ -f "$dir/README.md" ] && score=$((score+5))
|
|
5880
|
+
[ -f "$dir/Dockerfile" ] && score=$((score+5))
|
|
5881
|
+
[ -f "$dir/.github/workflows/ci.yml" ] && score=$((score+10))
|
|
5882
|
+
|
|
5883
|
+
echo "$dir: $score / 25 (快速检查)"
|
|
5884
|
+
}
|
|
5885
|
+
|
|
5886
|
+
for dir in services/*/; do check_service "$dir"; done
|
|
5887
|
+
```
|
|
5888
|
+
|
|
5889
|
+
**输出**:服务健康评分卡 + 改进优先级清单
|
|
5890
|
+
|
|
5891
|
+
#### 5. 服务生命周期管理
|
|
5892
|
+
|
|
5893
|
+
建立服务从孵化到下线的标准生命周期流程:
|
|
5894
|
+
|
|
5895
|
+
**四阶段生命周期**
|
|
5896
|
+
```
|
|
5897
|
+
孵化(Experimental)→ 成熟(Production)→ 维护(Maintenance)→ 下线(Deprecated)
|
|
5898
|
+
```
|
|
5899
|
+
|
|
5900
|
+
**各阶段定义与要求**
|
|
5901
|
+
|
|
5902
|
+
| 阶段 | lifecycle 值 | 要求 | 颜色标记 |
|
|
5903
|
+
|------|-------------|------|---------|
|
|
5904
|
+
| 孵化 | experimental | 开发中,不可用于生产 | 🟡 |
|
|
5905
|
+
| 成熟 | production | 满足健康评分 ≥ 80 | 🟢 |
|
|
5906
|
+
| 维护 | maintenance | 仅修复 Bug,不加新功能 | 🟠 |
|
|
5907
|
+
| 下线 | deprecated | 已有替代服务,设定截止日 | 🔴 |
|
|
5908
|
+
|
|
5909
|
+
**服务下线流程**
|
|
5910
|
+
```
|
|
5911
|
+
Day 0: 更新 lifecycle: deprecated,添加 deprecation notice
|
|
5912
|
+
通知所有已知消费者(来自 catalog 的 consumers 列表)
|
|
5913
|
+
|
|
5914
|
+
Day 30: 确认所有消费者已迁移
|
|
5915
|
+
关闭新注册/新依赖
|
|
5916
|
+
|
|
5917
|
+
Day 60: 停止接受新请求(返回 410 Gone)
|
|
5918
|
+
保留 Read-only 访问用于历史数据查询
|
|
5919
|
+
|
|
5920
|
+
Day 90: 完全下线,数据归档
|
|
5921
|
+
从 catalog 中标记 archived: true
|
|
5922
|
+
```
|
|
5923
|
+
|
|
5924
|
+
**服务晋升 Checklist(Experimental → Production)**
|
|
5925
|
+
- [ ] 服务健康评分 ≥ 80
|
|
5926
|
+
- [ ] 通过 Load Test(目标 TPS 的 150% 压力)
|
|
5927
|
+
- [ ] 完成安全扫描(0 个 High/Critical 漏洞)
|
|
5928
|
+
- [ ] Runbook 已编写并通过演练
|
|
5929
|
+
- [ ] 监控告警已配置并验证(OnCall 团队确认)
|
|
5930
|
+
- [ ] 至少经过 2 周 Canary 发布观察
|
|
5931
|
+
|
|
5932
|
+
**输出**:服务生命周期文档 + 下线流程 + 晋升 Checklist
|
|
5933
|
+
|
|
5934
|
+
**输出格式**: catalog-info.yaml 文件 + 服务依赖关系图(Mermaid)+ 服务健康评分卡 + 生命周期管理规范
|
|
5935
|
+
|
|
5936
|
+
**注意事项**:
|
|
5937
|
+
- 服务目录的价值在于"被发现"——确保 catalog-info.yaml 提交到代码库并与 Backstage/IDP 自动同步
|
|
5938
|
+
- 从高 Tier(Tier-1 核心服务)开始建立目录,而非试图一次性覆盖所有服务
|
|
5939
|
+
- 服务健康评分应作为季度技术评审的固定议题,驱动持续改进
|
|
5940
|
+
- 依赖关系图定期更新至关重要——过时的依赖图比没有图更危险
|
|
5941
|
+
- Backstage 是目前最成熟的开源 IDP,推荐中大型团队直接采用;小团队可用 catalog-info.yaml 文件 + 简单脚本代替
|
|
5942
|
+
|
|
5943
|
+
---
|
|
5944
|
+
|
|
5945
|
+
### 34. 移动端专项审查 (`mobile-review`)
|
|
5946
|
+
|
|
5947
|
+
**描述**: 审查移动端应用的平台合规、性能基准、离线同步、无障碍与崩溃防护
|
|
5948
|
+
|
|
5949
|
+
**触发词**: `移动端审查`, `mobile review`, `ios review`, `android review`, `flutter review`, `react native review`, `移动端性能`, `app review`, `@ethan mobile-review`, `/mobile-review`
|
|
5950
|
+
|
|
5951
|
+
**执行步骤**:
|
|
5952
|
+
|
|
5953
|
+
#### 1. 平台合规审查
|
|
5954
|
+
|
|
5955
|
+
检查 iOS 和 Android 的合规要求,确保顺利通过审核:
|
|
5956
|
+
|
|
5957
|
+
**iOS App Store 合规 Checklist**
|
|
5958
|
+
```
|
|
5959
|
+
□ 隐私权限声明
|
|
5960
|
+
- Info.plist 中每个权限 Key 必须有 Usage Description
|
|
5961
|
+
- NSCameraUsageDescription: "用于拍摄商品照片"
|
|
5962
|
+
- NSLocationWhenInUseUsageDescription: "用于查找附近服务"
|
|
5963
|
+
- NSPhotoLibraryUsageDescription: "用于选择头像图片"
|
|
5964
|
+
|
|
5965
|
+
□ App Tracking Transparency (ATT)
|
|
5966
|
+
- iOS 14+ 追踪用户需要弹窗授权
|
|
5967
|
+
- NSUserTrackingUsageDescription 必须填写
|
|
5968
|
+
|
|
5969
|
+
□ 隐私清单 (Privacy Manifest, iOS 17+)
|
|
5970
|
+
- PrivacyInfo.xcprivacy 文件
|
|
5971
|
+
- 声明使用的 Required Reason API(UserDefaults/File timestamp 等)
|
|
5972
|
+
|
|
5973
|
+
□ 网络安全配置
|
|
5974
|
+
- 所有网络请求使用 HTTPS(ATS 默认开启)
|
|
5975
|
+
- 自定义 Exception 需要合理理由
|
|
5976
|
+
|
|
5977
|
+
□ 支付合规
|
|
5978
|
+
- 数字商品/虚拟货币必须使用 In-App Purchase
|
|
5979
|
+
- 不得引导用户到外部网站购买
|
|
5980
|
+
```
|
|
5981
|
+
|
|
5982
|
+
**Android Google Play 合规 Checklist**
|
|
5983
|
+
```
|
|
5984
|
+
□ 权限最小化
|
|
5985
|
+
- 仅申请功能必需的权限
|
|
5986
|
+
- targetSdkVersion ≥ 33(Play Store 要求)
|
|
5987
|
+
- 危险权限在使用前实时申请(非安装时)
|
|
5988
|
+
|
|
5989
|
+
□ 数据安全表(Data Safety Section)
|
|
5990
|
+
- 明确声明收集的数据类型
|
|
5991
|
+
- 说明是否与第三方共享
|
|
5992
|
+
|
|
5993
|
+
□ 广告 ID(GAID)
|
|
5994
|
+
- Android 12+ 需要 AD_ID 权限
|
|
5995
|
+
- 儿童类应用禁止使用广告 ID
|
|
5996
|
+
|
|
5997
|
+
□ 64位支持
|
|
5998
|
+
- 所有 native library 提供 arm64-v8a 版本
|
|
5999
|
+
```
|
|
6000
|
+
|
|
6001
|
+
**输出**:平台合规检查报告(iOS + Android,通过/未通过标注)
|
|
6002
|
+
|
|
6003
|
+
#### 2. 性能专项审查
|
|
6004
|
+
|
|
6005
|
+
评估移动端应用的关键性能指标:
|
|
6006
|
+
|
|
6007
|
+
**性能基准标准**
|
|
6008
|
+
|
|
6009
|
+
| 指标 | 优秀 | 可接受 | 需优化 |
|
|
6010
|
+
|------|------|--------|--------|
|
|
6011
|
+
| **冷启动时间** | < 1.5s | < 2.5s | > 2.5s |
|
|
6012
|
+
| **热启动时间** | < 0.5s | < 1.0s | > 1.0s |
|
|
6013
|
+
| **帧率(FPS)** | 60fps | 55fps | < 50fps |
|
|
6014
|
+
| **首屏渲染** | < 1.0s | < 2.0s | > 2.0s |
|
|
6015
|
+
| **内存占用(前台)** | < 150MB | < 250MB | > 250MB |
|
|
6016
|
+
| **安装包大小** | < 30MB | < 60MB | > 60MB |
|
|
6017
|
+
| **电池消耗** | < 5%/h | < 10%/h | > 10%/h |
|
|
6018
|
+
|
|
6019
|
+
**启动性能优化**
|
|
6020
|
+
```swift
|
|
6021
|
+
// iOS — 减少 AppDelegate 启动时工作
|
|
6022
|
+
@main class AppDelegate: UIResponder, UIApplicationDelegate {
|
|
6023
|
+
func application(_ application: UIApplication,
|
|
6024
|
+
didFinishLaunchingWithOptions options: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
|
6025
|
+
|
|
6026
|
+
// ❌ 避免在启动时进行:
|
|
6027
|
+
// - 同步网络请求
|
|
6028
|
+
// - 大量数据库初始化
|
|
6029
|
+
// - 复杂计算
|
|
6030
|
+
|
|
6031
|
+
// ✅ 启动时只做:
|
|
6032
|
+
setupCrashReporting() // 轻量
|
|
6033
|
+
configureDependencyInjection() // 必须
|
|
6034
|
+
|
|
6035
|
+
// ✅ 延迟到首屏渲染后
|
|
6036
|
+
DispatchQueue.main.async {
|
|
6037
|
+
self.setupAnalytics()
|
|
6038
|
+
self.prefetchData()
|
|
6039
|
+
}
|
|
6040
|
+
return true
|
|
6041
|
+
}
|
|
6042
|
+
}
|
|
6043
|
+
```
|
|
6044
|
+
|
|
6045
|
+
```kotlin
|
|
6046
|
+
// Android — 使用 App Startup 库延迟初始化
|
|
6047
|
+
class MyInitializer : Initializer<Unit> {
|
|
6048
|
+
override fun create(context: Context) {
|
|
6049
|
+
// 只初始化核心功能
|
|
6050
|
+
}
|
|
6051
|
+
override fun dependencies() = emptyList<Class<out Initializer<*>>>()
|
|
6052
|
+
}
|
|
6053
|
+
```
|
|
6054
|
+
|
|
6055
|
+
**包大小优化**
|
|
6056
|
+
```
|
|
6057
|
+
iOS:
|
|
6058
|
+
- 使用 Asset Catalog 而非散落图片
|
|
6059
|
+
- On-Demand Resources 下载非核心资源
|
|
6060
|
+
- App Thinning(Bitcode + Slicing)
|
|
6061
|
+
|
|
6062
|
+
Android:
|
|
6063
|
+
- App Bundle (.aab) 替代 APK(减少 20-40%)
|
|
6064
|
+
- R8/ProGuard 代码压缩
|
|
6065
|
+
- WebP 替换 PNG/JPEG
|
|
6066
|
+
- 动态功能模块(Dynamic Feature Modules)
|
|
6067
|
+
```
|
|
6068
|
+
|
|
6069
|
+
**输出**:性能基准报告 + 优化建议清单
|
|
6070
|
+
|
|
6071
|
+
#### 3. 离线与数据同步
|
|
6072
|
+
|
|
6073
|
+
设计可靠的离线能力和数据同步策略:
|
|
6074
|
+
|
|
6075
|
+
**离线架构模式**
|
|
6076
|
+
|
|
6077
|
+
**① Cache-First(适合内容类 App)**
|
|
6078
|
+
```typescript
|
|
6079
|
+
// React Native — 先读本地缓存,后台刷新
|
|
6080
|
+
async function getArticles(): Promise<Article[]> {
|
|
6081
|
+
const cached = await AsyncStorage.getItem('articles');
|
|
6082
|
+
|
|
6083
|
+
// 立即返回缓存(用户不等待)
|
|
6084
|
+
if (cached) {
|
|
6085
|
+
const data = JSON.parse(cached);
|
|
6086
|
+
// 后台静默刷新
|
|
6087
|
+
fetchAndCache().catch(console.error);
|
|
6088
|
+
return data;
|
|
6089
|
+
}
|
|
6090
|
+
|
|
6091
|
+
// 无缓存时等待网络
|
|
6092
|
+
return fetchAndCache();
|
|
6093
|
+
}
|
|
6094
|
+
```
|
|
6095
|
+
|
|
6096
|
+
**② Optimistic Update(适合操作类 App)**
|
|
6097
|
+
```typescript
|
|
6098
|
+
// 先更新本地,后台同步到服务器
|
|
6099
|
+
async function toggleLike(postId: string) {
|
|
6100
|
+
// 立即更新 UI(乐观更新)
|
|
6101
|
+
dispatch({ type: 'TOGGLE_LIKE', postId });
|
|
6102
|
+
|
|
6103
|
+
try {
|
|
6104
|
+
await api.post(`/posts/${postId}/like`);
|
|
6105
|
+
} catch (error) {
|
|
6106
|
+
// 失败时回滚
|
|
6107
|
+
dispatch({ type: 'REVERT_LIKE', postId });
|
|
6108
|
+
showToast('操作失败,已恢复');
|
|
6109
|
+
}
|
|
6110
|
+
}
|
|
6111
|
+
```
|
|
6112
|
+
|
|
6113
|
+
**冲突解决策略**
|
|
6114
|
+
```
|
|
6115
|
+
策略 适用场景
|
|
6116
|
+
─────────────────────────────────
|
|
6117
|
+
Last-Write-Wins 一般设置、非关键数据
|
|
6118
|
+
Server-Wins 金融数据、库存数量
|
|
6119
|
+
Client-Wins 用户个人偏好
|
|
6120
|
+
Merge(合并) 文档协作(CRDT)
|
|
6121
|
+
Ask User(提示用户) 重要数据冲突
|
|
6122
|
+
```
|
|
6123
|
+
|
|
6124
|
+
**网络状态监听**
|
|
6125
|
+
```typescript
|
|
6126
|
+
import NetInfo from '@react-native-community/netinfo';
|
|
6127
|
+
|
|
6128
|
+
NetInfo.addEventListener(state => {
|
|
6129
|
+
if (state.isConnected && hasPendingSync()) {
|
|
6130
|
+
syncPendingOperations(); // 联网后自动同步
|
|
6131
|
+
}
|
|
6132
|
+
});
|
|
6133
|
+
|
|
6134
|
+
// 队列离线操作
|
|
6135
|
+
async function queueOperation(op: Operation) {
|
|
6136
|
+
await OfflineQueue.push(op);
|
|
6137
|
+
if (await NetInfo.fetch().then(s => s.isConnected)) {
|
|
6138
|
+
await flushQueue();
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
```
|
|
6142
|
+
|
|
6143
|
+
**输出**:离线架构方案 + 冲突解决策略 + 代码模板
|
|
6144
|
+
|
|
6145
|
+
#### 4. 无障碍审查
|
|
6146
|
+
|
|
6147
|
+
确保应用对视障、听障、行动不便用户友好:
|
|
6148
|
+
|
|
6149
|
+
**无障碍审查 Checklist**
|
|
6150
|
+
|
|
6151
|
+
**① 屏幕阅读器(VoiceOver/TalkBack)**
|
|
6152
|
+
```swift
|
|
6153
|
+
// iOS — 所有交互元素必须有 accessibilityLabel
|
|
6154
|
+
imageView.accessibilityLabel = "用户头像"
|
|
6155
|
+
imageView.accessibilityHint = "双击查看个人资料"
|
|
6156
|
+
|
|
6157
|
+
// 自定义控件
|
|
6158
|
+
button.isAccessibilityElement = true
|
|
6159
|
+
button.accessibilityTraits = .button
|
|
6160
|
+
button.accessibilityLabel = "发布文章"
|
|
6161
|
+
|
|
6162
|
+
// ❌ 避免:仅用图标表示功能,无文字描述
|
|
6163
|
+
// ✅ 正确:图标 + accessibilityLabel
|
|
6164
|
+
```
|
|
6165
|
+
|
|
6166
|
+
```kotlin
|
|
6167
|
+
// Android
|
|
6168
|
+
imageButton.contentDescription = "发布文章"
|
|
6169
|
+
// 装饰性图片应设置 importantForAccessibility = no
|
|
6170
|
+
decorativeImage.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
6171
|
+
```
|
|
6172
|
+
|
|
6173
|
+
**② 触控目标大小**
|
|
6174
|
+
```
|
|
6175
|
+
最小触控目标:
|
|
6176
|
+
- iOS: 44pt × 44pt(Apple HIG 标准)
|
|
6177
|
+
- Android: 48dp × 48dp(Material Design 标准)
|
|
6178
|
+
|
|
6179
|
+
检查方法:
|
|
6180
|
+
- iOS Accessibility Inspector: Xcode → Open Developer Tool
|
|
6181
|
+
- Android: 开发者选项 → 显示触控区域
|
|
6182
|
+
```
|
|
6183
|
+
|
|
6184
|
+
**③ 色彩对比度**
|
|
6185
|
+
```
|
|
6186
|
+
WCAG 2.1 标准:
|
|
6187
|
+
- 正文文字(<18pt):对比度 ≥ 4.5:1
|
|
6188
|
+
- 大文字(≥18pt):对比度 ≥ 3:1
|
|
6189
|
+
- 用户界面组件:对比度 ≥ 3:1
|
|
6190
|
+
|
|
6191
|
+
工具:
|
|
6192
|
+
- Colour Contrast Analyser(桌面工具)
|
|
6193
|
+
- axe DevTools(Web)
|
|
6194
|
+
- Xcode Accessibility Inspector(iOS)
|
|
6195
|
+
```
|
|
6196
|
+
|
|
6197
|
+
**④ 动态字体支持**
|
|
6198
|
+
```swift
|
|
6199
|
+
// iOS — 支持系统字体大小调整
|
|
6200
|
+
label.font = UIFont.preferredFont(forTextStyle: .body)
|
|
6201
|
+
label.adjustsFontForContentSizeCategory = true
|
|
6202
|
+
```
|
|
6203
|
+
|
|
6204
|
+
**⑤ 减弱动态效果**
|
|
6205
|
+
```swift
|
|
6206
|
+
// 尊重"减弱动态效果"设置
|
|
6207
|
+
if UIAccessibility.isReduceMotionEnabled {
|
|
6208
|
+
// 使用简单的淡入淡出替代复杂动画
|
|
6209
|
+
UIView.animate(withDuration: 0.1) { view.alpha = 1 }
|
|
6210
|
+
} else {
|
|
6211
|
+
// 完整动画
|
|
6212
|
+
performFullAnimation()
|
|
6213
|
+
}
|
|
6214
|
+
```
|
|
6215
|
+
|
|
6216
|
+
**输出**:无障碍审查报告(VoiceOver/TalkBack + 色彩 + 触控 + 字体)
|
|
6217
|
+
|
|
6218
|
+
#### 5. 崩溃与 ANR 防护
|
|
6219
|
+
|
|
6220
|
+
防止主线程阻塞、内存泄漏和崩溃:
|
|
6221
|
+
|
|
6222
|
+
**崩溃防护 Checklist**
|
|
6223
|
+
|
|
6224
|
+
**① 主线程保护(ANR / 卡顿)**
|
|
6225
|
+
```kotlin
|
|
6226
|
+
// Android — 严格模式检测主线程 I/O(开发阶段)
|
|
6227
|
+
if (BuildConfig.DEBUG) {
|
|
6228
|
+
StrictMode.setThreadPolicy(
|
|
6229
|
+
StrictMode.ThreadPolicy.Builder()
|
|
6230
|
+
.detectDiskReads()
|
|
6231
|
+
.detectDiskWrites()
|
|
6232
|
+
.detectNetwork()
|
|
6233
|
+
.penaltyLog()
|
|
6234
|
+
.build()
|
|
6235
|
+
)
|
|
6236
|
+
}
|
|
6237
|
+
```
|
|
6238
|
+
|
|
6239
|
+
```swift
|
|
6240
|
+
// iOS — 所有耗时操作移到后台线程
|
|
6241
|
+
// ❌ 在主线程执行网络请求(卡 UI)
|
|
6242
|
+
let data = try! Data(contentsOf: url) // 同步,阻塞主线程
|
|
6243
|
+
|
|
6244
|
+
// ✅ 后台执行,主线程更新 UI
|
|
6245
|
+
Task {
|
|
6246
|
+
let data = await fetchData(url: url) // 异步
|
|
6247
|
+
await MainActor.run {
|
|
6248
|
+
updateUI(data) // 回主线程
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
```
|
|
6252
|
+
|
|
6253
|
+
**② 内存泄漏防护**
|
|
6254
|
+
```swift
|
|
6255
|
+
// iOS — 使用 weak/unowned 避免循环引用
|
|
6256
|
+
class ViewController: UIViewController {
|
|
6257
|
+
var closure: (() -> Void)?
|
|
6258
|
+
|
|
6259
|
+
func setup() {
|
|
6260
|
+
// ❌ 强引用循环
|
|
6261
|
+
closure = { self.doSomething() }
|
|
6262
|
+
|
|
6263
|
+
// ✅ weak 引用
|
|
6264
|
+
closure = { [weak self] in self?.doSomething() }
|
|
6265
|
+
}
|
|
6266
|
+
}
|
|
6267
|
+
|
|
6268
|
+
// 使用 Instruments 的 Leaks 模板检测
|
|
6269
|
+
// Xcode → Product → Profile → Leaks
|
|
6270
|
+
```
|
|
6271
|
+
|
|
6272
|
+
**③ 崩溃上报集成**
|
|
6273
|
+
```typescript
|
|
6274
|
+
// React Native — 集成 Sentry
|
|
6275
|
+
import * as Sentry from '@sentry/react-native';
|
|
6276
|
+
|
|
6277
|
+
Sentry.init({
|
|
6278
|
+
dsn: 'https://xxx@sentry.io/xxx',
|
|
6279
|
+
tracesSampleRate: 0.2,
|
|
6280
|
+
beforeSend: (event) => {
|
|
6281
|
+
// 过滤敏感信息
|
|
6282
|
+
delete event.user?.email;
|
|
6283
|
+
return event;
|
|
6284
|
+
},
|
|
6285
|
+
});
|
|
6286
|
+
|
|
6287
|
+
// 自定义崩溃上下文
|
|
6288
|
+
Sentry.setContext('order', { orderId, userId });
|
|
6289
|
+
```
|
|
6290
|
+
|
|
6291
|
+
**④ 崩溃率基准**
|
|
6292
|
+
```
|
|
6293
|
+
指标 目标值 警戒线
|
|
6294
|
+
──────────────────────────────
|
|
6295
|
+
崩溃率 < 0.1% > 0.5%
|
|
6296
|
+
ANR 率 < 0.05% > 0.2%
|
|
6297
|
+
内存 OOM 率 < 0.01% > 0.1%
|
|
6298
|
+
```
|
|
6299
|
+
|
|
6300
|
+
**输出**:崩溃防护代码模板 + ANR 检测配置 + 崩溃率监控方案
|
|
6301
|
+
|
|
6302
|
+
**输出格式**: 平台合规报告(iOS + Android)+ 性能基准报告 + 离线架构方案 + 无障碍审查报告 + 崩溃防护 Checklist
|
|
6303
|
+
|
|
6304
|
+
**注意事项**:
|
|
6305
|
+
- App Store 审核可能因隐私声明不完整被拒——提交前必须逐项核对 Info.plist 权限说明
|
|
6306
|
+
- 移动端性能优化应以真机测试为准,模拟器结果不具代表性(特别是低端 Android 设备)
|
|
6307
|
+
- 无障碍功能不仅是道德责任,在部分国家/地区(如美国 ADA、欧盟 EAA)也是法律要求
|
|
6308
|
+
- 崩溃率超过 0.5% 会被 Google Play 标记为"问题应用",影响商店排名
|
|
6309
|
+
- 包大小每增加 10MB 约导致 Android 安装转化率下降 1-2%,务必定期监控
|
|
6310
|
+
|
|
6311
|
+
---
|
|
6312
|
+
|
|
6313
|
+
### 35. 数据管道设计 (`data-pipeline`)
|
|
6314
|
+
|
|
6315
|
+
**描述**: 设计 Batch/Streaming 数据管道,制定数据质量规则、容错策略与血缘追踪方案
|
|
6316
|
+
|
|
6317
|
+
**触发词**: `数据管道`, `data pipeline`, `etl`, `elt`, `data engineering`, `数据工程`, `airflow`, `kafka pipeline`, `streaming pipeline`, `@ethan data-pipeline`, `/data-pipeline`
|
|
6318
|
+
|
|
6319
|
+
**执行步骤**:
|
|
6320
|
+
|
|
6321
|
+
#### 1. 架构选型
|
|
6322
|
+
|
|
6323
|
+
根据数据特征和业务需求选择合适的管道架构:
|
|
6324
|
+
|
|
6325
|
+
**四大架构模式决策矩阵**
|
|
6326
|
+
|
|
6327
|
+
| 架构 | 延迟 | 复杂度 | 适用场景 | 代表工具 |
|
|
6328
|
+
|------|------|--------|---------|---------|
|
|
6329
|
+
| **Batch(批处理)** | 小时~天 | 低 | 日报、数据仓库 ETL、离线训练 | Airflow + Spark |
|
|
6330
|
+
| **Streaming(流处理)** | 秒~毫秒 | 高 | 实时监控、欺诈检测、事件驱动 | Kafka + Flink |
|
|
6331
|
+
| **Lambda(λ)** | 两套 | 极高 | 需要批量精确性+流式时效性 | Kafka + Spark + Hive |
|
|
6332
|
+
| **Kappa(κ)** | 秒 | 中 | 一切皆流,重播历史数据 | Kafka + Flink |
|
|
6333
|
+
|
|
6334
|
+
**决策树**
|
|
6335
|
+
```
|
|
6336
|
+
数据新鲜度要求 < 1分钟?
|
|
6337
|
+
├── YES → Streaming(Kafka + Flink)
|
|
6338
|
+
└── NO
|
|
6339
|
+
└── 需要精确的历史重算?
|
|
6340
|
+
├── YES + 实时结果 → Lambda 架构
|
|
6341
|
+
├── YES + 可接受重播 → Kappa 架构
|
|
6342
|
+
└── NO → Batch(Airflow + Spark/dbt)
|
|
6343
|
+
```
|
|
6344
|
+
|
|
6345
|
+
**Source → Transform → Sink 数据流图**
|
|
6346
|
+
```
|
|
6347
|
+
数据源(Sources):
|
|
6348
|
+
MySQL / PostgreSQL(CDC)
|
|
6349
|
+
Kafka Topics
|
|
6350
|
+
REST API / Webhook
|
|
6351
|
+
文件系统(S3/GCS)
|
|
6352
|
+
│
|
|
6353
|
+
▼
|
|
6354
|
+
变换层(Transform):
|
|
6355
|
+
数据清洗(去重/标准化/脱敏)
|
|
6356
|
+
业务逻辑计算
|
|
6357
|
+
聚合/Join
|
|
6358
|
+
│
|
|
6359
|
+
▼
|
|
6360
|
+
目标层(Sinks):
|
|
6361
|
+
数据仓库(BigQuery/Snowflake/Redshift)
|
|
6362
|
+
数据湖(S3/GCS Parquet)
|
|
6363
|
+
搜索引擎(Elasticsearch)
|
|
6364
|
+
特征存储(Feature Store)
|
|
6365
|
+
```
|
|
6366
|
+
|
|
6367
|
+
**技术栈推荐**
|
|
6368
|
+
```
|
|
6369
|
+
小团队/初创:
|
|
6370
|
+
Airflow + dbt + BigQuery(低运维成本)
|
|
6371
|
+
|
|
6372
|
+
中大型团队:
|
|
6373
|
+
Kafka + Flink + Iceberg + Trino(高吞吐实时)
|
|
6374
|
+
|
|
6375
|
+
全托管方案:
|
|
6376
|
+
Fivetran(Ingestion)+ dbt Cloud(Transform)+ Snowflake(Warehouse)
|
|
6377
|
+
```
|
|
6378
|
+
|
|
6379
|
+
**输出**:架构选型报告 + 数据流图
|
|
6380
|
+
|
|
6381
|
+
#### 2. 数据质量规则
|
|
6382
|
+
|
|
6383
|
+
定义并实施数据质量检查规则:
|
|
6384
|
+
|
|
6385
|
+
**数据质量四维度**
|
|
6386
|
+
|
|
6387
|
+
| 维度 | 定义 | 检查方式 | 阈值示例 |
|
|
6388
|
+
|------|------|---------|---------|
|
|
6389
|
+
| **完整性** | 必填字段不为空 | NULL 率检查 | < 0.1% |
|
|
6390
|
+
| **准确性** | 值在合理范围内 | 范围/格式校验 | 异常值率 < 1% |
|
|
6391
|
+
| **一致性** | 跨系统数据一致 | 对账/交叉校验 | 差异率 < 0.01% |
|
|
6392
|
+
| **及时性** | 数据按时到达 | 延迟监控 | 最大延迟 < 2h |
|
|
6393
|
+
|
|
6394
|
+
**dbt 数据质量测试(推荐)**
|
|
6395
|
+
```yaml
|
|
6396
|
+
# models/orders.yml
|
|
6397
|
+
version: 2
|
|
6398
|
+
models:
|
|
6399
|
+
- name: orders
|
|
6400
|
+
columns:
|
|
6401
|
+
- name: order_id
|
|
6402
|
+
tests:
|
|
6403
|
+
- not_null
|
|
6404
|
+
- unique
|
|
6405
|
+
- name: user_id
|
|
6406
|
+
tests:
|
|
6407
|
+
- not_null
|
|
6408
|
+
- relationships:
|
|
6409
|
+
to: ref('users')
|
|
6410
|
+
field: id
|
|
6411
|
+
- name: status
|
|
6412
|
+
tests:
|
|
6413
|
+
- accepted_values:
|
|
6414
|
+
values: ['pending', 'paid', 'shipped', 'completed', 'cancelled']
|
|
6415
|
+
- name: amount
|
|
6416
|
+
tests:
|
|
6417
|
+
- not_null
|
|
6418
|
+
- dbt_expectations.expect_column_values_to_be_between:
|
|
6419
|
+
min_value: 0
|
|
6420
|
+
max_value: 1000000
|
|
6421
|
+
```
|
|
6422
|
+
|
|
6423
|
+
**Great Expectations 数据期望(Python)**
|
|
6424
|
+
```python
|
|
6425
|
+
import great_expectations as gx
|
|
6426
|
+
|
|
6427
|
+
context = gx.get_context()
|
|
6428
|
+
suite = context.add_expectation_suite("orders_suite")
|
|
6429
|
+
|
|
6430
|
+
# 定义数据期望
|
|
6431
|
+
suite.add_expectation(
|
|
6432
|
+
gx.expectations.ExpectColumnValuesToNotBeNull(column="order_id")
|
|
6433
|
+
)
|
|
6434
|
+
suite.add_expectation(
|
|
6435
|
+
gx.expectations.ExpectColumnValuesToBeBetween(
|
|
6436
|
+
column="amount", min_value=0, max_value=1_000_000
|
|
6437
|
+
)
|
|
6438
|
+
)
|
|
6439
|
+
suite.add_expectation(
|
|
6440
|
+
gx.expectations.ExpectTableRowCountToBeBetween(
|
|
6441
|
+
min_value=1000, # 每日订单不少于1000
|
|
6442
|
+
max_value=1_000_000
|
|
6443
|
+
)
|
|
6444
|
+
)
|
|
6445
|
+
|
|
6446
|
+
# 运行验证
|
|
6447
|
+
result = context.run_checkpoint("daily_orders_checkpoint")
|
|
6448
|
+
if not result.success:
|
|
6449
|
+
raise DataQualityError("数据质量检查未通过!")
|
|
6450
|
+
```
|
|
6451
|
+
|
|
6452
|
+
**数据质量告警**
|
|
6453
|
+
```yaml
|
|
6454
|
+
# 数据质量 SLA
|
|
6455
|
+
- NULL 率超过 1%:🔴 阻断管道,Slack 告警
|
|
6456
|
+
- 行数下降 > 20%(对比昨日):🟠 告警,人工核查
|
|
6457
|
+
- P99 延迟 > 2 小时:🟡 警告,关注
|
|
6458
|
+
```
|
|
6459
|
+
|
|
6460
|
+
**输出**:数据质量规则文档 + dbt/GE 测试配置
|
|
6461
|
+
|
|
6462
|
+
#### 3. 管道设计与代码模板
|
|
6463
|
+
|
|
6464
|
+
生成可复用的管道代码模板:
|
|
6465
|
+
|
|
6466
|
+
**Airflow DAG 模板(Batch)**
|
|
6467
|
+
```python
|
|
6468
|
+
# dags/daily_orders_etl.py
|
|
6469
|
+
from airflow import DAG
|
|
6470
|
+
from airflow.operators.python import PythonOperator
|
|
6471
|
+
from airflow.providers.postgres.hooks.postgres import PostgresHook
|
|
6472
|
+
from datetime import datetime, timedelta
|
|
6473
|
+
|
|
6474
|
+
default_args = {
|
|
6475
|
+
'owner': 'data-team',
|
|
6476
|
+
'retries': 3,
|
|
6477
|
+
'retry_delay': timedelta(minutes=5),
|
|
6478
|
+
'on_failure_callback': alert_slack,
|
|
6479
|
+
}
|
|
6480
|
+
|
|
6481
|
+
with DAG(
|
|
6482
|
+
'daily_orders_etl',
|
|
6483
|
+
default_args=default_args,
|
|
6484
|
+
schedule_interval='0 2 * * *', # 每日凌晨2点
|
|
6485
|
+
start_date=datetime(2024, 1, 1),
|
|
6486
|
+
catchup=False, # 不回填历史
|
|
6487
|
+
tags=['orders', 'daily'],
|
|
6488
|
+
) as dag:
|
|
6489
|
+
|
|
6490
|
+
extract = PythonOperator(task_id='extract', python_callable=extract_orders)
|
|
6491
|
+
validate = PythonOperator(task_id='validate', python_callable=run_quality_checks)
|
|
6492
|
+
transform = PythonOperator(task_id='transform', python_callable=transform_orders)
|
|
6493
|
+
load = PythonOperator(task_id='load', python_callable=load_to_warehouse)
|
|
6494
|
+
notify = PythonOperator(task_id='notify', python_callable=send_completion_report)
|
|
6495
|
+
|
|
6496
|
+
extract >> validate >> transform >> load >> notify
|
|
6497
|
+
```
|
|
6498
|
+
|
|
6499
|
+
**Kafka + Flink 流处理模板**
|
|
6500
|
+
```python
|
|
6501
|
+
# Flink Python API
|
|
6502
|
+
from pyflink.datastream import StreamExecutionEnvironment
|
|
6503
|
+
from pyflink.datastream.connectors.kafka import KafkaSource, KafkaSink
|
|
6504
|
+
|
|
6505
|
+
env = StreamExecutionEnvironment.get_execution_environment()
|
|
6506
|
+
env.set_parallelism(4)
|
|
6507
|
+
|
|
6508
|
+
# Source
|
|
6509
|
+
kafka_source = KafkaSource.builder() .set_bootstrap_servers("kafka:9092") .set_topics("orders") .set_group_id("flink-order-processor") .set_value_only_deserializer(JsonRowDeserializationSchema()) .build()
|
|
6510
|
+
|
|
6511
|
+
stream = env.from_source(kafka_source, WatermarkStrategy.no_watermarks(), "Kafka Source")
|
|
6512
|
+
|
|
6513
|
+
# Transform(滚动窗口:每分钟统计)
|
|
6514
|
+
result = stream .key_by(lambda x: x['region']) .window(TumblingEventTimeWindows.of(Time.minutes(1))) .aggregate(OrderAggregateFunction())
|
|
6515
|
+
|
|
6516
|
+
# Sink
|
|
6517
|
+
result.sink_to(KafkaSink.builder()
|
|
6518
|
+
.set_bootstrap_servers("kafka:9092")
|
|
6519
|
+
.set_record_serializer(JsonRowSerializationSchema())
|
|
6520
|
+
.build())
|
|
6521
|
+
|
|
6522
|
+
env.execute("Order Stream Processing")
|
|
6523
|
+
```
|
|
6524
|
+
|
|
6525
|
+
**输出**:Airflow DAG 模板 + Flink/Spark 流处理模板
|
|
6526
|
+
|
|
6527
|
+
#### 4. 容错与重试策略
|
|
6528
|
+
|
|
6529
|
+
确保管道在故障时可靠恢复:
|
|
6530
|
+
|
|
6531
|
+
**核心容错原则**
|
|
6532
|
+
|
|
6533
|
+
**① 幂等性设计(最重要)**
|
|
6534
|
+
```python
|
|
6535
|
+
def load_orders_idempotent(batch_date: str, orders: list):
|
|
6536
|
+
"""幂等加载:同一批次多次执行结果相同"""
|
|
6537
|
+
# 使用 INSERT ... ON CONFLICT DO NOTHING
|
|
6538
|
+
# 或 MERGE(UPSERT)语义
|
|
6539
|
+
pg_hook.run("""
|
|
6540
|
+
INSERT INTO orders_dw (order_id, batch_date, amount, status)
|
|
6541
|
+
VALUES %s
|
|
6542
|
+
ON CONFLICT (order_id) DO UPDATE SET
|
|
6543
|
+
amount = EXCLUDED.amount,
|
|
6544
|
+
status = EXCLUDED.status,
|
|
6545
|
+
updated_at = NOW()
|
|
6546
|
+
""", [(o['id'], batch_date, o['amount'], o['status']) for o in orders])
|
|
6547
|
+
```
|
|
6548
|
+
|
|
6549
|
+
**② 死信队列(DLQ)**
|
|
6550
|
+
```python
|
|
6551
|
+
# Kafka — 处理失败消息路由到 DLQ
|
|
6552
|
+
def process_message(msg):
|
|
6553
|
+
try:
|
|
6554
|
+
transform_and_load(msg)
|
|
6555
|
+
except Exception as e:
|
|
6556
|
+
# 路由到死信队列,保留原始消息 + 错误信息
|
|
6557
|
+
dlq_producer.send('orders-dlq', {
|
|
6558
|
+
'original_message': msg,
|
|
6559
|
+
'error': str(e),
|
|
6560
|
+
'failed_at': datetime.utcnow().isoformat(),
|
|
6561
|
+
'retry_count': msg.get('retry_count', 0) + 1
|
|
6562
|
+
})
|
|
6563
|
+
logger.error(f"Message sent to DLQ: {e}")
|
|
6564
|
+
```
|
|
6565
|
+
|
|
6566
|
+
**③ 断点续传(Checkpoint)**
|
|
6567
|
+
```python
|
|
6568
|
+
def process_large_dataset():
|
|
6569
|
+
checkpoint_file = '.etl_checkpoint'
|
|
6570
|
+
last_id = load_checkpoint(checkpoint_file)
|
|
6571
|
+
|
|
6572
|
+
for batch in fetch_in_batches(after_id=last_id, batch_size=1000):
|
|
6573
|
+
process_batch(batch)
|
|
6574
|
+
save_checkpoint(checkpoint_file, batch[-1]['id']) # 每批保存进度
|
|
6575
|
+
```
|
|
6576
|
+
|
|
6577
|
+
**④ 指数退避重试**
|
|
6578
|
+
```python
|
|
6579
|
+
import tenacity
|
|
6580
|
+
|
|
6581
|
+
@tenacity.retry(
|
|
6582
|
+
wait=tenacity.wait_exponential(multiplier=1, min=4, max=60),
|
|
6583
|
+
stop=tenacity.stop_after_attempt(5),
|
|
6584
|
+
retry=tenacity.retry_if_exception_type(TransientError),
|
|
6585
|
+
before_sleep=tenacity.before_sleep_log(logger, logging.WARNING)
|
|
6586
|
+
)
|
|
6587
|
+
def call_external_api(data):
|
|
6588
|
+
return requests.post(API_URL, json=data, timeout=30)
|
|
6589
|
+
```
|
|
6590
|
+
|
|
6591
|
+
**输出**:容错策略文档 + 幂等加载模板 + DLQ 配置
|
|
6592
|
+
|
|
6593
|
+
#### 5. 数据血缘与可观测性
|
|
6594
|
+
|
|
6595
|
+
建立数据血缘追踪和管道监控体系:
|
|
6596
|
+
|
|
6597
|
+
**数据血缘(Data Lineage)**
|
|
6598
|
+
```python
|
|
6599
|
+
# OpenLineage 标准(Marquez/Atlan/DataHub 支持)
|
|
6600
|
+
from openlineage.client import OpenLineageClient
|
|
6601
|
+
from openlineage.client.run import RunEvent, Job, Run, Dataset
|
|
6602
|
+
|
|
6603
|
+
client = OpenLineageClient.from_environment()
|
|
6604
|
+
|
|
6605
|
+
# 记录数据流转关系
|
|
6606
|
+
client.emit(RunEvent(
|
|
6607
|
+
eventType="COMPLETE",
|
|
6608
|
+
job=Job(namespace="etl", name="daily_orders_transform"),
|
|
6609
|
+
run=Run(runId=str(uuid4())),
|
|
6610
|
+
inputs=[Dataset(namespace="postgres", name="raw_orders")],
|
|
6611
|
+
outputs=[Dataset(namespace="bigquery", name="orders_dw.fact_orders")]
|
|
6612
|
+
))
|
|
6613
|
+
```
|
|
6614
|
+
|
|
6615
|
+
**管道健康仪表盘指标**
|
|
6616
|
+
```
|
|
6617
|
+
关键指标(Grafana 面板):
|
|
6618
|
+
─────────────────────────────
|
|
6619
|
+
延迟指标:
|
|
6620
|
+
- 数据新鲜度(最新记录时间戳)
|
|
6621
|
+
- Pipeline P95 执行时长
|
|
6622
|
+
- Kafka Consumer Lag
|
|
6623
|
+
|
|
6624
|
+
质量指标:
|
|
6625
|
+
- 每日 NULL 率趋势
|
|
6626
|
+
- 行数异常检测(±30% 告警)
|
|
6627
|
+
- 数据质量测试通过率
|
|
6628
|
+
|
|
6629
|
+
运维指标:
|
|
6630
|
+
- 管道成功率(目标 > 99%)
|
|
6631
|
+
- 重试次数分布
|
|
6632
|
+
- DLQ 消息积压
|
|
6633
|
+
```
|
|
6634
|
+
|
|
6635
|
+
**Airflow SLA 监控**
|
|
6636
|
+
```python
|
|
6637
|
+
with DAG(
|
|
6638
|
+
'daily_orders_etl',
|
|
6639
|
+
sla_miss_callback=sla_miss_alert, # SLA 超时回调
|
|
6640
|
+
...
|
|
6641
|
+
) as dag:
|
|
6642
|
+
load_task = PythonOperator(
|
|
6643
|
+
task_id='load',
|
|
6644
|
+
python_callable=load_to_warehouse,
|
|
6645
|
+
sla=timedelta(hours=4), # 必须在4小时内完成
|
|
6646
|
+
)
|
|
6647
|
+
```
|
|
6648
|
+
|
|
6649
|
+
**数据目录集成**
|
|
6650
|
+
```yaml
|
|
6651
|
+
# dbt docs(自动生成)
|
|
6652
|
+
# 运行后访问:dbt docs serve
|
|
6653
|
+
# 包含:模型血缘图、列描述、测试结果
|
|
6654
|
+
|
|
6655
|
+
# 推荐工具
|
|
6656
|
+
生产级:DataHub, Atlan, Alation
|
|
6657
|
+
开源轻量:Marquez, OpenMetadata
|
|
6658
|
+
dbt 生态:dbt docs + Elementary
|
|
6659
|
+
```
|
|
6660
|
+
|
|
6661
|
+
**输出**:数据血缘配置 + 监控仪表盘指标定义 + 数据目录方案
|
|
6662
|
+
|
|
6663
|
+
**输出格式**: 架构选型文档 + 数据流图 + 质量规则配置(dbt/GE)+ 管道代码模板 + 容错策略 + 血缘追踪方案
|
|
6664
|
+
|
|
6665
|
+
**注意事项**:
|
|
6666
|
+
- 幂等性是数据管道的第一原则——任何任务必须可以安全重试而不产生重复数据
|
|
6667
|
+
- 从 Batch 开始,在有真实需求时再迁移到 Streaming,避免过早的复杂度
|
|
6668
|
+
- 数据质量检查应与管道强耦合(而非事后补救),质量不达标应阻断下游加载
|
|
6669
|
+
- 死信队列不是垃圾桶——DLQ 中的消息代表业务异常,必须定期审查和处理
|
|
6670
|
+
- dbt + Airflow + BigQuery 是目前最常见的现代数据栈,对大多数团队足够用
|
|
6671
|
+
|
|
6672
|
+
---
|
|
6673
|
+
|
|
6674
|
+
### 36. ML 实验管理 (`ml-experiment`)
|
|
6675
|
+
|
|
6676
|
+
**描述**: 规范 ML 实验设计、追踪配置(MLflow/W&B)、数据版本控制、Model Card 生成与部署监控
|
|
6677
|
+
|
|
6678
|
+
**触发词**: `ml experiment`, `mlops`, `model training`, `模型训练`, `实验追踪`, `model card`, `mlflow`, `wandb`, `dvc`, `机器学习实验`, `@ethan ml-experiment`, `/ml-experiment`
|
|
6679
|
+
|
|
6680
|
+
**执行步骤**:
|
|
6681
|
+
|
|
6682
|
+
#### 1. 实验设计
|
|
6683
|
+
|
|
6684
|
+
在开始训练前明确实验目标和控制变量:
|
|
6685
|
+
|
|
6686
|
+
**假设驱动实验设计**
|
|
6687
|
+
```markdown
|
|
6688
|
+
## 实验设计文档
|
|
6689
|
+
|
|
6690
|
+
### 研究问题
|
|
6691
|
+
我们假设:[变量 X] 会导致 [指标 Y] 提升 [Z%],
|
|
6692
|
+
因为 [理论依据或先验知识]。
|
|
6693
|
+
|
|
6694
|
+
### 实验配置
|
|
6695
|
+
- 控制组(Baseline):[现有模型/方法描述]
|
|
6696
|
+
- 实验组:[变更内容描述]
|
|
6697
|
+
- 控制变量:[保持不变的因素:数据集/超参/随机种子]
|
|
6698
|
+
|
|
6699
|
+
### 评估指标
|
|
6700
|
+
- 主要指标:AUC-ROC(因为关注排序,不关注具体阈值)
|
|
6701
|
+
- 次要指标:Precision@K, Recall@K
|
|
6702
|
+
- 业务指标:CTR 提升(线上 A/B 验证)
|
|
6703
|
+
|
|
6704
|
+
### 成功标准
|
|
6705
|
+
- 主要指标提升 ≥ 2%(统计显著性 p < 0.05)
|
|
6706
|
+
- 推理延迟不增加超过 20%
|
|
6707
|
+
- 训练成本不超过 $50
|
|
6708
|
+
```
|
|
6709
|
+
|
|
6710
|
+
**实验矩阵(超参搜索)**
|
|
6711
|
+
```python
|
|
6712
|
+
# 使用 Optuna 自动超参优化
|
|
6713
|
+
import optuna
|
|
6714
|
+
|
|
6715
|
+
def objective(trial):
|
|
6716
|
+
params = {
|
|
6717
|
+
'learning_rate': trial.suggest_float('lr', 1e-5, 1e-1, log=True),
|
|
6718
|
+
'batch_size': trial.suggest_categorical('batch_size', [16, 32, 64, 128]),
|
|
6719
|
+
'dropout': trial.suggest_float('dropout', 0.1, 0.5),
|
|
6720
|
+
'hidden_dim': trial.suggest_int('hidden_dim', 64, 512, step=64),
|
|
6721
|
+
}
|
|
6722
|
+
model = train_model(**params)
|
|
6723
|
+
return evaluate(model)['auc']
|
|
6724
|
+
|
|
6725
|
+
study = optuna.create_study(direction='maximize')
|
|
6726
|
+
study.optimize(objective, n_trials=50, timeout=3600)
|
|
6727
|
+
print(f"Best params: {study.best_params}")
|
|
6728
|
+
```
|
|
6729
|
+
|
|
6730
|
+
**最小可行实验(MVE)原则**
|
|
6731
|
+
```
|
|
6732
|
+
先用 10% 数据快速验证假设(10min 训练)
|
|
6733
|
+
→ 有提升信号 → 扩展到全量数据
|
|
6734
|
+
→ 无提升信号 → 调整假设重新实验
|
|
6735
|
+
```
|
|
6736
|
+
|
|
6737
|
+
**输出**:实验设计文档 + 评估指标定义 + 超参搜索配置
|
|
6738
|
+
|
|
6739
|
+
#### 2. 实验追踪配置
|
|
6740
|
+
|
|
6741
|
+
配置 MLflow 或 W&B 实现实验全自动追踪:
|
|
6742
|
+
|
|
6743
|
+
**MLflow 完整配置**
|
|
6744
|
+
```python
|
|
6745
|
+
import mlflow
|
|
6746
|
+
import mlflow.pytorch
|
|
6747
|
+
|
|
6748
|
+
# 配置追踪服务器
|
|
6749
|
+
mlflow.set_tracking_uri("http://mlflow-server:5000")
|
|
6750
|
+
mlflow.set_experiment("recommendation-model-v2")
|
|
6751
|
+
|
|
6752
|
+
with mlflow.start_run(run_name="bert-finetune-lr1e-4") as run:
|
|
6753
|
+
# 记录所有超参
|
|
6754
|
+
mlflow.log_params({
|
|
6755
|
+
"model_name": "bert-base-chinese",
|
|
6756
|
+
"learning_rate": 1e-4,
|
|
6757
|
+
"batch_size": 32,
|
|
6758
|
+
"epochs": 10,
|
|
6759
|
+
"optimizer": "AdamW",
|
|
6760
|
+
"warmup_steps": 500,
|
|
6761
|
+
})
|
|
6762
|
+
|
|
6763
|
+
# 记录数据集信息
|
|
6764
|
+
mlflow.log_param("train_samples", len(train_dataset))
|
|
6765
|
+
mlflow.log_param("data_version", "v2024-01-15")
|
|
6766
|
+
|
|
6767
|
+
for epoch in range(config.epochs):
|
|
6768
|
+
train_loss = train_epoch(model, train_loader)
|
|
6769
|
+
val_metrics = evaluate(model, val_loader)
|
|
6770
|
+
|
|
6771
|
+
# 实时记录指标
|
|
6772
|
+
mlflow.log_metrics({
|
|
6773
|
+
"train_loss": train_loss,
|
|
6774
|
+
"val_loss": val_metrics['loss'],
|
|
6775
|
+
"val_auc": val_metrics['auc'],
|
|
6776
|
+
"val_f1": val_metrics['f1'],
|
|
6777
|
+
}, step=epoch)
|
|
6778
|
+
|
|
6779
|
+
# 注册最终模型
|
|
6780
|
+
mlflow.pytorch.log_model(
|
|
6781
|
+
model,
|
|
6782
|
+
"model",
|
|
6783
|
+
registered_model_name="recommendation-model",
|
|
6784
|
+
pip_requirements=["torch==2.1.0", "transformers==4.35.0"],
|
|
6785
|
+
)
|
|
6786
|
+
|
|
6787
|
+
# 记录评估报告
|
|
6788
|
+
mlflow.log_artifact("reports/confusion_matrix.png")
|
|
6789
|
+
mlflow.log_artifact("reports/feature_importance.html")
|
|
6790
|
+
|
|
6791
|
+
print(f"Run ID: {run.info.run_id}")
|
|
6792
|
+
```
|
|
6793
|
+
|
|
6794
|
+
**W&B 配置(更丰富的可视化)**
|
|
6795
|
+
```python
|
|
6796
|
+
import wandb
|
|
6797
|
+
|
|
6798
|
+
wandb.init(
|
|
6799
|
+
project="recommendation-v2",
|
|
6800
|
+
name="bert-finetune-lr1e-4",
|
|
6801
|
+
config={
|
|
6802
|
+
"learning_rate": 1e-4,
|
|
6803
|
+
"architecture": "bert-base",
|
|
6804
|
+
"dataset": "user-clicks-v2",
|
|
6805
|
+
},
|
|
6806
|
+
tags=["bert", "production-candidate"],
|
|
6807
|
+
)
|
|
6808
|
+
|
|
6809
|
+
# 自动记录梯度和权重(PyTorch)
|
|
6810
|
+
wandb.watch(model, log='all', log_freq=100)
|
|
6811
|
+
|
|
6812
|
+
# 训练循环中
|
|
6813
|
+
wandb.log({"train_loss": loss, "val_auc": auc}, step=epoch)
|
|
6814
|
+
|
|
6815
|
+
# 完成时
|
|
6816
|
+
wandb.finish()
|
|
6817
|
+
```
|
|
6818
|
+
|
|
6819
|
+
**输出**:MLflow/W&B 实验追踪配置 + 训练脚本模板
|
|
6820
|
+
|
|
6821
|
+
#### 3. 数据版本控制
|
|
6822
|
+
|
|
6823
|
+
使用 DVC 管理数据集和特征工程的版本:
|
|
6824
|
+
|
|
6825
|
+
**DVC 初始化与数据版本管理**
|
|
6826
|
+
```bash
|
|
6827
|
+
# 初始化 DVC(与 Git 协同)
|
|
6828
|
+
git init && dvc init
|
|
6829
|
+
|
|
6830
|
+
# 添加数据集到 DVC 管理
|
|
6831
|
+
dvc add data/train.parquet
|
|
6832
|
+
dvc add data/test.parquet
|
|
6833
|
+
git add data/.gitignore data/train.parquet.dvc data/test.parquet.dvc
|
|
6834
|
+
git commit -m "Add training data v1"
|
|
6835
|
+
|
|
6836
|
+
# 配置远程存储(S3/GCS/Azure)
|
|
6837
|
+
dvc remote add -d myremote s3://my-ml-bucket/dvc-store
|
|
6838
|
+
dvc push # 上传数据到远程
|
|
6839
|
+
|
|
6840
|
+
# 切换到不同数据版本
|
|
6841
|
+
git checkout v1.0-data-tag
|
|
6842
|
+
dvc pull # 下载对应版本数据
|
|
6843
|
+
```
|
|
6844
|
+
|
|
6845
|
+
**特征工程管道(可复现)**
|
|
6846
|
+
```python
|
|
6847
|
+
# dvc.yaml — 定义可复现的管道
|
|
6848
|
+
stages:
|
|
6849
|
+
prepare_data:
|
|
6850
|
+
cmd: python src/prepare.py --input data/raw --output data/prepared
|
|
6851
|
+
deps:
|
|
6852
|
+
- src/prepare.py
|
|
6853
|
+
- data/raw
|
|
6854
|
+
outs:
|
|
6855
|
+
- data/prepared
|
|
6856
|
+
|
|
6857
|
+
feature_engineering:
|
|
6858
|
+
cmd: python src/features.py --input data/prepared --output data/features
|
|
6859
|
+
deps:
|
|
6860
|
+
- src/features.py
|
|
6861
|
+
- data/prepared
|
|
6862
|
+
- params.yaml # 特征工程超参
|
|
6863
|
+
outs:
|
|
6864
|
+
- data/features
|
|
6865
|
+
metrics:
|
|
6866
|
+
- reports/feature_stats.json
|
|
6867
|
+
|
|
6868
|
+
train:
|
|
6869
|
+
cmd: python src/train.py
|
|
6870
|
+
deps:
|
|
6871
|
+
- src/train.py
|
|
6872
|
+
- data/features
|
|
6873
|
+
outs:
|
|
6874
|
+
- models/model.pkl
|
|
6875
|
+
metrics:
|
|
6876
|
+
- reports/metrics.json
|
|
6877
|
+
```
|
|
6878
|
+
|
|
6879
|
+
```bash
|
|
6880
|
+
# 运行完整管道(只重新执行有变化的阶段)
|
|
6881
|
+
dvc repro
|
|
6882
|
+
|
|
6883
|
+
# 对比不同版本的指标
|
|
6884
|
+
dvc metrics diff v1.0 v2.0
|
|
6885
|
+
# ┌──────────────┬───────┬───────┬────────┐
|
|
6886
|
+
# │ Metric │ HEAD │ v1.0 │ Change │
|
|
6887
|
+
# ├──────────────┼───────┼───────┼────────┤
|
|
6888
|
+
# │ val_auc │ 0.847 │ 0.831 │ +0.016 │
|
|
6889
|
+
# └──────────────┴───────┴───────┴────────┘
|
|
6890
|
+
```
|
|
6891
|
+
|
|
6892
|
+
**输出**:DVC 配置 + 特征管道 dvc.yaml + 版本对比命令
|
|
6893
|
+
|
|
6894
|
+
#### 4. Model Card 生成
|
|
6895
|
+
|
|
6896
|
+
为每个发布的模型生成标准化的 Model Card:
|
|
6897
|
+
|
|
6898
|
+
**Model Card 模板**
|
|
6899
|
+
```markdown
|
|
6900
|
+
# Model Card: 推荐模型 v2.1.0
|
|
6901
|
+
|
|
6902
|
+
## 模型概述
|
|
6903
|
+
- **模型类型**:双塔召回模型(BERT + 协同过滤)
|
|
6904
|
+
- **任务**:电商商品推荐
|
|
6905
|
+
- **训练日期**:2024-01-20
|
|
6906
|
+
- **版本**:v2.1.0
|
|
6907
|
+
- **MLflow Run ID**:abc123def456
|
|
6908
|
+
|
|
6909
|
+
## 预期用途
|
|
6910
|
+
### 主要用途
|
|
6911
|
+
为已登录用户生成个性化商品推荐,覆盖首页、详情页猜你喜欢。
|
|
6912
|
+
|
|
6913
|
+
### 不适合的用途
|
|
6914
|
+
- 冷启动用户(注册 < 7 天)→ 使用热门推荐替代
|
|
6915
|
+
- 价格敏感决策(不提供价格预测)
|
|
6916
|
+
|
|
6917
|
+
## 训练数据
|
|
6918
|
+
- **数据集**:user-click-events v2024-01
|
|
6919
|
+
- **训练样本**:5,200,000 条用户点击行为
|
|
6920
|
+
- **时间范围**:2023-07-01 ~ 2023-12-31
|
|
6921
|
+
- **数据版本**:DVC tag `data-v2024-01`
|
|
6922
|
+
|
|
6923
|
+
## 性能指标
|
|
6924
|
+
| 指标 | 离线值 | 在线 A/B(1周) |
|
|
6925
|
+
|------|--------|---------------|
|
|
6926
|
+
| Recall@20 | 0.847 | 0.821 |
|
|
6927
|
+
| NDCG@20 | 0.623 | 0.598 |
|
|
6928
|
+
| CTR | — | +4.2%(vs 旧模型)|
|
|
6929
|
+
| Latency P99 | 45ms | 52ms |
|
|
6930
|
+
|
|
6931
|
+
## 偏差与公平性
|
|
6932
|
+
- 对新品(上架 < 30 天)存在曝光不足偏差 → 已通过 Explore 策略缓解
|
|
6933
|
+
- 价格区间分布分析:低价商品点击率被高估(训练集偏差)→ 已加权修正
|
|
6934
|
+
|
|
6935
|
+
## 限制
|
|
6936
|
+
- 模型不包含实时库存信息,需上层过滤下架商品
|
|
6937
|
+
- 不支持多语言商品(仅中文描述)
|
|
6938
|
+
|
|
6939
|
+
## 负责任的 AI 声明
|
|
6940
|
+
- 不使用性别/年龄等受保护属性作为特征
|
|
6941
|
+
- 符合公司隐私政策,用户行为数据已脱敏处理
|
|
6942
|
+
```
|
|
6943
|
+
|
|
6944
|
+
**自动生成脚本**
|
|
6945
|
+
```python
|
|
6946
|
+
def generate_model_card(run_id: str, ab_results: dict) -> str:
|
|
6947
|
+
"""从 MLflow run 自动生成 Model Card"""
|
|
6948
|
+
run = mlflow.get_run(run_id)
|
|
6949
|
+
params = run.data.params
|
|
6950
|
+
metrics = run.data.metrics
|
|
6951
|
+
|
|
6952
|
+
return MODEL_CARD_TEMPLATE.format(
|
|
6953
|
+
version=params.get('model_version'),
|
|
6954
|
+
train_date=run.info.start_time,
|
|
6955
|
+
recall_at_20=metrics.get('val_recall_at_20'),
|
|
6956
|
+
ctr_lift=ab_results.get('ctr_lift'),
|
|
6957
|
+
run_id=run_id,
|
|
6958
|
+
)
|
|
6959
|
+
```
|
|
6960
|
+
|
|
6961
|
+
**输出**:Model Card 文档 + 自动生成脚本
|
|
6962
|
+
|
|
6963
|
+
#### 5. 部署与监控
|
|
6964
|
+
|
|
6965
|
+
模型上线、A/B 测试和生产监控完整方案:
|
|
6966
|
+
|
|
6967
|
+
**模型注册与晋升流程**
|
|
6968
|
+
```python
|
|
6969
|
+
# MLflow 模型注册与阶段管理
|
|
6970
|
+
from mlflow.tracking import MlflowClient
|
|
6971
|
+
|
|
6972
|
+
client = MlflowClient()
|
|
6973
|
+
|
|
6974
|
+
# 注册模型
|
|
6975
|
+
model_uri = f"runs:/{run_id}/model"
|
|
6976
|
+
mv = client.create_model_version(
|
|
6977
|
+
name="recommendation-model",
|
|
6978
|
+
source=model_uri,
|
|
6979
|
+
run_id=run_id,
|
|
6980
|
+
)
|
|
6981
|
+
|
|
6982
|
+
# 模型阶段:None → Staging → Production
|
|
6983
|
+
# 先晋升到 Staging(测试)
|
|
6984
|
+
client.transition_model_version_stage(
|
|
6985
|
+
name="recommendation-model",
|
|
6986
|
+
version=mv.version,
|
|
6987
|
+
stage="Staging",
|
|
6988
|
+
)
|
|
6989
|
+
|
|
6990
|
+
# 验证通过后晋升到 Production
|
|
6991
|
+
client.transition_model_version_stage(
|
|
6992
|
+
name="recommendation-model",
|
|
6993
|
+
version=mv.version,
|
|
6994
|
+
stage="Production",
|
|
6995
|
+
archive_existing_versions=True, # 归档旧版本
|
|
6996
|
+
)
|
|
6997
|
+
```
|
|
6998
|
+
|
|
6999
|
+
**A/B 测试配置**
|
|
7000
|
+
```python
|
|
7001
|
+
# 按用户哈希分流
|
|
7002
|
+
def get_model_for_user(user_id: str) -> str:
|
|
7003
|
+
hash_val = int(hashlib.md5(user_id.encode()).hexdigest(), 16) % 100
|
|
7004
|
+
if hash_val < 10: # 10% 流量
|
|
7005
|
+
return "recommendation-model:v2.1.0"
|
|
7006
|
+
else:
|
|
7007
|
+
return "recommendation-model:v2.0.0"
|
|
7008
|
+
|
|
7009
|
+
# 记录 A/B 分组(用于统计分析)
|
|
7010
|
+
mlflow.log_param("ab_group", "control" if version == "v2.0.0" else "treatment")
|
|
7011
|
+
```
|
|
7012
|
+
|
|
7013
|
+
**数据漂移监控(Evidently)**
|
|
7014
|
+
```python
|
|
7015
|
+
from evidently.report import Report
|
|
7016
|
+
from evidently.metric_preset import DataDriftPreset, ModelPerformancePreset
|
|
7017
|
+
|
|
7018
|
+
# 对比训练数据分布 vs 生产数据分布
|
|
7019
|
+
report = Report(metrics=[
|
|
7020
|
+
DataDriftPreset(), # 特征分布漂移
|
|
7021
|
+
ModelPerformancePreset(), # 模型性能漂移
|
|
7022
|
+
])
|
|
7023
|
+
|
|
7024
|
+
report.run(reference_data=train_df, current_data=production_df_last_7d)
|
|
7025
|
+
report.save_html("reports/drift_report.html")
|
|
7026
|
+
|
|
7027
|
+
# 自动回滚条件
|
|
7028
|
+
drift_detected = report.as_dict()['metrics'][0]['result']['dataset_drift']
|
|
7029
|
+
if drift_detected:
|
|
7030
|
+
alert_and_rollback(reason="Feature distribution drift detected")
|
|
7031
|
+
```
|
|
7032
|
+
|
|
7033
|
+
**生产监控仪表盘**
|
|
7034
|
+
```
|
|
7035
|
+
关键指标(每日监控):
|
|
7036
|
+
─────────────────────────────────
|
|
7037
|
+
模型性能:
|
|
7038
|
+
在线 CTR(实验 vs 对照)
|
|
7039
|
+
推荐命中率(Recall@K)
|
|
7040
|
+
|
|
7041
|
+
数据漂移:
|
|
7042
|
+
用户特征分布变化(PSI > 0.2 告警)
|
|
7043
|
+
商品特征新增/消失比率
|
|
7044
|
+
|
|
7045
|
+
运维指标:
|
|
7046
|
+
推理延迟 P99(< 100ms)
|
|
7047
|
+
模型服务错误率(< 0.1%)
|
|
7048
|
+
预测请求量(异常波动告警)
|
|
7049
|
+
```
|
|
7050
|
+
|
|
7051
|
+
**输出**:模型注册流程 + A/B 测试配置 + 漂移监控脚本 + 自动回滚方案
|
|
7052
|
+
|
|
7053
|
+
**输出格式**: 实验设计文档 + MLflow/W&B 追踪配置 + DVC 数据管道 + Model Card + 部署 A/B 测试方案 + 漂移监控配置
|
|
7054
|
+
|
|
7055
|
+
**注意事项**:
|
|
7056
|
+
- 实验追踪不是可选项——没有追踪记录的实验结果无法复现,也无法与团队分享
|
|
7057
|
+
- 每个 MLflow/W&B Run 必须记录数据版本,否则6个月后无法知道用了哪份训练数据
|
|
7058
|
+
- Model Card 对外部合作和监管合规至关重要,建议与模型一起版本化管理
|
|
7059
|
+
- 数据漂移监控比模型指标监控更重要——漂移发生在指标下降之前
|
|
7060
|
+
- 优先用 MLflow(开源自托管)降低工具成本;大团队再考虑 W&B 或 Vertex AI 等商业方案
|
|
7061
|
+
|
|
7062
|
+
---
|
|
7063
|
+
|
|
4104
7064
|
*Ethan - Your AI Workflow Assistant | 让每一步都有据可依*
|