应使用 os.writefile 而非已弃用的 ioutil.writefile;需显式传 0644 权限、用 filepath.join 拼接路径并 clean 过滤、结构体加 json 标签统一字段名、时间格式化为字符串、成功提示用 fmt.printf、错误输出到 os.stderr 并 exit(1)。

为什么用 os.WriteFile 而不是 ioutil.WriteFile
因为 ioutil.WriteFile 在 Go 1.16+ 已被弃用,直接用 os.WriteFile 更安全、更轻量。它底层就是封装了 os.OpenFile + Write + Close,但省去了手动处理 os.O_CREATE | os.O_TRUNC | os.O_WRONLY 的麻烦。
常见错误是沿用旧教程里的 ioutil,编译直接报 undefined: ioutil.WriteFile;或者自己手写文件打开逻辑却忘了设 0644 权限,导致笔记文件不可读(尤其在 Linux/macOS 上)。
-
os.WriteFile默认会覆盖,适合「保存即生效」的笔记场景 - 权限参数必须显式传
0644,不能写成644(那是八进制字面量,Go 里要加前缀) - 如果需要追加内容(比如日志式记录),得换用
os.OpenFile+Seek,os.WriteFile不支持
如何让 JSON 笔记文件可读又兼容 CLI 参数解析
CLI 工具常要读写结构化数据,JSON 是最直接的选择,但容易踩两个坑:字段名大小写和时间序列化。
Go 的 json.Marshal 默认导出大写首字母字段(如 Title → "Title"),而用户输入的命令行参数通常是小写(如 --title)。不统一就导致「存进去的字段对不上,读出来是空」。
立即学习“go语言免费学习笔记(深入)”;
- 所有结构体字段加
json:"title"标签,强制序列化为小写键名 - 时间字段别用
time.Time直接序列化——默认输出带时区和纳秒,人类难读;改用Format("2006-01-02 15:04")存字符串 - 如果 CLI 解析用
flag包,注意flag.String返回的是*string,解引用前要判空,否则json.Marshal会写null
文件路径拼接别硬连字符串,用 filepath.Join
新手常写 "./data/" + noteID + ".json",看着简单,但在 Windows 上会生成 .\data\id.json,而 Go 的 os.Stat 或 os.WriteFile 可能因路径分隔符不一致返回 no such file 错误(尤其跨平台测试时)。
更隐蔽的问题是相对路径没做清理:../ 或 ../../ 可能被恶意构造的 noteID 注入,导致写到任意目录。
- 一律用
filepath.Join("data", noteID+".json"),自动适配系统分隔符 - 存之前用
filepath.Clean过滤路径遍历:比如filepath.Clean(filepath.Join("data", "../etc/passwd"))会变成"etc/passwd",这时应拒绝该 ID - 建议初始化时用
os.MkdirAll("data", 0755)确保目录存在,避免首次运行报错
CLI 命令执行后没反馈?加 fmt.Println 比日志库更实在
初级项目不用上 logrus 或 zerolog。用户敲完 note add --title "test",如果终端没回显,会怀疑命令没生效——尤其是异步或静默失败时。
但也不能每行都打 fmt.Println,容易污染输出。关键是区分「成功提示」和「错误信息」:
- 成功路径只输出简洁结果,比如
fmt.Printf("✅ Created: %s\n", id),别带堆栈 - 错误必须用
fmt.Fprintln(os.Stderr, ...)输出,并以非零状态码退出(os.Exit(1)),否则管道或脚本调用会误判成功 - 避免在结构体方法里混用
fmt.Println—— 把 I/O 逻辑收口到 main 函数或 cmd 包,方便后期替换为 JSON 输出模式
持久化本身不难,难的是让用户相信「他刚输的那条笔记真的落盘了」。路径干净、字段对得上、终端有回应,这三件事没做全,就不是可用的 CLI 工具。










