ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Spring Boot与Vue前后端分离的工时管理系统源码解析及部署避坑指南

Spring Boot与Vue前后端分离的工时管理系统源码解析及部署避坑指南

简介:这是一套基于前后端分离架构的 Java 项目工时管理系统源码(oak project),主要服务于需要统计项目工时、核算人工成本的中小团队,也适合 Java Web 学习者用作企业级管理系统实战参考。系统围绕员工工时上报、原型预览与版本管理、UI 效果图分发三大模块展开,支持实时动态展示项目投入,后端采用 Spring Boot,前端使用 Vue,数据库基于 MySQL 8,代码中保留了环境配置与数据库脚本,便于本地部署和二次开发。压缩包整体结构清晰,共 851 个文件,约 4.16MB,以 Java、Vue、JS、SCSS 源文件为主,同时包含 XML、YML、SQL、PNG、SVG 等配置、脚本和静态资源,并附有启动/打包脚本与多环境配置,可支撑从代码阅读、环境搭建到功能扩展的完整学习路径。资源目前已有 1028 人学习/下载,适合希望借鉴前后端分离架构、工时统计流程和原型托管实现细节的开发者。

1. 一份能直接跑的 Java 项目工时管理系统源码,到底解决了什么

如果你在一个 10 人左右的研发团队待过,大概率经历过后端、前端、产品、UI 在微信群里传原型包、月底用 Excel 接龙统计工时的日子。这份 Java 项目工时管理系统源码(oak project)把工时统计、原型预览、效果图管理收进同一个 Web 应用:员工上报工时,按项目、按人员实时汇总投入;原型和 UI 切图传上去就生成链接,点开即看,还带版本记录。服务端 Spring Boot + 前端 Vue,跑在 Java 1.8 和 MySQL 8 上,前后端分离,没有微服务那一堆依赖。适合不想买商业项目管理系统的小团队,也适合想读一份完整前后端分离源码的 Java 工程师——哪怕你 Java 基础刚补完,按文档把环境配好,也能把这套东西跑起来。

2. 系统骨架与技术选型:Spring Boot + Vue 的前后端分离是怎么分工的

2.1 前端 Vue 管展示,后端 Spring Boot 管数据:这条边界为什么合适

前后端分离这个词在 Java 圈子里提了好几年,但很多小团队的“前后端分离”只是把 HTML 扔进 Tomcat,接口还是 MVC 那套。oak project 的做法更彻底:Vue 负责页面渲染和交互,Spring Boot 只暴露 REST 接口,两者通过 JSON 通信。这样做的好处很实际——前端改版不用重新编译后端,后端加接口不用碰页面。对团队意味着,只要约定好接口结构,前端和后端可以并行开发,排期能压下来一周左右。

从资源本身也能看出这个设计。压缩包里的 main.css、reset.min.css、font-awesome.min.css、ie9.css、noscript.css 都是构建后的前端静态资源,说明交付物里已经包含了打包好的前端产物;而 .env.development、.env.demonstrate 这种文件,是 Vue CLI 在构建和本地开发时读取的环境变量文件。后端 Spring Boot 这边,典型的接口长这样:

@RestController @RequestMapping("/api/work-hour") public class WorkHourController { @PostMapping("/record") public Result submit(@RequestBody WorkHourVO vo) { // 校验项目ID、工时时长、上报日期是否合法 // 检查当天是否已提交过,避免重复上报 // 写入 work_hour 表并返回自增ID return Result.ok(); } }

这段代码的核心在 @RequestMapping 和 @PostMapping 两个注解:前者统一了接口前缀,后者把 POST 请求映射到 submit 方法。@RequestBody 表示请求体是 JSON,前端 axios 传什么结构,后端 VO 就接什么结构。Result 是统一返回体,里面至少包含 code、message、data 三个字段,这样前端拿到响应后只需要判断 code 是否为 0,不用每个接口单独处理异常分支。

对应前端调用,一般会用 axios 封装一个实例:

// src/api/workHour.js 中的典型写法 import axios from 'axios' const api = axios.create({ baseURL: process.env.VUE_APP_BASE_API, timeout: 10000 }) export function submitWorkHour(data) { return api.post('/work-hour/record', data) }

这里有两个参数值得留意。baseURL 不是硬编码的,它从 process.env.VUE_APP_BASE_API 读取,而 VUE_APP_BASE_API 是在 .env.development 或 .env.demonstrate 里定义的——这就是下一节要说的环境变量机制。timeout 设成 10000 毫秒,避免员工在弱网下提交工时,请求一直转圈,实际上后端已经写库成功了,用户又重复提交,产生双倍工时。这个坑我在集成别的系统时踩过一次,后来凡是写上报类接口,前端一定显式设置 timeout。

2.2 .env.demonstrate 与 .env.development:环境变量把开发、演示、生产隔开

Vue CLI 有个约定:项目根目录下的 .env.development 在 npm run serve 启动本地开发服务器时自动加载,.env.demonstrate 则通过指定 mode 来加载。压缩包里同时出现这两个文件,说明交付方把“本地开发”和“演示环境”分开了。.env.development 指向本地后端地址,比如 localhost:8080;.env.demonstrate 指向部署好的演示服务器地址。这样同一个前端代码包,切环境只改文件不改代码。

# .env.development 示例(按交付资源推断的常规写法) NODE_ENV=development VUE_APP_BASE_API=http://localhost:8080/api VUE_APP_PROTOTYPE_URL=http://localhost:8080/prototype
# .env.demonstrate 示例 NODE_ENV=demonstrate VUE_APP_BASE_API=http://your-server-ip:8080/api VUE_APP_PROTOTYPE_URL=http://your-server-ip/prototype

之所以用两个文件而不是一套配置,原因很直接:原型链接和效果图链接在演示模式下必须用服务器地址,不然发给客户的原型链接在手机上打开是 localhost,谁都访问不了。这一点对原型预览功能尤其关键,后面第四章还会展开。

再来看压缩包里的 package.bat、run-web.bat、build.bat。三个脚本的分工,我按常见做法给你拆一下:build.bat 负责构建前端产物,内部通常是npm run build或npm run build:demonstrate,执行完输出 dist 目录;package.bat 负责后端打包,典型内容是mvn clean package -DskipTests,产出可执行 jar;run-web.bat 负责把前后端串起来——先检查端口占用,再启动 Spring Boot jar,然后用静态资源方式托管前端 dist。一个 Windows 环境下的启动脚本大致长这样:

@echo off rem 启动后端服务,jar 包名按实际产物改 java -jar target/oak-project.jar --spring.profiles.active=demonstrate

这里有个关键参数--spring.profiles.active,它决定后端读 application-demonstrate.yml 还是 application-development.yml。很多新手直接把 jar 双击跑起来,看到控制台报数据库连接失败,就是因为默认 profile 里没有配置 MySQL 8 的连接串。后面避坑章节会专门讲。

2.3 Java 1.8 + MySQL 8:为什么说这是小团队最稳的组合

选 Java 1.8 不是因为它新,而是因为它旧得稳定。绝大多数公司服务器上的 JDK 还是 1.8,没有升级到 11 或 17 的动力;Spring Boot 2.x 对 1.8 的支持非常成熟,网上随便一搜就是各种踩坑记录,团队里任何一个 Java 工程师都能上手。MySQL 8 虽然比 5.7 晚,但它默认字符集是 utf8mb4,存 UI 效果图的文件名、原型备注里的 emoji 都不会乱码,而且窗口函数对工时成本按周汇总这类需求很省事。更重要的是,这两样在小团队现有的服务器上大概率已经装好了,不需要为这个系统单独引入 Redis、MQ 之类的基础设施。

当然,选型也有代价。Java 1.8 的内存占用比 Go 那类编译型语言高,启动一个 Spring Boot jar 通常要吃 300MB 左右内存,如果团队服务器只有 1GB 内存,就得在配置里调小 JVM 堆,否则会出现系统卡死。常见做法是在启动脚本里加-Xms256m -Xmx512m把堆内存压下来,前提是业务量不大。这个项目的定位本来就是轻量级管理工具,同时在线人数一般不会超过几十人,这样的配置完全够用。

3. 工时统计模块拆解:从员工上报到人工成本核算的完整链路

3.1 员工上报的数据流:一次 submit 背后发生了什么

工时统计是整个系统里逻辑最重的一块。员工打开前端页面,选择项目、填写工时数和备注,点提交,后端收到请求后做三件事:校验、去重、写库。校验指的是项目ID存在、工时数大于 0 且不超过 24、日期不是未来日期;去重指的是同一员工同一天同一个项目不能提交两次,否则月底统计会出现虚增;写库则是把记录落到 work_hour 表。

一张典型的 work_hour 表,按业务需求反推大概是这些字段:

字段名类型说明
idbigint主键,自增
staff_idbigint员工ID,关联员工表
project_idbigint项目ID,关联项目表
work_datedate上报日期
hoursdecimal(4,1)工时数,支持0.5小时粒度
remarkvarchar(255)备注说明
create_timedatetime创建时间

hours 用 decimal 而不是 int,是因为很多团队上报粒度是半小时甚至一刻钟。如果设计成 int,员工填 3.5 小时就被迫四舍五入,月底统计误差会滚雪球。remark 加 255 的长度限制,避免有人把整段代码贴进来撑爆字段。另外 create_time 和 work_date 是两个概念,前者是这条记录的落库时间,后者是员工实际干活的日子,统计时必须以 work_date 为准,否则补报昨天的工时会被记到今天。

去重逻辑在正常业务里一定要靠唯一索引兜底,而不是只靠代码判断。我见过只靠select count(*)判断的版本,高并发下两个请求同时通过校验,双双写库成功,数据直接翻倍。正确的做法是在表上建唯一索引:

ALTER TABLE work_hour ADD UNIQUE KEY uk_staff_project_date (staff_id, project_id, work_date);

建了唯一索引之后,即使并发请求同时进来,数据库层面也只允许一条成功,第二条插入直接报 Duplicate entry 异常。后端把这个异常捕获后返回“今天已上报过该项目的工时”提示即可。这个细节在 Java 面试题里经常被拿出来问,也是真实系统里必须补上的防护。很多人写项目只注重功能实现,忽略了唯一索引,结果演示时看不出问题,一上线多人同时提交就翻车。

3.2 从工时到成本:成本核算的统计口径

光有工时记录还不够,项目经理真正关心的是钱。这个系统把员工月薪或时薪折算成单价,再用“工时 × 单价”算出项目人工成本。成本核算的 SQL 大体是这样一个按项目聚合的查询:

SELECT p.project_name, SUM(w.hours) AS total_hours, SUM(w.hours * s.hourly_cost) AS total_cost FROM work_hour w JOIN project p ON w.project_id = p.id JOIN staff s ON w.staff_id = s.id WHERE w.work_date BETWEEN '2025-01-01' AND '2025-01-31' GROUP BY p.project_name ORDER BY total_cost DESC;

这条 SQL 做了三张表的关联:work_hour 明细、project 项目、staff 员工。staff 表里有一个 hourly_cost 字段,存的是该员工的工时单价。把单价放在员工表而非工时表,考虑的是成本单价会变,但历史工时不该跟着变——不过更严谨的做法是在 work_hour 表里冗余一个 cost_snapshot 字段,上报当天就把单价快照进去,否则员工涨薪后,历史项目的成本会被重新计算,影响项目盈亏结论。要是你打算把这套系统用于正式的成本核算,建议加上冗余快照。

有了 total_hours 和 total_cost,页面上的项目看板就能实时显示“这个月投入了多少人天、烧了多少人工成本”。所谓实时、动态、真实的展示项目投入,本质就是后端提供一个聚合接口,前端定时刷新。聚合接口的关键是不要每次都扫描全表,加上(project_id, work_date)的联合索引,或者干脆维护一张按日预聚合的 project_daily_stat 表,月末查询直接从统计表取数。

聚合接口的常见实现用 MyBatis 的注解 SQL 就够,不需要单独引入复杂框架。很多 Java 工程师看到“动态展示”就以为要上 WebSocket 实时推送,实际上内部管理工具完全没必要,前端定时轮询加一个 loading 态,效果一样,代码量还少。我在自己团队做类似功能时,就是前端的 setInterval 每 5 分钟拉一次统计数据,后端接口加 3 秒缓存,数据库压力几乎可以忽略。至于 MyBatis Plus 根据实体类自动生成建表 SQL 的那套玩法,在这个项目里也能用上——用注解建实体后直接在测试环境跑一遍 DDL,比自己手写建表语句省事,但生产环境还是建议 DBA 审核过再执行。

4. 原型预览与效果图管理:把第三方托管换成自己的静态资源服务

4.1 原型分发:一个链接搞定版本管理和历史回看

产品原型在开发流程里是高频被访问的东西。传统做法是设计同学把原型包导出成 HTML,压缩成 zip,丢到微信群,后端同学下载、解压、本地打开,看到的往往还是过期版本。oak project 的做法是把原型文件传上服务器,通过链接直接访问。链接的形式一般是http://server-ip/prototype/项目名/版本号/index.html。

这个功能在实现上依赖两件事:文件存储的目录结构和静态资源映射。目录结构一般按三级来组织,项目名做一级目录,版本号做二级目录,原型文件放版本目录里:

prototype/ |- order-center/ | |- v1.0/index.html | |- v1.1/index.html | └─ v2.0/index.html └─ user-center/ └─ v1.0/index.html

后端把 prototype 目录映射为静态资源路径,Spring Boot 里一行配置就能搞定:

@Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/prototype/**") .addResourceLocations("file:" + prototypeDir + "/"); }

这里的prototypeDir是配置文件里指定的磁盘路径,Windows 上形如D:/data/oak/prototype/,Linux 上形如/opt/oak/prototype/。把原型目录放在工程目录之外,是为了避免重新部署后端时把原型文件一起覆盖掉。版本管理则是上传时带一个版本号参数,系统把文件放进对应版本目录,并在版本表里记录创建人和创建时间。前端原型列表页按项目分组展示所有版本,点某个版本就是访问对应的 index.html,天然实现了历史回看。我见过好几个团队用文件名后缀来区分版本,比如原型最终版(2).zip、原型改完不再动.zip,时间一长根本分不清哪个是最新的,用版本目录管理之后这个问题彻底消失。

4.2 效果图管理:命名约定和权限边界

效果图管理和原型预览共享同一套静态资源映射,区别在于效果图一般需要增加一层权限控制。原型是给团队内部看的,效果图则可能涉及未发布产品的 UI 截图,不该让所有人都能直接拼链接访问。常规做法是给效果图目录加一个简单的 token 校验——访问 URL 时附带一个短 token,后端拦截器校验通过才放行,而不是把整个系统接上完整的 RBAC 权限体系。

命名上我的习惯是强制按“项目_模块_版本_日期”的规则上传,比如order_list_v1.0_20250601.png。这个命名规范能让效果图的列表页按文件名自动排序,同一个模块的历史版本一眼就能看出新旧,省去一张张打开对比的时间。压缩包里同时出现 main.css、font-awesome.min.css 这些前端资源,也说明列表页完全可以支持图片缩略图网格展示——CSS 框架已经就位,前端只需要一个缩略图组件。font-awesome.min.css 里的字体图标可以直接用来做文件类型标识,比如 png 文件显示图片图标,zip 文件显示压缩包图标,几行代码的事。

4.3 为什么说替代第三方原型托管是可行的

第三方原型托管工具本身不差,但小团队用起来总会遇到几个现实问题:免费版原型数量有限制,一个项目做完就得清理旧版本;原型涉及未公开产品,传第三方平台总有数据安全的顾虑;团队成员离职后账号交接麻烦。oak project 的这套方案把原型文件放在自己的服务器上,访问链接受内网或端口控制,版本不清理,理论上没有数量限制。代价是要有人维护磁盘空间和静态资源服务的可用性。对一个 10 人团队来说,这个代价完全可接受——一块 500GB 的硬盘能放好几年的原型文件,而省下来的是第三方工具的会员费和管理成本。

5. 部署与运行避坑:五个最常见的问题和排查路径

5.1 双击 run-web.bat 闪退

现象:双击 run-web.bat 后黑窗口一闪而过,什么都看不到,后端也没起来。

原因:脚本执行异常直接退出。最常见的情况是 JDK 没装或者 JAVA_HOME 没配置,Java 命令找不到;其次是压缩包解压路径有中文或空格,导致 jar 包路径解析失败;再往下才是 jar 包本身没打出来。

解决:先在命令行窗口手动执行java -version,看有没有输出。如果提示“不是内部或外部命令”,先配 JAVA_HOME 和 PATH。确认 JDK 没问题后,不要双击脚本,改成在 cmd 里执行call run-web.bat,这样能看到完整的错误输出。拿这个项目来说,JDK 必须是 1.8,装了 17 大概率会在 Spring Boot 启动早期就报 Unsupported class file version 之类的错误。Java 环境配置这块是 java 工程师的基本功,但经常被忽略,建议团队统一发一份 JDK 安装规范,别让每个人自己下。

5.2 MySQL 8 连不上:时区、驱动、密码加密方式

现象:后端启动日志里报Access denied for user或者Communications link failure,数据库连接池初始化失败。

原因:MySQL 8 默认认证插件是 caching_sha2_password,而某些旧版 JDBC 驱动只支持 mysql_native_password,两边握手失败。另外 MySQL 8 的 JDBC 连接串必须显式指定时区,否则驱动不知道用哪个时区,连接会被拒。

解决:连接串里加上serverTimezone=Asia/Shanghai&useSSL=false&allowPublicKeyRetrieval=true,并且确认 pom 里 mysql-connector-java 的版本在 8.0 以上。如果数据库里已有的用户是用 mysql_native_password 创建的,也可以执行下面的 SQL 转换:

ALTER USER 'oak'@'localhost' IDENTIFIED WITH mysql_native_password BY 'your_password'; FLUSH PRIVILEGES;

这里有个容易混淆的点:allowPublicKeyRetrieval=true是 MySQL 8 特有的参数,不加的话驱动会拒绝获取服务端公钥,导致认证失败。很多老项目从 5.7 迁到 8 之后连接报错,查来查去最后都是这个参数的问题。

5.3 .env.development 改了没生效

现象:改了 .env.development 里的 VUE_APP_BASE_API,重新刷新前端页面,接口请求的还是旧地址。

原因:Vue CLI 的环境变量不是在浏览器运行时读取的,而是在构建时注入的。也就是说,修改 .env 文件后必须重新执行构建或重新启动 dev server,浏览器端拿到的还是打包时写死的地址。这个坑十个人里有八个会踩。

解决:改完 .env 后,先停掉 dev server,再重新npm run serve;如果用的是打包后的静态资源,则要重新执行 build 脚本。判断是否生效可以看前端接口请求的 URL,用浏览器开发者工具的 Network 面板直接观察。另外注意 .env 文件不要提交进 Git,开发环境和演示环境的地址不一样,提交了容易互相覆盖。

5.4 原型页面打开但 CSS 和图片全挂

现象:原型链接能打开 HTML,但页面上的图片、样式完全没有,只有文字。

原因:原型文件里引用资源用的是绝对路径,比如/assets/css/main.css,部署后根路径变化导致资源 404。也可能是静态资源映射只打开了 HTML 后缀,没匹配到 css、js、png 文件,或者 Nginx 配置里没有正确 alias 到原型目录。

解决:把原型访问前缀和实际路径对齐。如果用的是 Nginx 托管,配置大致是:

location /prototype/ { alias /data/oak/prototype/; index index.html; }

另一个经验是原型包导出时尽量选相对路径,不要选绝对路径,这样放到任何一层目录下都能正常访问。用相对路径的原型,在本地双击打开和放到服务器上访问,效果是一致的,省去一堆排查时间。

5.5 端口被占用导致后端起不来

现象:启动 jar 时日志报Port 8080 was already in use,服务一直起不来。

原因:服务器上已有其他服务占用了 8080,常见的是另一个 Java 服务或者 Tomcat。

解决:改端口。在 application.yml 里加server.port=8081,同时把前端 .env 里的 VUE_APP_BASE_API 和 Nginx 代理端口一起改。改完记得前后端两边保持一致的端口,否则会出现前端页面正常、接口全部 404 的情况。更规范的做法是用java -jar oak.jar --server.port=8081这种命令行参数方式覆盖,不用改配置文件,部署脚本里写死端口,避免多人开发时互相踩。

6. 把 oak project 改造成适合自己团队的三个增强点

原型版本管理现在已经能用了,但如果你想让它在团队里真正落地,我建议加一个“版本与迭代里程碑关联”的小功能。具体做法是在版本表里加一个 milestone 字段,值填 v1.0、v1.1、v2.0 这样的迭代号,原型列表页按里程碑分组展示。这样产品经理打开页面,第一眼就能看到当前迭代对应的最新原型是哪个,不用自己在链接后面猜版本号。实现成本很低,一个字段加一个分组逻辑,前后端改动不超过两小时。

工时模块的第二个增强点是周报导出。月底手动导 Excel 太被动,可以写一条按周聚合的 SQL,每周一早上自动把上周的工时明细和成本汇总发到项目经理邮箱。注意导出时要包含员工上报日期和备注,否则项目经理看到一笔异常工时,还得再进系统查明细。SQL 的写法就是 3.2 节那条聚合语句把时间范围换成自然周,再联一张员工表把名字带出来。

第三个增强点是成本单价按角色配置,而不是纯粹按人。比如初级、中级、高级工程师三个角色各设定一个单价,员工表里存角色 ID,成本核算时 JOIN 角色单价表。这样员工晋升后只改角色,历史成本快照不受影响,账目更清晰。综合来看,这套系统的核心价值不在功能多花哨,而在于数据闭环:上报即沉淀、沉淀即汇总、汇总即呈现。从那以后,我在团队里推行任何管理类工具,都强制走一遍“录入-汇总-导出”的闭环验证,确认数据流转没有断点再上线,这个习惯帮我避掉了好几次返工。希望帮到你。

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

返回列表