springboot中通过lua脚本来获取序列号的方法

网友投稿 239 2022-12-05


springboot中通过lua脚本来获取序列号的方法

序言:

事件:此web项目的功能及其简单,就是有客户端来访问redis序列号服务时发送jison报文,项目已经在测试环境成功运行2周了,具体的代码我就直接上了,此博客仅是自己的记录,同学们可做参考!

一、工程目录结构

二、配置文件

1、pom.xml

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

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.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

xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

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.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

logs

filePattern="${baseDir}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log">

filePattern="${baseDir}/$${date:yyyy-MM}/all-%d{yyyy-MM-dd}-%i.log">

filePattern="${baseDir}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">

filePattern="${baseDir}/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log">

filePattern="${baseDir}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">

filePattern="${baseDir}/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log">

三、代码部分

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.JSONObhttp://ject;

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 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 jsonMap = new HashMap();

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 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 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 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 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 =http:// 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 putReq2Map(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("\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 redisScript;

private static DefaultScriptExecutor 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 Busilist) throws Exception{

Long seqFromRedis = scriptExecutor.execute(redisScript, Busilist);

return seqFromRedis;

}

}

总结


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Maven 的配置文件路径读取方法
下一篇:SpringBoot项目中分页插件PageHelper无效的问题及解决方法
相关文章

 发表评论

暂时没有评论,来抢沙发吧~