基于Golang的简易CMS系统设计_内容管理API开发实战

1次阅读

安全处理文章api需:①开头校验json并解码;②路径参数用strconv.parseint强转;③写操作严格校验http方法;④单条查用queryrow.scan,列表用query+rows.next;⑤json输出字段首字母大写并加json tag,NULL字段用sql.nullx并判.valid。

基于Golang的简易CMS系统设计_内容管理API开发实战

gohttp.HandlerFunc 怎么安全处理文章增删改查

直接用 http.HandlerFunc 写内容 API 最容易掉进「状态裸奔」和「参数校验缺失」两个坑。比如 POST 新文章时没校验 Title 非空,或 delete 时只靠 URL 路径里的 id 就删库,没核对用户权限和资源归属。

实操建议:

  • 每个 handler 开头先做 json.NewDecoder(r.Body).Decode(&req) + if err != nil { http.Error(w, "invalid json", 400) },别信前端传来的任何结构体字段顺序或类型
  • URL 路径参数(如 /api/articles/{id})必须用 chi.URLParam(r, "id") 或手动从 r.URL.Path 提取,再强转为 int64 —— Go 的 strconv.Atoi 遇到非数字会 panic,得用 strconv.ParseInt(x, 10, 64)
  • 所有写操作(POST/PUT/DELETE)必须检查 r.Method == "POST" 等,别依赖前端 header;csrf 不是重点,但方法校验是底线

database/sql 操作文章表时,ScanQueryRow 哪个更稳

QueryRow 更适合单条文章读取(如 GET /api/articles/123),Scan 配合 Rows 迭代更适合列表(GET /api/articles?limit=20)。错用会导致 panic 或漏数据。

常见错误现象:

立即学习go语言免费学习笔记(深入)”;

  • QueryRow().Scan() 查列表 → 只扫第一行,其余被丢弃,还可能因 no rows 报 sql.ErrNoRows
  • Query().Scan() 直接扫单行 → panic:“sql: expected 1 destination argument, got X”
  • Struct 字段没加 db:"title" tag,Scan 时字段全零值,但不报错

实操建议:

  • 查单条:用 err := db.QueryRow("select id,title,... FROM articles WHERE id = ?", id).Scan(&a.ID, &a.Title, ...)
  • 查列表:用 rows, _ := db.Query("SELECT ..."); defer rows.Close(); for rows.Next() { rows.Scan(&a.ID, ...) }
  • struct 字段 tag 必须和 SELECT 字段顺序、类型严格一致;时间字段优先用 time.Time,别用 String

为什么 json.Marshal 返回空对象或乱码,而不是文章数据

根本原因就两个:struct 字段未导出(小写开头),或数据库查出来是 nil 指针没解引用。不是编码问题,是 Go 的反射规则和 SQL 扫描逻辑在打架。

使用场景典型例子:

  • 定义 type Article struct { id int; Title string }json.Marshal 后是 {},因为 id 小写不可见
  • 数据库字段允许 NULL(如 summary TEXT NULL),用 sql.NullString 接收,但忘了判断 .Valid 就直接 json.Marshal → summary 字段消失或变空字符串
  • struct 里嵌了另一个 struct(如 Author AuthorInfo),但 AuthorInfo 里有未导出字段 → 整个 Author 在 JSON 里为空对象

实操建议:

  • 所有要 JSON 输出的字段名首字母大写,且加 json:"title,omitempty" tag;omitempty 可避免零值污染响应
  • NULL 字段统一用 sql.NullString/sql.NullTime,marshal 前做转换:Summary: a.Summary.String(注意 String 方法返回空串而非 panic)
  • 别在 struct 里直接 embed sql.Rows*sql.Rows —— 它们不能被 JSON 序列化

gin 或 chi 路由里怎么传 context 给数据库层而不全局变量

用全局 var db *sql.DB 是可行的,但一旦要加请求级 trace ID、用户身份或超时控制,就必须把 context.Context 从 handler 一路透传到 db.QueryContext。否则并发下日志串、超时失效、权限混淆。

性能影响很实际:不用 Context 的查询无法被 cancel,一个慢查询会卡住整个 goroutine,而带 ctx 的可以设 ctx, cancel := context.WithTimeout(r.Context(), 3*time.Second)

实操建议:

  • handler 里不要直接调 db.Query,而是封装一层 GetArticle(ctx context.Context, id int64) (*Article, error)
  • 数据库操作函数第一参数永远是 ctx,内部用 db.QueryRowContext(ctx, ...) 替代 QueryRow
  • 别用 context.background() 补位;如果 handler 没传 ctx,宁可 panic 也不硬造 —— 这能暴露设计断点

最常被忽略的是:中间件注入的 value(比如用户 ID)也要通过 context.WithValue 往下传,而不是塞进全局 map。value key 必须是自定义类型,别用 string,否则跨包冲突。

text=ZqhQzanResources