ARTICLE DETAIL

资讯详情

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

基于JSP+Servlet的科研成果申报管理系统设计与实现

基于JSP+Servlet的科研成果申报管理系统设计与实现 简介面向政府机构信息化管理人员、科研项目管理员及高校计算机相关专业毕业设计人员该文档围绕科研成果申报管理系统展开。系统采用JSPJavaBean与SQL Server 2000数据库相结合的开发模式完成申报管理、审批管理、用户管理、科研项目管理等功能模块。文档从开发背景、需求分析、可行性分析入手逐步深入阐述系统总体设计、数据库模型设计含数据流图、实体关系图及数据表结构以及JavaBean关键技术的实现细节并附有系统主界面、科研申报、审批、信息查询等关键页面的运行截图与部分源代码分析。通过完整的设计思路梳理与实现步骤呈现可帮助读者快速掌握这类管理系统从理论建模到编码落地的全过程对同类课题开发或实际系统建设具有较高的参考价值。资源为1个doc格式文档压缩包大小516KB已有62人学习下载适合具有一定JSP基础、需要系统设计参考或论文撰写借鉴的读者使用。1. 科研成果申报管理系统到底要解决什么问题科研处老师还在一版一版地收 Word、Excel 申报书文件名带着“最终版”“打死不改版”打开十个文件能凑出十种格式统计时对着表格眼睛都快看瞎了。这套“JSPServletMySQL”的科研成果申报管理系统就是把申报、初审、终审、统计这条完整链路搬到浏览器里老师在线填表提交管理员在线审核留痕年底报表一条 SQL 就能拉出来。技术栈选 JSP 不是因为它新而是它是 JavaWeb 课程设计和毕业设计里资料最全、部署最直白的组合对准备做基于 JSP 的毕设选题的学生来说答辩时也能讲得清楚。这篇按“设计 → 实现 → 踩坑”的顺序把整条落地路径和血泪经验一起写出来。2. JSP技术选型与Model2架构从选型理由到最小工程骨架2.1 从“Nginx支持JSP吗”说起JSP在Tomcat里是如何被执行的被问到最多的一个问题是“Nginx 支持 JSP 吗”。答案是不支持也不该让它直接支持。Nginx 擅长处理静态资源和反向代理它收到 .jsp 或 /login 这类动态请求时正确做法是把请求反代到 Tomcat 上由 Tomcat 里的 JSP 引擎完成翻译和编译再把生成的 HTML 返回给 Nginx。理解这一点部署时才不会闹出“Nginx 代理了 404”的笑话。JSP 本质上是一个 Servlet。第一次有人访问某个 .jsp 页面时Tomcat 会先把这个页面翻译成 .java 文件一个继承了 HttpServlet 的类再用 javac 编译成 .class然后实例化并调用它的 service 方法。翻译和编译都发生在第一次访问时这就是为什么项目刚部署完第一次点开某个 JSP 页面会明显比后续访问慢后面再改 JSPTomcat 会自动检测文件变更并重新翻译编译不需要重启。JSP 页面里能用的语法主要有四类指令% page ... %、声明%! ... %、脚本片段% ... %、动作标签 jsp:xxx 。但项目里我强烈建议不要用脚本片段去写 Java 业务代码只用来做循环遍历和简单判断复杂逻辑全部放到后台。JSP 内置的 9 个对象里实际用得最多的是 request、response、session、application 和 pageContext。session 管登录态application 管全局配置pageContext 在需要手动指定查找范围时偶尔用一下其他对象大部分时间待在角落。2.2 JSP Model2架构为什么科研成果申报不能把Java写在页面里JSP 早期流行 Model1 架构就是页面里既写 HTML 又写 Java 代码甚至直接在 JSP 里用 JDBC 查数据库。这种写法做三五页的展示型小站还凑合但科研成果申报系统有角色权限、状态流转、文件上传、统计分析要是把这些逻辑全塞进 JSP后面每改一个审批规则都要在十几个页面里翻找代码改到怀疑人生。按照 JSP Model2 思想系统拆成三个部分JSP 只负责渲染视图Servlet 只充当控制器接收请求和决定跳转真正干活的是 JavaBean 或 Service 层。就拿实现用户注册功能来说按 Model2 的经典套路是注册页面提交表单 → RegisterServlet 接收参数并封装成 User 对象 → 调用 UserService 里的 register 方法 → Service 里做完校验和事务控制后交给 DAO 操作数据库 → 最后 Servlet 根据处理结果 forward 到成功页或错误页。请求流转清晰每层职责单一。科研成果申报系统的控制器我一般设计成一个总入口 DispatchServlet通过 action 参数区分是登录、提交申报、审批还是查询统计。Servlet 里不出现一行 SQL只做参数解析、调用 Service、处理异常、跳转发散这几件事。Service 层负责业务规则比如“只有状态为待审的申报书才能被审批通过”“退回时必须填写原因”。DAO 层只做最简单的增删改查用 JDBC 或 MyBatis 都行。这样分层之后前端改了不带动后台审批规则变了也不用动页面。2.3 最小工程骨架目录结构、Maven依赖与web.xml配置一个最省心的工程骨架是 Maven 管理的 war 包项目目录结构固定如下JSP 必须放在 src/main/webapp 下Java 源码放在 src/main/java 下两者不能混。src/main/java/com/cwgl/ ├── servlet/ # Servlet 控制器 ├── service/ # 业务接口与实现 ├── dao/ # 数据访问 ├── model/ # 实体类 └── util/ # 工具类DB连接、文件保存 src/main/webapp/ ├── WEB-INF/ │ ├── web.xml │ └── lib/ # 或由 Maven 依赖管理 ├── css/ js/ ├── login.jsp ├── index.jsp └── pages/ ├── declare_list.jsp ├── declare_form.jsp ├── audit_list.jsp └── stats.jspMaven 依赖只需要五样javax.servlet-api、javax.servlet.jsp-api、jstl、mysql-connector-java再加上一个文件上传用的 commons-fileupload如果用 Servlet 3.0 原生 Part 接口这个也可以省。注意 servlet-api 和 jsp-api 的作用域要设成 provided因为 Tomcat 本身带这两个 jar不加 provided 打出来的 war 会引发类冲突。web.xml 里最重要的三个配置段落是Servlet 3.0 或 4.0 的版本声明、Session 超时时间、以及字符编码过滤器。JSP 文件顶部还需要写% page contentTypetext/html;charsetUTF-8 pageEncodingUTF-8 %这一行缺了页面中文直接乱。顺带说一个跟 Spring Boot 集成 JSP 相关的常见翻车Spring Boot 里要跑 JSPJSP 必须放在src/main/webapp/META-INF/resources下同时项目要打包成 war 而不是 jar因为 JSP 文件没法被 jar 包内的类加载器直接扫描到。如果非要用 Spring Boot 内嵌 Tomcat 跑 JSP启动类还得继承 SpringBootServletInitializer 重写 configure 方法。折腾一圈下来你会发现纯 JSP Servlet 在传统 JavaWeb 场景下更省心这也是这类系统最终大多落在裸 Tomcat 上的原因。2.4 选型对比JSPServlet、Spring Boot内嵌JSP与前后端分离很多同学纠结到底用 JSP Servlet 还是 Spring Boot甚至要不要直接上 Vue 前后端分离。我的建议是看系统形态。科研申报管理系统是典型的服务端渲染应用页面数量不算多但都在内网用不需要做移动端适配JSP 直出页面最贴合场景。技术选型页面渲染方式部署复杂度适合场景JSP ServletJSP 模板直出 HTML打 war 丢 Tomcat最简单课设、毕设、学校内部小工具Spring Boot JSP同样 JSP 直出需要处理 webapp 目录和 war 打包想用 Spring 生态又放不下 JSP 的老项目前后端分离Vue REST前端编译成静态资源前端 Nginx 后端接口服务有独立前端开发、需要多端复用的团队如果你只想以最小成本把系统跑起来给科研处用我一般会直接选第一行。王重点是数据库设计和状态机设计这两个不出问题后端用什么壳都不会太难受。3. 科研成果申报的业务建模与数据库设计四张表与三条状态迁移3.1 先梳理业务四种角色与一条申报审批链建表之前先把业务角色和流程捋清楚这是整个系统最不能省的一步。科研申报系统里通常有四种角色申报人教师或学生、科研秘书、科研处管理员、系统管理员。系统管理员管账号和字典数据不参与具体审批真正走业务的是前三种角色。申报人提交一份成果申报书先是科研秘书对该单位内的申报做初审重点看材料齐不齐、成果类型选得对不对初审通过后科研处管理员做终审终审通过意味着这项成果被认定入库。任何一个环节被打回申报书退回给申报人修改修改完再次提交重新走审核流。整个过程里审核意见必须被记录哪怕最终被打回也要能查到是谁在什么时间因为什么原因退的。角色核心权限对应功能页申报人新增申报书、修改草稿、查看结果申报填写、我的申报列表科研秘书初审本单位申报、填写退回意见待初审列表、初审处理科研处管理员终审、统计报表、导出待终审列表、统计页系统管理员用户维护、成果类型字典维护用户管理、字典管理3.2 四张核心表与建表SQL用户、申报书、审批记录、附件数据库是这种系统的地基建表用 utf8mb4 字符集是必须的不然存个生僻作者名都可能报错。时间字段我推荐用 DATETIME 而不是 TIMESTAMPTIMESTAMP 的上限是 2038 年DATETIME 能撑到 9999 年而且 DATETIME 不受时区影响内网系统不需要那套时区转换。下面是四张核心表的建表 SQL实际项目里可能还要加一张字典表存成果类型这里为了演示先写主链路。CREATE DATABASE IF NOT EXISTS cg_system DEFAULT CHARACTER SET utf8mb4 DEFAULT COLLATE utf8mb4_general_ci; CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名工号/学号, password VARCHAR(64) NOT NULL COMMENT 存MD5或SHA-256摘要不存明文, real_name VARCHAR(50) NOT NULL COMMENT 真实姓名, role TINYINT NOT NULL DEFAULT 1 COMMENT 1申报人 2科研秘书 3科研处管理员 4系统管理员, dept_name VARCHAR(100) DEFAULT COMMENT 所属学院/部门, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT用户表; CREATE TABLE declare_project ( id INT PRIMARY KEY AUTO_INCREMENT, project_no VARCHAR(32) NOT NULL COMMENT 申报编号生成规则见说明, user_id INT NOT NULL COMMENT 申报人外键关联sys_user, title VARCHAR(200) NOT NULL COMMENT 成果名称, type_code VARCHAR(20) NOT NULL COMMENT 成果类型论文/专利/软著/获奖等, status TINYINT NOT NULL DEFAULT 0 COMMENT 0草稿 1待初审 2待终审 3已通过 4已退回, content TEXT COMMENT 成果简介前端富文本提交, file_path VARCHAR(255) DEFAULT COMMENT 附件相对路径存库不存绝对路径, submit_time DATETIME DEFAULT NULL COMMENT 提交时间, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user (user_id), KEY idx_status (status) ) ENGINEInnoDB COMMENT申报书主表; CREATE TABLE audit_record ( id INT PRIMARY KEY AUTO_INCREMENT, project_id INT NOT NULL COMMENT 申报书ID, auditor_id INT NOT NULL COMMENT 审核人ID, action VARCHAR(10) NOT NULL COMMENT pass通过 / reject退回, opinion VARCHAR(500) DEFAULT COMMENT 审核意见退回时必须填写, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_project (project_id) ) ENGINEInnoDB COMMENT审批记录表; CREATE TABLE appendix_file ( id INT PRIMARY KEY AUTO_INCREMENT, project_id INT NOT NULL, original_name VARCHAR(200) NOT NULL COMMENT 原始文件名用于展示下载名, stored_name VARCHAR(100) NOT NULL COMMENT 磁盘上存储用的UUID名, file_size INT DEFAULT 0 COMMENT 单位字节, upload_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT附件表;申报编号的设计有个讲究不要在页面里让用户填而是代码生成。常见做法是取“当前年份 部门编码 当天序号”比如 2025CWL001。合法性校验放在 Service 层做数据库里的外键约束我一般只建索引不建物理外键物理外键在高并发下会有锁竞争问题内网系统虽然压力不大但用逻辑关联够用了代码维护也方便。3.3 状态机设计草稿、待审、退回、通过如何流转申报书的状态字段是整个系统的命脉设计成 TINYINT 存数字不要用字符串“已通过、待审核”直接存理由有两个一是中文占空间又容易改错二是改状态文案时不用动历史数据前端映射一下就行。状态迁移总共三条主链草稿到提交提交到审批审批到退回或通过。草稿状态允许申报人反复修改点击“保存”只是落盘点击“提交”才进入审批流。提交之后申报人不能再编辑想看只能看。科研秘书初审通过后进入待终审终审通过则状态变为已通过。任一层级只要点了退回状态回到草稿申报人修改后再提交重新从初审开始走。注意退回后申报人必须能修改所以退回时不能把文件和历史字段清掉界面要提示有哪些问题需要修改。当前状态触发动作下一状态前置条件草稿(0)保存草稿草稿(0)仅本人可操作草稿(0)提交申报待初审(1)必填项完成、附件已传待初审(1)秘书通过待终审(2)当前用户是科研秘书待初审(1)秘书退回草稿(0)退回原因非空待终审(2)管理员通过已通过(3)当前用户是科研处管理员待终审(2)管理员退回草稿(0)退回原因非空这块最容易犯的错是审核页直接把状态设为新值而不管当前值。比如两个审核人同时打开同一份申报书A 先点了通过B 再点退回如果 SQL 是UPDATE declare_project SET status4 WHERE id?而不带条件B 就把 A 的结果覆盖了。正确做法是条件更新把状态当作更新条件一部分这部分在 4.3 会给出完整代码。数据库设计到这里就可以开工写代码了表不用一次建完后面发现要存新字段时加列是常态。4. 核心功能实现登录拦截、文件上传与乐观锁审批4.1 登录与会话控制LoginServlet与登录拦截Filter登录是第一个要落地的功能逻辑不复杂但必须配合拦截器一起做否则登录页形同虚设。下面这个 LoginServlet 接收用户名密码、校验成功后把用户信息放进 session同时把用户角色也存进去后面的审批页面要靠这个角色做权限判断。WebServlet(/login) public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); UserService userService new UserService(); User user userService.login(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(request.getContextPath() /index.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(/login.jsp).forward(request, response); } } }这段代码的核心是登录成功后的处理把 User 对象放进 session页面里通过c:if test${sessionScope.loginUser.role 3}就能控制按钮显隐不需要每个 JSP 再查一次数据库。密码的 MD5 摘要要在 Service 层的 login 方法里做不能让明文 SQL 拼接进查询。配套的登录拦截 Filter 要拦截所有页面和接口但放行 login.jsp、login 接口、静态资源三样。拦截逻辑写在 doFilter 里如果 session 里没有 loginUser 就重定向到 login.jsp如果是静态资源如 css、js、图片直接放行否则每个请求都被拦到登录页会出死循环。4.2 申报提交与文件上传Part API与外部磁盘路径文件上传是整个系统里坑最多的地方。Servlet 3.0 提供原生 Part API不需要额外引 commons-fileuploadTomcat 9 默认支持 multipart/form-data。上传代码要解决三个问题中文文件名乱码、重复文件覆盖、以及最致命的“文件存进部署目录后重启丢文件”。WebServlet(/uploadDeclare) MultipartConfig(maxFileSize 10 * 1024 * 1024, maxRequestSize 50 * 1024 * 1024) public class UploadServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); // Part接口获取文件表单里的input typefile namefileField Part part request.getPart(fileField); String originalName getFileName(part); // 取原始文件名 // 用UUID做磁盘文件名避免中文乱码和重名覆盖 String storedName UUID.randomUUID().toString().replace(-, ) originalName.substring(originalName.lastIndexOf(.)); // 外部目录硬编码到统一配置绝不写进部署目录 String uploadDir D:/cg_system_upload/; File dir new File(uploadDir); if (!dir.exists()) dir.mkdirs(); part.write(uploadDir storedName); // 数据库只存相对路径即文件名 String projectId request.getParameter(projectId); appendixDao.insert(Integer.parseInt(projectId), originalName, storedName, (int) part.getSize()); response.sendRedirect(pages/declare_form.jsp?projectId projectId); } private String getFileName(Part part) { // 解析Content-Disposition里的filename字段 String header part.getHeader(content-disposition); String[] arr header.split(;); for (String s : arr) { if (s.trim().startsWith(filename)) { return s.substring(s.indexOf() 1).trim().replace(\, ); } } return unknown_file; } }参数说明MultipartConfig 里的 maxFileSize 是单个文件上限 10MBmaxRequestSize 是整个请求包含表单字段的总上限 50MB超出会抛 IllegalStateException需要在 web.xml 或全局异常处理里捕获否则浏览器直接 500。getFileName 这个方法必须自己写因为 Part.getName() 返回的是表单字段名而非文件名。核心是 storedName 用 UUID 重命名文件用户在列表页看到的下载名从数据库 appendix_file 表里的 original_name 字段取这样既避免磁盘乱码又保证用户下载时看到的是正常中文名。文件存“D:/cg_system_upload/”这种外部路径是必须坚持的。很多教材会让用 request.getServletContext().getRealPath(/upload)这个 API 返回的是 Tomcat 部署目录一旦重新部署 war 或 Tomcat 自己清理临时目录文件就全没了。数据库里存的 file_path 只存文件名或相对路径展示时再拼上配置里的磁盘根路径这层间接关系是文件不丢的关键。忘记这段配置后面重启一次服务器所有申报书附件一律 404。4.3 审批流转乐观锁更新状态与审批记录写入审批功能的代码容易被当成简单的“点一下按钮改个状态”但实际落地时最怕并发覆盖和操作无痕。下面的审批方法把状态更新和审批记录插入放在一个事务里并且用带条件的 UPDATE 保证状态只从预期的旧值迁移到新值。public boolean approve(int projectId, int auditorId, String action, String opinion) throws Exception { Connection conn null; try { conn DBUtil.getConnection(); conn.setAutoCommit(false); // 1. 条件更新status1 表示只有待初审的申报书才能被这一步处理 String sql UPDATE declare_project SET status ? WHERE id ? AND status ?; PreparedStatement ps conn.prepareStatement(sql); if (pass.equals(action)) { ps.setInt(1, 2); // 待初审 - 待终审 } else { ps.setInt(1, 0); // 退回草稿 } ps.setInt(2, projectId); ps.setInt(3, 1); // 旧状态必须是待初审 int rows ps.executeUpdate(); if (rows 0) { conn.rollback(); return false; // 说明状态已被别人改过拒绝本次操作 } // 2. 插入审批记录退回时 opinion 在 Service 层已经校验非空 String logSql INSERT INTO audit_record(project_id, auditor_id, action, opinion) VALUES(?,?,?,?); PreparedStatement ps2 conn.prepareStatement(logSql); ps2.setInt(1, projectId); ps2.setInt(2, auditorId); ps2.setString(3, action); ps2.setString(4, opinion); ps2.executeUpdate(); conn.commit(); return true; } finally { if (conn ! null) { conn.setAutoCommit(true); conn.close(); } } }这个方式的精妙之处是 UPDATE 语句的 WHERE 条件里放上了“当前状态必须是 1待初审”。两个审核人同时打开页面时先提交的人把状态改成 2后提交的人执行 UPDATE 时因为 status 已经不是 1 而影响行数为 0代码里通过 rows 0 判定拒绝操作从根上杜绝了覆盖。这比先 SELECT 再 UPDATE 的写法可靠得多省掉了分布式锁。审批记录和状态更新在同一事务里提交避免出现“状态改了但审批记录没写”的脏数据。Service 层还要额外判断操作者角色科研秘书不能终审、科研处管理员不能初审角色判断放在 Service 层做前端按钮隐藏只是体验不是安全措施。4.4 统计报表按学院、按成果类型的GROUP BY查询统计报表是这类系统里最给科研处提效的功能以前用 Excel 手工汇总要两天现在一条 SQL 就出来。最常见的是三张统计图按学院统计申报数、按成果类型统计占比、按月份统计申报趋势。-- 按学院统计申报数量关联用户表取部门名称 SELECT u.dept_name AS dept, COUNT(p.id) AS cnt FROM declare_project p LEFT JOIN sys_user u ON p.user_id u.id WHERE p.status 3 -- 只统计已通过 GROUP BY u.dept_name ORDER BY cnt DESC; -- 按成果类型统计 SELECT type_code, COUNT(*) AS cnt FROM declare_project WHERE status 3 GROUP BY type_code ORDER BY cnt DESC; -- 按月份统计提交趋势DATE_FORMAT 把时间格式化到年月 SELECT DATE_FORMAT(submit_time, %Y-%m) AS month, COUNT(*) AS cnt FROM declare_project WHERE status ! 0 GROUP BY DATE_FORMAT(submit_time, %Y-%m) ORDER BY month;统计页面的 JSP 里用 JSTL 的 forEach 循环结果集生成表格行和简单的 CSS 条形图即可不需要引入 ECharts 这种重库。要注意的是统计 SQL 的 WHERE 条件如果科研处想看全口径含草稿外的所有状态就把状态条件去掉如果想看某一年度的就在 SQL 里加 submit_time 的范围过滤这个参数做成页面下拉框由管理员选择不要写死。报表功能最忌讳的就是 SQL 里面没有检索条件直接全表 GROUP BY数据量到几万条时页面会卡到怀疑人生所以统计页必须提供年度、学院、类型三个筛选条件。5. 科研成果申报系统高频问题排查乱码、文件丢失与数据覆盖5.1 中文乱码三层问题页面、请求体与数据库连接串中文乱码是这类系统最经典的“玄学”问题其实可以拆成三个独立故障点。现象一JSP 页面上的中文文字直接显示成问号原因是 JSP 文件本身保存成了 GBK 或文件顶部缺少 pageEncoding 声明解决方法是统一把 JSP 文件保存为 UTF-8并在第一行写上 pageEncoding 和 contentType 两个参数。现象二页面显示正常但表单提交到后台后中文变成乱码原因是 POST 请求体的解码字符集没有被设置DO GET 方法不会有这个问题因为参数在 URL 里解决方式是在 web.xml 里配置一个全局 CharacterEncodingFilter把 request 和 response 的编码统一设为 UTF-8。现象三数据库里存的中文变成乱码这种往往前面两个都没问题但数据库连接串里少了 characterEncodingutf8MySQL 5.7 和 8.0 的连接串必须显式加上否则驱动默认用系统字符集查出来的中文全是问号。5.2 文件上传半天后404附件被Tomcat清理了现象是上传附件时一切正常能传能下但项目重启一次或者过了几天再打开下载链接附件 404。原因非常统一附件被写进了 Tomcat 的部署目录。用 getRealPath(/upload) 拿到的路径指向 Tomcat/webapps 下的项目目录Tomcat 在重新部署、清理工作目录或者因为磁盘空间触发临时目录清理时会把非代码文件一并清掉。解决方法是代码里不能出现任何 getRealPath 和 getContextPath 拼接的物理路径改用一个项目外部的统一磁盘目录Linux 上用 /data/cg_system_uploadWindows 上用 D:/cg_system_upload并在配置类常量类里统一管理这个路径。数据库文件表里只存 UUID 文件名页面上展示下载时动态拼接目录根路径和文件名。这个改动很简单但极其关键不然上线后的后悔药可不好找。5.3 审批状态被后提交的覆盖WHERE条件少了状态判断现象是同一份申报书科研秘书 A 和科研处管理员 B 同时打开审核页面A 点了通过、B 点了退回最终状态变成了 B 的退回但 A 通过时生成的审计记录还留在表里造成状态和记录对不上。原因是审批 SQL 只按主键 UPDATE没判断当前状态。解决方式就是把状态当作更新条件写进 WHEREUPDATE 影响行数为 0 时终止操作并提示用户刷新页面重新查看。注意还要给审批记录表加一个“当前状态快照”或用 action 字段区分否则状态对不上很难查。审查类的操作一定要先读后写且写的时候带条件这是所有带审批流系统的基本功。5.4 页面脚本片段太多翻车Model1式JSP改需求改到崩溃现象是系统改版时加一个字段要同时改十几个 JSP 页面每个页面里都嵌着一样的 JDBC 查询代码改漏一个页面功能就错。原因不复杂开发初期图省事直接在 JSP 里写了 Java 代码这就是 Model1 架构网页一多就维护不动。解决方式也很直接把 JSP 里所有 Java 代码块往外搬页面里只留 JSTL 和 EL 表达式数据查询统一收拢到 Service 层页面通过请求转发拿到已经准备好的列表、对象不再自己查库。如果系统已经堆了不少脚本片段改造方式是先建一个公共查询接口让页面逐步切换不要一次性重写全部页面改动范围容易失控。5.5 JSP首次访问特别慢翻译与编译避不开现象是 Tomcat 刚启动完第一次点开某个 JSP 页面要等两三秒之后访问就正常。原因不是服务器配置差而是 JSP 引擎在首次访问时对页面做了翻译和编译。Tomcat 默认在应用部署时预编译 JSP但很多自定义安装或 IDE 内嵌启动方式不会预编译就会表现为首次访问慢。解决方式有两个正式上线前对 JSP 做预编译Tomcat 自带 JspC 工具可以完成或者接受首次编译的延迟但必须在压测时把第一次访问的时间算进页面响应里不能让评审专家第一次点开页面就喊慢。这个问题不是故障但每次重启后第一次访问都慢很容易被误判成性能问题排查半天最后发现是 JSP 机制本身。6. 进阶动态申报表单、Excel批量导入与上线前验收清单6.1 让申报表单动起来扩展字段与JSON列方案不同的成果类型要填的字段不一样论文要填期刊级别和影响因子专利要填专利号和授权日期软著要填登记号。给每一种类型单独建一张表会让查询越来越复杂常见做法是在 declare_project 表上加一个扩展 JSON 列把不同类型的差异化字段存成 JSON 字符串展示时按 type_code 渲染对应的表单模板。这个方案改动小加新成果类型时不用改表结构。要注意的是 JSON 字段只存基本信息用于统计和检索的字段还是要抽成独立列加索引不能全塞 JSON 里非要用 LIKE 去查。ALTER TABLE declare_project ADD COLUMN ext_json JSON NULL COMMENT 扩展字段按成果类型存储差异化信息;6.2 用POI做Excel批量导入先校验表头再入库科研处每年手里都有一批往年的 Excel 申报记录需要一次性导入系统。用 POI 导入时我一般先把模板表头读出来跟配置好的模板比对表头不对直接拒绝导入避免数据错位。下面是校验表头的核心代码逻辑。Row header sheet.getRow(0); String[] expected {成果名称, 成果类型, 申报人, 所属部门}; for (int i 0; i expected.length; i) { String cellValue header.getCell(i).getStringCellValue().trim(); if (!expected[i].equals(cellValue)) { throw new RuntimeException(第 (i 1) 列表头应为 expected[i]); } }整行数据都校验通过后插入数据库遇到错误要记录行号和原因最后生成一份导入失败报告给用户。不要边读边插否则数据读到一半报错前面插入的数据就成了脏数据。6.3 上线前的验收清单与兜底操作上线前一晚我一般会把下面几个关键点过一遍一是权限验证普通用户能不能直接访问审批页面的 URL前端隐藏不等于权限控制二是状态流转把草稿、待初审、待终审、退回、通过五条迁移链各走一遍确认每一步都写入了审计记录三是文件目录确认上传目录在项目外部数据库路径存储的是相对路径备份任务把上传目录纳入备份范围四是数据库定时备份用系统自带的计划任务每天凌晨执行 mysqldump备份文件保留三十天。做这类系统时我有个深刻教训所有业务逻辑先画状态图再写代码永远比直接开搞快。第一次做科研申报系统我把文件写进部署目录一次重启全部附件消失后来改成外部目录加 UUID 命名再没丢过文件。把这些边界场景提前想明白你的系统离真正可用就不远了希望帮到你。本文还有配套的精品资源点击获取
返回列表