
本文讲解如何在 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))
}
}✅ 优势总结:
- 无需在 main() 中显式传递状态,Handler 更简洁;
- 内置读写锁,线程安全;
- 易于后续替换为 Redis / SQLite 等持久化层(只需重写 model 包内部实现);
- 符合 Go 的“小而专”包设计哲学,职责清晰。
? 最终建议
- 学习阶段:优先实践方案一(指针传递),快速理解 Go 值传递本质;
- 实际项目:务必采用方案二(包级变量 + sync),兼顾可维护性、并发安全与可扩展性;
- 长远架构:内存数据库只是临时方案,应尽早规划持久化存储与 API 版本管理。
通过以上任一方式,/add 与 /rss 将真正共享同一份动态更新的数据源,彻底解决“添加后 RSS 不刷新”的典型问题。










