本文主要是介绍【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(二)日志输出中间件、校验token中间件、配置路由、基础工具函数。,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
上一篇:【Go】基于GoFiber从零开始搭建一个GoWeb后台管理系统(一)搭建项目
上一篇我们搭好了项目框架,那接下来就可以开始写业务了,然后在写业务之前,我们还需要考虑几个问题:
- 首先一个就是日志问题,控制台日志输出、日志输出到文件;
- 第二个就是token校验,每次请求服务器时,在执行接口业务之前,需要先校验请求头的token是否有效;
- 第三个就是请求成功后,返回回去的参数需要统一。
日志
我们先来说说日志,我们需要控制全局日志输出,输出打印时间、请求的接口、错误信息、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中间件、配置路由、基础工具函数。的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!