Dawn's Blogs

分享技术 记录成长

0%

ORM

ORM(Object Relational Mapping,对象关系映射),作用是在关系型数据库和对象之间作一个映射。

1
2
3
数据表	 <-->  类
数据行 <--> 类的实例
字段 <--> 类的字段

GROM入门

详细内容查看GORM文档

连接数据库

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
package main

import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)

type UserInfo struct {
ID uint
Name string
Gender string
Hobby string
}

func main() {
dsn := "user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local"
db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{})
if err != nil {
panic(err)
}

// 创建表 自动迁移(把结构体与数据表进行对应)
db.AutoMigrate(&UserInfo{})

// 插入数据
u1 := UserInfo{1, "Dawn", "男", "Study"}
db.Create(&u1)

// 查询
var res UserInfo
db.First(&res)
fmt.Println(res)

// 更新
db.Model(&res).Update("hobby", "Games")

// 删除
db.Delete(&res)
}
阅读全文 »

回文数

回文数

解题思路

  1. 将数字转化为字符串,再判断是否是回文字符串
  2. 反转一半数字,如果该数字是回文,其后半部分反转后应该与原始数字的前半部分相同。
    • 如何知道反转数字的位数已经达到原始数字位数的一半?当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。

反转一半数字

阅读全文 »

Gin中间件

Gin框架允许开发者在处理请求的过程中,加入用户自己的钩子(Hook)函数。这个钩子函数就叫中间件,中间件适合处理一些公共的业务逻辑,比如登录认证、权限校验、数据分页、记录日志、耗时统计等。

定义中间件

Gin的中间件必须是一个gin.handlerFunc类型type HandlerFunc func(*Context)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// TimeCost是一个统计请求耗时的中间件
func TimeCost(c *gin.Context) {
start := time.Now()
// 调用该请求的剩余处理程序(中间件)
c.Next()
// 不调用该请求的剩余处理程序
// c.Abort()
// 计算耗时
cost := time.Since(start)
fmt.Printf("Time cost = %v\n", cost)
}

// 可以将中间件定义为闭包的形式,HadlerFunc以函数值返回
func otherMiddleware() gin.HadlerFunc {
// 做一些校验/准备/查询数据库工作
// ....
return func(c *gin.Context) {
/*
一些中间件的处理逻辑
*/
}
}
阅读全文 »

文件上传

单文件

使用FormFile方法可以获取到POST请求中上传的文件:

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
func main() {
r := gin.Default()
r.LoadHTMLFiles("upload.html")

r.GET("/upload", func(c *gin.Context) {
c.HTML(http.StatusOK, "upload.html", nil)
})

r.POST("/upload", func(c *gin.Context) {
// 从POST请求中读取文件
f, err := c.FormFile("f1")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{
"error": err.Error(),
})
} else {
// 把文件保存到本地
dst := fmt.Sprintf("./%s", f.Filename)
c.SaveUploadedFile(f, dst)
c.JSON(http.StatusOK, gin.H{
"status": "OK",
})
}
})

r.Run(":9090")
}

多文件

请求头参数设置 Content-Type: multipart/form-data,多文件上传请求为:

1
2
3
4
curl -X POST http://localhost:8080/upload \
-F "upload[]=@/Users/appleboy/test1.zip" \
-F "upload[]=@/Users/appleboy/test2.zip" \
-H "Content-Type: multipart/form-data"

首先解析 multipart forms,然后获取文件列表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func main() {
router := gin.Default()
// 为 multipart forms 设置较低的内存限制 (默认是 32 MiB)
router.MaxMultipartMemory = 8 << 20 // 8 MiB
router.POST("/upload", func(c *gin.Context) {
// Multipart form
form, _ := c.MultipartForm()
files := form.File["upload[]"]

for _, file := range files {
log.Println(file.Filename)

// 上传文件至指定目录
c.SaveUploadedFile(file, dst)
}
c.String(http.StatusOK, fmt.Sprintf("%d files uploaded!", len(files)))
})
router.Run(":8080")
}

重定向

HTTP重定向

使用Redirect方法即可进行HTTP重定向:

1
2
3
r.GET("/index", func(c *gin.Context) {
c.Redirect(http.StatusMovedPermanently, "http://www.baidu.com")
})

通过 POST 方法进行 HTTP 重定向(这里可以使用 StatusMovedPermanently 301 状态码、StatusFound 302 状态码和 StatusSeeOther 303 状态码)。

1
2
3
r.POST("/test", func(c *gin.Context) {
c.Redirect(http.StatusFound, "/foo")
})

303 状态码明确表示客户端应当采用 GET 方法获取资源,这点与 302 状态码有区别。

当 301、302、303 响应状态码返回时,几乎所有的浏览器都会把 POST 改成 GET,并删除请求报文内的主体,之后请求会自动再次发送。301、302 标准是禁止将 POST 方法改变成 GET 方法的,但实际使用时大家都会这么做。

阅读全文 »

Gin渲染

HTML渲染

首先编写模板文件index.tmpl

1
2
3
4
5
6
7
8
9
10
11
12
13
<!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>posts/index</title>
</head>
<body>
{{.title}}
</body>
</html>

Gin中使用LoadHTMLGlob()或者LoadHTMLFiles()进行模板渲染:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func main() {
r := gin.Default()
// 模板解析
r.LoadHTMLFiles("templates/index.tmpl")

r.GET("/index", func(c *gin.Context) {
// 模板渲染
c.HTML(http.StatusOK, "index.tmpl", gin.H{
"title": "Dawn ZH",
})
})
// 运行
r.Run(":9090")
}

阅读全文 »

概述

Gin是一个由Go语言编写的高性能Web框架。

简单案例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import (
"github.com/gin-gonic/gin"
"net/http"
)

func sayHello(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"message": "Hello world!",
})
}

func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// GET:请求方式;/hello:请求的路径
// 当客户端以GET方法请求/hello路径时,会执行sayHello
r.GET("/hello", sayHello)
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
r.Run()
}

RESTful

REST,即Representational State Transfer,表现层状态转化。RESTful架构:

  • 每一个URL代表一种资源
  • 客户端与服务器之间,传递这种资源的某种表现层(把资源具体呈现出来的形式,叫做它的表现层)
  • 客户端通过四个HTTP动词,对服务器端资源进行操作,实现表现层状态转化:
    • GET用于获取资源
    • POST用于新建资源
    • PUT用于更新资源
    • DELETE用于删除资源

Gin框架支持开发RESTful API的开发:

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
func main() {
// 创建一个默认的路由引擎
r := gin.Default()
// RESTFul风格
r.GET("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "GET",
})
})
r.POST("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "POST",
})
})
r.PUT("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "PUT",
})
})
r.DELETE("/book", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"method": "DELETE",
})
})
// 启动HTTP服务,默认在0.0.0.0:8080启动服务
r.Run()
}

概述

多版本并发控制(Multi-Version Concurrency Control,MVCC),通过数据的多个版本管理来实现数据库的并发控制。

在MySQL中,可重复读是默认的隔离级别。可重复读隔离级别下,不仅解决了脏读不可重复读问题,因为使用了MVCC,所以还一定程度上解决了幻读的问题。

快照读

快照读又叫一致性读,读取的是快照数据不加锁的简单的SELECT语句,都属于快照读

之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于MVCC,它在很多情况下,避免了加锁操作,降低了开销。既然是基于多版本,那么快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本。

当前读

当前读读取的是记录的最新版本的数据,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁加锁的SELECT语句,以及对数据进行增删改都会进行当前读

阅读全文 »

概述

主从复制就是,主机数据更新后,自动同步到备机的master/slaver机制,master以写为主,slaver以读为主

好处:

  • 读写分离
  • 容灾快速恢复(一台服务器宕机了,转到其他服务器)

主从复制

1
2
3
4
5
6
# 在从机上使用slaveof命令加入主机,host为主机地址,port为主机端口
# slaveof是异步操作,不论成功与否,直接返回OK,然后在后台异步与主机连接
slaveof host port

# 查看主从状态
info replication
阅读全文 »

零钱兑换 II

零钱兑换 II

解题思路

利用动态规划dp[i]表示金额为i的硬币组合数,边界条件是dp[0] = 1,表示组成金额0有一种组合数。

  • 初始化:dp[0] = 1
  • 遍历coins,对于其中的每个元素coin
    • 遍历icoin到amount,dp[i] += dp[i-coin]
  • dp[amount]为最终答案

解题代码

1
2
3
4
5
6
7
8
9
10
11
12
func change(amount int, coins []int) int {
dp := make([]int, amount+1) // dp[i]表示组成金额i的组合数
dp[0] = 1

for _, coin := range coins {
for i := coin; i <= amount; i++ {
dp[i] += dp[i-coin]
}
}

return dp[amount]
}
阅读全文 »

MySQL中利用来保证事务的隔离性,对并发操作进行控制。同时,锁冲突也是影响数据库并发访问性能的重要因素。

并发问题的解决

脏写

对于两个事务都进行写数据(写-写情况)的操作,可能会产生脏写问题,这是任何一种隔离级别都不允许这种问题的发生的。

所以在多个未提交事务相继对一条记录做改动时,需要让它们排队执行(通过锁实现)。

脏读 不可重复读 幻读

对于一个事务进行读取操作一个事务进行写数据的操作(读-写情况),可能会产生脏读、不可重复读和幻读的问题。对于这些问题,有两种解决方案。

方案一:读操作利用多版本并发控制MVCC,写操作进行加锁

所谓MVCC,就是生成一个ReadView,通过ReadView找到符合条件的记录版本。查询语句只能到生成ReadView之前已提交事务所做的更改。而写操作肯定针对的是最新的版本信息记录的历史版本和改动记录的最新版本并不冲突,也就是采用MVCC时,读-写并不冲突

普通的SELECT语句在READ COMMITTEDREPEATABLE READ隔离级别下会使用到MVCC读取记录:

  • READ COMMITTED隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了事务不可以读取到未提交的事务所做的更改,也就是避免了脏读现象。
  • REPEATABLE READ隔离级别下,一个事务在执行过程中只有第一次执行SELECT操作才会生成一个ReadView,之后的SELECT操作都复用这个ReadView,这样也就避免了不可重复读幻读的问题。
阅读全文 »