序言:
事件:此web項目的功能及其簡單,就是有客戶端來訪問redis序列號服務時發送jison報文,項目已經在測試環境成功運行2周了,具體的代碼我就直接上了,此博客僅是自己的記錄,同學們可做參考!
一、工程目錄結構
二、配置文件
1、pom.xml
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.slf4j slf4j-log4j12 1.7.21 commons-logging commons-logging 1.2 --> 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
FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL --> logs
三、代碼部分
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 RedisTemplateredisTemplate; @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(" "+prettyJson); JSONObject jsonObject = new JSONObject(); RspBean rw = new RspBean(); String response = null; MapjsonMap = new HashMap(); try { // 將報文放入map中 jsonMap = CommonUtils.putReq2Map(jsonparam); response = redisService.createResponse(jsonMap); prettyJson = CommonUtils.prettyJson(response); log.info("send Response: "); log.info(" "+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(MapjsonMap); }
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(MapjsonMap) { String response = null; RspBean rw = null; JSONObject json = null; // 之所以要遍歷map是因為怕傳過來的key值有小寫的,怕get不到對應的值 String key = null; String sNNameValue = null; String increAmountValue = null; for (Map.Entryentry : jsonMap.entrySet()) { key = entry.getKey(); if ("SNName".equalsIgnoreCase(key)) { sNNameValue = entry.getValue(); } else if("SNNum".equalsIgnoreCase(key)){ increAmountValue = entry.getValue(); } } String seq="0"; // 從redis中獲取序列號(根據序列號名稱和步長獲取序列號) Listbusilist = 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 MapputReq2Map(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(" "); } 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 RedisScriptredisScript; private static DefaultScriptExecutorscriptExecutor; 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(ListBusilist) throws Exception{ Long seqFromRedis = scriptExecutor.execute(redisScript, Busilist); return seqFromRedis; } }
[niceskyabc ] springboot中通過lua腳本來獲取序列號的方法已經有301次圍觀