资讯详情

Java工时管理系统源码解析:数据模型、Spring Boot分层与二次开发实战

📅 2026/9/23 17:08:54 | 华诺云谱 👁 阅读
Java工时管理系统源码解析:数据模型、Spring Boot分层与二次开发实战
简介这是一套基于 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 建议用 decimalapprovalid, 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) { // 查询该用户该周期内未提交的工时 ListWorklog 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 idsumHoursByProject resultTypemap 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_date和hours的拆分逻辑常见做法是允许一天填多条记录每条对应不同项目。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.xJDK 8 也能跑。不要强行用 JDK 8 去编译 Spring Boot 3 的项目会报一堆Unsupported class file major version错误。Maven 版本建议用 3.8 以上避免依赖下载时的协议问题。3.2 数据库初始化建表脚本与初始数据源码里一般会有schema.sql和data.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?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/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 doesnt 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_date、project_id、user_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_date和idx_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); }这个测试锁定了「只统计已审批工时」的规则。如果后续有人改了统计逻辑把驳回的也算进去测试会立刻失败。工时系统的测试重点就三个状态过滤、日期边界、精度处理。把这三类用例覆盖到二次开发时心里就有底了。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。