牛刀小试.开胃菜·代码思路·10-1

YangeIT大约 7 分钟

牛刀小试.开胃菜·代码思路·10-1

10-1-预加载活动编号(实现)

1. 思路:

  • ​根据马边成指出的问题,问题定位是在添加活动的时候出现的。
  • ​首先定位添加活动的前端页面,根据前端页面调取后端接口,阅读这部分后端代码
  • ​定位优化的点
  • ​F12查看对应的后端接口位置,定位对应的异常信息打印的原因
  • ​解决BUG

提示

  1. redis里的数据是什么时候存储进去的?
  2. CommandLineRunner接口的作用是什么?
  3. 现有代码中这样做的目的是什么,这样做的意义是什么?
  4. 组内讨论如何优化
  5. 时间富余并且对这一块感兴趣的同学可以调研一下 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/activityPOST请求

全局搜索(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 👈

空间换时间

  1. ​利用内存中的读取速度远高于对mysql的io操作
  2. 提前将部分mysql中的数据保存在内存空间中,利用内存空间中的大小,换取代码的执行效率
  3. 这样的思想叫空间换时间
      1. 提高代码的执行效率 👈
      1. 对于大量的数据,占用内存较多的,不宜使用 👈
  4. 缓存中的数据需要和数据库中的数据一致,,即由于将数据保存在了内存空间中,修改了这部分数据,修改的是数据库中的数据,但是缓存中的数据没有变更,会造成数据不一致

6️⃣ 6.提出自己的解决方案

思路

​UUID可以理解成全球的唯一标识符 Universally Unique Identifier的简写,它是一个固定格式的字符串

UUID分类

UUID 一般有如下几种:

  1. 基于时间的UUID
  2. DCE安全的UUID
  3. 基于MD5,SH1的uuid
  4. 随机生成的UUID

1️⃣ 基于时间的UUID

  • ​能保证不同设备下UUID是唯一的 ,但在同一设备上,出现超过并发,可能重复 ⚠️
  • ​因为该UUID主要使用的是设备的MAC地址+时间来计算UUID
  • ​如果设备相同,时间也相同(毫秒内并发),会出现重复
解决方案

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方法

开胃菜 🚀 🚀

🎉 🎉 🎉恭喜你,完成上述任务,接下来,你可以尝试一下开胃菜