编程只是起点
我们的终点是星辰大海

基于人脸识别的课堂考勤系统1.0

设计说明:

软件开发的模式是RESTful API+各客户端(学生端,教师端,管理端)的形式,用户认证使用JWT。
文档规定了后端需要提供的接口、前后端交互的数据格式、数据库的结构以及认证方式。技术栈的选择,客户端的设计等,都有很大的自由度。

系统架构简图:

 

主要工作:

管理端 (技术栈:Jquery,Vue或React)
学生端 (技术栈:微信小程序)
教师端 (技术栈:react-native跨平台app 或 android,ios原生)
人脸识别模块 (图像处理使用opencv,人脸识别使用face_recognition,提供docker image)
后端接口 (Laravel)
美工 (图标设计,形象展示页设计)
运维 (系统架构设计,上线环境搭建)
ps:每人独立完成一个端,有余力可以选择第二个端,最终软件使用较优秀的端进行组合。

用户认证:

为保护接口不被攻击和恶意利用,服务器会对每次请求进行验证。

首先,管理端和教师端需要将用户名和密码发送给服务器。
登录接口 POST https://face.keinx.com/login

{
    "role":"admin",    //用户身份,固定为admin(管理端,教师端)
    "username":"root",
    "pwd":"12345"
}
//返回值 {"user_id":1}
//注销 DELETE https://face.keinx.com/login 请求处理成功返回204状态码

学生端需要将登录凭证(code)发送给服务器。
登录接口 POST https://face.keinx.com/login

{
    "role":"student", //用户身份,固定为student(微信小程序)
    "code":"w93uer0urhf023g7rhe"
}

//无注销

服务器收到请求后判断请求者的身份。如果是admin用户,服务器核对用户名和密码,核对成功后将用户id作为jwt的载荷role_id的值,然后将载荷与头部分别进行Base64编码拼接后签名,形成一个JWT。

如果是student用户,服务器将携带AppID、AppSecret和code请求微信服务器,如请求成功微信服务器返回

{
    "openid":"OPENID",          //用户唯一标识
    "session_key":"SESSIONKEY", //会话密钥(用不到)
}

服务器拿到openid后,根据openid新建一个用户,然后将openid的值作为jwt的载荷role_id的值,形成一个JWT。

JWT未编码前的结构为:

//头部(Header)
{
    "typ": "JWT",
    "alg": "HS256"
}
//载荷(Payload)
{
    "iat": 1441593502,    //签发时间
    "exp": 1441594722,    //过期时间
    "role":0              //0学生1教师2普通管理员3超级管理员
    "role_id":"1"         //如果用户身份是教师、管理员或超级管理员,此字段值为用户id,如果是学生,此字段值为openid 
}
//加密秘钥暂为 xxxx

接下来服务器将jwt字符串作为该登录请求响应Cookie返回给用户,Cookie的键为jwt,值为xx.xx.xx形式的jwt字符串。
Cookie失效或者被删除前,以后的每次请求服务器都会接收到客户端携带的jwt信息,服务端会验证jwt的合法性(jwt字符串是否被篡改,jwt是否已过期),验证失败服务端返回401状态码,客户端可引导用户进入认证的界面。

验证成功后则检测用户是否有操作权限,角色及角色权限如下:
学生: class[GET]、image[POST]、student[GET/PUT]

以student[GET/PUT]为例,student为角色被允许操作的资源,[GET/PUT]中为被允许的方法

教师: face[POST]、class[GET]、image[POST]、login[POST/DELETE]、student[GET/POST/PATCH]、course[GET]、arrive_record[GET/POST/PATCH]
普通管理员: class[GET/POST/DELETE/PATCH]、college[GET/POST/DELETE/PATCH]、grade[GET]login[POST/DELETE]student[GET/POST/PATCH/DELETE]、course[GET/POST/PATCH/DELETE]、arrive_record[GET/POST/PATCH]
超级管理员: class[GET/POST/DELETE/PATCH]、college[GET/POST/DELETE/PATCH]、grade[GET]、login[POST/DELETE]、student[GET/POST/PATCH/DELETE]、course[GET/POST/PATCH/DELETE]、arrive_record[GET/POST/PATCH]、admin[GET/POST/PATCH/DELETE]
如果没有权限则返回403状态码。如果认证成功,服务器将返回相应资源。

ps:
服务器端Cookie要设置HttpOnly属性来防止Xss,设置Access-Control-Allow-Credentials: true等等允许cookie跨域。
微信小程序未支持Cookie,手机app关闭后cookie会失效,所以学生端和教师端的过程是提取出header头中set-cookie中的jwt,做本地存储,在以后每次请求的header中添加cookie字段并且带上的jwt=xxx.xxx.xxx
管理端是在浏览器上的,原生支持Cookie,只需要ajax请求中设置xhr.withCredentials = true使浏览器携带跨域Cookie

状态码和错误处理:

软件中主要使用以下状态码

200 OK – [GET]:服务器成功返回用户请求的数据。
201 CREATED – [POST/PUT/PATCH]:用户新建或修改数据成功。
202 Accepted – [ * ]:表示一个请求已经进入后台排队(异步任务)
204 NO CONTENT – [DELETE]:用户删除数据成功。
400 INVALID REQUEST – [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作。
401 Unauthorized – [ * ]:表示用户未通过认证(令牌、用户名、密码错误)。
403 Forbidden – [ * ] 表示用户通过认证,但是没有访问该资源的权限。
404 NOT FOUND – [ * ]:用户发出的请求针对的是不存在的记录,服务器没有进行操作。
500 INTERNAL SERVER ERROR – [ * ]:服务器发生错误,用户将无法判断发出的请求是否成功。

如果状态码非2xx,服务端会向客户端返回出错信息。返回的信息中error作为键名,键值为错误信息。

{
    error:"Invalid API key"
}

 

提供一下接口:

学生管理

新建图片 POST https://face.keinx.com/image

{
    "type":0,               //0学生正脸照片,1微信头像,2课堂拍摄照片
    "img":"img_data"
}

//请求成功返回
{
    "path":"图片url"
}

修改学生信息1 PUT https://face.keinx.com/student/ID
修改学生信息2(学生端使用) PUT https://face.keinx.com/student

//修改学生信息2(学生端使用) 解释:小程序登录成功后,服务器端会根据微信用户的唯一标识(openid)创建一个用户,并把openid添加进返回的jwt信息中,以后小程序的每次请求都要携带jwt信息,服务器会解密解码得到openid,从而确定用户身份。
//以下字段必须提供,如果没有值留空,如 "wx_name":""
{
    "card_number":"学生卡卡号",
    "name":"名字",
    "sex":0,
    "class_id":1,
    "photo":"img_path",               //正脸照片url地址
    "qq":"2414192895",
    "phone":"158661528XX",
    "wx_name":"微信昵称",
    "wx_openid":"微信用户唯一标识",
    "wx_photo":"img_path",           //微信头像url地址
}

查询学生信息 GET https://face.keinx.com/student/ID
查询学生信息2(根据小程序openid查询) GET https://face.keinx.com/student

//服务端解密请求Cookie中的jwt,得到openid确定用户身份
{
    "student_id":1,
    "card_number":"学生卡卡号",
    "name":"名字",
    "sex":0,
    "class_id":1,
    "class_name":"计算机应用技术(英谷一班)",
    "college_id":1,
    "college_name":"计算机科学系",
    "grade":2017,
    "photo":"img_path",                          //正脸照片url地址
    "qq":"2414192895",
    "phone":"158661528XX",
    "wx_name":"微信昵称",
    "wx_openid":"微信用户唯一标识",
    "wx_photo":"img_path",                       //微信头像url地址
}

删除学生信息 DELETE https://face.keinx.com/student/ID

//请求成功返回204状态码

查询某班全部学生信息 GET https://face.keinx.com/student/class/ID

//请求成功返回
[
    {
        "student_id":1,
        "card_number":"学生卡卡号",
        "name":"名字",
        "sex":0,
        "qq":"2414192895",
        "phone":"158661528XX",
        "wx_openid":"微信用户唯一标识",
        "wx_name":"微信昵称",
        "photo":"url",           //照片url
        "wx_photo":"url",        //微信头像url
    },
    ......
]

课程查询

查询当前时间点对应课程的信息 GET https://face.keinx.com/course/teacher/ID/now_time

//时间线向前推5分钟,比如10:10上课,10:5点便可以查到次堂课信息,老师可以在上课前点名
//上课时间未在数据库内记录,暂时先写死,下一版本再优化
{
    "id":1,
    "name":"课程名",
    "location":"上课地点",
    "teacher_id":1,
    "year":"2017-2018",     //学年
    "Semester":1,           //学期,1或者2
    "week":1,               //周几上课,0-6 周日到周六
    "part":"12",            //第几节课,0早自习,12上午第一节,34上午第二节,56下午第一节,78下午第二节9晚自习
    "all_sutdent":[
        {"id":1,"name":"吴春晖","card_number":"20170121017"},
        {"id":2,"name":"王雪燕","card_number":"20170121018"},
        ......
    ]
}

查询某堂课的信息 GET https://face.keinx.com/course/ID

{
    "id":1,
    "name":"课程名",
    "location":"上课地点",
    "teacher_id":1,
    "year":"2017-2018",     //学年
    "Semester":1,           //学期,1或者2
    "week":1,               //周几上课,0-6 周日到周六
    "part":"12",            //第几节课,0早自习,12上午第一节,34上午第二节,56下午第一节,78下午第二节9晚自习
    "all_sutdent":[
        {"id":1,"name":"吴春晖","card_number":"20170121017"},
        {"id":2,"name":"王雪燕","card_number":"20170121018"},
        ......
    ]
}

查询某教师课程表 GET https://face.keinx.com/course/teacher/ID

//服务器处理成功返回
[
    {
        "id":1,
        "name":"课程名",
        "location":"上课地点",
        "teacher_id":1,
        "year":"2017-2018", //学年
        "Semester":1,       //学期,1或者2
        "week":1,           //周几上课,0-6 周日到周六
        "part":"12",        //第几节课,0早自习,12上午第一节,34上午第二节,56下午第一节,78下午第二节9晚自习
    },
    ......
]

考勤管理

新建图片 POST https://face.keinx.com/image

{
    "type":2,               //0学生正脸照片,1微信头像,2课堂拍摄照片
    "img":"img_data"
}

//请求成功返回
{
    "path":"图片url"
}

照片中人脸(多张)身份识别 POST https://face.keinx.com/face

{
    "course_id":1,             //课程id
    "student_imgs":"img_path"  //照片url
}

//返回照片中检测到的学生
[
    {
        "student_id":1,
        "area":[x,y,w,h]       //x,y是矩阵左上点的坐标,w,h是矩阵的宽和高
    },
    ...
]

增加考勤记录 POST https://face.keinx.com/arrive_record


[
    {
        "student_id":1,            //学生id
        "course_id":1,             //课程id
        "status":1                 //0缺勤,1出勤,2请假
    },
    ...
]

修改考勤记录 PATCH https://face.keinx.com/arrive_record/ID

{
    "is_arrive":2,                               //是否出勤,0缺勤,1表示出勤,2请假
}

//修改成功返回200状态码

查询考勤记录(获取某学生某年某月的所有记录) GET https://face.keinx.com/arrive_record/student/ID/year/2017/month/2

[   
    {
        "course_id":1, 
        "course_name":"高等数学",  
        "arrive_record_id":1                     //考勤表记录id
        "status":1,                              //0缺勤,1表示出勤,2请假
        "record_time":"2018-06-03 16:04:18",     //考勤时间  
    },
    {
        "course_id":2, 
        "course_name":"JAVA程序设计",  
        "arrive_record_id":2                     //考勤表记录id
        "status":1,                              //0缺勤,1表示出勤,2请假
        "record_time":"2018-06-04 16:04:18",     //考勤时间  
    },
    ......
]

查询考勤记录2(某课堂所有记录) GET https://face.keinx.com/arrive_record/course/ID

[   
    {
        "record_time":"2018-06-03 16:04:18",     //考勤时间 
        "data":[
                {
                    "student_id":1,
                    "student_name":"吴春辉",
                    "card_number":"20170121017" 
                    "arrive_record_id":1                     //考勤表记录id
                    "status":1,                              //0缺勤,1表示出勤,2请假
                },
                {
                    "student_id":2,
                    "student_name":"王雪燕",
                    "card_number":"20170121018" 
                    "arrive_record_id":2                     //考勤表记录id
                    "status":1,                              //0缺勤,1表示出勤,2请假
                },
                ......
        ] 
    },
    ......
]

院系和班级管理

添加院系 POST https://face.keinx.com/college

{
    "college_name":"计算机科学系",
}

//处理成功返回
{
    "college_id":1,
    "college_name":"计算机科学系",
}

删除院系(增加逻辑上的外键约束,如果子表有数据,则不允许直接删除父表的值)DELETE https://face.keinx.com/college/ID

//请求成功返回204状态码

修改院系 PATCH https://face.keinx.com/college/ID

{
    "class_name":"2017计算机科学与技术(网络安全)233"
}

//请求成功返回201状态码

查询院系 GET https://face.keinx.com/college/ID


{
    "college_name":"计算机科学系"
}

添加班级 POST https://face.keinx.com/class/college/ID

{
    "grade":2017,
    "class_name":"计算机科学与技术(网络安全)"
}

//服务器处理成功返回
{
    "class_id":1,
}

删除班级 DELETE https://face.keinx.com/class/ID

//请求成功返回204状态码

修改班级 PATCH https://face.keinx.com/class/ID

//以下字段都为可选
{   
    "grede":2017,
    "class_name":"计算机科学与技术(网络安全)233"
}

查询班级 GET https://face.keinx.com/class/ID

{
    "class_id":1,
    "class_name":"计算机科学与技术(网络安全)",
    "college_id":1,
    "college_name":"计算机科学系",
    "grade":2017
}

获取所有院系 GET https://face.keinx.com/college

[1,2,3,4]

获取所有年级 GET https://face.keinx.com/grade

[2015,2016,2017,2018]

获取班级1(根据年份获取院系和班级的关联列表) GET https://face.keinx.com/class/grade/2017

[
    {
        "college_id":1,              
        "college_name":"计算机科学系",
        "class":[
            {"id":1,"name":"计算机科学与技术(软件开发)"},
            {"id":2,"name":"计算机科学与技术(网络安全)"},
            .....
        ]
    },
    {
        "college_id":2,              
        "college_name":"教育系",
        "class":[
            {"id":3,"name":"学前教育"},
            {"id":4,"name":"级心理学"},
            ......
        ]
    },
    ......
]

获取班级2(根据年份和学院ID获取某学院的班级)GET https://face.keinx.com/class/grade/2017/college/ID

[
    {"id":3,"name":"学前教育"},
    {"id":4,"name":"级心理学"},
    ......
]

账号管理

账号添加 POST https://face.keinx.com/admin

//超级管理员有账号增删改查的权限
{
    "username":"admin2",                  //小于30个字符
    "pwd":"admin2",                       //大于6位
    "phone":"15766152852",                //可选字段
    "email":"2414192895#qq.com",          //可选字段
    "role":1,                             //0学生1教师2普通管理员3超级管理员(0可以是班委,为下一版本预留)
    "remark":"这是教务处xx主任"            //可选字段
}

账号删除 DELETE https://face.keinx.com/admin/ID

//请求成功返回204状态码

账号修改 PATCH https://face.keinx.com/admin/ID

//以下都为可选
{
    "username":"admin2",
    "pwd":"admin2",
    "phone":"15766152852",
    "email":"2414192895#qq.com",
    "role":1,                             //0学生1教师2普通管理员3超级管理员
    "remark":"这是教务处xx主任"
}

账号查询 GET https://face.keinx.com/admin/ID

//字段没有值返回 mull
{
    "id":1,
    "username":"admin2",
    "phone":"15766152852",
    "email":"2414192895#qq.com",
    "role":1,                             //0学生1教师2普通管理员3超级管理员
    "status":1,
    "remark":"这是教务处xx主任"
}

账号查询2(根据角色查询) GET https://face.keinx.com/admin/role/1

//返回用户id的数组
[1,2,3,4,5]

课程管理

添加课程 POST https://face.keinx.com/course


{
    "name":"课程名",
    "location":"上课地点",
    "teacher_id":1,     //教师id
    "year":"2017-2018", //学年
    "semester":2,       //学期,1或者2
    "week":1,           //周几上课,0-6 周日到周六
    "part":"12",        //第几节课,0早自习,12上午第一节,34上午第二节,56下午第一节,78下午第二节9晚自习
    "all_student":[1,2,3,4]
}
//处理成功返回
{
    " course_id":1          //添加成功课程表的id 
}

删除课程 DELETE https://face.keinx.com/course/ID

//请求成功返回204状态码

修改课程 PATCH https://face.keinx.com/course/ID

{
    "name":"课程名",
    "location":"上课地点",
    "teacher_id":1,           //教师id
    "year":"2017-2018",       //学年
    "semester":2,             //学期,1或者2
    "week":1,                 //周几上课,0-6 周日到周六
    "part":"12",              //第几节课,0早自习,12上午第一节,34上午第二节,56下午第一节,78下午第二节9晚自习
    "all_student":[1,2,3]     //学生id
}

程序版本:

Php版本7.1.7 laravel版本5.5
python版本3.6,flask版本1.0.2
mysql 5.7
vue 版本2.9.5
react-native版本 0.56

本文采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可 »钟声博客 » 基于人脸识别的课堂考勤系统1.0

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    接口写的不错

    张煊 6年前 (2018-09-05)回复
    • 接口都设计的很烂额…修改好多次后接口才勉强能用
      做其他端的同学也都是新手,经验不足,很多地方的体验感都不行
      加上服务器性能不足,,最后做出的东西体验感极差

      钟声 6年前 (2018-09-05)

闲鱼助手友情链接