使用go语言来完成复杂excel表的导出导入

使用go语言来完成复杂excel表的导出导入(一)

1.复杂表的导入

   开发需求是需要在功能页面上开发一个excel文件的导入导出功能,
   这里的复杂指定是表内数据夹杂着一对多,多对一的形式,如下图所示。数据杂乱而且对应不统一。

在这里插入图片描述
首先我们先设计一个页面处理器,也就是一个前端页面用来上传要处理的excel文件,注意这里应该是可以处理多个Sheet,代码如下图。

func UploadPage(w http.ResponseWriter, r *http.Request) {
	if r.Method == http.MethodGet {
		// 渲染上传页面
		tmpl := template.Must(template.ParseFiles(filepath.Join("template", "index.html")))
		err := tmpl.Execute(w, nil)
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to render template: %v", err), http.StatusInternalServerError)
		}
	}
}

这段代码的主要作用就是用户在前端点击按钮之后,接收一个excel文件。接收文件之后,就进行处理。
首先就是先从上传的excel文件中获取所有的Sheet,然后再进行数据库的连接,
根据表的特性设计代码提取每一列的数据插入到数据库中,在这里为了确保插入时表的完整性需要使用数据库的事务,我还在这里添加了检查数据是否重复的报错功能,来确保数据行列中不会出现重复数据。代码如下,

func ImportData(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
		return
	}

	// 从前端上传文件
	file, _, err := r.FormFile("excelFile")
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to get form file: %v", err), http.StatusInternalServerError)
		return
	}
	defer file.Close()

	// 打开 Excel 文件
	f, err := excelize.OpenReader(file)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to open Excel file: %v", err), http.StatusInternalServerError)
		return
	}

	// 获取所有工作表名称
	sheetNames := f.GetSheetMap()
	if len(sheetNames) == 0 {
		http.Error(w, "No sheets found in Excel file", http.StatusInternalServerError)
		return
	}

	// 连接 MySQL 数据库
	dsn := "root:root@tcp(127.0.0.1:3306)/database_4"
	db, err := sql.Open("mysql", dsn)
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to connect to database: %v", err), http.StatusInternalServerError)
		return
	}
	defer db.Close()

	// 确保连接有效
	if err := db.Ping(); err != nil {
		http.Error(w, fmt.Sprintf("Failed to ping database: %v", err), http.StatusInternalServerError)
		return
	}

	for _, sheetName := range sheetNames {
		// 开始事务
		tx, err := db.Begin()
		if err != nil {
			http.Error(w, fmt.Sprintf("Failed to begin transaction: %v", err), http.StatusInternalServerError)
			return
		}

		// 读取工作表
		rows := f.GetRows(sheetName)
		if len(rows) == 0 {
			http.Error(w, fmt.Sprintf("No rows found in sheet: %v", sheetName), http.StatusInternalServerError)
			tx.Rollback()
			continue
		}

		// 使用 map 分别检查 SubSpanNumber 和 BeamNumber 的重复
		subSpanNumberSet := make(map[string]int)
		beamNumberSet := make(map[string]int)

		// 变量来跟踪当前的 spanNumber 和 beamNumber
		var currentSpanNumber string
		var currentBeamNumber string

		// 跳过前两行
		for i, row := range rows[2:] {
			// 跳过空行
			if len(row) == 0 {
				continue
			}

			// 检查行的长度是否足够go
			if len(row) < 17 {
				log.Printf("Row %d does not have enough columns: %v", i+2, row)
				continue
			}

			// 提取每一列的数据
			id := row[0]
			bridgeName := row[1]
			centerPileNumber := row[2]
			beamType := row[3]
			subSpanNumber := row[4]
			spanNumber := row[5]
			beamNumber := row[6]
			designBeamLength, _ := strconv.ParseFloat(row[7], 64)
			designBeamHeight, _ := strconv.ParseFloat(row[8], 64)
			topPlateWidth, _ := strconv.ParseFloat(row[9], 64)
			bottomPlateWidth, _ := strconv.ParseFloat(row[10], 64)
			webThickness, _ := strconv.ParseFloat(row[11], 64)
			flangeThickness, _ := strconv.ParseFloat(row[12], 64)
			camber := row[13]
			expansionJointType := row[14]
			concreteStrength := row[15]
			concreteUsage, _ := strconv.ParseFloat(row[16], 64)

			// 如果 spanNumber 变化,重置 subSpanNumberSet
			if spanNumber != currentSpanNumber {
				subSpanNumberSet = make(map[string]int)
				currentSpanNumber = spanNumber
			}

			// 如果 beamNumber 变化,重置 beamNumberSet
			if beamNumber != currentBeamNumber {
				beamNumberSet = make(map[string]int)
				currentBeamNumber = beamNumber
			}

			// 跳过 spanNumber 为空格的检查,但仍保留其他数据插入
			if spanNumber != "" {
				// 检查 spanNumber 是否重复
				if _, found := subSpanNumberSet[spanNumber]; found {
					http.Error(w, fmt.Sprintf("在 %v 表 联跨编号列 第%d行出现数据重复: %v", sheetName, i+3, subSpanNumber), http.StatusInternalServerError)
					tx.Rollback()
					return
				}
				subSpanNumberSet[spanNumber] = i + 3 // 保存行号以便于调试
			}

			// 检查 BeamNumber 是否重复
			if _, found := beamNumberSet[beamNumber]; found {
				http.Error(w, fmt.Sprintf("在 %v 表 梁体编号列 第%d行出现数据重复: %v", sheetName, i+3, beamNumber), http.StatusInternalServerError)
				tx.Rollback()
				return
			}
			beamNumberSet[beamNumber] = i + 3 // 保存行号以便于调试

			// 插入数据到数据库
			query := `
            INSERT INTO bridge_data (
                id, bridge_name, center_pile_number, beam_type, sub_span_number, span_number, beam_number,
                design_beam_length, design_beam_height, top_plate_width, bottom_plate_width, web_thickness,
                flange_thickness, camber, expansion_joint_type, concrete_strength, concrete_usage, filename
            ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
            `
			_, err = tx.Exec(query, id, bridgeName, centerPileNumber, beamType, subSpanNumber, spanNumber, beamNumber,
				designBeamLength, designBeamHeight, topPlateWidth, bottomPlateWidth, webThickness,
				flangeThickness, camber, expansionJointType, concreteStrength, concreteUsage, sheetName)
			if err != nil {
				log.Printf("Failed to insert row %d: %v", i+3, err)
				http.Error(w, fmt.Sprintf("Failed to insert row %d: %v", i+3, err), http.StatusInternalServerError)
				tx.Rollback()
				return
			}
		}

		// 插入成功后,添加标志行
		currentTime := time.Now().Format(time.RFC3339)
		fileName := sheetName
		status := "success"

		markQuery := `
        INSERT INTO process_mark (
            timestamp, filename, status
        ) VALUES (?, ?, ?)
        `
		_, err = tx.Exec(markQuery, currentTime, fileName, status)
		if err != nil {
			log.Printf("Failed to insert process mark for sheet %s: %v", sheetName, err)
			http.Error(w, fmt.Sprintf("Failed to insert process mark for sheet %s: %v", sheetName, err), http.StatusInternalServerError)
			tx.Rollback()
			return
		}

		// 提交事务
		if err := tx.Commit(); err != nil {
			http.Error(w, fmt.Sprintf("Failed to commit transaction: %v", err), http.StatusInternalServerError)
			return
		}
	}

	fmt.Fprintln(w, "excel数据导入成功")
}

处理完成之后的效果就是(部分效果图)
在这里插入图片描述
**

  • 10
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
Hutool的ExcelUtil提供了非常方便的导入导出Excel的方法,可以处理复杂头的情况。具体步骤如下: 1. 导入ExcelUtil类和相关依赖: ```java import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; ``` 2. 创建一个ExcelWriter对象,并指定输出路径: ```java ExcelWriter writer = ExcelUtil.getWriter("D:/test.xlsx"); ``` 3. 定义头信息,使用链式语法设置头: ```java writer.merge(0, 0, 0, 3, "学生信息"); writer.merge(1, 1, 0, 1, "基本信息"); writer.merge(1, 1, 2, 3, "联系方式"); writer.writeRow(Arrays.asList("姓名", "年龄", "电话", "邮箱")); ``` 4. 写入数据: ```java List<Student> list = new ArrayList<>(); list.add(new Student("张三", 18, "12345678901", "zhangsan@qq.com")); list.add(new Student("李四", 20, "23456789012", "lisi@qq.com")); writer.write(list, true); ``` 5. 关闭ExcelWriter对象: ```java writer.close(); ``` 完整代码示例: ```java import cn.hutool.poi.excel.ExcelUtil; import cn.hutool.poi.excel.ExcelWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; public class Test { public static void main(String[] args) { ExcelWriter writer = ExcelUtil.getWriter("D:/test.xlsx"); writer.merge(0, 0, 0, 3, "学生信息"); writer.merge(1, 1, 0, 1, "基本信息"); writer.merge(1, 1, 2, 3, "联系方式"); writer.writeRow(Arrays.asList("姓名", "年龄", "电话", "邮箱")); List<Student> list = new ArrayList<>(); list.add(new Student("张三", 18, "12345678901", "zhangsan@qq.com")); list.add(new Student("李四", 20, "23456789012", "lisi@qq.com")); writer.write(list, true); writer.close(); } } class Student { private String name; private int age; private String phone; private String email; public Student(String name, int age, String phone, String email) { this.name = name; this.age = age; this.phone = phone; this.email = email; } // getter、setter方法省略 } ``` 以上代码中,我们通过ExcelWriter对象的merge方法来设置头的合并单元格,使用writeRow方法写入头数据,使用write方法写入实际数据,最后关闭ExcelWriter对象即可。 注意:代码中的Student类是自定义的,可以根据实际情况修改。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值