csvtostruct.go 8.77 KB
package main

import (
	"encoding/csv"
	"fmt"
	"github.com/axgle/mahonia"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"pro2d/csvdata"
	"reflect"
	"strconv"
	"strings"
	"unicode"
)

/**
 * 将excel中的前四列转化为struct
 * 第一列字段类型		如 int
 * 第二列字段名称		如 显示顺序
 * 第三列字段名		如 id
 * 第四列s,c,all 	s表示服务端使用 c表示客户端使用 all表示都使用
 */

var (
	lineNumber           = 4                                     // 每个工作表至少需要读取的行数
	structBegin          = "type %s struct {\n"                  // 结构体开始
	structValue          = "    %s %s	`json:\"%s\" client:\"%s\"`" // 结构体的内容
	structValueForServer = "    %s %s	`json:\"%s\"`"              // 服务端使用的结构体内容
	structRemarks        = "	 // %s"                             // 结构体备注
	structValueEnd       = "\n"                                  // 结构体内容结束
	structEnd            = "}\n"                                 // 结构体结束
	header               = "package %s\n\r"                      // 文件头

	initpkg				="package csvdata\n\nimport \"sync\"\n\nvar CsvStruct map[string]interface{}\nvar CsvData *sync.Map\n\nfunc init()  {\n\tCsvStruct = make(map[string]interface{})\n\tCsvData = new(sync.Map)\n\n\t%s\n}\n\nfunc GetCsv(key string) interface{}{\n\tif data, ok := CsvData.Load(key); ok {\n\t\treturn data\n\t}\n\treturn nil\n}\nfunc SetCsv(key, value interface{}) {\n\tCsvData.Store(key, value)\n}"
	initline 			= "CsvStruct[\"%s\"] = %s{}\n"
)

type Generate struct {
	savePath string // 生成文件的保存路径
	data     string // 生成文件的内容
	allType  string // 文件当中的数据类型
}

func NewGenerate(savePath, allType string) *Generate {
	return &Generate{
		savePath: savePath,
		allType:  allType,
	}
}

// GBK 转 UTF-8
func GbkToUtf8(s string) string {
	decoder := mahonia.NewDecoder("GBK")
	return decoder.ConvertString(s)
}

// UTF-8 转 GBK
func Utf8ToGbk(s string) string {
	decoder := mahonia.NewDecoder("UTF-8")
	return decoder.ConvertString(s)
}

func GetGoTByCustomerT(t string) string {
	switch t {
	case "IntSlice":
		return "[]int"
	case "StringSlice":
		return "[]string"
	case "FloatSlice":
		return "[]float32"
	default:
		return t
	}
}

func CustomerTypeToGo(allType string) map[string]bool {
	s := strings.Split(allType, ",")
	mt := make(map[string]bool)
	for _, t := range s {
		mt[GetGoTByCustomerT(t)] = true
	}
	return mt
}

//csv to struct
func (g *Generate) CsvToStruct(csvpath, filename string) error {
	csvfile, err := os.Open(csvpath+filename)
	if err != nil {
		return fmt.Errorf("ReadExcel|xlsx.OpenFile is err :%v", err)
	}
	defer csvfile.Close()

	r := csv.NewReader(csvfile)
	i := 0
	sheetData := make([][]string, 0)
	for {
		if i >= lineNumber {
			break
		}
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}
		i++
		sheetData = append(sheetData, record)
	}
	name := strings.Replace(filename, ".csv", "", -1)
	data, err := g.SplicingData(sheetData, name)
	if err != nil {
		return fmt.Errorf("fileName:\"%v\" is err:%v", name, err)
	}

	if data == "" {
		return fmt.Errorf("ReadExcel|this.data is nil")
	}
	err = g.WriteNewFile(name, data)
	if err != nil {
		return err
	}

	return nil
}

//csv to mem
func (g *Generate)CsvToMem(csvpath, filename string) error {
	csvfile, err := os.Open(csvpath+filename)
	if err != nil {
		return fmt.Errorf("ReadExcel|xlsx.OpenFile is err :%v", err)
	}
	defer csvfile.Close()

	r := csv.NewReader(csvfile)
	i := 0
	sheetData := make([][]string, 0)
	name := strings.Replace(filename, ".csv", "", -1)
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(err)
		}

		if i < lineNumber {
			sheetData = append(sheetData, record)
		}else {
			//4行之后
			fields := sheetData[1]
			typs := sheetData[2] //int, string
			keys := sheetData[3] //all, key, key1, key2

			t := reflect.ValueOf(csvdata.CsvStruct[name]).Type()
			v := reflect.New(t).Elem()
			for idx, field := range fields {
				if field == "" || record[idx] == "" {
					continue
				}

				f :=  v.FieldByName(firstRuneToUpper(field))
				switch typs[idx] {
				case "int":
					id, err := strconv.Atoi(record[idx])
					if err != nil {
						return err
					}
					f.SetInt(int64(id))
				case "string":
					f.SetString(record[idx])
				}

				switch keys[idx] {
				case "all":
				case "key":
				case "key1":

				case "key2":
				default:
				}

				//fmt.Printf("%d, %s: %v, %v\n", idx,  firstRuneToUpper(field), v.Field(idx), v.Interface())
			}
			//csvdata.SetCsv(name, datam)
		}
		i++
	}
	return nil
}

//拼装struct
func (g *Generate) SplicingData(data [][]string, structName string) (string, error) {
	if len(data) != lineNumber {
		return "", fmt.Errorf("SplicingData|sheetName:%v col's len:%d is err", data, len(data))
	}
	marks := data[0]
	fields := data[1]
	typs := data[2]
	keys := data[3]
	structData := fmt.Sprintf(structBegin, firstRuneToUpper(structName))

	for i, field := range fields {
		err := g.CheckType(typs[i], structName)
		if err != nil {
			return "", err
		}
		switch keys[i] {
		case "key","all":
			structData += fmt.Sprintf(structValue, firstRuneToUpper(field), GetGoTByCustomerT(typs[i]), strings.ToLower(field), strings.ToLower(field))
			if field != "" {
				structData += fmt.Sprintf(structRemarks, GbkToUtf8(marks[i]))
			}
			structData += fmt.Sprintf(structValueEnd)
		case "s":
			structData += fmt.Sprintf(structValueForServer, firstRuneToUpper(field), GetGoTByCustomerT(typs[i]), strings.ToLower(field))
			if field != "" {
				structData += fmt.Sprintf(structRemarks,GbkToUtf8(marks[i]))
			}
			structData += fmt.Sprintf(structValueEnd)
		case "c":
			continue
		default:
			return "", fmt.Errorf("SplicingData|keys[%d]:\"%v\" is not in s,c,all", i, keys[i])
		}
	}
	structData += structEnd
	return structData, nil
}

// 拼装好的struct写入新的文件
func (g *Generate) WriteNewFile(filename, data string) error {
	str := strings.Split(g.savePath, "/")
	if len(str) == 0 {
		return fmt.Errorf("WriteNewFile|len(str) is 0")
	}

	header = fmt.Sprintf(header, str[len(str)-1])
	data = header + data

	fw, err := os.OpenFile(g.savePath +"/" +filename+".go", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("WriteNewFile|OpenFile is err:%v", err)
	}
	defer fw.Close()

	_, err = fw.Write([]byte(data))
	if err != nil {
		return fmt.Errorf("WriteNewFile|Write is err:%v", err)
	}

	return nil
}

// 拼装好的初始化写入init.go文件
func (g *Generate) WriteInitPkg(data string) error {
	str := strings.Split(g.savePath, "/")
	if len(str) == 0 {
		return fmt.Errorf("WriteNewFile|len(str) is 0")
	}

	fw, err := os.OpenFile(g.savePath +"/init.go", os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("WriteNewFile|OpenFile is err:%v", err)
	}
	defer fw.Close()

	_, err = fw.Write([]byte(fmt.Sprintf(initpkg, data)))
	if err != nil {
		return fmt.Errorf("WriteNewFile|Write is err:%v", err)
	}

	return nil
}

// 检测解析出来的字段类型是否符合要求
func (g *Generate) CheckType(dataType, structName string) error {
	res := strings.Index(g.allType, dataType)
	if res == -1 {
		return fmt.Errorf("CheckType|struct:\"%v\" dataType:\"%v\" is not in provide dataType", structName, dataType)
	}
	return nil
}

// 字符串首字母转换成大写
func firstRuneToUpper(str string) string {
	data := []byte(str)
	for k, v := range data {
		if k == 0 {
			first := []byte(strings.ToUpper(string(v)))
			newData := data[1:]
			data = append(first, newData...)
			break
		}
	}
	return string(data[:])
}

// 判断是否存在汉字或者是否为默认的工作表
func hasChineseOrDefault(r string) bool {
	if strings.Index(r, "Sheet") != -1 {
		return true
	}
	for _, v := range []rune(r) {
		if unicode.Is(unicode.Han, v) {
			return true
		}
	}
	return false
}

// 读取Csv 转化为 struct
func (g *Generate) ReadCsvToStruct(readPath string) error {
	files, err := ioutil.ReadDir(readPath)
	if err != nil {
		return fmt.Errorf("ReadExcel|ReadDir is err:%v", err)
	}


	data := ""
	for _, file := range files {
		if path.Ext(file.Name()) != ".csv" || hasChineseOrDefault(file.Name()) {
			continue
		}
		err := g.CsvToStruct(readPath, file.Name())
		if err != nil {
			return err
		}

		name := strings.Replace(file.Name(), ".csv", "", -1)
		data += fmt.Sprintf(initline, name, firstRuneToUpper(name))
	}
	g.WriteInitPkg(data)
	return nil
}

// 读取Csv 转化为 memory数据
func (g *Generate) ReadCsvToMemory(readPath string) error {
	files, err := ioutil.ReadDir(readPath)
	if err != nil {
		return fmt.Errorf("ReadExcel|ReadDir is err:%v", err)
	}

	for _, file := range files {
		if path.Ext(file.Name()) != ".csv" || hasChineseOrDefault(file.Name()) {
			continue
		}
		err := g.CsvToMem(readPath, file.Name())
		if err != nil {
			return err
		}

	}
	return nil
}