Java如何不建表完成各种复杂的映射关系(鉴权概念、区域概念、通用概念)

工作中会发现很多东西数据现在不多,未来也不会多,建表的成本开销就太大了,很多时候都会选择使用枚举来做,但是又有一个问题,不使用表的格式,未来增加相关映射关系的时候就会很麻烦,头都大了,假设有三个枚举,我们就要维护三个枚举,如果涉及到映射关系,那就需要再加。

个枚举,。在下次呢,。后面的人去改代码的时候怎么办,。一个一个加枚举吗?肯定不太现实。

无表映射(Table-less Mapping)反而能带来更优雅、高效的解决方案。

对于同一个业务而言,如果不想建表(未来几年都不会扩展的时候,但是映射关系经常变更)

我见过的几种方式

1:配置中心配置+代码映射

企业项目大多都是基于nacos配置中心来区分不同环境的如test,overseatest,pre,overseapre等等,如何仍然使用枚举的化就会涉及到每次要重新发版,如果使用配置中心,就只需求去配置中心增加相关映射关系并重新服务就ok了。成本要小很多。

关系很简单,只涉及一个类,一个yml

以用户行为标签表做映射配置

类写法

java 复制代码
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

/**
 * 用户行为标签映射配置类
 */
@Component
@RefreshScope  // 支持配置热更新
@ConfigurationProperties(prefix = "user-tag-mapping")
@Data
public class UserTagMappingConfig {
    //以下是一些通用配置,也可以nacos配置,。通过@value,这里只演示list字段
    /**
     * 配置版本
     */
    private String version;
    
    /**
     * 最后更新时间
     */
    private String lastUpdated;
    
    /**
     * 默认标签(未匹配到时使用)
     */
    private String defaultTag;
    
    /**
     * 是否启用缓存
     */
    private Boolean enableCache;
    
    /**
     * 缓存持续时间(秒)
     */
    private Integer cacheDuration;
    
    /**
     * 映射关系列表
     */
    private List<TagMappingItem> mappings = new ArrayList<>();
    
    
    /**
     * 根据行为类型获取标签名称(单条匹配)
     * 
     * @param actionType 行为类型
     * @return 对应的标签名称,未找到返回默认标签
     */
    public String getTagNameByActionType(String actionType) {
        if (actionType == null || actionType.trim().isEmpty()) {
            return defaultTag;
        }
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .filter(item -> actionType.equals(item.getActionType()))
                .findFirst()  // 返回第一条匹配的记录
                .map(TagMappingItem::getTagName)
                .orElse(defaultTag);
    }
    
    /**
     * 根据行为类型获取所有匹配的标签名称(多条匹配)
     * 
     * @param actionType 行为类型
     * @return 所有匹配的标签名称列表
     */
    public List<String> getAllTagNamesByActionType(String actionType) {
        if (actionType == null || actionType.trim().isEmpty()) {
            return new ArrayList<>();
        }
        
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .filter(item -> actionType.equals(item.getActionType()))
                .map(TagMappingItem::getTagName)
                .distinct()
                .collect(Collectors.toList());
    }
    
    /**
     * 根据行为类型获取优先级最高的标签
     * 
     * @param actionType 行为类型
     * @return 优先级最高的标签名称
     */
    public String getHighestPriorityTag(String actionType) {
        if (actionType == null || actionType.trim().isEmpty()) {
            return defaultTag;
        }
        
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .filter(item -> actionType.equals(item.getActionType()))
                .max((a, b) -> Integer.compare(
                    Optional.ofNullable(a.getPriority()).orElse(0),
                    Optional.ofNullable(b.getPriority()).orElse(0)
                ))
                .map(TagMappingItem::getTagName)
                .orElse(defaultTag);
    }
    
    /**
     * 根据行为类型和条件上下文获取标签
     * 
     * @param actionType 行为类型
     * @param context 条件上下文(用于SpEL表达式判断)
     * @return 符合条件的标签名称
     */
    public String getTagNameWithCondition(String actionType, Map<String, Object> context) {
        if (actionType == null || actionType.trim().isEmpty()) {
            return defaultTag;
        }
        
        // 这里可以集成SpEL表达式引擎来评估conditions字段
        // 简化版:如果有conditions配置,需要context不为空
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .filter(item -> actionType.equals(item.getActionType()))
                .filter(item -> evaluateCondition(item.getConditions(), context))
                .findFirst()
                .map(TagMappingItem::getTagName)
                .orElse(defaultTag);
    }
    
    /**
     * 获取所有行为类型与标签的映射关系
     * 
     * @return Map<actionType, List<tagName>>
     */
    public Map<String, List<String>> getAllMappings() {
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .collect(Collectors.groupingBy(
                    TagMappingItem::getActionType,
                    Collectors.mapping(TagMappingItem::getTagName, Collectors.toList())
                ));
    }
    
    /**
     * 检查行为类型是否存在映射
     * 
     * @param actionType 行为类型
     * @return 是否存在映射
     */
    public boolean hasMappingForAction(String actionType) {
        if (actionType == null || actionType.trim().isEmpty()) {
            return false;
        }
        
        return mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .anyMatch(item -> actionType.equals(item.getActionType()));
    }
    
    /**
     * 获取配置统计信息
     */
    public Map<String, Object> getStats() {
        long totalMappings = mappings.size();
        long enabledMappings = mappings.stream()
                .filter(item -> item.getEnabled() != Boolean.FALSE)
                .count();
        long uniqueActionTypes = mappings.stream()
                .map(TagMappingItem::getActionType)
                .distinct()
                .count();
        
        return Map.of(
            "version", version,
            "totalMappings", totalMappings,
            "enabledMappings", enabledMappings,
            "uniqueActionTypes", uniqueActionTypes,
            "defaultTag", defaultTag
        );
    }
    
    /**
     * 评估条件表达式(简化版)
     * 实际项目中可以集成Spring SpEL或Groovy引擎
     */
    private boolean evaluateCondition(String condition, Map<String, Object> context) {
        if (condition == null || condition.trim().isEmpty()) {
            return true; // 无条件限制
        }
        
        if (context == null || context.isEmpty()) {
            return false; // 有条件但无上下文,无法评估
        }
        
        // 简化实现:实际项目中应使用表达式引擎
        // 这里只是示例,实际需要解析condition字符串
        try {
            // 示例:假设condition是简单的属性判断
            // 实际应该用SpEL: SpelExpressionParser.parseExpression(condition).getValue(context, Boolean.class)
            return true;
        } catch (Exception e) {
            // 表达式解析失败,默认不匹配
            return false;
        }
    }
//映射的内部类相关字段定义
@Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    public static class TagMappingItem implements Serializable {
/**
     * 行为类型 - 作为查询键
     */
    private String actionType;
    
    /**
     * 对应的标签名称 - 主要查询目标
     */
    private String tagName;
    
    /**
     * 优先级(数字越大优先级越高)
     */
    private Integer priority;
    
    /**
     * 标签过期天数
     */
    private Integer expirationDays;
    
    /**
     * 附加条件(SpEL表达式)
     */
    private String conditions;
    
    /**
     * 子标签(可选)
     */
    private String subTag;
    
    /**
     * 标签描述(可选)
     */
    private String description;
    
    /**
     * 是否启用
     */
    @Builder.Default
    private Boolean enabled = true;
    
    /**
     * 创建时间
     */
    private String createTime;
    }
}

yml文件

java 复制代码
# 用户行为标签映射配置
user-tag-mapping:
  version: "1.0.2"
  last-updated: "2024-01-15T14:30:00Z"
  
  # 通用配置
  default-tag: "新用户"
  enable-cache: true
  cache-duration: 300  # 秒
  
  # 核心映射列表
  mappings:
    - action-type: "login"
      tag-name: "活跃用户"
      priority: 10
      expiration-days: 30
      conditions: "frequency > 3 && lastLoginDays < 7"
    
    - action-type: "purchase"
      tag-name: "消费用户"
      priority: 20
      expiration-days: 90
      conditions: "totalAmount > 100"
      sub-tag: "高价值用户"
    
    - action-type: "purchase"
      tag-name: "普通买家"
      priority: 15
      expiration-days: 60
      conditions: "totalAmount > 0 && totalAmount <= 100"
    
    - action-type: "comment"
      tag-name: "内容贡献者"
      priority: 5
      expiration-days: 180
      conditions: "commentCount > 5"
    
    - action-type: "share"
      tag-name: "传播达人"
      priority: 8
      expiration-days: 90
      conditions: "shareCount > 10"
    
    - action-type: "login"
      tag-name: "忠实用户"
      priority: 25
      expiration-days: 365
      conditions: "loginDays > 100 && continuousLoginDays > 30"
    
    - action-type: "register"
      tag-name: "新注册用户"
      priority: 1
      expiration-days: 7
      conditions: "registerDays < 7"
    
    - action-type: "refund"
      tag-name: "售后用户"
      priority: 30
      expiration-days: 180
      conditions: "refundCount > 0"
    
    - action-type: "subscribe"
      tag-name: "关注用户"
      priority: 3
      expiration-days: 30
      conditions: "subscribeTime != null"

2:嵌套枚举(区域概念模型)

举个例子,比如在做州国省城的映射关系的时候,不是每个国家都有省的概念的,也不是都有州的概念的,而对于区县市完全就可以使用通用的字段,这里可以建表来做,字段就没有那么多了,直接将大层面的东西用嵌套枚举做,就可以了。

java 复制代码
/**
 * 全球行政区划枚举树
 */
@Getter
@NoArgsConstructor
public enum AdministrativeRegion {
    
    // 根节点 → 大洲 → 国家
    WORLD("世界", 
        ASIA("亚洲",
            CHINA("中国",
                BEIJING("北京市", "京", 
                    DISTRICT("东城区"), DISTRICT("西城区"), DISTRICT("朝阳区")),
                SHANGHAI("上海市", "沪",
                    DISTRICT("黄浦区"), DISTRICT("浦东新区"), DISTRICT("徐汇区")),
                GUANGDONG("广东省", "粤",
                    GUANGZHOU("广州市"), SHENZHEN("深圳市"), DONGGUAN("东莞市"))
            ),
            JAPAN("日本", "JP",
                TOKYO("东京都"), OSAKA("大阪府"), KYOTO("京都府")
            )
        ),
        //根节点,大洲-国家
        EUROPE("欧洲",
            FRANCE("法国", "FR",
                ILE_DE_FRANCE("法兰西岛大区", 
                    PARIS("巴黎市"))
            ),
            GERMANY("德国", "DE",
                BAVARIA("巴伐利亚州", 
                    MUNICH("慕尼黑市")),
                BERLIN("柏林州", 
                    BERLIN_CITY("柏林市"))
            )
        )
    );
    //需要的名字
    private final String name;
//代号
    private final String code; 
//父子关系
    private final AdministrativeRegion[] children;
// 反向引用
    private AdministrativeRegion parent; 
    
    // 叶节点构造(城市)
    AdministrativeRegion(String name) {
        this(name, null);
    }
    
    AdministrativeRegion(String name, String code) {
        this(name, code, new AdministrativeRegion[0]);
    }
    
    // 非叶节点构造(有子节点)
    AdministrativeRegion(String name, AdministrativeRegion... children) {
        this(name, null, children);
    }
    
    AdministrativeRegion(String name, String code, AdministrativeRegion... children) {
        this.name = name;
        this.code = code;
        this.children = children;
        
        // 建立反向父子关系
        for (AdministrativeRegion child : children) {
            child.parent = this;
        }
    }
    
    /**
     * 查找完整路径:中国/北京市/朝阳区
     */
    public static List<AdministrativeRegion> findPath(String... names) {
        if (names == null || names.length == 0) {
            return Collections.emptyList();
        }
        
        List<AdministrativeRegion> path = new ArrayList<>();
        AdministrativeRegion current = WORLD;
        
        for (String name : names) {
            Optional<AdministrativeRegion> found = Arrays.stream(current.children)
                .filter(region -> region.name.equals(name))
                .findFirst();
            
            if (found.isPresent()) {
                current = found.get();
                path.add(current);
            } else {
// 路径中断
                break; 
            }
        }
        
        return path.size() == names.length ? path : Collections.emptyList();
    }
    
    /**
     * 获取所有叶子节点(所有添加的最底层枚举)
     */
    public List<AdministrativeRegion> getAllCities() {
        List<AdministrativeRegion> cities = new ArrayList<>();
        traverseForLeaves(this, cities);
        return cities;
    }
    
    private void traverseForLeaves(AdministrativeRegion node, 
                                  List<AdministrativeRegion> result) {
        if (node.children.length == 0) {
            result.add(node);
        } else {
            for (AdministrativeRegion child : node.children) {
                traverseForLeaves(child, result);
            }
        }
    }
    
    /**
     * 获取兄弟节点(同一层级的所有节点)
     */
    public List<AdministrativeRegion> getSiblings() {
        if (parent == null) {
            return Collections.emptyList(); // 根节点没有兄弟
        }
        return Arrays.asList(parent.children);
    }
    
    /**
     * 根据code快速查找(如"京"找北京)
     */
    public static Optional<AdministrativeRegion> findByCode(String code) {
        return deepFind(WORLD, region -> code.equals(region.code));
    }
    
   
    //optinal保证安全
    private static Optional<AdministrativeRegion> deepFind(
            AdministrativeRegion start, 
            Predicate<AdministrativeRegion> predicate) {
        
        if (predicate.test(start)) {
            return Optional.of(start);
        }
        
        for (AdministrativeRegion child : start.children) {
            Optional<AdministrativeRegion> found = deepFind(child, predicate);
            if (found.isPresent()) {
                return found;
            }
        }
        
        return Optional.empty();
    }
   
    
    public String getFullPath() {
        List<String> names = new ArrayList<>();
        AdministrativeRegion current = this;
        
        while (current != null && current != WORLD) {
            names.add(0, current.name);
            current = current.parent;
        }
        
        return String.join("/", names);
    }
    
    public boolean isLeaf() {
        return children.length == 0;
    }
    
    public int getDepth() {
        int depth = 0;
        AdministrativeRegion current = this;
        
        while (current != null && current != WORLD) {
            depth++;
            current = current.parent;
        }
        
        return depth;
    }
    
    // 内部枚举:区分节点类型
    private static enum NodeType {
        CONTINENT, COUNTRY, PROVINCE, CITY, DISTRICT
    }
}

3:图状枚举(鉴权基本概念模型)

java 复制代码
/**
 * 
 * 用户角色与权限、用户间关系枚举
 */
@Getter
public enum SocialGraph {
    
    // 节点定义(用户角色)
    ADMIN("管理员", RoleLevel.SYSTEM),
    MODERATOR("版主", RoleLevel.MANAGEMENT),
    VIP_USER("VIP用户", RoleLevel.PRIVILEGED),
    REGULAR_USER("普通用户", RoleLevel.NORMAL),
    GUEST("游客", RoleLevel.RESTRICTED),
    
    // 内容类型
    PUBLIC_POST("公开帖子", ContentType.PUBLIC),
    PRIVATE_MESSAGE("私信", ContentType.PRIVATE),
    GROUP_CHAT("群聊消息", ContentType.GROUP),
    COMMENT("评论", ContentType.PUBLIC),
    
    // 权限动作
    READ("读取", ActionType.VIEW),
    WRITE("写入", ActionType.MODIFY),
    DELETE("删除", ActionType.MODIFY),
    SHARE("分享", ActionType.SOCIAL),
    INVITE("邀请", ActionType.SOCIAL);
    
    private final String displayName;
    private final NodeType type;
    
    SocialGraph(String displayName, NodeType type) {
        this.displayName = displayName;
        this.type = type;
    }
    
    
    // 邻接表:定义节点间关系
    private static final Map<SocialGraph, Set<Edge>> GRAPH = new EnumMap<>(SocialGraph.class);
    
    // 关系权重矩阵
    private static final Map<Pair<SocialGraph, SocialGraph>, Integer> WEIGHTS = new HashMap<>();
    
//先初始化
    static {
        initGraph();
        initWeights();
    }
    
    private static void initGraph() {
        // 角色 → 可访问的内容类型
        addEdge(ADMIN, PUBLIC_POST, 1);
        addEdge(ADMIN, PRIVATE_MESSAGE, 1);
        addEdge(ADMIN, GROUP_CHAT, 1);
        
        addEdge(MODERATOR, PUBLIC_POST, 1);
        addEdge(MODERATOR, COMMENT, 1);
        
        addEdge(VIP_USER, PUBLIC_POST, 1);
// 部分权限
        addEdge(VIP_USER, GROUP_CHAT, 0.5); 
        
        addEdge(REGULAR_USER, PUBLIC_POST, 1);
        
        // 内容类型 → 允许的操作
        addEdge(PUBLIC_POST, READ, 1);
        addEdge(PUBLIC_POST, WRITE, 0.8);
        addEdge(PUBLIC_POST, DELETE, 0.3);
        addEdge(PUBLIC_POST, SHARE, 1);
        
        addEdge(PRIVATE_MESSAGE, READ, 1);
        addEdge(PRIVATE_MESSAGE, WRITE, 1);
        addEdge(PRIVATE_MESSAGE, DELETE, 0.5);
        
        // 操作 → 需要的最低角色
        addInverseEdge(READ, GUEST); 
// 游客也能读
        addInverseEdge(WRITE, REGULAR_USER);
        addInverseEdge(DELETE, MODERATOR);
        addInverseEdge(SHARE, REGULAR_USER);
        addInverseEdge(INVITE, VIP_USER);
    }
    
    private static void initWeights() {
        // 定义关系强度(用于最短路径计算)
        WEIGHTS.put(Pair.of(ADMIN, PUBLIC_POST), 1);
        WEIGHTS.put(Pair.of(ADMIN, DELETE), 1);
        WEIGHTS.put(Pair.of(GUEST, READ), 5); 
// 游客读权限"距离远"
        WEIGHTS.put(Pair.of(VIP_USER, INVITE), 2);
     
    }
    
    
    /**
     * 检查权限:角色是否能对内容执行操作
     * 查找路径:角色 → 内容类型 → 操作
     */
    public static boolean hasPermission(SocialGraph role, 
                                       SocialGraph contentType, 
                                       SocialGraph action) {
        // 使用BFS查找是否存在路径
        return findPath(role, action, 
// 必须经过内容类型节点
            node -> node == contentType,  3 
// 最大深度限制
        ).isPresent();
    }
    
    /**
     * 查找最短权限路径
     */
    public static Optional<List<SocialGraph>> findShortestPermissionPath(
            SocialGraph from, SocialGraph to) {
        
        // Dijkstra算法实现
        Map<SocialGraph, Integer> distances = new EnumMap<>(SocialGraph.class);
        Map<SocialGraph, SocialGraph> previous = new EnumMap<>(SocialGraph.class);
        PriorityQueue<SocialGraph> queue = new PriorityQueue<>(
            Comparator.comparingInt(node -> distances.getOrDefault(node, Integer.MAX_VALUE))
        );
        
        distances.put(from, 0);
        queue.add(from);
        
        while (!queue.isEmpty()) {
            SocialGraph current = queue.poll();
            
            if (current == to) break;
            
            for (Edge edge : GRAPH.getOrDefault(current, Collections.emptySet())) {
                int weight = WEIGHTS.getOrDefault(
                    Pair.of(current, edge.target), 
                    edge.defaultWeight
                );
                
                int newDist = distances.get(current) + weight;
                if (newDist < distances.getOrDefault(edge.target, Integer.MAX_VALUE)) {
                    distances.put(edge.target, newDist);
                    previous.put(edge.target, current);
                    queue.add(edge.target);
                }
            }
        }
        
        // 重建路径
        if (!distances.containsKey(to)) {
            return Optional.empty();
        }
        
        List<SocialGraph> path = new LinkedList<>();
        for (SocialGraph node = to; node != null; node = previous.get(node)) {
            path.add(0, node);
        }
        
        return Optional.of(path);
    }
    
    /**
     * 获取角色的所有直接权限
     */
    public static Set<SocialGraph> getDirectPermissions(SocialGraph role) {
        return GRAPH.getOrDefault(role, Collections.emptySet()).stream()
            .map(edge -> edge.target)
            .collect(Collectors.toSet());
    }
    
    /**
     * 查找需要特定权限的最小角色
     */
    public static Optional<SocialGraph> findMinimalRoleForAction(SocialGraph action) {
        return Arrays.stream(values())
            .filter(node -> node.type == RoleLevel.NORMAL || 
                           node.type == RoleLevel.PRIVILEGED ||
                           node.type == RoleLevel.MANAGEMENT)
            .filter(role -> hasPermission(role, PUBLIC_POST, action)) // 以公开帖子为例
            .min(Comparator.comparingInt(Enum::ordinal));
    }
    
    private static void addEdge(SocialGraph from, SocialGraph to, double weight) {
        GRAPH.computeIfAbsent(from, k -> new HashSet<>())
             .add(new Edge(to, weight));
    }
    
    private static void addInverseEdge(SocialGraph from, SocialGraph to) {
        addEdge(from, to, 1.0);
// 双向关系
        addEdge(to, from, 1.0); 
    }
    
    private static Optional<List<SocialGraph>> findPath(
            SocialGraph start, SocialGraph end, 
            Predicate<SocialGraph> mustPass, int maxDepth) {
        
        Deque<PathNode> stack = new ArrayDeque<>();
        Set<SocialGraph> visited = new HashSet<>();
        
        stack.push(new PathNode(start, Collections.emptyList()));
        
        while (!stack.isEmpty()) {
            PathNode current = stack.pop();
            
            if (current.depth > maxDepth) continue;
            if (!visited.add(current.node)) continue;
            
            List<SocialGraph> newPath = new ArrayList<>(current.path);
            newPath.add(current.node);
            
            if (current.node == end && 
                (mustPass == null || newPath.stream().anyMatch(mustPass))) {
                return Optional.of(newPath);
            }
            
            for (Edge edge : GRAPH.getOrDefault(current.node, Collections.emptySet())) {
                stack.push(new PathNode(edge.target, newPath, current.depth + 1));
            }
        }
        
        return Optional.empty();
    }
    
    private enum RoleLevel { SYSTEM, MANAGEMENT, PRIVILEGED, NORMAL, RESTRICTED }
    private enum ContentType { PUBLIC, PRIVATE, GROUP }
    private enum ActionType { VIEW, MODIFY, SOCIAL }
    private enum NodeType { ROLE, CONTENT, ACTION }
    
    private static class Edge {
        final SocialGraph target;
        final double defaultWeight;
        
        Edge(SocialGraph target, double defaultWeight) {
            this.target = target;
            this.defaultWeight = (int) (defaultWeight * 10); // 转换为整数权重
        }
    }
    
    private static class PathNode {
        final SocialGraph node;
        final List<SocialGraph> path;
        final int depth;
        
        PathNode(SocialGraph node, List<SocialGraph> path) {
            this(node, path, 0);
        }
        
        PathNode(SocialGraph node, List<SocialGraph> path, int depth) {
            this.node = node;
            this.path = path;
            this.depth = depth;
        }
    }
    
    // 简单Pair实现
    private static class Pair<K, V> {
        final K key;
        final V value;
        
        Pair(K key, V value) {
            this.key = key;
            this.value = value;
        }
        
        static <K, V> Pair<K, V> of(K key, V value) {
            return new Pair<>(key, value);
        }
        
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof Pair)) return false;
            Pair<?, ?> pair = (Pair<?, ?>) o;
            return Objects.equals(key, pair.key) && 
                   Objects.equals(value, pair.value);
        }
        
        @Override
        public int hashCode() {
            return Objects.hash(key, value);
        }
    }
    
    public String getDisplayName() { return displayName; }
    public NodeType getNodeType() { return type; }
    
    public static Set<SocialGraph> getAllRoles() {
        return Arrays.stream(values())
            .filter(node -> node.type == NodeType.ROLE)
            .collect(Collectors.toSet());
    }
    
    public static Set<SocialGraph> getAllActions() {
        return Arrays.stream(values())
            .filter(node -> node.type == NodeType.ACTION)
            .collect(Collectors.toSet());
    }
}
相关推荐
cike_y3 小时前
JSP内置对象及作用域&双亲委派机制
java·前端·网络安全·jsp·安全开发
也许是_3 小时前
大模型应用技术之 Spring AI 2.0 变更说明
java·人工智能·spring
xunyan62343 小时前
面向对象(下)-内部类的分类
java·学习
Insight.3 小时前
背包问题——01背包、完全背包、多重背包、分组背包(Python)
开发语言·python
巴拉巴拉~~3 小时前
KMP 算法通用进度条组件:KmpProgressWidget 多维度 + 匹配进度联动 + 平滑动画
java·服务器·前端
aini_lovee3 小时前
改进遗传算法求解VRP问题时的局部搜索能力
开发语言·算法·matlab
Yeniden4 小时前
Deepeek用大白话讲解 --> 迭代器模式(企业级场景1,多种遍历方式2,隐藏集合结构3,Java集合框架4)
java·开发语言·迭代器模式
SmoothSailingT4 小时前
C#——LINQ方法
开发语言·c#·linq
景川呀4 小时前
Java的类加载器
java·开发语言·java类加载器