我来设计一个基于 Gometa 的通用 Excel 导出工具。这个工具将利用 Gometa 的标签解析能力和 godi 的依赖注入特性实现声明式、可扩展的 Excel 导出功能。架构设计┌─────────────────────────────────────────┐│ 业务层 (Business Layer) ││ type Order struct { ││ basedto.Gometa gometa:excel:导出订单 ││ OrderNo string excel:column:订单编号;width:20 ││ Amount float64 excel:column:订单金额;width:15;format:%.2f ││ Status int excel:column:状态;width:10;enum:0:待支付,1:已支付,2:已发货 ││ } │├─────────────────────────────────────────┤│ 元数据层 (Gometa Parser) ││ - ParseTags(excel) 解析字段标签 ││ - 构建 ExcelColumnMetadata 列表 ││ - 支持嵌套结构体递归解析 │├─────────────────────────────────────────┤│ 导出引擎 (ExcelExporter) ││ - 反射读取数据 ││ - 动态生成表头 ││ - 格式化单元格数字、日期、枚举映射 ││ - 样式渲染标题、边框、对齐 │├─────────────────────────────────────────┤│ 底层实现 (Excel Library) ││ - excelize / xlsx 等库 ││ - 流式写入大数据量 ││ - 多Sheet支持 │└─────────────────────────────────────────┘核心实现代码1. 元数据定义与解析package excelmetaimport (reflectstrconvstringsgithub.com/leijmdas/goboot/basedto)// ExcelColumn 定义列元数据type ExcelColumn struct {FieldName string // 结构体字段名ColumnName string // Excel列显示名Width float64 // 列宽Format string // 格式化字符串如 %.2fEnumMap map[string]string // 枚举映射 0:待支付,1:已支付DateFormat string // 日期格式 2006-01-02 15:04:05SortOrder int // 排序优先级Nested []*ExcelColumn // 嵌套结构体字段IsNested bool // 是否为嵌套结构体}// ExcelMetadata 整个导出配置type ExcelMetadata struct {Title string // 导出标题来自 gometa:excel:标题SheetName string // Sheet名Columns []*ExcelColumn // 列定义HasNested bool // 是否包含嵌套结构体}// Parser 解析器type Parser struct{}// Parse 解析任意结构体func (p *Parser) Parse(data interface{}) *ExcelMetadata {t : reflect.TypeOf(data)if t.Kind() reflect.Ptr {t t.Elem()}if t.Kind() reflect.Slice {t t.Elem()if t.Kind() reflect.Ptr {t t.Elem()}}meta : ExcelMetadata{SheetName: Sheet1,Columns: make([]*ExcelColumn, 0),}// 解析 Gometa 标签获取标题if gm : parseGometa(t); gm ! nil {if excelTag : gm.Tags[excel]; excelTag ! {parts : strings.Split(excelTag, :)if len(parts) 2 {meta.Title parts[1]}}}// 递归解析字段meta.Columns p.parseFields(t, )return meta}// parseFields 递归解析字段标签func (p *Parser) parseFields(t reflect.Type, prefix string) []*ExcelColumn {columns : make([]*ExcelColumn, 0)for i : 0; i t.NumField(); i {field : t.Field(i)// 跳过 Gometa 和 BaseEntityif field.Type reflect.TypeOf(basedto.Gometa{}) ||field.Type.String() basedto.BaseEntity {continue}// 检查是否有 excel 标签tag : field.Tag.Get(excel)if tag !isNestedStruct(field) {continue}col : ExcelColumn{FieldName: field.Name,}if isNestedStruct(field) tag {// 递归解析嵌套结构体col.IsNested truenestedType : field.Typeif nestedType.Kind() reflect.Ptr {nestedType nestedType.Elem()}col.Nested p.parseFields(nestedType, prefixfield.Name.)if len(col.Nested) 0 {columns append(columns, col)}} else {// 解析 excel 标签p.parseTag(col, tag)col.SortOrder len(columns)columns append(columns, col)}}return columns}func (p *Parser) parseTag(col *ExcelColumn, tag string) {parts : strings.Split(tag, ;)for _, part : range parts {kv : strings.SplitN(part, :, 2)if len(kv) ! 2 {continue}key, value : strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])switch key {case column:col.ColumnName valuecase width:col.Width, _ strconv.ParseFloat(value, 64)case format:col.Format valuecase enum:col.EnumMap parseEnumMap(value)case date:col.DateFormat value}}}func parseEnumMap(s string) map[string]string {m : make(map[string]string)pairs : strings.Split(s, ,)for _, p : range pairs {kv : strings.SplitN(p, :, 2)if len(kv) 2 {m[strings.TrimSpace(kv[0])] strings.TrimSpace(kv[1])}}return m}func isNestedStruct(f reflect.StructField) bool {kind : f.Type.Kind()if kind reflect.Ptr {kind f.Type.Elem().Kind()}return kind reflect.Struct}func parseGometa(t reflect.Type) *basedto.Gometa {// 实际实现通过反射查找 Gometa 字段并解析其标签for i : 0; i t.NumField(); i {if t.Field(i).Type reflect.TypeOf(basedto.Gometa{}) {gm : basedto.Gometa{}// 解析 gometa 标签...return gm}}return nil}2. 导出引擎实现package exporterimport (fmtreflecttimegithub.com/xuri/excelize/v2github.com/leijmdas/goboot/excelmeta)// Exporter 导出引擎type Exporter struct {parser *excelmeta.Parserstyle *StyleManager}func New() *Exporter {return Exporter{parser: excelmeta.Parser{},style: NewStyleManager(),}}// Export 通用导出方法func (e *Exporter) Export(data interface{}, filename string) ([]byte, error) {// 解析元数据meta : e.parser.Parse(data)// 创建 Excel 文件f : excelize.NewFile()defer f.Close()// 设置 Sheet 名if meta.SheetName ! {f.SetSheetName(Sheet1, meta.SheetName)}sheet : meta.SheetNameif sheet {sheet Sheet1}// 写入标题if meta.Title ! {e.writeTitle(f, sheet, meta.Title, len(flattenColumns(meta.Columns)))}// 写入表头e.writeHeader(f, sheet, meta.Columns, meta.Title ! )// 写入数据if err : e.writeData(f, sheet, data, meta.Columns, meta.Title ! ); err ! nil {return nil, err}// 调整列宽e.setColumnWidth(f, sheet, meta.Columns)return f.WriteToBuffer().Bytes()}// writeTitle 写入大标题func (e *Exporter) writeTitle(f *excelize.File, sheet, title string, colCount int) {cell : A1f.SetCellValue(sheet, cell, title)f.MergeCell(sheet, A1, fmt.Sprintf(%s1, columnName(colCount-1)))f.SetCellStyle(sheet, cell, cell, e.style.TitleStyle())}// writeHeader 写入表头func (e *Exporter) writeHeader(f *excelize.File, sheet string, cols []*excelmeta.ExcelColumn, hasTitle bool) {row : 1if hasTitle {row 2}colIdx : 0for _, col : range cols {if col.IsNested {// 嵌套结构体递归写入子列for _, nested : range col.Nested {cell : fmt.Sprintf(%s%d, columnName(colIdx), row)f.SetCellValue(sheet, cell, nested.ColumnName)f.SetCellStyle(sheet, cell, cell, e.style.HeaderStyle())colIdx}} else {cell : fmt.Sprintf(%s%d, columnName(colIdx), row)f.SetCellValue(sheet, cell, col.ColumnName)f.SetCellStyle(sheet, cell, cell, e.style.HeaderStyle())colIdx}}}// writeData 写入数据行func (e *Exporter) writeData(f *excelize.File, sheet string, data interface{}, cols []*excelmeta.ExcelColumn, hasTitle bool) error {v : reflect.ValueOf(data)if v.Kind() ! reflect.Slice {return fmt.Errorf(data must be slice)}startRow : 2if hasTitle {startRow 3}for i : 0; i v.Len(); i {rowNum : startRow iitem : v.Index(i)if item.Kind() reflect.Ptr {item item.Elem()}colIdx : 0for _, col : range cols {if col.IsNested {// 获取嵌套结构体值nestedVal : getFieldValue(item, col.FieldName)if nestedVal.IsValid() {for _, nestedCol : range col.Nested {val : getFieldValue(nestedVal, nestedCol.FieldName)cell : fmt.Sprintf(%s%d, columnName(colIdx), rowNum)e.setCellValue(f, sheet, cell, val, nestedCol)colIdx}}} else {val : getFieldValue(item, col.FieldName)cell : fmt.Sprintf(%s%d, columnName(colIdx), rowNum)e.setCellValue(f, sheet, cell, val, col)colIdx}}}return nil}// setCellValue 设置单元格值含格式化func (e *Exporter) setCellValue(f *excelize.File, sheet, cell string, val reflect.Value, col *excelmeta.ExcelColumn) {if !val.IsValid() {f.SetCellValue(sheet, cell, )return}var displayValue interface{}switch val.Kind() {case reflect.String:s : val.String()if col.EnumMap ! nil {if mapped, ok : col.EnumMap[s]; ok {s mapped}}displayValue scase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:n : val.Int()if col.EnumMap ! nil {key : fmt.Sprintf(%d, n)if mapped, ok : col.EnumMap[key]; ok {displayValue mapped} else {displayValue n}} else {displayValue n}case reflect.Float32, reflect.Float64:f : val.Float()if col.Format ! {displayValue fmt.Sprintf(col.Format, f)} else {displayValue f}case reflect.Struct:if t, ok : val.Interface().(time.Time); ok {format : col.DateFormatif format {format 2006-01-02 15:04:05}displayValue t.Format(format)} else {displayValue fmt.Sprintf(%v, val.Interface())}default:displayValue fmt.Sprintf(%v, val.Interface())}f.SetCellValue(sheet, cell, displayValue)}// setColumnWidth 设置列宽func (e *Exporter) setColumnWidth(f *excelize.File, sheet string, cols []*excelmeta.ExcelColumn) {colIdx : 0for _, col : range cols {if col.IsNested {for _, nested : range col.Nested {if nested.Width 0 {f.SetColWidth(sheet, columnName(colIdx), columnName(colIdx), nested.Width)}colIdx}} else {if col.Width 0 {f.SetColWidth(sheet, columnName(colIdx), columnName(colIdx), col.Width)}colIdx}}}// 辅助函数func getFieldValue(v reflect.Value, fieldName string) reflect.Value {field : v.FieldByName(fieldName)if !field.IsValid() {// 尝试从嵌入字段查找for i : 0; i v.NumField(); i {fv : v.Field(i)if fv.Kind() reflect.Struct v.Type().Field(i).Anonymous {if res : getFieldValue(fv, fieldName); res.IsValid() {return res}}}}return field}func columnName(idx int) string {// 0-A, 1-B, 25-Z, 26-AAif idx 26 {return string(rune(A idx))}return columnName(idx/26-1) string(rune(Aidx%26))}func flattenColumns(cols []*excelmeta.ExcelColumn) []*excelmeta.ExcelColumn {result : make([]*excelmeta.ExcelColumn, 0)for _, col : range cols {if col.IsNested {result append(result, flattenColumns(col.Nested)...)} else {result append(result, col)}}return result}3. 样式管理器package exporterimport github.com/xuri/excelize/v2type StyleManager struct {styles map[string]int}func NewStyleManager() *StyleManager {return StyleManager{styles: make(map[string]int)}}func (sm *StyleManager) TitleStyle() int {if id, ok : sm.styles[title]; ok {return id}style, _ : excelize.NewStyle(excelize.Style{Font: excelize.Font{Bold: true,Size: 16,Color: FFFFFF,},Fill: excelize.Fill{Type: pattern,Color: []string{4472C4},Pattern: 1,},Alignment: excelize.Alignment{Horizontal: center,Vertical: center,},})sm.styles[title] stylereturn style}func (sm *StyleManager) HeaderStyle() int {if id, ok : sm.styles[header]; ok {return id}style, _ : excelize.NewStyle(excelize.Style{Font: excelize.Font{Bold: true,Size: 11,Color: FFFFFF,},Fill: excelize.Fill{Type: pattern,Color: []string{5B9BD5},Pattern: 1,},Border: excelize.Border{Left: excelize.BorderStyle{Style: thin, Color: 000000},Right: excelize.BorderStyle{Style: thin, Color: 000000},Top: excelize.BorderStyle{Style: thin, Color: 000000},Bottom: excelize.BorderStyle{Style: thin, Color: 000000},},Alignment: excelize.Alignment{Horizontal: center,Vertical: center,},})sm.styles[header] stylereturn style}4. 使用示例package mainimport (ostimegithub.com/leijmdas/goboot/basedtogithub.com/yourproject/exporter)// 订单实体 - 声明式配置type Order struct {basedto.Gometa gometa:excel:订单导出报表basedto.BaseEntityOrderNo string excel:column:订单编号;width:25Customer string excel:column:客户名称;width:20Amount float64 excel:column:订单金额;width:15;format:%.2fStatus int excel:column:订单状态;width:12;enum:0:待付款,1:待发货,2:已发货,3:已完成CreateTime time.Time excel:column:下单时间;width:20;date:2006-01-02 15:04// 嵌套结构体Address Address // 自动递归导出}type Address struct {Province string excel:column:省份;width:15City string excel:column:城市;width:15Detail string excel:column:详细地址;width:40}func main() {// 准备数据orders : []Order{{OrderNo: ORD2024001,Customer: 张三科技,Amount: 15800.50,Status: 2,CreateTime: time.Now(),Address: Address{Province: 广东省,City: 深圳市,Detail: 南山区科技园,},},// ... 更多数据}// 导出exp : exporter.New()bytes, err : exp.Export(orders, orders.xlsx)if err ! nil {panic(err)}os.WriteFile(orders.xlsx, bytes, 0644)}5. 高级特性扩展// 大数据量流式导出func (e *Exporter) ExportStream(dataChan -chan interface{}, filename string) error {f : excelize.NewFile()streamWriter, _ : f.NewStreamWriter(Sheet1)// 先写表头meta : e.parser.Parse(-dataChan)// ... 流式写入逻辑return f.SaveAs(filename)}// 多 Sheet 导出type MultiSheet struct {Sheets []SheetData excel:sheets}type SheetData struct {Name string excel:sheet_nameData interface{} excel:data}// 模板导出预定义样式func (e *Exporter) ExportWithTemplate(data interface{}, templatePath string) ([]byte, error) {// 加载模板填充数据}核心优势特性 实现方式 价值声明式配置 excel:column:名称;width:20 零代码实现导出自动元数据 Gometa 标签解析 统一报表标题管理嵌套支持 递归解析嵌套结构体 复杂对象一键导出类型安全 反射 格式化标签 避免运行时错误可扩展 接口化设计 支持多种 Excel 库这个工具充分利用了 Gometa 的标签解析能力和 godi 的依赖注入特性实现了声明式、低侵入的 Excel 导出方案与 leijmdas 全家桶的工程化理念保持一致。
想通过gometa实现通用excel导出工具
我来设计一个基于 Gometa 的通用 Excel 导出工具。这个工具将利用 Gometa 的标签解析能力和 godi 的依赖注入特性实现声明式、可扩展的 Excel 导出功能。架构设计┌─────────────────────────────────────────┐│ 业务层 (Business Layer) ││ type Order struct { ││ basedto.Gometa gometa:excel:导出订单 ││ OrderNo string excel:column:订单编号;width:20 ││ Amount float64 excel:column:订单金额;width:15;format:%.2f ││ Status int excel:column:状态;width:10;enum:0:待支付,1:已支付,2:已发货 ││ } │├─────────────────────────────────────────┤│ 元数据层 (Gometa Parser) ││ - ParseTags(excel) 解析字段标签 ││ - 构建 ExcelColumnMetadata 列表 ││ - 支持嵌套结构体递归解析 │├─────────────────────────────────────────┤│ 导出引擎 (ExcelExporter) ││ - 反射读取数据 ││ - 动态生成表头 ││ - 格式化单元格数字、日期、枚举映射 ││ - 样式渲染标题、边框、对齐 │├─────────────────────────────────────────┤│ 底层实现 (Excel Library) ││ - excelize / xlsx 等库 ││ - 流式写入大数据量 ││ - 多Sheet支持 │└─────────────────────────────────────────┘核心实现代码1. 元数据定义与解析package excelmetaimport (reflectstrconvstringsgithub.com/leijmdas/goboot/basedto)// ExcelColumn 定义列元数据type ExcelColumn struct {FieldName string // 结构体字段名ColumnName string // Excel列显示名Width float64 // 列宽Format string // 格式化字符串如 %.2fEnumMap map[string]string // 枚举映射 0:待支付,1:已支付DateFormat string // 日期格式 2006-01-02 15:04:05SortOrder int // 排序优先级Nested []*ExcelColumn // 嵌套结构体字段IsNested bool // 是否为嵌套结构体}// ExcelMetadata 整个导出配置type ExcelMetadata struct {Title string // 导出标题来自 gometa:excel:标题SheetName string // Sheet名Columns []*ExcelColumn // 列定义HasNested bool // 是否包含嵌套结构体}// Parser 解析器type Parser struct{}// Parse 解析任意结构体func (p *Parser) Parse(data interface{}) *ExcelMetadata {t : reflect.TypeOf(data)if t.Kind() reflect.Ptr {t t.Elem()}if t.Kind() reflect.Slice {t t.Elem()if t.Kind() reflect.Ptr {t t.Elem()}}meta : ExcelMetadata{SheetName: Sheet1,Columns: make([]*ExcelColumn, 0),}// 解析 Gometa 标签获取标题if gm : parseGometa(t); gm ! nil {if excelTag : gm.Tags[excel]; excelTag ! {parts : strings.Split(excelTag, :)if len(parts) 2 {meta.Title parts[1]}}}// 递归解析字段meta.Columns p.parseFields(t, )return meta}// parseFields 递归解析字段标签func (p *Parser) parseFields(t reflect.Type, prefix string) []*ExcelColumn {columns : make([]*ExcelColumn, 0)for i : 0; i t.NumField(); i {field : t.Field(i)// 跳过 Gometa 和 BaseEntityif field.Type reflect.TypeOf(basedto.Gometa{}) ||field.Type.String() basedto.BaseEntity {continue}// 检查是否有 excel 标签tag : field.Tag.Get(excel)if tag !isNestedStruct(field) {continue}col : ExcelColumn{FieldName: field.Name,}if isNestedStruct(field) tag {// 递归解析嵌套结构体col.IsNested truenestedType : field.Typeif nestedType.Kind() reflect.Ptr {nestedType nestedType.Elem()}col.Nested p.parseFields(nestedType, prefixfield.Name.)if len(col.Nested) 0 {columns append(columns, col)}} else {// 解析 excel 标签p.parseTag(col, tag)col.SortOrder len(columns)columns append(columns, col)}}return columns}func (p *Parser) parseTag(col *ExcelColumn, tag string) {parts : strings.Split(tag, ;)for _, part : range parts {kv : strings.SplitN(part, :, 2)if len(kv) ! 2 {continue}key, value : strings.TrimSpace(kv[0]), strings.TrimSpace(kv[1])switch key {case column:col.ColumnName valuecase width:col.Width, _ strconv.ParseFloat(value, 64)case format:col.Format valuecase enum:col.EnumMap parseEnumMap(value)case date:col.DateFormat value}}}func parseEnumMap(s string) map[string]string {m : make(map[string]string)pairs : strings.Split(s, ,)for _, p : range pairs {kv : strings.SplitN(p, :, 2)if len(kv) 2 {m[strings.TrimSpace(kv[0])] strings.TrimSpace(kv[1])}}return m}func isNestedStruct(f reflect.StructField) bool {kind : f.Type.Kind()if kind reflect.Ptr {kind f.Type.Elem().Kind()}return kind reflect.Struct}func parseGometa(t reflect.Type) *basedto.Gometa {// 实际实现通过反射查找 Gometa 字段并解析其标签for i : 0; i t.NumField(); i {if t.Field(i).Type reflect.TypeOf(basedto.Gometa{}) {gm : basedto.Gometa{}// 解析 gometa 标签...return gm}}return nil}2. 导出引擎实现package exporterimport (fmtreflecttimegithub.com/xuri/excelize/v2github.com/leijmdas/goboot/excelmeta)// Exporter 导出引擎type Exporter struct {parser *excelmeta.Parserstyle *StyleManager}func New() *Exporter {return Exporter{parser: excelmeta.Parser{},style: NewStyleManager(),}}// Export 通用导出方法func (e *Exporter) Export(data interface{}, filename string) ([]byte, error) {// 解析元数据meta : e.parser.Parse(data)// 创建 Excel 文件f : excelize.NewFile()defer f.Close()// 设置 Sheet 名if meta.SheetName ! {f.SetSheetName(Sheet1, meta.SheetName)}sheet : meta.SheetNameif sheet {sheet Sheet1}// 写入标题if meta.Title ! {e.writeTitle(f, sheet, meta.Title, len(flattenColumns(meta.Columns)))}// 写入表头e.writeHeader(f, sheet, meta.Columns, meta.Title ! )// 写入数据if err : e.writeData(f, sheet, data, meta.Columns, meta.Title ! ); err ! nil {return nil, err}// 调整列宽e.setColumnWidth(f, sheet, meta.Columns)return f.WriteToBuffer().Bytes()}// writeTitle 写入大标题func (e *Exporter) writeTitle(f *excelize.File, sheet, title string, colCount int) {cell : A1f.SetCellValue(sheet, cell, title)f.MergeCell(sheet, A1, fmt.Sprintf(%s1, columnName(colCount-1)))f.SetCellStyle(sheet, cell, cell, e.style.TitleStyle())}// writeHeader 写入表头func (e *Exporter) writeHeader(f *excelize.File, sheet string, cols []*excelmeta.ExcelColumn, hasTitle bool) {row : 1if hasTitle {row 2}colIdx : 0for _, col : range cols {if col.IsNested {// 嵌套结构体递归写入子列for _, nested : range col.Nested {cell : fmt.Sprintf(%s%d, columnName(colIdx), row)f.SetCellValue(sheet, cell, nested.ColumnName)f.SetCellStyle(sheet, cell, cell, e.style.HeaderStyle())colIdx}} else {cell : fmt.Sprintf(%s%d, columnName(colIdx), row)f.SetCellValue(sheet, cell, col.ColumnName)f.SetCellStyle(sheet, cell, cell, e.style.HeaderStyle())colIdx}}}// writeData 写入数据行func (e *Exporter) writeData(f *excelize.File, sheet string, data interface{}, cols []*excelmeta.ExcelColumn, hasTitle bool) error {v : reflect.ValueOf(data)if v.Kind() ! reflect.Slice {return fmt.Errorf(data must be slice)}startRow : 2if hasTitle {startRow 3}for i : 0; i v.Len(); i {rowNum : startRow iitem : v.Index(i)if item.Kind() reflect.Ptr {item item.Elem()}colIdx : 0for _, col : range cols {if col.IsNested {// 获取嵌套结构体值nestedVal : getFieldValue(item, col.FieldName)if nestedVal.IsValid() {for _, nestedCol : range col.Nested {val : getFieldValue(nestedVal, nestedCol.FieldName)cell : fmt.Sprintf(%s%d, columnName(colIdx), rowNum)e.setCellValue(f, sheet, cell, val, nestedCol)colIdx}}} else {val : getFieldValue(item, col.FieldName)cell : fmt.Sprintf(%s%d, columnName(colIdx), rowNum)e.setCellValue(f, sheet, cell, val, col)colIdx}}}return nil}// setCellValue 设置单元格值含格式化func (e *Exporter) setCellValue(f *excelize.File, sheet, cell string, val reflect.Value, col *excelmeta.ExcelColumn) {if !val.IsValid() {f.SetCellValue(sheet, cell, )return}var displayValue interface{}switch val.Kind() {case reflect.String:s : val.String()if col.EnumMap ! nil {if mapped, ok : col.EnumMap[s]; ok {s mapped}}displayValue scase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:n : val.Int()if col.EnumMap ! nil {key : fmt.Sprintf(%d, n)if mapped, ok : col.EnumMap[key]; ok {displayValue mapped} else {displayValue n}} else {displayValue n}case reflect.Float32, reflect.Float64:f : val.Float()if col.Format ! {displayValue fmt.Sprintf(col.Format, f)} else {displayValue f}case reflect.Struct:if t, ok : val.Interface().(time.Time); ok {format : col.DateFormatif format {format 2006-01-02 15:04:05}displayValue t.Format(format)} else {displayValue fmt.Sprintf(%v, val.Interface())}default:displayValue fmt.Sprintf(%v, val.Interface())}f.SetCellValue(sheet, cell, displayValue)}// setColumnWidth 设置列宽func (e *Exporter) setColumnWidth(f *excelize.File, sheet string, cols []*excelmeta.ExcelColumn) {colIdx : 0for _, col : range cols {if col.IsNested {for _, nested : range col.Nested {if nested.Width 0 {f.SetColWidth(sheet, columnName(colIdx), columnName(colIdx), nested.Width)}colIdx}} else {if col.Width 0 {f.SetColWidth(sheet, columnName(colIdx), columnName(colIdx), col.Width)}colIdx}}}// 辅助函数func getFieldValue(v reflect.Value, fieldName string) reflect.Value {field : v.FieldByName(fieldName)if !field.IsValid() {// 尝试从嵌入字段查找for i : 0; i v.NumField(); i {fv : v.Field(i)if fv.Kind() reflect.Struct v.Type().Field(i).Anonymous {if res : getFieldValue(fv, fieldName); res.IsValid() {return res}}}}return field}func columnName(idx int) string {// 0-A, 1-B, 25-Z, 26-AAif idx 26 {return string(rune(A idx))}return columnName(idx/26-1) string(rune(Aidx%26))}func flattenColumns(cols []*excelmeta.ExcelColumn) []*excelmeta.ExcelColumn {result : make([]*excelmeta.ExcelColumn, 0)for _, col : range cols {if col.IsNested {result append(result, flattenColumns(col.Nested)...)} else {result append(result, col)}}return result}3. 样式管理器package exporterimport github.com/xuri/excelize/v2type StyleManager struct {styles map[string]int}func NewStyleManager() *StyleManager {return StyleManager{styles: make(map[string]int)}}func (sm *StyleManager) TitleStyle() int {if id, ok : sm.styles[title]; ok {return id}style, _ : excelize.NewStyle(excelize.Style{Font: excelize.Font{Bold: true,Size: 16,Color: FFFFFF,},Fill: excelize.Fill{Type: pattern,Color: []string{4472C4},Pattern: 1,},Alignment: excelize.Alignment{Horizontal: center,Vertical: center,},})sm.styles[title] stylereturn style}func (sm *StyleManager) HeaderStyle() int {if id, ok : sm.styles[header]; ok {return id}style, _ : excelize.NewStyle(excelize.Style{Font: excelize.Font{Bold: true,Size: 11,Color: FFFFFF,},Fill: excelize.Fill{Type: pattern,Color: []string{5B9BD5},Pattern: 1,},Border: excelize.Border{Left: excelize.BorderStyle{Style: thin, Color: 000000},Right: excelize.BorderStyle{Style: thin, Color: 000000},Top: excelize.BorderStyle{Style: thin, Color: 000000},Bottom: excelize.BorderStyle{Style: thin, Color: 000000},},Alignment: excelize.Alignment{Horizontal: center,Vertical: center,},})sm.styles[header] stylereturn style}4. 使用示例package mainimport (ostimegithub.com/leijmdas/goboot/basedtogithub.com/yourproject/exporter)// 订单实体 - 声明式配置type Order struct {basedto.Gometa gometa:excel:订单导出报表basedto.BaseEntityOrderNo string excel:column:订单编号;width:25Customer string excel:column:客户名称;width:20Amount float64 excel:column:订单金额;width:15;format:%.2fStatus int excel:column:订单状态;width:12;enum:0:待付款,1:待发货,2:已发货,3:已完成CreateTime time.Time excel:column:下单时间;width:20;date:2006-01-02 15:04// 嵌套结构体Address Address // 自动递归导出}type Address struct {Province string excel:column:省份;width:15City string excel:column:城市;width:15Detail string excel:column:详细地址;width:40}func main() {// 准备数据orders : []Order{{OrderNo: ORD2024001,Customer: 张三科技,Amount: 15800.50,Status: 2,CreateTime: time.Now(),Address: Address{Province: 广东省,City: 深圳市,Detail: 南山区科技园,},},// ... 更多数据}// 导出exp : exporter.New()bytes, err : exp.Export(orders, orders.xlsx)if err ! nil {panic(err)}os.WriteFile(orders.xlsx, bytes, 0644)}5. 高级特性扩展// 大数据量流式导出func (e *Exporter) ExportStream(dataChan -chan interface{}, filename string) error {f : excelize.NewFile()streamWriter, _ : f.NewStreamWriter(Sheet1)// 先写表头meta : e.parser.Parse(-dataChan)// ... 流式写入逻辑return f.SaveAs(filename)}// 多 Sheet 导出type MultiSheet struct {Sheets []SheetData excel:sheets}type SheetData struct {Name string excel:sheet_nameData interface{} excel:data}// 模板导出预定义样式func (e *Exporter) ExportWithTemplate(data interface{}, templatePath string) ([]byte, error) {// 加载模板填充数据}核心优势特性 实现方式 价值声明式配置 excel:column:名称;width:20 零代码实现导出自动元数据 Gometa 标签解析 统一报表标题管理嵌套支持 递归解析嵌套结构体 复杂对象一键导出类型安全 反射 格式化标签 避免运行时错误可扩展 接口化设计 支持多种 Excel 库这个工具充分利用了 Gometa 的标签解析能力和 godi 的依赖注入特性实现了声明式、低侵入的 Excel 导出方案与 leijmdas 全家桶的工程化理念保持一致。