Go语言如何写一个TODO应用_Golang初级项目完整示例

Go TODO应用用net/http实现轻量Web服务,以切片+sync.RWMutex管理并发安全的TODO数据,通过http.HandleFunc路由、template渲染HTML、r.ParseForm解析表单,并注意重定向后return避免重复响应。

Go 语言写一个 TODO 应用,不需要框架也能快速跑起来——核心是用 net/http 搭个轻量 Web 服务,配合内存存储(如 map 或切片)完成增删查改。真正卡住新手的不是语法,而是路由设计、请求解析、状态保持这几个环节。

http.HandleFunc 实现基础路由和 HTML 渲染

别急着上 gorilla/muxgin,先用标准库把流程走通。每个路由对应一个函数,用 http.ServeFile 静态服务 HTML,用 template 渲染动态内容。

关键点:

  • http.HandleFunc 的第二个参数必须是 func(http.ResponseWriter, *http.Request) 类型
  • HTML 表单的 method 要和 Go 中判断的 r.Method 一致(比如 POST 提交就要检查 r.Method == "POST"
  • 渲染模板前务必调用 template.ParseFiles(),且路径要相对于执行时的当前目录(不是源码目录)

struct 和切片管理 TODO 数据,避免全局 map 并发问题

初学者常直接用 var todos = make(map[int]*Todo),但 HTTP 处理函数是并发调用的,map 非线程安全。更稳妥的做法是用切片 + 互斥锁,或干脆用 sync.Map(适合读多写少)。

推荐结构:

type Todo struct {
	ID     int    `json:"id"`
	Text   string `json:"text"`
	Done   bool   `json:"done"`
}

var (
	todos = []Todo{}
	mu    sync.RWMutex
)

所有读写操作前后加 mu.Lock()/mu.Unlock()mu.RLock()/mu.RUnlock()

处理表单提交:用 r.ParseForm()r.FormValue() 取值

Go 不会自动解析 POST 表单,必须显式调用 r.ParseForm(),否则 r.FormValue("text") 返回空字符串。常见错误包括:

  • 忘记调用 r.ParseForm(),尤其在 if r.Method == "POST" 分支里
  • HTML 表单中 缺少 name="text" 属性,导致 r.FormValue("text") 找不到键
  • 重定向后没加 return,导致后续代码继续执行并重复写响应(HTTP 500 或 “http: multiple response.WriteHeader calls” 错误)

典型处理逻辑:

if r.Method == "POST" {
	r.ParseForm()
	text := r.FormValue("text")
	if text != "" {
		mu.Lock()
		todos = append(todos, Todo{
			ID:   len(todos) + 1,
			Text: text,
			Done: false,
		})
		mu.Unlock()
	}
	http.Redirect(w, r, "/", http.StatusSeeOther)
	return
}

html/template 安全渲染列表,防止 XSS

直接拼接 HTML 字符串(如 w.Write([]byte("

  • " + t.Text + "
  • ")))有 XSS 风险。必须用 html/template,它会自动转义变量内容。

    模板示例(保存为 index.html):

    {{range .Todos}}
    
  • {{.Text}}
  • {{else}}
  • No todos yet.
  • {{end}}

    渲染时传入数据结构:

    data := struct {
    	Todos []Todo
    }{
    	Todos: todos,
    }
    tmpl.Execute(w, data)

    注意:模板中字段名首字母必须大写(Text 可导出,text 不可),否则无法访问。