Commit 40f9fb59 authored by uuo00_n's avatar uuo00_n

feat(edu-service): 实现仪表盘功能并迁移至教育服务

refactor: 将仪表盘路由从LLM服务迁移至教育服务
feat: 添加仪表盘相关DTO、控制器和服务实现
fix: 修复JWT认证过滤器中的用户上下文处理
docs: 更新账户初始化文档和数据库脚本
chore: 清理LLM服务中旧的仪表盘代码
parent 6cf83571
- 当后端项目修改后要更新重启docker容器
\ No newline at end of file
- 修改代码后要重新编译并更新重启docker容器
\ No newline at end of file
# 默认初始化账号清单
以下账号已在数据库中初始化完成,可直接用于测试。所有账号的默认密码均为:**`password123`**
## 🎓 教育版 (Edu Edition)
适用于 K12 学校场景,包含完整的教务管理角色。
### 学生账号 (Students)
| 角色 | 账号 | 角色等级 | 姓名 | 班级 | 说明 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **学生** | `student_101` | 1 (User) | 张一 | 高一(1)班 | 理科创新班 |
| **学生** | `student_102` | 1 (User) | 李二 | 高一(2)班 | 理科普通班 |
| **学生** | `student_103` | 1 (User) | 王三 | 高一(3)班 | 文科普通班 |
### 教师账号 (Teachers)
| 角色 | 账号 | 角色等级 | 姓名 | 职务 | 说明 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **班主任** | `teacher_101` | 2 (Manager) | 赵老师 | 班主任 | 负责高一(1)班 |
| **班主任** | `teacher_102` | 2 (Manager) | 钱老师 | 班主任 | 负责高一(2)班 |
| **班主任** | `teacher_103` | 2 (Manager) | 孙老师 | 班主任 | 负责高一(3)班 |
### 管理层账号 (Administration)
| 角色 | 账号 | 角色等级 | 姓名 | 部门/职务 | 说明 |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **中层干部** | `leader_academic` | 3 (Leader) | 周主任 | 教务处 | 教务管理 |
| **中层干部** | `leader_grade` | 3 (Leader) | 吴组长 | 年级组 | 年级管理 |
| **校级领导** | `master_principal` | 4 (Master) | 郑校长 | 校长室 | 决策层 |
| **系统管理员** | `admin_edu` | 5 (Admin) | - | 运维部 | 超级管理员 |
## 🏢 企业版 (Biz Edition)
适用于企业培训与管理场景。
| 角色 | 账号 | 角色等级 | 说明 |
| :--- | :--- | :--- | :--- |
| **普通员工** | `staff_biz` | 1 (User) | 基础员工 |
| **组长/主管** | `manager_biz` | 2 (Manager) | 基层管理者 |
## 📦 初始化数据概览
系统初始化了以下核心业务数据:
- **班级**: 3个高一年级班级 (1班、2班、3班),包含不同的文理科方向。
- **人员**: 覆盖了学生、班主任、教务主任、年级组长、校长等多种角色。
- **课表**: 为每个班级生成了一周的完整课表,包含主科(语数英)和副科,且配置了对应的授课老师。
- **考勤与操行**: 生成了当天的考勤记录和操行评分示例。
- **通知公告**: 生成了校级、年级级、部门级的多层级通知数据。
## ⚠️ 注意事项
1. **密码安全**: 上述密码仅用于开发测试环境,**切勿在生产环境使用**
2. **数据重置**: 运行 `scripts/init_mongo.py` 会清空 MongoDB 中的相关集合,请谨慎操作。
3. **ID 关联**: MongoDB 中的 `account_id` 字段严格对应 PostgreSQL 中生成的自增 ID。若重新初始化数据库,请确保两边脚本配套执行。
......@@ -66,6 +66,7 @@ services:
- "8000:8000"
volumes:
- ./microservices/llm-service:/app
- ./scripts:/app/scripts
environment:
- MONGODB_URL=mongodb://mongo:27017
- DB_NAME=llm_filter_db
......
......@@ -81,6 +81,12 @@ http {
proxy_set_header Host $host;
}
# 仪表盘路由 (迁移至 Edu Service)
location /api/v1/dashboard {
proxy_pass http://edu_service/api/v1/dashboard;
proxy_set_header Host $host;
}
# Edu Service Swagger UI
location /swagger-ui/ {
proxy_pass http://edu_service/swagger-ui/;
......
......@@ -9,10 +9,14 @@ import (
type AuthService struct {
repo *repository.UserRepository
bindingRepo *repository.BindingRepository
}
func NewAuthService(repo *repository.UserRepository) *AuthService {
return &AuthService{repo: repo}
func NewAuthService(repo *repository.UserRepository, bindingRepo *repository.BindingRepository) *AuthService {
return &AuthService{
repo: repo,
bindingRepo: bindingRepo,
}
}
// RegisterRequest 注册请求参数
......@@ -81,7 +85,16 @@ func (s *AuthService) Login(req *LoginRequest) (string, *model.User, error) {
return "", nil, errors.New("invalid username or password")
}
token, err := utils.GenerateToken(user.ID, user.Username, user.Role)
// 获取用户的主绑定信息
var personID string
var personType string
binding, err := s.bindingRepo.FindPrimaryByUserID(user.ID)
if err == nil && binding != nil {
personID = binding.PersonID
personType = binding.Type
}
token, err := utils.GenerateToken(user.ID, user.Username, user.Role, user.RoleLevel, user.Edition, personID, personType)
if err != nil {
return "", nil, err
}
......
......@@ -73,13 +73,13 @@ func main() {
// 初始化管理员账号
initAdminUser(userRepo)
authSvc := service.NewAuthService(userRepo)
authHandler := handler.NewAuthHandler(authSvc)
bindingRepo := repository.NewBindingRepository(db)
bindingSvc := service.NewBindingService(bindingRepo)
bindingHandler := handler.NewBindingHandler(bindingSvc)
authSvc := service.NewAuthService(userRepo, bindingRepo)
authHandler := handler.NewAuthHandler(authSvc)
r := gin.Default()
// Swagger 文档路由
......
......@@ -33,11 +33,15 @@ func CheckPasswordHash(password, hash string) bool {
}
// GenerateToken 生成 JWT Token
func GenerateToken(userID uint, username string, role string) (string, error) {
func GenerateToken(userID uint, username string, role string, roleLevel int, edition string, personID string, personType string) (string, error) {
claims := jwt.MapClaims{
"sub": userID,
"name": username,
"role": role,
"role_level": roleLevel,
"edition": edition,
"person_id": personID,
"person_type": personType,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
"iat": time.Now().Unix(),
}
......
package com.llmfilter.edu.controller;
import com.llmfilter.edu.dto.*;
import com.llmfilter.edu.security.UserContext;
import com.llmfilter.edu.security.UserContextHolder;
import com.llmfilter.edu.service.DashboardService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/v1/dashboard")
@RequiredArgsConstructor
public class DashboardController {
private final DashboardService dashboardService;
@GetMapping("/student/today")
public StudentTodaySummary getStudentToday() {
UserContext user = UserContextHolder.getContext();
return dashboardService.getStudentTodaySummary(user);
}
@GetMapping("/student/week")
public StudentWeekSummary getStudentWeek(@RequestParam(required = false) Integer week) {
UserContext user = UserContextHolder.getContext();
return dashboardService.getStudentWeekSchedule(user, week);
}
@GetMapping("/teacher/week")
public TeacherWeekSummary getTeacherWeek(@RequestParam(required = false) Integer week) {
UserContext user = UserContextHolder.getContext();
return dashboardService.getTeacherWeekSchedule(user, week);
}
@GetMapping("/homeroom/current")
public HomeroomCurrentSummary getHomeroomCurrent() {
UserContext user = UserContextHolder.getContext();
return dashboardService.getHomeroomCurrentSummary(user);
}
@GetMapping("/department/overview")
public DepartmentOverview getDepartmentOverview() {
UserContext user = UserContextHolder.getContext();
return dashboardService.getDepartmentOverview(user);
}
@GetMapping("/campus/overview")
public CampusOverview getCampusOverview() {
UserContext user = UserContextHolder.getContext();
return dashboardService.getCampusOverview(user);
}
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CampusOverview {
private Long totalStudents;
private Long present;
private Long leaves;
private Long directives;
private List<Map<String, Object>> termGoals;
private List<Map<String, Object>> departmentProgress;
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class DepartmentOverview {
private StudentsAttendance studentsAttendance;
private List<TeacherRate> teacherAttendanceRates;
private List<AnomalyClass> anomalies;
private List<DirectiveInfo> directives;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class StudentsAttendance {
private Long total;
private Long present;
private Long absentOrLeave;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TeacherRate {
private String teacherId;
private Integer presentSlots;
private Integer totalSlots;
private Double rate;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class AnomalyClass {
private String classId;
private Double anomalyRate;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DirectiveInfo {
private String level;
private String content;
private String createdAt;
}
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HomeroomCurrentSummary {
private List<CurrentLesson> currentLessons;
private List<AttendanceRate> attendanceRates;
private List<LeaveInfo> leaves;
private List<DirectiveInfo> directives;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CurrentLesson {
private String classId;
private String courseName;
private String location;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class AttendanceRate {
private String classId;
private Long present;
private Integer total;
private Double rate;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class LeaveInfo {
private String studentId;
private String classId;
private String reason;
private String status;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class DirectiveInfo {
private String content;
private String createdAt;
}
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StudentTodaySummary {
private StudentInfo student;
private List<TodayScheduleItem> todaySchedule;
private List<TodayAttendanceItem> todayAttendance;
private TodayConduct todayConduct;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class StudentInfo {
private String studentId;
private String name;
private String classId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TodayScheduleItem {
private String lessonId;
private Integer period;
private String courseName;
private String location;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TodayAttendanceItem {
private String lessonId;
private String status;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TodayConduct {
private String date;
private Map<String, Object> metrics;
private String teacherComment;
private String headTeacherComment;
private Double score;
}
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class StudentWeekSummary {
private StudentInfo student;
private Integer currentWeek;
private Map<Integer, String> weekDates;
private Map<String, List<WeekScheduleItem>> schedule;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class StudentInfo {
private String studentId;
private String name;
private String classId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class WeekScheduleItem {
private String lessonId;
private Integer period;
private String courseName;
private String location;
private String startTime;
private String endTime;
private String teacherPersonId;
}
}
package com.llmfilter.edu.dto;
import lombok.Data;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;
import java.util.List;
import java.util.Map;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class TeacherWeekSummary {
private TeacherInfo teacher;
private Integer currentWeek;
private Map<Integer, String> weekDates;
private Map<String, List<WeekScheduleItem>> schedule;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class TeacherInfo {
private String teacherId;
private String name;
private String personId;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class WeekScheduleItem {
private String lessonId;
private Integer period;
private String courseName;
private List<ClassInfo> classes;
private String startTime;
private String endTime;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class ClassInfo {
private String classId;
private String location;
}
}
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
@AllArgsConstructor
@Entity
@Table(name = "classes")
public class ClassEntity {
public class ClassEntity implements java.io.Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
......@@ -24,6 +24,12 @@ public class ClassEntity {
@Column(name = "class_id", unique = true, nullable = false)
private String classId; // 业务上的班级ID,如 "2023-01"
@Column(nullable = false)
private String name;
@Column
private String major;
@Column(name = "head_teacher_person_id")
private String headTeacherPersonId; // 班主任人物ID(指向 Teacher.personId)
......
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
@AllArgsConstructor
@Entity
@Table(name = "persons")
public class Person {
public class Person implements java.io.Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
......
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
@AllArgsConstructor
@Entity
@Table(name = "schedules")
public class Schedule {
public class Schedule implements java.io.Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
......@@ -38,7 +38,7 @@ public class Schedule {
// 存储班级信息的 JSON 字符串 (简化处理,实际生产建议用关联表)
// Python code: classes: List[Dict[str, Any]]
@Column(columnDefinition = "TEXT")
@Column(name = "classes", columnDefinition = "jsonb")
private String classesJson;
@CreationTimestamp
......
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
@AllArgsConstructor
@Entity
@Table(name = "students")
public class Student {
public class Student implements java.io.Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
......@@ -27,6 +27,9 @@ public class Student {
@Column(nullable = false)
private String name;
@Column(name = "person_id", unique = true)
private String personId;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "class_id", referencedColumnName = "class_id")
private ClassEntity clazz; // 关联班级
......
......@@ -16,7 +16,7 @@ import java.time.LocalDateTime;
@AllArgsConstructor
@Entity
@Table(name = "teachers")
public class Teacher {
public class Teacher implements java.io.Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
......
......@@ -11,4 +11,25 @@ import java.util.Optional;
public interface ScheduleRepository extends JpaRepository<Schedule, Long> {
Optional<Schedule> findByLessonId(String lessonId);
List<Schedule> findAllByOrderByWeekdayAscPeriodAsc();
// 注意:Schedule 实体中的 classes 字段是 JSONB 类型,直接查询可能需要原生 SQL
// 这里暂时使用 Native Query 进行模拟,或者如果 Schedule 实体已正确映射,尝试使用 JPA 方法
// 假设 Schedule 实体中没有直接关联 ClassEntity,而是通过 JSON 存储
// 简化起见,我们先用原生 SQL 查(PostgreSQL JSONB 语法)
// SELECT * FROM schedules s, jsonb_array_elements(s.classes) c WHERE c->>'class_id' = :classId AND s.weekday = :weekday ORDER BY s.period ASC
// 为避免复杂的 SQL 编写,这里先定义方法签名,实现留给开发者或使用简单的全表过滤(不推荐但可行)
// 或者假设 Schedule 实体通过中间表关联了 ClassEntity
// 根据之前的 init_mongo.py,schedules 包含 classes: [{"class_id": "...", "location": "..."}]
// 临时方案:使用 Native Query 查询 JSONB
@org.springframework.data.jpa.repository.Query(value = "SELECT * FROM schedules s WHERE s.weekday = :weekday AND EXISTS (SELECT 1 FROM jsonb_array_elements(s.classes) c WHERE c->>'class_id' = :classId) ORDER BY s.period ASC", nativeQuery = true)
List<Schedule> findByWeekdayAndClassIdOrderByPeriodAsc(Integer weekday, String classId);
@org.springframework.data.jpa.repository.Query(value = "SELECT * FROM schedules s WHERE EXISTS (SELECT 1 FROM jsonb_array_elements(s.classes) c WHERE c->>'class_id' = :classId) ORDER BY s.weekday ASC, s.period ASC", nativeQuery = true)
List<Schedule> findByClassIdOrderByWeekdayAscPeriodAsc(String classId);
List<Schedule> findByTeacherPersonIdAndWeekdayOrderByPeriodAsc(String teacherPersonId, Integer weekday);
List<Schedule> findByTeacherPersonIdOrderByWeekdayAscPeriodAsc(String teacherPersonId);
}
......@@ -9,4 +9,5 @@ import java.util.Optional;
@Repository
public interface StudentRepository extends JpaRepository<Student, Long> {
Optional<Student> findByStudentId(String studentId);
Optional<Student> findByPersonId(String personId);
}
......@@ -59,18 +59,37 @@ public class JwtAuthenticationFilter implements Filter {
Long userId = claims.get("user_id", Long.class);
// 注意:JWT 中的 user_id 有时是 number 有时是 string,取决于生成逻辑
// 这里做一个兼容处理
if (userId == null && claims.get("user_id") != null) {
// 兼容处理:如果 sub 是 ID 形式,优先使用 sub
if (userId == null) {
try {
userId = Long.valueOf(claims.getSubject());
} catch (NumberFormatException e) {
// sub 不是数字 ID,可能是用户名
if (claims.get("user_id") != null) {
userId = Long.valueOf(claims.get("user_id").toString());
}
}
}
String username = claims.get("name", String.class);
if (username == null) {
username = claims.getSubject();
}
String username = claims.getSubject(); // sub is username
String role = claims.get("role", String.class);
String personId = claims.get("person_id", String.class);
String personType = claims.get("person_type", String.class);
String edition = claims.get("edition", String.class);
Integer roleLevel = claims.get("role_level", Integer.class);
UserContext userContext = UserContext.builder()
.userId(userId)
.username(username)
.role(role)
.personId(personId)
.personType(personType)
.edition(edition)
.roleLevel(roleLevel)
.build();
UserContextHolder.setContext(userContext);
......
......@@ -13,4 +13,8 @@ public class UserContext {
private Long userId;
private String username;
private String role;
private String personId;
private String personType;
private String edition;
private Integer roleLevel;
}
package com.llmfilter.edu.service;
import com.llmfilter.edu.dto.*;
import com.llmfilter.edu.security.UserContext;
public interface DashboardService {
StudentTodaySummary getStudentTodaySummary(UserContext user);
StudentWeekSummary getStudentWeekSchedule(UserContext user, Integer week);
TeacherWeekSummary getTeacherWeekSchedule(UserContext user, Integer week);
HomeroomCurrentSummary getHomeroomCurrentSummary(UserContext user);
DepartmentOverview getDepartmentOverview(UserContext user);
CampusOverview getCampusOverview(UserContext user);
}
package com.llmfilter.edu.service.impl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.llmfilter.edu.dto.*;
import com.llmfilter.edu.model.*;
import com.llmfilter.edu.repository.*;
import com.llmfilter.edu.security.UserContext;
import com.llmfilter.edu.service.DashboardService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.server.ResponseStatusException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;
@Service
@RequiredArgsConstructor
@Slf4j
public class DashboardServiceImpl implements DashboardService {
private final StudentRepository studentRepository;
private final TeacherRepository teacherRepository;
private final ScheduleRepository scheduleRepository;
private final ObjectMapper objectMapper;
private int getWeekday() {
return LocalDate.now().getDayOfWeek().getValue();
}
private Map<Integer, String> getWeekDates() {
LocalDate today = LocalDate.now();
LocalDate monday = today.with(java.time.DayOfWeek.MONDAY);
Map<Integer, String> weekDates = new HashMap<>();
for (int i = 1; i <= 7; i++) {
weekDates.put(i, monday.plusDays(i - 1).format(DateTimeFormatter.ISO_DATE));
}
return weekDates;
}
private String getLocationFromClassesJson(String classesJson, String targetClassId) {
if (classesJson == null || targetClassId == null) return "未知地点";
try {
List<Map<String, Object>> classes = objectMapper.readValue(classesJson, new TypeReference<List<Map<String, Object>>>(){});
for (Map<String, Object> cls : classes) {
if (targetClassId.equals(cls.get("class_id"))) {
return (String) cls.getOrDefault("location", "未知地点");
}
}
} catch (Exception e) {
log.error("Failed to parse classes json: {}", classesJson, e);
}
return "未知地点";
}
@Override
public StudentTodaySummary getStudentTodaySummary(UserContext user) {
String personId = user.getPersonId();
if (personId == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "未绑定学生");
}
Student student = studentRepository.findByPersonId(personId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "未找到学生档案"));
String classId = student.getClazz() != null ? student.getClazz().getClassId() : null;
List<StudentTodaySummary.TodayScheduleItem> schedules = new ArrayList<>();
if (classId != null) {
int weekday = getWeekday();
List<Schedule> dailySchedules = scheduleRepository.findByWeekdayAndClassIdOrderByPeriodAsc(weekday, classId);
for (Schedule s : dailySchedules) {
String location = getLocationFromClassesJson(s.getClassesJson(), classId);
schedules.add(StudentTodaySummary.TodayScheduleItem.builder()
.lessonId(s.getLessonId())
.period(s.getPeriod())
.courseName(s.getCourseName())
.location(location)
.build());
}
}
return StudentTodaySummary.builder()
.student(StudentTodaySummary.StudentInfo.builder()
.studentId(student.getStudentId())
.name(student.getName())
.classId(classId)
.build())
.todaySchedule(schedules)
.todayAttendance(new ArrayList<>())
.todayConduct(StudentTodaySummary.TodayConduct.builder().build())
.build();
}
@Override
public StudentWeekSummary getStudentWeekSchedule(UserContext user, Integer week) {
String personId = user.getPersonId();
if (personId == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "未绑定学生");
}
Student student = studentRepository.findByPersonId(personId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "未找到学生档案"));
String classId = student.getClazz() != null ? student.getClazz().getClassId() : null;
Map<String, List<StudentWeekSummary.WeekScheduleItem>> weekScheduleMap = new HashMap<>();
// Initialize map for 1-5 (Mon-Fri) or 1-7
for (int i = 1; i <= 7; i++) {
weekScheduleMap.put(String.valueOf(i), new ArrayList<>());
}
if (classId != null) {
List<Schedule> allSchedules = scheduleRepository.findByClassIdOrderByWeekdayAscPeriodAsc(classId);
for (Schedule s : allSchedules) {
String location = getLocationFromClassesJson(s.getClassesJson(), classId);
StudentWeekSummary.WeekScheduleItem item = StudentWeekSummary.WeekScheduleItem.builder()
.lessonId(s.getLessonId())
.period(s.getPeriod())
.courseName(s.getCourseName())
.location(location)
.teacherPersonId(s.getTeacherPersonId())
.build();
String dayKey = String.valueOf(s.getWeekday());
if (weekScheduleMap.containsKey(dayKey)) {
weekScheduleMap.get(dayKey).add(item);
}
}
}
return StudentWeekSummary.builder()
.currentWeek(week != null ? week : 1) // Default to week 1 if not provided
.student(StudentWeekSummary.StudentInfo.builder()
.studentId(student.getStudentId())
.name(student.getName())
.classId(classId)
.build())
.schedule(weekScheduleMap)
.weekDates(getWeekDates())
.build();
}
@Override
public TeacherWeekSummary getTeacherWeekSchedule(UserContext user, Integer week) {
String personId = user.getPersonId();
if (personId == null) {
throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "未绑定教师");
}
Teacher teacher = teacherRepository.findByPersonId(personId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "未找到教师档案"));
Map<String, List<TeacherWeekSummary.WeekScheduleItem>> weekScheduleMap = new HashMap<>();
for (int i = 1; i <= 7; i++) {
weekScheduleMap.put(String.valueOf(i), new ArrayList<>());
}
List<Schedule> allSchedules = scheduleRepository.findByTeacherPersonIdOrderByWeekdayAscPeriodAsc(personId);
for (Schedule s : allSchedules) {
// 解析 classesJson 获取所有班级名称和地点
List<TeacherWeekSummary.ClassInfo> classInfos = new ArrayList<>();
try {
if (s.getClassesJson() != null) {
List<Map<String, Object>> classes = objectMapper.readValue(s.getClassesJson(), new TypeReference<List<Map<String, Object>>>(){});
for (Map<String, Object> cls : classes) {
classInfos.add(TeacherWeekSummary.ClassInfo.builder()
.classId((String) cls.getOrDefault("class_id", ""))
.location((String) cls.getOrDefault("location", ""))
.build());
}
}
} catch (Exception e) {
log.error("Failed to parse classes json for teacher schedule", e);
}
TeacherWeekSummary.WeekScheduleItem item = TeacherWeekSummary.WeekScheduleItem.builder()
.lessonId(s.getLessonId())
.period(s.getPeriod())
.courseName(s.getCourseName())
.classes(classInfos)
.build();
String dayKey = String.valueOf(s.getWeekday());
if (weekScheduleMap.containsKey(dayKey)) {
weekScheduleMap.get(dayKey).add(item);
}
}
return TeacherWeekSummary.builder()
.currentWeek(week != null ? week : 1)
.teacher(TeacherWeekSummary.TeacherInfo.builder()
.teacherId(teacher.getTeacherId())
.name("Unknown") // Name logic might need adjustment if not in Teacher table
.personId(teacher.getPersonId())
.build())
.schedule(weekScheduleMap)
.weekDates(getWeekDates())
.build();
}
@Override
public HomeroomCurrentSummary getHomeroomCurrentSummary(UserContext user) {
return HomeroomCurrentSummary.builder().build();
}
@Override
public DepartmentOverview getDepartmentOverview(UserContext user) {
return DepartmentOverview.builder().build();
}
@Override
public CampusOverview getCampusOverview(UserContext user) {
return CampusOverview.builder().build();
}
}
......@@ -31,7 +31,7 @@ router = APIRouter(dependencies=[Depends(require_edition_for_mode())])
},
)
async def create_new_conversation(current_user: dict = Depends(get_current_active_user)):
conversation_id = await create_conversation(str(current_user["_id"]))
conversation_id = await create_conversation(str(current_user["id"]))
return {"id": conversation_id}
@router.get(
......@@ -44,7 +44,7 @@ async def create_new_conversation(current_user: dict = Depends(get_current_activ
},
)
async def list_conversations(current_user: dict = Depends(get_current_active_user)):
conversations = await get_user_conversations(str(current_user["_id"]))
conversations = await get_user_conversations(str(current_user["id"]))
return conversations
@router.get(
......@@ -61,7 +61,7 @@ async def get_single_conversation(
conversation_id: str = Path(..., description="对话ID"),
current_user: dict = Depends(get_current_active_user)
):
conversation = await get_conversation(conversation_id, str(current_user["_id"]))
conversation = await get_conversation(conversation_id, str(current_user["id"]))
if not conversation:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
......@@ -83,7 +83,7 @@ async def remove_conversation(
conversation_id: str = Path(..., description="对话ID"),
current_user: dict = Depends(get_current_active_user)
):
ok = await delete_conversation(conversation_id, str(current_user["_id"]))
ok = await delete_conversation(conversation_id, str(current_user["id"]))
if not ok:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="对话不存在或无权限")
return {"deleted": True, "message": "删除成功"}
......@@ -104,7 +104,7 @@ async def send_message(
current_user: dict = Depends(get_current_active_user)
):
# 检查对话是否存在
conversation = await get_conversation(conversation_id, str(current_user["_id"]))
conversation = await get_conversation(conversation_id, str(current_user["id"]))
if not conversation:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
......@@ -112,6 +112,6 @@ async def send_message(
)
# 添加消息并获取回复
result = await add_message(conversation_id, str(current_user["_id"]), message.content)
result = await add_message(conversation_id, str(current_user["id"]), message.content)
return result
from fastapi import APIRouter, Depends, Query
from pydantic import BaseModel
from typing import List, Optional, Dict, Any
from app.api.deps import require_edition_for_mode, require_role, require_binding
from app.services.dashboard import (
student_today_summary,
student_week_schedule,
teacher_week_schedule,
homeroom_current_summary,
department_overview,
campus_overview,
)
router = APIRouter(dependencies=[Depends(require_edition_for_mode())])
class StudentTodaySchedule(BaseModel):
lesson_id: Optional[str]
period: Optional[int]
course_name: Optional[str]
location: Optional[str]
class StudentTodayAttendance(BaseModel):
lesson_id: Optional[str]
status: Optional[str]
class StudentTodayConduct(BaseModel):
date: Optional[str]
metrics: Optional[Dict[str, Any]]
teacher_comment: Optional[str]
head_teacher_comment: Optional[str]
score: Optional[float]
class StudentTodaySummary(BaseModel):
student: Dict[str, Optional[str]]
today_schedule: List[StudentTodaySchedule]
today_attendance: List[StudentTodayAttendance]
today_conduct: Dict[str, Any]
class StudentWeekScheduleItem(BaseModel):
lesson_id: Optional[str] = None
period: Optional[int] = None
course_name: Optional[str] = None
location: Optional[str] = None
start_time: Optional[str] = None
end_time: Optional[str] = None
teacher_person_id: Optional[str] = None
class StudentWeekSummary(BaseModel):
student: Dict[str, Optional[str]]
current_week: int
week_dates: Dict[int, str]
schedule: Dict[str, List[StudentWeekScheduleItem]]
class TeacherWeekScheduleItem(BaseModel):
lesson_id: Optional[str] = None
period: Optional[int] = None
course_name: Optional[str] = None
classes: List[Dict[str, Optional[str]]] = []
start_time: Optional[str] = None
end_time: Optional[str] = None
class TeacherWeekSummary(BaseModel):
teacher: Dict[str, Optional[str]]
current_week: int
week_dates: Dict[int, str]
schedule: Dict[str, List[TeacherWeekScheduleItem]]
class HomeroomLesson(BaseModel):
class_id: Optional[str]
course_name: Optional[str]
location: Optional[str]
class HomeroomRate(BaseModel):
class_id: Optional[str]
present: int
total: int
rate: float
class HomeroomLeave(BaseModel):
student_id: Optional[str]
class_id: Optional[str]
reason: Optional[str]
status: Optional[str]
class DirectiveItem(BaseModel):
content: Optional[str]
created_at: Optional[str]
class HomeroomCurrentSummary(BaseModel):
current_lessons: List[HomeroomLesson]
attendance_rates: List[HomeroomRate]
leaves: List[HomeroomLeave]
directives: List[DirectiveItem]
class DepartmentTeacherRate(BaseModel):
teacher_id: Optional[str]
present_slots: int
total_slots: int
rate: float
class DepartmentStudentsAttendance(BaseModel):
total: int
present: int
absent_or_leave: int
class DepartmentOverview(BaseModel):
students_attendance: DepartmentStudentsAttendance
teacher_attendance_rates: List[DepartmentTeacherRate]
anomalies: List[Dict[str, Any]]
directives: List[Dict[str, Any]]
class CampusOverview(BaseModel):
total_students: int
present: int
leaves: int
directives: int
term_goals: List[Dict[str, Any]]
department_progress: List[Dict[str, Any]]
@router.get(
"/student/today",
summary="学生端:今日个人课表、出勤与操行",
description="需角色等级≥1且主绑定为学生;返回当天课表(共享节次按班级定位教室)、今日出勤记录与操行评语。"
, response_model=StudentTodaySummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定学生", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
)
async def student_today(current_user: dict = Depends(require_role(1)), _b: dict = Depends(require_binding("student"))) -> StudentTodaySummary:
return await student_today_summary(current_user)
@router.get(
"/student/week",
summary="学生端:周课表查询",
description="需角色等级≥1且主绑定为学生;返回指定周次(默认当前周)的完整课表,支持 'week' 参数查询下一周。",
response_model=StudentWeekSummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定学生", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
)
async def student_week(
week: Optional[int] = Query(None, description="周次(如 1, 2, ...),不传则默认为当前周"),
current_user: dict = Depends(require_role(1)),
_b: dict = Depends(require_binding("student"))
) -> StudentWeekSummary:
return await student_week_schedule(current_user, week)
@router.get(
"/teacher/week",
summary="教师端:周课表查询",
description="需角色等级≥2且主绑定为教师;返回指定周次(默认当前周)的任课课表(包含上课班级与地点)。",
response_model=TeacherWeekSummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定教师", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
)
async def teacher_week(
week: Optional[int] = Query(None, description="周次(如 1, 2, ...),不传则默认为当前周"),
current_user: dict = Depends(require_role(2)),
_b: dict = Depends(require_binding("teacher"))
) -> TeacherWeekSummary:
return await teacher_week_schedule(current_user, week)
@router.get(
"/homeroom/current",
summary="班主任端:当前节次课程与地点、出勤率、请假、指示",
description="需角色等级≥2且主绑定为教师;按 head_teacher_person_id 定位所辖班,统计当前节次的课程与地点、节次出勤率、今日请假与部门指示。"
, response_model=HomeroomCurrentSummary,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足或未绑定教师", "content": {"application/json": {"example": {"detail": "实体绑定不存在或类型不匹配"}}}},
}
)
async def homeroom_current(current_user: dict = Depends(require_role(2)), _b: dict = Depends(require_binding("teacher"))) -> HomeroomCurrentSummary:
return await homeroom_current_summary(current_user)
@router.get(
"/department/overview",
summary="中层端:教师节次出勤率、学生出勤、异常班级、指示",
description="需角色等级≥3;按今日工作日统计每位教师的节次出勤率、全校学生出勤聚合、异常班级(缺勤+请假占比>0.3)与近期部门/校园指示。"
, response_model=DepartmentOverview,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
}
)
async def department(current_user: dict = Depends(require_role(3))) -> DepartmentOverview:
return await department_overview(current_user)
@router.get(
"/campus/overview",
summary="校级端:校园整体总览",
description="需角色等级≥4;返回学生总数、今日出勤、请假与指示数量等宏观数据。"
, response_model=CampusOverview,
responses={
401: {"description": "未认证", "content": {"application/json": {"example": {"detail": "无效的认证凭据"}}}},
403: {"description": "权限不足", "content": {"application/json": {"example": {"detail": "权限不足"}}}},
}
)
async def campus(current_user: dict = Depends(require_role(4))) -> CampusOverview:
return await campus_overview(current_user)
from fastapi import APIRouter
from app.api.v1 import conversation, admin, dashboard
from app.api.v1 import conversation, admin
api_router = APIRouter()
# 注册各模块路由
api_router.include_router(conversation.router, prefix="/conversations", tags=["对话"])
api_router.include_router(admin.router, prefix="/admin", tags=["管理员"])
\ No newline at end of file
api_router.include_router(dashboard.router, prefix="/dashboard", tags=["仪表盘"])
\ No newline at end of file
......@@ -14,7 +14,7 @@ async def create_conversation(user_id: str) -> str:
- 新建对话的字符串 ID
"""
conversation = {
"user_id": ObjectId(user_id),
"user_id": user_id,
"title": f"新会话 {datetime.now().strftime('%m-%d %H:%M')}",
"messages": [],
"created_at": datetime.now(),
......@@ -35,7 +35,7 @@ async def get_conversation(conversation_id: str, user_id: str) -> Optional[Dict]
"""
conversation = await db.db.conversations.find_one({
"_id": ObjectId(conversation_id),
"user_id": ObjectId(user_id)
"user_id": user_id
})
if not conversation:
......@@ -97,7 +97,7 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[
# 预取对话用于判断是否首次消息与标题更新
conversation = await db.db.conversations.find_one({
"_id": ObjectId(conversation_id),
"user_id": ObjectId(user_id)
"user_id": user_id
})
if not conversation:
raise ValueError("对话不存在或无权限")
......@@ -168,7 +168,7 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[
# 创建敏感词记录(包含详细信息)
sensitive_record = {
"user_id": ObjectId(user_id),
"user_id": user_id,
"conversation_id": ObjectId(conversation_id),
"message_content": content,
"sensitive_words_found": detailed_words,
......@@ -246,7 +246,7 @@ async def get_user_conversations(user_id: str) -> List[Dict]:
- 对话字典列表(每项仅含最近一条 messages)
"""
conversations: List[Dict[str, Any]] = []
cursor = db.db.conversations.find({"user_id": ObjectId(user_id)}).sort("updated_at", -1)
cursor = db.db.conversations.find({"user_id": user_id}).sort("updated_at", -1)
async for c in cursor:
c_id = str(c["_id"])
......@@ -293,7 +293,7 @@ async def delete_conversation(conversation_id: str, user_id: str) -> bool:
返回:
- 是否删除成功(True/False)
"""
filter_cond = {"_id": ObjectId(conversation_id), "user_id": ObjectId(user_id)}
filter_cond = {"_id": ObjectId(conversation_id), "user_id": user_id}
del_res = await db.db.conversations.delete_one(filter_cond)
if del_res.deleted_count != 1:
return False
......
This diff is collapsed.
......@@ -8,3 +8,4 @@ python-multipart==0.0.6
httpx==0.25.0
python-dotenv==1.0.0
PyJWT==2.8.0
psycopg2-binary==2.9.9
This diff is collapsed.
import asyncio
import os
from datetime import datetime
from motor.motor_asyncio import AsyncIOMotorClient
# 配置
MONGODB_URL = os.getenv("MONGODB_URL", "mongodb://localhost:27017")
DB_NAME = os.getenv("DB_NAME", "llm_filter_db")
async def init_mongo():
print(f"Connecting to MongoDB: {MONGODB_URL} ...")
client = AsyncIOMotorClient(MONGODB_URL)
db = client[DB_NAME]
# 清空集合 (彻底清空旧业务数据)
# 只保留 LLM 相关的集合
collections = ["sensitive_words", "filter_logs", "audit_trails",
"attendance", "conduct", "leaves", "directives", "conversations"] # 顺便清理掉旧的
for col in collections:
await db[col].drop()
print("Cleaned up MongoDB collections.")
# 1. 初始化敏感词库 (Sensitive Words)
print("Initializing sensitive words library...")
sensitive_words = [
{"word": "暴力", "category": "violence", "level": "high"},
{"word": "赌博", "category": "gambling", "level": "high"},
{"word": "作弊", "category": "academic_misconduct", "level": "medium"},
{"word": "代写", "category": "academic_misconduct", "level": "medium"},
{"word": "色情", "category": "pornography", "level": "high"},
{"word": "自杀", "category": "self_harm", "level": "critical"},
{"word": "约架", "category": "violence", "level": "high"}
]
for sw in sensitive_words:
await db.sensitive_words.insert_one({
"word": sw["word"],
"category": sw["category"],
"level": sw["level"],
"created_at": datetime.now(),
"updated_at": datetime.now()
})
print(f"Inserted {len(sensitive_words)} sensitive words.")
print("MongoDB initialization completed successfully! (LLM Filter Data Only)")
client.close()
if __name__ == "__main__":
asyncio.run(init_mongo())
-- 创建 Users 表 (如果不存在)
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
role VARCHAR(20) NOT NULL,
role_level INTEGER DEFAULT 1,
edition VARCHAR(20) DEFAULT 'edu',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Classes 表
CREATE TABLE IF NOT EXISTS classes (
class_id VARCHAR(50) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
major VARCHAR(50),
grade INTEGER,
head_teacher_person_id VARCHAR(50),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Persons 表
CREATE TABLE IF NOT EXISTS persons (
person_id VARCHAR(50) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
type VARCHAR(20) NOT NULL, -- student, teacher
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Bindings 表 (User -> Person)
CREATE TABLE IF NOT EXISTS bindings (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
person_id VARCHAR(50) REFERENCES persons(person_id) ON DELETE CASCADE,
type VARCHAR(20),
"primary" BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Students 表
CREATE TABLE IF NOT EXISTS students (
student_id VARCHAR(50) PRIMARY KEY,
name VARCHAR(100) NOT NULL,
gender VARCHAR(10),
class_id VARCHAR(50) REFERENCES classes(class_id),
person_id VARCHAR(50) REFERENCES persons(person_id),
status VARCHAR(20) DEFAULT 'active',
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Teachers 表
CREATE TABLE IF NOT EXISTS teachers (
teacher_id VARCHAR(50) PRIMARY KEY,
person_id VARCHAR(50) REFERENCES persons(person_id),
department VARCHAR(50),
roles VARCHAR(100), -- comma separated roles
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Schedules 表
CREATE TABLE IF NOT EXISTS schedules (
id SERIAL PRIMARY KEY,
lesson_id VARCHAR(50),
weekday INTEGER,
period INTEGER,
course_name VARCHAR(100),
teacher_person_id VARCHAR(50),
classes JSONB, -- stores array of {class_id, location}
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Attendance 表 (考勤)
CREATE TABLE IF NOT EXISTS attendance (
id SERIAL PRIMARY KEY,
student_id VARCHAR(50) REFERENCES students(student_id),
class_id VARCHAR(50) REFERENCES classes(class_id),
date DATE,
lesson_id VARCHAR(50),
status VARCHAR(20),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Conduct 表 (操行)
CREATE TABLE IF NOT EXISTS conduct (
id SERIAL PRIMARY KEY,
student_id VARCHAR(50) REFERENCES students(student_id),
date DATE,
metrics JSONB, -- {"discipline": 5, "hygiene": 4}
score INTEGER,
teacher_comment TEXT,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 创建 Directives 表 (通知/指示)
CREATE TABLE IF NOT EXISTS directives (
id SERIAL PRIMARY KEY,
level VARCHAR(20), -- campus, grade, department
content TEXT,
issuer_id VARCHAR(50) REFERENCES persons(person_id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- 清理旧数据
TRUNCATE TABLE users RESTART IDENTITY CASCADE;
TRUNCATE TABLE classes RESTART IDENTITY CASCADE;
TRUNCATE TABLE persons RESTART IDENTITY CASCADE;
TRUNCATE TABLE bindings RESTART IDENTITY CASCADE;
TRUNCATE TABLE students RESTART IDENTITY CASCADE;
TRUNCATE TABLE teachers RESTART IDENTITY CASCADE;
TRUNCATE TABLE schedules RESTART IDENTITY CASCADE;
TRUNCATE TABLE attendance RESTART IDENTITY CASCADE;
TRUNCATE TABLE conduct RESTART IDENTITY CASCADE;
TRUNCATE TABLE directives RESTART IDENTITY CASCADE;
-- 插入 Edu 版用户 (密码均为 password123 的 bcrypt 哈希)
-- $2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm
-- 1. 学生 (对应3个班级)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('student_101', 'stu101@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'user', 1, 'edu', NOW(), NOW()), -- ID: 1, Class 1
('student_102', 'stu102@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'user', 1, 'edu', NOW(), NOW()), -- ID: 2, Class 2
('student_103', 'stu103@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'user', 1, 'edu', NOW(), NOW()); -- ID: 3, Class 3
-- 2. 班主任 (对应3个班级)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('teacher_101', 'tea101@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'manager', 2, 'edu', NOW(), NOW()), -- ID: 4, Class 1 Homeroom
('teacher_102', 'tea102@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'manager', 2, 'edu', NOW(), NOW()), -- ID: 5, Class 2 Homeroom
('teacher_103', 'tea103@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'manager', 2, 'edu', NOW(), NOW()); -- ID: 6, Class 3 Homeroom
-- 3. 教务/中层 (2位)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('leader_academic', 'academic@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'leader', 3, 'edu', NOW(), NOW()), -- ID: 7, 教务主任
('leader_grade', 'grade@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'leader', 3, 'edu', NOW(), NOW()); -- ID: 8, 年级组长
-- 4. 校级领导 (1位)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('master_principal', 'principal@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'master', 4, 'edu', NOW(), NOW()); -- ID: 9, 校长
-- 5. 系统管理员 (1位)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('admin_edu', 'admin_edu@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'admin', 5, 'edu', NOW(), NOW()); -- ID: 10
-- 6. Biz 版保留账号 (2位)
INSERT INTO users (username, email, password, role, role_level, edition, created_at, updated_at) VALUES
('staff_biz', 'staff_biz@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'user', 1, 'biz', NOW(), NOW()), -- ID: 11
('manager_biz', 'manager_biz@example.com', '$2b$12$P7tzdKfHMVJd43wyX1Z1GO0W9TMf.oNo2lqbPDTXLH/dfRpB4SgMm', 'manager', 2, 'biz', NOW(), NOW()); -- ID: 12
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment