数据库部分
数据库采用了mongo,使用mongoose来进行操作数据库,mongoose相关知识看下面的文章自行学习:
数据库连接配置
var mongoose = require('mongoose');
var dbUrl = 'mongodb://127.0.0.1:27017/examSystem';
var db = mongoose.connect(dbUrl, { useNewUrlParser: true });
db.connection.on('error', function(error) {
console.log('数据库链接失败:' + error);
});
db.connection.on('connected', function() {
console.log('数据库链接成功!');
});
db.connection.on('disconnected', function() {
console.log('Mongoose connection disconnected');
});
module.exports = db;
schema是mongoose里会用到的一种数据模式,可以理解为表结构的定义,特点为
- 每个schema会映射到mongodb中的一个collection,它不具备操作数据库的能
- 对每个定义的生成一个Good的model并导出,model是由schema生成的模型,可以对数据库的操作
本系统的schema代码如下:
//papers.js
const mongoose = require('mongoose');
let Schema = mongoose.Schema;
let paperSchema = new Schema({
name: String, //试卷名
totalPoints: Number, //试卷总分
time: Number, //考试总时长
startTime: Date, //考试开始时间
examgrade: Number, //考试年级
examclass: Number, //考试班级
status: Number, //状态,0表示没开考,1表示开考了但是没阅卷,2表示已阅卷
_teacher: {
type: Schema.Types.ObjectId,
ref: 'Teacher'
}, // 出卷老师
_questions: [{
type: Schema.Types.ObjectId,
ref: 'Question'
}]
})
module.exports = mongoose.model('Paper', paperSchema);
const mongoose = require('mongoose');
let Schema = mongoose.Schema;
let questionSchema = new Schema({
_teacher: {
type: Schema.Types.ObjectId,
ref: 'Teacher'
}, //出题老师
_papers: [{
type: Schema.Types.ObjectId,
ref: 'Paper'
}], //所属试卷
content: String, //内容
type: {
type: String,
enum: [ //类型
'single', //单选
'multi', //多选
'apfill', //填空题
'Q&A', //简答
'judgement' //判断
]
},
score: Number, //分数
answer: String, //答案
selection: Array, //选项
useState: Number, //使用状态,0表示可以使用并没有开考的试卷,1表示题目对应有试卷开考了,2表示题目失效了,失效的题目就是题目存在所属试卷,但是老师又把它删了,目前看来状态0和1好像没用了,因为只要题目被试卷引用了,题目就设置为失效,但是因为相关代码都写了,就不删了,怕后面又要用到这俩状态。
});
module.exports = mongoose.model('Question', questionSchema);
const mongoose = require('mongoose');
let Schema = mongoose.Schema;
let studentSchema = new Schema({
userId: Number, //学号
userName: String, //姓名
password: String, //密码
grade: Number, //年级
class: Number, //班级
exams: [{ //参加的考试
_paper: {
type: Schema.Types.ObjectId,
ref: 'Paper'
}, //试卷
date: Number, //考试总时长
examStatus: Number, //0表示没开考,为1表示开考了没阅卷,为2表示阅卷了,为3表示试卷没有主观题且暂存答案,为4表示试卷有主观题且暂存答案
score: Number, //考试分数
startTime: Date,
answers: [{
_question: {
type: Schema.Types.ObjectId,
ref: 'Question'
},
answer: String
}]
}]
});
module.exports = mongoose.model('Student', studentSchema);
const mongoose = require('mongoose');
let Schema = mongoose.Schema;
let teacherSchema = new Schema({
userId: Number, // 教师工号
userName: String, // 姓名
password: String, // 密码
_papers: [{ type: Schema.Types.ObjectId, ref: 'Paper' }], //试卷
_questions: [{
type: Schema.Types.ObjectId,
ref: 'Question'
}] //问题
});
module.exports = mongoose.model('Teacher', teacherSchema);
主要涉及到的mongo操作
新建记录(以教师注册为例)
let user = req.body.user;//前端发送过来的用户数据,格式同数据库数据格式
Teacher.create(user, (err1, doc1) => {
if (err1) {
res.json({
status: '1',
msg: err1.message
})
} else {
if (doc1) {
res.json({
status: '0',
msg: 'success'
})
} else {
res.json({
status: '3',
msg: '注册失败'
})
}
}
})
下面的代码只列出关键语句,那些if else之类的和上面相同
搜索记录
Teacher.findOne({
userId: userId
}, (err, doc) => {})//返回含有符合条件的对象
//--------------------------------------------
Teacher.find({
userId: userId
}, (err, doc) => {})//返回含有符合条件的数组
//--------------------------------------------
Teacher.find({
name: new RegExp(name, 'i')
}, (err, doc) => {})//模糊搜索,不分大小写
//--------------------------------------------
let searchParams,i=0;
if(i==1){
searchParams={name:1,id:1}
}else{
searchParams={name:0,id:0}
}
Teacher.find(searchParams, (err, doc) => {})//定义查询参数查询
//--------------------------------------------
let idArr=[1,2,3];
Teacher.find({
"_id": { $in: idArr},
}, (err, doc) => {})//搜索id为1或id为2或id为3的记录,$in的介绍见下文技术要点
//-------------------------------------------
Teacher.find({
"_id": { $ne:2 },
}, (err, doc) => {})//搜索id不为2的记录,$ne的介绍见下文技术要点
分页
//----------
limit 是 pageSize,skip 是第几页*pageSize,例如:
db.goods.find().skip(2).limit(4):跳过前2条数据来到第三条,查询共4条数据,即查第3,4,5,6条数据分页
db.goods.find().skip(0).limit(4):查询第1页的内容,第1页一共4条数据
db.goods.find().skip(4).limit(4):查询第2页的内容,第2页一共4条数据
db.goods.find().skip(8).limit(4):查询第3页的内容,第3页一共4条数据
所以在毕设中,分页写为:
let pageSize = parseInt(req.param("pageSize")); //每页条数
let pageNumber = parseInt(req.param("pageNumber")); //第几页
let skip = (pageNumber - 1) * pageSize; // 跳过几条数据
Question.find({
"content": reg,
}).skip(skip).limit(pageSize)
.exec((err2, doc2) => {}
对文档集合中的数组元素进行分页使用$slice,$slice介绍如下:
所以在毕设中,对学生的考试记录进行分页,代码为(populate介绍见下文):
let pageSize = parseInt(req.param("pageSize")); //每页条数
let pageNumber = parseInt(req.param("pageNumber")); //第几页
let skip = (pageNumber - 1) * pageSize; // 跳过几条
Student.findOne({
"userId": userId,
}, {
"exams": {
$slice: [skip, pageSize]
}
}).populate({
path: 'exams._paper',
select: 'name status _questions',
match: {
name: reg
}
}).exec((err1, doc1) => {})
参考文档:MongoDB学习 (六):查询
连接查询
技术要点:
查询语句:Query.populate(path, [select], [model], [match], [options])
path:指定要填充的关联字段,可以理解为在表中定义的外键名,如果外键定义在数组内(比如学生表),那么使用的时候就直接写“数组名.属性名”
select:指定填充 document 中的哪些字段,比如连接查询想查到对应的name,那么就select:name
model:指定关联字段的 model,如果没有指定就会使用Schema的ref,这个查询一般不会写
match:指定附加的查询条件,相当于对查询后的结果做一个筛选
options:指定附加的其他查询选项,如排序以及条数限制等等
试卷表用_questions为外键,如果想查看试卷中的信息以及对应题目的全部,那么连接查询代码写为:
let paperId="5ce80e8215bd0b06c82573e9";
Paper.findOne({
'_id': paperId
}).populate({
path: '_questions'
}).exec((err1, doc1) => {}
查询结果见下图1
//----------------------------------
学生表的schema结构为:
let studentSchema = new Schema({
...
exams: [{ //参加的考试
_paper: {
type: Schema.Types.ObjectId,
ref: 'Paper'
}, //试卷
...
}]
});
所以要查看学生的试卷以及试卷的name status _questions三个值,查询语句为:
Student.findOne({
"userId": userId,
}, {
"exams": {
$slice: [skip, pageSize]
}
}).populate({
path: 'exams._paper',
select: 'name status _questions',
}).exec((err1, doc1) => {})
查询结果如下右图:
删除操作
数组删除见下方数组修改的删除部分
非数组删除使用remove方法,remove方法介绍如下:
db.collection.remove(
<query>,//查询语句
{
justOne: <boolean>,(可选)如果设为 true 或 1,则只删除一个文档,如果不设置该参数,或使用默认值 false,则删除所有匹配条件的文档。
}
)
例如删除试卷:
Paper.remove({
"_id": {
$in: paperId
}
}, function(err1, doc1) {})
修改操作
修改操作之非数组修改
update方法介绍:
db.collection.update(
<query>,//查询语句
<update>,//update对象和一些更新操作符
{
upsert: <boolean>,//如果不存在update的记录,是否插入objNew,true为插入,默认是false,不插入
multi: <boolean>,//只更新找到的第一条记录,如果这个参数为true,就把按条件查出来多条记录全部更新,默认是false
}
)
修改方法一:
先定义要修改的值,然后使用update方法修改,例如修改教师信息:
let user = req.body.user;//前端发来的数据
Teacher.update({
"userId": user.userId
}, user, (err2, doc2) => {})
修改方法二:
直接使用$set和update修改,例如修改试卷考试状态:
let paperId = req.body.paperId;//前端发来的数据
Paper.update({
'_id': paperId
}, {
'$set': {
'status': 2
}
}, (err1, doc1) => {})
修改方法三:
首先使用findOne找到对象,然后对对象结果进行操作,然后使用.save()方法保存对象,例如教师表添加新增的题目的_id,就是先找到教师信息,然后新增题目,然后把新增题目的_id加入教师信息中,然后使用.save()方法保存:
Teacher.findOne({
"userId": teacherId,
}, (err2, doc2) => {
...
if (doc2) {
questionData.forEach(item => {
item._teacher = doc2._id;
})
Question.create(questionData, function(err, doc) { //创造题目
...
if (doc) {
doc.forEach(item2 => {
doc2._questions.push(item2._id);
})
doc2.save();//使用save方便保存修改
.....
修改操作之数组修改
技术要点:
$push:向文档数组中添加元素,如果没有该数组,则自动添加数组。
$addToSet:添加值到一个数组中去,如果数组中已经存在该值,那么将不会有任何的操作。
$pull:删除数组元素,将所有匹配的元素删除。
$each:可用于一次表示添加多个值数组,若只想添加一个值,则数组只放一个值就好。
$ne:不等于,例如查询x不等于3 的数据:db.things.find( { x : { $ne : 3 } } );
$in:包含,可用于查询某一范围内的值,例如查询x 的值为2、4、6的数据,可写为
db.things.find({x:{$in: [2,4,6]}});
$set:替换掉指定字段的值
参考文档:
spring mongodb数组修改器—
p
u
s
h
、
push、
push、ne、
a
d
d
t
o
s
e
t
、
addtoset、
addtoset、pop、$pull
MongoDB高级查询
添加和删除以教师表为例,教师表结构:
- 数组添加新值
向_papers数组插入多个新的questionId:
插入语句为:
let questionId=["5ce80d3e15bd0b06c82573e4","5ce80d3e15bd0b06c82573e5"];
Teacher.update({
"_id": teacherId,
}, {
"$addToSet": {
"_questions": {
"$each": questionId
}
}
}, (err, doc) => {...})
参考文档:MongoDB数组更新操作$addToSet和$each修饰符
- 删除数组的多个数据
删除_papers数组多个questionId:
let questionId=["5ce80d3e15bd0b06c82573e4","5ce80d3e15bd0b06c82573e5"];
Teacher.update({
"_id": teacherId,
}, {
"$pull": {
"_questions": {
"$in": questionId
}
}
}, (err, doc) => {...})
- 修改数组操作
占位符$技术要点:
- 1.占位符$的作用主要是用于返回数组中第一个匹配的数组元素值(子集),例如下列集合:
{ "_id" : 2, "semester" : 1, "grades" : [ "901", "902", "903" ] },
{ "_id" : 3, "semester" : 1, "grades" : [ "9021", "9022", "9023" ] },
{ "_id" : 4, "semester" : 1, "grades" : [ "9031", "9032", "9033" ] },
{ "_id" : 5, "semester" : 1, "grades" : [ "80", "95", "96" ] },
模糊查询所grades含有“90”的记录如正下方图: 加上$的结果如正下方图:
- 2.在更新时未显示指定数组中元素位置的情形下,占位符$用于识别元素的位置,也就是说,当要修改数组中的元素时,如果不知道数组中元素的位置,可以使用位置$操作符,但是修改的时候也只会修改第一个匹配到的子集记录,如下图:
- 更新数组内嵌文档样式可以使用.$.成员方式,例如上面学生表的结果更新exam的date字段,可写为:exams.$.date
以学生表为例,学生表结构:
例如:对于上面的学生表结构,当想要修改paperId为1的数据的date值时,语句写成:
let peparId=1;
Student.updateMany({
"exams._paper": paperId,
}, {
$set: { "exams.$.date": paperData.time }
}, (err4, doc4) => {})
参考文档:
Mongodb数组操作$(update)、$占位符更新嵌套数组、嵌套文档集合
MongoDB 数组元素增删改