Go:Gin 与 SQLite 任务学习记录 API · 文件与依赖至Gin SQLite:事务回滚与外键
前置:HTTP 衔接。本章把任务、学习心得和状态变化连起来:创建任务 → 写心得 → 开始 → 完成 → 查询复盘。项目是本机单用户教学服务,与知识库网站无关;不包含登录、多人授权或公网部署。
教学组合 Go 1.27.2、Gin 1.12.0、modernc.org/sqlite 1.60.1。Windows amd64、Ubuntu 26.04.1 amd64/WSL2 的测试、构建、实际启动和重启持久化属于历史验证;本轮仅核对正文与源码,修改后未执行示例;macOS arm64 仅保留平台说明,本项目不执行平台运行验证。纯 Go SQLite 驱动降低本地 C 工具链要求,不代表所有平台已测试。
Go:Gin 与 SQLite 任务学习记录 API
前置:HTTP 衔接。本章把任务、学习心得和状态变化连起来:创建任务 → 写心得 → 开始 → 完成 → 查询复盘。项目是本机单用户教学服务,与知识库网站无关;不包含登录、多人授权或公网部署。
教学组合 Go 1.27.2、Gin 1.12.0、modernc.org/sqlite 1.60.1。Windows amd64、Ubuntu 26.04.1 amd64/WSL2 的测试、构建、实际启动和重启持久化属于历史验证;本轮仅核对正文与源码,修改后未执行示例;macOS arm64 仅保留平台说明,本项目不执行平台运行验证。纯 Go SQLite 驱动降低本地 C 工具链要求,不代表所有平台已测试。
文件与依赖
仓库目录 examples/go/learning-api 包含 go.mod、go.sum、main.go、main_test.go。已取得仓库则直接进入该目录。独立练习可在新空目录保存下面 go.mod、main.go 和 main_test.go,再运行 go mod tidy 解析依赖并生成 go.sum;后续将生成的 go.sum 一起保留。首次下载需要网络。
go.mod
module showdocs.example/learning-api
go 1.27.0
toolchain go1.27.2
require (
github.com/gin-gonic/gin v1.12.0
modernc.org/sqlite v1.60.1
)
require (
github.com/bytedance/gopkg v0.1.3 // indirect
github.com/bytedance/sonic v1.15.0 // indirect
github.com/bytedance/sonic/loader v0.5.0 // indirect
github.com/cloudwego/base64x v0.1.6 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.30.1 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/ncruces/go-strftime v1.0.0 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.59.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.5.0 // indirect
golang.org/x/arch v0.22.0 // indirect
golang.org/x/crypto v0.48.0 // indirect
golang.org/x/net v0.51.0 // indirect
golang.org/x/sys v0.48.0 // indirect
golang.org/x/text v0.34.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect
modernc.org/libc v1.77.1 // indirect
modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.12.1 // indirect
)main.go(完整实现)
完整文件:main.go 完整源码。保留原始内容,使用前请核对本文的版本与环境说明。
模块目录中执行(PowerShell/Bash/Zsh 通用):
go version
go mod download
go test ./...
go run .预期测试 ok showdocs.example/learning-api,服务日志包含 http://127.0.0.1:8080。默认在运行目录创建 learning.db。PowerShell 可设置 $env:LEARNING_DB = Join-Path $PWD 'exercise.db';Bash/Zsh 用 export LEARNING_DB="$PWD/exercise.db",再启动。父目录应存在;选择另一文件相当于另一份数据,不是迁移。用新文件练习,不操作真实任务。
契约与输入验证
Task 含 id、title、category、status、created_at、updated_at。id 是正整数,title 去空白后 1–120 个 Unicode 码点,category 最多 40 个;时间为 UTC RFC3339Nano。LearningRecord 含 id、task_id、content、created_at,content 去空白后 1–2000 个码点。StatusChange 保存前后状态,由程序自动记录,不能当作心得写入。
| 方法与路径 | 输入 | 成功结果 |
|---|---|---|
| POST /tasks | title、可选 category | 201,状态固定 todo |
| GET /tasks | category、status、q、page、page_size | 200,items/total/page/page_size |
| GET /tasks/ | 正整数 id | 200,任务 |
| PATCH /tasks/ | title/category 至少一个 | 200,更新任务 |
| DELETE /tasks/ | 正整数 id | 204,关联记录同时删除 |
| PUT /tasks/{id}/status | status | 200,更新任务与状态历史 |
| POST /tasks/{id}/records | content | 201,心得记录 |
| GET /tasks/{id}/records | id | 200,items 按 id 正序 |
| GET /tasks/{id}/changes | id | 200,items 按 id 正序 |
状态允许 todo→doing、doing→todo/done、done→doing。todo 直接到 done 返回 409;重复提交当前状态返回 200,不重复记历史。PATCH 不接受 status,避免绕过转换校验。
错误统一为 {"error":{"code":"...","message":"..."}}。坏 JSON、null、未知字段、第二个 JSON 值、超出 1 MiB、无效字段/分页为 400;资源不存在 404;错误方法 405;非法转换 409;数据库操作失败 500。内部数据库错误写服务日志,不把内部细节原样返回客户端。
Gin 路由与中间件边界
前置:net/http 请求模型。框架承接路由分发、JSON 响应和请求级中间件,输入、错误和资源生命周期仍由应用设计。本例 router 使用 gin.New,显式安装 Logger 与 CustomRecovery;恢复 panic 后统一返回 internal_error JSON,而不是默认文本错误。正常输入失败仍返回约定的 400/404/409,不能靠 panic/recover 表达所有失败。
HandleMethodNotAllowed、NoMethod 和 NoRoute 分别让错误方法与未知路径保持统一 JSON 格式。middleware 执行公共逻辑,失败时调用 Abort 后停止继续处理。不要把 gin.Context 保存给无明确退出条件的后台任务;只提取必要数据,并为任务安排自己的生命周期。数据库调用沿用请求 context,超时与关闭由服务入口配置。
旧 Gin 笔记中的 ping 对照例子已由 HTTP 衔接和本项目替代。需要理解框架差别时,比较两个项目的路由构建与 httptest:协议约定和失败断言由应用负责,使用框架不会自动保证其正确。
分类分页与 SQL 查询
GET /tasks?category=Go&status=done&q=学习&page=1&page_size=20 同时筛选。category 精确匹配,q 为标题字面子串,status 必须是声明状态。默认第一页、每页 20,最多每页 100;零、负数、非整数和会导致偏移溢出的参数都拒绝。超出最后一页返回空 items,total 仍为匹配总数。
数据库表可以理解为具有约束的记录集合。tasks 的 id 为主键,records/changes 的 task_id 是指向任务的外键。SQL INSERT 新增、SELECT 查询、UPDATE 更新、DELETE 删除;WHERE 限制作用范围。示例用 ? 参数绑定用户输入,不能把标题直接拼进 SQL。q 用 SQLite 的 instr,输入 % 不会变成通配符。
排序固定 id 倒序后再 LIMIT/OFFSET,避免仅靠表自然顺序分页。此教学分页没有快照语义:翻页期间插入或删除会影响结果,不能把它用于要求精确遍历不变集合的场景。总数与列表也不是跨查询的一致快照。
Gin SQLite:事务回滚与外键
状态修改和状态历史必须一起成功:开始事务 → 查当前状态 → 校验转换 → UPDATE tasks → INSERT changes → Commit。任一步失败就 Rollback;如果状态已变但日志没写入,用户复盘会看到不完整历史。事务中始终使用 tx,不混用 db 的另一条连接;提交错误也必须处理。
源码先登记 defer Rollback,成功 Commit 后的 Rollback 无实际修改。测试用临时触发器让历史插入失败,再读任务,验证状态仍是 todo;这比只测成功响应更能证明回滚边界。
外键 ON DELETE CASCADE 删除任务关联记录,但 SQLite 必须在实际连接上开启 foreign_keys。示例在驱动 DSN 中对每条连接设置 _pragma=foreign_keys(1),并设置 busy_timeout;只在初始化时执行一次 PRAGMA,连接替换后可能丢配置。测试强制更换连接再删除,确认没有孤立记录。连接池限制为 1 是本地教学简化,不是高并发调优结论。
继续阅读
- 开发实践
- 开发实践
- 开发实践