当前位置: 首页 > news >正文

尚品汇-秒杀下单实现-页面轮询查询订单状态(五十三)

目录:

(1)整合秒杀业务

(2)秒杀下单

(3)秒杀下单监听

(4)页面轮询接口

(1)整合秒杀业务

秒杀的主要目的就是获取一个下单资格,拥有下单资格就可以去下单支付,获取下单资格后的流程就与正常下单流程一样,只是没有购物车这一 步,总结起来就是,秒杀根据库存获取下单资格,拥有下单资格进入下单页面(选择地址,支付方式,提交订单,然后支付订单)

步骤:

  1. 校验下单码,只有正确获得下单码的请求才是合法请求
  2. 校验状态位state

State为null,说明非法请求;

State0说明已经售罄;

State为1,说明可以抢购

状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力

  1. 前面条件都成立,将秒杀用户加入队列,然后直接返回
  2. 前端轮询秒杀状态,查询秒杀结果

(2)秒杀下单

添加mq常量MqConst

/*** 秒杀*/
public static final String EXCHANGE_DIRECT_SECKILL_USER = "exchange.direct.seckill.user";
public static final String ROUTING_SECKILL_USER = "seckill.user";
//队列
public static final String QUEUE_SECKILL_USER  = "queue.seckill.user";

定义实体UserRecode

记录哪个用户要购买哪个商品!

@Data
public class UserRecode implements Serializable {private static final long serialVersionUID = 1L;private Long skuId;private String userId;
}

编写控制器SeckillGoodsApiController

@Autowired
private RabbitService rabbitService;
/*** 根据用户和商品ID实现秒杀下单* @param skuId* @return*/
@PostMapping("auth/seckillOrder/{skuId}")
public Result seckillOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) throws Exception {//校验下单码(抢购码规则可以自定义)String userId = AuthContextHolder.getUserId(request);String skuIdStr = request.getParameter("skuIdStr");if (!skuIdStr.equals(MD5.encrypt(userId))) {//请求不合法return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);}//校验状态位//产品标识, 1:可以秒杀 0:秒杀结束String state = (String) CacheHelper.get(skuId.toString());if (StringUtils.isEmpty(state)) {//请求不合法return Result.build(null, ResultCodeEnum.SECKILL_ILLEGAL);}if ("1".equals(state)) {//用户记录UserRecode userRecode = new UserRecode();userRecode.setUserId(userId);userRecode.setSkuId(skuId);//发送消息rabbitService.sendMessage(MqConst.EXCHANGE_DIRECT_SECKILL_USER, MqConst.ROUTING_SECKILL_USER, userRecode);} else {//已售罄return Result.build(null, ResultCodeEnum.SECKILL_FINISH);}return Result.ok();
}

全局统一返回结果类: 

package com.atguigu.gmall.common.result;import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;/*** 全局统一返回结果类**/
@Data
@ApiModel(value = "全局统一返回结果")
public class Result<T> {@ApiModelProperty(value = "返回码")private Integer code;@ApiModelProperty(value = "返回消息")private String message;@ApiModelProperty(value = "返回数据")private T data;public Result(){}// 返回数据protected static <T> Result<T> build(T data) {Result<T> result = new Result<T>();if (data != null)result.setData(data);return result;}public static <T> Result<T> build(T body, ResultCodeEnum resultCodeEnum) {Result<T> result = build(body);result.setCode(resultCodeEnum.getCode());result.setMessage(resultCodeEnum.getMessage());return result;}public static<T> Result<T> ok(){return Result.ok(null);}/*** 操作成功* @param data* @param <T>* @return*/public static<T> Result<T> ok(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.SUCCESS);}public static<T> Result<T> fail(){return Result.fail(null);}/*** 操作失败* @param data* @param <T>* @return*/public static<T> Result<T> fail(T data){Result<T> result = build(data);return build(data, ResultCodeEnum.FAIL);}public Result<T> message(String msg){this.setMessage(msg);return this;}public Result<T> code(Integer code){this.setCode(code);return this;}public boolean isOk() {if(this.getCode().intValue() == ResultCodeEnum.SUCCESS.getCode().intValue()) {return true;}return false;}
}

统一返回结果状态信息类:

package com.atguigu.gmall.common.result;import lombok.Getter;/*** 统一返回结果状态信息类**/
@Getter
public enum ResultCodeEnum {SUCCESS(200,"成功"),FAIL(201, "失败"),SERVICE_ERROR(2012, "服务异常"),ILLEGAL_REQUEST( 204, "非法请求"),PAY_RUN(205, "支付中"),LOGIN_AUTH(208, "未登陆"),PERMISSION(209, "没有权限"),SECKILL_NO_START(210, "秒杀还没开始"),SECKILL_RUN(211, "正在排队中"),SECKILL_NO_PAY_ORDER(212, "您有未支付的订单"),SECKILL_FINISH(213, "已售罄"),SECKILL_END(214, "秒杀已结束"),SECKILL_SUCCESS(215, "抢单成功"),SECKILL_FAIL(216, "抢单失败"),SECKILL_ILLEGAL(217, "请求不合法"),SECKILL_ORDER_SUCCESS(218, "下单成功"),COUPON_GET(220, "优惠券已经领取"),COUPON_LIMIT_GET(221, "优惠券已发放完毕"),;private Integer code;private String message;private ResultCodeEnum(Integer code, String message) {this.code = code;this.message = message;}
}

(3)秒杀下单监听

思路:

  1. 首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了;
  2. 判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,怎么控制呢?很简单,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false,直接返回,过期时间可以根据业务自定义,这样用户这一段咋们就控制注了
  3. 获取队列中的商品,如果能够获取,则商品有库存,可以下单。如果获取的商品id为空,则商品售罄,商品售罄我们要第一时间通知兄弟节点,更新状态位,所以在这里发送redis广播
  4. 将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功
  5. 秒杀成功要更新库存

SeckillReceiver添加监听方法

@Autowired
private SeckillGoodsService seckillGoodsService;//  监听用户与商品的消息!
@SneakyThrows
@RabbitListener(bindings = @QueueBinding(value = @Queue(value = MqConst.QUEUE_SECKILL_USER,durable = "true",autoDelete = "false"),exchange = @Exchange(value = MqConst.EXCHANGE_DIRECT_SECKILL_USER),key = {MqConst.ROUTING_SECKILL_USER}
))
public void seckillUser(UserRecode userRecode,Message message,Channel channel){try {//  判断接收过来的数据if (userRecode!=null){//  预下单处理!seckillGoodsService.seckillOrder(userRecode.getSkuId(),userRecode.getUserId());}} catch (Exception e) {e.printStackTrace();}//  手动确认channel.basicAck(message.getMessageProperties().getDeliveryTag(),false);
}

预下单接口SeckillGoodsService接口


/*** 根据用户和商品ID实现秒杀下单* @param skuId* @param userId*/
void seckillOrder(Long skuId, String userId);

秒杀订单实体类

package com.atguigu.gmall.model.activity;@Data
public class OrderRecode implements Serializable {private static final long serialVersionUID = 1L;private String userId;private SeckillGoods seckillGoods;private Integer num;private String orderStr;
}

 实现类

/**** 创建订单* @param skuId* @param userId*/
@Override
public void seckillOrder(Long skuId, String userId) {//产品状态位, 1:可以秒杀 0:秒杀结束String state = (String) CacheHelper.get(skuId.toString());if("0".equals(state)) {//已售罄return;}//判断用户是否下单boolean isExist = redisTemplate.opsForValue().setIfAbsent(RedisConst.SECKILL_USER + userId, skuId.toString(), RedisConst.SECKILL__TIMEOUT, TimeUnit.SECONDS);if (!isExist) {return;}//获取队列中的商品,取List中的一个,从右边出来一个,如果能够获取,则商品存在,可以下单String goodsId = (String) redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).rightPop();if (StringUtils.isEmpty(goodsId)) {//商品售罄,更新状态位  0位售罄状态  发布订阅消息,商品已经销售完了redisTemplate.convertAndSend("seckillpush", skuId+":0");//已售罄return;}//订单记录OrderRecode orderRecode = new OrderRecode();orderRecode.setUserId(userId);orderRecode.setSeckillGoods(this.getSeckillGoods(skuId));orderRecode.setNum(1);//生成订单单码orderRecode.setOrderStr(MD5.encrypt(userId+skuId));//订单数据存入ReidsredisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).put(orderRecode.getUserId(), orderRecode);//更新库存this.updateStockCount(orderRecode.getSeckillGoods().getSkuId());
}

更新库存

//  表示更新mysql -- redis 的库存数据!
public void updateStockCount(Long skuId) {//  加锁!Lock lock = new ReentrantLock();//  上锁lock.lock();try {//  获取到存储库存剩余数!//  key = seckill:stock:46String stockKey = RedisConst.SECKILL_STOCK_PREFIX + skuId;//  redisTemplate.opsForList().leftPush(key,seckillGoods.getSkuId());//获取库存//Long count = this.redisTemplate.boundListOps(RedisConst.SECKILL_STOCK_PREFIX + skuId).size();Long count = redisTemplate.boundListOps(stockKey).size();//  减少库存数!方式一减少压力!//if (count%2==0){//  开始更新数据!SeckillGoods seckillGoods = this.getSeckillGoods(skuId);//  赋值剩余库存数!seckillGoods.setStockCount(count.intValue());//  更新的数据库!seckillGoodsMapper.updateById(seckillGoods);//  更新缓存!redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).put(seckillGoods.getSkuId().toString(),seckillGoods);//}} finally {//  解锁!lock.unlock();}
}
 /*** 获取商品详情* @param skuId* @return*/@Overridepublic SeckillGoods getSeckillGoods(Long skuId) {return (SeckillGoods) this.redisTemplate.boundHashOps(RedisConst.SECKILL_GOODS).get(skuId.toString());}

 

减少一个:之前9个 

生成一个订单

用户买过之后生成一个用户的信息(防止多次下单)

(4)页面轮询接口

 

思路:

1.  判断用户是否在缓存中存在

 

2.  判断用户是否抢单成功

3.  判断用户是否下过订单

4.  判断状态位

接口

SeckillGoodsService接口
/**** 根据商品id与用户ID查看订单信息* @param skuId* @param userId* @return*/
Result checkOrder(Long skuId, String userId);

实现类

@Override
public Result checkOrder(Long skuId, String userId) {// 用户在缓存中存在,有机会秒杀到商品boolean isExist =redisTemplate.hasKey(RedisConst.SECKILL_USER + userId);if (isExist) {//判断用户是否正在排队//判断用户是否抢单成功boolean isHasKey = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).hasKey(userId);if (isHasKey) {//抢单成功  获取用户临时订单:说明还没有支付OrderRecode orderRecode = (OrderRecode) redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS).get(userId);// 秒杀成功! return Result.build(orderRecode, ResultCodeEnum.SECKILL_SUCCESS);}}//判断是否下单,查询总订单 seckill:orders:users userId OrderIdboolean isExistOrder = redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).hasKey(userId);if(isExistOrder) {String orderId = (String)redisTemplate.boundHashOps(RedisConst.SECKILL_ORDERS_USERS).get(userId);return Result.build(orderId, ResultCodeEnum.SECKILL_ORDER_SUCCESS);}String state = (String) CacheHelper.get(skuId.toString());if("0".equals(state)) {//已售罄 抢单失败return Result.build(null, ResultCodeEnum.SECKILL_FAIL);}//正在排队中return Result.build(null, ResultCodeEnum.SECKILL_RUN);
}

控制器

SeckillGoodsApiController
@GetMapping(value = "auth/checkOrder/{skuId}")
public Result checkOrder(@PathVariable("skuId") Long skuId, HttpServletRequest request) {//当前登录用户String userId = AuthContextHolder.getUserId(request);return seckillGoodsService.checkOrder(skuId, userId);
}

 

 

 

轮询排队页面

该页面有四种状态:

  1. 排队中
  2. 各种提示(非法、已售罄等)
  3. 抢购成功,去下单
  4. 抢购成功,已下单,显示我的订单

抢购成功,页面显示去下单,跳转下单确认页面

<div class="seckill_dev" v-if="show == 3">抢购成功&nbsp;&nbsp;<a href="/seckill/trade.html" target="_blank">去下单</a>
</div>

总结:

商品秒杀流程:

1.用户抢单的时候先会生成一个下单码,后面会先校验用户的下单码,只有正确获得下单码的请求才是合法请求,然后再校验状态位state,状态位是在内存中判断,效率极高,如果售罄,直接就返回了,不会给服务器造成太大压力,前面条件都成立,将秒杀用户加入队列,然后直接返回

2.监听队列,进行清单,首先判断产品状态位,我们前面不是已经判断过了吗?因为产品可能随时售罄,mq队列里面可能堆积了十万数据,但是已经售罄了,那么后续流程就没有必要再走了

然后判断用户是否已经下过订单,这个地方就是控制用户重复下单,同一个用户只能抢购一个下单资格,我们可以利用setnx控制用户,当用户第一次进来时,返回true,可以抢购,以后进入返回false

要控制库存数量,不能超卖,提供一种解决方案,那就我们在导入商品缓存数据时,同时将商品库存信息导入队列{list},利用redis队列的原子性,保证库存不超卖

然后将订单记录放入redis缓存,说明用户已经获得下单资格,秒杀成功,秒杀成功要更新库存(更新Mysql的库存和Redis中的库存)

3.前端页面轮训查询订单状态,判断用户是否抢单成功,下单了Redis生成临时订单数据(支付了会删除临时订单)抢单成功,去下单页面,判断用户是否下单查询总订单数据是否支付,下单了去我们订单页面,判断状态位,是否售罄

相关文章:

尚品汇-秒杀下单实现-页面轮询查询订单状态(五十三)

目录&#xff1a; &#xff08;1&#xff09;整合秒杀业务 &#xff08;2&#xff09;秒杀下单 &#xff08;3&#xff09;秒杀下单监听 &#xff08;4&#xff09;页面轮询接口 &#xff08;1&#xff09;整合秒杀业务 秒杀的主要目的就是获取一个下单资格&#xff0c;拥…...

2024年微电子与纳米技术国际研讨会(ICMN 2024) Microelectronics and Nanotechnology

文章目录 一、会议详情二、重要信息三、大会介绍四、出席嘉宾五、征稿主题六、咨询 一、会议详情 二、重要信息 大会官网&#xff1a;https://ais.cn/u/vEbMBz提交检索&#xff1a;EI Compendex、IEEE Xplore、Scopus大会时间&#xff1a;2024年9月20-22日地点&#xff1a;成都…...

2024最新版,人大赵鑫老师《大语言模型》新书pdf分享

本书主要面向希望系统学习大语言模型技术的读者&#xff0c;将重点突出核心概念与 算法&#xff0c;并且配以示例与代码&#xff08;伪代码&#xff09;帮助读者理解特定算法的实现逻辑。由于大语言模型技术的快速更迭&#xff0c;本书无法覆盖所有相关内容&#xff0c;旨在梳理…...

[Leetcode 543][Easy]-二叉树的直径-递归

目录 一、题目描述 二、整体思路 三、代码 一、题目描述 原题地址 二、整体思路 取一个结点的最大直径就是取一个结点的左子树最大深度右子树最大深度之和&#xff0c;因此可以定义一个递归函数&#xff0c;作用是取一个结点的最大直径。这个函数中还实现了求左子树最大深度…...

高级大数据开发学习路线指南

掌握大数据技术是一项系统性工程&#xff0c;涉及到广泛的技能和专业知识。为了帮助初学者构建坚实的基础&#xff0c;并逐步成长为大数据领域的专家&#xff0c;下面详细阐述了一条全面而深入的学习路线&#xff1a; 1. Java 编程基础 - 打造坚实的底层技能 关键知识点&…...

SpringBoot设置mysql的ssl连接

因工作需要&#xff0c;mysql连接需要开启ssl认证&#xff0c;本文主要讲述客户端如何配置ssl连接。 开发环境信息&#xff1a; SpringBoot&#xff1a; 2.0.5.RELEASE mysql-connector-java&#xff1a; 8.0.18 mysql version&#xff1a;8.0.18 一、检查服务端是否开启ssl认…...

2024-1.2.12-Android-Studio配置

本地博客: https://k1t0111.github.io/ K1T0 最近在做一些app方向的移动技术开发学习&#xff0c;但是由于AS的配置问题&#xff0c;市面上找不到最新的2024版本的AS的相关配置。笔者也是踩了很多坑&#xff0c;因此想写一篇文章记录一下最新的AS 2024 1.2.12的对应java环境的一…...

前端vue左侧树的一整套功能实现(一):vue2+vite封装v-resize指令,实现左侧树拖拽宽度和折叠展开

实现v-resize指令&#xff0c;具体以下功能&#xff1a; 指令接收宽度最大最小值&#xff0c;接收一个id用于localStorage存储拖拽宽度&#xff0c;接收padding拖拽时产生虚线拖拽&#xff0c;松开鼠标再进行元素宽度调整折叠展开图标使用本地图片 封装一个vite下使用本地图片…...

本地部署huggingface模型,建立自己的翻译应用

过去&#xff0c;我们使用翻译接口时&#xff0c;往往都是使用百度等的接口&#xff0c;每天有一定量的免费额度。今天为大家介绍一个可以进行翻译的模型&#xff0c;具备英译中、中译英的能力。并且在这个过程中&#xff0c;向大家介绍一个如何在本地部署模型。在之前的”五天…...

基于python+django+vue的在线学习资源推送系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…...

.Net Gacutil工具(全局程序集缓存工具)使用教程

GAC介绍&#xff1a; GAC&#xff08;Global Assembly Cache&#xff09;全局程序集缓存&#xff0c;是用于存放.Net应用程序共享的程序集。 像平常我们在Visual Studio中引用系统程序集时&#xff0c;这些程序集便来自于GAC。 GAC默认位置为&#xff1a;%windir%\Microsoft…...

安卓13修改设置设备型号和设备名称分析与更改-android13设置设备型号和设备名称更改

总纲 android13 rom 开发总纲说明 文章目录 1.前言2.问题分析3.代码分析4.代码修改5.编译6.彩蛋1.前言 用户要定制一些系统显示的设备型号和设备名称,这就需要我们分析设置里面的相关信息来找到对应的位置进行修改了。 2.问题分析 像这种信息要么是config.xml里面写死了,要…...

AI健身体能测试之基于paddlehub实现引体向上计数个数统计

【引体向上计数】 本项目使用PaddleHub中的骨骼检测模型human_pose_estimation_resnet50_mpii&#xff0c;进行人体运动分析&#xff0c;实现对引体向上的自动计数。 1. 项目介绍 人体运动分析是近几年许多领域研究的热点问题。在学科的交叉研究上&#xff0c;人体运动分析涉…...

Redis常见报错及解决方法总结

Redis常见报错及解决方法总结 Redis作为高效的内存数据库&#xff0c;在实际使用过程中不可避免会遇到一些问题和报错。为了帮助大家更好地应对这些问题&#xff0c;我将常见的Redis报错及其解决方法进行总结&#xff0c;并提供具体的操作步骤。 1. Connection Refused 错误…...

【TabBar嵌套Navigation案例-JSON的简单使用 Objective-C语言】

一、JSON的简单使用 1.我们先来看一下示例程序里边,产品推荐页面, 在我们这个产品推荐页面里面, 它是一个CollectionViewController,注册的是一个xib的一个类型,xib显示这个cell,叫做item,然后,这个邮箱大师啊,包括这个图标,以及这些东西,都是从哪儿来的呢,都是从…...

通过鼠标移动来调整两个盒子的宽度(响应式)

DOM结构&#xff1a; <div class"courer"> // 外层盒子<div class"dividing-line" title"拖动"></div> // 拖动的那个线<div class"course-title-box"> // 第一个盒子<div class"course-content-…...

React Zustand状态管理库的使用

Zustand 是一个轻量级的状态管理库&#xff0c;适用于 React 和浏览器环境中的状态管理需求。它由 Vercel 开发并维护&#xff0c;旨在提供一种简单的方式来管理和共享状态。Zustand 的设计理念是尽可能简化状态管理&#xff0c;使其更加直观和易于使用。 Zustand 官网点击跳转…...

pyrosetta MoveMap介绍

在 PyRosetta 中,MoveMap 是一个非常重要的类,用来控制蛋白质分子中哪些部分可以在某些操作(如折叠、旋转、优化等)中被移动。MoveMap 允许你精确地指定哪些残基、键角或原子可以进行特定的运动,从而帮助你在蛋白质结构预测、优化和设计中进行灵活的控制。 MoveMap 的功能…...

在线安全干货|如何更改IP地址?

更改IP地址是一个常见的需求&#xff0c;无论是为了保护个人隐私、绕过地理限制还是进行商业数据分析。不同的IP更改方法适用于不同的需求和环境。但请注意&#xff0c;更改IP地址应在合法场景下进行&#xff0c;无论使用什么方法&#xff0c;都需要在符合当地网络安全法律法规…...

【C++】【网络】【Linux系统编程】单例模式,加锁封装TCP/IP协议套接字

目录 引言 获取套接字 绑定套接字 表明允许监听 单例模式设计 完整代码示例 个人主页&#xff1a;东洛的克莱斯韦克-CSDN博客 引言 有关套接字编程的细节和更多的系统调用课参考《UNIX环境高级编程》一书&#xff0c;可以在如下网站搜索电子版&#xff0c;该书在第16章详…...

Matplotlib在运维开发中的应用

在现代运维开发中,数据可视化扮演着越来越重要的角色。它能帮助我们更直观地理解系统状态,快速发现潜在问题,并辅助决策制定。Python的Matplotlib库作为一个强大而灵活的绘图工具,在运维领域有着广泛的应用。本文将探讨Matplotlib在运维开发中的常见应用场景,并提供实用的代码示…...

centos下nvme over rdma 环境配置

nvme over rdma 环境配置 本文主要介绍NVMe over RDMA的安装和配置。关于什么是NVMe over Fabrics,什么是NVMe over RDMA&#xff0c;本文就不做介绍了&#xff0c;网上资料一大堆。 可以看看什么是NVMe over Fabrics&#xff1f; RDMA&#xff08;全称&#xff1a;Remote Dir…...

【C++】——多态详解

目录 1、什么是多态&#xff1f; 2、多态的定义及实现 2.1多态的构成条件 ​2.2多态语法细节处理 2.3协变 2.4析构函数的重写 2.5C11 override 和 final关键字 2.6重载—重写—隐藏的对比分析 3、纯虚函数和抽象类 4、多态的原理分析 4.1多态是如何实现的 4.2虚函数…...

STM32上实现FFT算法精准测量正弦波信号的幅值、频率和相位差(标准库)

在研究声音、电力或任何形式的波形时&#xff0c;我们常常需要穿过表面看本质。FFT&#xff08;快速傅里叶变换&#xff09;就是这样一种强大的工具&#xff0c;它能够揭示隐藏在复杂信号背后的频率成分。本文将带你走进FFT的世界&#xff0c;了解它是如何将时域信号转化为频域…...

计算机毕业设计 农场投入品运营管理系统 Java+SpringBoot+Vue 前后端分离 文档报告 代码讲解 安装调试

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…...

【笔记】2.1 半导体三极管(BJT,Bipolar Junction Transistor)

一、结构和符号 1. 三极管结构 常用的三极管的结构有硅平面管和锗合金管两种类型。各有PNP型和NPN型两种结构。 左图是NPN型硅平面三极管,右图是PNP型锗合金三极管。 从图中可见平面型三极管是先在一块大的金属板上注入杂质使之变成N型,然后再在中间注入杂质使之变成P型,…...

企业中文档团队的三种组织形式

大家好&#xff01;今天咱们来聊聊企业里文档团队都是怎么组织的。 企业中&#xff0c;常见的文档团队组织形式有三种。 首先&#xff0c;最常见的就是集中式的文档团队。就是说&#xff0c;公司里头有几个不同的部门&#xff0c;每个部门都需要做文档。于是呢&#xff0c;公…...

古诗词四首鉴赏

1、出自蓟北门行 唐李白 虏阵横北荒&#xff0c;胡星曜精芒。 羽书速惊电&#xff0c;烽火昼连光。 虎竹救边急&#xff0c;戎车森已行。 明主不安席&#xff0c;按剑心飞扬。 推毂出猛将&#xff0c;连旗登战场。 兵威冲绝漠&#xff0c;杀气凌穹苍。…...

全国行政区划下载(高德、阿里、天地图)

一、网站链接&#xff1a; 阿里云数据可视化平台&#xff1a; DataV.GeoAtlas地理小工具系列 (aliyun.com) 链接: link 高德地图API获取行政区域&#xff1a; 高德地图API获取行政区域 (naivemap.com) 链接: link 天地图服务中心&#xff1a; 天地图 服务中心 (tianditu.g…...

Springboot提升-MapStruct组件使用

文章目录 1. 添加依赖2. 创建映射接口3. 在Spring Boot中使用MapStruct映射器4. 配置MapStruct 在Spring Boot项目中使用MapStruct可以帮助你更方便地管理对象之间的映射逻辑。下面是一些基本步骤来设置和使用MapStruct&#xff1a; 1. 添加依赖 首先&#xff0c;你需要在项目…...