springboot中通过lua脚本来获取序列号的方法
作者:fhf2424045058
这篇文章主要介绍了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= 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
<?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脚本来获取序列号内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!