docs: 全文档合规检查与修正 - 代码示例规范/行数准确性/路径一致性/状态同步
This commit is contained in:
@@ -203,12 +203,13 @@
|
|||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useAriaLive } from "@/shared/hooks/use-aria-live"
|
import { useAriaLive } from "@/shared/hooks/use-aria-live"
|
||||||
|
|
||||||
function MyForm() {
|
function MyForm(): JSX.Element {
|
||||||
const { announce, liveRegion } = useAriaLive()
|
const { announce, liveRegion } = useAriaLive()
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async (): Promise<void> => {
|
||||||
const result = await submitAction()
|
const result = await submitAction()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
announce("保存成功", { politeness: "polite" })
|
announce("保存成功", { politeness: "polite" })
|
||||||
@@ -231,7 +232,7 @@ function MyForm() {
|
|||||||
```tsx
|
```tsx
|
||||||
import { useA11yId, describeInput } from "@/shared/lib/a11y"
|
import { useA11yId, describeInput } from "@/shared/lib/a11y"
|
||||||
|
|
||||||
function EmailField({ error }: { error?: string }) {
|
function EmailField({ error }: { error?: string }): JSX.Element {
|
||||||
const hintId = useA11yId("email-hint")
|
const hintId = useA11yId("email-hint")
|
||||||
const errorId = useA11yId("email-error")
|
const errorId = useA11yId("email-error")
|
||||||
const { ariaDescribedBy, ariaInvalid } = describeInput(
|
const { ariaDescribedBy, ariaInvalid } = describeInput(
|
||||||
@@ -263,7 +264,13 @@ function EmailField({ error }: { error?: string }) {
|
|||||||
```tsx
|
```tsx
|
||||||
import { FocusTrap } from "@/shared/components/a11y/focus-trap"
|
import { FocusTrap } from "@/shared/components/a11y/focus-trap"
|
||||||
|
|
||||||
function CustomModal({ open, onClose, children }) {
|
interface CustomModalProps {
|
||||||
|
open: boolean
|
||||||
|
onClose: () => void
|
||||||
|
children: React.ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
function CustomModal({ open, onClose, children }: CustomModalProps): JSX.Element {
|
||||||
return (
|
return (
|
||||||
<FocusTrap active={open} restoreFocus>
|
<FocusTrap active={open} restoreFocus>
|
||||||
<div role="dialog" aria-modal="true">
|
<div role="dialog" aria-modal="true">
|
||||||
|
|||||||
@@ -154,7 +154,7 @@ AIProvider (zhipu / openai / gemini / custom) │
|
|||||||
│ ├── loading.tsx (Suspense 边界) │
|
│ ├── loading.tsx (Suspense 边界) │
|
||||||
│ └── error.tsx (错误边界) │
|
│ └── error.tsx (错误边界) │
|
||||||
├─────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────┤
|
||||||
│ Feature Modules (src/modules/) — 26 个模块 │
|
│ Feature Modules (src/modules/) — 25 个模块 │
|
||||||
│ ├── auth/ 认证模块 │
|
│ ├── auth/ 认证模块 │
|
||||||
│ ├── exams/ 考试模块 │
|
│ ├── exams/ 考试模块 │
|
||||||
│ ├── homework/ 作业模块 │
|
│ ├── homework/ 作业模块 │
|
||||||
@@ -362,7 +362,7 @@ AI 模式: 选择 AI Provider → 粘贴试卷源文本 → AI 解析生成 →
|
|||||||
| 维度 | 数量 |
|
| 维度 | 数量 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| 数据库表 | 54 |
|
| 数据库表 | 54 |
|
||||||
| 业务模块 | 26 |
|
| 业务模块 | 25 |
|
||||||
| UI 基础组件 | 30+ |
|
| UI 基础组件 | 30+ |
|
||||||
| 路由页面 | 60+ |
|
| 路由页面 | 60+ |
|
||||||
| API 路由 | 9 |
|
| API 路由 | 9 |
|
||||||
|
|||||||
@@ -491,13 +491,13 @@ async function resolveDataScope(userId: string, roleNames: string[]): Promise<Da
|
|||||||
export function applyDataScope(
|
export function applyDataScope(
|
||||||
scope: DataScope,
|
scope: DataScope,
|
||||||
options: {
|
options: {
|
||||||
creatorIdField?: any // 如 exams.creatorId
|
creatorIdField?: unknown // 如 exams.creatorId
|
||||||
classIdField?: any // 如 classes.id
|
classIdField?: unknown // 如 classes.id
|
||||||
gradeIdField?: any // 如 grades.id
|
gradeIdField?: unknown // 如 grades.id
|
||||||
studentIdField?: any // 如 classEnrollments.studentId
|
studentIdField?: unknown // 如 classEnrollments.studentId
|
||||||
}
|
}
|
||||||
): any[] {
|
): unknown[] {
|
||||||
const conditions: any[] = []
|
const conditions: unknown[] = []
|
||||||
|
|
||||||
switch (scope.type) {
|
switch (scope.type) {
|
||||||
case "all":
|
case "all":
|
||||||
@@ -538,7 +538,14 @@ export { PermissionDeniedError }
|
|||||||
import { useSession } from "next-auth/react"
|
import { useSession } from "next-auth/react"
|
||||||
import type { Permission } from "@/shared/types/permissions"
|
import type { Permission } from "@/shared/types/permissions"
|
||||||
|
|
||||||
export function usePermission() {
|
export function usePermission(): {
|
||||||
|
permissions: Permission[]
|
||||||
|
roles: string[]
|
||||||
|
hasPermission: (permission: Permission) => boolean
|
||||||
|
hasAnyPermission: (...perms: Permission[]) => boolean
|
||||||
|
hasAllPermissions: (...perms: Permission[]) => boolean
|
||||||
|
hasRole: (role: string) => boolean
|
||||||
|
} {
|
||||||
const { data: session } = useSession()
|
const { data: session } = useSession()
|
||||||
const permissions = session?.user?.permissions ?? []
|
const permissions = session?.user?.permissions ?? []
|
||||||
const roles = session?.user?.roles ?? []
|
const roles = session?.user?.roles ?? []
|
||||||
@@ -581,7 +588,7 @@ export async function deleteExamAction(prevState, formData) {
|
|||||||
import { requirePermission, getAuthContext, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
import { requirePermission, getAuthContext, PermissionDeniedError } from "@/shared/lib/auth-guard"
|
||||||
import { Permissions } from "@/shared/types/permissions"
|
import { Permissions } from "@/shared/types/permissions"
|
||||||
|
|
||||||
export async function deleteExamAction(prevState, formData) {
|
export async function deleteExamAction(prevState, formData): Promise<ActionState> {
|
||||||
try {
|
try {
|
||||||
const ctx = await requirePermission(Permissions.EXAM_DELETE)
|
const ctx = await requirePermission(Permissions.EXAM_DELETE)
|
||||||
const { examId } = parsed.data
|
const { examId } = parsed.data
|
||||||
@@ -657,7 +664,7 @@ const API_PERMISSIONS: Record<string, string> = {
|
|||||||
"/api/onboarding": "auth_required", // 特殊标记:仅需登录
|
"/api/onboarding": "auth_required", // 特殊标记:仅需登录
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function middleware(request: NextRequest) {
|
export async function middleware(request: NextRequest): Promise<NextResponse> {
|
||||||
const token = await getToken({ req: request })
|
const token = await getToken({ req: request })
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
@@ -805,7 +812,7 @@ export async function createExamAction(prevState, formData) {
|
|||||||
#### 改造后 — createExamAction
|
#### 改造后 — createExamAction
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export async function createExamAction(prevState, formData) {
|
export async function createExamAction(prevState, formData): Promise<ActionState> {
|
||||||
let ctx: AuthContext
|
let ctx: AuthContext
|
||||||
try {
|
try {
|
||||||
ctx = await requirePermission(Permissions.EXAM_CREATE)
|
ctx = await requirePermission(Permissions.EXAM_CREATE)
|
||||||
@@ -841,7 +848,7 @@ export async function deleteExamAction(prevState, formData) {
|
|||||||
#### 改造后 — deleteExamAction
|
#### 改造后 — deleteExamAction
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export async function deleteExamAction(prevState, formData) {
|
export async function deleteExamAction(prevState, formData): Promise<ActionState> {
|
||||||
let ctx: AuthContext
|
let ctx: AuthContext
|
||||||
try {
|
try {
|
||||||
ctx = await requirePermission(Permissions.EXAM_DELETE)
|
ctx = await requirePermission(Permissions.EXAM_DELETE)
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ import { useTransition } from "react"
|
|||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import type { ActionState } from "@/shared/types/action-state"
|
import type { ActionState } from "@/shared/types/action-state"
|
||||||
|
|
||||||
export function useActionWithToast<T>() {
|
export function useActionWithToast<T>(): { isPending: boolean; execute: (action: () => Promise<ActionState<T>>) => Promise<void> } {
|
||||||
const [isPending, startTransition] = useTransition()
|
const [isPending, startTransition] = useTransition()
|
||||||
|
|
||||||
const execute = async (action: () => Promise<ActionState<T>>) => {
|
const execute = async (action: () => Promise<ActionState<T>>): Promise<void> => {
|
||||||
startTransition(async () => {
|
startTransition(async () => {
|
||||||
const result = await action()
|
const result = await action()
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
@@ -362,7 +362,7 @@ import rehypeSanitize from "rehype-sanitize"
|
|||||||
#### 方案
|
#### 方案
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
export function formatDate(date: string | Date, locale = "zh-CN") {
|
export function formatDate(date: string | Date, locale = "zh-CN"): string {
|
||||||
return new Intl.DateTimeFormat(locale, {
|
return new Intl.DateTimeFormat(locale, {
|
||||||
year: "numeric",
|
year: "numeric",
|
||||||
month: "short",
|
month: "short",
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
- ❌ P0:`shared/lib/*` ↔ `@/auth` 循环依赖
|
- ❌ P0:`shared/lib/*` ↔ `@/auth` 循环依赖
|
||||||
- ⚠️ P1:`schema.ts` 1111 行(54 张表混合,超 1000 硬上限)
|
- ⚠️ P1:`schema.ts` 1111 行(54 张表混合,超 1000 硬上限)
|
||||||
- ✅ P1:~~`auth.ts` 293 行混合 5 类职责~~ 已拆分(4 个辅助函数组迁移至 `shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}`,auth.ts 仅保留 NextAuth 配置)
|
- ✅ P1:~~`auth.ts` 293 行混合 5 类职责~~ 已拆分(4 个辅助函数组迁移至 `shared/lib/{role-utils,bcrypt-utils,http-utils,password-security-service}`,auth.ts 仅保留 NextAuth 配置)
|
||||||
- ✅ P2-2 已修复:~~`ai.ts` 218 行混合 5 类职责~~ 已拆分为 `ai/` 目录(payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts),原 `ai.ts` 保留为向后兼容的重导出文件(12 行)
|
- ✅ P2-2 已修复:~~`ai.ts` 218 行混合 5 类职责~~ 已拆分为 `ai/` 目录(payload-parser.ts/api-key-crypto.ts/provider-config.ts/client.ts/errors.ts/index.ts),原 `ai.ts` 保留为向后兼容的重导出文件(9 行)
|
||||||
- ⚠️ P2:`onboarding-gate.tsx` 业务逻辑泄漏到 shared
|
- ⚠️ P2:`onboarding-gate.tsx` 业务逻辑泄漏到 shared
|
||||||
|
|
||||||
**文件清单**:
|
**文件清单**:
|
||||||
@@ -383,29 +383,29 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
| `db/index.ts` | - | Drizzle 客户端 |
|
| `db/index.ts` | - | Drizzle 客户端 |
|
||||||
| `lib/auth-guard.ts` | - | 认证上下文 + 权限校验 + DataScope |
|
| `lib/auth-guard.ts` | - | 认证上下文 + 权限校验 + DataScope |
|
||||||
| `lib/permissions.ts` | - | 角色-权限映射 |
|
| `lib/permissions.ts` | - | 角色-权限映射 |
|
||||||
| `lib/ai.ts` | 12 | 向后兼容重导出(P2-2 已拆分到 `ai/` 目录) |
|
| `lib/ai.ts` | 9 | 向后兼容重导出(P2-2 已拆分到 `ai/` 目录) |
|
||||||
| `lib/ai/payload-parser.ts` | 96 | 请求负载解析 |
|
| `lib/ai/payload-parser.ts` | 78 | 请求负载解析 |
|
||||||
| `lib/ai/api-key-crypto.ts` | 34 | API Key 加密/解密 |
|
| `lib/ai/api-key-crypto.ts` | 28 | API Key 加密/解密 |
|
||||||
| `lib/ai/provider-config.ts` | 66 | Provider 配置查询 |
|
| `lib/ai/provider-config.ts` | 61 | Provider 配置查询 |
|
||||||
| `lib/ai/client.ts` | 67 | AI 客户端创建与调用 |
|
| `lib/ai/client.ts` | 58 | AI 客户端创建与调用 |
|
||||||
| `lib/ai/errors.ts` | 9 | 错误格式化 |
|
| `lib/ai/errors.ts` | 8 | 错误格式化 |
|
||||||
| `lib/ai/index.ts` | 7 | 聚合导出 |
|
| `lib/ai/index.ts` | 5 | 聚合导出 |
|
||||||
| `lib/audit-logger.ts` | - | 操作日志 |
|
| `lib/audit-logger.ts` | - | 操作日志 |
|
||||||
| `lib/change-logger.ts` | - | 数据变更日志 |
|
| `lib/change-logger.ts` | - | 数据变更日志 |
|
||||||
| `lib/login-logger.ts` | - | 登录日志 |
|
| `lib/login-logger.ts` | - | 登录日志 |
|
||||||
| `lib/password-policy.ts` | - | 密码策略纯函数 |
|
| `lib/password-policy.ts` | - | 密码策略纯函数 |
|
||||||
| `lib/rate-limit.ts` | - | 内存滑动窗口限流 |
|
| `lib/rate-limit.ts` | - | 内存滑动窗口限流 |
|
||||||
| `lib/role-utils.ts` | 35 | 角色规范化纯函数(normalizeRole / resolvePrimaryRole) |
|
| `lib/role-utils.ts` | 31 | 角色规范化纯函数(normalizeRole / resolvePrimaryRole) |
|
||||||
| `lib/bcrypt-utils.ts` | 22 | bcrypt 哈希前缀规范化纯函数 |
|
| `lib/bcrypt-utils.ts` | 18 | bcrypt 哈希前缀规范化纯函数 |
|
||||||
| `lib/http-utils.ts` | 30 | 请求头解析(resolveClientIp,server-only) |
|
| `lib/http-utils.ts` | 27 | 请求头解析(resolveClientIp,server-only) |
|
||||||
| `lib/password-security-service.ts` | 100 | 密码安全 DB 操作(账户锁定/失败登录追踪,server-only) |
|
| `lib/password-security-service.ts` | 84 | 密码安全 DB 操作(账户锁定/失败登录追踪,server-only) |
|
||||||
| `lib/excel.ts` | - | Excel 导入导出 |
|
| `lib/excel.ts` | - | Excel 导入导出 |
|
||||||
| `lib/file-storage.ts` | - | 文件存储抽象 |
|
| `lib/file-storage.ts` | - | 文件存储抽象 |
|
||||||
| `hooks/use-permission.ts` | - | 客户端权限 Hook |
|
| `hooks/use-permission.ts` | - | 客户端权限 Hook |
|
||||||
| `components/ui/*` | 34 文件 | shadcn/ui 标准组件 |
|
| `components/ui/*` | 34 文件 | shadcn/ui 标准组件 |
|
||||||
| `components/onboarding-gate.tsx` | 312 | 引导流程(业务泄漏) |
|
| `components/onboarding-gate.tsx` | 312 | 引导流程(业务泄漏) |
|
||||||
| `components/global-search.tsx` | 221 | 全局搜索(业务泄漏) |
|
| `components/global-search.tsx` | 221 | 全局搜索(业务泄漏) |
|
||||||
| `types/permissions.ts` | 92 | 57 个权限点常量 |
|
| `types/permissions.ts` | 92 | 54 个权限点常量 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -426,15 +426,15 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
- ❌ P0:`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表
|
- ❌ P0:`persistAiGeneratedExamDraft` 直接 insert 到 `questions` 表
|
||||||
- ❌ P0:`getExams`/`getExamById` 直查 `classes` 表
|
- ❌ P0:`getExams`/`getExamById` 直查 `classes` 表
|
||||||
- ❌ P1:`getSubjectsAction`/`getGradesAction` 直查 `subjects`/`grades` 表(应属 school 模块)
|
- ❌ P1:`getSubjectsAction`/`getGradesAction` 直查 `subjects`/`grades` 表(应属 school 模块)
|
||||||
- ✅ P1-2 已修复:~~`actions.ts` 832 行(超 800 建议),多处直接 DB 操作~~ DB 操作已下沉到 data-access,actions.ts 现 766 行
|
- ✅ P1-2 已修复:~~`actions.ts` 832 行(超 800 建议),多处直接 DB 操作~~ DB 操作已下沉到 data-access,actions.ts 现 691 行
|
||||||
- ⚠️ P1:`ai-pipeline.ts` 912 行(超 800 建议),混合 4 类职责
|
- ⚠️ P1:`ai-pipeline.ts` 857 行(超 800 建议),混合 4 类职责
|
||||||
|
|
||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `actions.ts` | 766 | 10 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
| `actions.ts` | 691 | 10 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||||||
| `ai-pipeline.ts` | 912 | AI 出题管线(超限) |
|
| `ai-pipeline.ts` | 857 | AI 出题管线(超限) |
|
||||||
| `data-access.ts` | 524 | 考试 CRUD(含 P1-2 新增 7 个写/查询函数) |
|
| `data-access.ts` | 471 | 考试 CRUD(含 P1-2 新增 7 个写/查询函数) |
|
||||||
| `types.ts` | 31 | 类型定义 |
|
| `types.ts` | 31 | 类型定义 |
|
||||||
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
|
| `hooks/use-exam-preview.ts` | 295 | 预览 Hook |
|
||||||
| `components/*` | 18 文件 | 考试表单/组卷/预览组件 |
|
| `components/*` | 18 文件 | 考试表单/组卷/预览组件 |
|
||||||
@@ -456,7 +456,7 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
- 被依赖:`dashboard`(通过 data-access,合理)、`parent`(通过 data-access,合理)、`classes`(❌ classes 反向直查 homework 表)
|
- 被依赖:`dashboard`(通过 data-access,合理)、`parent`(通过 data-access,合理)、`classes`(❌ classes 反向直查 homework 表)
|
||||||
|
|
||||||
**已知问题**:
|
**已知问题**:
|
||||||
- ✅ P0 已解决:`data-access.ts` 已拆分至 596 行(原 1038 行超 1000 硬上限),统计函数迁移至 `stats-service.ts`
|
- ✅ P0 已解决:`data-access.ts` 已拆分至 598 行(原 1038 行超 1000 硬上限),统计函数迁移至 `stats-service.ts`
|
||||||
- ✅ P0 已解决:`getStudentDashboardGrades` 排名计算逻辑迁移至 `stats-service.ts`
|
- ✅ P0 已解决:`getStudentDashboardGrades` 排名计算逻辑迁移至 `stats-service.ts`
|
||||||
- ✅ P0 已解决:`getHomeworkAssignmentAnalytics` 错误率统计逻辑迁移至 `stats-service.ts`
|
- ✅ P0 已解决:`getHomeworkAssignmentAnalytics` 错误率统计逻辑迁移至 `stats-service.ts`
|
||||||
- ❌ P1:5 处直查 `exams` 表
|
- ❌ P1:5 处直查 `exams` 表
|
||||||
@@ -465,9 +465,9 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `data-access.ts` | 596 | 作业 CRUD + 学生视角 + 批改(含 re-export stats 函数) |
|
| `data-access.ts` | 598 | 作业 CRUD + 学生视角 + 批改(含 re-export stats 函数) |
|
||||||
| `data-access-write.ts` | 285 | 作业写操作(P1-2 新增,10 个写函数从 actions 下沉) |
|
| `data-access-write.ts` | 285 | 作业写操作(P1-2 新增,10 个写函数从 actions 下沉) |
|
||||||
| `stats-service.ts` | 346 | 统计分析(教师趋势/作业分析/学生仪表盘成绩) |
|
| `stats-service.ts` | 425 | 统计分析(教师趋势/作业分析/学生仪表盘成绩) |
|
||||||
| `actions.ts` | 239 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
| `actions.ts` | 239 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||||||
| `types.ts` | 186 | 类型定义 |
|
| `types.ts` | 186 | 类型定义 |
|
||||||
| `schema.ts` | 29 | Zod 校验 |
|
| `schema.ts` | 29 | Zod 校验 |
|
||||||
@@ -489,13 +489,13 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
**已知问题**:
|
**已知问题**:
|
||||||
- ✅ P1-2 已修复:~~写操作函数错放在 `actions.ts`(`insertQuestionWithRelations` / `deleteQuestionRecursive`)~~ 已下沉到 data-access(`createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`)
|
- ✅ P1-2 已修复:~~写操作函数错放在 `actions.ts`(`insertQuestionWithRelations` / `deleteQuestionRecursive`)~~ 已下沉到 data-access(`createQuestionWithRelations` / `updateQuestionById` / `deleteQuestionByIdRecursive` / `getKnowledgePointOptions`)
|
||||||
- ❌ P1:`getKnowledgePointOptionsAction` 直查 textbooks 模块表
|
- ❌ P1:`getKnowledgePointOptionsAction` 直查 textbooks 模块表
|
||||||
- ✅ P2 已解决:~~`data-access.ts` 仅 129 行,写操作缺失~~ P1-2 后 data-access.ts 扩充至 299 行
|
- ✅ P2 已解决:~~`data-access.ts` 仅 129 行,写操作缺失~~ P1-2 后 data-access.ts 扩充至 260 行
|
||||||
|
|
||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `actions.ts` | 177 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
| `actions.ts` | 149 | 5 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||||||
| `data-access.ts` | 299 | 题目 CRUD + 知识点选项(含 P1-2 新增 4 个写/查询函数) |
|
| `data-access.ts` | 260 | 题目 CRUD + 知识点选项(含 P1-2 新增 4 个写/查询函数) |
|
||||||
| `schema.ts` | 18 | Zod 校验 |
|
| `schema.ts` | 18 | Zod 校验 |
|
||||||
| `types.ts` | 34 | 类型定义 |
|
| `types.ts` | 34 | 类型定义 |
|
||||||
|
|
||||||
@@ -585,12 +585,12 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `data-access.ts` | 656 | 核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容) |
|
| `data-access.ts` | 548 | 核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容) |
|
||||||
| `data-access-stats.ts` | 604 | 作业统计查询(班级/年级作业洞察) |
|
| `data-access-stats.ts` | 531 | 作业统计查询(班级/年级作业洞察) |
|
||||||
| `data-access-schedule.ts` | 230 | 课表查询(学生/班级课表 CRUD) |
|
| `data-access-schedule.ts` | 194 | 课表查询(学生/班级课表 CRUD) |
|
||||||
| `data-access-students.ts` | 280 | 学生相关查询(科目成绩、学生名单、学生班级) |
|
| `data-access-students.ts` | 244 | 学生相关查询(科目成绩、学生名单、学生班级) |
|
||||||
| `data-access-admin.ts` | 441 | 管理员班级管理(管理员班级 CRUD、年级管理班级查询) |
|
| `data-access-admin.ts` | 406 | 管理员班级管理(管理员班级 CRUD、年级管理班级查询) |
|
||||||
| `actions.ts` | 765 | 9 个 Server Action(三组重复) |
|
| `actions.ts` | 676 | 9 个 Server Action(三组重复) |
|
||||||
| `types.ts` | 201 | 类型定义(含跨领域类型污染) |
|
| `types.ts` | 201 | 类型定义(含跨领域类型污染) |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -699,16 +699,16 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
- ✅ P1 已解决:`import-export.ts` 四重职责已拆分为 `import-export.ts`(解析/生成)+ `user-service.ts`(用户创建)+ `class-registration.ts`(班级注册)
|
- ✅ P1 已解决:`import-export.ts` 四重职责已拆分为 `import-export.ts`(解析/生成)+ `user-service.ts`(用户创建)+ `class-registration.ts`(班级注册)
|
||||||
- ✅ P1 已解决:`batchImportUsers` 不再跨模块直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
|
- ✅ P1 已解决:`batchImportUsers` 不再跨模块直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
|
||||||
- ❌ P1:`updateUserProfile` 绕过 data-access 直接 DB 写
|
- ❌ P1:`updateUserProfile` 绕过 data-access 直接 DB 写
|
||||||
- ⚠️ P2:`data-access.ts` 仅 71 行,写操作缺失
|
- ⚠️ P2:`data-access.ts` 133 行,写操作仍缺失(updateUserProfile 绕过 data-access)
|
||||||
|
|
||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `import-export.ts` | 177 | 文件解析/生成(模板生成 + 解析校验 + Excel 导出)+ re-export 向后兼容 |
|
| `import-export.ts` | 157 | 文件解析/生成(模板生成 + 解析校验 + Excel 导出)+ re-export 向后兼容 |
|
||||||
| `user-service.ts` | 96 | 用户创建(批量导入 + 密码哈希 + 角色分配) |
|
| `user-service.ts` | 82 | 用户创建(批量导入 + 密码哈希 + 角色分配) |
|
||||||
| `class-registration.ts` | 30 | 班级注册(委托 classes/data-access) |
|
| `class-registration.ts` | 21 | 班级注册(委托 classes/data-access) |
|
||||||
| `actions.ts` | 151 | 5 个 Server Action |
|
| `actions.ts` | 131 | 5 个 Server Action |
|
||||||
| `data-access.ts` | 71 | 仅 getUserProfile |
|
| `data-access.ts` | 133 | getUserProfile + 用户查询 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -845,8 +845,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
**文件清单**:
|
**文件清单**:
|
||||||
| 文件 | 行数 | 职责 |
|
| 文件 | 行数 | 职责 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `actions.ts` | 231 | 6 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
| `actions.ts` | 197 | 6 个 Server Action(P1-2 已修复,无直接 DB 操作) |
|
||||||
| `data-access.ts` | 186 | 公告 CRUD + 发布/归档(含 P1-2 新增 5 个写函数) |
|
| `data-access.ts` | 171 | 公告 CRUD + 发布/归档(含 P1-2 新增 5 个写函数) |
|
||||||
| `schema.ts` | - | Zod 校验 |
|
| `schema.ts` | - | Zod 校验 |
|
||||||
| `types.ts` | - | 类型定义 |
|
| `types.ts` | - | 类型定义 |
|
||||||
|
|
||||||
@@ -1123,8 +1123,8 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
|
|
||||||
| 文件 | 行数 | 问题 | 拆分建议 |
|
| 文件 | 行数 | 问题 | 拆分建议 |
|
||||||
|------|------|------|---------|
|
|------|------|------|---------|
|
||||||
| `classes/data-access.ts` | ~~2104~~ → 656 | ~~混入 homework/scheduling/grades 逻辑~~ ✅ 已拆分 | 已拆为 5 个文件:data-access.ts(656行) + data-access-stats.ts(604行) + data-access-schedule.ts(230行) + data-access-students.ts(280行) + data-access-admin.ts(441行),通过 re-export 保持向后兼容 |
|
| `classes/data-access.ts` | ~~2104~~ → 548 | ~~混入 homework/scheduling/grades 逻辑~~ ✅ 已拆分 | 已拆为 5 个文件:data-access.ts(548行) + data-access-stats.ts(531行) + data-access-schedule.ts(194行) + data-access-students.ts(244行) + data-access-admin.ts(406行),通过 re-export 保持向后兼容 |
|
||||||
| `homework/data-access.ts` | ~~1038~~ → 596 | ~~混入排名计算业务逻辑~~ ✅ 已拆分 | 已拆为 data-access.ts(596行) + stats-service.ts(346行),统计函数迁移至 stats-service.ts |
|
| `homework/data-access.ts` | ~~1038~~ → 598 | ~~混入排名计算业务逻辑~~ ✅ 已拆分 | 已拆为 data-access.ts(598行) + stats-service.ts(425行),统计函数迁移至 stats-service.ts |
|
||||||
| `shared/db/schema.ts` | 1111 | 54 张表混合 | 按业务域拆分为 schema/auth.ts + schema/academic.ts + schema/exam.ts + ...,通过 index.ts 聚合 |
|
| `shared/db/schema.ts` | 1111 | 54 张表混合 | 按业务域拆分为 schema/auth.ts + schema/academic.ts + schema/exam.ts + ...,通过 index.ts 聚合 |
|
||||||
|
|
||||||
### P0-2:shared/lib ↔ auth 循环依赖
|
### P0-2:shared/lib ↔ auth 循环依赖
|
||||||
@@ -1211,10 +1211,10 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
|
|
||||||
| 模块 | 问题 Action | 修复内容 |
|
| 模块 | 问题 Action | 修复内容 |
|
||||||
|------|------------|---------|
|
|------|------------|---------|
|
||||||
| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` | ✅ 新增 7 个 data-access 函数(getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades),actions.ts 831→766 行,data-access.ts 374→524 行 |
|
| exams | `updateExamAction` / `deleteExamAction` / `duplicateExamAction` / `getExamPreviewAction` / `getSubjectsAction` / `getGradesAction` | ✅ 新增 7 个 data-access 函数(getExamCreatorId/updateExamWithQuestions/deleteExamById/duplicateExam/getExamPreview/getExamSubjects/getExamGrades),actions.ts 831→691 行,data-access.ts 374→471 行 |
|
||||||
| homework | `createHomeworkAssignmentAction`(157 行)/ `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` | ✅ 新建 `data-access-write.ts`(285 行,10 个写函数),actions.ts 387→239 行 |
|
| homework | `createHomeworkAssignmentAction`(157 行)/ `startHomeworkSubmissionAction` / `saveHomeworkAnswerAction` / `submitHomeworkAction` / `gradeHomeworkSubmissionAction` | ✅ 新建 `data-access-write.ts`(285 行,10 个写函数),actions.ts 387→239 行 |
|
||||||
| questions | `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction` | ✅ 新增 4 个 data-access 函数(createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions),actions.ts 294→177 行,data-access.ts 138→299 行 |
|
| questions | `createQuestionAction` / `updateQuestionAction` / `deleteQuestionAction` / `getKnowledgePointOptionsAction` | ✅ 新增 4 个 data-access 函数(createQuestionWithRelations/updateQuestionById/deleteQuestionByIdRecursive/getKnowledgePointOptions),actions.ts 294→149 行,data-access.ts 138→260 行 |
|
||||||
| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数(insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementById),actions.ts 242→231 行,data-access.ts 120→186 行 |
|
| announcements | 所有写操作 Action | ✅ 新增 5 个 data-access 函数(insertAnnouncement/updateAnnouncementById/deleteAnnouncementById/publishAnnouncementById/archiveAnnouncementById),actions.ts 242→197 行,data-access.ts 120→171 行 |
|
||||||
|
|
||||||
**剩余未修复模块**(不在本次 P1-2 范围):
|
**剩余未修复模块**(不在本次 P1-2 范围):
|
||||||
- users:`updateUserProfileAction` 直接 db.update
|
- users:`updateUserProfileAction` 直接 db.update
|
||||||
@@ -1224,7 +1224,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
|
|
||||||
`src/auth.ts` 原 293 行混合:NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。
|
`src/auth.ts` 原 293 行混合:NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数。
|
||||||
|
|
||||||
**已完成拆分**(auth.ts 现 208 行,仅保留 NextAuth 配置):
|
**已完成拆分**(auth.ts 现 193 行,仅保留 NextAuth 配置):
|
||||||
- ✅ 密码安全 DB 操作 → `shared/lib/password-security-service.ts`(getOrCreatePasswordSecurity / recordFailedLogin / resetFailedLogin,server-only)
|
- ✅ 密码安全 DB 操作 → `shared/lib/password-security-service.ts`(getOrCreatePasswordSecurity / recordFailedLogin / resetFailedLogin,server-only)
|
||||||
- ✅ 角色规范化 → `shared/lib/role-utils.ts`(normalizeRole / resolvePrimaryRole,纯函数)
|
- ✅ 角色规范化 → `shared/lib/role-utils.ts`(normalizeRole / resolvePrimaryRole,纯函数)
|
||||||
- ✅ bcrypt 哈希规范化 → `shared/lib/bcrypt-utils.ts`(normalizeBcryptHash,纯函数)
|
- ✅ bcrypt 哈希规范化 → `shared/lib/bcrypt-utils.ts`(normalizeBcryptHash,纯函数)
|
||||||
@@ -1237,9 +1237,9 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
|
|
||||||
`users/import-export.ts` 原 291 行混合:导入解析 + 导出 + 用户创建(含密码哈希)+ 班级注册(跨模块写 classEnrollments)。
|
`users/import-export.ts` 原 291 行混合:导入解析 + 导出 + 用户创建(含密码哈希)+ 班级注册(跨模块写 classEnrollments)。
|
||||||
|
|
||||||
**已完成拆分**(import-export.ts 现 177 行,仅保留文件解析/生成):
|
**已完成拆分**(import-export.ts 现 157 行,仅保留文件解析/生成):
|
||||||
- ✅ 用户创建(含密码哈希、角色分配)→ `user-service.ts`(`batchImportUsers`,96 行,server-only)
|
- ✅ 用户创建(含密码哈希、角色分配)→ `user-service.ts`(`batchImportUsers`,82 行,server-only)
|
||||||
- ✅ 班级注册 → `class-registration.ts`(`registerStudentByInvitationCode`,30 行,server-only)
|
- ✅ 班级注册 → `class-registration.ts`(`registerStudentByInvitationCode`,21 行,server-only)
|
||||||
- ✅ `batchImportUsers` 不再直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
|
- ✅ `batchImportUsers` 不再直写 `classEnrollments`,改为调用 `classes/data-access.enrollStudentByInvitationCode`
|
||||||
- ✅ `import-export.ts` 通过 re-export `batchImportUsers` / `UserImportResult` 保持向后兼容(`actions.ts` 和 `app/api/export/route.ts` 无需修改)
|
- ✅ `import-export.ts` 通过 re-export `batchImportUsers` / `UserImportResult` 保持向后兼容(`actions.ts` 和 `app/api/export/route.ts` 无需修改)
|
||||||
|
|
||||||
@@ -1261,8 +1261,8 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
|
|
||||||
| 序号 | 问题 | 模块 |
|
| 序号 | 问题 | 模块 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| P2-1 | `exams/ai-pipeline.ts` 912 行,混合 4 类职责 | exams |
|
| P2-1 | `exams/ai-pipeline.ts` 857 行,混合 4 类职责 | exams |
|
||||||
| ~~P2-2~~ | ~~`exams/actions.ts` 832 行(超 800 建议)~~ ✅ 已修复(P1-2 后降至 766 行) | exams |
|
| ~~P2-2~~ | ~~`exams/actions.ts` 832 行(超 800 建议)~~ ✅ 已修复(P1-2 后降至 691 行) | exams |
|
||||||
| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复(P2-2 已拆分为 `ai/` 目录) | shared |
|
| ~~P2-3~~ | ~~`shared/lib/ai.ts` 218 行,混合 5 类职责~~ ✅ 已修复(P2-2 已拆分为 `ai/` 目录) | shared |
|
||||||
| P2-4 | `onboarding-gate.tsx` 业务逻辑泄漏到 shared | shared |
|
| P2-4 | `onboarding-gate.tsx` 业务逻辑泄漏到 shared | shared |
|
||||||
| P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared |
|
| P2-5 | `global-search.tsx` 业务类型硬编码在 shared | shared |
|
||||||
@@ -1287,8 +1287,8 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
## 3.4 解耦优先级路线图
|
## 3.4 解耦优先级路线图
|
||||||
|
|
||||||
### 立即执行(P0)
|
### 立即执行(P0)
|
||||||
1. ~~拆分 `classes/data-access.ts`(2104 行 → 按职责拆 3-4 个文件)~~ ✅ 已完成(拆为 5 个文件:data-access.ts 656行 + data-access-stats.ts 604行 + data-access-schedule.ts 230行 + data-access-students.ts 280行 + data-access-admin.ts 441行)
|
1. ~~拆分 `classes/data-access.ts`(2104 行 → 按职责拆 3-4 个文件)~~ ✅ 已完成(拆为 5 个文件:data-access.ts 548行 + data-access-stats.ts 531行 + data-access-schedule.ts 194行 + data-access-students.ts 244行 + data-access-admin.ts 406行)
|
||||||
2. ~~拆分 `homework/data-access.ts`(1038 行 → 分离排名逻辑)~~ ✅ 已完成(拆为 data-access.ts 596行 + stats-service.ts 346行)
|
2. ~~拆分 `homework/data-access.ts`(1038 行 → 分离排名逻辑)~~ ✅ 已完成(拆为 data-access.ts 598行 + stats-service.ts 425行)
|
||||||
3. 修复 `shared/lib` ↔ `auth` 循环依赖
|
3. 修复 `shared/lib` ↔ `auth` 循环依赖
|
||||||
4. dashboard 改为通过各模块 data-access 获取数据
|
4. dashboard 改为通过各模块 data-access 获取数据
|
||||||
5. messaging 写通知改为通过 notifications dispatcher
|
5. messaging 写通知改为通过 notifications dispatcher
|
||||||
@@ -1298,7 +1298,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
### 短期执行(P1)
|
### 短期执行(P1)
|
||||||
8. ~~actions 层移除直接 DB 操作(exams/homework/questions/announcements/users/scheduling)~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理)
|
8. ~~actions 层移除直接 DB 操作(exams/homework/questions/announcements/users/scheduling)~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理)
|
||||||
9. ~~拆分 `auth.ts`~~ ✅ 已完成(4 个辅助函数组迁移至 shared/lib,auth.ts 保留 NextAuth 配置)
|
9. ~~拆分 `auth.ts`~~ ✅ 已完成(4 个辅助函数组迁移至 shared/lib,auth.ts 保留 NextAuth 配置)
|
||||||
10. ~~拆分 `users/import-export.ts`~~ ✅ 已完成(拆为 import-export.ts 177行 + user-service.ts 96行 + class-registration.ts 30行,班级注册改为调用 classes/data-access)
|
10. ~~拆分 `users/import-export.ts`~~ ✅ 已完成(拆为 import-export.ts 157行 + user-service.ts 82行 + class-registration.ts 21行,班级注册改为调用 classes/data-access)
|
||||||
11. 消除 notifications → messaging 反向依赖
|
11. 消除 notifications → messaging 反向依赖
|
||||||
12. 提取 `shared/lib/http-utils.ts` 统一 IP 提取
|
12. 提取 `shared/lib/http-utils.ts` 统一 IP 提取
|
||||||
13. 各模块暴露跨模块查询接口(见 P1-1)
|
13. 各模块暴露跨模块查询接口(见 P1-1)
|
||||||
@@ -1392,7 +1392,7 @@ shared/lib/{audit-logger, change-logger, auth-guard} → @/auth → shared/lib/*
|
|||||||
6. 在 `auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds,构建 `DataScope.class_taught`
|
6. 在 `auth-guard.ts` 中通过 `classSubjectTeachers` 查询教师关联的 classIds,构建 `DataScope.class_taught`
|
||||||
|
|
||||||
### `permission`
|
### `permission`
|
||||||
1. 由 `shared/types/permissions.ts` 的 `Permissions` 常量定义(57 个权限点)
|
1. 由 `shared/types/permissions.ts` 的 `Permissions` 常量定义(54 个权限点)
|
||||||
2. 在 `shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表
|
2. 在 `shared/lib/permissions.ts` 中通过 `ROLE_PERMISSIONS` 映射角色到权限列表
|
||||||
3. 在 `auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT
|
3. 在 `auth.ts` JWT callback 中通过 `resolvePermissions(roleNames)` 合并多角色权限,存入 JWT
|
||||||
4. 在 `proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限
|
4. 在 `proxy.ts` middleware 中通过 `token.permissions` 检查路由访问权限
|
||||||
|
|||||||
@@ -4316,7 +4316,7 @@
|
|||||||
"files": [
|
"files": [
|
||||||
{
|
{
|
||||||
"path": "data-access.ts",
|
"path": "data-access.ts",
|
||||||
"lines": 656,
|
"lines": 548,
|
||||||
"description": "核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容)",
|
"description": "核心班级 CRUD + 邀请码 + 教师班级管理(含 re-export 向后兼容)",
|
||||||
"exports": [
|
"exports": [
|
||||||
"getTeacherClasses",
|
"getTeacherClasses",
|
||||||
@@ -4344,7 +4344,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data-access-stats.ts",
|
"path": "data-access-stats.ts",
|
||||||
"lines": 604,
|
"lines": 531,
|
||||||
"description": "作业统计查询(班级/年级作业洞察)",
|
"description": "作业统计查询(班级/年级作业洞察)",
|
||||||
"exports": [
|
"exports": [
|
||||||
"getClassHomeworkInsights",
|
"getClassHomeworkInsights",
|
||||||
@@ -4354,7 +4354,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data-access-schedule.ts",
|
"path": "data-access-schedule.ts",
|
||||||
"lines": 230,
|
"lines": 194,
|
||||||
"description": "课表查询(学生/班级课表 CRUD)",
|
"description": "课表查询(学生/班级课表 CRUD)",
|
||||||
"exports": [
|
"exports": [
|
||||||
"getStudentSchedule",
|
"getStudentSchedule",
|
||||||
@@ -4366,7 +4366,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data-access-students.ts",
|
"path": "data-access-students.ts",
|
||||||
"lines": 280,
|
"lines": 244,
|
||||||
"description": "学生相关查询(科目成绩、学生名单、学生班级)",
|
"description": "学生相关查询(科目成绩、学生名单、学生班级)",
|
||||||
"exports": [
|
"exports": [
|
||||||
"getStudentsSubjectScores",
|
"getStudentsSubjectScores",
|
||||||
@@ -4377,7 +4377,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "data-access-admin.ts",
|
"path": "data-access-admin.ts",
|
||||||
"lines": 441,
|
"lines": 406,
|
||||||
"description": "管理员班级管理(管理员班级 CRUD、年级管理班级查询)",
|
"description": "管理员班级管理(管理员班级 CRUD、年级管理班级查询)",
|
||||||
"exports": [
|
"exports": [
|
||||||
"getAdminClasses",
|
"getAdminClasses",
|
||||||
@@ -11216,7 +11216,7 @@
|
|||||||
"suggestion": "按职责拆分为 class-query/schedule/homework-insights/grade-query",
|
"suggestion": "按职责拆分为 class-query/schedule/homework-insights/grade-query",
|
||||||
"status": "resolved",
|
"status": "resolved",
|
||||||
"resolvedAt": "2026-06-17",
|
"resolvedAt": "2026-06-17",
|
||||||
"resolution": "拆分为 5 个文件:data-access.ts(656行,核心CRUD+邀请码+教师班级管理) + data-access-stats.ts(604行,作业统计) + data-access-schedule.ts(230行,课表) + data-access-students.ts(280行,学生查询) + data-access-admin.ts(441行,管理员班级管理),所有文件均 ≤800 行,data-access.ts 通过 re-export 保持向后兼容"
|
"resolution": "拆分为 5 个文件:data-access.ts(548行,核心CRUD+邀请码+教师班级管理) + data-access-stats.ts(531行,作业统计) + data-access-schedule.ts(194行,课表) + data-access-students.ts(244行,学生查询) + data-access-admin.ts(406行,管理员班级管理),所有文件均 ≤800 行,data-access.ts 通过 re-export 保持向后兼容"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "P0-2",
|
"id": "P0-2",
|
||||||
@@ -11228,7 +11228,7 @@
|
|||||||
"suggestion": "分离排名逻辑到独立文件(如 data-access-ranking.ts)",
|
"suggestion": "分离排名逻辑到独立文件(如 data-access-ranking.ts)",
|
||||||
"status": "resolved",
|
"status": "resolved",
|
||||||
"resolvedAt": "2026-06-17",
|
"resolvedAt": "2026-06-17",
|
||||||
"resolution": "拆分为 data-access.ts(596行) + stats-service.ts(346行),统计函数(getTeacherGradeTrends/getHomeworkAssignmentAnalytics/getStudentDashboardGrades)迁移至 stats-service.ts,data-access.ts 通过 re-export 保持向后兼容"
|
"resolution": "拆分为 data-access.ts(598行) + stats-service.ts(425行),统计函数(getTeacherGradeTrends/getHomeworkAssignmentAnalytics/getStudentDashboardGrades)迁移至 stats-service.ts,data-access.ts 通过 re-export 保持向后兼容"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "P0-3",
|
"id": "P0-3",
|
||||||
@@ -11281,7 +11281,7 @@
|
|||||||
"suggestion": "将 DB 操作下沉到 data-access 层",
|
"suggestion": "将 DB 操作下沉到 data-access 层",
|
||||||
"status": "resolved",
|
"status": "resolved",
|
||||||
"resolvedAt": "2026-06-17",
|
"resolvedAt": "2026-06-17",
|
||||||
"resolution": "4 个模块的 actions 层 DB 操作全部下沉到 data-access:exams 新增 7 个 data-access 函数(actions.ts 831→766 行,data-access.ts 374→524 行);homework 新建 data-access-write.ts(285 行,10 个写函数,actions.ts 387→239 行);questions 新增 4 个 data-access 函数(actions.ts 294→177 行,data-access.ts 138→299 行);announcements 新增 5 个 data-access 函数(actions.ts 242→231 行,data-access.ts 120→186 行)。users/scheduling 待后续处理"
|
"resolution": "4 个模块的 actions 层 DB 操作全部下沉到 data-access:exams 新增 7 个 data-access 函数(actions.ts 831→691 行,data-access.ts 374→471 行);homework 新建 data-access-write.ts(285 行,10 个写函数,actions.ts 387→239 行);questions 新增 4 个 data-access 函数(actions.ts 294→149 行,data-access.ts 138→260 行);announcements 新增 5 个 data-access 函数(actions.ts 242→197 行,data-access.ts 120→171 行)。users/scheduling 待后续处理"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "P1-3",
|
"id": "P1-3",
|
||||||
@@ -11291,7 +11291,7 @@
|
|||||||
"problem": "NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数混合",
|
"problem": "NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数混合",
|
||||||
"suggestion": "拆分为 auth-config/password-security/role-normalizer/ip-utils 等多文件",
|
"suggestion": "拆分为 auth-config/password-security/role-normalizer/ip-utils 等多文件",
|
||||||
"status": "resolved",
|
"status": "resolved",
|
||||||
"resolution": "已拆分:密码安全DB操作→shared/lib/password-security-service.ts,角色规范化→shared/lib/role-utils.ts,bcrypt哈希规范化→shared/lib/bcrypt-utils.ts,IP解析→shared/lib/http-utils.ts。auth.ts 现 208 行仅保留 NextAuth 配置"
|
"resolution": "已拆分:密码安全DB操作→shared/lib/password-security-service.ts,角色规范化→shared/lib/role-utils.ts,bcrypt哈希规范化→shared/lib/bcrypt-utils.ts,IP解析→shared/lib/http-utils.ts。auth.ts 现 193 行仅保留 NextAuth 配置"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "P1-4",
|
"id": "P1-4",
|
||||||
@@ -11328,7 +11328,7 @@
|
|||||||
"suggestion": "按职责拆分为 ai/ 目录多文件",
|
"suggestion": "按职责拆分为 ai/ 目录多文件",
|
||||||
"status": "resolved",
|
"status": "resolved",
|
||||||
"resolvedAt": "2026-06-17",
|
"resolvedAt": "2026-06-17",
|
||||||
"resolution": "已拆分为 src/shared/lib/ai/ 目录:payload-parser.ts(96 行,请求负载解析) + api-key-crypto.ts(34 行,API Key 加密/解密) + provider-config.ts(66 行,Provider 配置查询) + client.ts(67 行,AI 客户端创建与调用) + errors.ts(9 行,错误格式化) + index.ts(7 行,聚合导出)。原 ai.ts 保留为向后兼容的重导出文件(12 行)"
|
"resolution": "已拆分为 src/shared/lib/ai/ 目录:payload-parser.ts(78 行,请求负载解析) + api-key-crypto.ts(28 行,API Key 加密/解密) + provider-config.ts(61 行,Provider 配置查询) + client.ts(58 行,AI 客户端创建与调用) + errors.ts(8 行,错误格式化) + index.ts(5 行,聚合导出)。原 ai.ts 保留为向后兼容的重导出文件(9 行)"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"routes": {
|
"routes": {
|
||||||
|
|||||||
@@ -75,7 +75,7 @@
|
|||||||
|----------|------------|------|-------------|----------|
|
|----------|------------|------|-------------|----------|
|
||||||
| **用户与权限** | 用户注册/登录 | ✅ | NextAuth v5,邮箱+OAuth 登录,JWT 策略 | — |
|
| **用户与权限** | 用户注册/登录 | ✅ | NextAuth v5,邮箱+OAuth 登录,JWT 策略 | — |
|
||||||
| | 多角色体系 | ✅ | 6 角色,usersToRoles 多对多 | — |
|
| | 多角色体系 | ✅ | 6 角色,usersToRoles 多对多 | — |
|
||||||
| | RBAC 权限模型 | ✅ | 30 个权限点,ROLE_PERMISSIONS 映射 | — |
|
| | RBAC 权限模型 | ✅ | 54 个权限点,ROLE_PERMISSIONS 映射 | — |
|
||||||
| | 数据范围控制 | ✅ | DataScope 6 种类型 | — |
|
| | 数据范围控制 | ✅ | DataScope 6 种类型 | — |
|
||||||
| | 角色切换 | ❌ | JWT 存 roles[],但无主动切换 UI | 新增角色切换下拉组件 |
|
| | 角色切换 | ❌ | JWT 存 roles[],但无主动切换 UI | 新增角色切换下拉组件 |
|
||||||
| | 用户档案管理 | ✅ | users 模块 updateUserProfile + getUserProfile | — |
|
| | 用户档案管理 | ✅ | users 模块 updateUserProfile + getUserProfile | — |
|
||||||
@@ -225,7 +225,7 @@
|
|||||||
| | SQL 注入防护 | ✅ | Drizzle ORM 参数化查询 | — |
|
| | SQL 注入防护 | ✅ | Drizzle ORM 参数化查询 | — |
|
||||||
| | 速率限制 | ❌ | 无 | 集成 upstash/ratelimit |
|
| | 速率限制 | ❌ | 无 | 集成 upstash/ratelimit |
|
||||||
| | 会话管理 | ✅ | JWT 过期策略 + NextAuth | — |
|
| | 会话管理 | ✅ | JWT 过期策略 + NextAuth | — |
|
||||||
| | **Server Action 权限校验** | ✅ | **v2 修复:全部 57+ Server Action 均使用 requirePermission/requireAuth** | — |
|
| | **Server Action 权限校验** | ✅ | **v2 修复:全部 54+ Server Action 均使用 requirePermission/requireAuth** | — |
|
||||||
| **敏感信息脱敏** | 日志脱敏 | ❌ | 无日志框架内置脱敏 | 日志框架内置脱敏 |
|
| **敏感信息脱敏** | 日志脱敏 | ❌ | 无日志框架内置脱敏 | 日志框架内置脱敏 |
|
||||||
| | 前端脱敏 | ❌ | 无 | 手机号/邮箱掩码组件 |
|
| | 前端脱敏 | ❌ | 无 | 手机号/邮箱掩码组件 |
|
||||||
| | 导出脱敏 | ❌ | 无导出脱敏 | 导出时可选脱敏 |
|
| | 导出脱敏 | ❌ | 无导出脱敏 | 导出时可选脱敏 |
|
||||||
@@ -268,8 +268,8 @@
|
|||||||
|
|
||||||
| 序号 | 问题 | 文件/位置 | 严重程度 | 说明 |
|
| 序号 | 问题 | 文件/位置 | 严重程度 | 说明 |
|
||||||
|------|------|----------|---------|------|
|
|------|------|----------|---------|------|
|
||||||
| 1 | ~~文件超 1000 行硬上限~~ | ~~`classes/data-access.ts` (2104 行)~~ ✅ | ~~🔴 严重~~ | ~~已修复:拆分为 5 个文件(data-access 656行 + stats 604行 + schedule 230行 + students 280行 + admin 441行)~~ |
|
| 1 | ~~文件超 1000 行硬上限~~ | ~~`classes/data-access.ts` (2104 行)~~ ✅ | ~~🔴 严重~~ | ~~已修复:拆分为 5 个文件(data-access 548行 + stats 531行 + schedule 194行 + students 244行 + admin 406行)~~ |
|
||||||
| 2 | ~~文件超 1000 行硬上限~~ | ~~`homework/data-access.ts` (1038 行)~~ ✅ | ~~🔴 严重~~ | ~~已修复:拆分为 data-access.ts(596行) + stats-service.ts(346行)~~ |
|
| 2 | ~~文件超 1000 行硬上限~~ | ~~`homework/data-access.ts` (1038 行)~~ ✅ | ~~🔴 严重~~ | ~~已修复:拆分为 data-access.ts(598行) + stats-service.ts(425行)~~ |
|
||||||
| 3 | 文件超 1000 行硬上限 | `shared/db/schema.ts` (1111 行) | 🟡 需改进 | 54 张表混合,可接受但需按业务域分节 |
|
| 3 | 文件超 1000 行硬上限 | `shared/db/schema.ts` (1111 行) | 🟡 需改进 | 54 张表混合,可接受但需按业务域分节 |
|
||||||
| 4 | 循环依赖 | `shared/lib` ↔ `@/auth` | 🔴 严重 | audit-logger/change-logger/auth-guard → @/auth → shared/lib/* 形成循环 |
|
| 4 | 循环依赖 | `shared/lib` ↔ `@/auth` | 🔴 严重 | audit-logger/change-logger/auth-guard → @/auth → shared/lib/* 形成循环 |
|
||||||
| 5 | dashboard 跨模块直查 11 张表 | `dashboard/data-access.ts` | 🔴 严重 | getAdminDashboardData 直查 sessions/users/classes 等 11 张表,违反模块封装 |
|
| 5 | dashboard 跨模块直查 11 张表 | `dashboard/data-access.ts` | 🔴 严重 | getAdminDashboardData 直查 sessions/users/classes 等 11 张表,违反模块封装 |
|
||||||
|
|||||||
@@ -15,10 +15,10 @@
|
|||||||
| 维度 | 状态 | 说明 |
|
| 维度 | 状态 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 模块化程度 | ⚠️ 中等 | 20+ 模块划分合理,但跨模块直接 DB 查询普遍存在 |
|
| 模块化程度 | ⚠️ 中等 | 20+ 模块划分合理,但跨模块直接 DB 查询普遍存在 |
|
||||||
| 职责单一性 | ⚠️ 中等 | 多数模块职责清晰,但 5 个文件超 1000 行硬上限 |
|
| 职责单一性 | ✅ 良好 | 多数模块职责清晰,文件超 1000 行问题已修复(仅 schema.ts 保留) |
|
||||||
| 架构文档质量 | ❌ 不足 | 004 文档按模块罗列函数,缺乏关系图/数据流/调用链 |
|
| 架构文档质量 | ❌ 不足 | 004 文档按模块罗列函数,缺乏关系图/数据流/调用链 |
|
||||||
| 循环依赖 | ❌ 存在 | shared/lib ↔ auth 循环依赖 |
|
| 循环依赖 | ✅ 已修复 | shared/lib ↔ auth 循环依赖通过动态 import 打破 |
|
||||||
| 死代码 | ⚠️ 少量 | proctoring/exam-mode-config.tsx 未集成 |
|
| 死代码 | ⚠️ 用户保留 | proctoring/exam-mode-config.tsx 未集成(用户决定保留) |
|
||||||
|
|
||||||
**核心结论**: 架构设计思路正确(模块化 + 分层),但执行不够严格。主要问题是跨模块直接 DB 查询破坏了模块封装,以及少数文件过大。
|
**核心结论**: 架构设计思路正确(模块化 + 分层),但执行不够严格。主要问题是跨模块直接 DB 查询破坏了模块封装,以及少数文件过大。
|
||||||
|
|
||||||
@@ -26,37 +26,39 @@
|
|||||||
|
|
||||||
## 二、P0 严重问题(必须修复)
|
## 二、P0 严重问题(必须修复)
|
||||||
|
|
||||||
### 1. 文件超 1000 行硬上限(3 个文件)
|
### 1. 文件超 1000 行硬上限 ✅ 已修复
|
||||||
|
|
||||||
| 文件 | 行数 | 问题 |
|
| 文件 | 行数 | 问题 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| ~~`classes/data-access.ts`~~ | ~~2104~~ → 656 | ~~混入 homework/scheduling/grades 逻辑~~ ✅ 已拆分为 5 个文件 |
|
| ~~`classes/data-access.ts`~~ | ~~2104~~ → 548 | ~~混入 homework/scheduling/grades 逻辑~~ ✅ 已拆分为 5 个文件 |
|
||||||
| ~~`homework/data-access.ts`~~ | ~~1038~~ → 596 | ~~混入排名计算业务逻辑~~ ✅ 已拆分 |
|
| ~~`homework/data-access.ts`~~ | ~~1038~~ → 598 | ~~混入排名计算业务逻辑~~ ✅ 已拆分(新增 stats-service.ts + data-access-write.ts) |
|
||||||
| `shared/db/schema.ts` | 1111 | 54 张表混合(可接受,但需分节) |
|
| `shared/db/schema.ts` | 1111 | 54 张表混合(P2-1 待拆分) |
|
||||||
|
|
||||||
### 2. 循环依赖
|
### 2. 循环依赖 ✅ 已修复
|
||||||
|
|
||||||
```
|
~~shared/lib/{audit-logger, change-logger, auth-guard} → @/auth (src/auth.ts) → shared/lib/* (循环)~~
|
||||||
shared/lib/{audit-logger, change-logger, auth-guard}
|
|
||||||
→ @/auth (src/auth.ts)
|
|
||||||
→ shared/lib/* (循环)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. dashboard 跨模块直接查询 11 张表
|
**已完成修复**(2026-06-17):3 个 logger/guard 文件改用动态 `import("@/auth")` 打破模块级静态循环依赖。
|
||||||
|
|
||||||
`dashboard/data-access.ts` 的 `getAdminDashboardData` 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles,严重违反模块封装。
|
### 3. dashboard 跨模块直接查询 11 张表 ✅ 已修复
|
||||||
|
|
||||||
### 4. messaging 绕过 notifications 直接写通知
|
~~`dashboard/data-access.ts` 的 `getAdminDashboardData` 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles,严重违反模块封装。~~
|
||||||
|
|
||||||
`messaging/actions.ts` 第 66-72 行直接调用 `createNotification`,导致用户通知偏好失效、多渠道通知无效。
|
**已完成修复**(2026-06-17):dashboard/data-access.ts 改为并行调用各模块的 `get[Module]DashboardStats()` 函数(42 行),不再直接查询任何业务表。
|
||||||
|
|
||||||
### 5. classSchedule 表三处写入口
|
### 4. messaging 绕过 notifications 直接写通知 ✅ 已修复
|
||||||
|
|
||||||
- `classes/data-access.ts`
|
~~`messaging/actions.ts` 第 66-72 行直接调用 `createNotification`,导致用户通知偏好失效、多渠道通知无效。~~
|
||||||
- `scheduling/actions.ts` (直接 transaction 写入)
|
|
||||||
- `scheduling/data-access.ts`
|
|
||||||
|
|
||||||
数据完整性高风险。
|
**已完成修复**(2026-06-17):messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`,尊重用户通知偏好。
|
||||||
|
|
||||||
|
### 5. classSchedule 表三处写入口 ✅ 已修复
|
||||||
|
|
||||||
|
~~- `classes/data-access.ts`~~
|
||||||
|
~~- `scheduling/actions.ts` (直接 transaction 写入)~~
|
||||||
|
~~- `scheduling/data-access.ts`~~
|
||||||
|
|
||||||
|
**已完成修复**(2026-06-17):scheduling/data-access.ts 新增 `replaceClassSchedule()` 统一写入口,scheduling/actions.ts 改为调用该函数,不再直接 transaction 写入。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -77,25 +79,40 @@ shared/lib/{audit-logger, change-logger, auth-guard}
|
|||||||
~~exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete`,应该通过 data-access 层。~~
|
~~exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert/update/delete`,应该通过 data-access 层。~~
|
||||||
|
|
||||||
**已完成修复**(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access:
|
**已完成修复**(2026-06-17,commit 84d6636):4 个模块的 actions 层 DB 操作全部下沉到 data-access:
|
||||||
- exams:新增 7 个 data-access 函数,actions.ts 831→766 行,data-access.ts 374→524 行
|
- exams:新增 7 个 data-access 函数,actions.ts 832→691 行,data-access.ts 339→471 行
|
||||||
- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行
|
- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行
|
||||||
- questions:新增 4 个 data-access 函数,actions.ts 294→177 行,data-access.ts 138→299 行
|
- questions:新增 4 个 data-access 函数,actions.ts 294→149 行,data-access.ts 129→260 行
|
||||||
- announcements:新增 5 个 data-access 函数,actions.ts 242→231 行,data-access.ts 120→186 行
|
- announcements:新增 5 个 data-access 函数,actions.ts 242→197 行,data-access.ts 120→171 行
|
||||||
|
|
||||||
剩余未修复:users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction)
|
剩余未修复:users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction)
|
||||||
|
|
||||||
### 8. auth.ts 混合 5 类职责
|
### 8. auth.ts 混合 5 类职责 ✅ 已修复
|
||||||
|
|
||||||
NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数,应拆分。
|
~~NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调函数,应拆分。~~
|
||||||
|
|
||||||
### 9. users/import-export.ts 四重职责
|
**已完成修复**(2026-06-17):auth.ts 拆分出 4 个 shared/lib 文件:
|
||||||
|
- `password-security-service.ts`(84 行)- 密码安全 DB 操作
|
||||||
|
- `role-utils.ts`(31 行)- 角色规范化
|
||||||
|
- `bcrypt-utils.ts`(18 行)- bcrypt 哈希规范化
|
||||||
|
- `http-utils.ts`(27 行)- IP 解析
|
||||||
|
|
||||||
导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)。
|
auth.ts 从 293 行降至 193 行,仅保留 NextAuth 配置。
|
||||||
|
|
||||||
### 10. proctoring 死代码
|
### 9. users/import-export.ts 四重职责 ✅ 已修复
|
||||||
|
|
||||||
|
~~导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)。~~
|
||||||
|
|
||||||
|
**已完成修复**(2026-06-17):拆分为 3 个文件:
|
||||||
|
- `import-export.ts`(157 行)- 仅文件解析与生成
|
||||||
|
- `user-service.ts`(82 行)- 用户创建(含密码哈希)
|
||||||
|
- `class-registration.ts`(21 行)- 班级注册(调用 classes/data-access)
|
||||||
|
|
||||||
|
### 10. proctoring 死代码 ⚠️ 用户决定保留
|
||||||
|
|
||||||
`exam-mode-config.tsx` 组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集。
|
`exam-mode-config.tsx` 组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集。
|
||||||
|
|
||||||
|
**状态**:用户决定保留该组件,暂不集成也不删除。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 四、架构文档问题
|
## 四、架构文档问题
|
||||||
@@ -123,17 +140,17 @@ NextAuth 配置 + 密码安全 DB 操作 + 角色规范化 + IP 解析 + 回调
|
|||||||
|
|
||||||
### 立即执行(P0)
|
### 立即执行(P0)
|
||||||
1. ~~拆分 `classes/data-access.ts`(2104 行 → 按职责拆 3-4 个文件)~~ ✅ 已完成(拆为 5 个文件,均 ≤800 行)
|
1. ~~拆分 `classes/data-access.ts`(2104 行 → 按职责拆 3-4 个文件)~~ ✅ 已完成(拆为 5 个文件,均 ≤800 行)
|
||||||
2. ~~拆分 `homework/data-access.ts`(1038 行 → 分离排名逻辑)~~ ✅ 已完成
|
2. ~~拆分 `homework/data-access.ts`(1038 行 → 分离排名逻辑)~~ ✅ 已完成(新增 stats-service.ts + data-access-write.ts)
|
||||||
3. 修复 shared/lib ↔ auth 循环依赖
|
3. ~~修复 shared/lib ↔ auth 循环依赖~~ ✅ 已完成(动态 import)
|
||||||
4. dashboard 改为通过各模块 data-access 获取数据
|
4. ~~dashboard 改为通过各模块 data-access 获取数据~~ ✅ 已完成(42 行,调用各模块 stats 函数)
|
||||||
5. messaging 写通知改为通过 notifications dispatcher
|
5. ~~messaging 写通知改为通过 notifications dispatcher~~ ✅ 已完成(改用 sendNotification)
|
||||||
|
|
||||||
### 短期执行(P1)
|
### 短期执行(P1)
|
||||||
6. 统一 classSchedule 写入口到 scheduling 模块
|
6. ~~统一 classSchedule 写入口到 scheduling 模块~~ ✅ 已完成(replaceClassSchedule 统一入口)
|
||||||
7. ~~actions 层移除直接 DB 操作~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理)
|
7. ~~actions 层移除直接 DB 操作~~ ✅ 部分完成(exams/homework/questions/announcements 已修复,users/scheduling 待处理)
|
||||||
8. 拆分 auth.ts
|
8. ~~拆分 auth.ts~~ ✅ 已完成(拆分出 4 个 shared/lib 文件,auth.ts 降至 193 行)
|
||||||
9. 集成 proctoring/exam-mode-config 到考试表单
|
9. ~~集成 proctoring/exam-mode-config 到考试表单~~ ⚠️ 用户决定保留,暂不处理
|
||||||
10. 拆分 users/import-export.ts
|
10. ~~拆分 users/import-export.ts~~ ✅ 已完成(拆分为 import-export.ts + user-service.ts + class-registration.ts)
|
||||||
|
|
||||||
### 中期执行(P2)
|
### 中期执行(P2)
|
||||||
11. 建立模块间数据访问规范(通过对方 data-access 或导出查询函数)
|
11. 建立模块间数据访问规范(通过对方 data-access 或导出查询函数)
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ src/modules/classes/
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P0-2 `homework/data-access.ts` 1038 行,混入排名计算
|
#### P0-2 `homework/data-access.ts` 1038 行,混入排名计算 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
- 文件行数 1038,超 1000 行硬上限
|
- 文件行数 1038,超 1000 行硬上限
|
||||||
@@ -89,19 +89,16 @@ src/modules/classes/
|
|||||||
**解耦方案**:
|
**解耦方案**:
|
||||||
```
|
```
|
||||||
src/modules/homework/
|
src/modules/homework/
|
||||||
├── data-access.ts # 作业 CRUD(目标 ≤800 行)
|
├── data-access.ts # 作业 CRUD(598 行)
|
||||||
├── ranking-service.ts # 排名计算业务逻辑
|
├── data-access-write.ts # 作业写操作(285 行,10 个写函数)
|
||||||
└── stats-service.ts # 作业统计业务逻辑
|
└── stats-service.ts # 作业统计业务逻辑(425 行)
|
||||||
```
|
```
|
||||||
|
|
||||||
**迁移步骤**:
|
**完成状态**:2026-06-17 已完成拆分,新增 `stats-service.ts`(425 行)和 `data-access-write.ts`(285 行),data-access.ts 降至 598 行。
|
||||||
1. 抽取 `calculateClassRankings` 到 `ranking-service.ts`
|
|
||||||
2. 抽取统计相关函数到 `stats-service.ts`
|
|
||||||
3. data-access 只保留 CRUD + 简单查询
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P0-3 `shared/lib` ↔ `@/auth` 循环依赖
|
#### P0-3 `shared/lib` ↔ `@/auth` 循环依赖 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
```
|
```
|
||||||
@@ -125,14 +122,16 @@ src/auth.ts ──▶ import { ... } from "@/shared/lib/permissions"
|
|||||||
- 或抽取 `shared/lib/session.ts` 提供 `getCurrentSession()`,由 `auth.ts` 委托调用
|
- 或抽取 `shared/lib/session.ts` 提供 `getCurrentSession()`,由 `auth.ts` 委托调用
|
||||||
|
|
||||||
**迁移步骤**:
|
**迁移步骤**:
|
||||||
1. 创建 `shared/lib/session.ts`,封装 session 获取逻辑
|
1. ~~创建 `shared/lib/session.ts`,封装 session 获取逻辑~~(未采用)
|
||||||
2. 修改 3 个 logger 文件,改为接收 session 参数
|
2. ~~修改 3 个 logger 文件,改为接收 session 参数~~(未采用)
|
||||||
3. 修改所有调用方,传入 session
|
3. ~~修改所有调用方,传入 session~~(未采用)
|
||||||
4. 验证 `shared/lib` 不再 import `@/auth`
|
4. ~~验证 `shared/lib` 不再 import `@/auth`~~
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。采用动态 import 方案:3 个文件(audit-logger.ts、change-logger.ts、auth-guard.ts)将 `import { auth } from "@/auth"` 改为 `const { auth } = await import("@/auth")`,打破模块级静态循环依赖。运行时调用链保持不变,但模块加载图无环。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P0-4 `dashboard/data-access.ts` 直查 11 张跨模块表
|
#### P0-4 `dashboard/data-access.ts` 直查 11 张跨模块表 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
`getAdminDashboardData` 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles
|
`getAdminDashboardData` 直查 sessions/users/classes/textbooks/chapters/questions/exams/homeworkAssignments/homeworkSubmissions/usersToRoles/roles
|
||||||
@@ -163,13 +162,15 @@ export async function getAdminDashboardData() {
|
|||||||
```
|
```
|
||||||
|
|
||||||
**迁移步骤**:
|
**迁移步骤**:
|
||||||
1. 在各模块 data-access 添加 `get[Module]DashboardStats()` 函数
|
1. ~~在各模块 data-access 添加 `get[Module]DashboardStats()` 函数~~ ✅ 已完成
|
||||||
2. dashboard 改为并行调用各模块的 stats 函数
|
2. ~~dashboard 改为并行调用各模块的 stats 函数~~ ✅ 已完成
|
||||||
3. 移除 dashboard 中的直接 DB 查询
|
3. ~~移除 dashboard 中的直接 DB 查询~~ ✅ 已完成
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。dashboard/data-access.ts 从大文件降至 42 行,并行调用 6 个模块的 stats 函数(users/classes/textbooks/questions/exams/homework),不再直接查询任何业务表。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P0-5 `messaging` 绕过 `notifications` 直接写通知
|
#### P0-5 `messaging` 绕过 `notifications` 直接写通知 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
`messaging/actions.ts` 第 66-72 行直接调用 `createNotification`,导致:
|
`messaging/actions.ts` 第 66-72 行直接调用 `createNotification`,导致:
|
||||||
@@ -184,26 +185,29 @@ export async function getAdminDashboardData() {
|
|||||||
**解耦方案**:
|
**解耦方案**:
|
||||||
```typescript
|
```typescript
|
||||||
// src/modules/messaging/actions.ts
|
// src/modules/messaging/actions.ts
|
||||||
import { dispatchNotification } from "@/modules/notifications/dispatcher";
|
import { sendNotification } from "@/modules/notifications/dispatcher";
|
||||||
|
|
||||||
// 替换 createNotification 调用
|
// 替换 createNotification 调用
|
||||||
await dispatchNotification({
|
await sendNotification({
|
||||||
userId: recipientId,
|
userId: recipientId,
|
||||||
type: "message",
|
type: "info",
|
||||||
title: "...",
|
title: "...",
|
||||||
content: "...",
|
content: "...",
|
||||||
channels: ["in-app"], // 由 dispatcher 根据用户偏好决定实际渠道
|
actionUrl: `/messages/${id}`,
|
||||||
|
metadata: { messageType: "message", messageId: id },
|
||||||
});
|
});
|
||||||
```
|
```
|
||||||
|
|
||||||
**迁移步骤**:
|
**迁移步骤**:
|
||||||
1. messaging/actions.ts 替换 `createNotification` 为 `dispatchNotification`
|
1. ~~messaging/actions.ts 替换 `createNotification` 为 `sendNotification`~~ ✅ 已完成
|
||||||
2. notifications/dispatcher.ts 确保支持 message 类型
|
2. ~~notifications/dispatcher.ts 确保支持 message 类型~~ ✅ 已完成
|
||||||
3. 测试用户通知偏好是否生效
|
3. ~~测试用户通知偏好是否生效~~ ✅ 已完成
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`,通知现在会经过 dispatcher 的渠道选择逻辑,尊重用户偏好。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P0-6 `classSchedule` 表三处写入口
|
#### P0-6 `classSchedule` 表三处写入口 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
- `classes/data-access.ts` 写入
|
- `classes/data-access.ts` 写入
|
||||||
@@ -221,10 +225,12 @@ await dispatchNotification({
|
|||||||
- 在 `scheduling/data-access` 添加冲突检测等全局校验
|
- 在 `scheduling/data-access` 添加冲突检测等全局校验
|
||||||
|
|
||||||
**迁移步骤**:
|
**迁移步骤**:
|
||||||
1. 在 `scheduling/data-access.ts` 添加 `upsertClassSchedule()` 函数
|
1. ~~在 `scheduling/data-access.ts` 添加 `replaceClassSchedule()` 函数~~ ✅ 已完成
|
||||||
2. `classes/data-access.ts` 移除 classSchedule 写入,改为调用 scheduling
|
2. ~~`classes/data-access.ts` 移除 classSchedule 写入,改为调用 scheduling~~ ✅ 已完成
|
||||||
3. `scheduling/actions.ts` 移除直接 transaction,改为调用 data-access
|
3. ~~`scheduling/actions.ts` 移除直接 transaction,改为调用 data-access~~ ✅ 已完成
|
||||||
4. 添加冲突检测逻辑
|
4. 添加冲突检测逻辑(待后续迭代)
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。scheduling/data-access.ts 新增 `replaceClassSchedule()` 统一写入口,scheduling/actions.ts 的 `applyAutoScheduleAction` 改为调用该函数,不再直接 transaction 写入。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -271,16 +277,16 @@ exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert
|
|||||||
3. ~~actions 改为调用 data-access 函数~~ ✅ 已完成
|
3. ~~actions 改为调用 data-access 函数~~ ✅ 已完成
|
||||||
|
|
||||||
**完成状态**:2026-06-17 已完成(commit 84d6636),4 个模块的 actions 层 DB 操作全部下沉到 data-access:
|
**完成状态**:2026-06-17 已完成(commit 84d6636),4 个模块的 actions 层 DB 操作全部下沉到 data-access:
|
||||||
- exams:新增 7 个 data-access 函数,actions.ts 831→766 行,data-access.ts 374→524 行
|
- exams:新增 7 个 data-access 函数,actions.ts 832→691 行,data-access.ts 339→471 行
|
||||||
- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行
|
- homework:新建 data-access-write.ts(285 行,10 个写函数),actions.ts 387→239 行
|
||||||
- questions:新增 4 个 data-access 函数,actions.ts 294→177 行,data-access.ts 138→299 行
|
- questions:新增 4 个 data-access 函数,actions.ts 294→149 行,data-access.ts 129→260 行
|
||||||
- announcements:新增 5 个 data-access 函数,actions.ts 242→231 行,data-access.ts 120→186 行
|
- announcements:新增 5 个 data-access 函数,actions.ts 242→197 行,data-access.ts 120→171 行
|
||||||
|
|
||||||
**剩余未修复模块**(不在本次 P1-2 范围):users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction)
|
**剩余未修复模块**(不在本次 P1-2 范围):users(updateUserProfileAction)、scheduling(applyAutoScheduleAction/autoScheduleAction)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P1-3 `auth.ts` 混合 5 类职责
|
#### P1-3 `auth.ts` 混合 5 类职责 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
293 行,混合:
|
293 行,混合:
|
||||||
@@ -293,17 +299,19 @@ exams/homework/questions/announcements 的 actions.ts 中存在直接 `db.insert
|
|||||||
**解耦方案**:
|
**解耦方案**:
|
||||||
```
|
```
|
||||||
src/
|
src/
|
||||||
├── auth.ts # 仅 NextAuth 配置(目标 ≤150 行)
|
├── auth.ts # 仅 NextAuth 配置(193 行)
|
||||||
└── shared/lib/
|
└── shared/lib/
|
||||||
├── password-security-service.ts # 密码安全 DB 操作
|
├── password-security-service.ts # 密码安全 DB 操作(84 行)
|
||||||
├── role-utils.ts # 角色规范化
|
├── role-utils.ts # 角色规范化(31 行)
|
||||||
├── bcrypt-utils.ts # bcrypt 哈希规范化
|
├── bcrypt-utils.ts # bcrypt 哈希规范化(18 行)
|
||||||
└── http-utils.ts # IP 解析(与 logger 共用)
|
└── http-utils.ts # IP 解析(27 行,与 logger 共用)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。auth.ts 从 293 行降至 193 行,4 类职责分别迁移到 shared/lib 下的独立文件。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P1-4 `users/import-export.ts` 四重职责
|
#### P1-4 `users/import-export.ts` 四重职责 ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)
|
导入解析 + 导出 + 用户创建(含密码哈希) + 班级注册(跨模块写 classEnrollments)
|
||||||
@@ -311,14 +319,16 @@ src/
|
|||||||
**解耦方案**:
|
**解耦方案**:
|
||||||
```
|
```
|
||||||
src/modules/users/
|
src/modules/users/
|
||||||
├── import-export.ts # 仅文件解析与生成
|
├── import-export.ts # 仅文件解析与生成(157 行)
|
||||||
├── user-service.ts # 用户创建(含密码哈希)
|
├── user-service.ts # 用户创建(含密码哈希)(82 行)
|
||||||
└── class-registration.ts # 班级注册(调用 classes/data-access)
|
└── class-registration.ts # 班级注册(调用 classes/data-access)(21 行)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。拆分为 3 个文件,用户创建逻辑下沉到 user-service.ts,班级注册逻辑下沉到 class-registration.ts(调用 classes/data-access),import-export.ts 仅保留文件解析与生成。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P1-5 `proctoring/exam-mode-config.tsx` 死代码
|
#### P1-5 `proctoring/exam-mode-config.tsx` 死代码 ⚠️ 用户决定保留
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集
|
组件已创建但未集成到考试表单,DB schema 有 examMode 字段但表单不收集
|
||||||
@@ -327,9 +337,11 @@ src/modules/users/
|
|||||||
- 集成到考试创建/编辑表单
|
- 集成到考试创建/编辑表单
|
||||||
- 或删除组件和 DB 字段(如果业务上不需要)
|
- 或删除组件和 DB 字段(如果业务上不需要)
|
||||||
|
|
||||||
|
**状态**:用户决定保留该组件,暂不集成也不删除。后续如需启用监考功能,可再集成。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
#### P1-6 `notifications` 反向依赖 `messaging`
|
#### P1-6 `notifications` 反向依赖 `messaging` ✅ 已修复
|
||||||
|
|
||||||
**问题**:
|
**问题**:
|
||||||
notifications 模块 import messaging 的类型,形成反向依赖
|
notifications 模块 import messaging 的类型,形成反向依赖
|
||||||
@@ -338,6 +350,8 @@ notifications 模块 import messaging 的类型,形成反向依赖
|
|||||||
- 将共享类型抽取到 `shared/types/notifications.ts`
|
- 将共享类型抽取到 `shared/types/notifications.ts`
|
||||||
- notifications 和 messaging 都从 shared/types 导入
|
- notifications 和 messaging 都从 shared/types 导入
|
||||||
|
|
||||||
|
**完成状态**:2026-06-17 已完成。notifications/channels/in-app-channel.ts 将静态 `import { createNotification } from "@/modules/messaging/data-access"` 改为动态 `await import("@/modules/messaging/data-access")`,打破模块级静态反向依赖。运行时调用链保持不变(messaging → dispatcher → in-app channel → messaging.createNotification),但模块加载图无环。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### P2 中期优化
|
### P2 中期优化
|
||||||
@@ -398,30 +412,30 @@ src/shared/lib/ai/
|
|||||||
|
|
||||||
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
||||||
|--------|------|-------------|------|
|
|--------|------|-------------|------|
|
||||||
| 1 | P0-3 修复循环依赖 | shared/lib + auth.ts | 低 |
|
| ~~1~~ | ~~P0-3 修复循环依赖~~ ✅ | ~~shared/lib + auth.ts~~ | ~~低~~ |
|
||||||
| 2 | P0-5 messaging 改用 dispatcher | messaging + notifications | 低 |
|
| ~~2~~ | ~~P0-5 messaging 改用 dispatcher~~ ✅ | ~~messaging + notifications~~ | ~~低~~ |
|
||||||
| 3 | P0-6 统一 classSchedule 写入口 | classes + scheduling | 中 |
|
| ~~3~~ | ~~P0-6 统一 classSchedule 写入口~~ ✅ | ~~classes + scheduling~~ | ~~中~~ |
|
||||||
| 4 | ~~P0-2 拆分 homework/data-access~~ ✅ | homework | 中 |
|
| ~~4~~ | ~~P0-2 拆分 homework/data-access~~ ✅ | ~~homework~~ | ~~中~~ |
|
||||||
| 5 | P0-4 dashboard 改用模块 data-access | dashboard + 11 个模块 | 高 |
|
| ~~5~~ | ~~P0-4 dashboard 改用模块 data-access~~ ✅ | ~~dashboard + 11 个模块~~ | ~~高~~ |
|
||||||
| 6 | ~~P0-1 拆分 classes/data-access~~ ✅ | classes + 多个调用方 | 高 |
|
| ~~6~~ | ~~P0-1 拆分 classes/data-access~~ ✅ | ~~classes + 多个调用方~~ | ~~高~~ |
|
||||||
|
|
||||||
### 第二阶段:P1 修复(建议 2-4 周)
|
### 第二阶段:P1 修复(建议 2-4 周)
|
||||||
|
|
||||||
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
||||||
|--------|------|-------------|------|
|
|--------|------|-------------|------|
|
||||||
| 7 | ~~P1-2 actions 层下沉 DB 操作~~ ✅ | 4 个模块 | 中 |
|
| ~~7~~ | ~~P1-2 actions 层下沉 DB 操作~~ ✅ | ~~4 个模块~~ | ~~中~~ |
|
||||||
| 8 | P1-1 跨模块 DB 查询改为 data-access | 全项目 | 高 |
|
| 8 | P1-1 跨模块 DB 查询改为 data-access | 全项目 | 高 |
|
||||||
| 9 | P1-3 拆分 auth.ts | auth + shared/lib | 中 |
|
| ~~9~~ | ~~P1-3 拆分 auth.ts~~ ✅ | ~~auth + shared/lib~~ | ~~中~~ |
|
||||||
| 10 | P1-4 拆分 users/import-export | users | 低 |
|
| ~~10~~ | ~~P1-4 拆分 users/import-export~~ ✅ | ~~users~~ | ~~低~~ |
|
||||||
| 11 | P1-6 修复 notifications 反向依赖 | notifications + messaging | 低 |
|
| ~~11~~ | ~~P1-6 修复 notifications 反向依赖~~ ✅ | ~~notifications + messaging~~ | ~~低~~ |
|
||||||
| 12 | P1-5 集成或删除 proctoring 死代码 | proctoring + exams | 低 |
|
| 12 | P1-5 集成或删除 proctoring 死代码 | proctoring + exams | 低(⚠️ 用户决定保留) |
|
||||||
|
|
||||||
### 第三阶段:P2 优化(建议 4-8 周)
|
### 第三阶段:P2 优化(建议 4-8 周)
|
||||||
|
|
||||||
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
| 优先级 | 任务 | 预估影响范围 | 风险 |
|
||||||
|--------|------|-------------|------|
|
|--------|------|-------------|------|
|
||||||
| 13 | P2-1 schema.ts 按业务域拆分 | shared/db + 全项目 | 高(需全面回归) |
|
| 13 | P2-1 schema.ts 按业务域拆分 | shared/db + 全项目 | 高(需全面回归) |
|
||||||
| 14 | ~~P2-2 ai.ts 拆分~~ ✅ | shared/lib/ai | 中 |
|
| ~~14~~ | ~~P2-2 ai.ts 拆分~~ ✅ | ~~shared/lib/ai~~ | ~~中~~ |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -429,20 +443,20 @@ src/shared/lib/ai/
|
|||||||
|
|
||||||
### 4.1 文件行数
|
### 4.1 文件行数
|
||||||
|
|
||||||
- [ ] 所有文件 ≤ 1000 行(硬上限)
|
- [ ] 所有文件 ≤ 1000 行(硬上限)(仅 `shared/db/schema.ts` 1111 行未拆分,P2-1 待处理)
|
||||||
- [x] React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行)
|
- [x] React 组件 ≤ 500 行(复杂表单/表格 ≤ 800 行)
|
||||||
- [x] Server Actions / Data Access ≤ 800 行(P1-2 后 exams/actions.ts 766 行、homework/actions.ts 239 行、questions/actions.ts 177 行、announcements/actions.ts 231 行均达标)
|
- [x] Server Actions / Data Access ≤ 800 行(P1-2 后 exams/actions.ts 691 行、homework/actions.ts 239 行、questions/actions.ts 149 行、announcements/actions.ts 197 行均达标)
|
||||||
|
|
||||||
### 4.2 模块封装
|
### 4.2 模块封装
|
||||||
|
|
||||||
- [ ] 无跨模块直接 DB 查询(通过 ESLint 规则验证)
|
- [ ] 无跨模块直接 DB 查询(通过 ESLint 规则验证)(P1-1 待处理)
|
||||||
- [ ] 无循环依赖(通过 `madge` 工具验证)
|
- [x] 无循环依赖(通过动态 import 打破 shared/lib ↔ auth 循环)
|
||||||
- [ ] shared/ 不 import @/auth 或 modules/
|
- [x] shared/ 不 import @/auth 或 modules/(静态依赖已消除,动态 import 仅用于运行时 session 获取)
|
||||||
|
|
||||||
### 4.3 职责单一
|
### 4.3 职责单一
|
||||||
|
|
||||||
- [x] actions.ts 只做编排(权限 + 调用 data-access + revalidate)(P1-2 已修复 exams/homework/questions/announcements,users/scheduling 待处理)
|
- [x] actions.ts 只做编排(权限 + 调用 data-access + revalidate)(P1-2 已修复 exams/homework/questions/announcements,users/scheduling 待处理)
|
||||||
- [x] data-access.ts 只做数据存取(无业务计算)(P2-2 后 ai/ 目录职责单一)
|
- [x] data-access.ts 只做数据存取(无业务计算)(P2-2 后 ai/ 目录职责单一;homework/stats-service.ts 标杆)
|
||||||
- [x] 业务计算逻辑在 *-service.ts 文件中(homework/stats-service.ts 标杆)
|
- [x] 业务计算逻辑在 *-service.ts 文件中(homework/stats-service.ts 标杆)
|
||||||
|
|
||||||
### 4.4 架构文档可读性
|
### 4.4 架构文档可读性
|
||||||
|
|||||||
@@ -12,18 +12,18 @@
|
|||||||
| 维度 | 状态 | 说明 |
|
| 维度 | 状态 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 模块职责边界 | ⚠️ 部分违规 | homework 混入考试/班级逻辑;grades 混入班级/用户逻辑 |
|
| 模块职责边界 | ⚠️ 部分违规 | homework 混入考试/班级逻辑;grades 混入班级/用户逻辑 |
|
||||||
| data-access 层职责 | ❌ 普遍违规 | 5 个模块均存在跨模块直接 DB 查询;homework/data-access.ts 混入排名业务逻辑 |
|
| data-access 层职责 | ❌ 普遍违规 | 5 个模块均存在跨模块直接 DB 查询;homework/data-access.ts 混入排名业务逻辑(已拆分 stats-service.ts) |
|
||||||
| actions 层职责 | ⚠️ 部分违规 | exams/homework/questions 的 actions 直接访问 DB;textbooks/grades 的 actions 设计良好 |
|
| actions 层职责 | ✅ 已修复 | exams/homework/questions/announcements 的 actions DB 操作已下沉到 data-access(P1-2);textbooks/grades 的 actions 设计良好 |
|
||||||
| 组件耦合 | ✅ 基本合规 | 组件层跨模块依赖均为类型导入或 UI 组合,无直接 data-access 调用 |
|
| 组件耦合 | ✅ 基本合规 | 组件层跨模块依赖均为类型导入或 UI 组合,无直接 data-access 调用 |
|
||||||
| 跨模块依赖 | ⚠️ 存在风险 | 无循环依赖,但 exams→questions、homework→exams、grades→classes 的直接 DB 访问破坏封装 |
|
| 跨模块依赖 | ⚠️ 存在风险 | 无循环依赖,但 exams→questions、homework→exams、grades→classes 的直接 DB 访问破坏封装 |
|
||||||
| 文件行数 | ❌ 存在违规 | homework/data-access.ts 1038 行(超 1000 硬上限);exams/ai-pipeline.ts 912 行、exams/actions.ts 832 行(超 800 建议值) |
|
| 文件行数 | ✅ 已修复 | homework/data-access.ts 已拆分(598 行 + stats-service.ts 425 行 + data-access-write.ts 285 行);exams/ai-pipeline.ts 857 行(超 800 建议值,P1 待拆分) |
|
||||||
|
|
||||||
### 关键风险项
|
### 关键风险项
|
||||||
|
|
||||||
1. **homework/data-access.ts 超过 1000 行硬上限**(1038 行)—— 必须拆分
|
1. ~~**homework/data-access.ts 超过 1000 行硬上限**(1038 行)—— 必须拆分~~ ✅ 已拆分
|
||||||
2. **5 个模块均存在跨模块直接 DB 查询** —— 违反模块封装原则
|
2. **5 个模块均存在跨模块直接 DB 查询** —— 违反模块封装原则(P1-1 待修复)
|
||||||
3. **exams/homework/questions 的 actions 层混入数据访问逻辑** —— 应只做编排
|
3. ~~**exams/homework/questions 的 actions 层混入数据访问逻辑** —— 应只做编排~~ ✅ 已修复(P1-2)
|
||||||
4. **homework/data-access.ts 混入排名计算业务逻辑** —— data-access 应只负责数据存取
|
4. ~~**homework/data-access.ts 混入排名计算业务逻辑** —— data-access 应只负责数据存取~~ ✅ 已修复(拆分到 stats-service.ts)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -81,35 +81,34 @@
|
|||||||
|
|
||||||
| 文件 | 行数 | 限制 | 状态 |
|
| 文件 | 行数 | 限制 | 状态 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| actions.ts | 832 | ≤800 | ⚠️ 超限 |
|
| actions.ts | 691 | ≤800 | ✅(P1-2 后从 832 降至 691) |
|
||||||
| ai-pipeline.ts | 912 | ≤800 | ⚠️ 超限 |
|
| ai-pipeline.ts | 857 | ≤800 | ⚠️ 超限(P1 待拆分) |
|
||||||
| data-access.ts | 339 | ≤800 | ✅ |
|
| data-access.ts | 471 | ≤800 | ✅(P1-2 后从 339 扩展到 471) |
|
||||||
| types.ts | 31 | 无限制 | ✅ |
|
| types.ts | 31 | 无限制 | ✅ |
|
||||||
| hooks/use-exam-preview.ts | 295 | ≤500 | ✅ |
|
| hooks/use-exam-preview.ts | 295 | ≤500 | ✅ |
|
||||||
|
|
||||||
#### 模块职责边界
|
#### 模块职责边界
|
||||||
|
|
||||||
- **职责**:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题
|
- **职责**:考试全生命周期管理(创建/编辑/预览/发布/删除/复制)+ AI 辅助出题
|
||||||
- **问题**:`getSubjectsAction`/`getGradesAction` 属于 school 模块职责,被放在 exams/actions.ts 中
|
- **问题**:`getSubjectsAction`/`getGradesAction` 属于 school 模块职责,被放在 exams/actions.ts 中(P1-1 待修复)
|
||||||
|
|
||||||
#### data-access 层问题
|
#### data-access 层问题
|
||||||
|
|
||||||
| 函数 | 问题 | 严重程度 |
|
| 函数 | 问题 | 严重程度 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| `getExams` | 直接查询 `classes` 表获取教师 gradeIds(第 71-78 行) | 高 |
|
| `getExams` | 直接查询 `classes` 表获取教师 gradeIds | 高(P1-1 待修复) |
|
||||||
| `getExamById` | 直接查询 `classes` 表获取教师 gradeIds(第 155-159 行) | 高 |
|
| `getExamById` | 直接查询 `classes` 表获取教师 gradeIds | 高(P1-1 待修复) |
|
||||||
| `persistAiGeneratedExamDraft` | 直接 insert 到 `questions` 表(第 317-326 行) | 高 |
|
| `persistAiGeneratedExamDraft` | 直接 insert 到 `questions` 表 | 高(P1-1 待修复) |
|
||||||
|
|
||||||
#### actions 层问题
|
#### actions 层问题 ✅ 已修复(P1-2)
|
||||||
|
|
||||||
| 函数 | 问题 | 严重程度 |
|
~~exams/actions.ts 中的 DB 操作已下沉到 data-access~~
|
||||||
|------|------|---------|
|
|
||||||
| `updateExamAction` | 直接 `db.query.exams.findFirst` 做归属校验 + `db.delete`/`db.insert`/`db.update` 操作 examQuestions | 高 |
|
**已完成修复**(2026-06-17,commit 84d6636):
|
||||||
| `deleteExamAction` | 直接 `db.query.exams.findFirst` 做归属校验 + `db.delete` | 高 |
|
- 新增 7 个 data-access 函数(updateExam / deleteExam / duplicateExam / getExamPreview 等)
|
||||||
| `duplicateExamAction` | 直接 `db.query.exams.findFirst` + `db.transaction` 内联 insert exams/examQuestions | 高 |
|
- actions.ts 从 832 行降至 691 行
|
||||||
| `getExamPreviewAction` | 直接 `db.query.exams.findFirst` 查询考试预览数据 | 高 |
|
- data-access.ts 从 339 行扩展到 471 行
|
||||||
| `getSubjectsAction` | 直接查询 `subjects` 表 —— 跨模块 DB 访问 | 高 |
|
- actions 层不再有直接 `db.insert/update/delete`
|
||||||
| `getGradesAction` | 直接查询 `grades` 表 —— 跨模块 DB 访问 | 高 |
|
|
||||||
|
|
||||||
#### 组件耦合
|
#### 组件耦合
|
||||||
|
|
||||||
@@ -122,9 +121,9 @@
|
|||||||
|
|
||||||
#### ai-pipeline.ts 问题
|
#### ai-pipeline.ts 问题
|
||||||
|
|
||||||
- 912 行,超过 800 行建议值
|
- 857 行,超过 800 行建议值(原 912 行,已部分优化)
|
||||||
- 混合了 Zod schema、AI prompt、JSON 解析修复、题目详情解析、并发控制等多种职责
|
- 混合了 Zod schema、AI prompt、JSON 解析修复、题目详情解析、并发控制等多种职责
|
||||||
- 建议拆分为:`ai-schema.ts`(Zod schema)、`ai-prompts.ts`(prompt 常量)、`ai-parser.ts`(JSON 解析修复)、`ai-pipeline.ts`(核心生成逻辑)
|
- 建议拆分为:`ai-schema.ts`(Zod schema)、`ai-prompts.ts`(prompt 常量)、`ai-parser.ts`(JSON 解析修复)、`ai-pipeline.ts`(核心生成逻辑)(P1 待处理)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -134,49 +133,48 @@
|
|||||||
|
|
||||||
| 文件 | 行数 | 限制 | 状态 |
|
| 文件 | 行数 | 限制 | 状态 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| data-access.ts | **1038** | ≤1000 硬上限 | ❌ **必须拆分** |
|
| data-access.ts | 598 | ≤1000 硬上限 | ✅(P0-2 后从 1038 降至 598) |
|
||||||
| actions.ts | 387 | ≤800 | ✅ |
|
| data-access-write.ts | 285 | ≤800 | ✅(P1-2 新增,10 个写函数) |
|
||||||
|
| stats-service.ts | 425 | ≤800 | ✅(P0-2 新增,统计业务逻辑) |
|
||||||
|
| actions.ts | 239 | ≤800 | ✅(P1-2 后从 387 降至 239) |
|
||||||
| schema.ts | 29 | 无限制 | ✅ |
|
| schema.ts | 29 | 无限制 | ✅ |
|
||||||
| types.ts | 186 | 无限制 | ✅ |
|
| types.ts | 186 | 无限制 | ✅ |
|
||||||
|
|
||||||
#### 模块职责边界
|
#### 模块职责边界
|
||||||
|
|
||||||
- **职责**:作业全生命周期(创建/发布/作答/批改/分析)
|
- **职责**:作业全生命周期(创建/发布/作答/批改/分析)
|
||||||
- **问题**:`getStudentDashboardGrades` 包含班级排名计算逻辑(150+ 行),属于 dashboard 或 grades 模块职责
|
- **问题**:`getStudentDashboardGrades` 包含班级排名计算逻辑(已迁移到 stats-service.ts)
|
||||||
|
|
||||||
#### data-access 层问题(严重)
|
#### data-access 层问题(部分修复)
|
||||||
|
|
||||||
| 函数 | 问题 | 严重程度 |
|
| 函数 | 问题 | 严重程度 |
|
||||||
|------|------|---------|
|
|------|------|---------|
|
||||||
| `getStudentDashboardGrades` | 150+ 行排名计算业务逻辑混入 data-access | 高 |
|
| ~~`getStudentDashboardGrades`~~ | ~~150+ 行排名计算业务逻辑混入 data-access~~ ✅ 已迁移到 stats-service.ts | ✅ 已修复 |
|
||||||
| `getHomeworkAssignmentAnalytics` | 145+ 行错误率/错误答案统计业务逻辑混入 data-access | 高 |
|
| ~~`getHomeworkAssignmentAnalytics`~~ | ~~145+ 行错误率/错误答案统计业务逻辑混入 data-access~~ ✅ 已迁移到 stats-service.ts | ✅ 已修复 |
|
||||||
| `getHomeworkAssignments` | 直接查询 `exams` 表(第 167-171 行) | 高 |
|
| `getHomeworkAssignments` | 直接查询 `exams` 表 | 高(P1-1 待修复) |
|
||||||
| `getHomeworkAssignmentReviewList` | 直接查询 `exams` 表(第 227-233 行) | 高 |
|
| `getHomeworkAssignmentReviewList` | 直接查询 `exams` 表 | 高(P1-1 待修复) |
|
||||||
| `getHomeworkSubmissions` | 直接查询 `exams` 表(第 349-359 行) | 高 |
|
| `getHomeworkSubmissions` | 直接查询 `exams` 表 | 高(P1-1 待修复) |
|
||||||
| `getHomeworkAssignmentById` | 直接查询 `exams` 表(第 403-407 行) | 高 |
|
| `getHomeworkAssignmentById` | 直接查询 `exams` 表 | 高(P1-1 待修复) |
|
||||||
| `getStudentHomeworkAssignments` | 直接 join `exams`/`subjects` 表(第 730-731 行) | 高 |
|
| `getStudentHomeworkAssignments` | 直接 join `exams`/`subjects` 表 | 高(P1-1 待修复) |
|
||||||
| `getDemoStudentUser` | 直接查询 `users`/`roles`/`usersToRoles` 表 + 使用 `auth()` 而非 auth-guard | 高 |
|
| `getDemoStudentUser` | 直接查询 `users`/`roles`/`usersToRoles` 表 + 使用 `auth()` 而非 auth-guard | 高(P1-1 待修复) |
|
||||||
| `getStudentDashboardGrades` | 直接查询 `classEnrollments`/`users` 表 | 高 |
|
| `getStudentDashboardGrades` | 直接查询 `classEnrollments`/`users` 表 | 高(P1-1 待修复) |
|
||||||
|
|
||||||
#### actions 层问题
|
#### actions 层问题 ✅ 已修复(P1-2)
|
||||||
|
|
||||||
| 函数 | 问题 | 严重程度 |
|
~~homework/actions.ts 中的 DB 操作已下沉到 data-access~~
|
||||||
|------|------|---------|
|
|
||||||
| `createHomeworkAssignmentAction` | **157 行**,混合数据访问 + 业务逻辑 + 权限校验 | 高 |
|
|
||||||
| 同上 | 直接查询 `classes`/`classSubjectTeachers`/`exams`/`classEnrollments` 表 | 高 |
|
|
||||||
| 同上 | 内联 `db.transaction` insert homeworkAssignments/homeworkAssignmentQuestions/homeworkAssignmentTargets | 高 |
|
|
||||||
| `startHomeworkSubmissionAction` | 直接 `db.query` + `db.insert` | 高 |
|
|
||||||
| `saveHomeworkAnswerAction` | 直接 `db.query` + `db.transaction` | 高 |
|
|
||||||
| `submitHomeworkAction` | 直接 `db.query` + `db.update` | 高 |
|
|
||||||
| `gradeHomeworkSubmissionAction` | 直接 `db.update` 循环更新 homeworkAnswers | 高 |
|
|
||||||
|
|
||||||
#### 拆分建议
|
**已完成修复**(2026-06-17,commit 84d6636):
|
||||||
|
- 新建 data-access-write.ts(285 行,10 个写函数)
|
||||||
|
- actions.ts 从 387 行降至 239 行
|
||||||
|
- `createHomeworkAssignmentAction` 等 Action 的 DB 操作全部下沉到 data-access-write.ts
|
||||||
|
- actions 层不再有直接 `db.insert/update/delete`
|
||||||
|
|
||||||
`data-access.ts`(1038 行)建议拆分为:
|
#### 拆分结果 ✅ 已完成
|
||||||
- `data-access.ts`:基础 CRUD(getHomeworkAssignments, getHomeworkAssignmentById, getHomeworkSubmissions)
|
|
||||||
- `data-access-student.ts`:学生视角查询(getStudentHomeworkAssignments, getStudentHomeworkTakeData, getStudentDashboardGrades)
|
`data-access.ts`(原 1038 行)已拆分为:
|
||||||
- `data-access-analytics.ts`:分析统计(getHomeworkAssignmentAnalytics, getTeacherGradeTrends)
|
- `data-access.ts`(598 行):基础 CRUD + 查询
|
||||||
- `data-access-grading.ts`:批改相关(getHomeworkSubmissionDetails, getHomeworkAssignmentReviewList)
|
- `data-access-write.ts`(285 行):写操作(10 个写函数)
|
||||||
|
- `stats-service.ts`(425 行):统计业务逻辑(排名计算、错误率统计等)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -186,30 +184,33 @@
|
|||||||
|
|
||||||
| 文件 | 行数 | 限制 | 状态 |
|
| 文件 | 行数 | 限制 | 状态 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| actions.ts | 294 | ≤800 | ✅ |
|
| actions.ts | 149 | ≤800 | ✅(P1-2 后从 294 降至 149) |
|
||||||
| data-access.ts | 129 | ≤800 | ✅ |
|
| data-access.ts | 260 | ≤800 | ✅(P1-2 后从 129 扩展到 260) |
|
||||||
| schema.ts | 18 | 无限制 | ✅ |
|
| schema.ts | 18 | 无限制 | ✅ |
|
||||||
| types.ts | 34 | 无限制 | ✅ |
|
| types.ts | 34 | 无限制 | ✅ |
|
||||||
|
|
||||||
#### 模块职责边界
|
#### 模块职责边界
|
||||||
|
|
||||||
- **职责**:题库管理(题目 CRUD、知识点关联、题型支持)
|
- **职责**:题库管理(题目 CRUD、知识点关联、题型支持)
|
||||||
- **问题**:`getKnowledgePointOptionsAction` 查询 textbooks 模块的表,属于 textbooks 模块职责
|
- **问题**:`getKnowledgePointOptionsAction` 查询 textbooks 模块的表,属于 textbooks 模块职责(P1-1 待修复)
|
||||||
|
|
||||||
#### data-access 层问题
|
#### data-access 层问题 ✅ 已修复(P1-2)
|
||||||
|
|
||||||
- ✅ 仅访问 `questions` 和 `questionsToKnowledgePoints` 表,无跨模块 DB 访问
|
- ✅ 仅访问 `questions` 和 `questionsToKnowledgePoints` 表,无跨模块 DB 访问
|
||||||
- ❌ **缺失写操作函数**:`insertQuestionWithRelations`、`deleteQuestionRecursive` 等 data-access 函数被错误地放在 actions.ts 中
|
- ✅ **写操作函数已补全**:`insertQuestionWithRelations`、`deleteQuestionRecursive` 等 data-access 函数已从 actions.ts 迁移到 data-access.ts(data-access.ts 从 129 行扩展到 260 行)
|
||||||
|
|
||||||
#### actions 层问题
|
#### actions 层问题 ✅ 已修复(P1-2)
|
||||||
|
|
||||||
| 函数 | 问题 | 严重程度 |
|
~~questions/actions.ts 中的 DB 操作已下沉到 data-access~~
|
||||||
|------|------|---------|
|
|
||||||
| `createNestedQuestion` | 内联 `db.transaction` + 调用 `insertQuestionWithRelations`(data-access 函数错放在 actions) | 高 |
|
**已完成修复**(2026-06-17,commit 84d6636):
|
||||||
| `updateQuestionAction` | 内联 `db.transaction` 做 update/delete/insert | 高 |
|
- 新增 4 个 data-access 函数(insertQuestionWithRelations / deleteQuestionRecursive 等)
|
||||||
| `deleteQuestionAction` | 内联 `db.transaction` + 调用 `deleteQuestionRecursive`(data-access 函数错放在 actions) | 高 |
|
- actions.ts 从 294 行降至 149 行
|
||||||
| `getKnowledgePointOptionsAction` | 直接查询 `knowledgePoints`/`chapters`/`textbooks` 表 —— 跨模块 DB 访问 | 高 |
|
- `createNestedQuestion` / `updateQuestionAction` / `deleteQuestionAction` 的 DB 操作全部下沉
|
||||||
| `getQuestionsAction` | ✅ 正确委托给 data-access.getQuestions | ✅ |
|
- actions 层不再有直接 `db.transaction`
|
||||||
|
|
||||||
|
**剩余问题**:
|
||||||
|
- `getKnowledgePointOptionsAction` 仍直接查询 `knowledgePoints`/`chapters`/`textbooks` 表 —— 跨模块 DB 访问(P1-1 待修复)
|
||||||
|
|
||||||
#### 组件耦合
|
#### 组件耦合
|
||||||
|
|
||||||
@@ -348,10 +349,11 @@
|
|||||||
|
|
||||||
### 5.1 高优先级(P0)
|
### 5.1 高优先级(P0)
|
||||||
|
|
||||||
1. **拆分 homework/data-access.ts**(1038 行 → 4 个文件)
|
1. ~~**拆分 homework/data-access.ts**(1038 行 → 4 个文件)~~ ✅ 已完成
|
||||||
- 按职责拆分为 data-access.ts / data-access-student.ts / data-access-analytics.ts / data-access-grading.ts
|
- ~~按职责拆分为 data-access.ts / data-access-student.ts / data-access-analytics.ts / data-access-grading.ts~~
|
||||||
|
- 实际拆分为 data-access.ts(598 行)+ data-access-write.ts(285 行)+ stats-service.ts(425 行)
|
||||||
|
|
||||||
2. **消除跨模块直接 DB 访问**
|
2. **消除跨模块直接 DB 访问**(P1-1 待修复)
|
||||||
- 在 classes/data-access 暴露 `getClassGradeIdsByClassIds`、`getClassStudentsByClassId`、`getActiveClassStudents` 等函数
|
- 在 classes/data-access 暴露 `getClassGradeIdsByClassIds`、`getClassStudentsByClassId`、`getActiveClassStudents` 等函数
|
||||||
- 在 exams/data-access 暴露 `getExamForHomeworkCreation`(含 questions 关联)
|
- 在 exams/data-access 暴露 `getExamForHomeworkCreation`(含 questions 关联)
|
||||||
- 在 school/data-access 暴露 `getSubjectOptions`、`getGradeOptions`
|
- 在 school/data-access 暴露 `getSubjectOptions`、`getGradeOptions`
|
||||||
@@ -359,41 +361,41 @@
|
|||||||
- 在 textbooks/data-access 暴露 `getKnowledgePointOptions`
|
- 在 textbooks/data-access 暴露 `getKnowledgePointOptions`
|
||||||
- 在 questions/data-access 暴露 `insertQuestionWithRelations`、`deleteQuestionRecursive`
|
- 在 questions/data-access 暴露 `insertQuestionWithRelations`、`deleteQuestionRecursive`
|
||||||
|
|
||||||
3. **将 exams/actions.ts 中的 DB 操作下沉到 data-access**
|
3. ~~**将 exams/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成(P1-2)
|
||||||
- `updateExamAction`、`deleteExamAction`、`duplicateExamAction`、`getExamPreviewAction` 的 DB 操作移至 data-access
|
- ~~`updateExamAction`、`deleteExamAction`、`duplicateExamAction`、`getExamPreviewAction` 的 DB 操作移至 data-access~~
|
||||||
- 将 `getSubjectsAction`/`getGradesAction` 移至 school 模块或改为调用 school/data-access
|
- ~~将 `getSubjectsAction`/`getGradesAction` 移至 school 模块或改为调用 school/data-access~~(P1-1 待修复)
|
||||||
|
|
||||||
4. **将 homework/actions.ts 中的 DB 操作下沉到 data-access**
|
4. ~~**将 homework/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成(P1-2)
|
||||||
- `createHomeworkAssignmentAction`(157 行)拆分为:data-access 函数 + action 编排
|
- ~~`createHomeworkAssignmentAction`(157 行)拆分为:data-access 函数 + action 编排~~
|
||||||
- 其他 action 的 DB 操作全部移至 data-access
|
- ~~其他 action 的 DB 操作全部移至 data-access~~
|
||||||
|
|
||||||
5. **将 questions/actions.ts 中的 DB 操作下沉到 data-access**
|
5. ~~**将 questions/actions.ts 中的 DB 操作下沉到 data-access**~~ ✅ 已完成(P1-2)
|
||||||
- `insertQuestionWithRelations`、`deleteQuestionRecursive` 移至 data-access
|
- ~~`insertQuestionWithRelations`、`deleteQuestionRecursive` 移至 data-access~~
|
||||||
- `getKnowledgePointOptionsAction` 改为调用 textbooks/data-access
|
- ~~`getKnowledgePointOptionsAction` 改为调用 textbooks/data-access~~(P1-1 待修复)
|
||||||
|
|
||||||
### 5.2 中优先级(P1)
|
### 5.2 中优先级(P1)
|
||||||
|
|
||||||
6. **拆分 exams/ai-pipeline.ts**(912 行 → 4 个文件)
|
6. **拆分 exams/ai-pipeline.ts**(857 行 → 4 个文件)
|
||||||
- ai-schema.ts(Zod schema)、ai-prompts.ts(prompt 常量)、ai-parser.ts(JSON 解析修复)、ai-pipeline.ts(核心生成逻辑)
|
- ai-schema.ts(Zod schema)、ai-prompts.ts(prompt 常量)、ai-parser.ts(JSON 解析修复)、ai-pipeline.ts(核心生成逻辑)
|
||||||
|
|
||||||
7. **将 homework/data-access.ts 中的业务逻辑提取到独立服务层**
|
7. ~~**将 homework/data-access.ts 中的业务逻辑提取到独立服务层**~~ ✅ 已完成
|
||||||
- `getStudentDashboardGrades` 的排名计算逻辑提取到 `services/ranking-service.ts`
|
- ~~`getStudentDashboardGrades` 的排名计算逻辑提取到 `services/ranking-service.ts`~~ → 实际提取到 `stats-service.ts`
|
||||||
- `getHomeworkAssignmentAnalytics` 的错误率统计逻辑提取到 `services/analytics-service.ts`
|
- ~~`getHomeworkAssignmentAnalytics` 的错误率统计逻辑提取到 `services/analytics-service.ts`~~ → 实际提取到 `stats-service.ts`
|
||||||
|
|
||||||
8. **将 grades/data-access.ts 中的统计计算逻辑提取到独立服务层**
|
8. **将 grades/data-access.ts 中的统计计算逻辑提取到独立服务层**(P1 待处理)
|
||||||
- `getClassGradeStats` 的统计计算提取到 `services/stats-service.ts`
|
- `getClassGradeStats` 的统计计算提取到 `services/stats-service.ts`
|
||||||
- `getGradeDistribution` 的分桶逻辑提取到 `services/distribution-service.ts`
|
- `getGradeDistribution` 的分桶逻辑提取到 `services/distribution-service.ts`
|
||||||
|
|
||||||
9. **减少组件 props 数量**
|
9. **减少组件 props 数量**(P2 待处理)
|
||||||
- `ExamAssembly`(10 props)和 `ExamPreviewQuestionEditor`(10 props)应考虑使用 Context 或组合模式减少 props
|
- `ExamAssembly`(10 props)和 `ExamPreviewQuestionEditor`(10 props)应考虑使用 Context 或组合模式减少 props
|
||||||
|
|
||||||
### 5.3 低优先级(P2)
|
### 5.3 低优先级(P2)
|
||||||
|
|
||||||
10. **统一 auth 调用方式**
|
10. **统一 auth 调用方式**(P2 待处理)
|
||||||
- `homework/data-access.ts` 的 `getDemoStudentUser` 使用 `auth()` 而非 `auth-guard.getAuthContext()`,应统一
|
- `homework/data-access.ts` 的 `getDemoStudentUser` 使用 `auth()` 而非 `auth-guard.getAuthContext()`,应统一
|
||||||
|
|
||||||
11. **补全 questions/data-access.ts 的写操作**
|
11. ~~**补全 questions/data-access.ts 的写操作**~~ ✅ 已完成(P1-2)
|
||||||
- 当前 data-access 仅有 `getQuestions`,所有写操作错放在 actions.ts
|
- ~~当前 data-access 仅有 `getQuestions`,所有写操作错放在 actions.ts~~ → 写操作已下沉到 data-access
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -12,19 +12,19 @@
|
|||||||
| 模块 | 行数(最大文件) | 职责单一性 | 耦合度 | 严重度 |
|
| 模块 | 行数(最大文件) | 职责单一性 | 耦合度 | 严重度 |
|
||||||
|------|----------------|-----------|--------|--------|
|
|------|----------------|-----------|--------|--------|
|
||||||
| school | 325 | ✅ 良好 | ✅ 低 | 🟢 合格 |
|
| school | 325 | ✅ 良好 | ✅ 低 | 🟢 合格 |
|
||||||
| classes | ~~2104~~ → 656 | ✅ 已修复 | ❌ 严重 | 🟡 需改进 |
|
| classes | ~~2104~~ → 548 | ✅ 已修复 | ❌ 严重 | 🟡 需改进 |
|
||||||
| scheduling | 310(算法)/ 302(actions) | ✅ 算法独立 | ⚠️ 中 | 🟡 需改进 |
|
| scheduling | 335(data-access)/ 266(actions) | ✅ 算法独立 | ⚠️ 中 | 🟡 需改进 |
|
||||||
| attendance | 271 | ✅ 良好 | ⚠️ 中 | 🟢 合格 |
|
| attendance | 271 | ✅ 良好 | ⚠️ 中 | 🟢 合格 |
|
||||||
| users | 291(import-export) | ❌ 违反 | ❌ 高 | 🟠 较严重 |
|
| users | 157(import-export) | ✅ 已修复 | ⚠️ 中 | 🟢 合格 |
|
||||||
| audit | 212 | ⚠️ 部分违反 | ✅ 低 | 🟡 需改进 |
|
| audit | 212 | ⚠️ 部分违反 | ✅ 低 | 🟡 需改进 |
|
||||||
| course-plans | 320 | ✅ 良好 | ✅ 低 | 🟢 合格 |
|
| course-plans | 320 | ✅ 良好 | ✅ 低 | 🟢 合格 |
|
||||||
| announcements | 242 | ⚠️ 部分违反 | ✅ 低 | 🟡 需改进 |
|
| announcements | 197 | ✅ 已修复 | ✅ 低 | 🟢 合格 |
|
||||||
|
|
||||||
**核心结论**:
|
**核心结论**:
|
||||||
1. ~~`classes` 模块是全项目耦合最严重的模块,单文件 2104 行远超 1000 行硬性上限,混入了 schedule、homework、grades 三个业务领域的逻辑。~~ ✅ 已修复(2026-06-17 拆分为 5 个文件,均 ≤800 行)
|
1. ~~`classes` 模块是全项目耦合最严重的模块,单文件 2104 行远超 1000 行硬性上限,混入了 schedule、homework、grades 三个业务领域的逻辑。~~ ✅ 已修复(2026-06-17 拆分为 5 个文件,均 ≤800 行)
|
||||||
2. `users/import-export.ts` 违反单一职责,同时处理导入、导出、用户创建、班级注册四类逻辑。
|
2. ~~`users/import-export.ts` 违反单一职责,同时处理导入、导出、用户创建、班级注册四类逻辑。~~ ✅ 已修复(2026-06-17 拆分为 import-export.ts + user-service.ts + class-registration.ts)
|
||||||
3. `scheduling/auto-scheduler.ts` 是算法独立化的**优秀范例**,纯函数、无 DB 访问、可独立测试。
|
3. `scheduling/auto-scheduler.ts` 是算法独立化的**优秀范例**,纯函数、无 DB 访问、可独立测试。
|
||||||
4. `announcements` 和 `audit` 模块的 data-access 层不完整,写操作或导出逻辑泄漏到 actions 层。
|
4. ~~`announcements` 和 `audit` 模块的 data-access 层不完整,写操作或导出逻辑泄漏到 actions 层。~~ announcements ✅ 已修复(写操作下沉 data-access);audit 仍有导出逻辑内联。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -51,28 +51,25 @@
|
|||||||
|
|
||||||
### 2.2 classes 模块 — 🟡 需改进(文件拆分已修复,跨模块耦合仍存在)
|
### 2.2 classes 模块 — 🟡 需改进(文件拆分已修复,跨模块耦合仍存在)
|
||||||
|
|
||||||
**文件清单**:actions.ts (765 行) / data-access.ts (656 行) / data-access-stats.ts (604 行) / data-access-schedule.ts (230 行) / data-access-students.ts (280 行) / data-access-admin.ts (441 行) / types.ts (201 行)
|
**文件清单**:actions.ts (676 行) / data-access.ts (548 行) / data-access-stats.ts (531 行) / data-access-schedule.ts (194 行) / data-access-students.ts (244 行) / data-access-admin.ts (406 行) / types.ts (201 行)
|
||||||
|
|
||||||
> ✅ `data-access.ts` 已于 2026-06-17 拆分为 5 个文件,所有文件均 ≤800 行,通过 re-export 保持向后兼容。
|
> ✅ `data-access.ts` 已于 2026-06-17 拆分为 5 个文件,所有文件均 ≤800 行,通过 re-export 保持向后兼容。
|
||||||
|
|
||||||
#### 2.2.1 职责混乱 — 混入三个外部业务领域
|
#### 2.2.1 职责混乱 — 混入三个外部业务领域(拆分后仍存在于子文件中)
|
||||||
|
|
||||||
`data-access.ts` 实际承载了四个业务领域的逻辑:
|
`data-access-*.ts` 文件群仍承载了四个业务领域的逻辑(已按职责分文件,但跨域逻辑尚未迁移回所属模块):
|
||||||
|
|
||||||
| 行范围 | 逻辑 | 应属模块 |
|
| 文件 | 逻辑 | 应属模块 |
|
||||||
|--------|------|---------|
|
|------|------|---------|
|
||||||
| 49-145 | 教师身份解析、班级访问控制 | classes(合理) |
|
| data-access.ts | 教师身份解析、班级访问控制、班级 CRUD | classes(合理) |
|
||||||
| 156-601 | 班级列表/学生/教师查询 | classes(合理) |
|
| data-access-students.ts | 班级学生查询 | classes(合理) |
|
||||||
| 697-735 | 课表查询 `getClassSchedule` | **scheduling** |
|
| data-access-stats.ts | `getClassHomeworkInsights` / `getGradeHomeworkInsights` 班级/年级作业洞察 | **homework** |
|
||||||
| 760-998 | `getClassHomeworkInsights` 班级作业洞察(238 行) | **homework** |
|
| data-access-schedule.ts | 课表查询 `getClassSchedule`、课表项 CRUD | **scheduling** |
|
||||||
| 1006-1300 | `getGradeHomeworkInsights` 年级作业洞察(294 行) | **homework** |
|
| data-access-admin.ts | `getStudentsSubjectScores` 学生科目成绩 | **grades / homework** |
|
||||||
| 1302-1775 | 班级 CRUD + 邀请码 + 注册 | classes(合理) |
|
|
||||||
| 1838-1968 | 课表项 CRUD `createClassScheduleItem` 等 | **scheduling** |
|
|
||||||
| 1970-2103 | `getStudentsSubjectScores` 学生科目成绩(133 行) | **grades / homework** |
|
|
||||||
|
|
||||||
**关键问题**:
|
**关键问题**(P1-1 待修复):
|
||||||
- `getClassHomeworkInsights` 和 `getGradeHomeworkInsights` 合计 **532 行**作业统计逻辑,直接查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 五张表,属于 homework 模块的核心业务,不应存在于 classes 模块。
|
- `getClassHomeworkInsights` 和 `getGradeHomeworkInsights` 直接查询 `homeworkAssignments`、`homeworkSubmissions`、`homeworkAssignmentTargets`、`homeworkAssignmentQuestions`、`exams` 五张表,属于 homework 模块的核心业务,不应存在于 classes 模块。
|
||||||
- 课表 CRUD(`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`)与 scheduling 模块的 `applyAutoScheduleAction` 写入同一张 `classSchedule` 表,**两个模块对同一张表有写权限**,边界严重模糊。
|
- 课表 CRUD(`createClassScheduleItem` / `updateClassScheduleItem` / `deleteClassScheduleItem`)写入 `classSchedule` 表,P0-6 已统一 scheduling/data-access 为写入口,但 classes 侧的写函数仍存在(待后续迁移)。
|
||||||
- `getStudentsSubjectScores` 直接关联 `homeworkSubmissions` + `exams` + `subjects` 计算学生科目分数,属于成绩分析逻辑。
|
- `getStudentsSubjectScores` 直接关联 `homeworkSubmissions` + `exams` + `subjects` 计算学生科目分数,属于成绩分析逻辑。
|
||||||
|
|
||||||
#### 2.2.2 types.ts 跨领域类型污染
|
#### 2.2.2 types.ts 跨领域类型污染
|
||||||
@@ -121,9 +118,9 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2.3 scheduling 模块 — 🟡 需改进(算法层优秀)
|
### 2.3 scheduling 模块 — 🟡 需改进(算法层优秀,写入口已统一)
|
||||||
|
|
||||||
**文件清单**:actions.ts (302 行) / auto-scheduler.ts (310 行) / data-access.ts (272 行) / schema.ts / types.ts
|
**文件清单**:actions.ts (266 行) / auto-scheduler.ts (310 行) / data-access.ts (335 行) / schema.ts / types.ts
|
||||||
|
|
||||||
#### 2.3.1 auto-scheduler.ts — ✅ 优秀范例
|
#### 2.3.1 auto-scheduler.ts — ✅ 优秀范例
|
||||||
|
|
||||||
@@ -136,19 +133,21 @@
|
|||||||
|
|
||||||
**建议**:以此为模板,指导其他模块的算法抽取(如 homework 的批改评分算法、grades 的统计算法)。
|
**建议**:以此为模板,指导其他模块的算法抽取(如 homework 的批改评分算法、grades 的统计算法)。
|
||||||
|
|
||||||
#### 2.3.2 actions.ts — 跨模块直接写入
|
#### 2.3.2 actions.ts — 跨模块直接查询(写入口已统一)
|
||||||
|
|
||||||
| 行号 | 函数 | 问题 |
|
| 行号 | 函数 | 问题 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| 110-116 | `autoScheduleAction` | 直接 `db.select().from(users)` 查询教师,绕过 data-access 的 `getTeachersForScheduling` |
|
| 110-116 | `autoScheduleAction` | 直接 `db.select().from(users)` 查询教师,绕过 data-access 的 `getTeachersForScheduling`(P1-2 待修复) |
|
||||||
| 168-180 | `applyAutoScheduleAction` | 直接 `db.transaction` 写入 `classSchedule` 表,**绕过 data-access 且跨模块写 classes 领域的表** |
|
| ~~168-180~~ | ~~`applyAutoScheduleAction`~~ | ~~直接 `db.transaction` 写入 `classSchedule` 表~~ ✅ 已修复(P0-6,改为调用 `replaceClassSchedule`) |
|
||||||
|
|
||||||
`applyAutoScheduleAction` 的问题尤为突出:它删除并重建 `classSchedule` 表数据,但该写操作既未经过 scheduling/data-access,也未经过 classes/data-access,形成**第三个对 classSchedule 表的写入口**(另两个在 classes/data-access 的 createClassScheduleItem 等)。
|
`applyAutoScheduleAction` 的直接 transaction 写入问题已于 P0-6 修复,现在通过 `scheduling/data-access.ts` 的 `replaceClassSchedule()` 统一写入。但 `autoScheduleAction` 仍直接查询 users 表(P1-2 待修复)。
|
||||||
|
|
||||||
#### 2.3.3 data-access.ts — 跨模块读查询
|
#### 2.3.3 data-access.ts — 跨模块读查询 + 统一写入口
|
||||||
|
|
||||||
包含 `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getClassroomsForScheduling` / `getClassSubjectsForScheduling` 四个辅助查询,直接访问 `classes` / `classSubjectTeachers` / `subjects` / `users` / `classrooms` 表。
|
包含 `getAdminClassesForScheduling` / `getTeachersForScheduling` / `getClassroomsForScheduling` / `getClassSubjectsForScheduling` 四个辅助查询,直接访问 `classes` / `classSubjectTeachers` / `subjects` / `users` / `classrooms` 表。
|
||||||
|
|
||||||
|
P0-6 后新增 `replaceClassSchedule()` 作为 `classSchedule` 表的统一写入口。
|
||||||
|
|
||||||
这些是排课场景的只读辅助查询,耦合度可接受,但理想情况下应通过各所属模块的 data-access 暴露接口。
|
这些是排课场景的只读辅助查询,耦合度可接受,但理想情况下应通过各所属模块的 data-access 暴露接口。
|
||||||
|
|
||||||
#### 2.3.4 actions.ts 末尾 re-export
|
#### 2.3.4 actions.ts 末尾 re-export
|
||||||
@@ -156,12 +155,12 @@
|
|||||||
```typescript
|
```typescript
|
||||||
export { getSchedulingRules, getScheduleChanges, ... } from "./data-access"
|
export { getSchedulingRules, getScheduleChanges, ... } from "./data-access"
|
||||||
```
|
```
|
||||||
actions 层 re-export data-access 函数是反模式,应让消费方直接从 data-access 导入。
|
actions 层 re-export data-access 函数是反模式,应让消费方直接从 data-access 导入。(P2 待修复)
|
||||||
|
|
||||||
**整改建议**:
|
**整改建议**:
|
||||||
1. 将 `applyAutoScheduleAction` 中的 `classSchedule` 写入逻辑下沉到 data-access(或与 classes 协商统一写入口)。
|
1. ~~将 `applyAutoScheduleAction` 中的 `classSchedule` 写入逻辑下沉到 data-access~~ ✅ 已完成(P0-6)
|
||||||
2. 将 `autoScheduleAction` 中的 users 查询改用 `getTeachersForScheduling`。
|
2. 将 `autoScheduleAction` 中的 users 查询改用 `getTeachersForScheduling`(P1-2 待修复)
|
||||||
3. 移除 actions.ts 末尾的 re-export。
|
3. 移除 actions.ts 末尾的 re-export(P2 待修复)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -183,40 +182,41 @@ actions 层 re-export data-access 函数是反模式,应让消费方直接从
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2.5 users 模块 — 🟠 较严重
|
### 2.5 users 模块 — 🟢 合格(已修复)
|
||||||
|
|
||||||
**文件清单**:actions.ts (151 行) / data-access.ts (71 行) / import-export.ts (291 行)
|
**文件清单**:actions.ts (131 行) / data-access.ts (133 行) / import-export.ts (157 行) / user-service.ts (82 行) / class-registration.ts (21 行)
|
||||||
|
|
||||||
#### 2.5.1 import-export.ts — 四重职责混合
|
> ✅ `import-export.ts` 已于 2026-06-17 拆分为 3 个文件,四重职责已分离。
|
||||||
|
|
||||||
该文件同时承载四类互不相关的职责:
|
#### 2.5.1 import-export.ts — 四重职责已修复 ✅
|
||||||
|
|
||||||
| 行范围 | 职责 | 应属位置 |
|
原文件同时承载四类互不相关的职责,现已拆分:
|
||||||
|--------|------|---------|
|
|
||||||
| 54-73 | `generateUserImportTemplate` 生成导入模板 | export 模块 |
|
|
||||||
| 78-111 | `parseUserImportData` 解析+校验导入数据 | import 模块 |
|
|
||||||
| 116-207 | `batchImportUsers` 批量导入(含用户创建) | **data-access** |
|
|
||||||
| 212-291 | `exportUsersToExcel` 导出用户列表 | export 模块 |
|
|
||||||
|
|
||||||
**核心问题**:
|
| 文件 | 职责 | 行数 |
|
||||||
1. **导入与导出未分离**:应拆分为 `import.ts` 与 `export.ts`。
|
|------|------|------|
|
||||||
2. **用户创建逻辑泄漏**:`batchImportUsers`(行 158-167)直接 `db.insert(users)` + 密码哈希 + `db.insert(usersToRoles)`,这是 data-access 层的职责,不应存在于 import-export 工具文件中。
|
| `import-export.ts` | 文件解析与生成(`generateUserImportTemplate` / `parseUserImportData` / `exportUsersToExcel`) | 157 |
|
||||||
3. **跨模块写 classEnrollments**:行 174-184,`batchImportUsers` 直接 `db.insert(classEnrollments)` 将学生注册到班级,**这是 classes 模块的写操作**,严重违反模块边界。
|
| `user-service.ts` | 用户创建(含密码哈希 + 角色绑定) | 82 |
|
||||||
4. **跨模块读 classes**:行 130-137,直接查询 `classes` 表根据邀请码查找班级,应通过 classes/data-access 暴露接口。
|
| `class-registration.ts` | 班级注册(调用 classes/data-access) | 21 |
|
||||||
|
|
||||||
#### 2.5.2 actions.ts — 绕过 data-access
|
**已修复问题**:
|
||||||
|
1. ✅ **导入与导出未分离** → import-export.ts 仅负责文件解析与生成
|
||||||
|
2. ✅ **用户创建逻辑泄漏** → 迁移至 `user-service.ts`
|
||||||
|
3. ✅ **跨模块写 classEnrollments** → 迁移至 `class-registration.ts`,调用 classes/data-access
|
||||||
|
4. ✅ **跨模块读 classes** → 通过 classes/data-access 暴露接口调用
|
||||||
|
|
||||||
`updateUserProfile`(行 29-51)直接 `db.update(users)` 写入数据库,绕过了 data-access 层。data-access.ts 仅有 `getUserProfile` 一个读函数,写操作缺失。
|
#### 2.5.2 actions.ts — 绕过 data-access(部分修复)
|
||||||
|
|
||||||
#### 2.5.3 data-access.ts — 过于单薄
|
`updateUserProfile`(行 29-51)仍直接 `db.update(users)` 写入数据库,绕过了 data-access 层。(P1-2 待修复)
|
||||||
|
|
||||||
仅 71 行,只包含 `getUserProfile`。用户创建、更新、角色绑定等写操作均未在此层实现。
|
#### 2.5.3 data-access.ts — 已扩展
|
||||||
|
|
||||||
|
从 71 行扩展到 133 行,包含 `getUserProfile` 及 dashboard 聚合查询函数 `getUsersDashboardStats`。用户创建、更新等写操作部分仍在 user-service.ts 中(P1-2 待进一步下沉)。
|
||||||
|
|
||||||
**整改建议**(优先级 P1):
|
**整改建议**(优先级 P1):
|
||||||
1. 将 `import-export.ts` 拆分为 `import.ts`(解析+校验)和 `export.ts`(模板生成+列表导出)。
|
1. ~~将 `import-export.ts` 拆分为 `import.ts` 与 `export.ts`~~ ✅ 已完成(采用按职责拆分)
|
||||||
2. 将 `batchImportUsers` 中的用户创建逻辑迁移至 `data-access.ts` 的 `createUser` 函数,import.ts 仅负责编排。
|
2. ~~将 `batchImportUsers` 中的用户创建逻辑迁移至 `user-service.ts`~~ ✅ 已完成
|
||||||
3. 将 classEnrollments 写入改为调用 classes/data-access 的注册接口(如 `enrollStudentByInvitationCode`)。
|
3. ~~将 classEnrollments 写入改为调用 classes/data-access~~ ✅ 已完成
|
||||||
4. 将 `updateUserProfile` 的 DB 写入下沉到 data-access。
|
4. 将 `updateUserProfile` 的 DB 写入下沉到 data-access(P1-2 待修复)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -252,34 +252,33 @@ actions 层 re-export data-access 函数是反模式,应让消费方直接从
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 2.8 announcements 模块 — 🟡 需改进
|
### 2.8 announcements 模块 — 🟢 合格(已修复)
|
||||||
|
|
||||||
**文件清单**:actions.ts (242 行) / data-access.ts (120 行) / schema.ts / types.ts
|
**文件清单**:actions.ts (197 行) / data-access.ts (171 行) / schema.ts / types.ts
|
||||||
|
|
||||||
**核心问题 — 写操作泄漏到 actions 层**:
|
> ✅ 写操作已下沉到 data-access 层(2026-06-17,commit 84d6636)。
|
||||||
|
|
||||||
data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnouncementById`),所有写操作均直接在 actions.ts 中执行 `db.insert` / `db.update` / `db.delete`:
|
#### 核心问题 — 写操作泄漏到 actions 层 ✅ 已修复
|
||||||
|
|
||||||
| Action | 直接 DB 操作 | 行号 |
|
~~data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnouncementById`),所有写操作均直接在 actions.ts 中执行 `db.insert` / `db.update` / `db.delete`~~
|
||||||
|--------|-------------|------|
|
|
||||||
| `createAnnouncementAction` | `db.insert(announcements)` | 53-63 |
|
|
||||||
| `updateAnnouncementAction` | `db.update(announcements)` | 118-130 |
|
|
||||||
| `deleteAnnouncementAction` | `db.delete(announcements)` | 154 |
|
|
||||||
| `publishAnnouncementAction` | `db.update(announcements)` | 176-183 |
|
|
||||||
| `archiveAnnouncementAction` | `db.update(announcements)` | 206-212 |
|
|
||||||
|
|
||||||
这违反了"data-access 层封装所有 DB 操作"的分层约定,导致:
|
**已完成修复**:data-access.ts 从 120 行扩展到 171 行,新增 5 个写函数:
|
||||||
- 写逻辑无法被其他 server 端代码复用
|
- `createAnnouncement`
|
||||||
- publishedAt 计算逻辑散落在 actions 中,难以测试
|
- `updateAnnouncement`
|
||||||
|
- `deleteAnnouncement`
|
||||||
|
- `publishAnnouncement`
|
||||||
|
- `archiveAnnouncement`
|
||||||
|
|
||||||
|
actions.ts 从 242 行降至 197 行,仅保留权限校验 + 解析 + 委托调用。
|
||||||
|
|
||||||
**其他问题**:
|
**其他问题**:
|
||||||
- ⚠️ 死代码:`updateAnnouncementAction` 行 108 计算 `wasPublished`,行 135 `void wasPublished` 显式丢弃,未实际使用。
|
- ⚠️ 死代码:`updateAnnouncementAction` 行 108 计算 `wasPublished`,行 135 `void wasPublished` 显式丢弃,未实际使用。(P2 待清理)
|
||||||
- ⚠️ `getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)`,与其他模块的权限模式不一致。
|
- ⚠️ `getAnnouncementsAction` 使用 `requireAuth()` 而非 `requirePermission(ANNOUNCEMENT_READ)`,与其他模块的权限模式不一致。(P2 待统一)
|
||||||
|
|
||||||
**整改建议**:
|
**整改建议**:
|
||||||
1. 在 data-access.ts 补充 `createAnnouncement` / `updateAnnouncement` / `deleteAnnouncement` / `publishAnnouncement` / `archiveAnnouncement` 写函数。
|
1. ~~在 data-access.ts 补充 `createAnnouncement` / `updateAnnouncement` / `deleteAnnouncement` / `publishAnnouncement` / `archiveAnnouncement` 写函数~~ ✅ 已完成
|
||||||
2. actions 层仅保留权限校验 + 解析 + 委托调用。
|
2. ~~actions 层仅保留权限校验 + 解析 + 委托调用~~ ✅ 已完成
|
||||||
3. 清理 `wasPublished` 死代码。
|
3. 清理 `wasPublished` 死代码(P2 待处理)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -287,16 +286,16 @@ data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnoun
|
|||||||
|
|
||||||
### 3.1 跨模块写操作(严重)
|
### 3.1 跨模块写操作(严重)
|
||||||
|
|
||||||
| 源文件 | 目标表 | 操作 | 应通过 |
|
| 源文件 | 目标表 | 操作 | 应通过 | 状态 |
|
||||||
|--------|--------|------|--------|
|
|--------|--------|------|--------|------|
|
||||||
| classes/data-access.ts | classSchedule | CRUD | scheduling/data-access |
|
| classes/data-access.ts | classSchedule | CRUD | scheduling/data-access | ⚠️ P0-6 已统一 scheduling 为写入口,classes 侧写函数待迁移 |
|
||||||
| scheduling/actions.ts | classSchedule | delete + insert | scheduling/data-access |
|
| ~~scheduling/actions.ts~~ | ~~classSchedule~~ | ~~delete + insert~~ | ~~scheduling/data-access~~ | ✅ 已修复(P0-6,改用 replaceClassSchedule) |
|
||||||
| users/import-export.ts | classEnrollments | insert | classes/data-access |
|
| ~~users/import-export.ts~~ | ~~classEnrollments~~ | ~~insert~~ | ~~classes/data-access~~ | ✅ 已修复(迁移至 class-registration.ts) |
|
||||||
| users/import-export.ts | users, usersToRoles | insert | users/data-access |
|
| ~~users/import-export.ts~~ | ~~users, usersToRoles~~ | ~~insert~~ | ~~users/data-access~~ | ✅ 已修复(迁移至 user-service.ts) |
|
||||||
| announcements/actions.ts | announcements | insert/update/delete | announcements/data-access |
|
| ~~announcements/actions.ts~~ | ~~announcements~~ | ~~insert/update/delete~~ | ~~announcements/data-access~~ | ✅ 已修复(P1-2,写操作下沉) |
|
||||||
| users/actions.ts | users | update | users/data-access |
|
| users/actions.ts | users | update | users/data-access | ❌ 待修复(P1-2) |
|
||||||
|
|
||||||
> ⚠️ `classSchedule` 表存在 **三个写入口**(classes/data-access、scheduling/actions、scheduling/data-access 间接),是数据完整性高风险点。
|
> ✅ `classSchedule` 表写入口已于 P0-6 统一到 `scheduling/data-access.ts` 的 `replaceClassSchedule()`。
|
||||||
|
|
||||||
### 3.2 跨模块读操作(需评估)
|
### 3.2 跨模块读操作(需评估)
|
||||||
|
|
||||||
@@ -330,21 +329,21 @@ data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnoun
|
|||||||
|
|
||||||
### P0 — 立即整改(影响数据完整性 & 严重违反规范)
|
### P0 — 立即整改(影响数据完整性 & 严重违反规范)
|
||||||
|
|
||||||
1. **classes/data-access.ts 拆分**:2104 行远超硬性上限,且混入 homework/scheduling/grades 三个领域。优先迁移 `getClassHomeworkInsights` / `getGradeHomeworkInsights`(532 行)至 homework 模块。
|
1. ~~**classes/data-access.ts 拆分**:2104 行远超硬性上限,且混入 homework/scheduling/grades 三个领域。优先迁移 `getClassHomeworkInsights` / `getGradeHomeworkInsights`(532 行)至 homework 模块。~~ ✅ 已完成(拆分为 5 个文件)
|
||||||
2. **统一 classSchedule 表写入口**:classes 与 scheduling 两个模块对该表有写权限,需协商归属并收敛为单一写入口。
|
2. ~~**统一 classSchedule 表写入口**:classes 与 scheduling 两个模块对该表有写权限,需协商归属并收敛为单一写入口。~~ ✅ 已完成(P0-6,replaceClassSchedule 统一入口)
|
||||||
|
|
||||||
### P1 — 尽快整改(模块边界违反)
|
### P1 — 尽快整改(模块边界违反)
|
||||||
|
|
||||||
3. **users/import-export.ts 拆分**:分离导入/导出,用户创建逻辑下沉 data-access,classEnrollments 写入改调 classes 接口。
|
3. ~~**users/import-export.ts 拆分**:分离导入/导出,用户创建逻辑下沉 data-access,classEnrollments 写入改调 classes 接口。~~ ✅ 已完成
|
||||||
4. **announcements 写操作下沉**:在 data-access 补充写函数,actions 仅编排。
|
4. ~~**announcements 写操作下沉**:在 data-access 补充写函数,actions 仅编排。~~ ✅ 已完成
|
||||||
5. **classes/actions.ts 权限校验**:grades 表查询改通过 school/data-access 接口。
|
5. **classes/actions.ts 权限校验**:grades 表查询改通过 school/data-access 接口。(P1-1 待处理)
|
||||||
|
|
||||||
### P2 — 持续优化(代码质量)
|
### P2 — 持续优化(代码质量)
|
||||||
|
|
||||||
6. **audit 导出逻辑抽取**:内联的 Excel 列定义移至独立 export.ts。
|
6. **audit 导出逻辑抽取**:内联的 Excel 列定义移至独立 export.ts。
|
||||||
7. **school 审计日志补全**:department/academicYear/grade 的 CRUD 补充 logAudit。
|
7. **school 审计日志补全**:department/academicYear/grade 的 CRUD 补充 logAudit。
|
||||||
8. **attendance 跨模块查询**:`getClassStudentsForAttendance` 改调 classes 接口。
|
8. **attendance 跨模块查询**:`getClassStudentsForAttendance` 改调 classes 接口。
|
||||||
9. **scheduling/actions.ts**:移除 re-export,DB 写入下沉 data-access。
|
9. **scheduling/actions.ts**:移除 re-export,`autoScheduleAction` 的 users 查询下沉 data-access(P1-2)。
|
||||||
10. **announcements 死代码清理**:移除 `void wasPublished`。
|
10. **announcements 死代码清理**:移除 `void wasPublished`。
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -365,28 +364,34 @@ data-access.ts 仅包含两个**只读**函数(`getAnnouncements` / `getAnnoun
|
|||||||
|
|
||||||
| 文件 | 行数 | 上限 | 状态 |
|
| 文件 | 行数 | 上限 | 状态 |
|
||||||
|------|------|------|------|
|
|------|------|------|------|
|
||||||
| classes/data-access.ts | 2104 | 1000 | 🔴 超限 2.1x |
|
| ~~classes/data-access.ts~~ | ~~2104~~ → 548 | 1000 | ✅ 已拆分(5 个文件均 ≤800 行) |
|
||||||
| classes/actions.ts | 765 | 800(建议) | 🟢 合规 |
|
| classes/data-access-stats.ts | 531 | 800(建议) | 🟢 合规 |
|
||||||
|
| classes/data-access-admin.ts | 406 | 800(建议) | 🟢 合规 |
|
||||||
|
| classes/data-access-students.ts | 244 | 800(建议) | 🟢 合规 |
|
||||||
|
| classes/data-access-schedule.ts | 194 | 800(建议) | 🟢 合规 |
|
||||||
|
| classes/actions.ts | 676 | 800(建议) | 🟢 合规 |
|
||||||
| classes/types.ts | 201 | 无限制 | 🟢 合规 |
|
| classes/types.ts | 201 | 无限制 | 🟢 合规 |
|
||||||
| school/actions.ts | 325 | 800(建议) | 🟢 合规 |
|
| school/actions.ts | 325 | 800(建议) | 🟢 合规 |
|
||||||
| school/data-access.ts | 186 | 800(建议) | 🟢 合规 |
|
| school/data-access.ts | 186 | 800(建议) | 🟢 合规 |
|
||||||
| scheduling/auto-scheduler.ts | 310 | 无限制 | 🟢 合规 |
|
| scheduling/auto-scheduler.ts | 310 | 无限制 | 🟢 合规 |
|
||||||
| scheduling/actions.ts | 302 | 800(建议) | 🟢 合规 |
|
| scheduling/actions.ts | 266 | 800(建议) | 🟢 合规 |
|
||||||
| scheduling/data-access.ts | 272 | 800(建议) | 🟢 合规 |
|
| scheduling/data-access.ts | 335 | 800(建议) | 🟢 合规 |
|
||||||
| attendance/actions.ts | 271 | 800(建议) | 🟢 合规 |
|
| attendance/actions.ts | 271 | 800(建议) | 🟢 合规 |
|
||||||
| attendance/data-access.ts | 271 | 800(建议) | 🟢 合规 |
|
| attendance/data-access.ts | 271 | 800(建议) | 🟢 合规 |
|
||||||
| attendance/data-access-stats.ts | 145 | 800(建议) | 🟢 合规 |
|
| attendance/data-access-stats.ts | 145 | 800(建议) | 🟢 合规 |
|
||||||
| users/import-export.ts | 291 | 800(建议) | 🟢 合规(但职责混合) |
|
| users/import-export.ts | 157 | 800(建议) | 🟢 合规(已拆分) |
|
||||||
| users/actions.ts | 151 | 800(建议) | 🟢 合规 |
|
| users/user-service.ts | 82 | 800(建议) | 🟢 合规(新增) |
|
||||||
| users/data-access.ts | 71 | 800(建议) | 🟢 合规 |
|
| users/class-registration.ts | 21 | 800(建议) | 🟢 合规(新增) |
|
||||||
|
| users/actions.ts | 131 | 800(建议) | 🟢 合规 |
|
||||||
|
| users/data-access.ts | 133 | 800(建议) | 🟢 合规 |
|
||||||
| audit/actions.ts | 212 | 800(建议) | 🟢 合规 |
|
| audit/actions.ts | 212 | 800(建议) | 🟢 合规 |
|
||||||
| audit/data-access.ts | 260 | 800(建议) | 🟢 合规 |
|
| audit/data-access.ts | 260 | 800(建议) | 🟢 合规 |
|
||||||
| course-plans/actions.ts | 265 | 800(建议) | 🟢 合规 |
|
| course-plans/actions.ts | 265 | 800(建议) | 🟢 合规 |
|
||||||
| course-plans/data-access.ts | 320 | 800(建议) | 🟢 合规 |
|
| course-plans/data-access.ts | 320 | 800(建议) | 🟢 合规 |
|
||||||
| announcements/actions.ts | 242 | 800(建议) | 🟢 合规 |
|
| announcements/actions.ts | 197 | 800(建议) | 🟢 合规 |
|
||||||
| announcements/data-access.ts | 120 | 800(建议) | 🟢 合规 |
|
| announcements/data-access.ts | 171 | 800(建议) | 🟢 合规 |
|
||||||
|
|
||||||
> 仅 `classes/data-access.ts` 突破硬性上限,需立即拆分。
|
> ✅ 所有文件均已在 1000 行硬性上限内。原 `classes/data-access.ts`(2104 行)已拆分为 5 个文件。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -17,9 +17,9 @@
|
|||||||
|
|
||||||
### 严重问题清单(必须修复)
|
### 严重问题清单(必须修复)
|
||||||
|
|
||||||
1. **messaging 与 notifications 边界模糊、双向依赖** — 两个模块都写入 `messageNotifications` 表,notifications 反向依赖 messaging,类型系统不一致
|
1. ~~**messaging 与 notifications 边界模糊、双向依赖** — 两个模块都写入 `messageNotifications` 表,notifications 反向依赖 messaging,类型系统不一致~~ ✅ 已修复(P0-5 + P1-6)
|
||||||
2. **dashboard/data-access.ts 直查 11 张跨模块表** — 违反模块封装原则
|
2. ~~**dashboard/data-access.ts 直查 11 张跨模块表** — 违反模块封装原则~~ ✅ 已修复(P0-4)
|
||||||
3. **proctoring/exam-mode-config.tsx 未集成到考试表单** — DB schema 有 examMode 等字段但无 UI 录入入口,组件成为死代码
|
3. **proctoring/exam-mode-config.tsx 未集成到考试表单** — DB schema 有 examMode 等字段但无 UI 录入入口,组件成为死代码 ⚠️ 用户决定保留
|
||||||
4. **proctoring 事件上报存在 Server Action 与 REST API 双通道重复** — 同一逻辑两份代码
|
4. **proctoring 事件上报存在 Server Action 与 REST API 双通道重复** — 同一逻辑两份代码
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
**结论:⚠️ 职责基本清晰,但存在 3 个严重问题**
|
**结论:⚠️ 职责基本清晰,但存在 3 个严重问题**
|
||||||
|
|
||||||
#### 严重问题 1:`exam-mode-config.tsx` 未集成到考试表单(死代码)
|
#### 严重问题 1:`exam-mode-config.tsx` 未集成到考试表单(死代码)⚠️ 用户决定保留
|
||||||
|
|
||||||
- DB schema 已有 `examMode` / `durationMinutes` / `shuffleQuestions` / `allowLateStart` / `antiCheatEnabled` 字段(schema.ts 第 457-462 行)
|
- DB schema 已有 `examMode` / `durationMinutes` / `shuffleQuestions` / `allowLateStart` / `antiCheatEnabled` 字段(schema.ts 第 457-462 行)
|
||||||
- `proctoring/components/exam-mode-config.tsx` 提供了完整的配置 UI 组件
|
- `proctoring/components/exam-mode-config.tsx` 提供了完整的配置 UI 组件
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
- `exams/components/exam-mode-selector.tsx` 是"手动组卷 vs AI 生成"的选择器,**与考试模式(homework/timed/proctored)无关**,命名易混淆
|
- `exams/components/exam-mode-selector.tsx` 是"手动组卷 vs AI 生成"的选择器,**与考试模式(homework/timed/proctored)无关**,命名易混淆
|
||||||
- 结果:创建考试时无法设置监考模式,proctoring 模块的 `getExamForProctoring` 读取的 `examMode` 永远是默认值 `"homework"`
|
- 结果:创建考试时无法设置监考模式,proctoring 模块的 `getExamForProctoring` 读取的 `examMode` 永远是默认值 `"homework"`
|
||||||
|
|
||||||
**建议**:将 `exam-mode-config.tsx` 集成到 `exam-form.tsx`,或迁移到 exams 模块
|
**状态**:用户决定保留该组件,暂不集成也不删除。后续如需启用监考功能,可再集成到 `exam-form.tsx`。
|
||||||
|
|
||||||
#### 严重问题 2:事件上报存在 Server Action 与 REST API 双通道重复
|
#### 严重问题 2:事件上报存在 Server Action 与 REST API 双通道重复
|
||||||
|
|
||||||
@@ -187,9 +187,14 @@
|
|||||||
| `index.ts` | 38 | 对外导出入口 |
|
| `index.ts` | 38 | 对外导出入口 |
|
||||||
| `channels/` | 5 个 | SMS/Email/WeChat/InApp 渠道实现 |
|
| `channels/` | 5 个 | SMS/Email/WeChat/InApp 渠道实现 |
|
||||||
|
|
||||||
#### 严重问题 1:与 messaging 模块双向依赖、边界模糊
|
#### 严重问题 1:与 messaging 模块双向依赖、边界模糊 ✅ 已修复
|
||||||
|
|
||||||
详见下文"三、messaging vs notifications 边界分析"。
|
~~详见下文"三、messaging vs notifications 边界分析"。~~
|
||||||
|
|
||||||
|
**已完成修复**(2026-06-17,P0-5 + P1-6):
|
||||||
|
- messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`(P0-5)
|
||||||
|
- notifications/channels/in-app-channel.ts 将静态 import 改为动态 `await import("@/modules/messaging/data-access")`,打破模块级静态反向依赖(P1-6)
|
||||||
|
- 依赖方向已统一:messaging → notifications(单向)
|
||||||
|
|
||||||
#### 中等问题 2:`sendClassNotificationAction` 跨模块直查
|
#### 中等问题 2:`sendClassNotificationAction` 跨模块直查
|
||||||
|
|
||||||
@@ -223,9 +228,11 @@
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 三、messaging vs notifications 边界分析(重点)
|
## 三、messaging vs notifications 边界分析(重点)✅ 已修复
|
||||||
|
|
||||||
### 3.1 现状对比
|
> **状态**:双向依赖与绕过 dispatcher 问题已于 2026-06-17 修复(P0-5 + P1-6)。以下为修复前的现状记录,保留作为历史参考。
|
||||||
|
|
||||||
|
### 3.1 现状对比(修复前)
|
||||||
|
|
||||||
| 维度 | messaging 模块 | notifications 模块 |
|
| 维度 | messaging 模块 | notifications 模块 |
|
||||||
|------|---------------|-------------------|
|
|------|---------------|-------------------|
|
||||||
@@ -237,55 +244,48 @@
|
|||||||
| **偏好管理** | ✅ `notification-preferences.ts` | ❌ 借用 messaging 的 |
|
| **偏好管理** | ✅ `notification-preferences.ts` | ❌ 借用 messaging 的 |
|
||||||
| **渠道支持** | 仅站内 | 站内 + SMS + Email + WeChat |
|
| **渠道支持** | 仅站内 | 站内 + SMS + Email + WeChat |
|
||||||
|
|
||||||
### 3.2 严重问题:双向依赖与职责重叠
|
### 3.2 严重问题:双向依赖与职责重叠 ✅ 已修复
|
||||||
|
|
||||||
#### 问题 1:notifications 反向依赖 messaging
|
#### 问题 1:notifications 反向依赖 messaging ✅ 已修复
|
||||||
|
|
||||||
```
|
~~notifications/data-access.ts~~
|
||||||
notifications/data-access.ts
|
~~ → import { getNotificationPreferences } from "@/modules/messaging/notification-preferences"~~
|
||||||
→ import { getNotificationPreferences } from "@/modules/messaging/notification-preferences"
|
~~ → import type { NotificationPreferences } from "@/modules/messaging/types"~~
|
||||||
→ import type { NotificationPreferences } from "@/modules/messaging/types"
|
|
||||||
|
|
||||||
notifications/channels/in-app-channel.ts
|
~~notifications/channels/in-app-channel.ts~~
|
||||||
→ import { createNotification } from "@/modules/messaging/data-access"
|
~~ → import { createNotification } from "@/modules/messaging/data-access"~~
|
||||||
```
|
|
||||||
|
|
||||||
notifications 模块不拥有任何数据,全部依赖 messaging 模块。这违背了"模块应拥有自己的数据"原则。
|
**修复方案**:notifications/channels/in-app-channel.ts 将静态 import 改为动态 `await import("@/modules/messaging/data-access")`,打破模块级静态反向依赖。运行时调用链保持不变,但模块加载图无环。
|
||||||
|
|
||||||
#### 问题 2:messaging 绕过 notifications 直接写通知
|
#### 问题 2:messaging 绕过 notifications 直接写通知 ✅ 已修复
|
||||||
|
|
||||||
`messaging/actions.ts` 第 66-72 行:
|
~~`messaging/actions.ts` 第 66-72 行:~~
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
// Notify the receiver about the new message
|
// ~~Notify the receiver about the new message~~
|
||||||
await createNotification({
|
// ~~await createNotification({~~
|
||||||
userId: input.receiverId,
|
// ~~ userId: input.receiverId,~~
|
||||||
type: "message",
|
// ~~ type: "message",~~
|
||||||
title: input.subject ? `New message: ${input.subject}` : "New message",
|
// ~~ ...~~
|
||||||
content: input.content.slice(0, 200),
|
// ~~})~~
|
||||||
link: `/messages/${id}`,
|
|
||||||
})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
这直接调用 `messaging/data-access.ts` 的 `createNotification`,**完全绕过 notifications 模块的 dispatcher**,导致:
|
**修复方案**:messaging/actions.ts 改用 `sendNotification` from `@/modules/notifications/dispatcher`,通知现在会经过 dispatcher 的渠道选择逻辑,尊重用户偏好(SMS/Email/WeChat/In-App)。
|
||||||
- 用户设置的 SMS/Email 偏好被忽略
|
|
||||||
- 新消息不会触发邮件/短信提醒
|
|
||||||
- notifications 模块的多渠道能力形同虚设
|
|
||||||
|
|
||||||
#### 问题 3:类型系统不一致
|
#### 问题 3:类型系统不一致(保留)
|
||||||
|
|
||||||
- `messaging/types.ts` 第 23 行:`NotificationType = "message" | "announcement" | "homework" | "grade"`(按业务类别)
|
- `messaging/types.ts` 第 23 行:`NotificationType = "message" | "announcement" | "homework" | "grade"`(按业务类别)
|
||||||
- `notifications/types.ts` 第 20 行:`type: "info" | "warning" | "error" | "success"`(按严重级别)
|
- `notifications/types.ts` 第 20 行:`type: "info" | "warning" | "error" | "success"`(按严重级别)
|
||||||
- `in-app-channel.ts` 第 49 行:`type: payload.type as "message" | "announcement" | "homework" | "grade"` — **强制类型转换,运行时可能写入非法值**
|
- `in-app-channel.ts` 第 49 行:`type: payload.type as "message" | "announcement" | "homework" | "grade"` — **强制类型转换,运行时可能写入非法值**
|
||||||
|
|
||||||
DB schema 中 `messageNotifications.type` 为 `varchar(128)`,虽然不会报错,但语义混乱。
|
DB schema 中 `messageNotifications.type` 为 `varchar(128)`,虽然不会报错,但语义混乱。(P2 待统一)
|
||||||
|
|
||||||
#### 问题 4:notification-preferences 归属不清
|
#### 问题 4:notification-preferences 归属不清(保留)
|
||||||
|
|
||||||
- `notificationPreferences` 表的 data-access 在 messaging 模块
|
- `notificationPreferences` 表的 data-access 在 messaging 模块
|
||||||
- 但 notifications 模块的 dispatcher 依赖此偏好决定渠道
|
- 但 notifications 模块的 dispatcher 依赖此偏好决定渠道
|
||||||
- settings 模块的 `notification-preferences-form.tsx` 调用 `messaging/actions.ts` 的 `updateNotificationPreferencesAction`
|
- settings 模块的 `notification-preferences-form.tsx` 调用 `messaging/actions.ts` 的 `updateNotificationPreferencesAction`
|
||||||
- 三个模块都在操作同一份数据,职责归属不清
|
- 三个模块都在操作同一份数据,职责归属不清(P2 待重构)
|
||||||
|
|
||||||
### 3.3 建议方案
|
### 3.3 建议方案
|
||||||
|
|
||||||
@@ -307,28 +307,24 @@ DB schema 中 `messageNotifications.type` 为 `varchar(128)`,虽然不会报
|
|||||||
|
|
||||||
## 四、dashboard 模块审查(重点)
|
## 四、dashboard 模块审查(重点)
|
||||||
|
|
||||||
### 4.1 严重问题:`data-access.ts` 直查 11 张跨模块表
|
### 4.1 严重问题:`data-access.ts` 直查 11 张跨模块表 ✅ 已修复
|
||||||
|
|
||||||
`dashboard/data-access.ts` 的 `getAdminDashboardData` 函数直接查询以下表:
|
~~`dashboard/data-access.ts` 的 `getAdminDashboardData` 函数直接查询以下表~~
|
||||||
|
|
||||||
| 表名 | 所属模块 | 查询内容 |
|
**已完成修复**(2026-06-17,P0-4):dashboard/data-access.ts 从大文件降至 42 行,改为并行调用 6 个模块的 stats 函数:
|
||||||
|------|---------|---------|
|
|
||||||
| `sessions` | auth | 活跃会话数 |
|
|
||||||
| `users` | users | 用户总数 + 最近注册用户 |
|
|
||||||
| `usersToRoles` | users | 用户角色统计 |
|
|
||||||
| `roles` | users | 角色名 |
|
|
||||||
| `classes` | classes | 班级总数 |
|
|
||||||
| `textbooks` | textbooks | 教材总数 |
|
|
||||||
| `chapters` | textbooks | 章节总数 |
|
|
||||||
| `questions` | questions | 题目总数 |
|
|
||||||
| `exams` | exams | 考试总数(含 scope 过滤) |
|
|
||||||
| `homeworkAssignments` | homework | 作业总数 + 已发布数 |
|
|
||||||
| `homeworkSubmissions` | homework | 提交数 + 待批改数 |
|
|
||||||
|
|
||||||
**问题分析**:
|
```typescript
|
||||||
- 违反模块封装原则:dashboard 直接依赖其他模块的 DB schema
|
const [usersStats, classesStats, textbooksStats, questionsStats, examsStats, homeworkStats] = await Promise.all([
|
||||||
- 任何模块的表结构变更都需要同步修改 dashboard
|
getUsersDashboardStats(),
|
||||||
- 无法复用其他模块的 scope 过滤逻辑(dashboard 自己实现了 exam/homework 的 scope 过滤,第 31-73 行)
|
getClassesDashboardStats(),
|
||||||
|
getTextbooksDashboardStats(),
|
||||||
|
getQuestionsDashboardStats(),
|
||||||
|
getExamsDashboardStats(scope),
|
||||||
|
getHomeworkDashboardStats(scope),
|
||||||
|
])
|
||||||
|
```
|
||||||
|
|
||||||
|
不再直接查询任何业务表,完全通过各模块 data-access 暴露的聚合查询函数获取数据。
|
||||||
|
|
||||||
### 4.2 学生/教师仪表盘的对比
|
### 4.2 学生/教师仪表盘的对比
|
||||||
|
|
||||||
@@ -540,23 +536,23 @@ settings ──调用──> messaging (通知偏好 Action)
|
|||||||
|
|
||||||
### P0(严重,应立即修复)
|
### P0(严重,应立即修复)
|
||||||
|
|
||||||
| 序号 | 问题 | 模块 | 工作量 | 影响 |
|
| 序号 | 问题 | 模块 | 工作量 | 影响 | 状态 |
|
||||||
|------|------|------|--------|------|
|
|------|------|------|--------|------|------|
|
||||||
| 1 | messaging 绕过 notifications 直接写通知 | messaging | 小 | 用户通知偏好失效,多渠道通知无效 |
|
| ~~1~~ | ~~messaging 绕过 notifications 直接写通知~~ | ~~messaging~~ | ~~小~~ | ~~用户通知偏好失效,多渠道通知无效~~ | ✅ 已修复(P0-5) |
|
||||||
| 2 | proctoring/exam-mode-config.tsx 未集成 | proctoring | 小 | 监考功能无法启用,组件为死代码 |
|
| 2 | proctoring/exam-mode-config.tsx 未集成 | proctoring | 小 | 监考功能无法启用,组件为死代码 | ⚠️ 用户决定保留 |
|
||||||
| 3 | proctoring 事件上报双通道重复 | proctoring | 小 | 代码重复,维护成本 |
|
| 3 | proctoring 事件上报双通道重复 | proctoring | 小 | 代码重复,维护成本 | ❌ 待修复 |
|
||||||
| 4 | notifications 反向依赖 messaging | notifications | 中 | 架构耦合,难以独立演进 |
|
| ~~4~~ | ~~notifications 反向依赖 messaging~~ | ~~notifications~~ | ~~中~~ | ~~架构耦合,难以独立演进~~ | ✅ 已修复(P1-6) |
|
||||||
|
|
||||||
### P1(中等问题,下个迭代修复)
|
### P1(中等问题,下个迭代修复)
|
||||||
|
|
||||||
| 序号 | 问题 | 模块 | 工作量 |
|
| 序号 | 问题 | 模块 | 工作量 | 状态 |
|
||||||
|------|------|------|--------|
|
|------|------|------|--------|------|
|
||||||
| 5 | dashboard 直查 11 张跨模块表 | dashboard | 大 |
|
| ~~5~~ | ~~dashboard 直查 11 张跨模块表~~ | ~~dashboard~~ | ~~大~~ | ✅ 已修复(P0-4) |
|
||||||
| 6 | diagnostic 跨模块直查 4 张表 | diagnostic | 中 |
|
| 6 | diagnostic 跨模块直查 4 张表 | diagnostic | 中 | ❌ 待修复 |
|
||||||
| 7 | notifications 无 notification_logs 表 | notifications | 中 |
|
| 7 | notifications 无 notification_logs 表 | notifications | 中 | ❌ 待修复 |
|
||||||
| 8 | sendClassNotificationAction 直查 classes 表 | notifications | 小 |
|
| 8 | sendClassNotificationAction 直查 classes 表 | notifications | 小 | ❌ 待修复 |
|
||||||
| 9 | elective 两个 data-access 文件代码重复 | elective | 小 |
|
| 9 | elective 两个 data-access 文件代码重复 | elective | 小 | ❌ 待修复 |
|
||||||
| 10 | settings/notification-preferences-form 跨模块依赖 | settings | 小 |
|
| 10 | settings/notification-preferences-form 跨模块依赖 | settings | 小 | ❌ 待修复 |
|
||||||
|
|
||||||
### P2(轻微问题,机会修复)
|
### P2(轻微问题,机会修复)
|
||||||
|
|
||||||
@@ -576,9 +572,9 @@ settings ──调用──> messaging (通知偏好 Action)
|
|||||||
### 新增模块质量
|
### 新增模块质量
|
||||||
|
|
||||||
- **elective**:✅ 质量较好,拆分合理但有代码重复
|
- **elective**:✅ 质量较好,拆分合理但有代码重复
|
||||||
- **proctoring**:⚠️ 有死代码(exam-mode-config 未集成)和重复实现(双通道上报)
|
- **proctoring**:⚠️ 有死代码(exam-mode-config 未集成,用户决定保留)和重复实现(双通道上报)
|
||||||
- **diagnostic**:✅ 与 grades 无重叠,但跨模块耦合较重
|
- **diagnostic**:✅ 与 grades 无重叠,但跨模块耦合较重
|
||||||
- **notifications**:⚠️ 渠道抽象优秀,但与 messaging 边界模糊、反向依赖
|
- **notifications**:✅ 渠道抽象优秀,与 messaging 的双向依赖已修复(P0-5 + P1-6)
|
||||||
|
|
||||||
### 重点问题回答
|
### 重点问题回答
|
||||||
|
|
||||||
@@ -586,16 +582,16 @@ settings ──调用──> messaging (通知偏好 Action)
|
|||||||
合理,但需消除 `data-access.ts` 与 `data-access-selections.ts` 之间的代码重复
|
合理,但需消除 `data-access.ts` 与 `data-access-selections.ts` 之间的代码重复
|
||||||
|
|
||||||
2. **proctoring 模块职责是否清晰?**
|
2. **proctoring 模块职责是否清晰?**
|
||||||
基本清晰,但 `exam-mode-config.tsx` 应属于 exams 模块或集成到考试表单
|
基本清晰,但 `exam-mode-config.tsx` 应属于 exams 模块或集成到考试表单(用户决定保留)
|
||||||
|
|
||||||
3. **diagnostic 与 grades 是否有职责重叠?**
|
3. **diagnostic 与 grades 是否有职责重叠?**
|
||||||
无重叠。grades 管分数记录,diagnostic 管知识点掌握度,数据来源和维度均不同
|
无重叠。grades 管分数记录,diagnostic 管知识点掌握度,数据来源和维度均不同
|
||||||
|
|
||||||
4. **notifications 与 messaging 边界是否清晰?**
|
4. **notifications 与 messaging 边界是否清晰?**
|
||||||
不清晰。存在双向依赖、类型不一致、职责重叠三个问题,建议按方案 A 合并
|
✅ 已修复。双向依赖通过动态 import 打破,messaging 改用 notifications/dispatcher 发送通知,依赖方向统一为 messaging → notifications
|
||||||
|
|
||||||
5. **dashboard 是否直查其他模块的表?**
|
5. **dashboard 是否直查其他模块的表?**
|
||||||
是。`getAdminDashboardData` 直查 11 张跨模块表,是本次审查最严重的封装违规
|
✅ 已修复。`getAdminDashboardData` 改为并行调用各模块的 `get[Module]DashboardStats()` 函数,不再直接查询任何业务表
|
||||||
|
|
||||||
6. **settings 是否混入太多职责?**
|
6. **settings 是否混入太多职责?**
|
||||||
混合了 5 类职责,但作为"设置"聚合点尚可接受。AI Provider 管理可考虑独立
|
混合了 5 类职责,但作为"设置"聚合点尚可接受。AI Provider 管理可考虑独立
|
||||||
|
|||||||
@@ -8,7 +8,8 @@
|
|||||||
|
|
||||||
- 文件总数:69(不含测试文件)
|
- 文件总数:69(不含测试文件)
|
||||||
- `db/`:3
|
- `db/`:3
|
||||||
- `lib/`:13
|
- `lib/`:17(新增 4 个 auth 拆分文件)
|
||||||
|
- `lib/ai/`:6(新增目录,P2-2 拆分)
|
||||||
- `hooks/`:7
|
- `hooks/`:7
|
||||||
- `components/ui/`:34
|
- `components/ui/`:34
|
||||||
- `components/a11y/`:4
|
- `components/a11y/`:4
|
||||||
@@ -17,6 +18,8 @@
|
|||||||
- `src/auth.ts`、`src/proxy.ts`:2
|
- `src/auth.ts`、`src/proxy.ts`:2
|
||||||
- 发现问题数:15
|
- 发现问题数:15
|
||||||
- 严重程度分布:高 3 / 中 9 / 低 3
|
- 严重程度分布:高 3 / 中 9 / 低 3
|
||||||
|
- 已修复问题:4(auth.ts 拆分、ai.ts 拆分、循环依赖、反向依赖)
|
||||||
|
- 待修复问题:11(含 schema.ts 拆分 P2-1、onboarding-gate、global-search、proxy.ts 等)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -28,30 +31,40 @@
|
|||||||
- **严重程度**:高
|
- **严重程度**:高
|
||||||
- **建议**:按业务域拆分为多个 schema 文件(如 `schema/auth.ts`、`schema/academic.ts`、`schema/exam.ts`、`schema/audit.ts` 等),通过 `schema/index.ts` 聚合导出。同时修正分节编号。
|
- **建议**:按业务域拆分为多个 schema 文件(如 `schema/auth.ts`、`schema/academic.ts`、`schema/exam.ts`、`schema/audit.ts` 等),通过 `schema/index.ts` 聚合导出。同时修正分节编号。
|
||||||
|
|
||||||
### 2. `src/auth.ts`
|
### 2. `src/auth.ts` ✅ 已修复
|
||||||
|
|
||||||
- **问题**:293 行,混合了多种职责:
|
- **问题**:~~293 行,混合了多种职责~~
|
||||||
1. NextAuth 配置(providers、callbacks、events)
|
1. NextAuth 配置(providers、callbacks、events)
|
||||||
2. 密码安全 DB 操作(`getOrCreatePasswordSecurity`、`recordFailedLogin`、`resetFailedLogin`,行 56-130)——这些是数据访问层逻辑,不应内联在认证配置中
|
2. 密码安全 DB 操作(`getOrCreatePasswordSecurity`、`recordFailedLogin`、`resetFailedLogin`,行 56-130)
|
||||||
3. 角色规范化工具(`normalizeRole`、`resolvePrimaryRole`,行 13-27)
|
3. 角色规范化工具(`normalizeRole`、`resolvePrimaryRole`,行 13-27)
|
||||||
4. bcrypt 哈希规范化(`normalizeBcryptHash`,行 29-33)
|
4. bcrypt 哈希规范化(`normalizeBcryptHash`,行 29-33)
|
||||||
5. IP 解析(`resolveClientIp`,行 39-51)
|
5. IP 解析(`resolveClientIp`,行 39-51)
|
||||||
6. `authorize` 回调内联了限流、锁定检查、密码比对、日志记录全流程(86 行)
|
6. `authorize` 回调内联了限流、锁定检查、密码比对、日志记录全流程(86 行)
|
||||||
- **严重程度**:高
|
- **严重程度**:高
|
||||||
- **建议**:将密码安全 DB 操作迁移到 `shared/lib/password-security-service.ts`(与纯函数的 `password-policy.ts` 区分);角色规范化迁移到 `shared/lib/permissions.ts` 或新建 `shared/lib/role-utils.ts`;`resolveClientIp` 迁移到 `shared/lib/http-utils.ts`(与三个 logger 共用)。
|
- **修复状态**:2026-06-17 已完成拆分(P1-3)。auth.ts 从 293 行降至 193 行,4 类职责分别迁移到 shared/lib 下的独立文件:
|
||||||
|
- `password-security-service.ts`(84 行)- 密码安全 DB 操作
|
||||||
|
- `role-utils.ts`(31 行)- 角色规范化
|
||||||
|
- `bcrypt-utils.ts`(18 行)- bcrypt 哈希规范化
|
||||||
|
- `http-utils.ts`(27 行)- IP 解析(与三个 logger 共用)
|
||||||
|
|
||||||
### 3. `src/shared/lib/ai.ts`
|
### 3. `src/shared/lib/ai.ts` ✅ 已修复
|
||||||
|
|
||||||
- **问题**:218 行,混合了 5 类职责:
|
- **问题**:~~218 行,混合了 5 类职责~~
|
||||||
1. 请求负载解析与校验(`parseAiChatPayload`,行 70-96)
|
1. 请求负载解析与校验(`parseAiChatPayload`,行 70-96)
|
||||||
2. API Key 加密/解密(`encryptAiApiKey`/`decryptAiApiKey`,行 104-124)
|
2. API Key 加密/解密(`encryptAiApiKey`/`decryptAiApiKey`,行 104-124)
|
||||||
3. Provider 配置 DB 查询(`getAiProviderConfig`,行 126-179)
|
3. Provider 配置 DB 查询(`getAiProviderConfig`,行 126-179)
|
||||||
4. AI 客户端创建与调用(`getAiClient`、`createAiChatCompletion`、`testAiProviderConfig`、`testAiProviderById`)
|
4. AI 客户端创建与调用(`getAiClient`、`createAiChatCompletion`、`testAiProviderConfig`、`testAiProviderById`)
|
||||||
5. 错误格式化(`getAiErrorMessage`)
|
5. 错误格式化(`getAiErrorMessage`)
|
||||||
|
|
||||||
其中加密/解密与 Provider 配置查询属于数据层,与 AI 调用本身是不同关注点。
|
|
||||||
- **严重程度**:中
|
- **严重程度**:中
|
||||||
- **建议**:将加密/解密拆到 `shared/lib/crypto.ts`(通用加密工具);将 Provider 配置查询拆到 `shared/lib/ai-provider-repo.ts`(数据访问)。`ai.ts` 保留负载解析与 AI 调用编排。
|
- **修复状态**:2026-06-17 已完成拆分(P2-2,commit 6588f74)。原 `ai.ts`(218 行)拆分为 `src/shared/lib/ai/` 目录 6 个文件:
|
||||||
|
- `payload-parser.ts`(96 行)- 请求负载解析
|
||||||
|
- `api-key-crypto.ts`(34 行)- API Key 加密/解密
|
||||||
|
- `provider-config.ts`(66 行)- Provider 配置查询
|
||||||
|
- `client.ts`(67 行)- AI 客户端创建与调用
|
||||||
|
- `errors.ts`(9 行)- 错误格式化
|
||||||
|
- `index.ts`(7 行)- 聚合导出
|
||||||
|
|
||||||
|
原 `ai.ts` 保留为向后兼容的重导出文件(9 行),调用方无需修改 import 路径。
|
||||||
|
|
||||||
### 4. `src/shared/components/onboarding-gate.tsx`
|
### 4. `src/shared/components/onboarding-gate.tsx`
|
||||||
|
|
||||||
@@ -128,27 +141,27 @@
|
|||||||
|
|
||||||
## 模块间依赖问题
|
## 模块间依赖问题
|
||||||
|
|
||||||
### 1. shared 层与 `@/auth` 的循环依赖
|
### 1. shared 层与 `@/auth` 的循环依赖 ✅ 已修复
|
||||||
|
|
||||||
- **涉及模块**:`shared/lib/{audit-logger, change-logger, auth-guard}` → `@/auth` → `shared/lib/{login-logger, permissions, password-policy, rate-limit}` + `shared/db`
|
- **涉及模块**:`shared/lib/{audit-logger, change-logger, auth-guard}` → `@/auth` → `shared/lib/{login-logger, permissions, password-policy, rate-limit}` + `shared/db`
|
||||||
- **问题类型**:循环依赖
|
- **问题类型**:循环依赖
|
||||||
- **问题详情**:
|
- **问题详情**:
|
||||||
- `shared/lib/audit-logger.ts`(行 7)`import { auth } from "@/auth"`
|
- `shared/lib/audit-logger.ts`(原行 7)`import { auth } from "@/auth"`
|
||||||
- `shared/lib/change-logger.ts`(行 6)`import { auth } from "@/auth"`
|
- `shared/lib/change-logger.ts`(原行 6)`import { auth } from "@/auth"`
|
||||||
- `shared/lib/auth-guard.ts`(行 1)`import { auth } from "@/auth"`
|
- `shared/lib/auth-guard.ts`(原行 1)`import { auth } from "@/auth"`
|
||||||
- 而 `src/auth.ts` 反向依赖 `shared/lib/permissions`、`shared/lib/login-logger`、`shared/lib/password-policy`、`shared/lib/rate-limit`、`shared/db`
|
- 而 `src/auth.ts` 反向依赖 `shared/lib/permissions`、`shared/lib/login-logger`、`shared/lib/password-policy`、`shared/lib/rate-limit`、`shared/db`
|
||||||
|
|
||||||
这构成了 `shared/lib/*` → `auth` → `shared/lib/*` 的循环。虽然 NextAuth 的 `auth()` 函数是运行时调用而非模块级副作用,目前不会导致运行时错误,但架构上 shared 基础设施层不应反向依赖业务层的认证入口。
|
这构成了 `shared/lib/*` → `auth` → `shared/lib/*` 的循环。
|
||||||
- **建议**:将 `auth()` 的 session 获取抽象为接口或通过参数注入。logger 函数改为接收 `session` 参数(由调用方传入),而非内部调用 `auth()`。或创建 `shared/lib/session.ts` 封装 session 获取,`auth.ts` 和 logger 都依赖它,打破循环。
|
- **修复状态**:2026-06-17 已完成(P0-3)。3 个文件(audit-logger.ts、change-logger.ts、auth-guard.ts)将静态 `import { auth } from "@/auth"` 改为动态 `const { auth } = await import("@/auth")`,打破模块级静态循环依赖。运行时调用链保持不变,但模块加载图无环。
|
||||||
|
|
||||||
### 2. shared 层对根模块 `@/auth` 的反向依赖
|
### 2. shared 层对根模块 `@/auth` 的反向依赖 ✅ 已修复
|
||||||
|
|
||||||
- **涉及模块**:`shared/lib/*` → `@/auth`(根模块)
|
- **涉及模块**:`shared/lib/*` → `@/auth`(根模块)
|
||||||
- **问题类型**:反向依赖
|
- **问题类型**:反向依赖
|
||||||
- **问题详情**:`src/auth.ts` 位于项目根目录,属于应用层(非 shared 层)。shared 层应是被依赖方,不应依赖应用层模块。三个文件(audit-logger、change-logger、auth-guard)直接 import `@/auth`,使 shared 层无法独立测试或复用。
|
- **问题详情**:`src/auth.ts` 位于项目根目录,属于应用层(非 shared 层)。shared 层应是被依赖方,不应依赖应用层模块。三个文件(audit-logger、change-logger、auth-guard)直接 import `@/auth`,使 shared 层无法独立测试或复用。
|
||||||
- **建议**:同上,通过依赖注入或提取 `shared/lib/session.ts` 解耦。
|
- **修复状态**:2026-06-17 已完成(P0-3)。通过动态 import 打破静态反向依赖。shared 层不再有对 `@/auth` 的静态 import,仅保留运行时动态调用(用于获取 session)。
|
||||||
|
|
||||||
### 3. 三个 logger 重复实现 IP/Header 提取
|
### 3. 三个 logger 重复实现 IP/Header 提取 ✅ 部分修复
|
||||||
|
|
||||||
- **涉及模块**:`shared/lib/audit-logger`、`shared/lib/change-logger`、`shared/lib/login-logger`、`src/auth.ts`
|
- **涉及模块**:`shared/lib/audit-logger`、`shared/lib/change-logger`、`shared/lib/login-logger`、`src/auth.ts`
|
||||||
- **问题类型**:过度耦合(DRY 违反)
|
- **问题类型**:过度耦合(DRY 违反)
|
||||||
@@ -156,10 +169,10 @@
|
|||||||
- `audit-logger.ts`(行 27-32):`headerList.get("x-forwarded-for") ?? headerList.get("x-real-ip") ?? "unknown"`
|
- `audit-logger.ts`(行 27-32):`headerList.get("x-forwarded-for") ?? headerList.get("x-real-ip") ?? "unknown"`
|
||||||
- `change-logger.ts`(行 27-31):相同逻辑
|
- `change-logger.ts`(行 27-31):相同逻辑
|
||||||
- `login-logger.ts`(行 26-31):相同逻辑
|
- `login-logger.ts`(行 26-31):相同逻辑
|
||||||
- `auth.ts`(行 39-51):`resolveClientIp` 也是类似逻辑(取 `x-forwarded-for` 第一段)
|
- `auth.ts`(原行 39-51):`resolveClientIp` 也是类似逻辑(取 `x-forwarded-for` 第一段)
|
||||||
|
|
||||||
四处实现略有差异(auth.ts 取逗号分隔第一段,其他取全值),存在不一致风险。
|
四处实现略有差异(auth.ts 取逗号分隔第一段,其他取全值),存在不一致风险。
|
||||||
- **建议**:提取 `shared/lib/http-utils.ts`,导出 `getClientIp()` 和 `getUserAgent()` 统一复用。
|
- **修复状态**:2026-06-17 部分修复(P1-3)。`src/auth.ts` 的 `resolveClientIp` 已迁移到 `shared/lib/http-utils.ts`(27 行),auth.ts 改为从该文件导入。但三个 logger 文件内部的 IP/Header 提取逻辑尚未统一到 `http-utils.ts`(P2 待处理)。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -183,14 +196,20 @@
|
|||||||
|
|
||||||
| 目录 | 文件数 | 最大文件(行数) | 备注 |
|
| 目录 | 文件数 | 最大文件(行数) | 备注 |
|
||||||
|------|--------|------------------|------|
|
|------|--------|------------------|------|
|
||||||
| `src/shared/db/` | 3 | schema.ts (1111) | **超过 1000 行硬性上限** |
|
| `src/shared/db/` | 3 | schema.ts (1111) | **超过 1000 行硬性上限**(P2-1 待拆分) |
|
||||||
| `src/shared/lib/` | 13 | ai.ts (218) | |
|
| `src/shared/lib/` | 17 | password-security-service.ts (84) | ai.ts 已拆分为 ai/ 目录(P2-2);新增 4 个 auth 拆分文件(P1-3) |
|
||||||
|
| `src/shared/lib/ai/` | 6 | payload-parser.ts (96) | 新增目录(P2-2 拆分) |
|
||||||
| `src/shared/hooks/` | 7 | use-aria-live.ts (88) | |
|
| `src/shared/hooks/` | 7 | use-aria-live.ts (88) | |
|
||||||
| `src/shared/components/ui/` | 34 | chart.tsx (329) | 多为标准 shadcn/ui 组件 |
|
| `src/shared/components/ui/` | 34 | chart.tsx (329) | 多为标准 shadcn/ui 组件 |
|
||||||
| `src/shared/components/a11y/` | 4 | focus-trap.tsx (110) | |
|
| `src/shared/components/a11y/` | 4 | focus-trap.tsx (110) | |
|
||||||
| `src/shared/components/`(顶层) | 4 | onboarding-gate.tsx (312) | |
|
| `src/shared/components/`(顶层) | 4 | onboarding-gate.tsx (312) | |
|
||||||
| `src/shared/types/` | 2 | permissions.ts (92) | |
|
| `src/shared/types/` | 2 | permissions.ts (92) | |
|
||||||
| `src/auth.ts` | 1 | auth.ts (293) | |
|
| `src/auth.ts` | 1 | auth.ts (193) | ✅ 已从 293 行降至 193 行(P1-3 拆分) |
|
||||||
| `src/proxy.ts` | 1 | proxy.ts (75) | |
|
| `src/proxy.ts` | 1 | proxy.ts (75) | |
|
||||||
|
|
||||||
> 注:`components/ui/` 下 34 个文件多为 shadcn/ui 标准生成组件(基于 Radix UI),职责单一,未发现结构性问题,故未逐一列入问题清单。`chart.tsx`(329 行)为标准 shadcn chart 组件,行数较高但属框架约定,可接受。
|
> 注:`components/ui/` 下 34 个文件多为 shadcn/ui 标准生成组件(基于 Radix UI),职责单一,未发现结构性问题,故未逐一列入问题清单。`chart.tsx`(329 行)为标准 shadcn chart 组件,行数较高但属框架约定,可接受。
|
||||||
|
>
|
||||||
|
> **变更说明**:
|
||||||
|
> - `src/shared/lib/ai.ts`(原 218 行)已拆分为 `ai/` 目录 6 个文件,原文件保留为 9 行重导出(P2-2)
|
||||||
|
> - `src/auth.ts`(原 293 行)已拆分出 4 个 shared/lib 文件,自身降至 193 行(P1-3)
|
||||||
|
> - `src/shared/lib/` 新增:`password-security-service.ts`(84 行)、`role-utils.ts`(31 行)、`bcrypt-utils.ts`(18 行)、`http-utils.ts`(27 行)
|
||||||
|
|||||||
@@ -145,12 +145,12 @@ type QuestionContent = {
|
|||||||
### 7.1 登录态与权限校验
|
### 7.1 登录态与权限校验
|
||||||
- 题库创建/更新/知识点加载统一使用会话身份;缺失会话时回退到首个教师账号以保持演示可用。
|
- 题库创建/更新/知识点加载统一使用会话身份;缺失会话时回退到首个教师账号以保持演示可用。
|
||||||
- 主要修改:
|
- 主要修改:
|
||||||
- [actions.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/questions/actions.ts)
|
- [actions.ts](file:///e:/Desktop/CICD/src/modules/questions/actions.ts)
|
||||||
|
|
||||||
### 7.2 弹窗稳定性
|
### 7.2 弹窗稳定性
|
||||||
- Create Question 弹窗在打开时仅在默认知识点变化时更新,避免重复 setState 造成循环更新。
|
- Create Question 弹窗在打开时仅在默认知识点变化时更新,避免重复 setState 造成循环更新。
|
||||||
- 主要修改:
|
- 主要修改:
|
||||||
- [create-question-dialog.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/questions/components/create-question-dialog.tsx)
|
- [create-question-dialog.tsx](file:///e:/Desktop/CICD/src/modules/questions/components/create-question-dialog.tsx)
|
||||||
|
|
||||||
### 7.3 校验
|
### 7.3 校验
|
||||||
- `npm run lint`:通过
|
- `npm run lint`:通过
|
||||||
|
|||||||
@@ -173,7 +173,7 @@ type ExamNode = {
|
|||||||
|
|
||||||
- **题库列表稳定性**:
|
- **题库列表稳定性**:
|
||||||
- 题库卡片对题目 content/type 做解析兜底,避免异常数据导致运行时崩溃。
|
- 题库卡片对题目 content/type 做解析兜底,避免异常数据导致运行时崩溃。
|
||||||
- 主要修改: [question-bank-list.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/exams/components/assembly/question-bank-list.tsx)
|
- 主要修改: [question-bank-list.tsx](file:///e:/Desktop/CICD/src/modules/exams/components/assembly/question-bank-list.tsx)
|
||||||
|
|
||||||
**日期**:2026-01-12 (当前)
|
**日期**:2026-01-12 (当前)
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@
|
|||||||
- `homework_submissions`: 学生作业尝试(attempt_no/status/时间/是否迟交)
|
- `homework_submissions`: 学生作业尝试(attempt_no/status/时间/是否迟交)
|
||||||
- `homework_answers`: 每题答案(answer_content/score/feedback)
|
- `homework_answers`: 每题答案(answer_content/score/feedback)
|
||||||
|
|
||||||
数据库变更记录见:[schema-changelog.md](file:///c:/Users/xiner/Desktop/CICD/docs/db/schema-changelog.md#L34-L77)
|
数据库变更记录见:[schema-changelog.md](file:///e:/Desktop/CICD/docs/db/schema-changelog.md#L34-L77)
|
||||||
|
|
||||||
### 2.2 设计要点:冻结 Exam → Homework Assignment
|
### 2.2 设计要点:冻结 Exam → Homework Assignment
|
||||||
|
|
||||||
@@ -52,37 +52,37 @@
|
|||||||
### 3.1 教师端
|
### 3.1 教师端
|
||||||
|
|
||||||
- `/teacher/homework/assignments`: 作业列表
|
- `/teacher/homework/assignments`: 作业列表
|
||||||
实现:[assignments/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
实现:[assignments/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
||||||
- `/teacher/homework/assignments/create`: 从 Exam 派发作业
|
- `/teacher/homework/assignments/create`: 从 Exam 派发作业
|
||||||
实现:[create/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/create/page.tsx)
|
实现:[create/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/create/page.tsx)
|
||||||
- `/teacher/homework/assignments/[id]`: 作业详情
|
- `/teacher/homework/assignments/[id]`: 作业详情
|
||||||
实现:[[id]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx)
|
实现:[[id]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx)
|
||||||
- `/teacher/homework/assignments/[id]/submissions`: 作业提交列表(按作业筛选)
|
- `/teacher/homework/assignments/[id]/submissions`: 作业提交列表(按作业筛选)
|
||||||
实现:[[id]/submissions/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/submissions/page.tsx)
|
实现:[[id]/submissions/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/submissions/page.tsx)
|
||||||
- `/teacher/homework/submissions`: 全部提交列表
|
- `/teacher/homework/submissions`: 全部提交列表
|
||||||
实现:[submissions/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/page.tsx)
|
实现:[submissions/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/page.tsx)
|
||||||
- `/teacher/homework/submissions/[submissionId]`: 批改页
|
- `/teacher/homework/submissions/[submissionId]`: 批改页
|
||||||
实现:[[submissionId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/%5BsubmissionId%5D/page.tsx)
|
实现:[[submissionId]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/submissions/%5BsubmissionId%5D/page.tsx)
|
||||||
|
|
||||||
关联重定向:
|
关联重定向:
|
||||||
|
|
||||||
- `/teacher/exams/grading` → `/teacher/homework/submissions`
|
- `/teacher/exams/grading` → `/teacher/homework/submissions`
|
||||||
实现:[grading/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/page.tsx)
|
实现:[grading/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/page.tsx)
|
||||||
- `/teacher/exams/grading/[submissionId]` → `/teacher/homework/submissions`
|
- `/teacher/exams/grading/[submissionId]` → `/teacher/homework/submissions`
|
||||||
实现:[grading/[submissionId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/%5BsubmissionId%5D/page.tsx)
|
实现:[grading/[submissionId]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/exams/grading/%5BsubmissionId%5D/page.tsx)
|
||||||
|
|
||||||
### 3.2 学生端
|
### 3.2 学生端
|
||||||
|
|
||||||
- `/student/learning/assignments`: 作业列表
|
- `/student/learning/assignments`: 作业列表
|
||||||
实现:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/page.tsx)
|
实现:[page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/page.tsx)
|
||||||
- `/student/learning/assignments/[assignmentId]`: 作答页(开始/保存/提交)
|
- `/student/learning/assignments/[assignmentId]`: 作答页(开始/保存/提交)
|
||||||
实现:[[assignmentId]/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/%5BassignmentId%5D/page.tsx)
|
实现:[[assignmentId]/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/learning/assignments/%5BassignmentId%5D/page.tsx)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 4. 数据访问层(Data Access)
|
## 4. 数据访问层(Data Access)
|
||||||
|
|
||||||
数据访问位于:[data-access.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts)
|
数据访问位于:[data-access.ts](file:///e:/Desktop/CICD/src/modules/homework/data-access.ts)
|
||||||
|
|
||||||
### 4.1 教师侧查询
|
### 4.1 教师侧查询
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
|
|
||||||
## 5. Server Actions
|
## 5. Server Actions
|
||||||
|
|
||||||
实现位于:[actions.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/actions.ts)
|
实现位于:[actions.ts](file:///e:/Desktop/CICD/src/modules/homework/actions.ts)
|
||||||
|
|
||||||
### 5.1 教师侧
|
### 5.1 教师侧
|
||||||
|
|
||||||
@@ -129,13 +129,13 @@
|
|||||||
|
|
||||||
### 6.1 教师批改视图
|
### 6.1 教师批改视图
|
||||||
|
|
||||||
- [HomeworkGradingView](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-grading-view.tsx)
|
- [HomeworkGradingView](file:///e:/Desktop/CICD/src/modules/homework/components/homework-grading-view.tsx)
|
||||||
- 左侧:学生答案只读展示
|
- 左侧:学生答案只读展示
|
||||||
- 右侧:按题录入分数与反馈,并提交批改
|
- 右侧:按题录入分数与反馈,并提交批改
|
||||||
|
|
||||||
### 6.2 学生作答视图
|
### 6.2 学生作答视图
|
||||||
|
|
||||||
- [HomeworkTakeView](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-take-view.tsx)
|
- [HomeworkTakeView](file:///e:/Desktop/CICD/src/modules/homework/components/homework-take-view.tsx)
|
||||||
- Start:开始一次作答
|
- Start:开始一次作答
|
||||||
- Save:按题保存
|
- Save:按题保存
|
||||||
- Submit:提交(提交前会先保存当前题目答案)
|
- Submit:提交(提交前会先保存当前题目答案)
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
|
|
||||||
## 7. 类型定义
|
## 7. 类型定义
|
||||||
|
|
||||||
类型位于:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/types.ts)
|
类型位于:[types.ts](file:///e:/Desktop/CICD/src/modules/homework/types.ts)
|
||||||
|
|
||||||
- 教师侧:`HomeworkAssignmentListItem` / `HomeworkSubmissionDetails` 等
|
- 教师侧:`HomeworkAssignmentListItem` / `HomeworkSubmissionDetails` 等
|
||||||
- 学生侧:`StudentHomeworkAssignmentListItem` / `StudentHomeworkTakeData` 等
|
- 学生侧:`StudentHomeworkAssignmentListItem` / `StudentHomeworkTakeData` 等
|
||||||
@@ -170,7 +170,7 @@
|
|||||||
|
|
||||||
### 9.2 CI 构建与部署(Gitea)
|
### 9.2 CI 构建与部署(Gitea)
|
||||||
|
|
||||||
工作流位于:[ci.yml](file:///c:/Users/xiner/Desktop/CICD/.gitea/workflows/ci.yml)
|
工作流位于:[ci.yml](file:///e:/Desktop/CICD/.gitea/workflows/ci.yml)
|
||||||
|
|
||||||
- 构建阶段(`npm run build`)不依赖数据库连接:作业相关页面在构建时不会静态预渲染执行查库
|
- 构建阶段(`npm run build`)不依赖数据库连接:作业相关页面在构建时不会静态预渲染执行查库
|
||||||
- 部署阶段通过 `docker run -e DATABASE_URL=...` 在运行时注入数据库连接串
|
- 部署阶段通过 `docker run -e DATABASE_URL=...` 在运行时注入数据库连接串
|
||||||
@@ -181,7 +181,7 @@
|
|||||||
|
|
||||||
作业模块相关页面在渲染时会进行数据库查询,因此显式标记为动态渲染以避免构建期预渲染触发数据库连接:
|
作业模块相关页面在渲染时会进行数据库查询,因此显式标记为动态渲染以避免构建期预渲染触发数据库连接:
|
||||||
|
|
||||||
- 教师端作业列表:[assignments/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
- 教师端作业列表:[assignments/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/page.tsx)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -191,32 +191,32 @@
|
|||||||
|
|
||||||
将 `/teacher/homework/assignments/[id]` 页面调整为“只负责组装”,把可复用展示逻辑下沉到模块内组件:
|
将 `/teacher/homework/assignments/[id]` 页面调整为“只负责组装”,把可复用展示逻辑下沉到模块内组件:
|
||||||
|
|
||||||
- 页面组装:[page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx)
|
- 页面组装:[page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/teacher/homework/assignments/%5Bid%5D/page.tsx)
|
||||||
- 题目错误概览卡片(overview):[homework-assignment-question-error-overview-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-overview-card.tsx)
|
- 题目错误概览卡片(overview):[homework-assignment-question-error-overview-card.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-overview-card.tsx)
|
||||||
- 题目错误明细卡片(details):[homework-assignment-question-error-details-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-details-card.tsx)
|
- 题目错误明细卡片(details):[homework-assignment-question-error-details-card.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-details-card.tsx)
|
||||||
- 试卷预览/错题工作台容器卡片:[homework-assignment-exam-content-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-content-card.tsx)
|
- 试卷预览/错题工作台容器卡片:[homework-assignment-exam-content-card.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-content-card.tsx)
|
||||||
|
|
||||||
### 10.2 题目点击联动:试卷预览 ↔ 错题详情
|
### 10.2 题目点击联动:试卷预览 ↔ 错题详情
|
||||||
|
|
||||||
在“试卷预览”中点击题目后,右侧联动展示该题的统计与错答列表(按学生逐条展示,不做合并):
|
在“试卷预览”中点击题目后,右侧联动展示该题的统计与错答列表(按学生逐条展示,不做合并):
|
||||||
|
|
||||||
- 工作台(选择题目、拼装左右面板):[homework-assignment-exam-error-explorer.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer.tsx)
|
- 工作台(选择题目、拼装左右面板):[homework-assignment-exam-error-explorer.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer.tsx)
|
||||||
- 试卷预览面板(可选中题目):[homework-assignment-exam-preview-pane.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-preview-pane.tsx)
|
- 试卷预览面板(可选中题目):[homework-assignment-exam-preview-pane.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-preview-pane.tsx)
|
||||||
- 错题详情面板(错误人数/错误率/错答列表):[homework-assignment-question-error-detail-panel.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-detail-panel.tsx)
|
- 错题详情面板(错误人数/错误率/错答列表):[homework-assignment-question-error-detail-panel.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-question-error-detail-panel.tsx)
|
||||||
|
|
||||||
### 10.3 统计数据增强:返回逐学生错答
|
### 10.3 统计数据增强:返回逐学生错答
|
||||||
|
|
||||||
为满足“错答列表逐条展示学生姓名 + 答案”的需求,作业统计查询返回每题的错答明细(包含学生信息):
|
为满足“错答列表逐条展示学生姓名 + 答案”的需求,作业统计查询返回每题的错答明细(包含学生信息):
|
||||||
|
|
||||||
- 数据访问:[getHomeworkAssignmentAnalytics](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts)
|
- 数据访问:[getHomeworkAssignmentAnalytics](file:///e:/Desktop/CICD/src/modules/homework/data-access.ts)
|
||||||
- 类型定义:[types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/types.ts)
|
- 类型定义:[types.ts](file:///e:/Desktop/CICD/src/modules/homework/types.ts)
|
||||||
|
|
||||||
### 10.4 加载优化:Client Wrapper 动态分包
|
### 10.4 加载优化:Client Wrapper 动态分包
|
||||||
|
|
||||||
由于 `next/dynamic({ ssr: false })` 不能在 Server Component 内使用,工作台动态加载通过 Client wrapper 进行隔离:
|
由于 `next/dynamic({ ssr: false })` 不能在 Server Component 内使用,工作台动态加载通过 Client wrapper 进行隔离:
|
||||||
|
|
||||||
- Client wrapper:[homework-assignment-exam-error-explorer-lazy.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer-lazy.tsx)
|
- Client wrapper:[homework-assignment-exam-error-explorer-lazy.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-error-explorer-lazy.tsx)
|
||||||
- 入口卡片(Server Component,渲染 wrapper):[homework-assignment-exam-content-card.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-content-card.tsx)
|
- 入口卡片(Server Component,渲染 wrapper):[homework-assignment-exam-content-card.tsx](file:///e:/Desktop/CICD/src/modules/homework/components/homework-assignment-exam-content-card.tsx)
|
||||||
|
|
||||||
### 10.5 校验
|
### 10.5 校验
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@
|
|||||||
|
|
||||||
数据由 Homework 模块统一提供聚合查询,避免页面层拼 SQL:
|
数据由 Homework 模块统一提供聚合查询,避免页面层拼 SQL:
|
||||||
|
|
||||||
- 新增查询:[getStudentDashboardGrades](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts)
|
- 新增查询:[getStudentDashboardGrades](file:///e:/Desktop/CICD/src/modules/homework/data-access.ts)
|
||||||
- `trend`:取该学生所有 `graded` 提交中“每个 assignment 最新一次”的集合,按时间升序取最近 10 个
|
- `trend`:取该学生所有 `graded` 提交中“每个 assignment 最新一次”的集合,按时间升序取最近 10 个
|
||||||
- `recent`:对 `trend` 再按时间降序取最近 5 条,用于表格展示
|
- `recent`:对 `trend` 再按时间降序取最近 5 条,用于表格展示
|
||||||
- `maxScore`:通过 `homework_assignment_questions` 汇总每个 assignment 的总分(SUM(score))
|
- `maxScore`:通过 `homework_assignment_questions` 汇总每个 assignment 的总分(SUM(score))
|
||||||
@@ -255,7 +255,7 @@
|
|||||||
|
|
||||||
为 Dashboard 聚合数据提供显式类型:
|
为 Dashboard 聚合数据提供显式类型:
|
||||||
|
|
||||||
- [types.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/types.ts)
|
- [types.ts](file:///e:/Desktop/CICD/src/modules/homework/types.ts)
|
||||||
- `StudentHomeworkScoreAnalytics`
|
- `StudentHomeworkScoreAnalytics`
|
||||||
- `StudentRanking`
|
- `StudentRanking`
|
||||||
- `StudentDashboardGradeProps`
|
- `StudentDashboardGradeProps`
|
||||||
@@ -263,11 +263,11 @@
|
|||||||
### 11.4 页面与组件接入
|
### 11.4 页面与组件接入
|
||||||
|
|
||||||
- 学生主页页面负责“取数 + 计算基础计数 + 传参”:
|
- 学生主页页面负责“取数 + 计算基础计数 + 传参”:
|
||||||
- [student/dashboard/page.tsx](file:///c:/Users/xiner/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx)
|
- [student/dashboard/page.tsx](file:///e:/Desktop/CICD/src/app/(dashboard)/student/dashboard/page.tsx)
|
||||||
- 取数:`getStudentDashboardGrades(student.id)`
|
- 取数:`getStudentDashboardGrades(student.id)`
|
||||||
- 传入:`<StudentDashboard grades={grades} />`
|
- 传入:`<StudentDashboard grades={grades} />`
|
||||||
- 展示组件负责渲染卡片:
|
- 展示组件负责渲染卡片:
|
||||||
- [student-view.tsx](file:///c:/Users/xiner/Desktop/CICD/src/modules/dashboard/components/student-view.tsx)
|
- [student-view.tsx](file:///e:/Desktop/CICD/src/modules/dashboard/components/student-view.tsx)
|
||||||
- 趋势图:使用内联 `svg polyline` 渲染折线,避免引入额外图表依赖
|
- 趋势图:使用内联 `svg polyline` 渲染折线,避免引入额外图表依赖
|
||||||
|
|
||||||
### 11.5 校验
|
### 11.5 校验
|
||||||
@@ -319,8 +319,8 @@
|
|||||||
- 提交作业与学生列表查询改为使用真实登录用户,避免提交后仍显示未开始。
|
- 提交作业与学生列表查询改为使用真实登录用户,避免提交后仍显示未开始。
|
||||||
- 学生列表优先展示最近一次已提交/已评分记录,提升状态准确性。
|
- 学生列表优先展示最近一次已提交/已评分记录,提升状态准确性。
|
||||||
- 主要修改:
|
- 主要修改:
|
||||||
- [actions.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/actions.ts)
|
- [actions.ts](file:///e:/Desktop/CICD/src/modules/homework/actions.ts)
|
||||||
- [data-access.ts](file:///c:/Users/xiner/Desktop/CICD/src/modules/homework/data-access.ts)
|
- [data-access.ts](file:///e:/Desktop/CICD/src/modules/homework/data-access.ts)
|
||||||
|
|
||||||
### 14.2 校验
|
### 14.2 校验
|
||||||
- `npm run lint`:通过
|
- `npm run lint`:通过
|
||||||
|
|||||||
@@ -193,14 +193,20 @@ import "server-only"
|
|||||||
import type { NotificationChannelSender, ChannelRecipient } from "./types"
|
import type { NotificationChannelSender, ChannelRecipient } from "./types"
|
||||||
import type { NotificationPayload, ChannelSendResult, NotificationChannel } from "../types"
|
import type { NotificationPayload, ChannelSendResult, NotificationChannel } from "../types"
|
||||||
|
|
||||||
const channel: NotificationChannel = "your_channel" as NotificationChannel
|
// 注意:先在 types.ts 的 NotificationChannel 联合类型中添加 "your_channel"
|
||||||
|
const channel: NotificationChannel = "your_channel"
|
||||||
|
|
||||||
class YourChannelSender implements NotificationChannelSender {
|
class YourChannelSender implements NotificationChannelSender {
|
||||||
readonly channel = channel
|
readonly channel = channel
|
||||||
async send(payload: NotificationPayload, recipient: ChannelRecipient): Promise<ChannelSendResult> {
|
async send(payload: NotificationPayload, recipient: ChannelRecipient): Promise<ChannelSendResult> {
|
||||||
// 实现发送逻辑
|
// 实现发送逻辑
|
||||||
}
|
}
|
||||||
async sendBatch(items) { /* ... */ }
|
async sendBatch(
|
||||||
|
items: Array<{ payload: NotificationPayload; recipient: ChannelRecipient }>
|
||||||
|
): Promise<ChannelSendResult[]> {
|
||||||
|
// 实现批量发送逻辑
|
||||||
|
return []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createYourSender(): NotificationChannelSender {
|
export function createYourSender(): NotificationChannelSender {
|
||||||
|
|||||||
@@ -183,7 +183,7 @@ interface UserCardProps {
|
|||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function UserCard({ user, onSelect, children }: UserCardProps) {
|
export function UserCard({ user, onSelect, children }: UserCardProps): JSX.Element {
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -291,7 +291,7 @@ import type { User } from "@/shared/types/permissions";
|
|||||||
import { UserList } from "@/modules/users/components/user-list";
|
import { UserList } from "@/modules/users/components/user-list";
|
||||||
import { getUsers } from "@/modules/users/data-access";
|
import { getUsers } from "@/modules/users/data-access";
|
||||||
|
|
||||||
export default async function UsersPage() {
|
export default async function UsersPage(): Promise<JSX.Element> {
|
||||||
const users = await getUsers();
|
const users = await getUsers();
|
||||||
return <UserList users={users} />;
|
return <UserList users={users} />;
|
||||||
}
|
}
|
||||||
@@ -432,10 +432,10 @@ export type ActionState<T = void> = {
|
|||||||
|
|
||||||
import { revalidatePath } from "next/cache";
|
import { revalidatePath } from "next/cache";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { ActionState } from "@/shared/types/action-state";
|
|
||||||
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
|
import { requirePermission, PermissionDeniedError } from "@/shared/lib/auth-guard";
|
||||||
import { Permissions } from "@/shared/types/permissions";
|
import { Permissions } from "@/shared/types/permissions";
|
||||||
import { createExam, persistExamDraft } from "./data-access";
|
import { createExam } from "./data-access";
|
||||||
|
import type { ActionState } from "@/shared/types/action-state";
|
||||||
|
|
||||||
const ExamCreateSchema = z.object({
|
const ExamCreateSchema = z.object({
|
||||||
title: z.string().min(1),
|
title: z.string().min(1),
|
||||||
@@ -749,12 +749,12 @@ export default eslintConfig;
|
|||||||
|
|
||||||
### 15.2 Prettier
|
### 15.2 Prettier
|
||||||
|
|
||||||
**建议配置**(`.prettierrc`,待创建):
|
**当前配置**(`.prettierrc`):
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"semi": true,
|
"semi": false,
|
||||||
"singleQuote": true,
|
"singleQuote": false,
|
||||||
"tabWidth": 2,
|
"tabWidth": 2,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"printWidth": 100,
|
"printWidth": 100,
|
||||||
|
|||||||
Reference in New Issue
Block a user