⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7509408640438059008 「天天摸鱼的java工程师」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

各位开发者好,我是在项目里被登录功能折磨过无数次的老码农。还记得三年前接手一个多端登录项目,用户名密码、微信、手机号验证码三种登录方式挤在一个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
@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 搭舞台,依赖注入真叫爽,需求万变不用慌,代码优雅没商量!

文章目录
  1. 1. 一、需求分析:当产品说 “我们要支持 10 种登录方式”
    1. 1.0.1. 1. 典型登录场景:代码里的 “联合国”
    2. 1.0.2. 2. 设计模式选择:策略模式解耦算法,工厂模式创建实例
  • 2. 二、Spring Boot 项目搭建:先搭好 “舞台”
    1. 2.0.1. 1. 创建 Spring Boot 项目
    2. 2.0.2. 2. 定义统一登录策略接口
  • 3. 三、策略模式实现:每种登录方式都是 “独立演员”
    1. 3.0.1. 1. 用户名密码登录策略
    2. 3.0.2. 2. 微信扫码登录策略
    3. 3.0.3. 3. 手机号验证码登录策略
  • 4. 四、工厂模式实现:让 “导演” 决定用哪个 “演员”
    1. 4.0.1. 1. 登录策略工厂类
    2. 4.0.2. 2. 策略 Bean 配置(关键!)
  • 5. 五、控制器集成:对外提供统一接口
    1. 5.0.1. 1. 登录请求参数类
    2. 5.0.2. 2. 登录控制器
  • 6. 六、测试验证:三种登录方式轻松切换
    1. 6.0.1. 1. 用户名密码登录请求
    2. 6.0.2. 2. 微信扫码登录请求
    3. 6.0.3. 3. 新增登录方式:比如支付宝登录
  • 7. 七、核心优势:让代码具备 “抗需求变化体质”
    1. 7.0.1. 1. 策略模式的好处
    2. 7.0.2. 2. 工厂模式的好处
    3. 7.0.3. 3. 结合 Spring Boot 的优势
  • 8. 八、最佳实践:这些细节别忽略
    1. 8.0.1. 1. 参数校验前置
    2. 8.0.2. 2. 公共逻辑抽取
    3. 8.0.3. 3. 日志和异常处理
    4. 8.0.4. 4. 配置化登录类型
  • 9. 九、总结:设计模式让代码更有 “尊严”