作者 yangfu

feat: support app file

ALTER TABLE files ADD file_from TEXT;
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);
\ No newline at end of file
... ...
package command
import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
type AppTableFileAppendDataCommand struct {
Name string `json:"name"`
// name 字段中文名
Fields []*domain.Field `json:"fields"`
// 数据列表 key:name(字段中文名) value:值(字符串类型)
Data []map[string]string `json:"data"`
AppKey string `json:"appKey"`
}
... ...
package command
import "gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
type CreateAppTableFileCommand struct {
Name string `json:"name"`
// name 字段中文名
Fields []*domain.Field `json:"fields"`
// 数据列表 key:name(字段中文名) value:值(字符串类型)
Data []map[string]string `json:"data"`
}
... ...
package command
type DeleteAppTableFileCommand struct {
Name string `json:"name"`
AppKey string `json:"appKey"`
}
... ...
... ... @@ -17,6 +17,10 @@ type CreateFileCommand struct {
Url string `cname:"文件地址" json:"url" valid:"Required"`
// 文件大小 单位KB
FileSize int `cname:"文件大小" json:"fileSize" valid:"Required"`
// 文件来源
FileFrom string `json:"-"`
// AppKey
AppKey string `json:"-" valid:"Required"`
}
var MaxFileSize = 50 * 1024 * 1024
... ...
... ... @@ -20,6 +20,8 @@ type FileDto struct {
Time string `json:"time"`
// 行号
HeaderRow int `json:"headerRow"`
// 所属应用
AppKey string `json:"appKey"`
}
func (d *FileDto) Load(f *domain.File) *FileDto {
... ... @@ -33,5 +35,13 @@ func (d *FileDto) Load(f *domain.File) *FileDto {
d.Ext = f.FileInfo.Ext
d.Time = xtime.New(f.UpdatedAt).Local().Format("2006-01-02 15:04:05")
d.HeaderRow = domain.GetHeaderRow(f.FileInfo.HeaderRow)
d.AppKey = f.AppKey
return d
}
type AppDto struct {
AppId int64 `json:"appId"`
AppKey string `json:"appKey"`
AppName string `json:"appName"`
Files []*FileDto `json:"files"`
}
... ...
package query
type ListAppTableFileCommand struct {
Name string `json:"name"`
AppKey string `json:"appKey"`
}
... ...
... ... @@ -21,6 +21,7 @@ type SearchFileQuery struct {
PageSize int `cname:"页数" json:"pageSize,omitempty"`
LastId int `cname:"最后一条记录ID" json:"lastId"`
FileType domain.FileType `cname:"文件类型" json:"fileType" valid:"Required"`
InAppKeys []string `json:"inAppKeys"`
Context *domain.Context
}
... ...
package service
import (
"bytes"
"errors"
"fmt"
"github.com/beego/beego/v2/client/httplib"
"github.com/linmadan/egglib-go/core/application"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/query"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/apilib"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
"os"
"strings"
"time"
)
func (fileService *FileService) CreateAppTableFile(ctx *domain.Context, cmd *command.CreateAppTableFileCommand) (*command.CreateFileCommand, error) {
response := &command.CreateFileCommand{}
var (
titles = make([]string, 0)
dataList = make([][]string, 0)
)
for _, filed := range cmd.Fields {
titles = append(titles, filed.Name)
}
for i := range cmd.Data {
row := make([]string, 0)
for _, filed := range titles {
if v, ok := cmd.Data[i][filed]; ok {
row = append(row, v)
} else {
row = append(row, "")
}
}
dataList = append(dataList, row)
}
fileUpload, err := saveFile(cmd.Name, titles, dataList, nil)
if err != nil {
return nil, factory.FastError(err)
}
response.Name = cmd.Name
if !strings.HasSuffix(response.Name, domain.XLSX) {
response.Name = response.Name + domain.XLSX
}
response.Url = fileUpload.Url
response.FileSize = int(fileUpload.FileSize)
response.FileFrom = domain.FileFromDigitalAppClient
return response, nil
}
func saveFile(name string, title []string, dataList [][]string, toInterfaces func([]string) []interface{}) (FileUpload, error) {
var (
response = FileUpload{}
err error
)
var writerTo = excel.NewXLXSWriterTo(title, dataList)
if toInterfaces != nil {
writerTo.ToInterfaces = toInterfaces
}
filename := fmt.Sprintf("%v_%v.xlsx", name, time.Now().Format("060102150405"))
path := fmt.Sprintf("public/%v", filename)
if err = writerTo.Save(path); err != nil {
return response, factory.FastError(err)
}
api := apilib.NewApiAuthLib(constant.OPEN_API_HOST)
uploadResponse, err := api.Upload(apilib.RequestUpload{
UploadFileMap: map[string]string{"file": path},
})
if err != nil {
return response, err
}
if stat, err := os.Stat(path); err == nil {
response.FileSize = stat.Size()
}
response.Url = uploadResponse.Path
response.FileName = name
response.Ext = domain.XLSX
return response, nil
}
type FileUpload struct {
Url string `json:"url"`
Ext string `json:"ext"`
FileName string `json:"fileName"`
FileSize int64 `json:"fileSize"`
}
func (fileService *FileService) DeleteAppTableFile(ctx *domain.Context, cmd *command.DeleteAppTableFileCommand) (interface{}, error) {
//if err := cmd.ValidateCommand(); err != nil {
// return nil, application.ThrowError(application.ARG_ERROR, err.Error())
//}
transactionContext, err := factory.CreateTransactionContext(nil)
if err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
if err := transactionContext.StartTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
defer func() {
transactionContext.RollbackTransaction()
}()
fileRepository, file, _ := factory.FastPgFile(transactionContext, 0)
file, err = fileRepository.FindOne(map[string]interface{}{"appKey": cmd.AppKey, "fileName": cmd.Name, "fileType": domain.SourceFile})
if err == domain.ErrorNotFound {
return nil, factory.FastError(errors.New("文件不存在"))
}
if err != nil {
return nil, factory.FastError(err)
}
if _, err := fileRepository.Remove(file); err != nil {
return nil, factory.FastError(err)
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return struct{}{}, nil
}
func (fileService *FileService) AppTableFileAppendData(ctx *domain.Context, cmd *command.AppTableFileAppendDataCommand) (interface{}, error) {
//if err := cmd.ValidateCommand(); err != nil {
// return nil, application.ThrowError(application.ARG_ERROR, err.Error())
//}
transactionContext, err := factory.CreateTransactionContext(nil)
if err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
if err := transactionContext.StartTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
defer func() {
transactionContext.RollbackTransaction()
}()
fileRepository, file, _ := factory.FastPgFile(transactionContext, 0)
file, err = fileRepository.FindOne(map[string]interface{}{"appKey": cmd.AppKey, "fileName": cmd.Name, "fileType": domain.SourceFile})
if err == domain.ErrorNotFound {
return nil, factory.FastError(errors.New("文件不存在"))
}
if err != nil {
return nil, factory.FastError(err)
}
// 下载文件
f, err := httplib.Get(domain.ConvertFileUrlToInternal(file.FileInfo.Url)).Bytes()
if err != nil {
return nil, factory.FastError(err)
}
reader := bytes.NewReader(f)
var importer *excel.Importer = excel.NewExcelImportByFile(file.FileInfo.Ext)
data, err := importer.OpenExcelFromIoReader(reader)
if err != nil {
return nil, factory.FastError(err)
}
titles := importer.Reader().Header().Columns
for _, f := range cmd.Fields {
found := false
for _, column := range titles {
if column == f.Name {
found = true
break
}
}
if !found {
titles = append(titles, f.Name)
}
}
// 填充旧数据
// 追加文件
for i := range data {
if len(data[i]) < len(titles) {
for j := 0; j < (len(titles) - len(data[i])); j++ {
data[i] = append(data[i], "")
}
}
}
for i := range cmd.Data {
row := make([]string, 0)
for _, filed := range titles {
if v, ok := cmd.Data[i][filed]; ok {
row = append(row, v)
} else {
row = append(row, "")
}
}
data = append(data, row)
}
// 上传文件
fileUpload, err := saveFile(cmd.Name, titles, data, nil)
if err != nil {
return nil, factory.FastError(err)
}
// 更新文件
file.FileInfo.Url = fileUpload.Url
file.FileInfo.FileSize = int(fileUpload.FileSize)
file.FileInfo.RowCount = len(data)
_, err = fileRepository.Save(file)
if err != nil {
return nil, factory.FastError(err)
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return struct{}{}, nil
}
func (fileService *FileService) AppTableFileList(ctx *domain.Context, cmd *query.ListAppTableFileCommand) (interface{}, error) {
return fileService.GetAppFile(ctx, cmd.AppKey, cmd.Name)
}
... ...
... ... @@ -4,17 +4,12 @@ import (
"bytes"
"fmt"
"github.com/beego/beego/v2/client/httplib"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/utils"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/log"
"os"
"time"
"github.com/linmadan/egglib-go/core/application"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/factory"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/command"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/application/file/dto"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/excel"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/redis"
)
... ... @@ -302,11 +297,7 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp
return nil, factory.FastError(err)
}
var response = struct {
Url string `json:"url"`
Ext string `json:"ext"`
FileName string `json:"fileName"`
}{}
var response = FileUpload{}
if file.FileType == domain.SourceFile.ToString() {
response.Url = file.FileInfo.Url
response.Ext = domain.XLSX
... ... @@ -328,41 +319,46 @@ func (fileService *FileService) ExportFile(ctx *domain.Context, cmd *command.Exp
if err != nil {
return nil, factory.FastError(err)
}
filename := fmt.Sprintf("%v_%v.xlsx", file.FileInfo.Name, time.Now().Format("060102150405"))
path := fmt.Sprintf("public/%v", filename)
writerTo := excel.NewXLXSWriterTo(importer.Reader().Header().Columns, data)
writerTo.ToInterfaces = domain.MakeToInterfaces(table.DataFields)
if err := writerTo.Save(path); err != nil {
return nil, factory.FastError(err)
}
var (
config = utils.RouterConfig{
OssEndPoint: "oss-cn-hangzhou.aliyuncs-internal.com",
AccessKeyID: "LTAI4Fz1LUBW2fXp6QWaJHRS",
AccessKeySecret: "aLZXwK8pgrs10Ws03qcN7NsrSXFVsg",
BuckName: "byte-bank",
}
key = fmt.Sprintf("byte-bank/%v/%v", time.Now().Format("2006-01-02"), filename)
)
bucket, bucketErr := utils.NewBucket(config)
if bucketErr == nil && bucket != nil {
log.Logger.Info(fmt.Sprintf("end-point:%v key:%v", config.OssEndPoint, key))
f, _ := os.Open(path)
if err = utils.CreateObjects(bucket, utils.Object{
Key: key,
Value: f,
}); err != nil {
log.Logger.Error(err.Error())
} else {
response.Url = domain.ConvertInternalFileUrlToPublic(fmt.Sprintf("https://%v.%v/%v", config.BuckName, config.OssEndPoint, key))
}
}
if len(response.Url) == 0 {
response.Url = domain.DownloadUrl(filename)
response, err = saveFile(file.FileInfo.Name, importer.Reader().Header().Columns, data, domain.MakeToInterfaces(table.DataFields))
if err != nil {
return nil, factory.FastError(err)
}
response.FileName = file.FileInfo.Name
response.Ext = domain.XLSX
//filename := fmt.Sprintf("%v_%v.xlsx", file.FileInfo.Name, time.Now().Format("060102150405"))
//path := fmt.Sprintf("public/%v", filename)
//writerTo := excel.NewXLXSWriterTo(importer.Reader().Header().Columns, data)
//writerTo.ToInterfaces = domain.MakeToInterfaces(table.DataFields)
//if err := writerTo.Save(path); err != nil {
// return nil, factory.FastError(err)
//}
//
//var (
// config = utils.RouterConfig{
// OssEndPoint: "oss-cn-hangzhou.aliyuncs-internal.com",
// AccessKeyID: "LTAI4Fz1LUBW2fXp6QWaJHRS",
// AccessKeySecret: "aLZXwK8pgrs10Ws03qcN7NsrSXFVsg",
// BuckName: "byte-bank",
// }
// key = fmt.Sprintf("byte-bank/%v/%v", time.Now().Format("2006-01-02"), filename)
//)
//bucket, bucketErr := utils.NewBucket(config)
//if bucketErr == nil && bucket != nil {
// log.Logger.Info(fmt.Sprintf("end-point:%v key:%v", config.OssEndPoint, key))
// f, _ := os.Open(path)
// if err = utils.CreateObjects(bucket, utils.Object{
// Key: key,
// Value: f,
// }); err != nil {
// log.Logger.Error(err.Error())
// } else {
// response.Url = domain.ConvertInternalFileUrlToPublic(fmt.Sprintf("https://%v.%v/%v", config.BuckName, config.OssEndPoint, key))
// }
//}
//if len(response.Url) == 0 {
// response.Url = domain.DownloadUrl(filename)
//}
//response.FileName = file.FileInfo.Name
//response.Ext = domain.XLSX
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
... ...
... ... @@ -2,7 +2,10 @@ package service
import (
"fmt"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
"path/filepath"
"strings"
"time"
"github.com/linmadan/egglib-go/core/application"
... ... @@ -49,6 +52,8 @@ func (fileService *FileService) CreateFile(ctx *domain.Context, createFileComman
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
Context: ctx,
FileFrom: createFileCommand.FileFrom,
AppKey: createFileCommand.AppKey,
}
fileRepository, _, _ := factory.FastPgFile(transactionContext, 0)
... ... @@ -213,6 +218,126 @@ func (fileService *FileService) SearchFile(listFileQuery *query.SearchFileQuery)
}, nil
}
// 返回文件服务列表
func (fileService *FileService) SearchAppFile(ctx *domain.Context, listFileQuery *query.SearchFileQuery) (interface{}, error) {
if err := listFileQuery.ValidateQuery(); err != nil {
return nil, application.ThrowError(application.ARG_ERROR, err.Error())
}
transactionContext, err := factory.CreateTransactionContext(nil)
if err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
if err := transactionContext.StartTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
defer func() {
transactionContext.RollbackTransaction()
}()
var fileRepository, _, _ = factory.FastPgFile(transactionContext, 0)
apiAuthLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST).WithToken(ctx.AccessToken)
response, err := apiAuthLib.MeAppInfo(authlib.RequestUserMeQuery{UserId: ctx.TenantId})
if err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
inAppKeys := make([]string, 0)
for _, app := range response.Apps {
inAppKeys = append(inAppKeys, app.AppKey)
}
var (
fileDtos = make([]*dto.FileDto, 0)
total int64
)
if len(inAppKeys) > 0 {
queryOptions := utils.ObjectToMap(listFileQuery)
queryOptions["inAppKeys"] = inAppKeys
queryOptions["limit"] = 1000
queryOptions["fileName"] = listFileQuery.FileName
count, files, err := fileRepository.Find(queryOptions)
if err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
for _, file := range files {
var item = &dto.FileDto{}
item.Load(file)
fileDtos = append(fileDtos, item)
}
total = count
}
var apps = make([]*dto.AppDto, 0)
for _, app := range response.Apps {
if len(listFileQuery.FileName) > 0 && !strings.Contains(app.AppName, listFileQuery.FileName) {
continue
}
files := make([]*dto.FileDto, 0)
for _, file := range fileDtos {
if file.AppKey == app.AppKey {
files = append(files, file)
}
}
apps = append(apps, &dto.AppDto{
AppId: app.AppId,
AppKey: app.AppKey,
AppName: app.AppName,
Files: files,
})
}
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return map[string]interface{}{
"apps": apps,
"count": total,
}, nil
}
// GetAppFile 返回应用对应的文件服务列表
func (fileService *FileService) GetAppFile(ctx *domain.Context, appKey string, fileName string) (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()
}()
var fileRepository, _, _ = factory.FastPgFile(transactionContext, 0)
var (
fileDtos = make([]*dto.FileDto, 0)
total int64
)
queryOptions := make(map[string]interface{})
queryOptions["fileType"] = domain.SourceFile
queryOptions["inAppKeys"] = []string{appKey}
queryOptions["limit"] = 100
count, files, err := fileRepository.Find(queryOptions)
if err != nil {
return nil, application.ThrowError(application.INTERNAL_SERVER_ERROR, err.Error())
}
for _, file := range files {
var item = &dto.FileDto{}
if fileName != "" && file.FileInfo.Name != fileName {
continue
}
item.Load(file)
fileDtos = append(fileDtos, item)
}
total = count
if err := transactionContext.CommitTransaction(); err != nil {
return nil, application.ThrowError(application.TRANSACTION_ERROR, err.Error())
}
return map[string]interface{}{
"count": total,
"files": fileDtos,
}, nil
}
// 移除文件服务
func (fileService *FileService) RemoveFile(ctx *domain.Context, removeFileCommand *command.RemoveFileCommand) (interface{}, error) {
if err := removeFileCommand.ValidateCommand(); err != nil {
... ...
... ... @@ -28,6 +28,8 @@ var BYTE_CORE_HOST = "http://192.168.100.34:8303"
var AUTH_SERVER_HOST = "http://digital-platform-dev.fjmaimaimai.com"
var OPEN_API_HOST = "http://mmm-open-api-test.fjmaimaimai.com"
var BLACK_LIST_USER int64
var BLACK_LIST_COMPANY int64
var WHITE_LIST_USERS []int
... ... @@ -52,6 +54,7 @@ func init() {
SERVICE_ENV = Configurator.DefaultString("SERVICE_ENV", SERVICE_ENV)
HTTP_PORT = Configurator.DefaultInt("HTTP_PORT", HTTP_PORT)
AUTH_SERVER_HOST = Configurator.DefaultString("AUTH_SERVER_HOST", AUTH_SERVER_HOST)
OPEN_API_HOST = Configurator.DefaultString("OPEN_API_HOST", OPEN_API_HOST)
SERVICE_NAME = fmt.Sprintf("%v-%v", SERVICE_NAME, SERVICE_ENV)
PPROF_ON = Configurator.DefaultBool("PPROF_ON", PPROF_ON)
CACHE_PREFIX = SERVICE_NAME + ":" + SERVICE_ENV
... ...
... ... @@ -11,6 +11,10 @@ type Context struct {
OperatorName string `json:"operatorName"`
// 租户 (个人、企业)
TenantId int `json:"tenantId"`
// 应用键值
AppKey string `json:"appKey"`
// Token
AccessToken string `json:"-"`
// 附加数据
data map[string]interface{}
}
... ...
... ... @@ -419,6 +419,17 @@ func (t LogLevel) ToString() string {
return string(t)
}
type FileFromType string
const (
FileFromByteBankWebClient = "ByteBankWebClient"
FileFromDigitalAppClient = "DigitalAppClient"
)
func (t FileFromType) ToString() string {
return string(t)
}
const (
DefaultPkField = "id"
)
... ...
... ... @@ -6,6 +6,7 @@ const (
InvalidSign = 903
InvalidClientId = 904
InvalidUUid = 905
InvalidApp = 906
)
var CodeMsg = map[int]string{
... ... @@ -14,4 +15,5 @@ var CodeMsg = map[int]string{
InvalidSign: "sign 签名无效,需重新登录手机 APP",
InvalidClientId: "client id 或 client secret 无效,需强制更新手机 APP",
InvalidUUid: "uuid 无效",
InvalidApp: "AppKey或者Token无效",
}
... ...
... ... @@ -26,6 +26,10 @@ type File struct {
Version int `json:"version"`
// 扩展
Context *Context `json:"context"`
// 文件来源
FileFrom string `json:"fileFrom"`
// 来源是 DigitalAppClient 时有值
AppKey string `json:"appKey"`
}
type FileRepository interface {
... ... @@ -42,15 +46,16 @@ func (file *File) Identify() interface{} {
return file.FileId
}
func (file *File) UpdateFileUrl(url string) {
func (file *File) UpdateFileUrl(url string) *File {
if len(url) == 0 {
return
return file
}
if url == file.FileInfo.Url {
return
return file
}
file.FileInfo.Ext = filepath.Ext(url)
file.FileInfo.Url = url
return file
}
func (file *File) Update(data map[string]interface{}) error {
... ... @@ -81,13 +86,21 @@ func (file *File) CopyTo(fileType FileType, ctx *Context) *File {
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
SourceFileId: file.FileId,
FileFrom: file.FileFrom,
Context: ctx,
}
return t
}
func (file *File) SetHeaderRow(headerRow int) {
func (file *File) SetHeaderRow(headerRow int) *File {
//file.FileInfo.HeaderRow = headerRow
return file
}
func (file *File) SetContext(context *Context) *File {
//file.FileInfo.HeaderRow = headerRow
file.Context = context
return file
}
func (file *File) GetHeaderRow() int {
... ...
package apilib
import (
"fmt"
"github.com/beego/beego/v2/core/logs"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api"
"net/http"
"time"
)
type OpenApiLib struct {
Token string
api.BaseServiceGateway
}
func (gateway *OpenApiLib) WithToken(token string) *OpenApiLib {
gateway.Token = token
return gateway
}
func (gateway *OpenApiLib) DefaultHeader() http.Header {
var header = make(map[string][]string)
header["x-mmm-accesstoken"] = []string{gateway.Token}
header["x-mmm-appproject"] = []string{"byte-bank"}
return header
}
func NewApiAuthLib(host string) *OpenApiLib {
gt := api.NewBaseServiceGateway(host)
gt.ConnectTimeout = 360 * time.Second
gt.ReadWriteTimeout = 360 * time.Second
gt.Interceptor = func(msg string) {
logs.Debug(msg)
}
gt.ServiceName = "【开发接口】"
return &OpenApiLib{
BaseServiceGateway: gt,
}
}
func (gateway *OpenApiLib) Upload(param RequestUpload) (*DataUploadItem, error) {
url := gateway.Host() + "/v1/vod/putObject"
method := "post"
var data DataUpload
err := gateway.FastDoRequest(url, method, struct{}{}, &data, api.WithHeader(gateway.DefaultHeader()), api.WithFileMap(param.UploadFileMap))
if err != nil {
return nil, err
}
if len(data) > 0 {
return data[0], nil
}
return nil, fmt.Errorf("上传失败")
}
... ...
package apilib
type DataUpload []*DataUploadItem
type DataUploadItem struct {
Host string `json:"host"`
Key string `json:"key"`
Path string `json:"path"`
FileName string `json:"fileName"`
}
type RequestUpload struct {
UploadFileMap map[string]string
}
... ...
... ... @@ -48,6 +48,17 @@ func (gateway *ApiAuthLib) MeInfo(param RequestUserMeQuery) (*DataUserMe, error)
return &data, nil
}
func (gateway *ApiAuthLib) MeAppInfo(param RequestUserMeQuery) (*DataUserAppInfo, error) {
url := gateway.Host() + "/v1/user/me-app-info"
method := "get"
var data DataUserAppInfo
err := gateway.FastDoRequest(url, method, param, &data, api.WithHeader(gateway.DefaultHeader()))
if err != nil {
return nil, err
}
return &data, nil
}
func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck, error) {
url := gateway.Host() + "/v1/login/check?token=" + param.Token
method := "get"
... ... @@ -64,3 +75,14 @@ func (gateway *ApiAuthLib) LoginCheck(param RequestLoginCheck) (*DataLoginCheck,
}
return &data, nil
}
func (gateway *ApiAuthLib) AppLogin(param RequestAppLogin) (*DataAppLogin, error) {
url := gateway.Host() + "/v1/login/app-login"
method := "post"
var data DataAppLogin
err := gateway.FastDoRequest(url, method, param, &data, api.WithHeader(gateway.DefaultHeader()))
if err != nil {
return nil, err
}
return &data, nil
}
... ...
... ... @@ -50,3 +50,24 @@ type DataLoginCheck struct {
Code int `json:"code"`
Msg string `json:"msg"`
}
type (
RequestAppLogin struct {
AppKey string `json:"appKey" valid:"Required"` // 应用键值
Token string `json:"token" valid:"Required"` // 凭证
}
DataAppLogin struct {
AppEnabled bool `json:"appEnabled"`
}
)
type (
DataUserAppInfo struct {
Apps []AppItem `json:"apps"`
}
AppItem struct {
AppId int64
AppKey string
AppName string
}
)
... ...
... ... @@ -16,7 +16,7 @@ type MessageCode struct {
Msg string `json:"msg"`
}
//Response 统一消息返回格式
// Response 统一消息返回格式
type Response struct {
MessageCode
Data rawjson.RawMessage `json:"data"`
... ... @@ -58,6 +58,9 @@ func (gateway BaseServiceGateway) CreateRequest(url string, method string, optio
request.Header(k, strings.Join(v, ";"))
}
}
for k, v := range options.FileMap {
request.PostFile(k, v)
}
return request.SetTimeout(gateway.ConnectTimeout, gateway.ReadWriteTimeout)
}
... ... @@ -104,11 +107,14 @@ func (gateway BaseServiceGateway) FastDoRequest(url, method string, param interf
func (gateway BaseServiceGateway) DoRequest(requestParam Request, val interface{}, options *RequestOptions) error {
r := gateway.CreateRequest(requestParam.Url, requestParam.Method, options)
req, err := r.JSONBody(requestParam.Param)
var err error
if len(options.FileMap) == 0 {
r, err = r.JSONBody(requestParam.Param)
if err != nil {
return err
}
byteResult, err := req.Bytes()
}
byteResult, err := r.Bytes()
if err != nil {
return err
}
... ... @@ -135,6 +141,8 @@ func NewBaseServiceGateway(host string) BaseServiceGateway {
type RequestOptions struct {
Header http.Header
// key:form key value:path
FileMap map[string]string
}
type Option func(o *RequestOptions)
... ... @@ -144,3 +152,9 @@ func WithHeader(header http.Header) Option {
o.Header = header
}
}
func WithFileMap(v map[string]string) Option {
return func(o *RequestOptions) {
o.FileMap = v
}
}
... ...
... ... @@ -78,8 +78,10 @@ func (ptr *FlushDataTableService) flushSourceFile(ctx *domain.Context, table *do
}
file.FileInfo.TableId = table.TableId
file.FileType = domain.VerifiedFile.ToString()
file.UpdateFileUrl(url)
file.SetHeaderRow(sourceFile.FileInfo.HeaderRow)
file.UpdateFileUrl(url).SetHeaderRow(sourceFile.FileInfo.HeaderRow)
if file.FileFrom == domain.FileFromDigitalAppClient {
file.SetContext(ctx)
}
if file, err = fileRepository.Save(file); err != nil {
return err
}
... ...
... ... @@ -27,4 +27,8 @@ type File struct {
Version int `comment:"版本"`
// 扩展
Context *domain.Context `json:"context"`
// 文件来源
FileFrom string
// 来源是 DigitalAppClient 时有值
AppKey string
}
... ...
... ... @@ -17,5 +17,7 @@ func TransformToFileDomainModelFromPgModels(fileModel *models.File) (*domain.Fil
DeletedAt: fileModel.DeletedAt,
Version: fileModel.Version,
Context: fileModel.Context,
AppKey: fileModel.AppKey,
FileFrom: fileModel.FileFrom,
}, nil
}
... ...
... ... @@ -28,6 +28,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
"deleted_at",
"version",
"context",
"file_from",
"app_key",
}
insertFieldsSnippet := sqlbuilder.SqlFieldsSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at"))
insertPlaceHoldersSnippet := sqlbuilder.SqlPlaceHoldersSnippet(sqlbuilder.RemoveSqlFields(sqlBuildFields, "file_id", "deleted_at"))
... ... @@ -48,6 +50,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
&file.DeletedAt,
&file.Version,
&file.Context,
&file.FileFrom,
&file.AppKey,
),
fmt.Sprintf("INSERT INTO metadata.files (%s) VALUES (%s) RETURNING %s", insertFieldsSnippet, insertPlaceHoldersSnippet, returningFieldsSnippet),
file.FileType,
... ... @@ -58,6 +62,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
file.UpdatedAt,
file.Version,
file.Context,
file.FileFrom,
file.AppKey,
); err != nil {
return file, err
}
... ... @@ -76,6 +82,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
&file.DeletedAt,
&file.Version,
&file.Context,
&file.FileFrom,
&file.AppKey,
),
fmt.Sprintf("UPDATE metadata.files SET %s WHERE file_id=? and version=? RETURNING %s", updateFieldsSnippet, returningFieldsSnippet),
file.FileType,
... ... @@ -86,6 +94,8 @@ func (repository *FileRepository) Save(file *domain.File) (*domain.File, error)
file.UpdatedAt,
file.Version,
file.Context,
file.FileFrom,
file.AppKey,
file.Identify(),
oldVersion,
); err != nil {
... ... @@ -111,6 +121,7 @@ func (repository *FileRepository) FindOne(queryOptions map[string]interface{}) (
WhereContext(query, queryOptions)
query.SetWhereByQueryOption("file_info->>'name' = ?", "fileName")
query.SetWhereByQueryOption("file_type = ?", "fileType")
query.SetWhereByQueryOption("app_key = ?", "appKey")
if err := query.First(); err != nil {
if err.Error() == "pg: no rows in result set" {
return nil, domain.ErrorNotFound
... ... @@ -138,6 +149,9 @@ func (repository *FileRepository) Find(queryOptions map[string]interface{}) (int
if v, ok := queryOptions["notInFileIds"]; ok && len(v.([]int)) > 0 {
query.Where(`file_id not in (?)`, pg.In(v.([]int)))
}
if v, ok := queryOptions["inAppKeys"]; ok && len(v.([]string)) > 0 {
query.Where(`app_key in (?)`, pg.In(v.([]string)))
}
if v, ok := queryOptions["updatedAtBegin"]; ok && !v.(time.Time).IsZero() {
query.Where(`updated_at>?`, v.(time.Time))
}
... ...
... ... @@ -7,10 +7,8 @@ import (
"github.com/beego/beego/v2/server/web/context"
"github.com/linmadan/egglib-go/web/beego/filters"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
"net/http"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
"os"
"strconv"
"strings"
... ... @@ -56,7 +54,7 @@ func init() {
}
web.InsertFilter("/*", web.BeforeRouter, filters.AllowCors())
web.InsertFilter("/*", web.BeforeRouter, JwtFilter())
web.InsertFilter("/data/*", 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)
... ... @@ -76,48 +74,6 @@ func CreateRequestLogFilter(console bool) func(ctx *context.Context) {
}
}
func JwtFilter() func(ctx *context.Context) {
authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)
authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond
authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond
return func(ctx *context.Context) {
//token := ctx.Request.Header.Get("Authorization")
token := ctx.Request.Header.Get("x-mmm-accesstoken")
if len(token) > 0 {
token = strings.TrimPrefix(token, "Bearer ")
userToken := &domain.UserToken{}
err := userToken.ParseToken(token)
if err != nil {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
return
}
if userToken.UserId > 0 && userToken.CompanyId > 0 {
loginCheckResponse, _ := authLib.LoginCheck(authlib.RequestLoginCheck{Token: token})
if loginCheckResponse != nil && loginCheckResponse.Code == 901 {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
return
}
}
ctx.Input.SetData("UserToken", userToken)
ctx.Input.SetData("Accesstoken", token)
}
}
}
func WithCodeMsgResponse(code int) map[string]interface{} {
msg := "token 过期或无效,需刷新令牌"
if codeMsg, ok := domain.CodeMsg[code]; ok {
msg = codeMsg
}
return map[string]interface{}{
"msg": msg,
"code": code,
"data": struct{}{},
}
}
func RequestCostBefore() func(ctx *context.Context) {
return func(ctx *context.Context) {
ctx.Input.SetData("cost-begin", time.Now().UnixMilli())
... ...
... ... @@ -43,6 +43,22 @@ func Must(err error) {
}
}
func ParseAppKey(c beego.BaseController) string {
appKey := c.Ctx.Input.GetData("AppKey")
if appKey == nil {
return ""
}
return appKey.(string)
}
func ParseAccessToken(c beego.BaseController) string {
token := c.Ctx.Input.GetData("Accesstoken")
if token == nil {
return ""
}
return token.(string)
}
func ParseContext(c beego.BaseController) *domain.Context {
var companyId int = 1598224576532189184
var userId int = 1
... ... @@ -84,6 +100,7 @@ END:
CompanyId: companyId,
OperatorId: userId,
OperatorName: userName,
AccessToken: ParseAccessToken(c),
TenantId: 1,
}
return ctx
... ...
... ... @@ -16,11 +16,55 @@ func (controller *FileController) CreateFile() {
fileService := service.NewFileService(nil)
createFileCommand := &command.CreateFileCommand{}
controller.Unmarshal(createFileCommand)
createFileCommand.FileFrom = domain.FileFromByteBankWebClient
ctx := ParseContext(controller.BaseController)
data, err := fileService.CreateFile(ctx, createFileCommand)
controller.Response(data, err)
}
func (controller *FileController) CreateAppTableFile() {
fileService := service.NewFileService(nil)
createDigitalAppFileCommand := &command.CreateAppTableFileCommand{}
controller.Unmarshal(createDigitalAppFileCommand)
ctx := ParseContext(controller.BaseController)
createFileCommand, err := fileService.CreateAppTableFile(ctx, createDigitalAppFileCommand)
if err != nil {
controller.Response(nil, err)
return
}
createFileCommand.AppKey = ParseAppKey(controller.BaseController)
data, err := fileService.CreateFile(&domain.Context{}, createFileCommand)
controller.Response(data, err)
}
func (controller *FileController) DeleteAppTableFile() {
fileService := service.NewFileService(nil)
cmd := &command.DeleteAppTableFileCommand{}
controller.Unmarshal(cmd)
cmd.AppKey = ParseAppKey(controller.BaseController)
data, err := fileService.DeleteAppTableFile(&domain.Context{}, cmd)
controller.Response(data, err)
}
func (controller *FileController) AppendDataAppTableFile() {
fileService := service.NewFileService(nil)
cmd := &command.AppTableFileAppendDataCommand{}
controller.Unmarshal(cmd)
cmd.AppKey = ParseAppKey(controller.BaseController)
data, err := fileService.AppTableFileAppendData(&domain.Context{}, cmd)
controller.Response(data, err)
}
func (controller *FileController) ListAppTableFile() {
fileService := service.NewFileService(nil)
cmd := &query.ListAppTableFileCommand{}
controller.Unmarshal(cmd)
cmd.AppKey = ParseAppKey(controller.BaseController)
data, err := fileService.AppTableFileList(&domain.Context{}, cmd)
controller.Response(data, err)
}
func (controller *FileController) UpdateFile() {
fileService := service.NewFileService(nil)
updateFileCommand := &command.UpdateFileCommand{}
... ... @@ -82,6 +126,22 @@ func (controller *FileController) SearchSourceFile() {
controller.Response(data, err)
}
func (controller *FileController) SearchAppSourceFile() {
fileService := service.NewFileService(nil)
cmd := &query.SearchFileQuery{}
Must(controller.Unmarshal(cmd))
cmd.FileType = domain.SourceFile
data, err := fileService.SearchAppFile(ParseContext(controller.BaseController), cmd)
controller.Response(data, err)
}
func (controller *FileController) GetAppFile() {
fileService := service.NewFileService(nil)
appKey := controller.GetString("app_key", "")
data, err := fileService.GetAppFile(ParseContext(controller.BaseController), appKey, "")
controller.Response(data, err)
}
func (controller *FileController) SearchVerifiedFile() {
fileService := service.NewFileService(nil)
cmd := &query.SearchFileQuery{}
... ...
package middleware
import (
"github.com/beego/beego/v2/server/web/context"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
"net/http"
"time"
)
func AppAccessFilter() func(ctx *context.Context) {
authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)
authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond
authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond
return func(ctx *context.Context) {
token := ctx.Request.Header.Get("x-mmm-accesstoken")
appKey := ctx.Request.Header.Get("x-mmm-appkey")
if len(appKey) == 0 || len(token) == 0 {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
return
}
response, err := authLib.AppLogin(authlib.RequestAppLogin{
AppKey: appKey,
Token: token,
})
if err != nil {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
return
}
if !response.AppEnabled {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidApp), false, false)
return
}
ctx.Input.SetData("AppKey", appKey)
}
}
... ...
package middleware
import (
"github.com/beego/beego/v2/server/web/context"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/constant"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/domain"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/infrastructure/api/authlib"
"net/http"
"strings"
"time"
)
func JwtFilter() func(ctx *context.Context) {
authLib := authlib.NewApiAuthLib(constant.AUTH_SERVER_HOST)
authLib.BaseServiceGateway.ConnectTimeout = 200 * time.Millisecond
authLib.BaseServiceGateway.ReadWriteTimeout = 200 * time.Millisecond
return func(ctx *context.Context) {
//token := ctx.Request.Header.Get("Authorization")
token := ctx.Request.Header.Get("x-mmm-accesstoken")
if len(token) > 0 {
token = strings.TrimPrefix(token, "Bearer ")
userToken := &domain.UserToken{}
err := userToken.ParseToken(token)
if err != nil {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
return
}
if userToken.UserId > 0 && userToken.CompanyId > 0 {
loginCheckResponse, _ := authLib.LoginCheck(authlib.RequestLoginCheck{Token: token})
if loginCheckResponse != nil && loginCheckResponse.Code == 901 {
ctx.Output.SetStatus(http.StatusOK)
ctx.Output.JSON(WithCodeMsgResponse(domain.InvalidRefreshToken), false, false)
return
}
}
ctx.Input.SetData("UserToken", userToken)
ctx.Input.SetData("Accesstoken", token)
}
}
}
func WithCodeMsgResponse(code int) map[string]interface{} {
msg := "token 过期或无效,需刷新令牌"
if codeMsg, ok := domain.CodeMsg[code]; ok {
msg = codeMsg
}
return map[string]interface{}{
"msg": msg,
"code": code,
"data": struct{}{},
}
}
... ...
package routers
import (
"github.com/beego/beego/v2/server/web"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
)
func init() {
web.InsertFilter("/api/app-table-file/*", web.BeforeRouter, middleware.AppAccessFilter())
web.Router("/api/app-table-file/create", &controllers.FileController{}, "Post:CreateAppTableFile")
web.Router("/api/app-table-file/delete", &controllers.FileController{}, "Post:DeleteAppTableFile")
web.Router("/api/app-table-file/append-data", &controllers.FileController{}, "Post:AppendDataAppTableFile")
web.Router("/api/app-table-file/list", &controllers.FileController{}, "Post:ListAppTableFile")
}
... ...
... ... @@ -3,8 +3,10 @@ package routers
import (
"github.com/beego/beego/v2/server/web"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/controllers"
"gitlab.fjmaimaimai.com/allied-creation/character-library-metadata-bastion/pkg/port/beego/middleware"
)
func init() {
web.InsertFilter("/static/*", web.BeforeRouter, middleware.JwtFilter())
web.Router("/static/:filename", &controllers.DownloadFileController{}, "*:DownloadHandle")
}
... ...
... ... @@ -15,11 +15,14 @@ func init() {
web.Router("/data/files/check-status", &controllers.FileController{}, "Post:CheckFileVerifyStatus")
web.Router("/data/files/search", &controllers.FileController{}, "Post:SearchFile")
web.Router("/data/files/search-source-file", &controllers.FileController{}, "Post:SearchSourceFile")
web.Router("/data/files/search-app-source-file", &controllers.FileController{}, "Post:SearchAppSourceFile")
web.Router("/data/files/search-verified-file", &controllers.FileController{}, "Post:SearchVerifiedFile")
web.Router("/data/files/cancel-verifying-file", &controllers.FileController{}, "Post:CancelVerifyingFile")
web.Router("/data/files/prepare-temporary-file", &controllers.FileController{}, "Post:PrepareTemporaryFile")
web.Router("/data/files/export-file", &controllers.FileController{}, "Post:ExportFile")
web.Router("/data/files/app-file", &controllers.FileController{}, "Get:GetAppFile")
web.Router("/data/file-preview", &controllers.FileController{}, "Post:FilePreview")
web.Router("/data/edit-data-table", &controllers.FileController{}, "Post:EditDataTable")
web.Router("/data/flush-data-table", &controllers.FileController{}, "Post:FlushDataTable")
... ...