Java工时管理系统源码解析:数据模型、Spring Boot分层与二次开发实战
2026/9/23 17:08:52 网站建设 项目流程

简介:这是一套基于 Java 的轻量级项目管理工具源码,项目代号 oak project,面向需要自建项目协作平台的中小团队与开发者,重点解决工时统计、原型分发和效果图管理三类协作痛点。工时模块支持员工上报,实时记录项目投入,便于核算人工成本;原型模块通过链接直接浏览,省去文件互传与第三方托管,并带版本管理;效果图模块同样支持在线分发与查看。源码采用前后端分离架构,服务端为 SpringBoot,前端为 Vue,运行环境为 JDK 1.8 与 MySQL 8。压缩包共 851 个文件,约 4.16MB,其中 385 个 java 文件承载后端业务逻辑,132 个 vue 与 113 个 js 文件构成前端页面与交互,另有 32 个 xml、6 个 sql 及多份 yml 配置,便于快速部署与二次开发。目前已有 1028 人学习下载,适合作为毕业设计、企业内训或自研项目管理平台的参考实现。

1. 拿到一份 Java 工时管理系统源码,先别急着改代码

很多团队在选型工时管理系统时,第一反应是找现成的 Java 项目源码,下载下来跑起来看看。但真正落地过的人都知道,一份能跑的源码和一份能用的系统之间,差的是对业务模型和数据结构的理解。工时管理系统的核心不是增删改查,而是「谁在什么项目上花了多少时间、这些时间怎么归集、怎么被审批、怎么被统计成报表」这条链路。如果一上来就改前端页面或者调接口,后面大概率会陷入字段对不上、审批状态乱掉的困境。

这份源码适合三类人:一是需要快速搭建内部工时填报平台的研发团队,二是想拿一个完整 Spring Boot 项目练手的中高级开发者,三是需要在此基础上做二次开发、对接考勤或项目管理系统的集成方。它解决的是工时从填报到审批再到统计的闭环问题,涉及用户权限、项目关联、工时记录、审批流和报表导出几个模块。理解这套模型,比记住某个类的名字重要得多。

2. 工时管理系统的数据模型与 Spring Boot 分层设计

2.1 核心实体关系:用户、项目、工时记录、审批单

工时管理系统的数据模型决定了后续所有功能的实现难度。常见做法是四张核心表:用户表、项目表、工时记录表、审批单表。用户和项目是多对多关系,通过项目成员表关联;工时记录表关联用户和项目,记录某人在某天某个项目上投入的小时数和工作内容;审批单表则把一组工时记录打包,走审批流程。

表名关键字段说明
sys_userid, username, dept_id, role用户与角色,角色决定审批权限
projectid, name, manager_id, status项目基本信息与负责人
project_memberproject_id, user_id, join_date项目成员关系,控制填报范围
worklogid, user_id, project_id, work_date, hours, content工时明细,hours 建议用 decimal
approvalid, user_id, period, status, approver_id按周期打包审批,status 控制流转

这里有个容易踩的坑:工时记录的 hours 字段如果用 float 或 double,统计月度工时时会出现 0.30000000000000004 这类精度问题。我一般会用 decimal(5,2),Java 侧对应 BigDecimal,避免报表对不上。另一个坑是审批状态的设计,不要用布尔值表示「已审批/未审批」,因为实际业务里还有「驳回」「撤回」「待审批」等状态,用枚举或字典表更稳妥。

2.2 Spring Boot 分层:Controller、Service、Mapper 的职责边界

拿到源码后,先看包结构。典型的 Spring Boot 项目会按 controller、service、mapper、entity、dto 分层。工时管理系统的业务逻辑集中在 Service 层,比如「提交工时审批」这个操作,需要校验工时是否属于当前用户、是否在项目有效期内、是否重复提交,然后生成审批单并更新工时状态。这些校验如果写在 Controller 里,后续加审批流会非常痛苦。

@Service public class WorklogService { @Autowired private WorklogMapper worklogMapper; @Autowired private ApprovalMapper approvalMapper; @Transactional public void submitApproval(Long userId, String period) { // 查询该用户该周期内未提交的工时 List<Worklog> logs = worklogMapper.selectUnsubmitted(userId, period); if (logs.isEmpty()) { throw new BizException("没有待提交的工时"); } // 校验工时是否属于有效项目 for (Worklog log : logs) { if (!projectMemberMapper.exists(log.getProjectId(), userId)) { throw new BizException("项目 " + log.getProjectId() + " 不在您的填报范围"); } } // 生成审批单并批量更新工时状态 Approval approval = new Approval(); approval.setUserId(userId); approval.setPeriod(period); approval.setStatus(ApprovalStatus.PENDING); approvalMapper.insert(approval); worklogMapper.batchUpdateStatus(logs.stream().map(Worklog::getId).collect(Collectors.toList()), WorklogStatus.SUBMITTED, approval.getId()); } }

这段代码的关键点在于@Transactional保证审批单生成和工时状态更新在同一个事务里,避免出现审批单创建了但工时没更新、或者反过来。参数period一般用「2025-06」这种格式,方便按月归集。selectUnsubmitted的 SQL 里要带上status = 'DRAFT'条件,否则会把已提交的工时重复打包。

2.3 用 MyBatis 做工时统计查询的 SQL 写法

工时统计是这类系统里 SQL 最重的地方。常见的需求有:按项目统计月度总工时、按人员统计工时分布、按部门汇总。这些查询如果直接用 MyBatis 的 XML 写,要注意索引和分组字段。

<select id="sumHoursByProject" resultType="map"> SELECT p.name AS projectName, SUM(w.hours) AS totalHours, COUNT(DISTINCT w.user_id) AS memberCount FROM worklog w JOIN project p ON w.project_id = p.id WHERE w.work_date BETWEEN #{startDate} AND #{endDate} AND w.status = 'APPROVED' GROUP BY p.id, p.name ORDER BY totalHours DESC </select>

这里status = 'APPROVED'很关键,只统计已审批的工时,草稿和待审批的不计入。work_date上要建索引,否则数据量上来后按月查询会全表扫描。如果系统支持跨天填报,还要考虑work_datehours的拆分逻辑,常见做法是允许一天填多条记录,每条对应不同项目。

3. 本地跑通源码:环境配置、数据库初始化与启动排错

3.1 Java 环境与 Maven 依赖的版本对齐

拿到源码后第一步是看 pom.xml 里的 Java 版本和 Spring Boot 版本。常见报错「源发行版 17 需要目标发行版 17」就是本地 JDK 版本和 pom 里配置的不一致。我一般会先确认三件事:本地java -version输出的版本、JAVA_HOME指向的路径、pom.xml 里<java.version>的值。三者必须一致。

# 查看当前 Java 版本 java -version # 查看 JAVA_HOME echo $JAVA_HOME # 如果版本不对,临时切换(以 JDK 17 为例) export JAVA_HOME=/usr/lib/jvm/java-17-openjdk export PATH=$JAVA_HOME/bin:$PATH

如果 pom 里用的是 Spring Boot 3.x,那 JDK 最低要求是 17;如果是 2.7.x,JDK 8 也能跑。不要强行用 JDK 8 去编译 Spring Boot 3 的项目,会报一堆Unsupported class file major version错误。Maven 版本建议用 3.8 以上,避免依赖下载时的协议问题。

3.2 数据库初始化:建表脚本与初始数据

源码里一般会有schema.sqldata.sql,或者一个doc/sql目录。先看建表语句里的字符集和引擎,工时管理系统涉及中文项目名和工作内容,字符集用utf8mb4,引擎用 InnoDB。初始化顺序是先执行建表脚本,再执行初始数据脚本。

-- 创建数据库 CREATE DATABASE worklog_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; -- 导入建表脚本 USE worklog_db; SOURCE /path/to/schema.sql; -- 导入初始数据 SOURCE /path/to/data.sql;

初始数据里通常有一个管理员账号和几个测试项目。登录后先别急着填工时,去「项目管理」里确认项目状态是「进行中」,否则填报时会提示项目不可用。如果源码里没有提供初始数据,至少要手动插入一个管理员用户,密码字段注意看是明文还是 BCrypt 加密。

3.3 启动报错排查:端口占用、数据源连接、跨域配置

启动时最常见的三个报错:端口被占用、数据源连不上、前端跨域。端口占用改application.yml里的server.port即可。数据源问题先检查spring.datasource.url里的数据库名、用户名、密码,以及 MySQL 是否允许远程连接。

server: port: 8080 spring: datasource: url: jdbc:mysql://localhost:3306/worklog_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver

跨域问题如果前端是单独启动的,需要在后端加CorsConfig或者在 Controller 上加@CrossOrigin。注意serverTimezone要设成Asia/Shanghai,否则工时日期会差 8 小时,导致「今天填的工时跑到昨天去了」。

提示:如果启动时提示Table 'worklog_db.sys_user' doesn't exist,说明建表脚本没执行或者执行到了错误的数据库,先确认USE语句和连接 URL 里的库名一致。

4. 二次开发实战:审批流扩展与工时报表导出

4.1 从单级审批扩展到多级审批的改造点

很多源码只做了单级审批,即员工提交、项目经理审批。实际业务里可能需要部门经理再审批,或者金额超过阈值时升级审批。改造的核心是把审批单表加一个current_level字段,再建一张审批节点表记录每一级的审批人和状态。

public enum ApprovalLevel { PROJECT_MANAGER(1, "项目经理"), DEPT_MANAGER(2, "部门经理"), HR(3, "人事"); private final int level; private final String desc; // 构造方法和 getter 省略 }

审批流转时,根据current_level找到对应的审批人,审批通过后current_level + 1,直到最后一级通过则整单状态变为APPROVED。驳回时把状态置为REJECTED,并记录驳回意见。这里要注意并发问题,两个审批人同时操作同一单时,用乐观锁(版本号字段)或者UPDATE ... WHERE status = 'PENDING'来保证只有一个人能成功。

4.2 用 EasyExcel 导出月度工时报表

报表导出是工时管理系统的刚需。用 EasyExcel 比 POI 更省内存,写法也更简洁。先定义导出对象,用@ExcelProperty注解表头。

@Data public class WorklogExportVO { @ExcelProperty("员工姓名") private String userName; @ExcelProperty("项目名称") private String projectName; @ExcelProperty("工时日期") private String workDate; @ExcelProperty("工时数") private BigDecimal hours; @ExcelProperty("工作内容") private String content; }

导出时从数据库查出已审批的工时记录,转换成 VO 列表,然后调用EasyExcel.write(outputStream, WorklogExportVO.class).sheet("工时明细").doWrite(list)。注意hours用 BigDecimal,导出到 Excel 后保持两位小数。如果数据量超过 10 万行,建议分页查询后分批写入,避免 OOM。

4.3 工时统计接口的性能优化:缓存与索引

月度统计接口在数据量大时容易变慢。除了在work_dateproject_iduser_id上建联合索引,还可以把统计结果缓存到 Redis,设置 5 分钟过期。缓存 key 用worklog:stat:{projectId}:{month},value 存 JSON 字符串。

public ProjectStatVO getProjectStat(Long projectId, String month) { String cacheKey = "worklog:stat:" + projectId + ":" + month; String cached = redisTemplate.opsForValue().get(cacheKey); if (cached != null) { return JSON.parseObject(cached, ProjectStatVO.class); } ProjectStatVO stat = worklogMapper.sumHoursByProject(projectId, month); redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(stat), 5, TimeUnit.MINUTES); return stat; }

缓存失效策略要跟审批操作联动,审批通过或驳回时删除对应的缓存 key,否则用户会看到旧数据。索引方面,worklog表建议建idx_user_dateidx_project_date两个联合索引,覆盖按人查和按项目查两种场景。

5. 源码二次分发的边界与工时数据校验技巧

5.1 源码混淆与二次分发前的检查清单

如果这份源码要用于商业项目或者对外分发,先确认授权协议。很多网上流传的 Java 项目源码带的是学习许可,不能直接商用。技术层面,分发前建议做几件事:把application.yml里的数据库密码、Redis 密码、第三方接口密钥全部抽到环境变量;检查日志里是否打印了敏感信息;用 ProGuard 或类似工具对核心业务类做混淆,但注意 MyBatis 的实体类不要混淆,否则映射会失败。

# 用 mvn 打包时跳过测试,加快构建 mvn clean package -DskipTests # 检查打包后的 jar 里是否包含配置文件中的明文密码 unzip -p target/worklog.jar BOOT-INF/classes/application.yml | grep -i password

如果发现明文密码,改成${DB_PASSWORD}这种占位符,启动时通过环境变量注入。另外,源码里的README.md和注释如果包含内部地址或账号,分发前要清理干净。

5.2 工时数据校验:防止重复填报与超时填报

工时系统最怕的是重复填报和超时填报。重复填报的校验逻辑是:同一用户、同一项目、同一日期,如果已有记录且状态不是「驳回」,则不允许再填。超时填报一般限制在当前日期往前 7 天内,超过 7 天需要走补填审批。

public void validateWorklog(WorklogDTO dto) { // 重复校验 int count = worklogMapper.countByUserProjectDate(dto.getUserId(), dto.getProjectId(), dto.getWorkDate()); if (count > 0) { throw new BizException("该日期已填报过此项目"); } // 超时校验 LocalDate limit = LocalDate.now().minusDays(7); if (dto.getWorkDate().isBefore(limit)) { throw new BizException("超过 7 天的工时需要走补填审批"); } // 工时数校验 if (dto.getHours().compareTo(BigDecimal.ZERO) <= 0 || dto.getHours().compareTo(new BigDecimal("24")) > 0) { throw new BizException("单日工时必须在 0 到 24 之间"); } }

这段校验放在 Service 层,Controller 只负责参数绑定。countByUserProjectDate的 SQL 要带上status != 'REJECTED'条件,否则被驳回的记录会挡住重新填报。工时数上限设 24 是防止误填,实际业务里单项目单日超过 12 小时就应该触发预警。

5.3 用单元测试锁定工时计算逻辑

工时计算涉及跨天、跨月、审批状态变更,逻辑容易改出 bug。建议对核心计算写单元测试,用 JUnit 5 加 Mockito。

@Test void testSumHoursExcludeRejected() { when(worklogMapper.selectByPeriod("2025-06")).thenReturn(Arrays.asList( buildWorklog(new BigDecimal("8"), WorklogStatus.APPROVED), buildWorklog(new BigDecimal("4"), WorklogStatus.REJECTED), buildWorklog(new BigDecimal("6"), WorklogStatus.APPROVED) )); BigDecimal total = worklogService.sumApprovedHours("2025-06"); assertEquals(new BigDecimal("14"), total); }

这个测试锁定了「只统计已审批工时」的规则。如果后续有人改了统计逻辑把驳回的也算进去,测试会立刻失败。工时系统的测试重点就三个:状态过滤、日期边界、精度处理。把这三类用例覆盖到,二次开发时心里就有底了。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询