【Redis使用系列】使用Redis做防止重复提交

前言

在平时的开发中我们都需要处理重复提交的问题,避免业务出错或者产生脏数据,虽然可以通过前端控制但这并不是可以完全避免,最好的方式还是前后端均进行控制,这样的话就可以更有效,尽可能全面的去减少错误的发生。

一、比如我们注册的时候需要发送验证码

如果用户频繁点击或者恶意攻击的话就会造成不断的请求对服务器产生很大的压力,为了避免这种情况我们需要做处理,传统的模式中是在数据库中记录手机号、验证码已经发送时间,再次请求的时候呢去数据库查询是否有该手机号记录,并校验是否超过间隔时间,如果超过则重新发送并更新时间,否组不予发送,这样有一个缺点就是如果同时又很多人在做相同的业务同时查询就会对数据库造成很大的压力。

根据此种情况我们可以使用Redis incrde 原子性递增,来解决这种高并发的秒杀或者分布式序列号生成等场景。鉴于本场景我们只用他来做计数实现间隔时间内只接收一次请求。

实现逻辑:在发送短信之后使用Redis的incr设置一个递增的KEY(根据自己的需要设定但是要保证每一个人的唯一),来判断该KEY的数值,如果等于1说明这是第一次请求,发送短信记录日志,并设置有效期,如果不等于的话说明是间隔时间内多次请求,就提示请求频繁,稍后重试。

 1 String redisKey = "SMS_SEND_" + smsPhone;
 2 long count = redisTemplate.opsForValue().increment(redisKey, 1);
 3 if (count == 1) {
 4 //设置有效期一分钟
 5   redisTemplate.expire(redisKey, 60, TimeUnit.SECONDS);
 6 }
 7 if (count > 1) {
 8    resultMap.put("retCode", "-1");
 9    resultMap.put("retMsg", "每分钟只能发送一次短信");
10    outPrintJson(resultMap);
11    return;
12 }
13 /** 发送短信 */
14 ......
15 /** 记录发送日志 */
16 ......

二、上述方式可以解决特定的问题,当需要处理的情况多的话我们可以考虑使用切面来解决

代码如下:

 1 package com.cul.culsite.annotation;
 2
 3 public class RedisLockBean {
 4     private String key;
 5     private int timeInSecond;
 6     private String codeName;
 7     private String msgName;
 8     private String code;
 9     private String msg;
10     private boolean isAtController;
11     private boolean isAtService;
12     private boolean isAtParameter;
13     private String returnType;
14     public String getKey() {
15         return key;
16     }
17     public void setKey(String key) {
18         this.key = key;
19     }
20     public int getTimeInSecond() {
21         return timeInSecond;
22     }
23     public void setTimeInSecond(int timeInSecond) {
24         this.timeInSecond = timeInSecond;
25     }
26     public String getCodeName() {
27         return codeName;
28     }
29     public void setCodeName(String codeName) {
30         this.codeName = codeName;
31     }
32     public String getMsgName() {
33         return msgName;
34     }
35     public void setMsgName(String msgName) {
36         this.msgName = msgName;
37     }
38     public String getCode() {
39         return code;
40     }
41     public void setCode(String code) {
42         this.code = code;
43     }
44     public String getMsg() {
45         return msg;
46     }
47     public void setMsg(String msg) {
48         this.msg = msg;
49     }
50
51
52     public boolean isAtController() {
53         return isAtController;
54     }
55     public void setAtController(boolean isAtController) {
56         this.isAtController = isAtController;
57     }
58     public boolean isAtService() {
59         return isAtService;
60     }
61     public void setAtService(boolean isAtService) {
62         this.isAtService = isAtService;
63     }
64     public boolean isAtParameter() {
65         return isAtParameter;
66     }
67     public void setAtParameter(boolean isAtParameter) {
68         this.isAtParameter = isAtParameter;
69     }
70     public String getReturnType() {
71         return returnType;
72     }
73     public void setReturnType(String returnType) {
74         this.returnType = returnType;
75     }
76     @Override
77     public String toString() {
78         return "RedisLockBean [key=" + key + ", timeInSecond=" + timeInSecond
79                 + ", codeName=" + codeName + ", msgName=" + msgName + ", code="
80                 + code + ", msg=" + msg + ", isAtController=" + isAtController
81                 + ", isAtService=" + isAtService + ", isAtParameter="
82                 + isAtParameter + ", returnType=" + returnType + "]";
83     }
84 }
 1 package com.cul.culsite.annotation;
 2
 3 import java.lang.annotation.ElementType;
 4 import java.lang.annotation.Retention;
 5 import java.lang.annotation.RetentionPolicy;
 6 import java.lang.annotation.Target;
 7 /**
 8  * 解决的问题:<br>
 9  *         1.数据库加锁性能较差<br>
10  *          2.数据库加锁,若相应线程异常,所无法释放<br>
11  * 注意事项:<br>
12  *           方法的返回值对象必须包含错误码,错误信息属性及其的get方法
13  *
14  */
15 @Target({ElementType.PARAMETER,ElementType.METHOD})
16 @Retention(RetentionPolicy.RUNTIME)
17 public @interface RedisLock {
18     /**
19      * 若加注解的入参时基本数据类型(int,long)或String时,fieldName无效<br>
20      * 若注解的参数是自定义对象时,请注意一下几点:<br>
21      *         1.确保定义有相应属性public修饰的get方法<br>
22      *         2.get方法的返回参数是基本的数据类型或String<br>
23      *         3.get方法的返回值不为空<br>
24      * 否则,加锁失败.
25      * @return
26      */
27     String[] fieldName() default {};
28     /**
29      * 锁的有效时间,单位为秒,默认值为1
30      * @return
31      */
32     int timeInSecond() default 1;
33     /**
34      * 加锁,锁已被其它请求获取时,直接返回重复提交,codeName指定返回对象的返回码对应的属性,默认值‘code‘
35      * @return
36      */
37     String codeName() default "code";
38     /**
39      * 加锁,锁已被其它请求获取时,直接返回重复提交,msgName指定返回对象的返回信息对应的属性,默认值‘msg‘
40      * @return
41      */
42     String msgName() default "msg";
43     /**
44      * 加锁,锁已被其它请求获取时,直接返回重复提交,code指定返回对象的返回码对应的值,默认值‘09‘
45      * @return
46      */
47     String code() default "09";
48     /**
49      * 加锁,锁已被其它请求获取时,直接返回重复提交,msg指定返回对象的返回码对应的值,默认值‘重复提交‘
50      * @return
51      */
52     String msg()  default "重复提交";
53     /**
54      * 注解作用与方法时,指定参数在参数列表中的索引
55      */
56     int paramIndex() default 0;
57 }
  1 package com.cul.culsite.annotation;
  2
  3 import java.lang.annotation.Annotation;
  4 import java.lang.reflect.InvocationTargetException;
  5 import java.lang.reflect.Method;
  6 import java.util.ArrayList;
  7 import java.util.Arrays;
  8 import java.util.Collections;
  9 import java.util.HashMap;
 10 import java.util.List;
 11 import java.util.Map;
 12
 13 import javax.servlet.http.HttpServletRequest;
 14
 15 import org.apache.commons.lang.StringUtils;
 16 import org.aspectj.lang.ProceedingJoinPoint;
 17 import org.aspectj.lang.Signature;
 18 import org.aspectj.lang.reflect.MethodSignature;
 19 import org.slf4j.Logger;
 20 import org.slf4j.LoggerFactory;
 21 import org.springframework.beans.factory.annotation.Autowired;
 22 import org.springframework.stereotype.Component;
 23 import org.springframework.stereotype.Controller;
 24 import org.springframework.web.bind.annotation.RequestMapping;
 25
 26 import com.alibaba.fastjson.JSONObject;
 27 import com.cul.culsite.common.RedisKeyConstants;
 28 import com.cul.culsite.service.RedisService;
 29 import com.cul.culsite.util.DateUtil;
 30 import com.cul.culsite.util.OxmHelper;
 31
 32 @Component
 33 public class RedisLockAspect {
 34     private final static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class);
 35
 36     protected static final String XML_TYPE = "xml";
 37     protected static final String JSON_TYPE = "json";
 38     protected static final String ILLEGAL_TYPE = "illegal type";
 39     @Autowired
 40     private RedisService redisService;
 41
 42     public Object redisLockParse(ProceedingJoinPoint p) throws Throwable{
 43         Signature signature = p.getSignature();
 44         boolean isRepetition = false;
 45         RedisLockBean redisLockBean = null;
 46         String value = System.nanoTime()+"";
 47         if(signature instanceof MethodSignature){
 48             //获得接口中定义的方法的Method,但注解时加载实现类中方法的参数上
 49             MethodSignature methodSignature = (MethodSignature)signature;
 50             Method serviceMethod = methodSignature.getMethod();
 51
 52             try {
 53                 Method serviceImpMethod = p.getTarget().getClass().getMethod(serviceMethod.getName(), serviceMethod.getParameterTypes());
 54                 //获取key值
 55                 redisLockBean = getRedisLockKey(p.getTarget(),serviceImpMethod,p.getArgs());
 56                 //成功获取key值,在redis中加锁
 57                 if(redisLockBean!=null){
 58                     logger.info("redis lock value is :{}",value);
 59                     boolean isPutSuccess =redisService.setIfAbsent(redisLockBean.getKey(), value, redisLockBean.getTimeInSecond());
 60                     //加锁失败,直接返回
 61                     if(!isPutSuccess){
 62                         logger.info("get redis lock fail for {}",redisLockBean.getKey());
 63                         if(redisLockBean.isAtParameter()||redisLockBean.isAtService()){
 64                             Class<?> returnType = serviceImpMethod.getReturnType();
 65                             //加锁方法有返回值
 66                             if(!returnType.getName().equals(java.lang.Void.class.getName())){
 67                                 //实例化返回值对象
 68                                 try {
 69                                     Object result = returnType.newInstance();
 70                                     //设置返回码
 71                                     returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getCodeName()), java.lang.String.class).invoke(result, redisLockBean.getCode());
 72                                     //设置返回信息
 73                                     returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getMsgName()), java.lang.String.class).invoke(result, redisLockBean.getMsg());
 74                                     return result;
 75                                 } catch (InstantiationException e) {
 76                                     e.printStackTrace();
 77                                 } catch (IllegalAccessException e) {
 78                                     e.printStackTrace();
 79                                 } catch (IllegalArgumentException e) {
 80                                     e.printStackTrace();
 81                                 } catch (InvocationTargetException e) {
 82                                     e.printStackTrace();
 83                                 }
 84                             }else{
 85                                 throw new RuntimeException("@RedisLock作用的方法没有返回参数");
 86                             }
 87                         }else if(redisLockBean.isAtController()){
 88                             Map<String,String> result = new HashMap<String,String>();
 89                             result.put(redisLockBean.getCodeName(), redisLockBean.getCode());
 90                             result.put(redisLockBean.getMsgName(), redisLockBean.getMsg());
 91                             return response(redisLockBean.getReturnType()==null?"json":redisLockBean.getReturnType(), result);
 92                         }
 93                     }else{
 94                         logger.info("get redis lock success for {}",redisLockBean.getKey());
 95                         isRepetition = true;
 96                     }
 97                 }
 98             } catch (NoSuchMethodException e) {
 99                 e.printStackTrace();
100             } catch (SecurityException e) {
101                 e.printStackTrace();
102             }
103         }
104         Object result = null;
105         try {
106             result = p.proceed();
107         } catch (Throwable e) {
108             throw e;
109         }finally{
110             if(redisLockBean!=null){
111                 if(isRepetition&&value.equals(redisService.get(redisLockBean.getKey()))){
112                     logger.info("lock has released :{}",redisLockBean.getKey());
113                     redisService.delete(redisLockBean.getKey());
114                 }
115
116             }
117         }
118         return result;
119     }
120
121     private RedisLockBean getRedisLockKey(Object target,Method method,Object... object){
122         if(target == null){
123             throw new RuntimeException("get redis lock key error,target is null");
124         }
125         if(method==null){
126             throw new RuntimeException("get redis lock key error,method is null");
127         }
128         List<String> fieldValueList = new ArrayList<String>();
129         RedisLockBean redisLockBean = new RedisLockBean();
130         RedisLock redisLock = null;
131         //类上有@Controller说明@RedisLock是放在请求方法上,使用HttpServletRequest获取请求参数
132         if(method.isAnnotationPresent(RedisLock.class)&&target.getClass().isAnnotationPresent(Controller.class)){
133             //controller层方法时对外开放的接口
134             if(method.isAnnotationPresent(RequestMapping.class)){
135                 redisLock = method.getAnnotation(RedisLock.class);
136                 //获取方法中的HttpServletRequest类型的参数
137                 HttpServletRequest request = null;
138                 for(Object para:object){
139                     if(para instanceof HttpServletRequest){
140                         request = (HttpServletRequest)para;
141                         break;
142                     }
143                 }
144                 if(request==null){
145                     throw new RuntimeException("@RedisLock作用于controller层方法时,方法需要包含HttpServletRequest类型的参数");
146                 }
147                 //未定义加锁参数时,默认使用mac
148                 String[] paraName = redisLock.fieldName();
149                 if(paraName==null||paraName.length==0){
150                     paraName=new String[]{"mac"};
151                 }
152                 for(String para:paraName){
153                     fieldValueList.add(request.getParameter(para));
154                 }
155                 if(fieldValueList.isEmpty()){
156                     throw new RuntimeException("@RedisLock作用于controller层方法时,生成key失败,请求中没有mac签名");
157                 }
158                 //标示注解作用在controller成方法上
159                 redisLockBean.setAtController(true);
160             }else{
161                 throw new RuntimeException("@RedisLock作用于controller层的方法时,该方法上需要使用@RequestMapping注解");
162             }
163             //注解作用于非controller层方法上
164         }else if(method.isAnnotationPresent(RedisLock.class)){
165             redisLock = method.getAnnotation(RedisLock.class);
166             //参数的索引位置
167             int index = redisLock.paramIndex();
168             String[] fieldName = redisLock.fieldName();
169             String[] values = getFieldValue(object[index],fieldName);
170             //注解的参数时基本的数据类型或String,不需要传入属性名称,否则设置的属性,都必须获得该属性值
171             if(values==null || values.length!=fieldName.length && fieldName.length>0){
172                 return null;
173             }
174             fieldValueList.addAll(Arrays.asList(values));
175             redisLockBean.setAtService(true);
176         }else{
177             Annotation[][] annotations;
178             annotations = method.getParameterAnnotations();
179             for(int i=0;i<annotations.length;i++){
180                 for(Annotation annotation:annotations[i]){
181                     if(annotation instanceof RedisLock){
182                         RedisLock redisLockTmp = (RedisLock)annotation;
183                         if(redisLock==null){
184                             redisLock = redisLockTmp;
185                         }
186                         String[] fieldName = redisLockTmp.fieldName();
187                         String[] values = getFieldValue(object[i],fieldName);
188                         //注解的参数时基本的数据类型或String,不需要传入属性名称,否则设置的属性,都必须获得该属性值
189                         if(values==null || values.length!=fieldName.length && fieldName.length>0){
190                             return null;
191                         }
192                         fieldValueList.addAll(Arrays.asList(values));
193                         redisLockBean.setAtParameter(true);
194                     }
195                 }
196             }
197         }
198         //未使用注解
199         if(fieldValueList.isEmpty()){
200             return null;
201         }
202
203         //设置其它参数值
204         if(redisLockBean.getTimeInSecond()==0){
205             redisLockBean.setTimeInSecond(redisLock.timeInSecond());
206         }
207         if(StringUtils.isEmpty(redisLockBean.getCodeName())){
208             redisLockBean.setCodeName(redisLock.codeName());
209         }
210         if(StringUtils.isEmpty(redisLockBean.getCode())){
211             redisLockBean.setCode(redisLock.code());
212         }
213         if(StringUtils.isEmpty(redisLockBean.getMsgName())){
214             redisLockBean.setMsgName(redisLock.msgName());
215         }
216         if(StringUtils.isEmpty(redisLockBean.getMsg())){
217             redisLockBean.setMsg(redisLock.msg());
218         }
219
220         Collections.sort(fieldValueList);
221         logger.info("all value of fieldName is {}",fieldValueList);
222         //生成key值
223         StringBuilder builder = new StringBuilder();
224         builder.append(target.getClass().getName())
225         .append("-")
226         .append(method.getName())
227         .append("-")
228         .append(Arrays.asList(method.getParameterTypes()))
229         .append("-")
230         .append(fieldValueList);
231         String lockKey = RedisKeyConstants.REDIS_LOCK + builder.toString();
232         logger.info("redis lock key is :{}",builder.toString());
233         redisLockBean.setKey(lockKey);
234         logger.info("redisLockBean :{}",redisLockBean.toString());
235         return redisLockBean;
236     }
237     private String[] getFieldValue(Object argObj,String...fieldName){
238         if(fieldName ==null || fieldName.length == 0){
239             return new String[]{getBaseClassValue(argObj)};
240         }
241         List<String> fieldsValue = new ArrayList<String>();
242         for(String field:fieldName){
243             String value = getFieldValue(argObj,field);
244             logger.info("value of fieldName ‘{}‘ is :{}",fieldName,value);
245             if(value!=null){
246                 fieldsValue.add(value);
247             }
248         }
249         return fieldsValue.toArray(new String[0]);
250     }
251     private String getFieldValue(Object argObj,String fieldName){
252         if(argObj==null){
253             throw new RuntimeException("argObj is null,cannot get field value of fieldName");
254         }
255         String value = getBaseClassValue(argObj);
256         if(!StringUtils.isEmpty(value)){
257             return value;
258         }
259         String methodName = getGetMethodValueByFieldName(fieldName);
260         Object result = null;
261         try {
262             Method method = argObj.getClass().getMethod(methodName);
263             result = method.invoke(argObj);
264         } catch (NoSuchMethodException e) {
265             logger.error("method {} without parameter is not exists!",methodName);
266             e.printStackTrace();
267         } catch (SecurityException e) {
268             e.printStackTrace();
269         } catch (IllegalAccessException e) {
270             logger.error("method {} without parameter is not public!",methodName);
271             e.printStackTrace();
272         } catch (IllegalArgumentException e) {
273             logger.error("method {} has parameter!",methodName);
274             e.printStackTrace();
275         } catch (InvocationTargetException e) {
276             e.printStackTrace();
277         }
278         if(result==null){
279             logger.warn("method {} does not have returnValue",methodName);
280             return null;
281         }
282         return getBaseClassValue(result);
283     }
284     private String getBaseClassValue(Object object){
285         if(object==null){
286             throw new RuntimeException("argObj is null,cannot get field value ");
287         }
288         if(object instanceof String){
289             return object.toString();
290         }
291         if(object instanceof Integer){
292             int i = (Integer)object;
293             //剔除成员变量的默认值
294             if(i!=0){
295                 return i+"";
296             }
297         }
298         if(object instanceof Long){
299             long i = (Long)object;
300             if(i!=0){
301                 return i+"";
302             }
303         }
304         return null;
305     }
306
307     private String getGetMethodValueByFieldName(String fieldName){
308         return getMethodNameByFieldNameAndPrefix("get",fieldName);
309     }
310     private String getSetMethodNameByFieldName(String fieldName){
311         return getMethodNameByFieldNameAndPrefix("set",fieldName);
312     }
313     private String getMethodNameByFieldNameAndPrefix(String prefix,String fieldName){
314         if(StringUtils.isEmpty(fieldName)){
315             throw new RuntimeException("cannot get Get method by null or length is 0");
316         }
317         if(StringUtils.isEmpty(prefix)){
318             throw new RuntimeException("cannot get Get method by null without prefix");
319         }
320         String getMethodName = prefix+fieldName.substring(0, 1).toUpperCase();
321         //fieldName 的长度大于一时,索引大于一的字符不改变大小写
322         if(fieldName.length()>1){
323             getMethodName = getMethodName + fieldName.substring(1);
324         }
325         return getMethodName;
326     }
327
328     private String response(String type, Object obj) {
329         if (XML_TYPE.equalsIgnoreCase(type)) {
330             String ret = OxmHelper.marshal(obj);
331             logger.info("response:{}",ret);
332             return ret;
333         }
334         if (JSON_TYPE.equalsIgnoreCase(type)) {
335             String ret = JSONObject.toJSONString(obj);
336             logger.info("response:{}",ret);
337             return ret;
338         }
339         return ILLEGAL_TYPE + ":" + type;
340     }
341 }
  1 package com.cul.culsite.service.impl;
  2
  3 import java.util.ArrayList;
  4 import java.util.List;
  5 import java.util.Set;
  6 import java.util.concurrent.TimeUnit;
  7
  8 import org.slf4j.Logger;
  9 import org.slf4j.LoggerFactory;
 10 import org.springframework.beans.factory.annotation.Autowired;
 11 import org.springframework.dao.DataAccessException;
 12 import org.springframework.data.redis.core.RedisOperations;
 13 import org.springframework.data.redis.core.RedisTemplate;
 14 import org.springframework.data.redis.core.SessionCallback;
 15 import org.springframework.data.redis.core.ValueOperations;
 16 import org.springframework.stereotype.Service;
 17 import org.springframework.util.Assert;
 18
 19 import com.cul.culsite.service.RedisService;
 20
 21 @Service
 22 public class RedisServiceImpl implements RedisService {
 23     private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class);
 24     @Autowired
 25     private RedisTemplate<String, String> redisTemplate;
 26
 27     /**
 28      * 获取redis值
 29      *
 30      * @param key
 31      *            键
 32      * @return 值
 33      */
 34     public String get(String key) {
 35         Assert.hasText(key, "redis get key cannot null");
 36
 37         return redisTemplate.opsForValue().get(key);
 38     }
 39
 40     /**
 41      * 删除redis键
 42      *
 43      * @param key
 44      *            键
 45      */
 46     @Override
 47     public  void delete(String key) {
 48         Assert.hasText(key, "redis delete key cannot null");
 49         redisTemplate.delete(key);
 50     }
 51
 52
 53
 54     /**
 55      * 设置redis值
 56      *
 57      * @param key
 58      *            键
 59      * @param value
 60      *            值
 61      * @param time
 62      *            时间(分钟)   如果小于0 默认为1分钟
 63      */
 64     @Override
 65     public  boolean setIfAbsent(final String key,final String value,int time) {
 66         Assert.hasText(key, "redis set key cannot null");
 67         Assert.hasText(value, "redis set value cannot null");
 68
 69         if(time<=0){
 70             time = 1;
 71         }
 72         final int timeInSecond = time;
 73         try{
 74
 75             @SuppressWarnings("unchecked")
 76             Object isSetSuccess = redisTemplate.execute(new SessionCallback() {
 77
 78                 @Override
 79                 public Object execute(RedisOperations arg0)
 80                         throws DataAccessException {
 81                     try{
 82                          //开始事务
 83                         List<Object> result=new ArrayList<Object>();
 84                         arg0.multi();
 85                         arg0.opsForValue().setIfAbsent(key, value);
 86 //                        arg0.expireAt(key,DateUtils.addSeconds(new Date(),timeInSecond));
 87                         arg0.expire(key, timeInSecond, TimeUnit.SECONDS);
 88                      //提交事务
 89                         result= arg0.exec();
 90
 91                         logger.info("redis mutil for get lock result is :{}",result);
 92                      //执行了两次redis操作,应该有两个返回值,否则防止key永久有效,执行删除操作
 93                         if(result == null||result.size()!=2){
 94                             redisTemplate.delete(key);
 95                             return false;
 96                         }
 97                      //获取加锁操作的返回结果
 98                         boolean setIfAbsentResult = false;
 99                         if(result.get(0) instanceof Boolean){
100                             setIfAbsentResult =(Boolean)result.get(0);
101                         }
102                      //获取设置key有效时间返回结果
103                         boolean expireAtResult = false;
104                         if(result.get(1) instanceof Boolean){
105                             expireAtResult = (Boolean)result.get(1);
106                         }
107                         if(setIfAbsentResult&&expireAtResult){
108                             logger.info("加锁成功.......");
109                             return true;
110                         }
111                     }catch(Exception e){
112                         e.printStackTrace();
113                     }
114                     return false;
115                 }
116
117             });
118             if(isSetSuccess instanceof Boolean){
119                 return (Boolean) isSetSuccess;
120             }
121             return false;
122         }catch(Exception e){
123             e.printStackTrace();
124             return false;
125         }
126     }
127
128     @Override
129     public Set<String> keys(String keyPattern) {
130         Assert.hasText(keyPattern, "keys pattern is null");
131
132         return redisTemplate.keys(keyPattern);
133     }
134
135     @Override
136     public long incr(String key) {
137         Assert.hasText(key, "key is null");
138
139         return redisTemplate.opsForValue().increment(key, 1L);
140     }
141     @Override
142     public long decr(String key) {
143         Assert.hasText(key, "key is null");
144
145         return redisTemplate.opsForValue().increment(key, -1L);
146     }
147
148     @Override
149     public void set(String key, String value) {
150         Assert.hasText(key, "key is null");
151         Assert.hasText(value, "value is null");
152         redisTemplate.opsForValue().set(key, value);
153     }
154
155     @Override
156     public boolean set(final String key, final long value, final int timeInSecond) {
157         Assert.hasText(key, "key is null");
158         Assert.hasText(value + "", "value is null");
159         Assert.hasText(timeInSecond + "", "timeInSecond is null");
160
161         try{
162
163             @SuppressWarnings("unchecked")
164             Object isSetSuccess = redisTemplate.execute(new SessionCallback() {
165
166                 @Override
167                 public Object execute(RedisOperations arg0)
168                         throws DataAccessException {
169                     try{
170                          //开始事务
171                         List<Object> result=new ArrayList<Object>();
172                         arg0.multi();
173                         arg0.opsForValue().increment(key, value);
174                         arg0.expire(key, timeInSecond, TimeUnit.SECONDS);
175                      //提交事务
176                         result= arg0.exec();
177
178                         logger.info("result of redis set long value is :{}",result);
179                      //执行了两次redis操作,应该有两个返回值,否则防止key永久有效,执行删除操作
180                         if(result == null || result.size() != 2){
181                             redisTemplate.opsForValue().increment(key, (0 - value));
182                             return false;
183                         }
184                      //获取加锁操作的返回结果
185                         long incrementResult = 0;
186                         if(result.get(0) instanceof Long){
187                             incrementResult =(Long)result.get(0);
188                         }
189                      //获取设置key有效时间返回结果
190                         boolean expireAtResult = false;
191                         if(result.get(1) instanceof Boolean){
192                             expireAtResult = (Boolean)result.get(1);
193                         }
194                         if((incrementResult == value) && expireAtResult){
195                             return true;
196                         }
197                     }catch(Exception e){
198                         e.printStackTrace();
199                     }
200                     redisTemplate.opsForValue().increment(key, (0 - value));
201                     return false;
202                 }
203
204             });
205             if(isSetSuccess instanceof Boolean){
206                 return (Boolean) isSetSuccess;
207             }
208             return false;
209         }catch(Exception e){
210             e.printStackTrace();
211             return false;
212         }
213
214     }
215
216
217     public Long getLong(String key) {
218         try{
219          Set<String> keys = redisTemplate.keys(key);
220          //key指定的数据不存在
221          if (keys == null || keys.isEmpty()) {
222              return null;
223          }
224          return redisTemplate.opsForValue().increment(key, 0);
225         } catch (DataAccessException e) {
226             logger.info("error :{}", e);
227             logger.info("{}指定的数据不是数值类型", key);
228             throw new RuntimeException(key + "指定的数据不是数值类型");
229         }
230     }
231
232     public Long getLongNoKeys(String key) {
233         try {
234             long keys = redisTemplate.opsForValue().increment(key, 0);
235             return keys;
236         } catch (DataAccessException e) {
237             logger.info("error :{}", e);
238             logger.info("{}指定的数据不是数值类型", key);
239             throw new RuntimeException(key + "指定的数据不是数值类型");
240         }
241     }
242
243
244
245
246     /**
247      * 删除set集合中的对象
248      * @param key
249      * @param value
250      */
251     @Override
252     public void srem(String key, String value) {
253         redisTemplate.boundSetOps(key).remove(value);
254     }
255 }

使用方式:

/**
     *
     * @Description:
     * @param @param request
     * @param @return
     * @param @throws Exception
     * @return String
     * @throws
     * @author liping.sang
     * @date 2017-8-8
     */
    @RedisLock(fieldName={"reqNo"},timeInSecond=3)
    @RequestMapping(method = { RequestMethod.GET, RequestMethod.POST }, value = "/conversionCodeModel2")
    @ResponseBody
    public String conversionCodeModel2(HttpServletRequest request)
            throws Exception {
1 <aop:pointcut expression="execution(* com.cul.culsite.controller.ConversionCodeController.conversionCodeModel2(..))" id="conversionCodeModel2Redis"/>
2
3             <aop:around method="redisLockParse" pointcut-ref="conversionCodeModel2Redis"/>            
时间: 2024-10-31 10:51:11

【Redis使用系列】使用Redis做防止重复提交的相关文章

关于防止表单表达重复提交的几个解决方法

表达重复提交的问题,是B/S系统开发中经常容易被忽视,但常常又令程序员头疼的一个问题.根据墨菲定律,如果你不做防止重复提交的机制,那些用户行为往往就会给你带来麻烦,然后就等着产品经理的抱怨吧.下面,我就总结了几条常见的关于B/S系统中防止表单重复提交的几个办法: 1.页面上控制.怕用户点击提交按钮2次?用javascript控制下吧:怕用户后退导致重复提交?那就干脆打开个新页面吧.总之你要设想到用户在页面上的所有可能的操作,把这些容易导致BUG的操作消灭的萌芽中. 2.session控制.如果实

记一次遇到由于重复提交导致的问题

需求 问题 解决和复盘 这是新手期间第一个上线功能搞出的bug,同时也明白了一个道理:1. 线上环境总是复杂的,不可预知的,一定要做好各种准备: 2. 重要的功能要做放重复提交:3. 基础要打打牢. 需求 需求其实很简单,就是一个修改密码的入口,用户输入提交表单后,如果之前没有设置过密码,则设置,否则修改.然后验证密码的逻辑就不用说了,查出表里的密码,然后对比. 项目是ibatis+oralce,之前DAO的逻辑如下 ... if (isNotExists()){ insertPassword;

1.【Redis系列】redis是可以做什么?

原文:1.[Redis系列]redis是可以做什么? Redis是互联网技术领域使用最为广泛的存储中间件.它以其超高的性能.完美的文档.简洁易懂的源码和丰富的客户端库支持在开源中间件领域广受好评.国内外很多大型网站都在使用Redis,比如Twitter.Youporn.Github.腾讯.阿里.京东.华为等等,很多小型公司也在应用. Redis可以做什么呢 Redis的应用非常广泛,我们梳理下redis可以用在哪些方面. 1.记录帖子的点赞数.评论数和点击数 2.记录用户帖子ID的列表,便于快速

2.【Redis系列】Redis基础数据结构

原文:2.[Redis系列]Redis基础数据结构 千里之行始于足下,我们先来看看redis的基础知识. Redis有5中基本数据类型:字符串(string).列表(list).集合(set).有序集合(zset).字典(hash).熟练掌握这5种基本数据结构也是最基本最重要的部分. String(字符串) 字符串是redis中最简单的数据结构,Redis所有的数据结构都是以唯一key作为名称,然后通过唯一的key来获取相应的redis数据.不同类型的数据结构的差异就在于value的结构不一样.

分布式缓存技术redis学习系列(四)——redis高级应用(集群搭建、集群分区原理、集群操作)

本文是redis学习系列的第四篇,前面我们学习了redis的数据结构和一些高级特性,点击下面链接可回看 <详细讲解redis数据结构(内存模型)以及常用命令> <redis高级应用(主从.事务与锁.持久化)> 本文我们继续学习redis的高级特性--集群.本文主要内容包括集群搭建.集群分区原理和集群操作的学习. Redis集群简介 Redis 集群是3.0之后才引入的,在3.0之前,使用哨兵(sentinel)机制(本文将不做介绍,大家可另行查阅)来监控各个节点之间的状态.Redi

Redis笔记系列(二)——Redis安装部署与维护详解

本文介绍Redis2.8的安装部署和维护方法. Redis在linux上的安装 步骤1: 首先从官网下在redis正式版的压缩包redis-2.8.19.tar.gz http://download.redis.io/releases/redis-2.8.19.tar.gz 步骤2:编译源程序: tar zxvf redis-2.8.19.tar.gz [[email protected] Downloads]$ tar zxvf redis-2.8.19.tar.gz [[email prot

[ 搭建Redis本地服务器实践系列三 ] :图解Redis客户端工具连接Redis服务器

上一章 [ 搭建Redis本地服务器实践系列二 ] :图解CentOS7配置Redis  介绍了Redis的初始化脚本文件及启动配置文件,并图解如何以服务的形式来启动.终止Redis服务,可以说我们的Redis本地服务器已基本搭建完成,那可能就有小伙伴要问了,什么叫基本搭建完成,原因是此时的Redis服务虽然已经可以正常启\停,但是客户端还无法远程连接到Redis并执行响应的操作.若要实现远程客户端正常连接,我们仍需要对Redis启动配置文件进行一些设置,这个章节我们重点来讲解下如何对Redis

redis系列:redis介绍与安装

前言 这个redis系列的文章将会记录博主学习redis的过程.基本上现在的互联网公司都会用到redis,所以学习这门技术于你于我都是有帮助的. 博主在写这个系列是用的是目前最新版本4.0.10,虚拟机装的是4.0.10,为了方便window也安装了(版本3.2.100).后续命令会采用命令行,jedis和spring集成jedis这三种方式进行操作. 在这片博文的开始,可以先试着问几个问题,带着问题看博文,或许能更有收获. 什么是redis? 为什么要使用redis? 如何搭建redis环境?

高并发架构系列:Redis并发竞争key的解决方案详解

需求由来 1.Redis高并发的问题 Redis缓存的高性能有目共睹,应用的场景也是非常广泛,但是在高并发的场景下,也会出现问题:缓存击穿.缓存雪崩.缓存和数据一致性,以及今天要谈到的缓存并发竞争. 这里的并发指的是多个redis的client同时set key引起的并发问题. 2.出现并发设置Key的原因 Redis是一种单线程机制的nosql数据库,基于key-value,数据可持久化落盘.由于单线程所以Redis本身并没有锁的概念,多个客户端连接并不存在竞争关系,但是利用jedis等客户端