gin返回前端excel文件流
==背景==
以前导出文件的时候,都是先把文件扔到某一个临时路径,然后返回给前端这个临时路径,前端收到这个路径之后在下载文件
这次想试试数据不落地,直接返回给前端文件流,这样可以减少复杂性
==代码样例==
【封装应答】
// Write 封装返回文件流 func Write(ctx *gin.Context, fileName string, file *excelize.File) { ctx.Writer.Header().Add("Content-Type", "application/octet-stream") ctx.Writer.Header().Add("Content-disposition", "attachment;filename="+fileName) ctx.Writer.Header().Add("Content-Transfer-Encoding", "binary") _ = file.Write(ctx.Writer) }
【Controller处理】
func (pc *PointInfoController) ExportPoint(ctx *gin.Context) { var param = new(request.GetPointList) err := ctx.ShouldBindJSON(param) if err != nil { common.Fail(ctx, err.Error()) return } service := services.PointService{} list, err := service.GetPointInfoList(param) if err != nil { common.Fail(ctx, err.Error()) return } var dataSlice = make([]map[string]interface{}, 0) for _, info := range list { data := utils.StructToMap(info) dataSlice = append(dataSlice, data) } t := reflect.TypeOf(models.PointInfo{}) file, err := utils.WriteExcel("", t, dataSlice) if err != nil { common.Fail(ctx, err.Error()) return } fileName := utils.GetExportFileName("point_info") common.Write(ctx, fileName, file) }
【Excel封装】
package utils import ( "fmt" "github.com/mitchellh/mapstructure" "github.com/xuri/excelize/v2" "io" "log" "pulse-service/src/common" "reflect" ) type File interface { io.Reader io.ReaderAt io.Seeker io.Closer } // Style 样式 type Style struct { Border []Border `json:"border"` Fill Fill `json:"fill"` Font *Font `json:"font"` Alignment *Alignment `json:"alignment"` Protection *Protection `json:"protection"` NumFmt int `json:"number_format"` DecimalPlaces int `json:"decimal_places"` CustomNumFmt *string `json:"custom_number_format"` Lang string `json:"lang"` NegRed bool `json:"negred"` } // Border 边框 type Border struct { Type string `json:"type"` Color string `json:"color"` Style int `json:"style"` } // Fill 填充 type Fill struct { Type string `json:"type"` Pattern int `json:"pattern"` Color []string `json:"color"` Shading int `json:"shading"` } // Font 字体 type Font struct { Bold bool `json:"bold"` // 是否加粗 Italic bool `json:"italic"` // 是否倾斜 Underline string `json:"underline"` // single double Family string `json:"family"` // 字体样式 Size float64 `json:"size"` // 字体大小 Strike bool `json:"strike"` // 删除线 Color string `json:"color"` // 字体颜色 } // Protection 保护 type Protection struct { Hidden bool `json:"hidden"` Locked bool `json:"locked"` } // Alignment 对齐 type Alignment struct { Horizontal string `json:"horizontal"` // 水平对齐方式 Indent int `json:"indent"` // 缩进 只要设置了值,就变成了左对齐 JustifyLastLine bool `json:"justify_last_line"` // 两端分散对齐,只有在水平对齐选择 distributed 时起作用 ReadingOrder uint64 `json:"reading_order"` // 文字方向 不知道值范围和具体的含义 RelativeIndent int `json:"relative_indent"` // 不知道具体的含义 ShrinkToFit bool `json:"shrink_to_fit"` // 缩小字体填充 TextRotation int `json:"text_rotation"` // 文本旋转 Vertical string `json:"vertical"` // 垂直对齐 WrapText bool `json:"wrap_text"` // 自动换行 } var cellCols = []string{"A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z"} func ReadExcel(file File, output interface{}) ([]interface{}, error) { f, err := excelize.OpenReader(file) if err != nil { log.Println(err.Error()) return nil, err } firstSheet := f.GetSheetName(0) rows, err := f.GetRows(firstSheet) if err != nil { log.Println(err.Error()) return nil, err } var title []string var ret []map[string]string for i, row := range rows { if i == 0 { for _, colCell := range row { title = append(title, colCell) } } else { theRow := map[string]string{} for j, colCell := range row { k := title[j] theRow[k] = colCell } ret = append(ret, theRow) } } slice := make([]interface{}, 0) for _, data := range ret { err = mapstructure.WeakDecode(data, &output) if err != nil { log.Println(err.Error()) return nil, err } slice = append(slice, output) } return slice, nil } func WriteExcel(sheetName string, rType reflect.Type, d []map[string]interface{}) (*excelize.File, error) { // 创建File file := excelize.NewFile() // 创建Sheet if IsEmpty(sheetName) { sheetName = "Sheet1" } file.NewSheet(sheetName) // 定义表头样式(通过结构体方式指定) headStyle, err := file.NewStyle(getHeaderStyle()) if err != nil { log.Println(err.Error()) return nil, err } // 定义行样式(通过JSON格式指定) rowStyle, err := file.NewStyle(getRowStyle()) if err != nil { log.Println(err.Error()) return nil, err } fieldNum := rType.NumField() tIndex := 0 for i := 0; i < fieldNum; i++ { field := rType.Field(i) tag := field.Tag.Get("mapstructure") if tag == "" { continue } cel := fmt.Sprintf("%s%d", cellCols[tIndex], 1) // 写入表头内容 _ = file.SetCellValue(sheetName, cel, tag) // 设置表头样式 _ = file.SetCellStyle(sheetName, cel, cel, headStyle) // 下一列 tIndex++ } // 循环写入数据 line := 1 for _, value := range d { line++ cIndex := 0 for i := 0; i < fieldNum; i++ { field := rType.Field(i) tag := field.Tag.Get("mapstructure") if tag == "" { continue } cel := fmt.Sprintf("%s%d", cellCols[cIndex], line) // 写入行内容 v, ok := value[field.Name].(common.JsonTime) if ok { _ = file.SetCellValue(sheetName, cel, v.Format()) } else { _ = file.SetCellValue(sheetName, cel, value[field.Name]) } // 设置行样式 _ = file.SetCellStyle(sheetName, cel, cel, rowStyle) // 下一列 cIndex++ } } return file, nil } func getHeaderStyle() interface{} { return &excelize.Style{ Border: []excelize.Border{ { Type: "right", Color: "#000000", Style: 1, }, { Type: "left", Color: "#000000", Style: 1, }, { Type: "top", Color: "#000000", Style: 1, }, { Type: "bottom", Color: "#000000", Style: 1, }, }, Fill: excelize.Fill{ // gradient: 渐变色 pattern 填充图案 // Pattern: 1, // 填充样式 当类型是 pattern 0-18 填充图案 1 实体填充 // Color: []string{"#FF0000"}, // 当Type = pattern 时,只有一个 Type: "gradient", Color: []string{"#DDEBF7", "##DDEBF7"}, // 类型是 gradient 使用 0-5 横向(每种颜色横向分布) 纵向 对角向上 对角向下 有外向内 由内向外 Shading: 1, }, Font: &excelize.Font{ // Bold: true, // 假哭 // Italic: false, // 斜体 // Underline: "single", // 下划线 Size: 11, Family: "宋体", // Strike: true, // 删除线 // Color: "#00FFFF", }, Alignment: &excelize.Alignment{ // 水平对齐方式 center left right fill(填充) justify(两端对齐) centerContinuous(跨列居中) distributed(分散对齐) Horizontal: "left", // 垂直对齐方式 center top justify distributed Vertical: "center", // Indent: 1, // 缩进 只要有值就变成了左对齐 + 缩进 // TextRotation: 30, // 旋转 // RelativeIndent: 10, // 好像没啥用 // ReadingOrder: 0, // 不知道怎么设置 // JustifyLastLine: true, // 两端分散对齐,只有 水平对齐 为 distributed 时 设置true 才有效 // WrapText: true, // 自动换行 // ShrinkToFit: true, // 缩小字体以填充单元格 }, Protection: &excelize.Protection{ Hidden: true, Locked: true, }, // 内置的数字格式样式 0-638 常用的 0-58 配合lang使用,因为语言不同样式不同 具体的样式参照文档 NumFmt: 0, // zh-cn 中文 Lang: "zh-cn", // 小数位数 只有NumFmt是 2-11 有效 // CustomNumFmt: "",// 自定义样式 是指针,只能通过变量的方式 DecimalPlaces: 2, NegRed: true, } } func getRowStyle() interface{} { return &excelize.Style{ Border: []excelize.Border{ { Type: "right", Color: "#000000", Style: 1, }, { Type: "left", Color: "#000000", Style: 1, }, { Type: "top", Color: "#000000", Style: 1, }, { Type: "bottom", Color: "#000000", Style: 1, }, }, Font: &excelize.Font{ Size: 11, Family: "宋体", }, Alignment: &excelize.Alignment{ Horizontal: "left", Vertical: "center", }, } }
分类:
golang
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)