秒杀优化+秒杀安全

1.Redis预减库存

1.OrderServiceImpl.java 问题分析

image-20240513102548010

2.具体实现 SeckillController.java
1.实现InitializingBean接口的afterPropertiesSet方法,在bean初始化之后将库存信息加载到Redis
    /**
     * 系统初始化,将秒杀商品库存加载到redis中
     *
     * @throws Exception
     */
    @Override
    public 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";

    // 创建一个消息队列
    @Bean
    public Queue seckillQueue() {
        return new Queue(SECKILL_QUEUE, true);
    }

    // 创建一个topic交换机
    @Bean
    public TopicExchange seckillExchange() {
        return new TopicExchange(SECKILL_EXCHANGE);
    }

    // 将消息队列绑定到交换机
    @Bean
    public 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 {
    @Resource
    private 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 {
    @Resource
    private GoodsService goodsService;
    @Resource
    private 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
    @Override
    public 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;
    }

    @Override
    public 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);
        }
        // 校验path
        boolean check = orderService.checkPath(user, goodsId, path);
        if (!check) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        // 根据goodsId获取GoodsVo
        GoodsVo 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
    @Override
    public 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 {
    @Resource
    private UserService userService;
    @Resource
    RedisTemplate redisTemplate;

    /**
     * 拦截请求,进行限流处理,在目标方法前执行
     *
     * @param request
     * @param response
     * @param handler
     * @return
     * @throws Exception
     */
    @Override
    public 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加上用户id
                key += ":" + 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) {
                // 访问次数加1
                redisTemplate.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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java使用GDAL来解析KMZ及KML实战

目录 前言 一、在GQIS中浏览数据 1、关于空间参考 2、属性表格 二、GDAL的相关驱动及解析实战 1、GDAL中的KMZ驱动 2、GDAL实际解析 三、数据解析成果 1、KML解析结果 2、KMZ文件入库 四、总结 前言 在前面的博客中讲过纯Java实现Google地图的KMZ和KML文件的解析&…

学习周报:文献阅读+Fluent案例+Fluent相关算法学习

目录 摘要 Abstract 文献阅读&#xff1a;求解正逆运动波问题的物理信息神经网络 文献摘要 讨论|结论 理论基础 KWM&#xff08;运动波动方程&#xff09; Hard constraint &#xff08;硬约束方式&#xff09; 具有重新分布的搭配点的PINN 具有停止梯度的分数阶方程 …

艾体宝方案 | ntopng监测异常流量并通知到企业微信

你是否曾因网络异常而感到困扰&#xff1f;在数字化时代&#xff0c;网络流量异常可能给企业带来巨大损失。但别担心&#xff0c;我们为您准备了一份详尽的解决方案&#xff01;想知道如何利用ntopng及时发现异常流量&#xff0c;并通过企业微信等渠道通知你的团队吗&#xff1…

[网鼎杯 2020 青龙组]jocker

运行程序,发现是要我们自己输入 那么肯定是拿到enc慢慢还原 32位,无壳 进来就红一下报错 这里可以看见长度为24 动调一下看看 这里进行了大量的异或 这里是对地址开始的硬编码进行异或,从而达到smc的效果 所以你也可以发现在进行这一步操作之前 encry函数全是报错 你点开…

【Vue】组件的存放目录问题

注意&#xff1a; .vue文件 本质无区别 组件分类 .vue文件分为2类&#xff0c;都是 .vue文件&#xff08;本质无区别&#xff09; 页面组件 &#xff08;配置路由规则时使用的组件&#xff09;复用组件&#xff08;多个组件中都使用到的组件&#xff09; 存放目录 分类开来的…

apifox 生成签名

目录 前言准备编写签名脚本签名说明捋清思路编码获取签名所需的参数生成签名将签名放到合适的位置完整代码 在apifox中配置脚本新增公共脚本引用公共脚本添加环境变量 参考 前言 略 准备 查看apifox提供的最佳实践文章&#xff1a;接口签名如何处理 编写签名脚本 签名说明…

四款优秀的电脑屏幕监控软件|监控电脑屏幕的必备软件

在选择监控电脑屏幕的软件时&#xff0c;我们需要考虑多个因素&#xff0c;包括软件的功能性、易用性、兼容性、安全性以及价格等。以下是几款在市场上广受好评的监控电脑屏幕的软件&#xff0c;它们各自具有独特的特点和优势。 1.安企神软件 安企神软件是一款专业的电脑屏幕监…

【学习笔记】Windows GDI绘图(十三)动画播放ImageAnimator(可调速)

文章目录 前言定义方法CanAnimate 是否可动画显示Animate 动画显示多帧图像UpdateFramesStopAnimate终止动画Image.GetFrameCount 获取动画总帧数Image.GetPropertyItem(0x5100) 获取帧延迟 自定义GIF播放(可调速) 前言 在前一篇文章中用到ImageAnimator获取了GIF动画的一些属…

代码随想录算法训练营第十五天| 110.平衡二叉树、 257. 二叉树的所有路径、404.左叶子之和

110.平衡二叉树 题目链接&#xff1a;110.平衡二叉树 文档讲讲&#xff1a;代码随想录 状态&#xff1a;还可以 思路&#xff1a;计算左右子树的深度差&#xff0c;递归判断左右子树是否符合平衡条件 题解&#xff1a; public boolean isBalanced(TreeNode root) {if (root n…

Turnitin揭露AI写作痕迹,是否会影响论述是重复率?

Turnitin&#xff08;www.checktoo.com&#xff09;为学术界提供了便捷的服务&#xff0c;以确保论文的原创性和学术诚信。然而&#xff0c;许多学生和研究人员在使用Turnitin时&#xff0c;常常会想Turnitin查论文AI率和重复率之间的关系。那么&#xff0c;使用Turnitin查重论…

Kafka消费者api编写教程

1.基本属性配置 输入new Properties().var 回车 //创建属性Properties properties new Properties();//连接集群properties.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG,"node1:9092,node2:9092");//反序列化properties.put(ConsumerConfig.KEY_DESERIALIZER_CL…

Windows mstsc

windows mstsc 局域网远程计算机192.168.0.113为例&#xff0c;远程控制命令mstsc

odoo10 权限控制用户只允许看到自己的字段

假设一个小区管理员用户&#xff0c;只想看到自己小区的信息。 首先添加一个用户信息选项卡界面&#xff0c;如下图的 用户 > 隶属信息&#xff1a; 我们在自己创建的user模块中&#xff0c;views文件夹下添加base_user.xml <?xml version"1.0" encoding&q…

【Microelectronic Systems】期末速通

PART1 嵌入式系统概述与玩转mbed 1 嵌入式系统&#xff0c;微控制器&#xff0c;与ARM 1.1什么是嵌入式系统&#xff1f; 微处理器不仅仅存在于通用计算机中&#xff0c;也可以安置在一些不需要计算的设备内部&#xff0c;比如洗衣机&#xff0c;摄像机。微处理器常常可以控制…

前端面试题(二)答案版

面试形式&#xff1a;线上面试&#xff08;不露脸&#xff09;&#xff1a;时长40分钟 面试评价&#xff1a;由易到难&#xff0c;由细到全&#xff0c;比较不错 面试官&#xff1a;项目经理 面试官提问&#xff08;面试题&#xff09;&#xff1a; 1、聊聊最近写的这个项目…

VLAN与三层交换机

目录 一、VLAN的概念及优势 1.1 分割广播域 1.2 VLAN的优势 二、VLAN的种类 2.1 静态VLAN (重点&#xff0c;大部分用的是静态&#xff09; 2.2 动态VLAN 2.3 查看VLAN表&#xff08;命令&#xff09; 三、静态VLAN的配置 3.1 VLAN的范围 3.2 配置静态VLAN的步骤 3.…

Go源码--sync库(2)

简介 这边文章主要讲解 Sync.Cond和Sync.Rwmutex Sync.Cond 简介 sync.Cond 经常用来处理 多个协程等待 一个协程通知 这种场景&#xff0c; 主要 是阻塞在某一协程中 等待被另一个协程唤醒 继续执行 这个协程后续的功能。cond经常被用来协调协程对某一资源的访问 ants协程池…

拥抱生态农业,享受绿色生活

随着人们对健康生活的追求日益增强&#xff0c;生态农业逐渐成为人们关注的焦点。我们深知生态农业对于保护生态环境、提高农产品品质的重要性&#xff0c;因此&#xff0c;我们积极推广生态农业理念&#xff0c;让更多的人了解并参与到生态农业的实践中来。 生态农业的蓝总说&…

[消息队列 Kafka] Kafka 架构组件及其特性(二)Producer原理

这边整理下Kafka三大主要组件Producer原理。 目录 一、Producer发送消息源码流程 二、ACK应答机制和ISR机制 1&#xff09;ACK应答机制 2&#xff09;ISR机制 三、消息的幂等性 四、Kafka生产者事务 一、Producer发送消息源码流程 Producer发送消息流程如上图。主要是用…

flask招聘数据分析及展示平台-计算机毕业设计源码39292

目 录 摘要 1 绪论 1.1研究意义 1.2国内外研究进展 1.3flask框架介绍 2 1.4论文结构与章节安排 3 2 招聘数据分析及展示平台分析 4 2.1 可行性分析 4 2.2 系统流程分析 4 2.2.1数据增加流程 5 2.3.2数据修改流程 5 2.3.3数据删除流程 5 2.3 系统功能分析 5 2.3.1 功能性分…