generator client { provider = "prisma-client-js" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id String @id @default(cuid(2)) email String password String name String? avatarId String? // 头像文件 ID isSuperAdmin Boolean @default(false) // 超级管理员标记 createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? roles UserRole[] uploadFiles File[] @relation("FileUploader") oidcGrants OidcGrant[] // 复合唯一约束:未删除用户邮箱唯一,已删除用户邮箱可重复 @@unique([email, deletedAt]) @@map("users") } // 文件表 model File { id String @id @default(cuid(2)) filename String // 原始文件名 objectName String // MinIO 中的对象名 mimeType String // MIME 类型 size Int // 文件大小(字节) purpose String // 用途: avatar, attachment 等 uploaderId String // 上传者 ID createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? uploader User @relation("FileUploader", fields: [uploaderId], references: [id]) @@index([uploaderId]) @@index([purpose]) @@map("files") } // 角色表 model Role { id String @id @default(cuid(2)) code String @unique // 角色编码: admin, user name String // 角色名称 description String? isSystem Boolean @default(false) // 系统内置角色不可删 isEnabled Boolean @default(true) sort Int @default(0) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? users UserRole[] permissions RolePermission[] menus RoleMenu[] @@map("roles") } // 权限表 model Permission { id String @id @default(cuid(2)) code String @unique // 权限编码: user:create name String description String? resource String // 资源: user action String // 操作: create isEnabled Boolean @default(true) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? roles RolePermission[] @@index([resource]) @@map("permissions") } // 菜单表 model Menu { id String @id @default(cuid(2)) parentId String? code String @unique name String type String @default("menu") // dir / menu / button path String? icon String? // Lucide 图标名 isExternal Boolean @default(false) isHidden Boolean @default(false) isEnabled Boolean @default(true) isStatic Boolean @default(false) // 静态/动态菜单 sort Int @default(0) meta Json? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? parent Menu? @relation("MenuTree", fields: [parentId], references: [id]) children Menu[] @relation("MenuTree") roles RoleMenu[] @@index([parentId]) @@map("menus") } // 用户-角色关联表 model UserRole { id String @id @default(cuid(2)) userId String roleId String createdAt DateTime @default(now()) user User @relation(fields: [userId], references: [id], onDelete: Cascade) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) @@unique([userId, roleId]) @@map("user_roles") } // 角色-权限关联表 model RolePermission { id String @id @default(cuid(2)) roleId String permissionId String createdAt DateTime @default(now()) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) permission Permission @relation(fields: [permissionId], references: [id], onDelete: Cascade) @@unique([roleId, permissionId]) @@map("role_permissions") } // 角色-菜单关联表 model RoleMenu { id String @id @default(cuid(2)) roleId String menuId String createdAt DateTime @default(now()) role Role @relation(fields: [roleId], references: [id], onDelete: Cascade) menu Menu @relation(fields: [menuId], references: [id], onDelete: Cascade) @@unique([roleId, menuId]) @@map("role_menus") } // ============ 教学管理模块 ============ // 教师表 model Teacher { id String @id @default(cuid(2)) teacherNo String @unique // 工号 name String gender String? // male / female phone String? email String? subject String? // 任教科目 createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? // 关系 headOfClasses Class[] @relation("HeadTeacher") // 作为班主任的班级(一对一的反向) teachClasses ClassTeacher[] // 任课班级(多对多) @@map("teachers") } // 班级表 model Class { id String @id @default(cuid(2)) code String @unique // 班级代码 name String // 班级名称 grade String? // 年级 headTeacherId String? // 班主任 ID(一对一) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? // 关系 headTeacher Teacher? @relation("HeadTeacher", fields: [headTeacherId], references: [id]) students Student[] // 班级学生(一对多) teachers ClassTeacher[] // 任课教师(多对多) @@index([headTeacherId]) @@map("classes") } // 学生表 model Student { id String @id @default(cuid(2)) studentNo String @unique // 学号 name String gender String? // male / female phone String? email String? classId String? // 所属班级(多对一) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? // 关系 class Class? @relation(fields: [classId], references: [id]) @@index([classId]) @@map("students") } // 班级-教师关联表(多对多:任课关系) model ClassTeacher { id String @id @default(cuid(2)) classId String teacherId String createdAt DateTime @default(now()) class Class @relation(fields: [classId], references: [id], onDelete: Cascade) teacher Teacher @relation(fields: [teacherId], references: [id], onDelete: Cascade) @@unique([classId, teacherId]) @@map("class_teachers") } // ============ OIDC Provider 模块 ============ // OIDC 客户端应用 model OidcClient { id String @id @default(cuid(2)) clientId String @unique // 客户端 ID clientSecret String? // 客户端密钥(公开客户端可为空) clientName String // 客户端名称 clientUri String? // 客户端主页 logoUri String? // Logo URL redirectUris String[] // 回调地址列表 postLogoutRedirectUris String[] // 登出后回调地址 grantTypes String[] // 授权类型: authorization_code, refresh_token, client_credentials responseTypes String[] // 响应类型: code, token, id_token scopes String[] // 允许的 scope: openid, profile, email, etc. tokenEndpointAuthMethod String @default("client_secret_basic") // 认证方式 applicationType String @default("web") // web / native isEnabled Boolean @default(true) isFirstParty Boolean @default(false) // 是否为第一方应用(跳过授权确认) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt deletedAt DateTime? grants OidcGrant[] refreshTokens OidcRefreshToken[] @@map("oidc_clients") } // OIDC 刷新令牌(长期,需要持久化) model OidcRefreshToken { id String @id @default(cuid(2)) jti String @unique // JWT ID grantId String // 关联的授权 ID clientId String // 客户端 ID userId String // 用户 ID scope String // 授权范围 data Json // 完整令牌数据 expiresAt DateTime consumedAt DateTime? createdAt DateTime @default(now()) client OidcClient @relation(fields: [clientId], references: [clientId]) @@index([grantId]) @@index([clientId]) @@index([userId]) @@map("oidc_refresh_tokens") } // OIDC 授权记录(用户对客户端的授权) model OidcGrant { id String @id @default(cuid(2)) grantId String @unique // oidc-provider 生成的 grant ID clientId String // 客户端 ID userId String // 用户 ID scope String // 已授权的 scope data Json // 完整授权数据 expiresAt DateTime? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt client OidcClient @relation(fields: [clientId], references: [clientId]) user User @relation(fields: [userId], references: [id]) @@unique([clientId, userId]) @@index([userId]) @@map("oidc_grants") }