【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。

本文主要是介绍【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

上一篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(一)搭建项目

在这里插入图片描述

上一篇我们搭好了项目框架,那接下来就可以开始写业务了,然后在写业务之前,我们还需要考虑几个问题:

  1. 首先一个就是日志问题,控制台日志输出、日志输出到文件;
  2. 第二个就是token校验,每次请求服务器时,在执行接口业务之前,需要先校验请求头的token是否有效;
  3. 第三个就是请求成功后,返回回去的参数需要统一。

日志

我们先来说说日志,我们需要控制全局日志输出,输出打印时间、请求的接口、错误信息、SQL语句等这些信息。除了将日志输出到控制台之外,我们还需要将日志输出到文件,按天生成,像这样:

在这里插入图片描述

全局日志我们可以用中间件来实现,中间件里输出的日志就是请求的接口、接口执行报错的错误信息等。

除了在中间件里拦截一些信息作为日志输出外,我们还可以自定义日志格式化输出。比如说你在代码的哪个位置想输出一句话,用fmt.Print() 也是可以直接打印的,但是我想要进行格式输出打印时间、具体的文件、具体的某一行,这就需要我们去自定义实现输出格式了。

还有就是除了上面两个日志输出,我操作数据库的时候,我也希望将具体的sql打印出来。

代码

日志输出中间件

middleware.go

// 统一的日志格式化输出中间件
func LoggerPrint() fiber.Handler {return func(c *fiber.Ctx) error {start := time.Now()// 处理请求err := c.Next()var logMessage stringif err != nil {// 记录日志logMessage = fmt.Sprintf("[%s] %s %s - %s ==> [Error] %s\n", start.Format("2006-01-02 15:04:05"), c.Method(), c.Path(), time.Since(start), err.Error())} else {// 记录日志logMessage = fmt.Sprintf("[%s] %s %s - %s\n", start.Format("2006-01-02 15:04:05"), c.Method(), c.Path(), time.Since(start))}// 输出到控制台fmt.Print(logMessage)// 输出到文件filename := "logs/" + time.Now().Format("2006-01-02") + ".log"file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err != nil {mylog.Error("日志文件的打开错误 : " + err.Error())return err}defer file.Close()if _, err := file.WriteString(logMessage); err != nil {mylog.Error("写入日志文件错误 : " + err.Error())}return err}
}

自定义格式日志输出

log.go

package mylogimport ("fmt""gorm.io/gorm/logger""os""path/filepath""runtime""strings""time"
)func Info(msg string) {logOut(msg, "Info")
}func Debug(msg string) {logOut(msg, "Debug")
}func Error(msg string) {logOut(msg, "Error")
}// 日志格式化输出
func LogOut(msg string) {// 替换掉彩色打印符号msg = strings.ReplaceAll(msg, logger.Reset, "")msg = strings.ReplaceAll(msg, logger.Red, "")msg = strings.ReplaceAll(msg, logger.Green, "")msg = strings.ReplaceAll(msg, logger.Yellow, "")msg = strings.ReplaceAll(msg, logger.Blue, "")msg = strings.ReplaceAll(msg, logger.Magenta, "")msg = strings.ReplaceAll(msg, logger.Cyan, "")msg = strings.ReplaceAll(msg, logger.White, "")msg = strings.ReplaceAll(msg, logger.BlueBold, "")msg = strings.ReplaceAll(msg, logger.MagentaBold, "")msg = strings.ReplaceAll(msg, logger.RedBold, "")msg = strings.ReplaceAll(msg, logger.YellowBold, "")logOutFile(msg) // 输出到文件
}// 日志格式化输出
func logOut(msg string, level string) {start := time.Now()// 获取调用的文件和行号_, file, line, _ := runtime.Caller(2)file = filepath.Base(file)// 使用日志包记录日志,并包括级别、文件名和行号logMsg := fmt.Sprintf("[%s] - %s ==> [%s:%d] [%s] %s\n",start.Format("2006-01-02 15:04:05"),time.Since(start),file,line,level,msg,)fmt.Print(logMsg)  // 打印到控制台logOutFile(logMsg) // 输出到文件
}// 日志输出到文件
func logOutFile(msg string) {// 输出到文件filename := "logs/" + time.Now().Format("2006-01-02") + ".log"file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err != nil {fmt.Println("日志文件的打开错误 :", err)}defer file.Close()if _, err := file.WriteString(msg); err != nil {fmt.Println("写入日志文件错误 :", err)}
}

gorm SQL日志输出到文件

控制 Gorm 的 SQL 日志输出,在 application.go 控制就行,然后我上一篇是有把 application.go 的代码写出来的,这里直接在上一篇的基础上把下面的代码复制过去就行。

application.go

type Writer struct{}// 自定义的sql日志输出(到文件)
func (w Writer) Printf(format string, args ...interface{}) {msg := fmt.Sprintf(format, args...) + "\n"fmt.Println(msg)  // 打印到控制台mylog.LogOut(msg) // 输出到文件
}func LoadMysql() {dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=%s",Config.Get("database.username"),Config.Get("database.password"),Config.Get("database.host"),Config.GetInt("database.port"),Config.Get("database.database"),Config.Get("database.timeout"))// 设置操作数据库的日志输出到文件mylogger := logger.New(Writer{},logger.Config{SlowThreshold: time.Second, // 慢 SQL 阈值LogLevel:      logger.Info, // Log levelColorful:      true,        // 允许彩色打印},)db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{// 跳过默认事务:为了确保数据一致性,GORM 会在事务里执行写入操作(创建、更新、删除)。如果没有这方面的要求,您可以在初始化时禁用它,这样可以获得60%的性能提升//SkipDefaultTransaction: true,//Logger: logger.Default.LogMode(logger.Info), // sql全局日志Logger: mylogger, // 使用自定义的日志输出NamingStrategy: schema.NamingStrategy{//TablePrefix:   "sys_",  // 表名前缀SingularTable: true, // 单数表名//NoLowerCase:   false, // 关闭小写转换},})if err != nil {fmt.Println("无法连接到MySQL :", err)}DB = db
}// 省略其他代码......

你别说日志输出这一块当时纠结了我挺久的。。。

token校验中间件

校验token和请求的IP。我这个系统的token不是用的jwt生成的,就是一串随机字符串生成的。

middleware.go

// token校验
func CheckToken(c *fiber.Ctx) error {parsedIP := c.IP() // 获取用户请求的ip// 校验用户 IP 是否在白名单中if !IsIPInWhitelist(parsedIP) {return c.Status(http.StatusOK).JSON(config.Error("非法访问"))}path := c.Path() // 这里获取的是接口的完整路径,如:/sys/login、/sys/user/list// 排除指定接口,不校验tokenif path == "/sys/login" || path == "/sys/getKey" || path == "/sys/getCode" {return c.Next()}// 获取请求头中的 Tokentoken := c.Get(config.TokenHeader)// 如果 Token 为空,返回未授权状态if token == "" {return c.Status(http.StatusOK).JSON(config.ErrorCode(1003, "用户未登录"))}// 校验携带的token在redis中是否存在val := config.RedisConn.HExists(config.CachePrefix+token, "token").Val()if !val {return c.Status(http.StatusOK).JSON(config.ErrorCode(1003, "用户未登录"))}// 刷新有效期v := model.GetCreateTime(token) // 获取token的创建时间//判断token的创建时间是否大于2小时,如果是的话则需要刷新tokens := time.Now().Unix() - vhour := s / 1000 / (60 * 60)if hour > 2 {// TODO 获取当前用户信息,重新登录,生成新的token,将新token设置到响应头中user := model.GetLoginUser(token)// TODO 这里重新登录(会把旧登录删除),生成新的tokensplits := strings.Split(token, "_")var newToken stringif len(splits) > 1 {newToken = user.Login(splits[0], config.TokenExpire)} else {newToken = user.Login("", config.TokenExpire)}if len(newToken) == 0 {return c.Status(http.StatusOK).JSON(config.Error("登录失败"))}token = newTokenc.Response().Header.Set(config.TokenHeader, newToken) // 将新的token设置到请求头中}timeOut := model.GetTimeOut(token) // 获取token的过期时间if timeOut != -1 {                 // token没过期,过期时间不是-1的时候,每次请求都刷新过期时间model.UpdateTimeOut(token, config.TokenExpire)}return c.Next()
}// 辅助函数:检查 IP 是否在白名单中
func IsIPInWhitelist(ip string) bool {parsedIP := net.ParseIP(ip)for _, allowedIP := range config.AuthHost {if allowedIP == "*" {return true}if strings.Contains(allowedIP, "*") {// 将 * 转换为正则表达式regexPattern := strings.ReplaceAll(allowedIP, "*", ".*")if match, _ := regexp.MatchString("^"+regexPattern+"$", ip); match {return true}} else {// 非通配符的精确匹配if parsedIP.Equal(net.ParseIP(allowedIP)) {return true}}}return false
}

定义baseModel、全局常量、统一返回参数等

这个 base_model.go 文件就是定义一些全局需要用到的 结构体、常量、返回参数 等。这些放在一起找起来、看起来比较方便,不用在各个文件跳来跳去。只要你代码写好注释、规划好哪一块连续放在一起、风格统一,就不会显得杂乱。

base_model.go

package configimport "time"// ===================================== 公共常量 =====================================
const (CachePrefix       = "go-web:login:"                                                  // 缓存前缀ERROR_COUNT       = "go-web:errorCount:"                                             // 密码错误次数缓存keyTokenHeader       = "go-web"                                                         // request请求头属性TokenExpire       = time.Second * 1800                                               // token默认有效期(单位秒)UNKNOWN_EXCEPTION = "未知异常"                                                           // 全局异常 未知异常PARENT_VIEW       = "ParentView"                                                     // ParentView组件标识InitPassword      = "123456"                                                         // 初始密码RandomCharset     = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" // 随机字符串RandomCaptcha     = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ"                               // 验证码字符串DATA_SCOPE        = "go-web:dataScope:"                                              // 数据范围缓存
)// ==================================== 公共model ====================================// 一个公共的model
type BaseModel struct {Id         string    `query:"id" json:"id" form:"id"`CreatorId  *string   `json:"creatorId" form:"creatorId"`CreateTime time.Time `json:"createTime" form:"createTime"`UpdateId   *string   `json:"updateId" form:"updateId"`UpdateTime *string   `json:"updateTime" form:"updateTime"`Token      string    `gorm:"-" json:"token" form:"token"` // token
}// 统一的返回参数格式
type Result struct {Code    int    `json:"code"`    // 统一的返回码,0 成功 -1 失败Message string `json:"message"` // 统一的返回信息Data    any    `json:"data"`    // 统一的返回数据
}// 统一的树形结构格式
/*type TreeVo struct {Id       string   `json:"id"`       // 统一的返回码,0 成功 -1 失败Label    string   `json:"label"`    // 统一的返回信息ParentId string   `json:"parentId"` // 统一的返回数据Children []TreeVo `json:"children"` // 子级数据
}*/// 将list转为统一的树形结构格式
/*func ConvertToTreeVo(list []interface{}) []TreeVo {result := []TreeVo{}for _, t := range list {item, _ := t.(map[string]interface{})id, _ := item["id"].(string)parentId, _ := item["parentId"].(string)label, _ := item["name"].(string)children, _ := item["children"].([]interface{})tree := TreeVo{Id: id, ParentId: parentId, Label: label}tree.Children = ConvertToTreeVo(children)result = append(result, tree)}return result
}*/// 请求成功的默认返回
func Success(obj any) Result {return Result{0, "ok", obj}
}// 请求失败的默认返回,code默认为-1
func Error(message string) Result {return ErrorCode(-1, message)
}// 请求失败的默认返回
func ErrorCode(code int, message string) Result {return Result{code, message, nil}
}// 分页结构体封装
type PageInfo struct {List  any   `json:"list"`  // 返回结果Total int64 `json:"total"` // 返回总数
}

路由

在go中,我们写的controller接口是需要全部手动注册到路由中的,在路由中写具体的接口名,在controller中写接口的具体实现函数或方法。

然后在注册路由之前,我们需要先注册中间件。

router.go

package routerimport ("fmt""github.com/gofiber/fiber/v2""go-web2/app/common/config""go-web2/app/common/middleware"api "go-web2/app/controller/sys" // 这种写法是,如果包名重复了,我们可以在前面定义这个包名用于区分重复的包
)// 初始化路由
func InitRouter() *fiber.App {app := fiber.New()// 初始化yml配置_, err := config.InitConfig()if err != nil {panic(fmt.Errorf("加载yml配置文件错误: %s \n", err))}// 中间件app.Use(middleware.LoggerPrint) // 使用日志中间件app.Use(middleware.SetHeader)     // 设置统一的请求头app.Use(middleware.CheckToken)    // 应用 token 校验中间件到需要验证的路由//app.common.config.StartScheduledTask() // 调用定时任务// 注册路由loginRouter(app) // 登录logRouter(app)   // 日志管理safeRouter(app)  // 安全设置userRouter(app)  // 用户管理deptRouter(app)  // 部门管理roleRouter(app)  // 角色管理menuRouter(app)  // 菜单管理dictRouter(app)  // 字典管理return app
}// 登录路由
func loginRouter(app *fiber.App) {controller := api.LoginController{}login := app.Group("/sys"){login.Get("/getKey", controller.GetKey)    // 获取RSA公钥login.Get("/getCode", controller.GetCode)  // 获取验证码login.Post("/login", controller.Login)     // 用户登录login.Delete("/logout", controller.Logout) // 用户退出}
}// 日志管理路由
func logRouter(app *fiber.App) {log := app.Group("/sys/log"){log.Get("/list", api.LogController{}.GetPage) // 日志列表}
}// 安全设置路由
func safeRouter(app *fiber.App) {controller := api.SafeController{}safe := app.Group("/sys/safe"){safe.Get("/getSafeSet", controller.GetSafeSet) // 获取安全设置safe.Post("/update", controller.Update)        // 修改安全设置}
}// 用户管理路由
func userRouter(app *fiber.App) {controller := api.UserController{}user := app.Group("/sys/user"){user.Get("/getLoginUser", controller.GetLoginUser)      // 获取当前登录的用户user.Get("/list", controller.GetPage)                   // 用户列表user.Get("/getById/:id", controller.GetById)            // 根据id获取用户user.Post("/insert", controller.Insert)                 // 新增用户user.Post("/update", controller.Update)                 // 修改用户user.Delete("/delete", controller.Delete)               // 删除用户user.Post("/updatePassword", controller.UpdatePassword) // 修改密码user.Post("/resetPassword", controller.ResetPassword)   // 重置密码user.Post("/upload", controller.Upload)                 // 上传头像}
}// 部门管理路由
func deptRouter(app *fiber.App) {controller := api.DeptController{}dept := app.Group("/sys/dept"){dept.Get("/list", controller.GetList)             // 部门树列表dept.Get("/getById/:id", controller.GetById)      // 根据id获取部门dept.Post("/insert", controller.Insert)           // 新增部门dept.Post("/update", controller.Update)           // 修改部门dept.Delete("/delete/:id", controller.Delete)     // 删除部门dept.Get("/deptSelect", controller.GetSelectList) // 部门下拉树列表}
}// 角色管理路由
func roleRouter(app *fiber.App) {controller := api.RoleController{}role := app.Group("/sys/role"){role.Get("/list", controller.GetPage)              // 角色列表role.Get("/getById/:id", controller.GetById)       // 根据id获取角色role.Get("/createRoleCode", controller.CreateCode) // 生成角色编码role.Post("/insert", controller.Insert)            // 新增角色role.Post("/update", controller.Update)            // 修改角色role.Post("/updateState", controller.UpdateState)  // 修改角色状态role.Delete("/delete", controller.Delete)          // 删除角色role.Get("/roleSelect", controller.GetSelectList)  // 角色下拉框}
}// 菜单管理路由
func menuRouter(app *fiber.App) {controller := api.MenuController{}menu := app.Group("/sys/menu"){menu.Get("/list", controller.GetList)                      // 菜单列表menu.Get("/getRouters", controller.GetRouters)             // 路由列表menu.Get("/getById/:id", controller.GetById)               // 根据id获取菜单menu.Get("/roleMenuTree/:roleId", controller.RoleMenuTree) // 获取对应角色菜单列表树menu.Post("/insert", controller.Insert)                    // 新增菜单menu.Post("/update", controller.Update)                    // 修改菜单menu.Delete("/delete/:id", controller.Delete)              // 删除菜单}
}// 字典管理路由
func dictRouter(app *fiber.App) {controller := api.DictController{}dict := app.Group("/sys/dict"){dict.Get("/typeList", controller.GetTypeList)         // 获取字段类型列表dict.Get("/list", controller.GetPage)                 // 字段项列表分页dict.Get("/getById/:id", controller.GetById)          // 根据id获取字段dict.Get("/createDictCode", controller.CreateCode)    // 生成字典代码dict.Get("/hasDictByName", controller.HasByName)      // 字典名称是否存在dict.Get("/hasDictByCode", controller.HasByCode)      // 字典代码是否存在dict.Post("/insert", controller.Insert)               // 新增字典dict.Post("/update", controller.Update)               // 修改字典dict.Delete("/deleteType/:id", controller.DeleteType) // 删除字典类型dict.Delete("/delete", controller.Delete)             // 删除字典dict.Get("/getByTypeCode", controller.GetByTypeCode)  // 根据字典类型代码获取字典项列表}
}

然后main.go就改成下面的写法:

package mainimport ("fmt""go-web2/app/common/config""go-web2/app/common/util""go-web2/app/router"
)func main() {app := router.InitRouter()util.GenerateKeyPair() // 初始化RSA密钥对app.Listen(fmt.Sprintf(":%d", config.HTTPPort))
}

最后

middleware.go 中间件完整代码

package middlewareimport ("fmt""github.com/gofiber/fiber/v2""go-web2/app/common/config""go-web2/app/common/mylog"model "go-web2/app/model/sys""net""net/http""os""regexp""strings""time"
)// ================================================= 中间件合集 =================================================// 统一的日志格式化输出中间件
func LoggerPrint() fiber.Handler {return func(c *fiber.Ctx) error {start := time.Now()// 处理请求err := c.Next()var logMessage stringif err != nil {// 记录日志logMessage = fmt.Sprintf("[%s] %s %s - %s ==> [Error] %s\n", start.Format("2006-01-02 15:04:05"), c.Method(), c.Path(), time.Since(start), err.Error())} else {// 记录日志logMessage = fmt.Sprintf("[%s] %s %s - %s\n", start.Format("2006-01-02 15:04:05"), c.Method(), c.Path(), time.Since(start))}// 输出到控制台fmt.Print(logMessage)// 输出到文件filename := "logs/" + time.Now().Format("2006-01-02") + ".log"file, err := os.OpenFile(filename, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)if err != nil {mylog.Error("日志文件的打开错误 : " + err.Error())return err}defer file.Close()if _, err := file.WriteString(logMessage); err != nil {mylog.Error("写入日志文件错误 : " + err.Error())}return err}
}// 设置请求头
func SetHeader(c *fiber.Ctx) error {c.Set("Set-Cookie", "name=value; SameSite=Strict; cookiename=httponlyTest;Path=/;Domain=domainvalue;Max-Age=seconds;Secure;HTTPOnly")c.Set("Content-Security-Policy", "default-src 'self'; script-src 'self'; frame-ancestors 'self'; object-src 'none'")c.Set("Access-Control-Allow-Credentials", "true")c.Set("Referrer-Policy", "no-referrer")c.Set("X-XSS-Protection", "1; mode=block") //1; mode=block:启用XSS保护,并在检查到XSS攻击时,停止渲染页面c.Set("X-Content-Type-Options", "nosniff") //互联网上的资源有各种类型,通常浏览器会根据响应头的Content-Type字段来分辨它们的类型。通过这个响应头可以禁用浏览器的类型猜测行为c.Set("X-Frame-Options", "SAMEORIGIN")     //SAMEORIGIN:不允许被本域以外的页面嵌入c.Set("X-DNS-Prefetch-Control", "off")c.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")c.Set("Cache-Control", "no-cache, no-store, must-revalidate")c.Set("Pragma", "no-cache")c.Set("Expires", "0")return c.Next()
}// token校验
func CheckToken(c *fiber.Ctx) error {parsedIP := c.IP() // 获取用户请求的ip// 校验用户 IP 是否在白名单中if !IsIPInWhitelist(parsedIP) {return c.Status(http.StatusOK).JSON(config.Error("非法访问"))}path := c.Path() // 这里获取的是接口的完整路径,如:/sys/login、/sys/user/list// 排除指定接口,不校验tokenif path == "/sys/login" || path == "/sys/getKey" || path == "/sys/getCode" {return c.Next()}// 获取请求头中的 Tokentoken := c.Get(config.TokenHeader)// 如果 Token 为空,返回未授权状态if token == "" {return c.Status(http.StatusOK).JSON(config.ErrorCode(1003, "用户未登录"))}// 校验携带的token在redis中是否存在val := config.RedisConn.HExists(config.CachePrefix+token, "token").Val()if !val {return c.Status(http.StatusOK).JSON(config.ErrorCode(1003, "用户未登录"))}// 刷新有效期v := model.GetCreateTime(token) // 获取token的创建时间//判断token的创建时间是否大于2小时,如果是的话则需要刷新tokens := time.Now().Unix() - vhour := s / 1000 / (60 * 60)if hour > 2 {// TODO 获取当前用户信息,重新登录,生成新的token,将新token设置到响应头中user := model.GetLoginUser(token)// TODO 这里重新登录(会把旧登录删除),生成新的tokensplits := strings.Split(token, "_")var newToken stringif len(splits) > 1 {newToken = user.Login(splits[0], config.TokenExpire)} else {newToken = user.Login("", config.TokenExpire)}if len(newToken) == 0 {return c.Status(http.StatusOK).JSON(config.Error("登录失败"))}token = newTokenc.Response().Header.Set(config.TokenHeader, newToken) // 将新的token设置到请求头中}timeOut := model.GetTimeOut(token) // 获取token的过期时间if timeOut != -1 {                 // token没过期,过期时间不是-1的时候,每次请求都刷新过期时间model.UpdateTimeOut(token, config.TokenExpire)}return c.Next()
}// 辅助函数:检查 IP 是否在白名单中
func IsIPInWhitelist(ip string) bool {parsedIP := net.ParseIP(ip)for _, allowedIP := range config.AuthHost {if allowedIP == "*" {return true}if strings.Contains(allowedIP, "*") {// 将 * 转换为正则表达式regexPattern := strings.ReplaceAll(allowedIP, "*", ".*")if match, _ := regexp.MatchString("^"+regexPattern+"$", ip); match {return true}} else {// 非通配符的精确匹配if parsedIP.Equal(net.ParseIP(allowedIP)) {return true}}}return false
}

基础工具 base_util.go

生成随机token、生成登录验证码、MD5加密、RSA非对称加密解密、盐值加密、中文首字母转大写、保存文件等等。

package utilimport ("crypto""crypto/md5"crand "crypto/rand" // 当出现两个相同的包名但父级包不同时,可以给引用起别名进行区分"crypto/rsa""crypto/sha256""crypto/x509""encoding/base64""encoding/hex""encoding/pem""github.com/gofiber/fiber/v2/log""github.com/mojocn/base64Captcha""github.com/mozillazg/go-pinyin""github.com/pkg/errors""go-web2/app/common/config""golang.org/x/crypto/bcrypt""image/color""io"mrand "math/rand""mime/multipart""os""path/filepath""strings""time"
)// 生成随机字符串作为令牌
func GenerateRandomToken(length int) string {tokenBytes := make([]byte, length)mrand.NewSource(time.Now().UnixNano())for i := range tokenBytes {tokenBytes[i] = config.RandomCharset[mrand.Intn(len(config.RandomCharset))]}return string(tokenBytes)
}// 验证码,第一个参数是验证码的上限个数(最多存多少个验证码),第二个参数是验证码的有效时间
var captchaStore = base64Captcha.NewMemoryStore(500, 1*time.Minute)// 生成验证码
func GenerateCaptcha(length, width, height int) (lid string, lb64s string) {var driver base64Captcha.Drivervar driverString base64Captcha.DriverString// 配置验证码信息captchaConfig := base64Captcha.DriverString{Height:          height,Width:           width,NoiseCount:      0,ShowLineOptions: 2 | 4,Length:          length,Source:          config.RandomCaptcha,BgColor: &color.RGBA{R: 3,G: 102,B: 214,A: 125,},Fonts: []string{"wqy-microhei.ttc"},}driverString = captchaConfigdriver = driverString.ConvertFonts()captcha := base64Captcha.NewCaptcha(driver, captchaStore)lid, lb64s, _ = captcha.Generate()//fmt.Println(lid)//fmt.Println(lb64s)return
}// 验证captcha是否正确
func CaptVerify(id string, capt string) bool {capt = strings.ToUpper(capt) // 默认将验证码转为大写// 第三个参数为true,表示校验完之后,这个验证码删除(不管校验是否通过)if captchaStore.Verify(id, capt, true) {return true} else {return false}
}// md5加密
func MD5(v string) string {d := []byte(v)m := md5.New()m.Write(d)return hex.EncodeToString(m.Sum(nil))
}var (PrivateKey *rsa.PrivateKeyPublicKey  rsa.PublicKey
)// 生成随机密钥对
func GenerateKeyPair() (*rsa.PrivateKey, *rsa.PublicKey) {var err anyPrivateKey, err = rsa.GenerateKey(crand.Reader, 2048) //生成私钥if err != nil {panic(err)}PublicKey = PrivateKey.PublicKey //生成公钥return PrivateKey, &PublicKey
}// 获取公钥
func GetPublicKey() string {publicKeyBytes := x509.MarshalPKCS1PublicKey(&PublicKey)pemBlock := &pem.Block{Type:  "PUBLIC KEY",Bytes: publicKeyBytes,}return base64.StdEncoding.EncodeToString(pem.EncodeToMemory(pemBlock))
}// RSA加密
func RSAEncrypt(str string) string {//根据公钥加密encryptedBytes, err := rsa.EncryptOAEP(sha256.New(), crand.Reader, &PublicKey, []byte(str), nil)if err != nil {panic(err)return ""}// 加密后进行base64编码encryptBase64 := base64.StdEncoding.EncodeToString(encryptedBytes)return encryptBase64
}// RSA解密
func RSADecrypt(str string) string {// base64解码decodedBase64, err := base64.StdEncoding.DecodeString(str)if err != nil {panic(err)log.Error(err.Error())return ""}//根据私钥解密decryptBytes, err := PrivateKey.Decrypt(nil, decodedBase64, &rsa.OAEPOptions{Hash: crypto.SHA256})if err != nil {panic(err)log.Error(err.Error())return ""}return string(decryptBytes)
}// 盐值加密(根据明文密码,获取密文)
func GetEncryptedPassword(password string) (string, error) {// 加密密码,使用 bcrypt 包当中的 GenerateFromPassword 方法,bcrypt.DefaultCost 代表使用默认加密成本encryptPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)if err != nil {panic(err)log.Error(err.Error())return "", err}return string(encryptPassword), nil
}// 判断密码是否正确(根据明文和密文对比)plaintextPassword 明文 encryptedPassword 密文
func AuthenticatePassword(plaintextPassword string, encryptedPassword string) bool {// 使用 bcrypt 当中的 CompareHashAndPassword 对比密码是否正确,第一个参数为密文,第二个参数为明文err := bcrypt.CompareHashAndPassword([]byte(encryptedPassword), []byte(plaintextPassword))// 对比密码是否正确会返回一个异常,按照官方的说法是只要异常是 nil 就证明密码正确return err == nil
}// 判断数组中是否包含指定元素
func IsContain(items interface{}, item interface{}) bool {switch items.(type) {case []int:intArr := items.([]int)for _, value := range intArr {if value == item.(int) {return true}}case []string:strArr := items.([]string)for _, value := range strArr {if value == item.(string) {return true}}default:return false}return false
}// 中文转拼音大写(首字母)
func ConvertToPinyin(text string, p pinyin.Args) string {var initials []stringfor _, r := range text {if r >= 0x4e00 && r <= 0x9fff { // 判断字符是否为中文字符pinyinResult := pinyin.Pinyin(string(r), p)initials = append(initials, strings.ToUpper(string(pinyinResult[0][0][0])))} else if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9' || r == '_') { // 判断字符是否为英文字母或数字initials = append(initials, strings.ToUpper(string(r)))}}return strings.Join(initials, "")
}// 上传保存文件 form 文件数据、relative 文件保存的相对路径、fileName 文件名称
func SaveFile(form *multipart.Form, relative, fileName string) error {// 获取文件数据file, err := form.File["file"][0].Open()if err != nil {err = errors.New("上传文件失败:" + err.Error())return err}defer file.Close()content, err := io.ReadAll(file)if err != nil {err = errors.New("上传文件失败:" + err.Error())return err}// 获取当前项目路径currentDir, err := os.Getwd()if err != nil {err = errors.New("获取项目路径失败:" + err.Error())return err}// 文件上传的绝对路径absolute := filepath.Join(currentDir, relative)// 创建目录err = os.MkdirAll(absolute, os.ModePerm)if err != nil {err = errors.New("上传文件失败:" + err.Error())return err}// 在当前项目路径中的 /upload/20231208/ 下创建新文件newFile, err := os.Create(filepath.Join(absolute, fileName))if err != nil {err = errors.New("上传文件失败:" + err.Error())return err}defer newFile.Close()// 将文件内容写入新文件_, err = newFile.Write(content)if err != nil {err = errors.New("上传文件失败:" + err.Error())return err}return nil
}

ok,以上就是本篇文章的全部内容了,等我更完这个项目的全部文章,我会放出完整代码的地址,欢迎大家多多点赞支持下,最后可以关注我不迷路~

这篇关于【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/488656

相关文章

Zookeeper安装和配置说明

一、Zookeeper的搭建方式 Zookeeper安装方式有三种,单机模式和集群模式以及伪集群模式。 ■ 单机模式:Zookeeper只运行在一台服务器上,适合测试环境; ■ 伪集群模式:就是在一台物理机上运行多个Zookeeper 实例; ■ 集群模式:Zookeeper运行于一个集群上,适合生产环境,这个计算机集群被称为一个“集合体”(ensemble) Zookeeper通过复制来实现

CentOS7安装配置mysql5.7 tar免安装版

一、CentOS7.4系统自带mariadb # 查看系统自带的Mariadb[root@localhost~]# rpm -qa|grep mariadbmariadb-libs-5.5.44-2.el7.centos.x86_64# 卸载系统自带的Mariadb[root@localhost ~]# rpm -e --nodeps mariadb-libs-5.5.44-2.el7

hadoop开启回收站配置

开启回收站功能,可以将删除的文件在不超时的情况下,恢复原数据,起到防止误删除、备份等作用。 开启回收站功能参数说明 (1)默认值fs.trash.interval = 0,0表示禁用回收站;其他值表示设置文件的存活时间。 (2)默认值fs.trash.checkpoint.interval = 0,检查回收站的间隔时间。如果该值为0,则该值设置和fs.trash.interval的参数值相等。

NameNode内存生产配置

Hadoop2.x 系列,配置 NameNode 内存 NameNode 内存默认 2000m ,如果服务器内存 4G , NameNode 内存可以配置 3g 。在 hadoop-env.sh 文件中配置如下。 HADOOP_NAMENODE_OPTS=-Xmx3072m Hadoop3.x 系列,配置 Nam

wolfSSL参数设置或配置项解释

1. wolfCrypt Only 解释:wolfCrypt是一个开源的、轻量级的、可移植的加密库,支持多种加密算法和协议。选择“wolfCrypt Only”意味着系统或应用将仅使用wolfCrypt库进行加密操作,而不依赖其他加密库。 2. DTLS Support 解释:DTLS(Datagram Transport Layer Security)是一种基于UDP的安全协议,提供类似于

hdu1171(母函数或多重背包)

题意:把物品分成两份,使得价值最接近 可以用背包,或者是母函数来解,母函数(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v)(1 + x^v+x^2v+.....+x^num*v) 其中指数为价值,每一项的数目为(该物品数+1)个 代码如下: #include<iostream>#include<algorithm>

高效录音转文字:2024年四大工具精选!

在快节奏的工作生活中,能够快速将录音转换成文字是一项非常实用的能力。特别是在需要记录会议纪要、讲座内容或者是采访素材的时候,一款优秀的在线录音转文字工具能派上大用场。以下推荐几个好用的录音转文字工具! 365在线转文字 直达链接:https://www.pdf365.cn/ 365在线转文字是一款提供在线录音转文字服务的工具,它以其高效、便捷的特点受到用户的青睐。用户无需下载安装任何软件,只

【Python编程】Linux创建虚拟环境并配置与notebook相连接

1.创建 使用 venv 创建虚拟环境。例如,在当前目录下创建一个名为 myenv 的虚拟环境: python3 -m venv myenv 2.激活 激活虚拟环境使其成为当前终端会话的活动环境。运行: source myenv/bin/activate 3.与notebook连接 在虚拟环境中,使用 pip 安装 Jupyter 和 ipykernel: pip instal

零基础学习Redis(10) -- zset类型命令使用

zset是有序集合,内部除了存储元素外,还会存储一个score,存储在zset中的元素会按照score的大小升序排列,不同元素的score可以重复,score相同的元素会按照元素的字典序排列。 1. zset常用命令 1.1 zadd  zadd key [NX | XX] [GT | LT]   [CH] [INCR] score member [score member ...]

搭建Kafka+zookeeper集群调度

前言 硬件环境 172.18.0.5        kafkazk1        Kafka+zookeeper                Kafka Broker集群 172.18.0.6        kafkazk2        Kafka+zookeeper                Kafka Broker集群 172.18.0.7        kafkazk3