springbootlua脚本序列号 springboot中通过lua脚本来获取序列号的方法
fhf2424045058 人气:0序言:
事件:此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; } }
总结
加载全部内容