招标解析
This commit is contained in:
parent
8b8ba24e1c
commit
b33e804d5f
|
|
@ -89,9 +89,31 @@
|
|||
|
||||
<!--更新项目数据-->
|
||||
<update id="editProData">
|
||||
UPDATE tb_pro SET pro_name = #{proName},pro_introduction = #{proIntroduction},
|
||||
pro_code = #{proCode},tenderer = #{tenderer},agency = #{agency},bid_opening_time = #{bidOpeningTime},
|
||||
bid_opening_method = #{bidOpeningMethod} WHERE pro_id = #{proId}
|
||||
UPDATE tb_pro
|
||||
<set>
|
||||
<if test="proName != null and proName != ''">
|
||||
pro_name = #{proName},
|
||||
</if>
|
||||
<if test="proIntroduction != null and proIntroduction != ''">
|
||||
pro_introduction = #{proIntroduction},
|
||||
</if>
|
||||
<if test="proCode != null and proCode != ''">
|
||||
pro_code = #{proCode},
|
||||
</if>
|
||||
<if test="tenderer != null and tenderer != ''">
|
||||
tenderer = #{tenderer},
|
||||
</if>
|
||||
<if test="agency != null and agency != ''">
|
||||
agency = #{agency},
|
||||
</if>
|
||||
<if test="bidOpeningTime != null">
|
||||
bid_opening_time = #{bidOpeningTime},
|
||||
</if>
|
||||
<if test="bidOpeningMethod != null and bidOpeningMethod != ''">
|
||||
bid_opening_method = #{bidOpeningMethod},
|
||||
</if>
|
||||
</set>
|
||||
WHERE pro_id = #{proId}
|
||||
</update>
|
||||
|
||||
<!--更新标段数据-->
|
||||
|
|
|
|||
|
|
@ -19,4 +19,9 @@ public class TwoAnalysisResponse {
|
|||
private Long id;
|
||||
|
||||
private String jsonStr;
|
||||
|
||||
public TwoAnalysisResponse(Long id, String jsonStr) {
|
||||
this.id = id;
|
||||
this.jsonStr = jsonStr;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package com.bonus.rabbitmq.consumer;
|
||||
|
||||
import com.bonus.analysis.service.IASAnalysisService;
|
||||
import com.bonus.common.domain.analysis.dto.AnalysisProDto;
|
||||
import com.bonus.common.domain.analysis.po.ProComposition;
|
||||
import com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo;
|
||||
import com.bonus.common.domain.analysis.vo.AnalysisVo;
|
||||
import com.bonus.common.domain.ocr.dto.AnalysisOcrRequest;
|
||||
import com.bonus.common.domain.ocr.vo.AnalysisResponse;
|
||||
import com.bonus.common.domain.ocr.vo.TwoAnalysisResponse;
|
||||
|
|
@ -11,9 +13,13 @@ import com.bonus.common.utils.FileUtil;
|
|||
import com.bonus.file.config.MinioConfig;
|
||||
import com.bonus.file.util.MinioUtil;
|
||||
import com.bonus.ocr.service.AnalysisOcrService;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import com.rabbitmq.client.Channel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.amqp.core.Message;
|
||||
import org.springframework.amqp.rabbit.annotation.RabbitListener;
|
||||
|
|
@ -26,7 +32,11 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Array;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
|
|
@ -40,8 +50,6 @@ import java.util.stream.Collectors;
|
|||
@Slf4j
|
||||
public class RabbitMQConsumerService {
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
@Resource
|
||||
private MinioConfig minioConfig;
|
||||
|
||||
|
|
@ -54,6 +62,19 @@ public class RabbitMQConsumerService {
|
|||
@Resource(name = "IASAnalysisService")
|
||||
private IASAnalysisService analysisService;
|
||||
|
||||
private static final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
/**项目相关字段*/
|
||||
private static final String[] PRO_LABEL_ARR = {"PRO_NAME","PRO_INTRODUCTION","PRO_CODE","TENDERER","AGENCY","BID_OPENING_TIME","BID_OPENING_METHOD"};
|
||||
private static final String[] BID_LABEL_ARR = {"BID_SECTION_LIST"};
|
||||
|
||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy年MM月dd日HH时mm分ss秒");
|
||||
|
||||
// 正则表达式:匹配 "开标时间:" 后面的日期时间部分
|
||||
// 它会捕获直到第一个分号 (;) 或字符串结束前的所有内容
|
||||
// 匹配中文日期格式: YYYY年MM月DD日HH时MM分SS秒
|
||||
private static final Pattern DATE_PATTERN =
|
||||
Pattern.compile("开标时间[:|:](\\d{4}年\\d{2}月\\d{2}日\\d{2}时\\d{2}分\\d{2}秒)");
|
||||
|
||||
@RabbitListener(
|
||||
queues = "myQueue",
|
||||
|
|
@ -207,10 +228,24 @@ public class RabbitMQConsumerService {
|
|||
throw new RuntimeException("招标解析算法服务返回结果为空");
|
||||
}
|
||||
log.info("OCR识别成功 - 数据: {}", ocrResponse2.getData());
|
||||
Map<String, Object> data = ocrResponse2.getData();
|
||||
List<TwoAnalysisResponse> analysisDataList = new ArrayList<>();
|
||||
List<TwoAnalysisResponse> analysisDataList = processFileContent(objectMapper.writeValueAsString(ocrResponse2));
|
||||
|
||||
// 查询项目相关数据
|
||||
if(message.getCompositionType() == 1){
|
||||
List<Map<String, Object>> proMapList = convertData(labelItemVoList,1);
|
||||
List<Map<String, Object>> newProMapList = mergeData(proMapList, analysisDataList);
|
||||
Map<String, Object> proMap = extractAllNonIdKeys(newProMapList);
|
||||
AnalysisProDto analysisProDto = convertMapToAnalysisVo(proMap);
|
||||
analysisProDto.setProId(message.getProId());
|
||||
// 更新项目数据
|
||||
analysisService.editProData(analysisProDto);
|
||||
List<Map<String, Object>> bidMapList = convertData(labelItemVoList,2);
|
||||
List<Map<String, Object>> newBidMapList = mergeData(bidMapList, analysisDataList);
|
||||
}
|
||||
// 更新解析标签数据
|
||||
analysisService.updateProBidAnalysisData(message, analysisDataList);
|
||||
if(CollectionUtils.isNotEmpty(analysisDataList)) {
|
||||
analysisService.updateProBidAnalysisData(message, analysisDataList);
|
||||
}
|
||||
return true;
|
||||
} catch (IOException e) {
|
||||
log.error("OCR识别失败", e);
|
||||
|
|
@ -218,6 +253,62 @@ public class RabbitMQConsumerService {
|
|||
}
|
||||
}
|
||||
|
||||
public static List<TwoAnalysisResponse> processFileContent(String fileContent){
|
||||
|
||||
try {
|
||||
// Jackson ObjectMapper 是 JSON 序列化和反序列化的核心
|
||||
List<TwoAnalysisResponse> resultList = new ArrayList<>();
|
||||
|
||||
// 1. 解析整个文件内容
|
||||
JsonNode rootNode = objectMapper.readTree(fileContent);
|
||||
|
||||
// 2. 导航到 "data" 节点
|
||||
JsonNode dataNode = rootNode.path("data");
|
||||
|
||||
if (dataNode.isMissingNode() || !dataNode.isObject()) {
|
||||
return resultList;
|
||||
}
|
||||
// 3. 遍历 'data' 节点下的所有属性(即每个分析结果对象)
|
||||
// dataNode.fields() 返回一个迭代器,包含属性名和对应的值节点
|
||||
Iterator<Map.Entry<String, JsonNode>> fields = dataNode.fields();
|
||||
|
||||
while (fields.hasNext()) {
|
||||
Map.Entry<String, JsonNode> field = fields.next();
|
||||
JsonNode analysisObjectNode = field.getValue(); // 获取内部对象,如 BID_VALIDITY_WAYDYC 的值
|
||||
|
||||
if (!analysisObjectNode.isObject()) {
|
||||
continue; // 跳过非对象属性
|
||||
}
|
||||
|
||||
// 将 JsonNode 强制转换为 ObjectNode,以便进行字段操作(如移除)
|
||||
ObjectNode originalObjectNode = (ObjectNode) analysisObjectNode;
|
||||
|
||||
// 4. 提取 'id' 字段
|
||||
JsonNode idNode = originalObjectNode.get("id");
|
||||
if (idNode == null || !idNode.isNumber()) {
|
||||
System.err.println("警告:对象缺少或 'id' 不是数字,跳过该项。");
|
||||
continue;
|
||||
}
|
||||
Long id = idNode.asLong();
|
||||
|
||||
// 5. 从对象中移除 'id' 字段
|
||||
// **注意:** 此操作会修改 originalObjectNode,但这是我们所需要的。
|
||||
originalObjectNode.remove("id");
|
||||
|
||||
// 6. 将剩余的对象内容(即移除了id后的所有内容)转换成 JSON 字符串
|
||||
String jsonStr = objectMapper.writeValueAsString(originalObjectNode);
|
||||
|
||||
// 7. 创建 TwoAnalysisResponse 对象并添加到列表
|
||||
resultList.add(new TwoAnalysisResponse(id, jsonStr));
|
||||
}
|
||||
|
||||
return resultList;
|
||||
} catch (JsonProcessingException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建招标解析算法服务请求 - 一次处理
|
||||
*
|
||||
|
|
@ -283,6 +374,218 @@ public class RabbitMQConsumerService {
|
|||
return 1; // 规则: 剩下的情况也就是都是 1 了
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据要求筛选和转换数据列表。
|
||||
*
|
||||
* @param labelItemVoList 输入的列表
|
||||
* @return 转换后的 List<Map<String, Object>>
|
||||
*/
|
||||
public List<Map<String, Object>> convertData(List<AnalysisLabelItemOcrVo> labelItemVoList,int type) {
|
||||
// 1. 定义目标键数组 (转换为小写)
|
||||
// 这是一个优化步骤,避免在循环中重复转换大小写
|
||||
Map<String, String> lowerCaseKeyMap = new HashMap<>();
|
||||
for (String key : type == 1 ? PRO_LABEL_ARR : BID_LABEL_ARR) {
|
||||
lowerCaseKeyMap.put(key, key.toLowerCase());
|
||||
}
|
||||
List<Map<String, Object>> resultList = new ArrayList<>();
|
||||
|
||||
// 2. 遍历输入列表
|
||||
for (AnalysisLabelItemOcrVo vo : labelItemVoList) {
|
||||
// 3. 筛选数据: analysisLevel == 3
|
||||
if (vo.getAnalysisLevel() != null && vo.getAnalysisLevel() == 3) {
|
||||
|
||||
String targetField = vo.getTargetField();
|
||||
|
||||
// 确保 targetField 对应的值在我们的目标数组中
|
||||
if (lowerCaseKeyMap.containsKey(targetField)) {
|
||||
|
||||
// 4. 提取数据和转换
|
||||
Map<String, Object> resultMap = new HashMap<>();
|
||||
|
||||
// 获取小写键,例如 "PRO_NAME" -> "pro_name"
|
||||
String lowerCaseKey = lowerCaseKeyMap.get(targetField);
|
||||
|
||||
// 5. 构建 Map: { "id": vo.getId(), "pro_name": targetFieldValue }
|
||||
// 注意:根据您的描述 "取出对应的 数据 的 id 和 proLabelArr 中 的参数 改为(小写) 作为 key 形成 list map"
|
||||
// 结果Map的结构可能应该是 { "pro_name": vo.getId() },表示 pro_name 对应的值是这个对象的 id。
|
||||
// 我提供两种可能的解释:
|
||||
|
||||
// 【解释 A:Map 中只包含一个键值对,键是小写参数,值是 ID】
|
||||
// resultMap.put(lowerCaseKey, vo.getId());
|
||||
|
||||
// 【解释 B (更常见的数据结构):Map 中包含 ID 字段和目标字段】
|
||||
resultMap.put("id", vo.getId()); // 增加 id 键
|
||||
resultMap.put(lowerCaseKey, targetField); // 假设 targetField 字符串就是需要的值
|
||||
// 5. 收集结果
|
||||
resultList.add(resultMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据 ID 匹配,将 analysisDataList 中的 jsonStr 数据填充到 proMapList 对应的 Map 中。
|
||||
* * @param proMapList 包含待填充 Map 的列表 (应包含 "id" 键)。
|
||||
* @param analysisDataList 包含 JSON 字符串数据的列表。
|
||||
* @return 填充完成的 proMapList。
|
||||
*/
|
||||
public List<Map<String, Object>> mergeData(
|
||||
List<Map<String, Object>> proMapList,
|
||||
List<TwoAnalysisResponse> analysisDataList) {
|
||||
|
||||
// 1. 构建查找表 (基于 ID 的 Map)
|
||||
// 假设 proMapList 中的每个 Map 都包含一个 "id" 键
|
||||
Map<String, Map<String, Object>> proMapLookup = proMapList.stream()
|
||||
.filter(m -> m.containsKey("id"))
|
||||
.collect(Collectors.toMap(
|
||||
// 键:从 Map 中取出 Long 类型的 id,并将其转换为 String
|
||||
m -> String.valueOf(m.get("id")),
|
||||
// 值:Map 本身
|
||||
m -> m,
|
||||
// 处理 ID 冲突:如果两个 Map 具有相同的 "id",保留现有的那个
|
||||
(existing, replacement) -> existing
|
||||
));
|
||||
|
||||
// 2. 遍历分析列表
|
||||
for (TwoAnalysisResponse analysisData : analysisDataList) {
|
||||
String id = analysisData.getId().toString();
|
||||
String jsonStr = analysisData.getJsonStr();
|
||||
|
||||
// 检查 ID 是否存在于查找表中
|
||||
Map<String, Object> targetMap = proMapLookup.get(id);
|
||||
|
||||
if (targetMap != null && jsonStr != null && !jsonStr.trim().isEmpty()) {
|
||||
try {
|
||||
// 3. 解析 JSON 字符串为 Map
|
||||
// 使用 raw type 以适应不同的值类型
|
||||
Map<String, Object> sourceJsonMap =
|
||||
objectMapper.readValue(jsonStr, new com.fasterxml.jackson.core.type.TypeReference<Map<String, Object>>() {});
|
||||
|
||||
// 4. 匹配与填充
|
||||
for (Map.Entry<String, Object> entry : sourceJsonMap.entrySet()) {
|
||||
String originalKey = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
|
||||
// 将 JSON 键转换为小写,与 proMapList 的键保持一致
|
||||
String lowerCaseKey = originalKey.toLowerCase();
|
||||
|
||||
// 仅填充除 "id" 以外的键值对(根据您的要求)
|
||||
if (!"id".equals(lowerCaseKey)) {
|
||||
// 填充或覆盖目标 Map 中的值
|
||||
targetMap.put(lowerCaseKey, value);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (JsonProcessingException e) {
|
||||
// 处理 JSON 解析错误,例如打印日志或跳过
|
||||
System.err.println("Error parsing JSON for ID " + id + ": " + e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// proMapList 已经在循环中被修改(因为 Map 是引用类型),可以直接返回
|
||||
return proMapList;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. 转换 newProMapList 中 "bid_opening_time" 字段的值:
|
||||
* - 从复杂字符串中提取日期时间。
|
||||
* - 将提取到的日期时间转换为 Date 类型。
|
||||
* 2. 提取所有 Map 里除 "id" 以外的键,组成一个新的 Map 对象。
|
||||
*
|
||||
* @param newProMapList 包含数据的列表
|
||||
* @return 包含所有不重复键的新 Map (键为字段名,值为 null)
|
||||
*/
|
||||
public Map<String, Object> extractAllNonIdKeys(List<Map<String, Object>> newProMapList) {
|
||||
|
||||
// --- 步骤 1: 复杂日期类型转换 ---
|
||||
processComplexDateConversion(newProMapList);
|
||||
|
||||
// --- 步骤 2: 提取不重复的键 (原有的逻辑) ---
|
||||
// (省略与上一个回答中相同的键提取逻辑,因为它没有变化)
|
||||
Map<String, Object> allKeysMap = new HashMap<>();
|
||||
|
||||
if (newProMapList == null || newProMapList.isEmpty()) {
|
||||
return allKeysMap;
|
||||
}
|
||||
|
||||
for (Map<String, Object> map : newProMapList) {
|
||||
if (map != null) {
|
||||
for (String key : map.keySet()) {
|
||||
if (!"id".equals(key)) {
|
||||
allKeysMap.put(key, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allKeysMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 辅助方法:处理 "bid_opening_time" 字段的复杂日期字符串提取和转换
|
||||
*/
|
||||
private void processComplexDateConversion(List<Map<String, Object>> newProMapList) {
|
||||
if (newProMapList == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (Map<String, Object> map : newProMapList) {
|
||||
if (map != null && map.containsKey("bid_opening_time")) {
|
||||
Object timeValue = map.get("bid_opening_time");
|
||||
|
||||
if (timeValue instanceof String) {
|
||||
String fullTimeStr = (String) timeValue;
|
||||
|
||||
// 1. 使用正则表达式查找匹配的日期时间字符串
|
||||
Matcher matcher = DATE_PATTERN.matcher(fullTimeStr);
|
||||
|
||||
if (matcher.find()) {
|
||||
// 提取捕获组 1 中的日期时间 (例如:"2025年06月23日15时00分00秒")
|
||||
String dateOnlyStr = matcher.group(1);
|
||||
|
||||
try {
|
||||
// 2. 尝试将提取出的字符串解析为 Date 对象
|
||||
Date parsedDate = DATE_FORMAT.parse(dateOnlyStr);
|
||||
|
||||
// 3. 替换 Map 中的值,完成转换
|
||||
map.put("bid_opening_time", parsedDate);
|
||||
|
||||
} catch (ParseException e) {
|
||||
// 如果解析失败,可能是日期格式有微小差异
|
||||
System.err.println("Warning: Could not parse extracted date string '" + dateOnlyStr + "'. Error: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
// 如果正则表达式没有找到匹配的日期
|
||||
System.err.println("Warning: Date pattern not found in string: " + fullTimeStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public AnalysisProDto convertMapToAnalysisVo(Map<String, Object> proMap) {
|
||||
if (proMap == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 使用 ObjectMapper 的 convertValue 方法进行转换
|
||||
// Jackson 会自动匹配 Map 的键到 AnalysisVo 的字段,并处理类型转换。
|
||||
// 注意:如果 Map 中包含 Date 对象 (如 bidOpeningTime),Jackson 会正确处理。
|
||||
// 如果 Map 中包含的日期是字符串,则需要确保 AnalysisVo 字段上添加了适当的 @JsonFormat 注解。
|
||||
try {
|
||||
AnalysisProDto analysisProDto = objectMapper.convertValue(proMap, AnalysisProDto.class);
|
||||
return analysisProDto;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// 处理转换失败的情况,例如类型不匹配或字段缺失
|
||||
System.err.println("Error converting Map to AnalysisVo: " + e.getMessage());
|
||||
// 抛出异常或返回 null,取决于业务需求
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 从minio中获取文件
|
||||
*
|
||||
|
|
|
|||
Loading…
Reference in New Issue