Given the widespread adoption of WeChat, the client side of this platform is developed as a WeChat Mini Program to align with real-world usage scenarios.
1. Implementing WeChat Login (Controller Layer)
@PostMapping("/wechat-login")
@ApiOperation("WeChat user authentication")
public Result<AuthResponseVO> authenticate(@RequestBody WechatAuthDTO authDTO) {
log.info("WeChat login attempt with code: {}", authDTO.getWxCode());
// Authenticate user via WeChat
AppUser user = userAccountService.handleWechatAuth(authDTO);
// Generate JWT token for the user
Map<String, Object> claims = new HashMap<>();
claims.put(JwtClaimsConstant.USER_KEY, user.getUserId());
String jwtToken = JwtUtil.generateToken(jwtConfig.getSecret(), jwtConfig.getExpiration(), claims);
AuthResponseVO response = AuthResponseVO.builder()
.userId(user.getUserId())
.wechatOpenId(user.getWechatOpenId())
.accessToken(jwtToken)
.build();
return Result.success(response);
}
2. Backend Processing and User Registration (Service Layer)
The Java back end intercepts the login request from the Mini Program. It validates the user; if the user is new, the system persists the data and returns the user details to the client.
public AppUser handleWechatAuth(WechatAuthDTO authDTO) {
String openId = retrieveOpenId(authDTO.getWxCode());
// Validate OpenID retrieval
if (openId == null) {
throw new AuthenticationException(MessageConstant.AUTH_FAILED);
}
// Check if user exists
AppUser existingUser = userRepository.findByOpenId(openId);
if (existingUser == null) {
// Auto-register new user
existingUser = AppUser.builder()
.wechatOpenId(openId)
.registeredAt(LocalDateTime.now())
.build();
userRepository.saveNewUser(existingUser);
}
return existingUser;
}
3. Database Persistence (MySQL)
<insert id="saveNewUser" useGeneratedKeys="true" keyProperty="userId">
INSERT INTO app_user (wechat_open_id, full_name, mobile, gender, identity_no, profile_pic, registered_at)
VALUES (#{wechatOpenId}, #{fullName}, #{mobile}, #{gender}, #{identityNo}, #{profilePic}, #{registeredAt})
</insert>
4. Shopping Cart Functionality
The virtual shopping cart mirrors a physical one, storing data in a dedicated table for standard CRUD operations.
When adding an item, the system checks for existing records. If found, it increments the quantity. Otherwise, it identifeis whether the item is a single dish or a combo meal and inserts a new record accordingly.
API Endpoint
// Add item to cart
@PostMapping("/cart/add")
public Result addItem(@RequestBody CartItemDTO itemDTO) {
cartService.processCartAddition(itemDTO);
return Result.success();
}
Service Implementation
/**
* Adds an item to the shopping cart.
*/
public void processCartAddition(CartItemDTO itemDTO) {
CartItem cartItem = new CartItem();
BeanUtils.copyProperties(itemDTO, cartItem);
Long currentUserId = UserContext.getActiveUserId();
cartItem.setUserId(currentUserId);
List<CartItem> existingItems = cartMapper.findItems(cartItem);
if (existingItems != null && !existingItems.isEmpty()) {
// Update quantity if item exists
CartItem currentItem = existingItems.get(0);
currentItem.setQuantity(currentItem.getQuantity() + 1);
cartMapper.updateQuantity(currentItem);
} else {
// Insert new item
Long dishId = itemDTO.getDishId();
if (dishId != null) {
Dish dishDetails = dishRepo.findById(dishId);
cartItem.setItemName(dishDetails.getName());
cartItem.setImageUrl(dishDetails.getImage());
cartItem.setPrice(dishDetails.getCost());
} else {
Long comboId = itemDTO.getComboId();
Combo comboDetails = comboRepo.findById(comboId);
cartItem.setItemName(comboDetails.getName());
cartItem.setImageUrl(comboDetails.getImage());
cartItem.setPrice(comboDetails.getCost());
}
cartItem.setQuantity(1);
cartItem.setAddedAt(LocalDateTime.now());
cartMapper.insertItem(cartItem);
}
}
Data Access Layer
/**
* Dynamic query for cart items
*/
List<CartItem> findItems(CartItem cartItem);
/**
* Update item quantity
*/
@Update("UPDATE user_cart SET quantity = #{quantity} WHERE cart_id = #{cartId}")
void updateQuantity(CartItem cartItem);
/**
* Insert new cart item
*/
@Insert("INSERT INTO user_cart(item_name, image_url, user_id, dish_id, combo_id, flavor, price, added_at) " +
"VALUES (#{itemName}, #{imageUrl}, #{userId}, #{dishId}, #{comboId}, #{flavor}, #{price}, #{addedAt})")
void insertItem(CartItem cartItem);
5. Viewing and Clearing the Cart
Retrieving or clearing the cart involves simple GET and DELETE requests to query or remove data.
// Retrieve cart contents
@GetMapping("/cart/view")
public Result<List<CartItem>> viewCart() {
List<CartItem> contents = cartService.getCartContents();
return Result.success(contents);
}
// Empty the cart
@DeleteMapping("/cart/empty")
public Result emptyCart() {
cartService.clearUserCart();
return Result.success();
}
/**
* Fetch current cart contents
*/
public List<CartItem> getCartContents() {
Long currentUserId = UserContext.getActiveUserId();
CartItem query = CartItem.builder().userId(currentUserId).build();
return cartMapper.findItems(query);
}
/**
* Clear all items for the user
*/
public void clearUserCart() {
Long currentUserId = UserContext.getActiveUserId();
cartMapper.removeAllByUser(currentUserId);
}