招标解析

This commit is contained in:
cwchen 2025-12-06 15:25:32 +08:00
parent 783490fa2e
commit 8b8ba24e1c
11 changed files with 646 additions and 139 deletions

View File

@ -107,6 +107,14 @@ public class AnalysisController extends BaseController {
return analysisService.saveBidData(dto);
}
@ApiOperation(value = "招标解析", notes = "查看解析数据")
@GetMapping("getAnalysisData")
@SysLog(title = "招标解析", module = "招标解析->查看解析数据", businessType = OperaType.QUERY, details = "查看解析数据", logType = 1)
@RequiresPermissions("analysis:analysis:query")
public AjaxResult getAnalysisData(AnalysisDto dto) {
return analysisService.getAnalysisData(dto);
}
@ApiOperation(value = "测试mq异步消息", notes = "测试mq异步消息")
@GetMapping("/testAsyncMq2")
public AjaxResult testAsyncMq2() {

View File

@ -10,6 +10,7 @@ import com.bonus.common.domain.analysis.dto.AnalysisProDto;
import com.bonus.common.domain.analysis.po.ProComposition;
import com.bonus.common.domain.analysis.vo.AnalysisBidVo;
import com.bonus.common.domain.analysis.vo.AnalysisVo;
import com.bonus.common.domain.analysis.vo.ProBidAnalysisResultVo;
import com.bonus.common.domain.file.vo.ResourceFileVo;
import com.bonus.common.domain.file.vo.SysFile;
import com.bonus.common.domain.ocr.dto.OcrRequest;
@ -24,6 +25,7 @@ import com.bonus.ocr.service.WordConvertPdfService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.checkerframework.checker.units.qual.A;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
@ -33,6 +35,7 @@ import java.io.File;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.stream.Collectors;
/**
@ -112,7 +115,7 @@ public class AnalysisService {
})
.join(); // 确保每个消息发送完成后再进行下一个
}*/
List<Map<String,Object>> list = new ArrayList<>();
List<Map<String, Object>> list = new ArrayList<>();
for (int i = 0; i < 2; i++) {
Map<String, Object> map = new HashMap<>();
String taskId = UUID.randomUUID().toString();
@ -128,6 +131,7 @@ public class AnalysisService {
/**
* 招标解析->查询列表
*
* @param dto
* @return List<AnalysisVo>
* @author cwchen
@ -139,6 +143,7 @@ public class AnalysisService {
/**
* 招标解析->新建项目
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -159,7 +164,7 @@ public class AnalysisService {
List<ProComposition> compositions = new ArrayList<>();
for (String uploadType : dto.getUploadType()) {
ProComposition vo = createVo(dto.getProId(), uploadType,"1");
ProComposition vo = createVo(dto.getProId(), uploadType, "1");
compositions.add(vo);
}
// 保存项目的文件组成数据
@ -178,6 +183,7 @@ public class AnalysisService {
msg.setProId(dto.getProId());
msg.setTemplateId(dto.getTemplateId());
msg.setAnalysisLabelId(dto.getAnalysisLabelId());
msg.setProCompositionId(id);
msg.setCompositionType(1);
msg.setTaskName("OCR_PROCESS");
asyncTaskList.add(msg);
@ -187,13 +193,13 @@ public class AnalysisService {
AsyncManager.me().executeSendRabbitMqMessage(asyncTaskList);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
}
public ProComposition createVo(Long proId,String value,String compositionType){
public ProComposition createVo(Long proId, String value, String compositionType) {
ProComposition proComposition = new ProComposition();
proComposition.setProId(proId);
proComposition.setCompositionFileName(value);
@ -203,6 +209,7 @@ public class AnalysisService {
/**
* 招标解析->查看详情
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -219,15 +226,16 @@ public class AnalysisService {
try {
WordConvertPdfResponse wordConvertPdfResponse = wordConvertPdfService.convertWordToPdf(ocrRequest);
String pdfBase64 = wordConvertPdfResponse.getPdfBase64();
Base64ToPdfConverter.convertToFile(pdfBase64,"C:\\Users\\10488\\Desktop\\test12121212.pdf");
Base64ToPdfConverter.convertToFile(pdfBase64, "C:\\Users\\10488\\Desktop\\test12121212.pdf");
} catch (IOException e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
}
return null;
}
/**
* 招标解析->查看项目详情
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -239,7 +247,7 @@ public class AnalysisService {
List<String> bidCompositions = analysisService.getBidCompositionByBid(analysisVo);
analysisVo.setBidCompositions(bidCompositions);
// 查询类型为2时查询文件
if(dto.getQueryType() == 2){
if (dto.getQueryType() == 2) {
// 查询项目组成文件
dto.setCompositionType("1");
List<ProComposition> compositions = analysisService.getProComposition(dto);
@ -248,10 +256,10 @@ public class AnalysisService {
for (ProComposition composition : compositions) {
List<ResourceFileVo> fileVoList = sourceFileService.getFilesByTable(composition.getId(), TableConstants.TB_PRO_COMPOSITION);
// 4.取minio中的文件访问路径
if(CollectionUtils.isNotEmpty(fileVoList)){
if (CollectionUtils.isNotEmpty(fileVoList)) {
for (ResourceFileVo file : fileVoList) {
SysFile sysFile = fileUploadService.getFile(file.getFilePath());
if(Objects.nonNull(sysFile)){
if (Objects.nonNull(sysFile)) {
file.setLsFilePath(sysFile.getUrl());
}
}
@ -265,6 +273,7 @@ public class AnalysisService {
/**
* 招标解析->更新项目数据
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -282,7 +291,7 @@ public class AnalysisService {
analysisService.editProData(dto);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
@ -300,7 +309,7 @@ public class AnalysisService {
analysisService.editBidData(dto);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
@ -308,6 +317,7 @@ public class AnalysisService {
/**
* 删除项目数据
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -327,9 +337,9 @@ public class AnalysisService {
analysisService.delBidData(dto);
// 删除项目组成文件
List<ProComposition> compositions = analysisService.getProComposition(dto);
if(CollectionUtils.isNotEmpty(compositions)){
if (CollectionUtils.isNotEmpty(compositions)) {
for (ProComposition composition : compositions) {
sourceFileService.delResourceFileByTable(composition.getId(),TableConstants.TB_PRO_COMPOSITION);
sourceFileService.delResourceFileByTable(composition.getId(), TableConstants.TB_PRO_COMPOSITION);
}
}
// 删除模板组成数据
@ -338,7 +348,7 @@ public class AnalysisService {
analysisService.delProBidAnalysisResult(dto);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
@ -346,6 +356,7 @@ public class AnalysisService {
/**
* 招标解析->标段提交文件解析
*
* @param dto
* @return AjaxResult
* @author cwchen
@ -365,7 +376,7 @@ public class AnalysisService {
List<ProComposition> compositions = new ArrayList<>();
for (String uploadType : dto.getUploadType()) {
ProComposition vo = createVo(dto.getBidId(), uploadType,"2");
ProComposition vo = createVo(dto.getBidId(), uploadType, "2");
compositions.add(vo);
}
// 保存标段的文件组成数据
@ -391,10 +402,106 @@ public class AnalysisService {
AsyncManager.me().executeSendRabbitMqMessage(asyncTaskList);
return AjaxResult.success();
} catch (Exception e) {
log.error(e.toString(),e);
log.error(e.toString(), e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
return AjaxResult.error();
}
}
/**
* 查看标段解析数据
*
* @param dto
* @return AjaxResult
* @author cwchen
* @date 2025/12/6 13:41
*/
public AjaxResult getAnalysisData(AnalysisDto dto) {
// 查询工程和标段的解析数据
List<ProBidAnalysisResultVo> proList = analysisService.getProBidAnalysisResult(dto, 1);
List<ProBidAnalysisResultVo> bidList = analysisService.getProBidAnalysisResult(dto, 2);
// 2. 判空处理防止空指针
/*if (CollectionUtils.isNotEmpty(proList) && CollectionUtils.isNotEmpty(bidList)) {
// 3. bidList 转换为 Map<ItemId, Content>
// Key: analysisLabelItemId, Value: analysisContent
// (v1, v2) -> v1 是为了防止 bidList 中有重复 ID 报错取第一个找到的值
Map<String, String> bidContentMap = bidList.stream()
.filter(item -> item.getAnalysisLabelItemId() != null && item.getAnalysisContent() != null)
.collect(Collectors.toMap(
ProBidAnalysisResultVo::getAnalysisLabelItemId,
ProBidAnalysisResultVo::getAnalysisContent,
(v1, v2) -> v1
));
// 4. 遍历 proList 进行合并
for (ProBidAnalysisResultVo proItem : proList) {
// 逻辑如果 pro 的内容为空null 空字符串才去尝试合并
if (proItem.getAnalysisContent() == null || proItem.getAnalysisContent().trim().isEmpty()) {
// 根据 ID Map 中获取内容
String contentFromBid = bidContentMap.get(proItem.getAnalysisLabelItemId());
// 如果 bid 中有值则赋值
if (contentFromBid != null) {
proItem.setAnalysisContent(contentFromBid);
}
}
}
}*/
List<ProBidAnalysisResultVo> tree = buildTree(proList, "0");
return AjaxResult.success("查询成功", tree);
}
/**
* 将扁平 List 转换为树形结构并按 analysisSort 排序
*
* @param flatList 扁平的 ProBidAnalysisResultVo 列表
* @param rootId 根节点的 parentId (通常是 null, "0", 或其他特定值)
* @return 排序后的树形结构根节点列表
*/
public static List<ProBidAnalysisResultVo> buildTree(
List<ProBidAnalysisResultVo> flatList,
String rootId) {
if (flatList == null || flatList.isEmpty()) {
return new ArrayList<>();
}
// 1. parentId 分组Map<String (parentId), List<ProBidAnalysisResultVo>>
// 这一步是性能优化的关键 O(N^2) 降低到 O(N)
Map<String, List<ProBidAnalysisResultVo>> map = flatList.stream()
.filter(item -> item.getParentId() != null) // 过滤掉 parentId null 的情况
.collect(Collectors.groupingBy(ProBidAnalysisResultVo::getParentId));
// 2. 递归构建树
return buildTreeRecursive(rootId, map);
}
/**
* 递归构建树的核心逻辑
*/
private static List<ProBidAnalysisResultVo> buildTreeRecursive(
String parentId,
Map<String, List<ProBidAnalysisResultVo>> map) {
// 1. Map 中取出当前 parentId 的所有子节点
List<ProBidAnalysisResultVo> childrenList = map.getOrDefault(parentId, Collections.emptyList());
// 2. 对当前层级的子节点列表进行排序 (根据 analysisSort 升序)
// 注意analysisSort 可能是 Integer需要处理 null 通常放在最后或最前
childrenList.sort(Comparator.comparing(
ProBidAnalysisResultVo::getAnalysisSort,
Comparator.nullsLast(Comparator.naturalOrder())
));
// 3. 递归处理每个子节点
for (ProBidAnalysisResultVo node : childrenList) {
// 递归调用以当前节点的 analysisLabelItemId 作为新的 parentId
List<ProBidAnalysisResultVo> grandChildren = buildTreeRecursive(node.getAnalysisLabelItemId(), map);
// 设置子节点
node.setChildren(grandChildren);
}
// 4. 返回当前层级已排序且完成子节点挂载的列表
return childrenList;
}
}

View File

@ -7,8 +7,11 @@ import com.bonus.common.domain.analysis.po.ProComposition;
import com.bonus.common.domain.analysis.vo.AnalysisBidVo;
import com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo;
import com.bonus.common.domain.analysis.vo.AnalysisVo;
import com.bonus.common.domain.analysis.vo.ProBidAnalysisResultVo;
import com.bonus.common.domain.ocr.vo.TwoAnalysisResponse;
import com.bonus.common.domain.rabbitmq.dto.RabbitMqMessage;
import org.apache.ibatis.annotations.Param;
import org.springframework.security.core.parameters.P;
import org.springframework.stereotype.Repository;
import java.util.List;
@ -96,12 +99,12 @@ public interface IASAnalysisMapper {
/**
* 获取解析标签项
* @param analysisLabelId
* @param message
* @return List<AnalysisLabelItemVo>
* @author cwchen
* @date 2025/11/29 14:49
*/
List<AnalysisLabelItemOcrVo> getAnalysisLabels(@Param("analysisLabelId") Long analysisLabelId, @Param("templateId") Long templateId);
List<AnalysisLabelItemOcrVo> getAnalysisLabels(RabbitMqMessage message);
/**
* 删除项目数据
@ -174,4 +177,52 @@ public interface IASAnalysisMapper {
* @date 2025/11/30 15:03
*/
List<ProComposition> getProCompositionState(RabbitMqMessage message);
/**
* isLabelExist
* @param message
* @return int
* @author cwchen
* @date 2025/12/6 11:07
*/
int isLabelExist(RabbitMqMessage message);
/**
* 从解析内容中读取标签配置
* @param message
* @return List<AnalysisLabelItemOcrVo>
* @author cwchen
* @date 2025/12/6 11:13
*/
List<AnalysisLabelItemOcrVo> getAnalysisLabelsByProOrBid(RabbitMqMessage message);
/**
* 保存解析标签数据
* @param message
* @param labelItemVoList
* @return void
* @author cwchen
* @date 2025/12/6 12:26
*/
void saveProBidAnalysisData(@Param("params") RabbitMqMessage message, @Param("list") List<AnalysisLabelItemOcrVo> labelItemVoList);
/**
* 更新解析标签数据
* @param message
* @param analysisDataList
* @return void
* @author cwchen
* @date 2025/12/6 13:17
*/
void updateProBidAnalysisData(@Param("params") RabbitMqMessage message, @Param("list")List<TwoAnalysisResponse> analysisDataList);
/**
* 查询工程和标段的解析数据
* @param dto
* @param type
* @return List<ProBidAnalysisResultVo>
* @author cwchen
* @date 2025/12/6 13:47
*/
List<ProBidAnalysisResultVo> getProBidAnalysisResult(@Param("params") AnalysisDto dto,@Param("type") int type);
}

View File

@ -7,6 +7,8 @@ import com.bonus.common.domain.analysis.po.ProComposition;
import com.bonus.common.domain.analysis.vo.AnalysisBidVo;
import com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo;
import com.bonus.common.domain.analysis.vo.AnalysisVo;
import com.bonus.common.domain.analysis.vo.ProBidAnalysisResultVo;
import com.bonus.common.domain.ocr.vo.TwoAnalysisResponse;
import com.bonus.common.domain.rabbitmq.dto.RabbitMqMessage;
import java.util.List;
@ -22,6 +24,7 @@ public interface IASAnalysisService {
/**
* 招标解析->查询列表
*
* @param dto
* @return List<AnalysisVo>
* @author cwchen
@ -31,6 +34,7 @@ public interface IASAnalysisService {
/**
* 保存项目数据
*
* @param dto
* @return void
* @author cwchen
@ -40,6 +44,7 @@ public interface IASAnalysisService {
/**
* 招标解析->查看详情
*
* @param dto
* @return List<AnalysisBidVo>
* @author cwchen
@ -49,6 +54,7 @@ public interface IASAnalysisService {
/**
* 招标解析->查看项目详情
*
* @param dto
* @return AnalysisVo
* @author cwchen
@ -58,6 +64,7 @@ public interface IASAnalysisService {
/**
* 更新项目数据
*
* @param dto
* @return void
* @author cwchen
@ -67,6 +74,7 @@ public interface IASAnalysisService {
/**
* 更新标段数据
*
* @param dto
* @return void
* @author cwchen
@ -76,6 +84,7 @@ public interface IASAnalysisService {
/**
* 保存项目的文件组成
*
* @param compositions
* @return void
* @author cwchen
@ -85,6 +94,7 @@ public interface IASAnalysisService {
/**
* 查询项目组成文件
*
* @return List<ProComposition>
* @author cwchen
* @date 2025/11/26 15:54
@ -94,15 +104,17 @@ public interface IASAnalysisService {
/**
* 获取解析标签项
* @param analysisLabelId
*
* @param message
* @return List<AnalysisLabelItemVo>
* @author cwchen
* @date 2025/11/29 14:48
*/
List<AnalysisLabelItemOcrVo> getAnalysisLabels(Long analysisLabelId, Long templateId);
List<AnalysisLabelItemOcrVo> getAnalysisLabels(RabbitMqMessage message);
/**
* 删除项目数据
*
* @param dto
* @return void
* @author cwchen
@ -112,6 +124,7 @@ public interface IASAnalysisService {
/**
* 删除标段数据
*
* @param dto
* @return void
* @author cwchen
@ -121,6 +134,7 @@ public interface IASAnalysisService {
/**
* 删除模板组成数据
*
* @param dto
* @return void
* @author cwchen
@ -130,6 +144,7 @@ public interface IASAnalysisService {
/**
* 删除项目/标段解析数据
*
* @param dto
* @return void
* @author cwchen
@ -139,6 +154,7 @@ public interface IASAnalysisService {
/**
* 查询标段文件的文件组成
*
* @param analysisVo
* @return List<ProComposition>
* @author cwchen
@ -148,6 +164,7 @@ public interface IASAnalysisService {
/**
* 更新项目或者标段的文件组成的解析状态
*
* @param message
* @return void
* @author cwchen
@ -157,6 +174,7 @@ public interface IASAnalysisService {
/**
* 更新项目或者标段的解析状态
*
* @param message
* @return void
* @author cwchen
@ -166,10 +184,61 @@ public interface IASAnalysisService {
/**
* 查询项目或标段的组成文件的解析状态
*
* @param message
* @return List<ProComposition>
* @author cwchen
* @date 2025/11/30 15:02
*/
List<ProComposition> getProCompositionState(RabbitMqMessage message);
/**
* 查询项目标签是否已经保存
*
* @param message
* @return int
* @author cwchen
* @date 2025/12/6 11:06
*/
int isLabelExist(RabbitMqMessage message);
/**
* 从解析结果中读取标签项配置
*
* @param message
* @return List<AnalysisLabelItemOcrVo>
* @author cwchen
* @date 2025/12/6 11:11
*/
List<AnalysisLabelItemOcrVo> getAnalysisLabelsByProOrBid(RabbitMqMessage message);
/**
* 保存解析标签数据
* @param message
* @param labelItemVoList
* @return void
* @author cwchen
* @date 2025/12/6 12:26
*/
void saveProBidAnalysisData(RabbitMqMessage message, List<AnalysisLabelItemOcrVo> labelItemVoList);
/**
* 更新解析标签数据
* @param message
* @param analysisDataList
* @return void
* @author cwchen
* @date 2025/12/6 13:16
*/
void updateProBidAnalysisData(RabbitMqMessage message, List<TwoAnalysisResponse> analysisDataList);
/**
* 查询工程和标段的解析数据
* @param dto
* @param type
* @return List<ProBidAnalysisResultVo>
* @author cwchen
* @date 2025/12/6 13:46
*/
List<ProBidAnalysisResultVo> getProBidAnalysisResult(AnalysisDto dto, int type);
}

View File

@ -9,6 +9,8 @@ import com.bonus.common.domain.analysis.po.ProComposition;
import com.bonus.common.domain.analysis.vo.AnalysisBidVo;
import com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo;
import com.bonus.common.domain.analysis.vo.AnalysisVo;
import com.bonus.common.domain.analysis.vo.ProBidAnalysisResultVo;
import com.bonus.common.domain.ocr.vo.TwoAnalysisResponse;
import com.bonus.common.domain.rabbitmq.dto.RabbitMqMessage;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@ -82,9 +84,9 @@ public class ASAnalysisServiceImpl implements IASAnalysisService {
}
@Override
public List<AnalysisLabelItemOcrVo> getAnalysisLabels(Long analysisLabelId, Long templateId) {
public List<AnalysisLabelItemOcrVo> getAnalysisLabels(RabbitMqMessage message) {
try {
return Optional.ofNullable(analysisMapper.getAnalysisLabels(analysisLabelId,templateId)).orElse(new ArrayList());
return Optional.ofNullable(analysisMapper.getAnalysisLabels(message)).orElse(new ArrayList());
} catch (Exception e) {
return new ArrayList<>();
}
@ -135,4 +137,29 @@ public class ASAnalysisServiceImpl implements IASAnalysisService {
public List<ProComposition> getProCompositionState(RabbitMqMessage message) {
return analysisMapper.getProCompositionState(message);
}
@Override
public int isLabelExist(RabbitMqMessage message) {
return analysisMapper.isLabelExist(message);
}
@Override
public List<AnalysisLabelItemOcrVo> getAnalysisLabelsByProOrBid(RabbitMqMessage message) {
return analysisMapper.getAnalysisLabelsByProOrBid(message);
}
@Override
public void saveProBidAnalysisData(RabbitMqMessage message, List<AnalysisLabelItemOcrVo> labelItemVoList) {
analysisMapper.saveProBidAnalysisData(message,labelItemVoList);
}
@Override
public void updateProBidAnalysisData(RabbitMqMessage message, List<TwoAnalysisResponse> analysisDataList) {
analysisMapper.updateProBidAnalysisData(message,analysisDataList);
}
@Override
public List<ProBidAnalysisResultVo> getProBidAnalysisResult(AnalysisDto dto, int type) {
return analysisMapper.getProBidAnalysisResult(dto,type);
}
}

View File

@ -112,6 +112,34 @@
)
</foreach>
</insert>
<!--保存解析标签数据-->
<insert id="saveProBidAnalysisData">
INSERT INTO tb_pro_bid_analysis_result
(id,pro_id,analysis_label_item_id,analysis_name,
analysis_code,parent_id,analysis_level,analysis_sort,
extract_content,analysis_type
)
VALUES
<foreach collection="list" item="item" separator=",">
(
<if test="params.compositionType == 1">
#{params.proId},
</if>
<if test="params.compositionType == 2">
#{params.bidId},
</if>
#{params.proId},
#{item.id},
#{item.searchKeyword},
#{item.targetField},
#{item.parentId},
#{item.analysisLevel},
#{item.analysisSort},
#{item.description},
#{params.compositionType}
)
</foreach>
</insert>
<!--查询项目文件组成详情-->
<select id="getProComposition" resultType="com.bonus.common.domain.analysis.po.ProComposition">
@ -151,7 +179,56 @@
AND pro_id = #{bidId}
</if>
</select>
<!--查询标签是否已经保存-->
<select id="isLabelExist" resultType="java.lang.Integer">
SELECT COUNT(*) FROM tb_pro_bid_analysis_result
<where>
<if test="compositionType == 1">
id = #{proId} AND analysis_type = '1'
</if>
<if test="compositionType == 2">
AND id = #{bidId} AND analysis_type = '2'
</if>
</where>
</select>
<!--从解析内容中读取标签配置-->
<select id="getAnalysisLabelsByProOrBid" resultType="com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo">
SELECT tpbar.analysis_label_item_id AS id,
tpbar.analysis_name AS searchKeyword,
tpbar.analysis_code AS analysisCode,
tpbar.parent_id AS parentId,
tpbar.analysis_level AS analysisLevel,
tpbar.analysis_sort AS analysisSort,
tpbar.extract_content AS description
FROM tb_pro_bid_analysis_result tpbar
<where>
<if test="compositionType == 1">
tpbar.id = #{proId} AND tpbar.analysis_type = '1'
</if>
<if test="compositionType == 2">
AND tpbar.id = #{bidId} AND tpbar.analysis_type = '2'
</if>
</where>
</select>
<!--查询工程和标段的解析数据-->
<select id="getProBidAnalysisResult" resultType="com.bonus.common.domain.analysis.vo.ProBidAnalysisResultVo">
SELECT analysis_label_item_id AS analysisLabelItemId,
analysis_name AS analysisName,
analysis_code AS analysisCode,
IFNULL(parent_id,'0') AS parentId,
analysis_level AS analysisLevel,
analysis_sort AS analysisSort,
analysis_content AS analysisContent
FROM tb_pro_bid_analysis_result
<where>
<if test="type == 1">
AND id = #{params.proId}
</if>
<if test="type == 2">
AND id = #{params.bidId}
</if>
</where>
</select>
<!--删除项目数据-->
@ -175,12 +252,7 @@
</update>
<!--更新项目或者标段的文件组成的解析状态-->
<update id="updateProCompositionState">
<if test="compositionType == 1">
UPDATE tb_pro_composition SET analysis_state = #{analysisState} WHERE pro_id = #{proId} AND composition_type = '1'
</if>
<if test="compositionType == 2">
UPDATE tb_pro_composition SET analysis_state = #{analysisState} WHERE pro_id = #{bidId} AND composition_type = '2'
</if>
UPDATE tb_pro_composition SET analysis_state = #{analysisState} WHERE id = #{proCompositionId}
</update>
<!--更新项目或者标段的解析状态-->
<update id="updateProOrBidState">
@ -191,4 +263,16 @@
UPDATE tb_pro_bid SET parsing_state = #{analysisState} WHERE bid_id = #{bidId}
</if>
</update>
<!--更新解析标签数据-->
<update id="updateProBidAnalysisData">
<foreach collection="list" item="item" separator=";">
UPDATE tb_pro_bid_analysis_result SET analysis_content = #{item.jsonStr} WHERE analysis_label_item_id = #{item.id}
<if test="params.compositionType == 1">
AND id = #{params.proId}
</if>
<if test="params.compositionType == 2">
AND id = #{params.bidId}
</if>
</foreach>
</update>
</mapper>

View File

@ -0,0 +1,64 @@
package com.bonus.common.domain.analysis.vo;
import lombok.Data;
import java.util.List;
/**
* @className:ProBidAnalysisResultVo
* @author:cwchen
* @date:2025-12-06-13:43
* @version:1.0
* @description:
*/
@Data
public class ProBidAnalysisResultVo {
/**
* 标段id / 项目id
*/
private Long id;
/**
* 项目id
*/
private Long proId;
/**
* 配置id
*/
private String analysisLabelItemId;
/**
* 解析名称
*/
private String analysisName;
/**
* 解析编码
*/
private String analysisCode;
/**
* 父节点
*/
private String parentId;
/**
* 层级
*/
private Integer analysisLevel;
/**
* 排序
*/
private Integer analysisSort;
/**
* 解析内容
*/
private String analysisContent;
private List<ProBidAnalysisResultVo> children;
}

View File

@ -0,0 +1,22 @@
package com.bonus.common.domain.ocr.vo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Map;
import java.util.Objects;
/**
* @className:OcrResponse
* @author:cwchen
* @date:2025-10-16-9:53
* @version:1.0
* @description: 招标解析响应结果
*/
@Data
public class TwoAnalysisResponse {
private Long id;
private String jsonStr;
}

View File

@ -16,7 +16,7 @@ public class RabbitMqMessage implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 业务id
* 项目id
* */
private Long proId;
@ -25,6 +25,11 @@ public class RabbitMqMessage implements Serializable {
* */
private Long bidId;
/**
* 文件组成id
* */
private Long proCompositionId;
/**
* 标签组id
* */
@ -70,4 +75,6 @@ public class RabbitMqMessage implements Serializable {
* */
private String analysisState;
}

View File

@ -153,10 +153,6 @@ public class AnalysisOcrService {
// 将请求对象转换为JSON字符串
String jsonRequest = convertToJson(analysisOcrRequest);
// 打印请求参数
log.info("OCR请求URL: {}", extractInfoUrl);
log.info("OCR请求参数: {}", jsonRequest);
// 设置请求体为JSON
StringEntity entity = new StringEntity(jsonRequest, ContentType.APPLICATION_JSON);
httpPost.setEntity(entity);

View File

@ -5,6 +5,7 @@ import com.bonus.common.domain.analysis.po.ProComposition;
import com.bonus.common.domain.analysis.vo.AnalysisLabelItemOcrVo;
import com.bonus.common.domain.ocr.dto.AnalysisOcrRequest;
import com.bonus.common.domain.ocr.vo.AnalysisResponse;
import com.bonus.common.domain.ocr.vo.TwoAnalysisResponse;
import com.bonus.common.domain.rabbitmq.dto.RabbitMqMessage;
import com.bonus.common.utils.FileUtil;
import com.bonus.file.config.MinioConfig;
@ -17,14 +18,16 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.sql.Array;
import java.util.*;
import java.util.stream.Collectors;
/**
* @className:RabbitMQConsumerService
@ -52,7 +55,6 @@ public class RabbitMQConsumerService {
private IASAnalysisService analysisService;
@RabbitListener(
queues = "myQueue",
containerFactory = "multiConsumerFactory" // 使用上面配置的工厂保证按顺序消费
@ -102,41 +104,188 @@ public class RabbitMQConsumerService {
/**
* 处理招标解析算法服务业务
*
* @param message
* @return void
* @author cwchen
* @date 2025/11/29 13:25
*/
private void processBusiness(RabbitMqMessage message) {
// 更新项目或者标段的解析状态
message.setAnalysisState("0");
analysisService.updateProOrBidState(message);
// 更新项目或者标段的文件组成的解析状态
message.setAnalysisState("0");
analysisService.updateProCompositionState(message);
// 查询项目或标段的组成文件的解析状态
List<ProComposition> proCompositionList = analysisService.getProCompositionState(message);
// 招标解析执行一次处理
String uploadPath = message.getUploadPath();
File fileFromMinio = getFileFromMinio(uploadPath);
AnalysisResponse ocrResponse = performAnalysisRecognition(fileFromMinio);
String folderPath = Optional.ofNullable(ocrResponse)
.map(AnalysisResponse::getData)
.map(data -> data.get("folder_path"))
.filter(Objects::nonNull)
.map(Object::toString)
.orElse(null);
if(StringUtils.isNotBlank(folderPath)) {
// 招标解析执行二次处理
performAnalysisRecognition2(message, folderPath);
}else{
// 解析失败
public void processBusiness(RabbitMqMessage message) {
try {
// 开始解析 更新项目或者标段的解析状态 && 项目或者标段的文件组成的解析状态
message.setAnalysisState("0");
analysisService.updateProOrBidState(message);
analysisService.updateProCompositionState(message);
// 招标解析执行一次处理
String uploadPath = message.getUploadPath();
File fileFromMinio = getFileFromMinio(uploadPath);
AnalysisResponse ocrResponse = performAnalysisRecognition(fileFromMinio);
String folderPath = Optional.ofNullable(ocrResponse)
.map(AnalysisResponse::getData)
.map(data -> data.get("folder_path"))
.filter(Objects::nonNull)
.map(Object::toString)
.orElse(null);
if (StringUtils.isNotBlank(folderPath)) {
// 招标解析执行二次处理
performAnalysisRecognition2(message, folderPath);
// 更新文件组成的解析状态
message.setAnalysisState("1");
analysisService.updateProCompositionState(message);
// 查询项目或标段的组成文件的解析状态
List<ProComposition> proCompositionList = analysisService.getProCompositionState(message);
Integer analysisState = determineAnalysisState(proCompositionList);
if (analysisState != null) {
message.setAnalysisState(analysisState.toString());
analysisService.updateProOrBidState(message);
}
} else {
// 第一次解析失败
message.setAnalysisState("2");
analysisService.updateProOrBidState(message);
analysisService.updateProCompositionState(message);
}
} catch (Exception e) {
message.setAnalysisState("2");
analysisService.updateProOrBidState(message);
analysisService.updateProCompositionState(message);
throw new RuntimeException(e);
}
}
/**
* 解析一次处理
*
* @param file
* @return AnalysisResponse
* @author cwchen
* @date 2025/11/29 13:30
*/
private AnalysisResponse performAnalysisRecognition(File file) {
try {
AnalysisOcrRequest analysisOcrRequest = buildOcrRequest(file);
AnalysisResponse ocrResponse = analysisOcrService.callOcrService(analysisOcrRequest);
// 修复检查 招标解析算法服务 响应是否为 null
if (Objects.isNull(ocrResponse)) {
throw new RuntimeException("招标解析算法服务返回结果为空");
}
log.info("OCR识别成功 - 数据: {}", ocrResponse.getData());
return ocrResponse;
} catch (Exception e) {
log.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage(), e);
}
}
/**
* 解析二次处理
*
* @param message
* @param folderPath
* @return void
* @author cwchen
* @date 2025/12/6 13:21
*/
public boolean performAnalysisRecognition2(RabbitMqMessage message, String folderPath) {
try {
// 查询项目/标段解析标签是否已经保存未保存则从配置中读取 已保存则从项目/标段解析结果中读取
int result = analysisService.isLabelExist(message);
List<AnalysisLabelItemOcrVo> labelItemVoList = null;
if (result == 0) {
// 保存解析标签数据
labelItemVoList = analysisService.getAnalysisLabels(message);
analysisService.saveProBidAnalysisData(message, labelItemVoList);
} else {
labelItemVoList = analysisService.getAnalysisLabelsByProOrBid(message);
}
AnalysisOcrRequest analysisOcrRequest = buildOcrRequest2(folderPath, labelItemVoList);
AnalysisResponse ocrResponse2 = analysisOcrService.callOcrService(analysisOcrRequest);
// 修复检查 招标解析算法服务 响应是否为 null
if (Objects.isNull(ocrResponse2)) {
throw new RuntimeException("招标解析算法服务返回结果为空");
}
log.info("OCR识别成功 - 数据: {}", ocrResponse2.getData());
Map<String, Object> data = ocrResponse2.getData();
List<TwoAnalysisResponse> analysisDataList = new ArrayList<>();
// 更新解析标签数据
analysisService.updateProBidAnalysisData(message, analysisDataList);
return true;
} catch (IOException e) {
log.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage(), e);
}
}
/**
* 构建招标解析算法服务请求 - 一次处理
*
* @param file
* @return OcrRequest
* @author cwchen
* @date 2025/11/29 13:29
*/
private AnalysisOcrRequest buildOcrRequest(File file) {
AnalysisOcrRequest ocrRequest = new AnalysisOcrRequest();
ocrRequest.setFile(file);
ocrRequest.setType(FileUtil.getMimeTypeByFilename(file.getName()));
ocrRequest.setAnalysisType("1");
return ocrRequest;
}
/**
* 构建招标解析算法服务请求 - 二次处理
*
* @return OcrRequest
* @author cwchen
* @date 2025/11/29 13:29
*/
private AnalysisOcrRequest buildOcrRequest2(String filePath, List<AnalysisLabelItemOcrVo> list) {
AnalysisOcrRequest ocrRequest = new AnalysisOcrRequest();
ocrRequest.setExtraction_items(list);
ocrRequest.setDoc_folder_path(filePath);
ocrRequest.setAnalysisType("2");
return ocrRequest;
}
/**
* 处理数据
*
* @param proCompositionList
* @return Integer
* @author cwchen
* @date 2025/12/6 12:59
*/
public Integer determineAnalysisState(List<ProComposition> proCompositionList) {
// 1. 判空处理
if (proCompositionList == null || proCompositionList.isEmpty()) {
return null;
}
// 2. 提取所有的 analysisState 并过滤掉 null
List<String> states = proCompositionList.stream()
.map(ProComposition::getAnalysisState)
.filter(Objects::nonNull) // 需要 import java.util.Objects;
.collect(Collectors.toList());
// 3. 根据规则判断
if (states.isEmpty()) {
return null; // 规则: 都是 null (或列表为空)
}
if (states.contains("2")) {
return 2; // 规则: 存在一个 2
}
if (states.contains("0")) {
return 0; // 规则: 存在至少一个 0 (且不存在 2)
}
return 1; // 规则: 剩下的情况也就是都是 1
}
/**
* 从minio中获取文件
*
* @param uploadPath
* @return File
* @author cwchen
@ -153,81 +302,4 @@ public class RabbitMQConsumerService {
throw new RuntimeException("获取Minio文件失败: " + uploadPath, e);
}
}
/**
* 调用算法服务
* @param file
* @return AnalysisResponse
* @author cwchen
* @date 2025/11/29 13:30
*/
private AnalysisResponse performAnalysisRecognition(File file) {
try {
AnalysisOcrRequest analysisOcrRequest = buildOcrRequest(file);
AnalysisResponse ocrResponse = analysisOcrService.callOcrService(analysisOcrRequest);
// 修复检查 招标解析算法服务 响应是否为 null
if (Objects.isNull(ocrResponse)) {
throw new RuntimeException("招标解析算法服务返回结果为空");
}
log.info("OCR识别成功 - 数据: {}", ocrResponse.getData());
return ocrResponse;
} catch (Exception e) {
log.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage(), e);
}
}
public void performAnalysisRecognition2(RabbitMqMessage message,String folderPath){
try {
if(message.getBidId() == null){
// 1.项目解析
}else {
// 2.标段解析
}
List<AnalysisLabelItemOcrVo> labelItemVoList = analysisService.getAnalysisLabels(message.getAnalysisLabelId(),message.getTemplateId());
AnalysisOcrRequest analysisOcrRequest = buildOcrRequest2(folderPath, labelItemVoList);
AnalysisResponse ocrResponse2 = analysisOcrService.callOcrService(analysisOcrRequest);
// 修复检查 招标解析算法服务 响应是否为 null
if (Objects.isNull(ocrResponse2)) {
throw new RuntimeException("招标解析算法服务返回结果为空");
}
log.info("OCR识别成功 - 数据: {}", ocrResponse2.getData());
} catch (IOException e) {
log.error("OCR识别失败", e);
throw new RuntimeException("OCR识别失败: " + e.getMessage(), e);
}
}
/**
* 构建招标解析算法服务请求 - 一次处理
* @param file
* @return OcrRequest
* @author cwchen
* @date 2025/11/29 13:29
*/
private AnalysisOcrRequest buildOcrRequest(File file) {
AnalysisOcrRequest ocrRequest = new AnalysisOcrRequest();
ocrRequest.setFile(file);
ocrRequest.setType(FileUtil.getMimeTypeByFilename(file.getName()));
ocrRequest.setAnalysisType("1");
return ocrRequest;
}
/**
* 构建招标解析算法服务请求 - 二次处理
* @return OcrRequest
* @author cwchen
* @date 2025/11/29 13:29
*/
private AnalysisOcrRequest buildOcrRequest2(String filePath,List<AnalysisLabelItemOcrVo> list) {
AnalysisOcrRequest ocrRequest = new AnalysisOcrRequest();
ocrRequest.setExtraction_items(list);
ocrRequest.setDoc_folder_path(filePath);
ocrRequest.setAnalysisType("2");
return ocrRequest;
}
}