
基于微信小程序的水果店管理系统是许多毕业设计和课程设计会选的方向业务场景清晰用户端和管理端边界明确又能在小程序端、后端接口、数据库设计、订单流程等几个层面展开技术点。这类项目看起来简单但真正把它做成“能演示、能答辩、能扩展”的状态需要把业务流程、数据库关系、接口设计和异常处理对齐而不是只堆几个页面。这篇文章从业务分析开始先讲为什么选用“微信小程序 Spring Boot MySQL”这套组合再给出数据库表结构、后端接口、小程序页面的实现思路最后覆盖本地运行、验证清单和常见问题排查。文中的表结构和代码是通用示例实际项目需要根据自己的包名、路径、版本和需求调整。1. 先理解这个系统要解决什么业务问题1.1 水果店管理的典型痛点水果店和普通标准品零售不同商品有季节性、有损耗、有等级差异同一类水果在不同批次价格会浮动。传统手工记账方式在商品数量少时还能维持一旦门店有几十种水果、每日多笔订单就会出现几个明显问题库存只能靠盘点时发现不能提前预警。售价变更后纸质价签和收银系统不一致。顾客下单后店员需要手动确认订单、配送或自提状态。缺少按日、按周的销售统计进货决策靠经验。课程设计或毕业设计选择这个主题并不是要完成一个真正的商业 SaaS而是通过一个完整的小型系统把数据库建模、接口设计、小程序开发、状态流转这些能力串起来。因此业务模块不需要做得太重但流程必须完整商品管理、用户登录、浏览下单、订单处理、库存扣减。1.2 小程序加轻量后台的整体方案水果店管理系统的整体形态可以分为两端顾客端微信小程序负责商品浏览、搜索、详情查看、购物车、下单、订单查询。管理端可以是 Web 后台也可以是小程序内的管理页面负责商品上下架、库存调整、订单发货或核销、销售统计。两端的业务数据都来自同一个后端接口服务和同一个数据库。这样设计的好处是商品信息只需要维护一份顾客端和管理端看到的数据始终一致。管理端如果单独再建一套系统就需要考虑数据同步问题复杂度会上升。系统采用分层结构可以用一句话概括小程序负责展示和交互后端负责业务规则和数据持久化数据库负责最终存储。1.3 系统角色与核心业务流程整个系统涉及的角色有三种角色使用端主要操作顾客微信小程序注册登录、浏览商品、加购物车、下单、支付或选择货到付款、查看订单商家管理员管理后台商品管理、库存调整、订单处理、数据统计系统维护者后端与数据库配置管理、日志排查、数据备份核心流程是商品从上架到订单完成的完整链路管理员在后台录入商品包括名称、分类、价格、库存、图片。小程序首页展示在售商品。顾客将商品加入购物车选择购买数量。顾客提交订单系统校验库存并冻结或扣减。订单进入待支付或待处理状态。管理员处理订单更新为已发货或已核销。顾客确认收货并评价可选。这条链路中最需要设计清楚的是订单状态和库存扣减机制否则很容易出现超卖、重复下单、状态错乱等问题。后面的章节会重点说明。2. 技术选型与项目结构设计2.1 前端、后端、数据库选型依据微信小程序端的原生开发仍然是课程设计中比较稳妥的选择。原生小程序不依赖额外框架开发者工具直接支持调试和真机预览都方便。如果对 Vue 更熟悉也可以使用 uni-app但要注意路由、组件和原生小程序差异排错成本会高一些。后端选择 Spring Boot主要原因是约定优于配置适合快速搭建 REST 接口而且相关的资料、示例在遇到问题时更容易检索。持久层框架可以选择 MyBatis 或 MyBatis-PlusMyBatis-Plus 的代码生成和内置通用方法能减少重复的 CRUD 代码适合项目周期比较短的课程设计。数据库使用 MySQL订单和商品这类关系型数据用 MySQL 管理最直接事务支持也成熟。如果团队不熟悉 MySQL 安装配置也可以使用 H2 数据库做演示但最终交给老师或部署到云服务器时MySQL 更接近真实环境。技术栈的选型理由可以整理成下面的表格层次选型选择理由备选方案小程序端微信原生小程序工具链完整调试方便uni-app、Taro后端Spring Boot 2.7 或 3.x生态成熟适合 REST APISpring MVC、Node.js ExpressORMMyBatis-Plus减少重复 CRUD 代码MyBatis、JPA数据库MySQL 8.x支持事务数据关系清晰H2、PostgreSQL鉴权小程序登录 code2session JWT与小程序登录流程匹配Redis Session2.2 项目目录结构后端项目建议按模块分包而不是把所有类堆在同一个包下。一个可参考的结构如下fruit-store-admin/ ├── pom.xml └── src/main/java/com/example/fruitstore/ ├── FruitStoreApplication.java ├── config/ │ ├── WebConfig.java │ └── MybatisPlusConfig.java ├── controller/ │ ├── ProductController.java │ ├── CategoryController.java │ ├── CartController.java │ ├── OrderController.java │ └── AdminController.java ├── service/ │ ├── ProductService.java │ ├── OrderService.java │ └── UserService.java ├── mapper/ │ ├── ProductMapper.java │ ├── OrderMapper.java │ └── UserMapper.java ├── entity/ │ ├── Product.java │ ├── Category.java │ ├── Order.java │ └── User.java ├── dto/ │ ├── LoginRequest.java │ ├── OrderCreateRequest.java │ └── Result.java ├── common/ │ ├── BusinessException.java │ └── GlobalExceptionHandler.java └── utils/ └── JwtUtil.java小程序端在微信开发者工具中创建项目后页面放在pages目录公共请求封装放在utils目录接口地址建议集中在一个config.js文件里避免每个页面都写死 IP 和端口。2.3 环境准备与版本要求开始编码前先确认开发环境。不同版本的 JDK、Spring Boot 和微信基础库之间有一些兼容性差异提前对齐可以省去后面的排错时间。工具或组件建议版本用途JDKJDK 8 或 JDK 17编译和运行 Spring BootMaven3.6 以上依赖管理MySQL5.7 或 8.0数据存储微信开发者工具最新稳定版即可小程序调试和预览小程序基础库2.10.0 以上兼容大部分 API后端框架Spring Boot 2.7.x接口开发需要注意如果使用 Spring Boot 3.xJDK 版本要求 17 以上MyBatis-Plus 也要选择支持 Spring Boot 3 的版本。课程设计项目中Spring Boot 2.7 加 JDK 8 的资料最多遇到问题更容易找到答案。3. 数据库设计围绕商品、订单、库存建模3.1 核心表结构与字段说明水果店管理系统最少需要这些表用户表、商品分类表、商品表、购物车表、订单表、订单明细表。用户表保存小程序的 openid这是微信侧唯一标识。不要把用户的明文密码保存在这张表里顾客端小程序登录走微信授权不需要自己在服务端维护密码体系。管理员账号可以单独建一张管理员表或者在用户表上用 role 字段区分课程设计通常建议分开更清晰。商品表和订单明细表是核心关系。商品表保存当前库存和售价订单明细表保存下单时的快照价格。这里要特别注意订单明细中的价格不能直接关联商品表的实时价格否则商品调价后历史订单金额会被改变。3.2 建表 SQL 示例下面是一组可参考的核心表结构。实际建表时请根据自己项目的字段需求调整比如是否需要规格、单位、损耗率等字段。CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT COMMENT 用户ID, openid varchar(64) NOT NULL COMMENT 微信openid, nickname varchar(50) DEFAULT NULL COMMENT 昵称, phone varchar(20) DEFAULT NULL COMMENT 手机号, avatar varchar(255) DEFAULT NULL COMMENT 头像地址, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE category ( id bigint NOT NULL AUTO_INCREMENT COMMENT 分类ID, name varchar(50) NOT NULL COMMENT 分类名称, sort int DEFAULT 0 COMMENT 排序权重, status tinyint DEFAULT 1 COMMENT 1启用 0停用, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品分类表; CREATE TABLE product ( id bigint NOT NULL AUTO_INCREMENT COMMENT 商品ID, category_id bigint NOT NULL COMMENT 分类ID, name varchar(100) NOT NULL COMMENT 商品名称, cover varchar(255) DEFAULT NULL COMMENT 封面图, price decimal(10,2) NOT NULL COMMENT 售价, unit varchar(10) DEFAULT 斤 COMMENT 单位, stock int NOT NULL DEFAULT 0 COMMENT 库存, sales int NOT NULL DEFAULT 0 COMMENT 销量, description text COMMENT 商品描述, status tinyint DEFAULT 1 COMMENT 1上架 0下架, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 更新时间, PRIMARY KEY (id), KEY idx_category (category_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT商品表; CREATE TABLE cart ( id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL COMMENT 用户ID, product_id bigint NOT NULL COMMENT 商品ID, quantity int NOT NULL DEFAULT 1 COMMENT 数量, selected tinyint DEFAULT 1 COMMENT 是否选中, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_user (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT购物车表; CREATE TABLE orders ( id bigint NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 订单号, user_id bigint NOT NULL COMMENT 用户ID, total_amount decimal(10,2) NOT NULL COMMENT 订单总金额, status tinyint NOT NULL DEFAULT 0 COMMENT 订单状态, pay_method tinyint DEFAULT 0 COMMENT 支付方式, pay_time datetime DEFAULT NULL COMMENT 支付时间, receiver_name varchar(50) DEFAULT NULL COMMENT 收货人, receiver_phone varchar(20) DEFAULT NULL COMMENT 收货电话, receiver_address varchar(255) DEFAULT NULL COMMENT 收货地址, remark varchar(255) DEFAULT NULL COMMENT 备注, create_time datetime DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE order_item ( id bigint NOT NULL AUTO_INCREMENT, order_id bigint NOT NULL COMMENT 订单ID, product_id bigint NOT NULL COMMENT 商品ID, product_name varchar(100) NOT NULL COMMENT 商品快照名称, product_cover varchar(255) DEFAULT NULL COMMENT 商品快照图片, price decimal(10,2) NOT NULL COMMENT 商品快照价格, quantity int NOT NULL COMMENT 购买数量, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;订单表的status字段是状态机核心不建议用字符串随意填而是定一套数字状态在代码里用常量或枚举统一管理。3.3 订单状态机设计订单状态是整个系统最容易出问题的地方。推荐用下面这套状态状态值含义后续流转0待支付支付成功到 1超时关闭到 51待发货或待核销管理员发货或核销后到 22待收货或已完成顾客确认后到 33已完成可申请售后课程设计阶段可到此为止4已取消用户主动取消5已关闭超时未支付系统关闭状态只能按顺序或指定路径流转不允许从待发货直接跳回待支付。在 Service 层写状态更新时要先用SELECT ... FOR UPDATE或乐观锁保证当前订单状态是最新的再做更新。4. 后端接口实现商品、购物车、订单4.1 项目依赖与基础配置后端使用 Maven 管理依赖。一个比较精简的pom.xml核心依赖如下dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.3.1/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdorg.projectlombok/groupId artifactIdlombok/artifactId optionaltrue/optional /dependency /dependenciesapplication.yml基础配置包括数据源、MyBatis-Plus 配置和文件上传或图片访问路径server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fruit_store?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0这里要解释两个配置的作用。map-underscore-to-camel-case把数据库字段create_time自动映射到实体类的createTime避免手写大量映射。StdOutImpl会在控制台打印 SQL课程设计阶段非常有用能看到 MyBatis-Plus 实际执行的语句。4.2 用户登录与会话小程序的登录流程是小程序调用wx.login()获取临时 code后端用 code 到微信接口换取 openid然后签发自定义 token 返回给小程序。后续接口通过 token 识别用户。Controller 和 Service 的简化实现如下RestController RequestMapping(/api/user) public class UserController { Resource private UserService userService; PostMapping(/login) public ResultString login(RequestBody LoginRequest request) { // request.code 从小程序 wx.login 获取 String token userService.login(request.getCode()); return Result.ok(token); } }Service public class UserServiceImpl implements UserService { Resource private UserMapper userMapper; Override public String login(String code) { // 1. 调用微信接口使用 code 换取 openid // 实际项目中需要配置 appid 和 secret并注意缓存 access_token String openid wechatService.code2Session(code); // 2. 根据 openid 查找用户不存在则创建 User user userMapper.selectOne( new LambdaQueryWrapperUser().eq(User::getOpenid, openid)); if (user null) { user new User(); user.setOpenid(openid); userMapper.insert(user); } // 3. 签发 JWTtoken 中只放用户标识不放敏感信息 return JwtUtil.createToken(user.getId()); } }这里的重点在于不要在服务端自己实现一套用户名密码登录。小程序端使用微信登录是标准做法后端只需要完成 code 换 openid再维护自己的用户表即可。4.3 商品与分类接口商品接口需要区分顾客端和管理端。顾客端只查询status 1的上架商品管理端可以查询全部商品并执行上下架操作。两个接口如果共用同一个方法很容易出现顾客看到下架商品的情况。顾客端商品列表接口GetMapping(/api/products) public ResultPageProductVO listProduct( RequestParam(defaultValue 1) int page, RequestParam(defaultValue 10) int size, RequestParam(required false) Long categoryId, RequestParam(required false) String keyword) { PageProduct productPage productService.page( new Page(page, size), new LambdaQueryWrapperProduct() .eq(Product::getStatus, 1) .eq(categoryId ! null, Product::getCategoryId, categoryId) .like(StringUtils.hasText(keyword), Product::getName, keyword) .orderByDesc(Product::getSales) ); return Result.ok(productPage); }管理端商品维护接口PostMapping(/admin/products) public ResultVoid saveProduct(RequestBody Product product) { if (product.getId() null) { productService.save(product); } else { productService.updateById(product); } return Result.ok(); } PutMapping(/admin/products/{id}/status) public ResultVoid updateStatus(PathVariable Long id, RequestParam Integer status) { productService.lambdaUpdate() .eq(Product::getId, id) .set(Product::getStatus, status) .update(); return Result.ok(); }管理端接口必须做权限校验不能只是后端“看起来分开”就认为安全。至少要在拦截器里判断请求的 token 对应用户是否有管理员角色。4.4 订单创建接口与库存扣减创建订单是事务最密集的地方。简化逻辑包含检查购物车、校验库存、计算金额、扣减库存、生成订单和订单明细、清空购物车。这些操作必须在同一个事务里执行否则出现扣了库存但订单没生成或订单生成了但库存没扣的问题。Transactional(rollbackFor Exception.class) public OrderInfo createOrder(Long userId, OrderCreateRequest request) { // 1. 查出用户选中的购物车项 ListCart cartList cartService.findSelectedByUser(userId); if (CollectionUtils.isEmpty(cartList)) { throw new BusinessException(购物车没有选中商品); } BigDecimal totalAmount BigDecimal.ZERO; ListOrderItem itemList new ArrayList(); for (Cart cart : cartList) { // 2. 使用悲观锁或乐观锁查询商品校验库存 Product product productMapper.selectByIdForUpdate(cart.getProductId()); if (product null || product.getStatus() ! 1) { throw new BusinessException(商品已下架 cart.getProductId()); } if (product.getStock() cart.getQuantity()) { throw new BusinessException(库存不足 product.getName()); } // 3. 计算小计 BigDecimal subtotal product.getPrice().multiply(BigDecimal.valueOf(cart.getQuantity())); totalAmount totalAmount.add(subtotal); // 4. 商品快照写入订单明细 OrderItem item new OrderItem(); item.setProductId(product.getId()); item.setProductName(product.getName()); item.setProductCover(product.getCover()); item.setPrice(product.getPrice()); item.setQuantity(cart.getQuantity()); itemList.add(item); } // 5. 生成订单号和订单主体 Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setTotalAmount(totalAmount); order.setStatus(OrderStatus.PENDING_PAYMENT.getCode()); order.setReceiverName(request.getReceiverName()); order.setReceiverPhone(request.getReceiverPhone()); order.setReceiverAddress(request.getReceiverAddress()); orderMapper.insert(order); // 6. 批量保存订单明细 for (OrderItem item : itemList) { item.setOrderId(order.getId()); } orderItemMapper.insertBatch(itemList); // 7. 扣减库存、增加销量、清空购物车 for (Cart cart : cartList) { productMapper.decreaseStock(cart.getProductId(), cart.getQuantity()); } cartService.removeByUserAndProductIds(userId, cartList); return new OrderInfo(order); }代码中的selectByIdForUpdate是自定义 SQL用SELECT ... FOR UPDATE锁住商品行防止两个请求同时看到同一份库存。在生产环境如果并发量高还可以引入 Redis 预扣库存但课程设计阶段使用数据库锁已经足够而且更容易解释原理。4.5 管理端接口订单处理与统计管理端需要处理的订单操作是发货或核销。核销场景适合自提水果店顾客到店后凭订单号或核销码取货。PutMapping(/admin/orders/{id}/ship) public ResultVoid shipOrder(PathVariable Long id) { orderService.updateStatus(id, OrderStatus.PENDING_RECEIPT.getCode()); return Result.ok(); }状态更新不能直接无条件覆盖。这里需要先查询当前状态确认是从“待发货”流转到“待收货”否则要抛出业务异常。更好的做法是在 SQL 中带上旧状态作为条件boolean updated orderMapper.updateStatusWithCondition( id, OrderStatus.PENDING_RECEIPT.getCode(), OrderStatus.PENDING_SHIPMENT.getCode() ); if (!updated) { throw new BusinessException(订单状态已变化请刷新后重试); }销售统计可以提供一个简单的按日汇总接口用于管理端首页展示GetMapping(/admin/stats/overview) public ResultMapString, Object overview() { MapString, Object data new HashMap(); data.put(todayOrderCount, orderMapper.countToday()); data.put(todaySales, orderMapper.sumToday()); data.put(productCount, productMapper.selectCount(null)); data.put(lowStockProducts, productMapper.selectLowStock(10)); return Result.ok(data); }5. 小程序前端实现5.1 页面结构与路由小程序端页面可以按下表组织页面路径作用首页pages/index/index轮播、分类、热门商品商品列表pages/product/list按分类或关键字展示商品商品详情pages/product/detail商品图片、价格、库存、加入购物车购物车pages/cart/cart选择商品、修改数量、结算确认订单pages/order/confirm填写收货信息、提交订单订单列表pages/order/list按状态查看订单订单详情pages/order/detail订单状态和明细我的pages/user/user用户信息、订单入口、收货地址app.json中注册页面和底部 tabBar{ pages: [ pages/index/index, pages/product/list, pages/product/detail, pages/cart/cart, pages/order/confirm, pages/order/list, pages/order/detail, pages/user/user ], window: { navigationBarBackgroundColor: #4CAF50, navigationBarTitleText: 鲜果小店, navigationBarTextStyle: white }, tabBar: { list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/cart/cart, text: 购物车 }, { pagePath: pages/user/user, text: 我的 } ] }, style: v2, sitemapLocation: sitemap.json }5.2 API 请求封装小程序没有浏览器里的fetch和XMLHttpRequest统一使用wx.request。建议封装一个公共请求模块统一处理 baseURL、token、错误码和登录态失效。// utils/request.js const BASE_URL http://localhost:8080/api; function request({ url, method GET, data {} }) { return new Promise((resolve, reject) { wx.request({ url: ${BASE_URL}${url}, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 200 res.data.code 200) { resolve(res.data.data); } else if (res.statusCode 401) { wx.removeStorageSync(token); wx.navigateTo({ url: /pages/login/login }); reject(res.data); } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(res.data); } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); } module.exports { request };封装请求模块的核心目的是统一处理鉴权头和错误提示。实际开发中如果每个页面都单独写wx.request一旦接口地址变化或登录态失效逻辑调整需要改的地方会非常多。5.3 首页和商品列表首页可以直接用分类和商品推荐两个模块来演示。分类使用纵向布局或横向滚动商品列表使用两列网格。关键代码是调用商品接口并渲染// pages/index/index.js const { request } require(../../utils/request); Page({ data: { categories: [], products: [], activeCategoryId: 0 }, onLoad() { this.loadCategories(); this.loadProducts(0); }, async loadCategories() { const categories await request({ url: /categories }); this.setData({ categories }); }, async loadProducts(categoryId) { const page await request({ url: /products, data: { page: 1, size: 10, categoryId } }); this.setData({ products: page.records }); }, onSelectCategory(e) { const id e.currentTarget.dataset.id; this.setData({ activeCategoryId: id }); this.loadProducts(id); } });!-- pages/index/index.wxml -- view classcategory-bar wx:for{{categories}} wx:keyid view classcategory-item {{activeCategoryId item.id ? active : }} >settle() { const selectedItems this.data.cartList.filter(item item.selected); if (selectedItems.length 0) { wx.showToast({ title: 请先选择商品, icon: none }); return; } wx.setStorageSync(selectedCartItems, selectedItems); wx.navigateTo({ url: /pages/order/confirm }); }确认订单页面把本地缓存中的选中购物车项展示出来填写收货人、电话、地址后调用创建订单接口。这里有一个容易忽略的问题前端展示的价格和后端计算的价格必须以接口返回为准不能信任前端的合计金额。课程设计演示时可以直接以后端金额返回展示避免只改前端数据造成订单金额错误。5.5 支付对接思路与微信支付 v3水果店下单后支付是完整闭环的一部分。微信小程序支付通常使用微信支付 v3 协议。完整接入需要以下步骤申请商户号并开通小程序支付权限。在小程序后台关联商户号配置支付目录。后端生成订单后调用微信支付统一下单接口传入小程序 appid、商户号、订单号、金额和回调地址。微信返回预支付交易会话标识prepay_id后端再生成签名参数返回给小程序。小程序调用wx.requestPayment拉起收银台。用户支付成功后微信服务器把支付结果通知到后端回调地址后端验签、解密并更新订单状态。如果只是课程设计或演示环境并没有真实商户资质通常有两种做法一种是把支付按钮保留但提示“演示环境暂未开通真实支付”另一种是在后端预留支付接口的对接层实现一个模拟支付通道点击后直接标记为已支付。这里不建议为了演示真实支付而使用任何第三方非官方通道微信小程序支付的官方接入要求非常严格商户资质、签约和结算都需要合规流程。如果做了微信支付 v3 对接回调验签是最容易出错的部分。接口的日志里至少应该包含收到的报文原文、签名头信息、验签结果、订单号和支付结果。上线前要用微信支付平台的测试工具充分验证回调场景。6. 本地运行与功能验证6.1 启动后端后端启动前先确认 MySQL 中已经创建数据库并导入建表 SQL。然后执行mvn spring-boot:run看到类似下面的日志说明启动成功Tomcat started on port(s): 8080 (http) with context path Started FruitStoreApplication in 5.2 seconds建议启动后用 Swagger 或直接请求接口验证一次比如curl -X GET http://localhost:8080/api/products?page1size10如果返回 JSON 数据后端基本没问题。6.2 运行小程序在微信开发者工具中导入小程序项目填写自己的 AppID。如果没有 AppID可以使用测试号但测试号不支持部分接口。开发调试阶段注意以下配置在详情 - 本地设置中勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”。如果后端运行在本地小程序真机不能直接访问localhost需要让小程序和后端处于同一局域网并把BASE_URL改为电脑的局域网 IP例如http://192.168.1.100:8080/api。后端接口要允许跨域或不做跨域限制小程序wx.request不受浏览器同源策略限制但如果是 Web 管理后台就需要在 Spring Boot 中配置 CORS。6.3 功能验证清单课程设计或毕业设计提交前建议按下面的清单完整走一遍模块验证点预期结果登录用户首次进入小程序自动登录或弹窗提示登录用户表有新记录商品浏览首页加载商品只显示上架商品分类切换正常商品详情点击商品卡片进入详情页展示图片、价格、库存购物车添加商品、修改数量数量和金额实时更新提交订单选中多个商品结算生成订单库存减少购物车清空订单取消取消待支付订单状态变为已取消库存回退订单管理管理端发货顾客端订单状态同步更新异常场景库存不足提交订单后端返回错误提示订单不生成库存不变权限未登录访问管理接口返回 401 或提示未登录数据统计管理端首页展示今日订单数和销售额上面这份清单也是答辩时的演示脚本。如果清单里的每个流程都能按照预期走通项目的成熟度已经足够说明完整性和工程能力。7. 常见问题排查链路7.1 真机预览白屏或请求全部失败现象开发者工具中数据正常真机预览时页面空白控制台看到request:fail或ERR_CERT_COMMON_NAME_INVALID。排查顺序确认BASE_URL不是localhost。真机上localhost指向手机自身不是开发电脑。确认电脑防火墙允许内网访问 8080 端口。确认小程序后台已经配置合法域名或者仅调试时在工具中取消域名校验。如果使用 HTTPS 域名检查证书是否为合规 CA 签发小程序不支持自签名证书。解决方案把后端接口地址改为开发电脑局域网 IP并确保小程序和后端在同一网络。生产环境则必须配置 HTTPS 域名。7.2 登录态失效或 code2session 返回错误现象用户登录后接口正常过一段时间请求返回 401重新登录也没有效果或后端调用微信接口时报40029 invalid code。原因通常有几种code 是一次性的一个 code 只能使用一次重复调用会报错。APPID 和 SECRET 配置错误导致code2session返回错误码。后端生成的 token 过期时间太短前端没有拦截 401 后重新登录。处理方式后端对code2session的返回先做日志记录看到 code 再确认是否被重复使用。JWT 过期时间可以设为 7 天或 30 天并让前端在收到 401 时统一跳转登录。7.3 订单重复提交导致库存超卖现象两个用户同时购买同一件商品库存只剩 1 件两个订单都成功库存变成负数。原因是查询库存和扣减库存之间存在时间差两个请求都读到库存等于 1都认为可以购买。解决办法是像第 4 章那样使用SELECT ... FOR UPDATE锁行或者使用UPDATE product SET stock stock - #{quantity} WHERE id #{id} AND stock #{quantity}这种条件更新通过受影响行数判断是否扣减成功。7.4 支付回调验签失败或回调重复接收现象用户已经支付成功但订单状态未更新后台日志出现验签失败。微信支付 v3 的回调是异步且可能重复推送的。验签失败常见原因是平台证书没有更新、回调参数使用了错误的签名、或没有按微信文档顺序拼接字段。排查路径先开启后端日志完整打印回调 headers 和 body。验证微信支付平台证书序列号确认使用的证书是最新的。确认验签时的请求体和签名头没有被网关框架修改。回调处理接口要保证幂等同一订单的支付通知如果重复发送只更新一次状态并在数据库用订单状态条件约束。课程设计如果只做模拟支付不需要处理这些复杂情况但如果在简历里写了“对接微信支付 v3”面试官很可能追问回调验签和幂等处理这部分值得认真准备。7.5 小程序图片无法显示现象商品图片在网页或本地点开正常小程序中显示空白。原因通常是小程序基础库对图片域名校验严格或者图片是 HTTP 地址。排查和处理本地调试时勾选“不校验合法域名”。真机环境必须使用 HTTPS 图片域名。如果图片地址包含中文或特殊字符先编码后再拼接。在image组件上使用binderror事件打印错误信息方便定位。8. 从课程设计到生产环境的优化方向8.1 安全与权限课程设计阶段管理端接口可能只做了非常简单的 token 校验甚至没有校验。生产环境必须在拦截器或 Spring Security 中建立完整的权限体系接口按角色区分普通顾客接口和管理员接口分开鉴权。管理端请求必须校验管理员身份不只是“接口路径里带 admin 就算受保护”。数据库密码、微信 AppSecret、支付商户密钥不能写在代码里要通过环境变量或配置中心注入。订单金额、支付结果以后端为准不能信任前端传入的金额。8.2 性能与稳定性小型系统上线后要考虑的不只是功能还有资源占用和异常恢复商品列表接口增加分页和缓存。热点分类或推荐商品可以使用 Redis 缓存减少数据库压力。库存扣减使用条件更新加重试机制避免大并发下超卖。订单创建使用异步日志和消息队列进一步增强但课程设计阶段不必引入 MQ。增加统一异常处理接口返回结构保持一致避免异常堆栈直接暴露给前端。数据库定期备份商品和订单表至少每日备份一次。8.3 答辩或提交前的可复用检查清单这个项目如果用于毕业设计、课程设计或课后实践建议把下面这份检查清单作为交付前的通用标准检查项具体要求项目结构后端分层清晰小程序页面命名规范数据库设计表结构有注释主外键关系合理状态字段有枚举说明接口规范返回结构统一错误码有说明分页参数一致核心流程商品、购物车、订单、库存、统计都能跑通异常处理库存不足、订单状态错误、登录失效有明确提示权限控制管理端接口有鉴权普通用户不能访问文档材料有环境部署文档代码注释能支撑答辩讲解演示脚本准备一条完整的从登录到订单完成的操作路径安全处理密钥不写进代码数据库连接使用独立账号版本控制使用 Git 管理代码提交历史可追溯8.4 扩展方向在基础版本之上可以按自己的兴趣和能力扩展增加水果损耗管理进货时登记损耗量月末自动计算损耗率。增加会员积分和优惠券订单金额计算时考虑折扣。增加月度销售图表用 ECharts 在管理端展示销量趋势。增加自提核销码顾客到店后展示二维码管理端扫码核销。增加消息通知模板订单状态变化时通过微信订阅消息通知顾客。无论选择哪个方向都要先保证现有的主流程稳定再考虑扩展。课程设计或毕业设计的评阅重点通常不是功能数量而是你对核心流程的理解、对异常场景的处理以及对技术选型原因的解释。把一个水果店管理系统做成闭环、可运行、可解释就已经把数据库、后端接口和小程序开发三个层面的能力完整地串起来了。