黑马点评——商户查询缓存(P37店铺类型查询业务添加缓存练习题答案)redis缓存、更新、穿透、雪崩、击穿、工具封装

news2024/9/21 4:32:40

文章目录

  • 什么是缓存?
  • 添加Redis缓存
    • 店铺类型查询业务添加缓存练习题
  • 缓存更新策略
    • 给查询商铺的缓存添加超时剔除和主动更新的策略
  • 缓存穿透
    • 缓存空对象
    • 布隆过滤
  • 缓存雪崩
    • 解决方案
  • 缓存击穿
    • 解决方案
    • 基于互斥锁方式解决缓存击穿问题
    • 基于逻辑过期的方式解决缓存击穿问题
  • 缓存工具封装

什么是缓存?

在这里插入图片描述

缓存也要考虑成本的问题,不是随便用的
在这里插入图片描述

添加Redis缓存

在这里插入图片描述
在这里插入图片描述


    @Override
    public Result queryById(Long id) {
        String redisKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1. 从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isNotBlank(shopJson)){
            // 3. 存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }
        // 4. 不存在,根据id查询数据库
        Shop shop = getById(id);
        // 5. 不存在,写入redis
        if(shop == null){
            return Result.fail("店铺不存在!");
        }
        // 6. 存在,写入redis
        stringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop));
        // 7. 返回
        return Result.ok(shop);
    }

店铺类型查询业务添加缓存练习题

@Override
    public Result queryTypeList() {
        // 1. 从redis查询店铺类别缓存
        List<String> shopTypeRedisKey = stringRedisTemplate.opsForList().range(RedisConstants.CACHE_SHOP_TYPE_KEY,0,-1);
        // 2. 判断是否命中缓存
        if(!CollectionUtils.isEmpty(shopTypeRedisKey)){
            // 3. 存在,直接返回,即是命中缓存
            // 使用stream流将json集合转为
            List<ShopType> shopTypeList = shopTypeRedisKey.stream()
                    .map(item -> JSONUtil.toBean(item, ShopType.class))
                    .sorted(Comparator.comparingInt(ShopType::getSort))
                    .collect(Collectors.toList());
            // 返回缓存数据
            return Result.ok(shopTypeList);
        }
        // 4. 不存在,查询数据库
        List<ShopType> shopTypes = query().orderByAsc("sort").list();
        // 判断数据库中是否有数据
        if(CollectionUtils.isEmpty(shopTypes)){
            // 不存在则缓存一个空集合,解决缓存穿透
            stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_TYPE_KEY, Collections.emptyList().toString(),RedisConstants.CACHE_NULL_TTL, TimeUnit.MINUTES);
            return Result.fail("商品分类信息为空");
        }
        // 5. 数据存在,先写入redis,再返回
        // 使用stream流将bean集合转为json集合
        List<String> shopTypeCache = shopTypes.stream()
                .sorted(Comparator.comparingInt(ShopType::getSort))
                .map(item -> JSONUtil.toJsonStr(item))
                .collect(Collectors.toList());

        stringRedisTemplate.opsForList().rightPushAll(RedisConstants.CACHE_SHOP_TYPE_KEY,shopTypeCache);
        stringRedisTemplate.expire(RedisConstants.CACHE_SHOP_TYPE_KEY,RedisConstants.CACHE_SHOP_TYPE_TTL, TimeUnit.MINUTES);
        // 6. 返回(按类别升序排序)
        return Result.ok(shopTypes);
    }

缓存更新策略

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
操作缓存和数据库的顺序,不论谁先进行都可能会有线程安全的问题
在这里插入图片描述

但方案二的发生可能性更小,所以更优
总结:
在这里插入图片描述

给查询商铺的缓存添加超时剔除和主动更新的策略

在这里插入图片描述
查询店铺:

  @Override
    public Result queryById(Long id) {
        String redisKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1. 从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isNotBlank(shopJson)){
            // 3. 存在,直接返回
            Shop shop = JSONUtil.toBean(shopJson, Shop.class);
            return Result.ok(shop);
        }

        // 4. 不存在,根据id查询数据库
        Shop shop = getById(id);
        // 5. 不存在,返回错误
        if(shop == null){
            return Result.fail("店铺不存在!");
        }
        // 6. 存在,写入redis
        stringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        // 7. 返回
        return Result.ok(shop);
    }

修改店铺:

@Override
    @Transactional
    public Result update(Shop shop) {
        Long id = shop.getId();
        if(id == null){
            return Result.fail("店铺id不能为空");
        }
        // 更新数据库,在删除缓存
        updateById(shop);
        // 删除缓存
        stringRedisTemplate.delete(RedisConstants.CACHE_SHOP_KEY + id);
        return Result.ok();
    }

缓存穿透

客户端请求的数据在缓存和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库

在这里插入图片描述

缓存空对象

在这里插入图片描述
可以设置一个TTL,解决内存消耗问题
可能存在短期不一致的问题,控制TTL的时间,可以一定程度的缓解这个问题。

布隆过滤

客户端个redis之间,在加一层过滤——布隆过滤器——哈希算法二进制位保存数据
布隆过滤器说如果不存在一定是不存在,但存在不一定是100% 的
在这里插入图片描述
先看一下之前查询商铺信息的业务流程
在这里插入图片描述
物品们采用方案一应该把空数据写入redis

在这里插入图片描述
在这里插入图片描述

缓存雪崩

在这里插入图片描述

解决方案

  • 给不同的key的TTL添加随机值——针对问题一
  • 利用redis集群提高服务的可用性——针对问题二
  • 给缓存业务添加降级限流策略
  • 给业务添加多级缓存

缓存击穿

在这里插入图片描述

解决方案

互斥锁和逻辑过期
在这里插入图片描述
在这里插入图片描述

基于互斥锁方式解决缓存击穿问题

在这里插入图片描述
获取锁:
- redis的setnx指令可以在key不存在的时候写,存在的时候不能写,就类似于互斥
释放锁:
- 删掉就行了
设置锁的时候要设置有效期,避免因为某种原因锁得不到释放

 @Override
    public Result queryById(Long id) {
        // 缓存穿透
//        Shop shop = queryWithPassThrough(id);

        // 互斥锁解决缓存击穿
        Shop shop = queryWithMutex(id);
        if(shop == null){
            return Result.fail("店铺不存在!");
        }
        // 7. 返回
        return Result.ok(shop);
    }
 /**
     * 解决缓存击穿(互斥锁)的写法
     * @param id
     * @return
     */
    public Shop queryWithMutex(Long id){
        String redisKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1. 从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isNotBlank(shopJson)){
            // 3. 存在,直接返回
            return JSONUtil.toBean(shopJson, Shop.class);
        }

        // 命中的是否是空值
        if(shopJson != null){
            // 返回一个错误信息
            return null;
        }
        //4. 开始实现缓存重建
        // 4.1 获取互斥锁
        String lockKey = "lock:shop:" + id;
        Shop shop = null;
        try{
            boolean isLock = tryLock(lockKey);
            // 4.2 判断是否获取成功
            if(!isLock){
                // 4.3 如果失败,则休眠并重试
                Thread.sleep(50);
                return queryWithMutex(id);
            }

            // 4.4 如果成功,根据id查询数据库
            shop = getById(id);
            // 模拟重建的延时——测试的时候打开
//            Thread.sleep(200);
            // 5. 不存在,返回错误
            if(shop == null){
                // 将空值写入redis——解决缓存穿透
                stringRedisTemplate.opsForValue().set(redisKey,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
                // 返回错误信息
                return null;
            }
            // 6. 存在,写入redis
            stringRedisTemplate.opsForValue().set(redisKey,JSONUtil.toJsonStr(shop), RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);
        } catch (InterruptedException e){
            throw new RuntimeException(e);
        }finally {
            // 释放互斥锁
            unLock(lockKey);
        }
        // 7. 返回
        return shop;
    }
    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);  // 因为flag是封装类,而要求的返回值是基本数据类型,在返回的时候就会进行自动的拆箱,拆箱的时候会出现空指针
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }

基于逻辑过期的方式解决缓存击穿问题

在这里插入图片描述

有个小问题,我们想要给存入redis的数据添加过期时间,但是我们的Shop实体类中又没有过期时间这个字段怎么办呢?
我们去给这个Shop实体添加过期时间字段可行吗?可行,但是对代码有侵入性,而且这个字段除了这里其他地方都用不到。
那怎么办?
我们可以声明一个RedisData的实体类,里面有一个过期时间的属性,让Shop继承这个实体类,Shop也就有了过期时间的属性了,但还是有一点点不好,还是需要修改源代码,需要修改Shop,有一定的侵入性,虽然也蛮好的。
还有一种方案:在RedisData中在声明一个Object的字段,把想要存储的数据放到Object中。

@Data
public class RedisData {
    private LocalDateTime expireTime;
    private Object data;
}

实际的项目肯定会有管理系统在后台点击,把热点数据提前缓存进redis,我们这里用一个单元测试完成这个功能。
先写一个缓存进redis的方法

    public void saveShop2Redis(Long id, Long expireSeconds){
        // 1. 查询店铺数据
        Shop shop = getById(id);
        // 2. 封装逻辑过期时间
        RedisData redisData = new RedisData();
        redisData.setData(shop);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(expireSeconds));
        ///3.写入redis
        stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisData));
    }

在编写一个单元测试

@SpringBootTest
class HmDianPingApplicationTests {

    @Autowired
    private ShopServiceImpl shopService;

    @Test
    void testSaveShop() {
        shopService.saveShop2Redis(1L, 10L);
    }

}

下面我们完成基于逻辑过期的方式解决缓存击穿的商铺查询的代码

// 使用线程池来开辟新线程
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    /**
     * 解决缓存击穿(逻辑过期)的写法
     * @param id
     * @return
     */
    public Shop queryWithLogicalExpire(Long id){
        String redisKey = RedisConstants.CACHE_SHOP_KEY + id;
        // 1. 从redis查询商铺缓存
        String shopJson = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isBlank(shopJson)){
            // 3. 不存在,直接返回
            return null;
        }
        // 4. 命中需要判断过期时间,需要先把json反序列化位对象
        RedisData redisData = JSONUtil.toBean(shopJson, RedisData.class);
        JSONObject jsonData = (JSONObject) redisData.getData(); // 如果不强转就是一个Object,但本质上是JSONObject,所以先转成JSONObject
        Shop shop = JSONUtil.toBean(jsonData, Shop.class);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5. 判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            // 5.1 未过期,直接返回店铺信息
            return shop;
        }
        // 5.2 已过期,需要缓存重建
        // 6. 缓存重建
        // 6.1 获取互斥锁
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2 判断是否获取锁成功
        if(isLock){
            //6.3成功 开启独立线程实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
               try {
                   // 重建缓存
                   this.saveShop2Redis(id,20L);
               }catch (Exception e){

               } finally {
                   // 释放锁
                   unLock(lockKey);
               }
            });
        }
        // 6.4 返回过期的商铺信息
        return shop;
    }

缓存工具封装

在这里插入图片描述
把封装的代码放到CacheClient这个类中,并添加@Component注解,把这个bean交给Spring管理,封装的工具类如下:


@Slf4j
@Component
public class CacheClient {


    private final StringRedisTemplate stringRedisTemplate;

    // 用构造器注入
    public CacheClient(StringRedisTemplate stringRedisTemplate){
        this.stringRedisTemplate = stringRedisTemplate;
    }

    public void set(String key, Object value, Long time, TimeUnit unit){
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(value),time,unit);
    }

    public void setWithLogicalExpire(String key, Object value, Long time, TimeUnit unit){
        // 设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(unit.toSeconds(time)));
        // 写入Redis
        stringRedisTemplate.opsForValue().set(key, JSONUtil.toJsonStr(redisData));
    }


    public <R, ID> R queryWithPassThrough(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String redisKey = keyPrefix + id;
        // 1. 从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isNotBlank(json)){
            // 3. 存在,直接返回
            return JSONUtil.toBean(json, type);
        }

        // 命中的是否是空值
        if(json != null){
            // 返回一个错误信息
            return null;
        }

        // 4. 不存在,根据id查询数据库——我们哪知道去查哪个数据库,只能调用者告诉我们,——函数式编程
        R r = dbFallback.apply(id);
        // 5. 不存在,返回错误
        if(r == null){
            // 将空值写入redis——解决缓存穿透
            stringRedisTemplate.opsForValue().set(redisKey,"",RedisConstants.CACHE_NULL_TTL,TimeUnit.MINUTES);
            // 返回错误信息
            return null;
        }
        // 6. 存在,写入redis
        this.set(redisKey, r, time, unit);
        // 7. 返回
        return r;
    }

    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    public <R,ID> R queryWithLogicalExpire(String keyPrefix, ID id, Class<R> type, Function<ID, R> dbFallback, Long time, TimeUnit unit){
        String redisKey = keyPrefix + id;
        // 1. 从redis查询商铺缓存
        String json = stringRedisTemplate.opsForValue().get(redisKey);
        // 2. 判读是否存在
        if(StrUtil.isBlank(json)){
            // 3. 不存在,直接返回
            return null;
        }
        // 4. 命中需要判断过期时间,需要先把json反序列化位对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        JSONObject jsonData = (JSONObject) redisData.getData(); // 如果不强转就是一个Object,但本质上是JSONObject,所以先转成JSONObject
        R r = JSONUtil.toBean(jsonData, type);
        LocalDateTime expireTime = redisData.getExpireTime();
        // 5. 判断是否过期
        if(expireTime.isAfter(LocalDateTime.now())){
            // 5.1 未过期,直接返回店铺信息
            return r;
        }
        // 5.2 已过期,需要缓存重建
        // 6. 缓存重建
        // 6.1 获取互斥锁
        String lockKey = RedisConstants.LOCK_SHOP_KEY + id;
        boolean isLock = tryLock(lockKey);
        // 6.2 判断是否获取锁成功
        if(isLock){
            //6.3成功 开启独立线程实现缓存重建
            CACHE_REBUILD_EXECUTOR.submit(()->{
                try {
                    // 重建缓存
                    // 先查数据库
                    R r1 = dbFallback.apply(id);
                    // 写入redis
                    this.setWithLogicalExpire(redisKey, r1, time, unit);
                }catch (Exception e){

                } finally {
                    // 释放锁
                    unLock(lockKey);
                }
            });
        }
        // 6.4 返回过期的商铺信息
        return r;
    }


    private boolean tryLock(String key){
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return BooleanUtil.isTrue(flag);  // 因为flag是封装类,而要求的返回值是基本数据类型,在返回的时候就会进行自动的拆箱,拆箱的时候会出现空指针
    }

    private void unLock(String key){
        stringRedisTemplate.delete(key);
    }


}

封装这个工具类,有很多的技巧要总结:

  1. 传递的参数和返回的数据类型要泛型
  2. 函数式编程:在封装queryWithPassThrough的时候,里面在redis查询不存在的时候,我们要去查询数据库,那查询数据库的代码,我们泛型传递的参数,调用哪个查询数据库的函数去查询数据库呢?这时要用函数式编程,把要用到的函数通过参数传递过来,有参数有返回值就用Function<ID, R> dbFallback,使用的时候直接R r = dbFallback.apply(id);即可,调用这个工具方法的时候把具体的查询函数作为参数传进去。

那这些工具类在调用的时候又该怎么调用呢?

  @Override
    public Result queryById(Long id) {
        // 缓存穿透
//        Shop shop = cacheClient.queryWithPassThrough(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);

        // 逻辑过期解决缓存击穿问题
        Shop shop = cacheClient.queryWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY, id, Shop.class, this::getById, 20L, TimeUnit.SECONDS);

        if(shop == null){
            return Result.fail("店铺不存在!");
        }
        // 7. 返回
        return Result.ok(shop);
    }

那我们的缓存击穿想测试的话,还是得先用单元测试的方法,先往redis中写入点热点数据,现在就可以改进我们的单元测试代码


@SpringBootTest
class HmDianPingApplicationTests {
    @Autowired
    private CacheClient cacheClient;

    @Test
    void testSaveShop() {
        Shop shop = shopService.getById(1L);
        cacheClient.setWithLogicalExpire(RedisConstants.CACHE_SHOP_KEY + 1L,shop,10L, TimeUnit.SECONDS);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2091848.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【教程】实测np.fromiter 和 np.array 的性能

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你&#xff0c;欢迎[点赞、收藏、关注]哦~ 目录 函数简介 np.fromiter np.array 测试代码 实验结果 结果分析 实验总结 学长想说 函数简介 np.fromiter np.fromiter 是 NumPy 提供的一…

【SuperCraft AI:无限工作流画布】

SuperCraft AI&#xff1a;无限工作流画布 SuperCraft 是一款全新的 AI 工具。它具有将手绘草图转换为不同产品图像的功能&#xff0c;提供了一个无限大的协作画布&#xff0c;让设计师能够在此手绘草图&#xff0c;并利用生成式 AI 技术将草图转化为高质量的 2D 图像和 3D 渲…

NC 二分查找-II

系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到网站&#xff0c;这篇文章男女通用&#xff0c;看懂了就去分享给你的码吧。 描述 请实现有重复…

Unity TreeView扩展

实现效果 这里原来是做的一个检测网络、事件回调耗时的工具。简单改了成了一个演示TreeView的demo。实现了TreeView的基本功能并且实现了对列的排序。TreeView还可以制作点击&#xff0c;双击&#xff0c;右键等事件&#xff0c;但这里暂时不需要用到。 思维导图 工程&#xf…

arcgisjs4.0 内网部署字体不显示问题处理

问题背景问题定位解决方案 问题背景 内网环境&#xff0c;通过压缩包的hash值验证了包是一摸一样的&#xff0c;ningx也读到了index.html&#xff0c;但是网格的字提显示出不来&#xff0c;并且地图上的注记文字均不显示 本地环境地图情况&#xff1a; 内网环境地图情况&…

Bluetooth: att protocol

一篇搞懂 ATT 支持的东西都有什么。 READ_BY_GROUP_TYPE_REQ/RSP 如下是 Spec 内容: The attributes returned shall be the attributes with the lowest handles within the handle range. These are known as the requested attributes.If the attributes with the requeste…

石油设备和相关机械都包涵那些?

关键字&#xff1a;钻杆测径仪&#xff0c;泵管测径仪&#xff0c;固井管道直线度测量仪&#xff0c;输送管测径仪&#xff0c;输送管检测设备&#xff0c; 石油设备是指在石油和天然气的勘探、开发、生产、储存和运输等过程中使用的各种机械和装置。这些设备通常包括但不限于…

黄力医生科普:如何有效预防冠心病,这几个保健措施不可少!

冠心病&#xff0c;作为心血管系统的一种常见病&#xff0c;主要因冠状动脉粥样硬化导致管腔狭窄或闭塞&#xff0c;进而引发心肌缺血缺氧。此病多发于中老年群体&#xff0c;且具有一定遗传性。然而&#xff0c;无论发病因素如何&#xff0c;我们都可以通过一系列有效的预防措…

C++类和对象(6)——初始化列表

一般的初始化 class A { public:A(int a){ //一般的初始化&#xff0c;在{}括号里面给成员变量赋值_a a;cout << _a << endl;}~A() {}private:int _a; }; 用 初始化列表 初始化 当成员变量是以下两种情况时&#xff0c; 必须使用初始化列表&#xff01; cons…

单自由度无阻尼系统振动分析

特别感谢&#xff1a;https://www.bilibili.com/video/BV114411y7ab/?p6&spm_id_frompageDriver&vd_sourceebe07816bf845358030fc92d23830b29 本文图片该系列视频 tips&#xff1a;关于特征方程与振动方程&#xff1a; 特征方程有助于我们理解和确定系统的固有频率和模…

【算法】贪心算法解析:基本概念、策略证明与代码例题演示

文章目录 1. 什么是贪心算法&#xff1f;2. 贪心算法的特点3. 例题&#xff08;贪心策略&#xff09;① 找零问题② 最小路径和③ 背包问题 4. 贪心策略证明 1. 什么是贪心算法&#xff1f; 在学习贪心算法之前&#xff0c;一定要理解的是贪心策略&#xff1a; 贪心策略是一种…

Ubuntu中qt类与类信号槽的创建及使用

今天学习到了新的一个小玩意&#xff0c;我们在QT中创建一个大项目的时候一般会创建多个类&#xff0c;那我们就来学习一下如何在自定义的类中声名和使用信号与槽函数。 首先我们CTRLn来创建我们新的类&#xff1a; 我们创建新的C的类&#xff0c;一个School&#xff0c;一个S…

举办知识竞赛是线上好还是线下好

举办知识竞赛线上和线下各有优势&#xff0c;选择哪种方式取决于具体的需求和条件。 线上举办知识竞赛的优缺点&#xff1a; 优点&#xff1a; 便捷性&#xff1a;线上竞赛不受地域限制&#xff0c;参与者可以在任何有网络的地方参与。 选手数&#xff1a;可以同时满足人数较…

单门店共享自习室小程序系统源码搭建对接门禁和空开api

共享自习室小程序&#xff0c;单门店共享自习室小程序&#xff0c;有源码&#xff0c;对接门禁和电控api接口&#xff0c;php开发语言&#xff0c;前端是uniapp。可以源码搭建&#xff0c;也可以二开或定制。 一 用户端 在线选择预约时间&#xff0c;选择座位&#xff0c;选择…

macOS搭建Python3.11+Django4.2.15的平台框架使用Poetry管理

最近想使用Python开发&#xff0c;使用Django框架搭建平台&#xff0c;之前没有使用过Python&#xff0c;所以记录下整个过程&#xff1a; 1、Python版本的选择&#xff0c;直接去官网【Download Python | Python.org】看最新稳定版是哪个版本&#xff0c;选择安装&#xff0c…

Ascend C算子开发(入门)—— 什么是算子?

文章目录 Ascend C算子开发&#xff08;入门&#xff09;—— 什么是算子&#xff1f;一、从人工智能到算子1.1 人工智能的四个层面1.2 人工智能之三大流派1.3 算子、神经元、神经网络 二、算子的基本概念2.1 算子在数学中的定义&#xff1a;2.2 算子基本概念 —— 总览2.3 算子…

利用clip模型实现text2draw

参考论文 实践 有数据增强的代码 import math import collections import CLIP_.clip as clip import torch import torch.nn as nn from torchvision import models, transforms import numpy as np import webp from PIL import Image import skimage import torchvision …

基于单片机的楼宇消防控制系统设计

本设计基于单片机的楼宇消防控制系统&#xff0c;主要包括温湿度检测模块、空气质量检测模块、火焰检测模块、ZigBee通信模块、报警模块和自动喷水模块。首先&#xff0c;系统通过温湿度检测模块实时监测楼道内的温湿度状况&#xff0c;以便及时掌握火灾发生前的环境变化。其次…

足底筋膜炎怎么治疗效果好

足底筋膜炎的症状 足底筋膜炎是一种常见的足部疾病&#xff0c;主要表现为足底区域&#xff08;尤其是脚跟附近&#xff09;的疼痛和不适。这种疼痛在早晨起床或长时间休息后初次站立时尤为明显&#xff0c;被形象地称为“晨间痛”。随着行走时间的增加&#xff0c;疼痛可能会…

直击源头!劳保鞋厂家揭秘机械制造业防护安全鞋挑选秘籍

在机械制造业这一高风险、高强度的行业中&#xff0c;选择合适的劳保鞋对于保障工人的安全至关重要。作为劳保鞋的生产厂家&#xff0c;我们深知一双优质的防护鞋能为工人提供怎样的保护。今天百华小编和大家从多个维度看一下机械制造业是如何挑选防护安全鞋的挑选秘籍&#xf…