超詳細講解Java秒殺項目用戶驗證模塊的實現(xiàn)
接著上期內(nèi)容超詳細講解Java秒殺項目登陸模塊的實現(xiàn)
一、用戶驗證
未登錄的用戶不能進入首頁
根據(jù)上期內(nèi)容,我未登錄也能進入首頁:
1、在方法內(nèi)添加請求與反應(yīng)
①、IUserService
package com.example.seckill.service; import com.example.seckill.pojo.User; import com.baomidou.mybatisplus.extension.service.IService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; /** * <p> * 用戶信息表 服務(wù)類 * </p> * * @author lv * @since 2022-03-15 */ public interface IUserService extends IService<User> { ResponseResult<?> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response); }
②、UserServiceImpl
③、UserController
package com.example.seckill.controller; import com.example.seckill.service.IUserService; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.vo.UserVo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.validation.Valid; /** * <p> * 用戶信息表 前端控制器 * </p> * * @author lv * @since 2022-03-15 */ @RestController @RequestMapping("/user") public class UserController { @Autowired private IUserService userService; // 用戶登錄 @RequestMapping("/login") public ResponseResult<?> login(@Valid UserVo userVo, HttpServletRequest request, HttpServletResponse response){ // 調(diào)用service的登錄驗證 return userService.findByAccount(userVo,request,response); } }
2、cookie操作的封裝
package com.example.seckill.util; import lombok.extern.slf4j.Slf4j; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; @Slf4j public class CookieUtils { /** * @Description: 得到Cookie的值, 不編碼 */ public static String getCookieValue(HttpServletRequest request, String cookieName) { return getCookieValue(request, cookieName, false); } /** * @Description: 得到Cookie的值 */ public static String getCookieValue(HttpServletRequest request, String cookieName, boolean isDecoder) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { if (isDecoder) { retValue = URLDecoder.decode(cookieList[i].getValue(), "UTF-8"); } else { retValue = cookieList[i].getValue(); } break; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return retValue; } /** * @Description: 得到Cookie的值 */ public static String getCookieValue(HttpServletRequest request, String cookieName, String encodeString) { Cookie[] cookieList = request.getCookies(); if (cookieList == null || cookieName == null) { return null; } String retValue = null; try { for (int i = 0; i < cookieList.length; i++) { if (cookieList[i].getName().equals(cookieName)) { retValue = URLDecoder.decode(cookieList[i].getValue(), encodeString); break; } } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return retValue; } /** * @Description: 設(shè)置Cookie的值 不設(shè)置生效時間默認瀏覽器關(guān)閉即失效,也不編碼 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue) { setCookie(request, response, cookieName, cookieValue, -1); } /** * @Description: 設(shè)置Cookie的值 在指定時間內(nèi)生效,但不編碼 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage) { setCookie(request, response, cookieName, cookieValue, cookieMaxage, false); } /** * @Description: 設(shè)置Cookie的值 不設(shè)置生效時間,但編碼 * 在服務(wù)器被創(chuàng)建,返回給客戶端,并且保存客戶端 * 如果設(shè)置了SETMAXAGE(int seconds),會把cookie保存在客戶端的硬盤中 * 如果沒有設(shè)置,會默認把cookie保存在瀏覽器的內(nèi)存中 * 一旦設(shè)置setPath():只能通過設(shè)置的路徑才能獲取到當前的cookie信息 */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, boolean isEncode) { setCookie(request, response, cookieName, cookieValue, -1, isEncode); } /** * @Description: 設(shè)置Cookie的值 在指定時間內(nèi)生效, 編碼參數(shù) */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, isEncode); } /** * @Description: 設(shè)置Cookie的值 在指定時間內(nèi)生效, 編碼參數(shù)(指定編碼) */ public static void setCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { doSetCookie(request, response, cookieName, cookieValue, cookieMaxage, encodeString); } /** * @Description: 刪除Cookie帶cookie域名 */ public static void deleteCookie(HttpServletRequest request, HttpServletResponse response, String cookieName) { doSetCookie(request, response, cookieName, null, -1, false); } /** * @Description: 設(shè)置Cookie的值,并使其在指定時間內(nèi)生效 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, boolean isEncode) { try { if (cookieValue == null) { cookieValue = ""; } else if (isEncode) { cookieValue = URLEncoder.encode(cookieValue, "utf-8"); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request) {// 設(shè)置域名的cookie String domainName = getDomainName(request); log.info("========== domainName: {} ==========", domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } } /** * @Description: 設(shè)置Cookie的值,并使其在指定時間內(nèi)生效 */ private static final void doSetCookie(HttpServletRequest request, HttpServletResponse response, String cookieName, String cookieValue, int cookieMaxage, String encodeString) { try { if (cookieValue == null) { cookieValue = ""; } else { cookieValue = URLEncoder.encode(cookieValue, encodeString); } Cookie cookie = new Cookie(cookieName, cookieValue); if (cookieMaxage > 0) cookie.setMaxAge(cookieMaxage); if (null != request) {// 設(shè)置域名的cookie String domainName = getDomainName(request); log.info("========== domainName: {} ==========", domainName); if (!"localhost".equals(domainName)) { cookie.setDomain(domainName); } } cookie.setPath("/"); response.addCookie(cookie); } catch (Exception e) { e.printStackTrace(); } } /** * @Description: 得到cookie的域名 */ private static final String getDomainName(HttpServletRequest request) { String domainName = null; String serverName = request.getRequestURL().toString(); if (serverName == null || serverName.equals("")) { domainName = ""; } else { serverName = serverName.toLowerCase(); serverName = serverName.substring(7); final int end = serverName.indexOf("/"); serverName = serverName.substring(0, end); if (serverName.indexOf(":") > 0) { String[] ary = serverName.split("\\:"); serverName = ary[0]; } final String[] domains = serverName.split("\\."); int len = domains.length; if (len > 3 && !isIp(serverName)) { // www.xxx.com.cn domainName = "." + domains[len - 3] + "." + domains[len - 2] + "." + domains[len - 1]; } else if (len <= 3 && len > 1) { // xxx.com or xxx.cn domainName = "." + domains[len - 2] + "." + domains[len - 1]; } else { domainName = serverName; } } return domainName; } public static String trimSpaces(String IP) {//去掉IP字符串前后所有的空格 while (IP.startsWith(" ")) { IP = IP.substring(1, IP.length()).trim(); } while (IP.endsWith(" ")) { IP = IP.substring(0, IP.length() - 1).trim(); } return IP; } public static boolean isIp(String IP) {//判斷是否是一個IP boolean b = false; IP = trimSpaces(IP); if (IP.matches("\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}")) { String s[] = IP.split("\\."); if (Integer.parseInt(s[0]) < 255) if (Integer.parseInt(s[1]) < 255) if (Integer.parseInt(s[2]) < 255) if (Integer.parseInt(s[3]) < 255) b = true; } return b; } }
3、UserServiceImpl
// 最初版本(session),但Session內(nèi)數(shù)據(jù)是服務(wù)器共用的 // 所有不讓名字重復(fù) String ticket=UUID.randomUUID().toString().replace("-",""); request.getSession().setAttribute(ticket,user); // 將生成的ticket給用戶,用戶如何驗證?使用cookie CookieUtils.setCookie(request,response,"ticket",ticket);
點擊登錄時,查看應(yīng)用程序,cookie內(nèi)有值
4、跳轉(zhuǎn)界面PathController
跳轉(zhuǎn)方法:
// 跳所有二級頁面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request){ String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket對應(yīng)的用戶,判斷是否有值 Object obj=request.getSession().getAttribute(ticket); if(obj==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; }
未登錄時訪問主界面失?。褐挥械卿洺晒蟛拍茉L問
二、全局session
根據(jù)以上操作完后,session仍然有問題,,session只能在當前某一臺服務(wù)器中,
需使用第三者完成數(shù)據(jù)共享,此處使用redis方式,將需要緩存的數(shù)據(jù)丟到緩存內(nèi)
1、導(dǎo)入依賴
此處全局session是spring session中的一個,spring session就是spring中的一個文件
<!--commons-pool2--> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-pool2</artifactId> </dependency> <!--spring-session將session借助于第三方存儲(redis/mongodb等等),默認redis--> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session-data-redis</artifactId> </dependency>
2、配置yml文件redis
spring:
redis:
host: 47.100.191.44
password: xiaoli_redis
database: 0
port: 6379
3、開啟虛擬機
現(xiàn)在開啟虛擬機
三、自定義redis實現(xiàn)功能
完成全局session的作用
1、新建RedisConfig文件
用于操作redis
package com.example.seckill.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisConnectionFactory; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer; import org.springframework.data.redis.serializer.StringRedisSerializer; @Configuration public class RedisConfig { public RedisTemplate redisTemplate(RedisConnectionFactory factory){ // 新建一個 RedisTemplate redisTemplate=new RedisTemplate(); //設(shè)置一下使用連接工廠 redisTemplate.setConnectionFactory(factory); // 額外設(shè)置 // 將key序列化操作,轉(zhuǎn)化為string redisTemplate.setKeySerializer(new StringRedisSerializer()); // value被轉(zhuǎn)化為json redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()); // 額外設(shè)置(hash 就是 map集合) redisTemplate.setHashKeySerializer(new StringRedisSerializer()); redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer()); // 讓設(shè)置生效 redisTemplate.afterPropertiesSet(); return redisTemplate; } }
2、實現(xiàn)全局session
①、寫個幫助類,用于調(diào)用
IRedisService接口:
package com.example.seckill.service; import com.example.seckill.pojo.User; public interface IRedisService { void putUserByTicket(String ticket, User user); User getUserByTicket(String ticket); }
實現(xiàn)接口:
package com.example.seckill.service.impl; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.util.concurrent.TimeUnit; @Service public class RedisServiceImpl implements IRedisService { @Autowired private RedisTemplate redisTemplate; @Override public void putUserByTicket(String ticket, User user) { redisTemplate.opsForValue().set("user:"+ticket,user,2L, TimeUnit.HOURS); } @Override public User getUserByTicket(String ticket) { Object obj=redisTemplate.opsForValue().get("user:"+ticket); if(obj==null || !(obj instanceof User)){ return null; } return (User)obj; } }
②、redisService到緩存中拿元素
@Autowired private IRedisService redisService; // 跳所有二級頁面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, HttpServletRequest request){ // 獲取用戶的ticket String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket對應(yīng)的用戶,判斷是否有值 // Object obj=request.getSession().getAttribute(ticket); // 去緩存中拿元素 User user=redisService.getUserByTicket(ticket); if(user==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return dir+"/"+path; }
③、放到緩存中
UserServiceImpl:
// 放到緩存中去 redisService.putUserByTicket(ticket,user);
package com.example.seckill.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.mapper.UserMapper; import com.example.seckill.service.IRedisService; import com.example.seckill.service.IUserService; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.MD5Utils; import com.example.seckill.util.ValidatorUtils; import com.example.seckill.util.response.ResponseResult; import com.example.seckill.util.response.ResponseResultCode; import com.example.seckill.vo.UserVo; import com.sun.deploy.nativesandbox.comm.Request; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestBody; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.websocket.Session; import java.util.Date; import java.util.UUID; /** * <p> * 用戶信息表 服務(wù)實現(xiàn)類 * </p> * * @author lv * @since 2022-03-15 */ @Service public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService { @Autowired private IRedisService redisService; @Override public ResponseResult<?> findByAccount(UserVo userVo, HttpServletRequest request, HttpServletResponse response) { // 先判斷信息是否符合(賬號是否是手機號碼,密碼是不是空) // if(!ValidatorUtils.isMobile(userVo.getMobile())){ // throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_MOBLIE); // } // if(StringUtils.isBlank(userVo.getPassword())){ // throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); // } // 再去數(shù)據(jù)庫查出對應(yīng)的用戶(mobile) User user=this.getOne(new QueryWrapper<User>().eq("id",userVo.getMobile())); if(user==null){ throw new BusinessException(ResponseResultCode.USER_ACCOUNT_NOT_FIND); } // 比較密碼 // 二重加密(前端->后端,后端->數(shù)據(jù)庫) String salt=user.getSalt(); // 將前臺的加密密碼和后端的鹽再次進行加密 String newPassword=MD5Utils.formPassToDbPass(userVo.getPassword(),salt); if(!newPassword.equals(user.getPassword())){ throw new BusinessException(ResponseResultCode.USER_PASSWORD_NOT_MATCH); } // 修改最后的登錄時間 this.update(new UpdateWrapper<User>().eq("id",userVo.getMobile()).set("last_login_date",new Date()).setSql("login_count=login_count+1")); // 最初版本(session),但Session內(nèi)數(shù)據(jù)是服務(wù)器共用的 // 所有不讓名字重復(fù) String ticket=UUID.randomUUID().toString().replace("-",""); // 放到全局或者當服務(wù)器的session // request.getSession().setAttribute(ticket,user); // 放到緩存中去 redisService.putUserByTicket(ticket,user); // 將生成的ticket給用戶,用戶如何驗證?使用cookie CookieUtils.setCookie(request,response,"ticket",ticket); return ResponseResult.success(); } }
④、LocalDateTime無法構(gòu)造實例
LocalDateTime是Java8推薦使用的時間類,我此處無法轉(zhuǎn)化,需要去配置解析器,我就改變實體類中的LocalDateTime改為時間戳Timestamp
四、使用參數(shù)解析器
在controller類中方法內(nèi)加上User實體類參數(shù),查詢出用戶,自動將信息賦值
1、新建WebConfig文件
package com.example.seckill.config; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import java.util.List; @Configuration //打開mvc的功能 @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Autowired private UserArgumentResolvers userArgumentResolvers; @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(userArgumentResolvers); } // 使靜態(tài)資源仍然可使用 @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { //靜態(tài)資源訪問映射 映射路徑 -> 本地資源路徑 registry.addResourceHandler("/static/**") .addResourceLocations("classpath:/static/"); } }
2、定義參數(shù)解析器
UserArgumentResolvers :
package com.example.seckill.config; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.response.ResponseResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.stereotype.Component; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; @Component public class UserArgumentResolvers implements HandlerMethodArgumentResolver { @Autowired private IRedisService redisService; // supportsParameter方法判斷參數(shù)是否需要解析,決定了resolveArgument方法是否運行 @Override public boolean supportsParameter(MethodParameter methodParameter) { return methodParameter.getParameterType() == User.class; } // 解析參數(shù) @Override public Object resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer, NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception { // 參數(shù)解析User,因為很多地方需要做登錄驗證 HttpServletRequest request=(HttpServletRequest) nativeWebRequest.getNativeRequest(); // 獲取用戶的ticket String ticket= CookieUtils.getCookieValue(request,"ticket"); if(ticket==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } // 去session中取到ticket對應(yīng)的用戶,判斷是否有值 // Object obj=request.getSession().getAttribute(ticket); // 去緩存中拿元素 User user=redisService.getUserByTicket(ticket); if(user==null){ throw new BusinessException(ResponseResultCode.TICKET_ERROR); } return user;//經(jīng)過了參數(shù)解析后,參數(shù)會變成你這個地方返回的值 } }
3、PathController
package com.example.seckill.controller; import com.example.seckill.exception.BusinessException; import com.example.seckill.pojo.User; import com.example.seckill.service.IRedisService; import com.example.seckill.util.CookieUtils; import com.example.seckill.util.response.ResponseResultCode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import javax.servlet.http.HttpServletRequest; @Controller public class PathController { // 登錄跳首頁 @RequestMapping("/") public String toPath(){ return "login"; } // 跳所有二級頁面 @RequestMapping("/{dir}/{path}") public String toPath(@PathVariable("dir") String dir, @PathVariable("path") String path, User user){ return dir+"/"+path; } }
4、訪問主界面得到相關(guān)信息:
本期內(nèi)容結(jié)束~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
到此這篇關(guān)于超詳細講解Java秒殺項目用戶驗證模塊的實現(xiàn)的文章就介紹到這了,更多相關(guān)Java 用戶驗證內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot實現(xiàn)PDF添加水印的三種方法
本文主要介紹了SpringBoot實現(xiàn)PDF添加水印的三種方法,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07SpringBoot整合Security實現(xiàn)權(quán)限控制框架(案例詳解)
Spring Security是一個能夠為基于Spring的企業(yè)應(yīng)用系統(tǒng)提供聲明式的安全訪問控制解決方案的安全框,是一個重量級的安全管理框架,本文給大家介紹的非常詳細,需要的朋友參考下吧2021-08-08Java并行執(zhí)行任務(wù)的幾種方案小結(jié)
這篇文章主要介紹了Java并行執(zhí)行任務(wù)的幾種方案小結(jié),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Javaweb mybatis接口開發(fā)實現(xiàn)過程詳解
這篇文章主要介紹了Javaweb mybatis接口開發(fā)實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-07-07@Autowired注解注入的xxxMapper報錯問題及解決
這篇文章主要介紹了@Autowired注解注入的xxxMapper報錯問題及解決,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11將Swagger2文檔導(dǎo)出為HTML或markdown等格式離線閱讀解析
這篇文章主要介紹了將Swagger2文檔導(dǎo)出為HTML或markdown等格式離線閱讀,本文給大家介紹的非常詳細,具有一定的參考借鑒價值,需要的朋友可以參考下2019-11-11