瀚文机械键盘固件开发详解:HWKeyboard.h文件解析与应用
【手把手教程】从零开始的机械键盘固件开发:HWKeyboard.h详解
前言
大家好,我是键盘DIY爱好者Despacito0o!今天想和大家分享我开发的机械键盘固件核心头文件HWKeyboard.h
的设计思路和技术要点。这个项目是我多年来对键盘固件研究的心血结晶,希望能帮助更多对单片机开发和键盘DIY感兴趣的小伙伴入门!
本文将按模块详解每部分代码的具体作用和设计目的,让完全没有键盘开发经验的朋友也能一看就懂。后续文章会继续分享.cpp
文件的实现细节,形成一个完整系列。
一、为什么要自己开发键盘固件?
在开始代码解析前,先聊聊为什么要自己写键盘固件:
- 学习目的:深入理解单片机编程和嵌入式系统
- 个性化需求:市面上的键盘功能很难完全满足个人需求
- DIY乐趣:自己设计的键盘、自己写的固件,用起来格外有成就感
- 开发能力提升:涉及SPI通信、USB协议、RGB驱动等多种技术
二、整体架构设计目的
我设计这个键盘固件的主要目标是:
- 模块化设计:核心功能独立封装,便于扩展和维护
- 高效率:采用SPI批量读取按键状态,降低扫描延迟
- 丰富功能:支持RGB灯效、多层按键映射、触控条等
- 可定制性:预留足够扩展接口,方便用户个性化配置
下面就正式开始代码详解!
三、HWKeyboard类定义与初始化
#ifndef HELLO_WORD_KEYBOARD_FW_HW_KEYBOARD_H
#define HELLO_WORD_KEYBOARD_FW_HW_KEYBOARD_H#include "spi.h" // 引入SPI相关头文件,用于与74HC165和WS2812B通信// 硬件键盘类定义 - 整合键盘所有硬件控制功能
class HWKeyboard
{
public:// 构造函数,传入已初始化的SPI句柄explicit HWKeyboard(SPI_HandleTypeDef* _spi) :spiHandle(_spi) // 将SPI句柄存储到类成员变量{scanBuffer = &spiBuffer[1]; // scanBuffer指向spiBuffer的第2个字节,第1个字节用于SPI命令// 使能74HC165芯片(拉低CE引脚激活芯片)HAL_GPIO_WritePin(CE_GPIO_Port,CE_Pin,GPIO_PIN_RESET);// 初始化所有RGB灯为关闭状态for (uint8_t i = 0; i < HWKeyboard::LED_NUMBER; i++)SetRgbBufferByID(i, HWKeyboard::Color_t{0, 0, 0});}
模块设计目的:
- 构造函数设计初衷是简化键盘初始化流程,只需传入一个SPI句柄,就能完成所有硬件初始化
- SPI句柄传递意在将底层硬件控制与键盘逻辑分离,提高代码可移植性
- scanBuffer偏移设计是因为SPI传输需要命令字节,实际有效数据从第2个字节开始
- CE引脚控制用于激活74HC165移位寄存器,是扫描电路的核心控制信号
- RGB灯初始化为关闭是一个安全设计,避免上电瞬间灯光异常
四、常量定义模块
// 常量定义区 - 配置键盘硬件参数static const uint8_t IO_NUMBER = 11 * 8; // IO总数:11片74HC165,每片8位,共88个IO点static const uint8_t KEY_NUMBER = 82; // 按键总数:82个物理按键static const uint8_t TOUCHPAD_NUMBER = 6; // 触控条数量:6个电容触摸点static const uint8_t LED_NUMBER = 104; // RGB灯数量:104颗WS2812B可编程灯珠static const uint16_t KEY_REPORT_SIZE = 1 + 16; // 键盘HID报告长度:1字节报告ID + 16字节键盘数据static const uint16_t RAW_REPORT_SIZE = 1 + 32; // 原始报告长度:1字节报告ID + 32字节原始扫描数据static const uint16_t HID_REPORT_SIZE = KEY_REPORT_SIZE + RAW_REPORT_SIZE; // 完整HID报告总长度
模块设计目的:
- 使用静态常量明确定义硬件规格,方便后续修改适配不同的键盘布局
- IO_NUMBER设为88是为了预留足够的IO口,实际使用82个物理按键
- 分离KEY_NUMBER和IO_NUMBER是考虑到部分IO可能用于特殊功能而非按键
- TOUCHPAD_NUMBER定义触控点数量,用于后续触控条功能的实现
- HID报告大小严格按照USB标准制定,确保与操作系统兼容
五、键码枚举模块
// 键码枚举定义 - 遵循USB HID标准,方便进行按键映射enum KeyCode_t : int16_t{/*------------------------- HID报告数据定义 -------------------------*/LEFT_CTRL = -8,LEFT_SHIFT = -7,LEFT_ALT = -6,LEFT_GUI = -5, // 左侧修饰键(负值方便识别)RIGHT_CTRL = -4,RIGHT_SHIFT = -3,RIGHT_ALT = -2,RIGHT_GUI = -1, // 右侧修饰键(Windows/Command键)RESERVED = 0,ERROR_ROLL_OVER,POST_FAIL,ERROR_UNDEFINED, // 保留键值和错误码(0-3)A,B,C,D,E,F,G,H,I,J,K,L,M, // 字母键A-M(4-16)N,O,P,Q,R,S,T,U,V,W,X,Y,Z, // 字母键N-Z(17-29)NUM_1/*1!*/,NUM_2/*2@*/,NUM_3/*3#*/,NUM_4/*4$*/,NUM_5/*5%*/, // 数字键1-5(30-34)NUM_6/*6^*/,NUM_7/*7&*/,NUM_8/*8**/,NUM_9/*9(*/,NUM_0/*0)*/, // 数字键6-0(35-39)ENTER,ESC,BACKSPACE,TAB,SPACE, // 常用功能键(40-44)MINUS/*-_*/,EQUAL/*=+*/,LEFT_U_BRACE/*[{*/,RIGHT_U_BRACE/*]}*/, // 符号键(45-48)BACKSLASH/*\|*/,NONE_US/**/,SEMI_COLON/*;:*/,QUOTE/*'"*/, // 符号键(49-52)GRAVE_ACCENT/*`~*/,COMMA/*,<*/,PERIOD/*.>*/,SLASH/*/?*/, // 符号键(53-56)// ...(省略部分键码定义以简化显示)FN = 1000 // Fn功能键,使用1000作为特殊值(超出标准HID范围)/*------------------------- HID报告数据定义结束 -------------------------*/};
模块设计目的:
- 用枚举类型定义所有键码,使代码更易读,避免直接使用数字常量
- 修饰键使用负值,普通键使用正值,便于程序判断键的类型
- 严格遵循USB HID标准键码顺序,确保与操作系统完全兼容
- FN键使用1000这个特殊值,因为它是自定义功能键,不属于标准USB HID键码
- 注释中标明每个键的实际符号,提高代码可读性
六、颜色结构体与WS2812B协议定义
// RGB颜色结构体定义 - 存储单个灯珠的RGB值struct Color_t{uint8_t r; // 红色分量 (0-255)uint8_t g; // 绿色分量 (0-255)uint8_t b; // 蓝色分量 (0-255)};// WS2812B协议字节定义 - SPI模拟WS2812B时序关键enum SpiWs2812Byte_t : uint8_t{WS_HIGH = 0xFE, // 表示WS2812B协议中的"1"位 (二进制: 11111110)WS_LOW = 0xE0 // 表示WS2812B协议中的"0"位 (二进制: 11100000)};
模块设计目的:
- Color_t结构体简化RGB颜色处理,使设置灯光效果代码更加直观
- SpiWs2812Byte_t枚举是本固件的一个创新点,用SPI模拟WS2812B协议
- 0xFE和0xE0这两个特殊值经过精确计算,在特定SPI时钟频率下恰好满足WS2812B的时序要求
- 使用枚举而非直接使用数值,增强代码可读性和可维护性
技术拓展:为什么选择0xFE和0xE0作为WS2812B协议的高低位表示?
WS2812B要求"1"位的高电平持续时间约为800ns,低电平约为450ns;"0"位的高电平约为400ns,低电平约为850ns。按8MHz SPI时钟计算,一位传输需要125ns,因此0xFE(11111110)提供了7位高电平(875ns)和1位低电平(125ns),而0xE0(11100000)提供了3位高电平(375ns)和5位低电平(625ns),非常接近WS2812B的时序要求。
七、功能函数声明模块
// 功能函数声明区 - 键盘核心功能接口uint8_t* ScanKeyStates(); // 扫描按键状态,通过SPI读取74HC165数据void ApplyDebounceFilter(uint32_t _filterTimeUs = 100); // 应用按键消抖,消除机械开关抖动uint8_t* Remap(uint8_t _layer = 1); // 按键重映射,将物理按键转换为逻辑键码void SyncLights(); // 同步RGB灯光,通过SPI将数据发送到WS2812Bbool FnPressed(); // 检测Fn键是否按下,用于层切换bool KeyPressed(KeyCode_t _key); // 检测指定键码是否按下,用于组合键判断void Press(KeyCode_t _key); // 模拟按下某键,用于宏功能void Release(KeyCode_t _key); // 模拟释放某键,配合Press使用uint8_t* GetHidReportBuffer(uint8_t _reportId); // 获取HID报告缓冲区,用于USB通信uint8_t GetTouchBarState(uint8_t _id = 0); // 获取触控条状态,实现触控功能void SetRgbBufferByID(uint8_t _keyId, Color_t _color, float _brightness = 1); // 设置RGB灯颜色和亮度
模块设计目的:
- 提供完整的功能接口集,将复杂的底层操作封装成简单易用的函数
- 遵循单一职责原则,每个函数只负责一个明确的功能,便于调试和维护
- 参数默认值设计,如默认消抖时间100μs、默认使用第1层按键映射等,简化调用
- 函数命名清晰表达功能,如
ScanKeyStates
、ApplyDebounceFilter
等,提高代码可读性
八、按键映射表模块
// 按键映射表(多层)- 核心功能:实现按键多层定义int16_t keyMap[5][IO_NUMBER] = {// 物理按键到逻辑键的映射(0层,物理布局,标识PCB上按键的实际位置索引){67,61,60,58,59,52,55,51,50,49,48,47,46,3,80,81,64,57,62,63,53,54,45,44,40,31,26,18,2,19,70,71,66,65,56,36,37,38,39,43,42,41,28,1,15,74,73,72,68,69,29,30,35,34,33,32,24,0,14,76,77,78,79,16,20,21,22,23,27,25,17,4,13,12,8,75,9,10,7,11,6,5,86,84,82,87,85,83}, // TouchBar索引位置(最后6个值)// 第一层映射(标准QWERTY键盘布局,日常使用的基础层){ESC,F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12,PAUSE,GRAVE_ACCENT,NUM_1,NUM_2,NUM_3,NUM_4,NUM_5,NUM_6,NUM_7,NUM_8,NUM_9,NUM_0,MINUS,EQUAL,BACKSPACE,INSERT,TAB,Q,W,E,R,T,Y,U,I,O,P,LEFT_U_BRACE,RIGHT_U_BRACE,BACKSLASH,DELETE,CAP_LOCK,A,S,D,F,G,H,J,K,L,SEMI_COLON,QUOTE,ENTER,PAGE_UP,LEFT_SHIFT,Z,X,C,V,B,N,M,COMMA,PERIOD,SLASH,RIGHT_SHIFT,UP_ARROW,PAGE_DOWN,LEFT_CTRL,LEFT_GUI,LEFT_ALT,SPACE,RIGHT_ALT,FN,RIGHT_CTRL,LEFT_ARROW,DOWN_ARROW,RIGHT_ARROW},// 第二层映射(自定义功能层,按下Fn键时激活){ESC,F1,F2,F3,F4,F5,F6,F7,F8,F9,F10,F11,F12,PAUSE,GRAVE_ACCENT,NUM_1,NUM_2,NUM_3,NUM_4,NUM_5,NUM_6,NUM_7,NUM_8,NUM_9,NUM_0,MINUS,EQUAL,BACKSPACE,INSERT,TAB,A,B,C,D,E,F,G,H,I,J,LEFT_U_BRACE,RIGHT_U_BRACE,BACKSLASH,DELETE,CAP_LOCK,K,L,M,N,O,P,Q,R,S,SEMI_COLON,QUOTE,ENTER,PAGE_UP,LEFT_SHIFT,T,U,V,W,X,Y,Z,COMMA,PERIOD,SLASH,RIGHT_SHIFT,A,PAGE_DOWN,LEFT_CTRL,LEFT_GUI,LEFT_ALT,SPACE,RIGHT_ALT,FN,RIGHT_CTRL,LEFT_ARROW,DOWN_ARROW,RIGHT_ARROW}};
模块设计目的:
- 设计多层按键映射机制,实现一键多功能,大大提高键盘的可用性
- 第0层(物理层)存储每个按键在电路中的实际位置索引,不是功能映射
- 第1层是标准QWERTY键盘布局,作为默认使用层
- 第2层是演示用的自定义层,将字母区重新排列为ABCDEF顺序
- 预留5层空间(
keyMap[5][IO_NUMBER]
),为将来扩展更多功能层提供可能 - 使用前面定义的键码枚举值,使映射表更加清晰易读
知识拓展:多层按键映射的实际应用
多层按键映射是现代机械键盘的重要功能,允许在不增加物理按键的情况下实现更多功能:
- 媒体控制层:在Fn+F1~F12可以映射为音量控制、播放/暂停等多媒体功能
- 鼠标控制层:将WASD键映射为鼠标移动,实现无鼠标操作
- 宏功能层:将常用的按键组合映射到单个按键,提高工作效率
- 游戏专用层:为不同游戏定制专用按键布局
九、状态标志与私有成员变量
volatile bool isRgbTxBusy; // RGB灯DMA传输忙标志,用于中断同步bool isCapsLocked = false; // 大写锁定状态标志,用于CapsLock LED控制private:SPI_HandleTypeDef* spiHandle; // SPI句柄指针,用于底层硬件通信uint8_t spiBuffer[IO_NUMBER / 8 + 1]{}; // SPI接收缓冲区(每8个IO点占用1字节,外加1字节命令)uint8_t* scanBuffer; // 扫描缓冲区指针,指向spiBuffer中的有效数据部分uint8_t debounceBuffer[IO_NUMBER / 8 + 1]{}; // 按键消抖缓冲区,存储上一次稳定的按键状态uint8_t hidBuffer[HID_REPORT_SIZE]{}; // HID报告缓冲区,用于USB通信uint8_t remapBuffer[IO_NUMBER / 8]{}; // 按键重映射缓冲区,存储逻辑按键状态uint8_t rgbBuffer[LED_NUMBER][3][8]{}; // RGB灯数据缓冲区,3色各8位,存储WS2812B时序数据uint8_t wsCommit[64] = {0}; // WS2812B协议复位信号缓冲区(至少50µs低电平)uint8_t brightnessPreDiv = 2; // RGB亮度预分频(值为2表示亮度为1/4)
};#endif
模块设计目的:
- 公有标志变量:提供给外部访问的状态标志
isRgbTxBusy
设计为volatile
是因为它会在中断中被修改,避免编译器优化导致的问题isCapsLocked
用于跟踪大写锁定状态,便于实现CapsLock LED指示
- 私有成员变量:封装内部数据结构,防止外部直接访问
- 缓冲区设计遵循数据处理流程:
spiBuffer
→debounceBuffer
→remapBuffer
→hidBuffer
rgbBuffer
特殊设计为三维数组,精确映射WS2812B的时序要求wsCommit
是WS2812B协议结束信号,确保所有LED能正确锁存数据
- 缓冲区设计遵循数据处理流程:
十、实际应用详解
下面通过一个具体例子,演示这个键盘类的完整工作流程:
// 1. 包含必要头文件
#include "hw_keyboard.h"
#include "spi.h" // STM32 HAL库
#include "usbd_hid.h" // USB设备HID库// 2. 全局变量定义
extern SPI_HandleTypeDef hspi1; // 假设在CubeMX中已配置SPI1
extern USBD_HandleTypeDef hUsbDeviceFS; // USB设备句柄
HWKeyboard myKeyboard(&hspi1); // 创建键盘对象// 3. 自定义RGB灯效 - 呼吸灯效果
void breathingEffect(HWKeyboard &kb, uint32_t timeMs) {// 计算亮度值(0-255之间呼吸变化)uint8_t brightness = (sin(timeMs * 0.001f) + 1.0f) * 127.5f;// 设置所有按键为相同颜色,但亮度随时间变化for (uint8_t i = 0; i < HWKeyboard::LED_NUMBER; i++) {// 使用蓝色作为基础颜色,亮度随时间变化kb.SetRgbBufferByID(i, HWKeyboard::Color_t{0, 0, brightness});}// 同步灯光数据到WS2812Bkb.SyncLights();
}// 4. 主程序循环
void mainLoop() {uint32_t currentTime = HAL_GetTick(); // 获取当前时间(毫秒)static uint32_t lastReportTime = 0; // 上次发送HID报告的时间static uint32_t lastLightTime = 0; // 上次更新灯光的时间// 4.1 按键扫描和处理(1ms周期)if (currentTime - lastReportTime >= 1) {// 扫描按键状态myKeyboard.ScanKeyStates();// 应用消抖滤波myKeyboard.ApplyDebounceFilter();// 检测Fn键状态,确定当前使用的映射层uint8_t currentLayer = myKeyboard.FnPressed() ? 2 : 1;// 执行按键重映射,生成逻辑按键状态myKeyboard.Remap(currentLayer);// 获取键盘HID报告并通过USB发送uint8_t* keyReport = myKeyboard.GetHidReportBuffer(1);USBD_HID_SendReport(&hUsbDeviceFS, keyReport, HWKeyboard::KEY_REPORT_SIZE);// 更新上次发送时间lastReportTime = currentTime;}// 4.2 灯光效果更新(20ms周期,避免频繁更新造成闪烁)if (currentTime - lastLightTime >= 20 && !myKeyboard.isRgbTxBusy) {// 调用呼吸灯效果函数breathingEffect(myKeyboard, currentTime);// 更新上次灯光更新时间lastLightTime = currentTime;}
}
实现要点解析:
-
初始化流程
- 创建键盘对象时只需传入SPI句柄,简化初始化
- 构造函数自动完成硬件初始化,无需额外代码
-
按键处理流水线
- 扫描原始按键状态 → 消抖处理 → 层选择 → 重映射 → 生成HID报告 → 发送USB数据
- 整个流程清晰,每步对应一个函数调用
-
灯光效果实现
- 示例中实现了简单的呼吸灯效果,适合入门学习
- 使用
isRgbTxBusy
避免在DMA传输过程中修改灯光数据 - 灯光更新频率比按键扫描低,避免过度占用CPU资源
-
并行任务处理
- 按键扫描和灯光控制使用不同的更新周期,实现并行处理
- 时间戳机制确保任务按照预定间隔执行
十一、开发中的技术难点与解决方案
在开发这个键盘固件的过程中,我遇到了几个关键技术难点:
1. 按键抖动处理
难点:机械开关按下或释放时会产生数毫秒的抖动,导致一次按键被识别为多次。
解决方案:
- 实现了时间窗口消抖算法,记录状态变化点并延迟确认
- 在
ApplyDebounceFilter()
中,通过比较当前状态与上次稳定状态来判断变化 - 当检测到变化时,等待指定时间(默认100μs)后再次确认,确保状态稳定
2. SPI模拟WS2812B时序
难点:WS2812B要求严格的时序,传统方法需要精确的延时控制,难以实现。
解决方案:
- 创新地使用SPI接口发送特定字节模式来模拟WS2812B时序
WS_HIGH
和WS_LOW
两种字节模式在特定SPI频率下恰好满足时序要求- 通过DMA传输大量数据,避免CPU干预,实现稳定可靠的灯光控制
3. 多层按键映射实现
难点:如何高效地实现按键层切换,同时保证响应速度。
解决方案:
- 使用二维数组存储多层映射关系,第一维是层索引,第二维是按键索引
- 通过判断Fn键状态动态选择当前激活层
Remap()
函数实现从物理按键到逻辑按键的转换映射
十二、拓展知识:自制键盘的完整流程
想要完全自制一把机械键盘,整体流程大致如下:
-
设计键盘布局
- 选择键盘尺寸(60%、75%、TKL、全尺寸等)
- 设计键位布局(ANSI、ISO或自定义)
-
设计硬件电路
- 选择单片机(本项目使用STM32F103)
- 设计键盘矩阵电路(本项目使用74HC165方案)
- 规划RGB灯珠布局(WS2812B)
-
PCB设计与制作
- 使用KiCad或Altium Designer设计PCB
- 发送至PCB厂商制作
-
固件开发(本文重点)
- 编写按键扫描代码
- 实现消抖算法
- 开发多层按键映射功能
- 实现RGB灯效控制
- 开发USB通信模块
-
外壳设计与3D打印
- 使用Fusion 360等软件设计键盘外壳
- 3D打印或CNC加工外壳
-
组装与调试
- 焊接元器件
- 安装轴体与键帽
- 烧录固件并调试
总结
本文详细介绍了一个完整的机械键盘固件头文件设计,从硬件接口到功能实现,逐模块进行了解析。这个.h
文件为后续.cpp
文件的实现奠定了基础,定义了清晰的接口和数据结构。
通过这个项目,我们不仅实现了基本的键盘功能,还加入了RGB灯效、多层按键映射、触控条等高级特性。希望这篇教程能帮助更多对键盘DIY感兴趣的朋友入门,为你的定制键盘之旅提供参考。
在下一篇文章中,我将分享.cpp
文件的实现细节,敬请期待!
关键词:机械键盘固件、单片机编程、SPI通信、WS2812B驱动、多层按键映射、HID协议、DIY机械键盘、STM32开发
本文作者:Despacito0o | 出处:CSDN | 原创文章,欢迎转载,请注明出处
相关文章:
瀚文机械键盘固件开发详解:HWKeyboard.h文件解析与应用
【手把手教程】从零开始的机械键盘固件开发:HWKeyboard.h详解 前言 大家好,我是键盘DIY爱好者Despacito0o!今天想和大家分享我开发的机械键盘固件核心头文件HWKeyboard.h的设计思路和技术要点。这个项目是我多年来对键盘固件研究的心血结晶…...

学习路之PHP--webman安装及使用、webman/admin安装
学习路之PHP--webman安装及使用 一、安装webman二、运行三、安装webman/admin四、效果五、配置Nginx反向代理(生产环境:可选)六、使用 一、安装webman 准备: PHP > 8.1 Composer > 2.0 启用函数: putenv proc_o…...
Python打卡训练营day45——2025.06.05
作业:对resnet18在cifar10上采用微调策略下,用tensorboard监控训练过程。 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms, models from torch.utils.data import DataLoader import m…...
益莱储参加 Keysight World 2025,助力科技加速创新
全球领先的测试和测量技术解决方案提供商益莱储 / Electro Rent 再次受邀参加2025 年 6 月 26 日将于在 上海浦东嘉里大酒店隆重举行的 Keysight World Tech Day 2025 年度盛会,与是德科技深度合作,助力行业科技创新,为客户提供更经济、更灵活…...

基于cornerstone3D的dicom影像浏览器 第二十八章 LabelTool文字标记,L标记,R标记及标记样式设置
文章目录 前言一、L标记、R标记二、修改工具样式1. 样式的四种级别2. 导入annotation3. 示例1 - 修改toolGroup中的样式4. 示例2 - 修改viewport中的样式 三、可配置样式 前言 cornerstone3D 中的文字标记工具LabelTool,在添加文字标记时会弹出对话框让用户输入文字…...
基于责任链模式进行订单参数的校验
目录 概念 总体分为三步 我们定义责任链模式接口 各个节点的具体逻辑 用户校验器 库存校验器 商品校验器 把责任链编排在一起 概念 责任链模式 是一种行为设计模式 可以通过将一系列处理器按照顺序连接起来 使每个处理器都有机会处理请求 我理解的责任链的实现类似于…...

电路图识图基础知识-自耦变压器降压启动电动机控制电路(十六)
自耦变压器降压启动电动机控制电路 自耦变压器降压启动电动机控制电路是将自耦变压器的原边绕组接于电源侧,副边绕组接 于电机侧。电动机定子绕组启动时的电压为自耦变压器降压后得到的电压,这样可以减少电动 机的启动电流和启动力矩,当电动…...

神经网络与深度学习 网络优化与正则化
1.网络优化存在的难点 (1)结构差异大:没有通用的优化算法;超参数多 (2)非凸优化问题:参数初始化,逃离局部最优 (3)梯度消失(爆炸) …...

【Git系列】如何同步原始仓库的更新到你的fork仓库?
🎉🎉🎉欢迎来到我们的博客!无论您是第一次访问,还是我们的老朋友,我们都由衷地感谢您的到来。无论您是来寻找灵感、获取知识,还是单纯地享受阅读的乐趣,我们都希望您能在这里找到属于…...
PDF.js无法显示数字签名
问题 pdfjs加载pdf文件时无法显示数字签名 PDF.js 从 v2.9.359 版本开始正式支持数字签名的渲染与显示,此前版本需通过修改源代码实现基础兼容。 建议升级pdfjs组件大于等于v2.9.359 pdfjs历史版本:https://github.com/mozilla/pdf.js/releases pdfjs…...
spel 多层list嵌套表达式踩坑记
场景 Expression exp spelParser.parseExpression("#{#avgTable?.get(2)?.get(0)}", new TemplateParserContext()); String _result exp.getValue(evalContext, String.class);当avgTable?.get(2)为空时,Method threw java.lang.IndexO…...

深度强化学习驱动的智能爬取策略优化:基于网页结构特征的状态表示方法
传统网络爬虫依赖静态规则(如广度优先搜索)或启发式策略,在面对动态网页(如SPA单页应用)、复杂层级结构(如多层嵌套导航)及反爬机制时,常表现出爬取效率低下、覆盖率不足等问题。本文…...
【网络安全】XSS攻击
如果文章不足还请各位师傅批评指正! XSS攻击是什么? XSS全称是“Cross Site Scripting”,也就是跨站脚本攻击。想象一下,你正在吃一碗美味的面条,突然发现里面有一只小强!恶心不?XSS攻击就是这么…...

如何轻松将视频从安卓设备传输到电脑?
现在,我们可以轻松地使用安卓手机拍摄高分辨率视频。然而,这些视频会占用大量的存储空间。如果您想将视频从安卓设备传输到电脑以释放存储空间、编辑素材或只是备份记忆,可以使用本文介绍的 8 种实用方法来完成视频传输。 第 1 部分ÿ…...

时代星光推出战狼W60智能运载无人机,主要性能超市场同类产品一倍!
在刚刚结束的第九届世界无人机大会上,时代星光科技发布了其全新产品战狼W60智能运载无人机,并展示了基于战狼W60无人机平台的多种应用场景解决方案。据了解,该产品作为一款多旋翼无人机,主要性能参数均远超市场同类产品࿰…...

BUUCTF[极客大挑战 2019]Secret File 1题解
[极客大挑战 2019]Secret File 1 分析:解题界面1:界面二:界面3: 总结: 分析: 事后来看,这道题主打一个走一步看一步。我们只能从题目的标题中猜到,这道题与文件有关。 解题 界面1:…...

Odoo电子邮件使用配置指南
在Odoo中配置邮件收发功能需要设置SMTP发件服务器和IMAP/POP3收件服务器,并确保DNS记录(如SPF、DKIM)正确,以避免邮件被标记为垃圾邮件。以下指南是详细配置步骤: 1. 配置出站邮件(SMTP) 1.1 使…...
自定义Spring Boot Starter的全面指南
自定义Starter的核心优势 开发效率提升 通过将通用依赖和配置封装至Starter中,开发者可显著减少重复性工作: 消除样板代码:自动包含基础依赖(如Web、JPA等),无需在每个项目中手动添加 // build.gradle配…...
Spring Security中的认证实现
Spring Security认证架构概述 Spring Security的认证流程建立在精心设计的组件协作体系之上。图3.1展示了该框架实现认证过程的核心架构,这个架构由多个关键组件构成,理解这些组件的交互关系对于任何Spring Security实现都至关重要。 认证流程核心组件…...

MacOS解决局域网“没有到达主机的路由 no route to host“
可能原因:MacOS 15新增了"本地网络"访问权限,在 APP 第一次尝试访问本地网络的时候会请求权限,可能顺手选择了关闭。 解决办法:给想要访问本地网络的 APP (例如 terminal、Navicat、Ftp)添加访问…...

找到每一个单词+模拟的思路和算法
如大家所知,我们可以对给定的字符串 sentence 进行一次遍历,找出其中的每一个单词,并根据题目的要求进行操作。 在寻找单词时,我们可以使用语言自带的 split() 函数,将空格作为分割字符,得到所有的单词。为…...
澄清 STM32 NVIC 中断优先级
我们来澄清一下 STM32 NVIC 中断优先级的行为,特别是在抢占优先级和响应优先级(子优先级)都相同的情况下: 核心规则回顾: 抢占优先级 (Preemption Priority): 决定了中断是否可以打断另一个正在执行的中断。 高抢占优…...

2025东南亚跨境选择:Lazada VS. Shopee深度对比
东南亚电商市场持续爆发,2025年预计规模突破2000亿美元。对跨境卖家而言,Lazada与Shopee仍是两大核心战场,但平台生态与竞争格局已悄然变化。深入对比,方能制胜未来。 一、平台基因与核心优势对比 维度 Lazada (阿里系) Shopee …...
如何做好一份技术文档?(上篇)
如何做好一份技术文档?(上篇) 上篇:技术文档的基石设计 ——构建可持续迭代的文档体系 文档金字塔模型 [概念层] 为什么 —— 设计理念/适用场景 ▲ [指南层] 怎么做 —— 任务教程/最佳实践 ▲ [参考层] 是什么 ——…...
StarRocks
StarRocks 是一款由中国公司 北京快立方科技有限公司(Fenruilab)开发的 高性能分析型数据库,专注于解决大规模数据分析和实时查询场景的需求。它基于 MPP(大规模并行处理)架构设计,具备高并发、低延迟、易扩…...

Java-39 深入浅出 Spring - AOP切面增强 核心概念 通知类型 XML+注解方式 附代码
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...

.NET 8集成阿里云短信服务完全指南【短信接口】
文章目录 前言一、准备工作1.1 阿里云账号准备1.2 .NET 8项目创建 二、集成阿里云短信SDK2.1 安装NuGet包2.2 配置阿里云短信参数2.3 创建配置类 三、实现短信发送服务3.1 创建短信服务接口3.2 实现短信服务3.3 注册服务 四、创建控制器五、测试与优化5.1 单元测试5.2 性能优化…...

实现仿中国婚博会微信小程序
主要功能: 1、完成底部标签导航设计、首页海报轮播效果设计和宫格导航设计,如图1所示 2、在首页里,单击全部分类宫格导航的时候,会进入到全部分类导航界面,把婚博会相关内容的导航集成到一个界面里,如图2…...
互联网大厂Java面试:从Spring Cloud到Kafka的技术考察
场景:互联网大厂Java求职者面试 面试官与谢飞机的对话 面试官:我们先从基础开始,谢飞机,你能简单介绍一下Java SE和Java EE的区别吗? 谢飞机:哦,这个简单。Java SE是标准版,适合桌…...
策略梯度核心:Advantage 与 GAE 原理详解
一.Advantage(优势函数)详解 什么是 Advantage? Advantage 表示当前动作比平均水平好多少。 其定义公式为: A ( s , a ) Q ( s , a ) − V ( s ) A(s, a) Q(s, a) - V(s) A(s,a)Q(s,a)−V(s) 其中: Q ( s , a ) …...