【员工管理系统】

前言

这是一个使用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 1
    char 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;
}

注:完整代码见:员工管理系统

测试

对所有功能进行测试,是否可以实现多个客户端同时登录,是否可以添加、删除、修改、查看员工信息,以及管理员查看历史记录等功能:

员工管理系统

猜你喜欢

转载自blog.csdn.net/a1379292747/article/details/129238827