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

打卡机设计

任务概述

应市场需求,某工程师现设计了一款新上下班打卡机,打卡机具有以下功能:
(1) 上班打卡,员工具有编号(首位为 1 的六位编号),输入编号后,再
输入校验码,校验码生成规则:员工编号除首位反序,再与员工编号
求和,如:员工编号,110086,校验码为 178087。校验码错误即打
卡失败。记录打卡时间
(2) 下班打卡,只需输入员工编号即可。记录打卡时间,显示该人员今天
上班时长,如果上班时长不够,显示早退 xx 分钟。可以更新下班打
卡时间。无下班打卡显示缺卡。
(3) 可以设置规定上班时长,如 9 小时
(4) 测试需要可以规定 6 秒=实际 1 小时,每次测试,输入指令后,开启
打卡机,打卡机开启模拟时间为:周一早上七点。程序运行结束为周
五晚 12 点。
(5) 实行弹性打卡制,如前一天上班时长超过规定时长 3 小时以上,第
二天迟到 2 小时以内不算迟到。
(6) 打卡机运行结束之前,每周该打卡机会生成每周考勤周报,显示周平
均上班时长,周迟到,早退,缺卡次数等。

设计过程

本次设计分两个步骤:

步骤一:完成打卡机的基本功能,比如:计时及打卡。

步骤二:完善打卡机的功能,比如:弹性打卡。

1.打卡机的基本功能

1.1-打卡机计时功能与功能列表的实现

因测试需要规定6s为1h,则打卡机的1s为10m、1ms为600ms;

有因为在计时的同时需要响应用户的操作(打卡…)所以需要开启输入函数的非阻塞模式

1.1.1-非阻塞的IO的使用

在记时的同时需要响应用户的打卡等功能,有因为scanf函数默认是阻塞的所以我们需要开启非阻塞io。

代码如下:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(int argc, char const *argv[])
{
    int flags, flags2,test;
    //使用非阻塞io
    if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
    {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
    {
        perror("fcntl");
        return -1;
    }
    scanf("%d",&test);
    printf("#############1\n");
    usleep(2000000);
    printf("***************2\n");
    //关闭非阻塞io
    flags &= ~O_NONBLOCK;
    if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
    {
        perror("fcntl");
        return -1;
    }
    scanf("%d",&test);
    return 0;
}

1.1.2-打卡机记时功能实现与功能列表

要实现打卡机的时间记录功首先必须对打卡机的时候和现实时间进行对应最先想到的就是枚举

//实现代码
typedef unsigned long ulong;
enum
{
milliseconds = 600;
second = (milliseconds * 1000);
minutes = (second * 60);
hours = (minutes * 60); 
}

但是enum的成员变量都是int类型到hour的时候已经超出了int类型的表示范围。

所以换成结构体实现

//实现代码
typedef unsigned long ulong;

struct MachineTime
{
ulong milliseconds;
ulong seconds;
ulong minutes;
ulong hours;
}TimeState;

void machineTimer()
{
//打卡机时间对应
TimeState.milliseconds = 600;
TimeState.seconds = (TimeState.milliseconds * 1000);
TimeState.minutes = (TimeState.seconds * 60);
TimeState.hours = (TimeState.minutes * 60);     
}

但使用这种方式又会导致每个时间都需要转换再计算,所以我们可以使用宏定义一个毫秒数在超出1000ms的时候对其进行转会成秒然后依次转化。

又因为需要存储5天(周一至周五)的数据所以可以将其存储在一个5行3列的数组当中。

在记时的同时我们可以用switch对输入的数据进行判断,实现功能列表功能。

用switch的好处在于可以美化代码,如使用if实现代码比较冗余;且用switch方便添加功能方便后期维护。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

#include "../head/utils.h"

typedef unsigned char uint8_t;
typedef unsigned long uint64_t;

#define millionsecond 600


int clockin_machine_start()
{
 uint8_t timeRecorder[5][3] = {0};
 uint8_t currentDays = 0;
 char funcSelect;
 int flags;
 uint64_t msTmpRecord = 0;

 
 //周一早上七点打卡机开始工作
 timeRecorder[0][2] = 7;
 //开启非阻塞io
 if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
 {
     perror("fcntl");
     return -1;
 }
 flags |= O_NONBLOCK;
 if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
 {
     perror("fcntl");
     return -1;
 }

 printf("记时功能开始.\n");
 while (1)
 {
     usleep(1000);
     //打卡机的1ms相当于600ms
     msTmpRecord += millionsecond;
     //ms -> s
     if (msTmpRecord >= 1000)
     {
         timeRecorder[currentDays][0] += 1;
         msTmpRecord -= 1000;
     }
     //s -> m
     if (timeRecorder[currentDays][0] >= 60)
     {
         timeRecorder[currentDays][1] += 1;
         timeRecorder[currentDays][0] -= 60;
     }
     //m -> h
     if (timeRecorder[currentDays][1] >= 60)
     {
         timeRecorder[currentDays][2] += 1;
         timeRecorder[currentDays][1] -= 60;
     }
     //一天的时间超过了
     if (timeRecorder[currentDays][2] >= 24)
     {
         currentDays += 1;
     }

     if (currentDays >= 5)
     {
         break;
     }
     
     
     scanf("%c",&funcSelect);
     switch (funcSelect)
     {
     case 'q':
         printf("签退\n");
         break;
     case 'r':
         printf("签到\n");
         break;
     default:
         break;
     }
     //保留测试接口用于退出记时
     if (funcSelect == 'S')
     {
         break;
     }

     
 }
 printf("记时功能结束.\n");
 printf("现在是星期%u %hhu时 %hhu分 %hhu秒.\n", (currentDays + 1), timeRecorder[currentDays][2], timeRecorder[currentDays][1], timeRecorder[currentDays][0]);

 // //关闭非阻塞io
 // flags &= ~O_NONBLOCK;
 // if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
 // {
 //     perror("fcntl");
 //     return -1;
 // }
 

}

1.2-打卡机打卡功能实现

打卡功能实现需要输出员工编号;然后对编号进行计算得到验证码。

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include "../head/utils.h"

#define millionsecond 600

/*
*********************************
  @函数功能:
    记时,功能列表
********************************* 
*/
int clockin_machine_start()
{
    uint8_t timeRecorder[5][3] = {0};
    uint8_t checkInRecorder[5][3] = {0};
    uint8_t checkOutRecorder[5][3] = {0};
    uint8_t currentDays = 0;
    uint8_t flagCheckIn = 0;
    int flags;
    uint64_t msTmpRecord = 0;

    //开启非阻塞io
    if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
    {
        perror("fcntl");
        return -1;
    }
    flags |= O_NONBLOCK;
    if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
    {
        perror("fcntl");
        return -1;
    }

    //周一早上七点打卡机开始工作
    timeRecorder[0][2] = 7;

    printf("记时功能开始.\n");
    while (1)
    {
        char funcSelect = 0;


        scanf("%c%*c",&funcSelect);
        switch (funcSelect)
        {
        case 'q':
            checkOutRecorder[currentDays][0] = timeRecorder[currentDays][0]; 
            checkOutRecorder[currentDays][1] = timeRecorder[currentDays][1];
            checkOutRecorder[currentDays][2] = timeRecorder[currentDays][2];
            printf("北京世界%u时 %u分 %u秒签退成功.\n",checkOutRecorder[currentDays][2], checkOutRecorder[currentDays][1], checkOutRecorder[currentDays][0]); 
            break;
        case 'r':
            printf("签到\n");
            /*
            *********************************
                @bug:
                    关闭非阻塞IO的时候没有记时所以打卡函数执行的时候没有记时
            ********************************* 
            */


            //关闭非阻塞io
            flags &= ~O_NONBLOCK;
            if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
            {
                perror("fcntl");
                return -1;
            }        
                       
            switch (sign_in())
            {
            case 0:
                flagCheckIn = 1;
                //打卡时间记录
                checkInRecorder[currentDays][0] = timeRecorder[currentDays][0]; 
                checkInRecorder[currentDays][1] = timeRecorder[currentDays][1];
                checkInRecorder[currentDays][2] = timeRecorder[currentDays][2];
                printf("北京世界%u时 %u分 %u秒签到成功.\n",checkInRecorder[currentDays][2], checkInRecorder[currentDays][1], checkInRecorder[currentDays][0]);
                break;
            case -1:
                printf("签到失败,返回功能选择列表.\n");
                break;
            default:
                printf("打卡机异常请稍后再试.\n");
                break;
            }

            //开启非阻塞io
            if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
            {
                perror("fcntl");
                return -1;
            }
            flags |= O_NONBLOCK;
            if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
            {
                perror("fcntl");
                return -1;
            }

            break;
        default:
            break;
        }

        usleep(1000);
        //打卡机的1ms相当于600ms
        msTmpRecord += millionsecond;
        //ms -> s
        if (msTmpRecord >= 1000)
        {
            timeRecorder[currentDays][0] += 1;
            msTmpRecord -= 1000;
        }
        //s -> m
        if (timeRecorder[currentDays][0] >= 60)
        {
            timeRecorder[currentDays][1] += 1;
            timeRecorder[currentDays][0] -= 60;
        }
        //m -> h
        if (timeRecorder[currentDays][1] >= 60)
        {
            timeRecorder[currentDays][2] += 1;
            timeRecorder[currentDays][1] -= 60;
        }
        //一天的时间超过了
        if (timeRecorder[currentDays][2] >= 24)
        {
            currentDays += 1;
            flagCheckIn = 0;
        }

        if (currentDays >= 5)
        {
            break;
        }
        
        

        //保留测试接口用于退出记时
        if (funcSelect == 'S')
        {
            break;
        }

        
    }
    printf("记时功能结束.\n");
    printf("现在是星期%u %hhu时 %hhu分 %hhu秒.\n", (currentDays + 1), timeRecorder[currentDays][2], timeRecorder[currentDays][1], timeRecorder[currentDays][0]);

}

/*
*********************************
    @函数功能:
        签到打卡
    @参数:
        NULL       
    @返回值:

********************************* 
*/
int sign_in()
{
    int flag = 0;
    uint8_t input_count = 0;
    uint32_t employeeID = 0;
    uint32_t verifyCode = 0, verifyCodeIn = 0;
    printf("请输入您的工号:");
    scanf("%u%*c",&employeeID);
    putchar('\n');
    verifyCode = generator_verifyCode(employeeID);
    printf("请输入验证码:");
    scanf("%u%*c",&verifyCodeIn);
    putchar('\n');

    while (verifyCodeIn != verifyCode)
    {
        input_count++;
        printf("验证码错误,请重新输入.您还剩%d次输入机会.",(3-input_count));
        printf("请输入验证码:");
        scanf("%u%*c",&verifyCodeIn);
        putchar('\n');
        if (input_count >= 3)
        {
            flag = -1;
            break;
        }
        
    }
        
    
    return flag;
}
/*
*********************************~
    @函数功能:
        生成验证码
    @参数:
        id -> 工号
    @返回值:

********************************* 
*/
uint32_t generator_verifyCode(uint32_t id){
    uint32_t oid = id % 100000;
    uint32_t tid = oid ;
    uint32_t checkcode = (id / 100000) * 100000; 
    uint32_t nid = 0;

    //取出后面的数
    while ( tid != 0 )
    {
        nid = (nid * 10) + (tid % 10);
        tid = tid / 10;
    }
    //计算校验码
    checkcode = checkcode + (oid + nid);

    return checkcode; 
}

1.3-打卡机签退功能实现

打卡机的签退的时候需要判断是否签到,所以需要设置标志变量在打卡后和每天结束的时候进行打卡标志清除。

case 'q':
    if (flagCheckIn)
    {
    flagCheckOut = 1;
    checkOutRecorder[currentDays][0] = timeRecorder[currentDays][0]; 
    checkOutRecorder[currentDays][1] = timeRecorder[currentDays][1];
    checkOutRecorder[currentDays][2] = timeRecorder[currentDays][2];
    printf("北京世界%u时 %u分 %u秒签退成功.\n",checkOutRecorder[currentDays][2], checkOutRecorder[currentDays][1], checkOutRecorder[currentDays][0]); 

    //工作时长
    }
    else
    {
    printf("您还没有签到,请您先进行签到.");
    }
	break;

2.打卡机功能完善

实现打卡机的早退、迟到缺卡和周总结函数

2.1-打卡机记录工作时长、早退、缺席

在一天结束的时候计算工作时长,再通过工作时长计算是否早退以及早退时间;

根据是否有打卡记录和签退记录判断是否缺席。

弹性打卡的标志也在此

//一天的时间超过了
if (timeRecorder[currentDays][2] >= 24)
{
    flexibility = 0;
    if (flagCheckIn && flagCheckOut)
    {
        //工作时长计算
        uint8_t tmpMinute = 0;
        if (checkOutRecorder[currentDays][1] >= checkInRecorder[currentDays][1])
        {
            tmpMinute = checkOutRecorder[currentDays][1] - checkInRecorder[currentDays][1];
        }
        else
        {
            checkOutRecorder[currentDays][2]--;
            checkOutRecorder[currentDays][1] += 60;
            tmpMinute = checkOutRecorder[currentDays][1] - checkInRecorder[currentDays][1];
        }
        worktime[currentDays] = time_convert((checkOutRecorder[currentDays][2] - checkInRecorder[currentDays][2]), tmpMinute);
        printf("您今天工作了%u小时%u分钟.\n", (worktime[currentDays] / 60), (worktime[currentDays] % 60));
        if ((worktime[currentDays] >= FixedWorkingtime) && ((worktime[currentDays] - FixedWorkingtime) / 60) >=3)
        {
            flexibility = 2;
        }
        else if (worktime[currentDays] < FixedWorkingtime)
        {
            leaveEarly[currentDays] = (FixedWorkingtime - worktime[currentDays]);
            // printf("worktime = %u\n", worktime[currentDays]);
            // printf("leaveEarly = %u\n", leaveEarly[currentDays]);
            printf("您今天早退了%u小时 %u分钟.\n", leaveEarly[currentDays] / 60, \
                   leaveEarly[currentDays] % 60);
        }


    }
    else
    {
        //缺席
        absence[currentDays] = 1;
    }


    currentDays += 1;
    flagCheckIn = 0;
    flagCheckOut = 0;
    employeeID = 0;


}

2-2.打卡机迟到计算

根据上一段的弹性打卡标志再打卡成功的时候判断是否迟到

switch (sign_in(employeeID))
{
    case 0:
        flagCheckIn = 1;
        //打卡时间记录
        checkInRecorder[currentDays][0] = timeRecorder[currentDays][0]; 
        checkInRecorder[currentDays][1] = timeRecorder[currentDays][1];
        checkInRecorder[currentDays][2] = timeRecorder[currentDays][2];
        printf("北京世界%u时 %u分 %u秒签到成功.\n",checkInRecorder[currentDays][2], checkInRecorder[currentDays][1], checkInRecorder[currentDays][0]);

        uint32_t flagLate = ((checkInRecorder[currentDays][2] - flexibility) > CheckInTime) ? 1 : 0;   
        // printf("123=%u   flagLate=%u\n", checkInRecorder[currentDays][2] - flexibility, flagLate);         
        //迟到
        if (flagLate)
        {
            arriveLate[currentDays] = 1;  
            // printf("迟到!!!\n");
        }

        break;
    case -1:
        printf("签到失败,返回功能选择列表.\n");
        break;
    default:
        printf("打卡机异常请稍后再试.\n");
        break;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页