go-zero整合Excelize并实现Excel导入导出

作者 : admin 本文共11022个字,预计阅读时间需要28分钟 发布时间: 2024-06-16 共1人阅读

go-zero整合Excelize并实现Excel导入导出

本教程基于go-zero微服务入门教程,项目工程结构同上一个教程。

本教程主要实现go-zero框架整合Excelize,并暴露接口实现Excel模板下载、Excel导入、Excel导出。

go-zero微服务入门教程:https://blog.csdn.net/u011019141/article/details/136233473

本文源码:https://gitee.com/songfayuan/go-zero-demo (教程源码分支:6.zero整合Excelize操作Excel)

准备工作

  • 如不熟悉go-zero项目的,请先查看上一篇go-zero微服务入门教程

安装依赖

Excelize官方文档

项目工程父级目录下执行如下指令安装依赖:

# 下载安装Excelize
go get github.com/xuri/excelize/v2

编写API Gateway代码

编写api文件

excel.api

在api目录下创建新目录doc/excel,在excel目录下创建excel.api文件。

syntax = "v1"

info(
    title: "excel操作相关"
    desc: "excel操作相关"
    author: "宋发元"
)

type (
    ExcelImportReq {
        DeptId string `json:"deptId"`            // 部门id(Content-Type: form-data)
        File interface{} `json:"file,optional"`  // excel文件(Content-Type: form-data)
    }

    ExcelImportData {
        Total int64 `json:"total"`      // 导入总数
        Success int64 `json:"success"`  // 导入成功数
        Msg string `json:"msg"`         // 提示信息
    }

    ExcelImportResp {
        Code int64 `json:"code"`
        Message string `json:"message"`
        Data ExcelImportData `json:"data"`
    }

    ExcelExportlReq{
        TimeStart string `form:"timeStart,optional"`                      // 时间(开始) yyyy-mm-dd
        TimeEnd   string `form:"timeEnd,optional"`                        // 时间(结束) yyyy-mm-dd
    }

    DefaultResponse {
        Code    int64  `json:"code,default=200"`
        Message string `json:"message,default=操作成功"`
    }
)


@server(
    group : excel/test
    prefix : /excel/test
)

service admin-api {
    @doc (
        summary :"excel模板下载"
    )
    @handler ExcelTemplateDownload
    get /excel/templateDownload

    @doc(
        summary :"excel导入"
    )
    @handler ExcelImport
    post /excel/excelImport (ExcelImportReq) returns (ExcelImportResp)

    @doc(
        summary :"excel导出"
    )
    @handler ExcelExport
    get /excel/excelExport (ExcelExportlReq)returns (DefaultResponse)
}
admin.api

在api/doc/admin.api文件添加配置信息。

import "excel/excel.api"

用goctl生成API Gateway代码

生成方法同上篇文章,自行查看。但是此处要基于admin.api文件去生成代码,如果基于excel.api生成,则生成的代码只有excel.api定义的接口代码,其他api文件定义的接口代码不被生成。

api新增文件操作配置

以下操作在api模块执行。

admin-api.yaml

admin-api.yaml配置文件新增文件操作配置信息,如下:

#文件
UploadFile:
  MaxFileNum: 100
  MaxFileSize: 104857600  # 100MB
  SavePath: template/uploads/
  TemplatePath: template/excel/

config.go

config.go文件中新增UploadFile配置信息,如下:

type Config struct {
	rest.RestConf

	SysRpc zrpc.RpcClientConf

  //这里新增
	UploadFile UploadFile
}

type UploadFile struct {
	MaxFileNum   int64
	MaxFileSize  int64
	SavePath     string
	TemplatePath string
}

修改API Gateway代码

exceltemplatedownloadlogic.go

修改api/internal/logic/excel/test/exceltemplatedownloadlogic.go里的ExcelTemplateDownload方法,完整代码如下:

package test

import (
	"context"
	"go-zero-demo/common/errors/errorx"
	"net/http"
	"os"

	"github.com/zeromicro/go-zero/core/logx"
	"go-zero-demo/api/internal/svc"
)

type ExcelTemplateDownloadLogic struct {
	logx.Logger
	ctx    context.Context
	svcCtx *svc.ServiceContext
	writer http.ResponseWriter
}

func NewExcelTemplateDownloadLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *ExcelTemplateDownloadLogic {
	return &ExcelTemplateDownloadLogic{
		Logger: logx.WithContext(ctx),
		ctx:    ctx,
		svcCtx: svcCtx,
		writer: writer,
	}
}

func (l *ExcelTemplateDownloadLogic) ExcelTemplateDownload() (err error) {
	SavePath := l.svcCtx.Config.UploadFile.TemplatePath
	filePath := "demo_excel_template.xlsx"

	fullPath := SavePath + filePath
	fileName := "Excel导入模板.xlsx"

	//fullPath = "/Users/songfayuan/GolandProjects/go-zero-demo/template/excel/demo_excel_template.xlsx"  //测试地址,绝对路径
	_, err = os.Stat(fullPath)
	if err != nil || os.IsNotExist(err) {
		return errorx.New("文件不存在")
	}
	bytes, err := os.ReadFile(fullPath)
	if err != nil {
		return errorx.New("读取文件失败")
	}

	l.writer.Header().Add("Content-Type", "application/octet-stream")
	l.writer.Header().Add("Content-Disposition", "attachment; filename= "+fileName)
	l.writer.Write(bytes)

	return
}

excelimportlogic.go

修改api/internal/logic/excel/test/excelimportlogic.go里的ExcelImport方法,完整代码如下:

package test
import (
"context"
"fmt"
"github.com/xuri/excelize/v2"
"github.com/zeromicro/go-zero/core/mapping"
"go-zero-demo/common/errors/errorx"
"go-zero-demo/common/utils"
"path/filepath"
"strings"
"go-zero-demo/api/internal/svc"
"go-zero-demo/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ExcelImportLogic struct {
logx.Logger
ctx    context.Context
svcCtx *svc.ServiceContext
}
type excelDataForDept struct {
DeptId       string `json:"DeptId,optional" excel:"col=1"`       // 第1列:部门id
ParentDeptId string `json:"ParentDeptId,optional" excel:"col=2"` // 第2列:上级部门id
DeptName     string `json:"DeptName,optional" excel:"col=3"`     // 第3列:部门名称
Level        string `json:"Level,optional" excel:"col=4"`        // 第4列:部门等级(分级名称)
}
type excelDataForMember struct {
DeptId  string `json:"DeptId,optional" excel:"col=1"`  // 第1列:部门
Name    string `json:"Name,optional" excel:"col=2"`    // 第2列:姓名
Account string `json:"Account,optional" excel:"col=3"` // 第3列:帐号
Level   string `json:"Level,optional" excel:"col=4"`   // 第4列:等级(分级名称)
IpAddr  string `json:"IpAddr,optional" excel:"col=5"`  // 第5列:IP
MacAddr string `json:"MacAddr,optional" excel:"col=6"` // 第6列:MAC
}
var (
validUploadFileExt = map[string]any{
".xlsx": nil,
".xls":  nil,
}
)
func NewExcelImportLogic(ctx context.Context, svcCtx *svc.ServiceContext) *ExcelImportLogic {
return &ExcelImportLogic{
Logger: logx.WithContext(ctx),
ctx:    ctx,
svcCtx: svcCtx,
}
}
func (l *ExcelImportLogic) ExcelImport(req *types.ExcelUploadReq) (resp *types.ExcelImportResp, err error) {
if _, ok := validUploadFileExt[strings.ToLower(filepath.Ext(req.File.FileHeader.Filename))]; !ok {
return nil, errorx.New("无效的文件格式")
}
// 打开文件
f, err := excelize.OpenReader(req.File.File)
if err != nil {
return nil, errorx.New("无效的文件")
}
/* 解析部门Sheet数据 start */
// 解析文件参数
var excelDept []excelDataForDept
if excelDept, err = parseFileDept(f); err != nil {
return
}
// format
for _, i := range excelDept {
fmt.Printf("Excel数据:%v/%v/%v/%v", i.DeptId, i.ParentDeptId, i.DeptName, i.Level)
}
/* 解析部门Sheet数据 end */
/* 解析用户Sheet数据 start */
// 解析文件参数
var excelMember []excelDataForMember
if excelMember, err = parseFileUser(f); err != nil {
return
}
// format
for _, i := range excelMember {
fmt.Printf("Excel数据:%v/%v/%v/%v/%v/%v", i.DeptId, i.Name, i.Account, i.Level, i.IpAddr, i.MacAddr)
}
/* 解析用户Sheet数据 end */
return &types.ExcelImportResp{
Code:    200,
Message: "导入成功",
Data: types.ExcelImportData{
Total:   10,
Success: 10,
Msg:     "成功",
},
}, nil
}
// 解析部门Sheet数据
func parseFileDept(f *excelize.File) ([]excelDataForDept, error) {
// 解析参数(可选)
excelOption := utils.ExcelOption{Sheet: "部门", StartRow: 2}
// 映射回调
all := make([]excelDataForDept, 0)
cbHandler := func(data map[string]interface{}) error {
temp := excelDataForDept{}
err := mapping.UnmarshalJsonMap(data, &temp)
if err != nil {
return err
}
all = append(all, temp)
return nil
}
// 映射
if err := utils.ParseExcel(f, excelDataForDept{}, cbHandler, excelOption); err != nil {
return nil, errorx.New("解析文件时出错:" + err.Error())
}
if len(all) == 0 {
return nil, errorx.New("文件中无有效数据")
}
return all, nil
}
// 解析用户Sheet数据
func parseFileUser(f *excelize.File) ([]excelDataForMember, error) {
// 解析参数(可选)
excelOption := utils.ExcelOption{Sheet: "用户", StartRow: 2}
// 映射回调
all := make([]excelDataForMember, 0)
cbHandler := func(data map[string]interface{}) error {
temp := excelDataForMember{}
err := mapping.UnmarshalJsonMap(data, &temp)
if err != nil {
return err
}
all = append(all, temp)
return nil
}
// 映射
if err := utils.ParseExcel(f, excelDataForMember{}, cbHandler, excelOption); err != nil {
return nil, errorx.New("解析文件时出错:" + err.Error())
}
if len(all) == 0 {
return nil, errorx.New("文件中无有效数据")
}
return all, nil
}

excelexportlogic.go

修改api/internal/logic/excel/test/excelexportlogic.go里的ExcelExport方法,完整代码如下:

package test
import (
"context"
"fmt"
"github.com/xuri/excelize/v2"
"net/http"
"go-zero-demo/api/internal/svc"
"go-zero-demo/api/internal/types"
"github.com/zeromicro/go-zero/core/logx"
)
type ExcelExportLogic struct {
logx.Logger
ctx    context.Context
svcCtx *svc.ServiceContext
writer http.ResponseWriter
}
type cellValue struct {
sheet string
cell  string
value string
}
func NewExcelExportLogic(ctx context.Context, svcCtx *svc.ServiceContext, writer http.ResponseWriter) *ExcelExportLogic {
return &ExcelExportLogic{
Logger: logx.WithContext(ctx),
ctx:    ctx,
svcCtx: svcCtx,
writer: writer,
}
}
func (l *ExcelExportLogic) ExcelExport(req *types.ExcelExportlReq) (resp *types.DefaultResponse, err error) {
//这里仅演示Excel导出逻辑,真实数据自己增加对应的查询逻辑。
excelFile := excelize.NewFile()
//insert title
cellValues := make([]*cellValue, 0)
cellValues = append(cellValues, &cellValue{
sheet: "sheet1",
cell:  "A1",
value: "序号",
}, &cellValue{
sheet: "sheet1",
cell:  "B1",
value: "IP地址",
}, &cellValue{
sheet: "sheet1",
cell:  "C1",
value: "账号",
}, &cellValue{
sheet: "sheet1",
cell:  "D1",
value: "姓名",
}, &cellValue{
sheet: "sheet1",
cell:  "E1",
value: "最近访问时间",
}, &cellValue{
sheet: "sheet1",
cell:  "F1",
value: "设备状态",
}, &cellValue{
sheet: "sheet1",
cell:  "G1",
value: "访问分级",
})
// 创建一个工作表
index, _ := excelFile.NewSheet("Sheet1")
// 设置工作簿的默认工作表
excelFile.SetActiveSheet(index)
//插入表格头
for _, cellValue := range cellValues {
excelFile.SetCellValue(cellValue.sheet, cellValue.cell, cellValue.value)
}
//设置表格头字体样式
styleId, err := excelFile.NewStyle(&excelize.Style{
Font: &excelize.Font{
Bold:   true,  //黑体
Italic: false, //倾斜
Family: "宋体",
Size:   14,
//Color:  "微软雅黑",
},
})
if err != nil {
fmt.Println(err)
}
for _, data := range cellValues {
excelFile.SetCellStyle(data.sheet, data.cell, data.cell, styleId)
}
excelFile.SetColWidth("sheet1", "B", "G", 20)
cnt := 1
for i := 0; i <= 6; i++ {
cnt = cnt + 1
for k1, v1 := range cellValues {
switch k1 {
case 0:
v1.cell = fmt.Sprintf("A%d", cnt)
v1.value = fmt.Sprintf("%d", i+1)
case 1:
v1.cell = fmt.Sprintf("B%d", cnt)
v1.value = "1"
case 2:
v1.cell = fmt.Sprintf("C%d", cnt)
v1.value = "2"
case 3:
v1.cell = fmt.Sprintf("D%d", cnt)
v1.value = "3"
case 4:
v1.cell = fmt.Sprintf("E%d", cnt)
v1.value = "4"
case 5:
v1.cell = fmt.Sprintf("F%d", cnt)
v1.value = "5"
case 6:
v1.cell = fmt.Sprintf("G%d", cnt)
v1.value = "6"
}
}
for _, vc := range cellValues {
excelFile.SetCellValue(vc.sheet, vc.cell, vc.value)
}
}
fileName := "ABCD.xlsx"
//如果是下载,则需要在Header中设置这两个参数
l.writer.Header().Add("Content-Type", "application/octet-stream")
l.writer.Header().Add("Content-Disposition", "attachment; filename= "+fileName)
//l.writer.Header().Add("Content-Transfer-Encoding", "binary")
excelFile.Write(l.writer)
return
}

exceltemplatedownloadhandler.go

exceltemplatedownloadhandler.go代码微调整。

func ExcelTemplateDownloadHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
l := test.NewExcelTemplateDownloadLogic(r.Context(), svcCtx, w)
err := l.ExcelTemplateDownload()
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.Ok(w)
}
}
}

excelimporthandler.go

excelimporthandler.go代码微调整。

func ExcelImportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ExcelUploadReq
req.DeptId = r.FormValue("deptId")
f, fh, e := utils.ParseFile(r, "file")
if e != nil {
httpx.Error(w, e)
return
}
req.File = &types.File{File: f, FileHeader: fh}
l := test.NewExcelImportLogic(r.Context(), svcCtx)
resp, err := l.ExcelImport(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJson(w, resp)
}
}
}

excelexporthandler.go

excelexporthandler.go代码微调整。

func ExcelExportHandler(svcCtx *svc.ServiceContext) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
var req types.ExcelExportlReq
if err := httpx.Parse(r, &req); err != nil {
httpx.ErrorCtx(r.Context(), w, err)
return
}
l := test.NewExcelExportLogic(r.Context(), svcCtx, w)
resp, err := l.ExcelExport(&req)
if err != nil {
httpx.ErrorCtx(r.Context(), w, err)
} else {
httpx.OkJsonCtx(r.Context(), w, resp)
}
}
}

base.go

在路径api/internal/types下创建base.go,内容如下:

package types
import "mime/multipart"
type File struct {
File       multipart.File
FileHeader *multipart.FileHeader
}
type ExcelUploadReq struct {
DeptId string `json:"deptId"` // 部门id
File   *File  `json:"file"`   // excel文件
}

Excel导入模板

在项目根目录下创建template/excel目录,里面存放Excel导入模板demo_excel_template.xlsx。

模板内容见源码!!!
go-zero整合Excelize并实现Excel导入导出插图
go-zero整合Excelize并实现Excel导入导出插图(1)

完整调用演示

最后,在根目录go-zero-demo执行下命令。

go mod tidy

运行rpc服务

运行方法同上篇文章,具体查看教程go-zero微服务入门教程完整调用演示部分。

运行api

运行方法同上篇文章,具体查看教程go-zero微服务入门教程完整调用演示部分。

api调用

以下调用采用postman调用。

Excel模板下载
localhost:8888/excel/test/excel/templateDownload
Excel导入
localhost:8888/excel/test/excel/excelImport
Excel导出
localhost:8888/excel/test/excel/excelExport
本站无任何商业行为
个人在线分享 » go-zero整合Excelize并实现Excel导入导出
E-->