牛刀小试.开胃菜·代码思路·10-1
牛刀小试.开胃菜·代码思路·10-1
10-1-预加载活动编号(实现)
1. 思路:
- 根据
马边成
指出的问题,问题定位是在添加活动
的时候出现的。 - 首先定位
添加活动
的前端页面,根据前端页面调取后端接口
,阅读这部分后端代码
- 定位优化的点
- F12查看对应的后端接口位置,定位对应的异常信息打印的原因
- 解决BUG
提示
- redis里的数据是什么时候存储进去的?
CommandLineRunner
接口的作用是什么?- 现有
代码中这样做
的目的是什么,这样做的意义是什么? - 组内讨论
如何优化
? 时间富余
并且对这一块感兴趣的同学可以调研一下 CommandLineRunner,InitializingBean,PostConstruct,BeanPostProcessor
2.具体思路:
1️⃣ 1.通过前端定位
由于是添加活动,这里需要找到添加活动页面 👈


传入参数: 👈

{
"channel":"0",
"name":"测试活动编号",
"info":"用于查看活动编号",
"type":"1",
"discount":8,
"beginTime":"2022-03-15 00:00",
"status":null,
"endTime":"2022-04-13 23:59",
"activityTime":[
"2022-03-15 00:00",
"2022-04-13 23:59"
]
}
返回值:👈
{
"msg":"操作成功",
"code":200
}
2️⃣ 2.找到后端接口
定位到对应的接口/clues/activity
是POST
请求
全局搜索(idea-->edit--find-->find in path (tab中的project))
关键词:/clues/activity

找到对应的Post请求 👈
TbActivityController
/**
* 新增活动管理
*/
@PreAuthorize("@ss.hasPermi('clues:activity:add')")
@Log(title = "活动管理", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@RequestBody TbActivity tbActivity) {
return toAjax(tbActivityService.insertTbActivity(tbActivity));
}
3️⃣ 3.查看接口内容
ITbActivityService
/**
* 新增活动管理
* @param tbActivity 活动管理
* @return 结果
*/
public int insertTbActivity(TbActivity tbActivity);
TbActivityServiceImpl
/**
* 新增活动管理
*
* @param tbActivity 活动管理
* @return 结果
*/
@Override
@Transactional
public int insertTbActivity(TbActivity tbActivity){
tbActivity.setCreateTime(DateUtils.getNowDate());
tbActivity.setCode(getCode());
tbActivity.setStatus("2");
int rows= tbActivityMapper.insertTbActivity(tbActivity);
loadAllActivityCode();
return rows;
}
private String getCode(){
//随机8位编码
String code= StringUtils.getRandom(8);
//店铺校验
Set<String> codeSets = redisCache.getCacheSet(Constants.ACT_CODE_KEY);
if(codeSets.contains(code)){
//yg提示: 直到调用 不包含为止---->唯一性
return getCode();
}
return code;
}
相关代码 缓存里的值是什么时候添加进去的
HuikeApplication
package com.huike;
/**
* 启动程序*/
@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class})
@EnableScheduling
public class HuiKeApplication implements CommandLineRunner {
@Autowired
private ITbActivityService activityService;
public static void main(String[] args){
SpringApplication.run(HuiKeApplication.class, args);
}
@Override
public void run(String... args) {
try{
//yg提示:加载所有活动code到缓存
activityService.loadAllActivityCode();
}catch (Exception ex){
ex.printStackTrace();
}
}
}
ITbActivityService
/**
* 预加载活动编号
*/
void loadAllActivityCode();
TbActivityServiceImpl
@Override
public void loadAllActivityCode() {
List<String> codeList= tbActivityMapper.selectAllCode();
Set<String> set= new HashSet<>(codeList);
//yg提示:没有设置有效期
redisCache.setCacheSet(Constants.ACT_CODE_KEY, set);
}
当一个类实现了CommandLineRunner接口后,需要重写run方法里的内容,在服务启动完成后,会自动执行run方法里的内容
4️⃣ 4.定位问题所在
获取code的时候是采用一个随机数的方式随机,可能会存在重复如果出现了重复,则递归调用获取随机活动编号的方法,这部分应该有问题,因为目的就是获取一个唯一的code,一定有一种更简单的方式来实现
5️⃣ 5.思考原作者为什么要这么写
原作者希望获得一个id这个id不能和其他的活动id进行重复,但是如果每次随机生成都去查询数据库的话那么性能就太差了,这个时候原作者就使用了空间换时间的思想,利用CommandLineRunner接口,在项目启动完成的时候加载系统中所有的活动code在缓存中,然后通过缓存来对比,这样是一种空间换时间的思想,并且在添加活动的时候还要刷新缓存里的活动code。
其目的就是为了得到一个全局唯一的code 👈
空间换时间
- 利用内存中的读取速度远高于对mysql的io操作
- 提前将部分mysql中的数据保存在内存空间中,利用内存空间中的大小,换取代码的执行效率
- 这样的思想叫空间换时间
- 利:
- 提高代码的执行效率 👈
- 弊:
- 对于大量的数据,占用内存较多的,不宜使用 👈
- 利:
- 缓存中的数据需要和数据库中的数据一致,,即由于将数据保存在了内存空间中,修改了这部分数据,修改的是数据库中的数据,但是缓存中的数据没有变更,会造成数据不一致
6️⃣ 6.提出自己的解决方案思路
UUID可以理解成全球的唯一标识符 Universally Unique Identifier的简写,它是一个固定格式的字符串
UUID分类
UUID 一般有如下几种:
- 基于时间的UUID
- DCE安全的UUID
- 基于MD5,SH1的uuid
- 随机生成的UUID
1️⃣ 基于时间的UUID
- 能保证不同设备下UUID是唯一的 ,但在同一设备上,出现超过并发,可能重复 ⚠️
- 因为该UUID主要使用的是设备的MAC地址+时间来计算UUID
- 如果设备相同,时间也相同(毫秒内并发),会出现重复
2️⃣ 基于DCE安全的UUID
- DCE指的是身份验证和安全服务
- 设计用户隐私
- 有损时间戳导致精度丢失
即使用用户的某些特征,结合一些标识来生成UUID,因为在生成UUID的时候需要使用用户的数据,涉及到用户的隐私
3️⃣ 基于MD5,SH1的UUID
相同命名空间下存在冲突
4️⃣ 随机生成的UUID
- 完全随机生成的UUID,会存在极小概率情况的重复,可以视为不会重复 千亿分之一的概率
- 与设备和环境无关
- 生成完全无规律可循
java.util.UUID
UUID.randomUUID();
1)移除空间换时间部分代码

2)添加一个UUIDUtils,使用Random随机生成一个8位的UUID
public class UUIDUtils {
//字符库
public static String[] chars = new String[] { "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n", "o", "p", "q", "r", "s",
"t", "u", "v", "w", "x", "y", "z", "0", "1", "2", "3", "4", "5",
"6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I",
"J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V",
"W", "X", "Y", "Z" };
public static String getUUID() {
//调用Java提供的生成随机字符串的对象:32位,十六进制,中间包含-
String uuid= UUID.randomUUID().toString().replace("-", "");
StringBuffer shortBuffer = new StringBuffer();
for (int i = 0; i < 8; i++) { //分为8组
String str = uuid.substring(i * 4, i * 4 + 4); //每组4位
int x = Integer.parseInt(str, 16); //输出str在16进制下的表示
shortBuffer.append(chars[x % 0x3E]); //用该16进制数取模62(十六进制表示为314(14即E)),结果作为索引取出字符
}
return shortBuffer.toString();//生成8位字符
}
}
3)使用UUIDUtils替换获取活动Code部分方法
/**
* 新增活动管理
*
* @param tbActivity 活动管理
* @return 结果
*/
@Override
@Transactional
public int insertTbActivity(TbActivity tbActivity){
tbActivity.setCreateTime(DateUtils.getNowDate());
tbActivity.setCode(UUIDUtils.getUUID());
tbActivity.setStatus("2");
int rows= tbActivityMapper.insertTbActivity(tbActivity);
return rows;
}
4)移除通知部分代码
删除所有insert方法里的loadAllActivityCode方法
开胃菜 🚀 🚀
🎉 🎉 🎉恭喜你,完成上述任务,接下来,你可以尝试一下开胃菜