From be37903a1a8d526df80b96796b5584569f16e761 Mon Sep 17 00:00:00 2001 From: dzc <2388224167@qq.com> Date: Tue, 21 Jul 2026 14:26:04 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E7=9B=91=E6=8A=A4=E4=BA=BA=E7=BB=B4?= =?UTF-8?q?=E6=8A=A4=E4=BA=BA=E8=84=B8=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...260721_add_sys_user_face_reject_reason.sql | 3 + db/800pjcloudx_wallet.sql | 3 +- .../pjcloud/admin/api/dto/SysUserFaceDto.java | 5 + .../dto/SysUserFaceGuardianWardCreateDto.java | 27 ++ .../admin/api/entity/SysUserFaceEntity.java | 8 +- .../vo/SysUserFaceGuardianWardOptionVo.java | 30 ++ .../vo/SysUserFaceGuardianWardWithdrawVo.java | 30 ++ .../controller/SysUserFaceController.java | 60 ++++ .../admin/mapper/SysUserFaceMapper.java | 17 +- .../admin/service/SysUserFaceService.java | 44 ++- .../admin/service/SysUserRelationService.java | 19 ++ .../service/impl/SysUserFaceServiceImpl.java | 263 +++++++++++++++++- .../impl/user/SysUserRelationServiceImpl.java | 53 ++++ .../resources/mapper/SysUserFaceMapper.xml | 20 +- .../user/SysUserRelationServiceImplTest.java | 23 ++ 15 files changed, 592 insertions(+), 13 deletions(-) create mode 100644 db/20260721_add_sys_user_face_reject_reason.sql create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceGuardianWardCreateDto.java create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardOptionVo.java create mode 100644 pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardWithdrawVo.java diff --git a/db/20260721_add_sys_user_face_reject_reason.sql b/db/20260721_add_sys_user_face_reject_reason.sql new file mode 100644 index 000000000..c40838852 --- /dev/null +++ b/db/20260721_add_sys_user_face_reject_reason.sql @@ -0,0 +1,3 @@ +-- 为人脸审核记录补充拒绝原因,供审核详情页面展示。 +ALTER TABLE sys_user_face + ADD COLUMN reject_reason varchar(500) DEFAULT NULL COMMENT '审核拒绝原因' AFTER process_time; diff --git a/db/800pjcloudx_wallet.sql b/db/800pjcloudx_wallet.sql index 64565fc4a..42efd830b 100644 --- a/db/800pjcloudx_wallet.sql +++ b/db/800pjcloudx_wallet.sql @@ -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 ='积分记录'; \ No newline at end of file +) ENGINE = InnoDB COMMENT ='积分记录'; diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceDto.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceDto.java index 96b976f53..10e9f7727 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceDto.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceDto.java @@ -15,6 +15,11 @@ public class SysUserFaceDto extends SysUserFaceEntity implements BaseIsIncludeTr private List userIds; + /** + * 审核状态集合 + */ + private List processStatuses; + private List deptIds; private String isGlobalFace; diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceGuardianWardCreateDto.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceGuardianWardCreateDto.java new file mode 100644 index 000000000..1f948a89d --- /dev/null +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/dto/SysUserFaceGuardianWardCreateDto.java @@ -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; +} diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUserFaceEntity.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUserFaceEntity.java index b7a6efc3b..767245024 100644 --- a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUserFaceEntity.java +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/entity/SysUserFaceEntity.java @@ -67,6 +67,12 @@ public class SysUserFaceEntity extends Model { @Schema(description = "审核时间") private LocalDateTime processTime; + /** + * 审核拒绝原因 + */ + @Schema(description = "审核拒绝原因") + private String rejectReason; + /** * 人脸图片 */ @@ -114,4 +120,4 @@ public class SysUserFaceEntity extends Model { */ @Schema(description = "租户ID") private Long tenantId; -} \ No newline at end of file +} diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardOptionVo.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardOptionVo.java new file mode 100644 index 000000000..197528be7 --- /dev/null +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardOptionVo.java @@ -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; +} diff --git a/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardWithdrawVo.java b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardWithdrawVo.java new file mode 100644 index 000000000..275e0b5c7 --- /dev/null +++ b/pjcloud-upms/pjcloud-upms-api/src/main/java/com/bsd/pjcloud/admin/api/vo/SysUserFaceGuardianWardWithdrawVo.java @@ -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; +} diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserFaceController.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserFaceController.java index dd2bcf887..c316a0e5c 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserFaceController.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/controller/SysUserFaceController.java @@ -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> createGuardianWardFaces(@Valid @RequestBody SysUserFaceGuardianWardCreateDto dto) { + return R.ok(sysUserFaceService.createGuardianWardFaces(dto)); + } + + /** + * 查询当前监护人名下可选择的有效被监护人。 + */ + @Operation(summary = "查询被监护人选择项", description = "仅返回当前监护人名下有效被监护人的姓名和头像") + @ApiCryptoAes + @GetMapping("/app/guardian-ward/options") + public R> listGuardianWardOptions() { + return R.ok(sysUserFaceService.listGuardianWardOptions()); + } + + /** + * 分页查询当前监护人名下被监护人的人脸审核记录。 + */ + @Operation(summary = "查询被监护人人脸审核记录", description = "查询当前用户全部有效被监护人的审核记录,并返回被监护人姓名") + @ApiCryptoAes + @GetMapping("/app/guardian-ward/page") + public R> 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 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 cancelGuardianWardFace(@RequestParam Long id) { + return R.ok(sysUserFaceService.cancelGuardianWardFace(id)); + } + @Operation(summary = "app修改人脸样本表", description = "修改人脸样本表") @SysLog("修改人脸样本表") @ApiCryptoAes diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserFaceMapper.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserFaceMapper.java index a5a996461..550986a8a 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserFaceMapper.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/mapper/SysUserFaceMapper.java @@ -27,6 +27,12 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper Long selectAllCount(); + /** + * 联表查询单条人脸记录及用户姓名。 + * + * @param id 人脸记录ID + * @return 人脸记录详情 + */ default SysUserFaceVo join(Long id) { MPJLambdaWrapper wrapper = new MPJLambdaWrapper() .selectAll(SysUserFaceEntity.class) @@ -36,6 +42,13 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper return selectJoinOne(SysUserFaceVo.class, wrapper); } + /** + * 联表分页查询人脸记录,并按用户和审核状态条件过滤。 + * + * @param page 分页参数 + * @param dto 人脸查询条件 + * @return 人脸记录分页数据 + */ default Page pageJoin(Page page, SysUserFaceDto dto) { MPJLambdaWrapper wrapper = new MPJLambdaWrapper() .selectAll(SysUserFaceEntity.class) @@ -49,6 +62,8 @@ public interface SysUserFaceMapper extends PjcloudBaseMapper .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 } return selectJoinPage(page, SysUserFaceVo.class, wrapper); } -} \ No newline at end of file +} diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserFaceService.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserFaceService.java index 1ef233b84..9b04797c8 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserFaceService.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserFaceService.java @@ -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 { Long create(SysUserFaceEntity sysUserFace); + /** + * 监护人代被监护人提交人脸,并强制进入审核流程。 + * + * @param dto 被监护人及人脸图片 + * @return 新增的人脸记录ID + */ + List createGuardianWardFaces(SysUserFaceGuardianWardCreateDto dto); + + /** + * 查询当前监护人名下可选择的有效被监护人。 + * + * @return 被监护人选择项列表 + */ + List listGuardianWardOptions(); + + /** + * 分页查询当前监护人名下全部或指定被监护人的人脸审核记录。 + * + * @param page 分页参数 + * @param wardUserId 被监护人用户ID,为空时查询全部被监护人 + * @param processStatus 审核状态 + * @return 人脸审核记录分页数据 + */ + Page 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 { * 审批回调 */ void applyCallback(ProcessNodeRecordAssignUserParamDto dto); -} \ No newline at end of file +} diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserRelationService.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserRelationService.java index 9d71023cd..5bec02586 100755 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserRelationService.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/SysUserRelationService.java @@ -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 { /** @@ -17,6 +19,23 @@ public interface SysUserRelationService extends IService */ SysUserRelationGraphVo getRelationGraph(Long userId); + /** + * 判断指定用户之间是否存在有效的监护人到被监护人关系。 + * + * @param guardianUserId 监护人用户ID + * @param wardUserId 被监护人用户ID + * @return 是否存在有效监护关系 + */ + boolean hasActiveGuardianWardRelation(Long guardianUserId, Long wardUserId); + + /** + * 查询指定监护人当前所有有效的被监护人用户ID。 + * + * @param guardianUserId 监护人用户ID + * @return 被监护人用户ID列表 + */ + List listActiveGuardianWardUserIds(Long guardianUserId); + /** * 创建用户并建立关系,新用户作为关系主语 * diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserFaceServiceImpl.java b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserFaceServiceImpl.java index 28129b22b..e6efc5ca2 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserFaceServiceImpl.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/java/com/bsd/pjcloud/admin/service/impl/SysUserFaceServiceImpl.java @@ -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 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 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 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 listGuardianWardOptions() { + List wardUserIds = sysUserRelationService.listActiveGuardianWardUserIds(SecurityUtils.getUser().getId()); + if (wardUserIds.isEmpty()) { + return List.of(); + } + Map 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 pageGuardianWardFaces(Page page, Long wardUserId, String processStatus) { + Long guardianUserId = SecurityUtils.getUser().getId(); + List 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() + .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 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 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() + .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() + .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().eq(SysUserFaceEntity::getUserId, userId).eq(SysUserFaceEntity::getProcessStatus, TaskCheckStatusEnum.RESULT.getCode())); @@ -289,4 +540,4 @@ public class SysUserFaceServiceImpl extends ServiceImpl listActiveGuardianWardUserIds(Long guardianUserId) { + SysUserRelationTypeEntity relationType = getActiveGuardianWardRelationType(); + if (relationType == null) { + return List.of(); + } + return list(Wrappers.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.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) { diff --git a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserFaceMapper.xml b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserFaceMapper.xml index 7057675ed..2f2d7ffe9 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserFaceMapper.xml +++ b/pjcloud-upms/pjcloud-upms-biz/src/main/resources/mapper/SysUserFaceMapper.xml @@ -5,6 +5,7 @@ + @@ -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 @@ and a.user_id = #{pa.userId} + + and a.user_id in + + #{userId} + + and a.process_status = #{pa.processStatus} + + and a.process_status in + + #{status} + + - \ No newline at end of file + diff --git a/pjcloud-upms/pjcloud-upms-biz/src/test/java/com/bsd/pjcloud/admin/service/impl/user/SysUserRelationServiceImplTest.java b/pjcloud-upms/pjcloud-upms-biz/src/test/java/com/bsd/pjcloud/admin/service/impl/user/SysUserRelationServiceImplTest.java index 30d95e5e1..66cf1f99c 100644 --- a/pjcloud-upms/pjcloud-upms-biz/src/test/java/com/bsd/pjcloud/admin/service/impl/user/SysUserRelationServiceImplTest.java +++ b/pjcloud-upms/pjcloud-upms-biz/src/test/java/com/bsd/pjcloud/admin/service/impl/user/SysUserRelationServiceImplTest.java @@ -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.>any()); + + List 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);