
一次搞懂学院教学工作量统计系统怎么做——基于Java Vue的前后端分离实践每年到了期末教务处催着要工作量统计表教学秘书加班到深夜对着Excel核对课时、系数、跨学院借课记录任课教师反复修改申报数据院领导想要一张汇总图却只能看到一堆流水账……这不是个例而是很多高校二级学院都在经历的痛。我自己接过一次这种需求最后落地的方案就是一套基于Java Vue的学院教学工作量统计管理系统包含完整的源码、数据库脚本和配套文档。今天把整个项目的思路、架构、表结构设计、核心实现和部署过程拆开讲清楚希望能帮到正在被教学工作量统计折磨的同行也给准备做JavaWeb课程设计或毕业设计的同学一份能直接参考的项目范本。这套系统做出来能解决什么简单说就是让教师在网页端自主填报教学工作量教学秘书在线审核系统按学院设定的计算规则自动算分、按职称/课程类型/学期自动汇总院领导和教务处能随时查看统计报表并导出Excel。解决的核心痛点是手工Excel汇总容易出错、计算规则不透明、历史数据不好追溯。适合谁看第一是高校里负责教学管理但不懂技术的行政人员你可以通过这篇文章了解这类系统应有的功能和数据逻辑第二是正在做JavaWeb项目的学生开发者本文会给出数据库表结构、核心代码思路和部署教程可以直接参考复用。1. 需求拆解工作量统计系统到底难在哪1.1 不要急着写代码先把计分规则谈明白这是我做这个项目踩过最深的坑。教学工作量统计表面上看就是一个“填表 汇总”的事情但真正跟教学秘书聊完才发现规则远比想象中复杂。以我们学院为例工作量分为理论教学、实验教学、实践环节课程设计、实习实训、毕业设计指导、竞赛指导、第二课堂等六大类。每一类对应的计算方式都不一样而且和课程性质、教师职称、合班人数都有关系。比如理论课是“学时数 × 课程系数 × 人数系数”其中课程系数和课程性质有关公共基础课是1.0专业核心课是1.1双语课是1.2新开课再乘1.1人数系数则是按修读人数分档35人以下为1.036到70人为1.170人以上为1.2。实验课又不一样是按“学时数 × 0.8 × 人数组数系数”来算的。这里必须要强调一个实践原则技术方案可以先定但工作量计算规则必须在开工前用书面文档确认下来。我当时把规则做成了一张Excel表发给教学秘书确认来回改了三版才定稿。因为规则一旦做进系统后面想改计算逻辑虽然不难但涉及历史数据的重新计算人员和关联表都得跟着动麻烦远比想象中大。所以接这类项目第一步不是建工程而是确认规则文档最好让学院签字或至少用邮件确认。1.2 参与角色与核心流程梳理系统不复杂但角色权限必须清晰。我梳理下来一共有五种角色教师普通用户在线填报工作量、查看自己的历史记录和审核状态。教研室主任对本教研室教师提交的数据进行一级审核确认无误后提交到院级。教学秘书院级审核也是这个系统最核心的管理员可以导入教学任务书、管理课程库、维护计算系数、处理调整申请。院领导只看统计报表可以按教师、课程、学期多维查看导出数据。系统管理员负责用户管理、字典维护、日志查看。核心流程是教师填报 → 提交后锁定 → 教研室主任审核退回则教师修改后重新提交→ 学院审核 → 计算工作量分值时数 → 汇总入月度/学期统计 → 导出报表。这里有个设计要点提交后必须锁定教师不能随意改。如果教学秘书发现数据有问题需要走“退回”流程教师修改后再重新提交这样每一步都有操作记录出了问题可以追溯学期末对账的时候不用扯皮。系统中我用了一个状态字段去管理值分别是0草稿、1待审核、2审核通过、3已退回、4已汇总每个状态变更都写审计日志。1.3 隐藏需求历史数据迁移与导入导出还有一个容易忽略的需求老系统的历史数据怎么办我们学院之前用Excel管理了五年的数据如果新系统不支持导入教学秘书会直接崩溃。所以在设计阶段就明确了系统需要支持Excel导入教师信息、教学任务书以及导出任意学期任意教师的工作量明细和汇总表。这里我用的是EasyExcel大文件导入不卡、好控制格式后面实操部分会展开讲。2. 技术选型为什么是Java Vue有哪些替代方案2.1 后端用JavaSpring Boot的理由很多人纠结技术选型尤其是做课程设计的学生总觉得用什么框架都行。我个人的建议是如果项目要求是JavaWeb那就老老实实用Spring Boot MyBatis Plus别整花活。为什么第一Spring Boot生态成熟相关的博客、解决方案一搜一大把遇到问题好查第二教学工作量系统的核心是权限控制和报表计算Spring Security Sa-Token这种成熟的方案能省很多事第三学校里的机房服务器大多是Windows Server JDK环境Spring Boot打一个jar包就能跑部署简单。MyBatis Plus比MyBatis更省事的地方在于代码生成器和条件构造器。比如需求是“查询某学期所有状态为待审核的记录按教师和课程分组”用MyBatis Plus的QueryWrapper几行就写完了不用手写一堆SQL映射。当然复杂统计我还是建议写原生SQL比如后面要讲的工作量汇总报表多表关联加分组用注解或XML写SQL会更直观。2.2 前端用Vue而不是JSP的原因这个项目的早期版本我考虑过用JSP Bootstrap后端渲染页面一个Tomcat直接跑完。后来放弃了原因是这个系统有个特点交互比较多。教师填报时动态增删行、选课程后自动带出学时和人数、教学秘书审核时前后对照、领导看图表……这些交互如果用JSP前后端代码耦合在一起后段返回一堆HTML改起来很痛苦。Vue刚好解决这个问题。我用的技术栈是Vue 2 Element UI Axios ECharts。为什么不用Vue 3因为Element UI对Vue 2的支持最成熟网上组件示例最多对不熟悉前端的人来说复制粘贴能跑通最重要。如果你熟悉Vue 3用Element Plus也一样原理相同。Vue负责页面渲染和用户交互后端只管提供JSON接口两者通过Axios通信分工明确。2.3 数据库选型和整体架构数据库选了MySQL 8.0InnoDB引擎utf8mb4字符集。字符集必须用utf8mb4别用utf8不然教师填写一些生僻字或者表情符号入库就乱码。整体架构是标准的单体应用 前后端分离前端Vue 2 Vue Router Vuex Element UI Axios ECharts打包为静态资源部署时让Nginx托管静态页面同时代理/api路径到后端。后端Spring Boot 2.7 MyBatis Plus Spring Security JWT EasyExcel Hutool工具类包打包为可执行jar。数据库MySQL 8.0核心表有用户表、角色表、权限表、课程表、教学任务表、工作量申报表、工作量明细计算表、审核记录表、系数规则表。部署形态在服务器上安装Nginx和JDK8前端dist目录丢给Nginx后端jar包用systemd或直接java -jar方式运行。这个架构的好处是前端和后端可以分别开发、分别升级接口文档用Swagger自动生成联调的时候很方便。坏处是需要处理跨域但Spring Boot配置一个CorsFilter就解决了后面会讲。3. 数据库设计八个核心表一次讲透3.1 用户权限模型这套系统的用户权限采用的是RBAC基于角色的访问控制模型一共三张表用户表、角色表、用户角色关联表。用户表的核心字段包括user_id、username、password、real_name、title职称因为工作量计算和职称挂钩、department_id、phone、email、status。角色表就很简单role_id、role_name、role_codeTEACHER/LEADER/DEAN/ADMIN。用户角色关联表建立多对多关系。有人会问为什么不用Spring Security里那套完整的五表模型用户、角色、权限、用户角色、角色权限因为这里的权限控制到角色级别就够了工作量系统操作权限就那么多不需要搞按钮级别的细粒度控制。如果后续需要加角色权限表也不难但在当前阶段属于过度设计。3.2 核心业务表结构课程表 coursecourse_id、course_name、course_code、course_type公共基础/专业核心/专业选修/实践性课程、credit学分、hours计划学时、is_new是否新开课、is_bilingual是否双语课、department_id、status。课程数据怎么来的两种情况——学校教务系统有接口就直接同步过来没有接口就得人工维护我做的版本是先提供一个Excel导入模板教学秘书每学期把教学任务书导入进来系统自动匹配和新增课程。教学任务表 teaching_task这是教学任务书导入后生成的数据字段有task_id、course_id、teacher_id主讲教师、class_name教学班级、student_count修读人数、actual_hours实际上课学时、semester学期格式为2024-2025-1、task_type理论/实验/实践。这张表的意义在于教师填报工作量的候系统用“课程 班级”去匹配教学任务书自动带出学时和人数不需要教师手动填写减少出错。工作量申报表 workload_record这是系统的核心表teacher_id、semester、record_type申报类型、course_id、task_id、claim_hours教师申报的学时数、status0草稿/1待审核/2通过/3退回、declared_at、reason教师备注。每一行就是教师申报的一条记录。工作量计算明细表 workload_detail这张表用于保存计算过程。因为计算结果需要可解释、可追溯不能只存一个数字。字段有detail_id、record_id、workload_hours最终折算学时数、points工作量分值、calc_process计算过程说明比如“32学时 × 课程系数1.1 × 人数系数1.1 38.72”、calc_type、calc_status、calculated_at。工作量系数规则表 workload_rulerule_id、rule_typeCOURSE_TYPE/STUDENT_COUNT/TITLE/SPECIAL、rule_name、rule_value系数值或分值、condition_desc适用条件说明、effective_semester_start、effective_semester_end、status。比如公共基础课系数1.0就存一条记录condition_desc写“课程类型公共基础”。审核记录表 audit_loglog_id、record_id、auditor_id、audit_actionSUBMIT/PASS/REJECT/RETURN、audit_comment、audited_at。所有状态流转都记录在这里。3.3 关于工作量计算的两点设计经验第一计算逻辑不要写死在业务代码里要配置化。工作量规则不是永远不变的可能隔一两年就调整一次。我第一版把计算规则写死在service里后来规则一改就要改代码、重新打包发布很麻烦。第二版做成了数据表配置改系数直接改数据库记录系统立即可见。虽然改计算逻辑本身还是需要发版但改系数这种高频操作就不用找程序员了。第二明细要保存下来不要只存一个最终值。学期末对账的时候教学秘书经常要回答“这个老师的工作量到底怎么算出来的”这类问题。如果数据库里只存一个总数这时候就傻眼了。把计算过程拼成一段文字存在calc_process字段里导出Excel时直接能展示谁算的都清清楚楚。这可能不符合数据库规范化的要求同样的数据其实可以由其他字段计算得到但在这种管理系统中保存冗余的计算解释字段非常有价值。4. 核心功能实现从登录到报表导出4.1 基于JWT的登录认证和权限拦截系统前后端分离Session那一套不好使了其实也能用但跨域处理起来麻烦所以我用的JWT令牌。后端用Sa-Token框架做的登录认证和权限校验比Spring Security配置简单很多。具体流程是用户输入用户名密码后端校验通过后生成一个JWT令牌里面包含用户ID、角色编码、过期时间返回给前端。前端把令牌存到localStorage每次请求在请求头里带上Authorization: Bearer token。后端用一个拦截器拦截需要认证的请求校验令牌合法性和有效期然后根据角色编码判断是否有权限访问。这里有几个细节值得说密码不能明文存储用BCrypt加密。即使用户密码泄露也不能直接看到明文。JWT的有效期我设置的是2小时前端Axios封装了自动刷新令牌的逻辑检测到401就调刷新接口。这个虽然后面写起来有点模板代码但体验比让用户反复登录好太多。拦截器的白名单要配置好登录接口、验证码接口、Swagger文档路径必须放行否则开发阶段调试接口都会被拦截很烦。4.2 教师填报模块自动带出学时和人数教师填报是系统使用频率最高的功能体验一定要做好。我的实现思路是教师点击“新增申报”后选择学期和课程课程下拉框来自教学任务表只显示当前教师主讲的课程选择课程后系统自动带出该课程的教学班级、修读人数、计划学时。教师只需要确认数据无误、填写备注然后提交。这里的关键点是用Axios级联请求实现联动。前端监听课程选择的变化向后端发送请求获取课程详情回填到表单中。后端对应接口的逻辑是根据course_id和teacher_id去teaching_task表查询返回给前端。填报过程中有一个防呆设计如果系统检测到当前教师已经对该课程填写过申报状态为草稿会提示“该课程已有未提交的申报记录是否继续新增”。这个防止了老师重复填报我们做的时候就有老师反映以前用Excel总是同一个课程填两遍。工作量申报和教学任务书匹配不上的情况也要处理。比如临时接课、代课教学任务书里没有记录这时候教师可以在表单中勾选“无教学任务书”然后手动输入课程名称、学时和人数。这种记录在审核的时候会标记为“特殊申报”教学秘书需要重点核对。4.3 审核模块流程状态流转审核模块分两级教研室主任审核和教学秘书审核。按照我们的流程教师提交后状态变1待审核教研室主任可以勾选通过或退回通过的记录状态变2进入院级待审核池退回的记录状态变3教师修改后重新提交回到待审核。这个模块的技术难点不多主要是状态机要控制好不允许跳状态。比如教师不能把状态为2的记录直接改成1所以但凡是修改状态的操作后端都会校验当前状态和目标的流转关系是否合法。我用一个Map保存了允许的流转路径0→1、1→2、1→3、3→1非法流转直接抛异常。另一个贴心的小功能是批量审核。一个教研室几十份申报让主任一个个点开看再审核不现实。所以我的列表页支持多选点“批量通过”后会显示一个弹窗列出所有待审核记录的概要信息填一条审核意见后统一处理。这个功能教学秘书反馈很好用。4.4 工作量自动计算策略规则引擎的思路工作量计算是整个系统的“技术心脏”。我的实现方式是工作量和申报记录通过状态字段关联当记录通过审核之后系统自动触发生成工作量明细。具体实现时我在工作量计算Service中按照以下顺序处理根据课程类型查workload_rule表得到课程系数course_factor如公共基础课1.0。根据修读人数查人数系数student_factor35人以下1.036~70人1.170人以上1.2。根据是否新开课、是否双语课追加系数新开课乘1.1双语课乘1.2。如果是实验课直接使用实验课算法实际学时数×0.8×实验组数系数。将最终折算学时数workload_hours写入工作量明细表同时生成calc_process的说明文本。这里有一个思路值得展开我把计算逻辑拆成了“计算算式”和“计算因子”两个部分。计算因子从规则表查计算算式写在代码里。为什么算式不也做成配置因为教学场景中算式的变化频率远低于因子的变化频率课程系数、人数系数这些经常调整而“学时 × 课程系数 × 人数系数”这个基本公式在短期内不会变。把规则表做得好用已经把80%的灵活性给出来了。教师职称会影响计分标准比如教授和讲师算出的工作量分值不同。这个也走规则表workload_rule表里存了按职称区分的idle_factor分值转换率比如教授每1学时折算1.2分副教授1.1分讲师1.0分。4.5 统计报表与Excel导出报表是院领导最关心的功能。我做了三个维度的统计页面按学期统计展示该学期全院总工作量、各教学类型工作量占比、人均工作量排名用ECharts画柱状图和饼图。按教师统计点击教师名字查看其工作量明细列表和审核状态。按课程统计查看某门课程在不同学期的申报情况和工作量对比。Excel导出我用的是EasyExcel。很多人会用POI但POI需要写大量样板代码而且大数据量容易内存溢出。EasyExcel封装得很好用注解就能定义导出列。代码大致是这样// 导出Excel文件的Servlet接口 GetMapping(/export) public void exportWorkload(HttpServletResponse response, RequestParam String semester) { ListWorkloadDetailVO list workloadService.getExportData(semester); try { response.setContentType(application/vnd.ms-excel); response.setCharacterEncoding(utf-8); String fileName URLEncoder.encode(教学工作量_ semester, UTF-8).replaceAll(\\, %20); response.setHeader(Content-disposition, attachment;filename*utf-8 fileName .xlsx); // 输出到HTTP响应流前端浏览器会弹出下载框 EasyExcel.write(response.getOutputStream(), WorkloadDetailVO.class).sheet(工作量明细).doWrite(list); } catch (IOException e) { log.error(导出工作量数据失败, e); } }要点是fileName需要做URL编码否则中文文件名下载下来全是乱码如果文件名里有空格要替换成%20不然有些浏览器会截断。4.6 数据看板让领导一眼看懂大屏展示或者说数据看板是加分项但实际做下来并不复杂。我在首页做成统计看板用ECharts展示几个关键指标本学期已审核工作量总额、待审核申报数量、跨学院借课占比、最近6个月工作量趋势图。数据来源就是后端封装好的统计接口返回JSON数组前端直接用ECharts的setOption渲染。一开始我想做得花哨一点后来发现用户其实只需要一目了然看几个数字加趋势图。做完了教学秘书说领导看了一次就记住了比年初汇报时的PPT还直观。这就是真实的使用场景很多管理系统不需要多炫酷数据清晰、交互简单才是硬道理。5. 项目部署与实战细节从零到能跑5.1 开发环境准备开发时我用的环境是JDK 1.8Maven 3.6.3Node.js 14.16.0Vue 2 Element UI 对Node版本要求不高16也行MySQL 8.0IDE后端IDEA前端VS Code开发时前后端分离跑前端npm run serve启动在8080端口后端Spring Boot启动在8081端口前端通过vue.config.js里配置的代理把/api开头的请求转发到8081从而规避了开发环境的跨域问题。这一步很关键如果不在开发环境配代理调试时每次请求都要处理CORS非常麻烦。5.2 后端关键配置application.yml里几个核心配置server: port: 8081 servlet: context-path: /api spring: datasource: url: jdbc:mysql://localhost:3306/workload_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0值得提醒的是MySQL连接URL里要加serverTimezoneAsia/Shanghai否则8.0的驱动会报时区错误url里加上characterEncodingutf8是为了避免中文乱码useSSLfalse是因为本地开发没有配置SSL证书不加这个会有警告日志。5.3 前端构建与部署前端开发完了执行npm run build生成dist目录里面是编译好的HTML、JS、CSS文件。部署的时候我采用Nginx托管静态文件并反向代理接口。一个比较完整的Nginx配置如下server { listen 80; server_name your-domain.com; # 前端静态文件 location / { root /opt/workload-system/dist; index index.html; try_files $uri $uri/ /index.html; # 解决Vue路由history模式刷新404 } # 后端API反向代理 location /api/ { proxy_pass http://localhost:8081; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # 上传文件大小限制导入教学任务书可能比较大 client_max_body_size 50m; }有一个点要专门提醒Vue Router如果用的是history模式路由地址不带#部署时必须在Nginx里配置try_files规则否则刷新页面会404。用hash模式就不用配但地址栏会多个#号看起来比较丑所以我还是建议用history模式配合try_files。5.4 初始化数据的准备系统做完了要能直接用不能交给用户一个空库。我在数据库脚本里预置了管理员账号admin/admin123、五类角色的测试账号、部分课程数据和几套典型的工作量系数。这样用户拿到项目后导入SQL脚本登录进去就能看到一个有数据的系统而不是空荡荡的界面。这些种子数据对开发阶段的联调也很有用。我调试工作量计算的时候就用初始化的数据在界面上跑了一遍核对计算结果跟人工算的是否一致确认无误后才交出去。6. 常见问题与排错实录踩过的坑都在这6.1 中文乱码问题这个项目里中文乱码可能出现三处数据库层面、后端返回JSON乱码、Excel导出文件名乱码。数据库层面的解决方案是建库时指定utf8mb4字符集如CREATE DATABASE workload_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;连接URL加characterEncodingutf8。后端返回JSON乱码确认一下Spring Boot是不是设置了server.servlet.encoding.forcetrue有时候需要强制一下。Excel导出的文件名乱码用上面代码里的URLEncoder编码方式可以解决。6.2 后端接口报404或CORS错误现象是前端页面能打开但一请求数据就报跨域或者404。我排查过几次大部分情况下是配置问题而非代码问题确认前端的代理配置是/api还是/api/这两种写法代理的路径匹配方式不一样。确认后端应用的context-path是不是/api如果Nginx已经把/api路径做为代理标识转发而后端又设置了/api前缀请求路径就会变成/api/api/xxx这种情况后端就会全部404。跨域问题确认Spring Boot的CorsFilter有没有生效或者Nginx的代理头是否正确。6.3 批量导入教学任务书总是校验失败EasyExcel导入Excel时我做了两种校验非空校验和格式校验。教学秘书反映总是导入失败查看日志后发现Excel里有些列名和实体类不一致比如表头写的是“教师姓名”而实体类映射的是“任课教师”EasyExcel读不到列就会把整行跳过。解决方案是在导入模板上做一个严格的模板下载入口把列名规范好、单元格格式设好比如“学期”必须是“2024-2025-1”这种字符串格式。另外在导入逻辑中不要一遇到错误就中断而是收集所有错误行返回一个错误信息列表给前端提示“第3行教师姓名不能为空第5行修读人数必须为数字”这样教学秘书改起来方便很多。6.4 导出Excel内存溢出在大批量讲师一年数据超过几千行导出的时候POI的XSSFWorkbook非常吃内存很容易OOM。如果你用的是POI的SXSSFWorkbook方式问题也不大但我后来还是换成了EasyExcel因为它的流式读取和写入在性能上确实好很多内存占用低而且API简单。6.5 工作量计算核对不上这是我个人最推荐的排查方法在开发阶段先用一组已知数据手工算一遍预期结果再拿系统计算的结果对比。比如某老师上的是公共基础课计划学时32修读人数50人系数1.1系统预期结果应该是32×1.0×1.135.2但系统算出来是35.2还是38.72如果不对就去看calc_process字段它会告诉你计算过程是“32学时×课程系数1.0×人数系数1.135.2”。这样很快能定位是哪个因子查错了还是算式写错了。6.6 前端Vue打包后资源路径错误项目做完后我遇到过这样一个问题本地npm run serve一切正常但npm run build部署之后页面白屏打开控制台发现JS和CSS的路径全是绝对路径/js/chunk-xxx.js在子路径部署时找不到文件。解决方法是修改vue.config.js里的publicPath为./让打包后的资源使用相对路径。如果你把dist目录放在Nginx的根路径下用默认的/也没问题但如果放在子目录、或者像校园网内网一样用二级路径访问就必须改成相对路径。7. 最终总结和经验建议这套系统从需求调研到正式上线前后花了我大概三周时间。第一周业务梳理和数据库设计第二周前后端开发第三周联调测试、部署和写文档。如果按时间划分业务梳理和规则确认占的时间比例比写代码要多得多这点我希望想要复现这个项目的人能提前有心理准备。最后分享几个我从这个项目中积累的个人经验第一做管理系统花20%的时间写代码花80%的时间确认需求绝对不过分。尤其是教学工作量这种和钱课时费、和职称评审挂钩的系统规则只要错一丁点用户就会失去信任后面怎么优化都显得没有说服力。第二Excel导入导出这种功能看起来不起眼但实际上岗率最高的功能就是它。教学秘书和老师们用Excel用了很多年你要是不能让他们方便地从Excel往系统里倒数据或者把系统数据导回Excel这个系统就很难真正用起来。第三工作量和计算过程一定要留痕。系统上线后几乎每个月都有老师来问“我这个月工作量为什么比上个月少了”之类的问题如果没有审核记录和计算过程说明双方就会开始扯皮。现在用了审计日志和calc_process字段直接查出来给老师看有理有据系统使用阻力就小很多。这个项目的扩展方向其实也很多比如对接学校的统一身份认证CAS/OAuth2、对接教务系统自动获取任务书、增加工作量预警申报可能遗漏时提醒教师、审核超时提醒教学秘书、通过钉钉或企业微信发送审核通知等。如果后续还需要做这些当前的架构和表结构设计基本都能平滑支持不会推翻重来。如果不是手里这个项目真的上线跑了一学期我不会想到一个“看起来只是填表”的系统里面会藏这么多门道。希望这篇分享能帮你少走一些弯路无论是作为课设题目还是真实需求都能做出一套能用、好用、肯用的教学工作量统计系统。