You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

3.9 KiB

04 · 值对象与上下文按模块化:DataVisibility / DataScopeLevel / DataVisibilityContext

Type: grilling Status: resolved Blocked by: 01

Question

DataVisibility(现承载单一 level + 部门集合)、DataScopeLevelDataVisibilityContext (ThreadLocal 放一份 DataVisibility)如何改造成「按模块」的结构?

要定的点:

  • 上下文形态:ThreadLocal 从「一份 DataVisibility」变成「module → DataVisibility 的 map」? 还是「一份持有各模块档位的聚合对象」?
  • 部门集合、expandedDeptIds(子树展开)是否可跨模块共享(同一用户部门集合不随模块变,仅档位变)—— 这决定改造能否只在「档位」维度分模块、而部门集合仍算一次。
  • DataVisibility 的不可变/整体装入契约(ADR-0006「不存在设了一半」)如何在多模块下保持。
  • DataScopeLevel 枚举本身是否需要动(大概率不动,仅其"每模块一份")。
  • currentScope() / currentOwnership() 的签名如何变(读侧需按模块问 scope)。

依赖 01(模块标识)。与 05(拦截器)互相牵连,但结构决策在此先定。

Answer

方案 A:一个 DataVisibility 持有 Map<String, DataScopeLevel>

1. DataVisibility 结构变化

把单个 level 字段替换为 Map<String, DataScopeLevel> moduleLevels(key = module code,value = 档位)。 身份数据不变,只存一份:

public final class DataVisibility {
    private final Long userId;              // 不变
    private final Long primaryDeptId;       // 不变
    private final List<Long> deptIds;       // 不变
    private final List<Long> expandedDeptIds; // 不变
    private final Map<String, DataScopeLevel> moduleLevels; // 新:替代单个 level
}
  • Map.copyOf(moduleLevels) 封装,不可变。
  • 空构造校验:userId 必填;moduleLevels 必填但允许空 Map(空 = 所有模块走默认 SELF)。
  • expandedDeptIds 校验放宽:只要任一模块的 level 是 DEPT_AND_CHILDREN 就要求非 null(空 List 可以,表示一律不可见)。

2. 方法签名变化

// 读侧:按模块取档位 → 折算 VisibilityScope
public VisibilityScope visibilityScope(String moduleCode) {
    DataScopeLevel level = moduleLevels.getOrDefault(moduleCode, DataScopeLevel.SELF);
    return switch (level) {
        case SELF -> VisibilityScope.ownedBy(userId);
        case DEPT -> departmentsOrNone(deptIds);
        case DEPT_AND_CHILDREN -> departmentsOrNone(expandedDeptIds);
        case ALL -> VisibilityScope.allVisible();
    };
}

// 写侧:不变
public DataOwnership dataOwnership() { ... } // 完全不动

3. DataVisibilityContext 变化

ThreadLocal 仍持有一份 DataVisibility,但 currentScope() 加参数:

public static VisibilityScope currentScope(String moduleCode) {
    DataVisibility visibility = CURRENT.get();
    return visibility != null
        ? visibility.visibilityScope(moduleCode)
        : VisibilityScope.allVisible();
}

// currentOwnership() 完全不变
public static Optional<DataOwnership> currentOwnership() { ... }

4. 部门集合只算一次

deptIds / expandedDeptIds 是用户级的(主部门 + 兼职并集 + 子树展开),与模块无关。 PermissionResolver 算一次,存入 DataVisibility。各模块的档位决定用不用这组部门 ID,不需要重算

5. DataScopeLevel 枚举不动

四个值(SELF/DEPT/DEPT_AND_CHILDREN/ALL)不变,每模块各选一个。枚举本身无需改。

6. ADR-0006(不存在设了一半)保持

DataVisibility 仍不可变、构造成功即完整。Map 用 Map.copyOf 封装。空 Map 是合法状态(所有模块走 SELF)。不存在「设了一半」——要么整个上下文没有(无登录),要么整个上下文有(带完整的 moduleLevels map)。