新增业务打印绑定功能,整合打印模板与业务数据的映射配置,优化打印数据生成逻辑。新增免密接口,支持桌面端打印模板的查询与列表展示,提升用户体验和系统的实时数据同步能力。同时,重构相关控制器以增强系统的可维护性和扩展性。

This commit is contained in:
geht
2026-05-14 10:43:51 +08:00
parent 642cecb04d
commit 8bcc34aee0
649 changed files with 18804 additions and 70 deletions

View File

@@ -9,12 +9,14 @@ import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import com.alibaba.fastjson.JSON;
import jakarta.servlet.http.HttpServletRequest;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.jeecg.common.api.vo.Result;
@@ -36,11 +38,13 @@ import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
import org.jeecg.modules.print.vo.PrintBizTypeVO;
import org.jeecg.modules.print.vo.PrintTemplateFieldItemVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.web.bind.annotation.*;
/**
* 业务与打印模板绑定:可视化配置字段映射
*/
@Slf4j
@Tag(name = "业务打印绑定")
@RestController
@RequestMapping("/print/bizTemplateBind")
@@ -51,6 +55,7 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
@Autowired private IPrintTemplateService printTemplateService;
@Autowired private IPrintBizBindPermWhitelistService printBizBindPermWhitelistService;
@Autowired private IPrintBizPermEntityService printBizPermEntityService;
@Autowired private SimpMessagingTemplate messagingTemplate;
@Operation(summary = "业务打印绑定-分页列表")
@GetMapping("/list")
@@ -81,6 +86,7 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
}
normalizeMappingJson(entity);
service.save(entity);
publishPrintBizTemplateBindChanged("add", entity.getId());
return Result.OK("添加成功");
}
@@ -103,6 +109,7 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
}
normalizeMappingJson(entity);
service.updateById(entity);
publishPrintBizTemplateBindChanged("edit", entity.getId());
return Result.OK("编辑成功");
}
@@ -111,7 +118,9 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
@DeleteMapping("/delete")
@RequiresPermissions("print:bizBind:delete")
public Result<String> delete(@RequestParam(name = "id") String id) {
service.removeById(id);
if (service.removeById(id)) {
publishPrintBizTemplateBindChanged("delete", id);
}
return Result.OK("删除成功");
}
@@ -239,6 +248,10 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
ArrayNode mapping = PrintBizDataMappingUtil.parseMappingArray(bind.getFieldMappingJson());
JsonNode bizRoot = PrintBizDataMappingUtil.parseBizJson(body.getBizDataJson());
ObjectNode printData = PrintBizDataMappingUtil.mapBizToPrintData(bizRoot, mapping);
PrintTemplate tpl = printTemplateService.getById(bind.getTemplateId());
if (tpl != null && StringUtils.isNotBlank(tpl.getTemplateJson())) {
PrintBizDataMappingUtil.fillMissingDataBindingParamKeys(printData, tpl.getTemplateJson());
}
Map<String, Object> res = new HashMap<>(4);
res.put("templateCode", bind.getTemplateCode());
res.put("templateId", bind.getTemplateId());
@@ -308,4 +321,43 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
public static class PermWhitelistBody {
private java.util.List<String> permIds;
}
// ═══════════════════════════ 桌面端免密只读 ═══════════════════════════
@Operation(summary = "业务打印绑定-免密分页列表(桌面端缓存同步)")
@GetMapping("/anon/list")
public Result<IPage<PrintBizTemplateBind>> anonList(
PrintBizTemplateBind query,
@RequestParam(name = "pageNo", defaultValue = "1") Integer pageNo,
@RequestParam(name = "pageSize", defaultValue = "100") Integer pageSize,
HttpServletRequest req) {
QueryWrapper<PrintBizTemplateBind> qw =
QueryGenerator.initQueryWrapper(query, req.getParameterMap());
qw.orderByDesc("create_time");
Page<PrintBizTemplateBind> page = new Page<>(pageNo, pageSize);
return Result.OK(service.page(page, qw));
}
@Operation(summary = "业务打印绑定-免密按 id 查询(桌面端)")
@GetMapping("/anon/queryById")
public Result<PrintBizTemplateBind> anonQueryById(@RequestParam(name = "id") String id) {
PrintBizTemplateBind row = service.getById(id);
return row != null ? Result.OK(row) : Result.error("未找到记录");
}
/**
* 广播到 /topic/sync/print-biz-binds与桌面端 PrintBizTemplateBindSyncCoordinator 对应。
*/
private void publishPrintBizTemplateBindChanged(String action, String bindId) {
try {
Map<String, Object> event = new HashMap<>();
event.put("cmd", "PRINT_BIZ_TEMPLATE_BIND_CHANGED");
event.put("action", action);
event.put("bindId", bindId);
event.put("timestamp", System.currentTimeMillis());
messagingTemplate.convertAndSend("/topic/sync/print-biz-binds", JSON.toJSONString(event));
} catch (Exception e) {
log.debug("广播 STOMP 事件失败 [PRINT_BIZ_TEMPLATE_BIND_CHANGED]: {}", e.getMessage());
}
}
}

View File

@@ -4,7 +4,9 @@ import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.jeecg.modules.print.vo.PrintTemplateFieldItemVO;
/** 按映射规则把业务 JSON 转为模板打印数据(键为模板 bindField */
public final class PrintBizDataMappingUtil {
@@ -24,15 +26,75 @@ public final class PrintBizDataMappingUtil {
}
String templateField = text(rule, "templateField");
String bizField = text(rule, "bizField");
if (StringUtils.isAnyBlank(templateField, bizField)) {
// 仅要求模板字段名;业务字段为空表示「不参与取数」,仍向 printData 写入空字符串,避免模板占位符缺键
if (StringUtils.isBlank(templateField)) {
continue;
}
JsonNode val = resolvePath(bizRoot, bizField);
JsonNode val;
if (StringUtils.isBlank(bizField)) {
val = MAPPER.getNodeFactory().textNode("");
} else {
val = resolvePath(bizRoot, bizField);
}
setPath(printData, templateField, val);
}
return printData;
}
/**
* 按模板中已声明的绑定路径({@code dataBinding.params}、画布/表格等元素的 {@code bindField},与
* {@link PrintNativeTemplateFieldExtractor} 一致),向 printData 补齐缺失路径(空字符串)。
*
* <p>避免字段映射未包含某键时 API 缺键,桌面端渲染把「设计稿占位 text」当成数据显示。
*/
public static ObjectNode fillMissingDataBindingParamKeys(ObjectNode printData, String templateJson) {
if (printData == null) {
printData = MAPPER.createObjectNode();
}
if (StringUtils.isBlank(templateJson)) {
return printData;
}
try {
List<PrintTemplateFieldItemVO> fields = PrintNativeTemplateFieldExtractor.extract(templateJson);
for (PrintTemplateFieldItemVO item : fields) {
if (item == null || StringUtils.isBlank(item.getBindField())) {
continue;
}
String bf = item.getBindField().trim();
if (!hasPath(printData, bf)) {
setPath(printData, bf, MAPPER.getNodeFactory().textNode(""));
}
}
} catch (Exception ignored) {
// 模板解析异常时不阻断打印
}
return printData;
}
/** 判断 printData 上是否存在该点分路径(含嵌套对象) */
private static boolean hasPath(ObjectNode root, String path) {
if (StringUtils.isBlank(path)) {
return false;
}
String[] parts = path.split("\\.");
JsonNode cur = root;
for (int i = 0; i < parts.length; i++) {
if (cur == null || !cur.isObject()) {
return false;
}
ObjectNode obj = (ObjectNode) cur;
String p = parts[i];
if (p.isEmpty() || !obj.has(p)) {
return false;
}
if (i == parts.length - 1) {
return true;
}
cur = obj.get(p);
}
return false;
}
private static JsonNode resolvePath(JsonNode root, String path) {
if (root == null || StringUtils.isBlank(path)) {
return null;

View File

@@ -48,6 +48,7 @@ public class JeecgSystemApplication extends SpringBootServletInitializer {
String port = env.getProperty("server.port");
String path = oConvertUtils.getString(env.getProperty("server.servlet.context-path"));
log.info("\n----------------------------------------------------------\n\t" +
"Application Jeecg-Boot is running! Access URLs:\n\t" +
"Local: \t\thttp://localhost:" + port + path + "\n\t" +
"External: \thttp://" + ip + ":" + port + path + "/doc.html\n\t" +