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/;
......
......@@ -8,11 +8,15 @@ import (
)
type AuthService struct {
repo *repository.UserRepository
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,13 +33,17 @@ 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,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
"iat": time.Now().Unix(),
"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(),
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
......
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) {
userId = Long.valueOf(claims.get("user_id").toString());
// 兼容处理:如果 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=["管理员"])
api_router.include_router(dashboard.router, prefix="/dashboard", tags=["仪表盘"])
\ No newline at end of file
api_router.include_router(admin.router, prefix="/admin", 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
......
from datetime import datetime, timedelta
from typing import Dict, List, Any
from bson import ObjectId
from fastapi import HTTPException
from app.db.mongodb import db
from app.core.config import settings
async def _today_iso() -> str:
return datetime.now().date().isoformat()
async def _weekday() -> int:
return datetime.now().isoweekday()
async def _get_current_week() -> int:
try:
start_date = datetime.strptime(settings.TERM_START_DATE, "%Y-%m-%d").date()
today = datetime.now().date()
delta = today - start_date
if delta.days < 0:
return 1
return (delta.days // 7) + 1
except Exception:
return 1
def _is_week_valid(week: int, week_range: str) -> bool:
if not week_range:
return True
try:
parts = week_range.split(',')
for part in parts:
if '-' in part:
start, end = map(int, part.split('-'))
if start <= week <= end:
return True
else:
if int(part) == week:
return True
except:
pass
return False
async def _current_period() -> int:
h = datetime.now().hour
if h < 10:
return 1
if h < 11:
return 2
if h < 12:
return 3
return 4
async def _get_primary_binding(account_id: ObjectId) -> Dict[str, Any]:
b = await db.db.bindings.find_one({"account_id": account_id, "primary": True})
if not b:
raise HTTPException(status_code=404, detail="未绑定人物")
return b
async def _get_student_entity(account_id: ObjectId, binding: Dict[str, Any]) -> Dict[str, Any]:
pid = binding.get("person_id")
if isinstance(pid, str) and ObjectId.is_valid(pid):
pid = ObjectId(pid)
s = await db.db.students.find_one({"person_id": pid})
if s:
return s
raise HTTPException(status_code=404, detail="未找到学生实体")
async def _get_teacher_entity(account_id: ObjectId, binding: Dict[str, Any]) -> Dict[str, Any]:
pid = binding.get("person_id")
if isinstance(pid, str) and ObjectId.is_valid(pid):
pid = ObjectId(pid)
t = await db.db.teachers.find_one({"person_id": pid})
if t:
return t
raise HTTPException(status_code=404, detail="未找到教师实体")
async def _get_student_by_user(user_id: ObjectId) -> Dict[str, Any]:
b = await _get_primary_binding(user_id)
if b.get("type") != "student":
raise HTTPException(status_code=404, detail="当前绑定非学生")
return await _get_student_entity(user_id, b)
async def student_today_summary(current_user: Dict[str, Any]) -> Dict[str, Any]:
today = await _today_iso()
weekday = await _weekday()
user_id_str = str(current_user["id"])
if not ObjectId.is_valid(user_id_str):
raise HTTPException(status_code=400, detail="无效的用户ID格式")
student = await _get_student_by_user(ObjectId(user_id_str))
class_id = student.get("class_id") if student else None
schedules: List[Dict[str, Any]] = []
if class_id:
cursor = db.db.schedules.find({"weekday": weekday, "classes.class_id": class_id}).sort("period", 1)
async for doc in cursor:
loc = None
for c in doc.get("classes", []):
if c.get("class_id") == class_id:
loc = c.get("location")
break
schedules.append({
"lesson_id": doc.get("lesson_id"),
"period": doc.get("period"),
"course_name": doc.get("course_name"),
"location": loc,
})
attendance = []
if student:
cursor = db.db.attendance.find({"student_id": student.get("student_id"), "date": today})
async for a in cursor:
attendance.append({
"lesson_id": a.get("lesson_id"),
"status": a.get("status"),
})
conduct_doc = await db.db.conduct.find_one({"student_id": student.get("student_id") if student else None, "date": today})
conduct = {}
if conduct_doc:
conduct = {
"date": conduct_doc.get("date"),
"metrics": conduct_doc.get("metrics"),
"teacher_comment": conduct_doc.get("teacher_comment"),
"head_teacher_comment": conduct_doc.get("head_teacher_comment"),
"score": conduct_doc.get("score"),
}
return {
"student": {
"student_id": student.get("student_id") if student else None,
"name": student.get("name") if student else None,
"class_id": class_id,
},
"today_schedule": schedules,
"today_attendance": attendance,
"today_conduct": conduct,
}
async def student_week_schedule(current_user: Dict[str, Any], week: int = None) -> Dict[str, Any]:
if week is None:
week = await _get_current_week()
user_id_str = str(current_user["id"])
if not ObjectId.is_valid(user_id_str):
raise HTTPException(status_code=400, detail="无效的用户ID格式")
student = await _get_student_by_user(ObjectId(user_id_str))
class_id = student.get("class_id") if student else None
try:
start_date = datetime.strptime(settings.TERM_START_DATE, "%Y-%m-%d").date()
monday_of_week = start_date + timedelta(weeks=week-1)
except:
monday_of_week = datetime.now().date()
week_dates = {}
for i in range(1, 8):
d = monday_of_week + timedelta(days=i-1)
week_dates[i] = d.isoformat()
schedules_by_day = {str(i): [] for i in range(1, 8)}
if class_id:
cursor = db.db.schedules.find({"classes.class_id": class_id}).sort("period", 1)
async for doc in cursor:
w_range = doc.get("week_range", "")
if not _is_week_valid(week, w_range):
continue
wd = doc.get("weekday")
loc = None
for c in doc.get("classes", []):
if c.get("class_id") == class_id:
loc = c.get("location")
break
item = {
"lesson_id": doc.get("lesson_id"),
"period": doc.get("period"),
"course_name": doc.get("course_name"),
"location": loc,
"start_time": doc.get("start_time"),
"end_time": doc.get("end_time"),
"teacher_person_id": str(doc.get("teacher_person_id")) if doc.get("teacher_person_id") else None
}
if str(wd) in schedules_by_day:
schedules_by_day[str(wd)].append(item)
return {
"student": {
"student_id": student.get("student_id") if student else None,
"name": student.get("name") if student else None,
"class_id": class_id,
},
"current_week": week,
"week_dates": week_dates,
"schedule": schedules_by_day
}
async def teacher_week_schedule(current_user: Dict[str, Any], week: int = None) -> Dict[str, Any]:
if week is None:
week = await _get_current_week()
user_id_str = str(current_user["id"])
if not ObjectId.is_valid(user_id_str):
raise HTTPException(status_code=400, detail="无效的用户ID格式")
user_id = ObjectId(user_id_str)
binding = await _get_primary_binding(user_id)
if binding.get("type") != "teacher":
raise HTTPException(status_code=403, detail="当前绑定非教师")
teacher = await _get_teacher_entity(user_id, binding)
teacher_person_id = teacher.get("person_id")
# 确保类型匹配
if isinstance(teacher_person_id, str) and ObjectId.is_valid(teacher_person_id):
teacher_person_id = ObjectId(teacher_person_id)
try:
start_date = datetime.strptime(settings.TERM_START_DATE, "%Y-%m-%d").date()
monday_of_week = start_date + timedelta(weeks=week-1)
except:
monday_of_week = datetime.now().date()
week_dates = {}
for i in range(1, 8):
d = monday_of_week + timedelta(days=i-1)
week_dates[i] = d.isoformat()
schedules_by_day = {str(i): [] for i in range(1, 8)}
# 查询该教师的所有课程
# 注意:teacher_person_id 在 schedules 中可能是 ObjectId 也可能是字符串,视具体数据而定
# 为稳妥起见,如果 teacher_person_id 是 ObjectId,也可以尝试转字符串匹配,或者由数据库层面保证一致性
# 这里我们使用 $in 查询来兼容
query_ids = [teacher_person_id]
if isinstance(teacher_person_id, ObjectId):
query_ids.append(str(teacher_person_id))
cursor = db.db.schedules.find({"teacher_person_id": {"$in": query_ids}}).sort("period", 1)
async for doc in cursor:
w_range = doc.get("week_range", "")
if not _is_week_valid(week, w_range):
continue
wd = doc.get("weekday")
# 提取班级和地点信息
classes_info = []
for c in doc.get("classes", []):
classes_info.append({
"class_id": c.get("class_id"),
"location": c.get("location")
})
item = {
"lesson_id": doc.get("lesson_id"),
"period": doc.get("period"),
"course_name": doc.get("course_name"),
"classes": classes_info,
"start_time": doc.get("start_time"),
"end_time": doc.get("end_time")
}
if str(wd) in schedules_by_day:
schedules_by_day[str(wd)].append(item)
return {
"teacher": {
"teacher_id": teacher.get("teacher_id"),
"name": teacher.get("name"), # 注意:teacher表可能没有name,通常在person表
"person_id": str(teacher.get("person_id"))
},
"current_week": week,
"week_dates": week_dates,
"schedule": schedules_by_day
}
async def homeroom_current_summary(current_user: Dict[str, Any]) -> Dict[str, Any]:
today = await _today_iso()
weekday = await _weekday()
period = await _current_period()
current_lesson_id = f"W{weekday}-P{period}"
# 获取当前用户的教师绑定信息
user_id_str = str(current_user["id"])
if not ObjectId.is_valid(user_id_str):
raise HTTPException(status_code=400, detail="无效的用户ID格式")
user_id = ObjectId(user_id_str)
binding = await _get_primary_binding(user_id)
if binding.get("type") != "teacher":
raise HTTPException(status_code=403, detail="当前绑定非教师")
teacher = await _get_teacher_entity(user_id, binding)
teacher_person_id = teacher.get("person_id")
if isinstance(teacher_person_id, str) and ObjectId.is_valid(teacher_person_id):
teacher_person_id = ObjectId(teacher_person_id)
classes = []
# 使用 person_id 查询班级
cursor = db.db.classes.find({"head_teacher_person_id": teacher_person_id})
async for c in cursor:
classes.append(c)
class_ids = [c.get("class_id") for c in classes]
lessons: List[Dict[str, Any]] = []
for cid in class_ids:
doc = await db.db.schedules.find_one({"weekday": weekday, "period": period, "classes.class_id": cid})
if doc:
loc = None
for cc in doc.get("classes", []):
if cc.get("class_id") == cid:
loc = cc.get("location")
break
lessons.append({
"class_id": cid,
"course_name": doc.get("course_name"),
"location": loc,
})
rates: List[Dict[str, Any]] = []
for c in classes:
total = c.get("students_count", 0)
present = await db.db.attendance.count_documents({
"class_id": c.get("class_id"),
"date": today,
"lesson_id": current_lesson_id,
"status": "出勤",
})
rate = (present / total) if total else 0
rates.append({"class_id": c.get("class_id"), "present": present, "total": total, "rate": rate})
leaves: List[Dict[str, Any]] = []
for cid in class_ids:
cursor = db.db.leaves.find({"class_id": cid, "from_date": {"$lte": today}, "to_date": {"$gte": today}})
async for l in cursor:
leaves.append({
"student_id": l.get("student_id"),
"class_id": cid,
"reason": l.get("reason"),
"status": l.get("status"),
})
directives: List[Dict[str, Any]] = []
cursor = db.db.directives.find({"level": "department"}).sort("created_at", -1).limit(5)
async for d in cursor:
directives.append({
"content": d.get("content"),
"created_at": d.get("created_at").isoformat() if isinstance(d.get("created_at"), datetime) else d.get("created_at"),
})
return {
"current_lessons": lessons,
"attendance_rates": rates,
"leaves": leaves,
"directives": directives,
}
async def department_overview(current_user: Dict[str, Any]) -> Dict[str, Any]:
today = await _today_iso()
weekday = await _weekday()
total_students = await db.db.students.count_documents({})
present_today = await db.db.attendance.count_documents({"date": today, "status": "出勤"})
absent_or_leave_today = await db.db.attendance.count_documents({"date": today, "status": {"$in": ["缺勤", "请假"]}})
anomalies: List[Dict[str, Any]] = []
cursor_classes = db.db.classes.find({})
async for c in cursor_classes:
cid = c.get("class_id")
total = c.get("students_count", 0)
abnormal = await db.db.attendance.count_documents({
"class_id": cid,
"date": today,
"status": {"$in": ["缺勤", "请假"]}
})
rate = (abnormal / total) if total else 0
if rate > 0.3:
anomalies.append({"class_id": cid, "anomaly_rate": rate})
teacher_stats: Dict[str, Dict[str, int]] = {}
cursor_sched = db.db.schedules.find({"weekday": weekday})
async for s in cursor_sched:
tid = s.get("teacher_id")
if not tid:
continue
key = str(tid)
st = teacher_stats.get(key)
if not st:
st = {"present_slots": 0, "total_slots": 0}
teacher_stats[key] = st
st["total_slots"] += 1
ratios: List[float] = []
for cc in s.get("classes", []):
cid = cc.get("class_id")
cls = await db.db.classes.find_one({"class_id": cid})
total = cls.get("students_count", 0) if cls else 0
present = await db.db.attendance.count_documents({
"class_id": cid,
"date": today,
"lesson_id": s.get("lesson_id"),
"status": "出勤",
})
ratios.append((present / total) if total else 0)
avg_ratio = (sum(ratios) / len(ratios)) if ratios else 0
if avg_ratio >= 0.7:
st["present_slots"] += 1
teacher_attendance_rates: List[Dict[str, Any]] = []
for key, st in teacher_stats.items():
total_slots = st.get("total_slots", 0)
present_slots = st.get("present_slots", 0)
rate = (present_slots / total_slots) if total_slots else 0
teacher_attendance_rates.append({
"teacher_id": key,
"present_slots": present_slots,
"total_slots": total_slots,
"rate": rate,
})
directives: List[Dict[str, Any]] = []
cursor_dir = db.db.directives.find({"level": {"$in": ["department", "campus"]}}).sort("created_at", -1).limit(5)
async for d in cursor_dir:
directives.append({
"level": d.get("level"),
"content": d.get("content"),
"created_at": d.get("created_at"),
})
return {
"students_attendance": {
"total": total_students,
"present": present_today,
"absent_or_leave": absent_or_leave_today,
},
"teacher_attendance_rates": teacher_attendance_rates,
"anomalies": anomalies,
"directives": directives,
}
async def campus_overview(current_user: Dict[str, Any]) -> Dict[str, Any]:
today = await _today_iso()
total_students = await db.db.students.count_documents({})
present = await db.db.attendance.count_documents({"date": today, "status": "出勤"})
leaves = await db.db.leaves.count_documents({"from_date": {"$lte": today}, "to_date": {"$gte": today}})
directives_count = await db.db.directives.count_documents({})
return {
"total_students": total_students,
"present": present,
"leaves": leaves,
"directives": directives_count,
"term_goals": [],
"department_progress": [],
}
\ No newline at end of file
......@@ -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
import asyncio
import os
import random
import json
from datetime import datetime, timedelta
import psycopg2
from psycopg2.extras import Json
# 配置
DB_HOST = os.getenv("DB_HOST", "postgres")
DB_USER = os.getenv("DB_USER", "admin")
DB_PASSWORD = os.getenv("DB_PASSWORD", "password")
DB_NAME = os.getenv("DB_NAME", "llm_filter_db")
DB_PORT = os.getenv("DB_PORT", "5432")
# 用户映射 (对应 init_postgres.sql)
PG_USERS = {
"student_101": 1,
"student_102": 2,
"student_103": 3,
"teacher_101": 4,
"teacher_102": 5,
"teacher_103": 6,
"leader_academic": 7,
"leader_grade": 8,
"master_principal": 9,
"admin_edu": 10,
"staff_biz": 11,
"manager_biz": 12
}
def get_db_connection():
return psycopg2.connect(
host=DB_HOST,
user=DB_USER,
password=DB_PASSWORD,
dbname=DB_NAME,
port=DB_PORT
)
def init_postgres_data():
print(f"Connecting to PostgreSQL: {DB_HOST}:{DB_PORT}/{DB_NAME} ...")
conn = get_db_connection()
cur = conn.cursor()
try:
# 1. 清理旧业务数据 (保留 users 表)
# 注意表名与 Java Entity @Table(name=...) 对应
tables = ["attendance", "conduct", "directives", "schedules", "students", "teachers", "classes", "persons", "bindings"]
for table in tables:
# 检查表是否存在
cur.execute(f"SELECT to_regclass('public.{table}')")
if cur.fetchone()[0]:
print(f"Truncating table {table}...")
cur.execute(f"TRUNCATE TABLE {table} RESTART IDENTITY CASCADE")
# 2. 创建班级 (Classes)
print("Creating classes...")
class_ids = {} # idx -> class_id (string)
classes_info = [
{"name": "软件2301班", "grade": 2023, "major": "软件工程"},
{"name": "软件2302班", "grade": 2023, "major": "软件工程"},
{"name": "网络2301班", "grade": 2023, "major": "网络技术"}
]
for i, info in enumerate(classes_info):
cid = f"CLASS-2023-{i+1:02d}"
class_ids[i+1] = cid
# Java Entity: ClassEntity (id, classId, name, major, grade, headTeacherPersonId)
cur.execute("""
INSERT INTO classes (class_id, name, major, grade, created_at, updated_at)
VALUES (%s, %s, %s, %s, NOW(), NOW())
""", (cid, info["name"], info["major"], info["grade"]))
print(f"Created {len(class_ids)} classes.")
# 3. 创建人物、绑定、实体
print("Creating persons, bindings and entities...")
person_ids = {} # username -> person_id
# --- Students ---
students_data = [
{"user": "student_101", "name": "张一", "class_idx": 1, "sid": "S20230101"},
{"user": "student_102", "name": "李二", "class_idx": 2, "sid": "S20230102"},
{"user": "student_103", "name": "王三", "class_idx": 3, "sid": "S20230103"}
]
for s in students_data:
pid = f"PID-{s['user'].upper()}" # 构造一个唯一ID
person_ids[s["user"]] = pid
# Person
cur.execute("""
INSERT INTO persons (person_id, name, type, created_at, updated_at)
VALUES (%s, %s, 'student', NOW(), NOW())
""", (pid, s["name"]))
# Binding
user_id = PG_USERS[s["user"]]
cur.execute("""
INSERT INTO bindings (user_id, person_id, type, "primary", created_at, updated_at)
VALUES (%s, %s, 'student', true, NOW(), NOW())
""", (user_id, pid))
# Student
cid = class_ids[s["class_idx"]]
gender = random.choice(["男", "女"])
cur.execute("""
INSERT INTO students (student_id, name, gender, class_id, person_id, created_at, updated_at)
VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
""", (s["sid"], s["name"], gender, cid, pid))
# --- Teachers ---
teachers_data = [
{"user": "teacher_101", "name": "赵老师", "dept": "软件教研室", "class_idx": 1, "tid": "T001"},
{"user": "teacher_102", "name": "钱老师", "dept": "基础教研室", "class_idx": 2, "tid": "T002"},
{"user": "teacher_103", "name": "孙老师", "dept": "网络教研室", "class_idx": 3, "tid": "T003"}
]
for t in teachers_data:
pid = f"PID-{t['user'].upper()}"
person_ids[t["user"]] = pid
# Person
cur.execute("""
INSERT INTO persons (person_id, name, type, created_at, updated_at)
VALUES (%s, %s, 'teacher', NOW(), NOW())
""", (pid, t["name"]))
# Binding
user_id = PG_USERS[t["user"]]
cur.execute("""
INSERT INTO bindings (user_id, person_id, type, "primary", created_at, updated_at)
VALUES (%s, %s, 'teacher', true, NOW(), NOW())
""", (user_id, pid))
# Teacher
cur.execute("""
INSERT INTO teachers (teacher_id, person_id, department, roles, created_at, updated_at)
VALUES (%s, %s, %s, 'teacher,homeroom', NOW(), NOW())
""", (t["tid"], pid, t["dept"]))
# Update Class Head Teacher
cid = class_ids[t["class_idx"]]
cur.execute("""
UPDATE classes SET head_teacher_person_id = %s WHERE class_id = %s
""", (pid, cid))
# --- Leaders ---
leaders_data = [
{"user": "leader_academic", "name": "周主任", "dept": "教务处", "role": "cadre", "tid": "L001"},
{"user": "leader_grade", "name": "吴组长", "dept": "学生处", "role": "cadre", "tid": "L002"},
{"user": "master_principal", "name": "郑校长", "dept": "校长室", "role": "master", "tid": "M001"}
]
for l in leaders_data:
pid = f"PID-{l['user'].upper()}"
person_ids[l["user"]] = pid
# Person
cur.execute("""
INSERT INTO persons (person_id, name, type, created_at, updated_at)
VALUES (%s, %s, 'teacher', NOW(), NOW())
""", (pid, l["name"]))
# Binding
user_id = PG_USERS[l["user"]]
cur.execute("""
INSERT INTO bindings (user_id, person_id, type, "primary", created_at, updated_at)
VALUES (%s, %s, 'teacher', true, NOW(), NOW())
""", (user_id, pid))
# Teacher
roles = f"teacher,{l['role']}"
cur.execute("""
INSERT INTO teachers (teacher_id, person_id, department, roles, created_at, updated_at)
VALUES (%s, %s, %s, %s, NOW(), NOW())
""", (l["tid"], pid, l["dept"], roles))
# 4. 创建课表 (Schedules)
print("Creating schedules...")
weekdays = [1, 2, 3, 4, 5]
periods = [1, 2, 3, 4, 5, 6, 7, 8]
courses_pool = ["Java程序设计", "MySQL数据库", "Web前端开发", "计算机网络", "Linux操作系统", "Python编程", "职场英语", "体育与健康", "毛概", "心理健康"]
for class_idx, cid in class_ids.items():
homeroom_user = f"teacher_10{class_idx}"
homeroom_pid = person_ids[homeroom_user]
if class_idx == 1:
main_course = "Java程序设计"
elif class_idx == 2:
main_course = "Web前端开发"
else:
main_course = "计算机网络"
for wd in weekdays:
for p in periods:
teacher_pid = None
course = None
location = f"实训楼{200+class_idx}"
if p == 1:
course = main_course
teacher_pid = homeroom_pid
elif wd == 1 and p == 8:
course = "班会"
teacher_pid = homeroom_pid
else:
course = random.choice(courses_pool)
if random.random() < 0.1:
teacher_pid = person_ids["leader_academic"]
lesson_id = f"C{class_idx}-W{wd}-P{p}"
# Construct classes JSON
# Java entity uses 'classes' column (jsonb)
classes_json = [{"class_id": cid, "location": location}]
cur.execute("""
INSERT INTO schedules (lesson_id, weekday, period, course_name, teacher_person_id, classes, created_at, updated_at)
VALUES (%s, %s, %s, %s, %s, %s, NOW(), NOW())
""", (lesson_id, wd, p, course, teacher_pid, Json(classes_json)))
# 5. 创建出勤与操行 (Attendance & Conduct) - 迁移自 MongoDB
print("Creating attendance and conduct (Migrated to Postgres)...")
today = datetime.now().date()
for s in students_data:
cid = class_ids[s["class_idx"]]
sid = s["sid"]
class_idx = s["class_idx"]
# 出勤
cur.execute("""
INSERT INTO attendance (student_id, class_id, date, lesson_id, status, created_at, updated_at)
VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
""", (sid, cid, today, f"C{class_idx}-W1-P1", random.choice(["出勤", "出勤", "出勤", "迟到"])))
# 操行
metrics = {"discipline": random.randint(3,5), "hygiene": random.randint(3,5)}
score = random.randint(8, 10)
comment = random.choice(["表现良好", "积极发言", "需注意纪律"])
cur.execute("""
INSERT INTO conduct (student_id, date, metrics, score, teacher_comment, created_at, updated_at)
VALUES (%s, %s, %s, %s, %s, NOW(), NOW())
""", (sid, today, Json(metrics), score, comment))
# 6. 创建指示 (Directives) - 迁移自 MongoDB
print("Creating directives (Migrated to Postgres)...")
directives_data = [
{"level": "campus", "content": "【重要】关于举办首届'软件杯'程序设计大赛的通知", "issuer": "master_principal"},
{"level": "grade", "content": "23级软件专业实训周安排", "issuer": "leader_grade"},
{"level": "department", "content": "软件教研室关于开展Java课程教学研讨", "issuer": "leader_academic"}
]
for d in directives_data:
issuer_pid = person_ids[d["issuer"]]
cur.execute("""
INSERT INTO directives (level, content, issuer_id, created_at, updated_at)
VALUES (%s, %s, %s, NOW(), NOW())
""", (d["level"], d["content"], issuer_pid))
conn.commit()
print("PostgreSQL initialization completed successfully!")
except Exception as e:
conn.rollback()
print(f"Error initializing PostgreSQL: {e}")
raise e
finally:
cur.close()
conn.close()
if __name__ == "__main__":
init_postgres_data()
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