第26天-秒杀服务(秒杀系统设计与实现)

news2024/11/23 22:25:57

1.秒杀设计


1.1.秒杀业务

秒杀具有瞬间高并发特点,针对这一特点,必须要做限流+异步+缓存(页面静态化)+独立部署

限流方式:

  • 前端限流,一些高并发的网站直接在前端页面开始限流,例如:小米的验证码设计
  • Nginx限流,直接负载部分请求到错误的静态页面:令牌算法,漏斗算法
  • 网关限流,限流过滤器
  • 代码中使用分布式信号量
  • RabbitMQ限流,chanel.basicQos(1),保证发挥所有服务器的性能

1.2.秒杀流程


在这里插入图片描述

1.3.秒杀系统设计

1.3.1.秒杀(高并发)系统关注的问题

  • 服务单一职责+独立部署
    • 秒杀服务即使自己扛不住压力,宕机,不要影响别的服务
  • 秒杀链接加密
    • 防止恶意攻击,模拟秒杀请求,1000次/s 攻击
    • 防止链接暴露,自己工作人员,提前秒杀商品
  • 库存预热+快速扣减
    • 秒杀读多写少,无需每次实时校验库存,进行库存预热,放到redis中,信号量控制进来秒杀的请求
    • Redis集群+分片高可用
  • 动静分离
    • nginx做好动静分离,保证秒杀和商品详情页的动态请求才打到后端的服务集群
    • 上线可以使用CDN网络,分担本集群压力
  • 恶意请求拦截
    • 识别非法攻击请求并进行拦截,比如登录拦截器
    • 网关层处理
  • 流程错峰
    • 使用各种手段,将流量分担到大宽度的时间点,比如验证码,加入购物车
  • 限流、熔断、降级
    • 前端限流+后端限流(网关)
    • 限制次数,限制总量,快速失败降级运行,熔断隔离防止雪崩
  • 队列削峰
    • 1万个商品,每个1000件秒杀(信号量)
    • 所有秒杀成功的请求,进入队列,慢慢创建订单,扣减库存即可


2.秒杀服务

2.1.构建秒杀服务


在这里插入图片描述

配置域名映射及网关

192.168.139.10 seckill.gmall.com
spring:
  cloud:
	gateway:
	  routes:
	    - id: gmall_seckill_route
		  uri: lb://gmall-seckill
		  predicates:
		  - Host=seckill.gmall.com

2.2.添加秒杀场次

后台管理系统:优惠营销 -> 每日秒杀

在这里插入图片描述

2.3.秒杀场次关联秒杀商品

在这里插入图片描述

2.4.秒杀商品定时上架

  • 定时任务扫描最近三天需要上架的秒杀商品
  • 库存预热,让秒杀商品缓存到Redis中

SeckillSkuScheduled

package com.atguigu.gmall.seckill.scheduled;

import com.atguigu.gmall.seckill.service.SeckillService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;

import java.util.concurrent.TimeUnit;

/**
 * 秒杀商品定时上架 {@link SeckillSkuScheduled}
 *  每天凌晨3点,上架最近三天需要秒杀商品
 *  当天 00:00:00 - 23:59:59
 *  明天 00:00:00 - 23:59:59
 *  后天 00:00:00 - 23:59:59
 *
 * @author zhangwen
 * @email: 1466787185@qq.com
 */
@Slf4j
@Service
public class SeckillSkuScheduled {

    private final String UPLOAD_LOCK = "seckill:upload:lock";

    @Autowired
    private SeckillService seckillService;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 秒杀商品定时上架
     *
     * 幂等处理:
     *  - 1.分布式锁
     *  - 2.缓存数据时判断是否已经存在key
     *      - key不存在就缓存
     *      - key存在不做处理
     */
    @Scheduled(cron = "0 0 3 * * ?")
    public void uploadSeckillSkuLast3Days(){
        log.info("秒杀商品定时上架...");
        // 幂等处理,分布式锁
        // 锁的业务执行完成,状态已经更新完成,释放锁以后,其他人获取到的就是最新的状态
        RLock lock = redissonClient.getLock(UPLOAD_LOCK);
        lock.lock(10, TimeUnit.SECONDS);
        try {
            seckillService.uploadSeckillSkuLast3Days();
        } finally {
            lock.unlock();
        }
    }
}

SeckillServcieImpl

	/**
     * 上架最近三天的秒杀商品
     */
    @Override
    public void uploadSeckillSkuLast3Days() {
        // 扫描最近三天需要参与的秒杀活动与商品信息
        R r = couponFeignService.getLast3DaySession();
        if (r.getCode() == 0) {
            // 需要上架的商品
            List<SeckillSessionsWithSkusVO> sessions = r.getData("data",
                    new TypeReference<List<SeckillSessionsWithSkusVO>>() {
            });
            // 缓存秒杀活动信息
            saveSessions(sessions);
            // 缓存秒杀活动关联的商品信息
            saveSessionSkus(sessions);
        } else {
            log.error("远程调用 gmall-coupon 获取秒杀活动失败");
        }
    }
    
    /**
     * 缓存秒杀活动信息
     * @param sessions
     */
    private void saveSessions(List<SeckillSessionsWithSkusVO> sessions) {
        if (sessions != null && sessions.size() > 0) {
            sessions.stream().forEach(session -> {
                Long startTime = session.getStartTime().getTime();
                long endTime = session.getEndTime().getTime();
                String key = SESSIONS_CACHE_PREFIX + startTime + "_" + endTime;
                Boolean hasKey = redisTemplate.hasKey(key);
                if (!hasKey) {
                    // 缓存秒杀活动信息
                    List<String> skuIds = session.getRelationSkus().stream()
                            .map(item -> item.getPromotionSessionId() + "_" + item.getSkuId())
                            .collect(Collectors.toList());
                    redisTemplate.opsForList().leftPushAll(key, skuIds);
                }
            });
        }
    }

    /**
     * 缓存秒杀活动关联的商品信息
     * @param sessions
     */
    private void saveSessionSkus(List<SeckillSessionsWithSkusVO> sessions){
        if (sessions != null && sessions.size() > 0) {
            sessions.stream().forEach(session -> {
                BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SECKILL_SKU_CACHE_PREFIX);
                session.getRelationSkus().stream().forEach(seckillSkuVO -> {
                    String hashKey = seckillSkuVO.getPromotionSessionId() + "_" + seckillSkuVO.getSkuId();
                    if (!hashOps.hasKey(hashKey)) {
                        // 缓存商品
                        SeckillSkuRedisTO redisTO = new SeckillSkuRedisTO();
                        // 1.sku基本信息
                        R r = productFeignService.getSkuInfo(seckillSkuVO.getSkuId());
                        if (r.getCode() == 0) {
                            SkuInfoVO skuInfo = r.getData("skuInfo", new TypeReference<SkuInfoVO>() {
                            });
                            redisTO.setSkuInfo(skuInfo);
                        }

                        // 2.sku秒杀信息
                        BeanUtils.copyProperties(seckillSkuVO, redisTO);

                        // 3.设置当前商品秒杀时间
                        redisTO.setStartTime(session.getStartTime().getTime());
                        redisTO.setEndTime(session.getEndTime().getTime());

                        // 4.设置随机码,防止恶意攻击
                        String token = UUID.randomUUID().toString().replace("-", "");
                        redisTO.setRandomCode(token);

                        String json = JsonUtils.objectToJson(redisTO);
                        hashOps.put(hashKey, json);
**加粗样式**
                        // 5.使用秒杀商品库存作为分布式的信号量,限流
                        RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + token);
                        semaphore.trySetPermits(seckillSkuVO.getSeckillCount().intValue());
                    }
                });
            });
        }
    }

2.5.展示秒杀商品(首页)

index.html

//获取当前秒杀场次商品
$.get('http://seckill.gmall.com/currentSeckillSkus', function(resp){
	if(resp.data.length > 0){
		resp.data.forEach(function(item){
			$('<li onclick="toItemPage('+item.skuId+')"> </li>')
				.append($('<img src="'+item.skuInfo.skuDefaultImg+'"
				style="width: 130px;height: 130px">'))
				.append($('<p>'+item.skuInfo.skuTitle+'</p>'))
				.append($('<span>'+item.seckillPrice+'</span>'))
				.append($('<s>'+item.skuInfo.price+'</s>'))
				.appendTo($('#seckillSkus'))
		})
	}
})

function toItemPage(skuId){
	location.href = `http://item.gmall.com/${skuId}.html`
}

2.6.商品秒杀预告(详情页)

SkuInfoServiceImpl

   /**
    * 商品详情
    * @param skuId
    * @return
    */
   @Override
   public SkuItemVO item(Long skuId) throws Exception {
       SkuItemVO skuItemVO = new SkuItemVO();

       // 异步编排
       CompletableFuture<SkuInfoEntity> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
           // 1.sku基本信息 pms_sku_info
           SkuInfoEntity skuInfo = getById(skuId);
           skuItemVO.setSkuInfo(skuInfo);
           return skuInfo;
       }, executor);

       CompletableFuture<Void> saleAttrFuture = skuInfoFuture.thenAcceptAsync((res) -> {
           // 2.spu销售属性组合
           List<SkuSaleAttrVO> saleAttrs = skuSaleAttrValueService.getSaleAttrsBySpuId(res.getSpuId());
           skuItemVO.setSaleAttrs(saleAttrs);
       }, executor);

       CompletableFuture<Void> descFuture = skuInfoFuture.thenAcceptAsync((res) -> {
           // 3.spu商品介绍
           SpuInfoDescEntity spuInfoDescEntity = spuInfoDescService.getById(res.getSpuId());
           skuItemVO.setSpuDesc(spuInfoDescEntity);
       }, executor);

       CompletableFuture<Void> baseAttrFuture = skuInfoFuture.thenAcceptAsync((res) -> {
           // 4.spu规格参数
           List<SpuAttrGroupVO> groupAttrs = attrGroupService.getAttrGroupWithAttrsBySpuId(
                   res.getCatalogId(), res.getSpuId());
           skuItemVO.setGroupAttrs(groupAttrs);
       }, executor);

       CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
           // 5.sku图片信息 pms_sku_images
           List<SkuImagesEntity> images = skuImagesService.getImagesBySkuId(skuId);
           skuItemVO.setImages(images);
       }, executor);

       CompletableFuture<Void> seckillFuture = CompletableFuture.runAsync(() -> {
           // 查询当前sku是否参与秒杀优惠
           R r = seckillFeignService.getSkuSeckillInfo(skuId);
           if (r.getCode() == 0) {
               SeckillSkuVO seckillSkuVO = r.getData("data", new TypeReference<SeckillSkuVO>() {
               });
               skuItemVO.setSeckillInfo(seckillSkuVO);
           } else {
               log.error("远程调用 gmall-seckill 获取商品秒杀信息失败");
           }
       }, executor);


       // 等待所有任务执行完
       CompletableFuture.allOf(saleAttrFuture, descFuture, baseAttrFuture, imageFuture, seckillFuture).get();

       // TODO 查询库存
       skuItemVO.setHasStock(true);

       return skuItemVO;
   }

item.html

<li th:if="${skuItemVO.seckillInfo!=null}" style="color: red">
	<span th:if="${#dates.createNow().getTime() <skuItemVO.seckillInfo.startTime}">
		商品将会在[[${#dates.format(new java.util.Date(skuItemVO.seckillInfo.startTime), 'yyyy-MM-dd HH:mm:ss')}]]进行秒杀
	</span>
	<span th:if="${#dates.createNow().getTime() >= skuItemVO.seckillInfo.startTime && #dates.createNow().getTime() <= skuItemVO.seckillInfo.endTime}">
	    秒杀价:[[${#numbers.formatDecimal(skuItemVO.seckillInfo.seckillPrice,1,2)}]]
	</span>
</li>
<div class="box-btns-two" th:if="${skuItemVO.seckillInfo!=null && (#dates.createNow().getTime() >= skuItemVO.seckillInfo.startTime && #dates.createNow().getTime() <= skuItemVO.seckillInfo.endTime)}">
	<a href="#" id="toSeckill" th:attr="skuId=${skuItemVO.skuInfo.skuId},sessionId=${skuItemVO.seckillInfo.promotionSessionId},code=${skuItemVO.seckillInfo.randomCode}">立即抢购</a>
</div>
<div class="box-btns-two" th:if="${skuItemVO.seckillInfo==null || (#dates.createNow().getTime() < skuItemVO.seckillInfo.startTime || #dates.createNow().getTime() > skuItemVO.seckillInfo.endTime)}">
	<a href="#" id="addToCart" th:attr="skuId=${skuItemVO.skuInfo.skuId}">加入购物车</a>
</div>

<script>
// 立即抢购
	$('#toSeckill').click(function(){
		let isLogin = [[${session.loginUser!=null}]]
		if(isLogin){
			let killId = $(this).attr('sessionId') + "_" + $(this).attr('skuId')
			let code = $(this).attr('code')
			let num = $('#num').val()
			location.href = `http://seckill.gmall.com/seckill?killId=${killId}&key=${code}&num=${num}`
		} else {
			alert('秒杀商品,请先登录!')
			location.href = 'http://auth.gmall.com/login.html'
		}

		return false
	})
</script>

2.7.秒杀核心业务实现

2.7.1.核心流程

在这里插入图片描述

2.7.2.秒杀业务

SeckillController

/**
 * 秒杀
 * @param killId sessionId_skuId
 * @param key 商品随机码
 * @param num 秒杀数量
 * @return
 */
@GetMapping("/seckill")
public String seckill(@RequestParam("killId") String killId,
                      @RequestParam("key") String key,
                      @RequestParam("num") Integer num,
                      Model model){
    String orderSn = seckillService.seckill(killId, key, num);
    model.addAttribute("orderSn", orderSn);

    return "success";
}

SeckillServcieImpl

/**
 * 秒杀
 * @param killId 秒杀场次id_商品id
 * @param key 随机码
 * @param num 商品数量
 * @return
 */
@Override
public String seckill(String killId, String key, Integer num) {
    MemberVO memberVO = LoginInterceptor.threadLocal.get();

    // 获取当前秒杀商品信息
    BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SECKILL_SKU_CACHE_PREFIX);
    String json = hashOps.get(killId);
    if (StringUtils.isEmpty(json)) {
        return null;
    }

    SeckillSkuRedisTO redisTO = JsonUtils.jsonToPojo(json, SeckillSkuRedisTO.class);

    // 校验合法性
    // 1.校验时间
    long currentTime = System.currentTimeMillis();
    if (currentTime >= redisTO.getStartTime() && currentTime <= redisTO.getEndTime()) {
        // 2.校验随机码和商品id
        String randomCode = redisTO.getRandomCode();
        String skuId = redisTO.getPromotionSessionId() + "_" + redisTO.getSkuId();
        if (randomCode.equals(key) && skuId.equals(killId)) {
            // 3.校验购物数量
            if (num <= redisTO.getSeckillLimit().intValue()) {
                // 4.验证是否购买过
                // 幂等性,只要秒杀成功,就去redis占位 SETNX,userId_sessionId_skuId
                String redisKey = memberVO.getId() + "_" + skuId;
                Long ttl = redisTO.getEndTime() - redisTO.getStartTime();
                Boolean ifAbsent = redisTemplate.opsForValue()
                        .setIfAbsent(redisKey, num.toString(), ttl, TimeUnit.MILLISECONDS);
                if (ifAbsent) {
                    // 占位成功,说明没有购买过
                    RSemaphore semaphore = redissonClient.getSemaphore(SKU_STOCK_SEMAPHORE + randomCode);
                    // 快速尝试
                    boolean acquire = semaphore.tryAcquire(num);
                    if (acquire) {
                        // 秒杀成功,快速下单,发消息到MQ
                        String orderSn = IdWorker.getTimeId();
                        SeckillOrderTO seckillOrderTO = new SeckillOrderTO();
                        seckillOrderTO.setOrderSn(orderSn);
                        seckillOrderTO.setMemberId(memberVO.getId());
                        seckillOrderTO.setNum(num);
                        seckillOrderTO.setPromotionSessionId(redisTO.getPromotionSessionId());
                        seckillOrderTO.setSkuId(redisTO.getSkuId());
                        seckillOrderTO.setSeckillPrice(redisTO.getSeckillPrice());

                        rabbitTemplate.convertAndSend(SECKILL_ORDER_EVENT_EXCHANGE,
                                SECKILL_ORDER_QUEUE_ROUTING_KEY, seckillOrderTO);

                        return  orderSn;
                    }
                }
            }
        }
    }

    return null;
}

2.7.3.秒杀响应页面

  • 秒杀完成后,跳转到success页面,显示秒杀结果
  • 秒杀成功,则自动跳转到支付页面进行支付
  • 秒杀成功,订单服务消费秒杀消息,进行订单处理

success.html

   <div class="main">
       <div class="success-wrap">
            <div class="w" id="result">
               <div class="m succeed-box">
                   <div th:if="${orderSn!=null}" class="mc success-cont">
                        <h3 style="margin: 20px 0px">恭喜,秒杀成功,订单号:[[${orderSn}]]</h3>
                        <p>
                            <a th:href="'http://order.gmall.com/payOrder?orderSn='+${orderSn}" id="pay">
                                正在准备订单数据,请您耐心等待 <span id="payTime">10</span> 秒后进行支付!
                            </a>
                        </p>
                    </div>
                    <div th:if="${orderSn==null}" class="mc success-cont">
                        <h3 style="margin: 20px 0px">手气不佳,秒杀失败,下次再来!</h3>
                    </div>
                </div>
            </div>
        </div>
    </div>

    <script type="text/javascript">
        // 倒计时跳转支付
        $(function () {
            let href = $('#pay').attr('href')
            $('#pay').removeAttr('href')
            $('#pay').attr('disabled', true)
            let orderSn = [[${orderSn}]]
            let count = 10
            let countdown = setInterval(CountDown, 1000)
            function CountDown() {
                $("#payTime").text(count)
                if (count == 0) {
                    clearInterval(countdown)
                    $('#pay').text('支付订单')
                    $('#pay').attr('href', href)
                    $('#pay').removeAttr('disabled')
                }
                count--;
            }
        });
    </script>


3.定时任务

  • Quartz:http://www.quartz-scheduler.org/
  • Timer
  • Spring @Scheduled

3.1.cron表达式

语法: 秒 分 时 日 月 周 年 (年,Spring不支持)

http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

在这里插入图片描述
特殊字符:

  1. , :枚举
    corn=“7,9,23 * * * * ?” :任意时刻的 7,9,23 秒启动这个任务
  2. - :范围
    corn=“7-20 * * * * ?” :任意时刻的7-20秒之间,每秒启动一次
  3. *:任意
    指定位置的任意时刻都可以
  4. / :步长
    corn=“7/5 * * * * ?” :第7秒启动,每5秒一次
    corn=“*/5 * * * * ?” :任意秒启动,每5秒一次
  5. ? :出现在日和周几的位置,为了防止日和周冲突,在日和周上如果要写通配符使用?
    corn=" * * * 1 * ?" :每月的1号,而且必须是周二,然后启动这个任务
  6. L :出现在日和周的位置
    Last:最后一个
    corn=" * * * ? * 3L" :每月的最后一个周二
  7. W
    Work Day:工作日
    cron=“* * * W * ?” :每个月的工作日触发
    cron=“* * * LW * ?” :每个月的最后一个工作日触发
  8. #:第几个
    cron=“* * * ? * 5#2” :每个月的第2个周4

3.2.cron示例

http://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

在这里插入图片描述

3.3.Spring Boot整合定时任务

@Slf4j
@Component
@EnableScheduling //开启定时任务
public class MyScheduled {
	/**
	 * 1.Spring中6位组成,不允许第7位的年
	 * 2.在周几的位置,1-7表示周一到周日,和quartz有区别(1-7表示周日到周六)
	 */
	@Scheduled(cron = "* * * ? * 5")
	public void hello(){
		log.info("hello ю ")
	}
}

注意:

  • Spring中6位组成,不允许第7位的年

  • 在周几的位置,1-7表示周一到周日,和quartz有区别(1-7表示周日到周六)

  • 解决定时任务不阻塞,默认是阻塞的

    • 可以让业务方法以异步的方式运行,自己提交到线程池
@Scheduled(cron = "* * * ? * 5")
public void hello(){
	log.info("hello ю ");
	//异步方式运行
	CompletableFuture.runAsync(() Ѷ ۏ {
		xxxServcie.method();
	});
}
    • 让定时任务异步执行
@Slf4j
@Component
@EnableAsync //开启异步任务
@EnableScheduling //开启定时任务
public class MyScheduled {
	/**
	 * 1.Spring中6位组成,不允许第7位的年
	 * 2.在周几的位置,1-7表示周一到周日,和quartz有区别(1-7表示周日到周六)
	 */
	@Async
	@Scheduled(cron = "* * * ? * 5")
	public void hello(){
		log.info("hello ... ");
	}
}

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

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

相关文章

web-文件包含

产生原因&#xff1a; 开发人员都希望代码更加灵活&#xff0c;所以通常会将被包含的文件设置为变量&#xff0c;用来进行动态调用。正是这种灵活性&#xff0c;从而导致客户端可以调用一个恶意文件&#xff0c;造成文件包含漏洞。 实际上被包含文件可以是任意格式的&#xff0…

【数据结构】带你轻松拿捏顺序表(内附源码)

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello,米娜桑们&#xff0c;这里是君兮_&#xff0c;今天正式开始开新坑啦&#xff01;在接下来的这一个月来我会逐步带大家了解初阶数据结构的知识&#xff0c;如果是你主修的是计算机专业数据结构的重要性不言而喻&…

深度学习论文: Q-YOLO: Efficient Inference for Real-time Object Detection及其PyTorch实现

深度学习论文: Q-YOLO: Efficient Inference for Real-time Object Detection及其PyTorch实现 Q-YOLO: Efficient Inference for Real-time Object Detection PDF: https://arxiv.org/pdf/2307.04816.pdf PyTorch代码: https://github.com/shanglianlm0525/CvPytorch PyTorch代…

LeetCode 918. Maximum Sum Circular Subarray【数组,动态规划】中等

本文属于「征服LeetCode」系列文章之一&#xff0c;这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁&#xff0c;本系列将至少持续到刷完所有无锁题之日为止&#xff1b;由于LeetCode还在不断地创建新题&#xff0c;本系列的终止日期可能是永远。在这一系列刷题文章…

215.数组中的第K个最大元素-C++

题目来源&#xff1a;力扣 题目描述&#xff1a; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 k 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现时间复杂度为 O(n) 的算法…

Java书签 #解锁MyBatis的4种批量插入方式及ID返回姿势

1. 今日书签 项目开发中&#xff0c;我们经常会用到单条插入和批量插入。但是实际情况可能是&#xff0c;项目初期由于种种原因&#xff0c;在业务各处直接使用单条插入SQL进行开发&#xff08;未开启批处理&#xff09;&#xff0c;在后面的迭代中&#xff0c;系统性能问题渐…

Spring Boot 集成 Redis 三种模式实践汇总

背景 项目的某个模块集成了 SpringBoot Redis 包&#xff0c;客户端使用 Lettuce&#xff0c;Redis 测试环境单机模式。但是现场反馈的 Redis 环境是集群&#xff0c;如果简单的修改 spring.redis 配置为集群的配置信息&#xff0c;程序能否能无缝衔接呢&#xff1f; 本文记录…

Android Studio 的版本控制Git

Android Studio 的版本控制Git。 Git 是最流行的版本控制工具&#xff0c;本文介绍其在安卓开发环境Android Studio下的使用。 本文参考链接是&#xff1a;https://learntodroid.com/how-to-use-git-and-github-in-android-studio/ 一&#xff1a;Android Studio 中设置Git …

智安网络|实现数据安全:探索数据动态脱敏的落地策略

在当今数字化时代&#xff0c;数据安全成为企业和组织管理中的头等大事。然而&#xff0c;数据共享和数据大规模处理的需求也日益增长&#xff0c;这就需要在数据传输和存储过程中采取措施来保护用户的隐私。数据动态脱敏技术应运而生&#xff0c;为解决数据隐私和保护的问题提…

【雕爷学编程】Arduino动手做(93)--- 0.96寸OLED液晶屏模块3

37款传感器与执行器的提法&#xff0c;在网络上广泛流传&#xff0c;其实Arduino能够兼容的传感器模块肯定是不止这37种的。鉴于本人手头积累了一些传感器和执行器模块&#xff0c;依照实践出真知&#xff08;一定要动手做&#xff09;的理念&#xff0c;以学习和交流为目的&am…

Doris(二) -通过外部表同步数据

前言 参考网址 1.官网 2.ODBC External Table Of Doris 3.Apache doris ODBC外表使用方式 第一步 创建 RESOURCE DROP RESOURCE IF EXISTS mysql_test_odbc; CREATE EXTERNAL RESOURCE mysql_test_odbc PROPERTIES ( "type" "odbc_catalog", "…

《JeecgBoot系列》 表单实现指定列导出Excel“合并单元格“

表单"合计"列导出Excel合并单元格 注解&#xff1a; Excel 参数&#xff1a;mergeVertical&#xff0c;参数设置为Boolean类型&#xff0c;默认为false。当设置为true时&#xff0c;可以纵向合并内容相同的单元格 1.需求 介绍&#xff1a;每个类别下有多个型号&…

linux免交互

免交互 指不需要人为控制就可以完成的自动化操作。 shell脚本和免交互是一个概念&#xff0c;但是有两种写法。shell脚本基于bash。 here Document 免交互 它是一种标准输入&#xff0c;只能接收正确的命令&#xff0c;它主要是使用i/o重定向的方式将命令的列表提供给交互式…

BFS()

目录 多源BFS 矩阵距离 最小步数模型 魔板 八数码 双端队列广搜 电路维修 双向广搜 字串变换 A* 第K短路 多源BFS 单源BFS是求一个点到起点的最短距离 多源BFS是求有很多个起点&#xff0c;某一点到离它最近一个起点的距离 矩阵距离 给定一个 N 行M 列的 01矩阵…

【每日一题】—— C. Tiles Comeback (Codeforces Round 888 (Div. 3))

&#x1f30f;博客主页&#xff1a;PH_modest的博客主页 &#x1f6a9;当前专栏&#xff1a;每日一题 &#x1f48c;其他专栏&#xff1a; &#x1f534; 每日反刍 &#x1f7e1; C跬步积累 &#x1f7e2; C语言跬步积累 &#x1f308;座右铭&#xff1a;广积粮&#xff0c;缓称…

FTP客户端登录报错:Login failed

FTP客户端登录报错&#xff1a;Login failed 是selinux的问题&#xff0c;一般用户无法登录用户主目录 [rootchenshuyi ~]# setsebool -P tftp_home_dir 1

Filebeat学习笔记

Filebeat基本概念 简介 Filebeat是一种轻量级日志采集器&#xff0c;内置有多种模块&#xff08;auditd、Apache、Nginx、System、MySQL等&#xff09;&#xff0c;针对常见格式的日志大大简化收集、解析和可视化过程&#xff0c;只需一条命令即可。之所以能实现这一点&#…

【Go语言】Golang保姆级入门教程 Go初学者介绍chapter1

Golang 开山篇 Golang的学习方向 区块链研发工程师&#xff1a; 去中心化 虚拟货币 金融 Go服务器端、游戏软件工程师 &#xff1a; C C 处理日志 数据打包 文件系统 数据处理 很厉害 处理大并发 Golang分布式、云计算软件工程师&#xff1a;盛大云 cdn 京东 消息推送 分布式文…

集睿致远推出CS5466多功能拓展坞方案:支持DP1.4、HDMI2.1视频8K输出

ASL新推出的 CS5466是一款Type-C/DP1.4转HDMI2.1的显示协议转换芯片,&#xff0c;它通过类型C/显示端口链路接收视频和音 频流&#xff0c;并转换为支持TMDS或FRL输出信令。DP接收器支持81.Gbp s链路速率。HDMI输出端口可以作为TMDS或FRL发射机工作。FRL发射机符合HDMI 2.1规范…

08综合评价作业

某核心企业需要在6个待选的零部件供应商中选择一个合作伙伴&#xff0c;各待选供应商有关数据如表1所列&#xff0c;试从中选择一个最优供应商(理想解法) 评价指标产品质量产品价格/元地理位置/km售后服务/h技术水平经济效益供应能力/件市场影响度交货情况10.83326213.20.20.1…