24

MongoDB实现问卷/考试设计

 4 years ago
source link: https://www.tuicool.com/articles/zuAzya2
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

MongoDB的特点

MongoDB是一个面向文档存储的数据库。在MongoDB中,一条记录叫做document(文档),由类似于JSON结构的键值对组成。

Uzymyy3.png!web

由于类似于MongoDB直接存储JSON的特性,MongoDB天生适合作为存储结构复杂的数据结构的介质。类似于问卷调查和考试这种需求,用mysql这种关系型数据库实现起来太过复杂,效率低下;而如果使用MongoDB来实现的话,则会发现异常清晰简单。

需求分析

在一张试卷中,会有很多个问题,问题的类型大体上可以分为单选题、多选题、判断题、简答题等。每一个问题又会有很多个选项,选项可以是文字描述也可以是图片又或者图文结合。

fmmiauF.png!web

那么一张试卷的JSON格式应该大体上长成这样:

RraIZjz.png!web

当然这只是最简单的数据结构,要完成一张试卷,还需要加入更多的属性。

结构设计

我们采用自底向上的结构设计方式,先对每个选项的数据结构进行设计。

选项设计

public class Option {
    /**
     * 选项类型
     */
    private Integer oType = 1;

    /**
     * 选项内容
     */
    private String text;
    
    /**
     * 选项图片
     */
    private String img;

    /**
     * 是否正确答案
     */
    private Boolean right;

    /**
     * 用户是否选择
     */
    private Boolean selected;
    ...

选项类型 oType 用来标志选项是普通文本还是图片或者图文; right 用来标志这个选项是否是正确答案,用于自动判卷; selected 用来标志用户有没有选择这个答案。

问题设计

public class Question extends MongoBean {

    /**
     * 数据的id
     */
    private String dataId;

    /**
     * 题目类型,1判断题;2单选题;3多选题
     */
    private Integer qType;

    /**
     * 题目标题
     */
    private String title;

    /**
     * 题目选项
     */
    private List<Option> options;
    /**
     * 数据类型
     * @see rmjk.enums.BizTypeEnum
     */
    private Integer dataType;

    /**
     * 数据标题
     */
    private String dataTitle;
    /**
     * 解析
     */
    private String analysis;

    /**
     * 这题是否答对
     */
    private Boolean right;

    /**
     * 这题答的时长
     */
    private Long duration;

    /**
     * 这题的得分
     */
    private Long points;
    ...

dataId 用于将这个问题同一个业务数据绑定, dataType 用来标志这个业务数据的类型,这两个字段方便数据的扩展; dataTitle 是业务数据的标题; options 是这个问题的选项; analysis 问题的解析,用于用户答题结束后的自查; right 用来记录问题的正确与否。

新增问题

上层接口

提供新增问题的接口:

@PostMapping("/saveOrUpdateQuestion")
public JsonData saveOrUpdateQuestion(@RequestBody Question data) {
    questionService.saveOrUpdateQuestion(data);
    return JsonData.success();
}

QuestionService:

public void saveOrUpdateQuestion(Question data) {
    if (StringUtils.isEmpty(data.getId())) {// 新增
        writer.insert(manager.getExamDataBase(), ExamConstant.QUESTION_COLLECT, data);
    } else {//修改
        writer.updateDocument(data, ExamConstant.QUESTION_COLLECT);
    }
}

DAO

Writer:

public void insert(String dataBase, String collect, MongoBean data) {
    if (data.getId() == null) {
        data.setId(BsonTool.uuid());
    }
    MongoCollection<Document> collection = getCollection(dataBase, collect);
    collection.insertOne(Document.parse(JSONObject.toJSONString(data)));
}

public Document updateDocument(MongoBean data, String questionCollect) {
    Document filter = new Document();
    filter.put("id", data.getId());
    Document res = new Document();
    res.put("$set", BsonDocument.parse(JSONObject.toJSONString(data)));
    update(manager.getExamDataBase(), questionCollect, filter, res);
    return res;
}
public boolean update(String dataBase, String collect, Bson filter, Bson update) {
    MongoCollection<Document> collection = getCollection(dataBase, collect);
    UpdateResult ur = collection.updateOne(filter, update);
    return ur.getModifiedCount() > 0;
}

这样后端的工作就全部完成了,接下来就是前端怎么给后端提供这样的数据结构了。

前端实现数据结构

前端使用 vue 实现JSON的构造:

<Modal title="问题编辑" v-model="showEdit" :closable="false" :mask-closable="false">
    <Form ref="question" :model="question" :rules="ruleValidate">
        <FormItem label="题目类型:" prop="qType">
            <Select v-model="question.qType" class="input-180" placeholder="题目类型" @on-change="changeQType(question)">
                <Option v-for="d in qTypes" :value="d.value" :key="d.value">{{ d.label }}</Option>
            </Select>
        </FormItem>
        <FormItem label="题目:" prop="title">
            <Input
                   class="input-95-per"
                   v-model="question.title"
                   type="textarea"
                   row="1"
                   placeholder="题目"
                   ></Input>
        </FormItem>
        <FormItem label="选项:">
            <div v-for="(o, i2) in question.options" :key="i2" style="display:flex">
                <Input class="input-95-per margin-bot-8 margin-right-10" v-model="o.text">
                    <span slot="prepend">{{i2+1}}:</span>
                </Input>
                <Button size="small" @click="addOpt(question)" v-if="i2===0">+</Button>
                <Button size="small" @click="delOpt(question, o)" v-if="i2">-</Button>
                <Checkbox v-model="o.right">正确答案</Checkbox>
            </div>
        </FormItem>
        <FormItem label="答案解析:">
            <Input
                   class="input-95-per"
                   v-model="question.analysis"
                   type="textarea"
                   row="1"
                   placeholder="答案解析"
                   ></Input>
        </FormItem>
    </Form>
    <div slot="footer">
        <Button type="text" @click="cancelQuestion">取消</Button>
        <Button type="primary" :loading="saveLoading" @click="saveQuestion">保存</Button>
    </div>
</Modal>

这里绑定的 question 就是一个问题了。而一张试卷则是由多个问题,再加上试卷的额外属性构成的。

6N3eiqQ.png!web

question 上的dataId刚好就能绑定上试卷的id

Exam exam = new Exam();
List<Question> questions = reader.findRandom(manager.getExamDataBase(), ExamConstant.QUESTION_COLLECT, new Document(), Question.class, no);
exam.setTitle(title);
exam.setDuration(dutation);
return exam;

y2Qbq2B.png!web

转评赞就是最大的鼓励

zYRVjmE.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK