新增打印业务绑定功能,整合原材料卡片和入场记录的打印模板配置,优化打印数据准备逻辑。新增打印机查询接口,提升打印服务的灵活性和用户体验。同时,重构相关控制器以支持新的打印常量定义,增强系统的可维护性和扩展性。
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
package org.jeecg.modules.print.bootstrap;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.ApplicationArguments;
|
||||
import org.springframework.boot.ApplicationRunner;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
/**
|
||||
* 项目启动后异步从数据库 {@code sys_permission} 读取真实 component / component_name,预热
|
||||
* {@code print_biz_perm_entity},不依赖人工白名单操作。关闭:{@code jeecg.print.biz-perm-warmup=false}
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@Order(2000)
|
||||
public class PrintBizPermEntityWarmupRunner implements ApplicationRunner {
|
||||
|
||||
@Autowired private IPrintBizPermEntityService printBizPermEntityService;
|
||||
|
||||
@Value("${jeecg.print.biz-perm-warmup:true}")
|
||||
private boolean warmupEnabled;
|
||||
|
||||
@Override
|
||||
public void run(ApplicationArguments args) {
|
||||
if (!warmupEnabled) {
|
||||
log.info("打印菜单-实体映射预热已关闭(jeecg.print.biz-perm-warmup=false)");
|
||||
return;
|
||||
}
|
||||
CompletableFuture.runAsync(
|
||||
() -> {
|
||||
try {
|
||||
log.info("打印菜单-实体映射:开始异步预热(来源 sys_permission 表)");
|
||||
printBizPermEntityService.warmupMappingsFromSysPermissionTable();
|
||||
} catch (Exception e) {
|
||||
log.warn("打印菜单-实体映射预热失败(不影响系统启动)", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
package org.jeecg.modules.print.catalog;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
import org.jeecg.modules.print.vo.PrintBizTypeVO;
|
||||
|
||||
/**
|
||||
* 业务类型及可映射字段目录(与实体 JSON 字段名一致,camelCase)。
|
||||
* 新增业务:在此注册 bizCode 与字段列表。
|
||||
*/
|
||||
public final class PrintBizTypeCatalog {
|
||||
|
||||
private static final Map<String, PrintBizTypeVO> REGISTRY = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
registerRawMaterialCard();
|
||||
}
|
||||
|
||||
private PrintBizTypeCatalog() {}
|
||||
|
||||
private static void registerRawMaterialCard() {
|
||||
PrintBizTypeVO vo = new PrintBizTypeVO();
|
||||
vo.setBizCode("MES_RAW_MATERIAL_CARD");
|
||||
vo.setBizName("原材料卡片");
|
||||
vo.setDescription("mes_xsl_raw_material_card / MesXslRawMaterialCard");
|
||||
List<PrintBizFieldItemVO> fields = new ArrayList<>();
|
||||
fields.add(f("id", "主键"));
|
||||
fields.add(f("barcode", "条码"));
|
||||
fields.add(f("splitDetailId", "拆码明细行ID"));
|
||||
fields.add(f("batchNo", "批次号"));
|
||||
fields.add(f("entryDate", "入场日期"));
|
||||
fields.add(f("materialId", "物料ID"));
|
||||
fields.add(f("materialName", "物料名称"));
|
||||
fields.add(f("materialDesc", "物料描述"));
|
||||
fields.add(f("supplierId", "供应商ID"));
|
||||
fields.add(f("supplierName", "供应商名称"));
|
||||
fields.add(f("manufacturerMaterialName", "厂家物料名称"));
|
||||
fields.add(f("shelfLife", "保质期"));
|
||||
fields.add(f("totalWeight", "总重"));
|
||||
fields.add(f("remainingWeight", "剩余重量"));
|
||||
fields.add(f("remainingQuantity", "剩余数量"));
|
||||
fields.add(f("status", "状态(字典)"));
|
||||
fields.add(f("testResult", "检测结果(字典)"));
|
||||
fields.add(f("warehouseArea", "库区/库位"));
|
||||
fields.add(f("unloadOperator", "卸货操作人"));
|
||||
fields.add(f("priorityPickup", "优先出库"));
|
||||
fields.add(f("createBy", "创建人"));
|
||||
fields.add(f("createTime", "创建时间"));
|
||||
fields.add(f("updateBy", "更新人"));
|
||||
fields.add(f("updateTime", "更新时间"));
|
||||
vo.setFields(Collections.unmodifiableList(fields));
|
||||
REGISTRY.put(vo.getBizCode(), vo);
|
||||
}
|
||||
|
||||
private static PrintBizFieldItemVO f(String key, String label) {
|
||||
return new PrintBizFieldItemVO(key, label, "");
|
||||
}
|
||||
|
||||
public static List<PrintBizTypeVO> listAll() {
|
||||
return new ArrayList<>(REGISTRY.values());
|
||||
}
|
||||
|
||||
public static PrintBizTypeVO getByCode(String bizCode) {
|
||||
if (bizCode == null) {
|
||||
return null;
|
||||
}
|
||||
return REGISTRY.get(bizCode.trim());
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@@ -20,13 +21,18 @@ import org.jeecg.common.api.vo.Result;
|
||||
import org.jeecg.common.aspect.annotation.AutoLog;
|
||||
import org.jeecg.common.system.base.controller.JeecgController;
|
||||
import org.jeecg.common.system.query.QueryGenerator;
|
||||
import org.jeecg.modules.print.catalog.PrintBizTypeCatalog;
|
||||
import org.jeecg.modules.print.entity.PrintBizTemplateBind;
|
||||
import org.jeecg.modules.print.entity.PrintTemplate;
|
||||
import org.jeecg.modules.print.service.IPrintBizBindPermWhitelistService;
|
||||
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
|
||||
import org.jeecg.modules.print.service.IPrintBizTemplateBindService;
|
||||
import org.jeecg.modules.print.service.IPrintTemplateService;
|
||||
import org.jeecg.modules.print.util.PrintBizDataMappingUtil;
|
||||
import org.jeecg.modules.print.util.PrintBizDetailPropertyScanner;
|
||||
import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
|
||||
import org.jeecg.modules.print.util.PrintNativeTemplateFieldExtractor;
|
||||
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
|
||||
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;
|
||||
@@ -43,6 +49,8 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
|
||||
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
|
||||
|
||||
@Autowired private IPrintTemplateService printTemplateService;
|
||||
@Autowired private IPrintBizBindPermWhitelistService printBizBindPermWhitelistService;
|
||||
@Autowired private IPrintBizPermEntityService printBizPermEntityService;
|
||||
|
||||
@Operation(summary = "业务打印绑定-分页列表")
|
||||
@GetMapping("/list")
|
||||
@@ -107,11 +115,41 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
|
||||
return Result.OK("删除成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "已注册的业务类型及字段目录")
|
||||
@Operation(summary = "已配置的菜单-实体映射及反射字段(完整目录;biz_code 为菜单 id)")
|
||||
@GetMapping("/bizTypes")
|
||||
@RequiresPermissions("print:bizBind:list")
|
||||
public Result<List<PrintBizTypeVO>> bizTypes() {
|
||||
return Result.OK(PrintBizTypeCatalog.listAll());
|
||||
return Result.OK(printBizPermEntityService.listAllBizTypeVOs());
|
||||
}
|
||||
|
||||
/**
|
||||
* 「新增/编辑业务打印绑定」业务下拉:print_biz_perm_entity 中的映射,并按「打印业务白名单」过滤。
|
||||
* 白名单为空表示不过滤;非空则仅保留菜单 id 在白名单内的项。
|
||||
*/
|
||||
@Operation(summary = "可选业务类型(白名单过滤后)")
|
||||
@GetMapping("/bizTypesForBinding")
|
||||
@RequiresPermissions("print:bizBind:list")
|
||||
public Result<List<PrintBizTypeVO>> bizTypesForBinding() {
|
||||
return Result.OK(printBizBindPermWhitelistService.listBizTypesForBinding());
|
||||
}
|
||||
|
||||
@Operation(summary = "打印业务白名单:当前勾选的菜单 id(catalog 恒为空,避免全库反射超时)")
|
||||
@GetMapping("/permWhitelist")
|
||||
@RequiresPermissions("print:bizBind:whitelist")
|
||||
public Result<PrintBizPermWhitelistVO> getPermWhitelist() {
|
||||
PrintBizPermWhitelistVO vo = new PrintBizPermWhitelistVO();
|
||||
vo.setPermIds(printBizBindPermWhitelistService.listPermIds());
|
||||
vo.setCatalog(Collections.emptyList());
|
||||
return Result.OK(vo);
|
||||
}
|
||||
|
||||
@AutoLog(value = "业务打印绑定-保存打印业务白名单")
|
||||
@Operation(summary = "保存打印业务白名单(按菜单 permission id)")
|
||||
@PostMapping("/permWhitelist")
|
||||
@RequiresPermissions("print:bizBind:whitelist")
|
||||
public Result<String> savePermWhitelist(@RequestBody PermWhitelistBody body) {
|
||||
printBizBindPermWhitelistService.replacePermIds(body == null ? null : body.getPermIds());
|
||||
return Result.OK("保存成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "解析原生模板中的占位字段(bindField)")
|
||||
@@ -131,6 +169,47 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
|
||||
return Result.OK(fields);
|
||||
}
|
||||
|
||||
@Operation(summary = "主实体上的明细槽位(List<明细实体> / 明细数组 / 嵌套对象),用于先选明细再反射明细字段")
|
||||
@GetMapping("/detailSlots")
|
||||
@RequiresPermissions("print:bizBind:list")
|
||||
public Result<List<PrintBizDetailSlotVO>> detailSlots(@RequestParam(name = "bizCode") String bizCode) {
|
||||
if (StringUtils.isBlank(bizCode)) {
|
||||
return Result.error("bizCode 不能为空");
|
||||
}
|
||||
PrintBizTypeVO bizVo = printBizPermEntityService.resolveBizTypeVo(bizCode.trim());
|
||||
if (bizVo == null || StringUtils.isBlank(bizVo.getDescription())) {
|
||||
return Result.OK(Collections.emptyList());
|
||||
}
|
||||
Class<?> main = PrintBizEntityFieldIntrospector.tryLoadClass(bizVo.getDescription().trim());
|
||||
if (main == null) {
|
||||
return Result.OK(Collections.emptyList());
|
||||
}
|
||||
return Result.OK(PrintBizDetailPropertyScanner.listSlots(main));
|
||||
}
|
||||
|
||||
@Operation(summary = "反射指定明细槽位元素类的字段(fieldKey 带「属性名.」前缀,用于与模板明细占位绑定)")
|
||||
@GetMapping("/bizFieldsForDetailSlot")
|
||||
@RequiresPermissions("print:bizBind:list")
|
||||
public Result<List<PrintBizFieldItemVO>> bizFieldsForDetailSlot(
|
||||
@RequestParam(name = "bizCode") String bizCode,
|
||||
@RequestParam(name = "detailProperty") String detailProperty,
|
||||
@RequestParam(name = "slotKind", defaultValue = "LIST") String slotKind) {
|
||||
if (StringUtils.isAnyBlank(bizCode, detailProperty)) {
|
||||
return Result.error("bizCode 与 detailProperty 不能为空");
|
||||
}
|
||||
PrintBizTypeVO bizVo = printBizPermEntityService.resolveBizTypeVo(bizCode.trim());
|
||||
if (bizVo == null || StringUtils.isBlank(bizVo.getDescription())) {
|
||||
return Result.OK(Collections.emptyList());
|
||||
}
|
||||
Class<?> main = PrintBizEntityFieldIntrospector.tryLoadClass(bizVo.getDescription().trim());
|
||||
if (main == null) {
|
||||
return Result.OK(Collections.emptyList());
|
||||
}
|
||||
return Result.OK(
|
||||
PrintBizDetailPropertyScanner.listPrefixedDetailFields(
|
||||
main, detailProperty.trim(), slotKind.trim()));
|
||||
}
|
||||
|
||||
@Operation(summary = "按业务编码查询绑定(供打印调用)")
|
||||
@GetMapping("/queryByBizCode")
|
||||
@RequiresPermissions("print:bizBind:list")
|
||||
@@ -183,9 +262,12 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
|
||||
return "打印模板不存在";
|
||||
}
|
||||
entity.setTemplateCode(tpl.getTemplateCode());
|
||||
if (PrintBizTypeCatalog.getByCode(entity.getBizCode()) != null
|
||||
&& StringUtils.isBlank(entity.getBizName())) {
|
||||
entity.setBizName(PrintBizTypeCatalog.getByCode(entity.getBizCode()).getBizName());
|
||||
PrintBizTypeVO bizVo = printBizPermEntityService.resolveBizTypeVo(entity.getBizCode());
|
||||
if (bizVo == null) {
|
||||
return "无效的业务:该菜单无法解析到实体类(需菜单 component 为如 xslmes/xxx/XxxList,或在 print_biz_perm_entity 中配置 entity_class;biz_code 为菜单 id)";
|
||||
}
|
||||
if (StringUtils.isBlank(entity.getBizName())) {
|
||||
entity.setBizName(bizVo.getBizName());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -213,4 +295,17 @@ public class PrintBizTemplateBindController extends JeecgController<PrintBizTemp
|
||||
/** 业务对象 JSON(对象或可解析字符串) */
|
||||
private Object bizDataJson;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PrintBizPermWhitelistVO {
|
||||
/** 已勾选的 sys_permission.id;空集合表示未配置(不限制可选业务) */
|
||||
private java.util.List<String> permIds;
|
||||
/** 完整打印业务目录(含 linkedPermissionId,便于对照菜单) */
|
||||
private java.util.List<PrintBizTypeVO> catalog;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class PermWhitelistBody {
|
||||
private java.util.List<String> permIds;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.jeecg.modules.print.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/** 业务打印绑定可选范围:白名单(sys_permission.id) */
|
||||
@Data
|
||||
@TableName("print_biz_bind_perm_whitelist")
|
||||
public class PrintBizBindPermWhitelist implements Serializable {
|
||||
|
||||
@TableId(type = IdType.INPUT)
|
||||
private String permId;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package org.jeecg.modules.print.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.FieldStrategy;
|
||||
import com.baomidou.mybatisplus.annotation.IdType;
|
||||
import com.baomidou.mybatisplus.annotation.TableField;
|
||||
import com.baomidou.mybatisplus.annotation.TableId;
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
/** 打印业务:菜单权限与实体类映射(biz_code 使用 perm_id) */
|
||||
@Data
|
||||
@TableName("print_biz_perm_entity")
|
||||
public class PrintBizPermEntity implements Serializable {
|
||||
|
||||
@TableId(value = "perm_id", type = IdType.INPUT)
|
||||
private String permId;
|
||||
|
||||
/**
|
||||
* 实体类全限定名;为空表示占位。ALWAYS 保证 update 时带上 entity_class(含置 null),避免仅主键导致无 SET 子句。
|
||||
*/
|
||||
@TableField(updateStrategy = FieldStrategy.ALWAYS)
|
||||
private String entityClass;
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.jeecg.modules.print.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.print.entity.PrintBizBindPermWhitelist;
|
||||
|
||||
/** 打印业务菜单白名单 */
|
||||
public interface PrintBizBindPermWhitelistMapper extends BaseMapper<PrintBizBindPermWhitelist> {}
|
||||
@@ -0,0 +1,7 @@
|
||||
package org.jeecg.modules.print.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.jeecg.modules.print.entity.PrintBizPermEntity;
|
||||
|
||||
/** 菜单-实体映射 */
|
||||
public interface PrintBizPermEntityMapper extends BaseMapper<PrintBizPermEntity> {}
|
||||
@@ -0,0 +1,19 @@
|
||||
package org.jeecg.modules.print.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import java.util.List;
|
||||
import org.jeecg.modules.print.entity.PrintBizBindPermWhitelist;
|
||||
import org.jeecg.modules.print.vo.PrintBizTypeVO;
|
||||
|
||||
/** 打印业务与系统菜单白名单 */
|
||||
public interface IPrintBizBindPermWhitelistService extends IService<PrintBizBindPermWhitelist> {
|
||||
|
||||
/** 当前白名单中的菜单 id(空集合表示未配置,放行全部目录中的打印业务) */
|
||||
List<String> listPermIds();
|
||||
|
||||
/** 全量替换白名单 */
|
||||
void replacePermIds(List<String> permIds);
|
||||
|
||||
/** 「新增业务打印绑定」下拉里可用的业务类型(print_biz_perm_entity + 反射字段,受白名单过滤) */
|
||||
List<PrintBizTypeVO> listBizTypesForBinding();
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package org.jeecg.modules.print.service;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.IService;
|
||||
import java.util.List;
|
||||
import org.jeecg.modules.print.entity.PrintBizPermEntity;
|
||||
import org.jeecg.modules.print.vo.PrintBizTypeVO;
|
||||
|
||||
/** 菜单与实体映射 + 反射生成业务类型 VO */
|
||||
public interface IPrintBizPermEntityService extends IService<PrintBizPermEntity> {
|
||||
|
||||
PrintBizPermEntity getByPermId(String permId);
|
||||
|
||||
/** 全部映射对应的业务类型(含反射字段);用于目录展示与白名单弹窗 catalog */
|
||||
List<PrintBizTypeVO> listAllBizTypeVOs();
|
||||
|
||||
/**
|
||||
* 按白名单过滤:permIds 为空表示不过滤(返回全部映射);非空则仅保留 perm_id 在集合内的项。
|
||||
*/
|
||||
List<PrintBizTypeVO> listBizTypeVOsFiltered(List<String> whitelistPermIds);
|
||||
|
||||
/**
|
||||
* 解析单个菜单对应的业务类型:优先 print_biz_perm_entity,否则按菜单 component 推断实体类并反射字段。
|
||||
*/
|
||||
PrintBizTypeVO resolveBizTypeVo(String permId);
|
||||
|
||||
/**
|
||||
* 将白名单勾选的菜单写入/补全 print_biz_perm_entity:已有可加载的 entity_class 不覆盖;否则按菜单 component 推断并插入或更新。
|
||||
*/
|
||||
void upsertMappingsForWhitelist(List<String> permIds);
|
||||
|
||||
/**
|
||||
* 启动预热:扫描数据库 sys_permission(menu_type=1 且 component 非空),用表中真实的 component / component_name 推断实体类并写入
|
||||
* print_biz_perm_entity(不覆盖已有可加载的 entity_class)。
|
||||
*/
|
||||
void warmupMappingsFromSysPermissionTable();
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
package org.jeecg.modules.print.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.print.entity.PrintBizBindPermWhitelist;
|
||||
import org.jeecg.modules.print.mapper.PrintBizBindPermWhitelistMapper;
|
||||
import org.jeecg.modules.print.service.IPrintBizBindPermWhitelistService;
|
||||
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
|
||||
import org.jeecg.modules.print.vo.PrintBizTypeVO;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
@Service
|
||||
public class PrintBizBindPermWhitelistServiceImpl
|
||||
extends ServiceImpl<PrintBizBindPermWhitelistMapper, PrintBizBindPermWhitelist>
|
||||
implements IPrintBizBindPermWhitelistService {
|
||||
|
||||
@Autowired private IPrintBizPermEntityService printBizPermEntityService;
|
||||
|
||||
@Override
|
||||
public List<String> listPermIds() {
|
||||
return list().stream().map(PrintBizBindPermWhitelist::getPermId).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void replacePermIds(List<String> permIds) {
|
||||
remove(
|
||||
Wrappers.<PrintBizBindPermWhitelist>lambdaQuery()
|
||||
.isNotNull(PrintBizBindPermWhitelist::getPermId));
|
||||
if (permIds == null || permIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Set<String> seen = new HashSet<>();
|
||||
List<PrintBizBindPermWhitelist> batch = new ArrayList<>();
|
||||
for (String raw : permIds) {
|
||||
String id = StringUtils.trimToEmpty(raw);
|
||||
if (id.isEmpty() || seen.contains(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
PrintBizBindPermWhitelist row = new PrintBizBindPermWhitelist();
|
||||
row.setPermId(id);
|
||||
batch.add(row);
|
||||
}
|
||||
if (!batch.isEmpty()) {
|
||||
// 分段写入,避免单次 SQL 包过大
|
||||
saveBatch(batch, 500);
|
||||
List<String> savedIds =
|
||||
batch.stream().map(PrintBizBindPermWhitelist::getPermId).collect(Collectors.toList());
|
||||
printBizPermEntityService.upsertMappingsForWhitelist(savedIds);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizTypeVO> listBizTypesForBinding() {
|
||||
List<String> whitelist = listPermIds();
|
||||
return printBizPermEntityService.listBizTypeVOsFiltered(whitelist);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
package org.jeecg.modules.print.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.print.entity.PrintBizPermEntity;
|
||||
import org.jeecg.modules.print.mapper.PrintBizPermEntityMapper;
|
||||
import org.jeecg.modules.print.service.IPrintBizPermEntityService;
|
||||
import org.jeecg.modules.print.util.PrintBizEntityFieldIntrospector;
|
||||
import org.jeecg.modules.print.util.PrintBizMenuEntityInference;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
import org.jeecg.modules.print.vo.PrintBizTypeVO;
|
||||
import org.jeecg.modules.system.entity.SysPermission;
|
||||
import org.jeecg.modules.system.service.ISysPermissionService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@Slf4j
|
||||
@Service
|
||||
public class PrintBizPermEntityServiceImpl
|
||||
extends ServiceImpl<PrintBizPermEntityMapper, PrintBizPermEntity>
|
||||
implements IPrintBizPermEntityService {
|
||||
|
||||
@Autowired private ISysPermissionService sysPermissionService;
|
||||
|
||||
@Override
|
||||
public PrintBizPermEntity getByPermId(String permId) {
|
||||
if (StringUtils.isBlank(permId)) {
|
||||
return null;
|
||||
}
|
||||
return getById(permId.trim());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizTypeVO> listAllBizTypeVOs() {
|
||||
List<PrintBizTypeVO> out = new ArrayList<>();
|
||||
// 仅使用 print_biz_perm_entity,避免全库扫菜单+反射导致接口超时;由「保存白名单」时 upsert 写入表
|
||||
for (PrintBizPermEntity row : list()) {
|
||||
if (row == null || StringUtils.isBlank(row.getPermId())) {
|
||||
continue;
|
||||
}
|
||||
PrintBizTypeVO vo = buildVoForPermId(row.getPermId());
|
||||
if (vo != null) {
|
||||
out.add(vo);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<PrintBizTypeVO> listBizTypeVOsFiltered(List<String> whitelistPermIds) {
|
||||
if (whitelistPermIds == null || whitelistPermIds.isEmpty()) {
|
||||
return listAllBizTypeVOs();
|
||||
}
|
||||
List<PrintBizTypeVO> out = new ArrayList<>();
|
||||
for (String raw : whitelistPermIds) {
|
||||
String id = StringUtils.trimToEmpty(raw);
|
||||
if (id.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
PrintBizTypeVO vo = buildVoForPermId(id);
|
||||
if (vo != null) {
|
||||
out.add(vo);
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PrintBizTypeVO resolveBizTypeVo(String permId) {
|
||||
return buildVoForPermId(permId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void upsertMappingsForWhitelist(List<String> permIds) {
|
||||
if (permIds == null || permIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
List<String> ids = new ArrayList<>();
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (String raw : permIds) {
|
||||
String id = StringUtils.trimToEmpty(raw);
|
||||
if (id.isEmpty() || seen.contains(id)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(id);
|
||||
ids.add(id);
|
||||
}
|
||||
if (ids.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// 批量查询,避免勾选数百条时 N 次 getById 导致超时
|
||||
Map<String, PrintBizPermEntity> existingMap = new HashMap<>(ids.size());
|
||||
for (PrintBizPermEntity e : listByIds(ids)) {
|
||||
if (e != null && StringUtils.isNotBlank(e.getPermId())) {
|
||||
existingMap.put(e.getPermId(), e);
|
||||
}
|
||||
}
|
||||
Map<String, SysPermission> permMap = new HashMap<>(ids.size());
|
||||
for (SysPermission p : sysPermissionService.listByIds(ids)) {
|
||||
if (p != null && StringUtils.isNotBlank(p.getId())) {
|
||||
permMap.put(p.getId(), p);
|
||||
}
|
||||
}
|
||||
List<PrintBizPermEntity> toSave = new ArrayList<>();
|
||||
for (String id : ids) {
|
||||
PrintBizPermEntity existing = existingMap.get(id);
|
||||
// 已有可加载的实体类配置则保留,不再覆盖
|
||||
if (existing != null && StringUtils.isNotBlank(existing.getEntityClass())) {
|
||||
Class<?> loaded =
|
||||
PrintBizEntityFieldIntrospector.tryLoadClass(existing.getEntityClass().trim());
|
||||
if (loaded != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
SysPermission p = permMap.get(id);
|
||||
String inferred = PrintBizMenuEntityInference.inferEntityClassFqn(p);
|
||||
PrintBizPermEntity row = existing != null ? existing : new PrintBizPermEntity();
|
||||
row.setPermId(id);
|
||||
if (StringUtils.isNotBlank(inferred)
|
||||
&& PrintBizEntityFieldIntrospector.tryLoadClass(inferred) != null) {
|
||||
row.setEntityClass(inferred);
|
||||
} else {
|
||||
// 勾选即落库(与勾选数量一致);无法推断或类不在 classpath 时占位 NULL,可手工 UPDATE
|
||||
row.setEntityClass(null);
|
||||
}
|
||||
toSave.add(row);
|
||||
}
|
||||
if (!toSave.isEmpty()) {
|
||||
saveOrUpdateBatch(toSave, 500);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void warmupMappingsFromSysPermissionTable() {
|
||||
List<SysPermission> menus =
|
||||
sysPermissionService
|
||||
.lambdaQuery()
|
||||
.eq(SysPermission::getMenuType, 1)
|
||||
.isNotNull(SysPermission::getComponent)
|
||||
.list();
|
||||
if (menus == null || menus.isEmpty()) {
|
||||
log.info("打印菜单-实体映射预热:无子菜单数据");
|
||||
return;
|
||||
}
|
||||
List<String> ids =
|
||||
menus.stream()
|
||||
.map(SysPermission::getId)
|
||||
.filter(StringUtils::isNotBlank)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
Map<String, PrintBizPermEntity> existingMap = new HashMap<>(ids.size());
|
||||
for (PrintBizPermEntity e : listByIds(ids)) {
|
||||
if (e != null && StringUtils.isNotBlank(e.getPermId())) {
|
||||
existingMap.put(e.getPermId(), e);
|
||||
}
|
||||
}
|
||||
List<PrintBizPermEntity> toSave = new ArrayList<>();
|
||||
for (SysPermission p : menus) {
|
||||
if (p == null || StringUtils.isBlank(p.getId())) {
|
||||
continue;
|
||||
}
|
||||
String comp = p.getComponent();
|
||||
if (StringUtils.isBlank(comp)
|
||||
|| comp.contains("layouts")
|
||||
|| comp.contains("RouteView")
|
||||
|| comp.contains("ParentView")) {
|
||||
continue;
|
||||
}
|
||||
PrintBizPermEntity existing = existingMap.get(p.getId());
|
||||
if (existing != null && StringUtils.isNotBlank(existing.getEntityClass())) {
|
||||
if (PrintBizEntityFieldIntrospector.tryLoadClass(existing.getEntityClass().trim()) != null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
String inferred = PrintBizMenuEntityInference.inferEntityClassFqn(p);
|
||||
if (StringUtils.isBlank(inferred)
|
||||
|| PrintBizEntityFieldIntrospector.tryLoadClass(inferred) == null) {
|
||||
continue;
|
||||
}
|
||||
PrintBizPermEntity row = existing != null ? existing : new PrintBizPermEntity();
|
||||
row.setPermId(p.getId());
|
||||
row.setEntityClass(inferred);
|
||||
toSave.add(row);
|
||||
}
|
||||
if (toSave.isEmpty()) {
|
||||
log.info("打印菜单-实体映射预热:无新增可解析项");
|
||||
return;
|
||||
}
|
||||
saveOrUpdateBatch(toSave, 500);
|
||||
log.info("打印菜单-实体映射预热完成,本次写入/更新 {} 条(数据来自 sys_permission 表中的 component/component_name)", toSave.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* 显式表优先;否则按 SysPermission.component 推断实体类名并加载字段。
|
||||
*/
|
||||
private PrintBizTypeVO buildVoForPermId(String permId) {
|
||||
if (StringUtils.isBlank(permId)) {
|
||||
return null;
|
||||
}
|
||||
PrintBizPermEntity row = getById(permId.trim());
|
||||
String entityFqn = null;
|
||||
if (row != null && StringUtils.isNotBlank(row.getEntityClass())) {
|
||||
entityFqn = row.getEntityClass().trim();
|
||||
} else {
|
||||
SysPermission p = sysPermissionService.getById(permId.trim());
|
||||
entityFqn = PrintBizMenuEntityInference.inferEntityClassFqn(p);
|
||||
}
|
||||
if (StringUtils.isBlank(entityFqn)) {
|
||||
return null;
|
||||
}
|
||||
Class<?> clazz = PrintBizEntityFieldIntrospector.tryLoadClass(entityFqn);
|
||||
List<PrintBizFieldItemVO> fields =
|
||||
clazz == null ? new ArrayList<>() : PrintBizEntityFieldIntrospector.listFields(clazz);
|
||||
if (clazz == null) {
|
||||
// 类不在 classpath(模块未引入)时不生成下拉项,避免空字段误导
|
||||
return null;
|
||||
}
|
||||
PrintBizTypeVO vo = new PrintBizTypeVO();
|
||||
vo.setBizCode(permId.trim());
|
||||
vo.setLinkedPermissionId(permId.trim());
|
||||
vo.setBizName(resolveMenuName(permId.trim()));
|
||||
vo.setDescription(entityFqn);
|
||||
vo.setFields(fields);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String resolveMenuName(String permId) {
|
||||
SysPermission p = sysPermissionService.getById(permId);
|
||||
if (p != null && StringUtils.isNotBlank(p.getName())) {
|
||||
return p.getName();
|
||||
}
|
||||
return permId;
|
||||
}
|
||||
}
|
||||
@@ -43,11 +43,33 @@ public final class PrintBizDataMappingUtil {
|
||||
if (cur == null || p.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
cur = cur.get(p);
|
||||
if (cur.isArray()) {
|
||||
if (isNonNegativeIntString(p)) {
|
||||
cur = cur.get(Integer.parseInt(p));
|
||||
} else {
|
||||
JsonNode first = cur.size() > 0 ? cur.get(0) : null;
|
||||
cur = first != null ? first.get(p) : null;
|
||||
}
|
||||
} else {
|
||||
cur = cur.get(p);
|
||||
}
|
||||
}
|
||||
return cur;
|
||||
}
|
||||
|
||||
/** 全数字段按数组下标解析,否则在 JSON 数组上取首行再取属性(如 lines.qty 表示第一行明细的 qty) */
|
||||
private static boolean isNonNegativeIntString(String p) {
|
||||
if (p == null || p.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
for (int i = 0; i < p.length(); i++) {
|
||||
if (!Character.isDigit(p.charAt(i))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void setPath(ObjectNode target, String path, JsonNode value) {
|
||||
if (target == null || StringUtils.isBlank(path)) {
|
||||
return;
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
package org.jeecg.modules.print.util;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
import org.jeecg.modules.print.vo.PrintBizDetailSlotVO;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
|
||||
/**
|
||||
* 扫描主实体类上可作为「明细」的来源属性:{@code List<Entity>} / {@code Set<Entity>} / 数组 / 嵌套业务对象。
|
||||
*/
|
||||
public final class PrintBizDetailPropertyScanner {
|
||||
|
||||
private PrintBizDetailPropertyScanner() {}
|
||||
|
||||
/**
|
||||
* 根据已选明细槽位解析元素类型:LIST 取集合元素类或数组组件类型;OBJECT 取嵌套属性类型。
|
||||
*
|
||||
* @param slotKind LIST 或 OBJECT(与 {@link PrintBizDetailSlotVO#getSlotKind()} 一致)
|
||||
*/
|
||||
public static Class<?> resolveItemClassForSlot(
|
||||
Class<?> mainClazz, String propertyName, String slotKind) {
|
||||
if (mainClazz == null || StringUtils.isBlank(propertyName)) {
|
||||
return null;
|
||||
}
|
||||
Field f = findDeclaredField(mainClazz, propertyName.trim());
|
||||
if (f == null) {
|
||||
return null;
|
||||
}
|
||||
if ("OBJECT".equalsIgnoreCase(StringUtils.trimToEmpty(slotKind))) {
|
||||
Class<?> t = f.getType();
|
||||
return isLikelyBizBean(t) ? t : null;
|
||||
}
|
||||
Class<?> elem = resolveCollectionElementClass(f);
|
||||
if (elem != null) {
|
||||
return elem;
|
||||
}
|
||||
if (f.getType().isArray()) {
|
||||
Class<?> comp = f.getType().getComponentType();
|
||||
return isSimpleOrJdkValueType(comp) ? null : comp;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Field findDeclaredField(Class<?> start, String name) {
|
||||
Class<?> c = start;
|
||||
while (c != null && c != Object.class) {
|
||||
try {
|
||||
return c.getDeclaredField(name);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** 明细元素类型反射字段,fieldKey 已带「属性名.」前缀,便于与模板明细占位映射 */
|
||||
public static List<PrintBizFieldItemVO> listPrefixedDetailFields(
|
||||
Class<?> mainClazz, String propertyName, String slotKind) {
|
||||
Class<?> item = resolveItemClassForSlot(mainClazz, propertyName, slotKind);
|
||||
if (item == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
List<PrintBizFieldItemVO> raw = PrintBizEntityFieldIntrospector.listFields(item);
|
||||
String prefix = propertyName.trim();
|
||||
List<PrintBizFieldItemVO> out = new ArrayList<>(raw.size());
|
||||
for (PrintBizFieldItemVO x : raw) {
|
||||
String path = prefix + "." + x.getFieldKey();
|
||||
String label = "明细「" + prefix + "」→ " + x.getLabel();
|
||||
out.add(new PrintBizFieldItemVO(path, label, x.getDescription()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public static List<PrintBizDetailSlotVO> listSlots(Class<?> mainClazz) {
|
||||
Map<String, PrintBizDetailSlotVO> ordered = new LinkedHashMap<>();
|
||||
Class<?> c = mainClazz;
|
||||
while (c != null && c != Object.class) {
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
int mod = f.getModifiers();
|
||||
if (Modifier.isStatic(mod) || f.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
String name = f.getName();
|
||||
if ("serialVersionUID".equals(name)) {
|
||||
continue;
|
||||
}
|
||||
Class<?> elem = resolveCollectionElementClass(f);
|
||||
if (elem != null && isLikelyBizBean(elem)) {
|
||||
ordered.putIfAbsent(
|
||||
name,
|
||||
new PrintBizDetailSlotVO(name, elem.getName(), "LIST", resolveFieldLabel(f)));
|
||||
continue;
|
||||
}
|
||||
Class<?> ft = f.getType();
|
||||
if (ft.isArray() && !isSimpleOrJdkValueType(ft.getComponentType())) {
|
||||
Class<?> comp = ft.getComponentType();
|
||||
ordered.putIfAbsent(
|
||||
name, new PrintBizDetailSlotVO(name, comp.getName(), "LIST", resolveFieldLabel(f)));
|
||||
continue;
|
||||
}
|
||||
if (!ft.isPrimitive()
|
||||
&& !ft.getName().startsWith("java.lang")
|
||||
&& !Number.class.isAssignableFrom(ft)
|
||||
&& !java.util.Date.class.isAssignableFrom(ft)
|
||||
&& !ft.getName().startsWith("java.time")
|
||||
&& !Map.class.isAssignableFrom(ft)
|
||||
&& !Collection.class.isAssignableFrom(ft)
|
||||
&& isLikelyBizBean(ft)) {
|
||||
ordered.putIfAbsent(
|
||||
name, new PrintBizDetailSlotVO(name, ft.getName(), "OBJECT", resolveFieldLabel(f)));
|
||||
}
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return new ArrayList<>(ordered.values());
|
||||
}
|
||||
|
||||
/** 解析 List<T> / Set<T> 的元素类型 T */
|
||||
private static Class<?> resolveCollectionElementClass(Field f) {
|
||||
Type gt = f.getGenericType();
|
||||
if (!(gt instanceof ParameterizedType)) {
|
||||
return null;
|
||||
}
|
||||
ParameterizedType pt = (ParameterizedType) gt;
|
||||
Type raw = pt.getRawType();
|
||||
if (!(raw instanceof Class<?> rc) || !Collection.class.isAssignableFrom(rc)) {
|
||||
return null;
|
||||
}
|
||||
Type[] args = pt.getActualTypeArguments();
|
||||
if (args.length != 1) {
|
||||
return null;
|
||||
}
|
||||
Type arg0 = args[0];
|
||||
if (arg0 instanceof Class<?>) {
|
||||
Class<?> ac = (Class<?>) arg0;
|
||||
return isSimpleOrJdkValueType(ac) ? null : ac;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSimpleOrJdkValueType(Class<?> cl) {
|
||||
if (cl == null || cl.isPrimitive()) {
|
||||
return true;
|
||||
}
|
||||
if (cl.isEnum()) {
|
||||
return true;
|
||||
}
|
||||
String n = cl.getName();
|
||||
if (n.startsWith("java.lang") || n.startsWith("java.time")) {
|
||||
return true;
|
||||
}
|
||||
if (Number.class.isAssignableFrom(cl) || java.util.Date.class.isAssignableFrom(cl)) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** 非 JDK 简单值、非集合/Map 的自定义类型视为可映射明细实体 */
|
||||
private static boolean isLikelyBizBean(Class<?> cl) {
|
||||
return cl != null && !cl.isEnum() && !isSimpleOrJdkValueType(cl) && !Map.class.isAssignableFrom(cl);
|
||||
}
|
||||
|
||||
private static String resolveFieldLabel(Field f) {
|
||||
Schema schema = f.getAnnotation(Schema.class);
|
||||
if (schema != null && StringUtils.isNotBlank(schema.description())) {
|
||||
return schema.description().trim();
|
||||
}
|
||||
Excel excel = f.getAnnotation(Excel.class);
|
||||
if (excel != null && StringUtils.isNotBlank(excel.name())) {
|
||||
return excel.name().trim();
|
||||
}
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package org.jeecg.modules.print.util;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.print.vo.PrintBizFieldItemVO;
|
||||
import org.jeecgframework.poi.excel.annotation.Excel;
|
||||
|
||||
/**
|
||||
* 从实体类反射可映射字段(供打印业务绑定下拉与映射);子类字段优先于父类同名字段。
|
||||
*/
|
||||
public final class PrintBizEntityFieldIntrospector {
|
||||
|
||||
private PrintBizEntityFieldIntrospector() {}
|
||||
|
||||
public static List<PrintBizFieldItemVO> listFields(Class<?> clazz) {
|
||||
Map<String, PrintBizFieldItemVO> ordered = new LinkedHashMap<>();
|
||||
Class<?> c = clazz;
|
||||
while (c != null && c != Object.class) {
|
||||
for (Field f : c.getDeclaredFields()) {
|
||||
int mod = f.getModifiers();
|
||||
if (Modifier.isStatic(mod) || f.isSynthetic()) {
|
||||
continue;
|
||||
}
|
||||
String name = f.getName();
|
||||
if ("serialVersionUID".equals(name)) {
|
||||
continue;
|
||||
}
|
||||
ordered.putIfAbsent(name, new PrintBizFieldItemVO(name, resolveLabel(f), ""));
|
||||
}
|
||||
c = c.getSuperclass();
|
||||
}
|
||||
return new ArrayList<>(ordered.values());
|
||||
}
|
||||
|
||||
private static String resolveLabel(Field f) {
|
||||
Schema schema = f.getAnnotation(Schema.class);
|
||||
if (schema != null && StringUtils.isNotBlank(schema.description())) {
|
||||
return schema.description().trim();
|
||||
}
|
||||
Excel excel = f.getAnnotation(Excel.class);
|
||||
if (excel != null && StringUtils.isNotBlank(excel.name())) {
|
||||
return excel.name().trim();
|
||||
}
|
||||
return f.getName();
|
||||
}
|
||||
|
||||
/** 按全限定类名加载 Class,失败返回 null */
|
||||
public static Class<?> tryLoadClass(String entityClassFqn) {
|
||||
if (StringUtils.isBlank(entityClassFqn)) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return Class.forName(entityClassFqn.trim());
|
||||
} catch (Throwable e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package org.jeecg.modules.print.util;
|
||||
|
||||
import java.util.Objects;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.jeecg.modules.system.entity.SysPermission;
|
||||
|
||||
/**
|
||||
* 根据菜单 component / componentName 推断实体类全名。<br>
|
||||
* 典型:<br>
|
||||
* - xslmes/mesXslWarehouseArea/MesXslWarehouseAreaList<br>
|
||||
* - mes/materialinfo/index + componentName=MesMaterialList → org.jeecg.modules.mes.material.entity.MesMaterial
|
||||
*/
|
||||
public final class PrintBizMenuEntityInference {
|
||||
|
||||
/** MES 物料等菜单实体默认落在该包下(与工程 mes.material.entity 一致) */
|
||||
private static final String MES_MATERIAL_ENTITY_PKG = "org.jeecg.modules.mes.material.entity.";
|
||||
|
||||
private PrintBizMenuEntityInference() {}
|
||||
|
||||
/**
|
||||
* @return 实体类全限定名;无法推断时返回 null(按钮、目录等)
|
||||
*/
|
||||
public static String inferEntityClassFqn(SysPermission permission) {
|
||||
if (permission == null) {
|
||||
return null;
|
||||
}
|
||||
if (Objects.equals(permission.getMenuType(), 2)) {
|
||||
return null;
|
||||
}
|
||||
String comp = permission.getComponent();
|
||||
if (StringUtils.isBlank(comp)) {
|
||||
return null;
|
||||
}
|
||||
String c = comp.trim();
|
||||
if (c.contains("layouts")
|
||||
|| c.contains("RouteView")
|
||||
|| c.contains("ParentView")
|
||||
|| c.startsWith("http")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Jeecg Vue:component 常为 mes/xxx/index,实体类名在 component_name(如 MesMaterialList)
|
||||
if (c.startsWith("mes/") && c.endsWith("/index")) {
|
||||
String fromMesIndex =
|
||||
tryInferMesMaterialModuleFromComponentName(permission.getComponentName());
|
||||
if (StringUtils.isNotBlank(fromMesIndex)) {
|
||||
return fromMesIndex;
|
||||
}
|
||||
}
|
||||
|
||||
String[] segs = c.split("/");
|
||||
if (segs.length < 2) {
|
||||
return null;
|
||||
}
|
||||
String last = segs[segs.length - 1];
|
||||
if (StringUtils.isBlank(last)) {
|
||||
return null;
|
||||
}
|
||||
String simple =
|
||||
last.endsWith("List") ? last.substring(0, last.length() - 4) : last;
|
||||
if (simple.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String module = segs[0];
|
||||
if (StringUtils.isBlank(module)) {
|
||||
return null;
|
||||
}
|
||||
String trial = "org.jeecg.modules." + module + ".entity." + simple;
|
||||
if (PrintBizEntityFieldIntrospector.tryLoadClass(trial) != null) {
|
||||
return trial;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** mes 模块下 index 路由:用 componentName(MesXxxList)推断 mes.material.entity.MesXxx */
|
||||
private static String tryInferMesMaterialModuleFromComponentName(String componentName) {
|
||||
if (StringUtils.isBlank(componentName)) {
|
||||
return null;
|
||||
}
|
||||
String cn = componentName.trim();
|
||||
if (!cn.endsWith("List")) {
|
||||
return null;
|
||||
}
|
||||
String simple = cn.substring(0, cn.length() - 4);
|
||||
if (simple.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
String trial = MES_MATERIAL_ENTITY_PKG + simple;
|
||||
if (PrintBizEntityFieldIntrospector.tryLoadClass(trial) != null) {
|
||||
return trial;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package org.jeecg.modules.print.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.io.Serializable;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
/** 主实体上可作为「明细数据来源」的属性(集合元素类型或嵌套对象类型) */
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
@Schema(description = "业务实体明细槽位")
|
||||
public class PrintBizDetailSlotVO implements Serializable {
|
||||
|
||||
@Schema(description = "Java 属性名(JSON 路径前缀,如 lines、headerExt)")
|
||||
private String propertyName;
|
||||
|
||||
@Schema(description = "明细元素类型全限定名")
|
||||
private String itemEntityClassFqn;
|
||||
|
||||
@Schema(description = "LIST=集合明细一行映射;OBJECT=嵌套对象字段映射")
|
||||
private String slotKind;
|
||||
|
||||
@Schema(description = "展示名")
|
||||
private String label;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ import lombok.Data;
|
||||
@Data
|
||||
@Schema(description = "可配置的业务类型")
|
||||
public class PrintBizTypeVO implements Serializable {
|
||||
@Schema(description = "业务编码")
|
||||
@Schema(description = "业务编码(与菜单权限 id、绑定表 biz_code 一致)")
|
||||
private String bizCode;
|
||||
|
||||
@Schema(description = "业务名称")
|
||||
@@ -17,6 +17,13 @@ public class PrintBizTypeVO implements Serializable {
|
||||
@Schema(description = "说明")
|
||||
private String description;
|
||||
|
||||
/**
|
||||
* 与「系统菜单」中的菜单主键(sys_permission.id)对应;用于「打印业务白名单」按菜单勾选。
|
||||
* 未设置表示未挂菜单,白名单生效时仍可出现于下拉(避免仅后端注册、尚未配菜单的业务被误过滤)。
|
||||
*/
|
||||
@Schema(description = "关联菜单权限ID(sys_permission.id)")
|
||||
private String linkedPermissionId;
|
||||
|
||||
@Schema(description = "业务侧可用字段目录")
|
||||
private List<PrintBizFieldItemVO> fields;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user