1. 项目背景与痛点分析
最近在重构一个企业级应用的单点登录模块时,遇到了典型的代码维护难题。最初系统只需要对接一个外部认证系统,登录逻辑直接写在Service层,代码还算清晰。但随着业务发展,需要对接第二个外部系统时,我简单地在原有代码上增加了if-else分支。当第三个外部系统需要接入时,代码已经变成了这样:
java复制public String login(STymhDTO dto) {
if ("systemA".equals(dto.getType())) {
// 50行处理逻辑
} else if ("systemB".equals(dto.getType())) {
// 60行处理逻辑
} else if ("systemC".equals(dto.getType())) {
// 70行处理逻辑
}
// 更多if-else...
}
这种写法存在几个明显问题:
- 单个方法过于臃肿,动辄几百行代码
- 新增登录方式需要修改核心业务类,违反开闭原则
- 不同系统的登录逻辑高度耦合,难以单独测试
- 方法复杂度呈指数级增长,维护成本高
2. 重构方案设计思路
2.1 设计模式选型考量
面对这种"同类型不同实现"的业务场景,策略模式是最佳选择。策略模式的核心思想是将算法或行为抽象为接口,使它们可以相互替换。在我们的案例中,不同外部系统的登录流程就是不同的策略实现。
但单纯使用策略模式还存在一个问题:客户端代码(Service层)仍然需要知道所有具体策略类,当新增策略时仍需修改客户端代码。因此需要引入工厂模式,将策略对象的创建过程封装起来。
2.2 整体架构设计
重构后的架构分为三个层次:
- 实体层:定义统一的参数传输对象(DTO)
- 策略层:抽象登录行为接口,实现具体策略类
- 工厂层:根据输入参数自动匹配策略
mermaid复制classDiagram
class STymhDTO {
+String tymhType
+String tymhUserName
//...其他字段
}
class ITymhHandler {
<<interface>>
+matches(STymhDTO): boolean
+handle(STymhDTO): String
}
class TymhHandlerFactory {
-List~ITymhHandler~ handlers
+getMatchedHandler(STymhDTO): ITymhHandler
}
class TymhLoginService {
-TymhHandlerFactory factory
+processLogin(STymhDTO): String
}
class SystemALoginHandler {
+matches(STymhDTO): boolean
+handle(STymhDTO): String
}
ITymhHandler <|-- SystemALoginHandler
ITymhHandler <|-- SystemBLoginHandler
ITymhHandler <|-- SystemCLoginHandler
TymhHandlerFactory --> ITymhHandler
TymhLoginService --> TymhHandlerFactory
3. 核心实现细节
3.1 统一参数实体定义
首先定义统一的参数传输对象,这是所有策略类处理的输入标准:
java复制@Data
public class STymhDTO {
// 必填字段
private String tymhType; // 系统类型标识
private String tymhUserName; // 用户名
// 可选字段(根据系统需求)
private String tymhAppCode;
private String tymhHost;
private String tymhDefaultRoleId;
// 同步相关URL
private String tymhSyncDeptUrl;
private String tymhSyncUserUrl;
}
注意:所有字段都应有清晰的Javadoc说明其用途和约束条件,这是团队协作的基础。
3.2 策略接口设计与实现
定义策略接口时,除了核心的handle方法外,还增加了matches方法用于策略匹配:
java复制public interface ITymhHandler {
/**
* 判断当前策略是否匹配输入参数
*/
boolean matches(STymhDTO sTymhDTO);
/**
* 执行登录处理逻辑
* @return 登录结果(如token或错误信息)
*/
String handle(STymhDTO sTymhDTO);
}
具体策略实现示例(以SystemA为例):
java复制@Component
@Order(1) // 定义策略优先级
public class SystemALoginHandler implements ITymhHandler {
private final AuthClient authClient;
@Autowired
public SystemALoginHandler(AuthClient authClient) {
this.authClient = authClient;
}
@Override
public boolean matches(STymhDTO dto) {
return "systemA".equals(dto.getTymhType());
}
@Override
public String handle(STymhDTO dto) {
// 1. 参数校验
if (StringUtils.isEmpty(dto.getTymhUserName())) {
throw new IllegalArgumentException("用户名不能为空");
}
// 2. 调用认证接口
AuthResponse response = authClient.authenticate(
dto.getTymhUserName(),
dto.getTymhAppCode()
);
// 3. 处理响应
if (!response.isSuccess()) {
throw new RuntimeException("认证失败: " + response.getMessage());
}
// 4. 生成本地token
return JwtUtils.generateToken(response.getUserId());
}
}
3.3 策略工厂实现
工厂类的核心职责是自动匹配适合的策略:
java复制@Component
public class TymhHandlerFactory {
private final List<ITymhHandler> handlers;
@Autowired
public TymhHandlerFactory(List<ITymhHandler> handlers) {
// 根据@Order排序
this.handlers = handlers.stream()
.sorted(AnnotationAwareOrderComparator.INSTANCE)
.collect(Collectors.toList());
}
public ITymhHandler getMatchedHandler(STymhDTO dto) {
if (dto == null) {
throw new IllegalArgumentException("参数不能为空");
}
return handlers.stream()
.filter(handler -> handler.matches(dto))
.findFirst()
.orElseThrow(() -> new UnsupportedOperationException(
"未找到匹配的处理策略: " + dto.getTymhType()));
}
}
实际项目中,可以在工厂类中添加缓存机制,避免每次请求都重新匹配策略。
4. 服务层整合
最终的Service层变得非常简洁:
java复制@Service
@RequiredArgsConstructor
public class TymhLoginService {
private final TymhHandlerFactory handlerFactory;
public String processLogin(STymhDTO dto) {
try {
ITymhHandler handler = handlerFactory.getMatchedHandler(dto);
return handler.handle(dto);
} catch (IllegalArgumentException e) {
log.error("参数错误", e);
return "参数错误: " + e.getMessage();
} catch (Exception e) {
log.error("登录处理异常", e);
return "系统繁忙,请稍后再试";
}
}
}
5. 扩展与优化实践
5.1 新增外部系统支持
现在要新增一个SystemD的支持,只需要:
- 创建新的策略实现类
- 实现matches和handle方法
- 添加@Component注解
java复制@Component
@Order(2)
public class SystemDLoginHandler implements ITymhHandler {
@Override
public boolean matches(STymhDTO dto) {
return "systemD".equals(dto.getTymhType());
}
@Override
public String handle(STymhDTO dto) {
// 实现SystemD特有的登录逻辑
}
}
完全不需要修改任何现有代码,真正实现了开闭原则。
5.2 性能优化技巧
- 策略缓存:在工厂类中缓存type到handler的映射,避免每次匹配
- 并行校验:对于需要调用外部接口的策略,使用CompletableFuture并行处理
- 懒加载:对于资源密集的策略实现,可以使用懒加载模式
java复制// 缓存示例
private final Map<String, ITymhHandler> handlerCache = new ConcurrentHashMap<>();
public ITymhHandler getMatchedHandler(STymhDTO dto) {
return handlerCache.computeIfAbsent(dto.getTymhType(),
type -> handlers.stream()
.filter(h -> h.matches(dto))
.findFirst()
.orElseThrow(...));
}
5.3 测试策略
针对这种架构,测试应该分为三个层次:
- 单元测试:每个策略类独立测试
- 集成测试:测试工厂类能否正确匹配策略
- 端到端测试:测试整个登录流程
java复制// 策略单元测试示例
@Test
public void testSystemAHandler() {
SystemALoginHandler handler = new SystemALoginHandler(mockAuthClient);
STymhDTO dto = new STymhDTO();
dto.setTymhType("systemA");
assertTrue(handler.matches(dto));
// 测试handle方法...
}
// 工厂测试示例
@Test
public void testFactoryMatcher() {
STymhDTO dto = new STymhDTO();
dto.setTymhType("systemB");
ITymhHandler handler = factory.getMatchedHandler(dto);
assertTrue(handler instanceof SystemBLoginHandler);
}
6. 常见问题与解决方案
6.1 策略匹配冲突
问题:多个策略同时匹配同一个输入
解决方案:
- 使用@Order注解定义优先级
- 在matches方法中实现更精确的匹配逻辑
- 工厂类选择第一个匹配的策略
6.2 策略初始化性能
问题:策略类过多导致启动慢
解决方案:
- 使用懒加载模式
- 将不常用的策略移到单独模块
- 实现策略的动态注册机制
6.3 异常处理统一
问题:不同策略抛出不同异常
解决方案:
- 定义统一的异常体系
- 在策略接口中明确异常声明
- Service层统一捕获转换
java复制// 自定义异常体系
public abstract class LoginException extends RuntimeException {
private final ErrorCode errorCode;
public LoginException(ErrorCode code, String message) {
super(message);
this.errorCode = code;
}
public ErrorCode getErrorCode() {
return errorCode;
}
}
// 业务异常
public class InvalidCredentialException extends LoginException {
public InvalidCredentialException() {
super(ErrorCode.AUTH_FAILED, "认证失败");
}
}
7. 进一步优化方向
- 策略组合:支持多个策略组合处理复杂场景
- 动态策略:运行时动态加载新策略(如通过Groovy)
- 策略监控:收集各策略的执行指标(成功率、耗时等)
- 熔断机制:对失败率高的策略自动熔断
java复制// 策略监控示例
public class MonitoredHandler implements ITymhHandler {
private final ITymhHandler delegate;
private final MeterRegistry meterRegistry;
@Override
public String handle(STymhDTO dto) {
Timer.Sample sample = Timer.start(meterRegistry);
try {
String result = delegate.handle(dto);
sample.stop(Timer.builder("login.strategy")
.tag("type", dto.getTymhType())
.tag("status", "success")
.register(meterRegistry));
return result;
} catch (Exception e) {
sample.stop(Timer.builder("login.strategy")
.tag("type", dto.getTymhType())
.tag("status", "error")
.register(meterRegistry));
throw e;
}
}
}
在实际项目中,这种架构已经帮助我们轻松对接了7个不同的外部认证系统,代码仍然保持清晰可维护。每次新增对接需求时,开发效率提升了60%以上,因为开发者只需要关注新系统的特定逻辑,无需担心影响现有功能。