refactor: 优化权限逻辑

This commit is contained in:
zhou-hao
2026-06-25 19:12:45 +08:00
parent d0a2822316
commit 418c1a94b2
4 changed files with 224 additions and 26 deletions

View File

@@ -21,6 +21,7 @@ import lombok.Getter;
import lombok.Setter;
import org.hswebframework.web.authorization.*;
import javax.annotation.concurrent.NotThreadSafe;
import java.io.Serial;
import java.io.Serializable;
import java.util.*;
@@ -46,7 +47,6 @@ public class SimpleAuthentication implements Authentication {
private List<Dimension> dimensions = new ArrayList<>();
@Setter
private Map<String, Serializable> attributes = new HashMap<>();
public static Authentication of() {
@@ -72,6 +72,26 @@ public class SimpleAuthentication implements Authentication {
return attributes == null ? Collections.emptyMap() : attributes;
}
public void setAttributes(Map<String, Serializable> attributes) {
this.attributes = new HashMap<>(attributes);
}
@Override
public void setAttribute(String key, Serializable value) {
if (key == null) {
return;
}
if (this.attributes == null) {
this.attributes = new HashMap<>();
}
this.attributes.put(key, value);
}
public void putAttributes(Map<String, Serializable> attributes) {
this.attributes = new HashMap<>(this.attributes);
this.attributes.putAll(attributes);
}
public SimpleAuthentication merge(Authentication authentication) {
Map<String, Permission> mePermissionGroup = permissions
.stream()
@@ -92,12 +112,16 @@ public class SimpleAuthentication implements Authentication {
}
me.getActions().addAll(permission.getActions());
}
//merge 保持 self 优先:仅当 self 不含该 (type,id) 时才并入 other 的维度
this.dimensions = new ArrayList<>(this.getDimensions());
for (Dimension dimension : authentication.getDimensions()) {
if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) {
dimensions.add(dimension);
if (!containsDimension(this.dimensions, dimension)) {
this.dimensions.add(dimension);
}
}
//重建了 permissions/dimensions 列表,失效索引缓存
this.permissionMapping = null;
this.dimensionMapping = null;
return this;
}
@@ -128,23 +152,116 @@ public class SimpleAuthentication implements Authentication {
public void setUser(User user) {
this.user = user;
dimensions.add(user);
addDimension(user);
}
protected void setUser0(User user) {
this.user = user;
}
protected List<Dimension> newDimensions() {
this.dimensions = new ArrayList<>();
if (user != null) {
this.dimensions.add(user);
}
return this.dimensions;
}
public void setDimensions(List<Dimension> dimensions) {
this.dimensions.addAll(dimensions);
this.dimensions = distinctLastWins(newDimensions(), dimensions);
this.dimensionMapping = null;
}
public void setDimensions(Collection<Dimension> dimensions) {
this.dimensions.addAll(dimensions);
this.dimensions = distinctLastWins(newDimensions(), dimensions);
this.dimensionMapping = null;
}
public void addDimension(Dimension dimension) {
this.dimensions.add(dimension);
if (dimension != null) {
List<Dimension> target = writableDimensions();
int index = indexOfDimension(target, dimension);
//已存在相同 (type,id) 则替换为最新,否则追加;线性查找避免分配索引结构
if (index >= 0) {
target.set(index, dimension);
} else {
target.add(dimension);
}
}
this.dimensionMapping = null;
}
public void cleanPermissions() {
this.permissions = new ArrayList<>();
this.permissionMapping = null;
}
protected List<Dimension> writableDimensions() {
return this.dimensions == null ? this.dimensions = new ArrayList<>() : this.dimensions;
}
public void addDimensions(Collection<? extends Dimension> dimensions) {
this.dimensions = distinctLastWins(getDimensions(), dimensions);
this.dimensionMapping = null;
}
/**
* 以 seed 为基础并入 toAdd,按 (type,id) 去重;命中相同标识时<b>后者覆盖前者(替换为最新)</b>,
* 并保持该标识首次出现的位置.
*/
private static List<Dimension> distinctLastWins(List<Dimension> seed,
Collection<? extends Dimension> toAdd) {
//结构化 (type,id) -> 维度,LinkedHashMap 保位置、put 覆盖实现 last-wins
LinkedHashMap<List<String>, Dimension> merged = new LinkedHashMap<>();
for (Dimension dimension : seed) {
merged.put(dimensionKey(dimension), dimension);
}
if (toAdd != null) {
for (Dimension dimension : toAdd) {
if (dimension != null) {
merged.put(dimensionKey(dimension), dimension);
}
}
}
return new ArrayList<>(merged.values());
}
//结构化 (type,id) key,避免拼接字符串带来的分隔符歧义/碰撞
private static List<String> dimensionKey(Dimension dimension) {
DimensionType type = dimension.getType();
return Arrays.asList(type == null ? null : type.getId(), dimension.getId());
}
//merge 中判断 self 是否已含相同 (type,id);零分配线性查找
private static boolean containsDimension(List<Dimension> dimensions, Dimension dimension) {
return indexOfDimension(dimensions, dimension) >= 0;
}
//线性查找相同 (type,id) 的下标,无则 -1;零分配,用于单个 add 的就地替换
private static int indexOfDimension(List<Dimension> dimensions, Dimension dimension) {
DimensionType type = dimension.getType();
String typeId = type == null ? null : type.getId();
String id = dimension.getId();
for (int i = 0; i < dimensions.size(); i++) {
Dimension exist = dimensions.get(i);
DimensionType existType = exist.getType();
if (Objects.equals(id, exist.getId())
&& Objects.equals(typeId, existType == null ? null : existType.getId())) {
return i;
}
}
return -1;
}
public void addPermissions(Collection<? extends Permission> permissions) {
this.permissions = new ArrayList<>(getPermissions());
this.permissions.addAll(permissions);
this.permissionMapping = null;
}
public void addPermission(Permission permission) {
this.permissions.add(permission);
this.permissionMapping = null;
}
private transient volatile Map<String, Map<String, Dimension>> dimensionMapping;
@@ -158,21 +275,21 @@ public class SimpleAuthentication implements Authentication {
permissionMapping = permissions == null
? Collections.emptyMap()
: permissions
.stream()
.collect(Collectors
.toMap(Permission::getId,
Function.identity(),
(a, b) -> b));
.stream()
.collect(Collectors
.toMap(Permission::getId,
Function.identity(),
(a, b) -> b));
dimensionMapping = dimensions == null
? Collections.emptyMap()
: dimensions
.stream()
.collect(Collectors
.groupingBy(d -> d.getType().getId(),
Collectors.toMap(
Dimension::getId,
Function.identity(),
(a, b) -> a)));
.stream()
.collect(Collectors
.groupingBy(d -> d.getType().getId(),
Collectors.toMap(
Dimension::getId,
Function.identity(),
(a, b) -> a)));
}
}
return permissionMapping != null;

View File

@@ -55,7 +55,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
@Override
public AuthenticationBuilder role(List<Role> role) {
authentication.getDimensions().addAll(role);
authentication.addDimensions(role);
return this;
}
@@ -67,7 +67,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
@Override
public AuthenticationBuilder permission(List<Permission> permission) {
authentication.setPermissions(permission);
authentication.addPermissions(permission);
return this;
}
@@ -95,7 +95,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
}
permissions.add(permission);
}
authentication.setPermissions(permissions);
authentication.addPermissions(permissions);
return this;
}
@@ -106,13 +106,13 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
@Override
public AuthenticationBuilder attributes(String attributes) {
authentication.getAttributes().putAll(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
authentication.putAttributes(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
return this;
}
@Override
public AuthenticationBuilder attributes(Map<String, Serializable> permission) {
authentication.getAttributes().putAll(permission);
authentication.putAttributes(permission);
return this;
}
@@ -137,7 +137,7 @@ public class SimpleAuthenticationBuilder implements AuthenticationBuilder {
options
));
}
authentication.setDimensions(dimensions);
authentication.addDimensions(dimensions);
return this;

View File

@@ -550,6 +550,87 @@ class SimpleAuthenticationTest {
assertTrue(userDimension.isPresent());
}
@Test
void testSetDimensionsDeduplicatesUser() {
authentication.setUser(user);
// setDimensions 再次带入 user(及角色)时,user 维度不应被重复添加
authentication.setDimensions(Arrays.asList(user, dimension2));
long userCount = authentication
.getDimensions()
.stream()
.filter(d -> d.getId().equals(user.getId())
&& d.getType().getId().equals(DefaultDimensionType.user.getId()))
.count();
assertEquals(1, userCount, "user 维度不应被重复添加");
assertEquals(2, authentication.getDimensions().size());
}
@Test
void testAddDimensionReplacesExisting() {
authentication.addDimension(SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null));
// 相同 (type,id) 再次 add => 替换为最新(last-wins),不重复
authentication.addDimension(SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null));
assertEquals(1, authentication.getDimensions().size());
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
}
@Test
void testSetDimensionsLastWins() {
// 列表内相同 (type,id),后者覆盖前者
authentication.setDimensions(Arrays.asList(
SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null),
SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null)));
assertEquals(1, authentication.getDimensions().size());
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
}
@Test
void testMergeKeepsSelfOnDuplicate() {
// merge 保持 self 优先: 相同 (type,id) 时 self 的值不被 other 覆盖
authentication.setDimensions(Collections.singletonList(
SimpleDimension.of("org-1", "SELF", SimpleDimensionType.of("org"), null)));
SimpleAuthentication other = new SimpleAuthentication();
other.setDimensions(Collections.singletonList(
SimpleDimension.of("org-1", "OTHER", SimpleDimensionType.of("org"), null)));
authentication.merge(other);
assertEquals(1, authentication.getDimensions().size());
assertEquals("SELF", authentication.getDimension("org", "org-1").get().getName());
}
@Test
void testAddDimensionsDeduplicates() {
authentication.addDimension(dimension1);
authentication.addDimensions(Arrays.asList(dimension1, dimension2));
assertEquals(2, authentication.getDimensions().size());
assertTrue(authentication.hasDimension("org", "org-1"));
assertTrue(authentication.hasDimension("role", "role-1"));
}
@Test
void testMergeInvalidatesFastPathCache() {
authentication.setUser(user);
authentication.setDimensions(Collections.singletonList(dimension1));
// 触发 fastPath 缓存构建(第8次访问)
for (int i = 0; i < 8; i++) {
authentication.getDimension("org", "org-1");
}
SimpleAuthentication other = new SimpleAuthentication();
other.setDimensions(Collections.singletonList(dimension2));
authentication.merge(other);
// merge 重建了 dimensions/permissions 列表,缓存须失效,新维度应可被查询到
assertTrue(authentication.getDimension("role", "role-1").isPresent(),
"merge 后新维度应可查询(fastPath 缓存已失效)");
}
// ========== 性能测试 ==========
@Test

View File

@@ -78,7 +78,7 @@ public class EmbedAuthenticationInfo {
user.setUsername(username);
user.setUserType(type);
authentication.setUser(user);
authentication.getDimensions().addAll(roles);
authentication.addDimensions(roles);
List<Permission> permissionList = new ArrayList<>();
permissionList.addAll(permissions.stream()