- 移除extension模块,将功能迁移至node-agent - 修复类型导出问题,使用export type明确类型导出 - 统一数据库连接方式,从直接导入改为使用config/database - 更新文档中的项目结构描述 - 添加多个服务的实用方法,如getForecast、getBalances等 - 修复类型错误和TS1205警告 - 优化RedisService调用方式 - 添加新的实体类型定义 - 更新审计日志格式,统一字段命名
22 KiB
22 KiB
代码质量规范(Code Quality Standards)
模块: 01_Architecture - 代码质量与 ESLint 规范
更新日期: 2026-03-20
适用范围: 全项目(dashboard、server、node-agent)
1. 概述(Overview)
1.1 目标
- 代码一致性:统一代码风格和结构
- 类型安全:通过 ESLint + TypeScript 确保类型正确
- 可维护性:提高代码可读性和可维护性
- 自动化检查:通过工具链自动检查代码质量
1.2 工具链
| 工具 | 用途 | 优先级 |
|---|---|---|
| ESLint | 代码质量检查 | P0 |
| Prettier | 代码格式化 | P0 |
| TypeScript | 类型检查 | P0 |
| Husky | Git 钩子 | P1 |
| lint-staged | 提交前检查 | P1 |
2. ESLint 配置(ESLint Configuration)
2.1 安装依赖
# 核心依赖
npm install -D eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin
# 额外插件
npm install -D eslint-plugin-prettier eslint-config-prettier
npm install -D eslint-plugin-import eslint-plugin-boundaries
2.2 基础配置(.eslintrc.js)
module.exports = {
env: {
node: true,
es2021: true,
browser: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:@typescript-eslint/recommended-requiring-type-checking',
'plugin:import/recommended',
'plugin:import/typescript',
'plugin:prettier/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
project: './tsconfig.json',
tsconfigRootDir: __dirname
},
plugins: [
'@typescript-eslint',
'import',
'boundaries'
],
settings: {
'import/resolver': {
typescript: {
alwaysTryTypes: true,
project: './tsconfig.json'
}
},
'boundaries/elements': [
{
type: 'controller',
pattern: 'src/api/controllers/*'
},
{
type: 'service',
pattern: 'src/services/*'
},
{
type: 'repository',
pattern: 'src/repositories/*'
},
{
type: 'model',
pattern: 'src/models/*'
},
{
type: 'dto',
pattern: 'src/dto/*'
}
]
},
rules: {
// TypeScript 核心规则
'@typescript-eslint/no-explicit-any': 'error',
'@typescript-eslint/explicit-function-return-type': 'error',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-unused-vars': ['error', {
argsIgnorePattern: '^_',
varsIgnorePattern: '^_'
}],
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/strict-boolean-expressions': 'warn',
'@typescript-eslint/no-unnecessary-type-assertion': 'error',
'@typescript-eslint/consistent-type-imports': ['error', {
prefer: 'type-imports',
disallowTypeAnnotations: false
}],
'@typescript-eslint/no-non-null-assertion': 'warn',
// Import 规则
'import/order': ['error', {
groups: [
'builtin',
'external',
'internal',
'parent',
'sibling',
'index'
],
'newlines-between': 'always',
alphabetize: {
order: 'asc',
caseInsensitive: true
}
}],
'import/no-duplicates': 'error',
'import/no-cycle': 'error',
'import/no-unresolved': 'off',
// Boundaries 规则(架构分层)
'boundaries/element-types': ['error', {
default: 'disallow',
rules: [
{
from: 'controller',
allow: ['service', 'dto']
},
{
from: 'service',
allow: ['repository', 'model', 'dto']
},
{
from: 'repository',
allow: ['model']
},
{
from: 'model',
allow: []
}
]
}],
// 通用规则
'no-console': ['warn', { allow: ['warn', 'error'] }],
'no-debugger': 'error',
'no-alert': 'error',
'no-var': 'error',
'prefer-const': 'error',
'prefer-arrow-callback': 'error',
'no-const-assign': 'error',
'no-dupe-keys': 'error',
'no-duplicate-case': 'error',
'no-empty': 'error',
'no-eval': 'error',
'no-implied-eval': 'error',
'no-irregular-whitespace': 'error',
'no-iterator': 'error',
'no-multi-spaces': 'error',
'no-new-wrappers': 'error',
'no-return-await': 'error',
'no-sequences': 'error',
'no-shadow-restricted-names': 'error',
'no-sparse-arrays': 'error',
'no-throw-literal': 'error',
'no-unreachable': 'error',
'no-unsafe-finally': 'error',
'no-useless-concat': 'error',
'no-useless-return': 'error',
'no-with': 'error',
'radix': 'error',
'yoda': 'error',
// Prettier 集成
'prettier/prettier': 'error'
},
ignorePatterns: [
'dist',
'node_modules',
'*.config.js',
'*.config.ts',
'.umi',
'.umi-production'
]
}
2.3 模块特定配置
Dashboard(前端)
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:react/recommended',
'plugin:react-hooks/recommended',
'plugin:jsx-a11y/recommended',
'plugin:prettier/recommended'
],
plugins: ['react', 'react-hooks', 'jsx-a11y'],
settings: {
react: {
version: 'detect'
}
},
rules: {
'react/react-in-jsx-scope': 'off',
'react/prop-types': 'off',
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
}
Server(后端)
module.exports = {
env: {
node: true,
es2021: true
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
rules: {
'no-console': ['warn', { allow: ['warn', 'error'] }]
}
}
3. Prettier 配置(Prettier Configuration)
3.1 安装依赖
npm install -D prettier eslint-config-prettier eslint-plugin-prettier
3.2 配置文件(.prettierrc.js)
module.exports = {
printWidth: 100,
tabWidth: 2,
useTabs: false,
semi: true,
singleQuote: true,
quoteProps: 'as-needed',
jsxSingleQuote: false,
trailingComma: 'es5',
bracketSpacing: true,
bracketSameLine: false,
arrowParens: 'always',
proseWrap: 'preserve',
htmlWhitespaceSensitivity: 'css',
endOfLine: 'lf',
embeddedLanguageFormatting: 'auto'
}
3.3 忽略文件(.prettierignore)
node_modules
dist
build
coverage
*.min.js
*.min.css
package-lock.json
yarn.lock
pnpm-lock.yaml
.umi
.umi-production
4. 代码风格规范(Code Style Standards)
4.1 命名规范
| 类型 | 命名规则 | 示例 |
|---|---|---|
| 变量/函数 | camelCase | getUserData, userName |
| 类/接口/类型 | PascalCase | UserService, Product |
| 常量 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, API_BASE_URL |
| 私有成员 | camelCase + _ 前缀 |
_privateMethod, _internalState |
| 枚举 | PascalCase | Status, OrderStatus |
| 文件名 | PascalCase (组件) / kebab-case (工具) | UserList.tsx, api-utils.ts |
4.2 注释规范
JSDoc 注释
/**
* 获取用户信息
*
* @param userId - 用户ID
* @param includeProfile - 是否包含个人资料
* @returns 用户信息对象
* @throws {Error} 当用户不存在时抛出错误
*
* @example
* ```typescript
* const user = await getUser('123', true)
* console.log(user.name)
* ```
*/
async function getUser(
userId: string,
includeProfile: boolean = false
): Promise<User> {
const user = await userRepository.findById(userId)
if (!user) {
throw new Error(`User not found: ${userId}`)
}
if (includeProfile) {
return {
...user,
profile: await profileRepository.findByUserId(userId)
}
}
return user
}
单行注释
// 计算订单总金额
const totalAmount = calculateTotalAmount(items)
// TODO: 需要优化性能
const result = heavyComputation(data)
多行注释
/*
* 订单处理流程:
* 1. 验证订单数据
* 2. 检查库存
* 3. 创建支付记录
* 4. 更新订单状态
*/
4.3 代码组织
文件结构
// 1. 导入(按顺序:Node.js 内置、第三方库、内部模块)
import { promises as fs } from 'fs'
import axios from 'axios'
import { UserService } from '@/services/UserService'
import type { User } from '@/types/domain/User'
// 2. 类型定义
interface UserDTO {
id: string
name: string
email: string
}
// 3. 常量定义
const MAX_RETRY_COUNT = 3
const API_TIMEOUT = 5000
// 4. 类/函数定义
export class UserController {
// ...
}
// 5. 导出
export { UserDTO }
导入顺序
// 1. Node.js 内置模块
import { promises as fs } from 'fs'
import path from 'path'
// 2. 第三方库
import axios from 'axios'
import { z } from 'zod'
// 3. 内部模块(按路径层级)
import { UserService } from '@/services/UserService'
import { UserRepository } from '@/repositories/UserRepository'
import type { User } from '@/types/domain/User'
import { validateUser } from '@/utils/validators'
// 4. 相对路径导入
import { formatDate } from './utils/date'
5. TypeScript 特定规范(TypeScript Specific Standards)
5.1 类型定义
// ✅ 正确:使用 interface 定义对象类型
interface User {
id: string
name: string
email: string
}
// ✅ 正确:使用 type 定义联合类型、交叉类型
type Status = 'active' | 'inactive' | 'pending'
type UserWithProfile = User & { profile: Profile }
// ❌ 错误:使用 type 定义简单对象类型
type User = {
id: string
name: string
email: string
}
5.2 泛型使用
// ✅ 正确:使用泛型提高代码复用性
function fetchData<T>(url: string): Promise<T> {
return axios.get<T>(url).then(res => res.data)
}
// ✅ 正确:使用泛型约束
function processItem<T extends { id: string }>(item: T): void {
console.log(item.id)
}
// ❌ 错误:过度使用泛型
function process<T, U, V, W>(a: T, b: U, c: V, d: W): void {
// ...
}
5.3 类型守卫
// ✅ 正确:使用类型守卫
function isProduct(data: unknown): data is Product {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'price' in data
)
}
function handleData(data: unknown) {
if (isProduct(data)) {
// TypeScript 知道 data 是 Product 类型
console.log(data.name)
}
}
5.4 可选链和空值合并
// ✅ 正确:使用可选链
const userName = user?.profile?.name
// ✅ 正确:使用空值合并
const displayName = userName ?? 'Anonymous'
// ✅ 正确:结合使用
const value = obj?.prop ?? defaultValue
// ❌ 错误:过度使用可选链
const value = obj?.prop?.nested?.value?.toString()
6. React 特定规范(React Specific Standards)
6.1 组件定义
// ✅ 正确:使用函数组件 + TypeScript
interface Props {
title: string
count: number
onAction: () => void
}
export function ProductCard({ title, count, onAction }: Props) {
return (
<div className="product-card">
<h2>{title}</h2>
<p>Count: {count}</p>
<button onClick={onAction}>Action</button>
</div>
)
}
// ✅ 正确:使用 React.FC(不推荐,但可用)
export const ProductCard: React.FC<Props> = ({ title, count, onAction }) => {
return (
<div className="product-card">
<h2>{title}</h2>
<p>Count: {count}</p>
<button onClick={onAction}>Action</button>
</div>
)
}
6.2 Hooks 使用
// ✅ 正确:使用自定义 Hook
function useUserData(userId: string) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
async function fetchUser() {
try {
setLoading(true)
const data = await userService.getUser(userId)
setUser(data)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
fetchUser()
}, [userId])
return { user, loading, error }
}
// ✅ 正确:在组件中使用
function UserProfile({ userId }: { userId: string }) {
const { user, loading, error } = useUserData(userId)
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
if (!user) return <div>User not found</div>
return <div>{user.name}</div>
}
6.3 Props 类型定义
// ✅ 正确:明确的 Props 类型
interface ProductListProps {
products: Product[]
loading?: boolean
onProductClick: (product: Product) => void
className?: string
}
export function ProductList({
products,
loading = false,
onProductClick,
className
}: ProductListProps) {
if (loading) return <div>Loading...</div>
return (
<div className={className}>
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onClick={() => onProductClick(product)}
/>
))}
</div>
)
}
7. 后端特定规范(Backend Specific Standards)
7.1 Controller 层
// ✅ 正确:Controller 只负责请求/响应
export class UserController {
constructor(private readonly userService: UserService) {}
async getUser(req: Request, res: Response): Promise<void> {
try {
const { userId } = req.params
const user = await this.userService.getUser(userId)
res.json({ success: true, data: user })
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
})
}
}
}
7.2 Service 层
// ✅ 正确:Service 负责业务逻辑
export class UserService {
constructor(
private readonly userRepository: UserRepository,
private readonly profileRepository: ProfileRepository
) {}
async getUser(userId: string): Promise<UserWithProfile> {
const user = await this.userRepository.findById(userId)
if (!user) {
throw new Error(`User not found: ${userId}`)
}
const profile = await this.profileRepository.findByUserId(userId)
return {
...user,
profile
}
}
}
7.3 Repository 层
// ✅ 正确:Repository 只负责数据访问
export class UserRepository {
constructor(private readonly db: Knex) {}
async findById(id: string): Promise<User | null> {
const row = await this.db('users').where({ id }).first()
return row ? this.mapToEntity(row) : null
}
private mapToEntity(row: any): User {
return {
id: row.id,
name: row.name,
email: row.email,
createdAt: new Date(row.created_at),
updatedAt: new Date(row.updated_at)
}
}
}
8. 测试规范(Testing Standards)
8.1 测试文件命名
src/
services/
UserService.ts
UserService.test.ts # 单元测试
UserService.integration.test.ts # 集成测试
8.2 测试结构
describe('UserService', () => {
let userService: UserService
let mockUserRepository: jest.Mocked<UserRepository>
beforeEach(() => {
mockUserRepository = {
findById: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn()
} as any
userService = new UserService(mockUserRepository)
})
describe('getUser', () => {
it('should return user when user exists', async () => {
const mockUser = {
id: '123',
name: 'John Doe',
email: 'john@example.com'
}
mockUserRepository.findById.mockResolvedValue(mockUser)
const result = await userService.getUser('123')
expect(result).toEqual(mockUser)
expect(mockUserRepository.findById).toHaveBeenCalledWith('123')
})
it('should throw error when user does not exist', async () => {
mockUserRepository.findById.mockResolvedValue(null)
await expect(userService.getUser('123')).rejects.toThrow('User not found: 123')
})
})
})
9. 性能优化规范(Performance Standards)
9.1 避免不必要的渲染
// ✅ 正确:使用 React.memo
export const ProductCard = React.memo(function ProductCard({ product }: { product: Product }) {
return <div>{product.name}</div>
})
// ✅ 正确:使用 useMemo
function ProductList({ products }: { products: Product[] }) {
const sortedProducts = useMemo(
() => [...products].sort((a, b) => a.name.localeCompare(b.name)),
[products]
)
return (
<div>
{sortedProducts.map(product => (
<ProductCard key={product.id} product={product} />
))}
</div>
)
}
// ✅ 正确:使用 useCallback
function ProductList({ products, onProductClick }: { products: Product[], onProductClick: (product: Product) => void }) {
const handleClick = useCallback(
(product: Product) => {
onProductClick(product)
},
[onProductClick]
)
return (
<div>
{products.map(product => (
<ProductCard key={product.id} product={product} onClick={handleClick} />
))}
</div>
)
}
9.2 避免内存泄漏
// ✅ 正确:清理副作用
function useInterval(callback: () => void, delay: number) {
useEffect(() => {
const intervalId = setInterval(callback, delay)
return () => {
clearInterval(intervalId)
}
}, [callback, delay])
}
// ✅ 正确:取消请求
function useUserData(userId: string) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const abortController = new AbortController()
async function fetchUser() {
try {
setLoading(true)
const data = await userService.getUser(userId, { signal: abortController.signal })
setUser(data)
} catch (error) {
if (error.name !== 'AbortError') {
console.error(error)
}
} finally {
setLoading(false)
}
}
fetchUser()
return () => {
abortController.abort()
}
}, [userId])
return { user, loading }
}
10. 安全规范(Security Standards)
10.1 输入验证
// ✅ 正确:使用 zod 验证输入
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
password: z.string().min(8).regex(/[A-Z]/).regex(/[0-9]/)
})
async function createUser(data: unknown): Promise<User> {
const validatedData = CreateUserSchema.parse(data)
return userRepository.create(validatedData)
}
10.2 SQL 注入防护
// ✅ 正确:使用参数化查询
async function getUserById(id: string): Promise<User | null> {
const row = await this.db('users').where({ id }).first()
return row ? this.mapToEntity(row) : null
}
// ❌ 错误:字符串拼接 SQL
async function getUserByIdBad(id: string): Promise<User | null> {
const row = await this.db.raw(`SELECT * FROM users WHERE id = '${id}'`)
return row ? this.mapToEntity(row) : null
}
10.3 XSS 防护
// ✅ 正确:React 自动转义
function UserDisplay({ name }: { name: string }) {
return <div>{name}</div>
}
// ✅ 正确:使用 DOMPurify 清理 HTML
import DOMPurify from 'dompurify'
function RichTextContent({ html }: { html: string }) {
const cleanHtml = DOMPurify.sanitize(html)
return <div dangerouslySetInnerHTML={{ __html: cleanHtml }} />
}
11. Git 提交规范(Git Commit Standards)
11.1 提交信息格式
<type>(<scope>): <subject>
<body>
<footer>
11.2 Type 类型
| Type | 说明 | 示例 |
|---|---|---|
feat |
新功能 | feat(user): add user registration |
fix |
修复 bug | fix(auth): resolve login issue |
docs |
文档更新 | docs(readme): update installation guide |
style |
代码格式 | style: format code with prettier |
refactor |
重构 | refactor(service): simplify user service |
perf |
性能优化 | perf(api): optimize database query |
test |
测试 | test(user): add user service tests |
chore |
构建/工具 | chore(deps): update dependencies |
11.3 示例
feat(user): add user registration feature
- Add user registration endpoint
- Implement email verification
- Add user profile creation
Closes #123
12. 自动化检查流程(Automated Check Process)
12.1 开发流程
写代码
↓
ESLint 实时报错(IDE 插件)
↓
Prettier 自动格式化(保存时)
↓
提交前 lint-staged 拦截
↓
CI 执行完整检查
↓
代码审查
↓
合并
12.2 package.json 脚本
{
"scripts": {
"lint": "eslint . --ext .ts,.tsx,.js,.jsx",
"lint:fix": "eslint . --ext .ts,.tsx,.js,.jsx --fix",
"format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\"",
"format:check": "prettier --check \"**/*.{ts,tsx,js,jsx,json,md}\"",
"typecheck": "tsc --noEmit",
"test": "jest",
"test:coverage": "jest --coverage",
"validate": "npm run lint && npm run format:check && npm run typecheck && npm run test",
"prepare": "husky install"
}
}
12.3 lint-staged 配置
{
"lint-staged": {
"*.{ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{js,jsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,md}": [
"prettier --write"
]
}
}
13. 版本管理
| 版本 | 变更内容 | 日期 |
|---|---|---|
| 1.0.0 | 初始版本 | 2026-03-20 |