增加学员修改逻辑

This commit is contained in:
buliping committed 2026-09-28 17:07:58 +08:00
1 parent 9d6689569c
commit ad533c55fb
5 files changed
+165 -7

No files matched your search

@@ -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();
}
}
@@ -18,6 +18,14 @@ public interface TrainStudentMapper
int insertTrainStudent(TrainStudent trainStudent);
/**
* 修改学员主档(仅更新非空字段,身份信息随身份证变更同步刷新)
*
* @param trainStudent 学员信息(必须携带 sId)
* @return 影响行数
*/
int updateTrainStudent(TrainStudent trainStudent);
/**
* 根据系统用户ID查询学员信息 (del_flag = '0')
*
@@ -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)
*
@@ -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();
}
/**
* 修改学员主档信息。
*
* <p>与 createStudent 保持完全一致的安全语义:姓名必填、身份证格式校验、
* 身份证以可逆密文 + 冻结查重哈希落库、末四位仅用于脱敏展示。差异点:</p>
* <ul>
* <li>身份证未提供时保留库内既有密文,避免误清空已归档的身份信息;</li>
* <li>身份证变更时才做全局查重,哈希未变化视为本人,不做重复判定;</li>
* <li>userId / sourceType / status / delFlag 不属于编辑范围,一律不覆盖。</li>
* </ul>
*
* @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)
@@ -90,6 +90,29 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
)
</insert>
<!-- 修改学员主档:仅覆盖传入的非空字段,userId/sourceType/status/delFlag 不在编辑范围 -->
<update id="updateTrainStudent" parameterType="TrainStudent">
update train_student
<trim prefix="SET" suffixOverrides=",">
<if test="sName != null">s_name = #{sName},</if>
<if test="sGender != null">s_gender = #{sGender},</if>
<if test="sBirth != null">s_birth = #{sBirth},</if>
<if test="orgName != null">org_name = #{orgName},</if>
<if test="sPhone != null">s_phone = #{sPhone},</if>
<if test="sEmail != null">s_email = #{sEmail},</if>
<if test="idCardCiphertext != null">id_card_ciphertext = #{idCardCiphertext},</if>
<if test="idCardHash != null">id_card_hash = #{idCardHash},</if>
<if test="idCardLast4 != null">id_card_last4 = #{idCardLast4},</if>
<if test="faceRef != null">face_ref = #{faceRef},</if>
<if test="avatar != null">avatar = #{avatar},</if>
<if test="dutyLevel != null">duty_level = #{dutyLevel},</if>
<if test="remark != null">remark = #{remark},</if>
update_time = #{updateTime},
update_by = #{updateBy}
</trim>
where s_id = #{sId} and del_flag = '0'
</update>
<!-- 根据系统用户ID查询学员 (仅限未删除 del_flag = '0') -->
<select id="selectTrainStudentByUserId" parameterType="Long" resultMap="TrainStudentResult">
<include refid="selectTrainStudentDomain" />