docs: 适配企业级编码规范并补充配置

- 新增 docs/standards/coding-standards.md 编码规范文档(16 章节)
  - 适配当前项目: 单应用+模块化架构(非 Monorepo)
  - 保留 data-access.ts 模式(非 services/)
  - 使用 proxy.ts(Next.js 16 重命名)
  - 保留企业级行数规范(组件 500/800, Actions 800/1000)
  - 保留 Tailwind v4 CSS 变量设计令牌
  - 保留 ActionState<T> 类型
  - 含"与原规范的差异说明"附录(10 项差异及原因)
- 更新 .trae/rules/project_rules.md:
  - 新增编码规范章节, 引用 coding-standards.md
  - 新增架构分层/模块结构/TS规则/命名/组件/Action/Tailwind/安全/提交规范
  - 架构文档清单新增解耦路线图
- 新增 .prettierrc 配置(匹配现有代码风格: 双引号/无分号/2空格)
- 更新 docs/README.md 新增编码规范章节
- 更新 work_log
This commit is contained in:
SpecialX
2026-06-17 22:54:29 +08:00
parent ee517f2b33
commit 02dc1093fb
5 changed files with 960 additions and 1 deletions

9
.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"semi": false,
"singleQuote": false,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "always",
"plugins": ["prettier-plugin-tailwindcss"]
}

View File

@@ -16,6 +16,7 @@
| `docs/architecture/005_architecture_data.json` | AI 友好格式的结构化数据 |
| `docs/architecture/006_k12_feature_checklist.md` | 标准功能模块清单 |
| `docs/architecture/007_gap_audit_report.md` | 差距审计报告 |
| `docs/architecture/audit/01_decoupling_roadmap.md` | 解耦路线图 |
### 需要同步图的场景
@@ -33,7 +34,11 @@
- 修改 JSON 文档中对应的节点(`modules.*.exports``permissions``dependencyMatrix``routes``dbTables` 等)
- 确保两个文档内容一致
## 代码质量规则
## 编码规范
**详细规范见 `docs/standards/coding-standards.md`,以下为核心强制规则。**
### 代码质量规则
- 每次修改后运行 `npm run lint``npx tsc --noEmit` 确保零错误
- Server Action 必须使用 `requirePermission()` 进行权限校验
@@ -42,5 +47,77 @@
- 配置文件、常量文件、类型定义文件:无限制
- React 组件:建议 ≤ 500 行(复杂表单/大型表格可放宽至 800 行)
- Server Actions / Data Access 模块:建议 ≤ 800 行
- 工具函数:建议 ≤ 40 行
- 自定义 Hook建议 ≤ 80 行
- 超过建议行数时应考虑拆分(如 data-access 拆分为多个按职责划分的文件)
- 硬性上限:任何文件不超过 1000 行,超过必须拆分
### 架构分层规则
- 严格三层架构,依赖方向单向:`app → modules → shared`
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access不直接访问 DB
- `modules/` 之间通过对方 data-access 通信,**不直接查询对方 DB 表**
- `shared/` 是被依赖方,**不得反向依赖** `@/auth``@/proxy` 或任何 `modules/*`
### 模块标准结构
```
src/modules/[module]/
├─ actions.ts # Server Actions编排层
├─ data-access.ts # 数据访问层(可拆分为 data-access-*.ts
├─ schema.ts # Zod 验证(可选)
├─ types.ts # 类型定义
├─ components/ # 模块专属组件
└─ hooks/ # 模块专属 Hook可选
```
### TypeScript 规则
- **禁止 `any`**:未知类型用 `unknown` 并做类型守卫
- **禁止 `as` 断言**(除非从 `unknown` 转换或测试中,需注释原因)
- **函数返回值必须显式标注**,特别是 `Promise<T>`
- **仅用于类型的导入必须使用 `import type`**
- **可选链后禁止跟非空断言 `!`**
### 命名规范
- 目录kebab-case`user-profile/`
- 组件文件PascalCase`UserProfile.tsx`
- Hook 文件camelCase`useAuth.ts`
- 变量/函数camelCase布尔值用 `is/has/can/should` 前缀
- 常量UPPER_SNAKE_CASE`MAX_RETRY_COUNT`
- 类/接口PascalCase接口不加 `I` 前缀
### 组件规范
- 组件必须为纯函数,使用 `function` 声明
- 页面组件(`page.tsx`)使用默认导出;其余组件使用具名导出
- 默认服务端组件,需要交互时才添加 `"use client"`(必须位于文件第一行)
- **不使用 `React.FC`**,直接用函数声明 + 显式标注 props 类型
### Server Action 规范
- 每个 Action 必须调用 `requirePermission()` 进行权限校验
- 输入使用 Zod 验证,验证失败返回结构化错误
- 返回值统一采用 `ActionState<T>` 类型
- 使用 `revalidatePath` 精确刷新缓存
### Tailwind 规范
- 使用 `cn()` 工具函数管理条件类名
- **禁止**字符串拼接动态类名(`bg-${color}-500`
- **禁止**使用任意值(`w-[137px]`),除非有充分理由并注释
- 设计令牌在 `src/app/globals.css` 中使用 CSS 变量定义
### 安全规范
- **禁止 `dangerlySetInnerHTML`**(如必须使用,先用 DOMPurify 清洗)
- JWT/session ID 存储在 httpOnly + Secure + SameSite=Strict 的 Cookie 中
- 服务端环境变量不加 `NEXT_PUBLIC_` 前缀
- 环境变量使用 `@t3-oss/env-nextjs` + Zod 校验(已实现于 `src/env.mjs`
### 提交规范
- 使用 Conventional Commits 格式:`feat(scope): description`
- 类型:`feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `perf`, `ci`
- 提交前必须运行 `npm run lint``npx tsc --noEmit` 确保零错误

View File

@@ -26,6 +26,13 @@
| [管理模块群审查](architecture/audit/management-modules-audit.md) | school/classes/users/audit 等管理模块审查 |
| [新增模块和其他模块审查](architecture/audit/new-and-other-modules-audit.md) | diagnostic/elective/proctoring/notifications 等新增模块审查 |
## 编码规范
| 文档 | 用途 |
|------|------|
| [编码规范](standards/coding-standards.md) | 适配当前项目的企业级编码规范TypeScript/React/Next.js/Tailwind/安全/测试/CI |
| [项目规则](../.trae/rules/project_rules.md) | AI 助手项目规则(架构图优先 + 核心强制规则) |
## 专题文档(活跃维护)
| 文档 | 用途 |

View File

@@ -0,0 +1,828 @@
# Next_Edu 编码规范
> 版本1.02026-06-17 适配当前项目)
> 依据Google TypeScript Style + Airbnb React + Next.js 16 + Tailwind v4 最佳实践
> 适用范围Next_Edu K12 智慧教务系统(单应用 + 模块化架构)
> 关联文档:
> - [项目规则](../../.trae/rules/project_rules.md)
> - [架构影响地图](../architecture/004_architecture_impact_map.md)
> - [解耦路线图](../architecture/audit/01_decoupling_roadmap.md)
---
## 目录
1. [项目原则与理念](#一项目原则与理念)
2. [项目结构](#二项目结构)
3. [命名规范](#三命名规范)
4. [TypeScript 强制规范](#四typescript-强制规范)
5. [React 与 Next.js 组件规范](#五react-与-nextjs-组件规范)
6. [Tailwind CSS 规范](#六tailwind-css-规范)
7. [数据获取与状态管理](#七数据获取与状态管理)
8. [路由、代理与安全](#八路由代理与安全)
9. [错误处理与可观测性](#九错误处理与可观测性)
10. [测试规范](#十测试规范)
11. [Git 工作流与提交规范](#十一git-工作流与提交规范)
12. [CI/CD 流水线](#十二cicd-流水线)
13. [可访问性A11y规范](#十三可访问性a11y规范)
14. [文档与交付物](#十四文档与交付物)
15. [统一工具配置](#十五统一工具配置)
16. [代码审查清单](#十六代码审查清单)
---
## 一、项目原则与理念
1. **可读性优先于机巧**:代码首先是写给队友看的
2. **显式优于隐式**:避免魔法值、隐式类型转换、隐式全局副作用
3. **单一职责**:每个文件、函数、组件只做一件事,衡量标准是"能否用一句话描述它"
4. **防御性编程**:永远假设输入可能是 null/undefined 或非法格式
5. **工具强制一致性**:风格、格式、类型由 ESLint、Prettier、TypeScript 自动保证
6. **架构图优先**:任何任务开始前先查阅 [004 架构影响地图](../architecture/004_architecture_impact_map.md),按图索骥
7. **模块封装**:模块间不直接查询对方 DB 表,必须通过 data-access 函数
---
## 二、项目结构
### 2.1 顶层结构(单应用 + 模块化)
本项目**不是 Monorepo**,采用单 Next.js 应用 + 严格模块化架构:
```
root/
├─ src/
│ ├─ app/ # App Router 路由层
│ │ ├─ (auth)/ # 路由组:认证页面
│ │ ├─ (dashboard)/ # 路由组业务页面admin/teacher/student/parent
│ │ ├─ api/ # REST API 路由
│ │ ├─ globals.css # Tailwind v4 指令 + CSS 变量设计令牌
│ │ ├─ layout.tsx # 根布局Provider 组合)
│ │ └─ page.tsx # 首页
│ ├─ modules/ # 业务模块层26 个模块)
│ │ ├─ exams/ # 每个模块标准结构见 2.2
│ │ ├─ homework/
│ │ ├─ classes/
│ │ └─ ...
│ ├─ shared/ # 基础设施层(被依赖方,不反向依赖)
│ │ ├─ components/ # 共享组件ui/ + a11y/ + 顶层)
│ │ ├─ db/ # Drizzle ORMschema.ts + relations.ts + index.ts
│ │ ├─ hooks/ # 全局自定义 Hook
│ │ ├─ lib/ # 纯工具函数auth-guard, ai, permissions, ...
│ │ └─ types/ # 公共类型定义
│ ├─ auth.ts # NextAuth 配置(根模块)
│ ├─ proxy.ts # Next.js 16 代理(原 middleware.ts
│ └─ env.mjs # 环境变量校验(@t3-oss/env-nextjs + Zod
├─ tests/ # 测试目录
│ ├─ e2e/ # Playwright E2E 测试
│ ├─ integration/ # 集成测试
│ ├─ visual/ # 视觉回归测试
│ └─ setup/ # 测试 setup
├─ scripts/ # 运维脚本db/backup/security/dr
├─ docs/ # 文档(架构/审查/专题/设计)
├─ drizzle/ # 数据库迁移
└─ .gitea/workflows/ # CI/CD 流水线
```
### 2.2 模块标准结构
每个业务模块遵循**统一结构**,职责分离:
```
src/modules/[module]/
├─ actions.ts # Server Actions编排层权限校验 + 调用 data-access + revalidate
├─ data-access.ts # 数据访问层DB CRUD仅服务端可拆分为多个 data-access-*.ts
├─ schema.ts # Zod 验证 schema可选按需
├─ types.ts # 模块类型定义
├─ components/ # 模块专属组件
│ └─ [feature]/ # 复杂功能可分子目录
└─ hooks/ # 模块专属 Hook可选
```
**分层规则**(严格单向依赖):
```
app/ ──▶ modules/ ──▶ shared/
禁止反向依赖
```
- `app/` 只能调用 `modules/` 的 Server Actions 和 data-access 函数,**不直接访问 DB**
- `modules/` 之间通过对方 data-access 通信,**不直接查询对方 DB 表**
- `shared/` 是被依赖方,**不得反向依赖** `@/auth``@/proxy` 或任何 `modules/*`
- `src/auth.ts``src/proxy.ts` 位于根目录,属于应用层
**文件拆分规则**(当 data-access 过大时):
```
src/modules/classes/
├─ data-access.ts # 核心 CRUD
├─ data-access-stats.ts # 统计查询
├─ data-access-schedule.ts # 课表查询
└─ data-access-grades.ts # 成绩汇总
```
### 2.3 路由组织
- 使用路由组 `(group)` 组织相同布局的页面,不影响 URL
- 动态路由 `[slug]`、捕获所有 `[...slug]`、可选捕获 `[[...slug]]` 按需使用
- 路由命名一律使用**小写与连字符**`user-profile`
- 每个路由段应提供 `loading.tsx`(骨架屏)和 `error.tsx`(错误边界)
### 2.4 核心原则
- 页面组件(`page.tsx`)必须使用**默认导出**;布局、加载、错误使用**具名导出**
- 每个路由段都必须提供 `error.tsx`,不得出现未捕获异常导致白屏
- `loading.tsx` 必须提供骨架屏或最小可感知的加载状态,**不得使用全局 spin 遮罩**
- 服务端数据获取通过模块的 `data-access.ts`,标记 `import "server-only"` 以防客户端误用
---
## 三、命名规范
### 3.1 文件与目录
| 对象 | 命名风格 | 示例 |
|------|---------|------|
| 目录 | kebab-case | `user-profile/`, `class-detail/` |
| 组件文件 | PascalCase | `UserProfile.tsx`, `ExamForm.tsx` |
| Hook 文件 | camelCase | `useAuth.ts`, `useExamPreview.ts` |
| 工具函数文件 | camelCase | `formatCurrency.ts`, `auditLogger.ts` |
| 类型定义文件 | camelCase | `user.ts`, `permissions.ts` |
| 测试文件 | `*.test.ts` / `*.spec.ts` | `utils.test.ts`, `auth.spec.ts` |
| Server Action | `actions.ts``xxx-actions.ts` | `actions.ts`, `actions-analytics.ts` |
| Data Access | `data-access.ts``data-access-*.ts` | `data-access.ts`, `data-access-stats.ts` |
| 代理Next.js 16 | `proxy.ts` | `src/proxy.ts` |
| 常量文件 | camelCase | `navigation.ts`, `permissions.ts` |
| 环境变量 | UPPER_SNAKE_CASE | `DATABASE_URL`, `NEXTAUTH_SECRET` |
### 3.2 变量、函数、类
- **变量**camelCase。布尔值用 `is/has/can/should` 前缀(`isVisible`, `hasError`
- **函数**camelCase动词开头`fetchUser`, `handleSubmit`, `validateForm`
- **常量**UPPER_SNAKE_CASE`MAX_RETRY_COUNT`, `API_BASE_URL`
- **类与接口**PascalCase。接口**不加** `I` 前缀Google 风格)
- **类型别名**PascalCase`type UserId = string`
- **泛型参数**:使用描述性名称,如 `TData`, `TResponse`(避免单字母 `T`
- **枚举**:推荐联合类型 + 字符串字面量tree-shaking 友好)。如必须用枚举,成员名 PascalCase
### 3.3 组件与 Props
- 组件名必须为**多词**`UserProfile` 而非 `Profile`),以免与 HTML 元素冲突
- Props 类型命名为 `组件名Props``UserProfileProps`),定义在组件文件顶部
- 事件回调 Props 使用 `on` 前缀(`onSave`, `onClose`
- `children` 必须显式声明类型 `React.ReactNode`
- **不使用 `React.FC`**,直接用函数声明 + 显式标注 props 类型Google 风格)
```tsx
// 推荐方式
interface UserCardProps {
user: User;
onSelect: (id: string) => void;
children?: React.ReactNode;
}
export function UserCard({ user, onSelect, children }: UserCardProps) {
// ...
}
```
---
## 四、TypeScript 强制规范
### 4.1 配置tsconfig.json
当前项目配置需升级以符合规范。**目标配置**
```json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"strict": true,
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"forceConsistentCasingInFileNames": true,
"isolatedModules": true,
"moduleResolution": "bundler",
"esModuleInterop": true,
"skipLibCheck": true,
"module": "ESNext",
"jsx": "react-jsx",
"incremental": true,
"noEmit": true,
"paths": {
"@/*": ["./src/*"]
}
}
}
```
**当前差异**(需逐步升级):
- `target`: `ES2017``ES2022`
- 缺失 `noUncheckedIndexedAccess`(数组/对象索引返回 `T | undefined`
- 缺失 `noImplicitReturns`(函数所有分支必须返回)
- 缺失 `noFallthroughCasesInSwitch`
### 4.2 类型规则
1. **禁止 `any`**:未知类型用 `unknown` 并做类型守卫。若极特殊情况必须使用,需 `// eslint-disable-next-line @typescript-eslint/no-explicit-any` 并注释原因
2. **优先 `interface` 描述对象形状**`type` 用于联合、交叉、映射类型
3. **不使用 `as` 断言**,除非从 `unknown` 强制转换或在测试中(需注释原因)。可用 `satisfies` 保持类型推导
4. **函数返回值必须显式标注**,特别是 `Promise<T>`
5. **可选链后禁止跟非空断言 `!`**`x?.y!` 是矛盾的)
6. **所有仅用于类型的导入必须使用 `import type`**
```typescript
import type { User, Permission } from "@/shared/types/permissions";
```
7. **避免 `object` 或 `{}` 作为类型**,使用 `Record<string, unknown>` 或具体接口
8. **泛型使用有意义的名称**;若函数只有一处使用,不一定需要泛型
### 4.3 导入顺序(强制执行)
```typescript
// 1. React 相关
import React from "react";
// 2. 第三方库
import { z } from "zod";
import { useQuery } from "@tanstack/react-query";
// 3. 内部绝对路径(使用别名 @/
import { Button } from "@/shared/components/ui/button";
import { requirePermission } from "@/shared/lib/auth-guard";
// 4. 相对路径导入
import { formatDate } from "../utils";
import { getExams } from "./data-access";
// 5. 类型导入
import type { User } from "@/shared/types/permissions";
```
使用 `eslint-plugin-import` 规则 `import/order` 自动排序,分组间空一行。
---
## 五、React 与 Next.js 组件规范
### 5.1 组件定义
- 组件必须为**纯函数**,使用 `function` 声明非箭头函数Google 风格)
- 页面组件(`page.tsx`)使用**默认导出**;其余所有组件使用**具名导出**
- **禁止在渲染期间**修改外部变量、执行网络请求、读取/写入 DOM除 ref 初始化)
### 5.2 服务端组件 vs 客户端组件
- **默认服务端组件**。只有需要交互(事件处理)、状态(`useState`/`useReducer`)、效果(`useEffect`)或浏览器 API 时,才在文件顶部添加 `"use client"` 指令
- `"use client"` 必须位于文件**第一行**,之后空一行再写代码
- 客户端组件应尽可能小而聚焦。将需要交互的局部提取为客户端组件,外层容器保持服务端渲染
- **禁止在服务端组件中使用** `useState`, `useEffect`, `onClick` 等客户端特性
```tsx
// 容器页面(服务端)
import { UserList } from "@/modules/users/components/user-list";
import { getUsers } from "@/modules/users/data-access";
export default async function UsersPage() {
const users = await getUsers();
return <UserList users={users} />;
}
```
### 5.3 组件拆分指南
本项目采用**企业级行数规范**(见 [项目规则](../../.trae/rules/project_rules.md)
| 文件类型 | 建议行数 | 硬性上限 |
|---------|---------|---------|
| 配置/常量/类型定义文件 | 无限制 | 无限制 |
| React 组件 | ≤ 500 行 | 800 行(复杂表单/大型表格) |
| Server Actions / Data Access | ≤ 800 行 | 1000 行 |
| 工具函数 | ≤ 40 行 | - |
| 自定义 Hook | ≤ 80 行 | - |
**超过建议行数时的拆分信号**
1. **语义边界**:子模块能用一个明确名称独立描述其作用
2. **状态边界**:有独立的 `useState`/`useEffect` 逻辑,或生命周期明显不同
3. **复用潜力**:某段 UI 或逻辑可能在另一页面使用
4. **复杂度预警**Hook 调用超过 3 个,或 JSX 嵌套层级超过 4 层
5. **可测试性**:若要对组件的一部分逻辑编写单元测试,说明该部分应该独立
### 5.4 Hook 规范
- 命名以 `use` 开头,驼峰式
- **单一职责**:一个 Hook 只做一件事
- 返回值使用**对象形式**(非数组),方便使用者按需提取
```ts
const { data, isLoading, error } = useUser(userId);
```
- 必须编写 JSDoc描述用途、参数、返回值和可能副作用
- 所有 `useEffect` 必须提供**清理函数**(如订阅、定时器)
- `useEffect` 依赖数组必须**完整**,不得遗漏响应式变量。若确实需要忽略,用 `// eslint-disable-next-line react-hooks/exhaustive-deps` 并注释原因
---
## 六、Tailwind CSS 规范
### 6.1 核心策略
本项目使用 **Tailwind v4**,采用 **CSS 变量设计令牌**(在 `globals.css` 中定义),而非传统的 `tailwind.config.ts` 扩展。
- **移动优先**:所有类名从无前缀(移动端)开始,逐步通过 `sm:`, `md:`, `lg:`, `xl:`, `2xl:` 增强
- **类名组织顺序**:布局 → 盒模型 → 排版 → 背景 → 边框 → 效果 → 状态
- **可读性**:当单个元素类名超过 10 个时,考虑提取为组件
### 6.2 类名编写最佳实践
使用 `cn()` 工具函数(基于 `clsx` + `tailwind-merge`)管理条件类名:
```tsx
import { cn } from "@/shared/lib/utils";
<button
className={cn(
"inline-flex items-center rounded px-4 py-2",
"text-sm font-medium text-white",
variant === "primary" && "bg-primary hover:bg-primary/90",
disabled && "cursor-not-allowed opacity-50"
)}
/>
```
**禁止模式**
- ❌ 禁止字符串拼接动态类名(`bg-${color}-500`Tailwind 无法静态分析
- ❌ 禁止使用 `!important`Tailwind 的 `!` 前缀在必须覆盖第三方样式时可使用,但需谨慎)
- ❌ 禁止使用任意值(`w-[137px]`),除非有充分理由并注释说明
### 6.3 设计令牌配置
本项目在 `src/app/globals.css` 中使用 CSS 变量定义设计令牌:
```css
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--primary: 240 5.9% 10%;
--primary-foreground: 0 0% 98%;
--destructive: 0 84.2% 60.2%;
--border: 240 5.9% 90%;
--radius: 0.5rem;
/* ... */
}
```
**所有视觉设计决策**(颜色、字号、间距)必须体现在设计令牌中,组件中不使用硬编码值。
---
## 七、数据获取与状态管理
### 7.1 数据获取分层
本项目采用**模块化 data-access 层**(替代传统 `services/`
| 层级 | 位置 | 职责 | 标记 |
|------|------|------|------|
| 服务端数据 | `modules/[module]/data-access.ts` | DB CRUD + 查询 | `import "server-only"` |
| 客户端动态数据 | TanStack Query | 缓存、重试、乐观更新 | `"use client"` |
| Server Actions | `modules/[module]/actions.ts` | 编排:权限 + 调用 data-access + revalidate | `"use server"` |
**规则**
- 服务端数据获取通过模块的 `data-access.ts` 函数
- 客户端动态数据统一使用 **TanStack Query v5+****禁止在 `useEffect` 中手写 fetch**
- 缓存策略:服务端请求必须显式设置 `next.revalidate` 或使用 `unstable_cache`,并注释缓存时长理由
### 7.2 Server Actions
**文件命名**`actions.ts``xxx-actions.ts`(如 `actions-analytics.ts`),置于对应模块目录
**强制规则**
1. 每个 Action 函数必须使用 `"use server"`(文件顶部或函数级)
2. **权限校验**:函数体内必须调用 `requirePermission()`,绝不信任客户端参数
3. **输入验证**:使用 Zod 定义 schema在 Action 入口解析,验证失败返回结构化错误
4. **返回值**:统一采用 `ActionState<T>` 类型(已定义于 `@/shared/types/action-state`
```typescript
// 当前 ActionState 定义
export type ActionState<T = void> = {
success: boolean;
message?: string;
errors?: Record<string, string[]>;
data?: T;
};
```
5. **错误处理**Action 内所有错误必须捕获并转为 `ActionState` 返回,客户端不得捕获到未处理异常
6. **缓存刷新**:使用 `revalidatePath``revalidateTag` 精确刷新相关缓存,避免全站重新验证
**标准 Action 模板**
```typescript
"use server";
import { revalidatePath } from "next/cache";
import { z } from "zod";
import { ActionState } from "@/shared/types/action-state";
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
import { Permissions } from "@/shared/types/permissions";
import { createExam, persistExamDraft } from "./data-access";
const ExamCreateSchema = z.object({
title: z.string().min(1),
// ...
});
export async function createExamAction(
_prev: ActionState,
formData: FormData
): Promise<ActionState<{ id: string }>> {
try {
const ctx = await requirePermission(Permissions.EXAM_CREATE);
const parsed = ExamCreateSchema.safeParse(Object.fromEntries(formData));
if (!parsed.success) {
return {
success: false,
message: "表单校验失败",
errors: parsed.error.flatten().fieldErrors,
};
}
const exam = await createExam(ctx.userId, parsed.data);
revalidatePath("/teacher/exams");
return { success: true, data: { id: exam.id }, message: "创建成功" };
} catch (error) {
if (error instanceof PermissionDeniedError) {
return { success: false, message: "权限不足" };
}
return { success: false, message: "创建失败,请重试" };
}
}
```
### 7.3 状态管理
| 场景 | 方案 |
|------|------|
| 局部 UI 状态 | `useState` / `useReducer` |
| 跨组件共享(小范围) | React Context |
| 跨组件共享(大范围) | Zustand轻量、可选择订阅 |
| 全局状态 | 仅存放真正全局必要数据(认证信息、主题、通知列表) |
| URL 状态 | `nuqs`(已集成) |
**规则**
- Context 拆分:一个 Context 只负责一类数据,避免无关状态变化引发不必要的渲染
- 业务数据一律通过路由参数或 TanStack Query 获取,**不存入全局状态**
- Zustand 的 `persist` 中间件必须处理版本迁移和敏感数据加密
---
## 八、路由、代理与安全
### 8.1 路由组织
- 使用路由组 `(group)` 组织相同布局的页面,不影响 URL
- 动态路由 `[slug]`、捕获所有 `[...slug]`、可选捕获 `[[...slug]]` 按需使用
- 路由命名一律使用**小写与连字符**`user-profile`
### 8.2 代理proxy.ts
> **Next.js 16 重要变更**`middleware.ts` 已重命名为 `proxy.ts`。本项目使用 `src/proxy.ts`。
- 集中处理**身份验证**、**权限路由**、**API 权限**
- 代理逻辑必须**轻量**(执行时间 < 50ms复杂逻辑委托给 API 或服务端组件
- 使用 `NextResponse` 提供统一错误响应,**不做重型数据库操作**
当前 `proxy.ts` 实现:
- 路由前缀 → 最低权限映射(`/admin``school:manage``/teacher``exam:read`,等)
- API 路由前缀 → 权限映射(`/api/ai/chat``ai:chat`
- 未认证 → 重定向登录;权限不足 → 重定向默认页
### 8.3 安全规范
1. **XSS 防护**JSX 默认转义,**禁止 `dangerlySetInnerHTML`**。如必须使用,必须先用 DOMPurify 清洗
2. **CSRF 防护**所有状态变更操作POST/PUT/DELETE必须校验 Origin/Referer 头。Next.js Server Actions 默认通过 POST 发送,仍需应用层校验
3. **认证令牌**JWT/session ID 存储在 `httpOnly``Secure``SameSite=Strict` 的 Cookie 中,前端不可读
4. **环境变量**
- 服务端变量**不加** `NEXT_PUBLIC_` 前缀
- 客户端变量**必须加** `NEXT_PUBLIC_` 前缀,且仅暴露非敏感信息
- 使用 `@t3-oss/env-nextjs` + Zod 在应用启动时验证环境变量(已实现于 `src/env.mjs`
5. **权限校验**
- Server Action 必须使用 `requirePermission()` 进行权限校验
- 前端组件**禁止使用** `role === "xxx"` 硬编码,统一使用 `usePermission().hasPermission()`
6. **依赖扫描**CI 中集成 `npm audit` + Snyk + Trivy高危漏洞阻断合并
---
## 九、错误处理与可观测性
1. **错误边界**:每个路由段必须有 `error.tsx`;全局兜底 `global-error.tsx`
2. **结构化日志**:服务端日志使用 `shared/lib/audit-logger.ts``change-logger.ts``login-logger.ts`,记录关键操作
3. **性能监控**:通过 `web-vitals` 库上报 CLS、FID、LCP 到监控平台;生产环境开启 Next.js 内置分析
4. **API 速率限制**:对公开 API 或 Server Action 实施速率限制(`shared/lib/rate-limit.ts`),防止滥用
5. **审计日志**
- 登录日志:`login-logger.ts` 记录所有登录尝试(成功/失败)
- 数据变更日志:`change-logger.ts` 记录所有数据变更操作
- 审计日志:`audit-logger.ts` 记录关键业务操作
---
## 十、测试规范
### 10.1 测试分层
| 层级 | 工具 | 覆盖率目标 | 位置 |
|------|------|-----------|------|
| 单元测试 | Vitest | 100%(工具函数) | 同目录 `*.test.ts` |
| 组件测试 | React Testing Library + Vitest | ≥ 80% | 同目录 `*.test.tsx` |
| 集成测试 | Vitest + jsdom | 关键流程 | `tests/integration/` |
| E2E 测试 | Playwright | 核心业务路径 | `tests/e2e/` |
| 视觉回归 | Playwrightvisual-chromium 项目) | 关键页面 | `tests/visual/` |
### 10.2 编写规范
- 测试文件与源文件**同目录**,命名为 `*.test.ts(x)``*.spec.ts(x)`
- 使用 `describe`/`it` 结构,描述应说明预期行为:`it("should disable button while loading")`
- 查询元素优先使用 `byRole`(符合无障碍),其次 `byLabelText`;避免 `byTestId` 除非必要
- 异步交互必须用 `waitFor``findBy*`**禁止固定 `setTimeout`**
- Mock 仅用于外部边界API、数据库组件自身逻辑须真实运行
### 10.3 测试命令
```bash
npm run test:unit # 单元测试
npm run test:integration # 集成测试
npm run test:e2e # E2E 测试
npm run test:visual # 视觉回归测试
npm run test # 全部测试
```
---
## 十一、Git 工作流与提交规范
### 11.1 分支策略
- `main` 分支受保护,禁止直接推送
- 功能分支:`feat/JIRA-123-add-user-avatar`
- 修复分支:`fix/JIRA-456-correct-total`
- 发布分支:`release/v1.2.0`
- 热修复:`hotfix/security-patch`
### 11.2 提交信息Conventional Commits
```
feat(dashboard): add revenue chart
fix(cart): handle empty cart on checkout
chore(deps): update next to 16.0.0
docs(readme): add local setup guide
style(button): adjust hover color
refactor(utils): extract date formatting
test(checkout): cover discount edge cases
```
- 类型必须是 `feat`, `fix`, `chore`, `docs`, `style`, `refactor`, `test`, `perf`, `ci` 之一
- 范围可选但推荐(小写,与功能域对应)
- 提交信息使用**中文或英文**均可,但需与项目现有风格一致
### 11.3 质量门禁
- **提交前**:必须运行 `npm run lint``npx tsc --noEmit` 确保零错误
- **pre-commit**(推荐配置 Husky + lint-staged对暂存文件执行 `eslint --fix`, `prettier --write`
- **commit-msg**(推荐配置 commitlint检查提交信息格式
---
## 十二、CI/CD 流水线
当前项目使用 **Gitea Actions**`.gitea/workflows/`
| 工作流 | 文件 | 用途 |
|--------|------|------|
| CI | `ci.yml` | Lint + 类型检查 + 单元测试 + 集成测试 + 构建 + 安全扫描 |
| 安全扫描 | `security.yml` | 每周深度扫描npm audit + Snyk + Trivy + OWASP ZAP |
| 灾备演练 | `dr-drill.yml` | 每周灾备演练 |
**CI 必须包含**
1. 安装依赖(`npm ci`,利用缓存)
2. Lint 检查ESLint
3. 类型检查(`tsc --noEmit`
4. 单元测试Vitest + 覆盖率报告)
5. 构建(`next build`,确保 standalone 输出)
6. 安全审计(`npm audit` + Snyk + Trivy
7. E2E 测试(仅主分支,部署 staging 后运行 Playwright
---
## 十三、可访问性A11y规范
**目标**WCAG 2.2 AA 合规
### 13.1 强制规则
1. **ESLint 插件**`eslint-plugin-jsx-a11y` 设置为 `error`(待集成)
2. **键盘导航**:所有交互元素可用 Tab 访问Enter/Space 激活Escape 关闭弹层。焦点管理必须合理(弹窗打开时焦点移入,关闭后复原)
3. **语义化 HTML**:使用 `<header>`, `<main>`, `<nav>`, `<footer>`,按钮必须是 `<button>`(非 `div`
4. **图片**:必须提供 `alt` 属性;装饰性图片用 `alt=""`
5. **表单**:每个输入元素关联 `<label>`,必填项明确标识
6. **色彩对比度**:文本与背景至少 4.5:1普通文本或 3:1大文本
7. **动态内容**:使用 `aria-live` 区域通知屏幕阅读器
### 13.2 已实现的 A11y 工具
- `src/shared/lib/a11y.ts``useA11yId`, `mergeA11yProps`, `describeInput`, `loadingAria`
- `src/shared/hooks/use-aria-live.ts`aria-live 区域管理 Hook
- `src/shared/components/a11y/``skip-link`, `visually-hidden`, `focus-trap`, `aria-status`
- UI 组件table, dialog已增强系统性 ARIA role
### 13.3 工具检查
- CI 中可集成 `@axe-core/playwright` 做自动化检查
- 视觉回归测试(`tests/visual/`)覆盖关键页面的渲染一致性
---
## 十四、文档与交付物
### 14.1 项目必写文档
| 文档 | 位置 | 状态 |
|------|------|------|
| README.md | 根目录 | ✅ 待更新(当前为默认模板) |
| 架构文档 | `docs/architecture/` | ✅ 已完善001-007 |
| 文档索引 | `docs/README.md` | ✅ 已创建 |
| 工作日志 | `docs/work_log.md` | ✅ 持续维护 |
| CONTRIBUTING.md | 根目录 | ❌ 待创建 |
| CHANGELOG.md | 根目录 | ❌ 待创建 |
### 14.2 架构文档维护规则
**任何源码修改后,必须同步更新架构文档**
| 修改场景 | 需更新文档 |
|---------|-----------|
| 新增/删除/重命名导出函数、组件、Hook、类型 | 004 + 005 |
| 修改函数签名(参数、返回类型) | 004 + 005 |
| 修改权限点或角色-权限映射 | 004 + 005 |
| 新增/删除数据库表 | 004 + 005 |
| 新增/删除路由页面或 API 路由 | 004 + 005 |
| 修改模块间依赖关系 | 004 + 005 |
| 新增模块 | 004 + 005 + 006 |
### 14.3 可交付物清单
- 源代码仓库(完整提交历史)
- CI/CD 配置文件(`.gitea/workflows/`
- 数据库迁移脚本(`drizzle/`
- 环境配置模板(`.env.example`
- 测试报告与覆盖率数据
- 安全扫描报告(`scripts/security-scan.sh`
- 运维手册(`docs/dr/``scripts/backup-*.sh`
---
## 十五、统一工具配置
### 15.1 ESLint当前配置 + 建议增强)
**当前配置**`eslint.config.mjs`
```javascript
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
{
rules: {
"react-hooks/incompatible-library": "off",
},
},
// ...
]);
export default eslintConfig;
```
**建议增强**(待逐步集成):
```javascript
{
extends: [
"next/core-web-vitals",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
rules: {
"@typescript-eslint/no-explicit-any": "error",
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [2, { "namedComponents": "function-declaration" }],
"import/order": ["error", {
"groups": ["builtin", "external", "internal", "parent", "sibling", "index", "type"],
"newlines-between": "always"
}]
}
}
```
### 15.2 Prettier
**建议配置**`.prettierrc`,待创建):
```json
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "all",
"printWidth": 100,
"arrowParens": "always",
"plugins": ["prettier-plugin-tailwindcss"]
}
```
### 15.3 lint-staged建议配置
```json
{
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{css,scss}": ["prettier --write"]
}
```
---
## 十六、代码审查清单
审查者必须逐一确认:
### 16.1 架构与设计
- [ ] 命名表意清晰,无歧义
- [ ] 类型安全:无 `any`,无多余断言
- [ ] 组件拆分合理,无巨型组件(>500 行需讨论,>800 行必须拆分)
- [ ] 单一职责:每个文件/函数/组件只做一件事
- [ ] 模块封装:无跨模块直接 DB 查询(通过 data-access 通信)
### 16.2 实现质量
- [ ] 使用 Tailwind 正确,设计令牌符合主题,无任意值
- [ ] 数据获取:客户端用 TanStack Query服务端用 data-access
- [ ] Server Actions 有权限校验(`requirePermission`),输入 Zod 验证,返回 `ActionState`
- [ ] 错误边界完善:`error.tsx`, `loading.tsx`, 网络异常处理
- [ ] 可访问性焦点、键盘、ARIA、对比度
### 16.3 安全与合规
- [ ] 无 XSS 风险(无 `dangerlySetInnerHTML`
- [ ] Cookie 安全httpOnly + Secure + SameSite
- [ ] 环境变量未泄露(服务端变量无 `NEXT_PUBLIC_` 前缀)
- [ ] 权限校验到位(前端 `usePermission`,后端 `requirePermission`
### 16.4 测试与文档
- [ ] 关键逻辑有单元测试,交互有组件测试
- [ ] 新增组件有 Storybook如适用
- [ ] 复杂逻辑有注释
- [ ] 架构文档已同步更新004 + 005
- [ ] 提交信息规范,无无关文件混入
---
## 附录:与原规范的差异说明
本规范基于通用 Next.js 企业级规范适配,主要差异:
| 项目 | 通用规范 | 本项目 | 原因 |
|------|---------|-------|------|
| 项目结构 | Monorepo (Turborepo) | 单应用 + 模块化 | 项目规模适中,模块化已满足需求 |
| 数据获取层 | `services/` | `modules/[module]/data-access.ts` | 模块封装更好,避免跨模块直查 DB |
| 中间件 | `middleware.ts` | `proxy.ts` | Next.js 16 重命名 |
| 组件行数限制 | 300 行 | 500 行(硬上限 800 | 企业级 K12 系统表单/表格较复杂 |
| Actions 行数限制 | 无 | 800 行(硬上限 1000 | 编排层包含权限+验证+调用,需更多空间 |
| Tailwind 配置 | `tailwind.config.ts` 扩展 | CSS 变量Tailwind v4 | Tailwind v4 推荐方式 |
| ActionState | `ActionResult<T>` 联合类型 | `ActionState<T>` 对象类型 | 已有实现,保持兼容 |
| 状态管理 | Zustand + Context | Zustand + Context + nuqs | URL 状态用 nuqs 更适合 Next.js |
| 环境变量校验 | Zod 自定义 | `@t3-oss/env-nextjs` + Zod | 已实现,更简洁 |

View File

@@ -2,6 +2,44 @@
## 2026-06-17
### 编码规范适配与配置完善
#### 问题背景
用户提供了一份通用 Next.js 企业级编码规范,需要结合当前项目实际情况进行适配。
#### 完成工作
##### 1. 创建适配后的编码规范文档
- 新增 `docs/standards/coding-standards.md`16 章节,全面适配当前项目)
- 适配要点:
- 项目结构:保留单应用 + 模块化架构(非 Monorepo
- 数据获取层:保留 `modules/[module]/data-access.ts`(非 `services/`
- 中间件:使用 `proxy.ts`Next.js 16 重命名,非 `middleware.ts`
- 行数限制:保留企业级规范(组件 500/800Actions 800/1000
- Tailwind保留 v4 CSS 变量设计令牌方式
- ActionState保留现有 `ActionState<T>` 类型(非 `ActionResult` 联合类型)
- 环境变量:保留 `@t3-oss/env-nextjs` + Zod已实现
- 新增"与原规范的差异说明"附录,列出 10 项差异及原因
##### 2. 更新项目规则
- 更新 `.trae/rules/project_rules.md`
- 新增"编码规范"章节,引用 `docs/standards/coding-standards.md`
- 新增架构分层规则、模块标准结构、TypeScript 规则、命名规范、组件规范、Server Action 规范、Tailwind 规范、安全规范、提交规范
- 架构文档清单新增解耦路线图
- 行数规范新增工具函数 ≤40 行、自定义 Hook ≤80 行
##### 3. 补充缺失的配置文件
- 新增 `.prettierrc`匹配现有代码风格双引号、无分号、2 空格、printWidth 100
- 配置 `prettier-plugin-tailwindcss` 插件(已在 devDependencies 中)
##### 4. 更新文档索引
- `docs/README.md` 新增"编码规范"章节,登记 coding-standards.md 和 project_rules.md
#### 验证
- 待验证 lint + tsc
---
### 架构全面审查与文档重构
#### 问题背景