Full-Stack Food Ordering Platform with Vue 3 Frontend and Spring Boot + MyBatis-Plus Backend
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.