秒杀优化+秒杀安全

1.Redis预减库存

1.OrderServiceImpl.java 问题分析

image-20240513102548010

2.具体实现 SeckillController.java
1.实现InitializingBean接口的afterPropertiesSet方法,在bean初始化之后将库存信息加载到Redis
    /*** 系统初始化,将秒杀商品库存加载到redis中** @throws Exception*/@Overridepublic void afterPropertiesSet() throws Exception {// 将秒杀商品库存加载到redis中List<GoodsVo> goodsVoList = goodsService.findGoodsVo();// 如果没有秒杀商品,直接返回if (CollectionUtils.isEmpty(goodsVoList)) {return;}goodsVoList.forEach(goodsVo -> {redisTemplate.opsForValue().set("seckillGoods:" + goodsVo.getId(), goodsVo.getStockCount());});}
2.进行库存预减
        // 库存预减Long stock = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);// 判断库存是否充足if (stock < 0) {// 库存不足,返回秒杀失败页面redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);model.addAttribute("errmsg", RespBeanEnum.EMPTY_STOCK.getMessage());return "secKillFail";}
3.优化分析
  • 正常情况下,每次都需要到数据库减少库存,来解决超卖问题
  • 使用Redis进行库存预减,可以减少对数据库的操作,从而提升效率
4.测试
1.清空Redis

image-20240513111015701

2.清空订单表和秒杀商品表,设置一号商品库存为10

image-20240513111216371

3.将项目部署上线
4.UserUtil.java生成100个用户

image-20240513111713541

5.发送5000次请求
1.线程组配置

image-20240513111955965

2.cookie管理器

image-20240513112038442

3.秒杀请求

image-20240513112155873

4.QPS为307,从80提升到了307提升了283%

image-20240513112324226

5.但是,出现了库存遗留问题

image-20240513113053268

5.缓存遗留原因分析

image-20240513120739584

2.内存标记优化高并发

1.问题分析
  • 在未使用内存标记时,每次请求都需要对库存进行预减,来判断是否有库存,即使库存为0
  • 所以采用内存标记的方式,当库存为0的时候,就不用进行库存预减
2.具体实现 SeckillController.java
1.首先定义一个标记是否有库存的map

image-20240513133912046

2.在系统初始化时,初始化map

image-20240513134008613

3.如果库存预减发现没有库存了,就设置内存标记

image-20240513134102161

4.在库存预减前,判断内存标记,减少redis访问

image-20240513134121258

3.测试
1.将项目上线
2.清空订单表和秒杀商品表,设置一号商品库存为10
3.清空Redis
4.发送5000次请求,QPS为330,从307提高到了330

image-20240513135337854

3.消息队列实现异步秒杀

1.问题分析

image-20240513140733314

2.思路分析

image-20240513140723821

image-20240513141159400

3.构建秒杀消息对象 SeckillMessage.java
package com.sxs.seckill.pojo;import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;/*** Description: 秒杀消息** @Author sun* @Create 2024/5/13 14:15* @Version 1.0*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SeckillMessage {private User user;private Long goodsId;
}
4.秒杀RabbitMQ配置
package com.sxs.seckill.config;import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;/*** Description: 秒杀RabbitMQ配置** @Author sun* @Create 2024/5/13 14:23* @Version 1.0*/
@Configuration
public class RabbitMQSeckillConfig {// 定义一个消息队列和一个topic交换机的名字public static final String SECKILL_QUEUE = "seckillQueue";public static final String SECKILL_EXCHANGE = "seckillExchange";// 创建一个消息队列@Beanpublic Queue seckillQueue() {return new Queue(SECKILL_QUEUE, true);}// 创建一个topic交换机@Beanpublic TopicExchange seckillExchange() {return new TopicExchange(SECKILL_EXCHANGE);}// 将消息队列绑定到交换机@Beanpublic Binding binding() {// 绑定消息队列到交换机,并指定routingKey,表示只接收routingKey为seckill.#的消息return BindingBuilder.bind(seckillQueue()).to(seckillExchange()).with("seckill.#");}
}
5.生产者和消费者
1.生产者 MQSendMessage.java
package com.sxs.seckill.rabbitmq;import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** Description: 消息队列发送消息** @Author sun* @Create 2024/5/13 15:14* @Version 1.0*/
@Service
@Slf4j
public class MQSendMessage {@Resourceprivate RabbitTemplate rabbitTemplate;// 发送秒杀消息public void sendSeckillMessage(String message) {log.info("发送消息:" + message);rabbitTemplate.convertAndSend("seckillExchange", "seckill.message", message);}
}
2.消费者,进行秒杀
1.引入hutool工具类
        <dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.3.3</version></dependency>
2. MQReceiverMessage.java
package com.sxs.seckill.rabbitmq;import cn.hutool.json.JSONUtil;
import com.sxs.seckill.pojo.SeckillMessage;
import com.sxs.seckill.pojo.User;
import com.sxs.seckill.service.GoodsService;
import com.sxs.seckill.service.OrderService;
import com.sxs.seckill.service.SeckillGoodsService;
import com.sxs.seckill.vo.GoodsVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Service;import javax.annotation.Resource;/*** Description: 消息队列接收消息** @Author sun* @Create 2024/5/13 15:17* @Version 1.0*/
@Service
@Slf4j
public class MQReceiverMessage {@Resourceprivate GoodsService goodsService;@Resourceprivate OrderService orderService;// 接收秒杀消息@RabbitListener(queues = "seckillQueue")public void receiveSeckillMessage(String message) {log.info("接收消息:" + message);// 此时的message是秒杀的消息,要将其转换为SeckillMessage对象SeckillMessage seckillMessage = JSONUtil.toBean(message, SeckillMessage.class);// 获取秒杀信息User user = seckillMessage.getUser();Long goodsId = seckillMessage.getGoodsId();// 根据商品id查询商品详情GoodsVo goodsVoByGoodsId = goodsService.findGoodsVoByGoodsId(goodsId);// 进行秒杀orderService.seckill(user, goodsVoByGoodsId);}
}
6.编写控制层
1.SeckillController.java

image-20240513154029078

        // MQ实现异步秒杀// 封装秒杀信息SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);// 使用hutool工具类将SeckillMessage对象转换为json字符串并发送mqSendMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));// 返回排队中页面model.addAttribute("errmsg", RespBeanEnum.QUEUE_ERROR.getMessage());return "secKillFail";
2.RespBeanEnum.java 新增响应枚举类

image-20240513154055007

7.测试
1.将项目上线
2.清空订单表和秒杀商品表,设置一号商品库存为10
3.清空Redis
4.发送5000次请求,QPS为363

image-20240513161847459

秒杀安全

1.秒杀接口隐藏

1.需求分析

image-20240514103812853

2.思路分析

image-20240514104439584

3.具体实现
1.RespBeanEnum.java 新增几个响应
2.OrderService.java 新增方法
    /*** 方法:生成秒杀路径* @param user* @param goodsId* @return*/String createPath(User user, Long goodsId);/*** 方法:校验秒杀路径* @param user* @param goodsId* @param path* @return*/boolean checkPath(User user, Long goodsId, String path);
3.OrderServiceImpl.java
    @Overridepublic String createPath(User user, Long goodsId) {// 对参数进行校验if (user == null || goodsId <= 0) {return null;}// 生成秒杀路径String path = MD5Util.md5(UUIDUtil.uuid() + "123456");// 保存到redis中,设置过期时间为60秒redisTemplate.opsForValue().set("seckillPath:" + user.getId() + ":" + goodsId, path, 60, TimeUnit.SECONDS);return path;}@Overridepublic boolean checkPath(User user, Long goodsId, String path) {// 对参数进行校验if (user == null || goodsId <= 0 || StringUtils.isBlank(path)) {return false;}// 从redis中获取秒杀路径String redisPath = (String) redisTemplate.opsForValue().get("seckillPath:" + user.getId() + ":" + goodsId);// 判断是否相等,并返回return path.equals(redisPath);}
4.SeckillController.java
    @RequestMapping("/{path}/doSeckill")public RespBean doSeckill(Model model, User user, Long goodsId, @PathVariable String path) {// 判断用户是否登录if (user == null) {return RespBean.error(RespBeanEnum.SESSION_ERROR);}// 校验pathboolean check = orderService.checkPath(user, goodsId, path);if (!check) {return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);}// 根据goodsId获取GoodsVoGoodsVo goodsVoByGoodsId = goodsService.findGoodsVoByGoodsId(goodsId);// 判断是否有库存if (goodsVoByGoodsId.getStockCount() < 1) {return RespBean.error(RespBeanEnum.EMPTY_STOCK);}// 从redis中判断是否复购if (redisTemplate.hasKey("order:" + user.getId() + ":" + goodsId)) {return RespBean.error(RespBeanEnum.REPEATE_ERROR);}// 首先判断内存标记if (inventoryTagging.get(goodsId)) {return RespBean.error(RespBeanEnum.EMPTY_STOCK);}// 库存预减Long stock = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);// 判断库存是否充足if (stock < 0) {// 标记库存不足inventoryTagging.put(goodsId, true);// 库存不足,返回秒杀失败页面redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);return RespBean.error(RespBeanEnum.EMPTY_STOCK);}// MQ实现异步秒杀// 封装秒杀信息SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);// 使用hutool工具类将SeckillMessage对象转换为json字符串并发送mqSendMessage.sendSeckillMessage(JSONUtil.toJsonStr(seckillMessage));// 返回排队中return RespBean.success(RespBeanEnum.SEK_KILL_WAIT);}/*** 生成秒杀地址* @param user* @param goodsId* @return*/@ResponseBody@RequestMapping("/path")public RespBean getPath(User user, Long goodsId) {// 参数校验if (user == null || goodsId <= 0) {return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);}// 调用OrderService中的createPath方法生成秒杀地址String path = orderService.createPath(user, goodsId);return RespBean.success(path);}
5.goodsDetail.html
1.秒杀首先获取路径

image-20240514132039228

2.解析环境变量,区分多环境

image-20240514120006961

3.新增两个方法,使用隐藏秒杀接口的方式秒杀商品

4.测试

image-20240514133435103

image-20240514133441653

2.验证码防止脚本攻击

1.思路分析

image-20240514134151357

2.具体实现
1.pom.xml 引入依赖
        <dependency><groupId>com.ramostear</groupId><artifactId>Happy-Captcha</artifactId><version>1.0.1</version></dependency>
2.SeckillController.java 编写方法生成验证码
    /*** 生成验证码* @param user* @param goodsId* @param request* @param response*/@RequestMapping("/captcha")public void happyCaptcha(User user, Long goodsId, HttpServletRequest request, HttpServletResponse response) {HappyCaptcha.require(request, response).style(CaptchaStyle.ANIM) //设置展现样式为动画.type(CaptchaType.NUMBER) //设置验证码内容为数字.length(6) //设置字符长度为 6.width(220) //设置动画宽度为 220.height(80) //设置动画高度为 80.font(Fonts.getInstance().zhFont()) //设置汉字的字体.build().finish(); //生成并输出验证码// 这个验证码的结果会存储在session中,可以通过request.getSession().getAttribute("happy-captcha")获取// 获取验证码的值,放入redis中String verifyCode = request.getSession().getAttribute("happy-captcha").toString();redisTemplate.opsForValue().set("captcha:" + user.getId() + ":" + goodsId, verifyCode, 60, TimeUnit.SECONDS);}
3.OrderService.java 校验用户输入的验证码
    /*** 校验用户输入的验证码* @param user* @param goodsId* @param captcha* @return*/boolean checkCaptcha(User user, Long goodsId, String captcha);
4.OrderServiceImpl.java
    @Overridepublic boolean checkCaptcha(User user, Long goodsId, String captcha) {// 参数校验if (user == null || goodsId <= 0 || StringUtils.isBlank(captcha)) {return false;}// 从redis中获取验证码String verifyCode = (String) redisTemplate.opsForValue().get("captcha:" + user.getId() + ":" + goodsId);return captcha.equals(verifyCode);}
5.SeckillController.java 加入验证码校验
6.goodsDetail.html
1.前端请求验证码
2.测试

image-20240514142410612

3.获取用户输入的验证码,并携带验证码

image-20240514142750293

image-20240514143247969

3.秒杀接口限流-防刷

1.思路分析

image-20240514144320230

2.简单接口限流
1.SeckillController.java

image-20240514151732430

2.测试

image-20240514151841132

4.通用接口限流防刷

1.思路分析

image-20240514153042300

2.编写自定义限流注解 AccessLimit.java
package com.sxs.seckill.config;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;/*** Description: 限流注解** @Author sun* @Create 2024/5/14 15:38* @Version 1.0*/
@Retention(RetentionPolicy.RUNTIME) // 运行时生效
@Target(ElementType.METHOD) // 作用在方法上
public @interface AccessLimit {int seconds(); // 时间范围int maxCount(); // 最大访问次数boolean needLogin() default true; // 是否需要登录
}
3.使用方式 SeckillController.java

image-20240514154353081

4.编写 config/UserContext.java 使用ThreadLocal存储user
package com.sxs.seckill.config;import com.sxs.seckill.pojo.User;/*** Description:** @Author sun* @Create 2024/5/14 15:46* @Version 1.0*/
public class UserContext {// 初始化ThreadLocal以存储用户信息private static ThreadLocal<User> threadLocal = new ThreadLocal<>();public static User getUser() {return threadLocal.get();}public static void setUser(User user) {threadLocal.set(user);}// 清除ThreadLocal中的数据public static void removeUser() {threadLocal.remove();}
}
5.编写自定义限流拦截器 config/AccessLimitInterceptor.java
package com.sxs.seckill.config;import com.sxs.seckill.exception.GlobalException;
import com.sxs.seckill.pojo.User;
import com.sxs.seckill.service.UserService;
import com.sxs.seckill.utils.CookieUtil;
import com.sxs.seckill.vo.RespBeanEnum;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.concurrent.TimeUnit;/*** Description: 限流拦截器** @Author sun* @Create 2024/5/14 15:55* @Version 1.0*/
@Component
public class AccessLimitInterceptor implements HandlerInterceptor {@Resourceprivate UserService userService;@ResourceRedisTemplate redisTemplate;/*** 拦截请求,进行限流处理,在目标方法前执行** @param request* @param response* @param handler* @return* @throws Exception*/@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (handler instanceof HandlerMethod) {// 如果是方法级别的拦截// 1.获取user对象,放到threadLocal中User user = getUser(request, response);UserContext.setUser(user);// 2.处理限流注解HandlerMethod handlerMethod = (HandlerMethod) handler;AccessLimit accessLimit = handlerMethod.getMethodAnnotation(AccessLimit.class);if (accessLimit == null) {return true;}// 3.获取注解上的参数int seconds = accessLimit.seconds();int maxCount = accessLimit.maxCount();boolean needLogin = accessLimit.needLogin();String key = request.getRequestURI();if (needLogin) {// 如果需要登录,但是没有登录,返回错误信息if (user == null) {// 如果需要登录,但是没有登录,返回错误信息throw new GlobalException(RespBeanEnum.USER_NOT_LOGIN);}// 如果登录了,key加上用户idkey += ":" + user.getId();}// 4.对访问次数进行限制,如果登陆了就是对这个用户的访问次数进行限制,如果没有登录就是对这个接口的访问次数进行限制Integer count = (Integer) redisTemplate.opsForValue().get(key);if (count == null) {// 第一次访问redisTemplate.opsForValue().set(key, 1, seconds, TimeUnit.SECONDS);} else if (count < maxCount) {// 访问次数加1redisTemplate.opsForValue().increment(key);} else {// 超过访问次数throw new GlobalException(RespBeanEnum.ACCESS_LIMIT_REACHED);}}// 如果不是方法级别的拦截,直接放行return true;}// 单独编写方法,获取User对象private User getUser(HttpServletRequest request, HttpServletResponse response) {String ticket = CookieUtil.getCookieValue(request, "userTicket");if (ticket == null) {return null;}return userService.getUserByCookie(ticket, request, response);}
}
6.config/WebConfig.java中注册拦截器

image-20240514165547226

7.修改自定义参数解析器UserArgumentResolver.java,直接从ThreadLocal中获取User

image-20240514165815611

8.测试

image-20240514170313513

9.解决库存遗留问题,为每个用户id加锁即可

image-20240515084923122

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

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

相关文章

【YOLOv10】使用 TensorRT C++ API 调用GPU加速部署 YOLOv10 实现 500FPS 推理速度——快到飞起!

NVIDIA TensorRT ™ 是一款用于高性能深度学习推理的 SDK&#xff0c;包含深度学习推理优化器和运行时&#xff0c;可为推理应用程序提供低延迟和高吞吐量。YOLOv10是清华大学研究人员近期提出的一种实时目标检测方法&#xff0c;通过消除NMS、优化模型架构和引入创新模块等策…

平稳交付 20+ 医院,卓健科技基于 OpenCloudOS 的落地实践

导语&#xff1a;随着数字化转型于各个行业领域当中持续地深入推进&#xff0c;充当底层支撑的操作系统正发挥着愈发关键且重要的作用。卓健科技把 OpenCloudOS 当作首要的交付系统&#xff0c;达成了项目交付速度的提升、安全可靠性的增强、运维成本的降低。本文将会阐述卓健科…

【转】ES, 广告索引

思考&#xff1a; 1&#xff09;直接把别名切换到上一个版本索引 --解决问题 2&#xff09;广告层级索引如何解决&#xff1f; -routing、join 3&#xff09;查询的过程&#xff1a;query and fetch, 优化掉fetch 4&#xff09;segment合并策略 5&#xff09;全量写入时副…

gcc与g++的原理

程序的翻译 1 快速认识gcc/g2 程序翻译的过程2.1 预处理阶段2.2 编译阶段2.3 汇编阶段2.4 链接阶段 3 动静态库 1 快速认识gcc/g 首先我们需要在linux系统中安装对应的编译器gcc/g&#xff0c;安装命令如下&#xff08;centos7环境下&#xff09;&#xff1a; gcc: yum instal…

什么是哈希冲突?如何解决哈希冲突?HashMap和TreeMap之间的区别?

Map 和 Set 的概念 Map和Set是一种专门用来进行搜索的容器或者数据结构&#xff0c;其搜索的效率与其具体的实例化子类有关 。 为什么说它是一种专门用来进行搜索的数据结构呢&#xff1f; 我们应该都用过“遍历查找”、“二分查找”&#xff0c;这两种方式也都是用来对目标…

Spring Security系列之PasswordEncoder

概述 任何一个登录系统的密码不能明文存储&#xff0c;万一发生数据库泄漏事故&#xff08;不管是内部人员导出数据库数据还是被黑客攻击破解数据库实例节点拿到数据库数据等&#xff0c;又或者是其他情况造成的&#xff09;&#xff0c;将产生巨大的损失。因此明文密码在存储…

DOS 操作系统

DOS 介绍 DOS&#xff1a;disk operating system&#xff0c;磁盘操作系统。 中国DOS联盟下载 MS-DOS 7.10完整安装版&#xff08;含图形安装程序&#xff09; DOS 环境下的操作 输入部分内容后按下 Tab 可以快速自动补全。 按住 Ctrl 键可以用鼠标滚轮改变字号大小。 DO…

【数据结构初阶】--- 顺序表

顺序表&#xff0c;好像学C语言时从来没听过&#xff0c;实际上就是给数组穿了层衣服&#xff0c;本质是一模一样的。 这里的顺序表实际是定义了一个结构体&#xff0c;设计各种函数来实现它的功能&#xff0c;比如说数组中的增删改查插入&#xff0c;这些基本操作其实平时就会…

c++调用动态库LNK2019无法解析的外部符号LNK1120无法解析的外部命令

严重性 代码 说明 项目 文件 行 禁止显示状态 错误 LNK1120 6 个无法解析的外部命令 ConsoleApplication1 D:\vs_qt_project\ConsoleApplication1\x64\Debug\ConsoleApplication1.exe 1 严重性 代码 说明 项目 文件 行 …

应用层——HTTP协议(自己实现一个http协议)——客户端(浏览器)的请求做反序列化和请求分析,然后创建http向响应结构

应用层&#xff1a;之前我们写的创建套接字&#xff0c;发送数据&#xff0c;序列化反序列化这些都是在写应用层 我们程序员写的一个个解决我们实际问题, 满足我们日常需求的网络程序, 都是在应用层 之前的网络计算机是我们自定义的协议&#xff1a;传输的数据最终是什么样的结…

高级文件操作

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Python内置的os模块除了可以对目录进行操作&#xff0c;还可以对文件进行一些高级操作&#xff0c;具体函数如表4所示。 表4 os模块提供的与文件相…

【AI基础】第三步:纯天然保姆喂饭级-安装并运行chatglm2-6b

chatglm2构建时使用了RUST&#xff0c;所以在安装chatglm2之前&#xff0c;先安装RUST。 此系列文章列表&#xff1a; 【AI基础】第一步&#xff1a;安装python开发环境-windows篇_下载安装ai环境python-CSDN博客 【AI基础】第一步&#xff1a;安装python开发环境-conda篇_mini…

知识图谱的应用---智慧司法

文章目录 智慧司法典型应用 智慧司法 智慧司法是综合运用人工智能、大数据、互联网、物联网、云计算等信息技术手段&#xff0c;遵循司法公开、公平、公正的原则&#xff0c;与司法领域业务知识经验深度融合&#xff0c;使司法机关在审判、检查、侦查、监管职能各方面得到全面的…

【数据结构】图论入门

引入 数据的逻辑结构&#xff1a; 集合&#xff1a;数据元素间除“同属于一个集合”外&#xff0c;无其他关系线性结构&#xff1a;一个对多个&#xff0c;例如&#xff1a;线性表、栈、队列树形结构&#xff1a;一个对多个&#xff0c;例如&#xff1a;树图形结构&#xff1…

SpringBoot中的WebMvcConfigurationSupport和WebMvcConfigurer

在SpringBoot中可以通过以下两种方式来完成自定义WebMvc的配置&#xff1a; &#xff08;1&#xff09;继承WebMvcConfigurationSupport类 &#xff08;2&#xff09;实现WebMvcConfigurer接口 通过这两种方式完成的WebMvc配置存在差异&#xff0c;本文将对此作简单说明与区…

【Vue】单页应用程序介绍

通常基于Vue去开发一整个网站&#xff0c;开发出来的这整个网站应用&#xff0c;我们都会叫做单页应用程序 概念 单页应用程序&#xff1a;SPA【Single Page Application】是指所有的功能都在一个html页面上实现 我们可以将页面共用的部分封装成组件&#xff0c;底下要切换的也…

工具-金舟投屏软件: 手机如何投屏到电脑上 / Wi-Fi / USB

金舟安卓/iOS苹果投屏-正版软件下载中心 方法一、金舟投屏软件-wifi 1.1、准备工作 确保苹果手机和Windows电脑都连接到同一个Wi-Fi网络。 在Windows电脑上安装并打开金舟投屏软件。 1.2、操作步骤 在金舟投屏软件上选择“苹果手机投屏”功能。 在苹果手机上下滑屏幕&am…

New Work-flow of Circuit Bootstrapping

参考文献&#xff1a; [CGGI17] Chillotti I, Gama N, Georgieva M, et al. Faster packed homomorphic operations and efficient circuit bootstrapping for TFHE. ASIACRYPT 2017 (1): 377-408.[CDKS21] Chen H, Dai W, Kim M, et al. Efficient homomorphic conversion be…

dots_image 增强图像中的圆点特征

dots_image 增强图像中的圆点特征 1. dot_image 有什么用途&#xff1f;2. 点状字符的特征增强3. Halcon代码 1. dot_image 有什么用途&#xff1f; Enhance circular dots in an image. 这个算子可以增强图像中的圆点特征&#xff0c;例如下面的例子。 2. 点状字符的特征增强…

忆恒创源国产系列新品 —— PBlaze7 7A40 取得 PCI-SIG 兼容性认证

在此前报道中&#xff0c;我们曾预告了忆恒创源国产系列 PCIe 5.0 SSD 新品 —— PBlaze7 7A40&#xff0c;今天&#xff0c;这款 SSD 已经顺利通过 PCI-SIG 的严格测试并出现在 Integrators List 集成商列表当中&#xff0c;标志着距离 PBlaze7 7A40 的正式发布又近了一步。 正…