☰
WorkBuddy到DSH迁移实战:格式转换、配置映射与自动化校验闭环
2026/10/13 12:11:25 网站建设 项目流程

如果你也像我一样,每天的工作记录都丢在WorkBuddy里,攒了大半年之后突然发现数据分析侧只认DSH格式,那你大概能体会我当时的处境。我当时的第一个念头是自己写个脚本做转换,磨了两天脚本,最后还是老实改用workbuddy-to-dsh,把一堆边界情况交给这个现成的工具处理。

这篇文章我打算把它从安装到自动化,完整地走一遍。不是照着README念一遍,而是把我自己在真实迁移过程中趟过的坑、验证过的命令、最后留用的配置,全部摊开来说。不管是刚听说这个工具、第一次跑通命令的小白,还是已经在手动写脚本转换、想找一个更稳方案的开发者,应该都能从里面找到对应自己的那一段。

1. 为什么要做WorkBuddy到DSH的迁移

1.1 两种工具的根本定位差异

WorkBuddy是我一直在用的一款轻量工作记录工具,它的核心是把一天的时间切成一个个工作片段:每个片段包含项目名、任务描述、开始时间、结束时间、标签。它擅长的是“记录”这件事,数据以一种非常简单直观的形式存在本机,基本就是一个JSON文件加一个可选的CSV导出,字段命名完全按照日常用语来,谁打开都能看明白。

DSH则完全是另一侧的产物。它的定位是“消费”,服务于数据管道、看板、统计报表这类下游场景。DSH要求每条记录是一个扁平事件,字段有严格约定:事件ID、事件类型、来源、发生时间、持续时间、附加指标JSON。字段名是英文驼峰式,时间戳统一用UTC毫秒,时长必须是浮点小时数。

这两者之间的差异,导致了一个很尴尬的局面:数据在WorkBuddy里看着非常有条理,但到了DSH这边完全不认。你没法把WorkBuddy的嵌套JSON直接丢给DSH的导入接口,字段名对不上,时间格式对不上,项目的划分逻辑也不一样。于是workbuddy-to-dsh这个转换工具的出现也就顺理成章了。

1.2 手工处理为什么总是翻车

可能有朋友会说:不就转个格式吗,我写个脚本半小时就搞定了。我一开始也是这么想的,后来发现真正的问题不在格式,而在边界情况太多。

最典型的是时间。WorkBuddy记录的是本地时间,DSH要求UTC时间戳带毫秒,而且跨天的工作片段要么自动拆分、要么得明确标记。第二是项目名合并问题,同一个项目在WorkBuddy里叫“官网改版”,在DSH里必须映射成web-redesign这样的英文标识,否则下游报表的项目维度就是乱的。第三是时长字段,WorkBuddy里是分钟整数,DSH里要求浮点小时并保留两位小数。

这些规则一次两次人工处理还能应付,一旦数据量到几千条,手工脚本就很容易在某一条边界数据上暴雷,而且你很难发现。更麻烦的是,每次导出的数据都会有细微的差异,脚本今天能跑,明天可能就挂。workbuddy-to-dsh的价值,就是把这些规则集中、可配置、可验证地固化下来,不用每次迁移都重新造一遍轮子。

2. 安装与首次验证

2.1 运行环境与安装命令

这个工具基于Python开发,要求Python 3.9及以上。它用到了比较新的类型注解和内置的zoneinfo时区库,如果你还在用Python 3.8,安装的时候会直接报依赖解析错误,不用怀疑是装错了包,版本不够就是版本不够。

安装方式就一条命令:

pip install workbuddy-to-dsh

如果你跟我一样,系统里同时存在多个Python环境,强烈建议用虚拟环境,避免和系统自带的包管理搅在一起:

python3 -m venv .wb2dsh source .wb2dsh/bin/activate pip install workbuddy-to-dsh

装完先确认版本,这是排查后续一切问题的基础:

workbuddy2dsh --version

正常会输出类似workbuddy2dsh 0.4.2这样一行。如果这里提示command not found,多半是虚拟环境没激活,或者pip的bin目录不在PATH里。macOS和Linux上常见的是前者,Windows上常见的是后者,处理方式大同小异,把对应目录加进PATH就行。

2.2 用--help快速建立全局认识

我不太喜欢一上来就翻完整的文档,习惯先跑一条命令建立骨架式的认识:

workbuddy2dsh --help

输出里会把所有子命令列出来。这个工具的命令结构并不复杂,核心就三个子命令:convert、preview、validate,外加一个全局的--config参数。这个设计思路我很认可:先预览、再校验、最后转换,三步分开,而不是一把梭到底。

看清楚命令结构之后再逐个看参数,比一开始就捧着文档啃要高效得多。你会在后面逐步意识到,preview和validate在正式迁移中起的作用比convert还大,初期很容易被忽略,但它们在数据迁移里恰恰是帮你省最多时间的两个命令。

3. 核心命令与参数详解

3.1 convert命令的完整参数

convert命令负责真正的转换逻辑,它的参数虽然不算多,但每个都有讲究,不搞清楚就很容易在后期出现问题。

参数默认值作用
--input必填WorkBuddy导出的源文件路径
--output必填转换后的DSH文件路径
--config./config.yaml映射配置,决定字段怎么对应
--project-map无项目名映射表,JSON格式,可替代配置文件里的映射段
--tag-filter无按标签过滤,只转换带指定标签的记录
--merge-bynone将连续同项目的片段合并,可选day或none
--tzlocal源数据时区,强烈建议显式指定

举个例子,一条最基本的转换命令长这样:

workbuddy2dsh convert \ --input workbuddy-export.json \ --output sessions.dsh.json \ --config workbuddy.yaml

第一次跑的时候,我建议把顺序固定下来:先写--input和--output,再看--config。以后每次执行都保持同样的参数顺序,既方便看日志,也方便写自动化脚本的时候不容易漏参数。

3.2 配置文件里的映射逻辑

配置文件是这个工具的灵魂。它做的事情本质上是“字段级翻译”,把WorkBuddy里叫task_name的字段,翻译成DSH里叫event_type的字段,顺带做枚举值替换。

source: timezone: "Asia/Shanghai" mapping: event_id: "session_id" event_type: "task_name" source: "constant:workbuddy" timestamp: "start_time" duration_hours: "duration_minutes" project_map: "官网改版": "web-redesign" "数据统计任务": "analytics-report" "内部工具开发": "internal-tool" transform: duration_divisor: 60 timestamp_format: "utc-millis"

mapping段里冒号左边是DSH的目标字段,右边是WorkBuddy的源字段。constant:workbuddy这种写法表示不取自源数据,直接写死一个常量。这个技巧很实用,因为DSH要求每条事件必须有来源标识,而WorkBuddy数据本身没有这个字段,用常量填充是最干净的解法。

project_map解决项目名不一致的问题,这是整个工具里最容易被低估的一段配置。transform里的duration_divisor: 60意思是把分钟除以60转成小时,这是时长单位转换最常用的动作。

这个配置文件是YAML格式,好处是注释友好,坏处是字段一多容易写错缩进。我的建议是:从官方示例模板改起,不要从零写。缩进错了工具会直接报解析错误,但解析通过了也不代表映射是对的,所以后面一定要配合preview命令做确认。

3.3 preview与validate为什么不能省

preview命令不产生输出文件,只把转换结果的前20条打印到屏幕。它的价值在于低成本检查:字段映射有没有生效、项目名有没有被正确替换、时长是不是变成了预期的小时数,这些都是屏幕上扫一眼就能确认的事情。

workbuddy2dsh preview --input workbuddy-export.json --config workbuddy.yaml

validate则是对转换后的DSH文件做全面合规检查。它会检查必填字段是否存在、时间戳是否可解析、duration_hours是否在合理范围内、事件ID是否重复。这一条命令我建议在每次转换之后都跑一遍,养成习惯:

workbuddy2dsh validate --input sessions.dsh.json

如果校验不过,它会输出具体错误记录的定位提示,比如“第42条记录:duration_hours为负值”,而不是笼统地告诉你“格式错误”。这一点对排查问题来说价值很大,因为数据量大了以后,你不可能人工逐条去翻问题出在哪,直接定位到行号能省掉大量时间。

4. 一次完整的数据迁移实战

4.1 从WorkBuddy导出源数据

我假设一个具体场景,这也是我真实处理过的场景:某团队要把过去三个月的工时记录,从WorkBuddy迁移到DSH体系里,用来跑月度工时报表。

第一步永远是从WorkBuddy里导出原始数据。导出时我建议勾选全部字段,不要精简。道理很简单,映射阶段字段越多越好,多余的可以在配置里忽略,但缺失的到后面再补就麻烦了,你得重新导出重跑。导出得到的JSON大概长这样:

[ { "session_id": "WB-20241001-001", "project": "官网改版", "task_name": "前端联调", "start_time": "2024-10-01 09:12:00", "end_time": "2024-10-01 10:40:00", "duration_minutes": 88, "tags": ["前端", "联调", "关键路径"] } ]

拿到这个文件之后,先别急着转换。先看一眼总条数,再随机翻几条确认时间字段有没有缺失。我踩过的一个坑是:WorkBuddy偶尔会因为软件异常退出,在导出文件里留下几条没有end_time的记录。这种脏数据如果直接进转换流程,时长字段会变成空,最终导致DSH统计出来的总工时明显偏小,而且很难追查。

4.2 编写并验证映射配置

基于上面的数据,配置文件可以这样写:

source: timezone: "Asia/Shanghai" mapping: event_id: "session_id" event_type: "task_name" source: "constant:workbuddy" timestamp: "start_time" duration_hours: "duration_minutes" project_map: "官网改版": "web-redesign" "数据统计任务": "analytics-report" transform: duration_divisor: 60 timestamp_format: "utc-millis" rules: drop_missing_end_time: true

注意最后我加了一条drop_missing_end_time: true,让转换器直接丢弃没有结束时间的记录。它对统计报表影响不大,但避免了脏数据污染目标端。如果你不想丢数据,也可以改成fill_zero: true,把缺失的时长记成0,然后在DSH侧单独标记处理。

配置写完先跑preview:

workbuddy2dsh preview --input workbuddy-export.json --config workbuddy.yaml

预览输出里重点看三样东西:第一条记录的时间戳是否变成了UTC毫秒、event_type是否正确映射、duration_hours是否从88分钟变成了1.47小时。这三处都对,映射基本就靠谱了。

4.3 执行转换并进入校验闭环

预览确认无误后,执行转换:

workbuddy2dsh convert \ --input workbuddy-export.json \ --output sessions.dsh.json \ --config workbuddy.yaml

转换过程完全在本地执行,不涉及网络请求,速度非常快,几千条记录通常是秒级完成。这也是我敢直接在命令行里反复跑的原因,错了重来成本极低。转换完成后再执行校验:

workbuddy2dsh validate --input sessions.dsh.json

校验通过会有类似ALL RECORDS VALID的提示。如果提示有错误,它会给你一个错误码。我整理了几个高频错误码的含义,供你排查时对照:

错误码含义常见原因
E1001缺少必填字段源记录字段被删或映射写错
E1007时间戳无法解析时区不规范或时间格式变了
E1009时长不在合理范围除零、负值或单位配错
E1010事件ID重复多批次导出存在重叠

4.4 批量导入DSH存储端

校验通过之后,最后一步是把sessions.dsh.json导入到DSH的存储端。导入这一步不属于本工具的范围,不同团队的DSH实现方式不一样,但有个通用规律:DSH导入接口通常支持批量提交,一次最好控制在500条以内,单条数据过大容易触发网关超时。

我个人的做法是写一个简单的分片导入脚本,把文件按500行切分,逐批提交。这样即使中间某批失败,也能从断点续传,不用重跑整个转换。

split -l 500 -d sessions.dsh.json part-

切出来之后按文件名顺序导入即可。导入完成后,在DSH侧抽查一两天的数据,和WorkBuddy里的原始记录对一下总时长。偏差在1%以内基本就没问题,超过这个幅度就要回头查映射配置了。

5. 我在实际使用中踩过的坑

5.1 时区问题:时间漂移了8小时

这个坑几乎每个做数据迁移的人都会遇到。WorkBuddy默认用本地时区记录时间,DSH要求UTC时间戳。如果配置文件里忘记写source.timezone,工具会采用默认的local,而这个local取的是运行转换时所在机器的时区。

问题出在跨机器执行时:你在自己电脑上转一遍是对的,放上服务器定时跑,服务器时区如果是UTC,同一份源数据转出来的时间戳就会整体偏移8小时,月度报表的时间分布直接错位。我当时是怎么发现的呢?是某个月的工时分布图上,所有任务都集中在凌晨两点到上午十点,明显不是人类的作息。

我的教训是:配置里永远显式写死时区,不要依赖“本地时区”这个不确定项。写Asia/Shanghai还是写UTC不重要,重要的是写出来、写对。这样不管在哪台机器上跑,结果都完全一致。

5.2 重复事件ID:多批次导出的重叠问题

有一次我在校验阶段收到大量E1010报错,提示事件ID重复。一开始我以为是工具的去重逻辑有bug,后来排查发现是源数据的问题:团队里有人手动跑了两次导出,第二次导出的文件里包含了第一次的全部记录,导致ID大面积重复。

解决思路分两层。第一层是在源端去重,比较所有记录的时间和片段描述,只保留一份。第二层是在工具端处理,给事件ID加前缀批次号,比如在配置里设置id_prefix: "batch2024q4",这样即使两批数据ID相同,转出来的事件ID也不冲突。这个参数比较隐蔽,我也是翻源码的时候才发现的,但实用性很强。

5.3 标签文本规范化

WorkBuddy里的标签是人手打的,自由度很大。“前端”“前端 ”“前端,联调”这些看起来差不多的标签,在数据库里其实是三个不同字符串。DSH侧的统计是按精确匹配做的,标签不统一,报表上的分类就会碎成一片。

我用的办法是在配置里加一个tag_normalize段:

tag_normalize: trim: true collapse_spaces: true map: "前端 ": "前端" "前端开发": "前端"

先做前后空格清理,再做同义词归一。这个功能不需要专门写脚本,配置文件里声明就行。做完归一之后,DSH侧的周报里前端类工时占比明显回升,原因就是之前大量带尾随空格的“前端”被当成独立标签统计漏掉了。

6. 从手动到自动:定时转换与校验闭环

6.1 用cron做每日自动转换

数据迁移不是一次性的,WorkBuddy每天都有新记录,DSH需要保持更新。手动跑命令撑不了几天,迟早要自动化。

最简单的方案是cron定时任务。假设每个周日的晚上十点把本周新数据转一次:

0 22 * * 0 cd /data/workbuddy-to-dsh && python3 -m venv .venv && .venv/bin/workbuddy2dsh convert --input weekly-export.json --output weekly.dsh.json --config weekly.yaml && .venv/bin/workbuddy2dsh validate --input weekly.dsh.json >> weekly.log 2>&1

这条任务有两个细节值得注意。一是每一次调度都显式走一遍venv的bin目录,避免因为环境变量变化导致命令找不到;二是把validate放在convert后面,用&&串联,校验失败就不会进入后续逻辑。日志重定向到weekly.log,出问题的时候有迹可循。

6.2 用文件监听做近实时转换

如果业务上要求DSH数据的延迟在分钟级,cron就不够了。可以用watchdog写一个简单的目录监听脚本,一旦WorkBuddy导出的目录里出现新文件,就立刻触发转换。

from watchdog.observers import Observer from watchdog.events import FileSystemEventHandler import subprocess class ExportHandler(FileSystemEventHandler): def on_created(self, event): if event.is_directory: return if not event.src_path.endswith(".json"): return subprocess.run([ "workbuddy2dsh", "convert", "--input", event.src_path, "--output", event.src_path.replace(".json", ".dsh.json"), "--config", "/data/workbuddy/config.yaml" ]) if __name__ == "__main__": observer = Observer() observer.schedule(ExportHandler(), "/data/workbuddy/exports", recursive=False) observer.start() observer.join()

这个脚本很轻量,适合单机场景。注意on_created只在文件新建时触发,如果导出工具是覆盖写入同一个文件,就需要改用on_modified,同时加一个文件长度检查,防止文件写到一半就被拿去转换,产出残缺数据。

6.3 用Git钩子做转换前的质量闸门

最后分享一个进阶玩法:把转换步骤嵌进Git工作流里,在提交前强制校验。

做法是在仓库里加一个pre-commit钩子,逻辑很简单:如果本次提交涉及WorkBuddy导出文件,就先转成DSH格式,再校验结果,不过就阻止提交。

#!/bin/sh # .git/hooks/pre-commit STAGED_EXPORT=$(git diff --cached --name-only | grep "workbuddy.*\.json$") if [ -n "$STAGED_EXPORT" ]; then for f in $STAGED_EXPORT; do workbuddy2dsh convert --input "$f" --output "/tmp/check.dsh.json" --config config.yaml if ! workbuddy2dsh validate --input "/tmp/check.dsh.json"; then echo "转换校验失败,请检查 $f" exit 1 fi done fi

这个方案的核心思路是把质量检查前移,让问题在进入版本库之前就被拦下来。对团队协作来说,这比事后排查要省心得多,因为每个人提交时都会触发检查,脏数据根本没有机会流到DSH侧。

我自己的体会是,workbuddy-to-dsh最大的价值不在于它一次性解决了转换问题,而在于它提供了一套可重复、可校验、可自动化的数据流转闭环。配置好之后,每周只需要看一眼校验日志,剩下的活都交给定时任务去跑了。对任何一个长期维护数据管道的人来说,这种“配置一次、稳定运行”的状态,才是最舒服的。

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

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

立即咨询