feat(school): 教师管理前后端对齐 - 支持loginAccount/classIds字段

- TeacherCreateRequest: 添加@JsonAlias(loginAccount)支持前端字段
- TeacherCreateRequest/UpdateRequest: 支持classIds班级分配
- TeacherResponse: 返回loginAccount、classIds、classNames、lessonCount
- 重置密码接口: 自动生成并返回tempPassword
- 创建/更新教师时处理class_teacher关联

Made-with: Cursor
This commit is contained in:
zhonghua 2026-03-19 14:11:41 +08:00
parent 81dd74662e
commit ccce7e66bb
6 changed files with 154 additions and 11 deletions

View File

@ -1,7 +1,6 @@
package com.reading.platform.controller.school;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
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;
@ -25,14 +24,13 @@ import java.util.List;
public class SchoolTeacherController {
private final TeacherService teacherService;
private final TeacherMapper teacherMapper;
@Operation(summary = "Create teacher")
@PostMapping
public Result<TeacherResponse> createTeacher(@Valid @RequestBody TeacherCreateRequest request) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Teacher teacher = teacherService.createTeacher(tenantId, request);
return Result.success(teacherMapper.toVO(teacher));
return Result.success(teacherService.toTeacherResponse(teacher));
}
@Operation(summary = "Update teacher")
@ -40,7 +38,7 @@ public class SchoolTeacherController {
public Result<TeacherResponse> updateTeacher(@PathVariable Long id, @RequestBody TeacherUpdateRequest request) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Teacher teacher = teacherService.updateTeacherWithTenantCheck(id, tenantId, request);
return Result.success(teacherMapper.toVO(teacher));
return Result.success(teacherService.toTeacherResponse(teacher));
}
@Operation(summary = "Get teacher by ID")
@ -48,7 +46,7 @@ public class SchoolTeacherController {
public Result<TeacherResponse> getTeacher(@PathVariable Long id) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Teacher teacher = teacherService.getTeacherByIdWithTenantCheck(id, tenantId);
return Result.success(teacherMapper.toVO(teacher));
return Result.success(teacherService.toTeacherResponse(teacher));
}
@Operation(summary = "Get teacher page")
@ -60,7 +58,7 @@ public class SchoolTeacherController {
@RequestParam(required = false) String status) {
Long tenantId = SecurityUtils.getCurrentTenantId();
Page<Teacher> page = teacherService.getTeacherPage(tenantId, pageNum, pageSize, keyword, status);
List<TeacherResponse> voList = teacherMapper.toVO(page.getRecords());
List<TeacherResponse> voList = teacherService.toTeacherResponseList(page.getRecords());
return Result.success(PageResult.of(voList, page.getTotal(), page.getCurrent(), page.getSize()));
}
@ -74,10 +72,10 @@ public class SchoolTeacherController {
@Operation(summary = "Reset teacher password")
@PostMapping("/{id}/reset-password")
public Result<Void> resetPassword(@PathVariable Long id, @RequestParam String newPassword) {
public Result<java.util.Map<String, String>> resetPassword(@PathVariable Long id) {
Long tenantId = SecurityUtils.getCurrentTenantId();
teacherService.resetPasswordWithTenantCheck(id, tenantId, newPassword);
return Result.success();
String tempPassword = teacherService.resetPasswordAndReturnTemp(id, tenantId);
return Result.success(java.util.Map.of("tempPassword", tempPassword));
}
}

View File

@ -1,15 +1,19 @@
package com.reading.platform.dto.request;
import com.fasterxml.jackson.annotation.JsonAlias;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "教师创建请求")
public class TeacherCreateRequest {
@NotBlank(message = "用户名不能为空")
@Schema(description = "用户名")
@JsonAlias("loginAccount")
@Schema(description = "用户名/登录账号")
private String username;
@NotBlank(message = "密码不能为空")
@ -20,6 +24,7 @@ public class TeacherCreateRequest {
@Schema(description = "姓名")
private String name;
@NotBlank(message = "手机号不能为空")
@Schema(description = "电话")
private String phone;
@ -32,4 +37,7 @@ public class TeacherCreateRequest {
@Schema(description = "简介")
private String bio;
@Schema(description = "负责班级ID列表")
private List<Long> classIds;
}

View File

@ -3,6 +3,8 @@ package com.reading.platform.dto.request;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Data
@Schema(description = "教师更新请求")
public class TeacherUpdateRequest {
@ -28,4 +30,7 @@ public class TeacherUpdateRequest {
@Schema(description = "状态")
private String status;
@Schema(description = "负责班级ID列表")
private List<Long> classIds;
}

View File

@ -1,10 +1,12 @@
package com.reading.platform.dto.response;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
/**
* 教师响应
@ -21,7 +23,8 @@ public class TeacherResponse {
@Schema(description = "租户 ID")
private Long tenantId;
@Schema(description = "用户名")
@JsonProperty("loginAccount")
@Schema(description = "登录账号")
private String username;
@Schema(description = "姓名")
@ -45,6 +48,15 @@ public class TeacherResponse {
@Schema(description = "状态")
private String status;
@Schema(description = "负责班级ID列表")
private List<Long> classIds;
@Schema(description = "负责班级名称")
private Object classNames;
@Schema(description = "授课次数")
private Integer lessonCount;
@Schema(description = "最后登录时间")
private LocalDateTime lastLoginAt;

View File

@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.reading.platform.dto.request.TeacherCreateRequest;
import com.reading.platform.dto.request.TeacherUpdateRequest;
import com.reading.platform.dto.response.TeacherResponse;
import com.reading.platform.entity.Teacher;
import java.util.List;
@ -63,9 +64,24 @@ public interface TeacherService extends IService<Teacher> {
*/
void resetPasswordWithTenantCheck(Long id, Long tenantId, String newPassword);
/**
* 重置密码并返回临时密码带租户验证
*/
String resetPasswordAndReturnTemp(Long id, Long tenantId);
/**
* 根据 ID 列表查询教师
*/
List<Teacher> getTeachersByIds(List<Long> teacherIds);
/**
* 转换为教师响应含班级授课数等扩展信息
*/
TeacherResponse toTeacherResponse(Teacher teacher);
/**
* 批量转换为教师响应
*/
List<TeacherResponse> toTeacherResponseList(List<Teacher> teachers);
}

View File

@ -6,17 +6,28 @@ import com.reading.platform.common.enums.ErrorCode;
import com.reading.platform.common.exception.BusinessException;
import com.reading.platform.dto.request.TeacherCreateRequest;
import com.reading.platform.dto.request.TeacherUpdateRequest;
import com.reading.platform.dto.response.TeacherResponse;
import com.reading.platform.entity.ClassTeacher;
import com.reading.platform.entity.Clazz;
import com.reading.platform.entity.Lesson;
import com.reading.platform.entity.Teacher;
import com.reading.platform.mapper.ClassTeacherMapper;
import com.reading.platform.mapper.ClazzMapper;
import com.reading.platform.mapper.LessonMapper;
import com.reading.platform.mapper.TeacherMapper;
import com.reading.platform.service.ClassService;
import com.reading.platform.service.TeacherService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
/**
* 教师服务实现类
@ -28,6 +39,11 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
implements TeacherService {
private final TeacherMapper teacherMapper;
private final com.reading.platform.common.mapper.TeacherMapper teacherVoMapper;
private final ClassTeacherMapper classTeacherMapper;
private final ClazzMapper clazzMapper;
private final LessonMapper lessonMapper;
private final ClassService classService;
private final PasswordEncoder passwordEncoder;
@Override
@ -57,6 +73,18 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
teacherMapper.insert(teacher);
// 分配教师到班级
if (!CollectionUtils.isEmpty(request.getClassIds())) {
for (Long classId : request.getClassIds()) {
classService.getClassByIdWithTenantCheck(classId, tenantId);
ClassTeacher classTeacher = new ClassTeacher();
classTeacher.setClassId(classId);
classTeacher.setTeacherId(teacher.getId());
classTeacher.setRole("MAIN");
classTeacherMapper.insert(classTeacher);
}
}
log.info("教师创建成功ID: {}", teacher.getId());
return teacher;
}
@ -92,6 +120,21 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
teacherMapper.updateById(teacher);
// 更新教师班级分配
if (request.getClassIds() != null) {
classTeacherMapper.delete(
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getTeacherId, id)
);
for (Long classId : request.getClassIds()) {
classService.getClassByIdWithTenantCheck(classId, teacher.getTenantId());
ClassTeacher classTeacher = new ClassTeacher();
classTeacher.setClassId(classId);
classTeacher.setTeacherId(id);
classTeacher.setRole("MAIN");
classTeacherMapper.insert(classTeacher);
}
}
log.info("教师更新成功ID: {}", id);
return teacher;
}
@ -200,6 +243,18 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
resetPassword(id, newPassword);
}
@Override
@Transactional
public String resetPasswordAndReturnTemp(Long id, Long tenantId) {
log.info("开始重置密码并返回临时密码ID: {}, tenantId: {}", id, tenantId);
Teacher teacher = getTeacherByIdWithTenantCheck(id, tenantId);
String tempPassword = UUID.randomUUID().toString().replace("-", "").substring(0, 8);
teacher.setPassword(passwordEncoder.encode(tempPassword));
teacherMapper.updateById(teacher);
log.info("密码重置成功ID: {}", id);
return tempPassword;
}
@Override
public List<Teacher> getTeachersByIds(List<Long> teacherIds) {
log.debug("根据 ID 列表查询教师ID 列表:{}", teacherIds);
@ -215,4 +270,53 @@ public class TeacherServiceImpl extends com.baomidou.mybatisplus.extension.servi
return teacherMapper.selectList(wrapper);
}
@Override
public TeacherResponse toTeacherResponse(Teacher teacher) {
if (teacher == null) return null;
TeacherResponse response = teacherVoMapper.toVO(teacher);
enrichTeacherResponse(response, teacher.getId());
return response;
}
@Override
public List<TeacherResponse> toTeacherResponseList(List<Teacher> teachers) {
if (teachers == null) return null;
List<TeacherResponse> list = new ArrayList<>(teachers.size());
for (Teacher teacher : teachers) {
list.add(toTeacherResponse(teacher));
}
return list;
}
private void enrichTeacherResponse(TeacherResponse response, Long teacherId) {
List<ClassTeacher> classTeachers = classTeacherMapper.selectList(
new LambdaQueryWrapper<ClassTeacher>().eq(ClassTeacher::getTeacherId, teacherId)
);
List<Long> classIds = new ArrayList<>();
List<String> classNames = new ArrayList<>();
for (ClassTeacher ct : classTeachers) {
classIds.add(ct.getClassId());
Clazz clazz = clazzMapper.selectById(ct.getClassId());
if (clazz != null) {
classNames.add(clazz.getName());
}
}
response.setClassIds(classIds);
response.setClassNames(classNames.isEmpty() ? null : classNames);
if ("active".equals(response.getStatus())) {
response.setStatus("ACTIVE");
}
long lessonCount = 0;
try {
lessonCount = lessonMapper.selectCount(
new LambdaQueryWrapper<Lesson>().eq(Lesson::getTeacherId, teacherId)
);
} catch (Exception e) {
log.debug("Query lesson count failed: {}", e.getMessage());
}
response.setLessonCount((int) lessonCount);
}
}