要说这几年Java后端最容易撞车的手写项目,“基于SpringBoot的旅游网站管理系统”绝对排得进前三。这个题目我在毕设季帮着看过多套代码,自己也完整重构过一版,说实话它表面看着有点老套,但把SpringBoot体系里最常被问到的能力全串起来了:与MyBatis的整合、事务控制、登录鉴权、文件上传、静态资源映射、多环境配置、Docker打包。整体覆盖的技术点足够全面,又没有电商系统那种吓人的并发压力,对刚学完SpringBoot基础、想用真实业务把知识串起来的人来说,是性价比很高的练手对象。这篇文章就从我实际动手的主线出发,把从需求拆解到最终部署的完整过程写清楚,每处关键选择都讲讲当时的理由。正在做毕设、项目实训,或者纯粹想拿SpringBoot练手的同学,应该能从这里找到可以直接参考的路径。
1. 标题看似普通,背后其实是一套完整的业务闭环
1.1 项目定位先想清楚
“旅游网站管理系统”这个名字,核心是后面四个字——“管理系统”。很多同学一看到“旅游网站”就想着把页面做得花里胡哨,结果花了大把时间调轮播图、调CSS,后端逻辑反而一塌糊涂。实际上这个项目的重心在于:面向游客的前台展示 + 面向运营者的后台管理。
我重构这套系统时对着需求反复捋过,最终拆成了两条线:
- 前台(游客视角):注册登录、浏览旅游线路、查看景点详情、酒店信息展示、生成旅游订单、发表评论、收藏线路。
- 后台(运营者视角):用户管理、线路管理、景点信息维护、酒店与房型管理、订单处理、评论审核、公告发布,以及菜单权限的配置。
这个结构本身就是很多企业应用的通用骨架。前台解决“用户怎么用”,后台解决“运营怎么管”,中间是数据流和业务状态流转。SpringBoot的定位就是快速搭建后端服务,把这一整套CRUD加业务逻辑加权限控制的流程走一遍,你也就基本掌握了企业里最普通的后端开发日常。
1.2 功能清单要先收敛,别让需求膨胀
任何管理系统最怕“做着做着需求越来越大”。比如加个“拼团优惠”,再来个“积分商城”,项目直接失控。所以我建议动手之前先做一张功能清单,把它当成需求合同,后面所有设计都以它为准。
| 模块 | 前台功能 | 后台功能 |
|---|---|---|
| 用户 | 注册、登录、个人中心 | 用户列表、禁用/启用、角色分配 |
| 线路 | 列表、搜索、详情 | 新增/编辑/上下架、价格管理 |
| 景点 | 详情展示、关联线路 | 维护景点基础信息 |
| 酒店 | 列表、详情 | 维护酒店与房型数据 |
| 订单 | 提交订单、模拟支付、取消订单 | 订单查询、状态流转、经营统计 |
| 评论 | 发表评论、点赞 | 评论审核、删除 |
| 公告 | 查看公告 | 发布/下线公告 |
这张表定下来以后,项目边界就清楚了。前台不需要做复杂的营销活动,后台也不需要做细粒度的数据权限,把主流程做扎实,比贪多更有价值。我自己见过太多人把时间耗在“后台四个Tab多二十个页面”上,最后主链路反而没跑通,非常可惜。
1.3 把散落的功能串成一条主链路
功能清单是一张二维表,但实际业务是线性的。我建议先在纸上画一条主链路:用户注册登录 → 浏览线路列表 → 查看线路详情 → 下单支付 → 后台收到新订单 → 处理订单状态 → 用户确认完成 → 发表评论。
这条链路就是整个系统的主动脉。建表时围绕它去设计,接口开发时也围绕它去排优先级。比如第一轮开发我只会做用户、线路、订单这三个核心模块;景点、酒店、评论、公告全部放在第二轮。这样即使时间不够,交付出来的项目也是一个能跑通闭环的产品,而不是一堆没有关系的页面堆砌。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 技术选型别盲目跟风:我为什么锁定SpringBoot 2.7.18
2.1 版本不是越新越好
SpringBoot现在3.x都迭代到很稳定了,为什么我这次仍然选了2.7.18?说实话是被现实教育过。早前我试过一次直接用SpringBoot 3.2搭项目,结果遇到两个问题:一是很多第三方组件对Jakarta命名空间的适配还没跟上来,二是网上老教程里大量javax.*的包名换成jakarta.*之后,照抄代码全都报红。对初学者或者时间紧张的毕设来说,这种版本适配成本极不划算。
2.7.x是SpringBoot 2.x分支的最终维护版本,社区积累的教程、踩坑记录、第三方 Starter 最全,基本所有轮子都是可用的。它基于Java 8,和很多学校机房的教学环境一致,不用额外折腾JDK版本。如果你的目标是快速稳定地完成一个完整项目,2.7.18是锚点;如果是新公司选型或没什么历史包袱的正式项目,再考虑3.x不迟。
2.2 持久层用MyBatis而不是JPA
热词里“springboot整合mybatis”能成为高频搜索词是有原因的。这个项目非常适合用MyBatis,因为旅游业务天生多表关联、查询形态灵活。比如“按价格区间筛线路”“按热度排序”“统计每月订单量”,这些用MyBatis手写SQL更加直观可控。
JPA当然也很优秀,但国内中小型项目和教育体系中MyBatis的普及率明显更高。遇到问题随便一搜就能找到对应解决方案,这在实际开发里是非常重要的隐形优势。整合方式也很简单:引入mybatis-spring-boot-starter,配置mapper-locations,主类上加@MapperScan,剩下的事情交给自动配置。我在后面第4章会具体展开。
2.3 前端方案:前后端分离还是服务端渲染
这个项目经常被拿来配Vue做前后端分离,热词里“springboot vue前后端分离”热度也很高。我这次确实选了前后端分离,但我的理由不是“大家都在用”,而是旅游网站的前台展示页确实需要灵活交互——线路列表的分页筛选、详情的图片预览、订单的异步状态刷新,用Vue前端来处理体验更顺。后台管理界面用现成的Vue管理模板,开发效率也高。
但这里必须泼一盆冷水:如果你是第一次独立做完整项目,前后端分离会直接让工作量翻倍。你不仅要写两套前端工程,还要处理跨域、Token传递、路由守卫这些额外话题。如果你时间不够,稳妥方案是用Thymeleaf做服务端渲染,先把后端逻辑跑通,再考虑拆Vue。别为了“听起来高级”拖垮整个项目进度。
2.4 其他组件的取舍原则
我在项目里最终保留的组件很克制:
- 权限校验:用JWT,不直接上Spring Security。Spring Security当然更强大,但学习曲线陡峭,管理系统这类场景用拦截器加JWT完全够用,而且更容易讲清楚原理。
- 定时任务:用Spring自带的
@Scheduled,比如每天凌晨把线路热度降权。暂不引入Quartz,项目早期用不到分布式调度。 - 文件存储:本地磁盘路径加静态资源映射。前提是Docker部署时把目录挂载出来,后面第6章会细说。
- 接口文档:集成SpringDoc(Swagger的SpringBoot 2.x版本),方便调试也方便答辩展示。
我的取舍原则很简单:SpringBoot本身能支撑的,不额外引入框架;必须引入的,选流行度和资料量最高的那个。
3. 数据表设计决定返工率:旅游业务建模的几个关键点
3.1 核心表结构一次讲透
数据库设计是整个旅游项目里付出回报比最高的一件事。表建合理了,后面写接口顺风顺水;表建拧巴了,后面改一处崩三处。我围绕前面那条主链路,最终保留下这些核心表:
sys_user:用户表,区分游客和管理员,通过user_type字段或角色表绑定。scenic_spot:景点表,存景点名称、介绍、图片、开放时间、门票参考价。travel_line:线路表,存线路名称、出发城市、天数、价格、封面图、行程简介。line_spot_relation:线路与景点的关联表,因为一条线路包含多个景点,一个景点也可能出现在多条线路里。hotel与room_type:酒店表及房型表,订单可能涉及酒店,所以预留。travel_order:订单表,记录用户买了哪条线路、价格、状态、支付时间。order_item:订单明细表,如果以后支持线路加酒店打包购买,这里就派上用场。travel_comment:评论表,用户对线路的评价。favorite:收藏表,处理“收藏线路”这种高频操作。sys_role、sys_menu、sys_role_menu:RBAC权限相关,后台菜单和按钮权限全靠这三张表支撑。
这里最核心的设计是线路与景点的多对多关系。我在最初版偷懒,直接在travel_line表里加了个spot_ids字段用逗号拼接,查询时再拆字符串。后来景区详情要展示关联线路,反向查询直接变成灾难。老老实实建中间表之后,正反两个方向都用一条连接查询搞定,还方便加“景点在线路中的顺序”这类扩展字段。
3.2 订单表用状态机思维来设计
订单是旅游网站最关键的实体,订单表建议至少包含这些字段:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键 |
| order_no | varchar(32) | 唯一订单号 |
| user_id | bigint | 下单用户 |
| line_id | bigint | 线路 |
| total_amount | decimal(10,2) | 应付金额 |
| status | tinyint | 订单状态 |
| payment_time | datetime | 支付时间 |
| create_time | datetime | 创建时间 |
| update_time | datetime | 更新时间 |
状态字段我建议用数字枚举,事先定义好:0-待支付,1-已支付,2-已完成,3-已取消,4-退款中,5-已退款。然后在代码里把“哪些状态可以流转到哪些状态”集中管理,而不是每个接口各写各的判断逻辑。比如只有待支付状态能允许取消,只有已支付状态能发起退款。这能防止后台乱改状态导致订单数据变成一团乱麻。
金额类型要特别强调:不要用double,用decimal(10,2)。旅游线路价格常涉及小数点,浮点类型在地球上就存在精度问题,虽然大多数情况下只差一分钱,但用户和财务都不会放过你。
3.3 冗余与索引的平衡
旅游网站是典型的“读多写少”业务,合理冗余能极大减轻查询压力。我在travel_line表里直接存了封面图URL字段,而没有单独建照片表让每次查询都去关联。景点介绍这种大文本字段也放到了独立的详情表里,主表只保留列表查询需要的短字段,避免大字段拖慢普通查询。
索引这块我踩过坑。最初我只给主键加了索引,结果后台“按订单号查订单”的SQL全表扫描,几万条数据就明显变慢。后来给order_no建了唯一索引,给user_id、line_id、status分别建了普通索引,查询速度立竿见影。原则就是:where条件里高频出现的字段,必须有索引;唯一性要求高的字段,用唯一索引兜底。
3.4 建表时的两个具体教训
第一个教训是评论表的唯一约束。一开始我对业务理解不深,允许用户多次评论同一线路,结果前台展示时评论列表里有大量同一个人的刷屏内容。后来在user_id和line_id上加了联合唯一约束,同时修改业务逻辑为“重复评论时更新原评论”,既满足了功能需求,又保证数据干净。
第二个教训是时间字段的类型。一版用了timestamp,在时区切换时出现过数据显示错乱。后来统一用datetime,在Java侧统一用LocalDateTime,并在JDBC连接串里显式指定serverTimezone=Asia/Shanghai,问题才彻底消停。看似小问题,排查起来非常费劲。
4. SpringBoot整合落地:自动装配、事务与循环依赖绕不开
4.1 自动装配原理不需要背源码,但逻辑要懂
热词里“springboot自动装配原理”常年挂在搜索榜,说明很多人对这个话题又爱又怕。其实用大白话讲:SpringBoot把传统Spring里那一大堆繁琐的XML配置和Java配置,变成了“根据你引入的依赖自动帮你配置好”的机制。核心是@SpringBootApplication里包含的@EnableAutoConfiguration,它通过META-INF/spring.factories文件找到所有自动配置类,再用条件注解判断“这个类是否生效”。
以这个旅游项目为例,你引入spring-boot-starter-web,它就帮你装好内嵌Tomcat和DispatcherServlet;引入mybatis-spring-boot-starter,它就帮你创建SqlSessionFactory。你不需要知道每个Bean是怎么new出来的,但你需要理解“条件不满足时自动配置会默默跳过”这一点。否则当“明明配了数据源还是报找不到DataSource”时,你会无从下手。排查思路是:去看看DataSource的自动配置类有没有生效,检查是不是少了连接池依赖或数据库驱动。
4.2 整合MyBatis时易踩的细节
整合本身不复杂,步骤很固定:
xml复制<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.3.2</version>
</dependency>
yaml复制mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: com.travel.entity
主启动类上加@MapperScan("com.travel.mapper")。这里我吃过一个亏:XML文件放在src/main/resources/mapper下,但忘了在pom.xml里配置resources过滤,导致打包后XML没打进jar,应用启动直接报“Invalid bound statement”。排查半天才发现是构建配置的问题。解决方法是加一段:
xml复制<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
</resource>
</resources>
</build>
另外XML里的namespace必须与Mapper接口全限定名一致,resultMap也别图省事直接写resultType。尤其联表查询时字段名和属性名对不上,返回一堆null,查起来格外痛苦。
4.3 @Transactional失效的典型场景
“springboot事务失效场景”能上热词,说明大家是真的被坑过。我在这个项目里做过一次“批量上架线路”的接口,本意是有一条失败就全部回滚,结果发现部分数据还是写进去了。排查下来命中了两个典型失效场景:
第一,异常被try-catch吞掉。事务是靠异常触发的,你在Service方法里把异常捕获后不抛出,Spring根本感知不到出错了,事务自然不回滚。正确做法是:捕获异常后要通过throw new RuntimeException(...)重新抛出,或者干脆不在业务层做捕获,让异常上抛到全局异常处理器处理。
第二,同类内部调用this.xxx()导致代理失效。@Transactional默认只对代理对象的外部调用生效,同类里的方法直接调用,走的不是代理,事务注解被无视。解决方式是拆到不同的Service类里,或者用@Autowired注入自身的代理对象(不推荐循环依赖)。
还有两个细节:方法必须public,@Transactional加到private方法上是不生效的;建议显式指定rollbackFor = Exception.class,因为默认只对RuntimeException回滚,受检异常抛出来时事务不会回滚。
4.4 循环依赖:能避免就避免
SpringBoot 2.7.x默认允许循环依赖,但这不代表它是对的。我在项目里遇到过一个比较隐蔽的场景:OrderService需要调用UserService获取用户信息,UserService的一个方法里又反向调用OrderService统计用户订单数量,启动时控制台直接报出依赖循环。
解决方案不是开spring.main.allow-circular-references=true把这个错误压下去,而是从根上解耦。我当时抽了一个StatisticsService,把“统计订单数”的逻辑从UserService挪出去,让OrderService依赖StatisticsService,UserService也依赖StatisticsService,循环自然消失。如果暂时没法重构,@Lazy延迟注入可以应急,但长期来看代码的模块边界必须清晰。这算是给管理系统这类业务项目的一个重要提醒:Service别写得太肥,互相调用的关系就容易乱。
5. 管理后台的硬骨头:JWT鉴权、文件上传与配置拆分
5.1 JWT登录鉴权与Swagger放行
热词里“springboot jwt 放开swagger”是真实的需求场景。这个项目里用户端和管理端共用一个后端,不能所有接口都裸奔,也不能全部拦截。我的做法是:用户登录成功后将用户ID和角色信息生成JWT返回前端,前端把Token放到Authorization请求头里。后端写一个HandlerInterceptor,在preHandle里验证Token,没有Token或Token过期直接返回401。
拦截器配路径时,Swagger一定要放行,否则联调文档都打不开:
java复制@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(jwtInterceptor)
.addPathPatterns("/api/**")
.excludePathPatterns(
"/api/user/login",
"/api/user/register",
"/api/line/list",
"/api/line/detail/**",
"/api/scenic/**",
"/swagger-ui/**",
"/v3/api-docs/**",
"/doc.html"
);
}
有个小坑:Swagger相关路径在不同版本里前缀不一样,如果还是旧的swagger-ui.html或/swagger-resources/**,记得一并放行。不然你看到的就是前端控制和“Unable to infer base url”这种莫名其妙的报错。
5.2 菜单角色管理:RBAC模型落表
这个项目叫“管理系统”,后台的菜单和权限是含金量所在。我用的是标准RBAC模型:用户与角色多对多,角色与菜单多对多。用户表不直接存菜单,用户登录后先查角色,再把角色对应的菜单权限汇总成树形结构返回前端,前端据此渲染左侧菜单和按钮。
表结构上就是三张主表配两张中间表:
sys_user(用户表)sys_role(角色表)sys_menu(菜单表,菜单项可以无限层级)sys_user_role(用户角色关联表)sys_role_menu(角色菜单关联表)
这里的实际难点不是建表,而是菜单树的递归组装。我写了一个通用方法:先把某个角色拥有的所有菜单查出来,然后在Java内存里按parentId组装成树,避免在数据库里反复递归查询。数据量不大时这个方案简单高效。后来再扩展按钮权限,只要在sys_menu里加一个menu_type字段区分目录、菜单和按钮,权限判断就完整了。
5.3 文件上传与静态资源映射
旅游项目里图片是刚需:景点图片、线路封面、酒店房型图。我选本地磁盘存储加虚拟URL映射的方式,不引入OSS或其他对象存储,保证项目在任何环境都能直接跑。
先定义配置:
yaml复制upload:
dir: ${UPLOAD_DIR:./upload}
上传接口里用MultipartFile接收文件,按日期分目录保存:
java复制String dateDir = new SimpleDateFormat("yyyyMMdd").format(new Date());
File targetDir = new File(uploadDir + File.separator + dateDir);
if (!targetDir.exists()) {
targetDir.mkdirs();
}
String originalFilename = file.getOriginalFilename();
String ext = originalFilename.substring(originalFilename.lastIndexOf("."));
String newFilename = UUID.randomUUID().toString().replace("-", "") + ext;
file.transferTo(new File(targetDir, newFilename));
String url = "/files/" + dateDir + "/" + newFilename;
然后写一个配置类做资源映射,把磁盘上的./upload目录映射到/files/**:
java复制@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Value("${upload.dir}")
private String uploadDir;
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/files/**")
.addResourceLocations("file:" + uploadDir + "/");
}
}
注意这里的addResourceLocations必须以file:开头,结尾要有/,否则Windows和Linux下都容易踩资源映射404的坑。
5.4 多环境配置拆分
项目从本地到服务器,配置文件的拆分是必须提前做的。我把配置拆了三层:
application.yml:公共配置,如server.port、应用名、SpringBoot基础配置。application-dev.yml:开发环境,数据源指向本地MySQL,日志级别调成DEBUG。application-prod.yml:生产环境,数据源指向云服务器数据库,日志级别调到INFO。
启动时指定环境:java -jar travel-server.jar --spring.profiles.active=prod。这块在Docker部署时尤其重要,镜像里不带具体环境的配置,把这个参数留到容器启动时传进去,同一套镜像就能在多个环境复用。
另外热词里“springboot yml配置map”也很常见。如果需要在配置里自定义一组键值对,可直接在yml里这样写:
yaml复制jwt:
secret: travel-secret-key
expire-hours: 12
admin-paths:
- /api/admin/**
- /api/order/list
对应的配置类用@ConfigurationProperties(prefix = "jwt")来接收,其中admin-paths可以映射到List<String>。这个方式比在代码里硬编码配置要清爽得多,推荐优先使用。
6. 本地到Docker Desktop:打包部署与后期演进方向
6.1 为什么要用Docker Desktop跑JDK1.8项目
热词里“springboot jdk1.8打包到docker desktop”说明很多人在这个环节卡过壳。我最初是在本地直接用java -jar部署,后来因为要频繁清理环境,“污染”了本机JDK和MySQL环境,于是切到Docker Desktop做容器化。Docker Desktop在本地体验容器化是最顺滑的方案,安装后一个命令就能起一个MySQL容器,不用再忍受各种MySQL卸载残留。
JDK1.8的SpringBoot项目打包成镜像并不复杂,核心是选好基础镜像。我一开始试了openjdk:8-jdk-alpine,镜像确实小,但后来发现项目里生成验证码图片的时候,alpine里的字体支持有问题,中文全部乱码。换到eclipse-temurin:8-jre-alpine后问题消失。教训就是:追求镜像体积没错,但要为应用的实际功能预留余地。
6.2 Dockerfile与Compose编排的完整落地
我的Dockerfile长这样:
dockerfile复制FROM eclipse-temurin:8-jre-alpine
LABEL maintainer="travel-project"
WORKDIR /app
COPY target/travel-server-1.0.0.jar app.jar
ENV TZ=Asia/Shanghai
EXPOSE 8080
ENTRYPOINT ["java", "-Xmx512m", "-jar", "app.jar", "--spring.profiles.active=prod"]
这里把TZ=Asia/Shanghai直接写进环境变量,解决容器时区与数据库时间不对齐的问题。JVM参数-Xmx512m是防止容器在内存较小或被其他容器挤压时发生OOM。生产环境可以根据机器内存动态调整。
因为应用需要依赖MySQL,我用docker-compose.yml统一编排:
yaml复制version: "3"
services:
mysql:
image: mysql:5.7
container_name: travel-mysql
environment:
MYSQL_ROOT_PASSWORD: root123456
MYSQL_DATABASE: travel_db
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./sql:/docker-entrypoint-initdb.d
command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
app:
build: .
container_name: travel-server
depends_on:
- mysql
environment:
UPLOAD_DIR: /data/upload
SPRING_PROFILES_ACTIVE: prod
ports:
- "8080:8080"
volumes:
- ./upload:/data/upload
volumes:
mysql_data:
这个编排里有两个关键点。第一,MySQL挂了初始化SQL目录,首次启动自动创建表和测试数据,省去手工导入的麻烦;第二,应用容器的UPLOAD_DIR指向/data/upload,同时宿主机./upload目录挂载进去,这样即使容器销毁重建,上传的图片也不会丢。
6.3 部署版资源映射要注意的坑
前面第5章讲的静态资源映射在容器化部署时有新变化。本地开发时upload.dir可以是项目根目录./upload,但容器里工作目录是/app,如果不显式挂载,容器一删上传的图片就没了。所以我在生产环境里专门把UPLOAD_DIR设置为/data/upload,并且在Compose里做volume挂载,这是整套部署方案最关键的闭环。
另外,如果前端页面也是放在SpringBoot容器里的,比如打包到static目录,还要注意前端路由问题。Vue的history路由模式下刷新页面会404,需要单独配置forward或者改用hash路由。如果前后端分离部署,则要配置Nginx反向代理并处理跨域,这就进入另一个话题了。我的建议是学习阶段先不把Nginx加进来,Compose只跑后端和数据库,前端在本地npm run dev联调,等整体流程顺了再考虑前端容器化。
6.4 部署时最疼的3个问题
第一,时区错乱。容器默认时区是UTC,MySQL写入的时间比北京时间慢8小时。我上面的Dockerfile已经设置了TZ=Asia/Shanghai,但如果你在宿主机直接用java -jar跑,也要在启动命令里带上-Duser.timezone=Asia/Shanghai。同时数据库连接串一定要有serverTimezone=Asia/Shanghai,两处配合才能彻底解决。
第二,端口冲突。Docker Desktop的端口映射如果遇到本机8080被占用,启动会直接失败。解决办法是把Compose里的应用端口改成"18080:8080",宿主机用18080访问,容器内应用仍然是8080,互不影响。
第三,初始化数据没进去。MySQL官方镜像的/docker-entrypoint-initdb.d目录只会在数据目录为空时执行初始化脚本。如果你之前已经跑过一个带数据的MySQL容器,再挂载SQL文件进去不会执行。遇到这种问题,把volume清掉重新创建数据卷即可,命令是docker compose down -v,注意这个操作会删除数据,生产环境慎用。
6.5 后期演进:组件可以加,但别一次加满
项目跑通之后,很多同学喜欢往上堆组件。热词里“springboot整合activemq”“springboot使用flowable”“powerjob springboot集成”都是大家爱关注的方向。以这个旅游项目为底子,确实有很自然的演进路径:
- 线路审核流程、退款审批要多级审批,可以考虑接入Flowable工作流引擎;
- 下单成功后要异步发短信、邮件通知,可以引入RabbitMQ或ActiveMQ做消息解耦;
- 定时统计每日订单量、同步酒店价格,先用
@Scheduled撑住,后面量大了再换PowerJob这类分布式调度; - 线路详情页高并发访问,可以引入Redis做缓存,减少数据库压力。
但我的态度一直很明确:组件是解决痛点的,不是装饰门面的。如果你的项目日活只有几十个人,引入MQ和分布式调度只会徒增维护成本。先把SpringBoot本身的基础能力练扎实,等数据量和业务复杂度真的上来了,再按需演进。
实际操作中我最大的体会是:这个项目最锻炼人的不是某个高深框架,而是把一条完整业务链路从需求到设计、从开发到部署全程打通的能力。你可能会在订单状态流转上栽跟头,也可能会在Docker部署时被时区问题折磨,但每个坑踩过去之后,你对SpringBoot的理解都会上一个台阶。如果你也正拿着这个题目做项目,建议先把登录、线路、下单、后台处理订单这条主链路跑通,再逐步加组件拓展。最后再分享一个小技巧:所有配置项都通过环境变量或application.yml暴露出来,别在代码里写死任何路径和账号,这套系统以后拿到任何一台机器上部署都能顺利跑起来。
