工厂模式实现短信验证的demo

发布时间:2026/7/2 1:19:09

工厂模式实现短信验证的demo 用 Spring Boot 实战工厂模式一个多登录方式认证系统的设计思路github仓库https://github.com/startmas/Model_Test.git技术栈Spring Boot 4.1 · Java 21 · MyBatis-Plus · MySQL核心设计工厂模式 策略模式一、为什么需要工厂模式在认证系统中「登录」和「发验证码」往往不是单一流程而是多种方式并存loginType说明password账号密码登录qq_email_code邮箱验证码登录phone_code手机验证码登录如果把这些逻辑全部堆在一个AuthService里用if-else/switch分支处理很快会遇到这些问题违反开闭原则每新增一种登录方式都要改核心服务类职责不清发码、验码、查用户、建会话混在一起难以测试一个方法里分支太多单元测试成本高协作冲突多人同时改同一个大类合并冲突频繁Factory_Test_Demo的解法是把每种登录/发码逻辑拆成独立 Handler再用 Factory 按loginType选择具体实现。二、整体架构工厂 策略这个项目并不是教科书里那种「new ProductA()/new ProductB()」的简单工厂而是更贴近工程实践的Spring 注册式工厂Controller ↓ AuthService统一入口不关心具体实现 ↓ LoginHandlerFactory / SendCodeHandlerFactory工厂按类型选对象 ↓ LoginHandler / SendCodeHandler策略接口 ↓ PasswordLoginHandler / EmailCodeLoginHandler / PhoneCodeLoginHandler ...用 Mermaid 表示调用链LoginHandlerLoginHandlerFactoryAuthServiceAuthControllerLoginHandlerLoginHandlerFactoryAuthServiceAuthControllerlogin(LoginRequest)getHandler(loginType)stream().filter(supports)具体 Handlerhandle(req, clientIp)AuthUsercreateSessionAndResponse()LoginResponse这里有两个设计模式在配合策略模式StrategyLoginHandler定义统一行为handle()不同登录方式各自实现工厂模式FactoryLoginHandlerFactory负责「创建/选择」合适的策略对象三、策略接口统一行为隔离变化登录侧的策略接口非常简洁publicinterfaceLoginHandler{/** 是否支持该 loginType */booleansupports(StringloginType);/** 执行登录成功返回用户失败返回 null */AuthUserhandle(LoginRequestreq,StringclientIp);}发验证码侧同理publicinterfaceSendCodeHandler{booleansupports(StringloginType);MapString,Objecthandle(SendCodeRequestreq,StringclientIp);}关键点在于上层只依赖接口不依赖具体实现supports()让每个实现自己声明「我处理哪种类型」handle()封装完整业务Factory 和 Service 都不需要知道细节四、具体策略一种登录方式一个类4.1 密码登录ComponentRequiredArgsConstructorpublicclassPasswordLoginHandlerimplementsLoginHandler{privatefinalAuthUserRepositoryuserRepo;Overridepublicbooleansupports(StringloginType){returnpassword.equals(loginType);}OverridepublicAuthUserhandle(LoginRequestreq,StringclientIp){if(req.getAccount()null||req.getPassword()null)returnnull;OptionalAuthUseruouserRepo.findByUsername(req.getAccount());if(uo.isEmpty())returnnull;AuthUseruuo.get();if(u.getPasswordHash()null)returnnull;if(!u.getPasswordHash().equals(AuthService.sha256Hex(req.getPassword())))returnnull;returnu;}}4.2 邮箱验证码登录ComponentRequiredArgsConstructorpublicclassEmailCodeLoginHandlerimplementsLoginHandler{Overridepublicbooleansupports(StringloginType){returnqq_email_code.equals(loginType);}OverridepublicAuthUserhandle(LoginRequestreq,StringclientIp){// 1. 查有效验证码// 2. 校验 codeHash// 3. 标记验证码已使用// 4. 查找或自动注册用户// ...}}4.3 手机验证码登录PhoneCodeLoginHandler与邮箱版结构几乎一致只是identityType和loginType不同。这正是策略模式的价值相似流程可以复制模板互不影响。五、工厂类Spring 帮你完成「注册表」项目里的工厂实现非常精炼ComponentRequiredArgsConstructorpublicclassLoginHandlerFactory{privatefinalListLoginHandlerhandlers;publicLoginHandlergetHandler(StringloginType){returnhandlers.stream().filter(h-h.supports(loginType)).findFirst().orElseThrow(()-newIllegalArgumentException(unsupported loginType));}}SendCodeHandlerFactory写法完全一致。这里最值得学习的点传统工厂模式需要手写MapString,LoginHandlermapnewHashMap();map.put(password,newPasswordLoginHandler());map.put(qq_email_code,newEmailCodeLoginHandler());// 每加一个就要改工厂...而 Spring 提供了更优雅的方式所有 Handler 标注Component工厂注入ListLoginHandlerSpring 自动把所有实现收集进 List新增 Handler 时只加类不改工厂这是一种「自动注册式工厂」在 Spring 项目里非常常见也常被称作Factory Strategy DI组合。六、业务入口Service 层保持干净AuthService不再关心具体登录逻辑只做编排ServiceRequiredArgsConstructorpublicclassAuthService{privatefinalLoginHandlerFactoryhandlerFactory;privatefinalSendCodeHandlerFactorysendCodeHandlerFactory;publicMapString,ObjectsendCode(SendCodeRequestreq,StringclientIp){varhandlersendCodeHandlerFactory.getHandler(req.getLoginType());returnhandler.handle(req,clientIp);}publicLoginResponselogin(LoginRequestreq,StringclientIp){Stringtypereq.getLoginType();if(typenull)thrownewIllegalArgumentException(loginType required);varhandlerhandlerFactory.getHandler(type);AuthUseruserhandler.handle(req,clientIp);if(usernull)returnnull;returncreateSessionAndResponse(user,req.getRememberMe());}}注意职责划分层级职责AuthController接收 HTTP 请求、返回统一响应AuthService流程编排选 Handler → 登录 → 建 SessionXxxHandlerFactory按类型选择实现XxxHandler具体业务逻辑Repository数据访问Session 创建、Token 刷新等「与登录方式无关」的逻辑统一留在AuthService避免每个 Handler 重复实现。七、对比不用工厂模式会怎样❌ 反例巨型 if-elsepublicLoginResponselogin(LoginRequestreq){if(password.equals(req.getLoginType())){// 50 行密码登录逻辑}elseif(qq_email_code.equals(req.getLoginType())){// 60 行邮箱验证码逻辑}elseif(phone_code.equals(req.getLoginType())){// 60 行手机验证码逻辑}else{thrownewIllegalArgumentException(unsupported loginType);}// 再统一建 session...}问题显而易见类越来越长改邮箱逻辑可能误伤密码登录。✅ 正例当前项目结构auth/service/ ├── AuthService.java ├── login/ │ ├── LoginHandler.java │ ├── LoginHandlerFactory.java │ ├── PasswordLoginHandler.java │ ├── EmailCodeLoginHandler.java │ └── PhoneCodeLoginHandler.java └── sendcode/ ├── SendCodeHandler.java ├── SendCodeHandlerFactory.java ├── EmailSendCodeHandler.java └── PhoneSendCodeHandler.java每个文件职责单一阅读和维护成本都更低。八、如何扩展新增「微信扫码登录」假设要新增wechat_scan登录步骤如下Step 1新增 HandlerComponentRequiredArgsConstructorpublicclassWechatScanLoginHandlerimplementsLoginHandler{Overridepublicbooleansupports(StringloginType){returnwechat_scan.equals(loginType);}OverridepublicAuthUserhandle(LoginRequestreq,StringclientIp){// 微信扫码登录逻辑returnuser;}}Step 2无需修改 FactorySpring 会自动把它加入ListLoginHandler。Step 3Controller / Service 无需改动前端传loginTypewechat_scan即可走新逻辑。这就是开闭原则对扩展开放对修改关闭的实际体现。九、项目中的演进痕迹从通用 Handler 到专用 Handler项目中还保留了一个已废弃的CodeLoginHandlerOverridepublicbooleansupports(StringloginType){// 通用 Handler 已被专用 Handler 替代returnfalse;}这说明设计经历过一次演进最初一个CodeLoginHandler处理所有验证码登录后来邮箱、手机逻辑分化拆成EmailCodeLoginHandler和PhoneCodeLoginHandler旧类保留但supports()返回false避免误用这是真实项目里常见的重构路径先抽象再拆分而不是一开始过度设计。十、工厂模式的适用场景与边界适合使用的场景同一接口有多种实现运行时按参数选择新增类型频繁希望少改核心代码各类实现依赖不同外部资源邮件服务、短信网关、第三方 OAuth不必使用的场景只有 12 种固定分支且几乎不会扩展逻辑极短拆类反而增加跳转成本本项目有 3 种登录 2 种发码且未来很可能继续扩展 OAuth、扫码等使用工厂模式是合理选择。十一、测试建议得益于策略拆分可以对每个 Handler 单独测试TestvoidpasswordLogin_success(){when(userRepo.findByUsername(admin)).thenReturn(Optional.of(user));AuthUserresulthandler.handle(request,127.0.0.1);assertNotNull(result);}TestvoidpasswordLogin_wrongPassword(){when(userRepo.findByUsername(admin)).thenReturn(Optional.of(user));request.setPassword(wrong);assertNull(handler.handle(request,127.0.0.1));}Factory 也可以单独测Testvoidfactory_shouldReturnEmailHandler(){LoginHandlerhandlerfactory.getHandler(qq_email_code);assertTrue(handlerinstanceofEmailCodeLoginHandler);}十二、总结Factory_Test_Demo用一个真实的认证模块展示了工厂模式在 Spring Boot 中的最佳实践策略接口定义统一行为LoginHandler/SendCodeHandler具体实现类封装每种登录/发码逻辑工厂类按loginType选择实现Spring DI自动注册所有 Handler新增实现零侵入Service 层只做流程编排保持简洁工厂模式的价值不在于背出 UML 图而在于当业务变化来临时你可以只改一个类而不是在 300 行的if-else里找 bug。附录相关 API 速查接口方法说明/api/auth/code/sendPOST发送验证码/api/auth/loginPOST登录/api/auth/refreshPOST刷新 Token请求示例{loginType:qq_email_code,target:userqq.com,code:123456,rememberMe:true}

相关新闻