file_table_flush_data_table_service.go 9.1 KB
package domainService

import (
	"bytes"
	"fmt"
	"github.com/google/uuid"
	pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
	"github.com/linmadan/egglib-go/utils/tool_funs"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/dao"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
	"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
	"time"
)

type FlushDataTableService struct {
	FileId             int
	transactionContext *pgTransaction.TransactionContext
}

// Flush 保存表 【data-table】
func (ptr *FlushDataTableService) Flush(ctx *domain.Context, fileId int, table *domain.Table) (interface{}, error) {
	fileRepository, _ := repository.NewFileRepository(ptr.transactionContext)
	file, err := fileRepository.FindOne(map[string]interface{}{"fileId": fileId})
	if err != nil {
		return nil, fmt.Errorf("临时文件不存在")
	}
	sourceFile, err := fileRepository.FindOne(map[string]interface{}{"fileId": file.SourceFileId})
	if err != nil {
		return nil, fmt.Errorf("源文件不存在")
	}
	sourceFile.SetHeaderRow(table.HeaderRow)
	// New Table
	table = NewTable(domain.ExcelTable, file.FileInfo.Name, table.DataFields, table.RowCount).WithContext(ctx)
	// 通知底层保存、进行回调
	var response *domain.DataSaveTable
	if response, err = ByteCore.SaveTable(domain.ReqSaveTable{FileId: fileId, Table: table}); err != nil {
		return nil, err
	}
	// 来自源文件的
	// 临时文件 -》校验文件
	var newUrl string
	if response != nil {
		newUrl = domain.ConvertInternalFileUrlToPublic(response.Url)
	}
	log.Logger.Info("更新文件地址", map[string]interface{}{"from_url": file.FileInfo.Url, "to_url": newUrl, "sourceFileId": file.SourceFileId})
	switch sourceFile.FileType {
	case domain.SourceFile.ToString():
		if err = ptr.flushSourceFile(ctx, table, file, sourceFile, fileRepository, newUrl); err != nil {
			return nil, err
		}
	case domain.VerifiedFile.ToString():
		if err = ptr.flushVerifiedFile(ctx, table, file, sourceFile, fileRepository, newUrl); err != nil {
			return nil, err
		}
	}
	// 日志
	if err = FastLog(ptr.transactionContext, domain.CommonLog, file.FileId, &FileVerifyLog{
		LogEntry: domain.NewLogEntry(file.FileInfo.Name, domain.VerifiedFile.ToString(), domain.FileVerify, ctx),
		Total:    table.RowCount,
	}); err != nil {
		return nil, err
	}

	return struct{}{}, nil
}

func (ptr *FlushDataTableService) flushSourceFile(ctx *domain.Context, table *domain.Table, file *domain.File, sourceFile *domain.File, fileRepository domain.FileRepository, url string) error {
	var err error
	// 新增
	tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
	table, err = tableRepository.Save(table)
	if err != nil {
		return err
	}
	if _, err = fileRepository.Save(sourceFile); err != nil {
		return err
	}
	file.FileInfo.TableId = table.TableId
	file.FileType = domain.VerifiedFile.ToString()
	file.UpdateFileUrl(url).SetHeaderRow(sourceFile.FileInfo.HeaderRow)
	if file.FileFrom == domain.FileFromDigitalAppClient {
		file.SetContext(ctx)
	}
	if file, err = fileRepository.Save(file); err != nil {
		return err
	}
	_, files, err := fileRepository.Find(map[string]interface{}{"context": ctx, "equalFileName": sourceFile.FileInfo.Name, "fileType": domain.VerifiedFile.ToString(), "notInFileIds": []int{file.FileId}})
	if err != nil {
		return err
	}
	if len(files) > 0 {
		for _, existFile := range files {
			if err = FastLog(ptr.transactionContext, domain.CommonLog, file.FileId, &FileReplaceLog{
				LogEntry: domain.NewLogEntry(existFile.FileInfo.Name, domain.VerifiedFile.ToString(), domain.FileUpload, ctx),
			}); err != nil {
				return err
			}
		}
	}
	deleteFileService, _ := NewDeleteFileService(ptr.transactionContext)
	if err = deleteFileService.DeleteFiles(ctx, files...); err != nil {
		return err
	}
	return nil
}

func (ptr *FlushDataTableService) flushVerifiedFile(ctx *domain.Context, table *domain.Table, file *domain.File, sourceFile *domain.File, fileRepository domain.FileRepository, url string) error {
	var err error
	// 追加日志到校验文件
	if err = dao.ChangeStepLogOwner(ptr.transactionContext, file.FileId, sourceFile.FileId); err != nil {
		return err
	}
	sourceFile.UpdateFileUrl(url)
	if _, err = fileRepository.Save(sourceFile); err != nil {
		return err
	}
	// 删除中间文件
	if err = dao.FileDelete(ptr.transactionContext, file.FileId, domain.TemporaryFile); err != nil {
		return err
	}
	// 更新表信息
	tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
	sourceTable, err := tableRepository.FindOne(map[string]interface{}{"tableId": sourceFile.FileInfo.TableId})
	if err != nil {
		return err
	}
	sourceTable.DataFields = table.DataFields
	sourceTable.DataFieldIndex = table.DataFieldIndex
	sourceTable.UpdatedAt = time.Now()
	_, err = tableRepository.Save(sourceTable)
	if err != nil {
		return err
	}
	return nil
}

func NewFlushDataTableService(transactionContext *pgTransaction.TransactionContext) (*FlushDataTableService, error) {
	if transactionContext == nil {
		return nil, fmt.Errorf("transactionContext参数不能为nil")
	} else {
		return &FlushDataTableService{
			transactionContext: transactionContext,
		}, nil
	}
}

func NewTable(tableType domain.TableType, fileName string, dataFields []*domain.Field, rowCount int) *domain.Table {
	var table = &domain.Table{}
	// New Table
	table.TableType = tableType.ToString()
	table.Name = fileName
	table.SQLName = pin(fileName) //SQLTableName()
	table.PK = PK()
	if table.TableType == domain.CalculateTable.ToString() || table.TableType == domain.CalculateItem.ToString() {
		table.PK = nil
	}
	builder := NewDataFieldsBuilder()
	table.DataFieldIndex = len(dataFields)
	for _, field := range dataFields {
		//table.DataFields = append(table.DataFields, DataField(field.Name, field.SQLType, domain.MainTableField, i+1))
		table.DataFields = append(table.DataFields, builder.NewDataField(field.Name, field.SQLType, domain.MainTableField))
	}
	table.ManualFields = make([]*domain.Field, 0)
	table.CreatedAt = time.Now()
	table.UpdatedAt = time.Now()
	table.RowCount = rowCount
	table.TableInfo = domain.NewTableInfo()
	return table
}

func NewCopyTable(tableType domain.TableType, fileName string, dataFields []*domain.Field, rowCount int) *domain.Table {
	var table = &domain.Table{}
	table.TableType = tableType.ToString()
	table.Name = fileName
	table.SQLName = pin(fileName)
	table.PK = PK()
	if table.TableType == domain.CalculateItem.ToString() {
		table.PK = nil
	}
	table.DataFieldIndex = len(dataFields)
	table.DataFields = dataFields
	table.ManualFields = make([]*domain.Field, 0)
	table.CreatedAt = time.Now()
	table.UpdatedAt = time.Now()
	table.RowCount = rowCount
	table.TableInfo = domain.NewTableInfo()
	return table
}

func SQLTableName() string {
	id, _ := uuid.NewUUID()
	return id.String()
}

func PK() *domain.Field {
	return &domain.Field{
		Index:       0,
		Name:        "序号",
		SQLName:     "id",
		SQLType:     domain.String.ToString(),
		Description: "主键",
		Flag:        domain.PKField,
	}
}

func DataField(name string, sqlType string, flag int, index int) *domain.Field {
	return &domain.Field{
		Index:       index,
		Name:        name,
		SQLName:     fmt.Sprintf("%v_c%d", pin(name), index),
		SQLType:     sqlType,
		Description: "",
		Flag:        flag,
	}
}

type DataFieldsBuilder struct {
	mapPinFields map[string]int
}

func NewDataFieldsBuilder() *DataFieldsBuilder {
	builder := &DataFieldsBuilder{
		mapPinFields: make(map[string]int),
	}
	return builder
}

func (builder *DataFieldsBuilder) NewDataField(name string, sqlType string, flag int) *domain.Field {
	var index = 0
	pinName := pin(name)
	index = builder.AddPinItem(pinName)
	return &domain.Field{
		Index:       index,
		Name:        name,
		SQLName:     fmt.Sprintf("%v_%d", pinName, index),
		SQLType:     sqlType,
		Description: "",
		Flag:        flag,
	}
}

func (builder *DataFieldsBuilder) AddPinItem(pin string) int {
	var index = 1
	for {
		key := fmt.Sprintf("%v_%d", pin, index)
		if _, ok := builder.mapPinFields[key]; !ok {
			builder.mapPinFields[key] = index
			break
		}
		index++
	}
	return index
}

func pin(name string) string {
	pinyin := tool_funs.ToPinYin(name, "_")
	newPinyin := bytes.NewBuffer(nil)
	firstCharIsLetter := false
	for i := range pinyin {
		b := pinyin[i]
		if !firstCharIsLetter {
			firstCharIsLetter = isLetters(b)
		}
		if !firstCharIsLetter {
			continue
		}
		if !(isDigital(b) || isLetters(b) || b == byte('_')) {
			continue
		}
		newPinyin.WriteByte(b)
	}
	return newPinyin.String()
}

func pinFull(name string) string {
	prefixBuf := bytes.NewBuffer(nil)
	runes := []rune(name)
	for _, r := range runes {
		if r > 0xFF {
			if prefixBuf.Len() > 0 {
				prefixBuf.WriteString("_")
			}
			prefixBuf.WriteString(pin(string(r)))
			continue
		}
		//if isDigital(byte(r)) || isLetters(byte(r)){
		prefixBuf.WriteRune(r)
		//}
	}
	return prefixBuf.String()
}

func isDigital(b byte) bool {
	return b >= byte('0') && b <= byte('9')
}

func isLetters(b byte) bool {
	return (b >= byte('a') && b <= byte('z')) || (b >= byte('A') && b <= byte('Z'))
}