通过会话变量在Golang layout.tpl中有条件地呈现HTML

时间:2015-04-17 02:54:13

标签: html go

我使用Gorilla会话(通过negroni-sessions)将我的用户会话存储在Cookie中。我还使用github.com/unrolled/render进行HTML模板渲染:

main.go:

    package main

    import (
        ...
        "github.com/codegangsta/negroni"
        "github.com/goincremental/negroni-sessions"
        "github.com/goincremental/negroni-sessions/cookiestore"
        "github.com/julienschmidt/httprouter"
        "github.com/unrolled/render"
        ...
    )

    func init() {
        ...
        ren = render.New(render.Options{
            Directory:     "templates",
            Layout:        "layout",
            Extensions:    []string{".html"},
            Funcs:         []template.FuncMap{TemplateHelpers}, 
            IsDevelopment: false,
        })
        ...
    }

    func main() {
        ...
        router := httprouter.New()
        router.GET("/", HomeHandler)

        // Add session store
        store := cookiestore.New([]byte("my password"))
        store.Options(sessions.Options{
            //MaxAge: 1200,
            Domain: "",
            Path:   "/",
        })

        n := negroni.New(
            negroni.NewRecovery(),
            sessions.Sessions("cssession", store),
            negroni.NewStatic(http.Dir("../static")), 
        )

        n.UseHandler(router)
        n.Run(":9000")

    }

正如您在上面所看到的,我使用 layout.html 主HTML模板,该模板在任何页面呈现时都会包含在内,例如我的主页:

    package main

    import (
        "html/template"
        "github.com/julienschmidt/httprouter"
    )

    func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {

        var model = struct {
            CatalogPicks   []PromotionalModelList
            ClearanceItems []Model
        }{
            CatalogPicks:   GetCatalogPicks(),
            ClearanceItems: GetClearanceItems(),
        }

        ren.HTML(w, http.StatusOK, "home", model)
    }

在我的 layout.html 主HTML模板中,我想呈现一个管理菜单,但前提是当前用户是管理员:

layout.html

<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
      <title>{{ template "title" . }}</title>
        ...
  </head>

  <body>
    ...
    <!--Main Menu-->
      <nav class="menu">
         <ul class="catalog">
             <li class="has-submenu">
                {{ RenderMenuCategories }}
             </li>
             <li><a href="javascript:void(0)">Blog</a></li>
             <li><a href="javascript:void(0)">Company</a></li>

             {{ RenderAdminMenu }}

         </ul>
      </nav>

...

我的问题是上面的模板助手函数RenderAdminMenu()无法访问HTTP Request对象,因此无法访问用户会话对象以确定用户是否为admin。

我可以通过Home页面处理程序将User对象传递给模板上下文,并使用if语句RenderAdminMenu()函数,就像这样

{{ if .User.IsAdmin }}
   {{ RenderAdminMenu }}
{{ end }}

...但由于我使用的是主模板,因此必须从网站上的每个网页进行操作。有更有效的方法吗?

我想也许可能有办法从Context(或layout.html)中访问包含RenderAdminMenu()详细信息的某种全局Request对象(就像你可以在ASP.NET)

2 个答案:

答案 0 :(得分:3)

要将这些结合在一起,您需要做一些事情。我不会展示一个完整的例子,因为它既相当冗长又可能与您的代码(您尚未发布)相匹配。它将包含基本构建块:如果您遇到困难,请回过头来直接提问和代码片段,您将获得更直接的答案:)

  1. 在[login]处理程序中编写一些中间件或逻辑,用户在登录时将用户数据保存在会话中.userID,email和admin boolean值就足够了。 e.g。

    // In your login handler, once you've retrieved the user &
    // matched their password hash (scrypt, of course!) from the DB.
    session.Values["user"] = &youruserobject
    err := session.Save(r, w)
    if err != nil {
        // Throw a HTTP 500
    }
    
  2. 注意:如果您想存储自己的类型,请记住,gorilla/sessions docs需要gob.Register(&youruserobject{})

    1. 写一个助手,当你把它拉出会话时键入断言你的类型,例如

      var ErrInvalidUser= errors.New("invalid user stored in session")
      
      func GetUser(session *sessions.Session) (*User, error) {
          // You can make the map key a constant to avoid typos/errors
          user, ok := session.Values["user"].(*User)
          if !ok || user == nil {
              return nil, ErrInvalidUser
          }
          return user, nil
      }
      
      
      // Use it like this in a handler that serves user content
      session, err := store.Get("yoursessionname", r)
      if err != nil {
          // Throw a HTTP 500
      }
      
      user, err := GetUser(session)
         if err != nil {
          // Re-direct back to the login page or
          // show a HTTP 403 Forbidden, etc.
      }
      
    2. 写一些东西来检查返回的用户是否是管理员:

      func IsAdmin(user *User) bool {
              if user.Admin == true && user.ID != "" && user.Email != "" {
                      return true
              }
      
              return false
      }
      
    3. 将其传递给模板:

       err := template.Execute(w, "sometemplate.html", map[string]interface{}{
              "admin": IsAdmin(user),
              "someotherdata": someStructWithData,
          }
      
       // In your template...
       {{ if .admin }}{{ template "admin_menu" }}{{ end }}
      
    4. 另外,请确保您为会话Cookie设置了身份验证密钥(阅读大猩猩文档),最好是加密密钥,并且您通过HTTPS为您的网站设置了安全:true标志。< / p>

      请记住,上述方法也得到了简化:如果您在数据库中将用户取消标记为admin,则只要会话持续,应用程序就会继续以管理员身份检测它们。默认情况下,这可能是7天,因此如果您处于风险管理流失是一个真正的问题的环境中,可能需要付出真正的短会话点击{{1}内的数据库功能只是为了安全。如果它是一个个人博客而且只是你,而不是那么多。


      已添加:如果您想将用户对象直接传递给模板,您也可以这样做。请注意,在处理程序/中间件中执行它比在模板逻辑中执行它更高效。您还可以获得更多错误处理的灵活性,以及​​之前“纾困”的选项 - 即如果会话中没有任何内容,您可以启动HTTP 500错误,而不是渲染半个模板或者必须在您的文档中放入大量逻辑用于处理IsAdmin数据的模板。

      您仍然需要在会话中存储nil对象(或等效对象),并从User检索它,然后才能将其传递给模板。

      session.Values

答案 1 :(得分:1)

似乎你无法从模板或模板助手函数访问Request上下文(所以我接受了上面的答案)。我的解决方案是创建一个Page结构,我将其作为每个模板的上下文传递。它包含Content作为通用接口,User对象以及其他有用的参数:

//Page holds the model to be rendered for every HTTP handler.
type Page struct {
     MetaTitle     string
     User          User
     HeaderStyles  string
     HeaderScripts string
     FooterScripts string
     Content       interface{}
 }

 func (pg *Page) Init(r *http.Request) {

    if pg.MetaTitle == "" {
        pg.MetaTitle = "This is the default <title> content for the page!"
    }

    user, _ := GetUserFromSession(r)
    pg.User = *user

    if user.IsAdmin() {
        pg.HeaderStyles += `<link href="/css/libs/summernote/summernote.css" rel="stylesheet">`
        pg.FooterScripts += `<script src="/js/libs/summernote/summernote.min.js"></script>`
    }

}

Init方法允许我设置默认值并更轻松地使用Page结构,只需指定页面的Content,如果这就是我需要的全部内容:

package main

import (
    "html/template"
    "github.com/julienschmidt/httprouter"
)

func HomeHandler(w http.ResponseWriter, r *http.Request, p httprouter.Params) {

    var model = struct {
        CatalogPicks   []PromotionalModelList
        ClearanceItems []Model
    }{
        CatalogPicks:   GetCatalogPicks(),
        ClearanceItems: GetClearanceItems(),
    }

    pg := &Page{Content: model}
    pg.Init(r)
    ren.HTML(w, http.StatusOK, "home", pg)
}