Redis学习Day3——项目工程开发`

扩展阅读推荐:

黑马程序员Redis入门到实战教程_哔哩哔哩_bilibili

使用git命令行将本地仓库代码上传到gitee/github远程仓库-CSDN博客

一、项目介绍及其初始化

        学习Redis的过程,我们还将遇到各种实际问题,例如缓存击穿、雪崩、热Key等问题,只有在实际的项目实践中解决这些问题,才能更好的掌握和理解Redis的企业开发思维。

        以下是本次【黑马点评】项目的主要内容:

本项目的功能概述
本项目的搭建结构
本项目的搭建结构

1.1 数据库连接配置

在yml配置文件中,配置好自己的数据库连接信息

数据库表结构

1.2 工程启动演示

想要成功启动该项目,需要以下步骤:

1. 打开VM虚拟机,激活Linux系统中事先配置好的Redis数据库

2. 启动nignx服务器(注意nignx服务器必须放在无中文的目录下)

3. 启动后端程序(观察端口,访问初始工程)

4. 访问请求地址,验证工程启动正确http://localhost:8081/shop-type/list

前端网页端口:8080 
后端数据端口:8081

二、短信登录功能实现

2.1 基于传统Session实现的短信登录及其校验

2.1.1 基于Session登录校验的流程设计

2.1.2 实现短信验证码发送功能

请求接口/user/code
请求类型post
请求参数phone
返回值
    /*** 发送手机验证码*/@PostMapping("/code")public Result sendCode(@RequestParam("phone") String phone, HttpSession session) {log.info("发送验证码, 手机号:{}", phone);return userService.sendCode(phone, session);}/*** 发送验证码* @param phone* @param session* @return*/@Overridepublic Result sendCode(String phone, HttpSession session) {// 1. 校验手机号码if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号码格式错误!");}// 2. 生成验证码String code = RandomUtil.randomNumbers(6);// 3. 将验证码保存到Session中session.setAttribute("code", code);//TODO 4. 调用阿里云 将短信信息发送到指定手机log.info("发送短信验证码成功,验证码:{}", code);return Result.ok();}

2.1.3 实现登录、注册功能

请求接口/user/login
请求类型post
请求参数LoginForm---> phone,code,[password]
返回值
    /*** 登录功能* @param loginForm 登录参数,包含手机号、验证码;或者手机号、密码*/@PostMapping("/login")public Result login(@RequestBody LoginFormDTO loginForm, HttpSession session){log.info("用户登录, 参数:{}", loginForm);return userService.login(loginForm, session);}/*** 登录功能* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1. 校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号码格式错误!");}// 2. 校验验证码Object cacheCode = session.getAttribute("code");String code = loginForm.getCode();if(cacheCode==null || !cacheCode.toString().equals(code)){return Result.fail("验证码错误!");}// 3. 根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();//    if 0 :创建新用户,保存数据库,将用户信息存储到Session//if(user == null){user = createUserWithPhone(phone);}//else: 登录成功,将用户信息存储到Sessionsession.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));return Result.ok();}/*** 根据手机号创建用户* @param phone* @return*/private User createUserWithPhone(String phone) {//创建用户User user = new User();user.setPhone(phone);user.setNickName(USER_NICK_NAME_PREFIX + RandomUtil.randomString(10));//保存用户save(user);// 返回return user;}

2.1.4 实现登录状态校验拦截器

        由于日后项目功能会越来越多,需要登录才能进行访问的界面也会越来越多,我们必须想办法将登录状态校验抽离出来形成一个前置校验的条件,再放行到后续逻辑。

1. 封装TreadLocal工具类

将用户信息保存到 TreadLocal中 并封装TreadLocal工具类用于 保存用户、获取用户、移除用户

在 urils / UserHolder 

/*** TreadLocal工具类*/
public class UserHolder {private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();// 保存用户public static void saveUser(UserDTO user){tl.set(user);}// 获取ThreadLocal中的用户public static UserDTO getUser(){return tl.get();}// 清空ThreadLocalpublic static void removeUser(){tl.remove();}
}
2. 创建登录拦截器

在 urils / LoginInterceptor 

public class LoginInterceptor implements HandlerInterceptor {/*** 前置拦截器*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1. 获取sessionHttpSession session = request.getSession();// 2. 获取session中的用户Object user = session.getAttribute("user");// 3. 判断用户是否存在if(user == null){response.setStatus(401);return false;}// 4. 如果存在,用户信息保存到 ThreadLocal 并放行UserHolder.saveUser((UserDTO) user);return true;}/*** 后置拦截器(移除用户)*/@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}
3. 添加配置,生效拦截器,并配置放行路径

在 config/ MvcConfig

@Configuration
public class MvcConfig implements WebMvcConfigurer {/*** 添加拦截器* @param registry*/public void addInterceptors(InterceptorRegistry registry) {registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/host","/shop/**","/shop-type/**","/voucher/**");}
}

2.1.5 实现获取用户请求

前端点击我的,发送请求到后端,获取当前登录状态,方能进入个人中心

    /*** 获取当前登录的用户* @return*/@GetMapping("/me")public Result me(){UserDTO user = UserHolder.getUser();return Result.ok(user);}

2.1.6 (附加)用户信息脱敏处理

为防止出现以下这种情况(将用户隐私信息暴露过多),我们采用UserDTO对象对用户信息脱敏处理:

@Data
public class UserDTO {private Long id;private String nickName;private String icon;
}

并借助拷贝工具 进行对象拷贝

session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));

2.2 传统Session在集群环境下的弊端

Session共享问题

多台Tomcat并不共享session存储空间,当请求切换到不同tomcat服务时导致数据丢失的问题。

解决策略

1. 让Session可以共享

Tomcat提供了Session拷贝功能,但是这会增加服务器的额外内存开销,并且带来数据一致性问题

2. 【推荐】使用Redis进行替代

数据共享、内存存储(快)、key-value结构

2.3 基于Redis实现短信登录功能

2.3.1 基于Redis实现短信登录流程设计

发送短信验证码逻辑
校验登录状态逻辑

        对于验证码,使用 手机号码作为KEY,确保了正确的手机对应着正确的短信验证码。

        对于用户信息唯一标识使用 UUID生成的Token作为 KEY,而不使用手机号码,从而提高了用户数据安全性。

2.3.2 修改发送短信验证码功能

只需要在Session的基础上,将第三步保存到Redis中

格式:

keyvalueTTL
login:code:[手机号码][验证码]120S
//        // 3. 将验证码保存到Session中
//        session.setAttribute("code", code);// 3. 将验证码保存到Redis中stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone,code,LOGIN_CODE_TTL, TimeUnit.MINUTES);

2.3.3 修改登录、注册功能

1. 手机号校验

2. 从Redis中取出验证码进行校验

3.查询用户信息

4. 将用户信息存储到Redis ---> 需要以Hash结构进行存储 ----> 需要将user对象转成 Map对象

5. 将token返回给客户端 ,充当Session的标识作用

/*** 登录功能* @param loginForm* @param session* @return*/@Overridepublic Result login(LoginFormDTO loginForm, HttpSession session) {// 1. 校验手机号String phone = loginForm.getPhone();if(RegexUtils.isPhoneInvalid(phone)){return Result.fail("手机号码格式错误!");}// 2. 校验验证码 REDIS
//        Object cacheCode = session.getAttribute("code");// 2.1 从Redis中获取验证码String redisCode = stringRedisTemplate.opsForValue().get(LOGIN_CODE_KEY + phone);// 2.2 校验验证码String code = loginForm.getCode();if(redisCode==null || !redisCode.equals(code)){return Result.fail("验证码错误!");}// 3. 根据手机号查询用户 select * from tb_user where phone = ?User user = query().eq("phone", phone).one();//    if 0 :创建新用户,保存数据库,将用户信息存储到Sessionif(user == null){user = createUserWithPhone(phone);}
//        //else: 登录成功,将用户信息存储到Session
//        session.setAttribute("user", BeanUtil.copyProperties(user, UserDTO.class));// 4. 将用户信息存储到Redis中// 1. 随机生成token,作为登录令牌 ---> UUID导入工具包中的方法,不要导入java自带的String token = UUID.randomUUID().toString(true);// 2. 以hash结构进行存储UserDTO userDTO = BeanUtil.copyProperties(user,UserDTO.class);//TODO 这里报错了,因为UserDTO中有个id属性,不是字符串,在Redis序列化下报错
//        Map<String,Object> userMap = BeanUtil.beanToMap(userDTO);Map<String,Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),CopyOptions.create().setIgnoreNullValue(true).setFieldValueEditor((fieldName,fieldValue)-> fieldValue.toString()));// 3. 存储到Redis中stringRedisTemplate.opsForHash().putAll(LOGIN_USER_KEY + token,userMap);// 给token设置有效期// 超过30分钟不访问任何界面就会剔除,所以还需要设置在访问过程中不断更新token的有效期// 实现方式: 在登录拦截器中进行处理stringRedisTemplate.expire(LOGIN_USER_KEY + token, LOGIN_USER_TTL, TimeUnit.MINUTES);// 5. 返回token到客户端,客户端保存到浏览器中return Result.ok(token);}

2.3.4 添加刷新Token拦截器逻辑 (只做判断,不做拦截)

        首先,由于需要在自定义的拦截器中使用StringRedisTemplate对象,由于不是交由spring管理的,所以我们需要自己写构造函数进行导入。同时在MvcConfig中直接交给Spring管理

        其次,这里选择了新建一个专门负责刷新Token的“拦截器”,只做判断不做拦截。确保请求在经过登录校验拦截器之前,会统一先被该“拦截器”获取,并对Token进行判断,如果没有Token,则会被接下来的登录拦截器进行拦截

package com.hmdp.config;import com.hmdp.Interceptor.LoginInterceptor;
import com.hmdp.Interceptor.RefreshTokenInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;import javax.annotation.Resource;@Configuration
public class MvcConfig implements WebMvcConfigurer {@Resourceprivate StringRedisTemplate stringRedisTemplate;/*** 添加拦截器* @param registry*/public void addInterceptors(InterceptorRegistry registry) {// 刷新token拦截器 全部拦截 只做判断registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate)).addPathPatterns("/**");registry.addInterceptor(new LoginInterceptor()).excludePathPatterns("/user/code","/user/login","/blog/host","/shop/**","/shop-type/**","/voucher/**");}
}
package com.hmdp.Interceptor;import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.hmdp.dto.UserDTO;
import com.hmdp.utils.UserHolder;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.servlet.HandlerInterceptor;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import java.util.Map;
import java.util.concurrent.TimeUnit;import static com.hmdp.utils.RedisConstants.LOGIN_USER_KEY;
import static com.hmdp.utils.RedisConstants.LOGIN_USER_TTL;/*** Token缓存刷新拦截器 只会放行不会拦截*/
public class RefreshTokenInterceptor implements HandlerInterceptor {private StringRedisTemplate stringRedisTemplate;/*** 手动创建的对象,需要手动注入,所以需要构造方法* @param stringRedisTemplate*/public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 1.获取请求头中的tokenString token = request.getHeader("authorization");if (StrUtil.isBlank(token)) {return true;}// 2.基于TOKEN获取redis中的用户String key  = LOGIN_USER_KEY + token;Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);// 3.判断用户是否存在if (userMap.isEmpty()) {return true;}// 5.将查询到的hash数据转为UserDTOUserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);// 6.存在,保存用户信息到 ThreadLocalUserHolder.saveUser(userDTO);// 7.刷新token有效期stringRedisTemplate.expire(key, LOGIN_USER_TTL, TimeUnit.MINUTES);// 8.放行return true;}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 移除用户UserHolder.removeUser();}
}

2.3.5 (补充)退出登录功能实现

        前端点击退出登录时发送logout请求,我们只需要将TreadLocal的用户对象给清除掉,这样一来前端的请求就获取不到用户信息,强制被拦截到登录界面了

    /*** 登出功能* @return 无*/@PostMapping("/logout")public Result logout(){// 清除用户登录状态UserHolder.removeUser();return Result.ok();}

2.3.5 【故障排查】关于一登陆后前端立马闪退回登录界面的问题

        在跟着视频练习的过程中,在所以代码开发完成后,我发现了每当自己点击登录校验成功后,前端又会重复的闪退回登录界面。对此我进行了以下排除手段:

1. 检查Redis是否 将 短信验证码及其用户token信息保存成功,有则证明这两个大环节没有问题

2. 猜测是拦截器问题:

         一开始,我们模仿老师将刷新Token的逻辑一并写到了登录校验拦截器上。但是!登录校验拦截器在开发的过程中,有一些需要Token【或是说需要校验TreadLocal对象】的接口并没有被拦截器拦截下来,导致前端认为该用户操作并未携带Token【没被存储到TreadLocal中】,从而误判为未登录状态,从而剔除该用户,强制跳转到登录界面。

        为此,秉承单一职责原则,对于Token,我们需要新建一个将所有界面都“拦截”的拦截器,这样子可以保证在进入后续拦截器的请求,不会再有被误判的情况出现。

    而且,也确保了不管用户进行了什么操作,Token都能刷新时长

(该故障经过拆分拦截器已正常解决,但是题主并没有深刻去揪到底是拿一些请求被误判了,这个故障原因也是我分析,如有高见请分享一下)

2.4 (TODO) 基于阿里云完善验证码功能

TODO

三、商户查询缓存系列功能实现

3.1 缓存的理解

        我们的程序如果想要用户有一个比较良好的使用体验,在请求数据速度上必然要有所突出。因此我们一般会在项目中运用缓存策略,从而提高我们程序的响应速度。与此同时,在添加缓存策略之后,数据一致性、缓存击穿、雪崩、热Key、维护成本提高等相继出现。如何平衡好这种关系,成为了我们学习Redis的重要所在。

3.2 查询商户店铺---添加Redis缓存

3.2.1 添加缓存逻辑理解

        由于Redis访问快的优点,我们在客户端与数据库之间添加一层 Redis数据层。用户发送的请求首先打到Redis进行查询,

- 如果在Redis上查询命中,则直接返回给用户,从而减轻了底层数据库服务器数据压力

- 如果没能命中,则请求打到数据库进行查询,查询未命中则说明此次请求为 错误请求

- 数据库命中的话,就将数据写入Redis中 ,接着返回给用户。

缓存作用模型

3.2.2 添加缓存逻辑实现

    /*** 根据id查询商铺信息* @param id 商铺id* @return 商铺详情数据*/@GetMapping("/{id}")public Result queryShopById(@PathVariable("id") Long id) {return shopService.queryById(id);}/*** 根据id查询店铺(添加Redis缓存版)* @param id* @return*/@Overridepublic Result queryById(Long id) {//1. 根据id到Redis中查询用户信息//2. Redis命中 ----------------> 返回商户信息 -------> 结束//3. Redis未命中 查询数据库//4. 查询数据库未命中 ----------------> 返回错误信息 ------> 结束//5. 数据库命中 ---------------> 将数据写入Redis ------> 返回商户信息 -------> 结束String stopJson =  stringRedisTemplate.opsForValue().get(RedisConstants.CACHE_SHOP_KEY + id);if(StrUtil.isNotBlank(stopJson)){// 存在直接返回Shop shop = JSONUtil.toBean(stopJson, Shop.class); //将JSON字符串转换为对象return Result.ok(shop);}
//        query().eq("shop_id",id);// 查询数据库Shop shop = getById(id);if(shop == null){Result.fail("店铺不存在");}stringRedisTemplate.opsForValue().set(RedisConstants.CACHE_SHOP_KEY + id,JSONUtil.toJsonStr(shop),RedisConstants.CACHE_SHOP_TTL,TimeUnit.MINUTES);return Result.ok(shop);}

3.2.3 缓存功能效果展示

第一次查询商户店铺,未缓存Redis,后台查询数据库

第二次再次查询商户店铺,已缓存Redis,后台没有查询店铺的数据库语句

3.3(课后作业)查询商户分类列表---添加Redis缓存

3.3.1 使用String存储类型实现

    /*** 查询店铺类型 (添加Redis版)* @return*/@GetMapping("list")public Result queryTypeList() {
//        List<ShopType> typeList = typeService
//                .query().orderByAsc("sort").list();return typeService.queryTypeList();}/*** 查询店铺类型列表(添加Redis版)* String 实现版* @return*/@Overridepublic Result queryTypeList() {// 1. 在Redis中查询店铺类型列表// 2. Redis命中 ------> 直接返回店铺类型数据 -------> 结束// 3. Redis未命中, 查询数据库// 4. 数据库未命中 -------> 返回报错信息 --------> 结束// 5. 数据库命中,-------> 将数据存入Redis --------> 返回店铺类型数据 --------> 结束String Key = RedisConstants.CACHE_SHOP_TYPE_KEY;String shopTypeJSON = stringRedisTemplate.opsForValue().get(Key);// 将字符串转换为对象List<ShopType> shopTypeList = null;if(StrUtil.isNotBlank(shopTypeJSON)){shopTypeList = JSONUtil.toList(shopTypeJSON, ShopType.class);return Result.ok(shopTypeList); // 返回店铺类型数据}// 查询数据库shopTypeList = query().orderByAsc("sort").list();// 将对象转换为字符串shopTypeJSON = JSONUtil.toJsonStr(shopTypeList);// 将数据存入RedisstringRedisTemplate.opsForValue().set(Key, shopTypeJSON);return Result.ok(shopTypeList);}

3.3.2 使用List存储类型实现

/*** 查询店铺类型列表(添加Redis版)* List 实现版* @return*/@Overridepublic Result queryTypeList() {// 1. 在Redis中查询店铺类型列表// 2. Redis命中 ------> 直接返回店铺类型数据 -------> 结束// 3. Redis未命中, 查询数据库// 4. 数据库未命中 -------> 返回报错信息 --------> 结束// 5. 数据库命中,-------> 将数据存入Redis --------> 返回店铺类型数据 --------> 结束String Key = RedisConstants.CACHE_SHOP_TYPE_KEY;// 获取列表中所有元素(字符串格式)List<String> shopTypeJSON = stringRedisTemplate.opsForList().range(Key, 0, -1);   // 获取列表中所有元素if(shopTypeJSON != null && !shopTypeJSON.isEmpty()){// Redis中存在数据,需要将所有的Value转换成 ShopType对象// 将字符串转换为对象List<ShopType> shopTypeList = new ArrayList<>();for(String str : shopTypeJSON){shopTypeList.add(JSONUtil.toBean(str, ShopType.class));}return Result.ok(shopTypeList); // 返回店铺类型数据}// 查询数据库List<ShopType> shopTypeList = query().orderByAsc("sort").list();if(shopTypeList == null || shopTypeList.isEmpty()){return Result.fail("店铺类型不存在");}// 将对象转换为字符串(每一项都是)for (ShopType shopType : shopTypeList) {stringRedisTemplate.opsForList().rightPushAll(Key, JSONUtil.toJsonStr(shopType));}// 设置过期时间stringRedisTemplate.expire(Key, RedisConstants.CACHE_SHOP_TTL, TimeUnit.MINUTES);return Result.ok(shopTypeList);}

3.3.3 缓存功能效果展示

分类列表数据成功存储到Redis中
查询效率从1秒提升到45毫秒

3.4 (知识点)缓存更新策略最佳实践

3.4.1 数据一致性问题

        前面引入Redis时已经讲过了,缓存的使用可以降低后端负载、降低响应时间、提高读写效率。但也会带来系列问题,数据一致性便是其中最为常见的问题之一。

        数据一致性问题的根本原因就是缓存和数据库中的数据不同步,因此,我们需要提出一套良好的缓存更新方案,尽可能的使得缓存和数据库中的数据进行及时同步。

3.4.2 (TODO)缓存更新策略的最佳实践

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

IGNAV_NHC分析

extern int nhc(insstate_t *ins,const insopt_t *opt,const imud_t *imu)函数名 insstate_t* ins IO ins state insopt_t* opt I ins options imud_t* imu I imu measurement data return : 1 (ok) or 0 (fail) 用NHC进行约束&#xff0c;其实用NHC做量测去…

从大脑图谱/ROI中提取BOLD信号

动机 在功能连接&#xff08;Functional Connectivity&#xff0c;FC&#xff09;构建过程中&#xff0c;由于FC中元素数目是节点数目的平方关系&#xff0c;所以在计算FC之前进行数据降维是一个常见的选择。 一般会将体素级/顶点级BOLD信号&#xff08;在2mm的图像分辨率下大脑…

Android libui新加接口,编译报错:error: Please update ABI references

1.背景信息 由于项目需要,要合入google的bug fix:https://cs.android.com/android/_/android/platform/frameworks/native/+/2c1782c6f986debe5ec89d5cdd3a3f08b08d5683 查看google的修改发现,对Transform.h 增加了一个方法:android::ui::Transform::det。合入修改之后,我…

NXP,S32K1XX汽车通用微控制器开发笔记

文章目录 1. 概述2. 开发环境配置2.1 S32 Design Studio2.2 安装SDK2.3 新建demo工程2.4 字体配置2.5 按需求修改demo2.5.1 修改pin脚定义2.5.2 增加串口打印功能2.6 编译代码2.7 debuger 配置参考1. 概述 S32K1系列32位微控制器(MCU)提供基于Arm Cortex-M的MCU,以及基本的…

pycharm中函数或方法的跳转以及返回

跳转 跳转很方便&#xff0c;ctrl 函数名即可。 跳转返回 有自带的回退按钮&#xff0c;找到视图->外观->工具栏&#xff0c;选中工具栏&#xff0c;这样就能出现箭头按钮&#xff0c;左箭头就是回退&#xff0c;右箭头前进。 快捷按钮可以为&#xff1a; 回退&…

Docker高级管理之compose容器编排与私有仓库的部署

Compose容器编排 Compose&#xff1a;容器的编排技术&#xff08;可以管理多个容器&#xff09;&#xff0c;移植性、迁移性更强 查看使用的Compose的版本&#xff1a;docker-compose -v 首先创建一个编排文件 文件内容 compose文件格式&#xff1a; 缩进&#xff08;严格意…

基于SpringBoot+Vue的房屋租赁管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于JavaSpringBootVueMySQL的房屋租赁…

【Linux C | 终端设备】Linux下 tty、ttyS*、ttyAMA*、console 的区别,以及系统输出重定向(附带代码)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-09-11 …

RickdiculouslyEasy-CTF-综合靶场

步骤一&#xff1a;利用Goby搜索靶机地址 步骤二&#xff1a;访问靶机地址 步骤二&#xff1a;扫描端口 nmap 172.16.1.7 -p 1-65535 步骤三&#xff1a; 扫描目录 dirsearch -u http://172.16.1.7/ 第一个flag&#xff1a;命令&#xff1a;nmap -A -v -T4 172.16.1.7 -p 1-6…

RK3576芯片在智能家居里中型智慧屏产品的应用方案分析

智能家居在近年来得到了快速发展&#xff0c;AI技术不断发展&#xff0c;人机交互十分成熟&#xff0c;各种家电也都迎来了智能化浪潮&#xff0c;智能家居为人们提供了优秀的产品体验&#xff0c;受到主流消费者的青睐&#xff0c;智能家居里的中型智慧屏产品也随之兴起。 瑞芯…

2024最新盘点,主流生产报工软件有哪些?

本文将盘点知名的生产报工软件&#xff0c;为企业选型提供参考&#xff01; 各位生产经理有没有碰到过这种情况&#xff0c;产品生产从工单-报工-质检-入库的过程中不能实时知道任务进度&#xff0c;生产日报也不清晰&#xff0c;老是被客户催&#xff0c;上头领导不满意&…

Netty权威指南:Netty总结-编解码与序列化

第四章 TCP粘包/拆包问题 4.1 TCP 粘包/拆包 TCP是流协议&#xff0c;也就是没有界限的的一串数据&#xff0c;底层并不知道上层业务数据的具体含义&#xff0c;也就是说一个完整的包可能会被拆分成多个包进行发送&#xff0c;也可能把几个小包封装成一个大的数据包发送。这就…

自注意力机制 SANS(论文复现)

自注意力机制 SANS&#xff08;论文复现&#xff09; 本文所涉及所有资源均在传知代码平台可获取 前言 在NLP模型领域中&#xff0c;seq2seq是一种常见的模型结构&#xff08;序列到序列&#xff09;&#xff0c;其于 2013年、2014 年被多位学者共同提出&#xff0c;在机器翻译…

《Learning to Prompt for Vision-Language Models》CoOp论文中文校对版

系列论文研读目录 文章目录 系列论文研读目录摘要1 简介2 相关工作2.1视觉语言模型2.2 NLP中的提示学习 3 方法论3.1视觉语言预训练3.2上下文优化3.3讨论 4 实验4.1少数学习4.2领域泛化4.3进一步分析 5 结论、局限性和未来的工作 摘要 像CLIP这样的大型预训练视觉语言模型在学…

天通报警呼叫柱:为边防哨所筑起坚固的通信堡垒

一、背景 边防哨所是国家安全的重要防线&#xff0c;肩负着守护边境安全、维护国家主权和领土完整的神圣使命。由于边防哨所通常位于地理位置偏远、环境恶劣的地区&#xff0c;通信问题成为影响边防工作的重要因素&#xff0c;给边防官兵的日常工作和应急响应带来了不小的挑战…

Vue 3 watchEffect:如何用 onInvalidate 优化副作用处理

在 Vue3 中&#xff0c;watchEffect 是一个用于在响应式数据变化时自动重新执行的函数。它在创建响应式副作用时特别有用&#xff0c;比如在某些数据变化时更新 DOM、发起网络请求或处理复杂的逻辑。 watchEffect 的 onInvalidate 是一个非常重要的功能&#xff0c;用于处理副…

blender我的对称模型好像中点被我不小心移动了 我现在如果雕刻 两边修改的地方不是对称的 我该怎么办

blender我的对称模型好像中点被我不小心移动了 我现在如果雕刻 两边修改的地方不是对称的 我该怎么办 首先请调整好模型确保左右前后对其相应的xyz轴 之后CtrlA应用变换 确保这些都归0且模型和xyz轴对应 如果在Blender中模型的中点&#xff08;对称轴&#xff09;不小心被移动了…

使用豆包MarsCode 编写 Node.js 全栈应用开发实践

以下是「豆包MarsCode 体验官」优秀文章&#xff0c;作者狼叔。 欢迎更多用户使用豆包MarsCode 并分享您的产品使用心得及反馈、创意项目开发等&#xff0c;【有奖征集&#xff5c;人人都是豆包MarsCode 测评官&#xff01;】活动正在火热进行中&#xff0c;欢迎大家投稿参加&a…

6-Python基础编程之数据类型操作——数值、布尔和字符串

Python基础编程之数据类型操作——数值、布尔和字符串 数值整数浮点数复数常用操作 布尔类型字符串字符串拼接字符串切片常用函数查找计算转换操作填充压缩分割拼接判定操作 数值 表现形式&#xff1a;整数&#xff08;int&#xff09;、浮点数&#xff08;float&#xff09;、…

校园管理|基于springboot+vue的校园管理系统(源码+数据库+文档)

校园管理|校园管理系统 目录 基于springbootvue的校园管理系统 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&#xff0c…