做网站的带宽钦州市住房和城乡建设局网站

当前位置: 首页 > news >正文

做网站的带宽,钦州市住房和城乡建设局网站,做直播哪个网站好,怎样才能建立自已的网站【尚庭公寓SpringBoot Vue 项目实战】登录管理#xff08;十八#xff09; 文章目录 【尚庭公寓SpringBoot Vue 项目实战】登录管理#xff08;十八#xff09;1、登录业务介绍2、接口开发2.1、获取图形验证码2.2、登录接口2.3、获取登录用户个人信息 1、登录业务介绍 登…【尚庭公寓SpringBoot Vue 项目实战】登录管理十八 文章目录 【尚庭公寓SpringBoot Vue 项目实战】登录管理十八1、登录业务介绍2、接口开发2.1、获取图形验证码2.2、登录接口2.3、获取登录用户个人信息 1、登录业务介绍 登录管理共需三个接口分别是获取图形验证码、登录、获取登录用户个人信息除此之外我们还需为所有受保护的接口增加验证JWT合法性的逻辑这一功能可通过HandlerInterceptor来实现。后台管理系统的登录流程如下图所示 2、接口开发 2.1、获取图形验证码 查看接口 代码开发 查看响应的数据结构 查看web-admin模块下的com.atguigu.lease.web.admin.vo.login.CaptchaVo内容如下 Data Schema(description 图像验证码) AllArgsConstructor public class CaptchaVo {Schema(description验证码图片信息)private String image;Schema(description验证码key)private String key; }配置所需依赖 验证码生成工具 本项目使用开源的验证码生成工具EasyCaptcha其支持多种类型的验证码例如gif、中文、算术等并且简单易用具体内容可参考其官方文档。 在common模块的pom.xml文件中增加如下内容 dependencygroupIdcom.github.whvcse/groupIdartifactIdeasy-captcha/artifactId /dependencyRedis 在common模块的pom.xml中增加如下内容 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-data-redis/artifactId /dependency在application.yml中增加如下配置 spring:data:redis:host: hostnameport: portpassword:passworddatabase: 0注意上述hostname、password和port需根据实际情况进行修改,,如果你redis没有密码可以省略 编写Controller层逻辑 在LoginController中增加如下内容 Operation(summary 获取图形验证码) GetMapping(login/captcha) public ResultCaptchaVo getCaptcha() {CaptchaVo captcha service.getCaptcha();return Result.ok(captcha); }编写Service层逻辑 在LoginService中增加如下内容 CaptchaVo getCaptcha();在LoginServiceImpl中增加如下内容 Autowired private StringRedisTemplate redisTemplate;Override public CaptchaVo getCaptcha() {SpecCaptcha specCaptcha new SpecCaptcha(130, 48, 4);specCaptcha.setCharType(Captcha.TYPE_DEFAULT);String code specCaptcha.text().toLowerCase();String key RedisConstant.ADMIN_LOGIN_PREFIX UUID.randomUUID();String image specCaptcha.toBase64();redisTemplate.opsForValue().set(key, code, RedisConstant.ADMIN_LOGIN_CAPTCHA_TTL_SEC, TimeUnit.SECONDS);return new CaptchaVo(image, key); }知识点 本项目Reids中的key需遵循以下命名规范项目名:功能模块名:其他例如admin:login:123456 spring-boot-starter-data-redis已经完成了StringRedisTemplate的自动配置我们直接注入即可。 为方便管理可以将Reids相关的一些值定义为常量例如key的前缀、TTL时长内容如下。大家可将这些常量统一定义在common模块下的com.atguigu.lease.common.constant.RedisConstant类中 public class RedisConstant {public static final String ADMIN_LOGIN_PREFIX admin:login:;public static final Integer ADMIN_LOGIN_CAPTCHA_TTL_SEC 60;public static final String APP_LOGIN_PREFIX app:login:;public static final Integer APP_LOGIN_CODE_RESEND_TIME_SEC 60;public static final Integer APP_LOGIN_CODE_TTL_SEC 60 * 10;public static final String APP_ROOM_PREFIX app:room:; }2.2、登录接口 查看接口 登录校验逻辑 用户登录的校验逻辑分为三个主要步骤分别是校验验证码校验用户状态和校验密码具体逻辑如下 前端发送username、password、captchaKey、captchaCode请求登录。判断captchaCode是否为空若为空则直接响应验证码为空若不为空进行下一步判断。根据captchaKey从Redis中查询之前保存的code若查询出来的code为空则直接响应验证码已过期若不为空进行下一步判断。比较captchaCode和code若不相同则直接响应验证码不正确若相同则进行下一步判断。根据username查询数据库若查询结果为空则直接响应账号不存在若不为空则进行下一步判断。查看用户状态判断是否被禁用若禁用则直接响应账号被禁若未被禁用则进行下一步判断。比对password和数据库中查询的密码若不一致则直接响应账号或密码错误若一致则进行入最后一步。创建JWT并响应给浏览器。 代码开发 查看请求数据结构 查看web-admin模块下的com.atguigu.lease.web.admin.vo.login.LoginVo具体内容如下 Data Schema(description 后台管理系统登录信息) public class LoginVo {Schema(description用户名)private String username;Schema(description密码)private String password;Schema(description验证码key)private String captchaKey;Schema(description验证码code)private String captchaCode; }配置所需依赖 登录接口需要为登录成功的用户创建并返回JWT本项目使用开源的JWT工具Java-JWT配置如下具体内容可参考官方文档。 引入Maven依赖 在common模块的pom.xml文件中增加如下内容 dependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-api/artifactId /dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-impl/artifactIdscoperuntime/scope /dependencydependencygroupIdio.jsonwebtoken/groupIdartifactIdjjwt-jackson/artifactIdscoperuntime/scope /dependency创建JWT工具类 在common模块下创建com.atguigu.lease.common.utils.JwtUtil工具类内容如下 public class JwtUtil {private static long tokenExpiration 60 * 60 * 1000L;private static SecretKey tokenSignKey Keys.hmacShaKeyFor(M0PKKI6pYGVWWfDZw90a0lTpGYX1d4AQ.getBytes());public static String createToken(Long userId, String username) {String token Jwts.builder().setSubject(USER_INFO).setExpiration(new Date(System.currentTimeMillis() tokenExpiration)).claim(userId, userId).claim(username, username).signWith(tokenSignKey).compact();return token;} }编写Controller层逻辑 在LoginController中增加如下内容 Operation(summary 登录) PostMapping(login) public ResultString login(RequestBody LoginVo loginVo) {String token service.login(loginVo);return Result.ok(token); }编写Service层逻辑 在LoginService中增加如下内容 String login(LoginVo loginVo);在LoginServiceImpl中增加如下内容 Override public String login(LoginVo loginVo) {//1.判断是否输入了验证码if (!StringUtils.hasText(loginVo.getCaptchaCode())) {throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_NOT_FOUND);}//2.校验验证码String code redisTemplate.opsForValue().get(loginVo.getCaptchaKey());if (code null) {throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_EXPIRED);}if (!code.equals(loginVo.getCaptchaCode().toLowerCase())) {throw new LeaseException(ResultCodeEnum.ADMIN_CAPTCHA_CODE_ERROR);}//3.校验用户是否存在SystemUser systemUser systemUserMapper.selectOneByUsername(loginVo.getUsername());if (systemUser null) {throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_NOT_EXIST_ERROR);}//4.校验用户是否被禁if (systemUser.getStatus() BaseStatus.DISABLE) {throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_DISABLED_ERROR);}//5.校验用户密码if (!systemUser.getPassword().equals(DigestUtils.md5Hex(loginVo.getPassword()))) {throw new LeaseException(ResultCodeEnum.ADMIN_ACCOUNT_ERROR);}//6.创建并返回TOKENreturn JwtUtil.createToken(systemUser.getId(), systemUser.getUsername()); }编写Mapper层逻辑 在LoginMapper中增加如下内容 SystemUser selectOneByUsername(String username);在LoginMapper.xml中增加如下内容 select idselectOneByUsername resultTypecom.atguigu.lease.model.entity.SystemUserselect id,username,password,name,type,phone,avatar_url,additional_info,post_id,statusfrom system_userwhere is_deleted 0and username #{username} /select编写HandlerInterceptor 我们需要为所有受保护的接口增加校验JWT合法性的逻辑。具体实现如下 在JwtUtil中增加parseToken方法内容如下 public static Claims parseToken(String token){if (tokennull){throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);}try{JwtParser jwtParser Jwts.parserBuilder().setSigningKey(secretKey).build();return jwtParser.parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);}catch (JwtException e){throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);} }编写HandlerInterceptor 在web-admin模块中创建com.atguigu.lease.web.admin.custom.interceptor.AuthenticationInterceptor类内容如下有关HanderInterceptor的相关内容可参考官方文档。 Component public class AuthenticationInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token request.getHeader(access-token);JwtUtil.parseToken(token);return true;} }注意 我们约定前端登录后后续请求都将JWT放置于HTTP请求的Header中其Header的key为access-token。 注册HandlerInterceptor 在web-admin模块的com.atguigu.lease.web.admin.custom.config.WebMvcConfiguration中增加如下内容 Autowired private AuthenticationInterceptor authenticationInterceptor;Override public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(this.authenticationInterceptor).addPathPatterns(/admin/).excludePathPatterns(/admin/login/); }Knife4j配置 在增加上述拦截器后为方便继续调试其他接口可以获取一个长期有效的Token将其配置到Knife4j的全局参数中如下图所示。 注意每个接口分组需要单独配置刷新页面任选一个接口进行调试会发现发送请求时会自动携带该header
2.3、获取登录用户个人信息 查看接口 代码开发 查看请求和响应的数据结构 响应的数据结构 查看web-admin模块下的com.atguigu.lease.web.admin.vo.system.user.SystemUserInfoVo内容如下 Schema(description 员工基本信息) Data public class SystemUserInfoVo {Schema(description 用户姓名)private String name;Schema(description 用户头像)private String avatarUrl; }请求的数据结构 按理说前端若想获取当前登录用户的个人信息需要传递当前用户的id到后端进行查询。但是由于请求中携带的JWT中就包含了当前登录用户的id故请求个人信息时就无需再传递id。 修改JwtUtil中的parseToken方法 由于需要从Jwt中获取用户id因此需要为parseToken 方法增加返回值如下 public static Claims parseToken(String token){if (tokennull){throw new LeaseException(ResultCodeEnum.ADMIN_LOGIN_AUTH);}try{JwtParser jwtParser Jwts.parserBuilder().setSigningKey(secretKey).build();return jwtParser.parseClaimsJws(token).getBody();}catch (ExpiredJwtException e){throw new LeaseException(ResultCodeEnum.TOKEN_EXPIRED);}catch (JwtException e){throw new LeaseException(ResultCodeEnum.TOKEN_INVALID);} }编写ThreadLocal工具类 理论上我们可以在Controller方法中使用RequestHeader获取JWT然后在进行解析如下 Operation(summary 获取登陆用户个人信息) GetMapping(info) public ResultSystemUserInfoVo info(RequestHeader(access-token) String token) {Claims claims JwtUtil.parseToken(token);Long userId claims.get(userId, Long.class);SystemUserInfoVo userInfo service.getLoginUserInfo(userId);return Result.ok(userInfo); }上述代码的逻辑没有任何问题但是这样做JWT会被重复解析两次一次在拦截器中一次在该方法中。为避免重复解析通常会在拦截器将Token解析完毕后将结果保存至ThreadLocal中这样一来我们便可以在整个请求的处理流程中进行访问了。 ThreadLocal概述 ThreadLocal的主要作用是为每个使用它的线程提供一个独立的变量副本使每个线程都可以操作自己的变量而不会互相干扰其用法如下图所示。 在common模块中创建com.atguigu.lease.common.login.LoginUserHolder工具类 public class LoginUserHolder {public static ThreadLocalLoginUser threadLocal new ThreadLocal();public static void setLoginUser(LoginUser loginUser) {threadLocal.set(loginUser);}public static LoginUser getLoginUser() {return threadLocal.get();}public static void clear() {threadLocal.remove();} }同时在common模块中创建com.atguigu.lease.common.login.LoginUser类 Data AllArgsConstructor public class LoginUser {private Long userId;private String username; }修改AuthenticationInterceptor拦截器 Component public class AuthenticationInterceptor implements HandlerInterceptor {Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {String token request.getHeader(access-token);Claims claims JwtUtil.parseToken(token);Long userId claims.get(userId, Long.class);String username claims.get(username, String.class);LoginUserHolder.setLoginUser(new LoginUser(userId, username));return true;}Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {LoginUserHolder.clear();} }编写Controller层逻辑 在LoginController中增加如下内容 Operation(summary 获取登陆用户个人信息) GetMapping(info) public ResultSystemUserInfoVo info() {SystemUserInfoVo userInfo service.getLoginUserInfo(LoginUserHolder.getLoginUser().getUserId());return Result.ok(userInfo); }编写Service层逻辑 在LoginService中增加如下内容 Override public SystemUserInfoVo getLoginUserInfo(Long userId) {SystemUser systemUser systemUserMapper.selectById(userId);SystemUserInfoVo systemUserInfoVo new SystemUserInfoVo();systemUserInfoVo.setName(systemUser.getName());systemUserInfoVo.setAvatarUrl(systemUser.getAvatarUrl());return systemUserInfoVo; }