111
This commit is contained in:
1 parent
d2ae6c6ff6
commit
3dfac779ce
4 files changed
+136
-8
No files matched your search
+25
-1
@@ -3,8 +3,32 @@ package com.bsd.pjcloud.admin.mapper.user;
|
||||
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
|
||||
import com.bsd.pjcloud.common.data.datascope.PjcloudBaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
/**
|
||||
* 用户关系数据访问层,负责关系记录的常规持久化及逻辑删除后的安全恢复。
|
||||
*/
|
||||
@Mapper
|
||||
public interface SysUserRelationMapper extends PjcloudBaseMapper<SysUserRelationEntity> {
|
||||
|
||||
}
|
||||
/**
|
||||
* 按唯一键查询已逻辑删除的关系记录,查询范围由租户拦截器限制为当前租户。
|
||||
*
|
||||
* @param subjectUserId 关系主语用户 ID
|
||||
* @param objectUserId 关系对象用户 ID
|
||||
* @param relationTypeId 关系类型 ID
|
||||
* @return 已删除的关系记录,仅返回关系 ID;不存在时返回空
|
||||
*/
|
||||
SysUserRelationEntity selectDeletedByUniqueKey(@Param("subjectUserId") Long subjectUserId,
|
||||
@Param("objectUserId") Long objectUserId,
|
||||
@Param("relationTypeId") Long relationTypeId);
|
||||
|
||||
/**
|
||||
* 恢复指定的已删除关系,并使用本次创建请求覆盖关系时效和备注信息。
|
||||
*
|
||||
* @param relation 包含原关系 ID 以及本次创建请求数据的关系实体
|
||||
* @return 实际恢复的记录数,返回 0 表示记录已被并发恢复或不存在
|
||||
*/
|
||||
int restoreDeletedRelation(@Param("relation") SysUserRelationEntity relation);
|
||||
|
||||
}
|
||||
+45
-6
@@ -26,6 +26,7 @@ import com.bsd.pjcloud.common.core.exception.ErrorCodes;
|
||||
import com.bsd.pjcloud.common.core.util.MsgUtils;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.dao.DuplicateKeyException;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@@ -482,6 +483,12 @@ public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMappe
|
||||
return newUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 为两个已有用户建立关系;若相同唯一键的历史关系已逻辑删除,则恢复原记录而不是重复插入。
|
||||
*
|
||||
* @param dto 关系双方、关系类型、有效期及备注
|
||||
* @return 新增或恢复后的关系 ID
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Long createRelation(SysUserRelationCreateDto dto) {
|
||||
@@ -491,10 +498,38 @@ public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMappe
|
||||
|
||||
SysUserRelationEntity relation = new SysUserRelationEntity();
|
||||
BeanUtils.copyProperties(dto, relation);
|
||||
save(relation);
|
||||
Long relationId = saveOrRestoreRelation(relation);
|
||||
if (isGuardianWardRelationType(relationType)) {
|
||||
// 监护关系主语即监护人,关系保存后直接用其 sys_user.phone 重算学生 guardian_phones。
|
||||
syncStudentGuardianPhones(List.of(relation.getObjectUserId()));
|
||||
}
|
||||
return relationId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增关系或恢复相同唯一键下的已删除关系,兼容唯一索引不包含删除标记的历史表结构。
|
||||
*
|
||||
* @param relation 本次待建立的关系数据
|
||||
* @return 新增或恢复后的关系 ID
|
||||
*/
|
||||
private Long saveOrRestoreRelation(SysUserRelationEntity relation) {
|
||||
SysUserRelationEntity deletedRelation = baseMapper.selectDeletedByUniqueKey(
|
||||
relation.getSubjectUserId(), relation.getObjectUserId(), relation.getRelationTypeId());
|
||||
if (deletedRelation != null) {
|
||||
relation.setRelationId(deletedRelation.getRelationId());
|
||||
if (baseMapper.restoreDeletedRelation(relation) != 1) {
|
||||
throw new RuntimeException("所选用户之间已存在该类型的关系");
|
||||
}
|
||||
return relation.getRelationId();
|
||||
}
|
||||
try {
|
||||
if (!save(relation)) {
|
||||
throw new RuntimeException("用户关系建立失败");
|
||||
}
|
||||
} catch (DuplicateKeyException exception) {
|
||||
// 并发请求可能同时通过前置校验,最终仍以数据库唯一索引兜底并返回可读的业务提示。
|
||||
throw new RuntimeException("所选用户之间已存在该类型的关系", exception);
|
||||
}
|
||||
return relation.getRelationId();
|
||||
}
|
||||
|
||||
@@ -568,6 +603,14 @@ public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMappe
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验关系双方及关系类型;监护人手机号由关系保存后的同步流程直接从用户表读取。
|
||||
*
|
||||
* @param subjectUserId 关系主语用户 ID
|
||||
* @param objectUserId 关系对象用户 ID
|
||||
* @param relationTypeId 关系类型 ID
|
||||
* @return 已校验且当前启用的关系类型
|
||||
*/
|
||||
private SysUserRelationTypeEntity validateRelation(Long subjectUserId, Long objectUserId, Long relationTypeId) {
|
||||
if (Objects.equals(subjectUserId, objectUserId)) {
|
||||
throw new RuntimeException("不能与自己建立用户关系");
|
||||
@@ -576,11 +619,7 @@ public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMappe
|
||||
if (subjectUser == null) {
|
||||
throw new RuntimeException("关系主语用户不存在");
|
||||
}
|
||||
SysUserRelationTypeEntity relationType = validateObjectUserAndRelationType(objectUserId, relationTypeId);
|
||||
if (isGuardianWardRelationType(relationType) && StrUtil.isBlank(subjectUser.getPhone())) {
|
||||
throw new RuntimeException("监护人手机号不能为空");
|
||||
}
|
||||
return relationType;
|
||||
return validateObjectUserAndRelationType(objectUserId, relationTypeId);
|
||||
}
|
||||
|
||||
private SysUserRelationTypeEntity validateObjectUserAndRelationType(Long objectUserId, Long relationTypeId) {
|
||||
|
||||
+23
-1
@@ -3,5 +3,27 @@
|
||||
|
||||
<mapper namespace="com.bsd.pjcloud.admin.mapper.user.SysUserRelationMapper">
|
||||
|
||||
<!-- 唯一索引不包含删除标记,重新建立关系时需要先定位当前租户内的历史删除记录。 -->
|
||||
<select id="selectDeletedByUniqueKey"
|
||||
resultType="com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity">
|
||||
SELECT relation_id AS relationId
|
||||
FROM sys_user_relation
|
||||
WHERE subject_user_id = #{subjectUserId}
|
||||
AND object_user_id = #{objectUserId}
|
||||
AND relation_type_id = #{relationTypeId}
|
||||
AND del_flag = '1'
|
||||
LIMIT 1
|
||||
</select>
|
||||
|
||||
</mapper>
|
||||
<!-- 恢复历史关系时覆盖本次提交的有效期和备注,并通过删除标记防止并发重复恢复。 -->
|
||||
<update id="restoreDeletedRelation">
|
||||
UPDATE sys_user_relation
|
||||
SET effective_time = #{relation.effectiveTime},
|
||||
expire_time = #{relation.expireTime},
|
||||
remark = #{relation.remark},
|
||||
del_flag = '0',
|
||||
update_time = CURRENT_TIMESTAMP
|
||||
WHERE relation_id = #{relation.relationId}
|
||||
AND del_flag = '1'
|
||||
</update>
|
||||
</mapper>
|
||||
+43
@@ -1,14 +1,18 @@
|
||||
package com.bsd.pjcloud.admin.service.impl.user;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.Wrapper;
|
||||
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateDto;
|
||||
import com.bsd.pjcloud.admin.api.entity.SysUser;
|
||||
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
|
||||
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationTypeEntity;
|
||||
import com.bsd.pjcloud.admin.api.vo.user.SysUserRelationGraphVo;
|
||||
import com.bsd.pjcloud.admin.mapper.user.SysUserRelationMapper;
|
||||
import com.bsd.pjcloud.admin.service.SysUserRelationTypeService;
|
||||
import com.bsd.pjcloud.admin.service.SysUserService;
|
||||
import com.bsd.pjcloud.admin.service.SysUserStudentService;
|
||||
import com.bsd.pjcloud.common.core.constant.CommonConstants;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -19,7 +23,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
@@ -164,6 +170,43 @@ class SysUserRelationServiceImplTest {
|
||||
verify(studentService).updateGuardianPhones(FIRST_WARD_USER_ID, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 验证相同关系曾被逻辑删除时,创建接口恢复原记录而不是再次插入触发唯一索引冲突。
|
||||
*/
|
||||
@Test
|
||||
void shouldRestoreDeletedRelationInsteadOfInsertingDuplicate() {
|
||||
SysUserService sysUserService = mock(SysUserService.class);
|
||||
SysUserRelationTypeService relationTypeService = mock(SysUserRelationTypeService.class);
|
||||
SysUserRelationMapper relationMapper = mock(SysUserRelationMapper.class);
|
||||
SysUserRelationServiceImpl service = spy(new SysUserRelationServiceImpl(
|
||||
sysUserService, relationTypeService, mock(SysUserStudentService.class)));
|
||||
ReflectionTestUtils.setField(service, "baseMapper", relationMapper);
|
||||
|
||||
SysUserRelationTypeEntity relationType = createGuardianRelationType();
|
||||
relationType.setStatus(CommonConstants.STATUS_NORMAL);
|
||||
when(sysUserService.getById(GUARDIAN_USER_ID)).thenReturn(createUser(GUARDIAN_USER_ID, "监护人"));
|
||||
when(sysUserService.getById(SECOND_WARD_USER_ID)).thenReturn(createUser(SECOND_WARD_USER_ID, "被监护人二"));
|
||||
when(relationTypeService.getById(RELATION_TYPE_ID)).thenReturn(relationType);
|
||||
doReturn(false).when(service).exists(org.mockito.ArgumentMatchers.<Wrapper<SysUserRelationEntity>>any());
|
||||
doNothing().when(service).syncStudentGuardianPhones(any());
|
||||
|
||||
SysUserRelationEntity deletedRelation = createRelation(99L, SECOND_WARD_USER_ID);
|
||||
when(relationMapper.selectDeletedByUniqueKey(GUARDIAN_USER_ID, SECOND_WARD_USER_ID, RELATION_TYPE_ID))
|
||||
.thenReturn(deletedRelation);
|
||||
when(relationMapper.restoreDeletedRelation(any())).thenReturn(1);
|
||||
SysUserRelationCreateDto dto = new SysUserRelationCreateDto();
|
||||
dto.setSubjectUserId(GUARDIAN_USER_ID);
|
||||
dto.setObjectUserId(SECOND_WARD_USER_ID);
|
||||
dto.setRelationTypeId(RELATION_TYPE_ID);
|
||||
|
||||
Long relationId = service.createRelation(dto);
|
||||
|
||||
assertEquals(99L, relationId);
|
||||
verify(relationMapper).restoreDeletedRelation(any());
|
||||
verify(relationMapper, never()).insert(any());
|
||||
verify(service).syncStudentGuardianPhones(List.of(SECOND_WARD_USER_ID));
|
||||
}
|
||||
|
||||
private SysUserRelationEntity createRelation(Long relationId, Long wardUserId) {
|
||||
SysUserRelationEntity relation = new SysUserRelationEntity();
|
||||
relation.setRelationId(relationId);
|
||||
|
||||
Reference in new issue
Block a user