Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Constructing a University Second-hand Marketplace Using Vue and Spring Boot

Tech 1

Architecture Overview

Excessive commodity production has led to significant resource surplus, creating a demand for efficient redistribution channels. This university-oriented resale platform enables students to liquidate unused possessions efficiently, minimizing resource wastage.

The application is engineered utilizing IntelliJ IDEA. The client-side leverages Vue.js integrated with the View UI component framework for visual rendering, while the server-side operates on the Spring Boot framework. The full stack comprises a Windows environment, Java runtime, Tomcat servlet container, MySQL data storage, and MyBatis persistence mapping. Adopting a Browser/Server paradigm with decoupled front-end and back-end endpoints, the solution bifurcates into a public-facing storefront and an administrative dashboard, facilitating product listings, acquisitions, interactive messaging, and real-time news feeds.

  • Client Layer: Vue 2.7.10
  • Server Layer: Spring Boot 3.1.10
  • Data Layer: MySQL 8.0.31

Functional Architecture

The marketplace operational requirements encompass six core domains: Administration Hub, Item Registry, Physical Inspection Scheduler, Purchase Request Manager, Dispute Resolution Board, and Bulletin Publisher. These domains function within a web-driven backend interface.

Administration Hub

This domain encapsulates foundational system configurations: credential governance, organizational hierarchy mapping, access control matrices, activity tracking, and centralized asset storage.

Credential governance allows administrators to perform CRUD operations on registered accounts.

Organizational hierarchy mapping defines the internal departmental structure and staff affiliations of the operating entity.

Access control matrices dictate menu-level visibility assigned to distinct user roles.

Activity tracking records authentication events to audit and trace user behavior patterns.

Centralized asset storage manages visual assets like item photographs, warranty certificates, and legal agreements.

Item Registry Module

The item registry curates all listed pre-owned commodities. Administrators possess full CRUD capabilities over item metadata entries.

Physical Inspection Scheduler

When a prospective purchaser shows preliminary intreest, they may request an in-person examination. Registered members initiate viewing requests through the item registry; vendors subsequently receive these requests to coordinate offline meetups.

Purchase Request Manager

A purchase request signifies a buyer's intent to acquire a specific commodity. Purchasers select desired listings, submit a proposed monetary offer alongside optional remarks, and transmit the transaction proposal. Vendors hold the prerogative to endorse or reject the proposal. Endorsement triggers the generation of a formal transaction contract, solidifying the exchange.

Dispute Resolution Board

Transaction processes inevitably generate inquiries and conflicts. The dispute resolution board offers a dedicated space for buyers, vendors, and platform operators to articulate concerns. Participants may post original messages or compose threaded replies to exsiting threads.

Bulletin Publisher

This module disseminates marketplace updates and anti-fraud advisory content, ensuring registered members remain informed about relevant news.

Data Model Specification

Account Entity

The central account entity captures profile attributes: telecommunication number, residential location, electronic mailbox, government identifier, and biological sex classification.

Item Entity

The item entity tracks listing attributes: unique identifier, manufacturer trademark, third-party verification status, visual representation, monetary valuation, vendor identity, vendor telecommunication number, and supplementary annotations.

Inspection Request Entity

The inspection request entity records scheduling attributes: item identifier, manufacturer trademark, visual representation, vendor identity, vendor telecommunication number, scheduled timestamp, designated location, and supplementary annotations.

Purchase Proposal Entity

The purchase proposal entity archives transaction initiation attributes: item identifier, manufacturer trademark, visual representation, vendor identity, vendor telecommunication number, submission timestamp, proposed monetary valuation, and supplementary annotations.

Dispute Thread Entity

The dispute thread entity preserves communication attributes: textual content, author identity, submission timestamp, response status indicator, respondent identity, response timestamp, response textual content, and supplementary annotations.

Bulletin Entity

The bulletin entity stores broadcast attributes: publisher identifier, publisher identity, broadcast content, visual assets, attached documents, publication timestamp, display priority value, and publication status flag.

Core Implementation

Client Credential Verification

@GetMapping("/authenticateClient")
public ServiceResponse<String> authenticateClient(@RequestParam String accountName, @RequestParam String secretKey) {
    LambdaQueryWrapper<Account> filter = new LambdaQueryWrapper<>();
    filter.eq(Account::getUsername, accountName);
    List<Account> matchedAccounts = accountService.list(filter);
    if (matchedAccounts.isEmpty()) {
        return ServiceResponse.failure("Account not registered");
    }
    Account targetAccount = matchedAccounts.get(0);
    boolean secretValid = new BCryptPasswordEncoder().matches(secretKey, targetAccount.getEncodedPassword());
    if (!secretValid) {
        return ServiceResponse.failure("Invalid credentials");
    }
    String jwtToken = tokenGenerator.generateToken(targetAccount.getUsername(), true);
    var authContext = new UsernamePasswordAuthenticationToken(
        new AuthenticatedPrincipal(targetAccount), null, null
    );
    SecurityContextHolder.getContext().setAuthentication(authContext);
    return ServiceResponse.success(jwtToken);
}

Client Enrollment

@GetMapping("/enrollClient")
public ServiceResponse<String> enrollClient(@RequestParam String accountName, @RequestParam String phoneNum, @RequestParam String secretKey) {
    LambdaQueryWrapper<Account> duplicateFilter = new LambdaQueryWrapper<>();
    duplicateFilter.and(wrapper -> wrapper.eq(Account::getUsername, accountName).or().eq(Account::getPhone, phoneNum));
    long existingCount = accountService.count(duplicateFilter);
    if (existingCount > 0) {
        return ServiceResponse.failure("Account or phone already exists");
    }
    Account freshAccount = new Account();
    freshAccount.setUsername(accountName);
    freshAccount.setDisplayName(accountName);
    freshAccount.setPhone(phoneNum);
    freshAccount.setMailbox(phoneNum + "@campus.edu");
    String hashedSecret = new BCryptPasswordEncoder().encode(secretKey);
    freshAccount.setEncodedPassword(hashedSecret);
    freshAccount.setRoleCategory(0);
    accountService.persistOrUpdate(freshAccount);

    LambdaQueryWrapper<SystemRole> roleFilter = new LambdaQueryWrapper<>();
    roleFilter.eq(SystemRole::isDefaultAssignment, true);
    List<SystemRole> defaultRoles = systemRoleService.list(roleFilter);
    for (SystemRole role : defaultRoles) {
        AccountRoleMapping mapping = new AccountRoleMapping();
        mapping.setAccountId(freshAccount.getId());
        mapping.setRoleId(role.getId());
        accountRoleMappingService.persistOrUpdate(mapping);
    }

    String jwtToken = tokenGenerator.generateToken(freshAccount.getUsername(), true);
    var authContext = new UsernamePasswordAuthenticationToken(
        new AuthenticatedPrincipal(freshAccount), null, null
    );
    SecurityContextHolder.getContext().setAuthentication(authContext);
    return ServiceResponse.success(jwtToken);
}

Inspection Request Creation

@PostMapping("/scheduleInspection")
public ServiceResponse<Object> scheduleInspection(@RequestParam String listingId) {
    ResaleItem targetItem = resaleItemService.fetchById(listingId);
    if (targetItem == null) {
        return ServiceResponse.failure("Listing unavailable");
    }
    Account activeUser = authUtility.getActiveUser();
    LambdaQueryWrapper<InspectionRequest> overlapFilter = new LambdaQueryWrapper<>();
    overlapFilter.eq(InspectionRequest::getItemRef, listingId);
    overlapFilter.eq(InspectionRequest::getRequesterRef, activeUser.getId());
    long overlapCount = inspectionRequestService.count(overlapFilter);
    if (overlapCount > 0L) {
        return ServiceResponse.failure("Duplicate inspection request detected");
    }
    InspectionRequest newRequest = new InspectionRequest();
    newRequest.setItemRef(listingId);
    newRequest.setItemTrademark(targetItem.getTrademark());
    newRequest.setValuation(targetItem.getValuation());
    newRequest.setVisualRef(targetItem.getVisualRef());
    newRequest.setVendorRef(targetItem.getVendorId());
    newRequest.setVendorIdentity(targetItem.getVendorIdentity());
    newRequest.setVendorTelecom(targetItem.getVendorTelecom());
    newRequest.setRequesterRef(activeUser.getId());
    newRequest.setRequesterIdentity(activeUser.getDisplayName());
    newRequest.setCreationTimestamp(LocalDateTime.now().toString());
    newRequest.setAgreementStatus(false);
    newRequest.setAgreementTimestamp("");
    inspectionRequestService.persistOrUpdate(newRequest);
    return ServiceResponse.success();
}

Transaction Endorsement

@PostMapping("/endorseTransaction")
public ServiceResponse<Object> endorseTransaction(@RequestParam String transactionRef) {
    InspectionRequest storedRequest = inspectionRequestService.fetchById(transactionRef);
    if (storedRequest == null) {
        return ServiceResponse.failure("Transaction record missing");
    }
    storedRequest.setEndorsementFlag(true);
    inspectionRequestService.persistOrUpdate(storedRequest);
    return ServiceResponse.success();
}

Dispute Thread Retrieval

@GetMapping("/fetchDisputeThreads")
public ServiceResponse<IPage<DisputeThread>> fetchDisputeThreads(@ModelAttribute DisputeThread threadFilter, @ModelAttribute PaginationSpec pageSpec) {
    LambdaQueryWrapper<DisputeThread> queryFilter = new LambdaQueryWrapper<>();
    if (StringUtils.hasText(threadFilter.getRecordDate())) {
        queryFilter.eq(DisputeThread::getRecordDate, threadFilter.getRecordDate());
    }
    if (StringUtils.hasText(threadFilter.getParentThreadRef())) {
        queryFilter.eq(DisputeThread::getParentThreadRef, threadFilter.getParentThreadRef());
    } else {
        queryFilter.eq(DisputeThread::getParentThreadRef, "");
    }
    IPage<DisputeThread> resultPage = disputeThreadService.page(
        PageUtility.convertToPage(pageSpec), queryFilter
    );
    return ServiceResponse.success(resultPage);
}

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.