三、并发技术以及数据库表设计
使用到的并发技术
ConcurrentHashMap 线程安全的Map
ThreadLocal 每个线程保存一份它自己的副本
Semaphore 信号量
CountDownLatch 闭锁
数据库表设计
表名 prefix_sequence
prefix_name varchar(255) 主键 前缀编码名字
width int 主键 宽度
date_flag int 主键 是否带日期(0否,1是)
sequence_value varchar(255) 顺序号
sequence_date datetime 日期
四、思路和代码实现
基础的顺序号实现
基础实体类,dao,service代码
/**
- 前缀顺序号实体类
*/
@Data
public class PrefixSequence {
// 前缀名字
private String prefixName;
// 宽度
private Integer width;
// 是否带日期(0否,1是)
private Integer dateFlag;
// 顺序号
private String sequenceValue;
// 日期
private Date sequenceDate;
}
/**
- 顺序号mapper
*/
public interface PrefixSequenceMapper {
int deleteByPrimaryKey(@Param(“prefixName”) String prefixName, @Param(“width”) Integer width, @Param(“dateFlag”) Integer dateFlag);
int insert(PrefixSequence record);
int insertSelective(PrefixSequence record);
PrefixSequence selectByPrimaryKey(@Param(“prefixName”) String prefixName, @Param(“width”) Integer width, @Param(“dateFlag”) Integer dateFlag);
int updateByPrimaryKeySelective(PrefixSequence record);
int updateByPrimaryKey(PrefixSequence record);
}
xml文件
<?xml version="1.0" encoding="UTF-8"?> prefix_name, width, date_flag, sequence_value, sequence_date select from prefix_sequence where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} delete from prefix_sequence where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} insert into prefix_sequence (prefix_name, width, date_flag, sequence_value, sequence_date) values (#{prefixName,jdbcType=VARCHAR}, #{width,jdbcType=INTEGER}, #{dateFlag,jdbcType=INTEGER}, #{sequenceValue,jdbcType=VARCHAR}, #{sequenceDate,jdbcType=TIMESTAMP}) insert into prefix_sequence prefix_name, width, date_flag, sequence_value, sequence_date, #{prefixName,jdbcType=VARCHAR}, #{width,jdbcType=INTEGER}, #{dateFlag,jdbcType=INTEGER}, #{sequenceValue,jdbcType=VARCHAR}, #{sequenceDate,jdbcType=TIMESTAMP}, update prefix_sequence sequence_value = #{sequenceValue,jdbcType=VARCHAR}, sequence_date = #{sequenceDate,jdbcType=TIMESTAMP}, where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER} update prefix_sequence set sequence_value = #{sequenceValue,jdbcType=VARCHAR}, sequence_date = #{sequenceDate,jdbcType=TIMESTAMP} where prefix_name = #{prefixName,jdbcType=VARCHAR} and width = #{width,jdbcType=INTEGER} and date_flag = #{dateFlag,jdbcType=INTEGER}/**
- 前缀顺序号服务
*/
public interface PrefixSequenceService {
/**
- 生成带日期的顺序号
- @param prefixName
- @param width
- @return
*/
String generateSequenceDateValue(String prefixName, Integer width) throws Exception;
/**
- 1 生成带日期的线程安全的顺序号
- @param prefixName
- @param width
- @return
- @throws Exception
*/
String generateSynSequenceDateValue(String prefixName, Integer width) throws Exception;
/**
- 2 生成带日期的线程安全的顺序号 相同的前缀名加锁,不同的前缀名可以并行
- @param prefixName
- @param width
- @return
- @throws Exception
*/
String generateSynKeyLockSequenceDateValue(String prefixName, Integer width) throws Exception;
}
/**
- 前缀顺序号服务实现
*/
@Service
public class PrefixSequenceServiceImpl implements PrefixSequenceService {
@Autowired
PrefixSequenceMapper prefixSequenceMapper;
@Override
public String generateSequenceDateValue(String prefixName, Integer width) throws Exception{
if(StringUtils.isEmpty(prefixName)||null == width){
throw new Exception(“参数不能为空”);
}
// 查询数据库顺序号
PrefixSequence prefixSequence = prefixSequenceMapper.selectByPrimaryKey(prefixName,width,1);
// 如果没有就新建
if(prefixSequence==null){
String newSequence = generateNewSequence(prefixName,width,1);
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
String format = dateFormat.format(new Date());
return prefixName+“-”+format+“-”+newSequence;
}else{
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
// 如果是当天,递增顺序号更新并返回
if(SequenceStringUtil.judgeSameDay(prefixSequence)){
// 将顺序号转换为Long
Long value = Long.valueOf(prefixSequence.getSequenceValue());
String updateValue = SequenceStringUtil.formatSequenceValue(width,++value);
prefixSequence.setSequenceValue(updateValue);
prefixSequenceMapper.updateByPrimaryKeySelective(prefixSequence);
// 返回更新后的顺序号
String updateSequence = prefixSequence.getPrefixName()+“-”+
dateFormat.format(prefixSequence.getSequenceDate())+“-”+
prefixSequence.getSequenceValue();
return updateSequence;
}else{
// 如果不是当天,更新顺序号(更新当前日期,如果宽度为5,顺序号为00001)并返回。
String newSequence = SequenceStringUtil.formatSequenceValue(width, 1L);
prefixSequence.setSequenceValue(newSequence);
prefixSequence.setSequenceDate(new Date());
prefixSequenceMapper.updateByPrimaryKeySelective(prefixSequence);
// 返回更新后的顺序号
String updateSequence = prefixSequence.getPrefixName()+“-”+
dateFormat.format(prefixSequence.getSequenceDate())+“-”+
prefixSequence.getSequenceValue();
return updateSequence;
}
}
}
/**
- 辅助方法
- 创建一个新的顺序号,指定宽度,从1开始
- @param prefixName
- @param width
- @param dateFlag
- @return
*/
private String generateNewSequence(String prefixName, Integer width, Integer dateFlag) {
// 创建一个新的顺序号,指定宽度,从1开始
String newSequence = SequenceStringUtil.formatSequenceValue(width, 1L);
// 保存顺序号
PrefixSequence prefixSequence = new PrefixSequence();
prefixSequence.setPrefixName(prefixName);
prefixSequence.setWidth(width);
prefixSequence.setSequenceValue(newSequence);
prefixSequence.setSequenceDate(new Date());
prefixSequence.setDateFlag(dateFlag);
prefixSequenceMapper.insertSelective(prefixSequence);
// 返回新生成的顺序号
return newSequence;
}
}
/**
- 顺序号工具类
- 使用 StringBuilder 以及 DecimalFormat 填充前缀0
*/
public class SequenceStringUtil {
// 填充0并格式化值
public static String formatSequenceValue(Integer sequenceWidth, Long value){
StringBuilder sb = new StringBuilder();
for(int i=0;i<sequenceWidth;i++){
sb.append(“0”);
}
DecimalFormat decimalFormat = new DecimalFormat(sb.toString());
String resultValue = decimalFormat.format(value);
return resultValue;
}
//判断是否为同一天
public static boolean judgeSameDay(PrefixSequence sequence) {
SimpleDateFormat dateFormat=new SimpleDateFormat(“yyyyMMdd”);
return dateFormat.format(sequence.getSequenceDate()).equals(dateFormat.format(new Date()));
}
}
生成顺序号核心逻辑
a) 根据前缀名,宽度,是否带日期查询数据库中的顺序号。
b) 如果没有找到,生成新的顺序号(比如宽度为5,从00001开始)保存并返回。
c) 如果已经有该前缀以及宽度的顺序号,如果日期相同,将顺序号递增,更新并返回。
d) 如果已经有该前缀以及宽度的顺序号,如果日期不相同,更新日期(当天)以及初始化顺序号
(比如宽度为5,从00001开始)并返回。
基础的顺序号服务就开发完了,我们来测试一下。
@RestController
@RequestMapping(“/sequence”)
public class PrefixSequenceController {
@Autowired
PrefixSequenceService prefixSequenceService;
/**
- 生成带日期的顺序号
- @param prefixName
- @param width
- @return
- @throws Exception
*/
@GetMapping(“/generateSequenceDateValue/{prefixName}/{width}”)
public AjaxResult generateSequenceDateValue(@PathVariable(“prefixName”) String prefixName, @PathVariable(“width”) Integer width) throws Exception {
return AjaxResult.success(“成功”,prefixSequenceService.generateSequenceDateValue(prefixName,width));
}
}
单次请求返回结果大致如下
AAAA-20230721-00001
AAAA-20230721-00002
AAAA-20230721-00003
BBBB-20230721-00001
BBBB-20230721-00002
返回了预期的结果。
下面我们模拟高并发场景下的顺序号生成
@RestController
@RequestMapping(“/sequence”)
public class PrefixSequenceController {
@Autowired
PrefixSequenceService prefixSequenceService;
/**
- 高并发下测试顺序号
- @param prefixName
- @param width
- @return
- @throws Exception
*/
@GetMapping(“/test/generateSequenceDateValue/{prefixName}/{width}”)
public AjaxResult testGenerateSequenceDateValue(@PathVariable(“prefixName”) String prefixName, @PathVariable(“width”) Integer width) throws Exception {
CountDownLatch countDownLatch = new CountDownLatch(100);
class PrefixSequenceRun implements Runnable {
@SneakyThrows
@Override
public void run() {
countDownLatch.countDown();
countDownLatch.await();
String sequence = prefixSequenceService.generateSequenceDateValue(prefixName,width);
System.out.println(sequence);
}
}
for(int i=0;i<100;i++){
new Thread(new PrefixSequenceRun()).start();
}
return AjaxResult.success();
}
}
使用闭锁模拟100次同时请求顺序号服务(前缀为AAAA,宽度为5)。
输出大致结果如下
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数网络安全工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年网络安全全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上网络安全知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注网络安全获取)
写在最后
在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版PDF学习资源私我
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
:vip204888 (备注网络安全获取)**
[外链图片转存中…(img-ELgEJudp-1712498732146)]
写在最后
在结束之际,我想重申的是,学习并非如攀登险峻高峰,而是如滴水穿石般的持久累积。尤其当我们步入工作岗位之后,持之以恒的学习变得愈发不易,如同在茫茫大海中独自划舟,稍有松懈便可能被巨浪吞噬。然而,对于我们程序员而言,学习是生存之本,是我们在激烈市场竞争中立于不败之地的关键。一旦停止学习,我们便如同逆水行舟,不进则退,终将被时代的洪流所淘汰。因此,不断汲取新知识,不仅是对自己的提升,更是对自己的一份珍贵投资。让我们不断磨砺自己,与时代共同进步,书写属于我们的辉煌篇章。
需要完整版PDF学习资源私我
一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-D5b2nQPx-1712498732146)]