通用點(diǎn)贊設(shè)計(jì)(二)使用redis的hash和zset類型

靡不有初,鮮克有終

這次我們來使用hash和zset完成點(diǎn)贊功能

使用hash類型設(shè)計(jì)點(diǎn)贊功能

我們知道redis的hash類型需要三個(gè)參數(shù)。key、hashkey 和 value。在點(diǎn)贊業(yè)務(wù)中,可以做如下設(shè)計(jì)

key 設(shè)計(jì) ==> 使用 枚舉標(biāo)識(shí)符:文章的ID作為key
hashkey 設(shè)計(jì)==> 點(diǎn)贊用戶的id作為hashkey
value 設(shè)計(jì) ==> 使用點(diǎn)贊的用戶信息做為value

這樣就能完成直接展示點(diǎn)贊人的具體信息,而不需要到mysql中再次查詢了。之前使用set類型是做不到這點(diǎn)的

image.png

使用zset設(shè)計(jì)獲贊主體排行榜功能

要展示一個(gè)被點(diǎn)贊數(shù)據(jù)從大到小排序的文章列表,這個(gè)功能就可以使用redis的Zset類型來實(shí)現(xiàn),因?yàn)閆set是可排序的

那么可以做如下設(shè)計(jì)

key 設(shè)計(jì) ==> 使用 主體類型的 枚舉標(biāo)識(shí)符作為key
value 設(shè)計(jì) ==> 使用被點(diǎn)贊的主體信息做為value

image.png

具體實(shí)現(xiàn)

主體類型枚舉類

package com.springboot.study.demo1.like.model;

/**
 *@description: BType 點(diǎn)贊主體的枚舉類型
 *@author: yinkai
 *@create: 2020/3/11 15:31
 */
public enum BType {
    //點(diǎn)贊
    LIKED_ARTICLE("文章點(diǎn)贊"),
    LIKED_ARTICLECOMMENT("文章評(píng)論點(diǎn)贊"),
    LIKED_VIDEO("視頻點(diǎn)贊"),
    LIKED_VIDEOCOMMENT("視頻評(píng)論點(diǎn)贊"),

    //點(diǎn)贊統(tǒng)計(jì)
    LIKED_ARTICLE_REPORT ("文章點(diǎn)贊統(tǒng)計(jì)"),
    LIKED_ARTICLECOMMENT_REPORT("文章評(píng)論點(diǎn)贊統(tǒng)計(jì)"),
    LIKED_VIDEO_REPORT("視頻點(diǎn)贊統(tǒng)計(jì)"),
    LIKED_VIDEOCOMMENT_REPORT("視頻評(píng)論點(diǎn)贊統(tǒng)計(jì)");

    private String bType;

    BType(String bType) {
        this.bType = bType;
    }

    public String getbType() {
        return bType;
    }

    public void setbType(String bType) {
        this.bType = bType;
    }
}

redis的 key生成工具類

package com.springboot.study.demo1.like.utils;

import com.springboot.study.demo1.like.model.BType;

/**
 * 生成Redis的 key
 */
public class LikedUtil {


    /**
     * 生成點(diǎn)贊的key
     *
     * @param bType
     * @param subjectId
     * @return
     */
    public static String getKey(BType bType, Object subjectId) {
        return bType + ":" + subjectId;
    }


    /**
     * 生成點(diǎn)贊數(shù)量的key
     *
     * @param bType
     * @return
     */
    public static String getReportKey(BType bType) {

        BType type = null;

        switch (bType) {
            case LIKED_ARTICLE:
                type = BType.LIKED_ARTICLE_REPORT;
                break;
            case LIKED_ARTICLECOMMENT:
                type = BType.LIKED_VIDEO_REPORT;
                break;
            case LIKED_VIDEO:
                type = BType.LIKED_VIDEO_REPORT;
                break;
            case LIKED_VIDEOCOMMENT:
                type = BType.LIKED_VIDEOCOMMENT_REPORT;
                break;
            //默認(rèn)返回LIKED_ARTICLE_REPORT 文章數(shù)量key
            default:
                type = BType.LIKED_ARTICLE_REPORT;

        }
        return type.name();
    }


    public static void main(String[] args) {
        String key = LikedUtil.getReportKey(BType.LIKED_ARTICLE);
        System.out.println(key);
    }
}

創(chuàng)建User實(shí)體類

package com.springboot.study.demo1.entity;
import com.baomidou.mybatisplus.annotation.TableField;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import sun.plugin2.message.Serializer;

import java.io.Serializable;

/**
 *@description: User 實(shí)體類
 *@author: yinkai
 *@create: 2020/2/25 9:21
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class User implements Serializable {
    private Long id;
    private String name;
    private Integer age;
    private String email;
    @TableField(exist = false)
    private Integer count;
}

主體(文章)實(shí)體的實(shí)體類

package com.springboot.study.demo1.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;

@Data
@AllArgsConstructor
@NoArgsConstructor
@Accessors(chain = true)
public class Article {
    //文章id
    private Integer articleId;
    //文章標(biāo)題
    private String title;
    //文章摘要
    private String summary;
    //文章圖片
    private String img;

    //點(diǎn)贊數(shù),該字段只用于排行版統(tǒng)計(jì)
    private Integer likeNum;
}

定義service接口

package com.springboot.study.demo1.like.service;
import com.springboot.study.demo1.like.model.BType;
import java.util.Set;

/**
 *@description: ILikedService 點(diǎn)贊接口
 *@author: yinkai
 *@create: 2020/3/11 16:39
 */
public interface ILikedService {

    /**
     * 點(diǎn)贊/取消點(diǎn)贊
     *
     * @param bType     業(yè)務(wù)類型
     * @param subjectId 被點(diǎn)贊主體ID
     * @param postId    點(diǎn)贊主體ID
     * @param user    點(diǎn)贊用戶
     * @param subject    被點(diǎn)贊主體
     */
    void liked(BType bType, Object subjectId, Object postId, Object user,Object subject);

    /**
     * 查詢單個(gè)主體(如文章)的獲贊個(gè)數(shù),如 id為 1的文章被點(diǎn)贊的數(shù)量
     *
     * @param bType     實(shí)體類型
     * @param subjectId 被點(diǎn)贊主體ID
     * @return 點(diǎn)贊數(shù)量
     */
    Long count(BType bType, Object subjectId);

    /**
     * 獲得獲得點(diǎn)贊數(shù) 前top名的bType排行版
     * @param bType  實(shí)體類型
     * @return top 排行版截取數(shù)
     */
    Set<Object> getSubjectTopN(BType bType, Long top);

}

編寫實(shí)現(xiàn)類

實(shí)現(xiàn)了三個(gè)方法。 liked方法 實(shí)現(xiàn) 點(diǎn)贊和取消點(diǎn)贊、count方法查詢具體主體(文章)的獲贊總數(shù)、getSubjectTopN方法 獲得點(diǎn)贊數(shù)指定前多少名的主體排行榜

liked方法在實(shí)現(xiàn)點(diǎn)贊邏輯以外還 插入了被點(diǎn)贊文章的信息。這樣為之后的排行榜查詢提供了數(shù)據(jù)

package com.springboot.study.demo1.like.service.impl;
import com.springboot.study.demo1.entity.Article;
import com.springboot.study.demo1.like.model.BType;
import com.springboot.study.demo1.like.service.ILikedService;
import com.springboot.study.demo1.like.utils.LikedUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;
import java.util.Set;

@Service
public class ILikedServiceImpl implements ILikedService {

    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private HashOperations<String, Object, Object> hashOperations;
    @Autowired
    private ZSetOperations<String, Object> zSetOperations;


    /**
     * 點(diǎn)贊/取消點(diǎn)贊
     *
     * @param bType     業(yè)務(wù)類型
     * @param subjectId 被點(diǎn)贊主體ID
     * @param postId    點(diǎn)贊主體ID
     */
    @Override
    public void liked(BType bType, Object subjectId, Object postId, Object user, Object subject) {

        /**
         * 點(diǎn)贊實(shí)現(xiàn),操作hash
         */
        //生成點(diǎn)贊key
        String key = LikedUtil.getKey(bType, subjectId);
        //判斷key對(duì)應(yīng)的set中是否存在subjectId
        //存在則取消點(diǎn)贊
        Boolean aBoolean = hashOperations.hasKey(key, postId);
        if (aBoolean) {
            hashOperations.delete(key, postId);
        } else {
            //不存在則點(diǎn)贊
            hashOperations.put(key, postId, user);
        }

        /**
         *
         *
         * 在點(diǎn)贊和取消點(diǎn)贊的同時(shí)進(jìn)行 點(diǎn)贊統(tǒng)計(jì)排序?qū)崿F(xiàn),操作zset
         */

        //生成點(diǎn)贊統(tǒng)計(jì)key
        String reportKey = LikedUtil.getReportKey(bType);
        if (aBoolean) {
            //已經(jīng)存在贊

            //需要取消點(diǎn)贊,設(shè)置分?jǐn)?shù)為 -1 , 最低分?jǐn)?shù)
            zSetOperations.incrementScore(reportKey, subject, -1);
        } else {
            //不存在贊

            //需要點(diǎn)贊

            //獲得分?jǐn)?shù)
            Double score = zSetOperations.score(reportKey, subject);
            if (score == null) {
                //score分?jǐn)?shù)不存在則設(shè)置 score為1
                zSetOperations.add(reportKey, subject, 1);
            } else {
                //score分?jǐn)?shù)存在則 zset的score分?jǐn)?shù)加1
                zSetOperations.incrementScore(reportKey, subject, 1);
            }
        }

    }

    /**
     * 查詢單個(gè)主體(如文章)的獲贊個(gè)數(shù),如 id為 1的文章被點(diǎn)贊的數(shù)量
     *
     * @param bType     業(yè)務(wù)類型
     * @param subjectId 被點(diǎn)贊主體ID
     * @return 點(diǎn)贊數(shù)量
     */
    @Override
    public Long count(BType bType, Object subjectId) {
        //生成key
        String key = LikedUtil.getKey(bType, subjectId);
        //獲得點(diǎn)贊數(shù)量
        Long size = hashOperations.size(key);
        return size;
    }

    @Override
    public Set<Object> getSubjectTopN(BType bType, Long top) {

        //獲得點(diǎn)贊主體排行版
        Set<Object> set = zSetOperations.reverseRange(LikedUtil.getReportKey(bType), 0, top);

        //得到以bType開頭的所有key組成的集合
        Set<String> keys = redisTemplate.keys(bType + ":*");


        //給返回結(jié)果集設(shè)置 點(diǎn)贊數(shù)
        for (Object obj : set) {
            for (String key : keys) {
                Integer id = Integer.valueOf(key.substring(key.lastIndexOf(":") + 1));
                if (((Article) obj).getArticleId().equals(id)) {
                    ((Article) obj).setLikeNum(hashOperations.size(key).intValue());
                }
            }
        }
        return set;
    }


}





功能測試

package com.springboot.study.demo1;

import com.springboot.study.demo1.entity.Article;
import com.springboot.study.demo1.entity.User;
import com.springboot.study.demo1.like.model.BType;
import com.springboot.study.demo1.like.service.ILikedService;
import org.junit.runner.RunWith;
import org.omg.PortableInterceptor.SYSTEM_EXCEPTION;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import reactor.core.publisher.Flux;

import java.util.Map;
import java.util.Set;


/**
 * @author ceshi
 * @Title:
 * @Package
 * @Description:
 * @date 2020/3/1115:49
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class ILikedServiceImplTest {
    @Autowired
    ILikedService iLikedService;


    @org.junit.Test
    public void liked() {


        //文章1、文章2、文章3分被點(diǎn)贊

        Article article1 = new Article().setArticleId(1000).setTitle("文章1").setSummary("aaaaaaaaaaaaa").setImg("圖");
        Article article2 = new Article().setArticleId(1001).setTitle("文章2").setSummary("bbbbbbbbbbbbb").setImg("圖");
        Article article3 = new Article().setArticleId(1002).setTitle("文章3").setSummary("ccccccccccccc").setImg("圖");

        //對(duì)id為 1000點(diǎn)贊
        iLikedService.liked(BType.LIKED_ARTICLE, 1000, 1, new User().setId((long) 1).setName("A").setAge(22), article1);

        //對(duì)id為 1001點(diǎn)贊
        iLikedService.liked(BType.LIKED_ARTICLE, 1001, 2, new User().setId((long) 2).setName("B").setAge(23),article2);
        iLikedService.liked(BType.LIKED_ARTICLE, 1001, 3, new User().setId((long) 3).setName("B").setAge(24),article2);


        //對(duì)id為 1002點(diǎn)贊
        iLikedService.liked(BType.LIKED_ARTICLE, 1002, 1, new User().setId((long) 1).setName("C").setAge(24),article3);
        iLikedService.liked(BType.LIKED_ARTICLE, 1002, 2, new User().setId((long) 2).setName("C").setAge(24),article3);
        iLikedService.liked(BType.LIKED_ARTICLE, 1002, 3, new User().setId((long) 3).setName("C").setAge(24),article3);

    }


    @org.junit.Test
    public void likedCancel() {

        //取消id為2的用戶對(duì)LIKED_ARTICLE文章id為1000的點(diǎn)贊
        Article article1 = new Article().setArticleId(1000).setTitle("文章1").setSummary("aaaaaaaaaaaaa").setImg("圖");
        iLikedService.liked(BType.LIKED_ARTICLE, 1000, 0, new User().setId((long) 0).setName("A").setAge(22), article1);

    }


    @org.junit.Test
    public void count() {

        Long count = iLikedService.count(BType.LIKED_ARTICLE, 1000);
        System.out.println(count);

    }

    @org.junit.Test
    public void getSubjectTopN() {

        Set<Object> subjectTopN = iLikedService.getSubjectTopN(BType.LIKED_ARTICLE, 100L);

        System.out.println(subjectTopN);

    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容