
简介Java Web绩效考评系统是一套面向企业人事管理场景的可运行项目采用Servlet、JSP、Spring MVC及Hibernate/MyBatis等主流技术栈覆盖员工管理、绩效指标设置、考核周期设定、评价录入、自动计分、报表生成与反馈闭环等环节适合Java Web学习者、毕业设计者以及需要快速搭建绩效管理原型的技术人员参考。压缩包共509个文件核心包含91个Java源文件、91个class文件、39个JSP页面以及SQL数据库脚本、配置文件等包体约982KB可完整还原项目结构并用于二次开发。资源包内置员工表、部门表、绩效标准表等数据库设计以及登录、权限、评分处理等核心Servlet代码有助于理解企业级Web应用的请求处理、数据持久化与业务分层。目前已有175人学习下载适合作为理解Java EE经典分层架构及企业绩效考核流程的实践案例。1. 绩效考评系统 java web 项目一份 .rar 源码背后要解决的问题第一次接手“绩效考评系统 java web”这份源码时我没有把它当成一个简单的课程设计来看。这类基于 Java 的绩效系统核心不是登录注册而是“考核模板谁定、考核任务怎么派、分数怎么汇总、结果谁能看”这一串流程。它解决的是企业内部季度考核、年度考核从线下表格搬到线上的问题适合用来做毕业设计、公司内网小工具也适合初级 Java 开发用来复盘别人怎么组织一个完整 Web 工程。很多人下载 .rar 压缩包后第一个动作就是解压导入 IDE结果被旧框架、乱码、驱动版本折腾到劝退。这篇文章会照着“先看懂设计再跑起来最后改得动”的顺序把这条路走通。2. 绩效考评系统的核心设计角色权限、考核指标与 MySQL 表结构怎么搭2.1 角色与考核流程先把“谁考谁、怎么算分”定下来大部分绩效考评系统的源码无论界面多简陋角色都不会少于三类系统管理员、考评人、被考评人。管理员负责配置考核模板和指标权重考评人通常是部门主管或指定同事被考评人就是普通员工。如果你接手的项目里还多了一个“人事专员”角色那多半是用来审核考核结果和发起申诉流程的我一般会把它合并到管理员权限里避免权限判断复杂化。考核流程可以压缩成四步理解了这四步再看任何一份 java web 绩效项目代码都会快很多第一步管理员创建考核模板比如“2024 年 Q4 绩效”并给模板挂上若干个考核指标第二步管理员发起考核任务指定哪些人参与打分通常一个任务会有多个考评人和多个被考评人形成一张打分关系表第三步考评人登录后看到分配给自己的待办逐项打分并提交第四步系统按指标权重汇总分数生成考核结果被考评人可以查看自己的最终得分和分项得分。这里有一个容易踩的坑很多初学者把“角色”和“菜单权限”做成一棵树每个页面都去查数据库判断能否访问最后整个项目堆满了权限判断代码性能还差。常见做法是直接在用户表上加一个role_type字段用整数区分管理员、考核人、员工然后在过滤器里做拦截。这样做对绩效考评这种角色固定、页面不超过 20 个的系统完全够用后续要扩展成 RBAC 也不难保留用户表和角色表再把菜单表加上就行。还有一个必须先定下来的事分数计算规则。是取平均分还是按考评人权重算要不要去掉一个最高分和一个最低分绩效考评系统最常见的规则是“按权重加权平均且去掉最高最低分后再平均”因为部门主管打分容易偏高或偏低去掉极端值能减少关系分的影响。这个规则直接决定了你数据库表要怎么设计、Java 代码里要怎么写计算逻辑。我见过不少源码把“去掉最高最低分”写死在 SQL 里导致后来要调规则就得重构所以更推荐在 Java 层做计算SQL 只负责取原始分。2.2 五张核心数据表用 SQL 把考核模板和评分结果落库如果让我重新搭一套绩效考评系统的表我绝不会一张表装下所有数据。比较合理的拆分是五张表用户表、考核模板表、考核指标表、考核任务表、考核评分表。你可以先看看手里 .rar 源码里的sql目录一般会有一个init.sql或者performance.sql文件如果它把这五张表拆得干净说明作者的基本功还可以如果只有两三张表那就得小心了后续扩展会很痛苦。先看用户表这里用最简设计适合演示也适合二次开发CREATE TABLE sys_user ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) NOT NULL UNIQUE, password VARCHAR(64) NOT NULL, real_name VARCHAR(50) NOT NULL, dept_id INT, role_type TINYINT NOT NULL DEFAULT 3 COMMENT 1管理员 2考评人 3员工, status TINYINT DEFAULT 1 COMMENT 1启用 0停用, create_time DATETIME DEFAULT CURRENT_TIMESTAMP );密码字段用 64 位是为了能放 SHA-256 或 MD5 加盐后的结果不建议明文存储。role_type是权限判断的核心过滤器里直接用这个字段做判断。dept_id是为后续的“行级权限”预留的如果你做的是企业内网部署部门主管只能看到本部门员工的考核数据这个字段就能派上用场。接着是考核模板表CREATE TABLE assess_template ( id INT PRIMARY KEY AUTO_INCREMENT, template_name VARCHAR(100) NOT NULL, year INT NOT NULL, quarter INT NOT NULL, status TINYINT DEFAULT 0 COMMENT 0草稿 1已发布 2已归档, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_template (year, quarter) );status字段很重要已发布的模板不允许再修改指标不然考核已经开始打分指标却被改了数据就乱套了。year quarter加唯一约束保证一个季度只能有一套模板避免管理员手滑创建两套。考核指标表要挂到模板下CREATE TABLE assess_indicator ( id INT PRIMARY KEY AUTO_INCREMENT, template_id INT NOT NULL, indicator_name VARCHAR(100) NOT NULL, weight DECIMAL(5,2) NOT NULL DEFAULT 0, max_score DECIMAL(5,2) NOT NULL DEFAULT 100, sort_no INT DEFAULT 0, KEY idx_template (template_id) );weight表示权重0 到 1 之间的小数比如 0.3 代表这个指标占总成绩的 30%。这里把权重存在指标行里是最直观的做法查询模板时一次拿到所有指标没有必要为了“规范化”拆成模板表和模板指标关系表因为绩效考核的指标就是从属于模板的不会有多个模板共享指标的情况。考核任务表用来记录“谁考核谁”CREATE TABLE assess_task ( id INT PRIMARY KEY AUTO_INCREMENT, template_id INT NOT NULL, assessor_id INT NOT NULL COMMENT 考评人id, target_user_id INT NOT NULL COMMENT 被考评人id, status TINYINT DEFAULT 0 COMMENT 0待打分 1已提交, submit_time DATETIME, KEY idx_template (template_id), KEY idx_assessor (assessor_id), KEY idx_target (target_user_id) );这张表是整个系统的连接点管理员发起考核时按“考评人-被考评人”关系批量插入任务。考评人登录后WHERE assessor_id 当前用户id AND status 0就是他的待办列表。记住要加submit_time报表统计时能用来判断是否逾期。最后是评分表它存的是考评人对被考评人每一个指标的打分CREATE TABLE assess_score ( id INT PRIMARY KEY AUTO_INCREMENT, task_id INT NOT NULL, indicator_id INT NOT NULL, score DECIMAL(5,2) NOT NULL, comment VARCHAR(500), UNIQUE KEY uk_task_indicator (task_id, indicator_id) );这张表设计成按task_id和indicator_id唯一防止同一个人对同一个指标重复打分。综合成绩不用存每次需要时从assess_score现算就行一是保证数据一致性二是绩效规则调整时不用改历史数据。如果你担心查询性能后期可以加一张assess_result汇总表但建议只在模板归档时生成一次。这五张表基本上把一个绩效考评系统的数据底盘立住了。你在跑任何一份 .rar 源码前先对照这个结构看它的建表 SQL马上就能判断这个项目值不值得继续读下去也能知道它当年的设计水平在什么位置。3. 用 Java 实现绩效考评评分汇总、权限拦截与页面交互的关键代码3.1 加权评分与去最高最低分ScoreCalculator 的写法与参数绩效考评的计算逻辑是整个 Java 后端最值得复用的部分。我见过的情况是原始项目把计算逻辑放在 JSP 页面里的不少表单提交后直接用脚本算出结果再输出。这种写法很不好维护页面上还动不动就报空指针。合理的做法是单独写一个ScoreCalculator类里面只干一件事传入原始分数返回汇总结果。这样跑单元测试也好改规则也好都不会波及页面。下面这段代码是一个比较典型的加权计算实现去掉最高最低分后再按权重加权平均import java.math.BigDecimal; import java.math.RoundingMode; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * 绩效评分计算器 * 规则去掉一个最高分和一个最低分再按指标权重加权平均 */ public class ScoreCalculator { /** * param scores 某个被考评人在某个指标下的所有打分不能为空 * param weights 所有指标的权重顺序要与指标列表保持一致 * return 汇总后的综合得分 */ public static BigDecimal calculate(ListBigDecimal scores, ListBigDecimal weights) { if (scores null || scores.size() 3) { // 打分人数少于3时去掉最高最低分会丢失样本直接取平均 return average(scores); } ListBigDecimal sorted new ArrayList(scores); Collections.sort(sorted); // 去掉一个最高分和一个最低分 BigDecimal sum BigDecimal.ZERO; for (int i 1; i sorted.size() - 1; i) { sum sum.add(sorted.get(i)); } BigDecimal avg sum.divide( BigDecimal.valueOf(sorted.size() - 2), 2, RoundingMode.HALF_UP ); // 加权计算weights 的和一般是1 BigDecimal result BigDecimal.ZERO; for (int i 0; i weights.size(); i) { result result.add(avg.multiply(weights.get(i))); } return result.setScale(2, RoundingMode.HALF_UP); } private static BigDecimal average(ListBigDecimal scores) { BigDecimal sum BigDecimal.ZERO; for (BigDecimal s : scores) { sum sum.add(s); } return sum.divide(BigDecimal.valueOf(scores.size()), 2, RoundingMode.HALF_UP); } }这里有几个参数值得认真对待。scores.size() 3时不能直接去最高最低分否则两个考评人的数据会被削成一个等于没算权重列表weights的元素顺序必须和数据库查出来的指标顺序一致所以在 Service 层查询指标时要按sort_no排序否则加权就是错的setScale(2, RoundingMode.HALF_UP)保留两位小数是给报表展示用的数据库里存储时需要先用BigDecimal处理不能在 SQL 里做除法截断。这个类可以继续扩展如果规则要改成“不去最高最低分”直接把中间那段排序逻辑删掉即可如果某个指标要求所有人得分都一样那是模板配置的事计算器不用动。保持计算器只依赖ListBigDecimal入参是它能够被复用的关键。3.2 登录与角色权限用 Filter 拦截未授权请求而不是每个页面判断绩效考评系统的页面权限常见做法是用 Filter 统一拦截谁登录了、能不能访问某个功能在过滤器里判断完Servlet 里就只管业务。这么做能少写很多重复代码也可以避免新人把“判断是否登录”写在每个 JSP 顶上。看下面这段过滤器实现import javax.servlet.*; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.io.IOException; public class AuthFilter implements Filter { Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req (HttpServletRequest) request; HttpServletResponse resp (HttpServletResponse) response; HttpSession session req.getSession(false); // 未登录用户一律跳转登录页 if (session null || session.getAttribute(loginUser) null) { resp.sendRedirect(req.getContextPath() /login.jsp); return; } // 已经登录但访问路径有角色要求时再做一次角色校验 Integer roleType (Integer) session.getAttribute(roleType); String uri req.getRequestURI(); if (uri.contains(/admin/) roleType ! 1) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } if (uri.contains(/assessor/) roleType 2) { resp.sendError(HttpServletResponse.SC_FORBIDDEN); return; } chain.doFilter(request, response); } }这段代码的核心点是只在 URL 路径包含特定目录时检查角色而不是对每个 URL 都查一遍数据库。绩效考评系统的页面可以按角色分成三个目录/admin/管理员用/assessor/考评人用/employee/员工查询用。这种目录划分配合 Filter 拦截清晰且不容易漏判。还有一个细节要注意/employee/这样的员工路径不能让员工通过修改 URL 去访问别人的考核结果因为 URL 里很可能带着targetUserId2这样的参数。过滤器只能保证“登录了”不能保证“看的是自己的数据”所以在 Servlet 里还要再校验一次目标对象和当前登录用户是否匹配。在绩效这种场景里这就是真正的行级权限控制部门主管只能看到本部门员工的数据员工只能看自己的数据。我一般会在 Service 层做一个checkAccess(currentUser, targetUserId)方法返回布尔值避免把校验逻辑散落在各个 Servlet 里。这个校验方法本身也要写成单元测试覆盖住因为绩效数据一旦泄露挨投诉事小考核公平性被质疑才是大问题。3.3 JSP 表单如何把考核结果送到 Servlet一次典型的前后端交互旧式 Java Web 项目的页面交互最典型的就是 JSP 表单 POST 到 ServletServlet 再调 Service 落库。绩效考评里的打分页面就是这样页面展示被考评人和一系列指标考评人填分数后提交。你要能看懂这层交互才看得懂绝大多数 .rar 源码。表单部分长这样form action${pageContext.request.contextPath}/score/submit methodpost input typehidden nametaskId value${task.id} / input typehidden nametargetUserId value${task.targetUserId} / c:forEach items${indicators} varind varStatusstatus div classindicator-row label${ind.indicatorName}权重 ${ind.weight}/label input typenumber namescore_${ind.id} min0 max${ind.maxScore} required / /div /c:forEach button typesubmit提交考核结果/button /form注意每个输入框的name是score_指标id比如score_3、score_5这样在 Servlet 端能直接通过指标 id 识别分数比前端用数组再人工对齐要可靠得多。taskId和targetUserId是隐藏字段告诉后端这条打分记录属于谁。这里看起来有点把业务参数暴露在页面上但旧项目里这是常规操作代价是你需要在 Servlet 里校验这两个参数和登录用户的关系。Servlet 端接收参数的写法WebServlet(/score/submit) public class ScoreSubmitServlet extends HttpServlet { Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Integer taskId Integer.valueOf(req.getParameter(taskId)); Integer targetUserId Integer.valueOf(req.getParameter(targetUserId)); Integer currentUserId (Integer) req.getSession().getAttribute(userId); // 防止考评人替别人提交或提交不存在的任务 if (!scoreService.isTaskAssignedToMe(taskId, currentUserId)) { resp.sendError(403); return; } // 动态从参数里读取每个指标的分数 MapInteger, BigDecimal scoreMap new HashMap(); for (String paramName : req.getParameterMap().keySet()) { if (paramName.startsWith(score_)) { Integer indicatorId Integer.valueOf(paramName.substring(6)); BigDecimal score new BigDecimal(req.getParameter(paramName)); scoreMap.put(indicatorId, score); } } scoreService.submitScores(taskId, targetUserId, scoreMap); resp.sendRedirect(req.getContextPath() /assessor/pending.jsp); } }这段代码的亮点是“遍历参数名以前缀识别业务字段”即使指标数量在模板调整后变化了Servlet 逻辑也不用改。你可以看到参数在 Java 侧变成了一个MapInteger, BigDecimal这个 Map 正好可以作为上一节ScoreCalculator计算前的原始数据来源。要注意的是req.getParameterMap()返回的是只读 Map不能直接往里放数据new BigDecimal(String)一定要捕获NumberFormatException因为用户可能在输入框里填了非数字内容。我一般在 Service 的submitScores方法里先做一次scoreMap完整性检查比对模板指标数量和传入指标数量少了就返回“还有指标未打分”多了就报“非法提交”。这步不做的话考评人提交一个分数缺失的考核结果算出来就是错的非常难以排查。4. 把 .rar 源码跑起来从解压到 Tomcat 部署的完整步骤4.1 解压后先认清目录结构src、WebRoot、sql、lib 各自做什么你下载到的通常是一个绩效考评系统—java web.rar压缩包用解压工具解开后会看到至少四个目录或文件src、WebRoot、sql和一堆.jar包也可能在一个lib文件夹里。在往 IDE 里导入之前先手动点开看一眼能帮你判断项目基于什么框架。标准目录大概长这样绩效考评系统/ ├── src/ │ ├── com/company/action/ # Action 或 Servlet 类 │ ├── com/company/dao/ # 数据访问层 │ ├── com/company/service/ # 业务逻辑层 │ └── com/company/util/ # 工具类 ├── WebRoot/ │ ├── WEB-INF/ │ │ ├── web.xml │ │ └── lib/ # 项目依赖的 jar 包 │ ├── admin/ # 管理员页面 │ ├── assessor/ # 考评人页面 │ ├── employee/ # 员工查询页面 │ ├── css/ │ └── js/ ├── sql/ │ └── init.sql └── README.txt可能有也可能没有如果src下面有一堆struts.xml或applicationContext.xml配置文件那多半是 SSHStruts Spring Hibernate组合SSH 项目的启动配置比纯 Servlet 要多如果只有web.xml和 Servlet 类那处理起来会轻松很多。无论哪种先别急着改代码下一步要做的是把数据库初始化跑通。有一点提前给你打预防针很多源码包自带的lib里只有少量 jar而且版本很旧项目能不能编译跑起来很大程度取决于这些 jar 是否齐全。我一般会对照WEB-INF/lib下的文件如果要用的 jar 缺失再去 Maven 仓库按坐标下载同版本不建议直接拿最新版旧代码经常和新版本不兼容。4.2 数据库初始化与连接配置建库、导数据、改 jdbc.properties几乎每个 java web 项目都会带一个 SQL 脚本常见文件名是init.sql或performance.sql。打开看一眼确认里面包含CREATE DATABASE语句。有些脚本只建表不建库那你就得手动先建库。我习惯首先用它执行一遍而不是自己手工创建表因为脚本里可能还包含测试数据没有那批数据登录页面验证不了。执行脚本我经常用命令行环境变量配置好的情况下最直接mysql -uroot -p sql/init.sql如果你的 MySQL 装在远程服务器上就加-h指定主机如果脚本里没有指定USE语句进库后先执行USE performance_db;再跑脚本。这个脚本执行完之后需要确认一下库里有没有用户表、考核模板表这些核心表以及有没有插入管理员账号。管理员账号一般就写在 SQL 脚本里比如INSERT INTO sys_user ... role_type 1。如果脚本里不放初始账号那项目启动后你没法登录只能自己往表里插一条管理员记录。接下来改数据库连接配置。源码包里一般会有一个jdbc.properties文件在src根目录或resources目录下jdbc.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/performance_db?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.password123456这里最需要关注的是编码参数useUnicodetruecharacterEncodingutf8一定要保留否则页面提交中文到数据库后会乱码。如果你的 MySQL 是 8.x上面的com.mysql.jdbc.Driver已经不是默认驱动了得改成com.mysql.cj.jdbc.Driver这个问题在下一章避坑里还会具体展开。4.3 部署到 TomcatIDEA 2024 里配置 Artifact 并启动把项目导入 IntelliJ IDEA 2024 后不能直接按运行按钮因为 java web 项目要先配置 Servlet 容器才能启动。我这里给的是在 IDEA 里从零配置 Tomcat 的步骤在 Eclipse 里也一样能配只是菜单名称略有不同。首先打开 Project Structure确认 Project SDK 是 JDK 8。这套旧代码我用过 8 是最稳的JDK 11 虽然也能跑但某些 SSH 框架的反射机制在 11 下面会有兼容问题。然后添加一个 Web Facet把WebRoot或webapp目录指定为 Web 资源目录再在 Artifacts 里选择 Web Application Exploded这样改 JSP 不用重启。然后配置 Tomcat在 Run Configuration 里添加 Tomcat Server LocalApplication server 选到你的 Tomcat 8.5 或 9.0 安装目录Deployment 菜单里把刚才建立的 Artifact 加进去Application context 填/performance。这里我建议用/performance而不是/因为后面 filter 拦截、页面跳转都会依赖这个 context path太短容易和其他项目冲突。启动前最后一项检查确认 Tomcat 端口没被占用。如果 8080 端口被别的进程占用在 Server 配置里把端口改成 8081 就行。启动后如果看到类似Deployment of web application archive ... has finished in ... ms的日志就代表部署成功了。浏览器访问http://localhost:8080/performance/login.jsp能出登录页说明基本成了。4.4 功能验证清单管理员建考核、考评人打分、员工看结果项目启动后验证功能时不要漫无目的地到处点。绩效考评系统的核心链路就那么几条按顺序走一遍能快速确认系统可用。第一用管理员账号登录创建一套考核模板模板里至少填 2 到 3 个指标并设置不同的权重。保存后把模板状态改为“已发布”。这一步验证的是模板管理流程能成功保存并看到指标列表说明数据库写入正常。第二发起一个考核任务找一个考评人一个被考评人导入或手工生成考核关系。如果系统支持批量生成任务就多生成几条后面算分时数据更真实。第三退出管理员账号换考评人账号登录在待办列表里看到刚才分配的任务进去给每个指标打分并提交。提交后检查assess_score表里是否多了对应的行行数应该等于指标数。第四换被考评人账号登录看是否能查到自己的汇总成绩。这时候你可能会发现成绩是 0 或者报错那就是第 3 章的ScoreCalculator没有被正确调用或者权重没求和回代码里查。这四步如果都走通这套 java web 绩效考评系统的核心功能就算验证完毕。接下来要处理的就是各种边边角角的报错和性能问题。5. 绩效考评系统部署与运维避坑从中文乱码到 Tomcat 10 兼容性5.1 页面和数据库全是问号字符集不一致导致的乱码现象登录后页面上的中文全是问号往数据库里插入考核指标名称表里存进去的也是?????。更奇怪的是 CSS 和页面看起来都是正常的只有从数据库取出来的中文是乱的。原因字符集问题通常出在三处中的至少一处JSP 页面没指定编码、数据库连接 URL 没带编码参数、数据库表字符集不是 utf8。最常见的是连接 URL 里characterEncodingutf8被删了或者 MySQL 的my.ini里默认字符集是 latin1。解决先把 JSP 页面头部统一改成% page contentTypetext/html;charsetUTF-8 languagejava %再把jdbc.properties里的 URL 换成jdbc:mysql://localhost:3306/performance_db?useUnicodetruecharacterEncodingutf8useSSLfalse最后确认数据库表字符集已经改成 utf8mb4执行ALTER TABLE assess_indicator CONVERT TO CHARACTER SET utf8mb4;。改完重启 Tomcat乱码问题基本消失。记住 utf8mb4 是 utf8 的超集能存 emoji绩效考评里的“非常好”这类中文完全够用。5.2 MySQL 8 连不上驱动类名过时现象Tomcat 启动后项目日志里出现ClassNotFoundException: com.mysql.jdbc.Driver或者Communications link failure但数据库明明开着账号密码也对。原因旧项目里放的是 MySQL Connector/J 5.x 的驱动包驱动类名是com.mysql.jdbc.DriverMySQL 8 之后官方驱动更名成com.mysql.cj.jdbc.Driver并且在连接 URL 中还需要追加serverTimezoneAsia/Shanghai。很多 .rar 项目依赖的 lib 里没有新驱动自然连不上。解决从 Maven 仓库下载mysql-connector-java 8.0.33的 jar 包替换掉WEB-INF/lib下的旧驱动。同时更新jdbc.properties文件jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/performance_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse改完注意删除 Tomcat 的缓存目录因为 Tomcat 会把 lib 下的 jar 锁住不清理的话总是加载旧类。这个问题的痕迹有两个启动日志里的驱动类名和 Tomcat 日志里的 ClassNotFoundException看到就基本可以确诊。5.3 Tomcat 10 下 JSP 启动报错javax 变 jakarta 的迁移问题现象用 Tomcat 10 启动项目控制台报错java.lang.NoClassDefFoundError: javax/servlet/ServletException或者一堆ClassNotFoundException: javax.servlet.http.HttpServletRequest但项目代码里明明引用了这些类。原因Tomcat 10 开始把 Java EE 的javax.*命名空间换成了jakarta.*旧项目编译时依赖的是javax.servlet-api的包运行在 Tomcat 10 里容器提供的类全变成了jakarta.*于是运行期找不到类。这不是代码写错了是容器的兼容性断裂。解决最省事的办法是换回 Tomcat 8.5 或 Tomcat 9.0旧版源码包默认就是为这两个版本准备的一般不需要改代码。如果你一定要用 Tomcat 10那就得把源码里所有import javax.servlet批量替换成import jakarta.servlet同时web.xml里的 schema 版本也要升级到 5.0 或 6.0工作量不小。我接手这类 .rar 项目第一件事就是确认 Tomcat 版本8.5 是最稳的。5.4 部署后 404Artifact 与 context path 没配对现象Tomcat 正常启动了日志也没报错但浏览器访问http://localhost:8080/performance/login.jsp时返回 404。访问http://localhost:8080/能显示 Tomcat 默认首页但切不到自己项目的页面。原因IDEA 部署 Artifact 时Deployment 里的 Application context 没有设置为/performance或者 Artifact 没有被加到部署列表里导致 Tomcat 根本不知道有这个 Web 应用。有时候还会出现 Tomcat 目录下 webapps 里没有项目文件夹因为 IDEA 是直接用 Exploded Artifact 部署的不会复制到 webapps。解决打开 Run Configuration找到 Deployment 选项卡确认里面有这个 Artifact并在 Application context 里填/performance。然后打开WEB-INF/web.xml查看servlet-mapping里配置的路径比如登录 Servlet 的 URL 是/login那访问路径就是http://localhost:8080/performance/login。404 问题首先要区分是 Tomcat 没部署还是路径写错检查 Tomcat 的conf/Catalina/localhost目录如果启动了部署这里会生成一个performance.xml临时文件没有这个文件说明根本没部署上。5.5 导出 Excel 报错poi 版本与 JDK 不兼容现象管理员点击“导出考核结果”按钮页面报 500 错误控制台输出java.lang.NoSuchMethodError指向 POI 的某个方法比如setCellValue或createRow。原因源码包里带的 Apache POI 版本太低比如 3.9用的 JDK 8 编译后调用某些新方法时不存在或者项目里同时存在多个 POI 版本类互相冲突。绩效考评系统导出 Excel 几乎都用 POI这个问题非常普遍。解决确认当前 JDK 版本如果用的是 JDK 8直接换 POI 4.1.2这个版本对 JDK 8 最友好如果非要留在 JDK 7那就只能继续用 POI 3.17。替换办法是把WEB-INF/lib下的旧poi-*.jar删干净放一个poi-4.1.2.jar、poi-ooxml-4.1.2.jar和对应的commons-collections4依赖进去。如果项目里用的是 Maven就在pom.xml里改成上面的版本号然后强制更新依赖。这个报错的规律是它往往不在导入数据时发生而是发生在点击导出按钮那一瞬间因为 POI 类是在生成 Excel 时才第一次被加载。6. 让这套绩效考评系统更顺手用一份模拟数据做回归验证的最小改造6.1 用 SQL 脚本造一套假数据验证考核结果跑通之后最值得做的事是造一批可控的模拟数据来验证算分逻辑有没有被改坏。比如给同一个被考评人安排 5 个考评人分数从 60 到 100 各不同然后用 SQL 算一遍期望值再和页面显示值对比。如果对不上说明ScoreCalculator的去最高最低分逻辑或权重计算有偏差。我常用的一条验证 SQL 长这样SELECT task_id, AVG(score) AS raw_avg FROM assess_score GROUP BY task_id;这条 SQL 算出来的是包含所有打分的普通平均分在页面里的分数应该比这个平均分略有不同因为代码去掉了最高最低分。如果你发现两边完全一样就去检查一下ScoreCalculator是否真的执行了去极值逻辑八成是条件scores.size() 3刚好触发了测试数据太少导致走了平均分分支。这也是为什么要造 5 条以上打分数据的原因。6.2 小步改造把 Servlet 接口保留前端换成 Ajax 调用老项目的 JSP 页面每次打分都要整页刷新体验差且调试麻烦。你可以做一个小幅改造不动后端 Java 结构只把打分表单的提交改成 Ajax这样在验证绩效数据时不用反复刷新页面也方便抓取返回的 JSON 排查问题。前端改造成这样fetch(contextPath /score/submit, { method: POST, headers: { Content-Type: application/x-www-form-urlencoded }, body: new URLSearchParams(new FormData(document.querySelector(#scoreForm))) }) .then(resp { if (!resp.ok) throw new Error(提交失败); return resp.text(); }) .then(() { window.location.href contextPath /assessor/pending.jsp; }) .catch(err console.error(err));后端要做的最小改动是把ScoreSubmitServlet返回值从sendRedirect改成直接写一串文本比如{code:0,msg:ok}这只需要在doPost里加几行resp.setContentType(application/json;charsetutf-8)和resp.getWriter().write(...)即可。改完记住要把原来的sendRedirect注释掉或删掉否则 Ajax 拿到的还是一个重定向页面而不是 JSON。我给自己定的习惯是凡是接手 .rar 源码先按最小链路跑通再决定要不要动手重构。这套绩效考评系统跑起来不难难的是你别一上来就一头扎进代码里改样式、换框架先让它稳定跑两天用模拟数据验证核心算分再谈优化。这么多 Java Web 项目做下来我发现翻车最狠的多数不是业务逻辑而是环境问题和乱改依赖。希望帮到你。本文还有配套的精品资源点击获取