当前位置: 首页 > news >正文

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

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

相关文章:

  • pytest+requests+allure自动化测试接入Jenkins学习
  • uniapp地图选择位置
  • docker部署redis实践
  • `kubectl get pod -oyaml` 和 `kubectl describe pod`
  • C#面:什么是 Windows 服务,它的生命周期与标准的 EXE 程序有什么不同
  • 【2024算力大会分会 | SPIE独立出版 | 往届均已完成EI检索】2024云计算、性能计算与深度学习国际学术会议(CCPCDL 2024)
  • Pydantic的BaseConfig
  • 一款优秀的下载和共享工具
  • 数值计算精度问题(浮点型和双整型累加精度测试)
  • 机器学习专题记录
  • 机器学习作业6——svm支持向量机
  • 探索AOSP中的RRO:运行时资源覆盖的奥秘
  • tmux 移植到ARM板端运行环境搭建
  • Leetcode3174. 清除数字
  • 107.网络游戏逆向分析与漏洞攻防-装备系统数据分析-装备信息更新的处理
  • JavaScript-如何实现克隆(clone)函数
  • [iOS]Core Data浅析一 -- 启用Core Data
  • [rust! #004] [译] Rust 的内置 Traits, 使用场景, 方式, 和原因
  • 【391天】每日项目总结系列128(2018.03.03)
  • 【EOS】Cleos基础
  • 10个确保微服务与容器安全的最佳实践
  • angular2 简述
  • C# 免费离线人脸识别 2.0 Demo
  • CentOS7 安装JDK
  • JAVA_NIO系列——Channel和Buffer详解
  • leetcode98. Validate Binary Search Tree
  • Mysql5.6主从复制
  • php面试题 汇集2
  • Python连接Oracle
  • React16时代,该用什么姿势写 React ?
  • SegmentFault 社区上线小程序开发频道,助力小程序开发者生态
  • spring security oauth2 password授权模式
  • 纯 javascript 半自动式下滑一定高度,导航栏固定
  • 订阅Forge Viewer所有的事件
  • 如何胜任知名企业的商业数据分析师?
  • 事件委托的小应用
  • 问:在指定的JSON数据中(最外层是数组)根据指定条件拿到匹配到的结果
  • 我是如何设计 Upload 上传组件的
  • 应用生命周期终极 DevOps 工具包
  • - 转 Ext2.0 form使用实例
  • 【干货分享】dos命令大全
  • HanLP分词命名实体提取详解
  • Linux权限管理(week1_day5)--技术流ken
  • MyCAT水平分库
  • 阿里云服务器如何修改远程端口?
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 正则表达式-基础知识Review
  • ​Redis 实现计数器和限速器的
  • # 达梦数据库知识点
  • # 飞书APP集成平台-数字化落地
  • #70结构体案例1(导师,学生,成绩)
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • #VERDI# 关于如何查看FSM状态机的方法
  • #每日一题合集#牛客JZ23-JZ33
  • #使用清华镜像源 安装/更新 指定版本tensorflow