必须显式调用 r.ParseForm() 或 r.ParseMultipartForm() 才能解析表单;漏调则 r.FormValue() 返回空;Body 只可读一次,须先解析再取值;文本字段用 r.FormValue(),多值用 r.Form["key"],文件上传需 ParseMultipartForm()。
net/http 正确解析表单提交的用户资料Go 默认不自动解析 multipart 表单(含文件上传)和普通 application/x-www-form-urlencoded,必须显式调用 r.ParseForm() 或 r.ParseMultipartForm()。漏掉这步会导致 r.FormValue("name") 始终返回空字符串。
常见错误是只在 POST 路由里读 r.Body 一次,再调用 ParseForm() 就失效了——因为 Body 是单次读取流。正确做法是:先调用 ParseForm(),再用 r.FormValue() 取值。
name、email)直接用 r.FormValue("name")
hobby=reading&hobby=swimming)用 r.Form["hobby"] 获取 []string
r.ParseMultipartForm(32 并检查 err == nil,否则 r.MultipartForm 为 nil
仅靠前端限制毫无意义。后端至少需验证:email 格式与唯一性、username 长度与字符合法性、password(若提供)是否满足最小长度且未明文存储。跳过校验可能引发 SQL 注入、邮箱劫持或越权修改。
示例中常犯错的是把密码字段也无条件更新进数据库——实际应只在用户主动提交新密码时才哈希并更新 password_hash 字段,否则保留原值。
net/mail.ParseAddress(r.FormValue("email")) 初筛邮箱格式email 是否已被他人占用(排除自身 ID)username 使用 regexp.MustCompile(`^[a-zA-Z0-9_]{3,20}$`) 限制r.FormValue("password") != "",才调用 bcrpyt.GenerateFromPassword() 重新哈希database/sql 安全更新用户记录的写法拼接 SQL 字符串更新用户资料等于邀请 SQL 注入。必须用参数化查询,且只更新明确允许的字段。别写 UPDATE users SET "+field+" = ? 这种动态字段名——字段名不能参数化,只能白名单控制。
stmt := `UPDATE users SET name = ?, email = ?, bio = ?, updated_at = NOW() WHERE id = ? AND status = 'active'` _, err := db.Exec(stmt, name, email, bio, userID)
注意点:
WHERE 条件必须包含 id 和状态检查(如 status = 'active'),防止越权批量更新UPDATE 中包含 password_hash 字段,除非已确认新密码非空并完成哈希err 和 rowsAffected(sql.Result.RowsAffected()),为 0 表示用户不存在或被禁用
须用 http.Redirect 而不是手动设状态码表单提交后若直接返回 HTML 页面,会留下“刷新重复提交”隐患。必须用 303 See Other 重定向到详情页。但很多人手写 w.WriteHeader(http.StatusSeeOther) + w.Header().Set("Location", "/profile"),这容易漏掉 Content-Type 清空或浏览器兼容问题。
http.Redirect() 内部已处理好这些细节,并默认使用 303(POST 后重定向的标准状态码)。用错成 302 可能在某些客户端导致重复 POST。
http.StatusSeeOther 作为第 4 个参数,别依赖默认值"/profile"),相对路径在某些代理下会出错字段校验失败时渲染表单页可以返回 200,但成功更新后这一跳绝不能省——否则用户刷新页面就会二次提交。