SpringBoot | 大新聞項目后端(redis優化登錄)

該項目的前篇內容的使用jwt令牌實現登錄認證,使用Md5加密實現注冊,在上一篇:http://t.csdnimg.cn/vn3rB

該篇主要內容:redis優化登錄和ThreadLocal提供線程局部變量,以及該大新聞項目的主要代碼。

redis優化登錄

其實在前面項目中的登錄,有一個令牌機制的bug,就是在你修改密碼后,原來密碼的登錄進去的token,還是可以使用的,舊令牌并沒有失效,這會造成用戶在修改密碼后,但是原來密碼登錄進去的頁面仍然可以正常訪問,有很大的安全隱患。

所以使用redis來解決這個問題

令牌主動失效機制

  • 登錄成功后,給瀏覽器響應令牌的同時,把該令牌存儲到 redis 中
  • LoginInterceptor 攔截器中,需要驗證瀏覽器攜帶的令牌,并同時需要獲取到 redis 中存儲的與之相同的令牌
  • 當用戶修改密碼成功后,刪除 redis 中存儲的舊令牌

redis的測試代碼:

package com.xu;import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;import java.util.concurrent.TimeUnit;@SpringBootTest //如果在測試類上添加了這個注釋,那么將來單元測試方法執行之前,會先初始化Spring容器
public class RedisTest {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Testpublic void testSet(){//讓redis中存儲一個鍵值對 StringRedisTemplateValueOperations<String, String> operations = stringRedisTemplate.opsForValue();operations.set("username","zhangsan");operations.set("id","1",15, TimeUnit.SECONDS);}@Testpublic void testGet(){ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();System.out.println(operations.get("username"));}
}

運行效果:

其實里面的id是設置了失效的時間,所以在超出時間的范圍外,則get不到id的值。

在整個項目的代碼中,redis的使用也是類似:

UserController部分代碼:

@Autowired
private UserService userService;
@Autowired
private StringRedisTemplate stringRedisTemplate;@PostMapping("login")public Result<String> login(@Pattern(regexp = "^\\S{5,16}$")String username, @Pattern(regexp = "^\\S{5,16}$")String password){//根據用戶名查詢用戶User loginUser=userService.findByUserName(username);//判斷用戶是否存在if(loginUser==null){return Result.error("用戶名錯誤");}//判斷密碼是否正確if(Md5Util.getMD5String(password).equals(loginUser.getPassword())){//登錄成功Map<String,Object> claims=new HashMap<>();claims.put("id",loginUser.getId());claims.put("username",loginUser.getUsername());String token= JwtUtil.genToken(claims);//把token存儲到redis里面ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();operations.set(token,token,1, TimeUnit.HOURS);return Result.success(token);}return Result.error("密碼錯誤");}@PatchMapping("updatePwd")public Result updatePwd(@RequestBody Map<String,String> params,@RequestHeader("Authorization") String token){//校驗參數String oldPwd = params.get("old_pwd");String newPwd = params.get("new_pwd");String rePwd = params.get("re_pwd");if(!StringUtils.hasLength(oldPwd) || !StringUtils.hasLength(newPwd) || !StringUtils.hasLength(rePwd)){return Result.error("缺失必要的參數");}//原密碼是否正確//調用userService根據用戶名拿到原密碼,再和old_pwd比對Map<String,Object> map=ThreadLocalUtil.get();String username=(String) map.get("username");User loginUser=userService.findByUserName(username);if(!loginUser.getPassword().equals(Md5Util.getMD5String(oldPwd))){return Result.error("原密碼填寫不正確");}//newPwd和rePwd是否一樣if(!rePwd.equals(newPwd)){return Result.error("兩次填寫的新密碼不一樣");}//調用service完成密碼更新userService.updatePwd(newPwd);//刪除redis中對應的tokenValueOperations<String, String> operations = stringRedisTemplate.opsForValue();operations.getOperations().delete(token);return Result.success();}

LoginInterceptor代碼:

package com.xu.interceptors;import com.xu.pojo.Result;
import com.xu.utils.JwtUtil;
import com.xu.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Map;@Component
public class LoginInterceptor implements HandlerInterceptor {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//令牌驗證String token=request.getHeader("Authorization");//驗證tokentry {//從redis中獲取相同的tokenValueOperations<String, String> operations = stringRedisTemplate.opsForValue();String redisToken = operations.get(token);if(redisToken==null){//token已經失效throw new RuntimeException();}Map<String,Object> claims= JwtUtil.parseToken(token);//把業務數據存儲到ThreadLocal中ThreadLocalUtil.set(claims);//放行return true;}catch (Exception e){response.setStatus(401);//不放行return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//清空ThreadLocal中的數據ThreadLocalUtil.remove();}
}

ThreadLocal

適用場景ThreadLocal?適用于每個線程需要獨立的實例或數據的場景,不適用于需要線程間共享數據的場景。

  • 用來存取數據 : set()/get()
  • 使用 ThreadLocal 存儲的數據 , 線程安全
  • 用完記得調用 remove 方法釋放

而在本項目中文章分類和文章管理都是通過用戶去操作的,所以適合用ThreadLocal 存儲數據。

測試代碼:

package com.xu;import org.junit.jupiter.api.Test;public class ThreadLocalSetAndGet {@Testpublic void testThreadLocalSetAndGet(){//提供一個ThreadLocal對象ThreadLocal tl=new ThreadLocal();//開啟兩個線程new Thread(()->{tl.set("cookie");System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());},"pig").start();new Thread(()->{tl.set("offer");System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());System.out.println(Thread.currentThread().getName()+":"+tl.get());},"lucky").start();}
}

運行結果:

ThreadLocalUtil代碼:

package com.xu.utils;import java.util.HashMap;
import java.util.Map;/*** ThreadLocal 工具類*/
@SuppressWarnings("all")
public class ThreadLocalUtil {//提供ThreadLocal對象,private static final ThreadLocal THREAD_LOCAL = new ThreadLocal();//根據鍵獲取值public static <T> T get(){return (T) THREAD_LOCAL.get();}//存儲鍵值對public static void set(Object value){THREAD_LOCAL.set(value);}//清除ThreadLocal 防止內存泄漏public static void remove(){THREAD_LOCAL.remove();}
}

ArticleServiceImpl部分使用到了ThreadLocal的代碼:

package com.xu.service.impl;import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.xu.mapper.ArticleMapper;
import com.xu.pojo.Article;
import com.xu.pojo.PageBean;
import com.xu.service.ArticleService;
import com.xu.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;@Service
public class ArticleServiceImpl implements ArticleService {@Autowiredprivate ArticleMapper articleMapper;@Overridepublic void add(Article article) {//補充屬性值article.setCreateTime(LocalDateTime.now());article.setUpdateTime(LocalDateTime.now());Map<String,Object> map= ThreadLocalUtil.get();Integer userId=(Integer) map.get("id");article.setCreateUser(userId);articleMapper.add(article);}@Overridepublic PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {//創建PageBean對象PageBean<Article> pb=new PageBean<>();//開啟分頁查詢 PageHelperPageHelper.startPage(pageNum,pageSize);//調用mapperMap<String,Object> map=ThreadLocalUtil.get();Integer userId=(Integer)map.get("id");List<Article> as= articleMapper.list(userId,categoryId,state);Page<Article> p=(Page<Article>) as;//把數據填充到PageBean對象pb.setTotal(p.getTotal());pb.setItems(p.getResult());return pb;}
}

分組校驗

把校驗項進行歸類分組,在完成不同的功能的時候,校驗指定組中的校驗項

  • 1. 定義分組
  • 2. 定義校驗項時指定歸屬的分組
  • 3. 校驗時指定要校驗的分組

1. 如何定義分組?

在實體類內部定義接口
2. 如何對校驗項分組?

通過 groups 屬性指定
3. 校驗時如何指定分組?

給 @Validated 注解的 value 屬性賦值
4. 校驗項默認屬于什么組 ?

Default

在本項目中,category里面的新增和更新方法,需要攜帶的校驗參數是不一樣,比如:新增的id是自增的,更新的id是要修改category對應的id(那么更新就必須攜帶id參數),所以在實體類category里面可以使用groups進行分組

category代碼:

package com.xu.pojo;import com.fasterxml.jackson.annotation.JsonFormat;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.groups.Default;
import lombok.Data;import java.time.LocalDateTime;
@Data
public class Category {@NotNull(groups = Update.class)private Integer id;//主鍵ID@NotEmptyprivate String categoryName;//分類名稱@NotEmptyprivate String categoryAlias;//分類別名private Integer createUser;//創建人ID@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime createTime;//創建時間@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")private LocalDateTime updateTime;//更新時間//如果說某個校驗項沒有指定分組,默認屬于Default分組//分組之間可以繼承, A extends B  那么A中擁有B中所有的校驗項public interface Add extends Default {}public interface Update extends Default {}
}

CategoryController部分方法代碼:

package com.xu.controller;import com.xu.pojo.Category;
import com.xu.pojo.Result;
import com.xu.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/category")
public class CategoryController {@Autowiredprivate CategoryService categoryService;@PostMappingpublic Result add(@RequestBody @Validated(Category.Add.class) Category category){categoryService.add(category);return Result.success();}@PutMappingpublic Result update(@RequestBody @Validated(Category.Update.class) Category category) {categoryService.update(category);return Result.success();}
}

使用上面這些,需要在pom.xml里面添加:


<!--      validation依賴--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-validation</artifactId></dependency><!--      redis坐標--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency>

大新聞項目的重要業務有文件上傳,分頁查詢以及文章管理(增刪改查)等,

以下是一些難點的業務:

文件上傳:

文件上傳里面使用了UUID是為了防止相同文件名的,被覆蓋,所以就使用UUID生成隨機的文件名

分頁查詢:

ArticleController部分代碼:

    @GetMappingpublic Result<PageBean<Article>> list(Integer pageNum,Integer pageSize,@RequestParam(required = false) Integer categoryId,@RequestParam(required = false) String state){PageBean<Article> pb=articleService.list(pageNum,pageSize,categoryId,state);return Result.success(pb);}

ArticleServiceImpl的代碼:

package com.xu.service.impl;import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.xu.mapper.ArticleMapper;
import com.xu.pojo.Article;
import com.xu.pojo.PageBean;
import com.xu.service.ArticleService;
import com.xu.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;@Service
public class ArticleServiceImpl implements ArticleService {@Autowiredprivate ArticleMapper articleMapper;@Overridepublic void add(Article article) {//補充屬性值article.setCreateTime(LocalDateTime.now());article.setUpdateTime(LocalDateTime.now());Map<String,Object> map= ThreadLocalUtil.get();Integer userId=(Integer) map.get("id");article.setCreateUser(userId);articleMapper.add(article);}@Overridepublic PageBean<Article> list(Integer pageNum, Integer pageSize, Integer categoryId, String state) {//創建PageBean對象PageBean<Article> pb=new PageBean<>();//開啟分頁查詢 PageHelperPageHelper.startPage(pageNum,pageSize);//調用mapperMap<String,Object> map=ThreadLocalUtil.get();Integer userId=(Integer)map.get("id");List<Article> as= articleMapper.list(userId,categoryId,state);Page<Article> p=(Page<Article>) as;//把數據填充到PageBean對象pb.setTotal(p.getTotal());pb.setItems(p.getResult());return pb;}
}

ArticleMapper代碼:

package com.xu.mapper;import com.xu.pojo.Article;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;import java.util.List;@Mapper
public interface ArticleMapper {//新增@Insert("insert into article(title,content,cover_img,state,category_id,create_user,create_time,update_time) "+"values(#{title},#{content},#{coverImg},#{state},#{categoryId},#{createUser},#{createTime},#{updateTime})")void add(Article article);List<Article> list(Integer userId, Integer categoryId, String state);
}

這里使用到了動態sql,要保證在resource目錄下的路徑映射和mapper的一樣:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.xu.mapper.ArticleMapper">
<!--    動態sql--><select id="list" resultType="com.xu.pojo.Article">select * from article<where><if test="categoryId!=null">category_id=#{categoryId}</if><if test="state!=null">and state=#{state}</if>and create_user=#{userId}</where></select>
</mapper>

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/41124.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/41124.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/41124.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

macOS版ChatGPT更新:修復AI對話純文本存儲問題

貓頭虎 &#x1f42f; 建聯貓頭虎&#xff0c;商務合作&#xff0c;產品評測&#xff0c;產品推廣&#xff0c;個人自媒體創作&#xff0c;超級個體&#xff0c;漲粉秘籍&#xff0c;一起探索編程世界的無限可能&#xff01; macOS版ChatGPT更新&#xff1a;修復AI對話純文本…

HOW - React Router v6.x Feature 實踐(react-router-dom)

目錄 基本特性ranked routes matchingactive linksNavLinkuseMatch relative links1. 相對路徑的使用2. 嵌套路由的增強行為3. 優勢和注意事項4. . 和 ..5. 總結 data loadingloading or changing data and redirectpending navigation uiskeleton ui with suspensedata mutati…

JAVA高級進階11多線程

第十一天、多線程 線程安全問題 線程安全問題 多線程給我們帶來了很大性能上的提升,但是也可能引發線程安全問題 線程安全問題指的是當個多線程同時操作同一個共享資源的時候,可能會出現的操作結果不符預期問題 線程同步方案 認識線程同步 線程同步 線程同步就是讓多個線…

內網滲透學習-殺入內網

1、靶機上線cs 我們已經拿到了win7的shell&#xff0c;執行whoami&#xff0c;發現win7是administrator權限&#xff0c;且在域中 執行ipconfig發現了win7存在內網網段192.168.52.0/24 kali開啟cs服務端 客戶端啟動cs 先在cs中創建一個監聽器 接著用cs生成后門&#xff0c;記…

Mysql 的第二次作業

一、數據庫 1、登陸數據庫 2、創建數據庫zoo 3、修改數據庫zoo字符集為gbk 4、選擇當前數據庫為zoo 5、查看創建數據庫zoo信息 6、刪除數據庫zoo 1&#xff09;登陸數據庫。 打開命令行&#xff0c;輸入登陸用戶名和密碼。 mysql -uroot -p123456 ? 2&#xff09;切換數據庫…

菜雞的原地踏步史(???)

leetcode啟動&#xff01;(╯‵□′)╯︵┻━┻ 嘗試改掉想到哪寫哪的代碼壞習慣 鏈表 相交鏈表 public class Solution {/**ac&#xff08;公共長度&#xff09;b所以 鏈表A的長度 a c&#xff0c;鏈表B的長度b ca b c b c a只要指針a從headA開始走&#xff0c;走完再…

利用pg_rman進行備份與恢復操作

文章目錄 pg_rman簡介一、安裝配置pg_rman二、創建表與用戶三、備份與恢復 pg_rman簡介 pg_rman 是 PostgreSQL 的在線備份和恢復工具。類似oracle 的 rman pg_rman 項目的目標是提供一種與 pg_dump 一樣簡單的在線備份和 PITR 方法。此外&#xff0c;它還為每個數據庫集群維護…

抖音使矛,美團用盾

有市場&#xff0c;就有競爭。抖音全力進軍本地生活市場欲取代美團&#xff0c;已不是新聞。 互聯網行業進入存量時代&#xff0c;本地生活市場是為數不多存在較大增長空間的賽道。艾媒咨詢數據顯示&#xff0c;預計2025年在線餐飲外賣市場規模達到17469億元&#xff0c;生鮮電…

Day05-01-jenkins進階

Day05-01-jenkins進階 10. 案例07: 理解 案例06基于ans實現10.1 整體流程10.2 把shell改為Ansible劇本10.3 jk調用ansible全流程10.4 書寫劇本 11. Jenkins進階11.1 jenkins分布式1&#xff09;概述2&#xff09;案例08&#xff1a;拆分docker功能3&#xff09;創建任務并綁定到…

安裝 ClamAV 并進行病毒掃描

安裝 ClamAV 并進行病毒掃描 以下是安裝 ClamAV 并使用它進行病毒掃描的步驟&#xff1a; 1. 安裝 ClamAV 在 Debian/Ubuntu 系統上&#xff1a; sudo apt update sudo apt install clamav clamav-daemon在 RHEL/CentOS 系統上&#xff1a; sudo yum install epel-release…

開發指南040-swagger加header

swagger可以在線生成接口文檔&#xff0c;便于前后端溝通&#xff0c;而且還可以在線調用接口&#xff0c;方便后臺調試。但是接口需要經過登錄校驗&#xff0c;部分接口還需要得到登錄token&#xff0c;使用token識別用戶身份進行后續操作。這種情況下&#xff0c;都需要接口增…

【刷題筆記(編程題)05】另類加法、走方格的方案數、井字棋、密碼強度等級

1. 另類加法 給定兩個int A和B。編寫一個函數返回AB的值&#xff0c;但不得使用或其他算數運算符。 測試樣例&#xff1a; 1,2 返回&#xff1a;3 示例 1 輸入 輸出 思路1: 二進制0101和1101的相加 0 1 0 1 1 1 0 1 其實就是 不帶進位的結果1000 和進位產生的1010相加 無進位加…

ssm校園志愿服務信息系統-計算機畢業設計源碼97697

摘 要 隨著社會的進步和信息技術的發展&#xff0c;越來越多的學校開始重視志愿服務工作&#xff0c;通過組織各種志愿服務活動&#xff0c;讓學生更好地了解社會、服務社會。然而&#xff0c;在實際操作中&#xff0c;志愿服務的組織和管理面臨著諸多問題&#xff0c;如志愿者…

dledger原理源碼分析系列(一)-架構,核心組件和rpc組件

簡介 dledger是openmessaging的一個組件&#xff0c; raft算法實現&#xff0c;用于分布式日志&#xff0c;本系列分析dledger如何實現raft概念&#xff0c;以及dledger在rocketmq的應用 本系列使用dledger v0.40 本文分析dledger的架構&#xff0c;核心組件&#xff1b;rpc組…

【pytorch16】MLP反向傳播

鏈式法則回顧 多輸出感知機的推導公式回顧 只與w相關的輸出節點和輸入節點有關 多層多輸入感知機 擴展為多層感知機的話&#xff0c;意味著還有一些層&#xff08;理解為隱藏層σ函數&#xff09;&#xff0c;暫且設置為 x j x_{j} xj?層 對于 x j x_{j} xj?層如果把前面的…

迅捷PDF編輯器合并PDF

迅捷PDF編輯器是一款專業的PDF編輯軟件&#xff0c;不僅支持任意添加文本&#xff0c;而且可以任意編輯PDF原有內容&#xff0c;軟件上方的工具欄中還有豐富的PDF標注、編輯功能&#xff0c;包括高亮、刪除線、下劃線這些基礎的&#xff0c;還有規則或不規則框選、箭頭、便利貼…

【護眼小知識】護眼臺燈真的護眼嗎?防近視臺燈有效果嗎?

當前&#xff0c;近視問題在人群中愈發普遍&#xff0c;據2024年的統計數據顯示&#xff0c;我國兒童青少年的總體近視率已高達52.7%。并且近視背后潛藏著諸多眼部并發癥的風險&#xff0c;例如視網膜脫離、白內障以及開角型青光眼等&#xff0c;嚴重的情況甚至可能引發失明。為…

PMP--知識卡片--波士頓矩陣

文章目錄 記憶黑話概念作用圖示 記憶 一說到波士頓就聯想到波士頓龍蝦&#xff0c;所以波士頓矩陣跟動物有關&#xff0c;狗&#xff0c;牛。 黑話 你公司的現金牛業務&#xff0c;正在逐漸變成瘦狗&#xff0c;應盡快采取收割策略&#xff1b;問題業務的儲備太少&#xff0…

必須掌握的Linux的九大命令

ifconfig 命令用于配置和查看網絡接口的參數。 ping 命令用于測試主機之間的網絡連通性。 telnet用于通過Telnet協議連接到遠程主機。 telnet 127.0.0.1 8000 telnet example.com telnet example.com 8080 iostat 命令用于報告 CPU 統計信息和 I/O 設備負載。 iostat&…

護眼熱點:臺燈護眼是真的嗎?一起來看臺燈的功能作用有哪些

如今近視問題日益嚴峻&#xff0c;尤為引人矚目的是&#xff0c;高度近視學生群體占比已逼近10%的警戒線&#xff0c;且這一比例伴隨著學齡的增長而悄然攀升——從幼兒園6歲孩童中那令人憂慮的1.5%&#xff0c;到高中階段驚人的17.6%&#xff0c;每一組數據都敲響了保護兒童視力…