excel导出
"github.com/douyacun/go-struct-excel"框架是一个比较实用的框架。
- 结构体数组转化成字节
go
import ex "github.com/douyacun/go-struct-excel"
// 导出为excel文件流
// T为任意了类型
// data参数为T类型的指针数组,必须是指针数组
// name为导出的文件名
func Export[T any](data []T, name string, sheetName string) ([]byte, error) {
// helloworld.xlsx
if name == "" {
name = "test.xlsx"
}
if !strings.Contains(name, ".xlsx") {
name = name + ".xlsx"
}
excel := ex.NewExcel(name)
defer excel.File.Close()
sheet, err := excel.AddSheet(sheetName)
if err != nil {
return nil, err
}
if err = sheet.AddData(data); err != nil {
return nil, err
}
return excel.Bytes()
}
上述方法可以将带excel标签的结构体数组转成字节,并设置页脚以及文件名等设置。这是使用泛型包装,实际data需要数组指针或切片。
- 返回数据字节
go
func ResponsibilityExport(c *fiber.Ctx) error {
var req model.ResponsibilitySubjectGet
if err := c.QueryParser(&req); err != nil {
return respond.Fail(c, "参数错误", err)
}
resp, err := service.ResponsibilityExport(req)
if err != nil {
return respond.Fail(c, "操作失败", err)
}
c.Set("Content-Disposition", "attachment; filename=file"+".xlsx")
data, ok := resp.([]byte)
if ok {
return c.Send(data)
}
return respond.Fail(c, "导出失败", errors.New("导出失败"))
}
go
func ResponsibilityExport(req model.ResponsibilitySubjectGet) (resp any, err error) {
session := db.Gorm.Model(&model.ResponsibilitySubject{}).Where("is_deleted = ?", false)
if req.AdminRegionCode != "" {
session.Where("admin_region_code like ?", tool.ConvertArea(req.AdminRegionCode))
}
if req.SubjectName != "" {
session.Where("subject_name like ?", "%"+req.SubjectName+"%")
}
if req.SubjectType != "" {
session.Where("subject_type = ?", req.SubjectType)
}
if req.Status != "" {
session.Where("status = ?", req.Status)
}
var (
param []model.ResponsibilitySubject
export []*excel_util.ResponsibilitySubject
)
err = session.Find(¶m).Error
if err != nil {
return nil, err
}
for _, subject := range param {
tmp := &excel_util.ResponsibilitySubject{
SubjectName: subject.SubjectName,
SubjectType: subject.SubjectType,
AdminRegionCode: subject.AdminRegionCode,
Principal: subject.Principal,
PrincipalPhone: subject.PrincipalPhone,
}
export = append(export, tmp)
}
bytes, err := excel_util.Export[*excel_util.ResponsibilitySubject](export, "责任主体", "Sheet1")
if err != nil {
return nil, err
}
return bytes, nil
}
go
type ResponsibilitySubject struct {
SubjectName string `json:"subject_name" excel:"责任主体名称"`
SubjectType string `json:"subject_type" excel:"责任主体类型"`
AdminRegionCode string `json:"admin_region_code" excel:"所属行政区域"`
Principal string `json:"principal" excel:"负责人"`
PrincipalPhone string `json:"principal_phone" excel:"联系电话"`
}
go-struct-excel框架提供了一个excel标签对应的注释会直接转化为表头。上述方法在构建携带excel标签的实际数据。只需要将结构体数据传给Export方法并将数据字节作为流返回就可以得到excel文件
excel导入
"github.com/xuri/excelize/v2" 框架解析excel文件。
- 读取excel文件流
go
func ParseExcel[T any](reader io.Reader, sheet string) ([]T, error) {
f, err := excelize.OpenReader(reader)
if err != nil {
fmt.Println(err)
return nil, err
}
defer func() {
if err := f.Close(); err != nil {
fmt.Println(err)
}
}()
// 获取sheet所有行
rows, err := f.GetRows(sheet)
if err != nil {
fmt.Println("ParseExcel err:", err)
return nil, err
}
var result = make([]T, 0)
// _是索引号,从0开始,row是整行数据
for i, row := range rows {
if i == 0 {
continue
}
//for _, colCell := range row {
//
//}
array := tool.GenValByArray[T](row)
result = append(result, array)
}
return result, nil
}
上述方法是从Reader中读取excel文件流,并将其转化为结构体数组,该框架将excel解析为一个二维数组[][]的格式,需要将二维数组解析为一个结构体数组。tool.GenValByArray[T](row)该方法就是实现二维数组转化为结构体数组的。
- 二维数组转结构体数组
go
func GenValByArray[T any](p []string) T {
var param T
info := ReflectInfo(param)
structType := reflect.TypeOf(param)
// 使用反射创建结构体实例
structValue := reflect.New(structType).Elem()
for i, model := range info {
if i > len(p)-1 {
continue
}
// 设置结构体字段的值
field1 := structValue.FieldByName(model.Name)
if field1.IsValid() && field1.CanSet() {
if model.TypeName == "time.Time" {
// 判断是日期还是时间
// 注意时间的格式只支持`2024-10-10` 和`2024-10-10 12:30:00`两种。
if len(p[i]) == 10 {
field1.Set(reflect.ValueOf(StrToDate(p[i])))
} else if len(p[i]) == 19 {
field1.Set(reflect.ValueOf(StrToTime(p[i])))
} else {
field1.Set(reflect.ValueOf(StrToTime("")))
}
} else if model.TypeName == "int" {
field1.Set(reflect.ValueOf(StrInt(p[i])))
} else if model.TypeName == "int32" {
field1.Set(reflect.ValueOf(StrInt32(p[i])))
} else if model.TypeName == "float32" || model.TypeName == "float64" {
field1.Set(reflect.ValueOf(StrFloat64(p[i])))
} else if model.TypeName == "bool" {
field1.Set(reflect.ValueOf(StrBool(p[i])))
} else {
field1.Set(reflect.ValueOf(p[i]))
}
}
}
return structValue.Interface().(T)
}
type ReflectModel struct {
Name string
TypeName string
Value any
}
func ReflectInfo[T any](model T) []ReflectModel {
var result = make([]ReflectModel, 0)
getType := reflect.TypeOf(model)
for i := 0; i < getType.NumField(); i++ {
fieldType := getType.Field(i)
name := fieldType.Name
typeName := fieldType.Type.String()
value := reflect.ValueOf(model).Field(i)
if typeName == "time.Time" {
result = append(result, ReflectModel{name, typeName, TimeStandardStr(time.Now())})
} else {
result = append(result, ReflectModel{name, typeName, value.Interface()})
}
}
return result
}
上述方法将数组转化为一个结构体,T为目标结构体,p数组参数,需要注意的是结构体属性的个数一定要等于,参数数组的长度。
方法中对一些基本的数组类型做了处理,如时间excel中默认是2024/02/21,这是只解析标准的YYYY-MM-dd这种格式的,其他格式请额外添加处理逻辑,同时int, float等类型会转化为string类型。
这样最终就得到了T类型的结构体数组。有了结构体数组就可以入库了。