从 gin_scaffold 到网关后端 go_gateway
在正式开始代码的编写之前,先对 gin_scaffold 进行处理,完成代码的删除、修改,并为下一步代码的编写做好准备。
删除controller 、dao 、dto 目录下的内容,并删除 /router/route.go 文件中多余的 router,并替换项目中出现的目录内容,完成对正式开发的准备。
大纲:
- 登陆
- 登陆信息获取
- 退出
- 修改密码
管理员登陆接口
首先编写管理员登陆控制器初步框架
type AdminLoginController struct{}
func AdminLoginRegisterController(router *gin.RouterGroup) {
}
func (adminlogin *AdminLoginController) AdminLogin(c *gin.Context) {
}
接下来需要对请求参数进行校验。需要用户名与密码参数,因此在 dto 中进行参数的定义
package dto
type AdminLoginInput struct {
Username string `json:"username" form:"username" comment:"姓名" example:"admin" validate:"required"`
Password string `json:"password" form:"password" comment:"密码" example:"123456" validate:"required"`
}
在 Go 语言中,结构体字段后面可以带一个标签(Tag),用反引号
`包裹。标签是键值对的形式,用于为字段附加元信息, 这些元信息可以在运行时通过反射获取,从而被各种库或框架用于不同的目的
在管理员登录输入 AdminLoginInput 结构体中,对定义了上述标签:
其中,json 标签和 form 标签 分别指定对应字段在 JSON 序列化/反序列化时的键名和 HTTP 表单中的参数名。 comment 标签和 example 标签通常在生成 API 文档时使用。 而 validate 标签用于定义字段的验证规则。
在 Go 语言中,结构体的标签不占用结构体实例的内存。标签是类型的一部分,而不是值的一部分,它们存储在程序的只读数据段(
.rodata)中,每个类型只会存储一份,与创建了多少个结构体实例无关。标签字符串(如
json:"username")在编译时被嵌入到二进制文件的只读数据区,作为类型元数据的一部分。Go 的反射包(reflect)通过访问类型的元信息来获取标签,而不是从实例中读取。
创建绑定结构体并校验参数函数:
func (param *AdminLoginInput) BindValidParam(c *gin.Context) error {
return public.DefaultGetValidParams(c, param)
}
随后完成 controller 注册到 group 的代码:
type AdminLoginController struct{}
func AdminLoginRegisterController(router *gin.RouterGroup) {
adminLogin := &AdminLoginController{}
group.POST("/login", adminLogin.AdminLogin)
}
func (adminlogin *AdminLoginController) AdminLogin(c *gin.Context) {
// 接下来需要对请求参数进行校验。需要用户名与密码参数,因此在 dto 中进行参数的定义
params := &dto.AdminLoginInput{}
if err := params.BindValidParam(c); err != nil {
middleware.ResponseError(c, 1001, err)
return
}
middleware.ResponseSuccess(c, "")
}
随后实现路由注册,将下面代码添加到 route.go 中:
adminLoginRouter := router.Group("/admin_login")
store, err := sessions.NewRedisStore(10, "tcp", "localhost:6379", "", []byte("secret"))
if err != nil {
log.Fatal("sessions.NewRedisStore err:%v", err)
}
// 用 Router 设置关于 session 的中间件
adminLoginRouter.Use(
sessions.Sessions("mysession", store),
middleware.RecoveryMiddleware(),
middleware.RequestLog(),
middleware.TranslationMiddleware())
// 为 adminLoginRouter group 注册子 group
{
controller.AdminLoginRegister(adminLoginRouter)
}
最后进行测试,服务器应当正确运行并输出:
2026/03/02 21:53:04 ------------------------------------------------------------------------
2026/03/02 21:53:04 [INFO] config=./conf/dev/
2026/03/02 21:53:04 [INFO] start loading resources.
2026/03/02 21:53:04 [INFO] success loading resources.
2026/03/02 21:53:04 ------------------------------------------------------------------------
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.
[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
- using env: export GIN_MODE=release
- using code: gin.SetMode(gin.ReleaseMode)
[GIN-debug] GET /ping --> go_gateway_demo/router.InitRouter.func1 (3 handlers)
[GIN-debug] GET /swagger/*any --> github.com/swaggo/gin-swagger.CustomWrapHandler.func1 (3 handlers)
[GIN-debug] POST /admin_login/login --> go_gateway_demo/controller.(*AdminLoginController).AdminLogin-fm (7 handlers)
2026/03/02 21:53:04 [INFO] HttpServerRun::8880
使用 curl 进行测试应当响应:
curl.exe -X POST "http://127.0.0.1:8880/admin_login/login" -d "username=1111&password=2222"
{"errno":0,"errmsg":"","data":"","trace_id":"a9fefcc169a596efd22868cc5a802db0","stack":null}
接下来分别通过两种方式完成用户名和密码的教研:写死与数据库校验
首先对验证器 tag 进行扩充,并在 /middleware/translation.go 中对验证器进行实现:
Username string `json:"username" form:"username" comment:"管理员用户名" example:"admin" validate:"required,is_valid_username"`
//自定义验证方法
//https://github.com/go-playground/validator/blob/v9/_examples/custom-validation/main.go
val.RegisterValidation("is_valid_username", func(fl validator.FieldLevel) bool {
return fl.Field().String() == "admin"
})
//自定义翻译器
//https://github.com/go-playground/validator/blob/v9/_examples/translations/main.go
val.RegisterTranslation("is_valid_username", trans, func(ut ut.Translator) error {
return ut.Add("is_valid_username", "{0} 填写不正确哦", true)
}, func(ut ut.Translator, fe validator.FieldError) string {
t, _ := ut.T("is_valid_username", fe.Field())
return t
})
接口文档生成
在下面的内容中,我们进行接口文档的编写,便于开发过程中的测试。
在 controller/admin_login.go 中新增如下内容,并在 dto/admin_login.go 中新增 Output 结构体:
// AdminLogin godoc
// @Summary 管理员登陆
// @Description 管理员登陆
// @Tags 管理员登录接口
// @ID /admin_login/login
// @Accept json
// @Produce json
// @Param body body dto.AdminLoginInput true "body"
// @Success 200 {object} middleware.Response{data=dto.AdminLoginOutput} "success"
// @Router /admin_login/login [post]
func (adminlogin *AdminLoginController) AdminLogin(c *gin.Context) {
// 接下来需要对请求参数进行校验。需要用户名与密码参数,因此在 dto 中进行参数的定义
params := &dto.AdminLoginInput{}
if err := params.BindValidParam(c); err != nil {
middleware.ResponseError(c, 1001, err)
return
}
out := &dto.AdminLoginOutput{Token: params.Username}
fmt.Println(out)
middleware.ResponseSuccess(c, "")
}
type AdminLoginOutput struct {
Token string `json:"token" form:"token" comment:"token" example:"token" validate:""`
}
运行 swag init 指令并启动服务器,可以在 Swagger UI 地址查看到接口文档。
登陆接口开发
在定义输入、输出和接口文档后,开始进行登陆业务逻辑的开发。
- 通过 params.UserName 取得管理员信息 admininfo
- 使用 admininfo.salt 和 params.Password 进行 sha256 加密得到 saltPassword
- 校验 if saltPassword ==admininfo.password
在第一步中涉及到数据库查询,因此在 /dao 目录下创建 admin.go ,实现对数据库中 gateway_admin 表格的查询
package dao
import (
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type Admin struct {
Id int `json:"id" gorm:"primary_key" description:"自增主键"`
UserName string `json:"user_name" gorm:"column:user_name" description:"管理员用户名"`
Salt string `json:"salt" gorm:"column:salt" description:"盐"`
Password string `json:"password" gorm:"column:password" description:"密码"`
UpdatedAt time.Time `json:"update_at" gorm:"column:update_at" description:"更新时间"`
CreatedAt time.Time `json:"create_at" gorm:"column:create_at" description:"创建时间"`
IsDelete int `json:"is_delete" gorm:"column:is_delete" description:"是否删除"`
}
func (t *Admin) TableName() string {
return "gateway_admin"
}
func (t *Admin) Find(c *gin.Context, tx *gorm.DB, search *Admin) (*Admin, error) {
out := &Admin{}
err := tx.WithContext(c).Where(search).Find(out).Error
if err != nil {
return nil, err
}
return out, nil
}
由此可以实现取得管理员信息,接下来实现加 salt 操作
func (t *Admin) LoginCheck(c *gin.Context, tx *gorm.DB, param *dto.AdminLoginInput) (*Admin, error) {
adminInfo, err := t.Find(c, tx, (&Admin{UserName: param.UserName, IsDelete: 0}))
if err != nil {
return nil, errors.New("用户信息不存在")
}
//param.Password
//adminInfo.Salt
return nil, nil
}
至此,需要一个新的方法构建 saltpassword ,因此在 public 目录下创建 util.go 进行编写:
package public
import (
"crypto/sha256"
"fmt"
)
func GenSaltPassword(salt, password string) string {
s1 := sha256.New()
s1.Write([]byte(password))
str1 := fmt.Sprintf("%x", s1.Sum(nil))
s2 := sha256.New()
s2.Write([]byte(str1 + salt))
return fmt.Sprintf("%x", s2.Sum(nil))
}
由此,就可以实现密码校验的流程:
//param.Password
//adminInfo.Salt
saltPassword := public.GenSaltPassword(adminInfo.Salt, param.Password)
if adminInfo.Password != saltPassword {
return nil, errors.New("密码错误,请重新输入")
}
完成登陆信息的校验后,进行 session 的设置,首先在 /dto/admin_login.go 中创建 session 的结构体:
type AdminSessionInfo struct {
ID int `json:"id"`
UserName string `json:"username"`
LoginTime time.Time `json:"login_time"`
}
随后实现 session 的设置:
// 设置session
sessInfo := &dto.AdminSessionInfo{
ID: admin.Id,
UserName: admin.UserName,
LoginTime: time.Now(),
}
sessBts, err := json.Marshal(sessInfo)
if err != nil {
middleware.ResponseError(c, 2003, err)
return
}
sess := sessions.Default(c)
sess.Set(public.AdminSessionInfoKey, string(sessBts))
sess.Save()
至此,登陆接口的开发基本完成,在接下来的开发中继续实现后续的接口开发。
登陆信息获取接口开发
在之前的实现中,登陆采用的是 adminlogin 接口,在注册路由的流程中不对登陆信息进行校验。
获取登陆信息则需要对信息进行校验, 因此需要重新建立一个 controller ,并使用 session 校验中间件(/middleware/session_auth.go)
接口内部流程如下:
- 读取 sessionKey 对应 json 转换为结构体
- 取出数据然后封装输出结构体
首先定义输出的结构体 AdminInfoOutput:
package dto
import "time"
type AdminInfoOutput struct {
ID int `json:"id"`
UserName string `json:"name"`
LoginTime time.Time `json:"login_time"`
Avatar string `json:"avatar"`
Introduction string `json:"introduction"`
Roles []string `json:"roles"`
}
随后实现 controller 逻辑并进行注册:
package controller
import (
"encoding/json"
"fmt"
"go_gateway_demo/dto"
"go_gateway_demo/middleware"
"go_gateway_demo/public"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
type AdminController struct{}
func AdminRegister(group *gin.RouterGroup) {
adminLogin := &AdminController{}
group.GET("/admin_info", adminLogin.AdminInfo)
}
// AdminInfo godoc
// @Summary 管理员信息
// @Description 管理员信息
// @Tags 管理员登录接口
// @ID /admin/admin_info
// @Accept json
// @Produce json
// @Success 200 {object} middleware.Response{data=dto.AdminInfoOutput} "success"
// @Router /admin/admin_info [get]
func (adminlogin *AdminController) AdminInfo(c *gin.Context) {
// 1. 读取 sessionKey 对应 json 转换为结构体
// 2. 取出数据然后封装输出结构体
sess := sessions.Default(c)
sessInfo := sess.Get(public.AdminSessionInfoKey)
adminSessionInfo := &dto.AdminSessionInfo{}
if err := json.Unmarshal([]byte(fmt.Sprint(sessInfo)), adminSessionInfo); err != nil {
middleware.ResponseError(c, 2000, err)
return
}
out := &dto.AdminInfoOutput{
ID: adminSessionInfo.ID,
Name: adminSessionInfo.UserName,
LoginTime: adminSessionInfo.LoginTime,
Avatar: "",
Introduction: "I am a super administrator",
Roles: []string{"admin"},
}
middleware.ResponseSuccess(c, out)
}
adminRouter := router.Group("/admin")
adminRouter.Use(
sessions.Sessions("mysession", store),
middleware.RecoveryMiddleware(),
middleware.RequestLog(),
middleware.SessionAuthMiddleware(),
middleware.TranslationMiddleware())
{
controller.AdminRegister(adminRouter)
}
中间件 session_auth 的认证逻辑如下:
package middleware
import (
"errors"
"go_gateway_demo/public"
"github.com/gin-gonic/contrib/sessions"
"github.com/gin-gonic/gin"
)
func SessionAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
session := sessions.Default(c)
if adminInfo, ok := session.Get(public.AdminSessionInfoKey).(string); !ok || adminInfo == "" {
ResponseError(c, InternalErrorCode, errors.New("user not login"))
c.Abort()
return
}
c.Next()
}
}
退出接口开发
退出接口逻辑相对简单,在登陆接口的基础上进行实现即可:
// AdminLogin godoc
// @Summary 管理员退出
// @Description 管理员退出
// @Tags 管理员接口
// @ID /admin_login/logout
// @Accept json
// @Produce json
// @Success 200 {object} middleware.Response{data=string} "success"
// @Router /admin_login/logout [get]
func (adminlogin *AdminLoginController) AdminLogout(c *gin.Context) {
sess := sessions.Default(c)
sess.Delete(public.AdminSessionInfoKey)
sess.Save()
middleware.ResponseSuccess(c, "")
}
并为 AdminLoginRegister 添加内容:
func AdminLoginRegister(group *gin.RouterGroup) {
adminLogin := &AdminLoginController{}
group.POST("/login", adminLogin.AdminLogin)
group.GET("/logout", adminLogin.AdminLogout)
}
密码修改接口开发
密码修改的逻辑与登陆逻辑相似,均需要对用户 session 信息进行校验,具体的流程如下:
- session 读取用户信息到结构体 sessInfo
- sessInfo.ID 读取数据库信息 adminInfo
- params.password + adminInfo.salt sha256 saltPassword
- adminInfo ==> adminInfo.password 执行数据保存
代码实现及接口文档生成代码如下:
// AdminInfo godoc
// @Summary 修改密码
// @Description 修改密码
// @Tags 管理员接口
// @ID /admin/change_pwd
// @Accept json
// @Produce json
// @Param body body dto.ChangePwdInput true "body"
// @Success 200 {object} middleware.Response{data=string} "success"
// @Router /admin/change_pwd [post]
func (adminlogin *AdminController) ChangePwd(c *gin.Context) {
params := &dto.ChangePwdInput{}
if err := params.BindValidParam(c); err != nil {
middleware.ResponseError(c, 2001, err)
return
}
sess := sessions.Default(c)
sessInfo := sess.Get(public.AdminSessionInfoKey)
adminSessionInfo := &dto.AdminSessionInfo{}
if err := json.Unmarshal([]byte(fmt.Sprint(sessInfo)), adminSessionInfo); err != nil {
middleware.ResponseError(c, 2000, err)
return
}
// 从数据库中读取 adminInfo
tx, err := lib.GetGormPool("default")
if err != nil {
middleware.ResponseError(c, 2001, err)
return
}
adminInfo := &dao.Admin{}
adminInfo, err = adminInfo.Find(c, tx, (&dao.Admin{UserName: adminSessionInfo.UserName}))
if err != nil {
middleware.ResponseError(c, 2002, err)
return
}
saltPassword := public.GenSaltPassword(adminInfo.Salt, params.Password)
adminInfo.Password = saltPassword
if err := adminInfo.UpdatePassword(c, tx, saltPassword); err != nil {
middleware.ResponseError(c, 2003, err)
return
}
middleware.ResponseSuccess(c, "")
}
UpdatePassword 逻辑如下:
func (t *Admin) UpdatePassword(c *gin.Context, tx *gorm.DB, newPassword string) error {
return tx.WithContext(c).Model(t).Update("password", newPassword).Error
}
结语
至此,登陆管理功能接口开发基本完成,完整实现了登陆、登陆信息获取、退出和修改密码四个功能,接口文档页面如下:
