ARTICLE DETAIL

资讯详情

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

JavaWeb实习管理系统开发教程:Servlet+JSP+MySQL从设计到部署

JavaWeb实习管理系统开发教程:Servlet+JSP+MySQL从设计到部署 简介面向计算机及相关专业专科、本科毕业生的原创毕业论文选题为基于JavaWeb的实习管理系统设计与实现。论文完整覆盖实习管理的需求分析、MVC系统架构设计、数据库表结构规划、前后端界面设计以及功能编码、系统测试与部署维护等环节可作为毕业设计或论文撰写的直接参考。包体为1份docx文档压缩后约32KB打开即可查看正文和目录结构编辑修改方便。据了解文档已通过原创性与重复率检查内容篇幅达万字完整性较好。目前已有133人学习下载。这份论文的价值在于它按软件工程流程讲清了实习管理系统从零到落地的全过程便于读者理解JavaWeb项目开发中Servlet、JSP、JDBC及Spring、MyBatis等技术的实际搭配方式同时提供实习生信息管理、单位管理、计划安排、评价反馈等模块的设计思路和登录权限控制要点能为类似管理系统的设计与实现提供可复用的框架与文档模板。1. 一款基于Javaweb的实习管理系统能解决什么从Excel轰炸到在线流转又到实习季信息学院每年有 400 多名学生要分散到各地企业教务老师手里攥着一份共享 Excel登记岗位、回收周报、核对成绩一到月底就因为漏登和版本冲突翻车。把这条流程搬到线上就是一套基于 Javaweb 的实习管理系统学生在线提交申请和周报指导教师在后台审核评分管理员统一管理岗位并导出成绩。技术栈选用 Servlet JSP MySQL 这套 Javaweb 最常见的老三样覆盖登录鉴权、角色分流、文件上传、分页查询等完整 Web 开发链路非常适合做课程设计、毕业设计或者想拿完整案例练手的人。下面按选型、建表、编码、IDEA 部署、排错五个环节展开每一步都给出能直接复现的配置和代码。2. 系统设计与技术选型为什么这套组合值得复刻2.1 ServletJSP 还是 SpringMVC毕设场景下的选型逻辑实习管理系统连上管理员一共三类角色核心动作就是发布岗位、提交申请、写周报、打成绩。这类系统的并发量通常只有几十个人同时在线数据库规模也就几万行用 Spring Boot MyBatis Plus 当然能做但对课程设计和本科毕设来说会让答辩现场解释一下项目里最难的点变成一场灾难。Spring 容器在做自动装配时把你和底层细节隔开了背完面试题都说不清一个请求到底是怎么从 Tomcat 进到 Controller 的。而 Servlet JSP 这套东西过滤器和 Servlet 的注册路径全部自己控制请求在哪一层、做了哪些操作在答辩现场指着代码就能讲透。网上那些 javaweb 项目完整案例 mysql 老项目反而能帮你理解 HTTP、Session、Cookie 这些 Web 基础黑马 javaweb 笔记数据里也是先讲透 Servlet 再讲框架这个路径和下面要写的一致。我还特意避开了直接用 SSM 框架SSM 的好处是贴近企业开发坏处是配置文件太多spring-mvc.xml、spring-dao.xml、mybatis-config.xml 三个文件互相引用一个 namespace 写错就启动失败排查成本比业务代码本身还高。用 Servlet 做控制层后我把数据源、事务、过滤器集中到少量配置里整个项目在 IDEA 里启动一次就能跑通。最终选型如下Servlet 3.1 JSP 2.3 JDBC 原生封装 MySQL 5.7 Druid 连接池前端用 JSP Bootstrap 4 做后台布局不做前后端分离。原因有两点第一这类管理系统页面多但结构简单服务端渲染能少建一堆接口和请求处理代码第二课程设计和毕设评审看重功能完整度和能否现场演示前后端分离会多出 node 构建步骤无端增加翻车概率。这个选择也留了升级余地Servlet 类里的逻辑和 SpringMVC 的 Controller 方法几乎一一对应后面想换框架改动集中在方法签名和返回值处理上业务代码基本能平移。2.2 六张核心表的字段设计与表关系数据模型是整个系统最不能省的一步。网上能搜到的实习管理类案例经常只给用户表和岗位表周报和成绩全靠一张表硬塞后期想按周次查都不好写 SQL。我把数据拆成六张表用户、岗位、实习申请、周报、成绩、公告字段以够用为准不做过度设计。用户表承担三种角色的登录信息密码字段要留足长度因为后面要做加盐哈希而不是存明文。岗位表记录企业发布的实习职位用 status 字段控制发布状态。申请表把学生和岗位关联起来同时背负整个审核流程的状态流转。周报表挂在申请下面而不是直接挂学生因为一个学生可能申请多个岗位直接挂 student_id 会分不清这篇周报属于哪次实习。成绩表与申请表是一对一关系教师审核通过后顺手初始化一条空成绩记录后续再填分数。CREATE TABLE t_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(32) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(128) NOT NULL COMMENT 加盐哈希后的密码, real_name VARCHAR(32) NOT NULL, role VARCHAR(16) NOT NULL COMMENT student/teacher/admin, student_no VARCHAR(20) COMMENT 学号或工号, college VARCHAR(64), class_name VARCHAR(64), phone VARCHAR(20), create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_internship_post ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL COMMENT 岗位标题, company VARCHAR(128) NOT NULL, position VARCHAR(64) NOT NULL, location VARCHAR(64), headcount INT DEFAULT 1, requirement TEXT, status TINYINT DEFAULT 0 COMMENT 0草稿 1发布 2结束, publisher_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_apply ( id INT PRIMARY KEY AUTO_INCREMENT, student_id INT NOT NULL, post_id INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0待审核 1通过 -1拒绝, apply_reason VARCHAR(500), review_comment VARCHAR(500), review_time DATETIME, KEY idx_student (student_id), KEY idx_post (post_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_weekly_report ( id INT PRIMARY KEY AUTO_INCREMENT, apply_id INT NOT NULL, week_no INT NOT NULL COMMENT 第几周, content TEXT, attachment_url VARCHAR(255), submit_time DATETIME DEFAULT CURRENT_TIMESTAMP, teacher_reply VARCHAR(500), KEY idx_apply (apply_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_score ( id INT PRIMARY KEY AUTO_INCREMENT, apply_id INT NOT NULL UNIQUE, score DECIMAL(5,2), comment VARCHAR(500), evaluator_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE t_announcement ( id INT PRIMARY KEY AUTO_INCREMENT, title VARCHAR(128) NOT NULL, content TEXT, publisher_id INT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;编码统一用 utf8mb4因为 MySQL 5.7 默认 utf8 是 utf8mb3存不了生僻字和某些特殊符号学生姓名里出现冷僻字时整个插入会报错。外键我没有建物理约束只建了普通索引。这是很多实训项目的常规做法物理外键在数据量上来后会拖慢写入而且删除用户时会被外键卡住更合适的方式是在 service 层做逻辑校验比如插入申请前先查学生和岗位是否存在。毕业论文里写物理外键也能解释清楚但实际演示和后续扩展都不如索引加逻辑校验灵活。2.3 分层约定controller/service/dao 的职责边界包结构按 controller、service、dao、model、filter、util 拆开这是老牌 javaweb 项目完整案例里最常见的组织方式也符合后面讲到的所有代码的位置约定。src/main/java/com/edu/internship/ ├── controller/ # Servlet接收请求、返回视图 │ ├── LoginServlet.java │ ├── PostServlet.java │ ├── ApplyServlet.java │ └── WeeklyReportServlet.java ├── service/ # 业务校验、事务控制 │ ├── UserService.java │ ├── PostService.java │ └── ApplyService.java ├── dao/ # JDBC 操作一个方法对应一条 SQL │ ├── UserDao.java │ ├── PostDao.java │ ├── ApplyDao.java │ └── WeeklyReportDao.java ├── model/ # 实体类和表字段一一对应 ├── filter/ # 角色权限过滤器 └── util/ # DBUtil、EncryptUtil、FileUploadUtil为什么把 service 拆出来而不是让 Servlet 直接写 JDBC这个系统表只有六张但很多操作不止查一张表。比如教师审核一份申请时既要更新 t_apply 的状态又要在 t_score 里插入一条成绩记录如果这两步放在 Servlet 里一旦中间抛异常就会出现申请状态改了但成绩没生成的情况。把两步放进 ApplyService 的 auditApply 方法里用事务包住就能避免数据不一致。请求的流转顺序是JSP 表单提交触发对应 ServletServlet 从 request 里拿参数并转成实体对象调用 Service 的业务方法Service 内部完成校验、事务、组合多张表的操作把结果放回 request 或 session再 forward 或 redirect 到 JSP。这个链路跟 SpringMVC 的 Controller-Service-Mapper 结构几乎一样区别只是少了依赖注入。所以后续做扩展时把 Servlet 里的手工 new 对象换成 Spring 的注入即可改动范围可控。3. 核心功能实现从登录到周报上传的完整链路3.1 登录与角色权限用 Filter 做三个角色的访问分流登录是所有页面的入口。翻那些 javaweb 项目完整案例 mysql 会发现很多登录逻辑在 Servlet 的 doPost 里狂写一二十个 if把验证码校验、用户查询、角色跳转全糊在一起。更常规的做法是让 LoginServlet 只做两件事校验验证码然后查用户并写入 Session角色跳转放在前端页面通过 Session 里的 loginUser 字段区分菜单。先看 LoginServlet 的核心代码WebServlet(/login) public class LoginServlet extends HttpServlet { private UserService userService new UserService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); String username req.getParameter(username); String password req.getParameter(password); String verifyCode req.getParameter(verifyCode); String codeInSession (String) req.getSession().getAttribute(code); // 验证码不区分大小写先于用户查询校验减少无效数据库请求 if (verifyCode null || !verifyCode.equalsIgnoreCase(codeInSession)) { req.setAttribute(msg, 验证码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } User user userService.login(username, password); if (user null) { req.setAttribute(msg, 用户名或密码错误); req.getRequestDispatcher(/login.jsp).forward(req, resp); return; } HttpSession session req.getSession(); session.setAttribute(loginUser, user); // 按角色跳到不同首页前端再根据 loginUser.role 细化左侧菜单 String role user.getRole(); if (student.equals(role)) { resp.sendRedirect(req.getContextPath() /student/index.jsp); } else if (teacher.equals(role)) { resp.sendRedirect(req.getContextPath() /teacher/index.jsp); } else { resp.sendRedirect(req.getContextPath() /admin/index.jsp); } } }UserService 里的 login 方法负责把明文密码做加盐哈希再和数据库里存的哈希串比对。真正的密码摘要逻辑放在后面的工具类里LoginServlet 本身不写 JDBC只负责把 request 参数翻译成调用。这里注意 req.setCharacterEncoding(UTF-8) 必须放在读取任何参数之前否则中文用户名会直接乱码后面排查时会展开讲。权限控制的主载荷放在 Filter 上。三个角色对应三套 URL 前缀/student/、/teacher/、/admin/*每个前缀挂一个 Filter。用 Filter 而不是在 JSP 里判断角色是因为 JSP 判断总能被绕过——直接输入不带校验的 .jsp 路径就能刷开页面。下面贴学生角色的 Filter其他两个改一下角色字符串即可WebFilter(/student/*) public class StudentFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; User user (User) req.getSession().getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } if (!student.equals(user.getRole())) { resp.setContentType(text/html;charsetUTF-8); resp.getWriter().write(h3403 无权访问学生功能/h3); return; } chain.doFilter(request, response); } }每个角色的 Filter 只有两个判断有没有登录、角色是否匹配。两个条件都不满足就中断请求满足则放行。这里要特别小心 Filter 的 URL 匹配范围WebFilter(/student/*) 只拦以 /student/ 开头的路径不会误伤登录接口和静态资源这也避免了后面避坑章里登录后反复跳回登录页的问题。3.2 岗位发布与申请状态字段驱动的一对多流程岗位发布和申请是系统的业务主轴。管理员把岗位信息写入 t_internship_poststatus 置为 1 表示发布学生查看已发布岗位提交 t_apply教师在待审核列表里通过或拒绝。整个过程用 status 这一个字段推动不设计复杂状态机。学生发起申请的逻辑如下WebServlet(/apply/add) public class ApplyAddServlet extends HttpServlet { private ApplyService applyService new ApplyService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); if (user null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } int postId Integer.parseInt(req.getParameter(postId)); String reason req.getParameter(applyReason); try { applyService.addApply(user.getId(), postId, reason); resp.sendRedirect(req.getContextPath() /student/applyList.jsp); } catch (BusinessException e) { req.setAttribute(msg, e.getMessage()); req.getRequestDispatcher(/student/apply.jsp).forward(req, resp); } } }addApply 的业务逻辑有三步先查 t_apply 里是否存在同一学生同一岗位的记录存在就抛 BusinessException 提示不能重复申请再检查岗位是否仍处于发布状态防止管理员下线岗位后学生继续提交最后插入记录status 默认 0。把查询、校验、插入放在同一个 service 方法里用事务包住避免并发请求下产生重复申请。教师审核是反向操作。审核页面列出 status0 的记录教师点通过或拒绝时Servlet 调 ApplyService.auditApply(applyId, result, comment)。这个方法在同一个事务里更新申请表状态并在通过时往 t_score 插入一条空成绩最后把审核意见写回 t_apply。这里有个容易踩的细节审核意见不能只放在 t_score 里因为 t_apply 里的 review_comment 要用于列表页直接展示两张表各自存一份避免审核列表要连表查成绩表才能看到意见。3.3 周报文件上传MultipartConfig 与磁盘路径存储实习周报通常包含 Word 或 PDF 附件上传功能用 Servlet 3.0 的 MultipartConfig 注解就能实现不需要引入 Commons-FileUpload 这种老库。但存储路径要提前想清楚直接把文件写到项目部署目录下次重新部署 WAR 包会被清空我一般会在 Tomcat 外部配置一个物理路径比如 /data/internship/uploads再用虚拟目录映射到 /uploads 供页面访问。WebServlet(/weekly/add) MultipartConfig( maxFileSize 10 * 1024 * 1024, maxRequestSize 20 * 1024 * 1024, fileSizeThreshold 1024 * 1024 ) public class WeeklyReportServlet extends HttpServlet { private WeeklyReportService reportService new WeeklyReportService(); Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { req.setCharacterEncoding(UTF-8); Part filePart req.getPart(attachment); String weekNo req.getParameter(weekNo); String content req.getParameter(content); HttpSession session req.getSession(); User user (User) session.getAttribute(loginUser); // 用 getSubmittedFileName 拿到的可能是完整客户端路径必须重新取文件名 String originName ; if (filePart ! null filePart.getSize() 0) { String submitted filePart.getSubmittedFileName(); originName Paths.get(submitted).getFileName().toString(); } String storeName System.currentTimeMillis() _ originName; String uploadPath /data/internship/uploads; File dir new File(uploadPath); if (!dir.exists()) { dir.mkdirs(); } if (filePart ! null filePart.getSize() 0) { filePart.write(uploadPath File.separator storeName); } reportService.addWeeklyReport(user.getId(), Integer.parseInt(weekNo), content, storeName); resp.sendRedirect(req.getContextPath() /student/weeklyList.jsp); } }从 Part 取上传文件名时用 Paths.get(submitted).getFileName().toString() 重新取一遍是为了处理 Windows 浏览器可能提交的 C:\fakepath\xxx.docx 这类值直接拼接会导致写入路径变成 C:\fakepath\ 加在你的 uploadPath 里文件写到错误位置打开自然损坏。存储文件名加 System.currentTimeMillis() 前缀是防重名的常规做法数据库里存的是 storeName 而不是全路径因为前端显示时通过虚拟目录访问物理路径完全对外屏蔽。maxFileSize 是单个文件上限超过后会抛 IllegalStateException要在外层统一 catch 后提示学生压缩文件而不是让 Tomcat 直接弹 500 页面。4. 在 IDEA 里把 JavaWeb 项目跑起来配置与部署实操4.1 Maven 项目初始化pom.xml 里最容易被坑的依赖新建一个 Maven Web 工程后第一步是把 pom.xml 改成一套与 Tomcat 9、MySQL 5.7 匹配的依赖组合。很多照着老教程配置的人会卡在 Servlet API 版本上要么启动时 NoClassDefFoundError要么打包后在别的 Tomcat 里跑不起来。这里直接给出当前可用的组合project xmlnshttp://maven.apache.org/POM/4.0.0 modelVersion4.0.0/modelVersion groupIdcom.edu/groupId artifactIdinternship-system/artifactId version1.0/version packagingwar/packaging properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target project.build.sourceEncodingUTF-8/project.build.sourceEncoding /properties dependencies !-- Servlet APITomcat 9 对应 4.0.x -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency !-- JSP API -- dependency groupIdjavax.servlet.jsp/groupId artifactIdjavax.servlet.jsp-api/artifactId version2.3.3/version scopeprovided/scope /dependency !-- JSTL 标签库 -- dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- MySQL 驱动注意版本和数据库版本匹配 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version5.1.49/version /dependency !-- Druid 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.8/version /dependency /dependencies build finalNameinternship-system/finalName /build /projectjavax.servlet-api 的 scope 必须设成 provided意思是打包 WAR 时不带这份依赖因为 Tomcat 自己提供 Servlet 实现。如果漏掉这个 scopeWAR 里会塞一份 Servlet API 类和 Tomcat 的类冲突轻则方法签名不一致报错重则连启动都过不去。MySQL 驱动版本要看数据库版本本机装 MySQL 5.7 配 5.1.49装 MySQL 8.0 就要换 8.0.29 并把驱动类改成 com.mysql.cj.jdbc.Driver。这里案例按 5.7 写后面初始化数据库时别弄混。4.2 Tomcat 配置与 Artifacts浏览一直 404 的修复路径在 IDEA 里运行老式 Web 项目最让人心里没底的是 Artifacts 配置。很多人第一次接触时按教程点完 Tomcat 后Deployment 页签里什么都没有启动也不报错但浏览器一直 404。原因是 IDEA 不知道把哪个构建产物部署给 Tomcat。操作路径是Run 菜单选 Edit Configurations左上角加号选 Tomcat Server - Local在 Server 页填 Tomcat 安装目录并确认端口切到 Deployment 页签点加号选 Artifact选择 internship-system:war exploded。如果 Artifact 列表为空需要先打开 File - Project Structure - Artifacts点加号选 Web Application:Archive把项目模块关联进去再回到 Deployment 选择。最后把 Application context 设为 /internship浏览器访问 http://localhost:8080/internship/login.jsp 就能看到页面。war exploded 是未压缩的调试模式改完 JSP 刷新浏览器就能生效war 是压缩包模式每次修改代码都要重新构建再部署调试期没必要用。Application context 这个路径决定所有项目内跳转的前缀如果改成 /原来带 /internship 前缀的绝对路径全部 404。所以 Servlet 和 JSP 里统一用 req.getContextPath() 拼接路径写死 /internship 的话换前缀时全局翻车。4.3 数据库初始化与连接池MySQL 版本不对引发的连锁问题IDEA 里能启动 Tomcat 不代表数据层能连上。初始化数据库最容易翻车的是连接串和驱动版本不匹配其次是连接池配置文件写错但无提示。先贴 Druid 配置文件和连接工具类driverClassNamecom.mysql.jdbc.Driver urljdbc:mysql://localhost:3306/internship_db?useUnicodetruecharacterEncodingUTF-8useSSLfalseserverTimezoneAsia/Shanghai usernameroot password123456 initialSize3 maxActive10 minIdle1 maxWait5000public class DBUtil { private static DruidDataSource dataSource; static { try (InputStream in DBUtil.class.getClassLoader() .getResourceAsStream(druid.properties)) { Properties props new Properties(); props.load(in); dataSource (DruidDataSource) DruidDataSourceFactory.createDataSource(props); } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }Druid 有一个隐蔽特性properties 文件里如果漏了 driverClassName 或 username它会静默改用默认值不报错等真正 getConnection 才失败。所以连接池配完一定要先用一个简单的 main 方法测一次 getConnection而不是直接启动 Tomcat 去猜问题。数据库初始化按第 2.2 节的 SQL 在 Navicat 或 IDEA Database 面板执行建表语句再插入几个测试账号。MySQL 5.7 建库时如果没有指定 utf8mb4建表后要检查表结构否则中文登录名和姓名会变成问号。5. 避坑指南从 IDEA 运行 JavaWeb 项目配置到入库的五个翻车现场下面这个项目里最容易翻车的 5 个现场我连续帮四批做课设的同学排查过问题集中在依赖导入、编码、权限范围这三处每一条都按现象、原因、解决展开。5.1 ClassNotFoundException: com.mysql.jdbc.Driver依赖没进 WAR现象是 Tomcat 启动后第一次请求数据库就抛 ClassNotFoundException: com.mysql.jdbc.Driver。去 pom.xml 里看依赖明明加上了IDEA 的外部库列表里也能看到 mysql-connector-java。原因多数不是 Maven 依赖缺失而是 IDEA 的 Artifacts 没把依赖带进最终部署单元的 WEB-INF/lib。Maven 引入的包在编译期可用但运行时 Tomcat 是从 Artifacts 的 lib 目录加载类的两套目录是分离的。解决方法是打开 Project Structure - Artifacts在输出目录里确认存在 WEB-INF/lib/mysql-connector-java-5.1.49.jar不存在就右键 Artifacts 选 Put into Output Root再重新构建部署。这个坑在老教程里也常见核心思路是运行时类路径和编译期类路径不是一回事。5.2 数据库里中文变成问号四段编码必须统一现象是页面标题正常但表单提交的中文用户名、申请理由存进数据库后全是问号或者浏览器显示乱码。原因是编码链条断了一环。JSP 声明了 UTF-8Servlet 里却没调用 req.setCharacterEncoding连接串没带 characterEncodingUTF-8MySQL 表用了 utf8mb3。四个环节只要有一个不一致中文数据就会在传输中途损坏。解决方法是把四段全部统一JSP 头部写 pageEncodingUTF-8 和 contentTypetext/html;charsetUTF-8Servlet 里所有读取参数前先调 setCharacterEncodingJDBC 连接串加 useUnicodetruecharacterEncodingUTF-8建表语句用 utf8mb4。如果这样还有问题检查 Tomcat 的 server.xml 里 Connector 是否设置了 URIEncodingUTF-8Tomcat 8 以上默认就是 UTF-8老配置不要画蛇添足。5.3 登录成功又跳回登录页Filter 把登录请求一起拦了现象是用户输入正确的账号密码浏览器地址栏短暂跳转后又被弹回登录页特别在首次部署时高频出现。原因多为 Filter 的拦截路径写得太宽。比如为了省事把 WebFilter 配置成 /*登录请求本身还没有 loginUser 属性Filter 判断 user null 就重定向回 login.jsp形成死循环。解决方法是先用注释或控制台日志确认 Filter 拦截的 URL在 Filter 开头打印 req.getRequestURI()如果包含 /login 就放行。更稳的做法是沿用第 2.3 节的约定Filter 只挂在角色路径的前缀上登录接口用单独路径两者天然隔离。5.4 上传的 Word 打不开getSubmittedFileName 被完整路径污染现象是附件上传后数据库里存了一条记录但下载或打开时文件损坏目录里找不到对应文件或文件名变成一长串奇怪路径。原因是前端文件域 getSubmittedFileName() 在多数浏览器里返回的是完整客户端路径或 C:\fakepath\xxx.docx直接拼到 uploadPath 后面目录不存在文件就写失败。解决方法是解析文件名时用 Paths.get(submitted).getFileName().toString() 过滤掉路径部分再拼 System.currentTimeMillis() 前缀生成唯一 storeName。另外如果上传目录在 Tomcat 内部且未授权写入会报 FileNotFoundException把 uploadPath 指到 Tomcat 外的绝对路径并设置目录读写权限即可避免。5.5 Tomcat 端口 8080 被占用上一次进程没退干净现象是 IDEA 点完运行控制台报 Address already in use: JVM_Bind或者提示 8080 端口被占用。原因一般是上一次 Tomcat 异常关闭残留了 Java 进程或者本机装了其他服务占用 8080。解决方法是命令行执行 netstat -ano | findstr 8080 找到 PIDtaskkill /F /PID 对应进程再重新启动。也可以把 IDEA 里 Tomcat 的 HTTP port 改成 8081 一劳永逸但改完后要同步改项目里的跳转前缀和前端请求地址不然会陷入改了端口页面仍 404 的迷惑现场。这个属于最没技术含量的玄学问题但浪费的时间最多养成用完关闭 Tomcat 的习惯比查端口更快。6. 进阶把实习管理系统做稳的优化与验证技巧系统能跑通只是第一步要扛得住答辩和实际用的还有三件事密码加盐、分页查询和按角色走通验收路径。密码不能存明文。前面 UserService 里只调了加密工具这里看一下实现。加盐的作用是让同一密码在不同账号下哈希结果不同防止彩虹表反推public class EncryptUtil { public static String sha256WithSalt(String password, String salt) { try { MessageDigest md MessageDigest.getInstance(SHA-256); byte[] bytes md.digest((salt password).getBytes(StandardCharsets.UTF_8)); StringBuilder sb new StringBuilder(); for (byte b : bytes) { sb.append(String.format(%02x, b)); } return sb.toString(); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } } }登录校验时先从库里查出该用户的 salt再把用户输入的密码拼 salt 哈希后比对。salt 可以直接用 username 字符串或者注册时生成的随机串。这种方案比 Spring Security 轻量得多适合课设本质逻辑没有差别。分页是管理系统躲不开的优化点。岗位列表、周报列表、待审核列表都要分页SQL 统一用 LIMIT offset, pageSize 的写法pageSize 设 10offset 等于 (currentPage - 1) * pageSize。数据量到几万行时用 COUNT(*) 子查询统计总数再配合 idx_student、idx_apply 索引响应时间能控制在毫秒级。不要用 PageHelper 这类框架手写分页能让答辩老师看到你理解 SQL。最后是验收路径管理员发布岗位、学生申请、教师审核通过、学生传周报、教师打分五个环节各角色走一遍确认状态流转和权限拦截都正确。我做这套系统时踩过最深的坑不是技术本身而是低估了数据一致性。当时教师审核申请时我把更新申请表和插入成绩表写了两个 try 块用户反馈有时审核了但成绩没了后来把它们合并到一个事务里再加了日志问题才彻底消失。如果你正拿这套系统做毕设或课设记住先把业务闭环跑通再谈优化交付前自己按三个角色各演示一遍。希望帮到你。本文还有配套的精品资源点击获取
返回列表