# 代码规范 > **版本:** v1.0 > **创建日期:** 2025-10-10 > **适用范围:** 前端(React/TypeScript)+ 后端(Node.js/TypeScript) --- ## 📋 目录 1. [通用规范](#通用规范) 2. [TypeScript规范](#typescript规范) 3. [React规范](#react规范) 4. [Node.js后端规范](#nodejs后端规范) 5. [命名规范](#命名规范) 6. [注释规范](#注释规范) 7. [Git提交规范](#git提交规范) --- ## 🌟 平台能力使用规范(2025-11-16 新增) > **⭐ 重要提示**:平台已提供完整的基础设施服务 > **详细规范**:[云原生开发规范](./08-云原生开发规范.md) > **详细文档**:[平台基础设施规划](../09-架构实施/04-平台基础设施规划.md) ### 必须复用的平台服务 **业务模块(ASL/AIA/PKB/DC等)禁止重复实现以下功能:** | 服务 | 导入方式 | 用途 | |------|---------|------| | **存储服务** | `import { storage } from '@/common/storage'` | 文件上传下载 | | **日志系统** | `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调用 | --- ### ✅ 正确示例:使用平台服务 ```typescript // backend/src/modules/asl/services/literatureService.ts import { storage } from '@/common/storage' import { logger } from '@/common/logging' import { jobQueue } from '@/common/jobs' import { cache } from '@/common/cache' import { prisma } from '@/config/database' export class LiteratureService { async uploadPDF(projectId: string, pdfBuffer: Buffer) { // 1. 使用平台存储服务 const key = `asl/projects/${projectId}/pdfs/${Date.now()}.pdf` const url = await storage.upload(key, pdfBuffer) // 2. 使用平台日志系统 logger.info('PDF uploaded', { projectId, url }) // 3. 使用平台数据库 const literature = await prisma.aslLiterature.create({ data: { projectId, pdfUrl: url, pdfFileSize: pdfBuffer.length } }) // 4. 使用平台缓存 await cache.set(`literature:${literature.id}`, literature, 3600) return literature } async startScreening(projectId: string, literatureIds: string[]) { // 5. 使用平台异步任务(长时间任务必须异步) const job = await jobQueue.push('asl:screening', { projectId, literatureIds }) logger.info('Screening job created', { jobId: job.id }) return { jobId: job.id } // 立即返回 } } ``` --- ### ❌ 错误示例:重复实现平台能力 ```typescript // ❌ 错误:在业务模块中自己实现存储 // 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) // ❌ 重复实现 return '/uploads/file.pdf' } } // ❌ 错误:在业务模块中自己实现日志 // backend/src/modules/asl/logger/logger.ts ← 不应该存在! import winston from 'winston' export const logger = winston.createLogger({...}) // ❌ 重复实现 // ❌ 错误:每次新建数据库连接 import { PrismaClient } from '@prisma/client' export function getUser() { const prisma = new PrismaClient() // ❌ 连接泄漏 return prisma.user.findMany() } ``` **为什么错误?** - ❌ 重复代码,难以维护 - ❌ 不同模块实现不一致 - ❌ 无法统一切换环境(本地/云端) - ❌ 浪费开发时间 - ❌ 云端部署会失败(Serverless限制) --- ### 文件上传规范 ```typescript // ✅ 正确:使用存储抽象层 const url = await storage.upload('asl/pdf/123.pdf', buffer) // ❌ 错误:直接操作文件系统 fs.writeFileSync('./uploads/123.pdf', buffer) // Serverless容器重启会丢失 // ❌ 错误:硬编码存储路径 const filePath = 'D:/uploads/123.pdf' // Windows路径,Linux无法运行 ``` --- ### 异步任务规范 ```typescript // ✅ 正确:长时间任务(>10秒)必须异步处理 app.post('/screening/start', async (req, res) => { const job = await jobQueue.push('asl:screening', data) res.send({ jobId: job.id }) // 立即返回,不等待完成 }) // 查询进度 app.get('/screening/jobs/:id', async (req, res) => { const job = await jobQueue.getJob(req.params.id) res.send({ status: job.status, progress: job.progress }) }) // ❌ 错误:同步等待长时间任务 app.post('/screening/start', async (req, res) => { const results = await processAllLiteratures(data) // 可能需要10分钟 res.send({ results }) // Serverless 30秒超时! }) ``` --- ### 数据库连接规范 ```typescript // ✅ 正确:使用全局Prisma实例 import { prisma } from '@/config/database' export async function getUsers() { return await prisma.user.findMany() } // ❌ 错误:每次新建实例 export async function getUsers() { const prisma = new PrismaClient() // 连接数耗尽! return await prisma.user.findMany() } ``` --- ### 日志规范 ```typescript // ✅ 正确:使用平台日志系统 import { logger } from '@/common/logging' logger.info('Operation successful', { userId, action: 'upload' }) logger.error('Operation failed', { error: err.message, userId }) // ❌ 错误:使用console.log console.log('Operation successful') // 无法集中收集,难以查询 // ❌ 错误:写本地日志文件 fs.appendFileSync('./app.log', 'Operation successful') // Serverless不支持 ``` --- ## 通用规范 ### 代码风格 - ✅ 使用ESLint和Prettier统一代码风格 - ✅ 缩进:2个空格 - ✅ 字符串:优先使用单引号 `'` - ✅ 行尾:不加分号(除非必要) - ✅ 单行最大长度:100字符 - ✅ 使用尾随逗号(对象、数组) ### 文件组织 - ✅ 一个文件一个组件/类 - ✅ 相关文件放在同一目录 - ✅ 使用barrel exports(index.ts) - ✅ 测试文件与源文件同目录 ``` src/ ├── components/ │ ├── Button/ │ │ ├── Button.tsx │ │ ├── Button.test.tsx │ │ ├── Button.styles.ts │ │ └── index.ts # export { Button } from './Button' ``` ### 代码注释 - ✅ 复杂逻辑必须注释 - ✅ 公共API必须注释 - ✅ 避免无用注释 - ✅ 使用JSDoc格式 --- ## TypeScript规范 ### 类型定义 **✅ 推荐:** ```typescript // 使用interface定义对象结构 interface User { id: string email: string name?: string } // 使用type定义联合类型 type Status = 'active' | 'inactive' | 'suspended' // 使用enum定义常量集合 enum UserRole { USER = 'user', ADMIN = 'admin', } ``` **❌ 避免:** ```typescript // 不要使用any function process(data: any) { // ❌ // ... } // 应该明确类型 function process(data: ProcessData) { // ✅ // ... } ``` ### 类型导入导出 ```typescript // types.ts export interface Project { id: string name: string description: string } export type ProjectStatus = 'active' | 'archived' // project.service.ts import type { Project, ProjectStatus } from './types' ``` ### 严格模式 ```json // tsconfig.json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true } } ``` --- ## React规范 ### 组件定义 **✅ 推荐:函数组件 + Hooks** ```tsx import { useState } from 'react' interface ButtonProps { label: string onClick: () => void variant?: 'primary' | 'secondary' disabled?: boolean } export function Button({ label, onClick, variant = 'primary', disabled = false }: ButtonProps) { const [isLoading, setIsLoading] = useState(false) const handleClick = async () => { setIsLoading(true) try { await onClick() } finally { setIsLoading(false) } } return ( ) } ``` **❌ 避免:类组件** ```tsx // 除非有特殊需求,否则不使用类组件 class Button extends React.Component { ... } // ❌ ``` ### Hooks规范 **✅ 推荐:自定义Hooks** ```typescript // useProjects.ts import { useState, useEffect } from 'react' import { projectService } from '@/services' import type { Project } from '@/types' export function useProjects() { const [projects, setProjects] = useState([]) const [loading, setLoading] = useState(false) const [error, setError] = useState(null) useEffect(() => { loadProjects() }, []) const loadProjects = async () => { setLoading(true) setError(null) try { const data = await projectService.getProjects() setProjects(data) } catch (err) { setError(err as Error) } finally { setLoading(false) } } return { projects, loading, error, reload: loadProjects } } // 使用 function ProjectList() { const { projects, loading, error } = useProjects() if (loading) return if (error) return return (
    {projects.map(project => (
  • {project.name}
  • ))}
) } ``` ### 组件组织 ```tsx // ✅ 推荐的组件结构 import { useState, useEffect, useMemo, useCallback } from 'react' import { useNavigate } from 'react-router-dom' import { SomeComponent } from '@/components' import { useCustomHook } from '@/hooks' import type { SomeType } from '@/types' interface ComponentProps { // props定义 } export function Component({ prop1, prop2 }: ComponentProps) { // 1. Hooks const navigate = useNavigate() const [state, setState] = useState() const { data } = useCustomHook() // 2. 派生状态(useMemo) const computedValue = useMemo(() => { return heavyComputation(data) }, [data]) // 3. 事件处理(useCallback) const handleClick = useCallback(() => { // 处理逻辑 }, []) // 4. Effects useEffect(() => { // 副作用 }, []) // 5. 早期返回(Loading/Error) if (!data) return // 6. 渲染 return (
{/* JSX */}
) } ``` ### 条件渲染 **✅ 推荐:** ```tsx // 简单条件:使用 && {isLoggedIn && } // if-else:使用三元运算符 {isLoggedIn ? : } // 多条件:提取为函数或组件 function renderContent() { if (loading) return if (error) return if (data.length === 0) return return } return
{renderContent()}
``` **❌ 避免:** ```tsx // 避免复杂的嵌套三元运算符 {condition1 ? ( condition2 ? : ) : ( condition3 ? : )} // ❌ 难以理解 ``` --- ## Node.js后端规范 ### 文件组织 ``` backend/src/ ├── routes/ # 路由定义 │ ├── auth.routes.ts │ └── project.routes.ts ├── services/ # 业务逻辑 │ ├── auth.service.ts │ └── project.service.ts ├── controllers/ # 控制器(可选) ├── models/ # Prisma模型 ├── utils/ # 工具函数 ├── config/ # 配置加载 ├── types/ # 类型定义 └── server.ts # 入口文件 ``` ### 路由定义 ```typescript // routes/project.routes.ts import { FastifyInstance } from 'fastify' import { projectService } from '../services/project.service' import { authMiddleware } from '../middleware/auth' export async function projectRoutes(server: FastifyInstance) { // 获取项目列表 server.get( '/api/v1/projects', { preHandler: [authMiddleware], schema: { querystring: { type: 'object', properties: { page: { type: 'number' }, pageSize: { type: 'number' }, }, }, }, }, async (request, reply) => { const { page = 1, pageSize = 20 } = request.query as any const userId = request.user.id const result = await projectService.getProjects(userId, { page, pageSize, }) return reply.send({ success: true, data: result, }) } ) // 创建项目 server.post( '/api/v1/projects', { preHandler: [authMiddleware], schema: { body: { type: 'object', required: ['name', 'description'], properties: { name: { type: 'string', minLength: 1, maxLength: 200 }, description: { type: 'string', minLength: 1 }, }, }, }, }, async (request, reply) => { const userId = request.user.id const data = request.body as CreateProjectDto const project = await projectService.createProject(userId, data) return reply.code(201).send({ success: true, data: project, }) } ) } ``` ### Service层 ```typescript // services/project.service.ts import { prisma } from '../lib/prisma' import type { CreateProjectDto, UpdateProjectDto } from '../types' export class ProjectService { /** * 获取用户的项目列表 */ async getProjects(userId: string, options: PaginationOptions) { const { page, pageSize } = options const [items, total] = await Promise.all([ prisma.project.findMany({ where: { userId }, skip: (page - 1) * pageSize, take: pageSize, orderBy: { createdAt: 'desc' }, }), prisma.project.count({ where: { userId } }), ]) return { items, pagination: { page, pageSize, total, totalPages: Math.ceil(total / pageSize), hasNext: page * pageSize < total, hasPrev: page > 1, }, } } /** * 创建项目 */ async createProject(userId: string, data: CreateProjectDto) { return prisma.project.create({ data: { userId, name: data.name, description: data.description, }, }) } /** * 更新项目 */ async updateProject( userId: string, projectId: string, data: UpdateProjectDto ) { // 验证权限 const project = await prisma.project.findFirst({ where: { id: projectId, userId }, }) if (!project) { throw new Error('Project not found or unauthorized') } return prisma.project.update({ where: { id: projectId }, data, }) } /** * 删除项目 */ async deleteProject(userId: string, projectId: string) { // 验证权限 const project = await prisma.project.findFirst({ where: { id: projectId, userId }, }) if (!project) { throw new Error('Project not found or unauthorized') } await prisma.project.delete({ where: { id: projectId }, }) } } export const projectService = new ProjectService() ``` ### 错误处理 ```typescript // utils/errors.ts export class AppError extends Error { constructor( public code: string, public message: string, public statusCode: number = 400, public details?: any ) { super(message) this.name = 'AppError' } } export class ValidationError extends AppError { constructor(message: string, details?: any) { super('VALIDATION_ERROR', message, 422, details) } } export class UnauthorizedError extends AppError { constructor(message: string = 'Unauthorized') { super('UNAUTHORIZED', message, 401) } } export class NotFoundError extends AppError { constructor(resource: string) { super('NOT_FOUND', `${resource} not found`, 404) } } // 使用 async function getProject(id: string) { const project = await prisma.project.findUnique({ where: { id } }) if (!project) { throw new NotFoundError('Project') } return project } ``` ### 错误处理中间件 ```typescript // middleware/error-handler.ts import { FastifyError, FastifyReply, FastifyRequest } from 'fastify' import { AppError } from '../utils/errors' export async function errorHandler( error: FastifyError | AppError, request: FastifyRequest, reply: FastifyReply ) { // 记录错误 request.log.error(error) // 自定义错误 if (error instanceof AppError) { return reply.code(error.statusCode).send({ success: false, error: { code: error.code, message: error.message, details: error.details, }, timestamp: new Date().toISOString(), }) } // Prisma错误 if (error.name === 'PrismaClientKnownRequestError') { // 处理Prisma特定错误 return reply.code(400).send({ success: false, error: { code: 'DATABASE_ERROR', message: 'Database operation failed', }, timestamp: new Date().toISOString(), }) } // 默认错误 return reply.code(500).send({ success: false, error: { code: 'INTERNAL_ERROR', message: 'Internal server error', }, timestamp: new Date().toISOString(), }) } ``` --- ## 命名规范 ### 文件命名 | 类型 | 命名方式 | 示例 | |------|---------|------| | React组件 | PascalCase | `Button.tsx`, `ProjectList.tsx` | | Hooks | camelCase + use前缀 | `useProjects.ts`, `useAuth.ts` | | 工具函数 | camelCase | `formatDate.ts`, `api.ts` | | 类型定义 | camelCase + .types | `user.types.ts`, `api.types.ts` | | 常量 | camelCase + .constants | `routes.constants.ts` | | 测试文件 | 同源文件 + .test | `Button.test.tsx` | ### 变量命名 ```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 // 无意义的变量名 ``` ### 函数命名 ```typescript // ✅ 推荐 function getUser() { } // get: 获取数据 function fetchProjects() { } // fetch: 异步获取 function createProject() { } // create: 创建 function updateProject() { } // update: 更新 function deleteProject() { } // delete: 删除 function handleClick() { } // handle: 事件处理 function validateEmail() { } // validate: 验证 function formatDate() { } // format: 格式化 // ❌ 避免 function data() { } // 不清楚功能 function doSomething() { } // 太模糊 function process() { } // 不明确 ``` ### 组件命名 ```typescript // ✅ 推荐