Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Design and Implementation of a Cross-Platform Online Tutoring Platform Using Spring Boot and Vue.js

Tech May 18 2

Architecture and Technology Stack

The platform adopts a decoupled client-server architecture to ensure scalability and maintainability. The backend leverages Spring Boot, which simplifies enterprise Java development through automatic configuration and embedded servlet containers (Tomcat or Undertow). This eliminates the need for manual deployment descriptors and accelerates the initialization process. Spring Boot's ecosystem integration provides out-of-the-box support for data access, security, and cloud-native features.

On the frontend, Vue.js drives the web interface. Its core relies on a reactive data-binding mechanism and a virtual DOM implementation, which optimizes rendering performance by minimizing direct DOM manipulations. Component-based architecture allows for modular UI development, enabling developers to focus on state management rather than manual view updates. For mobile and cross-platform distribution, uniapp translates the Vue codebase into native mini-program and app bundles, ensuring consistent user experiences across iOS, Android, and various web environments.

Database persistence is managed through MyBatis-Plus, an enhancement layer over MyBatis. It introduces code generation utilities, pagination plugins, and dynamic SQL capabilities while reducing boilerplate. The framework abstracts common CRUD operations through lambda-based query wrappers, significantly cutting down manual SQL drafting and mapping configuration.

Authentication and Session Management

Stateless authentication is implemented using a custom token-based strategy. Upon successful credential verification, the system generates a unique acces key and stores session metadata in a dedicated repository. Subsequent requests are validated through an HTTP interceptor that inspects headers, bypasses public endpoints, and injects user context into the request lifecycle.

package com.tutoring.platform.security;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.tutoring.platform.common.ResponseWrapper;
import com.tutoring.platform.entity.AccountRecord;
import com.tutoring.platform.service.SessionManager;
import com.tutoring.platform.annotation.PublicEndpoint;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import jakarta.servlet.http.HttpServletRequest;
import java.util.Map;
import java.util.UUID;

@RestController
@RequestMapping("/api/v1/session")
public class SessionController {

    private final AccountRepository accountRepo;
    private final SessionManager sessionService;

    public SessionController(AccountRepository accountRepo, SessionManager sessionService) {
        this.accountRepo = accountRepo;
        this.sessionService = sessionService;
    }

    @PostMapping("/initiate")
    @PublicEndpoint
    public ResponseEntity<ResponseWrapper<Map<String, Object>>> authenticate(
            @RequestParam String accountName,
            @RequestParam String credential,
            HttpServletRequest context) {

        LambdaQueryWrapper<AccountRecord> query = new LambdaQueryWrapper<>();
        query.eq(AccountRecord::getName, accountName);
        AccountRecord existingUser = accountRepo.getOne(query);

        if (existingUser == null || !existingUser.getCredential().equals(credential)) {
            return ResponseEntity.ok(ResponseWrapper.failure("Invalid credentials provided"));
        }

        String accessKey = UUID.randomUUID().toString().replace("-", "");
        String roleType = existingUser.getPermissionLevel();
        String sessionPayload = sessionService.persistSession(existingUser.getId(), accountName, roleType, accessKey);

        return ResponseEntity.ok(ResponseWrapper.success(Map.of("sessionKey", sessionPayload)));
    }
}

package com.tutoring.platform.security;

import com.tutoring.platform.entity.SessionRecord;
import com.tutoring.platform.service.SessionRepository;
import org.springframework.stereotype.Service;

import java.time.Instant;
import java.time.temporal.ChronoUnit;

@Service
public class SessionManager {

    private final SessionRepository sessionRepo;

    public SessionManager(SessionRepository sessionRepo) {
        this.sessionRepo = sessionRepo;
    }

    public String persistSession(Long userRef, String userName, String roleType, String accessKey) {
        Instant expiration = Instant.now().plus(1, ChronoUnit.HOURS);
        SessionRecord existing = sessionRepo.findByUserRefAndRole(userRef, roleType);

        if (existing != null) {
            existing.setAccessKey(accessKey);
            existing.setExpiresAt(expiration);
            sessionRepo.updateById(existing);
        } else {
            SessionRecord newRecord = new SessionRecord();
            newRecord.setUserRefId(userRef);
            newRecord.setUserName(userName);
            newRecord.setRoleType(roleType);
            newRecord.setAccessKey(accessKey);
            newRecord.setExpiresAt(expiration);
            sessionRepo.insert(newRecord);
        }
        return accessKey;
    }
}

package com.tutoring.platform.security;

import com.tutoring.platform.entity.SessionRecord;
import com.tutoring.platform.service.SessionRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.HandlerMethod;

import java.io.PrintWriter;

@Component
public class AccessControlFilter implements HandlerInterceptor {

    private static final String HEADER_KEY = "Authorization-Token";
    private final SessionRepository sessionRepo;

    public AccessControlFilter(SessionRepository sessionRepo) {
        this.sessionRepo = sessionRepo;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        configureCors(response, request);

        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return false;
        }

        boolean isPublic = handler instanceof HandlerMethod method &&
                method.getMethod().isAnnotationPresent(PublicEndpoint.class);

        if (isPublic) return true;

        String token = request.getHeader(HEADER_KEY);
        if (token == null || token.isBlank()) {
            rejectRequest(response);
            return false;
        }

        SessionRecord session = sessionRepo.findByAccessKey(token);
        if (session == null || session.getExpiresAt().isBefore(Instant.now())) {
            rejectRequest(response);
            return false;
        }

        request.getSession().setAttribute("currentUserId", session.getUserRefId());
        request.getSession().setAttribute("currentRole", session.getRoleType());
        return true;
    }

    private void configureCors(HttpServletResponse response, HttpServletRequest request) {
        response.setHeader("Access-Control-Allow-Origin", request.getHeader("Origin"));
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "Authorization-Token, Content-Type, Accept");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Max-Age", "3600");
    }

    private void rejectRequest(HttpServletResponse response) throws Exception {
        response.setContentType("application/json");
        response.setCharacterEncoding("UTF-8");
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        try (PrintWriter writer = response.getWriter()) {
            writer.write("{\"status\": 401, \"message\": \"Authentication required\"}");
        }
    }
}

Database Schema for Session Tracking

The persistence layer stores active sessions alongside expiration timestamps to enforce automatic logout. The schema avoids plaintext credential storage and focuses strictly on session metadata.

-- ----------------------------
-- Table structure for auth_sessions
-- ----------------------------
DROP TABLE IF EXISTS `auth_sessions`;
CREATE TABLE `auth_sessions` (
  `session_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT 'Primary Key',
  `user_ref_id` BIGINT(20) NOT NULL COMMENT 'Associated User Identifier',
  `account_name` VARCHAR(128) NOT NULL COMMENT 'Display Name',
  `role_type` VARCHAR(64) DEFAULT NULL COMMENT 'Permission Tier',
  `access_key` VARCHAR(255) NOT NULL COMMENT 'Unique Session Token',
  `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Activation Timestamp',
  `expires_at` TIMESTAMP NOT NULL DEFAULT '0000-00-00 00:00:00' COMMENT 'Invalidation Timestamp',
  PRIMARY KEY (`session_id`) USING BTREE,
  KEY `idx_user_ref` (`user_ref_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='Active Session Registry';

-- ----------------------------
-- Sample Records
-- ----------------------------
INSERT INTO `auth_sessions` VALUES 
(1, 101, 'instructor_alpha', 'INSTRUCTOR', 'a8f3k92m1x0p5v7n4q2r8s6t0u4w9y1z', '2023-10-12 09:15:00', '2023-10-12 10:15:00'),
(2, 204, 'student_beta', 'LEARNER', 'b7g4j83n2y1q6w8o5r3s9t1v5x0z2a4c', '2023-10-12 10:30:00', '2023-10-12 11:30:00'),
(3, 307, 'admin_center', 'ADMIN', 'c6h5k94o3z2r7x9p6s4t0u2w6y1a3b5d', '2023-10-12 11:00:00', '2023-10-12 12:00:00');

Quality Assurance and Functional Validation

System validation follows a black-box testing methodology, focusing on input boundaries, mandatory field enforcement, and role-based access control. Test scenarios simulate real-world interactions to verify logic flow, error handling, and data integrity without exposing internal implementation details.

Authentication Validation Matrix

Input Configuration Expected Behavior Observed Outcome Validation Status
Account: admin01 / Credential: valid_pass / Token: correct Session initialized successfully Session initialized successfully Match
Account: admin01 / Credential: wrong_pass / Token: correct Access denied prompt Access denied prompt Match
Account: admin01 / Credential: valid_pass / Token: invalid Challenge verification failed Challenge verification failed Match
Account: [Empty] / Credential: valid_pass / Token: correct Field validation error Field validation error Match
Account: admin01 / Credential: [Empty] / Token: correct Credential validation error Credential validation error Match

Account Management Validation Matrix

Operation Expected Behavior Observed Outcome Validation Status
Create new record with complete fields Record persisted and visible in grid Record persisted and visible in grid Match
Modify existing record attributes Update reflected immediately Update reflected immediately Match
Trigger deletion workflow Confirmation prompt followed by removal Confirmation prompt followed by removal Match
Submit creation form with missing identifier Non-null constraint warning Non-null constraint warning Match
Attempt duplicate identifier entry Uniqueness violation alert Uniqueness violation alert Match

Functional testing confirms that boundary conditions, mandatory validations, and role permisions operate as specified. The testing framework isolates UI interactions from back end logic, ensuring that data flow remains consistent across different operational paths. By systematically exercising positive and negative scenarios, the platform demonstrates stable execution under typical usage patterns.

Tags: Spring Boot

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

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