资讯动态

微信小程序+Spring Boot水果商城:从数据库设计到接口实现全解析

发布时间:2026/10/27 6:30:44 来源:尧图企业网站定制
简介基于微信小程序与Spring Boot框架的水果销售系统毕业设计资料包面向计算机专业毕业设计、大作业及项目实战学习者。项目完整覆盖用户端水果分类浏览、详情查看、在线下单、微信支付与订单处理后台还提供订单管理和发货功能整个购物流程简洁高效。源码经过本地编译与严格调试可直接运行难度适中适合作为高校课程设计和本科毕业设计的参考项目。压缩包共832个文件、34.55MB核心代码包含117个Java后端逻辑、145个Vue页面及38个WXML小程序页面另有SQL数据库脚本、论文docx、开发文档、数据库说明文档以及JS、JSON、WXSS等配套资源前后端与文档一一对应。随包附带的论文详细介绍了系统背景、需求分析、设计思路、实现技术与测试结果开发文档涵盖从环境搭建到项目部署的完整流程数据库文档记录了所有表结构与字段关系便于二次开发目前已有134人下载学习适合需要直接可运行源码的毕业生也适合想深入理解小程序与Spring Boot整合开发的学习者和开发者。1. 这个仓库里藏着一套完整的水果商城从微信小程序到Spring Boot的骨架拆解当你拿到一个名字叫“基于微信小程序的水果销售系统的设计与实现springboot源码、论文的、说明文档、数据库文档.zip”的压缩包第一反应通常是这玩意能跑起来吗解压后你会看到springboot后端工程、小程序前端目录、一份Word论文、几个.sql和说明文档。这不是普通的demo而是一个典型的毕业设计级前后端分离项目小程序负责卖水果Spring Boot负责出接口MySQL负责记库存和订单。它解决的核心问题是“用微信生态做一单水果生意”的最小闭环——用户打开小程序、浏览水果、加购、下单管理员在后台维护商品和订单。适合三类人想快速交付毕设/课设的学生、刚学完Spring Boot想找个真实场景练手的新手、以及打算把校园水果配送做成副业的开发者。下面我会顺着数据库、后端、小程序三条线把这个项目拆到能直接照着改的程度。2. 水果销售系统的数据库怎么设计三张核心表与库存扣减的坑拿到源码包里的数据库文档别急着跑脚本先看表结构。水果销售系统本质上就是“人买水果、水果被买、订单被管理”所以业务表绕不开用户、商品、购物车、订单、订单明细这几张。我一般会先把建表SQL和ER图过一遍确认主外键和索引再动后端代码。很多源码包里的数据库文档写得并不规范但至少三张核心表是齐全的水果商品表、订单表、订单明细表。这一章就围绕它们展开顺带把库存扣减这个最容易翻车的点讲透。2.1 商品表、订单表、订单明细表的字段与关系先看水果商品表。水果和普通商品不同它有季节性、易损耗、按斤计价这些特性所以字段设计上要有category、unit、stock这些维度。下面这份建表语句是源码包里最常见的形态我在此基础上补了注释和索引-- 水果商品表 CREATE TABLE fruit ( id bigint(20) NOT NULL AUTO_INCREMENT, name varchar(64) NOT NULL COMMENT 水果名称, category varchar(32) DEFAULT NULL COMMENT 分类如 热带/浆果/柑橘, price decimal(10,2) NOT NULL DEFAULT 0.00 COMMENT 单价元/斤, unit varchar(16) DEFAULT 斤 COMMENT 计价单位斤/个/箱, stock int(11) NOT NULL DEFAULT 0 COMMENT 库存数量按unit, image varchar(255) DEFAULT NULL COMMENT 图片URL, description text COMMENT 描述, create_time datetime DEFAULT CURRENT_TIMESTAMP, update_time datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_category (category) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT水果商品表;这里有几个字段值得注意price 用 decimal 而不是 float因为金额精确度不能靠浮点数保证unit 用 varchar 而不是 int因为水果有“斤、个、箱”三种表示store 和展示更灵活category 加了普通索引因为首页大概率会按分类筛选。如果你要存水果的规格比如“红心火龙果 2个装”可以在后面追加 spec 字段但不要把它塞进 name 里否则后续统计销量很难拆。接着是订单主表。订单表要记录的是“谁在什么时候下的单、多少钱、送哪里”状态字段是最容易被忽略的。CREATE TABLE orders ( id bigint(20) NOT NULL AUTO_INCREMENT, order_no varchar(32) NOT NULL COMMENT 业务订单号前端展示用, user_id bigint(20) NOT NULL COMMENT 下单用户ID, total_price decimal(10,2) NOT NULL COMMENT 总金额, status tinyint(4) NOT NULL DEFAULT 0 COMMENT 0待付款 1待发货 2待收货 3已完成 4已取消, receiver_name varchar(32) NOT NULL COMMENT 收货人, receiver_phone varchar(16) NOT NULL COMMENT 联系电话, receiver_address varchar(255) NOT NULL COMMENT 收货地址, remark varchar(255) DEFAULT NULL COMMENT 用户备注, create_time datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uk_order_no (order_no), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表;status 用 tinyint 而不是字符串枚举是考虑到以后要加“退款中”“已退款”等状态改字段定义比改代码里的常量更麻烦。order_no 必须唯一因为前端给你回调用的是这个号不是自增主键。注意这里没有存商品明细因为那是订单明细表的事。订单明细表对应一次订单里的每一行水果它必须做“快照”CREATE TABLE order_item ( id bigint(20) NOT NULL AUTO_INCREMENT, order_id bigint(20) NOT NULL COMMENT 订单主表ID, fruit_id bigint(20) NOT NULL COMMENT 水果ID, fruit_name varchar(64) NOT NULL COMMENT 水果名称快照, fruit_price decimal(10,2) NOT NULL COMMENT 下单时单价快照, quantity int(11) NOT NULL COMMENT 数量, subtotal decimal(10,2) NOT NULL COMMENT 小计, PRIMARY KEY (id), KEY idx_order_id (order_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单明细表;为什么冗余 fruit_name 和 fruit_price 快照因为管理员可能改水果名称、调价格如果你下单完还去关联 fruit 表历史订单里的名称金额就全变了。这个问题在做数据核对时特别难受。订单明细表不需要“库存”字段库存只属于 fruit 表下单时统一从 fruit 表扣。三张表的关系相当直白fruit 与 order_item 是多对多一个水果可以出现在多个订单里一个订单可以包含多个水果orders 与 order_item 是一对多。购物车表相对简单我建议做成临时表字段就是 user_id、fruit_id、quantity、checked不需要快照因为购物车不是交易凭证。如果源码包里没有购物车表通常是用小程序本地存储模拟的这个后面会讲。2.2 用一条UPDATE实现安全的库存扣减避免超卖水果销售系统最怕两件事超卖和负库存。很多源码包的下单逻辑是先 SELECT stock 判断库存是否够再 UPDATE stock stock - quantity。这两个操作之间如果有并发就会卖出比库存更多的水果。正确的做法是把判断和扣减合并到一条SQL里UPDATE fruit SET stock stock - #{quantity} WHERE id #{fruitId} AND stock #{quantity}注意看 WHERE 条件里的 stock #{quantity}这条SQL只有“当前库存不小于要买的数量”时才会执行成功。用 MyBatis 的 update 方法返回的影响行数如果为 1说明扣减成功可以继续插入订单如果返回 0说明库存不够直接抛异常或提示用户。下面是下单的完整事务逻辑Transactional(rollbackFor Exception.class) public void createOrder(OrderCreateDTO dto) { // 扣减库存影响行数为0则抛库存不足 int rows fruitMapper.deductStock(dto.getFruitId(), dto.getQuantity()); if (rows 0) { throw new BizException(库存不足); } // 插入订单主表 Order order buildOrder(dto); orderMapper.insert(order); // 插入订单明细表 orderItemMapper.insert(buildOrderItem(dto, order.getId())); }这段代码里的 Transactional 很关键它保证扣库存和插订单要么都成功、要么都回滚。如果没有事务扣库存成功了但插订单失败库存就凭空少了一块。你可能会问如果并发不高先查后改的写法是不是也行我建议不要赌这个毕设答辩时老师最喜欢问的就是并发场景用这条SQL可以直接扛住这个问题。另外如果你用 MyBatis-Plus可以直接在 Service 里写一个 LambdaUpdateWrapperboolean success fruitService.update( new LambdaUpdateWrapperFruit() .setSql(stock stock - dto.getQuantity()) .eq(Fruit::getId, dto.getFruitId()) .ge(Fruit::getStock, dto.getQuantity()) );注意 setSql 里直接拼接数字有SQL注入风险这里数量是后端校验过的正整数或者你用参数绑定。实际项目中建议写SQL注解或XML。2.3 数据库文档怎么用ER图、字段注释和同步脚本数据库文档不是摆设。源码包里的 database 目录通常有建表SQL、ER图截图和一份字段说明表。我先教你看懂它ER图里fruit 和 order_item 之间的连线就是外键关系字段说明表里重点关注有没有多余字段、有没有时间字段没设置默认值。如果文档里的表和代码里的实体类对不上说明这份源码包被人改过一版但没同步文档这种情况很常见你需要以代码为准。数据库同步是另一个高频需求。比如你把源码跑在本地改了几个字段想同步到云服务器很多同学喜欢用数据库同步工具结果外键顺序和自增ID经常出问题。我推荐直接用 mysqldump 做整库备份和迁移mysqldump -uroot -p fruit_shop --single-transaction --default-character-setutf8mb4 fruit_shop.sql--single-transaction 参数保证备份过程中不锁表InnoDB 下可以做到一致快照--default-character-setutf8mb4 是为了避免导出的SQL文件变成乱码。导入到目标库时先执行 SET FOREIGN_KEY_CHECKS0 关闭外键检查和 SET NAMES utf8mb4再 source 这个文件。如果你只想同步某几行数据可以用 where 条件导出但不要用第三方同步软件去全表覆盖因为你没法控制它怎么处理主键冲突。数据库文档里如果有版本号或变更记录记得每次改表后同步更新这能省下很多“为什么线上和本地数据对不上”的无谓排查时间。3. Spring Boot后端工程拆解用MyBatis-Plus搭出商品与登录接口源码包里的 Spring Boot 工程目录结构大差不差controller 接受前端请求service 写业务逻辑mapper 操作数据库。真正跑起来之前先看 pom.xml 里的依赖版本和 application.yml 的配置这两个地方最容易产生“第一天启动失败”的卡点。这一章我把后端拆成三个片段项目结构和版本选择、商品分页接口、登录与手机号获取的模拟方案。3.1 项目结构与依赖版本选择为什么建议锁定Spring Boot 2.7.x解压后你会看到典型的 Maven 结构src/main/java/com/fruit下面有 controller、service、mapper、entity、config、common 这些包。如果是用 MyBatis-Plusentity 里是 TableName 注解的实体mapper 里是继承 BaseMapper 的接口。我建议你先看 pom.xml 里父工程的版本这是第一个坑。很多源码包基于 Spring Boot 2.x 写成javax.servlet的包名在 Spring Boot 3.x 里被改成了jakarta.servlet如果你本机装的是 3.x 的 Spring Boot直接编译会报“程序包javax.servlet不存在”。所以拿到源码先确认版本再决定改不改代码。下面是毕设项目最稳妥的依赖组合parent groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-parent/artifactId version2.7.18/version relativePath/ /parent dependencies dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-web/artifactId /dependency dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-boot-starter/artifactId version3.5.5/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-api/artifactId version0.11.5/version /dependency dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt-impl/artifactId version0.11.5/version scoperuntime/scope /dependency /dependencies这里的版本选择不是随意的。Spring Boot 2.7.18 是 2.x 的最后一个社区版本稳定且不会有 jarkata 迁移问题MyBatis-Plus 3.5.5 兼容 Boot 2.x如果你用 3.5.7 以上加 Boot 3.x需要额外引入 mybatis-plus-spring-boot3-starter 这个单独的坐标。JWT 库选 jjwt 0.11.5因为它同时提供 api 和 impl避免了 0.9.x 的老旧空实现问题。如果你不想用 JWT也可以用 Spring Session 或一个简单的 token 表但 JWT 在微信小程序这种“无状态接口”场景更常见。3.2 商品分页接口Controller、Service和MyBatis-Plus的Page商品列表是小程序首页的入口后端接口通常是 GET /api/fruit/list?page1size10返回分页数据。MyBatis-Plus 自带分页插件只要配置了 PaginationInnerInterceptor就能在一个 Page 对象里拿到记录和总数。Controller 可以写成这样RestController RequestMapping(/api/fruit) public class FruitController { Autowired private FruitService fruitService; GetMapping(/list) public ResultIPageFruit list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { PageFruit p new Page(page, size); IPageFruit result fruitService.page(p); return Result.success(result); } }这段代码的逻辑很简单把 page 和 size 包装成 MyBatis-Plus 的 Page 对象调用 IService 里自带的 page 方法底层会自动执行 SELECT * FROM fruit LIMIT ? OFFSET ?。返回的 IPage 对象里有 records、total、current、pages 四个字段前端正好可以用。但这里有一个隐患SELECT * 会把 description 这种长文本也查到列表页根本用不到。我一般会在 service 层加一个 LambdaQueryWrapper只查询需要的字段LambdaQueryWrapperFruit wrapper new LambdaQueryWrapper(); wrapper.select(Fruit::getId, Fruit::getName, Fruit::getPrice, Fruit::getUnit, Fruit::getImage, Fruit::getStock) .orderByDesc(Fruit::getCreateTime); IPageFruit result fruitService.page(p, wrapper);参数说明page 从 1 开始size 不要给太大小程序端一屏一般 10 条足够如果要做“分类筛选”在 wrapper 上再加 .eq(Fruit::getCategory, category) 即可。这里需要注意如果实体里 stock 被查出来了前端就能看到实时库存但有些源码包会把 stock 字段隐藏只让后端判断库存足不足避免用户刷接口压低库存。看你的产品策略毕设阶段展示库存没关系。3.3 登录与获取手机号的接口真实微信登录流程与本地模拟方案小程序端登录不是用用户名密码而是调用 wx.login 拿一个临时 code后端拿这个 code 去微信接口换 openid 和 session_key。但源码包往往没给你真实的 appid 和 secret所以里面的登录接口大概率是 mock 的。我建议保留 mock 逻辑同时把真实流程的注释写清楚答辩时能说清楚就行。下面是一个典型的本地登录接口设计PostMapping(/api/auth/login) public ResultString login(RequestBody LoginDTO dto) { // 真实场景用 dto.getCode() 调用微信 jscode2session 接口 // 换取 openid 后查 user 表没有则创建用户 // 本地模拟直接把 userId 当成登录凭证 String userId dto.getUserId() ! null ? dto.getUserId() : 1L; User user userService.getById(userId); if (user null) { throw new BizException(用户不存在); } String token JwtUtil.createToken(user.getId().toString(), user.getRole()); return Result.success(token); }注意这里 LoginDTO 里需要两个字段code 和 userId。真实前端调用 wx.login 后会拿到 code 传过来但你本地没有微信 appid所以先用 userId 顶替。JwtUtil 的 createToken 方法我用的是 jjwt 的标准写法public static String createToken(String subject, String role) { return Jwts.builder() .setSubject(subject) .claim(role, role) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() 7 * 24 * 3600 * 1000L)) .signWith(Keys.hmacShaKeyFor(SECRET.getBytes(StandardCharsets.UTF_8)), SignatureAlgorithm.HS256) .compact(); }token 有效期设 7 天移动端用户不会频繁登录7 天过期后让小程序再调一次 wx.login 即可。SECRET 是硬编码在配置里的字符串至少 32 字节。关于“获取手机号”真实流程是用户点击button open-typegetPhoneNumber前端拿到 code 后传给后端后端再调微信接口换手机号。这个接口要求小程序完成认证、非个人主体、且用户主动触发很多毕设项目根本满足不了。所以源码包里十有八九是写死手机号或者在登录接口里默认给一个 13800000000。如果你做的是毕设不需要纠结这个把 mock 注释写清楚真机上线时替换成微信接口调用即可。还要注意 Spring Boot 的 application.yml 配置数据库连接串很容易漏参数server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/fruit_shop?useUnicodetruecharacterEncodingutf8mb4useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456url 里的 serverTimezoneAsia/Shanghai 必须加否则 MySQL 8.x 默认用 UTC 时间你插入的订单时间会比北京时间早 8 个小时。MyBatis-Plus 还要在启动类上配分页插件这是很多新手漏掉的不配分页插件 Page 查询会查全表。4. 微信小程序端对接Spring Bootrequest封装、token存储与导航栏适配小程序端是整个系统离用户最近的一层。拿到源码先看 app.json 里注册了哪些页面再看 utils 里有没有封装好的 request.js。很多源码包的小程序端写得比较粗糙页面数据直接写在 onLoad 里请求也没有统一错误处理。这一章我按“目录结构、请求封装、列表页渲染”三步走帮你把小程序端和后端接口的链路打通。4.1 小程序目录结构与tabBar配置微信小程序的目录是固定套路根目录的 app.json 是全局配置pages 下面每个页面一个文件夹包含 .js、.wxml、.wxss、.json 四个文件。utils 放公共工具函数components 放自定义组件。对于水果商城tabBar 是最常见的底部导航配置在 app.json 里{ pages: [ pages/index/index, pages/cart/cart, pages/order/order, pages/profile/profile ], tabBar: { color: #999999, selectedColor: #4CAF50, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/cart/cart, text: 购物车 }, { pagePath: pages/order/order, text: 订单 }, { pagePath: pages/profile/profile, text: 我的 } ] }, window: { navigationBarTitleText: 水果商城, navigationBarBackgroundColor: #4CAF50, navigationBarTextStyle: white } }pages 数组第一个元素是启动页必须是首页否则编译后打开你的自定义页面。tabBar 的 list 里 2 到 5 个元素图标可以暂时不配但如果不配tabBar 上就只有文字你可以接受这个样式。这里的 url 是页面路径不要写成带斜杠的相对路径。如果你的源码包是 uniapp 写的那目录结构会是 src/pages打包到微信开发者工具时生成dist/dev/mp-weixinapp.json 在生成目录里不要改源目录的 app.json。顺便说一句如果你用 uniapp 微信小程序打包页面路由和 tabBar 配置都在 pages.json 里本质和原生小程序的 app.json 是同一套逻辑。4.2 用Promise封装wx.request统一带上token微信小程序的 wx.request 默认是回调风格用起来很痛苦。源码包里通常会有一个 utils/request.js它把 wx.request 包成 Promise并且把 token 自动加到 header 里。下面是一个我常用的封装// utils/request.js const BASE_URL http://127.0.0.1:8080; 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 { wx.showToast({ title: res.data.message || 请求失败, icon: none }); reject(res); } }, fail: (err) { wx.showToast({ title: 网络请求失败, icon: none }); reject(err); } }); }); } module.exports { request };这段代码做了三件事把 url 和 BASE_URL 拼接把 storage 里的 token 塞进 header把后端统一响应体里的 data 解出来返回。使用的时候在 page 里这样调const { request } require(../../utils/request.js); Page({ data: { list: [], loading: false }, async onLoad() { const data await request(/api/fruit/list, GET, { page: 1, size: 10 }); this.setData({ list: data.records }); } });这里有两个坑BASE_URL 如果写的是 localhost在开发者工具里可能会被解析成 IPv6 的 ::1导致连接不上后端。我建议写 127.0.0.1或者直接写你电脑的局域网 IP比如 http://192.168.1.100:8080。真机预览时必须保证手机和电脑在同一 Wi-Fi 下并且后端要允许局域网访问——Spring Boot 默认是监听 0.0.0.0 的问题不大。另外如果后端接口返回的状态码不是 200 而是 401你需要去路由刷 token 或者让用户重新登录。这个封装只处理了成功和失败没有做 401 跳转可以自己补一个。还有一个常见问题开发者工具里请求 http 地址必须勾选“不校验合法域名”。这个配置在右上角的“详情-本地设置”里默认是不勾选的很多新手第一次跑通本地联调就是卡在这里报错码是 10002。这个后面避坑章单独说。4.3 水果列表页onLoad拉数据、setData渲染、图片懒加载列表页是商城门面逻辑不复杂但细节不少。wxml 里用 wx:for 渲染水果卡片js 里通过 onLoad 触发请求滚动到底部再触发下一页。下面是带分页的 index.js 写法const { request } require(../../utils/request.js); Page({ data: { fruitList: [], page: 1, size: 10, hasMore: true, loading: false }, onLoad() { this.loadFruitList(); }, async loadFruitList() { if (this.data.loading || !this.data.hasMore) return; this.setData({ loading: true }); try { const data await request(/api/fruit/list, GET, { page: this.data.page, size: this.data.size }); this.setData({ fruitList: this.data.fruitList.concat(data.records), page: this.data.page 1, hasMore: data.current data.pages }); } catch (e) { // 错误已由 request.js 统一 toast } finally { this.setData({ loading: false }); } }, onReachBottom() { this.loadFruitList(); } });关键逻辑在 hasMore 的判断后端返回的 IPage 里有 current 和 pages只要 current pages 就还有下一页。每次请求结束后 page 加 1下次 onReachBottom 触发时请求下一页。注意这里用 concat 而不是直接覆盖否则翻页会把前面的数据清掉。loading 标志防止用户在数据返回前连续触发多次 onReachBottom这个卡顿问题在真机上特别明显不加这个标志很容易出现重复请求。wxml 里给图片加 lazy-load 属性这是原生小程序支持的图片懒加载view classfruit-card wx:for{{fruitList}} wx:keyid image src{{item.image}} modeaspectFill lazy-loadtrue / text{{item.name}} {{item.price}}元/{{item.unit}}/text button bindtapaddToCart>mysql -uroot -p --default-character-setutf8mb4 fruit_shop fruit_shop.sql进入 MySQL 里也可以先执行 SET NAMES utf8mb4; 再 source 文件。然后改 Spring Boot 的 datasource url把 characterEncodingutf8mb4 明确写进去注意是 utf8mb4 不是 utf8。检查 mysql 配置文件 my.cnf 的 [client] 和 [mysqld] 两段都设 character-set-serverutf8mb4然后重启 MySQL。如果数据已经乱码了把表 drop 掉重新导入不要想着用 UPDATE 修回来因为 ? 已经丢失了原始字节信息。5.2 小程序请求一直pending最后报10002现象微信开发者工具里网络请求转圈很久然后 fail控制台报 request:fail 或者错误码 10002。原因10002 是 wx.request 失败时常见的错误码最普遍的原因是开发者工具默认校验合法域名http://127.0.0.1 不在白名单里其次是 BASE_URL 写错比如末尾多了一个斜杠、或者用了 localhost 导致 IPv6 解析失败。解决打开微信开发者工具右上角“详情”-“本地设置”勾选“不校验合法域名、web-view业务域名、TLS 版本以及 HTTPS 证书”这一项。这一步只对开发者工具有效真机预览依然需要你在手机上设置“调试模式”或者用工具自带的“真机调试 2.0”。另外把 BASE_URL 改成 http://127.0.0.1:8080不要写 http://localhost:8080。如果你要真机调试BASE_URL 改成电脑的局域网 IP比如 http://192.168.1.100:8080同时关闭 Windows 防火墙对 8080 端口的拦截。注意项目上线时小程序要求所有 request 域名必须是 HTTPS 且在小程序管理后台配置 request 合法域名这是硬性要求开发阶段可以绕过。5.3 Spring Boot启动失败端口被占用或javax不存在现象启动时报 Web server failed to start. Port 8080 was already in use或者编译期报“程序包javax.servlet不存在”。原因第一本机的 8080 端口被其他进程占了第二Spring Boot 版本和源码不匹配源码是 2.x 的 javax你用了 3.x 的 starter3.x 把包名换成了 jakarta。解决先换端口改 application.yml 里 server.port 为 8081这是最快办法。如果想清理占用进程Windows 下执行 netstat -ano | findstr 8080 拿到 PID再用 taskkill /pid /f。对于 javax 不存在的问题看到 pom.xml 的 spring-boot-starter-parent 版本是 2.7.x 就不会有这个问题如果是 3.x要么把版本改回 2.7.18要么全局替换 import javax 为 import jakarta并升级 MyBatis-Plus 到 mybatis-plus-spring-boot3-starter。这里还有个隐藏坑JWT 的 jjwt 0.11.x 不依赖 javax/jakarta但一些老版本源码用的 io.jsonwebtoken:jjwt:0.9.0 是依赖 javax.xml.bind 的在 JDK 11 上会报 ClassNotFoundException直接用 0.11.5 替代即可。5.4 微信小程序登录获取手机号失败报错invalid code现象点击“获取手机号”按钮后端返回 invalid code或者小程序端直接提示“该小程序没有权限获取手机号”。原因获取手机号并不是 wx.login 的 code而是 button 组件 open-typegetPhoneNumber 触发后返回的 code它要求小程序已经通过微信认证、主体不是个人、且在“小程序后台-功能-手机号快速验证组件”中开通。毕设项目通常用测试号或者个人小程序根本不满足条件。解决本地开发阶段不要碰真实手机号接口直接把手机号字段写死一个测试值。在后端登录逻辑里加一个配置开关Value(${mock.phone:true}) private boolean mockPhone; if (mockPhone) { user.setPhone(13800000000); user.setOpenid(mock_ dto.getUserId()); } else { // 调用微信 getPhoneNumber 接口替换为真实逻辑 }这样答辩时你可以说真实手机号获取已实现但需要认证小程序才能演示本地用 mock 值代替。别觉得这是糊弄事生产环境调试手机号组件本来就需要正式资质很多公司开发联调也是这么干的。5.5 数据库同步软件导数据之后订单和商品对不上现象用 Navicat 的“数据传输”或某些数据库同步软件把几张表从开发库导到生产库后订单表里的 fruit_id 在商品表中找不到或者导入时直接报“键重复”。原因GUI 同步工具默认按你勾选的表顺序一张张传不分析外键依赖。比如你先导了 orders再导 fruit但 orders 里外键 fruit_id 对应的记录在目标库还不存在外键约束就失败如果不勾外键又会把错误的引用导进去。另外自增主键在源和目标不一致时工具不会重置 AUTO_INCREMENT导致新插入的数据撞主键。解决整库迁移用 mysqldump不要用 GUI。先导出mysqldump -uroot -p fruit_shop --single-transaction --set-gtid-purgedOFF fruit_shop.sql导入时先关外键检查mysql -uroot -p fruit_shop -e SET FOREIGN_KEY_CHECKS0; SOURCE fruit_shop.sql; SET FOREIGN_KEY_CHECKS1;如果只改了一两张表你可以只导出那张表mysqldump -uroot -p fruit_shop fruit --whereid 100 --no-create-info fruit_incr.sql增量同步不要覆盖整表加上 --no-create-info 只导数据就行。同步完成后跑一遍订单明细和商品的 left join查出所有 fruit_id 为 NULL 的订单项那就是垃圾数据要么补水果要么删订单。这能避免你上线后用户突然发现“订单里的苹果变不存在了”。5.6 微信小程序顶部导航栏高度适配iPhone刘海屏把标题顶出屏幕现象同样的代码在安卓上正常在 iPhone 14 上页面顶部内容被状态栏遮挡自定义导航按钮点击没反应。原因如果你的小程序页面在 app.json 里设置了 navigationStyle: custom那么页面内容会从屏幕最顶部开始渲染但刘海屏的状态栏高度约 44pxiPhone 的刘海区域不是 0。你手动写的顶部搜索栏或标题栏没有给状态栏让位就被系统时间、信号栏盖住。解决在 app.js 的 onLaunch 里把状态栏高度存到全局变量App({ onLaunch() { const sys wx.getSystemInfoSync(); this.globalData.statusBarHeight sys.statusBarHeight || 20; this.globalData.navBarHeight sys.statusBarHeight 44; // 胶囊按钮高度约32px自定义导航时用 }, globalData: {} });页面 wxml 里这样用view classcustom-nav stylepadding-top: {{statusBarHeight}}px; 我的水果商城 /view需要注意wx.getSystemInfoSync 在不同微信基础库版本里可能被废弃现在推荐用 wx.getWindowInfo 和 wx.getMenuButtonBoundingClientRect但源码包如果是老项目getSystemInfoSync 仍然可用。如果你想用纯 CSS 适配可以在 app.wxss 里设置page { padding-top: constant(safe-area-inset-top); padding-top: env(safe-area-inset-top); }但这种方式只对 iPhone 生效安卓不识别且对于自定义导航栏不够精确。所以最稳的做法还是 JS 读取状态栏高度再传给页面样式。如果你的项目是 uniapp 微信小程序打包则用 uni.getSystemInfoSync()逻辑一样。6. 进阶玩法把毕设水果商城改成可上线的小程序还差几步当你能把源码包跑通、看完数据库和接口这个项目就只是“能演示”。想让它真正参与运营还需要补四件事。一是商品图片不要用本地路径改放到对象存储上小程序对图片域名有白名单上线时所有资源域名都必须是 HTTPS 并完成备案。二是下单接口必须加事务和库存扣减的乐观锁否则多人同时买最后一件水果会超卖我在第2章写的 UPDATE 方案可以直接用。三是在 Spring Boot 里加一个简单的后台管理接口至少能改价格和库存哪怕不做页面用 Postman 调用也算。四是把数据库从 MySQL 单机升级到主从同步或者给热点水果加 Redis 缓存降低频繁读库的压力。以我的经验最容易被忽略的就是事务。当年我把下单接口写成先查库存再 update没加 Transactional压测时卖出了超过库存数量的订单最后只能人工退款。后来改成UPDATE fruit SET stock stock - #{quantity} WHERE stock #{quantity}并放在事务里影响行数为0就抛异常才彻底解决。另一个实用技巧是给订单表加一个延迟关闭机制——用户提交订单后15分钟内未支付定时任务自动把状态改成已取消并回补库存这是生产环境水果商城的标配。实现方式很简单用 Spring Boot 的 Scheduled 每30秒扫一次 orders 表找 create_time 超过15分钟且 status0 的订单批量改状态并回补对应订单项的库存。如果你只是交毕设做到事务和定时任务这两点就足够在答辩时加分了。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑