Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Full-Stack Food Ordering Platform with Vue 3 Frontend and Spring Boot + MyBatis-Plus Backend

Tech May 19 2

Tech Stack Overview

Backend with Spring Boot

Spring Boot simplifies building standalone, production-ready Spring applications by eliminating extensive boilerplate configuration through auto-configuration and "convention over configuration" principles. It embeds web servers like Tomcat, enabling deployment as a single executable JAR, and offers Actuator for runtime monitoring. Starter dependencies streamline integration with security, data persistence, caching, and other enterprise features.

Frontend with Vue 3

Vue 3 is a lightweight, flexible JavaScript framework for building reactive UIs and SPAs. Its core strengths include the Composition API for modular logic reuse, improved performance with the Virtual DOM rewrite, and robust TypeScript support. Vue’s component-based architecture breaks applications into reusable, testable units, and its active community provides extensive plugins, tools, and documentation.

Core Back end Code

Application Bootstrap Class

package com.restaurantorder;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.boot.builder.SpringApplicationBuilder;

@SpringBootApplication
@MapperScan(basePackages = {"com.restaurantorder.persistence.mapper"})
public class RestaurantOrderApp extends SpringBootServletInitializer {

    public static void main(String[] startupArgs) {
        SpringApplication.run(RestaurantOrderApp.class, startupArgs);
    }

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder appBuilder) {
        return appBuilder.sources(RestaurantOrderApp.class);
    }
}

Customer Management Controller

package com.restaurantorder.api.controller;

import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.*;
import javax.servlet.http.HttpServletRequest;

import com.restaurantorder.utils.ValidationHelper;
import org.apache.commons.lang3.StringUtils;
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.restaurantorder.annotation.PublicEndpoint;

import com.restaurantorder.persistence.entity.CustomerEntity;
import com.restaurantorder.persistence.view.CustomerView;

import com.restaurantorder.service.CustomerService;
import com.restaurantorder.service.AuthTokenService;
import com.restaurantorder.utils.PaginationUtil;
import com.restaurantorder.utils.ApiResponse;
import com.restaurantorder.utils.MPQueryUtil;
import java.io.IOException;

@RestController
@RequestMapping("/customer")
public class CustomerController {
    @Autowired
    private CustomerService customerService;

    @Autowired
    private AuthTokenService authTokenService;

    @PublicEndpoint
    @PostMapping("/auth/login")
    public ApiResponse authenticate(String customerUsername, String customerPassword, String captchaInput, HttpServletRequest req) {
        CustomerEntity existingCustomer = customerService.getOne(new QueryWrapper<CustomerEntity>().eq("customer_username", customerUsername));
        if(existingCustomer == null || !existingCustomer.getCustomerPassword().equals(customerPassword)) {
            return ApiResponse.error("Invalid credentials provided");
        }

        String jwtToken = authTokenService.createToken(existingCustomer.getId(), customerUsername, "customer", "registered diner");
        return ApiResponse.success().setData("token", jwtToken);
    }

    @PublicEndpoint
    @PostMapping("/auth/register")
    public ApiResponse signUp(@RequestBody CustomerEntity newCustomer) {
        CustomerEntity duplicateCheck = customerService.getOne(new QueryWrapper<CustomerEntity>().eq("customer_username", newCustomer.getCustomerUsername()));
        if(duplicateCheck != null) {
            return ApiResponse.error("Username already registered");
        }
        newCustomer.setId(System.currentTimeMillis());
        customerService.save(newCustomer);
        return ApiResponse.success();
    }

    @PostMapping("/auth/logout")
    public ApiResponse endSession(HttpServletRequest req) {
        req.getSession().invalidate();
        return ApiResponse.success("Session terminated");
    }

    @GetMapping("/profile/session")
    public ApiResponse fetchSessionProfile(HttpServletRequest req) {
        Long profileId = (Long)req.getSession().getAttribute("activeUserId");
        CustomerEntity currentCustomer = customerService.getById(profileId);
        return ApiResponse.success().setData("profile", currentCustomer);
    }

    @PublicEndpoint
    @PostMapping("/auth/reset-password")
    public ApiResponse resetPassword(String customerUsername, HttpServletRequest req) {
        CustomerEntity targetCustomer = customerService.getOne(new QueryWrapper<CustomerEntity>().eq("customer_username", customerUsername));
        if(targetCustomer == null) {
            return ApiResponse.error("Username not found");
        }
        targetCustomer.setCustomerPassword("tempPass123");
        customerService.updateById(targetCustomer);
        return ApiResponse.success("Password reset to: tempPass123");
    }

    @GetMapping("/admin/list")
    public ApiResponse adminPaginatedList(@RequestParam Map<String, Object> queryParams, CustomerEntity filterCustomer, HttpServletRequest req) {
        QueryWrapper<CustomerEntity> queryWrapper = new QueryWrapper<>();
        PaginationUtil pageData = customerService.queryPage(queryParams, MPQueryUtil.adjustWrapper(queryWrapper, filterCustomer, queryParams));
        return ApiResponse.success().setData("pageData", pageData);
    }

    @PublicEndpoint
    @GetMapping("/public/list")
    public ApiResponse publicPaginatedList(@RequestParam Map<String, Object> queryParams, CustomerEntity filterCustomer, HttpServletRequest req) {
        QueryWrapper<CustomerEntity> queryWrapper = new QueryWrapper<>();
        PaginationUtil pageData = customerService.queryPage(queryParams, MPQueryUtil.adjustWrapper(queryWrapper, filterCustomer, queryParams));
        return ApiResponse.success().setData("pageData", pageData);
    }

    @GetMapping("/all")
    public ApiResponse fetchAllMatching(CustomerEntity filterCustomer) {
        QueryWrapper<CustomerEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.allEq(MPQueryUtil.toPrefixedMap(filterCustomer, "customer"));
        return ApiResponse.success().setData("customerList", customerService.listView(queryWrapper));
    }

    @GetMapping("/search")
    public ApiResponse searchSingle(CustomerEntity filterCustomer) {
        QueryWrapper<CustomerEntity> queryWrapper = new QueryWrapper<>();
        queryWrapper.allEq(MPQueryUtil.toPrefixedMap(filterCustomer, "customer"));
        CustomerView foundCustomer = customerService.getView(queryWrapper);
        return ApiResponse.success("Customer found").setData("customer", foundCustomer);
    }

    @GetMapping("/admin/detail/{id}")
    public ApiResponse adminFetchDetail(@PathVariable("id") Long customerId) {
        CustomerEntity customer = customerService.getById(customerId);
        return ApiResponse.success().setData("customer", customer);
    }

    @PublicEndpoint
    @GetMapping("/public/detail/{id}")
    public ApiResponse publicFetchDetail(@PathVariable("id") Long customerId) {
        CustomerEntity customer = customerService.getById(customerId);
        return ApiResponse.success().setData("customer", customer);
    }

    @PostMapping("/admin/save")
    public ApiResponse adminAddCustomer(@RequestBody CustomerEntity newCustomer, HttpServletRequest req) {
        if(customerService.count(new QueryWrapper<CustomerEntity>().eq("customer_username", newCustomer.getCustomerUsername())) > 0) {
            return ApiResponse.error("Username already exists");
        }
        newCustomer.setId(System.currentTimeMillis() + new Double(Math.floor(Math.random() * 2000)).longValue());
        customerService.save(newCustomer);
        return ApiResponse.success();
    }

    @PostMapping("/public/add")
    public ApiResponse publicAddCustomer(@RequestBody CustomerEntity newCustomer, HttpServletRequest req) {
        if(customerService.count(new QueryWrapper<CustomerEntity>().eq("customer_username", newCustomer.getCustomerUsername())) > 0) {
            return ApiResponse.error("Username already exists");
        }
        newCustomer.setId(System.currentTimeMillis());
        customerService.save(newCustomer);
        return ApiResponse.success();
    }

    @PutMapping("/update")
    @Transactional
    public ApiResponse modifyCustomer(@RequestBody CustomerEntity updatedCustomer, HttpServletRequest req) {
        if(customerService.count(new QueryWrapper<CustomerEntity>().ne("id", updatedCustomer.getId()).eq("customer_username", updatedCustomer.getCustomerUsername())) > 0) {
            return ApiResponse.error("Username already in use by another account");
        }
        customerService.updateById(updatedCustomer);
        return ApiResponse.success();
    }

    @DeleteMapping("/delete")
    public ApiResponse removeCustomers(@RequestBody Long[] customerIds) {
        customerService.removeByIds(Arrays.asList(customerIds));
        return ApiResponse.success();
    }
}

System Testing

Core Testing Objectives

System testing verifies that the platform meets functional and non-functional requirements, identifies and resolves defects before deployment, and ensures a smooth end-user experience. Testing scenarios are designed from the diner and restaurant staff perspectives to cover realistic workflows.

Functional Testing Approach

Black-box testing is the primary method, validating each feature through clicks, boundary value inputs, and required/optional field checks. Test cases are written to cover normal, edge, and error conditions.

For example, login testing verifies that only valid credentials paired with the correct role grant access, invalid inputs trigger clear error messages, and CAPTCHA (if enabled) blocks automated attempts.

Testing Outcomes

After executing all test cases, the platform demonstrates stable performance, correct feature implementation, and adherence to initial design requirements. All identified defects have been resolved, and workflows align with end-user expectations.

Tags: Spring Boot

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...

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...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.