Spring Boot 实战:用工厂 + 策略模式优雅实现多端登录
各位开发者好,我是在项目里被登录功能折磨过无数次的老码农。还记得三年前接手一个多端登录项目,用户名密码、微信、手机号验证码三种登录方式挤在一个if-else里,新增支付宝登录时整整改了 17 处代码,最后还漏了一处校验 —— 从此发誓再也不用 "面条代码" 处理登录逻辑。今天就把结合工厂模式和策略模式的优雅实现分享出来,带你从需求分析到 Spring Boot 实战,彻底告别登录模块的维护噩梦。
一、需求分析:当产品说 "我们要支持 10 种登录方式"
1. 典型登录场景:代码里的 "联合国"
假设我们要实现一个支持三种登录方式的系统:
- 用户名密码登录:需要校验密码加密、账号是否锁定
- 微信扫码登录:需要调用微信开放平台 API,校验授权码
- 手机号验证码登录:需要生成验证码、校验有效期和正确性
传统写法是写一个LoginService,用if-else判断登录类型:
public String login(String loginType, Map<String, Object> params) {
if ("password".equals(loginType)) {
// 用户名密码登录逻辑
} else if ("wechat".equals(loginType)) {
// 微信登录逻辑
} else if ("sms".equals(loginType)) {
// 手机号登录逻辑
} else {
throw new IllegalArgumentException("不支持的登录类型");
}
}
这种写法的问题在于:
- 扩展性差:新增登录方式要改if-else,违反开闭原则
- 职责混乱:所有逻辑挤在一个类里,可读性差
- 复用困难:不同登录方式的公共逻辑(如用户校验)无法抽取
2. 设计模式选择:策略模式解耦算法,工厂模式创建实例
- 策略模式:将每种登录方式封装成独立策略类,实现统一接口,调用者无需关心具体实现
- 工厂模式:通过工厂类根据登录类型创建对应的策略实例,避免调用者直接 new 对象
二、Spring Boot 项目搭建:先搭好 "舞台"
1. 创建 Spring Boot 项目
添加 Web 依赖,项目结构如下:
src/main/java/com/example/login
├── config
│ └── StrategyConfig.java // 策略Bean配置
├── controller
│ └── LoginController.java // 登录控制器
├── factory
│ └── LoginStrategyFactory.java // 登录策略工厂
├── model
│ └── LoginRequest.java // 登录请求参数
├── service
│ ├── impl
│ │ ├── PasswordLoginStrategy.java // 用户名密码策略
│ │ ├── WechatLoginStrategy.java // 微信策略
│ │ └── SmsLoginStrategy.java // 手机号策略
│ └── LoginStrategy.java // 登录策略接口
└── Application.java
2. 定义统一登录策略接口
public interface LoginStrategy {
// 登录类型标识,如"password"、"wechat"
String getLoginType();
// 登录方法,参数用Map传递不同登录方式的参数
String execute(Map<String, Object> params);
}
三、策略模式实现:每种登录方式都是 "独立演员"
1. 用户名密码登录策略
@Service
public class PasswordLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "password";
}
@Override
public String execute(Map<String, Object> params) {
String username = (String) params.get("username");
String password = (String) params.get("password");
// 模拟密码校验(实际应从数据库查询+密码解密)
if (!"123456".equals(password)) {
throw new IllegalArgumentException("密码错误");
}
// 模拟用户校验
checkUserLocked(username);
return "登录成功(用户名密码)";
}
private void checkUserLocked(String username) {
// 调用用户服务检查账号是否锁定
System.out.println("检查用户" + username + "是否锁定");
}
}
2. 微信扫码登录策略
@Service
public class WechatLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "wechat";
}
@Override
public String execute(Map<String, Object> params) {
String authCode = (String) params.get("authCode");
// 模拟调用微信接口获取用户信息
String openId = callWechatApi(authCode);
// 模拟数据库查询用户绑定关系
String userId = getUserIdByOpenId(openId);
if (userId == null) {
throw new IllegalArgumentException("微信账号未绑定系统用户");
}
return "登录成功(微信扫码)";
}
private String callWechatApi(String authCode) {
// 实际应调用微信开放平台API
System.out.println("调用微信接口,authCode=" + authCode);
return "wechat_open_id_123";
}
}
3. 手机号验证码登录策略
@Service
public class SmsLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "sms";
}
@Override
public String execute(Map<String, Object> params) {
String phone = (String) params.get("phone");
String code = (String) params.get("code");
// 模拟验证码校验(实际应从Redis获取)
if (!"666888".equals(code)) {
throw new IllegalArgumentException("验证码错误");
}
// 模拟用户校验
checkPhoneRegistered(phone);
return "登录成功(手机号验证码)";
}
private void checkPhoneRegistered(String phone) {
// 检查手机号是否注册
System.out.println("检查手机号" + phone + "是否注册");
}
}
四、工厂模式实现:让 "导演" 决定用哪个 "演员"
1. 登录策略工厂类
@Component
public class LoginStrategyFactory {
private final Map<String, LoginStrategy> strategyMap;
// 通过Spring依赖注入获取所有LoginStrategy实现类
public LoginStrategyFactory(Map<String, LoginStrategy> strategyMap) {
this.strategyMap = strategyMap;
}
public LoginStrategy getStrategy(String loginType) {
LoginStrategy strategy = strategyMap.get(loginType);
if (strategy == null) {
throw new IllegalArgumentException("不支持的登录类型:" + loginType);
}
return strategy;
}
}
这里利用 Spring 的自动装配,将所有@Service标记的LoginStrategy实现类注入到strategyMap中,键为 Bean 名称(默认是类名首字母小写,如passwordLoginStrategy),但我们在策略类中通过getLoginType()返回自定义的类型标识,所以需要在配置类中调整 Bean 名称:
2. 策略 Bean 配置(关键!)
@Configuration
public class StrategyConfig {
@Bean("passwordStrategy") // 自定义Bean名称
public LoginStrategy passwordLoginStrategy() {
return new PasswordLoginStrategy();
}
@Bean("wechatStrategy")
public LoginStrategy wechatLoginStrategy() {
return new WechatLoginStrategy();
}
@Bean("smsStrategy")
public LoginStrategy smsLoginStrategy() {
return new SmsLoginStrategy();
}
}
然后在策略类中重写getLoginType()返回和前端约定的类型标识(如 "password"),并在工厂类中建立类型标识到 Bean 的映射:
// 修改工厂类的构造方法,建立正确映射
public LoginStrategyFactory(Map<String, LoginStrategy> strategyMap) {
this.strategyMap = new HashMap<>();
strategyMap.forEach((beanName, strategy) ->
this.strategyMap.put(strategy.getLoginType(), strategy)
);
}
五、控制器集成:对外提供统一接口
1. 登录请求参数类
public class LoginRequest {
private String loginType; // 登录类型,如"password"、"wechat"
private Map<String, Object> params; // 具体参数,不同登录方式不同
// 省略getter/setter
}
2. 登录控制器
@RestController
@RequestMapping("/login")
public class LoginController {
private final LoginStrategyFactory factory;
@Autowired
public LoginController(LoginStrategyFactory factory) {
this.factory = factory;
}
@PostMapping
public String login(@RequestBody LoginRequest request) {
String loginType = request.getLoginType();
Map<String, Object> params = request.getParams();
LoginStrategy strategy = factory.getStrategy(loginType);
return strategy.execute(params);
}
}
六、测试验证:三种登录方式轻松切换
1. 用户名密码登录请求
{
"loginType": "password",
"params": {
"username": "user123",
"password": "123456"
}
}
2. 微信扫码登录请求
{
"loginType": "wechat",
"params": {
"authCode": "wechat_auth_code_456"
}
}
3. 新增登录方式:比如支付宝登录
只需新增AlipayLoginStrategy类并实现接口,无需修改现有代码:
@Service("alipayStrategy")
public class AlipayLoginStrategy implements LoginStrategy {
@Override
public String getLoginType() {
return "alipay";
}
@Override
public String execute(Map<String, Object> params) {
// 支付宝登录逻辑
return "登录成功(支付宝)";
}
}
七、核心优势:让代码具备 "抗需求变化体质"
1. 策略模式的好处
- 解耦算法:每种登录逻辑独立在策略类中,可读性强
- 易于扩展:新增登录方式只需添加新策略类,符合开闭原则
- 方便测试:可以单独测试每个策略类,无需关心其他逻辑
2. 工厂模式的好处
- 封装创建逻辑:调用者无需知道策略类的创建细节
- 集中管理实例:通过 Spring 管理策略 Bean,支持依赖注入和生命周期管理
3. 结合 Spring Boot 的优势
- 自动装配:通过@Service和Map<String, LoginStrategy>自动收集所有策略 Bean
- 类型安全:工厂类在运行时检查登录类型是否合法,避免NullPointerException
八、最佳实践:这些细节别忽略
1. 参数校验前置
在控制器中先对loginType和params做基础校验,比如必填参数检查,避免策略类中重复校验
2. 公共逻辑抽取
如果多种登录方式有公共逻辑(如登录成功后的 Token 生成),可以创建抽象策略类,让具体策略类继承
3. 日志和异常处理
在策略类中添加登录日志记录,统一捕获异常并返回友好的错误信息:
@Service
public class PasswordLoginStrategy implements LoginStrategy {
@Override
public String execute(Map<String, Object> params) {
try {
// 登录逻辑
} catch (Exception e) {
log.error("用户名密码登录失败:{}", e.getMessage());
throw new LoginException("登录失败,请重试"); // 自定义业务异常
}
}
}
4. 配置化登录类型
将支持的登录类型存储在配置文件中,前端调用时先获取支持的登录类型列表,避免硬编码
九、总结:设计模式让代码更有 "尊严"
回顾三年前的面条代码,再看现在的实现,最大的感受是:好的设计模式能让代码在需求变化面前保持优雅。工厂模式和策略模式的组合,就像给登录模块装了一个 "热插拔" 接口,新增功能时不用改核心逻辑,只需要添加新的 "插件"。
最后送大家一句口诀:登录逻辑别硬刚,策略模式来帮忙,工厂负责创实例,开闭原则记心上,Spring Boot 搭舞台,依赖注入真叫爽,需求万变不用慌,代码优雅没商量!