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:
@@ -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>銝W仃<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>碶噶鈭擧䰻霂W<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>辣銝W仃
|
||||
**问题**:
|
||||
- 容器重启或扩容后文件丢失
|
||||
- 多实例间无法共享文件
|
||||
- 磁盘空间有限
|
||||
|
||||
**甇<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>銝W仃
|
||||
- 憭𡁜<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>摨行䰻霂X𦻖<EFBCB8><F0A6BB96><EFBFBD>
|
||||
- [ ] <20>滨垢<E6BBA8>臬炏<E887AC>㕑蔭霂X<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>a<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>?
|
||||
**文档状态:** ✅ 已完成
|
||||
**强制执行:** ✅ 所有代码提交前必须检查
|
||||
|
||||
|
||||
Reference in New Issue
Block a user