123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- package excel
- import (
- "fmt"
- "github.com/xuri/excelize/v2"
- "io"
- "math"
- "net/http"
- "strconv"
- "time"
- )
- // ExcelGenerator 通用Excel生成器
- type ExcelGenerator struct {
- file *excelize.File
- sheetName string
- headers []string
- headerMap map[string]string // 表头映射(英文字段名->中文显示名)
- currentRow int // 当前行号
- }
- // NewExcelGenerator 创建新的Excel生成器
- func NewExcelGenerator(sheetName string, headers []string, headerMap map[string]string) *ExcelGenerator {
- f := excelize.NewFile()
- // 默认sheet名称是Sheet1,如果传入的是其他名称,则创建新sheet并删除默认sheet
- defaultSheetName := "Sheet1"
- if sheetName != defaultSheetName {
- f.NewSheet(sheetName)
- f.DeleteSheet(defaultSheetName)
- }
-
- return &ExcelGenerator{
- file: f,
- sheetName: sheetName,
- headers: headers,
- headerMap: headerMap,
- currentRow: 1, // 从第1行开始(通常第1行是表头)
- }
- }
- // WriteHeaders 写入表头
- func (g *ExcelGenerator) WriteHeaders() error {
- // 写入表头
- for i, header := range g.headers {
- // 获取对应的显示名,如果没有映射则使用原字段名
- displayName, exists := g.headerMap[header]
- if !exists {
- displayName = header
- }
-
- cell := fmt.Sprintf("%s%d", columnName(i), g.currentRow)
- if err := g.file.SetCellValue(g.sheetName, cell, displayName); err != nil {
- return err
- }
-
- // 设置表头样式(加粗、居中等)
- style, err := g.file.NewStyle(&excelize.Style{
- Font: &excelize.Font{
- Bold: true,
- },
- Alignment: &excelize.Alignment{
- Horizontal: "center",
- Vertical: "center",
- },
- })
- if err != nil {
- return err
- }
- if err := g.file.SetCellStyle(g.sheetName, cell, cell, style); err != nil {
- return err
- }
- }
-
- g.currentRow++ // 表头写完后,行号+1
- return nil
- }
- // WriteRows 写入多行数据
- func (g *ExcelGenerator) WriteRows(data []map[string]interface{}) error {
- for _, row := range data {
- if err := g.WriteRow(row); err != nil {
- return err
- }
- }
- return nil
- }
- // WriteRow 写入单行数据
- func (g *ExcelGenerator) WriteRow(rowData map[string]interface{}) error {
- for i, field := range g.headers {
- value, exists := rowData[field]
- if !exists {
- value = "" // 如果数据中不存在该字段,则写入空值
- }
-
- cell := fmt.Sprintf("%s%d", columnName(i), g.currentRow)
-
- // 根据不同的数据类型处理
- switch v := value.(type) {
- case time.Time:
- // 时间格式化为 YYYY-MM-DD HH:MM:SS
- if err := g.file.SetCellValue(g.sheetName, cell, v.Format("2006-01-02 15:04:05")); err != nil {
- return err
- }
- default:
- if err := g.file.SetCellValue(g.sheetName, cell, v); err != nil {
- return err
- }
- }
- }
-
- g.currentRow++ // 一行写完后,行号+1
- return nil
- }
- // SaveToWriter 保存到io.Writer接口
- func (g *ExcelGenerator) SaveToWriter(w io.Writer) error {
- return g.file.Write(w)
- }
- // SaveToBuffer 保存到内存
- func (g *ExcelGenerator) SaveToBuffer() ([]byte, error) {
- buffer, err := g.file.WriteToBuffer()
- if err != nil {
- return nil, err
- }
- return buffer.Bytes(), nil
- }
- // columnName 将列索引转换为Excel列名(A, B, C, ... Z, AA, AB, ...)
- func columnName(colIndex int) string {
- if colIndex < 26 {
- return string('A' + colIndex)
- }
-
- // 超过26列时需要用两个或更多字母表示
- result := ""
- for colIndex >= 0 {
- remainder := colIndex % 26
- result = string('A'+remainder) + result
- colIndex = colIndex/26 - 1
- if colIndex < 0 {
- break
- }
- }
-
- return result
- }
- // TransferOption 传输选项
- type TransferOption struct {
- FileName string
- ContentType string
- }
- // ExportType 导出类型枚举
- type ExportType int
- const (
- // ExportTypeNormal 普通导出(小文件,加载到内存后直接传输)
- ExportTypeNormal ExportType = iota
-
- // ExportTypeStream 流式导出(大文件,流式传输避免占用过多内存)
- ExportTypeStream
-
- // ExportTypeChunk 分块导出(超大文件,分块处理并传输)
- ExportTypeChunk
- )
- // SmartExport 智能选择导出方式
- // dataCount: 数据条数
- // rowSize: 每行数据的估计大小(字节数)
- func SmartExport(dataCount int, rowSize int) ExportType {
- // 估算导出文件大小(表头+数据)
- estimatedSize := (dataCount + 1) * rowSize
-
- // 根据估算大小选择不同的导出方式
- switch {
- case estimatedSize <= 5*1024*1024: // 5MB以下用普通导出
- return ExportTypeNormal
-
- case estimatedSize <= 50*1024*1024: // 5MB-50MB用流式导出
- return ExportTypeStream
-
- default: // 超过50MB用分块导出
- return ExportTypeChunk
- }
- }
- // NormalExport 普通导出(小文件,一次性加载到内存)
- func NormalExport(g *ExcelGenerator, w http.ResponseWriter, option TransferOption) error {
- // 设置响应头
- w.Header().Set("Content-Type", option.ContentType)
- w.Header().Set("Content-Disposition", "attachment; filename="+option.FileName)
-
- // 直接写入响应
- return g.SaveToWriter(w)
- }
- // StreamExport 流式导出(大文件,流式传输)
- func StreamExport(g *ExcelGenerator, w http.ResponseWriter, option TransferOption) error {
- // 设置响应头
- w.Header().Set("Content-Type", option.ContentType)
- w.Header().Set("Content-Disposition", "attachment; filename="+option.FileName)
- w.Header().Set("Transfer-Encoding", "chunked")
-
- // 流式写入
- return g.SaveToWriter(w)
- }
- // ChunkExport 分块导出(超大文件)
- // 这个方法需要配合前端实现,例如通过分页API多次获取数据并合并
- func ChunkExport(w http.ResponseWriter, option TransferOption, totalRecords int, pageSize int) {
- totalPages := int(math.Ceil(float64(totalRecords) / float64(pageSize)))
-
- // 设置响应头
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("X-Total-Pages", strconv.Itoa(totalPages))
- w.Header().Set("X-Total-Records", strconv.Itoa(totalRecords))
- w.Header().Set("X-Page-Size", strconv.Itoa(pageSize))
-
- // 返回分块导出信息
- w.Write([]byte(fmt.Sprintf(`{
- "message": "File is too large for direct download. Please use paginated export.",
- "total_records": %d,
- "total_pages": %d,
- "page_size": %d
- }`, totalRecords, totalPages, pageSize)))
- }
|