简介:一份面向Java Web毕设与实训的山东红色旅游信息管理系统完整项目,采用后端Java语言、SSM框架(Spring、SpringMVC与MyBatis)以及前端Vue/JSP技术构建。系统围绕红色旅游资源推广与管理,涵盖景点介绍、旅游线路推荐、红色文化教育、在线预订、评价反馈与社区交流等业务,并实现了用户、景点、预订、评价及后台管理等核心模块。项目已经过严格调试,运行稳定,代码分层清晰,能够帮助学习者深入掌握SSM框架整合、前后端数据交互、数据库设计以及旅游信息管理系统的完整开发流程。压缩包共包含1319个文件,整体大小约19.21MB,主要文件类型包括Java后端源码、JSP页面、CSS样式表、JavaScript脚本、PNG/GIF/JPG图片素材,以及SQL数据库脚本、Doc功能说明文档和XML配置文件,便于本地部署、二次开发与毕业设计参考。目前已有3513人学习和下载。通过该项目,可以获取完整的项目源码、数据库初始化脚本和功能介绍文档,快速理解系统的需求分析、模块划分与实现思路,并在此基础上进行功能扩展或界面优化,直接作为毕业设计的可行方案启动开发。
1. ssm9716 是什么:一套能直接跑通全链路的 JSP + SSM 红色旅游管理系统
拿到“ssm9716基于web的山东红色旅游信息管理系统的设计与实现+jsp.zip”这个标题,别把它当成又一个课程设计老项目。它把 SSM(Spring、SpringMVC、MyBatis)和 JSP 视图层完整串联在一起,业务对象是旅游信息管理:景点、路线、资讯、留言、用户,每个模块都对应一个信息管理系统的标准场景。想弄明白“一个 web 项目从浏览器请求到数据库字段到底怎么走的”,它比空讲框架理论有用得多;准备交课程设计、毕业设计的同学,也能直接把它当成可复用的骨架。适合刚学完 JavaWeb 还没做过整站的人,也适合手里有类似后台需求、想快速把管理端落地的人。
2. 从需求到表结构:红色旅游系统的角色、功能与三张核心业务表
2.1 用户端和管理员端的功能边界与请求流程
红色旅游信息管理系统的角色划分其实很固定,不需要被“山东”这个地域词带偏。普通用户(游客或注册会员)面对的是内容展示加轻交互:红色景点列表、景点详情、推荐路线、资讯公告、留言反馈,个人中心再存一些浏览或收藏记录。管理员面对的则是典型 web 后台:景点增删改查、图片上传、路线维护、资讯发布、留言审核、用户管理。这个边界决定了你拆解项目时看什么:URL 里带 admin、manage 前缀的基本都是后台功能,剩下的就是前台展示,两头代码混在一起是这类项目最容易乱的地方。
请求流程值得先画清楚,因为后续所有排错都建立在这条链路上。常见做法是:浏览器访问/spot/list,SpringMVC 的 DispatcherServlet 根据 HandlerMapping 找到对应 Controller 方法,Controller 调 Service 接口,Service 调 MyBatis 的 Mapper 接口,Mapper 的 XML 里写 SQL 查数据库,结果逐层向上返回,最后 Controller 把数据塞进 Model 并 forward 到指定 JSP。链路里任何一个环节断了,页面不是 404 就是 500:注解没扫到、Mapper 接口没注册、SQL 字段名对不上、视图前缀写错,全都可能。调试这类项目时,先沿着请求路径确认每一层有没有执行,比自己瞎猜快得多。
用户端和管理员端还有一个交互分支是 Ajax。景点详情页里的收藏、留言、分页加载,老项目也常走 JSON 接口,Controller 方法上加@ResponseBody返回一个统一 Result 对象。这种混用 JSP 渲染和 JSON 接口的写法在 SSM 项目里很常见,我在 3.1 节会给出一段完整的分页查询代码,你能看到同一套 SSM 怎么同时服务页面跳转和数据返回。
2.2 数据库设计与关键字段:坐标、图片路径、发布时间为什么不能省
信息管理系统的根是表结构。这个项目最少需要六张表:用户表、景点表、路线表、资讯表、留言表,以及一张路线与景点的关联表。设计时别贪多,先把主键、名称、状态、创建时间这套基础字段定好,再补业务特有字段。景点表是整系统的信息中枢,路线、资讯、留言都围着它转,下面这个建表语句是这类项目里比较典型的设计,字段已经做过精简:
CREATE TABLE `spot` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键', `name` VARCHAR(100) NOT NULL COMMENT '景点名称', `category` VARCHAR(50) DEFAULT NULL COMMENT '分类:纪念馆/旧址/烈士陵园等', `province` VARCHAR(50) DEFAULT '山东省' COMMENT '所属省份', `city` VARCHAR(50) DEFAULT NULL COMMENT '所属地市', `address` VARCHAR(255) DEFAULT NULL COMMENT '详细地址', `latitude` DECIMAL(10,6) DEFAULT NULL COMMENT '纬度', `longitude` DECIMAL(10,6) DEFAULT NULL COMMENT '经度', `cover_image` VARCHAR(255) DEFAULT NULL COMMENT '封面图片URL', `detail_images` TEXT COMMENT '详情图片,逗号分隔', `video_url` VARCHAR(255) DEFAULT NULL COMMENT '视频链接', `open_time` VARCHAR(100) DEFAULT NULL COMMENT '开放时间', `introduction` TEXT COMMENT '简介', `status` TINYINT DEFAULT 1 COMMENT '1启用 0停用', `view_count` INT DEFAULT 0 COMMENT '浏览量', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_status` (`status`), KEY `idx_city` (`city`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='红色景点表';这段 SQL 里有三个容易踩坑的点。第一,经纬度要用DECIMAL(10,6)而不是 FLOAT,float 在展示和比较时会有精度漂移,坐标是定位和排序的依据,精度不能省。第二,detail_images用逗号分隔多个图片路径,看起来不符合规范化,但在这类中小型管理系统里反而是常见做法,前端按逗号 split 就能用;只有图片数量可能增长到几十张时才需要拆子表。第三,status字段必须给默认值并建索引,所有前台查询入口都按status=1过滤,否则管理端下架了景点,用户还能通过旧链接看到,这在答辩时会被直接问住。
2.3 表间关系与一个容易漏掉的关联设计
景点和路线是多对多关系:一条红色旅游路线包含多个景点,一个景点也能出现在多条路线里。老式写法是给路线表加一个spot_ids字段,逗号串起景点主键,查询时再拆分;规范做法是建关联表。如果是为了答辩,我建议建关联表,因为能顺带讲清楚“为什么不用冗余字段”;如果只求快速出活,逗号字段也不是不能接受。关联表结构如下:
CREATE TABLE `route_spot` ( `id` INT NOT NULL AUTO_INCREMENT, `route_id` INT NOT NULL COMMENT '路线ID', `spot_id` INT NOT NULL COMMENT '景点ID', `sort_order` INT DEFAULT 0 COMMENT '在路线中的顺序', PRIMARY KEY (`id`), UNIQUE KEY `uk_route_spot` (`route_id`,`spot_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='路线-景点关联表';sort_order是最容易被忽略的字段。没有它,路线里景点只能按 id 排序,而红色路线的叙事逻辑通常是“第一站瞻仰、第二站学习、第三站体验”,顺序本身就是内容。剩下几张表相对简单:用户表存 username、password(记得存加密后的密文)、nickname、avatar、role;资讯表存 title、cover、summary、content、publish_time、status;留言表一定要包含 user_id、spot_id、content、reply_content、reply_time,管理员回复后就靠这些字段支撑。
常规表清单汇总如下:
| 表名 | 核心字段 | 用途 |
|---|---|---|
| user | username, password, nickname, avatar, role | 登录鉴权与用户信息 |
| spot | 见上方建表语句 | 景点信息维护 |
| route | title, days, waypoints, cover | 红色路线推荐 |
| route_spot | route_id, spot_id, sort_order | 路线与景点关联 |
| article | title, cover, content, publish_time | 资讯与红色故事发布 |
| comment | user_id, spot_id, content, reply_content | 用户留言与管理员回复 |
最后提醒一句:内容展示类系统,create_time、update_time、status这三个字段最好建表时就加上,后面做“按发布时间排序”“后台上下架”全依赖它们。我的血泪经验是项目写到一半再补时间字段,要同时改表、改 JSP、改查询 SQL,返工量比开始多花十分钟大得多。
3. SSM 三层落地:Controller、Service、Mapper 的请求流转与关键配置
3.1 一个景点分页查询从 URL 到数据库的完整路径
理解 SSM 项目最快的方式,是跟一条完整的请求路径。拿最典型的“景点分页列表”举例。页面访问/spot/list?pageNum=1&pageSize=10&keyword=纪念馆,Controller 接收参数、调用 Service、返回视图:
@Controller @RequestMapping("/spot") public class SpotController { @Autowired private SpotService spotService; @GetMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, @RequestParam(required = false) String keyword, @RequestParam(required = false) String city, Model model) { PageInfo<Spot> page = spotService.pageQuery(pageNum, pageSize, keyword, city); model.addAttribute("page", page); model.addAttribute("keyword", keyword); model.addAttribute("city", city); return "spot/list"; } @GetMapping("/detail/{id}") public String detail(@PathVariable Integer id, Model model) { Spot spot = spotService.getById(id); model.addAttribute("spot", spot); return "spot/detail"; } }这段代码里两个注解要注意区分:@RequestParam取请求参数,适合列表查询;@PathVariable取 URL 路径中的变量,适合详情页。返回字符串spot/list不是直接给用户的路径,而是逻辑视图名,InternalResourceViewResolver 会自动拼出/WEB-INF/views/spot/list.jsp。Model 里的page对象在 JSP 中通过${page.list}取值。中间务必要加defaultValue,否则缺失参数时 SpringMVC 直接报 400,而不是按第一页展示。
Service 层的写法决定了项目好不好维护。这里使用 PageHelper 做物理分页:
@Service public class SpotServiceImpl implements SpotService { @Autowired private SpotMapper spotMapper; @Override public PageInfo<Spot> pageQuery(Integer pageNum, Integer pageSize, String keyword, String city) { PageHelper.startPage(pageNum, pageSize); List<Spot> spotList = spotMapper.selectByCondition(keyword, city); return new PageInfo<>(spotList); } }注意:
PageHelper.startPage()后面必须紧跟着第一条要分页的查询,中间不能穿插其他数据库操作。很多人翻车在 startPage 之后又查了一次用户或日志表,结果是分页作用到了错误的 SQL 上,列表永远不分页。
Mapper 层是 SQL 真正执行的地方。MyBatis 的 Mapper 接口里定义方法,XML 里写对应的 SQL:
<select id="selectByCondition" resultType="com.example.entity.Spot"> select id, name, category, city, address, cover_image, open_time, view_count, create_time from spot <where> status = 1 <if test="keyword != null and keyword != ''"> and (name like concat('%', #{keyword}, '%') or address like concat('%', #{keyword}, '%')) </if> <if test="city != null and city != ''"> and city = #{city} </if> </where> order by create_time desc </select><where>标签会自动处理条件接缝,第一个条件前不加 and,后续条件自动补 and,避免手写 where 1=1 的脏代码。<if test>做动态拼接,空 keyword 不会污染查询。这里的核心坑是 like 拼接:concat('%', #{keyword}, '%')是正确写法,写成'%#{keyword}%'会被当成纯字符串字面量,结果什么都查不到。resultType对应实体类全限定名,字段映射规则在 3.2 节配置里打开驼峰转换后,cover_image才能正确落到coverImage属性上。
3.2 springmvc.xml 与 mybatis 配置里必须配对的三个开关
SSM 项目的落地点很大程度上在配置里。很多人代码写得没问题,项目起不来是因为配置开关没配对。我一般拿到项目先检查三处。
第一处是组件扫描范围。springmvc.xml 里只扫描 Controller:
<context:component-scan base-package="com.example.controller"/>Service 和 Mapper 放进 spring 根容器(spring-dao.xml 或 applicationContext.xml)扫描,事务注解<tx:annotation-driven/>也必须配在根容器里,否则事务不生效。很多项目启动时不报错,但数据死活写不进去,就是事务没被 Spring 管理。
第二处是 MyBatis 的下划线转驼峰,必须显式打开:
<configuration> <settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> </configuration>不打开这个,数据库的create_time映射不到实体的createTime,查出来全是 null,页面一片空白。有人靠给每个字段起别名绕过,结果是 SQL 越来越长,完全没有必要。
第三处是静态资源映射。DispatcherServlet 的url-pattern如果配成/,会把所有请求都拦下来,图片、CSS、JS 全部 404。必须在 springmvc.xml 里放行静态资源:
<mvc:resources mapping="/static/**" location="/static/"/> <mvc:resources mapping="/upload/**" location="/upload/"/>同时要注意 web.xml 里 DispatcherServlet 映射路径。如果你配的是*.do,那 JSP 不受影响;如果配的是/,JSP 本身必须交给 Tomcat 的 JspServlet 处理,不能在 springmvc.xml 里把 JSP 也拦截成视图,否则视图解析路径全错。这类“配置黑匣子”问题,排查时先把这三处开关对照一遍,能解决大半启动问题。
3.3 文件上传与图片展示:把照片存服务器还是数据库
红色景点系统的图片比较多,封面、详情图、视频资源都涉及上传。数据库里只存相对路径,文件本体放服务器磁盘,这是 web 项目标准做法。上传功能基于 CommonsMultipartResolver 实现,springmvc.xml 里配:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="defaultEncoding" value="UTF-8"/> <property name="maxUploadSize" value="52428800"/> </bean>Controller 处理上传时,注意保存路径的选取:
@PostMapping("/admin/spot/upload") @ResponseBody public Result upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) { String dir = request.getServletContext().getRealPath("/upload/spot/"); File folder = new File(dir); if (!folder.exists()) { folder.mkdirs(); } String originalFilename = file.getOriginalFilename(); String ext = originalFilename.substring(originalFilename.lastIndexOf(".")); String filename = System.currentTimeMillis() + "_" + System.currentTimeMillis() + ext; file.transferTo(new File(folder, filename)); return Result.ok("/upload/spot/" + filename); }注意:用
getRealPath()得到的目录对应 Tomcat 发布后的临时位置,重新部署或清理 work 目录后文件可能丢失。我一般改成配置一个外部目录,例如D:/red_tourism_upload,通过mvc:resources映射到 URL 路径。这样升级、备份、迁移都方便,也不算复杂。
图片路径存进数据库后,JSP 页面直接用${spot.coverImage}拼接<img>的 src。如果页面出现图片裂开,先确认返回的 URL 能在浏览器里直接访问,再检查静态资源映射,这两个方向能解决九成图片 404 问题。文件重名问题用时间戳加随机串处理,别用原始文件名直接落盘,否则用户传两次“合影.jpg”,后一次会覆盖前一次。
4. JSP 页面里最像“功能”的三个细节:图片坐标定位、视频播放与打印导出
4.1 景点导览图上的坐标定位:不用地图 API 也能标记可点击位置
红色景区经常需要一张园区导览图,图上标出纪念馆、纪念碑、旧址等位置,用户点击标记进入详情。很多人的第一反应是集成高德或百度地图。实际上在 JSP 老项目里完全可以用“图片坐标定位”实现,而且效果足够:管理员在后台上传一张平面导览图,再在图上点几下保存坐标,前端页面根据坐标在图上叠加可点击标记。
坐标存储建议用百分比而不是像素。像素值跟图片实际尺寸绑定,图片一换全部错位;百分比坐标在任何缩放比例下都能准确定位。后台录入坐标的 JS 逻辑如下:
$(function(){ $('#mapImg').on('click', function(e){ var rect = this.getBoundingClientRect(); var x = (e.clientX - rect.left) / rect.width * 100; var y = (e.clientY - rect.top) / rect.height * 100; $('#percentX').val(x.toFixed(2)); $('#percentY').val(y.toFixed(2)); alert('标记点坐标:' + x.toFixed(2) + ', ' + y.toFixed(2)); }); });录入的percentX、percentY存到数据库(可以放在 spot 扩展表或单独 marker 表)。展示时 JSP 循环输出标记点,用绝对定位固定到图片容器上:
<div style="position:relative; display:inline-block;"> <img id="mapImg" src="${spot.mapImage}" width="800" /> <c:forEach items="${markerList}" var="m"> <a href="/spot/detail/${m.targetId}" class="spot-marker" style="left:${m.percentX}%; top:${m.percentY}%;">${m.name}</a> </c:forEach> </div>CSS 里给标记点定一个圆形小标:
.spot-marker{ position:absolute; transform:translate(-50%, -50%); cursor:pointer; z-index:10; background:#c00; color:#fff; padding:2px 10px; border-radius:10px; font-size:13px; }这才是真正的“图片定位”实现。坑在于:如果图片不是固定宽度,而是响应式缩放,标记点的容器必须和图片实际渲染宽度一致,否则坐标对不上。最稳妥的方式是用同一个 CSS 类控制容器宽度,图片width:100%,标记点用百分比定位,二者同步缩放。
4.2 在 JSP 里嵌入 mp4 视频:为什么本地能放、换台电脑就放不了
JSP 页面的视频展播功能,HTML5 的<video>标签足够。红色景点详情页放一段介绍视频,代码很短:
<video controls width="640" preload="metadata"> <source src="${spot.videoUrl}" type="video/mp4"> </video>问题往往不在标签上,而在 Tomcat 的 MIME 映射。老版 Tomcat 的全局 web.xml 里没有注册 mp4 的 MIME 类型,响应头里的 Content-Type 变成application/octet-stream,浏览器的表现为视频一直转圈不播放,按 F12 看网络响应才能发现问题。解决方式是在项目 web.xml 里补一段:
<mime-mapping> <extension>mp4</extension> <mime-type>video/mp4</mime-type> </mime-mapping>另一个坑和视频路径有关。如果视频文件放在/upload/video下并且文件名带中文,Tomcat 8 以下的 GET 请求默认按 ISO-8859-1 解码,URL 里的中文文件名变成乱码,请求 200 但内容完全不对。server.xml 里给 Connector 加上URIEncoding="UTF-8",并且上传文件时用时间戳重命名,能从根上避免这个坑。
还有一个很多人没注意的点:视频拖动进度条失败。Tomcat 默认支持 Range 请求,也就是支持拖动进度,但如果你在 SpringMVC 里把/upload/**的访问方式配得不对,例如被某个拦截器拦下来做了路径改写,Range 头就保不住,表现为视频能加载却无法 seek。遇到这种问题,先关掉项目里的自定义过滤器,再试一次,基本能定位是谁动了请求头。
4.3 web 页面打印成 PDF:给游客下载参观手册的简易方案
系统有时候需要给游客提供“参观手册”或“路线介绍”的下载。最轻量的方案不是服务端生成 PDF,而是直接让浏览器打印:JSP 页面放一个按钮,点击window.print(),再用 CSS 的@media print把无关内容隐藏掉,保留核心内容区。这样既能在电脑端输出 PDF,也能在手机端走系统打印能力,几乎零成本:
<button onclick="window.print()" class="print-btn">打印 / 保存为PDF</button> <style> @media print { .header, .navbar, .print-btn, .comment-box { display:none; } .content { width:100%; margin:0; } img { max-width:100%; } } </style>这个方案的优点是稳定,缺点是不能一键生成文件。如果需求明确是“点击按钮直接下载 PDF 文件”,常见做法是引入 html2canvas 和 jsPDF 两个前端库,把指定区域转换为图片后再导出:
async function exportPdf() { const canvas = await html2canvas(document.querySelector('.content'), {scale: 2}); const imgData = canvas.toDataURL('image/jpeg', 0.95); const pdf = new jsPDF('p', 'mm', 'a4'); const pageWidth = 210; const imgHeight = canvas.height * pageWidth / canvas.width; pdf.addImage(imgData, 'JPEG', 0, 0, pageWidth, imgHeight); pdf.save('guide.pdf'); }这里的隐藏坑是图片高度。如果.content区域特别长,生成出来的图片会超出一页 A4 的高度,导致内容截断。解决思路是先把内容按显示高度拆分为多页,每页生成一张图再addPage(),或者干脆限定导出的内容为固定高度的卡片区域。用这种方案导长页面时,浏览器内存占用会明显上升,建议给 html2canvas 的scale设置为 1.5 到 2 之间,不要盲目调大。JSP 老项目引入这两个库时,直接放到 webapp 的 static/js 目录下,用<script src="/static/js/html2canvas.min.js">引入即可,不需要改任何后端代码。
5. 部署避坑排查:zip 导入后在 IDEA + Tomcat 里跑不起来的五个高频故障
5.1 故障一:Maven 依赖标红,Spring 核心包拉不下来
现象:从压缩包解压后用 IDEA 2024 打开项目,pom.xml 里大片依赖下划线红色波浪线,编译时报Could not resolve dependencies。
原因:老项目的 pom.xml 里版本号停留在 Spring 4.3、MyBatis 3.4 时代,这些版本在默认中央仓库远不如新版本好拉,有些镜像甚至没有;另一个常见原因是本机 JDK 版本太高,老版本 Spring 与 JDK 8 以上的模块系统出现冲突。
解决:先确认本地 Maven 仓库路径没搞错,然后把 Maven 镜像源配好;再检查 pom 里的依赖版本,如果长期拉不下来,直接对齐到一组成熟稳定的版本组合,例如 Spring 5.1.8.RELEASE、MyBatis 3.5.6、PageHelper 5.2.0、Jackson 2.9.9。改动 pom 后强制刷新:
mvn clean -U这样能强制重新拉取,也能避免 IDEA 缓存导致的假性报错。JDK 版本方面,我这边的习惯是用 JDK 8 跑这类项目,虽然高版本也行,但没必要在这个过程中跟模块系统较劲。
5.2 故障二:Tomcat 启动正常,访问页面 404 或跳到默认首页
现象:Tomcat 启动没有任何异常,但访问http://localhost:8080/看到的是 Tomcat 默认欢迎页,访问项目路径直接 404;或者访问某个 JSP 时报 404,但同一个路径换成/spot/list就正常。
原因:没有配欢迎页,或者配了欢迎页但页面不在可访问目录下;还有可能是 IDEA 部署时没有把项目 Artifact 挂到 Tomcat,导致 context path 与预期不一致。
解决:先看 IDEA 里 Tomcat 配置页的 Deployment 标签,确认 Artifact 已添加,且 Application context 路径你是知道的。然后在 web.xml 里配欢迎页:
<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list>JSP 如果放在/WEB-INF下面是不能直接作为欢迎页访问的,因为 WEB-INF 目录对浏览器不可见。常见做法是在 webapp 根目录放一个 index.jsp,内容只做一件事:跳转到真正的入口。
<jsp:forward page="/spot/list" />如果访问 JSP 显示 404 而访问/spot/list正常,多半是视图解析器路径问题。去 springmvc.xml 里核对InternalResourceViewResolver的 prefix 和 suffix 有没有对准 JSP 实际位置,这个低级错误能让人排查一小时。
5.3 故障三:数据库连接报 Access denied 或 Communications link failure
现象:项目启动时日志出现Access denied for user 'root'@'localhost',或者Communications link failure,页面上所有涉及查数据库的功能全挂。
原因:压缩包里自带的 jdbc.properties 往往写的是开发机的数据库密码,拿到你机器上必然不对;如果数据库是 MySQL 8,老配置里的驱动类名和连接串还需要调整,MySQL 8 默认不使用 SSL 会直接拒绝连接。
解决:检查 src/main/resources 下的 jdbc.properties,一般我会改成下面这组配置:
jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/red_tourism?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true jdbc.username=root jdbc.password=改成你的密码驱动类名用了com.mysql.cj.jdbc.Driver,对应 MySQL 8;serverTimezone必须指定,否则连接会报时区异常;allowPublicKeyRetrieval=true解决 MySQL 8 的公钥检索错误。导入数据库前建库:
mysql -uroot -p < red_tourism.sql如果你没有现成的 SQL 文件,用建表脚本手动执行也行。检查连接是否通,最直接的方式是先用 Navicat 或命令行连一次,能连上再看项目,不要把黑匣子留在项目里。
5.4 故障四:页面中文乱码,数据库存进去的汉字变成问号
现象:JSP 页面显示中文正常,但通过表单提交到数据库后变成问号;或者 JSP 页面本身中文全部乱码。
原因:多层编码没对齐。JSP 页面没有声明 UTF-8;Tomcat 的 GET 请求默认按 ISO-8859-1 解码;数据库连接串没指定 characterEncoding;数据库表本身是 latin1 字符集。
解决:一层一层补齐。JSP 文件头部加:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>web.xml 里加编码过滤器,保证 POST 请求统一按 UTF-8 处理:
<filter> <filter-name>encoding</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>encoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>连接串补characterEncoding=utf8,建表时统一用utf8mb4。如果数据库已经建好且表是 latin1,不要直接在 SQL 里改字符集,把数据备份后重建表。这个顺序不能反,我在这上面吃过亏,改完表结构发现历史数据全是问号,只能手动清理。
5.5 故障五:Ajax 接口返回 406,页面能打开但数据不渲染
现象:列表页静态部分正常,但页面上的分页数据区域空白,打开浏览器开发者工具,发现 Ajax 请求返回 406 Not Acceptable。
原因:Controller 方法返回了对象,也加了@ResponseBody,但项目里没有 Jackson 相关依赖,SpringMVC 找不到能把对象转成 JSON 的转换器;或者方法上写了produces = "application/json",但返回值类型是 String,转换器匹配不上。
解决:在 pom.xml 里补上 Jackson 依赖,这是最常缺失的一块:
<dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.9</version> </dependency>同时把 Controller 方法上的 produces 去掉,让它走默认协商。检查思路是:先用浏览器直接访问这个 Ajax 地址,如果返回的是 JSON,说明后端没问题,问题在前端解析;如果返回 406,就是缺失转换器或 produces 写错。另外,如果返回的是一个Result对象,记得里面要有无参构造器和 getter/setter,否则 Jackson 会报No default constructor found,控制台会给出明确错误。
6. 先照单验证再动手改造:一张冒烟清单与 JSP 视图升级的思路
拿到这类压缩包项目,我最不建议直接读源码。先花十分钟把系统跑起来,照着一张清单过一遍核心链路,确认“能不能用”,再谈“怎么改”。我常用的一张冒烟清单长这样:
| 检查项 | 操作 | 期望结果 |
|---|---|---|
| 注册登录 | 用户端注册账号并登录 | 能进入个人中心,退出后未登录状态正常 |
| 分页搜索 | 列表页输入“纪念馆”关键词搜索 | 只显示匹配景点,分页条能翻页 |
| 内容发布 | 后台上传一张图片并发布资讯 | 前台可见资讯,图片不裂 |
| 留言互动 | 用户在详情页发表留言 | 管理后台能看到,并能回复 |
| 状态下架 | 后台停用一条景点 | 前台列表不再展示该景点 |
这五项过了,项目的主干就没有大问题。之后再去读代码,你会发现带着问题上手比漫无目的看快得多。
如果这是一个课程设计或毕业设计项目,我建议不要动技术栈,把精力放在补齐业务功能和理解请求链路,最终把文档和运行截图做扎实。如果是在公司做类似的信息管理系统,升级方向就有讲究了:保留 SSM 的 Service 层和 Mapper 层,这两层是数据资产;把 JSP 视图替换成 Vue + Element UI 这类前后端分离方案,Controller 改成@RestController,统一返回 JSON。原因是 JSP 在工程化、热更新、组件复用上比前端框架落后太多,但底层的数据访问逻辑完全可以复用,不需要推倒重来。切分时先挑一个模块做样板,跑通后再逐步替换,别一下子全换。
最后说一个我自己的翻车教训:有一次图省事,直接在一个老项目里把视图层全改了,结果发现数据库字段映射和接口文档对不上,前端改了两周。后来养成的习惯是——先跑通,再拆散,再改造,顺序反了就是一地鸡毛。如果你手边正好拿着一个 ssm 结尾的 web 项目压缩包,照这个路子来,能少走很多弯路。希望帮到你。
本文还有配套的精品资源,点击获取