从需求开始至架构设计的适用于商家及小吃摊的点餐小程序

文章目录

背景

有朋友让我给他做一个在线预定点餐的小程序系统。功能涵盖餐饮分类;具体商品包含规格;商家联系方式;地址,支持导航;动态分享;我的订单;购物车;自提或者外送;线上自助付费(可输入金额自己付费);客服(接入微信客服);

需求分析

为了节省时间,这里省略了脑图,直接将所提需求提炼出功能点,具体分为小程序功能和后台管理端的功能概要,如下:

小程序功能

支付模块

接入微信支付,实现用户在小程序内完成付款

客服

完成用户在小程序内容联系商家的

线上自助付费

完成支付模块既可以完成对应的线上自助付款功能

自提或者外送

自提或者外送的切换功能,用户再下单的时候,可以自助选择是到商家自提,还是商家配送(单独收取一定的配送费)

购物车

可展示保留用户所选购的商品

我的订单

可以查看当前用户所下过的订单,并保留信息

动态分享

展示商家所发布的动态信息;让用户更好的掌握商户的一些公开活动

地址管理

完成用户的地址管理, 可以编辑删除的,为了更好的支持外送功能

菜品信息

展示当先店铺的一些菜品类别及菜品的名称及规格

菜品分类

菜品分类,比如酒水、甜品、主食、汤等等

菜品及规格

菜品信息,对应每个分类可以快速导菜品信息,并可以选择菜品的规格,比如甜品可分为大份、小份等等

后台管理系统

支付管理模块

微信支付参数的可配置(这里可能不需要,卸载配置文件中即可,或者接入其他三方、四方支付系统);

自助付费接入支付即可实现手动输入金额完成付款;

系统配置

营业时间配置

商家的营业时间通过这里配置

客服配置

接入微信客服插件即可绑定商家微信

自提或者外送配置

可以配置自提是否有额外费用(目前没有配置这个)

外送的运费:外送功能可根据实际情况是否接入三方配送,目前选择自己配送收取一定数额的配送费;

购物车管理

购物车统计当前所选购的商品并展示;

是否在一定时间后清理购物车,减少数据库存储的压力(目前没有这个配置)

订单管理

订单管理可以查看当前用户所下订单的支付情况,并包含了退款功能;

动态分享管理

动态分享是发布和管理当前商家所发布的动态信息

用户地址管理

可管理用户的地址信息,避免一些垃圾地址的出现(这个功能可以在确定,目前没有实现)

商家店铺管理

配置商家的店铺信息;

包括:店铺名称、联系方式、地址包含了经纬度,可导航到此处、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;
    }
}
相关推荐
2501_915106326 小时前
HTTPS 爬虫实战指南 从握手原理到反爬应对与流量抓包分析
爬虫·网络协议·ios·小程序·https·uni-app·iphone
2501_916007476 小时前
iOS 上架技术支持全流程解析,从签名配置到使用 开心上架 的实战经验分享
android·macos·ios·小程序·uni-app·cocoa·iphone
阿登林6 小时前
如何利用扣子生成小程序并进行发布指南
小程序·扣子
流***陌16 小时前
手办盲盒抽赏小程序前端功能设计:兼顾收藏需求与抽赏乐趣
前端·小程序
Tencent_TCB16 小时前
云开发CloudBase AI+实战:快速搭建AI小程序全流程指南
人工智能·ai·小程序·ai编程·云开发
说私域18 小时前
“开源AI大模型AI智能名片S2B2C商城小程序”视角下的教育用户策略研究
人工智能·小程序
2501_9160074721 小时前
提升 iOS 26 系统流畅度的实战指南,多工具组合监控
android·macos·ios·小程序·uni-app·cocoa·iphone
一匹电信狗1 天前
【MySQL】数据库表的操作
linux·运维·服务器·数据库·mysql·ubuntu·小程序
2501_915921431 天前
iOS 应用代上架流程,多工具组合与使用 开心上架 跨平台自动化上传指南
android·ios·小程序·uni-app·自动化·cocoa·iphone