单片机开发----一个简单的Boot

文章目录

  • 一、设计思路
      • **整体框架设计**
      • **各文件/模块功能解析**
        • 1. `main.c`(主程序入口,核心控制)
        • 2. 隐含的核心模块(框架中未展示但必备)
      • **设计亮点**
  • 二、代码
    • bootloader.h
    • bootloader.c
    • flash.c
    • main.c

一、设计思路

整体框架设计

该Bootloader采用分层模块化架构,核心逻辑可分为入口控制层核心功能层硬件适配层,整体流程如下:

  1. 系统初始化 → 2. 检查启动条件(进入Bootloader还是跳转应用) → 3. 执行对应流程(Bootloader模式/应用程序)
  2. 内置故障处理机制,确保极端情况下系统可恢复

各文件/模块功能解析

1. main.c(主程序入口,核心控制)

这是Bootloader的入口文件,负责统筹整个启动流程,关键功能包括:

  • 系统初始化

    • 调用 system_init():完成单片机底层硬件初始化(如时钟、GPIO、中断向量表设置等),为Bootloader运行提供基础环境。
    • 调用 bootloader_init():初始化Bootloader核心组件(通信模块、Flash驱动、校验模块等)。
  • 启动条件判断

    • bootloader_check_enter_condition():决定系统进入Bootloader模式还是直接启动应用程序。
      常见判断条件:按键触发(用户主动升级)、应用程序校验失败(自动进入修复模式)、特定寄存器标志(远程升级指令)等。
  • 分支执行

    • 若需进入Bootloader:调用 bootloader_run() 进入升级主循环(接收固件、校验、写入Flash等)。
    • 若无需进入:调用 bootloader_jump_to_app() 跳转到用户应用程序(核心功能,需关闭Bootloader占用的外设和中断,确保应用正常运行)。
  • 故障兜底

    • 重定义 HardFault_Handler():当系统发生致命错误(如硬件故障、应用程序崩溃)时,强制进入Bootloader模式,避免设备变砖,提高可靠性。
2. 隐含的核心模块(框架中未展示但必备)

根据Bootloader的功能描述,完整框架还包含以下关键模块(通常在 bootloader.h 及对应源文件中实现):

  • 通信模块

    • 支持UART、SPI等多种接口,负责与上位机(如PC工具、网关)通信,接收升级命令和固件数据。
    • 实现数据帧解析、超时重传、校验和等机制,确保固件传输可靠(尤其在工业环境等干扰较强的场景)。
  • Flash操作模块

    • 封装Flash擦除、写入、读取等底层操作,适配不同单片机的Flash特性(如扇区大小、保护机制)。
    • 包含擦写失败重试逻辑,避免因瞬时干扰导致Flash损坏。
  • 固件校验模块

    • 采用CRC32等算法验证接收到的固件完整性,防止因传输错误导致的升级失败。
    • 支持版本检查功能,避免降级或重复升级(可根据需求开启)。
  • 安全与保护模块

    • 内存边界检查:防止固件写入超出应用程序分区的地址(避免覆盖Bootloader自身)。
    • 可选加密/解密功能:对传输的固件进行加密,防止固件被窃取或篡改(适合商业设备)。
  • 失败恢复机制

    • 双分区设计:预留两个应用程序分区(当前分区+备份分区),升级失败时自动回滚到备份分区。
    • 升级标志位:在Flash中设置升级状态标志(如“升级中”“升级完成”),若中途断电,重启后可根据标志位恢复流程。

设计亮点

  1. 高可靠性

    • 故障兜底:硬错误中断强制进入Bootloader,避免设备无法启动。
    • 多重校验:从通信到写入全流程校验,减少升级失败概率。
  2. 多场景适配

    • 多通信接口:支持UART(有线)、SPI(外设扩展)等,可根据硬件场景选择。
    • 灵活的启动条件:兼顾用户主动升级和自动修复(如应用损坏时)。
  3. 可扩展性

    • 模块化设计:各功能模块解耦,便于添加新通信方式(如CAN、以太网)或安全功能(如加密)。
    • 硬件无关性:核心逻辑与硬件分离,移植到不同单片机时只需修改底层驱动。

该框架适合工业控制、物联网设备等对稳定性要求高的场景,通过分层设计和容错机制,平衡了可靠性与灵活性。实际使用时,需根据具体单片机型号(如STM32、GD32等)适配Flash驱动和外设初始化代码。

二、代码

bootloader.h

#ifndef BOOTLOADER_H
#define BOOTLOADER_H#include <stdint.h>
#include <stdbool.h>
#include <string.h>// 配置参数 - 根据实际硬件修改
#define APP_START_ADDR         0x08008000      // 应用程序起始地址
#define BOOTLOADER_VERSION     "1.2.0"         // Bootloader版本
#define MAX_FIRMWARE_SIZE      (512 * 1024)    // 最大固件大小
#define COMM_BUFFER_SIZE       1024            // 通信缓冲区大小
#define CRC32_POLYNOMIAL       0xEDB88320UL    // CRC32多项式// 固件信息结构体
typedef struct {uint32_t magic;              // 魔术字,用于验证uint32_t firmware_size;      // 固件大小uint32_t crc32;              // 固件CRC32校验值uint8_t  version[16];        // 固件版本uint8_t  reserved[12];       // 预留空间
} firmware_info_t;// 通信命令
typedef enum {CMD_GET_VERSION = 0x01,      // 获取版本信息CMD_START_UPDATE = 0x02,     // 开始更新CMD_SEND_DATA = 0x03,        // 发送数据CMD_VERIFY = 0x04,           // 验证固件CMD_BOOT_APP = 0x05,         // 启动应用程序CMD_ERASE = 0x06,            // 擦除FlashCMD_NACK = 0xFF,             // 否定应答CMD_ACK = 0x00               // 肯定应答
} comm_command_t;// 升级状态
typedef enum {BOOT_IDLE = 0,               // 空闲状态BOOT_WAITING,                // 等待数据BOOT_RECEIVING,              // 接收中BOOT_VERIFYING,              // 验证中BOOT_FLASHING,               // 烧写中BOOT_COMPLETE,               // 完成BOOT_ERROR                   // 错误
} boot_state_t;// 通信接口类型
typedef enum {COMM_UART = 0,               // UART通信COMM_SPI = 1                 // SPI通信
} comm_interface_t;// 错误代码
typedef enum {ERROR_NONE = 0,              // 无错误ERROR_CRC = 1,               // CRC错误ERROR_SIZE = 2,              // 大小错误ERROR_FLASH = 3,             // Flash错误ERROR_COMM = 4,              // 通信错误ERROR_VERIFY = 5,            // 验证错误ERROR_INVALID_CMD = 6        // 无效命令
} error_code_t;// 函数声明
void bootloader_init(void);
bool bootloader_check_enter_condition(void);
void bootloader_run(void);
void bootloader_jump_to_app(void);
uint32_t crc32_calculate(const uint8_t *data, uint32_t length);
error_code_t flash_erase_sector(uint32_t address);
error_code_t flash_write_data(uint32_t address, const uint8_t *data, uint32_t length);#endif // BOOTLOADER_H

bootloader.c

#include "bootloader.h"
#include "uart.h"
#include "spi.h"
#include "flash.h"
#include "gpio.h"
#include "timer.h"// 全局变量
static boot_state_t current_state = BOOT_IDLE;
static comm_interface_t current_interface = COMM_UART;
static uint8_t comm_buffer[COMM_BUFFER_SIZE];
static uint8_t *firmware_buffer = (uint8_t *)APP_START_ADDR;  // 固件缓冲区
static firmware_info_t current_firmware_info;
static uint32_t received_bytes = 0;
static uint32_t timeout_counter = 0;// 检查是否进入Bootloader模式
bool bootloader_check_enter_condition(void) {// 条件1: 特定引脚被拉低if (gpio_read_boot_pin() == 0) {return true;}// 条件2: 应用程序区域为空if (*(volatile uint32_t *)APP_START_ADDR == 0xFFFFFFFF) {return true;}// 条件3: 应用程序请求更新 (通过特定内存位置标记)volatile uint32_t *update_flag = (volatile uint32_t *)0x20000000;  // 示例地址if (*update_flag == 0xDEADBEEF) {*update_flag = 0x00000000;  // 清除标志return true;}return false;
}// 初始化Bootloader
void bootloader_init(void) {// 初始化硬件gpio_init();uart_init(115200);  // 默认UART波特率spi_init();flash_init();timer_init(10);     // 10ms定时器// 初始化状态current_state = BOOT_IDLE;received_bytes = 0;timeout_counter = 0;memset(&current_firmware_info, 0, sizeof(firmware_info_t));// 输出启动信息uart_send_string("Bootloader " BOOTLOADER_VERSION " started\r\n");uart_send_string("Press and hold BOOT pin to enter update mode\r\n");
}// CRC32计算
uint32_t crc32_calculate(const uint8_t *data, uint32_t length) {uint32_t crc = 0xFFFFFFFFUL;for (uint32_t i = 0; i < length; i++) {crc ^= data[i];for (uint8_t j = 0; j < 8; j++) {if (crc & 1) {crc = (crc >> 1) ^ CRC32_POLYNOMIAL;} else {crc >>= 1;}}}return crc ^ 0xFFFFFFFFUL;
}// 发送响应
static void send_response(comm_command_t cmd, error_code_t error) {uint8_t response[3] = {0xAA, (uint8_t)cmd, (uint8_t)error};if (current_interface == COMM_UART) {uart_send_bytes(response, 3);} else {spi_send_bytes(response, 3);}
}// 处理接收到的命令
static void process_command(uint8_t *data, uint16_t length) {if (length < 1) {send_response(CMD_NACK, ERROR_INVALID_CMD);return;}comm_command_t cmd = (comm_command_t)data[0];error_code_t error = ERROR_NONE;switch (cmd) {case CMD_GET_VERSION: {// 发送Bootloader版本uint8_t response[32];response[0] = 0xAA;response[1] = CMD_GET_VERSION;strncpy((char *)&response[2], BOOTLOADER_VERSION, sizeof(response) - 2);response[sizeof(response) - 1] = '\0';if (current_interface == COMM_UART) {uart_send_bytes(response, strlen((char *)response) + 2);} else {spi_send_bytes(response, strlen((char *)response) + 2);}break;}case CMD_START_UPDATE: {if (length < sizeof(firmware_info_t) + 1) {error = ERROR_INVALID_CMD;break;}// 复制固件信息memcpy(&current_firmware_info, &data[1], sizeof(firmware_info_t));// 检查魔术字if (current_firmware_info.magic != 0x12345678) {error = ERROR_VERIFY;break;}// 检查固件大小if (current_firmware_info.firmware_size > MAX_FIRMWARE_SIZE) {error = ERROR_SIZE;break;}// 擦除应用程序区域error = flash_erase_sector(APP_START_ADDR);if (error == ERROR_NONE) {current_state = BOOT_WAITING;received_bytes = 0;uart_send_string("Ready to receive firmware...\r\n");}break;}case CMD_SEND_DATA: {if (current_state != BOOT_WAITING && current_state != BOOT_RECEIVING) {error = ERROR_INVALID_CMD;break;}if (length < 2) {  // 至少1字节命令 + 1字节数据error = ERROR_COMM;break;}uint16_t data_len = length - 1;  // 减去命令字节// 检查是否超出固件大小if (received_bytes + data_len > current_firmware_info.firmware_size) {error = ERROR_SIZE;break;}// 写入数据到缓冲区memcpy(&firmware_buffer[received_bytes], &data[1], data_len);received_bytes += data_len;current_state = BOOT_RECEIVING;timeout_counter = 0;  // 重置超时计数器uart_send_stringf("Received %d/%d bytes\r\n", received_bytes, current_firmware_info.firmware_size);break;}case CMD_VERIFY: {if (current_state != BOOT_RECEIVING) {error = ERROR_INVALID_CMD;break;}// 检查是否接收完整if (received_bytes != current_firmware_info.firmware_size) {error = ERROR_SIZE;break;}// 计算CRC32并验证uint32_t calculated_crc = crc32_calculate(firmware_buffer, current_firmware_info.firmware_size);if (calculated_crc != current_firmware_info.crc32) {error = ERROR_CRC;break;}current_state = BOOT_VERIFYING;uart_send_string("Firmware verified successfully\r\n");break;}case CMD_BOOT_APP: {if (current_state == BOOT_VERIFYING || current_state == BOOT_COMPLETE) {uart_send_string("Jumping to application...\r\n");send_response(CMD_BOOT_APP, ERROR_NONE);// 短延时确保数据发送完成for (volatile int i = 0; i < 1000000; i++);bootloader_jump_to_app();} else {error = ERROR_INVALID_CMD;}break;}default:error = ERROR_INVALID_CMD;break;}send_response(cmd, error);
}// 处理通信数据
static void process_communication(void) {uint16_t bytes_received = 0;// 检查UART数据if (current_interface == COMM_UART) {bytes_received = uart_receive_bytes(comm_buffer, COMM_BUFFER_SIZE);}// 检查SPI数据else if (current_interface == COMM_SPI) {bytes_received = spi_receive_bytes(comm_buffer, COMM_BUFFER_SIZE);}if (bytes_received > 0) {process_command(comm_buffer, bytes_received);timeout_counter = 0;  // 重置超时计数器}
}// 定时器回调函数 - 处理超时
void timer_callback(void) {if (current_state != BOOT_IDLE) {timeout_counter++;// 5秒超时 (500 * 10ms)if (timeout_counter >= 500) {uart_send_string("Timeout occurred\r\n");current_state = BOOT_IDLE;timeout_counter = 0;}}
}// 运行Bootloader主循环
void bootloader_run(void) {uart_send_string("Entering bootloader mode\r\n");while (1) {// 处理通信process_communication();// 状态机处理switch (current_state) {case BOOT_VERIFYING:// 验证完成,等待启动应用程序current_state = BOOT_COMPLETE;uart_send_string("Update completed. Send BOOT_APP command to start application\r\n");break;case BOOT_ERROR:// 错误状态,重置uart_send_string("Error occurred. Resetting...\r\n");current_state = BOOT_IDLE;break;default:break;}// 空闲状态下检查是否自动启动应用程序if (current_state == BOOT_IDLE) {timeout_counter++;// 3秒超时自动启动应用 (300 * 10ms)if (timeout_counter >= 300) {uart_send_string("Timeout, booting application...\r\n");bootloader_jump_to_app();}}}
}// 跳转到应用程序
void bootloader_jump_to_app(void) {// 检查应用程序是否存在if ((*(volatile uint32_t *)APP_START_ADDR) & 0x2FFE0000) {// 关闭所有外设uart_deinit();spi_deinit();timer_deinit();// 设置栈指针和程序计数器void (*app_reset_handler)(void) = (void *)*(volatile uint32_t *)(APP_START_ADDR + 4);// 设置主栈指针__set_MSP(*(volatile uint32_t *)APP_START_ADDR);// 跳转到应用程序app_reset_handler();}// 如果应用程序不存在,停留在Bootloaderuart_send_string("No valid application found\r\n");while (1);
}

flash.c

#include "bootloader.h"
#include "stm32f4xx_hal.h"  // 根据实际MCU修改// 初始化Flash
void flash_init(void) {// 初始化Flash外设HAL_FLASH_Unlock();// 清除所有标志__HAL_FLASH_CLEAR_FLAG(FLASH_FLAG_EOP | FLASH_FLAG_OPERR | FLASH_FLAG_WRPERR | FLASH_FLAG_PGAERR | FLASH_FLAG_PGPERR | FLASH_FLAG_PGSERR);HAL_FLASH_Lock();
}// 擦除Flash扇区
error_code_t flash_erase_sector(uint32_t address) {// 检查地址是否在应用程序区域if (address < APP_START_ADDR) {return ERROR_FLASH;}FLASH_EraseInitTypeDef erase_init;uint32_t sector_error;error_code_t result = ERROR_NONE;// 解锁Flashif (HAL_FLASH_Unlock() != HAL_OK) {return ERROR_FLASH;}// 确定要擦除的扇区// 注意:不同MCU的扇区划分不同,需要根据实际情况修改if (address < 0x08020000) {erase_init.Sector = FLASH_SECTOR_2;} else if (address < 0x08040000) {erase_init.Sector = FLASH_SECTOR_3;} else {// 根据实际情况添加更多扇区判断erase_init.Sector = FLASH_SECTOR_4;}erase_init.TypeErase = FLASH_TYPEERASE_SECTORS;erase_init.VoltageRange = FLASH_VOLTAGE_RANGE_3;erase_init.NbSectors = 1;  // 可以根据需要擦除多个扇区// 执行擦除if (HAL_FLASHEx_Erase(&erase_init, &sector_error) != HAL_OK) {result = ERROR_FLASH;}// 锁定FlashHAL_FLASH_Lock();return result;
}// 写入数据到Flash
error_code_t flash_write_data(uint32_t address, const uint8_t *data, uint32_t length) {// 检查地址和长度if (address < APP_START_ADDR || (address + length) > (APP_START_ADDR + MAX_FIRMWARE_SIZE)) {return ERROR_FLASH;}error_code_t result = ERROR_NONE;uint32_t i = 0;// 解锁Flashif (HAL_FLASH_Unlock() != HAL_OK) {return ERROR_FLASH;}// 按字写入数据while (i < length) {uint32_t data_word;// 处理最后几个字节(不足4字节)if (length - i < 4) {data_word = 0;memcpy(&data_word, &data[i], length - i);} else {memcpy(&data_word, &data[i], 4);}// 写入数据if (HAL_FLASH_Program(FLASH_TYPEPROGRAM_WORD, address + i, data_word) != HAL_OK) {result = ERROR_FLASH;break;}i += 4;}// 锁定FlashHAL_FLASH_Lock();return result;
}

main.c

#include "bootloader.h"
#include "system.h"int main(void) {// 初始化系统system_init();// 初始化Bootloaderbootloader_init();// 检查是否需要进入Bootloader模式if (bootloader_check_enter_condition()) {// 进入Bootloader主循环bootloader_run();} else {// 直接跳转到应用程序bootloader_jump_to_app();}// 正常情况下不会到达这里while (1);
}// 重定向异常处理到Bootloader
void HardFault_Handler(void) {// 发生硬错误时,尝试进入Bootloaderbootloader_init();bootloader_run();while (1);
}

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

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

相关文章

Day2p2 夏暮客的Python之路

day2p2 The Hard Way to learn Python 文章目录day2p2 The Hard Way to learn Python前言一、提问和提示1.1 关于raw_input()1.2 关于input()二、参数、解包、变量2.1 解读参数2.2 解读解包2.3 解读变量2.4 实例2.5 模块和功能2.6 练习前言 author&#xff1a;SummerEnd date…

【C++设计模式】第二篇:策略模式(Strategy)--从基本介绍,内部原理、应用场景、使用方法,常见问题和解决方案进行深度解析

C设计模式系列文章目录 【第一篇】C单例模式–懒汉与饿汉以及线程安全 【C设计模式】第二篇&#xff1a;策略模式&#xff08;Strategy&#xff09;--从基本介绍&#xff0c;内部原理、应用场景、使用方法&#xff0c;常见问题和解决方案进行深度解析一、策略模式的基本介绍1.…

四十岁编程:热爱、沉淀与行业的真相-优雅草卓伊凡

四十岁编程&#xff1a;热爱、沉淀与行业的真相-优雅草卓伊凡今日卓伊凡收到一个问题&#xff1a;「如何看待40岁还在撸代码的程序员&#xff1f;」这让我不禁思考&#xff1a;从何时起&#xff0c;年龄成了程序员职业中的敏感词&#xff1f;在互联网的某些角落&#xff0c;弥漫…

pycharm解释器使用anaconda建立的虚拟环境里面的python,无需系统里面安装python。

Anaconda建立的虚拟环境可以在虚拟环境里设置任何的python版本&#xff0c;pycharm解释器使用anaconda建立的虚拟环境里面的python&#xff0c;比如anaconda建立的虚拟环境1、虚拟环境2&#xff0c;pycharm解释器使用anaconda建立虚拟环境1也可以使用虚拟环境2&#xff0c;根本…

机器学习:后篇

目录 一、KNN算法-分类 样本距离 KNN算法原理 缺点 API 二、模型选择与调优 交叉验证 保留交叉验证(HoldOut) k-折交叉验证(K-fold) 分层k-折交叉验证(Stratified k-fold) 其他交叉验证 三、朴素贝叶斯-分类 理论介绍 拉普拉斯平滑系数 API 四、决策树-分类 理论…

C++17无锁编程实战

在多线程编程里&#xff0c;“锁” 这东西就像把双刃剑 —— 用好了能保数据安全&#xff0c;用不好就麻烦了&#xff1a;大粒度的锁把并发度压得死死的&#xff0c;稍不注意加错锁还可能搞出死锁&#xff0c;程序直接 “僵住”。 但如果能摆脱锁&#xff0c;搞出支持安全并发…

SVT-AV1 svt_aom_motion_estimation_kernel 函数分析

void *svt_aom_motion_estimation_kernel(void *input_ptr) // 运动估计内核主函数&#xff0c;接收线程输入参数{// 从输入参数中获取线程上下文指针EbThreadContext * thread_ctx (EbThreadContext *)input_ptr;// 从线程上下文中获取运动估计上下文指针MotionEstimationCon…

关于NET Core jwt Bearer Token 验证的大坑,浪费3个小时,给各位兄弟搭个桥。

net core 使用jwt Bearer Token 认证获取接口访问权限&#xff0c;前期一阵操作没任何问题&#xff0c;等认证接口写的好了&#xff0c;通过PostMan测试的时候&#xff0c;总是报一个 IDX14102: Unable to decode the header eyJhbGciOiJIUzI1NiIsInR5cCI6 &#xff0c;错误&a…

系统架构设计师备考第14天——业务处理系统(TPS)

一、TPS的核心概念与定位 1. 定义与演进 定义&#xff1a;TPS&#xff08;Transaction Processing System&#xff09;又称电子数据处理系统&#xff08;EDPS&#xff09;&#xff0c;是处理企业日常事务的信息系统&#xff0c;如财务、库存、销售等局部业务管理。历史地位&…

目标检测系列-Yolov5下载及运行

由于项目需要&#xff0c;最近一直在看目标检测相关的资料&#xff0c;不过纸上得来终觉浅&#xff0c;绝知此事要躬行啊。从今日起&#xff0c;将学习的过程记录一下&#xff0c;作为以后用来复习的材料吧。 我想最快的学习便是直接动手做项目&#xff0c;因此今天就将yolov5模…

Linux内核进程管理子系统有什么第四十二回 —— 进程主结构详解(38)

接前一篇文章&#xff1a;Linux内核进程管理子系统有什么第四十一回 —— 进程主结构详解&#xff08;37&#xff09; 本文内容参考&#xff1a; Linux内核进程管理专题报告_linux rseq-CSDN博客 《趣谈Linux操作系统 核心原理篇&#xff1a;第三部分 进程管理》—— 刘超 《…

基于飞算JavaAI的学生成绩综合统计分析系统

第一章&#xff1a;项目概述与背景 1.1 项目背景与意义 在教育信息化飞速发展的今天&#xff0c;学生成绩管理已成为学校教学管理的核心环节。传统的学生成绩管理多依赖于手工操作或基础的信息管理系统&#xff0c;存在数据处理效率低、统计分析功能薄弱、数据可视化缺失等问题…

C++程序员必懂:std::bad_function_call异常的真相与预防秘诀

std::bad_function_call 是 C++ 标准库在 <functional> 头文件中定义的一个异常类型。当程序试图调用一个未持有任何可调用目标(即处于“空状态”)的 std::function 对象时,此异常会被抛出。本文将深入探讨该异常的根本原因、详细的触发场景,并提供一套完整的预防与处…

Html重绘和重排

在网页渲染过程中&#xff0c;重绘&#xff08;repaint&#xff09;和重排&#xff08;reflow&#xff09;是两个重要的概念。理解它们的区别和优化方法对于提升网页性能至关重要。重排&#xff08;Reflow&#xff09;重排是指当页面元素的位置、尺寸等几何属性发生变化时&…

Redis 客户端与服务器:银行的 “客户服务系统” 全流程

目录 一、Redis 客户端&#xff1a;银行的 “客户档案” 二、客户端关闭&#xff1a;银行的 “终止服务规则” 三、命令处理流程&#xff1a;柜员办理业务的 “标准步骤” 1. 接收申请单&#xff08;读取命令请求&#xff09; 2. 确认业务类型&#xff08;查找命令&#x…

HTML图片标签及路径详解

图片是网页内容的重要组成部分&#xff0c;能够使页面更加生动直观。在HTML中&#xff0c;使用<img>标签插入图片&#xff0c;而正确设置图片路径则是确保图片能够正常显示的关键。一、图片标签&#xff08;<img>&#xff09;1. 图片标签的基本语法<img>标签…

【数据库通过日志恢复数据解读】

在数据库恢复机制中&#xff0c;日志文件是实现事务原子性、持久性和崩溃恢复的核心组件。以下通过具体示例和解读方法&#xff0c;结合主流数据库系统的实现细节&#xff0c;详细说明日志文件的内容与分析逻辑。 一、日志文件的核心作用与结构 日志文件通过**预写式日志&#…

【面试题】搜索准确性不高你怎么排查?

系统性排查框架&#xff1a;数据层检查 索引覆盖率&#xff1a;检查文档是否全部正确索引数据新鲜度&#xff1a;确认索引更新频率和延迟文档质量&#xff1a;分析被索引内容的质量和完整性查询理解层 分词分析&#xff1a;检查查询分词是否正确意图识别&#xff1a;验证意图分…

当AI开始“偷吃”用户数据并拼装功能模块:初级开发者的脑洞保卫战与老码农的灵魂蘸料

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;今天给大家分享一篇文章&#xff01;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;创作不易&#xff0c;如果能帮助到大家或者给大家一些灵感和启发&#xff0c;欢迎收藏关注哦 &#x1f495; 目录当AI开…

Flowable——流程定义与部署(RepositoryService)

文章目录 前言 参考资料 流程图的部署、查询与删除 流程图的准备 流程的部署 指定已部署流程信息查询 删除指定已部署流程 结语 前言 前篇博客具体说了怎么使用flowable去自动建表,以及搭建配置前端UI绘图的界面。本篇博客主要说明对于已绘制好的流程图如何去进行部署。 Flow…