
String code = String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, 5)));
需要依赖的pom: <dependency> <groupId>com.aliyun</groupId> <artifactId>dysmsapi20170525</artifactId> <version>2.0.1</version> </dependency> java代码:package com.ehl.developerplatform.service.impl; import com.alibaba.fastjson.JSONObject; import com.aliyun.dysmsapi20170525.models.SendSmsRequest; import com.aliyun.dysmsapi20170525.models.SendSmsResponse; import com.aliyun.teaopenapi.models.Config; import com.ehl.developerplatform.config.SmsProperties; import com.ehl.developerplatform.enums.ResponseEnum; import com.ehl.developerplatform.exception.DeveloperPlatFormException; import com.ehl.developerplatform.pojo.DataResponse; import com.ehl.developerplatform.util.RedisPrefix; import com.ehl.developerplatform.util.RedisUtil; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.stereotype.Service; import java.util.HashMap; import java.util.Map; /** * #------------------------------------------------------------------- # * # CONFIDENTIAL --- CUSTOM STUDIOS # * #------------------------------------------------------------------- # * # # * # @Project Name : develop # * # # * # @File Name : SmsService.java # * # # * # @Programmer : 王震 # * # # * # @Start Date : 2021/4/28 18:08 # * # # * # @Last Update : 2021/4/28 18:08 # * # # * #------------------------------------------------------------------- # * # Classes: # * # # * #------------------------------------------------------------------- # */ @Slf4j @Service @SuppressWarnings("all") @EnableConfigurationProperties(value = SmsProperties.class) public final class SmsService { @Autowired private SmsProperties smsProperties; @Autowired private RedisUtil redisUtil; private SmsService() { } /** * 功能描述: <br> * 〈根据用户输入的phone发送验证码〉 * * @Param: [phone] 电话号码 * @Return: com.ehl.developerplatform.pojo.DataResponse * @Author: 王震 * @Date: 2021/4/28 19:01 */ public DataResponse<?> sendSmsCode(final String phone) { String code = this.randomCode(); String message = this.sendMessage(phone, code).getBody().getMessage(); if (!"OK".equals(message)) { log.error("手机号码:{},发送短信失败,失败原因:{},code:{}", phone, message,code); return DataResponse.fail(ResponseEnum.SEND_MESSAGE_ERROR); } log.info("手机号码:{},发送短信成功,验证码为:{}", phone, code); redisUtil.set(RedisPrefix.buildSendMessageKey(phone), code, 300); return DataResponse.success("短信发送成功!"); } public SendSmsResponse sendMessage(final String phone, final String code) { try { com.aliyun.dysmsapi20170525.Client client = createClient(smsProperties.getAccessKeyId(), smsProperties.getAccessKeySecret()); Map<String, Object> map = new HashMap<>(); map.put("code", code); SendSmsRequest sendSmsRequest = new SendSmsRequest() .setSignName(smsProperties.getSignName()) .setTemplateCode(smsProperties.getTemplate()) .setPhoneNumbers(phone) .setTemplateParam(JSONObject.toJSONString(map)); // 复制代码运行请自行打印 API 的返回值 return client.sendSms(sendSmsRequest); } catch (Exception e) { log.error("手机号码:{},发送短信失败,失败原因:{}", phone, e.getMessage()); throw new DeveloperPlatFormException(ResponseEnum.SEND_MESSAGE_ERROR); } } /** * 使用AK&SK初始化账号Client * * @param accessKeyId * @param accessKeySecret * @return Client * @throws Exception */ public static com.aliyun.dysmsapi20170525.Client createClient(final String accessKeyId, final String accessKeySecret) throws Exception { Config config = new Config() // 您的AccessKey ID .setAccessKeyId(accessKeyId) // 您的AccessKey Secret .setAccessKeySecret(accessKeySecret); // 访问的域名 config.endpoint = "dysmsapi.aliyuncs.com"; return new com.aliyun.dysmsapi20170525.Client(config); } /** * 功能描述: <br> * 〈随机数6位〉 * * @Return: com.ehl.developerplatform.pojo.DataResponse * @Author: 王震 * @Date: 2021/4/28 19:34 */ private String randomCode() { return String.valueOf((int) ((Math.random() * 9 + 1) * Math.pow(10, 5))); } } 一些项目中的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是校验参数的注解,可以注释掉。
2021年12月