package controllers import ( "encoding/csv" "encoding/json" "fmt" "path/filepath" "reflect" "strconv" "strings" "time" "github.com/gin-gonic/gin" "github.com/xuri/excelize/v2" ) // ExportHelper provides export utilities for CSV, JSON, etc. type ExportHelper struct{} // NewExportHelper creates a new ExportHelper instance func NewExportHelper() *ExportHelper { return &ExportHelper{} } // ExportToCSV exports data to CSV format func (eh *ExportHelper) ExportToCSV(c *gin.Context, data interface{}, filename string, headers []string) error { c.Header("Content-Type", "text/csv") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) writer := csv.NewWriter(c.Writer) defer writer.Flush() // Write headers if err := writer.Write(headers); err != nil { return err } // Convert data to slice of records records, err := eh.convertToCSVRecords(data) if err != nil { return err } // Write records for _, record := range records { if err := writer.Write(record); err != nil { return err } } return nil } // ExportToJSON exports data to JSON format func (eh *ExportHelper) ExportToJSON(c *gin.Context, data interface{}, filename string) error { c.Header("Content-Type", "application/json") c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename)) return json.NewEncoder(c.Writer).Encode(data) } // convertToCSVRecords converts a slice of structs to CSV records func (eh *ExportHelper) convertToCSVRecords(data interface{}) ([][]string, error) { v := reflect.ValueOf(data) if v.Kind() != reflect.Slice { return nil, fmt.Errorf("data must be a slice") } records := make([][]string, 0, v.Len()) for i := 0; i < v.Len(); i++ { item := v.Index(i) if item.Kind() == reflect.Ptr { item = item.Elem() } record := make([]string, 0, item.NumField()) for j := 0; j < item.NumField(); j++ { field := item.Field(j) record = append(record, eh.formatFieldValue(field)) } records = append(records, record) } return records, nil } // formatFieldValue formats a field value for CSV export func (eh *ExportHelper) formatFieldValue(v reflect.Value) string { switch v.Kind() { case reflect.String: return v.String() case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: return strconv.FormatInt(v.Int(), 10) case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: return strconv.FormatUint(v.Uint(), 10) case reflect.Float32, reflect.Float64: return strconv.FormatFloat(v.Float(), 'f', -1, 64) case reflect.Bool: return strconv.FormatBool(v.Bool()) case reflect.Struct: // Handle time.Time if t, ok := v.Interface().(time.Time); ok { return t.Format(time.RFC3339) } return fmt.Sprintf("%v", v.Interface()) case reflect.Ptr: if v.IsNil() { return "" } return eh.formatFieldValue(v.Elem()) default: return fmt.Sprintf("%v", v.Interface()) } } // ImportFromCSV imports tabular data from an uploaded file. // It supports traditional CSV files as well as Excel workbooks (.xlsx). // The caller always receives a [][]string where the first row is treated // as the header row by higher-level import functions. func (eh *ExportHelper) ImportFromCSV(c *gin.Context, formFieldName string) ([][]string, error) { file, err := c.FormFile(formFieldName) if err != nil { return nil, err } f, err := file.Open() if err != nil { return nil, err } defer f.Close() ext := strings.ToLower(filepath.Ext(file.Filename)) if ext == ".xlsx" { wb, err := excelize.OpenReader(f) if err != nil { return nil, err } defer func() { _ = wb.Close() }() sheets := wb.GetSheetList() if len(sheets) == 0 { return nil, fmt.Errorf("xlsx file has no sheets") } rows, err := wb.GetRows(sheets[0]) if err != nil { return nil, err } return rows, nil } reader := csv.NewReader(f) records, err := reader.ReadAll() if err != nil { return nil, err } return records, nil } // Global ExportHelper instance var Exporter = NewExportHelper()