Skip to content

Commit dae0a8f

Browse files
committed
增加 gin 实践 连载十四
1 parent dc7585b commit dae0a8f

File tree

1 file changed

+368
-0
lines changed

1 file changed

+368
-0
lines changed
Lines changed: 368 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,368 @@
1+
# Golang Gin实践 连载十四 实现导出、导入 Excel
2+
3+
项目地址:https://github.com/EDDYCJY/go-gin-example
4+
5+
如果对你有所帮助,欢迎点个 Star 👍
6+
7+
## 前言
8+
9+
在本节,我们将实现对标签信息的导出、导入功能,这是很标配功能了,希望你掌握基础的使用方式
10+
11+
另外在本文我们使用了 2 个 Excel 的包,excelize 最初的 XML 格式文件的一些结构,是通过 tealeg/xlsx 格式文件结构演化而来的,因此特意在此都展示了,你可以根据自己的场景和喜爱去使用
12+
13+
## 配置
14+
15+
首先要指定导出的 Excel 文件的存储路径,在 app.ini 中增加配置:
16+
17+
```
18+
[app]
19+
...
20+
21+
ExportSavePath = export/
22+
```
23+
24+
修改 setting.go 的 App struct:
25+
26+
``` go
27+
type App struct {
28+
JwtSecret string
29+
PageSize int
30+
PrefixUrl string
31+
32+
RuntimeRootPath string
33+
34+
ImageSavePath string
35+
ImageMaxSize int
36+
ImageAllowExts []string
37+
38+
ExportSavePath string
39+
40+
LogSavePath string
41+
LogSaveName string
42+
LogFileExt string
43+
TimeFormat string
44+
}
45+
```
46+
47+
在这里需增加 ExportSavePath 配置项,另外将先前 ImagePrefixUrl 改为 PrefixUrl 用于支撑两者的 HOST 获取
48+
49+
(注意修改 image.go 的 GetImageFullUrl 方法)
50+
51+
## pkg
52+
53+
新建 pkg/export/excel.go 文件,如下:
54+
55+
```
56+
package export
57+
58+
import "github.com/EDDYCJY/go-gin-example/pkg/setting"
59+
60+
func GetExcelFullUrl(name string) string {
61+
return setting.AppSetting.PrefixUrl + "/" + GetExcelPath() + name
62+
}
63+
64+
func GetExcelPath() string {
65+
return setting.AppSetting.ExportSavePath
66+
}
67+
68+
func GetExcelFullPath() string {
69+
return setting.AppSetting.RuntimeRootPath + GetExcelPath()
70+
}
71+
```
72+
73+
这里编写了一些常用的方法,以后取值方式如果有变动,直接改内部代码即可,对外不可见
74+
75+
## 尝试一下标准库
76+
77+
```
78+
f, err := os.Create(export.GetExcelFullPath() + "test.csv")
79+
if err != nil {
80+
panic(err)
81+
}
82+
defer f.Close()
83+
84+
f.WriteString("\xEF\xBB\xBF")
85+
86+
w := csv.NewWriter(f)
87+
data := [][]string{
88+
{"1", "test1", "test1-1"},
89+
{"2", "test2", "test2-1"},
90+
{"3", "test3", "test3-1"},
91+
}
92+
93+
w.WriteAll(data)
94+
```
95+
96+
在 Go 提供的标准库 encoding/csv 中,天然的支持 csv 文件的读取和处理,在本段代码中,做了如下工作:
97+
98+
1、os.Create:
99+
100+
创建了一个 test.csv 文件
101+
102+
2、f.WriteString("\xEF\xBB\xBF"):
103+
104+
`\xEF\xBB\xBF` 是 UTF-8 BOM 的 16 进制格式,在这里的用处是标识文件的编码格式,通常会出现在文件的开头,因此第一步就要将其写入。如果不标识 UTF-8 的编码格式的话,写入的汉字会显示为乱码
105+
106+
3、csv.NewWriter:
107+
108+
```
109+
func NewWriter(w io.Writer) *Writer {
110+
return &Writer{
111+
Comma: ',',
112+
w: bufio.NewWriter(w),
113+
}
114+
}
115+
```
116+
117+
4、w.WriteAll:
118+
119+
```
120+
func (w *Writer) WriteAll(records [][]string) error {
121+
for _, record := range records {
122+
err := w.Write(record)
123+
if err != nil {
124+
return err
125+
}
126+
}
127+
return w.w.Flush()
128+
}
129+
```
130+
131+
WriteAll 实际是对 Write 的封装,需要注意在最后调用了 `w.w.Flush()`,这充分了说明了 WriteAll 的使用场景,你可以想想作者的设计用意
132+
133+
## 导出
134+
135+
### Service 方法
136+
137+
打开 service/tag.go,增加 Export 方法,如下:
138+
139+
```
140+
func (t *Tag) Export() (string, error) {
141+
tags, err := t.GetAll()
142+
if err != nil {
143+
return "", err
144+
}
145+
146+
file := xlsx.NewFile()
147+
sheet, err := file.AddSheet("标签信息")
148+
if err != nil {
149+
return "", err
150+
}
151+
152+
titles := []string{"ID", "名称", "创建人", "创建时间", "修改人", "修改时间"}
153+
row := sheet.AddRow()
154+
155+
var cell *xlsx.Cell
156+
for _, title := range titles {
157+
cell = row.AddCell()
158+
cell.Value = title
159+
}
160+
161+
for _, v := range tags {
162+
values := []string{
163+
strconv.Itoa(v.ID),
164+
v.Name,
165+
v.CreatedBy,
166+
strconv.Itoa(v.CreatedOn),
167+
v.ModifiedBy,
168+
strconv.Itoa(v.ModifiedOn),
169+
}
170+
171+
row = sheet.AddRow()
172+
for _, value := range values {
173+
cell = row.AddCell()
174+
cell.Value = value
175+
}
176+
}
177+
178+
time := strconv.Itoa(int(time.Now().Unix()))
179+
filename := "tags-" + time + ".xlsx"
180+
181+
fullPath := export.GetExcelFullPath() + filename
182+
err = file.Save(fullPath)
183+
if err != nil {
184+
return "", err
185+
}
186+
187+
return filename, nil
188+
}
189+
```
190+
191+
## routers 入口
192+
193+
打开 routers/api/v1/tag.go,增加如下方法:
194+
195+
```
196+
func ExportTag(c *gin.Context) {
197+
appG := app.Gin{C: c}
198+
name := c.PostForm("name")
199+
state := -1
200+
if arg := c.PostForm("state"); arg != "" {
201+
state = com.StrTo(arg).MustInt()
202+
}
203+
204+
tagService := tag_service.Tag{
205+
Name: name,
206+
State: state,
207+
}
208+
209+
filename, err := tagService.Export()
210+
if err != nil {
211+
appG.Response(http.StatusOK, e.ERROR_EXPORT_TAG_FAIL, nil)
212+
return
213+
}
214+
215+
appG.Response(http.StatusOK, e.SUCCESS, map[string]string{
216+
"export_url": export.GetExcelFullUrl(filename),
217+
"export_save_url": export.GetExcelPath() + filename,
218+
})
219+
}
220+
```
221+
222+
### 路由
223+
224+
在 routers/router.go 文件中增加路由方法,如下
225+
226+
```
227+
apiv1 := r.Group("/api/v1")
228+
apiv1.Use(jwt.JWT())
229+
{
230+
...
231+
//导出标签
232+
r.POST("/tags/export", v1.ExportTag)
233+
}
234+
```
235+
236+
### 验证接口
237+
238+
访问 `http://127.0.0.1:8000/tags/export`,结果如下:
239+
240+
```
241+
{
242+
"code": 200,
243+
"data": {
244+
"export_save_url": "export/tags-1528903393.xlsx",
245+
"export_url": "http://127.0.0.1:8000/export/tags-1528903393.xlsx"
246+
},
247+
"msg": "ok"
248+
}
249+
```
250+
251+
最终通过接口返回了导出文件的地址和保存地址
252+
253+
### StaticFS
254+
255+
那你想想,现在直接访问地址肯定是无法下载文件的,那么该如何做呢?
256+
257+
打开 router.go 文件,增加代码如下:
258+
259+
```
260+
r.StaticFS("/export", http.Dir(export.GetExcelFullPath()))
261+
```
262+
263+
若你不理解,强烈建议温习下前面的章节,举一反三
264+
265+
## 验证下载
266+
267+
再次访问上面的 export_url ,如:`http://127.0.0.1:8000/export/tags-1528903393.xlsx`,是不是成功了呢?
268+
269+
## 导入
270+
271+
### Service 方法
272+
273+
打开 service/tag.go,增加 Import 方法,如下:
274+
275+
```
276+
func (t *Tag) Import(r io.Reader) error {
277+
xlsx, err := excelize.OpenReader(r)
278+
if err != nil {
279+
return err
280+
}
281+
282+
rows := xlsx.GetRows("标签信息")
283+
for irow, row := range rows {
284+
if irow > 0 {
285+
var data []string
286+
for _, cell := range row {
287+
data = append(data, cell)
288+
}
289+
290+
models.AddTag(data[1], 1, data[2])
291+
}
292+
}
293+
294+
return nil
295+
}
296+
```
297+
298+
## routers 入口
299+
300+
打开 routers/api/v1/tag.go,增加如下方法:
301+
302+
```
303+
func ImportTag(c *gin.Context) {
304+
appG := app.Gin{C: c}
305+
306+
file, _, err := c.Request.FormFile("file")
307+
if err != nil {
308+
logging.Warn(err)
309+
appG.Response(http.StatusOK, e.ERROR, nil)
310+
return
311+
}
312+
313+
tagService := tag_service.Tag{}
314+
err = tagService.Import(file)
315+
if err != nil {
316+
logging.Warn(err)
317+
appG.Response(http.StatusOK, e.ERROR_IMPORT_TAG_FAIL, nil)
318+
return
319+
}
320+
321+
appG.Response(http.StatusOK, e.SUCCESS, nil)
322+
}
323+
```
324+
325+
### 路由
326+
327+
在 routers/router.go 文件中增加路由方法,如下
328+
329+
```
330+
apiv1 := r.Group("/api/v1")
331+
apiv1.Use(jwt.JWT())
332+
{
333+
...
334+
//导入标签
335+
r.POST("/tags/import", v1.ImportTag)
336+
}
337+
```
338+
339+
### 验证
340+
341+
![image](https://i.imgur.com/awRs9HA.jpg)
342+
343+
在这里我们将先前导出的 Excel 文件作为入参,访问 `http://127.0.0.01:8000/tags/import`,检查返回和数据是否正确入库
344+
345+
346+
## 总结
347+
348+
在本文中,简单介绍了 Excel 的导入、导出的使用方式,使用了以下 2 个包:
349+
350+
- [tealeg/xlsx](https://github.com/tealeg/xlsx)
351+
- [360EntSecGroup-Skylar/excelize](https://github.com/360EntSecGroup-Skylar/excelize)
352+
353+
你可以细细阅读一下它的实现和使用方式,对你的把控更有帮助 🤔
354+
355+
356+
## 课外
357+
358+
- tag:导出使用 excelize 的方式去实现(可能你会发现更简单哦)
359+
- tag:导入去重功能实现
360+
- artice :导入、导出功能实现
361+
362+
363+
也不失为你很好的练手机会,如果有兴趣,可以试试
364+
365+
## 参考
366+
### 本系列示例代码
367+
- [go-gin-example](https://github.com/EDDYCJY/go-gin-example)
368+

0 commit comments

Comments
 (0)