feat(order): 新增身份证号手动填写及格式校验
- 在订单确认页新增身份证号输入框,支持识别失败时手动填写 - 身份证号字段改为可选项,仅有值时进行格式校验,格式校验规则为18位数字或含X/x结尾的号码 - 提交订单时仅当身份证号非空才将该字段传给后端,避免覆盖后端OCR识别结果 - 移除前端OCR相关密钥配置及调用,改为后端统一识别处理 - 优化上传逻辑及界面交互,确保身份证号信息正确传递及提示
This commit is contained in:
@@ -0,0 +1,31 @@
|
|||||||
|
# 2026-08-23 工作日志
|
||||||
|
|
||||||
|
## 订单"确认身份"卡住 —— 根因与最终修复(已落地)
|
||||||
|
|
||||||
|
### 根因(完整调用链,后端为命门)
|
||||||
|
1. 前端列表 `pages/order/index.vue:93`、`pages/order/detail.vue:528` 用 `receiptStatus==20 && idCode==null` 判定"确认身份"。`idCode` 前端从不赋值,必须由后端写入。
|
||||||
|
2. 后端 `OpenEquipmentController.receipt()` → `verifyIdCard()` 调 `IdcardServiceImpl`(蜜堂有信/阿里云市场,已失效的第三方 OCR)。OCR 调不通 → `order.setIdCode()` 永不执行 → `idCode` 恒为 null → 列表卡死。
|
||||||
|
3. 更关键:**`shop_order` 表根本没有 `id_code` / `real_name` / `address` 三列**(实体 `Order` 声明了字段但列不存在)。一旦 `setIdCode` 就 SQL 报错。
|
||||||
|
4. `OrderReceiptParam` 也没有 `idCode` 字段,所以前端发 `idCode` 后端不读(早期前端直连 OCR 方案实际无效)。
|
||||||
|
|
||||||
|
### 最终方案:后端百度 OCR(密钥服务端,安全)
|
||||||
|
- 用户给了百度密钥(API Key / Secret Key)+ 后端路径 `/Users/gxwebsoft/JAVA/yunxinwei-api`,明确"换接口"。
|
||||||
|
- 把失效的 `IdcardService` 替换为百度身份证识别,由后端从上传的正面图识别并写 `idCode`。
|
||||||
|
|
||||||
|
### 后端改动(已 BUILD SUCCESS)
|
||||||
|
- 新增 `common/core/utils/BaiduOcrService.java`:用 apiKey/secretKey 换 access_token(进程内缓存),调 `aip.baidubce.com/rest/2.0/ocr/v1/idcard` 取 `words_result['公民身份号码']`(校验 18 位格式),回填 `Order.idCode/realName/address` 及 `User`。
|
||||||
|
- 新增 `common/core/utils/BaiduIdCardResult.java`(结果 VO)。
|
||||||
|
- `OpenEquipmentController.java`:`verifyIdCard()` 改用 `baiduOcrService.recognizeFront(front)`;移除 `IdcardService`/`IdcardRespVO` 依赖(旧实现仍在注释块内)。
|
||||||
|
- `shop/param/OrderReceiptParam.java`:新增非必填 `idCode`(前端手动填写兜底通道,经 BeanCopier 写库)。
|
||||||
|
- `application.yml`:新增 `baidu.ocr.api-key/secret-key`(用户密钥已写入)。
|
||||||
|
- **新增 `数据库脚本/alter_shop_order_idcard.sql`:必须先在 `yunxinwei` 库执行 `ALTER TABLE shop_order ADD id_code/real_name/address`,否则 `updateById` 会因列不存在而失败。** 这是部署前必做的人工步骤。
|
||||||
|
|
||||||
|
### 前端改动(收敛,去掉暴露密钥的直连 OCR)
|
||||||
|
- `config.js`:移除前端 `baiduOcr` 密钥块。
|
||||||
|
- `pages/order/delivery.vue`:移除 `getBaiduToken`/`recognizeIdCard` 及 `chooseImage` 里的 OCR 触发;`idCode` 改为可选项(有值才格式校验);新增可手动填写的身份证号输入框(兜底);`handleSubmit` 改为仅当 `idCode` 非空才随请求发送(避免空串覆盖后端识别结果)。
|
||||||
|
|
||||||
|
### 实测验证要点(交用户部署后确认)
|
||||||
|
- 部署后端前先跑 SQL 加列。
|
||||||
|
- 提交收货后回列表,应出现"去支付"(`idCode` 非空)。
|
||||||
|
- 若 OCR 偶尔失败,用户可手动填身份证号兜底。
|
||||||
|
- 不再需要小程序加百度域名白名单(OCR 走服务端)。
|
||||||
@@ -17,7 +17,6 @@ module.exports = {
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// 注册用户的默认角色ID
|
// 注册用户的默认角色ID
|
||||||
// roleId: 81
|
// roleId: 81
|
||||||
// 商户编号
|
// 商户编号
|
||||||
|
|||||||
@@ -62,6 +62,7 @@
|
|||||||
<u-input type="number" v-model="emergentUser" placeholder="请输入紧急联系人电话" />
|
<u-input type="number" v-model="emergentUser" placeholder="请输入紧急联系人电话" />
|
||||||
<u-input type="text" v-model="officeAddress" placeholder="请输入单位地址" />
|
<u-input type="text" v-model="officeAddress" placeholder="请输入单位地址" />
|
||||||
<u-input type="text" v-model="homeAddress" placeholder="请输入家庭地址" />
|
<u-input type="text" v-model="homeAddress" placeholder="请输入家庭地址" />
|
||||||
|
<u-input type="idcard" v-model="idCode" placeholder="身份证号(一般自动识别,识别失败可手动填写)" />
|
||||||
</view>
|
</view>
|
||||||
</u-form>
|
</u-form>
|
||||||
<view class="btn">
|
<view class="btn">
|
||||||
@@ -132,6 +133,15 @@
|
|||||||
message: '请输入家庭地址',
|
message: '请输入家庭地址',
|
||||||
trigger: ['blur', 'change']
|
trigger: ['blur', 'change']
|
||||||
}],
|
}],
|
||||||
|
idCode: [{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
// 允许为空(身份证号由后端 OCR 自动识别);有值时才校验格式
|
||||||
|
if (!value) return true
|
||||||
|
return /^\d{17}[\dXx]$/.test(value || '')
|
||||||
|
},
|
||||||
|
message: '身份证号格式不正确',
|
||||||
|
trigger: ['blur', 'change']
|
||||||
|
}],
|
||||||
}
|
}
|
||||||
|
|
||||||
// 图片槽位说明(与 orderSourceData 下标一一对应)
|
// 图片槽位说明(与 orderSourceData 下标一一对应)
|
||||||
@@ -157,6 +167,8 @@
|
|||||||
emergentUser: '',
|
emergentUser: '',
|
||||||
officeAddress: '',
|
officeAddress: '',
|
||||||
homeAddress: '',
|
homeAddress: '',
|
||||||
|
// 身份证号(上传正面后由 OCR 自动识别回填,也可手动修改)
|
||||||
|
idCode: '',
|
||||||
// 按钮禁用
|
// 按钮禁用
|
||||||
// disabled: false,
|
// disabled: false,
|
||||||
submitText: '证件已上传并确认收货',
|
submitText: '证件已上传并确认收货',
|
||||||
@@ -269,7 +281,7 @@
|
|||||||
handleSubmit() {
|
handleSubmit() {
|
||||||
const that = this
|
const that = this
|
||||||
const app = this
|
const app = this
|
||||||
const { orderId, orderSourceData, captcha, receiptPhone, emergentUser, officeAddress, homeAddress } = app
|
const { orderId, orderSourceData, captcha, receiptPhone, emergentUser, officeAddress, homeAddress, idCode } = app
|
||||||
|
|
||||||
// 1) 校验 5 张图是否都已上传
|
// 1) 校验 5 张图是否都已上传
|
||||||
const missingIndex = []
|
const missingIndex = []
|
||||||
@@ -285,7 +297,8 @@
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
receiptOrder({
|
// 2) 身份证号优先由后端 OCR 自动识别并写库;前端仅在用户手动填写时才随请求带上(避免空串覆盖后端结果)
|
||||||
|
const params = {
|
||||||
orderId: orderId,
|
orderId: orderId,
|
||||||
orderSourceData: JSON.stringify(orderSourceData),
|
orderSourceData: JSON.stringify(orderSourceData),
|
||||||
captcha: captcha,
|
captcha: captcha,
|
||||||
@@ -293,7 +306,12 @@
|
|||||||
emergentUser: emergentUser,
|
emergentUser: emergentUser,
|
||||||
officeAddress: officeAddress,
|
officeAddress: officeAddress,
|
||||||
homeAddress: homeAddress
|
homeAddress: homeAddress
|
||||||
}).then(result => {
|
}
|
||||||
|
if (idCode) {
|
||||||
|
params.idCode = idCode
|
||||||
|
}
|
||||||
|
|
||||||
|
receiptOrder(params).then(result => {
|
||||||
if(result.code == 0) {
|
if(result.code == 0) {
|
||||||
that.$success("确认收货成功!")
|
that.$success("确认收货成功!")
|
||||||
// uni.navigateBack()
|
// uni.navigateBack()
|
||||||
@@ -348,6 +366,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user