编程导航后端模板-登录功能改造

突发奇想

在学习OJ项目的时候,想着后期跟着教程学完后,想扩展更多的登录方式。想到后期代码偏多不如现在就直接扩展,于是就有了以下的操作。

模板中的登录接口

现有的登录接口

image.png

存在多个登录认证接口,如果又有别的登录方式,又要多写一个接口。显然是不合理的,那么我们需要设计成对外只提供一个认证接口,然后内部使用根据不同的请求类型,做不同的认证。

这里需要使用到 策略模式 和 SpringBoot中的Ioc来实现调用对应的策略

认证接口设计

URI: auth/login

请求参数:

参数名参数说明
authType认证类型;password, wx_open
data认证参数(JSON):不同的authType有不同的请求参数

UML

定义一个策略接口:AuthStrategy,用来管理系统的认证接口。

AuthStrategy:

  1. 定义一个默认方法:用于根据不同的authType来调用系统中不同的具体策略类。
  2. 提供一个抽象方法:用于给具体策略类来实现自己不同的登录策略

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个评论
点击登录,快来和大家讨论吧~
表情
图片
暂无评论
sakura
下载 APP