多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

旅游小程序源码落地指南:Spring Boot后端与MySQL联调避坑全解析

旅游小程序源码落地指南:Spring Boot后端与MySQL联调避坑全解析 简介面向计算机专业毕业设计或课程设计场景这套基于微信小程序的旅游服务软件完整实现了客户端与后端联动。后端采用Java与SSM框架前端为微信小程序开发者使用IDEA与微信开发者工具分别导入工程并安装MySQL8.0即可直接运行无需额外改配置。功能上覆盖旅游攻略、旅游资讯、景点信息、搜索查询、酒店信息、论坛中心、门票预订、酒店预订、推荐路线、用户发帖及用户管理等多个模块从信息浏览到在线预订形成闭环。资源包共包含22个文件大小约61.15MB其中有19张PNG界面截图、2个ZIP源码压缩包以及1个SQL数据库脚本分别对应效果展示、前后端代码和数据库初始化数据。目前已有2604人学习下载适合需要快速搭建可演示项目或借鉴模块设计的学生与开发者。1. 拿到一套旅游小程序源码先别急着跑前端很多团队第一次接触带源码的旅游服务小程序项目都以为最难的是前端页面实际上手才发现黑匣子全在后端和数据库。我见过不止一个小组拿到“基于微信小程序旅游服务软件设计与实现项目源码数据库文件后端java开发”这类交付物后卡在同一个地方数据库文件导不进去、Java后端启动报错、小程序请求接口连不通——三天没跑出一套能演示的系统。这篇文章就围绕这套交付物讲清楚三件事后端Java工程怎么落地、数据库文件怎么处理、小程序和后端联调有哪些坑。适合毕业设计、课设团队也适合第一次接前后端分离小程序外包的开发者熟悉 Spring Boot 的老手可以直接跳到第 5 章看联调避坑清单。2. 三端一库这套旅游服务软件的架构到底怎么拆“微信小程序旅游服务软件”这个名字听起来像一个完整产品但拆开看它其实是三个独立部件微信小程序前端、Java 后端、MySQL 数据库文件。搞明白这三块怎么协同后面跑通才有方向。2.1 交付物里通常躺着三块内容前端工程、后端工程与数据库文件按照常见的交付形态你会拿到一个压缩包或一个 Git 仓库打开后大致是这个结构travel-miniapp/ # 微信小程序前端 ├── pages/ │ ├── index/ # 首页景区列表、轮播图 │ ├── scenic/ # 景区详情、地图定位 │ ├── order/ # 门票/导游订单 │ └── mine/ # 个人中心、收藏、登录 ├── utils/ # 请求封装、工具函数 ├── app.js / app.json # 小程序入口与全局配置 └── project.config.json # 开发者工具项目配置 travel-server/ # Java 后端Spring Boot ├── src/main/java/com/... │ ├── controller/ # REST 接口层 │ ├── service/ # 业务逻辑层 │ ├── mapper/ # MyBatis-Plus 数据访问层 │ ├── entity/ # 实体类 │ ├── config/ # 跨域、拦截器、全局配置 │ └── TravelApplication.java ├── src/main/resources/ │ ├── application.yml # 数据源、端口、MyBatis 配置 │ └── mapper/ # XML 文件如果走 XML 方式 └── pom.xml travel-db/ └── travel.sql # 数据库脚本建库建表 初始数据这段目录结构不是某个固定模板而是这类交付最常见的工程组织。前端和后端是两个独立工程靠 HTTP 接口通信这就是所谓的前后端分离项目实战。数据库脚本单独放一个目录是因为后端启动前必须先把它导进 MySQL否则一运行就会因为找不到表直接翻车。2.2 为什么这样选原生小程序、Spring Boot 与 MySQL 的取舍先聊技术选型。旅游服务类小程序前端用原生微信小程序就够了页面量级一般在十个左右首页、景区列表、详情、下单、订单列表、个人中心。原生小程序渲染性能好、调试顺手没有必要在这类项目里引入 uniapp 再做一层打包如果你未来要把同一套代码发到支付宝小程序再考虑 uniapp 也不迟。后端用 Java 和 Spring Boot是这个行业里最不冒险的选择。Spring Boot 把配置简化了一大截内嵌 Tomcat一个 main 方法就能起服务对“拿到源码要快速跑起来”的场景特别友好。数据访问层常见做法是用 MyBatis-Plus因为这类项目全是标准的单表增删改查MyBatis-Plus 的 BaseMapper 直接给你把 insert、update、selectById 都实现好了少写大量样板代码。数据库用 MySQL理由就更直接了交付的数据库文件绝大多数是 .sql 脚本换成别的库还得改方言。旅游业务里有地理位置字段MySQL 的 DECIMAL 存经纬度足够真的需要按距离排序时再用空间函数也来得及。地图展示一般是在小程序端集成腾讯地图或天地图组件后端只需要把景区的经纬度字段返回给前端——这是旅游类小程序和普通商城类小程序最不一样的业务点。2.3 请求链路过一遍小程序到后端到数据库的调用关系把三块串起来看一次完整的“查看景区列表”请求是这样的微信小程序页面 ↓ wx.request携带 token 后端 Controller 接收请求 ↓ 校验登录态 参数 Service 层业务处理 ↓ 调用 Mapper MyBatis-Plus 执行 SQL ↓ MySQL 返回结果 → 逐层封装 → 小程序渲染为了让这条链路稳定接口设计上有个约定俗成的规范所有后端接口统一以 /api 开头返回体固定为 JSON 结构{ code: 0, msg: success, data: { list: [], total: 58 } }code 为 0 表示业务成功非 0 表示业务失败data 里放业务数据。小程序端封装请求时只需要统一判断 code 就能处理所有成功和失败分支。后端如果用了 Result 泛型类代码里会经常看到 Result.success(...) 和 Result.error(...)后面第 4 章的接口示例会再展示。3. 让数据库文件落地导入SQL脚本与核心表设计拆解数据库文件是整套系统的地基。很多新手拿到 .sql 文件的第一反应是双击打开、复制粘贴到 Navicat 里执行结果报错一串。原因通常是字符集不一致或数据库没先创建好。正确处理方式是先建库、再导脚本。3.1 导入SQL文件命令行与可视化工具两条路先在 MySQL 里创建一个空库再导入 SQL 脚本。命令行方式最省事mysql -uroot -p CREATE DATABASE travel_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE travel_db; SOURCE /path/to/travel.sql;前两步手动完成关键在于字符集数据库必须用 utf8mb4。原因很直接微信小程序用户昵称里带 emoji 是常态而 emoji 是四字节字符utf8 存不下导入或写入时会报Incorrect string value: \xF0\x9F\x98\x80...这种错。utf8mb4 是 utf8 的超集能覆盖四字节字符排序规则用 utf8mb4_unicode_ci 对中文排序也更合理。如果你习惯用 Navicat操作路径是新建数据库 travel_db字符集选 utf8mb4创建完成后右键该库选择“运行 SQL 文件”选中 travel.sql 执行。这里有个细节Navicat 运行 SQL 文件时如果脚本里已经包含了 CREATE DATABASE 和 USE 语句你也可以直接在 root 连接上运行整个脚本但很多交付脚本不写这两句所以我还是建议先手工建库再导。3.2 核心表结构拆解用户、景区、订单、评论怎么建旅游服务软件的表不会太多核心就四张用户表、景区表、订单表、评论表。读懂这几张表你就读懂了整个系统的业务骨架。下面是简化后的建表结构CREATE TABLE user ( id BIGINT NOT NULL AUTO_INCREMENT, openid VARCHAR(64) NOT NULL COMMENT 微信openid唯一标识, nickname VARCHAR(64) DEFAULT NULL COMMENT 昵称, avatar VARCHAR(255) DEFAULT NULL COMMENT 头像URL, phone VARCHAR(20) DEFAULT NULL COMMENT 手机号, created_at DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 注册时间, PRIMARY KEY (id), UNIQUE KEY uk_openid (openid) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT用户表; CREATE TABLE scenic ( id BIGINT NOT NULL AUTO_INCREMENT, name VARCHAR(128) NOT NULL COMMENT 景区名称, cover VARCHAR(255) DEFAULT NULL COMMENT 封面图URL, images TEXT COMMENT 详情图片JSON数组或逗号分隔, description TEXT COMMENT 景区介绍, address VARCHAR(255) DEFAULT NULL COMMENT 地址, latitude DECIMAL(10,7) DEFAULT NULL COMMENT 纬度, longitude DECIMAL(10,7) DEFAULT NULL COMMENT 经度, price DECIMAL(10,2) DEFAULT 0.00 COMMENT 门票价格, stock INT DEFAULT 0 COMMENT 当日剩余票数, PRIMARY KEY (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, scenic_id BIGINT NOT NULL COMMENT 景区ID, quantity INT DEFAULT 1 COMMENT 购买数量, total_amount DECIMAL(10,2) DEFAULT 0.00 COMMENT 订单金额, status TINYINT DEFAULT 0 COMMENT 0待支付 1已支付 2已取消, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 下单时间, PRIMARY KEY (id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT订单表; CREATE TABLE comment ( id BIGINT NOT NULL AUTO_INCREMENT, user_id BIGINT NOT NULL, scenic_id BIGINT NOT NULL, content VARCHAR(500) DEFAULT NULL, rating TINYINT DEFAULT 5 COMMENT 1-5星, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), KEY idx_scenic_id (scenic_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT评论表;注意几个设计细节。openid 是微信用户的唯一标识每次 wx.login 都会返回同一个 openid用它做唯一键能防止同一个用户重复注册。订单表特意加了订单号 order_no而不是直接用自增 id因为订单号要暴露给用户和小程序用时间戳加随机数生成的业务编号既安全又方便对账。景区表里 images 用 TEXT 存 JSON 字符串是这类项目的常见做法——小程序端解析 JSON 数组即可没必要为图片列表单独建一张表。经纬度用 DECIMAL(10,7)可以精确到小数点后 7 位大约厘米级精度对景区定位绰绰有余。price 用 DECIMAL(10,2) 而不是 FLOAT是因为浮点数在金额计算上会有精度丢失这是做支付相关表时的基本要求。3.3 初始数据与开发态账号导入后先做这三步验证SQL 脚本里除了建表语句一般还会带一批 INSERT 初始数据这是让项目“开箱即演示”的关键。导入完成后别急着启动后端先执行三条验证语句USE travel_db; SHOW TABLES; SELECT COUNT(*) FROM scenic; SELECT id, nickname, phone FROM user LIMIT 5;SHOW TABLES 确认四张核心表都建出来了COUNT(*) 看景区表有没有数据如果返回 0说明脚本里没附初始数据后端接口能通但页面是空的查 user 表是为了确认手机号字段结构对不对方便后面调试登录。这里最容易忽视的是开发态账号。交付的脚本里通常会有一个或多个测试用户openid 是类似test_openid_001的占位字符串。为什么要这样设计因为真用户必须在微信开发者工具里通过 wx.login 才能拿到真实 openid。开发阶段你写的 SQL 查询、手工构造的订单数据都得挂在一个固定的 openid 下才能用测试账号在小程序里看到效果。4. 后端Java工程从配置启动到接口跑通的完整链路数据库就绪后接下来把 Java 后端跑起来。这一章我会按环境准备、配置文件、接口实现三个层面讲每一层都有具体的操作步骤和失败时的观察点。4.1 环境准备JDK、Maven、IDEA 与 Lombok 插件后端工程是 Maven 项目环境要求一般是 JDK 8 或 11Maven 3.6 以上。如果前一个人用的是 JDK 8 开发的而你本机装了 JDK 17最好在 IDEA 里把 Project SDK 切到 8 或 11避免编译报错。这里的 java 基础问题经常会卡住新手Spring Boot 版本和 JDK 版本有对应关系老版本的 Spring Boot 2.x 在 JDK 17 下运行会有兼容隐患。还有一个高频翻车点工程里用了 Lombok。交付源码里实体类常见这样的写法Data public class User { private Long id; private String openid; private String nickname; }Data 注解会在编译时自动生成 getter、setter、toString但如果 IDEA 没装 Lombok 插件编译会直接报“找不到符号 getNickname()”之类的错误。这不是代码问题是环境问题。装好插件后还要在 IDEA 设置里开启 Annotation Processing否则插件不生效。我一般拿到工程第一件事就是确认这两个设置能省半小时的无谓排查。4.2 配置application.yml数据源、端口与MyBatis-Plus后端启动前必须改对配置文件。打开 src/main/resources/application.yml核心内容长这样server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/travel_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0大概需要改三处端口、数据库账号密码、时区参数。serverTimezoneAsia/Shanghai 必须有MySQL 8 驱动默认要求显式时区不加会报The server time zone value Öйú±ê׼ʱ¼ä is unrecognized。driver-class-name 用 com.mysql.cj.jdbc.Driver这是 8.x 驱动的类名老版本驱动类是 com.mysql.jdbc.Driver如果工程的 pom.xml 里引的是 mysql-connector-java 8.x却写了老驱动名启动时同样会失败。mybatis-plus.map-underscore-to-camel-case 这个配置是 MyBatis-Plus 自动把数据库的 create_time 映射成 Java 属性 createTime必须设成 true否则第 5 章会讲到字段全为 null 的坑。启动方式是标准的两种IDEA 里直接运行 TravelApplication 的 main 方法或者命令行执行mvn spring-boot:run看到Started TravelApplication in x.xx seconds就说明启动成功。启动失败的日志里如果出现Access denied for user rootlocalhost就是账号密码配错了出现Unknown database travel_db说明第 3 章的建库步骤没有执行。4.3 接口分层与小程序请求封装REST规范落地后端工程按 Controller、Service、Mapper 三层分包。拿景区列表接口做例子Controller 层长这样RestController RequestMapping(/api/scenic) public class ScenicController { private final ScenicService scenicService; public ScenicController(ScenicService scenicService) { this.scenicService scenicService; } GetMapping(/list) public ResultIPageScenic list(RequestParam(defaultValue 1) Integer page, RequestParam(defaultValue 10) Integer size) { return Result.success(scenicService.pageList(page, size)); } GetMapping(/{id}) public ResultScenic detail(PathVariable Long id) { return Result.success(scenicService.getById(id)); } }这里用构造器注入而非 Autowired 字段注入是 Spring 官方推荐的方式好处是依赖关系清晰测试时可以直接 new 一个 Controller 传入 mock Service。分页参数 page 和 size 都有默认值防止前端没传参时接口报 NPE。返回类型统一用 Result 这个类在工程里的 common 包下通常包含 code、msg、data 三个字段它的静态方法 success 和 error 负责封装统一返回结构。Service 层和 Mapper 层在 MyBatis-Plus 下非常薄往往就是继承一个 ServiceImplService public class ScenicServiceImpl extends ServiceImplScenicMapper, Scenic implements ScenicService { public IPageScenic pageList(Integer page, Integer size) { return this.page(new Page(page, size)); } }小程序端对应的请求封装是所有页面共用的工具const BASE_URL http://localhost:8080/api; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method, data, header: { Content-Type: application/json }, success: (res) { if (res.data.code 0) { resolve(res.data.data); } else { wx.showToast({ title: res.data.msg, icon: none }); reject(res.data); } }, fail: (err) reject(err) }); }); } module.exports { request };这段封装把“业务失败”和“网络失败”做了区分code 不等于 0 时弹 toastreject 出去网络不通时走 fail 回调。页面里调用时就只管拿数据不用每个页面都写 toast 逻辑。BASE_URL 是联调时最常改的地方本地跑后端就写 localhost真机调试要换成电脑的局域网 IP上线后换 HTTPS 域名。后面第 5 章会细说这个切换的坑。5. 联调与部署避坑后端Java和小程序对接的5个常见问题这一章写的都是我见过无数次的现场翻车。每条都按“现象 → 原因 → 解决”来讲你对照着排查就行。5.1 真机预览连不上后端localhost 的幻觉现象微信开发者工具里请求接口一切正常一到真机预览所有列表加载不出来Network 面板显示请求超时。原因小程序开发者工具运行时localhost 指向的是你的电脑真机上 localhost 指向的是手机自己。手机根本访问不到电脑上的 8080 端口。解决把 BASE_URL 里的 localhost 换成电脑的局域网 IP比如http://192.168.1.5:8080/api并保证手机和电脑连同一个 WiFi。另外在开发者工具的“详情 → 本地设置”里勾选“不校验合法域名、web-view业务域名”这是调试阶段必备选项否则连 localhost 也会被拦。这里需要提醒这个勾选只用于本地开发和真机调试上线版本必须去掉。体验版和正式版要求所有请求域名在微信公众平台配置为合法域名且走 HTTPS这是微信的强制规则逃不掉。真机预览时如果手机浏览器能打开http://192.168.1.5:8080/api/scenic/list但小程序里不通九成是 WiFi 隔离或防火墙挡了端口。5.2 数据库连接启动报错时区与驱动名现象后端启动控制台抛Cannot create PoolableConnectionExceptionCaused by 里有一行时区相关的错误或者ClassNotFoundException: com.mysql.jdbc.Driver。原因MySQL 连接串里没带 serverTimezone或者驱动类名和依赖版本不匹配。MySQL 8.x 驱动强制要求时区老驱动类名在新驱动里已经被移除了。解决照抄第 4.2 节的配置url 末尾加上serverTimezoneAsia/Shanghai驱动类名换成com.mysql.cj.jdbc.Driver。如果还报Public Key Retrieval is not allowed在 url 里追加allowPublicKeyRetrievaltrue即可这是 MySQL 8 使用 caching_sha2_password 认证时的已知行为。5.3 接口通了但字段全空驼峰映射没开现象调用景区列表接口HTTP 200 返回了但每条记录的 createTime、userName 这类多单词字段全是 null单单词字段正常。原因数据库字段是下划线风格 create_timeJava 实体是驼峰风格 createTimeMyBatis 默认不自动转换需要开启 map-underscore-to-camel-case。解决确认 application.yml 里mybatis-plus.configuration.map-underscore-to-camel-case: true。如果配置没问题再检查实体类字段上有没有 TableField 注解覆盖了映射规则。还有一种隐蔽情况实体类用了 Data 但字段名拼写和数据库不一致比如数据库是 scenic_idJava 写了 scencId这种只能靠人工逐个核对。5.4 登录与手机号获取token 会话的两个坑现象小程序端登录成功后过一会儿再请求接口就报“未登录”或者 getUserInfo 能拿到昵称头像但 wx.getPhoneNumber 换手机号时后端一直报解密失败。原因很多简单项目没有做 token 机制每次请求只传 openid而手机号获取依赖 session_keysession_key 在每次 wx.login 之后会失效。如果你先调了 wx.login 又调 wx.getPhoneNumber两次登录会刷新 session_key后端拿旧的 session_key 解密手机号必然失败。解决登录流程按下面这条路走。首先用 wx.login 拿 code后端拿着 code 去微信接口换 openid 和 session_key接着后端生成自己的 token比如 UUID存到 Redis 或数据库并把这个 token 返回给小程序小程序把 token 存进 storage后续所有请求在 header 里带Authorization: token。后端通过拦截器校验 token而不是每次拿 openid 现查。这样既避免频繁查库也让会话过期可控。wx.getPhoneNumber 的正确调用姿势是在用户点击“手机号快速验证”按钮时把按钮上的 code 传给后端后端用当前会话的 session_key 解密。千万别在调 wx.login 之后再取手机号这是血泪经验。项目代码里如果用的是“先 wx.login 再 getPhoneNumber”的错误顺序那就得改掉。5.5 上线后白屏与图片加载失败域名校验与 HTTPS现象本地演示一切都好提交体验版之后页面白屏或者景区图片全部裂开。原因两个问题叠加。白屏通常是请求被微信拦截——wx.request、wx.uploadFile 等接口的域名必须在微信公众平台配置为合法域名且必须是 HTTPS。图片裂开是第二个问题如果图片地址是 http 或者 IP 地址小程序同样会拒绝加载。解决正规做法是准备一个已备案的域名配好 SSL 证书然后通过 Nginx 把/路径请求转发到后端的 8080 端口。很多树莓派和国内服务器用户直接用宝塔面板操作面板里配站点、装 SSL、做转发都可视化了比手写 Nginx 配置省事不少。后端自身的跨域配置也要检查Spring Boot 里写一个 WebMvcConfigurer 的 CORS 配置允许https://你的域名来源否则浏览器环境里调试 H5 页面时会报跨域错误——小程序本身没有跨域限制但如果这个系统以后要加管理后台网页端跨域配置就得提前留好。6. 从能跑到能交付接口自测清单与上线检查项系统在本地能跑通离“能交付”还有距离。我的习惯是打开小程序之前先做一轮纯后端自测。6.1 先别打开小程序Postman 接口自测清单按业务主流程列一份接口自测顺序获取景区列表、查看景区详情、登录换 token、创建订单、查询订单列表、发表评论、查看评论列表。每测一个接口同时看两处响应码是否 0data 里关键字段是否完整。发现字段为 null 或列表为空优先查数据库数据和 mapper 映射别急着改小程序端。Postman 里可以先把 token 存在环境变量里这样测订单、评论这类需要鉴权的接口时不用每次手动填 Authorization 头。6.2 自定义导航栏适配顶部导航栏高度动态计算旅游类小程序首页喜欢做沉浸式头部把景区大图延伸到导航栏底下这就涉及自定义导航栏。这里的经典坑是不同机型顶部安全区高度不一样刘海屏和普通屏差了 20 多个像素。常见做法是在小程序启动时读取胶囊按钮位置动态计算导航栏高度const menu wx.getMenuButtonBoundingClientRect(); const statusBarHeight wx.getSystemInfoSync().statusBarHeight; const navBarHeight (menu.top - statusBarHeight) * 2 menu.height;这段代码把状态栏高度和胶囊按钮位置结合起来得出来的 navBarHeight 就是自定义导航栏的实际高度。写死在样式里只能适配你手头那台测试机一换 iPhone 14 Pro 就露馅。放在全局 app.js 或者独立工具文件里所有页面都能复用。顺着这个思路交付前最后做一遍这三件事换一台不是自己日常用的手机跑真机预览关掉开发者工具的“不校验合法域名”选项模拟体验版环境把数据库里的测试账号密码改成空白并输入你自己的账号密码确认别人拿到源码后能在 10 分钟内按 README 跑起来。我自己的习惯是每次交付都把 README 里的启动步骤从零执行一遍专治“我机器上能跑”的玄学问题。这套流程走下来希望你少踩我当年踩过的坑希望帮到你。本文还有配套的精品资源点击获取
返回列表