Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Building a Curated Chinese Animation Portal: Spring Boot and Vue.js Architecture Guide

Tech 1

System Architecture Overview

The platform operates on a decoupled Client/Server model utilizing a Browser/Server topology. The frontend layer, powered by Vue.js, handles dynamic rendering, state management, and user interaction flows. The backend leverages Spring Boot to expose RESTful endpoints for data processing, business logic execution, and database orchestration. MySQL serves as the relational storage engine, optimized for high-throughput read/write operations typical in content aggregation platforms.

Core functional domains include an interactive community space for content discovery, comment threads, and engagement metrics (views, likes), alongside an administrative console for role-based access control, user moderation, and analytics tracking.

Module Workflow Design

Authentication Sequence: Upon submitting credentials, the frontend transmits a JSON payload to the /api/auth/login endpoint. The backend validates inputs against the account repository, verifies cryptographic signatures, checks account status flags, and issues a signed session token. This token is cached client-side and appended to subsequent HTTP headers for authorized requests.

Content Submission Sequence: Authenticated users initiate post creation via the dashboard. The frontend serializes form data and metadata (tags, categories, media assets). The API gateway intercepts the request, performs input sanitization, delegates to the service layer for persistence mapping, and returns a structured success response containing the generated resource identifier.

Relational Data Modeling

The database schema normalizes entities in to five primary tables to support efficient querying and referential integrity:

Table Name Key Fields Description
user_accounts id, username, email, password_hash, status, created_at Stores credential data, verification states, and lifecycle timestamps.
role_definitions id, role_name, description Defines permission tiers (e.g., Viewer, Editor, Administrator).
permission_mappings id, role_id, menu_path, access_level Associates roles with navigable routes and action capabilities.
content_articles id, title, summary, category, author_id, view_count, like_count, audit_status Central repository for posts, including moderation flags and engagement counters.
interaction_comments id, article_id, user_id, content_body, parent_id, timestamp Manages threaded discussions linked to specific articles.

Configuration Layer

Server connectivity and framework behavior are externalized into YAML configuration files. Connection pooling, character encoding, and multipart upload limits are explicitly defined.

server:
  port: 8080
  servlet:
    context-path: /api/v1
  tomcat:
    uri-encoding: UTF-8

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/animation_hub?useSSL=false&serverTimezone=UTC&characterEncoding=utf8
    username: ${DB_USER}
    password: ${DB_PASS}

  jackson:
    time-zone: UTC

  servlet:
    multipart:
      max-file-size: 15MB
      max-request-size: 15MB

mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  global-config:
    db-config:
      id-type: ASSIGN_ID
      logic-delete-field: deleted
      logic-delete-value: 1
      logic-not-delete-value: 0
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

Authentication & Session Management

The core authentication controller handles registration, credential verification, token issuance, and session termination. Logic prioritizes stateless validation and secure credential hashing.

@RestController
@RequestMapping("/api/auth")
public class AuthenticationController {

    private final AccountService accountService;
    private final TokenManager tokenManager;
    private final RoleResolver roleResolver;

    public AuthenticationController(AccountService accountService, 
                                    TokenManager tokenManager, 
                                    RoleResolver roleResolver) {
        this.accountService = accountService;
        this.tokenManager = tokenManager;
        this.roleResolver = roleResolver;
    }

    @PostMapping("/register")
    public ResponseEntity<ApiResponse> register(@RequestBody UserRegistrationRequest req) {
        boolean exists = accountService.findByUsername(req.getUsername()).isPresent();
        if (exists) {
            return ResponseEntity.status(HttpStatus.CONFLICT).body(ApiResponse.error("Username already taken"));
        }
        
        String hashedPass = SecurityUtils.encodePassword(req.getPassword());
        UserEntity newUser = UserEntity.builder()
                .username(req.getUsername())
                .passwordHash(hashedPass)
                .status(STATUS_PENDING)
                .build();
                
        accountService.save(newUser);
        return ResponseEntity.ok(ApiResponse.success("Registration successful"));
    }

    @PostMapping("/login")
    public ResponseEntity<ApiResponse> authenticate(@RequestBody LoginPayload payload) {
        Optional<UserEntity> optionalUser = accountService.findByCredentials(payload.getUsername(), payload.getEmail());
        
        if (optionalUser.isEmpty()) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error("Invalid credentials"));
        }

        UserEntity user = optionalUser.get();
        if (!SecurityUtils.matchesPassword(payload.getPassword(), user.getPasswordHash())) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error("Incorrect password"));
        }
        if (user.getStatus().equals(STATUS_BANNED)) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error("Account suspended"));
        }

        UserRole role = roleResolver.resolveByUserId(user.getId());
        if (!role.isActive()) {
            return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ApiResponse.error("Role approval pending"));
        }

        String sessionToken = UUID.randomUUID().toString().replace("-", "");
        tokenManager.persist(sessionToken, user.getId());

        Map<String, Object> responsePayload = new HashMap<>();
        responsePayload.put("userId", user.getId());
        responsePayload.put("username", user.getUsername());
        responsePayload.put("sessionToken", sessionToken);
        responsePayload.put("role", role.getName());

        return ResponseEntity.ok(ApiResponse.success(responsePayload));
    }

    @GetMapping("/verify")
    public ResponseEntity<ApiResponse> verifySession(HttpServletRequest request) {
        String token = extractTokenFromHeader(request);
        Integer userId = tokenManager.validate(token);
        
        if (userId == null) {
            return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ApiResponse.error("Expired or invalid session"));
        }
        
        UserEntity user = accountService.findById(userId);
        return ResponseEntity.ok(ApiResponse.success(buildPublicProfile(user)));
    }

    @PostMapping("/logout")
    public ResponseEntity<ApiResponse> terminateSession(HttpServletRequest request) {
        String token = extractTokenFromHeader(request);
        tokenManager.invalidate(token);
        return ResponseEntity.ok(ApiResponse.success("Session terminated"));
    }

    private String extractTokenFromHeader(HttpServletRequest req) {
        String authHeader = req.getHeader("Authorization");
        return (authHeader != null && authHeader.startsWith("Bearer ")) 
               ? authHeader.substring(7) : null;
    }
}

Cryptographic Helper Utilities

Secure string transformation relies on standardized digest algorithms. The following utility abstracts hexadecimal conversion and stream-based hashing operations.

public final class CryptoHashHelper {

    private static final char[] HEX_CHARS = "0123456789abcdef".toCharArray();

    private CryptoHashHelper() {}

    public static String generateDigest(String input) {
        if (input == null || input.isBlank()) {
            throw new IllegalArgumentException("Input cannot be null or empty");
        }
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hashBytes = digest.digest(input.getBytes(StandardCharsets.UTF_8));
            return bytesToHex(hashBytes);
        } catch (NoSuchAlgorithmException e) {
            throw new RuntimeException("Missing cryptographic provider", e);
        }
    }

    public static String hashFile(String filePath) {
        Path path = Paths.get(filePath);
        if (!Files.exists(path)) return "";

        try (InputStream fis = Files.newInputStream(path)) {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                digest.update(buffer, 0, bytesRead);
            }
            return bytesToHex(digest.digest());
        } catch (IOException | NoSuchAlgorithmException e) {
            throw new RuntimeException("Failed to compute file hash", e);
        }
    }

    private static String bytesToHex(byte[] bytes) {
        StringBuilder hexBuilder = new StringBuilder(bytes.length * 2);
        for (byte b : bytes) {
            hexBuilder.append(HEX_CHARS[(b >> 4) & 0x0F]);
            hexBuilder.append(HEX_CHARS[b & 0x0F]);
        }
        return hexBuilder.toString();
    }
}

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.