一款轻量级的 Web 端工时记录工具,帮助用户精确回答 "时间花在哪里"。
本项目是旧版 worktime (Python/Flask)(当时误拼为 WokTime)的 Go 语言重写版,功能对齐,单二进制分发,无运行时依赖。
在日常工作中,经常遇到以下问题:
- 一天下来不知道时间花在了哪里
- 汇报工作时说不出每个项目/任务花了多少时间
- 缺乏一个轻便的工具随手记录工时
workTime 就是为了解决这些问题而生的。它让用户能够:
- 快速记录每天在每个项目/任务上花费的时间(分钟级)
- 通过日/周/月视图直观查看时间分布
- 支持穿透查看——从周/月汇总点击某天直达该天明细
- 导出 CSV 做进一步分析或汇报
| 模块 | 功能 |
|---|---|
| 用户切换 | 多用户数据隔离,无密码,点击即切换 |
| 项目管理 | 创建/编辑/删除项目;自动创建默认项目(不可删) |
| 任务管理 | 在项目下创建/编辑/删除任务;自动创建默认任务(不可删) |
| 工时录入 | 起止时间自动计算分钟数(自动排除 12:00-13:00 午休),默认 09:00-18:00,也可手动填写 |
| 日视图 | 明细列表 + 按项目/项目-任务双饼图 + 汇总表格 |
| 周视图 | 柱状图(Chart.js,纵轴小时),每日卡片,点击穿透 |
| 月视图 | 自定义起始日期的月度滚动视图,工时色阶(<8.5h / 8.5-9.5 / 9.5-10.5 / 10.5-11.5 / ≥11.5h)、项目标签、法定节假日 |
| CSV导出 | 自定义日期范围,含 BOM 头兼容 Excel |
| 工时转移 | 删除任务/项目时自动将工时转移至默认实体,数据不丢失 |
| 层级 | 技术 |
|---|---|
| 后端框架 | Go 标准库 net/http(Go 1.22+,无第三方 Web 框架) |
| 模板引擎 | html/template(编译期嵌入,go:embed) |
| 数据库 | SQLite(modernc.org/sqlite,纯 Go 实现,无 CGO) |
| 前端图表 | Chart.js(柱状图 + 饼图,CDN 引入) |
| 会话 | HMAC 签名 Cookie(uid + CSRF + flash 消息) |
| CSS | 纯手写,无框架依赖 |
单二进制分发:模板与静态资源全部通过 go:embed 打包进可执行文件,部署只需一个 exe + 可选的 holiday/ 目录。
采用项目-任务二级结构,每个项目下可包含多个任务:
用户
├── 项目1(可多个)
│ ├── 默认任务(自动创建,不可删)
│ ├── 任务A
│ └── 任务B
└── 项目2
├── 默认任务
└── ...
- 软删除:项目和任务删除时仅标记
is_deleted=1,数据保留在数据库中以供历史统计 - 工时转移:删除非默认任务 → 工时自动转移至同项目的默认任务;删除非默认项目 → 工时转移至全局默认项目
- 物理删除:工时记录可物理删除(唯一可彻底删除的数据)
- 默认实体保护:每个用户有一个"默认项目"、每个项目有一个"默认任务",可改名但不可删除,确保始终有兜底容器
- 午休扣除:工时录入时自动计算分钟数,自动排除 12:00-13:00 午休时间(如 09:00-18:00 计算为 8 小时而非 9 小时)
- 自动备份:启动时备份 + 每周六 03:00 定时备份,保留最近 9 份备份文件
- 日视图:按日期查询明细,按项目/任务分组汇总,双饼图可视化占比
- 周视图:以周一~周日为一周,Chart.js 柱状图(纵轴小时),tooltip 显示 X小时X分钟
- 月视图:以用户设置的起始日期为起点,范围到下个月同日 -1 天。日工时按 <8.5 / 8.5-9.5 / 9.5-10.5 / 10.5-11.5 / ≥11.5h 色阶显示(法定节假日有加班时至少为 9.5-10.5 档);当天任一任务名称含"请假"时整格标蓝(该机制不在图例显示);每天展示涉及的项目标签和法定节假日
worktime_go/
├── main.go # 启动入口(--host / --port)
├── config.go # 数据目录配置
├── db.go # SQLite 连接 + 建表
├── models.go # 数据访问层
├── session.go # 会话(Cookie 签名 + CSRF + Flash)
├── context.go # 会话/登录/CSRF 中间件
├── holiday.go # 节假日加载模块(内置 + 外部覆盖)
├── holiday_data/ # 内置节假日 JSON(2007-2027,go:embed 打包)
├── backup.go # 数据库自动备份
├── mux.go # 路由注册
├── handlers_auth.go # 用户选择/创建/切换
├── handlers_projects.go # 项目 + 任务 CRUD
├── handlers_time.go # 工时录入/编辑/删除
├── handlers_views.go # 日/周/月视图
├── handlers_export.go # CSV 导出
├── web/
│ ├── templates/ # html/template 模板
│ └── static/ # CSS / JS
├── .github/workflows/build.yml # GitHub Actions 多平台构建
└── instance/ # 运行时自动创建(数据库/备份/密钥)
cd worktime_go
go mod tidy # 首次运行生成 go.sum
go run . --port 5000
# 访问 http://127.0.0.1:5000
# 也可指定监听地址和端口:
go run . --host 0.0.0.0 --port 8080go mod tidy
go build -trimpath -ldflags "-s -w" -o workTime.exe .CGO_ENABLED=0 GOOS=linux GOARCH=arm GOARM=7 go build -o workTime-linux-armv7 .项目内置了 GitHub Actions 工作流(.github/workflows/build.yml),每次推送到 main 分支自动构建,并自动打递增版本号 tag(v0.1 → v0.2 → ...)发布到 Releases。产物统一命名为 workTime(Windows 为 workTime.exe):
| 平台 | 产物 |
|---|---|
| windows/amd64 | workTime-vX.Y-windows-amd64.exe |
| linux/amd64 | workTime-vX.Y-linux-amd64 |
| linux/arm64 | workTime-vX.Y-linux-arm64 |
| linux/armv7(OpenWrt 等) | workTime-vX.Y-linux-armv7 |
推送代码到 main 分支即可:
git push构建完成后自动创建 Release(含 tag 和全部平台二进制)。
在 GitHub 仓库页面点击 Actions → Build workTime → Run workflow。
scp user@host:/path/to/workTime /root/
chmod +x /root/workTime
/root/workTime --host 0.0.0.0 --port 5000月视图支持显示法定节假日。数据已内置打包(2007-2027 年,来自 holiday-cn),开箱即用;同时支持外部目录覆盖:
- 内置数据:编译时打包在
holiday_data/目录(go:embed) - 外部覆盖:
二进制同级目录/holiday/(也兼容工作目录下的holiday/),外部文件优先级更高,更新节假日无需重新编译
数据按年份存放,遵循 holiday-cn 格式:
{
"year": 2026,
"days": [
{"name": "元旦", "date": "2026-01-01", "isOffDay": true}
]
}isOffDay: true— 法定假日(显示)isOffDay: false— 调休上班日(不显示)
部署时,将
holiday/目录放在二进制同级,无需重新编译即可更新节假日。 节假日数据可在启动时通过环境变量无刷新更新后重启进程生效(数据目录:WORKTIME_DATA_DIR可自定义数据库存放位置)。
| 项 | worktime (Python 旧版) | workTime (Go) |
|---|---|---|
| 运行方式 | 需要 Python 环境 / PyInstaller 打包 | 单二进制,零依赖 |
| 打包产物 | ~15MB (PyInstaller) | ~8MB (Go 静态编译) |
| 交叉编译 | 需要 QEMU + Alpine 容器 | GOOS/GOARCH 原生支持 |
| 数据库文件 | instance/woktime.db(旧版误拼) |
instance/worktime.db |
| 节假日数据 | exe 同级 holiday/ 目录 |
内置打包 + holiday/ 目录可选覆盖 |
| 前端依赖 | Chart.js 走 CDN | Chart.js 打包进 exe,完全离线可用 |
| 数据/备份/密钥 | — | instance/(二进制同级,WORKTIME_DATA_DIR 可覆盖) |
从 Flask 移植到 Go 过程中遇到的实际问题,改动相关代码前先看这里:
modernc.org/sqlite 对声明为 DATE 类型的列,读出时可能返回带时间部分的字符串(如 2026-09-21T00:00:00+08:00),直接与 "2026-09-21" 做字符串相等比较永远不等。曾导致周/月视图按天匹配全部为 0、CSV 导出节假日列全空。
对策:models.go 中 scanEntry 读出后统一经 normalizeDate() 规整为 YYYY-MM-DD 再参与匹配、展示、导出。任何新查询若涉及日期列,都必须走这个规整。
Go 的 http.ResponseWriter 在第一次 WriteHeader/Write 后响应头即固化,之后再调用 Set-Cookie 无效且不报错。中间件若在 handler 返回后才写会话 Cookie,登录态、Flash 消息会全部静默丢失(Flask 没有这个问题,因为它在响应结束才统一序列化 header)。
对策:session.go 用 sessionWriter 包装 ResponseWriter,在首次写入前注入 Cookie。新增 handler 时注意:不要在写出响应之后再改会话状态。
//go:embed web/static 嵌入后,FS 内路径是 web/static/css/style.css。路由剥掉 /static/ 前缀后按 css/style.css 查找会 404。
对策:render.go 用 fs.Sub(staticFSRoot, "web/static") 挂载子目录。同理模板解析用完整路径 web/templates/xxx.html。
用户网络环境可能无法访问 jsdelivr。Chart.js 在 CI 构建时下载(jsdelivr 失败自动切 unpkg)覆盖 web/static/js/chart.umd.min.js 占位文件后打进 exe。本地开发若需要图表,手动下载该文件覆盖占位文件即可(占位文件是合法 JS,不影响编译和其他功能)。
- 表结构与 Python 版完全一致;旧数据库文件手动改名为
instance/worktime.db即可沿用 - 启动时尝试
ALTER TABLE time_entries ADD COLUMN content并忽略已存在错误 - 节假日判定规则:法定假期(isOffDay:true)和周末都算休息日,调休上班日(isOffDay:false)不算
modernc 驱动无 CGO,单写者模型。连接池限制 SetMaxOpenConns(1) 避免并发写锁冲突(本地单用户工具足够)。
- SQLite DSN 中的路径用
filepath.ToSlash()转正斜杠,避免反斜杠转义问题 - 中文字符串切分(如星期名)必须用
[]rune,直接按字节切片会得到 UTF-8 碎片
- go.sum 不入库(沙箱无法联网生成),CI 构建前执行
go mod tidy - 避免使用过新的标准库 API(如
http.Header.AddSetCookie需 Go 1.20+,CI 环境可能没有),用Cookie.String()+Header.Add替代 - 备份清理等切片操作先判断长度再切,避免
files[maxBackups:]越界 panic
本项目基于 Go 标准库构建,数据库使用 SQLite(modernc 纯 Go 驱动,零 CGO 依赖),前端采用服务端渲染(html/template)+ 原生 JS + Chart.js,无需 Node.js 或前端构建工具,开箱即用。