網(wǎng)站代碼怎么改/個人在線做網(wǎng)站免費
學(xué)習(xí)目標(biāo):
提示:學(xué)習(xí)如何利用Redisson實現(xiàn)點贊排行榜功能,按照時間順序
當(dāng)用戶給某一篇文章點贊后,會再數(shù)據(jù)庫中存儲一條數(shù)據(jù),并且在Redis中存儲一條數(shù)據(jù)為當(dāng)前博客的點贊用戶標(biāo)識,來區(qū)分哪個用戶對文章進行了點贊,使用ZSet數(shù)據(jù)結(jié)構(gòu)對點贊用戶進行排序來實現(xiàn)排行榜功能
學(xué)習(xí)產(chǎn)出:
解決方案:
- 點贊后的用戶記錄在Redis的set數(shù)據(jù)類型中
1. 準(zhǔn)備pom環(huán)境
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope><version>5.1.47</version></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.3</version></dependency><!--hutool--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.7.17</version></dependency><dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.23.1</version></dependency>
2. 配置ThreadLocal和過濾器
public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();public static void saveUser(UserDTO user){tl.set(user);}public static UserDTO getUser(){return tl.get();}public static void removeUser(){tl.remove();}
}
@Configuration
public class MvcConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate redis;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/hot","/shop/**","/shop-type/**","/voucher/**").order(2);registry.addInterceptor(new RefreshTokenInterceptor(redis)).addPathPatterns("/**").order(1);}
}
---------------------------------------------
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {//controller執(zhí)行之前@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.判斷是否需要攔截ThreadLocalif (UserHolder.getUser()==null) {response.setStatus(401);return false;}//7.放行return true;}//渲染后返回給前臺數(shù)據(jù)前@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {//移除用戶,避免內(nèi)存泄露UserHolder.removeUser();}
}
---------------------------------------------------
@Slf4j
public class RefreshTokenInterceptor implements HandlerInterceptor {//這個對象不是由spring管理的所以不能用注解自動注入private StringRedisTemplate redis;public RefreshTokenInterceptor(StringRedisTemplate redis) {this.redis = redis;}//controller執(zhí)行之前@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//1.獲取請求頭中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}//2.基于token獲取redis中的用戶//通過key取到hash中的map集合數(shù)據(jù)Map<Object, Object> userMap = redis.opsForHash().entries("login:token:" + token);//3.判斷用戶是否存在if (userMap.isEmpty()) {return true;}//5.將查詢到的hash數(shù)據(jù)轉(zhuǎn)為userDto對象UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);//6.存在,保存用戶信息到ThreadLocal中UserHolder.saveUser(userDTO);//7.刷新token有效期redis.expire(LOGIN_USER_KEY + token, 30, TimeUnit.MINUTES);log.info("我是第一個攔截器當(dāng)前攔截所有請求的用戶為,線程為{},{}",UserHolder.getUser(),Thread.currentThread());//8.放行return true;}
3. Controller層:負(fù)責(zé)接收請求和向下分配
@RestController
@RequestMapping("/blog")
public class BlogController{@Resourceprivate IBlogService blogService;@PutMapping("/like/{id}")public Result likeBlog(@PathVariable("id") Long id) {return blogService.likeBlog(id);}
}
4. Service層:負(fù)責(zé)業(yè)務(wù)的處理邏輯點贊功能,將文章的點贊用戶以時間戳為分?jǐn)?shù)存入Redis
@Service
public class BlogServiceImpl extends ServiceImpl<BlogMapper, Blog> implements IBlogService {@Autowiredprivate IUserService userService;@Resourceprivate StringRedisTemplate redis;@Overridepublic Result likeBlog(Long id) {//1.獲取登錄用戶Long userId = UserHolder.getUser().getId();//2.判斷當(dāng)前用戶是否已經(jīng)點贊String key = "blog:liked:" + id;//獲取當(dāng)前登錄用戶的分?jǐn)?shù),若文章中的用戶id分?jǐn)?shù)為null說明未點贊Double score = redis.opsForZSet().score(key, userId.toString());if (score == null) {//3.如果未點贊,可以點贊//3.1 點贊+1boolean isSuccess = update().setSql("liked= liked +1").eq("id", id).update();//3.2保存當(dāng)前點贊用戶到Redis的文章set集合中,文章set集合中記錄的是點贊用戶的id,//分?jǐn)?shù)是時間戳,可以進行排序if (isSuccess) {//存入Redis的分?jǐn)?shù)值以當(dāng)前時間戳存入redis.opsForZSet().add(key, userId.toString(), System.currentTimeMillis());}} else {//4.如果已點贊,取消點贊//4.1點贊-1boolean isSuccess = update().setSql("liked = liked -1").eq("id", id).update();//4.2把用戶從Redis的set集合移除if (isSuccess) {redis.opsForZSet().remove(key, userId.toString());}}return null;}
}
5. 上述為下面的排行榜做鋪墊
查詢當(dāng)前文章的點贊排行榜,id是文章id號
@PutMapping("/likes/{id}")public Result likesBlog(@PathVariable("id") Long id) {return blogService.queryBlogLikes(id);}
@Overridepublic Result queryBlogLikes(Long id) {String key="blog:liked:" + id;//取出前五條數(shù)據(jù)Set<String> rangeData = redis.opsForZSet().range(key, 0, 4);if (rangeData==null) {return Result.ok(Collections.emptyList());}//將文章點贊的前五條用戶id轉(zhuǎn)換為Long類型List<Long> ids = rangeData.stream().map(Long::valueOf).collect(Collectors.toList());String idStr = StrUtil.join(",", ids);//去數(shù)據(jù)庫把這些用戶查詢出來,并且數(shù)據(jù)脫敏返回給前端List<User> users = userService.query().in("id",ids).last("order by field(id,"+idStr+")").list();UserDTO userData = BeanUtil.copyProperties(users, UserDTO.class);return Result.ok(userData);}