作者 yangfu

feat: support app file

  1 +ALTER TABLE files ADD file_from TEXT;
  2 +ALTER TABLE files ADD app_key TEXT;
  3 +Update files set file_from = 'ByteBankWebClient';
  4 +
  5 +CREATE INDEX IF NOT EXISTS idx_files_app_key ON metadata.files USING btree(app_key);
  1 +package command
  2 +
  3 +import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  4 +
  5 +type AppTableFileAppendDataCommand struct {
  6 + Name string `json:"name"`
  7 + // name 字段中文名
  8 + Fields []*domain.Field `json:"fields"`
  9 + // 数据列表 key:name(字段中文名) value:值(字符串类型)
  10 + Data []map[string]string `json:"data"`
  11 +
  12 + AppKey string `json:"appKey"`
  13 +}
  1 +package command
  2 +
  3 +import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  4 +
  5 +type CreateAppTableFileCommand struct {
  6 + Name string `json:"name"`
  7 + // name 字段中文名
  8 + Fields []*domain.Field `json:"fields"`
  9 + // 数据列表 key:name(字段中文名) value:值(字符串类型)
  10 + Data []map[string]string `json:"data"`
  11 +}
  1 +package command
  2 +
  3 +type DeleteAppTableFileCommand struct {
  4 + Name string `json:"name"`
  5 + AppKey string `json:"appKey"`
  6 +}
@@ -17,6 +17,10 @@ type CreateFileCommand struct { @@ -17,6 +17,10 @@ type CreateFileCommand struct {
17 Url string `cname:"文件地址" json:"url" valid:"Required"` 17 Url string `cname:"文件地址" json:"url" valid:"Required"`
18 // 文件大小 单位KB 18 // 文件大小 单位KB
19 FileSize int `cname:"文件大小" json:"fileSize" valid:"Required"` 19 FileSize int `cname:"文件大小" json:"fileSize" valid:"Required"`
  20 + // 文件来源
  21 + FileFrom string `json:"-"`
  22 + // AppKey
  23 + AppKey string `json:"-" valid:"Required"`
20 } 24 }
21 25
22 var MaxFileSize = 50 * 1024 * 1024 26 var MaxFileSize = 50 * 1024 * 1024
@@ -20,6 +20,8 @@ type FileDto struct { @@ -20,6 +20,8 @@ type FileDto struct {
20 Time string `json:"time"` 20 Time string `json:"time"`
21 // 行号 21 // 行号
22 HeaderRow int `json:"headerRow"` 22 HeaderRow int `json:"headerRow"`
  23 + // 所属应用
  24 + AppKey string `json:"appKey"`
23 } 25 }
24 26
25 func (d *FileDto) Load(f *domain.File) *FileDto { 27 func (d *FileDto) Load(f *domain.File) *FileDto {
@@ -33,5 +35,13 @@ func (d *FileDto) Load(f *domain.File) *FileDto { @@ -33,5 +35,13 @@ func (d *FileDto) Load(f *domain.File) *FileDto {
33 d.Ext = f.FileInfo.Ext 35 d.Ext = f.FileInfo.Ext
34 d.Time = xtime.New(f.UpdatedAt).Local().Format("2006-01-02 15:04:05") 36 d.Time = xtime.New(f.UpdatedAt).Local().Format("2006-01-02 15:04:05")
35 d.HeaderRow = domain.GetHeaderRow(f.FileInfo.HeaderRow) 37 d.HeaderRow = domain.GetHeaderRow(f.FileInfo.HeaderRow)
  38 + d.AppKey = f.AppKey
36 return d 39 return d
37 } 40 }
  41 +
  42 +type AppDto struct {
  43 + AppId int64 `json:"appId"`
  44 + AppKey string `json:"appKey"`
  45 + AppName string `json:"appName"`
  46 + Files []*FileDto `json:"files"`
  47 +}
  1 +package query
  2 +
  3 +type ListAppTableFileCommand struct {
  4 + Name string `json:"name"`
  5 + AppKey string `json:"appKey"`
  6 +}
@@ -17,11 +17,12 @@ type SearchFileQuery struct { @@ -17,11 +17,12 @@ type SearchFileQuery struct {
17 // 页码 17 // 页码
18 // PageNumber int `cname:"页码" json:"pageNumber,omitempty"` 18 // PageNumber int `cname:"页码" json:"pageNumber,omitempty"`
19 // 页数 19 // 页数
20 - FileName string `cname:"文件名称" json:"fileName,omitempty"`  
21 - PageSize int `cname:"页数" json:"pageSize,omitempty"`  
22 - LastId int `cname:"最后一条记录ID" json:"lastId"`  
23 - FileType domain.FileType `cname:"文件类型" json:"fileType" valid:"Required"`  
24 - Context *domain.Context 20 + FileName string `cname:"文件名称" json:"fileName,omitempty"`
  21 + PageSize int `cname:"页数" json:"pageSize,omitempty"`
  22 + LastId int `cname:"最后一条记录ID" json:"lastId"`
  23 + FileType domain.FileType `cname:"文件类型" json:"fileType" valid:"Required"`
  24 + InAppKeys []string `json:"inAppKeys"`
  25 + Context *domain.Context
25 } 26 }
26 27
27 func (cmd *SearchFileQuery) Valid(validation *validation.Validation) { 28 func (cmd *SearchFileQuery) Valid(validation *validation.Validation) {
  1 +package service
  2 +
  3 +import (
  4 + "bytes"
  5 + "errors"
  6 + "fmt"
  7 + "github.com/beego/beego/v2/client/httplib"
  8 + "github.com/linmadan/egglib-go/core/application"
  9 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
  10 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command"
  11 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/query"
  12 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
  13 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  14 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/apilib"
  15 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
  16 + "os"
  17 + "strings"
  18 + "time"
  19 +)
  20 +
  21 +func (fileService *FileService) CreateAppTableFile(ctx *domain.Context, cmd *command.CreateAppTableFileCommand) (*command.CreateFileCommand, error) {
  22 + response := &command.CreateFileCommand{}
  23 + var (
  24 + titles = make([]string, 0)
  25 + dataList = make([][]string, 0)
  26 + )
  27 + for _, filed := range cmd.Fields {
  28 + titles = append(titles, filed.Name)
  29 + }
  30 + for i := range cmd.Data {
  31 + row := make([]string, 0)
  32 + for _, filed := range titles {
  33 + if v, ok := cmd.Data[i][filed]; ok {
  34 + row = append(row, v)
  35 + } else {
  36 + row = append(row, "")
  37 + }
  38 + }
  39 + dataList = append(dataList, row)
  40 + }
  41 + fileUpload, err := saveFile(cmd.Name, titles, dataList, nil)
  42 + if err != nil {
  43 + return nil, factory.FastError(err)
  44 + }
  45 + response.Name = cmd.Name
  46 + if !strings.HasSuffix(response.Name, domain.XLSX) {
  47 + response.Name = response.Name + domain.XLSX
  48 + }
  49 + response.Url = fileUpload.Url
  50 + response.FileSize = int(fileUpload.FileSize)
  51 + response.FileFrom = domain.FileFromDigitalAppClient
  52 + return response, nil
  53 +}
  54 +
  55 +func saveFile(name string, title []string, dataList [][]string, toInterfaces func([]string) []interface{}) (FileUpload, error) {
  56 + var (
  57 + response = FileUpload{}
  58 + err error
  59 + )
  60 + var writerTo = excel.NewXLXSWriterTo(title, dataList)
  61 + if toInterfaces != nil {
  62 + writerTo.ToInterfaces = toInterfaces
  63 + }
  64 + filename := fmt.Sprintf("%v_%v.xlsx", name, time.Now().Format("060102150405"))
  65 + path := fmt.Sprintf("public/%v", filename)
  66 + if err = writerTo.Save(path); err != nil {
  67 + return response, factory.FastError(err)
  68 + }
  69 + api := apilib.NewApiAuthLib(constant.OPEN_API_HOST)
  70 + uploadResponse, err := api.Upload(apilib.RequestUpload{
  71 + UploadFileMap: map[string]string{"file": path},
  72 + })
  73 + if err != nil {
  74 + return response, err
  75 + }
  76 + if stat, err := os.Stat(path); err == nil {
  77 + response.FileSize = stat.Size()
  78 + }
  79 + response.Url = uploadResponse.Path
  80 + response.FileName = name
  81 + response.Ext = domain.XLSX
  82 +
  83 + return response, nil
  84 +}
  85 +
  86 +type FileUpload struct {
  87 + Url string `json:"url"`
  88 + Ext string `json:"ext"`
  89 + FileName string `json:"fileName"`
  90 + FileSize int64 `json:"fileSize"`
  91 +}
  92 +
  93 +func (fileService *FileService) DeleteAppTableFile(ctx *domain.Context, cmd *command.DeleteAppTableFileCommand) (interface{}, error) {
  94 + //if err := cmd.ValidateCommand(); err != nil {
  95 + // return nil, application.ThrowError(application.ARG_ERROR, err.Error())
  96 + //}
  97 + transactionContext, err := factory.CreateTransactionContext(nil)
  98 + if err != nil {
  99 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  100 + }
  101 + if err := transactionContext.StartTransaction(); err != nil {
  102 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  103 + }
  104 + defer func() {
  105 + transactionContext.RollbackTransaction()
  106 + }()
  107 +
  108 + fileRepository, file, _ := factory.FastPgFile(transactionContext, 0)
  109 + file, err = fileRepository.FindOne(map[string]interface{}{"appKey": cmd.AppKey, "fileName": cmd.Name, "fileType": domain.SourceFile})
  110 + if err == domain.ErrorNotFound {
  111 + return nil, factory.FastError(errors.New("文件不存在"))
  112 + }
  113 + if err != nil {
  114 + return nil, factory.FastError(err)
  115 + }
  116 + if _, err := fileRepository.Remove(file); err != nil {
  117 + return nil, factory.FastError(err)
  118 + }
  119 + if err := transactionContext.CommitTransaction(); err != nil {
  120 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  121 + }
  122 + return struct{}{}, nil
  123 +}
  124 +
  125 +func (fileService *FileService) AppTableFileAppendData(ctx *domain.Context, cmd *command.AppTableFileAppendDataCommand) (interface{}, error) {
  126 + //if err := cmd.ValidateCommand(); err != nil {
  127 + // return nil, application.ThrowError(application.ARG_ERROR, err.Error())
  128 + //}
  129 + transactionContext, err := factory.CreateTransactionContext(nil)
  130 + if err != nil {
  131 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  132 + }
  133 + if err := transactionContext.StartTransaction(); err != nil {
  134 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  135 + }
  136 + defer func() {
  137 + transactionContext.RollbackTransaction()
  138 + }()
  139 +
  140 + fileRepository, file, _ := factory.FastPgFile(transactionContext, 0)
  141 + file, err = fileRepository.FindOne(map[string]interface{}{"appKey": cmd.AppKey, "fileName": cmd.Name, "fileType": domain.SourceFile})
  142 + if err == domain.ErrorNotFound {
  143 + return nil, factory.FastError(errors.New("文件不存在"))
  144 + }
  145 + if err != nil {
  146 + return nil, factory.FastError(err)
  147 + }
  148 +
  149 + // 下载文件
  150 + f, err := httplib.Get(domain.ConvertFileUrlToInternal(file.FileInfo.Url)).Bytes()
  151 + if err != nil {
  152 + return nil, factory.FastError(err)
  153 + }
  154 + reader := bytes.NewReader(f)
  155 + var importer *excel.Importer = excel.NewExcelImportByFile(file.FileInfo.Ext)
  156 + data, err := importer.OpenExcelFromIoReader(reader)
  157 + if err != nil {
  158 + return nil, factory.FastError(err)
  159 + }
  160 + titles := importer.Reader().Header().Columns
  161 + for _, f := range cmd.Fields {
  162 + found := false
  163 + for _, column := range titles {
  164 + if column == f.Name {
  165 + found = true
  166 + break
  167 + }
  168 + }
  169 + if !found {
  170 + titles = append(titles, f.Name)
  171 + }
  172 + }
  173 + // 填充旧数据
  174 + // 追加文件
  175 + for i := range data {
  176 + if len(data[i]) < len(titles) {
  177 + for j := 0; j < (len(titles) - len(data[i])); j++ {
  178 + data[i] = append(data[i], "")
  179 + }
  180 + }
  181 + }
  182 + for i := range cmd.Data {
  183 + row := make([]string, 0)
  184 + for _, filed := range titles {
  185 + if v, ok := cmd.Data[i][filed]; ok {
  186 + row = append(row, v)
  187 + } else {
  188 + row = append(row, "")
  189 + }
  190 + }
  191 + data = append(data, row)
  192 + }
  193 +
  194 + // 上传文件
  195 + fileUpload, err := saveFile(cmd.Name, titles, data, nil)
  196 + if err != nil {
  197 + return nil, factory.FastError(err)
  198 + }
  199 +
  200 + // 更新文件
  201 + file.FileInfo.Url = fileUpload.Url
  202 + file.FileInfo.FileSize = int(fileUpload.FileSize)
  203 + file.FileInfo.RowCount = len(data)
  204 + _, err = fileRepository.Save(file)
  205 + if err != nil {
  206 + return nil, factory.FastError(err)
  207 + }
  208 +
  209 + if err := transactionContext.CommitTransaction(); err != nil {
  210 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  211 + }
  212 + return struct{}{}, nil
  213 +}
  214 +
  215 +func (fileService *FileService) AppTableFileList(ctx *domain.Context, cmd *query.ListAppTableFileCommand) (interface{}, error) {
  216 + return fileService.GetAppFile(ctx, cmd.AppKey, cmd.Name)
  217 +}
@@ -4,17 +4,12 @@ import ( @@ -4,17 +4,12 @@ import (
4 "bytes" 4 "bytes"
5 "fmt" 5 "fmt"
6 "github.com/beego/beego/v2/client/httplib" 6 "github.com/beego/beego/v2/client/httplib"
7 - "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"  
8 - "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils"  
9 - "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"  
10 - "os"  
11 - "time"  
12 -  
13 "github.com/linmadan/egglib-go/core/application" 7 "github.com/linmadan/egglib-go/core/application"
14 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory" 8 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
15 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command" 9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command"
16 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/dto" 10 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/dto"
17 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain" 11 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  12 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
18 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis" 13 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis"
19 ) 14 )
20 15
@@ -302,11 +297,7 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp @@ -302,11 +297,7 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp
302 return nil, factory.FastError(err) 297 return nil, factory.FastError(err)
303 } 298 }
304 299
305 - var response = struct {  
306 - Url string `json:"url"`  
307 - Ext string `json:"ext"`  
308 - FileName string `json:"fileName"`  
309 - }{} 300 + var response = FileUpload{}
310 if file.FileType == domain.SourceFile.ToString() { 301 if file.FileType == domain.SourceFile.ToString() {
311 response.Url = file.FileInfo.Url 302 response.Url = file.FileInfo.Url
312 response.Ext = domain.XLSX 303 response.Ext = domain.XLSX
@@ -328,41 +319,46 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp @@ -328,41 +319,46 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp
328 if err != nil { 319 if err != nil {
329 return nil, factory.FastError(err) 320 return nil, factory.FastError(err)
330 } 321 }
331 - filename := fmt.Sprintf("%v_%v.xlsx", file.FileInfo.Name, time.Now().Format("060102150405"))  
332 - path := fmt.Sprintf("public/%v", filename)  
333 - writerTo := excel.NewXLXSWriterTo(importer.Reader().Header().Columns, data)  
334 - writerTo.ToInterfaces = domain.MakeToInterfaces(table.DataFields)  
335 - if err := writerTo.Save(path); err != nil { 322 +
  323 + response, err = saveFile(file.FileInfo.Name, importer.Reader().Header().Columns, data, domain.MakeToInterfaces(table.DataFields))
  324 + if err != nil {
336 return nil, factory.FastError(err) 325 return nil, factory.FastError(err)
337 } 326 }
338 -  
339 - var (  
340 - config = utils.RouterConfig{  
341 - OssEndPoint: "oss-cn-hangzhou.aliyuncs-internal.com",  
342 - AccessKeyID: "LTAI4Fz1LUBW2fXp6QWaJHRS",  
343 - AccessKeySecret: "aLZXwK8pgrs10Ws03qcN7NsrSXFVsg",  
344 - BuckName: "byte-bank",  
345 - }  
346 - key = fmt.Sprintf("byte-bank/%v/%v", time.Now().Format("2006-01-02"), filename)  
347 - )  
348 - bucket, bucketErr := utils.NewBucket(config)  
349 - if bucketErr == nil && bucket != nil {  
350 - log.Logger.Info(fmt.Sprintf("end-point:%v key:%v", config.OssEndPoint, key))  
351 - f, _ := os.Open(path)  
352 - if err = utils.CreateObjects(bucket, utils.Object{  
353 - Key: key,  
354 - Value: f,  
355 - }); err != nil {  
356 - log.Logger.Error(err.Error())  
357 - } else {  
358 - response.Url = domain.ConvertInternalFileUrlToPublic(fmt.Sprintf("https://%v.%v/%v", config.BuckName, config.OssEndPoint, key))  
359 - }  
360 - }  
361 - if len(response.Url) == 0 {  
362 - response.Url = domain.DownloadUrl(filename)  
363 - }  
364 - response.FileName = file.FileInfo.Name  
365 - response.Ext = domain.XLSX 327 + //filename := fmt.Sprintf("%v_%v.xlsx", file.FileInfo.Name, time.Now().Format("060102150405"))
  328 + //path := fmt.Sprintf("public/%v", filename)
  329 + //writerTo := excel.NewXLXSWriterTo(importer.Reader().Header().Columns, data)
  330 + //writerTo.ToInterfaces = domain.MakeToInterfaces(table.DataFields)
  331 + //if err := writerTo.Save(path); err != nil {
  332 + // return nil, factory.FastError(err)
  333 + //}
  334 + //
  335 + //var (
  336 + // config = utils.RouterConfig{
  337 + // OssEndPoint: "oss-cn-hangzhou.aliyuncs-internal.com",
  338 + // AccessKeyID: "LTAI4Fz1LUBW2fXp6QWaJHRS",
  339 + // AccessKeySecret: "aLZXwK8pgrs10Ws03qcN7NsrSXFVsg",
  340 + // BuckName: "byte-bank",
  341 + // }
  342 + // key = fmt.Sprintf("byte-bank/%v/%v", time.Now().Format("2006-01-02"), filename)
  343 + //)
  344 + //bucket, bucketErr := utils.NewBucket(config)
  345 + //if bucketErr == nil && bucket != nil {
  346 + // log.Logger.Info(fmt.Sprintf("end-point:%v key:%v", config.OssEndPoint, key))
  347 + // f, _ := os.Open(path)
  348 + // if err = utils.CreateObjects(bucket, utils.Object{
  349 + // Key: key,
  350 + // Value: f,
  351 + // }); err != nil {
  352 + // log.Logger.Error(err.Error())
  353 + // } else {
  354 + // response.Url = domain.ConvertInternalFileUrlToPublic(fmt.Sprintf("https://%v.%v/%v", config.BuckName, config.OssEndPoint, key))
  355 + // }
  356 + //}
  357 + //if len(response.Url) == 0 {
  358 + // response.Url = domain.DownloadUrl(filename)
  359 + //}
  360 + //response.FileName = file.FileInfo.Name
  361 + //response.Ext = domain.XLSX
366 362
367 if err := transactionContext.CommitTransaction(); err != nil { 363 if err := transactionContext.CommitTransaction(); err != nil {
368 return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error()) 364 return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
@@ -2,7 +2,10 @@ package service @@ -2,7 +2,10 @@ package service
2 2
3 import ( 3 import (
4 "fmt" 4 "fmt"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
5 "path/filepath" 7 "path/filepath"
  8 + "strings"
6 "time" 9 "time"
7 10
8 "github.com/linmadan/egglib-go/core/application" 11 "github.com/linmadan/egglib-go/core/application"
@@ -49,6 +52,8 @@ func (fileService *FileService) CreateFile(ctx *domain.Context, createFileComman @@ -49,6 +52,8 @@ func (fileService *FileService) CreateFile(ctx *domain.Context, createFileComman
49 CreatedAt: time.Now(), 52 CreatedAt: time.Now(),
50 UpdatedAt: time.Now(), 53 UpdatedAt: time.Now(),
51 Context: ctx, 54 Context: ctx,
  55 + FileFrom: createFileCommand.FileFrom,
  56 + AppKey: createFileCommand.AppKey,
52 } 57 }
53 fileRepository, _, _ := factory.FastPgFile(transactionContext, 0) 58 fileRepository, _, _ := factory.FastPgFile(transactionContext, 0)
54 59
@@ -213,6 +218,126 @@ func (fileService *FileService) SearchFile(listFileQuery *query.SearchFileQuery) @@ -213,6 +218,126 @@ func (fileService *FileService) SearchFile(listFileQuery *query.SearchFileQuery)
213 }, nil 218 }, nil
214 } 219 }
215 220
  221 +// 返回文件服务列表
  222 +func (fileService *FileService) SearchAppFile(ctx *domain.Context, listFileQuery *query.SearchFileQuery) (interface{}, error) {
  223 + if err := listFileQuery.ValidateQuery(); err != nil {
  224 + return nil, application.ThrowError(application.ARG_ERROR, err.Error())
  225 + }
  226 + transactionContext, err := factory.CreateTransactionContext(nil)
  227 + if err != nil {
  228 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  229 + }
  230 + if err := transactionContext.StartTransaction(); err != nil {
  231 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  232 + }
  233 + defer func() {
  234 + transactionContext.RollbackTransaction()
  235 + }()
  236 + var fileRepository, _, _ = factory.FastPgFile(transactionContext, 0)
  237 +
  238 + apiAuthLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST).WithToken(ctx.AccessToken)
  239 + response, err := apiAuthLib.MeAppInfo(authlib.RequestUserMeQuery{UserId: ctx.TenantId})
  240 + if err != nil {
  241 + return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
  242 + }
  243 + inAppKeys := make([]string, 0)
  244 + for _, app := range response.Apps {
  245 + inAppKeys = append(inAppKeys, app.AppKey)
  246 + }
  247 + var (
  248 + fileDtos = make([]*dto.FileDto, 0)
  249 + total int64
  250 + )
  251 + if len(inAppKeys) > 0 {
  252 + queryOptions := utils.ObjectToMap(listFileQuery)
  253 + queryOptions["inAppKeys"] = inAppKeys
  254 + queryOptions["limit"] = 1000
  255 + queryOptions["fileName"] = listFileQuery.FileName
  256 + count, files, err := fileRepository.Find(queryOptions)
  257 + if err != nil {
  258 + return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
  259 + }
  260 + for _, file := range files {
  261 + var item = &dto.FileDto{}
  262 + item.Load(file)
  263 + fileDtos = append(fileDtos, item)
  264 + }
  265 + total = count
  266 + }
  267 + var apps = make([]*dto.AppDto, 0)
  268 + for _, app := range response.Apps {
  269 + if len(listFileQuery.FileName) > 0 && !strings.Contains(app.AppName, listFileQuery.FileName) {
  270 + continue
  271 + }
  272 + files := make([]*dto.FileDto, 0)
  273 + for _, file := range fileDtos {
  274 + if file.AppKey == app.AppKey {
  275 + files = append(files, file)
  276 + }
  277 + }
  278 + apps = append(apps, &dto.AppDto{
  279 + AppId: app.AppId,
  280 + AppKey: app.AppKey,
  281 + AppName: app.AppName,
  282 + Files: files,
  283 + })
  284 + }
  285 +
  286 + if err := transactionContext.CommitTransaction(); err != nil {
  287 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  288 + }
  289 + return map[string]interface{}{
  290 + "apps": apps,
  291 + "count": total,
  292 + }, nil
  293 +}
  294 +
  295 +// GetAppFile 返回应用对应的文件服务列表
  296 +func (fileService *FileService) GetAppFile(ctx *domain.Context, appKey string, fileName string) (interface{}, error) {
  297 + transactionContext, err := factory.CreateTransactionContext(nil)
  298 + if err != nil {
  299 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  300 + }
  301 + if err := transactionContext.StartTransaction(); err != nil {
  302 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  303 + }
  304 + defer func() {
  305 + transactionContext.RollbackTransaction()
  306 + }()
  307 + var fileRepository, _, _ = factory.FastPgFile(transactionContext, 0)
  308 +
  309 + var (
  310 + fileDtos = make([]*dto.FileDto, 0)
  311 + total int64
  312 + )
  313 +
  314 + queryOptions := make(map[string]interface{})
  315 + queryOptions["fileType"] = domain.SourceFile
  316 + queryOptions["inAppKeys"] = []string{appKey}
  317 + queryOptions["limit"] = 100
  318 + count, files, err := fileRepository.Find(queryOptions)
  319 + if err != nil {
  320 + return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
  321 + }
  322 + for _, file := range files {
  323 + var item = &dto.FileDto{}
  324 + if fileName != "" && file.FileInfo.Name != fileName {
  325 + continue
  326 + }
  327 + item.Load(file)
  328 + fileDtos = append(fileDtos, item)
  329 + }
  330 + total = count
  331 +
  332 + if err := transactionContext.CommitTransaction(); err != nil {
  333 + return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
  334 + }
  335 + return map[string]interface{}{
  336 + "count": total,
  337 + "files": fileDtos,
  338 + }, nil
  339 +}
  340 +
216 // 移除文件服务 341 // 移除文件服务
217 func (fileService *FileService) RemoveFile(ctx *domain.Context, removeFileCommand *command.RemoveFileCommand) (interface{}, error) { 342 func (fileService *FileService) RemoveFile(ctx *domain.Context, removeFileCommand *command.RemoveFileCommand) (interface{}, error) {
218 if err := removeFileCommand.ValidateCommand(); err != nil { 343 if err := removeFileCommand.ValidateCommand(); err != nil {
@@ -28,6 +28,8 @@ var BYTE_CORE_HOST = "http://192.168.100.34:8303" @@ -28,6 +28,8 @@ var BYTE_CORE_HOST = "http://192.168.100.34:8303"
28 28
29 var AUTH_SERVER_HOST = "http://digital-platform-dev.fjmaimaimai.com" 29 var AUTH_SERVER_HOST = "http://digital-platform-dev.fjmaimaimai.com"
30 30
  31 +var OPEN_API_HOST = "http://mmm-open-api-test.fjmaimaimai.com"
  32 +
31 var BLACK_LIST_USER int64 33 var BLACK_LIST_USER int64
32 var BLACK_LIST_COMPANY int64 34 var BLACK_LIST_COMPANY int64
33 var WHITE_LIST_USERS []int 35 var WHITE_LIST_USERS []int
@@ -52,6 +54,7 @@ func init() { @@ -52,6 +54,7 @@ func init() {
52 SERVICE_ENV = Configurator.DefaultString("SERVICE_ENV", SERVICE_ENV) 54 SERVICE_ENV = Configurator.DefaultString("SERVICE_ENV", SERVICE_ENV)
53 HTTP_PORT = Configurator.DefaultInt("HTTP_PORT", HTTP_PORT) 55 HTTP_PORT = Configurator.DefaultInt("HTTP_PORT", HTTP_PORT)
54 AUTH_SERVER_HOST = Configurator.DefaultString("AUTH_SERVER_HOST", AUTH_SERVER_HOST) 56 AUTH_SERVER_HOST = Configurator.DefaultString("AUTH_SERVER_HOST", AUTH_SERVER_HOST)
  57 + OPEN_API_HOST = Configurator.DefaultString("OPEN_API_HOST", OPEN_API_HOST)
55 SERVICE_NAME = fmt.Sprintf("%v-%v", SERVICE_NAME, SERVICE_ENV) 58 SERVICE_NAME = fmt.Sprintf("%v-%v", SERVICE_NAME, SERVICE_ENV)
56 PPROF_ON = Configurator.DefaultBool("PPROF_ON", PPROF_ON) 59 PPROF_ON = Configurator.DefaultBool("PPROF_ON", PPROF_ON)
57 CACHE_PREFIX = SERVICE_NAME + ":" + SERVICE_ENV 60 CACHE_PREFIX = SERVICE_NAME + ":" + SERVICE_ENV
@@ -11,6 +11,10 @@ type Context struct { @@ -11,6 +11,10 @@ type Context struct {
11 OperatorName string `json:"operatorName"` 11 OperatorName string `json:"operatorName"`
12 // 租户 (个人、企业) 12 // 租户 (个人、企业)
13 TenantId int `json:"tenantId"` 13 TenantId int `json:"tenantId"`
  14 + // 应用键值
  15 + AppKey string `json:"appKey"`
  16 + // Token
  17 + AccessToken string `json:"-"`
14 // 附加数据 18 // 附加数据
15 data map[string]interface{} 19 data map[string]interface{}
16 } 20 }
@@ -419,6 +419,17 @@ func (t LogLevel) ToString() string { @@ -419,6 +419,17 @@ func (t LogLevel) ToString() string {
419 return string(t) 419 return string(t)
420 } 420 }
421 421
  422 +type FileFromType string
  423 +
  424 +const (
  425 + FileFromByteBankWebClient = "ByteBankWebClient"
  426 + FileFromDigitalAppClient = "DigitalAppClient"
  427 +)
  428 +
  429 +func (t FileFromType) ToString() string {
  430 + return string(t)
  431 +}
  432 +
422 const ( 433 const (
423 DefaultPkField = "id" 434 DefaultPkField = "id"
424 ) 435 )
@@ -6,6 +6,7 @@ const ( @@ -6,6 +6,7 @@ const (
6 InvalidSign = 903 6 InvalidSign = 903
7 InvalidClientId = 904 7 InvalidClientId = 904
8 InvalidUUid = 905 8 InvalidUUid = 905
  9 + InvalidApp = 906
9 ) 10 )
10 11
11 var CodeMsg = map[int]string{ 12 var CodeMsg = map[int]string{
@@ -14,4 +15,5 @@ var CodeMsg = map[int]string{ @@ -14,4 +15,5 @@ var CodeMsg = map[int]string{
14 InvalidSign: "sign 签名无效,需重新登录手机 APP", 15 InvalidSign: "sign 签名无效,需重新登录手机 APP",
15 InvalidClientId: "client id 或 client secret 无效,需强制更新手机 APP", 16 InvalidClientId: "client id 或 client secret 无效,需强制更新手机 APP",
16 InvalidUUid: "uuid 无效", 17 InvalidUUid: "uuid 无效",
  18 + InvalidApp: "AppKey或者Token无效",
17 } 19 }
@@ -26,6 +26,10 @@ type File struct { @@ -26,6 +26,10 @@ type File struct {
26 Version int `json:"version"` 26 Version int `json:"version"`
27 // 扩展 27 // 扩展
28 Context *Context `json:"context"` 28 Context *Context `json:"context"`
  29 + // 文件来源
  30 + FileFrom string `json:"fileFrom"`
  31 + // 来源是 DigitalAppClient 时有值
  32 + AppKey string `json:"appKey"`
29 } 33 }
30 34
31 type FileRepository interface { 35 type FileRepository interface {
@@ -42,15 +46,16 @@ func (file *File) Identify() interface{} { @@ -42,15 +46,16 @@ func (file *File) Identify() interface{} {
42 return file.FileId 46 return file.FileId
43 } 47 }
44 48
45 -func (file *File) UpdateFileUrl(url string) { 49 +func (file *File) UpdateFileUrl(url string) *File {
46 if len(url) == 0 { 50 if len(url) == 0 {
47 - return 51 + return file
48 } 52 }
49 if url == file.FileInfo.Url { 53 if url == file.FileInfo.Url {
50 - return 54 + return file
51 } 55 }
52 file.FileInfo.Ext = filepath.Ext(url) 56 file.FileInfo.Ext = filepath.Ext(url)
53 file.FileInfo.Url = url 57 file.FileInfo.Url = url
  58 + return file
54 } 59 }
55 60
56 func (file *File) Update(data map[string]interface{}) error { 61 func (file *File) Update(data map[string]interface{}) error {
@@ -81,13 +86,21 @@ func (file *File) CopyTo(fileType FileType, ctx *Context) *File { @@ -81,13 +86,21 @@ func (file *File) CopyTo(fileType FileType, ctx *Context) *File {
81 CreatedAt: time.Now(), 86 CreatedAt: time.Now(),
82 UpdatedAt: time.Now(), 87 UpdatedAt: time.Now(),
83 SourceFileId: file.FileId, 88 SourceFileId: file.FileId,
  89 + FileFrom: file.FileFrom,
84 Context: ctx, 90 Context: ctx,
85 } 91 }
86 return t 92 return t
87 } 93 }
88 94
89 -func (file *File) SetHeaderRow(headerRow int) { 95 +func (file *File) SetHeaderRow(headerRow int) *File {
90 //file.FileInfo.HeaderRow = headerRow 96 //file.FileInfo.HeaderRow = headerRow
  97 + return file
  98 +}
  99 +
  100 +func (file *File) SetContext(context *Context) *File {
  101 + //file.FileInfo.HeaderRow = headerRow
  102 + file.Context = context
  103 + return file
91 } 104 }
92 105
93 func (file *File) GetHeaderRow() int { 106 func (file *File) GetHeaderRow() int {
  1 +package apilib
  2 +
  3 +import (
  4 + "fmt"
  5 + "github.com/beego/beego/v2/core/logs"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api"
  7 + "net/http"
  8 + "time"
  9 +)
  10 +
  11 +type OpenApiLib struct {
  12 + Token string
  13 + api.BaseServiceGateway
  14 +}
  15 +
  16 +func (gateway *OpenApiLib) WithToken(token string) *OpenApiLib {
  17 + gateway.Token = token
  18 + return gateway
  19 +}
  20 +
  21 +func (gateway *OpenApiLib) DefaultHeader() http.Header {
  22 + var header = make(map[string][]string)
  23 + header["x-mmm-accesstoken"] = []string{gateway.Token}
  24 + header["x-mmm-appproject"] = []string{"byte-bank"}
  25 + return header
  26 +}
  27 +
  28 +func NewApiAuthLib(host string) *OpenApiLib {
  29 + gt := api.NewBaseServiceGateway(host)
  30 + gt.ConnectTimeout = 360 * time.Second
  31 + gt.ReadWriteTimeout = 360 * time.Second
  32 + gt.Interceptor = func(msg string) {
  33 + logs.Debug(msg)
  34 + }
  35 + gt.ServiceName = "【开发接口】"
  36 + return &OpenApiLib{
  37 + BaseServiceGateway: gt,
  38 + }
  39 +}
  40 +
  41 +func (gateway *OpenApiLib) Upload(param RequestUpload) (*DataUploadItem, error) {
  42 + url := gateway.Host() + "/v1/vod/putObject"
  43 + method := "post"
  44 + var data DataUpload
  45 + err := gateway.FastDoRequest(url, method, struct{}{}, &data, api.WithHeader(gateway.DefaultHeader()), api.WithFileMap(param.UploadFileMap))
  46 + if err != nil {
  47 + return nil, err
  48 + }
  49 + if len(data) > 0 {
  50 + return data[0], nil
  51 + }
  52 + return nil, fmt.Errorf("上传失败")
  53 +}
  1 +package apilib
  2 +
  3 +type DataUpload []*DataUploadItem
  4 +
  5 +type DataUploadItem struct {
  6 + Host string `json:"host"`
  7 + Key string `json:"key"`
  8 + Path string `json:"path"`
  9 + FileName string `json:"fileName"`
  10 +}
  11 +
  12 +type RequestUpload struct {
  13 + UploadFileMap map[string]string
  14 +}
@@ -48,6 +48,17 @@ func (gateway *ApiAuthLib) MeInfo(param RequestUserMeQuery) (*DataUserMe, error) @@ -48,6 +48,17 @@ func (gateway *ApiAuthLib) MeInfo(param RequestUserMeQuery) (*DataUserMe, error)
48 return &data, nil 48 return &data, nil
49 } 49 }
50 50
  51 +func (gateway *ApiAuthLib) MeAppInfo(param RequestUserMeQuery) (*DataUserAppInfo, error) {
  52 + url := gateway.Host() + "/v1/user/me-app-info"
  53 + method := "get"
  54 + var data DataUserAppInfo
  55 + err := gateway.FastDoRequest(url, method, param, &data, api.WithHeader(gateway.DefaultHeader()))
  56 + if err != nil {
  57 + return nil, err
  58 + }
  59 + return &data, nil
  60 +}
  61 +
51 func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck, error) { 62 func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck, error) {
52 url := gateway.Host() + "/v1/login/check?token=" + param.Token 63 url := gateway.Host() + "/v1/login/check?token=" + param.Token
53 method := "get" 64 method := "get"
@@ -64,3 +75,14 @@ func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck, @@ -64,3 +75,14 @@ func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck,
64 } 75 }
65 return &data, nil 76 return &data, nil
66 } 77 }
  78 +
  79 +func (gateway *ApiAuthLib) AppLogin(param RequestAppLogin) (*DataAppLogin, error) {
  80 + url := gateway.Host() + "/v1/login/app-login"
  81 + method := "post"
  82 + var data DataAppLogin
  83 + err := gateway.FastDoRequest(url, method, param, &data, api.WithHeader(gateway.DefaultHeader()))
  84 + if err != nil {
  85 + return nil, err
  86 + }
  87 + return &data, nil
  88 +}
@@ -50,3 +50,24 @@ type DataLoginCheck struct { @@ -50,3 +50,24 @@ type DataLoginCheck struct {
50 Code int `json:"code"` 50 Code int `json:"code"`
51 Msg string `json:"msg"` 51 Msg string `json:"msg"`
52 } 52 }
  53 +
  54 +type (
  55 + RequestAppLogin struct {
  56 + AppKey string `json:"appKey" valid:"Required"` // 应用键值
  57 + Token string `json:"token" valid:"Required"` // 凭证
  58 + }
  59 + DataAppLogin struct {
  60 + AppEnabled bool `json:"appEnabled"`
  61 + }
  62 +)
  63 +
  64 +type (
  65 + DataUserAppInfo struct {
  66 + Apps []AppItem `json:"apps"`
  67 + }
  68 + AppItem struct {
  69 + AppId int64
  70 + AppKey string
  71 + AppName string
  72 + }
  73 +)
@@ -16,7 +16,7 @@ type MessageCode struct { @@ -16,7 +16,7 @@ type MessageCode struct {
16 Msg string `json:"msg"` 16 Msg string `json:"msg"`
17 } 17 }
18 18
19 -//Response 统一消息返回格式 19 +// Response 统一消息返回格式
20 type Response struct { 20 type Response struct {
21 MessageCode 21 MessageCode
22 Data rawjson.RawMessage `json:"data"` 22 Data rawjson.RawMessage `json:"data"`
@@ -58,6 +58,9 @@ func (gateway BaseServiceGateway) CreateRequest(url string, method string, optio @@ -58,6 +58,9 @@ func (gateway BaseServiceGateway) CreateRequest(url string, method string, optio
58 request.Header(k, strings.Join(v, ";")) 58 request.Header(k, strings.Join(v, ";"))
59 } 59 }
60 } 60 }
  61 + for k, v := range options.FileMap {
  62 + request.PostFile(k, v)
  63 + }
61 return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout) 64 return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout)
62 } 65 }
63 66
@@ -104,11 +107,14 @@ func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interf @@ -104,11 +107,14 @@ func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interf
104 107
105 func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}, options *RequestOptions) error { 108 func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}, options *RequestOptions) error {
106 r := gateway.CreateRequest(requestParam.Url, requestParam.Method, options) 109 r := gateway.CreateRequest(requestParam.Url, requestParam.Method, options)
107 - req, err := r.JSONBody(requestParam.Param)  
108 - if err != nil {  
109 - return err 110 + var err error
  111 + if len(options.FileMap) == 0 {
  112 + r, err = r.JSONBody(requestParam.Param)
  113 + if err != nil {
  114 + return err
  115 + }
110 } 116 }
111 - byteResult, err := req.Bytes() 117 + byteResult, err := r.Bytes()
112 if err != nil { 118 if err != nil {
113 return err 119 return err
114 } 120 }
@@ -135,6 +141,8 @@ func NewBaseServiceGateway(host string) BaseServiceGateway { @@ -135,6 +141,8 @@ func NewBaseServiceGateway(host string) BaseServiceGateway {
135 141
136 type RequestOptions struct { 142 type RequestOptions struct {
137 Header http.Header 143 Header http.Header
  144 + // key:form key value:path
  145 + FileMap map[string]string
138 } 146 }
139 147
140 type Option func(o *RequestOptions) 148 type Option func(o *RequestOptions)
@@ -144,3 +152,9 @@ func WithHeader(header http.Header) Option { @@ -144,3 +152,9 @@ func WithHeader(header http.Header) Option {
144 o.Header = header 152 o.Header = header
145 } 153 }
146 } 154 }
  155 +
  156 +func WithFileMap(v map[string]string) Option {
  157 + return func(o *RequestOptions) {
  158 + o.FileMap = v
  159 + }
  160 +}
@@ -78,8 +78,10 @@ func (ptr *FlushDataTableService) flushSourceFile(ctx *domain.Context, table *do @@ -78,8 +78,10 @@ func (ptr *FlushDataTableService) flushSourceFile(ctx *domain.Context, table *do
78 } 78 }
79 file.FileInfo.TableId = table.TableId 79 file.FileInfo.TableId = table.TableId
80 file.FileType = domain.VerifiedFile.ToString() 80 file.FileType = domain.VerifiedFile.ToString()
81 - file.UpdateFileUrl(url)  
82 - file.SetHeaderRow(sourceFile.FileInfo.HeaderRow) 81 + file.UpdateFileUrl(url).SetHeaderRow(sourceFile.FileInfo.HeaderRow)
  82 + if file.FileFrom == domain.FileFromDigitalAppClient {
  83 + file.SetContext(ctx)
  84 + }
83 if file, err = fileRepository.Save(file); err != nil { 85 if file, err = fileRepository.Save(file); err != nil {
84 return err 86 return err
85 } 87 }
@@ -27,4 +27,8 @@ type File struct { @@ -27,4 +27,8 @@ type File struct {
27 Version int `comment:"版本"` 27 Version int `comment:"版本"`
28 // 扩展 28 // 扩展
29 Context *domain.Context `json:"context"` 29 Context *domain.Context `json:"context"`
  30 + // 文件来源
  31 + FileFrom string
  32 + // 来源是 DigitalAppClient 时有值
  33 + AppKey string
30 } 34 }
@@ -17,5 +17,7 @@ func TransformToFileDomainModelFromPgModels(fileModel *models.File) (*domain.Fil @@ -17,5 +17,7 @@ func TransformToFileDomainModelFromPgModels(fileModel *models.File) (*domain.Fil
17 DeletedAt: fileModel.DeletedAt, 17 DeletedAt: fileModel.DeletedAt,
18 Version: fileModel.Version, 18 Version: fileModel.Version,
19 Context: fileModel.Context, 19 Context: fileModel.Context,
  20 + AppKey: fileModel.AppKey,
  21 + FileFrom: fileModel.FileFrom,
20 }, nil 22 }, nil
21 } 23 }
@@ -28,6 +28,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error) @@ -28,6 +28,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
28 "deleted_at", 28 "deleted_at",
29 "version", 29 "version",
30 "context", 30 "context",
  31 + "file_from",
  32 + "app_key",
31 } 33 }
32 insertFieldsSnippet := sqlbuilder.SqlFieldsSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at")) 34 insertFieldsSnippet := sqlbuilder.SqlFieldsSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at"))
33 insertPlaceHoldersSnippet := sqlbuilder.SqlPlaceHoldersSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at")) 35 insertPlaceHoldersSnippet := sqlbuilder.SqlPlaceHoldersSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at"))
@@ -48,6 +50,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error) @@ -48,6 +50,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
48 &file.DeletedAt, 50 &file.DeletedAt,
49 &file.Version, 51 &file.Version,
50 &file.Context, 52 &file.Context,
  53 + &file.FileFrom,
  54 + &file.AppKey,
51 ), 55 ),
52 fmt.Sprintf("INSERT INTO metadata.files (%s) VALUES (%s) RETURNING %s", insertFieldsSnippet, insertPlaceHoldersSnippet, returningFieldsSnippet), 56 fmt.Sprintf("INSERT INTO metadata.files (%s) VALUES (%s) RETURNING %s", insertFieldsSnippet, insertPlaceHoldersSnippet, returningFieldsSnippet),
53 file.FileType, 57 file.FileType,
@@ -58,6 +62,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error) @@ -58,6 +62,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
58 file.UpdatedAt, 62 file.UpdatedAt,
59 file.Version, 63 file.Version,
60 file.Context, 64 file.Context,
  65 + file.FileFrom,
  66 + file.AppKey,
61 ); err != nil { 67 ); err != nil {
62 return file, err 68 return file, err
63 } 69 }
@@ -76,6 +82,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error) @@ -76,6 +82,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
76 &file.DeletedAt, 82 &file.DeletedAt,
77 &file.Version, 83 &file.Version,
78 &file.Context, 84 &file.Context,
  85 + &file.FileFrom,
  86 + &file.AppKey,
79 ), 87 ),
80 fmt.Sprintf("UPDATE metadata.files SET %s WHERE file_id=? and version=? RETURNING %s", updateFieldsSnippet, returningFieldsSnippet), 88 fmt.Sprintf("UPDATE metadata.files SET %s WHERE file_id=? and version=? RETURNING %s", updateFieldsSnippet, returningFieldsSnippet),
81 file.FileType, 89 file.FileType,
@@ -86,6 +94,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error) @@ -86,6 +94,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
86 file.UpdatedAt, 94 file.UpdatedAt,
87 file.Version, 95 file.Version,
88 file.Context, 96 file.Context,
  97 + file.FileFrom,
  98 + file.AppKey,
89 file.Identify(), 99 file.Identify(),
90 oldVersion, 100 oldVersion,
91 ); err != nil { 101 ); err != nil {
@@ -111,6 +121,7 @@ func (repository *FileRepository) FindOne(queryOptions map[string]interface{}) ( @@ -111,6 +121,7 @@ func (repository *FileRepository) FindOne(queryOptions map[string]interface{}) (
111 WhereContext(query, queryOptions) 121 WhereContext(query, queryOptions)
112 query.SetWhereByQueryOption("file_info->>'name' = ?", "fileName") 122 query.SetWhereByQueryOption("file_info->>'name' = ?", "fileName")
113 query.SetWhereByQueryOption("file_type = ?", "fileType") 123 query.SetWhereByQueryOption("file_type = ?", "fileType")
  124 + query.SetWhereByQueryOption("app_key = ?", "appKey")
114 if err := query.First(); err != nil { 125 if err := query.First(); err != nil {
115 if err.Error() == "pg: no rows in result set" { 126 if err.Error() == "pg: no rows in result set" {
116 return nil, domain.ErrorNotFound 127 return nil, domain.ErrorNotFound
@@ -138,6 +149,9 @@ func (repository *FileRepository) Find(queryOptions map[string]interface{}) (int @@ -138,6 +149,9 @@ func (repository *FileRepository) Find(queryOptions map[string]interface{}) (int
138 if v, ok := queryOptions["notInFileIds"]; ok && len(v.([]int)) > 0 { 149 if v, ok := queryOptions["notInFileIds"]; ok && len(v.([]int)) > 0 {
139 query.Where(`file_id not in (?)`, pg.In(v.([]int))) 150 query.Where(`file_id not in (?)`, pg.In(v.([]int)))
140 } 151 }
  152 + if v, ok := queryOptions["inAppKeys"]; ok && len(v.([]string)) > 0 {
  153 + query.Where(`app_key in (?)`, pg.In(v.([]string)))
  154 + }
141 if v, ok := queryOptions["updatedAtBegin"]; ok && !v.(time.Time).IsZero() { 155 if v, ok := queryOptions["updatedAtBegin"]; ok && !v.(time.Time).IsZero() {
142 query.Where(`updated_at>?`, v.(time.Time)) 156 query.Where(`updated_at>?`, v.(time.Time))
143 } 157 }
@@ -7,10 +7,8 @@ import ( @@ -7,10 +7,8 @@ import (
7 "github.com/beego/beego/v2/server/web/context" 7 "github.com/beego/beego/v2/server/web/context"
8 "github.com/linmadan/egglib-go/web/beego/filters" 8 "github.com/linmadan/egglib-go/web/beego/filters"
9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant" 9 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
10 - "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"  
11 - "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"  
12 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers" 10 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
13 - "net/http" 11 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
14 "os" 12 "os"
15 "strconv" 13 "strconv"
16 "strings" 14 "strings"
@@ -56,7 +54,7 @@ func init() { @@ -56,7 +54,7 @@ func init() {
56 } 54 }
57 55
58 web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors()) 56 web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors())
59 - web.InsertFilter("/*", web.BeforeRouter, JwtFilter()) 57 + web.InsertFilter("/data/*", web.BeforeRouter, middleware.JwtFilter())
60 web.InsertFilter("/*", web.BeforeRouter, RequestCostBefore()) 58 web.InsertFilter("/*", web.BeforeRouter, RequestCostBefore())
61 web.InsertFilter("/*", web.BeforeExec, controllers.BlacklistFilter(controllers.BlacklistRouters)) 59 web.InsertFilter("/*", web.BeforeExec, controllers.BlacklistFilter(controllers.BlacklistRouters))
62 web.InsertFilter("/*", web.BeforeExec, CreateRequestLogFilter(true)) // filters.CreateRequstLogFilter(Logger) 60 web.InsertFilter("/*", web.BeforeExec, CreateRequestLogFilter(true)) // filters.CreateRequstLogFilter(Logger)
@@ -76,48 +74,6 @@ func CreateRequestLogFilter(console bool) func(ctx *context.Context) { @@ -76,48 +74,6 @@ func CreateRequestLogFilter(console bool) func(ctx *context.Context) {
76 } 74 }
77 } 75 }
78 76
79 -func JwtFilter() func(ctx *context.Context) {  
80 - authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)  
81 - authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond  
82 - authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond  
83 - return func(ctx *context.Context) {  
84 - //token := ctx.Request.Header.Get("Authorization")  
85 - token := ctx.Request.Header.Get("x-mmm-accesstoken")  
86 - if len(token) > 0 {  
87 - token = strings.TrimPrefix(token, "Bearer ")  
88 - userToken := &domain.UserToken{}  
89 - err := userToken.ParseToken(token)  
90 - if err != nil {  
91 - ctx.Output.SetStatus(http.StatusOK)  
92 - ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)  
93 - return  
94 - }  
95 - if userToken.UserId > 0 && userToken.CompanyId > 0 {  
96 - loginCheckResponse, _ := authLib.LoginCheck(authlib.RequestLoginCheck{Token: token})  
97 - if loginCheckResponse != nil && loginCheckResponse.Code == 901 {  
98 - ctx.Output.SetStatus(http.StatusOK)  
99 - ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)  
100 - return  
101 - }  
102 - }  
103 - ctx.Input.SetData("UserToken", userToken)  
104 - ctx.Input.SetData("Accesstoken", token)  
105 - }  
106 - }  
107 -}  
108 -  
109 -func WithCodeMsgResponse(code int) map[string]interface{} {  
110 - msg := "token 过期或无效,需刷新令牌"  
111 - if codeMsg, ok := domain.CodeMsg[code]; ok {  
112 - msg = codeMsg  
113 - }  
114 - return map[string]interface{}{  
115 - "msg": msg,  
116 - "code": code,  
117 - "data": struct{}{},  
118 - }  
119 -}  
120 -  
121 func RequestCostBefore() func(ctx *context.Context) { 77 func RequestCostBefore() func(ctx *context.Context) {
122 return func(ctx *context.Context) { 78 return func(ctx *context.Context) {
123 ctx.Input.SetData("cost-begin", time.Now().UnixMilli()) 79 ctx.Input.SetData("cost-begin", time.Now().UnixMilli())
@@ -43,6 +43,22 @@ func Must(err error) { @@ -43,6 +43,22 @@ func Must(err error) {
43 } 43 }
44 } 44 }
45 45
  46 +func ParseAppKey(c beego.BaseController) string {
  47 + appKey := c.Ctx.Input.GetData("AppKey")
  48 + if appKey == nil {
  49 + return ""
  50 + }
  51 + return appKey.(string)
  52 +}
  53 +
  54 +func ParseAccessToken(c beego.BaseController) string {
  55 + token := c.Ctx.Input.GetData("Accesstoken")
  56 + if token == nil {
  57 + return ""
  58 + }
  59 + return token.(string)
  60 +}
  61 +
46 func ParseContext(c beego.BaseController) *domain.Context { 62 func ParseContext(c beego.BaseController) *domain.Context {
47 var companyId int = 1598224576532189184 63 var companyId int = 1598224576532189184
48 var userId int = 1 64 var userId int = 1
@@ -84,6 +100,7 @@ END: @@ -84,6 +100,7 @@ END:
84 CompanyId: companyId, 100 CompanyId: companyId,
85 OperatorId: userId, 101 OperatorId: userId,
86 OperatorName: userName, 102 OperatorName: userName,
  103 + AccessToken: ParseAccessToken(c),
87 TenantId: 1, 104 TenantId: 1,
88 } 105 }
89 return ctx 106 return ctx
@@ -16,11 +16,55 @@ func (controller *FileController) CreateFile() { @@ -16,11 +16,55 @@ func (controller *FileController) CreateFile() {
16 fileService := service.NewFileService(nil) 16 fileService := service.NewFileService(nil)
17 createFileCommand := &command.CreateFileCommand{} 17 createFileCommand := &command.CreateFileCommand{}
18 controller.Unmarshal(createFileCommand) 18 controller.Unmarshal(createFileCommand)
  19 + createFileCommand.FileFrom = domain.FileFromByteBankWebClient
19 ctx := ParseContext(controller.BaseController) 20 ctx := ParseContext(controller.BaseController)
20 data, err := fileService.CreateFile(ctx, createFileCommand) 21 data, err := fileService.CreateFile(ctx, createFileCommand)
21 controller.Response(data, err) 22 controller.Response(data, err)
22 } 23 }
23 24
  25 +func (controller *FileController) CreateAppTableFile() {
  26 + fileService := service.NewFileService(nil)
  27 + createDigitalAppFileCommand := &command.CreateAppTableFileCommand{}
  28 + controller.Unmarshal(createDigitalAppFileCommand)
  29 +
  30 + ctx := ParseContext(controller.BaseController)
  31 + createFileCommand, err := fileService.CreateAppTableFile(ctx, createDigitalAppFileCommand)
  32 + if err != nil {
  33 + controller.Response(nil, err)
  34 + return
  35 + }
  36 + createFileCommand.AppKey = ParseAppKey(controller.BaseController)
  37 + data, err := fileService.CreateFile(&domain.Context{}, createFileCommand)
  38 + controller.Response(data, err)
  39 +}
  40 +
  41 +func (controller *FileController) DeleteAppTableFile() {
  42 + fileService := service.NewFileService(nil)
  43 + cmd := &command.DeleteAppTableFileCommand{}
  44 + controller.Unmarshal(cmd)
  45 + cmd.AppKey = ParseAppKey(controller.BaseController)
  46 + data, err := fileService.DeleteAppTableFile(&domain.Context{}, cmd)
  47 + controller.Response(data, err)
  48 +}
  49 +
  50 +func (controller *FileController) AppendDataAppTableFile() {
  51 + fileService := service.NewFileService(nil)
  52 + cmd := &command.AppTableFileAppendDataCommand{}
  53 + controller.Unmarshal(cmd)
  54 + cmd.AppKey = ParseAppKey(controller.BaseController)
  55 + data, err := fileService.AppTableFileAppendData(&domain.Context{}, cmd)
  56 + controller.Response(data, err)
  57 +}
  58 +
  59 +func (controller *FileController) ListAppTableFile() {
  60 + fileService := service.NewFileService(nil)
  61 + cmd := &query.ListAppTableFileCommand{}
  62 + controller.Unmarshal(cmd)
  63 + cmd.AppKey = ParseAppKey(controller.BaseController)
  64 + data, err := fileService.AppTableFileList(&domain.Context{}, cmd)
  65 + controller.Response(data, err)
  66 +}
  67 +
24 func (controller *FileController) UpdateFile() { 68 func (controller *FileController) UpdateFile() {
25 fileService := service.NewFileService(nil) 69 fileService := service.NewFileService(nil)
26 updateFileCommand := &command.UpdateFileCommand{} 70 updateFileCommand := &command.UpdateFileCommand{}
@@ -82,6 +126,22 @@ func (controller *FileController) SearchSourceFile() { @@ -82,6 +126,22 @@ func (controller *FileController) SearchSourceFile() {
82 controller.Response(data, err) 126 controller.Response(data, err)
83 } 127 }
84 128
  129 +func (controller *FileController) SearchAppSourceFile() {
  130 + fileService := service.NewFileService(nil)
  131 + cmd := &query.SearchFileQuery{}
  132 + Must(controller.Unmarshal(cmd))
  133 + cmd.FileType = domain.SourceFile
  134 + data, err := fileService.SearchAppFile(ParseContext(controller.BaseController), cmd)
  135 + controller.Response(data, err)
  136 +}
  137 +
  138 +func (controller *FileController) GetAppFile() {
  139 + fileService := service.NewFileService(nil)
  140 + appKey := controller.GetString("app_key", "")
  141 + data, err := fileService.GetAppFile(ParseContext(controller.BaseController), appKey, "")
  142 + controller.Response(data, err)
  143 +}
  144 +
85 func (controller *FileController) SearchVerifiedFile() { 145 func (controller *FileController) SearchVerifiedFile() {
86 fileService := service.NewFileService(nil) 146 fileService := service.NewFileService(nil)
87 cmd := &query.SearchFileQuery{} 147 cmd := &query.SearchFileQuery{}
  1 +package middleware
  2 +
  3 +import (
  4 + "github.com/beego/beego/v2/server/web/context"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  7 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
  8 + "net/http"
  9 + "time"
  10 +)
  11 +
  12 +func AppAccessFilter() func(ctx *context.Context) {
  13 + authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)
  14 + authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond
  15 + authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond
  16 + return func(ctx *context.Context) {
  17 + token := ctx.Request.Header.Get("x-mmm-accesstoken")
  18 + appKey := ctx.Request.Header.Get("x-mmm-appkey")
  19 + if len(appKey) == 0 || len(token) == 0 {
  20 + ctx.Output.SetStatus(http.StatusOK)
  21 + ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
  22 + return
  23 + }
  24 + response, err := authLib.AppLogin(authlib.RequestAppLogin{
  25 + AppKey: appKey,
  26 + Token: token,
  27 + })
  28 + if err != nil {
  29 + ctx.Output.SetStatus(http.StatusOK)
  30 + ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
  31 + return
  32 + }
  33 + if !response.AppEnabled {
  34 + ctx.Output.SetStatus(http.StatusOK)
  35 + ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
  36 + return
  37 + }
  38 + ctx.Input.SetData("AppKey", appKey)
  39 + }
  40 +}
  1 +package middleware
  2 +
  3 +import (
  4 + "github.com/beego/beego/v2/server/web/context"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
  7 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
  8 + "net/http"
  9 + "strings"
  10 + "time"
  11 +)
  12 +
  13 +func JwtFilter() func(ctx *context.Context) {
  14 + authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)
  15 + authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond
  16 + authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond
  17 + return func(ctx *context.Context) {
  18 + //token := ctx.Request.Header.Get("Authorization")
  19 + token := ctx.Request.Header.Get("x-mmm-accesstoken")
  20 + if len(token) > 0 {
  21 + token = strings.TrimPrefix(token, "Bearer ")
  22 + userToken := &domain.UserToken{}
  23 + err := userToken.ParseToken(token)
  24 + if err != nil {
  25 + ctx.Output.SetStatus(http.StatusOK)
  26 + ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
  27 + return
  28 + }
  29 + if userToken.UserId > 0 && userToken.CompanyId > 0 {
  30 + loginCheckResponse, _ := authLib.LoginCheck(authlib.RequestLoginCheck{Token: token})
  31 + if loginCheckResponse != nil && loginCheckResponse.Code == 901 {
  32 + ctx.Output.SetStatus(http.StatusOK)
  33 + ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
  34 + return
  35 + }
  36 + }
  37 + ctx.Input.SetData("UserToken", userToken)
  38 + ctx.Input.SetData("Accesstoken", token)
  39 + }
  40 + }
  41 +}
  42 +
  43 +func WithCodeMsgResponse(code int) map[string]interface{} {
  44 + msg := "token 过期或无效,需刷新令牌"
  45 + if codeMsg, ok := domain.CodeMsg[code]; ok {
  46 + msg = codeMsg
  47 + }
  48 + return map[string]interface{}{
  49 + "msg": msg,
  50 + "code": code,
  51 + "data": struct{}{},
  52 + }
  53 +}
  1 +package routers
  2 +
  3 +import (
  4 + "github.com/beego/beego/v2/server/web"
  5 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
  7 +)
  8 +
  9 +func init() {
  10 + web.InsertFilter("/api/app-table-file/*", web.BeforeRouter, middleware.AppAccessFilter())
  11 + web.Router("/api/app-table-file/create", &controllers.FileController{}, "Post:CreateAppTableFile")
  12 + web.Router("/api/app-table-file/delete", &controllers.FileController{}, "Post:DeleteAppTableFile")
  13 + web.Router("/api/app-table-file/append-data", &controllers.FileController{}, "Post:AppendDataAppTableFile")
  14 + web.Router("/api/app-table-file/list", &controllers.FileController{}, "Post:ListAppTableFile")
  15 +}
@@ -3,8 +3,10 @@ package routers @@ -3,8 +3,10 @@ package routers
3 import ( 3 import (
4 "github.com/beego/beego/v2/server/web" 4 "github.com/beego/beego/v2/server/web"
5 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers" 5 "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
  6 + "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
6 ) 7 )
7 8
8 func init() { 9 func init() {
  10 + web.InsertFilter("/static/*", web.BeforeRouter, middleware.JwtFilter())
9 web.Router("/static/:filename", &controllers.DownloadFileController{}, "*:DownloadHandle") 11 web.Router("/static/:filename", &controllers.DownloadFileController{}, "*:DownloadHandle")
10 } 12 }
@@ -15,11 +15,14 @@ func init() { @@ -15,11 +15,14 @@ func init() {
15 web.Router("/data/files/check-status", &controllers.FileController{}, "Post:CheckFileVerifyStatus") 15 web.Router("/data/files/check-status", &controllers.FileController{}, "Post:CheckFileVerifyStatus")
16 web.Router("/data/files/search", &controllers.FileController{}, "Post:SearchFile") 16 web.Router("/data/files/search", &controllers.FileController{}, "Post:SearchFile")
17 web.Router("/data/files/search-source-file", &controllers.FileController{}, "Post:SearchSourceFile") 17 web.Router("/data/files/search-source-file", &controllers.FileController{}, "Post:SearchSourceFile")
  18 + web.Router("/data/files/search-app-source-file", &controllers.FileController{}, "Post:SearchAppSourceFile")
18 web.Router("/data/files/search-verified-file", &controllers.FileController{}, "Post:SearchVerifiedFile") 19 web.Router("/data/files/search-verified-file", &controllers.FileController{}, "Post:SearchVerifiedFile")
19 web.Router("/data/files/cancel-verifying-file", &controllers.FileController{}, "Post:CancelVerifyingFile") 20 web.Router("/data/files/cancel-verifying-file", &controllers.FileController{}, "Post:CancelVerifyingFile")
20 web.Router("/data/files/prepare-temporary-file", &controllers.FileController{}, "Post:PrepareTemporaryFile") 21 web.Router("/data/files/prepare-temporary-file", &controllers.FileController{}, "Post:PrepareTemporaryFile")
21 web.Router("/data/files/export-file", &controllers.FileController{}, "Post:ExportFile") 22 web.Router("/data/files/export-file", &controllers.FileController{}, "Post:ExportFile")
22 23
  24 + web.Router("/data/files/app-file", &controllers.FileController{}, "Get:GetAppFile")
  25 +
23 web.Router("/data/file-preview", &controllers.FileController{}, "Post:FilePreview") 26 web.Router("/data/file-preview", &controllers.FileController{}, "Post:FilePreview")
24 web.Router("/data/edit-data-table", &controllers.FileController{}, "Post:EditDataTable") 27 web.Router("/data/edit-data-table", &controllers.FileController{}, "Post:EditDataTable")
25 web.Router("/data/flush-data-table", &controllers.FileController{}, "Post:FlushDataTable") 28 web.Router("/data/flush-data-table", &controllers.FileController{}, "Post:FlushDataTable")