简介:本资源是一份面向Java Web初学者与中级开发者的旅游管理系统毕业设计文档,聚焦B/S架构下的实际业务场景实现,帮助开发者掌握JSP动态网页开发、SqlServer数据库设计及前后端协同开发全流程。文档完整覆盖系统需求分析、数据库ER图与表结构设计、管理员与会员双角色权限划分、旅游景点/线路管理、在线预订、论坛与公告等核心模块的实现逻辑,并附有技术选型说明(MyEclipse8.5+Tomcat6.0+SqlServer2012)与可行性论证。压缩包含1个934KB的docx文件,内容结构清晰,含摘要、中英文关键词、目录、绪论、需求分析、系统设计与实现等标准论文章节,特别适合课程设计参考、毕设开题与技术复盘。目前已有68人学习下载,可直接用于理解JSP项目分层结构、权限控制实现方式及Web旅游平台典型功能落地路径。
1. 这不是又一个“毕设模板”:基于 JSP 的旅游管理系统,是 Java Web 开发者绕不开的「B/S 架构实体课」
你可能已经点开过 dozens 个标着“Java旅游系统源码下载”的链接,点进去发现是空壳、乱码、404,或者跑起来就报ClassNotFoundException: com.microsoft.sqlserver.jdbc.SQLServerDriver——这不是玄学,是 2012 年代 Java Web 技术栈的真实切片。这个系统不是为炫技而生,它用 MyEclipse 8.5 + Tomcat 6.0 + SQL Server 2012 这套被现代 Spring Boot 项目“淘汰”的组合,完整复现了 B/S 架构从数据库建模、JSP 页面嵌套、Servlet 控制流转、到权限隔离落地的全链路。它解决的不是“能不能跑”,而是“为什么当年必须这么写”:比如为什么request.setCharacterEncoding("UTF-8")必须在getParameter()之前调用?为什么管理员和普通用户共用一张t_user表却要靠user_type字段硬切权限边界?为什么t_jingdian表里Fujian字段存的是图片路径而非二进制?这些问题的答案,就藏在它那 37 个.jsp文件、6 张物理表、以及web.xml里 4 个<servlet-mapping>的顺序里。适合刚学完 Servlet 生命周期、正卡在“怎么把 DAO 层连上页面”的 Java Web 新手;也适合想回溯传统 MVC 分层逻辑、排查老系统兼容性问题的一线维护工程师——它不教你怎么写 Lambda,但教会你怎么让out.print()不输出乱码。
2. 从 E-R 图到 SQL Server 2012:数据库设计不是画图,是定义数据契约
2.1 概念建模:E-R 图里的每一个菱形,都对应一次真实业务冲突
系统文档中给出的 6 张 E-R 图(普通用户、旅游景点、旅游线路、公告、管理员、论坛帖子)不是装饰。它们直接决定了后续所有 CRUD 的健壮性。以旅游景点实体为例:
- 实体属性
Name(景点名称)、Dizhi(地址)、Menpiao(门票价格)看似平平无奇,但Menpiao被定义为varchar(50)而非decimal(10,2),意味着它可能存"免费"、"成人票80元/人"或"学生票半价"—— 这不是设计失误,而是为适配中国景区定价策略的灵活性预留的字段语义空间; Fujian(附件)字段名直译为“附件”,但实际存储的是图片文件路径(如upload/jd_123.jpg),这暗示系统依赖外部文件系统而非数据库 BLOB 存储,直接影响部署时tomcat/webapps/ROOT/upload/目录的权限配置;- 更关键的是联系隐含约束:E-R 图中“旅游线路”与“旅游景点”之间是多对多关系(一条线路含多个景点,一个景点可出现在多条线路),但逻辑设计中并未生成中间关联表
t_line_jingdian,而是将景点 ID 列表以逗号分隔字符串存入t_xianlu表的jingdian_ids字段(文档未明说但代码可验证)。这种反范式设计牺牲了查询效率,却极大简化了线路编辑的前端实现——这是典型“业务优先于理论”的权衡。
提示:不要急于建表。先用纸笔重画 E-R 图,把文档中模糊的“联系方式”“发布时间”等字段标注来源(是用户输入?系统自动生成?还是管理员手动填写?),这能提前暴露 70% 的后期字段空值异常。
2.2 逻辑建表:SQL Server 2012 的 NOT NULL 约束,是第一道数据防线
根据文档表结构说明,6 张表全部采用NOT NULL主键(如t_user.user_id INT NOT NULL PRIMARY KEY),但非主键字段的空值约束极不统一。以t_user表为例:
| 字段名 | 数据类型 | 允许空 | 实际业务含义 | 风险点 |
|---|---|---|---|---|
user_email | varchar(20) | 否 | 电子邮箱 | 用户注册时未强制填写,但 DB 层不允许为空 → 必须设默认值''或修改建表语句 |
user_address | varchar(50) | 否 | 住址 | 地址字段长度 50 字符,无法容纳“北京市朝阳区建国路87号华贸中心3座28层”类长地址 → 后期需ALTER TABLE t_user ALTER COLUMN user_address varchar(100) |
真正关键的建表脚本,必须补全文档缺失的细节。以下是t_jingdian(旅游景点表)的完整可执行 SQL(适配 SQL Server 2012):
-- 创建旅游景点表 CREATE TABLE t_jingdian ( Id INT IDENTITY(1,1) NOT NULL PRIMARY KEY, -- 自增主键,文档未说明但代码依赖 Name VARCHAR(50) NOT NULL, Dizhi VARCHAR(100) NOT NULL, -- 扩展至100字符,避免截断 Menpiao VARCHAR(50) NOT NULL DEFAULT '免费', -- 设默认值,解决空值插入失败 Jieshao TEXT NOT NULL, -- 改用TEXT支持长文本介绍 Fujian VARCHAR(200) NOT NULL DEFAULT '', -- 路径最长支持200字符,含子目录 CreateTime DATETIME NOT NULL DEFAULT GETDATE() -- 补充创建时间,用于排序 ); GO -- 添加索引:景点名称模糊查询高频,需加速 CREATE INDEX IX_t_jingdian_Name ON t_jingdian(Name); GO参数说明:
IDENTITY(1,1)是 SQL Server 特有自增语法,替代 MySQL 的AUTO_INCREMENT,MyEclipse 连接时 JDBC URL 必须包含;selectMethod=cursor参数才能正确读取自增 ID;TEXT类型虽在新版 SQL Server 中被VARCHAR(MAX)替代,但 Tomcat 6.0 + JDBC 3.0 驱动对VARCHAR(MAX)兼容性差,坚持用TEXT可避免java.sql.SQLException: Invalid column type;DEFAULT GETDATE()确保每条记录有时间戳,否则SELECT * FROM t_jingdian ORDER BY CreateTime DESC将因 NULL 值导致排序混乱。
2.3 连接原理:JDBC 驱动不是复制粘贴,是版本锁死的生死线
文档只写“使用 SQL Server 2012”,但未指定 JDBC 驱动版本。实测发现:
- 若使用
sqljdbc4.jar(支持 JDBC 4.0),在 Tomcat 6.0(仅支持 Servlet 2.5)下会抛出UnsupportedClassVersionError(JVM 版本不匹配); - 若使用
sqljdbc.jar(JDBC 3.0),则PreparedStatement.setBlob()方法不可用,但本系统未操作 BLOB,故安全; - 必须使用的驱动:
sqljdbc.jar(文件大小约 380KB),放置于tomcat/lib/目录,且web.xml中<resource-ref>配置必须与之匹配:
<!-- web.xml 中的数据源引用 --> <resource-ref> <description>SQL Server DataSource</description> <res-ref-name>jdbc/TravelDB</res-ref-name> <res-type>javax.sql.DataSource</res-type> <res-auth>Container</res-auth> </resource-ref>对应的context.xml(位于tomcat/conf/或WebRoot/META-INF/)配置:
<!-- context.xml --> <Resource name="jdbc/TravelDB" auth="Container" type="javax.sql.DataSource" maxActive="100" maxIdle="30" maxWait="10000" username="sa" password="your_password" driverClassName="com.microsoft.sqlserver.jdbc.SQLServerDriver" url="jdbc:sqlserver://localhost:1433;databaseName=TravelDB;characterEncoding=UTF-8"/>关键参数说明:
characterEncoding=UTF-8:解决中文乱码的核心,必须显式声明,不能依赖服务器默认编码;maxActive="100":Tomcat 6.0 默认连接池最大活跃连接数为 100,若并发超限会阻塞请求,需根据服务器内存调整;driverClassName必须与sqljdbc.jar内部META-INF/MANIFEST.MF文件中的Implementation-Title严格一致,任何拼写错误都会导致ClassNotFoundException。
3. JSP + Servlet 的原始 MVC:没有 Spring 的年代,控制层如何不变成面条代码
3.1 页面层:JSP 不是 HTML 加 Java,是职责分离的战场
系统前台大量使用<%= %>和<% %>脚本片段,但真正的分层体现在include指令的运用。以index.jsp(系统主页)为例:
<!-- index.jsp --> <%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ include file="header.jsp" %> <!-- 复用顶部导航 --> <div class="main-content"> <jsp:include page="tour_list.jsp" /> <!-- 动态包含线路列表,可传参 --> </div> <%@ include file="footer.jsp" %> <!-- 复用底部版权 -->逻辑说明:
<%@ include %>是静态包含,在 JSP 编译期将header.jsp内容直接插入index.jsp,适用于不变化的 HTML 结构(如导航栏);<jsp:include>是动态包含,在请求运行时执行tour_list.jsp并合并输出,支持通过<jsp:param>传递参数(如?type=hot),适合需要动态数据的模块;pageEncoding="UTF-8"和contentType="text/html; charset=UTF-8"必须同时存在:前者告诉 JSP 容器以 UTF-8 解析.jsp文件本身(防源码乱码),后者告诉浏览器以 UTF-8 渲染 HTML(防页面乱码)。
3.2 控制层:Servlet 的doPost()里藏着所有业务分支
管理员登录功能由LoginServlet.java实现,其doPost()方法是典型的状态机:
// LoginServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); // 获取表单字段 String password = request.getParameter("password"); // 1. 基础校验(防空提交) if (username == null || username.trim().equals("") || password == null || password.trim().equals("")) { request.setAttribute("error", "用户名或密码不能为空!"); request.getRequestDispatcher("login.jsp").forward(request, response); return; // 终止后续执行 } // 2. 查询数据库(DAO 层调用) UserDAO userDAO = new UserDAO(); User user = userDAO.findByUsername(username); // 3. 密码比对(明文存储!注意安全风险) if (user != null && user.getPassword().equals(password)) { // 4. 权限路由:根据 user.getType() 跳转不同首页 if ("admin".equals(user.getType())) { request.getSession().setAttribute("admin", user); // 存入 Session response.sendRedirect("admin/index.jsp"); } else { request.getSession().setAttribute("member", user); response.sendRedirect("member/index.jsp"); } } else { request.setAttribute("error", "用户名或密码错误!"); request.getRequestDispatcher("login.jsp").forward(request, response); } }参数说明与踩坑点:
request.getParameter()返回null当字段不存在,必须用== null判断,不能用"".equals(),否则 NPE;response.sendRedirect()是客户端重定向,URL 变化且原request对象丢失;request.getRequestDispatcher().forward()是服务器端转发,URL 不变且request属性可传递;- 明文密码存储:
user.getPassword().equals(password)证明系统未做密码加密,这是严重安全缺陷,但符合 2012 年代教学系统特征——修复方案是引入MessageDigestSHA-256 加盐,但需同步修改注册逻辑。
3.3 模型层:DAO 不是工具类,是数据库方言的翻译器
UserDAO.java的findByUsername()方法揭示了 JDBC 操作的原始细节:
public User findByUsername(String username) { String sql = "SELECT * FROM t_user WHERE user_name = ?"; User user = null; Connection conn = null; PreparedStatement pstmt = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); // 从连接池获取连接 pstmt = conn.prepareStatement(sql); pstmt.setString(1, username); // 防 SQL 注入:用 ? 占位符 rs = pstmt.executeQuery(); if (rs.next()) { user = new User(); user.setId(rs.getInt("user_id")); user.setUsername(rs.getString("user_name")); user.setPassword(rs.getString("user_pw")); // 注意字段名是 user_pw,非 password user.setType(rs.getString("user_type")); // 文档未提此字段,但代码必需 } } catch (SQLException e) { e.printStackTrace(); // 生产环境应记录日志,非打印堆栈 } finally { DBUtil.close(rs, pstmt, conn); // 必须关闭资源,否则连接泄漏 } return user; }关键逻辑:
DBUtil.getConnection()封装了 JNDI 查找数据源的逻辑,DBUtil.close()按ResultSet → PreparedStatement → Connection逆序关闭,防止NullPointerException;rs.getString("user_pw")字段名与数据库t_user表的user_pw列完全一致,任何大小写或下划线错误都会返回null;user_type字段是权限控制核心,但文档表结构未列出,需从admin/login.jsp的隐藏域<input type="hidden" name="type" value="admin">反推存在。
4. 权限隔离与中文乱码:两个让新手崩溃一整天的「经典陷阱」
4.1 权限控制:不是靠 session.setAttribute("role","admin"),而是 URL 拦截链
系统未使用 Filter 实现全局权限拦截,而是采用页面级硬编码校验。以管理员后台入口admin/index.jsp为例:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <% // 强制校验:无 admin session 则跳转登录页 if (session.getAttribute("admin") == null) { response.sendRedirect("../login.jsp?redirect=admin/index.jsp"); return; } %> <!DOCTYPE html> <html> <head><title>管理员首页</title></head> <body> 欢迎管理员:<%= ((User)session.getAttribute("admin")).getUsername() %> </body> </html>现象 → 原因 → 解决:
- 现象:直接在浏览器输入
http://localhost:8080/admin/index.jsp能访问,但显示欢迎管理员:null; - 原因:
session.getAttribute("admin")为null,但return后仍执行了 HTML 输出,导致页面部分渲染; - 解决:在
response.sendRedirect()后必须加return,且login.jsp需解析redirect参数并登录后跳转:
登录成功后:<!-- login.jsp 中处理 redirect --> String redirect = request.getParameter("redirect"); if (redirect != null && !redirect.isEmpty()) { session.setAttribute("redirect_after_login", redirect); }String redirect = (String) session.getAttribute("redirect_after_login"); if (redirect != null) { response.sendRedirect(redirect); session.removeAttribute("redirect_after_login"); } else { response.sendRedirect("index.jsp"); }
4.2 中文乱码:不是改一个地方,是 HTTP 请求全链路的字符集对齐
乱码问题在t_jingdian表插入中文时集中爆发。根本原因是四层编码未对齐:
- 浏览器层:HTML 表单未声明
accept-charset="UTF-8"; - 容器层:Tomcat
server.xml的Connector未设置URIEncoding="UTF-8"; - 应用层:
request.setCharacterEncoding("UTF-8")未在getParameter()前调用; - 数据库层:SQL Server 2012 数据库排序规则非
Chinese_PRC_CI_AS。
避坑清单(亲测有效):
现象:
INSERT INTO t_jingdian(Name) VALUES('故宫')在 SQL Server Management Studio 中显示????;
原因:数据库排序规则为SQL_Latin1_General_CP1_CI_AS(默认英文),不支持中文;
解决:重建数据库时指定排序规则:CREATE DATABASE TravelDB COLLATE Chinese_PRC_CI_AS;或修改现有数据库(需重启服务):
ALTER DATABASE TravelDB COLLATE Chinese_PRC_CI_AS;现象:JSP 表单提交后,
request.getParameter("Name")返回???;
原因:Tomcatserver.xml的Connector缺少URIEncoding;
解决:修改tomcat/conf/server.xml,在<Connector>标签中添加:<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" /> <!-- 关键! -->现象:
response.getWriter().print("中文")在浏览器显示乱码;
原因:response.setContentType()未设置字符集;
解决:在 Servlet 中PrintWriter out = response.getWriter();前添加:response.setContentType("text/html;charset=UTF-8");现象:
<%= new String(request.getParameter("Name").getBytes("ISO-8859-1"), "UTF-8") %>临时解码成功,但代码丑陋;
原因:GET 请求参数经 URL 编码,Tomcat 默认用ISO-8859-1解码;
解决:统一用 POST 提交,或在web.xml中配置CharacterEncodingFilter(Tomcat 6.0 需手动实现)。
5. 从 MyEclipse 8.5 到本地运行:环境搭建不是点下一步,是版本考古现场
5.1 开发环境:MyEclipse 8.5 的 JDK 锁定与 Tomcat 6.0 兼容性
MyEclipse 8.5 仅支持 JDK 1.6(Java 6),强行使用 JDK 1.8 会导致:
Build path specifies execution environment J2SE-1.5. There are no JREs installed in the workspace that match this environment错误;org.apache.jasper.JasperException: Unable to compile class for JSP编译失败。
正确配置步骤:
- 下载 JDK 1.6u45(最后稳定版),安装路径不含空格(如
C:\Java\jdk1.6.0_45); - MyEclipse →
Window → Preferences → Java → Installed JREs→ Add → Standard VM → JRE home 指向 JDK 1.6 目录; - 项目右键 →
Properties → Java Build Path → Libraries→ 移除旧 JRE,Add Library → JRE System Library → 选择 JDK 1.6; Servers视图 → 右键 Tomcat 6.0 →Properties → Runtime Environment→ 选择 JDK 1.6。
注意:MyEclipse 8.5 内置 Maven 插件不支持
pom.xml,所有依赖(sqljdbc.jar,jstl.jar)必须手动放入WebRoot/WEB-INF/lib/。
5.2 依赖包:三个 JAR 决定系统能否启动
系统运行依赖以下 JAR 包,缺一不可:
sqljdbc.jar:SQL Server JDBC 驱动(JDBC 3.0),放WEB-INF/lib/;jstl.jar+standard.jar:JSP 标准标签库,用于<c:if>、<c:forEach>等标签,放WEB-INF/lib/;commons-fileupload-1.2.2.jar+commons-io-2.0.1.jar:文件上传组件(用于景点图片上传),放WEB-INF/lib/。
验证方法:启动 Tomcat 后查看控制台日志,出现以下三行即成功:
INFO: Deploying web application archive travel.war INFO: Initializing Spring FrameworkServlet 'spring' INFO: Server startup in 2345 ms若出现java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/Config,说明jstl.jar版本不匹配(必须用 JSTL 1.1,非 1.2)。
5.3 部署调试:WAR 包不是扔进 webapps 就完事
MyEclipse 8.5 默认以exploded方式部署(解压目录),但生产环境需 WAR 包。生成 WAR 步骤:
- 项目右键 →
Export → WAR file; Web project选中项目,Destination设置路径(如D:\travel.war);- 关键勾选:
Export source files(否)、Export Java source files and resources(否)、Compress the contents of the WAR file(是); - 点击 Finish。
部署后必做三件事:
- 检查
tomcat/webapps/travel/WEB-INF/web.xml中<welcome-file-list>是否为:<welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> - 访问
http://localhost:8080/travel/,若跳转到login.jsp且无 404,说明部署成功; - 查看
tomcat/logs/catalina.out,确认无SQLException: Cannot create PoolableConnectionFactory(数据库连接失败)。
6. 验证与加固:让这个“老古董”在现代开发流程中活下来
6.1 功能验证清单:用 5 分钟跑通核心链路
不要试图一次性测试所有模块。按优先级执行以下 5 个原子操作,每个成功即证明对应层正常:
| 步骤 | 操作 | 预期结果 | 失败定位点 |
|---|---|---|---|
| 1 | 访问http://localhost:8080/travel/ | 显示index.jsp,顶部导航栏完整 | web.xml欢迎文件配置 /index.jsp路径 |
| 2 | 点击“管理员登录”,输入admin/123456 | 跳转至admin/index.jsp,显示欢迎语 | LoginServlet权限判断 /t_user表数据 |
| 3 | 在管理员后台添加景点:名称=西湖,地址=杭州 | t_jingdian表新增记录,Name字段为西湖 | 数据库排序规则 /request.setCharacterEncoding() |
| 4 | 普通用户注册:用户名=test,密码=123 | t_user表新增记录,user_type='member' | RegisterServlet逻辑 /user_type默认值 |
| 5 | 普通用户登录后预订线路 | t_order表(若存在)或t_xianlu表更新状态字段 | 订单表结构 /OrderServlet事务控制 |
技巧:每次操作后,用 SQL Server Management Studio 执行SELECT TOP 10 * FROM t_jingdian,直接观察数据是否落库,比看页面更快。
6.2 安全加固:三个最低成本改造,让系统脱离“教学演示”范畴
这个系统最大的技术债务是明文密码 + 无 CSRF 防护 + SQL 注入风险。无需重写,只需三处修改:
1. 密码加盐哈希(5 分钟)
修改RegisterServlet.java的注册逻辑:
// 替换原密码存储 String salt = UUID.randomUUID().toString().replace("-", ""); String hashedPassword = DigestUtils.sha256Hex(password + salt); user.setPassword(hashedPassword); user.setSalt(salt); // 需为 t_user 表新增 salt 字段对应LoginServlet.java登录验证:
String inputHash = DigestUtils.sha256Hex(inputPassword + user.getSalt()); if (inputHash.equals(user.getPassword())) { ... }使用
commons-codec-1.9.jar(已包含在 MyEclipse 8.5 中),DigestUtils是 Apache Commons Codec 工具类。
2. 防 CSRF(3 分钟)
在login.jsp表单中添加隐藏域:
<input type="hidden" name="token" value="<%= session.getAttribute("token") != null ? session.getAttribute("token") : "" %>" />在LoginServlet.doPost()开头验证:
String token = request.getParameter("token"); if (token == null || !token.equals(session.getAttribute("token"))) { request.setAttribute("error", "非法请求,请刷新页面重试"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } session.setAttribute("token", UUID.randomUUID().toString()); // 生成新 token3. 防 SQL 注入(2 分钟)
检查所有Statement用法(如admin/jingdian_del.jsp中的删除逻辑),强制替换为PreparedStatement:
// 错误:Statement stmt = conn.createStatement(); // stmt.executeUpdate("DELETE FROM t_jingdian WHERE Id=" + id); // 正确: String sql = "DELETE FROM t_jingdian WHERE Id = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, Integer.parseInt(id)); pstmt.executeUpdate();从那以后我每次接手老系统,第一件事就是 grep 所有Statement和getParameter(),然后花 15 分钟打上这三个补丁。它不会让系统变成金融级安全,但足以让扫描器不再报高危漏洞,也让甲方爸爸的安全部门点头——毕竟,一个能跑通、有数据、没明显漏洞的 Java Web 系统,在很多中小旅游公司,就是他们真实的生产环境。希望帮到你。
本文还有配套的精品资源,点击获取