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();
}
private String cResetValue(String count, boolean b) {
Long c = Long.parseLong(count);
if (b) {
c += 1;
} else {
c -= 1;
}
return c.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是校验参数的注解,可以注释掉。
斜体样式