BUILD models and DEFINE API for room applications
$ npm install
$ npm start
make sure that the mongodb running and listen the port 27017 before run this app.
活动室申请系统是一个广泛存在的系统,如学生想要组织活动,需要学校的场地,那么他们会去向有权限的老师申请场地,然后在规定时间去规定的场地活动,如果这个场地是一个有锁的房间,那么可能还需要找门卫要钥匙开门,通常还需要出示申请通过的证明……
这是一件逻辑简单的事务,随着办公互联网化、无纸化办公的需要,活动室申请系统应运而生。
我们可以抽象地描述刚才的事务:
首先有若干场地、空间、房间(Room)需要挂载在系统上;
然后申请人(Applicant)提出申请(Application);
然后管理员(Administrator)审批申请;
然后门卫(GateKeeper)核对申请并提供进入场地的方法;
由于这个东西与线下资源结合紧密,自动提供进入场地的设施需(自动门卫)要嵌入式设备的支持,目前暂不考虑。
可以看到,场地是最根本的东西,没有场地,那后面所说的一切都不重要了,但场地绝不是这个系统第一个诞生的对象。
这个后端的系统已经用NodeJS实现,并以MIT协议开源:function-x/room-application 前后端耦合度非常低,可以说后端仅仅是提供了一套API。 前后端通过AJAX,JSON格式传递信息。
这个系统内具有三类对象:用户(User:包括申请人、管理员、门卫)、场地(Room)、申请表(Application)。
用户有一个属性:用户组——来区别申请人、管理员或者是门卫。
我们假设管理员与门卫是坚决站在场地所有人一方的,管理员具有系统基本上所有的权力(但他们可能不能修改系统代码),门卫则仅仅有管理线下场地的权力。
另外,隐藏有一个可以操作系统后台代码文件的人(通常是运维人员),这不属于系统内部,不作考虑(尽管它可能对系统具有无上的权力)。
但开放注册的用户群,不能排除存在恶意用户的情况,必须严加提防。
一个门卫可以管多个场地,但一个场地最多只能归一个门卫管。
因此场地应该具有一个门卫的引用属性,但可以为空,因为有一些场地是不需要门卫的。
场地有不重复的名称、描述、门卫三个属性,我们假设一个场地的合法门卫只能是一个人,避免责任分摊;他可以授权给其他人(线下),但一旦发生门禁意外,由门卫负责财产损失。
申请表是由用户提出的,是申请人为了场地的使用权限而提交的表单。
我们假设,申请至少要自拟一个标题,并作详细的使用场地的描述。
除此之外,要提供“具体申请的是哪个场地”、“是谁申请的”、“在哪个时间段使用”这样的信息。
管理员可以审核这个申请,给出“通过”或者“拒绝”的判断,或者来回切换,至于如何发送通知,可以借助Email,或者发送短信等(通知的部分我没有实现)。
可以通过算法判断当时场地是否被使用:
在用户新增申请时,判断这个区间内是否已有已经通过审核的申请:如果有,申请提交直接失败;如果没有,加入数据库。 在管理员审核申请时,当审批“通过”时,将其他所有具有公共区间的申请全部自动审批为“拒绝”。
对于一个审核通过的申请,会自动生成一个口令,只有申请人与对应的 门卫可以看到,他们可以在线下直接通过对口令的方式获取信任。
这个口令有两个设计原则:
- 对口令要方便,不能生成一个长的Hash码,让人直接比对,这不现实。
- 口令不能仅根据申请信息生成,要加入足够的随机因子,使得口令不能被别人直接算出来。
在刚刚部署的时候,一个管理员都没有,因此需要一个API来创建第一个管理员,以便后续操作。
{
url: '/init',
type: 'post',
data: {
username: String, // 设定管理员账户
password: String // 设定管理员密码
}
}
这个API会检查当前的管理员数量,如果是0才会添加管理员,否则将返回错误(代码为1)。
注册用户的API
{
url: '/signup',
type: 'post',
data: {
username: String, // 设定账户
password: String, // 设定密码
}
}
登录用户的API
{
url: '/signin',
type: 'post',
data: {
username: String, // 账户
password: String, // 密码
}
}
这个API会返回当前用户的状态(会直接将当前用户的可公开数据回传)
{
url: '/status',
type: 'get',
}
用户登出的API
{
url: '/signout',
type: 'get'
}
会列出所有(也可以自定义查询)用户的可公开信息(即隐藏密码),仅有管理员身份的用户可以调用这个API。
{
url: '/user',
type: 'get',
data: any // 自定义查询(筛选条件)
}
会列出某用户的可公开信息,所有人可以调用。
{
url: '/user/:_id', // _id 是用户的唯一标识ID
type: 'get'
}
更改用户的账户、密码等,仅限用户本人与管理员 更改用户的权限组,仅限管理员。
{
url: '/user/:_id', // _id 是用户的唯一标识ID
type: 'put',
data: Any // 自由的查询结构
}
仅管理员可以调用。
{
url: '/user/:_id', // _id 是用户的唯一标识ID
type: 'delete'
}
准确的说是登记场地,仅管理员可用。
{
url: '/room',
type: 'post',
data: {
name: String, // 场地名称,不能重复
description: String, // 场地描述,通常包含场地的具体位置,设施条件等
gatekeeper: ObjectId // 其门卫的ID,默认为空。
}
}
列出所有场地,所有人可用。
{
url: '/room',
type: 'get'
}
更改某场地的名称或描述或门卫,仅管理员可用。
{
url: '/room/:_id', // _id 为场地的唯一标识ID
type: 'put',
data: Any // 为新建场地的数据格式的子集。
}
仅管理员可用。
{
url: '/room/:_id', // _id 为场地的唯一标识ID
type: 'put',
}
新建一个申请并提交,仅登录的用户可用。
{
url: '/application',
type: 'get',
data: {
title: String, // 申请的标题,通常是指活动的主题
description: String, // 活动的具体描述
room: ObjectId, // 场地的唯一标识ID,必选
start: Number, // 开始时间,数字格式
end: Number, // 结束时间,数字格式
}
}
所有人均可以自定义查询条件,至少要给出要查询的场地ID。 默认会查询从现在开始24小时内的所有已通过的申请。
{
url: '/application',
type: 'get',
data: {
room: ObjectId, // 场地的唯一标识ID,必选
start: Number, // 开始时间,数字格式
end: Number, // 结束时间,数字格式
status: String // 状态:仅接受'accepted', 'failed', 'pending' 三种值
}
}
按照申请的ID来查看申请,如果申请通过并且查看人是申请者或者是对应的门卫,则会显示口令。
{
url: '/application/:_id', // _id 为申请的唯一标识ID
type: 'get',
}
仅管理员可用,可以更改申请的状态(通过或者不通过)
{
url: '/application/:_id', // _id 为申请的唯一标识ID
type: 'put',
data: {
status: String // 只接受'accepted'与'failed'
}
}
仅管理员可用。
{
url: '/application/:_id', // _id 为申请的唯一标识ID
type: 'delete'
}