Go Echo 学习笔记(三)登陆案例
Echo 登陆案例
本文将使用 Echo 来实现一个登陆的案例,在这个案例中将使用到 Echo 的相关特性。
需求
实现一个用户登陆的案例,要求实现用户登陆以及注销的功能,并且具有会话控制的能力。
实现
首先创建项目的启动文件,main.go:
package main
import (
"github.com/gorilla/sessions"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/labstack/gommon/log"
"io"
"math/rand"
"os"
"time"
)
// 将session信息进行保存(测试使用)
var cookieStore = sessions.NewCookieStore([]byte("studyEcho"))
// 初始化操作
func init() {
rand.Seed(time.Now().UnixNano())
os.Mkdir("./log", 0755)
}
// 实现一个登陆的案例
func main() {
// 创建一个echo实例
e := echo.New()
// 配置日志信息
configureLogger(e)
// 设置静态路由
e.Static("img", "./01-demo/img")
e.File("/favicon.ico", "./01-demo/img/favicon.ico")
e.File("/logo.png", "./01-demo/img/logo.png")
// 设置中间件
setMiddleWare(e)
// 注册路由
RegisterRouter(e)
// 启动服务
e.Logger.Fatal(e.Start(":2020"))
}
// configureLogger 设置当前服务器的Logger信息
func configureLogger(e *echo.Echo) {
// 设置日志级别为info
e.Logger.SetLevel(log.INFO)
// 记录业务日志到文件中
logFile, err := os.OpenFile("./log/echo.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755)
if err != nil {
panic(err)
}
// 设置日志输出位置(文件以及终端)
e.Logger.SetOutput(io.MultiWriter(logFile, os.Stdout))
}
// setMiddleWare 设置中间件
func setMiddleWare(e *echo.Echo) {
// access log输出到文件中
accessLog, err := os.OpenFile("./log/access.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
if err != nil {
panic(err)
}
// 设置日志文件的输出路径(文件以及终端)
middleware.DefaultLoggerConfig.Output = accessLog
middleware.DefaultLoggerConfig.Output = os.Stdout
// 设置对应的中间件
e.Use(middleware.Logger()) // 使用日志中间件记录http请求信息
e.Use(AutoLogin) // 使用自定义中间件验证用户的登陆信息
e.Use(middleware.Recover()) // 使用恢复中间件恢复panic恐慌状态
}
由于demo需要对用户的登陆信息进行验证,因此创建用户对应的结构体:user.go
package main
type User struct {
UID int64
Username string
Password string
}
在该demo中需要对每次请求进行用户登陆信息验证,因此自定义一个中间件:middleware.go
package main
import "github.com/labstack/echo/v4"
// AutoLogin 自定义中间件,如果上次记住了则自动登陆
func AutoLogin(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
// 获取用户cookie信息
cookie, err := ctx.Cookie("username")
// 当前用户已经登陆
if err == nil && cookie.Value != "" {
// 将user信息放在context中,即记住用户信息
user := &User{Username: cookie.Value}
ctx.Set("user", user)
}
// 返回中间件
return next(ctx)
}
}
该demo中所涉及到的所有的路由信息都放置在route.go中
package main
import (
"bytes"
"github.com/gorilla/sessions"
"github.com/labstack/echo/v4"
"html/template"
"net/http"
"time"
)
// RegisterRouter 注册请求路由处理
func RegisterRouter(e *echo.Echo) {
// 首页路由
e.GET("/", func(ctx echo.Context) error {
// 加载模版信息
tpl := template.Must(template.ParseFiles("./01-demo/template/login.html"))
ctx.Logger().Info("this is login page")
// 获取当前的请求参数msg的值
data := map[string]interface{}{
"msg": ctx.QueryParam("msg"),
}
// 判断用户是否已经登陆,从context中获取user
if user, ok := ctx.Get("user").(*User); ok {
// 用户已经登陆则从context中获取用户的信息
data["username"] = user.Username
data["had_login"] = true
} else {
// 用户没有登陆则从session中获取用户的登陆信息
sess := getCookieSession(ctx)
if flashes := sess.Flashes("username"); len(flashes) > 0 {
data["username"] = flashes[0]
}
sess.Save(ctx.Request(), ctx.Response())
}
// 将模版以及数据信息写入到缓冲区中
var buf bytes.Buffer
err := tpl.Execute(&buf, data)
if err != nil {
return err
}
// 将模版信息以html的方式返回
return ctx.HTML(http.StatusOK, buf.String())
})
// 登陆路由
e.POST("/login", func(ctx echo.Context) error {
// 获取请求参数
username := ctx.FormValue("username")
passwd := ctx.FormValue("passwd")
remember_me := ctx.FormValue("remember_me")
if username == "admin" && passwd == "123456" {
// 用户名密码正确则用标准库种cookie
cookie := &http.Cookie{
Name: "username",
Value: username,
HttpOnly: true,
}
// 查看用户是否选择记住用户名
if remember_me == "1" {
cookie.MaxAge = 7 * 24 * 3600 // 7天
}
ctx.SetCookie(cookie)
// 重定向到首页
return ctx.Redirect(http.StatusSeeOther, "/")
}
// 用户名密码错误则返回相应信息
// 首先使用session保存当前用户的用户名信息
session := getCookieSession(ctx)
session.AddFlash("username", username)
err := session.Save(ctx.Request(), ctx.Response())
if err != nil {
return ctx.Redirect(http.StatusSeeOther, "/?msg="+err.Error())
}
return ctx.Redirect(http.StatusSeeOther, "/?msg=用户名或者密码错误")
})
// 注销路由
e.GET("/logout", func(ctx echo.Context) error {
// 覆盖当前cookie
cookie := &http.Cookie{
Name: "username",
Value: "",
Expires: time.Now().Add(-1e9),
MaxAge: -1,
}
ctx.SetCookie(cookie)
return ctx.Redirect(http.StatusSeeOther, "/")
})
}
// getCookieSession 获取当前登陆context中的session信息
func getCookieSession(ctx echo.Context) *sessions.Session {
session, _ := cookieStore.Get(ctx.Request(), "request-scope")
return session
}
对应的登陆页面信息显示:login.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>登录 — Echo 框架学习</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico">
<link href="https://cdn.bootcss.com/twitter-bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div class="container">
<div class="text-center m-2">
<img src="/img/logo.png" class="img-fluid" alt="Go语言中文网" width="64">
</div>
<h1 class="text-center">Echo 框架学习:登录案例</h1>
<hr>
<form class="form-horizontal" action="/login" method="post">
<div class="p-2 mb-2 bg-success text-white text-center">欢迎您,
<a href="/logout" class="text-white float-right">退出登录</a>
</div>
<div class="p-2 mb-2 bg-danger text-white text-center"></div>
<div class="form-group row">
<label for="username" class="col-sm-2 col-form-label">用户名</label>
<div class="col-sm-10">
<input type="text" class="form-control" id="username" name="username" value="" placeholder="请输入用户名">
</div>
</div>
<div class="form-group row">
<label for="passwd" class="col-sm-2 col-form-label">密 码</label>
<div class="col-sm-10">
<input type="password" class="form-control" id="passwd" name="passwd" placeholder="请输入密码">
</div>
</div>
<div class="form-group">
<div class="offset-sm-2 col-sm-10">
<div class="checkbox">
<label>
<input type="checkbox" name="remember_me" value="1"> 记住我
</label>
</div>
</div>
</div>
<div class="form-group row">
<div class="offset-sm-5 col-sm-5">
<button type="submit" class="btn btn-success">登录</button>
</div>
</div>
</form>
</div>
</body>
</html>