feat:监护人维护人脸流程

This commit is contained in:
dzc committed 2026-07-21 14:26:04 +08:00
1 parent 0149da2823
commit be37903a1a
15 files changed
+592 -13

No files matched your search

@@ -0,0 +1,3 @@
-- 为人脸审核记录补充拒绝原因,供审核详情页面展示。
ALTER TABLE sys_user_face
ADD COLUMN reject_reason varchar(500) DEFAULT NULL COMMENT '审核拒绝原因' AFTER process_time;
+2 -1
View File
@@ -43,6 +43,7 @@ CREATE TABLE `sys_user_face`
process_id varchar(256) COMMENT '流程id',
process_status varchar(10) COMMENT '审核状体',
process_time datetime COMMENT '审核时间',
reject_reason varchar(500) DEFAULT NULL COMMENT '审核拒绝原因',
face_img varchar(256) NOT NULL COMMENT '人脸图片',
create_by varchar(64) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '创建人',
create_time datetime DEFAULT NULL COMMENT '创建时间',
@@ -809,4 +810,4 @@ CREATE TABLE integral_transaction
del_flag char(1) COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '删除标记',
tenant_id bigint DEFAULT NULL COMMENT '租户ID',
PRIMARY KEY (id)
) ENGINE = InnoDB COMMENT ='积分记录';
) ENGINE = InnoDB COMMENT ='积分记录';
@@ -15,6 +15,11 @@ public class SysUserFaceDto extends SysUserFaceEntity implements BaseIsIncludeTr
private List<Long> userIds;
/**
* 审核状态集合
*/
private List<String> processStatuses;
private List<Long> deptIds;
private String isGlobalFace;
@@ -0,0 +1,27 @@
package com.bsd.pjcloud.admin.api.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
/**
* 监护人代被监护人提交人脸审核。
*/
@Data
@Schema(description = "监护人代被监护人提交人脸审核")
public class SysUserFaceGuardianWardCreateDto {
@NotNull(message = "被监护人不能为空")
@Schema(description = "被监护人用户ID")
private Long wardUserId;
@NotEmpty(message = "请上传人脸图片")
@Size(min = 1, max = 2, message = "请上传1至2张人脸图片")
@Schema(description = "人脸图片地址,最多2张")
private List<@NotBlank(message = "人脸图片地址不能为空") String> faceImgs;
}
@@ -67,6 +67,12 @@ public class SysUserFaceEntity extends Model<SysUserFaceEntity> {
@Schema(description = "审核时间")
private LocalDateTime processTime;
/**
* 审核拒绝原因
*/
@Schema(description = "审核拒绝原因")
private String rejectReason;
/**
* 人脸图片
*/
@@ -114,4 +120,4 @@ public class SysUserFaceEntity extends Model<SysUserFaceEntity> {
*/
@Schema(description = "租户ID")
private Long tenantId;
}
}
@@ -0,0 +1,30 @@
package com.bsd.pjcloud.admin.api.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 被监护人选择项。
*/
@Data
@Schema(description = "被监护人选择项")
public class SysUserFaceGuardianWardOptionVo {
/**
* 被监护人用户ID
*/
@Schema(description = "被监护人用户ID")
private Long wardUserId;
/**
* 被监护人姓名
*/
@Schema(description = "被监护人姓名")
private String wardName;
/**
* 被监护人头像
*/
@Schema(description = "被监护人头像")
private String wardAvatar;
}
@@ -0,0 +1,30 @@
package com.bsd.pjcloud.admin.api.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 被监护人人脸审核撤回结果。
*/
@Data
@Schema(description = "被监护人人脸审核撤回结果")
public class SysUserFaceGuardianWardWithdrawVo {
/**
* 被监护人用户ID
*/
@Schema(description = "被监护人用户ID")
private Long wardUserId;
/**
* 被监护人姓名
*/
@Schema(description = "被监护人姓名")
private String wardName;
/**
* 撤回后用于重新编辑的人脸图片
*/
@Schema(description = "撤回后用于重新编辑的人脸图片")
private String faceImg;
}
@@ -8,7 +8,10 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceDto;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceGuardianWardCreateDto;
import com.bsd.pjcloud.admin.api.entity.SysUserFaceEntity;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardOptionVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardWithdrawVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceVo;
import com.bsd.pjcloud.admin.service.SysUserFaceService;
import com.bsd.pjcloud.common.core.util.R;
@@ -29,6 +32,7 @@ import org.springframework.http.HttpHeaders;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.Operation;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -88,6 +92,62 @@ public class SysUserFaceController {
return R.ok(sysUserFaceService.create(dto));
}
/**
* 监护人代被监护人提交人脸审核。
*/
@Operation(summary = "监护人提交被监护人人脸", description = "仅监护关系中的监护人可提交,所有图片必须进入审核流程")
@SysLog("监护人提交被监护人人脸")
@ApiCryptoAes
@PostMapping("/app/guardian-ward")
@Idempotent(expireTime = 10, info = "正在处理中,请勿重复操作")
public R<List<Long>> createGuardianWardFaces(@Valid @RequestBody SysUserFaceGuardianWardCreateDto dto) {
return R.ok(sysUserFaceService.createGuardianWardFaces(dto));
}
/**
* 查询当前监护人名下可选择的有效被监护人。
*/
@Operation(summary = "查询被监护人选择项", description = "仅返回当前监护人名下有效被监护人的姓名和头像")
@ApiCryptoAes
@GetMapping("/app/guardian-ward/options")
public R<List<SysUserFaceGuardianWardOptionVo>> listGuardianWardOptions() {
return R.ok(sysUserFaceService.listGuardianWardOptions());
}
/**
* 分页查询当前监护人名下被监护人的人脸审核记录。
*/
@Operation(summary = "查询被监护人人脸审核记录", description = "查询当前用户全部有效被监护人的审核记录,并返回被监护人姓名")
@ApiCryptoAes
@GetMapping("/app/guardian-ward/page")
public R<Page<SysUserFaceVo>> pageGuardianWardFaces(@ParameterObject Page page,
@RequestParam(required = false) Long wardUserId,
@RequestParam(required = false) String processStatus) {
return R.ok(sysUserFaceService.pageGuardianWardFaces(page, wardUserId, processStatus));
}
/**
* 查询当前监护人名下被监护人的人脸审核详情。
*/
@Operation(summary = "查询被监护人人脸审核详情", description = "仅被监护人的有效监护人可以查看")
@ApiCryptoAes
@GetMapping("/app/guardian-ward/detail")
public R<SysUserFaceVo> getGuardianWardFaceDetail(@RequestParam Long id) {
return R.ok(sysUserFaceService.getGuardianWardFaceDetail(id));
}
/**
* 撤回并删除当前监护人名下被监护人仍在审核中的人脸记录。
*/
@Operation(summary = "撤回被监护人人脸审核", description = "仅审核中的记录可以撤回,撤回后删除旧记录并返回原图供重新编辑")
@SysLog("撤回被监护人人脸审核")
@ApiCryptoAes
@PutMapping("/app/guardian-ward/cancel")
@Idempotent(expireTime = 3, info = "正在处理中,请勿重复操作")
public R<SysUserFaceGuardianWardWithdrawVo> cancelGuardianWardFace(@RequestParam Long id) {
return R.ok(sysUserFaceService.cancelGuardianWardFace(id));
}
@Operation(summary = "app修改人脸样本表", description = "修改人脸样本表")
@SysLog("修改人脸样本表")
@ApiCryptoAes
@@ -27,6 +27,12 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper<SysUserFaceEntity>
Long selectAllCount();
/**
* 联表查询单条人脸记录及用户姓名。
*
* @param id 人脸记录ID
* @return 人脸记录详情
*/
default SysUserFaceVo join(Long id) {
MPJLambdaWrapper<SysUserFaceEntity> wrapper = new MPJLambdaWrapper<SysUserFaceEntity>()
.selectAll(SysUserFaceEntity.class)
@@ -36,6 +42,13 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper<SysUserFaceEntity>
return selectJoinOne(SysUserFaceVo.class, wrapper);
}
/**
* 联表分页查询人脸记录,并按用户和审核状态条件过滤。
*
* @param page 分页参数
* @param dto 人脸查询条件
* @return 人脸记录分页数据
*/
default Page<SysUserFaceVo> pageJoin(Page page, SysUserFaceDto dto) {
MPJLambdaWrapper<SysUserFaceEntity> wrapper = new MPJLambdaWrapper<SysUserFaceEntity>()
.selectAll(SysUserFaceEntity.class)
@@ -49,6 +62,8 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper<SysUserFaceEntity>
.eq(ObjectUtil.isNotEmpty(dto.getUserId()), SysUserFaceEntity::getUserId, dto.getUserId())
.eq(ObjectUtil.isNotEmpty(dto.getProcessStatus()), SysUserFaceEntity::getProcessStatus,
dto.getProcessStatus())
.in(ObjectUtil.isNotEmpty(dto.getProcessStatuses()), SysUserFaceEntity::getProcessStatus,
dto.getProcessStatuses())
.in(ObjectUtil.isNotEmpty(dto.getUserIds()), SysUserFaceEntity::getUserId, dto.getUserIds())
.orderByDesc(SysUserFaceEntity::getCreateTime)
.orderByDesc(SysUserFaceEntity::getUserId);
@@ -69,4 +84,4 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper<SysUserFaceEntity>
}
return selectJoinPage(page, SysUserFaceVo.class, wrapper);
}
}
}
@@ -3,18 +3,60 @@ package com.bsd.pjcloud.admin.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceDto;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceGuardianWardCreateDto;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.entity.SysUserFaceEntity;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardOptionVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardWithdrawVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceVo;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.flow.task.dto.ProcessNodeRecordAssignUserParamDto;
import java.util.ArrayList;
import java.util.List;
public interface SysUserFaceService extends IService<SysUserFaceEntity> {
Long create(SysUserFaceEntity sysUserFace);
/**
* 监护人代被监护人提交人脸,并强制进入审核流程。
*
* @param dto 被监护人及人脸图片
* @return 新增的人脸记录ID
*/
List<Long> createGuardianWardFaces(SysUserFaceGuardianWardCreateDto dto);
/**
* 查询当前监护人名下可选择的有效被监护人。
*
* @return 被监护人选择项列表
*/
List<SysUserFaceGuardianWardOptionVo> listGuardianWardOptions();
/**
* 分页查询当前监护人名下全部或指定被监护人的人脸审核记录。
*
* @param page 分页参数
* @param wardUserId 被监护人用户ID,为空时查询全部被监护人
* @param processStatus 审核状态
* @return 人脸审核记录分页数据
*/
Page<SysUserFaceVo> pageGuardianWardFaces(Page page, Long wardUserId, String processStatus);
/**
* 查询当前监护人名下被监护人的人脸审核详情。
*
* @param id 人脸审核记录ID
* @return 人脸审核详情
*/
SysUserFaceVo getGuardianWardFaceDetail(Long id);
/**
* 撤回并删除当前监护人名下被监护人仍在审核中的人脸记录。
*/
SysUserFaceGuardianWardWithdrawVo cancelGuardianWardFace(Long id);
void edit(SysUserFaceEntity sysUserFace);
Boolean editGlobalFace(SysUserFaceDto dto);
@@ -36,4 +78,4 @@ public interface SysUserFaceService extends IService<SysUserFaceEntity> {
* 审批回调
*/
void applyCallback(ProcessNodeRecordAssignUserParamDto dto);
}
}
@@ -7,6 +7,8 @@ import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationUpdateDto;
import com.bsd.pjcloud.admin.api.entity.user.SysUserRelationEntity;
import com.bsd.pjcloud.admin.api.vo.user.SysUserRelationGraphVo;
import java.util.List;
public interface SysUserRelationService extends IService<SysUserRelationEntity> {
/**
@@ -17,6 +19,23 @@ public interface SysUserRelationService extends IService<SysUserRelationEntity>
*/
SysUserRelationGraphVo getRelationGraph(Long userId);
/**
* 判断指定用户之间是否存在有效的监护人到被监护人关系。
*
* @param guardianUserId 监护人用户ID
* @param wardUserId 被监护人用户ID
* @return 是否存在有效监护关系
*/
boolean hasActiveGuardianWardRelation(Long guardianUserId, Long wardUserId);
/**
* 查询指定监护人当前所有有效的被监护人用户ID。
*
* @param guardianUserId 监护人用户ID
* @return 被监护人用户ID列表
*/
List<Long> listActiveGuardianWardUserIds(Long guardianUserId);
/**
* 创建用户并建立关系,新用户作为关系主语
*
@@ -1,28 +1,34 @@
package com.bsd.pjcloud.admin.service.impl;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceDto;
import com.bsd.pjcloud.admin.api.dto.SysUserFaceGuardianWardCreateDto;
import com.bsd.pjcloud.admin.api.entity.SysDept;
import com.bsd.pjcloud.admin.api.entity.SysUser;
import com.bsd.pjcloud.admin.api.entity.SysUserFaceEntity;
import com.bsd.pjcloud.admin.api.utils.DataUtil;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardOptionVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceGuardianWardWithdrawVo;
import com.bsd.pjcloud.admin.api.vo.SysUserFaceVo;
import com.bsd.pjcloud.admin.api.vo.UserVO;
import com.bsd.pjcloud.admin.mapper.SysUserFaceMapper;
import com.bsd.pjcloud.admin.service.SysDeptService;
import com.bsd.pjcloud.admin.service.SysRoleService;
import com.bsd.pjcloud.admin.service.SysUserFaceService;
import com.bsd.pjcloud.admin.service.SysUserRelationService;
import com.bsd.pjcloud.admin.service.SysUserService;
import com.bsd.pjcloud.admin.utils.flow.FlowUtil;
import com.bsd.pjcloud.admin.utils.flow.dto.UserFaceApplyFlowDto;
import com.bsd.pjcloud.common.core.constant.CommonConstants;
import com.bsd.pjcloud.common.core.constant.enums.YesNoEnum;
import com.bsd.pjcloud.common.core.exception.CheckedException;
import com.bsd.pjcloud.common.core.util.CollectionUtils;
import com.bsd.pjcloud.common.core.util.R;
import com.bsd.pjcloud.common.data.datascope.DataScope;
import com.bsd.pjcloud.common.security.service.PjcloudUser;
import com.bsd.pjcloud.common.security.util.SecurityUtils;
@@ -32,8 +38,6 @@ import com.bsd.pjcloud.flow.task.dto.ProcessNodeRecordAssignUserParamDto;
import com.bsd.pjcloud.flow.task.dto.TaskDto;
import com.bsd.pjcloud.flow.task.dto.TaskParamDto;
import com.bsd.pjcloud.mqtt.api.feign.RemoteMqttEventService;
import com.bsd.pjcloud.wallet.api.entity.WalletFreezeEntity;
import com.bsd.pjcloud.wallet.api.entity.recharge.WalletRechargeApplyEntity;
import cn.hutool.extra.spring.SpringUtil;
import lombok.RequiredArgsConstructor;
@@ -45,8 +49,12 @@ import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
* 人脸样本表
@@ -62,9 +70,18 @@ public class SysUserFaceServiceImpl extends ServiceImpl<SysUserFaceMapper, SysUs
static private final String TAG = "用户人脸";
/**
* 监护人端允许查看的人脸审核状态。
*/
private static final List<String> GUARDIAN_WARD_VISIBLE_STATUSES = List.of(
TaskCheckStatusEnum.PADDING.getCode(),
TaskCheckStatusEnum.RESULT.getCode(),
TaskCheckStatusEnum.REJECT.getCode());
private final SysUserService sysUserService;
private final SysDeptService sysDeptService;
private final SysRoleService sysRoleService;
private final SysUserRelationService sysUserRelationService;
private final RemoteMqttEventService remoteMqttEventService;
private final RemoteFlowEngineService remoteFlowEngineService;
@@ -103,6 +120,210 @@ public class SysUserFaceServiceImpl extends ServiceImpl<SysUserFaceMapper, SysUs
return sysUserFace.getId();
}
/**
* 监护人代被监护人提交一至两张人脸图片,所有记录强制进入审核流程。
*
* @param dto 被监护人及人脸图片
* @return 新增的人脸记录ID
*/
@Override
@Transactional(rollbackFor = Exception.class)
public List<Long> createGuardianWardFaces(SysUserFaceGuardianWardCreateDto dto) {
PjcloudUser loginUser = SecurityUtils.getUser();
if (new HashSet<>(dto.getFaceImgs()).size() != dto.getFaceImgs().size()) {
throw new RuntimeException("请勿重复提交同一张人脸图片");
}
SysUser guardianUser = sysUserService.getById(loginUser.getId());
if (guardianUser == null) {
throw new RuntimeException("监护人用户不存在");
}
if (sysUserService.getById(dto.getWardUserId()) == null) {
throw new RuntimeException("被监护人用户不存在");
}
if (!sysUserRelationService.hasActiveGuardianWardRelation(loginUser.getId(), dto.getWardUserId())) {
throw new RuntimeException("当前用户不是该用户的有效监护人,无法代为上传人脸");
}
if (sysDeptService.getById(guardianUser.getDeptId()) == null) {
throw new RuntimeException("监护人所属部门不存在,无法发起人脸审核");
}
validateGuardianWardFaceLimit(dto.getWardUserId(), dto.getFaceImgs().size());
List<Long> faceIds = new ArrayList<>();
for (String faceImg : dto.getFaceImgs()) {
SysUserFaceEntity userFace = new SysUserFaceEntity();
userFace.setUserId(dto.getWardUserId());
userFace.setFaceImg(faceImg);
userFace.setProcessStatus(TaskCheckStatusEnum.PADDING.getCode());
baseMapper.insert(userFace);
updateFaceAutoCode(userFace);
getSelf().applyFlow(guardianUser, userFace);
faceIds.add(userFace.getId());
}
return faceIds;
}
/**
* 查询当前监护人名下可选择的有效被监护人,并仅返回页面展示所需字段。
*
* @return 被监护人选择项列表
*/
@Override
public List<SysUserFaceGuardianWardOptionVo> listGuardianWardOptions() {
List<Long> wardUserIds = sysUserRelationService.listActiveGuardianWardUserIds(SecurityUtils.getUser().getId());
if (wardUserIds.isEmpty()) {
return List.of();
}
Map<Long, SysUser> wardUserMap = sysUserService.listByIds(wardUserIds).stream()
.collect(Collectors.toMap(SysUser::getUserId, Function.identity(), (first, second) -> first,
LinkedHashMap::new));
return wardUserIds.stream()
.map(wardUserMap::get)
.filter(ObjectUtil::isNotEmpty)
.map(this::buildGuardianWardOption)
.toList();
}
/**
* 将被监护人用户转换为移动端选择项。
*
* @param wardUser 被监护人用户
* @return 被监护人选择项
*/
private SysUserFaceGuardianWardOptionVo buildGuardianWardOption(SysUser wardUser) {
SysUserFaceGuardianWardOptionVo option = new SysUserFaceGuardianWardOptionVo();
option.setWardUserId(wardUser.getUserId());
option.setWardName(StrUtil.blankToDefault(wardUser.getName(), "被监护人"));
option.setWardAvatar(wardUser.getAvatar());
return option;
}
/**
* 分页查询当前监护人名下全部或指定被监护人的人脸审核记录。
*
* @param page 分页参数
* @param wardUserId 被监护人用户ID,为空时查询全部被监护人
* @param processStatus 审核状态
* @return 人脸审核记录分页数据
*/
@Override
public Page<SysUserFaceVo> pageGuardianWardFaces(Page page, Long wardUserId, String processStatus) {
Long guardianUserId = SecurityUtils.getUser().getId();
List<Long> wardUserIds = sysUserRelationService.listActiveGuardianWardUserIds(guardianUserId);
if (wardUserIds.isEmpty()) {
return new Page<>(page.getCurrent(), page.getSize());
}
if (wardUserId != null && !wardUserIds.contains(wardUserId)) {
throw new RuntimeException("当前用户不是该用户的有效监护人,无权查看人脸审核记录");
}
SysUserFaceDto query = new SysUserFaceDto();
if (wardUserId == null) {
query.setUserIds(wardUserIds);
} else {
query.setUserId(wardUserId);
}
if (StrUtil.isNotBlank(processStatus)) {
if (!GUARDIAN_WARD_VISIBLE_STATUSES.contains(processStatus)) {
throw new RuntimeException("不支持查询该人脸审核状态");
}
query.setProcessStatus(processStatus);
} else {
query.setProcessStatuses(GUARDIAN_WARD_VISIBLE_STATUSES);
}
return baseMapper.pageJoin(page, query);
}
/**
* 查询当前监护人名下被监护人的人脸审核详情。
*
* @param id 人脸审核记录ID
* @return 人脸审核详情
*/
@Override
public SysUserFaceVo getGuardianWardFaceDetail(Long id) {
SysUserFaceVo detail = baseMapper.join(id);
if (detail == null) {
throw new RuntimeException("人脸审核记录不存在");
}
validateGuardianWardUser(SecurityUtils.getUser().getId(), detail.getUserId());
return detail;
}
/**
* 撤回并删除当前监护人名下被监护人仍在审核中的人脸记录。
*
* @param id 人脸审核记录ID
* @return 重新编辑人脸时需要的被监护人和图片信息
*/
@Override
@Transactional(rollbackFor = Exception.class)
public SysUserFaceGuardianWardWithdrawVo cancelGuardianWardFace(Long id) {
SysUserFaceVo detail = baseMapper.join(id);
if (detail == null) {
throw new RuntimeException("人脸审核记录不存在");
}
validateGuardianWardUser(SecurityUtils.getUser().getId(), detail.getUserId());
if (!ObjectUtil.equals(detail.getProcessStatus(), TaskCheckStatusEnum.PADDING.getCode())) {
throw new RuntimeException("只有审核中的人脸记录可以撤销");
}
boolean deleted = update(new LambdaUpdateWrapper<SysUserFaceEntity>()
.eq(SysUserFaceEntity::getId, detail.getId())
.eq(SysUserFaceEntity::getProcessStatus, TaskCheckStatusEnum.PADDING.getCode())
.set(SysUserFaceEntity::getDelFlag, CommonConstants.STATUS_DEL)
.set(SysUserFaceEntity::getUpdateTime, LocalDateTime.now()));
if (!deleted) {
throw new RuntimeException("人脸审核状态已发生变化,请刷新后重试");
}
if (ObjectUtil.isNotEmpty(detail.getProcessId())) {
TaskParamDto taskParam = new TaskParamDto();
taskParam.setProcessInstanceIdList(List.of(detail.getProcessId()));
R<String> stopResult = remoteFlowEngineService.stopProcessInstance(taskParam);
if (stopResult == null || !stopResult.isOk()) {
String errorMessage = stopResult == null ? null : stopResult.getMsg();
throw new RuntimeException(StrUtil.blankToDefault(errorMessage, "停止人脸审核流程失败"));
}
}
SysUserFaceGuardianWardWithdrawVo result = new SysUserFaceGuardianWardWithdrawVo();
result.setWardUserId(detail.getUserId());
result.setWardName(detail.getUsername());
result.setFaceImg(detail.getFaceImg());
return result;
}
/**
* 校验指定用户是否属于当前监护人的有效被监护人范围。
*/
private void validateGuardianWardUser(Long guardianUserId, Long wardUserId) {
List<Long> wardUserIds = sysUserRelationService.listActiveGuardianWardUserIds(guardianUserId);
if (!wardUserIds.contains(wardUserId)) {
throw new RuntimeException("当前用户不是该用户的有效监护人,无权查看或操作人脸审核记录");
}
}
/**
* 更新人脸记录的自增编码。
*/
private void updateFaceAutoCode(SysUserFaceEntity userFace) {
Long count = baseMapper.selectAllCount();
SysUserFaceEntity updateObj = new SysUserFaceEntity();
updateObj.setId(userFace.getId());
updateObj.setAutoCode(String.valueOf(count));
baseMapper.updateById(updateObj);
}
/**
* 校验被监护人审核中和已通过的人脸总数是否超过三张。
*/
private void validateGuardianWardFaceLimit(Long wardUserId, int uploadCount) {
Long count = baseMapper.selectCount(new LambdaQueryWrapper<SysUserFaceEntity>()
.eq(SysUserFaceEntity::getUserId, wardUserId)
.in(SysUserFaceEntity::getProcessStatus,
TaskCheckStatusEnum.PADDING.getCode(), TaskCheckStatusEnum.RESULT.getCode()));
if (count + uploadCount > 3) {
throw new RuntimeException("审核中和已通过的人脸合计最多只能保留3张");
}
}
@Override
public void edit(SysUserFaceEntity sysUserFace) {
SysUserFaceEntity userFace = baseMapper.selectById(sysUserFace.getId());
@@ -258,7 +479,19 @@ public class SysUserFaceServiceImpl extends ServiceImpl<SysUserFaceMapper, SysUs
try {
Boolean isConsent = flowUtil.isConsent(dto);
String processStatus = isConsent ? TaskCheckStatusEnum.RESULT.getCode() : TaskCheckStatusEnum.REJECT.getCode();
baseMapper.updateById(SysUserFaceEntity.builder().id(detail.getId()).processTime(LocalDateTime.now()).processStatus(processStatus).build());
boolean updated = update(new LambdaUpdateWrapper<SysUserFaceEntity>()
.eq(SysUserFaceEntity::getId, detail.getId())
.eq(SysUserFaceEntity::getProcessStatus, TaskCheckStatusEnum.PADDING.getCode())
.set(SysUserFaceEntity::getProcessTime, LocalDateTime.now())
.set(SysUserFaceEntity::getProcessStatus, processStatus)
.set(SysUserFaceEntity::getRejectReason, isConsent ? null : getRejectReason(dto)));
if (!updated) {
log.warn("|{}|人脸审核状态已变化,忽略本次审批回调,dto:|{}|", TAG, JSON.toJSONString(dto));
return;
}
if (isConsent) {
sendFaceMq(List.of(detail.getUserId()), null);
}
} catch (Exception e) {
e.printStackTrace();
log.error("|{}|审批回调意外的错误,dto:|{}|error:|{}|", TAG, JSON.toJSONString(dto), e.getMessage());
@@ -266,6 +499,24 @@ public class SysUserFaceServiceImpl extends ServiceImpl<SysUserFaceMapper, SysUs
}
}
/**
* 从审批回调中提取拒绝原因,并限制为数据库字段允许的长度。
*
* @param dto 审批回调参数
* @return 审核拒绝原因
*/
private String getRejectReason(ProcessNodeRecordAssignUserParamDto dto) {
String rejectReason = StrUtil.trim(dto.getApproveDesc());
if (StrUtil.isBlank(rejectReason) && StrUtil.isNotBlank(dto.getData())) {
try {
rejectReason = StrUtil.trim(JSON.parseObject(dto.getData()).getString("approveDesc"));
} catch (Exception e) {
log.warn("|{}|解析人脸审核拒绝原因失败,dto:|{}|", TAG, JSON.toJSONString(dto));
}
}
return StrUtil.sub(StrUtil.blankToDefault(rejectReason, "未填写拒绝原因"), 0, 500);
}
// 上传的人脸是否超过最大限制
private Boolean isExceedMax(Long userId) {
Long count = baseMapper.selectCount(new LambdaQueryWrapper<SysUserFaceEntity>().eq(SysUserFaceEntity::getUserId, userId).eq(SysUserFaceEntity::getProcessStatus, TaskCheckStatusEnum.RESULT.getCode()));
@@ -289,4 +540,4 @@ public class SysUserFaceServiceImpl extends ServiceImpl<SysUserFaceMapper, SysUs
private SysUserFaceServiceImpl getSelf() {
return SpringUtil.getBean(getClass());
}
}
}
@@ -7,6 +7,7 @@ import com.bsd.pjcloud.admin.api.dto.UserDTO;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateUserDto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationCreateDto;
import com.bsd.pjcloud.admin.api.dto.user.SysUserRelationUpdateDto;
import com.bsd.pjcloud.admin.api.constant.user.UserRelationTypeEnum;
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;
@@ -93,6 +94,58 @@ public class SysUserRelationServiceImpl extends ServiceImpl<SysUserRelationMappe
return rootNode;
}
/**
* 判断指定用户之间是否存在有效的监护人到被监护人关系。
*
* @param guardianUserId 监护人用户ID
* @param wardUserId 被监护人用户ID
* @return 是否存在有效监护关系
*/
@Override
public boolean hasActiveGuardianWardRelation(Long guardianUserId, Long wardUserId) {
return listActiveGuardianWardUserIds(guardianUserId).contains(wardUserId);
}
/**
* 查询指定监护人当前所有有效的被监护人用户ID。
*
* @param guardianUserId 监护人用户ID
* @return 被监护人用户ID列表
*/
@Override
public List<Long> listActiveGuardianWardUserIds(Long guardianUserId) {
SysUserRelationTypeEntity relationType = getActiveGuardianWardRelationType();
if (relationType == null) {
return List.of();
}
return list(Wrappers.<SysUserRelationEntity>lambdaQuery()
.eq(SysUserRelationEntity::getSubjectUserId, guardianUserId)
.eq(SysUserRelationEntity::getRelationTypeId, relationType.getRelationTypeId())
.and(wrapper -> wrapper.isNull(SysUserRelationEntity::getEffectiveTime)
.or().le(SysUserRelationEntity::getEffectiveTime, java.time.LocalDateTime.now()))
.and(wrapper -> wrapper.isNull(SysUserRelationEntity::getExpireTime)
.or().ge(SysUserRelationEntity::getExpireTime, java.time.LocalDateTime.now())))
.stream()
.map(SysUserRelationEntity::getObjectUserId)
.filter(Objects::nonNull)
.distinct()
.toList();
}
/**
* 查询当前启用的监护关系类型。
*
* @return 启用的监护关系类型,不存在时返回空
*/
private SysUserRelationTypeEntity getActiveGuardianWardRelationType() {
return sysUserRelationTypeService.getOne(
Wrappers.<SysUserRelationTypeEntity>lambdaQuery()
.eq(SysUserRelationTypeEntity::getRelationCode,
UserRelationTypeEnum.GUARDIAN_WARD.getRelationCode())
.eq(SysUserRelationTypeEntity::getStatus, CommonConstants.STATUS_NORMAL)
.last("limit 1"));
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long createUserAndRelation(SysUserRelationCreateUserDto dto) {
@@ -5,6 +5,7 @@
<resultMap id="sysUserFaceMap" type="com.bsd.pjcloud.admin.api.entity.SysUserFaceEntity">
<id property="id" column="id"/>
<result property="userId" column="user_id"/>
<result property="rejectReason" column="reject_reason"/>
<result property="faceImg" column="face_img"/>
<result property="createBy" column="create_by"/>
<result property="createTime" column="create_time"/>
@@ -20,6 +21,7 @@
a.user_id,
a.process_id,
a.process_status,
a.reject_reason,
a.face_img,
a.create_by,
a.create_time,
@@ -35,9 +37,21 @@
<if test="pa.userId != null">
and a.user_id = #{pa.userId}
</if>
<if test="pa.userIds != null and pa.userIds.size() != 0">
and a.user_id in
<foreach collection="pa.userIds" item="userId" open="(" separator="," close=")">
#{userId}
</foreach>
</if>
<if test="pa.processStatus != null">
and a.process_status = #{pa.processStatus}
</if>
<if test="pa.processStatuses != null and pa.processStatuses.size() != 0">
and a.process_status in
<foreach collection="pa.processStatuses" item="status" open="(" separator="," close=")">
#{status}
</foreach>
</if>
</sql>
<select id="scopePageList" resultType="com.bsd.pjcloud.admin.api.vo.SysUserFaceVo">
@@ -53,10 +67,10 @@
left join sys_dept d on d.dept_id = u.dept_id and d.del_flag = '0'
<where>
<include refid="whereA"/>
<if test="pa.username != null">
<if test="pa.username != null and pa.username != ''">
and u.name LIKE CONCAT('%', #{pa.username}, '%')
</if>
<if test="pa.phone != null">
<if test="pa.phone != null and pa.phone != ''">
and u.phone LIKE CONCAT('%', #{pa.phone}, '%')
</if>
</where>
@@ -81,4 +95,4 @@
</where>
GROUP BY a.user_id
</select>
</mapper>
</mapper>
@@ -15,6 +15,7 @@ import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
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.mock;
@@ -66,6 +67,28 @@ class SysUserRelationServiceImplTest {
assertFalse(graph.getChildren().stream().anyMatch(child -> !child.getChildren().isEmpty()));
}
/**
* 验证监护人查询有效被监护人时,可以返回关系对象用户并自动去重。
*/
@Test
void shouldListDistinctActiveGuardianWardUserIds() {
SysUserRelationTypeService relationTypeService = mock(SysUserRelationTypeService.class);
SysUserRelationServiceImpl service = spy(
new SysUserRelationServiceImpl(mock(SysUserService.class), relationTypeService));
when(relationTypeService.getOne(any())).thenReturn(createGuardianRelationType());
doReturn(List.of(
createRelation(2079104982669385730L, FIRST_WARD_USER_ID),
createRelation(2079129636280758273L, SECOND_WARD_USER_ID),
createRelation(2079129636280758274L, SECOND_WARD_USER_ID)))
.when(service).list(org.mockito.ArgumentMatchers.<Wrapper<SysUserRelationEntity>>any());
List<Long> wardUserIds = service.listActiveGuardianWardUserIds(GUARDIAN_USER_ID);
assertEquals(List.of(FIRST_WARD_USER_ID, SECOND_WARD_USER_ID), wardUserIds);
assertTrue(service.hasActiveGuardianWardRelation(GUARDIAN_USER_ID, SECOND_WARD_USER_ID));
assertFalse(service.hasActiveGuardianWardRelation(GUARDIAN_USER_ID, 999L));
}
private SysUserRelationEntity createRelation(Long relationId, Long wardUserId) {
SysUserRelationEntity relation = new SysUserRelationEntity();
relation.setRelationId(relationId);