风控提交

This commit is contained in:
磷叶 2024-11-26 17:30:35 +08:00
parent d2e00163bd
commit 41daefbda2
19 changed files with 293 additions and 26 deletions

View File

@ -7,6 +7,7 @@ import com.ruoyi.common.enums.LoginType;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
/**
@ -83,6 +84,13 @@ public class LoginUser implements UserDetails
*/
private LoginType loginType;
public static LoginUser system() {
SysUser user = new SysUser();
user.setUserName("系统");
user.setUserId(0L);
return new LoginUser(user, Collections.emptySet());
}
public SmUser getSmUser() {
return smUser;
}

View File

@ -107,6 +107,10 @@ public class CollectionUtils extends org.springframework.util.CollectionUtils {
return list.stream().map(keyMapper).filter(Objects::nonNull).distinct().collect(Collectors.toList());
}
public static <T, R> List<R> map(Function<? super T, ? extends R> keyMapper, T ...arrays) {
return map(Arrays.asList(arrays), keyMapper);
}
/**
* 判断两个列表及其内容是否一致
*/

View File

@ -78,4 +78,9 @@ public interface RiskService
*/
int limitWithdraw(Long userId, String reason);
/**
* 结束封禁
* @param riskId
*/
int finish(Long riskId);
}

View File

@ -123,4 +123,15 @@ public class RiskServiceImpl implements RiskService
risk.setUnsealSelf(true);
return this.insertRisk(risk);
}
@Override
public int finish(Long riskId) {
if (riskId == null) {
return 0;
}
Risk data = new Risk();
data.setRiskId(riskId);
data.setEndTime(LocalDateTime.now());
return riskMapper.updateRisk(data);
}
}

View File

@ -1,11 +1,18 @@
package com.ruoyi.ss.riskInfo.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.List;
/**
* @author wjh
* 2024/11/25
*/
@Data
public class RiskInfoQuery extends RiskInfoVO {
@ApiModelProperty("状态列表")
private List<String> statusList;
}

View File

@ -1,5 +1,7 @@
package com.ruoyi.ss.riskInfo.domain;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.enums.DesensitizedType;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ -23,4 +25,28 @@ public class RiskInfoVO extends RiskInfo {
@ApiModelProperty("风控是否结束")
private Boolean riskIsFinished;
@ApiModelProperty("提交后自动解封")
private Boolean riskUnsealSelf;
@ApiModelProperty("风控原因")
private String riskReason;
@ApiModelProperty("风控类型")
private List<String> riskType;
@ApiModelProperty("风控视频提示")
private String riskVideoWords;
@ApiModelProperty("实名")
@Sensitive(desensitizedType = DesensitizedType.USERNAME)
private String realName;
@ApiModelProperty("身份证")
@Sensitive(desensitizedType = DesensitizedType.ID_CARD)
private String realIdCard;
@ApiModelProperty("人脸图片")
@Sensitive(desensitizedType = DesensitizedType.COMMON)
private String realFace;
}

View File

@ -0,0 +1,26 @@
package com.ruoyi.ss.riskInfo.domain.dto;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
/**
* @author wjh
* 2024/11/26
*/
@Data
public class RiskInfoVerifyDTO {
@ApiModelProperty("是否通过")
@NotNull(message = "是否通过不允许为空")
private Boolean pass;
@ApiModelProperty("材料ID")
@NotNull(message = "风控材料ID不允许为空")
private Long infoId;
@ApiModelProperty("审核意见")
private String verifyRemark;
}

View File

@ -1,9 +1,13 @@
package com.ruoyi.ss.riskInfo.domain.enums;
import com.ruoyi.common.utils.collection.CollectionUtils;
import com.ruoyi.ss.model.domain.enums.ModelTag;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.Getter;
import java.util.List;
/**
* @author wjh
* 2024/11/25
@ -20,4 +24,28 @@ public enum RiskInfoStatus {
private final String status;
private final String desc;
public static List<String> asList(RiskInfoStatus ...statuses) {
return CollectionUtils.map(RiskInfoStatus::getStatus, statuses);
}
/**
* 允许审核的状态
*/
public static List<String> canVerify() {
return asList(WAIT_VERIFY);
}
/**
* 允许提交的状态
*/
public static List<String> canSubmit() {
return asList(WAIT_SUBMIT);
}
/**
* 允许修改的状态
*/
public static List<String> canUpdate() {
return asList(WAIT_SUBMIT);
}
}

View File

@ -61,4 +61,9 @@ public interface RiskInfoMapper
* @return 结果
*/
public int deleteRiskInfoByInfoIds(Long[] infoIds);
/**
* 根据查询条件更新数据
*/
int updateByQuery(@Param("data") RiskInfo data, @Param("query") RiskInfoQuery query);
}

View File

@ -6,6 +6,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<resultMap type="RiskInfoVO" id="RiskInfoResult" autoMapping="true">
<result property="riskSubmitType" column="risk_submit_type" typeHandler="com.ruoyi.system.mapper.typehandler.StringSplitListTypeHandler"/>
<result property="riskType" column="risk_type" typeHandler="com.ruoyi.system.mapper.typehandler.StringSplitListTypeHandler"/>
</resultMap>
<sql id="selectRiskInfoVo">
@ -19,7 +20,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
sri.id_card_hand,
sri.video,
sri.duty_video,
sri.video_words,
sri.business_licence,
sri.verify_id,
sri.verify_by,
@ -29,11 +29,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
sri.submit_time,
sr.user_id as user_id,
sr.submit_type as risk_submit_type,
sr.unseal_self as risk_unseal_self,
sr.type as risk_type,
sr.reason as risk_reason,
sr.video_words as risk_video_words,
<include refid="com.ruoyi.ss.risk.mapper.RiskMapper.isFinished"/> as risk_is_finished,
if(su.is_real, su.real_name, su.user_name) as user_name
if(su.is_real, su.real_name, su.user_name) as user_name,
srn.name as real_name,
srn.id_card as real_id_card,
srn.face_image as real_face
from ss_risk_info sri
left join ss_risk sr on sr.risk_id = sri.risk_id
left join sm_user su on su.user_id = sr.user_id
left join ss_real_name srn on srn.id = sri.real_name_id
</sql>
<sql id="searchCondition">
@ -41,12 +49,17 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="query.riskId != null "> and sri.risk_id = #{query.riskId}</if>
<if test="query.status != null and query.status != ''"> and sri.status = #{query.status}</if>
<if test="query.realNameId != null "> and sri.real_name_id = #{query.realNameId}</if>
<if test="query.videoWords != null and query.videoWords != ''"> and sri.video_words like concat('%', #{query.videoWords}, '%')</if>
<if test="query.verifyId != null "> and sri.verify_id = #{query.verifyId}</if>
<if test="query.verifyBy != null and query.verifyBy != ''"> and sri.verify_by like concat('%', #{query.verifyBy}, '%')</if>
<if test="query.verifyRemark != null and query.verifyRemark != ''"> and sri.verify_remark like concat('%', #{query.verifyRemark}, '%')</if>
<if test="query.userId != null "> and sr.user_id = #{query.userId}</if>
<if test="query.userName != null and query.userName != ''"> and if(su.is_real, su.real_name, su.user_name) like concat('%', #{query.userName}, '%')</if>
<if test="query.statusList != null and query.statusList.size() > 0">
and sri.status in
<foreach collection="query.statusList" item="item" open="(" close=")" separator=",">
#{item}
</foreach>
</if>
</sql>
<select id="selectRiskInfoList" parameterType="RiskInfoQuery" resultMap="RiskInfoResult">
@ -72,7 +85,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="idCardHand != null">id_card_hand,</if>
<if test="video != null">video,</if>
<if test="dutyVideo != null">duty_video,</if>
<if test="videoWords != null">video_words,</if>
<if test="businessLicence != null">business_licence,</if>
<if test="verifyId != null">verify_id,</if>
<if test="verifyBy != null">verify_by,</if>
@ -90,7 +102,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="idCardHand != null">#{idCardHand},</if>
<if test="video != null">#{video},</if>
<if test="dutyVideo != null">#{dutyVideo},</if>
<if test="videoWords != null">#{videoWords},</if>
<if test="businessLicence != null">#{businessLicence},</if>
<if test="verifyId != null">#{verifyId},</if>
<if test="verifyBy != null">#{verifyBy},</if>
@ -109,6 +120,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where info_id = #{data.infoId}
</update>
<update id="updateByQuery">
update ss_risk_info sri
<trim prefix="SET" suffixOverrides=",">
<include refid="updateColumns"/>
</trim>
<where>
<include refid="searchCondition"/>
</where>
</update>
<sql id="updateColumns">
<if test="data.riskId != null">risk_id = #{data.riskId},</if>
<if test="data.status != null and data.status != ''">`status` = #{data.status},</if>
@ -118,7 +139,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="data.idCardHand != null">id_card_hand = #{data.idCardHand},</if>
<if test="data.video != null">video = #{data.video},</if>
<if test="data.dutyVideo != null">duty_video = #{data.dutyVideo},</if>
<if test="data.videoWords != null">video_words = #{data.videoWords},</if>
<if test="data.businessLicence != null">business_licence = #{data.businessLicence},</if>
<if test="data.verifyId != null">verify_id = #{data.verifyId},</if>
<if test="data.verifyBy != null">verify_by = #{data.verifyBy},</if>

View File

@ -1,9 +1,12 @@
package com.ruoyi.ss.riskInfo.service;
import java.util.List;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.ss.riskInfo.domain.RiskInfo;
import com.ruoyi.ss.riskInfo.domain.RiskInfoVO;
import com.ruoyi.ss.riskInfo.domain.RiskInfoQuery;
import com.ruoyi.ss.riskInfo.domain.dto.RiskInfoVerifyDTO;
/**
* 风控材料Service接口
@ -73,4 +76,9 @@ public interface RiskInfoService
* @return
*/
int updateRealName(Long infoId, Long realNameId);
/**
* 审核
*/
int verify(RiskInfoVerifyDTO dto, LoginUser verifier);
}

View File

@ -2,7 +2,13 @@ package com.ruoyi.ss.riskInfo.service.impl;
import java.time.LocalDateTime;
import java.util.List;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.utils.DateUtils;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.ServiceUtil;
import com.ruoyi.ss.risk.service.RiskService;
import com.ruoyi.ss.riskInfo.domain.dto.RiskInfoVerifyDTO;
import com.ruoyi.ss.riskInfo.domain.enums.RiskInfoStatus;
import com.ruoyi.ss.riskInfo.service.RiskInfoValidator;
import org.springframework.beans.factory.annotation.Autowired;
@ -12,6 +18,7 @@ import com.ruoyi.ss.riskInfo.domain.RiskInfo;
import com.ruoyi.ss.riskInfo.domain.RiskInfoVO;
import com.ruoyi.ss.riskInfo.domain.RiskInfoQuery;
import com.ruoyi.ss.riskInfo.service.RiskInfoService;
import org.springframework.transaction.support.TransactionTemplate;
/**
* 风控材料Service业务层处理
@ -28,6 +35,12 @@ public class RiskInfoServiceImpl implements RiskInfoService
@Autowired
private RiskInfoValidator riskInfoValidator;
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private RiskService riskService;
/**
* 查询风控材料
*
@ -110,14 +123,32 @@ public class RiskInfoServiceImpl implements RiskInfoService
}
// 校验数据
RiskInfoVO info = this.selectRiskInfoByInfoId(infoId);
riskInfoValidator.preSubmitCheck(info);
RiskInfoVO old = this.selectRiskInfoByInfoId(infoId);
riskInfoValidator.preSubmitCheck(old);
RiskInfoVO data = new RiskInfoVO();
data.setInfoId(infoId);
data.setSubmitTime(LocalDateTime.now());
data.setStatus(RiskInfoStatus.WAIT_VERIFY.getStatus());
return riskInfoMapper.updateRiskInfo(data);
Integer result = transactionTemplate.execute(status -> {
RiskInfoVO data = new RiskInfoVO();
data.setSubmitTime(LocalDateTime.now());
data.setStatus(RiskInfoStatus.WAIT_VERIFY.getStatus());
RiskInfoQuery query = new RiskInfoQuery();
query.setInfoId(infoId);
query.setStatusList(RiskInfoStatus.canSubmit());
int submit = this.updateByQuery(data, query);
ServiceUtil.assertion(submit != 1, "提交失败");
// 若开启了提交后自动通过则直接通过
if (old.getRiskUnsealSelf() != null && old.getRiskUnsealSelf()) {
RiskInfoVerifyDTO dto = new RiskInfoVerifyDTO();
dto.setInfoId(infoId);
dto.setPass(true);
dto.setVerifyRemark("免审核通过");
this.verify(dto, LoginUser.system());
}
return submit;
});
return result == null ? 0 : result;
}
@Override
@ -130,4 +161,40 @@ public class RiskInfoServiceImpl implements RiskInfoService
data.setRealNameId(realNameId);
return this.updateRiskInfo(data);
}
@Override
public int verify(RiskInfoVerifyDTO dto, LoginUser verifier) {
ServiceUtil.assertion(dto == null, "参数错误");
RiskInfoVO old = selectRiskInfoByInfoId(dto.getInfoId());
ServiceUtil.assertion(old == null, "待审核的风控材料不存在");
ServiceUtil.assertion(!RiskInfoStatus.canVerify().contains(old.getStatus()), "风控材料当前状态不允许审核");
Integer result = transactionTemplate.execute(status -> {
// 修改审核状态
RiskInfo data = new RiskInfo();
data.setStatus(dto.getPass() ? RiskInfoStatus.PASS.getStatus() : RiskInfoStatus.REJECT.getStatus());
data.setVerifyId(verifier.getUserId());
data.setVerifyBy(verifier.getUsername());
data.setVerifyTime(LocalDateTime.now());
data.setVerifyRemark(dto.getVerifyRemark());
RiskInfoQuery query = new RiskInfoQuery();
query.setInfoId(dto.getInfoId());
query.setStatusList(RiskInfoStatus.canVerify());
int update = this.updateByQuery(data, query);
ServiceUtil.assertion(update != 1, "当前风控材料状态已发生变化,请刷新后重试");
// 解封
int finish = riskService.finish(old.getRiskId());
ServiceUtil.assertion(finish != 1, "解封失败,请重试");
return update;
});
return result == null ? 0 : result;
}
private int updateByQuery(RiskInfo data, RiskInfoQuery query) {
return riskInfoMapper.updateByQuery(data, query);
}
}

View File

@ -9,6 +9,7 @@ import com.ruoyi.ss.risk.domain.enums.RiskSubmitType;
import com.ruoyi.ss.risk.service.RiskValidator;
import com.ruoyi.ss.riskInfo.domain.RiskInfo;
import com.ruoyi.ss.riskInfo.domain.RiskInfoVO;
import com.ruoyi.ss.riskInfo.domain.enums.RiskInfoStatus;
import com.ruoyi.ss.riskInfo.service.RiskInfoService;
import com.ruoyi.ss.riskInfo.service.RiskInfoValidator;
import org.springframework.beans.factory.annotation.Autowired;
@ -56,11 +57,19 @@ public class RiskInfoValidatorImpl implements RiskInfoValidator {
boolean exist = riskValidator.isExist(data.getRiskId());
ServiceUtil.assertion(!exist, "风控信息不存在");
}
// 判断信息是否可修改
if (data.getInfoId() != null) {
RiskInfoVO old = riskInfoService.selectRiskInfoByInfoId(data.getInfoId());
ServiceUtil.assertion(old == null, "待修改的材料信息不存在");
ServiceUtil.assertion(!RiskInfoStatus.canUpdate().contains(old.getStatus()), "风控材料当前状态不允许修改");
}
}
@Override
public void preSubmitCheck(RiskInfoVO data) {
ServiceUtil.assertion(data == null, "参数错误");
ServiceUtil.assertion(!RiskInfoStatus.canSubmit().contains(data.getStatus()), "风控材料当前状态不允许提交");
if (CollectionUtils.isEmptyElement(data.getRiskSubmitType())) {
return;

View File

@ -28,7 +28,7 @@ import java.util.List;
public class TransactionBillVO extends TransactionBill implements IotDevice {
@ApiModelProperty("用户名称")
@JsonView(JsonViewProfile.App.class)
@Sensitive(desensitizedType = DesensitizedType.COMMON)
@Sensitive(desensitizedType = DesensitizedType.USERNAME)
private String userName;
@ApiModelProperty("商户(到账用户)名称")

View File

@ -0,0 +1,23 @@
package com.ruoyi.ss.user.domain.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
/**
* 用户刷新实名认证结果
* @author wjh
* 2024/11/26
*/
@Data
public class UserRealRefreshVO {
@ApiModelProperty("是否通过")
private Boolean pass;
@ApiModelProperty("实名类型")
private String realNameType;
@ApiModelProperty("风控材料ID仅实名类型为风控会返回")
private Long infoId;
}

View File

@ -82,6 +82,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="userId != null"> and su.user_id = #{userId}</if>
<if test="delFlag == null"> and su.del_flag = '0'</if>
<if test="delFlag != null"> and su.del_flag = #{delFlag}</if>
<if test="deviceAdmin != null"> and su.device_admin = #{deviceAdmin}</if>
<if test="remark != null and remark != ''"> and su.remark like concat('%', #{remark}, '%')</if>
<if test="withdrawServiceType != null and withdrawServiceType != ''"> and withdraw_service_type = #{withdrawServiceType}</if>
<if test="serviceType != null and serviceType != ''"> and service_type = #{serviceType}</if>

View File

@ -8,6 +8,7 @@ import com.ruoyi.ss.user.domain.SmUserQuery;
import com.ruoyi.ss.user.domain.SmUserVO;
import com.ruoyi.ss.user.domain.dto.UserRealNameDTO;
import com.ruoyi.ss.user.domain.vo.UserRealNameVO;
import com.ruoyi.ss.user.domain.vo.UserRealRefreshVO;
import java.math.BigDecimal;
import java.util.List;
@ -229,7 +230,7 @@ public interface ISmUserService
* @param userId 用户ID
* @return 认证结果是否成功
*/
String refreshRealName(Long userId);
UserRealRefreshVO refreshRealName(Long userId);
/**
* 根据用户ID列表查询

View File

@ -32,7 +32,6 @@ import com.ruoyi.ss.realName.service.RealNameService;
import com.ruoyi.ss.realName.service.RealNameValidator;
import com.ruoyi.ss.recordBalance.domain.enums.RecordBalanceBstType;
import com.ruoyi.ss.recordBalance.service.RecordBalanceService;
import com.ruoyi.ss.risk.domain.RiskVO;
import com.ruoyi.ss.risk.domain.enums.RiskSubmitType;
import com.ruoyi.ss.risk.service.RiskService;
import com.ruoyi.ss.riskInfo.domain.RiskInfoVO;
@ -45,6 +44,7 @@ import com.ruoyi.ss.user.domain.SmUserVO;
import com.ruoyi.ss.user.domain.dto.UserFaceDTO;
import com.ruoyi.ss.user.domain.dto.UserRealNameDTO;
import com.ruoyi.ss.user.domain.vo.UserRealNameVO;
import com.ruoyi.ss.user.domain.vo.UserRealRefreshVO;
import com.ruoyi.ss.user.mapper.SmUserMapper;
import com.ruoyi.ss.user.service.ISmUserService;
import com.ruoyi.ss.user.service.UserValidator;
@ -413,9 +413,13 @@ public class SmUserServiceImpl implements ISmUserService
}
@Override
public String refreshRealName(Long userId) {
public UserRealRefreshVO refreshRealName(Long userId) {
// 结果
UserRealRefreshVO vo = new UserRealRefreshVO();
vo.setPass(false);
if (userId == null) {
return null;
return vo;
}
// 通过userId获取用户提交的信息
@ -452,18 +456,23 @@ public class SmUserServiceImpl implements ISmUserService
realName.setScore(faceIdb.getScore());
realName.setFaceImage(imageUrl);
realName.setType(face.getType());
// 普通实名
if (RealNameType.NORMAL.getType().equals(face.getType())) {
int result = this.handleNormalRealName(realName, cacheKey);
ServiceUtil.assertion(result != 1, "实名认证失败");
return RealNameType.NORMAL.getType(); // 普通实名
}
// 风控实名修改用户风控信息
else {
int result = this.handleRiskRealName(realName, cacheKey, face.getRiskInfoId());
ServiceUtil.assertion(result != 1, "风控实名失败");
return RealNameType.RISK.getType(); // 风控实名
}
vo.setRealNameType(face.getType());
vo.setInfoId(face.getRiskInfoId());
vo.setPass(true);
return vo;
}
/**
@ -490,12 +499,7 @@ public class SmUserServiceImpl implements ISmUserService
return insert;
});
// 提交
if (result != null && result == 1) {
return riskInfoService.submit(riskInfoId);
}
return 0;
return result == null ? 0 : result;
}
/**
@ -606,6 +610,7 @@ public class SmUserServiceImpl implements ISmUserService
"当前风控无需提交实名认证"
);
dto.setRiskInfoId(info.getInfoId());
dto.setUserId(info.getUserId());
SmUserVO user = this.selectSmUserByUserId(info.getUserId());
if (user.getIsReal()) {

View File

@ -2,8 +2,11 @@ package com.ruoyi.web.controller.ss;
import java.util.List;
import javax.servlet.http.HttpServletResponse;
import com.ruoyi.ss.riskInfo.domain.dto.RiskInfoVerifyDTO;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
@ -104,4 +107,14 @@ public class RiskInfoController extends BaseController
{
return toAjax(riskInfoService.deleteRiskInfoByInfoIds(infoIds));
}
/**
* 审核风控材料
*/
@PreAuthorize("@ss.hasPermi('ss:riskInfo:verify')")
@Log(title = "风控材料", businessType = BusinessType.OTHER)
@PutMapping("/verify")
public AjaxResult verify(@RequestBody @Validated RiskInfoVerifyDTO dto) {
return toAjax(riskInfoService.verify(dto, getLoginUser()));
}
}