基于SpringBoot+VUE(PC端+小程序端)的智能在线考试系统毕业设计

简介: 基于SpringBoot+VUE(PC端+小程序端)的智能在线考试系统毕业设计

项目编号:

一,项目简介

随着计算机技术的不断发展,我们的日常生活和工作都与计算机技术的关系越来越密切。计算机技术的发展改变了我们日常的生活和工作习惯,也改变了社会的发展速度,使得我们的生活更加便利和高效。伴随着计算机技术发展起来的互联网技术将我们的生活带领进信息化时代,改变了我们的学习和工作环境,例如我们经常面对的考试也随着互联网技术的发展产生了改变,伴随着信息技术的发展,在线无纸化的考试系统应运而生,不仅彻底改变了传统纸质考试的习惯和环境,更是提高了考试效率,保证了考试效果,达到了考试目的[1]。传统的纸质考试具有很多局限性和不足,主要包括以下几点:

1.传统纸质考试需要较多的人力资源和时间资源进行题目的设定,同时题目的难易程度和考核价值水平很难达到基本的要求;

2.传统纸质考试的阅卷采用人工的方式,人工阅卷难免会出现阅卷差错或者分数合算差错,这也会对考试的效果造成影响;

3.传统纸质考试的人工阅卷模式也会浪费大量的人力资源和时间资源,不能保证工作效率和工作质量;

4.传统纸质考试对考试的总结能力较差,不能够全面具体的分析考试结果,教师也很难得到基本的考试结果分析的数据信息,而这些数据信息是提高教学质量和教学效果的关键因素;

5.传统纸质考试对考试时间以及考试纪律的要求不能达到统一,这也会影响到考试的公平性。根据以上分析的传统纸质考试的不足之处,新型的结合计算机技术以及互联网技术的在线考试系统应运而生,不仅通过一种新的技术解决了传统纸质考试的基本问题,还提供了一种新的考试思路和考试理念,纠正了传统纸质考试的弊端,提供更加合理有效的考试过程。

二,环境介绍

三,技术说明

后端系统 前端系统 微信小程序
spring-boot 2.1.6.RELEASE vue 采用新版,使用了vue-cli4搭建的系统 iView 主题样式
spring-boot-security 用户登录验证 element-ui 最流行的vue UI框架
undertow web容器 vue-element-admin 模版
mybatis/mybatis_plus echarts 图表统计
hikari 速度最快的数据库连接池 ueditor 题目编辑器

四,功能列表

4.1 学生系统功能

模块 介绍
登录 用户名、密码
注册 年级、用户名、密码
任务中心 管理员发布的年级任务,每个学生只能做一次
考试 题干支持文本、图片、数学公式、表格等,学生答题支持:文本
固定试卷 可重复练习、自行批改的试卷
时段试卷 在时间限制内,可重复练习、自行批改的试卷
考试记录 查看答卷记录和试卷信息
错题本 答错题目会自动进入错题本,显示题目基本信息
个人信息 显示学生个人资料
更新信息 修改个人资料、头像
个人动态 显示用户最近的个人动态
消息中心 用于接收管理员发送的消息

4.2 管理系统功能

模块 介绍
登录 用户名、密码
主页 试卷总数、题目总数、用户活跃度、题目月数量
学生列表 显示系统所有的学生,新增、修改、删除、禁用
管理员列表 显示系统所有的管理员,新增、修改、删除、禁用
学科列表 学科查询、修改、删除
学科创编 创建学科
试卷列表 试卷查询、修改、删除
试卷创编 创建的试卷为时段试卷、固定试卷、任务试卷
题目列表 题目查询、修改、删除
题目创建 题目支持单选题、多选题、判断题、填空题、简答题,题干支持文本、图片、表格、数学公式
任务列表 任务查询、修改、删除
消息列表 显示已发送的消息,消息已读人数等信息
消息发送 发送消息给多个用户
用户日志 显示所有用户日志
个人资料 显示管理员用户名、真实姓名
时间线 显示管理员创建时间
修改资料 修改姓名、手机号

4.3 小程序功能

模块 介绍
登录 用户登录登出功能,登录会自动绑定微信账号,登出会解绑
注册 年级、用户名、密码
任务中心 管理员发布的年级任务,每个学生只能做一次
考试 题干支持文本、图片、数学公式、表格等,学生答题支持:文本
固定试卷 可重复练习、自行批改的试卷
时段试卷 在时间限制内,可重复练习、自行批改的试卷
考试记录 查看答卷记录和试卷信息
错题本 答错题目会自动进入错题本,显示题目基本信息
个人信息 显示学生个人资料
更新信息 修改个人资料、头像
个人动态 显示用户最近的个人动态
消息中心 用于接收管理员发送的消息

五,数据库设计

仅展示部分数据库字段设计

5.1 试卷表

字段名 类型 注释
id int
name varchar 试卷名称
subject_id int 学科
paper_type int 试卷类型( 1.固定试卷 4.时段试卷 6.任务试卷 )
grade_level int 年级
score int 试卷总分(千分制)
question_count int 题目数量
suggest_time int 建议时长(分钟)
limit_start_time datetime 时段试卷 开始时间
limit_end_time datetime 时段试卷 结束时间
frame_text_content_id int 试卷框架 内容为JSON
create_user int
create_time datetime
deleted bit
task_exam_id int

5.2 试卷答案表

字段名 类型 注释
id int
exam_paper_id int
paper_name varchar 试卷名称
paper_type int 试卷类型( 1.固定试卷 4.时段试卷 6.任务试卷 )
subject_id int 学科
system_score int 系统判定得分
user_score int 最终得分(千分制)
paper_score int 试卷总分
question_correct int 做对题目数量
question_count int 题目总数量
do_time int 做题时间(秒)
status int 试卷状态(1待判分 2完成)
create_user int 学生
create_time datetime 提交时间
task_exam_id int

5.3 题目表

字段名 类型 注释
id int
question_type int 1.单选题 2.多选题 3.判断题 4.填空题 5.简答题
subject_id int 学科
score int 题目总分(千分制)
grade_level int 级别
difficult int 题目难度
correct text 正确答案
info_text_content_id int 题目 填空、 题干、解析、答案等信息
create_user int 创建人
status int 1.正常
create_time datetime 创建时间
deleted bit

5.4 学科表

字段名 类型 注释
id int
name varchar 语文 数学 英语 等
level int 年级 (1-12) 小学 初中 高中 大学
level_name varchar 一年级、二年级等
item_order int 排序
deleted bit

5.5 用户表

字段名 类型 注释
id int
user_uuid varchar
user_name varchar 用户名
password varchar
real_name varchar 真实姓名
age int
sex int 1.男 2女
birth_day datetime
user_level int 学生年级(1-12)
phone varchar
role int 1.学生 3.管理员
status int 1.启用 2禁用
image_path varchar 头像地址
create_time datetime
modify_time datetime
last_active_time datetime
deleted bit 是否删除
wx_open_id varchar 微信openId

5.6 用户日志表

字段名 类型 注释
id int
user_id int 用户id
user_name varchar 用户名
real_name varchar 真实姓名
content text 内容
create_time datetime 时间

其他表的设计省略............

六,系统展示

6.1 后台管理

主页

用户管理

试卷管理

题目管理

添加题目

添加试卷

任务管理

教育管理

成绩管理

6.2 学生端

首页登录与注册

学生端首页

试卷中心

考试记录

错题本

个人中心

6.3 小程序端

登录与注册

首页

试卷考试

考试记录

我的

七,核心代码展示

@Service
public class TaskExamServiceImpl extends BaseServiceImpl<TaskExam> implements TaskExamService {
    protected final static ModelMapper modelMapper = ModelMapperSingle.Instance();
    private final TaskExamMapper taskExamMapper;
    private final TextContentService textContentService;
    private final ExamPaperMapper examPaperMapper;
    @Autowired
    public TaskExamServiceImpl(TaskExamMapper taskExamMapper, TextContentService textContentService, ExamPaperMapper examPaperMapper) {
        super(taskExamMapper);
        this.taskExamMapper = taskExamMapper;
        this.textContentService = textContentService;
        this.examPaperMapper = examPaperMapper;
    }
    @Override
    public PageInfo<TaskExam> page(TaskPageRequestVM requestVM) {
        return PageHelper.startPage(requestVM.getPageIndex(), requestVM.getPageSize(), "id desc").doSelectPageInfo(() ->
                taskExamMapper.page(requestVM)
        );
    }
    @Override
    @Transactional
    public void edit(TaskRequestVM model, User user) {
        ActionEnum actionEnum = (model.getId() == null) ? ActionEnum.ADD : ActionEnum.UPDATE;
        TaskExam taskExam = null;
        if (actionEnum == ActionEnum.ADD) {
            Date now = new Date();
            taskExam = modelMapper.map(model, TaskExam.class);
            taskExam.setCreateUser(user.getId());
            taskExam.setCreateUserName(user.getUserName());
            taskExam.setCreateTime(now);
            taskExam.setDeleted(false);
            //保存任务结构
            TextContent textContent = textContentService.jsonConvertInsert(model.getPaperItems(), now, p -> {
                TaskItemObject taskItemObject = new TaskItemObject();
                taskItemObject.setExamPaperId(p.getId());
                taskItemObject.setExamPaperName(p.getName());
                return taskItemObject;
            });
            textContentService.insertByFilter(textContent);
            taskExam.setFrameTextContentId(textContent.getId());
            taskExamMapper.insertSelective(taskExam);
        } else {
            taskExam = taskExamMapper.selectByPrimaryKey(model.getId());
            modelMapper.map(model, taskExam);
            TextContent textContent = textContentService.selectById(taskExam.getFrameTextContentId());
            //清空试卷任务的试卷Id,后面会统一设置
            List<Integer> paperIds = JsonUtil.toJsonListObject(textContent.getContent(), TaskItemObject.class)
                    .stream()
                    .map(d -> d.getExamPaperId())
                    .collect(Collectors.toList());
            examPaperMapper.clearTaskPaper(paperIds);
            //更新任务结构
            textContentService.jsonConvertUpdate(textContent, model.getPaperItems(), p -> {
                TaskItemObject taskItemObject = new TaskItemObject();
                taskItemObject.setExamPaperId(p.getId());
                taskItemObject.setExamPaperName(p.getName());
                return taskItemObject;
            });
            textContentService.updateByIdFilter(textContent);
            taskExamMapper.updateByPrimaryKeySelective(taskExam);
        }
        //更新试卷的taskId
        List<Integer> paperIds = model.getPaperItems().stream().map(d -> d.getId()).collect(Collectors.toList());
        examPaperMapper.updateTaskPaper(taskExam.getId(), paperIds);
        model.setId(taskExam.getId());
    }
    @Override
    public TaskRequestVM taskExamToVM(Integer id) {
        TaskExam taskExam = taskExamMapper.selectByPrimaryKey(id);
        TaskRequestVM vm = modelMapper.map(taskExam, TaskRequestVM.class);
        TextContent textContent = textContentService.selectById(taskExam.getFrameTextContentId());
        List<ExamResponseVM> examResponseVMS = JsonUtil.toJsonListObject(textContent.getContent(), TaskItemObject.class).stream().map(tk -> {
            ExamPaper examPaper = examPaperMapper.selectByPrimaryKey(tk.getExamPaperId());
            ExamResponseVM examResponseVM = modelMapper.map(examPaper, ExamResponseVM.class);
            examResponseVM.setCreateTime(DateTimeUtil.dateFormat(examPaper.getCreateTime()));
            return examResponseVM;
        }).collect(Collectors.toList());
        vm.setPaperItems(examResponseVMS);
        return vm;
    }
    @Override
    public List<TaskExam> getByGradeLevel(Integer gradeLevel) {
        return taskExamMapper.getByGradeLevel(gradeLevel);
    }
}
@Service
public class AuthenticationServiceImpl implements AuthenticationService {
    private final UserService userService;
    private final SystemConfig systemConfig;
    @Autowired
    public AuthenticationServiceImpl(UserService userService, SystemConfig systemConfig) {
        this.userService = userService;
        this.systemConfig = systemConfig;
    }
    /**
     * @param username username
     * @param password password
     * @return boolean
     */
    @Override
    public boolean authUser(String username, String password) {
        User user = userService.getUserByUserName(username);
        return authUser(user, username, password);
    }
    @Override
    public boolean authUser(User user, String username, String password) {
        if (user == null) {
            return false;
        }
        String encodePwd = user.getPassword();
        if (null == encodePwd || encodePwd.length() == 0) {
            return false;
        }
        String pwd = pwdDecode(encodePwd);
        return pwd.equals(password);
    }
    @Override
    public String pwdEncode(String password) {
        return RsaUtil.rsaEncode(systemConfig.getPwdKey().getPublicKey(), password);
    }
    @Override
    public String pwdDecode(String encodePwd) {
        return RsaUtil.rsaDecode(systemConfig.getPwdKey().getPrivateKey(), encodePwd);
    }
}
<view class="exam-page">
  <view class="view-wrap">
    <view class="exam-count-down">{{remainTimeStr}}</view>
  </view>
  <view class="view-wrap-hidden">
  </view>
  <view>
    <view class="exam-name-title">
      <h1>{{form.name}}</h1>
    </view>
    <form bindsubmit='formSubmit'>
      <i-panel title="{{titleItem.name}}" wx:for="{{form.titleItems}}" wx:for-item="titleItem" wx:key="{{titleItem.name}}" i-class="exam-panel-title">
        <i-cell-group i-class="exam-cell">
          <i-cell wx:for="{{titleItem.questionItems}}" wx:key="{{titleItem.id}}" wx:for-item="questionItem">
            <view wx:if="{{questionItem.questionType === 1}}">
              <rich-text nodes="{{questionItem.itemOrder}}. {{questionItem.title}}" />
              <radio-group class="radio-group" name="{{questionItem.itemOrder}}_{{questionItem.id}}_{{questionItem.questionType}}">
                <label class="radio" wx:for="{{questionItem.items}}" wx:key="{{questionItem.prefix}}" wx:for-item="radioItem" class="exam-radio-item-label">
                  <radio color="#2d8cf0" value="{{radioItem.prefix}}" checked="{{radioItem.checked}}" class="exam-item-left" />
                  <rich-text nodes="{{radioItem.prefix}}. {{radioItem.content}}" class="exam-item-left" />
                </label>
              </radio-group>
            </view>
            <view wx:elif="{{questionItem.questionType === 2}}">
              <rich-text nodes="{{questionItem.itemOrder}}. {{questionItem.title}}" class="exam-item-left" style="line-height:35px" />
              <checkbox-group class="exam-item-left" style="margin-left:10px" name="{{questionItem.itemOrder}}_{{questionItem.id}}_{{questionItem.questionType}}">
                <label wx:for="{{questionItem.items}}" wx:key="{{questionItem.prefix}}" wx:for-item="radioItem" class="exam-radio-item-label">
                  <checkbox color="#2d8cf0" value="{{radioItem.prefix}}" checked="{{radioItem.checked}}" class="exam-item-left" />
                  <rich-text nodes="{{radioItem.prefix}}. {{radioItem.content}}" class="exam-item-left" />
                </label>
              </checkbox-group>
            </view>
            <view wx:elif="{{questionItem.questionType === 3}}">
              <rich-text nodes="{{questionItem.itemOrder}}. {{questionItem.title}}" class="exam-item-left" style="line-height:35px" />
              <radio-group class="radio-group" class="exam-item-left" style="margin-left:10px" name="{{questionItem.itemOrder}}_{{questionItem.id}}_{{questionItem.questionType}}">
                <label class="radio" wx:for="{{questionItem.items}}" wx:key="{{questionItem.prefix}}" wx:for-item="radioItem" class="exam-radio-item-label">
                  <radio color="#2d8cf0" value="{{radioItem.prefix}}" checked="{{radioItem.checked}}" class="exam-item-left" />
                  <rich-text nodes="{{radioItem.content}}" class="exam-item-left" />
                </label>
              </radio-group>
            </view>
            <view wx:elif="{{questionItem.questionType === 4}}">
              <rich-text nodes="{{questionItem.itemOrder}}. {{questionItem.title}}" />
              <view class="exam-input-contain" wx:for="{{questionItem.items}}" wx:key="{{questionItem.prefix}}" wx:for-item="inputItem" wx:for-index="idx">
                <view class="exam-input-contain-label">{{inputItem.prefix}}</view>
                <input class="exam-input-contain-content" maxlength="-1" name="{{questionItem.itemOrder}}_{{questionItem.id}}_{{questionItem.questionType}}_{{idx}}" />
              </view>
            </view>
            <view wx:else>
              <rich-text nodes="{{questionItem.itemOrder}}. {{questionItem.title}}" />
              <view class="exam-textarea-contain">
                <textarea placeholder="答案" maxlength="-1" name="{{questionItem.itemOrder}}_{{questionItem.id}}_{{questionItem.questionType}}"></textarea>
              </view>
            </view>
          </i-cell>
        </i-cell-group>
      </i-panel>
      <view>
        <button class="i-btn  i-btn-primary i-btn-square" form-type='submit'>提交</button>
      </view>
      <i-action-sheet visible="true" visible="{{timeOutShow}}" mask-closable="{{ false }}">
        <view slot="header" style="padding: 16px">
          <view class="exam-timeout-title">考试试卷结束,请提交试卷!</view>
          <button class="i-btn  i-btn-primary i-btn-square" form-type='submit'>提交</button>
        </view>
      </i-action-sheet>
    </form>
    <i-modal title="考试结果" visible="{{modalShow}}" bind:ok="returnRecord" bind:cancel="returnRecord">
      <view>得分:{{result}}</view>
    </i-modal>
    <i-spin size="large" fix wx:if="{{ spinShow }}"></i-spin>
    <i-message id="message" />
  </view>
</view>

八,项目部署

8.1 目录结构

获取到源码进行解压后,文件列表如下:

8.2 导入数据库

打开Navicat(或者其他数据库连接工具也可),导入资料中的sql文件。

8.3 后端源码部署

8.3.1 导入源码

打开idea,新建工程【可以任意目录】。把解压后的xzs目录复制到创建好的idea工程中。进行导入。

导入进来之后,idea会进行编译。编译之后没有出现错误说明导入成功。

8.3.2 修改配置文件

修改数据库连接配置,在application-dev.yml配置文件中修改成自己的数据库名和自己的密码。

8.3.3 启动项目

SpringBoot的程序启动类,相信大部分小伙伴们都知道该怎么启动。不过在这里还是要写一下,以防个别小伙伴不知道。在src下找到XzsApplication启动类,在该类中进行右键运行即可。最后查看控制台有没有报错信息。没有报错信息,启动成功。

上面说明启动成功

8.4 管理员端和学生端部署

管理员端和学生端部署操作都是一样的,在这里以管理员端为例进行演示。

打开vscode【用其他前端开发工具打开也可】,导入资料中的vue目录下的xzs-admin工程,学生端是xzs-student。打开vscode的终端,进行安装依赖和启动项目。

8.4.1 安装依赖

命令:npm install

8.4.2 启动项目

命令:npm run serve

运行成功,端口号8002

8.5 小程序端部署

8.5.1 微信小程序开发工具下载与安装

开发工具的官方下载地址为:https://mp.weixin.qq.com/debug/wxadoc/dev/devtools/download.html

不支持Windows XP和Windows 7系统,建议使用WIN10。我这里选择Windows 64版本的安装包。

安装过程比较简单,不用设置什么,按照提示来就行了。下面是每一步详细截图

8.5.2 项目部署

打开微信开发工具---> 选择导入--->找到资料中的wx目录,导入xzs_student

到这一步会提示,输入appId,如果自己有就写自己的,没有的话,点击测试,使用测试号

导入进来之后,会自动提示是否运行,选择【信任并运行】

运行成功 。到此整个项目部署就已经完成了

九,项目总结

该项目是PC端+小程序端。Java做为后端支持。代码结构规整,源码容易阅读,功能完善,非常适合做为毕设来使用。

相关文章
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个具有动态路由和菜单功能的前后端分离应用。首先,创建并配置 Spring Boot 项目,实现后端 API;然后,使用 Ant Design Pro Vue 创建前端项目,配置动态路由和菜单。通过具体案例,展示了如何快速搭建高效、易维护的项目框架。
111 62
|
2天前
|
存储 JavaScript 前端开发
基于 SpringBoot 和 Vue 开发校园点餐订餐外卖跑腿Java源码
一个非常实用的校园外卖系统,基于 SpringBoot 和 Vue 的开发。这一系统源于黑马的外卖案例项目 经过站长的进一步改进和优化,提供了更丰富的功能和更高的可用性。 这个项目的架构设计非常有趣。虽然它采用了SpringBoot和Vue的组合,但并不是一个完全分离的项目。 前端视图通过JS的方式引入了Vue和Element UI,既能利用Vue的快速开发优势,
31 13
|
10天前
|
JavaScript 安全 Java
java版药品不良反应智能监测系统源码,采用SpringBoot、Vue、MySQL技术开发
基于B/S架构,采用Java、SpringBoot、Vue、MySQL等技术自主研发的ADR智能监测系统,适用于三甲医院,支持二次开发。该系统能自动监测全院患者药物不良反应,通过移动端和PC端实时反馈,提升用药安全。系统涵盖规则管理、监测报告、系统管理三大模块,确保精准、高效地处理ADR事件。
|
1月前
|
XML Java 数据库连接
SpringBoot集成Flowable:打造强大的工作流管理系统
在企业级应用开发中,工作流管理是一个核心组件,它能够帮助我们定义、执行和管理业务流程。Flowable是一个开源的工作流和业务流程管理(BPM)平台,它提供了强大的工作流引擎和建模工具。结合SpringBoot,我们可以快速构建一个高效、灵活的工作流管理系统。本文将探讨如何将Flowable集成到SpringBoot应用中,并展示其强大的功能。
138 1
|
1月前
|
JavaScript 安全 Java
如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能
本文介绍了如何使用 Spring Boot 和 Ant Design Pro Vue 构建一个前后端分离的应用框架,实现动态路由和菜单功能。首先,确保开发环境已安装必要的工具,然后创建并配置 Spring Boot 项目,包括添加依赖和配置 Spring Security。接着,创建后端 API 和前端项目,配置动态路由和菜单。最后,运行项目并分享实践心得,帮助开发者提高开发效率和应用的可维护性。
69 2
|
1月前
|
JavaScript NoSQL Java
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
CC-ADMIN后台简介一个基于 Spring Boot 2.1.3 、SpringBootMybatis plus、JWT、Shiro、Redis、Vue quasar 的前后端分离的后台管理系统
39 0
|
26天前
|
小程序 前端开发 JavaScript
在线课堂+工具组件小程序uniapp移动端源码
在线课堂+工具组件小程序uniapp移动端源码
26 0
在线课堂+工具组件小程序uniapp移动端源码
|
2月前
|
移动开发 小程序 数据可视化
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
基于npm CLI脚手架的uniapp项目创建、运行与打包全攻略(微信小程序、H5、APP全覆盖)
315 3
|
2月前
|
小程序 API
微信小程序更新提醒uniapp
在小程序开发中,版本更新至关重要。本方案利用 `uni-app` 的 `uni.getUpdateManager()` API 在启动时检测版本更新,提示用户并提供立即更新选项,自动下载更新内容,并在更新完成后重启小程序以应用新版本。适用于微信小程序,确保用户始终使用最新版本。以下是实现步骤: ### 实现步骤 1. **创建更新方法**:在 `App.vue` 中创建 `updateApp` 方法用于检查小程序是否有新版本。 2. **测试**:添加编译模式并选择成功状态进行模拟测试。
55 0
微信小程序更新提醒uniapp
|
4月前
|
小程序 前端开发 Java
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目
JavaDog Chat v1.0.0 是一款基于 SpringBoot、MybatisPlus 和 uniapp 的简易聊天软件,兼容 H5、小程序和 APP,提供丰富的注释和简洁代码,适合初学者。主要功能包括登录注册、消息发送、好友管理及群组交流。
115 0
SpringBoot+uniapp+uview打造H5+小程序+APP入门学习的聊天小项目