yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -37,7 +37,9 @@ import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -65,6 +67,7 @@ */ @Service @Validated @Slf4j public class HrmEmployeeServiceImpl implements HrmEmployeeService { @Resource @@ -201,12 +204,39 @@ adminUserApi.updateUserPassword(employee.getUserId(), updateReqVO.getPassword()); } // åæ¥ç³»ç»è´¦å·å¯å¯¹åºå段ï¼å§å / ææºå· / é¨é¨ / æ§å« / é®ç®±ï¼ if (employee.getUserId() != null) { syncSystemUserForEmployee(employee, updateReqVO); } // 忥æè²ç»å syncEducationList(employeeId, updateReqVO.getEducations()); // 忥工ä½ç»å syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories()); // åæ¥ç´§æ¥è系人 syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts()); } /** * å工修æ¹åï¼æå¯ä¸ä¸å¯¹åºçåæ®µåæ¥å°ç³»ç»è´¦å·ã * * åªåæ¥ï¼å§åâæµç§°ãææºå·âmobileãé¨é¨ãæ§å«ãé®ç®±ã * ç»å½å usernameãå²ä½ãè§è²ãç¶æä¸åæ¥ã * åæ¥å¤±è´¥åªè®°å½æ¥å¿ï¼ä¸é»æå工修æ¹ä¸»æµç¨ã */ private void syncSystemUserForEmployee(HrmEmployeeDO employee, HrmEmployeeSaveReqVO reqVO) { try { AdminUserSyncReqDTO dto = new AdminUserSyncReqDTO(); dto.setUserId(employee.getUserId()); dto.setNickname(reqVO.getName() != null ? reqVO.getName() : employee.getName()); dto.setMobile(reqVO.getPhone() != null ? reqVO.getPhone() : employee.getPhone()); dto.setDeptId(reqVO.getDeptId() != null ? reqVO.getDeptId() : employee.getDeptId()); dto.setSex(reqVO.getGender() != null ? reqVO.getGender() : employee.getGender()); dto.setEmail(reqVO.getEmail() != null ? reqVO.getEmail() : employee.getEmail()); adminUserApi.updateUserForEmployee(dto); } catch (Exception ex) { log.warn("[updateEmployee][åå·¥({}) åæ¥è´¦å·({}) 失败ï¼å·²å¿½ç¥]", employee.getId(), employee.getUserId(), ex); } } private void syncEducationList(Long employeeId, List<HrmEmployeeEducationSaveReqVO> newList) { @@ -249,15 +279,42 @@ @Transactional(rollbackFor = Exception.class) public void deleteEmployee(Long id) { // æ ¡éªåå¨ validateEmployeeExists(id); // 级èå é¤å表 HrmEmployeeDO employee = validateEmployeeExists(id); // 1. èå¨å é¤ç³»ç»è´¦å·ï¼ä¸åå¸äºä»¶ï¼é¿å åç¯ï¼ deleteLinkedSystemUser(employee); // 2. 级èå é¤å表 educationMapper.deleteByEmployeeId(id); workHistoryMapper.deleteByEmployeeId(id); emergencyContactMapper.deleteByEmployeeId(id); // å é¤åå·¥ // 3. å é¤åå·¥ employeeMapper.deleteById(id); } /** * å é¤åå·¥æ¶èå¨å é¤å ¶ç³»ç»è´¦å·ã * * å®å«ï¼ * 1. æªç»å®è´¦å· -> è·³è¿ * 2. å ç½® adminï¼id=1ï¼ -> è·³è¿ * 3. åä¸è´¦å·è¢«å¤æ¡å工档æ¡å¼ç¨ -> è·³è¿ï¼ä¸åå·¥ä¸è´¦å·çº¦å®ä¸ä¸åºåºç°ï¼ */ private void deleteLinkedSystemUser(HrmEmployeeDO employee) { Long userId = employee.getUserId(); if (userId == null) { return; } if (Long.valueOf(1L).equals(userId)) { log.warn("[deleteEmployee][åå·¥({}) ç»å®çæ¯å 置管çåè´¦å·({})ï¼ä¸èªå¨å é¤è´¦å·]", employee.getId(), userId); return; } Long count = employeeMapper.selectCount(HrmEmployeeDO::getUserId, userId); if (count != null && count > 1) { log.warn("[deleteEmployee][è´¦å·({}) å ³èäº {} æ¡å工档æ¡ï¼ä¸èªå¨å é¤è´¦å·]", userId, count); return; } adminUserApi.deleteUserSilently(userId); } private HrmEmployeeDO validateEmployeeExists(Long id) { HrmEmployeeDO employee = employeeMapper.selectById(id); if (employee == null) { yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/listener/HrmEmployeeUserSyncListener.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,116 @@ package cn.iocoder.yudao.module.hrm.service.employee.listener; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeWorkHistoryMapper; import cn.iocoder.yudao.module.system.api.user.event.AdminUserDeletedEvent; import cn.iocoder.yudao.module.system.api.user.event.AdminUserUpdatedEvent; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import java.util.List; /** * ç³»ç»è´¦å·åæ´ -> åå·¥æ¡£æ¡ åæ¥çå¬å¨ * * 设计说æï¼ * 1. åªå¤çãå·²åå¨å工档æ¡ãçè´¦å·ï¼æ²¡æå工档æ¡çè´¦å·ï¼adminãtestãç¨æ·ç®¡çç´æ¥å»ºçè´¦å·ï¼ç´æ¥è·³è¿ï¼ä¸æ¥éã * 2. ä½¿ç¨ {@link EventListener} 忥æ§è¡ï¼ä¸å叿¹å¤äºåä¸äºå¡ï¼ä¿è¯ä¸è´æ§ã * 3. çå¬å¨å åªä½¿ç¨ Mapperï¼ä¸è°ç¨ä¼å次åå¸äºä»¶ç Serviceï¼é¿å æ»å¾ªç¯ã * * @author è¶ çº§ç®¡çå */ @Component @Slf4j public class HrmEmployeeUserSyncListener { @Resource private HrmEmployeeMapper employeeMapper; @Resource private HrmEmployeeEducationMapper educationMapper; @Resource private HrmEmployeeWorkHistoryMapper workHistoryMapper; @Resource private HrmEmployeeEmergencyContactMapper emergencyContactMapper; /** * å é¤è´¦å· -> èå¨å é¤å工档æ¡åå表 */ @EventListener public void onUserDeleted(AdminUserDeletedEvent event) { Long userId = event.getUserId(); if (userId == null) { return; } List<HrmEmployeeDO> employees = employeeMapper.selectList(HrmEmployeeDO::getUserId, userId); if (employees.isEmpty()) { // admin/test/æªå»ºå工档æ¡çè´¦å·ï¼ç´æ¥è·³è¿ return; } if (employees.size() > 1) { // ä¸åå·¥ä¸è´¦å·çº¦å®ä¸ä¸åºåºç°ï¼ä¸ºå®å ¨èµ·è§ä¸èªå¨å é¤ log.error("[onUserDeleted][ç¨æ·({}) å ³èäº {} æ¡å工档æ¡ï¼é¢æä¸º 1 æ¡ï¼è·³è¿èªå¨å é¤]", userId, employees.size()); return; } Long employeeId = employees.get(0).getId(); deleteEmployeeCascade(employeeId); log.info("[onUserDeleted][ç¨æ·({}) å é¤ï¼å·²èå¨å é¤å工档æ¡({})]", userId, employeeId); } /** * ä¿®æ¹è´¦å· -> ååå工档æ¡å¯å¯¹åºå段 */ @EventListener public void onUserUpdated(AdminUserUpdatedEvent event) { Long userId = event.getUserId(); if (userId == null) { return; } List<HrmEmployeeDO> employees = employeeMapper.selectList(HrmEmployeeDO::getUserId, userId); if (employees.isEmpty()) { // 没æå工档æ¡çè´¦å·ï¼ç´æ¥è·³è¿ return; } if (employees.size() > 1) { log.error("[onUserUpdated][ç¨æ·({}) å ³èäº {} æ¡å工档æ¡ï¼é¢æä¸º 1 æ¡ï¼è·³è¿åæ¥]", userId, employees.size()); return; } try { HrmEmployeeDO updateObj = new HrmEmployeeDO(); updateObj.setId(employees.get(0).getId()); if (StrUtil.isNotBlank(event.getNickname())) { updateObj.setName(event.getNickname()); } if (StrUtil.isNotBlank(event.getMobile())) { updateObj.setPhone(event.getMobile()); } if (event.getDeptId() != null) { updateObj.setDeptId(event.getDeptId()); } if (event.getSex() != null && event.getSex() != 0) { updateObj.setGender(event.getSex()); } if (StrUtil.isNotBlank(event.getEmail())) { updateObj.setEmail(event.getEmail()); } employeeMapper.updateById(updateObj); log.info("[onUserUpdated][ç¨æ·({}) ä¿®æ¹ï¼å·²åæ¥å工档æ¡({})]", userId, updateObj.getId()); } catch (Exception ex) { // ä¿®æ¹åæ¥å¤±è´¥ä¸å½±åè´¦å·ä¿®æ¹ä¸»æµç¨ï¼ä» è®°å½æ¥å¿ log.error("[onUserUpdated][ç¨æ·({}) 忥å工档æ¡å¤±è´¥]", userId, ex); } } private void deleteEmployeeCascade(Long employeeId) { educationMapper.deleteByEmployeeId(employeeId); workHistoryMapper.deleteByEmployeeId(employeeId); emergencyContactMapper.deleteByEmployeeId(employeeId); employeeMapper.deleteById(employeeId); } } yudao-module-hrm/src/test/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImplSyncTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,179 @@ package cn.iocoder.yudao.module.hrm.service.employee; import cn.iocoder.yudao.module.hrm.controller.admin.employee.vo.HrmEmployeeSaveReqVO; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeWorkHistoryMapper; import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO; import cn.iocoder.yudao.module.infra.api.config.ConfigApi; import cn.iocoder.yudao.module.mes.api.cal.MesCalTeamApi; import cn.iocoder.yudao.module.system.api.dept.DeptApi; import cn.iocoder.yudao.module.system.api.dept.PostApi; import cn.iocoder.yudao.module.system.api.permission.RoleApi; import cn.iocoder.yudao.module.system.api.user.AdminUserApi; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** * åå·¥å é¤/ä¿®æ¹ -> ç³»ç»è´¦å· 忥åå æµè¯ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class HrmEmployeeServiceImplSyncTest { private static final Long EMPLOYEE_ID = 1L; private static final Long USER_ID = 200L; @Mock private HrmEmployeeMapper employeeMapper; @Mock private HrmEmployeeEducationMapper educationMapper; @Mock private HrmEmployeeWorkHistoryMapper workHistoryMapper; @Mock private HrmEmployeeEmergencyContactMapper emergencyContactMapper; @Mock private HrmNoRedisDAO noRedisDAO; @Mock private DeptApi deptApi; @Mock private PostApi postApi; @Mock private AdminUserApi adminUserApi; @Mock private RoleApi roleApi; @Mock private MesCalTeamApi mesCalTeamApi; @Mock private ConfigApi configApi; @Mock private HrmEmployeeService self; @InjectMocks private HrmEmployeeServiceImpl service; private HrmEmployeeDO employee(Long id, Long userId) { return HrmEmployeeDO.builder().id(id).userId(userId).build(); } // ==================== å é¤åå·¥ ==================== @Test @DisplayName("å é¤åå·¥ï¼å·²ç»å®è´¦å·æ¶ï¼èå¨å é¤ç³»ç»è´¦å·") void deleteEmployee_withUser_deleteAccount() { when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(employee(EMPLOYEE_ID, USER_ID)); when(employeeMapper.selectCount(any(SFunction.class), eq(USER_ID))).thenReturn(1L); service.deleteEmployee(EMPLOYEE_ID); verify(adminUserApi).deleteUserSilently(USER_ID); verify(educationMapper).deleteByEmployeeId(EMPLOYEE_ID); verify(workHistoryMapper).deleteByEmployeeId(EMPLOYEE_ID); verify(emergencyContactMapper).deleteByEmployeeId(EMPLOYEE_ID); verify(employeeMapper).deleteById(EMPLOYEE_ID); } @Test @DisplayName("å é¤åå·¥ï¼æªç»å®è´¦å·æ¶ï¼ä¸è°ç¨è´¦å·å é¤") void deleteEmployee_noUser_skip() { when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(employee(EMPLOYEE_ID, null)); service.deleteEmployee(EMPLOYEE_ID); verify(adminUserApi, never()).deleteUserSilently(anyLong()); verify(employeeMapper).deleteById(EMPLOYEE_ID); } @Test @DisplayName("å é¤åå·¥ï¼è´¦å·æ¯å ç½® admin(id=1) æ¶ï¼ä¸å é¤è´¦å·") void deleteEmployee_adminUser_skip() { when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(employee(EMPLOYEE_ID, 1L)); service.deleteEmployee(EMPLOYEE_ID); verify(adminUserApi, never()).deleteUserSilently(anyLong()); verify(employeeMapper).deleteById(EMPLOYEE_ID); } @Test @DisplayName("å é¤åå·¥ï¼è´¦å·è¢«å¤æ¡å工档æ¡å¼ç¨æ¶ï¼ä¸å é¤è´¦å·") void deleteEmployee_multiEmployees_skip() { when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(employee(EMPLOYEE_ID, USER_ID)); when(employeeMapper.selectCount(any(SFunction.class), eq(USER_ID))).thenReturn(2L); service.deleteEmployee(EMPLOYEE_ID); verify(adminUserApi, never()).deleteUserSilently(anyLong()); verify(employeeMapper).deleteById(EMPLOYEE_ID); } // ==================== ä¿®æ¹åå·¥ ==================== @Test @DisplayName("ä¿®æ¹åå·¥ï¼æå¯å¯¹åºåæ®µåæ¥å°è´¦å·") void updateEmployee_syncToAccount() { HrmEmployeeDO old = HrmEmployeeDO.builder() .id(EMPLOYEE_ID).userId(USER_ID).name("æ§å").phone("13800000000") .deptId(10L).gender(1).email("old@x.com").build(); when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(old); when(employeeMapper.selectByPhone("13900000000")).thenReturn(null); HrmEmployeeSaveReqVO reqVO = new HrmEmployeeSaveReqVO(); reqVO.setId(EMPLOYEE_ID); reqVO.setName("æ°å"); reqVO.setPhone("13900000000"); reqVO.setDeptId(11L); reqVO.setPostId(2L); reqVO.setGender(2); reqVO.setEmail("new@x.com"); service.updateEmployee(reqVO); ArgumentCaptor<AdminUserSyncReqDTO> captor = ArgumentCaptor.forClass(AdminUserSyncReqDTO.class); verify(adminUserApi).updateUserForEmployee(captor.capture()); AdminUserSyncReqDTO dto = captor.getValue(); assertEquals(USER_ID, dto.getUserId()); assertEquals("æ°å", dto.getNickname()); assertEquals("13900000000", dto.getMobile()); assertEquals(11L, dto.getDeptId()); assertEquals(2, dto.getSex()); assertEquals("new@x.com", dto.getEmail()); } @Test @DisplayName("ä¿®æ¹åå·¥ï¼æªç»å®è´¦å·æ¶ï¼ä¸åæ¥è´¦å·") void updateEmployee_noUser_skip() { HrmEmployeeDO old = HrmEmployeeDO.builder() .id(EMPLOYEE_ID).userId(null).name("æ§å").phone("13800000000") .deptId(10L).gender(1).build(); when(employeeMapper.selectById(EMPLOYEE_ID)).thenReturn(old); HrmEmployeeSaveReqVO reqVO = new HrmEmployeeSaveReqVO(); reqVO.setId(EMPLOYEE_ID); reqVO.setName("æ°å"); reqVO.setPhone("13800000000"); reqVO.setDeptId(10L); reqVO.setPostId(2L); reqVO.setGender(1); service.updateEmployee(reqVO); verify(adminUserApi, never()).updateUserForEmployee(any()); } } yudao-module-hrm/src/test/java/cn/iocoder/yudao/module/hrm/service/employee/listener/HrmEmployeeUserSyncListenerTest.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,132 @@ package cn.iocoder.yudao.module.hrm.service.employee.listener; import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEducationMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeEmergencyContactMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper; import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeWorkHistoryMapper; import cn.iocoder.yudao.module.system.api.user.event.AdminUserDeletedEvent; import cn.iocoder.yudao.module.system.api.user.event.AdminUserUpdatedEvent; import com.baomidou.mybatisplus.core.toolkit.support.SFunction; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; import java.util.Collections; import java.util.List; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; /** * è´¦å· -> åå·¥æ¡£æ¡ åæ¥çå¬å¨åå æµè¯ */ @ExtendWith(MockitoExtension.class) @MockitoSettings(strictness = Strictness.LENIENT) class HrmEmployeeUserSyncListenerTest { private static final Long USER_ID = 200L; @Mock private HrmEmployeeMapper employeeMapper; @Mock private HrmEmployeeEducationMapper educationMapper; @Mock private HrmEmployeeWorkHistoryMapper workHistoryMapper; @Mock private HrmEmployeeEmergencyContactMapper emergencyContactMapper; @InjectMocks private HrmEmployeeUserSyncListener listener; private void mockEmployees(List<HrmEmployeeDO> employees) { when(employeeMapper.selectList(any(SFunction.class), eq(USER_ID))).thenReturn(employees); } @Test @DisplayName("å é¤è´¦å·ï¼æ²¡æåå·¥æ¡£æ¡æ¶ç´æ¥è·³è¿ï¼ä¸æ¥é") void onUserDeleted_noEmployee_skip() { mockEmployees(Collections.emptyList()); listener.onUserDeleted(new AdminUserDeletedEvent(USER_ID)); verify(employeeMapper, never()).deleteById(anyLong()); verifyNoInteractions(educationMapper, workHistoryMapper, emergencyContactMapper); } @Test @DisplayName("å é¤è´¦å·ï¼åå¨ 1 æ¡åå·¥æ¡£æ¡æ¶ï¼èå¨å é¤åå·¥åå表") void onUserDeleted_oneEmployee_deleteCascade() { mockEmployees(Collections.singletonList(HrmEmployeeDO.builder().id(10L).userId(USER_ID).build())); listener.onUserDeleted(new AdminUserDeletedEvent(USER_ID)); verify(educationMapper).deleteByEmployeeId(10L); verify(workHistoryMapper).deleteByEmployeeId(10L); verify(emergencyContactMapper).deleteByEmployeeId(10L); verify(employeeMapper).deleteById(10L); } @Test @DisplayName("å é¤è´¦å·ï¼åå¨å¤æ¡åå·¥æ¡£æ¡æ¶ï¼ä¸èªå¨å é¤å¹¶è·³è¿") void onUserDeleted_multiEmployees_skip() { mockEmployees(List.of( HrmEmployeeDO.builder().id(10L).userId(USER_ID).build(), HrmEmployeeDO.builder().id(11L).userId(USER_ID).build())); listener.onUserDeleted(new AdminUserDeletedEvent(USER_ID)); verify(employeeMapper, never()).deleteById(anyLong()); verifyNoInteractions(educationMapper, workHistoryMapper, emergencyContactMapper); } @Test @DisplayName("ä¿®æ¹è´¦å·ï¼æ²¡æåå·¥æ¡£æ¡æ¶ç´æ¥è·³è¿") void onUserUpdated_noEmployee_skip() { mockEmployees(Collections.emptyList()); listener.onUserUpdated(new AdminUserUpdatedEvent(USER_ID, "æ°åå", "13900000000", 11L, 2, "new@x.com")); verify(employeeMapper, never()).updateById(any(HrmEmployeeDO.class)); } @Test @DisplayName("ä¿®æ¹è´¦å·ï¼ååå¯å¯¹åºå段") void onUserUpdated_syncFields() { mockEmployees(Collections.singletonList(HrmEmployeeDO.builder().id(10L).userId(USER_ID).build())); listener.onUserUpdated(new AdminUserUpdatedEvent(USER_ID, "æ°åå", "13900000000", 11L, 2, "new@x.com")); ArgumentCaptor<HrmEmployeeDO> captor = ArgumentCaptor.forClass(HrmEmployeeDO.class); verify(employeeMapper).updateById(captor.capture()); HrmEmployeeDO updateObj = captor.getValue(); assertEquals(10L, updateObj.getId()); assertEquals("æ°åå", updateObj.getName()); assertEquals("13900000000", updateObj.getPhone()); assertEquals(11L, updateObj.getDeptId()); assertEquals(2, updateObj.getGender()); assertEquals("new@x.com", updateObj.getEmail()); } @Test @DisplayName("ä¿®æ¹è´¦å·ï¼æ§å«æªç¥(0)æ¶ä¸è¦çåå·¥æ§å«") void onUserUpdated_sexUnknown_notOverride() { mockEmployees(Collections.singletonList(HrmEmployeeDO.builder().id(10L).userId(USER_ID).build())); listener.onUserUpdated(new AdminUserUpdatedEvent(USER_ID, null, null, null, 0, null)); ArgumentCaptor<HrmEmployeeDO> captor = ArgumentCaptor.forClass(HrmEmployeeDO.class); verify(employeeMapper).updateById(captor.capture()); assertNull(captor.getValue().getGender()); assertNull(captor.getValue().getName()); assertNull(captor.getValue().getPhone()); } } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApi.java
@@ -3,6 +3,7 @@ import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import java.util.Collection; import java.util.Collections; @@ -33,6 +34,25 @@ void updateUserPassword(Long id, String password); /** * å é¤ç¨æ·ï¼ä½**ä¸åå¸** {@code AdminUserDeletedEvent} * * ä¾ hrm 模åå é¤åå·¥æ¶èå¨å é¤è´¦å·ï¼é¿å äºä»¶åç¯ï¼åå·¥å â è´¦å·å â åå·¥å ï¼ã * * @param id ç¨æ·ç¼å· */ void deleteUserSilently(Long id); /** * å°å工档æ¡åæ®µåæ¥å°ç³»ç»è´¦å·ï¼ä½**ä¸åå¸** {@code AdminUserUpdatedEvent} * * ä¾ hrm 模åä¿®æ¹åå·¥æ¶åååæ¥è´¦å·ï¼é¿å äºä»¶åç¯ã * åªæ´æ° DTO ä¸é空çåæ®µã * * @param reqDTO åæ¥ä¿¡æ¯ */ void updateUserForEmployee(AdminUserSyncReqDTO reqDTO); /** * éè¿ç¨æ· ID æ¥è¯¢ç¨æ· * * @param id ç¨æ·ID yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/AdminUserApiImpl.java
@@ -7,6 +7,7 @@ import cn.iocoder.yudao.framework.datapermission.core.util.DataPermissionUtils; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserCreateReqDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; @@ -41,6 +42,16 @@ } @Override public void deleteUserSilently(Long id) { userService.deleteUserSilently(id); } @Override public void updateUserForEmployee(AdminUserSyncReqDTO reqDTO) { userService.updateUserForEmployee(reqDTO); } @Override public Long createUser(AdminUserCreateReqDTO createReqDTO) { UserSaveReqVO createReqVO = new UserSaveReqVO(); createReqVO.setUsername(createReqDTO.getUsername()); yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/dto/AdminUserSyncReqDTO.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,45 @@ package cn.iocoder.yudao.module.system.api.user.dto; import lombok.Data; /** * å工档æ¡åæ¥ç³»ç»è´¦å· Request DTO * * ç± hrm 模åè°ç¨ï¼åªåæ¥è½ä¸ä¸å¯¹åºçåæ®µï¼ä¸ºç©ºçåæ®µä¸æ´æ°ã * * @author è¶ çº§ç®¡çå */ @Data public class AdminUserSyncReqDTO { /** * ç¨æ·ç¼å·ï¼å¿ å¡«ï¼ */ private Long userId; /** * æµç§°ï¼å¯¹åºåå·¥å§åï¼ */ private String nickname; /** * ææºå· */ private String mobile; /** * é¨é¨ç¼å· */ private Long deptId; /** * æ§å«ï¼1-ç·ï¼2-女 */ private Integer sex; /** * é®ç®± */ private String email; } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/event/AdminUserDeletedEvent.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,23 @@ package cn.iocoder.yudao.module.system.api.user.event; import lombok.AllArgsConstructor; import lombok.Getter; /** * ç³»ç»ç¨æ·å é¤äºä»¶ * * ç¨äº system 模åå é¤è´¦å·åï¼éç¥ hrm 模åèå¨å é¤å工档æ¡ã * 注æï¼system ä¸è½ååä¾èµ hrmï¼å æ¤éè¿ Spring äºä»¶è§£è¦ã * * @author è¶ çº§ç®¡çå */ @Getter @AllArgsConstructor public class AdminUserDeletedEvent { /** * 被å é¤çç¨æ·ç¼å· */ private final Long userId; } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/user/event/AdminUserUpdatedEvent.java
¶Ô±ÈÐÂÎļþ @@ -0,0 +1,48 @@ package cn.iocoder.yudao.module.system.api.user.event; import lombok.AllArgsConstructor; import lombok.Getter; /** * ç³»ç»ç¨æ·ä¿®æ¹äºä»¶ * * ç¨äº system 模åä¿®æ¹è´¦å·åï¼éç¥ hrm 模åæå¯å¯¹åºå段ååå°å工档æ¡ã * 说æï¼äºä»¶åªæºå¸¦å¯åæ¥åæ®µï¼æªä¿®æ¹/䏿¯æçåæ®µä¸º nullï¼ç嬿¹æéç©ºåæ®µæ´æ°ã * * @author è¶ çº§ç®¡çå */ @Getter @AllArgsConstructor public class AdminUserUpdatedEvent { /** * ç¨æ·ç¼å· */ private final Long userId; /** * æµç§°ï¼å¯¹åºåå·¥å§åï¼ */ private final String nickname; /** * ææºå·ï¼å¯¹åºåå·¥ææºå·ï¼ */ private final String mobile; /** * é¨é¨ç¼å· */ private final Long deptId; /** * æ§å«ï¼1-ç·ï¼2-女ï¼0-æªç¥ */ private final Integer sex; /** * é®ç®± */ private final String email; } yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserService.java
@@ -10,6 +10,7 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; import jakarta.validation.Valid; @@ -96,6 +97,15 @@ void deleteUser(Long id); /** * å é¤ç¨æ·ï¼ä¸åå¸ AdminUserDeletedEventï¼ * * ä¾ hrm 模åå é¤åå·¥æ¶èå¨å é¤è´¦å·ï¼é¿å äºä»¶åç¯ã * * @param id ç¨æ·ç¼å· */ void deleteUserSilently(Long id); /** * æ¹éå é¤ç¨æ· * * @param ids ç¨æ·ç¼å·æ°ç» @@ -103,6 +113,15 @@ void deleteUserList(List<Long> ids); /** * å°å工档æ¡åæ®µåæ¥å°ç³»ç»è´¦å·ï¼ä¸åå¸ AdminUserUpdatedEventï¼ * * åªæ´æ° DTO ä¸é空çåæ®µã * * @param reqDTO åæ¥ä¿¡æ¯ */ void updateUserForEmployee(AdminUserSyncReqDTO reqDTO); /** * éè¿ç¨æ·åæ¥è¯¢ç¨æ· * * @param username ç¨æ·å yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/user/AdminUserServiceImpl.java
@@ -20,6 +20,9 @@ import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserImportRespVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserPageReqVO; import cn.iocoder.yudao.module.system.controller.admin.user.vo.user.UserSaveReqVO; import cn.iocoder.yudao.module.system.api.user.dto.AdminUserSyncReqDTO; import cn.iocoder.yudao.module.system.api.user.event.AdminUserDeletedEvent; import cn.iocoder.yudao.module.system.api.user.event.AdminUserUpdatedEvent; import cn.iocoder.yudao.module.system.dal.dataobject.dept.DeptDO; import cn.iocoder.yudao.module.system.dal.dataobject.dept.UserPostDO; import cn.iocoder.yudao.module.system.dal.dataobject.user.AdminUserDO; @@ -38,6 +41,7 @@ import jakarta.annotation.Resource; import jakarta.validation.ConstraintViolationException; import lombok.extern.slf4j.Slf4j; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; @@ -91,6 +95,9 @@ @Resource private AdminUserProducer adminUserProducer; @Resource private ApplicationEventPublisher eventPublisher; @Override @Transactional(rollbackFor = Exception.class) @@ -172,6 +179,11 @@ // 3. è®°å½æä½æ¥å¿ä¸ä¸æ LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class)); LogRecordContext.putVariable("user", oldUser); // 4. éç¥ hrm 模å忥å工档æ¡ï¼æ²¡æå工档æ¡çè´¦å·ä¼è¢«çå¬å¨ç´æ¥è·³è¿ï¼ eventPublisher.publishEvent(new AdminUserUpdatedEvent(updateReqVO.getId(), updateReqVO.getNickname(), updateReqVO.getMobile(), updateReqVO.getDeptId(), updateReqVO.getSex(), updateReqVO.getEmail())); } private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) { @@ -197,6 +209,7 @@ } @Override @Transactional(rollbackFor = Exception.class) public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) { // 1. æ ¡éªæ£ç¡®æ§ AdminUserDO oldUser = validateUserExists(id); @@ -208,6 +221,10 @@ // 3. æµç§° / 头åååæ¶ï¼åéæ¶æ¯ä¾ä¸æ¸¸è®¢é ï¼å¦ IM æ¨¡åæ¨ FRIEND_INFO_UPDATEDï¼ publishUserProfileUpdatedIfChanged(oldUser, reqVO.getNickname(), reqVO.getAvatar()); // 4. éç¥ hrm 模å忥å工档æ¡ï¼æ²¡æå工档æ¡çè´¦å·ä¼è¢«çå¬å¨ç´æ¥è·³è¿ï¼ eventPublisher.publishEvent(new AdminUserUpdatedEvent(id, reqVO.getNickname(), reqVO.getMobile(), null, reqVO.getSex(), reqVO.getEmail())); } /** @@ -273,6 +290,18 @@ @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = "{{#id}}", success = SYSTEM_USER_DELETE_SUCCESS) public void deleteUser(Long id) { doDeleteUser(id); // éç¥ hrm 模åèå¨å é¤å工档æ¡ï¼æ²¡æå工档æ¡çè´¦å·ä¼è¢«çå¬å¨ç´æ¥è·³è¿ï¼ eventPublisher.publishEvent(new AdminUserDeletedEvent(id)); } @Override @Transactional(rollbackFor = Exception.class) public void deleteUserSilently(Long id) { doDeleteUser(id); } private void doDeleteUser(Long id) { // 1. æ ¡éªç¨æ·åå¨ AdminUserDO user = validateUserExists(id); @@ -298,6 +327,44 @@ permissionService.processUserDeleted(id); userPostMapper.deleteByUserId(id); }); // 3. éç¥ hrm 模åèå¨å é¤åå·¥æ¡£æ¡ ids.forEach(id -> eventPublisher.publishEvent(new AdminUserDeletedEvent(id))); } @Override public void updateUserForEmployee(AdminUserSyncReqDTO reqDTO) { if (reqDTO == null || reqDTO.getUserId() == null) { return; } // 1. æ ¡éªç¨æ·åå¨ AdminUserDO user = validateUserExists(reqDTO.getUserId()); // 2. æ ¡éªææºå· / é®ç®±å¯ä¸ï¼ä¸æ§å¼ä¸åæ¶ææ ¡éªï¼ if (StrUtil.isNotBlank(reqDTO.getMobile()) && !ObjUtil.equal(user.getMobile(), reqDTO.getMobile())) { validateMobileUnique(reqDTO.getUserId(), reqDTO.getMobile()); } if (StrUtil.isNotBlank(reqDTO.getEmail()) && !ObjUtil.equal(user.getEmail(), reqDTO.getEmail())) { validateEmailUnique(reqDTO.getUserId(), reqDTO.getEmail()); } // 3. åªæ´æ°éç©ºåæ®µ AdminUserDO updateObj = new AdminUserDO(); updateObj.setId(reqDTO.getUserId()); if (StrUtil.isNotBlank(reqDTO.getNickname())) { updateObj.setNickname(reqDTO.getNickname()); } if (StrUtil.isNotBlank(reqDTO.getMobile())) { updateObj.setMobile(reqDTO.getMobile()); } if (reqDTO.getDeptId() != null) { updateObj.setDeptId(reqDTO.getDeptId()); } if (reqDTO.getSex() != null) { updateObj.setSex(reqDTO.getSex()); } if (StrUtil.isNotBlank(reqDTO.getEmail())) { updateObj.setEmail(reqDTO.getEmail()); } userMapper.updateById(updateObj); } @Override