选Cobra+BoltDB因轻量单文件无依赖:Cobra提供子命令和flag解析,BoltDB纯Go嵌入式KV库免外部服务;SQLite有CGO依赖,JSON并发写易丢数据且无事务。

为什么选 Cobra + BoltDB 而不是 SQLite 或 JSON 文件
因为你要的是轻量、单文件、无依赖的 CLI 工具,Cobra 提供开箱即用的子命令和 flag 解析,BoltDB 是纯 Go 实现的嵌入式 KV 数据库,不需外部服务、不需驱动、直接读写文件——适合 TODO 这种本地小数据场景。用 SQLite 会多一层 CGO 依赖,JSON 文件并发写入容易丢数据,且没事务保障。
常见错误现象:panic: open /path/to/data.db: permission denied —— 多半是程序没权限创建数据库目录,或路径里有未创建的父级目录。
- 确保调用
os.MkdirAll(filepath.Dir(dbPath), 0755)再打开BoltDB -
BoltDB文件必须以.db结尾(非强制但约定俗成),否则某些 IDE 或备份工具可能误判 - 不要在多个 goroutine 中复用同一个
*bolt.DB实例做写操作;它本身线程安全,但事务必须串行执行
Cobra 命令结构怎么组织才不混乱
TODO 工具最常需要的动词是 add、list、done、delete,对应 Cobra 的子命令。别把所有逻辑塞进 rootCmd.Run,每个子命令应有自己的 RunE 函数,并接收一个封装好的数据访问层实例。
使用场景:用户执行 todo add "buy milk" --priority high,你得把 flag 解析、业务校验、DB 写入拆开,而不是在 AddCmd.RunE 里直接写 bolt.Open。
立即学习“go语言免费学习笔记(深入)”;
- 在
init()或main()中初始化*bolt.DB,通过闭包或参数传给各子命令的RunE - 用
pflag的StringVarP绑定 flag 到局部变量,避免全局 flag 变量污染 -
list命令默认只显示未完成项,加--all才查全部;这个逻辑放在 handler 里,别塞进 DB 查询条件里硬编码
BoltDB 存 TODO 时 bucket 和 key 怎么设计
别建一堆 bucket(比如 todos、completed、archived),就用一个 todos bucket,靠字段区分状态。BoltDB 不是关系型数据库,没有 JOIN,也不支持索引,过度分 bucket 只会让遍历和迁移更麻烦。
性能影响:每次 Put 都要序列化 struct,建议用 encoding/json 而非 gob(后者不兼容跨版本 Go,且调试困难)。
- bucket 名固定为
"todos",key 用自增整数字符串("1"、"2"),用db.Batch()配合bucket.NextSequence()保证原子性 - value 存 JSON,字段至少包含:
ID(同 key)、Text、Done(bool)、CreatedAt(int64)、Priority(string) - 避免用时间戳当 key:纳秒级时间戳在高并发下可能重复,且无法保证插入顺序
如何安全地从 BoltDB 读取并返回结构化数据
BoltDB 的 ForEach 是只读迭代器,不能中途 break 或提前退出,如果只是查前 10 条待办,用它就浪费资源。应该用 Cursor.First() + Cursor.Next() 手动控制遍历步数。
容易踩的坑:json.Unmarshal 直接作用于 bytes 时,若 DB 中存的是空字节切片或损坏 JSON,会 panic;必须先检查 len(value) > 0,再用 json.Valid 预检。
- 读取时始终用
tx.Bucket([]byte("todos")).Cursor(),别省略tx.前缀,否则拿不到当前事务视图 - 对已完成项做统计(如
todo stats),不要遍历全表 count,改用bolt的Bucket.Stats()获取总数后,在迭代中计数 - 所有 DB 操作必须包裹在
db.Update或db.View中,漏掉就会卡死或返回 nil
复杂点在于事务生命周期和错误传播——RunE 返回 error 会自动触发 Cobra 的错误打印,但 DB 层的 error(比如 bolt.ErrTimeout)得显式转成用户能懂的提示,比如“数据库正被其他进程占用,请稍后重试”。










