Go语言GoFrame框架之三:cookie和session之web服务示例多端登录系统

一、示例简介:

  1. 模版生成的命令(gf init gf-login-web -u)生成示例
  2. 示例通过ghttp.Server注册一个中间件来做session的认证(如果有相应的session表示刚才登录过,直接返回登录过的结果),接着处理四个协议:“登录界面 获取 api”,“登录 获取 api”,“登录 请求 api”,“登出 请求 api”,最后把相应的结果(其中包括html网页)返回
  3. 通过cmd.go,consts.go,login_web.go,webhtml.go,midddleware.go,html.go,config.yaml,web_html_util.go,main.go(有部分文件是框架生成的)等文件了解GoFrame框架的规范和如何使用
    源码仓库https://github.com/CN007PG/gf-login-web查看。

二、目录结构

  • api
    • v1
      • login_web.go
  • hack(此目录在此暂时不介绍)
  • internal
    • cmd
      • cmd.go
    • consts
      • consts.go
    • controller
      • login_web.go
    • dao
    • logic
      • html
        • webhtml.go
      • logic.go
    • middleware
      • middleware.go
    • model
      • do
      • entity
    • packed(此目录在此暂时不介绍)
    • service
      • html.go
  • manifest
    • config
      • config.yaml
    • deploy(此目录在此暂时不介绍)
    • docker(此目录在此暂时不介绍)
  • resource
    • i18n
    • public(此目录在此暂时不介绍)
    • template
      • index.html
      • login.html
  • utility
    • web_html_util.go
  • go.mod
  • main.go
  • Makefile
  • README.MD
    目录结构祥细使用规范前面我们已经介绍了

三、相应文件代码

login_web.go

package v1

import (
	"github.com/gogf/gf/v2/frame/g"
)

type LoginUIGetReq struct {
	g.Meta `path:"/" tags:"LoginWeb" method:"get" summary:"登录界面 获取 api"`
}

type LoginUIGetRes struct {
	g.Meta `mime:"text/html" example:"string"`
}

type LoginGetReq struct {
	g.Meta `path:"/login" tags:"LoginWeb" method:"get" summary:"登录 获取 api"`
}

type LoginGetRes struct {
	g.Meta `mime:"text/html" example:"string"`
}

type LoginPostReq struct {
	g.Meta `path:"/login" tags:"LoginWeb" method:"post" summary:"登录 请求 api"`
}

type LoginPostRes struct {
	g.Meta `mime:"text/html" example:"string"`
}

type LogoutPostReq struct {
	g.Meta `path:"/logout" tags:"LoginWeb" method:"post" summary:"登出 请求 api"`
}

type LogoutPostRes struct {
	g.Meta `mime:"text/html" example:"string"`
}

cmd.go

package cmd

import (
	"context"
	"gf-login-web/internal/controller"
	"gf-login-web/internal/middleware"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/os/gcmd"
)

var (
	Main = gcmd.Command{
		Name:  "main",
		Usage: "main",
		Brief: "start http server",
		Func: func(ctx context.Context, parser *gcmd.Parser) (err error) {
			s := g.Server()
			//设置组
			s.Group("/", func(group *ghttp.RouterGroup) {
				//设置中间件
				group.Middleware(middleware.Middleware.MiddlewareAuth)
				//绑定LoginWeb
				group.Bind(
					controller.LoginWeb,
				)
			})
			//
			s.Run()
			return nil
		},
	}
)

consts.go

package consts

// 常量 登录web的session键
const SessionLoginWeb = "SessionLoginWeb"
  1. login_web.go
package controller

import (
   "context"
   "gf-login-web/internal/consts"
   "gf-login-web/internal/service"
   "gf-login-web/utility"
   "github.com/gogf/gf/v2/frame/g"
   "github.com/gogf/gf/v2/util/gconv"

   "gf-login-web/api/v1"
)

var (
   LoginWeb = cLoginWeb{}
)

type cLoginWeb struct{}

// 登录界面 获取 api 监听回调
func (c *cLoginWeb) LoginGetUI(ctx context.Context, req *v1.LoginUIGetReq) (res *v1.LoginUIGetRes, err error) {
   g.RequestFromCtx(ctx).Response.WriteTpl("login.html", g.Map{
   	"title": "登录页面",
   })
   return
}

// 登录 获取 api 监听回调
func (c *cLoginWeb) LoginGet(ctx context.Context, req *v1.LoginGetReq) (res *v1.LoginGetRes, err error) {
   username := utility.GetSessionUsername(g.RequestFromCtx(ctx), consts.SessionLoginWeb, "username")
   // 返回登录成功页面
   service.WebHtml().ReturnIndexHtml(g.RequestFromCtx(ctx), gconv.String(username))
   return
}

// 登录 请求 api 监听回调
func (c *cLoginWeb) LoginPost(ctx context.Context, req *v1.LoginPostReq) (res *v1.LoginPostRes, err error) {
   username := g.RequestFromCtx(ctx).Get("username")
   password := g.RequestFromCtx(ctx).Get("password")

   // 暂时写死 后面再用数据库
   dbUsername := "admin"
   dbPassword := "123456"
   if username.String() == dbUsername && password.String() == dbPassword {
   	// 添加session
   	g.RequestFromCtx(ctx).Session.Set(consts.SessionLoginWeb, g.Map{
   		"username": dbUsername,
   	})
   	// 返回登录成功页面
   	service.WebHtml().ReturnIndexHtml(g.RequestFromCtx(ctx), gconv.String(username))
   }
   // 返回登录失败
   g.RequestFromCtx(ctx).Response.WriteJson(g.Map{
   	"code": -1,
   	"msg":  "登录失败",
   })
   return
}

// 登出 请求 api 监听回调
func (c *cLoginWeb) LogoutPost(ctx context.Context, req *v1.LogoutPostReq) (res *v1.LogoutPostRes, err error) {
   // 删除session
   g.RequestFromCtx(ctx).Session.Remove(consts.SessionLoginWeb)
   // 返回登出页面
   g.RequestFromCtx(ctx).Response.WriteTpl("index.html", g.Map{
   	"tips":           "再见!",
   	"username":       "",
   	"button_display": "none",
   })
   return
}

webhtml.go

package html

import (
	"gf-login-web/internal/service"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
)

type (
	sWebHtml struct{}
)

// 此方法 为依赖注入时框架执行的匿名包初始化方法init
// 此方法 要在terminal执行gf gen service 命令导出service下的html.go后再加入
func init() {
	service.RegisterWebHtml(New())
}

// 此方法在init调用的 同上要在terminal执行gf gen service 命令导出service下的html.go后再加入
func New() *sWebHtml {
	return &sWebHtml{}
}

// 直接返回主页html
func (s *sWebHtml) ReturnIndexHtml(r *ghttp.Request, username string) {
	// 返回登录成功页面
	r.Response.WriteTpl("index.html", g.Map{
		"tips":           "欢迎您!",
		"username":       username,
		"button_display": "inline-block",
	})
	r.Exit()
}

logic.go

package logic

import (
	//增加html的引入 这样框架就会执行html包webhtml.go文件下的匿名包初始化方法init
	_ "gf-login-web/internal/logic/html"
)

middleware.go

package middleware

import (
	"gf-login-web/internal/consts"
	"gf-login-web/internal/service"
	"gf-login-web/utility"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/net/ghttp"
	"github.com/gogf/gf/v2/util/gconv"
)

var (
	Middleware = sMiddleware{}
)

type (
	sMiddleware struct{}
)

// 认证中间件
func (s *sMiddleware) MiddlewareAuth(r *ghttp.Request) {
	if r.Method == "GET" {
		//对GET模式下的“/"和”/login“申请作Session认证
		if r.Router.Uri == "/" {
			re, _ := r.Session.Contains(consts.SessionLoginWeb)
			if re {
				username := utility.GetSessionUsername(r, consts.SessionLoginWeb, "username")
				// 直接返回登录成功页面(也可以用RedirectTo("/login")做跳转)
				service.WebHtml().ReturnIndexHtml(r, gconv.String(username))
			} else {
				// 正常 登录
				r.Middleware.Next()
			}
		} else if r.Router.Uri == "/login" {
			re, _ := r.Session.Contains(consts.SessionLoginWeb)
			if re {
				r.Middleware.Next()
			} else {
				// 返回错误码
				r.Response.WriteJson(g.Map{
					"code": 403,
					"msg":  "您访问超时或已登出",
				})
			}
		} else {
			//get的其它申请直接下一步
			r.Middleware.Next()
		}
	} else {
		//post的申请直接下一步
		r.Middleware.Next()
	}

}

html.go此文件 由命令gf gen service 根据 webhtml.go生成的

// ================================================================================
// Code generated by GoFrame CLI tool. DO NOT EDIT.
// You can delete these comments if you wish manually maintain this interface file.
// ================================================================================

package service

import (
	"github.com/gogf/gf/v2/net/ghttp"
)

type (
	IWebHtml interface {
		ReturnIndexHtml(r *ghttp.Request, username string)
	}
)

var (
	localWebHtml IWebHtml
)

func WebHtml() IWebHtml {
	if localWebHtml == nil {
		panic("implement not found for interface IWebHtml, forgot register?")
	}
	return localWebHtml
}

func RegisterWebHtml(i IWebHtml) {
	localWebHtml = i
}

config.yaml

server:
address:     ":8000"
openapiPath: "/api.json"
swaggerPath: "/swagger"

logger:
level : "all"
stdout: true

viewer:
Delimiters: ["${", "}"]

index.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>主页</title>
    <style>
        body {
            background: no-repeat;
            background-size: 100% 130%;
        }
        #bg_box {
            width: 20%;
            height: 400px;
            background-color: #00000060;
            margin: auto;
            margin-top: 10%;
            text-align: center;
            border-radius: 10px;
            padding: 50px 50px;
        }
        h2 {
            color: #ffffff90;
            margin-top: 5%;
        }
        button {
            /*这种方式ide会报错*/
            /*display: ${ .button_display };*/
            margin-top: 50px;
            width: 60%;
            height: 30px;
            border-radius: 10px;
            border: 0;
            color: #fff;
            text-align: center;
            line-height: 30px;
            font-size: 15px;
            background-color: #032379;
        }

    </style>
</head>

<body>
<div id="bg_box">
    <h2>${ .tips }</h2>
    <form class="form-horizontal " role="form"  action="/logout" method="post"  >
        <h2 name="username">${ .username }</h2>
    <button>退出</button><br>
    </form>
</div>
</body>
<script>
    // 这种方式ide不会报错(作为程序员最不想见到的就是报错了)
    document.querySelectorAll("button").item(0).style.setProperty("display",'${ .button_display }');
</script>
</html>

login.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>登录</title>
    <style>
        body {
            background: no-repeat;
            background-size: 100% 130%;
        }

        #bg_box {
            width: 20%;
            height: 400px;
            background-color: #00000060;
            margin: auto;
            margin-top: 10%;
            text-align: center;
            border-radius: 10px;
            padding: 50px 50px;
        }

        h2 {
            color: #ffffff90;
            margin-top: 5%;
        }

        input {
            border: 0;
            width: 60%;
            font-size: 15px;
            color: #fff;
            background: transparent;
            border-bottom: 2px solid #fff;
            padding: 5px 10px;
            outline: none;
            margin-top: 10px;
        }

        button {
            margin-top: 50px;
            width: 60%;
            height: 30px;
            border-radius: 10px;
            border: 0;
            color: #fff;
            text-align: center;
            line-height: 30px;
            font-size: 15px;
            background-color: #032379;
        }

    </style>
</head>

<body>
<div id="bg_box">
    <h2>${ .title }</h2>
    <form class="form-horizontal " role="form"  action="/login" method="post"  >
    <div id="input_box">
        <input type="text" name="username" placeholder="请输入用户名">
    </div>
    <div class="input_box">
        <input type="password" name="password" placeholder="请输入密码">
    </div>
    <button>登录</button><br>
    </form>
</div>
</body>
</html>

web_html_util.go

package utility

import (
   "github.com/gogf/gf/v2/net/ghttp"
   "github.com/gogf/gf/v2/util/gconv"
)

// 取session 的用户名
func GetSessionUsername(r *ghttp.Request, sessionKey string, usernameKey string) string {
   // 返回相应的 session key 的 username key 的用户名
   re_val, _ := r.Session.Get(sessionKey)
   return gconv.String(re_val.Map()[usernameKey])
}

main.go

package main

import (
	//增加logic的引入 这样可以框架就会执行logic匿名包初始化方法
	_ "gf-login-web/internal/logic"

	_ "gf-login-web/internal/packed"

	"github.com/gogf/gf/v2/os/gctx"

	"gf-login-web/internal/cmd"
)

func main() {
	cmd.Main.Run(gctx.New())
}

//readme: 这只是一个示例,不是真正的项目

四、项目搭建流程

  1. 先创建 login_web.go 定义相应的接口,path是路由(也就是常说的接口,method协议方式get或post)
  2. 创建index.html,login.html两个网页html其中页面内以${ .变量名 }的方式来定义,运行的时候使用GoFrame框架来填充相应的变量,再返回前端
  3. 修改config.yaml文件,在文件内加入 viewer:Delimiters: [“${”, “}”] ,这里注意的是不用定义viewer的属性Path因为框架gview_parse.go已经做相应的查找了,加了反而会报错,如果直接复制过去要注意一下双引号的编码,最好是手动输入。
  4. 创建web_html_util.go文件,加入取session的用户名公共方法,因为我们可以了解到此文件夹是放公共函数的地方
  5. 创建webhtml.go文件,加入一个直接返回主页html的函数
  6. 通过命令gf gen service 生成 webhtml.go,并在webhtml.go文件加入init和New方法
  7. 在consts.go里加入SessionLoginWeb常量,也就是我们要定义的常量最后都放在此文件夹下
  8. 创建login_web.go文件,并定义type cLoginWeb struct 结构体实例 LoginWeb 和 相应的监听回调方法
  9. 创建middleware.go文件,加入MiddlewareAuth认证方法(也可以在根路由认证那,用RedirectTo的跳转)
  10. 修改cmd.go文件,在 Group回调设置中间件和绑定回调对象controller.LoginWeb
  11. 最后一步很重要,创建logic.go文件,并import “gf-login-web/internal/logic/html” 然后在main.go文件import “gf-login-web/internal/logic” 这样才框架才能执行webhtml.go下的init匿名包初始化方法

五、核心讲解

框架的cookie和session已经完美的做好并在ghttp里保存了,并且以RemoteAddr + Header通过guid模块做到了session id 的唯一,而且前端页面的cookie里以gfsession键保存session id ,申请时框架会通过前端页面的传来cookie取对应的session id保存在ghttp里,我们只要在这个唯一的session下保存登录状态就可以了。可以通过s.SetSessionMaxAge(time.Minute)设置Session一分钟过期来测试

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值