Gin框架: HTML模板渲染之配置与语法详解

Gin的HTML模板配置


1 )单一目录的配置

  • 配置模板目录,在与main.go同级下, 新建目录,下面二选一,仅作举例, 这里选择 tpls

    • templates
    • tpls
  • 在 tpls 目录下新建 news.html

    <!-- 最简单的 -->
    <h1>News Page</h1><h3>标题:{{.title}}</h3>
    <p>内容:{{.content}}</p>
    
    • 注意,这里加上define和end头尾也可以
      {{ define "news.html" }}
      <h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>
      {{ end }}
      
  • 应用程序示例中配置

    • r.LoadHTMLGlob("tpls/*") 通配设定
    • r.LoadHTMLFiles("tpls/news.html") 逐个指定,多个在字符串中用,分隔

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/*")// r.LoadHTMLFiles("tpls/news.html") // 逐个指定多个需要使用逗号分隔// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/news", func(c *gin.Context) {c.HTML(statusOK, "news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}

2 )多目录配置

  • 配置模板目录,在与main.go同级下, 新建目录 tpls, 在内部再创建两个目录: web, admin

    • tpls/web
    • tpls/admin
  • 新建 tpls/admin/news.html

    {{ define "admin/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
    <p>内容:{{.content}}</p>{{ end }}
    
    • 这里内容和之前保持一致, 但要注意上下的定义
    • 如果不添加,会出现 Error #01: html/template: "admin/news.html" is undefined 的错误
    • 这里的 define 和 end 非常重要
    • define 配置的路径是匹配c.HTML 中的第二个参数
  • 应用程序示例中配置

    • r.LoadHTMLGlob("tpls/**/*") 通配设定
    • r.LoadHTMLFiles("tpls/admin/news.html") 逐个指定,多个在字符串中用,分隔

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/**/*")// r.LoadHTMLFiles("tpls/admin/news.html") // 逐个指定多个需要使用逗号分隔// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/admin/news", func(c *gin.Context) {c.HTML(statusOK, "admin/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}

3 )多层复杂目录配置

  • 首先看下设定的结构

    yourGinProject/ ·······························  根目录├── go.mod ··································  go mod 文件├── go.sum ··································  go sum 文件├── main.go ·································  main 文件└── tpls ····································· html模板目录├── a│   └── b│  		└── c│  		    └── d│  		        └── e│  		            └── news.html├── admin│   └── news.html│└── news.html
    
    • tpls/news.html

      {{ define "news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • tpls/admin/news.html

      {{ define "admin/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • tpls/a/b/c/d/e/news.html

      {{ define "a/b/c/d/e/news.html" }}<h1>News Page</h1><h3>标题:{{.title}}</h3>
      <p>内容:{{.content}}</p>{{ end }}
      
    • 注意各个 news.html 的define设定

程序示例

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/**/**/**/**/**/*")// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})// 这个配置和紧挨着下面的配置一致,只是路由自己设定了一个简洁版的r.GET("/x/news", func(c *gin.Context) {c.HTML(statusOK, "a/b/c/d/e/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 多层路由,同上r.GET("/a/b/c/d/e/news", func(c *gin.Context) {c.HTML(statusOK, "a/b/c/d/e/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 二级r.GET("/admin/news", func(c *gin.Context) {// r.LoadHTMLGlob("tpls/**/*") // 这里会 panic 报错r.LoadHTMLFiles("tpls/admin/news.html") // 这里正常可以访问,权重会大于顶部的全局配置c.HTML(statusOK, "admin/news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})// 一级r.GET("/news", func(c *gin.Context) {// r.LoadHTMLGlob("tpls/*") // 这里会 panic 报错r.LoadHTMLFiles("tpls/news.html") // 这里正常可以访问,权重会大于顶部的全局配置c.HTML(statusOK, "news.html", gin.H{"title": "新闻标题","content": "这是详细的新闻内容",})})r.Run()
}
  • 可以看到,在单个路由中r.LoadHTMLFiles 配置的用处了
  • 所以,以上就比较麻烦,一般而言,在一开始设计模板的时候,就要约定好规则
  • 特殊的设定只能应用于特殊的场景下

Gin的HTML模板语法


1 )基本渲染

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKtype User struct {Id      intName    stringHobby   string
}func main() {// 创建一个默认的路由引擎r := gin.Default()// 配置模板的文件r.LoadHTMLGlob("tpls/*")// 根路由r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.GET("/user", func(c *gin.Context) {c.HTML(statusOK, "user.html", &User{Id:      1,Name:    " Wang ", // 注意,这里有2个空格Hobby:   "swimming",})})r.Run()
}

html模板

{{ define "user.html" }}<h1>User Page</h1><h3>用户Id:{{.Id}}</h3>
<!-- 下面这种也是注释 -->
{{/* 这里是姓名,本身数据存在空格,但是这个去空格的方式无法去除 */}}
<h3>用户姓名:{{- .Name -}}</h3>
<h3>用户爱好:{{.Hobby}}</h3>{{ $xId := .Id }}<h3>演示变量:  {{- $xId -}} </h3>{{ end }}
  • {{ .x }} x是属性,基于这种方式来输出数据
  • {{/* 这里是注释 */}} 注意 /**/ 紧贴着 {{}},可以多行,不可嵌套
  • {{ $xId := .Id }} 单独设定变量, 变量名只能使用一个 $
  • {{- $xId -}} 这种去除空格,只能去除周围的空格,无法去除属性内包含的空格,且-紧贴{{}},同时与模板值之间需要使用空格分隔

效果图

2 ) 比较, 判断, range, with

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)type Article struct {Title   stringContent string
}var statusOK = http.StatusOKfunc main() {r := gin.Default()//加载模板 放在配置路由上面r.LoadHTMLGlob("tpls/**/*")//前台r.GET("/", func(c *gin.Context) {c.HTML(statusOK, "web/index.html", gin.H{"title": "首页","msg":   " 我是msg","score": 89,"hobby": []string{"吃饭", "睡觉", "写代码"},"newsList": []interface{}{&Article{Title:   "新闻标题1",Content: "新闻详情1",},&Article{Title:   "新闻标题2",Content: "新闻详情2",},},"testSlice": []string{},"news": &Article{Title:   "新闻标题",Content: "新闻内容",},})})r.Run()
}

html模板

{{ define "web/index.html" }}<h2>title变量展示: {{.title}}</h2><!-- 定义变量 -->
{{$t := .title}}<!-- 这里展示 title -->
<h4>这里通过变量,再次展现title: {{$t}}
</h4><!-- 条件判断 -->
<h4>这里基于判断,展示是否及格</h4>
{{if ge .score 60}}<p>及格</p>
{{else}}<p>不及格</p>
{{end}}<h4>这里基于判断,更加细度展示成绩</h4>{{if gt .score 90}}<p>优秀</p>
{{else if gt .score 80}}<p>良好</p>
{{else if gt .score 60}}<p>及格</p>
{{else}}<p>不及格</p>
{{end}}<!-- 循环遍历数据 -->
<h3>下面用range开始遍历 hobby 数组</h3>
<ul>{{range $key,$value:=.hobby}}<li>{{$key}}----{{$value}}</li>{{end}}
</ul><h3>下面用range开始遍历 新闻 数组</h3>
<ul>{{range $key,$value:=.newsList}}<li>{{$key}}----{{$value.Title}}---{{$value.Content}}</li>{{end}}        
</ul><h3>下面用range遍历中,空数组场景的展示</h3>
<ul>{{range $key,$value:=.testSlice}}<li>{{$key}}----{{$value}}</li>{{else}}<li>数组中没有数据</li>{{end}}        
</ul><!-- with 解构结构体 -->
<h3>不使用 with 结构属性</h3>
<p>{{.news.Title}}</p>
<p>{{.news.Content}}</p><h3>使用 with 结构属性</h3>
{{with .news}}<p>{{.Title}}</p><p>{{.Content}}</p>
{{end}}{{ end }}
  • 关于比较函数中的一些注意事项
    • eq 如果 arg1 == arg2 则返回真
    • ne 如果 arg1 != arg2 则返回真
    • lt 如果 arg1 < arg2 则返回真
    • le 如果 arg1 <= arg2 则返回真
    • gt 如果 arg1 > arg2 则返回真
    • ge 如果 arg1 >= arg2 则返回真

效果图

3 )高阶用法:预定义函数,自定义模板函数,嵌套模板

  • 首先看下设定的结构
    yourGinProject/ ·······························  根目录├── go.mod ··································  go mod 文件├── go.sum ··································  go sum 文件├── main.go ·································  main 文件└── tpls ····································· html模板目录├── web│     └── index.html└── common└── page_header.html└── page_footer.html
    

主程序

package mainimport ("html/template""net/http""time""github.com/gin-gonic/gin"
)type Article struct {Title   stringContent string
}var statusOK = http.StatusOK//时间戳转换成日期
func UnixToTime(timestamp int) string {t := time.Unix(int64(timestamp), 0)return t.Format("2006-01-02 15:04:05") // 这个是按照这个时间来进行格式化, 必须是这个时间点, 据说是go诞生之日
}func Println(str1 string, str2 string) string {return str1 + "----" + str2
}func main() {// 创建一个默认的路由引擎r := gin.Default()//自定义模板函数, 注意要把这个函数放在加载模板前r.SetFuncMap(template.FuncMap{"UnixToTime": UnixToTime,"Println":    Println,})//加载模板 放在配置路由上面r.LoadHTMLGlob("tpls/**/*")// 前台r.GET("/", func(c *gin.Context) {c.HTML(statusOK, "web/index.html", gin.H{"title": "首页","titleEn": "homePage","msg": "demo","date": 1708344953,})})r.Run()
}

web/index.html模板

{{ define "web/index.html" }}<!-- 这里定义通用头部 -->{{template "common/page_header.html" .}}<h3>下面演示: 预定义函数</h3><!-- 预定义函数 -->{{ len .title }}<br />{{ len .titleEn }}<!-- 自定义模板函数 --><h3>下面演示: 自定义模板函数</h3>{{.date}}<br /><br />{{UnixToTime .date}}<br><br>{{Println .title .msg}}<!-- 这里定义通用尾部 -->{{template "common/page_footer.html" .}}{{ end }}

common/page_header.html模板

{{ define "common/page_header.html" }}<h3>我是一个公共的标题---{{.title}}</h3>
{{end}}

common/page_footer.html模板

{{ define "common/page_footer.html" }}<h3>我是一个公共的底部</h3>
{{end}}
  • 在 预定义函数 中,执行模板时,函数从两个函数字典中查找

    • 首先是模板函数字典,然后是全局函数字典。
    • 一般不在模板内定义函数,而是使用 Funcs 方法添加函数到模板里
  • 预定义的全局函数如下

    • and
      • 函数返回它的第一个 empty 参数或者最后一个参数
      • 就是说"and x y"等价于"if x then y else x";所有参数都会执行
    • or
      • 返回第一个非 empty 参数或者最后一个参数
      • 亦即"or x y"等价于"if x then x else y";所有参数都会执行
    • not
      • 返回它的单个参数的布尔值的否定
    • len
      • 返回它的参数的整数类型长度
    • index
      • 执行结果为第一个参数以剩下的参数为索引/键指向的值
      • 如"index x 1 2 3"返回 x[1][2][3]的值;每个被索引的主体必须是数组、切片或者字典
    • print
      • 即 fmt.Sprint
    • printf
      • 即 fmt.Sprintf
    • println
      • 即 fmt.Sprintln
    • html
      • 返回与其参数的文本表示形式等效的转义 HTML
      • 这个函数在 html/template 中不可用
    • urlquery
      • 以适合嵌入到网址查询中的形式返回其参数的文本表示的转义值
      • 这个函数在 html/template 中不可用
    • js
      • 返回与其参数的文本表示形式等效的转义 JavaScript
    • call
      • 执行结果是调用第一个参数的返回值,该参数必须是函数类型,其余参数作为调用该函
        数的参数
      • 如"call .X.Y 1 2"等价于 go 语言里的 dot.X.Y(1, 2)
      • 其中 Y 是函数类型的字段或者字典的值,或者其他类似情况
      • call 的第一个参数的执行结果必须是函数类型的值(和预定义函数如 print 明显不同)
      • 该函数类型值必须有 1 到 2 个返回值,如果有 2 个则后一个必须是 error 接口类型
      • 如果有 2 个返回值的方法返回的 error 非 nil,模板执行会中断并返回给调用模板执行者该错误
  • 在自定义模板函数中,比如定义了 formatDate 方法,有两种用法

    • {{.now | formatDate}}
    • {{formatDate .now }}
  • 在嵌套 template中,注意最后的点(.)

效果图

静态文件服务器的配置


这块比较简单

主程序

package mainimport ("net/http""github.com/gin-gonic/gin"
)var statusOK = http.StatusOKfunc main() {// 创建一个默认的路由引擎r := gin.Default()//配置静态web目录 第一个参数表示路由, 第二个参数表示映射的目录r.Static("/static", "./static")// 前台r.GET("/", func(c *gin.Context) {c.String(statusOK, "Welcome to %v", "Home Page")})r.Run()
}
  • 这样就设定好了,在与 main.go 同级新建 static/images 目录,添加 mysql-logo.svg 图片
  • 访问该图片: http://localhost:8080/static/images/mysql-logo.svg
  • 其他的,如:css, js 这类也同样适用
  • 这时候,静态文件服务器就搭建好了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/691073.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

如何在nginx增加健康检查接口

在docker中部署的nginx或者在nginx部署的nginx一般是需要一个健康检查接口的 这样的话&#xff0c;就可以确定容器当前的状态是否是健康的 那么&#xff0c;如何给nginx增加一个健康检查的接口呢&#xff1f; 接下来呢&#xff0c;我们就演示一个在nginx中如何增加健康检查的…

【工具类】开源照片管理工具pthtoprism

1. pthtoprism 1. pthtoprism 1.1. 安装1.2. 管理照片方式 1.2.1. 直接管理原始照片目录1.2.2. 导入照片 1.3. 界面功能1.4. 参考资料 1.1. 安装 wget https://dl.photoprism.app/docker/docker-compose.yml # 修改 docker-compose.yml 文件&#xff0c;具体参考下面内容 d…

ArcGIS API for JavaScript 4.X 本地部署(js,字体)

0 目录&#xff08;4.19&#xff09; /4.19/ 1 修改文件 1.1 init.js 编辑器打开/4.19/init.js搜索文本[HOSTNAME_AND_PATH_TO_JSAPI]&#xff0c;然后将其连同前面的https://替换为http://ip地址/4.19&#xff0c;可以是localhost&#xff0c;只能本机引用 替换后&#xff…

Minio Server + Minio Client 数据迁移、备份

文章目录 1、概要2、mc安装3、添加minio集群4、数据同步4、cp 和 mirror 区别5、效果 1、概要 Minio Server Minio Client 实现minio 不同集群之间的数据迁移、数据备份 2、mc安装 $ wget http://dl.minio.org.cn/client/mc/release/linux-amd64/mc -P /usr/local/bin/ $ c…

学习数据接构和算法的第11天

题目讲解 删除有序数组中的重复项 ​ 去重 ​ 给你一个有序数组nums,请你原地删除重复出现的元素&#xff0c;使没个元素值出现一次&#xff0c;返回删除后数组的长度 ​ **注意&#xff1a;**不要使用额外的数组空间&#xff0c;你必须在原地修改输入数组并使用O(1)额外空…

Camunda快速入门(三):设计一个人工任务流程并配置表单

接上一篇文章&#xff1a;Camunda快速入门&#xff08;二&#xff09;&#xff1a;设计并执行第一个BPMN流程 在本节中&#xff0c;您将学习如何使用 BPMN 2.0 用户任务让人类参与到您的流程中。 1、添加用户任务活动节点 我们想修改我们的流程&#xff0c;以便我们可以让人…

使用SiteScan合理信息收集

一、介绍 作者&#xff1a;kracer 定位&#xff1a;专注一站式解决渗透测试的信息收集任务。 语言&#xff1a;python3开发 功能&#xff1a;包括域名ip历史解析、nmap常见端口爆破、子域名信息收集、旁站信息收集、whois信息收集、网站架构分析、cms解析、备案号信息收集、…

小迪安全2023最新版笔记集合--续更

小迪安全2023最新版笔记集合–续更 小迪安全2023最新笔记集合 章节一 ---- 基础入门&#xff1a; 知识点集合&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下…

RK3568平台 有线以太网接口之MAC芯片与PHY芯片

一.平台网络网络通路 平台有线以太网通路&#xff1a;有线以太网一般插入的是RJ45 座要与 PHY 芯片&#xff08;RTL8306M&#xff09;连接在一起&#xff0c;但是中间需要一个网络变压器&#xff0c;网络变压器经过模数转换后到达网卡(RTL8111)转换为帧数据后到达SOC。 二.网络…

业务型 编辑器组件的封装(复制即可使用)

使用需要安装 wangeditor npm i --save wangeditor import React from react; import E from wangeditor; import ./index.lessclass EditorElem extends React.Component {constructor(props) {super(props);this.isChange false;this.state {}}componentDidMount() {con…

【leetcode热题】二叉树的层次遍历 II

难度&#xff1a; 简单通过率&#xff1a; 44.9%题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 题目描述 给定一个二叉树&#xff0c;返回其节点值自底向上的层次遍历。 &#xff08;即按从叶子节点所在层到根节点所在的层&…

MySQL学习Day18——逻辑架构

一、逻辑架构剖析: 1.服务器处理客户端请求: 首先 MySQL 是典型的C/S架构&#xff0c;即client/Server架构&#xff0c;服务器端程序使用的mysqld。不论客户端进程和服务器进程是采用哪种方式进行通信&#xff0c;最后实现的效果都是:客户端进程向服务器进程发送段文本(SQL语…

在script标签写export为什么会抛错|type module import ES5 ES6 预处理 指令序言 JavaScript JS

今天我们进入到语法部分的学习。在讲解具体的语法结构之前&#xff0c;这一堂课我首先要给你介绍一下 JavaScript 语法的一些基本规则。 脚本和模块 首先&#xff0c;JavaScript 有两种源文件&#xff0c;一种叫做脚本&#xff0c;一种叫做模块。这个区分是在 ES6 引入了模块…

如何防止被恶意刷接口?

在面试时&#xff0c;经常会被问一个问题&#xff1a;如何防止别人恶意刷接口&#xff1f; 这是一个非常有意思的问题&#xff0c;防范措施挺多的。今天这篇文章专门跟大家一起聊聊&#xff0c;希望对你会有所帮助。 1 防火墙 防火墙是网络安全中最基本的安全设备之一&#x…

一个网络请求是怎么进行的?

一个网络请求是怎么进行的&#xff0c;涉及到浏览器的运行机制、页面加载流程。 当我们打开一个页面的时候&#xff0c;可以在NetWork面板上看到浏览器发起的网络请求&#xff0c;包括了页面、图片、CSS样式文件以及请求的状态&#xff0c;请求耗时和响应内容等等信息。 实际…

2023年全球软件开发大会(QCon北京站2023)2月:核心内容与学习收获(附大会核心PPT下载)

本次峰会是一个汇集了最新技术趋势、最佳实践和创新思维的盛会。对于从事软件开发和相关领域的专业人士来说&#xff0c;参加这样的大会将有助于他们了解行业动态、提升技能水平、拓展职业视野&#xff0c;并与同行建立联系和合作。 本次峰会包含&#xff1a;AI基础架构、DevO…

C++ move semantics

Move casts the argument to rvalue reference, and forward casts the argument to the value type it is bound to. The cast doesn’t move, doesn’t change the obj itself and does nothing but let the compiler know this is an rvalue.Move semantics happens for mov…

CSS常用技巧

【1】制作三角形 <style>.box {width: 0;height: 0;margin: 0 auto;/* 等腰直角三角&#xff1a; 各边框宽度一致&#xff0c;将上边框保留&#xff0c;其他边框设置为透明色 */border: 100px solid transparent;border-top: 100px solid red;} </style> <d…

用户空间与内核通信(一)

在Linux中&#xff0c;内核空间与用户空间是操作系统中的两个主要部分&#xff0c;它们有着明显的区别和不同的功能。 内核空间&#xff1a; 内核空间是操作系统内核运行的区域&#xff0c;它包括了操作系统内核代码、数据结构和设备驱动程序等。内核空间位于虚拟地址空间的最…

tcpdump 命令简记录

命令 tcpdump -i eth0 -nvv -vv -c 1000 -s 0 host 127.0.0.1 and port 80 -w oms_result.cap分析 -i : 选择要捕获的接口&#xff0c;通常是以太网卡或无线网卡&#xff0c;也可以是 vlan 或其他特殊接口。 -n : 不把网络地址转换成名字 -vv : 输出详细的报文信息 -c : 在收到…