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-ddTimeEnd string `form:"timeEnd,optional"` // 时间(结束) yyyy-mm-dd}DefaultResponse {Code int64 `json:"code,default=200"`Message string `json:"message,default=操作成功"`}
)@server(group : excel/testprefix : /excel/test
)service admin-api {@doc (summary :"excel模板下载")@handler ExcelTemplateDownloadget /excel/templateDownload@doc(summary :"excel导入")@handler ExcelImportpost /excel/excelImport (ExcelImportReq) returns (ExcelImportResp)@doc(summary :"excel导出")@handler ExcelExportget /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: 100MaxFileSize: 104857600 # 100MBSavePath: template/uploads/TemplatePath: template/excel/
config.go
config.go文件中新增UploadFile配置信息,如下:
type Config struct {rest.RestConfSysRpc zrpc.RpcClientConf//这里新增UploadFile UploadFile
}type UploadFile struct {MaxFileNum int64MaxFileSize int64SavePath stringTemplatePath string
}
修改API Gateway代码
exceltemplatedownloadlogic.go
修改api/internal/logic/excel/test/exceltemplatedownloadlogic.go
里的ExcelTemplateDownload
方法,完整代码如下:
package testimport ("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.Loggerctx context.ContextsvcCtx *svc.ServiceContextwriter 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.TemplatePathfilePath := "demo_excel_template.xlsx"fullPath := SavePath + filePathfileName := "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 testimport ("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.Loggerctx context.ContextsvcCtx *svc.ServiceContext
}type excelDataForDept struct {DeptId string `json:"DeptId,optional" excel:"col=1"` // 第1列:部门idParentDeptId string `json:"ParentDeptId,optional" excel:"col=2"` // 第2列:上级部门idDeptName 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列:IPMacAddr 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 []excelDataForDeptif excelDept, err = parseFileDept(f); err != nil {return}// formatfor _, i := range excelDept {fmt.Printf("Excel数据:%v/%v/%v/%v", i.DeptId, i.ParentDeptId, i.DeptName, i.Level)}/* 解析部门Sheet数据 end *//* 解析用户Sheet数据 start */// 解析文件参数var excelMember []excelDataForMemberif excelMember, err = parseFileUser(f); err != nil {return}// formatfor _, 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 testimport ("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.Loggerctx context.ContextsvcCtx *svc.ServiceContextwriter http.ResponseWriter
}type cellValue struct {sheet stringcell stringvalue 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 titlecellValues := 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 := 1for i := 0; i <= 6; i++ {cnt = cnt + 1for 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.ExcelUploadReqreq.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.ExcelExportlReqif 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 typesimport "mime/multipart"type File struct {File multipart.FileFileHeader *multipart.FileHeader
}type ExcelUploadReq struct {DeptId string `json:"deptId"` // 部门idFile *File `json:"file"` // excel文件
}
Excel导入模板
在项目根目录下创建template/excel目录,里面存放Excel导入模板demo_excel_template.xlsx。
模板内容见源码!!!
完整调用演示
最后,在根目录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