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是校验参数的注解,可以注释掉。
上一篇:ehcache整合spring本地接口方式


下一篇:ehcache整合spring注解方式