Colbrze's Blog

兴趣使然的小站

不入流的软件工程师
现在的我是一名运维开发工程师,正在学习 DevOps 相关的知识。


利用Github搭建图床

使用 Golang 并利用 Github 搭建简单的图床功能

题图

背景

由于使用的腾讯云服务器配置只有1核1G1Mbps按流量计费,为了提升访问速度和减少流量,避免从服务器获取图片,就弄了这个简单的图床功能。

功能

具体的功能就是将图片拖拽到文件上传的框内后,服务器会保存图片,并利用 git 将图片推送到 github 服务器上, 再通过 github访问原始文件的功能,用文件在github上的链接生成短链接并返回。

使用的时候只要用过短链接便能展示图片了。

编码过程

这次顺便练手了一下 golang 写 web 应用。使用了 IRIS 库。

先说上传图片的功能

前端代码是从网上抄的一份。

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
<!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>
<link rel="stylesheet" href="//cdn.bootcss.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="//cdn.bootcss.com/jquery/2.1.4/jquery.min.js"></script>
<style>

body, html {
margin: 0 auto;
}

.up-header {
width: 600px;
text-align: center;
margin-bottom: 10px;
}

.up-content {
min-height: 200px;
border: 1px solid #CCCCCC;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background-color: #FAFAFA;
color: #999;
font-size: 20px;
text-align: center;
}

.up-area {
border: 2px dashed #ccc;
margin: 10px 20px 20px 20px;
width: 400px;
min-height: 200px;
line-height: 100px;
background-color: #fff;
}

.list-group {
margin: 0px auto;;
width: 350px;
min-height: 100px;
padding: 10px;
}

img {
max-width: 100%;
}

.btn {

}

.close {
margin-left: 550px;
margin-top: -20px;
}


</style>
</head>
<body>


<header id="header" class="page-header">
<!-- 头部显示 -->
<div class="container upload ">
<div class="up-header center-block">
<h2>文件上传</h2>
<div class="input-group" style="width:600px; display:flex;">
<input type="text" class="form-control" placeholder="在此处粘贴图片网址">
<button type="button" class="btn btn-primary">上传图片</button>
</div>
</div>
<div class="row">
<!-- 拖拽图片到这来 -->
<div class="col-md-5 col-md-offset-1 up-content dragFile">
<p style="margin-top:10px;">拖拽图片到这里哟</p>
<div class="up-area">
<input type="file" style="display:none;" id="fileDrag" name="fileDragselect" multiple>
<div class="row">
<ul class="list-group clearfix list-drag">
</ul>
</div>
</div>
<div class="btn">
<button type="button" class="btn btn-success" id="upload"> 上传</button>
</div>
</div>
<!-- 点击按钮上传文件 -->
<div class="col-md-5 up-content btnFile">
<form id="change_pass_form" class="form-horizontal" action="/upload"
method="post" enctype="multipart/form-data">
<div class="btn">
<button type="button" class="btn btn-success" id="btn"> 选择本地文件</button>
<input type="file" style="display:none;" id="fileInput" name="img" multiple>
</div>
<div class="up-area">
<div class="row">
<ul class="list-group clearfix list-btn">
</ul>
</div>
</div>
<div class="btn">
<input type="submit" class="btn btn-success" value="上传">
</div>
</form>
</div>
</div>


</div>
</header>

<script type="text/javascript">
//点击本地上传文件
$('#btn').click(() => {
$('#fileInput').click();
})
$('#fileInput').change((event) => {
var files = event.target.files;
for (file of files){
appendFile(file, '.list-btn');
}

})

//拖拽上传文件 在页面进行预览 上传form用到ajax
const dragbox = document.querySelector('.dragFile');
dragbox.addEventListener('dragover', function (e) {
e.preventDefault(); // 必须阻止默认事件
}, false);
dragbox.addEventListener('drop', function (e) {
e.preventDefault(); // 阻止默认事件
var files = e.dataTransfer.files; //获取文件
console.log(files);

// code
for(file of files){
var pic = appendFile(file, '.list-drag');
console.log(pic);
console.log(file);
var img = window.URL.createObjectURL(file);
var filename = file.name; //图片名称
var filesize = Math.floor((file.size) / 1024);
if (filesize > 500) {
alert("图片 "+filename+" 大小为"+filesize+"K.");
}
var str = "![](" + img + ")<p>图片名称:" + filename + "</p><p>大小:" + filesize + "KB</p>";
console.log(str);
// upload code
upload(file, pic)
}
}, false);
function upload(file, pic){
var formData = new FormData(); //创建一个forData
formData.append('img', file); //把file添加进去 name命名为img
$.ajax({
url: "/upload",
data: formData,
type: "POST",
cache: false,
contentType: false,
processData: false,
success: function(data) {
console.log(data.path);
pic.after('<p>' + data.path + '</p>');
},
error: function() {
//失败
console.log('失败')
}
})
}
function appendFile(file, listName) {
let url = window.URL.createObjectURL(file);
let liStr = `
<li class="list-group-item">
<div>
<img src="${url}" alt="文件" />
</div>
</li>
`;
$(listName).append(liStr);
return $(listName+" li:last-child")
}


</script>

</body>
</html>

简单的修改了里面的内容,比如<form>表单中上传图片的路径、上传成功后展示返回的短链接等。

通过监听drop事件来完成图片拖拽上传的功能。

这里需要注意的就是要屏蔽浏览器对拖拽的文件的行为的响应。像Chrome就会直接打开本地文件。

后端

1
2
3
4
5
6
7
8
9
10
11
12
func ImageUpload(ctx iris.Context) {
// 获取文件
file, info, err := ctx.FormFile("img")
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
return
}

defer file.Close()
...
}

IRIS 使用 ctx.FormFile(filename)获取表单上传的文件。

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
import (
"crypto/md5"
...
)
func ImageUpload(ctx iris.Context) {
...
md5 := md5.New()
_, err = io.Copy(md5, file)
if err != nil {
panic(err)
}
MD5Str := hex.EncodeToString(md5.Sum(nil))

// 在 uploads 文件夹中保存文件
fname := MD5Str + "." + strings.Split(info.Filename, ".")[1]
origin := config.Conf.Get("image.urlpath").(string) + "/" + fname

if tools.FileExists("./uploads/" + fname) {
if short, ok := database.DB.FindShortLink(origin); ok {
ctx.JSON(map[string]string{"path": config.Conf.Get("host.url").(string) + "/s/" + short})
return
}
}

...
}

获取文件MD5值,使用MD5值作为文件名,防止重复长传图片。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import (
"crypto/md5"
"encoding/hex"
...
)
func ImageUpload(ctx iris.Context) {
...
out, err := os.OpenFile("./uploads/"+fname, os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
ctx.StatusCode(iris.StatusInternalServerError)
ctx.HTML("Error while uploading: <b>" + err.Error() + "</b>")
return
}
defer out.Close()

_, _ = file.Seek(0, 0)
_, err = io.Copy(out, file)
if err != nil {
panic(err)
}
...
}

保存图片。

因为之前获取md5时读取过文件,这里需要使用file.Seek(0, 0) 将指针重新定位到文件开头。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import (
"os/exec"
)
func ImageUpload(ctx iris.Context) {
...
// 上传图片到github
cmd := exec.Command("bash", "-c", `cd ./uploads && git add . && git commit -m "uploads" && git push`)
stdout, err := os.OpenFile("git.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
if err != nil {
log.Fatalln(err)
}
defer stdout.Close()
cmd.Stdout = stdout
// 执行命令
if err := cmd.Start(); err != nil {
log.Println(err)
}
//go cmd.Run()

ctx.JSON(map[string]string{"path": GetShortUrl(origin)})
}

使用 git 推送到 github 仓库,之后返回短链接。

然后是短链接

因为只是个人使用,就没有考虑并发一类的问题(其实是不懂),只是简单的通过当前的unix时间,通过62进制转换成了不包含特殊符号的6位短码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func GetNewShort() string {
return TransNumToString(time.Now().Unix())
}

func TransNumToString(num int64) string {
var base int64
base = 62
baseHex := "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
base = int64(len(baseHex))
outputList := list.New()
for num/base != 0 {
outputList.PushFront(num % base)
num = num / base
}
outputList.PushFront(num % base)
str := ""
for iter := outputList.Front(); iter != nil; iter = iter.Next() {
str = str + string(baseHex[int(iter.Value.(int64))])
}
return str
}

然后通过 SQLite 将短码和外链储存后,拼接上自己的域名,完成了一个短链接的生成。

当然,只是生成还不行,我们需要响应短链接的访问。

通过返回302,重定向到原始链接。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* @short {get} /s/{short_link} 通过短链接访问
*/
func CallShortUrl(ctx iris.Context) {
short_link := ctx.Params().GetDefault("short_link", "default")
fmt.Println(short_link)
url, ok := database.DB.FindOriginLink(short_link.(string))
if ok {
ctx.Redirect(url.Origin, 302)
} else {
fmt.Println("not found")
ctx.NotFound()
}
}
最近的文章

初步体验 LDAP

先搞懂怎么用,再搞懂怎么装,最后才搞懂怎么配置。 想当初17年的时候我就被LDAP搞疯过一次,没想到这次又以失败告终了。虽然已经大致弄明白了怎么使用,但是还是感觉白白折腾了。 摘一段在这次找资料时看到的吐槽: 如果你搜索OpenLDAP的安装指南,很不幸地告诉你,网上不管中文的英文的,90 …

DevOps 继续阅读
更早的文章

有趣的代码注释

图片转ASCII码) 1234567891011121314151617181920/*** * .::::. * .::::::::. * ::::::::::: FUCK YOU * …

code 继续阅读