能直接运行的点赞接口需用sync.RWMutex保护内存map,通过gin.Default()启动,POST /like/:id实现计数+1,避免并发写panic;ID校验、状态码返回和清理机制是上线前必须补全的硬伤。

怎么用 Gin 实现一个可运行的点赞接口
直接上手就能跑,不需要额外数据库或状态服务:用内存 map 模拟点赞计数,配合 gin.Default() 启动服务即可。关键不是“多完整”,而是“能验证逻辑”。
-
gin.Default()自带Logger和Recovery中间件,适合开发调试;若要静默启动(比如测试压测),改用gin.New() - 点赞动作本质是「对某个资源 ID 做 +1」,所以接口设计为
POST /like/:id最自然 - 别用全局变量存数据——至少包级变量加
sync.Mutex,否则并发请求会丢计数
package main
import (
"sync"
"github.com/gin-gonic/gin"
)
var (
likes = make(map[string]int)
mu sync.RWMutex
)
func main() {
r := gin.Default()
r.POST("/like/:id", func(c *gin.Context) {
id := c.Param("id")
if id == "" {
c.JSON(400, gin.H{"error": "missing id"})
return
}
mu.Lock()
likes[id]++
count := likes[id]
mu.Unlock()
c.JSON(200, gin.H{"id": id, "count": count})
})
r.Run(":8080")
}
为什么不能直接写 likes[id]++ 而不加锁
Go 的 map 并发写 panic 是 runtime 级错误,不是逻辑错——一旦两个请求同时执行 likes[id]++,大概率触发 fatal error: concurrent map writes,整个服务直接崩溃。
- 读操作(如查当前值)可用
RWMutex.RLock(),但这里只写不读,Lock()就够 - 如果后续要支持「取消点赞」或「查总数」,就得统一用
RWMutex控制读写,不能混用 - 别试图用
sync.Map替代——它适合读多写少且 key 不固定场景;这里 key 有限、写频繁,普通 map + mutex 更快更可控
c.Param("id") 和 c.Query("id") 别混用
路径参数和查询参数语义不同,选错会导致接口行为不可预测。点赞这种「对某实体操作」,ID 必须是路径的一部分,而不是 query。
-
/like/123→ 正确,c.Param("id")取到"123" -
/like?id=123→ 错误,这是资源集合的筛选,不是对单个资源的动作 - 如果真要支持批量点赞,应设计为
POST /likes+ JSON body,而不是塞 query
上线前必须改掉的三个硬伤
这个 demo 能跑,但离可用还差几步。最容易被忽略的是「没做输入校验」和「没考虑资源生命周期」。
立即学习“go语言免费学习笔记(深入)”;
- ID 长度或格式没限制?恶意传入超长字符串可能撑爆内存,建议加
if len(id) > 32 { c.AbortWithStatusJSON(400, ...) } - 点赞数永远增长?没清理机制的话,map 会持续膨胀。真实场景需搭配 TTL 或定期归档
- 没返回 HTTP 状态码区分成功/失败?仅靠 JSON 字段判断不可靠,客户端应依赖 status code(如 201 表示新建,200 表示更新)
真正的难点不在写接口,而在决定「谁来负责清理过期点赞」「ID 冲突怎么处理」「是否需要幂等性」——这些没法靠框架自动解决。


















