
本文讲解如何在 go 的 http 服务中,让多个 HandlerFunc 共享同一份可变的内存数据(如 `[]Article`),重点解决“向切片添加元素后,其他路由能立即读取到最新状态”的核心问题,推荐使用指针传递与包级变量两种生产就绪方案。
在 Go Web 开发中,初学者常误将切片(slice)当作引用类型直接传递给 HTTP 处理器,导致修改不生效——根本原因在于:Go 中切片本身是值类型,其底层包含指向底层数组的指针、长度和容量;但将切片作为参数传入函数时,传递的是该结构体的副本。因此,在 /add/{base64} 中对 database 的 append 操作,仅修改了副本,原始切片未受影响,/rss 路由仍读取旧数据。
✅ 正确方案一:通过指针传递切片(推荐用于小型服务)
将 database 定义为指针类型 *[]Article,并在 Handler 中解引用修改:
// main.go func main() { database := model.ReadFileIntoSlice() r := mux.NewRouter() // 传入指针地址:&database r.HandleFunc("/add/{base64url}", rest.AddArticle(&database)) r.HandleFunc("/rss", rest.GenerateRSS(&database)) // 同样传指针 http.Handle("/", r) log.Printf("Server running on port %d", *port) http.ListenAndServe(":"+strconv.Itoa(*port), nil) }
// rest/handler.go func AddArticle(db *[]model.Article) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) base64URL := vars["base64url"] decoded, err := base64.StdEncoding.DecodeString(base64URL) if err != nil { http.Error(w, "Invalid base64", http.StatusBadRequest) return } newArticle := model.Article{URL: string(decoded)} // ✅ 关键:通过指针修改原始切片 *db = append(*db, newArticle) w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]string{"status": "added", "count": fmt.Sprintf("%d", len(*db))}) } } func GenerateRSS(db *[]model.Article) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/rss+xml") // ✅ 此时 *db 已包含最新添加项 rssXML := model.GenerateRSSFeed(*db) w.Write([]byte(rssXML)) } }
⚠️ 注意事项: 切片指针传递仅适用于单实例服务;若未来需水平扩展(多进程/多节点),此方式失效(内存不共享); 若并发写入(如多个 /add 请求同时触发),必须加锁(sync.Mutex)防止竞态,例如在 AddArticle 中包裹 mu.Lock()/Unlock()。
✅ 正确方案二:使用包级变量 + 同步控制(更健壮,推荐进阶使用)
将 database 提升为包级变量,并封装读写操作,天然支持跨 Handler 共享:
// model/database.go package model import "sync" var ( mu sync.RWMutex database []Article ) func InitDatabase() { database = ReadFileIntoSlice() } func GetArticles() []Article { mu.RLock() defer mu.RUnlock() // 返回副本,避免外部意外修改 result := make([]Article, len(database)) copy(result, database) return result } func AddArticle(a Article) { mu.Lock() defer mu.Unlock() database = append(database, a) }
// rest/handler.go func AddArticle() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // ... 解析逻辑同上 model.AddArticle(newArticle) // 直接调用包方法 w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(map[string]int{"total": len(model.GetArticles())}) } } func GenerateRSS() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/rss+xml") articles := model.GetArticles() // 获取当前全部文章 rssXML := GenerateRSSFeed(articles) w.Write([]byte(rssXML)) } }
✅ 优势总结:
? 最终建议
- 学习阶段:优先实践方案一(指针传递),快速理解 Go 值传递本质;
- 实际项目:务必采用方案二(包级变量 + sync),兼顾可维护性、并发安全与可扩展性;
- 长远架构:内存数据库只是临时方案,应尽早规划持久化存储与 API 版本管理。
通过以上任一方式,/add 与 /rss 将真正共享同一份动态更新的数据源,彻底解决“添加后 RSS 不刷新”的典型问题。