windows USB 设备驱动开发- 驱动操作USB配置

USB 设备以一系列称为 USB 配置的接口的形式公开其功能。 每个接口由一个或多个备用设置组成,每个备用设置由一组终结点组成。 设备必须至少提供一个配置,但它可以提供多个配置,这些配置是设备可以执行的操作的互斥定义。 有关配置描述符的详细信息,请参阅 USB 配置描述符。

设备配置指的是客户端驱动程序执行的任务,用于选择 USB 配置和每个接口中的备用接口。 在向设备发送 I/O 请求之前,客户端驱动程序必须读取设备的配置、分析信息并选择合适的配置。 客户端驱动程序必须至少选择一个受支持的配置才能使设备正常工作。

基于 WDM 的客户端驱动程序可以选择 USB 设备中的任何配置。

如果客户端驱动程序基于 内核模式驱动程序框架 或 用户模式驱动程序框架,则应使用相应的框架接口来配置 USB 设备。 如果使用随 Microsoft Visual Studio Professional 2012 提供的 USB 模板,模板代码将在每个接口中选择第一个配置和默认备用设置。

选择配置的限制

如果客户端驱动程序使用 WDF 对象,或者设备是具有单个接口还是多个接口,则某些限制适用。 更改默认配置之前,请考虑以下限制:

  • 通过 USB 通用父 驱动程序 (Usbccgp.sys) 管理接口或接口集合的复合设备的客户端驱动程序无法更改设备的配置值。 但是,客户端驱动程序可以将 Usbccgp.sys 配置为选择除第一个 (默认) 配置之外的配置。 有关详细信息,请参阅 配置 Usbccgp.sys 以选择非默认 USB 配置;
  • 使用框架的 USB I/O 目标的 基于 KMDF 的客户端驱动程序只能选择第一个配置;
  • WinUSB 仅支持第一个配置;
  • 类驱动程序通常缺少对多个配置的支持。 如果设备实现了由 USB 类规范定义的类,请参阅 USB 技术 网站,了解有关设备类和类规范的信息。 Microsoft 为支持的 USB 设备类提供类驱动程序。
如何选择 USB 设备的配置

若要为 USB 设备选择配置,设备的客户端驱动程序必须至少选择一个受支持的配置,并指定要使用的每个接口的备用设置。 客户端驱动程序将这些选项打包在 选择配置请求 中,并将请求发送到 Microsoft 提供的 USB 驱动程序堆栈,特别是 USB 总线驱动程序。 USB 总线驱动程序选择指定配置中的每个接口,并设置到接口内每个终结点的信道或 管道。 请求完成后,客户端驱动程序会收到所选配置的句柄,以及每个接口的活动备用设置中定义的终结点的管道句柄。 然后,客户端驱动程序可以使用收到的句柄更改配置设置,并将 I/O 读取和写入请求发送到特定终结点。

客户端驱动程序在 类型为 URB_FUNCTION_SELECT_CONFIGURATION 的 USB 请求块 (URB) 发送选择配置请求。 本主题中的过程介绍如何使用 USBD_SelectConfigUrbAllocateAndBuild 例程来生成该 URB。 例程为 URB 分配内存,为选择配置请求设置 URB 的格式,并将 URB 的地址返回到客户端驱动程序;或者,可以分配 URB 结构,然后手动或调用 UsbBuildSelectConfigurationRequest 宏格式化 URB 。

先决条件
  • 从 Windows 8 开始,USBD_SelectConfigUrbAllocateAndBuild替换USBD_CreateConfigurationRequestEx;
  • 在发送选择配置请求之前,必须具有一个 USBD 句柄,以便将客户端驱动程序注册到 USB 驱动程序堆栈。 创建 USBD 句柄调用 USBD_CreateHandle;
  • 确保已获取要选择的配置 (USB_CONFIGURATION_DESCRIPTOR 结构) 的配置描述符。 通常,提交类型的 URB URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE 查看 _URB_CONTROL_DESCRIPTOR_REQUEST来检索有关设备配置的信息;
步骤 1:创建USBD_INTERFACE_LIST_ENTRY结构的数组

1.获取配置中的接口数。 此信息包含在 USB_CONFIGURATION_DESCRIPTOR 结构的 bNumInterfaces 成员中。

2.创建 USBD_INTERFACE_LIST_ENTRY 结构的数组。 数组中的元素数必须比接口数多一个。 通过调用 RtlZeroMemory 初始化数组。

客户端驱动程序在 USBD_INTERFACE_LIST_ENTRY 结构数组中指定要启用的每个接口中的备用设置。

  • 每个 结构的 InterfaceDescriptor 成员指向包含备用设置的接口描述符;
  • 每个结构的 Interface 成员指向在其 Pipes 成员中包含管道信息的 USBD_INTERFACE_INFORMATION 结构。 管道 存储有关备用设置中定义的每个终结点的信息;

3.获取配置中每个接口 (或其备用设置) 的接口描述符。 可以通过调用 USBD_ParseConfigurationDescriptorEx 获取这些接口描述符。

关于 USB 复合设备的功能驱动程序:如果 USB 设备是复合设备,则配置由 Microsoft 提供的 USB 通用父驱动程序 (Usbccgp.sys) 选择。 客户端驱动程序是复合设备的功能驱动程序之一,无法更改配置,但驱动程序仍可以通过 Usbccgp.sys 发送选择配置请求。

在发送该请求之前,客户端驱动程序必须提交URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE请求。 作为响应,Usbccgp.sys 检索仅包含接口描述符的部分 配置描述 符,以及与为其加载客户端驱动程序的特定函数相关的其他描述符。 部分配置描述符的 bNumInterfaces 字段中报告的接口数小于为整个 USB 复合设备定义的接口总数。 此外,在部分配置描述符中,接口描述符的 bInterfaceNumber 指示相对于整个设备的实际接口编号。 例如,对于第一个接口,Usbccgp.sys 可能会报告部分配置描述符,其中 bNumfaces 值为 2,bInterfaceNumber 值为 4。 请注意,接口编号大于报告的接口数。

枚举部分配置中的接口时,请根据接口数计算接口编号,避免搜索接口。 在前面的示例中,如果在循环中调用 USBD_ParseConfigurationDescriptorEx ,该循环从零开始,在 (bNumInterfaces - 1)处结束,并在每次迭代中递增 InterfaceNumber 参数中指定的接口索引,则例程无法获取正确的接口。 相反,请确保通过在 InterfaceNumber 中传递 -1 来搜索配置描述符中的所有接口。

4.对于除数组中最后一个元素之外的每个元素,请将 InterfaceDescriptor 成员设置为接口描述符的地址。 对于数组中的第一个元素,将 InterfaceDescriptor 成员设置为表示配置中第一个接口的接口描述符的地址。 同样,对于数组中的 第 n个元素,将 InterfaceDescriptor 成员设置为表示配置中 第 n个接口的接口描述符的地址。

5.最后一个元素的 InterfaceDescriptor 成员必须设置为 NULL。

步骤 2:获取指向 USB 驱动程序堆栈分配的 URB 的指针

接下来 ,通过指定要 选择的配置和填充USBD_INTERFACE_LIST_ENTRY结构的数组来调用 USBD_SelectConfigUrbAllocateAndBuild 。 例程执行以下任务:

  • 创建一个 URB,并在其中填充有关指定配置、其接口和终结点的信息,并将请求类型设置为URB_FUNCTION_SELECT_CONFIGURATION;
  • 在该 URB 中, 为客户端驱动程序指定的每个接口描述符分配 USBD_INTERFACE_INFORMATION 结构;
  • 将调用方提供的USBD_INTERFACE_LIST_ENTRY数组的第 n个元素的 Interface 成员设置为 URB 中相应USBD_INTERFACE_INFORMATION结构的地址;
  • 初始化 InterfaceNumber、 AlternateSetting、 NumberOfPipes、 Pipes[i]。MaximumTransferSize 和 Pipes[i]。PipeFlags 成员;

在 Windows 7 和 ealier 中,客户端驱动程序通过调用 USBD_CreateConfigurationRequestEx 为选择配置请求创建了 URB。 在 Windows 2000 USBD_CreateConfigurationRequestEx 初始化 Pipes[i]。MaximumTransferSize 为单个 URB 读/写请求的默认最大传输大小。 客户端驱动程序可以在 Pipes[i] 中指定不同的最大传输大小。MaximumTransferSize。 USB 堆栈在 Windows XP、Windows Server 2003 和更高版本的操作系统中忽略此值。 

步骤 3:将 URB 提交到 USB 驱动程序堆栈

若要将 URB 提交到 USB 驱动程序堆栈,客户端驱动程序必须发送 IOCTL_INTERNAL_USB_SUBMIT_URB I/O 控制请求 。 

收到 URB 后,USB 驱动程序堆栈将填充每个 USBD_INTERFACE_INFORMATION 结构的其余成员。 具体而言, Pipes 数组成员将填充与接口终结点关联的管道的相关信息。

步骤 4:请求完成后,检查USBD_INTERFACE_INFORMATION结构和 URB

USB 驱动程序堆栈完成请求的 IRP 后,堆栈将返回 USBD_INTERFACE_LIST_ENTRY 数组中的备用设置和相关接口的列表。

1.每个USBD_INTERFACE_INFORMATION结构的 Pipes 成员指向USBD_PIPE_INFORMATION结构的数组,该数组包含与该特定接口的每个终结点关联的管道的相关信息。 客户端驱动程序可以从 Pipes[i] 获取管道句柄。PipeHandle 并使用它们向特定管道发送 I/O 请求。 Pipes[i]。PipeType 成员指定该管道支持的终结点和传输的类型。

2.在 URB 的 UrbSelectConfiguration 成员中,USB 驱动程序堆栈返回一个句柄,该句柄可用于通过提交另一个类型的 URB URB_FUNCTION_SELECT_INTERFACE (选择接口请求) 来选择备用接口设置。 若要为该请求分配和生成 URB 结构,请调用 USBD_SelectInterfaceUrbAllocateAndBuild。

如果没有足够的带宽支持已启用接口中的常时等量、控制和中断终结点,则选择配置请求和选择接口请求可能会失败。 在这种情况下,USB 总线驱动程序将 URB 标头的 Status 成员设置为USBD_STATUS_NO_BANDWIDTH。

下面的示例代码演示如何创建 USBD_INTERFACE_LIST_ENTRY 结构的数组并调用 USBD_SelectConfigUrbAllocateAndBuild。 该示例通过调用 SubmitUrbSync 以同步方式发送请求。 

/*++

Routine Description:
This helper routine selects the specified configuration.

Arguments:
USBDHandle - USBD handle that is retrieved by the 
client driver in a previous call to the USBD_CreateHandle routine.

ConfigurationDescriptor - Pointer to the configuration
descriptor for the device. The caller receives this pointer
from the URB_FUNCTION_GET_DESCRIPTOR_FROM_DEVICE request.

Return Value: NT status value
--*/

NTSTATUS SelectConfiguration (PDEVICE_OBJECT DeviceObject,
                              PUSB_CONFIGURATION_DESCRIPTOR ConfigurationDescriptor)
{
    PDEVICE_EXTENSION deviceExtension;
    PIO_STACK_LOCATION nextStack;
    PIRP irp;
    PURB urb = NULL;

    KEVENT    kEvent;
    NTSTATUS ntStatus;    

    PUSBD_INTERFACE_LIST_ENTRY   interfaceList = NULL; 
    PUSB_INTERFACE_DESCRIPTOR    interfaceDescriptor = NULL;
    PUSBD_INTERFACE_INFORMATION  Interface = NULL;
    USBD_PIPE_HANDLE             pipeHandle;

    ULONG                        interfaceIndex;

    PUCHAR StartPosition = (PUCHAR)ConfigurationDescriptor;

    deviceExtension = (PDEVICE_EXTENSION)DeviceObject->DeviceExtension;

    // Allocate an array for the list of interfaces
    // The number of elements must be one more than number of interfaces.
    interfaceList = (PUSBD_INTERFACE_LIST_ENTRY)ExAllocatePool (
        NonPagedPool, 
        sizeof(USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    if(!interfaceList)
    {
        //Failed to allocate memory
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    // Initialize the array by setting all members to NULL.
    RtlZeroMemory (interfaceList, sizeof (
        USBD_INTERFACE_LIST_ENTRY) *
        (deviceExtension->NumInterfaces + 1));

    // Enumerate interfaces in the configuration.
    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        interfaceDescriptor = USBD_ParseConfigurationDescriptorEx(
            ConfigurationDescriptor, 
            StartPosition, // StartPosition 
            -1,            // InterfaceNumber
            0,             // AlternateSetting
            -1,            // InterfaceClass
            -1,            // InterfaceSubClass
            -1);           // InterfaceProtocol

        if (!interfaceDescriptor) 
        {
            ntStatus = STATUS_INSUFFICIENT_RESOURCES;
            goto Exit;
        }

        // Set the interface entry
        interfaceList[interfaceIndex].InterfaceDescriptor = interfaceDescriptor;
        interfaceList[interfaceIndex].Interface = NULL;

        // Move the position to the next interface descriptor
        StartPosition = (PUCHAR)interfaceDescriptor + interfaceDescriptor->bLength;

    }

    // Make sure that the InterfaceDescriptor member of the last element to NULL.
    interfaceList[deviceExtension->NumInterfaces].InterfaceDescriptor = NULL;

    // Allocate and build an URB for the select-configuration request.
    ntStatus = USBD_SelectConfigUrbAllocateAndBuild(
        deviceExtension->UsbdHandle, 
        ConfigurationDescriptor, 
        interfaceList,
        &urb);

    if(!NT_SUCCESS(ntStatus)) 
    {
        goto Exit;
    }

    // Allocate the IRP to send the buffer down the USB stack.
    // The IRP will be freed by IO manager.
    irp = IoAllocateIrp((deviceExtension->NextDeviceObject->StackSize)+1, TRUE);  

    if (!irp)
    {
        //Irp could not be allocated.
        ntStatus = STATUS_INSUFFICIENT_RESOURCES;
        goto Exit;
    }

    ntStatus = SubmitUrbSync( 
        deviceExtension->NextDeviceObject, 
        irp, 
        urb, 
        CompletionRoutine);

    // Enumerate the pipes in the interface information array, which is now filled with pipe
    // information.

    for ( interfaceIndex = 0; 
        interfaceIndex < deviceExtension->NumInterfaces; 
        interfaceIndex++) 
    {
        ULONG i;

        Interface = interfaceList[interfaceIndex].Interface;

        for(i=0; i < Interface->NumberOfPipes; i++) 
        {
            pipeHandle = Interface->Pipes[i].PipeHandle;

            if (Interface->Pipes[i].PipeType == UsbdPipeTypeInterrupt)
            {
                deviceExtension->InterruptPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_IN (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkInPipe = pipeHandle;
            }
            if (Interface->Pipes[i].PipeType == UsbdPipeTypeBulk && USB_ENDPOINT_DIRECTION_OUT (Interface->Pipes[i].EndpointAddress))
            {
                deviceExtension->BulkOutPipe = pipeHandle;
            }
        }
    }

Exit:

    if(interfaceList) 
    {
        ExFreePool(interfaceList);
        interfaceList = NULL;
    }

    if (urb)
    {
        USBD_UrbFree( deviceExtension->UsbdHandle, urb); 
    }

    return ntStatus;
}

NTSTATUS CompletionRoutine ( PDEVICE_OBJECT DeviceObject,
                            PIRP           Irp,
                            PVOID          Context)
{
    PKEVENT kevent;

    kevent = (PKEVENT) Context;

    if (Irp->PendingReturned == TRUE)
    {
        KeSetEvent(kevent, IO_NO_INCREMENT, FALSE);
    }

    KdPrintEx(( DPFLTR_IHVDRIVER_ID, DPFLTR_INFO_LEVEL, "Select-configuration request completed. \n" ));

    return STATUS_MORE_PROCESSING_REQUIRED;
}
禁用 USB 设备的配置

若要禁用 USB 设备,请使用 NULL 配置描述符创建并提交选择配置请求。 对于该类型的请求,可以重复使用为在设备中选择配置的请求创建的 URB。 或者,可以通过调用 USBD_UrbAllocate 来分配新的 URB。 在提交请求之前,必须使用 UsbBuildSelectConfigurationRequest 宏设置 URB 的格式,如以下示例代码所示。

URB Urb;
UsbBuildSelectConfigurationRequest(
  &Urb,
  sizeof(_URB_SELECT_CONFIGURATION),
  NULL
);

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

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

相关文章

算法思想总结:优先级队列

一、最后一块石头的重量 . - 力扣&#xff08;LeetCode&#xff09; 我们每次都要快速找到前两个最大的石头进行抵消&#xff0c;这个时候用优先级队列&#xff08;建大堆&#xff09;,不断取堆顶元素是最好的&#xff01;每次删除堆顶元素后&#xff0c;可以自动调整&#xf…

IP地址:网络还是设备的标识符?

在数字化时代&#xff0c;IP地址已成为我们连接互联网、进行信息交流的基石。然而&#xff0c;关于IP地址的分配和来源&#xff0c;很多人可能并不清楚。它究竟是根据网络来分配&#xff0c;还是基于设备来赋予&#xff1f;下面跟着虎观代理小二一起来解析IP地址的奥秘&#xf…

高效使用 Guzzle:POST 请求与请求体参数的最佳实践

介绍 在现代爬虫技术中&#xff0c;高效发送 HTTP 请求并处理响应数据是关键步骤之一。Guzzle 是一个强大的 PHP HTTP 客户端&#xff0c;广泛应用于发送同步和异步请求。本文将介绍如何使用 Guzzle 发送 POST 请求&#xff0c;特别是如何传递请求体参数&#xff0c;并结合代理…

Windows系统安装分布式搜索和分析引擎Elasticsearch与远程访问详细教程

文章目录 前言系统环境1. Windows 安装Elasticsearch2. 本地访问Elasticsearch3. Windows 安装 Cpolar4. 创建Elasticsearch公网访问地址5. 远程访问Elasticsearch6. 设置固定二级子域名 前言 本文主要介绍如何在Windows系统安装分布式搜索和分析引擎Elasticsearch&#xff0c…

HandlerMethodArgumentResolver :深入spring mvc参数解析机制

❃博主首页 &#xff1a; <码到三十五> ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a; 搬的每块砖&#xff0c;皆为峰峦之基&#xff1b;公众号搜索(码到三十…

[k8s生产系列]:k8s集群故障恢复,etcd数据不一致,kubernetes集群异常

文章目录 摘要1 背景说明2 故障排查2.1 查询docker与kubelet状态2.2 查看kubelet服务日志2.3 重启docker与kubelet服务2.3.1 首先kubelet启动起来了&#xff0c;但是报错master节点找不到2.3.2 查询kubernetes集群服务&#xff0c;发现etcd与kube-apiserver均启动异常 2.4 etcd…

2024年中国网络安全市场全景图 -百度下载

是自2018年开始&#xff0c;数说安全发布的第七版全景图。 企业数智化转型加速已经促使网络安全成为全社会关注的焦点&#xff0c;在网络安全边界不断扩大&#xff0c;新理念、新产品、新技术不断融合发展的进程中&#xff0c;数说安全始终秉承科学的方法论&#xff0c;以遵循…

Rhino 犀牛三维建模工具下载安装,Rhino 适用于机械设计广泛领域

Rhinoceros&#xff0c;这款软件小巧而强大&#xff0c;无论是机械设计、科学工业还是三维动画等多元化领域&#xff0c;它都能展现出其惊人的建模能力。 Rhinoceros所包含的NURBS建模功能&#xff0c;堪称业界翘楚。NURBS&#xff0c;即非均匀有理B样条&#xff0c;是计算机图…

怎样在Python中使用oobabooga的API密钥,通过端口5000获取模型列表的授权

题意&#xff1a; oobabooga-textgen-web-ui how to get authorization to view model list from port 5000 via the oobas api-key in python 怎样在Python中使用oobabooga的API密钥&#xff0c;通过端口5000获取模型列表的授权 问题背景&#xff1a; I wish to extract an…

抬头显示器HUD原理及特性

HUD基本原理 抬头数字显示仪(Head Up Display)&#xff0c;又叫平视显示系统&#xff0c;它的作用&#xff0c;就是把时速、导 航等重要的行车信息&#xff0c;投影到驾驶员前风挡玻璃上&#xff0c;让驾驶员尽量做到不低头、不转头 就能看行车信息。 HUD成像为离轴三反的过程&…

代码随想录算法训练营第2天|LeetCode977,209,59

977.有序数组平方 题目链接&#xff1a; 977. 有序数组的平方 - 力扣&#xff08;LeetCode&#xff09; 文章讲解&#xff1a;代码随想录 视频讲解&#xff1a; 双指针法经典题目 | LeetCode&#xff1a;977.有序数组的平方_哔哩哔哩_bilibili 第一想法 暴力算法肯定是先将元素…

关于软件本地化,您应该了解什么?

软件本地化是调整软件应用程序以满足目标市场的语言、文化和技术要求的过程。它不仅仅涉及翻译用户界面&#xff1b;它包含一系列活动&#xff0c;以确保软件在目标语言环境中可用且相关。以下是您应该了解的有关软件本地化的一些关键方面&#xff1a; 了解范围 软件本地化是…

【软件测试】之黑盒测试用例的设计

&#x1f3c0;&#x1f3c0;&#x1f3c0;来都来了&#xff0c;不妨点个关注&#xff01; &#x1f3a7;&#x1f3a7;&#x1f3a7;博客主页&#xff1a;欢迎各位大佬! 文章目录 1.测试用例的概念2.测试用例的好处3. 黑盒测试用例的设计3.1 黑盒测试的概念3.2 基于需求进行测…

暗潮短视频:成都柏煜文化传媒有限公司

暗潮短视频&#xff1a;涌动的新媒体力量 在数字化时代的浪潮中&#xff0c;短视频以其独特的魅力和无限的潜力&#xff0c;迅速成为新媒体领域的一股强大力量。而在这片繁荣的短视频领域中&#xff0c;成都柏煜文化传媒有限公司“暗潮短视频”以其独特的定位和深邃的内容&…

论文浅尝 | 从最少到最多的提示可在大型语言模型中实现复杂的推理

笔记整理&#xff1a;王泽元&#xff0c;浙江大学博士 链接&#xff1a;https://openreview.net/forum?idWZH7099tgfM 1. 动机 尽管深度学习已经取得了巨大的成功&#xff0c;但它与人类智慧仍然存在一些明显差距。这些差距包括以下几个方面&#xff1a;1&#xff09;学习新任…

损失函数篇

损失函数 1、边界框损失函数/回归损失函数bbox_loss 2、分类损失函数cls_loss 3、置信度损失函数obj_loss YOLOv8损失函数 1、概述 通过YOLOv8-训练流程-正负样本分配的介绍&#xff0c;我们可以知道&#xff0c;经过预处理与筛选的过程得到最终的训练数据&#xff1a; a…

11 UDP的可靠传输协议QUIC

1.如何做到可靠性传输 2.UDP与TCP,我们如何选择 3.UDP如何可靠,KCP协议在哪些方面有优势 4.KCP协议精讲(重点讲解 5.OUIC时代是否已经到来 UDP如何做到可靠传输 ACK机制重传机制 重传策略序号机制(后发的包可能先到) 3 2 1-> 2 3 1重排机制 2 3 1-> 3 2 1窗口机制 流…

谷粒商城笔记-03-分布式基础概念

文章目录 一&#xff0c;微服务二&#xff0c;集群、分布式三&#xff0c;远程调用四&#xff0c;负载均衡五&#xff0c;服务注册、服务发现、注册中心六&#xff0c;配置中心七&#xff0c;服务熔断、服务降级1&#xff0c;服务熔断2&#xff0c;服务降级3&#xff0c;区别 八…

Spring框架的学习前言

1.注意事项 1.在接下来的学习中我们会将jdk的版本升级到17。 2.引入maven仓库用来存储依赖 3.在后面的javaSpring框架中要第一个项目的创建要选javaweb和lombook这两个依赖 2.Maven的主要功能 &#xff08;1&#xff09;maven的主要功能是引入依赖和管理依赖&#xff0c;在…

基于SpringCloud的分布式架构网上商城

基于SpringCloud的分布式架构网上商城的主要使用者管理员功能&#xff1a;首页、个人中心、用户管理、商品信息管理、商品分类管理、系统管理、订单管理等功能。 &#x1f495;&#x1f495;作者&#xff1a;Weirdo &#x1f495;&#x1f495;个人简介&#xff1a;擅长Java、C…