最近做了一个帖子的收藏、点赞数量的功能,其实之前也做过类似的功能,因为之前一直使用的mysql 总是感觉对于这种频繁需要改变的值,不应该给予Mysql过大的压力,本文章采用的是redis 做了持久化。下面贴出关键代码:DataResponse是项目中使用的结果封装实体类;forumDTO是此功能的参数实体,如果有需要请留言。
常量如下:
privatestaticfinalStringDEFAULT_VALUE="0:0:0:0:0:0"; publicstaticfinalByteBYTE_ZERO=0; publicstaticfinalByteBYTE_ONE=1; publicstaticfinalByteBYTE_TWO=2; publicstaticfinalByteBYTE_THREE=3; publicstaticfinalByteBYTE_FOUR=4; publicstaticfinalByteBYTE_FIVE=5; publicstaticfinalByteBYTE_SIX=6;
publicDataResponsekeepNum(ForumDTOforumDTO) { //将帖子id 设置为 keyStringkey=forumDTO.getPostId().toString(); //get 用户idStringuserId=forumDTO.getUserId(); Stringcount, newCount; //绑定数据集keyBoundHashOperations<String, Object, Object>post=redisTemplate.boundHashOps("post:"); //获取hKey// count: 0论坛-点赞量 1评论量 2收藏量 3浏览 4评论-点赞量if (null==post.get(key)) { //无则setpost.put(key, DEFAULT_VALUE); //再取出来赋值给 countcount=post.get(key).toString(); } else { //有直接赋值 countcount=post.get(key).toString(); } // operationType 1 浏览 2 帖子点赞 3 收藏 4评论-点赞Stringprefix; switch (forumDTO.getOperationType()) { case1: //记录浏览次数 OPERATIONTYPE 1 : 记录浏览次数newCount=resetValue(count, BYTE_THREE, true); post.put(key, newCount); break; case2: //记录帖子-点赞prefix="thumbs:post"; switch (forumDTO.getClickType()) { case0: /*** 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)) { returnDataResponse.fail("不能重复点赞哦"); } else { redisTemplate.opsForSet().add(prefix+":"+key, prefix+":"+userId); } newCount=resetValue(count, BYTE_ZERO, true); //set to redispost.put(key, newCount); break; case1: //OPERATIONTYPE 2: + CLICKTYPE 1 = 取消帖子点赞//1取消帖子点赞if (!redisTemplate.opsForSet().isMember(prefix+":"+key, prefix+":"+userId)) { //重复处理returnDataResponse.fail("不能重复取消哦"); } else { //删除redisTemplate.opsForSet().remove(prefix+":"+key, prefix+":"+userId); } newCount=resetValue(count, BYTE_ZERO, false); post.put(key, newCount); break; } break; case3: prefix="collection:post"; List<MqMessage>sendList=newLinkedList<>(); MqMessagemqMessage=newMqMessage(); switch (forumDTO.getClickType()) { //OPERATIONTYPE 3 + CLICKTYPE 0 = 记录收藏case0: //数量+1//根据用户id + 帖子id 查询redis 数据if (redisTemplate.opsForSet().isMember(prefix+":"+key, prefix+":"+userId)) { //重复处理returnDataResponse.fail("不能重复收藏哦"); } //addredisTemplate.opsForSet().add(prefix+":"+key, prefix+":"+userId); //set to redisnewCount=resetValue(count, BYTE_TWO, true); post.put(key, newCount); mqMessage.setType(newByte("9")); mqMessage.setSenderId(userId); mqMessage.setPostId(forumDTO.getPostId()); sendList.add(mqMessage); this.sendMq.send(sendList); break; //OPERATIONTYPE 3 + CLICKTYPE 1 = 取消收藏case1: //取消收藏//尝试从redis取出当前用户是否已经收藏if (!redisTemplate.opsForSet().isMember(prefix+":"+key, prefix+":"+userId)) { //重复处理returnDataResponse.fail("不能重复取消哦"); } //删除redisTemplate.opsForSet().remove(prefix+":"+key, prefix+":"+userId); newCount=resetValue(count, BYTE_TWO, false); post.put(key, newCount); mqMessage.setType(newByte("10")); mqMessage.setSenderId(userId); mqMessage.setPostId(forumDTO.getPostId()); sendList.add(mqMessage); this.sendMq.send(sendList); break; } break; case4: //记录评论-点赞// OPERATIONTYPE 4: + CLICKTYPE 0 = 给评论点赞if (null==forumDTO.getCommentId()) { returnDataResponse.fail("评论id不能为空"); } StringcommentNum, ckey=forumDTO.getCommentId().toString(); BoundHashOperations<String, Object, Object>comment=redisTemplate.boundHashOps("post:comment"); if (null==comment.get(ckey)) { //无则setcomment.put(ckey, "0"); //再取出来赋值给 countcommentNum=comment.get(ckey).toString(); } else { //有直接赋值 countcommentNum=comment.get(ckey).toString(); } //赞评论prefix="thumbs:comment"; switch (forumDTO.getClickType()) { case0: /*** 0点赞* 从redis中获取数量 帖子d 例如:177488r88t78r78r7* count: 0论坛-点赞量 1评论量 2收藏量 3浏览 4评论-点赞量* 避免每种数量都去查询redis 直接通过 redis value 记录所有的数量* 获取加 + 后的值*/if (redisTemplate.opsForSet().isMember(prefix+":"+ckey, prefix+":"+userId)) { returnDataResponse.fail("不能重复点赞哦"); } else { redisTemplate.opsForSet().add(prefix+":"+ckey, prefix+":"+userId); } //set to rediscomment.put(ckey, cResetValue(commentNum, true)); break; case1: //1取消评论点赞if (!redisTemplate.opsForSet().isMember(prefix+":"+ckey, prefix+":"+userId)) { //重复处理returnDataResponse.fail("不能重复取消哦"); } else { //删除redisTemplate.opsForSet().remove(prefix+":"+ckey, prefix+":"+userId); } newCount=cResetValue(commentNum, false); comment.put(ckey, newCount); break; } break; default: DataResponse.fail(ResponseEnum.FAILED); } returnDataResponse.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的包需要验证正则,效率较低。*/privateStringresetValue(Stringval, intj, booleanisPlus) { String[] value=StringUtils.split(val, ":"); Longtemp=Long.valueOf(value[j]); StringBuffersb=newStringBuffer(16); if (isPlus) { temp+=1; } else { temp-=1; } value[j] =temp.toString(); for (inti=0, len=value.length; i<len; i++) { if (i!=len-1) { sb.append(value[i]).append(":"); }else { sb.append(value[i]); } } returnsb.toString(); } #***下面附上DataResponse和DTO代码:***DataResponse:packagecom.ehl.developerplatform.pojo; importcom.ehl.developerplatform.euem.ResponseEnum; importlombok.Data; publicfinalclassDataResponse<T> { privatebooleansuccess; privateTdata; publicDataResponse<T>setData(Tdata) { this.data=data; returnthis; } privateIntegercode; privateStringmessage; publicDataResponse<T>setMessage(Stringmessage) { this.message=message; returnthis; } publicDataResponse() { } publicDataResponse(booleansuccess, Tdata, Integercode, Stringmessage) { this.code=code; this.data=data; this.message=message; this.success=success; } publicstatic<T>DataResponse<T>success(Stringmessage) { returnnewDataResponse<T>(true, null, ResponseEnum.SUCCESS.getStatus(), message); } publicstatic<T>DataResponse<T>success() { returnnewDataResponse<>(true, null, ResponseEnum.SUCCESS.getStatus(), ResponseEnum.SUCCESS.getMessage()); } publicstatic<T>DataResponse<T>success(ResponseEnumresponseEnum) { returnnewDataResponse<>(true, null, responseEnum.getStatus(), responseEnum.getMessage()); } publicstatic<T>DataResponse<T>success(Tdata) { returnnewDataResponse<>(true, data, ResponseEnum.SUCCESS.getStatus(), ResponseEnum.SUCCESS.getMessage()); } publicstatic<T>DataResponse<T>success(ResponseEnumresponseEnum, Tdata) { returnnewDataResponse<>(true, data, responseEnum.getStatus(), responseEnum.getMessage()); } publicstatic<T>DataResponse<T>success(ResponseEnumresponseEnum, Tdata, Object... args) { Strings=String.format(responseEnum.getMessage(), args); returnnewDataResponse<>(true, data, responseEnum.getStatus(), s); } publicstatic<T>DataResponse<T>fail() { returnnewDataResponse<>(false, null, ResponseEnum.FAILED.getStatus(), ResponseEnum.FAILED.getMessage()); } publicstatic<T>DataResponse<T>fail(ResponseEnumresponseEnum) { returnnewDataResponse<>(false, null, responseEnum.getStatus(), responseEnum.getMessage()); } publicstatic<T>DataResponse<T>fail(Tdata) { returnnewDataResponse<>(false, data, ResponseEnum.FAILED.getStatus(), ResponseEnum.FAILED.getMessage()); } publicstatic<T>DataResponse<T>fail(Stringmessage) { returnnewDataResponse<>(false, null, ResponseEnum.FAILED.getStatus(), message); } publicstatic<T>DataResponse<T>fail(Integercode, Stringmessage) { returnnewDataResponse<>(false, null, code, message); } publicstatic<T>DataResponse<T>fail(ResponseEnumresponseEnum, Tdata) { returnnewDataResponse<>(false, data, responseEnum.getStatus(), responseEnum.getMessage()); } publicstatic<T>DataResponse<T>fail(ResponseEnumresponseEnum, Tdata, Object... args) { Strings=String.format(responseEnum.getMessage(), args); returnnewDataResponse<>(false, data, responseEnum.getStatus(), s); } } DTO: packagecom.ehl.developerplatform.pojo.dto; importcom.ehl.developerplatform.anno.EnumValue; importlombok.Data; importjavax.validation.constraints.Min; importjavax.validation.constraints.NotEmpty; importjavax.validation.constraints.NotNull; importjavax.validation.constraints.Size; importjava.io.Serializable; publicclassForumDTOimplementsSerializable { groups= {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchPostByBlogId.class,SearchCommentList.class,SearchPostList.class},value=1, message="每页数据最少一条") (groups= {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchPostByBlogId.class,SearchCommentList.class,SearchPostList.class},message="[pageSize]字段不能为空") (privateIntegerpageSize; groups= {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchCommentList.class,SearchPostByBlogId.class,SearchPostList.class},value=1, message="页码必须大于零") (groups= {ForumDTO.SearchComment.class,SearchMyReply.class,searchMyLikePost.class,SearchMyCreatePost.class,SearchCommentList.class,SearchPostByBlogId.class,SearchPostList.class},message="[pageNum]字段不能为空") (privateIntegerpageNum; /*** 排序 1 更新日期 2 最近一天 3 最近三天 4 最近三个月 默认 1*/groups= {ForumDTO.SearchPostList.class}, byteValues= {1, 2, 3, 4},message="更新日期必须为指定值") (privateByteupdateTime; /*** 帖子名称*/groups= {AddTopic.class},message="帖子名称不能为空") (groups= {AddTopic.class},max=30,min=1,message="帖子名称长度最长不能超过30个字符 最短不能小于1个字符") (privateStringname; /*** 专题父id*/groups= {Comment.class,DeleteMyComment.class},message="父id不能为空") (privateLongpid; /*** postId 帖子id*/groups= {SearchCommentList.class,UpdateInit.class,DeleteMyPost.class,KeepNum.class,Comment.class,SearchLatestNum.class,SearchPostById.class,SearchPostDetailById.class},message="贴子id不能为空") (privateLongpostId; /*** 专区id*/groups= {AddTopic.class,Comment.class,SearchPostByBlogId.class},message="所属专题id不能为空") (privateLongblogId; /***帖子分类 1 技术问答 2 经验分享 3 大赛公告 4 大赛组队 5 全部 6 精华区 注:大赛组队只有选择大赛专区才能选择*/groups= {AddTopic.class},message="帖子分类不能为空") (groups= {SearchPostList.class,SearchPostByBlogId.class},byteValues= {1,2,3,4,5,6},message="帖子分类必须为指定值") (privateByteclassFy; /*** 专区名称*/groups= {AddTopic.class},message="所属专题名不能为空") (privateStringtitle; /*** 评论内容*/groups= {Comment.class,AddTopic.class},message="评论内容不能为空") (privateStringcontent; privateStringmarkText; /*** 用户id 发帖人id*/groups= {AddTopic.class,SearchMyReply.class,KeepNum.class,searchMyLikePost.class,SearchMyCreatePost.class,UpdatePostById.class,DeleteMyComment.class,Comment.class,DeleteMyPost.class,SearchLatestNum.class},message="用户id不能为空") (privateStringuserId; /*** operationType 1 浏览 2 帖子点赞 3 收藏 4评论-点赞*/groups= {ForumDTO.KeepNum.class},byteValues= {1, 2, 3, 4, 5, 6},message="[operationType]字段必须为指定值") (groups= {ForumDTO.KeepNum.class},message="[operationType]字段不能为空") (privateByteoperationType; /*** 评论id*/groups= {DeleteMyComment.class},message="评论id不能为空") (privateLongcommentId; /*** 0 增加操作 1 取消操作*/groups= {ForumDTO.KeepNum.class},byteValues= {0, 1}) (privateByteclickType; /*** 模糊查询字段*/privateStringkeyword; /*** ########################### 评论使用*//*** 头像地址*/privateStringphotoPath; /*** 用户名*/privateStringuserName; publicinterfaceAddTopic { } publicinterfaceSearchPostDetailById { } publicinterfaceSearchComment {} publicinterfaceSearchCommentList { } publicinterfaceKeepNum { } publicinterfaceSearchPostList { } publicinterfaceSearchBlogs { } publicinterfaceComment { } publicinterfaceSearchLatestNum { } publicinterfaceSearchAddTopicData { } publicinterfaceDeleteMyPost { } publicinterfaceDeleteMyComment { } publicinterfaceSearchPostById { } publicinterfaceUpdatePostById { } publicinterfaceSearchMyReply { } publicinterfaceSearchMyCreatePost { } publicinterfaceSearchPostByBlogId { } publicinterfacesearchMyLikePost { } publicinterfaceUpdateInit { } } 枚举:packagecom.ehl.developerplatform.euem; importlombok.Getter; /*** @author 王震* @date 2020/3/17 21:03* 自定义枚举*/publicenumResponseEnum { /*** 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, "用户中心异常"); privatefinalintstatus; privatefinalStringmessage; ResponseEnum(intstatus, Stringmessage) { this.status=status; this.message=message; } } EnumValue是校验参数的注解,可以注释掉。