diff --git a/.gitignore b/.gitignore index 1f51ab1..95a3c3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.o *.exe -bank.dat +*.dat +AGENTS.md .vscode/launch.json -.vscode/tasks.json +.vscode/tasks.json \ No newline at end of file diff --git a/README.md b/README.md deleted file mode 100644 index 0411f9d..0000000 --- a/README.md +++ /dev/null @@ -1,140 +0,0 @@ -# 简易银行账户管理系统 - -## 项目简介 -这是一个基于C语言开发的简易银行账户管理系统,支持基本的银行账户操作功能,包括开户、登录、存款、取款、查询余额、账户管理等。系统采用模块化设计,代码结构清晰,易于维护和扩展。 - -## 功能特性 -- **账户管理** - - 创建普通用户账户(权限2) - - 创建管理员账户(权限1) - - 删除账户(仅管理员可操作) - - 修改密码 -- **账户操作** - - 存款 - - 取款 - - 查询余额 -- **系统功能** - - 用户登录验证 - - 权限分级(管理员/普通用户) - - 数据持久化(自动保存到文件) - - 交易流水查询(管理员) - - 自动初始化默认管理员账户 - -## 系统架构 -### 核心模块 -1. **主程序 (bank.c)** - - 程序入口点 - - 菜单显示和用户交互 - - 权限控制逻辑 - -2. **账户管理 (account.c)** - - 账户创建、删除 - - 账户查找 - - 登录验证 - - 密码修改 - - 显示所有账户信息 - -3. **交易处理 (transaction.c)** - - 存款功能 - - 取款功能 - - 余额查询 - -4. **数据持久化 (file_io.c)** - - 数据保存到文件(bank.dat) - - 从文件加载数据 - - 初始化默认管理员账户 - -5. **工具函数 (utils.c)** - - 输入验证和清理 - - 安全的整数和浮点数输入 - -6. **头文件 (bank.h)** - - 全局常量定义 - - 账户结构体定义 - - 函数声明 - -7. **日志模块(log.c)** - - 日志记录和存储(循环链表) - -### 数据结构 -```c -typedef struct strAccount -{ - int id; // 账户ID(从1000开始) - char name[31]; // 姓名(最多30字符) - double balance; // 账户余额 - int password; // 6位数字密码 - int quanxian; // 权限(1=管理员,2=普通用户) -} STACCOUNT; -``` - -## 使用说明 -### 编译 -```bash -gcc bank.c account.c transaction.c file_io.c utils.c -o bank_system -``` - -### 运行 -```bash -./bank_system -``` - -### 操作流程 -1. **初始界面**(未登录状态) - - 选项1:开户(创建普通用户账户) - - 选项2:登录 - - 选项0:退出系统 - -2. **普通用户界面**(登录后) - - 选项1:存款 - - 选项2:取款 - - 选项3:查询余额 - - 选项0:退出账户 - -3. **管理员界面**(登录后) - - 选项1:存款 - - 选项2:取款 - - 选项3:查询余额 - - 选项4:显示所有账户 - - 选项5:删除账户 - - 选项6:创建管理员账户 - - 选项7:查看交易流水 - - 选项0:退出账户 - -### 默认账户 -系统首次运行时会自动创建一个默认管理员账户: -- **账户ID**: 1000 -- **姓名**: admin -- **密码**: 123456 -- **权限**: 管理员 - -## 技术细节 -- **最大账户数量**: 50个 -- **密码规则**: 6位数字(100000-999999) -- **数据存储**: 二进制文件(bank.dat) -- **输入验证**: 所有数值输入都包含错误处理 -- **内存管理**: 使用全局数组存储账户数据 - -## 文件说明 -- `bank.c` - 主程序文件 -- `account.c` - 账户管理功能实现 -- `transaction.c` - 交易处理功能实现 -- `file_io.c` - 文件读写功能实现 -- `utils.c` - 工具函数实现 -- `bank.h` - 头文件,包含结构体定义和函数声明 -- `bank.dat` - 数据存储文件(运行时自动生成) - -## 注意事项 -1. 密码必须为6位数字 -2. 存款和取款金额必须为正数 -3. 取款时会检查账户余额是否充足 -4. 删除账户功能仅管理员可用 -5. 系统会自动保存所有操作到文件,确保数据持久化 - -## 扩展建议 -- ~~添加交易记录功能~~ -- 实现账户排序和搜索 -- 增加更多权限级别 -- 改进用户界面(如使用图形界面) -- 添加数据加密功能 -- 实现多用户并发访问 \ No newline at end of file diff --git a/bank.c b/bank.c index c2b541a..f414eda 100644 --- a/bank.c +++ b/bank.c @@ -19,7 +19,7 @@ int main(void) printf("=== 简易银行账户管理系统 ===\n"); printf("欢迎使用! 系统已加载%d个账户\n", g_iAccCount); InitAdminAccount();//初始化管理员账号 - InitLogQueue(); + LoadMerkleRoot(); int iChoice=-1; //选择标志 int pChoice=-1; while (1) { @@ -59,7 +59,7 @@ int main(void) } break; case 1: - printf("1. 存款\n2. 取款\n3. 查询\n4. 显示所有账户\n5. 删除账户\n6. 创建管理员账户\n7.查看交易日志\n8.搜索交易日志\n0. 退出账户"); + printf("1. 存款\n2. 取款\n3. 查询\n4. 显示所有账户\n5. 删除账户\n6. 创建管理员账户\n7.查看交易日志\n8.搜索交易日志\n9.查看默克尔根\n10.验证日志完整性\n0. 退出账户"); pChoice=-1; scanf("%d", &pChoice); @@ -94,6 +94,15 @@ int main(void) scanf("%s", pattern); SearchLogs(pattern); break; + case 9: + PrintMerkleRoot(); + break; + case 10: + if (VerifyMerkleTree()) + printf("\n默克尔树验证通过,日志未被篡改。\n"); + else + printf("\n警告:默克尔树验证失败,日志可能已被篡改!\n"); + break; case 0://EXIT printf("谢谢使用!\n按任意键退出"); getchar(); diff --git a/bank.h b/bank.h index 805ca03..1d0dcbf 100644 --- a/bank.h +++ b/bank.h @@ -80,4 +80,22 @@ void ShowTransactionLogs(); // 显示所有日志 void SearchLogs(const char* pattern);//日志搜索 +int GetLogCount(); + +#define HASH_SIZE 32 + +typedef struct { + unsigned char data[HASH_SIZE]; +} HashValue; + +extern HashValue g_merkleRoot; + +void HashLogEntry(const LogEntry* entry, HashValue* out); +void BuildMerkleTree(HashValue* root); +void HashToHex(const HashValue* hash, char* hex_out); +void PrintMerkleRoot(); +int VerifyMerkleTree(); +void SaveMerkleRoot(); +void LoadMerkleRoot(); + #endif diff --git a/file_io.c b/file_io.c index ba986d2..48ff8f8 100644 --- a/file_io.c +++ b/file_io.c @@ -22,6 +22,11 @@ void SaveData() FILE *file = fopen("bank.dat", "wb"); fwrite(g_astAccounts, sizeof(STACCOUNT), 50, file); fclose(file); + + FILE *logfile = fopen("log.dat", "wb"); + fwrite(&g_logQueue, sizeof(LogQueue), 1, logfile); + fclose(logfile); + LoadData(); } @@ -52,4 +57,28 @@ void LoadData() } } g_iAccCount= result; + + InitLogQueue(); + FILE *logfile = fopen("log.dat", "rb"); + if (logfile != NULL) { + fread(&g_logQueue, sizeof(LogQueue), 1, logfile); + fclose(logfile); + } +} + +void SaveMerkleRoot() { + FILE* f = fopen("merkle.dat", "wb"); + if (!f) return; + fwrite(g_merkleRoot.data, 1, HASH_SIZE, f); + fclose(f); +} + +void LoadMerkleRoot() { + FILE* f = fopen("merkle.dat", "rb"); + if (!f) { + memset(g_merkleRoot.data, 0, HASH_SIZE); + return; + } + fread(g_merkleRoot.data, 1, HASH_SIZE, f); + fclose(f); } diff --git a/log.c b/log.c index 70c7a45..7073757 100644 --- a/log.c +++ b/log.c @@ -4,6 +4,13 @@ #include #include "bank.h" +int GetLogCount() { + if (g_logQueue.rear >= g_logQueue.front) + return g_logQueue.rear - g_logQueue.front; + else + return MAX_LOGS - g_logQueue.front + g_logQueue.rear; +} + // 初始化日志队列 void InitLogQueue() { g_logQueue.front = 0; @@ -50,6 +57,9 @@ int EnqueueLog(int id, const char* type, double amt, double bal) { // 队尾指针后移 g_logQueue.rear = (g_logQueue.rear + 1) % MAX_LOGS; + BuildMerkleTree(&g_merkleRoot); + SaveMerkleRoot(); + return 1; // 成功 } diff --git a/merkle.c b/merkle.c new file mode 100644 index 0000000..d05d670 --- /dev/null +++ b/merkle.c @@ -0,0 +1,98 @@ +#include +#include +#include +#include +#include +#include "bank.h" + +HashValue g_merkleRoot; + +static HCRYPTPROV g_hProv = 0; + +static void ensureProvider() { + if (g_hProv == 0) + CryptAcquireContext(&g_hProv, NULL, NULL, PROV_RSA_AES, CRYPT_VERIFYCONTEXT); +} + +static void sha256(unsigned char* out, const unsigned char* data1, unsigned int len1, + const unsigned char* data2, unsigned int len2) { + HCRYPTHASH hHash = 0; + DWORD hashLen = HASH_SIZE; + ensureProvider(); + CryptCreateHash(g_hProv, CALG_SHA_256, 0, 0, &hHash); + if (data1 && len1) CryptHashData(hHash, data1, len1, 0); + if (data2 && len2) CryptHashData(hHash, data2, len2, 0); + CryptGetHashParam(hHash, HP_HASHVAL, out, &hashLen, 0); + CryptDestroyHash(hHash); +} + +/* ==================== Merkle Tree ==================== */ + +void HashLogEntry(const LogEntry* entry, HashValue* out) { + char buf[256]; + sprintf(buf, "ID:%d Type:%s Amount:%.2f Balance:%.2f Time:%s Loc:%s", + entry->account_id, entry->type, entry->amount, + entry->balance, entry->timestamp, entry->location); + + sha256(out->data, (unsigned char*)buf, (unsigned int)strlen(buf), NULL, 0); +} + +static void hashPair(const HashValue* a, const HashValue* b, HashValue* out) { + sha256(out->data, a->data, HASH_SIZE, b->data, HASH_SIZE); +} + +void BuildMerkleTree(HashValue* root) { + int count = GetLogCount(); + + if (count == 0) { + memset(root->data, 0, HASH_SIZE); + return; + } + + HashValue* leaves = (HashValue*)malloc(count * sizeof(HashValue)); + if (!leaves) return; + + int i = g_logQueue.front; + int idx = 0; + while (i != g_logQueue.rear) { + HashLogEntry(&g_logQueue.logs[i], &leaves[idx]); + i = (i + 1) % MAX_LOGS; + idx++; + } + + int n = count; + while (n > 1) { + int j; + for (j = 0; j < n / 2; j++) + hashPair(&leaves[2 * j], &leaves[2 * j + 1], &leaves[j]); + + if (n % 2 == 1) { + hashPair(&leaves[n - 1], &leaves[n - 1], &leaves[n / 2]); + n = n / 2 + 1; + } else { + n = n / 2; + } + } + + memcpy(root->data, leaves[0].data, HASH_SIZE); + free(leaves); +} + +void HashToHex(const HashValue* hash, char* hex_out) { + int i; + for (i = 0; i < HASH_SIZE; i++) + sprintf(hex_out + i * 2, "%02x", hash->data[i]); + hex_out[HASH_SIZE * 2] = '\0'; +} + +void PrintMerkleRoot() { + char hex[65]; + HashToHex(&g_merkleRoot, hex); + printf("\n当前默克尔根: %s\n", hex); +} + +int VerifyMerkleTree() { + HashValue computed; + BuildMerkleTree(&computed); + return memcmp(computed.data, g_merkleRoot.data, HASH_SIZE) == 0; +} \ No newline at end of file diff --git a/transaction.c b/transaction.c index d3836e3..2c2608c 100644 --- a/transaction.c +++ b/transaction.c @@ -67,7 +67,7 @@ void Withdraw(int id) printf("请输入密码"); scanf("%d",&temp_password); if (g_astAccounts[FindAccount(id)].password==temp_password) { - if (g_astAccounts[FindAccount(id)].balance>amount) { + if (g_astAccounts[FindAccount(id)].balance>=amount) { g_astAccounts[FindAccount(id)].balance-=amount; EnqueueLog(id, "Withdraw", amount, g_astAccounts[FindAccount(id)].balance); printf("success");