image frame

XiShng Blog

既往拼搏,守护一生所爱

MySQL VarBinary使用

VARBINARY 是 MySQL 数据库中用于存储二进制数据的一种数据类型,它可以存储任何类型的二进制数据,例如图像、声音、视频等。与 BLOB 类型不同, VARBINARY 类型没有指定最大长度,而是根据实际需要动态调整存储空间。
如VARBINARY(16)所代表的含义为,16位的二进制数据流。

大部分情况下,我们这边使用VARBINARY类型时,更多的是针对于UUID作为索引的场景。

在业务代码中,我们可能会这样子来实现VARBINARY类型数据的写入/读取:

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
type UUIDBinary string

// 将UUID字符串,转换为二进制,并通过interface来实现gorm引擎可识别的数据格式,以供写入MySQL中
func (id UUIDBinary) Value() (v driver.Value, err error) {
if id == "" {
return nil, nil
}
// uuid.Parse 会将当前UUID,转换为Hex:以32个字符表示的UUID,去除了其中的'-'
u, err := uuid.Parse(string(id))
if err != nil {
return nil, err
}
// 将UUID的 hex 转换为 16 进制的 数据流
uuidBinary, err := u.MarshalBinary()
if err != nil {
return nil, err
}
return uuidBinary, nil
}

// 读取到数据后,将其转换为二进制,再进一步转换为原本的UUID数据
func (id *UUIDBinary) Scan(value interface{}) (err error) {
if value == nil {
*id = ""
return nil
}
s, ok := value.([]byte)

if !ok {
*id = ""
return errors.New("invalid scan source")
}

// 从 16 进制的数据流中,unhex出UUID
u, err := uuid.FromBytes(s)
if err != nil {
*id = ""
return err
}
*id = UUIDBinary(u.String())
return nil
}

而如果我们在插入数据后,去检索插入的数据时,可以发现该数据无法直接辨识出来:
无法直接辨识的数据
那么如何在不通过ORM的情形下,快速的可以查找到我们想要看到的数据呢?

现在定义一张数据表,如下所示:

1
2
3
4
5
6
7
8
create table stores
(
id varbinary(16) not null primary key, // 店铺ID对应的UUID主键
origin_id varchar(255) not null, // 店铺ID
created_at datetime not null,
updated_at datetime not null,
name varchar(255) null comment '店铺名称'
)

并通过我们的业务代码来插入了几条数据【代码不贴了,结合上面的UUIDBinary,就可以快速的搞出来了】。
现在有如下数据:
那么其中ID,便是UUID取hex后的生成的16进制数据流字符串【如果再此处还不理解,回头再看一下业务代码中的注释部分】
通过MySQL的Hex函数,我们可以还原ID的原貌:
新建的数据
那么,假如此时你知晓一个UUID,如:000b3ef9-2b37-4f5b-a8f9-33f313357c41,接下来你想知道这个Id对应的真实店铺ID是多少,那就可以尝试去查询了:

  1. 按照原理进行推导,可得如下查询语句
    1
    SELECT * FROM stores WHERE hex(id) = REPLACE('000b3ef9-2b37-4f5b-a8f9-33f313357c41', '-', '');
    匹配到的数据
  2. 尝试反推,进行优化
    1
    SELECT * FROM stores WHERE id = UNHEX(REPLACE('000b3ef9-2b37-4f5b-a8f9-33f313357c41', '-', ''));
    优化后匹配到的数据
    两次查询耗时,可以发现有着10倍速度的差距。
    那么为什么会出现这种情况呢?
  • 当我们的条件类型与字段类型一致时,MySQL本身是不需要做出任何的处理,即可快速的根据索引命中目标数据
  • 当我们的条件类型是需要将字段类型进行变更才可以比较时,MySQL会显式的将所有参与比较的字段进行转换,直至可以命中目标索引。

GoPDF书写PDF文件

INSTALLATION

1
go get -u github.com/signintech/gopdf

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
package main
import (
"log"
"github.com/signintech/gopdf"
)

func main() {
// 初始化PDF页面
pdf := gopdf.GoPdf{}
// 设定PDF页面大小
pdf.Start(gopdf.Config{ PageSize: *gopdf.PageSizeA4 })
// 添加页面
pdf.AddPage()
// 添加需要使用到的字体
err := pdf.AddTTFFont("wts11", "../ttf/wts11.ttf")
if err != nil {
log.Print(err.Error())
return
}

// 设定当前书写字体
err = pdf.SetFont("wts11", "", 14)
if err != nil {
log.Print(err.Error())
return
}
// 在指定位置书写
pdf.Cell(nil, "您好")
// 导出PDF
pdf.WritePdf("hello.pdf")
}

文字书写

添加字体集

注:GoPDF虽然支持中文字体输入,但需要导入的TTF字体包支持中文,否则在书写文字时会呈现空白

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
err = pdf.AddTTFFontWithOption(calibri, "/font/calibri-regular.ttf", gopdf.TtfOption{
Style: gopdf.Regular,
})
if err != nil {
return err
}
err = pdf.AddTTFFontWithOption(calibri, "/font/calibri-bold.ttf", gopdf.TtfOption{
Style: gopdf.Bold,
})
if err != nil {
return err
}
err = pdf.AddTTFFontWithOption(calibri, "/font/calibri-italic.ttf", gopdf.TtfOption{
Style: gopdf.Italic,
})
if err != nil {
return err
}

使用字体

1
2
3
4
5
6
7
8
9
10
11
12
// 使用calibri **常规** 字体
if err = pdf.SetFont(calibri, "", 10); err != nil {
return err
}
// 使用calibri **加粗** 字体
if err = pdf.SetFont(calibri, "B", 10); err != nil {
return err
}
// 使用calibri **斜体** 字体
if err = pdf.SetFont(calibri, "I", 10); err != nil {
return err
}

书写内容

常规书写

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
// 设定书写起始位置
pdf.SetXY(x, y)

// 从起始位置书写:行效果
if err = pdf.Cell(nil, "这是行效果"); err != nil {
return err
}

// 从起始位置书写:行效果 + 靠右 + 居中
if err = p.f.CellWithOption(&gopdf.Rect{W: 70, H: lineBoth}, detail.Price, gopdf.CellOption{
Align: gopdf.Right,
Float: gopdf.Center,
}); err != nil {
return err
}

// 从起始位置书写:块效果
if err = pdf.MultiCell(&gopdf.Rect{W: 400, H: 100}, "这是块效果"); err != nil {
return err
}

// 从起始位置书写:块效果 + 靠右 + 居中
if err = pdf.MultiCellWithOption(&gopdf.Rect{W: 400, H: 100}, "这是块效果", gopdf.CellOption{
Align: gopdf.Right,
Float: gopdf.Center,
}); err != nil {
return err
}

流式布局书写

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 设定书写起始位置
pdf.SetXY(x, y)
// IsFitMultiCell 判断给定块大小中书写,是否会超出块限制
// 如果超出返回:ok: false, height: 给定高度, err: nil
// 如果未超出返回: ok: true, height: 实际使用高度, err: nil
ok, height, err := pdf.IsFitMultiCell(&gopdf.Rect{W: 400, H: 50}, detail.Title)
if err != nil {
logger.Errorf("Cell has err: %v", err)
return err
}

// 如果OK则按照实际使用高度进行书写,同时后面的页面效果需要根据此高度进行调整
if ok {
if err = pdf.MultiCell(&gopdf.Rect{W: 400, H: height}, detail.Title); err != nil {
return err
}
}

// 如果非OK,可以通过递归,每次将给定高度增加:行高 + 字体大小 (每次增加一行)
...

添加图片

1
2
3
4
5
6
7
if err = pdf.Image("/pic/logo.png", x, y, &gopdf.Rect{
W: logoWidth, // 图片宽度
H: logoHeight, // 图片高度
}); err != nil {
logger.Errorf("Image has err: %v", err)
return err
}

画直线

直线的类型一共有三种:solid, dashed, dotted

1
2
3
4
5
6
// 设定直线类型
pdf.SetLineType("")
// 设定直线的宽度
pdf.SetLineWidth(2)
// 在指定的起止位置画直线
pdf.Line(startX, startY, endX, endY)

ProtoBuf 填坑日记

package、go_package

package:主要是用于避免命名冲突的,不同的项目(project)需要指定不同的package。

option go_package:

  1. 表明如果要引用这个proto生成的文件的时候import后面的路径
  2. 如果不指定–go_opt(默认值),生成的go文件存放的路径。

需要注意的是package 和 go_package 含义:

  1. package用于防止不同project之间定义了同名message结构的冲突,因为package名的一个作用是用于init方法中的注册
  2. go_package存在时,其最后一个单词是生成的go文件的package名字
  3. go_package不存在时,go文件的package名字就变成了proto中package指定的名字了。

如何使Proto生成的pb.go文件同源文件在相同目录

1
protoc {}.proto --go_out=. --go_opt=paths=source_relative
  • —go_out:表明数据的文件位置
  • —go_opt:表示生成go文件时候的目录选项,如上面写时表示生成的文件与proto在同一目录。

请我喝杯咖啡吧~

支付宝
微信