diff --git a/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java b/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java index 906d7c2..e019d7d 100644 --- a/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java +++ b/zt-module-base/zt-module-base-api/src/main/java/com/zt/plat/module/base/enums/ErrorCodeConstants.java @@ -46,6 +46,10 @@ public interface ErrorCodeConstants { ErrorCode TEMPLATE_INSTANCE_NOT_EXISTS = new ErrorCode(1_006_004_001, "模板实例不存在"); ErrorCode TEMPLATE_INSTANCE_CODE_DUPLICATE = new ErrorCode(1_006_004_002, "实例编码已存在"); + // 模板导出 1-006-005-xxx + ErrorCode TEMPLATE_EXPORT_TYPE_ERROR = new ErrorCode(1_006_005_001, "不支持的导出类型"); + ErrorCode TEMPLATE_EXPORT_FAILED = new ErrorCode(1_006_005_002, "模板导出失败"); + // ========== 物料属性 ========== ErrorCode MATERIAL_PROPERTIES_NOT_EXISTS = new ErrorCode(1_027_101_001, "物料属性不存在"); ErrorCode MATERIAL_HAS_PROPERTIES_NOT_EXISTS = new ErrorCode(1_027_101_002, "物料持有属性不存在"); diff --git a/zt-module-base/zt-module-base-server/pom.xml b/zt-module-base/zt-module-base-server/pom.xml index 431837b..c921c70 100644 --- a/zt-module-base/zt-module-base-server/pom.xml +++ b/zt-module-base/zt-module-base-server/pom.xml @@ -126,7 +126,20 @@ org.apache.velocity velocity-engine-core - 2.3 + + + + + org.docx4j + docx4j-core + 11.4.11 + + + + + org.docx4j + docx4j-JAXB-ReferenceImpl + 11.4.11 diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/DocTemplateInstanceController.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/DocTemplateInstanceController.java index 4b3b513..654d1ac 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/DocTemplateInstanceController.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/DocTemplateInstanceController.java @@ -7,15 +7,19 @@ import com.zt.plat.module.base.controller.admin.doctemplate.vo.DocTemplateInstan import com.zt.plat.module.base.controller.admin.doctemplate.vo.DocTemplateInstanceSaveReqVO; import com.zt.plat.module.base.service.doctemplate.DocTemplateInstanceService; import com.zt.plat.module.base.service.doctemplate.DocTemplateRenderService; +import com.zt.plat.module.base.service.doctemplate.DocumentRenderApiService; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import jakarta.annotation.Resource; import jakarta.validation.Valid; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; import java.util.List; import java.util.Map; @@ -38,6 +42,9 @@ public class DocTemplateInstanceController { @Resource private DocTemplateRenderService renderService; + @Resource + private DocumentRenderApiService documentRenderApiService; + @PostMapping("/create") @Operation(summary = "创建模板实例") @PreAuthorize("@ss.hasPermission('base:template-instance:create')") @@ -104,4 +111,35 @@ public class DocTemplateInstanceController { return success(true); } + @PostMapping("/render-and-export-word") + @Operation(summary = "渲染实例并导出为Word") + @PreAuthorize("@ss.hasPermission('base:template-instance:query')") + public ResponseEntity renderAndExportToWord( + @Parameter(name = "instanceId", description = "实例ID", required = true) @RequestParam("instanceId") Long instanceId, + @Parameter(name = "fileName", description = "文件名") @RequestParam(value = "fileName", required = false) String fileName, + @Parameter(name = "dataMap", description = "数据Map") @RequestBody(required = false) Map dataMap) { + byte[] fileContent = documentRenderApiService.renderAndExportToWord(instanceId, dataMap, fileName); + String actualFileName = (fileName != null ? fileName : "document") + ".docx"; + String encodedFileName = URLEncoder.encode(actualFileName, StandardCharsets.UTF_8).replace("+", "%20"); + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"document.docx\"; filename*=UTF-8''" + encodedFileName) + .header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document") + .body(fileContent); + } + + @PostMapping("/export-html-to-word") + @Operation(summary = "导出HTML为Word文档") + @PreAuthorize("@ss.hasPermission('base:template-instance:query')") + public ResponseEntity exportHtmlToWord( + @Parameter(name = "html", description = "HTML内容", required = true) @RequestBody String html, + @Parameter(name = "fileName", description = "文件名") @RequestParam(value = "fileName", required = false) String fileName) { + byte[] fileContent = documentRenderApiService.exportToWord(html, fileName); + String actualFileName = (fileName != null ? fileName : "document") + ".docx"; + String encodedFileName = URLEncoder.encode(actualFileName, StandardCharsets.UTF_8).replace("+", "%20"); + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"document.docx\"; filename*=UTF-8''" + encodedFileName) + .header("Content-Type", "application/vnd.openxmlformats-officedocument.wordprocessingml.document") + .body(fileContent); + } + } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/DocTemplateRenderController.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/DocTemplateRenderController.java new file mode 100644 index 0000000..8ccb28d --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/DocTemplateRenderController.java @@ -0,0 +1,223 @@ +package com.zt.plat.module.base.controller.admin.doctemplate.render; + +import com.zt.plat.framework.common.pojo.CommonResult; +import com.zt.plat.module.base.controller.admin.doctemplate.render.vo.*; +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateAdvancedRenderService; +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateDataSourceType; +import com.zt.plat.module.base.service.doctemplate.render.TemplateRenderRequest; +import com.zt.plat.module.base.service.doctemplate.render.export.ExportType; +import com.zt.plat.module.base.service.doctemplate.render.export.WordExportResult; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.*; + +import jakarta.annotation.Resource; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.base.enums.ErrorCodeConstants.*; + +/** + * 文档模板渲染控制器 + */ +@RestController +@RequestMapping("/base/doc-template-instance") +@Tag(name = "管理后台 - 文档模板渲染") +@Slf4j +public class DocTemplateRenderController { + + @Resource + private DocTemplateAdvancedRenderService advancedRenderService; + + @PostMapping("/preview/default") + @Operation(summary = "默认值预览") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public CommonResult previewWithDefaultValues( + @RequestParam("templateInstanceId") Long templateInstanceId) { + log.info("默认值预览: {}", templateInstanceId); + try { + TemplateRenderRequest request = new TemplateRenderRequest(); + request.setTemplateInstanceId(templateInstanceId); + request.setExportType(ExportType.HTML_PREVIEW); + Object result = advancedRenderService.renderAndExport(request); + return CommonResult.success(result.toString()); + } catch (Exception e) { + log.error("预览失败", e); + return CommonResult.error(500, "预览失败: " + e.getMessage()); + } + } + + @PostMapping("/preview/business") + @Operation(summary = "业务数据预览") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public CommonResult previewWithBusinessData(@RequestBody BusinessPreviewRequest request) { + log.info("业务预览: {}", request.getTemplateInstanceId()); + try { + Map fieldDataSources = new HashMap<>(); + if (request.getFieldMappings() != null) { + for (String key : request.getFieldMappings().keySet()) { + fieldDataSources.put(key, DocTemplateDataSourceType.BUSINESS); + } + } + + TemplateRenderRequest renderRequest = new TemplateRenderRequest(); + renderRequest.setTemplateInstanceId(request.getTemplateInstanceId()); + renderRequest.setBusinessType(request.getBusinessType()); + renderRequest.setDataSourceContext(request.getBusinessId()); + renderRequest.setFieldDataSources(fieldDataSources); + renderRequest.setExportType(ExportType.HTML_PREVIEW); + + Object result = advancedRenderService.renderAndExport(renderRequest); + return CommonResult.success(result.toString()); + } catch (Exception e) { + log.error("业务预览失败", e); + return CommonResult.error(500, "预览失败: " + e.getMessage()); + } + } + + @PostMapping("/preview/custom") + @Operation(summary = "自定义数据预览") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public CommonResult previewWithCustomData(@RequestBody CustomPreviewRequest request) { + log.info("自定义预览: {}", request.getTemplateInstanceId()); + try { + Map fieldDataSources = new HashMap<>(); + if (request.getCustomData() != null) { + for (String key : request.getCustomData().keySet()) { + fieldDataSources.put(key, DocTemplateDataSourceType.REQUEST); + } + } + + TemplateRenderRequest renderRequest = new TemplateRenderRequest(); + renderRequest.setTemplateInstanceId(request.getTemplateInstanceId()); + renderRequest.setFieldDataSources(fieldDataSources); + renderRequest.setDataSourceContext(request.getCustomData()); + renderRequest.setExportType(ExportType.HTML_PREVIEW); + + Object result = advancedRenderService.renderAndExport(renderRequest); + return CommonResult.success(result.toString()); + } catch (Exception e) { + log.error("自定义预览失败", e); + return CommonResult.error(500, "预览失败: " + e.getMessage()); + } + } + + @PostMapping("/export/default") + @Operation(summary = "默认值导出Word") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public ResponseEntity exportWithDefaultValues( + @RequestParam("templateInstanceId") Long templateInstanceId) { + log.info("默认值导出: {}", templateInstanceId); + try { + TemplateRenderRequest request = new TemplateRenderRequest(); + request.setTemplateInstanceId(templateInstanceId); + request.setExportType(ExportType.WORD); + + Object result = advancedRenderService.renderAndExport(request); + + if (result instanceof WordExportResult) { + WordExportResult wordResult = (WordExportResult) result; + String encodedFileName = URLEncoder.encode(wordResult.getFileName(), StandardCharsets.UTF_8).replace("+", "%20"); + + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"document.docx\"; filename*=UTF-8''" + encodedFileName) + .header("Content-Type", wordResult.getContentType()) + .body(wordResult.getFileContent()); + } + + throw exception(TEMPLATE_EXPORT_TYPE_ERROR); + } catch (Exception e) { + log.error("默认值导出失败", e); + throw exception(TEMPLATE_EXPORT_FAILED); + } + } + + @PostMapping("/export/business") + @Operation(summary = "业务数据导出Word") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public ResponseEntity exportWithBusinessData(@RequestBody BusinessExportRequest request) { + log.info("业务导出: {}", request.getTemplateInstanceId()); + try { + Map fieldDataSources = new HashMap<>(); + if (request.getFieldMappings() != null) { + for (String key : request.getFieldMappings().keySet()) { + fieldDataSources.put(key, DocTemplateDataSourceType.BUSINESS); + } + } + + TemplateRenderRequest renderRequest = new TemplateRenderRequest(); + renderRequest.setTemplateInstanceId(request.getTemplateInstanceId()); + renderRequest.setBusinessType(request.getBusinessType()); + renderRequest.setDataSourceContext(request.getBusinessId()); + renderRequest.setFieldDataSources(fieldDataSources); + renderRequest.setExportType(ExportType.WORD); + + Object result = advancedRenderService.renderAndExport(renderRequest); + + if (result instanceof WordExportResult) { + WordExportResult wordResult = (WordExportResult) result; + String encodedFileName = URLEncoder.encode(wordResult.getFileName(), StandardCharsets.UTF_8).replace("+", "%20"); + + return ResponseEntity.ok() + .header("Content-Disposition", "attachment; filename=\"document.docx\"; filename*=UTF-8''" + encodedFileName) + .header("Content-Type", wordResult.getContentType()) + .body(wordResult.getFileContent()); + } + + throw exception(TEMPLATE_EXPORT_TYPE_ERROR); + } catch (Exception e) { + log.error("业务数据导出失败", e); + throw exception(TEMPLATE_EXPORT_FAILED); + } + } + + @PostMapping("/export/custom") + @Operation(summary = "自定义数据导出Word") + @PreAuthorize("@ss.hasPermission('base:doctemplate:render')") + public void exportWithCustomData( + @RequestBody CustomExportRequest request, + jakarta.servlet.http.HttpServletResponse response) throws Exception { + log.info("自定义导出: {}", request.getTemplateInstanceId()); + try { + Map fieldDataSources = new HashMap<>(); + if (request.getCustomData() != null) { + for (String key : request.getCustomData().keySet()) { + fieldDataSources.put(key, DocTemplateDataSourceType.REQUEST); + } + } + + TemplateRenderRequest renderRequest = new TemplateRenderRequest(); + renderRequest.setTemplateInstanceId(request.getTemplateInstanceId()); + renderRequest.setFieldDataSources(fieldDataSources); + renderRequest.setDataSourceContext(request.getCustomData()); + renderRequest.setExportType(ExportType.WORD); + + Object result = advancedRenderService.renderAndExport(renderRequest); + + if (result instanceof WordExportResult) { + WordExportResult wordResult = (WordExportResult) result; + + // 先设置响应头 + response.addHeader("Content-Disposition", "attachment;filename=" + + com.zt.plat.framework.common.util.http.HttpUtils.encodeUtf8(wordResult.getFileName())); + response.setContentType(wordResult.getContentType()); + + // 再写入数据到输出流 + response.getOutputStream().write(wordResult.getFileContent()); + response.getOutputStream().flush(); + return; + } + + throw exception(TEMPLATE_EXPORT_TYPE_ERROR); + } catch (Exception e) { + log.error("自定义数据导出失败", e); + throw exception(TEMPLATE_EXPORT_FAILED); + } + } +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessExportRequest.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessExportRequest.java new file mode 100644 index 0000000..5d15b70 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessExportRequest.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.base.controller.admin.doctemplate.render.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BusinessExportRequest { + private Long templateInstanceId; + private String businessType; + private Long businessId; + private Map fieldMappings; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessPreviewRequest.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessPreviewRequest.java new file mode 100644 index 0000000..6691611 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/BusinessPreviewRequest.java @@ -0,0 +1,19 @@ +package com.zt.plat.module.base.controller.admin.doctemplate.render.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class BusinessPreviewRequest { + private Long templateInstanceId; + private String businessType; + private Long businessId; + private Map fieldMappings; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomExportRequest.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomExportRequest.java new file mode 100644 index 0000000..2fcb9c0 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomExportRequest.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.base.controller.admin.doctemplate.render.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CustomExportRequest { + private Long templateInstanceId; + private Map customData; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomPreviewRequest.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomPreviewRequest.java new file mode 100644 index 0000000..9cbd8ae --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/render/vo/CustomPreviewRequest.java @@ -0,0 +1,17 @@ +package com.zt.plat.module.base.controller.admin.doctemplate.render.vo; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CustomPreviewRequest { + private Long templateInstanceId; + private Map customData; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateCategoryRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateCategoryRespVO.java index fe74244..754e669 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateCategoryRespVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateCategoryRespVO.java @@ -34,9 +34,21 @@ public class DocTemplateCategoryRespVO { @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; - @Schema(description = "创建人", example = "admin") + @Schema(description = "创建人ID", example = "1") private String creator; + @Schema(description = "创建人名称", example = "管理员") + private String creatorName; + + @Schema(description = "更新时间") + private LocalDateTime updateTime; + + @Schema(description = "更新人ID", example = "1") + private String updater; + + @Schema(description = "更新人名称", example = "管理员") + private String updaterName; + @Schema(description = "子分类列表") private List children; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceRespVO.java index b27bab0..8eafe4c 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceRespVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceRespVO.java @@ -42,6 +42,24 @@ public class DocTemplateInstanceRespVO { @Schema(description = "状态", example = "draft") private String status; + @Schema(description = "SQL配置(JSON格式)", example = "{}") + private String sqlConfig; + + @Schema(description = "数据源标识", example = "default") + private String dataSource; + + @Schema(description = "分类ID", example = "1") + private Long categoryId; + + @Schema(description = "图标", example = "icon-file") + private String icon; + + @Schema(description = "描述", example = "采购合同模板实例") + private String description; + + @Schema(description = "版本号", example = "v1.0") + private String version; + @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED) private LocalDateTime createTime; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceSaveReqVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceSaveReqVO.java index 49e0a13..33d917a 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceSaveReqVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateInstanceSaveReqVO.java @@ -33,8 +33,7 @@ public class DocTemplateInstanceSaveReqVO { @Schema(description = "业务关联标签", example = "PC-2025-001") private String businessLabel; - @Schema(description = "用户编辑后的内容", requiredMode = Schema.RequiredMode.REQUIRED) - @NotBlank(message = "内容不能为空") + @Schema(description = "用户编辑后的内容(创建时可为空,将自动从模板复制)") private String editedContent; @Schema(description = "渲染后的最终内容") @@ -46,4 +45,22 @@ public class DocTemplateInstanceSaveReqVO { @Schema(description = "状态", example = "draft") private String status; + @Schema(description = "SQL配置(JSON格式)", example = "{}") + private String sqlConfig; + + @Schema(description = "数据源标识", example = "default") + private String dataSource; + + @Schema(description = "分类ID", example = "1") + private Long categoryId; + + @Schema(description = "图标", example = "icon-file") + private String icon; + + @Schema(description = "描述", example = "采购合同模板实例") + private String description; + + @Schema(description = "版本号", example = "v1.0") + private String version; + } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplatePageReqVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplatePageReqVO.java index ee8f37c..849d28b 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplatePageReqVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplatePageReqVO.java @@ -23,11 +23,8 @@ public class DocTemplatePageReqVO extends PageParam { @Schema(description = "模板编码", example = "PO_CONTRACT_001") private String tmplCode; - @Schema(description = "所属大类", example = "1") - private Long bigCategoryId; - - @Schema(description = "所属小类", example = "11") - private Long smallCategoryId; + @Schema(description = "所属分类", example = "1") + private Long categoryId; @Schema(description = "状态(1=启用,0=停用,2=草稿)", example = "1") private String enabled; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateRespVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateRespVO.java index 3a86c8e..d142891 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateRespVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateRespVO.java @@ -21,11 +21,11 @@ public class DocTemplateRespVO { @Schema(description = "模板图标", example = "📄") private String icon; - @Schema(description = "所属大类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - private Long bigCategoryId; + @Schema(description = "所属分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Long categoryId; - @Schema(description = "所属小类", example = "11") - private Long smallCategoryId; + @Schema(description = "分类名称", example = "合同信息/采购合同") + private String categoryName; @Schema(description = "版本号", example = "v1.2") private String version; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateSaveReqVO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateSaveReqVO.java index 19003af..7d0cc41 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateSaveReqVO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/controller/admin/doctemplate/vo/DocTemplateSaveReqVO.java @@ -24,12 +24,9 @@ public class DocTemplateSaveReqVO { @Schema(description = "模板图标", example = "📄") private String icon; - @Schema(description = "所属大类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") - @NotNull(message = "所属大类不能为空") - private Long bigCategoryId; - - @Schema(description = "所属小类", example = "11") - private Long smallCategoryId; + @Schema(description = "所属分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "所属分类不能为空") + private Long categoryId; @Schema(description = "版本号", example = "v1.2") private String version; diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dao/doctemplate/DocTemplateMapper.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dao/doctemplate/DocTemplateMapper.java index 8de6f6b..932df34 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dao/doctemplate/DocTemplateMapper.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dao/doctemplate/DocTemplateMapper.java @@ -19,8 +19,7 @@ public interface DocTemplateMapper extends BaseMapperX { return selectPage(reqVO, new LambdaQueryWrapperX() .likeIfPresent(DocTemplateDO::getTmplName, reqVO.getTmplName()) .likeIfPresent(DocTemplateDO::getTmplCode, reqVO.getTmplCode()) - .eqIfPresent(DocTemplateDO::getBigCategoryId, reqVO.getBigCategoryId()) - .eqIfPresent(DocTemplateDO::getSmallCategoryId, reqVO.getSmallCategoryId()) + .eqIfPresent(DocTemplateDO::getCategoryId, reqVO.getCategoryId()) .eqIfPresent(DocTemplateDO::getEnabled, reqVO.getEnabled()) .betweenIfPresent(DocTemplateDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(DocTemplateDO::getId)); diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateDO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateDO.java index 344835a..0c2faff 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateDO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateDO.java @@ -43,16 +43,10 @@ public class DocTemplateDO extends BusinessBaseDO { private String icon; /** - * 所属大类 + * 所属分类ID(支持任意级别分类) */ - @TableField("big_category_id") - private Long bigCategoryId; - - /** - * 所属小类 - */ - @TableField("small_category_id") - private Long smallCategoryId; + @TableField("category_id") + private Long categoryId; /** * 版本号 diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateInstanceDO.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateInstanceDO.java index 88621fd..b809b43 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateInstanceDO.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/dal/dataobject/doctemplate/DocTemplateInstanceDO.java @@ -84,4 +84,40 @@ public class DocTemplateInstanceDO extends BusinessBaseDO { @TableField("status") private String status; + /** + * SQL配置 + */ + @TableField("sql_config") + private String sqlConfig; + + /** + * 数据源标识 + */ + @TableField("data_source") + private String dataSource; + + /** + * 分类ID + */ + @TableField("category_id") + private Long categoryId; + + /** + * 图标 + */ + @TableField("icon") + private String icon; + + /** + * 描述 + */ + @TableField("description") + private String description; + + /** + * 版本号 + */ + @TableField("version") + private String version; + } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateInstanceServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateInstanceServiceImpl.java index 17100c4..55824e5 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateInstanceServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateInstanceServiceImpl.java @@ -49,6 +49,37 @@ public class DocTemplateInstanceServiceImpl implements DocTemplateInstanceServic // 插入实例 DocTemplateInstanceDO templateInstance = DocTemplateInstanceConvert.INSTANCE.convert(createReqVO); + + // 深拷贝模板内容到editedContent(实例完全独立于模板) + if (StrUtil.isBlank(templateInstance.getEditedContent()) && StrUtil.isNotBlank(template.getContent())) { + templateInstance.setEditedContent(template.getContent()); + } + + // 从模板复制快照字段(实例创建时获取模板的快照,后续修改模板不影响实例) + if (templateInstance.getSqlConfig() == null) { + templateInstance.setSqlConfig(template.getSqlConfig()); + } + if (templateInstance.getDataSource() == null) { + templateInstance.setDataSource(template.getDataSource()); + } + if (templateInstance.getCategoryId() == null) { + templateInstance.setCategoryId(template.getCategoryId()); + } + if (templateInstance.getIcon() == null) { + templateInstance.setIcon(template.getIcon()); + } + if (templateInstance.getDescription() == null) { + templateInstance.setDescription(template.getDescription()); + } + if (templateInstance.getVersion() == null) { + templateInstance.setVersion(template.getVersion()); + } + + // 设置默认状态为草稿 + if (templateInstance.getStatus() == null) { + templateInstance.setStatus("draft"); + } + docTemplateInstanceMapper.insert(templateInstance); // 更新模板使用次数 diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateServiceImpl.java index b5e353b..2f8d422 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocTemplateServiceImpl.java @@ -30,6 +30,9 @@ public class DocTemplateServiceImpl implements DocTemplateService { @Resource private DocTemplateMapper templateMapper; + @Resource + private DocTemplateCategoryService templateCategoryService; + @Override public Long createTemplate(DocTemplateSaveReqVO createReqVO) { // 校验模板编码唯一性 @@ -37,12 +40,13 @@ public class DocTemplateServiceImpl implements DocTemplateService { // 插入 DocTemplateDO template = DocTemplateConvert.INSTANCE.convert(createReqVO); + // 设置默认值 if (template.getUseCount() == null) { template.setUseCount(0); } if (template.getEnabled() == null) { - template.setEnabled("2"); // 默认为草稿状态 + template.setEnabled("0"); // 默认为禁用状态 (0=禁用, 1=启用) } templateMapper.insert(template); // 返回 @@ -97,19 +101,43 @@ public class DocTemplateServiceImpl implements DocTemplateService { @Override public DocTemplateRespVO getTemplate(Long id) { DocTemplateDO template = templateMapper.selectById(id); - return DocTemplateConvert.INSTANCE.convert(template); - } - - @Override - public PageResult getTemplatePage(DocTemplatePageReqVO pageReqVO) { - PageResult pageResult = templateMapper.selectPage(pageReqVO); - return DocTemplateConvert.INSTANCE.convertPage(pageResult); + DocTemplateRespVO respVO = DocTemplateConvert.INSTANCE.convert(template); + // 填充分类名称 + fillCategoryName(respVO); + return respVO; } @Override public List getTemplateList() { List list = templateMapper.selectList(); - return DocTemplateConvert.INSTANCE.convertList(list); + List result = DocTemplateConvert.INSTANCE.convertList(list); + // 填充分类名称 + result.forEach(this::fillCategoryName); + return result; + } + + @Override + public PageResult getTemplatePage(DocTemplatePageReqVO reqVO) { + PageResult pageResult = templateMapper.selectPage(reqVO); + PageResult result = DocTemplateConvert.INSTANCE.convertPage(pageResult); + // 填充分类名称 + result.getList().forEach(this::fillCategoryName); + return result; + } + + /** + * 填充分类名称(包含父级路径) + * + * @param respVO 模板响应VO + */ + private void fillCategoryName(DocTemplateRespVO respVO) { + if (respVO == null || respVO.getCategoryId() == null) { + return; + } + + // 调用分类Service获取完整路径 + String categoryPath = templateCategoryService.getCategoryFullPath(respVO.getCategoryId()); + respVO.setCategoryName(categoryPath); } } diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocumentRenderApiServiceImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocumentRenderApiServiceImpl.java index f599b5d..31bd9b0 100644 --- a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocumentRenderApiServiceImpl.java +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/DocumentRenderApiServiceImpl.java @@ -145,6 +145,7 @@ public class DocumentRenderApiServiceImpl implements DocumentRenderApiService { /** * 将 HTML 转换为 Word 文档内容 * 递归处理 HTML 节点,使用 docx4j API 构建完整的 Word 文档 + * 增强版本:支持CSS样式保留(颜色、对齐、字体等) */ private void processHtmlToWord(MainDocumentPart mainDocumentPart, Element element) throws Exception { for (Node node : element.childNodes()) { @@ -159,11 +160,11 @@ public class DocumentRenderApiServiceImpl implements DocumentRenderApiService { switch (tagName) { case "h1", "h2", "h3", "h4", "h5", "h6" -> { - String styleId = "Heading" + tagName.substring(1); - mainDocumentPart.addStyledParagraphOfText(styleId, element1.text()); + // 使用带样式的段落处理,而不是使用预定义样式 + addStyledParagraph(mainDocumentPart, element1, true); } case "p" -> { - mainDocumentPart.addStyledParagraphOfText("Normal", element1.text()); + addStyledParagraph(mainDocumentPart, element1, false); } case "br" -> { mainDocumentPart.addParagraphOfText(""); @@ -175,13 +176,13 @@ public class DocumentRenderApiServiceImpl implements DocumentRenderApiService { processHtmlToWord(mainDocumentPart, element1); } case "li" -> { - mainDocumentPart.addParagraphOfText("• " + element1.text()); + addStyledParagraph(mainDocumentPart, element1, false); } case "strong", "b" -> { - mainDocumentPart.addStyledParagraphOfText("Normal", element1.text()); + addStyledParagraph(mainDocumentPart, element1, false); } case "i", "em" -> { - mainDocumentPart.addStyledParagraphOfText("Normal", element1.text()); + addStyledParagraph(mainDocumentPart, element1, false); } case "div", "section", "article" -> { processHtmlToWord(mainDocumentPart, element1); @@ -194,6 +195,238 @@ public class DocumentRenderApiServiceImpl implements DocumentRenderApiService { } } + /** + * 添加带样式的段落(支持CSS样式解析) + * @param mainDocumentPart Word文档主体部分 + * @param element HTML元素 + * @param isHeading 是否是标题 + */ + private void addStyledParagraph(MainDocumentPart mainDocumentPart, Element element, boolean isHeading) throws Exception { + ObjectFactory factory = new ObjectFactory(); + P paragraph = factory.createP(); + + // 创建段落属性 + PPr pPr = factory.createPPr(); + paragraph.setPPr(pPr); + + // 解析并应用段落级样式(对齐方式) + String styleAttr = element.attr("style"); + if (StrUtil.isNotBlank(styleAttr)) { + // 解析对齐方式 + String textAlign = extractCssProperty(styleAttr, "text-align"); + if (StrUtil.isNotBlank(textAlign)) { + Jc jc = factory.createJc(); + switch (textAlign.toLowerCase()) { + case "center" -> jc.setVal(JcEnumeration.CENTER); + case "right" -> jc.setVal(JcEnumeration.RIGHT); + case "justify" -> jc.setVal(JcEnumeration.BOTH); + default -> jc.setVal(JcEnumeration.LEFT); + } + pPr.setJc(jc); + } + } + + // 创建文本运行 + R run = factory.createR(); + Text text = factory.createText(); + text.setValue(element.text()); + run.getContent().add(text); + + // 创建文本运行属性 + RPr rPr = factory.createRPr(); + run.setRPr(rPr); + + // 应用字体样式 + if (StrUtil.isNotBlank(styleAttr)) { + // 解析颜色 + String color = extractCssProperty(styleAttr, "color"); + if (StrUtil.isNotBlank(color)) { + Color colorObj = factory.createColor(); + String hexColor = convertColorToHex(color); + if (hexColor != null) { + colorObj.setVal(hexColor); + rPr.setColor(colorObj); + } + } + + // 解析字体大小 + String fontSize = extractCssProperty(styleAttr, "font-size"); + if (StrUtil.isNotBlank(fontSize)) { + HpsMeasure hpsMeasure = factory.createHpsMeasure(); + // 将px转换为half-points (1px ≈ 1.5半点) + try { + int pxValue = Integer.parseInt(fontSize.replaceAll("[^0-9]", "")); + int halfPoints = pxValue * 2; // 简化转换 + hpsMeasure.setVal(java.math.BigInteger.valueOf(halfPoints)); + rPr.setSz(hpsMeasure); + rPr.setSzCs(hpsMeasure); // 复杂脚本字体大小 + } catch (NumberFormatException e) { + log.warn("无法解析字体大小: {}", fontSize); + } + } + + // 解析字体 + String fontFamily = extractCssProperty(styleAttr, "font-family"); + if (StrUtil.isNotBlank(fontFamily)) { + RFonts rFonts = factory.createRFonts(); + fontFamily = fontFamily.replaceAll("['\"]", "").split(",")[0].trim(); + rFonts.setAscii(fontFamily); + rFonts.setHAnsi(fontFamily); + rFonts.setCs(fontFamily); + rFonts.setEastAsia(fontFamily); + rPr.setRFonts(rFonts); + } + + // 解析粗体 + String fontWeight = extractCssProperty(styleAttr, "font-weight"); + if ("bold".equalsIgnoreCase(fontWeight) || "700".equals(fontWeight) || "800".equals(fontWeight) || "900".equals(fontWeight)) { + BooleanDefaultTrue bold = factory.createBooleanDefaultTrue(); + bold.setVal(true); + rPr.setB(bold); + rPr.setBCs(bold); + } + + // 解析斜体 + String fontStyle = extractCssProperty(styleAttr, "font-style"); + if ("italic".equalsIgnoreCase(fontStyle) || "oblique".equalsIgnoreCase(fontStyle)) { + BooleanDefaultTrue italic = factory.createBooleanDefaultTrue(); + italic.setVal(true); + rPr.setI(italic); + rPr.setICs(italic); + } + + // 背景色支持 - 暂时注释掉因为 docx4j API 兼容性问题 + // 如果需要可以后续使用 CTShd 重新实现 + } + + // 处理标签自身的样式(strong, b, i, em) + String tagName = element.tagName().toLowerCase(); + if ("strong".equals(tagName) || "b".equals(tagName)) { + BooleanDefaultTrue bold = factory.createBooleanDefaultTrue(); + bold.setVal(true); + rPr.setB(bold); + rPr.setBCs(bold); + } + if ("i".equals(tagName) || "em".equals(tagName)) { + BooleanDefaultTrue italic = factory.createBooleanDefaultTrue(); + italic.setVal(true); + rPr.setI(italic); + rPr.setICs(italic); + } + + // 标题默认加粗且增大字号 + if (isHeading) { + BooleanDefaultTrue bold = factory.createBooleanDefaultTrue(); + bold.setVal(true); + rPr.setB(bold); + rPr.setBCs(bold); + + // 根据标题级别设置字号(如果没有显式指定) + if (rPr.getSz() == null) { + HpsMeasure hpsMeasure = factory.createHpsMeasure(); + String tag = element.tagName().toLowerCase(); + int size = switch (tag) { + case "h1" -> 32; // 16pt + case "h2" -> 28; // 14pt + case "h3" -> 24; // 12pt + case "h4" -> 22; // 11pt + case "h5" -> 20; // 10pt + case "h6" -> 18; // 9pt + default -> 22; + }; + hpsMeasure.setVal(java.math.BigInteger.valueOf(size)); + rPr.setSz(hpsMeasure); + rPr.setSzCs(hpsMeasure); + } + } + + paragraph.getContent().add(run); + mainDocumentPart.getContent().add(paragraph); + } + + /** + * 从CSS样式字符串中提取指定属性的值 + * @param styleAttr CSS样式字符串,例如 "color: black; text-align: center;" + * @param property 要提取的属性名,例如 "color" + * @return 属性值,例如 "black",如果不存在则返回null + */ + private String extractCssProperty(String styleAttr, String property) { + if (StrUtil.isBlank(styleAttr) || StrUtil.isBlank(property)) { + return null; + } + + // 分割样式声明 + String[] declarations = styleAttr.split(";"); + for (String declaration : declarations) { + String[] parts = declaration.split(":", 2); + if (parts.length == 2) { + String key = parts[0].trim().toLowerCase(); + String value = parts[1].trim(); + if (key.equals(property.toLowerCase())) { + return value; + } + } + } + return null; + } + + /** + * 将CSS颜色值转换为十六进制格式(用于Word) + * 支持:#rrggbb, rgb(r,g,b), 颜色名称 + * @param cssColor CSS颜色值 + * @return 十六进制颜色(不带#),例如 "000000",如果无法解析则返回null + */ + private String convertColorToHex(String cssColor) { + if (StrUtil.isBlank(cssColor)) { + return null; + } + + cssColor = cssColor.trim().toLowerCase(); + + // 已经是十六进制格式 + if (cssColor.startsWith("#")) { + return cssColor.substring(1); + } + + // rgb/rgba格式 + if (cssColor.startsWith("rgb")) { + try { + String rgbValues = cssColor.substring(cssColor.indexOf('(') + 1, cssColor.indexOf(')')); + String[] parts = rgbValues.split(","); + int r = Integer.parseInt(parts[0].trim()); + int g = Integer.parseInt(parts[1].trim()); + int b = Integer.parseInt(parts[2].trim()); + return String.format("%02X%02X%02X", r, g, b); + } catch (Exception e) { + log.warn("无法解析RGB颜色: {}", cssColor); + return null; + } + } + + // 颜色名称映射 + return switch (cssColor) { + case "black" -> "000000"; + case "white" -> "FFFFFF"; + case "red" -> "FF0000"; + case "green" -> "008000"; + case "blue" -> "0000FF"; + case "yellow" -> "FFFF00"; + case "cyan" -> "00FFFF"; + case "magenta" -> "FF00FF"; + case "gray", "grey" -> "808080"; + case "silver" -> "C0C0C0"; + case "maroon" -> "800000"; + case "olive" -> "808000"; + case "lime" -> "00FF00"; + case "aqua" -> "00FFFF"; + case "teal" -> "008080"; + case "navy" -> "000080"; + case "fuchsia" -> "FF00FF"; + case "purple" -> "800080"; + default -> null; + }; + } + /** * 处理表格 * 使用 docx4j 的 Table 和 Tbl API 创建 Word 表格 diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DefaultSqlConfigProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DefaultSqlConfigProvider.java new file mode 100644 index 0000000..1c1e4db --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DefaultSqlConfigProvider.java @@ -0,0 +1,60 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 默认SQL配置数据提供者实现 + * 这是一个临时的实现,实际项目中应该根据具体的SQL配置管理来实现 + * + * @author hwc + */ +@Component +@Slf4j +public class DefaultSqlConfigProvider implements ISqlConfigProvider { + + @Override + public List> querySqlConfig(String sqlConfigId) { + log.debug("查询SQL配置: {}", sqlConfigId); + + // TODO: 实际实现应该: + // 1. 从数据库中根据 sqlConfigId 查询SQL脚本 + // 2. 执行SQL脚本并返回结果 + // 3. 处理异常情况 + + // 临时实现:返回一些示例数据 + List> result = new ArrayList<>(); + + switch (sqlConfigId) { + case "CONTRACT_INFO": + Map contractData = new HashMap<>(); + contractData.put("contractName", "示例合同名称"); + contractData.put("supplier", "示例供应商"); + contractData.put("amount", "1000000"); + contractData.put("deliveryDate", "2025-12-31"); + result.add(contractData); + break; + + case "ORDER_INFO": + Map orderData = new HashMap<>(); + orderData.put("orderNumber", "ORD-2025-001"); + orderData.put("customer", "示例客户"); + orderData.put("totalAmount", "500000"); + orderData.put("orderDate", "2025-01-15"); + result.add(orderData); + break; + + default: + log.warn("未知的SQL配置ID: {}", sqlConfigId); + break; + } + + log.debug("SQL配置查询结果数量: {}", result.size()); + return result; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateAdvancedRenderService.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateAdvancedRenderService.java new file mode 100644 index 0000000..a9b2556 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateAdvancedRenderService.java @@ -0,0 +1,186 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import com.zt.plat.framework.common.exception.ServiceException; +import com.zt.plat.module.base.controller.admin.doctemplate.vo.DocTemplateInstanceRespVO; +import com.zt.plat.module.base.dal.dataobject.doctemplate.DocTemplateTagDO; +import com.zt.plat.module.base.dal.dao.doctemplate.DocTemplateTagMapper; +import com.zt.plat.module.base.service.doctemplate.DocTemplateInstanceService; +import com.zt.plat.module.base.service.doctemplate.render.export.DocTemplateExportStrategyFactory; +import com.zt.plat.module.base.service.doctemplate.render.export.IExportStrategy; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import jakarta.annotation.Resource; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * 增强版模板实例渲染服务(核心服务) + * 基于现有 DocTemplateRenderService 扩展,支持多数据源和策略模式 + * + * @author hwc + */ +@Service +@Slf4j +public class DocTemplateAdvancedRenderService { + + + @Resource + private DocTemplateInstanceService docTemplateInstanceService; + + @Resource + private DocTemplateTagMapper docTemplateTagMapper; + + @Resource + private IVelocityRenderEngine velocityRenderEngine; + + @Resource + private DocTemplateDataSourceProviderFactory dataSourceProviderFactory; + + @Resource + private DocTemplateExportStrategyFactory exportStrategyFactory; + + /** + * 渲染模板实例并导出 + */ + public Object renderAndExport(TemplateRenderRequest request) { + log.info("开始渲染和导出,模板实例ID: {}, 导出类型: {}", + request.getTemplateInstanceId(), request.getExportType()); + + // 1. 加载模板实例 + DocTemplateInstanceRespVO instance = docTemplateInstanceService + .getTemplateInstance(request.getTemplateInstanceId()); + + if (instance == null) { + throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(),"模板实例不存在: " + request.getTemplateInstanceId()); + } + + // 2. 加载标签定义(用于获取标签的默认值) + List tags = getTemplateTags(instance); + + if (tags == null || tags.isEmpty()) { + log.warn("模板实例 {} 没有关联的标签定义", request.getTemplateInstanceId()); + } + + Map tagMap = tags.stream() + .collect(Collectors.toMap(DocTemplateTagDO::getTagCode, t -> t)); + + // 3. 收集所有需要填充的字段值 + Map fillData = new HashMap<>(); + for (DocTemplateTagDO tag : tags) { + Object value = getFieldValue(tag, request, instance.getInstanceName()); + fillData.put(tag.getTagCode(), value != null ? value : ""); + } + + // 4. Velocity 渲染 + String templateContent = instance.getEditedContent(); + if (templateContent == null || templateContent.trim().isEmpty()) { + throw new ServiceException(HttpStatus.INTERNAL_SERVER_ERROR.value(), "模板实例内容为空"); + } + + String htmlContent = velocityRenderEngine.render(templateContent, fillData); + + // 5. 获取导出策略并执行导出 + IExportStrategy exportStrategy = exportStrategyFactory + .createStrategy(request.getExportType()); + + Object result = exportStrategy.export(htmlContent, instance.getInstanceName()); + + log.info("渲染和导出完成,模板实例ID: {}", request.getTemplateInstanceId()); + return result; + } + + /** + * 获取单个字段的值 + * 优先级:调用方覆盖 > 默认值 + */ + private Object getFieldValue(DocTemplateTagDO tag, TemplateRenderRequest request, + String templateName) { + String fieldName = tag.getTagCode(); + + // 判断是否有字段级的数据源覆盖 + DocTemplateDataSourceType sourceType = null; + if (request.getFieldDataSources() != null) { + sourceType = request.getFieldDataSources().get(fieldName); + } + + // 如果有覆盖,从指定的数据源获取值 + if (sourceType != null) { + try { + IDataSourceProvider provider = dataSourceProviderFactory + .createProvider( + sourceType, + request.getDataSourceContext(), + templateName, + request.getBusinessType() != null ? + request.getBusinessType() : templateName + ); + + Map data = provider.getData( + request.getDataSourceContext(), + java.util.List.of(fieldName) + ); + + if (data.containsKey(fieldName)) { + Object value = data.get(fieldName); + if (value != null) { + log.debug("从数据源 {} 获取字段值: {} = {}", sourceType, fieldName, value); + return value; + } + } + } catch (Exception e) { + log.warn("从数据源获取字段值失败,使用默认值: {}, 数据源: {}", fieldName, sourceType, e); + } + } + + // 使用默认值(从标签的defaultValue字段获取) + String defaultValue = extractDefaultValue(tag); + log.debug("使用标签默认值: {} = {}", fieldName, defaultValue); + return defaultValue; + } + + /** + * 从标签定义中提取默认值 + */ + private String extractDefaultValue(DocTemplateTagDO tag) { + if (tag.getDefaultValue() != null && !tag.getDefaultValue().trim().isEmpty()) { + return tag.getDefaultValue(); + } + return ""; + } + + /** + * 获取模板实例关联的标签列表 + * 通过实例的 categoryId 查询该分类下的所有标签定义 + */ + private List getTemplateTags(DocTemplateInstanceRespVO instance) { + if (instance.getCategoryId() == null) { + log.warn("模板实例 {} 没有关联分类ID", instance.getId()); + return List.of(); + } + + try { + // 通过 categoryId 查询该分类下的所有标签 + List tags = docTemplateTagMapper.selectList( + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper() + .eq(DocTemplateTagDO::getCategoryId, instance.getCategoryId()) + .eq(DocTemplateTagDO::getEnabled, "1") // 只查询启用的标签 + .orderByAsc(DocTemplateTagDO::getSort) + ); + + if (tags == null || tags.isEmpty()) { + log.warn("分类ID {} 下没有找到标签定义", instance.getCategoryId()); + return List.of(); + } + + log.debug("从分类ID {} 获取到 {} 个标签定义", instance.getCategoryId(), tags.size()); + return tags; + } catch (Exception e) { + log.error("查询分类 {} 的标签列表失败", instance.getCategoryId(), e); + return List.of(); + } + } +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceProviderFactory.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceProviderFactory.java new file mode 100644 index 0000000..928441a --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceProviderFactory.java @@ -0,0 +1,111 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import com.zt.plat.module.base.service.doctemplate.render.provider.*; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.NoSuchBeanDefinitionException; +import org.springframework.context.ApplicationContext; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 文档模板数据源提供者工厂(工厂模式) + * + * @author hwc + */ +@Component +@Slf4j +public class DocTemplateDataSourceProviderFactory { + + @Resource + private ApplicationContext applicationContext; + + @Resource + private ISqlConfigProvider sqlConfigProvider; + + /** + * 创建数据源提供者 + * + * @param sourceType 数据源类型 + * @param context 数据源上下文(含义由sourceType决定) + * @param templateName 模板名称(用于查找业务数据提供者) + * @param businessType 业务类型(当sourceType=BUSINESS时使用,如"contract"、"order"等) + * @return 数据源提供者实例 + */ + public IDataSourceProvider createProvider( + DocTemplateDataSourceType sourceType, + Object context, + String templateName, + String businessType) { + + log.debug("创建数据源提供者,类型: {}, 业务类型: {}", sourceType, businessType); + + switch (sourceType) { + case DEFAULT: + return new DocTemplateDefaultFieldDataSourceProvider(); + + case BUSINESS: + return createBusinessDataProvider(businessType); + + case REQUEST: + // context 应该是 Map 类型的请求数据 + return new DocTemplateRequestFieldDataSourceProvider((java.util.Map) context); + + case SQL_CONFIG: + // context 应该是 sqlConfigId + return new DocTemplateSqlConfigFieldDataSourceProvider(sqlConfigProvider, (String) context); + + default: + throw new IllegalArgumentException("不支持的数据源类型: " + sourceType); + } + } + + /** + * 创建业务数据提供者 + * + * @param businessType 业务类型 + * @return 数据源提供者 + */ + private IDataSourceProvider createBusinessDataProvider(String businessType) { + if (businessType == null || businessType.trim().isEmpty()) { + throw new IllegalArgumentException("使用BUSINESS数据源时,businessType不能为空"); + } + + // 业务数据提供者的Bean名称约定:{businessType}DataProvider + // 示例:contractDataProvider、orderDataProvider、purchaseOrderDataProvider等 + String providerBeanName = businessType + "DataProvider"; + + try { + IBusinessDataProvider businessDataProvider = applicationContext.getBean( + providerBeanName, + IBusinessDataProvider.class + ); + + log.debug("找到业务数据提供者: {}", providerBeanName); + return new DocTemplateBusinessFieldDataSourceProvider(businessDataProvider); + + } catch (NoSuchBeanDefinitionException e) { + throw new IllegalArgumentException( + "未找到业务数据提供者: " + providerBeanName + + "(业务类型:" + businessType + ")" + + "。请确保实现了 " + IBusinessDataProvider.class.getSimpleName() + + " 接口并注册为名为 '" + providerBeanName + "' 的Spring Bean。", e); + } + } + + /** + * 重载方法:不指定businessType时的调用 + * 此时将使用templateName作为businessType(向后兼容) + * + * @param sourceType 数据源类型 + * @param context 数据源上下文 + * @param templateName 模板名称 + * @return 数据源提供者实例 + */ + public IDataSourceProvider createProvider( + DocTemplateDataSourceType sourceType, + Object context, + String templateName) { + return createProvider(sourceType, context, templateName, templateName); + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceType.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceType.java new file mode 100644 index 0000000..76c34e2 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/DocTemplateDataSourceType.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +/** + * 数据源类型枚举 + * + * @author hwc + */ +public enum DocTemplateDataSourceType { + /** + * 标签默认值 + */ + DEFAULT("标签默认值"), + + /** + * 业务策略 + */ + BUSINESS("业务策略"), + + /** + * 前端入参 + */ + REQUEST("前端入参"), + + /** + * SQL配置 + */ + SQL_CONFIG("SQL配置"); + + private final String description; + + DocTemplateDataSourceType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IBusinessDataProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IBusinessDataProvider.java new file mode 100644 index 0000000..424a4da --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IBusinessDataProvider.java @@ -0,0 +1,30 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import java.util.Map; + +/** + * 业务数据提供者接口 + * 不同的业务类型实现此接口来提供数据 + * + *

命名约定:{业务类型}DataProvider + *

示例: + *

    + *
  • ContractDataProvider (合同数据提供者)
  • + *
  • OrderDataProvider (订单数据提供者)
  • + *
  • PurchaseOrderDataProvider (采购单数据提供者)
  • + *
  • SalesOrderDataProvider (销售单数据提供者)
  • + *
+ * + * @author hwc + */ +public interface IBusinessDataProvider { + + /** + * 获取业务数据 + * + * @param context 业务上下文(通常是业务ID,如contractId、orderId等) + * @param fieldNames 需要填充的字段名列表 + * @return 字段值映射表 + */ + Map provideData(Object context, java.util.List fieldNames); +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IDataSourceProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IDataSourceProvider.java new file mode 100644 index 0000000..803c79b --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IDataSourceProvider.java @@ -0,0 +1,28 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import java.util.Map; + +/** + * 文档模板数据源提供者接口(策略模式) + * 所有数据源实现都遵循此接口 + * + * @author hwc + */ +public interface IDataSourceProvider { + + /** + * 获取指定字段的值 + * + * @param context 上下文信息(内容因数据源类型而异) + * @param fieldNames 需要获取的字段名列表 + * @return 字段值映射表 + */ + Map getData(Object context, java.util.List fieldNames); + + /** + * 获取此数据源的类型 + * + * @return 数据源类型 + */ + DocTemplateDataSourceType getType(); +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/ISqlConfigProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/ISqlConfigProvider.java new file mode 100644 index 0000000..6fa14b8 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/ISqlConfigProvider.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import java.util.List; +import java.util.Map; + +/** + * SQL配置数据提供者接口 + * + * @author hwc + */ +public interface ISqlConfigProvider { + + /** + * 查询SQL配置结果 + * + * @param sqlConfigId SQL配置ID + * @return 查询结果列表,每个Map代表一行数据 + */ + List> querySqlConfig(String sqlConfigId); +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IVelocityRenderEngine.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IVelocityRenderEngine.java new file mode 100644 index 0000000..749534a --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/IVelocityRenderEngine.java @@ -0,0 +1,20 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import java.util.Map; + +/** + * Velocity 渲染引擎接口 + * + * @author hwc + */ +public interface IVelocityRenderEngine { + + /** + * 渲染模板内容 + * + * @param templateContent 模板内容 + * @param data 数据映射 + * @return 渲染后的内容 + */ + String render(String templateContent, Map data); +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/TemplateRenderRequest.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/TemplateRenderRequest.java new file mode 100644 index 0000000..5684aed --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/TemplateRenderRequest.java @@ -0,0 +1,21 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import com.zt.plat.module.base.service.doctemplate.render.export.ExportType; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class TemplateRenderRequest { + private Long templateInstanceId; + private Map fieldDataSources; + private Object dataSourceContext; + private ExportType exportType = ExportType.WORD; + private String businessType; +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/VelocityRenderEngineImpl.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/VelocityRenderEngineImpl.java new file mode 100644 index 0000000..5d70f23 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/VelocityRenderEngineImpl.java @@ -0,0 +1,99 @@ +package com.zt.plat.module.base.service.doctemplate.render; + +import lombok.extern.slf4j.Slf4j; +import org.apache.velocity.VelocityContext; +import org.apache.velocity.app.Velocity; +import org.apache.velocity.runtime.resource.loader.StringResourceLoader; +import org.springframework.stereotype.Component; + +import jakarta.annotation.PostConstruct; +import java.io.StringWriter; +import java.util.Map; +import java.util.Properties; + +/** + * Velocity 渲染引擎实现 + */ +@Component +@Slf4j +public class VelocityRenderEngineImpl implements IVelocityRenderEngine { + + private static volatile boolean velocityInitialized = false; + + @PostConstruct + public synchronized void initVelocity() { + if (velocityInitialized) { + return; + } + + try { + Properties props = new Properties(); + props.setProperty("input.encoding", "UTF-8"); + props.setProperty("output.encoding", "UTF-8"); + props.setProperty("resource.loader", "string"); + props.setProperty("string.resource.loader.class", StringResourceLoader.class.getName()); + props.setProperty("runtime.log.logsystem.class", + "org.apache.velocity.runtime.log.NullLogChute"); + props.setProperty("eventhandler.referenceinsertion.class", + "org.apache.velocity.app.event.implement.EscapeHtmlReference"); + props.setProperty("eventhandler.escape.html.match", "true"); + + Velocity.init(props); + velocityInitialized = true; + log.info("Velocity 模板引擎初始化成功"); + } catch (Exception e) { + log.error("Velocity 模板引擎初始化失败", e); + throw new RuntimeException("Velocity 模板引擎初始化失败", e); + } + } + + @Override + public String render(String templateContent, Map data) { + if (templateContent == null || templateContent.trim().isEmpty()) { + log.warn("模板内容为空"); + return ""; + } + + if (!velocityInitialized) { + initVelocity(); + } + + // 转换模板语法:{{variableName}} -> ${variableName} + // 支持Mustache/Handlebars风格的模板兼容Velocity语法 + String velocityTemplate = convertToVelocitySyntax(templateContent); + + VelocityContext context = new VelocityContext(); + if (data != null && !data.isEmpty()) { + data.forEach(context::put); + } + + try { + StringWriter writer = new StringWriter(); + Velocity.evaluate(context, writer, "TemplateRender", velocityTemplate); + String result = writer.toString(); + + log.debug("模板渲染成功,数据量: {}, 内容长度: {}", + data != null ? data.size() : 0, result.length()); + + return result; + } catch (Exception e) { + log.error("模板渲染失败,模板长度: {}", templateContent.length(), e); + throw new RuntimeException("模板渲染失败: " + e.getMessage(), e); + } + } + + /** + * 将 Mustache/Handlebars 语法 {{variableName}} 转换为 Velocity 语法 ${variableName} + * + * @param template 原始模板内容 + * @return 转换后的 Velocity 语法模板 + */ + private String convertToVelocitySyntax(String template) { + if (template == null) { + return null; + } + // 使用正则表达式替换 {{variable}} 为 ${variable} + // 匹配 {{ 和 }} 之间的内容(非贪婪模式) + return template.replaceAll("\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}", "\\${$1}"); + } +} diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateAbstractExportStrategy.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateAbstractExportStrategy.java new file mode 100644 index 0000000..8577bd5 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateAbstractExportStrategy.java @@ -0,0 +1,79 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import lombok.extern.slf4j.Slf4j; + +/** + * 抽象导出策略(模板方法模式) + * 定义导出的标准流程框架 + * + * @author hwc + */ +@Slf4j +public abstract class DocTemplateAbstractExportStrategy implements IExportStrategy { + + /** + * 模板方法:定义导出流程骨架 + */ + @Override + public final Object export(String htmlContent, String templateName) { + // 1. 验证输入 + validateInput(htmlContent, templateName); + + // 2. 生成文件名 + String fileName = generateFileName(templateName); + + // 3. 执行具体的导出逻辑 + Object result = doExport(htmlContent, fileName); + + // 4. 后处理(如日志、监控等) + postProcess(fileName, result); + + return result; + } + + /** + * 验证输入 + */ + protected void validateInput(String htmlContent, String templateName) { + if (htmlContent == null || htmlContent.isEmpty()) { + throw new IllegalArgumentException("HTML内容为空"); + } + if (templateName == null || templateName.isEmpty()) { + throw new IllegalArgumentException("模板名称为空"); + } + } + + /** + * 生成文件名 + */ + protected String generateFileName(String templateName) { + return templateName + "_" + System.currentTimeMillis() + getFileExtension(); + } + + /** + * 具体的导出逻辑(由子类实现) + * + * @param htmlContent HTML内容 + * @param fileName 文件名 + * @return 导出结果 + */ + protected abstract Object doExport(String htmlContent, String fileName); + + /** + * 获取文件扩展名 + * + * @return 文件扩展名 + */ + protected abstract String getFileExtension(); + + /** + * 后处理逻辑(可选,子类可覆盖) + * + * @param fileName 文件名 + * @param result 导出结果 + */ + protected void postProcess(String fileName, Object result) { + // 默认实现:记录日志 + log.info("导出完成: {}, 类型: {}", fileName, getExportType().getDescription()); + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateExportStrategyFactory.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateExportStrategyFactory.java new file mode 100644 index 0000000..a2f3fb5 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/DocTemplateExportStrategyFactory.java @@ -0,0 +1,58 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +/** + * 文档模板导出策略工厂(工厂模式) + * + * @author hwc + */ +@Component +@Slf4j +public class DocTemplateExportStrategyFactory { + + @Resource(name = "docTemplateWordExportTemplateStrategy") + private IExportStrategy wordExportStrategy; + + @Resource(name = "docTemplateHtmlPreviewTemplateStrategy") + private IExportStrategy htmlPreviewStrategy; + + // TODO: 添加其他导出策略的注入 + // @Resource(name = "docTemplateExcelExportTemplateStrategy") + // private IExportStrategy excelExportStrategy; + // + // @Resource(name = "docTemplatePdfExportTemplateStrategy") + // private IExportStrategy pdfExportStrategy; + + /** + * 创建导出方式策略 + * + * @param exportType 导出类型 + * @return 导出策略实例 + */ + public IExportStrategy createStrategy(ExportType exportType) { + log.debug("创建导出策略,类型: {}", exportType); + + switch (exportType) { + case WORD: + return wordExportStrategy; + + case EXCEL: + // TODO: 实现Excel导出策略 + throw new UnsupportedOperationException("Excel导出功能尚未实现"); + + case PDF: + // TODO: 实现PDF导出策略 + throw new UnsupportedOperationException("PDF导出功能尚未实现"); + + case HTML_PREVIEW: + return htmlPreviewStrategy; + + default: + throw new IllegalArgumentException("不支持的导出方式: " + exportType); + } + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/ExportType.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/ExportType.java new file mode 100644 index 0000000..5c22a94 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/ExportType.java @@ -0,0 +1,38 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +/** + * 导出方式枚举 + * + * @author hwc + */ +public enum ExportType { + /** + * Word文档 + */ + WORD("Word"), + + /** + * Excel表格 + */ + EXCEL("Excel"), + + /** + * PDF文档 + */ + PDF("PDF"), + + /** + * HTML预览 + */ + HTML_PREVIEW("HTML预览"); + + private final String description; + + ExportType(String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewResult.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewResult.java new file mode 100644 index 0000000..477d2df --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewResult.java @@ -0,0 +1,36 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import lombok.*; + +/** + * HTML预览结果 + * + * @author hwc + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString +public class HtmlPreviewResult { + /** + * HTML内容 + */ + private String htmlContent; + + /** + * 文件名 + */ + private String fileName; + + /** + * 内容类型 + */ + @Builder.Default + private String contentType = "text/html"; + + @Override + public String toString() { + return htmlContent; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewTemplateStrategyDocTemplate.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewTemplateStrategyDocTemplate.java new file mode 100644 index 0000000..188df1f --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/HtmlPreviewTemplateStrategyDocTemplate.java @@ -0,0 +1,39 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +/** + * HTML预览策略实现 + * 直接返回HTML内容用于前端预览 + * + * @author hwc + */ +@Component("docTemplateHtmlPreviewTemplateStrategy") +@Slf4j +public class HtmlPreviewTemplateStrategyDocTemplate extends DocTemplateAbstractExportStrategy { + + @Override + protected Object doExport(String htmlContent, String fileName) { + log.debug("HTML预览,内容长度: {}", htmlContent.length()); + + // 直接返回HTML内容 + HtmlPreviewResult result = HtmlPreviewResult.builder() + .htmlContent(htmlContent) + .fileName(fileName) + .build(); + + log.debug("HTML预览完成"); + return result; + } + + @Override + protected String getFileExtension() { + return ".html"; + } + + @Override + public ExportType getExportType() { + return ExportType.HTML_PREVIEW; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/IExportStrategy.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/IExportStrategy.java new file mode 100644 index 0000000..c1507b2 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/IExportStrategy.java @@ -0,0 +1,26 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +/** + * 导出方式策略接口(策略模式) + * 定义所有导出方式的标准接口 + * + * @author hwc + */ +public interface IExportStrategy { + + /** + * 执行导出 + * + * @param htmlContent 渲染后的HTML内容 + * @param templateName 模板名称(用于生成文件名) + * @return 导出结果 + */ + Object export(String htmlContent, String templateName); + + /** + * 获取导出方式类型 + * + * @return 导出类型 + */ + ExportType getExportType(); +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportResult.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportResult.java new file mode 100644 index 0000000..d7068f4 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportResult.java @@ -0,0 +1,48 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import lombok.*; + +import java.io.InputStream; + +/** + * Word 导出结果 + * + * @author hwc + */ +@Data +@Builder +@NoArgsConstructor +@AllArgsConstructor +@ToString(exclude = "fileContent") +public class WordExportResult { + /** + * 文件路径(如果保存到服务器) + */ + private String filePath; + + /** + * 文件流(如果直接返回) + */ + private InputStream fileStream; + + /** + * 文件内容(字节数组) + */ + private byte[] fileContent; + + /** + * 文件名 + */ + private String fileName; + + /** + * 文件大小(字节) + */ + private Long fileSize; + + /** + * 内容类型 + */ + @Builder.Default + private String contentType = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportTemplateStrategyDocTemplate.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportTemplateStrategyDocTemplate.java new file mode 100644 index 0000000..e0433eb --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/export/WordExportTemplateStrategyDocTemplate.java @@ -0,0 +1,68 @@ +package com.zt.plat.module.base.service.doctemplate.render.export; + +import com.zt.plat.module.base.service.doctemplate.DocumentRenderApiService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import jakarta.annotation.Resource; + +import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zt.plat.module.base.enums.ErrorCodeConstants.*; + +/** + * Word 导出策略实现 + * 基于现有的 DocumentRenderApiService 进行扩展 + * + * @author hwc + */ +@Component("docTemplateWordExportTemplateStrategy") +@Slf4j +public class WordExportTemplateStrategyDocTemplate extends DocTemplateAbstractExportStrategy { + + @Resource + private DocumentRenderApiService documentRenderApiService; + + @Override + protected Object doExport(String htmlContent, String fileName) { + try { + log.debug("开始导出Word文档,文件名: {}", fileName); + + // 调用 DocumentRenderApiService 将HTML转换为Word + byte[] wordContent = documentRenderApiService.exportToWord(htmlContent, fileName); + + // 构建导出结果 + WordExportResult result = WordExportResult.builder() + .fileName(fileName + getFileExtension()) + .fileContent(wordContent) + .fileSize((long) wordContent.length) + .contentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document") + .build(); + + log.info("Word导出完成,文件名: {}, 大小: {} bytes", fileName, wordContent.length); + return result; + + } catch (Exception e) { + log.error("Word导出失败,文件名: {}", fileName, e); + throw exception(TEMPLATE_EXPORT_FAILED); + } + } + + @Override + protected String getFileExtension() { + return ".docx"; + } + + @Override + public ExportType getExportType() { + return ExportType.WORD; + } + + @Override + protected void postProcess(String fileName, Object result) { + // Word导出特定的后处理逻辑 + if (result instanceof WordExportResult) { + WordExportResult wordResult = (WordExportResult) result; + log.info("Word导出后处理完成: {}, 文件大小: {}字节", fileName, wordResult.getFileSize()); + } + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/ContractDataProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/ContractDataProvider.java new file mode 100644 index 0000000..0784eec --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/ContractDataProvider.java @@ -0,0 +1,90 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.IBusinessDataProvider; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 合同数据提供者 + * 提供合同相关的业务数据 + * + * @author hwc + */ +@Component("contractDataProvider") +@Slf4j +public class ContractDataProvider implements IBusinessDataProvider { + + @Override + public Map provideData(Object context, List fieldNames) { + Long contractId = null; + if (context instanceof Long) { + contractId = (Long) context; + } else if (context instanceof String) { + try { + contractId = Long.valueOf((String) context); + } catch (NumberFormatException e) { + log.warn("无效的合同ID格式: {}", context); + return Map.of(); + } + } + + if (contractId == null) { + log.warn("合同ID为空"); + return Map.of(); + } + + log.debug("获取合同数据,合同ID: {}, 请求字段: {}", contractId, fieldNames); + + // TODO: 实际实现应该: + // 1. 根据合同ID从数据库或服务中查询合同信息 + // 2. 根据请求的字段名返回对应的数据 + // 3. 处理数据类型转换 + + // 临时实现:返回一些示例数据 + Map data = new HashMap<>(); + + // 模拟合同数据 + for (String fieldName : fieldNames) { + switch (fieldName) { + case "contractName": + case "contract_name": + data.put(fieldName, "中铜采购合同-" + contractId); + break; + case "supplier": + case "supplier_name": + data.put(fieldName, "上海金属贸易有限公司"); + break; + case "amount": + case "total_amount": + data.put(fieldName, 5000000.00); + break; + case "deliveryDate": + case "delivery_date": + data.put(fieldName, "2025-06-30"); + break; + case "contractNumber": + case "contract_number": + data.put(fieldName, "HT-2025-" + String.format("%04d", contractId % 10000)); + break; + case "signDate": + case "sign_date": + data.put(fieldName, "2025-01-15"); + break; + case "paymentTerms": + case "payment_terms": + data.put(fieldName, "预付30%,发货付60%,验收付10%"); + break; + default: + log.debug("未处理的合同字段: {}", fieldName); + break; + } + } + + log.debug("返回合同数据: {}", data.keySet()); + return data; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateBusinessFieldDataSourceProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateBusinessFieldDataSourceProvider.java new file mode 100644 index 0000000..bbf63a6 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateBusinessFieldDataSourceProvider.java @@ -0,0 +1,49 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateDataSourceType; +import com.zt.plat.module.base.service.doctemplate.render.IBusinessDataProvider; +import com.zt.plat.module.base.service.doctemplate.render.IDataSourceProvider; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; + +/** + * 业务数据源提供者实现 + * 包装业务数据提供者,提供统一的数据源接口 + * + * @author hwc + */ +@Slf4j +public class DocTemplateBusinessFieldDataSourceProvider implements IDataSourceProvider { + + private final IBusinessDataProvider businessDataProvider; + + public DocTemplateBusinessFieldDataSourceProvider(IBusinessDataProvider businessDataProvider) { + this.businessDataProvider = businessDataProvider; + log.debug("创建业务数据源提供者: {}", businessDataProvider.getClass().getSimpleName()); + } + + @Override + public Map getData(Object context, List fieldNames) { + if (businessDataProvider == null) { + log.warn("业务数据提供者为空"); + return Map.of(); + } + + try { + log.debug("调用业务数据提供者获取数据,上下文: {}, 字段数量: {}", context, fieldNames.size()); + Map data = businessDataProvider.provideData(context, fieldNames); + log.debug("业务数据提供者返回数据: {}", data.keySet()); + return data; + } catch (Exception e) { + log.error("业务数据提供者获取数据失败", e); + return Map.of(); + } + } + + @Override + public DocTemplateDataSourceType getType() { + return DocTemplateDataSourceType.BUSINESS; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateDefaultFieldDataSourceProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateDefaultFieldDataSourceProvider.java new file mode 100644 index 0000000..eaead68 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateDefaultFieldDataSourceProvider.java @@ -0,0 +1,31 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateDataSourceType; +import com.zt.plat.module.base.service.doctemplate.render.IDataSourceProvider; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 标签默认值数据源实现 + * 返回空数据,表示使用字段自身的默认值 + * + * @author hwc + */ +@Slf4j +public class DocTemplateDefaultFieldDataSourceProvider implements IDataSourceProvider { + + @Override + public Map getData(Object context, List fieldNames) { + // 默认值由调用方在外层处理,这里返回空,表示不提供覆盖数据 + log.debug("默认值数据源被调用,字段数量: {}", fieldNames != null ? fieldNames.size() : 0); + return new HashMap<>(); + } + + @Override + public DocTemplateDataSourceType getType() { + return DocTemplateDataSourceType.DEFAULT; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateRequestFieldDataSourceProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateRequestFieldDataSourceProvider.java new file mode 100644 index 0000000..f919c02 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateRequestFieldDataSourceProvider.java @@ -0,0 +1,52 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateDataSourceType; +import com.zt.plat.module.base.service.doctemplate.render.IDataSourceProvider; +import lombok.extern.slf4j.Slf4j; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 前端入参数据源提供者实现 + * 从传入的请求数据中提取字段值 + * + * @author hwc + */ +@Slf4j +public class DocTemplateRequestFieldDataSourceProvider implements IDataSourceProvider { + + private final Map requestData; + + public DocTemplateRequestFieldDataSourceProvider(Map requestData) { + this.requestData = requestData != null ? requestData : Map.of(); + log.debug("创建前端入参数据源提供者,数据量: {}", this.requestData.size()); + } + + @Override + public Map getData(Object context, List fieldNames) { + Map result = new HashMap<>(); + + if (requestData == null || requestData.isEmpty()) { + log.debug("前端入参数据为空"); + return result; + } + + for (String fieldName : fieldNames) { + if (requestData.containsKey(fieldName)) { + Object value = requestData.get(fieldName); + result.put(fieldName, value); + log.debug("从前端入参获取字段值: {} = {}", fieldName, value); + } + } + + log.debug("前端入参数据源提供结果: {}", result.keySet()); + return result; + } + + @Override + public DocTemplateDataSourceType getType() { + return DocTemplateDataSourceType.REQUEST; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateSqlConfigFieldDataSourceProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateSqlConfigFieldDataSourceProvider.java new file mode 100644 index 0000000..dfd246b --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/DocTemplateSqlConfigFieldDataSourceProvider.java @@ -0,0 +1,60 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.DocTemplateDataSourceType; +import com.zt.plat.module.base.service.doctemplate.render.IDataSourceProvider; +import com.zt.plat.module.base.service.doctemplate.render.ISqlConfigProvider; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; +import java.util.Map; + +/** + * SQL配置数据源提供者实现 + * 执行SQL配置查询并返回结果 + * + * @author hwc + */ +@Slf4j +public class DocTemplateSqlConfigFieldDataSourceProvider implements IDataSourceProvider { + + private final ISqlConfigProvider sqlConfigProvider; + private final String sqlConfigId; + + public DocTemplateSqlConfigFieldDataSourceProvider(ISqlConfigProvider sqlConfigProvider, String sqlConfigId) { + this.sqlConfigProvider = sqlConfigProvider; + this.sqlConfigId = sqlConfigId; + log.debug("创建SQL配置数据源提供者: {}", sqlConfigId); + } + + @Override + public Map getData(Object context, List fieldNames) { + if (sqlConfigProvider == null) { + log.warn("SQL配置提供者为空"); + return Map.of(); + } + + try { + log.debug("执行SQL配置查询: {}", sqlConfigId); + List> results = sqlConfigProvider.querySqlConfig(sqlConfigId); + + if (results == null || results.isEmpty()) { + log.debug("SQL配置查询无结果"); + return Map.of(); + } + + // 返回第一行结果(通常SQL配置查询应该返回单行) + Map result = results.get(0); + log.debug("SQL配置查询结果字段: {}", result.keySet()); + return result; + + } catch (Exception e) { + log.error("SQL配置查询失败: {}", sqlConfigId, e); + return Map.of(); + } + } + + @Override + public DocTemplateDataSourceType getType() { + return DocTemplateDataSourceType.SQL_CONFIG; + } +} \ No newline at end of file diff --git a/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/OrderDataProvider.java b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/OrderDataProvider.java new file mode 100644 index 0000000..37839a8 --- /dev/null +++ b/zt-module-base/zt-module-base-server/src/main/java/com/zt/plat/module/base/service/doctemplate/render/provider/OrderDataProvider.java @@ -0,0 +1,90 @@ +package com.zt.plat.module.base.service.doctemplate.render.provider; + +import com.zt.plat.module.base.service.doctemplate.render.IBusinessDataProvider; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * 订单数据提供者 + * 提供订单相关的业务数据 + * + * @author hwc + */ +@Component("orderDataProvider") +@Slf4j +public class OrderDataProvider implements IBusinessDataProvider { + + @Override + public Map provideData(Object context, List fieldNames) { + Long orderId = null; + if (context instanceof Long) { + orderId = (Long) context; + } else if (context instanceof String) { + try { + orderId = Long.valueOf((String) context); + } catch (NumberFormatException e) { + log.warn("无效的订单ID格式: {}", context); + return Map.of(); + } + } + + if (orderId == null) { + log.warn("订单ID为空"); + return Map.of(); + } + + log.debug("获取订单数据,订单ID: {}, 请求字段: {}", orderId, fieldNames); + + // TODO: 实际实现应该: + // 1. 根据订单ID从数据库或服务中查询订单信息 + // 2. 根据请求的字段名返回对应的数据 + // 3. 处理数据类型转换 + + // 临时实现:返回一些示例数据 + Map data = new HashMap<>(); + + // 模拟订单数据 + for (String fieldName : fieldNames) { + switch (fieldName) { + case "orderNumber": + case "order_number": + data.put(fieldName, "ORD-2025-" + String.format("%06d", orderId % 100000)); + break; + case "customer": + case "customer_name": + data.put(fieldName, "中铜制造有限公司"); + break; + case "totalAmount": + case "total_amount": + data.put(fieldName, 2500000.00); + break; + case "orderDate": + case "order_date": + data.put(fieldName, "2025-01-20"); + break; + case "deliveryAddress": + case "delivery_address": + data.put(fieldName, "上海市浦东新区金桥路123号"); + break; + case "contactPerson": + case "contact_person": + data.put(fieldName, "张经理"); + break; + case "contactPhone": + case "contact_phone": + data.put(fieldName, "13800138000"); + break; + default: + log.debug("未处理的订单字段: {}", fieldName); + break; + } + } + + log.debug("返回订单数据: {}", data.keySet()); + return data; + } +} \ No newline at end of file