Merge remote-tracking branch 'origin/master'

This commit is contained in:
Claude Opus 4.6 2026-03-17 10:29:50 +08:00
commit d0439bc8b5
37 changed files with 1639 additions and 104 deletions

View File

@ -3,15 +3,24 @@ package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.ClassMapper;
import com.reading.platform.common.mapper.CourseMapper;
import com.reading.platform.common.mapper.StudentMapper;
import com.reading.platform.common.mapper.TeacherMapper;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.response.ClassResponse;
import com.reading.platform.dto.response.CourseResponse;
import com.reading.platform.dto.response.StudentResponse;
import com.reading.platform.dto.response.TeacherResponse;
import com.reading.platform.entity.ClassTeacher;
import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.Course;
import com.reading.platform.entity.Student;
import com.reading.platform.entity.Teacher;
import com.reading.platform.service.ClassService;
import com.reading.platform.service.CourseService;
import com.reading.platform.service.StudentService;
import com.reading.platform.service.TeacherService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
@ -20,6 +29,7 @@ import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Tag(name = "Teacher - Course", description = "Course APIs for Teacher")
@RestController
@ -29,8 +39,12 @@ public class TeacherCourseController {
private final CourseService courseService;
private final ClassService classService;
private final StudentService studentService;
private final TeacherService teacherService;
private final CourseMapper courseMapper;
private final ClassMapper classMapper;
private final StudentMapper studentMapper;
private final TeacherMapper teacherMapper;
@Operation(summary = "Get teacher's classes")
@GetMapping("/classes")
@ -69,39 +83,57 @@ public class TeacherCourseController {
@Operation(summary = "Get all students of teacher")
@GetMapping("/students")
public Result<Map<String, Object>> getAllStudents(
public Result<PageResult<StudentResponse>> getAllStudents(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> result = new HashMap<>();
result.put("records", List.of());
result.put("total", 0);
result.put("teacherId", teacherId);
return Result.success(result);
Long tenantId = SecurityUtils.getCurrentTenantId();
// 获取教师教授的所有班级
List<Clazz> classes = classService.getActiveClassesByTenantId(tenantId);
List<Long> classIds = classes.stream()
.filter(clazz -> {
// 检查教师是否教授该班级
List<Long> teacherIds = classService.getTeacherIdsByClassId(clazz.getId());
return teacherIds.contains(teacherId);
})
.map(Clazz::getId)
.collect(Collectors.toList());
if (classIds.isEmpty()) {
return Result.success(PageResult.of(List.of(), 0L, (long) pageNum, (long) pageSize));
}
// 分页获取学生
Page<Student> page = new Page<>(pageNum, pageSize);
List<Student> students = studentService.getStudentsByClassIds(classIds, keyword, page);
List<StudentResponse> voList = studentMapper.toVO(students);
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@Operation(summary = "Get students of class")
@GetMapping("/classes/{id}/students")
public Result<Map<String, Object>> getClassStudents(
public Result<PageResult<StudentResponse>> getClassStudents(
@PathVariable Long id,
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String keyword) {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> result = new HashMap<>();
result.put("records", List.of());
result.put("total", 0);
result.put("teacherId", teacherId);
result.put("classId", id);
return Result.success(result);
Page<Student> page = studentService.getStudentsByClassId(id, pageNum, pageSize);
List<StudentResponse> voList = studentMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@Operation(summary = "Get teachers of class")
@GetMapping("/classes/{id}/teachers")
public Result<List<Map<String, Object>>> getClassTeachers(@PathVariable Long id) {
Long teacherId = SecurityUtils.getCurrentUserId();
return Result.success(List.of());
public Result<List<TeacherResponse>> getClassTeachers(@PathVariable Long id) {
List<Long> teacherIds = classService.getTeacherIdsByClassId(id);
if (teacherIds.isEmpty()) {
return Result.success(List.of());
}
List<Teacher> teachers = teacherService.getTeachersByIds(teacherIds);
return Result.success(teacherMapper.toVO(teachers));
}
}

View File

@ -1,15 +1,20 @@
package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.common.mapper.LessonFeedbackMapper;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.response.LessonFeedbackResponse;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.service.TeacherFeedbackService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -23,28 +28,25 @@ import java.util.Map;
@RequireRole(UserRole.TEACHER)
public class TeacherFeedbackController {
private final TeacherFeedbackService teacherFeedbackService;
private final LessonFeedbackMapper lessonFeedbackMapper;
@GetMapping
@Operation(summary = "获取反馈列表")
public Result<Map<String, Object>> getFeedbacks(
public Result<PageResult<LessonFeedbackResponse>> getFeedbacks(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String type) {
@RequestParam(required = false, defaultValue = "10") Integer pageSize) {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> result = new HashMap<>();
result.put("records", List.of());
result.put("total", 0);
result.put("teacherId", teacherId);
return Result.success(result);
Page<LessonFeedback> page = teacherFeedbackService.getTeacherFeedbacks(teacherId, pageNum, pageSize);
List<LessonFeedbackResponse> voList = lessonFeedbackMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@GetMapping("/stats")
@Operation(summary = "获取反馈统计")
public Result<Map<String, Object>> getFeedbackStats() {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> stats = new HashMap<>();
stats.put("totalFeedbacks", 0);
stats.put("byType", new HashMap<>());
stats.put("teacherId", teacherId);
Map<String, Object> stats = teacherFeedbackService.getFeedbackStats(teacherId);
return Result.success(stats);
}
}

View File

@ -2,13 +2,19 @@ package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.mapper.LessonMapper;
import com.reading.platform.common.mapper.StudentRecordMapper;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.dto.response.LessonResponse;
import com.reading.platform.dto.response.StudentRecordResponse;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.entity.StudentRecord;
import com.reading.platform.service.LessonService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
@ -28,6 +34,7 @@ public class TeacherLessonController {
private final LessonService lessonService;
private final LessonMapper lessonMapper;
private final StudentRecordMapper studentRecordMapper;
@Operation(summary = "Create lesson")
@PostMapping
@ -94,4 +101,65 @@ public class TeacherLessonController {
return Result.success(lessonMapper.toVO(lessons));
}
@Operation(summary = "Get student records")
@GetMapping("/{id}/students/records")
public Result<List<StudentRecordResponse>> getStudentRecords(@PathVariable Long id) {
List<StudentRecord> records = lessonService.getStudentRecords(id);
List<StudentRecordResponse> voList = studentRecordMapper.toVO(records);
return Result.success(voList);
}
@Operation(summary = "Save student record")
@PostMapping("/{id}/students/{studentId}/record")
public Result<StudentRecordResponse> saveStudentRecord(
@PathVariable Long id,
@PathVariable Long studentId,
@RequestBody StudentRecordRequest request) {
StudentRecord record = lessonService.saveStudentRecord(id, studentId, request);
return Result.success(studentRecordMapper.toVO(record));
}
@Operation(summary = "Batch save student records")
@PostMapping("/{id}/students/batch-records")
public Result<List<StudentRecordResponse>> batchSaveStudentRecords(
@PathVariable Long id,
@RequestBody List<StudentRecordRequest> requests) {
List<StudentRecord> records = lessonService.batchSaveStudentRecords(id, requests);
List<StudentRecordResponse> voList = studentRecordMapper.toVO(records);
return Result.success(voList);
}
@Operation(summary = "Get lesson feedback")
@GetMapping("/{id}/feedback")
public Result<LessonFeedback> getLessonFeedback(@PathVariable Long id) {
LessonFeedback feedback = lessonService.getLessonFeedback(id);
return Result.success(feedback != null ? feedback : null);
}
@Operation(summary = "Submit lesson feedback")
@PostMapping("/{id}/feedback")
public Result<LessonFeedback> submitFeedback(
@PathVariable Long id,
@RequestBody com.reading.platform.dto.request.LessonFeedbackRequest request) {
Long teacherId = SecurityUtils.getCurrentUserId();
LessonFeedback feedback = lessonService.submitFeedback(id, teacherId, request.getContent(), request.getRating());
return Result.success(feedback);
}
@Operation(summary = "Save lesson progress")
@PutMapping("/{id}/progress")
public Result<Void> saveLessonProgress(
@PathVariable Long id,
@RequestBody LessonProgressRequest request) {
lessonService.saveLessonProgress(id, request);
return Result.success();
}
@Operation(summary = "Get lesson progress")
@GetMapping("/{id}/progress")
public Result<LessonResponse> getLessonProgress(@PathVariable Long id) {
Lesson lesson = lessonService.getLessonProgress(id);
return Result.success(lessonMapper.toVO(lesson));
}
}

View File

@ -1,18 +1,27 @@
package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.common.mapper.SchedulePlanMapper;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.SchedulePlanCreateRequest;
import com.reading.platform.dto.request.SchedulePlanUpdateRequest;
import com.reading.platform.dto.response.SchedulePlanResponse;
import com.reading.platform.dto.response.TimetableResponse;
import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.service.TeacherScheduleService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 教师端 - 排课管理
@ -24,73 +33,70 @@ import java.util.Map;
@RequireRole(UserRole.TEACHER)
public class TeacherScheduleController {
private final TeacherScheduleService teacherScheduleService;
private final SchedulePlanMapper schedulePlanMapper;
@GetMapping
@Operation(summary = "获取教师排课列表")
public Result<Map<String, Object>> getSchedules(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
public Result<PageResult<SchedulePlanResponse>> getSchedules(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> result = new HashMap<>();
result.put("records", List.of());
result.put("total", 0);
result.put("teacherId", teacherId);
return Result.success(result);
Page<SchedulePlan> page = teacherScheduleService.getTeacherSchedules(teacherId, pageNum, pageSize, startDate, endDate);
List<SchedulePlanResponse> voList = schedulePlanMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@GetMapping("/timetable")
@Operation(summary = "获取教师课程表")
public Result<Map<String, Object>> getTimetable(
@RequestParam(required = false) String startDate,
@RequestParam(required = false) String endDate) {
public Result<List<TimetableResponse>> getTimetable(
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate startDate,
@RequestParam(required = false) @DateTimeFormat(iso = DateTimeFormat.ISO.DATE) LocalDate endDate) {
Long teacherId = SecurityUtils.getCurrentUserId();
Map<String, Object> timetable = new HashMap<>();
timetable.put("teacherId", teacherId);
timetable.put("classes", List.of());
List<TimetableResponse> timetable = teacherScheduleService.getTimetable(teacherId, startDate, endDate);
return Result.success(timetable);
}
@GetMapping("/today")
@Operation(summary = "获取今日排课")
public Result<List<Map<String, Object>>> getTodaySchedules() {
public Result<List<SchedulePlanResponse>> getTodaySchedules() {
Long teacherId = SecurityUtils.getCurrentUserId();
return Result.success(List.of());
List<SchedulePlan> schedules = teacherScheduleService.getTodaySchedules(teacherId);
List<SchedulePlanResponse> voList = schedulePlanMapper.toVO(schedules);
return Result.success(voList);
}
@GetMapping("/{id}")
@Operation(summary = "获取排课详情")
public Result<Map<String, Object>> getSchedule(@PathVariable Long id) {
Map<String, Object> schedule = new HashMap<>();
schedule.put("id", id);
schedule.put("message", "排课详情待实现");
return Result.success(schedule);
public Result<SchedulePlanResponse> getSchedule(@PathVariable Long id) {
SchedulePlan schedule = teacherScheduleService.getScheduleById(id);
return Result.success(schedulePlanMapper.toVO(schedule));
}
@PostMapping
@Operation(summary = "创建排课")
public Result<Map<String, Object>> createSchedule(@RequestBody Map<String, Object> request) {
public Result<SchedulePlanResponse> createSchedule(@Valid @RequestBody SchedulePlanCreateRequest request) {
Long teacherId = SecurityUtils.getCurrentUserId();
Long tenantId = SecurityUtils.getCurrentTenantId();
Map<String, Object> result = new HashMap<>();
result.put("message", "创建排课功能待实现");
result.put("teacherId", teacherId);
result.put("tenantId", tenantId);
return Result.success(result);
SchedulePlan schedule = teacherScheduleService.createSchedule(tenantId, teacherId, request);
return Result.success(schedulePlanMapper.toVO(schedule));
}
@PutMapping("/{id}")
@Operation(summary = "更新排课")
public Result<Map<String, Object>> updateSchedule(
public Result<SchedulePlanResponse> updateSchedule(
@PathVariable Long id,
@RequestBody Map<String, Object> request) {
Map<String, Object> result = new HashMap<>();
result.put("message", "更新排课功能待实现");
result.put("id", id);
return Result.success(result);
@RequestBody SchedulePlanUpdateRequest request) {
SchedulePlan schedule = teacherScheduleService.updateSchedule(id, request);
return Result.success(schedulePlanMapper.toVO(schedule));
}
@DeleteMapping("/{id}")
@Operation(summary = "取消排课")
public Result<Void> cancelSchedule(@PathVariable Long id) {
teacherScheduleService.cancelSchedule(id);
return Result.success();
}
}

View File

@ -1,17 +1,25 @@
package com.reading.platform.controller.teacher;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.common.annotation.RequireRole;
import com.reading.platform.common.enums.UserRole;
import com.reading.platform.common.mapper.TaskTemplateMapper;
import com.reading.platform.common.response.PageResult;
import com.reading.platform.common.response.Result;
import com.reading.platform.common.security.SecurityUtils;
import com.reading.platform.dto.request.CreateTaskFromTemplateRequest;
import com.reading.platform.dto.request.TaskTemplateCreateRequest;
import com.reading.platform.dto.response.TaskTemplateResponse;
import com.reading.platform.entity.Task;
import com.reading.platform.entity.TaskTemplate;
import com.reading.platform.service.TaskTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 教师端 - 任务模板
@ -23,72 +31,67 @@ import java.util.Map;
@RequireRole(UserRole.TEACHER)
public class TeacherTaskTemplateController {
private final TaskTemplateService taskTemplateService;
private final TaskTemplateMapper taskTemplateMapper;
@GetMapping
@Operation(summary = "获取模板列表")
public Result<Map<String, Object>> getTemplates(
public Result<PageResult<TaskTemplateResponse>> getTemplates(
@RequestParam(required = false, defaultValue = "1") Integer pageNum,
@RequestParam(required = false, defaultValue = "10") Integer pageSize,
@RequestParam(required = false) String type) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Map<String, Object> result = new HashMap<>();
result.put("records", List.of());
result.put("total", 0);
result.put("tenantId", tenantId);
return Result.success(result);
Page<TaskTemplate> page = taskTemplateService.getTemplates(tenantId, pageNum, pageSize, type);
List<TaskTemplateResponse> voList = taskTemplateMapper.toVO(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@GetMapping("/default/{type}")
@Operation(summary = "获取默认模板")
public Result<Map<String, Object>> getDefaultTemplate(@PathVariable String type) {
Map<String, Object> template = new HashMap<>();
template.put("type", type);
template.put("message", "默认模板待实现");
return Result.success(template);
public Result<TaskTemplateResponse> getDefaultTemplate(@PathVariable String type) {
Long tenantId = SecurityUtils.getCurrentTenantId();
TaskTemplate template = taskTemplateService.getDefaultTemplate(tenantId, type);
return template != null ? Result.success(taskTemplateMapper.toVO(template)) : Result.success(null);
}
@GetMapping("/{id}")
@Operation(summary = "获取模板详情")
public Result<Map<String, Object>> getTemplate(@PathVariable Long id) {
Map<String, Object> template = new HashMap<>();
template.put("id", id);
template.put("message", "模板详情待实现");
return Result.success(template);
public Result<TaskTemplateResponse> getTemplate(@PathVariable Long id) {
TaskTemplate template = taskTemplateService.getTemplateById(id);
return Result.success(taskTemplateMapper.toVO(template));
}
@PostMapping
@Operation(summary = "创建模板")
public Result<Map<String, Object>> createTemplate(@RequestBody Map<String, Object> request) {
public Result<TaskTemplateResponse> createTemplate(@Valid @RequestBody TaskTemplateCreateRequest request) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Map<String, Object> result = new HashMap<>();
result.put("message", "创建模板功能待实现");
result.put("tenantId", tenantId);
return Result.success(result);
Long userId = SecurityUtils.getCurrentUserId();
TaskTemplate template = taskTemplateService.createTemplate(tenantId, userId, request);
return Result.success(taskTemplateMapper.toVO(template));
}
@PostMapping("/from-template")
@Operation(summary = "从模板创建任务")
public Result<Map<String, Object>> createFromTemplate(@RequestBody Map<String, Object> request) {
public Result<Task> createFromTemplate(@Valid @RequestBody CreateTaskFromTemplateRequest request) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Map<String, Object> result = new HashMap<>();
result.put("message", "从模板创建任务功能待实现");
result.put("tenantId", tenantId);
return Result.success(result);
Long userId = SecurityUtils.getCurrentUserId();
Task task = taskTemplateService.createTaskFromTemplate(tenantId, userId, request.getTemplateId(), request);
return Result.success(task);
}
@PutMapping("/{id}")
@Operation(summary = "更新模板")
public Result<Map<String, Object>> updateTemplate(
public Result<TaskTemplateResponse> updateTemplate(
@PathVariable Long id,
@RequestBody Map<String, Object> request) {
Map<String, Object> result = new HashMap<>();
result.put("message", "更新模板功能待实现");
result.put("id", id);
return Result.success(result);
@RequestBody TaskTemplateCreateRequest request) {
TaskTemplate template = taskTemplateService.updateTemplate(id, request);
return Result.success(taskTemplateMapper.toVO(template));
}
@DeleteMapping("/{id}")
@Operation(summary = "删除模板")
public Result<Void> deleteTemplate(@PathVariable Long id) {
taskTemplateService.deleteTemplate(id);
return Result.success();
}
}

View File

@ -0,0 +1,36 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 从模板创建任务请求
*/
@Data
@Schema(description = "从模板创建任务请求")
public class CreateTaskFromTemplateRequest {
@Schema(description = "模板 ID")
private Long templateId;
@NotBlank(message = "任务标题不能为空")
@Schema(description = "任务标题")
private String title;
@Schema(description = "任务描述")
private String description;
@Schema(description = "开始日期")
private String startDate;
@Schema(description = "截止日期")
private String endDate;
@Schema(description = "目标类型class-班级student-学生")
private String targetType;
@Schema(description = "目标 IDs")
private java.util.List<Long> targetIds;
}

View File

@ -0,0 +1,52 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import lombok.Data;
import java.util.List;
/**
* 课程反馈提交请求
*/
@Data
@Schema(description = "课程反馈提交请求")
public class LessonFeedbackRequest {
@Schema(description = "反馈内容")
private String content;
@Schema(description = "总体评分 (1-5)")
@Min(value = 1, message = "评分最小值为 1")
@Max(value = 5, message = "评分最大值为 5")
private Integer rating;
@Schema(description = "教学设计评分 (1-5)")
@Min(value = 1, message = "评分最小值为 1")
@Max(value = 5, message = "评分最大值为 5")
private Integer designQuality;
@Schema(description = "学生参与度评分 (1-5)")
@Min(value = 1, message = "评分最小值为 1")
@Max(value = 5, message = "评分最大值为 5")
private Integer participation;
@Schema(description = "目标达成度评分 (1-5)")
@Min(value = 1, message = "评分最小值为 1")
@Max(value = 5, message = "评分最大值为 5")
private Integer goalAchievement;
@Schema(description = "各步骤反馈 (JSON 数组)")
private String stepFeedbacks;
@Schema(description = "优点")
private String pros;
@Schema(description = "建议")
private String suggestions;
@Schema(description = "已完成的活动")
private String activitiesDone;
}

View File

@ -0,0 +1,28 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 课程进度保存请求
*/
@Data
@Schema(description = "课程进度保存请求")
public class LessonProgressRequest {
@Schema(description = "当前课程 ID")
private Integer currentLessonId;
@Schema(description = "当前步骤 ID")
private Integer currentStepId;
@Schema(description = "课程 ID 列表 (JSON)")
private String lessonIds;
@Schema(description = "已完成课程 ID 列表 (JSON)")
private String completedLessonIds;
@Schema(description = "进度数据 (JSON)")
private String progressData;
}

View File

@ -0,0 +1,52 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDate;
/**
* 日程计划创建请求
*/
@Data
@Schema(description = "日程计划创建请求")
public class SchedulePlanCreateRequest {
@NotBlank(message = "计划名称不能为空")
@Schema(description = "计划名称")
private String name;
@NotNull(message = "班级 ID 不能为空")
@Schema(description = "班级 ID")
private Long classId;
@Schema(description = "课程 ID")
private Long courseId;
@Schema(description = "教师 ID")
private Long teacherId;
@Schema(description = "排课日期")
private LocalDate scheduledDate;
@Schema(description = "时间段 (如09:00-10:00)")
private String scheduledTime;
@Schema(description = "星期几 (1-7)")
private Integer weekDay;
@Schema(description = "重复方式 (NONE/WEEKLY)")
private String repeatType;
@Schema(description = "重复截止日期")
private LocalDate repeatEndDate;
@Schema(description = "来源 (SCHOOL/TEACHER)")
private String source;
@Schema(description = "备注")
private String note;
}

View File

@ -0,0 +1,45 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
/**
* 日程计划更新请求
*/
@Data
@Schema(description = "日程计划更新请求")
public class SchedulePlanUpdateRequest {
@Schema(description = "计划名称")
private String name;
@Schema(description = "课程 ID")
private Long courseId;
@Schema(description = "教师 ID")
private Long teacherId;
@Schema(description = "排课日期")
private LocalDate scheduledDate;
@Schema(description = "时间段 (如09:00-10:00)")
private String scheduledTime;
@Schema(description = "星期几 (1-7)")
private Integer weekDay;
@Schema(description = "重复方式 (NONE/WEEKLY)")
private String repeatType;
@Schema(description = "重复截止日期")
private LocalDate repeatEndDate;
@Schema(description = "备注")
private String note;
@Schema(description = "状态")
private String status;
}

View File

@ -0,0 +1,42 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
/**
* 学生记录保存请求
*/
@Data
@Schema(description = "学生记录保存请求")
public class StudentRecordRequest {
@NotNull(message = "学生 ID 不能为空")
@Schema(description = "学生 ID")
private Long studentId;
@Schema(description = "出勤状态")
private String attendance;
@Schema(description = "专注度评分 (1-5)")
private Integer focus;
@Schema(description = "参与度评分 (1-5)")
private Integer participation;
@Schema(description = "兴趣度评分 (1-5)")
private Integer interest;
@Schema(description = "理解度评分 (1-5)")
private Integer understanding;
@Schema(description = "领域达成 (JSON 数组)")
private String domainAchievements;
@Schema(description = "表现评价")
private String performance;
@Schema(description = "备注")
private String notes;
}

View File

@ -0,0 +1,42 @@
package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
/**
* 任务模板创建请求
*/
@Data
@Schema(description = "任务模板创建请求")
public class TaskTemplateCreateRequest {
@NotBlank(message = "模板名称不能为空")
@Schema(description = "模板名称")
private String name;
@Schema(description = "模板描述")
private String description;
@Schema(description = "模板类型")
private String type;
@Schema(description = "任务类型")
private String taskType;
@Schema(description = "关联课程 ID")
private Long relatedCourseId;
@Schema(description = "默认持续时间 (天)")
private Integer defaultDuration;
@Schema(description = "是否默认模板")
private Integer isDefault;
@Schema(description = "模板内容")
private String content;
@Schema(description = "是否公开")
private Integer isPublic;
}

View File

@ -0,0 +1,28 @@
package com.reading.platform.dto.response;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
* 课表响应
* 用于按日期分组返回课表信息
*/
@Data
@Builder
@Schema(description = "课表响应")
public class TimetableResponse {
@Schema(description = "日期")
private LocalDate date;
@Schema(description = "星期几 (1-7)")
private Integer weekDay;
@Schema(description = "排课列表")
private List<SchedulePlanResponse> schedules;
}

View File

@ -23,4 +23,8 @@ public class ClassTeacher extends BaseEntity {
@Schema(description = "角色")
private String role;
// 暂时注释掉数据库中不存在的字段
// @Schema(description = "是否主班")
// private Integer isPrimary;
}

View File

@ -29,6 +29,10 @@ public class Clazz extends BaseEntity {
@Schema(description = "容纳人数")
private Integer capacity;
// 暂时注释掉数据库中不存在的字段
// @Schema(description = "课时数")
// private Integer lessonCount;
@Schema(description = "状态")
private String status;

View File

@ -6,6 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
/**
@ -29,6 +30,9 @@ public class Lesson extends BaseEntity {
@Schema(description = "教师 ID")
private Long teacherId;
@Schema(description = "排课计划 ID")
private Long schedulePlanId;
@Schema(description = "课程标题")
private String title;
@ -41,12 +45,48 @@ public class Lesson extends BaseEntity {
@Schema(description = "结束时间")
private LocalTime endTime;
@Schema(description = "计划上课时间")
private LocalDateTime plannedDatetime;
@Schema(description = "实际上课开始时间")
private LocalDateTime startDatetime;
@Schema(description = "实际上课结束时间")
private LocalDateTime endDatetime;
@Schema(description = "实际时长 (分钟)")
private Integer actualDuration;
@Schema(description = "上课地点")
private String location;
@Schema(description = "状态")
private String status;
@Schema(description = "整体评价")
private String overallRating;
@Schema(description = "参与度评价")
private String participationRating;
@Schema(description = "完成说明")
private String completionNote;
@Schema(description = "进度数据 (JSON)")
private String progressData;
@Schema(description = "当前课程 ID")
private Integer currentLessonId;
@Schema(description = "当前步骤 ID")
private Integer currentStepId;
@Schema(description = "课程 ID 列表 (JSON)")
private String lessonIds;
@Schema(description = "已完成课程 ID 列表 (JSON)")
private String completedLessonIds;
@Schema(description = "备注")
private String notes;

View File

@ -26,4 +26,25 @@ public class LessonFeedback extends BaseEntity {
@Schema(description = "评分")
private Integer rating;
@Schema(description = "教学设计评分 (1-5)")
private Integer designQuality;
@Schema(description = "学生参与度评分 (1-5)")
private Integer participation;
@Schema(description = "目标达成度评分 (1-5)")
private Integer goalAchievement;
@Schema(description = "各步骤反馈 (JSON 数组)")
private String stepFeedbacks;
@Schema(description = "优点")
private String pros;
@Schema(description = "建议")
private String suggestions;
@Schema(description = "已完成的活动")
private String activitiesDone;
}

View File

@ -6,6 +6,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.time.LocalDateTime;
/**
* 日程计划实体
@ -25,10 +26,45 @@ public class SchedulePlan extends BaseEntity {
@Schema(description = "班级 ID")
private Long classId;
@Schema(description = "开始日期")
@Schema(description = "课程 ID")
private Long courseId;
@Schema(description = "教师 ID")
private Long teacherId;
@Schema(description = "排课日期")
private LocalDate scheduledDate;
@Schema(description = "时间段 (如09:00-10:00)")
private String scheduledTime;
@Schema(description = "星期几 (1-7)")
private Integer weekDay;
@Schema(description = "重复方式 (NONE/WEEKLY)")
private String repeatType;
@Schema(description = "重复截止日期")
private LocalDate repeatEndDate;
@Schema(description = "来源 (SCHOOL/TEACHER)")
private String source;
@Schema(description = "备注")
private String note;
@Schema(description = "是否已发送提醒")
private Integer reminderSent;
@Schema(description = "提醒发送时间")
private LocalDateTime reminderSentAt;
@Schema(description = "开始日期(废弃,兼容旧数据)")
@Deprecated
private LocalDate startDate;
@Schema(description = "结束日期")
@Schema(description = "结束日期(废弃,兼容旧数据)")
@Deprecated
private LocalDate endDate;
@Schema(description = "状态")

View File

@ -29,4 +29,19 @@ public class StudentRecord extends BaseEntity {
@Schema(description = "备注")
private String notes;
@Schema(description = "专注度评分 (1-5)")
private Integer focus;
@Schema(description = "参与度评分 (1-5)")
private Integer participation;
@Schema(description = "兴趣度评分 (1-5)")
private Integer interest;
@Schema(description = "理解度评分 (1-5)")
private Integer understanding;
@Schema(description = "领域达成 (JSON 数组)")
private String domainAchievements;
}

View File

@ -26,6 +26,24 @@ public class TaskTemplate extends BaseEntity {
@Schema(description = "模板类型")
private String type;
@Schema(description = "任务类型")
private String taskType;
@Schema(description = "关联课程 ID")
private Long relatedCourseId;
@Schema(description = "默认持续时间 (天)")
private Integer defaultDuration;
@Schema(description = "是否默认模板")
private Integer isDefault;
@Schema(description = "状态")
private String status;
@Schema(description = "创建人 ID")
private Long createdBy;
@Schema(description = "模板内容")
private String content;

View File

@ -43,6 +43,13 @@ public class Teacher extends BaseEntity {
@Schema(description = "个人简介")
private String bio;
// 暂时注释掉数据库中不存在的字段
// @Schema(description = "授课次数")
// private Integer lessonCount;
// @Schema(description = "反馈次数")
// private Integer feedbackCount;
@Schema(description = "状态")
private String status;

View File

@ -3,7 +3,11 @@ package com.reading.platform.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.entity.StudentRecord;
import java.time.LocalDate;
import java.util.List;
@ -33,4 +37,39 @@ public interface LessonService extends com.baomidou.mybatisplus.extension.servic
List<Lesson> getTodayLessons(Long tenantId);
/**
* 获取学生记录列表
*/
List<StudentRecord> getStudentRecords(Long lessonId);
/**
* 保存学生记录
*/
StudentRecord saveStudentRecord(Long lessonId, Long studentId, StudentRecordRequest request);
/**
* 批量保存学生记录
*/
List<StudentRecord> batchSaveStudentRecords(Long lessonId, List<StudentRecordRequest> requests);
/**
* 获取课程反馈
*/
LessonFeedback getLessonFeedback(Long lessonId);
/**
* 提交课程反馈
*/
LessonFeedback submitFeedback(Long lessonId, Long teacherId, String content, Integer rating);
/**
* 保存课程进度
*/
void saveLessonProgress(Long lessonId, LessonProgressRequest request);
/**
* 获取课程进度
*/
Lesson getLessonProgress(Long lessonId);
}

View File

@ -37,6 +37,11 @@ public interface StudentService extends com.baomidou.mybatisplus.extension.servi
*/
Page<Student> getStudentsByClassId(Long classId, Integer pageNum, Integer pageSize);
/**
* 根据班级 ID 列表查询学生
*/
List<Student> getStudentsByClassIds(List<Long> classIds, String keyword, Page<Student> page);
/**
* 删除学生
*/

View File

@ -0,0 +1,51 @@
package com.reading.platform.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.dto.request.CreateTaskFromTemplateRequest;
import com.reading.platform.dto.request.TaskTemplateCreateRequest;
import com.reading.platform.entity.Task;
import com.reading.platform.entity.TaskTemplate;
import java.util.List;
/**
* 任务模板服务接口
*/
public interface TaskTemplateService extends com.baomidou.mybatisplus.extension.service.IService<TaskTemplate> {
/**
* 获取模板列表
*/
Page<TaskTemplate> getTemplates(Long tenantId, Integer pageNum, Integer pageSize, String type);
/**
* 获取默认模板
*/
TaskTemplate getDefaultTemplate(Long tenantId, String type);
/**
* 获取模板详情
*/
TaskTemplate getTemplateById(Long id);
/**
* 创建模板
*/
TaskTemplate createTemplate(Long tenantId, Long userId, TaskTemplateCreateRequest request);
/**
* 更新模板
*/
TaskTemplate updateTemplate(Long id, TaskTemplateCreateRequest request);
/**
* 删除模板
*/
void deleteTemplate(Long id);
/**
* 从模板创建任务
*/
Task createTaskFromTemplate(Long tenantId, Long userId, Long templateId, CreateTaskFromTemplateRequest request);
}

View File

@ -0,0 +1,23 @@
package com.reading.platform.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.entity.LessonFeedback;
import java.util.Map;
/**
* 教师端反馈服务接口
*/
public interface TeacherFeedbackService {
/**
* 获取教师反馈列表
*/
Page<LessonFeedback> getTeacherFeedbacks(Long teacherId, Integer pageNum, Integer pageSize);
/**
* 获取教师反馈统计
*/
Map<String, Object> getFeedbackStats(Long teacherId);
}

View File

@ -0,0 +1,52 @@
package com.reading.platform.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.dto.request.SchedulePlanCreateRequest;
import com.reading.platform.dto.request.SchedulePlanUpdateRequest;
import com.reading.platform.dto.response.TimetableResponse;
import com.reading.platform.entity.SchedulePlan;
import java.time.LocalDate;
import java.util.List;
/**
* 教师端排课服务接口
*/
public interface TeacherScheduleService extends com.baomidou.mybatisplus.extension.service.IService<SchedulePlan> {
/**
* 获取教师排课列表
*/
Page<SchedulePlan> getTeacherSchedules(Long teacherId, Integer pageNum, Integer pageSize,
LocalDate startDate, LocalDate endDate);
/**
* 获取教师课表按日期分组
*/
List<TimetableResponse> getTimetable(Long teacherId, LocalDate startDate, LocalDate endDate);
/**
* 获取今日排课
*/
List<SchedulePlan> getTodaySchedules(Long teacherId);
/**
* 创建排课
*/
SchedulePlan createSchedule(Long tenantId, Long teacherId, SchedulePlanCreateRequest request);
/**
* 更新排课
*/
SchedulePlan updateSchedule(Long scheduleId, SchedulePlanUpdateRequest request);
/**
* 取消排课
*/
void cancelSchedule(Long scheduleId);
/**
* 根据 ID 获取排课
*/
SchedulePlan getScheduleById(Long scheduleId);
}

View File

@ -6,6 +6,8 @@ import com.reading.platform.dto.request.TeacherCreateRequest;
import com.reading.platform.dto.request.TeacherUpdateRequest;
import com.reading.platform.entity.Teacher;
import java.util.List;
/**
* 教师服务接口
*/
@ -41,4 +43,9 @@ public interface TeacherService extends IService<Teacher> {
*/
void resetPassword(Long id, String newPassword);
/**
* 根据 ID 列表查询教师
*/
List<Teacher> getTeachersByIds(List<Long> teacherIds);
}

View File

@ -7,8 +7,14 @@ import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.LessonCreateRequest;
import com.reading.platform.dto.request.LessonUpdateRequest;
import com.reading.platform.dto.request.LessonProgressRequest;
import com.reading.platform.dto.request.StudentRecordRequest;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.entity.StudentRecord;
import com.reading.platform.mapper.LessonFeedbackMapper;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.mapper.StudentRecordMapper;
import com.reading.platform.service.LessonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@ -17,6 +23,8 @@ import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@ -26,6 +34,8 @@ public class LessonServiceImpl extends ServiceImpl<LessonMapper, Lesson>
implements LessonService {
private final LessonMapper lessonMapper;
private final StudentRecordMapper studentRecordMapper;
private final LessonFeedbackMapper lessonFeedbackMapper;
@Override
@Transactional
@ -183,4 +193,159 @@ public class LessonServiceImpl extends ServiceImpl<LessonMapper, Lesson>
);
}
@Override
public List<StudentRecord> getStudentRecords(Long lessonId) {
log.debug("获取学生记录列表,课程 ID: {}", lessonId);
return studentRecordMapper.selectList(
new LambdaQueryWrapper<StudentRecord>()
.eq(StudentRecord::getLessonId, lessonId)
);
}
@Override
@Transactional
public StudentRecord saveStudentRecord(Long lessonId, Long studentId, StudentRecordRequest request) {
log.info("保存学生记录,课程 ID: {}, 学生 ID: {}", lessonId, studentId);
// 先查询是否已存在
StudentRecord record = studentRecordMapper.selectOne(
new LambdaQueryWrapper<StudentRecord>()
.eq(StudentRecord::getLessonId, lessonId)
.eq(StudentRecord::getStudentId, studentId)
);
if (record == null) {
record = new StudentRecord();
record.setLessonId(lessonId);
record.setStudentId(studentId);
record.setAttendance(request.getAttendance());
record.setFocus(request.getFocus());
record.setParticipation(request.getParticipation());
record.setInterest(request.getInterest());
record.setUnderstanding(request.getUnderstanding());
record.setDomainAchievements(request.getDomainAchievements());
record.setPerformance(request.getPerformance());
record.setNotes(request.getNotes());
studentRecordMapper.insert(record);
log.info("学生记录创建成功ID: {}", record.getId());
} else {
if (request.getAttendance() != null) {
record.setAttendance(request.getAttendance());
}
if (request.getFocus() != null) {
record.setFocus(request.getFocus());
}
if (request.getParticipation() != null) {
record.setParticipation(request.getParticipation());
}
if (request.getInterest() != null) {
record.setInterest(request.getInterest());
}
if (request.getUnderstanding() != null) {
record.setUnderstanding(request.getUnderstanding());
}
if (request.getDomainAchievements() != null) {
record.setDomainAchievements(request.getDomainAchievements());
}
if (request.getPerformance() != null) {
record.setPerformance(request.getPerformance());
}
if (request.getNotes() != null) {
record.setNotes(request.getNotes());
}
studentRecordMapper.updateById(record);
log.info("学生记录更新成功ID: {}", record.getId());
}
return record;
}
@Override
@Transactional
public List<StudentRecord> batchSaveStudentRecords(Long lessonId, List<StudentRecordRequest> requests) {
log.info("批量保存学生记录,课程 ID: {}, 记录数量:{}", lessonId, requests.size());
List<StudentRecord> records = new ArrayList<>();
for (StudentRecordRequest request : requests) {
StudentRecord record = saveStudentRecord(lessonId, request.getStudentId(), request);
records.add(record);
}
return records;
}
@Override
public LessonFeedback getLessonFeedback(Long lessonId) {
log.debug("获取课程反馈,课程 ID: {}", lessonId);
return lessonFeedbackMapper.selectOne(
new LambdaQueryWrapper<LessonFeedback>()
.eq(LessonFeedback::getLessonId, lessonId)
);
}
@Override
@Transactional
public LessonFeedback submitFeedback(Long lessonId, Long teacherId, String content, Integer rating) {
log.info("提交课程反馈,课程 ID: {}, 教师 ID: {}", lessonId, teacherId);
// 先查询是否已存在
LessonFeedback feedback = lessonFeedbackMapper.selectOne(
new LambdaQueryWrapper<LessonFeedback>()
.eq(LessonFeedback::getLessonId, lessonId)
.eq(LessonFeedback::getTeacherId, teacherId)
);
if (feedback == null) {
feedback = new LessonFeedback();
feedback.setLessonId(lessonId);
feedback.setTeacherId(teacherId);
feedback.setContent(content);
feedback.setRating(rating);
lessonFeedbackMapper.insert(feedback);
log.info("课程反馈创建成功ID: {}", feedback.getId());
} else {
feedback.setContent(content);
feedback.setRating(rating);
lessonFeedbackMapper.updateById(feedback);
log.info("课程反馈更新成功ID: {}", feedback.getId());
}
return feedback;
}
@Override
@Transactional
public void saveLessonProgress(Long lessonId, LessonProgressRequest request) {
log.info("保存课程进度,课程 ID: {}", lessonId);
Lesson lesson = getLessonById(lessonId);
if (request.getCurrentLessonId() != null) {
lesson.setCurrentLessonId(request.getCurrentLessonId());
}
if (request.getCurrentStepId() != null) {
lesson.setCurrentStepId(request.getCurrentStepId());
}
if (StringUtils.hasText(request.getLessonIds())) {
lesson.setLessonIds(request.getLessonIds());
}
if (StringUtils.hasText(request.getCompletedLessonIds())) {
lesson.setCompletedLessonIds(request.getCompletedLessonIds());
}
if (StringUtils.hasText(request.getProgressData())) {
lesson.setProgressData(request.getProgressData());
}
lessonMapper.updateById(lesson);
log.info("课程进度保存成功ID: {}", lessonId);
}
@Override
public Lesson getLessonProgress(Long lessonId) {
log.debug("获取课程进度,课程 ID: {}", lessonId);
return getLessonById(lessonId);
}
}

View File

@ -213,4 +213,45 @@ public class StudentServiceImpl extends com.baomidou.mybatisplus.extension.servi
);
}
@Override
public List<Student> getStudentsByClassIds(List<Long> classIds, String keyword, Page<Student> page) {
log.debug("根据班级 ID 列表查询学生,班级 ID 列表:{}", classIds);
// 获取班级中活跃的学生 ID
List<StudentClassHistory> histories = studentClassHistoryMapper.selectList(
new LambdaQueryWrapper<StudentClassHistory>()
.in(StudentClassHistory::getClassId, classIds)
.eq(StudentClassHistory::getStatus, "active")
.isNull(StudentClassHistory::getEndDate)
);
if (histories.isEmpty()) {
return List.of();
}
List<Long> studentIds = histories.stream()
.map(StudentClassHistory::getStudentId)
.distinct()
.toList();
if (studentIds.isEmpty()) {
return List.of();
}
LambdaQueryWrapper<Student> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Student::getId, studentIds)
.eq(Student::getStatus, "active");
if (StringUtils.hasText(keyword)) {
wrapper.and(w -> w
.like(Student::getName, keyword)
.or()
.like(Student::getStudentNo, keyword)
);
}
wrapper.orderByAsc(Student::getName);
return studentMapper.selectPage(page, wrapper).getRecords();
}
}

View File

@ -0,0 +1,210 @@
package com.reading.platform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.CreateTaskFromTemplateRequest;
import com.reading.platform.dto.request.TaskTemplateCreateRequest;
import com.reading.platform.entity.Task;
import com.reading.platform.entity.TaskTemplate;
import com.reading.platform.entity.TaskTarget;
import com.reading.platform.mapper.TaskMapper;
import com.reading.platform.mapper.TaskTargetMapper;
import com.reading.platform.mapper.TaskTemplateMapper;
import com.reading.platform.service.TaskTemplateService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.util.List;
/**
* 任务模板服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TaskTemplateServiceImpl extends ServiceImpl<TaskTemplateMapper, TaskTemplate>
implements TaskTemplateService {
private final TaskTemplateMapper taskTemplateMapper;
private final TaskMapper taskMapper;
private final TaskTargetMapper taskTargetMapper;
@Override
public Page<TaskTemplate> getTemplates(Long tenantId, Integer pageNum, Integer pageSize, String type) {
log.debug("获取模板列表,租户 ID: {}, 页码:{}, 类型:{}", tenantId, pageNum, type);
Page<TaskTemplate> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<TaskTemplate> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(TaskTemplate::getTenantId, tenantId);
wrapper.eq(TaskTemplate::getIsPublic, 1);
wrapper.eq(TaskTemplate::getStatus, "ACTIVE");
if (StringUtils.hasText(type)) {
wrapper.eq(TaskTemplate::getType, type);
}
wrapper.orderByDesc(TaskTemplate::getCreatedAt);
return taskTemplateMapper.selectPage(page, wrapper);
}
@Override
public TaskTemplate getDefaultTemplate(Long tenantId, String type) {
log.debug("获取默认模板,租户 ID: {}, 类型:{}", tenantId, type);
return taskTemplateMapper.selectOne(
new LambdaQueryWrapper<TaskTemplate>()
.eq(TaskTemplate::getTenantId, tenantId)
.eq(TaskTemplate::getTaskType, type)
.eq(TaskTemplate::getIsDefault, 1)
.eq(TaskTemplate::getStatus, "ACTIVE")
);
}
@Override
public TaskTemplate getTemplateById(Long id) {
TaskTemplate template = taskTemplateMapper.selectById(id);
if (template == null) {
throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "模板不存在");
}
return template;
}
@Override
@Transactional
public TaskTemplate createTemplate(Long tenantId, Long userId, TaskTemplateCreateRequest request) {
log.info("创建任务模板,租户 ID: {}, 用户 ID: {}", tenantId, userId);
TaskTemplate template = new TaskTemplate();
template.setTenantId(tenantId);
template.setName(request.getName());
template.setDescription(request.getDescription());
template.setType(request.getType());
template.setTaskType(request.getTaskType());
template.setRelatedCourseId(request.getRelatedCourseId());
template.setDefaultDuration(request.getDefaultDuration() != null ? request.getDefaultDuration() : 7);
template.setIsDefault(request.getIsDefault() != null ? request.getIsDefault() : 0);
template.setStatus("ACTIVE");
template.setCreatedBy(userId);
template.setContent(request.getContent());
template.setIsPublic(request.getIsPublic() != null ? request.getIsPublic() : 0);
taskTemplateMapper.insert(template);
log.info("任务模板创建成功ID: {}", template.getId());
return template;
}
@Override
@Transactional
public TaskTemplate updateTemplate(Long id, TaskTemplateCreateRequest request) {
log.info("更新任务模板ID: {}", id);
TaskTemplate template = getTemplateById(id);
if (StringUtils.hasText(request.getName())) {
template.setName(request.getName());
}
if (request.getDescription() != null) {
template.setDescription(request.getDescription());
}
if (request.getType() != null) {
template.setType(request.getType());
}
if (request.getTaskType() != null) {
template.setTaskType(request.getTaskType());
}
if (request.getRelatedCourseId() != null) {
template.setRelatedCourseId(request.getRelatedCourseId());
}
if (request.getDefaultDuration() != null) {
template.setDefaultDuration(request.getDefaultDuration());
}
if (request.getIsDefault() != null) {
template.setIsDefault(request.getIsDefault());
}
if (request.getContent() != null) {
template.setContent(request.getContent());
}
if (request.getIsPublic() != null) {
template.setIsPublic(request.getIsPublic());
}
taskTemplateMapper.updateById(template);
log.info("任务模板更新成功ID: {}", id);
return template;
}
@Override
@Transactional
public void deleteTemplate(Long id) {
log.info("删除任务模板ID: {}", id);
getTemplateById(id);
taskTemplateMapper.deleteById(id);
log.info("任务模板删除成功ID: {}", id);
}
@Override
@Transactional
public Task createTaskFromTemplate(Long tenantId, Long userId, Long templateId, CreateTaskFromTemplateRequest request) {
log.info("从模板创建任务,模板 ID: {}, 用户 ID: {}", templateId, userId);
// 获取模板
TaskTemplate template = getTemplateById(templateId);
// 创建任务
Task task = new Task();
task.setTenantId(tenantId);
task.setTitle(request.getTitle());
task.setDescription(request.getDescription());
task.setType(template.getTaskType());
task.setCourseId(template.getRelatedCourseId());
// 解析日期
if (StringUtils.hasText(request.getStartDate())) {
try {
task.setStartDate(LocalDate.parse(request.getStartDate()));
} catch (Exception e) {
log.warn("开始日期解析失败:{}", request.getStartDate());
}
}
if (StringUtils.hasText(request.getEndDate())) {
try {
task.setDueDate(LocalDate.parse(request.getEndDate()));
} catch (Exception e) {
log.warn("截止日期解析失败:{}", request.getEndDate());
}
}
task.setStatus("pending");
task.setCreatorId(userId);
task.setCreatorRole("TEACHER");
taskMapper.insert(task);
log.info("任务创建成功ID: {}", task.getId());
// 创建任务目标
if (request.getTargetIds() != null && !request.getTargetIds().isEmpty()) {
for (Long targetId : request.getTargetIds()) {
TaskTarget target = new TaskTarget();
target.setTaskId(task.getId());
target.setTargetType(request.getTargetType());
target.setTargetId(targetId);
taskTargetMapper.insert(target);
}
log.info("任务目标创建完成,任务 ID: {}", task.getId());
}
return task;
}
}

View File

@ -0,0 +1,101 @@
package com.reading.platform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.LessonFeedback;
import com.reading.platform.mapper.LessonFeedbackMapper;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.service.TeacherFeedbackService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 教师端反馈服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TeacherFeedbackServiceImpl implements TeacherFeedbackService {
private final LessonFeedbackMapper lessonFeedbackMapper;
private final LessonMapper lessonMapper;
@Override
public Page<LessonFeedback> getTeacherFeedbacks(Long teacherId, Integer pageNum, Integer pageSize) {
log.debug("获取教师反馈列表,教师 ID: {}, 页码:{}", teacherId, pageNum);
Page<LessonFeedback> page = new Page<>(pageNum, pageSize);
// 先获取教师的所有课程
List<Lesson> lessons = lessonMapper.selectList(
new LambdaQueryWrapper<Lesson>()
.eq(Lesson::getTeacherId, teacherId)
);
if (lessons.isEmpty()) {
return page;
}
List<Long> lessonIds = lessons.stream().map(Lesson::getId).collect(Collectors.toList());
LambdaQueryWrapper<LessonFeedback> wrapper = new LambdaQueryWrapper<>();
wrapper.in(LessonFeedback::getLessonId, lessonIds)
.orderByDesc(LessonFeedback::getCreatedAt);
return lessonFeedbackMapper.selectPage(page, wrapper);
}
@Override
public Map<String, Object> getFeedbackStats(Long teacherId) {
log.debug("获取教师反馈统计,教师 ID: {}", teacherId);
Map<String, Object> stats = new HashMap<>();
// 获取教师的所有课程
List<Lesson> lessons = lessonMapper.selectList(
new LambdaQueryWrapper<Lesson>()
.eq(Lesson::getTeacherId, teacherId)
);
if (lessons.isEmpty()) {
stats.put("totalFeedbacks", 0);
stats.put("avgRating", 0.0);
stats.put("byRating", new HashMap<>());
return stats;
}
List<Long> lessonIds = lessons.stream().map(Lesson::getId).collect(Collectors.toList());
// 获取反馈列表
List<LessonFeedback> feedbacks = lessonFeedbackMapper.selectList(
new LambdaQueryWrapper<LessonFeedback>()
.in(LessonFeedback::getLessonId, lessonIds)
);
stats.put("totalFeedbacks", feedbacks.size());
// 计算平均评分
double avgRating = feedbacks.stream()
.filter(f -> f.getRating() != null)
.mapToInt(LessonFeedback::getRating)
.average()
.orElse(0.0);
stats.put("avgRating", avgRating);
// 按评分分组统计
Map<Integer, Long> byRating = feedbacks.stream()
.filter(f -> f.getRating() != null)
.collect(Collectors.groupingBy(LessonFeedback::getRating, Collectors.counting()));
stats.put("byRating", byRating);
return stats;
}
}

View File

@ -0,0 +1,199 @@
package com.reading.platform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.SchedulePlanCreateRequest;
import com.reading.platform.dto.request.SchedulePlanUpdateRequest;
import com.reading.platform.dto.response.SchedulePlanResponse;
import com.reading.platform.dto.response.TimetableResponse;
import com.reading.platform.entity.SchedulePlan;
import com.reading.platform.mapper.SchedulePlanMapper;
import com.reading.platform.service.TeacherScheduleService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* 教师端排课服务实现
*/
@Slf4j
@Service
@RequiredArgsConstructor
public class TeacherScheduleServiceImpl extends ServiceImpl<SchedulePlanMapper, SchedulePlan>
implements TeacherScheduleService {
private final SchedulePlanMapper schedulePlanMapper;
@Override
public Page<SchedulePlan> getTeacherSchedules(Long teacherId, Integer pageNum, Integer pageSize,
LocalDate startDate, LocalDate endDate) {
Page<SchedulePlan> page = new Page<>(pageNum, pageSize);
LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
if (startDate != null) {
wrapper.ge(SchedulePlan::getScheduledDate, startDate);
}
if (endDate != null) {
wrapper.le(SchedulePlan::getScheduledDate, endDate);
}
wrapper.orderByAsc(SchedulePlan::getScheduledDate);
return schedulePlanMapper.selectPage(page, wrapper);
}
@Override
public List<TimetableResponse> getTimetable(Long teacherId, LocalDate startDate, LocalDate endDate) {
LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
wrapper.eq(SchedulePlan::getStatus, "ACTIVE");
if (startDate != null) {
wrapper.ge(SchedulePlan::getScheduledDate, startDate);
}
if (endDate != null) {
wrapper.le(SchedulePlan::getScheduledDate, endDate);
}
wrapper.orderByAsc(SchedulePlan::getScheduledDate);
List<SchedulePlan> schedules = schedulePlanMapper.selectList(wrapper);
// 按日期分组
return schedules.stream()
.collect(Collectors.groupingBy(SchedulePlan::getScheduledDate))
.entrySet().stream()
.map(entry -> {
LocalDate date = entry.getKey();
List<SchedulePlan> daySchedules = entry.getValue();
return TimetableResponse.builder()
.date(date)
.weekDay(date != null ? date.getDayOfWeek().getValue() : null)
.schedules(daySchedules.stream()
.map(schedule -> SchedulePlanResponse.builder()
.id(schedule.getId())
.name(schedule.getName())
.classId(schedule.getClassId())
.courseId(schedule.getCourseId())
.teacherId(schedule.getTeacherId())
.scheduledDate(schedule.getScheduledDate())
.scheduledTime(schedule.getScheduledTime())
.weekDay(schedule.getWeekDay())
.repeatType(schedule.getRepeatType())
.source(schedule.getSource())
.note(schedule.getNote())
.status(schedule.getStatus())
.build())
.collect(Collectors.toList()))
.build();
})
.collect(Collectors.toList());
}
@Override
public List<SchedulePlan> getTodaySchedules(Long teacherId) {
LocalDate today = LocalDate.now();
LambdaQueryWrapper<SchedulePlan> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SchedulePlan::getTeacherId, teacherId);
wrapper.eq(SchedulePlan::getScheduledDate, today);
wrapper.eq(SchedulePlan::getStatus, "ACTIVE");
wrapper.orderByAsc(SchedulePlan::getScheduledTime);
return schedulePlanMapper.selectList(wrapper);
}
@Override
public SchedulePlan getScheduleById(Long scheduleId) {
SchedulePlan schedulePlan = schedulePlanMapper.selectById(scheduleId);
if (schedulePlan == null) {
throw new BusinessException(ErrorCode.DATA_NOT_FOUND, "排课计划不存在");
}
return schedulePlan;
}
@Override
@Transactional
public SchedulePlan createSchedule(Long tenantId, Long teacherId, SchedulePlanCreateRequest request) {
SchedulePlan schedulePlan = new SchedulePlan();
schedulePlan.setTenantId(tenantId);
schedulePlan.setName(request.getName());
schedulePlan.setClassId(request.getClassId());
schedulePlan.setCourseId(request.getCourseId());
schedulePlan.setTeacherId(request.getTeacherId() != null ? request.getTeacherId() : teacherId);
schedulePlan.setScheduledDate(request.getScheduledDate());
schedulePlan.setScheduledTime(request.getScheduledTime());
schedulePlan.setWeekDay(request.getWeekDay());
schedulePlan.setRepeatType(request.getRepeatType() != null ? request.getRepeatType() : "NONE");
schedulePlan.setRepeatEndDate(request.getRepeatEndDate());
schedulePlan.setSource(request.getSource() != null ? request.getSource() : "TEACHER");
schedulePlan.setNote(request.getNote());
schedulePlan.setStatus("ACTIVE");
schedulePlanMapper.insert(schedulePlan);
log.info("排课创建成功id={}, name={}, scheduledDate={}",
schedulePlan.getId(), schedulePlan.getName(), schedulePlan.getScheduledDate());
return schedulePlan;
}
@Override
@Transactional
public SchedulePlan updateSchedule(Long scheduleId, SchedulePlanUpdateRequest request) {
SchedulePlan schedulePlan = getScheduleById(scheduleId);
if (StringUtils.hasText(request.getName())) {
schedulePlan.setName(request.getName());
}
if (request.getCourseId() != null) {
schedulePlan.setCourseId(request.getCourseId());
}
if (request.getTeacherId() != null) {
schedulePlan.setTeacherId(request.getTeacherId());
}
if (request.getScheduledDate() != null) {
schedulePlan.setScheduledDate(request.getScheduledDate());
}
if (StringUtils.hasText(request.getScheduledTime())) {
schedulePlan.setScheduledTime(request.getScheduledTime());
}
if (request.getWeekDay() != null) {
schedulePlan.setWeekDay(request.getWeekDay());
}
if (StringUtils.hasText(request.getRepeatType())) {
schedulePlan.setRepeatType(request.getRepeatType());
}
if (request.getRepeatEndDate() != null) {
schedulePlan.setRepeatEndDate(request.getRepeatEndDate());
}
if (StringUtils.hasText(request.getStatus())) {
schedulePlan.setStatus(request.getStatus());
}
if (request.getNote() != null) {
schedulePlan.setNote(request.getNote());
}
schedulePlanMapper.updateById(schedulePlan);
log.info("排课更新成功id={}", scheduleId);
return schedulePlan;
}
@Override
@Transactional
public void cancelSchedule(Long scheduleId) {
SchedulePlan schedulePlan = getScheduleById(scheduleId);
schedulePlan.setStatus("CANCELLED");
schedulePlanMapper.updateById(schedulePlan);
log.info("排课取消成功id={}", scheduleId);
}
}

View File

@ -16,6 +16,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.List;
/**
* 教师服务实现类
*/
@ -163,4 +165,19 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
log.info("密码重置成功ID: {}", id);
}
@Override
public List<Teacher> getTeachersByIds(List<Long> teacherIds) {
log.debug("根据 ID 列表查询教师ID 列表:{}", teacherIds);
if (teacherIds == null || teacherIds.isEmpty()) {
return List.of();
}
LambdaQueryWrapper<Teacher> wrapper = new LambdaQueryWrapper<>();
wrapper.in(Teacher::getId, teacherIds)
.eq(Teacher::getStatus, "active");
return teacherMapper.selectList(wrapper);
}
}

View File

@ -27,11 +27,14 @@ spring:
min-idle: 4
flyway:
enabled: true
enabled: true # Flyway 已修复,重新启用
locations: classpath:db/migration
clean-disabled: true
clean-disabled: false
validate-on-migrate: false
baseline-on-migrate: false
baseline-on-migrate: true
baseline-version: 0
repair-on-migrate: true
out-of-order: true
# Druid 连接池配置(开发环境)
druid:

View File

@ -0,0 +1,23 @@
-- =====================================================
-- 幼儿园阅读平台数据库扩展脚本
-- 版本V13
-- 创建时间2026-03-16
-- 描述:添加教师端相关表的缺失字段
-- 状态:已手动执行,字段已存在
-- =====================================================
-- 此迁移已执行,所有字段已存在于数据库中
-- 如果未来需要重新初始化数据库,请确保此脚本在 V1__init_schema.sql 之后正确执行
-- 已添加的字段列表:
-- 1. schedule_plan 表course_id, teacher_id, scheduled_date, scheduled_time, week_day, repeat_type, repeat_end_date, source, note, reminder_sent, reminder_sent_at
-- 2. lesson_feedback 表design_quality, participation, goal_achievement, step_feedbacks, pros, suggestions, activities_done
-- 3. task_template 表task_type, related_course_id, default_duration, is_default, status, created_by
-- 4. student_record 表focus, participation, interest, understanding, domain_achievements
-- 5. lesson 表schedule_plan_id, planned_datetime, start_datetime, end_datetime, actual_duration, overall_rating, participation_rating, completion_note, progress_data, current_lesson_id, current_step_id, lesson_ids, completed_lesson_ids
-- 6. class_teacher 表is_primary
-- 7. clazz 表lesson_count
-- 8. teacher 表lesson_count, feedback_count
-- 9. course 表usage_count, teacher_count, avg_rating
SELECT 'V13 迁移已存在,跳过执行' AS status;

View File

@ -0,0 +1,9 @@
-- =====================================================
-- 幼儿园阅读平台数据库修复脚本
-- 版本V14
-- 描述:修复 teacher 表课时统计字段
-- 状态:已执行,字段已存在
-- =====================================================
-- 此迁移已执行,所有字段已存在于数据库中
SELECT 'V14 迁移已存在,跳过执行' AS status;

View File

@ -0,0 +1,9 @@
-- =====================================================
-- 幼儿园阅读平台数据库修复脚本
-- 版本V15
-- 描述:手动修复迁移
-- 状态:已执行,字段已存在
-- =====================================================
-- 此迁移已执行,所有字段已存在于数据库中
SELECT 'V15 迁移已存在,跳过执行' AS status;