【SpringBoot应用篇】SpringBoot集成Caffeine本地缓存

news2025/1/10 23:38:07

【SpringBoot应用篇】SpringBoot集成Caffeine本地缓存

  • 本地缓存介绍
  • 本地缓存方案选型
  • Caffeine
  • SpringBoot 集成 Caffeine 两种方式
    • SpringBoot 集成 Caffeine 方式一
      • pom
      • application.yml
      • 缓存配置类
      • User实体
      • UserMapper
      • UserService
      • UserController
    • SpringBoot 集成 Caffeine 方式二
      • pom
      • 缓存配置类
      • UserService

本地缓存介绍

缓存在日常开发中启动至关重要的作用,由于是存储在内存中,数据的读取速度是非常快的,能大量减少对数据库的访问,减少数据库的压力。

之前介绍过 Redis 这种 NoSql 作为缓存组件,它能够很好的作为分布式缓存组件提供多个服务间的缓存,但是 Redis 这种还是需要网络开销,增加时耗。

本地缓存是直接从本地内存中读取,没有网络开销,例如秒杀系统或者数据量小的缓存等,比远程缓存更合适。

本地缓存方案选型

1、 基于ConcurrentHashMap实现本地缓存
缓存的本质就是存储在内存中的KV数据结构,对应的就是jdk中线程安全的ConcurrentHashMap,但是要实现缓存,还需要考虑淘汰、最大限制、缓存过期时间淘汰等等功能;

优点是实现简单,不需要引入第三方包,比较适合一些简单的业务场景。缺点是如果需要更多的特性,需要定制化开发,成本会比较高,并且稳定性和可靠性也难以保障。对于比较复杂的场景,建议使用比较稳定的开源工具。

2、基于Guava Cache实现本地缓存
Guava是Google团队开源的一款 Java 核心增强库,包含集合、并发原语、缓存、IO、反射等工具箱,性能和稳定性上都有保障,应用十分广泛。Guava Cache支持很多特性:

  • 支持最大容量限制
  • 支持两种过期删除策略(插入时间和访问时间)
  • 支持简单的统计功能
  • 基于LRU算法实现

3、基于Caffeine实现本地缓存
Caffeine是基于java8实现的新一代缓存工具,缓存性能接近理论最优。可以看作是Guava Cache的增强版,功能上两者类似,不同的是Caffeine采用了一种结合LRU、LFU优点的算法:W-TinyLFU,在性能上有明显的优越性

4、 基于Ehcache实现本地缓存
Ehcache是一个纯Java的进程内缓存框架,具有快速、精干等特点,是Hibernate中默认的CacheProvider。同Caffeine和Guava Cache相比,Ehcache的功能更加丰富,扩展性更强:

  • 支持多种缓存淘汰算法,包括LRU、LFU和FIFO
  • 缓存支持堆内存储、堆外存储、磁盘存储(支持持久化)三种
  • 支持多种集群方案,解决数据共享问题

Caffeine

在项目开发中,为提升系统性能,减少 IO 开销,本地缓存是必不可少的。最常见的本地缓存是 Guava 和 Caffeine。

Caffeine 是基于 Google Guava Cache 设计经验改进的结果,相较于 Guava 在性能和命中率上更具有效率,你可以认为其是 Guava Plus。

Caffeine 是基于Java 8 开发的、提供了近乎最佳命中率的高性能本地缓存组件,Spring5 开始不再支持 Guava Cache,改为使用 Caffeine。

在下面缓存组件中 Caffeine 性能是其中最好的

在这里插入图片描述

SpringBoot 集成 Caffeine 两种方式

SpringBoot 有俩种使用 Caffeine 作为缓存的方式:

方式一: 直接引入 Caffeine 依赖,然后使用 Caffeine 提供的api方法实现本地缓存。

方式二: 引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现本地缓存。

SpringBoot 集成 Caffeine 方式一

pom

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>com.github.ben-manes.caffeine</groupId>
        <artifactId>caffeine</artifactId>
    </dependency>
     <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.14</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
</dependencies>

<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
        </plugin>
    </plugins>
</build>

application.yml

# DataSource Config
spring:
  datasource:
    #   数据源基本配置
    url: jdbc:mysql://localhost:3306/study_db?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&nullCatalogMeansCurrent=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
    initialization-mode: always #表示始终都要执行初始化,2.x以上版本需要加上这行配置
    type: com.alibaba.druid.pool.DruidDataSource
    #   数据源其他配置
    initialSize: 5
    minIdle: 5
    maxActive: 20
    maxWait: 60000
    timeBetweenEvictionRunsMillis: 60000
    minEvictableIdleTimeMillis: 300000
    validationQuery: SELECT 1 FROM DUAL
    testWhileIdle: true
    testOnBorrow: false
    testOnReturn: false
    poolPreparedStatements: true
    #   配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
    filters: stat,wall,log4j
    maxPoolPreparedStatementPerConnectionSize: 20
    useGlobalDataSourceStat: true
    connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500

# Logger Config
logging:
  level:
    cn.zysheep.mapper: debug

缓存配置类

@Configuration
public class CaffeineCacheConfig {
    @Bean
    public Cache<String, Object> caffeineCache() {
        return Caffeine.newBuilder()
                // 设置最后一次写入或访问后经过固定时间过期
                .expireAfterWrite(60, TimeUnit.SECONDS)
                // 初始的缓存空间大小
                .initialCapacity(100)
                // 缓存的最大条数
                .maximumSize(1000)
                .build();
    }
}

User实体

@TableName(value ="tb_user")
@Data
public class User implements Serializable {
    /**
     * 用户ID
     */
    @TableId(type = IdType.AUTO)
    private Long id;

    /**
     * 姓名
     */
    @TableField("username")
    private String userName;

    /**
     * 现在住址
     */
    @TableField("address")
    private String address;
}

UserMapper

public interface UserMapper extends BaseMapper<User> {

}

UserService

public interface UserService extends IService<User> {

    void saveUser(User user);
    
    User getUserById(Long id);

    User updateUser(User user);

    String deleteUserById(Long id);
}
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);


    @Autowired
    private Cache<String, Object> caffeineCache;

    @Override
    public void saveUser(User user) {
        save(user);
        // 加入缓存
        caffeineCache.put(String.valueOf(user.getId()),user);
    }

    /**
     * 查询用户信息,并缓存结果
     *
     * @param id
     * @return
     */
    public User getUserById(Long id) {
        // 先从缓存读取
        caffeineCache.getIfPresent(id);
        User user = (User) caffeineCache.asMap().get(String.valueOf(id));
        if (Objects.nonNull(user)) {
            return user;

        }
        // 如果缓存中不存在,则从库中查找
        user = getById(id);
        // 如果用户信息不为空,则加入缓存
        if (user != null) {
            caffeineCache.put(String.valueOf(user.getId()), user);
        }

        log.info("从数据库中读取,而非从缓存读取!");
        log.info("users: {}", user);
        return user;
    }


    /**
     * 更新用户信息
     * @param user
     * @return
     */
    public User updateUser(User user) {
        log.info("user: {}", user);
        updateById(user);
        User user1 = getById(user.getId());
        // 替换缓存中的值
        caffeineCache.put(String.valueOf(user1.getId()), user1);
        return user1;
    }

    public String deleteUserById(Long id) {
        boolean b = removeById(id);
        if (b) {
            // 从缓存中删除
            caffeineCache.asMap().remove(String.valueOf(id));
        }
        return b ? "删除成功" : "删除失败";
    }
}

UserController

@Getter
@Setter
@SuppressWarnings({"AlibabaClassNamingShouldBeCamel"})
@Accessors(chain = true)
public class R<T> {
    public static final String DEF_ERROR_MESSAGE = "系统繁忙,请稍候再试";
    public static final String HYSTRIX_ERROR_MESSAGE = "请求超时,请稍候再试";
    public static final int SUCCESS_CODE = 0;
    public static final int FAIL_CODE = -1;
    public static final int TIMEOUT_CODE = -2;
    /**
     * 统一参数验证异常
     */
    public static final int VALID_EX_CODE = -9;
    public static final int OPERATION_EX_CODE = -10;
    /**
     * 调用是否成功标识,0:成功,-1:系统繁忙,此时请开发者稍候再试 详情见[ExceptionCode]
     */
    private int code;

    /**
     * 调用结果
     */
    private T data;

    /**
     * 结果消息,如果调用成功,消息通常为空T
     */
    private String msg = "ok";


    private String path;
    /**
     * 附加数据
     */
    private Map<String, Object> extra;

    /**
     * 响应时间
     */
    private long timestamp = System.currentTimeMillis();

    private R() {
        super();
    }

    public R(int code, T data, String msg) {
        this.code = code;
        this.data = data;
        this.msg = msg;
    }

    public static <E> R<E> result(int code, E data, String msg) {
        return new R<>(code, data, msg);
    }

    /**
     * 请求成功消息
     *
     * @param data 结果
     * @return RPC调用结果
     */
    public static <E> R<E> success(E data) {
        return new R<>(SUCCESS_CODE, data, "ok");
    }

    public static R<Boolean> success() {
        return new R<>(SUCCESS_CODE, true, "ok");
    }

    /**
     * 请求成功方法 ,data返回值,msg提示信息
     *
     * @param data 结果
     * @param msg  消息
     * @return RPC调用结果
     */
    public static <E> R<E> success(E data, String msg) {
        return new R<>(SUCCESS_CODE, data, msg);
    }

    /**
     * 请求失败消息
     *
     * @param msg
     * @return
     */
    public static <E> R<E> fail(int code, String msg) {
        return new R<>(code, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
    }

    public static <E> R<E> fail(String msg) {
        return fail(OPERATION_EX_CODE, msg);
    }

    public static <E> R<E> fail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(OPERATION_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> fail(BaseExceptionCode exceptionCode) {
        return validFail(exceptionCode);
    }

    public static <E> R<E> fail(BizException exception) {
        if (exception == null) {
            return fail(DEF_ERROR_MESSAGE);
        }
        return new R<>(exception.getCode(), null, exception.getMessage());
    }

    /**
     * 请求失败消息,根据异常类型,获取不同的提供消息
     *
     * @param throwable 异常
     * @return RPC调用结果
     */
    public static <E> R<E> fail(Throwable throwable) {
        return fail(FAIL_CODE, throwable != null ? throwable.getMessage() : DEF_ERROR_MESSAGE);
    }

    public static <E> R<E> validFail(String msg) {
        return new R<>(VALID_EX_CODE, null, (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg);
    }

    public static <E> R<E> validFail(String msg, Object... args) {
        String message = (msg == null || msg.isEmpty()) ? DEF_ERROR_MESSAGE : msg;
        return new R<>(VALID_EX_CODE, null, String.format(message, args));
    }

    public static <E> R<E> validFail(BaseExceptionCode exceptionCode) {
        return new R<>(exceptionCode.getCode(), null,
                (exceptionCode.getMsg() == null || exceptionCode.getMsg().isEmpty()) ? DEF_ERROR_MESSAGE : exceptionCode.getMsg());
    }

    public static <E> R<E> timeout() {
        return fail(TIMEOUT_CODE, HYSTRIX_ERROR_MESSAGE);
    }


    public R<T> put(String key, Object value) {
        if (this.extra == null) {
            this.extra = Maps.newHashMap();
        }
        this.extra.put(key, value);
        return this;
    }

    /**
     * 逻辑处理是否成功
     *
     * @return 是否成功
     */
    public Boolean getIsSuccess() {
        return this.code == SUCCESS_CODE || this.code == 200;
    }

    /**
     * 逻辑处理是否失败
     *
     * @return
     */
    public Boolean getIsError() {
        return !getIsSuccess();
    }

    @Override
    public String toString() {
        return JSONObject.toJSONString(this);
    }
}
@RestController
@RequestMapping("/api")
public class UserController {

    @Autowired
    private UserService userService;


    @PostMapping("save")
    public R saveUser(@RequestBody User user) {
        userService.saveUser(user);
        return R.success(null);
    }


    @GetMapping("getById")
    public R getById(@RequestParam Long id) {
        User user = userService.getUserById(id);
        return R.success(user);
    }

    @GetMapping("getByIdNoCache")
    public R getByNameNoCache(@RequestParam Long id) {
        List<User> users = userService.getUserByIdNoCache(id);
        return R.success(users);
    }


    @PostMapping("updateUser")
    public R updateUser(User user) {
        return R.success(userService.updateUser(user));
    }


    @PostMapping("deleteUserById")
    public R deleteUserById(Long id) {
        return R.success(userService.deleteUserById(id));
    }
}

SpringBoot 集成 Caffeine 方式二

引入 Caffeine 和 Spring Cache 依赖,使用 SpringCache 注解方法实现本地缓存。

pom

方式一的依赖中添加spring-boot-starter-cache依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

缓存配置类

@Configuration
@EnableCaching
public class CaffeineCacheConfig {
    @Bean
    public CacheManager cacheManager(){
        CaffeineCacheManager cacheManager = new CaffeineCacheManager();
        //Caffeine配置
        Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
                //最后一次写入后经过固定时间过期
                .expireAfterWrite(60*5, TimeUnit.SECONDS)
                //maximumSize=[long]: 缓存的最大条数
                .maximumSize(1000);
        cacheManager.setCaffeine(caffeine);
        return cacheManager;
    }

//    @Bean
//    public Cache<String, Object> caffeineCache() {
//        return Caffeine.newBuilder()
//                // 设置最后一次写入或访问后经过固定时间过期
//                .expireAfterWrite(60, TimeUnit.SECONDS)
//                // 初始的缓存空间大小
//                .initialCapacity(100)
//                // 缓存的最大条数
//                .maximumSize(1000)
//                .build();
//    }
}

UserService

public interface UserService extends IService<User> {

    void saveUser(User user);

    List<User> getUserByIdNoCache(Long id);

    User getUserById(Long id);

    User updateUser(User user);

    List<User> getUserByIdAndName(Long id, String userName);

    List<User> getUser(User user);

    String deleteUserById(Long id);
}
@Service
@Transactional
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    private static final Logger log = LoggerFactory.getLogger(UserServiceImpl.class);


//    @Autowired
//    private Cache<String, Object> caffeineCache;

    @Override
    public void saveUser(User user) {
        save(user);
        // 加入缓存
       // caffeineCache.put(String.valueOf(user.getId()),user);
    }

    public List<User> getUserByIdNoCache(Long id) {
        LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery().like(Objects.nonNull(id), User::getId, id);
        List<User> users = list(queryWrapper);
        log.info("从数据库中读取,而非从缓存读取!");
        log.info("users: {}", users);
        return users;
    }



    /**
     * 查询用户信息,并缓存结果
     *
     * @param id
     * @return
     */
    @Cacheable(cacheNames = "user", key = "#id")
    public User getUserById(Long id) {
        // 先从缓存读取
//        caffeineCache.getIfPresent(id);
//        User user = (User) caffeineCache.asMap().get(String.valueOf(id));
//        if (Objects.nonNull(user)) {
//            return user;
//
//        }
        // 如果缓存中不存在,则从库中查找
        User user = getById(id);
        // 如果用户信息不为空,则加入缓存
//        if (user != null) {
//            caffeineCache.put(String.valueOf(user.getId()), user);
//        }

        log.info("从数据库中读取,而非从缓存读取!");
        log.info("users: {}", user);
        return user;
    }

    // spEL使用"T(Type)"来表示 java.lang.Class 实例,"Type"必须是类全限定名,"java.lang"包除外。
    @Cacheable(cacheNames = "user", key = "T(String).valueOf(#id).concat('::').concat(#userName)")
    public List<User> getUserByIdAndName(Long id, String userName) {
        LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery()
                .like(StringUtils.isNotBlank(userName), User::getUserName, userName)
                .eq(Objects.nonNull(id), User::getId, id);
        List<User> users = list(queryWrapper);
        log.info("从数据库中读取,而非从缓存读取!");
        return users;
    }

    @Cacheable(cacheNames = "user", key = "#user.userName")
    public List<User> getUser(User user) {
        LambdaQueryWrapper<User> queryWrapper = Wrappers.<User>lambdaQuery().like(StringUtils.isNotBlank(user.getUserName()), User::getUserName, user.getUserName());
        List<User> users = list(queryWrapper);
        log.info("从数据库中读取,而非从缓存读取!");
        return users;
    }

    /**
     * 更新用户信息
     *
     * @param user
     * @return
     */
    @CachePut(cacheNames = "user", key = "#result.id")
    public User updateUser(User user) {
        log.info("user: {}", user);
        updateById(user);
        User user1 = getById(user.getId());


        // 替换缓存中的值
        // caffeineCache.put(String.valueOf(user1.getId()), user1);

        return user1;
    }

    @CacheEvict(cacheNames = "user", beforeInvocation = true, key = "#id")
    public String deleteUserById(Long id) {
        boolean b = removeById(id);
//        if (b) {
//            // 从缓存中删除
//            caffeineCache.asMap().remove(String.valueOf(id));
//        }
        //  int i = 1 / 0;
        return b ? "删除成功" : "删除失败";
    }
}

标注缓存注解

  • @Cacheable: @Cacheble注解表示这个方法有了缓存的功能,方法的返回值会被缓存下来,下一次调用该方法前,会去检查是否缓存中已经有值,如果有就直接返回,不调用方法。如果没有,就调用方法,然后把结果缓存起来。这个注解一般用在查询方法上。
  • @CacheEvict: @CacheEvict注解的方法,会清空指定缓存。一般用在更新或者删除的方法上。
  • @CachePut : @CachePut注解的方法,保证方法被调用,又希望结果被缓存。会把方法的返回值put到缓存里面缓存起来。它通常用 在新增方法上。
  • @Caching :定义复杂的缓存规则
  • @CacheConfig:抽取缓存的公共配置

注解的具体用法: 【SpringBoot高级篇】SpringBoot集成cache本地缓存

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

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

相关文章

开源抖音小程序系统源码分享+完整版安装教程

给大家分享一个抖音小程序系统源码&#xff0c;系统是开源运营版&#xff0c;源码包含完整搭建部署教程&#xff0c;内含多套模板、模块化自由DIY功能&#xff0c;支持除抖音小程序制作外&#xff0c;还支持一键同步微信、支付宝、百度、今日头条端小程序等等&#xff0c;功能是…

使用Python操作CSV文件,方便又快捷

概念 CSV是逗号分隔值或者字符分割值&#xff0c;其文件以纯文本形式存储表格数据。 CSV文件可以用文本文件或者转换成EXCEL&#xff08;直接用EXCEL也可以&#xff0c;但是可能会有一些问题&#xff09;打开。因此更适合通过CSV文件进行程序之间转移表格数据。 应用场景 需…

uniapp 实现多音频同时播放 解决sessionCategory = “ambient“不生效问题

1.1完整代码 <template><view></view> </template><script>export default {data() {return {BGInnerAudioContext1: null, //背景1BGInnerAudioContext2: null, //背景2}},onLoad() {this.play1()this.play2()},methods: {//音频1play1() {//背…

淘女郎买家秀API接口

淘女郎买家秀API接口是一款便捷、安全、高效的接口服务&#xff0c;旨在帮助各类电商平台实现在线评价、商品推荐和用户洞察等功能。接口提供了一系列API功能&#xff0c;包括用户认证、淘宝订单查询、评价抓取、订单分析、商品推荐、场景洞察等。接口同时支持多种语言&#xf…

数据分析必知的统计知识——方差分析共八篇(其六)

6. 方差分析 单因素多水平方差分析 例6.1 不同装配方式对生产的过滤系统数量的差异性检验 某城市过滤水系统生产公司&#xff0c;有A、B、C3种方式进行过滤水系统的装配&#xff0c;该公司为了研究三种装配方式生产的过滤系统数量是否有差异&#xff0c;从全体装配工人中抽取…

第17章 站点构建

mini商城第17章 站点构建 一、课题 站点构建 二、回顾 1、Gateway限流 2、Nginx限流 3、Redis集群应用 4、缓存灾难处理 三、目标 1、Sentinel Sentinel介绍 Sentinel核心功能 Sentinel集成Gateway Sentinel控制台 2、Lvs+Nginx集群 Lvs负载均衡模式 NAT模式 TUN模式 …

——二叉树

二叉树种类 二叉树有两种主要的形式&#xff1a;满二叉树和完全二叉树。 满二叉树 如果一棵二叉树只有度为0的结点和度为2的结点&#xff0c;并且度为0的结点在同一层上&#xff0c;则这棵二叉树为满二叉树。 完全二叉树 在完全二叉树中&#xff0c;除了最底层节点可能没…

最新数据库流行度最新排名(每月更新)

2023年09月数据库流行度最新排名 TOP DB顶级数据库索引是通过分析在谷歌上搜索数据库名称的频率来创建的 一个数据库被搜索的次数越多&#xff0c;这个数据库就被认为越受欢迎。这是一个领先指标。原始数据来自谷歌Trends 如果您相信集体智慧&#xff0c;那么TOP DB索引可以帮…

为什么说在「云端」进行产品开发与管理是大势所趋?

提起工业软件&#xff0c;大家会想到各种应用程序&#xff0c;比如机械设计软件、电气设计软件、仿真模拟、生命周期管理等&#xff0c;进而又引出各个工业软件原厂商。由于不同软件之间的壁垒&#xff0c;用户很难实现各个软件之间的协同。不过本文要谈的3DEXPERIENCE WORKS&a…

sql注入的数据提交方式和查询方式

死在山野的风里&#xff0c;活在自由的梦里 sql注入的数据提交方式和查询方式 数据提交方式GET方式注入POST方式注入Request方式注入HTTP头注入什么是Header头部注入&#xff1f;cookie注入 查询方式 数据提交方式 GET方式注入 get注入方式比较常见&#xff0c;主要是通过ur…

@全体开发者们,ChunJun 有奖征文,精美奖品,快来参加!

2022年4月&#xff0c;在 FlinkX 进行初版开源的整整四年后&#xff0c;技术团队决定对FlinkX进行整体升级&#xff0c;并更名为 ChunJun。到目前为止&#xff0c;ChunJun 正式更名上线已经过了一年多了。作为一款稳定、易⽤、⾼效、批流⼀体的数据集成框架&#xff0c;相信各位…

博客系统(升级(Spring))(四)(完)基本功能(阅读,修改,添加,删除文章)

博客系统 (三&#xff09; 博客系统博客主页前端后端个人博客前端后端显示个人文章删除文章 修改文章前端后端提取文章修改文章 显示正文内容前端后端文章阅读量功能 博客系统 博客系统是干什么的&#xff1f; CSDN就是一个典型的博客系统。而我在这里就是通过模拟实现一个博客…

ARM接口编程—WDT(exynos 4412平台)

WDT简介 Watch Dog Timer即看门狗定时器&#xff0c;其主要作用是当发生软件故障时可产生复位信号使SOC复位&#xff0c;其本质是一个计数器 WDT工作原理 WTD寄存器 wtd控制寄存器 用于设置一级分频、二级分频、使能、产生复位和中断信号 WTD数据寄存器 用于获取计数值&…

如何在Windows系统搭建filebrowser私人网盘并实现在外网访问本地内网

Windows系统搭建网盘神器filebrowser结合内网穿透实现公网访问 文章目录 Windows系统搭建网盘神器filebrowser结合内网穿透实现公网访问前言1.下载安装File Browser2.启动访问File Browser3.安装cpolar内网穿透3.1 注册账号3.2 下载cpolar客户端3.3 登录cpolar web ui管理界面3…

JBoss JMXInvokerServlet 反序列化漏洞复现(CVE-2015-7501)

一、漏洞说明 JBoss中/invoker/JMXInvokerServlet路径对外开放&#xff0c;JBoss的jmx组件支持反序列化。JBoss在/invoker/JMXInvokerServlet请求中读取了用户传入的对象&#xff0c;然后我们利用Apache Commons Collections中的Gadget执行任意代码。 二、影响版本 JBoss Enter…

虹科分享 | 知识产权盗窃:它是什么以及如何预防

知识产权 (IP) 涵盖各种形式的创造力和创新&#xff0c;例如艺术品、配方、徽标、文献、食谱、工业设计等。个人和企业都可以拥有知识产权&#xff0c;赋予他们对其想法和创作的合法权利。这些权利帮助知识产权所有者从他们的作品中获益、保护作品并防止复制。知识产权对于推动…

【Redis】Redis 的学习教程(九)之 发布 Pub、订阅 Sub

1. Pub/Sub 介绍 Redis 的发布订阅&#xff08;Pub/Sub&#xff09;模式是一种消息传递机制&#xff0c;它允许在发送者和接收者之间建立松耦合的通信关系。在这种模式中&#xff0c;发送者&#xff08;发布者&#xff09;将消息发布到一个指定的频道或模式&#xff0c;而接收…

用户体验设计师是什么,一篇文章读懂!

我是设计师l1m0&#xff0c;今天要给大家分享一个有趣的职业&#xff1a;UX设计师。 在我们日常生活中&#xff0c;我们无时无刻都在与产品发生交互行为&#xff0c;例如使用应用APP、访问网站、与实体陈燕萍进行交互&#xff08;例如试穿衣服&#xff09;或者享受某个服务&am…

恒运资本:多股涨停!“吃药”行情卷土重来;政策利好,元宇宙又可以了!

今日早盘&#xff0c;A股小幅震荡反弹&#xff0c;科创50指数继续围绕900点打开争夺。 盘面上&#xff0c;医药&#xff0c;轿车、元世界、煤炭等板块涨幅居前&#xff0c;航空、家居用品、卫星导航、房地产等板块跌幅居前。北上资金净流出4.4亿元。 医药股全线走强 医药股早…

12个小朋友手拉手站成一个圆圈 约瑟夫环 + 字节历险记

目录 12个小朋友手拉手站成一个圆圈&#xff0c;从某一个小朋友开始报数&#xff0c;报到7的那个小朋友退到圈外&#xff0c;然后他的下一位重新报“1”。这样继续下去&#xff0c;直到最后只剩下一个小朋友求解这个小朋友原来站在什么位置上呢? 请问在互联网公司中,OKR是什…