点击上方“程序员蜗牛g”,选择“设为星标”
跟蜗牛哥一起,每天进步一点点
程序员蜗牛g
大厂程序员一枚 跟蜗牛一起 每天进步一点点
33篇原创内容
**
公众号
一、需求分析:当产品说 "我们要支持 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
publicclass 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)) {
thrownew IllegalArgumentException("密码错误");
}
// 模拟用户校验
checkUserLocked(username);
return"登录成功(用户名密码)";
}
private void checkUserLocked(String username) {
// 调用用户服务检查账号是否锁定
System.out.println("检查用户" + username + "是否锁定");
}
}
2. 微信扫码登录策略
@Service
publicclass 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) {
thrownew IllegalArgumentException("微信账号未绑定系统用户");
}
return"登录成功(微信扫码)";
}
private String callWechatApi(String authCode) {
// 实际应调用微信开放平台API
System.out.println("调用微信接口,authCode=" + authCode);
return"wechat_open_id_123";
}
}
3. 手机号验证码登录策略
@Service
publicclass 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)) {
thrownew IllegalArgumentException("验证码错误");
}
// 模拟用户校验
checkPhoneRegistered(phone);
return"登录成功(手机号验证码)";
}
private void checkPhoneRegistered(String phone) {
// 检查手机号是否注册
System.out.println("检查手机号" + phone + "是否注册");
}
}
四、工厂模式实现:让 "导演" 决定用哪个 "演员"
1. 登录策略工厂类
@Component
publicclass LoginStrategyFactory {
privatefinal 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) {
thrownew IllegalArgumentException("不支持的登录类型:" + loginType);
}
return strategy;
}
}
这里利用 Spring 的自动装配,将所有@Service标记的LoginStrategy实现类注入到strategyMap中,键为 Bean 名称(默认是类名首字母小写,如passwordLoginStrategy),但我们在策略类中通过getLoginType()返回自定义的类型标识,所以需要在配置类中调整 Bean 名称:
2. 策略 Bean 配置(关键!)
@Configuration
publicclass StrategyConfig {
@Bean("passwordStrategy") // 自定义Bean名称
public LoginStrategy passwordLoginStrategy() {
returnnew PasswordLoginStrategy();
}
@Bean("wechatStrategy")
public LoginStrategy wechatLoginStrategy() {
returnnew WechatLoginStrategy();
}
@Bean("smsStrategy")
public LoginStrategy smsLoginStrategy() {
returnnew 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")
publicclass LoginController {
privatefinal 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. 配置化登录类型
将支持的登录类型存储在配置文件中,前端调用时先获取支持的登录类型列表,避免硬编码
九、总结:设计模式让代码更有 "尊严"
回顾三年前的面条代码,再看现在的实现,最大的感受是:好的设计模式能让代码在需求变化面前保持优雅。工厂模式和策略模式的组合,就像给登录模块装了一个 "热插拔" 接口,新增功能时不用改核心逻辑,只需要添加新的 "插件"。
如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。
关注公众号:woniuxgg,在公众号中回复:笔记 就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利