feat(testreport): 添加多模板普测报告生成功能
- 创建多模板普测任务表(ai_testreport_multi)用于管理批量任务 - 扩展测试报告表添加多模板关联字段和模板目录路径信息 - 添加测试报告点表的Word附件相关字段用于文件存储 - 实现多模板批任务控制器提供压缩包校验和任务创建功能 - 开发多模板批任务服务接口支持任务管理和报告生成 - 添加多模板批任务存储服务实现文件上传和元数据管理 - 创建多模板常量类定义任务状态和存储路径常量 - 实现多模板批任务参数验证和数据传输对象 - 添加多模板批任务验证结果和模板目录汇总视图对象 - 完善测试报告台账导入服务删除旧文件时处理Word存储路径
This commit is contained in:
@@ -562,6 +562,7 @@ public class TestReportLedgerImportService {
|
|||||||
private void deleteOldFiles(List<TestReportPointPO> oldPoints, List<TestReportGroupReportPO> oldGroupReports, String data) {
|
private void deleteOldFiles(List<TestReportPointPO> oldPoints, List<TestReportGroupReportPO> oldGroupReports, String data) {
|
||||||
for (TestReportPointPO point : oldPoints) {
|
for (TestReportPointPO point : oldPoints) {
|
||||||
testReportStorageService.deleteQuietly(point.getExcelStoragePath());
|
testReportStorageService.deleteQuietly(point.getExcelStoragePath());
|
||||||
|
testReportStorageService.deleteQuietly(point.getWordStoragePath());
|
||||||
}
|
}
|
||||||
for (TestReportGroupReportPO groupReport : oldGroupReports) {
|
for (TestReportGroupReportPO groupReport : oldGroupReports) {
|
||||||
testReportStorageService.deleteQuietly(groupReport.getReportStoragePath());
|
testReportStorageService.deleteQuietly(groupReport.getReportStoragePath());
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.njcn.common.pojo.exception.BusinessException;
|
|||||||
import com.njcn.gather.aireport.common.constant.AiReportFileConst;
|
import com.njcn.gather.aireport.common.constant.AiReportFileConst;
|
||||||
import com.njcn.gather.aireport.common.constant.AiReportFileExtensionConst;
|
import com.njcn.gather.aireport.common.constant.AiReportFileExtensionConst;
|
||||||
import com.njcn.gather.aireport.common.storage.AiReportMinioStorageService;
|
import com.njcn.gather.aireport.common.storage.AiReportMinioStorageService;
|
||||||
|
import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
|
||||||
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
|
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
@@ -34,6 +35,7 @@ public class TestReportStorageService {
|
|||||||
new LinkedHashSet<String>(Arrays.asList("doc", "docx", "xls", "xlsx", "pdf")));
|
new LinkedHashSet<String>(Arrays.asList("doc", "docx", "xls", "xlsx", "pdf")));
|
||||||
|
|
||||||
private final AiReportMinioStorageService minioStorageService;
|
private final AiReportMinioStorageService minioStorageService;
|
||||||
|
private final TestReportMapper testReportMapper;
|
||||||
|
|
||||||
public String saveSourceExcel(String reportId, MultipartFile file) {
|
public String saveSourceExcel(String reportId, MultipartFile file) {
|
||||||
validateMultipartFile(file, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "原始Excel文件不能为空", "原始Excel仅允许xls/xlsx格式");
|
validateMultipartFile(file, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "原始Excel文件不能为空", "原始Excel仅允许xls/xlsx格式");
|
||||||
@@ -59,6 +61,42 @@ public class TestReportStorageService {
|
|||||||
null, file, false).getObjectName();
|
null, file, false).getObjectName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String saveLedgerSummaryBytes(String reportId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "台账汇总文件不能为空", "台账汇总仅允许xls/xlsx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
|
||||||
|
null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String saveLedgerSummaryBytes(String reportId, String multiId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "台账汇总文件不能为空", "台账汇总仅允许xls/xlsx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_LEDGER_SUMMARY_DIR),
|
||||||
|
null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String savePointExcelBytes(String reportId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "监测点Excel附件不能为空", "监测点Excel附件仅允许xls/xlsx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
|
||||||
|
null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String savePointExcelBytes(String reportId, String multiId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, AiReportFileExtensionConst.EXCEL_EXTENSIONS, "监测点Excel附件不能为空", "监测点Excel附件仅允许xls/xlsx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_POINT_EXCEL_DIR),
|
||||||
|
null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String savePointWordBytes(String reportId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, POINT_WORD_ATTACHMENT_EXTENSIONS, "监测点Word附件不能为空", "监测点Word附件仅允许doc/docx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, TestReportConst.STORAGE_POINT_WORD_DIR),
|
||||||
|
null, fileName, content, resolveWordContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String savePointWordBytes(String reportId, String multiId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, POINT_WORD_ATTACHMENT_EXTENSIONS, "监测点Word附件不能为空", "监测点Word附件仅允许doc/docx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_POINT_WORD_DIR),
|
||||||
|
null, fileName, content, resolveWordContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
public UploadedBatch saveTempBatchFiles(String reportId, String batchId, Map<String, MultipartFile> fileMap) {
|
public UploadedBatch saveTempBatchFiles(String reportId, String batchId, Map<String, MultipartFile> fileMap) {
|
||||||
return saveBatchFiles(buildTempUploadDir(reportId, batchId), buildTempBatchRelativePath(reportId, batchId),
|
return saveBatchFiles(buildTempUploadDir(reportId, batchId), buildTempBatchRelativePath(reportId, batchId),
|
||||||
batchId, fileMap, "文件上传阶段:保存临时文件失败");
|
batchId, fileMap, "文件上传阶段:保存临时文件失败");
|
||||||
@@ -121,6 +159,12 @@ public class TestReportStorageService {
|
|||||||
null, fileName, content, MediaType.APPLICATION_OCTET_STREAM_VALUE, false).getObjectName();
|
null, fileName, content, MediaType.APPLICATION_OCTET_STREAM_VALUE, false).getObjectName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String saveGeneratedPlaceholder(String reportId, String multiId, String fileName, byte[] content) {
|
||||||
|
validateFileName(fileName, AiReportFileExtensionConst.WORD_EXTENSIONS, "生成报告文件名不能为空", "生成报告仅允许docx格式");
|
||||||
|
return minioStorageService.saveBytes(buildBusinessDir(reportId, multiId, TestReportConst.STORAGE_GENERATED_REPORT_DIR),
|
||||||
|
null, fileName, content, MediaType.APPLICATION_OCTET_STREAM_VALUE, false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
public byte[] readBusinessBytes(String storedPath) {
|
public byte[] readBusinessBytes(String storedPath) {
|
||||||
return minioStorageService.readBytes(storedPath);
|
return minioStorageService.readBytes(storedPath);
|
||||||
}
|
}
|
||||||
@@ -254,10 +298,22 @@ public class TestReportStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildBusinessDir(String reportId, String childDir) {
|
private String buildBusinessDir(String reportId, String childDir) {
|
||||||
|
return buildBusinessDir(reportId, resolveReportMultiId(reportId), childDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildBusinessDir(String reportId, String multiId, String childDir) {
|
||||||
|
if (StrUtil.isNotBlank(multiId)) {
|
||||||
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/multi-task/" + multiId + "/child-report/" + reportId + "/" + childDir;
|
||||||
|
}
|
||||||
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/" + childDir;
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/" + childDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildTempBatchDir(String reportId, String batchId) {
|
private String buildTempBatchDir(String reportId, String batchId) {
|
||||||
|
String multiId = resolveReportMultiId(reportId);
|
||||||
|
if (StrUtil.isNotBlank(multiId)) {
|
||||||
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/multi-task/" + multiId + "/child-report/" + reportId + "/"
|
||||||
|
+ TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
||||||
|
}
|
||||||
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/"
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + reportId + "/"
|
||||||
+ TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
+ TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
||||||
}
|
}
|
||||||
@@ -291,9 +347,21 @@ public class TestReportStorageService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildTempBatchRelativePath(String reportId, String batchId) {
|
private String buildTempBatchRelativePath(String reportId, String batchId) {
|
||||||
|
String multiId = resolveReportMultiId(reportId);
|
||||||
|
if (StrUtil.isNotBlank(multiId)) {
|
||||||
|
return "multi-task/" + multiId + "/child-report/" + reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
||||||
|
}
|
||||||
return reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
return reportId + "/" + TestReportConst.STORAGE_IMPORT_TEMP_DIR + "/" + batchId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String resolveReportMultiId(String reportId) {
|
||||||
|
String normalizedReportId = trimFileName(reportId);
|
||||||
|
if (StrUtil.isBlank(normalizedReportId) || testReportMapper == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return StrUtil.trimToNull(testReportMapper.selectReportMultiId(normalizedReportId));
|
||||||
|
}
|
||||||
|
|
||||||
private String buildValidateSessionRelativePath(String sessionId) {
|
private String buildValidateSessionRelativePath(String sessionId) {
|
||||||
return TestReportConst.STORAGE_VALIDATE_SESSION_DIR + "/" + sessionId;
|
return TestReportConst.STORAGE_VALIDATE_SESSION_DIR + "/" + sessionId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,9 @@ public interface TestReportMapper extends BaseMapper<TestReportPO> {
|
|||||||
|
|
||||||
@Select({
|
@Select({
|
||||||
"<script>",
|
"<script>",
|
||||||
"select r.id, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
|
"select r.id, r.multi_id as multiId, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
|
||||||
"r.report_model_id as reportModelId, rm.name as reportModelName, r.create_unit as createUnit,",
|
"r.report_model_id as reportModelId, rm.name as reportModelName, r.template_dir_name as templateDirName,",
|
||||||
|
"r.template_dir_path as templateDirPath, r.sort_no as sortNo, r.create_unit as createUnit,",
|
||||||
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
|
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
|
||||||
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
|
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
|
||||||
"r.report_generate_state as reportGenerateState,",
|
"r.report_generate_state as reportGenerateState,",
|
||||||
@@ -51,8 +52,9 @@ public interface TestReportMapper extends BaseMapper<TestReportPO> {
|
|||||||
Page<TestReportVO> selectReportPage(Page<TestReportVO> page, @Param("param") TestReportParam.QueryParam param);
|
Page<TestReportVO> selectReportPage(Page<TestReportVO> page, @Param("param") TestReportParam.QueryParam param);
|
||||||
|
|
||||||
@Select({
|
@Select({
|
||||||
"select r.id, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
|
"select r.id, r.multi_id as multiId, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
|
||||||
"r.report_model_id as reportModelId, rm.name as reportModelName, r.create_unit as createUnit,",
|
"r.report_model_id as reportModelId, rm.name as reportModelName, r.template_dir_name as templateDirName,",
|
||||||
|
"r.template_dir_path as templateDirPath, r.sort_no as sortNo, r.create_unit as createUnit,",
|
||||||
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
|
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
|
||||||
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
|
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
|
||||||
"r.report_generate_state as reportGenerateState,",
|
"r.report_generate_state as reportGenerateState,",
|
||||||
@@ -75,4 +77,29 @@ public interface TestReportMapper extends BaseMapper<TestReportPO> {
|
|||||||
|
|
||||||
@Select("select count(1) from ai_reportmodel where id = #{id} and status = 1")
|
@Select("select count(1) from ai_reportmodel where id = #{id} and status = 1")
|
||||||
int countNormalReportModel(@Param("id") String id);
|
int countNormalReportModel(@Param("id") String id);
|
||||||
|
|
||||||
|
@Select("select multi_id from ai_testreport where id = #{id} limit 1")
|
||||||
|
String selectReportMultiId(@Param("id") String id);
|
||||||
|
|
||||||
|
@Select({
|
||||||
|
"select r.id, r.multi_id as multiId, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
|
||||||
|
"r.report_model_id as reportModelId, rm.name as reportModelName, r.template_dir_name as templateDirName,",
|
||||||
|
"r.template_dir_path as templateDirPath, r.sort_no as sortNo, r.create_unit as createUnit,",
|
||||||
|
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
|
||||||
|
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
|
||||||
|
"r.report_generate_state as reportGenerateState,",
|
||||||
|
"(select count(1) from ai_testreport_point p where p.report_id = r.id and p.deleted = 0) as pointCount,",
|
||||||
|
"(select count(1) from ai_testreport_group_report gr where gr.report_id = r.id and gr.deleted = 0) as groupCount,",
|
||||||
|
"r.status, r.create_by as createBy, creator.name as createByName, r.create_time as createTime,",
|
||||||
|
"r.update_by as updateBy, updater.name as updateByName, r.update_time as updateTime",
|
||||||
|
"from ai_testreport r",
|
||||||
|
"left join ai_clientunit cu on r.client_unit_id = cu.id",
|
||||||
|
"left join ai_reportmodel rm on r.report_model_id = rm.id",
|
||||||
|
"left join sys_user creator on r.create_by = creator.id",
|
||||||
|
"left join sys_user updater on r.update_by = updater.id",
|
||||||
|
"left join sys_user checker on r.check_id = checker.id",
|
||||||
|
"where r.multi_id = #{multiId} and r.status = 1",
|
||||||
|
"order by r.sort_no asc, r.create_time asc"
|
||||||
|
})
|
||||||
|
java.util.List<TestReportVO> selectByMultiId(@Param("multiId") String multiId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,12 +16,20 @@ public class TestReportPO implements Serializable {
|
|||||||
|
|
||||||
@TableId("id")
|
@TableId("id")
|
||||||
private String id;
|
private String id;
|
||||||
|
@TableField("multi_id")
|
||||||
|
private String multiId;
|
||||||
@TableField("no")
|
@TableField("no")
|
||||||
private String no;
|
private String no;
|
||||||
@TableField("client_unit_id")
|
@TableField("client_unit_id")
|
||||||
private String clientUnitId;
|
private String clientUnitId;
|
||||||
@TableField("report_model_id")
|
@TableField("report_model_id")
|
||||||
private String reportModelId;
|
private String reportModelId;
|
||||||
|
@TableField("template_dir_name")
|
||||||
|
private String templateDirName;
|
||||||
|
@TableField("template_dir_path")
|
||||||
|
private String templateDirPath;
|
||||||
|
@TableField("sort_no")
|
||||||
|
private Integer sortNo;
|
||||||
@TableField("create_unit")
|
@TableField("create_unit")
|
||||||
private String createUnit;
|
private String createUnit;
|
||||||
@TableField("contract_number")
|
@TableField("contract_number")
|
||||||
|
|||||||
@@ -26,7 +26,11 @@ public class TestReportPointPO {
|
|||||||
private String protocolCapacity;
|
private String protocolCapacity;
|
||||||
private String powerSupplyCapacity;
|
private String powerSupplyCapacity;
|
||||||
private String excelAttachmentName;
|
private String excelAttachmentName;
|
||||||
|
private String excelAttachmentRelativePath;
|
||||||
private String excelStoragePath;
|
private String excelStoragePath;
|
||||||
|
private String wordAttachmentName;
|
||||||
|
private String wordAttachmentRelativePath;
|
||||||
|
private String wordStoragePath;
|
||||||
private Integer sortNo;
|
private Integer sortNo;
|
||||||
private Integer deleted;
|
private Integer deleted;
|
||||||
private String createBy;
|
private String createBy;
|
||||||
|
|||||||
@@ -18,4 +18,7 @@ public class TestReportPointVO {
|
|||||||
private String protocolCapacity;
|
private String protocolCapacity;
|
||||||
private String powerSupplyCapacity;
|
private String powerSupplyCapacity;
|
||||||
private String excelAttachmentName;
|
private String excelAttachmentName;
|
||||||
|
private String excelAttachmentRelativePath;
|
||||||
|
private String wordAttachmentName;
|
||||||
|
private String wordAttachmentRelativePath;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ public class TestReportVO {
|
|||||||
|
|
||||||
@ApiModelProperty("普测报告ID")
|
@ApiModelProperty("普测报告ID")
|
||||||
private String id;
|
private String id;
|
||||||
|
@ApiModelProperty("所属多模板任务ID")
|
||||||
|
private String multiId;
|
||||||
@ApiModelProperty("普测报告编号")
|
@ApiModelProperty("普测报告编号")
|
||||||
private String no;
|
private String no;
|
||||||
@ApiModelProperty("委托单位ID")
|
@ApiModelProperty("委托单位ID")
|
||||||
@@ -30,6 +32,12 @@ public class TestReportVO {
|
|||||||
private String reportModelId;
|
private String reportModelId;
|
||||||
@ApiModelProperty("报告模板名称")
|
@ApiModelProperty("报告模板名称")
|
||||||
private String reportModelName;
|
private String reportModelName;
|
||||||
|
@ApiModelProperty("压缩包一级模板目录名称")
|
||||||
|
private String templateDirName;
|
||||||
|
@ApiModelProperty("压缩包一级模板目录相对路径")
|
||||||
|
private String templateDirPath;
|
||||||
|
@ApiModelProperty("父任务内排序号")
|
||||||
|
private Integer sortNo;
|
||||||
@ApiModelProperty("检测公司ID")
|
@ApiModelProperty("检测公司ID")
|
||||||
private String createUnit;
|
private String createUnit;
|
||||||
@ApiModelProperty("合同编号")
|
@ApiModelProperty("合同编号")
|
||||||
@@ -88,6 +96,10 @@ public class TestReportVO {
|
|||||||
return defaultDisplay(no);
|
return defaultDisplay(no);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMultiId() {
|
||||||
|
return defaultDisplay(multiId);
|
||||||
|
}
|
||||||
|
|
||||||
public String getClientUnitId() {
|
public String getClientUnitId() {
|
||||||
return defaultDisplay(clientUnitId);
|
return defaultDisplay(clientUnitId);
|
||||||
}
|
}
|
||||||
@@ -104,6 +116,14 @@ public class TestReportVO {
|
|||||||
return defaultDisplay(reportModelName);
|
return defaultDisplay(reportModelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getTemplateDirName() {
|
||||||
|
return defaultDisplay(templateDirName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTemplateDirPath() {
|
||||||
|
return defaultDisplay(templateDirPath);
|
||||||
|
}
|
||||||
|
|
||||||
public String getCreateUnit() {
|
public String getCreateUnit() {
|
||||||
return defaultDisplay(createUnit);
|
return defaultDisplay(createUnit);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -599,6 +599,7 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
|
|||||||
private void deletePointFiles(List<TestReportPointPO> oldPoints) {
|
private void deletePointFiles(List<TestReportPointPO> oldPoints) {
|
||||||
for (TestReportPointPO oldPoint : oldPoints) {
|
for (TestReportPointPO oldPoint : oldPoints) {
|
||||||
testReportStorageService.deleteQuietly(oldPoint.getExcelStoragePath());
|
testReportStorageService.deleteQuietly(oldPoint.getExcelStoragePath());
|
||||||
|
testReportStorageService.deleteQuietly(oldPoint.getWordStoragePath());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -762,7 +763,7 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
|
|||||||
|
|
||||||
String reportName = report.getNo() + "-第" + groupReport.getGroupNo() + "组检测报告";
|
String reportName = report.getNo() + "-第" + groupReport.getGroupNo() + "组检测报告";
|
||||||
String reportFileName = ExportFileNameUtil.appendToday(reportName + ".docx");
|
String reportFileName = ExportFileNameUtil.appendToday(reportName + ".docx");
|
||||||
String relativePath = testReportStorageService.saveGeneratedPlaceholder(report.getId(), reportFileName,
|
String relativePath = testReportStorageService.saveGeneratedPlaceholder(report.getId(), report.getMultiId(), reportFileName,
|
||||||
("group:" + groupReport.getGroupNo()).getBytes(StandardCharsets.UTF_8));
|
("group:" + groupReport.getGroupNo()).getBytes(StandardCharsets.UTF_8));
|
||||||
groupReport.setReportName(reportName);
|
groupReport.setReportName(reportName);
|
||||||
groupReport.setReportFileName(reportFileName);
|
groupReport.setReportFileName(reportFileName);
|
||||||
@@ -795,6 +796,9 @@ public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestRep
|
|||||||
vo.setProtocolCapacity(defaultDash(point.getProtocolCapacity()));
|
vo.setProtocolCapacity(defaultDash(point.getProtocolCapacity()));
|
||||||
vo.setPowerSupplyCapacity(defaultDash(point.getPowerSupplyCapacity()));
|
vo.setPowerSupplyCapacity(defaultDash(point.getPowerSupplyCapacity()));
|
||||||
vo.setExcelAttachmentName(defaultDash(point.getExcelAttachmentName()));
|
vo.setExcelAttachmentName(defaultDash(point.getExcelAttachmentName()));
|
||||||
|
vo.setExcelAttachmentRelativePath(defaultDash(point.getExcelAttachmentRelativePath()));
|
||||||
|
vo.setWordAttachmentName(defaultDash(point.getWordAttachmentName()));
|
||||||
|
vo.setWordAttachmentRelativePath(defaultDash(point.getWordAttachmentRelativePath()));
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,169 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.component;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.aireport.common.constant.AiReportFileConst;
|
||||||
|
import com.njcn.gather.aireport.common.storage.AiReportMinioStorageService;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.constant.TestReportMultiConst;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TestReportMultiStorageService {
|
||||||
|
|
||||||
|
private final AiReportMinioStorageService minioStorageService;
|
||||||
|
|
||||||
|
public AiReportMinioStorageService.StoredFile saveSessionZip(String sessionId, MultipartFile file) {
|
||||||
|
validateZipFile(file);
|
||||||
|
return minioStorageService.saveMultipartFile(buildSessionZipDir(sessionId), null, file, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void saveSessionExtractedFile(String sessionId, String relativePath, byte[] content, String contentType) {
|
||||||
|
String normalizedRelativePath = normalizeRelativePath(relativePath);
|
||||||
|
minioStorageService.saveBytes(buildSessionExtractDir(sessionId, resolveParentPath(normalizedRelativePath)),
|
||||||
|
null, resolveFileName(normalizedRelativePath), content, contentType, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readSessionExtractedBytes(String sessionId, String relativePath) {
|
||||||
|
return minioStorageService.readBytes(buildSessionExtractObjectName(sessionId, relativePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeSessionMeta(String sessionId, String content) {
|
||||||
|
minioStorageService.saveBytes(buildSessionDir(sessionId), null, TestReportMultiConst.STORAGE_SESSION_META_FILE_NAME,
|
||||||
|
content == null ? new byte[0] : content.getBytes(StandardCharsets.UTF_8), MediaType.APPLICATION_JSON_VALUE, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String readSessionMeta(String sessionId) {
|
||||||
|
String objectName = buildSessionMetaObjectName(sessionId);
|
||||||
|
if (!minioStorageService.exists(objectName)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new String(minioStorageService.readBytes(objectName), StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readSessionZipBytes(String zipStoragePath) {
|
||||||
|
return minioStorageService.readBytes(zipStoragePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String saveTaskZipBytes(String multiId, String fileName, byte[] content) {
|
||||||
|
return minioStorageService.saveBytes(buildTaskDir(multiId, TestReportMultiConst.STORAGE_MULTI_ZIP_DIR),
|
||||||
|
null, fileName, content, "application/zip", false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String saveTaskSummaryBytes(String multiId, String fileName, byte[] content) {
|
||||||
|
return minioStorageService.saveBytes(buildTaskDir(multiId, TestReportMultiConst.STORAGE_MULTI_SUMMARY_DIR),
|
||||||
|
null, fileName, content, resolveExcelContentType(fileName), false).getObjectName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> listTaskChildReportObjectNames(String multiId) {
|
||||||
|
return minioStorageService.listObjectNames(buildTaskChildReportDir(multiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> listTaskSummaryObjectNames(String multiId) {
|
||||||
|
return minioStorageService.listObjectNames(buildTaskSummaryDir(multiId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] readStoredBytes(String objectName) {
|
||||||
|
return minioStorageService.readBytes(objectName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildTaskChildReportDir(String multiId) {
|
||||||
|
return buildTaskDir(multiId, TestReportMultiConst.STORAGE_MULTI_CHILD_REPORT_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String buildTaskSummaryDir(String multiId) {
|
||||||
|
return buildTaskDir(multiId, TestReportMultiConst.STORAGE_MULTI_SUMMARY_DIR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteSessionQuietly(String sessionId) {
|
||||||
|
deletePrefixQuietly(buildSessionDir(sessionId));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteQuietly(String storedPath) {
|
||||||
|
minioStorageService.deleteQuietly(storedPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateZipFile(MultipartFile file) {
|
||||||
|
if (file == null || file.isEmpty()) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包不能为空");
|
||||||
|
}
|
||||||
|
String fileName = file.getOriginalFilename();
|
||||||
|
if (StrUtil.isBlank(fileName) || !fileName.trim().toLowerCase().endsWith(".zip")) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "仅支持上传zip压缩包");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deletePrefixQuietly(String prefix) {
|
||||||
|
List<String> objectNames = minioStorageService.listObjectNames(prefix);
|
||||||
|
for (String objectName : objectNames) {
|
||||||
|
minioStorageService.deleteQuietly(objectName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveExcelContentType(String fileName) {
|
||||||
|
return fileName != null && fileName.toLowerCase().endsWith(".xls")
|
||||||
|
? "application/vnd.ms-excel"
|
||||||
|
: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionDir(String sessionId) {
|
||||||
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + TestReportMultiConst.STORAGE_MULTI_SESSION_DIR + "/" + sessionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionZipDir(String sessionId) {
|
||||||
|
return buildSessionDir(sessionId) + "/" + TestReportMultiConst.STORAGE_SESSION_ZIP_DIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionExtractDir(String sessionId, String parentPath) {
|
||||||
|
String dir = buildSessionDir(sessionId) + "/" + TestReportMultiConst.STORAGE_SESSION_EXTRACT_DIR;
|
||||||
|
return StrUtil.isBlank(parentPath) ? dir : dir + "/" + parentPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionExtractObjectName(String sessionId, String relativePath) {
|
||||||
|
return buildSessionDir(sessionId) + "/" + TestReportMultiConst.STORAGE_SESSION_EXTRACT_DIR + "/" + normalizeRelativePath(relativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildSessionMetaObjectName(String sessionId) {
|
||||||
|
return buildSessionDir(sessionId) + "/" + TestReportMultiConst.STORAGE_SESSION_META_FILE_NAME;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildTaskDir(String multiId, String childDir) {
|
||||||
|
return AiReportFileConst.BIZ_DIR_TEST_REPORT + "/" + TestReportMultiConst.STORAGE_MULTI_TASK_DIR + "/" + multiId + "/" + childDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveParentPath(String relativePath) {
|
||||||
|
int index = relativePath.lastIndexOf('/');
|
||||||
|
return index < 0 ? "" : relativePath.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveFileName(String relativePath) {
|
||||||
|
int index = relativePath.lastIndexOf('/');
|
||||||
|
return index < 0 ? relativePath : relativePath.substring(index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeRelativePath(String relativePath) {
|
||||||
|
String normalized = StrUtil.trimToEmpty(relativePath).replace("\\", "/");
|
||||||
|
while (normalized.startsWith("/")) {
|
||||||
|
normalized = normalized.substring(1);
|
||||||
|
}
|
||||||
|
while (normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(normalized)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包内文件路径不能为空");
|
||||||
|
}
|
||||||
|
for (String segment : normalized.split("/")) {
|
||||||
|
if (StrUtil.isBlank(segment) || ".".equals(segment) || "..".equals(segment)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包内文件路径不合法");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,344 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.component;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.exception.BusinessException;
|
||||||
|
import com.njcn.gather.aireport.testreport.component.TestReportLedgerExcelParser;
|
||||||
|
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiTemplateDirVO;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipInputStream;
|
||||||
|
|
||||||
|
@Component
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TestReportMultiZipParser {
|
||||||
|
|
||||||
|
private static final Set<String> SUPPORTED_EXTENSIONS = new LinkedHashSet<String>();
|
||||||
|
|
||||||
|
static {
|
||||||
|
SUPPORTED_EXTENSIONS.add("xls");
|
||||||
|
SUPPORTED_EXTENSIONS.add("xlsx");
|
||||||
|
SUPPORTED_EXTENSIONS.add("doc");
|
||||||
|
SUPPORTED_EXTENSIONS.add("docx");
|
||||||
|
}
|
||||||
|
|
||||||
|
private final TestReportLedgerExcelParser testReportLedgerExcelParser;
|
||||||
|
|
||||||
|
public ParsedArchive parse(MultipartFile zipFile) {
|
||||||
|
if (zipFile == null || zipFile.isEmpty()) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包不能为空");
|
||||||
|
}
|
||||||
|
String zipFileName = requireZipFileName(zipFile.getOriginalFilename());
|
||||||
|
Map<String, byte[]> rawEntries = readZipEntries(zipFile);
|
||||||
|
Map<String, byte[]> normalizedEntries = stripSingleWrapperDir(rawEntries);
|
||||||
|
String summaryPath = resolveSummaryPath(normalizedEntries.keySet());
|
||||||
|
if (summaryPath == null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包根目录缺少台账信息汇总.xls或台账信息汇总.xlsx");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] summaryBytes = normalizedEntries.get(summaryPath);
|
||||||
|
TestReportLedgerExcelParser.ParsedLedger ledger = testReportLedgerExcelParser.parseForValidate(
|
||||||
|
resolveFileName(summaryPath), new ByteArrayInputStream(summaryBytes));
|
||||||
|
|
||||||
|
ParsedArchive archive = new ParsedArchive();
|
||||||
|
archive.setZipFileName(zipFileName);
|
||||||
|
archive.setSummaryFileName(resolveFileName(summaryPath));
|
||||||
|
archive.setSummaryRelativePath(summaryPath);
|
||||||
|
archive.setSummaryFileBytes(summaryBytes);
|
||||||
|
archive.setLedger(ledger);
|
||||||
|
archive.setFileContentMap(normalizedEntries);
|
||||||
|
archive.setPointCount(ledger.getPoints().size());
|
||||||
|
archive.setExcelAttachmentCount(ledger.getPoints().size());
|
||||||
|
|
||||||
|
Map<String, TemplateDirSummary> dirSummaryMap = new LinkedHashMap<String, TemplateDirSummary>();
|
||||||
|
Set<String> referencedPaths = new HashSet<String>();
|
||||||
|
int wordAttachmentCount = 0;
|
||||||
|
|
||||||
|
for (TestReportLedgerExcelParser.ParsedPointRow pointRow : ledger.getPoints()) {
|
||||||
|
String excelRelativePath = normalizeRelativePath(pointRow.getExcelAttachmentName());
|
||||||
|
String templateDirPath = resolveTopLevelDir(excelRelativePath);
|
||||||
|
if (templateDirPath == null) {
|
||||||
|
archive.getFailReasons().add("第" + pointRow.getRowNo() + "行Excel附件名称必须包含一级模板目录");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!normalizedEntries.containsKey(excelRelativePath)) {
|
||||||
|
archive.getFailReasons().add("第" + pointRow.getRowNo() + "行缺少Excel附件:" + excelRelativePath);
|
||||||
|
}
|
||||||
|
referencedPaths.add(excelRelativePath);
|
||||||
|
|
||||||
|
String wordRelativePath = null;
|
||||||
|
if (StrUtil.isNotBlank(pointRow.getWordAttachmentName())) {
|
||||||
|
wordAttachmentCount++;
|
||||||
|
wordRelativePath = normalizeRelativePath(pointRow.getWordAttachmentName());
|
||||||
|
if (!normalizedEntries.containsKey(wordRelativePath)) {
|
||||||
|
archive.getFailReasons().add("第" + pointRow.getRowNo() + "行缺少Word附件:" + wordRelativePath);
|
||||||
|
}
|
||||||
|
if (!templateDirPath.equals(resolveTopLevelDir(wordRelativePath))) {
|
||||||
|
archive.getFailReasons().add("第" + pointRow.getRowNo() + "行Word附件与Excel附件不在同一一级目录");
|
||||||
|
}
|
||||||
|
referencedPaths.add(wordRelativePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
TemplateDirSummary dirSummary = dirSummaryMap.computeIfAbsent(templateDirPath, this::newTemplateDirSummary);
|
||||||
|
dirSummary.setPointCount(dirSummary.getPointCount() + 1);
|
||||||
|
dirSummary.setExcelAttachmentCount(dirSummary.getExcelAttachmentCount() + 1);
|
||||||
|
if (StrUtil.isNotBlank(wordRelativePath)) {
|
||||||
|
dirSummary.setWordAttachmentCount(dirSummary.getWordAttachmentCount() + 1);
|
||||||
|
}
|
||||||
|
if (pointRow.getGroupNo() != null) {
|
||||||
|
dirSummary.getGroupNos().add(pointRow.getGroupNo());
|
||||||
|
}
|
||||||
|
|
||||||
|
ParsedPointBinding pointBinding = new ParsedPointBinding();
|
||||||
|
pointBinding.setRowNo(pointRow.getRowNo());
|
||||||
|
pointBinding.setGroupNo(pointRow.getGroupNo());
|
||||||
|
pointBinding.setSubstationName(pointRow.getSubstationName());
|
||||||
|
pointBinding.setPointName(pointRow.getPointName());
|
||||||
|
pointBinding.setTemplateDirName(dirSummary.getDirName());
|
||||||
|
pointBinding.setTemplateDirPath(dirSummary.getDirPath());
|
||||||
|
pointBinding.setExcelAttachmentName(resolveFileName(excelRelativePath));
|
||||||
|
pointBinding.setExcelAttachmentRelativePath(excelRelativePath);
|
||||||
|
pointBinding.setWordAttachmentName(StrUtil.isBlank(wordRelativePath) ? null : resolveFileName(wordRelativePath));
|
||||||
|
pointBinding.setWordAttachmentRelativePath(wordRelativePath);
|
||||||
|
archive.getPoints().add(pointBinding);
|
||||||
|
}
|
||||||
|
|
||||||
|
archive.setWordAttachmentCount(wordAttachmentCount);
|
||||||
|
for (TemplateDirSummary dirSummary : dirSummaryMap.values()) {
|
||||||
|
TestReportMultiTemplateDirVO dirVO = new TestReportMultiTemplateDirVO();
|
||||||
|
dirVO.setDirName(dirSummary.getDirName());
|
||||||
|
dirVO.setDirPath(dirSummary.getDirPath());
|
||||||
|
dirVO.setPointCount(dirSummary.getPointCount());
|
||||||
|
dirVO.setGroupCount(dirSummary.getGroupNos().size());
|
||||||
|
dirVO.setExcelAttachmentCount(dirSummary.getExcelAttachmentCount());
|
||||||
|
dirVO.setWordAttachmentCount(dirSummary.getWordAttachmentCount());
|
||||||
|
archive.getTemplateDirs().add(dirVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String relativePath : normalizedEntries.keySet()) {
|
||||||
|
if (summaryPath.equals(relativePath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!referencedPaths.contains(relativePath)) {
|
||||||
|
archive.getOrphanAttachmentPaths().add(relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!archive.getOrphanAttachmentPaths().isEmpty()) {
|
||||||
|
archive.getFailReasons().add("压缩包存在未被台账引用的附件");
|
||||||
|
}
|
||||||
|
archive.setDirCount(archive.getTemplateDirs().size());
|
||||||
|
archive.setChildReportCount(archive.getTemplateDirs().size());
|
||||||
|
archive.setSuccess(archive.getFailReasons().isEmpty());
|
||||||
|
return archive;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, byte[]> readZipEntries(MultipartFile zipFile) {
|
||||||
|
Map<String, byte[]> result = new LinkedHashMap<String, byte[]>();
|
||||||
|
Set<String> duplicateCheck = new HashSet<String>();
|
||||||
|
try (ZipInputStream zipInputStream = new ZipInputStream(zipFile.getInputStream())) {
|
||||||
|
ZipEntry entry;
|
||||||
|
while ((entry = zipInputStream.getNextEntry()) != null) {
|
||||||
|
if (entry.isDirectory()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String normalizedPath = normalizeRelativePath(entry.getName());
|
||||||
|
if (isIgnoredEntry(normalizedPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
validateEntryExtension(normalizedPath);
|
||||||
|
String duplicateKey = normalizedPath.toLowerCase(Locale.ENGLISH);
|
||||||
|
if (!duplicateCheck.add(duplicateKey)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包中存在重复文件路径:" + normalizedPath);
|
||||||
|
}
|
||||||
|
result.put(normalizedPath, readEntryBytes(zipInputStream));
|
||||||
|
}
|
||||||
|
} catch (BusinessException exception) {
|
||||||
|
throw exception;
|
||||||
|
} catch (IOException exception) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "读取压缩包失败");
|
||||||
|
}
|
||||||
|
if (result.isEmpty()) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包中没有可用文件");
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, byte[]> stripSingleWrapperDir(Map<String, byte[]> rawEntries) {
|
||||||
|
if (rawEntries.isEmpty()) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
String wrapper = null;
|
||||||
|
for (String path : rawEntries.keySet()) {
|
||||||
|
int slashIndex = path.indexOf('/');
|
||||||
|
if (slashIndex <= 0) {
|
||||||
|
return rawEntries;
|
||||||
|
}
|
||||||
|
String currentWrapper = path.substring(0, slashIndex);
|
||||||
|
if (wrapper == null) {
|
||||||
|
wrapper = currentWrapper;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!wrapper.equals(currentWrapper)) {
|
||||||
|
return rawEntries;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Map<String, byte[]> result = new LinkedHashMap<String, byte[]>();
|
||||||
|
for (Map.Entry<String, byte[]> entry : rawEntries.entrySet()) {
|
||||||
|
String strippedPath = entry.getKey().substring(wrapper.length() + 1);
|
||||||
|
if (StrUtil.isBlank(strippedPath)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.put(strippedPath, entry.getValue());
|
||||||
|
}
|
||||||
|
return result.isEmpty() ? rawEntries : result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveSummaryPath(Set<String> relativePaths) {
|
||||||
|
String matchedPath = null;
|
||||||
|
for (String relativePath : relativePaths) {
|
||||||
|
if (!TestReportConst.isLedgerSummaryFileName(resolveFileName(relativePath))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (relativePath.contains("/")) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "台账信息汇总文件必须位于压缩包根目录");
|
||||||
|
}
|
||||||
|
if (matchedPath != null) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包根目录仅允许存在一个台账信息汇总文件");
|
||||||
|
}
|
||||||
|
matchedPath = relativePath;
|
||||||
|
}
|
||||||
|
return matchedPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TemplateDirSummary newTemplateDirSummary(String dirPath) {
|
||||||
|
TemplateDirSummary summary = new TemplateDirSummary();
|
||||||
|
summary.setDirPath(dirPath);
|
||||||
|
summary.setDirName(resolveFileName(dirPath));
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateEntryExtension(String path) {
|
||||||
|
String extension = resolveExtension(path);
|
||||||
|
if (!SUPPORTED_EXTENSIONS.contains(extension)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包中存在不支持的文件类型:" + path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isIgnoredEntry(String path) {
|
||||||
|
String lowerPath = path.toLowerCase(Locale.ENGLISH);
|
||||||
|
return lowerPath.startsWith("__macosx/") || lowerPath.endsWith("/.ds_store") || lowerPath.equals(".ds_store");
|
||||||
|
}
|
||||||
|
|
||||||
|
private byte[] readEntryBytes(ZipInputStream zipInputStream) throws IOException {
|
||||||
|
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||||
|
byte[] buffer = new byte[8192];
|
||||||
|
int length;
|
||||||
|
while ((length = zipInputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, length);
|
||||||
|
}
|
||||||
|
return outputStream.toByteArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String requireZipFileName(String fileName) {
|
||||||
|
String normalized = StrUtil.trimToNull(fileName);
|
||||||
|
if (normalized == null || !normalized.toLowerCase(Locale.ENGLISH).endsWith(".zip")) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "仅支持上传zip压缩包");
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveTopLevelDir(String relativePath) {
|
||||||
|
int slashIndex = relativePath.indexOf('/');
|
||||||
|
return slashIndex <= 0 ? null : relativePath.substring(0, slashIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveFileName(String relativePath) {
|
||||||
|
int slashIndex = relativePath.lastIndexOf('/');
|
||||||
|
return slashIndex < 0 ? relativePath : relativePath.substring(slashIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String resolveExtension(String filePath) {
|
||||||
|
int dotIndex = filePath.lastIndexOf('.');
|
||||||
|
return dotIndex < 0 || dotIndex == filePath.length() - 1 ? "" : filePath.substring(dotIndex + 1).toLowerCase(Locale.ENGLISH);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeRelativePath(String relativePath) {
|
||||||
|
String normalized = StrUtil.trimToEmpty(relativePath).replace("\\", "/");
|
||||||
|
while (normalized.startsWith("/")) {
|
||||||
|
normalized = normalized.substring(1);
|
||||||
|
}
|
||||||
|
while (normalized.endsWith("/")) {
|
||||||
|
normalized = normalized.substring(0, normalized.length() - 1);
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(normalized)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包内文件路径不能为空");
|
||||||
|
}
|
||||||
|
for (String segment : normalized.split("/")) {
|
||||||
|
if (StrUtil.isBlank(segment) || ".".equals(segment) || "..".equals(segment)) {
|
||||||
|
throw new BusinessException(CommonResponseEnum.FAIL, "压缩包内文件路径不合法:" + relativePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return normalized;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ParsedArchive {
|
||||||
|
private Boolean success = Boolean.FALSE;
|
||||||
|
private String zipFileName;
|
||||||
|
private String summaryFileName;
|
||||||
|
private String summaryRelativePath;
|
||||||
|
private byte[] summaryFileBytes;
|
||||||
|
private Integer dirCount = 0;
|
||||||
|
private Integer childReportCount = 0;
|
||||||
|
private Integer pointCount = 0;
|
||||||
|
private Integer excelAttachmentCount = 0;
|
||||||
|
private Integer wordAttachmentCount = 0;
|
||||||
|
private List<String> orphanAttachmentPaths = new ArrayList<String>();
|
||||||
|
private List<String> failReasons = new ArrayList<String>();
|
||||||
|
private List<TestReportMultiTemplateDirVO> templateDirs = new ArrayList<TestReportMultiTemplateDirVO>();
|
||||||
|
private List<ParsedPointBinding> points = new ArrayList<ParsedPointBinding>();
|
||||||
|
private Map<String, byte[]> fileContentMap = new LinkedHashMap<String, byte[]>();
|
||||||
|
private TestReportLedgerExcelParser.ParsedLedger ledger;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ParsedPointBinding {
|
||||||
|
private Integer rowNo;
|
||||||
|
private Integer groupNo;
|
||||||
|
private String substationName;
|
||||||
|
private String pointName;
|
||||||
|
private String templateDirName;
|
||||||
|
private String templateDirPath;
|
||||||
|
private String excelAttachmentName;
|
||||||
|
private String excelAttachmentRelativePath;
|
||||||
|
private String wordAttachmentName;
|
||||||
|
private String wordAttachmentRelativePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
private static class TemplateDirSummary {
|
||||||
|
private String dirName;
|
||||||
|
private String dirPath;
|
||||||
|
private int pointCount;
|
||||||
|
private int excelAttachmentCount;
|
||||||
|
private int wordAttachmentCount;
|
||||||
|
private Set<Integer> groupNos = new LinkedHashSet<Integer>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,119 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.controller;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.common.pojo.annotation.OperateInfo;
|
||||||
|
import com.njcn.common.pojo.constant.OperateType;
|
||||||
|
import com.njcn.common.pojo.enums.common.LogEnum;
|
||||||
|
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
|
||||||
|
import com.njcn.common.pojo.response.HttpResult;
|
||||||
|
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.param.TestReportMultiParam;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiVO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiValidateResultVO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.service.TestReportMultiService;
|
||||||
|
import com.njcn.web.controller.BaseController;
|
||||||
|
import com.njcn.web.utils.HttpResultUtil;
|
||||||
|
import io.swagger.annotations.Api;
|
||||||
|
import io.swagger.annotations.ApiImplicitParam;
|
||||||
|
import io.swagger.annotations.ApiOperation;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.validation.annotation.Validated;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
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.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Api(tags = "多模板普测批任务")
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/test-report-multi")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class TestReportMultiController extends BaseController {
|
||||||
|
|
||||||
|
private final TestReportMultiService testReportMultiService;
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询多模板批任务列表")
|
||||||
|
@PostMapping("/list")
|
||||||
|
public HttpResult<Page<TestReportMultiVO>> list(@RequestBody(required = false) TestReportMultiParam.QueryParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("list");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
|
||||||
|
testReportMultiService.list(param), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@ApiOperation("校验多模板批任务压缩包")
|
||||||
|
@PostMapping("/zip/validate")
|
||||||
|
public HttpResult<TestReportMultiValidateResultVO> validateZip(@RequestParam("file") MultipartFile file) {
|
||||||
|
String methodDescribe = getMethodDescribe("validateZip");
|
||||||
|
TestReportMultiValidateResultVO result = testReportMultiService.validateZip(file);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(Boolean.TRUE.equals(result.getSuccess())
|
||||||
|
? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
|
||||||
|
result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
|
||||||
|
@ApiOperation("创建多模板批任务")
|
||||||
|
@PostMapping("/create")
|
||||||
|
public HttpResult<Boolean> create(@RequestBody @Validated TestReportMultiParam.CreateParam param) {
|
||||||
|
String methodDescribe = getMethodDescribe("create");
|
||||||
|
boolean result = testReportMultiService.create(param);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
|
||||||
|
result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询多模板批任务详情")
|
||||||
|
@ApiImplicitParam(name = "id", value = "多模板批任务ID", required = true)
|
||||||
|
@GetMapping("/{id}")
|
||||||
|
public HttpResult<TestReportMultiVO> detail(@PathVariable("id") String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("detail");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
|
||||||
|
testReportMultiService.detail(id), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("查询子报告列表")
|
||||||
|
@ApiImplicitParam(name = "id", value = "多模板批任务ID", required = true)
|
||||||
|
@GetMapping("/{id}/child-report/list")
|
||||||
|
public HttpResult<List<TestReportVO>> listChildReports(@PathVariable("id") String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("listChildReports");
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
|
||||||
|
testReportMultiService.listChildReports(id), methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
|
||||||
|
@ApiOperation("下载多模板批任务结果压缩包")
|
||||||
|
@ApiImplicitParam(name = "id", value = "多模板批任务ID", required = true)
|
||||||
|
@GetMapping("/{id}/download")
|
||||||
|
public ResponseEntity<byte[]> download(@PathVariable("id") String id) {
|
||||||
|
return testReportMultiService.download(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
|
||||||
|
@ApiOperation("删除多模板批任务")
|
||||||
|
@PostMapping("/delete")
|
||||||
|
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
|
||||||
|
String methodDescribe = getMethodDescribe("delete");
|
||||||
|
boolean result = testReportMultiService.delete(ids);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
|
||||||
|
result, methodDescribe);
|
||||||
|
}
|
||||||
|
|
||||||
|
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
|
||||||
|
@ApiOperation("执行多模板批任务生成")
|
||||||
|
@ApiImplicitParam(name = "id", value = "多模板批任务ID", required = true)
|
||||||
|
@PostMapping("/{id}/generate")
|
||||||
|
public HttpResult<Boolean> generate(@PathVariable("id") String id) {
|
||||||
|
String methodDescribe = getMethodDescribe("generate");
|
||||||
|
boolean result = testReportMultiService.generate(id);
|
||||||
|
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
|
||||||
|
result, methodDescribe);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.mapper;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.param.TestReportMultiParam;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.po.TestReportMultiPO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiVO;
|
||||||
|
import org.apache.ibatis.annotations.Mapper;
|
||||||
|
import org.apache.ibatis.annotations.Param;
|
||||||
|
import org.apache.ibatis.annotations.Select;
|
||||||
|
|
||||||
|
@Mapper
|
||||||
|
public interface TestReportMultiMapper extends BaseMapper<TestReportMultiPO> {
|
||||||
|
|
||||||
|
@Select({
|
||||||
|
"<script>",
|
||||||
|
"select m.id, m.no, m.zip_file_name as zipFileName, m.summary_file_name as summaryFileName,",
|
||||||
|
"m.dir_count as dirCount, m.child_report_count as childReportCount, m.point_count as pointCount,",
|
||||||
|
"m.import_state as importState, m.generate_state as generateState, m.status,",
|
||||||
|
"m.create_by as createBy, creator.name as createByName, m.create_time as createTime,",
|
||||||
|
"m.update_by as updateBy, updater.name as updateByName, m.update_time as updateTime",
|
||||||
|
"from ai_testreport_multi m",
|
||||||
|
"left join sys_user creator on m.create_by = creator.id",
|
||||||
|
"left join sys_user updater on m.update_by = updater.id",
|
||||||
|
"where m.status = 1",
|
||||||
|
"<if test='param.keyword != null and param.keyword != \"\"'>",
|
||||||
|
"and (m.no like concat('%', #{param.keyword}, '%')",
|
||||||
|
"or m.zip_file_name like concat('%', #{param.keyword}, '%')",
|
||||||
|
"or m.summary_file_name like concat('%', #{param.keyword}, '%'))",
|
||||||
|
"</if>",
|
||||||
|
"<if test='param.searchBeginTime != null and param.searchBeginTime != \"\"'>",
|
||||||
|
"and m.create_time >= #{param.searchBeginTime}",
|
||||||
|
"</if>",
|
||||||
|
"<if test='param.searchEndTime != null and param.searchEndTime != \"\"'>",
|
||||||
|
"and m.create_time <= #{param.searchEndTime}",
|
||||||
|
"</if>",
|
||||||
|
"<if test='param.generateState != null'>",
|
||||||
|
"and m.generate_state = #{param.generateState}",
|
||||||
|
"</if>",
|
||||||
|
"order by m.create_time desc",
|
||||||
|
"</script>"
|
||||||
|
})
|
||||||
|
Page<TestReportMultiVO> selectPage(Page<TestReportMultiVO> page, @Param("param") TestReportMultiParam.QueryParam param);
|
||||||
|
|
||||||
|
@Select({
|
||||||
|
"select m.id, m.no, m.zip_file_name as zipFileName, m.summary_file_name as summaryFileName,",
|
||||||
|
"m.dir_count as dirCount, m.child_report_count as childReportCount, m.point_count as pointCount,",
|
||||||
|
"m.import_state as importState, m.generate_state as generateState, m.status,",
|
||||||
|
"m.create_by as createBy, creator.name as createByName, m.create_time as createTime,",
|
||||||
|
"m.update_by as updateBy, updater.name as updateByName, m.update_time as updateTime",
|
||||||
|
"from ai_testreport_multi m",
|
||||||
|
"left join sys_user creator on m.create_by = creator.id",
|
||||||
|
"left join sys_user updater on m.update_by = updater.id",
|
||||||
|
"where m.id = #{id} and m.status = 1"
|
||||||
|
})
|
||||||
|
TestReportMultiVO selectDetail(@Param("id") String id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.constant;
|
||||||
|
|
||||||
|
public final class TestReportMultiConst {
|
||||||
|
|
||||||
|
public static final Integer STATUS_DELETED = 0;
|
||||||
|
public static final Integer STATUS_NORMAL = 1;
|
||||||
|
|
||||||
|
public static final Integer IMPORT_STATE_PENDING = 0;
|
||||||
|
public static final Integer IMPORT_STATE_UPLOADED = 1;
|
||||||
|
public static final Integer IMPORT_STATE_VALIDATED = 2;
|
||||||
|
public static final Integer IMPORT_STATE_CREATED = 3;
|
||||||
|
public static final Integer IMPORT_STATE_FAILED = 4;
|
||||||
|
|
||||||
|
public static final Integer GENERATE_STATE_PENDING = 0;
|
||||||
|
public static final Integer GENERATE_STATE_PARTIAL_SUCCESS = 1;
|
||||||
|
public static final Integer GENERATE_STATE_ALL_SUCCESS = 2;
|
||||||
|
public static final Integer GENERATE_STATE_GENERATING = 3;
|
||||||
|
public static final Integer GENERATE_STATE_FAILED = 4;
|
||||||
|
|
||||||
|
public static final String STORAGE_MULTI_SESSION_DIR = "multi-session";
|
||||||
|
public static final String STORAGE_MULTI_TASK_DIR = "multi-task";
|
||||||
|
public static final String STORAGE_SESSION_ZIP_DIR = "zip";
|
||||||
|
public static final String STORAGE_SESSION_EXTRACT_DIR = "extract";
|
||||||
|
public static final String STORAGE_SESSION_META_FILE_NAME = "session-meta.json";
|
||||||
|
public static final String STORAGE_MULTI_ZIP_DIR = "zip";
|
||||||
|
public static final String STORAGE_MULTI_SUMMARY_DIR = "summary";
|
||||||
|
public static final String STORAGE_MULTI_CHILD_REPORT_DIR = "child-report";
|
||||||
|
|
||||||
|
private TestReportMultiConst() {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.param;
|
||||||
|
|
||||||
|
import com.njcn.web.pojo.param.BaseParam;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
|
import javax.validation.constraints.NotBlank;
|
||||||
|
import javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.Size;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TestReportMultiParam {
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
public static class QueryParam extends BaseParam {
|
||||||
|
@ApiModelProperty("关键字,匹配批任务编号、压缩包文件名或台账汇总文件名")
|
||||||
|
private String keyword;
|
||||||
|
|
||||||
|
@ApiModelProperty("创建开始时间,yyyy-MM-dd HH:mm:ss")
|
||||||
|
private String searchBeginTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("创建结束时间,yyyy-MM-dd HH:mm:ss")
|
||||||
|
private String searchEndTime;
|
||||||
|
|
||||||
|
@ApiModelProperty("整体生成状态")
|
||||||
|
private Integer generateState;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class CreateParam {
|
||||||
|
@ApiModelProperty("多模板批任务编号")
|
||||||
|
@NotBlank(message = "批任务编号不能为空")
|
||||||
|
@Size(max = 64, message = "批任务编号不能超过64个字符")
|
||||||
|
private String no;
|
||||||
|
|
||||||
|
@ApiModelProperty("校验通过的会话ID")
|
||||||
|
@NotBlank(message = "会话ID不能为空")
|
||||||
|
private String sessionId;
|
||||||
|
|
||||||
|
@ApiModelProperty("子报告配置列表")
|
||||||
|
@NotEmpty(message = "子报告配置不能为空")
|
||||||
|
private List<ChildReportItem> childReports;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class ChildReportItem {
|
||||||
|
@ApiModelProperty("压缩包一级模板目录名称")
|
||||||
|
@NotBlank(message = "模板目录名称不能为空")
|
||||||
|
@Size(max = 128, message = "模板目录名称不能超过128个字符")
|
||||||
|
private String templateDirName;
|
||||||
|
|
||||||
|
@ApiModelProperty("压缩包一级模板目录相对路径")
|
||||||
|
@NotBlank(message = "模板目录路径不能为空")
|
||||||
|
@Size(max = 255, message = "模板目录路径不能超过255个字符")
|
||||||
|
private String templateDirPath;
|
||||||
|
|
||||||
|
@ApiModelProperty("普测报告编号")
|
||||||
|
@NotBlank(message = "普测报告编号不能为空")
|
||||||
|
@Size(max = 32, message = "普测报告编号不能超过32个字符")
|
||||||
|
private String no;
|
||||||
|
|
||||||
|
@ApiModelProperty("委托单位ID")
|
||||||
|
@NotBlank(message = "委托单位不能为空")
|
||||||
|
private String clientUnitId;
|
||||||
|
|
||||||
|
@ApiModelProperty("报告模板ID")
|
||||||
|
@NotBlank(message = "报告模板不能为空")
|
||||||
|
private String reportModelId;
|
||||||
|
|
||||||
|
@ApiModelProperty("检测公司ID")
|
||||||
|
@NotBlank(message = "检测公司不能为空")
|
||||||
|
private String createUnit;
|
||||||
|
|
||||||
|
@ApiModelProperty("合同编号")
|
||||||
|
@NotBlank(message = "合同编号不能为空")
|
||||||
|
@Size(max = 32, message = "合同编号不能超过32个字符")
|
||||||
|
private String contractNumber;
|
||||||
|
|
||||||
|
@ApiModelProperty("检测依据ID列表")
|
||||||
|
@NotEmpty(message = "检测依据不能为空")
|
||||||
|
private List<String> standard;
|
||||||
|
|
||||||
|
@ApiModelProperty("联系电话")
|
||||||
|
@Size(max = 32, message = "联系电话不能超过32个字符")
|
||||||
|
private String phonenumber;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.po;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
|
import com.baomidou.mybatisplus.annotation.TableName;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@TableName("ai_testreport_multi")
|
||||||
|
public class TestReportMultiPO implements Serializable {
|
||||||
|
private static final long serialVersionUID = 1303351823037028556L;
|
||||||
|
|
||||||
|
@TableId("id")
|
||||||
|
private String id;
|
||||||
|
@TableField("no")
|
||||||
|
private String no;
|
||||||
|
@TableField("zip_file_name")
|
||||||
|
private String zipFileName;
|
||||||
|
@TableField("zip_storage_path")
|
||||||
|
private String zipStoragePath;
|
||||||
|
@TableField("summary_file_name")
|
||||||
|
private String summaryFileName;
|
||||||
|
@TableField("summary_storage_path")
|
||||||
|
private String summaryStoragePath;
|
||||||
|
@TableField("dir_count")
|
||||||
|
private Integer dirCount;
|
||||||
|
@TableField("child_report_count")
|
||||||
|
private Integer childReportCount;
|
||||||
|
@TableField("point_count")
|
||||||
|
private Integer pointCount;
|
||||||
|
@TableField("import_state")
|
||||||
|
private Integer importState;
|
||||||
|
@TableField("generate_state")
|
||||||
|
private Integer generateState;
|
||||||
|
@TableField("data")
|
||||||
|
private String data;
|
||||||
|
@TableField("status")
|
||||||
|
private Integer status;
|
||||||
|
@TableField("create_by")
|
||||||
|
private String createBy;
|
||||||
|
@TableField("create_time")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@TableField("update_by")
|
||||||
|
private String updateBy;
|
||||||
|
@TableField("update_time")
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TestReportMultiTemplateDirVO {
|
||||||
|
|
||||||
|
@ApiModelProperty("一级模板目录名称")
|
||||||
|
private String dirName;
|
||||||
|
@ApiModelProperty("一级模板目录相对路径")
|
||||||
|
private String dirPath;
|
||||||
|
@ApiModelProperty("监测点数")
|
||||||
|
private Integer pointCount;
|
||||||
|
@ApiModelProperty("分组数")
|
||||||
|
private Integer groupCount;
|
||||||
|
@ApiModelProperty("Excel附件数")
|
||||||
|
private Integer excelAttachmentCount;
|
||||||
|
@ApiModelProperty("Word附件数")
|
||||||
|
private Integer wordAttachmentCount;
|
||||||
|
}
|
||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.vo;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
|
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
|
||||||
|
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TestReportMultiVO {
|
||||||
|
private static final String DEFAULT_EMPTY_DISPLAY = "-";
|
||||||
|
|
||||||
|
@ApiModelProperty("多模板批任务ID")
|
||||||
|
private String id;
|
||||||
|
@ApiModelProperty("批任务编号")
|
||||||
|
private String no;
|
||||||
|
@ApiModelProperty("压缩包文件名")
|
||||||
|
private String zipFileName;
|
||||||
|
@ApiModelProperty("台账汇总文件名")
|
||||||
|
private String summaryFileName;
|
||||||
|
@ApiModelProperty("模板目录数量")
|
||||||
|
private Integer dirCount;
|
||||||
|
@ApiModelProperty("子报告数量")
|
||||||
|
private Integer childReportCount;
|
||||||
|
@ApiModelProperty("监测点总数")
|
||||||
|
private Integer pointCount;
|
||||||
|
@ApiModelProperty("导入状态")
|
||||||
|
private Integer importState;
|
||||||
|
@ApiModelProperty("整体生成状态")
|
||||||
|
private Integer generateState;
|
||||||
|
@ApiModelProperty("逻辑状态")
|
||||||
|
private Integer status;
|
||||||
|
@ApiModelProperty("创建人")
|
||||||
|
private String createBy;
|
||||||
|
@ApiModelProperty("创建人名称")
|
||||||
|
private String createByName;
|
||||||
|
@ApiModelProperty("创建时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
@ApiModelProperty("更新人")
|
||||||
|
private String updateBy;
|
||||||
|
@ApiModelProperty("更新人名称")
|
||||||
|
private String updateByName;
|
||||||
|
@ApiModelProperty("更新时间")
|
||||||
|
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||||
|
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
|
||||||
|
@JsonSerialize(using = LocalDateTimeSerializer.class)
|
||||||
|
private LocalDateTime updateTime;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return defaultDisplay(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNo() {
|
||||||
|
return defaultDisplay(no);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getZipFileName() {
|
||||||
|
return defaultDisplay(zipFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSummaryFileName() {
|
||||||
|
return defaultDisplay(summaryFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateBy() {
|
||||||
|
return defaultDisplay(createBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCreateByName() {
|
||||||
|
return defaultDisplay(createByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateBy() {
|
||||||
|
return defaultDisplay(updateBy);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUpdateByName() {
|
||||||
|
return defaultDisplay(updateByName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String defaultDisplay(String value) {
|
||||||
|
return StringUtils.hasText(value) ? value : DEFAULT_EMPTY_DISPLAY;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.pojo.vo;
|
||||||
|
|
||||||
|
import io.swagger.annotations.ApiModelProperty;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class TestReportMultiValidateResultVO {
|
||||||
|
|
||||||
|
@ApiModelProperty("校验是否通过")
|
||||||
|
private Boolean success = Boolean.FALSE;
|
||||||
|
@ApiModelProperty("校验通过的会话ID")
|
||||||
|
private String sessionId;
|
||||||
|
@ApiModelProperty("压缩包文件名")
|
||||||
|
private String zipFileName;
|
||||||
|
@ApiModelProperty("台账汇总文件名")
|
||||||
|
private String summaryFileName;
|
||||||
|
@ApiModelProperty("模板目录数量")
|
||||||
|
private Integer dirCount = 0;
|
||||||
|
@ApiModelProperty("子报告数量")
|
||||||
|
private Integer childReportCount = 0;
|
||||||
|
@ApiModelProperty("监测点总数")
|
||||||
|
private Integer pointCount = 0;
|
||||||
|
@ApiModelProperty("Excel附件总数")
|
||||||
|
private Integer excelAttachmentCount = 0;
|
||||||
|
@ApiModelProperty("Word附件总数")
|
||||||
|
private Integer wordAttachmentCount = 0;
|
||||||
|
@ApiModelProperty("孤儿附件相对路径")
|
||||||
|
private List<String> orphanAttachmentPaths = new ArrayList<String>();
|
||||||
|
@ApiModelProperty("失败原因")
|
||||||
|
private List<String> failReasons = new ArrayList<String>();
|
||||||
|
@ApiModelProperty("模板目录汇总")
|
||||||
|
private List<TestReportMultiTemplateDirVO> templateDirs = new ArrayList<TestReportMultiTemplateDirVO>();
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.njcn.gather.aireport.testreportmulti.service;
|
||||||
|
|
||||||
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
|
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.param.TestReportMultiParam;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.po.TestReportMultiPO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiVO;
|
||||||
|
import com.njcn.gather.aireport.testreportmulti.pojo.vo.TestReportMultiValidateResultVO;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface TestReportMultiService extends IService<TestReportMultiPO> {
|
||||||
|
|
||||||
|
Page<TestReportMultiVO> list(TestReportMultiParam.QueryParam param);
|
||||||
|
|
||||||
|
TestReportMultiValidateResultVO validateZip(MultipartFile file);
|
||||||
|
|
||||||
|
boolean create(TestReportMultiParam.CreateParam param);
|
||||||
|
|
||||||
|
TestReportMultiVO detail(String id);
|
||||||
|
|
||||||
|
List<TestReportVO> listChildReports(String id);
|
||||||
|
|
||||||
|
ResponseEntity<byte[]> download(String id);
|
||||||
|
|
||||||
|
boolean delete(List<String> ids);
|
||||||
|
|
||||||
|
boolean generate(String id);
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
37
tmp/2026-7-21-多模板普测报告生成_改造方案.sql
Normal file
37
tmp/2026-7-21-多模板普测报告生成_改造方案.sql
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
SET NAMES utf8mb4;
|
||||||
|
|
||||||
|
CREATE TABLE `ai_testreport_multi` (
|
||||||
|
`id` varchar(36) NOT NULL COMMENT '多模板普测任务ID(GUID)',
|
||||||
|
`no` varchar(64) NOT NULL COMMENT '批任务编号',
|
||||||
|
`zip_file_name` varchar(255) NOT NULL COMMENT '上传压缩包文件名',
|
||||||
|
`zip_storage_path` varchar(500) NOT NULL COMMENT '上传压缩包存储路径',
|
||||||
|
`summary_file_name` varchar(255) NOT NULL COMMENT '台账汇总文件名',
|
||||||
|
`summary_storage_path` varchar(500) NOT NULL COMMENT '台账汇总存储路径',
|
||||||
|
`dir_count` int NOT NULL DEFAULT 0 COMMENT '模板目录数',
|
||||||
|
`child_report_count` int NOT NULL DEFAULT 0 COMMENT '子报告数',
|
||||||
|
`point_count` int NOT NULL DEFAULT 0 COMMENT '监测点总数',
|
||||||
|
`import_state` tinyint(1) NOT NULL DEFAULT 0 COMMENT '导入状态:0待上传 1已上传待解析 2解析通过待创建 3已创建 4创建失败',
|
||||||
|
`generate_state` tinyint(1) NOT NULL DEFAULT 0 COMMENT '整体生成状态:0未生成 1部分生成成功 2全部生成成功 3生成中 4生成失败',
|
||||||
|
`data` json DEFAULT NULL COMMENT '批任务快照数据',
|
||||||
|
`status` tinyint(1) NOT NULL COMMENT '状态(0:删除 1:正常)',
|
||||||
|
`create_by` varchar(32) DEFAULT NULL COMMENT '创建人',
|
||||||
|
`create_time` datetime NOT NULL COMMENT '创建时间',
|
||||||
|
`update_by` varchar(32) DEFAULT NULL COMMENT '更新人',
|
||||||
|
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
|
||||||
|
PRIMARY KEY (`id`),
|
||||||
|
INDEX `idx_ai_testreport_multi_no` (`no`),
|
||||||
|
INDEX `idx_ai_testreport_multi_create_time` (`create_time`)
|
||||||
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='多模板普测任务表';
|
||||||
|
|
||||||
|
ALTER TABLE `ai_testreport`
|
||||||
|
ADD COLUMN `multi_id` varchar(36) DEFAULT NULL COMMENT '所属多模板普测任务ID(GUID)' AFTER `id`,
|
||||||
|
ADD COLUMN `template_dir_name` varchar(128) DEFAULT NULL COMMENT '压缩包一级模板目录名称' AFTER `report_model_id`,
|
||||||
|
ADD COLUMN `template_dir_path` varchar(255) DEFAULT NULL COMMENT '压缩包一级模板目录相对路径' AFTER `template_dir_name`,
|
||||||
|
ADD COLUMN `sort_no` int NOT NULL DEFAULT 0 COMMENT '父任务内排序号' AFTER `template_dir_path`,
|
||||||
|
ADD INDEX `idx_ai_testreport_multi_id` (`multi_id`);
|
||||||
|
|
||||||
|
ALTER TABLE `ai_testreport_point`
|
||||||
|
ADD COLUMN `excel_attachment_relative_path` varchar(500) DEFAULT NULL COMMENT 'Excel附件相对路径' AFTER `excel_attachment_name`,
|
||||||
|
ADD COLUMN `word_attachment_name` varchar(255) DEFAULT NULL COMMENT 'Word附件文件名' AFTER `excel_storage_path`,
|
||||||
|
ADD COLUMN `word_attachment_relative_path` varchar(500) DEFAULT NULL COMMENT 'Word附件相对路径' AFTER `word_attachment_name`,
|
||||||
|
ADD COLUMN `word_storage_path` varchar(500) DEFAULT NULL COMMENT 'Word附件存储路径' AFTER `word_attachment_relative_path`;
|
||||||
353
tmp/cn_tool.sql
353
tmp/cn_tool.sql
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user