数据库简介

  • 定义:数据库是⽤于存储、管理和处理数据的系统。它允许存储⼤量信息,⽀持数据的有效组织、存储、访问和更新。
  • 作⽤:
    • 数据存储:可持久存储各种数据,如⽂本、数字、图⽚等。
    • 数据管理:提供管理数据的功能,如数据的增加、删除、修改和查询。
    • 数据处理:⽀持对数据进⾏各种操作,以满⾜特定的业务需求。
    • 数据查询:允许通过SQL(结构化查询语⾔)等查询语⾔进⾏⾼效数据检索。
  • 应⽤场景:
    • 商业数据存储、⽹站数据管理、移动应⽤数据处理等。
  • 相关概念:
    • 数据库管理系统(Database Management System,DBMS)是⽤于定义、创建、维护和控制据库的软件系统。它提供了数据的访问、管理和保护等功能,常⻅的 DBMS 包括 MySQL、PostgreSQL、SQLite 等
    • 关系型数据库(Relational Database)是基于关系模型的数据库,数据以表格形式存储,表与表之间通过键进⾏关联。关系型数据库⽀持 SQL(结构化查询语⾔)进⾏数据操作。
  • SQL(Structured Query Language)是⽤于管理和查询关系型数据库的标准语⾔。它包括数据定义语⾔(DDL)、数据操纵语⾔(DML)、数据控制语⾔(DCL)和数据查询语⾔(DQL)等部分。

DBMS(数据库管理系统)和数据库对⽐

数据库(Database)

数据库是实际存储数据的集合,可以理解为⼀种容器,⽤于存放结构化的信息。数据库包括表、记
录、列等各种数据对象,通过这些对象来组织和存储信息。

  • 组成:数据库中的数据通常是按某种结构存储的,例如在关系型数据库中,数据按表(表由⾏和列组成)的形式存储。
  • 作⽤:数据库本⾝不具备管理、控制数据的功能,它只是数据的存储空间,数据存储的格式和内容由具体的需求决定。

2. 数据库管理系统(DBMS)

数据库管理系统(DBMS)是管理、控制和访问数据库的⼯具或软件系统,它提供了⼀组功能来实现对
数据库中的数据进⾏⾼效、安全的存储、检索、修改和删除。

  • 组成:DBMS 包含⼀系列软件组件,例如查询引擎、存储引擎、⽇志管理、事务管理等。
  • 作⽤:DBMS 负责数据的存储、更新、查询、并发控制、数据安全、数据备份和恢复等任务。它提供了⼀组接⼝,使得⽤⼾或应⽤程序可以通过查询语⾔(如 SQL)来操作数据库。

ACID

ACID 是数据库事务的四个基本特性,⽤于确保数据在并发操作和系统故障时的正确性和⼀致性。
ACID 是 原⼦性(Atomicity)、⼀致性(Consistency)、隔离性(Isolation) 和 持久性(Durability)的缩写。

  1. 原⼦性(Atomicity)
    原⼦性确保事务中的所有操作要么全部执⾏,要么全部不执⾏,不允许部分完成。例如,银⾏转账操作必须同时扣除⼀个账⼾的余额并增加另⼀个账⼾的余额,不能只执⾏其中⼀个操作。
    • 实现⽅法:数据库通过⽇志(log)或回滚机制来实现原⼦性。⽇志记录事务的每⼀个操作,若事务失败,数据库会使⽤⽇志回滚到事务开始前的状态。
  2. ⼀致性(Consistency)
    ⼀致性保证数据库在事务完成前后都处于⼀种合法的状态,即所有约束(如主键、外键、唯⼀性约束等)在事务执⾏前后都有效。例如,⼀个银⾏账⼾的余额不能变成负数。
  • 实现⽅法:数据库在每次事务执⾏时,会⾃动检查并保持所有约束的完整性,确保数据不会违反这些规则。若违反约束,事务会被回滚。
  1. 隔离性(Isolation)
    隔离性确保多个事务同时执⾏时不会互相⼲扰。⼀个事务的中间状态对其他事务是不可⻅的,直到该事务提交。隔离性防⽌了并发事务间的数据冲突。
  • 隔离级别:
    • 未提交读(Read Uncommitted):事务可以看到其他未提交事务的数据,容易引发脏读。
    • 已提交读(Read Committed):事务只能看到已提交的数据,避免了脏读。
    • 可重复读(Repeatable Read):保证在同⼀事务中多次读取的数据⼀致,防⽌不可重复读。
    • 序列化(Serializable):最⾼隔离级别,所有事务串⾏执⾏,避免并发问题,但性能较低。
    • 实现⽅法:数据库通常通过锁机制或多版本并发控制(MVCC)来实现隔离性。锁定数据可以防⽌其他事务在未提交时访问或修改数据,⽽ MVCC 通过保存数据的多个版本提供更⾼效的并发⽀持。
  1. 持久性(Durability)
    持久性保证事务⼀旦提交,数据就会永久保存在数据库中,即使系统故障或重启,已提交的数据也不会丢失。
  • 实现⽅法:数据库使⽤写前⽇志(WAL)和数据持久化机制来确保持久性。在事务提交前,数据库会将所有修改写⼊⽇志并保存到磁盘,确保数据的永久性。
    总结
  • 原⼦性:事务的操作要么全部完成,要么全部不执⾏。
  • ⼀致性:事务的执⾏不能破坏数据的完整性约束。
  • 隔离性:并发事务间互不⼲扰,避免数据冲突。
  • 持久性:事务提交后的数据永久保存在数据库中。
    ACID 特性确保了数据库系统的可靠性和⼀致性,使得数据库在并发和故障情况下仍能保持数据的正确性。

MySQL

MySQL的特点

  1. 开源免费:MySQL 采⽤了开放源代码许可证,可以免费使⽤、修改和分发,且具备付费商业版本,提供更⾼的⽀持。
  2. 跨平台:⽀持多种操作系统,包括 Windows、Linux、macOS 等,可以在不同平台之间迁移。
  3. 性能⾼效:MySQL 对查询和数据处理进⾏了优化,提供快速的读写操作,适⽤于中⼩型应⽤和Web 服务。
  4. ⽀持多⽤⼾并发访问:提供强⼤的并发性,可以处理多个⽤⼾的同时访问和操作。
  5. 数据安全性:提供访问控制、密码加密、数据备份等安全机制,确保数据的完整性和安全性。
  6. 丰富的存储引擎:⽀持多种存储引擎(如 InnoDB、MyISAM),可以根据需求选择合适的引擎来优化性能。

MySQL的缺陷

  1. 功能限制:相较于其他⾼级数据库(如 PostgreSQL、Oracle),MySQL 在某些⾼级功能(如触发器、存储过程、复杂查询)上较弱。
  2. 事务处理不如其他数据库:尽管 MySQL ⽀持事务处理(主要依赖 InnoDB 引擎),但在某些场景下,事务的隔离性和⼀致性可能不如其他数据库(如 PostgreSQL)。
  3. ⼤数据量性能下降:当数据量⾮常⼤或查询变得复杂时,MySQL 的性能可能会受到影响,并需要专⻔的优化。
  4. 有限的 ACID ⽀持:MySQL 对 ACID 特性(原⼦性、⼀致性、隔离性、持久性)的⽀持依赖于所选存储引擎,⽽部分引擎可能不完全满⾜ ACID 要求。
  5. 扩展性较弱:MySQL 对于集群和分布式架构的⽀持有限,需要借助第三⽅⼯具或插件来实现分布式数据存储和⾼可⽤性。

总结

MySQL 是⼀种开源、跨平台、性能⾼效的关系型数据库管理系统,适⽤于中⼩型应⽤、Web 开发等场景。尽管在事务处理和⼤数据量性能上有⼀定局限,但通过合理的优化和配置,MySQL 依然是⼀款⼴泛使⽤的数据库解决⽅案。

SQLite数据库

  • SQLite概述:
    • 嵌⼊式数据库:SQLite是⼀种轻量级的数据库,它不像传统数据库那样需要单独的服务器。SQLite的数据库是⼀个⽂件,可以直接集成到应⽤程序中。
  • 主要特点:
    • ⽆需服务器:不需要配置和维护独⽴的数据库服务器,数据库以⽂件形式存在于应⽤程序中。
    • 跨平台:⽀持多种操作系统,包括Windows、Linux、Mac OS等。
    • ⾃给⾃⾜:不需要依赖外部的数据库管理系统。
    • ⼩巧灵活:占⽤资源少,适⽤于资源受限的环境。
    • 易于操作:通过SQL语⾔进⾏数据库操作,简单易学。
  • 优势:
    • 适合于轻量级应⽤,尤其是对服务器依赖要求低的场景。
    • 易于部署和维护,降低了应⽤的复杂性和成本。
  • 应⽤场景:
    • 移动应⽤:如 Android、iOS 应⽤的数据存储。
    • 嵌⼊式系统:在硬件资源有限的设备上使⽤。
  • ⼩型⽹站:适⽤于访问量较⼩的⽹站或内部⼯具。
  • 测试和开发:快速搭建数据库环境,进⾏功能验证。
    基本架构:
  1. 核⼼库(Core Library):
  • SQLite的核⼼库包括SQL解析器、查询优化器、存储引擎等核⼼组件,⽤于处理SQL语句的解析、优化和执⾏。
  • 核⼼库负责管理数据库⽂件、表格、索引等数据结构。
  1. 存储引擎:
  • SQLite使⽤B-tree数据结构来管理表格和索引的数据存储。
  • B-tree是⼀种⾼效的数据结构,⽀持快速的数据查找、插⼊和删除操作。
  • SQLite⽀持多种存储引擎,例如磁盘存储引擎、内存存储引擎等,允许在不同场景下进⾏选
    择。
  1. 虚拟机(Virtual Machine):
  • SQLite使⽤虚拟机执⾏SQL语句。
  • SQL语句⾸先由SQL解析器解析为抽象语法树(AST),然后由虚拟机执⾏。
  • 虚拟机可以执⾏SQL查询、插⼊、更新和删除操作。
  1. ⽂件系统接⼝:
  • SQLite需要与底层⽂件系统交互,读取和写⼊数据库⽂件。
  • 不同平台的⽂件系统接⼝被封装成操作系统相关的代码,以保证跨平台兼容性。
  1. 外部接⼝:
  • SQLite提供了⼀系列的C语⾔API,以便应⽤程序与数据库进⾏交互。
  • 应⽤程序可以通过API执⾏SQL查询、事务管理、数据读写等操作。
  1. 内存管理:
  • SQLite需要有效地管理内存资源,包括分配、释放和缓存数据。
  • 内存管理器负责管理数据库的内存使⽤。
  1. 线程安全:
  • SQLite可以在多线程环境中使⽤,但需要注意线程安全性问题。
  • 在多线程应⽤中,SQLite通常需要使⽤互斥锁(Mutex)来确保数据的⼀致性和安全性。

SQL语法⼊⻔

SELECT语句

  • ⽤途:⽤于从表中检索数据。
    1
    2
    SELECT 列名1, 列名2, ...
    FROM 表名;
  • ⽰例:
    1
    SELECT 姓名, 年龄 FROM ⽤⼾;

WHERE⼦句

  • ⽤途:⽤于过滤满⾜特定条件的记录。
    1
    2
    3
    SELECT 列名1, 列名2, ...
    FROM 表名
    WHERE 条件;
  • ⽰例:
    1
    SELECT 姓名, 年龄 FROM ⽤⼾ WHERE 年龄 > 18;

INSERT INTO语句

  • ⽤途:⽤于过滤满⾜特定条件的记录。
    1
    2
    INSERT INTO 表名 (列名1, 列名2, ...)
    VALUES (值1, 值2, ...);
  • ⽰例:
    1
    INSERT INTO ⽤⼾ (姓名, 年龄) VALUES ('李四', 25);

UPDATE语句

  • ⽤途:⽤于更新表中的数据。
    1
    2
    3
    UPDATE 表名
    SET 列名1 =1, 列名2 =2, ...
    WHERE 条件;
  • ⽰例:
    1
    UPDATE ⽤⼾ SET 年龄 = 26 WHERE 姓名 = '李四';

DELETE语句

  • ⽤途:⽤于删除表中的记录。
    1
    DELETE FROM 表名 WHERE 条件;
  • ⽰例:
    1
    DELETE FROM ⽤⼾ WHERE 姓名 = '李四';

CREATE TABLE语句

  • ⽤途:⽤于创建新表。
    1
    2
    3
    4
    5
    CREATE TABLE 表名 (
    列名1 数据类型,
    列名2 数据类型,
    ...
    );
  • ⽰例:
    1
    2
    3
    4
    5
    CREATE TABLE ⽤⼾ (
    id INT AUTO_INCREMENT PRIMARY KEY,
    姓名 VARCHAR(100),
    年龄 INT
    );

ALTER TABLE语句(添加、删除、修改列)

  • ⽤途:⽤于修改已存在的表。
    1
    2
    3
    4
    5
    6
    7
    8
    ALTER TABLE 表名
    ADD 列名 数据类型;

    ALTER TABLE 表名
    DROP COLUMN 列名;

    ALTER TABLE 表名
    MODIFY COLUMN 列名 数据类型;
  • ⽰例:
    1
    2
    ALTER TABLE ⽤⼾ ADD 邮箱 VARCHAR(255);
    );

特殊符号

  1. *(星号):
  • 在SQL查询语句中, * 是⼀个通配符,⽤于表⽰选择表中的所有列。例如,在 SELECT * FROM table_name; 中, * 意味着从指定的table_name表中选取所有的字段数据。
  1. % (百分号):
  • 在SQL LIKE操作符中, % 是通配符,代表零个或多个任意字符。例如: SELECT column FROM table WHERE column LIKE ‘%pattern%’; 会查找column列中包含”pattern”任何位置的⾏。
  • 在⼀些数据库系统(如MySQL)的模式匹配函数中, % 也⽤于模糊搜索。
  1. _ (下划线):
  • 与 % 类似,但在LIKE操作符中, _ 代表单个任意字符。例如: SELECT column FROM table WHERE column LIKE ‘prefix___’; 将查找以”prefix”开头且后⾯跟三个任意字符的⾏。
  1. \ (反斜杠):
  • 在SQL字符串和正则表达式中,反斜杠 \ ⽤来转义特殊字符。如果要匹配⼀个实际的反斜杠字符,需要写两个反斜杠 \\ ,因为在SQL字符串字⾯量中,反斜杠本⾝也是⼀个转义字符。
  1. 连接运算符:
  • = 和 = 曾经在某些旧版数据库系统中⽤于表⽰外连接操作,但不是SQL标准的⼀部分。在现代SQL中,使⽤ LEFT JOIN , RIGHT JOIN 或 FULL OUTER JOIN 来进⾏外连接操
  • 作。如: SELECT * FROM table1 LEFT JOIN table2 ON table1.id *= table2.id; 这样的写法在⾮标准SQL中可能表⽰左连接,但在遵循ANSI SQL标准的系统中应写作 … LEFT JOIN … ON table1.id = table2.id;
  1. 其他符号和运算符:
  • < > <= >= <> != AND OR NOT IN BETWEEN 等是⽐较和逻辑运算符,⽤于在WHERE⼦句和其他条件表达式中构建复杂的过滤条件。

SQL注入及其防⽌

常⻅的SQL注⼊⽅法:

直接注⼊

  • 概念:攻击者在应⽤程序的输⼊字段中插⼊恶意SQL代码,利⽤不安全的字符串拼接或预编译语句处理机制,绕过验证并执⾏⾮预期的数据库操作。
  • 例⼦:假设⽹站登录表单存在漏洞,攻击者可以在⽤⼾名输⼊框内输⼊ admin’ OR ‘1’=’1 。系统⽣成的SQL查询可能变为 SELECT * FROM users WHERE username = ‘admin’OR ‘1’=’1’ AND password = ‘…’; 。由于条件 ‘1’=’1’ 永远为真,这将导致验证逻辑失效,使得攻击者即使不知道正确的密码也能成功登录。
    1
    SELECT * FROM users WHERE username = '' OR '1'='1' AND password = '...';

注释注⼊

  • 概念:攻击者通过在⽤⼾提供的输⼊中添加SQL注释符号(如 – 或 /* */ ),使得原始SQL语句的剩余部分被数据库忽略执⾏。
  • 例⼦:在登录场景中,攻击者在⽤⼾名字段输⼊ admin’– ,这样后续的密码验证部分将被视为注释内容,攻击者可能因此绕过密码验证环节,即使没有提供正确密码也能登录。
    1
    SELECT * FROM users WHERE username = ''; DROP TABLE users; --' AND password ='...';

联合查询注⼊

  • 概念:攻击者利⽤ UNION 操作符结合多个SELECT语句,从⽽从数据库中获取原本不应访问的数据记录。
  • 例⼦:攻击者尝试在输⼊域中输⼊ 1 UNION SELECT username, password FROM users ,这样可以将查询结果扩展⾄包含所有⽤⼾的⽤⼾名和密码信息。⼦查询注⼊
  • 概念:攻击者嵌⼊⼀个或多个额外的SELECT⼦查询到原SQL语句内部,以实现对数据库未经授权的读取或其他操作。
  • 例⼦:攻击者提交如下输⼊: 1; SELECT * FROM users WHERE username = ‘admin’ ,这种情况下,数据库可能会连续执⾏两个独⽴的查询,第⼆个查询可能暴露管理员账⼾的相关数据。

防⽌SQL注⼊的⽅法:

  1. 预编译语句(参数化查询)
  • 实践⽅法:在构建SQL语句时,预先定义好SQL命令的结构,并将⽤⼾提供的数据作为参数传⼊,⽽不是直接嵌⼊到字符串中。数据库系统会识别并正确处理这些参数,确保它们不会被解释为SQL代码。
  • 例⼦:使⽤Java和JDBC执⾏预编译查询时,可以先 PreparedStatement 对象来设置SQL模板,例如 SELECT * FROM users WHERE username = ? AND password = ?; ,然后通过 .setString() ⽅法绑定实际参数值,这样即使参数中包含特殊字符如单引号 ‘ ,也不会被执⾏。
  1. ORM框架
  • 原理与应⽤:通过使⽤Hibernate、Entity Framework等ORM框架,开发者可以通过操作对象的⽅式来间接操作数据库,ORM框架内部通常会对参数进⾏⾃动转义或使⽤参数化查询,从⽽避免SQL注⼊⻛险。
  • 例⼦:在.NET环境中,利⽤Entity Framework执⾏⼀个登录验证时,可以声明⼀个LINQ查询表达式,如 var user = context.Users.FirstOrDefault(u => u.Username == userInput && u.Password == passwordInput); ,这⾥的输⼊会被框架安全地处
  1. 输⼊验证
  • 实施步骤:对所有⽤⼾提交的数据进⾏严格的格式检查和类型校验,确保输⼊内容符合预期的业务逻辑和数据格式要求。
  • 例⼦:对于⼿机号码字段,应确保输⼊的是有效的11位数字串;在接收⽇期输⼊时,应检验其是否符合YYYY-MM-DD格式,⽆效格式则拒绝接收并提⽰错误。
  1. 最⼩权限原则
  • 策略描述:应⽤程序连接数据库所使⽤的账⼾应当仅拥有完成当前任务所需的最⼩⼦集权限,⽐如只读访问特定表,⽽⾮整个数据库的所有权限,更不能使⽤超级管理员账号。
  • 实例说明:假设某个模块只需要读取users表的信息,则该模块对应的数据库连接账⼾只需赋予SELECT权限于users表,⽽⽆需UPDATE、DELETE或其他表的任何权限。
  1. 禁⽌直接拼接SQL
  • 安全实践:绝对避免将⽤⼾输⼊直接插⼊到SQL字符串中形成新的查询,因为这种做法极易受到SQL注⼊攻击。
  • 反例警⽰:不安全的做法是这样的: String sql = “SELECT * FROM users WHERE username = ‘“ + userInput + “‘“; ,正确的做法是采⽤上述提及的预编译语句或者参数化查询。
  1. 使⽤安全函数
  • 应急措施:对于那些不⽀持预编译语句或参数化查询的⽼旧数据库接⼝,可使⽤数据库⾃⾝提供的安全函数对特殊字符进⾏转义以防⽌SQL注⼊。
  • ⽰例代码:在MySQL中,可以调⽤ mysqli_real_escape_string() 函数对⽤⼾输⼊的内容进⾏转义,⽽在PDO中,则可以使⽤ quote() ⽅法对字符串进⾏包裹,确保其在SQL语句中的安全性。例如:
    1
    2
    $usernameEscaped = $pdo->quote($userInput);
    $sql = "SELECT * FROM users WHERE username = $usernameEscaped";

防注⼊代码

1
2
3
4
5
6
7
8
9
// 准备SQL语句,预编译以防⽌SQL注⼊攻击
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
// 如果准备SQL语句失败,记录⽇志并返回false
LOG_INFO("Failed to prepare registration SQL for user:"username);
return false;
}
// 绑定SQL语句中的参数,防⽌SQL注⼊
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC);

Database.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
#include <sqlite3.h>
#include <string>

class Database {
private:
sqlite3* db;

public:
// 构造函数,用于打开数据库并创建用户表
Database(const std::string& db_path) {
if (sqlite3_open(db_path.c_str(), &db) != SQLITE_OK) {
throw std::runtime_error("Failed to open database");
}
// 创建用户表的SQL语句
const char* sql = "CREATE TABLE IF NOT EXISTS users (username TEXT PRIMARY KEY, password TEXT);";
char* errmsg;
//sqlite3_exec 是 SQLite C API 中的一个函数,用于执行一条或一组 SQL 命令,并处理其结果
//这里如果执行 sqlite3_exec 函数并尝试执行 SQL 命令时发生了任何错误,
//那么该条件将会成立,程序可能接下来会进行错误处理,比如打印或显示由 errmsg 指向的错误消息。
if (sqlite3_exec(db, sql, 0, 0, &errmsg) != SQLITE_OK) {
throw std::runtime_error("Failed to create table: " + std::string(errmsg));
}
}

// 析构函数,用于关闭数据库连接
~Database() {
sqlite3_close(db);
}

// 用户注册函数
bool registerUser(const std::string& username, const std::string& password) {
std::string sql = "INSERT INTO users (username, password) VALUES (?, ?);";
sqlite3_stmt* stmt;

// 准备SQL语句

if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
LOG_INFO("Failed to prepare registration SQL for user: %s" , username.c_str()); // 记录日志
return false;
}

// 绑定参数
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);
sqlite3_bind_text(stmt, 2, password.c_str(), -1, SQLITE_STATIC);

// 执行SQL语句
if (sqlite3_step(stmt) != SQLITE_DONE) {
LOG_INFO("Registration failed for user: %s " , username.c_str()); // 记录日志
sqlite3_finalize(stmt);
return false;
}

// 完成操作,关闭语句
sqlite3_finalize(stmt);
LOG_INFO("User registered: %s with password: %s" , username.c_str(), password.c_str()); // 记录日志
return true;
}

// 用户登录函数
bool loginUser(const std::string& username, const std::string& password) {
std::string sql = "SELECT password FROM users WHERE username = ?;";
sqlite3_stmt* stmt;

// 准备SQL语句

// int sqlite3_prepare_v2(
// sqlite3 *db, // 指向已打开的SQLite数据库连接
// const char *zSql, // 包含SQL命令的以空字符终止的字符串
// int nByte, // SQL命令的字节数,或-1表示使用整个字符串直到遇到'\0'
// sqlite3_stmt **ppStmt, // 输出参数,将指向新创建的预编译语句对象
// const char **pzTail // 可选输出参数,指向未被编译的部分(通常在处理多条SQL时有用)
// );
if (sqlite3_prepare_v2(db, sql.c_str(), -1, &stmt, nullptr) != SQLITE_OK) {
LOG_INFO("Failed to prepare login SQL for user: %s" , username.c_str()); // 记录日志
return false;
}

// 绑定参数函数原型
// int sqlite3_bind_text(sqlite3_stmt* stmt,
// int index,
// const char* value,
// int n,
// void(*destroy)(void*) /* 或者使用 SQLITE_TRANSIENT */);
sqlite3_bind_text(stmt, 1, username.c_str(), -1, SQLITE_STATIC);

// 执行SQL语句
//功能:执行预编译的 SQL 语句(prepared statement)。它会推进到下一个结果行或者直到整个查询完成。
//对于 SELECT 查询,每调用一次 sqlite3_step,就会获取下一行数据;
//对于 INSERT, UPDATE, DELETE 等非查询操作,则会在操作成功完成后返回。
//返回值:在处理 SELECT 查询时,如果还有更多的数据行可读取,将返回 SQLITE_ROW;
//当查询完全执行完毕且没有错误时,返回 SQLITE_DONE。
if (sqlite3_step(stmt) != SQLITE_ROW) {
LOG_INFO("User not found: %s" , username.c_str()); // 记录日志
sqlite3_finalize(stmt);
return false;
}

// 获取存储的密码并转换为std::string
//功能:该函数用于获取SQLite查询结果集中指定列的数据字节数,
//不包括结束符(对于文本数据)。如果查询结果当前行的指定列是一个BLOB或者字符串类型,则返回实际数据的长度。
//用法:在成功执行了SQL查询且调用 sqlite3_step 后,你可以循环遍历结果集中的每一行,
//然后对每一列调用 sqlite3_column_bytes 来获取该列数据的大小。
const char* stored_password = reinterpret_cast<const char*>(sqlite3_column_text(stmt, 0));
std::string password_str(stored_password, sqlite3_column_bytes(stmt, 0));

// 检查密码是否匹配
//sqlite3_finalize:
//功能:这个函数是用来释放预编译的SQL语句(也称为准备好的语句或预编译语句柄)所占用的资源。
//当使用 sqlite3_prepare_v2 或相关函数成功预编译了一个SQL查询后,该查询会关联一个内部表示,并在内存中保留一些状态信息,
//以便多次执行相同的查询而无需每次都解析和编译。
//用法:在完成所有查询执行并不再需要预编译的SQL语句时,应该调用 sqlite3_finalize 函数,
//传入预编译语句句柄作为参数。这样可以释放与该句柄相关的资源,防止内存泄漏。
sqlite3_finalize(stmt);
if (stored_password == nullptr || password != password_str) {
LOG_INFO("Login failed for user: %s password:%s stored password is %s" ,username.c_str() ,password.c_str(), password_str.c_str()); // 记录日志
return false;
}

// 登录成功,记录日志
LOG_INFO("User logged in: %s" , username.c_str());
return true;
}
};

myserver.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
#include <iostream>
#include <map>
#include <functional>
#include <string>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <string.h>
#include <iomanip>
#include <unistd.h>

#include <sstream>
#include "Logger.h"
#include "Database.h"
#define PORT 8080

// 请求处理函数类型定义
using RequestHandler = std::function<std::string(const std::string&)>;

// 分别为GET和POST请求设置路由表
std::map<std::string, RequestHandler> get_routes;
std::map<std::string, RequestHandler> post_routes;
Database db("users.db"); // 创建数据库对象



// 然后在 parseFormBody 函数中使用它
std::map<std::string, std::string> parseFormBody(const std::string& body) {
std::map<std::string, std::string> params;
std::istringstream stream(body);
std::string pair;

LOG_INFO("Parsing body: %s", body.c_str()); // 记录原始body数据

while (std::getline(stream, pair, '&')) {
std::string::size_type pos = pair.find('=');
if (pos != std::string::npos) {
std::string key = pair.substr(0, pos);
std::string value = pair.substr(pos + 1);
params[key] = value;

LOG_INFO("Parsed key-value pair: %s = %s" , key.c_str(), value.c_str()); // 记录每个解析出的键值对
} else {
// 错误处理:找不到 '=' 分隔符
std::string error_msg = "Error parsing: " + pair;
LOG_ERROR(error_msg.c_str()); // 记录错误信息
std::cerr << error_msg << std::endl;
}
}
return params;
}

// 初始化路由表
void setupRoutes() {
LOG_INFO("Setting up routes"); // 记录路由设置
// GET请求处理
get_routes["/"] = [](const std::string& request) {
return "Hello, World!";
};
get_routes["/register"] = [](const std::string& request) {
// TODO: 实现用户注册逻辑
return "Please use POST to register";
};
get_routes["/login"] = [](const std::string& request) {
// TODO: 实现用户登录逻辑
return "Please use POST to login";
};

// 修改POST路由逻辑
post_routes["/register"] = [](const std::string& request) {
// 解析用户名和密码
// 例如从请求中解析 username 和 password,这里需要您自己实现解析逻辑
auto params = parseFormBody(request);
std::string username = params["username"];
std::string password = params["password"];

// 调用数据库方法进行注册
if (db.registerUser(username, password)) {
return "Register Success!";
} else {
return "Register Failed!";
}
};

// 登录路由
post_routes["/login"] = [](const std::string& request) {
// 解析用户名和密码
auto params = parseFormBody(request);
std::string username = params["username"];
std::string password = params["password"];

// 调用数据库方法进行登录
if (db.loginUser(username, password)) {
return "Login Success!";
} else {
return "Login Failed!";
}
};
// TODO: 添加其他路径和处理函数
}

// 解析HTTP请求
std::tuple<std::string, std::string, std::string> parseHttpRequest(const std::string& request) {
LOG_INFO("Parsing HTTP request"); // 记录请求解析
// 解析请求方法和URI
size_t method_end = request.find(" ");
std::string method = request.substr(0, method_end);
size_t uri_end = request.find(" ", method_end + 1);
std::string uri = request.substr(method_end + 1, uri_end - method_end - 1);

// 提取请求体(对于POST请求)
std::string body;
if (method == "POST") {
size_t body_start = request.find("\r\n\r\n");
if (body_start != std::string::npos) {
body = request.substr(body_start + 4);
}
}

return {method, uri, body};
}



// 处理HTTP请求
std::string handleHttpRequest(const std::string& method, const std::string& uri, const std::string& body) {
LOG_INFO("Handling HTTP request for URI: %s" , uri.c_str()); // 记录请求处理
if (method == "GET" && get_routes.count(uri) > 0) {
return get_routes[uri](body);
} else if (method == "POST" && post_routes.count(uri) > 0) {
return post_routes[uri](body);
} else {
return "404 Not Found";
}
}

int main() {

int server_fd, new_socket;
struct sockaddr_in address;
int addrlen = sizeof(address);

// 创建socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
LOG_INFO("Socket created");

// 定义地址
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);

// 绑定socket
bind(server_fd, (struct sockaddr *)&address, sizeof(address));

// 监听请求
listen(server_fd, 3);
LOG_INFO("Server listening on port %d", PORT); // 记录服务器监听


// 设置路由
setupRoutes();
LOG_INFO("Server starting");
while (true) {
// 接受连接
new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen);

// 读取请求
char buffer[1024] = {0};
read(new_socket, buffer, 1024);
std::string request(buffer);

// 解析请求
auto [method, uri, body] = parseHttpRequest(request);

// 处理请求
std::string response_body = handleHttpRequest(method, uri, body);

// 发送响应
std::string response = "HTTP/1.1 200 OK\nContent-Type: text/plain\n\n" + response_body;
send(new_socket, response.c_str(), response.size(), 0);

// 关闭连接
close(new_socket);
}

return 0;
}