Files
go-common/docs/excel.md

448 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Excel导出工具文档
## 概述
Excel导出工具提供了将数据导出到Excel文件的功能支持结构体切片、自定义格式化、多工作表等特性。通过工厂模式外部项目可以方便地使用Excel导出功能。
## 功能特性
- **黑盒模式**提供直接调用的方法无需获取Excel对象
- **延迟初始化**Excel导出器在首次使用时才创建
- **支持结构体切片**自动将结构体切片转换为Excel行数据
- **支持嵌套字段**:支持访问嵌套结构体字段(如 "User.Name"
- **自定义格式化**:支持自定义字段值的格式化函数
- **自动列宽**:自动调整列宽以适应内容
- **表头样式**:自动应用表头样式(加粗、背景色等)
- **智能工作表管理**自动处理工作表的创建和删除避免产生空sheet
- **ExportData接口**支持实现ExportData接口进行高级定制
- **空数据处理**即使数据为空nil或空切片也会正常生成表头
- **统一接口**:只暴露 `ExportToWriter` 一个核心方法
## 使用方法
### 1. 创建工厂
```go
import "git.toowon.com/jimmy/go-common/factory"
// 从配置文件创建
fac, err := factory.NewFactoryFromFile("./config.json")
// 或Excel导出不需要配置可以传nil
fac := factory.NewFactory(nil)
```
### 2. 导出结构体切片到文件
```go
// 定义结构体
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
Status int `json:"status"`
}
// 准备数据
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 1},
}
// 定义导出列
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
{Header: "创建时间", Field: "CreatedAt", Width: 20},
{Header: "状态", Field: "Status", Width: 10},
}
// 导出到文件
err := fac.ExportToExcelFile("users.xlsx", "用户列表", columns, users)
if err != nil {
log.Fatal(err)
}
```
### 3. 导出到HTTP响应
```go
import "net/http"
func exportUsersHandler(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("./config.json")
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 1},
}
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID"},
{Header: "姓名", Field: "Name"},
{Header: "邮箱", Field: "Email"},
}
// 设置HTTP响应头
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=users.xlsx")
// 导出到响应
err := fac.ExportToExcel(w, "用户列表", columns, users)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
```
### 4. 使用格式化函数
```go
import "git.toowon.com/jimmy/go-common/excel"
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{
Header: "创建时间",
Field: "CreatedAt",
Width: 20,
Format: excel.AdaptTimeFormatter(tools.FormatDateTime), // 使用适配器直接调用tools函数
},
{
Header: "状态",
Field: "Status",
Width: 10,
Format: func(value interface{}) string {
// 自定义格式化函数
if status, ok := value.(int); ok {
if status == 1 {
return "启用"
}
return "禁用"
}
return ""
},
},
}
err := fac.ExportToExcelFile("users.xlsx", "用户列表", columns, users)
```
### 5. 使用ExportData接口高级功能
```go
// 实现ExportData接口
type UserExportData struct {
users []User
}
// GetExportColumns 获取导出列定义
func (d *UserExportData) GetExportColumns() []excel.ExportColumn {
return []excel.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
}
}
// GetExportRows 获取导出数据行
func (d *UserExportData) GetExportRows() [][]interface{} {
rows := make([][]interface{}, 0, len(d.users))
for _, user := range d.users {
row := []interface{}{
user.ID,
user.Name,
user.Email,
}
rows = append(rows, row)
}
return rows
}
// 使用
exportData := &UserExportData{users: users}
columns := exportData.GetExportColumns()
err := fac.ExportToExcelFile("users.xlsx", "用户列表", columns, exportData)
```
### 6. 获取Excel对象高级功能
```go
// 获取Excel导出器对象仅在需要高级功能时使用
excel, err := fac.GetExcel()
if err != nil {
log.Fatal(err)
}
// 获取excelize.File对象用于高级操作
file := excel.GetFile()
// 创建多个工作表
file.NewSheet("Sheet2")
file.SetCellValue("Sheet2", "A1", "数据")
// 自定义样式
style, _ := file.NewStyle(&excelize.Style{
Font: &excelize.Font{
Bold: true,
Size: 14,
},
})
file.SetCellStyle("Sheet2", "A1", "A1", style)
```
## API 参考
### 工厂方法
#### ExportToExcel(w io.Writer, sheetName string, columns []ExportColumn, data interface{}) error
导出数据到Writer。
**参数:**
- `w`: Writer对象如http.ResponseWriter
- `sheetName`: 工作表名称(可选,默认为"Sheet1"
- `columns`: 列定义
- `data`: 数据列表可以是结构体切片或实现了ExportData接口的对象
**返回:** 错误信息
**数据为空处理:**
- 支持 `nil`、空切片、指针类型等空数据情况
- 即使数据为空,表头也会正常生成
**工作表处理逻辑:**
- 如果 `sheetName` 为空,默认使用 "Sheet1"
- 如果指定的工作表不存在,会自动创建
- 使用自定义名称时会自动删除默认的"Sheet1"避免产生空sheet
**示例:**
```go
fac.ExportToExcel(w, "用户列表", columns, users)
fac.ExportToExcel(w, "空数据", columns, []User{}) // 空数据也会生成表头
```
#### ExportToExcelFile(filePath string, sheetName string, columns []ExportColumn, data interface{}) error
导出数据到文件。
**参数:**
- `filePath`: 文件路径
- `sheetName`: 工作表名称(可选,默认为"Sheet1"
- `columns`: 列定义
- `data`: 数据列表可以是结构体切片或实现了ExportData接口的对象
**返回:** 错误信息
**实现说明:**
- 此方法内部创建文件并调用 `ExportToWriter`
- 文件相关的封装由工厂方法处理
**工作表处理逻辑:**
- 如果 `sheetName` 为空,默认使用 "Sheet1"
- 如果指定的工作表不存在,会自动创建
- 使用自定义名称时会自动删除默认的"Sheet1"避免产生空sheet
**示例:**
```go
fac.ExportToExcelFile("users.xlsx", "用户列表", columns, users)
fac.ExportToExcelFile("empty.xlsx", "空数据", columns, []User{}) // 空数据也会生成表头
```
### 高级方法
#### GetExcel() (*excel.Excel, error)
获取Excel导出器对象。
**返回:** Excel导出器对象和错误信息
**说明:** 仅在需要使用高级功能时使用,推荐使用黑盒方法
### 结构体类型
#### ExportColumn
导出列定义。
```go
type ExportColumn struct {
Header string // 表头名称
Field string // 数据字段名(支持嵌套字段,如 "User.Name"
Width float64 // 列宽可选0表示自动
Format func(interface{}) string // 格式化函数(可选)
}
```
**字段说明:**
- `Header`: 表头显示的名称
- `Field`: 数据字段名,支持嵌套字段(如 "User.Name"
- `Width`: 列宽0表示自动调整
- `Format`: 格式化函数,用于自定义字段值的显示格式
### 格式化函数适配器
#### excel.AdaptTimeFormatter(fn func(time.Time, ...string) string) func(interface{}) string
适配器函数将tools包的格式化函数转换为Excel Format字段需要的函数类型。
**参数:**
- `fn`: tools包的格式化函数`tools.FormatDate``tools.FormatDateTime` 等)
**返回:** Excel Format字段需要的格式化函数
**说明:**
- 允许直接使用tools包的任何格式化函数
**示例:**
```go
import (
"git.toowon.com/jimmy/go-common/excel"
"git.toowon.com/jimmy/go-common/tools"
)
// 使用tools.FormatDate
Format: excel.AdaptTimeFormatter(tools.FormatDate)
// 使用tools.FormatDateTime
Format: excel.AdaptTimeFormatter(tools.FormatDateTime)
// 使用tools.FormatTime
Format: excel.AdaptTimeFormatter(tools.FormatTime)
// 使用自定义格式化函数
Format: excel.AdaptTimeFormatter(func(t time.Time) string {
return tools.Format(t, "2006-01-02 15:04:05", "Asia/Shanghai")
})
```
### ExportData接口
实现此接口的结构体可以直接导出。
```go
type ExportData interface {
// GetExportColumns 获取导出列定义
GetExportColumns() []ExportColumn
// GetExportRows 获取导出数据行
GetExportRows() [][]interface{}
}
```
## 完整示例
```go
package main
import (
"net/http"
"time"
"git.toowon.com/jimmy/go-common/excel"
"git.toowon.com/jimmy/go-common/factory"
)
type User struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
CreatedAt time.Time `json:"created_at"`
Status int `json:"status"`
}
func exportUsersHandler(w http.ResponseWriter, r *http.Request) {
fac, _ := factory.NewFactoryFromFile("./config.json")
// 从数据库或其他数据源获取数据
users := []User{
{ID: 1, Name: "Alice", Email: "alice@example.com", CreatedAt: time.Now(), Status: 1},
{ID: 2, Name: "Bob", Email: "bob@example.com", CreatedAt: time.Now(), Status: 1},
}
// 定义导出列
columns := []factory.ExportColumn{
{Header: "ID", Field: "ID", Width: 10},
{Header: "姓名", Field: "Name", Width: 20},
{Header: "邮箱", Field: "Email", Width: 30},
{
Header: "创建时间",
Field: "CreatedAt",
Width: 20,
Format: excel.AdaptTimeFormatter(tools.FormatDateTime),
},
{
Header: "状态",
Field: "Status",
Width: 10,
Format: func(value interface{}) string {
if status, ok := value.(int); ok {
if status == 1 {
return "启用"
}
return "禁用"
}
return ""
},
},
}
// 设置HTTP响应头
w.Header().Set("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
w.Header().Set("Content-Disposition", "attachment; filename=users.xlsx")
// 导出到响应
err := fac.ExportToExcel(w, "用户列表", columns, users)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
func main() {
http.HandleFunc("/export/users", exportUsersHandler)
http.ListenAndServe(":8080", nil)
}
```
## 设计优势
1. **降低复杂度**调用方无需关心Excel文件对象的创建和管理
2. **延迟初始化**Excel导出器在首次使用时才创建
3. **统一接口**:所有操作通过工厂方法调用
4. **灵活扩展**支持结构体切片、自定义格式化、ExportData接口等多种方式
5. **自动优化**:自动调整列宽、应用表头样式等
## 注意事项
1. **配置**Excel导出不需要配置可以传nil创建工厂
2. **错误处理**:所有方法都可能返回错误,需要正确处理
3. **延迟初始化**Excel导出器在首次使用时才创建首次调用可能稍慢
4. **字段名匹配**Field字段名必须与结构体字段名匹配区分大小写
5. **嵌套字段**:支持嵌套字段访问(如 "User.Name"),但需要确保字段路径正确
6. **格式化函数**格式化函数返回的字符串会直接写入Excel单元格
7. **列宽设置**Width为0时会自动调整列宽但可能影响性能大数据量时建议设置固定宽度
8. **工作表处理**工具会自动处理工作表的创建和删除确保不会产生空sheet
9. **空数据处理**:即使数据为 `nil` 或空切片,表头也会正常生成
10. **方法设计**
- `excel` 包只暴露 `ExportToWriter` 一个核心方法
- 文件相关的封装由工厂方法 `ExportToExcelFile` 处理
## 最佳实践
1. **使用工厂方法**:推荐使用 `ExportToExcel()``ExportToExcelFile()`
2. **设置列宽**:对于大数据量,建议设置固定列宽以提高性能
3. **使用格式化函数**:对于日期时间、状态等字段,使用格式化函数提高可读性
4. **错误处理**:始终检查导出方法的返回值
5. **HTTP响应**导出到HTTP响应时记得设置正确的Content-Type和Content-Disposition头
6. **工作表命名**:推荐使用有意义的工作表名称,工具会自动处理工作表的创建和删除
7. **空数据场景**即使查询结果为空也可以导出包含表头的Excel文件
## 示例
完整示例请参考 `examples/excel_example.go`