当前位置: 首页 > news >正文

哈尔滨网站建设可信赖惠州网站制作专业

哈尔滨网站建设可信赖,惠州网站制作专业,网站建设项目采购合同,网页设计与网站建设程序作业gin day1 今天的目标就是学懂#xff0c;看懂每一步代码。 gin框架 gin框架就是go语言的web框架。框架你也可以理解成一个库。里面有一堆封装好的工具#xff0c;帮你实现各种各样的功能#xff0c;这样使得你可以关注业务本身#xff0c;而在写代码上少费力。 快速入门看懂每一步代码。 gin框架 gin框架就是go语言的web框架。框架你也可以理解成一个库。里面有一堆封装好的工具帮你实现各种各样的功能这样使得你可以关注业务本身而在写代码上少费力。 快速入门 先懂得这些概念 1.什么是模块 Go模块Go Module是Go语言的包管理和依赖管理系统自Go 1.11版本引入。它提供了一种管理Go项目依赖的机制使得开发者可以在项目中明确指定所使用的外部依赖包的版本。这种机制确保了项目的构建是可重复的同时也简化了包的共享和版本控制。 Go模块的特点 版本控制Go模块支持语义版本控制Semantic Versioning每个模块的版本号清晰地反映了该版本的变更性质如向后兼容的更新、新特性添加或大变更。 依赖管理通过go.mod文件Go模块允许你为项目指定精确的依赖版本包括直接依赖和间接依赖。 易于共享Go模块使得共享和使用其他开发者编写的Go代码变得更加简单只需通过import语句引入模块路径即可使用。 模块代理Go 1.13引入了模块代理Module Proxy的概念允许通过设置GOPROXY环境变量来指定模块下载源加快模块下载速度提高依赖管理的效率。 使用Go模块 要在项目中启用Go模块支持 初始化模块在项目根目录下执行go mod init 命令这会创建一个go.mod文件。通常是项目的包名或代码库的路径。 添加依赖通过import语句在代码中引入所需的包然后运行go build、go test或go mod tidy等命令。Go工具会自动更新go.mod文件并可能创建一个go.sum文件来存储依赖的确切版本和校验和。 升级和管理依赖可以使用go get命令来添加、更新或移除依赖。go mod tidy命令会移除项目不再使用的依赖并更新go.mod文件以反映当前的依赖状态。 Go模块的好处 项目隔离每个项目可以有自己的依赖版本不同项目之间的依赖版本不会相互冲突。 构建可重复性由于所有依赖的版本都被精确记录项目的构建结果在任何环境下都是一致的。 简化依赖管理无需将第三方包的源代码直接包含在项目仓库中通过go.mod文件管理依赖使项目结构更清晰依赖更新更简单。 2.工作区是什么 工作区Workspace是一个概念它用于组织和管理一个或多个相关的项目或模块使得这些项目或模块可以作为一个整体被开发和管理。在不同的编程环境和工具中工作区的具体实现和用法可能有所不同但基本概念是一致的。对于Go语言来说工作区的概念在Go 1.18版本中通过go.work文件被引入主要用于管理和协调多个Go模块。 3.Go工作区的特点 多模块管理Go工作区允许在一个共享的开发环境中同时处理多个Go模块。这对于开发大型项目或多个相互依赖的微服务非常有用。 本地协调在工作区中你可以本地修改多个模块并且这些修改会被整个工作区中的其他模块所识别和使用而不需要先将更改推送到远程仓库。 灵活的依赖管理工作区使得跨模块的依赖管理变得更加灵活。你可以轻松地在本地模块之间进行更改测试这些更改对其他模块的影响然后再一次性提交所有的更改。 4.使用Go工作区 要在Go中创建一个工作区你需要 1.创建go.work文件在工作区的根目录下创建一个名为go.work的文件。这个文件告诉Go工具链哪些模块属于这个工作区。 2.指定工作区中的模块在go.work文件中使用use关键字列出工作区包含的所有模块的路径。例如 plaintext Copy code go 1.18 use ( ./module-a ./module-b ) 3.开发和构建在工作区根目录下执行Go命令如go build、go test等时Go工具链会考虑到所有在go.work文件中指定的模块。 5.工作区的好处 简化本地开发工作区简化了同时开发多个模块时的流程特别是当这些模块相互依赖时。 提高生产力无需频繁地推送和拉取远程仓库中的更改开发者可以更快地测试和迭代。 灵活的测试在提交更改之前可以在本地环境中测试模块间的集成确保所有更改都能协同工作。 快速入门解读 D:\go\projectmkdir ginlearn D:\go\projectcd ginlearn D:\go\project\ginlearngo work init D:\go\project\ginlearnmkdir helloworld D:\go\project\ginlearncd helloworld D:\go\project\ginlearn\helloworldgo mod init test.com/helloworld go: creating new go.mod: module test.com/helloworld D:\go\project\ginlearn\helloworldcd .. D:\go\project\ginlearngo work use ./helloworld解读 创建一个新的项目目录 mkdir ginlearn在D:\go\project目录下创建一个名为ginlearn的新目录这将作为新项目的根目录。 cd ginlearn切换到刚创建的ginlearn目录后续的操作都将在这个目录下进行。 初始化Go工作区 go work init在当前目录D:\go\project\ginlearn初始化一个新的Go工作区这会创建一个go.work文件。这个文件用于定义工作区内包含的Go模块使得你可以在本地开发环境中轻松管理和协调多个模块。 创建并初始化一个新的Go模块 mkdir helloworld在ginlearn项目目录下创建一个名为helloworld的子目录用于存放一个新的Go模块。 cd helloworld切换到helloworld目录准备在这个目录下初始化新的Go模块。 go mod init test.com/helloworld在当前目录D:\go\project\ginlearn\helloworld初始化一个新的Go模块模块名为test.com/helloworld。这会创建一个go.mod文件该文件描述了模块的名称和其他依赖信息。 将新模块添加到工作区 cd …返回到ginlearn项目根目录。 go work use ./helloworld将helloworld模块添加到工作区。这个命令更新go.work文件包含对helloworld模块的引用。这样当你在工作区根目录D:\go\project\ginlearn中运行Go命令时Go工具链会考虑到helloworld模块。 安装gin 下载安装gin后依赖是灰色的代表还没有使用它。 package mainimport (github.com/gin-gonic/ginlog )func main() {r : gin.Default()r.GET(/hello, func(ctx *gin.Context) {ctx.JSON(200, gin.H{name: hello,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)} } 解读 1.先导包 2.**gin.Default()**创建了一个Gin引擎实例r。Default方法返回一个默认的路由引擎已经包括了Logger和Recovery中间件这两个中间件可以帮助记录日志和管理panic使得服务更加稳定。 3. r.GET(/hello, func(ctx *gin.Context) {ctx.JSON(200, gin.H{name: hello,})}) 这是设置路由 设置了一个路由/hello用于处理GET请求。当访问这个路由时会执行一个匿名函数这个函数接受一个*gin.Context类型的参数ctx它封装了请求的上下文信息。函数内部使用ctx.JSON方法返回一个JSON响应状态码为200内容是一个包含name: hello键值对的JSON对象。 光是这样的理解我觉得还是很困难再进一步解读 要搞懂匿名函数的意义和ctx是什么。 匿名函数 在Gin框架中路由处理函数通常以匿名函数的形式提供。匿名函数允许你直接在路由定义中实现处理逻辑而不需要单独定义一个函数。这种方式使得代码简洁且易于理解特别是当处理逻辑相对简单时。 这里r.GET(“/hello”, …)定义了一个处理HTTP GET请求的路由/hello。当这个路由被访问时紧随其后的匿名函数就会被执行。 ctxContext ctx是*gin.Context类型的实例它是Gin框架提供的一个上下文对象封装了当前HTTP请求的所有信息以及对请求的响应操作的方法。你可以将ctx视为当前请求的上下文通过它可以访问请求的数据如查询参数、表单值、JSON请求体等设置响应的数据以及执行其他与请求处理相关的操作。 访问请求信息比如使用ctx.Query(“param”)来获取URL查询参数param的值或者ctx.PostForm(“field”)来获取表单中名为field的字段值。 设置响应在你的例子中ctx.JSON(200, gin.H{“name”: “hello”})是使用ctx设置响应的一个例子。这条语句发送了一个HTTP状态码为200的响应内容是一个JSON对象{“name”: “hello”}。gin.H是Gin提供的一个便利的方式来创建一个map这个map会被自动转换为JSON。 最后启动HTTP服务器监听8080端口。 路由 — 请求方法 package mainimport (github.com/gin-gonic/ginlog )func main() {r : gin.Default()r.GET(/hello, func(ctx *gin.Context) {ctx.JSON(200, gin.H{name: hello,})})r.GET(/get, func(ctx *gin.Context) {ctx.JSON(200, get)})r.POST(/post, func(ctx *gin.Context) {ctx.JSON(200, post)})r.PUT(/put, func(ctx *gin.Context) {ctx.JSON(200, update)})r.DELETE(/delete, func(ctx *gin.Context) {ctx.JSON(200, delete)})r.Any(/any, func(ctx *gin.Context) {ctx.JSON(200, Any)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)} } 这个就说几个要点 指定了路由的响应方法你用其他的方法去访问这个路由就会报404。 如果想一个路由可以支持所有的方法那么就要用Any来进行设置。 如果想一个路由支持某几种方法那就分别写这个路由的这几种方法的实现。 路由—URL URL书写时我们不需要关系scheme和authority这两部分我们主要通过path和query两部分的书写来进行资源定位。 静态url r.POST(/user/find, func(ctx *gin.Context) { })路径参数比如/user/find/:id r.POST(/user/find/:id, func(ctx *gin.Context) {param : ctx.Param(id)ctx.JSON(200, param)})创建了一个路由/user/find/:id其中:id是一个路径参数path parameter用于在URL路径中动态地表示数据在这个例子中是用户的ID。这种路由定义方式允许服务器接受包含特定ID的请求如/user/find/123其中123会被识别为ID的值。 param : ctx.Param(“id”)这行代码通过调用ctx.Param方法从请求的URL路径中提取名为id的路径参数的值并将这个值存储在变量param中。例如如果请求的URL是/user/find/123那么param的值将会是123。 *模糊匹配比如/user/path r.POST(/user/*path, func(ctx *gin.Context) {param : ctx.Param(path)ctx.JSON(200, param) })r.POST(/user/path, …)定义了一个路由规则它可以匹配如/user/action、/user/settings/profile等任意以/user/开头的POST请求。**这里的path是一个动态部分它会匹配这个位置及之后的所有路径。** param : ctx.Param(“path”)这行代码通过ctx.Param方法从请求的URL中提取名为path的参数值。例如如果请求的URL是/user/settings/profile则param将包含settings/profile。 有一个点需要注意就是两种动态方式不能一起用这里我说的一起用是前缀不能相同否则就会产生矛盾。 分组路由 v1 : r.Group(/v1){v1.GET(find, func(ctx *gin.Context) {ctx.JSON(200, v1 find)})v1.GET(save, func(ctx *gin.Context) {ctx.JSON(200, v1 save)})}v2 : r.Group(/v2){v2.GET(find, func(ctx *gin.Context) {ctx.JSON(200, v2 find)})v2.GET(save, func(ctx *gin.Context) {ctx.JSON(200, v2 save)})}这个思想对于业务上是很直观的 比如一个程序版本是v1那么对其的所有程序的编写那就是v1版本当想提升版本的时候总不可能去对v1进行改动这里就采用了组的形式可以有效的分组进行版本管理可以看到上述例子还是非常的直观。 对于用户来说就是去改了一点点域名由访问/v1而改去访问/v2 这种思想在分模块里面也是很好用的比如我可以进行分组/user 和/goods。 请求参数 http://localhost:8080/user/save?id11namezhangsan 比如我要获取这个里面的参数号后面的就是参数这个显然是键值对的形式。 r : gin.Default()//http://localhost:8080/user/save?id11namezhangsanr.GET(user/save, func(ctx *gin.Context) {id : ctx.Query(id)name : ctx.Query(name)ctx.JSON(200, gin.H{id: id,name: name,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}这里是通过Query来获取里面的参数传参传的是key返回值是value。 还可以通过GetQuery这种方式相比Query的好处就是它有一个返回值是判断 GetQuery(“address”),实际上我要访问的这个url中根本就没有这个参数那么返回值就是和flase。这种在处理如果没有该字段那就进行处理这种场景也是很好用的。 ctx.DefaultQuery(“address”,“beijing”)这个就是如果不存在就会给个默认值这种用法。这个用法也是很常见的。 上面这种方法有它的不足使用这几个方法得到的返回值value都是string类型的。那么有没有方法可以直接返回上面参数并且类型要符合要求比如id那就是intname那就是string。 回答是可以的要通过结构体实现ctx.BindQuery(struct) type User struct {Id int64 form:idName string form:name }r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {var user Usererr : ctx.BindQuery(user)if err ! nil {log.Println(err)}ctx.JSON(200, user)})err : r.Run()if err ! nil {log.Fatalln(err)}这个结构体中的tag标签也是非常重要这样才能使得参数与结构体里面的属性绑定起来这样返回到结构体中的数据那不就是有数据类型了 也就达到了要求。 但是更推荐使用ShouldBindQuery 再来看看这个例子 type User struct {Id int64 form:idName string form:nameAddress string form:address binding:required } 这个多加的bindingrequired表示在绑定的时候这个字段是必须的。r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {var user Usererr : ctx.ShouldBindQuery(user)if err ! nil {log.Println(err)}ctx.JSON(200, user)})err : r.Run()if err ! nil {log.Fatalln(err)}现在调用的话虽然结果不会错状态码是对的但是在ide环境上是会报错的原因也是很简单那就是Address属性是必须的但是代码中并没有发生address的值绑定。 但是如果在加了这个标签的情况下那就会发生错误状态码直接400而且返回值你会发现好像不是Json类型了。 总结 一般用ShouldBindQuery因为这种更方便就算ide报了错在结果上是不会产生影响的。ide上的错误处理又非常的容易。 两个方法如果属性都对应的上那么确实是会出现两种方式都可以的情况。 数组参数 传数组类型的参数是这么来传 http://localhost:8080/user/save?addressBeijingaddressshanghai 传参的时候传相同的参数名 只是值不同值相同也是可以的。 理解 这个URL相当于前端后端我们就是要用个数组去接收value现在我们的关注点就是怎么用数组接。 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {http: //localhost:8080/user/save?addressBeijingaddressshanghaiaddress : ctx.QueryArray(address)ctx.JSON(200, address)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}这里采用了QueryArray返回值是string类型的切片。 同理可以使用GetQueryArray有个判断的功能。 注意这个就没有Default的功能。 仍然可以实现绑定功能。 type User struct {Id int64 form:idName string form:nameAddress []string form:address binding:required }r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {var user Userctx.ShouldBindQuery(user)ctx.JSON(200, user)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}map参数 先看map传参时url的格式是如何传的。 http://localhost:8080/user/save?addressMap[home]BeijingaddressMap[company]shanghai 现在我们要关注的就是后端怎么去接收这样的参数。 主要是通过ctx.QueryMap(map名)来实现的对于上面参数[]里面的就是key就是value。 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {addressMap : ctx.QueryMap(addressMap)ctx.JSON(200, addressMap)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}map不支持绑定的方式去拿。 只能通过QueryMap Post请求参数 post一般针对的是表单参数和Json参数。 所以要发送的是Post请求。 这个例子是获取Form表单的参数 用的一般是PostForm,当然也有Get前缀的方法和Default前缀的方法这个在前面也学习过一种是带判断一种是带默认值。 怎么理解数组参数就是一个key有多个value。这就是数组。 r : gin.Default()r.POST(/user/save, func(ctx *gin.Context) {id : ctx.PostForm(id)name : ctx.PostForm(name)address : ctx.PostFormArray(address)addressMap : ctx.PostFormMap(addressMap)ctx.JSON(200, gin.H{id: id,name: name,address: address,addressMap: addressMap,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}当然也是可以使用结构体绑定这里绑定就是单纯的ShouldBind(user) r : gin.Default()r.POST(/user/save, func(ctx *gin.Context) {var user Userctx.ShouldBind(user)ctx.JSON(200, user)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}但是有同样的问题map同样获取不到。如果你一定要这个user里面的map有值你只能直接去通过QueryMap单独获取了然后对user结构体的map字段赋值然后再进行响应设置。 Json参数 这种获取参数可以采用绑定的形式 r : gin.Default()r.POST(/user/save, func(ctx *gin.Context) {var user Userctx.ShouldBindJSON(user)ctx.JSON(200, user)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}这里在知道是Json数据的情况下最好使用ShouldBindJSON 路径参数 这里有一个问题 type User struct {Id int64 form:idName string form:nameAddress []string form:address binding:requiredAddressMap map[string]interface{} form:addressMap } 这里说的是关于映射关系 比如我访问的时候 {Id: 11,Name: zhangsan,Address: [beijing,shanghai],AddressMap: {home:beijing} }我的json数据我突然改了Address改为Addressess这显然肯定就映射不到了。 这里我们想当然的去更改结构体里面的标签想通过form标签的修改从而使得Address和addressess又匹配上。但是你这么做你就会发现还是匹配不上这里说说原理 这是因为实际上这个匹配关系根form标签没什么关系这里默认情况下Json是将字段名转成小写实现了匹配这里要想实现匹配那么用的标签就不是form而是使用json标签来做修改。 这是用json匹配的一个重点。 路径参数 r : gin.Default()r.POST(/user/save/:id/:name, func(ctx *gin.Context) {id : ctx.Param(id)name : ctx.Param(name)ctx.JSON(200, gin.H{id: id,name: name,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}这个其实也讲过就是利用Param从url里提取参数然后进行返回。 也可以用绑定的形式去获取。 这个有个要点要加上uri的tag。不然ShouldBindUri(user)进行绑定时找不到它要对应的属性。 uri:“id” 这样的tag。 简单情况下就用param。复杂用tag 文件参数 用的MultipartFor r : gin.Default()r.POST(/user/save, func(ctx *gin.Context) {form, err1 : ctx.MultipartForm()if err1 ! nil {log.Fatalln(err1)}value : form.Valuefiles : form.Filefor _, fileArray : range files {for _, v : range fileArray {ctx.SaveUploadedFile(v, ./v.Filename)}}ctx.JSON(200, value)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}我看的时候心中还是有疑惑的。 form, err1 : ctx.MultipartForm()从请求中解析多部分表单数据。这个方法返回两个值一个multipart.Form对象和一个可能的错误。如果解析过程中出现错误错误会被记录并通过log.Fatalln(err1)输出。 value : form.Valueform.Value包含了所有表单的文本字段键值对。这些是非文件字段。 files : form.Fileform.File包含了所有上传的文件这些文件被组织为一个映射其中键是表单字段名值是对应的一组文件。 一组文件的理解 不是文件夹在这个上下文中“一组文件”指的是用户通过单个表单字段上传的多个文件而不是指一个文件夹或目录。这些文件以列表Slice的形式存在每个列表项代表一个文件。 这个就是非常字面的意思比如一个file1key它可以有多个value别问为什么就是这样的允许用户为同一个字段名在这个例子中是file1选择多个文件进行上传。这意味着对于这个字段名file1可以上传多个文件比如1.png, 2.png等。。所以一个key就代表了一个文件组里面的文件以slice的形式存在所以才需要先遍历for range 所有的文件组然后才是遍历这个文件组里面的文件。 现在我正式来解读这段代码 form, err1 : ctx.MultipartForm() 这个就是直接从请求中解析多部分得表单数据。会得到一给multipart.Form对象。 对这个对象调用这两个字段value : form.Value files : form.File 两个都是获取得map。 value是map[string][]string类型的代表了所有非文件的表单key和value的映射并且这里也考虑到了key相同而且value有几个的情况这也是很常见的比如前面说到的json数组参数。 files是map[string]*[]FileHeader类型的代表了文件组里面一层就是文件组中的文件。 这就是为什么要搞for循环的原因。 for _, fileArray : range files {for _, v : range fileArray {ctx.SaveUploadedFile(v, ./v.Filename)}} 外循环 for _, fileArray : range files这个循环遍历files映射中的每个条目。每次迭代fileArray会被赋值为与当前字段名相关联的文件数组或说是切片。这意味着如果你的表单允许用户在多个不同的字段上传文件这个循环会依次处理每个字段上传的所有文件。 内循环 for _, v : range fileArray对于每个字段名对应的文件数组这个内部循环会遍历数组中的每个文件。在每次迭代中v被赋值为当前的文件它是一个*multipart.FileHeader实例代表一个上传的文件。 文件保存 ctx.SaveUploadedFile(v, “./”v.Filename)对于每个文件v这行代码调用SaveUploadedFile方法将它保存到服务器上。第一个参数是文件*multipart.FileHeader类型第二个参数是保存路径。这里使用./v.Filename作为保存路径意味着文件将被保存在服务器应用程序的当前工作目录下文件名与上传时使用的文件名相同。 响应 所谓响应就是客户端请求发过来了服务器给客户端返回什么东西。 gin中响应有多种方式看需求选择 1.string方式 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.String(200, this is %s, ms string value)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}2.Json方式前面用的一直是json形式。 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.JSON(200, gin.H{name: 666,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}3.xml方式这个结构上和json类似 相较于json就只是改个函数调用值得格式都是一样。 只是xml不像json搞得键值对而是这种形式666key直接括起value。 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.XML(200, gin.H{name: 666,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}当然也可以自行定义结构体但是注意要 打标签xml:id 4.文件方式 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.File(文件路径)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}这个如果你用浏览器去访问那其实就是下载 如果改成ctx.FileAttachment(”文件路径““下载的时候改文件的名字” 5.设置http响应头 r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.Header(test,ms)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}test是keyms是value 这个就是用来在响应的时候更改头信息。 6.重定向 就是在访问的时候跳转到另一个地方去。 注意这个用状态码不能用200只能用301这个在http包下是http.StatusMovedPermanently r : gin.Default()r.GET(/user/save, func(ctx *gin.Context) {ctx.Redirect(http.StatusMovedPermanently,https://www.baidu.com)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}后面第二个参数就是重定向后的地址。就实现跳转到百度了。 7.YAML方式 用法和json一样的。就是函数改成YAML 这种格式只是有时候对这种格式有需求的时候就可以用。 8.还可以返回protoBuf格式。 模板渲染 模板是golang语言的一个标准库使用场景很多gin框架同样支持模板。 1.基本使用 r : gin.Default()r.LoadHTMLFiles(./templates/index.tmpl) r.GET(/index, func(ctx *gin.Context) {ctx.HTML(200, index.tmpl, gin.H{title: hello template,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}1.先加载模板就是模板的路径 2.然后还是构造get请求访问的路由是/index处理函数。是在响应的时候返回HTML第二个参数就是使用模板直接写模板的名字然后就是传入占位符的参数这里串了一个结构体进去与占位符title完成映射。 2.多个模板渲染 如果有多个模板。可以统一进行渲染。 就是templates这个文件夹下可能有多个模板文件。 想要同时使用多个比如两个仍然可以使用r,LoadHTMLFiles函数。直接在后面打个逗号再多加载一个模板然后再针对这个模板写一个Get请求时的响应方式即可。 r : gin.Default()r.LoadHTMLFiles(./templates/index.tmpl,./templates/user.tmpl)r.GET(/index, func(ctx *gin.Context) {ctx.HTML(200, index.tmpl, gin.H{title: hello template,})})r.GET(/user, func(ctx *gin.Context) {ctx.HTML(200, user.tmpl, gin.H{title: hello user template,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}如果模板再多一点那加载模板就要写一堆这里还有方法 可以使用正则表达式 加载模板的函数改成这个r.LoadHTMLGlob(./templates/) 加载templates下面的所有模板。 这种方法有注意事项。如果templates里面还有一个文件夹里面还有模板那么这种情况叫做加载子模板这个时候就要用./templates//*代表子模版不然就会报错 自定义模板函数 啥时候用当我们想对模板进行一些处理时用。这样能够使用起来更加的便捷。 实现也非常容易但是注意这个要在加载模板之前完成。要先定义后加载。 模板 !DOCTYPE html html headmeta charsetutf-8meta nameviewport contentwidthdevice-width, initial-scale1titlegin_templates/title /head body {{.title | safe}} /body /htmlr : gin.Default()r.SetFuncMap(template.FuncMap{safe: func(str string) template.HTML {return template.HTML(str)},})// 模板解析,解析templates目录下的所有模板文件r.LoadHTMLGlob(templates/**)r.GET(/index, func(c *gin.Context) {// HTML请求// 模板的渲染c.HTML(http.StatusOK, index.tmpl, gin.H{title: a hrefhttp://baidu.com跳转到其他地方/a,})})err : r.Run(:8080)if err ! nil {log.Fatalln(err)} }首先具体说说自定义模板函数有什么用 从模板文件就可以看出来主函数只是实现。 在提供的HTML模板文件中{{.title | safe}}使用了一个占位符title。这里title是模板渲染时传入的数据的一部分可以被动态替换。| safe表示在渲染title变量时会通过名为safe的函数进行处理。这个函数的目的是将传入的字符串标记为安全的HTML不会被自动转义。 总的来说这个模板函数的作用就是在你使用模板的时候你要传参那么这个函数就可以对传入的这个参数进行处理这个参数在模板中的体现就是这个占位符。 {{.title | safe}}这里就注意下这种格式。 然后就是函数实现 r.SetFuncMap(template.FuncMap{safe: func(str string) template.HTML {return template.HTML(str)},})参数对于这个函数就是str然后返回值是一个HTML类型的。 “title”: “跳转到其他地方”, 正常来说那就直接对这个进行打印了因为这就是一个字符串。但是经过这个处理函数处理之后就会进行转义变成HTML格式的。 静态文件处理 如果在模板中引入静态文件比如样式文件index.css 模板文件为这样 !DOCTYPE html html headmeta charsetutf-8meta nameviewport contentwidthdevice-width, initial-scale1titlegin_templates/titlelink relstylesheet href/css/index.css /head body {{.title}} /body /html这里就引入了样式表但是显然这个路径是不对的正确的应该是./static/css/index.css才行 但是这里我就要这样做看看有什么方法进行处理。 我要想办法让他能够找到这个正确的路径这里的做法 用了静态加载 r.Static(映射“,“映射到正确的目录” r.Static(/css, ./static/css)这里就相当于对这个css做了映射。 简而言之就是前者是我在模板里面用的后者就是实际这个文件所在的目录。 甚至可以直接指定正确的文件在哪个目录。 会话 会话涉及到cookie和session的使用。 cookie 1、设置cookie func (c *Context) SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool) name:cookie名 value:cookie值 maxAge:有效时间 path:cookie路径 domain:cookie作用域 secure:这个用来设置cookie只能用https发送给服务器 httpOnly设置cookie不能被js获取到 r : gin.Default()r.GET(/cookie, func(c *gin.Context) {// 设置cookiec.SetCookie(site_cookie, cookievalue, 3600, /, localhost, false, true)})err : r.Run(:8080)if err ! nil {log.Fatalln(err)}直接使用SetCookie函数然后把那些参数都填好就完成了cookie的设置。 既然可以设置那就可以读取cookie。 2.读取cookie根据cookie的名字读取 就是通过data,err:ctx.Cookie(“name”)来实现err一般就是你访问的cookie根本就不存在这个时候就会报错了。 读出来是cookie的value 3.删除cookie。 通过将cookie的MaxAge设置为-1达到删除cookie的目的所以这还是用到了setcookie函数。别的都不用动动这个Maxage字段就完成了实现。 调用之后你去浏览器里f12看就会发现那个cookie不见了 Session 在gin框架中我们可以依赖gin-contrib/sessions中间件处理session 安装session包 go get github.com/gin-contrib/sessions看例子 r : gin.Default()// 创建基于cookie的存储引擎secret 参数是用于加密的密钥store : cookie.NewStore([]byte(secret))// 设置session中间件参数mysession指的是session的名字也是cookie的名字// store是前面创建的存储引擎我们可以替换成其他存储引擎r.Use(sessions.Sessions(mysession, store))r.GET(/hello, func(c *gin.Context) {// 初始化session对象session : sessions.Default(c)// 通过session.Get读取session值// session是键值对格式数据因此需要通过key查询数据if session.Get(hello) ! world {fmt.Println(没读到)// 设置session数据session.Set(hello, world)session.Save()}c.JSON(200, gin.H{hello: session.Get(hello)})})r.Run(:8080)虽然注释的很清楚了但是我心中有一些疑问 这里从知识方面再解读 Session在Web应用中是用来持久化用户状态的一种机制。当用户与应用交互时HTTP协议本身是无状态的意味着服务器默认情况下不会记住用户之前的操作。Session通过在服务器端存储用户特定的信息来跨多个请求保持状态而这些信息通过客户端的Cookie来引用。 当我们说“这些信息通过客户端的Cookie来引用”时我们是指Session机制如何在客户端例如用户的浏览器和服务器之间维持状态信息的关联。这个过程大致如下 1. 服务器创建Session 当用户第一次访问应用时服务器为该用户创建一个Session。这个Session包含一个唯一的标识符通常称为Session ID和与该用户相关的任何状态信息。 2. 发送Session ID到客户端 一旦Session创建完毕服务器会将Session ID发送到客户端方法是设置一个Cookie。这个Cookie包含Session ID并随着HTTP响应一起发送回用户的浏览器。 3. 客户端存储Session ID 用户的浏览器接收到这个Cookie后会将其存储起来。在之后的每次请求中浏览器都会自动将这个Cookie即包含Session ID的Cookie随请求一起发送到服务器。 4.服务器识别Session 对于接下来的每一个请求服务器通过请求中携带的Cookie中的Session ID识别出是哪个用户发起的请求。服务器然后可以查找对应的Session数据进而访问该用户的状态信息。 5. 状态信息的引用 这里“通过客户端的Cookie来引用”实际上是指客户端的Cookie具体来说是Cookie中的Session ID被用作在服务器上查找特定Session数据的键。换句话说客户端的CookieSession ID为服务器提供了一个引用或索引服务器用它来找到存储的状态信息这些状态信息包含了用户特定的数据如登录状态、购物车内容等。 结论 因此尽管状态信息Session数据存储在服务器端客户端的Cookie携带Session ID起到了桥梁的作用使得服务器能够为不同的用户请求匹配到正确的Session从而“记住”用户的状态跨多个请求。这是一种在无状态的HTTP协议上实现状态保持的常用技术。 现在看这个代码压力就不大了。 cookie.NewStore([]byte(“secret”))创建一个基于Cookie的Session存储引擎其中secret是用于加密Cookie的密钥以保护Session数据的安全。 sessions.Sessions(“mysession”, store)中间件被添加到Gin应用中其中mysession是Session同时也是Cookie的名字。这个中间件负责处理所有通过Gin路由的请求为它们提供Session支持。 处理请求和操作Session sessions.Default©获取当前请求的Session对象。 session.Get(“hello”)尝试从Session中读取键为hello的值。如果这是用户的第一次请求或者Session中没有设置该值结果将为nil。 如果session.Get(“hello”)的结果不是world则**通过session.Set(“hello”, “world”)在Session中设置值并调用session.Save()**来确保更改被保存。这些更改将被存储在服务器端的Session存储中并通过客户端的Cookie来引用。 c.JSON(200, gin.H{“hello”: session.Get(“hello”)})返回一个JSON响应其中包含了从Session中读取的hello键的值。 多session package mainimport (github.com/gin-contrib/sessionsgithub.com/gin-contrib/sessions/cookiegithub.com/gin-gonic/gin )func main() {r : gin.Default()store : cookie.NewStore([]byte(secret))sessionNames : []string{a, b}r.Use(sessions.SessionsMany(sessionNames, store))r.GET(/hello, func(c *gin.Context) {sessionA : sessions.DefaultMany(c, a)sessionB : sessions.DefaultMany(c, b)if sessionA.Get(hello) ! world! {sessionA.Set(hello, world!)sessionA.Save()}if sessionB.Get(hello) ! world? {sessionB.Set(hello, world?)sessionB.Save()}c.JSON(200, gin.H{a: sessionA.Get(hello),b: sessionB.Get(hello),})})r.Run(:8080) }配置多Session中间件r.Use(sessions.SessionsMany(sessionNames, store))向Gin引擎添加一个中间件该中间件配置了多个Session。sessionNames : []string{“a”, “b”}定义了两个Session的名称a和b这些Session将使用前面创建的cookie存储引擎。 设置路由和处理函数 在GET /hello路由的处理函数中使用sessions.DefaultMany(c, “a”)和sessions.DefaultMany(c, “b”)分别获取名为a和b的Session实例。然后就可以对Session进行进一步的操作 。 检查每个Session中是否存在键hello如果不存在或值不匹配则分别设置它们的值为world!“和world?”并保存更改。 最后响应包含了从两个Session中获取到的hello键的值展示了如何在同一个请求中独立管理多个Session。 也可以把Session存储在数据库中比如redis。 中间件 在Gin框架中中间件Middleware指的是可以拦截http请求-响应生命周期的特殊函数在请求-响应生命周期中可以注册多个中间件每个中间件执行不同的功能一个中间执行完再轮到下一个中间件执行。 换句话说就是请求发过来要穿过中间件响应返回也要通过穿过中间件。 中间件的常见应用场景如下 请求限速 api接口签名处理 权限校验 统一错误处理 Gin支持设置全局中间件和针对路由分组设置中间件设置全局中间件意思就是会拦截所有请求针对分组路由设置中间件意思就是仅对这个分组下的路由起作用。 1.中间件的使用 从这个gin.Default的源码可以看出 func Default() *Engine {debugPrintWARNINGDefault()engine : New()engine.Use(Logger(), Recovery())return engine }发现了创建中间件然后调用中间件这样的操作。这里就使用了两个中间件一个Logger一个是Recovery r : gin.New()// 通过use设置全局中间件// 设置日志中间件主要用于打印请求日志r.Use(gin.Logger())// 设置Recovery中间件主要用于拦截paic错误不至于导致进程崩掉r.Use(gin.Recovery())r.GET(/test, func(ctx *gin.Context) {panic(errors.New(test error))})r.Run(:8080)这里在请求中报了一个panic如果用了中间件那么就不会导致我们的程序启动不起来会发现状态码是500但是如果你没有用Recovery中间件没有拦截panic错误那么就会直接崩掉你去访问这个路由会发现路由都是不存在的因为服务器都没启动起来。 里面还用到了一个日志中间件会打印日志。 中间件有啥好处就是利用这些功能提供很大的便捷。 2.自定义中间件 可以去看看我们用的中间件有什么特征 func Logger() HandlerFunc {return LoggerWithConfig(LoggerConfig{}) }可以发现就是一个返回了HandlerFunc类型的函数。我们实际上就是去开发这个函数。可以看到内部返回了一个函数我们自定义的目标就是去实现这样的内部返回函数。 比如我这里也来一个 func Logger() gin.HandleFunc{return func(c *gin.Context){fmt.Println(my custom midd)} }然后直接主函数里面调用use就可以了。这样就实现了一个简单的中间件的自定义。调用之后由于前面说过请求会穿过中间件那么我这个中间件在遇到请求的时候就会工作起来然后打印一个my custom midd package main // 导入gin包 import ( github.com/gin-gonic/ginlogtime )// 自定义个日志中间件 func Logger() gin.HandlerFunc {return func(c *gin.Context) {t : time.Now()// 可以通过上下文对象设置一些依附在上下文对象里面的键/值数据c.Set(example, 12345)// 在这里处理请求到达控制器函数之前的逻辑// 调用下一个中间件或者控制器处理函数具体得看注册了多少个中间件。c.Next()// 在这里可以处理请求返回给用户之前的逻辑latency : time.Since(t)log.Print(latency)// 例如查询请求状态吗status : c.Writer.Status()log.Println(status)} }func main() {r : gin.New()// 注册上面自定义的日志中间件r.Use(Logger())r.GET(/test, func(c *gin.Context) {// 查询我们之前在日志中间件注入的键值数据example : c.MustGet(example).(string)// it would print: 12345log.Println(example)})// Listen and serve on 0.0.0.0:8080r.Run(:8080) }我觉得我第一次看不懂主要出现在纠结这个c.Next()的问题。下面详细解读 调用c.Next()确实会将控制权传递给后续的处理链通常是业务逻辑处理函数。然而c.Next()的调用方式确实有点类似于中断或钩子的概念但更准确地说这是一种中间件模式的实现它允许在请求的前后执行特定的代码。 如何理解c.Next()和中间件的工作机制 当Gin处理一个请求时它会按照中间件和路由处理函数注册的顺序构建一个调用栈。每个中间件会被依次调用直到调用栈中的最后一个函数通常是实际的业务逻辑处理函数。c.Next()在中间件中的作用是将控制权传递给调用栈中的下一个函数。 在c.Next()之前的代码这部分代码在请求达到业务逻辑处理函数之前执行。 可以用于设置一些预处理条件比如记录请求开始的时间、检查请求头部等。 调用c.Next()这个调用启动了调用栈中的下一个中间件或者路由处理函数的执行。如果没有更多的中间件或处理函数Gin将开始逐步回溯调用栈。 在c.Next()之后的代码这部分代码在所有后续的中间件和业务逻辑处理完成之后执行。这是处理请求后的逻辑如记录处理时间、发送监控指标、记录响应状态等。 中间件的执行流程 假设有中间件A和中间件B以及最终的业务处理函数C 中间件A的c.Next()之前的代码执行。 中间件B的c.Next()之前的代码执行。 业务处理函数C执行。 中间件B的c.Next()之后的代码执行。 中间件A的c.Next()之后的代码执行。 如果中间件B中没有c.Next这个流程会怎样 中间件A的c.Next()之前的代码执行。 中间件B的c.Next()之前的代码执行。 在这一步由于中间件B没有调用c.Next()请求处理流程在此中断不会继续传递到业务处理函数C或任何后续的中间件。 业务处理函数C不会被执行。 因为中间件B没有传递控制权所以即使有业务逻辑定义在C中它也不会得到执行。 中间件B的c.Next()之后的代码不会执行。 由于c.Next()未被调用所以实际上并没有进入到B中间件的“之后”阶段。 中间件A的c.Next()之后的代码也不会执行。 同样由于请求处理流程在B中被中断所以A中间件的后续逻辑也不会被执行。 所以c.Next这个流程是很重要的我也不用去考虑多了因为一旦有这种情况后面都指向终端。没有这个调用那么处理流程就会提前终止。 现在再来分析代码逻辑 r.GET(/test, func(c *gin.Context) {// 查询我们之前在日志中间件注入的键值数据example : c.MustGet(example).(string)// it would print: 12345log.Println(example)})1.这里先发送到/test路由的请求。 2.然后由于设置了中间件所以会先到中间件函数。 中间件函数中的c.Next之前的就会先执行。 t : time.Now() c.Set(example, 12345)记录当前的时间然后在gin的上下文中设置一个键值对example: “12345” 然后调用c.Next()此时控制权被传递给调用栈中的下一个中间件或处理函数。在这个例子中下一个处理函数是/test路由的处理函数。 执行路由处理函数 c.MustGet(“example”)从Gin的上下文中检索键名为example的值。这个值是在之前执行的日志中间件中设置的。MustGet方法会返回一个interface{}类型的值所以我们通过类型断言(string)将其转换为string类型。如果键名example不存在MustGet会引发panic因此使用它时需要确保该键确实存在。 打印获取的值如果一切顺利example变量应该包含字符串12345这个值接着被打印到日志中。 4.中间件后置处理 计算延迟计算从请求开始到现在的总耗时latency并通过log.Print(latency)打印出来。 记录状态码获取并打印响应的HTTP状态码status。 响应发送 此时所有处理均已完成Gin框架将生成的响应发送给客户端。
http://www.zqtcl.cn/news/655314/

相关文章:

  • ps做网站的优点国际设计师网站有哪些
  • 学校网站建立安阳市网站建设
  • 邢台做网站流程摄影工作室网站设计
  • 掉关键词网站重大军事新闻
  • 建材网站建设功能方案上海建筑室内设计有限公司
  • 高端企业网站设计公司怎么帮公司做网站建设
  • 湖北专业网站建设维修电话企业网络管理方案
  • 做网站外链wordpress网页怎么上传
  • wordpress站点优化石景山网站开发
  • 企业网站建设的流程店铺推广引流
  • 北京网站优化wyhseo信息化建设杂志社官方网站
  • 网站图片处理方案动漫制作这个专业怎么样
  • 做写手哪个网站好黄页网站建设黄页网站建设
  • 多语言企业网站免费模板网站哪个好
  • 拟一份饰品网站建设合同襄樊门户网站建设
  • 你对网站第一印象受欢迎的广州做网站
  • 网站开发项目的需求分析浙江省城乡建设网站证件查询
  • 整站seo定制简单 大气 网站模版
  • 网站界面设计策划书怎么做云匠网订单多吗
  • html教程 pdf网站建设优化兰州
  • 招聘网站可以同时做两份简历吗外贸网站示例
  • 黑链 对网站的影响企业融资计划书范本
  • 自己的简历怎么制作网站学院网站建设成效
  • 周口seo 网站郑州建站网站的公司
  • 网站布局模板北京装修大概多少钱一平方
  • 德阳网站建设ghxhwl风景网站模板
  • 昌邑网站建设拓者设计吧现代效果图
  • 学校网站建设成功案例网站开发需要学习哪些内容
  • 怎么让公司建设网站seo于刷网站点击
  • 网站建设合同严瑾建设网站宣传