实名认证修改

This commit is contained in:
磷叶 2025-04-26 18:17:12 +08:00
parent 66bf3645b6
commit c5032f64b6
16 changed files with 277 additions and 188 deletions

View File

@ -0,0 +1,52 @@
package com.ruoyi.common.valid.towEle;
import java.util.HashMap;
import java.util.Map;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.utils.spring.SpringUtils;
import com.ruoyi.common.valid.towEle.vo.IDResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.http.Method;
import lombok.extern.slf4j.Slf4j;
@Slf4j
public class TwoEleUtil {
public static final String HOST = "https://zidv2.market.alicloudapi.com/idcheck/Post";
public static final String APPCODE = SpringUtils.getRequiredProperty("twoEle.appCode");
/**
* 校验身份二要素是否一致
* @param host 请求地址
* @param appcode appcode
* @param idCard 身份证号
* @param realName 姓名
* @return Boolean
*/
public static IDResponse verifyIdentity(String idCard, String realName){
try {
Map<String, String> headers = new HashMap<>();
headers.put("Authorization", "APPCODE " + APPCODE);
//根据API的要求定义相对应的Content-Type
headers.put("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
Map<String, Object> bodys = new HashMap<>();
bodys.put("cardNo", idCard);
bodys.put("realName", realName);
String result= HttpUtil.createRequest(Method.POST, HOST).addHeaders(headers).form(bodys).execute().body();
log.info("实名认证请求返回==================="+result);
if (result == null) {
return null;
}
return JSONObject.parseObject(result, IDResponse.class);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}

View File

@ -0,0 +1,47 @@
package com.ruoyi.common.valid.towEle.vo;
import lombok.Data;
@Data
public class IDResponse {
private int error_code;
private String reason;
private Result result;
private String sn;
@Data
public class Result {
private String realname;
private String idcard;
private Boolean isok;
private IdCardInfor IdCardInfor;
}
@Data
public class IdCardInfor {
private String province;
private String city;
private String district;
private String area;
private String sex;
private String birthday;
}
public static boolean isSuccess(IDResponse response) {
return response != null && response.getError_code() == 0 && response.getResult() != null && response.getResult().getIsok();
}
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.bst.realName.domain.Face;
package com.ruoyi.bst.realName.domain.face;
import lombok.Data;

View File

@ -8,6 +8,7 @@ import com.ruoyi.common.annotation.Excel;
import com.ruoyi.common.annotation.Sensitive;
import com.ruoyi.common.core.domain.BaseEntity;
import com.ruoyi.common.core.interfaces.LogBizParam;
import com.ruoyi.common.core.validate.ValidGroup;
import com.ruoyi.common.enums.DesensitizedType;
import io.swagger.annotations.ApiModelProperty;
@ -32,13 +33,13 @@ public class RealName extends BaseEntity implements LogBizParam
@Excel(name = "用户姓名")
@ApiModelProperty("用户姓名")
@NotBlank(message = "用户姓名不允许为空")
@NotBlank(message = "用户姓名不允许为空", groups = {ValidGroup.Create.class})
@Sensitive(desensitizedType = DesensitizedType.USERNAME)
private String userName;
@Excel(name = "身份证")
@ApiModelProperty("身份证")
@NotBlank(message = "身份证不允许为空")
@NotBlank(message = "身份证不允许为空", groups = {ValidGroup.Create.class})
@Sensitive(desensitizedType = DesensitizedType.ID_CARD)
private String idCard;
@ -52,14 +53,22 @@ public class RealName extends BaseEntity implements LogBizParam
@Excel(name = "手机号")
@ApiModelProperty("手机号")
@NotBlank(message = "手机号不允许为空")
@NotBlank(message = "手机号不允许为空", groups = {ValidGroup.Create.class})
@Sensitive(desensitizedType = DesensitizedType.PHONE)
private String mobile;
@Excel(name = "实名认证状态:0-未实名 1-已实名")
@ApiModelProperty("实名认证状态:0-未实名 1-已实名")
@Excel(name = "实名认证状态")
@ApiModelProperty("实名认证状态")
private String status;
@Excel(name = "认证类型")
@ApiModelProperty("认证类型")
private String type;
@Excel(name = "认证结果")
@ApiModelProperty("认证结果")
private String result;
@Override
public Object logBizId() {
return id;

View File

@ -12,5 +12,4 @@ public class RealNameVO extends RealName{
@ApiModelProperty("跳转URL")
private String jumpUrl;
}

View File

@ -1,4 +1,4 @@
package com.ruoyi.bst.realName.domain.Refresh;
package com.ruoyi.bst.realName.domain.refresh;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;

View File

@ -0,0 +1,24 @@
package com.ruoyi.bst.realName.domain.dto;
import javax.validation.constraints.NotBlank;
import com.ruoyi.common.core.validate.ValidGroup;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class RealNameTowEleDTO {
@ApiModelProperty("用户ID")
private Long userId;
@ApiModelProperty("用户姓名")
@NotBlank(message = "用户姓名不允许为空", groups = {ValidGroup.Create.class})
private String userName;
@ApiModelProperty("身份证")
@NotBlank(message = "身份证不允许为空", groups = {ValidGroup.Create.class})
private String idCard;
}

View File

@ -8,7 +8,8 @@ import lombok.Getter;
public enum RealNameStatus {
UNREAL("0", "未认证"),
REAL("1", "已认证");
REAL("1", "已认证"),
FAILED("2", "认证失败");
private final String code;
private final String name;

View File

@ -0,0 +1,14 @@
package com.ruoyi.bst.realName.domain.enums;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum RealNameType {
TWO_ELEMENT("1", "二要素认证"),
FACE("2", "人脸认证");
private final String code;
private final String name;
}

View File

@ -0,0 +1,14 @@
package com.ruoyi.bst.realName.domain.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
public class RealNameFaceVO {
@ApiModelProperty("人脸识别token")
private String faceToken;
@ApiModelProperty("跳转URL")
private String jumpUrl;
}

View File

@ -1,11 +1,13 @@
package com.ruoyi.bst.realName.mapper;
import java.util.List;
import com.ruoyi.bst.realName.domain.RealName;
import com.ruoyi.bst.realName.domain.RealNameVO;
import com.ruoyi.bst.realName.domain.RealNameQuery;
import org.apache.ibatis.annotations.Param;
import com.ruoyi.bst.realName.domain.RealName;
import com.ruoyi.bst.realName.domain.RealNameQuery;
import com.ruoyi.bst.realName.domain.RealNameVO;
/**
* 实名认证信息Mapper接口
*
@ -71,10 +73,4 @@ public interface RealNameMapper
* @return 结果
*/
public int deleteRealNameByIds(@Param("ids") List<Long> ids);
Boolean checkIdCard(String idCard);
RealNameVO selectRealNameByUserId(Long id);
int deleteRealNameByUserId(Long id);
}

View File

@ -17,7 +17,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
brn.mobile,
brn.status,
brn.create_time,
brn.update_time
brn.type,
brn.result
from bst_real_name brn
</sql>
@ -29,6 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="query.mobile != null and query.mobile != ''"> and mobile = #{query.mobile}</if>
<if test="query.status != null and query.status != ''"> and status = #{query.status}</if>
<if test="query.userId != null"> and user_id = #{query.userId}</if>
<if test="query.type != null and query.type != ''"> and type = #{query.type}</if>
<if test="query.result != null and query.result != ''"> and result like concat('%', #{query.result}, '%')</if>
${query.params.dataScope}
</sql>
@ -43,22 +46,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<include refid="selectRealNameVo"/>
where id = #{id}
</select>
<select id="checkIdCard" resultType="java.lang.Boolean">
select count(1) from bst_real_name where id_card = #{idCard}
</select>
<select id="selectRealNameByUserId" resultType="com.ruoyi.bst.realName.domain.RealNameVO">
select brn.id,
brn.user_name,
brn.id_card,
brn.picture,
brn.score,
brn.mobile,
brn.status,
brn.create_time,
brn.update_time
from bst_real_name brn where user_id = #{userId}
</select>
<insert id="insertRealName" parameterType="RealName" useGeneratedKeys="true" keyProperty="id">
insert into bst_real_name
@ -71,7 +58,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">status,</if>
<if test="createTime != null">create_time,</if>
<if test="updateTime != null">update_time,</if>
<if test="userId != null">user_id</if>
<if test="userId != null">user_id,</if>
<if test="type != null and type != ''">type,</if>
<if test="result != null and result != ''">result,</if>
</trim>
<trim prefix="values (" suffix=")" suffixOverrides=",">
<if test="userName != null">#{userName},</if>
@ -82,141 +71,18 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="status != null and status != ''">#{status},</if>
<if test="createTime != null">#{createTime},</if>
<if test="updateTime != null">#{updateTime},</if>
<if test="userId != null">#{userId}</if>
<if test="userId != null">#{userId},</if>
<if test="type != null and type != ''">#{type},</if>
<if test="result != null and result != ''">#{result},</if>
</trim>
</insert>
<insert id="batchInsert" parameterType="RealName" useGeneratedKeys="true" keyProperty="id">
insert into bst_real_name
<trim prefix="(" suffix=")" suffixOverrides=",">
user_name,
id_card,
picture,
score,
mobile,
status,
create_time,
update_time,
</trim>
values
<foreach collection="list" item="i" separator=",">
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="i.userName != null ">#{i.userName},</if>
<if test="i.userName == null ">default,</if>
<if test="i.idCard != null ">#{i.idCard},</if>
<if test="i.idCard == null ">default,</if>
<if test="i.picture != null ">#{i.picture},</if>
<if test="i.picture == null ">default,</if>
<if test="i.score != null ">#{i.score},</if>
<if test="i.score == null ">default,</if>
<if test="i.mobile != null ">#{i.mobile},</if>
<if test="i.mobile == null ">default,</if>
<if test="i.status != null and i.status != ''">#{i.status},</if>
<if test="i.status == null or i.status == ''">default,</if>
<if test="i.createTime != null ">#{i.createTime},</if>
<if test="i.createTime == null ">default,</if>
<if test="i.updateTime != null ">#{i.updateTime},</if>
<if test="i.updateTime == null ">default,</if>
</trim>
</foreach>
</insert>
<update id="batchUpdate">
update bst_real_name
<trim prefix="SET" suffixOverrides=",">
<foreach open="user_name = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.userName != null ">
WHEN #{item.id} THEN #{item.userName}
</when>
<otherwise>
WHEN #{item.id} THEN `user_name`
</otherwise>
</choose>
</foreach>
<foreach open="id_card = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.idCard != null ">
WHEN #{item.id} THEN #{item.idCard}
</when>
<otherwise>
WHEN #{item.id} THEN `id_card`
</otherwise>
</choose>
</foreach>
<foreach open="picture = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.picture != null ">
WHEN #{item.id} THEN #{item.picture}
</when>
<otherwise>
WHEN #{item.id} THEN `picture`
</otherwise>
</choose>
</foreach>
<foreach open="score = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.score != null ">
WHEN #{item.id} THEN #{item.score}
</when>
<otherwise>
WHEN #{item.id} THEN `score`
</otherwise>
</choose>
</foreach>
<foreach open="mobile = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.mobile != null ">
WHEN #{item.id} THEN #{item.mobile}
</when>
<otherwise>
WHEN #{item.id} THEN `mobile`
</otherwise>
</choose>
</foreach>
<foreach open="status = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.status != null and item.status != ''">
WHEN #{item.id} THEN #{item.status}
</when>
<otherwise>
WHEN #{item.id} THEN `status`
</otherwise>
</choose>
</foreach>
<foreach open="create_time = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.createTime != null ">
WHEN #{item.id} THEN #{item.createTime}
</when>
<otherwise>
WHEN #{item.id} THEN `create_time`
</otherwise>
</choose>
</foreach>
<foreach open="update_time = CASE id" collection="list" item="item" close="END,">
<choose>
<when test="item.updateTime != null ">
WHEN #{item.id} THEN #{item.updateTime}
</when>
<otherwise>
WHEN #{item.id} THEN `update_time`
</otherwise>
</choose>
</foreach>
</trim>
where id in
<foreach item="item" collection="list" open="(" separator="," close=")">
#{item.id}
</foreach>
</update>
<update id="updateRealName" parameterType="RealName">
update bst_real_name
<trim prefix="SET" suffixOverrides=",">
<include refid="updateColumns"/>
</trim>
where id = #{data.id} OR user_id = #{data.userId}
where id = #{data.id}
</update>
<sql id="updateColumns">
@ -229,7 +95,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<if test="data.createTime != null">create_time = #{data.createTime},</if>
<if test="data.updateTime != null">update_time = #{data.updateTime},</if>
<if test="data.userId != null">user_id = #{data.userId},</if>
<if test="data.type != null and data.type != ''">type = #{data.type},</if>
<if test="data.result != null and data.result != ''">result = #{data.result},</if>
</sql>
<delete id="deleteRealNameById" parameterType="Long">
@ -242,7 +109,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{id}
</foreach>
</delete>
<delete id="deleteRealNameByUserId">
DELETE from bst_real_name where user_id = #{userId}
</delete>
</mapper>

View File

@ -1,10 +1,13 @@
package com.ruoyi.bst.realName.service;
import java.util.List;
import com.ruoyi.bst.realName.domain.RealName;
import com.ruoyi.bst.realName.domain.RealNameVO;
import com.ruoyi.bst.realName.domain.RealNameQuery;
import com.ruoyi.bst.realName.domain.Refresh.RealNameRefreshVO;
import com.ruoyi.bst.realName.domain.RealNameVO;
import com.ruoyi.bst.realName.domain.dto.RealNameTowEleDTO;
import com.ruoyi.bst.realName.domain.refresh.RealNameRefreshVO;
import com.ruoyi.bst.realName.domain.vo.RealNameFaceVO;
/**
* 实名认证信息Service接口
@ -55,7 +58,24 @@ public interface RealNameService
*/
public int deleteRealNameById(Long id);
RealNameVO realName(RealName realName);
/**
* 人脸实名认证
* @param realName
* @return
*/
RealNameFaceVO faceRealName(RealName realName);
/**
* 刷新用户实名认证结果
* @param userId
* @return
*/
RealNameRefreshVO refreshRealName(Long userId);
/**
* 二要素实名认证
* @param dto
* @return
*/
int twoElementRealName(RealNameTowEleDTO dto);
}

View File

@ -6,15 +6,17 @@ import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;
import com.ruoyi.bst.realName.domain.RealName;
import com.ruoyi.bst.realName.domain.RealNameQuery;
import com.ruoyi.bst.realName.domain.RealNameVO;
import com.ruoyi.bst.realName.domain.Face.Face;
import com.ruoyi.bst.realName.domain.Refresh.RealNameRefreshVO;
import com.ruoyi.bst.realName.domain.dto.RealNameTowEleDTO;
import com.ruoyi.bst.realName.domain.enums.RealNameStatus;
import com.ruoyi.bst.realName.domain.enums.RealNameType;
import com.ruoyi.bst.realName.domain.face.Face;
import com.ruoyi.bst.realName.domain.refresh.RealNameRefreshVO;
import com.ruoyi.bst.realName.domain.vo.RealNameFaceVO;
import com.ruoyi.bst.realName.mapper.RealNameMapper;
import com.ruoyi.bst.realName.service.RealNameService;
import com.ruoyi.common.constant.CacheConstants;
@ -34,6 +36,8 @@ import com.ruoyi.common.valid.liveness.LivenessResponseQueryData;
import com.ruoyi.common.valid.liveness.LivenessResponseTokenData;
import com.ruoyi.common.valid.liveness.LivenessUtils;
import com.ruoyi.common.valid.liveness.enums.LivenessQueryResult;
import com.ruoyi.common.valid.towEle.TwoEleUtil;
import com.ruoyi.common.valid.towEle.vo.IDResponse;
import com.ruoyi.system.user.service.UserService;
/**
@ -118,24 +122,17 @@ public class RealNameServiceImpl implements RealNameService
return realNameMapper.deleteRealNameById(id);
}
/**
* 实名认证
* @param realName
* @return
*/
@Override
public RealNameVO realName(RealName realName) {
public RealNameFaceVO faceRealName(RealName realName) {
UserVO user = userService.selectUserById(realName.getUserId());
ServiceUtil.assertion(user == null,"用户信息不存在");
Boolean flag = realNameMapper.checkIdCard(realName.getIdCard());
ServiceUtil.assertion(flag,"用户身份证重复");
// 获取活体检测token
LivenessResponseTokenData tokenData = LivenessUtils.getToken(LIVENESS_RETURN_URL, null, null);
ServiceUtil.assertion(tokenData == null || StringUtils.isBlank(tokenData.getToken()), "获取活体检测token失败");
// 存入缓存:10分钟
// TODO 改为存入数据库有效期10分钟
String cacheKey = CacheConstants.LIVENESS_TOKEN + realName.getUserId();
Face face = new Face();
face.setUserId(realName.getUserId());
@ -146,7 +143,7 @@ public class RealNameServiceImpl implements RealNameService
redisCache.setCacheObject(cacheKey, face, 10, TimeUnit.MINUTES);
// 返回数据
RealNameVO vo = new RealNameVO();
RealNameFaceVO vo = new RealNameFaceVO();
vo.setFaceToken(tokenData.getToken());
vo.setJumpUrl(LivenessUtils.JUMP_URL + "?token=" + tokenData.getToken());
return vo;
@ -212,6 +209,46 @@ public class RealNameServiceImpl implements RealNameService
return vo;
}
@Override
public int twoElementRealName(RealNameTowEleDTO dto) {
// TODO 判断用户是否已经实名认证
IDResponse idRes = TwoEleUtil.verifyIdentity(dto.getIdCard(), dto.getUserName());
boolean isSuccess = IDResponse.isSuccess(idRes);
// 保存实名信息
RealName realName = new RealName();
realName.setUserId(dto.getUserId());
realName.setUserName(dto.getUserName());
realName.setIdCard(dto.getIdCard());
realName.setStatus(isSuccess ? RealNameStatus.REAL.getCode() : RealNameStatus.FAILED.getCode());
realName.setType(RealNameType.TWO_ELEMENT.getCode());
realName.setResult(isSuccess ? "认证成功" : idRes.getReason());
Integer result = transactionTemplate.execute(status -> {
int insert = this.insertRealName(realName);
if (insert > 0) {
if (isSuccess) {
// 修改用户实名信息
UserVO user = new UserVO();
user.setUserId(dto.getUserId());
user.setRealName(dto.getUserName());
user.setRealIdCard(dto.getIdCard());
user.setIsReal(true);
int update = userService.updateUserRealName(user);
ServiceUtil.assertion(update != 1, "更新用户信息失败");
}
}
return insert;
});
return result == null ? 0 : result;
}
private int handleNormalRealName(RealName realName, String cacheKey) {
// 修改实名认证信息用户表

View File

@ -9,10 +9,12 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.bst.realName.domain.RealName;
import com.ruoyi.bst.realName.domain.dto.RealNameTowEleDTO;
import com.ruoyi.bst.realName.service.RealNameService;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.validate.ValidGroup;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.enums.LogBizType;
@ -26,12 +28,20 @@ public class AppRealNameController extends BaseController {
@Autowired
private RealNameService realNameService;
@ApiOperation("用户实名认证")
@ApiOperation("用户人脸实名认证")
@PutMapping("/realName")
@Log(title = "用户实名认证", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.REAL_NAME)
public AjaxResult realName(@RequestBody @Validated RealName realName){
@Log(title = "用户人脸实名认证", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.REAL_NAME)
public AjaxResult realName(@RequestBody @Validated(ValidGroup.Create.class) RealName realName){
realName.setUserId(getUserId());
return success(realNameService.realName(realName));
return success(realNameService.faceRealName(realName));
}
@ApiOperation("用户二要素实名认证")
@PutMapping("/twoElementRealName")
@Log(title = "用户二要素实名认证", businessType = BusinessType.OTHER, bizIdName = "arg0", bizType = LogBizType.REAL_NAME)
public AjaxResult twoElementRealName(@RequestBody @Validated(ValidGroup.Create.class) RealNameTowEleDTO dto){
dto.setUserId(getUserId());
return toAjax(realNameService.twoElementRealName(dto));
}
@ApiOperation("刷新用户实名认证结果")

View File

@ -158,7 +158,9 @@ face:
appKey: 204590328
appCode: 32b6c6445b1a42ed862dd4202392c47d
appSecret: td0vlGZRy9GxlrpinlrxSXFXVW34JxDh
# 二要素认证
twoEle:
appCode: 32b6c6445b1a42ed862dd4202392c47d
# 阿里云短信配置
sms: