您好,登錄后才能下訂單哦!
這篇文章主要介紹“Redis+SpringBoot案例分析”,在日常操作中,相信很多人在Redis+SpringBoot案例分析問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Redis+SpringBoot案例分析”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
前端技術棧:Vue-Cli
前端軟體:WebStorm 2020.3
前端樣式: Bootstrap
后端技術棧:SpringBoot
后端軟體:IntelliJ IEDA2019
JavaJDK:1.8
服務器:阿里云Centos 7
其他:MyBatis,Redis,MySql,Docker,Shiro
項目源碼:shoppingProject01_pub : version6.0
項目參考:Project05;不良人_Vue-Cli;不良人_Redis;不良人_Axios;尚硅谷_Redis
項目功能:
1)郵箱注冊登錄:
用戶應用郵箱注冊點擊提交后,網站會給用戶發送郵件附帶激活碼鏈接,用戶點擊鏈接實現賬號激活傳送門。
2)短信注冊登錄:
用戶應用手機號注冊時,點擊“獲取驗證碼”按鈕,手機會接收到網站發送的短信附帶驗證碼。基于Redis實現了驗證碼有效期5分鐘,每個手機號只能獲取三次短信驗證碼傳送門。
3)alipay支付:
通過下載Android版支付寶沙箱app用戶可通過alipay掃碼購買網站上的商品,后臺MySql會記錄該訂單行為傳送門,網頁展示如圖1所示。
4)用戶小分級:
當用戶掃碼購買年度VIP會員后,購買網站上的商品一律半價,后臺MySql記錄用戶角色的變更。
5)用戶積分排行榜:
用戶購買商品會增加自己的積分,網頁展示如圖2所示。
項目中遇到的大坑:
1)郵件發送功能本地測試通過,服務器端測試Bug頻出,解決辦法。
2)項目在服務器部署后,無法連接服務器上的Redis。解決辦法:(1)將Redis在服務器部署而非Docker;(2)將Redis端口改為7000;(3)防火墻active狀態下放行服務器和阿里云的7000端口;(4)修改Redis.conf文件。
3)git上傳本地源碼到gitee,誤操作導致本地源碼被gitee上的舊代碼覆蓋,第二天才發現。解決辦法:因為在服務器上留有源碼的jar包,通過反編譯工具jd_gui救回半條命。另外git上傳文件參考傳送門。
1 Vue-Cli模塊說明:
1.1 Vue-Cli的概述:
1)以前后端分離、單頁面web應用(SPA)為特點,Vue-Cli可以創建一個Vue項目,這個項目存在腳手架規范。Vue-Cli的優勢如下:
(1) 基于腳手架規范的開發會變得很靈活。
(2) Vue-Cli基于webpack構建并帶有合理的默認配置,打包工具webpack能夠聚合單頁面和各種開發組件。
(3) Vue-Cli是一個豐富的官方插件集合,繼承了前端生態中最好的工具。
2)安裝過程:
(1) 安裝WebStorm(用于開發),安裝node.js,安裝vue-cli,安裝axios(用于發起跨域請求),引入bootstrap樣式。
3)部署過程:
npm run build # 在WebStorm終端執行,生成dist文件夾 docker pull nginx:1.19.10 # 不建議Vue-cli項目部署到tomcat,因為tomcat屬于動態服務器,啟動需要java環境,是為了解析動態語言jsp的;像純靜態的就部署到靜態服務器nginx上。 mkdir html # 為了做docker容器內外的數據卷映射 mv dist/ html/ docker run -p 80:80 --name nginx01 -d -v /root/html/dist/:/usr/share/nginx/html nginx:1.19.10 # 數據卷映射 # 此時可訪問 http://120.79.133.235:80/index.html
4)Vue-Cli開發要點:
(1)在WebStorm中,開發過程主要面向src文件,如圖3所示:
[1] 首先掌握路由(router)和組件(components[公有組件],views[私有組件]),組件就是一個個“頁面”,組件建立后要向路由進行注冊; [2] asserts封裝了bootstrap樣式,并在main.js中被導入; [3] 為了發送跨域請求,在utils中封裝了axios實例,代碼如下:
import axios from 'axios' // 創建默認實例 const instance = axios.create({ baseURL: 'http://120.79.133.235:8989/eb', // timeout: 10000, }); // 請求攔截器 instance.interceptors.request.use(config=>{ console.log("請求攔截器"); return config; }) // 響應攔截器 instance.interceptors.response.use(response=>{ console.log("響應攔截器"); return response; }, err=>{ console.log("響應出現錯誤時進入的攔截器"); }); // 暴露instance實例對象 export default instance;
在各組件中,對于后端的get和post請求方法如下:
// Get請求 // 向后端接口發當前頁碼,獲取當前頁面的商品List instance.get("/item/findAllItem?page="+this.page).then(res=>{ that.items = res.data.items; that.totalPage = res.data.totalPage; that.page = res.data.page; }); // Post請求 // 向后端接口發送當前商品id和用戶id,獲取商品購買狀態 instance.post("/order/alipay/callback",{itemId:this.itemid,userId:this.user.id}).then(res=>{ if ( res.data.code == 20000 ) { alert("提示:您已購買該商品"); } else { alert("提示:您未購買該商品"); } }); }
[4] 組件之間的跳轉和傳值方法如下:
// 跳轉到MailReg組件 this.$router.push({name:"MailReg"}); // 跳轉到item組件,并傳遞當前商品的id this.$router.push({path:"/item",query:{ItemId:myid}}); // item組件接收方法: this.itemid = this.$route.query.ItemId; // 另外不同組件可以依據token獲取登錄用戶信息,需要用到redis,詳見下文
2 用戶積分排行榜模塊說明:
1.1 Reids概述:
1) Redis是一種基于內存的數據存儲NoSql;
2) Redis支持豐富的數據類型(String, List, Set, ZSet, Hash);
3) Redis有兩種持久化方法: (1)快照(snapshot)存儲,也叫rdb持久化,保存當前時刻的數據狀態;(2) AOF(append only file)存儲,將所有redis的寫命令記錄到日志文件中,Redis支持持久化間隔最快也是一秒,所以它是事務不安全的,即是可能丟失數據的。
4)Redis的應用場景:
(1) 利用Redis字符串完成項目中手機驗證碼存儲的實現。------本項目采用
(2) 利用Redis字符串類型完成具有時效性的業務功能,如訂單還有40分鐘即將關閉。
(3) 利用Redis實現分布式集群系統中的Session共享。
(4) 利用Redis的ZSet數據類型(可排序set類型:元素+分數)實現排行榜功能。 ------本項目采用
(5) 利用Redis完成分布式緩存。 ------本項目實現MySql中數據的緩存
(6) 利用Redis存儲認證之后的token信息。 ------非常方便,本項目采用。
(7) 利用Redis解決分布式集群系統中分布式鎖問題。
1.2 基于Redis實現前端組件從后端獲取用戶信息:
Step1:前端Login.vue組件中用戶輸入登錄信息提交的接口如下:
// 這里調用了后端/user/login接口,獲取當前登錄用戶的token,存入Session的localStorage中,在后續網頁瀏覽過程中可隨時調取這個token instance.post("/user/login",this.user).then(res=>{ if ( res.data.state ) { alert(res.data.msg+",點擊確定進入主頁"); // 前端存儲token信息 localStorage.setItem("token",res.data.token); that.$router.push({path:"/itemList"}); } else { alert(res.data.msg); that.user = {}; } });
Step2:后端/user/login接口實現如下:
// Controller層 @PostMapping("login") public Map<String, Object> loginAccount(@RequestBody User user, HttpSession session) { return userService.loginAccount(user, session); } // Service層 // 情況3:查詢到一個用戶時 // 獲取主體對象 try { Subject subject = SecurityUtils.getSubject(); subject.login(new UsernamePasswordToken(user.getName(), user.getPassword())); User userDB = userListDB.get(0); String token = session.getId(); if (userDB.getScore() == null) { userDB.setScore(0.0); userDAO.updateUserScore(userDB); } redisTemplate.opsForValue().set("TOKEN_" + token, userDB, 30, TimeUnit.MINUTES); redisTemplate.opsForZSet().add("userRank", userDB, userDB.getScore()); map.put("token", token); map.put("state",true); map.put("msg","登錄成功"); return map; ...
Redis整合SpringBoot有兩種Template,即RedisTemplate和StringRedisTemplate。其中StringRedisTemplate是RedisTemplate的子類,兩個方法基本一致,不同之處在于操作的數據類型不同,RedisTemplate中的兩個泛型都是Object,意味著存儲的key和value都可以是一個對象,而StringRedisTemplate的兩個泛型都是String,意味著StringRedisTemplate的key和value都只能是字符串。
在Step2中,我將token和數據庫中的用戶信息userDB綁定在一起存入了Redis中,后續前端組件獲取登錄用戶信息的代碼如下:
// 從localStorage獲取token let token = localStorage.getItem("token"); let that = this; // 發送axios請求,根據token獲取用戶信息 instance.get("/user/token?token="+token).then(res=>{ that.user = res.data; console.log(that.user); })
后端/user/token的接口如下:
@GetMapping({"token"}) public User findUser(String token) { System.out.println("接收的token信息:" + token); return (User)redisTemplate.opsForValue().get("TOKEN_" + token); }
Step3:用戶退出登錄時,應消除瀏覽器中對應的token,后端接口代碼如下:
// 退出登錄 @DeleteMapping({"logout"}) public Map<String, Object> logout(String token) { Map<String, Object> map = new HashMap<>(); try { redisTemplate.delete("TOKEN_" + token); Subject subject = SecurityUtils.getSubject(); subject.logout(); map.put("state", true); map.put("msg", "提示:退出賬戶成功"); return map; } catch (Exception e) { e.printStackTrace(); map.put("state", false); map.put("msg", "提示:退出賬戶失敗"); return map; } }
1.3 基于Redis的用戶積分排行榜實現:
MySql中的用戶信息如圖4所示:
Redis中的UserRank如圖5所示:
Step1:當用戶登錄時,他的首要任務是接入UserRank對應的信息,后端代碼如下:
if (userDB.getScore() == null) { userDB.setScore(0.0); userDAO.updateUserScore(userDB); } redisTemplate.opsForValue().set("TOKEN_" + token, userDB, 30, TimeUnit.MINUTES); redisTemplate.opsForZSet().add("userRank", userDB, userDB.getScore());
userDB是數據庫中當前登錄用戶的信息(一定是有的,你注冊了,對吧?),若用戶首次登錄我將他的分數在數據庫設置為0.0,之后我在Redis的ZSet中加入這個用戶,你知道,Set集合不會存儲重復key值的元素,因此不會同一個用戶出現在UserRank中兩次。兩個template完成了token綁定User,User綁定UserRank中Score的過程,之后的分數更新過程會反復使用這兩個template實現。
Step2:當用戶信息更新時,相應的與用戶信息有關的兩個template都要發生變化,代碼如下:
// key值序列化 redisTemplate.setKeySerializer(new StringRedisSerializer()); // 由當前用戶的token獲取當前用戶的信息 User firstUser = (User)redisTemplate.opsForValue().get("TOKEN_" + token); // 刪除zSet中的當前用戶 redisTemplate.opsForZSet().remove("userRank", firstUser); // 產生新的當前用戶(昵稱改變) List<User> userListDB = this.userDAO.selectUserByName(user.getName()); User secondUser = userListDB.get(0); // 更新token中當前用戶的信息 redisTemplate.opsForValue().set("TOKEN_" + token, secondUser, 30, TimeUnit.MINUTES); // 產生zSet中的當前用戶 redisTemplate.opsForZSet().add("userRank", secondUser, secondUser.getScore());
Step3:當用戶掃碼支付時,首次進入的后端controller如下:
// 支付單件商品 @GetMapping("payForItem") public byte[] alipay(String itemid,String userid, String token) { this.token = token; log.info("itemid=====>"+itemid); log.info("userid=====>"+userid); PayVo payVo = new PayVo(); payVo.setItemId(itemid); payVo.setUserId(userid); System.out.println(payVo.getUserId()); return alipayService.alipay(payVo); }
在alipayService有一個小型用戶分級,即vip用戶購物價格減半:
// 1:支付的用戶 String userId = payVo.getUserId(); // my 1: 依據用戶id查詢用戶 User user = userService.selectUserById(Integer.valueOf(userId)); // 依據商品id查詢商品 Item item = itemService.getItemById(payVo.getItemId()); // my 1: 依據用戶id查詢用戶 if ( item == null ) return null; // 2: 支付金額 String tempMoney = item.getPrice().toString(); String money = ""; if ( user.getRole().equals("normal") ) { money = tempMoney; } if ( user.getRole().equals("vip") ) { Double tempMoney2 = Double.valueOf(tempMoney)*0.5; money = String.valueOf(tempMoney2); }
在payForItem相同文件下,調用了payCommonService,在這里會實現用戶積分更新和用戶等級更新:
payCommonService.payUserPublic(bodyJsonObject, userId, user.getName(), orderNumber, tradeno, "alipay", this.token);
將"VIP"這件商品的id設置為“666”,當用戶購買該商品時,當前用戶更新過程如下:
if ( itemId.equals("666") ) { int myuserId = Integer.valueOf(userId); User userDB = userService.selectUserById(myuserId); // key值序列化 this.redisTemplate.setKeySerializer(new StringRedisSerializer()); // 由當前token獲取當前用戶信息 User firstUser = (User)redisTemplate.opsForValue().get("TOKEN_" + token); // 由當前用戶信息刪除當前用戶zSet redisTemplate.opsForZSet().remove("userRank", firstUser); // 更新當前用戶信息身份 userDB.setRole("vip"); // 更新當前用戶新身份的分數 userService.updateUserRole(userDB); List<User> userListDB = this.userDAO.selectUserByName(userDB.getName()); // 獲取當前新身份用戶的完整信息 User secondUser = userListDB.get(0); // 更新當前token對應的當前用戶 redisTemplate.opsForValue().set("TOKEN_" + token, secondUser, 30, TimeUnit.MINUTES); // 設置當前用戶的zSet redisTemplate.opsForZSet().add("userRank", secondUser, secondUser.getScore().doubleValue()); }
當前用戶積分更新過程如下:
// 更新當前用戶的積分 double tempScore = Double.valueOf(orderDetail.getPrice()) * 0.3; String key1 = "TOKEN_" + token; // 由當前token獲取當前用戶 User user = (User)redisTemplate.opsForValue().get(key1); // 更新當前用戶的zSet分數 redisTemplate.opsForZSet().incrementScore("userRank", user, tempScore); // 獲取當前用戶的zSet分數 double newScore = redisTemplate.opsForZSet().score("userRank", user); // 刪除當前用戶的zSet(因為要更新當前用戶的信息,將當前用戶在數據庫中的分數進行同步) redisTemplate.opsForZSet().remove("userRank", new Object[] { user }); user.setScore(newScore); userDAO.updateUserScore(user); // 更新token對應的當前用戶的信息 redisTemplate.opsForValue().set(key1, user); // 新增當前用戶的zSet redisTemplate.opsForZSet().add("userRank", user, newScore);
到此,關于“Redis+SpringBoot案例分析”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。