diff --git a/src/main/java/com/yunhai/bschclassmanage/controller/oapi/oa/DbClassController.java b/src/main/java/com/yunhai/bschclassmanage/controller/oapi/oa/DbClassController.java index d0d6474..7fcfe60 100644 --- a/src/main/java/com/yunhai/bschclassmanage/controller/oapi/oa/DbClassController.java +++ b/src/main/java/com/yunhai/bschclassmanage/controller/oapi/oa/DbClassController.java @@ -66,6 +66,7 @@ public class DbClassController { row.put("SchSerID", schSerId); row.put("TeaTel", teacher.getTel()); row.put("TeaName", teacher.getName()); + row.put("TeaGender", teacher.getGender()); row.put("UserTeaSerID", teacher.getTeaId()); row.put("TeaIDCard", teacher.getIdCard()); row.put("TeaNo", teacher.getSort()); diff --git a/src/main/java/com/yunhai/bschclassmanage/controller/oapi/sch/OapiSchTeacherController.java b/src/main/java/com/yunhai/bschclassmanage/controller/oapi/sch/OapiSchTeacherController.java new file mode 100644 index 0000000..08117fc --- /dev/null +++ b/src/main/java/com/yunhai/bschclassmanage/controller/oapi/sch/OapiSchTeacherController.java @@ -0,0 +1,30 @@ +package com.yunhai.bschclassmanage.controller.oapi.sch; + +import com.yunhai.bschclassmanage.config.Config; +import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO; +import com.yunhai.bschclassmanage.service.impl.teacher.SchTeacherBaseInfoService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; + +@Tag(name = "OapiSchTeacher", description = "开放接口-教师基础信息") +@RestController +@RequestMapping(Config.OAPI + "/sch/") +public class OapiSchTeacherController { + @Autowired private SchTeacherBaseInfoService service; + + @GetMapping("getTeacherBaseInfo") + @Operation(summary = "获取教师基础信息", description = "姓名、性别、身份证、手机;teaId 为平台用户ID") + public ResponseEntity get(@RequestParam Integer schId, @RequestParam Integer teaId) { + return ResponseEntity.ok(service.get(schId, teaId)); + } + + @PostMapping("editTeacherBaseInfo") + @Operation(summary = "编辑教师基础信息并同步基础平台", description = "提交四项资料;手机、身份证可清空,性别0女1男") + public ResponseEntity edit(@Valid @RequestBody TeacherBaseInfoDTO dto) { + return ResponseEntity.ok(service.edit(dto)); + } +} diff --git a/src/main/java/com/yunhai/bschclassmanage/model/dto/TeacherBaseInfoDTO.java b/src/main/java/com/yunhai/bschclassmanage/model/dto/TeacherBaseInfoDTO.java new file mode 100644 index 0000000..fbe1c77 --- /dev/null +++ b/src/main/java/com/yunhai/bschclassmanage/model/dto/TeacherBaseInfoDTO.java @@ -0,0 +1,22 @@ +package com.yunhai.bschclassmanage.model.dto; + +import lombok.Data; +import javax.validation.constraints.*; + +/** 教师基础资料;teaId 为基础平台用户 ID,而非本地教师表主键。 */ +@Data +public class TeacherBaseInfoDTO { + @NotNull @Min(1) + private Integer schId; + @NotNull @Min(1) + private Integer teaId; + @NotBlank @Size(max = 255) + private String name; + /** 0=女,1=男;null=未登记 */ + @Min(0) @Max(1) + private Integer gender; + @Pattern(regexp = "^$|^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "身份证号码格式不正确") + private String idcard; + @Size(max = 255) + private String phone; +} diff --git a/src/main/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoService.java b/src/main/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoService.java new file mode 100644 index 0000000..7da5c06 --- /dev/null +++ b/src/main/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoService.java @@ -0,0 +1,97 @@ +package com.yunhai.bschclassmanage.service.impl.teacher; + +import com.yunhai.bschclassmanage.Repository.SchTeacherGroupRepository; +import com.yunhai.bschclassmanage.Repository.SchTeacherRepository; +import com.yunhai.bschclassmanage.entity.SchTeacher; +import com.yunhai.bschclassmanage.entity.SchTeacherGroup; +import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO; +import com.yunhai.bschclassmanage.annotation.SyncOpLog; +import com.yunhai.bschclassmanage.enums.SyncDataType; +import com.yunhai.platform.openapi.core.YschOpenCall; +import com.yunhai.platform.utils.model.BoaUser; +import com.yunhai.platform.utils.exception.DException; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.stream.Collectors; + +/** 开放接口只修改四项资料,通过平台签名客户端同步基础平台。 */ +@Service +public class SchTeacherBaseInfoService { + @Autowired private SchTeacherRepository teacherRepository; + @Autowired private SchTeacherGroupRepository groupRepository; + @Autowired private YschOpenCall openCall; + @Autowired private SchTeacherCacheService cacheService; + + public TeacherBaseInfoDTO get(Integer schId, Integer teaId) { + requireMembership(schId, teaId); + return toDto(schId, teachers(teaId).get(0)); + } + + @Transactional(rollbackFor = Exception.class) + @SyncOpLog(SyncDataType.SEND_EDIT_TEACHER) + public TeacherBaseInfoDTO edit(TeacherBaseInfoDTO dto) { + requireMembership(dto.getSchId(), dto.getTeaId()); + List teachers = teachers(dto.getTeaId()); + String name = dto.getName() == null ? "" : dto.getName().trim(); + if (name.isEmpty()) throw new DException("姓名不能为空"); + // 同名规则与管理端一致,排除教师自己的所有本地记录。 + List ids = groupRepository.findTeaIdsBySchId(dto.getSchId()); + if (teacherRepository.findByTeaIdIn(ids).stream() + .anyMatch(t -> !dto.getTeaId().equals(t.getTeaId()) && name.equals(t.getName()))) { + throw new DException("该校下已存在同名教师:" + name); + } + for (SchTeacher teacher : teachers) { + teacher.setName(name); + teacher.setGender(dto.getGender()); + teacher.setIdCard(trim(dto.getIdcard())); + teacher.setTel(trim(dto.getPhone())); + teacher.setUpdateDate(new Date()); + teacher.setUpdateUserId(dto.getTeaId()); + } + // 先同步;失败抛异常并回滚本地事务。teaId 可能对应多行,统一修改。 + Map profile = new LinkedHashMap<>(); + profile.put("realName", name); + profile.put("gender", dto.getGender()); + profile.put("phone", trim(dto.getPhone())); + profile.put("idcard", trim(dto.getIdcard())); + BoaUser synced = openCall.post("userbase", "/oapi/user/editBasicInfo/" + dto.getTeaId(), + null, profile, BoaUser.class); + if (synced == null || !dto.getTeaId().equals(synced.getId())) { + throw new DException("基础平台未返回有效教师信息,同步失败"); + } + teacherRepository.saveAll(teachers); + Set schools = groupRepository.findByTeaIdIn(Collections.singletonList(dto.getTeaId())).stream() + .map(SchTeacherGroup::getSchId).collect(Collectors.toSet()); + schools.add(dto.getSchId()); + cacheService.clearTeacherCaches(schools); + return toDto(dto.getSchId(), teachers.get(0)); + } + + private List requireMembership(Integer schId, Integer teaId) { + if (schId == null || schId <= 0 || teaId == null || teaId <= 0) { + throw new DException("学校和教师平台用户ID不能为空"); + } + List memberships = groupRepository.findByTeaIdAndSchId(teaId, schId).stream() + .filter(g -> Integer.valueOf(0).equals(g.getTransferStatus())).collect(Collectors.toList()); + if (memberships.isEmpty()) throw new DException("教师不属于指定学校或已转出"); + return memberships; + } + + private List teachers(Integer teaId) { + List teachers = teacherRepository.findByTeaId(teaId); + if (teachers.isEmpty()) throw new DException("教师不存在"); + return teachers; + } + + private TeacherBaseInfoDTO toDto(Integer schId, SchTeacher teacher) { + TeacherBaseInfoDTO dto = new TeacherBaseInfoDTO(); + dto.setSchId(schId); dto.setTeaId(teacher.getTeaId()); dto.setName(teacher.getName()); + dto.setGender(teacher.getGender()); dto.setIdcard(teacher.getIdCard()); dto.setPhone(teacher.getTel()); + return dto; + } + + private String trim(String value) { return value == null || value.trim().isEmpty() ? null : value.trim(); } +} diff --git a/src/test/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoServiceTest.java b/src/test/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoServiceTest.java new file mode 100644 index 0000000..afb691a --- /dev/null +++ b/src/test/java/com/yunhai/bschclassmanage/service/impl/teacher/SchTeacherBaseInfoServiceTest.java @@ -0,0 +1,74 @@ +package com.yunhai.bschclassmanage.service.impl.teacher; + +import com.yunhai.bschclassmanage.Repository.*; +import com.yunhai.bschclassmanage.entity.*; +import com.yunhai.bschclassmanage.model.dto.TeacherBaseInfoDTO; +import com.yunhai.platform.openapi.core.YschOpenCall; +import com.yunhai.platform.utils.exception.DException; +import com.yunhai.platform.utils.model.BoaUser; +import org.junit.jupiter.api.*; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.*; +import org.mockito.junit.jupiter.MockitoExtension; +import java.util.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.*; +import static org.mockito.ArgumentMatchers.*; + +@ExtendWith(MockitoExtension.class) +class SchTeacherBaseInfoServiceTest { + @InjectMocks private SchTeacherBaseInfoService service; + @Mock private SchTeacherRepository teacherRepository; + @Mock private SchTeacherGroupRepository groupRepository; + @Mock private YschOpenCall openCall; + @Mock private SchTeacherCacheService cacheService; + + private SchTeacherGroup group(int school) { + SchTeacherGroup g = new SchTeacherGroup(); + g.setSchId(school); g.setTeaId(101); g.setGroupId(31); g.setTransferStatus(0); + return g; + } + private TeacherBaseInfoDTO request() { + TeacherBaseInfoDTO d = new TeacherBaseInfoDTO(); + d.setSchId(3); d.setTeaId(101); d.setName("新姓名"); d.setGender(1); d.setPhone(""); + return d; + } + private List givenTeacher() { + when(groupRepository.findByTeaIdAndSchId(101, 3)).thenReturn(Collections.singletonList(group(3))); + SchTeacher a = new SchTeacher(); a.setId(11); a.setTeaId(101); a.setAccount("teacher"); a.setStatus(1); + SchTeacher b = new SchTeacher(); b.setId(12); b.setTeaId(101); b.setAccount("teacher"); b.setStatus(1); + List rows = Arrays.asList(a,b); + when(teacherRepository.findByTeaId(101)).thenReturn(rows); + return rows; + } + + @Test void updatesAllTeacherRowsAndInvalidatesAllSchoolsAfterSync() { + List rows = givenTeacher(); + when(openCall.post(eq("userbase"), eq("/oapi/user/editBasicInfo/101"), isNull(), any(), eq(BoaUser.class))) + .thenReturn(new BoaUser(101, "teacher", "新姓名")); + when(groupRepository.findByTeaIdIn(Collections.singletonList(101))).thenReturn(Arrays.asList(group(3),group(4))); + assertEquals("新姓名", service.edit(request()).getName()); + for (SchTeacher t : rows) { + assertEquals("新姓名", t.getName()); assertEquals(1,t.getGender()); + assertNull(t.getTel()); assertEquals("teacher",t.getAccount()); assertEquals(1,t.getStatus()); + } + InOrder order = inOrder(openCall,teacherRepository,cacheService); + order.verify(openCall).post(eq("userbase"),eq("/oapi/user/editBasicInfo/101"),isNull(),any(),eq(BoaUser.class)); + order.verify(teacherRepository).saveAll(rows); + order.verify(cacheService).clearTeacherCaches(new HashSet<>(Arrays.asList(3,4))); + } + + @Test void rejectsWrongSchoolAndTransferredOutTeacher() { + SchTeacherGroup out = group(3); out.setTransferStatus(1); + when(groupRepository.findByTeaIdAndSchId(101,3)).thenReturn(Collections.singletonList(out)); + assertThrows(DException.class, () -> service.edit(request())); + verifyNoInteractions(openCall,teacherRepository,cacheService); + } + + @Test void syncFailureDoesNotSaveOrClearCache() { + givenTeacher(); + when(openCall.post(anyString(),anyString(),isNull(),any(),eq(BoaUser.class))).thenThrow(new DException("平台失败")); + assertThrows(DException.class, () -> service.edit(request())); + verify(teacherRepository,never()).saveAll(any()); verifyNoInteractions(cacheService); + } +}