export GOPROXY=https://mirrors.aliyun.com/goproxy/
go get github.com/xuri/excelize/v2 # 读取 Excel
go get github.com/unidoc/unioffice/document # 处理 Word
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"sync"
"github.com/xuri/excelize/v2" // 读取 Excel
"github.com/unidoc/unioffice/document" // 处理 Word
)
func main() {
// 1. 读取 Excel 数据
excelPath := "data.xlsx"
rows, err := readExcel(excelPath)
if err != nil {
log.Fatalf("读取 Excel 失败: %v", err)
}
// 2. 加载 Word 模板
templatePath := "template.docx"
docTemplate, err := document.Open(templatePath)
if err != nil {
log.Fatalf("打开模板失败: %v", err)
}
// 3. 创建输出目录
outputDir := "output_docs"
if err := os.MkdirAll(outputDir, 0755); err != nil {
log.Fatalf("创建输出目录失败: %v", err)
}
// 4. 并发生成 Word 文档(Go 的 Goroutine 实现高并发)
var wg sync.WaitGroup
sem := make(chan struct{}, 10) // 控制并发数(最多 10 个 Goroutine)
for _, row := range rows {
wg.Add(1)
sem <- struct{}{} // 占用信号量,限制并发数
go func(id, content string) {
defer wg.Done()
defer func() { <-sem }() // 释放信号量
// 替换模板中的占位符
doc := docTemplate.Copy() // 复制模板(避免修改原模板)
replacePlaceholders(doc, id, content)
// 保存 Word 文件
filename := filepath.Join(outputDir, fmt.Sprintf("%s.docx", id))
if err := doc.Save(filename); err != nil {
log.Printf("保存 %s 失败: %v", filename, err)
return
}
log.Printf("生成成功: %s", filename)
}(row["ID"], row["Content"])
}
wg.Wait()
fmt.Println("所有文档生成完成!")
}
// readExcel 读取 Excel 数据,返回 map 列表(每行一个 map)
func readExcel(path string) ([]map[string]string, error) {
f, err := excelize.OpenFile(path)
if err != nil {
return nil, err
}
defer f.Close()
// 获取 Sheet1 的所有行
rows, err := f.GetRows("Sheet1")
if err != nil {
return nil, err
}
// 转换为 map 列表(键是列名,值是单元格内容)
var result []map[string]string
headers := rows[0] // 第一行是表头
for i := 1; i < len(rows); i++ {
row := rows[i]
data := make(map[string]string)
for j, header := range headers {
if j < len(row) {
data[header] = row[j]
}
}
result = append(result, data)
}
return result, nil
}
// replacePlaceholders 替换 Word 模板中的占位符
func replacePlaceholders(doc *document.Document, id, content string) {
// 遍历所有段落
for _, para := range doc.Paragraphs() {
// 遍历段落中的所有文本块(Run)
for _, run := range para.Runs() {
text := run.Text()
switch {
case strings.Contains(text, "{{ID}}"):
run.SetText(strings.ReplaceAll(text, "{{ID}}", id))
case strings.Contains(text, "{{Content}}"):
run.SetText(strings.ReplaceAll(text, "{{Content}}", content))
}
}
}
}