diff --git a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudentController.java b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudentController.java index f6646b8f9..2a2ae102a 100644 --- a/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudentController.java +++ b/RuoYi-Vue/ruoyi-admin/src/main/java/com/ruoyi/web/controller/train/TrainStudentController.java @@ -1,15 +1,13 @@ package com.ruoyi.web.controller.train; import java.util.List; +import java.util.regex.Pattern; import javax.validation.Valid; + +import com.ruoyi.common.utils.StringUtils; +import com.ruoyi.train.domain.TrainTeacher; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import com.github.pagehelper.PageInfo; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.constant.HttpStatus; @@ -18,6 +16,7 @@ import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.page.TableDataInfo; import com.ruoyi.common.enums.BusinessType; import com.ruoyi.train.domain.dto.StudentCreateDTO; +import com.ruoyi.train.domain.dto.StudentUpdateDTO; import com.ruoyi.train.domain.query.StudentQuery; import com.ruoyi.train.domain.vo.StudentAdminVO; import com.ruoyi.train.domain.vo.StudentDetailVO; @@ -30,6 +29,8 @@ import com.ruoyi.web.service.train.TrainAccountBindingAdminService; @RequestMapping({"/train/student", "/train/students"}) public class TrainStudentController extends BaseController { + private static final Pattern PHONE_PATTERN = Pattern.compile("^1[3-9]\\d{9}$"); + private final ITrainStudentService trainStudentService; private final TrainAccountBindingAdminService bindingAdminService; @@ -76,6 +77,27 @@ public class TrainStudentController extends BaseController return success(trainStudentService.createStudent(dto)); } + /** + * 修改学员信息 (PUT /train/student/{sId}) + */ + @PreAuthorize("@ss.hasPermi('train:student:edit')") + @Log(title = "学员主档", businessType = BusinessType.UPDATE, + isSaveRequestData = false, isSaveResponseData = true) + @PutMapping(value = "/{sId}") + public AjaxResult edit(@PathVariable("sId") String sId, @Valid @RequestBody StudentUpdateDTO dto) + { + dto.setsId(sId); + if (StringUtils.isNotEmpty(dto.getsName()) && dto.getsName().trim().length() > 50) + { + return AjaxResult.error("学员姓名长度不能超过50个字符"); + } + if (StringUtils.isNotEmpty(dto.getsPhone()) && !isValidPhone(dto.getsPhone())) + { + return AjaxResult.error("学员手机号码格式不正确"); + } + return success(trainStudentService.updateTrainStudent(dto)); + } + @PreAuthorize("@ss.hasPermi('train:student:remove')") @Log(title = "学员主档", businessType = BusinessType.DELETE) @DeleteMapping("/{sId}") @@ -83,4 +105,9 @@ public class TrainStudentController extends BaseController { return toAjax(trainStudentService.deleteTrainStudentBySId(sId)); } + + private boolean isValidPhone(String phone) + { + return PHONE_PATTERN.matcher(phone).matches(); + } } diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentMapper.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentMapper.java index 6112ae3f7..9e0476ef3 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentMapper.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/mapper/TrainStudentMapper.java @@ -18,6 +18,14 @@ public interface TrainStudentMapper int insertTrainStudent(TrainStudent trainStudent); + /** + * 修改学员主档(仅更新非空字段,身份信息随身份证变更同步刷新) + * + * @param trainStudent 学员信息(必须携带 sId) + * @return 影响行数 + */ + int updateTrainStudent(TrainStudent trainStudent); + /** * 根据系统用户ID查询学员信息 (del_flag = '0') * diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainStudentService.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainStudentService.java index a3eb6fdff..e4ebb9b55 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainStudentService.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/ITrainStudentService.java @@ -2,6 +2,7 @@ package com.ruoyi.train.service; import java.util.List; import com.ruoyi.train.domain.dto.StudentCreateDTO; +import com.ruoyi.train.domain.dto.StudentUpdateDTO; import com.ruoyi.train.domain.query.StudentQuery; import com.ruoyi.train.domain.vo.StudentDetailVO; import com.ruoyi.train.domain.vo.StudentItemVO; @@ -15,6 +16,14 @@ public interface ITrainStudentService String createStudent(StudentCreateDTO dto); + /** + * 修改学员主档 + * + * @param dto 学员修改请求(携带 sId) + * @return 学员主档ID + */ + String updateTrainStudent(StudentUpdateDTO dto); + /** * 安全删除学员档案(ZERO-REFERENCE SAFE DELETE) * diff --git a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainStudentServiceImpl.java b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainStudentServiceImpl.java index 385c1fcda..eed2c9ccf 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainStudentServiceImpl.java +++ b/RuoYi-Vue/ruoyi-train/src/main/java/com/ruoyi/train/service/impl/TrainStudentServiceImpl.java @@ -13,6 +13,7 @@ import com.ruoyi.common.utils.uuid.IdUtils; import com.ruoyi.train.constant.StudentErrorCode; import com.ruoyi.train.domain.TrainStudent; import com.ruoyi.train.domain.dto.StudentCreateDTO; +import com.ruoyi.train.domain.dto.StudentUpdateDTO; import com.ruoyi.train.domain.query.StudentQuery; import com.ruoyi.train.domain.vo.StudentDetailVO; import com.ruoyi.train.domain.vo.StudentItemVO; @@ -99,6 +100,7 @@ public class TrainStudentServiceImpl implements ITrainStudentService student.setOrgName(trimToNull(dto.getOrgName())); student.setsPhone(trimToNull(dto.getsPhone())); student.setsEmail(trimToNull(dto.getsEmail())); + student.setAvatar(trimToNull(dto.getAvatar())); if (normalizedIdCard != null) { @@ -144,6 +146,93 @@ public class TrainStudentServiceImpl implements ITrainStudentService return student.getsId(); } + /** + * 修改学员主档信息。 + * + *

与 createStudent 保持完全一致的安全语义:姓名必填、身份证格式校验、 + * 身份证以可逆密文 + 冻结查重哈希落库、末四位仅用于脱敏展示。差异点:

+ * + * + * @param dto 学员修改请求(携带 sId) + * @return 学员主档ID + */ + @Override + @Transactional(rollbackFor = Exception.class) + public String updateTrainStudent(StudentUpdateDTO dto) + { + if (dto == null || StringUtils.isEmpty(dto.getsId())) + { + throw new ServiceException("学员ID不能为空"); + } + validate(dto); + + TrainStudent existing = trainStudentMapper.selectTrainStudentBySId(dto.getsId()); + if (existing == null) + { + throw new ServiceException(StudentErrorCode.STUDENT_NOT_FOUND_MESSAGE, + StudentErrorCode.STUDENT_NOT_FOUND); + } + + TrainStudent student = new TrainStudent(); + student.setsId(existing.getsId()); + student.setsName(dto.getsName().trim()); + student.setsGender(trimToNull(dto.getsGender())); + student.setsBirth(dto.getsBirth()); + student.setOrgName(trimToNull(dto.getOrgName())); + student.setsPhone(trimToNull(dto.getsPhone())); + student.setsEmail(trimToNull(dto.getsEmail())); + student.setAvatar(trimToNull(dto.getAvatar())); + student.setFaceRef(trimToNull(dto.getFaceRef())); + student.setDutyLevel(dto.getDutyLevel()); + student.setRemark(trimToNull(dto.getRemark())); + + String normalizedIdCard = normalizeIdCard(dto.getIdCard()); + if (normalizedIdCard != null) + { + String hash = identityProtection.deterministicHash(normalizedIdCard); + // 哈希未变化说明仍是同一人,只有真正变更身份证时才需要全局查重 + if (!hash.equals(existing.getIdCardHash()) + && trainStudentMapper.selectOneByIdCardHash(hash) != null) + { + throw duplicateIdentity(); + } + student.setIdCardCiphertext(identityProtection.encrypt(normalizedIdCard)); + student.setIdCardHash(hash); + student.setIdCardLast4(normalizedIdCard.substring(normalizedIdCard.length() - 4)); + } + else + { + student.setIdCardCiphertext(existing.getIdCardCiphertext()); + student.setIdCardHash(existing.getIdCardHash()); + student.setIdCardLast4(existing.getIdCardLast4()); + } + + student.setUpdateTime(DateUtils.getNowDate()); + try + { + student.setUpdateBy(SecurityUtils.getUsername()); + } + catch (Exception ignored) + { + // 非 Web 单元测试无登录上下文;不使用任何请求数据作为审计用户兜底。 + } + + try + { + trainStudentMapper.updateTrainStudent(student); + } + catch (DuplicateKeyException e) + { + // 应用层预检之外,数据库唯一约束继续封堵并发重复身份证。 + throw duplicateIdentity(); + } + return student.getsId(); + } + private StudentItemVO toItemVO(TrainStudent student) { StudentItemVO item = new StudentItemVO(); @@ -164,6 +253,8 @@ public class TrainStudentServiceImpl implements ITrainStudentService target.setIdCardMasked(maskStoredIdentity(student)); target.setSourceType(student.getSourceType()); target.setStatus(student.getStatus()); + target.setAvatar(student.getAvatar()); + target.setDutyLevel(student.getDutyLevel()); } private String maskStoredIdentity(TrainStudent student) diff --git a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentMapper.xml b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentMapper.xml index 3f5a5b449..459cdcae9 100644 --- a/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentMapper.xml +++ b/RuoYi-Vue/ruoyi-train/src/main/resources/mapper/train/TrainStudentMapper.xml @@ -90,6 +90,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" ) + + + update train_student + + s_name = #{sName}, + s_gender = #{sGender}, + s_birth = #{sBirth}, + org_name = #{orgName}, + s_phone = #{sPhone}, + s_email = #{sEmail}, + id_card_ciphertext = #{idCardCiphertext}, + id_card_hash = #{idCardHash}, + id_card_last4 = #{idCardLast4}, + face_ref = #{faceRef}, + avatar = #{avatar}, + duty_level = #{dutyLevel}, + remark = #{remark}, + update_time = #{updateTime}, + update_by = #{updateBy} + + where s_id = #{sId} and del_flag = '0' + +