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

news/2024/10/28 0:17:22/

C语言-打卡机

  • 一.功能
      • 1.上班打卡
      • 2.下班打卡
      • 3.设置每日工作时长
      • 4.测试需要6s=1h
      • 5.弹性打卡制
      • 6.周报
  • 二.整体构思
  • 三.自定义时间
  • 四.Sqlite3数据库基本操作(增删改查)及其封装形式
  • 五.打卡机:多线程同时实现定时自动退出和用户主动退出
  • 六.通过宏定义实现测试和发布两个版本
  • 七.makefile文件编写

一.功能

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;elseprintf("您的工作时常不足,还差%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.oclkmachine: $(objects)clang $^ -o $@ -l sqlite3rm ./*.o
main.o:	main.cclang -c main.c
clockin_machine.o: clockin_machine.cclang -c clockin_machine.c
usr_time.o: usr_time.cclang -c usr_time.c
usr_sql3.o: usr_sql3.cclang -c usr_sql3.c
clean:rm ./*.o clkmachine

http://www.ppmy.cn/news/144385.html

相关文章

打卡机核心功能实现(C语言)

打卡机核心功能实现&#xff08;C语言&#xff09; 任务分析 应市场需求&#xff0c;某工程师现设计了一款新上下班打卡机&#xff0c;打卡机具有以下功能&#xff1a; &#xff08;1&#xff09;上班打卡&#xff0c;员工具有编号&#xff08;首位为 1 的六位编号&#xff0…

用树莓派制作刷脸打卡机——硬件部分

软件部分&#xff1a;https://blog.csdn.net/d_l_w_d_l_w/article/details/112273581 一.上手树莓派 1.安装系统 1.1 下载链接&#xff1a; ​ https://www.raspberrypi.org/software/operating-systems/#raspberry-pi-os-32-bit 1.2写入镜像 使用 Win32DiskImager 写入 …

用 node-xlsx 将从打卡机中导出的考勤信息处理生成考勤表

导出来的考勤信息表&#xff08;只是获取打卡信息并处理成报表.xlsx , 初始的表格没了&#xff09; 下图是 “报表.xlsx ” 看起来乱糟糟的&#xff0c;虽然能看但是需要花费大量的精力去处理才能成标准表格&#xff0c;下面我直接上代码&#xff08;代码里已有注释&#xff09…

视频打卡机是否可以应用了?

从GOOGLE近来公布的视频来看&#xff0c;视频识别还是蛮高的。举个例子&#xff0c;如果用在公司打卡上&#xff0c;就非常方便&#xff0c;只要从门口走进来&#xff0c;视频摄像头对准录一下&#xff0c;就可以实现打卡了&#xff0c;无接触&#xff0c;无停留。 如下图这样…

OpenHarmony成长计划#校园极客秀#碰一碰智能NFC打卡机

作品创意 NFC——近距离无线通讯技术目前已成为我们生活中随处可见、随手可用的技术&#xff0c;它可以提供轻松、安全、迅速的通信无线连接&#xff0c;被广泛地应用于各种场合&#xff0c;如公交一卡通、学生校园卡等。现在越来越多的智能手机都有NFC的功能&#xff0c;在这…

打卡机的设计——基本功能

打卡机设计 任务概述 应市场需求&#xff0c;某工程师现设计了一款新上下班打卡机&#xff0c;打卡机具有以下功能&#xff1a; &#xff08;1&#xff09; 上班打卡&#xff0c;员工具有编号&#xff08;首位为 1 的六位编号&#xff09;&#xff0c;输入编号后&#xff0c;再…

打卡机

1.题目&#xff1a; Problem Description LPRJ小工厂是刚兴起不久的标准工厂&#xff0c;每天早上八点开始上班&#xff0c;每天工作八小时&#xff0c;但是由于LPRJ小工厂近来员工懈怠于工作&#xff0c;经常迟到&#xff0c;于是经理LP决定用考勤打卡机来记录员工的上班时间&…

共建、共享开源 EDA 共性技术框架 | 2023 开放原子全球开源峰会开源 EDA 分论坛即将启幕

电子电路设计自动化&#xff08;EDA&#xff09;融合了计算机、微电子、计算数学、图形学和人工智能等众多前沿技术&#xff0c;为集成电路设计、制造和封装等整个产业提供至关重要的自动化辅助设计能力。集成电路是支撑国民经济、社会发展和保障国家安全的基础性、先导性和战略…