作者 yangfu

Merge branch 'test'

... ... @@ -3,3 +3,7 @@ ALTER TABLE files ADD app_key TEXT;
Update files set file_from = 'ByteBankWebClient';
CREATE INDEX IF NOT EXISTS idx_files_app_key ON metadata.files USING btree(app_key);
alter table metadata.tables add column apply_at timestamptz;
update metadata.tables set apply_at = null where table_type in ('MainTable','SubTable','SideTable') and apply_at is null;
\ No newline at end of file
... ...
... ... @@ -3,46 +3,50 @@ module gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion
go 1.16
require (
github.com/ajg/form v1.5.1 // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible // indirect
github.com/aliyun/aliyun-oss-go-sdk v2.2.7+incompatible
github.com/beego/beego/v2 v2.0.1
github.com/bwmarrin/snowflake v0.3.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
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-gota/gota v0.12.0
github.com/go-pg/pg/v10 v10.10.6
github.com/go-redis/redis v6.15.9+incompatible
github.com/google/go-querystring v1.1.0 // indirect
github.com/google/gofuzz v1.2.0
github.com/google/uuid v1.3.0
github.com/gookit/event v1.0.6
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/linmadan/egglib-go v0.0.0-20210313060205-8b5e456b11f7
github.com/moul/http2curl v1.0.0 // indirect
github.com/onsi/ginkgo v1.16.5
github.com/onsi/gomega v1.18.1
github.com/patrickmn/go-cache v2.1.0+incompatible
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shopspring/decimal v1.3.1
github.com/silenceper/wechat/v2 v2.1.4 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/stretchr/testify v1.7.1
github.com/xuri/excelize/v2 v2.6.0
github.com/zeromicro/go-zero v1.3.4
golang.org/x/text v0.3.7
gorm.io/driver/mysql v1.3.6
gorm.io/driver/postgres v1.3.9
gorm.io/gorm v1.23.8
)
require (
github.com/ajg/form v1.5.1 // indirect
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072 // indirect
github.com/fatih/structs v1.1.0 // indirect
github.com/google/go-querystring v1.1.0 // indirect
github.com/imkira/go-interpol v1.1.0 // indirect
github.com/moul/http2curl v1.0.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/smartystreets/goconvey v1.7.2 // indirect
github.com/valyala/fasthttp v1.38.0 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xuri/efp v0.0.0-20220603152613-6918739fd470 // 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
github.com/zeromicro/go-zero v1.3.4
golang.org/x/text v0.3.7
gorm.io/driver/mysql v1.3.6
gorm.io/driver/postgres v1.3.9
gorm.io/gorm v1.23.8
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
)
replace (
... ...
... ... @@ -16,7 +16,7 @@ import (
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/event"
)
const Version = "v1.3.0"
const Version = "v1.4.0"
func main() {
defer func() {
... ...
package service
import (
"errors"
"fmt"
"github.com/linmadan/egglib-go/core/application"
pgTransaction "github.com/linmadan/egglib-go/transaction/pg"
... ... @@ -77,62 +76,6 @@ func (tableEventService *TableEventService) Handler(ctx *domain.Context, cmd *co
return nil, nil
}
func (tableEventService *TableEventService) HandlerTableAffectedMarkToConflictStatus(ctx *domain.Context, cmd *command.TableEventCommand) (interface{}, 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()
}()
data := cmd.EventTable
tableId := 0
switch data.Type {
case domain.TableStructEditEvent, domain.TableDeleteEvent:
tableId = data.Table.TableId
case domain.QuerySetUpdateEvent, domain.QuerySetUpdateRenameEvent:
tableId = data.QuerySet.QuerySetInfo.BindTableId
default:
return nil, err
}
if tableId == 0 {
return nil, nil
}
// tableId 相关联的
tableRepository, _, _ := factory.FastPgTable(transactionContext, 0)
_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "dependencyTable": tableId, "tableTypesNotIn": []string{domain.TemporaryTable.ToString()}})
if errors.Is(err, domain.ErrorNotFound) {
return nil, nil
}
tableIds := make([]int, 0)
for _, table := range tables {
tableIds = append(tableIds, table.TableId)
}
if len(tableIds) == 0 {
return nil, nil
}
querySetRepository, _, _ := factory.FastPgQuerySet(transactionContext, 0)
_, querySets, _ := querySetRepository.Find(map[string]interface{}{"context": ctx, "bindTableIds": tableIds})
for _, querySet := range querySets {
log.Logger.Debug(fmt.Sprintf("【集合状态更新】 id:%v name:%v ReadyStatus:1", querySet.QuerySetId, querySet.Name))
querySet.QuerySetInfo.WithConflictStatus()
_, err = querySetRepository.Save(querySet)
if err != nil {
return nil, err
}
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return nil, nil
}
func NewTableEventService(options map[string]interface{}) *TableEventService {
svr := &TableEventService{}
delayNotifyTimingWheel, _ := collection.NewTimingWheel(time.Second, 10, svr.TimingWheelFunc)
... ...
package service
import (
"errors"
"fmt"
"github.com/linmadan/egglib-go/core/application"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/event/command"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
"reflect"
"sort"
)
func (tableEventService *TableEventService) HandlerTableAffectedMarkToConflictStatus(ctx *domain.Context, cmd *command.TableEventCommand) (interface{}, 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()
}()
data := cmd.EventTable
tableId := 0
switch data.Type {
case domain.TableStructEditEvent, domain.TableDeleteEvent:
tableId = data.Table.TableId
case domain.QuerySetUpdateEvent:
// 结构变更才报冲突
if !checkStructChange(cmd) {
return nil, err
}
tableId = data.QuerySet.QuerySetInfo.BindTableId
case domain.QuerySetUpdateRenameEvent:
tableId = data.QuerySet.QuerySetInfo.BindTableId
default:
return nil, err
}
if tableId == 0 {
return nil, nil
}
// tableId 相关联的
tableRepository, _, _ := factory.FastPgTable(transactionContext, 0)
_, tables, err := tableRepository.Find(map[string]interface{}{"context": ctx, "dependencyTable": tableId, "tableTypesNotIn": []string{domain.TemporaryTable.ToString()}})
if errors.Is(err, domain.ErrorNotFound) {
return nil, nil
}
tableIds := make([]int, 0)
for _, table := range tables {
tableIds = append(tableIds, table.TableId)
}
if len(tableIds) == 0 {
return nil, nil
}
querySetRepository, _, _ := factory.FastPgQuerySet(transactionContext, 0)
_, querySets, _ := querySetRepository.Find(map[string]interface{}{"context": ctx, "bindTableIds": tableIds})
for _, querySet := range querySets {
log.Logger.Debug(fmt.Sprintf("【集合状态更新】 id:%v name:%v ReadyStatus:1", querySet.QuerySetId, querySet.Name))
querySet.QuerySetInfo.WithConflictStatus()
_, err = querySetRepository.Save(querySet)
if err != nil {
return nil, err
}
}
//tableDependencyService, _ := domainService.NewTableDependencyService(transactionContext.(*pgTransaction.TransactionContext))
//tableDependTree := tableDependencyService.TableDependTree(tables, tableId)
//tree := tableDependTree.Tree
//
//querySetRepository, _, _ := factory.FastPgQuerySet(transactionContext, 0)
//if len(tree) > 0 {
// _, querySets, _ := querySetRepository.Find(map[string]interface{}{
// "types": []string{domain.SchemaTable.ToString(), domain.SubProcessTable.ToString(), domain.CalculateTable.ToString()},
// "bindTableIds": tree,
// })
// for _, querySet := range querySets {
// log.Logger.Debug(fmt.Sprintf("【集合状态更新】 id:%v name:%v 标记冲突", querySet.QuerySetId, querySet.Name))
// querySet.QuerySetInfo.WithConflictStatus()
// _, err = querySetRepository.Save(querySet)
// if err != nil {
// return nil, err
// }
// }
//}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return nil, nil
}
func checkStructChange(cmd *command.TableEventCommand) bool {
var (
newSet = cmd.EventTable.QuerySet
oldSet = cmd.EventTable.OldQuerySet
newTable = cmd.EventTable.Table
oldTable = cmd.EventTable.OldTable
)
if newSet == nil || oldSet == nil {
return false
}
var (
t string = newSet.Type
)
switch t {
case domain.SchemaTable.ToString(), domain.SubProcessTable.ToString():
// 第一步表有变更
newSetDepTables := newSet.GetDependencyTables(newSet.QueryComponents)
oldSetDepTables := newSet.GetDependencyTables(oldSet.QueryComponents)
sort.SliceStable(newSetDepTables, func(i, j int) bool {
return newSetDepTables[i] < newSetDepTables[j]
})
sort.SliceStable(oldSetDepTables, func(i, j int) bool {
return oldSetDepTables[i] < oldSetDepTables[j]
})
if !reflect.DeepEqual(newSetDepTables, oldSetDepTables) {
log.Logger.Debug(fmt.Sprintf("方案/过程:%v 依赖变更 %v -> %v", t, oldSetDepTables, newSetDepTables))
return true
}
case domain.CalculateTable.ToString():
case domain.CalculateItem.ToString(), domain.CalculateSet.ToString():
return false
}
if newTable == nil || oldTable == nil {
return false
}
// 第二步判断字段是否有变更
newTableFields := tableFields(newTable)
oldTableFields := tableFields(oldTable)
sort.Strings(newTableFields)
sort.Strings(oldTableFields)
if !reflect.DeepEqual(newTableFields, oldTableFields) {
log.Logger.Debug(fmt.Sprintf("计算表:%v 结构变更 %v -> %v", t, oldTableFields, newTableFields))
return true
}
return false
}
func tableFields(t *domain.Table) []string {
var result = make([]string, 0)
for _, f := range t.Fields(false) {
result = append(result, f.Name)
}
return result
}
... ...
... ... @@ -380,17 +380,25 @@ func (querySetService *QuerySetService) UpdateQuerySet(ctx *domain.Context, upda
}()
var (
qs *domain.QuerySet
qsTable *domain.Table
oldQs *domain.QuerySet
oldQsTable *domain.Table
)
defer func() {
if qs != nil {
domainService.AsyncEvent(domain.NewEventTable(ctx, domain.QuerySetUpdateEvent).WithQuerySet(qs))
domainService.AsyncEvent(domain.NewEventTable(ctx, domain.QuerySetUpdateEvent).
WithQuerySet(qs).WithOldQuerySet(oldQs).WithTable(qsTable).WithOldTable(oldQsTable))
}
}()
_, oldQs, _ = factory.FastPgQuerySet(transactionContext, updateQuerySetCommand.QuerySetId)
if oldQs != nil {
_, oldQsTable, _ = factory.FastPgTable(transactionContext, oldQs.QuerySetInfo.BindTableId)
}
svr, _ := factory.FastQuerySetServices(transactionContext)
if qs, err = svr.Update(ctx, updateQuerySetCommand.QuerySetId, updateQuerySetCommand.QueryComponents); err != nil {
return nil, factory.FastError(err)
}
_, qsTable, _ = factory.FastPgTable(transactionContext, qs.QuerySetInfo.BindTableId)
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
... ...
... ... @@ -27,6 +27,9 @@ type TableObjectDto struct {
InConflict bool `json:"inConflict"`
// 表字段
Fields []*domain.Field `json:"fields"`
// 引用于时间
ApplyAt int64 `json:"-"`
ApplyString string `json:"applyAtString"`
}
func (d *TableObjectDto) Load(m *domain.Table) *TableObjectDto {
... ... @@ -40,6 +43,8 @@ func (d *TableObjectDto) Load(m *domain.Table) *TableObjectDto {
d.Module = m.TableInfo.ApplyOnModule
}
d.Fields = make([]*domain.Field, 0)
d.ApplyAt = m.ApplyAt.Unix()
d.ApplyString = m.ApplyAt.Format("2006-01-02 15:00")
return d
}
... ...
... ... @@ -17,7 +17,9 @@ type SearchTableQuery struct {
TableTypes []string `cname:"表类型 MainTable:主表 SideTable:副表 SubTable:分表 ExcelTable:Excel表" json:"tableTypes" valid:"Required"`
// 父级ID
ParentId int `cname:"父级ID" json:"parentId"`
// 模块 应用于模块 1:数控中心 2:拆解模块 4:计算模块
// 当前所处的模块
CurrentModule int `json:"currentModule"`
// 需要查询的模块 应用于模块 1:数控中心 2:拆解模块 4:计算模块
Module int `json:"module"`
// 父级ID
ParentTableId int `cname:"父级ID" json:"parentTableId"`
... ...
... ... @@ -98,6 +98,11 @@ func (tableService *TableService) FieldOptionalValues(ctx *domain.Context, cmd *
if err != nil {
return nil, factory.FastError(err)
}
if cmd.Where != nil {
data, length := dataTable.FilterByWhere(*cmd.Where)
dataTable.Data = data
dataTable.Total = length
}
values := removeDuplicate(dataTable.Values(field))
sort.SliceStable(values, func(i, j int) bool {
return values[i] < values[j]
... ...
... ... @@ -16,6 +16,7 @@ import (
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/starrocks"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils"
"strings"
"time"
)
// 表服务
... ... @@ -424,8 +425,10 @@ func (tableService *TableService) ApplyOn(ctx *domain.Context, cmd *command.Appl
}
if table.TableInfo == nil {
table.TableInfo = domain.NewTableInfo().SetApplyOn(cmd.Module)
table.ApplyAt = time.Now()
} else {
table.TableInfo.SetApplyOn(cmd.Module)
table.ApplyAt = time.Now()
}
defer func() {
domainService.AsyncEvent(domain.NewEventTable(ctx, domain.TableApplyOnEvent).WithTable(table).WithMetadata("module", cmd.Module))
... ...
... ... @@ -83,8 +83,8 @@ func (tableService *TableService) TableObjectSearch(searchQuery *query.SearchTab
if filterTableByFilterRule(t, searchQuery) {
continue
}
// 默认关闭的都不返回(拆解、计算)
if t.Status == domain.StatusOff {
// 无模块权限 并且 关闭状态的都不返回(拆解、计算)
if !moduleHasAuth(searchQuery.CurrentModule, t.TableType) && t.Status == domain.StatusOff {
continue
}
if !domain.TableType(t.TableType).TableIsSplitByGroup() {
... ... @@ -124,9 +124,11 @@ func (tableService *TableService) TableObjectSearch(searchQuery *query.SearchTab
sort.Slice(response, func(i, j int) bool {
item1 := response[i]
item2 := response[j]
//k1 := fmt.Sprintf("%v-%v-%v", item1.TableType, item1.ParentId, item1.Id)
//k2 := fmt.Sprintf("%v-%v-%v", item2.TableType, item2.ParentId, item2.Id)
//return k1 < k2
// 拆解模块按应用时间倒叙排列
if domain.AssertTableType(item1.TableType, domain.MainTable, domain.SubTable, domain.SideTable) &&
domain.AssertTableType(item2.TableType, domain.MainTable, domain.SubTable, domain.SideTable) {
return item1.ApplyAt > item2.ApplyAt
}
return item1.Id < item2.Id
})
return map[string]interface{}{
... ... @@ -160,3 +162,21 @@ func filterTableByFilterRule(item *dto.TableObjectDto, searchQuery *query.Search
}
return false
}
// 验证当前模块的权限。属于当前模块可以访问所有
func moduleHasAuth(currentModule int, t string) bool {
if currentModule == 0 {
return false
}
if currentModule == domain.ModuleQuerySetCenter {
if t == domain.SchemaTable.ToString() || t == domain.SubProcessTable.ToString() || t == domain.CalculateTable.ToString() {
return true
}
}
if currentModule == domain.ModuleCalculateCenter {
if t == domain.CalculateItem.ToString() || t == domain.CalculateSet.ToString() {
return true
}
}
return false
}
... ...
... ... @@ -27,6 +27,8 @@ type EventTable struct {
Type EventType
Table *Table
QuerySet *QuerySet
OldQuerySet *QuerySet
OldTable *Table
Metadata map[string]interface{}
}
... ... @@ -52,11 +54,21 @@ func (et *EventTable) WithTable(t *Table) *EventTable {
return et
}
func (et *EventTable) WithOldTable(t *Table) *EventTable {
et.OldTable = t
return et
}
func (et *EventTable) WithQuerySet(t *QuerySet) *EventTable {
et.QuerySet = t
return et
}
func (et *EventTable) WithOldQuerySet(t *QuerySet) *EventTable {
et.OldQuerySet = t
return et
}
func (et *EventTable) WithMetadata(key string, values interface{}) *EventTable {
et.Metadata[key] = values
return et
... ... @@ -67,6 +79,8 @@ func (et *EventTable) ResolveEvent(e event.Event) {
et.Type = e.Get("Type").(EventType)
et.Table = e.Get("Table").(*Table)
et.QuerySet = e.Get("QuerySet").(*QuerySet)
et.OldQuerySet = e.Get("OldQuerySet").(*QuerySet)
et.OldTable = e.Get("OldTable").(*Table)
et.Metadata = e.Get("Metadata").(map[string]interface{})
}
... ... @@ -75,7 +89,9 @@ func (et *EventTable) FireEvent() event.Event {
e["Context"] = et.Context
e["Type"] = et.Type
e["Table"] = et.Table
e["OldTable"] = et.OldTable
e["QuerySet"] = et.QuerySet
e["OldQuerySet"] = et.OldQuerySet
e["Metadata"] = et.Metadata
return event.MustFire(et.Type.ToString(), e)
}
... ...
... ... @@ -28,15 +28,15 @@ type LayoutRuleItem struct {
}
type LayoutCell struct {
Type string `json:"type,omitempty"` // Table TableField Text Null
Data *LayoutCellData `json:"data,omitempty"`
Direction string `json:"direction,omitempty"` // 向右:Right 向下:Down
Position *Location `json:"position"`
X int `json:"-"`
Y int `json:"-"`
Length int `json:"-"`
BlockData []string `json:"-"`
ImageData string `json:"-"`
Type string `json:"type,omitempty"` // 配置单元类型 字段:TableField 文本:Text
Data *LayoutCellData `json:"data,omitempty"` // 配置单元配置值(设置的字段、设置的值)
Direction string `json:"direction,omitempty"` // 配置单元方向 向右:Right 向下:Down 无方向:Null
Position *Location `json:"position"` // 配置单元的定位点
X int `json:"-"` // 计算后的X坐标
Y int `json:"-"` // 计算后的Y坐标
Length int `json:"-"` // 数据长度
BlockData []string `json:"-"` // 数据块
ImageData string `json:"-"` // 调试使用的数据
}
type Location struct {
X int `json:"x"`
... ...
... ... @@ -45,7 +45,8 @@ type Table struct {
Context *Context `json:"context"`
// 表信息
TableInfo *TableInfo `json:"tableInfo"`
// 应用于的时间
ApplyAt time.Time `json:"applyAt"`
// 表头行号 从0开始
HeaderRow int `json:"-"`
}
... ...
... ... @@ -59,6 +59,35 @@ func (ptr *QuerySetService) LoadCalculateSetData(ctx *domain.Context, qs *domain
}
func CellsLocationAdjust(cells []*domain.LayoutCell) {
if len(cells) > 0 {
addedRows := 0
addedColumns := 0
preRowMaxDownLength := 1
preRawCellX := 0
for _, cell := range cells {
if preRawCellX != cell.X {
addedColumns = 0
addedRows = addedRows + preRowMaxDownLength - 1
preRowMaxDownLength = 1
preRawCellX = cell.X
}
cell.X = addedRows + cell.X
cell.Y = addedColumns + cell.Y
println("=======")
switch cell.Direction {
case domain.DirectionRight:
addedColumns = addedColumns + cell.Length - 1
case domain.DirectionDown:
if cell.Length > preRowMaxDownLength {
preRowMaxDownLength = cell.Length
}
case domain.DirectionNone:
}
}
}
}
func CellsLocationAdjustOld(cells []*domain.LayoutCell) {
xMin := 0
xMax := 0
yMin := 0
... ... @@ -422,48 +451,49 @@ func (d *DataLayoutDataTable) BlockData(cells *domain.LayoutCell) ([]string, int
}
func (d *DataLayoutDataTable) Shrink() error {
x := d.PointEnd.X - d.PointBegin.X
y := d.PointEnd.Y - d.PointBegin.Y
x := d.PointEnd.X - 0 //d.PointBegin.X
y := d.PointEnd.Y - 0 //d.PointBegin.Y
data := make([][]string, x+1)
for i := range data {
data[i] = make([]string, y+1)
}
iData := 0
for i := d.PointBegin.X; i <= d.PointEnd.X; i++ {
for i := 0; i <= d.PointEnd.X; i++ { //d.PointBegin.X
jData := 0
for j := d.PointBegin.Y; j <= d.PointEnd.Y; j++ {
for j := 0; j <= d.PointEnd.Y; j++ { //d.PointBegin.Y
data[iData][jData] = d.DataTable.Data[i][j]
jData++
}
iData++
}
d.DataTable.Data = data
for i := 0; i <= y; i++ {
d.DataTable.Fields = append(d.DataTable.Fields, &domain.Field{
Name: fmt.Sprintf("列%d", i),
SQLName: fmt.Sprintf("col%d", i),
SQLType: domain.String.ToString(),
})
}
// 默认计算集第一行作为标题
//if len(data) >= 1 {
// d.DataTable.Data = data[1:]
// columnMap := collection.NewSet()
// for i := 0; i < len(data[0]); i++ {
// if len(data[0][i]) == 0 {
// return fmt.Errorf("计算集标题第%d列不能为空", i+1)
// }
// if columnMap.Contains(data[0][i]) {
// return fmt.Errorf("计算集第%d列重复:%v", i+1, data[0][i])
// }
// columnMap.Add(data[0][i])
//d.DataTable.Data = data
//for i := 0; i <= y; i++ {
// d.DataTable.Fields = append(d.DataTable.Fields, &domain.Field{
// Name: data[0][i],
// Name: fmt.Sprintf("列%d", i),
// SQLName: fmt.Sprintf("col%d", i),
// SQLType: domain.String.ToString(),
// })
// }
//}
// 默认计算集第一行作为标题
if len(data) >= 1 {
d.DataTable.Data = data[1:]
columnMap := collection.NewSet()
for i := 0; i < len(data[0]); i++ {
columnValue := strings.TrimSpace(data[0][i])
if len(columnValue) == 0 {
return fmt.Errorf("计算集标题第%d列不能为空", i+1)
}
if columnMap.Contains(columnValue) {
return fmt.Errorf("计算集第%d列重复:%v", i+1, data[0][i])
}
columnMap.Add(columnValue)
d.DataTable.Fields = append(d.DataTable.Fields, &domain.Field{
Name: columnValue,
SQLName: fmt.Sprintf("col%d", i),
SQLType: domain.String.ToString(),
})
}
}
return nil
}
... ...
package domainService
import (
"github.com/stretchr/testify/assert"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"testing"
)
func TestNewDataLayout(t *testing.T) {
inputs := []struct {
cells []*domain.LayoutCell
flag Location
title string
debug bool
}{
{
title: "配置组多组混合",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 2,
ImageData: "2",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 1,
Length: 3,
ImageData: "3",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 2,
Length: 1,
ImageData: "",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 4,
Length: 5,
ImageData: "5",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 1,
Length: 2,
ImageData: "a",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 2,
Length: 2,
ImageData: "f",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 3,
Length: 2,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 2,
Y: 0,
Length: 2,
ImageData: "e",
Direction: domain.DirectionDown,
},
{
X: 2,
Y: 1,
Length: 1,
ImageData: "c",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 2,
Length: 1,
ImageData: "d",
Direction: domain.DirectionNone,
},
{
X: 5,
Y: 0,
Length: 6,
ImageData: "g",
Direction: domain.DirectionDown,
},
},
flag: Location{X: 2, Y: 2},
},
{
title: "全部无方向",
cells: []*domain.LayoutCell{
{
X: 0,
Y: 0,
Length: 1,
ImageData: "1",
Direction: domain.DirectionNone,
},
{
X: 0,
Y: 1,
Length: 1,
ImageData: "2",
Direction: domain.DirectionNone,
},
{
X: 0,
Y: 2,
Length: 1,
ImageData: "3",
Direction: domain.DirectionNone,
},
{
X: 1,
Y: 0,
Length: 1,
ImageData: "4",
Direction: domain.DirectionNone,
},
{
X: 1,
Y: 1,
Length: 1,
ImageData: "5",
Direction: domain.DirectionNone,
},
{
X: 1,
Y: 2,
Length: 1,
ImageData: "6",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 0,
Length: 1,
ImageData: "7",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 1,
Length: 1,
ImageData: "8",
Direction: domain.DirectionDown,
},
{
X: 2,
Y: 2,
Length: 1,
ImageData: "9",
Direction: domain.DirectionNone,
},
},
flag: Location{X: 2, Y: 2},
},
}
padding := func(cells []*domain.LayoutCell) {
for _, cell := range cells {
for i := 0; i < cell.Length; i++ {
cell.BlockData = append(cell.BlockData, cell.ImageData)
}
}
}
debugItem := false
for _, input := range inputs {
if debugItem && !input.debug {
continue
}
padding(input.cells)
// 根据数据修改位移
CellsLocationAdjust(input.cells)
// 数据布局
res := &domain.DataTable{}
res, err := DataLayout(res, input.cells)
if err != nil {
assert.NoError(t, err)
}
printRes(res)
}
}
... ...
... ... @@ -75,360 +75,266 @@ func TestDataLayout(t *testing.T) {
ImageData: "d",
Direction: domain.DirectionNone,
},
//分组二 右平移10
{
X: 0,
Y: 10,
Length: 2,
ImageData: "2",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 11,
Length: 3,
ImageData: "3",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 10,
Length: 2,
ImageData: "a",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 11,
Length: 2,
ImageData: "f",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 12,
Length: 2,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 13,
Length: 2,
ImageData: "e",
Direction: domain.DirectionDown,
},
{
X: 2,
Y: 10,
Length: 1,
ImageData: "c",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 11,
Length: 1,
ImageData: "d",
Direction: domain.DirectionNone,
},
},
flag: Location{X: 2, Y: 2},
},
{
title: "正常多字段横排",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 0,
Length: 5,
ImageData: "b",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 0,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 3,
Y: 0,
Length: 5,
ImageData: "d",
Direction: domain.DirectionRight,
},
// 分组二 平移10
{
X: 0,
Y: 10,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 10,
Length: 5,
ImageData: "b",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 10,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 3,
Y: 10,
Length: 5,
ImageData: "d",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 3, Y: 4},
},
{
title: "正常多字段横排+计算项目",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 0,
Length: 5,
ImageData: "b",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 0,
Length: 1,
ImageData: "c",
Direction: domain.DirectionNone,
},
{
X: 3,
Y: 0,
Length: 1,
ImageData: "d",
Direction: domain.DirectionNone,
},
// 分组二 平移10
{
X: 0,
Y: 10,
Length: 1,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 10,
Length: 5,
ImageData: "b",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 10,
Length: 1,
ImageData: "c",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 11,
Length: 1,
ImageData: "e",
Direction: domain.DirectionNone,
},
{
X: 3,
Y: 10,
Length: 1,
ImageData: "d",
Direction: domain.DirectionNone,
},
},
flag: Location{X: 2, Y: 1},
},
{
title: "正常多字段横排+竖排",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 1,
Length: 5,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 0,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 0,
Length: 5,
ImageData: "d",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 2, Y: 1},
},
{
title: "正常多字段横排(长度不一样)+竖排",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 0,
Y: 1,
Length: 5,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 0,
Length: 6,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 0,
Length: 7,
ImageData: "d",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 2, Y: 1},
},
{
title: "测试用例1",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 1,
ImageData: "a",
Direction: domain.DirectionNone,
},
{
X: 0,
Y: 1,
Length: 1,
ImageData: "b",
Direction: domain.DirectionNone,
},
{
X: 2,
Y: 2,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 0, Y: 0},
},
{
title: "测试用例3",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionRight,
},
{
X: 1,
Y: 0,
Length: 5,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 1,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 1,
Length: 5,
ImageData: "d",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 2, Y: 1},
},
{
title: "测试用例4",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionDown,
},
{
X: 0,
Y: 1,
Length: 5,
ImageData: "b",
Direction: domain.DirectionDown,
},
// ,
//{
// title: "正常多字段横排",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 1,
// Y: 0,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 0,
// Length: 5,
// ImageData: "c",
// Direction: domain.DirectionRight,
// },
// {
// X: 3,
// Y: 0,
// Length: 5,
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
//
// // 分组二 平移10
// {
// X: 0,
// Y: 10,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 1,
// Y: 10,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 10,
// Length: 5,
// ImageData: "c",
// Direction: domain.DirectionRight,
// },
// {
// X: 3,
// Y: 10,
// Length: 5,
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
// },
// flag: Location{X: 3, Y: 4},
//},
//
//{
// title: "正常多字段横排+计算项目",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 1,
// Y: 0,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 0,
// Length: 1,
// ImageData: "c",
// Direction: domain.DirectionNone,
// },
// {
// X: 3,
// Y: 0,
// Length: 1,
// ImageData: "d",
// Direction: domain.DirectionNone,
// },
//
// // 分组二 平移10
// {
// X: 0,
// Y: 10,
// Length: 1,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 1,
// Y: 10,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 10,
// Length: 1,
// ImageData: "c",
// Direction: domain.DirectionNone,
// },
// {
// X: 2,
// Y: 11,
// Length: 1,
// ImageData: "e",
// Direction: domain.DirectionNone,
// },
// {
// X: 3,
// Y: 10,
// Length: 1,
// ImageData: "d",
// Direction: domain.DirectionNone,
// },
// },
// flag: Location{X: 2, Y: 1},
//},
//
//{
// title: "正常多字段横排+竖排",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 0,
// Y: 1,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionDown,
// },
// {
// X: 1,
// Y: 0,
// Length: 5,
// ImageData: "c",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 0,
// Length: 5,
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
// },
// flag: Location{X: 2, Y: 1},
//},
//
//{
// title: "正常多字段横排(长度不一样)+竖排",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 0,
// Y: 1,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionDown,
// },
// {
// X: 1,
// Y: 0,
// Length: 6,
// ImageData: "c",
// Direction: domain.DirectionRight,
// },
// {
// X: 2,
// Y: 0,
// Length: 7,
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
// },
// flag: Location{X: 2, Y: 1},
//},
//
//{
// title: "测试用例1",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 1,
// ImageData: "a",
// Direction: domain.DirectionNone,
// },
// {
// X: 0,
// Y: 1,
// Length: 1,
// ImageData: "b",
// Direction: domain.DirectionNone,
// },
// {
// X: 2,
// Y: 2,
// Length: 5,
// ImageData: "c",
// Direction: domain.DirectionRight,
// },
// },
// flag: Location{X: 0, Y: 0},
//},
//{
// title: "测试用例3",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionRight,
// },
// {
// X: 1,
// Y: 0,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionDown,
// },
// {
// X: 1,
// Y: 1,
... ... @@ -443,42 +349,107 @@ func TestDataLayout(t *testing.T) {
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
},
flag: Location{X: 0, Y: 0},
},
{
title: "测试用例5",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 5,
ImageData: "a",
Direction: domain.DirectionDown,
},
{
X: 0,
Y: 1,
Length: 6,
ImageData: "b",
Direction: domain.DirectionDown,
},
{
X: 1,
Y: 0,
Length: 1,
ImageData: "t",
Direction: domain.DirectionNone,
},
{
X: 1,
Y: 1,
Length: 1,
ImageData: "x",
Direction: domain.DirectionNone,
},
// ,
// },
// flag: Location{X: 2, Y: 1},
//},
//{
// title: "测试用例4",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionDown,
// },
// {
// X: 0,
// Y: 1,
// Length: 5,
// ImageData: "b",
// Direction: domain.DirectionDown,
// },
// // ,
// // {
// // X: 1,
// // Y: 1,
// // Length: 5,
// // ImageData: "c",
// // Direction: domain.DirectionRight,
// // },
// // {
// // X: 2,
// // Y: 1,
// // Length: 5,
// // ImageData: "d",
// // Direction: domain.DirectionRight,
// // },
// },
// flag: Location{X: 0, Y: 0},
//},
//{
// title: "测试用例5",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 5,
// ImageData: "a",
// Direction: domain.DirectionDown,
// },
// {
// X: 0,
// Y: 1,
// Length: 6,
// ImageData: "b",
// Direction: domain.DirectionDown,
// },
// {
// X: 1,
// Y: 0,
// Length: 1,
// ImageData: "t",
// Direction: domain.DirectionNone,
// },
// {
// X: 1,
// Y: 1,
// Length: 1,
// ImageData: "x",
// Direction: domain.DirectionNone,
// },
// // ,
// // {
// // X: 1,
// // Y: 1,
// // Length: 5,
// // ImageData: "c",
// // Direction: domain.DirectionRight,
// // },
// // {
// // X: 2,
// // Y: 1,
// // Length: 5,
// // ImageData: "d",
// // Direction: domain.DirectionRight,
// // },
// },
// flag: Location{X: 0, Y: 0},
// debug:true,
//},
//{
// title: "测试用例6",
// cells: []*domain.LayoutCell{
// // 分组一
// {
// X: 0,
// Y: 0,
// Length: 1,
// ImageData: "a",
// Direction: domain.DirectionNone,
// },
// {
// X: 1,
// Y: 1,
... ... @@ -493,38 +464,9 @@ func TestDataLayout(t *testing.T) {
// ImageData: "d",
// Direction: domain.DirectionRight,
// },
},
flag: Location{X: 0, Y: 0},
debug:true,
},
{
title: "测试用例6",
cells: []*domain.LayoutCell{
// 分组一
{
X: 0,
Y: 0,
Length: 1,
ImageData: "a",
Direction: domain.DirectionNone,
},
{
X: 1,
Y: 1,
Length: 5,
ImageData: "c",
Direction: domain.DirectionRight,
},
{
X: 2,
Y: 1,
Length: 5,
ImageData: "d",
Direction: domain.DirectionRight,
},
},
flag: Location{X: 0, Y: 0},
},
// },
// flag: Location{X: 0, Y: 0},
//},
}
padding := func(cells []*domain.LayoutCell) {
for _, cell := range cells {
... ... @@ -533,9 +475,9 @@ func TestDataLayout(t *testing.T) {
}
}
}
debugItem:= false
debugItem := false
for _, input := range inputs {
if debugItem && !input.debug{
if debugItem && !input.debug {
continue
}
padding(input.cells)
... ...
... ... @@ -41,4 +41,6 @@ type Table struct {
Context *domain.Context `comment:"扩展"`
// 表信息
TableInfo *domain.TableInfo `comment:"表信息"`
// 应用于的时间
ApplyAt time.Time `comment:"应用于的时间"`
}
... ...
... ... @@ -27,5 +27,6 @@ func TransformToTableDomainModelFromPgModels(tableModel *models.Table) (*domain.
RowCount: tableModel.RowCount,
Context: tableModel.Context,
TableInfo: tableModel.TableInfo,
ApplyAt: tableModel.ApplyAt,
}, nil
}
... ...
... ... @@ -33,6 +33,7 @@ func (repository *TableRepository) Save(table *domain.Table) (*domain.Table, err
"row_count",
"context",
"table_info",
"apply_at",
}
insertFieldsSnippet := sqlbuilder.SqlFieldsSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "table_id", "deleted_at"))
insertPlaceHoldersSnippet := sqlbuilder.SqlPlaceHoldersSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "table_id", "deleted_at"))
... ... @@ -59,6 +60,7 @@ func (repository *TableRepository) Save(table *domain.Table) (*domain.Table, err
&table.RowCount,
&table.Context,
&table.TableInfo,
&table.ApplyAt,
),
fmt.Sprintf("INSERT INTO metadata.tables (%s) VALUES (%s) RETURNING %s", insertFieldsSnippet, insertPlaceHoldersSnippet, returningFieldsSnippet),
table.TableType,
... ... @@ -75,6 +77,7 @@ func (repository *TableRepository) Save(table *domain.Table) (*domain.Table, err
table.RowCount,
table.Context,
table.TableInfo,
table.ApplyAt,
); err != nil {
return table, err
}
... ... @@ -99,6 +102,7 @@ func (repository *TableRepository) Save(table *domain.Table) (*domain.Table, err
&table.RowCount,
&table.Context,
&table.TableInfo,
&table.ApplyAt,
),
fmt.Sprintf("UPDATE metadata.tables SET %s WHERE table_id=? and version=? RETURNING %s", updateFieldsSnippet, returningFieldsSnippet),
table.TableType,
... ... @@ -115,6 +119,7 @@ func (repository *TableRepository) Save(table *domain.Table) (*domain.Table, err
table.RowCount,
table.Context,
table.TableInfo,
table.ApplyAt,
table.Identify(),
oldVersion,
); err != nil {
... ...
... ... @@ -55,6 +55,7 @@ func init() {
web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors())
web.InsertFilter("/data/*", web.BeforeRouter, middleware.JwtFilter())
web.InsertFilter("/api/tables/*", web.BeforeRouter, middleware.JwtFilter())
web.InsertFilter("/*", web.BeforeRouter, RequestCostBefore())
web.InsertFilter("/*", web.BeforeExec, controllers.BlacklistFilter(controllers.BlacklistRouters))
web.InsertFilter("/*", web.BeforeExec, CreateRequestLogFilter(true)) // filters.CreateRequstLogFilter(Logger)
... ...
... ... @@ -146,6 +146,7 @@ func (controller *TableController) SearchQuerySetTables() {
domain.SubProcessTable.ToString(), domain.CalculateTable.ToString()}
}
cmd.Module = domain.ModuleQuerySetCenter
//cmd.CurrentModule = domain.ModuleQuerySetCenter
cmd.ReturnDetailStructInfo = true
cmd.Context = ParseContext(controller.BaseController)
data, err := tableService.Search(cmd)
... ... @@ -160,6 +161,20 @@ func (controller *TableController) TableObjectSearch() {
if cmd.ReturnGroupItem == nil {
cmd.ReturnGroupItem = &defaultReturnGroup
}
cmd.CurrentModule = cmd.Module
cmd.Context = ParseContext(controller.BaseController)
data, err := tableService.TableObjectSearch(cmd)
controller.Response(data, err)
}
func (controller *TableController) ApiTableObjectSearch() {
tableService := service.NewTableService(nil)
cmd := &query.SearchTableQuery{}
Must(controller.Unmarshal(cmd))
defaultReturnGroup := true
if cmd.ReturnGroupItem == nil {
cmd.ReturnGroupItem = &defaultReturnGroup
}
cmd.Context = ParseContext(controller.BaseController)
data, err := tableService.TableObjectSearch(cmd)
controller.Response(data, err)
... ...
... ... @@ -45,4 +45,10 @@ func init() {
web.Router("/data/reset-header-row", tableController, "Post:TableResetHeaderRow")
web.Router("/data/tables/exec/:name", tableController, "Get:ExecScript")
// 外部接口
web.Router("/api/tables/field-optional-values", tableController, "Post:FieldOptionalValues")
web.Router("/api/tables/table-object-search", tableController, "Post:ApiTableObjectSearch")
web.Router("/api/tables/table-preview", tableController, "Post:Preview")
web.Router("/api/tables/:tableId", tableController, "Get:GetTable")
}
... ...