设计说明:
软件开发的模式是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
接口写的不错
接口都设计的很烂额…修改好多次后接口才勉强能用
做其他端的同学也都是新手,经验不足,很多地方的体验感都不行
加上服务器性能不足,,最后做出的东西体验感极差