读这个标题的人,大概率不是第一次做管理系统了。SpringBoot + Vue这套组合在市面上已经被写到烂,随便一搜就是"xx管理系统源码",但真正能落到生产环境、经得起业务推敲的智慧旅游项目反而不多。很多所谓的源码就是把增删改查套了一层景点外壳,页面华丽但业务逻辑经不起问。我这次拆解的这座智慧旅游服务平台,不堆砌花哨功能,重点放在“游客、景区、商家、平台”四方角色如何在一个系统里跑通,以及前后端在真实联调、部署过程中最容易卡住的那些细节。项目源码和配套文档的结构,我会一并说明白,方便你直接拿去做毕业设计、课程设计或者二次开发的底子。
1. 项目形态与核心需求拆解
先把这个项目到底是什么说清楚。智慧旅游服务平台,本质上是一个面向C端游客、B端景区/商家、以及后台运营方的多角色业务系统。市面上很多同类项目只做了游客端的小程序/H5和后台管理,忽略了商家入驻、订单分账、景区导览这些真实业务流转环节,导致项目看起来完整,但一讨论业务就露馅。
这个项目的核心业务流可以拆成四条主线:
- 游客侧:注册登录、浏览景点与攻略、在线购票、预订酒店/民宿、查看旅游线路、发表游记评价、投诉建议。
- 商家侧:景区/酒店/旅行社入驻申请、商品上下架、订单处理、结算对账。
- 平台运营侧:内容审核、数据统计、用户管理、 banner 配置、系统参数维护。
- 公共支撑:地图导览、天气接入、路线推荐、消息通知、支付回调。
我见过不少项目把四条线强行压成两张表,最后做出来就是“景点信息表 + 订单表”,支付对接没有、审核流程没有、商家端就是摆设。这个项目在设计时做了比较务实的取舍:游客端、商家端、平台后台三端分离,角色权限走 RBAC,核心业务表按领域拆分。
从技术角度看,这项目不是简单的 CRUD 堆积。它涉及了文件上传、富文本内容管理、地图坐标存储与展示、订单状态机流转、支付回调验签、定时任务处理过期订单、Redis 缓存热点景点数据等多类后端常见场景,而这些恰好是企业里问得最多的点。
注意:真正的智慧旅游平台,重点不在“智慧”二字的技术包装,而在数据能否流转起来。游客行为数据、商家经营数据、平台运营数据如果互相割裂,那和传统 OA 没有本质区别。
2. 技术选型理由与整体架构设计
2.1 为什么是 SpringBoot + Vue,而不是微服务
项目定位是中大型单体应用,面向的是毕设、课设、小规模商用场景,没有自建机房和千亿级并发的顾虑,SpringBoot + Vue这是当下最稳妥的选择。SpringBoot 的自动装配机制极大简化了 Spring 体系的配置成本,内嵌 Tomcat 让部署变成“一个 jar 包扔到服务器”,这对没有专职运维的团队来说是刚需。
Vue 这边选择的是 Vue 2 + Element UI,不是 Vue 3 + Element Plus。原因很现实:网上可参考的组件案例、踩坑博客、二次封装方案,Vue 2 的存量远超 Vue 3;如果你学校或者公司的基础设施里还跑着老项目,Vue 2 的经验可以无缝迁移。另外项目代码生成器、权限指令、动态路由这些工具链在 Vue 2 下非常成熟。你说 Vue 3 组合式 API 写起来更爽,我承认,但在这个项目里,稳定性和可参考性优先。
2.2 前后端分离架构与目录规划
前后端分离是标配,但分离到什么程度有讲究。这个项目的分离不只是“前端一个项目、后端一个项目”这种物理隔离,更体现在开发流程和部署方式上:
- 后端只提供 RESTful API,不做页面跳转,所有接口统一返回
Result<T>结构体。 - 前端通过 Nginx 反向代理
/api前缀解决跨域和接口转发。 - 静态资源(图片、景点介绍附件、上传文件)单独走文件存储路径,不混入前后端工程目录。
后端目录我按 DDD 的轻量思想做了分层,但没有过度设计:
src/main/java/com/example/tourism/ ├── common/ # 通用返回体、异常处理、常量、工具类 ├── config/ # 全局配置:跨域、拦截器、Redis、MyBatis-Plus ├── controller/ # 接口层,只负责参数接收和结果封装 ├── service/ # 业务层,事务和核心逻辑都在这一层 ├── mapper/ # MyBatis-Plus 数据访问层 ├── entity/ # 数据库实体映射 ├── dto/ # 前端交互的数据传输对象,避免实体直接暴露 └── vo/ # 视图对象,按前端需要组装前端目录则按业务模块划分,常见的views/按角色拆成admin、merchant、client三个子目录,公共组件放在components/下,API 请求统一放在api/模块下,每一个页面组件对应一个 API 文件,避免请求代码散落在页面里。
2.3 核心依赖清单及其版本坑
依赖版本是这类项目最容易翻车的地方,直接贴出我实测可用的版本组合:
| 组件 | 版本 | 说明 |
|---|---|---|
| JDK | 1.8 | 不要用 JDK 17 跑 SpringBoot 2.x,会有一堆 module 访问报错 |
| SpringBoot | 2.7.x | 2.x 最后的稳定大版本,社区资料最多 |
| MyBatis-Plus | 3.5.x | 注意确认和 SpringBoot 2.x 的兼容性 |
| MySQL | 5.7 或 8.0 | 8.0 需要配置时区,建议直接 5.7 省心 |
| Redis | 5.x+ | 用默认配置跑热点缓存即可 |
| Vue | 2.6.x | 配合 Element UI 2.15.x |
| Node | 14.x/16.x | Node 18 打包老项目容易报 OpenSSL 错误,建议用 16 |
特别提醒:如果你用了 SpringBoot 2.7 + MyBatis-Plus 3.5.3 的组合,
application.yml里一定要写global-config: banner: false,否则启动时会有无伤大雅但看着很烦的警告。另外,SpringBoot 版本太高的坑主要在 javax 到 jakarta 的命名空间迁移,老教程里的import javax.servlet在 SpringBoot 3.x 下全部编译不通过,这也是我坚持让你用 2.7.x 的原因。
3. 后端核心模块的实现思路与关键表设计
3.1 用户体系与 RBAC 权限控制
用户体系是平台的基石,这个项目把用户分为四类角色:普通游客、景区管理员、商家、平台管理员。如果你做过 RBAC 就知道,最忌讳的做法是给每类角色建一张用户表,字段大量冗余不说,后续做统一登录、统一认证会痛苦到怀疑人生。
这里的做法是:一张sys_user表存账号密码和基本信息,一张sys_role表维护角色,一张sys_user_role关联表维护用户角色关系,菜单权限通过sys_menu和sys_role_menu控制。游客、商家、管理员本质上只是同一张用户表上挂了不同角色。
SpringBoot 集成 Spring Security + JWT 做认证授权。流程是这样的:用户登录时校验账号密码,成功之后生成 token 返回前端;前端每次请求在 header 里带上Authorization: Bearer <token>;后端通过 OncePerRequestFilter 拦截请求,解析 token 得到用户 ID,并从 Redis 里取出该用户的权限标识列表。
使用 Redis 缓存权限数据的好处是:用户权限变更(比如商家被平台下架)不需要强制用户重新登录,只要 Redis 里的权限数据刷新即可,这比每次都查数据库高效得多。
3.2 景点、线路、攻略的内容域表设计
内容域的几张表是整个平台的数据底座,需要仔细设计。首先是景点表scenic_spot,除了基础的名称、简介、封面图、经纬度、开放时间、联系电话之外,我特意加了一个status字段(0 待审核、1 已上架、2 已下架),所有景点信息必须先经过平台审核才能展示,这是合规要求。
旅游线路表travel_route和景点之间是多对多关系,路由点按顺序排列,所以加了一个route_order字段,而不是简单地存一个景点 ID 列表。攻略表travel_guide的正文是富文本,存储时用 HTML 格式,预览时直接渲染;其中封面图、标题、作者 ID、浏览量、点赞数、审核状态都是高频查询字段,需要建好索引。
三张表的关系你可以这样理解:景点是基础素材,线路是景点的有序组合,攻略是基于景点和线路的 UGC 内容。在写列表查询时,如果只查单表数据,MyBatis-Plus 的Page分页足够用;一旦要关联用户昵称、景点名称,就要手写 XML 里的自定义 SQL,用 LEFT JOIN 聚合并映射到 VO 上。这是这个项目里最考验基本功的地方。
3.3 订单状态机与支付回调的流程设计
订单是业务系统的核心,状态字段不是简简单单一个status(0/1/2)就完事。这个项目里订单设计了七种状态:
| 状态值 | 含义 | 触发动作 |
|---|---|---|
| 0 | 待支付 | 用户提交订单 |
| 1 | 已支付/待使用 | 支付回调成功 |
| 2 | 已使用/已消费 | 景区扫码核销 |
| 3 | 已完成 | 订单核销后自动完成 |
| 4 | 已取消 | 用户取消或超时未支付 |
| 5 | 退款中 | 用户发起退款申请 |
| 6 | 已退款 | 平台审核后原路退回 |
状态流转的代码实现不能散落在 service 的各个方法里,否则后续维护的人看着看着就疯了。这个项目用一个OrderStatusHandler来统一承载状态流转的判断逻辑,核心是维护一张状态流转表,只有合法的迁移路径才允许执行。比如待支付状态下可以执行“支付”和“取消”,但已支付状态下不能执行“支付”。
支付这块接的是微信支付 Native 支付。流程不复杂但细节多:前端调用后端下单接口,后端生成预付单并返回code_url,前端用二维码展示;支付成功后微信服务器异步回调后端接口,回调里要验证签名、校验订单金额、防止重复通知,然后更新订单状态。这里最容易出问题的点有两个:回调地址必须是公网可访问的 URL,且响应微信服务器必须返回{"code": "SUCCESS"}的明文,否则微信会一直重试回调。
超时未支付订单的关单我用的是延迟任务方案:用户下单时把订单号丢到一个 Redis 有序集合里,score 设为超时时间戳;定时任务每分钟扫一次,把超时的订单捞出来执行关单。这个方案比简单的@Scheduled轮询数据库高效一些,实现也不复杂。
4. 前端几个关键页面的落地细节
4.1 前端权限控制与路由守卫
前端权限控制的核心是“按钮级别”和“路由级别”双管齐下。路由级别用动态路由:用户登录后,后端返回该用户的菜单权限集合,前端router.addRoutes动态挂载对应路由;这样未登录用户即使手动输入 URL,也因为没有对应路由而跳转到 404 页。
按钮级别控制用的是自定义指令v-permission,指令内部判断当前用户是否拥有指定权限标识,如果没有就直接移除 DOM 元素。
// 在 main.js 里注册全局指令 Vue.directive('permission', { inserted(el, binding) { const required = binding.value const hasPermission = store.getters.permissions.includes(required) if (!hasPermission) { el.parentNode && el.parentNode.removeChild(el) } } })<el-button v-permission="'order:refund'" type="warning">退款</el-button>这里有个实际坑要提醒:动态路由在用户刷新页面后会丢失,因为刷新时 Vuex 数据重新初始化,动态 addRoutes 的路由又没了。解决办法是在main.js的全局前置守卫里加一个“是否已动态添加”的判断,用户刷新后先调一次获取用户信息的接口,拿到权限后再重新 addRoutes,用 if 判断避免死循环。
4.2 地图选点与景点坐标展示
景点都有经纬度坐标,后台地图选点这个交互做不好就特别低级。这里用的是 Vue 集成高德地图 JS API 2.0 的方案。做法是在index.html里引入高德地图的 JavaScript 文件:
<script src="https://webapi.amap.com/maps?v=2.0&key=你的Key&plugin=AMap.Scale,AMap.ToolBar"></script>然后在组件里定义全局AMap变量,地图初始化逻辑放在mounted生命周期里。地图选点功能的核心是给地图绑定click事件,用户点击地图后通过e.lnglat.getLng()和e.lnglat.getLat()拿到坐标,再回填到表单的隐藏字段里。
展示端则是在景点详情页初始化地图,使用AMap.Marker把坐标点标记出来。如果你不会申请高德 Key,也可以用 Leaflet + OpenStreetMap 的替代方案,但对国内景点来说高德的 POI 数据更准,地图瓦片在国内的加载速度也更好。
4.3 H5 视频播放 M3U8 流媒体的兼容处理
热门景区会做实时直播或者风景慢直播,这类视频流大多是 M3U8 格式(HLS 协议)。原生<video>标签不支持播放 M3U8,需要在项目中集成hls.js这个库。
npm install hls.js --save前端播放逻辑封装成一个通用组件:
<template> <video ref="videoRef" controls></video> </template> <script> import Hls from 'hls.js' export default { name: 'HlsVideo', props: { src: { type: String, required: true } }, mounted() { this.initVideo() }, methods: { initVideo() { const video = this.$refs.videoRef if (Hls.isSupported()) { const hls = new Hls() hls.loadSource(this.src) hls.attachMedia(video) this.hls = hls } else if (video.canPlayType('application/vnd.apple.mpegurl')) { // Safari 原生支持 HLS,直接赋值 src video.src = this.src } else { this.$message.error('当前浏览器不支持视频播放') } } }, beforeDestroy() { this.hls && this.hls.destroy() } } </script>特别提醒:M3U8 播放时如果视频地址是 HTTPS、页面是 HTTP,会出现混合内容拦截,视频一片黑。开发环境下确保后端视频服务地址和前端协议一致,生产环境统一走 HTTPS。
4.4 富文本编辑器与图片上传联动
攻略编辑这块用的是vue-quill-editor或者wangeditor。选择后者的话注意,v4 版本已经不再维护,直接装 v5 的@wangeditor/editor-for-vue。工具栏里图片上传功能默认是把图片转 Base64 塞进内容里,这样做有两个隐患:一是 HTML 体积爆炸,二是数据库存储压力大。必须改成自定义上传逻辑:用户选择图片后先调用后端上传接口,拿回图片 URL,再插入编辑器内容区。
import { Boot } from '@wangeditor/editor' import { uploadImage } from '@/api/file' const editorConfig = { MENU_CONF: { uploadImage: { async customUpload(file, insertFn) { const res = await uploadImage(file) insertFn(res.data.url, res.data.url, res.data.url) } } } }后端文件上传接口的存储路径要根据日期分目录,避免一个目录下文件过多,文件名使用 UUID 重新命名,防止中文文件名乱码和路径遍历攻击。
5. 数据缓存、定时任务与部署环节的实战坑点
5.1 热点数据缓存策略与一致性处理
景点列表、首页 banner、旅游攻略 Top10 这些数据属于读多写少的高频数据,适合放 Redis 缓存。这里的实现思路是:查询时先查缓存,缓存没有则查数据库并回填缓存,设置过期时间(比如 30 分钟);景点信息或攻略被后台修改后,主动删除对应缓存,而不是更新缓存,这样能避免并发下缓存和数据库不一致的问题。
缓存穿透的防护要做,否则有人恶意请求不存在的景点 ID,请求会直接打到 MySQL 上。简单方案是布隆过滤器,更简单粗暴的方案是:查询结果为空时也缓存一个空值,设置较短的过期时间(比如 5 分钟),这样能在绝大多数场景下挡住非法请求。
5.2 定时任务处理订单超时与数据统计
SpringBoot 的@Scheduled在这个项目里承担两件事:一是每分钟扫描 Redis 有序集合中过期的待支付订单,触发关单操作;二是每天凌晨 2 点定时统计前一天的运营数据写入报表表。
这里有个值得注意的坑:@Scheduled默认是单线程串行执行的,如果你配置了两个任务,其中一个执行时间长,另一个会被阻塞等待。解决方式是在启动类上加@EnableScheduling,并配置一个线程池支持多任务并行执行:
@Configuration public class SchedulingConfig implements SchedulingConfigurer { @Override public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { taskRegistrar.setScheduler(Executors.newScheduledThreadPool(10)); } }5.3 前后端打包部署与 Nginx 配置
后端打包成 jar 包,用 Maven 的package命令执行,生成文件在target/目录下。前端打包执行npm run build,产物在dist/目录。生产环境用 Nginx 托管前端静态文件,并反向代理后端 API:
server { listen 80; server_name your-domain.com; # 前端静态资源 root /usr/share/nginx/html; index index.html; # 解决 Vue Router history 模式刷新 404 location / { try_files $uri $uri/ /index.html; } # 后端接口反向代理 location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }注意:
proxy_pass http://127.0.0.1:8080/;结尾的/绝对不能漏。proxy_pass http://127.0.0.1:8080;会把完整的/api/login路径传给后端,而加了/之后会去掉/api前缀再转发,后端 Controller 里定义的 RequestMapping 就不会出现前缀不匹配的问题。
前端打包之后布局异常的排查也是重灾区。Vue 项目打包后容易出现样式丢失、图片 404、图标不显示,常见原因主要是这三个:一是publicPath没配置,打包后的 CSS/JS 路径是绝对路径/js/app.js,部署到子目录就直接挂掉,需要在vue.config.js里配置publicPath: './';二是路由模式用了 history,刷新后 Nginx 没配置try_files,飞到了 404 页面;三是图片、字体等静态资源用了绝对路径引用,解决办法是统一走相对路径或者使用 CDN。
5.4 跨域、数据库连接与端口冲突排查实录
开发环境下,前端跑在localhost:8081,后端跑在localhost:8080,必然有跨域问题。后端我这里没有用 Spring 的@CrossOrigin注解一个个加,而是在WebMvcConfigurer里写了一个全局跨域配置,允许的路径是/api/**,允许的请求头包含Authorization,允许的方法包含 OPTIONS、GET、POST、PUT、DELETE。这样保持了配置统一,不会出现“这个接口能访问、那个接口不能被访问”的玄学问题。
数据库连接建议使用 HikariCP,SpringBoot 2.x 默认就是它。连接池配置不要照抄网上的大数值,小项目给maximum-pool-size: 10,minimum-idle: 5,connection-timeout: 30000足够了。如果 MySQL 8.0 连不上,十有八九是时区问题,JDBC URL 后面手动加上serverTimezone=Asia/Shanghai和useSSL=false。
端口冲突的问题用一句命令排查:
netstat -ano | findstr 8080 # Linux/macOS 用: lsof -i:8080定位到占用进程后,要么杀掉进程,要么改 SpringBoot 配置里的server.port。如果改端口,要注意前端vue.config.js里的 devServer proxy 目标地址也得同步修改,这是新手最容易漏掉的一处。
6. 源码结构说明与配套文档的写作方案
6.1 源码目录结构与关键代码位置对照
一套能直接拿去用的源码,目录结构必须清晰,关键代码位置可快速定位。这个项目的后端关键代码位置对照如下:
| 功能模块 | 关键文件/包路径 |
|---|---|
| 登录认证与 JWT | controller/AuthController.java、utils/JwtUtil.java |
| 权限过滤拦截 | config/SecurityConfig.java、filter/JwtAuthenticationTokenFilter.java |
| 景点管理 | controller/ScenicSpotController.java、service/impl/ScenicSpotServiceImpl.java |
| 订单流转 | controller/OrderController.java、service/impl/OrderServiceImpl.java、handler/OrderStatusHandler.java |
| 支付回调 | controller/PayNotifyController.java、service/impl/WxPayServiceImpl.java |
| 文件上传 | controller/FileController.java、config/FileStorageConfig.java |
| 数据统计 | controller/StatisticsController.java、service/impl/StatisticsServiceImpl.java |
前端的关键代码位置对照如下:
| 功能模块 | 关键文件 |
|---|---|
| 动态路由与权限 | src/router/index.js、src/store/modules/permission.js |
| 全局请求封装 | src/utils/request.js |
| 后台管理布局 | src/layout/index.vue |
| 地图选点 | src/views/admin/scenic/ScenicEdit.vue |
| M3U8 播放组件 | src/components/HlsVideo/index.vue |
实体类、Mapper、Service 的代码生成我用的 MyBatis-Plus 代码生成器,一键生成之后手动补业务逻辑,能省下大量重复工作。生成器模板里注释要留好,把字段注释和表注释带出来,后续看代码不用反复开数据库看表结构。
6.2 配套文档的层次划分与写作建议
很多同学项目做完了才想起来写文档,最后写出来的东西就是操作手册和代码注释的缝合怪,答辩的时候老师一问三不知。我建议这套项目的文档分为四个层次,各有侧重:
项目部署文档:面向想让项目跑起来的人。包含环境要求、初始化数据库脚本的执行步骤、后端
application.yml的各项配置说明、前端npm install到npm run serve的完整过程、默认账号密码列表。这一部分一定要写清楚,很多人在环境依赖上卡死。系统设计文档:面向需要理解项目的人。重点写系统架构图、功能模块清单、数据库 ER 图、核心表结构说明、关键业务流程(登录认证、下单支付、审核上架)的流转描述。每个功能模块配一张页面截图和核心逻辑描述。
接口文档:面向需要二次开发的开发者。推荐直接用
knife4j或者springdoc-openapi自动生成 OpenAPI 文档,不用手写。但要注意在注释里写清楚参数的取值范围、是否必填、示例值,这样生成的接口文档才有实用价值。答辩/汇报 PPT 大纲:面向课程答辩或毕业答辩。按照“项目背景 → 需求分析 → 技术架构 → 功能演示 → 核心难点与解决方案 → 总结与展望”这条线组织内容。这里特别强调一个答辩技巧:不要通篇讲你做了什么功能,要挑一两个你解决得最漂亮的技术难点展开讲,比如订单状态机、支付回调幂等性设计,这些才是加分项。
6.3 项目 README 文件的写法和开源注意点
如果这套源码要开源或者作为作品集给面试官看,README 是门面。一份合格的 README 至少要包含:项目简介、技术栈列表、功能截图、快速开始步骤、目录结构说明、默认账号、常见问题列表。我见过很多项目源码好端端的,README 就一句话“旅游平台系统”,面试官下载下来跑不起来,第一印象直接崩掉。
另外要注意,如果你在网上参考了其他项目的代码,要留意许可证问题。GPL 协议的代码是不能直接抄进自己项目再闭源的。个人学习和毕设用途问题不大,但如果要商用或公开开源,需要谨慎处理。
7. 从零复现这个项目的实施路线建议
7.1 四个阶段的推进节奏与验证标准
如果你打算从零开始做这样一个项目,我建议按四个阶段推进,每个阶段有明确的验收标准,避免一盘散沙做到一半想放弃。
第一阶段:跑通最小闭环。目标是完成“用户注册登录 → 浏览景点列表 → 下单 → 模拟支付成功 → 查看订单”这五步。这一阶段不需要做商家端和管理后台,但数据库表设计要一步到位,后端接口返回的数据结构也要按最终格式设计。
第二阶段:完善角色与权限。给用户表加角色字段,引入 Spring Security + JWT,实现基于角色的权限控制。接着做商家入驻申请、平台审核、景点上架/下架这几条审核链路。做完之后验收标准是:游客账号访问不到商家后台,商家账号看不到平台管理菜单。
第三阶段:业务闭环深化。接入真正的文件上传、富文本编辑、地图组件,把攻略发布、线路规划、视频直播这些内容型功能补上。同时把订单状态机、支付回调、退款申请这些涉及资金安全的逻辑做完整。
第四阶段:打磨与部署。写单元测试或者接口自测脚本,把异常分支(重复提交订单、重复支付回调、并发下单超卖)都测一遍。然后配置 Nginx、部署到云服务器,申请 HTTPS 证书,把整个系统跑在公网上。这一步做完,项目才算真正从“能运行”变成“能交付”。
7.2 开发过程中的版本管理与备份策略
开发过程中版本管理一定要用 Git,不要觉得自己一个人写就不需要。我见过太多人写着写着把代码改坏了,Ctrl+Z 都救不回来,最后只能从头再来。推荐的 Git 提交频率是:功能完成一个提交一个,提交信息写清楚干了什么,比如feat: 完成游客下单接口、fix: 修复支付回调重复通知导致订单状态错乱。
数据库变更也要纳入版本管理。每次修改表结构,导出一个新的 SQL 增量脚本放在sql/migrations目录下,按日期命名。这样不管在哪台机器上部署,执行一遍初始化脚本加增量脚本,数据库环境就能保持一致。
7.3 二次开发时可以扩展的方向
这套项目源码的扩展空间很大,按投入产出比排序,我推荐这几个方向:
- 接入 Redis 分布式锁解决并发下单超卖问题,这是面试高频考点。
- 引入 Elasticsearch 做景点和攻略的全文检索,替换掉现在的 MySQL LIKE 模糊查询。
- 将小程序端纳入体系,复用现有的后端接口,不过需要处理微信登录态和接口鉴权。
- 增加消息通知模块,用 WebSocket 实现订单状态变更的实时推送。
- 引入 Docker Compose 一键部署,把后端、前端、MySQL、Redis 都容器化,交付体验直线上升。
我自己的体会是,这类全栈项目最能锻炼人的地方往往不是某个技术栈有多深,而是“把所有零件组装在一起还能稳定运转”的那种全局掌控感。你今天遇到的跨域问题、版本冲突、打包资源丢失,都是未来工作里每天都要打交道的东西。做项目就像搭积木,前期地基打得正,后面每一层都顺;前期图快跳过细节,后面返工的成本只会翻倍。这套智慧旅游平台源码里,我最满意的地方就是订单状态机那段设计——它不复杂,但把业务边界画得明明白白,这种思维方式比任何框架都值钱。