简介:这是一套面向计算机专业本科生的毕业设计级Java全栈项目,基于SSM(Spring+SpringMVC+MyBatis)与Vue前后端分离架构,完整实现民宿旅游业务闭环,覆盖用户预订、景点管理、路线规划、订单结算及后台运营等核心场景。资源包共922个文件,含167个Java后端逻辑类、58个Vue组件、156个JS交互脚本、58个HTML页面、46个CSS样式文件及2个SQL数据库脚本,结构清晰、模块解耦,便于理解MVC分层与前后端协同机制;压缩包大小26.72MB,开箱即用,已通过Eclipse/IDEA+MySQL 5.7环境严格调试。目前已有165人学习下载,配套源码、数据库、毕业论文及多套批处理脚本(如install.bat/run.bat),并保留了关键备份文件(.bak)与开发配置(.classpath/.project),有助于快速复现系统、分析调试流程与掌握企业级旅游平台开发范式。
1. 为什么一个基于SSM的民宿旅游系统,成了毕业季最常被选、也最容易翻车的Java项目?
你不是在写“一个Web系统”,而是在模拟一个真实轻量级运营场景:用户查房、下单、评价;房东上架、调价、接单;管理员审核、统计、导出。SSM(Spring + SpringMVC + MyBatis)不是过时的代名词——它仍是国内高校课程设计、中小型定制开发、毕业设计最稳的“技术基线”:不压内存、不卡IDEA、不依赖云原生生态、部署到Tomcat就能跑通全流程。这个标题里藏着五个硬性落地信号:“前台”意味着必须有游客免登录浏览+注册登录态管理,“B/S架构”锁死浏览器访问路径,“源码+数据库+毕业论文”说明它不是Demo而是可交付闭环,“民宿旅游”带出地域筛选、房型分类、订单状态机、图片多上传等典型业务褶皱。如果你正卡在“能跑但联调崩”“页面跳转404但控制台没报错”“MySQL插入中文变问号却查不到配置在哪改”,这篇笔记就是为你写的——我用同一套结构,在三个不同年级的模拟项目X中迭代过7版部署包,从本地Windows开发环境到阿里云ECS CentOS 7部署,把所有血泪经验压进可复现的命令、配置段和排查链路。
2. 搭建SSM骨架:从零初始化一个可编译、可启动、带基础分层的Maven工程
2.1 创建标准Maven Webapp结构并声明核心依赖版本
不要用IDEA一键生成“Spring Initializr”模板——它默认带Spring Boot,而本项目明确要求原生SSM。我们手动构建符合web.xml生命周期的传统结构:
<!-- pom.xml 核心依赖片段,注意版本对齐 --> <properties> <spring.version>5.3.31</spring.version> <mybatis.version>3.5.13</mybatis.version> <mysql-connector-java.version>8.0.33</mysql-connector-java.version> <junit.version>4.13.2</junit.version> </properties> <dependencies> <!-- Spring核心 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis整合 --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.7</version> </dependency> <!-- 数据库驱动与连接池 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>${mysql-connector-java.version}</version> <scope>runtime</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> <!-- JSP & Servlet API --> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>4.0.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>javax.servlet.jsp</groupId> <artifactId>jsp-api</artifactId> <version>2.2</version> <scope>provided</scope> </dependency> </dependencies>提示:Spring 5.3.x 是兼容JDK 8且稳定支持MyBatis 3.5.x的最后一代非Boot系主力版本;MySQL驱动必须用8.0+,否则
serverTimezone=GMT%2B8参数无效,导致时间字段全为0000-00-00。
2.2 配置web.xml:定义前端控制器、字符过滤器与监听器
这是B/S架构的入口契约。很多初学者删掉web.xml改用注解配置,结果静态资源404、POST中文乱码、Spring容器未加载——根源都在这里:
<!-- web.xml --> <?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 启动Spring容器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 前端控制器:接管所有 / 开头请求 --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 统一编码过滤器:必须放在所有Filter最前 --> <filter> <filter-name>encodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>逻辑说明:ContextLoaderListener加载根容器(Service/DAO层),DispatcherServlet加载Web容器(Controller层),二者通过WebApplicationContextUtils.getWebApplicationContext()桥接;CharacterEncodingFilter必须设forceEncoding=true,否则GET请求仍可能乱码。
2.3 分层配置文件规划:applicationContext.xml 与 spring-mvc.xml 职责切分
SSM项目最易混淆的点,就是把所有bean塞进一个XML。正确切分是稳定性的基石:
| 配置文件 | 加载时机 | 承载Bean类型 | 关键内容示例 |
|---|---|---|---|
applicationContext.xml | ContextLoaderListener启动时 | DataSource,SqlSessionFactory,TransactionManager,Service,Dao | Druid连接池配置、MyBatis扫描包、事务注解驱动<tx:annotation-driven/> |
spring-mvc.xml | DispatcherServlet初始化时 | Controller,ViewResolver,MultipartResolver,HandlerMapping | InternalResourceViewResolver前缀后缀、CommonsMultipartResolver文件上传限制 |
applicationContext.xml必须包含:
<!-- 扫描Service与Dao层,不扫Controller --> <context:component-scan base-package="com.minsu.service, com.minsu.dao"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- Druid数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <property name="url" value="jdbc:mysql://localhost:3306/minsu?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="123456"/> <property name="initialSize" value="5"/> <property name="minIdle" value="5"/> <property name="maxActive" value="20"/> </bean> <!-- MyBatis工厂 --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>spring-mvc.xml必须包含:
<!-- 扫描Controller层 --> <context:component-scan base-package="com.minsu.controller" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 文件上传支持 --> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <!-- 10MB --> <property name="defaultEncoding" value="UTF-8"/> </bean>注意:
<context:component-scan>的use-default-filters="false"是关键——它关闭默认扫描,只按<include-filter>显式指定的注解生效,避免Service被MVC容器误加载导致事务失效。
3. 数据库设计与MyBatis映射:民宿业务的核心实体建模与动态SQL实践
3.1 民宿系统核心表结构设计(含外键约束与索引建议)
本系统不是CRUD练习,需支撑“搜索+筛选+下单”闭环。以下是经三次迭代验证的最小可行表集(共7张),全部采用InnoDB引擎、UTF8MB4字符集:
| 表名 | 主要字段 | 业务说明 | 索引建议 |
|---|---|---|---|
t_user | id,username,password,phone,role(0=游客/1=房东/2=管理员),status | 用户主表,角色字段驱动权限分流 | username(UNIQUE),role+status(复合索引用于登录校验) |
t_house | id,title,price,area,bedroom,bathroom,address,lng,lat,status(0=下架/1=上架),user_id(FK) | 房源主表,经纬度支持地图搜索 | status,user_id,price(组合覆盖查询);lng+lat需GIS空间索引(若用MySQL 5.7+) |
t_house_img | id,house_id(FK),img_path,is_cover(0/1) | 房源多图,封面图标识 | house_id+is_cover |
t_order | id,order_no,house_id,user_id,check_in,check_out,total_price,status(0=待支付/1=已支付/2=已完成/3=已取消),create_time | 订单主表,状态机驱动业务流 | user_id+status,house_id+status,order_no(UNIQUE) |
t_comment | id,house_id,user_id,content,score(1~5),create_time | 用户评价,关联房源与用户 | house_id+create_time(倒序分页),user_id |
t_district | id,name,parent_id,level(1=省/2=市/3=区) | 地域字典表,支持三级联动筛选 | parent_id+level |
t_house_district | id,house_id(FK),district_id(FK) | 房源-区域多对多关系表 | house_id,district_id(联合唯一) |
提示:
t_house中lng/lat字段类型必须为DECIMAL(10,8)(经度)和DECIMAL(9,8)(纬度),而非DOUBLE——避免浮点精度导致距离计算偏差;t_order.order_no建议用yyyyMMddHHmmssSSS+3位随机数生成,杜绝并发重复。
3.2 MyBatis动态SQL实现多条件组合查询(以房源搜索为例)
前台搜索页需支持:关键词(标题/地址)、价格区间、房间数、区域ID、排序方式。硬拼SQL会爆炸,必须用<where>+<if>+<choose>:
<!-- mapper/HouseMapper.xml --> <select id="selectHousesByCondition" resultType="com.minsu.entity.House"> SELECT h.*, u.username AS landlord_name FROM t_house h LEFT JOIN t_user u ON h.user_id = u.id <where> <if test="keyword != null and keyword != ''"> AND (h.title LIKE CONCAT('%', #{keyword}, '%') OR h.address LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="minPrice != null"> AND h.price >= #{minPrice} </if> <if test="maxPrice != null"> AND h.price <= #{maxPrice} </if> <if test="bedroom != null and bedroom > 0"> AND h.bedroom >= #{bedroom} </if> <if test="districtIds != null and districtIds.size() > 0"> AND h.id IN ( SELECT hd.house_id FROM t_house_district hd WHERE hd.district_id IN <foreach collection="districtIds" item="id" open="(" close=")" separator=","> #{id} </foreach> ) </if> AND h.status = 1 </where> <choose> <when test="orderBy == 'price_asc'">ORDER BY h.price ASC</when> <when test="orderBy == 'price_desc'">ORDER BY h.price DESC</when> <when test="orderBy == 'time_desc'">ORDER BY h.create_time DESC</when> <otherwise>ORDER BY h.create_time DESC</otherwise> </choose> LIMIT #{offset}, #{limit} </select>参数说明:
#{}防SQL注入,${}仅用于ORDER BY字段(因MyBatis不支持#{}动态列名);districtIds是List<Long>,用<foreach>生成IN子句,避免N+1查询;LIMIT #{offset}, #{limit}为分页预留,实际调用时由PageHelper或手写分页工具注入。
3.3 MyBatis一对多映射:一个房源查出封面图+所有图片
House实体需包含coverImg(String)和allImgs(List )。不能用两个SQL,要用<resultMap>嵌套查询:
<!-- 实体类字段 --> public class House { private Long id; private String title; private String coverImg; // 封面图路径 private List<String> allImgs; // 所有图路径列表 } <!-- mapper/HouseMapper.xml --> <resultMap id="HouseWithImagesMap" type="com.minsu.entity.House"> <id property="id" column="id"/> <result property="title" column="title"/> <!-- 封面图:取is_cover=1的第一条 --> <association property="coverImg" javaType="java.lang.String" select="selectCoverImgByHouseId" column="id"/> <!-- 所有图:一对多 --> <collection property="allImgs" ofType="java.lang.String" select="selectAllImgsByHouseId" column="id"/> </resultMap> <select id="selectCoverImgByHouseId" resultType="java.lang.String"> SELECT img_path FROM t_house_img WHERE house_id = #{id} AND is_cover = 1 LIMIT 1 </select> <select id="selectAllImgsByHouseId" resultType="java.lang.String"> SELECT img_path FROM t_house_img WHERE house_id = #{id} </select>避坑:
<association>和<collection>的select属性会触发额外SQL,若查10个房源,将执行1+10+10=21次查询。生产环境务必开启二级缓存或改用<join>关联查询(见下一节)。
4. 前台功能落地:从游客浏览到订单生成的完整链路与关键拦截点
4.1 游客免登录浏览:首页轮播图+热门房源+区域导航的静态化优化
前台首页(index.jsp)需承载高并发访问,但学生项目常把所有数据都走Controller查库,导致首页加载超2秒。优化路径:
- 轮播图:存于
/static/images/banner/目录,用<c:forEach>遍历固定路径数组,不查DB; - 热门房源:按
order_count或comment_score排序,加Redis缓存(即使无Redis,也应在Service层用ConcurrentHashMap做10分钟本地缓存); - 区域导航:
t_district表数据极少变动,首次加载后存入ServletContext,Controller中直接getServletContext().getAttribute("districtTree")获取。
// 在系统启动时(如ServletContextListener)加载区域树 public class DistrictInitListener implements ServletContextListener { @Override public void contextInitialized(ServletContextEvent sce) { DistrictService districtService = new DistrictServiceImpl(); List<District> tree = districtService.buildDistrictTree(); // 递归组装省-市-区树 sce.getServletContext().setAttribute("districtTree", tree); } }JSP中直接使用:
<c:forEach items="${applicationScope.districtTree}" var="province"> <li class="province"> ${province.name} <c:forEach items="${province.children}" var="city"> <a href="/houses?districtId=${city.id}">${city.name}</a> </c:forEach> </li> </c:forEach>4.2 用户注册与登录:密码加密、Session管理与跨域兼容处理
民宿系统前台必须支持游客注册成为用户,再以用户身份下单。关键点:
- 密码不可明文存储:用
BCryptPasswordEncoder(Spring Security)或MD5+盐值(简易方案); - 登录态用HttpSession:不推荐JWT(增加复杂度),
request.getSession().setAttribute("user", user)即可; - 跨域问题:若前端用Vue/React单独部署,后端需在
spring-mvc.xml中添加CORS配置:
<mvc:cors> <mvc:mapping path="/api/**" allowed-origins="*" allow-credentials="true"/> </mvc:cors>Controller层登录方法示例:
@Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @PostMapping("/login") @ResponseBody public Result login(@RequestBody UserLoginDTO dto, HttpServletRequest request) { User user = userService.findByUsername(dto.getUsername()); if (user == null || !BCryptPasswordEncoder.matches(dto.getPassword(), user.getPassword())) { return Result.fail("用户名或密码错误"); } // 登录成功,存入Session request.getSession().setAttribute("user", user); return Result.success(user); } @GetMapping("/logout") @ResponseBody public Result logout(HttpServletRequest request) { request.getSession().invalidate(); return Result.success("退出成功"); } }注意:
@RequestBody要求前端发JSON,若用表单提交则改用@RequestParam;BCryptPasswordEncoder需在Spring配置中声明Bean,此处省略。
4.3 房源详情页与下单流程:图片懒加载、库存校验与分布式事务规避
详情页(house-detail.jsp)需展示:房源信息、房东信息、多图轮播、用户评价、预订表单。
- 图片懒加载:给
<img>加loading="lazy"属性,现代浏览器原生支持,无需JS库; - 库存校验:民宿无传统库存,但需校验
check_in/check_out时段是否已被其他订单占用:
// OrderService.java public boolean isDateAvailable(Long houseId, Date checkIn, Date checkOut) { // 查找该房源在[checkIn, checkOut)区间内是否有已支付订单 return orderMapper.countOverlappingOrders(houseId, checkIn, checkOut) == 0; }对应SQL(利用MySQL时间重叠判断):
SELECT COUNT(*) FROM t_order WHERE house_id = #{houseId} AND status = 1 -- 已支付 AND check_in < #{checkOut} -- 入住时间早于当前订单离店时间 AND check_out > #{checkIn}; -- 离店时间晚于当前订单入住时间- 下单事务:创建订单+扣减(逻辑)+发送通知,全部在同一个
@Transactional方法内完成,避免分布式事务。通知(如短信、邮件)应异步化(用@Async或消息队列),防止阻塞主流程。
5. 避坑指南:SSM民宿系统开发中90%人踩过的5个致命问题与现场急救方案
5.1 现象:页面跳转404,但控制台无任何错误日志
原因:web.xml中<servlet-mapping>的<url-pattern>配置错误,或spring-mvc.xml未启用注解驱动。
解决:
- 检查
web.xml中<servlet-mapping>的<url-pattern>是否为/(不是*.do或/app/*); - 确认
spring-mvc.xml中存在<mvc:annotation-driven/>; - 在Controller类上加
@Controller,方法上加@RequestMapping,确保包扫描路径正确(base-package="com.minsu.controller"); - 若用IDEA,右键项目 →
Add Framework Support→ 勾选Web Application,确认web.xml在src/main/webapp/WEB-INF/下。
5.2 现象:MySQL插入中文显示为???,但数据库和表字符集均为utf8mb4
原因:JDBC URL缺少characterEncoding=utf8,或Druid连接池未设置connectionProperties。
解决:
- JDBC URL必须完整:
jdbc:mysql://localhost:3306/minsu?useSSL=false&serverTimezone=GMT%2B8&characterEncoding=utf8; - Druid配置中追加:
<property name="connectionProperties" value="druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000;characterEncoding=utf8"/>5.3 现象:文件上传失败,MultipartFile始终为null
原因:CommonsMultipartResolver未配置,或JSP表单未设enctype="multipart/form-data",或maxUploadSize过小。
解决:
- 确保
spring-mvc.xml中已声明multipartResolverBean; - JSP中表单必须写:
<form action="/house/upload" method="post" enctype="multipart/form-data">; - Controller方法参数必须为
@RequestParam("file") MultipartFile file,不能是@RequestBody; - 检查
maxUploadSize单位是字节,10MB=10485760。
5.4 现象:@Transactional注解失效,数据库操作不回滚
原因:事务管理器未配置、Service类未被Spring容器管理、自调用(this.method())绕过代理。
解决:
- 确认
applicationContext.xml中已配置<tx:annotation-driven/>及DataSourceTransactionManager; - Service类必须用
@Service标注,且被<context:component-scan>扫描到; - 避免在Service内部用
this.xxx()调用事务方法,应改为@Autowired注入自身或使用AopContext.currentProxy()。
5.5 现象:登录后request.getSession().getAttribute("user")返回null
原因:Session未正确创建,或跨域请求未携带Cookie,或Tomcat配置了secure=true但未用HTTPS。
解决:
- 检查
web.xml中<session-config>是否设置了<http-only>true</http-only>(影响JS读取,但不影响服务端); - 若前端跨域,AJAX请求必须加
withCredentials: true,后端CORS配置allow-credentials="true"且allowed-origins不能为*(需指定具体域名); - Tomcat的
conf/context.xml中,<Context>标签删除secure="true"或确保Nginx反向代理已传X-Forwarded-Proto: https。
6. 毕业答辩与部署实战:从本地调试到CentOS服务器上线的5个关键动作
6.1 本地调试阶段:用Chrome DevTools精准定位前后端断点
别再靠System.out.println大海捞针。真正在意效率的开发者,会这样用浏览器调试:
- Network面板:查看每个请求的
Status、Headers(确认Content-Type: application/json)、Preview(看JSON结构是否符合预期)、Response(原始响应体); - Console面板:捕获JS语法错误、
fetch/axios的catch错误、undefined调用; - Sources面板:在JSP中打断点(需开启
Enable JavaScript source maps),或在Controller方法首行加debugger语句; - Application面板:检查
Cookies中JSESSIONID是否存在、Storage中localStorage数据是否正确。
血泪经验:90%的“页面空白”问题,都是前端JS报错导致后续渲染中断。先看Console红字,再查Network请求状态,最后翻后端日志——顺序错了,3小时变3天。
6.2 数据库脚本标准化:一个SQL文件搞定建库、建表、初始化数据
毕业答辩要求“一键部署”,就必须提供可执行SQL。不要分开create_table.sql和insert_data.sql,合并为minsu-init.sql,开头加:
-- minsu-init.sql SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS = 0; -- 创建数据库 CREATE DATABASE IF NOT EXISTS minsu CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE minsu; -- 创建表(粘贴前面设计的7张表DDL) CREATE TABLE t_user ( ... ); -- 初始化管理员账号(密码用BCrypt加密后的字符串) INSERT INTO t_user (username, password, phone, role, status) VALUES ('admin', '$2a$10$KQVZzYqR...(BCrypt密文)', '13800138000', 2, 1); -- 初始化测试房源、区域等 INSERT INTO t_district VALUES (1, '北京市', 0, 1), (2, '朝阳区', 1, 3); SET FOREIGN_KEY_CHECKS = 1;提示:BCrypt密文可用在线工具生成(搜索“BCrypt在线加密”),或在Java中用
new BCryptPasswordEncoder().encode("123456")。
6.3 Tomcat部署包瘦身:移除冗余JAR,控制WAR包小于30MB
学生项目常把lib塞满无关JAR(如spring-boot-starter-web),导致WAR包超100MB,上传失败。精简清单:
| 必须保留 | 可删除 | 说明 |
|---|---|---|
spring-webmvc-5.3.31.jar | spring-boot-*全系列 | SSM不用Spring Boot |
mybatis-3.5.13.jar | mybatis-spring-boot-starter-* | 同上 |
druid-1.2.16.jar | hikari-cp-* | 用Druid不用Hikari |
mysql-connector-java-8.0.33.jar | ojdbc8.jar | 只连MySQL |
commons-fileupload-1.5.jar | commons-io-2.11.0.jar | FileUpload已含IO功能 |
最终WEB-INF/lib应只有12~15个JAR,总大小<8MB。用jar -tvf minsu.war \| wc -l统计文件数,du -sh minsu.war看体积。
6.4 CentOS 7服务器部署:JDK 8 + Tomcat 9 + MySQL 8 完整命令链
在阿里云ECS(CentOS 7.9)上,我用以下命令链5分钟完成环境搭建:
# 1. 安装JDK 8(OpenJDK) sudo yum install -y java-1.8.0-openjdk-devel # 2. 安装Tomcat 9(官网下载tar.gz,解压到/opt) cd /opt && sudo wget https://downloads.apache.org/tomcat/tomcat-9/v9.0.83/bin/apache-tomcat-9.0.83.tar.gz sudo tar -zxvf apache-tomcat-9.0.83.tar.gz sudo mv apache-tomcat-9.0.83 tomcat9 sudo chown -R $USER:$USER /opt/tomcat9 # 3. 配置Tomcat(修改端口与编码) echo 'export JAVA_HOME=/usr/lib/jvm/java-1.8.0' >> /opt/tomcat9/bin/setenv.sh echo 'export CATALINA_OPTS="-Dfile.encoding=UTF-8"' >> /opt/tomcat9/bin/setenv.sh sed -i 's/8080/8081/g' /opt/tomcat9/conf/server.xml # 改为8081,避开常用端口 # 4. 安装MySQL 8(官方repo) sudo rpm -Uvh https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm sudo yum install -y mysql-community-server sudo systemctl start mysqld sudo systemctl enable mysqld # 获取临时密码:sudo grep 'temporary password' /var/log/mysqld.log mysql -uroot -p -e "ALTER USER 'root'@'localhost' IDENTIFIED BY 'Minsu@2024';" # 5. 导入数据库 mysql -uroot -pMinsu@2024 < /home/yourname/minsu-init.sql # 6. 部署WAR包 cp minsu.war /opt/tomcat9/webapps/ sudo systemctl start tomcat9关键技巧:Tomcat日志在
/opt/tomcat9/logs/catalina.out,实时监控用tail -f catalina.out;若启动失败,90%是JDK版本不匹配或JAVA_HOME未生效,检查/opt/tomcat9/bin/catalina.sh头部输出。
6.5 毕业答辩演示话术:3分钟讲清技术选型、架构亮点与个人贡献
答辩不是背论文,是证明你真做过。我教学生统一用这个结构:
“老师好,我做的民宿系统,技术栈是SSM+B/S架构。选SSM是因为它轻量、可控、教学普及度高,比Spring Boot更能体现三层解耦思想(指指PPT上的包结构图)。系统亮点有三个:第一,搜索模块用MyBatis动态SQL实现多条件组合,支持价格、区域、房型实时筛选(打开演示站,快速操作);第二,订单状态用枚举+数据库字段驱动,避免硬编码;第三,所有敏感操作(如删除、支付)都加了Service层事务,保证数据一致性。我个人完成了全部后端开发、数据库设计、Tomcat部署,并编写了32页毕业论文,重点分析了SSM各层协作机制与常见异常处理方案。”
最后一句收尾,我说得最多的是:“这个项目让我真正理解了‘框架’不是魔法,而是约定——只要守住web.xml的入口、分清applicationContext和spring-mvc的职责、把SQL写在Mapper里而不是Java里,SSM就永远不会失控。”
希望帮到你。
本文还有配套的精品资源,点击获取