天天看點

springboot +redis 實作點贊、浏覽、收藏、評論等數量的增減操作

最近做了一個文章的收藏、點贊數量的功能,其實之前也做過類似的功能,因為之前一直使用的mysql 總是感覺對于這種頻繁需要改變的值,不應該給予Mysql過大的壓力,本文章采用的是redis 做了持久化。下面貼出關鍵代碼:DataResponse是項目中使用的結果封裝實體類;forumDTO是此功能的參數實體,如果有需要請留言。

常量如下:

private static final String DEFAULT_VALUE = "0:0:0:0:0:0";
    public static final Byte BYTE_ZERO = 0;
    public static final Byte BYTE_ONE = 1;
    public static final Byte BYTE_TWO = 2;
    public static final Byte BYTE_THREE = 3;
    public static final Byte BYTE_FOUR = 4;
    public static final Byte BYTE_FIVE = 5;
    public static final Byte BYTE_SIX = 6;
      
@Override
    public DataResponse keepNum(ForumDTO forumDTO) {
        //将文章id 設定為 key
        String key = forumDTO.getPostId().toString();
        //get 使用者id
        String userId = forumDTO.getUserId();
        String count, newCount;
        //綁定資料集key
        BoundHashOperations<String, Object, Object> post = redisTemplate.boundHashOps("post:");
        //擷取hKey
        // count: 0論壇-點贊量  1評論量 2收藏量 3浏覽 4評論-點贊量
        if (null == post.get(key)) {
            //無則set
            post.put(key, DEFAULT_VALUE);
            //再取出來指派給 count
            count = post.get(key).toString();
        } else {
            //有直接指派 count
            count = post.get(key).toString();
        }
        // operationType 1 浏覽 2 文章點贊 3  收藏  4評論-點贊
        String prefix;
        switch (forumDTO.getOperationType()) {
            case 1:
                //記錄浏覽次數 OPERATIONTYPE 1 : 記錄浏覽次數
                newCount = resetValue(count, BYTE_THREE, true);
                post.put(key, newCount);
                break;
            case 2:
                //記錄文章-點贊
                prefix = "thumbs:post";
                switch (forumDTO.getClickType()) {
                    case 0:
                        /**
                         *  OPERATIONTYPE 2: + CLICKTYPE 0 = 給文章點贊
                         * 0點贊
                         * 從redis中擷取數量  文章d 例如:177488r88t78r78r7
                         *  count: 0論壇-點贊量  1評論量 2收藏量 3浏覽 4評論-點贊量
                         * 避免每種數量都去查詢redis 直接通過 redis value 記錄所有的數量
                         * 擷取加 +1 後的值
                         */
                        if (redisTemplate.opsForSet().isMember(prefix + ":" + key, prefix + ":" + userId)) {
                            return DataResponse.fail("不能重複點贊哦");
                        } else {
                            redisTemplate.opsForSet().add(prefix + ":" + key, prefix + ":" + userId);
                        }
                        newCount = resetValue(count, BYTE_ZERO, true);
                        //set to redis
                        post.put(key, newCount);
                        break;
                    case 1:
                        //OPERATIONTYPE 2: + CLICKTYPE 1 = 取消文章點贊
                        //1取消文章點贊
                        if (!redisTemplate.opsForSet().isMember(prefix + ":" + key, prefix + ":" + userId)) {
                            //重複處理
                            return DataResponse.fail("不能重複取消哦");
                        } else {
                            //删除
                            redisTemplate.opsForSet().remove(prefix + ":" + key, prefix + ":" + userId);
                        }
                        newCount = resetValue(count, BYTE_ZERO, false);
                        post.put(key, newCount);
                        break;
                }
                break;
            case 3:
                prefix = "collection:post";
                List<MqMessage> sendList = new LinkedList<>();
                MqMessage mqMessage = new MqMessage();
                switch (forumDTO.getClickType()) {
                    //OPERATIONTYPE 3 + CLICKTYPE 0 = 記錄收藏
                    case 0:
                        //數量+1
                        //根據使用者id + 文章id 查詢redis 資料
                        if (redisTemplate.opsForSet().isMember(prefix + ":" + key, prefix + ":" + userId)) {
                            //重複處理
                            return DataResponse.fail("不能重複收藏哦");
                        }
                        //add
                        redisTemplate.opsForSet().add(prefix + ":" + key, prefix + ":" + userId);
                        //set to redis
                        newCount = resetValue(count, BYTE_TWO, true);
                        post.put(key, newCount);
                        mqMessage.setType(new Byte("9"));
                        mqMessage.setSenderId(userId);
                        mqMessage.setPostId(forumDTO.getPostId());
                        sendList.add(mqMessage);
                        this.sendMq.send(sendList);
                        break;
                    //OPERATIONTYPE 3 + CLICKTYPE 1 = 取消收藏
                    case 1:
                        //取消收藏
                        //嘗試從redis取出目前使用者是否已經收藏
                        if (!redisTemplate.opsForSet().isMember(prefix + ":" + key, prefix + ":" + userId)) {
                            //重複處理
                            return DataResponse.fail("不能重複取消哦");
                        }
                        //删除
                        redisTemplate.opsForSet().remove(prefix + ":" + key, prefix + ":" + userId);
                        newCount = resetValue(count, BYTE_TWO, false);
                        post.put(key, newCount);
                        mqMessage.setType(new Byte("10"));
                        mqMessage.setSenderId(userId);
                        mqMessage.setPostId(forumDTO.getPostId());
                        sendList.add(mqMessage);
                        this.sendMq.send(sendList);
                        break;
                }
                break;
            case 4:
                //記錄評論-點贊
                // OPERATIONTYPE 4: + CLICKTYPE 0 = 給評論點贊
                if (null == forumDTO.getCommentId()) {
                    return DataResponse.fail("評論id不能為空");
                }
                String commentNum, ckey = forumDTO.getCommentId().toString();
                BoundHashOperations<String, Object, Object> comment = redisTemplate.boundHashOps("post:comment");
                if (null == comment.get(ckey)) {
                    //無則set
                    comment.put(ckey, "0");
                    //再取出來指派給 count
                    commentNum = comment.get(ckey).toString();
                } else {
                    //有直接指派 count
                    commentNum = comment.get(ckey).toString();
                }
                //贊評論
                prefix = "thumbs:comment";
                switch (forumDTO.getClickType()) {
                    case 0:
                        /**
                         * 0點贊
                         * 從redis中擷取數量  文章d 例如:177488r88t78r78r7
                         *  count: 0論壇-點贊量  1評論量 2收藏量 3浏覽 4評論-點贊量
                         * 避免每種數量都去查詢redis 直接通過 redis value 記錄所有的數量
                         * 擷取加 + 後的值
                         */
                        if (redisTemplate.opsForSet().isMember(prefix + ":" + ckey, prefix + ":" + userId)) {
                            return DataResponse.fail("不能重複點贊哦");
                        } else {
                            redisTemplate.opsForSet().add(prefix + ":" + ckey, prefix + ":" + userId);
                        }
                        //set to redis
                        comment.put(ckey, cResetValue(commentNum, true));
                        break;
                    case 1:
                        //1取消評論點贊
                        if (!redisTemplate.opsForSet().isMember(prefix + ":" + ckey, prefix + ":" + userId)) {
                            //重複處理
                            return DataResponse.fail("不能重複取消哦");
                        } else {
                            //删除
                            redisTemplate.opsForSet().remove(prefix + ":" + ckey, prefix + ":" + userId);
                        }
                        newCount = cResetValue(commentNum, false);
                        comment.put(ckey, newCount);
                        break;
                }
                break;
            default:
                DataResponse.fail(ResponseEnum.FAILED);
        }
        return DataResponse.success(ResponseEnum.SUCCESS);
    }
      

resetValue代碼:

/**
     * 功能描述: <br>
     * 〈點贊數、收藏數等數量重置〉
     * @param val    數組
     * @param type   0文章點贊量  1評論量 2收藏量 3浏覽 4評論點贊量
     * @param isPlus 是否增加數量 true +   false -
     * @Return: java.lang.String
     * @Author:王震
     * @Date: 2020/8/5 10:27
     * StringUtils包:import org.apache.commons.lang3.StringUtils;
     * 可以使用jdk的包替代split方法;但jdk的包需要驗證正則,效率較低。
     */
    private String resetValue(String val, int j, boolean isPlus) {
        String[] value = StringUtils.split(val, ":");
        Long temp = Long.valueOf(value[j]);
        StringBuffer sb = new StringBuffer(16);
        if (isPlus) {
            temp += 1;
        } else {
            temp -= 1;
        }
        value[j] = temp.toString();
        for (int i = 0, len = value.length; i < len; i++) {
            if (i != len - 1) {
                sb.append(value[i]).append(":");
            }else {
                sb.append(value[i]);
            }
        }
        return sb.toString();
    }
    
   

# ***下面附上DataResponse 和 DTO代碼:***

   DataResponse :

package com.ehl.developerplatform.pojo;
import com.ehl.developerplatform.euem.ResponseEnum;
import lombok.Data;

@Data
public final class DataResponse<T> {

    private boolean success;

    private T data;


    public DataResponse<T> setData(T data) {
        this.data = data;
        return this;
    }

    private Integer code;

    private String message;

    public DataResponse<T> setMessage(String message) {
        this.message = message;
        return this;
    }

    public DataResponse() {
    }

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


    public static <T> DataResponse<T> success(String message) {
        return new DataResponse<T>(true, null, ResponseEnum.SUCCESS.getStatus(), message);
    }

    public static <T> DataResponse<T> success() {
        return new DataResponse<>(true, null, ResponseEnum.SUCCESS.getStatus(), ResponseEnum.SUCCESS.getMessage());
    }

    public static <T> DataResponse<T> success(ResponseEnum responseEnum) {
        return new DataResponse<>(true, null, responseEnum.getStatus(), responseEnum.getMessage());
    }

    public static <T> DataResponse<T> success(T data) {
        return new DataResponse<>(true, data, ResponseEnum.SUCCESS.getStatus(), ResponseEnum.SUCCESS.getMessage());
    }

    public static <T> DataResponse<T> success(ResponseEnum responseEnum, T data) {
        return new DataResponse<>(true, data, responseEnum.getStatus(), responseEnum.getMessage());
    }

    public static <T> DataResponse<T> success(ResponseEnum responseEnum, T data, Object... args) {
        String s = String.format(responseEnum.getMessage(), args);
        return new DataResponse<>(true, data, responseEnum.getStatus(), s);
    }

    public static <T> DataResponse<T> fail() {
        return new DataResponse<>(false, null, ResponseEnum.FAILED.getStatus(), ResponseEnum.FAILED.getMessage());
    }

    public static <T> DataResponse<T> fail(ResponseEnum responseEnum) {
        return new DataResponse<>(false, null, responseEnum.getStatus(), responseEnum.getMessage());
    }

    public static <T> DataResponse<T> fail(T data) {
        return new DataResponse<>(false, data, ResponseEnum.FAILED.getStatus(), ResponseEnum.FAILED.getMessage());
    }

    public static <T> DataResponse<T> fail(String message) {
        return new DataResponse<>(false, null, ResponseEnum.FAILED.getStatus(), message);
    }

    public static <T> DataResponse<T> fail(Integer code, String message) {
        return new DataResponse<>(false, null, code, message);
    }

    public static <T> DataResponse<T> fail(ResponseEnum responseEnum, T data) {
        return new DataResponse<>(false, data, responseEnum.getStatus(), responseEnum.getMessage());
    }

    public static <T> DataResponse<T> fail(ResponseEnum responseEnum, T data, Object... args) {
        String s = String.format(responseEnum.getMessage(), args);
        return new DataResponse<>(false, data, responseEnum.getStatus(), s);
    }

}

DTO:

package com.ehl.developerplatform.pojo.dto;
import com.ehl.developerplatform.anno.EnumValue;
import lombok.Data;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.io.Serializable;

@Data
public class ForumDTO implements Serializable {


    @Min(groups = {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchPostByBlogId.class,SearchCommentList.class,SearchPostList.class},value = 1, message = "每頁資料最少一條")
    @NotNull(groups = {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchPostByBlogId.class,SearchCommentList.class,SearchPostList.class},message = "[pageSize]字段不能為空")
    private Integer pageSize;

    @Min(groups = {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchCommentList.class,SearchPostByBlogId.class,SearchPostList.class},value = 1, message = "頁碼必須大于零")
    @NotNull(groups = {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchCommentList.class,SearchPostByBlogId.class,SearchPostList.class},message = "[pageNum]字段不能為空")
    private Integer pageNum;

    /**
     * 排序 1 更新日期  2 最近一天 3 最近三天  4 最近三個月  預設 1
     */
    @EnumValue(groups = {ForumDTO.SearchPostList.class}, byteValues = {1, 2, 3, 4},message = "更新日期必須為指定值")
    private Byte updateTime;

    /**
     * 文章名稱
     */
    @NotEmpty(groups = {AddTopic.class},message = "文章名稱不能為空")
    @Size(groups = {AddTopic.class},max = 30,min = 1,message = "文章名稱長度最長不能超過30個字元 最短不能小于1個字元")
    private String name;
    /**
     * 專題父id
     */
    @NotNull(groups = {Comment.class,DeleteMyComment.class},message = "父id不能為空")
    private Long pid;


    /**
     * postId 文章id
     */
    @NotNull(groups = {SearchCommentList.class,UpdateInit.class,DeleteMyPost.class,KeepNum.class,Comment.class,SearchLatestNum.class,SearchPostById.class,SearchPostDetailById.class},message = "貼子id不能為空")
    private Long postId;

    /**
     * 專區id
     */
    @NotNull(groups = {AddTopic.class,Comment.class,SearchPostByBlogId.class},message = "所屬專題id不能為空")
    private Long blogId;

    /**
     *文章分類 1 技術問答 2 經驗分享 3 大賽公告 4 大賽組隊 5 全部 6 精華區  注:大賽組隊隻有選擇大賽專區才能選擇
     */
    @NotNull(groups = {AddTopic.class},message = "文章分類不能為空")
    @EnumValue(groups = {SearchPostList.class,SearchPostByBlogId.class},byteValues = {1,2,3,4,5,6},message = "文章分類必須為指定值")
    private Byte classFy;

    /**
     * 專區名稱
     */
    @NotEmpty(groups = {AddTopic.class},message = "所屬專題名不能為空")
    private String title;
    /**
     * 評論内容
     */
    @NotEmpty(groups = {Comment.class,AddTopic.class},message = "評論内容不能為空")
    private String content;


    private String markText;
    /**
     * 使用者id  發帖人id
     */
    @NotEmpty(groups = {AddTopic.class,SearchMyReply.class,KeepNum.class,searchMyLikePost.class,SearchMyCreatePost.class,UpdatePostById.class,DeleteMyComment.class,Comment.class,DeleteMyPost.class,SearchLatestNum.class},message = "使用者id不能為空")
    private String userId;

    /**
     * operationType 1 浏覽 2 文章點贊 3  收藏  4評論-點贊
     */
    @EnumValue(groups = {ForumDTO.KeepNum.class},byteValues = {1, 2, 3, 4, 5, 6},message = "[operationType]字段必須為指定值")
    @NotNull(groups = {ForumDTO.KeepNum.class},message = "[operationType]字段不能為空")
    private Byte operationType;

    /**
     * 評論id
     */
    @NotNull(groups = {DeleteMyComment.class},message = "評論id不能為空")
    private Long commentId;
    /**
     * 0 增加操作  1 取消操作
     */
    @EnumValue(groups = {ForumDTO.KeepNum.class},byteValues = {0, 1})
    private Byte clickType;
    /**
     * 模糊查詢字段
     */
    private String keyword;


    /**
     * ########################### 評論使用
     */

    /**
     *  頭像位址
     */
    private String photoPath;


    /**
     * 使用者名
     */
    private String userName;



    public interface AddTopic {
    }

    public interface SearchPostDetailById {
    }

    public interface SearchComment {}

    public interface SearchCommentList {
    }

    public interface KeepNum {
    }

    public interface SearchPostList {
    }

    public interface SearchBlogs {
    }

    public interface Comment {
    }

    public interface SearchLatestNum {
    }

    public interface SearchAddTopicData {
    }

    public interface DeleteMyPost {
    }

    public interface DeleteMyComment {
    }

    public interface SearchPostById {
    }

    public interface UpdatePostById {
    }

    public interface SearchMyReply {
    }

    public interface SearchMyCreatePost {
    }

    public interface SearchPostByBlogId {
    }


    public interface searchMyLikePost {
    }

    public interface UpdateInit {
    }
}

枚舉:
package com.ehl.developerplatform.euem;

import lombok.Getter;

/**
 * @author 王震
 * @date 2020/3/17 21:03
 * 自定義枚舉
 */
@Getter
public enum ResponseEnum {

    /**
     * 200+
     */
    SUCCESS(200, "操作成功"),
    DELETE_SUCCESS(200, "删除成功"),
    COLLECTION_SUCCESS(200, "收藏成功"),
    CANCEL_SUCCESS(200, "取消成功"),
    FAILED(202, "操作失敗,請稍後重試"),
    CREATED(200, "建立成功"),
    UPDATED(200, "修改成功"),
    UPDATE_ERROR(202, "修改失敗"),
    CREATE_ERROR(202, "建立失敗"),
    INVALID_VERIFY_CODE(202, "驗證碼錯誤!"),
    VERIFICATION_CODE_EXPIRED(202, "驗證碼過期!"),
    INVALID_USERNAME_PASSWORD(202, "無效的使用者名和密碼!"),
    USER_NOT_EXIST(202, "使用者不存在"),
    PHONE_CODE_ERROR(202, "手機号驗證碼錯誤"),
    PHONE_CODE_EXPIRED(202, "驗證碼過期"),
    INVALID_SERVER_ID_SECRET(202, "無效的服務id和密鑰!"),
    INSERT_OPERATION_FAIL(202, "新增操作失敗!"),
    UPDATE_OPERATION_FAIL(202, "更新操作失敗!"),
    DELETE_OPERATION_FAIL(202, "删除操作失敗!"),
    FILE_UPLOAD_ERROR(202, "檔案上傳失敗!"),
    DIRECTORY_WRITER_ERROR(202, "目錄寫入失敗!"),
    FILE_WRITER_ERROR(202, "檔案寫入失敗!"),
    SEND_MESSAGE_ERROR(202, "短信發送失敗!"),
    REPEAT_PROCESS(202, "重複處理!"),
    REPEAT_PROCESS_USER(202, "重複邀請!"),
    SECKILL_ALREADY_JOIN_ERROR(203, "目前參與使用者過多,請求稍後重試!"),
    TOO_MANY_VISITS(203, "通路太頻繁,請稍後重試"),
    DATA_NOT_FOUNT(204, "暫無資料"),
    FILE_NOT_FOUNT(204, "未查詢到檔案"),
    ALGORITHM_NOT_FOUND(204, "未找到算法資訊!"),
    DELETE_ERROR_DATA_NOTFOUND(204, "删除失敗!請确認是否有此資料"),

    /**
     * 300+
     */
    USER_NOT_LOGIN(300, "使用者未登入"),

    /**
     * 400+
     */
    PAGE_NOTNULL(400, "目前頁和每頁展示條數頁碼不能為空且頁碼必須大于等于1"),
    PARAM_FORMAT_ERROR(400, "參數格式錯誤"),
    CONTENT_TYPE_ERROR(400, "請檢查Content type類型"),
    JSON_PARAM_FORMAT_ERROR(400, "JSON參數格式錯誤"),
    JSON_INPUT_ERROR(412, "JSON檔案解析失敗!"),
    SIGN_CHANNEL_NOTNULL(400, "報名管道不能為空"),
    INVALID_FILE_TYPE(400, "無效的檔案類型!"),
    INVALID_PARAM_ERROR(400, "無效的請求參數!"),
    INVALID_PHONE_NUMBER(400, "無效的手機号碼"),
    LONG_SIZE(400, "長度不合法"),
    FILE_TO_LONG(400, "檔案大小超出規定"),
    INVALID_NOTIFY_PARAM(401, "回調參數有誤!"),
    INVALID_NOTIFY_SIGN(401, "回調簽名有誤!"),
    APPLICATION_NOT_FOUND(404, "應用不存在!"),
    /**
     * 比賽
     */
    TEAM_IS_NULL(404, "暫無此團隊"),
    COMPETITION_TEAM_ID_NOTNULL(400, "修改團隊時id不能為空"),
    COMPETITION_IS_NULL(404, "暫無此比賽"),

    /**
     * 資料集
     */
    CANNOT_UPDATE(401, "公開資料集不能改為私有資料集"),
    DATA_SET_NOT_FOUND(404, "暫無此資料集"),

    /**
     * 500+
     */
    DATA_TRANSFER_ERROR(500, "資料轉換異常!"),
    INVOKING_ERROR(500, "接口調用失敗!"),
    SQL_EXCEPTION(500, "SQL異常"),

    /**
     * 600+
     */
    UNKNOWN_ERROR(600, "伺服器錯誤"),
    REQUEST_METHOD_ERROR(600, "請求方式錯誤"),

    /**
     * 700+ 702已被使用者中心使用,不定義702
     */
    USER_CENTER_ERROR(700, "使用者中心異常");

    private final int status;
    private final String message;

    ResponseEnum(int status, String message) {
        this.status = status;
        this.message = message;
    }

}

EnumValue是校驗參數的注解,可以注釋掉。