Files
ClassHelper/packages/server/prisma/schema.prisma
T
LoveChengke 1073436ad6 feat(class-account): 学生端主体改为班级(班级码 + 班级密码)+ 数据按班级隔离
服务端:
- Class 新增 code(唯一)/passwordHash 与迁移(老数据回填 C00001 形式, 未设密码禁止登录)
- POST /auth/class-login: 班级码 + 班级密码 -> classSession 会话(sub=classId)
- PATCH /classes/:id/class-account: 管理员改班级码/重置密码(唯一性、格式校验)
- auth 中间件/socket 握手支持班级会话(sub 为 classId, 回查 Class 表, 加入 user:{classId} 房间)
- lib/session.ts: 班级设备代全班操作(通知已读/作业完成/成绩总览按全班读写), 普通学生账号行为不变
- 叫人消息同时广播到 user:{classId}, 班级设备立即上岛
- 班级列表/详情按权限返回班级码与密码状态(哈希不外泄)

客户端:
- 登录页改为班级码 + 班级密码(个人学生入口移除, /auth/login 接口保留兼容)
- 作业按钮/成绩标题按班级会话显示(全班标记完成 / 本班成绩)
- 冒烟脚本自动准备班级账号凭据(兼容全新与升级安装)

Web 管理端:
- 班级管理新增「班级账号」列与设置弹窗(改班级码/重置密码/随机生成)
- 新建班级可选自定义班级码, 默认由服务端生成

验证: verify:e2e 125/125(新增班级账号 18 项) verify:web 18/18 verify:desktop 40/40
      (login=高一(1)班 classSession=true)
文档: README 新增「学生端班级账号」章节、API/迁移/验收/默认账号更新; 安装包 README 同步
2026-09-12 15:51:08 +08:00

259 lines
7.9 KiB
Plaintext

// 班级小助手 - 数据模型
//
// 设计说明:
// 1) 本 schema 使用 sqlite provider(本地 MVP / 离线开发)。
// 字段全部使用 String / Int / Float / Boolean / DateTime 等跨库通用类型,
// 不使用 Prisma enum 与 @db.* 原生类型,因此可平移到 MySQL。
// MySQL 版本见同目录 schema.mysql.prisma,切换步骤见 docs/mysql.md。
// 2) 角色与优先级用 String 存储('ADMIN' | 'TEACHER' | 'STUDENT'、
// 'LOW' | 'NORMAL' | 'HIGH' | 'URGENT'),在 API 层用 zod 校验,
// 这样 SQLite / MySQL 行为完全一致。
// 3) 所有主键使用 cuid 字符串,便于多端离线生成与合并。
generator client {
provider = "prisma-client"
output = "../src/generated/prisma"
}
datasource db {
provider = "sqlite"
}
/// 用户:教师 / 管理员 / 学生统一存储
model User {
id String @id @default(cuid())
username String @unique
passwordHash String
name String
role String @default("STUDENT")
classId String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// 学生的主班级(一个学生属于一个主班级)
class Class? @relation("ClassStudents", fields: [classId], references: [id], onDelete: SetNull)
// 教师创建(负责)的班级
ownedClasses Class[] @relation("ClassOwner")
// 教师被分配协作的班级
classAssignments ClassTeacher[]
courses Course[]
homeworkStatuses HomeworkStatus[]
notificationReads NotificationRead[]
grades Grade[]
enrollments Enrollment[]
createdHomeworks Homework[] @relation("HomeworkCreator")
createdNotices Notification[] @relation("NotificationCreator")
@@index([classId])
@@index([role])
}
/// 班级
model Class {
id String @id @default(cuid())
name String
grade String
teacherId String
/// 班级码:学生端「班级账号」登录用(唯一,字母数字,4~16 位,存大写)
code String @unique
/// 班级密码(bcrypt 哈希);null = 尚未设置,此时禁止班级账号登录
passwordHash String?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
teacher User @relation("ClassOwner", fields: [teacherId], references: [id], onDelete: Cascade)
students User[] @relation("ClassStudents")
teachers ClassTeacher[]
courses Course[]
schedules Schedule[]
homeworks Homework[]
notifications Notification[]
grades Grade[]
enrollments Enrollment[]
timeLayouts TimeLayout[]
@@index([teacherId])
}
/// 教师-班级 分配关系(一个教师可管理多个班级)
model ClassTeacher {
id String @id @default(cuid())
classId String
teacherId String
createdAt DateTime @default(now())
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
teacher User @relation(fields: [teacherId], references: [id], onDelete: Cascade)
@@unique([classId, teacherId])
@@index([teacherId])
}
/// 学生-班级 选课/归属记录(与 User.classId 保持同步,支持未来多班)
model Enrollment {
id String @id @default(cuid())
userId String
classId String
createdAt DateTime @default(now())
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
@@unique([userId, classId])
@@index([classId])
}
/// 课程
model Course {
id String @id @default(cuid())
name String
teacherId String
classId String
createdAt DateTime @default(now())
teacher User @relation(fields: [teacherId], references: [id], onDelete: Cascade)
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
schedules Schedule[]
homeworks Homework[]
grades Grade[]
@@unique([classId, name])
@@index([classId])
}
/// 课表条目
model Schedule {
id String @id @default(cuid())
classId String
courseId String
/// 1=周一 ... 7=周日
dayOfWeek Int
/// HH:mm
startTime String
/// HH:mm
endTime String
location String?
/// 学期起始周次(含)
weekStart Int @default(1)
/// 学期结束周次(含)
weekEnd Int @default(20)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
course Course @relation(fields: [courseId], references: [id], onDelete: Cascade)
@@index([classId, dayOfWeek])
@@index([courseId])
}
/// 作业
model Homework {
id String @id @default(cuid())
classId String
courseId String?
title String
content String
attachmentUrl String?
dueAt DateTime?
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
course Course? @relation(fields: [courseId], references: [id], onDelete: SetNull)
creator User @relation("HomeworkCreator", fields: [createdBy], references: [id], onDelete: Cascade)
statuses HomeworkStatus[]
@@index([classId, createdAt])
@@index([courseId])
@@index([dueAt])
}
/// 学生作业完成状态
model HomeworkStatus {
id String @id @default(cuid())
homeworkId String
userId String
completed Boolean @default(false)
updatedAt DateTime @updatedAt
homework Homework @relation(fields: [homeworkId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([homeworkId, userId])
@@index([userId])
}
/// 通知
model Notification {
id String @id @default(cuid())
classId String
title String
content String
priority String @default("NORMAL")
createdBy String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
creator User @relation("NotificationCreator", fields: [createdBy], references: [id], onDelete: Cascade)
reads NotificationRead[]
@@index([classId, createdAt])
}
/// 通知已读记录
model NotificationRead {
id String @id @default(cuid())
notificationId String
userId String
readAt DateTime @default(now())
notification Notification @relation(fields: [notificationId], references: [id], onDelete: Cascade)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([notificationId, userId])
@@index([userId])
}
/// 课表时间配置(ClassIsland 同款 JSON 导入后的节次时间表)
model TimeLayout {
id String @id @default(cuid())
classId String
name String
/// 来源:classisland | manual
source String @default("classisland")
/// 节次数组(JSON 字符串):[{ index, name, startTime, endTime, type, skipped }]
items String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
@@index([classId])
}
/// 成绩
model Grade {
id String @id @default(cuid())
classId String
courseId String?
userId String
examName String
score Float
totalScore Float @default(100)
publishedAt DateTime @default(now())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
class Class @relation(fields: [classId], references: [id], onDelete: Cascade)
course Course? @relation(fields: [courseId], references: [id], onDelete: SetNull)
student User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@index([userId, publishedAt])
@@index([classId, examName])
@@index([courseId])
}