Fading Coder

One Final Commit for the Last Sprint

Home > Notes > Content

Design and Implementation of an Online Examination and Learning Collaboration Web Platform Based on Spring Boot and Vue

Notes May 18 11

System Overview

Modern industries rely on specialized software for daily operations, and internet technologies have become indispensable to global workforces. Existing exam and learning exchange management systems often suffer from non-standard operational workflows, low fault tolerance, and high administrative workloads. This online examination and learning collaboration web platform standardizes data handling processes and ensures data legitimacy and security. It supports full lifecycle management of learning videos, test papers, exams, questions, and forum discussions. Built using Java, Spring Boot, and MySQL, the system delivers reliable, secure performance that improves management efficiency for exam and learning-related data.

Development Environment

  • Programming Language: Java
  • Framework: Spring Boot
  • JDK Version: JDK 1.8
  • Application Server: Tomcat 7
  • Database: MySQL 5.7 (mandatory version requirement)
  • Database Management Tool: Navicat 11
  • Development IDEs: Eclipse, MyEclipse, IntelliJ IDEA
  • Maven Version: 3.3.9
  • Supported Browsers: Google Chrome
  • Backend Access Path: localhost:8080/{project-name}/admin/dist/index.html
  • Frontend Access Path: localhost:8080/{project-name}/front/dist/index.html (skip if no frontend module)
  • Default Admin Credentials: Username admin, Password admin

Core Technology Introduction

Java Programming Language

Java is a object-oriented, statically typed programming language with built-in multi-threading and modular design capabilities. Its core features include encapsulation, inheritance, and polymorphism, which enable high code reusability and maintainability. Java's built-in network APIs integrate seamlessly with web application libraries, and automatic garbage collection and exception handling mechanisms improve application stability and robustness. It is one of the most widely used genarel-purpose programming languages for modern software development.

Spring Boot Framework

Spring Boot has become one of the most popular backend development frameworks in recent years. It eliminates the complex configuration requirements of traditional Spring framework projects, greatly simplifying the setup and development of Spring applications. The framework retains all core capabilities of Spring while pre-configuring common dependencies during project initialization, reducing manual configuration overhead for developers. It also integrates most mainstream development libraries, eliminating the need to manually import and resolve dependency version conflicts, improving the stability of dependency references and accelerating application development cycles.

MySQL Database

MySQL is an open-source relational database management system originally developed by MySQL AB and now part of Oracle Corporation. It uses a simplified, easy-to-learn query language that requires fewer lines of code to implement complex operations compared to competing database systems. Key advantages include fast execution speed, wide compatibility, high data security, and low resource consumption. It supports data manipulation, database maintenance, and has high data sharing, low redundancy, and easy scalability. Built-in user authentication and data encryption mechanisms ensure the security of stored data. This system uses MySQL for all persistent data storage and management due to its mature ecosystem and suitability for web application development.

Core Implementation Code

File Upload and Download Controller

package org.exam.platform.controller;

import java.io.File;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import com.annotation.IgnoreAuth;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.entity.ConfigEntity;
import com.entity.EIException;
import com.service.ConfigService;
import com.utils.R;

/**
 * File upload and download controller
 */
@RestController
@RequestMapping("/file")
@SuppressWarnings({"unchecked", "rawtypes"})
public class FileResourceController {
    @Autowired
    private ConfigService configService;

    /**
     * Upload file
     */
    @RequestMapping("/upload")
    @IgnoreAuth
    public R uploadFile(@RequestParam("file") MultipartFile uploadFile, String configType) throws Exception {
        if (uploadFile.isEmpty()) {
            throw new EIException("Uploaded file cannot be empty");
        }
        String originalFilename = uploadFile.getOriginalFilename();
        String fileExtension = originalFilename.substring(originalFilename.lastIndexOf(".") + 1);
        File staticDir = new File(ResourceUtils.getURL("classpath:static").getPath());
        if (!staticDir.exists()) {
            staticDir = new File("");
        }
        File uploadDir = new File(staticDir.getAbsolutePath(), "/upload/");
        if (!uploadDir.exists()) {
            uploadDir.mkdirs();
        }
        String uniqueFileName = new Date().getTime() + "." + fileExtension;
        File targetFile = new File(uploadDir.getAbsolutePath() + "/" + uniqueFileName);
        uploadFile.transferTo(targetFile);

        // Optional: Copy files to external static directory to avoid loss on IDE restart
        /*
        String externalStaticPath = "/path/to/your/project/src/main/resources/static/upload";
        FileUtils.copyFile(targetFile, new File(externalStaticPath + "/" + uniqueFileName));
        */

        if (StringUtils.isNotBlank(configType) && configType.equals("1")) {
            EntityWrapper<ConfigEntity> configWrapper = new EntityWrapper<>();
            configWrapper.eq("name", "faceFile");
            ConfigEntity config = configService.selectOne(configWrapper);
            if (config == null) {
                config = new ConfigEntity();
                config.setName("faceFile");
            }
            config.setValue(uniqueFileName);
            configService.insertOrUpdate(config);
        }
        return R.ok().put("file", uniqueFileName);
    }

    /**
     * Download file
     */
    @IgnoreAuth
    @RequestMapping("/download")
    public ResponseEntity<byte[]> downloadFile(@RequestParam String fileName) {
        try {
            File staticDir = new File(ResourceUtils.getURL("classpath:static").getPath());
            if (!staticDir.exists()) {
                staticDir = new File("");
            }
            File uploadDir = new File(staticDir.getAbsolutePath(), "/upload/");
            File targetFile = new File(uploadDir.getAbsolutePath() + "/" + fileName);
            if (targetFile.exists()) {
                HttpHeaders headers = new HttpHeaders();
                headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);
                headers.setContentDispositionFormData("attachment", fileName);
                return new ResponseEntity<>(FileUtils.readFileToByteArray(targetFile), headers, HttpStatus.CREATED);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
    }
}

Discussion Forum Controller

package org.exam.platform.controller;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.annotation.IgnoreAuth;

import com.entity.DiscussionPostEntity;
import com.entity.view.DiscussionPostView;

import com.service.DiscussionPostService;
import com.utils.PageUtils;
import com.utils.R;
import com.utils.MPUtil;
import java.io.IOException;

/**
 * Discussion forum backend API
 */
@RestController
@RequestMapping("/discussion")
public class DiscussionForumController {
    @Autowired
    private DiscussionPostService discussionPostService;

    /**
     * Backend paginated post list
     */
    @RequestMapping("/page")
    public R getPaginatedAdminPosts(@RequestParam Map<String, Object> params, DiscussionPostEntity post, HttpServletRequest request) {
        if (!request.getSession().getAttribute("role").toString().equals("admin")) {
            post.setUserId((Long) request.getSession().getAttribute("userId"));
        }
        EntityWrapper<DiscussionPostEntity> wrapper = new EntityWrapper<>();
        PageUtils page = discussionPostService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(wrapper, post), params), params));
        return R.ok().put("data", page);
    }

    /**
     * Frontend paginated post list
     */
    @RequestMapping("/list")
    public R getPaginatedPublicPosts(@RequestParam Map<String, Object> params, DiscussionPostEntity post, HttpServletRequest request) {
        if (!request.getSession().getAttribute("role").toString().equals("admin")) {
            post.setUserId((Long) request.getSession().getAttribute("userId"));
        }
        EntityWrapper<DiscussionPostEntity> wrapper = new EntityWrapper<>();
        PageUtils page = discussionPostService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(wrapper, post), params), params));
        return R.ok().put("data", page);
    }

    /**
     * Public post list (no auth required)
     */
    @IgnoreAuth
    @RequestMapping("/flist")
    public R getPublicPostList(@RequestParam Map<String, Object> params, DiscussionPostEntity post, HttpServletRequest request) {
        EntityWrapper<DiscussionPostEntity> wrapper = new EntityWrapper<>();
        PageUtils page = discussionPostService.queryPage(params, MPUtil.sort(MPUtil.between(MPUtil.likeOrEq(wrapper, post), params), params));
        return R.ok().put("data", page);
    }

    /**
     * Query posts
     */
    @RequestMapping("/query")
    public R queryPosts(DiscussionPostEntity post) {
        EntityWrapper<DiscussionPostEntity> wrapper = new EntityWrapper<>();
        wrapper.allEq(MPUtil.allEQMapPre(post, "discussionPost"));
        DiscussionPostView postView = discussionPostService.selectView(wrapper);
        return R.ok("Query discussion posts successfully").put("data", postView);
    }

    /**
     * Backend post detail
     */
    @RequestMapping("/info/{id}")
    public R getAdminPostDetail(@PathVariable("id") Long id) {
        DiscussionPostEntity post = discussionPostService.selectById(id);
        return R.ok().put("data", post);
    }

    /**
     * Frontend post detail (no auth required)
     */
    @IgnoreAuth
    @RequestMapping("/detail/{id}")
    public R getPublicPostDetail(@PathVariable("id") Long id) {
        DiscussionPostEntity post = discussionPostService.selectById(id);
        return R.ok().put("data", post);
    }

    /**
     * Post thread detail with replies
     */
    @IgnoreAuth
    @RequestMapping("/thread/{id}")
    public R getPostThread(@PathVariable("id") String id) {
        DiscussionPostEntity post = discussionPostService.selectById(Long.parseLong(id));
        loadReplies(post);
        return R.ok().put("data", post);
    }

    private DiscussionPostEntity loadReplies(DiscussionPostEntity post) {
        java.util.List<DiscussionPostEntity> replies = discussionPostService.selectList(new EntityWrapper<DiscussionPostEntity>().eq("parentId", post.getId()));
        if (replies == null || replies.size() == 0) {
            return null;
        }
        post.setReplies(replies);
        for (DiscussionPostEntity reply : replies) {
            loadReplies(reply);
        }
        return post;
    }

    /**
     * Backend save post
     */
    @RequestMapping("/save")
    public R saveAdminPost(@RequestBody DiscussionPostEntity post, HttpServletRequest request) {
        post.setId(new Date().getTime() + (long) Math.floor(Math.random() * 1000));
        post.setUserId((Long) request.getSession().getAttribute("userId"));
        discussionPostService.insert(post);
        return R.ok();
    }

    /**
     * Frontend save post
     */
    @RequestMapping("/add")
    public R savePublicPost(@RequestBody DiscussionPostEntity post, HttpServletRequest request) {
        post.setId(new Date().getTime() + (long) Math.floor(Math.random() * 1000));
        post.setUserId((Long) request.getSession().getAttribute("userId"));
        discussionPostService.insert(post);
        return R.ok();
    }

    /**
     * Update post
     */
    @RequestMapping("/update")
    @Transactional
    public R updatePost(@RequestBody DiscussionPostEntity post, HttpServletRequest request) {
        discussionPostService.updateById(post);
        return R.ok();
    }

    /**
     * Delete posts
     */
    @RequestMapping("/delete")
    public R deletePosts(@RequestBody Long[] ids) {
        discussionPostService.deleteBatchIds(Arrays.asList(ids));
        return R.ok();
    }

    /**
     * Reminder count API
     */
    @RequestMapping("/remind/{columnName}/{type}")
    public R getRemindCount(@PathVariable("columnName") String columnName, HttpServletRequest request,
                            @PathVariable("type") String type, @RequestParam Map<String, Object> params) {
        params.put("column", columnName);
        params.put("type", type);

        if (type.equals("2")) {
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
            Calendar calendar = Calendar.getInstance();
            Date remindStartDate = null;
            Date remindEndDate = null;
            if (params.get("remindstart") != null) {
                int startDays = Integer.parseInt(params.get("remindstart").toString());
                calendar.setTime(new Date());
                calendar.add(Calendar.DAY_OF_MONTH, startDays);
                remindStartDate = calendar.getTime();
                params.put("remindstart", dateFormat.format(remindStartDate));
            }
            if (params.get("remindend") != null) {
                int endDays = Integer.parseInt(params.get("remindend").toString());
                calendar.setTime(new Date());
                calendar.add(Calendar.DAY_OF_MONTH, endDays);
                remindEndDate = calendar.getTime();
                params.put("remindend", dateFormat.format(remindEndDate));
            }
        }

        Wrapper<DiscussionPostEntity> wrapper = new EntityWrapper<>();
        if (params.get("remindstart") != null) {
            wrapper.ge(columnName, params.get("remindstart"));
        }
        if (params.get("remindend") != null) {
            wrapper.le(columnName, params.get("remindend"));
        }

        int count = discussionPostService.selectCount(wrapper);
        return R.ok().put("count", count);
    }
}

System Testing

This system was first installed and tested on a local development server. Following a thorough understanding of the system architecture and processing logic, both white-box and black-box testing were conducted. During software development, complex real-world problems often lead to defects across all stages of the software development lifecycle. The core goal of software testing is to identify undiscovered errors. The testing plan was developed following these core principles:

  1. All test cases must align with user requiremants
  2. Test planning should be completed before coding begins, based on finalized user requirements models
  3. Apply Pareto's Law: focus testing efforts on the ~20% of modules that account for ~80% of potential defects, starting with small-scale unit tests and expanding to full integrated system tests
  4. Design test cases to fully cover all program logic paths and validate compliance with functional requirements

System Advantages

This platform offers several key advantages over existing similar systems: comprehensive feature set, easy future updates, simplified database management, user-friendly interface, intuitive operation, high operational efficiency, and robust security. From a technical perspective:

  1. Using Java for dynamic page generation ensures high maintainability and code reusability
  2. The Spring Boot framework separates presentation and business logic clearly, simplifying module management especially for large-scale projects
  3. The MySQL database offers excellent support for XML standards, high extensibility, ease of use, and strong security, making it an ideal choice for this platform's data storage needs

Related Articles

Designing Alertmanager Templates for Prometheus Notifications

How to craft Alertmanager templates to format alert messages, improving clarity and presentation. Alertmanager uses Go’s text/template engine with additional helper functions. Alerting rules referenc...

Deploying a Maven Web Application to Tomcat 9 Using the Tomcat Manager

Tomcat 9 does not provide a dedicated Maven plugin. The Tomcat Manager interface, however, is backward-compatible, so the Tomcat 7 Maven Plugin can be used to deploy to Tomcat 9. This guide shows two...

Skipping Errors in MySQL Asynchronous Replication

When a replica halts because the SQL thread encounters an error, you can resume replication by skipping the problematic event(s). Two common approaches are available. Methods to Skip Errors 1) Skip a...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.