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,8 +1,9 @@
# 代码规范
> **鐗堟湰锛?* v1.0
> **鍒涘缓鏃ユ湡锛?* 2025-10-10
> **閫傜敤鑼冨洿锛?* 鍓嶇<E98D93>锛圧eact/TypeScript锛? 鍚庣<E98D9A>锛圢ode.js/TypeScript锛?
> **版本:** v1.0
> **创建日期:** 2025-10-10
> **适用范围:** 前端React/TypeScript+ 后端Node.js/TypeScript
---
## 📋 目录
@@ -17,26 +18,29 @@
---
## 馃専 骞冲彴鑳藉姏浣跨敤瑙勮寖锛?025-11-16 鏂板<EFBFBD>锛?
> **猸?閲嶈<E996B2>鎻愮ず**锛氬钩鍙板凡鎻愪緵瀹屾暣鐨勫熀纭€璁炬柦鏈嶅姟
> **璇︾粏瑙勮寖**锛歔浜戝師鐢熷紑鍙戣<E98D99>鑼僝(./08-浜戝師鐢熷紑鍙戣<E98D99>鑼?md)
## 🌟 平台能力使用规范2025-11-16 新增)
> **⭐ 重要提示**:平台已提供完整的基础设施服务
> **详细规范**[云原生开发规范](./08-云原生开发规范.md)
> **详细文档**[平台基础设施规划](../09-架构实施/04-平台基础设施规划.md)
### 蹇呴』澶嶇敤鐨勫钩鍙版湇鍔?
**涓氬姟妯″潡锛圓SL/AIA/PKB/DC绛夛級绂佹<E7BB82>閲嶅<E996B2>瀹炵幇浠ヤ笅鍔熻兘锛?*
### 必须复用的平台服务
| 鏈嶅姟 | 瀵煎叆鏂瑰紡 | 鐢ㄩ€?|
**业务模块ASL/AIA/PKB/DC等禁止重复实现以下功能**
| 服务 | 导入方式 | 用途 |
|------|---------|------|
| **存储服务** | `import { storage } from '@/common/storage'` | 文件上传下载 |
| **鏃ュ織绯荤粺** | `import { logger } from '@/common/logging'` | 鏍囧噯鍖栨棩蹇?|
| **寮傛<EFBFBD>浠诲姟** | `import { jobQueue } from '@/common/jobs'` | 闀挎椂闂翠换鍔?|
| **缂撳瓨鏈嶅姟** | `import { cache } from '@/common/cache'` | 鍒嗗竷寮忕紦瀛?|
| **鏁版嵁搴?* | `import { prisma } from '@/config/database'` | 鏁版嵁搴撴搷浣?|
| **日志系统** | `import { logger } from '@/common/logging'` | 标准化日志 |
| **异步任务** | `import { jobQueue } from '@/common/jobs'` | 长时间任务 |
| **缓存服务** | `import { cache } from '@/common/cache'` | 分布式缓存 |
| **数据库** | `import { prisma } from '@/config/database'` | 数据库操作 |
| **LLM能力** | `import { LLMFactory } from '@/common/llm'` | LLM调用 |
---
### 鉁?姝g‘绀轰緥锛氫娇鐢ㄥ钩鍙版湇鍔?
### ✅ 正确示例:使用平台服务
```typescript
// backend/src/modules/asl/services/literatureService.ts
import { storage } from '@/common/storage'
@@ -54,7 +58,8 @@ export class LiteratureService {
// 2. 使用平台日志系统
logger.info('PDF uploaded', { projectId, url })
// 3. 浣跨敤骞冲彴鏁版嵁搴? const literature = await prisma.aslLiterature.create({
// 3. 使用平台数据库
const literature = await prisma.aslLiterature.create({
data: { projectId, pdfUrl: url, pdfFileSize: pdfBuffer.length }
})
@@ -65,7 +70,8 @@ export class LiteratureService {
}
async startScreening(projectId: string, literatureIds: string[]) {
// 5. 浣跨敤骞冲彴寮傛<EFBFBD>浠诲姟锛堥暱鏃堕棿浠诲姟蹇呴』寮傛<EFBFBD>锛? const job = await jobQueue.push('asl:screening', {
// 5. 使用平台异步任务(长时间任务必须异步)
const job = await jobQueue.push('asl:screening', {
projectId,
literatureIds
})
@@ -78,44 +84,54 @@ export class LiteratureService {
---
### 鉂?閿欒<E996BF>绀轰緥锛氶噸澶嶅疄鐜板钩鍙拌兘鍔?
### ❌ 错误示例:重复实现平台能力
```typescript
// 鉂?閿欒<E996BF>锛氬湪涓氬姟妯″潡涓<E6BDA1>嚜宸卞疄鐜板瓨鍌?// backend/src/modules/asl/storage/LocalStorage.ts 鈫?涓嶅簲璇ュ瓨鍦<E793A8>
// ❌ 错误:在业务模块中自己实现存储
// backend/src/modules/asl/storage/LocalStorage.ts ← 不应该存在!
import fs from 'fs'
export class LocalStorage {
async upload(file: Buffer) {
await fs.writeFile('./uploads/file.pdf', file) // 鉂?閲嶅<E996B2>瀹炵幇
await fs.writeFile('./uploads/file.pdf', file) // ❌ 重复实现
return '/uploads/file.pdf'
}
}
// 鉂?閿欒<E996BF>锛氬湪涓氬姟妯″潡涓<E6BDA1>嚜宸卞疄鐜版棩蹇?// backend/src/modules/asl/logger/logger.ts 鈫?涓嶅簲璇ュ瓨鍦<E793A8>
// ❌ 错误:在业务模块中自己实现日志
// backend/src/modules/asl/logger/logger.ts ← 不应该存在!
import winston from 'winston'
export const logger = winston.createLogger({...}) // 鉂?閲嶅<E996B2>瀹炵幇
export const logger = winston.createLogger({...}) // ❌ 重复实现
// 鉂?閿欒<E996BF>锛氭瘡娆℃柊寤烘暟鎹<E69A9F>簱杩炴帴
// ❌ 错误:每次新建数据库连接
import { PrismaClient } from '@prisma/client'
export function getUser() {
const prisma = new PrismaClient() // 鉂?杩炴帴娉勬紡
const prisma = new PrismaClient() // ❌ 连接泄漏
return prisma.user.findMany()
}
```
**为什么错误?**
- 鉂?閲嶅<E996B2>爜锛岄毦浠ョ淮鎶?- 鉂?涓嶅悓妯″潡瀹炵幇涓嶄竴鑷?- 鉂?鏃犳硶缁熶竴鍒囨崲鐜<E5B4B2><E9909C>锛堟湰鍦?浜戠<E6B59C>锛?- 鉂?娴<>垂寮€鍙戞椂闂?- 鉂?浜戠<E6B59C>閮ㄧ讲浼氬け璐ワ紙Serverless闄愬埗锛?
- ❌ 重复代码,难以维护
- ❌ 不同模块实现不一致
- ❌ 无法统一切换环境(本地/云端)
- ❌ 浪费开发时间
- ❌ 云端部署会失败Serverless限制
---
### 文件上传规范
```typescript
// 鉁?姝g‘锛氫娇鐢ㄥ瓨鍌ㄦ娊璞″眰
// ✅ 正确:使用存储抽象层
const url = await storage.upload('asl/pdf/123.pdf', buffer)
// 鉂?閿欒<E996BF>锛氱洿鎺ユ搷浣滄枃浠剁郴缁?fs.writeFileSync('./uploads/123.pdf', buffer) // Serverless瀹瑰櫒閲嶅惎浼氫涪澶?
// 鉂?閿欒<E996BF>锛氱‖缂栫爜瀛樺偍璺<E5818D>
// ❌ 错误:直接操作文件系统
fs.writeFileSync('./uploads/123.pdf', buffer) // Serverless容器重启会丢失
// ❌ 错误:硬编码存储路径
const filePath = 'D:/uploads/123.pdf' // Windows路径Linux无法运行
```
@@ -124,7 +140,7 @@ const filePath = 'D:/uploads/123.pdf' // Windows路径Linux无法运行
### 异步任务规范
```typescript
// 鉁?姝g‘锛氶暱鏃堕棿浠诲姟锛?10绉掞級蹇呴』寮傛<E5AFAE>澶勭悊
// ✅ 正确:长时间任务(>10秒必须异步处理
app.post('/screening/start', async (req, res) => {
const job = await jobQueue.push('asl:screening', data)
res.send({ jobId: job.id }) // 立即返回,不等待完成
@@ -136,26 +152,29 @@ app.get('/screening/jobs/:id', async (req, res) => {
res.send({ status: job.status, progress: job.progress })
})
// 鉂?閿欒<E996BF>锛氬悓姝ョ瓑寰呴暱鏃堕棿浠诲姟
// ❌ 错误:同步等待长时间任务
app.post('/screening/start', async (req, res) => {
const results = await processAllLiteratures(data) // <EFBFBD>兘闇€瑕?0鍒嗛挓
const results = await processAllLiteratures(data) // 可能需要10分钟
res.send({ results }) // Serverless 30秒超时
})
```
---
### 鏁版嵁搴撹繛鎺ヨ<EFBFBD>鑼?
### 数据库连接规范
```typescript
// 鉁?姝g‘锛氫娇鐢ㄥ叏灞€Prisma瀹炰緥
// ✅ 正确:使用全局Prisma实例
import { prisma } from '@/config/database'
export async function getUsers() {
return await prisma.user.findMany()
}
// 鉂?閿欒<E996BF>锛氭瘡娆℃柊寤哄疄渚?export async function getUsers() {
const prisma = new PrismaClient() // 杩炴帴鏁拌€楀敖锛? return await prisma.user.findMany()
// ❌ 错误:每次新建实例
export async function getUsers() {
const prisma = new PrismaClient() // 连接数耗尽!
return await prisma.user.findMany()
}
```
@@ -164,44 +183,52 @@ export async function getUsers() {
### 日志规范
```typescript
// 鉁?姝g‘锛氫娇鐢ㄥ钩鍙版棩蹇楃郴缁?import { logger } from '@/common/logging'
// ✅ 正确:使用平台日志系统
import { logger } from '@/common/logging'
logger.info('Operation successful', { userId, action: 'upload' })
logger.error('Operation failed', { error: err.message, userId })
// 鉂?閿欒<E996BF>锛氫娇鐢╟onsole.log
console.log('Operation successful') // 鏃犳硶闆嗕腑鏀堕泦锛岄毦浠ユ煡璇?
// 鉂?閿欒<E996BF>锛氬啓鏈<E59593>湴鏃ュ織鏂囦欢
fs.appendFileSync('./app.log', 'Operation successful') // Serverless涓嶆敮鎸?```
// ❌ 错误使用console.log
console.log('Operation successful') // 无法集中收集,难以查询
// ❌ 错误:写本地日志文件
fs.appendFileSync('./app.log', 'Operation successful') // Serverless不支持
```
---
## 通用规范
### 代码风格
- ?ESLint鍜孭rettier缁熶竴浠g爜椋庢牸
- ??<EFBFBD>?- ?<EFBFBD>?`'`
- ?<EFBFBD>?- ?<EFBFBD>ч100<EFBFBD>
- ?彿<EFBFBD>°
- ✅ 使用ESLint和Prettier统一代码风格
- ✅ 缩进2个空格
- ✅ 字符串:优先使用单引号 `'`
- ✅ 行尾:不加分号(除非必要)
- ✅ 单行最大长度100字符
- ✅ 使用尾随逗号(对象、数组)
### 文件组织
- ?<EFBFBD><EFBFBD>??- ?稿<EFBFBD>
- ?barrel exports锛坕ndex.ts锛?- ??
- ✅ 一个文件一个组件/类
- ✅ 相关文件放在同一目录
- ✅ 使用barrel exportsindex.ts
- ✅ 测试文件与源文件同目录
```
src/
├── components/
鈹? 鈹溾攢鈹€ Button/
鈹? 鈹? 鈹溾攢鈹€ Button.tsx
鈹? 鈹? 鈹溾攢鈹€ Button.test.tsx
鈹? 鈹? 鈹溾攢鈹€ Button.styles.ts
鈹? 鈹? 鈹斺攢鈹€ index.ts # export { Button } from './Button'
│ ├── Button/
│ │ ├── Button.tsx
│ │ ├── Button.test.tsx
│ │ ├── Button.styles.ts
│ │ └── index.ts # export { Button } from './Button'
```
### 代码注释
- 鉁?澶嶆潅閫昏緫蹇呴』娉ㄩ噴
- 鉁?鍏<>叡API蹇呴』娉ㄩ噴
- 鉁?閬垮厤鏃犵敤娉ㄩ噴
- 鉁?浣跨敤JSDoc鏍煎紡
- ✅ 复杂逻辑必须注释
- ✅ 公共API必须注释
- ✅ 避免无用注释
- ✅ 使用JSDoc格式
---
@@ -209,7 +236,7 @@ src/
### 类型定义
**鉁?鎺ㄨ崘锛?*
**✅ 推荐:**
```typescript
// 使用interface定义对象结构
interface User {
@@ -228,14 +255,16 @@ enum UserRole {
}
```
**鉂?閬垮厤锛?*
**❌ 避免:**
```typescript
// 不要使用any
function process(data: any) { // 鉂? // ...
function process(data: any) { //
// ...
}
// 应该明确类型
function process(data: ProcessData) { // 鉁? // ...
function process(data: ProcessData) { //
// ...
}
```
@@ -274,7 +303,7 @@ import type { Project, ProjectStatus } from './types'
### 组件定义
**鉁?鎺ㄨ崘锛氬嚱鏁扮粍浠?+ Hooks**
**✅ 推荐:函数组件 + Hooks**
```tsx
import { useState } from 'react'
@@ -314,14 +343,15 @@ export function Button({
}
```
**鉂?閬垮厤锛氱被缁勪欢**
**❌ 避免:类组件**
```tsx
// 除非有特殊需求,否则不使用类组件
class Button extends React.Component { ... } // 鉂?```
class Button extends React.Component { ... } //
```
### Hooks规范
**?Hooks**
**✅ 推荐自定义Hooks**
```typescript
// useProjects.ts
import { useState, useEffect } from 'react'
@@ -373,7 +403,8 @@ function ProjectList() {
### 组件组织
```tsx
// 鉁?鎺ㄨ崘鐨勭粍浠剁粨鏋?import { useState, useEffect, useMemo, useCallback } from 'react'
// ✅ 推荐的组件结构
import { useState, useEffect, useMemo, useCallback } from 'react'
import { useNavigate } from 'react-router-dom'
import { SomeComponent } from '@/components'
import { useCustomHook } from '@/hooks'
@@ -389,19 +420,23 @@ export function Component({ prop1, prop2 }: ComponentProps) {
const [state, setState] = useState()
const { data } = useCustomHook()
// 2. 娲剧敓鐘舵€侊紙useMemo锛? const computedValue = useMemo(() => {
// 2. 派生状态useMemo
const computedValue = useMemo(() => {
return heavyComputation(data)
}, [data])
// 3. 浜嬩欢澶勭悊锛坲seCallback锛? const handleClick = useCallback(() => {
// 3. 事件处理(useCallback
const handleClick = useCallback(() => {
// 处理逻辑
}, [])
// 4. Effects
useEffect(() => {
// <EFBFBD>綔鐢? }, [])
// 副作用
}, [])
// 5. 鏃╂湡杩斿洖锛圠oading/Error锛? if (!data) return <Loading />
// 5. 早期返回Loading/Error
if (!data) return <Loading />
// 6. 渲染
return (
@@ -414,7 +449,7 @@ export function Component({ prop1, prop2 }: ComponentProps) {
### 条件渲染
**鉁?鎺ㄨ崘锛?*
**✅ 推荐:**
```tsx
// 简单条件:使用 &&
{isLoggedIn && <UserMenu />}
@@ -433,14 +468,14 @@ function renderContent() {
return <div>{renderContent()}</div>
```
**鉂?閬垮厤锛?*
**❌ 避免:**
```tsx
// 避免复杂的嵌套三元运算符
{condition1 ? (
condition2 ? <A /> : <B />
) : (
condition3 ? <C /> : <D />
)} // 鉂?闅句互鐞嗚В
)} // ❌ 难以理解
```
---
@@ -452,11 +487,11 @@ return <div>{renderContent()}</div>
```
backend/src/
├── routes/ # 路由定义
鈹? 鈹溾攢鈹€ auth.routes.ts
鈹? 鈹斺攢鈹€ project.routes.ts
│ ├── auth.routes.ts
│ └── project.routes.ts
├── services/ # 业务逻辑
鈹? 鈹溾攢鈹€ auth.service.ts
鈹? 鈹斺攢鈹€ project.service.ts
│ ├── auth.service.ts
│ └── project.service.ts
├── controllers/ # 控制器(可选)
├── models/ # Prisma模型
├── utils/ # 工具函数
@@ -536,7 +571,8 @@ export async function projectRoutes(server: FastifyInstance) {
}
```
### Service灞?
### Service
```typescript
// services/project.service.ts
import { prisma } from '../lib/prisma'
@@ -544,7 +580,8 @@ import type { CreateProjectDto, UpdateProjectDto } from '../types'
export class ProjectService {
/**
* 鑾峰彇鐢ㄦ埛鐨勯」鐩<EFBFBD>垪琛? */
* 获取用户的项目列表
*/
async getProjects(userId: string, options: PaginationOptions) {
const { page, pageSize } = options
@@ -675,7 +712,8 @@ async function getProject(id: string) {
}
```
### 閿欒<EFBFBD>澶勭悊涓<EFBFBD>棿浠?
### 错误处理中间件
```typescript
// middleware/error-handler.ts
import { FastifyError, FastifyReply, FastifyRequest } from 'fastify'
@@ -689,7 +727,8 @@ export async function errorHandler(
// 记录错误
request.log.error(error)
// <EFBFBD>畾涔夐敊璇? if (error instanceof AppError) {
// 自定义错误
if (error instanceof AppError) {
return reply.code(error.statusCode).send({
success: false,
error: {
@@ -744,22 +783,23 @@ export async function errorHandler(
### 变量命名
```typescript
// 鉁?鎺ㄨ崘
// ✅ 推荐
const userName = 'John' // camelCase
const USER_ROLE = 'admin' // 常量用UPPER_SNAKE_CASE
const isLoading = false // 布尔值用is/has/can前缀
const hasPermission = true
const canEdit = false
// 鉂?閬垮厤
// ❌ 避免
const user_name = 'John' // 不用snake_case
const loading = false // 布尔值缺少is前缀
const x = 10 // 鏃犳剰涔夌殑鍙橀噺鍚?```
const x = 10 // 无意义的变量名
```
### 函数命名
```typescript
// 鉁?鎺ㄨ崘
// ✅ 推荐
function getUser() { } // get: 获取数据
function fetchProjects() { } // fetch: 异步获取
function createProject() { } // create: 创建
@@ -767,20 +807,24 @@ function updateProject() { } // update: 更新
function deleteProject() { } // delete: 删除
function handleClick() { } // handle: 事件处理
function validateEmail() { } // validate: 验证
function formatDate() { } // format: 鏍煎紡鍖?
// 鉂?閬垮厤
function data() { } // 涓嶆竻妤氬姛鑳?function doSomething() { } // 澶<>ā绯?function process() { } // 涓嶆槑纭?```
function formatDate() { } // format: 格式化
// ❌ 避免
function data() { } // 不清楚功能
function doSomething() { } // 太模糊
function process() { } // 不明确
```
### 组件命名
```typescript
// 鉁?鎺ㄨ崘
// ✅ 推荐
<Button /> // 基础组件
<UserProfile /> // 业务组件
<ProjectList /> // 列表组件
<CreateProjectModal /> // 弹窗组件
// 鉂?閬垮厤
// ❌ 避免
<button /> // 不用小写
<user_profile /> // 不用snake_case
<ListProjects /> // 动词不要在前
@@ -794,9 +838,11 @@ function data() { } // 不清楚功
```typescript
/**
* 鍒涘缓鏂伴」鐩? * @param userId - 鐢ㄦ埛ID
* 创建新项目
* @param userId - 用户ID
* @param data - 项目数据
* @returns 鍒涘缓鐨勯」鐩<EFBFBD><EFBFBD>璞? * @throws {ValidationError} 褰撴暟鎹<E69A9F>獙璇佸け璐ユ椂
* @returns 创建的项目对象
* @throws {ValidationError} 当数据验证失败时
*/
async function createProject(
userId: string,
@@ -809,18 +855,21 @@ async function createProject(
### 代码注释
```typescript
// 鉁?濂界殑娉ㄩ噴锛氳В閲婁负浠€涔?// 浣跨敤setTimeout閬垮厤闃诲<E99783>UI娓叉煋
// ✅ 好的注释:解释为什么
// 使用setTimeout避免阻塞UI渲染
setTimeout(() => {
processLargeData()
}, 0)
// 绛夊緟Dify澶勭悊鏂囨。锛屾渶澶氶噸璇?0娆?for (let i = 0; i < 10; i++) {
// 等待Dify处理文档最多重试10次
for (let i = 0; i < 10; i++) {
const status = await checkStatus()
if (status === 'completed') break
await sleep(2000)
}
// 鉂?鍧忕殑娉ㄩ噴锛氶噸澶嶄唬鐮?// 璁剧疆loading涓簍rue
// ❌ 坏的注释:重复代码
// 设置loading为true
setLoading(true)
// 调用API
@@ -845,10 +894,10 @@ await api.getData()
| 类型 | 说明 |
|------|------|
| feat | 鏂板姛鑳?|
| feat | 新功能 |
| fix | Bug修复 |
| docs | 文档更新 |
| style | 浠g爜鏍煎紡锛堜笉褰卞搷鍔熻兘锛?|
| style | 代码格式(不影响功能) |
| refactor | 重构 |
| perf | 性能优化 |
| test | 测试相关 |
@@ -863,8 +912,11 @@ git commit -m "fix(project): 修复项目删除权限问题"
git commit -m "docs(api): 更新API文档"
git commit -m "refactor(chat): 优化消息组件结构"
# 涓嶅ソ鐨勬彁浜?git commit -m "update" # 鉂?澶<>ā绯?git commit -m "fix bug" # 鉂?娌℃湁璇存槑鏄<E6A791>粈涔坆ug
git commit -m "瀹屾垚鍔熻兘" # 鉂?娌℃湁璇存槑鏄<E6A791>粈涔堝姛鑳?```
# 不好的提交
git commit -m "update" # ❌ 太模糊
git commit -m "fix bug" # ❌ 没有说明是什么bug
git commit -m "完成功能" # ❌ 没有说明是什么功能
```
---
@@ -907,30 +959,36 @@ module.exports = {
---
## 浠g爜Review妫€鏌ユ竻鍗?
## 代码Review检查清单
### 功能
- [ ] 功能是否完整实现
- [ ] 是否有遗漏的边界情况
- [ ] 错误处理是否完善
### 代码质量
- [ ] 爜鏄<EFBFBD>惁鏄撹<EFBFBD>鏄撶悊瑙?- [ ] 鏄<>惁鏈夐噸澶嶄唬鐮?- [ ] 鍑芥暟鏄<E69A9F>惁杩囬暱锛堝缓璁?50琛岋級
- [ ] 代码是否易读易理解
- [ ] 是否有重复代码
- [ ] 函数是否过长(建议<50行
- [ ] 是否遵守命名规范
### 性能
- [ ] 是否有性能问题
- [ ] 是否有不必要的重渲染
- [ ] 鏁版嵁搴撴煡璇㈡槸鍚︿紭鍖?
- [ ] 数据库查询是否优化
### 安全
- [ ] 是否有SQL注入风险
- [ ] 是否有XSS风险
- [ ] 权限验证是否完善
### 测试
- [ ] <EFBFBD>惁鏈夊崟鍏冩祴璇?- [ ] 娴嬭瘯瑕嗙洊鐜囨槸鍚﹁冻澶?
- [ ] 是否有单元测试
- [ ] 测试覆盖率是否足够
---
**鏂囨。缁存姢锛?* 瑙勮寖鏇存柊闇€鍚屾<E98D9A>姝ゆ枃妗?
**文档维护:** 规范更新需同步此文档
**最后更新:** 2025-10-10
**维护者:** 技术负责人