refactor(certificate): 优化身份证OCR识别流程,增强容错性
- 身份证OCR识别改为尽力而为模式,识别失败不阻断业务流程 - 增加异常捕获,避免OCR服务异常导致业务中断 - 仅在OCR识别成功且信息一致时更新认证状态和用户认证信息 - 在订单身份核验中OCR识别异常或识别失败均记录日志,业务继续执行 - 回填实名信息时增加字段有效性校验,避免空字段导致信息错误 - 优化身份证图片缺失及格式异常处理逻辑,不再返回失败,仅记录警告日志
This commit is contained in:
@@ -13,6 +13,7 @@ import com.gxwebsoft.common.system.service.UserService;
|
|||||||
import com.gxwebsoft.love.entity.Certificate;
|
import com.gxwebsoft.love.entity.Certificate;
|
||||||
import com.gxwebsoft.love.param.CertificateParam;
|
import com.gxwebsoft.love.param.CertificateParam;
|
||||||
import com.gxwebsoft.love.service.CertificateService;
|
import com.gxwebsoft.love.service.CertificateService;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import com.gxwebsoft.oa.vo.IdcardRespVO;
|
import com.gxwebsoft.oa.vo.IdcardRespVO;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
@@ -31,6 +32,7 @@ import java.util.stream.Collectors;
|
|||||||
* @since 2023-06-24 11:18:52
|
* @since 2023-06-24 11:18:52
|
||||||
*/
|
*/
|
||||||
@Api(tags = "证件管理记录表管理")
|
@Api(tags = "证件管理记录表管理")
|
||||||
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/api/love/certificate")
|
@RequestMapping("/api/love/certificate")
|
||||||
public class CertificateController extends BaseController {
|
public class CertificateController extends BaseController {
|
||||||
@@ -95,28 +97,23 @@ public class CertificateController extends BaseController {
|
|||||||
return fail("请上传身份证照片", certificate);
|
return fail("请上传身份证照片", certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 身份证OCR识别(正反面合并)
|
// 身份证OCR识别(正反面合并,尽力而为:失败不阻断业务,识别成功且信息一致才置为已认证)
|
||||||
IdcardRespVO resp = certificateService.idcardOcr(collect.get(0), collect.get(1));
|
try {
|
||||||
if (resp == null) {
|
IdcardRespVO resp = certificateService.idcardOcr(collect.get(0), collect.get(1));
|
||||||
return fail("身份证识别调用失败", certificate);
|
if (resp != null && "FP00000".equals(resp.getCode())
|
||||||
|
&& certificate.getCertificateCode().equals(resp.getIdCardNo())
|
||||||
|
&& certificate.getRealName().equals(resp.getName())) {
|
||||||
|
certificate.setStatus(30);
|
||||||
|
User user = new User();
|
||||||
|
user.setUserId(loginUser.getUserId());
|
||||||
|
user.setCertification(1);
|
||||||
|
userService.updateById(user);
|
||||||
|
} else {
|
||||||
|
log.warn("身份证OCR识别未通过或跳过,证件先按待审核保存, code={}", resp == null ? "null" : resp.getCode());
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("身份证OCR识别调用异常,跳过校验,证件先按待审核保存", e);
|
||||||
}
|
}
|
||||||
if (!"FP00000".equals(resp.getCode())) {
|
|
||||||
return fail(resp.getMessage() == null ? "身份证识别失败" : resp.getMessage(), certificate);
|
|
||||||
}
|
|
||||||
|
|
||||||
certificate.setStatus(30);
|
|
||||||
|
|
||||||
// 信息是否一致
|
|
||||||
if (!certificate.getCertificateCode().equals(resp.getIdCardNo())) {
|
|
||||||
return fail("认证失败", certificate);
|
|
||||||
}
|
|
||||||
if (!certificate.getRealName().equals(resp.getName())) {
|
|
||||||
return fail("认证失败", certificate);
|
|
||||||
}
|
|
||||||
User user = new User();
|
|
||||||
user.setUserId(loginUser.getUserId());
|
|
||||||
user.setCertification(1);
|
|
||||||
userService.updateById(user);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
// 房屋认证
|
// 房屋认证
|
||||||
|
|||||||
@@ -446,75 +446,66 @@ public class OpenEquipmentController extends BaseController {
|
|||||||
* @param order 当前订单(校验通过时回填实名信息)
|
* @param order 当前订单(校验通过时回填实名信息)
|
||||||
* @return 校验失败返回 ApiResult(fail),校验通过返回 null
|
* @return 校验失败返回 ApiResult(fail),校验通过返回 null
|
||||||
*/
|
*/
|
||||||
|
/**
|
||||||
|
* 身份证 OCR 真实性校验(当前为「尽力而为」模式:OCR 调不通/未通过时仅记录日志,不阻断业务)。
|
||||||
|
* 等阿里云市场接口/AppCode 恢复正常后,本方法会在识别成功时自动回填实名信息,无需额外改动。
|
||||||
|
* orderSourceData 约定顺序:[0]=身份证正面、[1]=身份证反面、[2]=人车合照、[3]=车子照片、[4]=安装照片
|
||||||
|
*
|
||||||
|
* @param orderSourceData 前端传入的图片地址 JSON 数组字符串
|
||||||
|
* @param order 当前订单(校验通过时回填实名信息)
|
||||||
|
* @return 始终返回 null(放行业务);OCR 结果仅用于尽力回填
|
||||||
|
*/
|
||||||
private ApiResult<?> verifyIdCard(String orderSourceData, Order order) {
|
private ApiResult<?> verifyIdCard(String orderSourceData, Order order) {
|
||||||
// 1. 解析图片数组
|
// 1. 解析图片数组(解析失败仅记录,不阻断业务)
|
||||||
List<String> images;
|
List<String> images;
|
||||||
try {
|
try {
|
||||||
images = JSONObject.parseArray(orderSourceData, String.class);
|
images = JSONObject.parseArray(orderSourceData, String.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("身份证图片数据解析失败, orderSourceData={}", orderSourceData, e);
|
log.warn("身份证图片数据解析失败,跳过OCR校验, orderSourceData={}", orderSourceData);
|
||||||
return fail("身份证图片数据格式有误,请重新上传");
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 必须包含身份证正反面(约定下标 0=正面、1=反面)
|
// 2. 必须包含身份证正反面(约定下标 0=正面、1=反面);缺失则跳过 OCR,不阻断
|
||||||
if (images == null || images.size() < 2
|
if (images == null || images.size() < 2
|
||||||
|| !StringUtils.hasText(images.get(0)) || !StringUtils.hasText(images.get(1))) {
|
|| !StringUtils.hasText(images.get(0)) || !StringUtils.hasText(images.get(1))) {
|
||||||
return fail("请上传身份证正面和反面照片");
|
log.warn("身份证正反面照片缺失,跳过OCR校验,业务继续");
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 调第三方 OCR 识别(加保护,避免接口异常导致 NPE)
|
// 3. 调第三方 OCR 识别(best-effort:异常/失败均仅记录日志,放行业务)
|
||||||
String front = ImageUtil.ImageBase64(images.get(0) + "?x-oss-process=image/resize,w_750/quality,Q_80");
|
|
||||||
String back = ImageUtil.ImageBase64(images.get(1) + "?x-oss-process=image/resize,w_750/quality,Q_80");
|
|
||||||
|
|
||||||
IdcardRespVO verify;
|
|
||||||
try {
|
try {
|
||||||
verify = idcardService.verify(front, back);
|
String front = ImageUtil.ImageBase64(images.get(0) + "?x-oss-process=image/resize,w_750/quality,Q_80");
|
||||||
|
String back = ImageUtil.ImageBase64(images.get(1) + "?x-oss-process=image/resize,w_750/quality,Q_80");
|
||||||
|
|
||||||
|
IdcardRespVO verify = idcardService.verify(front, back);
|
||||||
|
if (verify == null) {
|
||||||
|
log.warn("身份证OCR识别返回为空,跳过校验,业务继续");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (!"FP00000".equals(verify.getCode())) {
|
||||||
|
log.warn("身份证OCR识别未通过 code={}, message={},跳过校验,业务继续", verify.getCode(), verify.getMessage());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 成功:回填实名信息
|
||||||
|
if (StringUtils.hasText(verify.getName()) && StringUtils.hasText(verify.getIdCardNo())) {
|
||||||
|
order.setRealName(verify.getName());
|
||||||
|
order.setIdCode(verify.getIdCardNo());
|
||||||
|
order.setAddress(verify.getAddress());
|
||||||
|
|
||||||
|
User user = userService.getById(order.getUserId());
|
||||||
|
if (user != null) {
|
||||||
|
user.setRealName(verify.getName());
|
||||||
|
user.setIdCard(verify.getIdCardNo());
|
||||||
|
user.setAddress(verify.getAddress());
|
||||||
|
userService.updateUser(user);
|
||||||
|
}
|
||||||
|
log.info("身份证OCR识别成功并回填实名 name={}, idCardNo={}", verify.getName(), verify.getIdCardNo());
|
||||||
|
} else {
|
||||||
|
log.warn("身份证OCR识别成功但字段缺失,跳过回填,业务继续");
|
||||||
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("身份证识别服务调用异常", e);
|
log.error("身份证OCR识别调用异常,跳过校验,业务继续", e);
|
||||||
return fail("身份证识别服务暂时不可用,请稍后重试");
|
|
||||||
}
|
|
||||||
if (verify == null) {
|
|
||||||
return fail("身份证识别服务调用失败,请稍后重试");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 正面校验:非 FP00000 说明传的不是有效身份证人像面
|
|
||||||
if (!"FP00000".equals(verify.getCode())) {
|
|
||||||
// 优先用阿里云返回的具体原因,没拿到再回退到通用提示
|
|
||||||
String reason = verify.getMessage();
|
|
||||||
if ("FRONT_FAIL".equals(verify.getCode())) {
|
|
||||||
return fail(StrUtil.isNotBlank(reason) ? reason : "身份证正面照片识别失败,请重新上传清晰的身份证人像面照片");
|
|
||||||
}
|
|
||||||
if ("BACK_FAIL".equals(verify.getCode())) {
|
|
||||||
return fail(StrUtil.isNotBlank(reason) ? reason : "身份证反面照片识别失败,请重新上传清晰的身份证国徽面照片");
|
|
||||||
}
|
|
||||||
log.warn("身份证识别未通过 code={}, message={}", verify.getCode(), verify.getMessage());
|
|
||||||
return fail(StrUtil.isNotBlank(reason) ? reason : "身份证识别失败,请重新上传清晰的身份证照片");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 5. 反面校验:签发机关/日期缺失说明传的不是有效身份证国徽面
|
|
||||||
if (StringUtils.isEmpty(verify.getIssueDate())
|
|
||||||
|| StringUtils.isEmpty(verify.getIssueOrg())
|
|
||||||
|| StringUtils.isEmpty(verify.getExpireDate())) {
|
|
||||||
return fail("身份证反面照片识别失败,请重新上传清晰的身份证国徽面照片");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 正面字段也兜底校验一次(避免 OCR 返回空字符串导致回填出空实名)
|
|
||||||
if (StringUtils.isEmpty(verify.getName())
|
|
||||||
|| StringUtils.isEmpty(verify.getIdCardNo())) {
|
|
||||||
return fail("身份证正面照片识别失败,请重新上传清晰的身份证人像面照片");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 6. 校验通过,回填实名信息
|
|
||||||
order.setRealName(verify.getName());
|
|
||||||
order.setIdCode(verify.getIdCardNo());
|
|
||||||
order.setAddress(verify.getAddress());
|
|
||||||
|
|
||||||
User user = userService.getById(order.getUserId());
|
|
||||||
if (user != null) {
|
|
||||||
user.setRealName(verify.getName());
|
|
||||||
user.setIdCard(verify.getIdCardNo());
|
|
||||||
user.setAddress(verify.getAddress());
|
|
||||||
userService.updateUser(user);
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user