在平時的開發中咱們都須要處理重複提交的問題,避免業務出錯或者產生髒數據,雖然能夠經過前端控制但這並非能夠徹底避免,最好的方式仍是先後端均進行控制,這樣的話就能夠更有效,儘量全面的去減小錯誤的發生。前端
若是用戶頻繁點擊或者惡意攻擊的話就會形成不斷的請求對服務器產生很大的壓力,爲了不這種狀況咱們須要作處理,傳統的模式中是在數據庫中記錄手機號、驗證碼已經發送時間,再次請求的時候呢去數據庫查詢是否有該手機號記錄,並校驗是否超過間隔時間,若是超過則從新發送並更新時間,否組不予發送,這樣有一個缺點就是若是同時又不少人在作相同的業務同時查詢就會對數據庫形成很大的壓力。java
根據此種狀況咱們能夠使用Redis incrde 原子性遞增,來解決這種高併發的秒殺或者分佈式序列號生成等場景。鑑於本場景咱們只用他來作計數實現間隔時間內只接收一次請求。web
實現邏輯:在發送短信以後使用Redis的incr設置一個遞增的KEY(根據本身的須要設定可是要保證每個人的惟一),來判斷該KEY的數值,若是等於1說明這是第一次請求,發送短信記錄日誌,並設置有效期,若是不等於的話說明是間隔時間內屢次請求,就提示請求頻繁,稍後重試。redis
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.slp.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.slp.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 }
package com.slp.annotation; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang.StringUtils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.reflect.MethodSignature; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import com.alibaba.fastjson.JSONObject; import com.cul.culsite.common.RedisKeyConstants; import com.cul.culsite.service.RedisService; import com.cul.culsite.util.DateUtil; import com.cul.culsite.util.OxmHelper; @Component public class RedisLockAspect { private final static Logger logger = LoggerFactory.getLogger(RedisLockAspect.class); protected static final String XML_TYPE = "xml"; protected static final String JSON_TYPE = "json"; protected static final String ILLEGAL_TYPE = "illegal type"; @Autowired private RedisService redisService; public Object redisLockParse(ProceedingJoinPoint p) throws Throwable{ Signature signature = p.getSignature(); boolean isRepetition = false; RedisLockBean redisLockBean = null; String value = System.nanoTime()+""; if(signature instanceof MethodSignature){ //得到接口中定義的方法的Method,但註解時加載實現類中方法的參數上 MethodSignature methodSignature = (MethodSignature)signature; Method serviceMethod = methodSignature.getMethod(); try { Method serviceImpMethod = p.getTarget().getClass().getMethod(serviceMethod.getName(), serviceMethod.getParameterTypes()); //獲取key值 redisLockBean = getRedisLockKey(p.getTarget(),serviceImpMethod,p.getArgs()); //成功獲取key值,在redis中加鎖 if(redisLockBean!=null){ logger.info("redis lock value is :{}",value); boolean isPutSuccess =redisService.setIfAbsent(redisLockBean.getKey(), value, redisLockBean.getTimeInSecond()); //加鎖失敗,直接返回 if(!isPutSuccess){ logger.info("get redis lock fail for {}",redisLockBean.getKey()); if(redisLockBean.isAtParameter()||redisLockBean.isAtService()){ Class<?> returnType = serviceImpMethod.getReturnType(); //加鎖方法有返回值 if(!returnType.getName().equals(java.lang.Void.class.getName())){ //實例化返回值對象 try { Object result = returnType.newInstance(); //設置返回碼 returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getCodeName()), java.lang.String.class).invoke(result, redisLockBean.getCode()); //設置返回信息 returnType.getMethod(getSetMethodNameByFieldName(redisLockBean.getMsgName()), java.lang.String.class).invoke(result, redisLockBean.getMsg()); return result; } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (IllegalArgumentException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } }else{ throw new RuntimeException("@RedisLock做用的方法沒有返回參數"); } }else if(redisLockBean.isAtController()){ Map<String,String> result = new HashMap<String,String>(); result.put(redisLockBean.getCodeName(), redisLockBean.getCode()); result.put(redisLockBean.getMsgName(), redisLockBean.getMsg()); return response(redisLockBean.getReturnType()==null?"json":redisLockBean.getReturnType(), result); } }else{ logger.info("get redis lock success for {}",redisLockBean.getKey()); isRepetition = true; } } } catch (NoSuchMethodException e) { e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } } Object result = null; try { result = p.proceed(); } catch (Throwable e) { throw e; }finally{ if(redisLockBean!=null){ if(isRepetition&&value.equals(redisService.get(redisLockBean.getKey()))){ logger.info("lock has released :{}",redisLockBean.getKey()); redisService.delete(redisLockBean.getKey()); } } } return result; } private RedisLockBean getRedisLockKey(Object target,Method method,Object... object){ if(target == null){ throw new RuntimeException("get redis lock key error,target is null"); } if(method==null){ throw new RuntimeException("get redis lock key error,method is null"); } List<String> fieldValueList = new ArrayList<String>(); RedisLockBean redisLockBean = new RedisLockBean(); RedisLock redisLock = null; //類上有@Controller說明@RedisLock是放在請求方法上,使用HttpServletRequest獲取請求參數 if(method.isAnnotationPresent(RedisLock.class)&&target.getClass().isAnnotationPresent(Controller.class)){ //controller層方法時對外開放的接口 if(method.isAnnotationPresent(RequestMapping.class)){ redisLock = method.getAnnotation(RedisLock.class); //獲取方法中的HttpServletRequest類型的參數 HttpServletRequest request = null; for(Object para:object){ if(para instanceof HttpServletRequest){ request = (HttpServletRequest)para; break; } } if(request==null){ throw new RuntimeException("@RedisLock做用於controller層方法時,方法須要包含HttpServletRequest類型的參數"); } //未定義加鎖參數時,默認使用mac String[] paraName = redisLock.fieldName(); if(paraName==null||paraName.length==0){ paraName=new String[]{"mac"}; } for(String para:paraName){ fieldValueList.add(request.getParameter(para)); } if(fieldValueList.isEmpty()){ throw new RuntimeException("@RedisLock做用於controller層方法時,生成key失敗,請求中沒有mac簽名"); } //標示註解做用在controller成方法上 redisLockBean.setAtController(true); }else{ throw new RuntimeException("@RedisLock做用於controller層的方法時,該方法上須要使用@RequestMapping註解"); } //註解做用於非controller層方法上 }else if(method.isAnnotationPresent(RedisLock.class)){ redisLock = method.getAnnotation(RedisLock.class); //參數的索引位置 int index = redisLock.paramIndex(); String[] fieldName = redisLock.fieldName(); String[] values = getFieldValue(object[index],fieldName); //註解的參數時基本的數據類型或String,不須要傳入屬性名稱,不然設置的屬性,都必須得到該屬性值 if(values==null || values.length!=fieldName.length && fieldName.length>0){ return null; } fieldValueList.addAll(Arrays.asList(values)); redisLockBean.setAtService(true); }else{ Annotation[][] annotations; annotations = method.getParameterAnnotations(); for(int i=0;i<annotations.length;i++){ for(Annotation annotation:annotations[i]){ if(annotation instanceof RedisLock){ RedisLock redisLockTmp = (RedisLock)annotation; if(redisLock==null){ redisLock = redisLockTmp; } String[] fieldName = redisLockTmp.fieldName(); String[] values = getFieldValue(object[i],fieldName); //註解的參數時基本的數據類型或String,不須要傳入屬性名稱,不然設置的屬性,都必須得到該屬性值 if(values==null || values.length!=fieldName.length && fieldName.length>0){ return null; } fieldValueList.addAll(Arrays.asList(values)); redisLockBean.setAtParameter(true); } } } } //未使用註解 if(fieldValueList.isEmpty()){ return null; } //設置其它參數值 if(redisLockBean.getTimeInSecond()==0){ redisLockBean.setTimeInSecond(redisLock.timeInSecond()); } if(StringUtils.isEmpty(redisLockBean.getCodeName())){ redisLockBean.setCodeName(redisLock.codeName()); } if(StringUtils.isEmpty(redisLockBean.getCode())){ redisLockBean.setCode(redisLock.code()); } if(StringUtils.isEmpty(redisLockBean.getMsgName())){ redisLockBean.setMsgName(redisLock.msgName()); } if(StringUtils.isEmpty(redisLockBean.getMsg())){ redisLockBean.setMsg(redisLock.msg()); } Collections.sort(fieldValueList); logger.info("all value of fieldName is {}",fieldValueList); //生成key值 StringBuilder builder = new StringBuilder(); builder.append(target.getClass().getName()) .append("-") .append(method.getName()) .append("-") .append(Arrays.asList(method.getParameterTypes())) .append("-") .append(fieldValueList); String lockKey = RedisKeyConstants.REDIS_LOCK + builder.toString(); logger.info("redis lock key is :{}",builder.toString()); redisLockBean.setKey(lockKey); logger.info("redisLockBean :{}",redisLockBean.toString()); return redisLockBean; } private String[] getFieldValue(Object argObj,String...fieldName){ if(fieldName ==null || fieldName.length == 0){ return new String[]{getBaseClassValue(argObj)}; } List<String> fieldsValue = new ArrayList<String>(); for(String field:fieldName){ String value = getFieldValue(argObj,field); logger.info("value of fieldName '{}' is :{}",fieldName,value); if(value!=null){ fieldsValue.add(value); } } return fieldsValue.toArray(new String[0]); } private String getFieldValue(Object argObj,String fieldName){ if(argObj==null){ throw new RuntimeException("argObj is null,cannot get field value of fieldName"); } String value = getBaseClassValue(argObj); if(!StringUtils.isEmpty(value)){ return value; } String methodName = getGetMethodValueByFieldName(fieldName); Object result = null; try { Method method = argObj.getClass().getMethod(methodName); result = method.invoke(argObj); } catch (NoSuchMethodException e) { logger.error("method {} without parameter is not exists!",methodName); e.printStackTrace(); } catch (SecurityException e) { e.printStackTrace(); } catch (IllegalAccessException e) { logger.error("method {} without parameter is not public!",methodName); e.printStackTrace(); } catch (IllegalArgumentException e) { logger.error("method {} has parameter!",methodName); e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } if(result==null){ logger.warn("method {} does not have returnValue",methodName); return null; } return getBaseClassValue(result); } private String getBaseClassValue(Object object){ if(object==null){ throw new RuntimeException("argObj is null,cannot get field value "); } if(object instanceof String){ return object.toString(); } if(object instanceof Integer){ int i = (Integer)object; //剔除成員變量的默認值 if(i!=0){ return i+""; } } if(object instanceof Long){ long i = (Long)object; if(i!=0){ return i+""; } } return null; } private String getGetMethodValueByFieldName(String fieldName){ return getMethodNameByFieldNameAndPrefix("get",fieldName); } private String getSetMethodNameByFieldName(String fieldName){ return getMethodNameByFieldNameAndPrefix("set",fieldName); } private String getMethodNameByFieldNameAndPrefix(String prefix,String fieldName){ if(StringUtils.isEmpty(fieldName)){ throw new RuntimeException("cannot get Get method by null or length is 0"); } if(StringUtils.isEmpty(prefix)){ throw new RuntimeException("cannot get Get method by null without prefix"); } String getMethodName = prefix+fieldName.substring(0, 1).toUpperCase(); //fieldName 的長度大於一時,索引大於一的字符不改變大小寫 if(fieldName.length()>1){ getMethodName = getMethodName + fieldName.substring(1); } return getMethodName; } private String response(String type, Object obj) { if (XML_TYPE.equalsIgnoreCase(type)) { String ret = OxmHelper.marshal(obj); logger.info("response:{}",ret); return ret; } if (JSON_TYPE.equalsIgnoreCase(type)) { String ret = JSONObject.toJSONString(obj); logger.info("response:{}",ret); return ret; } return ILLEGAL_TYPE + ":" + type; } }
package com.slp.service.impl; import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.data.redis.core.RedisOperations; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.data.redis.core.SessionCallback; import org.springframework.data.redis.core.ValueOperations; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import com.cul.culsite.service.RedisService; @Service public class RedisServiceImpl implements RedisService { private static final Logger logger = LoggerFactory.getLogger(RedisServiceImpl.class); @Autowired private RedisTemplate<String, String> redisTemplate; /** * 獲取redis值 * * @param key * 鍵 * @return 值 */ public String get(String key) { Assert.hasText(key, "redis get key cannot null"); return redisTemplate.opsForValue().get(key); } /** * 刪除redis鍵 * * @param key * 鍵 */ @Override public void delete(String key) { Assert.hasText(key, "redis delete key cannot null"); redisTemplate.delete(key); } /** * 設置redis值 * * @param key * 鍵 * @param value * 值 * @param time * 時間(分鐘) 若是小於0 默認爲1分鐘 */ @Override public boolean setIfAbsent(final String key,final String value,int time) { Assert.hasText(key, "redis set key cannot null"); Assert.hasText(value, "redis set value cannot null"); if(time<=0){ time = 1; } final int timeInSecond = time; try{ @SuppressWarnings("unchecked") Object isSetSuccess = redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations arg0) throws DataAccessException { try{ //開始事務 List<Object> result=new ArrayList<Object>(); arg0.multi(); arg0.opsForValue().setIfAbsent(key, value); // arg0.expireAt(key,DateUtils.addSeconds(new Date(),timeInSecond)); arg0.expire(key, timeInSecond, TimeUnit.SECONDS); //提交事務 result= arg0.exec(); logger.info("redis mutil for get lock result is :{}",result); //執行了兩次redis操做,應該有兩個返回值,不然防止key永久有效,執行刪除操做 if(result == null||result.size()!=2){ redisTemplate.delete(key); return false; } //獲取加鎖操做的返回結果 boolean setIfAbsentResult = false; if(result.get(0) instanceof Boolean){ setIfAbsentResult =(Boolean)result.get(0); } //獲取設置key有效時間返回結果 boolean expireAtResult = false; if(result.get(1) instanceof Boolean){ expireAtResult = (Boolean)result.get(1); } if(setIfAbsentResult&&expireAtResult){ logger.info("加鎖成功......."); return true; } }catch(Exception e){ e.printStackTrace(); } return false; } }); if(isSetSuccess instanceof Boolean){ return (Boolean) isSetSuccess; } return false; }catch(Exception e){ e.printStackTrace(); return false; } } @Override public Set<String> keys(String keyPattern) { Assert.hasText(keyPattern, "keys pattern is null"); return redisTemplate.keys(keyPattern); } @Override public long incr(String key) { Assert.hasText(key, "key is null"); return redisTemplate.opsForValue().increment(key, 1L); } @Override public long decr(String key) { Assert.hasText(key, "key is null"); return redisTemplate.opsForValue().increment(key, -1L); } @Override public void set(String key, String value) { Assert.hasText(key, "key is null"); Assert.hasText(value, "value is null"); redisTemplate.opsForValue().set(key, value); } @Override public boolean set(final String key, final long value, final int timeInSecond) { Assert.hasText(key, "key is null"); Assert.hasText(value + "", "value is null"); Assert.hasText(timeInSecond + "", "timeInSecond is null"); try{ @SuppressWarnings("unchecked") Object isSetSuccess = redisTemplate.execute(new SessionCallback() { @Override public Object execute(RedisOperations arg0) throws DataAccessException { try{ //開始事務 List<Object> result=new ArrayList<Object>(); arg0.multi(); arg0.opsForValue().increment(key, value); arg0.expire(key, timeInSecond, TimeUnit.SECONDS); //提交事務 result= arg0.exec(); logger.info("result of redis set long value is :{}",result); //執行了兩次redis操做,應該有兩個返回值,不然防止key永久有效,執行刪除操做 if(result == null || result.size() != 2){ redisTemplate.opsForValue().increment(key, (0 - value)); return false; } //獲取加鎖操做的返回結果 long incrementResult = 0; if(result.get(0) instanceof Long){ incrementResult =(Long)result.get(0); } //獲取設置key有效時間返回結果 boolean expireAtResult = false; if(result.get(1) instanceof Boolean){ expireAtResult = (Boolean)result.get(1); } if((incrementResult == value) && expireAtResult){ return true; } }catch(Exception e){ e.printStackTrace(); } redisTemplate.opsForValue().increment(key, (0 - value)); return false; } }); if(isSetSuccess instanceof Boolean){ return (Boolean) isSetSuccess; } return false; }catch(Exception e){ e.printStackTrace(); return false; } } public Long getLong(String key) { try{ Set<String> keys = redisTemplate.keys(key); //key指定的數據不存在 if (keys == null || keys.isEmpty()) { return null; } return redisTemplate.opsForValue().increment(key, 0); } catch (DataAccessException e) { logger.info("error :{}", e); logger.info("{}指定的數據不是數值類型", key); throw new RuntimeException(key + "指定的數據不是數值類型"); } } public Long getLongNoKeys(String key) { try { long keys = redisTemplate.opsForValue().increment(key, 0); return keys; } catch (DataAccessException e) { logger.info("error :{}", e); logger.info("{}指定的數據不是數值類型", key); throw new RuntimeException(key + "指定的數據不是數值類型"); } } /** * 刪除set集合中的對象 * @param key * @param value */ @Override public void srem(String key, String value) { redisTemplate.boundSetOps(key).remove(value); } }
1 /** 2 * 3 * @Description: 4 * @param @param request 5 * @param @return 6 * @param @throws Exception 7 * @return String 8 * @throws 9 * @author liping.sang 10 * @date 2017-8-8 11 */ 12 @RedisLock(fieldName={"reqNo"},timeInSecond=3) 13 @RequestMapping(method = { RequestMethod.GET, RequestMethod.POST }, value = "/test2") 14 @ResponseBody 15 public String test2(HttpServletRequest request) 16 throws Exception {