最近做了一個文章的收藏、點贊數量的功能,其實之前也做過類似的功能,因為之前一直使用的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是校驗參數的注解,可以注釋掉。