第二章 数据交互-模板渲染
第一节 使用模板
一. engine.LoadHTMLGlob:推荐
只有一个参数,通配符,如:template/* 意思是找当前项目路径下template文件夹下所有的html文件
e.g.:engine.LoadHTMLGlob("templates/*")
二. engine.LoadHTMLFiles:不推荐
不定长参数,可以传多个字符串,使用这个方法需要指定所有要使用的html文件路径
e.g.:engine.LoadHTMLFiles("templates/index.html","template/user.html")
三. 指定模板路径
// 使用*gin.Context下的HTML方法 func Hello(context *gin.Context) { name := "zhiliao" context.HTML(http.StatusOK,"index.html",name) }
注意:不要使用goland里面run,否则会报错
panic: html/template: pattern matches no files: `templates/*`
在cmd运行即可
四. 多级目录的模板指定
如果有多级目录,比如templates下有user和article两个目录,如果要使用里面的html文件,必须得在Load的时候指定多级才可以,比如:engine.LoadHTMLGlob("templates/**/*")
1.有几级目录,得在通配符上指明
两级:engine.LoadHTMLGlob("templates/**/*") 三级:engine.LoadHTMLGlob("templates/**/**/*")
2.指定html文件
// 除了第一级的templates路径不需要指定,后面的路径都要指定 e.g.:context.HTML(http.StatusOK,"user/index.html","zhiliao")
3.在html中
必须使用 {{ define "user/index.html" }} html内容 {{ end }} 包起来
第二节 静态文件的使用
一、指定静态文件路径
engine.Static("/front", "static") 推荐使用
第一个参数是url,第二个参数是url对应的文件夹
engine.StaticFS("/static", http.Dir("static"))
二、前端引入静态文件
<link rel="stylesheet" href="/front/user/index.css">
第三节 自定义模板选渲染器
使用"html/template"
要指定所有的html路径,不推荐
router := gin.Default() html := template.Must(template.ParseFiles("test1.html", "test2.html")) router.SetHTMLTemplate(html) router.Run(":8080")
第四节 其它数据类型渲染
一、结构体
后端: type User struct { Id int Name string } func Hello(context *gin.Context) { user := User{Id:1,Name:"hallen"} context.HTML(http.StatusOK,"user/index.html",user) } 前端:
{{.Id}} {{.Name}}
二、数组
后端: func Hello(context *gin.Context) { arr := [5]int{1,2,3,4,5} context.HTML(http.StatusOK,"user/index.html",arr) } 前端: {{range .}} {{.}} {{end}}
或者: {{range $i,$v := .}} {{$i}} {{$v}} {{end}} 注意:range后面有两个变量,那就是角标和对应的元素值 如果只有一个值,就是数组的元素值
三、结构体数组
后端 type User struct { Id int Name string } func Hello(context *gin.Context) { arr_struct := [2]User{{Id:1,Name:"hallen1"},{Id:2,Name:"hallen2"}} context.HTML(http.StatusOK,"user/index.html",arr_struct) } 前端: {{range $v := .}} {{$v.Id}} {{$v.Name}} <br> {{end}}
四、map
后端: func Hello(context *gin.Context) { map_data := map[string]string{ "name":"hallen", "age":"18", } context.HTML(http.StatusOK,"user/index.html",map_data) } 前端: {{.name}} {{.age}}
五、结构体+map
后端: type User struct { Id int Name string } func Hello(context *gin.Context) { map_struct_data := map[string]User{ "user":User{Id:1,Name:"hallen"}, } context.HTML(http.StatusOK,"user/index.html",map_struct_data ) } 前端: {{.user.Id}} {{.user.Name}}
六、切片
和数组类似,唯一的区别是不用指定长度了,长度是可变的
第五节 获取GET请求数据
一、带参数的路由:路径中直接加上参数值
e.g. http://127.0.0.1:8080/user/hallen
1.第一种情况:使用占位符: ,必须得指定这个路径
- 路由:engine.GET("/user/:name",Index)
- 如:http://127.0.0.1:8080/user/hallen,这里必须指定name这个路径,不然会找不到
- 获取方式:context.Param("name")
2.第二种情况:使用占位符*,可以不用匹配这个路径
- 路由:engine.GET("/user/*name",Index)
- 这里可以指定name这个路径,也可以不用指定
- 如:下面两种都可以访问
- 获取方式:context.Param("name")
区别:参数前面是使用冒号还是使用通配符,冒号的比如指定路径,通配符的可以不用
代码示例:
func Index(c *gin.Context) { name := c.Param("name") c.String(http.StatusOK, "Hello %s", name) } // engine.GET("/user/*name",Index) engine.GET("/user/:name",Index)
二、带参数的路由:路径中使用参数名
1.contxt.Query
-
获取:contxt.Query("name")
2.contxt.DefaultQuery
- 传参:http://127.0.0.1:8080/user/?name=hallen
- 获取:contxt.DefaultQuery("name","hallen")
区别:DefaultQuery比Query多了个默认值,如果没有获取到会使用默认值
3.contxt.QueryArray
-
获取:names := contxt.QueryArray("name")
-
数据结构:[1,2,3,4,5]
4.contxt.QueryMap
- 传参:http://127.0.0.1:8080/user?name[1]=hallen1&name[2]=hallen2
- 获取:name_map := contxt.QueryMap("name")
- 数据结构:map[1:hallen1 2:hallen2]
第六节 获取POST请求数据
注意:是post请求
一、获取表单提交的数据
1.contxt.PostForm("username") 获取表单中的name属性对应的值
示例代码:
前端:submit提交 <form action="/hello_add" method="post"> <input type="text" name="username"><br> <input type="text" name="age"><br> <input type="submit" value="提交"> </form> 后端: func IndexAdd(contxt *gin.Context) { name := contxt.PostForm("username") age := contxt.PostForm("age") contxt.String(200,"hello,%s,年龄为:%s",name,age) } func main() { engine := gin.Default() engine.LoadHTMLGlob("templates/**/*") engine.Static("/static","static") engine.POST("/hello_add",IndexAdd) engine.Run() }
2.contxt.DefaultPostForm("username", "hallen") 如果没有获取到则使用指定的默认值
3.contxt.PostFormArray("love") 如果提交的数据有多个相同的name,获取数组
前端: <form action="/hello_add" method="post"> <input type="text" name="username"><br> <input type="text" name="age"><br> ck1:<input type="checkbox" name="ck" value="1"> ck2:<input type="checkbox" name="ck" value="2"> ck3:<input type="checkbox" name="ck" value="3"> <input type="submit" value="提交"> </form> 后端: arr_ck := contxt.PostFormArray("ck") contxt.PostFormMap("username") 前端代码: <form action="/hello_add" method="post"> <input type="text" name="username[1]"><br> <input type="text" name="username[2]"><br> <input type="submit" value="提交"> </form> 后端代码: map_name := contxt.PostFormMap("username") 数据结构:map[1:xx1 2:xx2] 注意:name要以map的格式定义,指定key,用户输入value,
二、ajax交互
前端使用ajax提交,后端和form表单的获取方式一样,唯一的区别就是返回的是json
前端: <script src="/static/js/jquery.min.js"></script> <form> 姓名:<input type="text" id="name"> 年龄:<input type="text" id="age"> <input type="button" value="提交" id="btn_add"> </form> <script> var btn_add = document.getElementById("btn_add"); btn_add.onclick = function (ev) { var name = document.getElementById("name").value; var age = document.getElementById("age").value; $.ajax({ url:"/hello3_add", type:"POST", data:{ "name":name, "age":age }, success:function (data) { alert(data["code"]); alert(data["msg"]); }, fail:function (data) { } }) } </script> 注意:引入jquery.min.js: 后端: name := context.PostForm("name") age := context.PostForm("age") fmt.Println(name) fmt.Println(age) messgae_map := map[string]interface{}{ "code":200, "msg":"提交成功", } context.JSON(http.StatusOK,messgae_map) //context.JSON(http.StatusOK,gin.H{ // "code":200, // "msg":"提交成功", //})
第七节 参数绑定
能够基于请求自动提取JSON、form表单和QueryString类型的数据,并把值绑定到指定的结构体对象
一、ShouldBind
示例代码:
type User struct { Id int `form:"id" json:"id"` Name string `form:"name" json:"name"` } structTag:指定字段名称,不用使用首字母大写的 func Index(contxt *gin.Context) { var u User err := contxt.ShouldBind(&u) fmt.Println(err) fmt.Println(u) contxt.String(http.StatusOK, "Hello %s", u.Name) }
二、ShouldBindWith
可以使用显式绑定声明绑定 multipart form:
c.ShouldBindWith(&form, binding.Form)
或者简单地使用 ShouldBind 方法自动绑定
三、ShouldBindQuery
ShouldBindQuery函数只绑定 url 查询参数而忽略 post 数据
第八节 文件上传
一、form表单上传文件
1.单文件
前端: <form action="/upload2" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="submit" value="提交"> </form> 注意:设置enctype参数 后端: func Upload2(context *gin.Context) { fmt.Println("+++++++++++++++") file,_ := context.FormFile("file") // 获取文件 fmt.Println(file.Filename) file_path := "upload/" + file.Filename // 设置保存文件的路径,不要忘了后面的文件名 context.SaveUploadedFile(file, file_path) // 保存文件 context.String(http.StatusOK,"上传成功") } 防止文件名冲突,使用时间戳命名: unix_int := time.Now().Unix() // 时间戳,int类型 time_unix_str := strconv.FormatInt(unix_int,10) // 讲int类型转为string类型,方便拼接,使用sprinf也可以 file_path := "upload/" + time_unix_str + file.Filename // 设置保存文件的路径,不要忘了后面的文件名 context.SaveUploadedFile(file, file_path) // 保存文件
2.多文件上传
前端: <form action="/upload2" method="post" enctype="multipart/form-data"> <input type="file" name="file"> <input type="file" name="file"> <input type="submit" value="提交"> </form> 注意:不要忘了enctype参数 后端: func Upload2(context *gin.Context) { form,_ := context.MultipartForm() files := form.File["file"] for _,file := range files { // 循环 fmt.Println(file.Filename) unix_int := time.Now().Unix() // 时间戳,int类型 time_unix_str := strconv.FormatInt(unix_int,10) // 讲int类型转为string类型,方便拼接,使用sprinf也可以 file_path := "upload/" + time_unix_str + file.Filename // 设置保存文件的路径,不要忘了后面的文件名 context.SaveUploadedFile(file, file_path) // 保存文件 } context.String(http.StatusOK,"上传成功") } 注意:form.File["file"] 这里是中括号,不是小括号
二. ajax方式上传文件
后端代码和form表单方式一样的
1. 单文件
前端: <script src="/static/js/jquery.min.js"></script> <form> {{/*<input type="file" name="file">*/}} 用户名:<input type="test" id="name"><br> <input type="file" id="file"> <input type="button" value="提交" id="btn_add"> </form> <script> var btn_add = document.getElementById("btn_add"); btn_add.onclick = function (ev) { var name = document.getElementById("name").value; var file = $("#file")[0].files[0]; var form_data = new FormData(); form_data.append("name",name); form_data.append("file",file); $.ajax({ url:"/upload2", type:"POST", data:form_data, contentType:false, processData:false, success:function (data) { console.log(data); }, fail:function (data) { console.log(data); } }) } </script> 注意: 1.引入juery.min.js文件 2.ajax中需要加两个参数: contentType:false, processData:false, processData:false 默认为true,当设置为true的时候,jquery ajax 提交的时候不会序列化 data,而是直接使用data contentType: false 不使用默认的application/x-www-form-urlencoded这种contentType 分界符:目的是防止上传文件中出现分界符导致服务器无法正确识别文件起始位置 ajax 中 contentType 设置为 false 是为了避免 JQuery 对其操作,从而失去分界符
2.多文件
name名称不相同就是个单文件上传 name名称相同 <script> var btn_add = document.getElementById("btn_add"); btn_add.onclick = function (ev) { var name = document.getElementById("name").value; console.log($(".file")); var files_tag = $(".file"); var form_data = new FormData(); for (var i=0;i<files_tag.length;i++){ var file = files_tag[i].files[0]; form_data.append("file",file); } console.log(files); form_data.append("name",name); $.ajax({ url:"/upload2", type:"POST", data:form_data, contentType:false, processData:false, success:function (data) { console.log(data); }, fail:function (data) { console.log(data); } }) } </script>
第九节 其它数据格式输出
JSON
context.JSON(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "msg":"提交成功", "html":"<b>Hello, world!</b>", }) 结果:{"code":200,"html":"\u003cb\u003eHello, world!\u003c/b\u003e","msg":"提交成功","tag":"\u003cbr\u003e"}
AsciiJSON
生成具有转义的非 ASCII 字符的 ASCII-only JSON
context.AsciiJSON(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "msg":"提交成功", "html":"<b>Hello, world!</b>", }) 结果:{"code":200,"html":"\u003cb\u003eHello, world!\u003c/b\u003e","msg":"\u63d0\u4ea4\u6210\u529f","tag":"\u003cbr\u003e"}
JSONP
使用 JSONP 向不同域的服务器请求数据。如果查询参数存在回调,则将回调添加到响应体中
context.JSONP(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "msg":"提交成功", "html":"<b>Hello, world!</b>", }) 结果:{"code":200,"html":"\u003cb\u003eHello, world!\u003c/b\u003e","msg":"提交成功","tag":"\u003cbr\u003e"}
如果传输的数据在两个不同的域,由于在javascript里无法跨域获取数据,所以一般采取script标签的方式获取数据,传入一些callback来获取最终的数据,这就有可能造成敏感信息被劫持
PureJSON
context.PureJSON(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "msg":"提交成功", }) 结果:{"code":200,"html":"<b>Hello, world!</b>","msg":"提交成功","tag":"<br>"}
SecureJSON
使用 SecureJSON 防止 json 劫持。如果给定的结构是数组值,则默认预置 "while(1)," 到响应体。
names := []string{"lena", "austin", "foo"} // 将输出:while(1);["lena","austin","foo"] context.SecureJSON(http.StatusOK, names)
json劫持:利用网站的cookie未过期,然后访问了攻击者的虚假页面,那么该页面就可以拿到json形式的用户敏感信息
XML
context.XML(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "msg":"提交成功", "html":"<b>Hello, world!</b>", }) 结果: <map> <code>200</code> <tag><br></tag> <msg>提交成功</msg> <html><b>Hello, world!</b></html> </map>
YAML
context.YAML(http.StatusOK,gin.H{ "code":200, "tag":"<br>", "user":gin.H{"name":"zhiliao","age":18}, "html":"<b>Hello, world!</b>", }) 结果: code: 200 html: <b>Hello, world!</b> tag: <br> user: age: 18 name: zhiliao
ProtoBuf
定义proto文件
user.proto
syntax = 'proto3'; package user; message User { string name = 1; int32 age = 2; }
messages类型:message,server,enum
导出go文件
protoc --go_out=. user.proto
使用
user_data := &user.User{ Name:"zhiliao", Age:18, } context.ProtoBuf(200,user_data)
注意:是指针
第十节 重定向
ctx.Redirect(http.StatusFound,"/xxx") 状态码是302