Design and Implementation of a Cross-Platform Online Tutoring Platform Using Spring Boot and Vue.js
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.