From 35260a773e0d2dc5e6ccc0f040eda12fa1385732 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B5=B5=E5=BF=A0=E6=9E=97?= <170083662@qq.com> Date: Thu, 30 Jul 2026 23:28:21 +0800 Subject: [PATCH] =?UTF-8?q?refactor(certificate):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=BA=AB=E4=BB=BD=E8=AF=81OCR=E8=AF=86=E5=88=AB=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=EF=BC=8C=E5=A2=9E=E5=BC=BA=E5=AE=B9=E9=94=99=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 身份证OCR识别改为尽力而为模式,识别失败不阻断业务流程 - 增加异常捕获,避免OCR服务异常导致业务中断 - 仅在OCR识别成功且信息一致时更新认证状态和用户认证信息 - 在订单身份核验中OCR识别异常或识别失败均记录日志,业务继续执行 - 回填实名信息时增加字段有效性校验,避免空字段导致信息错误 - 优化身份证图片缺失及格式异常处理逻辑,不再返回失败,仅记录警告日志 --- .../controller/CertificateController.java | 39 +++---- .../controller/OpenEquipmentController.java | 103 ++++++++---------- 2 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/main/java/com/gxwebsoft/love/controller/CertificateController.java b/src/main/java/com/gxwebsoft/love/controller/CertificateController.java index e055d6b..8771a12 100644 --- a/src/main/java/com/gxwebsoft/love/controller/CertificateController.java +++ b/src/main/java/com/gxwebsoft/love/controller/CertificateController.java @@ -13,6 +13,7 @@ import com.gxwebsoft.common.system.service.UserService; import com.gxwebsoft.love.entity.Certificate; import com.gxwebsoft.love.param.CertificateParam; import com.gxwebsoft.love.service.CertificateService; +import lombok.extern.slf4j.Slf4j; import com.gxwebsoft.oa.vo.IdcardRespVO; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; @@ -31,6 +32,7 @@ import java.util.stream.Collectors; * @since 2023-06-24 11:18:52 */ @Api(tags = "证件管理记录表管理") +@Slf4j @RestController @RequestMapping("/api/love/certificate") public class CertificateController extends BaseController { @@ -95,28 +97,23 @@ public class CertificateController extends BaseController { return fail("请上传身份证照片", certificate); } - // 身份证OCR识别(正反面合并) - IdcardRespVO resp = certificateService.idcardOcr(collect.get(0), collect.get(1)); - if (resp == null) { - return fail("身份证识别调用失败", certificate); + // 身份证OCR识别(正反面合并,尽力而为:失败不阻断业务,识别成功且信息一致才置为已认证) + try { + IdcardRespVO resp = certificateService.idcardOcr(collect.get(0), collect.get(1)); + 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); } // 房屋认证 diff --git a/src/main/java/com/gxwebsoft/open/controller/OpenEquipmentController.java b/src/main/java/com/gxwebsoft/open/controller/OpenEquipmentController.java index fd43c2f..58f438d 100644 --- a/src/main/java/com/gxwebsoft/open/controller/OpenEquipmentController.java +++ b/src/main/java/com/gxwebsoft/open/controller/OpenEquipmentController.java @@ -446,75 +446,66 @@ public class OpenEquipmentController extends BaseController { * @param order 当前订单(校验通过时回填实名信息) * @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) { - // 1. 解析图片数组 + // 1. 解析图片数组(解析失败仅记录,不阻断业务) List images; try { images = JSONObject.parseArray(orderSourceData, String.class); } catch (Exception e) { - log.error("身份证图片数据解析失败, orderSourceData={}", orderSourceData, e); - return fail("身份证图片数据格式有误,请重新上传"); + log.warn("身份证图片数据解析失败,跳过OCR校验, orderSourceData={}", orderSourceData); + return null; } - // 2. 必须包含身份证正反面(约定下标 0=正面、1=反面) + // 2. 必须包含身份证正反面(约定下标 0=正面、1=反面);缺失则跳过 OCR,不阻断 if (images == null || images.size() < 2 || !StringUtils.hasText(images.get(0)) || !StringUtils.hasText(images.get(1))) { - return fail("请上传身份证正面和反面照片"); + log.warn("身份证正反面照片缺失,跳过OCR校验,业务继续"); + return null; } - // 3. 调第三方 OCR 识别(加保护,避免接口异常导致 NPE) - 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; + // 3. 调第三方 OCR 识别(best-effort:异常/失败均仅记录日志,放行业务) 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) { - log.error("身份证识别服务调用异常", 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); + log.error("身份证OCR识别调用异常,跳过校验,业务继续", e); } return null; }