【员工管理系统】
员工管理系统
- 前言
- 需求分析
- 系统设计
-
- 系统框图
- 所需技术
- 系统实现
-
- 编写代码
- 测试
前言
这是一个使用epoll实现TCP并发服务器,并让客户端登录服务器可以进行员工的管理,员工的信息存储在sqlite数据库中,对数据库进行增删改查实现对员工的添加,删除,修改,查询等功能;
需求分析
1)服务器负责管理所有员工表单(以数据库形式),其他客户端可通过网络连接服务器来查询员工表单。
2)需要账号密码登陆,其中需要区分管理员账号还是普通用户账号。
3)管理员账号可以查看、修改、添加、删除员工信息,同时具有查询历史记录功能,管理员要负责管理所有的普通用户。
4)普通用户只能查询修改与本人有关的相关信息,其他员工信息不得查看修改。
5)服务器能同时相应多台客户端的请求功能。实现并发
系统设计
系统框图
server端:
client端:
所需技术
一、信息存储:
使用sqlite数据库对员工信息的存储,其中包括管理员信息和普通员工信息;同时也要对历史记录进行存储;
二、TCP通信:
使用TCP服务器,实现服务器和客户端之间的接发数据,处理客户端发来的请求,实现对员工的管理;
三、并发服务器:
并发服务器的实现方法很多,可以使用多进程多线程,也可以使用IO多路复用,这里我使用了epoll的方法来实现并发服务器,可以同时处理多个客户端发来的请求;
系统实现
编写代码
一、函数和结构体的封装
#ifndef __COMMON_H__
#define __COMMON_H__
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sqlite3.h>
#include <time.h>
//定义大小
#define NAMELEN 20
#define DATALEN 50
#define MSGLEN 500
#define MAX_EVENTS 10
//定义IP地址和端口号
#define IP "192.168.250.100"
#define PORT 8888
// 定义消息类型
#define LOGIN 1
#define ADD 2
#define DELETE 3
#define MODIFY 4
#define SEARCH 5
#define HISTORY 6
//用户类型
#define ADMIN 0
#define USER 1
//定义员工信息结构体
typedef struct staff_info
{int id; // 员工编号int usertype; // ADMIN 0 USER 1char name[NAMELEN]; // 姓名char passwd[8]; // 密码int age; // 年龄char phone[NAMELEN]; // 电话char addr[DATALEN]; // 地址char work[DATALEN]; // 职位char date[DATALEN]; // 入职年月int level; // 等级double salary; // 工资
} staff_info_t;
/*定义双方通信的结构体信息*/
typedef struct
{int msgtype; // 请求的消息类型char recvmsg[MSGLEN]; // 通信的消息int flags; // 标志位staff_info_t info; // 员工信息
} MSG;//服务器用到的函数
int create_socket(const char *address, int port);
void init_sql(sqlite3 *db);
int handle_client(int clientfd, sqlite3 *db);
void getdata(char *date); //获取时间
int do_login(int clientfd, MSG *msg, sqlite3 *db);
int do_add(int clientfd, MSG *msg, sqlite3 *db);
int do_delete(int clientfd, MSG *msg, sqlite3 *db);
int do_change(int clientfd, MSG *msg, sqlite3 *db);
int do_search(int clientfd, MSG *msg, sqlite3 *db);
int do_history(int clientfd, MSG *msg, sqlite3 *db);
//客户端用到的函数
int create_socket(const char *address, int port);
int login(int socket, MSG *msg, int flag);
int add(int sockfd, MSG *msg);
int delete(int sockfd, MSG *msg);
int change(int sockfd, MSG *msg);
int search(int sockfd, MSG *msg);
int history(int sockfd, MSG *msg);#endif
二、epoll并发服务器模型
epfd = epoll_create(1);if (epfd == -1){perror("epoll_create1() error");exit(1);}// 将服务器套接字加入epoll实例的监听列表event.data.fd = sockfd;event.events = EPOLLIN;if (epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event) == -1){perror("epoll_ctl() error");exit(1);}while (1){epct = epoll_wait(epfd, events, MAX_EVENTS, -1); // 等待事件到来,阻塞模式if (epct == -1){perror("epoll_wait() error");exit(1);}// 处理准备就绪的套接字for (i = 0; i < epct; i++){if (events[i].data.fd == sockfd){// 新的客户端连接请求clientfd = accept(sockfd, (struct sockaddr *)&cin, &cin_len);if (clientfd < 0){perror("accept() error");exit(1);}printf("[%s:%d]连接到服务器..\\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));// 将客户端套接字加入epoll实例的监听列表event.data.fd = clientfd;event.events = EPOLLIN;if (epoll_ctl(epfd, EPOLL_CTL_ADD, clientfd, &event) == -1){perror("epoll_ctl() error");exit(1);}}else{// 客户端数据可读handle_client(events[i].data.fd, db);}}}
三、初始化数据库
void init_sql(sqlite3 *db)
{printf("正在初始化...\\n");// 创建表char sql[256] = "";char *errmsg = NULL;strcpy(sql, "create table if not exists usr (id int,name char PRIMARY KEY,passwd char,age int,phone char,addr char,work char,data char,level int,salary double);");if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){printf("sqlite3_exec error:%s\\n", errmsg);return;}strcpy(sql, "create table if not exists log (name char,operations char,time char);");if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){printf("sqlite3_exec error:%s\\n", errmsg);return;}// 判断管理员的信息是否在数据库中char **result = NULL;int rows = -1;int columns = 0;strcpy(sql, "select * from usr where name='admin'");if (sqlite3_get_table(db, sql, &result, &rows, &columns, &errmsg) != SQLITE_OK){printf("sqlite3_get_table error:%s line=%d\\n", errmsg, __LINE__);return;}if (rows == 0){// 插入管理员信息sprintf(sql, "insert into usr values('1000','admin','admin','20','19156058040','上海','嵌入式','2022.09','4','20000');");}sqlite3_free_table(result);
}
这里直接初始化了一个管理员账户信息,管理员信息后续无法修改,有且只有一个管理员,后续对员工的管理只可以通过此管理员进行管理。
四、员工登录界面无法使用管理员账号登录
在客户端记录了登录身份信息,当处于普通员工登录界面时,无法使用管理员账户登录,服务器端会对客户端发来的数据进行判断,如果处于员工登录界面时,且用户信息是管理员,则发送登录失败。
int do_login(int clientfd, MSG *msg, sqlite3 *db)
{char data[128];int row;int cloumn;char sql[128];char *errmsg = NULL;char **result;// 匹配用户信息是否与密码表中相同sprintf(sql, "select * from usr where name = '%s' and passwd = '%s';", msg->info.name, msg->info.passwd);if (sqlite3_get_table(db, sql, &result, &row, &cloumn, &errmsg) != SQLITE_OK){printf("sqlite3_get_table error:%s line=%d\\n", errmsg, __LINE__);return -1;}// 密码表中存在改用户if (row == 1){strcpy(msg->recvmsg, "登录成功!!");msg->flags = 1; // 代表操作成功// 插入记录(用户登陆成功)getdata(data);sprintf(sql, "insert into log values('%s', 'login', '%s')", msg->info.name, data);if (sqlite3_exec(db, sql, NULL, NULL, &errmsg) != SQLITE_OK){printf("sqlite3_exec error:%s line=%d\\n", errmsg, __LINE__);return -1;}}// 密码表中不存在改用户或者用户登录管理员账号if (row == 0 || (msg->info.usertype == 1 && strcmp(msg->info.name, "admin") == 0)){printf("登录失败\\n");strcpy(msg->recvmsg, "登录失败!!");msg->flags = 0; // 代表操作失败}// 返回用户登录信息if (send(clientfd, msg, sizeof(MSG), 0) < 0){perror("send err");}return 0;
}
五、查找用户信息(添加,修改,删除 功能类似)
查找分为普通员工查找和管理员查找,管理员查找又可以根据姓名查找和查找全部,由于普通员工查找信息也是根据自己的用户名查找,所以这里只需要分两种情况编写代码,一种是根据用户名,一种是查找全部,这里是通过判断客户端传来的flag来进行判断,如果是员工查找或者是管理员通过用户名查找,flag=1,查找全部flag=0。
在使用sqlite3_get_table时,循环向客户端发送信息,一行一行的发送,客户端循环接收,当循环发送结束时,向客户端发送结束标志。
客户端
int search(int sockfd, MSG *msg)
{int n;msg->msgtype = SEARCH;if (msg->info.usertype == ADMIN){system("clear");printf("======================可查找选项==========================\\n");printf("------------------------菜单-----------------------------\\n");printf("\\t\\t\\t1.根据用户名查找\\n");printf("\\t\\t\\t2.查找全部\\n");printf("-----------------------------------------------------------\\n");printf("请输入选项:\\n");scanf("%d", &n);getchar();if (n == 1){msg->flags = 1;printf("请输入要查找的用户名:\\n");scanf("%s", msg->info.name);getchar();if (send(sockfd, msg, sizeof(MSG), 0) < 0){printf("send err\\n");return -1;}printf("id\\t\\tname\\t\\tpasswd\\t\\tage\\tphone\\t\\taddr\\twork\\tdate\\t\\tlevel\\tsalary\\t\\t\\n");// 接收成功与否while (1){if (recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("recv err\\n");return -1;}printf("%s\\n", msg->recvmsg);if (0 == strcmp(msg->recvmsg, "query end")){break;}}// 本次操作如果失败直接返回if (msg->flags == 0){printf("查找失败%s\\n2秒刷新页面\\n", msg->recvmsg);sleep(2);return -1;}// 操作成功打印查找之后的信息printf("按任意键退出查询界面:\\n");getchar();return 0;}else if (n == 2){msg->flags = 0;if (send(sockfd, msg, sizeof(MSG), 0) < 0){printf("send err\\n");return -1;}// 接收成功与否printf("id\\t\\tname\\t\\tpasswd\\t\\tage\\tphone\\t\\taddr\\twork\\tdate\\t\\tlevel\\tsalary\\t\\t\\n");while (1){if (recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("recv err\\n");return -1;}printf("%s\\n", msg->recvmsg);if (0 == strcmp(msg->recvmsg, "query end")){break;}}// 本次操作如果失败直接返回if (msg->flags == 0){printf("查找失败%s\\n2秒刷新页面\\n", msg->recvmsg);sleep(2);return -1;}// 操作成功打印查找之后的信息printf("按任意键退出查询界面:\\n");getchar();return 0;}return 0;}else{// 员工查询自己msg->flags = 1;strcpy(msg->info.name, name);if (send(sockfd, msg, sizeof(MSG), 0) < 0){printf("send err\\n");return -1;}printf("id\\t\\tname\\t\\tpasswd\\t\\tage\\tphone\\t\\taddr\\twork\\tdate\\t\\tlevel\\tsalary\\t\\t\\n");// 接收成功与否while (1){if (recv(sockfd, msg, sizeof(MSG), 0) < 0){printf("recv err\\n");return -1;}printf("%s\\n", msg->recvmsg);if (0 == strcmp(msg->recvmsg, "query end")){break;}}// 本次操作如果失败直接返回if (msg->flags == 0){printf("查找失败%s\\n2秒刷新页面\\n", msg->recvmsg);sleep(2);return -1;}printf("按任意键退出查询界面:\\n");getchar();return 0;}
}
服务器
int do_search(int clientfd, MSG *msg, sqlite3 *db)
{char sql[128];char *errmsg;char **result;int row;int cloum;int i, j;if (msg->flags == 1) // 根据姓名查找{sprintf(sql, "select *from usr where name = '%s' ", msg->info.name);}else if (msg->flags == 0) // 查找全部{sprintf(sql, "select *from usr ");}if (sqlite3_get_table(db, sql, &result, &row, &cloum, &errmsg) != SQLITE_OK){printf("%s\\n", errmsg);msg->flags = 0; // 失败标志}else{msg->flags = 1; // 成功标志}for (int i = 1; i <= row; i++){sprintf(msg->recvmsg, "%-8s\\t%-8s\\t%-8s\\t%-5s\\t%-15s\\t%-8s\\t%-8s\\t%-10s\\t%-5s\\t%-10s\\n", result[i * cloum], result[i * cloum + 1], result[i * cloum + 2], result[i * cloum + 3], result[i * cloum + 4], result[i * cloum + 5], result[i * cloum + 6], result[i * cloum + 7], result[i * cloum + 8], result[i * cloum + 9]);if (send(clientfd, msg, sizeof(MSG), 0) < 0){perror("send err");}}strcpy(msg->recvmsg, "query end");if (send(clientfd, msg, sizeof(MSG), 0) < 0){perror("send err");}sqlite3_free_table(result);return 0;
}
六、查询历史记录功能
管理员可以查询历史记录,在执行登录、添加员工、删除员工、修改员工信息时,都会将记录插入到记录表中,管理员可以查询记录表中的内容。查询记录和查询员工信息相似。
int do_history(int clientfd, MSG *msg, sqlite3 *db)
{char sql[128];char *errmsg;char **result;int row;int cloum;int i, j;sprintf(sql, "select *from log ");if (sqlite3_get_table(db, sql, &result, &row, &cloum, &errmsg) != SQLITE_OK){printf("%s\\n", errmsg);msg->flags = 0; // 失败标志}else{msg->flags = 1; // 成功标志}for (int i = 1; i <= row; i++){sprintf(msg->recvmsg, "%-8s\\t%-8s\\t%-8s\\t\\n", result[i * cloum], result[i * cloum + 1], result[i * cloum + 2]);if (send(clientfd, msg, sizeof(MSG), 0) < 0){perror("send err");}}strcpy(msg->recvmsg, "query end");if (send(clientfd, msg, sizeof(MSG), 0) < 0){perror("send err");}sqlite3_free_table(result);return 0;
}
注:完整代码见:员工管理系统
测试
对所有功能进行测试,是否可以实现多个客户端同时登录,是否可以添加、删除、修改、查看员工信息,以及管理员查看历史记录等功能:
员工管理系统