设计说明:
软件开发的模式是RESTful API+各客户端(学生端,教师端,管理端)的形式,用户认证使用JWT。
文档规定了后端需要提供的接口、前后端交互的数据格式、数据库的结构以及认证方式。技术栈的选择,客户端的设计等,都有很大的自由度。
系统架构简图:
主要工作:
管理端 (技术栈:Jquery,Vue或React)
学生端 (技术栈:微信小程序)
教师端 (技术栈:react-native跨平台app 或 android,ios原生)
人脸识别模块 (图像处理使用opencv,人脸识别使用face_recognition,提供docker image)
后端接口 (Laravel)
美工 (图标设计,形象展示页设计)
运维 (系统架构设计,上线环境搭建)
ps:每人独立完成一个端,有余力可以选择第二个端,最终软件使用较优秀的端进行组合。
用户认证:
为保护接口不被攻击和恶意利用,服务器会对每次请求进行验证。
首先,管理端和教师端需要将用户名和密码发送给服务器。
登录接口 POST https://face.keinx.com/login
1 2 3 4 5 6 7 8 |
{ "role":"admin", //用户身份,固定为admin(管理端,教师端) "username":"root", "pwd":"12345" } //返回值 {"user_id":1} //注销 DELETE https://face.keinx.com/login 请求处理成功返回204状态码 |
学生端需要将登录凭证(code)发送给服务器。
登录接口 POST https://face.keinx.com/login
1 2 3 4 5 6 7 |
{ "role":"student", //用户身份,固定为student(微信小程序) "code":"w93uer0urhf023g7rhe" } //无注销 |
服务器收到请求后判断请求者的身份。如果是admin用户,服务器核对用户名和密码,核对成功后将用户id作为jwt的载荷role_id的值,然后将载荷与头部分别进行Base64编码拼接后签名,形成一个JWT。
如果是student用户,服务器将携带AppID、AppSecret和code请求微信服务器,如请求成功微信服务器返回
1 2 3 4 5 |
{ "openid":"OPENID", //用户唯一标识 "session_key":"SESSIONKEY", //会话密钥(用不到) } |
服务器拿到openid后,根据openid新建一个用户,然后将openid的值作为jwt的载荷role_id的值,形成一个JWT。
JWT未编码前的结构为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
//头部(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作为键名,键值为错误信息。
1 2 3 4 |
{ error:"Invalid API key" } |
提供一下接口:
学生管理
新建图片 POST https://face.keinx.com/image
1 2 3 4 5 6 7 8 9 10 |
{ "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//修改学生信息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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
//服务端解密请求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
1 2 |
//请求成功返回204状态码 |
查询某班全部学生信息 GET https://face.keinx.com/student/class/ID
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
//请求成功返回 [ { "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
//时间线向前推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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//服务器处理成功返回 [ { "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
1 2 3 4 5 6 7 8 9 10 |
{ "type":2, //0学生正脸照片,1微信头像,2课堂拍摄照片 "img":"img_data" } //请求成功返回 { "path":"图片url" } |
照片中人脸(多张)身份识别 POST https://face.keinx.com/face
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "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
1 2 3 4 5 6 7 8 9 10 11 |
[ { "student_id":1, //学生id "course_id":1, //课程id "status":1 //0缺勤,1出勤,2请假 }, ... ] |
修改考勤记录 PATCH https://face.keinx.com/arrive_record/ID
1 2 3 4 5 6 |
{ "is_arrive":2, //是否出勤,0缺勤,1表示出勤,2请假 } //修改成功返回200状态码 |
查询考勤记录(获取某学生某年某月的所有记录) GET https://face.keinx.com/arrive_record/student/ID/year/2017/month/2
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
[ { "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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
[ { "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
1 2 3 4 5 6 7 8 9 10 |
{ "college_name":"计算机科学系", } //处理成功返回 { "college_id":1, "college_name":"计算机科学系", } |
删除院系(增加逻辑上的外键约束,如果子表有数据,则不允许直接删除父表的值)DELETE https://face.keinx.com/college/ID
1 2 |
//请求成功返回204状态码 |
修改院系 PATCH https://face.keinx.com/college/ID
1 2 3 4 5 6 |
{ "class_name":"2017计算机科学与技术(网络安全)233" } //请求成功返回201状态码 |
查询院系 GET https://face.keinx.com/college/ID
1 2 3 4 5 |
{ "college_name":"计算机科学系" } |
添加班级 POST https://face.keinx.com/class/college/ID
1 2 3 4 5 6 7 8 9 10 |
{ "grade":2017, "class_name":"计算机科学与技术(网络安全)" } //服务器处理成功返回 { "class_id":1, } |
删除班级 DELETE https://face.keinx.com/class/ID
1 2 |
//请求成功返回204状态码 |
修改班级 PATCH https://face.keinx.com/class/ID
1 2 3 4 5 6 |
//以下字段都为可选 { "grede":2017, "class_name":"计算机科学与技术(网络安全)233" } |
查询班级 GET https://face.keinx.com/class/ID
1 2 3 4 5 6 7 8 |
{ "class_id":1, "class_name":"计算机科学与技术(网络安全)", "college_id":1, "college_name":"计算机科学系", "grade":2017 } |
获取所有院系 GET https://face.keinx.com/college
1 2 |
[1,2,3,4] |
获取所有年级 GET https://face.keinx.com/grade
1 2 |
[2015,2016,2017,2018] |
获取班级1(根据年份获取院系和班级的关联列表) GET https://face.keinx.com/class/grade/2017
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
[ { "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
1 2 3 4 5 6 7 |
[ {"id":3,"name":"学前教育"}, {"id":4,"name":"级心理学"}, ...... ] |
账号管理
账号添加 POST https://face.keinx.com/admin
1 2 3 4 5 6 7 8 9 10 |
//超级管理员有账号增删改查的权限 { "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
1 2 |
//请求成功返回204状态码 |
账号修改 PATCH https://face.keinx.com/admin/ID
1 2 3 4 5 6 7 8 9 10 |
//以下都为可选 { "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
1 2 3 4 5 6 7 8 9 10 11 |
//字段没有值返回 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
1 2 3 |
//返回用户id的数组 [1,2,3,4,5] |
课程管理
添加课程 POST https://face.keinx.com/course
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "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
1 2 |
//请求成功返回204状态码 |
修改课程 PATCH https://face.keinx.com/course/ID
1 2 3 4 5 6 7 8 9 10 11 |
{ "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
接口写的不错
接口都设计的很烂额…修改好多次后接口才勉强能用
做其他端的同学也都是新手,经验不足,很多地方的体验感都不行
加上服务器性能不足,,最后做出的东西体验感极差