點擊上方“程序員蝸牛g”,選擇“設為星標”

跟蝸牛哥一起,每天進步一點點

基於 SpringBoot 工廠+策略模式統一多端登錄_ide

程序員蝸牛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 的優勢
  • 自動裝配: 通過@ServiceMap<String, LoginStrategy>自動收集所有策略 Bean
  • 類型安全: 工廠類在運行時檢查登錄類型是否合法,避免NullPointerException

八、最佳實踐:這些細節別忽略

1. 參數校驗前置

在控制器中先對loginTypeparams做基礎校驗,比如必填參數檢查,避免策略類中重複校驗

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實戰語雀筆記,回覆面試、開發手冊、有超讚的粉絲福利