C语言-打卡机(sqlite数据库、多线程)

一.功能

1.上班打卡

2.下班打卡

3.设置每日工作时长

4.测试需要6s=1h

5.弹性打卡制

6.周报

二.整体构思

  1. 根据题目要求,发现如果不使用数据库就不太容易实现全部功能,也有想过将数据通过结构体储存,并且输出到自定义格式的文件中,给他写个load和saveAs,但这样比数据库更麻烦。所以最终选择用sqlite3来做。
  2. 数据库每周会新建一张表格,列:id,姓名,周一到周五上下班时间。(测试模式不新建,默认打开test表格)。
    i. Id储存方式:int工号
    ii. 上下班时间储存方式:由于上下班时间只需要记录在一天中的时刻就可以,所以选择将“从当天0:00到打卡时刻的秒数”以int方式储存,方便计算与转换。
  3. 总结一下需要封装的数据库操作:打开、关闭数据库,增加员工,上下班打卡,查询id是否存在,将当前表格格式化输出,打印周报。
  4. 时间:由于测试需要6秒代表一个小时,而且<time_h>里面的函数并不太好用,所以新建一个用户自定义的时间查询函数库,需要有以下功能:
    i. 初始化:记录打卡机开启时间,并返回离自动关闭剩余的时间(秒),方便计时线程自动kill掉打卡机线程。
    ii. 查询时间:“从当天0:00到打卡时刻的秒数”,用于打卡的时候写入数据库。
    iii. 查询时间:当日是星期几,用于打卡的时候写入数据库。

三.自定义时间

发布版本比较简单,就是个简单的查询。之于测试版,只需要记录初始化时间,以这个时间作为周一早上7点,查询函数只需要计算当前时间和记录的初始化时间之间的差值,就可以得出当前的时刻。具体如下:

usr_time.h:

#ifndef __USR_TIME__
#define __USR_TIME__


int usr_time_Init(void);        //初始化时间,返回离自动关闭剩余剩余的秒数
int usr_time_GetDayInWeek();    //获取当前星期,1-7
int usr_time_GetSecInDay();     //获取当前时刻的秒数(从当日0:00起)

#endif

usr_time.c:

#include "usr_time.h"
#include <time.h>
#include <stdio.h>
#include <unistd.h>
#include "main.h"
#ifdef __TEST__
time_t start_timet;
int usr_time_Init(void){//返回离自动关闭剩余的秒数
    time(&start_timet);
    return 6*(24*5+5);//6秒等于一小时,共5天零5小时。
}
time_t time_rt;
int usr_time_GetDayInWeek(){
    time(&time_rt);
    int res = (int)((time_rt-start_timet+42)/144)+1;
    res = (res<1)?1:((res>5)?5:res);
    return res;
}
int usr_time_GetSecInDay(){
    time(&time_rt);
    return ((time_rt-start_timet+42)%144)*600;
}
#else
time_t a ;
struct tm* b;
int usr_time_Init(void){//返回离自动关闭剩余的秒数
    time(&a);
    b = localtime(&a);
    if(b->tm_wday>5){
        return (12-(b->tm_wday))*86400+(24-(b->tm_hour)*3600)+(60-(b->tm_min)*60+(b->tm_sec));
    }
    return (5-(b->tm_wday))*86400+(86400-usr_time_GetSecInDay());
}
int usr_time_GetDayInWeek(){
    time(&a);
    return localtime(&a)->tm_wday;
}
int usr_time_GetSecInDay(){
    time(&a);
    b = localtime(&a);
    return (b->tm_hour)*3600+(b->tm_min)*60+(b->tm_sec);
}
#endif

四.Sqlite3数据库基本操作(增删改查)及其封装形式

Sqlite3的需要稍微掌握一下数据库的知识和操作语句。稍微复习一下增删改查:
增:
insert into <tab Name> values(xx,xx,x,xx,xx)
删:
delete from <tab Name> where id=xx
改:
update <tab Name> set <column Name>=xx where id=xx
查:
select * from <tab Name>
然后就是通过各种字符串操作,将这些常用的功能封装好,方便使用。
usr_sql3.h:


#ifndef __USR_SQL3_H__
#define __USR_SQL3_H__
/* 封装了一些数据库操作 ,分别是:
*  打开、关闭数据库             sql_OpenDB      sql_CloseDB
*  检查id是否存在              sql_IsIdExit
*  打开表格                   sql_OpenOrCreatTab
*  新建一行数据                sql_NewLine
*  删除一行数据                sql_DelLine
*  打卡                       sql_clock
*  打印当前表格                sql_showAll
*  从当前表格生成并打印周报表    sql_showWeekReport
*/
void sql_OpenDB(const char * address);
void sql_CloseDB(void);
int sql_IsIdExit(int id);
void sql_OpenOrCreatTab(const char* address);
void sql_NewLine(int id, char* name);
void sql_DelLine(int id);
void sql_clock(int id,int dayInWeek,int secondInADay, int in1_out2, int min_workHour);
void sql_showAll(void);
void sql_showWeekReport(int min_WorkHour);
#endif

usr_sql3.c:

#include <sqlite3.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "usr_sql3.h"
#include "main.h"

static sqlite3 * db;
static char* tabname_using;
static char *errorMsg;
void sql_OpenDB(const char* address){
    sqlite3_open(address,&db);
}
void sql_CloseDB(void){
    int res = sqlite3_close(db);
}
int sql_IsIdExit(int id){
    char* final = (char*)malloc(200);
    sprintf(final,"select * from %s",tabname_using);
    int nrow,ncolumn;
    char ** db_result;
    int res = sqlite3_get_table(db,final,&db_result,&nrow,&ncolumn,&errorMsg);
    int id_sele=0;
    for(int i=0;i<(nrow+1)*ncolumn;i+=ncolumn)
    {
        sscanf(db_result[i],"%d",&id_sele);
        if(id_sele==id){
            return i;
        }
    }
    return 0;
}
void sql_OpenOrCreatTab(const char* tabname){
    char* s1 = "create table if not exists ";
    char* s3 = "(id int(6),name char(20),mon_in int(20),mon_out int(20),tue_in int(20),tue_out int(20),wed_in int(20),wed_out int(20),thu_in int(20),thu_out int(20),fri_in int(20),fri_out int(20))";
    char* final = (char*)malloc(strlen(tabname)+strlen(s1)+strlen(s3));
    sprintf(final,"%s%s%s",s1,tabname,s3);
    sqlite3_exec(db,final,0,0,&errorMsg);
    tabname_using = (char* )tabname;
}
void sql_NewLine(int id,char* name){
    char final[200];
    sprintf(final,"insert into %s values(%d,'%s',90000,90000,90000,90000,90000,90000,90000,90000,90000,90000)",tabname_using,id,name);
    sqlite3_exec(db,final,0,0,&errorMsg);
}
void sql_DelLine(int id){
    char final[200];
    sprintf(final,"delete from %s where id=%d",tabname_using,id);
    sqlite3_exec(db,final,0,0,&errorMsg);
}
void sql_clock(int id,int dayInWeek,int secondInADay, int in1_out2 ,int min_workHour){
    char week[5];
    switch (dayInWeek)
    {
    case 1: strcpy(week, "mon_"); break;
    case 2: strcpy(week, "tue_"); break;
    case 3: strcpy(week, "wed_"); break;
    case 4: strcpy(week, "thu_"); break;
    case 5: strcpy(week, "fri_"); break;
    default:return;
    }
    char final[200];
    if(in1_out2==1){
        sprintf(final,"update %s set %s%s=%d where id=%d",tabname_using,week,"in",secondInADay,id);
        sqlite3_exec(db,final,0,0,&errorMsg);
        return;
    }
    sprintf(final,"update %s set %s%s=%d where id=%d",tabname_using,week,"out",secondInADay,id);
    //查询今天的上班打卡时间,并计算时常

    char select_query[100];
    sprintf(select_query,"select * from %s",tabname_using);
    int nrow,ncolumn;
    char ** db_result;
    sqlite3_get_table(db,select_query,&db_result,&nrow,&ncolumn,&errorMsg);
    int clkin_timep;
    sscanf(db_result[2*dayInWeek+sql_IsIdExit(id)],"%d",&clkin_timep);
    if (clkin_timep==90000){
        printf("您今日没有上班打卡!\n");
        return;
    }
    sqlite3_exec(db,final,0,0,&errorMsg);
    printf("%d:下班打卡成功\n",id);
    int hour_ = (secondInADay - clkin_timep)/3600;
    int min = ((secondInADay - clkin_timep)%3600) / 60;
    printf("您今天的工作时长是:%d小时%d分\n",hour_,min);
    if (hour_>=min_workHour)
        return;
    else
        printf("您的工作时常不足,还差%d分钟\n",(min_workHour*3600-(secondInADay-clkin_timep))/60);
}
void sql_showAll(void){
    char select_query[100];
    sprintf(select_query,"select * from %s",tabname_using);
    int nrow,ncolumn;
    char ** db_result;
    sqlite3_get_table(db,select_query,&db_result,&nrow,&ncolumn,&errorMsg);
    int i,j;
    int sec;
    for(i=0;i<(nrow+1)*ncolumn;i+=ncolumn)
    {
        for(j=0;j<ncolumn;j++)
        {
            if(j>1&&i>0){
                sscanf(db_result[i+j],"%d",&sec);
                if(sec>86400){
                    printf("无\t");
                }else{
                    printf("%d:%d\t",sec/3600,(sec%3600)/60);
                }
            }else{
                printf("%s\t",db_result[i+j]);
            }
        }
        printf("\n");
    }
}
void sql_showWeekReport(int min_WorkHour){
    
    char select_query[100];
    sprintf(select_query,"select * from %s",tabname_using);
    int nrow,ncolumn;
    char ** db_result;
    sqlite3_get_table(db,select_query,&db_result,&nrow,&ncolumn,&errorMsg);
    printf("\n\
**************************************************************\n\
**                          周报!                          **\n\
**************************************************************\n\
姓名:\t\t平均时长:\t缺卡次数:\t工时不足次数:\n");
    for(int i = ncolumn; i < (nrow+1) * ncolumn; i += ncolumn){
        int in[5]={90000,90000,90000,90000,90000};
        int out[5]={90000,90000,90000,90000,90000};
        for(int m = 2;m<12;m++){
            sscanf(db_result[i+m],"%d",(m%2==0)?(in+(m-2)/2):(out+(m-3)/2));
        }
        int sum=0;
        int lossTimes =0;
        int NotEnoughTimes =0;
        for(int index = 0;index<5;index++){
            if(in[index]!=90000 && out[index]!=90000){
                int dayWorkSec = out[index]-in[index];
                sum+= dayWorkSec;
                if(dayWorkSec<min_WorkHour*3600) NotEnoughTimes++;
                continue;
            }
            NotEnoughTimes++;
            if(in[index]==90000) lossTimes++;
            if(out[index]==90000) lossTimes++;
        }
        char* name = db_result[i+1];
        int aver_h = sum/5/3600;
        int aver_m = ((sum/5)%3600)/60;
        printf("%s\t\t%d时%d分\t\t%d\t\t%d\n",name,aver_h,aver_m,lossTimes,NotEnoughTimes);
    }
    printf("\n\n\n");
}

五.打卡机:多线程同时实现定时自动退出和用户主动退出

做到这个功能需要使用三个线程:
主线程:开启“自动退出计时”线程,和“真正的打卡机线程”,然后阻塞等待“真正的打卡机线程”结束,但是打卡机线程结束可能是用户主动选择退出,所以需要在“真正的打卡机线程”结束之后,kill掉“自动退出计时”线程。

自动退出计时:需要在某个时刻杀掉打卡机线程,所以“自动退出”线程很简单,就是暂停一段时间,然后kill掉“打卡机线程”。

打卡机线程:一个大循环阻塞式接收输入,然后switch到对应的功能。
代码如下:

clockin_machine.h:

#ifndef __CLOCKIN_MACHINE__
#define __CLOCKIN_MACHINE__
void clockin_machine_start(void);
void TrueClkInMachine(void);
void DeadLineShutDown(void);
void ClockIn_local(int a);
void AddEmployee(void);
void DelEmployee(void);
void SetWorkTimePerDay(void);
void ShowWeekSummary(void);
#endif

clockin_machine.c:


#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include "clockin_machine.h"
#include "usr_time.h"
#include "usr_sql3.h"
#include "sqlite3.h"
#include <unistd.h>
#include <time.h>
#include "main.h"
sqlite3 *db = 0;
pthread_t thTrueClkInMachine, thDeadLineShutDown;
int minWorkHour =9;

//进行一些打卡机的初始化工作,并控制打卡机的开启停止
void clockin_machine_start(){
    int res;
    char *s;
    printf("clockin_machine is started!\n");
    sql_OpenDB("./clk_machine.sqlite");
    #ifdef __TEST__
    //新建一个test空表格,列为:员工编号,姓名,周1-5上下班打卡时间
    sql_OpenOrCreatTab("test");
    #else
    //查询当前的周,如果是数据库里没有的,就新建一个表格,以该周的周一的日子命名,如果有,就打开这个表
    struct tm* tms;
    time_t tt;
    time(&tt);
    tms = localtime(&tt);
    int year = tms->tm_year;
    int FirstDayOfThisWeek_InYear = (tms->tm_wday<6)?((tms->tm_yday)-(tms->tm_wday-1)):((tms->tm_yday)+8-(tms->tm_wday));
    char finalTabName[20]="                    ";
    sprintf(finalTabName,"tab_%d%d",year,FirstDayOfThisWeek_InYear);
    sql_OpenOrCreatTab(finalTabName);
    printf("当前表格为:%s\n",finalTabName);

    #endif

    //打开真正的打卡机线程
    res = pthread_create(&thTrueClkInMachine, NULL, (void *)TrueClkInMachine, NULL);
    res = pthread_create(&thDeadLineShutDown, NULL, (void *)DeadLineShutDown, NULL);

    //线程结束以后,打印该周总结
    pthread_join(thTrueClkInMachine, NULL);
    //如果计时器没有结束(用户主动推出),就杀掉计时器。
    pthread_kill(thDeadLineShutDown,SIGKILL);
    //展示一周总结
    ShowWeekSummary();
}

//直接计算出离最终结束剩余的时间,直接延时到终结,然后shut down掉打卡机线程,结束。
void DeadLineShutDown(void){
    int restTimeSec = usr_time_Init();
    sleep(restTimeSec);
    pthread_kill(thTrueClkInMachine,SIGKILL);
}
//打卡机,根据命令 进行功能选择。
void TrueClkInMachine(void){
    system("clear");
    while(1){
        printf("\n\
        **************************\n\
        **        打卡机        **\n\
        **************************\n\
        1:上班打卡\n\
        2:下班打卡\n\
        3:添加员工\n\
        4:删除员工\n\
        5:设置每日上班时长\n\
        6:查看周报\n\
        7:结束\n\
        8:浏览表格\n\
        ");
        int chooseindex;
        int res = scanf("%d",&chooseindex);
        if(res ==1){//输入成功
            switch (chooseindex)
            {
            case 1:
                ClockIn_local(1);
                break;
            case 2:
                ClockIn_local(2);
                break;
            case 3:
                AddEmployee();
                break;
            case 4:
                DelEmployee();
                break;
            case 5:
                SetWorkTimePerDay();
                break;
            case 6:
                ShowWeekSummary();
                break;
            case 7:
                return;
            case 8:
                sql_showAll();
                break;
            default:
                break;
            }
        }
    }
}
void ClockIn_local(int in1_out2){
    system("clear");
    int id;
    printf("输入工号:____________\b\b\b\b\b\b\b\b\b\b\b\b");

    //输入六位数字工号 
    int res;
    do{
        printf("请输入六位数字!\n");
        fflush(stdin);
        res = scanf("%d",&id);
    }while(res!=1 || id<100000 || id>999999);
    //检查数据库中是否有这个工号
    if(!sql_IsIdExit(id)){
        printf("这个ID不存在!\n");
        return;
    }
    //计算出校验码,(id除首位反序,并求和)
    char antiid[7];
    sprintf(antiid,"%d",id);
    char reg;
    for(int i =1;i<3;i++){
        reg = antiid[i];
        antiid[i]=antiid[6-i];
        antiid[6-i] = reg;
    }
    int antiid_num;
    sscanf(antiid,"%d",&antiid_num);
    antiid_num+=id;
    printf("%d\n请输入校验码:______\b\b\b\b\b\b",antiid_num);
    int input;
    scanf("%d",&input);
    if(input==antiid_num){
        sql_clock(id,usr_time_GetDayInWeek(),usr_time_GetSecInDay(),in1_out2,minWorkHour);
        printf("%d打卡成功!\n",id);
    }else{
        printf("校验码输入错误!打卡失败!\n");
    }
}

void AddEmployee(){
    system("clear");
    int id;
    printf("输入工号:____________\b\b\b\b\b\b\b\b\b\b\b\b");

    //输入六位数字工号 
    int res;
    do{
        printf("请输入六位数字!\n");
        fflush(stdin);
        res = scanf("%d",&id);
    }while(res!=1 || id<100000 || id>999999);
    //检查数据库中是否有这个工号
    if(sql_IsIdExit(id)){
        printf("这个ID已经存在!\n");
        return;
    }
    char name[20];
    printf("请输入姓名:________\b\b\b\b\b\b\b\b");
    scanf("%s",name);
    sql_NewLine(id,name);
}
void DelEmployee(){
    
    system("clear");
    int id;
    printf("输入工号:____________\b\b\b\b\b\b\b\b\b\b\b\b");

    //输入六位数字工号 
    int res;
    do{
        printf("请输入六位数字!\n");
        fflush(stdin);
        res = scanf("%d",&id);
    }while(res!=1 || id<100000 || id>999999);
    //检查数据库中是否有这个工号
    if(!sql_IsIdExit(id)){
        printf("这个ID不存在!\n");
        return;
    }
    sql_DelLine(id);
}
void SetWorkTimePerDay(){
    printf("请设置每日最短工作时间:____小时。\b\b\b\b\b\b");
    scanf("%d",&minWorkHour);
}
void ShowWeekSummary(){
    sql_showWeekReport(minWorkHour);
}

六.通过宏定义实现测试和发布两个版本

在main.h中定义一个#define TEST
然后在需要测试和发布版本不同的地方,用条件编译:
#ifdef TEST
…………
#else
…………
#endif
Main和main.h代码如下:
main.h:

//#define __TEST__

main.c:

#include <stdio.h>
#include "clockin_machine.h"
#include "main.h"
int main(){
    clockin_machine_start();
}

七.makefile文件编写

makefile如何编写网上多的是,不再赘述。
这里要注意:由于sqlite3.h并不是c标准库,所要手动添加链接。最终makefile文件如下:

makefile

objects = main.o \
clockin_machine.o \
usr_time.o \
usr_sql3.o

clkmachine: $(objects)
	clang $^ -o $@ -l sqlite3
	rm ./*.o
main.o:	main.c
	clang -c main.c
clockin_machine.o: clockin_machine.c
	clang -c clockin_machine.c
usr_time.o: usr_time.c
	clang -c usr_time.c
usr_sql3.o: usr_sql3.c
	clang -c usr_sql3.c
clean:
	rm ./*.o clkmachine
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值