当前位置: 首页 > news >正文

推广深圳/搜索引擎优化技术

推广深圳,搜索引擎优化技术,购物网站开发的意义,沈阳网页模板建站Redis 学习笔记 3:黑马点评 准备工作 需要先导入项目相关资源: 数据库文件 hmdp.sql后端代码 hm-dianping.zip包括前端代码的 Nginx 启动后端代码和 Nginx。 短信登录 发送验证码 PostMapping("code") public Result sendCode(RequestP…

Redis 学习笔记 3:黑马点评

准备工作

需要先导入项目相关资源:

  • 数据库文件 hmdp.sql
  • 后端代码 hm-dianping.zip
  • 包括前端代码的 Nginx

启动后端代码和 Nginx。

短信登录

发送验证码

@PostMapping("code")
public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {// 发送短信验证码并保存验证码return userService.sendCode(phone, session);
}
@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手机号!");}String code = RandomUtil.randomNumbers(6);session.setAttribute("code", code);// 发送短信log.debug("发送短信验证码:{}", code);return Result.ok();}
}

登录

@PostMapping("/login")
public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session) {// 实现登录功能return userService.login(loginForm, session);
}
@Override
public Result login(LoginFormDTO loginForm, HttpSession session) {// 验证手机号和验证码if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号不合法!");}String code = (String) session.getAttribute("code");if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("验证码不正确!");}// 检查用户是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 将用户信息保存到 sessionsession.setAttribute("user", user);return Result.ok();
}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;
}

统一身份校验

定义拦截器:

public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从 session 获取用户信息HttpSession session = request.getSession();User user = (User) session.getAttribute("user");if (user == null) {response.setStatus(401);return false;}// 将用户信息保存到 ThreadLocalUserDTO userDTO = new UserDTO();userDTO.setIcon(user.getIcon());userDTO.setId(user.getId());userDTO.setNickName(user.getNickName());UserHolder.saveUser(userDTO);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

添加拦截器:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

使用 Redis 存储验证码和用户信息

用 Session 存储验证码和用户信息的系统,无法进行横向扩展,因为多台 Tomcat 无法共享 Session。如果改用 Redis 存储就可以解决这个问题。

修改后的 UserService:

@Log4j2
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Overridepublic Result sendCode(String phone, HttpSession session) {if (RegexUtils.isPhoneInvalid(phone)) {return Result.fail("不是合法的手机号!");}String code = RandomUtil.randomNumbers(6);stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL);// 发送短信log.debug("发送短信验证码:{}", code);return Result.ok();}@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 验证手机号和验证码if (RegexUtils.isPhoneInvalid(loginForm.getPhone())) {return Result.fail("手机号不合法!");}String code = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + loginForm.getPhone());if (code == null || !code.equals(loginForm.getCode())) {return Result.fail("验证码不正确!");}// 检查用户是否存在QueryWrapper<User> qw = new QueryWrapper<>();qw.eq("phone", loginForm.getPhone());User user = this.baseMapper.selectOne(qw);if (user == null) {user = this.createUserByPhone(loginForm.getPhone());}// 将用户信息保存到 sessionString token = UUID.randomUUID().toString(true);UserDTO userDTO = new UserDTO();BeanUtils.copyProperties(user, userDTO);try {stringRedisTemplate.opsForValue().set(LOGIN_USER_KEY + token,OBJECT_MAPPER.writeValueAsString(userDTO), LOGIN_USER_TTL);} catch (JsonProcessingException e) {e.printStackTrace();throw new RuntimeException(e);}return Result.ok(token);}private User createUserByPhone(String phone) {User user = new User();user.setPhone(phone);user.setNickName("user_" + RandomUtil.randomString(5));this.baseMapper.insert(user);return user;}
}

修改后的登录校验拦截器:

public class LoginInterceptor implements HandlerInterceptor {private final StringRedisTemplate stringRedisTemplate;private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();public LoginInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 从头信息获取 tokenString token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {// 缺少 tokenresponse.setStatus(401);return false;}// 从 Redis 获取用户信息String jsonUser = this.stringRedisTemplate.opsForValue().get(LOGIN_USER_KEY + token);UserDTO userDTO = OBJECT_MAPPER.readValue(jsonUser, UserDTO.class);if (userDTO == null) {response.setStatus(401);return false;}// 将用户信息保存到 ThreadLocalUserHolder.saveUser(userDTO);// 刷新 token 有效期stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {UserHolder.removeUser();}
}

还需要添加一个更新用户信息有效期的拦截器:

public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 如果请求头中有 token,且 redis 中有 token 相关的用户信息,刷新其有效期String token = request.getHeader("Authorization");if (ObjectUtils.isEmpty(token)) {return true;}if (Boolean.TRUE.equals(stringRedisTemplate.hasKey(LOGIN_USER_KEY + token))) {stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL);}return true;}
}

添加这个新的拦截器,并且确保其位于登录验证拦截器之前:

@Configuration
public class WebMVCConfig implements WebMvcConfigurer {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");registry.addInterceptor(new LoginInterceptor(stringRedisTemplate)).excludePathPatterns("/shop/**","/voucher/**","/shop-type/**","/upload/**","/blog/hot","/user/code","/user/login");}
}

商户查询

缓存

对商户类型查询使用 Redis 缓存以提高查询效率:

@Service
public class ShopTypeServiceImpl extends ServiceImpl<ShopTypeMapper, ShopType> implements IShopTypeService {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryTypeList() {String jsonTypeList = stringRedisTemplate.opsForValue().get(CACHE_TYPE_LIST_KEY);if (!StringUtils.isEmpty(jsonTypeList)) {List<ShopType> typeList = JSONUtil.toList(jsonTypeList, ShopType.class);return Result.ok(typeList);}List<ShopType> typeList = this.query().orderByAsc("sort").list();if (!typeList.isEmpty()){stringRedisTemplate.opsForValue().set(CACHE_TYPE_LIST_KEY, JSONUtil.toJsonStr(typeList), CACHE_TYPE_LIST_TTL);}return Result.ok(typeList);}
}

对商户详情使用缓存:

@Service
public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Overridepublic Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中没有,从数据库查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);}return Result.ok(shop);}
}

缓存更新策略

在编辑商户信息时,将对应的缓存删除:

@Override
public Result update(Shop shop) {if (shop.getId() == null) {return Result.fail("商户id不能为空");}// 更新商户信息this.updateById(shop);// 删除缓存stringRedisTemplate.delete(CACHE_SHOP_KEY + shop.getId());return Result.ok();
}

缓存穿透

缓存穿透指如果请求的数据在缓存和数据库中都不存在,就不会生成缓存数据,每次请求都不会使用缓存,会对数据库造成压力。

可以通过缓存空对象的方式解决缓存穿透问题。

在查询商铺信息时缓存空对象:

@Override
public Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// Redis 中没有,从数据库查Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 缓存空对象到缓存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店铺不存在");}
}

在这里,缓存中的空对象用空字符串代替,并且将缓存存活时间设置为一个较短的值(比如说2分钟)。

在从缓存中查询到空对象时,返回商铺不存在:

@Override
public Result queryById(Long id) {// 先从 Redis 中查询String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果从缓存中查询到空对象,表示商铺不存在if ("".equals(jsonShop)) {return Result.fail("商铺不存在");}// ...
}

缓存击穿

缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。

常见的解决方案有两种:

  • 互斥锁
  • 逻辑过期

可以利用 Redis 做互斥锁来解决缓存击穿问题:

@Override
public Result queryById(Long id) {//        return queryWithCachePenetration(id);return queryWithCacheBreakdown(id);
}/*** 用 Redis 创建互斥锁** @param name 锁名称* @return 成功/失败*/
private boolean lock(String name) {Boolean result = stringRedisTemplate.opsForValue().setIfAbsent(name, "1", Duration.ofSeconds(10));return BooleanUtil.isTrue(result);
}/*** 删除 Redis 互斥锁** @param name 锁名称*/
private void unlock(String name) {stringRedisTemplate.delete(name);
}/*** 查询店铺信息-缓存击穿** @param id* @return*/
private Result queryWithCacheBreakdown(Long id) {// 先查询是否存在缓存String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (!StringUtils.isEmpty(jsonShop)) {Shop shop = JSONUtil.toBean(jsonShop, Shop.class);return Result.ok(shop);}// 如果从缓存中查询到空对象,表示商铺不存在if ("".equals(jsonShop)) {return Result.fail("商铺不存在");}// 缓存不存在,尝试获取锁,并创建缓存final String lockName = "lock:shop:" + id;try {if (!lock(lockName)){// 获取互斥锁失败,休眠一段时间后重试Thread.sleep(50);return queryWithCacheBreakdown(id);}// 获取互斥锁成功,创建缓存// 模拟长时间才能创建缓存Thread.sleep(100);Shop shop = this.getById(id);if (shop != null) {jsonShop = JSONUtil.toJsonStr(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, jsonShop, CACHE_SHOP_TTL);return Result.ok(shop);} else {// 缓存空对象到缓存中stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, "", CACHE_NULL_TTL);return Result.fail("店铺不存在");}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {// 释放锁unlock(lockName);}
}

下面是用逻辑过期解决缓存击穿问题的方式。

首先需要将热点数据的缓存提前写入 Redis(缓存预热):

public class ShopServiceImpl extends ServiceImpl<ShopMapper, Shop> implements IShopService {/*** 创建店铺缓存** @param id       店铺id* @param duration 缓存有效时长*/public void saveShopCache(Long id, Duration duration) {Shop shop = getById(id);RedisCache<Shop> redisCache = new RedisCache<>();redisCache.setExpire(LocalDateTime.now().plus(duration));redisCache.setData(shop);stringRedisTemplate.opsForValue().set(CACHE_SHOP_KEY + id, JSONUtil.toJsonStr(redisCache));}// ...
}
@SpringBootTest
class HmDianPingApplicationTests {@Autowiredprivate ShopServiceImpl shopService;@Testpublic void testSaveShopCache(){shopService.saveShopCache(1L, Duration.ofSeconds(1));}}
@Data
public class RedisCache<T> {private LocalDateTime expire; //逻辑过期时间private T data; // 数据
}

Redis 中的缓存信息包含两部分:过期时间和具体信息。大致如下:

{"data": {"area": "大关","openHours": "10:00-22:00","sold": 4215,// ...},"expire": 1708258021725
}

且其 TTL 是-1,也就是永不过期。

具体的缓存读取和重建逻辑:

/*** 用逻辑过期解决缓存击穿问题** @return*/
private Result queryWithLogicalExpiration(Long id) {//检查缓存是否存在String jsonShop = stringRedisTemplate.opsForValue().get(CACHE_SHOP_KEY + id);if (StringUtils.isEmpty(jsonShop)) {// 缓存不存在return Result.fail("店铺不存在");}// 缓存存在,检查是否过期RedisCache<Shop> redisCache = JSONUtil.toBean(jsonShop, new TypeReference<RedisCache<Shop>>() {}, true);if (redisCache.getExpire().isBefore(LocalDateTime.now())) {// 如果过期,尝试获取互斥锁final String LOCK_NAME = LOCK_SHOP_KEY + id;if (lock(LOCK_NAME)) {// 获取互斥锁后,单独启动线程更新缓存CACHE_UPDATE_ES.execute(() -> {try {// 模拟缓存重建的延迟Thread.sleep(200);saveShopCache(id, Duration.ofSeconds(1));} catch (Exception e) {throw new RuntimeException(e);} finally {unlock(LOCK_NAME);}});}}// 无论是否过期,返回缓存对象中的信息return Result.ok(redisCache.getData());
}

封装 Redis 缓存工具类

可以对对 Redis 缓存相关逻辑进行封装,可以避免在业务代码中重复编写相关逻辑。封装后分别对应以下方法:

  • 设置缓存数据(TTL)
  • 设置缓存数据(逻辑过期时间)
  • 从缓存获取数据(用空对象解决缓存穿透问题)
  • 从缓存获取数据(用互斥锁解决缓存击穿问题)
  • 从缓存获取数据(用逻辑过期解决缓存击穿问题)

工具类的完整代码可以参考这里。

本文的完整示例代码可以从这里获取。

参考资料

  • 黑马程序员Redis入门到实战教程

相关文章:

Redis 学习笔记 3:黑马点评

Redis 学习笔记 3&#xff1a;黑马点评 准备工作 需要先导入项目相关资源&#xff1a; 数据库文件 hmdp.sql后端代码 hm-dianping.zip包括前端代码的 Nginx 启动后端代码和 Nginx。 短信登录 发送验证码 PostMapping("code") public Result sendCode(RequestP…...

电脑恢复删除数据的原理和方法

在恢复数据的时候&#xff0c;很多人都会问&#xff0c;为什么删除的数据还能恢复&#xff1f;本篇和大家一起了解下硬盘上数据的存储方式&#xff0c;文件被删除的时候具体发生了什么&#xff0c;帮助大家理解数据恢复的基本原理。最后还会分享一个好用的数据恢复工具并附上图…...

SpringBoot和SpringCloud的区别,使用微服务的好处和缺点

SpringBoot是一个用于快速开发单个Spring应用程序的框架&#xff0c;通过提供默认配置和约定大于配置的方式&#xff0c;快速搭建基于Spring的应用。让程序员更专注于业务逻辑的编写&#xff0c;不需要过多关注配置细节。可以看成是一种快速搭建房子的工具包&#xff0c;不用从…...

32单片机基础:GPIO输出

目录 简介&#xff1a; GPIO输出的八种模式 STM32的GPIO工作方式 GPIO支持4种输入模式&#xff1a; GPIO支持4种输出模式&#xff1a; 浮空输入模式 上拉输入模式 下拉输入模式 模拟输入模式&#xff1a; 开漏输出模式&#xff1a;&#xff08;PMOS无效&#xff0c;就…...

【linux】查看openssl程序的安装情况

【linux】查看openssl程序的安装情况 1、查看安装包信息 $ rpm -qa |grep openssl 2、安装路径 $ rpm -ql openssl $ rpm -ql openssl-libs $ rpm -ql openssl-devel 3、相关文件和目录 /usr/bin/openssl /usr/include/openssl /usr/lib64/libssl.so.* /usr/lib64/libcrypto…...

高防服务器主要运用在哪些场景?

高防服务器主要是用来防御DDOS攻击的服务器&#xff0c;能够为客户提供安全维护&#xff0c;高防服务器能够帮助网站拒绝服务攻击&#xff0c;定时扫描网络主节点&#xff0c;进行查找可能会出现的安全漏洞的服务类型&#xff0c;高防服务器也会根据不同的IDC机房环境来提供硬防…...

Eureka:微服务中的服务注册与发现机制

引言 在微服务架构中&#xff0c;由于服务数量巨大并且各个服务的实例可能会频繁上下线&#xff0c;因此服务注册和发现机制至关重要。 那么&#xff0c;有什么工具或技术可以帮助我们解决这个问题呢&#xff1f; 答案就是Eureka。 一、Eureka简介 Eureka是Netflix公司开源的…...

python程序设计基础:字符串与正则表达式

第四章&#xff1a;字符串与正则表达式 4.1字符串 最早的字符串编码是美国标准信息交换码ASCII&#xff0c;仅对10个数字、26个大写英文字母、26个小写英文字母及一些其他符号进行了编码。ASCII码采用1个字节来对字符进行编码&#xff0c;最多只能表示256个符号。 随着信息技…...

华为配置WDS手拉手业务示例

配置WDS手拉手业务示例 组网图形 图1 配置WDS手拉手业务示例组网图 业务需求组网需求数据规划配置思路配置注意事项操作步骤配置文件 业务需求 企业用户通过WLAN接入网络&#xff0c;以满足移动办公的最基本需求。但企业考虑到AP通过有线部署的成本较高&#xff0c;所以通过建立…...

Apache celeborn 安装及使用教程

1.下载安装包 https://celeborn.apache.org/download/ 测0.4.0时出现https://github.com/apache/incubator-celeborn/issues/835 2.解压 tar -xzvf apache-celeborn-0.3.2-incubating-bin.tgz 3.修改配置文件 cp celeborn-env.sh.template celeborn-env.shcp log4j2.xml.…...

数据保护:如何有效应对.BecSec-P-XXXXXXXX勒索病毒的威胁

导言&#xff1a; 随着网络安全威胁的不断增加&#xff0c;勒索软件成为了网络犯罪分子的一种常见手段之一。.BecSec-P-XXXXXXXX勒索病毒&#xff08;简称.BecSec勒索病毒&#xff09;作为其中之一&#xff0c;对用户的数据安全构成了严重威胁。本文91数据恢复将介绍.BecSec勒…...

流畅的Python(十二)-继承的优缺点

一、核心要义 1. 子类化内置类型的缺点 2.多重继承和方法解析顺序 二、代码示例 1. 子类化内置类型的缺点 #!/usr/bin/env python # -*- coding: utf-8 -*- # Time : 2024/2/24 7:29 # Author : Maple # File : 01-子类化内置类型的问题.py # Software: PyCharm fr…...

机器学习基础(三)监督学习的进阶探索

导语&#xff1a;上一节我们深入地探讨监督学习和非监督学习的知识&#xff0c;重点关注它们的理论基础、常用算法及实际应用场景&#xff0c;详情可见&#xff1a; 机器学习基础&#xff08;二&#xff09;监督与非监督学习-CSDN博客文章浏览阅读769次&#xff0c;点赞15次&a…...

avidemux-一个免费的视频编辑器,用于剪切、过滤和编码项目

avidemux-一个免费的视频编辑器&#xff0c;用于剪切、过滤和编码项目 avidemux-一个免费的视频编辑器&#xff0c;用于剪切、过滤和编码项目avidemux下载avidemux源代码参考资料 avidemux-一个免费的视频编辑器&#xff0c;用于剪切、过滤和编码项目 avidemux下载 avidemux …...

RisingWave最佳实践-利用Dynamic filters 和 Temporal filters 实现监控告警

心得的体会 刚过了年刚开工&#xff0c;闲暇之余调研了分布式SQL流处理数据库–RisingWave&#xff0c;本人是Flink&#xff08;包括FlinkSQL和Flink DataStream API&#xff09;的资深用户&#xff0c;但接触到RisingWave令我眼前一亮&#xff0c;并且拿我们生产上的监控告警…...

【Qt学习】QRadioButton 的介绍与使用(性别选择、模拟点餐)

文章目录 介绍实例使用实例1&#xff08;性别选择 - 单选 隐藏&#xff09;实例2&#xff08;模拟点餐&#xff0c;多组单选&#xff09; 相关资源文件 介绍 这里简单对QRadioButton类 进行介绍&#xff1a; QRadioButton 继承自 QAbstractButton &#xff0c;用于创建单选按…...

基于java springboot的图书管理系统设计和实现

基于java springboot的图书管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于Java技术领域 作者主页 央顺技术团队 Java毕设项目精品实战案例《1000套》 欢迎点赞 收藏 ⭐留言 文末获取源码联…...

自定义类型:联合和枚举

目录 1. 联合体 1.1 联合体类型的声明及特点 1.2 相同成员的结构体和联合体对比 1.3 联合体大小的计算 1.4 联合体的应用举例 2. 枚举类型 2.1 枚举类型的声明 2.2 枚举类型的优点 1. 联合体 1.1 联合体类型的声明及特点 像结构体一样&#xff0c;联合体也是由一个或…...

每日一学—由面试题“Redis 是否为单线程”引发的思考

文章目录 &#x1f4cb; 前言&#x1f330; 举个例子&#x1f3af; 什么是 Redis&#xff08;知识点补充&#xff09;&#x1f3af; Redis 中的多线程&#x1f3af; I/O 多线程&#x1f3af; Redis 中的多进程&#x1f4dd; 结论&#x1f3af;书籍推荐&#x1f525;参与方式 &a…...

chatGPT PLUS 绑卡提示信用卡被拒的解决办法

chatGPT PLUS 绑卡提示信用卡被拒的解决办法 一、 ChatGPT Plus介绍 作为人工智能领域的一项重要革新&#xff0c;ChatGPT Plus的上线引起了众多用户的关注&#xff0c;其背后的OpenAI表现出傲娇的态度&#xff0c;被誉为下一个GTP 4.0。总的来说&#xff0c;ChatGPT Plus的火…...

opencv鼠标操作与响应

//鼠标事件 Point sp(-1, -1); Point ep(-1, -1); Mat temp; static void on_draw(int event, int x, int y, int flags, void *userdata) {Mat image *((Mat*)userdata);if (event EVENT_LBUTTONDOWN) {sp.x x;sp.y y;std::cout << "start point:"<<…...

vue里echarts的使用:画饼图和面积折线图

vue里echarts的使用,我们要先安装echarts,然后在main.js里引入: //命令安装echarts npm i echarts//main.js里引入挂载到原型上 import echarts from echarts Vue.prototype.$echarts = echarts最终我们实现的效果如下: 头部标题这里我们封装了一个全局公共组件common-he…...

个人建站前端篇(六)插件unplugin-auto-import的使用

vue3日常项目中定义变量需要引入ref,reactive等等比较麻烦&#xff0c;可以通过unplugin-auto-import给我们自动引入 * unplugin-auto-import 解决了vue3-hook、vue-router、useVue等多个插件的自动导入&#xff0c;也支持自定义插件的自动导入&#xff0c;是一个功能强大的typ…...

【Python】 剪辑法欠采样 CNN压缩近邻法欠采样

借鉴&#xff1a;关于K近邻&#xff08;KNN&#xff09;&#xff0c;看这一篇就够了&#xff01;算法原理&#xff0c;kd树&#xff0c;球树&#xff0c;KNN解决样本不平衡&#xff0c;剪辑法&#xff0c;压缩近邻法 - 知乎 但是不要看他里面的代码&#xff0c;因为作者把代码…...

springmvc+ssm+springboot房屋中介服务平台的设计与实现 i174z

本论文拟采用计算机技术设计并开发的房屋中介服务平台&#xff0c;主要是为用户提供服务。使得用户可以在系统上查看房屋出租、房屋出售、房屋求购、房屋求租&#xff0c;管理员对信息进行统一管理&#xff0c;与此同时可以筛选出符合的信息&#xff0c;给笔者提供更符合实际的…...

挑战30天学完Python:Day19 文件处理

&#x1f4d8; Day 19 &#x1f389; 本系列为Python基础学习&#xff0c;原稿来源于 30-Days-Of-Python 英文项目&#xff0c;大奇主要是对其本地化翻译、逐条验证和补充&#xff0c;想通过30天完成正儿八经的系统化实践。此系列适合零基础同学&#xff0c;或仅了解Python一点…...

Spring Boot application.properties和application.yml文件的配置

在Spring Boot中&#xff0c;application.properties 和 application.yml 文件用于配置应用程序的各个方面&#xff0c;如服务器端口、数据库连接、日志级别等。这两个文件是Spring Boot的配置文件&#xff0c;位于 src/main/resources 目录下。 application.properties 示例 …...

Unity单元测试

Unity单元测试是一个专门用于嵌入式单元测试的库, 现在简单讲下移植以及代码结构. 源码地址: GitHub - ThrowTheSwitch/Unity: Simple Unit Testing for C 1.我们只需要移植三个文件即可: unity.c, unity.h, unity_internals.h 2.然后添加需要测试的函数. 3.在main.c中添加…...

Spring Bean 的生命周期了解么?

Spring Bean 的生命周期基本流程 一个Spring的Bean从出生到销毁的全过程就是他的整个生命周期, 整个生命周期可以大致分为3个大的阶段 : 创建 使用 销毁 还可以分为5个小步骤 : 实例化(Bean的创建) , 初始化赋值, 注册Destruction回调 , Bean的正常使用 以及 Bean的销毁 …...

.ryabina勒索病毒数据怎么处理|数据解密恢复

导言&#xff1a; 随着网络安全威胁的不断增加&#xff0c;勒索软件已成为严重的威胁之一&#xff0c;.ryabina勒索病毒是其中之一。本文将介绍.ryabina勒索病毒的特点、数据恢复方法和预防措施&#xff0c;以帮助用户更好地应对这一威胁。当面对被勒索病毒攻击导致的数据文件…...