用 Gin 快速搭建待办 API 服务,避免手写 net/http 路由;需规范结构体字段导出、json/binding tag、启用 ReleaseMode、依赖注入 DB、SQLite 正确获取自增 ID、安全处理 PATCH 更新、动态拼接 SQL、使用 os.Executable() 确保数据库路径正确、统一时间存储格式。

用 gin 快速搭起待办 API 服务,别碰 net/http 手写路由
直接上 gin,不是因为它多高级,而是它默认处理了表单解析、JSON 绑定、错误返回格式这些你迟早要补的坑。手写 net/http 路由+解包+校验,三天写不完一个增删改查。
常见错误现象:Binding JSON failed: invalid character —— 多半是前端没发 Content-Type: application/json,或者结构体字段没加 json: tag;gin 默认不校验必填字段,漏写 binding:"required" 就会静默接受空值。
- 定义结构体时,所有要绑定的字段必须首字母大写(导出),且带
json和bindingtag,比如:Text string `json:"text" binding:"required"` - 启动时加
gin.SetMode(gin.ReleaseMode),不然控制台刷一堆调试日志,上线后容易被当成“还在开发” - 别在 handler 里直接操作数据库连接,用依赖注入传入
*sql.DB或 repository 接口,否则测试和复用全卡死
sqlite3 做本地待办存储,但要注意 INSERT 后拿不到自增 ID 的写法
用 sqlite3 不是为了性能,是图它零配置、单文件、不用起服务——适合 TODO 这种个人小工具。但它有个反直觉点:db.Exec() 返回的 Result 对象,调 LastInsertId() 在某些驱动版本下会 panic 或返回 0。
使用场景:新增一条待办,需要立刻返回带 id 的完整对象给前端。
立即学习“go语言免费学习笔记(深入)”;
初始化并管理阿里云SDK Go 客户端,涵盖 sync.Once 单例、goroutine 安全、endpoint 与 region 配置、VPC 端点及调试。
- 必须用
db.QueryRow("INSERT ... RETURNING id", ...).Scan(&id)(PostgreSQL 风格)不行,sqlite3不支持RETURNING - 正确姿势是:
db.QueryRow("INSERT INTO todos(text, done) VALUES(?, ?); SELECT last_insert_rowid();", text, false).Scan(&id) - 别用
db.Exec+LastInsertId,Go 的sqlite3驱动对这个方法的支持不稳定,尤其在并发插入时
前端发 PATCH /todos/123 更新状态,Golang 怎么安全地只更新 done 字段
不能直接把整个结构体 BindJSON 进来然后 UPDATE ... SET ? 全字段覆盖——用户 PATCH 只传 {"done": true},你却把 text 设成空字符串或零值,数据就丢了。
性能影响:每次更新都查一次原记录再 merge,看似多一次查询,但比丢数据强;用 map[string]interface{} 动态拼 SQL 容易被注入,别试。
- 定义一个只含可更新字段的结构体:
type TodoUpdate struct { Done *bool `json:"done,omitempty"` },指针类型才能区分“没传”和“传了 false” - 用
map[string]interface{}构建SET子句,只加入非 nil 字段:if u.Done != nil { set = append(set, "done = ?"); args = append(args, *u.Done) } - SQL 语句末尾必须加
WHERE id = ?,且参数顺序要严格对应,漏一个问号或错位,更新就会命中错误行
部署时 os.OpenFile 报 no such file or directory,路径到底该写相对还是绝对
本地跑得好好的,扔进 Docker 或 systemd 就打不开 todos.db —— 根本不是权限问题,是当前工作目录(os.Getwd())变了。Golang 不会自动把相对路径转成二进制所在目录下的路径。
容易踩的坑:有人用 filepath.Dir(os.Args[0]) 拼路径,结果在 shell alias 或 symlink 下拿到的是链接路径,不是真实路径。
- 用
os.Executable()获取二进制绝对路径,再用filepath.Dir()得到目录,比如:exePath, _ := os.Executable(); dbPath := filepath.Join(filepath.Dir(exePath), "todos.db") - SQLite 文件路径不要硬编码
"./todos.db",也不要用$HOME,不同运行环境的 HOME 可能指向 root 或 nobody - 首次启动时检查
dbPath所在目录是否存在,不存在就os.MkdirAll(filepath.Dir(dbPath), 0755),别等 OpenFile 失败才报错
最麻烦的其实是时间字段——SQLite 没原生 datetime 类型,存字符串还是整数?选错一种,半年后导出 Excel 就得写转换脚本。这事没法偷懒。

















