歡迎您光臨本站 註冊首頁

springboot中通過lua腳本來獲取序列號的方法

←手機掃碼閱讀     niceskyabc @ 2020-06-11 , reply:0

序言:

事件:此web項目的功能及其簡單,就是有客戶端來訪問redis序列號服務時發送jison報文,項目已經在測試環境成功運行2周了,具體的代碼我就直接上了,此博客僅是自己的記錄,同學們可做參考!

一、工程目錄結構

在這裡插入圖片描述

二、配置文件

1、pom.xml

    	4.0.0  	  		org.springframework.boot  		spring-boot-starter-parent  		2.2.6.RELEASE  			  	com.test  	seq-gen  	0.0.1-SNAPSHOT  	seq-gen  	generate sequence from redis    	  		UTF-8  		UTF-8  		1.8  	    	  		  		  			org.slf4j  			slf4j-log4j12  			1.7.21  		  		  			commons-logging  			commons-logging  			1.2  		-->    				  			org.apache.logging.log4j  			log4j-api  			2.11.1  		  		  			org.apache.logging.log4j  			log4j-core  			2.11.1  		  		  			org.apache.logging.log4j  			log4j-web  			2.11.1  		  				  			org.apache.logging.log4j  			log4j-slf4j-impl  			2.11.1  		  				  			org.apache.logging.log4j  			log4j-1.2-api  			2.11.1  		  				  			com.lmax  			disruptor  			3.4.2  		  		  			org.slf4j  			slf4j-api  			1.7.21  		    		  			org.springframework.boot  			spring-boot-starter-data-redis  		  		  			com.alibaba  			fastjson  			1.2.62  		  		  			redis.clients  			jedis  		    		  			org.springframework.boot  			spring-boot-starter-web  		  		  			org.springframework.boot  			spring-boot-starter  			  				  					org.springframework.boot  					spring-boot-starter-logging  				  			  		  				  			org.springframework.boot  			spring-boot-devtools  			true  		  		  			org.projectlombok  			lombok  			true  		    		  			org.springframework.boot  			spring-boot-starter-test  			test  		  	    	  		  			  				org.apache.maven.plugins  				maven-compiler-plugin  				  					1.8  					1.8  				  			  			  				org.springframework.boot  				spring-boot-maven-plugin  				  					  						  							repackage  						  					  				  			  			  				org.apache.maven.plugins  				maven-surefire-plugin  				2.4.2  				  					true  				  			  		  	

 

2、applicaiton.properties

  spring.redis.database= 0  spring.redis.host= 127.0.0.1  spring.redis.port= 6379  spring.redis.pool.max-active= 8  spring.redis.pool.max-wait= -1ms  spring.redis.pool.max-idle= 8  spring.redis.pool.min-idle= 0  spring.redis.pool.timeout= 2000ms  server.port= 8085

 

3、luaScripts腳本

  local function get_next_seq()   --KEYS[1]:第一個參數代表存儲序列號的key 相當於代碼中的業務類型   local key = tostring(KEYS[1])   --KEYS[2]:第二個參數代表序列號增長速度   local incr_amoutt = tonumber(KEYS[2])   --KEYS[3]`:第四個參數為序列號 (yyMMddHHmmssSSS + 兩位隨機數)   local seq = tonumber(KEYS[3])   --序列號過期時間大小,單位是秒   -- local month_in_seconds = 24 * 60 * 60 * 7     --Redis的 SETNX 命令可以實現分佈式鎖,用於解決高併發   --如果key不存在,將 key 的值設為 seq,設置成成功返回1 未設置返回0   --若給定的 key 已經存在,則 SETNX 不做任何動作,獲取下一個按照步增的值     if (1 == redis.call('setnx', key, seq)) --不存在key,   then   --設置key的生存時間 為 month_in_seconds秒   -- 由於序列號需要永久有效,不能過期,所以取消這個設置,需要的可以取消註釋   -- redis.call('expire', key, month_in_seconds)   --將序列返回給調用者    return seq   else   --key值存在,直接獲取下一個增加的值    local nextSeq = redis.call('incrby', key, incr_amoutt)    return nextSeq   end  end  return get_next_seq()

 

4、log4j2.xml

     FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->    logs

 

三、代碼部分

1、啟動類

  package com.test;    import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.boot.SpringApplication;  import org.springframework.boot.autoconfigure.SpringBootApplication;    @SpringBootApplication  public class SeqGenApplication {  	private static final Logger log = LoggerFactory.getLogger(SeqGenApplication.class);  	public static void main(String[] args) {  		SpringApplication.run(SeqGenApplication.class, args);  		log.info("start SeqGenApplication sucessfully........");  	}  }

 

2、Bean

  package com.test.bean;    import com.alibaba.fastjson.annotation.JSONField;    /**   * Copyright (C), 2019-2020   *   * 此類是請求和響應中對應的屬性   *   * @author fanhf   * @date  2020-03-25   * @version v1.0.0   */  public class RspBean {   public RspBean(){}   /* 開始序列號 */   @JSONField(name = "SNNumB")   private Integer sNNumB;   /* 從redis中獲取的序列號 */   @JSONField(name = "SNNumE")   private Integer sNNumE;   /* 發起方操作流水 */   @JSONField(name = "OprNumb")   private String 	oprNumb;   /* 落地方操作時間 */   @JSONField(name = "OprTime")   private String oprTime;   /* 返回碼 */   @JSONField(name = "BizOrderResult")   private String bizOrderResult;   /* 返回碼描述 */   @JSONField(name = "ResultDesc")   private String resultDesc;       public Integer getSNNumB() {    return sNNumB;   }     public void setSNNumB(Integer sNNumB) {    this.sNNumB = sNNumB;   }     public Integer getSNNumE() {    return sNNumE;   }     public void setSNNumE(Integer sNNumE) {    this.sNNumE = sNNumE;   }     public String getOprNumb() {    return oprNumb;   }     public void setOprNumb(String oprNumb) {    this.oprNumb = oprNumb;   }     public String getOprTime() {    return oprTime;   }     public void setOprTime(String oprTime) {    this.oprTime = oprTime;   }     public String getBizOrderResult() {    return bizOrderResult;   }     public void setBizOrderResult(String bizOrderResult) {    this.bizOrderResult = bizOrderResult;   }     public String getResultDesc() {    return resultDesc;   }     public void setResultDesc(String resultDesc) {    this.resultDesc = resultDesc;   }     @Override   public String toString() {    return "RspBean{" +      "sNNumB=" + sNNumB +      ", sNNumE=" + sNNumE +      ", oprNumb='" + oprNumb + ''' +      ", oprTime='" + oprTime + ''' +      ", bizOrderResult='" + bizOrderResult + ''' +      ", resultDesc='" + resultDesc + ''' +      '}';   }  }

 

3、Controller

  package com.test.controller;    import com.test.bean.RspBean;  import com.test.service.RedisService;  import com.test.util.CommonUtils;  import com.alibaba.fastjson.JSONObject;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.beans.factory.annotation.Autowired;  import org.springframework.data.redis.core.RedisTemplate;  import org.springframework.web.bind.annotation.PostMapping;  import org.springframework.web.bind.annotation.RequestBody;  import org.springframework.web.bind.annotation.RestController;    import java.util.HashMap;  import java.util.Map;      /**   * Copyright (C), 2019-2020   *   * 此類是web層的入口,用來接收json請求   *   * @author fanhf   * @date  2020-03-29   * @version v1.0.0   */  @RestController  public class RedisControlLer {     private static final Logger log = LoggerFactory.getLogger(RedisControlLer.class);   @Autowired   private RedisTemplateredisTemplate;     @Autowired   private RedisService redisService;     @PostMapping(path = "/app/v1/sync/bizOrder/QuerySerialNumber", consumes = "application/json", produces = "application/json")   public String rcvReq(@RequestBody String jsonparam){      String prettyJson= CommonUtils.prettyJson(jsonparam);    log.info("receive requset: ");    log.info("  "+prettyJson);    JSONObject jsonObject = new JSONObject();    RspBean rw = new RspBean();    String response = null;    MapjsonMap = new HashMap();    try {     // 將報文放入map中     jsonMap = CommonUtils.putReq2Map(jsonparam);     response = redisService.createResponse(jsonMap);     prettyJson = CommonUtils.prettyJson(response);     log.info("send Response: ");     log.info("  "+prettyJson);    } catch (Exception ex) {     if (null == jsonObject || 0 == jsonObject.size()) {      try {       String oprNumb = jsonMap.get("oprNumb");       rw.setOprNumb(oprNumb);       rw.setBizOrderResult("30000");       rw.setResultDesc(ex.getMessage());       JSONObject json = (JSONObject) JSONObject.toJSON(rw);       response = json.toString();      } catch (Exception e) {       e.printStackTrace();      }      return response;     }    }    return response;   }  }

 

4、Service

  package com.test.service;    import java.util.Map;    public interface RedisService {    String createResponse(MapjsonMap);  }

 

ServiceImpl

  package com.test.service;    import com.test.bean.RspBean;  import com.test.util.CommonUtils;  import com.test.util.RedisUtil;  import com.alibaba.fastjson.JSONObject;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.stereotype.Component;  import org.springframework.stereotype.Service;  import org.springframework.util.StringUtils;    import java.util.*;    /**   * Copyright (C), 2019-2020   *   * 此類是service處理層,根據接收到的序列名稱和步長值,從redis中獲取序列號,再對返回的信息進行組裝   * 以及對異常情況時返回數據的處理   *   * @author fanhf   * @date  2020-04-05   * @version v1.0.0   */    @Component  @Service  public class RedisServiceImpl implements RedisService {   private static final Logger log = LoggerFactory.getLogger(RedisServiceImpl.class);   @Override   public String createResponse(MapjsonMap) {    String response = null;    RspBean rw = null;    JSONObject json = null;      // 之所以要遍歷map是因為怕傳過來的key值有小寫的,怕get不到對應的值    String key = null;    String sNNameValue = null;    String increAmountValue = null;    for (Map.Entryentry : jsonMap.entrySet()) {     key = entry.getKey();     if ("SNName".equalsIgnoreCase(key)) {      sNNameValue = entry.getValue();     } else if("SNNum".equalsIgnoreCase(key)){      increAmountValue = entry.getValue();     }    }    String seq="0";    // 從redis中獲取序列號(根據序列號名稱和步長獲取序列號)    Listbusilist = Arrays.asList(sNNameValue,increAmountValue,seq);      Long seqFromRedis = null;    try {     seqFromRedis = RedisUtil.getBusiSeq(busilist);    } catch (Exception e) {     log.error("cannot get seq from redis cluster ,please check redis cluster"+ "_" + e.getMessage(), e);    }    log.info("seqFromRedis:{}", seqFromRedis);    String oprNumb = jsonMap.get("OprNumb");    String oprTime = CommonUtils.getCurDateTimestamp();    try {     rw = new RspBean();     int sNNumB;     if(!StringUtils.isEmpty(seqFromRedis)){      sNNumB=seqFromRedis.intValue();      rw.setSNNumB(sNNumB);      rw.setSNNumE(sNNumB+Integer.parseInt(increAmountValue));      rw.setBizOrderResult("00000");      rw.setResultDesc("Success");     }else{      rw.setSNNumB(0);      rw.setSNNumE(0);      rw.setBizOrderResult("30000");      rw.setResultDesc("business handles failed....");     }     rw.setOprNumb(oprNumb);     rw.setOprTime(oprTime);     json = (JSONObject) JSONObject.toJSON(rw);     response = json.toString();    } catch (Exception e) {     log.error("boxing response of json happend error "+ "_" + e.getMessage(), e);     if (rw != null) {      rw.setBizOrderResult("30000");      rw.setResultDesc("business handles failed......");      json = (JSONObject) JSONObject.toJSON(rw);      response = json.toString();     }     log.info("send Response: [ {} ]", response );     jsonMap.put("responseToWzw", response);     return response;      }    return response;   }  }

 

5、Utils

5.1 CommonUtils

  package com.test.util;    import com.alibaba.fastjson.JSON;  import com.alibaba.fastjson.JSONObject;  import com.alibaba.fastjson.serializer.SerializerFeature;  import org.slf4j.Logger;  import org.slf4j.LoggerFactory;    import java.text.DateFormat;  import java.text.SimpleDateFormat;  import java.time.LocalDateTime;  import java.time.format.DateTimeFormatter;  import java.util.Date;  import java.util.Map;    /**   * 工具類   * @author fanhf   * @date  2020-04-01   * @version v1.0.0   */  public class CommonUtils {   private static final Logger log = LoggerFactory.getLogger(CommonUtils.class);   public static MapputReq2Map(String jsonparam) {    // 將json字符串轉換為json對象    return (Map) JSONObject.parse(jsonparam);   }     /**    * @Description 獲取系統當前時間    * @return 時間字符串    */   public static String getCurDateTimestamp(){    DateTimeFormatter dateTimeFormatter=DateTimeFormatter.ofPattern("yyyyMMddHHmmss");    LocalDateTime localDateTime = LocalDateTime.now();    String now=localDateTime.format(dateTimeFormatter);    return now;   }     /**    * 美化json格式,將一行json轉為為有回車換行的json    * @param reqJson    * @return 美化後的json    */   public static String prettyJson(String reqJson){    JSONObject object = JSONObject.parseObject(reqJson);    String prettyJson = JSON.toJSONString(object, SerializerFeature.PrettyFormat, SerializerFeature.WriteMapNullValue,SerializerFeature.WriteDateUseDateFormat);    return prettyJson;   }  }

 

5.2 ReadConfigsPathUtil

  package com.test.util;    import org.slf4j.Logger;  import org.slf4j.LoggerFactory;    import java.io.BufferedReader;  import java.io.File;  import java.io.FileReader;  import java.io.IOException;  import java.util.Properties;  /**   * @ Description : 用來獲取linux和windows的config的絕對路徑   * @ Author  : fanhf   * @ CreateDate : 2020/4/11 0:33   * @ UpdateUser : fanhf   * @ UpdateDate : 2020/4/11 0:33   * @ UpdateRemark : 修改內容   * @ Version  : 1.0.0   */  public class ReadConfigsPathUtil {   private static final Logger log = LoggerFactory.getLogger(ReadConfigsPathUtil.class);   private ReadConfigsPathUtil() {}   private static Properties properties = null;     /**    * @Description 獲取linux和windows系統中config的目錄    * @param configPath lua腳本的相對路徑    * @return linux和windows系統中config的目錄的絕對路徑    */   public static String getPropertiesPath(String configPath) {    String sysPath = getRelativePath();    log.info("sysPath:{}",sysPath);     String filepath = new StringBuffer(sysPath)       .append(File.separator)       .append("config")       .append(File.separator)       .append(configPath).toString();    log.info("filepath:{}",filepath);    return filepath;   }   /**    * @Description 獲取系統字符型屬性    * @author add by fanhf    * @date 2020-04-08    */   public static String getRelativePath() {    return System.getProperty("user.dir");   }     /**    * @Description 讀取lua腳本的內容    * @param luaScriptPath lua腳本的絕對路徑    * @return 讀取到的lua腳本的內容    * @author add by fanhf    * @date 2020-04-15    */   public static String readFileContent(String luaScriptPath) {    String filename = getPropertiesPath(luaScriptPath);    File file = new File(filename);    BufferedReader reader = null;    StringBuffer sbf = new StringBuffer();    try {     reader = new BufferedReader(new FileReader(file));     String tempStr;     while ((tempStr = reader.readLine()) != null) {      sbf.append(tempStr);      sbf.append("  ");     }     reader.close();     return sbf.toString();    } catch (IOException e) {     e.printStackTrace();    } finally {     if (reader != null) {      try {       reader.close();      } catch (IOException e1) {       e1.printStackTrace();      }     }    }    return sbf.toString();   }  }

 

5.3 RedisUtil

  package com.test.util;    import org.slf4j.Logger;  import org.slf4j.LoggerFactory;  import org.springframework.core.io.ClassPathResource;  import org.springframework.core.io.support.EncodedResource;  import org.springframework.data.redis.core.StringRedisTemplate;  import org.springframework.data.redis.core.script.DefaultRedisScript;  import org.springframework.data.redis.core.script.DefaultScriptExecutor;  import org.springframework.data.redis.core.script.RedisScript;  import org.springframework.stereotype.Component;  import org.springframework.util.FileCopyUtils;  import java.io.IOException;  import java.util.List;    /**   * @ Description : 用來加載和讀取lua腳本並加載   * @ Author  : fanhf   * @ CreateDate : 2020/4/01 0:32   * @ UpdateUser : fanhf   * @ UpdateDate : 2020/4/01 0:32   * @ UpdateRemark : 修改內容   * @ Version  : 1.0.0   */  @Component  public class RedisUtil {   private static final Logger log = LoggerFactory.getLogger(RedisUtil.class);     private static StringRedisTemplate redisStringTemplate;     private static RedisScriptredisScript;     private static DefaultScriptExecutorscriptExecutor;     private RedisUtil(StringRedisTemplate template) throws IOException {    RedisUtil.redisStringTemplate = template;     // 之所以會註釋掉是由於這段代碼可以直接讀取resource目錄下的非application.properties的文件,     // 但是這個方法在生產和測試環境不適用,因為配置文件必須暴露初打的jar包裡  //  ClassPathResource luaResource = new ClassPathResource("luaScript/genSeq.lua");  //  EncodedResource encRes = new EncodedResource(luaResource, "UTF-8");  //  String luaString = FileCopyUtils.copyToString(encRes.getReader());      String luaString = ReadConfigsPathUtil.readFileContent("luaScript/genSeq.lua");    redisScript = new DefaultRedisScript<>(luaString, Long.class);    scriptExecutor = new DefaultScriptExecutor<>(redisStringTemplate);   }   public static Long getBusiSeq(ListBusilist) throws Exception{    Long seqFromRedis = scriptExecutor.execute(redisScript, Busilist);    return seqFromRedis;   }  }

  


[niceskyabc ] springboot中通過lua腳本來獲取序列號的方法已經有296次圍觀

http://coctec.com/docs/java/show-post-238094.html