文章目录
背景
有朋友让我给他做一个在线预定点餐的小程序系统。功能涵盖餐饮分类;具体商品包含规格;商家联系方式;地址,支持导航;动态分享;我的订单;购物车;自提或者外送;线上自助付费(可输入金额自己付费);客服(接入微信客服);
需求分析
为了节省时间,这里省略了脑图,直接将所提需求提炼出功能点,具体分为小程序功能和后台管理端的功能概要,如下:
小程序功能
支付模块
接入微信支付,实现用户在小程序内完成付款
客服
完成用户在小程序内容联系商家的
线上自助付费
完成支付模块既可以完成对应的线上自助付款功能
自提或者外送
自提或者外送的切换功能,用户再下单的时候,可以自助选择是到商家自提,还是商家配送(单独收取一定的配送费)
购物车
可展示保留用户所选购的商品
我的订单
可以查看当前用户所下过的订单,并保留信息
动态分享
展示商家所发布的动态信息;让用户更好的掌握商户的一些公开活动
地址管理
完成用户的地址管理, 可以编辑删除的,为了更好的支持外送功能
菜品信息
展示当先店铺的一些菜品类别及菜品的名称及规格
菜品分类
菜品分类,比如酒水、甜品、主食、汤等等
菜品及规格
菜品信息,对应每个分类可以快速导菜品信息,并可以选择菜品的规格,比如甜品可分为大份、小份等等
后台管理系统
支付管理模块
微信支付参数的可配置(这里可能不需要,卸载配置文件中即可,或者接入其他三方、四方支付系统);
自助付费接入支付即可实现手动输入金额完成付款;
系统配置
营业时间配置
商家的营业时间通过这里配置
客服配置
接入微信客服插件即可绑定商家微信
自提或者外送配置
可以配置自提是否有额外费用(目前没有配置这个)
外送的运费:外送功能可根据实际情况是否接入三方配送,目前选择自己配送收取一定数额的配送费;
购物车管理
购物车统计当前所选购的商品并展示;
是否在一定时间后清理购物车,减少数据库存储的压力(目前没有这个配置)
订单管理
订单管理可以查看当前用户所下订单的支付情况,并包含了退款功能;
动态分享管理
动态分享是发布和管理当前商家所发布的动态信息
用户地址管理
可管理用户的地址信息,避免一些垃圾地址的出现(这个功能可以在确定,目前没有实现)
商家店铺管理
配置商家的店铺信息;
包括:店铺名称、联系方式、地址包含了经纬度,可导航到此处、logo;
菜品信息管理
管理当前店铺的菜品分类及分类下菜品及酒水的信息;
技术实现
为了快速上线,减少成本且适用于普通小商户的需求,避开华而不实微服务架构。选择单体架构,后期也可以通过分布式服务支撑用户量的增加;
小程序端
uniapp+vue3
管理端web系统
vue3
服务端
jdk17+springboot3+springsecurity+redis+mysql8+rocketMq(待定,前期可以不用)
数据库设计
主要表结构
代码实现
提供部分关键代码实现
多层次价格计算逻辑
// 价格计算服务
@Service
public class PriceCalculationService {
public CalculateResult calculateFinalPrice(Long tenantId, String userId,
Long productId, List<Long> specItemIds) {
CalculateResult result = new CalculateResult();
// 1. 计算基础价格 + 规格价格
BigDecimal basePrice = productService.getBasePrice(productId);
BigDecimal specPrice = specService.calculateSpecPrice(specItemIds);
result.setOriginalPrice(basePrice.add(specPrice));
// 2. 计算会员价格
BigDecimal memberPrice = memberService.getMemberPrice(tenantId, userId, productId);
if (memberPrice != null) {
result.setMemberPrice(memberPrice.add(specPrice));
}
// 3. 计算活动价格
BigDecimal promotionPrice = promotionService.calculatePromotionPrice(
tenantId, productId, result.getCurrentPrice(), 1); // 数量为1
result.setFinalPrice(promotionPrice);
result.setPriceType(getPriceType(memberPrice, promotionPrice));
return result;
}
}
优惠券使用逻辑
// 优惠券核销服务
@Service
public class CouponService {
public CouponVerifyResult verifyCoupon(String userCouponId, String userId,
BigDecimal orderAmount, List<Long> productIds) {
UserCoupon userCoupon = userCouponMapper.selectById(userCouponId);
// 验证基本条件
if (!userCoupon.getUserId().equals(userId)) {
return CouponVerifyResult.error("优惠券不属于当前用户");
}
if (userCoupon.getStatus() != CouponStatus.UNUSED) {
return CouponVerifyResult.error("优惠券已使用或已过期");
}
if (new Date().after(userCoupon.getCoupon().getEndTime())) {
return CouponVerifyResult.error("优惠券已过期");
}
// 验证使用条件
Coupon coupon = userCoupon.getCoupon();
if (coupon.getConditionValue().compareTo(BigDecimal.ZERO) > 0
&& orderAmount.compareTo(coupon.getConditionValue()) < 0) {
return CouponVerifyResult.error("未达到优惠券使用门槛");
}
// 计算优惠金额
BigDecimal discountAmount = calculateDiscountAmount(coupon, orderAmount);
return CouponVerifyResult.success(discountAmount, coupon);
}
private BigDecimal calculateDiscountAmount(Coupon coupon, BigDecimal orderAmount) {
switch (coupon.getType()) {
case DISCOUNT: // 折扣券
return orderAmount.multiply(
BigDecimal.ONE.subtract(coupon.getDiscountValue().divide(BigDecimal.valueOf(100)))
);
case REDUCE: // 满减券
return coupon.getDiscountValue();
case NO_THRESHOLD: // 无门槛券
return coupon.getDiscountValue().min(orderAmount); // 不能超过订单金额
default:
return BigDecimal.ZERO;
}
}
}
购物车逻辑
添加商品到购物车逻辑
@Service
public class CartService {
public CartItemVO addToCart(AddToCartRequest request) {
// 1. 获取或创建购物车
Cart cart = getOrCreateCart(request.getTenantId(), request.getUserId());
// 2. 验证商品和规格
Product product = validateProduct(request.getProductId(), request.getTenantId());
List<SpecItem> selectedSpecs = validateSpecs(request.getSpecItemIds(), request.getTenantId());
// 3. 计算最终价格(包含会员价、活动价)
BigDecimal finalPrice = priceCalculationService.calculateFinalPrice(
request.getTenantId(), request.getUserId(),
request.getProductId(), request.getSpecItemIds()
).getFinalPrice();
// 4. 检查购物车是否已存在相同商品+规格组合
CartItem existingItem = findExistingCartItem(cart.getId(), request.getProductId(), request.getSpecItemIds());
if (existingItem != null) {
// 更新数量
existingItem.setQuantity(existingItem.getQuantity() + request.getQuantity());
existingItem.setFinalPrice(finalPrice.multiply(BigDecimal.valueOf(existingItem.getQuantity())));
cartItemMapper.updateById(existingItem);
} else {
// 新增商品项
CartItem newItem = createCartItem(cart.getId(), product, selectedSpecs, request.getQuantity(), finalPrice);
cartItemMapper.insert(newItem);
}
// 5. 重新计算购物车总金额和总数量
recalculateCart(cart.getId());
return convertToVO(existingItem != null ? existingItem : newItem);
}
private CartItem createCartItem(Long cartId, Product product, List<SpecItem> specs,
Integer quantity, BigDecimal finalPrice) {
CartItem item = new CartItem();
item.setCartId(cartId);
item.setProductId(product.getId());
item.setProductName(product.getName());
item.setBasePrice(product.getBasePrice());
item.setQuantity(quantity);
item.setFinalPrice(finalPrice.multiply(BigDecimal.valueOf(quantity)));
item.setIsSelected(true);
// 保存规格快照
List<CartItemSpec> specSnapshots = specs.stream().map(spec -> {
CartItemSpec specSnapshot = new CartItemSpec();
specSnapshot.setSpecGroupId(spec.getSpecGroupId());
specSnapshot.setSpecGroupName(spec.getSpecGroup().getName());
specSnapshot.setSpecItemId(spec.getId());
specSnapshot.setSpecItemName(spec.getName());
specSnapshot.setExtraPrice(spec.getExtraPrice());
return specSnapshot;
}).collect(Collectors.toList());
item.setSelectedSpecs(JsonUtil.toJson(specSnapshots)); // JSON格式存储规格快照
return item;
}
}
购物车结算逻辑
// 购物车结算服务
@Service
public class CartCheckoutService {
public OrderCreateRequest prepareOrderFromCart(Long cartId, String userCouponId) {
Cart cart = cartMapper.selectById(cartId);
List<CartItem> selectedItems = cartItemMapper.selectSelectedItems(cartId);
// 验证购物车商品状态
validateCartItems(selectedItems);
OrderCreateRequest request = new OrderCreateRequest();
request.setTenantId(cart.getTenantId());
request.setUserId(cart.getUserId());
request.setUserCouponId(userCouponId);
// 转换购物车项为订单项
List<OrderItemRequest> orderItems = selectedItems.stream().map(item -> {
OrderItemRequest orderItem = new OrderItemRequest();
orderItem.setProductId(item.getProductId());
orderItem.setProductName(item.getProductName());
orderItem.setQuantity(item.getQuantity());
orderItem.setUnitPrice(item.getFinalPrice().divide(BigDecimal.valueOf(item.getQuantity())));
// 转换规格信息
if (StringUtils.isNotBlank(item.getSelectedSpecs())) {
List<CartItemSpec> specs = JsonUtil.fromJsonList(item.getSelectedSpecs(), CartItemSpec.class);
List<OrderItemSpecRequest> orderSpecs = specs.stream().map(spec -> {
OrderItemSpecRequest orderSpec = new OrderItemSpecRequest();
orderSpec.setSpecName(spec.getSpecGroupName() + ":" + spec.getSpecItemName());
orderSpec.setExtraPrice(spec.getExtraPrice());
return orderSpec;
}).collect(Collectors.toList());
orderItem.setSpecs(orderSpecs);
}
return orderItem;
}).collect(Collectors.toList());
request.setItems(orderItems);
return request;
}
public void clearCartAfterOrder(Long cartId) {
// 删除已选中的商品项
cartItemMapper.deleteSelectedItems(cartId);
// 重新计算购物车
recalculateCart(cartId);
}
}
购物车查询逻辑
// 购物车查询服务
@Service
public class CartQueryService {
public CartVO getCartDetail(String userId, Long tenantId) {
Cart cart = cartMapper.selectByUserAndTenant(userId, tenantId);
if (cart == null) {
return createEmptyCartVO(userId, tenantId);
}
List<CartItem> items = cartItemMapper.selectByCartId(cart.getId());
CartPriceVO priceInfo = cartPriceService.calculateCartPrice(cart.getId(), userId);
CartVO cartVO = new CartVO();
cartVO.setId(cart.getId());
cartVO.setTotalQuantity(priceInfo.getTotalQuantity());
cartVO.setTotalAmount(priceInfo.getProductTotal());
cartVO.setItems(convertToItemVOs(items));
cartVO.setPriceInfo(priceInfo);
return cartVO;
}
private List<CartItemVO> convertToItemVOs(List<CartItem> items) {
return items.stream().map(item -> {
CartItemVO vo = new CartItemVO();
vo.setId(item.getId());
vo.setProductId(item.getProductId());
vo.setProductName(item.getProductName());
vo.setBasePrice(item.getBasePrice());
vo.setQuantity(item.getQuantity());
vo.setFinalPrice(item.getFinalPrice());
vo.setIsSelected(item.getIsSelected());
// 解析规格快照
if (StringUtils.isNotBlank(item.getSelectedSpecs())) {
List<CartItemSpec> specs = JsonUtil.fromJsonList(item.getSelectedSpecs(), CartItemSpec.class);
vo.setSelectedSpecs(specs);
}
// 实时查询商品状态(是否下架、库存等)
ProductStatus status = productService.getProductStatus(item.getProductId());
vo.setProductStatus(status);
return vo;
}).collect(Collectors.toList());
}
}
购物车价格计算逻辑
@Service
public class CartPriceService {
public CartPriceVO calculateCartPrice(Long cartId, String userId) {
Cart cart = cartMapper.selectById(cartId);
List<CartItem> items = cartItemMapper.selectByCartId(cartId);
CartPriceVO result = new CartPriceVO();
// 1. 计算商品总金额
BigDecimal productTotal = BigDecimal.ZERO;
Integer totalQuantity = 0;
for (CartItem item : items) {
if (Boolean.TRUE.equals(item.getIsSelected())) {
productTotal = productTotal.add(item.getFinalPrice());
totalQuantity += item.getQuantity();
}
}
result.setProductTotal(productTotal);
result.setTotalQuantity(totalQuantity);
// 2. 计算配送费
BigDecimal deliveryFee = calculateDeliveryFee(cart.getTenantId(), productTotal);
result.setDeliveryFee(deliveryFee);
// 3. 计算订单金额(商品总价 + 配送费)
BigDecimal orderAmount = productTotal.add(deliveryFee);
result.setOrderAmount(orderAmount);
// 4. 计算可用优惠券
List<CouponVO> availableCoupons = couponService.getAvailableCoupons(
cart.getTenantId(), userId, orderAmount);
result.setAvailableCoupons(availableCoupons);
// 5. 计算最优优惠券
CouponVO bestCoupon = findBestCoupon(availableCoupons, orderAmount);
result.setBestCoupon(bestCoupon);
if (bestCoupon != null) {
result.setCouponDiscount(bestCoupon.getDiscountAmount());
}
// 6. 计算实付金额
BigDecimal finalAmount = orderAmount.subtract(result.getCouponDiscount());
result.setFinalAmount(finalAmount.max(BigDecimal.ZERO)); // 不能小于0
return result;
}
}