springboot中通過lua指令碼來獲取序列號的方法
阿新 • • 發佈:2020-06-10
序言:
事件:此web專案的功能及其簡單,就是有客戶端來訪問redis序列號服務時傳送jison報文,專案已經在測試環境成功執行2周了,具體的程式碼我就直接上了,此部落格僅是自己的記錄,同學們可做參考!
一、工程目錄結構
二、配置檔案
1、pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.2.6.RELEASE</version> <relativePath/> <!-- lookup parent from repository --> </parent> <groupId>com.test</groupId> <artifactId>seq-gen</artifactId> <version>0.0.1-SNAPSHOT</version> <name>seq-gen</name> <description>generate sequence from redis</description> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <java.version>1.8</java.version> </properties> <dependencies> <!--引入日誌依賴--> <!--<dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>--> <!-- log4j2的api、core和web包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-api</artifactId> <version>2.11.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> <version>2.11.1</version> </dependency> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-web</artifactId> <version>2.11.1</version> </dependency> <!-- slf4j與log4j2的連線包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-slf4j-impl</artifactId> <version>2.11.1</version> </dependency> <!-- log4j與log4j2的連線包 --> <dependency> <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-1.2-api</artifactId> <version>2.11.1</version> </dependency> <!-- log4j2支撐完全非同步模式的關鍵api --> <dependency> <groupId>com.lmax</groupId> <artifactId>disruptor</artifactId> <version>3.4.2</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.21</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.62</version> </dependency> <dependency> <groupId>redis.clients</groupId> <artifactId>jedis</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- 熱部署,整合測試--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <executions> <execution> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-surefire-plugin</artifactId> <version>2.4.2</version> <configuration> <skipTests>true</skipTests> </configuration> </plugin> </plugins> </build> </project>
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',month_in_seconds) --將序列返回給呼叫者 return seq else --key值存在,直接獲取下一個增加的值 local nextSeq = redis.call('incrby',incr_amoutt) return nextSeq end end return get_next_seq()
4、log4j2.xml
<?xml version="1.0" encoding="UTF-8"?> <!--日誌級別以及優先順序排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> <!--Configuration後面的status,這個用於設定log4j2自身內部的資訊輸出,可以不設定,當設定成trace時,你會看到log4j2內部各種詳細輸出--> <!--monitorInterval:Log4j能夠自動檢測修改配置 檔案和重新配置本身,設定間隔秒數--> <configuration status="INFO" monitorInterval="30" packages="org.apache.logging.log4j.core.layout"> <Properties> <Property name="baseDir">logs</Property> </Properties> <!--先定義所有的appender--> <appenders> <!-- 這個輸出控制檯的配置 --> <Console name="Console" target="SYSTEM_OUT"> <!-- 控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch) --> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <!-- 這個都知道是輸出日誌的格式 --> <PatternLayout pattern="[%date{DEFAULT}]-[%highlight{%level}]-[%threadName]-[%-class{4}:%line]-%msg%n"/> </Console> <!-- 這個會打印出所有的info及以下級別的資訊,每次大小超過size,則這size大小的日誌會自動存入按年份-月份建立的資料夾下面並進行壓縮,作為存檔--> <RollingFile name="RollingFileInfo" fileName="${baseDir}/seq_all.log" filePattern="${baseDir}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log"> <!--控制檯只輸出level及以上級別的資訊(onMatch),其他的直接拒絕(onMismatch)--> <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%date{DEFAULT}]-[%highlight{%level}]-[%threadName]-[%-class{4}:%line]-%msg%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="500 MB"/> </Policies> </RollingFile> <RollingFile name="RollingFileWarn" fileName="${baseDir}/seq_warn.log" filePattern="${baseDir}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%date{DEFAULT}]-[%highlight{%level}]-[%threadName]-[%-class{4}:%line]-%msg%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> <!-- DefaultRolloverStrategy屬性如不設定,則預設為最多同一資料夾下7個檔案,這裡設定了20 --> <DefaultRolloverStrategy max="20"/> </RollingFile> <RollingFile name="RollingFileErrorCommon" fileName="${baseDir}/seq_error.log" filePattern="${baseDir}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log"> <ThresholdFilter level="error" onMatch="ACCEPT" onMismatch="DENY"/> <PatternLayout pattern="[%date{DEFAULT}]-[%highlight{%level}]-[%threadName]-[%-class{4}:%line]-%msg%n"/> <Policies> <TimeBasedTriggeringPolicy/> <SizeBasedTriggeringPolicy size="100 MB"/> </Policies> </RollingFile> </appenders> <!--然後定義logger,只有定義了logger並引入的appender,appender才會生效--> <loggers> <!--過濾掉spring和mybatis的一些無用的DEBUG資訊--> <logger name="org.springframework" level="DEBUG"></logger> <logger name="org.mybatis" level="DEBUG"></logger> <logger name="com.alicl oud.openservices.tablestore" level="ERROR" additivity="false"> <appender-ref ref="RollingFileOtsError"/> </logger> <root level="INFO"> <appender-ref ref="Console"/> <appender-ref ref="RollingFileInfo"/> <appender-ref ref="RollingFileWarn"/> <appender-ref ref="RollingFileErrorCommon"/> </root> </loggers> </configuration>
三、程式碼部分
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 RedisTemplate<String,String> redisTemplate; @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("\r\n"+prettyJson); JSONObject jsonObject = new JSONObject(); RspBean rw = new RspBean(); String response = null; Map<String,String> jsonMap = new HashMap<String,String>(); try { // 將報文放入map中 jsonMap = CommonUtils.putReq2Map(jsonparam); response = redisService.createResponse(jsonMap); prettyJson = CommonUtils.prettyJson(response); log.info("send Response: "); log.info("\r\n"+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(Map<String,String> jsonMap); }
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(Map<String,String> jsonMap) { String response = null; RspBean rw = null; JSONObject json = null; // 之所以要遍歷map是因為怕傳過來的key值有小寫的,怕get不到對應的值 String key = null; String sNNameValue = null; String increAmountValue = null; for (Map.Entry<String,String> entry : jsonMap.entrySet()) { key = entry.getKey(); if ("SNName".equalsIgnoreCase(key)) { sNNameValue = entry.getValue(); } else if("SNNum".equalsIgnoreCase(key)){ increAmountValue = entry.getValue(); } } String seq="0"; // 從redis中獲取序列號(根據序列號名稱和步長獲取序列號) List<String> busilist = 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 Map<String,String> putReq2Map(String jsonparam) { // 將json字串轉換為json物件 return (Map<String,String>) 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("\r\n"); } 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 RedisScript<Long> redisScript; private static DefaultScriptExecutor<String> scriptExecutor; 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(List<String> Busilist) throws Exception{ Long seqFromRedis = scriptExecutor.execute(redisScript,Busilist); return seqFromRedis; } }
總結
到此這篇關於springboot中通過lua指令碼來獲取序列號的文章就介紹到這了,更多相關springboot中通過lua指令碼來獲取序列號內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!