springboot中通過lua腳本來獲取序列號的方法
序言:
事件:此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= 0spring.redis.host= 127.0.0.1spring.redis.port= 6379spring.redis.pool.max-active= 8spring.redis.pool.max-wait= -1msspring.redis.pool.max-idle= 8spring.redis.pool.min-idle= 0spring.redis.pool.timeout= 2000msserver.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 endendreturn 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;@SpringBootApplicationpublic 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 */@RestControllerpublic 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('rn'+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('rn'+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@Servicepublic 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('rn'); } 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 */@Componentpublic 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腳本來獲取序列號內容請搜索好吧啦網以前的文章或繼續瀏覽下面的相關文章希望大家以后多多支持好吧啦網!
相關文章:
