Skip to content

Commit 418c1a9

Browse files
committed
refactor: 优化权限逻辑
1 parent d0a2822 commit 418c1a9

4 files changed

Lines changed: 224 additions & 26 deletions

File tree

hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/SimpleAuthentication.java

Lines changed: 136 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import lombok.Setter;
2222
import org.hswebframework.web.authorization.*;
2323

24+
import javax.annotation.concurrent.NotThreadSafe;
2425
import java.io.Serial;
2526
import java.io.Serializable;
2627
import java.util.*;
@@ -46,7 +47,6 @@ public class SimpleAuthentication implements Authentication {
4647

4748
private List<Dimension> dimensions = new ArrayList<>();
4849

49-
@Setter
5050
private Map<String, Serializable> attributes = new HashMap<>();
5151

5252
public static Authentication of() {
@@ -72,6 +72,26 @@ public Map<String, Serializable> getAttributes() {
7272
return attributes == null ? Collections.emptyMap() : attributes;
7373
}
7474

75+
public void setAttributes(Map<String, Serializable> attributes) {
76+
this.attributes = new HashMap<>(attributes);
77+
}
78+
79+
@Override
80+
public void setAttribute(String key, Serializable value) {
81+
if (key == null) {
82+
return;
83+
}
84+
if (this.attributes == null) {
85+
this.attributes = new HashMap<>();
86+
}
87+
this.attributes.put(key, value);
88+
}
89+
90+
public void putAttributes(Map<String, Serializable> attributes) {
91+
this.attributes = new HashMap<>(this.attributes);
92+
this.attributes.putAll(attributes);
93+
}
94+
7595
public SimpleAuthentication merge(Authentication authentication) {
7696
Map<String, Permission> mePermissionGroup = permissions
7797
.stream()
@@ -92,12 +112,16 @@ public SimpleAuthentication merge(Authentication authentication) {
92112
}
93113
me.getActions().addAll(permission.getActions());
94114
}
115+
//merge 保持 self 优先:仅当 self 不含该 (type,id) 时才并入 other 的维度
95116
this.dimensions = new ArrayList<>(this.getDimensions());
96117
for (Dimension dimension : authentication.getDimensions()) {
97-
if (getDimension(dimension.getType(), dimension.getId()).isEmpty()) {
98-
dimensions.add(dimension);
118+
if (!containsDimension(this.dimensions, dimension)) {
119+
this.dimensions.add(dimension);
99120
}
100121
}
122+
//重建了 permissions/dimensions 列表,失效索引缓存
123+
this.permissionMapping = null;
124+
this.dimensionMapping = null;
101125
return this;
102126
}
103127

@@ -128,23 +152,116 @@ public Authentication copy(BiPredicate<Permission, String> permissionFilter,
128152

129153
public void setUser(User user) {
130154
this.user = user;
131-
dimensions.add(user);
155+
addDimension(user);
132156
}
133157

134158
protected void setUser0(User user) {
135159
this.user = user;
136160
}
137161

162+
protected List<Dimension> newDimensions() {
163+
this.dimensions = new ArrayList<>();
164+
if (user != null) {
165+
this.dimensions.add(user);
166+
}
167+
return this.dimensions;
168+
}
169+
138170
public void setDimensions(List<Dimension> dimensions) {
139-
this.dimensions.addAll(dimensions);
171+
this.dimensions = distinctLastWins(newDimensions(), dimensions);
172+
this.dimensionMapping = null;
140173
}
141174

142175
public void setDimensions(Collection<Dimension> dimensions) {
143-
this.dimensions.addAll(dimensions);
176+
this.dimensions = distinctLastWins(newDimensions(), dimensions);
177+
this.dimensionMapping = null;
144178
}
145179

146180
public void addDimension(Dimension dimension) {
147-
this.dimensions.add(dimension);
181+
if (dimension != null) {
182+
List<Dimension> target = writableDimensions();
183+
int index = indexOfDimension(target, dimension);
184+
//已存在相同 (type,id) 则替换为最新,否则追加;线性查找避免分配索引结构
185+
if (index >= 0) {
186+
target.set(index, dimension);
187+
} else {
188+
target.add(dimension);
189+
}
190+
}
191+
this.dimensionMapping = null;
192+
}
193+
194+
public void cleanPermissions() {
195+
this.permissions = new ArrayList<>();
196+
this.permissionMapping = null;
197+
}
198+
199+
protected List<Dimension> writableDimensions() {
200+
return this.dimensions == null ? this.dimensions = new ArrayList<>() : this.dimensions;
201+
}
202+
203+
public void addDimensions(Collection<? extends Dimension> dimensions) {
204+
this.dimensions = distinctLastWins(getDimensions(), dimensions);
205+
this.dimensionMapping = null;
206+
}
207+
208+
/**
209+
* 以 seed 为基础并入 toAdd,按 (type,id) 去重;命中相同标识时<b>后者覆盖前者(替换为最新)</b>,
210+
* 并保持该标识首次出现的位置.
211+
*/
212+
private static List<Dimension> distinctLastWins(List<Dimension> seed,
213+
Collection<? extends Dimension> toAdd) {
214+
//结构化 (type,id) -> 维度,LinkedHashMap 保位置、put 覆盖实现 last-wins
215+
LinkedHashMap<List<String>, Dimension> merged = new LinkedHashMap<>();
216+
for (Dimension dimension : seed) {
217+
merged.put(dimensionKey(dimension), dimension);
218+
}
219+
if (toAdd != null) {
220+
for (Dimension dimension : toAdd) {
221+
if (dimension != null) {
222+
merged.put(dimensionKey(dimension), dimension);
223+
}
224+
}
225+
}
226+
return new ArrayList<>(merged.values());
227+
}
228+
229+
//结构化 (type,id) key,避免拼接字符串带来的分隔符歧义/碰撞
230+
private static List<String> dimensionKey(Dimension dimension) {
231+
DimensionType type = dimension.getType();
232+
return Arrays.asList(type == null ? null : type.getId(), dimension.getId());
233+
}
234+
235+
//merge 中判断 self 是否已含相同 (type,id);零分配线性查找
236+
private static boolean containsDimension(List<Dimension> dimensions, Dimension dimension) {
237+
return indexOfDimension(dimensions, dimension) >= 0;
238+
}
239+
240+
//线性查找相同 (type,id) 的下标,无则 -1;零分配,用于单个 add 的就地替换
241+
private static int indexOfDimension(List<Dimension> dimensions, Dimension dimension) {
242+
DimensionType type = dimension.getType();
243+
String typeId = type == null ? null : type.getId();
244+
String id = dimension.getId();
245+
for (int i = 0; i < dimensions.size(); i++) {
246+
Dimension exist = dimensions.get(i);
247+
DimensionType existType = exist.getType();
248+
if (Objects.equals(id, exist.getId())
249+
&& Objects.equals(typeId, existType == null ? null : existType.getId())) {
250+
return i;
251+
}
252+
}
253+
return -1;
254+
}
255+
256+
public void addPermissions(Collection<? extends Permission> permissions) {
257+
this.permissions = new ArrayList<>(getPermissions());
258+
this.permissions.addAll(permissions);
259+
this.permissionMapping = null;
260+
}
261+
262+
public void addPermission(Permission permission) {
263+
this.permissions.add(permission);
264+
this.permissionMapping = null;
148265
}
149266

150267
private transient volatile Map<String, Map<String, Dimension>> dimensionMapping;
@@ -158,21 +275,21 @@ protected boolean fastPath() {
158275
permissionMapping = permissions == null
159276
? Collections.emptyMap()
160277
: permissions
161-
.stream()
162-
.collect(Collectors
163-
.toMap(Permission::getId,
164-
Function.identity(),
165-
(a, b) -> b));
278+
.stream()
279+
.collect(Collectors
280+
.toMap(Permission::getId,
281+
Function.identity(),
282+
(a, b) -> b));
166283
dimensionMapping = dimensions == null
167284
? Collections.emptyMap()
168285
: dimensions
169-
.stream()
170-
.collect(Collectors
171-
.groupingBy(d -> d.getType().getId(),
172-
Collectors.toMap(
173-
Dimension::getId,
174-
Function.identity(),
175-
(a, b) -> a)));
286+
.stream()
287+
.collect(Collectors
288+
.groupingBy(d -> d.getType().getId(),
289+
Collectors.toMap(
290+
Dimension::getId,
291+
Function.identity(),
292+
(a, b) -> a)));
176293
}
177294
}
178295
return permissionMapping != null;

hsweb-authorization/hsweb-authorization-api/src/main/java/org/hswebframework/web/authorization/simple/builder/SimpleAuthenticationBuilder.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public AuthenticationBuilder user(Map<String, String> user) {
5555

5656
@Override
5757
public AuthenticationBuilder role(List<Role> role) {
58-
authentication.getDimensions().addAll(role);
58+
authentication.addDimensions(role);
5959
return this;
6060
}
6161

@@ -67,7 +67,7 @@ public AuthenticationBuilder role(String role) {
6767

6868
@Override
6969
public AuthenticationBuilder permission(List<Permission> permission) {
70-
authentication.setPermissions(permission);
70+
authentication.addPermissions(permission);
7171
return this;
7272
}
7373

@@ -95,7 +95,7 @@ public AuthenticationBuilder permission(JSONArray jsonArray) {
9595
}
9696
permissions.add(permission);
9797
}
98-
authentication.setPermissions(permissions);
98+
authentication.addPermissions(permissions);
9999
return this;
100100
}
101101

@@ -106,13 +106,13 @@ public AuthenticationBuilder permission(String permissionJson) {
106106

107107
@Override
108108
public AuthenticationBuilder attributes(String attributes) {
109-
authentication.getAttributes().putAll(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
109+
authentication.putAttributes(JSON.<Map<String, Serializable>>parseObject(attributes, Map.class));
110110
return this;
111111
}
112112

113113
@Override
114114
public AuthenticationBuilder attributes(Map<String, Serializable> permission) {
115-
authentication.getAttributes().putAll(permission);
115+
authentication.putAttributes(permission);
116116
return this;
117117
}
118118

@@ -137,7 +137,7 @@ public AuthenticationBuilder dimension(JSONArray json) {
137137
options
138138
));
139139
}
140-
authentication.setDimensions(dimensions);
140+
authentication.addDimensions(dimensions);
141141

142142
return this;
143143

hsweb-authorization/hsweb-authorization-api/src/test/java/org/hswebframework/web/authorization/simple/SimpleAuthenticationTest.java

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,87 @@ void testUserAsDimension() {
550550
assertTrue(userDimension.isPresent());
551551
}
552552

553+
@Test
554+
void testSetDimensionsDeduplicatesUser() {
555+
authentication.setUser(user);
556+
// setDimensions 再次带入 user(及角色)时,user 维度不应被重复添加
557+
authentication.setDimensions(Arrays.asList(user, dimension2));
558+
559+
long userCount = authentication
560+
.getDimensions()
561+
.stream()
562+
.filter(d -> d.getId().equals(user.getId())
563+
&& d.getType().getId().equals(DefaultDimensionType.user.getId()))
564+
.count();
565+
assertEquals(1, userCount, "user 维度不应被重复添加");
566+
assertEquals(2, authentication.getDimensions().size());
567+
}
568+
569+
@Test
570+
void testAddDimensionReplacesExisting() {
571+
authentication.addDimension(SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null));
572+
// 相同 (type,id) 再次 add => 替换为最新(last-wins),不重复
573+
authentication.addDimension(SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null));
574+
575+
assertEquals(1, authentication.getDimensions().size());
576+
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
577+
}
578+
579+
@Test
580+
void testSetDimensionsLastWins() {
581+
// 列表内相同 (type,id),后者覆盖前者
582+
authentication.setDimensions(Arrays.asList(
583+
SimpleDimension.of("org-1", "OLD", SimpleDimensionType.of("org"), null),
584+
SimpleDimension.of("org-1", "NEW", SimpleDimensionType.of("org"), null)));
585+
586+
assertEquals(1, authentication.getDimensions().size());
587+
assertEquals("NEW", authentication.getDimension("org", "org-1").get().getName());
588+
}
589+
590+
@Test
591+
void testMergeKeepsSelfOnDuplicate() {
592+
// merge 保持 self 优先: 相同 (type,id) 时 self 的值不被 other 覆盖
593+
authentication.setDimensions(Collections.singletonList(
594+
SimpleDimension.of("org-1", "SELF", SimpleDimensionType.of("org"), null)));
595+
SimpleAuthentication other = new SimpleAuthentication();
596+
other.setDimensions(Collections.singletonList(
597+
SimpleDimension.of("org-1", "OTHER", SimpleDimensionType.of("org"), null)));
598+
599+
authentication.merge(other);
600+
601+
assertEquals(1, authentication.getDimensions().size());
602+
assertEquals("SELF", authentication.getDimension("org", "org-1").get().getName());
603+
}
604+
605+
@Test
606+
void testAddDimensionsDeduplicates() {
607+
authentication.addDimension(dimension1);
608+
authentication.addDimensions(Arrays.asList(dimension1, dimension2));
609+
610+
assertEquals(2, authentication.getDimensions().size());
611+
assertTrue(authentication.hasDimension("org", "org-1"));
612+
assertTrue(authentication.hasDimension("role", "role-1"));
613+
}
614+
615+
@Test
616+
void testMergeInvalidatesFastPathCache() {
617+
authentication.setUser(user);
618+
authentication.setDimensions(Collections.singletonList(dimension1));
619+
620+
// 触发 fastPath 缓存构建(第8次访问)
621+
for (int i = 0; i < 8; i++) {
622+
authentication.getDimension("org", "org-1");
623+
}
624+
625+
SimpleAuthentication other = new SimpleAuthentication();
626+
other.setDimensions(Collections.singletonList(dimension2));
627+
authentication.merge(other);
628+
629+
// merge 重建了 dimensions/permissions 列表,缓存须失效,新维度应可被查询到
630+
assertTrue(authentication.getDimension("role", "role-1").isPresent(),
631+
"merge 后新维度应可查询(fastPath 缓存已失效)");
632+
}
633+
553634
// ========== 性能测试 ==========
554635

555636
@Test

hsweb-authorization/hsweb-authorization-basic/src/main/java/org/hswebframework/web/authorization/basic/embed/EmbedAuthenticationInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public Authentication toAuthentication(DataAccessConfigBuilderFactory factory) {
7878
user.setUsername(username);
7979
user.setUserType(type);
8080
authentication.setUser(user);
81-
authentication.getDimensions().addAll(roles);
81+
authentication.addDimensions(roles);
8282
List<Permission> permissionList = new ArrayList<>();
8383

8484
permissionList.addAll(permissions.stream()

0 commit comments

Comments
 (0)