Go语言导入与导出excel文件

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(&param).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类型的结构体数组。有了结构体数组就可以入库了。

相关推荐
SilentSamsara1 小时前
高并发 API 压测与调优:locust + 火焰图 + 瓶颈定位
开发语言·python·青少年编程·docker·中间件
myenjoy_11 小时前
开源!Go+Wails+Vue3 手搓一个 PLC 实时监控桌面工具
开发语言·golang·开源
Flash.kkl1 小时前
C++基于websocketpp的多用户网页五子棋项目
开发语言·网络·数据库·c++·websocket·mysql
酉鬼女又兒1 小时前
零基础入门计算机网络物理层:核心概念、传输媒体、传输方式、编码调制与信道极限容量完整知识点总结
开发语言·网络·计算机网络·考研·职场和发展·php·信息与通信
曾几何时`2 小时前
Go(四)Channel
开发语言·后端·golang
未若君雅裁2 小时前
Java 线程基础:进程、线程、并发并行、创建方式与生命周期
java·开发语言
sugar__salt2 小时前
JS正则表达式与字符串高阶实战精讲
开发语言·javascript·正则表达式
AI浩2 小时前
梯度累积与 Micro-Batch 设计分层式精讲:有效批次、显存边界与分布式同步
开发语言·分布式·batch
未若君雅裁2 小时前
死锁产生条件与诊断:jps、jstack、VisualVM
java·开发语言