1 数据交互的格式
常见的数据交互格式有:
{"name":"lisi","address": ["广州","深圳"]}
2 JSON方式
2.1 JSON序列化
encoding/json
type Person struct {
Name string
Age int
}
p := Person {
Name: "lisi",
Age: 50,
}
data, _ := json.Marshal(&p)
fmt.Printf(string(data)); //{"Name":"lisi","Age":50}
同理,我们也可以使用上述方法对基本数据类型、切片、map等数据进行序列化。
keytag
type Person struct {
Name string `json:"my_name"`
Age int `json:"my_age"`
}
//序列化的结果:{"my_name":"lisi","my_age":50}
struct tag
"-""omitempty"",string"
2.2 JSON反序列化
str := `{"Name":"lisi","Age":50}`
// 反序列化json为结构体
type Person struct {
Name string
Age int
}
var p Person
json.Unmarshal([]byte(str), &p)
fmt.Println(p) //{lisi 50}
2.3 解析到interface
2.1和2.2的案例中,我们知道json的数据结构,可以直接进行序列化操作,如果不知道JSON具体的结构,就需要解析到interface,因为interface{}可以用来存储任意数据类型的对象。
map[string]interface{}[]interface{}
- bool 代表 JSON booleans,
- float64 代表 JSON numbers,
- string 代表 JSON strings,
- nil 代表 JSON null
现在我们假设有如下的JSON数据
jsonStr := `{"Name":"Lisi","Age":6,"Parents":["Lisan","WW"]}`
jsonBytes := []byte(jsonStr)
var i interface{}
json.Unmarshal(jsonBytes, &i)
fmt.Println(i) // map[Age:6 Name:Lisi Parents:[Lisan WW]]
i
i = map[string]interface{}{
"Name": "Lisi",
"Age": 6,
"Parents": []interface{}{
"Lisan",
"WW",
},
}
由于是空接口类型,无法直接访问,需要使用断言方式:
m := i.(map[string]interface{})
for k, v := range m {
switch r := v.(type) {
case string:
fmt.Println(k, " is string ", r)
case int:
fmt.Println(k, " is int ", r)
case []interface{}:
fmt.Println(k, " is array ", )
for i, u := range r {
fmt.Println(i, u)
}
default:
fmt.Println(k, " cannot be recognized")
}
}
上面是官方提供的解决方案,操作起来不是很方便,推荐使用第三方包有:
3 XML方式
3.1 解析XML
books.xml
<?xml version="1.0" encoding="utf-8"?>
<books version="1">
<book>
<bookName>离散数学</bookName>
<bookPrice>120</bookPrice>
</book>
<book>
<bookName>人月神话</bookName>
<bookPrice>75</bookPrice>
</book>
</books>
Unmarshal
package main
import (
"encoding/xml"
"fmt"
"io/ioutil"
"os"
)
type BookStore struct {
XMLName xml.Name `xml:"books"`
Version string `xml:"version,attr"`
Store []book `xml:"book"`
Description string `xml:",innerxml"`
}
type book struct {
XMLName xml.Name `xml:"book"`
BookName string `xml:"bookName"`
BookPrice string `xml:"bookPrice"`
}
func main() {
file, err := os.Open("books.xml")
if err != nil {
fmt.Printf("error: %v", err)
return
}
defer file.Close()
data, err := ioutil.ReadAll(file)
if err != nil {
fmt.Printf("error: %v", err)
return
}
v := BookStore{}
err = xml.Unmarshal(data, &v)
if err != nil {
fmt.Printf("error: %v", err)
return
}
fmt.Println(v)
}
3.2 生成XML
MarshalMarshalIndent
package main
import (
"encoding/xml"
"fmt"
"os"
)
type BookStore struct {
XMLName xml.Name `xml:"books"`
Version string `xml:"version,attr"`
Store []book `xml:"book"`
}
type book struct {
BookName string `xml:"bookName"`
BookPrice string `xml:"bookPrice"`
}
func main() {
bs := &BookStore{Version: "1"}
bs.Store = append(bs.Store, book{"离散数学", "120"})
bs.Store = append(bs.Store, book{"人月神话", "75"})
output, err := xml.MarshalIndent(bs, " ", " ")
if err != nil {
fmt.Printf("error: %v\n", err)
}
// 生成正确xml头
os.Stdout.Write([]byte(xml.Header))
os.Stdout.Write(output)
}
4 字段校验
len()
if len(r.Form["username"][0])==0{
//为空的处理
}
r.Form
r.Form.Get()r.Form.Get()
5 文件上传
2.1 前后端模拟上传
前端代码:
<html>
<head>
<title>上传文件</title>
</head>
<body>
<form enctype="multipart/form-data" action="/upload" method="post">
<input type="file" name="uploadfile" />
<input type="submit" value="upload" />
</form>
</body>
</html>
enctype
application/x-www-form-urlencoded # 表示在发送前编码所有字符(默认)
multipart/form-data # 文件上传使用,不会不对字符编码。
text/plain # 空格转换为 "+" 加号,但不对特殊字符编码。
golang的后端处理代码:
// 上传文件处理路由:http.HandleFunc("/upload", upload)
func upload(w http.ResponseWriter, r *http.Request) {
// 设置上传文件能使用的内存大小,超过了,则存储在系统临时文件中
r.ParseMultipartForm(32 << 20)
// 获取上传文件句柄
file, handler, err := r.FormFile("uploadfile")
if err != nil {
fmt.Println(err)
return
}
defer file.Close()
fmt.Fprintf(w, "%v", handler.Header)
f, err := os.OpenFile("./upload/" + handler.Filename, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer f.Close()
io.Copy(f, file)
}
2.2 go客户端模拟上传
Go支持模拟客户端表单功能支持文件上传:
package main
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"mime/multipart"
"net/http"
"os"
)
func postFile(filename string, targetUrl string) error {
bodyBuf := &bytes.Buffer{}
bodyWriter := multipart.NewWriter(bodyBuf)
//关键的一步操作
fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
if err != nil {
fmt.Println("error writing to buffer")
return err
}
//打开文件句柄操作
fh, err := os.Open(filename)
if err != nil {
fmt.Println("error opening file")
return err
}
defer fh.Close()
//iocopy
_, err = io.Copy(fileWriter, fh)
if err != nil {
return err
}
contentType := bodyWriter.FormDataContentType()
bodyWriter.Close()
resp, err := http.Post(targetUrl, contentType, bodyBuf)
if err != nil {
return err
}
defer resp.Body.Close()
resp_body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
fmt.Println(resp.Status)
fmt.Println(string(resp_body))
return nil
}
// sample usage
func main() {
target_url := "http://localhost:8080/upload"
filename := "./test.pdf"
postFile(filename, target_url)
}
3 防止重复提交
防止表单重复提交的方案有很多,其中之一是在表单中添加一个带有唯一值的隐藏字段:
- 1.在服务器端生成一个唯一的随机标识号,专业术语称为Token(令牌),同时在当前用户的Session域中保存这个Token。
- 2.将Token发送到客户端的Form表单中,在Form表单中使用隐藏域来存储这个Token
- 3.表单提交的时候连同这个Token一起提交到服务器端,然后在服务器端判断客户端提交上来的Token与服务器端生成的Token是否一致,如果不一致,那就是重复提交了,此时服务器端就可以不处理重复提交的表单。如果相同则处理表单提交,处理完后清除当前用户的Session域中存储的标识号。
在下列情况下,服务器程序将拒绝处理用户提交的表单请求:
- 存储Session域中的Token(令牌)与表单提交的Token(令牌)不同。
- 当前用户的Session中不存在Token(令牌)。
- 用户提交的表单数据中没有Token(令牌)。
用户名:<input type="text" name="username">
密码:<input type="password" name="password">
<input type="hidden" name="token" value="{{.}}">
<input type="submit" value="登陆">
token
func login(w http.ResponseWriter, r *http.Request) {
r.ParseForm()
token := r.Form.Get("token")
if token != "" {
//验证token的合法性
} else {
//不存在token报错
}
// 执行具体登录业务
}