目录
一、安装和基本概念
二、示例
三、测试
四、xorm库操作mysql
五、后端开发思路
六、常用工具
七、踩坑
一、安装和基本概念
1.在Goland控制台上输入以下语句安装gin
1 | go get -u github.com/gin-gonic/gin |
如果出现网络错误,就分别执行下述语句后再重新安装
1 | # 设置goproxy.io代理 |
2.几个内置的中间件
- gin.Logger(),用于日志
- gin.Recovery(),用于恢复恐慌panic()保持程序运行,然后返回500
3.gin.Default()定义的默认使用了Recovery和Logger中间件
- 而用gin.New()定义的则是不使用任何中间件
- 中间件的使用语法如下
1
router.Use(gin.Logger())
4.gin与mysql的交互
- 在Goland控制台上输入以下语句安装与mysql的驱动
1
go get github.com/go-sql-driver/mysql
- 在Goland控制台上输入以下语句安装xorm
1
go get xorm.io/xorm
5.如果在ajax异步调用的中间页面response函数中返回gin.HTML()的话,并且前端会使用这个返回值时,会直接将这个html文件所有代码附加在使用这个返回值地方
二、示例
1.基础网页demo
1 | package main |
- router := gin.Default()声明并创建名为router的路由
- router.GET(“/get”, func(c *gin.Context) {
})是访问触发时,c.JSON()返回一个状态码是200(200等价于http.StatusOK),响应内容是一个JSON格式字符串的响应c.JSON(200, gin.H{"message": "use get method"})
- “.GET”表示用GET方式来处理访问”/get”的请求
- func(c *gin.Context) {
c.JSON(200, gin.H{“message”: “use get method”})
}执行函数gin.Context,其内封装了request和response,其中c是变量名可以随意更改- 注意gin.H的H是一个map结构,不是结构体
2.不同的http请求格式示例
1 | router := gin.Default() |
3.切换输出的格式
1 | router.GET("/json", func(c *gin.Context) { |
4.获取api参数
- 定义,:name即表示把出现在这个位置的字符赋值给name属性,最后用c.Params读取
1
2
3router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
c.JSON(200, fmt.Sprintln(c.Params))
}) - 使用
1
2
3
4//若输入的url为http://localhost:8080/user/jane/20/beijing/female?id=999&height=170&wigth=100
//输出
"[{name jane} {age 20} {addr beijing} {sex female}]\n" - 获取某个指定值的写法
1
2
3
4router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
age := c.Param("age")
c.JSON(200, age)
})
5.获取url参数
- 定义
1
2
3
4
5
6
7router.GET("/user/:name/:age/:addr/:sex", func(c *gin.Context) {
id := c.Query("id") //id不存在时返回空串
//id := c.DefaultQuery("id", "001") //id不存在时返回001
height := c.Query("height")
wight := c.Query("wight")
c.JSON(200, gin.H("height": height, "id": id, "wight": wight)}
}) - 使用
1
2
3
4//若输入的url为http://localhost:8080/user/jane/20/beijing/female?id=999&height=170&wigth=100
//输出
{"height":"170","id":"999","wight":"100"}
6.获取表单参数
- 前端index.html代码为
1
2
3
4
5<form action="http://127.0.0.1:8080/form" method="post">
用户名:<input type="text" name="username" placeholder="请输入你的用户名"> <br>
密码:<input type="password" name="password" placeholder="请输入你的密码"> <br>
<input type="submit" value="提交">
</form> - 后端用gin接受
1
2
3
4
5
6
7router.POST("/form", func(c *gin.Context) {
types := c.DefaultPostForm("type", "post")
username := c.PostForm("username")
password := c.PostForm("password")
c.String(http.StatusOK, fmt.Sprintf("username:%s , password:%s , types:%s", username, password, types))
})
7.输出html文件
- 路由写法
1
2
3
4
5
6
7
8
9
10
11
12
13
14package main
import (
"github.com/gin-gonic/gin"
)
func main() {
router := gin.Default()
router.LoadHTMLGlob("tem/*")
router.GET("/index", func(c *gin.Context) {
c.HTML(200, "index.html", gin.H{"title": "测试", "ce": "123456"})
})
router.Run()
} - 根目录\tmp\index.html写法
1
2
3
4
5
6
7
8
9
10
11
12<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>{{.title}}</title>
</head>
<body>
fgkjdskjdsh{{.ce}}
</body>
</html> - 最终会在localhost:8080/index渲染成以下网页
1
2
3
4
5
6
7
8
9
10
11
12<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>测试</title>
</head>
<body>
fgkjdskjdsh123456
</body>
</html>
8.访问不同路径时的分离函数写法,下述代码无论是访问/demo、/test还是/aaaa都会去执行response,这种写法比之前的简洁多
1 | func response(c *gin.Context) { |
9.gin的前后端交互写法(以接受token为例)
- 前端
1
2
3
4
5
6
7
8<script src="https://cdn.dingxiang-inc.com/fe/common/jquery/1.9.1/jquery.min.js"></script>
$.ajax({
type:"GET",
url:"http://127.0.0.1:8080/index/bar",
dataType:"text",
data: { token: token}
}); - 后端
1
2
3
4
5
6
7
8
9func main(){
router := gin.Default()
router.GET("/index/bar", response)
}
func response(c *gin.Context){
token := c.Query("token") //从这里之后就可以用token的值了,注意是string类型
.....
}
10.测试时,显示字符串用法
1 | func response(c *gin.Context){ |
10.与mysql连接(使用xorm)
- 连接函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func main(){
orm1, err := OrmEngine()
if err != nil {
panic("创建orm错误")
}
}
func OrmEngine() (*xorm.Engine, error) {
//注册xorm引擎
engine, err := xorm.NewEngine("mysql", "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8")
if err != nil {
return nil, err
}
engine.ShowSQL(true)
return engine, nil
} - 增加操作
1
2
3sql := "insert into biao01(name,kkk) values (?, ?)"
orm1.Exec(sql, "Li", "4")
//在表biao01中增加一个name=Li, kkk=4的记录 - 删除操作
1
2
3sql := "delete from biao01 where kkk = ?"
orm1.Exec(sql, 4)
//删除表biao01中的kkk==4的记录 - 查询操作
1
2
3
4
5
6
7str, err := orm1.QueryString("select name from biao01 where kkk = 1;")
if err != nil {
panic(err)
}
for _, name_map := range str {
fmt.Println(name_map["name"])
} - 更改操作
1
2sql := "update biao01 set name = ? where kkk = ?"
orm1.Exec(sql, "wang", 4)
11.前后端加数据库交互demo
- 前端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>TEST</title>
<script src="https://cdn.dingxiang-inc.com/fe/common/jquery/1.9.1/jquery.min.js"></script>
</head>
<body>
<form action="#" method="GET" class="tm-call-to-action-form">
<input
type="text"
id="question"
value="初始值"
style="width: 500px"
required
/>
</form>
<button id="button1" onclick="myFunction()" class="btn btn-primary">
查询
</button>
<strong><p id="ans_class" ></p></strong>
<p id="ans" ></p>
<script>
function myFunction() {
var question = document.getElementById('question').value;
document.getElementById('button1').innerHTML = "正在查询,请稍等";
fetchData(question);
}
function fetchData(question = "默认值") {
$.ajax({
type: "GET",
url: "http://127.0.0.1:8080/qa_bar",
dataType:"text",
data: { Question: question},
success: function (result) {
document.getElementById('ans_class').innerHTML = "查询结果:";
if (result != 'None'){
document.getElementById('ans').innerHTML = result;
}else{
document.getElementById('ans').innerHTML = '不存在相关答案';
}
document.getElementById('button1').innerHTML = "查询";
}
});
}
</script>
</body>
</html> - 后端
1
2
3
4
5
6
7
8
9
10
11
12//router.GET("/qa_bar", res_qa)
func res_qa(c *gin.Context) {
str, err := Orm.QueryString("select name from biao01 where kkk = 4;")
if err != nil {
panic(err)
}
name := str[0]["name"]
question := c.Query("Question")
temple := name + question
c.String(200, temple)
}
12.后端传json和前端接json案例
- 后端
1
c.JSON(200, gin.H{"temple": temple})
- 前端
1
2
3
4
5
6
7
8
9
10$.ajax({
type: "GET",
url: "http://127.0.0.1:8080/qa_bar",
dataType:"text",
data: { Question: question},
success: function (result) {
var data = JSON.parse(result)
document.getElementById('ans').innerHTML = data.temple; //意为把id==ans的<p></p>内容改为json里的temple
}
});
13.分页的实现逻辑
1 | //controller层 |
17.将数据输出为浏览器下载Excel文件写法
- cotroller层写的响应函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28func (c *ControllerOrder) OrderTradeOutput(ctx *gin.Context) {
type param struct {
TradeStartTime int `form:"trade_start_time" json:"trade_start_time"`
TradeEndTime int `form:"trade_end_time" json:"trade_end_time"`
TradeType int `form:"trade_type" json:"trade_type"`
Page int `form:"page" json:"page"`
Limit int `form:"limit" json:"limit"`
}
request := ¶m{}
err := ctx.Bind(request)
if err != nil {
output.OutputOrderTrade(ctx, nil)
return
}
var search map[string]interface{}
err = util.StructTo(&request, &search)
if err != nil {
output.OutputOrderTrade(ctx, nil)
return
}
list, _, err := c.srv.Order().GetOrderTrade(ctx, search)
if err != nil {
output.OutputOrderTrade(ctx, nil)
return
}
output.OutputOrderTrade(ctx, list)
return
} - out工具层写输出excel具体实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65//DownloadRoleInfoBo用于最终生成excel所以要按一定格式处理
type DownloadRoleInfoBo struct {
TradeTime string
TradeType string
TradeAmount string
TradeUserTitle string
TradeNo string
}
/*注意这里list里的元素要是{
"trade_time": xx,
"trade_type": xx,
"trade_amount": xx,
"trade_user_title": xx,
"trade_no": xx,
}
*/
func OutputOrderTrade(context *gin.Context, list []map[string]interface{}) {
for _, role := range list {
res = append(res, &DownloadRoleInfoBo{
TradeTime: role["trade_time"].(string),
TradeType: role["trade_type"].(string),
TradeAmount: role["trade_amount"].(string),
TradeUserTitle: role["trade_user_title"].(string),
TradeNo: role["trade_no"].(string),
})
}
content := ToExcel([]string{`交易时间`, `交易类型`, `交易金额`, `交易人姓名`, `订单编号`}, res)
ResponseXls(context, content, "流水数据")
}
// 生成io.ReadSeeker 参数 titleList 为Excel表头,dataList 为数据
func ToExcel(titleList []string, dataList []interface{}) (content io.ReadSeeker) {
// 生成一个新的文件
file := xlsx.NewFile()
// 添加sheet页
sheet, _ := file.AddSheet("Sheet1")
// 插入表头
titleRow := sheet.AddRow()
for _, v := range titleList {
cell := titleRow.AddCell()
cell.Value = v
}
// 插入内容
for _, v := range dataList {
row := sheet.AddRow()
row.WriteStruct(v, -1)
}
var buffer bytes.Buffer
_ = file.Write(&buffer)
content = bytes.NewReader(buffer.Bytes())
return
}
// 向前端返回Excel文件
// 参数 content 为上面生成的io.ReadSeeker, fileTag 为返回前端的文件名
func ResponseXls(c *gin.Context, content io.ReadSeeker, fileTag string) {
fileName := fmt.Sprintf("%d%s%d%s%d%s%s.xlsx", time.Now().Year(), `-`, time.Now().Month(), `-`, time.Now().Day(), `-`, fileTag)
//fileName := fmt.Sprintf("%s.xlsx", fileTag)
c.Writer.Header().Add("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, fileName))
c.Writer.Header().Add("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
http.ServeContent(c.Writer, c.Request, fileName, time.Now(), content)
}
18.后端使用前端POST传的json写法
- 方法1用结构体接受
1
2
3
4
5
6
7
8
9type param struct {
Mobile string `form:"mobile" json:"mobile" binding:"required"`
PassWord string `form:"pass_word" json:"pass_word" binding:"required"`
BranchId int `form:"branch_id" json:"branch_id"`
Token string `form:"token" json:"token"`
}
var request param
ctx.Bind(&request) //ctx是*gin.Context类型
token := request.Token - 方法2用map[string]interface{}接受
1
2
3json := make(map[string]interface{}) //注意该结构接受的内容
ctx.BindJSON(&json)
token := json["token"].(string)
19.生成微信小程序对应页面二维码的响应函数(前端传一个path字符串)
1 | func (c *ControllerCourse) ProductCode(ctx *gin.Context) { |
20.一个*gin Context类型的常用方法
1 | //ctx是*gin Context类型 |
21.一个*gin.RouterGroup类型的常用方法
- 由*gin.Engine.Group(“xx”)产生,也可以看作是一个*gin.Engine对象
1
2
3
4
5
6
7
8
9
10
11
12//分组,此时app是127.0.0.1/qyk_back_api
app := router.Group("/qyk_back_api")
//再分组,有两个url:127.0.0.1/qyk_back_api/user/login和127.0.0.1/qyk_back_api/user/user_detail
userRouter := app.Group("/user")
{
userRouter.POST("/login", userController.Login)
userRouter.POST("/user_detail", userController.UserDetail)
}
//使用中间件,即路径前缀有127.0.0.1/qyk_back_api的都要先去调用一次auth.CORS()函数
app.Use(auth.CORS())
22.处理时间
- 例子:要求大于当前时间15分钟的订单全部更改状态
1
2d, _ := time.ParseDuration("-15m") //d为-15分钟
pastTime := time.Now().Add(d).Format("2006-01-02 15:04:05") //意为在当前时间上加上-15分钟,然后根据"2006-01-02 15:04:05"的格式转换成string
23.在外部想调用xorm数据库
- 需要store层的工厂方法生成出来的store.Factory
- 然后就可以用以下方法直接调用mysql层的方法了
1
2mysqlStore, _ := mysql.GetMysqlFactory()
orderList, err := mysqlStore.Order().GetListForPastTime(pastTime)
24.接受excel文件
1 | type param struct { |
25.接受json表示的数组
- 第一种:以string类型传过来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34type param struct {
List string `form:"list" json:"list" binding:"required"`
}
type paramDev struct {
Name string `form:"name" json:"name"`
IdNo string `form:"id_no" json:"id_no"`
}
var request param
ctx.Bind(&request)
//将List处理成每个成员符合json规范的[]string
strTmp := strings.Split(request.List, "},")
for index, element := range strTmp {
var str string
if index == 0 {
str = element[1:]
}
if index == len(strTmp)-1 {
str = element[:len(element)-1]
} else {
str += "}"
}
listTemple = append(listTemple, str)
}
for _, elementTemp := range listTemple {
//string通过json映射转结构体
var test paramDev
bt := []byte(elementTemp)
json.Unmarshal(bt, &test)
//结构体通过json映射转map
var element map[string]interface{}
err = util.StructTo(test, &element) //util.StructTo是自己写的工具函数 - 第二种:以array数组传过来
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21//result是[[{"id", "fullname"}, {...}]]格式的数据
type resGet struct {
Status int `json:"status"`
Message string `json:"message"`
DataVersion string `json:"data_version"`
Result [][]param `json:"result"`
}
type param struct {
Id string `json:"id"`
Name string `json:"fullname"`
}
var getRes resGet
allListUrl := "https://apis.map.qq.com/ws/district/v1/getchildren?key=" + config.Conf.UserWeb.TencentSecret
responseGet, err := http.Get(allListUrl)
json.NewDecoder(responseGet.Body).Decode(&getRes)
for _, element := range getRes.Result[0] {
fmt.Println(element.Name)
}
26.设计模式在项目中的实现
- 工厂模式实例
- store层的store.go有工厂函数接口,负责生产每张数据库表的store对象
- 每张数据库表的store对象也是一个接口,负责实现Get、Find的具体方法
27.gin框架设置和获取cookie的办法
- 设置cookie
1
2
3ctx.SetCookie("token", token, 1000, "/", "localhost", false, true)
# ctx是*gin.context类型
# 第一个参数为 cookie 名;第二个参数为 cookie 值;第三个参数为 cookie 有效时长,当 cookie 存在的时间超过设定时间时,cookie 就会失效,它就不再是我们有效的 cookie;第四个参数为 cookie 所在的目录;第五个为所在域,表示我们的 cookie 作用范围;第六个表示是否只能通过 https 访问;第七个表示 cookie 是否可以通过 js代码进行操作 - 获取cookie的值
1
cookie, err := context.Cookie("token")
28.gin框架获取header的方法
- 获得Header里的token
1
token := ctx.GetHeader("Authorization")
29.gin框架设置全局可用的键值对(绑定上下文)
1 | ctx.Set("user_id", user.Id) |
30.gin中间件
- 使用,在router.go文件里加上
1
router.Use(MiddleWare())
- 中间件.go的写法
1
2
3
4
5
6func MiddleWare() gin.HandlerFunc{
return func(ctx *gin.Context){
token := ctx.GetHeader("Authorization")
ctx.Next()
}
}
31.jwt生成token
- 生成token字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17func GetToken(userId int, secret string) (string,error) {
jwtKey = []byte(secret) //把密钥转化成[]byte类型,后面用来加密
claim := &MyClaims{
UserId: userId,
StandardClaims:jwt.StandardClaims{
IssuedAt: time.Now().Unix(), //签发时间
Subject: "userToken", //标识名
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claim) //生成jwt.token对象,HS256是对称加密,RS256是非对称加密(需要用额外的公/私钥生成库生成对应的公/私钥)
tokenString, err := token.SignedString(jwtKey) //用上述生成jwt.token对象和[]byte类型的secret加密生成token字符串
if err != nil {
return "", err
}
return tokenString, nil
} - 解析token字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func ParseToken(tokenString string)(*MyClaims, error) {
claim := &MyClaims{}
jwtKey = []byte(config.Conf.UserWeb.ApiSecret) //把密钥转化成[]byte类型,后面用来解密
token,err := jwt.ParseWithClaims(tokenString,claim, func(token *jwt.Token) (interface{}, error) {
return jwtKey,nil
})
if err == nil{
return nil, err
}
if token == nil{
return nil, errors.New("未获取到token")
}
if token.Valid == false{
return nil, errors.New("token解析失败")
}
return claim,nil
} - ExpiresAt表示的是过期时间,在结构体StandardClaims里声明下述属性意味着生成的token过期时间为5分钟,不设置默认是15天
1
ExpiresAt: nowTime.Add(time.Minute * 5).Unix(),
32.刷新token方案
- 双token机制,一个access_token里存用户信息用来调用业务接口,一个refresh_token不存用户信息只用来做刷新的识别
- 当access_token过期了就会用refresh_token去把两个token都刷新
- 如果refresh_token也过期了那么就需要用户重新登录
- 所以refresh_token一般需要设置过期时间是access_token的两倍,access_token是7天,refresh_token就是14天
- 原因:为防止
三、测试
1.postman进行web并发响应测试
- 新建一个collection
- 输入测试的url和参数并保存进里面
- 在collection右侧三角选择run collection
- iterations是并发数,delay是延迟(建议设置0)
2.Postman传文件
- Body下
- form-data下
- Key类型选file
- Value选本地文件
3.Postman传数组
- Body下
- form-data下
- Value值填以下格式数据
1
[{"name": "cupidatat enim ut dolore","id_no": "452525197305054416"},{"name": "cillum adipisicing Duis","id_no": "440126197310270915"}]
4.Postman传token
- Headers下
- 增添一个名为“Authorization”,值为token的项
四、xorm库操作mysql
1.获取所有的记录
1 | type QykBranch struct { |
2.指定从哪张表查的操作就是看传进去的model名
- 例如下述model就对应mysql库里的qy_branch表,不需要任何多余操作,默认按这种规则对应的
- 记住这个QykBranch其实就是xorm把mysql里的表转化成的结构体,是写死了的,每个属性对应得非常严密
1
2
3
4type QykBranch struct {
Id int `json:"id" xorm:"not null pk autoincr INT(11)"`
...
}
- 记住这个QykBranch其实就是xorm把mysql里的表转化成的结构体,是写死了的,每个属性对应得非常严密
- 举例使用,以下就表示从mysql库里的qy_branch表里找id和title
1
2branchList := make([]*model.QykBranch, 0)
err := b.orm.Cols("id", "title").Find(&branchList)
3.一些常规思想
- Get()方法一般要求传context.Context类型和一个int类型id进去,然后返回一个*model.QykBranch这种指针
1
2
3qyk_kc, err := o.store.Course().Get(ctx, qyk_order_detail.KcId)
//然后就可以可以用qyk_kc里面包含的值了
str := qyk_kc.Title
4.Where的使用
- 直接匹配
1
2session := o.orm.Where("del = ?", 0) //这里session后面可以看作是具有(del=0)的o.orm
session.Where("pay_time >= ? and pay_time <= ?", search.OrderStartTime, search.OrderEndTime) //session此时可以看作是具有(del=0和符合pay_time条件)的o.orm - 带and和or的用法
1
session.Where("kc_start_time >= ? and kc_end_time <= ?", search.StartTime, search.EndTime)
- 模糊匹配
1
2branch := strconv.Itoa(search.OrderNo) //strconv.Itoa()是把整型转换成字符串
session.Where("id like ?", "%"+branch+"%")
5.In的使用
1 | session.In("id", orderIdList) //即session是所有id存在orderIdList中的o.rom |
6.Limit的使用
1 | orderList := make([]*model.QykOrder, 0) |
7.更新数据(坑:为0时传进去会默认不更新,需要用col+update额外指定更新一下)
1 | func (m *messages) DoRead(ctx context.Context, id int) error { |
8.string类型的时间可以直接与mysql数据库的datetime进行比较
9.增加数据
- 先在model里赋值
1
2
3
4
5
6course := &model.QykKc{
BranchId: branchId.(int),
Title: request.CourseTitle,
IsFull: 2,
IsShelf: 2,
} - 在mysql层用Insert()
1
_, err := c.orm.Insert(course)
10.返回升序排序asc和降序排序desc的记录列表
1 | //按add_time从小到大排序(即时间靠后的排在前面) |
11.给表起别名Alias
1 | session.Alias("alias") .Where("alias.name = ?","u") |
12.xorm反序列化,即根据mysql的表自动生成model
- 执行以下命令新安装一个为xorm设计的cmd工具
1
go get github.com/go-xorm/cmd/xorm
- 安装完成后进入$GOPATH/pkg/mod/github.com/go-xorm/cmd/xorm目录,然后在cmd命令行运行以下命令
- 勿在ide的控制台运行,因为会有转义等命令格式错误
- windows系统在当前文件夹打开cmd命令行,可以直接在文件路径上输入cmd然后回车
1
2
3
4
5
6xorm reverse mysql root:741852963@tcp(39.105.120.230:3306)/dy?charset=utf8 templates/goxorm
# root是数据库用户名,741852963是数据库密码
# 39.105.120.230:3306是数据库地址,本地的话用127.0.0.1
# dy是数据库名字
# 若找不到xorm的话,用./ xorm运行或者把$GOPATH/pkg/mod/github.com/go-xorm/cmd/xorm该路径加入到环境变量中
- 最终得到一个model文件夹,里面存有根据数据库表格生成的model文件
- 若要得到带json反射tag的model文件,要在D:\goworkplace\pkg\mod\github.com\go-xorm\cmd\xorm\templates\goxorm\config的genJson改成1
1
genJson=1
- 若要满足在insert和update时自动更新时间
- 先修改config的created=add_time、updated=update_time,这个add_time和update_time是你希望在inser和update自动更新的字段名
- 再生成模型
- 若出现生成model命令不识别的问题
- 先在系统变量path里加一个D:\goworkplace(你的GOPATH路径)
- 然后进到D:\goworkplace\pkg\mod\github.com\go-xorm\cmd\xorm目录,在该路径下拉起cmd命令行
- 执行xorm reverse mysql 数据库管理员名:密码@(数据库ip地址:端口号)/数据库名?charset=utf8 templates/goxorm命令
13.xorm连接mysql的初始化
1 | func GetMysqlFactory() (store.Factory, error) { |
14.Ditsinct()去重只会筛选出去重的字段,而GroupBy()去重会筛选出所有的字段
- 即选种类数量用Distinct(),去重用GroupBy()
- 实际中我们往往用distinct来返回不重复字段的条数,其原因是distinct只能返回他的目标字段,而无法返回其他字段
15.数据库里的decimal类型生成的模型类型是string
16.遍历筛选的用法
- 情景:有一个group_id的[]int数组,要去表里找属性group_id在该数组里的记录
- 写法
- 接受传参
1
2
3
4
5type searchServer struct {
GroupId []int `json:"group_id"`
}
var param searchServer
util.StructTo(&search, ¶m) - 筛选
1
2
3
4
5
6
7
8
9if len(param.GroupId) != 0 {
for key, i := range param.GroupId {
if key == 0 { //这一步是为了防止出现Or()语句直接与前面的链接起来从而忽略掉group_id这个筛选条件
session = session.Where("group_id like ?", "%"+"\""+strconv.Itoa(i)+"\""+"%")
} else {
session = session.Or("group_id like ?", "%"+"\""+strconv.Itoa(i)+"\""+"%")
}
}
}
- 接受传参
五、后端开发思路
1.项目的分布和作用
- config文件夹:里面存用于配置的.json文件,方便改参数直接改这个就行
- tool包:用于写自己用得顺手的小工具,用途不限(初始化配置、生成token等等)
- router包:用于初始化路由和控制路由
- controller包:用于写路由中调用的反应函数
- 每个反应函数单独成一个文件,该文件就相当于是接口
- models包:里面文件用于控制数据库行为
2.go工作文件夹
- src放项目代码
- pkg放下载的库
3.一个接口的实现步骤
- 读前端写的API文档和产品写的原型图
- 路由层router写路由(url路径)
- controller层写方法:写构建返回前端的数据结构(srv)
- service写声明和实现:调用store层的方法获取数据,构建并返回给controller对应的数据结构(store)
- store层写声明,mysql层写实现:返回模型层的指针*
4.检查前端调用接口和前端数据
- 用Google打开网站
- 右键打开“检查”
- 在network里就记录了最近调用的请求路径和数据
5.软件开发应具有的特性
- 低耦合,高内聚:主要看类的内聚性是否高,耦合度是否低
- 敏捷开发:敏捷开发并不追求前期完美的设计,而是力求在很短的周期内开发出产品的核心功能,尽早发布出可用的版本。然后在后续的生产周期内,按照新需求不断迭代升级,完善产品
6.数据表必备字段
- id
- add_time
- update_time
- del_time
- del
7.MVC+store层概念(因为采用前后端分离,所有不用写View层)
- controller层处理返回值(每个接口有一个)
- service层调用store层方法进行筛选、组合字段等(每个接口有一个)
- store层一般写Create、Get、Update、Find、Del方法就足够用(多个接口共用)
- 运用到工厂模式,这里的store对象是由factor工厂生产出来的
- model层相对于一个结构体的声明,用于辅助store层,xorm库可以用脚本根据mysql库自动生成model
8.中间件的常用
- 出错使用
1
2ctx.Abort()
return - 顺利执行完后结尾用(注意这里不能用return,因为还要走下一步路由)
1
ctx.Next()
9.数据库有需要存[]int的需求时,将其转换成[“12”,”13”]的字符串存进去,因为这种形式可以直接structto方法用一个[]string接受在里面后变成str[0] = “12”,str[1] = “13”,然后就可以分别对每个成员直接用strconv.Atoi()来转成int了
六、踩坑
1.在mysql/mysql.go文件中一定要强制导入这个包,不然xorm会报错不识别mysql引擎
1 | _ "github.com/go-sql-driver/mysql" |
2.报错Error 1292: Incorrect datetime value: ‘’ for column是因为老版本mysql的datetime类型字段可以为’’,而新版本mysql使用严格模式导致不行,解决办法是修改sql_mode字段来实现
- 解决办法:进入服务器mysql环境依次执行以下语句
1
2
3mysql> set session sql_mode='';
mysql> set global sql_mode='';
3.要想保留两个int相除不损失精度的结果,应该用以下格式,先转float再除
1 | satisfy = float32(satisfySum) / float32(userNumber) |
4.excel的坑,如果在第21行增加了数据又删掉的话,会一直识别到21行而不是只识别有值的部分
- 最好用For row的循环中加以下代码来避坑
1
2
3if str[0] == "" && str[1] == "" && str[2] == "" {
continue
}
5.time.LoadLocation(“Asia/Shanghai”)在服务器上报错
- 原因是放到容器docker里运行时无法获得LoadLocation解析用的辅助文件
- 解决办法一:源代码改成以下代码
1
2
3
4tmp, err := time.LoadLocation("Asia/Shanghai")
if err != nil {
tmp = time.FixedZone("CST", 8*3600)
} - 解决办法二:在dockefile文件加入以下代码
1
2
3
4
5
6
7
8
9... ...
FROM golang:1.13-alpine
... ...
FROM alpine
... ...
COPY --from=0 /usr/local/go/lib/time/zoneinfo.zip /opt/zoneinfo.zip
... ...
ENV ZONEINFO /opt/zoneinfo.zip
... ...
6.Error:Field validation for failed on the ‘required’绑定Bind报错
- 原因是使用ctx.Bind(&request)时会根据前端请求的Content-Type来选择使用哪个解析,默认用form
- 解决办法(改用json格式传):
- 自测的话,需要选择postman的Body-raw-JSON来传
- 前端的话,需要Content-Type改成application/json
7.直接复制别的项目文件过来时,记得在go.mod文件里改module的名字成当前项目名
8.用map[string]的时候记得直接初始化,不要只var声明
1 | search := map[string]interface{}{} |