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 【故障排查】关于一登陆后前端立马闪退回登录界面的问题

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

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

2. 猜测是拦截器问题:

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

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

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

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

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

TODO

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

3.1 缓存的理解

我们的程序如果想要用户有一个比较良好的使用体验,在请求数据速度上必然要有所突出。因此我们一般会在项目中运用缓存策略,从而提高我们程序的响应速度。与此同时,添加缓存策略,数据一致性、缓存击穿问题、雪崩问题、维护成本提高等相继出翔。如何平衡好这种关系,成为了我们学习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 + UUID.randomUUID().toString(true);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(TODO) 使用List存储类型实现

3.3.3 缓存功能效果展示

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

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

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

相关文章

Ubuntu20.04+ros-noetic配置Cartographer

一、概述 因为要配置激光SLAM&#xff0c;Cartographer属于激光雷达SLAM 中比较经典的一款&#xff0c;在学习之前先将其在Ubuntu20.04首先配置出来并成功运行demo。 二、具体操作 &#xff08;一&#xff09;概述 使用平台是Windows的wsl2上的Ubuntu20.04子系统&#xff0c;…

如何看待IBM中国研发部裁员?

如何看待IBM中国研发部裁员&#xff1f;近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…

Java+vue的医药进出口交易系统(源码+数据库+文档)

外贸系统|医药进出口交易系统 目录 基于Javavue的服装定制系统 一、前言 二、系统设计 三、系统功能设计 仓储部门功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设…

中国书法——孙溟㠭浅析碑帖《三希堂法帖》

孙溟㠭浅析碑帖《三希堂法帖》 全称是《三希堂石渠宝笈法帖》&#xff0c;是中国清代宫廷刻帖&#xff0c;一共三十二册。 清朝高宗弘历收藏了晋王羲之《快雪时晴帖》&#xff0c;王献之的《中秋帖》&#xff0c;王珣的《伯远帖》三种王氏原墨迹。故而把所藏法书之所…

农产品管理与推荐系统Python+Django网页界面+计算机毕设项目+推荐算法

一、介绍 农产品管理与推荐系统。本系统使用Python作为主要开发语言&#xff0c;前端使用HTML&#xff0c;CSS&#xff0c;BootStrap等技术和框架搭建前端界面&#xff0c;后端使用Django框架处理应用请求&#xff0c;使用Ajax等技术实现前后端的数据通信。实现了一个综合性的…

2024年9月10日嵌入式学习

今日主要学习了缓冲帧。 Framebuffer&#xff08;帧缓冲&#xff09;是Linux系统为显示设备提供的一套应用程序接口&#xff0c;它将显存抽象为一种设备&#xff0c;允许上层应用程序在图形模式下直接进行显示缓冲区的读写操作。 原理&#xff1a;通过内存映射技术向显存空间…

MM-PhyQA——一个专门处理高中物理选择题的 LLM 聊天机器人

概述 论文地址&#xff1a;https://arxiv.org/abs/2404.12926 人工智能的发展正在改变我们的学习方式。特别是使用大规模语言模型&#xff08;LLM&#xff09;的聊天机器人&#xff0c;通过提供个性化指导和即时反馈&#xff0c;极大地拓展了教育的可能性。 然而&#xff0c…

带你深入了解C语言指针(一)

目录 前言 一、内存和地址 1. 内存 2. 究竟该如何理解编址 二、指针变量和地址 1. 取地址操作符&#xff08;&&#xff09; 2. 指针变量和解引用操作符&#xff08;*&#xff09; 2.1 指针变量 2.2 如何拆解指针类型 2.3 解引⽤操作符 3. 指针变量的大小 三、指…

LeetCode题练习与总结:矩形面积--223

一、题目描述 给你 二维 平面上两个 由直线构成且边与坐标轴平行/垂直 的矩形&#xff0c;请你计算并返回两个矩形覆盖的总面积。 每个矩形由其 左下 顶点和 右上 顶点坐标表示&#xff1a; 第一个矩形由其左下顶点 (ax1, ay1) 和右上顶点 (ax2, ay2) 定义。第二个矩形由其左…

配置 MinGW 以及使用 g++ 编译 C++ 程序

如何在 Windows 上安装和配置 MinGW 以及使用 g 编译 C 程序 (C语言&#xff08;gcc&#xff09;类似 ) 在Windows环境下&#xff0c;使用C进行编程需要一个编译器&#xff0c;而MinGW (Minimalist GNU for Windows) 是一个常用的C/C编译器工具集。对于编程新手来说&#xff0c…

SOMEIP_ETS_101: SD_ClientServiceActivate_send_StopOfferService

测试目的&#xff1a; 验证当DUT在客户端模式下开始发送FindService消息时&#xff0c;测试器发送StopOfferService后&#xff0c;DUT能够理解其正在寻找的服务和实例ID不再可用&#xff0c;并停止为此服务和实例ID发送FindService消息。同时&#xff0c;DUT仍然可以发送Find-…

云曦2024秋季开学考

ezezssrf 第一关&#xff1a;md5弱比较 yunxi%5B%5D1&wlgf%5B%5D2 第二关&#xff1a; md5强比较 需要在bp中传参&#xff0c;在hackbar里不行 yunxiiM%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DC V%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_B…

【HarmonyOS NEXT】实现网络图片保存到手机相册

【问题描述】 给定一个网络图片的地址&#xff0c;实现将图片保存到手机相册 【API】 phAccessHelper.showAssetsCreationDialog【官方文档】 https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-photoaccesshelper-V5#showassetscreationdialog…

降维打击 华为赢麻了

文&#xff5c;琥珀食酒社 作者 | 积溪 真是赢麻了 华为估计都懵了 这辈子还能打这么富裕的仗&#xff1f; 其实在苹果和华为的发布会召开之前 我就知道华为肯定会赢 但我没想到 苹果会这么拉胯 华为这是妥妥的降维打击啊 就说这苹果iPhone 16吧 屏幕是变大了、颜色…

编译安装调试 scaLapack 和 openmpi 以及 lapack

编译安装调试 scaLapack /home/hipper/ex_scalapack/ mkdir ./lapack mkdir -p ./lapack/local/lib mkdir ./openmpi mkdir ./scalapack 1&#xff0c;编译安装 Lapack 下载代码&#xff1a; cd lapack wget https://github.com/Reference-LAPACK/lapack/archive/refs/tags/…

Python | Leetcode Python题解之第398题随机数索引

题目&#xff1a; 题解&#xff1a; class Solution:def __init__(self, nums: List[int]):self.nums numsdef pick(self, target: int) -> int:ans cnt 0for i, num in enumerate(self.nums):if num target:cnt 1 # 第 cnt 次遇到 targetif randrange(cnt) 0:ans …

智能头盔语音识别声控芯片,AI离线语音识别ic方案,NRK3301

头盔是交通事故中保护电动车车主安全的最后一道屏障。为了增加骑行用户的安全保护&#xff0c;改善骑行用户的出行体验&#xff0c;让用户从被动使用头盔到主动佩戴头盔&#xff0c;头盔厂家与九芯电子合作&#xff0c;推出了语音智能头盔&#xff0c;它具备首家骑行专用的智能…

【网络安全】-xss跨站脚本攻击实战-xss-labs(1~10)

Level1: 检查页面源代码&#xff1a; function函数&#xff1a; (function(){try{let tn ;if(tn.includes(oem)){Object.defineProperty(document, referrer, {get: function(){return ;}});}else if(tn.includes(hao_pg)){if(!document.referrer.match(tn)){Object.definePro…

【python】python 安装和 pycharm 安装

1 python 安装 1.1 下载 下载地址&#xff1a;python 官网 1.2 安装 windows 安装为例。 双击.exe文件打开 安装界面 安装完成 1.3 检查安装是否成功 win/start 键r 键 运行窗口输入 cmd 回车 3 输入 python查看 显示版本信息&#xff0c;表示已经安装成功。 …

协议头,wireshark,http

目录 协议头 ip头 udp头 mac层 网络工具 telnet wireshark Http 一、HTTP 协议介绍 二、HTTP 协议的工作过程 三、使用抓包工具抓取报文 四、获取到http请求报文&#xff1a; 五、http请求&#xff08;request&#xff09; &#xff08;一&#xff09;、认识URL 项…