《Redis实战》-文章投票 RedisTemplate实现

2 篇文章 0 订阅

《Redis实战》第一章文章投票的实现,基本将书中所提到的情况考虑到了,下面是主要代码。

1. 依赖和配置文件
<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-redis</artifactId>
		</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>

		<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind -->
		<dependency>
			<groupId>com.fasterxml.jackson.core</groupId>
			<artifactId>jackson-databind</artifactId>
			<version>2.13.3</version>
		</dependency>

	</dependencies>
spring:
  redis:
    host: ip地址
    port: 6379
    database: 0
    password: ******
server:
  port: 8080
2. 统一返回结果
/**
 * 统一返回结果格式
 *
 * @author Caolele
 * @since 09-19-2022 周一
 */


@Data
public class Result<T> {

    private Integer code;
    private String message;
    private T data;

    public Result(Integer code, String message, T data) {
        this.code = code;
        this.message = message;
        this.data = data;
    }

    public Result(Integer code, T data) {
        this.code = code;
        this.data = data;
    }

    /**
     * 统一失败返回
     */
    public static Result fail() {
        return new Result(20001, "获取失败", null);
    }


    /**
     * 自定义失败消息返回
     */

    public static Result fail(String message) {
        Result result = new Result(20001, null);
        result.setMessage(message);
        return result;
    }

    /**
     * 统一成功返回
     */
    public static <T> Result success(T t) {

        Result result = new Result(20000, "获取数据成功", t);
        return result;
    }

    /**
     * 自定义成功消息返回
     */
    public static <T> Result success(String message, T t) {

        return new Result(20000, message, t);
    }

    /**
     * 自定义成功消息无数据返回
     */
    public static Result success(String message) {
        return new Result(20000, message);
    }

    /**
     * 虽然成功执行操作,但是没有数据
     *
     * @return
     */

    public static Result successWithoutData() {
        return new Result(20000, "未查询到数据", null);
    }


}


3. 序列化配置
@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){

        RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer();

        redisTemplate.setHashKeySerializer(stringRedisSerializer);
        redisTemplate.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(stringRedisSerializer);
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}
4. 常量

/**
 * @author Caolele
 * @since 09-19-2022 周一
 */

public class Article {
    /**
     * 文章投票过期时间
     */
    public static final Long ONE_WEEK_IN_SECONDS = 7*86400L;

    /**
     * 投票一次所加的分数
     */
    public static final Double VOTE_SCORE = 432D;

}
/**
 * @author Caolele
 * @since 09-19-2022 周一
 */

public class RedisArticleKey {


    /**
     * 文章详细信息的key的前缀
     * e.g. article:articleId
     */
    public static final String ARTICLE_DETAIL_KEY_PREFIX = "article:";

    /**
     * 文章标题
     */
    public static final String ARTICLE_DETAIL_TITLE = "title";

    /**
     * 链接
     */
    public static final String ARTICLE_DETAIL_LINK = "link";
    /**
     * 发布者
     * e.g. poster user:userId
     */
    public static final String ARTICLE_DETAIL_POSTER = "poster";

    /**
     * 时间戳
     */
    public static final String ARTICLE_DETAIL_TIME = "time";

    /**
     * 投票数量
     */
    public static final String ARTICLE_DETAIL_VOTES = "votes";



    /**
     * 文章发布时间 key
     */
    public static final String TIME_KEY = "time:";
    /**
     * 按时间排序的ZSet中的key前缀
     */
    public static final String TIME_ARTICLE_PREFIX ="article:";

    /**
     * 文章评分 key
     */
    public static final String SCORE_KEY = "score:";

    /**
     * 按分数排序,每篇文章的前缀
     */

    public static final String SCORE_ARTICLE_PREFIX = "article:";



    /**
     * Set  每篇文章的投票用户列表的key前缀
     */
    public static final String VOTED_KEY_PREFIX = "voted:";

    /**
     * 用户投票列表中的用户前缀
     */
    public static final String VOTED_USER_KEY_PREFIX = "user:";

    private RedisArticleKey() {

    }

}
5. 核心业务类

文章发布

/**
 * @author Caolele
 * @since 09-19-2022 周一
 */

@Service
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    @Override
    public void publish(Long articleId, Long userId) {

        Long timeStamp = System.currentTimeMillis() / 1000;
// TODO: 2022/9/19 下面三步是一个事务,暂未实现
        //1.通过article:articleId 记录每篇文章的详细信息
        Map<String, Object> map = getArticleDetailMap(articleId, timeStamp,userId);
        redisTemplate.opsForHash().putAll(RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX + articleId, map);

        //2.将发布的文章放入到按时间排序的有序集合
        redisTemplate.opsForZSet().add(RedisArticleKey.TIME_KEY,
                RedisArticleKey.TIME_ARTICLE_PREFIX + articleId,
                timeStamp);
        //3.直接用发布时间戳作为评分的初始化值
        redisTemplate.opsForZSet().add(RedisArticleKey.SCORE_KEY, RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX + articleId, timeStamp);

        //4.初始化文章的投票列表,仅允许存在7天,7天后自动注销,为了能建立这个set便于设置过期时间,放入发布者的key,这样发布者不可以给自己点赞
        redisTemplate.opsForSet().add(RedisArticleKey.VOTED_KEY_PREFIX+articleId,RedisArticleKey.VOTED_USER_KEY_PREFIX+userId);
        redisTemplate.expire(RedisArticleKey.VOTED_KEY_PREFIX+articleId, Article.ONE_WEEK_IN_SECONDS, TimeUnit.SECONDS);

    }

    //构造一篇文章
    private Map<String, Object> getArticleDetailMap(Long articleId, Long timeStamp,Long userId) {
        //记录发布文章的信息
        Map<String, Object> map = new HashMap();
        map.put(RedisArticleKey.ARTICLE_DETAIL_LINK, "#");
        map.put(RedisArticleKey.ARTICLE_DETAIL_POSTER,RedisArticleKey.VOTED_USER_KEY_PREFIX+userId);
        map.put(RedisArticleKey.ARTICLE_DETAIL_TITLE, "漂泊者们");
        map.put(RedisArticleKey.ARTICLE_DETAIL_VOTES, 0L);
        map.put(RedisArticleKey.ARTICLE_DETAIL_TIME, timeStamp);
        return map;
    }
 /**
     * 取出评分最高的数篇文章   zrevrange score: 0 9 取出前10个按分数从高到低
     */

    @Override
    public Result getTopScoreArticles(Integer size) {

        //先获取前size个文章的id,然后去查找指定Id文章的详细信息返回
        Set<Object> objects = redisTemplate.opsForZSet().reverseRange(RedisArticleKey.SCORE_KEY, 0, size - 1);
        if(objects.isEmpty())
        {
            return Result.successWithoutData();
        }
        List<Map<Object,Object>> maps = new LinkedList<>();

        objects.forEach(o -> {
            Map<Object, Object> entries = redisTemplate.opsForHash().entries(o.toString());
            maps.add(entries);
        });

        return Result.success(maps);

    }
}

文章投票

/**
 * @author Caolele
 * @since 09-19-2022 周一
 */

@Service
public class VoteServiceImpl implements VoteService {

    @Autowired
    private RedisTemplate redisTemplate;


    @Override
    public Result articleVote(Long articleId, Long userId) {

        if(!redisTemplate.hasKey(RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX+articleId)){
            return Result.fail("文章不存在");
        }
        if(redisTemplate.
                opsForHash().
                get(RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX+articleId,RedisArticleKey.ARTICLE_DETAIL_POSTER).
                equals(RedisArticleKey.VOTED_USER_KEY_PREFIX+userId)){ //用户给自己投票
            return Result.fail("你不可给自己投票");
        }

        Double cutoff = System.currentTimeMillis() / 1000 -
                redisTemplate.opsForZSet().score(RedisArticleKey.TIME_KEY,
                        RedisArticleKey.TIME_ARTICLE_PREFIX + articleId);
        if (cutoff > Article.ONE_WEEK_IN_SECONDS) { //距离发布时间过去7天,不可以再投票
            return Result.fail("发布日期超过7天,不能再投票");
        }

        boolean flag = redisTemplate.opsForSet().isMember(RedisArticleKey.VOTED_KEY_PREFIX + articleId,
                RedisArticleKey.VOTED_USER_KEY_PREFIX + userId);

        if (flag == true) {
            return Result.fail("不可重复投票");
        }
        //投票一次加432分
        redisTemplate.opsForZSet().incrementScore(RedisArticleKey.SCORE_KEY,
                RedisArticleKey.SCORE_ARTICLE_PREFIX + articleId,
                Article.VOTE_SCORE);
        //追加到文章的投票列表中
        redisTemplate.opsForSet().add(RedisArticleKey.VOTED_KEY_PREFIX + articleId,
                RedisArticleKey.VOTED_USER_KEY_PREFIX + userId);

        //将文章的详细信息中的投票数量加1
        Integer votes =  (Integer) redisTemplate.opsForHash().get(RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX + articleId,
                RedisArticleKey.ARTICLE_DETAIL_VOTES);
        redisTemplate.opsForHash().put(RedisArticleKey.ARTICLE_DETAIL_KEY_PREFIX + articleId,
                RedisArticleKey.ARTICLE_DETAIL_VOTES, votes+1L);


        return Result.success("投票成功");
    }


}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值