feat(admin): Add user management and upgrade to module permission system

Features - User Management (Phase 4.1):
- Database: Add user_modules table for fine-grained module permissions
- Database: Add 4 user permissions (view/create/edit/delete) to role_permissions
- Backend: UserService (780 lines) - CRUD with tenant isolation
- Backend: UserController + UserRoutes (648 lines) - 13 API endpoints
- Backend: Batch import users from Excel
- Frontend: UserListPage (412 lines) - list/filter/search/pagination
- Frontend: UserFormPage (341 lines) - create/edit with module config
- Frontend: UserDetailPage (393 lines) - details/tenant/module management
- Frontend: 3 modal components (592 lines) - import/assign/configure
- API: GET/POST/PUT/DELETE /api/admin/users/* endpoints

Architecture Upgrade - Module Permission System:
- Backend: Add getUserModules() method in auth.service
- Backend: Login API returns modules array in user object
- Frontend: AuthContext adds hasModule() method
- Frontend: Navigation filters modules based on user.modules
- Frontend: RouteGuard checks requiredModule instead of requiredVersion
- Frontend: Remove deprecated version-based permission system
- UX: Only show accessible modules in navigation (clean UI)
- UX: Smart redirect after login (avoid 403 for regular users)

Fixes:
- Fix UTF-8 encoding corruption in ~100 docs files
- Fix pageSize type conversion in userService (String to Number)
- Fix authUser undefined error in TopNavigation
- Fix login redirect logic with role-based access check
- Update Git commit guidelines v1.2 with UTF-8 safety rules

Database Changes:
- CREATE TABLE user_modules (user_id, tenant_id, module_code, is_enabled)
- ADD UNIQUE CONSTRAINT (user_id, tenant_id, module_code)
- INSERT 4 permissions + role assignments
- UPDATE PUBLIC tenant with 8 module subscriptions

Technical:
- Backend: 5 new files (~2400 lines)
- Frontend: 10 new files (~2500 lines)
- Docs: 1 development record + 2 status updates + 1 guideline update
- Total: ~4900 lines of code

Status: User management 100% complete, module permission system operational
This commit is contained in:
2026-01-16 13:42:10 +08:00
parent 98d862dbd4
commit 66255368b7
560 changed files with 70424 additions and 52353 deletions

View File

@@ -1,42 +1,47 @@
# 鈭穃<EFBFBD><EFBFBD><EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD>?
> **<2A><><EFBFBD><EFB99D>𧋦嚗?* V1.1
> **<EFBFBD>𥕦遣<EFBFBD><EFBFBD>嚗?* 2025-11-16
# 云原生开发规范
> **文档版本:** V1.1
> **创建日期:** 2025-11-16
> **最后更新:** 2025-12-13 🏆 **Postgres-Only 架构规范新增**
> **<EFBFBD><EFBFBD>鍂撖寡情嚗?* <20><><EFBFBD><EFBFBD><E58CA7>睲犖<E79DB2>?
> **撘箏<EFBFBD><EFBFBD><EFBFBD>** <20>?敹<><EFBFBD><EFBFBD>
> **适用对象:** 所有开发人员
> **强制性:** ✅ 必须遵守
> **维护者:** 架构团队
---
## 📋 文档说明
<EFBFBD><EFBFBD><EFBFBD><EFBFBD>銋劐<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗𠄎erverless SAE + RDS + OSS嚗劐<EFBFBD><EFBFBD>?*隞<><E99A9E><EFBFBD><E996AB>**嚗峕<E59A97><E5B395><EFBFBD><E58A90>⊥芋<E28AA5><EFBFBD>ASL<53><4C>IA<49><41>KB蝑㚁<E89D91><EFBFBD><EFBFBD><EFBFBD><E89084>?
**<EFBFBD><EFBFBD><EFBFBD>園𡢿**嚗?0 <20><><EFBFBD>
**<EFBFBD><EFBFBD>仿<EFBFBD><EFBFBD>?*嚗𡁏<E59A97>甈∩誨<E288A9><E8AAA8><EFBFBD>鈭文<E988AD>
本文档定义云原生环境Serverless SAE + RDS + OSS)下的**代码规范**所有业务模块ASL、AIA、PKB等必须遵守。
**阅读时间**10 分钟
**检查频率**:每次代码提交前
---
## <EFBFBD><EFBFBD> <20><EFBFBD><E8A9A8><EFBFBD>嚗𡁜<E59A97><F0A1819C>典像<E585B8><EFBFBD><E59597>?
> **潃?<3F><EFBFBD><E6BBA9>鞟內嚗?025-11-16 <20>湔鰵嚗?*嚗𡁜像<F0A1819C>啣歇<E595A3>𣂷<EFBFBD>摰峕㟲<E5B395><E39FB2>抅蝖<E68A85>霈暹鴌<E69AB9>滚𦛚
## 🌟 核心原则:复用平台能力
> **⭐ 重要提示2025-11-16 更新)**:平台已提供完整的基础设施服务
> **详细文档**[平台基础设施规划](../09-架构实施/04-平台基础设施规划.md)
### 平台已提供的服务
**业务模块ASL/AIA/PKB/DC等应该复用以下平台能力禁止重复实现**
| <EFBFBD>滚𦛚 | 撖澆<E69296><E6BE86><EFBFBD> | <20><EFBFBD>?| <20><> |
| 服务 | 导入方式 | 用途 | 文档 |
|------|---------|------|------|
| **摮睃<EFBFBD><EFBFBD>滚𦛚** | `import { storage } from '@/common/storage'` | <EFBFBD><EFBFBD>辣銝𠹺<EFBFBD>銝贝蝸 | <20>?撟喳蝱蝥?|
| **<EFBFBD><EFBFBD>蝟餌<EFBFBD>** | `import { logger } from '@/common/logging'` | <EFBFBD><EFBFBD><EFBFBD><EFBFBD>𡝗𠯫敹?| <20>?撟喳蝱蝥?|
| **<EFBFBD>郊隞餃𦛚** | `import { jobQueue } from '@/common/jobs'` | <EFBFBD>踵𧒄<EFBFBD>港遙<EFBFBD>?| <20>?撟喳蝱蝥?|
| **蝻枏<EFBFBD><EFBFBD>滚𦛚** | `import { cache } from '@/common/cache'` | <EFBFBD><EFBFBD><EFBFBD>撘讐<EFBFBD>摮?| <20>?撟喳蝱蝥?|
| **<EFBFBD><EFBFBD> <20><EFBFBD>蝏凋<E89D8F>** | `import { CheckpointService } from '@/common/jobs'` | 隞餃𦛚<EFBFBD><EFBFBD>蝞∠<EFBFBD> | <20>?撟喳蝱蝥改<E89DA5><E694B9><EFBFBD> |
| **<EFBFBD>唳旿摨?* | `import { prisma } from '@/config/database'` | <EFBFBD>唳旿摨𤘪<EFBFBD>雿?| <20>?撟喳蝱蝥?|
| **LLM<EFBFBD><EFBFBD>** | `import { LLMFactory } from '@/common/llm'` | LLM<EFBFBD>鍂 | <20>?撟喳蝱蝥?|
| **存储服务** | `import { storage } from '@/common/storage'` | 文件上传下载 | ✅ 平台级 |
| **日志系统** | `import { logger } from '@/common/logging'` | 标准化日志 | ✅ 平台级 |
| **异步任务** | `import { jobQueue } from '@/common/jobs'` | 长时间任务 | ✅ 平台级 |
| **缓存服务** | `import { cache } from '@/common/cache'` | 分布式缓存 | ✅ 平台级 |
| **🏆 断点续传** | `import { CheckpointService } from '@/common/jobs'` | 任务断点管理 | ✅ 平台级(新) |
| **数据库** | `import { prisma } from '@/config/database'` | 数据库操作 | ✅ 平台级 |
| **LLM能力** | `import { LLMFactory } from '@/common/llm'` | LLM调用 | ✅ 平台级 |
### 示例:正确使用平台服务
### 蝷箔<E89DB7>嚗𡁏迤蝖桐蝙<E6A190>典像<E585B8><EFBFBD><E594B3>?
```typescript
// <EFBFBD>?甇<>嚗𡁶凒<F0A181B6>亙紡<E4BA99>亙像<E4BA99><EFBFBD><E594B3>?import { storage } from '@/common/storage'
// ✅ 正确:直接导入平台服务
import { storage } from '@/common/storage'
import { logger } from '@/common/logging'
import { jobQueue } from '@/common/jobs'
import { prisma } from '@/config/database'
@@ -50,7 +55,8 @@ export class LiteratureService {
// 2. 使用平台日志系统
logger.info('PDF uploaded', { projectId, url })
// 3. 雿輻鍂撟喳蝱<EFBFBD>唳旿摨? const literature = await prisma.aslLiterature.create({
// 3. 使用平台数据库
const literature = await prisma.aslLiterature.create({
data: { projectId, pdfUrl: url }
})
@@ -59,26 +65,36 @@ export class LiteratureService {
}
```
### <EFBFBD>?<3F>躰秤嚗𡁻<E59A97>憭滚<E686AD><E6BB9A>啣像<E595A3><EFBFBD><E59597>?
### ❌ 错误:重复实现平台能力
```typescript
// <EFBFBD>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅<E79285>銝剛䌊撌勗<E6928C><E58B97><EFBFBD><E595A3>?// backend/src/modules/asl/storage/LocalStorage.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99><EFBFBD>
// ❌ 错误:在业务模块中自己实现存储
// backend/src/modules/asl/storage/LocalStorage.ts ← 不应该存在!
export class LocalStorage {
async upload(file: Buffer) {
await fs.writeFile('./uploads/file.pdf', file) // <EFBFBD>?<3F><EFBFBD>摰䂿緵
await fs.writeFile('./uploads/file.pdf', file) // ❌ 重复实现
}
}
// <EFBFBD>?<3F>躰秤嚗𡁜銁銝𡁜𦛚璅<E79285>銝剛䌊撌勗<E6928C><E58B97>唳𠯫敹?// backend/src/modules/asl/logger/logger.ts <20>?銝滚<E98A9D>霂亙<E99C82><E4BA99><EFBFBD>
export const logger = winston.createLogger({...}) // <20>?<3F><EFBFBD>摰䂿緵
// ❌ 错误:在业务模块中自己实现日志
// backend/src/modules/asl/logger/logger.ts ← 不应该存在!
export const logger = winston.createLogger({...}) // ❌ 重复实现
```
**<EFBFBD><EFBFBD>**嚗?- <20>?<3F><EFBFBD><EFBFBD><E99A9E>嚗屸𠗕隞亦輕<E4BAA6>?- <20>?銝滚<E98A9D><E79285>摰䂿緵銝滢<E98A9D><E6BBA2>?- <20>?<3F><EFBFBD>蝏煺<E89D8F><E785BA><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𧋦<EFBFBD>?鈭𤑳垢嚗?- <20>?瘚芾晶撘<E699B6><E69298>烐𧒄<E78390>?
**原因**
- ❌ 重复代码,难以维护
- ❌ 不同模块实现不一致
- ❌ 无法统一切换环境(本地/云端)
- ❌ 浪费开发时间
---
## <EFBFBD>?<3F><EFBFBD><E588BB>𡁏<EFBFBD>嚗㇄O嚗?
### 1. <20><>辣摮睃<E691AE> <20>?
## ✅ 推荐做法DO
### 1. 文件存储 ✅
```typescript
// <EFBFBD>?甇<>嚗帋蝙<E5B88B><EFBFBD><E585B8>冽𡂝鞊<E99E8A>
// ✅ 正确:使用存储抽象层
import { storage } from '@/common/storage/StorageFactory'
export async function uploadFile(file: Buffer, filename: string) {
@@ -87,7 +103,8 @@ export async function uploadFile(file: Buffer, filename: string) {
return url
}
// <EFBFBD>?甇<>嚗鍃xcel <20>湔𦻖隞𤾸<E99A9E>摮䁅圾<E48185>?import * as xlsx from 'xlsx'
// ✅ 正确Excel 直接从内存解析
import * as xlsx from 'xlsx'
export async function importExcel(buffer: Buffer) {
const workbook = xlsx.read(buffer, { type: 'buffer' }) // 内存解析
@@ -96,35 +113,42 @@ export async function importExcel(buffer: Buffer) {
}
```
**<EFBFBD><EFBFBD>眏**嚗?- 摰孵膥<E5ADB5>滚鍳銝滢<E98A9D><EFBCB7><E4BB83>
- <EFBFBD>砍𧑐撘<EFBFBD><EFBFBD><EFBFBD><EFBFBD>煺漣<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?- <20>芸𢆡<E88AB8>寞旿<E5AF9E><EFBFBD><E887AC><EFBFBD><E3979B><EFBFBD>揢摮睃<E691AE><E79D83><EFBFBD>
**理由**
- 容器重启不会丢失文件
- 本地开发和生产环境代码一致
- 自动根据环境变量切换存储方式
---
### 2. <EFBFBD>唳旿摨栞<EFBFBD><EFBFBD>?<3F>?
### 2. 数据库连接 ✅
```typescript
// <EFBFBD>?甇<>嚗帋蝙<E5B88B><EFBFBD><EFBFBD> Prisma Client
// ✅ 正确:使用全局 Prisma Client
import { prisma } from '@/config/database'
export async function createProject(data: any) {
return await prisma.aslScreeningProject.create({ data })
}
// <EFBFBD>?甇<>嚗𡁏鸌<F0A1818F>𤩺<EFBFBD>雿靝蝙<E99D9D><EFBFBD><E585B6>?export async function importLiteratures(literatures: any[]) {
// ✅ 正确:批量操作使用事务
export async function importLiteratures(literatures: any[]) {
return await prisma.$transaction(async (tx) => {
return await tx.aslLiterature.createMany({ data: literatures })
})
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD>摰硺<E691B0>憭滨鍂餈墧𦻖
**理由**
- 全局实例复用连接
- 避免连接数耗尽
- 鈭见𦛚靽肽<EFBFBD><EFBFBD>唳旿銝<EFBFBD><EFBFBD><EFBFBD>?
- 事务保证数据一致性
---
### 3. <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>滨蔭 <20>?
### 3. 环境变量配置 ✅
```typescript
// <EFBFBD>?甇<>嚗𡁶<E59A97><EFBFBD><E98A9D>滨蔭蝞∠<E89D9E>
// ✅ 正确:统一配置管理
// backend/src/config/env.ts
export const config = {
llm: {
@@ -140,19 +164,22 @@ export const config = {
}
}
// <EFBFBD>?甇<>嚗帋蝙<E5B88B><EFBFBD>蝵桀笆鞊?import { config } from '@/config/env'
// ✅ 正确:使用配置对象
import { config } from '@/config/env'
const apiKey = config.llm.apiKey
```
**<EFBFBD><EFBFBD>眏**嚗?- <20>滨蔭<E6BBA8><E894AD>葉蝞∠<E89D9E>
**理由**
- 配置集中管理
- 类型安全
- 便于切换环境
---
### 4. <EFBFBD>踵𧒄<EFBFBD>港遙<EFBFBD><EFBFBD><EFBFBD>?<3F>?
### 4. 长时间任务处理 ✅
```typescript
// <EFBFBD>?甇<>嚗𡁜<E59A97>甇乩遙<E4B9A9>?+ 餈𥕦漲頧株砭
// ✅ 正确:异步任务 + 进度轮询
export async function startScreening(req, res) {
// 1. 创建任务记录
const task = await prisma.aslScreeningTask.create({
@@ -166,8 +193,9 @@ export async function startScreening(req, res) {
// 2. 立即返回任务ID
res.send({ success: true, taskId: task.id })
// 3. <EFBFBD>𤾸蝱撘<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>霂瑟<EFBFBD>嚗? processScreeningAsync(task.id).catch(err => {
console.error('蝑偦<E89D91>劐遙<E58A90>仃韐?', err)
// 3. 后台异步执行(不阻塞请求)
processScreeningAsync(task.id).catch(err => {
console.error('筛选任务失败:', err)
})
}
@@ -184,16 +212,20 @@ export async function getTaskProgress(req, res) {
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD>霂瑟<E99C82><EFBFBD>𧒄嚗𠄎AE暺䁅恕30蝘𡜐<E89D98>
**理由**
- 避免请求超时SAE默认30秒
- 用户体验更好
- 支持批量任务
**<EFBFBD>?摰峕㟲摰噼殿<E599BC><E6AEBF><EFBFBD>?*嚗?霂西<E99C82> [Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>](../02-<2D>𡁶鍂<F0A181B6><EFBFBD>撅?Postgres-Only撘<79>郊隞餃𦛚憭<F0A69B9A><E686AD><EFBFBD><EFBFBD><EFBFBD>.md)嚗<>抅鈭𥟠C Tool C摰峕㟲摰噼殿嚗?
**✨ 完整实践参考**
详见 [Postgres-Only异步任务处理指南](../02-通用能力层/Postgres-Only异步任务处理指南.md)基于DC Tool C完整实践
---
### 5. <EFBFBD><EFBFBD>颲枏枂 <20>?
### 5. 日志输出 ✅
```typescript
// <EFBFBD>?甇<>嚗帋蝙<E5B88B>?logger 颲枏枂<EFBFBD>?stdout
// ✅ 正确:使用 logger 输出到 stdout
import pino from 'pino'
const logger = pino({
@@ -209,7 +241,7 @@ export async function uploadFile(req, res) {
// ... 上传逻辑
}
// <EFBFBD>?甇<>嚗𡁶<E59A97><F0A181B6><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
// ✅ 正确:结构化日志
logger.error({
error: err.message,
stack: err.stack,
@@ -217,13 +249,17 @@ logger.error({
}, 'Upload failed')
```
**<EFBFBD><EFBFBD>眏**嚗?- SAE <20>芸𢆡<E88AB8><F0A286A1><EFBFBD> stdout <20><EFBFBD>
- 蝏𤘪<EFBFBD><EFBFBD>碶噶鈭擧䰻霂<EFBFBD><EFBFBD>?- <20><><EFBFBD><EFBFBD>嚗䔶<E59A97>隡帋腺憭?
**理由**
- SAE 自动采集 stdout 日志
- 结构化便于查询分析
- 集中查看,不会丢失
---
### 6. <EFBFBD>躰秤憭<EFBFBD><EFBFBD> <20>?
### 6. 错误处理 ✅
```typescript
// <EFBFBD>?甇<>嚗𡁶<E59A97><EFBFBD><E98A9D>躰秤憭<E7A7A4><E686AD>
// ✅ 正确:统一错误处理
export async function uploadPdf(req, res) {
try {
const file = await req.file()
@@ -246,14 +282,17 @@ export async function uploadPdf(req, res) {
}
```
**<EFBFBD><EFBFBD>眏**嚗?- <20><EFBFBD><E586BD><EFBFBD><E8A781>见末<E8A781>躰秤靽⊥<E99DBD>
**理由**
- 用户看到友好错误信息
- 日志记录详细错误
- 銝齿𠂔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
- 不暴露内部实现
---
### 7. 銝湔𧒄<EFBFBD><EFBFBD>辣憭<EFBFBD><EFBFBD> <20>?
### 7. 临时文件处理 ✅
```typescript
// <EFBFBD>?甇<>嚗?tmp <20><EFBFBD><E6A180><EFBFBD>蝡见朖<E8A781>𣳇膄
// ✅ 正确:/tmp 目录用完立即删除
import fs from 'fs/promises'
import path from 'path'
@@ -261,7 +300,7 @@ export async function extractPdfText(ossKey: string): Promise<string> {
const tmpPath = path.join('/tmp', `${Date.now()}.pdf`)
try {
// 1. 隞?OSS 銝贝蝸<EFBFBD>?/tmp
// 1. OSS 下载到 /tmp
await storage.download(ossKey, tmpPath)
// 2. 提取文本
@@ -279,43 +318,51 @@ export async function extractPdfText(ossKey: string): Promise<string> {
}
```
**<EFBFBD><EFBFBD>眏**嚗?- /tmp 摰寥<E691B0><E5AFA5><EFBFBD>嚗?12MB嚗?- 摰孵膥<E5ADB5>滚鍳隡𡁏<E99AA1>蝛?- <20><EFBFBD><EFBFBD><E89DA4><EFBFBD>䭾說
**理由**
- /tmp 容量有限512MB
- 容器重启会清空
- 避免磁盘占满
---
## <EFBFBD><EFBFBD> Postgres-Only <EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗?025-12-13 <EFBFBD><EFBFBD>嚗?
## 🏆 Postgres-Only 架构规范2025-12-13 新增)
### 核心理念
**Platform-Only <EFBFBD>**嚗𡁏<E59A97><F0A1818F>劐遙<E58A90>∠恣<E288A0><E681A3><EFBFBD><EFBFBD><EFBFBD>摮睃<E691AE><E79D83>?`platform_schema.job.data`嚗䔶<EFBFBD><EFBFBD>∟”<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>∩縑<EFBFBD><EFBFBD>?
### 隞餃𦛚蝞∠<E89D9E><E288A0><EFBFBD>迤蝖桀<E89D96>瘜?
#### <EFBFBD>?DO: 雿輻鍂 job.data 摮睃<E691AE>隞餃𦛚蝞∠<E89D9E>靽⊥<E99DBD>
**Platform-Only 模式**:所有任务管理信息统一存储在 `platform_schema.job.data`,业务表只存储业务信息。
### 任务管理的正确做法
#### ✅ DO: 使用 job.data 存储任务管理信息
```typescript
// <EFBFBD>?甇<>嚗帋遙<E5B88B><EFBFBD><E28AA5><EFBFBD><EFBFBD><EFBFBD><EFBFBD>靽⊥<E99DBD>摮睃<E691AE><E79D83>?job.data
// ✅ 正确:任务拆分和断点信息存储在 job.data
import { jobQueue } from '@/common/jobs';
import { CheckpointService } from '@/common/jobs/CheckpointService';
// <EFBFBD><EFBFBD><EFBFBD><EFBFBD>⊥𧒄嚗<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>港縑<EFBFBD>?await jobQueue.push('asl:screening:batch', {
// 推送任务时,包含完整信息
await jobQueue.push('asl:screening:batch', {
// 业务信息
taskId: 'xxx',
projectId: 'yyy',
literatureIds: [...],
// <EFBFBD>?隞餃𦛚<E9A483><F0A69B9A><EFBFBD>靽⊥<E99DBD><EFBFBD><E59A97><EFBFBD>典銁 job.data嚗? batchIndex: 3,
// ✅ 任务拆分信息(存储在 job.data
batchIndex: 3,
totalBatches: 20,
startIndex: 150,
endIndex: 200,
// <EFBFBD>?餈𥕦漲餈質葵
// ✅ 进度追踪
processedCount: 0,
successCount: 0,
failedCount: 0,
});
// Worker 銝凋蝙<EFBFBD>?CheckpointService
// Worker 中使用 CheckpointService
const checkpointService = new CheckpointService(prisma);
// 靽嘥<EFBFBD><EFBFBD><EFBFBD><EFBFBD>?job.data
// 保存断点到 job.data
await checkpointService.saveCheckpoint(job.id, {
currentBatchIndex: 5,
currentIndex: 250,
@@ -323,39 +370,52 @@ await checkpointService.saveCheckpoint(job.id, {
totalBatches: 20
});
// <EFBFBD>㰘蝸<EFBFBD><EFBFBD>隞?job.data
// 加载断点从 job.data
const checkpoint = await checkpointService.loadCheckpoint(job.id);
if (checkpoint) {
resumeFrom = checkpoint.currentIndex;
}
```
#### <EFBFBD>?DON'T: <EFBFBD><EFBFBD><EFBFBD>∟”銝剖<EFBFBD><EFBFBD>其遙<EFBFBD>∠恣<EFBFBD><EFBFBD><EFBFBD>?
#### DON'T: 在业务表中存储任务管理信息
```typescript
// <EFBFBD>?<3F>躰秤嚗𡁜銁銝𡁜𦛚銵函<E98AB5> Schema 銝剜溶<E5899C>牐遙<E78990>∠恣<E288A0><E681A3><EFBFBD>畾?model AslScreeningTask {
// ❌ 错误:在业务表的 Schema 中添加任务管理字段
model AslScreeningTask {
id String @id
projectId String
// <EFBFBD>?銝滩<E98A9D>瘛餃<E7989B>餈嗘<E9A488>摮埈挾嚗? totalBatches Int // <20>?摨磰砲<E7A3B0>?job.data 銝? processedBatches Int // <20>?摨磰砲<E7A3B0>?job.data 銝? currentIndex Int // <20>?摨磰砲<E7A3B0>?job.data 銝? checkpointData Json? // <20>?摨磰砲<E7A3B0>?job.data 銝?}
// ❌ 不要添加这些字段!
totalBatches Int // ← 应该在 job.data 中
processedBatches Int // ← 应该在 job.data 中
currentIndex Int // ← 应该在 job.data 中
checkpointData Json? // ← 应该在 job.data 中
}
// <EFBFBD>?<3F>躰秤嚗朞䌊撌勗<E6928C><E58B97>唳鱏<E594B3><EFBFBD><E5AF9E>?class MyCheckpointService {
// ❌ 错误:自己实现断点服务
class MyCheckpointService {
async save(taskId: string) {
await prisma.aslScreeningTask.update({
where: { id: taskId },
data: { checkpointData: {...} } // <EFBFBD>?銝滩<E98A9D>餈蹱甅<E8B9B1>𡄯<EFBFBD>
data: { checkpointData: {...} } // ❌ 不要这样做!
});
}
}
```
**为什么不对?**
- <EFBFBD>?瘥譍葵璅<E79285><E288AA><EFBFBD>瘛餃<E7989B><E9A483><EFBFBD><E8A9A8><EFBFBD><EFBFBD>畾蛛<E795BE><EFBFBD><E99A9E><EFBFBD><EFBFBD>嚗?- <20>?餈嘥<E9A488> DRY <20><EFBFBD>
- <EFBFBD>?餈嘥<E9A488> 3 撅<><EFBFBD><E6B2B2><EFBFBD><EFBFBD>?- <20>?蝏湔擪<E6B994>圈𠗕嚗<F0A09795><EFBFBD><EFBFBD><EFBFBD><E9A489><EFBFBD><EFBFBD>㺿憭𡁜<E686AD>嚗?
- ❌ 每个模块都要添加相同的字段(代码重复)
- ❌ 违反 DRY 原则
- ❌ 违反 3 层架构原则
- ❌ 维护困难(修改逻辑需要改多处)
### 智能阈值判断的规范
#### <EFBFBD>?DO: 摰䂿緵<EFBFBD><EFBFBD><EFBFBD>峕芋撘誩<EFBFBD><EFBFBD>?
#### DO: 实现智能双模式处理
```typescript
const QUEUE_THRESHOLD = 50; // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>?
const QUEUE_THRESHOLD = 50; // 推荐阈值
export async function startTask(items: any[]) {
const useQueue = items.length >= QUEUE_THRESHOLD;
@@ -366,68 +426,84 @@ export async function startTask(items: any[]) {
await jobQueue.push('task:batch', {...});
}
} else {
// <EFBFBD>湔𦻖璅<EFBFBD>嚗𡁜<EFBFBD>隞餃𦛚嚗?50<35><EFBFBD>
processDirectly(items); // 敹恍<EFBFBD><EFBFBD>摨? }
// 直接模式:小任务(<50条
processDirectly(items); // 快速响应
}
}
```
**銝箔<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗?*
- <EFBFBD>?撠譍遙<E8AD8D><E288AA><EFBFBD>摨䈑<E691A8><E48891>𣳇<EFBFBD><F0A3B387>堒辣餈<E8BEA3><E9A488>
- <EFBFBD>?憭找遙<E689BE><EFBFBD><E28ABF><EFBFBD><EFBFBD>𣈲<EFBFBD><F0A388B2><EFBFBD>寧賒隡𩤃<E99AA1>
- <EFBFBD>?<3F><EFBFBD>銝𤾸虾<F0A4BEB8><EFBFBD>批像銵?
#### <20>?DON'T: <20><><EFBFBD>劐遙<E58A90><EFBFBD>韏圈<E99F8F><E59C88>?
**为什么这样做?**
- ✅ 小任务快速响应(无队列延迟)
- ✅ 大任务高可靠(支持断点续传)
- ✅ 性能与可靠性平衡
#### ❌ DON'T: 所有任务都走队列
```typescript
// <EFBFBD>?<3F>躰秤嚗𡁜朖雿?<3F>∟扇敶蓥<E695B6>雿輻鍂<E8BCBB><EFBFBD>
// ❌ 错误即使1条记录也使用队列
export async function startTask(items: any[]) {
// 无论多少数据,都推送到队列
await jobQueue.push('task:batch', items); // <EFBFBD>?撠譍遙<E8AD8D><EFBFBD><E288A9>匧辣餈?}
await jobQueue.push('task:batch', items); // ❌ 小任务会有延迟
}
```
**为什么不对?**
- <EFBFBD>?撠譍遙<E8AD8D><EFBFBD>摨娍<E691A8><EFBFBD><E59A97><EFBFBD><EFBFBD>頧株砭<E6A0AA><EFBFBD>嚗?- <20>?瘚芾晶<E88ABE><EFBFBD><EFBFBD><E99F8F>
- <EFBFBD>?<3F><EFBFBD>雿㯄<E99BBF>撌?
### <20><><EFBFBD>潭綫<E6BDAD>𣂼<EFBFBD>?
| 隞餃𦛚蝐餃<E89D90> | <20><EFBFBD><E588BB><EFBFBD><EFBFBD>?| <20><>眏 |
- ❌ 小任务响应慢(队列有轮询间隔)
- ❌ 浪费队列资源
- ❌ 用户体验差
### 阈值推荐值
| 任务类型 | 推荐阈值 | 理由 |
|---------|---------|------|
| <EFBFBD><EFBFBD>讃蝑偦<EFBFBD>?| 50蝭?| <20><EFBFBD>~7蝘𡜐<E89D98>50蝭㻬5<E3BBAC><35><EFBFBD> |
| <EFBFBD>唳旿<EFBFBD>𣂼<EFBFBD> | 50<35>?| <20>閙辺~5-10蝘𡜐<E89D98>50<35>﹚5<EFB99A><35><EFBFBD> |
| 蝏蠘恣璅<EFBFBD> | 30銝?| <20>蓥葵~10蝘𡜐<E89D98>30銝泠5<E6B3A0><35><EFBFBD> |
| 暺䁅恕 | 50<EFBFBD>?| <20>𡁶鍂<F0A181B6><EFBFBD><E588BB>?|
| 文献筛选 | 50篇 | 单篇~7秒50篇~5分钟 |
| 数据提取 | 50条 | 单条~5-10秒50条~5分钟 |
| 统计模型 | 30个 | 单个~10秒30个~5分钟 |
| 默认 | 50条 | 通用推荐值 |
---
## <EFBFBD>?蝳<><EFBFBD>𡁏<EFBFBD>嚗㇄ON'T嚗?
### 1. <20>砍𧑐<E7A08D><F0A79190>辣摮睃<E691AE> <20>?
## ❌ 禁止做法DON'T
### 1. 本地文件存储 ❌
```typescript
// <EFBFBD>?蝳<>迫嚗𡁏𧋦<F0A1818F><EFBFBD>隞嗥頂蝏笔<E89D8F><E7AC94>?import fs from 'fs'
// ❌ 禁止:本地文件系统存储
import fs from 'fs'
export async function uploadFile(req, res) {
const file = await req.file()
const buffer = await file.toBuffer()
// <EFBFBD>?<3F>躰秤嚗𡁜捆<F0A1819C><EFBFBD><E588B8>臭腺憭? fs.writeFileSync('./uploads/file.pdf', buffer)
// ❌ 错误:容器重启丢失
fs.writeFileSync('./uploads/file.pdf', buffer)
res.send({ url: '/uploads/file.pdf' })
}
// <EFBFBD>?蝳<>迫嚗帋<E59A97>韏𡝗𧋦<F0A19D97>啗楝敺?const uploadDir = '/var/app/uploads'
// ❌ 禁止:依赖本地路径
const uploadDir = '/var/app/uploads'
const filePath = path.join(uploadDir, filename)
```
**<EFBFBD><EFBFBD>**嚗?- 摰孵膥<E5ADB5>滚鍳<E6BB9A>𡝗<EFBFBD>摰孵<E691B0><E5ADB5><EFBFBD>辣銝
**问题**
- 容器重启或扩容后文件丢失
- 多实例间无法共享文件
- 磁盘空间有限
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗帋蝙<E5B88B>?`storage.upload()` 銝𠹺<EFBFBD><EFBFBD>?OSS
**正确做法**:使用 `storage.upload()` 上传到 OSS
---
### 2. <EFBFBD><EFBFBD><EFBFBD>蝻枏<EFBFBD> <20>?
### 2. 内存缓存 ❌
```typescript
// <EFBFBD>?蝳<>迫嚗𡁜<E59A97>摮条<E691AE>摮矋<E691AE>憭𡁜<E686AD>靘衤<E99D98><E8A1A4>曹澈嚗?const cache = new Map<string, any>()
// ❌ 禁止:内存缓存(多实例不共享)
const cache = new Map<string, any>()
export async function getProject(id: string) {
// <EFBFBD>?<3F>躰秤嚗𡁏<E59A97>摰孵<E691B0><E5ADB5><EFBFBD>摰硺<E691B0>霂颱<E99C82><E9A2B1><EFBFBD>摮? if (cache.has(id)) {
// ❌ 错误:扩容后其他实例读不到缓存
if (cache.has(id)) {
return cache.get(id)
}
@@ -436,32 +512,42 @@ export async function getProject(id: string) {
return project
}
// <EFBFBD>?蝳<>迫嚗𡁜<E59A97><EFBFBD><E69285><EFBFBD>摮睃<E691AE><E79D83><EFBFBD>?let taskStatus = {} // 憭𡁜<E686AD>靘衤<E99D98><E8A1A4>峕郊
// ❌ 禁止:全局变量存储状态
let taskStatus = {} // 多实例不同步
```
**<EFBFBD><EFBFBD>**嚗?- 憭𡁜<E686AD>靘钅𡢿<E99285>唳旿銝滚<E98A9D>甇?- <20>拙捆<E68B99>𡒊<EFBFBD>摮睃仃<E79D83>?- <20><><EFBFBD><EFBFBD>删鍂銝滚虾<E6BB9A>?
**甇<><EFBFBD>𡁏<EFBFBD>**嚗帋蝙<E5B88B>?Redis <20>𡝗㺭<F0A19D97><EFBFBD>
**问题**
- 多实例间数据不同步
- 扩容后缓存失效
- 内存占用不可控
**正确做法**:使用 Redis 或数据库
---
### 3. 蝖祉<EFBFBD><EFBFBD><EFBFBD><EFBFBD>蝵?<3F>?
### 3. 硬编码配置 ❌
```typescript
// <EFBFBD>?蝳<>迫嚗𡁶蝻𣇉<E89DBB>
// ❌ 禁止:硬编码
const LLM_API_KEY = 'sk-xxx'
const DB_HOST = '192.168.1.100'
const OSS_BUCKET = 'my-bucket'
// <EFBFBD>?蝳<>迫嚗𡁜<E59A97>甇餌垢<E9A48C>?app.listen(3001)
// ❌ 禁止:写死端口
app.listen(3001)
// <EFBFBD>?蝳<>迫嚗𡁜<E59A97>甇餃<E79487><E9A483>?const baseUrl = 'https://api.example.com'
// ❌ 禁止:写死域名
const baseUrl = 'https://api.example.com'
```
**<EFBFBD><EFBFBD>**嚗?- <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>
**问题**
- 无法切换环境
- 安全风险(密钥泄露)
- 部署困难
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?雿輻鍂<E8BCBB><EFBFBD><E887AC><EFBFBD>
**正确做法**
```typescript
// ✅ 使用环境变量
const apiKey = process.env.LLM_API_KEY
const port = process.env.PORT || 3001
app.listen(port)
@@ -469,99 +555,124 @@ app.listen(port)
---
### 4. <EFBFBD>峕郊<EFBFBD>蹂遙<EFBFBD>?<3F>?
### 4. 同步长任务 ❌
```typescript
// <20>?蝳<>迫嚗𡁜<E59A97>甇亙<E79487><E4BA99><EFBFBD>鵭隞餃𦛚
// ❌ 禁止:同步处理长任务
export async function screenLiteratures(req, res) {
const literatures = await prisma.aslLiterature.findMany({...})
// <EFBFBD>?<3F>躰秤嚗?00蝭<30><EFBFBD><EFBFBD>餈?0蝘? for (const lit of literatures) {
await llmScreening(lit) // 瘥讐<E798A5>2-3蝘? }
// ❌ 错误100篇可能超过30秒
for (const lit of literatures) {
await llmScreening(lit) // 每篇2-3秒
}
res.send({ success: true }) // 可能已经超时
}
// <EFBFBD>?蝳<>迫嚗𡁏瓷<F0A1818F><EFBFBD><E39591><EFBFBD><E597A1>?const result = await axios.get(url) // <20><EFBFBD>瘞訾<E7989E>蝑匧<E89D91>
// ❌ 禁止:没有超时保护
const result = await axios.get(url) // 可能永久等待
```
**<EFBFBD><EFBFBD>**嚗?- SAE 霂瑟<E99C82><EFBFBD>𧒄 30 蝘?- <20>滨垢蝑匧<E89D91><E58CA7>園𡢿餈<F0A1A2BF>
**问题**
- SAE 请求超时 30 秒
- 前端等待时间过长
- 无法显示进度
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗𡁜<E59A97>甇乩遙<E4B9A9>?+ 餈𥕦漲頧株砭嚗<E7A0AD><E59A97> DO 蝚?<3F><EFBFBD>
**正确做法**:异步任务 + 进度轮询(见 DO 第4条
---
### 5. <EFBFBD>砍𧑐<EFBFBD><EFBFBD><EFBFBD><EFBFBD><20>?
### 5. 本地日志文件 ❌
```typescript
// <20>?蝳<>迫嚗𡁜<E59A97><F0A1819C>交𧋦<E4BAA4><EFBFBD>隞?import fs from 'fs'
// ❌ 禁止:写入本地文件
import fs from 'fs'
export function logError(error: Error) {
// <EFBFBD>?<3F>躰秤嚗𡁜捆<F0A1819C><EFBFBD><E588B8>臭腺憭梧<E686AD><E6A2A7><EFBFBD><E4ADBE><EFBFBD><EFBFBD><EFBFBD>
// ❌ 错误:容器重启丢失,无法集中查看
fs.appendFileSync('/var/log/app.log', error.message + '\n')
}
// <EFBFBD>?蝳<>迫嚗帋蝙<E5B88B>?console.log <EFBFBD><EFBFBD><EFBFBD>?logger
console.log('User logged in') // <EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>嚗屸𠗕隞交䰻霂?```
// ❌ 禁止:使用 console.log 而不用 logger
console.log('User logged in') // 无结构化,难以查询
```
**<EFBFBD><EFBFBD>**嚗?- 摰孵膥<E5ADB5>滚鍳<E6BB9A><EFBFBD>
- 憭𡁜<E686AD>靘𧢲𠯫敹堒<E695B9><E5A092>?- <20><EFBFBD><E4ADBE><EFBFBD><EFBFBD><E89189><EFBFBD>
**问题**
- 容器重启日志丢失
- 多实例日志分散
- 无法集中分析
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?颲枏枂<E69E8F>?stdout嚗䔶蝙<E494B6>?logger
**正确做法**
```typescript
// ✅ 输出到 stdout使用 logger
logger.info({ userId, action: 'login' }, 'User logged in')
```
---
### 6. <EFBFBD>啣遣<EFBFBD>唳旿摨栞<EFBFBD><EFBFBD>?<3F>?
### 6. 新建数据库连接 ❌
```typescript
// <20>?蝳<>迫嚗𡁏<E59A97>甈∟窈瘙<E7AA88>鰵撱箄<E692B1><E7AE84>?import { PrismaClient } from '@prisma/client'
// ❌ 禁止:每次请求新建连接
import { PrismaClient } from '@prisma/client'
export async function getProjects(req, res) {
// <EFBFBD>?<3F>躰秤嚗𡁏<E59A97>甈⊥鰵撱綽<E692B1>餈墧𦻖<E5A2A7>唳𠂔憓? const prisma = new PrismaClient()
// ❌ 错误:每次新建,连接数暴增
const prisma = new PrismaClient()
const projects = await prisma.aslScreeningProject.findMany()
await prisma.$disconnect()
res.send(projects)
}
// <EFBFBD>?蝳<>迫嚗𡁶凒<F0A181B6>乩蝙<E4B9A9>?pg <20><EFBFBD>隞㚚店<E39A9A>?import { Pool } from 'pg'
// ❌ 禁止:直接使用 pg 或其他驱动
import { Pool } from 'pg'
const pool = new Pool({ connectionString: process.env.DATABASE_URL })
```
**<EFBFBD><EFBFBD>**嚗?- 餈墧𦻖<E5A2A7>啣翰<E595A3><EFBFBD>堒偷嚗㇌DS<44>𣂼<EFBFBD> 400 餈墧𦻖嚗?- <20><EFBFBD>雿𦒘<E99BBF><EFBFBD><E59A97><EFBFBD>亙遣蝡贝<E89DA1>埈𧒄嚗?- 韏<><E99F8F>瘚芾晶
**问题**
- 连接数快速耗尽RDS限制 400 连接)
- 性能低下(连接建立耗时)
- 资源浪费
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?雿輻鍂<E8BCBB><EFBFBD> Prisma Client
**正确做法**
```typescript
// ✅ 使用全局 Prisma Client
import { prisma } from '@/config/database'
const projects = await prisma.aslScreeningProject.findMany()
```
---
### 7. 敹賜裦<EFBFBD>躰秤 <20>?
### 7. 忽略错误 ❌
```typescript
// <20>?蝳<>迫嚗𡁶征<F0A181B6>?catch
// ❌ 禁止:空的 catch
try {
await storage.upload(key, buffer)
} catch (error) {
// <EFBFBD>?<3F>躰秤鋡怠<E98BA1><E680A0><EFBFBD><E39A81><EFBFBD><E4ADBE>埝䰻
// ❌ 错误被吞掉,无法排查
}
// <EFBFBD>?蝳<>迫嚗帋<E59A97><EFBFBD><E686AD> Promise rejection
// ❌ 禁止:不处理 Promise rejection
processAsync(taskId) // 没有 .catch()
// <EFBFBD>?蝳<>迫嚗朞<E59A97><E69C9E>墧芋蝟𢠃<E89D9F>霂?catch (error) {
// ❌ 禁止:返回模糊错误
catch (error) {
res.status(500).send({ error: 'Something went wrong' })
// 用户不知道什么错了,如何解决
}
```
**<EFBFBD><EFBFBD>**嚗?- <20>躰秤<E8BAB0><EFBFBD>餈質葵
- <20><EFBFBD>雿㯄<E99BBF>撌?- <20>埝䰻<E59F9D>圈𠗕
**问题**
- 错误无法追踪
- 用户体验差
- 排查困难
**<EFBFBD><EFBFBD>𡁏<EFBFBD>**嚗?```typescript
// <20>?霈啣<E99C88><E595A3><EFBFBD> + <20>见末<E8A781>躰秤靽⊥<E99DBD>
**正确做法**
```typescript
// ✅ 记录日志 + 友好错误信息
try {
await storage.upload(key, buffer)
} catch (error) {
@@ -578,39 +689,57 @@ try {
---
## <EFBFBD><EFBFBD><><E99A9E>摰⊥䰻璉<E4B0BB><E79289><EFBFBD><E4BAA4>?
<EFBFBD>?*<2A>𣂷漱隞<E6BCB1><E99A9E><EFBFBD>?*嚗諹窈<E8ABB9>鞾★璉<E29885><E79289><EFBFBD>
## 🔍 代码审查检查清单
在**提交代码前**,请逐项检查:
### 文件存储
- [ ] <20>臬炏雿輻鍂 `storage.upload()` <20><EFBFBD> `fs.writeFile()`嚗?- [ ] Excel <20>臬炏隞𤾸<E99A9E>摮䁅圾<E48185><EFBFBD><E7909C><EFBFBD>靽嘥<E99DBD><E598A5>唳𧋦<E594B3><EFBFBD>
- [ ] 是否使用 `storage.upload()` 而非 `fs.writeFile()`
- [ ] Excel 是否从内存解析,而非保存到本地?
- [ ] PDF 提取后是否立即删除临时文件?
### <EFBFBD>唳旿摨?- [ ] <20>臬炏雿輻鍂<E8BCBB><EFBFBD> `prisma` 摰硺<E691B0>嚗?- [ ] <20>臬炏<E887AC><EFBFBD><E8B8B9>典儐<E585B8>臭葉<E887AD><EFBFBD><E689AF>閙辺<E99699>亥砭嚗<E7A0AD><E59A97>摨磰砲<E7A3B0><EFBFBD><E5AFA5><EFBFBD>嚗?- [ ] <20><EFBFBD><E5AFA5><EFBFBD><E6BBA2>臬炏雿輻鍂鈭见𦛚嚗?
### 数据库
- [ ] 是否使用全局 `prisma` 实例?
- [ ] 是否避免在循环中执行单条查询?(应该批量操作)
- [ ] 批量操作是否使用事务?
### 配置管理
- [ ] <20>臬炏<E887AC><E7828F><EFBFBD><EFBFBD>蝵桅<E89DB5>隞?`process.env` 霂餃<E99C82>嚗?- [ ] <20>臬炏瘝⊥<E7989D>蝖祉<E89D96><E7A589><EFBFBD><EFBFBD> IP<49><50><EFBFBD><EFBFBD><EFBFBD><E6BABB><EFBFBD><EFBFBD><EFBFBD>
- [ ] 是否所有配置都从 `process.env` 读取?
- [ ] 是否没有硬编码的 IP、域名、密钥
- [ ] `.env.example` 是否已更新?
### <EFBFBD>踵𧒄<EFBFBD>港遙<EFBFBD>?- [ ] 頞<><E9A09E> 10 蝘垍<E89D98>隞餃𦛚<E9A483>臬炏<E887AC>嫣蛹撘<E89BB9>郊嚗?- [ ] <20>臬炏<E887AC>𣂷<EFBFBD><EFBFBD><E988AD>摨行䰻霂𦻖<EFBCB8><F0A6BB96><EFBFBD>
- [ ] <20>滨垢<E6BBA8>臬炏<E887AC>㕑蔭霂<E99C82> WebSocket <20><EFBFBD>餈𥕦漲嚗?
### 长时间任务
- [ ] 超过 10 秒的任务是否改为异步?
- [ ] 是否提供了进度查询接口?
- [ ] 前端是否有轮询或 WebSocket 获取进度?
### 日志
- [ ] <20>臬炏雿輻鍂 `logger` <20><EFBFBD> `console.log`嚗?- [ ] <20><EFBFBD><E4BA99>臬炏蝏𤘪<E89D8F><F0A498AA><EFBFBD>JSON<4F><EFBFBD>嚗㚁<E59A97>
- [ ] 是否使用 `logger` 而非 `console.log`
- [ ] 日志是否结构化JSON格式
- [ ] 是否记录了关键操作userId、action
### 错误处理
- [ ] <20><><EFBFBD>?async <20>賣㺭<E8B3A3>臬炏<E887AC>?try-catch嚗?- [ ] <20>臬炏霈啣<E99C88><EFBFBD>祕蝏<E7A595><E89D8F>霂舀𠯫敹梹<E695B9>
- [ ] <20>臬炏餈𥪜<E9A488><EFBFBD><E988AD>憟賜<E6869F><E8B39C>躰秤靽⊥<E99DBD>嚗?
- [ ] 所有 async 函数是否有 try-catch
- [ ] 是否记录了详细错误日志?
- [ ] 是否返回了友好的错误信息?
### 临时文件
- [ ] `/tmp` 目录使用后是否立即删除?
- [ ] <20>臬炏<E887AC>?`finally` <20>𦯀葉皜<E89189><E79A9C>嚗?- [ ] <20>臬炏<E887AC><EFBFBD><E8B8B9><EFBFBD>靘肽<E99D98> `/tmp`嚗?
- [ ] 是否在 `finally` 块中清理?
- [ ] 是否避免长期依赖 `/tmp`
---
## <EFBFBD>㴓 敹恍<E695B9>蠘䌊璉<E48C8A>嚗?<3F><><EFBFBD>嚗?
**餈鞱<E9A488>隞乩<E99A9E><E4B9A9>賭誘嚗峕<E59A97><E5B395>乩誨<E4B9A9><E8AAA8><EFBFBD>臬炏<E887AC><EFBFBD>閫?*嚗?
## 🎯 快速自检5分钟
**运行以下命令,检查代码中是否有违规**
```bash
# 检查是否有本地文件存储
grep -r "fs.writeFile\|fs.appendFile" backend/src/modules/
# <EFBFBD><EFBFBD>交糓<EFBFBD><EFBFBD>蝖祉<EFBFBD><EFBFBD><EFBFBD><EFBFBD>蝵?grep -r "sk-\|http://\|192.168" backend/src/modules/
# 检查是否有硬编码配置
grep -r "sk-\|http://\|192.168" backend/src/modules/
# 检查是否有新建 Prisma 连接
grep -r "new PrismaClient" backend/src/modules/
@@ -619,14 +748,15 @@ grep -r "new PrismaClient" backend/src/modules/
grep -r "console.log" backend/src/modules/
```
**<EFBFBD><EFBFBD>蝏𤘪<EFBFBD>**嚗𡁏<E59A97><F0A1818F><EFBFBD><E39787><EFBFBD>霂亥<E99C82><E4BAA5>?**0 銝芸龪<E88AB8>?*
**预期结果**:所有检查应该返回 **0 个匹配**
---
## <EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD>獢?
- [鈭穃<E988AD><E7A983><EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD><EFBFBD>㻩(../09-<2D><EFBFBD>摰墧鴌/03-鈭穃<E988AD><E7A983><EFBFBD>蝵脫沲<E884AB><E6B2B2><EFBFBD><EFBFBD>?md) - <20><>鉄摰峕㟲隞<E39FB2><E99A9E>蝷箔<E89DB7>
## 📚 参考文档
- [云原生部署架构指南](../09-架构实施/03-云原生部署架构指南.md) - 包含完整代码示例
- [前后端模块化架构设计-V2](../00-系统总体设计/前后端模块化架构设计-V2.md) - 架构总纲
- [<EFBFBD>唳旿摨栞挽霈∟<EFBFBD><EFBFBD><EFBFBD>(./01-<2D>唳旿摨栞挽霈∟<E99C88><E2889F>?md)
- [数据库设计规范](./01-数据库设计规范.md)
- [API设计规范](./02-API设计规范.md)
- [代码规范](./05-代码规范.md)
- [Git提交规范](./06-Git提交规范.md)
@@ -635,13 +765,14 @@ grep -r "console.log" backend/src/modules/
## 📝 更新日志
| <EFBFBD><EFBFBD> | <20><>𧋦 | <20>䀹凒<E480B9><E58792>捆 | 蝏湔擪<E6B994>?|
| 日期 | 版本 | 变更内容 | 维护者 |
|------|------|---------|--------|
| 2025-11-16 | V1.0 | <EFBFBD>𥕦遣<EFBFBD><EFBFBD>﹝嚗<EFBFBD><EFBFBD>銋劐<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>𤏸<EFBFBD><EFBFBD>?| <20><EFBFBD><E59786><EFBFBD> |
| 2025-11-16 | V1.0 | 创建文档,定义云原生开发规范 | 架构团队 |
---
**文档维护者:** 架构团队
**最后更新:** 2025-11-16
**<EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>** <20>?撌脣<E6928C><E884A3>?
**撘箏<EFBFBD><EFBFBD><EFBFBD>嚗?* <20>?<3F><><EFBFBD>劐誨<E58A90><E8AAA8><EFBFBD>鈭文<E988AD><EFBFBD>◆璉<E29786><E79289>?
**文档状态:** ✅ 已完成
**强制执行:** ✅ 所有代码提交前必须检查