【android bluetooth 框架分析 02】【Module详解 2】【gd_shim_module 模块介绍】

1. 背景

上一章节 我们介绍了 module_t 的 大体框架 ,本节内容我们就选择 我们的 gd_shim_module 模块为例子,具体剖析一下,它里面的逻辑。

static const char GD_SHIM_MODULE[] = "gd_shim_module";// system/main/shim/shim.cc
EXPORT_SYMBOL extern const module_t gd_shim_module = {.name = GD_SHIM_MODULE,.init = kUnusedModuleApi,.start_up = ShimModuleStartUp,.shut_down = GeneralShutDown,.clean_up = kUnusedModuleApi,.dependencies = {kUnusedModuleDependencies}};
  • 从上述代码可以知道, gd_shim_module 模块只有 start_up 和 shut_down 两个阶段。而且他不依赖任何模块。

  • gd_shim_module 的作用
    作为 新旧蓝牙协议栈的兼容层(Shim Layer)。

    • 将传统的 Bluedroid 接口适配到新的 GD(Google Direct)架构。
  • 设计原因
    渐进式重构蓝牙协议栈时,确保旧代码(如 Android 传统蓝牙服务)能无缝调用新模块。

生命周期:

  • 在 event_start_up_stack 阶段 调用 start_up , 实际调用 ShimModuleStartUp
  • 在 event_shut_down_stack 阶段 调用 shut_down, 实际调用 GeneralShutDown

那接下来我们就一起分析一下 他的 start_up 和 shut_down 中分别做了那些事情。

// system/main/shim/shim.cc
future_t* ShimModuleStartUp() {bluetooth::shim::Stack::GetInstance()->StartEverything();return kReturnImmediate;
}
// system/main/shim/stack.cc
Stack* Stack::GetInstance() {static Stack instance;return &instance;
}
  • 从 Stack::GetInstance 函数中,我们不难发现, 他此时获取的 是一个 局部静态 变量 Stack 对象。 类似是一种单例的实现方式。

  • 无论谁先调用 bluetooth::shim::Stack::GetInstance() 都将拿到 同一个 Stack 对象 。

  • ShimModuleStartUp() 函数其实调用的 Stack 对象的 StartEverything() 方法。

既然这里提到了 bluetooth::shim::Stack ,那我们不妨先来看一下他的数据结构。

2. bluetooth::shim::Stack

// The shim layer implementation on the Gd stack side.
namespace bluetooth {
namespace shim {// GD shim stack, having modes corresponding to legacy stack
class Stack {public:static Stack* GetInstance();Stack() = default;Stack(const Stack&) = delete;Stack& operator=(const Stack&) = delete;~Stack() = default;// Idle mode, config is loaded, but controller is not enabledvoid StartIdleMode();// Running mode, everything is upvoid StartEverything();void Stop();bool IsRunning();bool IsDumpsysModuleStarted() const;StackManager* GetStackManager();const StackManager* GetStackManager() const;legacy::Acl* GetAcl();LinkPolicyInterface* LinkPolicy();Btm* GetBtm();os::Handler* GetHandler();::rust::Box<rust::Hci>* GetRustHci() { return rust_hci_; }::rust::Box<rust::Controller>* GetRustController() {return rust_controller_;}private:mutable std::recursive_mutex mutex_;StackManager stack_manager_; // 管理 GD 协议栈中所有蓝牙模块的生命周期bool is_running_ = false;os::Thread* stack_thread_ = nullptr; // 为整个蓝牙协议栈提供专用的执行线程os::Handler* stack_handler_ = nullptr; // 提供消息处理机制,用于线程间通信和任务调度legacy::Acl* acl_ = nullptr;Btm* btm_ = nullptr;::rust::Box<rust::Stack>* rust_stack_ = nullptr;::rust::Box<rust::Hci>* rust_hci_ = nullptr;::rust::Box<rust::Controller>* rust_controller_ = nullptr;void Start(ModuleList* modules);
};}  // namespace shim
}  // namespace bluetooth

Stack 类是 蓝牙协议栈中的核心管理类,负责实现新的 GD (Google Direct) 蓝牙协议栈。它充当传统蓝牙协议栈和新模块化 GD 架构之间的桥梁。

1. 关键成员讲解

1. stack_manager_

StackManager stack_manager_;
  • 作用:管理 GD 协议栈中所有蓝牙模块的生命周期

  • 职责

    • 以正确的顺序启动和关闭模块
    • 提供对模块实例的访问
    • 维护模块间的依赖关系
  • 与其他组件的关系Stack 类拥有并控制 StackManager,用它来初始化和管理所有蓝牙模块

2. stack_thread_ (os::Thread)

  • 作用:为整个蓝牙协议栈提供专用的执行线程

  • 特点

    • 被设置为实时优先级(REAL_TIME)
    • 命名为"gd_stack_thread"
  • 与其他组件的关系

    • 由 Stack 类创建和管理
    • 为 stack_handler_ 提供运行环境
    • 被 StackManager 用来调度模块任务

3. stack_handler_ (os::Handler)

  • 作用:提供消息处理机制,用于线程间通信和任务调度

  • 特点

    • 与 stack_thread_ 关联
    • 用于处理异步操作和事件
  • 与其他组件的关系

    • 由 Stack 类创建并与 stack_thread_ 绑定
    • 被多个模块(如 ACL)用来发送和接收消息

4. ModuleList*

  • 作用:包含需要启动的蓝牙模块集合

  • 特点

    • 根据不同的启动模式(Idle/Everything)包含不同的模块
    • 使用 add<>() 方法动态添加模块
  • 与其他组件的关系

    • 被传递给 StackManager 的 StartUp 方法
    • 决定了哪些模块会被初始化和运行

2. Stack 类的主要功能

  1. 协议栈生命周期管理

    • StartIdleMode() - 启动最小配置(仅基础模块)
    • StartEverything() - 启动完整协议栈功能
    • Stop() - 关闭协议栈
  2. 资源管理

    • 创建和管理 PID 文件
    • 确保资源的正确初始化和释放
  3. 模块访问接口

    • 提供获取各种模块实例的方法(如 GetAcl(), GetBtm())
  4. 兼容层支持

    • 维护与传统协议栈的兼容接口

3.这么设计的好处

  1. 模块化设计

    • 将蓝牙功能分解为独立模块,便于维护和扩展
    • 允许按需加载模块,减少资源占用
  2. 线程安全

    • 使用递归互斥锁(mutex_)保护共享资源
    • 确保多线程环境下的安全访问
  3. 灵活配置

    • 通过 ModuleList 支持不同配置(如仅核心功能或完整功能)
    • 根据系统标志(gd_rust_is_enabled 等)动态调整行为
  4. 平滑过渡

    • 提供与传统协议栈兼容的接口
    • 支持新旧实现共存
  5. 资源控制

    • 集中管理线程和消息处理程序
    • 确保资源正确初始化和释放

这种设计使得蓝牙协议栈更加灵活、可维护,并且能够平滑地从传统实现过渡到新的 GD 架构,同时保持对现有应用的兼容性。

3. ShimModuleStartUp

上面我已经 为大家介绍了 Stack 数据结构, 或许你还有对 Stack 有其他疑问, 但不要着急, 我们一起来继续分析 我们 gd_shim_module 模块的 start_up 阶段。在这个过程中我们来不断体会 Stack 数据结构设计的巧妙之处。

// system/main/shim/shim.cc
future_t* ShimModuleStartUp() {bluetooth::shim::Stack::GetInstance()->StartEverything();return kReturnImmediate;
}
// system/main/shim/stack.cc
Stack* Stack::GetInstance() {static Stack instance;return &instance;
}
  • 从 Stack::GetInstance 函数中,我们不难发现, 他此时获取的 是一个 局部静态 变量 Stack 对象。 类似是一种单例的实现方式。

  • 无论谁先调用 bluetooth::shim::Stack::GetInstance() 都将拿到 同一个 Stack 对象 。

  • ShimModuleStartUp() 函数其实调用的 Stack 对象的 StartEverything() 方法。

3.1 Stack::StartEverything

void Stack::StartEverything() {// 1. **Rust路径**:当`gd_rust_is_enabled()`标志为真时,使用Rust实现的协议栈if (common::init_flags::gd_rust_is_enabled()) {if (rust_stack_ == nullptr) {rust_stack_ = new ::rust::Box<rust::Stack>(rust::stack_create());}rust::stack_start(**rust_stack_);// 获取HCI和Controller组件实例// 这些组件由Rust实现,通过FFI接口暴露给C++rust_hci_ = new ::rust::Box<rust::Hci>(rust::get_hci(**rust_stack_));rust_controller_ =new ::rust::Box<rust::Controller>(rust::get_controller(**rust_stack_));bluetooth::shim::hci_on_reset_complete();// Create pid since we're up and runningCreatePidFile();// Create the acl shim layeracl_ = new legacy::Acl(stack_handler_, legacy::GetAclInterface(),controller_get_interface()->get_ble_acceptlist_size(),controller_get_interface()->get_ble_resolving_list_max_size());return;}// 传统C++ 实现的 GD协议栈, 我们以 C++ 实现的分析为主//  使用递归锁保护整个启动过程//  防止多线程并发访问导致状态不一致std::lock_guard<std::recursive_mutex> lock(mutex_);ASSERT_LOG(!is_running_, "%s Gd stack already running", __func__);LOG_INFO("%s Starting Gd stack", __func__);// 方法通过`ModuleList`动态添加各种蓝牙模块:ModuleList modules;modules.add<metrics::CounterMetrics>();modules.add<hal::HciHal>();modules.add<hci::HciLayer>();modules.add<storage::StorageModule>();modules.add<shim::Dumpsys>();modules.add<hci::VendorSpecificEventManager>();modules.add<hci::Controller>();modules.add<hci::AclManager>();// 添加L2CAP相关模块if (common::init_flags::gd_l2cap_is_enabled()) {modules.add<l2cap::classic::L2capClassicModule>();modules.add<l2cap::le::L2capLeModule>();modules.add<hci::LeAdvertisingManager>();}// 添加安全模块if (common::init_flags::gd_security_is_enabled()) {modules.add<security::SecurityModule>();}modules.add<hci::LeAdvertisingManager>();modules.add<hci::LeScanningManager>();// 添加活动追踪模块if (common::init_flags::btaa_hci_is_enabled()) {modules.add<activity_attribution::ActivityAttribution>();}// 添加核心功能模块if (common::init_flags::gd_core_is_enabled()) {modules.add<att::AttModule>();modules.add<neighbor::ConnectabilityModule>();modules.add<neighbor::DiscoverabilityModule>();modules.add<neighbor::InquiryModule>();modules.add<neighbor::NameModule>();modules.add<neighbor::NameDbModule>();modules.add<neighbor::PageModule>();modules.add<neighbor::ScanModule>();modules.add<storage::StorageModule>();}// 根据上述的模块, 来实际启动协议栈Start(&modules);is_running_ = true; // 设置运行标志// 验证关键模块是否成功启动// Make sure the leaf modules are startedASSERT(stack_manager_.GetInstance<storage::StorageModule>() != nullptr);ASSERT(stack_manager_.GetInstance<shim::Dumpsys>() != nullptr);if (common::init_flags::gd_core_is_enabled()) {btm_ = new Btm(stack_handler_,stack_manager_.GetInstance<neighbor::InquiryModule>());}//  为传统API提供兼容层支持//  确保新旧实现可以协同工作if (!common::init_flags::gd_core_is_enabled()) {if (stack_manager_.IsStarted<hci::Controller>()) {acl_ = new legacy::Acl(stack_handler_, legacy::GetAclInterface(),controller_get_interface()->get_ble_acceptlist_size(),controller_get_interface()->get_ble_resolving_list_max_size());} else {LOG_ERROR("Unable to create shim ACL layer as Controller has not started");}}if (!common::init_flags::gd_core_is_enabled()) {bluetooth::shim::hci_on_reset_complete();}bluetooth::shim::init_advertising_manager();bluetooth::shim::init_scanning_manager();if (common::init_flags::gd_l2cap_is_enabled() &&!common::init_flags::gd_core_is_enabled()) {L2CA_UseLegacySecurityModule();}if (common::init_flags::btaa_hci_is_enabled()) {bluetooth::shim::init_activity_attribution();}// Create pid since we're up and runningCreatePidFile();
}
  • StartEverything 我们主要分析 C++ 的流程。

主要完成如下功能:

  1. 向 modules 中添加 需要的模块:例如 hal::HciHal 模块和 hci::HciLayer
  2. 调用 Start(&modules) 来启动这些模块
  3. 初始化兼容层组件
  4. 创建PID文件标记启动完成

这种方法设计既支持新特性的逐步引入,又保持了与传统实现的兼容性,是大型系统渐进式重构的典型范例。

设计特点分析:

  1. 条件编译支持

    • 通过功能标志控制代码路径和模块加载
    • 实现灵活的功能组合
  2. 模块化设计

    • 每个功能作为独立模块添加
    • 模块间通过定义良好的接口交互
  3. 渐进式迁移

    • 支持Rust和C++实现并存
    • 兼容层确保平稳过渡
  4. 生命周期管理

    • 明确的启动顺序控制
    • 关键资源的状态验证
  5. 诊断支持

    • PID文件记录运行状态
    • 丰富的日志输出

3.2 Stack::Start

我们来看一下 是如何将加入 modules 中的模块,启动起来的。

void Stack::Start(ModuleList* modules) {ASSERT_LOG(!is_running_, "%s Gd stack already running", __func__);LOG_INFO("%s Starting Gd stack", __func__);stack_thread_ =new os::Thread("gd_stack_thread", os::Thread::Priority::REAL_TIME);stack_manager_.StartUp(modules, stack_thread_);stack_handler_ = new os::Handler(stack_thread_);LOG_INFO("%s Successfully toggled Gd stack", __func__);
}
  • 首先这里创建了 gd_stack_thread 线程,
  • 将 我们的 modules 信息 和 stack_thread_ 线程全部传递给了 stack_manager_, 从这里就能看出来, 我们前面添加的 模块,全部由 StackManager 管理, 而且这些模块,全部都是工作在 gd_stack_thread线程中的。
  • 将我们 gd_stack_thread 对应的 handler 保存在 stack_handler_ 中。

我们来继续分析 stack_manager_.StartUp(modules, stack_thread_);

3.3 StackManager::StartUp

  • system/gd/stack_manager.cc
void StackManager::StartUp(ModuleList* modules, Thread* stack_thread) {// 这里有创建了一个线程 management_threadmanagement_thread_ = new Thread("management_thread", Thread::Priority::NORMAL);// 获取到 management_thread 的 handlerhandler_ = new Handler(management_thread_);WakelockManager::Get().Acquire();std::promise<void> promise;auto future = promise.get_future();// 将 modules 的初始化全部 交给 management_thread 线程 来处理, handler_->Post(common::BindOnce(&StackManager::handle_start_up, common::Unretained(this), modules, stack_thread,std::move(promise)));// 然后 bt_stack_manager_thread 线程在这里等待 4 s, 让所有加入的模块,都初始化完成。LOG_INFO("init wait 4s.");auto init_status = future.wait_for(std::chrono::seconds(4));WakelockManager::Get().Release();// 如果 4s 后, init_status == std::future_status::ready 表明,所有的模块都已经初始化完成。 ASSERT_LOG(init_status == std::future_status::ready,"Can't start stack, last instance: %s",registry_.last_instance_.c_str());LOG_INFO("init complete");
}
  • 我们 modules 的初始化 全部交给 management_thread 的 StackManager::handle_start_up 函数来处理了, 我们继续分析。

3.4 StackManager::handle_start_up

// system/gd/stack_manager.cc
void StackManager::handle_start_up(ModuleList* modules, Thread* stack_thread, std::promise<void> promise) {// 最终给到了 registry_.Start 来处理registry_.Start(modules, stack_thread);promise.set_value();
}

3.5 ModuleRegistry::Start

  • system/gd/module.cc
void ModuleRegistry::Start(ModuleList* modules, Thread* thread) {for (auto it = modules->list_.begin(); it != modules->list_.end(); it++) {Start(*it, thread); // 从 modules 拿出每一个 module, 调用 Start}
}
Module* ModuleRegistry::Start(const ModuleFactory* module, Thread* thread) {// 从 started_modules_ 中查找, 当前的 module 是否已经启动,如何启动,将不再重复启动auto started_instance = started_modules_.find(module);if (started_instance != started_modules_.end()) {return started_instance->second;}// 创建一个 module 实体Module* instance = module->ctor_();LOG_INFO("Starting of %s", instance->ToString().c_str());last_instance_ = "starting " + instance->ToString();// 将 当前 module 实体和 gd_stack_thread 线程绑定set_registry_and_handler(instance, thread);LOG_INFO("Starting dependencies of %s", instance->ToString().c_str());auto start_time = std::chrono::steady_clock::now();// 如果当前启动的 module 内部还依赖 其他很多module 将,他们都加入到 实体自己的 dependencies_ 依赖中,instance->ListDependencies(&instance->dependencies_);// 先启动 当前module 所依赖的 module.  同时将 gd_stack_thread 传入Start(&instance->dependencies_, thread);LOG_INFO("Finished starting dependencies and calling Start() of %s", instance->ToString().c_str());auto end_time = std::chrono::steady_clock::now();auto elapsed_time = std::chrono::duration_cast<std::chrono::milliseconds>(end_time - start_time).count();LOG_INFO("handle_start_up: Module initialization took %lld ms", elapsed_time);// 当 前module 所依赖的所有 module 都启动完毕后, 在调用自己的 Start() 函数来启动自己。instance->Start();start_order_.push_back(module);// 将启动的 module 实体加入到 started_modules_ 中, 为了避免重启启动。started_modules_[module] = instance;LOG_INFO("Started %s", instance->ToString().c_str());return instance;
}
  • 下面是 logcat 中的日志截取, 可以看到我们实际加载了这么多模块。
Starting of BluetoothCounterMetrics
Starting of Storage Module
Starting of BluetoothCounterMetrics
Starting of HciHalHidl
Starting of SnoopLogger
Starting of Btaa Module
Starting of Hci Layer
Starting of Storage Module
Starting of shim::Dumpsys
Starting of Vendor Specific Event Manager
Starting of Controller
Starting of Acl Manager
Starting of Le Advertising Manager
Starting of Le Scanning Manager

我们暂时先分析到这里, 会在 后面的文章中, 分别拿 HciHalHidl 和 Hci Layer 这两个模块 具体分析。我们先梳理启动一个 module 都要执行那几步:

  1. 创建module 实体

    • Module* instance = module->ctor_();
  2. 将 当前 module 实体和 gd_stack_thread 线程绑定

    • set_registry_and_handler(instance, thread);
  3. 启动当前模块所依赖的所有子模块。

    • instance->ListDependencies(&instance->dependencies_);
    • Start(&instance->dependencies_, thread);
  4. 最后调用自己的 Start() 函数

    • instance->Start();
  5. 将module 实体加入到 started_modules_

    • started_modules_[module] = instance;

这里我们俩重点关注一下, 每一个 module 实体如何 和 gd_stack_thread 线程绑定的。

void ModuleRegistry::set_registry_and_handler(Module* instance, Thread* thread) const {instance->registry_ = this;instance->handler_ = new Handler(thread);
}
  • 将我们的 gd_stack_thread 对应的 handle 直接保存在 Module->handler_ 中。

4. GeneralShutDown

// system/main/shim/shim.cc
future_t* GeneralShutDown() {bluetooth::shim::Stack::GetInstance()->Stop();return kReturnImmediate;
}
void Stack::Stop() {// First remove pid file so clients no stack is going downRemovePidFile();if (common::init_flags::gd_rust_is_enabled()) {if (rust_stack_ != nullptr) {rust::stack_stop(**rust_stack_);}return;}std::lock_guard<std::recursive_mutex> lock(mutex_);if (!common::init_flags::gd_core_is_enabled()) {bluetooth::shim::hci_on_shutting_down();}// Make sure gd acl flag is enabled and we started it upif (acl_ != nullptr) {acl_->FinalShutdown();delete acl_;acl_ = nullptr;}ASSERT_LOG(is_running_, "%s Gd stack not running", __func__);is_running_ = false;delete btm_;btm_ = nullptr;stack_handler_->Clear();stack_manager_.ShutDown();delete stack_handler_;stack_handler_ = nullptr;stack_thread_->Stop();delete stack_thread_;stack_thread_ = nullptr;LOG_INFO("%s Successfully shut down Gd stack", __func__);
}

这里看到 gd_shim_module 在结束的时候, 也是一样直接调用了 Stack::Stop , 这里不再继续展开分析了。

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

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

相关文章

【包管理器】主流包管理器_对比_应用场景

不定期更新&#xff0c;建议关注收藏点赞。 链接&#xff1a; npm专题 目录 主流包管理器简介对比 主流包管理器简介 主流的包管理器其实有不少&#xff0c;不同语言和平台都有各自的一套系统。 前端&#xff08;JavaScript/TypeScript&#xff09; 名称简介开发者特点npmNo…

参照Spring Boot后端框架实现序列化工具类

本文参照Jackson实现序列化工具类&#xff0c;旨在于简化开发 JacksonUtil.class public class JacksonUtil {private JacksonUtil() {}/*** 单例*/private final static ObjectMapper OBJECT_MAPPER;static {OBJECT_MAPPER new ObjectMapper();}private static ObjectMappe…

Rust入门之迭代器(Iterators)

Rust入门之迭代器&#xff08;Iterators&#xff09; 本文已同步本人博客网站 本文相关源码已上传Github 前言 迭代器&#xff08;Iterators&#xff09;是 Rust 中最核心的工具之一&#xff0c;它不仅是遍历集合的抽象&#xff0c;更是 Rust 零成本抽象&#xff08;Zero-Co…

若依框架二次开发——RuoYi-AI 本地部署流程

文章目录 项目环境安装后端1. 下载项目2. 使用 IDEA 导入项目3. 配置 Maven4. 配置 Maven settings.xml5. 初始化数据库6. 启动 Redis7. 修改数据库配置8. 启动后端服务安装管理端1. 下载管理端项目2. 安装依赖3. 启动管理端4. 修改管理端配置安装用户端1. 下载用户端项目2. 安…

精品推荐-最新大模型MCP核心架构及最佳实践资料合集(18份).zip

精品推荐-最新大模型MCP核心架构及最佳实践资料合集&#xff0c;共18份。 1、2025年程序员必学技能&#xff1a;大模型MCP核心技术.pdf 2、MCP 架构设计剖析&#xff1a;从 Service Mesh 演进到 Agentic Mesh.pdf 3、MCP 架构设计深度剖析&#xff1a;使用 Spring AI MCP 四步…

DataWorks智能体Agent发布!基于MCP实现数据开发与治理自动化运行

在传统的数据开发工作中&#xff0c;企业用户或者开发者常常需要进行繁琐的配置、复杂的代码撰写、反复的性能调优和大量重复性的操作&#xff0c;才能实现数据开发、数据集成和数据治理等工作&#xff0c;效率十分低下。 近日&#xff0c;阿里云大数据开发治理平台DataWorks基…

IDEA 中右侧没有显示Maven

IDEA 中右侧没有显示Maven 1. 检查 Maven 项目是否正确加载 现象 • 项目是 Maven 项目&#xff0c;但右侧没有 Maven 工具窗口。 • 项目根目录下有 pom.xml&#xff0c;但 IDEA 没有识别为 Maven 项目。 解决方法 手动重新加载 Maven 项目&#xff1a; • 在 IDEA 中&…

罗技K860键盘

罗技蓝牙键盘的顶部功能键F1-F12的原本功能 单击罗技键盘的功能键时&#xff0c;默认响应的是键盘上面显示的快进、调节音量等功能。改变回F1~F12原本功能&#xff0c;同时按下 fn和esc组合键

什么是大型语言模型(LLM)?哪个大模型更好用?

什么是 LLM&#xff1f; ChatGPT 是一种大型语言模型 (LLM)&#xff0c;您可能对此并不陌生。它以非凡的能力而闻名&#xff0c;已证明能够出色地完成各种任务&#xff0c;例如通过考试、生成产品内容、解决问题&#xff0c;甚至在最少的输入提示下编写程序。 他们的实力现已…

css画右上角 角标三角形

.corner {position: absolute;top: -2rem;right: -2rem;width: 0;height: 0;border: 2rem solid red;border-bottom-color: transparent;border-top-color: transparent;border-left-color: transparent;transform: rotateZ(135deg); } 基本思路就是设置border&#xff0c;只设…

vue自定义颜色选择器

vue自定义颜色选择器 效果图&#xff1a; step0: 默认写法 调用系统自带的颜色选择器 <input type"color">step1:C:\Users\wangrusheng\PycharmProjects\untitled18\src\views\Home.vue <template><div class"container"><!-- 颜…

[Python] 企业内部应用接入钉钉登录,端内免登录+浏览器授权登录

[Python] 为企业网站应用接入钉钉鉴权&#xff0c;实现钉钉客户端内自动免登授权&#xff0c;浏览器中手动钉钉授权登录两种逻辑。 操作步骤 企业内部获得 开发者权限&#xff0c;没有的话先申请。 访问 钉钉开放平台-应用开发 创建一个 企业内部应用-钉钉应用。 打开应用…

[蓝桥杯 2023 国 Python A] 整数变换

P10985 [蓝桥杯 2023 国 Python A] 整数变换 题目背景 建议使用 PyPy3 提交本题。 题目描述 小蓝有一个整数 n n n。每分钟&#xff0c;小蓝的数都会发生变化&#xff0c;变为上一分钟的数 减去上一分钟的数的各个数位和。 例如&#xff0c;如果小蓝开始时的数为 23 23 …

【Linux】TCP_Wrappers+iptables实现堡垒机功能

规划 显示jumpserver的简单功能&#xff0c;大致的网络拓扑图如下 功能规划 & 拓扑结构 JumpServer&#xff08;堡垒机&#xff09;主要功能&#xff1a; 对访问目标服务器进行统一入口控制&#xff08;例如 nginx、mysql、redis&#xff09;。使用 iptables 做 NAT 转…

用HTML和CSS绘制佩奇:我不是佩奇

在这篇博客中&#xff0c;我将解析一个完全使用HTML和CSS绘制的佩奇(Pig)形象。这个项目展示了CSS的强大能力&#xff0c;仅用样式就能创造出复杂的图形&#xff0c;而不需要任何图片或JavaScript。 项目概述 这个名为"我不是佩奇"的项目是一个纯CSS绘制的卡通猪形象…

Spring 中 WebFlux 编写一个简单的 Controller

引言&#xff1a;响应式编程与 WebFlux 随着应用程序需要处理大量并发请求的情况越来越多&#xff0c;传统的 Servlet 编程模式可能无法满足高效和低延迟的需求。为了应对这种情况&#xff0c;Spring 5 引入了 WebFlux&#xff0c;一个基于响应式编程的 Web 框架&#xff0c;旨…

React十案例下

代码下载 登录模块 用户登录 页面结构 新建 Login 组件&#xff0c;对应结构: export default function Login() {return (<div className{styles.root}><NavHeader className{styles.header}>账号登录</NavHeader><form className{styles.form}>&…

100道C#高频经典面试题带解析答案——全面C#知识点总结

100道C#高频经典面试题带解析答案 以下是100道C#高频经典面试题及其详细解析&#xff0c;涵盖基础语法、面向对象编程、集合、异步编程、LINQ等多个方面&#xff0c;旨在帮助初学者和有经验的开发者全面准备C#相关面试。 &#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSD…

机动车号牌管理系统设计与实现(代码+数据库+LW)

摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对机动车号牌信息管理的提升&…

VMWare Workstation Pro17.6最新版虚拟机详细安装教程(附安装包教程)

目录 前言 一、VMWare虚拟机下载 二、VMWare虚拟机安装 三、运行虚拟机 前言 VMware 是全球领先的虚拟化技术与云计算解决方案提供商&#xff0c;通过软件模拟计算机硬件环境&#xff0c;允许用户在一台物理设备上运行多个独立的虚拟操作系统或应用。其核心技术可提升硬件…