作者 yangfu

feat: table preview

... ... @@ -69,10 +69,12 @@
"data": {
"dataFields": [
{
"index": 1,
"name": "产品名称",
"type": "string"
},
{
"index": 2,
"name": "产品数量",
"type": "int"
}
... ... @@ -118,7 +120,8 @@
- [x] 匹配方案主表 /mapping-rule-config/prepare //主表 校验表 主表字段 校验文件表字段
- [x] 匹配方案添加 /mapping-rule-config/
- [x] 匹配方案删除 /mapping-rule-config/:id
- [ ] 追加数据到表格 /append-data-to-table // 验证是否追加过
- [x] 追加数据到表格 /append-data-to-table // 验证是否追加过
- [ ] 取消校验中的文件 /cancel-verifying-file //
- [x] 表结构更新 /tables/update-table-struct
- [x] 表结构添加 /tables/add-table-struct
... ... @@ -140,4 +143,26 @@
## 数据验证
- [ ] 文件验证 /data/edit-data-table
\ No newline at end of file
- [ ] 文件验证 /data/edit-data-table
## 底层字库接口
- [ ] 表格编辑
```json
{
"file": {},
"fields": [],
"action":"filed_rename",
"params": ["产品名2"]
}
```
- [ ] 保存校验文件 (文件地址)
- [ ] 生成主表
- [ ] 表复制
- [ ] 追加数据
- [ ] 表删除 (主表、副表、分表)
- [ ] 表拆分
- [ ] 更新表结构(分表)
- [ ] 编辑、添加、删除表数据(副表)
\ No newline at end of file
... ...
... ... @@ -3,36 +3,52 @@ module gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion
go 1.16
require (
github.com/Shopify/sarama v1.30.0 // indirect
github.com/ajg/form v1.5.1 // indirect
github.com/aswjh/excel v0.0.0-20190302031512-353c59e41f09
github.com/beego/beego/v2 v2.0.1
github.com/bwmarrin/snowflake v0.3.0
github.com/extrame/xls v0.0.1
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/gavv/httpexpect v2.0.0+incompatible
github.com/go-pg/pg/v10 v10.9.0
github.com/go-ole/go-ole v1.2.4 // indirect
github.com/go-pg/pg/v10 v10.10.6
github.com/go-redis/redis v6.15.9+incompatible
github.com/golang/snappy v0.0.3 // indirect
github.com/google/go-cmp v0.5.6 // indirect
github.com/google/go-cmp v0.5.7 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/uuid v1.3.0
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/linmadan/egglib-go v0.0.0-20210313060205-8b5e456b11f7
github.com/mattn/go-colorable v0.1.9 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect
github.com/moul/http2curl v1.0.0 // indirect
github.com/onsi/ginkgo v1.15.2
github.com/onsi/gomega v1.11.0
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.18.1
github.com/prometheus/client_golang v1.12.2 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.2.0
github.com/shopspring/decimal v1.3.1
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/stretchr/testify v1.7.0
github.com/stretchr/testify v1.7.1
github.com/valyala/fasthttp v1.38.0 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xuri/excelize/v2 v2.6.0
github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 // indirect
github.com/yudai/gojsondiff v1.0.0 // indirect
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
github.com/yudai/pp v2.0.1+incompatible // indirect
go.uber.org/automaxprocs v1.5.1 // indirect
golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect
golang.org/x/sys v0.0.0-20220429233432-b5fbb4746d32 // indirect
golang.org/x/text v0.3.7
golang.org/x/tools v0.1.5 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gorm.io/driver/mysql v1.3.6
gorm.io/gorm v1.23.8
)
replace github.com/linmadan/egglib-go v0.0.0-20210313060205-8b5e456b11f7 => github.com/tiptok/egglib-go v0.0.0-20220421085958-9682d0ac42c1
replace (
github.com/extrame/xls v0.0.1 => github.com/tiptok/xls v1.0.1
github.com/linmadan/egglib-go v0.0.0-20210313060205-8b5e456b11f7 => github.com/tiptok/egglib-go v0.0.0-20220421085958-9682d0ac42c1
)
... ...
... ... @@ -4,6 +4,7 @@ import (
"github.com/linmadan/egglib-go/core/application"
"github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain/bytecore"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/domainService"
)
... ... @@ -16,6 +17,10 @@ func CreateLoadDataTableService(transactionContext application.TransactionContex
return domainService.NewLoadDataTableService(transactionContext.(*pg.TransactionContext))
}
func CreateEditDataTableService(transactionContext application.TransactionContext) (domain.EditDataTableService, error) {
return domainService.NewEditDataTableService(transactionContext.(*pg.TransactionContext))
}
func CreateFlushDataTableService(transactionContext application.TransactionContext) (domain.FlushDataTableService, error) {
return domainService.NewFlushDataTableService(transactionContext.(*pg.TransactionContext))
}
... ... @@ -43,3 +48,12 @@ func CreateUpdateTableStructService(transactionContext application.TransactionCo
func CreateAddTableStructService(transactionContext application.TransactionContext) (domain.AddTableStructService, error) {
return domainService.NewAddTableStructService(transactionContext.(*pg.TransactionContext))
}
func CreateAppendDataToTableService(transactionContext application.TransactionContext) (domain.AppendDataToTableService, error) {
return domainService.NewAppendDataToTableService(transactionContext.(*pg.TransactionContext))
}
// 字库核心
func CreateByteCoreService(transactionContext application.TransactionContext) (bytecore.ByteLibService, error) {
return domainService.ByteCore, nil
}
... ...
package command
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"reflect"
"strings"
"github.com/beego/beego/v2/core/validation"
)
type AppendDataToTableCommand struct {
// 文件ID
FileId int `cname:"文件ID" json:"fileId" valid:"Required"`
// 文件ID
TableId int `cname:"表ID" json:"tableId" valid:"Required"`
// 校验文件列
MappingFields []*domain.MappingField `json:"mappingFields"`
}
func (cmd *AppendDataToTableCommand) Valid(validation *validation.Validation) {
}
func (cmd *AppendDataToTableCommand) ValidateCommand() error {
valid := validation.Validation{}
b, err := valid.Valid(cmd)
if err != nil {
return err
}
if !b {
elem := reflect.TypeOf(cmd).Elem()
for _, validErr := range valid.Errors {
field, isExist := elem.FieldByName(validErr.Field)
if isExist {
return fmt.Errorf(strings.Replace(validErr.Message, validErr.Field, field.Tag.Get("cname"), -1))
} else {
return fmt.Errorf(validErr.Message)
}
}
}
return nil
}
... ...
... ... @@ -2,6 +2,7 @@ package command
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"reflect"
"strings"
... ... @@ -11,6 +12,8 @@ import (
type EditDataTableCommand struct {
// 文件ID
FileId int `cname:"文件ID" json:"fileId" valid:"Required"`
Fields []*domain.Field
}
func (editDataTableCommand *EditDataTableCommand) Valid(validation *validation.Validation) {
... ...
... ... @@ -2,6 +2,7 @@ package command
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"reflect"
"strings"
... ... @@ -12,13 +13,19 @@ type LoadDataTableCommand struct {
// 文件ID
FileId int `cname:"文件ID" json:"fileId" valid:"Required"`
// 页号
PageNumber int `cname:"页号" json:"pageNumber"`
//PageNumber int `cname:"页号" json:"pageNumber"`
// 页号
PageSize int `cname:"数量" json:"pageSize"`
//PageSize int `cname:"数量" json:"pageSize"`
domain.Where
}
func (loadDataTableCommand *LoadDataTableCommand) Valid(validation *validation.Validation) {
if loadDataTableCommand.PageNumber == 0 {
loadDataTableCommand.PageNumber = 1
}
if loadDataTableCommand.PageSize == 0 {
loadDataTableCommand.PageSize = 20
}
}
func (loadDataTableCommand *LoadDataTableCommand) ValidateCommand() error {
... ...
... ... @@ -4,7 +4,6 @@ import (
"github.com/linmadan/egglib-go/core/application"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/dto"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
)
... ... @@ -25,7 +24,8 @@ func (fileService *FileService) LoadDataTable(ctx *domain.Context, loadDataTable
}()
loadDataTableService, _ := factory.CreateLoadDataTableService(transactionContext)
if _, err := loadDataTableService.Load(ctx, loadDataTableCommand.FileId); err != nil {
data, err := loadDataTableService.Load(ctx, loadDataTableCommand.FileId, loadDataTableCommand.Where)
if err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
... ... @@ -33,7 +33,8 @@ func (fileService *FileService) LoadDataTable(ctx *domain.Context, loadDataTable
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return dto.NewDataTableDtoDemo(loadDataTableService.GetFileId()), nil
//return dto.NewDataTableDtoDemo(loadDataTableService.GetFileId()), nil
return data, nil
}
// 编辑表格数据
... ... @@ -51,6 +52,7 @@ func (fileService *FileService) EditDataTable(ctx *domain.Context, editDataTable
defer func() {
transactionContext.RollbackTransaction()
}()
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
... ... @@ -118,3 +120,30 @@ func (fileService *FileService) GenerateMainTable(ctx *domain.Context, generateM
}
return struct{}{}, nil
}
// 生成主表
func (fileService *FileService) AppendDataToTable(ctx *domain.Context, cmd *command.AppendDataToTableCommand) (interface{}, error) {
if err := cmd.ValidateCommand(); err != nil {
return nil, application.ThrowError(application.ARG_ERROR, err.Error())
}
transactionContext, err := factory.CreateTransactionContext(nil)
if err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
if err := transactionContext.StartTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
defer func() {
transactionContext.RollbackTransaction()
}()
generateMainTableService, _ := factory.CreateAppendDataToTableService(transactionContext)
result, err := generateMainTableService.AppendData(ctx, cmd.FileId, cmd.TableId, cmd.MappingFields)
if err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return result, nil
}
... ...
... ... @@ -348,6 +348,31 @@ func (tableService *TableService) AddTableStruct(ctx *domain.Context, cmd *comma
return struct{}{}, nil
}
func (tableService *TableService) PreviewDataTable(ctx *domain.Context, cmd *command.AddTableStructCommand) (interface{}, error) {
if err := cmd.ValidateCommand(); err != nil {
return nil, application.ThrowError(application.ARG_ERROR, err.Error())
}
transactionContext, err := factory.CreateTransactionContext(nil)
if err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
if err := transactionContext.StartTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
defer func() {
transactionContext.RollbackTransaction()
}()
AddTableStructService, _ := factory.CreateAddTableStructService(transactionContext)
if _, err := AddTableStructService.AddTableStruct(ctx, cmd.TableId, cmd.Fields, cmd.Name); err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return struct{}{}, nil
}
func NewTableService(options map[string]interface{}) *TableService {
newTableService := &TableService{}
return newTableService
... ...
package bytecore
import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
type ByteLibService interface {
LoadDataTable(param ReqLoadDataTable) (*DataLoadDataTable, error)
EditTable(param ReqEditDataTable) (*DataEditDataTable, error)
}
type (
ReqLoadDataTable struct {
FileId int
FileName string
Url string
Ext string
domain.Where
}
DataLoadDataTable struct {
FileId int `json:"fileId"`
DataFields []*Field `json:"dataFields"`
DataRows [][]string `json:"dataRows"`
Total int `json:"total"`
PageNumber int `json:"pageNumber"`
InValidCells []InValidCell `json:"inValidCells"`
}
Field struct {
// 索引序号
Index int `json:"index"`
// 名称
Name string `json:"name"`
// 对应数据库类型
Type string `json:"type"`
}
InValidCell struct {
X int `json:"x"`
Y int `json:"y"`
Error string `json:"error"`
}
)
// https://github.com/go-gota/gota 类似pandas的数据处理包
// https://github.com/gonum/gonum
func (table DataLoadDataTable) Filter(where domain.Where) *DataLoadDataTable {
begin := (where.PageNumber - 1) * where.PageSize
if begin < 0 {
begin = 0
}
end := begin + where.PageSize
data := make([][]string, 0)
if begin < table.Total {
if end < table.Total {
data = table.DataRows[begin:end]
} else {
data = table.DataRows[begin:]
}
}
return &DataLoadDataTable{
FileId: table.FileId,
DataFields: table.DataFields,
DataRows: data,
Total: table.Total,
PageNumber: where.PageNumber,
InValidCells: make([]InValidCell, 0),
}
}
type (
ReqEditDataTable struct {
FileId int
FileName string
Url string
Ext string
Fields []*Field
Action string
Params []interface{}
}
DataEditDataTable struct {
DataLoadDataTable
}
)
... ...
... ... @@ -5,3 +5,8 @@ type DataTable struct {
Data [][]string
Total int
}
type Where struct {
PageNumber int `json:"pageNumber"`
PageSize int `json:"pageSize"`
}
... ...
package domain
type LoadDataTableService interface {
Load(ctx *Context, fileId int) (interface{}, error)
Load(ctx *Context, fileId int, where Where) (interface{}, error)
GetFileId() int
}
type EditDataTableService interface {
}
type FlushDataTableService interface {
Flush(ctx *Context, fileId int, table *Table) (interface{}, error)
}
... ... @@ -33,3 +36,7 @@ type UpdateTableStructService interface {
type AddTableStructService interface {
AddTableStruct(ctx *Context, parentTableId int, fields []*Field, name string) (interface{}, error)
}
type AppendDataToTableService interface {
AppendData(ctx *Context, fileId int, tableId int, mappingFields []*MappingField) (interface{}, error)
}
... ...
package api
import (
rawjson "encoding/json"
"github.com/linmadan/egglib-go/utils/json"
"time"
"github.com/beego/beego/v2/client/httplib"
)
type MessageCode struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
//Response 统一消息返回格式
type Response struct {
MessageCode
Data rawjson.RawMessage `json:"data"`
}
type BaseServiceGateway struct {
ConnectTimeout time.Duration
ReadWriteTimeout time.Duration
host string
}
type Request struct {
Url string
Method string
Param interface{}
}
func (gateway BaseServiceGateway) CreateRequest(url string, method string) *httplib.BeegoHTTPRequest {
var request *httplib.BeegoHTTPRequest
switch method {
case "get", "GET":
request = httplib.Get(url)
case "post", "POST":
request = httplib.Post(url)
case "put", "PUT":
request = httplib.Put(url)
case "delete", "DELETE":
request = httplib.Delete(url)
case "head", "HEADER":
request = httplib.Head(url)
default:
request = httplib.Get(url)
}
return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout)
}
func (gateway BaseServiceGateway) GetResponseData(result Response, data interface{}) error {
err := json.Unmarshal(result.Data, data)
if err != nil {
return NewErrCodeMsg(JsonUnMarshError, err.Error())
}
return nil
}
func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interface{}, data interface{}) error {
err := gateway.DoRequest(Request{
Url: url,
Method: method,
Param: param,
}, &data)
if err != nil {
return err
}
return nil
}
func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}) error {
r := gateway.CreateRequest(requestParam.Url, requestParam.Method)
req, err := r.JSONBody(requestParam.Param)
if err != nil {
return err
}
byteResult, err := req.Bytes()
if err != nil {
return err
}
var result Response
err = json.Unmarshal(byteResult, &result)
if err != nil {
return err
}
if result.Code != 0 && len(result.Msg) > 0 {
return NewErrCodeMsg(result.Code, result.Msg)
}
return gateway.GetResponseData(result, val)
}
func (gateway BaseServiceGateway) Host() string {
return gateway.host
}
func NewBaseServiceGateway(host string) BaseServiceGateway {
return BaseServiceGateway{
host: host,
}
}
... ...
package bytelib
import (
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain/bytecore"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api"
"time"
)
// ApiByteLib 字库底层接口
type ApiByteLib struct {
api.BaseServiceGateway
baseUrL string
}
func NewApiByteLib(host string) *ApiByteLib {
gt := api.NewBaseServiceGateway(host)
gt.ConnectTimeout = 10 * time.Second
gt.ReadWriteTimeout = 10 * time.Second
return &ApiByteLib{
BaseServiceGateway: gt,
}
}
// 加载数据
func (gateway ApiByteLib) LoadDataTable(param bytecore.ReqLoadDataTable) (*bytecore.DataLoadDataTable, error) {
return nil, nil
}
// EditTable 编辑表格
func (gateway ApiByteLib) EditTable(param bytecore.ReqEditDataTable) (*bytecore.DataEditDataTable, error) {
url := gateway.Host() + "/table/edit"
method := "post"
var data bytecore.DataEditDataTable
err := gateway.FastDoRequest(url, method, param, &data)
if err != nil {
return nil, err
}
return &data, nil
}
// 保存校验文件 (文件地址)
// 生成主表
// 表复制
// 追加数据
// 表删除 (主表、副表、分表)
// 表拆分
// 更新表结构(分表)
// 编辑、添加、删除表数据(副表)
... ...
package bytelib
import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain/bytecore"
type ApiByteLibService interface {
EditTable(param bytecore.ReqEditDataTable) (*bytecore.DataEditDataTable, error)
}
... ...
package api
var (
JsonUnMarshError int = 1000
)
type ErrCodeMsg struct {
Code int
Msg string
}
func (e ErrCodeMsg) Error() string {
return e.Msg
}
func NewErrCodeMsg(code int, msg string) ErrCodeMsg {
return ErrCodeMsg{
Code: code,
Msg: msg,
}
}
... ...
package domainService
import (
"fmt"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
)
type AppendDataToTableService struct {
transactionContext *pgTransaction.TransactionContext
}
func (ptr *AppendDataToTableService) AppendData(ctx *domain.Context, fileId int, tableId int, mappingFields []*domain.MappingField) (interface{}, error) {
fileRepository, _ := repository.NewFileRepository(ptr.transactionContext)
file, err := fileRepository.FindOne(map[string]interface{}{"fileId": fileId})
if err != nil {
return nil, fmt.Errorf("文件不存在")
}
tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
table, err := tableRepository.FindOne(map[string]interface{}{"tableId": tableId})
if err != nil {
return nil, fmt.Errorf("表不存在")
}
excelTable, err := tableRepository.FindOne(map[string]interface{}{"tableId": file.FileInfo.TableId})
if err != nil {
return nil, fmt.Errorf("文件未校验")
}
if !(table.TableType == domain.MainTable.ToString() || table.TableType == domain.SideTable.ToString()) {
return nil, fmt.Errorf("只能追加数据到主表或者副表")
}
var subTables []*domain.Table
_, subTables, err = tableRepository.Find(map[string]interface{}{"parentId": tableId, "tableTypes": []string{domain.SubTable.ToString()}})
if err != nil {
return nil, err
}
// 日志
if err = FastLog(ptr.transactionContext, domain.CommonLog, table.TableId, &AppendDataToTableLog{
LogEntry: domain.NewLogEntry(table.Name, domain.MainTable.ToString(), domain.AppendData, ctx),
File: file,
Table: table,
SubTables: subTables,
RowCount: excelTable.RowCount,
}); err != nil {
return nil, err
}
// 通知底层进行追加数据
return map[string]interface{}{
"result": fmt.Sprintf("源数据%v条,成功追加%v条;", excelTable.RowCount, excelTable.RowCount),
}, nil
}
func NewAppendDataToTableService(transactionContext *pgTransaction.TransactionContext) (*AppendDataToTableService, error) {
if transactionContext == nil {
return nil, fmt.Errorf("transactionContext参数不能为nil")
} else {
return &AppendDataToTableService{
transactionContext: transactionContext,
}, nil
}
}
... ...
package domainService
import (
"bytes"
"github.com/beego/beego/v2/client/httplib"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain/bytecore"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
)
type ByteCoreService struct {
TempDataTable map[int]*bytecore.DataLoadDataTable
}
var ByteCore = &ByteCoreService{} //bytecore.ByteLibService
func (ptr *ByteCoreService) LoadDataTable(param bytecore.ReqLoadDataTable) (*bytecore.DataLoadDataTable, error) {
if v, ok := ptr.load(param.FileId); ok {
return v.Filter(param.Where), nil
}
file, err := httplib.Get(param.Url).Bytes()
if err != nil {
return nil, err
}
reader := bytes.NewReader(file)
var importer *excel.Importer
if param.Ext == domain.XLSX {
importer = excel.NewExcelImport(&excel.XLXSReader{})
} else if param.Ext == domain.XLS {
importer = excel.NewExcelImport(&excel.XLSReader{})
}
data, err := importer.OpenExcelFromIoReader(reader)
if err != nil {
return nil, err
}
cols := importer.Reader().Header().Columns
var response = &bytecore.DataLoadDataTable{
FileId: param.FileId,
DataFields: columnToField(cols),
DataRows: data,
Total: len(data),
PageNumber: param.PageNumber,
InValidCells: make([]bytecore.InValidCell, 0),
}
ptr.save(param.FileId, response)
return response.Filter(param.Where), nil
}
func (ptr *ByteCoreService) EditTable(param bytecore.ReqEditDataTable) (*bytecore.DataEditDataTable, error) {
return nil, nil
}
func (ptr *ByteCoreService) save(fileId int, dataTable *bytecore.DataLoadDataTable) {
// Once
if ptr.TempDataTable == nil {
ptr.TempDataTable = make(map[int]*bytecore.DataLoadDataTable)
}
ptr.TempDataTable[fileId] = dataTable
}
func (ptr *ByteCoreService) load(fileId int) (*bytecore.DataLoadDataTable, bool) {
v, ok := ptr.TempDataTable[fileId]
return v, ok
}
func columnToField(cols []string) []*bytecore.Field {
var fields []*bytecore.Field
for i, col := range cols {
fields = append(fields, &bytecore.Field{
Name: col,
Index: i + 1,
Type: "string",
})
}
return fields
}
//////////////
// 字库核心
//////////////
func CreateByteCoreService() (*ByteCoreService, error) {
return ByteCore, nil
}
... ...
package domainService
import (
"fmt"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
)
type EditDataTableService struct {
transactionContext *pgTransaction.TransactionContext
}
func (ptr *EditDataTableService) Edit(ctx *domain.Context, fileId int, tableId int, mappingFields []*domain.MappingField) (interface{}, error) {
fileRepository, _ := repository.NewFileRepository(ptr.transactionContext)
file, err := fileRepository.FindOne(map[string]interface{}{"fileId": fileId})
if err != nil {
return nil, fmt.Errorf("文件不存在")
}
tableRepository, _ := repository.NewTableRepository(ptr.transactionContext)
table, err := tableRepository.FindOne(map[string]interface{}{"tableId": tableId})
if err != nil {
return nil, fmt.Errorf("表不存在")
}
excelTable, err := tableRepository.FindOne(map[string]interface{}{"tableId": file.FileInfo.TableId})
if err != nil {
return nil, fmt.Errorf("文件未校验")
}
if !(table.TableType == domain.MainTable.ToString() || table.TableType == domain.SideTable.ToString()) {
return nil, fmt.Errorf("只能追加数据到主表或者副表")
}
var subTables []*domain.Table
_, subTables, err = tableRepository.Find(map[string]interface{}{"parentId": tableId, "tableTypes": []string{domain.SubTable.ToString()}})
if err != nil {
return nil, err
}
// 日志
if err = FastLog(ptr.transactionContext, domain.CommonLog, table.TableId, &AppendDataToTableLog{
LogEntry: domain.NewLogEntry(table.Name, domain.MainTable.ToString(), domain.AppendData, ctx),
File: file,
Table: table,
SubTables: subTables,
RowCount: excelTable.RowCount,
}); err != nil {
return nil, err
}
// 通知底层进行追加数据
return map[string]interface{}{
"result": fmt.Sprintf("源数据%v条,成功追加%v条;", excelTable.RowCount, excelTable.RowCount),
}, nil
}
func NewEditDataTableService(transactionContext *pgTransaction.TransactionContext) (*EditDataTableService, error) {
if transactionContext == nil {
return nil, fmt.Errorf("transactionContext参数不能为nil")
} else {
return &EditDataTableService{
transactionContext: transactionContext,
}, nil
}
}
... ...
... ... @@ -4,6 +4,7 @@ import (
"fmt"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain/bytecore"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/repository"
)
... ... @@ -12,7 +13,7 @@ type LoadDataTableService struct {
transactionContext *pgTransaction.TransactionContext
}
func (ptr *LoadDataTableService) Load(ctx *domain.Context, fileId int) (interface{}, error) {
func (ptr *LoadDataTableService) Load(ctx *domain.Context, fileId int, where domain.Where) (interface{}, error) {
fileRepository, _ := repository.NewFileRepository(ptr.transactionContext)
file, err := fileRepository.FindOne(map[string]interface{}{"fileId": fileId})
if err != nil {
... ... @@ -30,10 +31,19 @@ func (ptr *LoadDataTableService) Load(ctx *domain.Context, fileId int) (interfac
ptr.FileId = file.FileId
// Load Data From Excel(python api)
byteCore, _ := CreateByteCoreService()
response, err := byteCore.LoadDataTable(bytecore.ReqLoadDataTable{
FileId: file.FileId,
FileName: file.FileInfo.Name,
Url: file.FileInfo.Url,
Ext: file.FileInfo.Ext,
Where: where,
})
if err != nil {
return nil, err
}
return map[string]interface{}{
"fileId": file.FileId,
}, nil
return response, nil
}
func (ptr *LoadDataTableService) GetFileId() int {
... ...
... ... @@ -207,3 +207,24 @@ func (l *DeleteTableLog) Content() string {
}
return msg
}
// 9.数据追加日志
type AppendDataToTableLog struct {
domain.LogEntry
Table *domain.Table
File *domain.File
RowCount int
SubTables []*domain.Table
}
func (l *AppendDataToTableLog) Content() string {
msg := fmt.Sprintf("来源文件:%v校验文件,导入成功%v条,目标表单:%v", l.File.FileInfo.Name, l.RowCount, l.Table.Name)
var tables []string
for _, t := range l.SubTables {
tables = append(tables, t.Name+"分表")
}
if len(tables) > 0 {
msg += fmt.Sprintf(",关联更新%s", strings.Join(tables, "/"))
}
return msg
}
... ...
package excel
import (
"io"
"path/filepath"
)
type Importer struct {
RowBegin int //第几行开始
ColumnBegin int //第几列开始,
ColumnEnd int //第几列结束,
Sheet string //获取的表格
reader Reader
FileName string
}
func (excelImport Importer) OpenExcelFromIoReader(r io.ReadSeeker) ([][]string, error) {
return excelImport.reader.Read(&excelImport, r)
}
func (excelImport Importer) Reader() Reader {
return excelImport.reader
}
func NewExcelImport(reader Reader) *Importer {
return &Importer{
RowBegin: 1,
ColumnBegin: 1,
Sheet: "Sheet1",
reader: reader,
}
}
func NewExcelImportByFile(fileName string) *Importer {
ext := filepath.Ext(fileName)
var reader Reader
if ext == ".xls" {
reader = &XLSReader{}
} else if ext == ".csv" {
reader = &CSVReader{}
} else {
reader = &XLXSReader{}
}
return &Importer{
RowBegin: 1,
ColumnBegin: 1,
Sheet: "Sheet1",
reader: reader,
}
}
... ...
package excel
import (
"bytes"
"encoding/csv"
"github.com/extrame/xls"
"github.com/xuri/excelize/v2"
"golang.org/x/text/encoding/simplifiedchinese"
"golang.org/x/text/transform"
"io"
"unicode/utf8"
)
type Reader interface {
Read(importer *Importer, r File) ([][]string, error)
Header() HeaderInfo
}
var _ Reader = (*XLXSReader)(nil)
type XLXSReader struct {
headerInfo HeaderInfo
}
func (reader *XLXSReader) Read(excelImport *Importer, f File) ([][]string, error) {
excelFile, err := excelize.OpenReader(f)
if err != nil {
return nil, err
}
sheets := excelFile.GetSheetList()
if len(sheets) > 0 && sheets[0] != excelImport.Sheet {
excelImport.Sheet = sheets[0]
}
rows, err := excelFile.Rows(excelImport.Sheet)
if err != nil {
return nil, err
}
var (
rowDataList = make([][]string, 0) //数据列表
rowIndex int //行计数
lenColumn int
)
for rows.Next() {
rowIndex++
cols, err := rows.Columns()
if err != nil {
return nil, err
}
if rowIndex < excelImport.RowBegin {
continue
}
if rowIndex == excelImport.RowBegin {
reader.headerInfo = NewHeaderInfo(cols)
lenColumn = len(cols)
continue
}
if len(cols) > 0 {
if len(cols) < lenColumn {
padding := make([]string, lenColumn-len(cols))
cols = append(cols, padding...)
}
rowDataList = append(rowDataList, cols)
}
}
return rowDataList, nil
}
func (reader *XLXSReader) Header() HeaderInfo {
return reader.headerInfo
}
var _ Reader = (*XLSReader)(nil)
type XLSReader struct {
headerInfo HeaderInfo
}
func (reader *XLSReader) Read(excelImport *Importer, f File) ([][]string, error) {
wb, err := xls.OpenReader(f, "utf-8")
if err != nil {
return nil, err
}
sheet := wb.GetSheet(0)
var (
rowDataList = make([][]string, 0) //数据列表
rowIndex int //行计数
lenColumn int
)
for rowIndex <= int(sheet.MaxRow) {
row := sheet.Row(rowIndex)
cols := make([]string, 0)
if row.LastCol() > 0 {
for j := 0; j < row.LastCol(); j++ {
col := row.Col(j)
cols = append(cols, col)
}
}
rowIndex++
if rowIndex < excelImport.RowBegin {
continue
}
if rowIndex == excelImport.RowBegin {
reader.headerInfo = NewHeaderInfo(cols)
lenColumn = len(cols)
continue
}
if len(cols) == lenColumn {
rowDataList = append(rowDataList, cols)
}
}
return rowDataList, nil
}
func (reader *XLSReader) Header() HeaderInfo {
return reader.headerInfo
}
var _ Reader = (*CSVReader)(nil)
type CSVReader struct {
headerInfo HeaderInfo
}
func (reader *CSVReader) Read(excelImport *Importer, f File) ([][]string, error) {
utf8Reader, err := reader.PrepareCheck(f)
if err != nil {
return nil, err
}
csvReader := csv.NewReader(utf8Reader)
csvReader.FieldsPerRecord = -1
csvReader.LazyQuotes = true
records, err := csvReader.ReadAll()
if err != nil {
return nil, err
}
var (
rowDataList = make([][]string, 0) //数据列表
rowIndex int //行计数
lenColumn int
)
for i := 0; i < len(records); i++ {
rowIndex++
if rowIndex < excelImport.RowBegin {
continue
}
cols := records[i]
if rowIndex == excelImport.RowBegin {
reader.headerInfo = NewHeaderInfo(cols)
lenColumn = len(cols)
continue
}
if len(cols) > 0 {
if len(cols) != lenColumn {
padding := make([]string, lenColumn-len(cols))
cols = append(cols, padding...)
}
rowDataList = append(rowDataList, cols)
}
}
return rowDataList, nil
}
func (reader *CSVReader) Header() HeaderInfo {
return reader.headerInfo
}
func (reader *CSVReader) PrepareCheck(r io.Reader) (io.Reader, error) {
return GBKToUtf8(r)
}
func GBKToUtf8(readIn io.Reader) (io.Reader, error) {
var (
err error
fileByte []byte
)
fileByte, err = io.ReadAll(readIn)
if err != nil {
return nil, err
}
if utf8.Valid(fileByte) {
return bytes.NewReader(fileByte), nil
} else {
utf8Reader := transform.NewReader(bytes.NewReader(fileByte), simplifiedchinese.GBK.NewDecoder())
return utf8Reader, nil
}
}
... ...
package excel
import (
"bytes"
"encoding/csv"
"fmt"
"github.com/aswjh/excel"
"github.com/xuri/excelize/v2"
"io"
"os"
)
type XLXSWriterTo struct {
data [][]string
title []string
}
func (wt *XLXSWriterTo) WriteTo(w io.Writer) (n int64, err error) {
var file *excelize.File
file, err = wt.newFile()
if err != nil {
return 0, nil
}
return file.WriteTo(w)
}
func (wt *XLXSWriterTo) Save(fileName string) error {
var file *excelize.File
var err error
file, err = wt.newFile()
if err != nil {
return nil
}
return file.SaveAs(fileName)
}
func (wt *XLXSWriterTo) newFile() (*excelize.File, error) {
sheet := "Sheet1"
file := excelize.NewFile()
streamWriter, err := file.NewStreamWriter(sheet)
if err != nil {
return nil, err
}
if len(wt.title) == 0 {
return nil, fmt.Errorf("未设置数据表头")
}
if err := streamWriter.SetRow("A1", stringsToInterfaces(wt.title)); err != nil {
return nil, err
}
var rowID = 2
for i := 0; i < len(wt.data); i++ {
row := stringsToInterfaces(wt.data[i])
cell, _ := excelize.CoordinatesToCellName(1, rowID)
if err := streamWriter.SetRow(cell, row); err != nil {
return nil, err
}
rowID += 1
}
if err := streamWriter.Flush(); err != nil {
return nil, err
}
return file, nil
}
func stringsToInterfaces(input []string) []interface{} {
output := make([]interface{}, len(input))
for i, v := range input {
output[i] = v
}
return output
}
func NewXLXSWriterTo(title []string, data [][]string) *XLXSWriterTo {
return &XLXSWriterTo{
data: data,
title: title,
}
}
type CSVWriterTo struct {
data [][]string
title []string
}
func (xw *CSVWriterTo) WriteTo(w io.Writer) (n int64, err error) {
var file = bytes.NewBuffer(nil)
_, err = xw.write(file)
if err != nil {
return 0, nil
}
return file.WriteTo(w)
}
func (xw *CSVWriterTo) Save(fileName string) error {
csvFile, err := os.Create(fileName)
if err != nil {
return err
}
defer csvFile.Close()
if _, err := xw.write(csvFile); err != nil {
return err
}
return nil
}
func (xw *CSVWriterTo) write(w io.Writer) (*csv.Writer, error) {
_, err := w.Write([]byte("\xEF\xBB\xBF")) //写入UTF-8 BOM
if err != nil {
return nil, err
}
csvWriter := csv.NewWriter(w)
if err := csvWriter.Write(xw.title); err != nil {
return nil, err
}
if err := csvWriter.WriteAll(xw.data); err != nil {
return nil, err
}
csvWriter.Flush()
return csvWriter, csvWriter.Error()
}
func NewCSVWriterTo(title []string, data [][]string) *CSVWriterTo {
return &CSVWriterTo{
data: data,
title: title,
}
}
type XlSWriterTo struct {
data [][]string
title []string
}
func (xw *XlSWriterTo) WriteTo(w io.Writer) (n int64, err error) {
var file = bytes.NewBuffer(nil)
err = xw.write(file)
if err != nil {
return 0, nil
}
return file.WriteTo(w)
}
func (xw *XlSWriterTo) Save(fileName string) error {
option := excel.Option{"Visible": true, "DisplayAlerts": true, "ScreenUpdating": true}
xl, err := excel.New(option) //xl, _ := excel.Open("test_excel.xls", option)
if err != nil {
}
defer xl.Quit()
sheet, _ := xl.Sheet(1) //xl.Sheet("sheet1")
defer sheet.Release()
index := 1
sheet.PutRange(excelRange(index, xw.title), toInterface(xw.title)...)
for _, item := range xw.data {
row := toInterface(item)
index += 1
sheet.PutRange(excelRange(index, item), row)
}
errs := xl.SaveAs(fileName)
if len(errs) > 0 {
return errs[0]
}
return nil
}
func excelRange(index int, data []string) string {
var begin byte = 'a'
r := fmt.Sprintf("%c%d:%c%d", begin, index, begin+byte(len(data)), index)
return r
}
func toInterface(data []string) []interface{} {
row := make([]interface{}, len(data))
for i := range data {
row[i] = data[i]
}
return row
}
func (xw *XlSWriterTo) write(w io.Writer) error {
return nil
}
func NewXlSWriterTo(title []string, data [][]string) *XlSWriterTo {
return &XlSWriterTo{
data: data,
title: title,
}
}
... ...
package excel
import "io"
type HeaderInfo struct {
Columns []string
}
func NewHeaderInfo(cols []string) HeaderInfo {
return HeaderInfo{
Columns: cols,
}
}
type File interface {
io.Reader
io.Seeker
}
... ...
... ... @@ -26,4 +26,8 @@ type QueryOptions struct {
type Condition struct {
Field *domain.Field
In []interface{}
Ex []interface{}
Range []interface{}
Order []interface{}
}
... ...
... ... @@ -121,3 +121,11 @@ func (controller *FileController) GenerateMainTable() {
data, err := fileService.GenerateMainTable(ParseContext(controller.BaseController), generateMainTableCommand)
controller.Response(data, err)
}
func (controller *FileController) AppendDataToTable() {
fileService := service.NewFileService(nil)
cmdd := &command.AppendDataToTableCommand{}
controller.Unmarshal(cmdd)
data, err := fileService.AppendDataToTable(ParseContext(controller.BaseController), cmdd)
controller.Response(data, err)
}
... ...
... ... @@ -93,6 +93,16 @@ func (controller *TableController) Search() {
controller.Response(data, err)
}
func (controller *TableController) RelationGraph() {
tableService := service.NewTableService(nil)
cmd := &query.SearchTableQuery{}
Must(controller.Unmarshal(cmd))
cmd.TableTypes = []string{domain.MainTable.ToString(), domain.SideTable.ToString(), domain.SubTable.ToString()}
cmd.Context = ParseContext(controller.BaseController)
data, err := tableService.Search(cmd)
controller.Response(data, err)
}
func (controller *TableController) SearchAppendedList() {
tableService := service.NewTableService(nil)
cmd := &query.SearchTableQuery{}
... ...
... ... @@ -19,4 +19,5 @@ func init() {
web.Router("/data/edit-data-table", &controllers.FileController{}, "Post:EditDataTable")
web.Router("/data/flush-data-table", &controllers.FileController{}, "Post:FlushDataTable")
web.Router("/data/generate-main-table", &controllers.FileController{}, "Post:GenerateMainTable")
web.Router("/data/append-data-to-table", &controllers.FileController{}, "Post:AppendDataToTable")
}
... ...
... ... @@ -12,6 +12,7 @@ func init() {
web.Router("/data/tables/:tableId", &controllers.TableController{}, "Delete:RemoveTable")
web.Router("/data/tables/", &controllers.TableController{}, "Get:ListTable")
web.Router("/data/tables/search", &controllers.TableController{}, "Post:Search")
web.Router("/data/tables/relation-graph", &controllers.TableController{}, "Post:RelationGraph")
web.Router("/data/tables/search-appended-list", &controllers.TableController{}, "Post:SearchAppendedList")
web.Router("/data/tables/search-sub-table-list", &controllers.TableController{}, "Post:SearchSubTableList")
... ... @@ -20,4 +21,5 @@ func init() {
web.Router("/data/tables/copy-data-table", &controllers.TableController{}, "Post:CopyDataTable")
web.Router("/data/tables/update-table-struct", &controllers.TableController{}, "Post:UpdateTableStruct")
web.Router("/data/tables/add-table-struct", &controllers.TableController{}, "Post:AddTableStruct")
// web.Router("/data/tables/preview", &controllers.TableController{}, "Post:PreviewDataTable")
}
... ...