尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSM+SpringBoot旅游网站开发实战:从数据库设计到安全部署

SSM+SpringBoot旅游网站开发实战:从数据库设计到安全部署 简介在Java Web开发领域SSM与SpringBoot的整合是构建企业级应用的经典方案。SSM作为Spring、SpringMVC与MyBatis的组合提供了灵活的分层架构与SQL控制能力而SpringBoot通过自动配置与内嵌容器让项目搭建和部署变得极简。二者并非替代关系而是底座与组件的协作。本文以旅游网站为例从技术选型、表结构与订单状态机设计到搜索、下单、模拟支付等核心业务闭环再到JWTRedis登录鉴权、文件上传安全、PDF与XSS防护等加固细节最后讲解Jar包与Docker部署方案。内容覆盖毕业设计及简历项目的完整链路帮助开发者快速构建一个功能齐全、安全可靠的旅游平台并深刻理解SpringBoot整合MyBatis的工程实践。 最近接了一个挺典型的咨询一个准备做毕设的同学把“SSMSpringboot 一个功能齐全的旅游网站”这个题目发给我第一句话就是“这俩框架是不是二选一我到底学哪个”。这个问题我太熟悉了因为“SSM”和“SpringBoot”这两个词放在一起确实容易让人懵SSM 不是已经被 SpringBoot 取代了吗怎么还能加在一起实际上这两个东西压根不在一个维度上。SSM 指的是 Spring SpringMVC MyBatis 这套经典 Java Web 组合而 SpringBoot 是一个自动配置、开箱即用的开发框架底座。在现代项目里完全可以用 SpringBoot 作为底座上面照样跑 SpringMVC 和 MyBatis这就是标题里“SSM SpringBoot”的真实含义也是很多毕业设计、课程设计、简历项目里最常见的写法。这篇我就以自己完整做过的一个旅游网站项目为例从技术选型、数据库设计、核心业务实现、安全加固一直讲到打包部署把“功能齐全”这四个字落地的过程完整拆一遍。适合准备做毕设或简历项目的同学也适合刚入门 SpringBoot 想跑通一条完整业务链路的初级开发。整个过程不搞花活重点说清楚每一步为什么这么设计以及我实际踩过的坑。1. SSM和SpringBoot不是二选一旅游网站的技术底座怎么搭1.1 标题里的SSM到底指什么很多教程会把“SSM”解释成 Spring SpringMVC MyBatis这个说法没有错但在 SpringBoot 时代它很容易被误解成一个“老掉牙”的框架组合。真实的开发情况是SpringBoot 本身包含了 Spring 的核心能力也内置了 SpringMVC只需要引入对应 Starter 就能用MyBatis 则通过 mybatis-spring-boot-starter 完成整合。所以当你看到“SSM SpringBoot”这个标题时可以理解成下面这个结构SpringBoot项目底座负责自动装配、配置管理、内嵌 Tomcat、健康检查SpringMVC负责前端请求的路由和响应就是平时写的 ControllerMyBatis负责数据库访问所有 SQL 都集中在 Mapper 层管理。这种组合的好处是你既有 SSM 时代“SQL 写起来灵活、分层清晰”的优点又能享受 SpringBoot 带来的极简配置和快速部署。对旅游网站这种业务场景来说恰恰是性价比最高的搭配。1.2 旅游网站为什么适合这套组合功能齐全的旅游网站本质上是一个“内容展示 交易”的系统。它包含景点介绍、旅游线路、酒店信息这类内容型数据又包含注册登录、下单、支付、订单管理这类交易型数据。这种项目最大的特点就是增删改查多、表关联复杂、业务流程长。MyBatis 在这类场景下非常合适因为线路列表、景点详情、订单详情这些页面往往需要多表联查SQL 直接写在 Mapper XML 里调整条件或优化查询都很方便。SpringMVC 的 Controller 足够简单能撑起从用户端到管理后台的所有接口。SpringBoot 则把所有繁琐的 XML 配置、包扫描、数据源初始化都自动化了省下的时间可以全部花在业务逻辑上。另外还得考虑到一个现实因素这类项目经常用来做毕业设计或者简历项目。SpringBoot 是目前企业使用的主流框架掌握它意味着你能直接适应公司项目而 MyBatis 在国内企业里占有率也一直不低。学一套组合等于同时覆盖了旧项目维护和新项目开发的常见场景。1.3 版本选择的建议别盲目上3.xSpringBoot 3.x 已经发布很久了但如果你是为了快速做完旅游网站这类项目我不建议直接从 3.x 开始。原因很简单SpringBoot 3 底层从 javax 包迁移到了 jakarta 包MyBatis 和相关组件的版本要求都变了网上大量旧教程里的配置和方法会直接报错。你本来是想快速搭项目结果光折腾包名和依赖就耗掉一两天。我实际的推荐组合是 SpringBoot 2.7.18。这个版本属于 2.x 的末期维护版稳定、资料多兼容性也足够好。下面的依赖版本我在项目里实测过可以直接抄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 groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version2.3.2/version /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency dependency groupIdcom.alibaba/groupId artifactIddruid-spring-boot-starter/artifactId version1.2.20/version /dependency dependency groupIdcom.github.pagehelper/groupId artifactIdpagehelper-spring-boot-starter/artifactId version1.4.7/version /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-data-redis/artifactId /dependency dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-validation/artifactId /dependency /dependencies这里有个细节很多人会踩MySQL 官方驱动从 8.0.31 之后某些版本在连接时要求显式开启 allowPublicKeyRetrieval否则会报 “Public Key Retrieval is not allowed”。所以在 JDBC URL 里最好加上这个参数后面配置章节我会再提。2. 功能齐全先从数据库开始核心表与订单状态设计2.1 先盘点“功能齐全”包含哪些模块做项目最容易犯的错是一上来就写代码写到一半发现表结构撑不起业务。所以动手之前我习惯先用一页纸把功能模块盘点清楚。旅游网站的“功能齐全”至少应该覆盖以下几条链路用户端注册、登录、修改资料、头像上传景点列表和详情、旅游线路的搜索与筛选线路/景点门票的加入订单、结算下单模拟支付、订单状态查询、取消订单订单完成后评价、收藏景点/线路。管理端管理员登录、用户管理景点、线路、酒店等内容管理订单查询、发货/核销、退款处理基础数据统计比如各线路销量、用户增长。把你做的模块列表写清楚数据库设计也就有依据了。模块之间不是孤立的用户下单会关联线路、支付流水、订单状态评价会关联订单和线路收藏会关联用户和产品。这张关系网就是核心表结构的来源。2.2 核心表结构和字段设计我这里把实际用到的核心表简化展示一下字段没有列全但关键字段都保留着。表名核心字段说明userid, username, password, nickname, phone, avatar, status, create_time用户表password存BCrypt密文scenicid, name, city, address, price, open_time, cover, intro, click_count, status景点表lineid, title, destination, days, line_type, price, origin_price, cover, detail, stock, status旅游线路表ordersid, order_no, user_id, product_type, product_id, product_name, product_cover, total_price, status, pay_time, cancel_time, create_time订单表product_type区分线路/门票/酒店paymentid, payment_no, order_no, channel, amount, status, callback_time支付流水表commentid, user_id, product_type, product_id, order_no, rating, content, create_time评价表favoriteid, user_id, product_type, product_id, create_time收藏表user_idproduct_id唯一订单表这里我要多说一句。很多人会设计成 order 主表 order_item 子表这当然规范。但如果你做的是个人项目且一次订单主要买一个线路或一张门票完全可以把商品名称、商品封面直接冗余在主表里。这样查询订单列表时不需要再去关联线路表既减少一次 JOIN也能避免线路信息被修改后历史订单显示错乱。2.3 表设计时容易被忽略的细节第一金额字段不要用 float 或 double。二进制浮点数在金额累计、比较时会出现精度问题轻则显示不对重则订单金额对不上。正确做法是用 decimal(10,2)或者干脆用 int以“分”为单位存储展示时再转换为元。第二订单号不要用自增 id。用户端会看到订单号自增 id 不仅暴露业务量还容易被枚举攻击。我习惯用“业务前缀 时间戳 随机数”的方式生成比如TC202406121030001234。如果后面要做并发压测建议再对订单号加唯一索引防止极端情况下生成重复。第三状态字段建议用 tinyint 存数字配合代码里的枚举类使用。比如订单状态0待支付、1已支付、2已完成、3已取消、4已退款。直接用数字存有一个好处就是索引效率高、占空间小但前提是代码里必须有明确的常量映射不能到处写魔法数字。第四软删除字段 delete_flag 尽量加上。用户删除了收藏记录、管理员下架了线路这些操作做成逻辑删除比物理删除安全得多后面想恢复数据也方便。2.4 订单状态机是可讨论的重点订单状态是整个交易系统的核心。面试官或者答辩老师问得最多的也是这里用户下单之后状态是怎么一步步变化的各种异常情况下你如何处理。我的订单状态流转是这样设计的用户提交订单生成一条 status0待支付的订单用户支付成功支付回调更新 status1已支付用户确认出游/核销完成更新 status2已完成用户在待支付状态下主动取消更新 status3已取消支付后申请退款管理端审核通过更新 status4已退款。这里有一个容易忽略的问题待支付订单如果一直不支付怎么办不能让它占着库存不释放所以必须有一个“超时未支付自动取消”的兜底机制。最简单的做法是定时任务每几分钟扫一次订单表把创建时间超过30分钟且 status0 的订单改掉并回补库存。如果你想让方案更完善可以在下单时用 Redis 的setnx记录订单号设置过期时间但定时任务仍然是个人项目里最稳、最好解释的方案。3. 骨架搭建依赖、配置文件和第一道坑3.1 目录结构与包名规划项目结构我习惯按“模块分包”而不是“技术分层”来组织。也就是说不是controller/ service/ mapper/三层平铺到底而是先分出功能模块再在每个模块内分层。比如com.travel ├── common // 通用返回结果、异常处理、常量 ├── config // 配置类WebMvc、Redis、Druid等 ├── controller // 控制层 ├── service // 业务层 ├── mapper // MyBatis Mapper接口 ├── entity // 数据库实体 ├── dto // 请求参数对象 └── vo // 响应视图对象当然如果项目规模不大按传统三层分包也没有问题。关键是包里职责要清晰不要让 Controller 里写一堆 SQL 逻辑也不要让 Service 层变成又长又臭的“上帝类”。3.2 配置文件这些参数踩坑率最高application.yml 是 SpringBoot 整合 SSM 时最容易出问题的地方。下面是一份我实际项目里能直接跑起来的配置关键地方我加了注释server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/travel?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: 123456 druid: initial-size: 5 max-active: 20 min-idle: 5 validation-query: SELECT 1 redis: host: localhost port: 6379 timeout: 3000ms servlet: multipart: max-file-size: 10MB max-request-size: 20MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.travel.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl pagehelper: helper-dialect: mysql reasonable: true support-methods-arguments: true这里几个连接参数值得单独说serverTimezoneAsia/Shanghai 必须加。不加的话MySQL 8 默认时区是 UTC你存入的时间会比北京时间早 8 小时查出来会一脸懵。allowPublicKeyRetrievaltrue 是 MySQL 8 和某些驱动版本组合下的常见报错点。开发环境开着没事生产环境如果用 SSL 或更安全的认证方式再按实际情况收紧。map-underscore-to-camel-casetrue 这一行一定要开。数据库字段是 create_time实体类是 createTime没有这个配置MyBatis 映射回来全是 null。分页插件 reasonabletrue 的作用是当你请求第 100 页但总共只有 10 页时自动获取最后一页数据而不是报错。这个参数在前后端联调时非常友好。3.3 统一返回、全局异常和事务控制我建议写业务代码之前先把三件事做掉统一返回对象、全局异常处理器、事务配置。否则后面每写一个接口都要重复处理错误码和异常代码会变得非常难看。统一返回对象我一般是这样定义的public class ResultT { private Integer code; private String message; private T data; // 构造方法、getter/setter 省略 public static T ResultT success(T data) { ResultT result new Result(); result.code 200; result.message success; result.data data; return result; } public static T ResultT error(Integer code, String message) { ResultT result new Result(); result.code code; result.message message; return result; } }全局异常用 SpringBoot 提供的RestControllerAdvice实现业务异常、参数校验异常、兜底异常分别处理。核心思路是异常信息不能直接甩给前端尤其是 SQL 异常里可能包含表名、字段名等敏感信息。事务方面SpringBoot 已经自动配置了事务管理器我只需要在 Service 方法上加Transactional即可。但这里有一个非常重要的经验不要把远程调用、消息发送、文件上传这类耗时操作放在事务方法里。事务的本质是锁住数据库资源如果事务长时间不提交连接池很容易被占满系统整体性能会直线下降。3.4 我踩过的第一个坑拦截器把静态资源也拦了SpringBoot 整合 SSM 项目后最常见的报错不是依赖冲突而是“静态资源被拦截器拦掉了”。因为你用拦截器校验用户登录时很容易写成“所有请求都要通过登录校验”结果网站前台的 CSS、JS、图片全部被当成未登录请求打回去页面一片乱。正确做法是在拦截器注册时添加排除列表。比如用户登录接口、注册接口、验证码接口、公开的景点列表接口、静态资源路径/static/**、上传文件访问路径/upload/**都要放行。我自己的经验是登录拦截范围只覆盖/user/**和/admin/**这类明显需要身份的路径前后台的公开接口另做排除不要图省事拦截全部请求再慢慢加白名单。4. 搜索、下单、支付、评价功能齐全的项目要把链路做闭环4.1 线路搜索与动态SQL旅游网站的搜索功能通常是多条件组合筛选按目的地、游玩天数、价格区间、线路类型、关键词。这些条件并不是每次都会传所以不能每换一个条件就写一条 SQL而要用 MyBatis 的动态 SQL 来处理。select idsearchLines resultTypecom.travel.entity.Line SELECT * FROM line where if testdestination ! null and destination ! AND destination LIKE CONCAT(%, #{destination}, %) /if if testdays ! null AND days #{days} /if if testminPrice ! null AND price #{minPrice} /if if testmaxPrice ! null AND price lt; #{maxPrice} /if if testkeyword ! null and keyword ! AND (title LIKE CONCAT(%, #{keyword}, %) OR destination LIKE CONCAT(%, #{keyword}, %)) /if AND status 1 /where ORDER BY create_time DESC /selectwhere标签会自动去掉第一个条件前面的 AND这个语法是 MyBatis 最常用的功能写动态查询一定要熟练。如果你想把搜索做得更像样一点可以考虑引入分词。比如中文关键词“三亚五日游”如果直接用 LIKE %三亚五日游%搜索效果很差如果先分词成“三亚”“五日游”再用 OR 拼接命中率会高很多。做 Java 的项目里HanLP 是一个不错的中文分词工具在 SpringBoot 里引入对应依赖业务层先调用分词接口再构造动态条件。不过这个属于锦上添花如果你的项目时段紧张先做好普通模糊搜索即可。4.2 下单防重复与库存扣减下单接口是整个系统里并发压力最大、也最容易出 Bug 的地方。核心问题有两个重复下单和超卖。重复下单的常见原因不是用户手抖而是前端请求超时后自动重试或者用户连续点击“提交订单”。前端可以防止手抖但后端必须有能力兜底。我的做法是第一同一个用户、同一个产品、在待支付状态下不允许再生成新订单。查询时把user_id、product_id、status0作为条件如果存在直接返回“您有未支付的订单”。第二给订单表加唯一索引防止极端情况下两条记录同时插入。订单号本身可以用唯一索引因为它是程序生成的不会重复。库存扣减更值得注意。新手最容易写出的代码是先查库存判断库存是否大于0执行减库存 update。这段逻辑在单线程下没问题并发场景下就会超卖。正确的做法是把判断和扣减放在一条 SQL 里UPDATE line SET stock stock - 1 WHERE id #{id} AND stock 0这条 SQL 利用数据库行锁保证原子性只有库存大于0时才会更新成功受影响行数为1表示扣减成功为0则表示库存不足。然后把“减库存”和“生成订单”放在同一个事务里任一步失败就整体回滚。4.3 支付流程先做模拟再理解真实回调个人项目接真实微信/支付宝支付需要营业执照和商户号很多同学不具备这个条件。所以最务实的方案是做“模拟支付”但模拟支付的设计逻辑要尽量贴近真实支付不然面试时讲不明白。我的模拟支付流程是这样的用户在前端点击“去支付”后后端生成一条支付流水记录 payment包含支付单号、订单号、渠道、金额、状态0待支付。前端跳转到一个模拟收银台页面用户点击“确认支付”前端请求“模拟支付确认接口”后端执行以下逻辑根据支付单号查出流水校验流水金额和订单金额一致把流水状态改为支付成功把订单状态从待支付改为已支付记录支付时间返回成功。真实支付的回调通知本质上就是这个“模拟支付确认接口”的替代品支付平台异步通知你的后端接口携带支付结果和签名后端验签后更新订单状态。这里关键的一点是幂等处理。支付平台的通知可能会重复发送好几次所以回调接口里必须先查订单状态——如果已经是已支付直接返回成功不再重复更新。否则就可能出现订单状态被覆盖、消费者重复收到消息的问题。支付成功后可以继续做两件事一是给用户生成消费凭证类似一个二维码或核销码二是发通知。通知可以不放在事务里同步做利用 ActiveMQ 或 Spring 事件机制支付成功后发布事件消费者异步发邮件/短信。这样主链路响应快系统也不容易被第三方服务拖垮。4.4 评价与收藏的约束细节评价功能看起来就是往 comment 表插一条记录但业务约束必须做全。用户只能评价自己订单里的产品订单状态必须是已完成且同一个订单不能重复评价。如果订单表里有 comment_status 字段也可以用这个字段来判断是否已经评价过。收藏功能的核心是唯一约束。建议在 favorite 表上建立(user_id, product_type, product_id)的唯一索引数据库层面直接拦截重复收藏。代码里再根据受影响行数返回不同提示体验会更好。5. 登录鉴权只算入门上传安全与PDF/XSS这类隐蔽问题5.1 用户登录与接口鉴权旅游网站有用户端和管理端最简单有效的鉴权方案是 JWT Redis。用户登录成功后后端签发一个 JWT token同时把 token 存到 Redis 里设置过期时间。前端请求时在 header 里带上 token拦截器解析 token再查一下 Redis 确认 token 是否有效。为什么加了 JWT 还要存 Redis因为 JWT 本身是无状态、不可撤销的。如果用户被管理员封禁或者用户自己修改了密码旧的 JWT 在过期之前仍然有效这是安全隐患。把 token 存在 Redis 里后端可以做主动失效同时还能实现续签、单点踢出等逻辑。拦截器的注册和前面说的静态资源放行是一套逻辑。我通常这样设计/api/**下的部分接口需要登录排除登录、注册、公开查询/admin/**下的全部接口需要管理员权限可以自定义一个RequireAdmin注解用拦截器判断当前用户的角色。5.2 给第三方接口加签名校验如果项目里有对外提供数据的接口比如给合作商查询线路信息就不能只靠 token。常见的做法是 AppKey AppSecret 签名。AppKey 标识身份AppSecret 用于签名请求参数加上时间戳服务端用相同规则计算签名并比对。这样可以防篡改时间戳还可以用来防重放比如超过5分钟的请求直接拒绝。这个点在简历项目里非常亮眼因为大部分毕业设计不会考虑到接口对接方的安全需求。实现也不复杂一个拦截器从 header 里取 AppKey、Sign、Timestamp查库拿到 AppSecret按约定规则拼接字符串、MD5 或 HMAC 签名比对一致才放行。5.3 文件上传扩展名校验是最弱的防线旅游后台要上传景点图片、线路封面图用户要上传头像这些都涉及文件上传安全。很多人只做了前端扩展名校验比如.jpg .png就放行这是非常危险的。攻击者完全可以把一个包含恶意代码的文件改成 .jpg 后缀上传。我实际项目里做的校验包括四层第一文件大小校验。SpringBoot 里用 MultipartFile 的 getSize() 判断超过限制直接拒绝同时配合 yml 里的 max-file-size 做统一限制。第二扩展名白名单校验。判断文件名的后缀必须在.jpg .jpeg .png .gif .pdf等白名单里。第三文件头魔数校验。这是最有效的一层。图片文件有自己的二进制头部标记比如 JPEG 文件前三个字节是FF D8 FFPNG 文件前八个字节是89 50 4E 47 0D 0A 1A 0APDF 文件通常是25 50 44 46即 %PDF。读取文件前几个字节判断是否匹配能拦截大多数“改后缀”的攻击。第四重新命名存储。不要用用户上传的原始文件名保存而是用 UUID 或时间戳生成新文件名扩展名从白名单里取。这样即使文件内容有问题也不会因为文件名中的特殊字符引发 XSS 或路径穿越问题。5.4 PDF/XSS一个容易被忽略的安全坑“SpringBoot 解决 PDF XSS 攻击”这个问题在搜索热度里出现过很多次说明有不少人在文件上传和预览环节遇到过麻烦。PDF 文件本身可能内嵌 JavaScript如果网站直接在浏览器中预览用户上传的 PDF脚本可能会在 PDF 上下文里执行带来跨站脚本攻击风险。另外文件名的处理不当也可能把script标签以 HTML 形式反射到页面上形成反射型 XSS。对策的思路是把“文件存储”和“文件展示”分开隔离存储时只允许特定扩展名并校验文件头文件名统一重命名为随机字符串原始文件名如果一定要展示输出时对文件名做编码处理使用 URLEncoder 或 Content-Disposition 的标准格式PDF 预览不要直接用浏览器原生打开而是使用 pdf.js 这类可控的渲染库关闭不必要的特性后台富文本编辑器上传内容时用 Jsoup 做白名单过滤把script、事件属性等标签直接清理掉。5.5 密码存储和其他敏感信息用户密码不要用 MD5 或 SHA 直接存储更不能用明文。推荐使用 BCrypt 加密。Spring Security 里自带的 BCryptPasswordEncoder 可以单独拿出来用注册时加密登录时 matches 校验。BCrypt 的特点是慢哈希自带盐值同样的密码每次加密结果都不同暴力破解成本高很多。管理后台展示用户手机号、身份证等敏感信息时记得脱敏。比如手机号中间四位用*代替。后端返回给前端的字段不要让敏感字段出现在接口响应里该用 VO 的地方必须用 VO不要直接把 Entity 转成 JSON 返回。6. 从本地跑通到上线Jar包、Docker、资源映射和中间件适配6.1 打包是小事但很多人第一遍都会踩坑本地开发运行没问题部署到服务器就用mvn打成可执行 jarmvn clean package -Dmaven.test.skiptrue打包完成后在target目录下会生成一个可执行的 jar。上传到服务器然后用下面这条命令启动nohup java -jar travel.jar --spring.profiles.activeprod logs/app.log 21 这里面的几个点要注意第一--spring.profiles.activeprod指定使用生产环境配置application-prod.yml 里应该配置生产数据库、Redis 地址和密码而不是开发环境配置。第二nohup和组合让应用在后台运行日志输出到 logs/app.log避免关掉终端后进程就没了。第三服务器时区问题。建议在启动命令或者 Dockerfile 里显式加上-Duser.timezoneAsia/Shanghai否则 JVM 默认取服务器系统时区时间对不上日志都排查不清楚。6.2 Docker 部署写一份干净的Dockerfile现在很多服务器的部署环境都要求容器化SpringBoot 项目打成镜像也很简单。下面是我项目里用的 Dockerfile很小很干净FROM openjdk:8-jdk-alpine ENV TZAsia/Shanghai COPY travel.jar /app.jar ENTRYPOINT [java, -Duser.timezoneAsia/Shanghai, -jar, /app.jar, --spring.profiles.activeprod]构建镜像docker build -t travel-service:1.0 .启动容器同时做端口映射和目录挂载docker run -d --name travel-service \ -p 8080:8080 \ -v /data/upload:/data/upload \ -v /data/logs:/data/logs \ travel-service:1.0挂载 upload 目录非常关键。旅游网站有大量上传的图片、PDF 文件如果这些文件存在容器内部容器删掉重建后数据就全没了。挂载到宿主机目录后无论容器怎么重建文件都还在。6.3 上传文件的访问SpringBoot资源映射上线后前端页面要能访问上传的图片需要一个 HTTP 路径映射到服务器上的文件目录。SpringBoot 里通过 WebMvcConfigurer 实现Configuration public class WebConfig implements WebMvcConfigurer { Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler(/upload/**) .addResourceLocations(file:/data/upload/); } }这样用户在浏览器访问http://你的域名/upload/xxx.jpg就会映射到服务器/data/upload/xxx.jpg路径下的文件。生产环境下这个路径不要放在应用目录内部数据和应用分离后续升级、迁移都方便。6.4 非内嵌Servlet容器的适配有些项目现场会指定应用必须部署到某个中间件上而不是用 SpringBoot 内嵌的 Tomcat。比如有人问过“改成信创的话是否需要东方通的 TongWeb”。这个问题从技术上回答很直接SpringBoot 项目默认打成 jar 用内嵌 Tomcat 运行但 SpringBoot 完全支持打成 war 包部署到外部 Servlet 容器。如果你遇到这样的要求改动点主要有三个第一pom.xml 的打包方式改为 warpackagingwar/packaging第二排除内嵌 Tomcat 依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-tomcat/artifactId scopeprovided/scope /dependency第三启动类继承 SpringBootServletInitializerSpringBootApplication public class TravelApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(TravelApplication.class); } public static void main(String[] args) { SpringApplication.run(TravelApplication.class, args); } }然后在外部容器里部署 war 包即可。这类中间件通常实现了标准的 Servlet 规范SpringBoot 项目迁移过去的主要兼容性差异集中在 Servlet API 版本、WebSocket 支持、部分 Tomcat 私有 API 的依赖上。如果项目里用到了这些能力开工前先和现场确认中间件的版本再针对性调整。把整个项目从头到尾做一遍之后我最大的感受是所谓“功能齐全”不是把功能列表堆得越长越好而是每一个用户能触达的操作链路都得是闭环的。注册之后能登录下单之后能支付支付之后订单有状态评价之后后台能看见。这些看不见的“链路完整性”才是项目真正值钱的地方。如果你也是拿这个题目做毕设或简历项目我的建议是先按照这篇里的思路把数据库表和订单状态机画清楚然后按“用户端主链路优先、管理端补充、安全加固最后”的顺序推进。等第一版完整跑通再考虑加秒杀、加推荐、加更复杂的技术组件。先把地基打牢后面加什么东西都不慌。本文还有配套的精品资源点击获取
返回列表