Merge remote-tracking branch 'origin/dev' into dev
This commit is contained in:
@@ -82,6 +82,16 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.seata</groupId>
|
||||||
|
<artifactId>seata-spring-boot-starter</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>druid</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ public interface ErrorCodeConstants {
|
|||||||
ErrorCode TEMPLATE_INSTANCE_NOT_EXISTS = new ErrorCode(1_006_004_001, "模板实例不存在");
|
ErrorCode TEMPLATE_INSTANCE_NOT_EXISTS = new ErrorCode(1_006_004_001, "模板实例不存在");
|
||||||
ErrorCode TEMPLATE_INSTANCE_CODE_DUPLICATE = new ErrorCode(1_006_004_002, "实例编码已存在");
|
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_PROPERTIES_NOT_EXISTS = new ErrorCode(1_027_101_001, "物料属性不存在");
|
||||||
ErrorCode MATERIAL_HAS_PROPERTIES_NOT_EXISTS = new ErrorCode(1_027_101_002, "物料持有属性不存在");
|
ErrorCode MATERIAL_HAS_PROPERTIES_NOT_EXISTS = new ErrorCode(1_027_101_002, "物料持有属性不存在");
|
||||||
|
|||||||
@@ -126,7 +126,20 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.apache.velocity</groupId>
|
<groupId>org.apache.velocity</groupId>
|
||||||
<artifactId>velocity-engine-core</artifactId>
|
<artifactId>velocity-engine-core</artifactId>
|
||||||
<version>2.3</version>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- docx4j - Word文档处理 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j-core</artifactId>
|
||||||
|
<version>11.4.11</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- docx4j JAXB 运行时支持 (Java 17 兼容) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.docx4j</groupId>
|
||||||
|
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
|
||||||
|
<version>11.4.11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<!-- 监控相关 -->
|
<!-- 监控相关 -->
|
||||||
|
|||||||
@@ -35,4 +35,6 @@ public class ElementPageReqVO extends PageParam {
|
|||||||
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
|
||||||
private LocalDateTime[] createTime;
|
private LocalDateTime[] createTime;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
}
|
}
|
||||||
@@ -44,4 +44,6 @@ public class ElementRespVO {
|
|||||||
@ExcelProperty("创建时间")
|
@ExcelProperty("创建时间")
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
}
|
}
|
||||||
@@ -34,4 +34,7 @@ public class ElementSaveReqVO {
|
|||||||
@NotEmpty(message = "品位单位不能为空")
|
@NotEmpty(message = "品位单位不能为空")
|
||||||
private String gradeUnit;
|
private String gradeUnit;
|
||||||
|
|
||||||
|
@Schema(description = "排序")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -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.controller.admin.doctemplate.vo.DocTemplateInstanceSaveReqVO;
|
||||||
import com.zt.plat.module.base.service.doctemplate.DocTemplateInstanceService;
|
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.DocTemplateRenderService;
|
||||||
|
import com.zt.plat.module.base.service.doctemplate.DocumentRenderApiService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@@ -38,6 +42,9 @@ public class DocTemplateInstanceController {
|
|||||||
@Resource
|
@Resource
|
||||||
private DocTemplateRenderService renderService;
|
private DocTemplateRenderService renderService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocumentRenderApiService documentRenderApiService;
|
||||||
|
|
||||||
@PostMapping("/create")
|
@PostMapping("/create")
|
||||||
@Operation(summary = "创建模板实例")
|
@Operation(summary = "创建模板实例")
|
||||||
@PreAuthorize("@ss.hasPermission('base:template-instance:create')")
|
@PreAuthorize("@ss.hasPermission('base:template-instance:create')")
|
||||||
@@ -104,4 +111,35 @@ public class DocTemplateInstanceController {
|
|||||||
return success(true);
|
return success(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/render-and-export-word")
|
||||||
|
@Operation(summary = "渲染实例并导出为Word")
|
||||||
|
@PreAuthorize("@ss.hasPermission('base:template-instance:query')")
|
||||||
|
public ResponseEntity<byte[]> 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<String, Object> 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<byte[]> 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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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<String> 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<String> previewWithBusinessData(@RequestBody BusinessPreviewRequest request) {
|
||||||
|
log.info("业务预览: {}", request.getTemplateInstanceId());
|
||||||
|
try {
|
||||||
|
Map<String, DocTemplateDataSourceType> 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<String> previewWithCustomData(@RequestBody CustomPreviewRequest request) {
|
||||||
|
log.info("自定义预览: {}", request.getTemplateInstanceId());
|
||||||
|
try {
|
||||||
|
Map<String, DocTemplateDataSourceType> 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<byte[]> 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<byte[]> exportWithBusinessData(@RequestBody BusinessExportRequest request) {
|
||||||
|
log.info("业务导出: {}", request.getTemplateInstanceId());
|
||||||
|
try {
|
||||||
|
Map<String, DocTemplateDataSourceType> 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<String, DocTemplateDataSourceType> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, String> fieldMappings;
|
||||||
|
}
|
||||||
@@ -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<String, String> fieldMappings;
|
||||||
|
}
|
||||||
@@ -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<String, Object> customData;
|
||||||
|
}
|
||||||
@@ -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<String, Object> customData;
|
||||||
|
}
|
||||||
@@ -34,9 +34,21 @@ public class DocTemplateCategoryRespVO {
|
|||||||
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
@Schema(description = "创建人", example = "admin")
|
@Schema(description = "创建人ID", example = "1")
|
||||||
private String creator;
|
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 = "子分类列表")
|
@Schema(description = "子分类列表")
|
||||||
private List<DocTemplateCategoryRespVO> children;
|
private List<DocTemplateCategoryRespVO> children;
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,24 @@ public class DocTemplateInstanceRespVO {
|
|||||||
@Schema(description = "状态", example = "draft")
|
@Schema(description = "状态", example = "draft")
|
||||||
private String status;
|
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)
|
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
private LocalDateTime createTime;
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ public class DocTemplateInstanceSaveReqVO {
|
|||||||
@Schema(description = "业务关联标签", example = "PC-2025-001")
|
@Schema(description = "业务关联标签", example = "PC-2025-001")
|
||||||
private String businessLabel;
|
private String businessLabel;
|
||||||
|
|
||||||
@Schema(description = "用户编辑后的内容", requiredMode = Schema.RequiredMode.REQUIRED)
|
@Schema(description = "用户编辑后的内容(创建时可为空,将自动从模板复制)")
|
||||||
@NotBlank(message = "内容不能为空")
|
|
||||||
private String editedContent;
|
private String editedContent;
|
||||||
|
|
||||||
@Schema(description = "渲染后的最终内容")
|
@Schema(description = "渲染后的最终内容")
|
||||||
@@ -46,4 +45,22 @@ public class DocTemplateInstanceSaveReqVO {
|
|||||||
@Schema(description = "状态", example = "draft")
|
@Schema(description = "状态", example = "draft")
|
||||||
private String status;
|
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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,11 +23,8 @@ public class DocTemplatePageReqVO extends PageParam {
|
|||||||
@Schema(description = "模板编码", example = "PO_CONTRACT_001")
|
@Schema(description = "模板编码", example = "PO_CONTRACT_001")
|
||||||
private String tmplCode;
|
private String tmplCode;
|
||||||
|
|
||||||
@Schema(description = "所属大类", example = "1")
|
@Schema(description = "所属分类", example = "1")
|
||||||
private Long bigCategoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
@Schema(description = "所属小类", example = "11")
|
|
||||||
private Long smallCategoryId;
|
|
||||||
|
|
||||||
@Schema(description = "状态(1=启用,0=停用,2=草稿)", example = "1")
|
@Schema(description = "状态(1=启用,0=停用,2=草稿)", example = "1")
|
||||||
private String enabled;
|
private String enabled;
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ public class DocTemplateRespVO {
|
|||||||
@Schema(description = "模板图标", example = "📄")
|
@Schema(description = "模板图标", example = "📄")
|
||||||
private String icon;
|
private String icon;
|
||||||
|
|
||||||
@Schema(description = "所属大类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
@Schema(description = "所属分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
private Long bigCategoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
@Schema(description = "所属小类", example = "11")
|
@Schema(description = "分类名称", example = "合同信息/采购合同")
|
||||||
private Long smallCategoryId;
|
private String categoryName;
|
||||||
|
|
||||||
@Schema(description = "版本号", example = "v1.2")
|
@Schema(description = "版本号", example = "v1.2")
|
||||||
private String version;
|
private String version;
|
||||||
|
|||||||
@@ -24,12 +24,9 @@ public class DocTemplateSaveReqVO {
|
|||||||
@Schema(description = "模板图标", example = "📄")
|
@Schema(description = "模板图标", example = "📄")
|
||||||
private String icon;
|
private String icon;
|
||||||
|
|
||||||
@Schema(description = "所属大类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
@Schema(description = "所属分类", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
|
||||||
@NotNull(message = "所属大类不能为空")
|
@NotNull(message = "所属分类不能为空")
|
||||||
private Long bigCategoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
@Schema(description = "所属小类", example = "11")
|
|
||||||
private Long smallCategoryId;
|
|
||||||
|
|
||||||
@Schema(description = "版本号", example = "v1.2")
|
@Schema(description = "版本号", example = "v1.2")
|
||||||
private String version;
|
private String version;
|
||||||
|
|||||||
@@ -19,8 +19,7 @@ public interface DocTemplateMapper extends BaseMapperX<DocTemplateDO> {
|
|||||||
return selectPage(reqVO, new LambdaQueryWrapperX<DocTemplateDO>()
|
return selectPage(reqVO, new LambdaQueryWrapperX<DocTemplateDO>()
|
||||||
.likeIfPresent(DocTemplateDO::getTmplName, reqVO.getTmplName())
|
.likeIfPresent(DocTemplateDO::getTmplName, reqVO.getTmplName())
|
||||||
.likeIfPresent(DocTemplateDO::getTmplCode, reqVO.getTmplCode())
|
.likeIfPresent(DocTemplateDO::getTmplCode, reqVO.getTmplCode())
|
||||||
.eqIfPresent(DocTemplateDO::getBigCategoryId, reqVO.getBigCategoryId())
|
.eqIfPresent(DocTemplateDO::getCategoryId, reqVO.getCategoryId())
|
||||||
.eqIfPresent(DocTemplateDO::getSmallCategoryId, reqVO.getSmallCategoryId())
|
|
||||||
.eqIfPresent(DocTemplateDO::getEnabled, reqVO.getEnabled())
|
.eqIfPresent(DocTemplateDO::getEnabled, reqVO.getEnabled())
|
||||||
.betweenIfPresent(DocTemplateDO::getCreateTime, reqVO.getCreateTime())
|
.betweenIfPresent(DocTemplateDO::getCreateTime, reqVO.getCreateTime())
|
||||||
.orderByDesc(DocTemplateDO::getId));
|
.orderByDesc(DocTemplateDO::getId));
|
||||||
|
|||||||
@@ -94,4 +94,7 @@ public class ElementDO extends BusinessBaseDO {
|
|||||||
@TableField("UPDATER_NAME")
|
@TableField("UPDATER_NAME")
|
||||||
private String updaterName;
|
private String updaterName;
|
||||||
|
|
||||||
|
@TableField("SORT")
|
||||||
|
private Integer sort;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -43,16 +43,10 @@ public class DocTemplateDO extends BusinessBaseDO {
|
|||||||
private String icon;
|
private String icon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 所属大类
|
* 所属分类ID(支持任意级别分类)
|
||||||
*/
|
*/
|
||||||
@TableField("big_category_id")
|
@TableField("category_id")
|
||||||
private Long bigCategoryId;
|
private Long categoryId;
|
||||||
|
|
||||||
/**
|
|
||||||
* 所属小类
|
|
||||||
*/
|
|
||||||
@TableField("small_category_id")
|
|
||||||
private Long smallCategoryId;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 版本号
|
* 版本号
|
||||||
|
|||||||
@@ -84,4 +84,40 @@ public class DocTemplateInstanceDO extends BusinessBaseDO {
|
|||||||
@TableField("status")
|
@TableField("status")
|
||||||
private String 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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,13 +26,14 @@ public interface ElementMapper extends BaseMapperX<ElementDO> {
|
|||||||
.likeIfPresent(ElementDO::getCoding, reqVO.getCoding())
|
.likeIfPresent(ElementDO::getCoding, reqVO.getCoding())
|
||||||
.eqIfPresent(ElementDO::getGradeUnit, reqVO.getGradeUnit())
|
.eqIfPresent(ElementDO::getGradeUnit, reqVO.getGradeUnit())
|
||||||
.betweenIfPresent(ElementDO::getCreateTime, reqVO.getCreateTime())
|
.betweenIfPresent(ElementDO::getCreateTime, reqVO.getCreateTime())
|
||||||
.orderByDesc(ElementDO::getId));
|
.orderByDesc(ElementDO::getSort));
|
||||||
}
|
}
|
||||||
|
|
||||||
String selectMaxCode();
|
String selectMaxCode();
|
||||||
|
|
||||||
default List<ElementDO> getElementNoPage() {
|
default List<ElementDO> getElementNoPage() {
|
||||||
return selectList(new LambdaQueryWrapperX<ElementDO>()
|
return selectList(new LambdaQueryWrapperX<ElementDO>()
|
||||||
.orderByDesc(ElementDO::getId));
|
.eq(ElementDO::getIsEnable, 1)
|
||||||
|
.orderByDesc(ElementDO::getSort));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -49,6 +49,37 @@ public class DocTemplateInstanceServiceImpl implements DocTemplateInstanceServic
|
|||||||
|
|
||||||
// 插入实例
|
// 插入实例
|
||||||
DocTemplateInstanceDO templateInstance = DocTemplateInstanceConvert.INSTANCE.convert(createReqVO);
|
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);
|
docTemplateInstanceMapper.insert(templateInstance);
|
||||||
|
|
||||||
// 更新模板使用次数
|
// 更新模板使用次数
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ public class DocTemplateServiceImpl implements DocTemplateService {
|
|||||||
@Resource
|
@Resource
|
||||||
private DocTemplateMapper templateMapper;
|
private DocTemplateMapper templateMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocTemplateCategoryService templateCategoryService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Long createTemplate(DocTemplateSaveReqVO createReqVO) {
|
public Long createTemplate(DocTemplateSaveReqVO createReqVO) {
|
||||||
// 校验模板编码唯一性
|
// 校验模板编码唯一性
|
||||||
@@ -37,12 +40,13 @@ public class DocTemplateServiceImpl implements DocTemplateService {
|
|||||||
|
|
||||||
// 插入
|
// 插入
|
||||||
DocTemplateDO template = DocTemplateConvert.INSTANCE.convert(createReqVO);
|
DocTemplateDO template = DocTemplateConvert.INSTANCE.convert(createReqVO);
|
||||||
|
|
||||||
// 设置默认值
|
// 设置默认值
|
||||||
if (template.getUseCount() == null) {
|
if (template.getUseCount() == null) {
|
||||||
template.setUseCount(0);
|
template.setUseCount(0);
|
||||||
}
|
}
|
||||||
if (template.getEnabled() == null) {
|
if (template.getEnabled() == null) {
|
||||||
template.setEnabled("2"); // 默认为草稿状态
|
template.setEnabled("0"); // 默认为禁用状态 (0=禁用, 1=启用)
|
||||||
}
|
}
|
||||||
templateMapper.insert(template);
|
templateMapper.insert(template);
|
||||||
// 返回
|
// 返回
|
||||||
@@ -97,19 +101,43 @@ public class DocTemplateServiceImpl implements DocTemplateService {
|
|||||||
@Override
|
@Override
|
||||||
public DocTemplateRespVO getTemplate(Long id) {
|
public DocTemplateRespVO getTemplate(Long id) {
|
||||||
DocTemplateDO template = templateMapper.selectById(id);
|
DocTemplateDO template = templateMapper.selectById(id);
|
||||||
return DocTemplateConvert.INSTANCE.convert(template);
|
DocTemplateRespVO respVO = DocTemplateConvert.INSTANCE.convert(template);
|
||||||
}
|
// 填充分类名称
|
||||||
|
fillCategoryName(respVO);
|
||||||
@Override
|
return respVO;
|
||||||
public PageResult<DocTemplateRespVO> getTemplatePage(DocTemplatePageReqVO pageReqVO) {
|
|
||||||
PageResult<DocTemplateDO> pageResult = templateMapper.selectPage(pageReqVO);
|
|
||||||
return DocTemplateConvert.INSTANCE.convertPage(pageResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<DocTemplateRespVO> getTemplateList() {
|
public List<DocTemplateRespVO> getTemplateList() {
|
||||||
List<DocTemplateDO> list = templateMapper.selectList();
|
List<DocTemplateDO> list = templateMapper.selectList();
|
||||||
return DocTemplateConvert.INSTANCE.convertList(list);
|
List<DocTemplateRespVO> result = DocTemplateConvert.INSTANCE.convertList(list);
|
||||||
|
// 填充分类名称
|
||||||
|
result.forEach(this::fillCategoryName);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PageResult<DocTemplateRespVO> getTemplatePage(DocTemplatePageReqVO reqVO) {
|
||||||
|
PageResult<DocTemplateDO> pageResult = templateMapper.selectPage(reqVO);
|
||||||
|
PageResult<DocTemplateRespVO> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package com.zt.plat.module.base.service.doctemplate;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档渲染API服务
|
||||||
|
* 供业务模块调用,支持多种渲染方式
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
public interface DocumentRenderApiService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据模板ID渲染 (方式1:直接模板渲染)
|
||||||
|
*
|
||||||
|
* @param templateId 模板ID
|
||||||
|
* @param dataMap 数据Map
|
||||||
|
* @return 渲染后的HTML
|
||||||
|
*/
|
||||||
|
String renderByTemplate(Long templateId, Map<String, Object> dataMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据实例ID渲染 (方式2:实例渲染)
|
||||||
|
* 优先使用实例的editedContent,如果为空则使用模板内容
|
||||||
|
*
|
||||||
|
* @param instanceId 实例ID
|
||||||
|
* @param dataMap 数据Map
|
||||||
|
* @return 渲染后的HTML
|
||||||
|
*/
|
||||||
|
String renderByInstance(Long instanceId, Map<String, Object> dataMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据业务类型渲染 (方式3:业务接入渲染)
|
||||||
|
* 业务系统可根据业务类型自定义数据集和渲染逻辑
|
||||||
|
*
|
||||||
|
* @param instanceId 实例ID
|
||||||
|
* @param businessType 业务类型 (如: 'PURCHASE_ORDER', 'SALES_ORDER' 等)
|
||||||
|
* @param businessDataMap 业务数据Map (由业务系统自己组织)
|
||||||
|
* @return 渲染后的HTML
|
||||||
|
*/
|
||||||
|
String renderByBusinessType(Long instanceId, String businessType, Map<String, Object> businessDataMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据直接内容渲染 (方式4:前端预览)
|
||||||
|
* 用于前端编辑时的实时预览,使用标签默认值
|
||||||
|
*
|
||||||
|
* @param content 模板内容 (HTML/Velocity语法)
|
||||||
|
* @param dataMap 数据Map (标签默认值)
|
||||||
|
* @return 渲染后的HTML
|
||||||
|
*/
|
||||||
|
String renderByContent(String content, Map<String, Object> dataMap);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将HTML导出为Word文档
|
||||||
|
*
|
||||||
|
* @param html HTML内容
|
||||||
|
* @param fileName 文件名 (不需要后缀,自动添加.docx)
|
||||||
|
* @return Word文件字节数组
|
||||||
|
*/
|
||||||
|
byte[] exportToWord(String html, String fileName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 渲染并导出为Word (一步完成)
|
||||||
|
*
|
||||||
|
* @param instanceId 实例ID
|
||||||
|
* @param dataMap 数据Map
|
||||||
|
* @param fileName 导出文件名
|
||||||
|
* @return Word文件字节数组
|
||||||
|
*/
|
||||||
|
byte[] renderAndExportToWord(Long instanceId, Map<String, Object> dataMap, String fileName);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,490 @@
|
|||||||
|
package com.zt.plat.module.base.service.doctemplate;
|
||||||
|
|
||||||
|
import cn.hutool.core.util.StrUtil;
|
||||||
|
import com.zt.plat.module.base.dal.dataobject.doctemplate.DocTemplateDO;
|
||||||
|
import com.zt.plat.module.base.dal.dataobject.doctemplate.DocTemplateInstanceDO;
|
||||||
|
import com.zt.plat.module.base.dal.dao.doctemplate.DocTemplateMapper;
|
||||||
|
import com.zt.plat.module.base.dal.dao.doctemplate.DocTemplateInstanceMapper;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.docx4j.Docx4J;
|
||||||
|
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
|
||||||
|
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
|
||||||
|
import org.docx4j.wml.*;
|
||||||
|
import org.jsoup.Jsoup;
|
||||||
|
import org.jsoup.nodes.Document;
|
||||||
|
import org.jsoup.nodes.Element;
|
||||||
|
import org.jsoup.nodes.Node;
|
||||||
|
import org.jsoup.nodes.TextNode;
|
||||||
|
import org.jsoup.select.Elements;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static com.zt.plat.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
|
import static com.zt.plat.module.base.enums.ErrorCodeConstants.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 文档渲染API服务实现类
|
||||||
|
* 使用 docx4j 库处理 Word 导出,支持多种渲染方式
|
||||||
|
*
|
||||||
|
* @author system
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
@Slf4j
|
||||||
|
public class DocumentRenderApiServiceImpl implements DocumentRenderApiService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocTemplateRenderService templateRenderService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocTemplateMapper templateMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private DocTemplateInstanceMapper instanceMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renderByTemplate(Long templateId, Map<String, Object> dataMap) {
|
||||||
|
if (templateId == null) {
|
||||||
|
throw new IllegalArgumentException("模板ID不能为空");
|
||||||
|
}
|
||||||
|
DocTemplateDO template = templateMapper.selectById(templateId);
|
||||||
|
if (template == null) {
|
||||||
|
throw exception(TEMPLATE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return templateRenderService.render(templateId, null, null, dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renderByInstance(Long instanceId, Map<String, Object> dataMap) {
|
||||||
|
if (instanceId == null) {
|
||||||
|
throw new IllegalArgumentException("实例ID不能为空");
|
||||||
|
}
|
||||||
|
DocTemplateInstanceDO instance = instanceMapper.selectById(instanceId);
|
||||||
|
if (instance == null) {
|
||||||
|
throw exception(TEMPLATE_INSTANCE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
return templateRenderService.render(null, instanceId, null, dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renderByBusinessType(Long instanceId, String businessType, Map<String, Object> businessDataMap) {
|
||||||
|
if (instanceId == null) {
|
||||||
|
throw new IllegalArgumentException("实例ID不能为空");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(businessType)) {
|
||||||
|
throw new IllegalArgumentException("业务类型不能为空");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取实例信息
|
||||||
|
DocTemplateInstanceDO instance = instanceMapper.selectById(instanceId);
|
||||||
|
if (instance == null) {
|
||||||
|
throw exception(TEMPLATE_INSTANCE_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 业务系统自己组织的dataMap,可以包含SQL查询结果、业务数据等
|
||||||
|
// 直接使用该dataMap进行渲染
|
||||||
|
if (businessDataMap == null || businessDataMap.isEmpty()) {
|
||||||
|
log.warn("业务数据集为空,instanceId: {}, businessType: {}", instanceId, businessType);
|
||||||
|
businessDataMap = Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return templateRenderService.render(null, instanceId, null, businessDataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renderByContent(String content, Map<String, Object> dataMap) {
|
||||||
|
if (StrUtil.isBlank(content)) {
|
||||||
|
throw new IllegalArgumentException("模板内容不能为空");
|
||||||
|
}
|
||||||
|
return templateRenderService.render(null, null, content, dataMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] exportToWord(String html, String fileName) {
|
||||||
|
if (StrUtil.isBlank(html)) {
|
||||||
|
throw new IllegalArgumentException("HTML内容不能为空");
|
||||||
|
}
|
||||||
|
if (StrUtil.isBlank(fileName)) {
|
||||||
|
fileName = "document";
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 创建 Word 文档
|
||||||
|
WordprocessingMLPackage wordPackage = WordprocessingMLPackage.createPackage();
|
||||||
|
MainDocumentPart mainDocumentPart = wordPackage.getMainDocumentPart();
|
||||||
|
|
||||||
|
// 解析 HTML
|
||||||
|
Document htmlDoc = Jsoup.parse(html);
|
||||||
|
|
||||||
|
// 处理 HTML 内容并添加到 Word 文档
|
||||||
|
processHtmlToWord(mainDocumentPart, htmlDoc.body());
|
||||||
|
|
||||||
|
// 转换为字节数组
|
||||||
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
|
wordPackage.save(baos);
|
||||||
|
|
||||||
|
log.info("Word导出成功,文件名: {}.docx, 大小: {} bytes", fileName, baos.size());
|
||||||
|
return baos.toByteArray();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Word导出失败,fileName: {}", fileName, e);
|
||||||
|
throw new RuntimeException("Word导出失败: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] renderAndExportToWord(Long instanceId, Map<String, Object> dataMap, String fileName) {
|
||||||
|
// 先渲染获取HTML
|
||||||
|
String html = renderByInstance(instanceId, dataMap);
|
||||||
|
// 再导出为Word
|
||||||
|
return exportToWord(html, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将 HTML 转换为 Word 文档内容
|
||||||
|
* 递归处理 HTML 节点,使用 docx4j API 构建完整的 Word 文档
|
||||||
|
* 增强版本:支持CSS样式保留(颜色、对齐、字体等)
|
||||||
|
*/
|
||||||
|
private void processHtmlToWord(MainDocumentPart mainDocumentPart, Element element) throws Exception {
|
||||||
|
for (Node node : element.childNodes()) {
|
||||||
|
if (node instanceof TextNode) {
|
||||||
|
String text = ((TextNode) node).getWholeText();
|
||||||
|
if (StrUtil.isNotBlank(text.trim())) {
|
||||||
|
mainDocumentPart.addStyledParagraphOfText("Normal", text.trim());
|
||||||
|
}
|
||||||
|
} else if (node instanceof Element) {
|
||||||
|
Element element1 = (Element) node;
|
||||||
|
String tagName = element1.tagName().toLowerCase();
|
||||||
|
|
||||||
|
switch (tagName) {
|
||||||
|
case "h1", "h2", "h3", "h4", "h5", "h6" -> {
|
||||||
|
// 使用带样式的段落处理,而不是使用预定义样式
|
||||||
|
addStyledParagraph(mainDocumentPart, element1, true);
|
||||||
|
}
|
||||||
|
case "p" -> {
|
||||||
|
addStyledParagraph(mainDocumentPart, element1, false);
|
||||||
|
}
|
||||||
|
case "br" -> {
|
||||||
|
mainDocumentPart.addParagraphOfText("");
|
||||||
|
}
|
||||||
|
case "table" -> {
|
||||||
|
processTable(mainDocumentPart, (Element) node);
|
||||||
|
}
|
||||||
|
case "ul", "ol" -> {
|
||||||
|
processHtmlToWord(mainDocumentPart, element1);
|
||||||
|
}
|
||||||
|
case "li" -> {
|
||||||
|
addStyledParagraph(mainDocumentPart, element1, false);
|
||||||
|
}
|
||||||
|
case "strong", "b" -> {
|
||||||
|
addStyledParagraph(mainDocumentPart, element1, false);
|
||||||
|
}
|
||||||
|
case "i", "em" -> {
|
||||||
|
addStyledParagraph(mainDocumentPart, element1, false);
|
||||||
|
}
|
||||||
|
case "div", "section", "article" -> {
|
||||||
|
processHtmlToWord(mainDocumentPart, element1);
|
||||||
|
}
|
||||||
|
default -> {
|
||||||
|
processHtmlToWord(mainDocumentPart, element1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 添加带样式的段落(支持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 表格
|
||||||
|
*/
|
||||||
|
private void processTable(MainDocumentPart mainDocumentPart, Element tableElement) throws Exception {
|
||||||
|
Elements rows = tableElement.select("tr");
|
||||||
|
if (rows.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ObjectFactory factory = new ObjectFactory();
|
||||||
|
Tbl tbl = factory.createTbl();
|
||||||
|
|
||||||
|
// 设置表格属性
|
||||||
|
TblPr tblPr = factory.createTblPr();
|
||||||
|
tbl.setTblPr(tblPr);
|
||||||
|
|
||||||
|
// 处理每一行
|
||||||
|
for (Element row : rows) {
|
||||||
|
Elements cells = row.select("td, th");
|
||||||
|
Tr tr = factory.createTr();
|
||||||
|
|
||||||
|
for (Element cell : cells) {
|
||||||
|
Tc tc = factory.createTc();
|
||||||
|
TcPr tcPr = factory.createTcPr();
|
||||||
|
tc.setTcPr(tcPr);
|
||||||
|
|
||||||
|
// 添加单元格内容
|
||||||
|
P cellParagraph = factory.createP();
|
||||||
|
R cellRun = factory.createR();
|
||||||
|
Text cellText = factory.createText();
|
||||||
|
cellText.setValue(cell.text());
|
||||||
|
cellRun.getContent().add(cellText);
|
||||||
|
cellParagraph.getContent().add(cellRun);
|
||||||
|
tc.getContent().add(cellParagraph);
|
||||||
|
|
||||||
|
tr.getContent().add(tc);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbl.getContent().add(tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将表格添加到文档
|
||||||
|
mainDocumentPart.getContent().add(tbl);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("表格处理失败,跳过表格内容", e);
|
||||||
|
// 降级处理:将表格内容作为文本添加
|
||||||
|
for (Element row : rows) {
|
||||||
|
Elements cells = row.select("td, th");
|
||||||
|
StringBuilder rowText = new StringBuilder();
|
||||||
|
for (Element cell : cells) {
|
||||||
|
rowText.append(cell.text()).append(" | ");
|
||||||
|
}
|
||||||
|
mainDocumentPart.addParagraphOfText(rowText.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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<Map<String, Object>> querySqlConfig(String sqlConfigId) {
|
||||||
|
log.debug("查询SQL配置: {}", sqlConfigId);
|
||||||
|
|
||||||
|
// TODO: 实际实现应该:
|
||||||
|
// 1. 从数据库中根据 sqlConfigId 查询SQL脚本
|
||||||
|
// 2. 执行SQL脚本并返回结果
|
||||||
|
// 3. 处理异常情况
|
||||||
|
|
||||||
|
// 临时实现:返回一些示例数据
|
||||||
|
List<Map<String, Object>> result = new ArrayList<>();
|
||||||
|
|
||||||
|
switch (sqlConfigId) {
|
||||||
|
case "CONTRACT_INFO":
|
||||||
|
Map<String, Object> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<DocTemplateTagDO> tags = getTemplateTags(instance);
|
||||||
|
|
||||||
|
if (tags == null || tags.isEmpty()) {
|
||||||
|
log.warn("模板实例 {} 没有关联的标签定义", request.getTemplateInstanceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, DocTemplateTagDO> tagMap = tags.stream()
|
||||||
|
.collect(Collectors.toMap(DocTemplateTagDO::getTagCode, t -> t));
|
||||||
|
|
||||||
|
// 3. 收集所有需要填充的字段值
|
||||||
|
Map<String, Object> 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<String, Object> 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<DocTemplateTagDO> getTemplateTags(DocTemplateInstanceRespVO instance) {
|
||||||
|
if (instance.getCategoryId() == null) {
|
||||||
|
log.warn("模板实例 {} 没有关联分类ID", instance.getId());
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 通过 categoryId 查询该分类下的所有标签
|
||||||
|
List<DocTemplateTagDO> tags = docTemplateTagMapper.selectList(
|
||||||
|
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<DocTemplateTagDO>()
|
||||||
|
.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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> 类型的请求数据
|
||||||
|
return new DocTemplateRequestFieldDataSourceProvider((java.util.Map<String, Object>) 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.zt.plat.module.base.service.doctemplate.render;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 业务数据提供者接口
|
||||||
|
* 不同的业务类型实现此接口来提供数据
|
||||||
|
*
|
||||||
|
* <p>命名约定:{业务类型}DataProvider
|
||||||
|
* <p>示例:
|
||||||
|
* <ul>
|
||||||
|
* <li>ContractDataProvider (合同数据提供者)</li>
|
||||||
|
* <li>OrderDataProvider (订单数据提供者)</li>
|
||||||
|
* <li>PurchaseOrderDataProvider (采购单数据提供者)</li>
|
||||||
|
* <li>SalesOrderDataProvider (销售单数据提供者)</li>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* @author hwc
|
||||||
|
*/
|
||||||
|
public interface IBusinessDataProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取业务数据
|
||||||
|
*
|
||||||
|
* @param context 业务上下文(通常是业务ID,如contractId、orderId等)
|
||||||
|
* @param fieldNames 需要填充的字段名列表
|
||||||
|
* @return 字段值映射表
|
||||||
|
*/
|
||||||
|
Map<String, Object> provideData(Object context, java.util.List<String> fieldNames);
|
||||||
|
}
|
||||||
@@ -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<String, Object> getData(Object context, java.util.List<String> fieldNames);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取此数据源的类型
|
||||||
|
*
|
||||||
|
* @return 数据源类型
|
||||||
|
*/
|
||||||
|
DocTemplateDataSourceType getType();
|
||||||
|
}
|
||||||
@@ -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<Map<String, Object>> querySqlConfig(String sqlConfigId);
|
||||||
|
}
|
||||||
@@ -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<String, Object> data);
|
||||||
|
}
|
||||||
@@ -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<String, DocTemplateDataSourceType> fieldDataSources;
|
||||||
|
private Object dataSourceContext;
|
||||||
|
private ExportType exportType = ExportType.WORD;
|
||||||
|
private String businessType;
|
||||||
|
}
|
||||||
@@ -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<String, Object> 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}");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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();
|
||||||
|
}
|
||||||
@@ -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";
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> provideData(Object context, List<String> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> getData(Object context, List<String> fieldNames) {
|
||||||
|
if (businessDataProvider == null) {
|
||||||
|
log.warn("业务数据提供者为空");
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("调用业务数据提供者获取数据,上下文: {}, 字段数量: {}", context, fieldNames.size());
|
||||||
|
Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> getData(Object context, List<String> fieldNames) {
|
||||||
|
// 默认值由调用方在外层处理,这里返回空,表示不提供覆盖数据
|
||||||
|
log.debug("默认值数据源被调用,字段数量: {}", fieldNames != null ? fieldNames.size() : 0);
|
||||||
|
return new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DocTemplateDataSourceType getType() {
|
||||||
|
return DocTemplateDataSourceType.DEFAULT;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> requestData;
|
||||||
|
|
||||||
|
public DocTemplateRequestFieldDataSourceProvider(Map<String, Object> requestData) {
|
||||||
|
this.requestData = requestData != null ? requestData : Map.of();
|
||||||
|
log.debug("创建前端入参数据源提供者,数据量: {}", this.requestData.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, Object> getData(Object context, List<String> fieldNames) {
|
||||||
|
Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> getData(Object context, List<String> fieldNames) {
|
||||||
|
if (sqlConfigProvider == null) {
|
||||||
|
log.warn("SQL配置提供者为空");
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
log.debug("执行SQL配置查询: {}", sqlConfigId);
|
||||||
|
List<Map<String, Object>> results = sqlConfigProvider.querySqlConfig(sqlConfigId);
|
||||||
|
|
||||||
|
if (results == null || results.isEmpty()) {
|
||||||
|
log.debug("SQL配置查询无结果");
|
||||||
|
return Map.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回第一行结果(通常SQL配置查询应该返回单行)
|
||||||
|
Map<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<String, Object> provideData(Object context, List<String> 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<String, Object> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import com.zt.plat.module.contractorder.api.dto.order.PurchaseOrderWithDetailsDT
|
|||||||
import com.zt.plat.module.contractorder.api.dto.order.SalesOrdDtlDTO;
|
import com.zt.plat.module.contractorder.api.dto.order.SalesOrdDtlDTO;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContract;
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContract;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContractPageReq;
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContractPageReq;
|
||||||
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntPushContractReqVO;
|
||||||
import com.zt.plat.module.contractorder.enums.ApiConstants;
|
import com.zt.plat.module.contractorder.enums.ApiConstants;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -44,7 +45,7 @@ public interface ContractApi {
|
|||||||
|
|
||||||
@PostMapping(PREFIX + "/push")
|
@PostMapping(PREFIX + "/push")
|
||||||
@Operation(summary = "国贸2.0系统推送合同")
|
@Operation(summary = "国贸2.0系统推送合同")
|
||||||
CommonResult<Boolean> push(@Valid @RequestBody IntContract reqVO);
|
void push(@Valid @RequestBody IntPushContractReqVO pushReqVO);
|
||||||
|
|
||||||
@GetMapping(PREFIX + "/logistics/list/page")
|
@GetMapping(PREFIX + "/logistics/list/page")
|
||||||
@Operation(summary = "国贸2.0系统合同分页查询")
|
@Operation(summary = "国贸2.0系统合同分页查询")
|
||||||
|
|||||||
@@ -236,6 +236,9 @@ public class ContractRespDTO {
|
|||||||
@Schema(description = "代理方名称")
|
@Schema(description = "代理方名称")
|
||||||
private String agentName;
|
private String agentName;
|
||||||
|
|
||||||
|
@Schema(description = "货权转移类型(字典:ASY_MTNG_TP)")
|
||||||
|
private String meteringType;
|
||||||
|
|
||||||
// 物料信息
|
// 物料信息
|
||||||
private List<DetailRespDTO> detail;
|
private List<DetailRespDTO> detail;
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,9 @@ public class ContractRespVO {
|
|||||||
@Schema(description = "代理方名称")
|
@Schema(description = "代理方名称")
|
||||||
private String agentName;
|
private String agentName;
|
||||||
|
|
||||||
|
@Schema(description = "货权转移类型(字典:ASY_MTNG_TP)")
|
||||||
|
private String meteringType;
|
||||||
|
|
||||||
// 物料信息
|
// 物料信息
|
||||||
private List<DetailRespVO> detail;
|
private List<DetailRespVO> detail;
|
||||||
|
|
||||||
|
|||||||
@@ -211,6 +211,9 @@ public class ContractSaveReqVO {
|
|||||||
@Schema(description = "代理方名称")
|
@Schema(description = "代理方名称")
|
||||||
private String agentName;
|
private String agentName;
|
||||||
|
|
||||||
|
@Schema(description = "货权转移类型(字典:ASY_MTNG_TP)")
|
||||||
|
private String meteringType;
|
||||||
|
|
||||||
// 物料信息
|
// 物料信息
|
||||||
private List<DetailSaveReqVO> detail;
|
private List<DetailSaveReqVO> detail;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package com.zt.plat.module.contractorder.api.vo.contract.international;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "国贸2.0系统推送合同 Request VO")
|
||||||
|
@Data
|
||||||
|
public class IntPushContractReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "接口请求号")
|
||||||
|
private String __requestId_;
|
||||||
|
@Schema(description = "接口类型")
|
||||||
|
private String __interfaceType__;
|
||||||
|
@Schema(description = "操作标志")
|
||||||
|
private String operateFlag;
|
||||||
|
@Schema(description = "发送时间 yyyyMMddHHmmss")
|
||||||
|
private String datetime;
|
||||||
|
@Schema(description = "单据号")
|
||||||
|
private String busiBillCode;
|
||||||
|
@Schema(description = "发送方系统")
|
||||||
|
private String system;
|
||||||
|
@Schema(description = "发送数据")
|
||||||
|
private IntContract data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.zt.plat.module.contractorder.api.vo.contract.international;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Schema(description = "国贸2.0系统推送合同 Response VO")
|
||||||
|
@Data
|
||||||
|
public class IntPushContractRespVO {
|
||||||
|
|
||||||
|
@Schema(description = "接口请求号")
|
||||||
|
private String __requestId_;
|
||||||
|
@Schema(description = "接口类型")
|
||||||
|
private String __interfaceType__;
|
||||||
|
@Schema(description = "单据号")
|
||||||
|
private String busiBillCode;
|
||||||
|
@Schema(description = "返回状态")
|
||||||
|
private Integer code;
|
||||||
|
@Schema(description = "返回信息")
|
||||||
|
private String message;
|
||||||
|
@Schema(description = "返回时间 yyyyMMddHHmmss")
|
||||||
|
private String datetime;
|
||||||
|
@Schema(description = "返回方系统")
|
||||||
|
private String system;
|
||||||
|
@Schema(description = "操作标志")
|
||||||
|
private String operateFlag;
|
||||||
|
@Schema(description = "返回数据")
|
||||||
|
private JSONObject data;
|
||||||
|
}
|
||||||
@@ -11,9 +11,7 @@ import com.zt.plat.module.contractorder.api.dto.contract.ContractRespDTO;
|
|||||||
import com.zt.plat.module.contractorder.api.dto.order.PrchOrdDtlDTO;
|
import com.zt.plat.module.contractorder.api.dto.order.PrchOrdDtlDTO;
|
||||||
import com.zt.plat.module.contractorder.api.dto.order.PurchaseOrderWithDetailsDTO;
|
import com.zt.plat.module.contractorder.api.dto.order.PurchaseOrderWithDetailsDTO;
|
||||||
import com.zt.plat.module.contractorder.api.dto.order.SalesOrdDtlDTO;
|
import com.zt.plat.module.contractorder.api.dto.order.SalesOrdDtlDTO;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContract;
|
import com.zt.plat.module.contractorder.api.vo.contract.international.*;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContractPageReq;
|
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.Partner;
|
|
||||||
import com.zt.plat.module.contractorder.controller.admin.purchaseorder.vo.PurchaseOrderDetailsRespVO;
|
import com.zt.plat.module.contractorder.controller.admin.purchaseorder.vo.PurchaseOrderDetailsRespVO;
|
||||||
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractMainDO;
|
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractMainDO;
|
||||||
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractOtherFieldDO;
|
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractOtherFieldDO;
|
||||||
@@ -33,10 +31,12 @@ import com.zt.plat.module.contractorder.dal.mysql.salesorder.SalesOrderMapper;
|
|||||||
import com.zt.plat.module.contractorder.enums.contract.DictEnum;
|
import com.zt.plat.module.contractorder.enums.contract.DictEnum;
|
||||||
import com.zt.plat.module.contractorder.service.contract.ContractService;
|
import com.zt.plat.module.contractorder.service.contract.ContractService;
|
||||||
import com.zt.plat.module.contractorder.service.purchaseorder.PurchaseOrderService;
|
import com.zt.plat.module.contractorder.service.purchaseorder.PurchaseOrderService;
|
||||||
|
import com.zt.plat.module.contractorder.util.ShareServiceUtil;
|
||||||
import com.zt.plat.module.erp.controller.admin.erp.vo.ErpContractSaveReqVO;
|
import com.zt.plat.module.erp.controller.admin.erp.vo.ErpContractSaveReqVO;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
@@ -73,6 +73,8 @@ public class ContractApiImpl implements ContractApi {
|
|||||||
private ContractOtherFieldMapper contractOtherFieldMapper;
|
private ContractOtherFieldMapper contractOtherFieldMapper;
|
||||||
@Resource
|
@Resource
|
||||||
private SystemRelativityMapper systemRelativityMapper;
|
private SystemRelativityMapper systemRelativityMapper;
|
||||||
|
@Autowired
|
||||||
|
private ShareServiceUtil shareServiceUtil;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ContractRespDTO getContractByPaperNumber(String contractPaperNumber) {
|
public ContractRespDTO getContractByPaperNumber(String contractPaperNumber) {
|
||||||
@@ -158,100 +160,106 @@ public class ContractApiImpl implements ContractApi {
|
|||||||
|
|
||||||
@Transactional
|
@Transactional
|
||||||
@Override
|
@Override
|
||||||
public CommonResult<Boolean> push(@RequestBody IntContract reqVO) {
|
public void push(@RequestBody IntPushContractReqVO pushReqVO) {
|
||||||
|
|
||||||
// 合同主信息表映射
|
log.info("接受到国贸系统推送的合同数据:{}", new JSONObject(pushReqVO));
|
||||||
ContractMainDO contractMainDO = internationalToMainDO(reqVO);
|
try {
|
||||||
|
// 合同主信息表映射
|
||||||
|
ContractMainDO contractMainDO = internationalToMainDO(pushReqVO.getData());
|
||||||
|
|
||||||
// 逻辑处理
|
// 逻辑处理
|
||||||
// 操作标志 I 新增/更新;D 删除
|
// 操作标志 I 新增/更新;D 删除
|
||||||
String operateFlag = reqVO.getOperateFlag();
|
String operateFlag = pushReqVO.getData().getOperateFlag();
|
||||||
// 合同唯一键
|
// 合同唯一键
|
||||||
String externalId = reqVO.getContractId();
|
String externalId = pushReqVO.getData().getContractId();
|
||||||
// 系统合同ID
|
// 系统合同ID
|
||||||
Long contractId = null;
|
Long contractId = null;
|
||||||
// 查询系统关联合同
|
// 查询系统关联合同
|
||||||
SystemRelativityDO systemRelativityDO = systemRelativityMapper.selectOne("UP_ID", externalId);
|
SystemRelativityDO systemRelativityDO = systemRelativityMapper.selectOne("UP_ID", externalId);
|
||||||
if ("I".equals(operateFlag)) {
|
if ("I".equals(operateFlag)) {
|
||||||
if (systemRelativityDO != null && systemRelativityDO.getDownId() != null) { // 修改合同
|
if (systemRelativityDO != null && systemRelativityDO.getDownId() != null) { // 修改合同
|
||||||
|
contractId = systemRelativityDO.getDownId();
|
||||||
|
contractMainDO.setId(contractId);
|
||||||
|
contractMainMapper.updateById(contractMainDO);
|
||||||
|
} else { // 新增合同
|
||||||
|
contractMainMapper.insert(contractMainDO);
|
||||||
|
contractId = contractMainDO.getId();
|
||||||
|
|
||||||
|
// 生成关联数据
|
||||||
|
SystemRelativityDO saveRelation = new SystemRelativityDO();
|
||||||
|
saveRelation.setUpId(Long.parseLong(externalId));
|
||||||
|
saveRelation.setDownId(contractId);
|
||||||
|
saveRelation.setWay(DictEnum.BSE_SYS_REL_WY_EXTERNAL.getCode());
|
||||||
|
saveRelation.setStatus(DictEnum.BSE_SYS_REL_TP_CONTRACT.getCode());
|
||||||
|
systemRelativityMapper.insert(saveRelation);
|
||||||
|
}
|
||||||
|
} else if ("D".equals(operateFlag)) {
|
||||||
|
if (systemRelativityDO == null || systemRelativityDO.getDownId() == null) throw exception(CONTRACT_NOT_EXISTS);
|
||||||
contractId = systemRelativityDO.getDownId();
|
contractId = systemRelativityDO.getDownId();
|
||||||
contractMainDO.setId(contractId);
|
contractMainMapper.deleteById(contractId);
|
||||||
contractMainMapper.updateById(contractMainDO);
|
// 删除动态条款信息
|
||||||
} else { // 新增合同
|
contractOtherFormMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
||||||
contractMainMapper.insert(contractMainDO);
|
contractOtherFieldMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
||||||
contractId = contractMainDO.getId();
|
pushResult(pushReqVO, 1, null);
|
||||||
|
} else {
|
||||||
// 生成关联数据
|
throw exception(CONTRACT_UNKNOWN_OPERATE);
|
||||||
SystemRelativityDO saveRelation = new SystemRelativityDO();
|
|
||||||
saveRelation.setUpId(Long.parseLong(externalId));
|
|
||||||
saveRelation.setDownId(contractId);
|
|
||||||
saveRelation.setWay(DictEnum.BSE_SYS_REL_WY_EXTERNAL.getCode());
|
|
||||||
saveRelation.setStatus(DictEnum.BSE_SYS_REL_TP_CONTRACT.getCode());
|
|
||||||
systemRelativityMapper.insert(saveRelation);
|
|
||||||
}
|
}
|
||||||
} else if ("D".equals(operateFlag)) {
|
|
||||||
if (systemRelativityDO == null || systemRelativityDO.getDownId() == null) throw exception(CONTRACT_NOT_EXISTS);
|
// 根据客商信息列表提交多个合同映射到erp
|
||||||
contractId = systemRelativityDO.getDownId();
|
if (pushReqVO.getData().getPartnerList() == null || pushReqVO.getData().getPartnerList().isEmpty()) {
|
||||||
contractMainMapper.deleteById(contractId);
|
throw exception(CONTRACT_PARTNER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合同主信息-合同编号
|
||||||
|
String contractPaperNumber = contractMainDO.getContractPaperNumber();
|
||||||
|
// 合同主信息-合同名称
|
||||||
|
String contractName = contractMainDO.getContractName();
|
||||||
|
for (int i = 0; i < pushReqVO.getData().getPartnerList().size(); i++) {
|
||||||
|
Partner partner = pushReqVO.getData().getPartnerList().get(i);
|
||||||
|
|
||||||
|
// 根据客商信息设置合同信息
|
||||||
|
// 合同编号
|
||||||
|
contractMainDO.setContractPaperNumber(contractPaperNumber + "_" + String.format("%03d", (i+1)));
|
||||||
|
pushReqVO.getData().getPartnerList().get(i)
|
||||||
|
.setErpContractPaperNumber(contractPaperNumber + "_" + String.format("%03d", (i+1)));
|
||||||
|
// 合同名称
|
||||||
|
contractMainDO.setContractName(contractName + "_" + String.format("%03d", (i+1)));
|
||||||
|
// 境内/境外 -> 客商信息:境内/外
|
||||||
|
contractMainDO.setIsDomestic(partner.getDomesticOrOverseas());
|
||||||
|
// 乙方公司编号(销售方) -> 客商信息:供应商代码
|
||||||
|
contractMainDO.setSalesCompanyNumber(partner.getPartnerCode());
|
||||||
|
// ERP乙方公司编码
|
||||||
|
contractMainDO.setErpSalesCompanyNumber(partner.getPartnerCode());
|
||||||
|
// 乙方公司名称 -> 客商信息:供应商名称
|
||||||
|
contractMainDO.setSalesCompanyName(partner.getPartnerName());
|
||||||
|
// ERP乙方公司名称
|
||||||
|
contractMainDO.setErpSalesCompanyName(partner.getPartnerName());
|
||||||
|
|
||||||
|
// 生成ERP合同映射表
|
||||||
|
ErpContractSaveReqVO erpContractVO = contractService.getErpContract(contractMainDO);
|
||||||
|
|
||||||
|
// 调用ERP模块
|
||||||
|
JSONObject erpResult = contractService.sendToErp(erpContractVO);
|
||||||
|
log.info("合同提交ERP结果:{}", erpResult);
|
||||||
|
|
||||||
|
// 调用ERP失败
|
||||||
|
if (!erpResult.getBool("success")) {
|
||||||
|
throw exception(CONTRACT_SUBMIT_ERP_FAIL, erpResult.getStr("errMsg"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 删除动态条款信息
|
// 删除动态条款信息
|
||||||
contractOtherFormMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
contractOtherFormMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
||||||
contractOtherFieldMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
contractOtherFieldMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
||||||
return success(true);
|
|
||||||
} else {
|
// 请求参数保存到动态条款
|
||||||
throw exception(CONTRACT_UNKNOWN_OPERATE);
|
saveIntContractFields(pushReqVO.getData(), contractId);
|
||||||
|
|
||||||
|
pushResult(pushReqVO, 1, null);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("国贸系统推送合同异常:{}", e.getMessage(), e);
|
||||||
|
pushResult(pushReqVO, -1, e.getMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 根据客商信息列表提交多个合同映射到erp
|
|
||||||
if (reqVO.getPartnerList() == null || reqVO.getPartnerList().isEmpty()) {
|
|
||||||
throw exception(CONTRACT_PARTNER_NOT_EXISTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 合同主信息-合同编号
|
|
||||||
String contractPaperNumber = contractMainDO.getContractPaperNumber();
|
|
||||||
// 合同主信息-合同名称
|
|
||||||
String contractName = contractMainDO.getContractName();
|
|
||||||
for (int i = 0; i < reqVO.getPartnerList().size(); i++) {
|
|
||||||
Partner partner = reqVO.getPartnerList().get(i);
|
|
||||||
|
|
||||||
// 根据客商信息设置合同信息
|
|
||||||
// 合同编号
|
|
||||||
contractMainDO.setContractPaperNumber(contractPaperNumber + "_" + String.format("%03d", (i+1)));
|
|
||||||
reqVO.getPartnerList().get(i)
|
|
||||||
.setErpContractPaperNumber(contractPaperNumber + "_" + String.format("%03d", (i+1)));
|
|
||||||
// 合同名称
|
|
||||||
contractMainDO.setContractName(contractName + "_" + String.format("%03d", (i+1)));
|
|
||||||
// 境内/境外 -> 客商信息:境内/外
|
|
||||||
contractMainDO.setIsDomestic(partner.getDomesticOrOverseas());
|
|
||||||
// 乙方公司编号(销售方) -> 客商信息:供应商代码
|
|
||||||
contractMainDO.setSalesCompanyNumber(partner.getPartnerCode());
|
|
||||||
// ERP乙方公司编码
|
|
||||||
contractMainDO.setErpSalesCompanyNumber(partner.getPartnerCode());
|
|
||||||
// 乙方公司名称 -> 客商信息:供应商名称
|
|
||||||
contractMainDO.setSalesCompanyName(partner.getPartnerName());
|
|
||||||
// ERP乙方公司名称
|
|
||||||
contractMainDO.setErpSalesCompanyName(partner.getPartnerName());
|
|
||||||
|
|
||||||
// 生成ERP合同映射表
|
|
||||||
ErpContractSaveReqVO erpContractVO = contractService.getErpContract(contractMainDO);
|
|
||||||
|
|
||||||
// 调用ERP模块
|
|
||||||
JSONObject erpResult = contractService.sendToErp(erpContractVO);
|
|
||||||
log.info("合同提交ERP结果:{}", erpResult);
|
|
||||||
|
|
||||||
// 调用ERP失败
|
|
||||||
if (!erpResult.getBool("success")) {
|
|
||||||
throw exception(CONTRACT_SUBMIT_ERP_FAIL, erpResult.getStr("errMsg"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 删除动态条款信息
|
|
||||||
contractOtherFormMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
|
||||||
contractOtherFieldMapper.delete("CTRT_MAIN_ID", contractId.toString());
|
|
||||||
|
|
||||||
// 请求参数保存到动态条款
|
|
||||||
saveIntContractFields(reqVO, contractId);
|
|
||||||
|
|
||||||
return success(true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -365,6 +373,32 @@ public class ContractApiImpl implements ContractApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void pushResult(IntPushContractReqVO pushReqVO, Integer code, String msg) {
|
||||||
|
|
||||||
|
// 返回数据
|
||||||
|
IntPushContractRespVO body = new IntPushContractRespVO();
|
||||||
|
body.set__requestId_(pushReqVO.get__requestId_());
|
||||||
|
body.set__interfaceType__("R_MY_JG_10");
|
||||||
|
body.setBusiBillCode(pushReqVO.getBusiBillCode());
|
||||||
|
body.setCode(code);
|
||||||
|
body.setMessage(String.format("推送合同[%s]%s", pushReqVO.getData().getContractCode(), code >= 0 ? "成功" : "失败:" + msg));
|
||||||
|
body.setDatetime(DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()));
|
||||||
|
body.setSystem("JYGK");
|
||||||
|
body.setOperateFlag(pushReqVO.getOperateFlag());
|
||||||
|
|
||||||
|
// 回调参数
|
||||||
|
JSONObject req = new JSONObject();
|
||||||
|
req.set("messageKey", "R_JG_MY_00");
|
||||||
|
req.set("messageBody", body);
|
||||||
|
try {
|
||||||
|
log.info("国贸系统推送合同回调参数:{}",req);
|
||||||
|
String res = shareServiceUtil.callShareService("S_EPLAT_04", req.toString());
|
||||||
|
log.info("国贸系统推送合同回调成功:{}",res);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("国贸系统推送合同回调失败:{}",e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommonResult<PageResult<IntContract>> logisticsListPage(IntContractPageReq pageReq) {
|
public CommonResult<PageResult<IntContract>> logisticsListPage(IntContractPageReq pageReq) {
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
package com.zt.plat.module.contractorder.config;
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.client.ClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||||
|
import org.springframework.http.converter.StringHttpMessageConverter;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
import java.nio.charset.Charset;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RestTemplate配置类
|
||||||
|
* @author ChenZhaoxue
|
||||||
|
* @date 2025/3/27
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
public class RestTemplateConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public RestTemplate restTemplate(ClientHttpRequestFactory factory){
|
||||||
|
RestTemplate restTemplate = new RestTemplate(factory);
|
||||||
|
restTemplate.getMessageConverters().set(1, new StringHttpMessageConverter(Charset.forName("UTF-8")));
|
||||||
|
return restTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
|
||||||
|
SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
|
||||||
|
factory.setConnectTimeout(10000);//单位为ms
|
||||||
|
factory.setReadTimeout(30000);//单位为ms
|
||||||
|
return factory;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,9 +6,11 @@ import com.zt.plat.framework.common.pojo.CommonResult;
|
|||||||
import com.zt.plat.framework.common.pojo.PageResult;
|
import com.zt.plat.framework.common.pojo.PageResult;
|
||||||
import com.zt.plat.framework.common.util.object.BeanUtils;
|
import com.zt.plat.framework.common.util.object.BeanUtils;
|
||||||
import com.zt.plat.module.contractorder.api.ContractApi;
|
import com.zt.plat.module.contractorder.api.ContractApi;
|
||||||
|
import com.zt.plat.module.contractorder.api.dto.order.PurchaseOrderWithDetailsDTO;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.*;
|
import com.zt.plat.module.contractorder.api.vo.contract.*;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContract;
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContract;
|
||||||
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContractPageReq;
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntContractPageReq;
|
||||||
|
import com.zt.plat.module.contractorder.api.vo.contract.international.IntPushContractReqVO;
|
||||||
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractMainDO;
|
import com.zt.plat.module.contractorder.dal.dataobject.contract.ContractMainDO;
|
||||||
import com.zt.plat.module.contractorder.service.contract.ContractService;
|
import com.zt.plat.module.contractorder.service.contract.ContractService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@@ -195,14 +197,21 @@ public class ContractController implements BusinessControllerMarker {
|
|||||||
@PostMapping("/push")
|
@PostMapping("/push")
|
||||||
@Operation(summary = "国贸2.0系统推送合同")
|
@Operation(summary = "国贸2.0系统推送合同")
|
||||||
@PreAuthorize("@ss.hasPermission('base:contract:create')")
|
@PreAuthorize("@ss.hasPermission('base:contract:create')")
|
||||||
CommonResult<Boolean> push(@Valid @RequestBody IntContract reqVO) {
|
public void push(@Valid @RequestBody IntPushContractReqVO pushReqVO) {
|
||||||
return contractApi.push(reqVO);
|
contractApi.push(pushReqVO);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/logistics/list/page")
|
@PostMapping("/logistics/list/page")
|
||||||
@Operation(summary = "国贸2.0系统合同分页查询")
|
@Operation(summary = "国贸2.0系统合同分页查询")
|
||||||
@PreAuthorize("@ss.hasPermission('base:contract:query')")
|
@PreAuthorize("@ss.hasPermission('base:contract:query')")
|
||||||
CommonResult<PageResult<IntContract>> logisticsListPage(IntContractPageReq pageReq) {
|
public CommonResult<PageResult<IntContract>> logisticsListPage(IntContractPageReq pageReq) {
|
||||||
return contractApi.logisticsListPage(pageReq);
|
return contractApi.logisticsListPage(pageReq);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/order-by-order-no")
|
||||||
|
@Operation(summary = "通过订单编号获取订单信息", description = "通过订单编号获取订单信息")
|
||||||
|
@PreAuthorize("@ss.hasPermission('base:contract:query')")
|
||||||
|
public CommonResult<List<PurchaseOrderWithDetailsDTO>> getOrderByOrderNo(@RequestBody List<String> orderNoS){
|
||||||
|
return contractApi.getOrderByOrderNo(orderNoS);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
package com.zt.plat.module.contractorder.controller.admin.contractorder;
|
package com.zt.plat.module.contractorder.controller.admin.contractorder;
|
||||||
|
|
||||||
|
import com.zt.plat.module.contractorder.api.dto.order.PurchaseOrderWithDetailsDTO;
|
||||||
|
import com.zt.plat.module.contractorder.service.contract.ContractService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import com.zt.plat.framework.common.pojo.CommonResult;
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -20,10 +23,12 @@ import static com.zt.plat.framework.common.pojo.CommonResult.success;
|
|||||||
@RequestMapping("/admin/contract-order/contract-order")
|
@RequestMapping("/admin/contract-order/contract-order")
|
||||||
public class ContractOrderController {
|
public class ContractOrderController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ContractService contractService;
|
||||||
|
|
||||||
@GetMapping("/hello")
|
@GetMapping("/hello")
|
||||||
@Operation(summary = "Hello ContractOrder")
|
@Operation(summary = "Hello ContractOrder")
|
||||||
public CommonResult<String> hello() {
|
public CommonResult<String> hello() {
|
||||||
return success("Hello, ContractOrder!");
|
return success("Hello, ContractOrder!");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.zt.plat.module.contractorder.dal.dataobject.contract;
|
|||||||
import com.baomidou.mybatisplus.annotation.*;
|
import com.baomidou.mybatisplus.annotation.*;
|
||||||
import com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO;
|
import com.zt.plat.framework.mybatis.core.dataobject.BusinessBaseDO;
|
||||||
import lombok.*;
|
import lombok.*;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@@ -205,12 +206,12 @@ public class ContractMainDO extends BusinessBaseDO {
|
|||||||
@TableField("RMK")
|
@TableField("RMK")
|
||||||
private String remark;
|
private String remark;
|
||||||
/**
|
/**
|
||||||
* 施工类型编号;与ERP(HTLXBH)对应,拓展信息
|
* 施工类型编号(字典:ERP_CTRT_HTLXBH);与ERP(HTLXBH)对应,拓展信息
|
||||||
*/
|
*/
|
||||||
@TableField("CON_TP_NUM")
|
@TableField("CON_TP_NUM")
|
||||||
private String constructionTypeNumber;
|
private String constructionTypeNumber;
|
||||||
/**
|
/**
|
||||||
* 施工类型名称;与ERP(HTLXMC)对应,拓展信息
|
* 施工类型名称(字典:ERP_CTRT_HTLXBH);与ERP(HTLXMC)对应,拓展信息
|
||||||
*/
|
*/
|
||||||
@TableField("CON_TP_NAME")
|
@TableField("CON_TP_NAME")
|
||||||
private String constructionTypeName;
|
private String constructionTypeName;
|
||||||
@@ -258,7 +259,7 @@ public class ContractMainDO extends BusinessBaseDO {
|
|||||||
* 建筑服务发生地;与ERP(JZFWFSD)对应,拓展信息,销售合同,且类型为SAP02COSR必填
|
* 建筑服务发生地;与ERP(JZFWFSD)对应,拓展信息,销售合同,且类型为SAP02COSR必填
|
||||||
*/
|
*/
|
||||||
@TableField("ARCH_SVC_PLCE")
|
@TableField("ARCH_SVC_PLCE")
|
||||||
private String architectureServicePlace;
|
private String architectureServicePlace;
|
||||||
/**
|
/**
|
||||||
* 达到收款条件金额;与ERP(DDSKJE)对应,拓展信息,销售合同,且类型为SAP02COSR必填
|
* 达到收款条件金额;与ERP(DDSKJE)对应,拓展信息,销售合同,且类型为SAP02COSR必填
|
||||||
*/
|
*/
|
||||||
@@ -399,4 +400,9 @@ public class ContractMainDO extends BusinessBaseDO {
|
|||||||
*/
|
*/
|
||||||
@TableField("AGT_NAME")
|
@TableField("AGT_NAME")
|
||||||
private String agentName;
|
private String agentName;
|
||||||
|
/**
|
||||||
|
* 货权转移类型(字典:ASY_MTNG_TP)
|
||||||
|
*/
|
||||||
|
@TableField("MTNG_TP")
|
||||||
|
private String meteringType;
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,565 @@
|
|||||||
|
package com.zt.plat.module.contractorder.util;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* redis 工具类
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Component
|
||||||
|
public class RedisUtil {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 指定缓存失效时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean expire(String key, long time) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.expire(key, time, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据key 获取过期时间
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @return 时间(秒) 返回0代表为永久有效
|
||||||
|
*/
|
||||||
|
public long getExpire(String key) {
|
||||||
|
return redisTemplate.getExpire(key, TimeUnit.SECONDS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断key是否存在
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean hasKey(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.hasKey(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除缓存
|
||||||
|
*
|
||||||
|
* @param key 可以传一个值 或多个
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void del(String key) {
|
||||||
|
redisTemplate.delete(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================String=============================
|
||||||
|
/**
|
||||||
|
* 普通缓存获取
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public Object get(String key) {
|
||||||
|
return key == null ? null : redisTemplate.opsForValue().get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存放入
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return true成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean set(String key, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForValue().set(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通缓存放入并设置时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期
|
||||||
|
* @return true成功 false 失败
|
||||||
|
*/
|
||||||
|
public boolean set(String key, Object value, long time) {
|
||||||
|
try {
|
||||||
|
if (time > 0) {
|
||||||
|
redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
|
||||||
|
} else {
|
||||||
|
set(key, value);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递增
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param by 要增加几(大于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long incr(String key, long delta) {
|
||||||
|
if (delta < 0) {
|
||||||
|
throw new RuntimeException("递增因子必须大于0");
|
||||||
|
}
|
||||||
|
return redisTemplate.opsForValue().increment(key, delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 递减
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param by 要减少几(小于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long decr(String key, long delta) {
|
||||||
|
if (delta < 0) {
|
||||||
|
throw new RuntimeException("递减因子必须大于0");
|
||||||
|
}
|
||||||
|
return redisTemplate.opsForValue().increment(key, -delta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================================Map=================================
|
||||||
|
/**
|
||||||
|
* HashGet
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 不能为null
|
||||||
|
* @return 值
|
||||||
|
*/
|
||||||
|
public Object hget(String key, String item) {
|
||||||
|
return redisTemplate.opsForHash().get(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取hashKey对应的所有键值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return 对应的多个键值
|
||||||
|
*/
|
||||||
|
public Map<Object, Object> hmget(String key) {
|
||||||
|
return redisTemplate.opsForHash().entries(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashSet
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param map 对应多个键值
|
||||||
|
* @return true 成功 false 失败
|
||||||
|
*/
|
||||||
|
public boolean hmset(String key, Map<String, Object> map) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().putAll(key, map);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* HashSet 并设置时间
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param map 对应多个键值
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return true成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hmset(String key, Map<String, Object> map, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().putAll(key, map);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向一张hash表中放入数据,如果不存在将创建
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param value 值
|
||||||
|
* @return true 成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hset(String key, String item, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().put(key, item, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 向一张hash表中放入数据,如果不存在将创建
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
|
||||||
|
* @return true 成功 false失败
|
||||||
|
*/
|
||||||
|
public boolean hset(String key, String item, Object value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForHash().put(key, item, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 删除hash表中的值
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 可以使多个 不能为null
|
||||||
|
*/
|
||||||
|
public void hdel(String key, Object... item) {
|
||||||
|
redisTemplate.opsForHash().delete(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 判断hash表中是否有该项的值
|
||||||
|
*
|
||||||
|
* @param key 键 不能为null
|
||||||
|
* @param item 项 不能为null
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean hHasKey(String key, String item) {
|
||||||
|
return redisTemplate.opsForHash().hasKey(key, item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hash递增 如果不存在,就会创建一个 并把新增后的值返回
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param by 要增加几(大于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double hincr(String key, String item, double by) {
|
||||||
|
return redisTemplate.opsForHash().increment(key, item, by);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* hash递减
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param item 项
|
||||||
|
* @param by 要减少记(小于0)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public double hdecr(String key, String item, double by) {
|
||||||
|
return redisTemplate.opsForHash().increment(key, item, -by);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================set=============================
|
||||||
|
/**
|
||||||
|
* 根据key获取Set中的所有值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Set<Object> sGet(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().members(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据value从一个set中查询,是否存在
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @return true 存在 false不存在
|
||||||
|
*/
|
||||||
|
public boolean sHasKey(String key, Object value) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().isMember(key, value);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将数据放入set缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 成功个数
|
||||||
|
*/
|
||||||
|
public long sSet(String key, Object... values) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().add(key, values);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将set数据放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 成功个数
|
||||||
|
*/
|
||||||
|
public long sSetAndTime(String key, long time, Object... values) {
|
||||||
|
try {
|
||||||
|
Long count = redisTemplate.opsForSet().add(key, values);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取set缓存的长度
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long sGetSetSize(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForSet().size(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除值为value的
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param values 值 可以是多个
|
||||||
|
* @return 移除的个数
|
||||||
|
*/
|
||||||
|
public long setRemove(String key, Object... values) {
|
||||||
|
try {
|
||||||
|
Long count = redisTemplate.opsForSet().remove(key, values);
|
||||||
|
return count;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ===============================list=================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取list缓存的内容
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param start 开始
|
||||||
|
* @param end 结束 0 到 -1代表所有值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public List<Object> lGet(String key, long start, long end) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().range(key, start, end);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取list缓存的长度
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public long lGetListSize(String key) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().size(key);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过索引 获取list中的值
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public Object lGetIndex(String key, long index) {
|
||||||
|
try {
|
||||||
|
return redisTemplate.opsForList().index(key, index);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPush(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, Object value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPush(key, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, List<Object> value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPushAll(key, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将list放入缓存
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param value 值
|
||||||
|
* @param time 时间(秒)
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lSet(String key, List<Object> value, long time) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().rightPushAll(key, value);
|
||||||
|
if (time > 0) {
|
||||||
|
expire(key, time);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据索引修改list中的某条数据
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param index 索引
|
||||||
|
* @param value 值
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public boolean lUpdateIndex(String key, long index, Object value) {
|
||||||
|
try {
|
||||||
|
redisTemplate.opsForList().set(key, index, value);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 移除N个值为value
|
||||||
|
*
|
||||||
|
* @param key 键
|
||||||
|
* @param count 移除多少个
|
||||||
|
* @param value 值
|
||||||
|
* @return 移除的个数
|
||||||
|
*/
|
||||||
|
public long lRemove(String key, long count, Object value) {
|
||||||
|
try {
|
||||||
|
Long remove = redisTemplate.opsForList().remove(key, count, value);
|
||||||
|
return remove;
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,127 @@
|
|||||||
|
package com.zt.plat.module.contractorder.util;
|
||||||
|
|
||||||
|
import cn.hutool.json.JSONObject;
|
||||||
|
import cn.hutool.json.JSONUtil;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.http.*;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.web.client.RestTemplate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ePlat共享服务调用工具类
|
||||||
|
*/
|
||||||
|
@Slf4j
|
||||||
|
@Configuration
|
||||||
|
@Component
|
||||||
|
public class ShareServiceUtil {
|
||||||
|
private static final String SHARE_TOKEN_KEY = "eplat:cache:shareToken";
|
||||||
|
private static final String SHARE_REFRESH_TOKEN_KEY = "eplat:cache:shareRefreshToken";
|
||||||
|
private static final int TOKEN_TIME_OUT = 5000; // token过期时间,默认7200秒,这里设置建议小一些,如7000秒
|
||||||
|
|
||||||
|
@Value("${eplat.share.urlPrex}")
|
||||||
|
private String urlPrex;
|
||||||
|
@Value("${eplat.share.clientId}")
|
||||||
|
private String clientId;
|
||||||
|
@Value("${eplat.share.clientSecret}")
|
||||||
|
private String clientSecret;
|
||||||
|
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RestTemplate restTemplate;
|
||||||
|
@Resource
|
||||||
|
private RedisUtil redisUtil;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ePlat共享服务调用
|
||||||
|
* @param serviceNo 服务号
|
||||||
|
* @param request 请求json字符串
|
||||||
|
* @return 调用结果
|
||||||
|
*/
|
||||||
|
public String callShareService(String serviceNo, String request) {
|
||||||
|
String url = String.format("%s/service/%s", urlPrex, serviceNo);
|
||||||
|
log.info("ePlat共享服务调用url:[" + url + "],request:[" + request + "]");
|
||||||
|
String token = generateToken();
|
||||||
|
log.info("目标token:" + token);
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
|
||||||
|
headers.add("Xplat-Token", token);
|
||||||
|
HttpEntity<String> entity = new HttpEntity<>(request, headers);
|
||||||
|
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
||||||
|
return result.getBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateToken() {
|
||||||
|
// 先从redis中获取未过期token
|
||||||
|
String token = (String) redisUtil.get(SHARE_TOKEN_KEY);
|
||||||
|
if (token == null) {
|
||||||
|
synchronized (ShareServiceUtil.class) {
|
||||||
|
token = (String) redisUtil.get(SHARE_TOKEN_KEY);
|
||||||
|
if (token == null) {
|
||||||
|
try {
|
||||||
|
token = refreshToken();
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.warn("生成token出错,可能刷新token有问题,重新尝试下", e);
|
||||||
|
redisUtil.del(SHARE_REFRESH_TOKEN_KEY);
|
||||||
|
token = refreshToken();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return token;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String refreshToken() {
|
||||||
|
// 先从redis中获取未过期的刷新token
|
||||||
|
String refreshToken = (String) redisUtil.get(SHARE_REFRESH_TOKEN_KEY);
|
||||||
|
if (refreshToken == null) {
|
||||||
|
// 重新创建token和刷新token
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
// 构造form表单
|
||||||
|
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
|
||||||
|
paramsMap.set("client_id", clientId);
|
||||||
|
paramsMap.set("client_secret", clientSecret);
|
||||||
|
paramsMap.set("grant_type", "client_credentials");
|
||||||
|
paramsMap.set("scope", "read");
|
||||||
|
String url = String.format("%s/eplat/oauth/token", urlPrex);
|
||||||
|
// 构造请求的实体。包含body和headers的内容
|
||||||
|
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(paramsMap, headers);
|
||||||
|
log.info("获取token调用url:[" + url + "],request:[" + paramsMap + "]");
|
||||||
|
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
||||||
|
JSONObject json = JSONUtil.parseObj(result.getBody());
|
||||||
|
String accessToken = json.getStr("access_token");
|
||||||
|
refreshToken = json.getStr("refresh_token");
|
||||||
|
// 缓存token、刷新token(刷新token过期时间为2倍token过期时间)
|
||||||
|
redisUtil.set(SHARE_TOKEN_KEY, accessToken, TOKEN_TIME_OUT);
|
||||||
|
redisUtil.set(SHARE_REFRESH_TOKEN_KEY, refreshToken, TOKEN_TIME_OUT * 2);
|
||||||
|
return accessToken;
|
||||||
|
} else {
|
||||||
|
HttpHeaders headers = new HttpHeaders();
|
||||||
|
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
|
||||||
|
// 构造form表单
|
||||||
|
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>();
|
||||||
|
paramsMap.set("client_id", clientId);
|
||||||
|
paramsMap.set("client_secret", clientSecret);
|
||||||
|
paramsMap.set("grant_type", "refresh_token");
|
||||||
|
paramsMap.set("refresh_token", refreshToken);
|
||||||
|
String url = String.format("%s/eplat/oauth/token", urlPrex);
|
||||||
|
// 构造请求的实体。包含body和headers的内容
|
||||||
|
HttpEntity<MultiValueMap<String, String>> entity = new HttpEntity<>(paramsMap, headers);
|
||||||
|
log.info("刷新token调用url:[" + url + "],request:[" + paramsMap + "]");
|
||||||
|
ResponseEntity<String> result = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
|
||||||
|
JSONObject json = JSONUtil.parseObj(result.getBody());
|
||||||
|
String accessToken = json.getStr("access_token");
|
||||||
|
refreshToken = json.getStr("refresh_token");
|
||||||
|
// 缓存token、刷新token(刷新token过期时间为2倍token过期时间)
|
||||||
|
redisUtil.set(SHARE_TOKEN_KEY, accessToken, TOKEN_TIME_OUT);
|
||||||
|
redisUtil.set(SHARE_REFRESH_TOKEN_KEY, refreshToken, TOKEN_TIME_OUT * 2);
|
||||||
|
return accessToken;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
224
单位转换系统使用文档.md
Normal file
224
单位转换系统使用文档.md
Normal file
@@ -0,0 +1,224 @@
|
|||||||
|
# 单位转换系统业务使用文档
|
||||||
|
|
||||||
|
## 一、系统概述
|
||||||
|
|
||||||
|
单位转换系统提供统一的计量单位转换服务,支持同一量纲内的单位自动转换。采用**单向配置、双向生效**机制,只需配置"非基准单位 → 基准单位"的转换规则,系统自动推导反向和间接转换。
|
||||||
|
|
||||||
|
**核心特性**:
|
||||||
|
- 单向配置、双向生效的转换机制
|
||||||
|
- 支持按单位ID、符号、名称进行转换
|
||||||
|
- 高精度计算,支持批量操作
|
||||||
|
- 跨模块统一服务
|
||||||
|
|
||||||
|
## 二、内容配置
|
||||||
|
|
||||||
|
### 2.1 管理菜单路径
|
||||||
|
后台管理 → 基础管理 → 计量单位 → 计量单位管理
|
||||||
|
|
||||||
|
### 2.2 配置功能
|
||||||
|
|
||||||
|
#### 计量量纲管理
|
||||||
|
- **功能**:创建和管理不同的量纲类型(如重量、长度、体积等)
|
||||||
|
- **操作**:新增量纲、编辑量纲信息、删除量纲
|
||||||
|
- **每个量纲只能设置一个基准单位**
|
||||||
|
|
||||||
|
#### 计量单位管理
|
||||||
|
- **功能**:创建和管理具体的计量单位
|
||||||
|
- **操作**:新增单位、编辑单位信息、删除单位
|
||||||
|
- **关联量纲**:将单位归属到具体的量纲下
|
||||||
|
|
||||||
|
#### 转换规则配置
|
||||||
|
- **功能**:配置单位间的转换规则
|
||||||
|
- **配置原则**:只需配置"非基准单位 → 基准单位"
|
||||||
|
- **自动推导**:系统自动推导反向转换和间接转换
|
||||||
|
|
||||||
|
#### 预置数据
|
||||||
|
系统已预置常用量纲和单位:
|
||||||
|
- **重量量纲**:千克(基准)、吨、克
|
||||||
|
- **长度量纲**:米(基准)、千米、厘米、毫米
|
||||||
|
- **体积量纲**:立方米(基准)、升、毫升
|
||||||
|
- **面积量纲**:平方米(基准)、平方千米、公顷
|
||||||
|
- **时间量纲**:秒(基准)、分钟、小时、天
|
||||||
|
|
||||||
|
### 2.3 配置建议
|
||||||
|
|
||||||
|
1. **量纲规划**:提前规划好业务需要的量纲类型
|
||||||
|
2. **基准单位选择**:选择业务中最常用、最稳定的单位作为基准
|
||||||
|
3. **转换规则**:优先使用整数转换系数,提高计算精度
|
||||||
|
4. **定期校验**:使用转换路径校验功能确保配置正确性
|
||||||
|
5. **转换完整性校验**:系统提供同一量纲内所有单位能否互相转换的校验功能,确保转换配置的完整性
|
||||||
|
|
||||||
|
## 三、API接口清单
|
||||||
|
|
||||||
|
### 3.1 单位管理接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 说明 | API文档 |
|
||||||
|
|------|------|------|------|---------|
|
||||||
|
| 获取量纲树 | GET | `/admin-api/base/unit-management/unit-quantity/tree` | 获取量纲和单位树形结构 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D%E9%87%8F/getTree) |
|
||||||
|
| 获取单位列表 | GET | `/admin-api/base/unit-management/unt-info/page` | 获取单位列表(用于下拉选择) | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E8%AE%A1%E9%87%8F%E5%8D%95%E4%BD%8D/getPage) |
|
||||||
|
|
||||||
|
### 3.2 单位转换接口
|
||||||
|
|
||||||
|
| 接口 | 方法 | 路径 | 说明 | API文档 |
|
||||||
|
|------|------|------|------|---------|
|
||||||
|
| 按ID转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert` | 通过单位ID转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convert) |
|
||||||
|
| 按符号转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-symbol` | 通过单位符号转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertBySymbol) |
|
||||||
|
| 按名称转换单位 | POST | `/admin-api/base/unit-management/unit-conversion/convert-by-name` | 通过单位名称转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/convertByName) |
|
||||||
|
| 批量ID转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert` | 按ID批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvert) |
|
||||||
|
| 批量符号转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-symbol` | 按符号批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertBySymbol) |
|
||||||
|
| 批量名称转换 | POST | `/admin-api/base/unit-management/unit-conversion/batch-convert-by-name` | 按名称批量转换 | [文档](http://172.16.46.63:30081/doc.html#/base-server/%E7%AE%A1%E7%90%86%E5%90%8E%E5%8F%B0%20-%20%E5%8D%95%E4%BD%8D%E8%BD%AC%E6%8D%A2/batchConvertByName) |
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 三、业务调用示例
|
||||||
|
|
||||||
|
### 合同订单模块使用
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class PurchaseOrderServiceImpl {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UnitConversionService unitConversionService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 处理采购订单,统一转换为千克计算
|
||||||
|
*/
|
||||||
|
public void processPurchaseOrder(PurchaseOrderSaveReqVO orderVO) {
|
||||||
|
for (PurchaseOrderDetailVO detail : orderVO.getDetails()) {
|
||||||
|
// 方式1:按符号转换
|
||||||
|
UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO();
|
||||||
|
convertReq.setSrcUnitSymbol(detail.getUnt());
|
||||||
|
convertReq.setTgtUnitSymbol("kg");
|
||||||
|
convertReq.setValue(detail.getQty());
|
||||||
|
convertReq.setPrecision(6);
|
||||||
|
|
||||||
|
UnitConvertRespVO result = unitConversionService.convertBySymbol(convertReq);
|
||||||
|
BigDecimal standardQuantity = result.getConvertedValue();
|
||||||
|
|
||||||
|
// 方式2:按ID转换(如果有单位ID)
|
||||||
|
// UnitConvertReqVO convertReq = new UnitConvertReqVO();
|
||||||
|
// convertReq.setSrcUntId(detail.getUntId());
|
||||||
|
// convertReq.setTgtUntId(kgUnitId);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 四、跨模块调用
|
||||||
|
|
||||||
|
### 4.1 直接Service调用(推荐)
|
||||||
|
|
||||||
|
在同一服务内直接注入使用:
|
||||||
|
```java
|
||||||
|
@Resource
|
||||||
|
private UnitConversionService unitConversionService;
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 跨服务调用(按需使用)
|
||||||
|
|
||||||
|
**1. 在 API 模块中定义 Feign 接口:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
package com.zt.plat.module.base.api;
|
||||||
|
|
||||||
|
import com.zt.plat.framework.common.pojo.CommonResult;
|
||||||
|
import com.zt.plat.module.base.enums.ApiConstants;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import org.springframework.cloud.openfeign.FeignClient;
|
||||||
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
|
||||||
|
@FeignClient(name = ApiConstants.NAME)
|
||||||
|
@Tag(name = "RPC 服务 - 单位转换")
|
||||||
|
public interface UnitConversionApi {
|
||||||
|
|
||||||
|
String PREFIX = ApiConstants.PREFIX + "/unit-conversion";
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/convert")
|
||||||
|
@Operation(summary = "按ID转换单位")
|
||||||
|
CommonResult<UnitConvertRespVO> convert(@RequestBody UnitConvertReqVO reqVO);
|
||||||
|
|
||||||
|
@PostMapping(PREFIX + "/convert-by-symbol")
|
||||||
|
@Operation(summary = "按符号转换单位")
|
||||||
|
CommonResult<UnitConvertRespVO> convertBySymbol(@RequestBody UnitConvertBySymbolReqVO reqVO);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**2. 在其他服务中调用:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
@Service
|
||||||
|
public class PurchaseServiceImpl {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private UnitConversionApi unitConversionApi;
|
||||||
|
|
||||||
|
public void processPurchase(PurchaseVO purchase) {
|
||||||
|
UnitConvertBySymbolReqVO convertReq = new UnitConvertBySymbolReqVO();
|
||||||
|
convertReq.setSrcUnitSymbol(purchase.getUnit());
|
||||||
|
convertReq.setTgtUnitSymbol("kg");
|
||||||
|
convertReq.setValue(purchase.getQuantity());
|
||||||
|
convertReq.setPrecision(6);
|
||||||
|
|
||||||
|
CommonResult<UnitConvertRespVO> result = unitConversionApi.convertBySymbol(convertReq);
|
||||||
|
if (result.isSuccess()) {
|
||||||
|
BigDecimal standardQty = result.getData().getConvertedValue();
|
||||||
|
// 业务处理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 五、前端使用
|
||||||
|
|
||||||
|
### 5.1 基本API调用
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 获取量纲树
|
||||||
|
export const getUnitQuantityTree = () => {
|
||||||
|
return request.get('/admin-api/base/unit-management/unit-quantity/tree')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取单位列表
|
||||||
|
export const getUntInfoPage = (params: any) => {
|
||||||
|
return request.get('/admin-api/base/unit-management/unt-info/page', { params })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单位转换
|
||||||
|
export const convertUnitBySymbol = (data: any) => {
|
||||||
|
return request.post('/admin-api/base/unit-management/unit-conversion/convert-by-symbol', data)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 六、常见问题
|
||||||
|
|
||||||
|
**Q1: 前端如何获取单位选项?**
|
||||||
|
|
||||||
|
A: 使用 `/admin-api/base/unit-management/unit-quantity/tree` 获取量纲树,然后根据选择的量纲调用 `/admin-api/base/unit-management/unt-info/page` 获取单位列表。
|
||||||
|
|
||||||
|
**Q2: 按ID转换和按符号转换哪个更好?**
|
||||||
|
|
||||||
|
A: 按ID转换更稳定,因为数据库ID不会变化。建议在前端保存单位ID,在业务转换时使用ID调用。
|
||||||
|
|
||||||
|
**Q3: 跨服务调用需要特殊配置吗?**
|
||||||
|
|
||||||
|
A: 不需要,项目已经统一配置好。所有Feign客户端都使用 `name = "base-server"`,路径使用 `/rpc-api` 前缀。
|
||||||
|
|
||||||
|
**Q4: 批量转换性能问题?**
|
||||||
|
|
||||||
|
A: 使用批量接口,设置ignoreErrors=true。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**更新时间**: 2025-11-06
|
||||||
|
**版本**: v6.0 (修正版)
|
||||||
Reference in New Issue
Block a user