ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SSM+JSP红色旅游信息管理系统实战开发指南

SSM+JSP红色旅游信息管理系统实战开发指南 简介这是一套面向Java Web开发学习与毕业设计的山东红色旅游信息管理系统完整源码包采用SSMSpringSpringMVCMyBatis整合框架、JSP/Vue前端搭配MySQL 5.7与Tomcat7。系统涵盖景点介绍、旅游线路推荐、红色文化教育、在线预订、评价反馈及后台管理等模块项目经过严格调试可直接部署运行适合用来理解企业级Web项目从架构设计到功能落地的全过程。压缩包共1319个文件大小19.21MB包含136个Java源码、125个JSP页面、364个JavaScript脚本、172个PNG图片及SQL数据库脚本、项目文档等前端样式、交互逻辑与后端业务代码分离目录结构清晰便于按模块学习。资源附带数据库脚本与功能说明文档能帮助快速还原项目环境。目前已有3513人学习下载适合正在做SSM毕设或希望系统掌握Java Web全栈开发的读者参考借鉴。1. 一套SSMJSP的红色旅游信息管理系统说到底解决的是资源散落的问题前阵子帮一个县级纪念馆做信息化摸底发现管理员的日常工作还是“Excel登记 微信群传图”几十处红色旧址的信息分散在不同人的电脑里游客想查哪个景点开放、哪条线路串联合理根本无从下手。标题里这套“基于Web的山东红色旅游信息管理系统”做的就是这类事用SSM作为后端框架MySQL存数据JSP做服务端页面把景点维护、线路推荐、新闻发布、留言互动这些典型业务收敛到一个Web系统里。它的核心价值不是技术多前沿而是把散落的红色旅游资源变成一套可检索、可更新、可权限控制的在线台账。这个课题的受众也很明确要复现毕业设计的高校学生以及预算不多、想给景区或纪念场馆做信息化的小团队。SSM加JSP在今天不算新潮但胜在资料密集、原理透明、单机就能跑通。对“信息管理系统”这类以增删改查为主线的课题这套组合反而是翻车成本最低的选项。下面从框架拆解讲到建表、写代码、踩坑按一条能真正跑通的路径走一遍。2. 选SSM这套组合的理由与配置骨架先弄明白三层各自干什么2.1 Spring、SpringMVC、MyBatis在系统里的角色分工常见做法是把SSM拆成三层来看Spring作为容器总管负责Service层对象的创建、依赖注入和事务控制SpringMVC负责Web层把浏览器发来的请求路由到对应的Controller方法再把Controller返回的视图名解析成JSPMyBatis负责数据访问把Java方法和SQL语句做映射避免在代码里硬拼JDBC。三者各管一段出了问题能很快定位到具体层。JSP在这个架构里的位置是视图层。它和现在流行的前后端分离不一样页面在服务器端渲染JSP里可以直接用${}、c:forEach配合JavaBean输出数据。对信息管理系统来说这种模式够用且调试时能直接看到渲染错误堆栈不用额外起Node服务。代价是前端交互做不了太复杂但红色旅游管理系统的核心交互是表单提交、列表展示、图片定位这类程度JSP完全扛得住。这套组合的成熟度是最大筹码。网上随便搜“SSM 景点管理系统”就能找到大量同类课题遇到配置问题几乎都能搜到对应现象和修复方式。对工期紧张的学生来说这个“生态红利”比技术先进性更值钱。2.2 分层目录结构与请求流转链路我一般会把一个SSM Web项目按这样的包结构组织src/main/java ├── com/example/entity // 实体类对应数据库表 ├── com/example/mapper // MyBatis接口方法名和SQL绑定 ├── com/example/service // 业务层接口和实现 ├── com/example/controller // SpringMVC控制器接收请求 └── com/example/util // 工具类比如坐标解析、分页 src/main/webapp ├── WEB-INF │ ├── views // JSP页面按模块放子目录 │ └── web.xml // 部署描述符配置DispatcherServlet ├── static // 静态资源CSS、JS、上传图片 └── index.jsp一个“景点列表”请求的完整路径是浏览器输入/scenic/listweb.xml里配置的DispatcherServlet把请求转给SpringMVC由ScenicController.list()方法接收Controller调用ScenicServiceService调用ScenicMapperMyBatis执行对应的select语句并返回ListScenicController把这个列表塞进Model返回视图名scenic/listDispatcherServlet再用视图解析器拼出/WEB-INF/views/scenic/list.jsp路径渲染成HTML回给浏览器。弄清这条链路比会敲代码更重要。后面遇到的“页面404”“数据不显示”“表单提交不了”等问题本质上都是在排查这条链路里某一个环节断了。目录结构里需要注意JSP放在WEB-INF/views下用户在浏览器地址栏没法直接访问必须通过Controller转发这个设计在权限控制上有先天优势——如果JSP直接放在webapp根目录用户就能绕过登录拦截直接打开页面。2.3 最小可用配置pom依赖和Spring配置文件依赖我用的是Spring 4.3.x、SpringMVC 4.3.x、MyBatis 3.4.x这一档位的组合兼容性好教程多。Maven的pom.xml核心部分长这样properties spring.version4.3.18.RELEASE/spring.version /properties dependencies !-- Spring核心与MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- MyBatis与Spring整合包注意这个包的版本要与MyBatis匹配 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.4.6/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version1.3.2/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.47/version /dependency !-- JSTL和Servlet API -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version3.1.0/version scopeprovided/scope /dependency /dependencies依赖版本的选择有一条血泪经验Spring的版本请尽量选“ .RELEASE”结尾的正式版不要选带M或RC的里程碑版mybatis-spring的版本必须和mybatis主版本对应比如MyBatis 3.4.x对mybatis-spring 1.3.x。混搭版本最典型的翻车现场是启动时抛NoSuchBeanDefinitionException实际上是整合包的版本不匹配排查起来很费时间。SpringMVC的核心配置spring-mvc.xml!-- 开启注解驱动处理JSON和请求参数绑定 -- mvc:annotation-driven/ !-- 静态资源放行JS/CSS/图片不经过DispatcherServlet -- mvc:resources mapping/static/** location/static// mvc:resources mapping/upload/** location/upload// !-- 扫描Controller包 -- context:component-scan base-packagecom.example.controller/ !-- 视图解析器Controller返回scenic/list时拼接成/WEB-INF/views/scenic/list.jsp -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /bean这里最容易被忽略的是mvc:resources。如果不放行静态资源用户打开页面会发现CSS完全失效、图片全部裂开因为请求被DispatcherServlet拦截后找不到对应的Handler直接返回404。放行路径的location要写成目录末尾带斜杠的形式否则资源路径拼接会出错。MyBatis配置里有一个参数值得单独说实体类属性是驼峰命名比如scenicName而数据库字段是下划线命名scenic_name需要在mybatis-config.xml里开启驼峰映射configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings /configuration不开启这个选项查询结果里scenic_name字段就无法自动装进scenicName属性表现通常是页面表格“这一列是空的”SQL单独跑又能查到值。很多新手把这种问题误判成SQL写错其实只差这一行配置。3. 数据表设计红色旅游系统的核心业务对象与关联关系3.1 从业务对象反推表结构红色旅游信息管理系统的核心业务对象常见做法是拆成五个表景点表、线路表、线路与景点关联表、新闻表、用户表另外加一个留言表承载游客互动。景点表是主表保存名称、简介、图片、所在城市、经纬度坐标、开放时间等信息线路表保存线路名称、主题分类、推荐天数线路和景点是多对多关系一个线路串起多个景点一个景点也能出现在不同线路里所以需要一张关联表来解耦。建表SQL我习惯找一段最小可跑的版本CREATE TABLE scenic ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 景点ID, scenic_name VARCHAR(100) NOT NULL COMMENT 景点名称, city VARCHAR(50) COMMENT 所在城市, address VARCHAR(200) COMMENT 详细地址, intro TEXT COMMENT 简介, image_url VARCHAR(200) COMMENT 主图路径, pos_x INT COMMENT 图片定位坐标X, pos_y INT COMMENT 图片定位坐标Y, open_time VARCHAR(50) COMMENT 开放时间, ticket_price DECIMAL(10,2) DEFAULT 0 COMMENT 门票价格, status TINYINT DEFAULT 1 COMMENT 1显示 0隐藏, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;pos_x和pos_y这两个字段是给景点图片加“坐标定位”功能用的后面会在实现章节详细展开。status字段属于软删除设计删除景点时只改状态不删记录避免前台页面突然出现图片裂图也方便后台做恢复操作。所有表统一用InnoDB引擎和utf8mb4字符集前者支持事务后者能存生僻字和特殊符号红色旅游景点名称里偶尔会有方言字用utf8会报“字符集不匹配”的错。3.2 表结构拆分的关键决策逻辑外键与索引线路表与关联表的设计有一个值得展开的决策点是否创建物理外键。常见做法是保留关联字段但不建FOREIGN KEY约束原因有二一是MyBatis开发中删除、更新数据时经常被外键约束卡住业务逻辑里要先删子表再删主表顺序写错就报错处理成本高于收益二是这套系统数据量不大靠代码层保证关联完整性完全够用。用逻辑外键也就是在关联表里存主表ID查询时用JOIN关联后续要做批量操作会灵活很多。为了提高查询效率关联表的两列都要建普通索引CREATE TABLE route_scenic ( id INT PRIMARY KEY AUTO_INCREMENT, route_id INT NOT NULL, scenic_id INT NOT NULL, sort_no INT DEFAULT 0 COMMENT 景点在线路中的顺序, INDEX idx_route (route_id), INDEX idx_scenic (scenic_id) );sort_no字段容易被忽略但实际做线路展示时游客希望看到“第一天去哪、第二天去哪”的顺序如果没有这个字段只能按ID排序体验很差。在MyBatis的XML里查询线路详情时用ORDER BY rs.sort_no ASC就能保证顺序。3.3 预留免登录浏览与后台权限控制的拆分游客访问前台不需要登录但后台管理必须登录。常见做法是给用户表加一个role字段0表示普通管理员1表示超级管理员超级管理员可以管理管理员账号普通管理员只能维护景点和新闻。这比拆成“用户表角色表权限表”的方案轻量得多对毕设和中小型系统完全够用。用户表的密码字段有一个细节长度预留到100以上不要用默认的50。原因是你不可能明文存密码会用MD5加盐或BCrypt加密后存储加密后的字符串长度远超原始密码。我见过表建好后存不了密文导致登录接口一直报错的案例根源就是VARCHAR(50)长度不足。另外表里补一个last_login_time字段不参与业务但能在后台界面展示“最近登录时间”一眼看出账号是否活跃这类小细节在答辩时很加分。4. 核心功能落地从Mapper写起到JSP页面完整跑通4.1 用MyBatis写景点分页查询SQL和参数一次说清MyBatis里写分页查询常见做法是不引入PageHelper插件直接手写LIMIT参数这样依赖更少、逻辑透明。Mapper接口和XML是成对出现的接口只定义方法签名SQL写在XML里// ScenicMapper.java public interface ScenicMapper { // 分页查询景点列表offset是偏移量pageSize是每页条数 ListScenic selectScenicPage(Param(offset) int offset, Param(pageSize) int pageSize); // 统计总数用于前端计算总页数 int countScenic(); }!-- ScenicMapper.xml -- select idselectScenicPage resultTypecom.example.entity.Scenic SELECT id, scenic_name, city, image_url, ticket_price FROM scenic WHERE status 1 ORDER BY create_time DESC LIMIT #{offset}, #{pageSize} /selectMapper接口里要注意每个方法参数都用Param注解声明名字否则XML里#{offset}会解析失败。普通单参数可以不写一旦有两个以上参数必须写这是MyBatis的硬性规则。SQL里LIMIT #{offset}, #{pageSize}两个占位符显示的是JDBC的预编译参数MyBatis会自动生成?占位符并设置值避免SQL注入风险。Controller层的调用方式接收页面传来的pageNum当前页码和pageSize计算出offset传给MapperRequestMapping(/scenic/list) public String list(RequestParam(defaultValue 1) int pageNum, Model model) { int pageSize 8; // 计算偏移量第一页从0开始第二页从8开始 int offset (pageNum - 1) * pageSize; ListScenic scenicList scenicService.getScenicPage(offset, pageSize); int total scenicService.countScenic(); int totalPages (int) Math.ceil((double) total / pageSize); model.addAttribute(scenicList, scenicList); model.addAttribute(totalPages, totalPages); model.addAttribute(currentPage, pageNum); return scenic/list; }RequestParam(defaultValue 1)这个写法的价值在于第一次不带页码访问时也能正常加载第一页如果不写defaultValue用户直接访问/scenic/list会抛MissingServletRequestParameterException。分页条在JSP里的渲染用JSTL的c:forEach遍历页码上一页和下一页加参数判断这是信息管理系统最常见的交互模式。4.2 前台景点首页与线路详情页JSP这样组织不踩坑前台页面我习惯用一份统一的header.jsp和footer.jsp做公共部分避免每个页面重复写导航栏。header.jsp里放导航链接和当前登录状态的判断% page contentTypetext/html;charsetUTF-8 languagejava % % taglib prefixc urihttp://java.sun.com/jsp/jstl/core % nav a href${pageContext.request.contextPath}/scenic/list景点列表/a a href${pageContext.request.contextPath}/route/list红色线路/a a href${pageContext.request.contextPath}/news/list新闻动态/a c:if test${sessionScope.adminUser ! null} a href${pageContext.request.contextPath}/admin/index后台管理/a /c:if /nav这里最容易被忽略的是${pageContext.request.contextPath}这个表达式。如果不加项目部署名链接会变成/scenic/list而项目实际部署路径可能是/redtourism/scenic/list所有导航链接都会404。凡是JSP里的路径都要用pageContext.request.contextPath拼接这条规则适用于href、src、表单action三处。c:if标签用于判断session里是否存了登录用户没有登录就不显示后台入口。线路详情页要展示“线路下按顺序排列的景点”这块在Service里完成关联查询。Controller先拿到线路基本信息再通过Mapper查关联表得到景点ID列表再批量查出景点内容。较高效的做法是写一个多表JOIN一次查全select idselectRouteDetail resultMapRouteWithScenicMap SELECT r.id, r.route_name, r.theme, s.id AS scenic_id, s.scenic_name, s.image_url FROM route r LEFT JOIN route_scenic rs ON r.id rs.route_id LEFT JOIN scenic s ON rs.scenic_id s.id WHERE r.id #{routeId} ORDER BY rs.sort_no ASC /select这种JOIN查询的结果集需要用resultMap来映射“一线路多景点”的结构关系。如果不会写resultMap退而求其次的办法是循环查两次数据量小、排序可控完全能接受只是SQL条数多一点。对毕设项目代码的可维护性和可解释性比极致性能重要答辩时你也能更清楚地讲出每一步在做什么。4.3 景点图片坐标定位JSP里的交互实现方式热词里关于“jsp图片如何对坐标定位”的检索量不小放在这个系统里就对应一个很实用的功能后台管理景点时在景点主图上标记一个位置前台详情页显示这个标记。常见做法是后台用一张可点击的图片接受坐标前台用CSS position把标记点按比例定位显示。后台管理的JSP页面里先用原生JavaScript监听图片的点击事件input typehidden idposX nameposX value${scenic.posX}/ input typehidden idposY nameposY value${scenic.posY}/ div img src${pageContext.request.contextPath}/${scenic.imageUrl} idmainImg stylewidth:600px; height:400px;/ /div script // 获取图片实际显示尺寸换算点击坐标与图片真实坐标的比例 document.getElementById(mainImg).addEventListener(click, function(e) { var rect this.getBoundingClientRect(); // e.clientX是浏览器窗口坐标减去rect.left得到图片内坐标 var x e.clientX - rect.left; var y e.clientY - rect.top; // 按显示尺寸与真实尺寸的比例缩放 var realX Math.round(x * (this.naturalWidth / rect.width)); var realY Math.round(y * (this.naturalHeight / rect.height)); document.getElementById(posX).value realX; document.getElementById(posY).value realY; }); /script这段代码的关键在比例换算。naturalWidth和naturalHeight是图片的真实像素尺寸rect.width是图片在页面上的布局尺寸两者往往不一致因为CSS限制了显示宽度。如果不做换算直接把点击坐标存进数据库前台展示时用不同尺寸的CSS渲染标记点就会偏移。这是一个非常典型的坑后台点了正确位置前台标记偏移几十像素。前台展示时只需要按比例定位div styleposition: relative; display: inline-block; img src${pageContext.request.contextPath}/${scenic.imageUrl} stylewidth:600px; idshowImg/ div styleposition: absolute; left: ${scenic.posX * 600 / scenic.imgWidth}px; top: ${scenic.posY * 400 / scenic.imgHeight}px; span stylecolor:red;●/span /div /div前台页面把后台存的真实坐标按当前图片的显示尺寸重新换算定位。这里要求后台还要保存图片的真实宽高imgWidth、imgHeight所以建表时不要只存坐标要把图片尺寸一起存上并在Controller层上传图片后用Java原生ImageIO读取尺寸写入数据库。这是一个先在后台点击、传坐标再到前台展示定位的完整链路红色旅游景点介绍里标记重点展品、标注旧址位置都很有用。4.4 文件上传与静态资源映射的配合景点图片和新闻配图都要走文件上传。SpringMVC的文件上传需要配置CommonsMultipartResolverbean idmultipartResolver classorg.springframework.web.multipart.commons.CommonsMultipartResolver !-- 限制上传大小单位是字节这里设置10MB -- property namemaxUploadSize value10485760/ !-- 指定上传时使用的字符编码 -- property namedefaultEncoding valueUTF-8/ /beanController层接收上传文件RequestMapping(/admin/scenic/save) public String saveScenic(RequestParam(file) MultipartFile file, Scenic scenic, HttpServletRequest request) { if (!file.isEmpty()) { // 用时间戳加随机数重新构造文件名避免中文和重名问题 String originalName file.getOriginalFilename(); String ext originalName.substring(originalName.lastIndexOf(.)); String newName System.currentTimeMillis() _ (int)(Math.random()*1000) ext; // 上传保存的物理路径在项目根目录的upload文件夹下 String savePath request.getServletContext().getRealPath(/upload); File dir new File(savePath); if (!dir.exists()) dir.mkdirs(); file.transferTo(new File(savePath / newName)); // 数据库只存相对路径页面访问时结合项目域名使用 scenic.setImageUrl(upload/ newName); } scenicService.saveScenic(scenic); return redirect:/scenic/list; }文件名重造是必须做的。很多JSP项目直接用中文文件名Tomcat在Windows和Linux上对中文路径的URL编码处理不一致同一个文件有时候能访问有时候404非常玄学。用System.currentTimeMillis()加随机数是最省事的保证唯一的方式。数据库只存“upload/xxx.jpg”这样的相对路径这样将来把整个upload目录迁移到CDN或对象存储时只需要改访问前缀不需要动数据库。上传路径getRealPath(/upload)有一个必须知道的边界如果你用IDEA的Tomcat插件启动上传文件会被写到IDEA的out目录里如果用外部Tomcat启动就会写到Tomcat的webapps目录下。两种启动方式下的路径不一致容易让新手以为文件“丢了”实际是被写到不同地方了。我一般会在代码里把保存路径打印到日志这个习惯能省大量排查时间。5. 避坑清单SSMJSP这条路上常翻车的5个地方5.1 IDEA 2024创建Web项目时找不到WEB-INF目录现象新建的Maven项目里没有src/main/webapp/WEB-INF/web.xml甚至没有webapp目录不知道把JSP和配置文件放哪。原因新版的IntelliJ IDEA在创建普通Maven工程时不再自动生成Web目录结构和web.xml需要手动添加Web支持这个操作藏得比较深。解决打开“Project Structure”选中模块后点“Add”里的“Web”然后在“Deployment Descriptor”区域指定web.xml路径为src/main/webapp/WEB-INF/web.xml最后在“Web Resource Directories”里把src/main/webapp设为资源目录。操作完成后就能正常把JSP放到webapp/WEB-INF/views下Tomcat配置里“Deployment”选项选war exploded模式来跑。5.2 JSP页面中文乱码改了一堆编码还是乱现象页面中文全部变成问号或者“汉嗔重写字符集声明也没用。原因乱码有三个叠加来源只改一处不够。JSP文件本身的编码、JSP页面声明的contentType编码、Tomcat连接器的URI编码这三层都要统一成UTF-8。解决JSP文件头写% page languagejava contentTypetext/html; charsetUTF-8 pageEncodingUTF-8 %IDEA右下角确认文件编码是UTF-8而不是系统默认的GBKTomcat的server.xml里给Connector加URIEncodingUTF-8。数据库连接串里也要加characterEncodingutf8否则乱码会一路传到数据库再传回页面。四层全部统一才能真正解决乱码。5.3 MyBatis查询某列数据为空SQL单独跑有值现象页面表格里“景点名称”列有数据显示“所在城市”列空白但把SQL粘到Navicat里执行能看到数据。原因最常见的是mapUnderscoreToCamelCase没开或者数据库字段命名和实体类属性名对不上。比如字段叫city_name实体类属性叫cityName没有驼峰映射时MyBatis不知道要映射到哪个属性默认按字段名找同名属性找不到就留空。解决在mybatis-config.xml里开启setting namemapUnderscoreToCamelCase valuetrue/或者用resultMap显式指定每个字段对应哪个属性。不要用写别名的方式解决SELECT city_name AS cityName在查询简单时有效但后期改表结构时别名容易漏改维护成本高。5.4 上传图片后访问404CSS也时好时坏现象上传一张图片在后台能看到保存成功的提示但访问/upload/xxx.jpg一直404页面CSS样式也一会儿有一会儿没有。原因SpringMVC的DispatcherServlet默认拦截所有请求包括静态资源和上传文件。如果你的配置里没有放行/upload/**和/static/**所有来到Tomcat的请求都会先找Controller里有没有对应的RequestMapping找不到就返回404。解决在spring-mvc.xml里加两行mvc:resources放行静态资源这是最标准、最不容易出错的做法。注意mvc:resources放行的目录和getRealPath指向的目录要一致很多项目把文件写到/upload放行路径却写成了/uploads这种错位排查很费眼。另外上传目录要建在webapp根目录下而不是WEB-INF下否则Tomcat把它当成受保护资源静态放行了也访问不了。5.5 JSP页面提交表单后跳回原页面数据是旧值现象编辑景点信息后点保存浏览器返回列表页发现刚才修改的内容没有生效。但数据库里查询已经有新值了。原因Controller里保存成功后用了forward转回页面而不是redirect重定向。forward时模型数据还是内存里的旧数据页面直接渲染出来浏览器地址栏也没变刷新后数据又变成数据库里的值看起来像“没保存成功”。解决保存、删除这类修改数据的操作成功后一律用return redirect:/scenic/list做重定向让浏览器重新发起一次全新的GET请求这样展示的数据是刚查的最新数据。forward只适合保存完成后马上要在当前页面展示成功提示的场景但要同步更新Model里的数据才能避免旧值问题。这个习惯能避免一大类“改了没变”的假象。6. 从跑通到能交付一条验证链路和两个加分改造6.1 用浏览器开发者工具验证一个完整请求系统跑起来后不要只在页面里点来点去打开浏览器开发者工具的Network面板逐一检查请求的状态码和耗时。一个正常通过的景点列表请求通常会看到一条/scenic/list的文档请求状态码是200紧接着有若干条CSS、JS、图片资源请求。如果发现某个静态资源状态码是404马上能对应到静态资源放行配置的问题如果/scenic/list本身是500点开标签页能看到完整的Java异常堆栈。再验证表单提交链路编辑景点并保存在Network里找到POST请求查看Payload里的参数名是否和Controller方法参数对得上比如表单里提交了posX、posY、imageUrl后端的Scenic实体有没有对应属性。用这个视角配合IDEA的Debug模式在Controller和Service里各打一个断点拖动一次请求就能把整个调用链走完。建议把这套操作作为一个固定动作每次写完一个功能模块都跑一遍比最后联调时集中排错高效得多。6.2 安全加固登录拦截和SQL注入的两条底线把系统交付给老师或真实使用之前至少要过两个安全底线。第一个是后台页面的登录拦截写一个HandlerInterceptor在preHandle里检查session里有没有adminUser没有就重定向到登录页。SpringMVC的拦截器配置在spring-mvc.xml里只拦截/admin/**路径放行前台的所有请求。这个拦截器不需要很复杂20行代码就能完成但能挡住绝大多数“直接猜URL进后台”的尝试。第二个是SQL注入和XSS的防线。MyBatis的#{}是预编译写法只要不在XML里用${}拼接字符串SQL注入基本堵得住${}的用途只有表名、排序字段等没法预编译的场景这个系统里尽量少用就写ORDER BY固定的字段而不接受用户传参。XSS方面在留言和新闻评论这类接受用户输入的地方用JSTL的c:out替代直接输出${}它会自动转义HTML标签防止用户在内容里注入脚本。6.3 一个加分的进阶改造在线PDF打印做红色旅游信息系统时有一个高频需求是“把某个红色景点的介绍页保存成PDF发给游客”。JSP项目不用引入前端打印框架利用浏览器的原生打印功能就能实现这个效果在详情页加一个“打印/存PDF”按钮点击后调用window.print()配合一段media print的CSS把导航栏和按钮区域隐藏只保留介绍内容区。代码量很小但在真实场景里很实用运营人员可以一键把景点介绍发给线下柜台。media print { /* 打印时隐藏导航、按钮、侧栏 */ .navbar, .footer, .print-btn { display: none !important; } /* 强制背景色和图片都输出 */ * { -webkit-print-color-adjust: exact; } }打印功能放在“红色线路”详情页时效果更好——用户可以把一条线路的行程按顺序打印出来作为实地游览的手册。这个功能在答辩演示时容易被评委当成亮点因为它是从真实使用场景倒推出来的功能而不是教科书的CRUD。做完上面这些这套SSMJSP的红色旅游信息管理系统就已经不是“能跑通的代码”了而是能给人用的工具。我自己做这类课题时最大的教训就是技术选型新不新不重要先把增删改查的闭环做透再把用户在使用过程中会遇到的边角问题处理干净就是一份对得起投入的交付。希望帮到你。本文还有配套的精品资源点击获取
返回列表