From f41c2c30dde42d377522f057777a9b4cb2e5780f Mon Sep 17 00:00:00 2001 From: cwchen <1048842385@qq.com> Date: Wed, 26 Nov 2025 11:24:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=8B=9B=E6=A0=87=E8=A7=A3=E6=9E=90=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../analysis/AnalysisController.java | 43 +++ .../web/service/analysis/AnalysisService.java | 94 +++++++ .../analysis/mapper/IASAnalysisMapper.java | 39 +++ .../analysis/service/IASAnalysisService.java | 39 +++ .../service/impl/ASAnalysisServiceImpl.java | 28 ++ .../main/resources/mapper/AnalysisMapper.xml | 48 ++++ .../common/core/domain/entity/SysUser.java | 2 +- .../domain/analysis/dto/AnalysisBidDto.java | 150 ++++++++++ .../domain/analysis/dto/AnalysisProDto.java | 122 +++++++++ .../domain/analysis/vo/AnalysisBidVo.java | 115 ++++++++ .../common/domain/analysis/vo/AnalysisVo.java | 13 +- .../domain/ocr/vo/WordConvertPdfResponse.java | 25 ++ .../common/utils/Base64ToPdfConverter.java | 179 ++++++++++++ .../com/bonus/common/utils/SecurityUtils.java | 2 +- .../framework/config/ResourcesConfig.java | 8 +- .../ocr/service/WordConvertPdfService.java | 259 ++++++++++++++++++ 16 files changed, 1159 insertions(+), 7 deletions(-) create mode 100644 bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisBidDto.java create mode 100644 bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisProDto.java create mode 100644 bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisBidVo.java create mode 100644 bonus-common/src/main/java/com/bonus/common/domain/ocr/vo/WordConvertPdfResponse.java create mode 100644 bonus-common/src/main/java/com/bonus/common/utils/Base64ToPdfConverter.java create mode 100644 bonus-ocr/src/main/java/com/bonus/ocr/service/WordConvertPdfService.java diff --git a/bonus-admin/src/main/java/com/bonus/web/controller/analysis/AnalysisController.java b/bonus-admin/src/main/java/com/bonus/web/controller/analysis/AnalysisController.java index c5ddd4e..836bce8 100644 --- a/bonus-admin/src/main/java/com/bonus/web/controller/analysis/AnalysisController.java +++ b/bonus-admin/src/main/java/com/bonus/web/controller/analysis/AnalysisController.java @@ -6,7 +6,10 @@ import com.bonus.common.core.controller.BaseController; //import com.bonus.web.service.analysis.AnalysisService; import com.bonus.common.core.domain.AjaxResult; import com.bonus.common.core.page.TableDataInfo; +import com.bonus.common.domain.analysis.dto.AnalysisBidDto; import com.bonus.common.domain.analysis.dto.AnalysisDto; +import com.bonus.common.domain.analysis.dto.AnalysisProDto; +import com.bonus.common.domain.analysis.vo.AnalysisBidVo; import com.bonus.common.domain.analysis.vo.AnalysisVo; import com.bonus.common.enums.OperaType; import com.bonus.web.service.analysis.AnalysisService; @@ -53,4 +56,44 @@ public class AnalysisController extends BaseController { public AjaxResult saveData(@RequestBody AnalysisDto.TemplateDto dto) { return analysisService.saveData(dto); } + + @ApiOperation(value = "招标解析", notes = "查看标段详情") + @GetMapping("getBidList") + @SysLog(title = "招标解析", module = "招标解析->查看标段详情", businessType = OperaType.QUERY, details = "查看标段详情", logType = 1) + @RequiresPermissions("analysis:analysis:query") + public TableDataInfo getBidList(AnalysisDto.TemplateDto dto) { + startPage(); + List list = analysisService.getBidList(dto); + return getDataTable(list); + } + + @ApiOperation(value = "招标解析", notes = "查看项目详情") + @GetMapping("getProDetail") + @SysLog(title = "招标解析", module = "招标解析->查看项目详情", businessType = OperaType.QUERY, details = "查看项目详情", logType = 1) + @RequiresPermissions("analysis:analysis:query") + public AjaxResult getProDetail(AnalysisDto.TemplateDto dto) { + return analysisService.getProDetail(dto); + } + + @ApiOperation(value = "招标解析", notes = "更新项目数据") + @PostMapping("editProData") + @SysLog(title = "招标解析", module = "招标解析->更新项目数据", businessType = OperaType.UPDATE, details = "更新项目数据", logType = 1) + @RequiresPermissions("analysis:analysis:edit") + public AjaxResult editProData(@RequestBody AnalysisProDto dto) { + return analysisService.editProData(dto); + } + + @ApiOperation(value = "招标解析", notes = "更新标段数据") + @PostMapping("editBidData") + @SysLog(title = "招标解析", module = "招标解析->更新标段数据", businessType = OperaType.UPDATE, details = "更新标段数据", logType = 1) + @RequiresPermissions("analysis:analysis:edit") + public AjaxResult editBidData(@RequestBody AnalysisBidDto dto) { + return analysisService.editBidData(dto); + } + + @ApiOperation(value = "测试mq异步消息", notes = "测试mq异步消息") + @GetMapping("/testAsyncMq2") + public AjaxResult testAsyncMq2() { + return analysisService.testAsyncMq2(); + } } diff --git a/bonus-admin/src/main/java/com/bonus/web/service/analysis/AnalysisService.java b/bonus-admin/src/main/java/com/bonus/web/service/analysis/AnalysisService.java index 39a15dd..f56f971 100644 --- a/bonus-admin/src/main/java/com/bonus/web/service/analysis/AnalysisService.java +++ b/bonus-admin/src/main/java/com/bonus/web/service/analysis/AnalysisService.java @@ -3,12 +3,21 @@ package com.bonus.web.service.analysis; import com.bonus.analysis.service.IASAnalysisService; import com.bonus.common.core.domain.AjaxResult; +import com.bonus.common.domain.analysis.dto.AnalysisBidDto; import com.bonus.common.domain.analysis.dto.AnalysisDto; +import com.bonus.common.domain.analysis.dto.AnalysisProDto; +import com.bonus.common.domain.analysis.vo.AnalysisBidVo; import com.bonus.common.domain.analysis.vo.AnalysisVo; import com.bonus.common.domain.mainDatabase.dto.EnterpriseDto; +import com.bonus.common.domain.ocr.dto.OcrRequest; +import com.bonus.common.domain.ocr.vo.WordConvertPdfResponse; import com.bonus.common.domain.rabbitmq.dto.RabbitMqMessage; +import com.bonus.common.utils.Base64ToPdfConverter; +import com.bonus.common.utils.FileUtil; import com.bonus.common.utils.ValidatorsUtils; import com.bonus.common.utils.uuid.UUID; +import com.bonus.ocr.service.OcrService; +import com.bonus.ocr.service.WordConvertPdfService; import com.bonus.web.rabbitmq.producer.RabbitMQProducerService; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; @@ -17,6 +26,8 @@ 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.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Executor; @@ -49,6 +60,9 @@ public class AnalysisService { @Resource(name = "RabbitMQProducerService") private RabbitMQProducerService rabbitMQProducerService; + @Resource(name = "WordConvertPdfService") + private WordConvertPdfService wordConvertPdfService; + public AjaxResult testAsyncMq() { /*taskExecutor.submit(() -> { @@ -123,5 +137,85 @@ public class AnalysisService { return AjaxResult.error(); } } + + /** + * 招标解析->查看详情 + * @param dto + * @return AjaxResult + * @author cwchen + * @date 2025/11/25 16:08 + */ + public List getBidList(AnalysisDto.TemplateDto dto) { + return analysisService.getBidList(dto); + } + + public AjaxResult testAsyncMq2() { + // 创建OCR识别请求参数 + OcrRequest ocrRequest = new OcrRequest(); + ocrRequest.setFile(new File("C:\\Users\\10488\\Desktop\\OCR环境配置.docx")); + try { + WordConvertPdfResponse wordConvertPdfResponse = wordConvertPdfService.convertWordToPdf(ocrRequest); + String pdfBase64 = wordConvertPdfResponse.getPdfBase64(); + Base64ToPdfConverter.convertToFile(pdfBase64,"C:\\Users\\10488\\Desktop\\test12121212.pdf"); + } catch (IOException e) { + log.error(e.toString(),e); + } + return null; + } + + /** + * 招标解析->查看项目详情 + * @param dto + * @return AjaxResult + * @author cwchen + * @date 2025/11/26 9:14 + */ + public AjaxResult getProDetail(AnalysisDto.TemplateDto dto) { + AnalysisVo analysisVo = analysisService.getProDetail(dto); + return AjaxResult.success(analysisVo); + } + + /** + * 招标解析->更新项目数据 + * @param dto + * @return AjaxResult + * @author cwchen + * @date 2025/11/26 9:25 + */ + @Transactional(rollbackFor = Exception.class) + public AjaxResult editProData(AnalysisProDto dto) { + try { + // 校验数据是否合法 + String validResult = validatorsUtils.valid(dto, AnalysisProDto.UPDATE.class); + if (StringUtils.isNotBlank(validResult)) { + return AjaxResult.error(validResult); + } + // 更新项目数据 + analysisService.editProData(dto); + return AjaxResult.success(); + } catch (Exception e) { + log.error(e.toString(),e); + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + return AjaxResult.error(); + } + } + + @Transactional(rollbackFor = Exception.class) + public AjaxResult editBidData(AnalysisBidDto dto) { + try { + // 校验数据是否合法 + String validResult = validatorsUtils.valid(dto, AnalysisBidDto.UPDATE.class); + if (StringUtils.isNotBlank(validResult)) { + return AjaxResult.error(validResult); + } + // 更新标段数据 + analysisService.editBidData(dto); + return AjaxResult.success(); + } catch (Exception e) { + log.error(e.toString(),e); + TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); + return AjaxResult.error(); + } + } } diff --git a/bonus-analysis/src/main/java/com/bonus/analysis/mapper/IASAnalysisMapper.java b/bonus-analysis/src/main/java/com/bonus/analysis/mapper/IASAnalysisMapper.java index 1dd18e8..4e24561 100644 --- a/bonus-analysis/src/main/java/com/bonus/analysis/mapper/IASAnalysisMapper.java +++ b/bonus-analysis/src/main/java/com/bonus/analysis/mapper/IASAnalysisMapper.java @@ -1,6 +1,9 @@ package com.bonus.analysis.mapper; +import com.bonus.common.domain.analysis.dto.AnalysisBidDto; import com.bonus.common.domain.analysis.dto.AnalysisDto; +import com.bonus.common.domain.analysis.dto.AnalysisProDto; +import com.bonus.common.domain.analysis.vo.AnalysisBidVo; import com.bonus.common.domain.analysis.vo.AnalysisVo; import org.springframework.stereotype.Repository; @@ -32,4 +35,40 @@ public interface IASAnalysisMapper { * @date 2025/11/24 14:05 */ void addProData(AnalysisDto.TemplateDto dto); + + /** + * 招标解析->查看标段详情 + * @param dto + * @return List + * @author cwchen + * @date 2025/11/25 16:27 + */ + List getBidList(AnalysisDto.TemplateDto dto); + + /** + * 招标解析->查看项目详情 + * @param dto + * @return AnalysisVo + * @author cwchen + * @date 2025/11/26 9:16 + */ + AnalysisVo getProDetail(AnalysisDto.TemplateDto dto); + + /** + * 更新项目数据 + * @param dto + * @return void + * @author cwchen + * @date 2025/11/26 9:27 + */ + void editProData(AnalysisProDto dto); + + /** + * 更新标段数据 + * @param dto + * @return void + * @author cwchen + * @date 2025/11/26 9:39 + */ + void editBidData(AnalysisBidDto dto); } diff --git a/bonus-analysis/src/main/java/com/bonus/analysis/service/IASAnalysisService.java b/bonus-analysis/src/main/java/com/bonus/analysis/service/IASAnalysisService.java index 5b773a7..992604d 100644 --- a/bonus-analysis/src/main/java/com/bonus/analysis/service/IASAnalysisService.java +++ b/bonus-analysis/src/main/java/com/bonus/analysis/service/IASAnalysisService.java @@ -1,6 +1,9 @@ package com.bonus.analysis.service; +import com.bonus.common.domain.analysis.dto.AnalysisBidDto; import com.bonus.common.domain.analysis.dto.AnalysisDto; +import com.bonus.common.domain.analysis.dto.AnalysisProDto; +import com.bonus.common.domain.analysis.vo.AnalysisBidVo; import com.bonus.common.domain.analysis.vo.AnalysisVo; import java.util.List; @@ -31,4 +34,40 @@ public interface IASAnalysisService { * @date 2025/11/24 14:04 */ void addProData(AnalysisDto.TemplateDto dto); + + /** + * 招标解析->查看详情 + * @param dto + * @return List + * @author cwchen + * @date 2025/11/25 16:27 + */ + List getBidList(AnalysisDto.TemplateDto dto); + + /** + * 招标解析->查看项目详情 + * @param dto + * @return AnalysisVo + * @author cwchen + * @date 2025/11/26 9:15 + */ + AnalysisVo getProDetail(AnalysisDto.TemplateDto dto); + + /** + * 更新项目数据 + * @param dto + * @return void + * @author cwchen + * @date 2025/11/26 9:27 + */ + void editProData(AnalysisProDto dto); + + /** + * 更新标段数据 + * @param dto + * @return void + * @author cwchen + * @date 2025/11/26 9:38 + */ + void editBidData(AnalysisBidDto dto); } diff --git a/bonus-analysis/src/main/java/com/bonus/analysis/service/impl/ASAnalysisServiceImpl.java b/bonus-analysis/src/main/java/com/bonus/analysis/service/impl/ASAnalysisServiceImpl.java index 073c137..73fdadf 100644 --- a/bonus-analysis/src/main/java/com/bonus/analysis/service/impl/ASAnalysisServiceImpl.java +++ b/bonus-analysis/src/main/java/com/bonus/analysis/service/impl/ASAnalysisServiceImpl.java @@ -2,7 +2,10 @@ package com.bonus.analysis.service.impl; import com.bonus.analysis.mapper.IASAnalysisMapper; import com.bonus.analysis.service.IASAnalysisService; +import com.bonus.common.domain.analysis.dto.AnalysisBidDto; import com.bonus.common.domain.analysis.dto.AnalysisDto; +import com.bonus.common.domain.analysis.dto.AnalysisProDto; +import com.bonus.common.domain.analysis.vo.AnalysisBidVo; import com.bonus.common.domain.analysis.vo.AnalysisVo; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -10,6 +13,7 @@ import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * @className:ASAnalysisServiceImpl @@ -34,4 +38,28 @@ public class ASAnalysisServiceImpl implements IASAnalysisService { public void addProData(AnalysisDto.TemplateDto dto) { analysisMapper.addProData(dto); } + + @Override + public List getBidList(AnalysisDto.TemplateDto dto) { + return analysisMapper.getBidList(dto); + } + + @Override + public AnalysisVo getProDetail(AnalysisDto.TemplateDto dto) { + try { + return Optional.ofNullable(analysisMapper.getProDetail(dto)).orElse(new AnalysisVo()); + } catch (Exception e) { + return new AnalysisVo(); + } + } + + @Override + public void editProData(AnalysisProDto dto) { + analysisMapper.editProData(dto); + } + + @Override + public void editBidData(AnalysisBidDto dto) { + analysisMapper.editBidData(dto); + } } diff --git a/bonus-analysis/src/main/resources/mapper/AnalysisMapper.xml b/bonus-analysis/src/main/resources/mapper/AnalysisMapper.xml index 580376c..2368bf2 100644 --- a/bonus-analysis/src/main/resources/mapper/AnalysisMapper.xml +++ b/bonus-analysis/src/main/resources/mapper/AnalysisMapper.xml @@ -39,4 +39,52 @@ #{templateId},#{createUserId},#{createUserName},#{updateUserId},#{updateUserName},'0' ) + + + + + + + + + + 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_bid SET mark_name = #{markName},unit = #{unit},bid_number = #{bidNumber}, + bid_name = #{bidName},maximum_bid_limit = #{maximumBidLimit},safety_const_fee = #{safetyConstFee}, + bid_bond = #{bidBond},duration = #{duration},bidding_stage = #{biddingStage} WHERE bid_id = #{bidId} + diff --git a/bonus-common/src/main/java/com/bonus/common/core/domain/entity/SysUser.java b/bonus-common/src/main/java/com/bonus/common/core/domain/entity/SysUser.java index 598bb53..eb2c2a4 100644 --- a/bonus-common/src/main/java/com/bonus/common/core/domain/entity/SysUser.java +++ b/bonus-common/src/main/java/com/bonus/common/core/domain/entity/SysUser.java @@ -135,7 +135,7 @@ public class SysUser extends BaseEntity public static boolean isAdmin(Long userId) { - return userId != null && 1L == userId; + return userId != null && (1L == userId || 6L == userId); } public Long getDeptId() diff --git a/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisBidDto.java b/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisBidDto.java new file mode 100644 index 0000000..558caf8 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisBidDto.java @@ -0,0 +1,150 @@ +package com.bonus.common.domain.analysis.dto; + +import com.bonus.common.core.domain.model.LoginUser; +import com.bonus.common.utils.SecurityUtils; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; +import java.math.BigDecimal; +import java.util.Date; +import java.util.Optional; + +/** + * @className:AnalysisDto + * @author:cwchen + * @date:2025-11-24-9:40 + * @version:1.0 + * @description:招标解析-标段dto + */ +@Data +public class AnalysisBidDto { + + /** + * 标段id + */ + @NotNull(message = "标段id不能为空", groups = {UPDATE.class}) + private Long bidId; + + /** + * 标的名称 + */ + @NotBlank(message = "标的名称不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "标的名称字符长度不能超过64", groups = {UPDATE.class}) + private String markName; + + /** + * 单位 + */ + @NotBlank(message = "单位名称不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "单位名称字符长度不能超过64", groups = {UPDATE.class}) + private String unit; + + /** + * 标段标号 + */ + @NotBlank(message = "标段标号不能为空", groups = {UPDATE.class}) + @Length(max = 32, message = "标段标号字符长度不能超过32", groups = {UPDATE.class}) + private String bidNumber; + + /** + * 标段名称 + */ + @NotBlank(message = "标段名称不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "标段名称字符长度不能超过64", groups = {UPDATE.class}) + private String bidName; + + /** + * 最高投标限价(万元) + */ + @NotNull(message = "最高投标限价不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "最高投标限价字符长度不能超过64", groups = {UPDATE.class}) + private String maximumBidLimit; + + /** + * 安全文明施工费(万元) + */ + @NotNull(message = "安全文明施工费不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "安全文明施工费字符长度不能超过64", groups = {UPDATE.class}) + private String safetyConstFee; + + /** + * 投标保证金(万元) + */ + @NotNull(message = "投标保证金不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "投标保证金字符长度不能超过64", groups = {UPDATE.class}) + private String bidBond; + + /** + * 工期 + */ + @NotBlank(message = "工期不能为空", groups = {UPDATE.class}) + @Length(max = 128, message = "工期字符长度不能超过128", groups = {UPDATE.class}) + private String duration; + + /** + * 招标阶段 + */ + @NotBlank(message = "招标阶段不能为空", groups = {UPDATE.class}) + @Length(max = 32, message = "招标阶段字符长度不能超过32", groups = {UPDATE.class}) + private String biddingStage; + + /** + * 解析状态 0.解析中 1.解析成功 2.解析失败 + */ + private String parsingState; + + /** + * 创建时间 + */ + private Date createTime; + + + + /** + * 修改时间 + */ + private Date updateTime; + + + /** + * 创建人 + */ + private Long createUserId = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUserId) + .orElse(null); + ; + + /** + * 创建人姓名 + */ + private String createUserName = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUsername) + .orElse(null); + ; + + + /** + * 修改人 + */ + private Long updateUserId = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUserId) + .orElse(null); + ; + + /** + * 修改人姓名 + */ + private String updateUserName = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUsername) + .orElse(null); + + + /** + * 修改条件限制 + */ + public interface UPDATE { + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisProDto.java b/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisProDto.java new file mode 100644 index 0000000..9b4168b --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/domain/analysis/dto/AnalysisProDto.java @@ -0,0 +1,122 @@ +package com.bonus.common.domain.analysis.dto; + +import com.bonus.common.core.domain.model.LoginUser; +import com.bonus.common.domain.file.po.ResourceFilePo; +import com.bonus.common.domain.mainDatabase.dto.EnterpriseDto; +import com.bonus.common.utils.SecurityUtils; +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.hibernate.validator.constraints.Length; +import org.springframework.format.annotation.DateTimeFormat; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import java.util.Date; +import java.util.List; +import java.util.Optional; + +/** + * @className:AnalysisDto + * @author:cwchen + * @date:2025-11-24-9:40 + * @version:1.0 + * @description:招标解析dto + */ +@Data +public class AnalysisProDto { + + /** + * 项目id + */ + @NotNull(message = "项目id不能为空", groups = {UPDATE.class}) + private Long proId; + + /** + * 项目名称 + */ + @NotBlank(message = "项目名称不能为空", groups = {UPDATE.class}) + @Length(max = 128, message = "项目名称字符长度不能超过128", groups = {UPDATE.class}) + private String proName; + + /** + * 项目简介 + */ + @NotBlank(message = "项目简介不能为空", groups = {UPDATE.class}) + private String proIntroduction; + + /** + * 项目编号 + */ + @NotBlank(message = "项目编号不能为空", groups = {UPDATE.class}) + @Length(max = 32, message = "项目编号字符长度不能超过32", groups = {UPDATE.class}) + private String proCode; + + /** + * 招标人 + */ + @NotBlank(message = "招标人不能为空", groups = {UPDATE.class}) + @Length(max = 32, message = "招标人字符长度不能超过32", groups = {UPDATE.class}) + private String tenderer; + + /** + * 代理机构 + */ + @NotBlank(message = "代理机构不能为空", groups = {UPDATE.class}) + @Length(max = 64, message = "代理机构字符长度不能超过64", groups = {UPDATE.class}) + private String agency; + + /** + * 开标时间 + */ + @NotNull(message = "开标时间不能为空", groups = {UPDATE.class}) + @DateTimeFormat(pattern = "yyyy-MM-dd") + @JsonFormat(pattern = "yyyy-MM-dd") + private Date bidOpeningTime; + + /** + * 开标方式 + */ + @NotBlank(message = "开标方式不能为空", groups = {UPDATE.class}) + @Length(max = 32, message = "开标方式字符长度不能超过32", groups = {UPDATE.class}) + private String bidOpeningMethod; + + /** + * 创建人 + */ + private Long createUserId = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUserId) + .orElse(null); + ; + + /** + * 创建人姓名 + */ + private String createUserName = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUsername) + .orElse(null); + ; + + + /** + * 修改人 + */ + private Long updateUserId = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUserId) + .orElse(null); + ; + + /** + * 修改人姓名 + */ + private String updateUserName = Optional.ofNullable(SecurityUtils.getLoginUser()) + .map(LoginUser::getUsername) + .orElse(null); + + + /** + * 修改条件限制 + */ + public interface UPDATE { + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisBidVo.java b/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisBidVo.java new file mode 100644 index 0000000..1dc5594 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisBidVo.java @@ -0,0 +1,115 @@ +package com.bonus.common.domain.analysis.vo; + +import com.fasterxml.jackson.annotation.JsonFormat; +import lombok.Data; +import org.springframework.format.annotation.DateTimeFormat; + +import java.math.BigDecimal; +import java.util.Date; + +/** + * @className:AnalysisVo + * @author:cwchen + * @date:2025-11-24-9:42 + * @version:1.0 + * @description:招标解析-标段vo + */ +@Data +public class AnalysisBidVo { + + /** + * 标段id + */ + private Long bidId; + + /** + * 项目id + */ + private Long proId; + + /** + * 标的名称 + */ + private String markName; + + /** + * 单位 + */ + private String unit; + + /** + * 标段标号 + */ + private String bidNumber; + + /** + * 标段名称 + */ + private String bidName; + + /** + * 最高投标限价(万元) + */ + private String maximumBidLimit; + + /** + * 安全文明施工费(万元) + */ + private String safetyConstFee; + + /** + * 投标保证金(万元) + */ + private String bidBond; + + /** + * 工期 + */ + private String duration; + + /** + * 招标阶段 + */ + private String biddingStage; + + /** + * 解析状态 0.解析中 1.解析成功 2.解析失败 + */ + private String parsingState; + + /** + * 创建时间 + */ + private Date createTime; + + /** + * 创建人 + */ + private Long createUserId; + + /** + * 创建人姓名 + */ + private String createUserName; + + /** + * 修改时间 + */ + private Date updateTime; + + /** + * 修改人 + */ + private Long updateUserId; + + /** + * 修改人姓名 + */ + private String updateUserName; + + /** + * 删除状态 0.未删除 1.删除 + */ + private String delFlag; + +} diff --git a/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisVo.java b/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisVo.java index ac9268b..3db496d 100644 --- a/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisVo.java +++ b/bonus-common/src/main/java/com/bonus/common/domain/analysis/vo/AnalysisVo.java @@ -5,6 +5,7 @@ import lombok.Data; import org.springframework.format.annotation.DateTimeFormat; import java.util.Date; +import java.util.List; /** * @className:AnalysisVo @@ -56,7 +57,7 @@ public class AnalysisVo { * 开标时间 */ @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8") - @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @DateTimeFormat(pattern = "yyyy-MM-dd") private Date bidOpeningTime; /** @@ -75,4 +76,14 @@ public class AnalysisVo { * 解析状态 0.解析中 1.解析成功 2.解析失败 */ private String analysisStatus; + + /** + * 项目简介 + */ + private String proIntroduction; + + /** + * 标段list + * */ + private List bidList; } diff --git a/bonus-common/src/main/java/com/bonus/common/domain/ocr/vo/WordConvertPdfResponse.java b/bonus-common/src/main/java/com/bonus/common/domain/ocr/vo/WordConvertPdfResponse.java new file mode 100644 index 0000000..f024369 --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/domain/ocr/vo/WordConvertPdfResponse.java @@ -0,0 +1,25 @@ +package com.bonus.common.domain.ocr.vo; + +import lombok.Data; + +/** + * @className:WordConvertPdfResponse + * @author:cwchen + * @date:2025-11-25-17:53 + * @version:1.0 + * @description: word转pdf响应结果 + */ +@Data +public class WordConvertPdfResponse { + + private String status; // "success" 或 "error" + private String filename; // 文件名 + private String pdfBase64; // PDF base64数据 + private String message; // 错误信息 + + // getter和setter方法 + // 辅助方法 + public boolean isSuccess() { + return "success".equals(status); + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/utils/Base64ToPdfConverter.java b/bonus-common/src/main/java/com/bonus/common/utils/Base64ToPdfConverter.java new file mode 100644 index 0000000..42d4e9d --- /dev/null +++ b/bonus-common/src/main/java/com/bonus/common/utils/Base64ToPdfConverter.java @@ -0,0 +1,179 @@ +package com.bonus.common.utils; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Base64; + +/** + * Base64转PDF工具类 + */ +public class Base64ToPdfConverter { + + /** + * 将Base64字符串转换为PDF文件 + * + * @param base64Content Base64编码的PDF内容 + * @param outputPath 输出PDF文件路径 + * @return 转换是否成功 + */ + public static boolean convertToFile(String base64Content, String outputPath) { + return convertToFile(base64Content, new File(outputPath)); + } + + /** + * 将Base64字符串转换为PDF文件 + * + * @param base64Content Base64编码的PDF内容 + * @param outputFile 输出PDF文件对象 + * @return 转换是否成功 + */ + public static boolean convertToFile(String base64Content, File outputFile) { + if (base64Content == null || base64Content.trim().isEmpty()) { + throw new IllegalArgumentException("Base64内容不能为空"); + } + + // 清理可能的Base64前缀(如:data:application/pdf;base64,) + String cleanBase64 = cleanBase64Content(base64Content); + + FileOutputStream fos = null; + try { + // 确保输出目录存在 + File parentDir = outputFile.getParentFile(); + if (parentDir != null && !parentDir.exists()) { + parentDir.mkdirs(); + } + + // 解码Base64 + byte[] pdfBytes = Base64.getDecoder().decode(cleanBase64); + + // 写入文件 + fos = new FileOutputStream(outputFile); + fos.write(pdfBytes); + fos.flush(); + + return true; + } catch (Exception e) { + System.err.println("Base64转PDF失败: " + e.getMessage()); + e.printStackTrace(); + return false; + } finally { + if (fos != null) { + try { + fos.close(); + } catch (IOException e) { + System.err.println("关闭文件流失败: " + e.getMessage()); + } + } + } + } + + /** + * 将Base64字符串转换为字节数组 + * + * @param base64Content Base64编码的PDF内容 + * @return PDF字节数组 + */ + public static byte[] convertToBytes(String base64Content) { + if (base64Content == null || base64Content.trim().isEmpty()) { + throw new IllegalArgumentException("Base64内容不能为空"); + } + + try { + String cleanBase64 = cleanBase64Content(base64Content); + return Base64.getDecoder().decode(cleanBase64); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException("Base64内容格式不正确", e); + } + } + + /** + * 验证Base64字符串是否为有效的PDF内容 + * + * @param base64Content Base64编码的内容 + * @return 是否为有效的PDF内容 + */ + public static boolean isValidPdfContent(String base64Content) { + if (base64Content == null || base64Content.trim().isEmpty()) { + return false; + } + + try { + String cleanBase64 = cleanBase64Content(base64Content); + byte[] pdfBytes = Base64.getDecoder().decode(cleanBase64); + + // 检查PDF文件头(PDF文件通常以 "%PDF-" 开头) + if (pdfBytes.length >= 5) { + return pdfBytes[0] == '%' && + pdfBytes[1] == 'P' && + pdfBytes[2] == 'D' && + pdfBytes[3] == 'F' && + pdfBytes[4] == '-'; + } + return false; + } catch (Exception e) { + return false; + } + } + + /** + * 清理Base64内容,移除可能的数据URI前缀 + * + * @param base64Content 原始Base64内容 + * @return 清理后的Base64内容 + */ + private static String cleanBase64Content(String base64Content) { + String content = base64Content.trim(); + + // 移除常见的数据URI前缀 + String[] prefixes = { + "data:application/pdf;base64,", + "data:application/octet-stream;base64,", + "data:application/x-pdf;base64,", + "data:text/plain;base64," + }; + + for (String prefix : prefixes) { + if (content.startsWith(prefix)) { + return content.substring(prefix.length()); + } + } + + return content; + } + + /** + * 批量转换多个Base64内容为PDF文件 + * + * @param base64Files 包含文件名和Base64内容的映射 + * @param outputDirectory 输出目录 + * @return 成功转换的文件数量 + */ + public static int batchConvert(java.util.Map base64Files, String outputDirectory) { + if (base64Files == null || base64Files.isEmpty()) { + return 0; + } + + File outputDir = new File(outputDirectory); + if (!outputDir.exists()) { + outputDir.mkdirs(); + } + + int successCount = 0; + for (java.util.Map.Entry entry : base64Files.entrySet()) { + String fileName = entry.getKey(); + if (!fileName.toLowerCase().endsWith(".pdf")) { + fileName += ".pdf"; + } + + File outputFile = new File(outputDir, fileName); + if (convertToFile(entry.getValue(), outputFile)) { + successCount++; + } + } + + return successCount; + } +} diff --git a/bonus-common/src/main/java/com/bonus/common/utils/SecurityUtils.java b/bonus-common/src/main/java/com/bonus/common/utils/SecurityUtils.java index 5434c7d..cf38e9e 100644 --- a/bonus-common/src/main/java/com/bonus/common/utils/SecurityUtils.java +++ b/bonus-common/src/main/java/com/bonus/common/utils/SecurityUtils.java @@ -122,7 +122,7 @@ public class SecurityUtils */ public static boolean isAdmin(Long userId) { - return userId != null && 1L == userId; + return userId != null && (1L == userId || 6L == userId); } /** diff --git a/bonus-framework/src/main/java/com/bonus/framework/config/ResourcesConfig.java b/bonus-framework/src/main/java/com/bonus/framework/config/ResourcesConfig.java index 74cfb18..d0db5c2 100644 --- a/bonus-framework/src/main/java/com/bonus/framework/config/ResourcesConfig.java +++ b/bonus-framework/src/main/java/com/bonus/framework/config/ResourcesConfig.java @@ -59,12 +59,12 @@ public class ResourcesConfig implements WebMvcConfigurer { registry.addInterceptor(repeatSubmitInterceptor).addPathPatterns("/**"); // 参数校验拦截器 - registry.addInterceptor(paramSecureHandler) + /*registry.addInterceptor(paramSecureHandler) .addPathPatterns("/**") .excludePathPatterns(EXCLUDEURLS) - .order(-10); + .order(-10);*/ // 防重放拦截器 - registry.addInterceptor(replayAttackInterceptor) + /*registry.addInterceptor(replayAttackInterceptor) .addPathPatterns("/**") .excludePathPatterns("/smartBid/captchaImage") .excludePathPatterns("/smartBid/login") @@ -74,7 +74,7 @@ public class ResourcesConfig implements WebMvcConfigurer .excludePathPatterns("/smartBid/session/check") .excludePathPatterns("/smartBid/sys/config/getConfig") .excludePathPatterns(EXCLUDEURLS) - .order(-15); + .order(-15);*/ } /** diff --git a/bonus-ocr/src/main/java/com/bonus/ocr/service/WordConvertPdfService.java b/bonus-ocr/src/main/java/com/bonus/ocr/service/WordConvertPdfService.java new file mode 100644 index 0000000..ee85291 --- /dev/null +++ b/bonus-ocr/src/main/java/com/bonus/ocr/service/WordConvertPdfService.java @@ -0,0 +1,259 @@ +package com.bonus.ocr.service; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import com.bonus.common.domain.ocr.dto.OcrRequest; +import com.bonus.common.domain.ocr.vo.WordConvertPdfResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.HttpEntity; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.util.EntityUtils; +import org.springframework.beans.factory.annotation.Value; +import org.apache.http.entity.mime.MultipartEntityBuilder; +import org.apache.http.entity.mime.HttpMultipartMode; +import org.apache.http.entity.mime.content.FileBody; +import org.apache.http.entity.ContentType; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +/** + * @className: WordConvertPdfService + * @author: cwchen + * @date: 2025-11-25-17:46 + * @version: 1.0 + * @description: word转pdf服务 + */ +@Service(value = "WordConvertPdfService") +@Slf4j +public class WordConvertPdfService { + + private static final String UTF_8 = "UTF-8"; + private static final String FILE_PART_NAME = "file"; + private static final String TYPE_PART_NAME = "type"; + + @Value("${ocr.service.convertUrl}") + private String ocrServiceUrl; + + @Value("${ocr.service.timeout:30000}") + private int timeout; + + private final CloseableHttpClient httpClient; + private final ObjectMapper objectMapper; + + public WordConvertPdfService() { + // 使用默认值30000毫秒作为fallback + int actualTimeout = timeout > 0 ? timeout : 30000; + + RequestConfig requestConfig = RequestConfig.custom() + .setConnectTimeout(actualTimeout) + .setSocketTimeout(actualTimeout) + .setConnectionRequestTimeout(actualTimeout) + .build(); + + this.httpClient = HttpClients.custom() + .setDefaultRequestConfig(requestConfig) + .build(); + this.objectMapper = new ObjectMapper(); + } + + /** + * 调用文件转换服务进行Word转PDF + * + * @param ocrRequest OCR请求参数 + * @return OCR响应结果,包含PDF base64数据 + * @throws IOException 当文件转换服务调用失败时抛出 + */ + public WordConvertPdfResponse convertWordToPdf(OcrRequest ocrRequest) throws IOException { + validateOcrRequest(ocrRequest); + + HttpPost httpPost = null; + try { + httpPost = createHttpPost(ocrRequest); + return executeOcrRequest(httpPost); + } catch (IOException e) { + log.error("调用文件转换服务进行Word转PDF失败", e); + throw new IOException("Word转PDF服务调用失败: " + e.getMessage(), e); + } finally { + cleanupResources(ocrRequest, httpPost); + } + } + + /** + * 验证OCR请求参数 + */ + private void validateOcrRequest(OcrRequest ocrRequest) { + if (ocrRequest == null) { + throw new IllegalArgumentException("OCR请求参数不能为空"); + } + if (ocrRequest.getFile() == null || !ocrRequest.getFile().exists()) { + throw new IllegalArgumentException("Word文件不能为空或文件不存在"); + } + + + // 验证文件类型是否为Word文档 + String fileName = ocrRequest.getFile().getName().toLowerCase(); + if (!fileName.endsWith(".doc") && !fileName.endsWith(".docx")) { + log.warn("文件类型可能不是Word文档: {}", fileName); + } + } + + /** + * 创建HTTP POST请求 + */ + private HttpPost createHttpPost(OcrRequest ocrRequest) { + HttpPost httpPost = new HttpPost(ocrServiceUrl); + + MultipartEntityBuilder builder = MultipartEntityBuilder.create(); + builder.setCharset(StandardCharsets.UTF_8); + builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE); + + // 添加文件字段 + builder.addPart(FILE_PART_NAME, + new FileBody(ocrRequest.getFile(), + ContentType.MULTIPART_FORM_DATA, + ocrRequest.getFile().getName())); + + httpPost.setEntity(builder.build()); + httpPost.setHeader("Accept", "application/json"); + + return httpPost; + } + + /** + * 执行文件转换服务请求 + */ + private WordConvertPdfResponse executeOcrRequest(HttpPost httpPost) throws IOException { + log.info("开始调用转换服务进行Word转PDF"); + + try (CloseableHttpResponse response = httpClient.execute(httpPost)) { + return processHttpResponse(response); + } + } + + /** + * 处理HTTP响应 + */ + private WordConvertPdfResponse processHttpResponse(CloseableHttpResponse response) throws IOException { + int statusCode = response.getStatusLine().getStatusCode(); + String responseBody = getResponseBody(response); + + log.info("文件转换服务响应状态: {}", statusCode); + log.debug("文件转换服务响应内容: {}", responseBody); + + // 检查HTTP状态码 + if (statusCode != 200) { + log.error("文件转换服务HTTP请求失败,状态码: {}, 响应: {}", statusCode, responseBody); + throw new IOException("文件转换服务HTTP请求失败,状态码: " + statusCode); + } + + WordConvertPdfResponse WordConvertPdfResponse = parseResponseBody(responseBody); + handleConvertResult(WordConvertPdfResponse); + + return WordConvertPdfResponse; + } + + /** + * 获取响应体 + */ + private String getResponseBody(CloseableHttpResponse response) throws IOException { + HttpEntity entity = response.getEntity(); + return EntityUtils.toString(entity, UTF_8); + } + + /** + * 解析响应体 + */ + private WordConvertPdfResponse parseResponseBody(String responseBody) throws IOException { + try { + return objectMapper.readValue(responseBody, WordConvertPdfResponse.class); + } catch (IOException e) { + log.error("解析OCR响应失败,响应内容: {}", responseBody, e); + throw new IOException("解析OCR响应失败: " + e.getMessage(), e); + } + } + + /** + * 处理Word转PDF结果 + */ + private void handleConvertResult(WordConvertPdfResponse WordConvertPdfResponse) { + if (WordConvertPdfResponse.isSuccess()) { + log.info("Word转PDF成功,文件名: {}", WordConvertPdfResponse.getFilename()); + logPdfResult(WordConvertPdfResponse); + } else { + log.warn("Word转PDF失败: {}", WordConvertPdfResponse.getMessage()); + } + } + + /** + * 记录PDF转换结果 + */ + private void logPdfResult(WordConvertPdfResponse WordConvertPdfResponse) { + if (WordConvertPdfResponse.getPdfBase64() != null) { + int pdfLength = WordConvertPdfResponse.getPdfBase64().length(); + log.info("PDF Base64数据长度: {} 字符", pdfLength); + + // 记录前50个字符用于调试 + if (log.isDebugEnabled() && pdfLength > 50) { + log.debug("PDF Base64前缀: {}...", WordConvertPdfResponse.getPdfBase64().substring(0, 50)); + } + } else { + log.warn("PDF Base64数据为空"); + } + } + + /** + * 清理资源 + */ + private void cleanupResources(OcrRequest ocrRequest, HttpPost httpPost) { + // 清理HTTP连接 + if (httpPost != null) { + httpPost.releaseConnection(); + } + + // 清理临时文件 +// cleanupTempFile(ocrRequest); + } + + /** + * 清理临时文件 + */ + private void cleanupTempFile(OcrRequest ocrRequest) { + if (ocrRequest.getFile() != null && ocrRequest.getFile().exists()) { + try { + boolean deleted = ocrRequest.getFile().delete(); + if (!deleted) { + log.warn("临时文件删除失败: {}", ocrRequest.getFile().getAbsolutePath()); + } else { + log.debug("临时文件已删除: {}", ocrRequest.getFile().getAbsolutePath()); + } + } catch (SecurityException e) { + log.error("删除临时文件时发生安全异常: {}", ocrRequest.getFile().getAbsolutePath(), e); + } + } + } + + /** + * 关闭HTTP客户端 + */ + public void close() { + try { + if (httpClient != null) { + httpClient.close(); + log.info("Word转PDF服务HTTP客户端已关闭"); + } + } catch (IOException e) { + log.error("关闭HTTP客户端失败", e); + } + } + + /** + * 销毁方法,用于Spring容器关闭时调用 + */ + public void destroy() { + close(); + } +} \ No newline at end of file