ESP32-CAM开发板 使用 sqlite3 数据库存储数据记录

news2024/11/24 16:02:14

忘记过去,超越自己

  • ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️
  • ❤️ 本篇创建记录 2023-05-29 ❤️
  • ❤️ 本篇更新记录 2023-05-29 ❤️
  • 🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝
  • 🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请留言轰炸哦!及时修正!感谢支持!
  • 🔥 Arduino ESP8266教程累计帮助过超过1W+同学入门学习硬件网络编程,入选过选修课程,刊登过无线电杂志 🔥

快速导读

    • 1、前言
    • 2、esp32_arduino_sqlite3_lib 库
    • 3、硬件说明
    • 4、API说明
      • 4.1 sqlite3_initialize —— 初始化Sqlite数据库引擎
      • 4.2 sqlite3_open —— 打开或创建一个 SQLite 数据库文件
      • 4.3 sqlite3_exec —— 执行 SQL 命令
      • 4.4 sqlite3_free —— 释放内存
      • 4.5 sqlite3_close —— 关闭先前打开的数据库连接
      • 4.6 sqlite3_errmsg —— 用于获取最近一次 SQLite API 调用产生的错误信息
      • 4.7 sqlite3_errcode —— 获取最近一次 SQLite API 调用产生的标准错误码
      • 4.8 sqlite3_extended_errcode —— 获取最近一次 SQLite API 调用产生的扩展错误码
    • 5、练习
      • 5.1 利用SPIFFS运行数据库
      • 5.2 利用SD卡运行数据库
    • 6、应用场景

1、前言

主要学习

https://github.com/siara-cc/esp32_arduino_sqlite3_lib

以及如何使用sqlite来存储我们的业务数据。

该库支持通过 ESP32 SoC 从 SPIFFSSD 卡访问 SQLite 数据库文件。但是对于我们来说SD卡可能更加合适,毕竟空间大。

注意,这里不教sqlite3的基本语法。以及sqlite为啥在嵌入式这么受欢迎。
Sqlite pager和B-tree的运行机制

2、esp32_arduino_sqlite3_lib 库

Arduino IDE -> 工具 -> 管理库 -> 搜索 Sqlite3Esp32-> 安装最新版本
在这里插入图片描述
除了 Arduino 和 ESP32 核心 SDK 之外,没有依赖项。Sqlite3 代码包含在库中。

注意,此库局限性。

  • 没有严重的限制,除了在大型数据集上有点慢。从包含 1000 万行的数据集中检索需要大约 700 毫秒,即使使用索引也是如此。但是对于我们来说已经足够。
  • 未实现锁定。所以它不能可靠地用于多线程/多核代码集,只读操作除外。
  • 任何闪存,例如 SPIFFS 或微型 SD 卡上可用的闪存,都对每个扇区的写入/擦除次数有限制。通常限制为 10000 次写入或 100000 次写入(在同一扇区)。尽管 ESP32 支持磨损均衡,但在进入写入密集型数据库项目之前要牢记这一点。从 Flash 读取没有限制。

3、硬件说明

这里直接选择esp32-cam,因为它直接有SD卡槽,插入一张sd卡即可,完美!!!!如果是其他esp32板子,就得自己连接一下SD卡连线。

4、API说明

4.1 sqlite3_initialize —— 初始化Sqlite数据库引擎

SQLite 数据库引擎的初始化函数。它在使用 SQLite 数据库之前必须被调用,以确保数据库引擎已被正确地初始化。该函数将创建或打开 SQLite 数据库的内部数据结构,设置全局变量和其他必要的初始化步骤。

4.2 sqlite3_open —— 打开或创建一个 SQLite 数据库文件

sqlite3_open 是 SQLite 数据库引擎的一个函数,用于打开或创建一个 SQLite 数据库文件。它的函数原型如下:

int sqlite3_open( 
 const char *filename,   /* 数据库文件名 */ 
  sqlite3 **ppDb          /* 指向指针的指针,用于存储打开的数据库句柄 */
  );

filename 参数是数据库文件的名称,如果该文件不存在,则会创建一个新的数据库文件。如果 filename 参数为 “:memory:”,则表示创建一个内存数据库。ppDb 参数是一个指向指针的指针,用于存储打开的数据库句柄。如果打开数据库成功,则会将句柄存储在该指针指向的内存中,如果打开失败,则会把指针设置为 NULL。

例如,以下代码段展示了如何使用 sqlite3_open 函数打开一个名为 test.db 的数据库文件:

sqlite3 *db;
int rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) { 
 // 打开数据库失败
 } else {  
 // 打开数据库成功,可以使用 db 句柄进行后续操作
 }

在使用完数据库之后,需要调用 sqlite3_close 函数关闭数据库。

4.3 sqlite3_exec —— 执行 SQL 命令

它的函数原型如下:

int sqlite3_exec(  
sqlite3* db,                    /* 数据库句柄 */  
const char* sql,                /* SQL 命令 */  
int (*callback)(void*,int,char**,char**),  /* 回调函数 */  
void *data,                     /* 回调函数的上下文参数 */  
char **errmsg                   /* 错误信息,如果有错误发生 */
);
  • db 参数是 SQLite 数据库句柄
  • sql 参数是需要执行的 SQL 命令
  • callback 参数是回调函数指针
  • data 参数是回调函数的上下文参数
  • errmsg 参数是一个指向字符指针的指针,用于存储错误信息(如果有错误发生)。

当执行 SQL 命令时,sqlite3_exec 函数会多次调用回调函数,每次调用传递一个结果行的数据。回调函数的函数原型为:

int callback(void* data, int argc, char** argv, char** colName);

其中,data 参数为回调函数的上下文参数,argc 参数为结果行中的列数,argv 参数为指向结果行中每个列的指针数组,colName 参数为指向结果行中每个列的名称的指针数组。

例如,以下代码段展示了如何使用 sqlite3_exec 函数执行一条 SELECT 命令:

sqlite3 *db;
int rc = sqlite3_open("test.db", &db);
if (rc != SQLITE_OK) {  
// 打开数据库失败
} else {  
    char *errmsg;  
    // SELECT 命令的回调函数  
    int callback(void *data, int argc, char **argv, char **colName) {    for (int i = 0; i < argc; i++) {      
    printf("%s = %s\n", colName[i], argv[i] ? argv[i] : "NULL");    
    }    
    return 0;  
}  
    // 执行 SELECT 命令  
    rc = sqlite3_exec(db, "SELECT * FROM mytable", callback, 0, &errmsg);  
    if (rc != SQLITE_OK) {    
    printf("SELECT failed: %s\n", errmsg);    
    sqlite3_free(errmsg);  
    }  
    sqlite3_close(db);
}

在使用完数据库之后,需要调用 sqlite3_close 函数关闭数据库。

4.4 sqlite3_free —— 释放内存

用于释放先前通过 sqlite3_malloc 或 sqlite3_realloc 分配的内存。它的原型为:

void sqlite3_free(void*);

其中,参数是一个指向要释放的内存块的指针。

使用 sqlite3_free 可以避免内存泄漏的问题,因为它会将之前分配的内存块标记为可用,以便后续的内存分配可以复用这些空间。但是需要注意的是,必须使用相同的内存分配函数(即 sqlite3_malloc 或 sqlite3_realloc)来分配和释放内存块,否则可能会导致内存错误。

4.5 sqlite3_close —— 关闭先前打开的数据库连接

用于关闭先前打开的数据库连接。它的原型为:

int sqlite3_close(sqlite3*);

其中,参数是一个指向已打开的数据库连接的指针。

使用 sqlite3_close 可以释放数据库连接占用的资源,包括内存和文件句柄等。在关闭数据库连接之前,应该先释放所有相关的内存和资源,例如关闭所有已经打开的数据库句柄和清理所有的 prepared statements,以避免资源泄漏。

需要注意的是,当一个数据库连接被关闭后,与该连接相关的所有数据库句柄和 prepared statements 都会变成无效。因此,在调用 sqlite3_close 之后,不应该再使用任何与该连接相关的资源。

4.6 sqlite3_errmsg —— 用于获取最近一次 SQLite API 调用产生的错误信息

它的原型为:

const char *sqlite3_errmsg(sqlite3*);

其中,参数是一个指向已打开的数据库连接的指针。如果最近一次的 SQLite API 调用没有产生错误,那么 sqlite3_errmsg 返回一个空字符串。

当 SQLite API 调用出现错误时,sqlite3_errmsg 可以用来获取错误信息,以便排查问题。例如,假设在执行 SQL 查询时出现错误,可以使用 sqlite3_errmsg 来获取错误信息并输出到控制台:

sqlite3 *db;
sqlite3_open("example.db", &db);
sqlite3_exec(db, "SELECT * FROM users WHERE id = 0;", NULL, NULL, NULL);
fprintf(stderr, "SQL error: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);

4.7 sqlite3_errcode —— 获取最近一次 SQLite API 调用产生的标准错误码

它的原型为:

int sqlite3_errcode(sqlite3*);

其中,参数是一个指向已打开的数据库连接的指针。

sqlite3_errcode 返回的错误码是一个整数,可以与 SQLite 的错误码宏比较,以判断错误的类型。例如,如果 sqlite3_errcode 返回 SQLITE_CONSTRAINT_UNIQUE,那么表示最近一次 SQLite API 调用遇到了一个唯一性约束错误。

需要注意的是,sqlite3_errcode 返回的错误码是一个标准错误码,与 sqlite3_extended_errcode 返回的扩展错误码不同。如果想要同时获取标准错误码和扩展错误码,可以使用 sqlite3_errcode 和 sqlite3_extended_errcode 配合使用。同时需要注意,sqlite3_errcode 只能返回最近一次 SQLite API 调用的错误码,如果想要获取之前的错误码,需要使用 sqlite3_extended_errcode。

4.8 sqlite3_extended_errcode —— 获取最近一次 SQLite API 调用产生的扩展错误码

与 sqlite3_errcode 不同的是,sqlite3_extended_errcode 提供了更加详细的错误信息,可以用于更好地排查问题。它的原型为:

int sqlite3_extended_errcode(sqlite3*);

其中,参数是一个指向已打开的数据库连接的指针。

sqlite3_extended_errcode 返回的错误码是一个整数,可以与 SQLite 的错误码宏比较,以判断错误的类型。例如,如果 sqlite3_extended_errcode 返回 SQLITE_CONSTRAINT_FOREIGNKEY,那么表示最近一次 SQLite API 调用遇到了一个外键约束错误。

需要注意的是,sqlite3_extended_errcode 返回的错误码是一个扩展错误码,与 sqlite3_errcode 返回的标准错误码不同。如果想要获取标准错误码,可以使用 sqlite3_errcode。如果想要同时获取标准错误码和扩展错误码,可以使用 sqlite3_errcode 和 sqlite3_extended_errcode 配合使用。

5、练习

接下来会通过几个练习来学习一下sqlite3的具体使用场景和用法。

5.1 利用SPIFFS运行数据库

/*
    This creates two empty databases, populates values, and retrieves them back
    from the SPIFFS file
    创建两个空数据库,用来测试简单CURD操作
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sqlite3.h>
#include <SPI.h>
#include <FS.h>
#include "SPIFFS.h"

/* You only need to format SPIFFS the first time you run a
   test or else use the SPIFFS plugin to create a partition
   https://github.com/me-no-dev/arduino-esp32fs-plugin */
#define FORMAT_SPIFFS_IF_FAILED true

const char* data = "Callback function called";
/***
 * 回调方法,用于数据库操作工程中结果回调
 */
static int callback(void *data, int argc, char **argv, char **azColName) {
   int i;
   Serial.printf("%s: ", (const char*)data);
   for (i = 0; i<argc; i++){
       Serial.printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   Serial.printf("\n");
   return 0;
}


/***
 * 打开数据库
 */
int db_open(const char *filename, sqlite3 **db) {
   Serial.print(F("DB Name:"));
   Serial.print(filename);
   Serial.println(F(""));
   if (db != NULL)
    sqlite3_close(*db);
   int rc = sqlite3_open(filename, db);
   if (rc) {
       Serial.print(F("Can't open database: "));
       Serial.print(sqlite3_extended_errcode(*db));
       Serial.print(" ");
       // 打印错误信息
       Serial.println(sqlite3_errmsg(*db));
       return rc;
   } else {
       Serial.printf("Opened database successfully\n");
   }
   return rc;
}

char *zErrMsg = 0;

/***
 * 执行数据库操作语句
 */
int db_exec(sqlite3 *db, const char *sql) {
   Serial.println(sql);
   // 记录一个开始执行时间
   long start = micros();
   int rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if (rc != SQLITE_OK) {
    Serial.print(F("SQL error: "));
    Serial.print(sqlite3_extended_errcode(db));
    Serial.print(" ");
    Serial.println(zErrMsg);
    sqlite3_free(zErrMsg);
   } else {
       Serial.printf("Operation done successfully\n");
   }
   // 计算花费时间
   Serial.print(F("Time taken:"));
   Serial.print(micros()-start);
   Serial.println(F(" us"));
   return rc;
}

void setup() {

   Serial.begin(115200);
   // 创建两个数据库对象
   sqlite3 *db1 = NULL;
   sqlite3 *db2 = NULL;
   int rc;

   // 初始化SPIFFS文件系统
   if (!SPIFFS.begin(FORMAT_SPIFFS_IF_FAILED)) {
       Serial.println("Failed to mount file system");
       return;
   }

   // 获取根目录
   File root = SPIFFS.open("/");
   if (!root) {
       Serial.println("- failed to open directory");
       return;
   }
   // 判断是否是一个文件夹
   if (!root.isDirectory()) {
       Serial.println(" - not a directory");
       return;
   }
   // 接下来打印当前所有文件
   File file = root.openNextFile();
   while (file) {
       if (file.isDirectory()) {
           Serial.print("  DIR : ");
           Serial.println(file.name());
       } else {
           Serial.print("  FILE: ");
           Serial.print(file.name());
           Serial.print("\tSIZE: ");
           Serial.println(file.size());
       }
       file = root.openNextFile();
   }

   // 移除已经存在的db文件
   SPIFFS.remove("/test1.db");
   SPIFFS.remove("/test2.db");

   // 初始化sqlite3
   sqlite3_initialize();

   // 打开数据库
   if (db_open("/spiffs/test1.db", &db1))
       return;
   if (db_open("/spiffs/test2.db", &db2))
       return;

   // 执行数据库db1建表语句
   rc = db_exec(db1, "CREATE TABLE test1 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db2建表语句
   rc = db_exec(db2, "CREATE TABLE test2 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db1插入语句
   rc = db_exec(db1, "INSERT INTO test1 VALUES (1, 'Hello, World from test1');");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db2插入语句
   rc = db_exec(db2, "INSERT INTO test2 VALUES (1, 'Hello, World from test2');");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db1查询语句
   rc = db_exec(db1, "SELECT * FROM test1");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db2查询语句
   rc = db_exec(db2, "SELECT * FROM test2");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 关闭数据库
   sqlite3_close(db1);
   sqlite3_close(db2);

}

void loop() {
}

在这里插入图片描述

  • 创建数据表时间花费为 220ms左右
  • 插入一条数据时间花费为130ms左右
  • 查询操作,10ms左右

5.2 利用SD卡运行数据库

/*
  在sd卡里面操作数据库
*/
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#include <SPI.h>
//#include <FS.h>
#include "SD_MMC.h"

const char* data = "Callback function called";
static int callback(void *data, int argc, char **argv, char **azColName){
   int i;
   Serial.printf("%s: ", (const char*)data);
   // 打印返回的数据
   for (i = 0; i<argc; i++){
       Serial.printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
   }
   Serial.printf("\n");
   return 0;
}

/***
 * 打开数据库
 */
int db_open(const char *filename, sqlite3 **db) {
   Serial.print(F("DB Name:"));
   Serial.print(filename);
   Serial.println(F(""));
   if (db != NULL)
    sqlite3_close(*db);
   int rc = sqlite3_open(filename, db);
   if (rc) {
       Serial.print(F("Can't open database: "));
       Serial.print(sqlite3_extended_errcode(*db));
       Serial.print(" ");
       // 打印错误信息
       Serial.println(sqlite3_errmsg(*db));
       return rc;
   } else {
       Serial.printf("Opened database successfully\n");
   }
   return rc;
}

char *zErrMsg = 0;
/***
 * 执行数据库操作语句
 */
int db_exec(sqlite3 *db, const char *sql) {
   Serial.print(F("----------------------- db_exec -----------------------\n"));
   Serial.println(sql);
   Serial.println("");
   // 记录一个开始执行时间
   long start = micros();
   int rc = sqlite3_exec(db, sql, callback, (void*)data, &zErrMsg);
   if (rc != SQLITE_OK) {
    Serial.print(F("SQL error: "));
    Serial.print(sqlite3_extended_errcode(db));
    Serial.print(" ");
    Serial.println(zErrMsg);
    sqlite3_free(zErrMsg);
   } else {
       Serial.printf("Operation done successfully\n");
   }
   // 计算花费时间
   Serial.print(F("Time taken:"));
   Serial.print(micros()-start);
   Serial.println(F(" us"));
   Serial.println(F("----------------------- db_exec -----------------------\n"));
   return rc;
}

void setup() {
   Serial.begin(115200);
   sqlite3 *db1;
   sqlite3 *db2;
   char *zErrMsg = 0;
   int rc;

   SPI.begin();
   SD_MMC.begin();

   sqlite3_initialize();

   // 打开数据库
   if (db_open("/sdcard/test1.db", &db1))
       return;
   if (db_open("/sdcard/test2.db", &db2))
       return;

   // 执行数据库db删表语句
   rc = db_exec(db1, "DROP TABLE IF EXISTS test1;");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   // 执行数据库db删表语句
   rc = db_exec(db2, "DROP TABLE IF EXISTS test2;");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   // 执行数据库db1建表语句
   rc = db_exec(db1, "CREATE TABLE IF NOT EXISTS test1 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   // 执行数据库db2建表语句
   rc = db_exec(db2, "CREATE TABLE IF NOT EXISTS test2 (id INTEGER, content);");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }

   // 执行数据库db1插入语句
   rc = db_exec(db1, "INSERT INTO test1 VALUES (1, 'Hello, World from test1');");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db2插入语句
   rc = db_exec(db2, "INSERT INTO test2 VALUES (1, 'Hello, World from test2');");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db1查询语句
   rc = db_exec(db1, "SELECT * FROM test1");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 执行数据库db2查询语句
   rc = db_exec(db2, "SELECT * FROM test2");
   if (rc != SQLITE_OK) {
       // 执行失败就关闭数据库
       sqlite3_close(db1);
       sqlite3_close(db2);
       return;
   }
   // 关闭数据库
   sqlite3_close(db1);
   sqlite3_close(db2);

}

void loop() {
}

操作test1.db 和 test2.db
在这里插入图片描述
串口日志内容:

DB Name:/sdcard/test1.db
Opened database successfully
DB Name:/sdcard/test2.db
Opened database successfully
----------------------- db_exec -----------------------
DROP TABLE IF EXISTS test1;

Operation done successfully
Time taken:26229 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
DROP TABLE IF EXISTS test2;

Operation done successfully
Time taken:15856 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
CREATE TABLE IF NOT EXISTS test1 (id INTEGER, content);

Operation done successfully
Time taken:99951 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
CREATE TABLE IF NOT EXISTS test2 (id INTEGER, content);

Operation done successfully
Time taken:71794 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
INSERT INTO test1 VALUES (1, 'Hello, World from test1');

Operation done successfully
Time taken:44203 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
INSERT INTO test2 VALUES (1, 'Hello, World from test2');

Operation done successfully
Time taken:36820 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
SELECT * FROM test1

Callback function called: id = 1
content = Hello, World from test1

Operation done successfully
Time taken:12690 us
----------------------- db_exec -----------------------

----------------------- db_exec -----------------------
SELECT * FROM test2

Callback function called: id = 1
content = Hello, World from test2

Operation done successfully
Time taken:12130 us
----------------------- db_exec -----------------------

6、应用场景

整体下来,个人建议可以先在电脑上创建好db文件(提前创建好),然后利用一个小内存的sd卡(比如几百MB等等),这样就可以存储大量的数据了。

  • 温湿度环境数据
  • 打卡数据
  • 远程环境监测
  • 等等

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

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

相关文章

Python - Pycharm 配置 autopep8 并设置快捷键

什么是 PEP8 官方&#xff1a;PEP 8 – Style Guide for Python Code | peps.python.org 中文翻译博客&#xff1a;https://www.cnblogs.com/ajianbeyourself/p/4377933.html PEP8 是 Python 官方推出的一套编码的规范&#xff0c;只要代码不符合它的规范&#xff0c;就会有…

iOS unable to find utility “pngcrush“, not a developer tool or in PATH

0x00 奇怪的Bug 很奇怪&#xff0c;还很蛋疼 T_T 前一秒还能 Build 成功&#xff0c;运行 后一秒直接 GG sh -c /Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild -sdk /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/S…

Win10系统更新时不小心中断了无法启动怎么办?

Win10系统更新时不小心中断了无法启动怎么办&#xff1f;有用户使用的Win10系统电脑在进行系统更新的时候&#xff0c;被自己误触了电脑导致更新进程中断了。那么遇到这样的情况我们怎么去进行问题的解决呢&#xff1f;接下来我们一起来看看以下的解决方法吧。 准备工作&#x…

flink写mysql报错Could not retrieve transation read-only status server

事务隔离级别前提下还是报错 SET GLOBAL tx_isolationREAD-COMMITTED; show global variables like wait timeout; 发现mysql是8小时。如果flnk超过8小时没有发送数据&#xff0c;invoke将会导致 mysql主动断开连接&#xff0c;而java侧并无感知。 解决问题&#xff0c;在使…

Benewake(北醒) TFmini-i-485/TF02-i-485/TF03-485 雷达Modbus协议在Python Tkinter模块上实现功能配置的GUI设计

目录 实验目的测试环境Python库需求Benewake(北醒) TF雷达接线示意图库安装说明例程运行展示 实验目的 实现485接口系列雷达Modbus协议在Python下Tkinter模块实现功能配置的GUI设计。 本例程主要功能如下&#xff1a; 1.设备连接&#xff08;已知雷达设备的波特率和站号&#…

C++11 auto类型推导

1.类型推导 C11引入了auto 和 decltype 关键字实现类型推导&#xff0c;通过这两个关键字不仅能方便地获取复杂的类型&#xff0c;而且还能简化书写&#xff0c;提高编码效率。 auto 类型推导的语法和规则 在之前的 C 版本中&#xff0c;auto 关键字用来指明变量的存储类型…

SSL/TLS协议核心原理解析与实战

什么是SSL/TLS SSL&#xff08;secure sockets layer&#xff0c;安全套接层&#xff09;安全传输技术。TCP是传输层的协议&#xff0c;但是它是明文传输的&#xff0c;是不安全的。SSL的诞生给TCP加了一层保险&#xff0c;为TCP通信提供安全及数据完整性保护。TLS只是SSL的升…

软件测试银行金融项目如何测?看看资深测试老鸟的总结,一篇足够...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 自动化测试&#x…

如何获取ios打包证书

要获取ios证书&#xff0c;需要去苹果开发者中心注册苹果开发者账号&#xff0c;百度苹果开发者中心即可进入苹果开发者中心官网。 假如你还从来没注册过苹果开发者&#xff0c;你可以参考下面这篇文章先注册成为苹果开发者&#xff0c;必须要有苹果开发者账号才能生成ios打包…

电商客户消费预测模型-基于数千万真实在线零售数据__企业调研_论文科研_毕业设计

之前发过 《谁主沉浮&#xff1f;银行&#xff0c;消金&#xff0c;互联网公司的精准营销_智慧营销完全解读》介绍了智慧营销/精准营销目的是降低运营成本。但精准营销可以带来很多额外收益&#xff0c;例如提高销售利润&#xff0c;提高客户忠诚度&#xff0c;降低客户流失率&…

MySQL的登录与退出(图文讲解)

MySQL的登录 前言一、服务的启动与停止1、方式1&#xff1a;使用图形界面工具2、方式2&#xff1a;使用命令行工具 二、自带客户端的登录与退出1、登录方式1&#xff1a;MySQL自带客户端2、登录方式2&#xff1a;windows命令行3、退出登录 前言 本博主将用CSDN记录软件开发求学…

越秀地产K2流程平台年度报告出炉,来看看“别人家”的流程平台

前不久&#xff0c;越秀地产K2流程平台2022年度运营报告新鲜出炉&#xff0c;K2流程平台再次递交出色成绩单。 2022年&#xff0c;越秀地产在K2流程平台上审批完成的流程共计103万条&#xff0c;日均发起流程数达2800条&#xff0c;日均点击量5万。在大体量、高负荷情形下&…

moment获取指定日期的周x,某月最后一天

安装了moment插件的情况下&#xff0c;使用moment处理时间&#xff0c;原生的Date对象是另一回事。 非官方中文网-文档 1 当前时间 moment() 2 格式化时间 YYYY/yyyy 四位数年份 MM 两位数月份 DD 两位数天 moment().format("YYYY MM DD") 2023 05 26 moment().…

某二手车逆向研究,竟然如此……

目录 一、逆向目标二、网站分析三、加密参数分析四、加密数据分析五、思路总结六、完整项目下载七、作者Info 一、逆向目标 通过抓包技术找出请求头的加密参数&#xff0c;当然也包括cookie&#xff0c;以及响应数据中的加密过的或编码过的数据&#xff0c;通过xhr/fetch请求定…

lidar-camera 标定系统

摘要 本文讨论了一个视觉系统的校准问题&#xff0c;该系统由RGB相机和3D光学雷达&#xff08;LiDAR&#xff09;传感器组成。将来自不同模态的两个独立点云进行配准始终是具有挑战性的。我们提出了一种新颖、准确的校准方法&#xff0c;使用已知尺寸的简单纸板箱。我们的方法…

Apache网页安全与安全优化--网页缓存、隐藏版本信息、Apache 防盗链

目录 --------网页缓存-------- 1.检查是否安装 mod_expires 模块 2.如果没有安装mod_expires 模块&#xff0c;重新编译安装 Apache 添加 mod_expires模块 3.配置 mod_expires 模块启用 4.检查安装情况&#xff0c;启动服务 5.测试缓存是否生效 --------隐藏版本信息--…

公开报名|CCPTP云渗透测试认证专家第二期培训班,将在云网基础设施安全国家工程研究中心举办

CCPTP云渗透测试认证专家由云安全联盟大中华区发布&#xff0c;是全球首个云渗透测试能力培养课程及人才培养认证&#xff0c;弥补了国内云渗透测试认知的差距和技能型人才培养的空白。4月1日-13日&#xff0c;CCPTP 首期班成功举办&#xff0c;于2023年5月10日部分学员完成考试…

C语言---函数

1、函数是什么 学习库函数网站&#xff1a; https://cplusplus.com/reference/http://en.cppreference.comhttp://zh.cppreference.com 我们参考文档&#xff0c;学习几个库函数 2、库函数 3、自定义函数 自定义函数和库函数一样&#xff0c;有函数名&#xff0c;返回值类…

TLS协议在ISO13400-2-2019文档中的内容解析

我很久之前写过解读ISO13400文档的系列文章:《详解ISO 13400文档(DoIP协议)》。当时没有说明解读的是哪一版13400,现在回过头看,应该是ISO13400-2-2012版本。那一版里没有TLS协议的相关内容,所以写的文章中也没有体现。 而2019版相比2012版,有两个方面的变化: 增加TLS…

1.AI绘画简介

1.1简介 ​ AI绘画即指人工智能绘画&#xff0c;是一种计算机生成绘画的方式。 ​ AI绘画主要包含两个部分&#xff0c;一个是对图像的分析与判断&#xff0c;即“学习”&#xff0c;一个是对图像的处理和还原&#xff0c;即“输出”。人工智能通过对数以万计的图像及绘画作品…