University Laboratory Resource Management System with Spring Boot, Vue, and UniApp

This system provides a unified platform for managing laboratory assets, booking schedules, equipment maintenance logs, and user permissions across academic institutions. Built with a modern full-stack architecture, it supports web and mobile access through responsive Vue-based dashboards and cross-platform uniapp clients.

Architecture Overview

Backend: Spring Boot 2.7+

The backend leverages Spring Boot’s auto-configuration to minimize boilerplate setup. Embedded Tomcat eliminates external server dependencies, while Spring Security handles role-based authentication and endpoint protection. Key integrations include:

  • Spring Data JPA for type-safe repository operations
  • Spring Validation for declarative input constraints
  • Spring AOP for centralized logging and audit trails
  • RESTful API design following RFC 8288 conventions

Frontend: Vue 3 Composition API

The admin interface uses Vue 3 with the Composition API and Pinia for state management. Components follow atomic design principles—each encapsulates logic, template, and styling. Routing is handled via Vue Router with lazy-loaded modules to reduce initial bundle size. Authentication state is persisted using secure HTTP-only cookies.

Mobile Client: UniApp 3.x

The student and faculty mobile app is built with UniApp, enabling single-codebase deploymnet to iOS, Android, and WeChat Mini Programs. It consumes the same REST API as the web client and implements offline-first strategies using localForage for cached lab availability data.

Authentication & Authorization Flow

A token-based session mechanism replaces traditional cookie sessions. Upon successful login, the backend issues a time-bound JWT containing user ID, role, and scope. Each protected request includes the token in the Authorization header. An interceptor validates tokens against an in-memory cache backed by Redis for low-latency lookups.

Below is a refactored version of the authentication handler that improves security posture and maintainability:

public class AuthInterceptor implements HandlerInterceptor {

    private static final String AUTH_HEADER = "Authorization";
    private static final String BEARER_PREFIX = "Bearer ";

    @Autowired
    private JwtTokenService jwtService;

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

        // Skip preflight requests
        if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
            return true;
        }

        // Allow public endpoints annotated with @PermitAll
        if (isPublicEndpoint(handler)) {
            return true;
        }

        String authHeader = request.getHeader(AUTH_HEADER);
        if (authHeader == null || !authHeader.startsWith(BEARER_PREFIX)) {
            sendUnauthorizedResponse(response, "Missing or malformed token");
            return false;
        }

        String rawToken = authHeader.substring(BEARER_PREFIX.length());
        try {
            JwtPayload payload = jwtService.verifyAndDecode(rawToken);
            SecurityContextHolder.getContext()
                .setAuthentication(new UsernamePasswordAuthenticationToken(
                    payload.getUsername(),
                    null,
                    Collections.singletonList(new SimpleGrantedAuthority(payload.getRole()))
                ));
            return true;
        } catch (JwtException e) {
            sendUnauthorizedResponse(response, "Invalid or expired token");
            return false;
        }
    }

    private void sendUnauthorizedResponse(HttpServletResponse response, String message) throws IOException {
        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        response.setContentType("application/json;charset=UTF-8");
        response.getWriter().write(
            new ObjectMapper().writeValueAsString(Map.of("code", 401, "msg", message))
        );
    }

    private boolean isPublicEndpoint(Object handler) {
        if (handler instanceof HandlerMethod) {
            HandlerMethod method = (HandlerMethod) handler;
            return method.hasMethodAnnotation(PermitAll.class) ||
                   method.getBeanType().isAnnotationPresent(PermitAll.class);
        }
        return false;
    }
}

Database Schema Highlights

The relational model centers around four core entities: lab, equipment, booking, and user_profile. Foreign key constraints enforce referential integrity, and indexes are applied on frequently queried columns such as booking.start_time and equipment.status.

Example DDL for the session tracking table:

CREATE TABLE `session_token` (
  `id` BIGINT PRIMARY KEY AUTO_INCREMENT,
  `user_id` BIGINT NOT NULL,
  `token_hash` CHAR(64) NOT NULL COMMENT 'SHA-256 hash of raw token',
  `role` ENUM('admin', 'instructor', 'student', 'technician') NOT NULL,
  `issued_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `expires_at` DATETIME NOT NULL,
  `last_used` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  INDEX idx_user_role (user_id, role),
  INDEX idx_expires (expires_at),
  FOREIGN KEY (`user_id`) REFERENCES `user_profile`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Functional Testing Strategy

Testing follows a layered approach:

  • Unit tests cover service-layer business logic using JUnit 5 and Mockito
  • Integration tests validate controller endpoints with @WebMvcTest and TestRestTemplate
  • E2E tests simulate user workflows using Cypress for web and Appium for mobile

Key test scenarios include:

  • Concurrent booking attempts for the same lab slot
  • Role-specific UI rendering (e.g., students cannot view maintenance cost fields)
  • Timezone-aware scheduling across multiple campuses
  • Equipment status transitions (available → reserved → under_maintenance → available)

Tags: spring-boot vuejs UniApp JWT MySQL

Posted on Fri, 15 May 2026 06:09:28 +0000 by activeserver