“JSP”这个词在JavaWeb领域里已经被讨论过无数遍了,但作为课程设计或者毕业设计的选题,它至今依然是高频首选。这套“JSP企业内部办公系统的设计与实现”,从交付物来看相当完整——程序、源码、数据库、调试部署、开发环境,五件套齐全,基本就是一个可以直接运行、可以直接演示、可以照着改的JavaWeb全流程案例。它解决的问题很明确:用传统JSP+Servlet+MySQL技术栈,实现一套覆盖员工管理、部门管理、公告发布、考勤记录、审批流程等典型场景的企业内部办公系统。如果你是正在做JavaWeb课程设计的学生,或者想从零上手传统JSP项目开发的初学者,这套系统的模块划分、数据库设计、核心功能实现和部署调试思路,都值得完整走一遍。我就围绕这个项目标题,把从设计到落地再到排错的全过程拆开讲清楚,看完你基本能复现一套属于自己的办公系统。
1. 项目整体设计与思路拆解
1.1 这套办公系统到底解决什么问题
做企业内部办公系统,首先要搞清楚一个核心问题:它面向谁?我在实际带课设和毕设时,见到太多人上来就堆功能,结果做了一堆用不上的页面,反而把登录鉴权和基础CRUD做漏了。这套系统的定位非常明确——给企业内部员工提供一个日常办公的信息化入口,承担传统线下流程搬到线上的工作。
从需求方来看分为两类角色:管理员和普通员工。管理员负责维护基础数据,比如员工信息的增删改查、部门结构的调整、公告的发布和下线;普通员工则聚焦在查看公告、查询个人信息、提交请假申请、查看考勤记录这些日常操作上。角色不同,权限边界就不同,系统对外的功能表面也就不同。这也是设计时最需要注意的地方——不能所有登录用户进入系统后看到一模一样的菜单。
再往细了拆,系统功能模块大概覆盖这些方向:
- 系统登录与注销:所有用户必须通过账号密码登录,退出时清空会话。
- 员工信息管理:管理员对员工资料进行维护,包括姓名、性别、部门、职位、联系方式、入职时间等。
- 部门信息管理:维护企业组织架构,部门的新增、修改、删除,以及查询部门下的员工列表。
- 公告管理:管理员发布企业公告,普通员工可以查看最新公告,做到信息的及时触达。
- 考勤记录:记录员工每日打卡或签到信息,按日期查询、按员工筛选。
- 请假审批:员工提交请假申请,管理员或上级审批,审批状态清晰可见。
从课程设计的角度看,这个功能集合很“恰到好处”:既有登录和权限控制,又有常规的增删改查,还有一条简单的流程性功能(请假审批),难度适中,工作量也适中。不管是你直接拿来做参考,还是在此基础上扩展,这套骨架都挺扎实。
1.2 为什么不做SpringBoot,反而坚持JSP这套老组合
很多同学一看到JSP就觉得“过时了”,问我为什么不直接上SpringBoot。说实话,如果你是一个纯自学、面向就业的开发者,SpringBoot确实是当前的主流,这点我不否认。但如果你是做课程设计、毕业设计,或者想搞懂JavaWeb最底层的请求流转机制,JSP+Servlet这套组合依然是很好的学习路径,这也是这个项目最核心的选型考量。
从项目实现角度讲,JSP+Servlet天然就是MVC模式:JSP负责View层——展示数据和接收表单输入;Servlet负责Controller层——接收请求、调用业务逻辑、控制页面跳转;JavaBean和DAO负责Model层——封装数据和访问数据库。三层职责清晰,没有框架的“魔法”,每一步都摆在明面上,这对学习者和答辩来说都是加分项:你能非常清楚地说出“请求是怎么进来、数据是怎么查出来、页面是怎么渲染的”。
从运行环境来说,JSP项目部署非常轻。不需要Maven中央仓库拉一堆依赖,不需要配置复杂的Spring容器,一个Tomcat加上一个MySQL就能跑起来。对于演示环境不固定的课设作品而言,这种低依赖特性意味着迁移成本低。比如你在一台电脑上开发完,换一台电脑部署,只要有JDK、Tomcat、MySQL三大件,几十分钟就能跑通。
当然,这套方案也有明显的短处:JSP页面和Java代码混写容易让页面变得混乱,传统的JDBC操作繁琐容易出错,并发性能一般。但这些短处在课程设计场景下并不致命。我的建议是:用它入门、做课设,把底层原理摸透,之后再学SpringBoot会有一种“原来框架帮我省了这么多事”的顿悟感。反过来,如果一上来就SpringBoot,你反而很难理解那些约定俗成的配置到底在背后做了什么。这个项目坚持JSP,恰恰是给JavaWeb打基础的最优路径。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 开发环境准备与数据库设计
2.1 环境清单:版本匹配才是最大的坑
一个JSP项目能不能跑起来,很多时候不是代码问题,而是环境版本匹配问题。这部分虽然基础,但我在调试部署环节见过太多翻车现场,所以单独拿出来讲。下面是这套系统开发时建议的环境清单,以及为什么是这个版本:
| 组件 | 推荐版本 | 说明 |
|---|---|---|
| JDK | 1.8(8u201+) | JavaWeb课设最稳妥的版本,向下兼容老项目,部署到Tomcat也最省心。高版本如17也能跑,但对老项目可能出现编译兼容问题。 |
| Tomcat | 8.5.x 或 9.0.x | 支持Servlet 3.1/4.0,和JDK 8搭配默契。这个项目如果是用Servlet注解方式(@WebServlet),不需要配置web.xml里的Servlet映射,用8.5就够用了。 |
| MySQL | 5.7 或 8.0 | 二者都可以,但驱动和连接串有区别,后面单独讲。 |
| IDEA | 2020.x 以上 | 社区版或专业版都行,课设项目不需要高级功能。 |
| 数据库工具 | Navicat 或 MySQL Workbench | 用于执行SQL脚本、查看表数据。 |
| JDBC驱动 | mysql-connector-java 5.1.x / 8.0.x | 必须与MySQL版本匹配,这是最容易踩的坑。 |
这里特别提醒两个版本细节。第一,如果你的MySQL是8.0以上,驱动类名要写成com.mysql.cj.jdbc.Driver,连接串要带时区参数,例如jdbc:mysql://localhost:3306/office_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8。老教程里写的com.mysql.jdbc.Driver在Connector/J 8.0里已经不建议使用了,虽然它内部做了兼容处理,但控制台会打出一堆弃用警告。第二,把驱动jar包放进WEB-INF/lib目录下才能被Tomcat运行时加载,放在其他地方编译能过但运行必报ClassNotFoundException,这是个高频坑。
2.2 数据库表结构:一张表一张表地设计明白
数据库设计是一套信息管理系统的地基。地基不稳,后面写代码全是窟窿。这套办公系统建议至少设计以下数据表:用户表、员工表、部门表、公告表、考勤表、请假表。下面逐一拆解设计要点和字段含义。
员工表(employee)是整个系统的核心。字段可以包含:自增主键id、员工编号emp_no(唯一性标记列)、姓名name、性别gender、所属部门id(逻辑外键,关联department表)、职位position、手机号phone、邮箱email、入职时间hire_date、状态status(1在职、0离职)。这里有个设计细节:为什么不直接把员工和用户合成一张表?原因在于职责分离——用户表管登录账号和密码,员工表管业务信息。一个账号对应一个员工,但业务上可能存在“账号已删除但员工历史数据保留”的情况,所以分开设计更灵活。
部门表(department)字段比较简单:id、部门名称dept_name、部门负责人leader、备注description。部门表和员工表是一对多关系:一个部门下有多个员工,一个员工只属于一个部门。在查询员工列表时,一般会通过JOIN联查显示部门名称,而不是直接显示部门id。
用户表(user)字段包括:id、员工id(关联employee表)、登录账号username、密码password、角色role(admin代表管理员,user代表普通员工)。密码字段建议保存MD5加密后的密文,虽然这个项目很简单,但直接明文存库在答辩时可能会被问住。用MD5工具类加密存储并不是什么复杂操作,却能让代码的专业性上一个台阶。
公告表(announcement)字段:id、标题title、内容content、发布人publisher、发布时间create_time。考勤表(attendance)字段:id、员工id、考勤日期work_date、上班时间check_in、下班时间check_out、状态status(正常、迟到、早退、缺勤)。请假表(leave_request)字段:id、员工id、开始时间start_time、结束时间end_time、请假事由reason、审批状态status(待审批、已通过、已驳回)、审批意见approve_comment。
这些表之间的外键关系在创建时可以加上,也可以不加实际约束,只用逻辑关联。我的建议是:课设阶段用逻辑外键反而更省心——不用纠结JPA/MyBatis里级联操作的麻烦,也避免删除父表时被外键约束拦住。表关联通过SQL的JOIN实现,逻辑清晰,演示也很直观。
2.3 JDBC连接与数据访问层封装思路
数据库连接是这套系统所有功能的血管。最基础的做法是通过DriverManager.getConnection()每次获取连接。但在实际项目中,这样做很浪费资源,而且并发量稍高就会出现连接超时。建议用连接池来管理连接,常见的做法是引入dbcp或c3p0。如果你不想引入太多额外jar包,写一个简单的数据库连接工具类也行,但连接池会让代码“看起来”更专业。
连接池配置核心参数一般在db.properties文件里维护:jdbc驱动、连接URL、用户名、密码、初始连接数和最大连接数。一个常见配置如下:
properties复制jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/office_db?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8
jdbc.username=root
jdbc.password=123456
jdbc.initialSize=5
jdbc.maxActive=20
数据访问层(DAO层)的设计思路是“一个实体对应一个DAO接口和实现类”。比如封装一个BaseDAO类,里面提供通用的增删改执行方法和查询方法,子类继承后再写具体业务方法。比如员工DAO里有insert(Employee emp)、update(Employee emp)、deleteById(int id)、findById(int id)、findAll(String keyword)等。这样做的好处是:JSP和Servlet层不直接碰SQL,所有数据操作都收敛到DAO层,后期改表或换数据库(比如从MySQL换到Oracle)只需要改DAO实现,页面层完全不受影响。
在这里我强烈建议你用PreparedStatement而不是Statement。前者预编译SQL,能防止SQL注入攻击,而且参数传递用?占位符,代码可读性也更好。答辩时如果老师问“你的系统怎么防SQL注入”,你回答PreparedStatement预编译占位符,这一分就稳了。
3. 核心功能实现与实操解析
3.1 登录、会话保持与权限控制的实现细节
登录模块是整个系统的门面,也是权限控制的第一道关卡。实现思路很标准:用户在login.jsp输入账号密码,表单POST提交到LoginServlet,Servlet里先调用UserDAO查询用户是否存在,再比对密码(如果用MD5加密就比对密文),比对成功就把用户信息放进HttpSession,重定向到主页面index.jsp;失败则回到登录页并携带错误提示。
这里有个容易被忽略的细节:request.setCharacterEncoding("UTF-8")必须放在读取任何请求参数之前。如果放在getParameter()之后才写,中文用户名或密码必然会乱码。建议在Servlet的doPost方法第一行就执行编码设置:
java复制protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String username = request.getParameter("username");
String password = MD5Util.md5(request.getParameter("password"));
// 查询用户并判断
}
权限控制方面,只靠登录时的判断是不够的。用户完全可以绕过登录页,直接访问后台页面的URL。所以必须加一个Filter过滤器统一拦截。核心逻辑:创建一个LoginFilter实现javax.servlet.Filter接口,在doFilter方法里判断——如果当前Session里没有用户信息,且请求路径不是登录相关的资源(login.jsp、LoginServlet、静态资源),就重定向到登录页;否则放行。伪代码大致是:
java复制public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession();
if (session.getAttribute("user") == null
&& !request.getRequestURI().contains("login")) {
response.sendRedirect(request.getContextPath() + "/login.jsp");
return;
}
chain.doFilter(req, resp);
}
在web.xml里给这个Filter配置url-pattern为/*,并配置初始化参数放行登录页和静态资源。这一步做完,整个系统的访问控制就闭环了。至于管理员和普通员工的菜单差异,建议在JSP里用<c:if>标签按角色判断,比如只有role = admin时才渲染“员工管理”“部门管理”这两个入口,普通用户只看得到公告、考勤和自己的请假记录。
3.2 员工管理的增删改查与分页查询
员工管理是本系统最典型的CRUD模块,拿它来完整走一遍JSP+Servlet+DAO的请求链路,其他模块照葫芦画瓢就能写出来。
显示列表的流程是:用户在左侧菜单点击“员工管理”,浏览器发起/employee/list请求 → EmployeeServlet的doGet方法收到请求 → 调用EmployeeDAO.getAll()从数据库查出所有员工列表 → 把列表放进request.setAttribute("empList", list) → 请求转发到employee/list.jsp → JSP页面用<c:forEach>遍历列表并渲染成HTML表格。这里的转发(request.getRequestDispatcher().forward())和重定向(response.sendRedirect())要区分清楚:转发是同一个请求内部跳转,能携带request域的数据;重定向是浏览器重新发一次新请求,request数据会丢失。所以在查询列表场景必须用转发。
分页查询是答辩的高频提问点,也是很多同学写不好的一块。分页的核心就两件事:算好偏移量、统计总页数。前端页面传递currentPage参数给Servlet,Servlet拿到后查询总记录数totalCount,然后根据每页显示条数pageSize计算总页数,最后用LIMIT子句查询当前页数据。取偏移量的公式:
java复制int totalPage = (totalCount % pageSize == 0) ? (totalCount / pageSize) : (totalCount / pageSize + 1);
int offset = (currentPage - 1) * pageSize;
String sql = "SELECT * FROM employee LIMIT ?, ?";
这里LIMIT的第一个参数是起始位置(从0开始),第二个参数是每页条数。如果当前页为3、每页显示10条,那么offset就是20,意思是跳过前20条,从第21条开始取10条。很多同学出错就出在offset算成了currentPage * pageSize,那第二页就已经越界了。页面底部的“上一页/下一页”按钮,只需要给currentPage做加减法,再带上原来的查询条件(比如姓名关键词)重新请求当前Servlet即可。
新增和编辑功能可以复用同一个JSP表单页面:新增时所有字段为空,表单提交到/employee/add;编辑时先把当前员工ID传到Servlet,Servlet查出该员工完整信息回填到表单,然后表单提交到/employee/update。两个Servlet方法内部再根据请求参数区分。修改操作完成后,使用重定向到列表页,避免用户刷新浏览器时重复提交表单。
删除操作建议用GET请求配合确认弹窗,或者用POST配合JavaScript弹窗确认。页面里每行数据放一个“删除”按钮,点击后先confirm("确定删除?"),确认后才跳转到删除Servlet。删除完成后同样重定向回列表页面。
3.3 公告、考勤与请假审批模块的实现思路
这三个模块在实际项目中并不复杂,但各有各的注意点,我挨个说一下。
公告管理本质上就是“标题+内容+时间”的CRUD。管理员新增公告时,发布人从当前Session里的用户信息自动带出,发布时间用数据库的NOW()函数或者Java端的当前时间。普通员工的公告列表页面,建议按发布时间倒序排列,让最新公告出现在最上方。为了更像真实系统,可以加一个“置顶”字段,但这属于锦上添花,基础版本不必加。
考勤模块的核心是“按时间维度查数据”。页面提供日期选择器和员工姓名关键词,查询时WHERE条件动态拼接。这里有一个细节:动态SQL拼接时建议用一个StringBuilder先拼接基本SQL,再根据条件是否为空决定是否追加AND子句。如果参数为空还强行拼接条件,SQL就会出错。比如用户只选了日期没填姓名,那条件就只拼日期;如果一个都没选,那就默认查当月所有数据。在Service层做这种判断,DAO层接收处理好的参数。
请假审批是系统里最接近“流程”的功能,也是能体现业务逻辑的地方。员工提交请假申请时,提交页面包含三个必填项:开始时间、结束时间、请假事由。Servlet收到请求后从Session取出当前员工ID,写入leave_request表,初始状态为“待审批”。管理员端的审批页面展示所有待审批记录,管理员可以点“通过”或“驳回”。审批通过时更新状态为“已通过”,同时写入审批意见;驳回时状态为“已驳回”。员工端查看自己的请假记录时,通过状态字段用不同颜色的标签展示,这种体验上的小细节很加分。
4. 调试部署全流程实录
4.1 本地开发环境搭建与IDEA调试技巧
拿到这套项目的源码之后,第一步不是急着改代码,而是先把环境搭通、让项目在本地跑起来。我以IDEA为例,完整走一遍流程。
先确认JDK和Maven环境。如果项目是传统结构(没有pom.xml,直接是src目录+WEB-INF),那你需要新建一个普通Java项目,然后将源码复制到src目录下,再把web目录下内容放到一个Web根目录中。如果项目带pom.xml,那就直接用IDEA的Open打开项目,等待Maven下载依赖即可。这个工作需要判断一下项目结构,不要盲目导入。
接下来配置Tomcat。在IDEA中点击Run -> Edit Configurations -> 左上角+号 -> Tomcat Server -> Local,在Deployment页签添加Artifact,Application context建议设为/office。然后确认Tomcat的端口——如果你本机8080端口被占用,在Server页签或Tomcat的conf/server.xml里改成8081或8090,保存后重新运行。启动后浏览器访问http://localhost:8080/office/,如果能看到登录页面,说明环境搭建成功。
调试时强烈建议开启断点模式。如果你要排查登录功能为什么失败,可以在LoginServlet的doPost方法第一行打一个断点(点击行号右侧空白处,红圈出现即成功),然后以Debug模式运行Tomcat。在前端页面提交登录表单,程序会在断点处暂停,这时候你可以在IDEA的Variables面板里看到所有局部变量,比如username、password的值,逐步按F8往下执行,观察SQL查询结果。对初学者来说,这种调试方式比到处打System.out.println高效太多。
这里必须提一个经典坑:IDEA控制台输出乱码。如果你运行项目时看到淇℃伮这样的乱码,多半是控制台编码和Tomcat日志编码不一致。解决办法:在Help -> Edit Custom VM Options中加上一行-Dfile.encoding=UTF-8,重启IDEA;同时在Tomcat配置的VM options里也加上-Dfile.encoding=UTF-8。改完之后控制台中文就能正常显示了。
4.2 war包打包与Tomcat独立部署
本地跑通只是第一步,课程设计和毕设通常需要把项目部署到一台独立环境里做演示,这里讲清楚war包方式。
首先要明确war包和exploded目录部署的区别。IDEA里默认的Artifact类型有两种:Web Application: Exploded(目录形式)和Web Application: Archive(war包形式)。你在本地调试时用Exploded更方便,改动代码刷新即生效;但要做独立部署,就选Archive构建出一个.war文件。操作路径:File -> Project Structure -> Artifacts -> +号 -> Web Application: Archive -> For '项目名:war exploded',然后Build -> Build Artifacts -> Build,构建好的war包通常在out/artifacts/目录下。
拿到war包后,部署就变成了一件非常简单的事情:把war文件复制到Tomcat的webapps目录下,然后启动Tomcat(bin/startup.sh或bin/startup.bat)。Tomcat启动时会自动把war包解压成一个同名目录,并部署该应用。比如你复制的是office.war,启动后浏览器访问http://localhost:8080/office/就能看到系统登录页。如果404了,多半是访问路径写错——注意应用上下文路径是war包的文件名,不是项目内部名称。
独立部署时最需要提前确认的是数据库配置。如果你在本地开发用的数据库账号密码是root/123456,部署到服务器后就必须同步修改db.properties或DBUtil类里的连接参数。常见的错误是本地跑得好好的,换台机器就报数据库连接失败,十有八九是连接配置没有改或者MySQL服务没有启动。
4.3 答辩演示前必须自查的部署要点
我见过不少人在答辩现场翻车,很大原因不是项目本身有问题,而是演示准备不充分。这里按经验列几个必查项,在你交付前一定要逐条核对。
- 数据库脚本是否已经导入?确认所有数据表存在,并且有基础测试数据。不要到了演示现场才发现公告表是空的,页面展示效果大打折扣。
- 初始账号密码是否还记得?在演示时,建议准备一个管理员账号和一个普通员工账号,提前登录好,或者把密码抄在纸上。现场临时输入密码还要翻代码,很尴尬。
- 端口是否被占用?如果答辩用的电脑上已经跑着其他Tomcat实例,端口冲突会直接导致启动失败。提前用
netstat -ano | findstr 8080检查一下。 - 依赖jar包是否齐全?重点检查
WEB-INF/lib下是否有mysql驱动、jstl、standard等必要jar包。经常有同学本地IDEA配置了依赖就能跑,但把项目拷到别的电脑就报错,原因就是jar包没一起打包。 - 中文乱码是否处理干净?最后用独立部署的war包方式完整跑一遍增删改查流程,确保所有中文展示正常。这一步能过滤掉90%的现场事故。
5. 常见问题与排查技巧实测
5.1 启动期报错:环境与依赖问题速查
这套系统绝大多数启动报错都集中在环境、依赖和配置三类问题上。我把高频问题整理成一张速查表,建议收藏:
| 报错信息 | 根本原因 | 解决方案 |
|---|---|---|
Port 8080 required by Tomcat ... is already in use |
端口被占用 | 修改Tomcat的server.xml端口,或者关掉占用端口的进程 |
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver |
缺少mysql驱动jar包 | 把mysql-connector-java的jar包放入WEB-INF/lib目录 |
Cannot load driver class: com.mysql.cj.jdbc.Driver |
Connector/J版本与驱动类名不一致 | 使用8.0驱动时把驱动类改为com.mysql.cj.jdbc.Driver |
java.lang.NoClassDefFoundError: javax/servlet/jsp/tagext/TagLibraryValidator |
JSTL依赖缺失 | 导入jstl.jar和standard.jar |
The superclass "javax.servlet.http.HttpServlet" was not found on the Java Build Path |
项目没关联Tomcat运行时 | 在IDEA的Project Structure中给项目添加Tomcat Library |
| HTTP 404且页面空白 | 访问路径或部署上下文不对 | 确认Application context与访问URL是否一致 |
这三张表格基本覆盖了启动阶段80%的报错。记住一个原则:报错信息就是最好的排查线索,把堆栈里第一行Caused by看明白,问题往往就解决了一半。不要在控制台看到一长串红字就慌了,从下往上翻,找到第一个Caused by才是根因。
5.2 连接数据库失败:一套完整的判断路径
数据库连接失败是所有JavaWeb项目里最折磨人的问题。失败时通常会看到一个类似下面这样的大串英文报错。这里不写具体报错,直接给你一套判断路径,按顺序排查即可。
第一步,确认MySQL服务是否在运行。Windows下可以打开任务管理器看服务列表,或者运行net start | findstr mysql。很多同学装完MySQL后把服务手动停了,代码写对了也连不上。
第二步,确认账号密码和授权。直接在Navicat或其他客户端工具里测试连接。如果客户端能连上而Java程序连不上,那问题不在账号密码,而在下一步。如果客户端也连不上,要么密码不对,要么账号没开远程访问权限,要么端口被防火墙拦截。
第三步,检查连接URL。MySQL 5.7和8.0的连接串有差异,8.0必须带serverTimezone参数。另外检查是否加了useUnicode=true&characterEncoding=utf8,这个参数直接影响中文存取。
第四步,检查jar包版本。这一步是“客户端连接正常但Java连不上”时最可能的原因。Connector/J 8.0不支持老版本MySQL协议的情况很少,但Connector/J 5.1.x 连接MySQL 8.0时经常报Communications link failure。建议MySQL 5.7配5.1.47驱动,MySQL 8.0配8.0.x驱动,不要混搭。
第五步,检查防火墙和host配置。如果数据库不在本机,需要确认目标机器的3306端口对外开放,并且bind-address没有限制成127.0.0.1。
5.3 页面乱码与404:字符编码和路径问题的处理
中文乱码问题在JSP老项目里尤其突出,因为JSP页面编译、Servlet请求解析、数据库存储三处都有编码环节,任何一处不一致就会出现中文变成问号或乱码。完整修复需要三层配合。
第一层,JSP页面本身。每个JSP文件头部必须声明<%@ page contentType="text/html;charset=UTF-8" pageEncoding="UTF-8" %>。第二层,Servlet请求和响应。请求用request.setCharacterEncoding("UTF-8"),响应用response.setContentType("text/html;charset=UTF-8")。第三层,数据库连接URL带characterEncoding=utf8参数,并且数据库表和字段的字符集设置为utf8mb4(推荐)或utf8。只要这三处全部处理到位,中文乱码基本可以根治。
404问题则要抓住两个检查点。第一,检查请求URL是否和web.xml中的<servlet-mapping>或者Servlet类上的@WebServlet("/xxx")注解完全匹配,多一个斜杠或少一个斜杠都会404。第二,检查部署上下文路径。IDEA里Application context设置为/office,那就必须通过http://localhost:8080/office/xxx访问;如果你手误把context设为/,访问路径就完全不同。切换部署环境后404,优先看这两点。
最后说一个很容易被忽略的细节:JSP文件里引用CSS、JS、图片资源时,路径要写成${pageContext.request.contextPath}拼出来的绝对路径,而不是相对路径。如果用相对路径,在有/employee/list这种多级URL的页面上,资源路径会错乱,页面样式丢失,看着就像CSS没生效。这个细节在我接手的项目里几乎有一半以上都踩过。
我个人在实际操作里最大的感触是:JSP项目虽然老,但正因为老,它每一步都能用肉眼观察到,很适合打通“需求→设计→编码→部署→排错”的完整链路。做完这一个系统,你对JavaWeb的理解会有一个质的提升,后面再学框架时,很多概念都能对上号。如果你打算在这个基础上继续扩展,可以考虑的方向很多:把表现层换成SpringBoot+Thymeleaf,把JDBC换成MyBatis,或者把数据存储升级成Redis缓存热点数据,都是不错的方向。但不管怎么改,这个项目的业务骨架和数据库设计都还继续有效。
