接入百度ocr结婚证识别
This commit is contained in:
parent
3ab80e7931
commit
5fa97cc878
|
|
@ -0,0 +1 @@
|
|||
1.8
|
||||
|
|
@ -0,0 +1,253 @@
|
|||
# API 文档(领取与兑奖流程)
|
||||
|
||||
本文件整理新婚送福活动前端页面所用接口,包括领取流程与兑奖页面改造所需的现有接口与拟新增接口。文档基于当前代码库(com-marriage-client 与 com-admin-client)。
|
||||
|
||||
## 基本信息
|
||||
- 服务与端口:
|
||||
- 领取端(com-marriage-client):`http://localhost:8200`,应用名 `nxfc-marriage-client`
|
||||
- 后台端(com-admin-client):`http://localhost:8000`,应用名 `nxfc-admin-client`
|
||||
- 认证:
|
||||
- 站点登录与短信验证接口位于 `com-marriage-client` 的 `/marriage/common` 路径下。
|
||||
- 后台活动管理接口位于 `com-admin-client` 的 `/admin/activity` 路径下(通常用于管理端)。
|
||||
|
||||
---
|
||||
|
||||
## 领取流程页面
|
||||
|
||||
### 1)活动介绍页
|
||||
用于展示当前活动的名称、时间和面额信息。
|
||||
|
||||
- 拟新增(面向领取端,便于前端直接获取展示数据)
|
||||
- 方法:`GET`
|
||||
- 路径:`/marriage/activity/current`
|
||||
- 入参:无
|
||||
- 出参(示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"activityName": "新婚送福",
|
||||
"activityStartTime": "2025-09-15 00:00:00",
|
||||
"activityEndTime": "2025-10-31 23:59:59",
|
||||
"money": 6000,
|
||||
"status": 1
|
||||
}
|
||||
}
|
||||
```
|
||||
- 备注:如暂未在领取端提供该接口,可临时通过后台端获取列表接口获得活动信息:
|
||||
- 方法:`POST`
|
||||
- 路径:`/admin/activity/list`
|
||||
- 入参:无
|
||||
- 出参:`ResultObject` 包含活动集合,字段参考 `MarriageActivity`。
|
||||
|
||||
### 2)OCR识别结婚证,自动带出信息,领取二维码页
|
||||
为避免用户手动输入,新增 OCR 流程:上传结婚证图片并识别证件信息,前端使用识别结果自动填充表单,再进行短信校验与领取。
|
||||
|
||||
- OCR 上传图片
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/ocr/upload`
|
||||
- 入参:`multipart/form-data`,字段:`file`(结婚证图片)
|
||||
- 出参(示例):
|
||||
```json
|
||||
{ "code": 200, "msg": "", "data": { "uploadId": "ab12cd34..." } }
|
||||
```
|
||||
- 备注:服务端将图片内容以 Base64 缓存于 Redis(键:`OCR_UPLOAD-{uploadId}`,有效期 10 分钟),不落盘文件。
|
||||
|
||||
- 发送短信验证码
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/common/sms`
|
||||
- 入参:`CommSmsDTO`
|
||||
- `mobile`:手机号(必填)
|
||||
- `type`:验证码类型,`0`=登录;`1`=兑换领取;`2`=OCR识别(本页使用 `2`)
|
||||
- 出参(示例):
|
||||
```json
|
||||
{ "code": 200, "msg": "", "data": null }
|
||||
```
|
||||
- 备注:当前实现对 `type=1` 的校验逻辑使用了 `code` 字段比对,可能与期望不一致(代码中以 `MarriageCode::getCode == mobile` 判断重复)。如需严格校验手机号唯一性,可调整实现。
|
||||
|
||||
- OCR 识别并返回证件信息(供前端自动填充)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/ocr/parse`
|
||||
- 入参:`OcrParseDTO`
|
||||
- `mobile`:手机号(必填)
|
||||
- `smsCode`:短信验证码(必填,取自上方 `/sms`,`type=2`)
|
||||
- `uploadId`:上传接口返回的标识(必填)
|
||||
- 出参(示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"raw": "<百度OCR原始JSON字符串>",
|
||||
"words": ["宁夏回族自治区婚姻登记证", "字号6403812025001056", "男方张三", "女方李四", "登记日期2025-10-01"],
|
||||
"parsed": {
|
||||
"marriageNo": "6403812025001056",
|
||||
"husbandName": "张三",
|
||||
"wifeName": "李四",
|
||||
"registerDate": "2025-10-01"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
- 备注:前端可将 `parsed.marriageNo` 用于后续校验与领取,将 `husbandName`/`wifeName`用于展示或校验提示;若识别失败,提示重新上传清晰图片。
|
||||
|
||||
- 领取前校验(生成二维码前的校验与预览)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/receiveCheck`
|
||||
- 入参:`MarriageCodeDTO`
|
||||
- `marriageNo`:结婚登记证号(必填,长度≥11,且需符合活动条件)
|
||||
- `receiveName`:领取人姓名(必填)
|
||||
- `receiveMobile`:领取人手机号(必填)
|
||||
- `code`:核销码(必填)
|
||||
- `smsCode`:短信验证码(必填,取自上方 `/sms`,`type=2`)
|
||||
- `signImage`:领取人电子签名(选填,校验阶段可为空)
|
||||
- `salesNo`:站点号(选填,用于落库统计)
|
||||
- 出参:`ResultObject<MarriageCodeVO>`,其中包含可用于二维码展示的 `code` 等字段。
|
||||
- 业务规则摘要:
|
||||
- 校验结婚证号格式与活动条件。
|
||||
- 校验手机号、结婚证号、核销码是否已参与或使用过。
|
||||
- 校验短信验证码正确性(Redis 缓存键:`VERICODE_MOBILE 1-<mobile>`)。
|
||||
|
||||
- 确认领取(落库并置为已核销)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/receiveCode`
|
||||
- 入参:`MarriageCodeDTO`
|
||||
- 与 `receiveCheck` 基本一致,但 `signImage` 必填(签字图片,建议前端以 Base64 上传)
|
||||
- 出参:`ResultObject`(成功返回 200)
|
||||
- 落库行为:将 `MarriageCode` 的 `marriageNo`、`receiveName`、`receiveMobile`、`signImage`、`salesNo`、`receiveMoney=6000`、`receiveTime`、`status=1` 写入并更新。
|
||||
|
||||
### 3)查看已领取二维码页(包括二维码状态)
|
||||
支持按站点和月份查看领取列表统计;也建议提供按核销码查询单条状态的接口。
|
||||
|
||||
- 已有:按站点号与月份分页统计列表
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/codeList`
|
||||
- 入参:`MarriageCodeDTO`
|
||||
- `salesNo`:站点号(必填)
|
||||
- `page`、`size`:分页参数(必填)
|
||||
- `dataTime`:月份(格式 `yyyy-MM`,可选,默认当前月)
|
||||
- 出参:`ResultObject<MarriageCodeVO>`
|
||||
- `codeList`:列表(`MarriageCodeListVO`)
|
||||
- `receiveCount`:数量合计(字符串)
|
||||
- `receiveAmount`:金额合计(字符串,单位分)
|
||||
|
||||
- 拟新增:按核销码查询状态(用于二维码状态页)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/code/status`
|
||||
- 入参:
|
||||
```json
|
||||
{ "code": "xxxxxx" }
|
||||
```
|
||||
- 出参(示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"code": "xxxxxx",
|
||||
"status": 0,
|
||||
"marriageNo": null,
|
||||
"receiveName": null,
|
||||
"receiveMobile": null,
|
||||
"receiveTime": null
|
||||
}
|
||||
}
|
||||
```
|
||||
- 备注:`status=0` 未核销;`status=1` 已核销。已核销时返回对应的领取人信息与时间。
|
||||
|
||||
---
|
||||
|
||||
## OCR识别流程(免手动输入)
|
||||
|
||||
### 端到端流程
|
||||
1. 前端上传结婚证图片:`POST /marriage/ocr/upload` → 返回 `uploadId`
|
||||
2. 发送短信验证码:`POST /marriage/common/sms`(`type=2`)
|
||||
3. 识别证件信息:`POST /marriage/ocr/parse`(`mobile`、`smsCode`、`uploadId`)→ 返回 `parsed.marriageNo` 等信息
|
||||
4. 自动填充领取表单,走校验与领取:`POST /marriage/receiveCheck` → `POST /marriage/receiveCode`
|
||||
|
||||
### 安全与权限
|
||||
- `POST /marriage/ocr/**` 与 `POST /marriage/common/sms` 已放行匿名访问,用于领取端无登录场景。
|
||||
- 后端不存储用户上传的原始图片文件,仅在 Redis 缓存 Base64(10 分钟),减少存储与泄露风险。
|
||||
|
||||
---
|
||||
|
||||
## 兑奖页面(改为自动带出结婚证号、手机号、姓名)
|
||||
|
||||
目标:在兑奖页输入(或扫码)核销码后,自动拉取该码对应的 `marriageNo`、`receiveMobile`、`receiveName` 等信息进行表单预填。
|
||||
|
||||
- 拟新增:根据核销码获取领取信息(用于自动带出)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/code/info`
|
||||
- 入参:
|
||||
```json
|
||||
{ "code": "xxxxxx" }
|
||||
```
|
||||
- 出参(示例):
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "",
|
||||
"data": {
|
||||
"code": "xxxxxx",
|
||||
"status": 1,
|
||||
"marriageNo": "NX-2025-XXXXXXXXX",
|
||||
"receiveName": "张三",
|
||||
"receiveMobile": "13800000000",
|
||||
"receiveTime": "2025-10-10 12:00:00",
|
||||
"salesNo": "1001"
|
||||
}
|
||||
}
|
||||
```
|
||||
- 备注:
|
||||
- 若未领取(`status=0`),则上述字段为空或不返回;前端可引导先走“领取流程”。
|
||||
- 若已领取(`status=1`),则用于自动填充兑奖页的表单,减少重复输入。
|
||||
|
||||
---
|
||||
|
||||
## 站点登录(如需)
|
||||
用于站点管理后台或业务员端登录。
|
||||
|
||||
- 发送登录短信验证码(type=0)
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/common/sms`
|
||||
- 入参:`CommSmsDTO`(`mobile`,`type=0`)
|
||||
- 出参:`ResultObject`
|
||||
|
||||
- 登录
|
||||
- 方法:`POST`
|
||||
- 路径:`/marriage/common/login`
|
||||
- 入参:`MarrigeLoginDTO`
|
||||
- `mobile`、`password`(当前固定为 `88888888`)、`smsCode`
|
||||
- 出参:`ResultObject`(包含登录票据与站点信息)
|
||||
|
||||
---
|
||||
|
||||
## 返回结构与错误码
|
||||
- 所有接口统一返回 `ResultObject`:
|
||||
- `code`:业务状态码,`200` 表示成功
|
||||
- `msg`:提示信息
|
||||
- `data`:实际数据载体(对象或集合)
|
||||
- 常见失败信息(摘录):
|
||||
- `结婚证字号不能为空/长度不对/不符合活动条件`
|
||||
- `领取人姓名不能为空/领取人手机号不能为空`
|
||||
- `核验码不能为空/此代金卷不正确/此代金卷已使用过`
|
||||
- `该领取人已领取过新婚送福活动刮刮乐/这个证号已参与过活动`
|
||||
- `验证码错误,请重新输入`
|
||||
|
||||
---
|
||||
|
||||
## 字段模型参考
|
||||
- `MarriageCodeDTO`
|
||||
- `marriageNo`、`receiveName`、`code`、`receiveMobile`、`signImage`、`salesNo`、`smsCode`、`dataTime`
|
||||
- `CommSmsDTO`
|
||||
- `mobile`、`type`(`0` 登录,`1` 兑换领取)
|
||||
- `MarriageCodeVO` / `MarriageCodeListVO`
|
||||
- 列表与详情视图,包含领取统计与单条领取信息
|
||||
|
||||
---
|
||||
|
||||
## 前端配合建议
|
||||
- 二维码内容直接使用核销码 `code` 编码,状态页通过 `code/status` 查询。
|
||||
- 兑奖页在扫描或输入核销码后,先调用 `code/info` 自动填充,再根据业务需要提交后续操作。
|
||||
- 如本地开发环境不具备外网资源(DB/Redis/微信),建议提供 `local` profile,临时屏蔽定时任务与外部依赖,仅用于接口联调与页面开发。
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
应用名称
|
||||
新婚送福
|
||||
应用描述
|
||||
新婚福彩送福活动
|
||||
AppID:7216574
|
||||
API Key:nQnKdxJZJs6UycitAauLMcUW
|
||||
Secret Key:HTEVDoN3mN5J8usjAABt9k8euZ5cPJyr
|
||||
|
|
@ -83,6 +83,7 @@ public class AdminSalesController {
|
|||
*/
|
||||
@PostMapping("/pageTotal")
|
||||
public ResultObject pageTotal(@RequestBody SalesDTO salesDTO) {
|
||||
log.info("[admin/sales/pageTotal] req month={}, page={}, size={}", salesDTO.getMonth(), salesDTO.getPage(), salesDTO.getSize());
|
||||
return marriageSalesService.pageTotal(salesDTO);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -37,8 +37,16 @@ mybatis-plus:
|
|||
logic-delete-field: isdel # 设置逻辑删除字段名称
|
||||
mapper-locations: classpath*:/mapper/**/*Mapper.xml
|
||||
configuration:
|
||||
#log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
|
||||
log-impl: org.apache.ibatis.logging.nologging.NoLoggingImpl
|
||||
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
|
||||
|
||||
logging:
|
||||
level:
|
||||
root: INFO
|
||||
com.jinrui: DEBUG
|
||||
com.jinrui.core.dao.mapper: TRACE
|
||||
org.apache.ibatis: DEBUG
|
||||
com.baomidou: DEBUG
|
||||
org.apache.ibatis.executor: DEBUG
|
||||
jwt:
|
||||
secret: asindaionsfioudhhqk38474ansfioau8ksudhhqk3847495asdasdasdaqwi2uasdfrvvqw1asdasdfgasda23hdi181234dfjf823j41ll123h9
|
||||
expiration: 259200000
|
||||
|
|
|
|||
|
|
@ -61,10 +61,13 @@
|
|||
<logger level="WARN" name="com.netflix"/>
|
||||
<logger level="WARN" name="org.hibernate.SQL"/>
|
||||
<logger level="DEBUG" name="com.jinrui"/>
|
||||
<!-- 打印 MyBatis 与 MyBatis-Plus 的 SQL(非 StdOutImpl 时也能看到) -->
|
||||
<logger level="DEBUG" name="org.apache.ibatis"/>
|
||||
<logger level="DEBUG" name="com.baomidou"/>
|
||||
|
||||
|
||||
<!--root节点 全局日志级别,用来指定最基础的日志输出级别-->
|
||||
<root level="ERROR">
|
||||
<root level="debug">
|
||||
<appender-ref ref="FILE"/>
|
||||
<appender-ref ref="ERROR_FILE"/>
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import com.jinrui.core.service.IMarriageSalesService;
|
|||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import com.jinrui.core.util.SecurityUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.BeanUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
|
@ -40,6 +41,7 @@ import java.util.stream.Collectors;
|
|||
* @since 2025-09-15
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MarriageSalesServiceImpl extends ServiceImpl<MarriageSalesMapper, MarriageSales> implements IMarriageSalesService {
|
||||
@Autowired
|
||||
private IMarriageCodeService marriageCodeService;
|
||||
|
|
@ -96,10 +98,12 @@ public class MarriageSalesServiceImpl extends ServiceImpl<MarriageSalesMapper, M
|
|||
dto.setMonth(month);
|
||||
}
|
||||
|
||||
|
||||
List<MarriageCode> marriageCodeList = marriageCodeService.list(new LambdaQueryWrapper<MarriageCode>()
|
||||
.select(MarriageCode::getSalesNo, MarriageCode::getReceiveMoney)
|
||||
.eq(MarriageCode::getStatus, 1)
|
||||
.apply(" DATE_FORMAT(receive_time,'%Y-%m') = {0} ", dto.getMonth()));
|
||||
log.info("[admin/sales/pageTotal] fetched rows={}", marriageCodeList == null ? 0 : marriageCodeList.size());
|
||||
Map<String, List<MarriageCode>> marriageCodeMap = marriageCodeList.stream().collect(Collectors.groupingBy(MarriageCode::getSalesNo));
|
||||
|
||||
for (MarriageSales record : marriageSalesPage.getRecords()) {
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ public class SecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
// 过滤请求
|
||||
.authorizeRequests()
|
||||
// 对于登录login 验证码captchaImage 允许匿名访问
|
||||
.antMatchers("/marriage/common/login", "/marriage/common/sms").permitAll()
|
||||
.antMatchers("/marriage/common/login", "/marriage/common/sms", "/marriage/ocr/**").permitAll()
|
||||
// .antMatchers("/**", "/captchaImage").anonymous()
|
||||
.antMatchers(
|
||||
HttpMethod.GET,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,186 @@
|
|||
package com.jinrui.marriage.client.controller;
|
||||
|
||||
import com.jinrui.assembly.utils.jackson.JacksonUtil;
|
||||
import com.jinrui.assembly.utils.result.ResultObject;
|
||||
import com.jinrui.assembly.utils.result.ResultUtil;
|
||||
import com.jinrui.core.consts.RedisCacheKey;
|
||||
import com.jinrui.core.redis.RedisCacheManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.*;
|
||||
|
||||
import com.jinrui.assembly.utils.http.HttpUtil;
|
||||
import com.jinrui.marriage.client.dto.OcrParseDTO;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/marriage/ocr")
|
||||
@Slf4j
|
||||
public class OcrController {
|
||||
|
||||
@Autowired
|
||||
private RedisCacheManager redisCacheManager;
|
||||
|
||||
@Value("${baidu.ocr.apiKey:}")
|
||||
private String apiKey;
|
||||
|
||||
@Value("${baidu.ocr.secretKey:}")
|
||||
private String secretKey;
|
||||
|
||||
@Value("${baidu.ocr.storagePath:data/ocr}")
|
||||
private String storagePath;
|
||||
|
||||
@PostMapping("/upload")
|
||||
public ResultObject upload(@RequestParam("file") MultipartFile file) {
|
||||
if (file == null || file.isEmpty()) {
|
||||
return ResultUtil.failedMessage("请上传结婚证图片文件!");
|
||||
}
|
||||
try {
|
||||
String uploadId = UUID.randomUUID().toString().replace("-", "");
|
||||
String imageBase64 = Base64.getEncoder().encodeToString(file.getBytes());
|
||||
String key = "OCR_UPLOAD-" + uploadId;
|
||||
redisCacheManager.putObject(RedisCacheKey.DBINDEX_DEFAULT, key, imageBase64, RedisCacheKey.TIMEUNIT_MIN * 10);
|
||||
Map<String, Object> resp = new HashMap<>();
|
||||
resp.put("uploadId", uploadId);
|
||||
return ResultUtil.success(resp);
|
||||
} catch (IOException e) {
|
||||
log.error("上传结婚证图片失败", e);
|
||||
return ResultUtil.failedMessage("上传失败,请稍后再试!");
|
||||
}
|
||||
}
|
||||
|
||||
@PostMapping("/parse")
|
||||
public ResultObject parse(@RequestBody OcrParseDTO dto) {
|
||||
if (StringUtils.isBlank(dto.getMobile())) {
|
||||
return ResultUtil.failedMessage("手机号不能为空!");
|
||||
}
|
||||
if (StringUtils.isBlank(dto.getSmsCode())) {
|
||||
return ResultUtil.failedMessage("验证码不能为空!");
|
||||
}
|
||||
if (StringUtils.isBlank(dto.getUploadId())) {
|
||||
return ResultUtil.failedMessage("上传标识不能为空!");
|
||||
}
|
||||
|
||||
String smsKey = RedisCacheKey.VERICODE_MOBILE + "2-" + dto.getMobile();
|
||||
String verifyCode = (String) redisCacheManager.getObject(RedisCacheKey.DBINDEX_DEFAULT, smsKey);
|
||||
if (!StringUtils.equals(dto.getSmsCode(), verifyCode)) {
|
||||
return ResultUtil.failedMessage("验证码错误,请重新输入!");
|
||||
}
|
||||
|
||||
String pathKey = "OCR_UPLOAD-" + dto.getUploadId();
|
||||
String imageBase64 = (String) redisCacheManager.getObject(RedisCacheKey.DBINDEX_DEFAULT, pathKey);
|
||||
if (StringUtils.isBlank(imageBase64)) {
|
||||
return ResultUtil.failedMessage("上传文件不存在或已过期,请重新上传!");
|
||||
}
|
||||
|
||||
if (StringUtils.isBlank(apiKey) || StringUtils.isBlank(secretKey)) {
|
||||
return ResultUtil.failedMessage("百度OCR配置未设置,请联系管理员!");
|
||||
}
|
||||
|
||||
try {
|
||||
String accessToken = getAccessToken();
|
||||
if (StringUtils.isBlank(accessToken)) {
|
||||
return ResultUtil.failedMessage("获取OCR令牌失败,请稍后再试!");
|
||||
}
|
||||
String ocrUrl = "https://aip.baidubce.com/rest/2.0/ocr/v1/general_basic?access_token=" + accessToken;
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("image", imageBase64);
|
||||
params.put("language_type", "CHN_ENG");
|
||||
params.put("detect_direction", "true");
|
||||
String ocrResp = HttpUtil.post(ocrUrl, HttpUtil.map2Url(params), null);
|
||||
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
result.put("raw", ocrResp);
|
||||
List<String> words = extractWords(ocrResp);
|
||||
result.put("words", words);
|
||||
Map<String, String> parsed = parseMarriageFields(words);
|
||||
result.put("parsed", parsed);
|
||||
return ResultUtil.success(result);
|
||||
} catch (Exception e) {
|
||||
log.error("调用百度OCR失败", e);
|
||||
return ResultUtil.failedMessage("识别失败,请稍后再试!");
|
||||
}
|
||||
}
|
||||
|
||||
private String getAccessToken() {
|
||||
try {
|
||||
String url = "https://aip.baidubce.com/oauth/2.0/token";
|
||||
Map<String, String> params = new HashMap<>();
|
||||
params.put("grant_type", "client_credentials");
|
||||
params.put("client_id", apiKey);
|
||||
params.put("client_secret", secretKey);
|
||||
String resp = HttpUtil.get(url, params);
|
||||
String token = JacksonUtil.getValue(resp, "access_token");
|
||||
if (StringUtils.isNotBlank(token)) {
|
||||
return token.replace("\"", "");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("获取百度OCR令牌异常", e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private List<String> extractWords(String ocrResp) {
|
||||
try {
|
||||
// 粗略解析 words_result 列表
|
||||
String wordsResultJson = JacksonUtil.getValue(ocrResp, "words_result");
|
||||
if (StringUtils.isBlank(wordsResultJson)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 由于 JacksonUtil.getValue 返回的是子节点字符串,这里简单处理,按关键字段提取
|
||||
List<String> words = new ArrayList<>();
|
||||
String[] items = wordsResultJson.split("\\{\\\"words\\\":");
|
||||
for (String item : items) {
|
||||
int end = item.indexOf("}\"");
|
||||
if (end > 0) {
|
||||
String w = item.substring(0, end);
|
||||
w = w.replace("\"", "").replace("}", "");
|
||||
if (StringUtils.isNotBlank(w)) {
|
||||
words.add(w);
|
||||
}
|
||||
}
|
||||
}
|
||||
return words;
|
||||
} catch (Exception e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, String> parseMarriageFields(List<String> words) {
|
||||
Map<String, String> map = new HashMap<>();
|
||||
if (words == null || words.isEmpty()) {
|
||||
return map;
|
||||
}
|
||||
for (String w : words) {
|
||||
if (StringUtils.contains(w, "字号") || StringUtils.contains(w, "证字号")) {
|
||||
map.put("marriageNo", w.replaceAll("[^0-9A-Za-z\\u4e00-\\u9fa5]", ""));
|
||||
}
|
||||
if (StringUtils.contains(w, "男方") || StringUtils.contains(w, "男")) {
|
||||
map.put("husbandName", w.replace("男方", "").replace("男", ""));
|
||||
}
|
||||
if (StringUtils.contains(w, "女方") || StringUtils.contains(w, "女")) {
|
||||
map.put("wifeName", w.replace("女方", "").replace("女", ""));
|
||||
}
|
||||
if (StringUtils.contains(w, "登记日期") || StringUtils.contains(w, "日期")) {
|
||||
map.put("registerDate", w.replaceAll("[^0-9-年/月日]", ""));
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
private String getExtension(String name) {
|
||||
if (StringUtils.isBlank(name) || !name.contains(".")) {
|
||||
return null;
|
||||
}
|
||||
return name.substring(name.lastIndexOf('.') + 1);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.jinrui.marriage.client.dto;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class OcrParseDTO {
|
||||
private String mobile;
|
||||
private String smsCode;
|
||||
private String uploadId;
|
||||
}
|
||||
|
||||
|
|
@ -75,3 +75,11 @@ token:
|
|||
# 令牌有效期(默认30分钟)
|
||||
expireTime: 30
|
||||
|
||||
# 百度OCR配置(请在部署环境中设置实际值,不要提交密钥)
|
||||
baidu:
|
||||
ocr:
|
||||
appId: ${BAIDU_OCR_APP_ID:}
|
||||
apiKey: ${BAIDU_OCR_API_KEY:}
|
||||
secretKey: ${BAIDU_OCR_SECRET_KEY:}
|
||||
# 存储上传的结婚证图片目录
|
||||
storagePath: ${OCR_STORAGE_PATH:data/ocr}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,49 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 基本配置:可通过环境变量覆盖
|
||||
SERVER="${SERVER:-root@101.35.149.39}"
|
||||
TARGET_DIR="${TARGET_DIR:-/opt/fucai}"
|
||||
JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx512m}"
|
||||
PROFILE="${PROFILE:-dev}"
|
||||
|
||||
# 模块与服务信息
|
||||
JAR_PATTERN="com-admin-client/target/*-SNAPSHOT.jar"
|
||||
SERVICE_NAME="fucai-admin.service"
|
||||
ENV_FILE_REMOTE="${TARGET_DIR}/admin.env"
|
||||
|
||||
echo "==> 步骤1:Maven 打包(跳过测试)"
|
||||
mvn -T 1C -pl com-admin-client -am clean package -DskipTests
|
||||
|
||||
echo "==> 步骤2:定位打包产物"
|
||||
JAR_LOCAL=$(ls ${JAR_PATTERN} | head -n 1)
|
||||
if [[ -z "${JAR_LOCAL}" ]]; then
|
||||
echo "未找到 JAR:${JAR_PATTERN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAR_NAME=$(basename "${JAR_LOCAL}")
|
||||
echo "ADMIN JAR: ${JAR_LOCAL}"
|
||||
|
||||
echo "==> 步骤3:创建远端目录 ${TARGET_DIR}"
|
||||
ssh "${SERVER}" "mkdir -p ${TARGET_DIR}"
|
||||
|
||||
echo "==> 步骤4:上传 JAR(保留原始文件名)"
|
||||
scp "${JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${JAR_NAME}"
|
||||
|
||||
|
||||
|
||||
echo "==> 步骤7:重载/启用并重启服务"
|
||||
ssh "${SERVER}" " systemctl restart ${SERVICE_NAME} && systemctl status ${SERVICE_NAME} --no-pager -n 0 || true"
|
||||
|
||||
echo "==> 完成:${SERVICE_NAME} 已部署并重启"
|
||||
|
||||
cat <<EOF
|
||||
可选环境变量:
|
||||
- SERVER=root@101.35.149.39
|
||||
- TARGET_DIR=/opt/fucai
|
||||
- JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||
- PROFILE=dev
|
||||
使用示例:
|
||||
SERVER=root@101.35.149.39 PROFILE=dev bash deploy/deploy-admin.sh
|
||||
EOF
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 基本配置:可通过环境变量覆盖
|
||||
SERVER="${SERVER:-root@101.35.149.39}"
|
||||
TARGET_DIR="${TARGET_DIR:-/opt/fucai}"
|
||||
JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx512m}"
|
||||
PROFILE="${PROFILE:-dev}"
|
||||
|
||||
# 模块与服务信息
|
||||
JAR_PATTERN="com-kaijiang-client/target/*-SNAPSHOT.jar"
|
||||
SERVICE_NAME="fucai-kaijiang.service"
|
||||
ENV_FILE_REMOTE="${TARGET_DIR}/kaijiang.env"
|
||||
|
||||
echo "==> 步骤1:Maven 打包(跳过测试)"
|
||||
mvn -T 1C -pl com-kaijiang-client -am clean package -DskipTests
|
||||
|
||||
echo "==> 步骤2:定位打包产物"
|
||||
JAR_LOCAL=$(ls ${JAR_PATTERN} | head -n 1)
|
||||
if [[ -z "${JAR_LOCAL}" ]]; then
|
||||
echo "未找到 JAR:${JAR_PATTERN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAR_NAME=$(basename "${JAR_LOCAL}")
|
||||
echo "KAIJIANG JAR: ${JAR_LOCAL}"
|
||||
|
||||
echo "==> 步骤3:创建远端目录 ${TARGET_DIR}"
|
||||
ssh "${SERVER}" "mkdir -p ${TARGET_DIR}"
|
||||
|
||||
echo "==> 步骤4:上传 JAR(保留原始文件名)"
|
||||
scp "${JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${JAR_NAME}"
|
||||
|
||||
|
||||
echo "==> 步骤7:重载/启用并重启服务"
|
||||
ssh "${SERVER}" " systemctl restart ${SERVICE_NAME} && systemctl status ${SERVICE_NAME} --no-pager -n 0 || true"
|
||||
|
||||
echo "==> 完成:${SERVICE_NAME} 已部署并重启"
|
||||
|
||||
cat <<EOF
|
||||
可选环境变量:
|
||||
- SERVER=root@101.35.149.39
|
||||
- TARGET_DIR=/opt/fucai
|
||||
- JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||
- PROFILE=dev
|
||||
使用示例:
|
||||
SERVER=root@101.35.149.39 PROFILE=dev bash deploy/deploy-kaijiang.sh
|
||||
EOF
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 基本配置:可通过环境变量覆盖
|
||||
SERVER="${SERVER:-root@101.35.149.39}"
|
||||
TARGET_DIR="${TARGET_DIR:-/opt/fucai}"
|
||||
JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx512m}"
|
||||
PROFILE="${PROFILE:-dev}"
|
||||
|
||||
# 模块与服务信息
|
||||
JAR_PATTERN="com-marriage-client/target/*-SNAPSHOT.jar"
|
||||
SERVICE_NAME="fucai-marry.service"
|
||||
ENV_FILE_REMOTE="${TARGET_DIR}/marry.env"
|
||||
|
||||
echo "==> 步骤1:Maven 打包(跳过测试)"
|
||||
mvn -T 1C -pl com-marriage-client -am clean package -DskipTests
|
||||
|
||||
echo "==> 步骤2:定位打包产物"
|
||||
JAR_LOCAL=$(ls ${JAR_PATTERN} | head -n 1)
|
||||
if [[ -z "${JAR_LOCAL}" ]]; then
|
||||
echo "未找到 JAR:${JAR_PATTERN}" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAR_NAME=$(basename "${JAR_LOCAL}")
|
||||
echo "MARRY JAR: ${JAR_LOCAL}"
|
||||
|
||||
echo "==> 步骤3:创建远端目录 ${TARGET_DIR}"
|
||||
ssh "${SERVER}" "mkdir -p ${TARGET_DIR}"
|
||||
|
||||
echo "==> 步骤4:上传 JAR(保留原始文件名)"
|
||||
scp "${JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${JAR_NAME}"
|
||||
|
||||
|
||||
echo "==> 步骤7:重载/启用并重启服务"
|
||||
ssh "${SERVER}" " systemctl restart ${SERVICE_NAME} && systemctl status ${SERVICE_NAME} --no-pager -n 0 || true"
|
||||
|
||||
echo "==> 完成:${SERVICE_NAME} 已部署并重启"
|
||||
|
||||
cat <<EOF
|
||||
可选环境变量:
|
||||
- SERVER=root@101.35.149.39
|
||||
- TARGET_DIR=/opt/fucai
|
||||
- JAVA_OPTS="-Xms512m -Xmx1024m"
|
||||
- PROFILE=dev
|
||||
使用示例:
|
||||
SERVER=root@101.35.149.39 PROFILE=dev bash deploy/deploy-marry.sh
|
||||
EOF
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# 配置:服务器地址与目标目录
|
||||
SERVER="${SERVER:-root@101.35.149.39}"
|
||||
TARGET_DIR="${TARGET_DIR:-/opt/fucai}"
|
||||
|
||||
# JVM 与 Spring Profile(如需改为 prod,可将 PROFILE 改为 prod,并准备对应配置)
|
||||
JAVA_OPTS="${JAVA_OPTS:--Xms256m -Xmx512m}"
|
||||
PROFILE="${PROFILE:-dev}"
|
||||
|
||||
# 模块路径与打包后的 JAR 通配(适配当前模块的 finalName 差异)
|
||||
ADMIN_JAR_PATTERN="com-admin-client/target/*-SNAPSHOT.jar"
|
||||
KAIJIANG_JAR_PATTERN="com-kaijiang-client/target/*-SNAPSHOT.jar"
|
||||
MARRY_JAR_PATTERN="com-marriage-client/target/*-SNAPSHOT.jar"
|
||||
|
||||
echo "==> 步骤1:Maven 打包(跳过测试)"
|
||||
mvn -T 1C -pl com-admin-client,com-kaijiang-client,com-marriage-client -am clean package -DskipTests
|
||||
|
||||
echo "==> 步骤2:定位打包产物"
|
||||
ADMIN_JAR_LOCAL=$(ls ${ADMIN_JAR_PATTERN} | head -n 1)
|
||||
KAIJIANG_JAR_LOCAL=$(ls ${KAIJIANG_JAR_PATTERN} | head -n 1)
|
||||
MARRY_JAR_LOCAL=$(ls ${MARRY_JAR_PATTERN} | head -n 1)
|
||||
|
||||
if [[ -z "${ADMIN_JAR_LOCAL}" || -z "${KAIJIANG_JAR_LOCAL}" || -z "${MARRY_JAR_LOCAL}" ]]; then
|
||||
echo "打包后的 JAR 未找到,请检查 target 目录。" >&2
|
||||
echo "ADMIN: ${ADMIN_JAR_LOCAL}" >&2
|
||||
echo "KAIJIANG: ${KAIJIANG_JAR_LOCAL}" >&2
|
||||
echo "MARRY: ${MARRY_JAR_LOCAL}" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "ADMIN JAR: ${ADMIN_JAR_LOCAL}"
|
||||
echo "KAIJIANG JAR: ${KAIJIANG_JAR_LOCAL}"
|
||||
echo "MARRY JAR: ${MARRY_JAR_LOCAL}"
|
||||
|
||||
# 计算原始文件名(保留原始 jar 文件名)
|
||||
ADMIN_JAR_NAME=$(basename "${ADMIN_JAR_LOCAL}")
|
||||
KAIJIANG_JAR_NAME=$(basename "${KAIJIANG_JAR_LOCAL}")
|
||||
MARRY_JAR_NAME=$(basename "${MARRY_JAR_LOCAL}")
|
||||
|
||||
echo "==> 步骤3:创建远端目录 ${TARGET_DIR}"
|
||||
ssh "${SERVER}" "mkdir -p ${TARGET_DIR}"
|
||||
|
||||
echo "==> 步骤4:上传 JAR 到远端(保留原始文件名)"
|
||||
scp "${ADMIN_JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${ADMIN_JAR_NAME}"
|
||||
scp "${KAIJIANG_JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${KAIJIANG_JAR_NAME}"
|
||||
scp "${MARRY_JAR_LOCAL}" "${SERVER}:${TARGET_DIR}/${MARRY_JAR_NAME}"
|
||||
|
||||
echo "==> 步骤5:生成/更新各服务的 EnvironmentFile"
|
||||
ssh "${SERVER}" "printf '%s\n' \
|
||||
'JAVA_OPTS=\"${JAVA_OPTS}\"' \
|
||||
'PROFILE=\"${PROFILE}\"' \
|
||||
'JAR_PATH=\"${TARGET_DIR}/${ADMIN_JAR_NAME}\"' \
|
||||
> ${TARGET_DIR}/admin.env"
|
||||
ssh "${SERVER}" "printf '%s\n' \
|
||||
'JAVA_OPTS=\"${JAVA_OPTS}\"' \
|
||||
'PROFILE=\"${PROFILE}\"' \
|
||||
'JAR_PATH=\"${TARGET_DIR}/${KAIJIANG_JAR_NAME}\"' \
|
||||
> ${TARGET_DIR}/kaijiang.env"
|
||||
ssh "${SERVER}" "printf '%s\n' \
|
||||
'JAVA_OPTS=\"${JAVA_OPTS}\"' \
|
||||
'PROFILE=\"${PROFILE}\"' \
|
||||
'JAR_PATH=\"${TARGET_DIR}/${MARRY_JAR_NAME}\"' \
|
||||
> ${TARGET_DIR}/marry.env"
|
||||
|
||||
echo "==> 步骤6:上传 systemd 单元文件"
|
||||
scp deploy/systemd/fucai-admin.service "${SERVER}:/etc/systemd/system/fucai-admin.service"
|
||||
scp deploy/systemd/fucai-kaijiang.service "${SERVER}:/etc/systemd/system/fucai-kaijiang.service"
|
||||
scp deploy/systemd/fucai-marry.service "${SERVER}:/etc/systemd/system/fucai-marry.service"
|
||||
|
||||
echo "==> 步骤7:重载并启用服务"
|
||||
ssh "${SERVER}" "systemctl daemon-reload && systemctl enable fucai-admin.service fucai-kaijiang.service fucai-marry.service"
|
||||
|
||||
echo "==> 步骤8:以指定 Profile 重启服务"
|
||||
ssh "${SERVER}" "\
|
||||
echo '使用 JAVA_OPTS: ${JAVA_OPTS}, PROFILE: ${PROFILE}'; \
|
||||
systemctl restart fucai-admin.service; \
|
||||
systemctl restart fucai-kaijiang.service; \
|
||||
systemctl restart fucai-marry.service; \
|
||||
systemctl status fucai-admin.service --no-pager -n 0 || true; \
|
||||
systemctl status fucai-kaijiang.service --no-pager -n 0 || true; \
|
||||
systemctl status fucai-marry.service --no-pager -n 0 || true; \
|
||||
"
|
||||
|
||||
echo "==> 完成:JAR 已部署到 ${SERVER}:${TARGET_DIR} 并重启服务"
|
||||
|
||||
cat <<EOF
|
||||
可选环境变量:
|
||||
- SERVER=root@101.35.149.39 # 覆盖目标服务器
|
||||
- TARGET_DIR=/opt/fucai # 覆盖远端部署目录
|
||||
- JAVA_OPTS="-Xms512m -Xmx1024m" # 调整 JVM 参数
|
||||
- PROFILE=dev # 切换 Spring Profile
|
||||
使用示例:
|
||||
SERVER=root@101.35.149.39 PROFILE=dev bash deploy/deploy.sh
|
||||
EOF
|
||||
Loading…
Reference in New Issue