Java

基于策略模式优化 Java 系统角色相关功能的方案文档

2026-06-26 #Springboot#Java#笔记#随手记

基于策略模式优化 Java 系统角色相关功能的方案文档

问题:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@ApiOperation("登录")
@AuditLogRecord(action = "登录", resource = "用户")
@PostMapping("/login")
public R login(@RequestBody Account account) {
Account dbAccount = null;
if ("ADMIN".equals(account.getRole())) {
dbAccount = adminServiceImpl.login(account);
}else if ("USER".equals(account.getRole())) {
dbAccount = userServiceImpl.login(account);
}else {
throw new CustomerException("非法请求");
}
return R.success(dbAccount);
}

@PostMapping("/register")
@AuditLogRecord(action = "用户注册", resource = "用户")
@ApiOperation("用户注册")
public R register(@RequestBody User user) {
userServiceImpl.register(user);
return R.ok();
}

@ApiOperation("更新密码")
@PostMapping("/updatePassword")
@AuditLogRecord(action = "更新密码", resource = "用户")
public R UpdatePassword(@RequestBody Account account) {
if("ADMIN".equals(account.getRole())){
adminServiceImpl.updatePassword(account);
}
if("USER".equals(account.getRole())){
userServiceImpl.updatePassword(account);
}


return R.ok();
}
  • 这里有两个方法 而且我有一个
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public enum RoleEnum {
ADMIN("ADMIN", "管理员"),
STUDENT("STUDENT", "学生"),
USER("USER", "用户");

private final String code;
private final String label;

RoleEnum(String code, String label) {
this.code = code;
this.label = label;
}

public static RoleEnum fromCode(String code) {
for (RoleEnum value : RoleEnum.values()) {
if (value.getCode().equalsIgnoreCase(code)) {
return value;
}
}
throw new IllegalArgumentException("无效角色标识: " + code);
}
}
  • 这个东西 那两个方法是写死的校验 这好像不太对违反了一些开发原则 请帮我修复一下

回答:

你说得非常对。你当前 login()updatePassword() 方法中的 if-else 写死了角色判断,直接调用具体的 adminServiceImpl / userServiceImpl,这确实违反了以下几个核心开发原则:


🚨 存在的问题

🔸 违反开闭原则(OCP)

  • 如果将来你新增了一个 TEACHER 角色,你需要修改原有方法逻辑,这违反了“对扩展开放,对修改封闭”。

🔸 违反依赖倒置原则(DIP)

  • Controller 直接依赖具体实现(adminServiceImpl / userServiceImpl),而不是抽象接口。

🔸 不利于扩展与维护

  • 每个角色的逻辑硬编码在控制器中,重复且耦合高。

✅ 改造方案:策略模式 + Spring 注解 + 抽象接口

我们可以使用策略模式封装每种角色的登录/修改密码行为,通过 Spring 自动注入的方式进行解耦。整体目标是:

  1. Controller 只调用通用接口,不关心具体角色逻辑;
  2. 每个角色的登录/更新密码逻辑交由具体策略类实现;
  3. 可扩展性强,新增角色只需实现接口并注入即可。

✅ 一、定义统一的接口

1
2
3
4
5
6
7
public interface RoleStrategy {
String getRole(); // 返回角色标识,如 ADMIN、USER、TEACHER

Account login(Account account);

void updatePassword(Account account);
}

✅ 二、实现每个角色的策略类

Admin 策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class AdminStrategy implements RoleStrategy {

@Autowired
private AdminServiceImpl adminService;

@Override
public String getRole() {
return RoleEnum.ADMIN.getCode(); // "ADMIN"
}

@Override
public Account login(Account account) {
return adminService.login(account);
}

@Override
public void updatePassword(Account account) {
adminService.updatePassword(account);
}
}

User 策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class UserStrategy implements RoleStrategy {

@Autowired
private UserServiceImpl userService;

@Override
public String getRole() {
return RoleEnum.USER.getCode(); // "USER"
}

@Override
public Account login(Account account) {
return userService.login(account);
}

@Override
public void updatePassword(Account account) {
userService.updatePassword(account);
}
}

✅ 三、统一封装策略上下文(自动路由角色)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Component
public class RoleStrategyContext {

private final Map<String, RoleStrategy> strategyMap = new HashMap<>();

@Autowired
public RoleStrategyContext(List<RoleStrategy> strategies) {
for (RoleStrategy strategy : strategies) {
strategyMap.put(strategy.getRole().toUpperCase(), strategy);
}
}

public RoleStrategy getStrategy(String role) {
RoleEnum.fromCode(role); // 验证合法性
RoleStrategy strategy = strategyMap.get(role.toUpperCase());
if (strategy == null) {
throw new CustomerException("未实现该角色逻辑: " + role);
}
return strategy;
}
}

✅ 四、修改 Controller(简洁可扩展)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@ApiOperation("登录")
@AuditLogRecord(action = "登录", resource = "用户")
@PostMapping("/login")
public R login(@RequestBody Account account) {
Account result = roleStrategyContext.getStrategy(account.getRole()).login(account);
return R.success(result);
}

@ApiOperation("更新密码")
@PostMapping("/updatePassword")
@AuditLogRecord(action = "更新密码", resource = "用户")
public R UpdatePassword(@RequestBody Account account) {
roleStrategyContext.getStrategy(account.getRole()).updatePassword(account);
return R.ok();
}

✅ 五、确保角色枚举强校验

你已经有如下方法:

1
2
3
4
5
6
7
8
public static RoleEnum fromCode(String code) {
for (RoleEnum value : RoleEnum.values()) {
if (value.getCode().equalsIgnoreCase(code)) {
return value;
}
}
throw new IllegalArgumentException("无效角色标识: " + code);
}

很好。可在 RoleStrategyContext.getStrategy() 中调用它进行角色存在性验证。


✅ 最终优点总结

优化项 原来的写法 改造后的方式
解耦性 Controller 依赖具体实现类 只依赖通用接口 RoleStrategy
可扩展性 添加角色需修改控制器 添加角色只需新建策略类
维护成本 多个 if/else 分支 无需判断,自动路由角色逻辑
代码质量 违反开闭原则、重复判断 满足 OCP,结构清晰、职责单一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
com.zapco.securitylogin
├── controller // Controller 层,对外暴露接口
│ └── LoginController.java

├── service // 业务层接口(如 UserService、AdminService)
│ ├── AccountService.java
│ └── impl
│ ├── AdminServiceImpl.java
│ └── UserServiceImpl.java

├── strategy // 策略模式相关类(新建)
│ ├── RoleStrategy.java // 抽象接口
│ ├── context
│ │ └── RoleStrategyContext.java // 策略路由器
│ └── impl
│ ├── AdminStrategy.java // 管理员策略实现
│ ├── UserStrategy.java // 普通用户策略实现
│ └── TeacherStrategy.java // 教师策略实现(可选)

├── annotation // 注解相关
│ └── AuditLogRecord.java

├── aspect // AOP切面相关
│ └── AuditLogAspect.java

├── model // 数据模型(DTO/VO/PO)
│ ├── Account.java
│ └── enums
│ └── RoleEnum.java // 角色枚举

├── config // 配置类、Bean、拦截器
│ └── SwaggerConfig.java

├── exception // 自定义异常类
│ └── CustomerException.java

包名 作用说明
controller 接收前端请求,作为系统的接口适配层。不得包含任何具体业务逻辑
service + impl 服务抽象层 + 实现层。建议维持你已有结构,对应实体业务边界
strategy 专用于抽象策略接口及实现类,防止散落在 impl 目录中引起耦合
strategy.context 把上下文(策略工厂、分发器)封装在独立子包,层次清晰
strategy.impl 每个角色的实现类集中管理,便于维护扩展
annotation 所有自定义注解统一管理
aspect 所有基于注解的 AOP 切面放在这里
model.enums 用于放置角色、状态码、业务类型等通用枚举
exception 自定义异常封装层,统一异常处理策略

完整角色策略模式模块代码清单,每个类都明确标注了包名,并按照之前推荐的结构组织:


✅ 1. 抽象策略接口

路径com.zapco.securitylogin.strategy.RoleStrategy

1
2
3
4
5
6
7
8
9
10
11
package com.zapco.securitylogin.strategy;

import com.zapco.securitylogin.model.Account;

public interface RoleStrategy {
String getRole(); // 返回角色标识:ADMIN / USER / TEACHER

Account login(Account account);

void updatePassword(Account account);
}

✅ 2. 角色策略实现类

管理员策略实现

路径com.zapco.securitylogin.strategy.impl.AdminStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.zapco.securitylogin.strategy.impl;

import com.zapco.securitylogin.model.Account;
import com.zapco.securitylogin.service.impl.AdminServiceImpl;
import com.zapco.securitylogin.strategy.RoleStrategy;
import com.zapco.securitylogin.model.enums.RoleEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class AdminStrategy implements RoleStrategy {

@Autowired
private AdminServiceImpl adminService;

@Override
public String getRole() {
return RoleEnum.ADMIN.getCode(); // "ADMIN"
}

@Override
public Account login(Account account) {
return adminService.login(account);
}

@Override
public void updatePassword(Account account) {
adminService.updatePassword(account);
}
}

普通用户策略实现

路径com.zapco.securitylogin.strategy.impl.UserStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.zapco.securitylogin.strategy.impl;

import com.zapco.securitylogin.model.Account;
import com.zapco.securitylogin.service.impl.UserServiceImpl;
import com.zapco.securitylogin.strategy.RoleStrategy;
import com.zapco.securitylogin.model.enums.RoleEnum;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class UserStrategy implements RoleStrategy {

@Autowired
private UserServiceImpl userService;

@Override
public String getRole() {
return RoleEnum.USER.getCode(); // "USER"
}

@Override
public Account login(Account account) {
return userService.login(account);
}

@Override
public void updatePassword(Account account) {
userService.updatePassword(account);
}
}

✅ 3. 策略上下文容器

路径com.zapco.securitylogin.strategy.context.RoleStrategyContext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package com.zapco.securitylogin.strategy.context;

import com.zapco.securitylogin.strategy.RoleStrategy;
import com.zapco.securitylogin.model.enums.RoleEnum;
import com.zapco.securitylogin.exception.CustomerException;
import org.springframework.stereotype.Component;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Component
public class RoleStrategyContext {

private final Map<String, RoleStrategy> strategyMap = new HashMap<>();

public RoleStrategyContext(List<RoleStrategy> strategies) {
for (RoleStrategy strategy : strategies) {
strategyMap.put(strategy.getRole().toUpperCase(), strategy);
}
}

public RoleStrategy getStrategy(String roleCode) {
RoleEnum.fromCode(roleCode); // 强校验
RoleStrategy strategy = strategyMap.get(roleCode.toUpperCase());
if (strategy == null) {
throw new CustomerException("暂未支持的角色类型: " + roleCode);
}
return strategy;
}
}

✅ 4. 修改后的 Controller

路径com.zapco.securitylogin.controller.LoginController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package com.zapco.securitylogin.controller;

import com.zapco.securitylogin.model.Account;
import com.zapco.securitylogin.strategy.context.RoleStrategyContext;
import com.zapco.securitylogin.annotation.AuditLogRecord;
import com.zapco.securitylogin.common.R;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class LoginController {

@Autowired
private RoleStrategyContext roleStrategyContext;

@ApiOperation("登录")
@AuditLogRecord(action = "登录", resource = "用户")
@PostMapping("/login")
public R login(@RequestBody Account account) {
return R.success(roleStrategyContext.getStrategy(account.getRole()).login(account));
}

@ApiOperation("用户注册")
@AuditLogRecord(action = "用户注册", resource = "用户")
@PostMapping("/register")
public R register(@RequestBody Account account) {
// 注册逻辑保持原有 UserServiceImpl
// 这里可以扩展成策略注册
throw new UnsupportedOperationException("暂未实现注册策略分发");
}

@ApiOperation("更新密码")
@AuditLogRecord(action = "更新密码", resource = "用户")
@PostMapping("/updatePassword")
public R updatePassword(@RequestBody Account account) {
roleStrategyContext.getStrategy(account.getRole()).updatePassword(account);
return R.ok();
}
}

✅ 5. 角色枚举(已有)

路径com.zapco.securitylogin.model.enums.RoleEnum

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.zapco.securitylogin.model.enums;

import lombok.Getter;

@Getter
public enum RoleEnum {
ADMIN("ADMIN", "管理员"),
STUDENT("STUDENT", "学生"),
USER("USER", "用户");

private final String code;
private final String label;

RoleEnum(String code, String label) {
this.code = code;
this.label = label;
}

public static RoleEnum fromCode(String code) {
for (RoleEnum value : RoleEnum.values()) {
if (value.getCode().equalsIgnoreCase(code)) {
return value;
}
}
throw new IllegalArgumentException("无效角色标识: " + code);
}
}

✅ 6. 自定义异常(若未定义)

路径com.zapco.securitylogin.exception.CustomerException

1
2
3
4
5
6
7
package com.zapco.securitylogin.exception;

public class CustomerException extends RuntimeException {
public CustomerException(String message) {
super(message);
}
}

✅ 整体依赖关系图(简化)

1
2
3
[Controller] → [RoleStrategyContext] → [RoleStrategy] ← [AdminStrategy | UserStrategy]
↑ ↑ ↑
Account参数 AdminServiceImpl / UserServiceImpl

你问得非常专业:如果我要“新增或修改角色”,该怎么做?需要改哪里?

这是使用策略模式最核心的价值——只需“新增一个类”+“配置角色 code”,不用动 Controller、已有逻辑不受影响,接下来我将以最专业的方式逐步解释和指导你如何操作。


✅ 一句话总结:新增角色 = 实现一个策略类 + 注册到上下文(自动完成)

假设你要新增一个角色 TEACHER,操作步骤如下:


✅ 步骤 1:在 RoleEnum 中新增角色标识

路径:com.zapco.securitylogin.model.enums.RoleEnum

1
TEACHER("TEACHER", "老师"),

完整示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Getter
public enum RoleEnum {
ADMIN("ADMIN", "管理员"),
STUDENT("STUDENT", "学生"),
USER("USER", "用户"),
TEACHER("TEACHER", "老师"); // ✅ 新增

private final String code;
private final String label;

RoleEnum(String code, String label) {
this.code = code;
this.label = label;
}

public static RoleEnum fromCode(String code) {
for (RoleEnum value : values()) {
if (value.getCode().equalsIgnoreCase(code)) {
return value;
}
}
throw new IllegalArgumentException("无效角色标识: " + code);
}
}

✅ 步骤 2:新增一个角色策略类(实现 RoleStrategy 接口)

路径建议:com.zapco.securitylogin.strategy.impl.TeacherStrategy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.zapco.securitylogin.strategy.impl;

import com.zapco.securitylogin.model.Account;
import com.zapco.securitylogin.model.enums.RoleEnum;
import com.zapco.securitylogin.service.impl.TeacherServiceImpl;
import com.zapco.securitylogin.strategy.RoleStrategy;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Component
public class TeacherStrategy implements RoleStrategy {

@Autowired
private TeacherServiceImpl teacherService;

@Override
public String getRole() {
return RoleEnum.TEACHER.getCode(); // "TEACHER"
}

@Override
public Account login(Account account) {
return teacherService.login(account);
}

@Override
public void updatePassword(Account account) {
teacherService.updatePassword(account);
}
}

注意事项:你的 TeacherServiceImpl 应提前定义好,如没有,可复制 UserServiceImpl 逻辑结构快速创建。


✅ 步骤 3:Controller 无需改动,系统自动识别策略

这是策略模式的核心优势:

  • 控制器统一调用 roleStrategyContext.getStrategy(role).xxx()
  • 新增的 TeacherStrategy 会被 Spring 自动注入到 List<RoleStrategy>
  • RoleStrategyContext 会自动收录并缓存它。

所以,Controller 不动,现有逻辑不动,功能自动支持 TEACHER 登录/改密码。


✅ 可选(步骤 4):如果有注册功能也需要支持角色差异

你可以将“注册”操作也抽象进策略接口:

修改 RoleStrategy 接口:

1
2
3
default void register(Account account) {
throw new UnsupportedOperationException("该角色不支持注册");
}

然后 Controller 中写:

1
2
3
4
5
@PostMapping("/register")
public R register(@RequestBody Account account) {
roleStrategyContext.getStrategy(account.getRole()).register(account);
return R.ok();
}

哪些角色支持注册,就在对应策略中实现;不支持则抛异常。


✅ 补充说明:修改角色逻辑时怎么办?

比如你想修改 ADMIN 的密码策略逻辑:

✅ 做法:

只需打开 AdminStrategy.java 文件,定位 updatePassword() 方法,修改实现即可:

1
2
3
4
5
@Override
public void updatePassword(Account account) {
// 新密码校验逻辑、密码强度判断、新加验证码验证等...
adminService.updatePassword(account);
}

无需修改 Controller、也无需触碰上下文,非常安全。


✅ 总结:你项目从此获得以下能力

能力类型 含义
🧩 插拔式角色接入 新角色无需改旧代码,只需新增策略类
🔒 高内聚低耦合 每个角色自己的业务逻辑自管理,职责单一
🔁 支持多接口拓展 login / register / updatePassword 可灵活定义
📈 拓展无限角色 想加 TEACHER、AUDITOR、PARENT 都毫无压力

✅ 最终操作总览表(建议收藏)

操作目的 修改内容
新增角色 1. RoleEnum 增加;
2. 创建 XXXStrategy 并注入
修改登录逻辑 修改对应 XXXStrategy.login() 方法
修改密码逻辑 修改对应 XXXStrategy.updatePassword() 方法
控制器适配 Controller 永不修改,全部通过 RoleStrategyContext 实现

评论
分享