feat:算例下编辑报告
This commit is contained in:
2
1-sql/2026-01-13/simulation_run.sql
Normal file
2
1-sql/2026-01-13/simulation_run.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE spdm_baseline.simulation_run ADD reportTemplate varchar(120) NULL COMMENT '报告模板uuid';
|
||||
ALTER TABLE spdm_baseline.simulation_run ADD reportContent MEDIUMTEXT NULL COMMENT '报告模板填充内容';
|
||||
@@ -1,22 +1,12 @@
|
||||
package com.sdm.capability.controller;
|
||||
|
||||
import com.sdm.capability.model.dto.ReportTemplateDto;
|
||||
import com.sdm.capability.model.entity.SimulationFlowTemplate;
|
||||
import com.sdm.capability.model.entity.SimulationReportTemplate;
|
||||
import com.sdm.capability.model.req.flow.GetFlowTemplateReq;
|
||||
import com.sdm.capability.model.req.flow.ReleaseFlowTemplateReq;
|
||||
import com.sdm.capability.service.IFlowService;
|
||||
import com.sdm.capability.service.ISimulationFlowNodeService;
|
||||
import com.sdm.capability.service.ISimulationReportTemplateService;
|
||||
import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.capability.FlowNodeDto;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.capability.FlowTemplateResp;
|
||||
import com.sdm.common.feign.inter.capability.ISimulationFlowFeignClient;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
import com.sdm.common.feign.inter.capability.ISimulationReportFeignClient;
|
||||
import com.sdm.common.log.annotation.SysLog;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.validation.annotation.Validated;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -106,7 +96,7 @@ public class ReportTemplateController implements ISimulationReportFeignClient {
|
||||
* @return
|
||||
*/
|
||||
@GetMapping("/queryReportTemplateInfo")
|
||||
public SdmResponse queryReportTemplateInfo(@RequestParam("uuid") String uuid) {
|
||||
public SdmResponse<ReportTemplateResp> queryReportTemplateInfo(@RequestParam("uuid") String uuid) {
|
||||
return reportTemplateService.queryReportTemplateInfo(uuid);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import com.sdm.capability.model.dto.ReportTemplateDto;
|
||||
import com.sdm.capability.model.entity.SimulationReportTemplate;
|
||||
import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
|
||||
public interface ISimulationReportTemplateService extends IService<SimulationReportTemplate> {
|
||||
|
||||
@@ -24,7 +25,7 @@ public interface ISimulationReportTemplateService extends IService<SimulationRep
|
||||
|
||||
SdmResponse turnOnOffReportTemplate(String uuid, int status);
|
||||
|
||||
SdmResponse queryReportTemplateInfo(String uuid);
|
||||
SdmResponse<ReportTemplateResp> queryReportTemplateInfo(String uuid);
|
||||
|
||||
SdmResponse handleApproveResult(LaunchApproveReq req);
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.sdm.common.entity.req.data.GetFileBaseInfoReq;
|
||||
import com.sdm.common.entity.req.data.UpdateScriptAndReportReq;
|
||||
import com.sdm.common.entity.req.data.UploadFilesReq;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
|
||||
import com.sdm.common.feign.impl.data.DataClientFeignClientImpl;
|
||||
import com.sdm.common.feign.impl.system.ApproveFeignClientImpl;
|
||||
@@ -269,9 +270,11 @@ public class SimulationReportTemplateServiceImpl extends ServiceImpl<SimulationR
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdmResponse queryReportTemplateInfo(String uuid) {
|
||||
public SdmResponse<ReportTemplateResp> queryReportTemplateInfo(String uuid) {
|
||||
SimulationReportTemplate reportTemplate = this.lambdaQuery().eq(SimulationReportTemplate::getUuid, uuid).one();
|
||||
return SdmResponse.success(reportTemplate);
|
||||
ReportTemplateResp resp = new ReportTemplateResp();
|
||||
resp.setTemplateName(reportTemplate.getTemplateName());
|
||||
return SdmResponse.success(resp);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -282,7 +285,7 @@ public class SimulationReportTemplateServiceImpl extends ServiceImpl<SimulationR
|
||||
JSONObject contentObj = JSONObject.parseObject(approveContent);
|
||||
if (contentObj != null && contentObj.containsKey("reportUuid")) {
|
||||
String reportTemplateUuid = contentObj.getString("reportUuid");
|
||||
SimulationReportTemplate reportTemplate = (SimulationReportTemplate) this.queryReportTemplateInfo(reportTemplateUuid).getData();
|
||||
SimulationReportTemplate reportTemplate = this.lambdaQuery().eq(SimulationReportTemplate::getUuid, reportTemplateUuid).one();
|
||||
if (reportTemplate != null) {
|
||||
// 审批通过
|
||||
if (NumberConstants.TWO == approveStatus) {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.sdm.common.entity.req.project;
|
||||
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public class EditReportReq {
|
||||
|
||||
private String runId;
|
||||
|
||||
private String reportTemplate;
|
||||
|
||||
private String reportContent;
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.sdm.common.entity.resp.capability;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Data
|
||||
public class ReportTemplateResp {
|
||||
|
||||
@Schema(description = "报告模版唯一ID")
|
||||
private String uuid;
|
||||
|
||||
@Schema(description = "报告模版编码")
|
||||
private String templateCode;
|
||||
|
||||
@Schema(description = "报告模版名称")
|
||||
private String templateName;
|
||||
|
||||
@Schema(description = "报告模版版本")
|
||||
private String templateVersion;
|
||||
|
||||
@Schema(description = "报告模版内容")
|
||||
private String templateContent;
|
||||
|
||||
@Schema(description = "报告模板绑定文件ID")
|
||||
private Long fileId;
|
||||
|
||||
@Schema(description = "报告模版状态 -1:草稿 0:禁用 1:启用")
|
||||
private Integer templateStatus;
|
||||
|
||||
@Schema(description = "报告模版类型(分析方向)")
|
||||
private String templateType;
|
||||
|
||||
@Schema(description = "报告模版审批状态 0:未审批 1:审批中 2:审批通过 3:审批未通过")
|
||||
private Integer approveType;
|
||||
|
||||
@Schema(description = "报告模版评审流ID")
|
||||
private String approveFlowId;
|
||||
|
||||
@Schema(description = "报告模版描述信息")
|
||||
private String comment;
|
||||
|
||||
@Schema(description = "租户ID")
|
||||
private Long tenantId;
|
||||
|
||||
@Schema(description = "更新时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime updateTime;
|
||||
|
||||
@Schema(description = "模版创建者ID")
|
||||
private Long creator;
|
||||
|
||||
@Schema(description= "创建者名称,列表展示使用")
|
||||
private String creatorName;
|
||||
|
||||
@Schema(description = "模版创建时间")
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime createTime;
|
||||
|
||||
@Schema(description = "模版更新人ID")
|
||||
private Long updater;
|
||||
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.sdm.common.feign.impl.capability;
|
||||
|
||||
import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
import com.sdm.common.feign.inter.capability.ISimulationReportFeignClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -30,4 +31,19 @@ public class SimulationReportFeignClientImpl implements ISimulationReportFeignCl
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdmResponse<ReportTemplateResp> queryReportTemplateInfo(String uuid) {
|
||||
SdmResponse response;
|
||||
try {
|
||||
response = reportFeignClient.queryReportTemplateInfo(uuid);
|
||||
if (!response.isSuccess()) {
|
||||
return SdmResponse.failed("查询报告模板失败");
|
||||
}
|
||||
return response;
|
||||
} catch (Exception e) {
|
||||
log.error("查询报告模板失败", e);
|
||||
return SdmResponse.failed("查询报告模板失败");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -2,9 +2,12 @@ package com.sdm.common.feign.inter.capability;
|
||||
|
||||
import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
|
||||
|
||||
@FeignClient(name = "capability",contextId = "reportTemplateFeignClient")
|
||||
@@ -13,4 +16,7 @@ public interface ISimulationReportFeignClient {
|
||||
@PostMapping("/report/approveHandleNotice")
|
||||
SdmResponse receiveApproveNotice(@RequestBody LaunchApproveReq req);
|
||||
|
||||
@GetMapping("/report/queryReportTemplateInfo")
|
||||
SdmResponse<ReportTemplateResp> queryReportTemplateInfo(@RequestParam("uuid") String uuid);
|
||||
|
||||
}
|
||||
|
||||
393
project/modifyReport.py
Normal file
393
project/modifyReport.py
Normal file
@@ -0,0 +1,393 @@
|
||||
"""根据前端传来的json数据,修改模板文件,生成新的word文档"""
|
||||
|
||||
from docx.oxml.ns import qn
|
||||
from docx.shared import Pt
|
||||
from docx.enum.text import WD_ALIGN_PARAGRAPH
|
||||
import os
|
||||
import sys
|
||||
import traceback
|
||||
from datetime import datetime
|
||||
import json
|
||||
import shutil
|
||||
from lxml import etree
|
||||
import base64
|
||||
from docx import Document
|
||||
from docx.shared import RGBColor
|
||||
import re
|
||||
from docx.enum.table import WD_ALIGN_VERTICAL
|
||||
|
||||
|
||||
def getJsonData(jsonFilePath):
|
||||
"""获取JSON数据"""
|
||||
if not os.path.exists(jsonFilePath):
|
||||
print(f"Error:JSON文件路径 '{jsonFilePath}' 不存在,请确认")
|
||||
return None
|
||||
|
||||
with open(jsonFilePath, 'r', encoding='utf-8') as jsonFile:
|
||||
data = json.load(jsonFile)
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class Tee:
|
||||
"""自定义文件对象,同时写入文件和原控制台"""
|
||||
|
||||
def __init__(self, *files):
|
||||
self.files = files
|
||||
|
||||
def write(self, obj):
|
||||
for f in self.files:
|
||||
f.write(obj)
|
||||
f.flush() # 确保立即写入
|
||||
|
||||
def flush(self):
|
||||
for f in self.files:
|
||||
f.flush()
|
||||
|
||||
|
||||
def replace_text_in_textbox(doc, data_dict):
|
||||
"""替换文本框中的文本"""
|
||||
namespaces = {
|
||||
'w': 'http://schemas.openxmlformats.org/wordprocessingml/2006/main',
|
||||
'wp': 'http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing',
|
||||
'a': 'http://schemas.openxmlformats.org/drawingml/2006/main',
|
||||
'wps': 'http://schemas.microsoft.com/office/word/2010/wordprocessingShape'
|
||||
}
|
||||
|
||||
doc_modified = False # 整个文档是否被修改
|
||||
for part_id, related_part in doc.part.related_parts.items():
|
||||
if hasattr(related_part, 'blob'):
|
||||
xml_content = related_part.blob
|
||||
# 检查是否是 XML 内容(以 <?xml 或 < 开头)
|
||||
if not xml_content.strip().startswith(b'<?xml') and not xml_content.strip().startswith(b'<'):
|
||||
continue
|
||||
try:
|
||||
try:
|
||||
content_str = xml_content.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
|
||||
# 检查是否包含 XML 声明或根元素
|
||||
if not content_str.strip().startswith('<?xml') and not content_str.strip().startswith('<'):
|
||||
continue
|
||||
|
||||
root = etree.fromstring(xml_content)
|
||||
# 查找文本框
|
||||
textboxes = root.xpath('//wps:txbx', namespaces=namespaces)
|
||||
part_modified = False # 当前部件是否被修改
|
||||
for textbox in textboxes:
|
||||
# 提取文本内容
|
||||
text_elements = textbox.xpath(
|
||||
'.//w:t', namespaces=namespaces)
|
||||
for elem in text_elements:
|
||||
# print(f"原始文本: {elem.text}")
|
||||
if elem.text and "$" in elem.text:
|
||||
keyName = elem.text.split("$")[1]
|
||||
value = data_dict.get(keyName, None)
|
||||
if value is not None:
|
||||
elem.text = elem.text.replace(
|
||||
f"${keyName}", str(value))
|
||||
print(f"文本框替换: ${keyName} -> {value}")
|
||||
part_modified = True
|
||||
doc_modified = True
|
||||
|
||||
# 关键步骤:将修改后的 XML 写回到部件中
|
||||
if part_modified:
|
||||
# 同时更新 _element 和 _blob
|
||||
if hasattr(related_part, '_element'):
|
||||
related_part._element = root
|
||||
updated_xml = etree.tostring(
|
||||
root, encoding='UTF-8', xml_declaration=True)
|
||||
related_part._blob = updated_xml
|
||||
|
||||
except etree.XMLSyntaxError as e:
|
||||
print(f"Warning:部件 {part_id} 不是有效的 XML: {e}")
|
||||
continue
|
||||
except Exception as e:
|
||||
print(f"Warning:处理部件 {part_id} 时出错: {e}")
|
||||
continue
|
||||
|
||||
|
||||
def replace_text_in_paragraph(paragraph, data_dict, csvPath=None):
|
||||
"""新增段落中的文本"""
|
||||
text = paragraph.text
|
||||
# 居中显示
|
||||
# paragraph.alignment = 1
|
||||
ngFlag = False
|
||||
if "$" in text and "<" not in text and ">" not in text:
|
||||
keys_list = re.findall(r'\$([a-zA-Z0-9_]+)', text)
|
||||
for keyName in keys_list:
|
||||
value = data_dict.get(keyName, None)
|
||||
if value is not None:
|
||||
placeholder = f"${keyName}"
|
||||
text = text.replace(placeholder, str(value))
|
||||
print(f"文本: {placeholder} -> {value}")
|
||||
paragraph.text = text
|
||||
|
||||
|
||||
def add_text_in_table(paragraph, data_dict):
|
||||
"""新增表格中的文本"""
|
||||
text = paragraph.text
|
||||
if "$" in text and "<" not in text and ">" not in text:
|
||||
keys_list = re.findall(r'\$([a-zA-Z0-9_]+)', text)
|
||||
for keyName in keys_list:
|
||||
value = data_dict.get(keyName, None)
|
||||
if value is not None:
|
||||
placeholder = f"${keyName}"
|
||||
text = text.replace(placeholder, str(value))
|
||||
print(f"文本: {placeholder} -> {value}")
|
||||
paragraph.text = text
|
||||
|
||||
|
||||
def get_reference_style(table):
|
||||
"""从表格第一行提取样式特征"""
|
||||
style = {
|
||||
'font_name': '宋体',
|
||||
'font_size': Pt(10.5),
|
||||
'alignment': WD_ALIGN_PARAGRAPH.CENTER,
|
||||
'vertical': WD_ALIGN_VERTICAL.CENTER
|
||||
}
|
||||
try:
|
||||
if len(table.rows) > 0:
|
||||
# 尝试从第一行第一个单元格获取字体信息
|
||||
cell = table.rows[0].cells[0]
|
||||
style['vertical'] = cell.vertical_alignment or WD_ALIGN_VERTICAL.CENTER
|
||||
if cell.paragraphs and cell.paragraphs[0].runs:
|
||||
run = cell.paragraphs[0].runs[0]
|
||||
if run.font.name:
|
||||
style['font_name'] = run.font.name
|
||||
if run.font.size:
|
||||
style['font_size'] = run.font.size
|
||||
style['alignment'] = cell.paragraphs[0].alignment or WD_ALIGN_PARAGRAPH.CENTER
|
||||
except Exception:
|
||||
pass
|
||||
return style
|
||||
|
||||
|
||||
def apply_style_to_cell(cell, text, style):
|
||||
"""将提取或默认的样式应用到单元格"""
|
||||
cell.text = str(text)
|
||||
cell.vertical_alignment = style['vertical']
|
||||
for paragraph in cell.paragraphs:
|
||||
paragraph.alignment = style['alignment']
|
||||
for run in paragraph.runs:
|
||||
run.font.name = style['font_name']
|
||||
run.font.size = style['font_size']
|
||||
# 确保中文字体兼容性
|
||||
run._element.rPr.rFonts.set(qn('w:eastAsia'), style['font_name'])
|
||||
|
||||
|
||||
def replace_text_in_table(table, data_dict, tableFlag=False, count_wrapper=[0]):
|
||||
"""
|
||||
替换表格中的文本
|
||||
count_wrapper: 传入一个列表,例如 [2],表示剩余可执行次数
|
||||
"""
|
||||
for row in table.rows:
|
||||
headers = [cell.text for cell in row.cells]
|
||||
if headers:
|
||||
header_str = ",".join(headers)
|
||||
if header_str in data_dict and not tableFlag and count_wrapper[0] > 0:
|
||||
print(f"================表头匹配到数据: {header_str}")
|
||||
table_data = data_dict[header_str]
|
||||
|
||||
# 在表格末尾添加数据行
|
||||
# 提取原表格样式
|
||||
ref_style = get_reference_style(table)
|
||||
for data_row in table_data:
|
||||
new_row = table.add_row()
|
||||
# 尝试继承原行高
|
||||
if len(table.rows) > 1:
|
||||
new_row.height = table.rows[0].height
|
||||
for idx, header in enumerate(headers):
|
||||
# content = data_row.get(header, "")
|
||||
# apply_style_to_cell(
|
||||
# new_row.cells[idx], content, ref_style)
|
||||
key_list = list(data_row.keys())
|
||||
new_row.cells[idx].text = str(data_row[key_list[idx]])
|
||||
# 根据内容长短,自适应水平居中显示 不换行
|
||||
# for paragraph in new_row.cells[idx].paragraphs:
|
||||
# paragraph.vertical = WD_ALIGN_VERTICAL.CENTER
|
||||
# paragraph.alignment = WD_ALIGN_PARAGRAPH.CENTER
|
||||
|
||||
print(
|
||||
f"表格新增行内容: {[str(data_row.get(header, '')) for header in headers]}")
|
||||
tableFlag = True
|
||||
count_wrapper[0] -= 1
|
||||
|
||||
if not tableFlag:
|
||||
for cell in row.cells:
|
||||
# 递归处理单元格中的嵌套表格
|
||||
if cell.tables:
|
||||
for nested_table in cell.tables:
|
||||
replace_text_in_table(
|
||||
nested_table, data_dict, tableFlag=tableFlag, count_wrapper=count_wrapper)
|
||||
|
||||
# 新增表格内容
|
||||
for paragraph in cell.paragraphs:
|
||||
replace_text_in_paragraph(paragraph, data_dict)
|
||||
|
||||
|
||||
def getDataDict(reportContent_list):
|
||||
"""获取数据字典 用于替换文本"""
|
||||
data_dict = {}
|
||||
table_num = 0
|
||||
for item_dict in reportContent_list:
|
||||
if item_dict["type"] == "text":
|
||||
data_dict[item_dict["key"]] = item_dict["value"]
|
||||
elif item_dict["type"] == "table":
|
||||
tableContent = item_dict.get("value", [])
|
||||
# 获取表头
|
||||
headers = []
|
||||
if tableContent:
|
||||
headers = list(tableContent[0].keys())
|
||||
if headers:
|
||||
header_str = ",".join(headers)
|
||||
# newKey = item_dict["key"] + "_" + header_str
|
||||
newKey = header_str
|
||||
data_dict[newKey] = tableContent
|
||||
table_num += 1
|
||||
elif item_dict["type"] == "conclusion":
|
||||
data_dict[item_dict["key"]] = item_dict["value"]
|
||||
return data_dict, table_num
|
||||
|
||||
|
||||
def base64ToImg(projectPath, reportContent_list):
|
||||
"""把base64字符串转成图片"""
|
||||
img_num = 0
|
||||
for item_dict in reportContent_list:
|
||||
if item_dict["type"] == "img":
|
||||
img_base64_list = item_dict["value"]
|
||||
img_base64_str = img_base64_list[0].get("src", "")
|
||||
img_base64 = img_base64_str.split(",")[1]
|
||||
img_data = base64.b64decode(img_base64)
|
||||
img_path = f"{projectPath}/{item_dict['key']}.png"
|
||||
img_num += 1
|
||||
with open(img_path, 'wb') as img_file:
|
||||
img_file.write(img_data)
|
||||
# 新增字典中的picPath为图片路径
|
||||
# item_dict["picPath"] = img_path
|
||||
return img_num
|
||||
|
||||
|
||||
def modiyReport(projectPath, reportContent_list, outputDocxPath):
|
||||
"""根据JSON数据修改报告文件"""
|
||||
doc = Document(outputDocxPath)
|
||||
|
||||
# 把base64转成图片
|
||||
img_num = base64ToImg(projectPath, reportContent_list)
|
||||
print(f"Info:共转换{img_num}张图片")
|
||||
# 图片与模版中图片名称的对应关系 模版从2开始 对应图片1.png 2.png ...
|
||||
image_dict = {
|
||||
f"image{i}.png": f"pic{i-1}.png" for i in range(2, img_num+2)}
|
||||
# image_dict = {"image2.png": "pic1.png",
|
||||
# "image3.png": "pic2.png",
|
||||
# "image4.png": "pic3.png",
|
||||
# "image5.png": "pic4.png",
|
||||
# "image6.png": "pic5.png",
|
||||
# }
|
||||
|
||||
# 获取数据字典 用于替换文本
|
||||
data_dict, table_num = getDataDict(reportContent_list)
|
||||
|
||||
# 处理页眉
|
||||
for section in doc.sections:
|
||||
if section.header:
|
||||
for paragraph in section.header.paragraphs:
|
||||
replace_text_in_paragraph(paragraph, data_dict)
|
||||
# 处理页眉中的表格
|
||||
for table in section.header.tables:
|
||||
replace_text_in_table(table, data_dict)
|
||||
|
||||
# 替换图片
|
||||
for rel in doc.part.rels.values():
|
||||
if "image" in rel.reltype:
|
||||
# print(rel.target_ref, rel.reltype)
|
||||
# media/image3.png
|
||||
imageItem = rel.target_ref.split("/")[-1]
|
||||
picName = image_dict.get(imageItem, None)
|
||||
if picName is not None and picName != ".png":
|
||||
replace_image_path = os.path.join(projectPath, picName)
|
||||
if os.path.exists(replace_image_path):
|
||||
print(f"图片: {imageItem} -> {picName}")
|
||||
rel.target_part._blob = open(
|
||||
replace_image_path, "rb").read()
|
||||
|
||||
# 处理表格
|
||||
remaining_count = [table_num]
|
||||
for table in doc.tables:
|
||||
replace_text_in_table(
|
||||
table, data_dict, tableFlag=False, count_wrapper=remaining_count)
|
||||
|
||||
# 处理结论
|
||||
# replaceConclusion(doc, data_dict)
|
||||
|
||||
# 处理文本框
|
||||
replace_text_in_textbox(doc, data_dict)
|
||||
|
||||
# 保存文档
|
||||
doc.save(outputDocxPath)
|
||||
print(f"Info:Word文档已创建: {outputDocxPath}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
projectPath = sys.argv[1]
|
||||
templatePath = sys.argv[2]
|
||||
# pythonLogPath = "/opt/report"
|
||||
|
||||
# currentPath = os.getcwd()
|
||||
# projectPath = f"{currentPath}/project/modiyTemp"
|
||||
# templatePath = f"{currentPath}/Test/report_tem.docx"
|
||||
pythonLogPath = os.path.dirname(projectPath)
|
||||
|
||||
# 输出log日志
|
||||
# 日志文件路径
|
||||
log_fileDir = f"{pythonLogPath}/pythonLog"
|
||||
if not os.path.exists(log_fileDir):
|
||||
os.makedirs(log_fileDir)
|
||||
# log文件用日期命名
|
||||
log_file = f"{log_fileDir}/{datetime.now().strftime('%Y%m%d')}.log"
|
||||
with open(log_file, 'a', encoding='utf-8') as log_file_obj:
|
||||
# 重定向标准输出和标准错误
|
||||
original_stdout = sys.stdout
|
||||
original_stderr = sys.stderr
|
||||
|
||||
# 创建Tee对象,同时输出到文件和原控制台
|
||||
tee = Tee(log_file_obj, original_stdout)
|
||||
sys.stdout = tee
|
||||
sys.stderr = tee
|
||||
|
||||
# 在日志开头添加时间戳
|
||||
print("=" * 60)
|
||||
print(f"程序开始时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print(f"日志文件: {log_file}")
|
||||
print("=" * 60)
|
||||
|
||||
try:
|
||||
# 获取报告内容JSON数据
|
||||
reportContentPath = f"{projectPath}/reportContent.json"
|
||||
reportContent_list = getJsonData(reportContentPath)
|
||||
|
||||
# 拷贝模板文件到项目目录下
|
||||
outputDocxPath = f"{projectPath}/report_{datetime.now().strftime('%Y%m%d')}.docx"
|
||||
shutil.copy(templatePath, outputDocxPath)
|
||||
|
||||
# 修改报告文件
|
||||
modiyReport(projectPath, reportContent_list, outputDocxPath)
|
||||
|
||||
print("\n" + "=" * 60)
|
||||
print(f"程序结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("程序执行成功")
|
||||
print("=" * 60)
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n程序执行出错: {str(e)}", file=sys.stderr)
|
||||
traceback.print_exc()
|
||||
print("=" * 60)
|
||||
print(f"程序异常结束时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||||
print("=" * 60)
|
||||
raise
|
||||
finally:
|
||||
# 恢复标准输出
|
||||
sys.stdout = original_stdout
|
||||
sys.stderr = original_stderr
|
||||
@@ -4,6 +4,7 @@ import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.data.CreateDirReq;
|
||||
import com.sdm.common.entity.req.data.QueryDirReq;
|
||||
import com.sdm.common.entity.req.data.UploadFilesReq;
|
||||
import com.sdm.common.entity.req.project.EditReportReq;
|
||||
import com.sdm.common.entity.req.project.SpdmReportReq;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.PageDataResp;
|
||||
@@ -220,6 +221,17 @@ public class SimulationRunController implements ISimulationRunFeignClient {
|
||||
runService.generateReport(req,response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑报告模板生成报告
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
@PostMapping("/editReport")
|
||||
@Operation(summary = "编辑报告模板生成报告", description = "编辑报告模板生成报告")
|
||||
public void editReport(@RequestBody EditReportReq req, HttpServletResponse response) {
|
||||
runService.editReport(req,response);
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部调用生成报告
|
||||
*
|
||||
|
||||
@@ -124,4 +124,12 @@ public class SimulationRun implements Serializable {
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
private LocalDateTime finishTime;
|
||||
|
||||
@Schema(description= "报告模板uuid")
|
||||
@TableField("reportTemplate")
|
||||
private String reportTemplate;
|
||||
|
||||
@Schema(description= "报告模板填充内容")
|
||||
@TableField("reportContent")
|
||||
private String reportContent;
|
||||
|
||||
}
|
||||
|
||||
@@ -32,4 +32,10 @@ public class SpdmAddTaskRunReq {
|
||||
|
||||
private String isPersonalTemplate;
|
||||
|
||||
@Schema(description= "报告模板uuid")
|
||||
private String reportTemplate;
|
||||
|
||||
@Schema(description= "报告模板填充内容")
|
||||
private String reportContent;
|
||||
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.sdm.common.common.SdmResponse;
|
||||
import com.sdm.common.entity.req.data.CreateDirReq;
|
||||
import com.sdm.common.entity.req.data.QueryDirReq;
|
||||
import com.sdm.common.entity.req.data.UploadFilesReq;
|
||||
import com.sdm.common.entity.req.project.EditReportReq;
|
||||
import com.sdm.common.entity.req.project.SpdmReportReq;
|
||||
import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.resp.PageDataResp;
|
||||
@@ -67,6 +68,9 @@ public interface ISimulationRunService extends IService<SimulationRun> {
|
||||
|
||||
void generateReport(SpdmReportReq req, HttpServletResponse response);
|
||||
|
||||
void editReport(EditReportReq req, HttpServletResponse response);
|
||||
|
||||
|
||||
/**
|
||||
* 内部调用生成报告
|
||||
* @param req 报告请求参数
|
||||
|
||||
@@ -19,6 +19,7 @@ import com.sdm.common.entity.flowable.dto.FlowElementDTO;
|
||||
import com.sdm.common.entity.flowable.dto.ProcessDefinitionDTO;
|
||||
import com.sdm.common.entity.req.capability.FlowNodeDto;
|
||||
import com.sdm.common.entity.req.data.*;
|
||||
import com.sdm.common.entity.req.project.EditReportReq;
|
||||
import com.sdm.common.entity.req.project.ProjecInfoReq;
|
||||
import com.sdm.common.entity.req.project.SimulationPerformance;
|
||||
import com.sdm.common.entity.req.project.SpdmReportReq;
|
||||
@@ -26,6 +27,7 @@ import com.sdm.common.entity.req.system.LaunchApproveReq;
|
||||
import com.sdm.common.entity.req.system.UserQueryReq;
|
||||
import com.sdm.common.entity.resp.PageDataResp;
|
||||
import com.sdm.common.entity.resp.capability.FlowTemplateResp;
|
||||
import com.sdm.common.entity.resp.capability.ReportTemplateResp;
|
||||
import com.sdm.common.entity.resp.data.BatchAddFileInfoResp;
|
||||
import com.sdm.common.entity.resp.data.FileMetadataInfoResp;
|
||||
import com.sdm.common.entity.resp.data.SimulationTaskResultCurveResp;
|
||||
@@ -33,6 +35,7 @@ import com.sdm.common.entity.resp.flowable.ProcessInstanceDetailResponse;
|
||||
import com.sdm.common.entity.resp.flowable.ProcessInstanceResp;
|
||||
import com.sdm.common.entity.resp.system.CIDUserResp;
|
||||
import com.sdm.common.feign.impl.capability.SimulationFlowFeignClientImpl;
|
||||
import com.sdm.common.feign.impl.capability.SimulationReportFeignClientImpl;
|
||||
import com.sdm.common.feign.impl.data.DataAnalysisFeignClientImpl;
|
||||
import com.sdm.common.feign.impl.data.DataClientFeignClientImpl;
|
||||
import com.sdm.common.feign.impl.flowable.FlowableClientFeignClientImpl;
|
||||
@@ -76,6 +79,8 @@ import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -140,9 +145,14 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
|
||||
@Autowired
|
||||
private DataFileService dataFileService;
|
||||
|
||||
@Autowired
|
||||
private SimulationReportFeignClientImpl reportFeignClient;
|
||||
|
||||
private final ObjectMapper objectMapper = new ObjectMapper();
|
||||
|
||||
private static final String TEMP_REPORT_PATH = "/opt/report/";
|
||||
// private static final String TEMP_REPORT_PATH = System.getProperty("user.dir") + File.separator + "project";
|
||||
|
||||
|
||||
private static final String TEMPLATE_PATH = " /opt/script/template ";
|
||||
|
||||
@@ -1313,6 +1323,116 @@ public class SimulationRunServiceImpl extends ServiceImpl<SimulationRunMapper, S
|
||||
deleteFolder(new File(TEMP_REPORT_PATH + randomId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void editReport(EditReportReq req, HttpServletResponse response) {
|
||||
log.info("编辑报告参数为:{}", req);
|
||||
SimulationRun simulationRun = this.lambdaQuery().eq(SimulationRun::getUuid, req.getRunId()).one();
|
||||
if (simulationRun != null) {
|
||||
String reportContent = req.getReportContent();
|
||||
// 算例绑定报告模板
|
||||
simulationRun.setReportTemplate(req.getReportTemplate());
|
||||
simulationRun.setReportContent(reportContent);
|
||||
this.updateById(simulationRun);
|
||||
|
||||
String randomId = RandomUtil.generateString(16);
|
||||
// 创建临时文件夹
|
||||
Path folder = Paths.get(TEMP_REPORT_PATH + randomId);
|
||||
if (!Files.exists(folder) || !Files.isDirectory(folder)) {
|
||||
if (!new File(TEMP_REPORT_PATH + randomId).mkdir()) {
|
||||
log.error("创建临时文件夹:{}失败",TEMP_REPORT_PATH + randomId);
|
||||
throw new RuntimeException("生成报告失败,原因为:创建临时文件夹失败");
|
||||
}
|
||||
}
|
||||
log.info("临时路径为:{}", randomId);
|
||||
|
||||
// 根据文件id下载文件到临时目录
|
||||
SdmResponse<ReportTemplateResp> reportResponse = reportFeignClient.queryReportTemplateInfo(req.getReportTemplate());
|
||||
if (reportResponse.isSuccess()) {
|
||||
Long reportTemplateFileId = reportResponse.getData().getFileId();
|
||||
// 获取报告模板名称
|
||||
GetFileBaseInfoReq getFileBaseInfoReq = new GetFileBaseInfoReq();
|
||||
getFileBaseInfoReq.setFileId(reportTemplateFileId);
|
||||
SdmResponse<FileMetadataInfoResp> fileBaseInfoResp = dataFeignClient.getFileBaseInfo(getFileBaseInfoReq);
|
||||
String originalName = fileBaseInfoResp.getData().getOriginalName();
|
||||
// 下载到本地临时目录
|
||||
dataFeignClient.downloadFileToLocal(reportTemplateFileId, TEMP_REPORT_PATH + randomId);
|
||||
|
||||
// 构建python命令
|
||||
List<String> command = new ArrayList<>();
|
||||
command.add("python");
|
||||
command.add("/opt/script/modifyReport.py");
|
||||
// command.add(TEMP_REPORT_PATH + File.separator +"modifyReport.py");
|
||||
command.add(TEMP_REPORT_PATH + randomId);
|
||||
command.add(TEMP_REPORT_PATH + randomId + File.separator + originalName);
|
||||
String commands = String.join(" ", command);
|
||||
|
||||
// 前端参数写入临时目录
|
||||
FileOutputStream projectInfoOutputStream = null;
|
||||
try {
|
||||
projectInfoOutputStream = new FileOutputStream(TEMP_REPORT_PATH + randomId + File.separator + "reportContent.json");
|
||||
projectInfoOutputStream.write(reportContent.getBytes(StandardCharsets.UTF_8));
|
||||
projectInfoOutputStream.flush();
|
||||
projectInfoOutputStream.close();
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
// 调用脚本
|
||||
log.info("执行 Python 命令: {}", commands);
|
||||
int runningStatus = -1;
|
||||
try {
|
||||
log.info("开始同步执行脚本");
|
||||
Process process = Runtime.getRuntime().exec(commands);
|
||||
log.info("开始获取脚本输出");
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
log.info("executePython:" + line);
|
||||
}
|
||||
log.info("脚本执行完成");
|
||||
runningStatus = process.waitFor();
|
||||
log.info("脚本运行状态:" + runningStatus);
|
||||
} catch (IOException | InterruptedException e) {
|
||||
log.error("执行脚本失败:" + e);
|
||||
return;
|
||||
}
|
||||
if (runningStatus != 0) {
|
||||
log.error("执行脚本失败");
|
||||
return;
|
||||
} else {
|
||||
log.info(commands + "执行脚本完成!");
|
||||
}
|
||||
byte[] fileData = null;
|
||||
if (response != null) {
|
||||
try {
|
||||
// 获取临时路径中脚本生成的报告
|
||||
String reportName = "report_" +
|
||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd")) +
|
||||
".docx";
|
||||
FileInputStream fileInputStream = new FileInputStream(TEMP_REPORT_PATH + randomId + File.separator + reportName);
|
||||
fileData = fileInputStream.readAllBytes();
|
||||
// 设置响应头
|
||||
response.reset();
|
||||
response.setContentType("application/octet-stream;charset=UTF-8");
|
||||
response.addHeader("Content-Length", String.valueOf(fileData.length));
|
||||
// 写入响应流
|
||||
OutputStream outputStream = response.getOutputStream();
|
||||
outputStream.write(fileData);
|
||||
outputStream.flush();
|
||||
outputStream.close();
|
||||
fileInputStream.close();
|
||||
} catch (Exception ex) {
|
||||
log.error("生成自动化报告失败:{}", ex.getMessage());
|
||||
throw new RuntimeException("生成自动化报告失败");
|
||||
}
|
||||
}
|
||||
// 删除临时路径
|
||||
log.info("删除临时路径:{},中。。。。。。", randomId);
|
||||
deleteFolder(new File(TEMP_REPORT_PATH + randomId));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public SdmResponse<Void> generateReportInternal(SpdmReportReq req) {
|
||||
log.info("内部调用生成自动化报告参数为:{}", req);
|
||||
|
||||
Reference in New Issue
Block a user