You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Go用户认证系统:如何强制用户设置博客标题后方可进入主页

问题描述

我正在用Go开发用户认证系统,流程是:引导用户填写邮箱、用户名、密码完成注册,随后向用户邮箱发送确认链接;用户点击确认链接后,必须为其博客选择标题,我需要确保用户未设置博客标题时无法进入主页。

我的ConfirmEmail函数代码如下:

func ConfirmEmail(w http.ResponseWriter, r *http.Request){
    err := r.ParseForm()
    if err != nil{
      log.Fatal("Unable to parse data")
    }
    token := r.Form.Get("token")
    db.ConnectDB()
    current_time := time.Now().Unix()
        user_id := 0
    var date_generated int64
    var date_expires  int64
    var date_used     int64
    row := db.Db.QueryRow("Select user_id, date_generated, date_expires, date_used from Token where token = ?", token)
    if err := row.Scan(&user_id, &date_generated, &date_expires, &date_used); err != nil{
       if err == sql.ErrNoRows{
        //todo: no such token provide a link to signup..
        fmt.Println("No such rows..")
       } else {
        log.Fatal("Something went wrong:", err)
       }
    }

   //reuse of the token...
    if (date_used != 0){
       http.Redirect(w,r, "/signup", http.StatusFound)
    }
    // use of expired token...
    if(date_expires < current_time){
        //todo: inform about the expired token and prompt for re confirmation..
        fmt.Println("Token expired..")

    } else{
        //todo: Check for blog title, if null prompt.
        var title string
        var username string
        if err := db.Db.QueryRow("select username, blogTitle from User where user_id = ?", user_id).Scan(&username, &title); err != nil{
            if err == sql.ErrNoRows{
                http.Redirect(w, r, "/signup", http.StatusFound)
            }
        }
        //want to do this until title is not provided..
        if len(title) == 0{
            err = templates.ExecuteTemplate(w, "chose-title.html", struct {
                Username string 
                Msg string
                }{
                Username: username,
                Msg: "",
                })
            if err != nil {
                log.Fatal("Unable to render provided template:",err)
               }
            return 
        }
        _, err = db.Db.Exec("Update Token set date_used = ? where token=?",current_time, token)
        if err != nil {
          log.Fatal("Unable to update with given data")
         }

        _, err = db.Db.Exec("Update User set Verified = true where user_id=?",user_id)
        if err != nil {
          log.Fatal("Unable to update with given data")
         } else {
           http.Redirect(w, r, "/login", http.StatusFound)
          }
    }
}

核心问题代码片段:

if len(title) == 0{
        err = templates.ExecuteTemplate(w, "chose-title.html", struct {
            Username string 
            Msg string
            }{
            Username: username,
            Msg: "",
            })
        if err != nil {
            log.Fatal("Unable to render provided template:",err)
           }
        return 
    }
    _, err = db.Db.Exec("Update Token set date_used = ? where token=?",current_time, token)
    if err != nil {
      log.Fatal("Unable to update with given data")
     }

    _, err = db.Db.Exec("Update User set Verified = true where user_id=?",user_id)
    if err != nil {
      log.Fatal("Unable to update with given data")
     } else {
       http.Redirect(w, r, "/login", http.StatusFound)
      }
}

我曾考虑使用while循环实现持续校验,但认为该方案不可行,请问有哪些其他可行的替代方案或工作流可以实现这一需求?


可行解决方案

方案1:拆分路由,单独处理博客标题设置

把博客标题设置的逻辑从ConfirmEmail中抽离,单独创建SetBlogTitle路由,将流程拆分为两步:

  1. 确认邮箱环节:完成token验证后,先标记token已使用、用户已验证,再检查用户是否有博客标题:
    • 无标题:将用户ID存入会话(如gorilla/sessions),重定向到/set-blog-title
    • 有标题:直接跳转至登录页或主页
  2. 标题设置环节:/set-blog-title路由负责渲染设置页面、接收用户提交的标题:
    • 校验标题合法性(非空、符合长度要求)
    • 合法则更新数据库中用户的blogTitle字段,完成后跳转至登录/主页
    • 不合法则重新渲染页面并提示错误

示例代码片段:

// 修改后的ConfirmEmail函数
func ConfirmEmail(w http.ResponseWriter, r *http.Request) {
    // ... 原有token验证、过期/重复使用检查逻辑 ...

    // 验证通过后,先更新token和用户验证状态
    _, err = db.Db.Exec("Update Token set date_used = ? where token=?", current_time, token)
    if err != nil {
        log.Fatal("Failed to update token status")
    }
    _, err = db.Db.Exec("Update User set Verified = true where user_id=?", user_id)
    if err != nil {
        log.Fatal("Failed to update user verification status")
    }

    // 查询用户博客标题
    var title string
    err = db.Db.QueryRow("select blogTitle from User where user_id = ?", user_id).Scan(&title)
    if err != nil && err != sql.ErrNoRows {
        log.Fatal("Failed to query user blog title")
    }

    // 无标题则跳转至设置页面
    if len(strings.TrimSpace(title)) == 0 {
        session, _ := store.Get(r, "user-session")
        session.Values["user_id"] = user_id
        session.Save(r, w)
        http.Redirect(w, r, "/set-blog-title", http.StatusFound)
        return
    }

    // 有标题则跳转至登录页
    http.Redirect(w, r, "/login", http.StatusFound)
}

// 新增SetBlogTitle处理函数
func SetBlogTitle(w http.ResponseWriter, r *http.Request) {
    // 从会话获取用户ID
    session, _ := store.Get(r, "user-session")
    userID, ok := session.Values["user_id"].(int)
    if !ok {
        http.Redirect(w, r, "/signup", http.StatusFound)
        return
    }

    if r.Method == http.MethodPost {
        r.ParseForm()
        title := strings.TrimSpace(r.Form.Get("blog_title"))
        
        // 标题为空则重新渲染页面并提示错误
        if len(title) == 0 {
            username := getUsernameByID(userID) // 实现该函数从数据库查询用户名
            err := templates.ExecuteTemplate(w, "chose-title.html", struct {
                Username string
                Msg      string
            }{
                Username: username,
                Msg:      "博客标题不能为空,请重新输入",
            })
            if err != nil {
                log.Fatal("Failed to render template")
            }
            return
        }

        // 更新数据库中的博客标题
        _, err := db.Db.Exec("Update User set blogTitle = ? where user_id=?", title, userID)
        if err != nil {
            log.Fatal("Failed to update blog title")
        }

        // 标题设置完成,跳转至登录页
        http.Redirect(w, r, "/login", http.StatusFound)
        return
    }

    // GET请求时渲染标题设置页面
    username := getUsernameByID(userID)
    err := templates.ExecuteTemplate(w, "chose-title.html", struct {
        Username string
        Msg      string
    }{
        Username: username,
        Msg:      "",
    })
    if err != nil {
        log.Fatal("Failed to render template")
    }
}

方案2:调整注册流程,提前设置博客标题

如果业务允许,可将博客标题设置环节前移至注册阶段:用户注册时需同时填写邮箱、用户名、密码和博客标题,确认邮箱后直接完成流程。此方案适合不需要“确认后再设置标题”的场景。

方案3:用中间件拦截未设置标题的请求

为防止用户通过其他路径绕过标题设置进入主页,可编写全局中间件,拦截所有需要验证标题状态的路由:

  • 检查当前登录用户是否已设置博客标题
  • 未设置则重定向至标题设置页面
  • 已设置则继续处理请求

示例中间件代码:

func RequireBlogTitle(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // 从会话获取当前登录用户ID
        session, _ := store.Get(r, "user-session")
        userID, ok := session.Values["user_id"].(int)
        if !ok {
            http.Redirect(w, r, "/login", http.StatusFound)
            return
        }

        // 查询用户博客标题
        var title string
        err := db.Db.QueryRow("select blogTitle from User where user_id = ?", userID).Scan(&title)
        if err != nil || len(strings.TrimSpace(title)) == 0 {
            http.Redirect(w, r, "/set-blog-title", http.StatusFound)
            return
        }

        // 标题已设置,继续处理请求
        next.ServeHTTP(w, r)
    })
}

将中间件应用到需要拦截的路由:

http.Handle("/home", RequireBlogTitle(http.HandlerFunc(HomeHandler)))
http.Handle("/dashboard", RequireBlogTitle(http.HandlerFunc(DashboardHandler)))

内容的提问来源于stack exchange,提问作者IcanCode

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 20:05:23