这是我参与「第三届青训营 -后端场」笔记创作活动的第6篇笔记;
Gin路由文件抽离
之前进行路由分组所有的分组程序都写在一个main.go文件里面了,这里对不同的路由进行文件分组,首先创建一个routing文件夹,在这个文件夹内新建三个分组文件:adminRoutes.go、apiRoutes.go、defaultRoutes.go,在最外面创建一个main.go来进行调用,如下所示:
路由文件adminRoutes.go
package routing
import "github.com/gin-gonic/gin"
func AdminRoutesInit(route *gin.Engine) {
//Engine是gin里面的一个结构体,里面有很多字段,它还嵌套了RouterGroup结构体
adminRouter := route.Group("/admin")
{
adminRouter.GET("/user", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "user-ok",
})
})
adminRouter.GET("/news", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "news-ok",
})
})
}
}
路由文件apiRoutes.go
package routing
import (
"github.com/gin-gonic/gin"
)
func ApiRoutesInit(router *gin.Engine) {
apiRoute := router.Group("/api")
{
apiRoute.GET("/user", func(c *gin.Context) {
c.JSON(200, gin.H{
"username": "张三",
})
})
apiRoute.GET("/news", func(c *gin.Context) {
c.JSON(200, gin.H{
"title": "新闻",
})
})
}
}
路由文件defaultRoutes.go
package routing
import (
"github.com/gin-gonic/gin"
)
func DefaultRoutesInit(router *gin.Engine) {
defaultRoute := router.Group("/")
{
defaultRoute.GET("/", func(c *gin.Context) {
c.String(200, "首页")
})
}
}
主函数main.go
package main
import (
"github.com/gin-gonic/gin"
"goweb/routing"
)
type Userinfo struct {
Username string `form:"username" json:"user"`
Password string `form:"password" json:"password"`
}
func main() {
r := gin.Default()
routing.AdminRoutesInit(r)
routing.ApiRoutesInit(r)
routing.DefaultRoutesInit(r)
r.Run(":80")
}
Gin中自定义控制器
还可以把控制器方法从路由器中单独抽离出来,并与结构体进行绑定,这样就能绑定父类的方法,从而实现,controller-service:
控制器方法
type UserController struct{}
func (con UserController) Index(c *gin.Context) {
//"con 结构体名"表示把当前方法挂载到此结构体上(方法名首字母要大写,因为外部要调用)
c.String(200, "用户列表")
}
func (con UserController) Add(c *gin.Context) {
c.String(200, "用户列表-add")
}
路由文件调用
func AdminRoutesInit(route *gin.Engine) {
adminRouter := route.Group("/admin")
{
...
adminRouter.GET("/user/index",admin.UserController{}.Index)
adminRouter.GET("/user/add", admin.UserController{}.Add)
...
}
}
主程序调用路由文件
import (
"github.com/gin-gonic/gin"
"goweb/routing"
)
...
r := gin.Default()
routing.AdminRoutesInit(r)
...
r.Run(":80")
控制器的继承
首先创建一个公共controller:
package admin
import "github.com/gin-gonic/gin"
type BaseController struct {
}
func (con BaseController) success(c *gin.Context) {
c.String(200, "成功")
}
func (con BaseController) error(c *gin.Context) {
c.String(200, "失败")
}
然后另一个controller继承这个controller,这样就能调用公共controller里面的方法了
...
type ArticleController struct {
BaseController//继承BaseController
}
...
func (con ArticleController) Success(c *gin.Context) {
con.success(c)//调用BaseController里面的方法
}
路由文件调用
xxx.GET("/article/success", admin.ArticleController{}.Success)
Gin中间件
Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。通俗的讲:中间件就是匹配路由前和匹配路由完成后执行的一系列操作;
路由中间件
首先看一下GET方法的源码:
func (group *RouterGroup) GET(relativePath string, handlers ...HandlerFunc) IRoutes {
return group.handle(http.MethodGet, relativePath, handlers)
}
可以发现它第一个参数是路径,后面可以跟多个func函数,那么我这样写:
adminRouter.GET("/index/index", func(c *gin.Context) {
fmt.Println("aaa")
}, admin.IndexController{}.Index)
那么在访问之前会执行打印aaa的方法:
这个前面的方法就可以相当于一个路由中间件,当然我们也可以把这个路由中间件方法抽离出来,这样就可以通过调用这个路由中间件方法达到处理一些公共的业务逻辑的功能了;
获取程序执行的时间:
func initMiddleware(c *gin.Context) {
fmt.Println("1-我是一个中间件")
start := time.Now().UnixNano() //获取此时纳秒的时间戳
c.Next() //作用:先执行后面的程序,最后再执行c.Next()后面的程序
end := time.Now().UnixNano() //获取此时纳秒的时间戳
fmt.Println("2-我是一个中间件")
fmt.Println(end - start)//求出之间的时间就是程序执行的时间
}
...
adminRouter.GET("/index/index", func (c *gin.Context) {
fmt.Println("这是一个首页")
c.String(200, "用户首页")
}
)
结果:
c.Abort():表示终止调用该请求的剩余处理程序(HandlerFunc),但还是会执行本处理程序(HandlerFunc)c.Abort()后面的程序;
一个路由可以配置多个路由中间件;
全局中间件
func initMiddleware(c *gin.Context) {
fmt.Println("1、我是一个全局中间件")
c.Next()
fmt.Println("2、我是一个全局中间件")
}
func main() {
r := gin.Default()
r.Use(initMiddleware)//全局中间件,同样可以传入多个
...
User源码可见也是可以传入多个中间件方法
路由分组中间件
...
func initMiddle(c *gin.Context) {
fmt.Println("我是一个路由分组中间件")
}
func ApiRoutesInit(router *gin.Engine) {
apiRoute := router.Group("/api", initMiddle)//直接在分组路径后面配置分组中间件函数名即可
...
中间件和对应控制器之间共享数据
中间件方法中设置一个值:
//方法名一定要大写,这样才能对外界开放
func InitMiddle(c *gin.Context) {
fmt.Println("我是一个路由分组中间件")
c.Set("username", "张诺")//设置一个值
}
在请求处理中调用这个值进行展示:
apiRoute.GET("/user", func(c *gin.Context) {
username, _ := c.Get("username") //接受这个值,error不接收
//username是一个空接口类型
//这里用类型断言将其转化为string类型
v, ok := username.(string)
if ok != true { //第二个参数是布尔类型,当为false时表示转换失败
v = "无名"
}
c.JSON(200, gin.H{
"username": v,
})
中间补充知识
gin.Default()默认使用了Logger和Recovery中间件,其中:
- Logger中间件将日志写入gin.DefaultWriter,即使配置了GIN_MODE=release。
- Recovery中间件会recover任何错误。如果有错误的话,会写入500响应码。
如果不想使用上面两个默认的中间件,可以使用
gin.New()新建一个没有任何默认中间件的路由。
gin中间件中使用goroutine协程
不能直接使用(c *gin.Context)中的c,需要先c.Copy()以下,使用其副本,如下:
路由中间件方法
func Printlog(c *gin.Context) {
cp := c.Copy()//使用其副本
fmt.Println("打印的url是:" + cp.Request.URL.Path)
}
路由分组文件调用此路由中间件方法
...
defaultRoute := router.Group("/", middlewares.Printlog)
...
gin中自定义model
如果我们的应用非常简单的话,我们可以在Controller里面处理常见的业务逻辑。但是如果我们有一个功能想在多个控制器、或者多个模板里面复用的话,那么我们就可以把公共的功能单独抽取出来作为一个模块(Model)。
一般我们会在Model里面封装一些公共的方法让不同Controller使用,也可以在Model中实现和数据库打交道;
gin上传图片并按照日期存储图片
要求把每天上传的图片放在每天的文件夹中,并且图片名是创建日期
创建两个工具方法
//获取今天的年月日
func GetDay() string {
return time.Now().Format("20060102")
}
//获取当前时间的时间戳
func GetUnix() int64 {
return time.Now().Unix()
}
上传代码
package routing
import (
"fmt"
"github.com/gin-gonic/gin"
"goweb/models"
"os"
"path"
"strconv"
)
/*流程
1、获取上传的图片
2、获取后缀名,判断类型是否正确.jpg.png.gif.jpeg
3、创建图片保存目录,如“static/imgs/20220511”
4、生成文件名称和文件保存目录
5、执行上传
*/
func Uploadpicture(route *gin.Engine) {
uploadroute := route.Group("/upload")
{
uploadroute.POST("/picture", func(c *gin.Context) {
file, err := c.FormFile("file") //1、获取上传文件
if err == nil {
//2、判断文件后缀名
extName := path.Ext(file.Filename) //获取文件后缀名
allowExrMap := map[string]bool{
".jpg": true,
".png": true,
".gif": true,
".jpeg": true,
}
if _, ok := allowExrMap[extName]; ok != true {
c.String(200, "上传文件类型不合法:"+extName)
return
}
//3、创建图片保存目录
var day string = models.GetDay()
dir := "static/imgs/" + day + "/"
err := os.MkdirAll(dir, 0666) //创建目录,第二个参数填的是权限
if err != nil {
fmt.Println(err)
c.String(200, "MkdirAll error")
return
}
//4、生成文件名称和文件保存目录
unix := models.GetUnix()
fileName := strconv.FormatInt(unix, 20) + extName
//5、执行上传
c.SaveUploadedFile(file, dir+fileName)
c.JSON(200, gin.H{
"message": "上传成功!!",
})
} else {
c.JSON(200, gin.H{
"message": "上传失败!!",
})
}
})
}
}