Bootstrap

redis实现热搜及最近搜索

原文链接:
https://blog.csdn.net/qq_41889508/article/details/123592124

https://blog.csdn.net/qq_41060647/article/details/127528075
首先感谢原文作者,在原有代码做了一点点小的修改,作为记录。直接贴代码

先说下作者改动:支持批量删除历史记录。

原创:

1.新增搜索记录:

新增一条该userid用户在搜索栏的历史记录
  public void addRedisRecentSearch(Long hhxsUserId,String searchKeywords) {
        ZSetOperations<String,String> zSet = redisTemplate.opsForZSet();
        //由于zset 的集合特性当插入已经存在的 v 值 (搜索记录) 时只会更新score 值,
        zSet.add(RedisKeyConstant.RECENT_SEARCH+hhxsUserId, searchKeywords, Instant.now().getEpochSecond());
        //获取到全部用户的最近搜索记录,用reverseRangeWithScores方法,可以获取到根据score排序之后的集合
        Set<ZSetOperations.TypedTuple<String>> typedTuples = zSet.reverseRangeWithScores(RedisKeyConstant.RECENT_SEARCH, 0, -1);
    }

2.用户的最近搜索

public List<String> searchRecentList() {
        Long userId = getUserId();
        Set<String> userRecentSearchVos = redisUtils.listRecentSearch(userId);
        List<String> collect = userRecentSearchVos.stream().collect(Collectors.toList());
        return collect;
    }
   /**
     * 最近搜索列表
     * @return
     */
    public Set<String> listRecentSearch(Long userId) {
        Set<ZSetOperations.TypedTuple<String>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(ConstantRedisKey.RECENT_SEARCH+userId, 0, -1);
        return Optional.ofNullable(typedTuples)
                .map(tuples -> tuples.stream()
                        .map(ZSetOperations.TypedTuple::getValue)
                        .filter(Objects::nonNull)
                        .limit(20)
                        .collect(Collectors.collectingAndThen(
                                Collectors.toCollection(LinkedHashSet::new), LinkedHashSet::new)))
                .orElseGet(LinkedHashSet::new);
    }

2.1 最近搜索添加权重

   public void addSearchCount(String searchKeywords) {
        String key = RedisKeyConstant.SEARCH_TITLE + searchKeywords;
        Boolean b = redisTemplate.hasKey(key);
        if(b){
            String count = (String)redisTemplate.opsForValue().get(key);
            redisTemplate.opsForValue().set(key,Long.valueOf(count)+1L);
        }else{
            redisTemplate.opsForValue().set(key, 1L);
        }
    }

3.删除该用户下所有历史搜索记录

public void removeZsetAll(Long userId) {
        redisTemplate.delete(RedisKeyConstant.RECENT_SEARCH + userId);
    }

以下为原作者,感谢!!!
1、pom文件

	<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
            <version>2.1.8.RELEASE</version>
	 </dependency>

2、HotSerachController

package net.beidousky.web.app.controller;
import net.beidousky.web.app.domain.Result;
import net.beidousky.web.app.service.impl.HotSearchService;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;

/**
 * 热搜及最近搜索
 * 2022-10-26
 * jwb
 */
@RequestMapping("/app/serach")
@RestController
public class HotSerachController {

    @Resource
    private RedisTemplate redisTemplate;

    @Resource
    private HotSearchService hotSearchService;

    /**
     * 删除redis
     * @param key
     * @return
     */
    @GetMapping("/remove")
    public Result removeRedis(String key){
        redisTemplate.delete(key);
        return Result.success();
    }

    /**
     * 搜索
     * @param query
     * @return
     */
    @GetMapping("/search")
    public Result listProduct(String query) {
        return Result.success(hotSearchService.search(query));
    }

    /**
     * 热搜列表
     * @return
     */
    @ResponseBody
    @GetMapping("/hotSearch")
    public Result listHotSearch() {
        return Result.success(hotSearchService.listHotSearch());
    }

    /**
     * 最近搜索列表
     * @return
     */
    @ResponseBody
    @GetMapping("/latelySearch")
    public Result recentHotSearch() {
        return Result.success(hotSearchService.listRecentSearch());
    }

}

2、SearchRedisHelper

package net.beidousky.web.app.controller;

import net.beidousky.common.core.domain.model.LoginUser;
import net.beidousky.common.utils.SecurityUtils;
import net.beidousky.web.app.domain.Product;
import net.beidousky.web.app.domain.UserRecentSearch;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.time.Instant;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

@Component
public class SearchRedisHelper {

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 热搜的key
     */
    public static final String HOT_SEARCH = "hotSearch";

    /**
     * 最近搜索的key
     */
    public static final String RECENT_SEARCH = "latelySearch";

    /**
     * 最近搜索的大小
     */
    public static final Integer CURRENT_SEARCH_SIZE = 3;

    /**
     * 热搜key的过期时间
     */
    public static final Integer HOT_SEARCH_EXPIRE_TIME = 3;

    /**
     * 设置redis的过期时间
     * expire其实是懒加载,不设置key的时候是不会执行的
     */
    @PostConstruct
    public void setHotSearchExpireTime() {
        redisTemplate.expire(HOT_SEARCH, HOT_SEARCH_EXPIRE_TIME, TimeUnit.SECONDS);
    }

    /**
     * redis添加最近搜索
     * @param query
     */
    public void addRedisRecentSearch(String query) {
        UserRecentSearch userRecentSearch = new UserRecentSearch();
        LoginUser loginUser = SecurityUtils.getLoginUser();
        //用户id 当前用户 
        userRecentSearch.setUnionId(loginUser.getUserId());
        //搜索信息
        userRecentSearch.setSearchInfo(query);
        //score为一个分值,需要把最近浏览的商品id 的分值设置为最大值,
        //此处我们可以设置为当前时间Instant.now().getEpochSecond()
        //这样最近浏览的商品id的分值一定最大,排在ZSet集合最前面。
        ZSetOperations<String, UserRecentSearch> zSet = redisTemplate.opsForZSet();
        //由于zset 的集合特性当插入已经存在的 v 值 (商品id) 时只会更新score 值,
        zSet.add(RECENT_SEARCH, userRecentSearch, Instant.now().getEpochSecond());

        //获取到全部用户的最近搜索记录,用reverseRangeWithScores方法,可以获取到根据score排序之后的集合
        Set<ZSetOperations.TypedTuple<UserRecentSearch>> typedTuples = zSet.reverseRangeWithScores(RECENT_SEARCH, 0, -1);

        //只得到当前用户的最近搜索记录,注意这里必须保证set集合的顺序
        Set<UserRecentSearch> userRecentSearches = listRecentSearch();
        
        if (userRecentSearches.size() > CURRENT_SEARCH_SIZE) {
            //获取到最开始浏览的第一条
            UserRecentSearch userRecentSearchLast = userRecentSearches.stream().reduce((first, second) -> second).orElse(null);
            //删除最开始浏览的第一条
            zSet.remove(RECENT_SEARCH, userRecentSearchLast);
        }
    }

    /**
     * 热搜列表
     * @return
     */
    public Set<Product> listHotSearch() {
        //0 5 表示0-5下标对应的元素
        return redisTemplate.opsForZSet().reverseRangeWithScores(HOT_SEARCH, 0, 10);
    }

    /**
     * redis添加热搜
     * @param query
     */
    public void addRedisHotSearch(String query) {
        //1:表示每调用一次,当前product的分数+1
       // productList.forEach(product -> redisTemplate.opsForZSet().incrementScore(HOT_SEARCH, product, 1D));
        redisTemplate.opsForZSet().incrementScore(HOT_SEARCH, query, 1D);
    }

    /**
     * 最近搜索列表
     * @return
     */
    public Set<UserRecentSearch> listRecentSearch() {
        LoginUser loginUser = SecurityUtils.getLoginUser();
        Set<ZSetOperations.TypedTuple<UserRecentSearch>> typedTuples = redisTemplate.opsForZSet().reverseRangeWithScores(RECENT_SEARCH, 0, -1);
        return Optional.ofNullable(typedTuples)
                .map(tuples -> tuples.stream()
                        .map(ZSetOperations.TypedTuple::getValue)
                        .filter(Objects::nonNull)
                        .filter(userRecentSearch -> Objects.equals(userRecentSearch.getUnionId(),loginUser.getUserId()))
                        .collect(Collectors.collectingAndThen(
                                Collectors.toCollection(LinkedHashSet::new), LinkedHashSet::new)))
                .orElseGet(LinkedHashSet::new);
    }
}

3、HotSearchService

package net.beidousky.web.app.service.impl;

import net.beidousky.web.app.controller.SearchRedisHelper;
import net.beidousky.web.app.domain.Product;
import net.beidousky.web.app.domain.UserRecentSearch;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.Set;

@Service
public class HotSearchService {

    @Resource
    private SearchRedisHelper searchRedisHelper;

    /**
     * 搜索
     * @param query
     * @return
     */
    public String search(String query) {
        //业务代码可用es.....此处略过....模拟数据库数据
        searchRedisHelper.addRedisRecentSearch(query);
        searchRedisHelper.addRedisHotSearch(query);
        return query;
    }

    /**
     * 热搜列表
     * @return
     */
    public Set<Product> listHotSearch() {
        return searchRedisHelper.listHotSearch();
    }

    /**
     * 最近搜索列表
     * @return
     */
    public Set<UserRecentSearch> listRecentSearch() {
        return searchRedisHelper.listRecentSearch();
    }
}

4、UserRecentSearch

package net.beidousky.web.app.domain;

import lombok.Data;

import java.io.Serializable;

@Data
public class UserRecentSearch implements Serializable {

    /**
     * 搜索信息
     */
    private String searchInfo;

    /**
     * 用户id
     */
    private Long unionId;

}

;