Redis使用系列使用Redis做防止重复提交
Posted 霓裳梦竹
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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"/>
以上是关于Redis使用系列使用Redis做防止重复提交的主要内容,如果未能解决你的问题,请参考以下文章