秒杀优化+秒杀安全
1.Redis预减库存
1.OrderServiceImpl.java 问题分析

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

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

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

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

2.cookie管理器

3.秒杀请求

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

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

5.缓存遗留原因分析

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

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

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

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

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

3.消息队列实现异步秒杀
1.问题分析

2.思路分析


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

// 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 新增响应枚举类

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

秒杀安全
1.秒杀接口隐藏
1.需求分析

2.思路分析

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.秒杀首先获取路径

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

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

4.测试


2.验证码防止脚本攻击
1.思路分析

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.测试

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


3.秒杀接口限流-防刷
1.思路分析

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

2.测试

4.通用接口限流防刷
1.思路分析

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

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中注册拦截器

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

8.测试

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

相关文章:
秒杀优化+秒杀安全
1.Redis预减库存 1.OrderServiceImpl.java 问题分析 2.具体实现 SeckillController.java 1.实现InitializingBean接口的afterPropertiesSet方法,在bean初始化之后将库存信息加载到Redis /*** 系统初始化,将秒杀商品库存加载到redis中** throws Excepti…...
48、Flink 的 Data Source API 详解
a)概述 本节将描述 FLIP-27 中引入的新 Source API 的主要接口。 b)Source Source API 是一个工厂模式的接口,用于创建以下组件。 Split EnumeratorSource ReaderSplit SerializerEnumerator Checkpoint Serializer 此外,Sou…...
深入解析Java扩展机制:SPI与Spring.factories
目录 Java SPI概述 1.1 什么是SPI?1.2 SPI的工作原理1.3 SPI的优缺点 SPI的应用 2.1 Java标准库中的SPI应用2.2 自定义SPI示例 Spring.factories概述 3.1 什么是spring.factories?3.2 spring.factories的工作原理3.3 spring.factories的优缺点 spring.f…...
Vue2之模板语法
文章目录 1.模板语法1.1 插值语法{{}}可以写什么1.2 指令语法1.2.1 指令概述1.2.2 v-bind指令1.2.3 v-model指令 1.模板语法 1.1 插值语法{{}}可以写什么 (1)在data中声明的 (2)常量 (3)合法的JavaScript…...
java基础练习题
1、一个".java"源文件中是否可以包括多个类?有什么限制? 可以包含多个类。但是只有一个类可以声明为public,且要求声明为public的类的类名与源文件名相同。 2、java的优势? a、跨平台性 b、安全性高 c、简单性 d、…...
unity中通过实现底层接口实现非按钮(图片)的事件监听
编写监听脚本 PEListenter 继承自MonoBehaviour类,并实现了IPointerDownHandler、IPointerUpHandler和IDragHandler接口,按照需求定义需要接收事件(鼠标按下、抬起、拖拽)的回调函数 //监听类(需要挂载在物体上面&am…...
重庆耶非凡科技有限公司的选品师项目加盟靠谱吗?
在当今电子商务的浪潮中,选品师的角色愈发重要。而重庆耶非凡科技有限公司以其独特的选品师项目,在行业内引起了广泛关注。对于想要加盟该项目的人来说,项目的靠谱性无疑是首要考虑的问题。 首先,我们来看看耶非凡科技有限公司的背…...
《青少年编程与数学》课程方案:4、课程策略
《青少年编程与数学》课程方案:4、课程策略 一、工程师思维二、使命感驱动三、价值观引领四、学习现代化五、工作生活化六、与时代共进 《青少年编程与数学》课程策略强调采用工程师思维,避免重复造轮子,培养使命感,通过探索兴趣、…...
用爬虫实现---模拟填志愿
先来说实现逻辑,首先我要获取到这个网站上所有的信息,那么我们就可以开始对元素进行检查 我们发现他的每一个学校信息都有一个对应的属性,并且是相同的,那么我们就可以遍历这个网页中的所有属性一样的开始爬取 在来分析࿰…...
vscode Run Code输出出现中文乱码情况问题解决方案
主要解决方案是通过修改计算机默认的编码格式,来完成的。 chcp 是 Windows 操作系统中的一个命令,用于显示或设置控制台的代码页(code page)。代码页决定了控制台如何解释和显示字符,特别是非 ASCII 字符(例如 Unicode 字符)。 使用方法 显示当前代码页: 输入 chcp 而…...
代码随想录训练营Day30
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言一、重新安排行程 前言 提示:这里可以添加本文要记录的大概内容: 今天是跟着代码随想录刷题的第30天,主要是复习了回溯算法…...
Swift 序列(Sequence)排序面面俱到 - 从过去到现在(二)
概览 在上篇 Swift 序列(Sequence)排序面面俱到 - 从过去到现在(一)博文中,我们讨论了 Swift 语言中序列和集合元素排序的一些基本知识,我们还给出了以自定义类型中任意属性排序的“康庄大道”。 不过在实际的撸码场景中,我们往往需要的是“多属性”同时参与到排序的考…...
STM32F103C8T6基于HAL库移植uC/OS-III
文章目录 一、建立STM32CubeMX工程二、移植1、 uC/OS-III源码2、移植过程 三、配置相关代码1、bsp.c和bsp.h2、main.c3、修改启动代码4、修改app_cfg.h文件5、修改includes.h文件6、修改lib_cfg.h文件 四、编译与烧录总结参考资料 学习嵌入式实时操作系统(RTOS&…...
微服务学习Day9-分布式事务Seata
文章目录 分布式事务seata引入理论基础CAP定理BASE理论 初识Seata动手实践XA模式AT模式TCC模式SAGA模式 高可用 分布式事务seata 引入 理论基础 CAP定理 BASE理论 初识Seata 动手实践 XA模式 AT模式 TCC模式 Service Slf4j public class AccountTCCServiceImpl implements A…...
vue用vite配置代理解决跨域问题(target、rewrite和changeOrigin的使用场景)
Vite的target、rewrite和changeOrigin的使用场景 1. target 使用场景:target 属性在 Vite 的 vite.config.ts 或 vite.config.js 文件的 server.proxy 配置中指定,用于设置代理服务器应该将请求转发到的目标地址。这通常是一个后端服务的API接口地址。…...
为什么PPT录制没有声音 电脑ppt录屏没有声音怎么办
一、为什么PPT录制没有声音 1.软件问题 我们下载软件的时候可能遇到软件损坏的问题,导致录制没有声音,但其他功能还是可以使用的。我建议使用PPT的隐藏功能,下载插件,在PPT界面的加载项选项卡中就能使用。我推荐一款可以解决录屏…...
JDBC学习笔记(三)高级篇
一、JDBC 优化及工具类封装 1.1 现有问题 1.2 JDBC 工具类封装 V1.0 resources/db.properties配置文件: driverClassNamecom.mysql.cj.jdbc.Driver urljdbc:mysql:///atguigu usernameroot password123456 initialSize10 maxActive20 工具类代码: p…...
c++编译器在什么情况下会提供类的默认构造函数等,与析构函数
我们都知道,在 c 里,编写的简单类,若没有自己编写构造析构函数与 copy 构造函数 与 赋值运算符函数,那么编译器会提供这些函数,并实现简单的语义,比如成员赋值。看 源码时,出现了下图类似的情形…...
SpringBoot3整合Mybatis-Plus3.5.5出现的问题
主要是由于 mybatis-plus 中 mybatis 的整合包版本不够导致的 排除 mybatis-plus 中自带的 mybatis 整合包,单独引入即可 java.lang.IllegalArgumentException: Invalid value type for attribute factoryBeanObjectType: java.lang.Stringat org.springframework.…...
服务器数据恢复—强制上线raid5阵列离线硬盘导致raid不可用的数据恢复案例
服务器数据恢复环境: 某品牌2850服务器中有一组由6块SCSI硬盘组建的raid5磁盘阵列,linux操作系统ext3文件系统。 服务器故障: 服务器运行过程中突然瘫痪。服务器管理员检查阵列后发现raid5阵列中有两块硬盘离线,将其中一块硬盘进行…...
TDengine 快速体验(Docker 镜像方式)
简介 TDengine 可以通过安装包、Docker 镜像 及云服务快速体验 TDengine 的功能,本节首先介绍如何通过 Docker 快速体验 TDengine,然后介绍如何在 Docker 环境下体验 TDengine 的写入和查询功能。如果你不熟悉 Docker,请使用 安装包的方式快…...
HTML 列表、表格、表单
1 列表标签 作用:布局内容排列整齐的区域 列表分类:无序列表、有序列表、定义列表。 例如: 1.1 无序列表 标签:ul 嵌套 li,ul是无序列表,li是列表条目。 注意事项: ul 标签里面只能包裹 li…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
Java编程之桥接模式
定义 桥接模式(Bridge Pattern)属于结构型设计模式,它的核心意图是将抽象部分与实现部分分离,使它们可以独立地变化。这种模式通过组合关系来替代继承关系,从而降低了抽象和实现这两个可变维度之间的耦合度。 用例子…...
【C++特殊工具与技术】优化内存分配(一):C++中的内存分配
目录 一、C 内存的基本概念 1.1 内存的物理与逻辑结构 1.2 C 程序的内存区域划分 二、栈内存分配 2.1 栈内存的特点 2.2 栈内存分配示例 三、堆内存分配 3.1 new和delete操作符 4.2 内存泄漏与悬空指针问题 4.3 new和delete的重载 四、智能指针…...
CRMEB 中 PHP 短信扩展开发:涵盖一号通、阿里云、腾讯云、创蓝
目前已有一号通短信、阿里云短信、腾讯云短信扩展 扩展入口文件 文件目录 crmeb\services\sms\Sms.php 默认驱动类型为:一号通 namespace crmeb\services\sms;use crmeb\basic\BaseManager; use crmeb\services\AccessTokenServeService; use crmeb\services\sms\…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
MacOS下Homebrew国内镜像加速指南(2025最新国内镜像加速)
macos brew国内镜像加速方法 brew install 加速formula.jws.json下载慢加速 🍺 最新版brew安装慢到怀疑人生?别怕,教你轻松起飞! 最近Homebrew更新至最新版,每次执行 brew 命令时都会自动从官方地址 https://formulae.…...
Chrome 浏览器前端与客户端双向通信实战
Chrome 前端(即页面 JS / Web UI)与客户端(C 后端)的交互机制,是 Chromium 架构中非常核心的一环。下面我将按常见场景,从通道、流程、技术栈几个角度做一套完整的分析,特别适合你这种在分析和改…...
