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