编程导航后端模板-登录功能改造
突发奇想
在学习OJ项目的时候,想着后期跟着教程学完后,想扩展更多的登录方式。想到后期代码偏多不如现在就直接扩展,于是就有了以下的操作。
模板中的登录接口
现有的登录接口
存在多个登录认证接口,如果又有别的登录方式,又要多写一个接口。显然是不合理的,那么我们需要设计成对外只提供一个认证接口,然后内部使用根据不同的请求类型,做不同的认证。
这里需要使用到 策略模式 和 SpringBoot中的Ioc来实现调用对应的策略
认证接口设计
URI: auth/login
请求参数:
| 参数名 | 参数说明 |
|---|---|
| authType | 认证类型;password, wx_open |
| data | 认证参数(JSON):不同的authType有不同的请求参数 |
UML
定义一个策略接口:AuthStrategy,用来管理系统的认证接口。
AuthStrategy:
- 定义一个默认方法:用于根据不同的authType来调用系统中不同的具体策略类。
- 提供一个抽象方法:用于给具体策略类来实现自己不同的登录策略
AuthPasswordStrategy、WxOpenStrategy就是具体的登录策略类,需要实现抽象策略类
实现步骤
第一步:前期准备
认证类型枚举类
▼java复制代码/** * 系统认证方式 */ @Getter public enum AuthTypeEnum { PASSWORD("password", "账号密码"), WX_OPEN("wx_open", "微信开放平台"); /** * 枚举值 */ final String value; /** * 描述 */ final String desc; AuthTypeEnum(String value, String desc) { this.value = value; this.desc = desc; } /** * 校验传入的value是否存在,如果存在就返回AuthTypeEnum枚举值,如果不存在就抛出异常 * * @param value 枚举值 * @return 枚举对象 */ public static AuthTypeEnum getEnumByValue(String value) { if (value == null || value.isEmpty()) { return null; } for (AuthTypeEnum anEnum : AuthTypeEnum.values()) { if (anEnum.value.equals(value)) { return anEnum; } } throw new BusinessException(ErrorCode.PARAMS_ERROR, "该认证方式系统暂未开放"); } }
认证请求类
▼java复制代码/** * 系统认证请求类 */ @Data public class AuthRequest implements Serializable { /** * 认证类型 */ private String authType; /** * 认证数据 */ private String data; }
修改模板中的SpringContextUtils
▼java复制代码/** * Spring 上下文获取工具 */ @Component public class SpringContextUtils extends SpringUtil { /** * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true */ public static boolean containsBean(String name) { return getBeanFactory().containsBean(name); } /** * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 * 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException) */ public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().isSingleton(name); } /** * @return Class 注册对象的类型 */ public static Class<?> getType(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().getType(name); } /** * 如果给定的bean名字在bean定义中有别名,则返回这些别名 */ public static String[] getAliases(String name) throws NoSuchBeanDefinitionException { return getBeanFactory().getAliases(name); } /** * 获取aop代理对象 */ @SuppressWarnings("unchecked") public static <T> T getAopProxy(T invoker) { return (T) getBean(invoker.getClass()); } /** * 获取spring上下文 */ public static ApplicationContext context() { return getApplicationContext(); } }
第二步:定义AuthStrategy抽象策略类
▼java复制代码/** * 登录认证抽象策略类 * 用于分发登录认证方式 */ public interface AuthStrategy { /** * 认证方式前缀 */ String AUTH_BASE_KEY = "auth:"; /** * 登录认证 * 该方法用于分发认证方式,并返回登录用户信息。 * 根据SpringContextUtils获取对应的认证方式,并调用对应的认证方法。 * * @param authRequest 认证请求 * @return 登录用户信息 {@link LoginUserVO} */ static LoginUserVO login(AuthRequest authRequest) { // 1. 校验authType AuthTypeEnum authTypeEnum = AuthTypeEnum.getEnumByValue(authRequest.getAuthType()); // 2. 使用SpringContextUtils获取对应的认证方式 String authBeanName = AUTH_BASE_KEY + authTypeEnum.getValue(); AuthStrategy authStrategy = SpringContextUtils.getBean(authBeanName); return authStrategy.login(authRequest.getData()); } /** * 抽象认证方法 * @param data 认证数据 Json * @return 登录用户信息 {@link LoginUserVO} */ LoginUserVO login(String data); }
第三步:具体策略类实现
password具体策略类
▼java复制代码/** * 账号密码认证策略 * * @author sakura */ @Service(AuthStrategy.AUTH_BASE_KEY + "password") public class PasswordAuthStrategy implements AuthStrategy { @Resource private UserService userService; @Override public LoginUserVO login(String data) { // 1. 解析data 会出现解析异常 UserLoginRequest userLoginRequest = JSONUtil.toBean(data, UserLoginRequest.class); if (ObjectUtils.isEmpty(userLoginRequest)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数未传递"); } if (ObjectUtils.anyNull(userLoginRequest.getUserAccount(), userLoginRequest.getUserPassword())) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数未传递"); } // 2. 获取请求上下文 todo 如果后期使用redis缓存登录信息,可以将这里删除 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求上下文未找到"); } HttpServletRequest request = attributes.getRequest(); return userService.userLogin(userLoginRequest.getUserAccount(), userLoginRequest.getUserPassword(), request); } }
微信开发平台
▼java复制代码/** * 微信开放平台认证策略 * * @author sakura */ @Slf4j @Service(AuthStrategy.AUTH_BASE_KEY + "wx_open") public class WxOpenAuthStrategy implements AuthStrategy { @Resource private WxOpenConfig wxOpenConfig; @Resource private UserService userService; @Override public LoginUserVO login(String data) { // 1. 解析 JSONObject jsonObject = JSONUtil.parseObj(data); String code = jsonObject.get("code", String.class); if (StringUtils.isAnyBlank(code)) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求参数未传递"); } WxOAuth2AccessToken accessToken; try { WxMpService wxService = wxOpenConfig.getWxMpService(); accessToken = wxService.getOAuth2Service().getAccessToken(code); WxOAuth2UserInfo userInfo = wxService.getOAuth2Service().getUserInfo(accessToken, code); String unionId = userInfo.getUnionId(); String mpOpenId = userInfo.getOpenid(); if (StringUtils.isAnyBlank(unionId, mpOpenId)) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误"); } // 2. 获取请求上下文 todo 如果后期使用redis缓存登录信息,可以将这里删除 ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "请求上下文未找到"); } HttpServletRequest request = attributes.getRequest(); return userService.userLoginByMpOpen(userInfo, request); } catch (Exception e) { log.error("userLoginByWxOpen error", e); throw new BusinessException(ErrorCode.SYSTEM_ERROR, "登录失败,系统错误"); } } }
第四步:修改系统登录接口
▼java复制代码/** * 系统认证接口 * 负责系统的认证和注册 * * @author Sakura */ @RestController @RequestMapping("auth") public class AuthController { // region 登录相关 /** * 用户登录 * * @param authRequest 认证请求数据 * @return {@link LoginUserVO} */ @PostMapping("/login") public BaseResponse<LoginUserVO> userLogin(@RequestBody AuthRequest authRequest) { if (authRequest == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } LoginUserVO loginUserVO = AuthStrategy.login(authRequest); return ResultUtils.success(loginUserVO); } }
测试
请求接口
▼http复制代码POST localhost:8101/api/auth { "authType": "password", "data": "{\"userAccount\":\"admin\",\"userPassword\":\"k01225.k\"}" }
结果
▼json复制代码{ "code": 0, "data": { "id": "1", "userName": "admin", "userAvatar": "", "userProfile": "", "userRole": "admin", "createTime": "2025-03-10T23:03:18.000+00:00", "updateTime": "2025-03-10T23:10:31.000+00:00" }, "message": "ok" }
那么现在系统的登录图, 这样就可以对外只提供一个登录接口,只需要传递不同的authType 即可完成不同的认证方式

评论
问答助学
相关内容
0个评论
全部评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
内容推荐
GPT-5.6 Sol、Terra、Luna 怎么选?看这一篇就够了
3
🛰️ dtSpaceMap - 实时卫星追踪 3D 可视化平台
5
AI应用开发中的流式输出:从协议原理到工程实战的完整指南
38
第1章 LearnWise AI项目介绍
5
【入职求助贴】萌新刚入职某大厂做后端开发,目前还在试用期。最近遇到一个棘手的问题,想向大家求助一下。入职不久,leader 给我派了一个任务。跟我说是0.5天就可以解决,我刚毕业入职,做了一个星期没有做出来。实现一个收集定时成功任务的案例。听起来好像不复杂,但我自己摸索着做了一整个星期,到现在还没达到预期效果。这一周我基本是“边学边做”的状态,遇到卡点也会每天主动找 leader 沟通进度和疑问。
2
