Constructing a Modern Bookstore E-Commerce Platform with Spring Boot and Vue.js

This implementation details a full-stack bookstore application utilizing Spring Boot for backend services and Vue.js for the frontend interface. The system supports dual user roles: administrators manage product catalogs and order fulfillment, while customers browse merchandise and process purchases. The architecture employs RESTful APIs for data exchange between client and server components, with MySQL providing persistent storage for inventory and transaction data.

Technical Environment Configuration

Development leverages Java 11 with Spring Boot 2.7 for backend services, featuring automatic dependency resolution through Maven 3.8. The MySQL 8.0 database handles data persistence with InnoDB engine optimizations. Frontend components are built using Vue 3 with Vite build tooling. Testing occurs in a local Tomcat 9.0 servlet container, with development conducted in IntelliJ IDEA using JDK 17. Authentication follows JWT standards with role-based access control for administrative functions.

Media Handling Service Implementation

The following controller manages resource uploads and retrievals for product imagery and user assets. Files are stored in versioned directories under static resources with UUID-based naming to prevent collisions. Configuration parameters dynamically update system settings when processing profile images.

package com.bookstore.api;

import java.io.IOException;
import java.nio.file.*;
import java.util.*;
import java.util.stream.Collectors;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.http.*;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import com.bookstore.model.SystemConfig;
import com.bookstore.service.ConfigurationService;
import com.bookstore.util.ApiResponse;

@RestController
@RequestMapping("/assets")
public class MediaController {
    @Autowired
    private ConfigurationService configService;
    private static final String STORAGE_ROOT = "classpath:static/resources/";

    @PostMapping("/upload")
    @IgnoreAuth
    public ApiResponse uploadResource(
        @RequestParam("content") MultipartFile resourceFile,
        @RequestParam(required = false) String category
    ) throws IOException {
        if (resourceFile.isEmpty()) {
            throw new IllegalArgumentException("Empty resource submission detected");
        }

        String extension = extractExtension(resourceFile.getOriginalFilename());
        Path storagePath = createStorageDirectory();
        String uniqueName = generateUniqueFilename(extension);
        Path targetPath = storagePath.resolve(uniqueName);

        Files.copy(resourceFile.getInputStream(), targetPath, StandardCopyOption.REPLACE_EXISTING);

        if ("profile".equals(category)) {
            updateProfileConfiguration(uniqueName);
        }

        return ApiResponse.success(Collections.singletonMap("path", uniqueName));
    }

    private String extractExtension(String filename) {
        return Optional.ofNullable(filename)
            .filter(f -> f.contains("."))
            .map(f -> f.substring(filename.lastIndexOf('.') + 1))
            .orElse("jpg");
    }

    private Path createStorageDirectory() throws IOException {
        Path base = Paths.get(STORAGE_ROOT).toAbsolutePath();
        if (!Files.exists(base)) {
            Files.createDirectories(base);
        }
        return base;
    }

    private String generateUniqueFilename(String extension) {
        return UUID.randomUUID() + "." + extension;
    }

    private void updateProfileConfiguration(String filename) {
        SystemConfig config = configService.findByName("avatar_path")
            .orElseGet(SystemConfig::new);
        
        config.setName("avatar_path");
        config.setValue(filename);
        configService.saveConfiguration(config);
    }

    @GetMapping("/{filename:.+}")
    @IgnoreAuth
    public ResponseEntity<Resource> fetchResource(@PathVariable String filename) {
        try {
            Path filePath = Paths.get(STORAGE_ROOT, filename).normalize();
            Resource fileResource = new UrlResource(filePath.toUri());
            
            if (!fileResource.exists()) {
                return ResponseEntity.notFound().build();
            }
            
            String contentType = "application/octet-stream";
            try {
                contentType = Files.probeContentType(filePath);
            } catch (IOException ignored) {}
            
            return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, 
                        "attachment; filename=\"" + fileResource.getFilename() + "\"")
                .body(fileResource);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
        }
    }
}

Discussion Forum Module

The discussion system implements hierarchical thread structures with recursive reply resolution. Access controls restrict administrative operations while enabling customer participation. The service employs optimized query patterns for paginated content retrieval and maintains thread integrity through transactional boundaries during modifications.

package com.bookstore.api;

import java.util.*;
import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.bookstore.model.Discussion;
import com.bookstore.model.DiscussionView;
import com.bookstore.service.DiscussionService;
import com.bookstore.util.ApiResponse;
import com.bookstore.util.PaginationUtil;

@RestController
@RequestMapping("/discussions")
public class DiscussionController {
    @Autowired
    private DiscussionService threadService;

    @GetMapping
    public ApiResponse getThreads(
        @RequestParam Map<String, Object> params,
        Discussion thread,
        HttpServletRequest request
    ) {
        applyRoleFilter(thread, request);
        QueryWrapper<Discussion> query = buildQuery(thread, params);
        return ApiResponse.success(PaginationUtil.paginate(params, 
            () -> threadService.list(query)));
    }

    @GetMapping("/{id}")
    @IgnoreAuth
    public ApiResponse getThread(@PathVariable Long id) {
        Discussion thread = threadService.getById(id);
        resolveReplies(thread);
        return ApiResponse.success(thread);
    }

    @PostMapping
    public ApiResponse createThread(
        @RequestBody Discussion thread,
        HttpServletRequest request
    ) {
        thread.setId(generateUniqueId());
        thread.setAuthorId((Long) request.getSession().getAttribute("userId"));
        threadService.save(thread);
        return ApiResponse.success();
    }

    @PutMapping
    @Transactional
    public ApiResponse updateThread(@RequestBody Discussion thread) {
        threadService.updateById(thread);
        return ApiResponse.success();
    }

    @DeleteMapping
    public ApiResponse deleteThreads(@RequestBody List<Long> ids) {
        threadService.removeByIds(ids);
        return ApiResponse.success();
    }

    private void applyRoleFilter(Discussion thread, HttpServletRequest request) {
        if (!"admin".equals(request.getSession().getAttribute("role"))) {
            thread.setAuthorId((Long) request.getSession().getAttribute("userId"));
        }
    }

    private QueryWrapper<Discussion> buildQuery(Discussion thread, Map<String, Object> params) {
        QueryWrapper<Discussion> wrapper = new QueryWrapper<>();
        wrapper.eq(thread.getAuthorId() != null, "author_id", thread.getAuthorId());
        wrapper.like(StringUtils.isNotBlank(thread.getTitle()), "title", thread.getTitle());
        return wrapper;
    }

    private void resolveReplies(Discussion thread) {
        List<Discussion> replies = threadService.findByParent(thread.getId());
        if (replies.isEmpty()) return;
        
        thread.setReplies(replies);
        replies.forEach(this::resolveReplies);
    }

    private Long generateUniqueId() {
        return System.currentTimeMillis() + new Random().nextInt(1000);
    }
}

Data base Integration Strategy

MySQL 8.0 serves as the primary data store with connection pooling managed through HikariCP. The schema implements normalized tables for products, orders, and user interactions with foreign key constraints ensuring referential integrity. Indexing strategies prioritize query performance for inventory searches and order history lookups, while transaction management guarantees atomic order processing operations. Database migrations are handled through Flyway for version-controlled schema evolution.

Tags: spring-boot Vue.js MySQL java rest-api

Posted on Fri, 28 Aug 2026 16:28:27 +0000 by George W. Bush