This commit is contained in:
parent
c4b114762b
commit
c554152f4f
|
|
@ -72,9 +72,9 @@ func wireApp(confServer *conf.Server, runtime *conf.Runtime, logger *slog.Logger
|
||||||
cleanup()
|
cleanup()
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage)
|
mediaUsecase := biz.NewMediaUsecase(mediaRepo, fileStorage, runtimeSettings)
|
||||||
taskExecutor := worker.NewTaskExecutor(taskUsecase, mediaUsecase, runtime)
|
taskExecutor := worker.NewTaskExecutor(taskUsecase, mediaUsecase, runtime)
|
||||||
taskScheduler := worker.NewTaskScheduler(taskUsecase, taskExecutor, logger)
|
taskScheduler := worker.NewTaskScheduler(taskUsecase, authorityUsecase, taskExecutor, logger)
|
||||||
taskRuntime := worker.NewTaskRuntime(taskScheduler)
|
taskRuntime := worker.NewTaskRuntime(taskScheduler)
|
||||||
taskApplicationUsecase := biz.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
|
taskApplicationUsecase := biz.NewTaskApplicationUsecase(taskUsecase, taskRuntime)
|
||||||
taskService := service.NewTaskService(taskApplicationUsecase)
|
taskService := service.NewTaskService(taskApplicationUsecase)
|
||||||
|
|
|
||||||
|
|
@ -104,12 +104,16 @@ admin:
|
||||||
# Upload chunks are stored below .chunks in the selected storage backend.
|
# Upload chunks are stored below .chunks in the selected storage backend.
|
||||||
session_ttl: 24
|
session_ttl: 24
|
||||||
max_file_size: 0
|
max_file_size: 0
|
||||||
|
chunk_dir: .chunks
|
||||||
system:
|
system:
|
||||||
use_redis: false
|
use_redis: false
|
||||||
use_multipoint: false
|
use_multipoint: false
|
||||||
use_strict_auth: false
|
use_strict_auth: false
|
||||||
disable_auto_migrate: false
|
disable_auto_migrate: false
|
||||||
use_mongo: false
|
use_mongo: false
|
||||||
|
addr: 8000
|
||||||
|
iplimit_count: 0
|
||||||
|
iplimit_time: 0
|
||||||
zap:
|
zap:
|
||||||
level: info
|
level: info
|
||||||
prefix: "[kra] "
|
prefix: "[kra] "
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,20 @@ func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, er
|
||||||
if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil {
|
if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil {
|
||||||
parent := byID[*item.ParentID]
|
parent := byID[*item.ParentID]
|
||||||
parent.Children = append(parent.Children, item)
|
parent.Children = append(parent.Children, item)
|
||||||
} else {
|
} else if item.ParentID == nil || *item.ParentID == 0 {
|
||||||
roots = append(roots, item)
|
roots = append(roots, item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// In GVA strict-authority mode a non-root actor receives its direct
|
||||||
|
// children as the top-level result even though their parent is omitted.
|
||||||
|
if len(roots) == 0 {
|
||||||
|
if actor, ok := ActorFromContext(ctx); ok {
|
||||||
|
for _, item := range items {
|
||||||
|
if item.ParentID != nil && *item.ParentID == actor.AuthorityID {
|
||||||
|
roots = append(roots, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return roots, nil
|
return roots, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ type StoredFile struct {
|
||||||
Path string
|
Path string
|
||||||
URL string
|
URL string
|
||||||
Size int64
|
Size int64
|
||||||
|
LastModified time.Time
|
||||||
|
ContentType string
|
||||||
}
|
}
|
||||||
|
|
||||||
// FileStorage owns the persistence boundary for uploaded files.
|
// FileStorage owns the persistence boundary for uploaded files.
|
||||||
|
|
@ -50,6 +52,7 @@ type CaptchaSettings struct {
|
||||||
type MediaSettings struct {
|
type MediaSettings struct {
|
||||||
SessionTTL int
|
SessionTTL int
|
||||||
MaxFileSize int64
|
MaxFileSize int64
|
||||||
|
ChunkDir string
|
||||||
}
|
}
|
||||||
|
|
||||||
// RuntimeSettings exposes only the active values needed by the application.
|
// RuntimeSettings exposes only the active values needed by the application.
|
||||||
|
|
@ -75,6 +78,7 @@ type AuthClaims struct {
|
||||||
Username string
|
Username string
|
||||||
NickName string
|
NickName string
|
||||||
AuthorityID uint
|
AuthorityID uint
|
||||||
|
UserType string
|
||||||
BufferTime time.Duration
|
BufferTime time.Duration
|
||||||
MustChangePwd bool
|
MustChangePwd bool
|
||||||
Issuer string
|
Issuer string
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,11 @@ type MediaRepo interface {
|
||||||
type MediaUsecase struct {
|
type MediaUsecase struct {
|
||||||
MediaRepo
|
MediaRepo
|
||||||
files FileStorage
|
files FileStorage
|
||||||
|
settings RuntimeSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMediaUsecase(repo MediaRepo, files FileStorage) *MediaUsecase {
|
func NewMediaUsecase(repo MediaRepo, files FileStorage, settings RuntimeSettings) *MediaUsecase {
|
||||||
return &MediaUsecase{MediaRepo: repo, files: files}
|
return &MediaUsecase{MediaRepo: repo, files: files, settings: settings}
|
||||||
}
|
}
|
||||||
|
|
||||||
var allowedMediaExtensions = map[string]bool{
|
var allowedMediaExtensions = map[string]bool{
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -15,6 +16,20 @@ import (
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func (uc *MediaUsecase) chunkPrefix(uploadID uint) string {
|
||||||
|
directory := ".chunks"
|
||||||
|
if uc.settings != nil {
|
||||||
|
if configured := strings.Trim(uc.settings.MediaSettings().ChunkDir, "/\\ "); configured != "" {
|
||||||
|
directory = configured
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return path.Join(directory, fmt.Sprintf("%d", uploadID))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (uc *MediaUsecase) chunkKey(uploadID uint, index int) string {
|
||||||
|
return path.Join(uc.chunkPrefix(uploadID), fmt.Sprintf("%08d", index))
|
||||||
|
}
|
||||||
|
|
||||||
func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*UploadSession, *MediaFile, []int, error) {
|
func (uc *MediaUsecase) InitUpload(ctx context.Context, userID uint, name, hash string, size, chunkSize int64, total int) (*UploadSession, *MediaFile, []int, error) {
|
||||||
if err := validateMediaName(name); err != nil {
|
if err := validateMediaName(name); err != nil {
|
||||||
return nil, nil, nil, err
|
return nil, nil, nil, err
|
||||||
|
|
@ -57,7 +72,7 @@ func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, in
|
||||||
return errors.New("上传会话状态不允许收片")
|
return errors.New("上传会话状态不允许收片")
|
||||||
}
|
}
|
||||||
hash := md5.New()
|
hash := md5.New()
|
||||||
key := fmt.Sprintf(".chunks/%d/%08d", uploadID, index)
|
key := uc.chunkKey(uploadID, index)
|
||||||
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
|
stored, err := uc.files.Put(ctx, key, io.TeeReader(reader, hash))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -95,7 +110,7 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
|
||||||
if chunk.Index != index {
|
if chunk.Index != index {
|
||||||
return fail(errors.New("分片序号不连续"))
|
return fail(errors.New("分片序号不连续"))
|
||||||
}
|
}
|
||||||
names = append(names, fmt.Sprintf(".chunks/%d/%08d", uploadID, index))
|
names = append(names, uc.chunkKey(uploadID, index))
|
||||||
}
|
}
|
||||||
ext := strings.ToLower(filepath.Ext(session.FileName))
|
ext := strings.ToLower(filepath.Ext(session.FileName))
|
||||||
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
|
key := time.Now().Format("20060102") + "/" + uuid.NewString() + ext
|
||||||
|
|
@ -114,7 +129,7 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
|
||||||
}
|
}
|
||||||
_ = uc.CompleteUploadSession(ctx, uploadID, key, media.ID)
|
_ = uc.CompleteUploadSession(ctx, uploadID, key, media.ID)
|
||||||
_ = uc.DeleteChunks(ctx, uploadID)
|
_ = uc.DeleteChunks(ctx, uploadID)
|
||||||
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
|
_ = uc.files.DeletePrefix(ctx, uc.chunkPrefix(uploadID))
|
||||||
return media, nil
|
return media, nil
|
||||||
}
|
}
|
||||||
func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint) error {
|
func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint) error {
|
||||||
|
|
@ -123,7 +138,7 @@ func (uc *MediaUsecase) CancelUpload(ctx context.Context, userID, uploadID uint)
|
||||||
return errors.New("上传会话不存在或无权操作")
|
return errors.New("上传会话不存在或无权操作")
|
||||||
}
|
}
|
||||||
_ = uc.DeleteChunks(ctx, uploadID)
|
_ = uc.DeleteChunks(ctx, uploadID)
|
||||||
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
|
_ = uc.files.DeletePrefix(ctx, uc.chunkPrefix(uploadID))
|
||||||
return uc.DeleteUploadSession(ctx, uploadID)
|
return uc.DeleteUploadSession(ctx, uploadID)
|
||||||
}
|
}
|
||||||
func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error {
|
func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error {
|
||||||
|
|
@ -138,7 +153,7 @@ func (uc *MediaUsecase) CleanupStale(ctx context.Context, ttlHours int) error {
|
||||||
if err = uc.DeleteUploadData(ctx, id); err != nil {
|
if err = uc.DeleteUploadData(ctx, id); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", id)); err != nil {
|
if err = uc.files.DeletePrefix(ctx, uc.chunkPrefix(id)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -91,8 +91,8 @@ type TaskRuntime interface {
|
||||||
TriggerID(context.Context, uint) error
|
TriggerID(context.Context, uint) error
|
||||||
NextRuns() map[uint]time.Time
|
NextRuns() map[uint]time.Time
|
||||||
Reload(context.Context) error
|
Reload(context.Context) error
|
||||||
Subscribe() chan []byte
|
Subscribe(uint) chan []byte
|
||||||
Unsubscribe(chan []byte)
|
Unsubscribe(uint, chan []byte)
|
||||||
}
|
}
|
||||||
|
|
||||||
type TaskUsecase struct{ TaskRepo }
|
type TaskUsecase struct{ TaskRepo }
|
||||||
|
|
@ -115,7 +115,7 @@ func (uc *TaskUsecase) Validate(value *TimedTask) error {
|
||||||
switch value.ExecutorType {
|
switch value.ExecutorType {
|
||||||
case TaskExecutorMethod:
|
case TaskExecutorMethod:
|
||||||
if !registeredTaskMethod(value.MethodName) {
|
if !registeredTaskMethod(value.MethodName) {
|
||||||
return errors.New("方法未注册")
|
return fmt.Errorf("方法 %s 未注册", value.MethodName)
|
||||||
}
|
}
|
||||||
if len(value.Params) > 0 && !json.Valid(value.Params) {
|
if len(value.Params) > 0 && !json.Valid(value.Params) {
|
||||||
return errors.New("params 必须是合法 JSON")
|
return errors.New("params 必须是合法 JSON")
|
||||||
|
|
@ -128,7 +128,7 @@ func (uc *TaskUsecase) Validate(value *TimedTask) error {
|
||||||
if len(value.HTTPHeader) > 0 {
|
if len(value.HTTPHeader) > 0 {
|
||||||
headers := map[string]string{}
|
headers := map[string]string{}
|
||||||
if json.Unmarshal(value.HTTPHeader, &headers) != nil {
|
if json.Unmarshal(value.HTTPHeader, &headers) != nil {
|
||||||
return errors.New("httpHeader 必须是 JSON 对象")
|
return errors.New(`httpHeader 必须是 {"Key":"Value"} 形式的 JSON 对象`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
@ -229,7 +229,9 @@ func (uc *TaskApplicationUsecase) Reload(ctx context.Context) error {
|
||||||
return uc.runtime.Reload(ctx)
|
return uc.runtime.Reload(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *TaskApplicationUsecase) Subscribe() chan []byte { return uc.runtime.Subscribe() }
|
func (uc *TaskApplicationUsecase) Subscribe(userID uint) chan []byte {
|
||||||
func (uc *TaskApplicationUsecase) Unsubscribe(events chan []byte) {
|
return uc.runtime.Subscribe(userID)
|
||||||
uc.runtime.Unsubscribe(events)
|
}
|
||||||
|
func (uc *TaskApplicationUsecase) Unsubscribe(userID uint, events chan []byte) {
|
||||||
|
uc.runtime.Unsubscribe(userID, events)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1168,6 +1168,7 @@ type AdminBackend_Media struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
SessionTtl int32 `protobuf:"varint,1,opt,name=session_ttl,json=sessionTtl,proto3" json:"session_ttl,omitempty"`
|
SessionTtl int32 `protobuf:"varint,1,opt,name=session_ttl,json=sessionTtl,proto3" json:"session_ttl,omitempty"`
|
||||||
MaxFileSize int64 `protobuf:"varint,2,opt,name=max_file_size,json=maxFileSize,proto3" json:"max_file_size,omitempty"`
|
MaxFileSize int64 `protobuf:"varint,2,opt,name=max_file_size,json=maxFileSize,proto3" json:"max_file_size,omitempty"`
|
||||||
|
ChunkDir string `protobuf:"bytes,3,opt,name=chunk_dir,json=chunkDir,proto3" json:"chunk_dir,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -1216,6 +1217,13 @@ func (x *AdminBackend_Media) GetMaxFileSize() int64 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *AdminBackend_Media) GetChunkDir() string {
|
||||||
|
if x != nil {
|
||||||
|
return x.ChunkDir
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
type AdminBackend_Disk struct {
|
type AdminBackend_Disk struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
MountPoint string `protobuf:"bytes,1,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"`
|
MountPoint string `protobuf:"bytes,1,opt,name=mount_point,json=mountPoint,proto3" json:"mount_point,omitempty"`
|
||||||
|
|
@ -1267,6 +1275,9 @@ type AdminBackend_System struct {
|
||||||
UseStrictAuth bool `protobuf:"varint,3,opt,name=use_strict_auth,json=useStrictAuth,proto3" json:"use_strict_auth,omitempty"`
|
UseStrictAuth bool `protobuf:"varint,3,opt,name=use_strict_auth,json=useStrictAuth,proto3" json:"use_strict_auth,omitempty"`
|
||||||
DisableAutoMigrate bool `protobuf:"varint,4,opt,name=disable_auto_migrate,json=disableAutoMigrate,proto3" json:"disable_auto_migrate,omitempty"`
|
DisableAutoMigrate bool `protobuf:"varint,4,opt,name=disable_auto_migrate,json=disableAutoMigrate,proto3" json:"disable_auto_migrate,omitempty"`
|
||||||
UseMongo bool `protobuf:"varint,5,opt,name=use_mongo,json=useMongo,proto3" json:"use_mongo,omitempty"`
|
UseMongo bool `protobuf:"varint,5,opt,name=use_mongo,json=useMongo,proto3" json:"use_mongo,omitempty"`
|
||||||
|
Addr int32 `protobuf:"varint,6,opt,name=addr,proto3" json:"addr,omitempty"`
|
||||||
|
IplimitCount int32 `protobuf:"varint,7,opt,name=iplimit_count,json=iplimitCount,proto3" json:"iplimit_count,omitempty"`
|
||||||
|
IplimitTime int32 `protobuf:"varint,8,opt,name=iplimit_time,json=iplimitTime,proto3" json:"iplimit_time,omitempty"`
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
}
|
}
|
||||||
|
|
@ -1336,6 +1347,27 @@ func (x *AdminBackend_System) GetUseMongo() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (x *AdminBackend_System) GetAddr() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.Addr
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdminBackend_System) GetIplimitCount() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.IplimitCount
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *AdminBackend_System) GetIplimitTime() int32 {
|
||||||
|
if x != nil {
|
||||||
|
return x.IplimitTime
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
type AdminBackend_Zap struct {
|
type AdminBackend_Zap struct {
|
||||||
state protoimpl.MessageState `protogen:"open.v1"`
|
state protoimpl.MessageState `protogen:"open.v1"`
|
||||||
Level string `protobuf:"bytes,1,opt,name=level,proto3" json:"level,omitempty"`
|
Level string `protobuf:"bytes,1,opt,name=level,proto3" json:"level,omitempty"`
|
||||||
|
|
@ -2054,7 +2086,7 @@ const file_conf_conf_proto_rawDesc = "" +
|
||||||
"\x12connect_timeout_ms\x18\n" +
|
"\x12connect_timeout_ms\x18\n" +
|
||||||
" \x01(\x03R\x10connectTimeoutMs\x12\x15\n" +
|
" \x01(\x03R\x10connectTimeoutMs\x12\x15\n" +
|
||||||
"\x06is_zap\x18\v \x01(\bR\x05isZap\x120\n" +
|
"\x06is_zap\x18\v \x01(\bR\x05isZap\x120\n" +
|
||||||
"\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xb4\x1b\n" +
|
"\x05hosts\x18\f \x03(\v2\x1a.kratos.api.Data.MongoHostR\x05hosts\"\xad\x1c\n" +
|
||||||
"\fAdminBackend\x12#\n" +
|
"\fAdminBackend\x12#\n" +
|
||||||
"\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" +
|
"\rrouter_prefix\x18\x01 \x01(\tR\frouterPrefix\x12.\n" +
|
||||||
"\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" +
|
"\x03jwt\x18\x02 \x01(\v2\x1c.kratos.api.AdminBackend.JWTR\x03jwt\x12:\n" +
|
||||||
|
|
@ -2097,20 +2129,24 @@ const file_conf_conf_proto_rawDesc = "" +
|
||||||
"\bnickname\x18\x05 \x01(\tR\bnickname\x12\x12\n" +
|
"\bnickname\x18\x05 \x01(\tR\bnickname\x12\x12\n" +
|
||||||
"\x04port\x18\x06 \x01(\x05R\x04port\x12\x15\n" +
|
"\x04port\x18\x06 \x01(\x05R\x04port\x12\x15\n" +
|
||||||
"\x06is_ssl\x18\a \x01(\bR\x05isSsl\x12\"\n" +
|
"\x06is_ssl\x18\a \x01(\bR\x05isSsl\x12\"\n" +
|
||||||
"\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1aL\n" +
|
"\ris_login_auth\x18\b \x01(\bR\visLoginAuth\x1ai\n" +
|
||||||
"\x05Media\x12\x1f\n" +
|
"\x05Media\x12\x1f\n" +
|
||||||
"\vsession_ttl\x18\x01 \x01(\x05R\n" +
|
"\vsession_ttl\x18\x01 \x01(\x05R\n" +
|
||||||
"sessionTtl\x12\"\n" +
|
"sessionTtl\x12\"\n" +
|
||||||
"\rmax_file_size\x18\x02 \x01(\x03R\vmaxFileSize\x1a'\n" +
|
"\rmax_file_size\x18\x02 \x01(\x03R\vmaxFileSize\x12\x1b\n" +
|
||||||
|
"\tchunk_dir\x18\x03 \x01(\tR\bchunkDir\x1a'\n" +
|
||||||
"\x04Disk\x12\x1f\n" +
|
"\x04Disk\x12\x1f\n" +
|
||||||
"\vmount_point\x18\x01 \x01(\tR\n" +
|
"\vmount_point\x18\x01 \x01(\tR\n" +
|
||||||
"mountPoint\x1a\xc3\x01\n" +
|
"mountPoint\x1a\x9f\x02\n" +
|
||||||
"\x06System\x12\x1b\n" +
|
"\x06System\x12\x1b\n" +
|
||||||
"\tuse_redis\x18\x01 \x01(\bR\buseRedis\x12%\n" +
|
"\tuse_redis\x18\x01 \x01(\bR\buseRedis\x12%\n" +
|
||||||
"\x0euse_multipoint\x18\x02 \x01(\bR\ruseMultipoint\x12&\n" +
|
"\x0euse_multipoint\x18\x02 \x01(\bR\ruseMultipoint\x12&\n" +
|
||||||
"\x0fuse_strict_auth\x18\x03 \x01(\bR\ruseStrictAuth\x120\n" +
|
"\x0fuse_strict_auth\x18\x03 \x01(\bR\ruseStrictAuth\x120\n" +
|
||||||
"\x14disable_auto_migrate\x18\x04 \x01(\bR\x12disableAutoMigrate\x12\x1b\n" +
|
"\x14disable_auto_migrate\x18\x04 \x01(\bR\x12disableAutoMigrate\x12\x1b\n" +
|
||||||
"\tuse_mongo\x18\x05 \x01(\bR\buseMongo\x1a\xf6\x03\n" +
|
"\tuse_mongo\x18\x05 \x01(\bR\buseMongo\x12\x12\n" +
|
||||||
|
"\x04addr\x18\x06 \x01(\x05R\x04addr\x12#\n" +
|
||||||
|
"\riplimit_count\x18\a \x01(\x05R\fiplimitCount\x12!\n" +
|
||||||
|
"\fiplimit_time\x18\b \x01(\x05R\viplimitTime\x1a\xf6\x03\n" +
|
||||||
"\x03Zap\x12\x14\n" +
|
"\x03Zap\x12\x14\n" +
|
||||||
"\x05level\x18\x01 \x01(\tR\x05level\x12\x16\n" +
|
"\x05level\x18\x01 \x01(\tR\x05level\x12\x16\n" +
|
||||||
"\x06prefix\x18\x02 \x01(\tR\x06prefix\x12\x16\n" +
|
"\x06prefix\x18\x02 \x01(\tR\x06prefix\x12\x16\n" +
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,7 @@ message AdminBackend {
|
||||||
message Media {
|
message Media {
|
||||||
int32 session_ttl = 1;
|
int32 session_ttl = 1;
|
||||||
int64 max_file_size = 2;
|
int64 max_file_size = 2;
|
||||||
|
string chunk_dir = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Disk {
|
message Disk {
|
||||||
|
|
@ -139,6 +140,9 @@ message AdminBackend {
|
||||||
bool use_strict_auth = 3;
|
bool use_strict_auth = 3;
|
||||||
bool disable_auto_migrate = 4;
|
bool disable_auto_migrate = 4;
|
||||||
bool use_mongo = 5;
|
bool use_mongo = 5;
|
||||||
|
int32 addr = 6;
|
||||||
|
int32 iplimit_count = 7;
|
||||||
|
int32 iplimit_time = 8;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Zap {
|
message Zap {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,7 @@ func (r *announcementRepo) UserOptions(ctx context.Context) ([]biz.UserOption, e
|
||||||
Label string
|
Label string
|
||||||
Value uint
|
Value uint
|
||||||
}
|
}
|
||||||
err := r.data.gormDB.WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Where("deleted_at IS NULL").Scan(&rows).Error
|
err := r.data.gormDB.WithContext(ctx).Table("sys_users").Select("nick_name AS label, id AS value").Scan(&rows).Error
|
||||||
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -98,7 +98,7 @@ func (r *authorityAccessRepo) CreateAuthority(ctx context.Context, value *biz.Au
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
|
defaults := []struct{ path, method string }{{"/menu/getMenu", "POST"}, {"/jwt/jsonInBlacklist", "POST"}, {"/base/login", "POST"}, {"/user/changePassword", "POST"}, {"/user/setUserAuthority", "POST"}, {"/user/getUserInfo", "GET"}, {"/user/setSelfInfo", "PUT"}, {"/fileUploadAndDownload/upload", "POST"}, {"/sysDictionary/findSysDictionary", "GET"}}
|
||||||
for _, item := range defaults {
|
for _, item := range defaults {
|
||||||
var api apiPO
|
var api apiPO
|
||||||
if err := tx.Where("path = ? AND method = ?", item.path, item.method).First(&api).Error; err == nil {
|
if err := tx.Where("path = ? AND method = ?", item.path, item.method).First(&api).Error; err == nil {
|
||||||
|
|
@ -186,13 +186,23 @@ func (r *authorityAccessRepo) CopyAuthority(ctx context.Context, sourceID uint,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
func (r *authorityAccessRepo) UpdateAuthority(ctx context.Context, value *biz.Authority) error {
|
||||||
return r.data.gormDB.WithContext(ctx).Model(&authorityPO{}).Where("authority_id = ?", value.AuthorityID).Updates(map[string]any{"authority_name": value.AuthorityName, "parent_id": value.ParentID, "data_scope": value.DataScope, "default_router": value.DefaultRouter}).Error
|
db := r.data.gormDB.WithContext(ctx)
|
||||||
|
var current authorityPO
|
||||||
|
if err := db.Where("authority_id = ?", value.AuthorityID).First(¤t).Error; err != nil {
|
||||||
|
return errors.New("查询角色数据失败")
|
||||||
|
}
|
||||||
|
updates := &authorityPO{AuthorityName: value.AuthorityName, ParentID: value.ParentID, DataScope: value.DataScope, DefaultRouter: value.DefaultRouter}
|
||||||
|
return db.Model(¤t).Updates(updates).Error
|
||||||
}
|
}
|
||||||
func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) error {
|
||||||
if id == 888 {
|
|
||||||
return errors.New("不能删除超级管理员")
|
|
||||||
}
|
|
||||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
var authority authorityPO
|
||||||
|
if err := tx.Where("authority_id = ?", id).First(&authority).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errors.New("该角色不存在")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
var users, children int64
|
var users, children int64
|
||||||
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Count(&users).Error; err != nil {
|
if err := tx.Model(&userAuthorityPO{}).Where("sys_authority_authority_id = ?", id).Count(&users).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -221,15 +231,12 @@ func (r *authorityAccessRepo) DeleteAuthority(ctx context.Context, id uint) erro
|
||||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil {
|
if err := tx.Where("authority_id = ?", id).Delete(&authorityButtonPO{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if err := tx.Where("authority_id = ?", id).Delete(&authorityDepartmentPO{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return tx.Delete(&authorityPO{}, "authority_id = ?", id).Error
|
return tx.Delete(&authorityPO{}, "authority_id = ?", id).Error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
func (r *authorityAccessRepo) ListAuthorities(ctx context.Context) ([]*biz.Authority, error) {
|
||||||
var pos []authorityPO
|
var pos []authorityPO
|
||||||
if err := r.data.gormDB.WithContext(ctx).Order("authority_id").Find(&pos).Error; err != nil {
|
if err := r.data.gormDB.WithContext(ctx).Find(&pos).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var allowed map[uint]bool
|
var allowed map[uint]bool
|
||||||
|
|
|
||||||
|
|
@ -66,6 +66,16 @@ func (r *initializationRepo) ConfigurationJSON() (json.RawMessage, error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
maskDataSecrets(dataConfig)
|
maskDataSecrets(dataConfig)
|
||||||
|
safeAdmin := cloneAdminConfig(adminConfig)
|
||||||
|
if safeAdmin != nil {
|
||||||
|
if safeAdmin.Jwt != nil {
|
||||||
|
safeAdmin.Jwt.SigningKey = "******"
|
||||||
|
}
|
||||||
|
if safeAdmin.Email != nil {
|
||||||
|
safeAdmin.Email.Secret = "******"
|
||||||
|
}
|
||||||
|
maskStorageSecrets(safeAdmin.Storage)
|
||||||
|
}
|
||||||
dataMap := map[string]any{}
|
dataMap := map[string]any{}
|
||||||
if dataConfig != nil {
|
if dataConfig != nil {
|
||||||
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(dataConfig)
|
raw, err := protojson.MarshalOptions{UseProtoNames: true}.Marshal(dataConfig)
|
||||||
|
|
@ -76,7 +86,9 @@ func (r *initializationRepo) ConfigurationJSON() (json.RawMessage, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return json.Marshal(map[string]any{"config": map[string]any{"admin": admin, "email": email, "data": dataMap}})
|
config := gvaConfiguration(dataConfig, safeAdmin)
|
||||||
|
config["admin"], config["email"], config["data"] = admin, email, dataMap
|
||||||
|
return json.Marshal(map[string]any{"config": config})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
|
func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json.RawMessage) error {
|
||||||
|
|
@ -86,6 +98,9 @@ func (r *initializationRepo) SaveConfigurationJSON(ctx context.Context, raw json
|
||||||
}
|
}
|
||||||
nextData := cloneDataConfig(currentData)
|
nextData := cloneDataConfig(currentData)
|
||||||
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
nextAdmin := proto.Clone(currentAdmin).(*conf.AdminBackend)
|
||||||
|
if err := applyGVAConfiguration(raw, nextData, nextAdmin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
var value configurationEnvelope
|
var value configurationEnvelope
|
||||||
if err := json.Unmarshal(raw, &value); err != nil {
|
if err := json.Unmarshal(raw, &value); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -154,6 +169,13 @@ func cloneDataConfig(value *conf.Data) *conf.Data {
|
||||||
return proto.Clone(value).(*conf.Data)
|
return proto.Clone(value).(*conf.Data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func cloneAdminConfig(value *conf.AdminBackend) *conf.AdminBackend {
|
||||||
|
if value == nil {
|
||||||
|
return &conf.AdminBackend{}
|
||||||
|
}
|
||||||
|
return proto.Clone(value).(*conf.AdminBackend)
|
||||||
|
}
|
||||||
|
|
||||||
func refreshDatabaseSources(value *conf.Data) error {
|
func refreshDatabaseSources(value *conf.Data) error {
|
||||||
if value == nil {
|
if value == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|
@ -222,6 +244,11 @@ func applyDataPatch(target, patch *conf.Data) {
|
||||||
func applyAdminPatch(target, patch *conf.AdminBackend) {
|
func applyAdminPatch(target, patch *conf.AdminBackend) {
|
||||||
target.RouterPrefix = patch.RouterPrefix
|
target.RouterPrefix = patch.RouterPrefix
|
||||||
if patch.System != nil {
|
if patch.System != nil {
|
||||||
|
if target.System != nil {
|
||||||
|
patch.System.Addr = target.System.Addr
|
||||||
|
patch.System.IplimitCount = target.System.IplimitCount
|
||||||
|
patch.System.IplimitTime = target.System.IplimitTime
|
||||||
|
}
|
||||||
target.System = patch.System
|
target.System = patch.System
|
||||||
}
|
}
|
||||||
if patch.Jwt != nil {
|
if patch.Jwt != nil {
|
||||||
|
|
@ -234,6 +261,9 @@ func applyAdminPatch(target, patch *conf.AdminBackend) {
|
||||||
target.Local = patch.Local
|
target.Local = patch.Local
|
||||||
}
|
}
|
||||||
if patch.Media != nil {
|
if patch.Media != nil {
|
||||||
|
if patch.Media.ChunkDir == "" && target.Media != nil {
|
||||||
|
patch.Media.ChunkDir = target.Media.ChunkDir
|
||||||
|
}
|
||||||
target.Media = patch.Media
|
target.Media = patch.Media
|
||||||
}
|
}
|
||||||
if patch.Storage != nil {
|
if patch.Storage != nil {
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,10 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"kra/internal/conf"
|
"kra/internal/conf"
|
||||||
|
|
||||||
|
|
@ -84,6 +86,45 @@ func mergeYAMLNode(dst, src *yaml.Node) {
|
||||||
dst.HeadComment, dst.LineComment, dst.FootComment = head, line, foot
|
dst.HeadComment, dst.LineComment, dst.FootComment = head, line, foot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func yamlMappingValue(node *yaml.Node, keys ...string) *yaml.Node {
|
||||||
|
if node == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if node.Kind == yaml.DocumentNode && len(node.Content) > 0 {
|
||||||
|
node = node.Content[0]
|
||||||
|
}
|
||||||
|
for _, key := range keys {
|
||||||
|
if node.Kind != yaml.MappingNode {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
var next *yaml.Node
|
||||||
|
for i := 0; i+1 < len(node.Content); i += 2 {
|
||||||
|
if node.Content[i].Value == key {
|
||||||
|
next = node.Content[i+1]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if next == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
node = next
|
||||||
|
}
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
func setServerHTTPPort(document *yaml.Node, port int32) error {
|
||||||
|
if port <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
host := "0.0.0.0"
|
||||||
|
if addr := yamlMappingValue(document, "server", "http", "addr"); addr != nil {
|
||||||
|
if currentHost, _, err := net.SplitHostPort(addr.Value); err == nil && currentHost != "" {
|
||||||
|
host = currentHost
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return setYAMLMapping(document, "server", map[string]any{"http": map[string]any{"addr": net.JoinHostPort(host, strconv.Itoa(int(port)))}})
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Data) persistConfig() error {
|
func (d *Data) persistConfig() error {
|
||||||
dataConfig, adminConfig := d.runtime.Values()
|
dataConfig, adminConfig := d.runtime.Values()
|
||||||
return d.persistConfigValues(dataConfig, adminConfig)
|
return d.persistConfigValues(dataConfig, adminConfig)
|
||||||
|
|
@ -122,6 +163,11 @@ func (d *Data) persistConfigValuesLocked(dataConfig *conf.Data, adminConfig *con
|
||||||
if err = setYAMLMapping(&document, "admin", adminValue); err != nil {
|
if err = setYAMLMapping(&document, "admin", adminValue); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if adminConfig.System != nil {
|
||||||
|
if err = setServerHTTPPort(&document, adminConfig.System.Addr); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
return writeConfigDocument(configPath, &document)
|
return writeConfigDocument(configPath, &document)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -108,12 +108,6 @@ func (r *departmentRepo) DeleteDepartment(ctx context.Context, id uint) error {
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
return errors.New("存在子部门,不允许删除")
|
return errors.New("存在子部门,不允许删除")
|
||||||
}
|
}
|
||||||
if err := r.data.gormDB.WithContext(ctx).Model(&userPO{}).Where("dept_id = ?", id).Count(&count).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
return errors.New("该部门下存在用户,不允许删除")
|
|
||||||
}
|
|
||||||
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil {
|
if err := r.data.gormDB.WithContext(ctx).Model(&userDepartmentPO{}).Where("sys_department_id = ?", id).Count(&count).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -135,7 +129,7 @@ func (r *departmentRepo) FindDepartment(ctx context.Context, id uint) (*biz.Depa
|
||||||
}
|
}
|
||||||
func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*biz.Department, error) {
|
||||||
var pos []departmentPO
|
var pos []departmentPO
|
||||||
db := r.data.gormDB.WithContext(ctx).Order("sort,id")
|
db := r.data.gormDB.WithContext(ctx).Order("sort")
|
||||||
if name != "" {
|
if name != "" {
|
||||||
db = db.Where("name LIKE ?", "%"+name+"%")
|
db = db.Where("name LIKE ?", "%"+name+"%")
|
||||||
}
|
}
|
||||||
|
|
@ -160,7 +154,7 @@ func (r *departmentRepo) ListDepartments(ctx context.Context, name string) ([]*b
|
||||||
n := nodes[po.ID]
|
n := nodes[po.ID]
|
||||||
if p := nodes[po.ParentID]; p != nil {
|
if p := nodes[po.ParentID]; p != nil {
|
||||||
p.Children = append(p.Children, n)
|
p.Children = append(p.Children, n)
|
||||||
} else {
|
} else if po.ParentID == 0 {
|
||||||
roots = append(roots, n)
|
roots = append(roots, n)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,19 +111,18 @@ func (r *dictionaryRepo) UpdateDictionary(ctx context.Context, v *biz.Dictionary
|
||||||
return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error
|
return r.data.gormDB.WithContext(ctx).Model(&dictionaryPO{}).Where("id = ?", v.ID).Updates(map[string]any{"name": v.Name, "type": v.Type, "status": v.Status, "desc": v.Desc, "parent_id": v.ParentID}).Error
|
||||||
}
|
}
|
||||||
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
func (r *dictionaryRepo) DeleteDictionary(ctx context.Context, id uint) error {
|
||||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
db := r.data.gormDB.WithContext(ctx)
|
||||||
var count int64
|
var dictionary dictionaryPO
|
||||||
if err := tx.Model(&dictionaryPO{}).Where("parent_id = ?", id).Count(&count).Error; err != nil {
|
if err := db.First(&dictionary, id).Error; err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
return errors.New("请不要搞事")
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if count > 0 {
|
if err := db.Delete(&dictionary).Error; err != nil {
|
||||||
return errors.New("存在子字典不可删除")
|
|
||||||
}
|
|
||||||
if err := tx.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error; err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.Delete(&dictionaryPO{}, id).Error
|
return db.Where("sys_dictionary_id = ?", id).Delete(&dictionaryDetailPO{}).Error
|
||||||
})
|
|
||||||
}
|
}
|
||||||
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
func (r *dictionaryRepo) FindDictionary(ctx context.Context, id uint, typ string, status *bool, details bool) (*biz.Dictionary, error) {
|
||||||
var po dictionaryPO
|
var po dictionaryPO
|
||||||
|
|
|
||||||
|
|
@ -111,21 +111,7 @@ func (r *exportRepo) UpdateExportTemplate(ctx context.Context, v *biz.ExportTemp
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error {
|
func (r *exportRepo) DeleteExportTemplates(ctx context.Context, ids []uint) error {
|
||||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return r.data.gormDB.WithContext(ctx).Delete(&[]exportTemplatePO{}, "id IN ?", ids).Error
|
||||||
var tids []string
|
|
||||||
if err := tx.Model(&exportTemplatePO{}).Where("id IN ?", ids).Pluck("template_id", &tids).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if len(tids) > 0 {
|
|
||||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportConditionPO{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := tx.Where("template_id IN ?", tids).Delete(&exportJoinPO{}).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tx.Delete(&exportTemplatePO{}, ids).Error
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) {
|
func (r *exportRepo) FindExportTemplate(ctx context.Context, id uint, tid string) (*biz.ExportTemplate, error) {
|
||||||
var po exportTemplatePO
|
var po exportTemplatePO
|
||||||
|
|
@ -173,31 +159,9 @@ func (r *exportRepo) ListExportTemplates(ctx context.Context, page, size int, q
|
||||||
if err := applyPagination(db, page, size, 0).Find(&pos).Error; err != nil {
|
if err := applyPagination(db, page, size, 0).Find(&pos).Error; err != nil {
|
||||||
return nil, 0, err
|
return nil, 0, err
|
||||||
}
|
}
|
||||||
templateIDs := make([]string, 0, len(pos))
|
|
||||||
for _, po := range pos {
|
|
||||||
templateIDs = append(templateIDs, po.TemplateID)
|
|
||||||
}
|
|
||||||
var conditions []exportConditionPO
|
|
||||||
var joins []exportJoinPO
|
|
||||||
if len(templateIDs) > 0 {
|
|
||||||
if err := r.data.gormDB.WithContext(ctx).Where("template_id IN ?", templateIDs).Find(&conditions).Error; err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
if err := r.data.gormDB.WithContext(ctx).Where("template_id IN ?", templateIDs).Find(&joins).Error; err != nil {
|
|
||||||
return nil, 0, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
conditionsByTemplate := make(map[string][]exportConditionPO, len(pos))
|
|
||||||
for _, condition := range conditions {
|
|
||||||
conditionsByTemplate[condition.TemplateID] = append(conditionsByTemplate[condition.TemplateID], condition)
|
|
||||||
}
|
|
||||||
joinsByTemplate := make(map[string][]exportJoinPO, len(pos))
|
|
||||||
for _, join := range joins {
|
|
||||||
joinsByTemplate[join.TemplateID] = append(joinsByTemplate[join.TemplateID], join)
|
|
||||||
}
|
|
||||||
out := make([]*biz.ExportTemplate, 0, len(pos))
|
out := make([]*biz.ExportTemplate, 0, len(pos))
|
||||||
for _, po := range pos {
|
for _, po := range pos {
|
||||||
out = append(out, exportFromPO(po, conditionsByTemplate[po.TemplateID], joinsByTemplate[po.TemplateID]))
|
out = append(out, exportFromPO(po, nil, nil))
|
||||||
}
|
}
|
||||||
return out, total, nil
|
return out, total, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,545 @@
|
||||||
|
package data
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"kra/internal/conf"
|
||||||
|
|
||||||
|
"google.golang.org/protobuf/types/known/durationpb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func gvaDatabase(value *conf.Data_Database) map[string]any {
|
||||||
|
if value == nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
path := value.Host
|
||||||
|
if value.Driver == "sqlite" {
|
||||||
|
path = value.Path
|
||||||
|
}
|
||||||
|
return map[string]any{
|
||||||
|
"prefix": value.Prefix, "port": value.Port, "config": value.Config,
|
||||||
|
"db-name": value.Name, "username": value.User, "password": value.Password,
|
||||||
|
"path": path, "engine": value.Engine, "log-mode": value.LogMode,
|
||||||
|
"max-idle-conns": value.MaxIdleConns, "max-open-conns": value.MaxOpenConns,
|
||||||
|
"conn-max-lifetime": value.ConnMaxLifetime, "singular": value.Singular,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvaRedis(value *conf.Data_Redis) map[string]any {
|
||||||
|
if value == nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
return map[string]any{"name": value.Name, "addr": value.Addr, "password": value.Password, "db": value.Db, "useCluster": value.UseCluster, "clusterAddrs": value.ClusterAddrs}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvaObjectStore(value *conf.AdminBackend_ObjectStore) map[string]any {
|
||||||
|
if value == nil {
|
||||||
|
return map[string]any{}
|
||||||
|
}
|
||||||
|
return map[string]any{
|
||||||
|
"endpoint": value.Endpoint, "region": value.Region, "bucket": value.Bucket,
|
||||||
|
"access-key": value.AccessKey, "secret-key": value.SecretKey, "base-url": value.BaseUrl,
|
||||||
|
"path-prefix": value.PathPrefix, "use-ssl": value.UseSsl,
|
||||||
|
"s3-force-path-style": value.ForcePathStyle, "account-id": value.AccountId,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvaObjectStores(storage *conf.AdminBackend_Storage) map[string]map[string]any {
|
||||||
|
if storage == nil {
|
||||||
|
storage = &conf.AdminBackend_Storage{}
|
||||||
|
}
|
||||||
|
aliyun, huawei := storage.AliyunOss, storage.HuaweiObs
|
||||||
|
tencent, aws := storage.TencentCos, storage.AwsS3
|
||||||
|
cloudflare, minio := storage.CloudflareR2, storage.Minio
|
||||||
|
if aliyun == nil {
|
||||||
|
aliyun = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
if huawei == nil {
|
||||||
|
huawei = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
if tencent == nil {
|
||||||
|
tencent = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
if aws == nil {
|
||||||
|
aws = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
if cloudflare == nil {
|
||||||
|
cloudflare = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
if minio == nil {
|
||||||
|
minio = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
return map[string]map[string]any{
|
||||||
|
"aliyun-oss": {"endpoint": aliyun.Endpoint, "access-key-id": aliyun.AccessKey, "access-key-secret": aliyun.SecretKey, "bucket-name": aliyun.Bucket, "bucket-url": aliyun.BaseUrl, "base-path": aliyun.PathPrefix},
|
||||||
|
"hua-wei-obs": {"path": huawei.PathPrefix, "bucket": huawei.Bucket, "endpoint": huawei.Endpoint, "access-key": huawei.AccessKey, "secret-key": huawei.SecretKey},
|
||||||
|
"tencent-cos": {"bucket": tencent.Bucket, "region": tencent.Region, "secret-id": tencent.AccessKey, "secret-key": tencent.SecretKey, "base-url": tencent.BaseUrl, "path-prefix": tencent.PathPrefix},
|
||||||
|
"aws-s3": {"bucket": aws.Bucket, "region": aws.Region, "endpoint": aws.Endpoint, "secret-id": aws.AccessKey, "secret-key": aws.SecretKey, "base-url": aws.BaseUrl, "path-prefix": aws.PathPrefix, "s3-force-path-style": aws.ForcePathStyle, "disable-ssl": !aws.UseSsl},
|
||||||
|
"cloudflare-r2": {"bucket": cloudflare.Bucket, "base-url": cloudflare.BaseUrl, "path": cloudflare.PathPrefix, "account-id": cloudflare.AccountId, "access-key-id": cloudflare.AccessKey, "secret-access-key": cloudflare.SecretKey},
|
||||||
|
"minio": {"endpoint": minio.Endpoint, "access-key-id": minio.AccessKey, "access-key-secret": minio.SecretKey, "bucket-name": minio.Bucket, "use-ssl": minio.UseSsl, "base-path": minio.PathPrefix, "bucket-url": minio.BaseUrl},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func gvaConfiguration(data *conf.Data, admin *conf.AdminBackend) map[string]any {
|
||||||
|
result := map[string]any{}
|
||||||
|
if data == nil {
|
||||||
|
data = &conf.Data{}
|
||||||
|
}
|
||||||
|
if admin == nil {
|
||||||
|
admin = &conf.AdminBackend{}
|
||||||
|
}
|
||||||
|
jwt := admin.Jwt
|
||||||
|
if jwt == nil {
|
||||||
|
jwt = &conf.AdminBackend_JWT{}
|
||||||
|
}
|
||||||
|
result["jwt"] = map[string]any{"signing-key": jwt.SigningKey, "expires-time": durationString(jwt.ExpiresTime), "buffer-time": durationString(jwt.BufferTime), "issuer": jwt.Issuer}
|
||||||
|
zap := admin.Zap
|
||||||
|
if zap == nil {
|
||||||
|
zap = &conf.AdminBackend_Zap{}
|
||||||
|
}
|
||||||
|
result["zap"] = map[string]any{
|
||||||
|
"level": zap.Level, "prefix": zap.Prefix, "format": zap.Format, "director": zap.Director,
|
||||||
|
"encode-level": zap.EncodeLevel, "stacktrace-key": zap.StacktraceKey, "show-line": zap.ShowLine,
|
||||||
|
"log-in-console": zap.LogInConsole, "retention-day": zap.RetentionDay,
|
||||||
|
"access-req-body": zap.AccessReqBody, "access-resp-data": zap.AccessRespData,
|
||||||
|
"access-req-headers": zap.AccessReqHeaders, "access-log-max-bytes": zap.AccessLogMaxBytes,
|
||||||
|
"file-only-modules": zap.FileOnlyModules,
|
||||||
|
}
|
||||||
|
result["redis"] = gvaRedis(data.Redis)
|
||||||
|
redisList := make([]map[string]any, 0, len(data.RedisList))
|
||||||
|
for _, item := range data.RedisList {
|
||||||
|
redisList = append(redisList, gvaRedis(item))
|
||||||
|
}
|
||||||
|
result["redis-list"] = redisList
|
||||||
|
mongo := data.Mongo
|
||||||
|
if mongo == nil {
|
||||||
|
mongo = &conf.Data_Mongo{}
|
||||||
|
}
|
||||||
|
hosts := make([]map[string]any, 0, len(mongo.Hosts))
|
||||||
|
for _, host := range mongo.Hosts {
|
||||||
|
if host != nil {
|
||||||
|
hosts = append(hosts, map[string]any{"host": host.Host, "port": host.Port})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result["mongo"] = map[string]any{"coll": mongo.Coll, "options": mongo.Options, "database": mongo.Database, "username": mongo.Username, "password": mongo.Password, "auth-source": mongo.AuthSource, "min-pool-size": mongo.MinPoolSize, "max-pool-size": mongo.MaxPoolSize, "socket-timeout-ms": mongo.SocketTimeoutMs, "connect-timeout-ms": mongo.ConnectTimeoutMs, "is-zap": mongo.IsZap, "hosts": hosts}
|
||||||
|
email := admin.Email
|
||||||
|
if email == nil {
|
||||||
|
email = &conf.AdminBackend_Email{}
|
||||||
|
}
|
||||||
|
result["email"] = map[string]any{"to": email.To, "from": email.From, "host": email.Host, "secret": email.Secret, "nickname": email.Nickname, "port": email.Port, "is-ssl": email.IsSsl, "is-loginauth": email.IsLoginAuth}
|
||||||
|
system := admin.System
|
||||||
|
if system == nil {
|
||||||
|
system = &conf.AdminBackend_System{}
|
||||||
|
}
|
||||||
|
storageType := "local"
|
||||||
|
if admin.Storage != nil && admin.Storage.Type != "" {
|
||||||
|
storageType = admin.Storage.Type
|
||||||
|
}
|
||||||
|
driver := ""
|
||||||
|
if data.Database != nil {
|
||||||
|
driver = data.Database.Driver
|
||||||
|
}
|
||||||
|
result["system"] = map[string]any{"db-type": driver, "oss-type": storageType, "router-prefix": admin.RouterPrefix, "addr": system.Addr, "iplimit-count": system.IplimitCount, "iplimit-time": system.IplimitTime, "use-multipoint": system.UseMultipoint, "use-redis": system.UseRedis, "use-mongo": system.UseMongo, "use-strict-auth": system.UseStrictAuth, "disable-auto-migrate": system.DisableAutoMigrate}
|
||||||
|
for _, name := range []string{"mysql", "mssql", "pgsql", "oracle", "sqlite"} {
|
||||||
|
result[name] = map[string]any{}
|
||||||
|
}
|
||||||
|
if driver != "" {
|
||||||
|
result[driver] = gvaDatabase(data.Database)
|
||||||
|
}
|
||||||
|
dbList := make([]map[string]any, 0, len(data.DatabaseList))
|
||||||
|
for _, item := range data.DatabaseList {
|
||||||
|
row := gvaDatabase(item)
|
||||||
|
if item != nil {
|
||||||
|
row["type"], row["alias-name"], row["disable"] = item.Driver, item.AliasName, item.Disable
|
||||||
|
}
|
||||||
|
dbList = append(dbList, row)
|
||||||
|
}
|
||||||
|
result["db-list"] = dbList
|
||||||
|
local := admin.Local
|
||||||
|
if local == nil {
|
||||||
|
local = &conf.AdminBackend_Local{}
|
||||||
|
}
|
||||||
|
result["local"] = map[string]any{"path": local.PathPrefix, "store-path": local.StorePath}
|
||||||
|
media := admin.Media
|
||||||
|
if media == nil {
|
||||||
|
media = &conf.AdminBackend_Media{}
|
||||||
|
}
|
||||||
|
result["media"] = map[string]any{"session-ttl": media.SessionTtl, "max-file-size": media.MaxFileSize, "chunk-dir": media.ChunkDir}
|
||||||
|
if admin.Storage == nil {
|
||||||
|
admin.Storage = &conf.AdminBackend_Storage{}
|
||||||
|
}
|
||||||
|
qiniu := admin.Storage.Qiniu
|
||||||
|
if qiniu == nil {
|
||||||
|
qiniu = &conf.AdminBackend_Qiniu{}
|
||||||
|
}
|
||||||
|
result["qiniu"] = map[string]any{"zone": qiniu.Zone, "bucket": qiniu.Bucket, "img-path": qiniu.BaseUrl, "access-key": qiniu.AccessKey, "secret-key": qiniu.SecretKey, "use-https": qiniu.UseHttps, "use-cdn-domains": qiniu.UseCdnDomains}
|
||||||
|
for key, value := range gvaObjectStores(admin.Storage) {
|
||||||
|
result[key] = value
|
||||||
|
}
|
||||||
|
disks := make([]map[string]any, 0, len(admin.DiskList))
|
||||||
|
for _, disk := range admin.DiskList {
|
||||||
|
if disk != nil {
|
||||||
|
disks = append(disks, map[string]any{"mount-point": disk.MountPoint})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result["disk-list"] = disks
|
||||||
|
cors := admin.Cors
|
||||||
|
if cors == nil {
|
||||||
|
cors = &conf.AdminBackend_CORS{}
|
||||||
|
}
|
||||||
|
whitelist := make([]map[string]any, 0, len(cors.Whitelist))
|
||||||
|
for _, rule := range cors.Whitelist {
|
||||||
|
if rule != nil {
|
||||||
|
whitelist = append(whitelist, map[string]any{"allow-origin": rule.AllowOrigin, "allow-methods": rule.AllowMethods, "allow-headers": rule.AllowHeaders, "expose-headers": rule.ExposeHeaders, "allow-credentials": rule.AllowCredentials})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result["cors"] = map[string]any{"mode": cors.Mode, "whitelist": whitelist}
|
||||||
|
result["app"] = map[string]any{"node": admin.GetApp().GetNode(), "app-id": admin.GetApp().GetAppId(), "env": admin.GetApp().GetEnv()}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func objectMap(value any) map[string]any {
|
||||||
|
result, _ := value.(map[string]any)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringValue(values map[string]any, key string) string {
|
||||||
|
value, _ := values[key].(string)
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func boolValue(values map[string]any, key string, fallback bool) bool {
|
||||||
|
value, ok := values[key].(bool)
|
||||||
|
if !ok {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
func int64Value(values map[string]any, key string, fallback int64) int64 {
|
||||||
|
value, ok := values[key].(float64)
|
||||||
|
if !ok {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return int64(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func stringSliceValue(values map[string]any, key string, fallback []string) []string {
|
||||||
|
items, ok := values[key].([]any)
|
||||||
|
if !ok {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
result := make([]string, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
if value, ok := item.(string); ok {
|
||||||
|
result = append(result, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyGVARedis(target *conf.Data_Redis, values map[string]any) {
|
||||||
|
if target == nil || values == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.Name, target.Addr = stringValue(values, "name"), stringValue(values, "addr")
|
||||||
|
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||||
|
target.Password = password
|
||||||
|
}
|
||||||
|
target.Db = int32(int64Value(values, "db", int64(target.Db)))
|
||||||
|
target.UseCluster = boolValue(values, "useCluster", target.UseCluster)
|
||||||
|
target.ClusterAddrs = stringSliceValue(values, "clusterAddrs", target.ClusterAddrs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func durationValue(raw string, fallback *durationpb.Duration) *durationpb.Duration {
|
||||||
|
if raw == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
value, err := parseGVADuration(raw)
|
||||||
|
if err != nil {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return durationpb.New(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGVADuration(raw string) (time.Duration, error) {
|
||||||
|
raw = strings.TrimSpace(raw)
|
||||||
|
if value, err := time.ParseDuration(raw); err == nil {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
if index := strings.Index(raw, "d"); index >= 0 {
|
||||||
|
days, _ := strconv.Atoi(raw[:index])
|
||||||
|
value := time.Duration(days) * 24 * time.Hour
|
||||||
|
remainder, err := time.ParseDuration(raw[index+1:])
|
||||||
|
if err != nil {
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
return value + remainder, nil
|
||||||
|
}
|
||||||
|
nanoseconds, err := strconv.ParseInt(raw, 10, 64)
|
||||||
|
return time.Duration(nanoseconds), err
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyGVADatabase(target *conf.Data_Database, values map[string]any, driver string) {
|
||||||
|
if target == nil || values == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.Driver = driver
|
||||||
|
target.Prefix, target.Port, target.Config = stringValue(values, "prefix"), stringValue(values, "port"), stringValue(values, "config")
|
||||||
|
target.Name, target.User = stringValue(values, "db-name"), stringValue(values, "username")
|
||||||
|
if password := stringValue(values, "password"); password != "" && password != "******" {
|
||||||
|
target.Password = password
|
||||||
|
}
|
||||||
|
if driver == "sqlite" {
|
||||||
|
target.Path = stringValue(values, "path")
|
||||||
|
} else {
|
||||||
|
target.Host = stringValue(values, "path")
|
||||||
|
}
|
||||||
|
target.Engine, target.LogMode = stringValue(values, "engine"), stringValue(values, "log-mode")
|
||||||
|
target.MaxIdleConns = int32(int64Value(values, "max-idle-conns", int64(target.MaxIdleConns)))
|
||||||
|
target.MaxOpenConns = int32(int64Value(values, "max-open-conns", int64(target.MaxOpenConns)))
|
||||||
|
target.ConnMaxLifetime = int32(int64Value(values, "conn-max-lifetime", int64(target.ConnMaxLifetime)))
|
||||||
|
target.Singular = boolValue(values, "singular", target.Singular)
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyGVAObjectStore(target *conf.AdminBackend_ObjectStore, values map[string]any) {
|
||||||
|
if target == nil || values == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
target.Endpoint, target.Region, target.Bucket = stringValue(values, "endpoint"), stringValue(values, "region"), stringValue(values, "bucket")
|
||||||
|
if target.Bucket == "" {
|
||||||
|
target.Bucket = stringValue(values, "bucket-name")
|
||||||
|
}
|
||||||
|
target.AccessKey = stringValue(values, "access-key")
|
||||||
|
if target.AccessKey == "" {
|
||||||
|
target.AccessKey = stringValue(values, "access-key-id")
|
||||||
|
}
|
||||||
|
if target.AccessKey == "" {
|
||||||
|
target.AccessKey = stringValue(values, "secret-id")
|
||||||
|
}
|
||||||
|
secret := stringValue(values, "secret-key")
|
||||||
|
if secret == "" {
|
||||||
|
secret = stringValue(values, "access-key-secret")
|
||||||
|
}
|
||||||
|
if secret == "" {
|
||||||
|
secret = stringValue(values, "secret-access-key")
|
||||||
|
}
|
||||||
|
if secret != "" && secret != "******" {
|
||||||
|
target.SecretKey = secret
|
||||||
|
}
|
||||||
|
target.BaseUrl = stringValue(values, "base-url")
|
||||||
|
if target.BaseUrl == "" {
|
||||||
|
target.BaseUrl = stringValue(values, "bucket-url")
|
||||||
|
}
|
||||||
|
target.PathPrefix = stringValue(values, "path-prefix")
|
||||||
|
if target.PathPrefix == "" {
|
||||||
|
target.PathPrefix = stringValue(values, "base-path")
|
||||||
|
}
|
||||||
|
if target.PathPrefix == "" {
|
||||||
|
target.PathPrefix = stringValue(values, "path")
|
||||||
|
}
|
||||||
|
target.UseSsl = boolValue(values, "use-ssl", !boolValue(values, "disable-ssl", !target.UseSsl))
|
||||||
|
target.ForcePathStyle = boolValue(values, "s3-force-path-style", target.ForcePathStyle)
|
||||||
|
target.AccountId = stringValue(values, "account-id")
|
||||||
|
}
|
||||||
|
|
||||||
|
func applyGVAConfiguration(raw json.RawMessage, data *conf.Data, admin *conf.AdminBackend) error {
|
||||||
|
var values map[string]any
|
||||||
|
if err := json.Unmarshal(raw, &values); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, isGVA := values["system"]; !isGVA {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if data.Database == nil {
|
||||||
|
data.Database = &conf.Data_Database{}
|
||||||
|
}
|
||||||
|
if admin.System == nil {
|
||||||
|
admin.System = &conf.AdminBackend_System{}
|
||||||
|
}
|
||||||
|
if admin.Storage == nil {
|
||||||
|
admin.Storage = &conf.AdminBackend_Storage{}
|
||||||
|
}
|
||||||
|
system := objectMap(values["system"])
|
||||||
|
driver := stringValue(system, "db-type")
|
||||||
|
if driver == "" {
|
||||||
|
driver = data.Database.Driver
|
||||||
|
}
|
||||||
|
applyGVADatabase(data.Database, objectMap(values[driver]), driver)
|
||||||
|
admin.Storage.Type = stringValue(system, "oss-type")
|
||||||
|
admin.RouterPrefix = stringValue(system, "router-prefix")
|
||||||
|
admin.System.UseMultipoint = boolValue(system, "use-multipoint", admin.System.UseMultipoint)
|
||||||
|
admin.System.UseRedis = boolValue(system, "use-redis", admin.System.UseRedis)
|
||||||
|
admin.System.UseMongo = boolValue(system, "use-mongo", admin.System.UseMongo)
|
||||||
|
admin.System.UseStrictAuth = boolValue(system, "use-strict-auth", admin.System.UseStrictAuth)
|
||||||
|
admin.System.DisableAutoMigrate = boolValue(system, "disable-auto-migrate", admin.System.DisableAutoMigrate)
|
||||||
|
admin.System.Addr = int32(int64Value(system, "addr", int64(admin.System.Addr)))
|
||||||
|
admin.System.IplimitCount = int32(int64Value(system, "iplimit-count", int64(admin.System.IplimitCount)))
|
||||||
|
admin.System.IplimitTime = int32(int64Value(system, "iplimit-time", int64(admin.System.IplimitTime)))
|
||||||
|
if admin.Jwt == nil {
|
||||||
|
admin.Jwt = &conf.AdminBackend_JWT{}
|
||||||
|
}
|
||||||
|
jwt := objectMap(values["jwt"])
|
||||||
|
if secret := stringValue(jwt, "signing-key"); secret != "" && secret != "******" {
|
||||||
|
admin.Jwt.SigningKey = secret
|
||||||
|
}
|
||||||
|
admin.Jwt.ExpiresTime = durationValue(stringValue(jwt, "expires-time"), admin.Jwt.ExpiresTime)
|
||||||
|
admin.Jwt.BufferTime = durationValue(stringValue(jwt, "buffer-time"), admin.Jwt.BufferTime)
|
||||||
|
admin.Jwt.Issuer = stringValue(jwt, "issuer")
|
||||||
|
if admin.Zap == nil {
|
||||||
|
admin.Zap = &conf.AdminBackend_Zap{}
|
||||||
|
}
|
||||||
|
zap := objectMap(values["zap"])
|
||||||
|
admin.Zap.Level, admin.Zap.Prefix, admin.Zap.Format, admin.Zap.Director = stringValue(zap, "level"), stringValue(zap, "prefix"), stringValue(zap, "format"), stringValue(zap, "director")
|
||||||
|
admin.Zap.EncodeLevel, admin.Zap.StacktraceKey = stringValue(zap, "encode-level"), stringValue(zap, "stacktrace-key")
|
||||||
|
admin.Zap.ShowLine, admin.Zap.LogInConsole = boolValue(zap, "show-line", admin.Zap.ShowLine), boolValue(zap, "log-in-console", admin.Zap.LogInConsole)
|
||||||
|
admin.Zap.RetentionDay = int32(int64Value(zap, "retention-day", int64(admin.Zap.RetentionDay)))
|
||||||
|
admin.Zap.AccessReqBody = boolValue(zap, "access-req-body", admin.Zap.AccessReqBody)
|
||||||
|
admin.Zap.AccessRespData = boolValue(zap, "access-resp-data", admin.Zap.AccessRespData)
|
||||||
|
admin.Zap.AccessReqHeaders = boolValue(zap, "access-req-headers", admin.Zap.AccessReqHeaders)
|
||||||
|
admin.Zap.AccessLogMaxBytes = int32(int64Value(zap, "access-log-max-bytes", int64(admin.Zap.AccessLogMaxBytes)))
|
||||||
|
if list, ok := zap["file-only-modules"].([]any); ok {
|
||||||
|
admin.Zap.FileOnlyModules = admin.Zap.FileOnlyModules[:0]
|
||||||
|
for _, item := range list {
|
||||||
|
if value, ok := item.(string); ok {
|
||||||
|
admin.Zap.FileOnlyModules = append(admin.Zap.FileOnlyModules, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if data.Redis == nil {
|
||||||
|
data.Redis = &conf.Data_Redis{}
|
||||||
|
}
|
||||||
|
applyGVARedis(data.Redis, objectMap(values["redis"]))
|
||||||
|
if items, ok := values["redis-list"].([]any); ok {
|
||||||
|
current := make(map[string]*conf.Data_Redis, len(data.RedisList))
|
||||||
|
for _, item := range data.RedisList {
|
||||||
|
if item != nil {
|
||||||
|
current[item.Name] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.RedisList = make([]*conf.Data_Redis, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
values := objectMap(item)
|
||||||
|
target := current[stringValue(values, "name")]
|
||||||
|
if target == nil {
|
||||||
|
target = &conf.Data_Redis{}
|
||||||
|
}
|
||||||
|
applyGVARedis(target, values)
|
||||||
|
data.RedisList = append(data.RedisList, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if mongo := objectMap(values["mongo"]); mongo != nil {
|
||||||
|
if data.Mongo == nil {
|
||||||
|
data.Mongo = &conf.Data_Mongo{}
|
||||||
|
}
|
||||||
|
data.Mongo.Coll, data.Mongo.Options, data.Mongo.Database = stringValue(mongo, "coll"), stringValue(mongo, "options"), stringValue(mongo, "database")
|
||||||
|
data.Mongo.Username, data.Mongo.AuthSource = stringValue(mongo, "username"), stringValue(mongo, "auth-source")
|
||||||
|
if password := stringValue(mongo, "password"); password != "" && password != "******" {
|
||||||
|
data.Mongo.Password = password
|
||||||
|
}
|
||||||
|
data.Mongo.MinPoolSize = uint64(int64Value(mongo, "min-pool-size", int64(data.Mongo.MinPoolSize)))
|
||||||
|
data.Mongo.MaxPoolSize = uint64(int64Value(mongo, "max-pool-size", int64(data.Mongo.MaxPoolSize)))
|
||||||
|
data.Mongo.SocketTimeoutMs = int64Value(mongo, "socket-timeout-ms", data.Mongo.SocketTimeoutMs)
|
||||||
|
data.Mongo.ConnectTimeoutMs = int64Value(mongo, "connect-timeout-ms", data.Mongo.ConnectTimeoutMs)
|
||||||
|
data.Mongo.IsZap = boolValue(mongo, "is-zap", data.Mongo.IsZap)
|
||||||
|
if hosts, ok := mongo["hosts"].([]any); ok {
|
||||||
|
data.Mongo.Hosts = make([]*conf.Data_MongoHost, 0, len(hosts))
|
||||||
|
for _, item := range hosts {
|
||||||
|
host := objectMap(item)
|
||||||
|
data.Mongo.Hosts = append(data.Mongo.Hosts, &conf.Data_MongoHost{Host: stringValue(host, "host"), Port: stringValue(host, "port")})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if items, ok := values["db-list"].([]any); ok {
|
||||||
|
current := make(map[string]*conf.Data_Database, len(data.DatabaseList))
|
||||||
|
for _, item := range data.DatabaseList {
|
||||||
|
if item != nil {
|
||||||
|
current[item.AliasName] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.DatabaseList = make([]*conf.Data_Database, 0, len(items))
|
||||||
|
for _, item := range items {
|
||||||
|
row := objectMap(item)
|
||||||
|
alias := stringValue(row, "alias-name")
|
||||||
|
target := current[alias]
|
||||||
|
if target == nil {
|
||||||
|
target = &conf.Data_Database{}
|
||||||
|
}
|
||||||
|
applyGVADatabase(target, row, stringValue(row, "type"))
|
||||||
|
target.AliasName, target.Disable = alias, boolValue(row, "disable", target.Disable)
|
||||||
|
data.DatabaseList = append(data.DatabaseList, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if admin.Email == nil {
|
||||||
|
admin.Email = &conf.AdminBackend_Email{}
|
||||||
|
}
|
||||||
|
email := objectMap(values["email"])
|
||||||
|
admin.Email.To, admin.Email.From, admin.Email.Host = stringValue(email, "to"), stringValue(email, "from"), stringValue(email, "host")
|
||||||
|
admin.Email.Nickname = stringValue(email, "nickname")
|
||||||
|
admin.Email.Port = int32(int64Value(email, "port", int64(admin.Email.Port)))
|
||||||
|
admin.Email.IsSsl, admin.Email.IsLoginAuth = boolValue(email, "is-ssl", admin.Email.IsSsl), boolValue(email, "is-loginauth", admin.Email.IsLoginAuth)
|
||||||
|
if secret := stringValue(email, "secret"); secret != "" && secret != "******" {
|
||||||
|
admin.Email.Secret = secret
|
||||||
|
}
|
||||||
|
if admin.Local == nil {
|
||||||
|
admin.Local = &conf.AdminBackend_Local{}
|
||||||
|
}
|
||||||
|
local := objectMap(values["local"])
|
||||||
|
admin.Local.PathPrefix, admin.Local.StorePath = stringValue(local, "path"), stringValue(local, "store-path")
|
||||||
|
if admin.Media == nil {
|
||||||
|
admin.Media = &conf.AdminBackend_Media{}
|
||||||
|
}
|
||||||
|
media := objectMap(values["media"])
|
||||||
|
admin.Media.SessionTtl = int32(int64Value(media, "session-ttl", int64(admin.Media.SessionTtl)))
|
||||||
|
admin.Media.MaxFileSize = int64Value(media, "max-file-size", admin.Media.MaxFileSize)
|
||||||
|
admin.Media.ChunkDir = stringValue(media, "chunk-dir")
|
||||||
|
if admin.Storage.Qiniu == nil {
|
||||||
|
admin.Storage.Qiniu = &conf.AdminBackend_Qiniu{}
|
||||||
|
}
|
||||||
|
qiniu := objectMap(values["qiniu"])
|
||||||
|
admin.Storage.Qiniu.Zone, admin.Storage.Qiniu.Bucket, admin.Storage.Qiniu.BaseUrl = stringValue(qiniu, "zone"), stringValue(qiniu, "bucket"), stringValue(qiniu, "img-path")
|
||||||
|
admin.Storage.Qiniu.AccessKey = stringValue(qiniu, "access-key")
|
||||||
|
if secret := stringValue(qiniu, "secret-key"); secret != "" && secret != "******" {
|
||||||
|
admin.Storage.Qiniu.SecretKey = secret
|
||||||
|
}
|
||||||
|
admin.Storage.Qiniu.UseHttps, admin.Storage.Qiniu.UseCdnDomains = boolValue(qiniu, "use-https", admin.Storage.Qiniu.UseHttps), boolValue(qiniu, "use-cdn-domains", admin.Storage.Qiniu.UseCdnDomains)
|
||||||
|
stores := []struct {
|
||||||
|
key string
|
||||||
|
target **conf.AdminBackend_ObjectStore
|
||||||
|
}{{"aliyun-oss", &admin.Storage.AliyunOss}, {"hua-wei-obs", &admin.Storage.HuaweiObs}, {"tencent-cos", &admin.Storage.TencentCos}, {"aws-s3", &admin.Storage.AwsS3}, {"cloudflare-r2", &admin.Storage.CloudflareR2}, {"minio", &admin.Storage.Minio}}
|
||||||
|
for _, store := range stores {
|
||||||
|
if *store.target == nil {
|
||||||
|
*store.target = &conf.AdminBackend_ObjectStore{}
|
||||||
|
}
|
||||||
|
applyGVAObjectStore(*store.target, objectMap(values[store.key]))
|
||||||
|
}
|
||||||
|
if disks, ok := values["disk-list"].([]any); ok {
|
||||||
|
admin.DiskList = make([]*conf.AdminBackend_Disk, 0, len(disks))
|
||||||
|
for _, item := range disks {
|
||||||
|
disk := objectMap(item)
|
||||||
|
admin.DiskList = append(admin.DiskList, &conf.AdminBackend_Disk{MountPoint: stringValue(disk, "mount-point")})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cors := objectMap(values["cors"]); cors != nil {
|
||||||
|
if admin.Cors == nil {
|
||||||
|
admin.Cors = &conf.AdminBackend_CORS{}
|
||||||
|
}
|
||||||
|
admin.Cors.Mode = stringValue(cors, "mode")
|
||||||
|
if rules, ok := cors["whitelist"].([]any); ok {
|
||||||
|
admin.Cors.Whitelist = make([]*conf.AdminBackend_CORSRule, 0, len(rules))
|
||||||
|
for _, item := range rules {
|
||||||
|
rule := objectMap(item)
|
||||||
|
admin.Cors.Whitelist = append(admin.Cors.Whitelist, &conf.AdminBackend_CORSRule{AllowOrigin: stringValue(rule, "allow-origin"), AllowMethods: stringValue(rule, "allow-methods"), AllowHeaders: stringValue(rule, "allow-headers"), ExposeHeaders: stringValue(rule, "expose-headers"), AllowCredentials: boolValue(rule, "allow-credentials", false)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if app := objectMap(values["app"]); app != nil {
|
||||||
|
if admin.App == nil {
|
||||||
|
admin.App = &conf.AdminBackend_App{}
|
||||||
|
}
|
||||||
|
admin.App.Node, admin.App.AppId, admin.App.Env = stringValue(app, "node"), stringValue(app, "app-id"), stringValue(app, "env")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -4,8 +4,11 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"os"
|
"os"
|
||||||
|
pathpkg "path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
@ -14,86 +17,320 @@ import (
|
||||||
"kra/internal/biz"
|
"kra/internal/biz"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (r *logFileRepo) logRoot() (string, error) {
|
const (
|
||||||
|
defaultLogChunkLines = 500
|
||||||
|
maxLogChunkBytes = 2 * 1024 * 1024
|
||||||
|
logReadBlockSize = 64 * 1024
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *logFileRepo) configuredLogRoot() (root string, exists bool, err error) {
|
||||||
admin := r.data.runtime.Admin()
|
admin := r.data.runtime.Admin()
|
||||||
if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
|
if admin == nil || admin.Zap == nil || strings.TrimSpace(admin.Zap.Director) == "" {
|
||||||
return "", biz.ErrLogRootUnavailable
|
return "", false, biz.ErrLogRootUnavailable
|
||||||
}
|
}
|
||||||
root, err := filepath.Abs(admin.Zap.Director)
|
root, err = filepath.Abs(admin.Zap.Director)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Join(biz.ErrLogRootUnavailable, err)
|
return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
}
|
}
|
||||||
info, err := os.Stat(root)
|
info, err := os.Stat(root)
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
return root, nil
|
return root, false, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Join(biz.ErrLogRootUnavailable, err)
|
return "", false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
}
|
}
|
||||||
if !info.IsDir() {
|
if !info.IsDir() {
|
||||||
return "", biz.ErrLogRootUnavailable
|
return "", false, biz.ErrLogRootUnavailable
|
||||||
}
|
}
|
||||||
return root, nil
|
return root, true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *logFileRepo) openConfiguredLogRoot() (root *os.Root, exists bool, err error) {
|
||||||
|
rootPath, exists, err := r.configuredLogRoot()
|
||||||
|
if err != nil || !exists {
|
||||||
|
return nil, exists, err
|
||||||
|
}
|
||||||
|
root, err = os.OpenRoot(rootPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
|
}
|
||||||
|
return root, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
func (r *logFileRepo) LogDates(ctx context.Context, month string) ([]biz.LogDate, error) {
|
||||||
if parsed, err := time.Parse("2006-01", month); err != nil || parsed.Format("2006-01") != month {
|
if err := validateLogMonth(month); err != nil {
|
||||||
return nil, biz.ErrInvalidLogMonth
|
return nil, err
|
||||||
}
|
}
|
||||||
root, err := r.logRoot()
|
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
entries, err := os.ReadDir(root)
|
if !exists {
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
|
||||||
return []biz.LogDate{}, nil
|
return []biz.LogDate{}, nil
|
||||||
}
|
}
|
||||||
|
defer logRoot.Close()
|
||||||
|
|
||||||
|
entries, err := fs.ReadDir(logRoot.FS(), ".")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
}
|
}
|
||||||
out := []biz.LogDate{}
|
result := make([]biz.LogDate, 0)
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if ctx.Err() != nil {
|
if err = ctx.Err(); err != nil {
|
||||||
return nil, ctx.Err()
|
return nil, err
|
||||||
}
|
}
|
||||||
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
|
if entry.Type()&os.ModeSymlink != 0 || !entry.IsDir() || !strings.HasPrefix(entry.Name(), month+"-") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if parsed, parseErr := time.Parse("2006-01-02", entry.Name()); parseErr != nil || parsed.Format("2006-01-02") != entry.Name() {
|
if validateLogDate(entry.Name()) != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
count := 0
|
count, countErr := countLogFiles(ctx, logRoot, entry.Name())
|
||||||
_ = filepath.WalkDir(filepath.Join(root, entry.Name()), func(_ string, item fs.DirEntry, walkErr error) error {
|
if countErr != nil {
|
||||||
if walkErr == nil && !item.IsDir() && strings.EqualFold(filepath.Ext(item.Name()), ".log") {
|
return nil, countErr
|
||||||
count++
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if count > 0 {
|
if count > 0 {
|
||||||
out = append(out, biz.LogDate{Date: entry.Name(), FileCount: count})
|
result = append(result, biz.LogDate{Date: entry.Name(), FileCount: count})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].Date < out[j].Date })
|
sort.Slice(result, func(i, j int) bool { return result[i].Date < result[j].Date })
|
||||||
return out, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile, error) {
|
||||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
if err := validateLogDate(date); err != nil {
|
||||||
return nil, biz.ErrInvalidLogDate
|
return nil, err
|
||||||
}
|
}
|
||||||
root, err := r.logRoot()
|
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dateRoot := filepath.Join(root, date)
|
if !exists {
|
||||||
out := []biz.LogFile{}
|
return []biz.LogFile{}, nil
|
||||||
err = filepath.WalkDir(dateRoot, func(path string, entry fs.DirEntry, walkErr error) error {
|
|
||||||
if walkErr != nil {
|
|
||||||
if errors.Is(walkErr, fs.ErrNotExist) {
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
defer logRoot.Close()
|
||||||
|
|
||||||
|
info, err := logRoot.Lstat(date)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return []biz.LogFile{}, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
|
}
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 || !info.IsDir() {
|
||||||
|
return []biz.LogFile{}, nil
|
||||||
|
}
|
||||||
|
dateRoot, err := logRoot.OpenRoot(date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
|
}
|
||||||
|
defer dateRoot.Close()
|
||||||
|
|
||||||
|
result := make([]biz.LogFile, 0)
|
||||||
|
err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
return walkErr
|
return walkErr
|
||||||
}
|
}
|
||||||
if ctx.Err() != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
return ctx.Err()
|
return err
|
||||||
|
}
|
||||||
|
if path == "." {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if entry.Type()&os.ModeSymlink != 0 {
|
||||||
|
if entry.IsDir() {
|
||||||
|
return filepath.SkipDir
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if entry.IsDir() || !strings.EqualFold(filepath.Ext(entry.Name()), ".log") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
fileInfo, infoErr := entry.Info()
|
||||||
|
if infoErr != nil {
|
||||||
|
return infoErr
|
||||||
|
}
|
||||||
|
if fileInfo.Mode().IsRegular() {
|
||||||
|
result = append(result, biz.LogFile{Path: path, Name: entry.Name(), Size: fileInfo.Size(), ModifiedAt: fileInfo.ModTime()})
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
|
}
|
||||||
|
sort.Slice(result, func(i, j int) bool { return result[i].Path < result[j].Path })
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *logFileRepo) LogContent(ctx context.Context, date, apiPath string, cursor *int64) (*biz.LogContent, error) {
|
||||||
|
result := &biz.LogContent{Date: date, Path: apiPath}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if cursor != nil && *cursor < 0 {
|
||||||
|
return nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
file, info, err := r.openValidatedLogFile(date, apiPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
end := info.Size()
|
||||||
|
if cursor != nil && *cursor <= end {
|
||||||
|
end = *cursor
|
||||||
|
}
|
||||||
|
start, limitedByBytes, err := findLogChunkStart(file, end)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
data, err := readLogRange(file, start, end)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
if int64(len(data)) < end-start {
|
||||||
|
currentInfo, statErr := file.Stat()
|
||||||
|
if statErr != nil || currentInfo.Size() < start {
|
||||||
|
if statErr != nil {
|
||||||
|
return nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr)
|
||||||
|
}
|
||||||
|
return nil, biz.ErrLogFileUnreadable
|
||||||
|
}
|
||||||
|
info = currentInfo
|
||||||
|
}
|
||||||
|
result.Content = string(data)
|
||||||
|
result.LineCount = countLogicalLines(data)
|
||||||
|
result.NextCursor = start
|
||||||
|
result.HasMore = start > 0
|
||||||
|
result.LimitedByBytes = limitedByBytes
|
||||||
|
result.Size = info.Size()
|
||||||
|
result.ModifiedAt = info.ModTime()
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *logFileRepo) openValidatedLogFile(date, apiPath string) (file *os.File, info os.FileInfo, err error) {
|
||||||
|
if err = validateLogDate(date); err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
segments, err := validateLogAPIPath(apiPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
logRoot, exists, err := r.openConfiguredLogRoot()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, nil, biz.ErrLogFileNotFound
|
||||||
|
}
|
||||||
|
defer logRoot.Close()
|
||||||
|
|
||||||
|
dateInfo, err := logRoot.Lstat(date)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, nil, biz.ErrLogFileNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
if dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
|
||||||
|
return nil, nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
dateRoot, err := logRoot.OpenRoot(date)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
defer dateRoot.Close()
|
||||||
|
|
||||||
|
relativePath := ""
|
||||||
|
var validatedInfo os.FileInfo
|
||||||
|
for index, segment := range segments {
|
||||||
|
relativePath = filepath.Join(relativePath, segment)
|
||||||
|
validatedInfo, err = dateRoot.Lstat(relativePath)
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, nil, biz.ErrLogFileNotFound
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
if validatedInfo.Mode()&os.ModeSymlink != 0 {
|
||||||
|
return nil, nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
if index < len(segments)-1 && !validatedInfo.IsDir() {
|
||||||
|
return nil, nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if validatedInfo == nil || !validatedInfo.Mode().IsRegular() {
|
||||||
|
return nil, nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
file, err = dateRoot.Open(relativePath)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
|
return nil, nil, biz.ErrLogFileNotFound
|
||||||
|
}
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, err)
|
||||||
|
}
|
||||||
|
openedInfo, statErr := file.Stat()
|
||||||
|
if statErr != nil || !openedInfo.Mode().IsRegular() || !os.SameFile(validatedInfo, openedInfo) {
|
||||||
|
file.Close()
|
||||||
|
if statErr != nil {
|
||||||
|
return nil, nil, fmt.Errorf("%w: %v", biz.ErrLogFileUnreadable, statErr)
|
||||||
|
}
|
||||||
|
return nil, nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
return file, openedInfo, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLogMonth(month string) error {
|
||||||
|
parsed, err := time.Parse("2006-01", month)
|
||||||
|
if err != nil || parsed.Format("2006-01") != month {
|
||||||
|
return biz.ErrInvalidLogMonth
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLogDate(date string) error {
|
||||||
|
parsed, err := time.Parse("2006-01-02", date)
|
||||||
|
if err != nil || parsed.Format("2006-01-02") != date {
|
||||||
|
return biz.ErrInvalidLogDate
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateLogAPIPath(apiPath string) ([]string, error) {
|
||||||
|
if apiPath == "" || strings.Contains(apiPath, "\\") || strings.Contains(apiPath, ":") || pathpkg.IsAbs(apiPath) {
|
||||||
|
return nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
segments := strings.Split(apiPath, "/")
|
||||||
|
for _, segment := range segments {
|
||||||
|
if segment == "" || segment == "." || segment == ".." {
|
||||||
|
return nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !strings.EqualFold(pathpkg.Ext(apiPath), ".log") {
|
||||||
|
return nil, biz.ErrInvalidLogPath
|
||||||
|
}
|
||||||
|
return segments, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func countLogFiles(ctx context.Context, logRoot *os.Root, date string) (count int, err error) {
|
||||||
|
dateInfo, err := logRoot.Lstat(date)
|
||||||
|
if err != nil || dateInfo.Mode()&os.ModeSymlink != 0 || !dateInfo.IsDir() {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
dateRoot, err := logRoot.OpenRoot(date)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
defer dateRoot.Close()
|
||||||
|
err = fs.WalkDir(dateRoot.FS(), ".", func(path string, entry fs.DirEntry, walkErr error) error {
|
||||||
|
if walkErr != nil {
|
||||||
|
return walkErr
|
||||||
|
}
|
||||||
|
if err := ctx.Err(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if path == "." {
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
if entry.Type()&os.ModeSymlink != 0 {
|
if entry.Type()&os.ModeSymlink != 0 {
|
||||||
if entry.IsDir() {
|
if entry.IsDir() {
|
||||||
|
|
@ -108,70 +345,80 @@ func (r *logFileRepo) LogFiles(ctx context.Context, date string) ([]biz.LogFile,
|
||||||
if infoErr != nil {
|
if infoErr != nil {
|
||||||
return infoErr
|
return infoErr
|
||||||
}
|
}
|
||||||
relative, _ := filepath.Rel(dateRoot, path)
|
if info.Mode().IsRegular() {
|
||||||
out = append(out, biz.LogFile{Path: filepath.ToSlash(relative), Name: entry.Name(), Size: info.Size(), ModifiedAt: info.ModTime()})
|
count++
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
sort.Slice(out, func(i, j int) bool { return out[i].Path < out[j].Path })
|
|
||||||
return out, err
|
|
||||||
}
|
|
||||||
func (r *logFileRepo) LogContent(ctx context.Context, date, path string, cursor *int64) (*biz.LogContent, error) {
|
|
||||||
if parsed, err := time.Parse("2006-01-02", date); err != nil || parsed.Format("2006-01-02") != date {
|
|
||||||
return nil, biz.ErrInvalidLogDate
|
|
||||||
}
|
|
||||||
root, err := r.logRoot()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
return 0, fmt.Errorf("%w: %v", biz.ErrLogRootUnavailable, err)
|
||||||
|
}
|
||||||
|
return count, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLogRange(file *os.File, start, end int64) ([]byte, error) {
|
||||||
|
if end <= start {
|
||||||
|
return []byte{}, nil
|
||||||
|
}
|
||||||
|
data := make([]byte, int(end-start))
|
||||||
|
n, err := file.ReadAt(data, start)
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dateRoot := filepath.Join(root, date)
|
return data[:n], nil
|
||||||
target := filepath.Clean(filepath.Join(dateRoot, filepath.FromSlash(path)))
|
|
||||||
if target == dateRoot || !strings.HasPrefix(target, dateRoot+string(os.PathSeparator)) || !strings.EqualFold(filepath.Ext(target), ".log") {
|
|
||||||
return nil, biz.ErrInvalidLogPath
|
|
||||||
}
|
}
|
||||||
info, err := os.Lstat(target)
|
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
func findLogChunkStart(file *os.File, end int64) (start int64, limitedByBytes bool, err error) {
|
||||||
return nil, biz.ErrLogFileNotFound
|
if end <= 0 {
|
||||||
|
return 0, false, nil
|
||||||
}
|
}
|
||||||
if err != nil {
|
lastByte := []byte{0}
|
||||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
if _, err = file.ReadAt(lastByte, end-1); err != nil {
|
||||||
|
return 0, false, err
|
||||||
}
|
}
|
||||||
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() {
|
targetNewlines := defaultLogChunkLines
|
||||||
return nil, biz.ErrInvalidLogPath
|
if lastByte[0] == '\n' {
|
||||||
|
targetNewlines++
|
||||||
}
|
}
|
||||||
end := info.Size()
|
position, scanned, newlines := end, int64(0), 0
|
||||||
if cursor != nil && *cursor >= 0 && *cursor < end {
|
for position > 0 && scanned < maxLogChunkBytes {
|
||||||
end = *cursor
|
readSize := int64(logReadBlockSize)
|
||||||
|
if readSize > position {
|
||||||
|
readSize = position
|
||||||
}
|
}
|
||||||
start := end - int64(2*1024*1024)
|
if remaining := int64(maxLogChunkBytes) - scanned; readSize > remaining {
|
||||||
limited := start > 0
|
readSize = remaining
|
||||||
if start < 0 {
|
|
||||||
start = 0
|
|
||||||
}
|
}
|
||||||
file, err := os.Open(target)
|
blockStart := position - readSize
|
||||||
if err != nil {
|
block := make([]byte, int(readSize))
|
||||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
n, readErr := file.ReadAt(block, blockStart)
|
||||||
|
if readErr != nil && !errors.Is(readErr, io.EOF) {
|
||||||
|
return 0, false, readErr
|
||||||
}
|
}
|
||||||
defer file.Close()
|
for index := n - 1; index >= 0; index-- {
|
||||||
data := make([]byte, end-start)
|
if block[index] == '\n' {
|
||||||
n, err := file.ReadAt(data, start)
|
newlines++
|
||||||
if err != nil && n == 0 {
|
if newlines == targetNewlines {
|
||||||
return nil, errors.Join(biz.ErrLogFileUnreadable, err)
|
return blockStart + int64(index) + 1, false, nil
|
||||||
}
|
}
|
||||||
data = data[:n]
|
|
||||||
lines := bytes.Split(data, []byte("\n"))
|
|
||||||
if len(lines) > 501 {
|
|
||||||
drop := len(lines) - 501
|
|
||||||
offset := 0
|
|
||||||
for _, line := range lines[:drop] {
|
|
||||||
offset += len(line) + 1
|
|
||||||
}
|
}
|
||||||
start += int64(offset)
|
|
||||||
data = data[offset:]
|
|
||||||
limited = true
|
|
||||||
}
|
}
|
||||||
lineCount := bytes.Count(data, []byte("\n"))
|
scanned += int64(n)
|
||||||
if len(data) > 0 && data[len(data)-1] != '\n' {
|
position = blockStart
|
||||||
lineCount++
|
|
||||||
}
|
}
|
||||||
return &biz.LogContent{Date: date, Path: path, Content: string(data), LineCount: lineCount, NextCursor: start, HasMore: start > 0, LimitedByBytes: limited, Size: info.Size(), ModifiedAt: info.ModTime()}, nil
|
if position > 0 {
|
||||||
|
return end - int64(maxLogChunkBytes), true, nil
|
||||||
|
}
|
||||||
|
return 0, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func countLogicalLines(data []byte) int {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
count := bytes.Count(data, []byte{'\n'})
|
||||||
|
if data[len(data)-1] != '\n' {
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
return count
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -68,7 +68,7 @@ func (s *runtimeSettings) MediaSettings() biz.MediaSettings {
|
||||||
if config == nil || config.Media == nil {
|
if config == nil || config.Media == nil {
|
||||||
return biz.MediaSettings{}
|
return biz.MediaSettings{}
|
||||||
}
|
}
|
||||||
return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize}
|
return biz.MediaSettings{SessionTTL: int(config.Media.SessionTtl), MaxFileSize: config.Media.MaxFileSize, ChunkDir: config.Media.ChunkDir}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *runtimeSettings) UseMultipoint() bool {
|
func (s *runtimeSettings) UseMultipoint() bool {
|
||||||
|
|
@ -110,5 +110,5 @@ func (i *tokenIssuer) ParseToken(token string) (*biz.AuthClaims, error) {
|
||||||
return nil, biz.ErrTokenInvalid
|
return nil, biz.ErrTokenInvalid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, Issuer: claims.Issuer, ExpiresAt: claims.ExpiresAt.Time}, nil
|
return &biz.AuthClaims{UUID: claims.UUID, ID: claims.ID, Username: claims.Username, NickName: claims.NickName, AuthorityID: claims.AuthorityID, UserType: claims.UserType, BufferTime: time.Duration(claims.BufferTime) * time.Second, MustChangePwd: claims.MustChangePwd, Issuer: claims.Issuer, ExpiresAt: claims.ExpiresAt.Time}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,9 @@ func (s *aliyunStorage) List(_ context.Context, prefix, cursor string, limit int
|
||||||
}
|
}
|
||||||
items := make([]*biz.StoredFile, 0, len(result.Objects))
|
items := make([]*biz.StoredFile, 0, len(result.Objects))
|
||||||
for _, object := range result.Objects {
|
for _, object := range result.Objects {
|
||||||
items = append(items, s.file(object.Key, object.Size))
|
item := s.file(object.Key, object.Size)
|
||||||
|
item.LastModified = object.LastModified
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
next := ""
|
next := ""
|
||||||
if result.IsTruncated {
|
if result.IsTruncated {
|
||||||
|
|
|
||||||
|
|
@ -141,7 +141,11 @@ func (s *awsStorage) List(ctx context.Context, prefix, cursor string, limit int)
|
||||||
if object.Size != nil {
|
if object.Size != nil {
|
||||||
size = *object.Size
|
size = *object.Size
|
||||||
}
|
}
|
||||||
items = append(items, s.file(*object.Key, size))
|
item := s.file(*object.Key, size)
|
||||||
|
if object.LastModified != nil {
|
||||||
|
item.LastModified = *object.LastModified
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
next := ""
|
next := ""
|
||||||
if result.NextContinuationToken != nil {
|
if result.NextContinuationToken != nil {
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,9 @@ func (s *huaweiStorage) List(_ context.Context, prefix, cursor string, limit int
|
||||||
}
|
}
|
||||||
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
||||||
for _, object := range result.Contents {
|
for _, object := range result.Contents {
|
||||||
items = append(items, s.file(object.Key, object.Size))
|
item := s.file(object.Key, object.Size)
|
||||||
|
item.LastModified = object.LastModified
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
next := result.NextMarker
|
next := result.NextMarker
|
||||||
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
@ -200,7 +201,7 @@ func (s *fileStorage) List(ctx context.Context, prefix, cursor string, limit int
|
||||||
if infoErr != nil {
|
if infoErr != nil {
|
||||||
return infoErr
|
return infoErr
|
||||||
}
|
}
|
||||||
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size()})
|
items = append(items, &biz.StoredFile{Name: entry.Name(), Path: relative, URL: s.urlPrefix + "/" + relative, Size: info.Size(), LastModified: info.ModTime(), ContentType: mime.TypeByExtension(filepath.Ext(entry.Name()))})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,10 @@ func (s *qiniuStorage) List(ctx context.Context, prefix, cursor string, limit in
|
||||||
}
|
}
|
||||||
out := make([]*biz.StoredFile, 0, len(entries))
|
out := make([]*biz.StoredFile, 0, len(entries))
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
out = append(out, s.file(entry.Key, entry.Fsize))
|
item := s.file(entry.Key, entry.Fsize)
|
||||||
|
item.LastModified = time.Unix(0, entry.PutTime*100)
|
||||||
|
item.ContentType = entry.MimeType
|
||||||
|
out = append(out, item)
|
||||||
}
|
}
|
||||||
return out, marker, more, nil
|
return out, marker, more, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,7 +118,10 @@ func (s *s3Storage) List(ctx context.Context, prefix, cursor string, limit int)
|
||||||
if item.Err != nil {
|
if item.Err != nil {
|
||||||
return nil, "", false, item.Err
|
return nil, "", false, item.Err
|
||||||
}
|
}
|
||||||
out = append(out, s.file(item.Key, item.Size))
|
file := s.file(item.Key, item.Size)
|
||||||
|
file.LastModified = item.LastModified
|
||||||
|
file.ContentType = item.ContentType
|
||||||
|
out = append(out, file)
|
||||||
if len(out) > limit {
|
if len(out) > limit {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
cos "github.com/tencentyun/cos-go-sdk-v5"
|
cos "github.com/tencentyun/cos-go-sdk-v5"
|
||||||
"kra/internal/biz"
|
"kra/internal/biz"
|
||||||
|
|
@ -107,7 +108,14 @@ func (s *tencentStorage) List(ctx context.Context, prefix, cursor string, limit
|
||||||
}
|
}
|
||||||
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
items := make([]*biz.StoredFile, 0, len(result.Contents))
|
||||||
for _, object := range result.Contents {
|
for _, object := range result.Contents {
|
||||||
items = append(items, s.file(object.Key, object.Size))
|
item := s.file(object.Key, object.Size)
|
||||||
|
for _, layout := range []string{time.RFC3339, "2006-01-02T15:04:05.000Z"} {
|
||||||
|
if parsed, parseErr := time.Parse(layout, object.LastModified); parseErr == nil {
|
||||||
|
item.LastModified = parsed
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
next := result.NextMarker
|
next := result.NextMarker
|
||||||
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
if result.IsTruncated && next == "" && len(result.Contents) > 0 {
|
||||||
|
|
|
||||||
|
|
@ -429,6 +429,9 @@ func (r *userRepo) UpdateUserWithAuthorities(ctx context.Context, user *biz.User
|
||||||
}
|
}
|
||||||
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||||
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return r.data.gormDB.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Where("id = ?", id).Delete(&userPO{}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
if err := tx.Where("sys_user_id = ?", id).Delete(&userAuthorityPO{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -438,7 +441,7 @@ func (r *userRepo) DeleteUser(ctx context.Context, id uint) error {
|
||||||
if err := tx.Where("sys_user_id = ?", id).Delete(&userPositionPO{}).Error; err != nil {
|
if err := tx.Where("sys_user_id = ?", id).Delete(&userPositionPO{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.Delete(&userPO{}, id).Error
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string, clearMustChange bool) error {
|
func (r *userRepo) UpdatePassword(ctx context.Context, id uint, password string, clearMustChange bool) error {
|
||||||
|
|
|
||||||
|
|
@ -242,10 +242,10 @@ func (h *Dictionary) Path(c *gin.Context) {
|
||||||
httpx.Fail(c, "字典详情ID格式错误")
|
httpx.Fail(c, "字典详情ID格式错误")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
item, err := h.service.DictionaryDetail(c.Request.Context(), uint(id))
|
path, err := h.service.DictionaryPath(c.Request.Context(), uint(id))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败")
|
httpx.Fail(c, "获取失败")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpx.Write(c, httpx.CodeSuccess, gin.H{"path": item.Path}, "获取成功")
|
httpx.Write(c, httpx.CodeSuccess, gin.H{"path": path}, "获取成功")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,11 +30,6 @@ func exportParams(values url.Values) map[string]string {
|
||||||
out[key] = items[0]
|
out[key] = items[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for key, items := range values {
|
|
||||||
if key != "params" && len(items) > 0 {
|
|
||||||
out[key] = items[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"kra/internal/server/httpx"
|
"kra/internal/server/httpx"
|
||||||
"kra/internal/service"
|
"kra/internal/service"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
|
@ -24,7 +22,7 @@ func (h *Organization) ListDepartments(c *gin.Context) {
|
||||||
_ = c.ShouldBindJSON(&req)
|
_ = c.ShouldBindJSON(&req)
|
||||||
items, err := h.departments.Departments(c.Request.Context(), req.Name)
|
items, err := h.departments.Departments(c.Request.Context(), req.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败")
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
|
httpx.Write(c, httpx.CodeSuccess, items, "获取成功")
|
||||||
|
|
@ -63,19 +61,23 @@ func (h *Organization) UpdateDepartment(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) DeleteDepartment(c *gin.Context) {
|
func (h *Organization) DeleteDepartment(c *gin.Context) {
|
||||||
var req dto.DeleteDepartmentRequest
|
var req dto.DeleteDepartmentRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil {
|
if err := h.departments.Delete(c.Request.Context(), req.ID); err != nil {
|
||||||
httpx.Fail(c, err.Error())
|
httpx.Fail(c, "删除失败:"+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
||||||
}
|
}
|
||||||
func (h *Organization) FindDepartment(c *gin.Context) {
|
func (h *Organization) FindDepartment(c *gin.Context) {
|
||||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
var req dto.DeleteDepartmentRequest
|
||||||
item, err := h.departments.Department(c.Request.Context(), uint(id))
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, err := h.departments.Department(c.Request.Context(), req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败:"+err.Error())
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -83,8 +85,12 @@ func (h *Organization) FindDepartment(c *gin.Context) {
|
||||||
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
|
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
|
||||||
}
|
}
|
||||||
func (h *Organization) DepartmentUsers(c *gin.Context) {
|
func (h *Organization) DepartmentUsers(c *gin.Context) {
|
||||||
id, _ := strconv.ParseUint(c.Query("deptId"), 10, 64)
|
var req dto.SetDepartmentUsersRequest
|
||||||
ids, err := h.departments.UserIDs(c.Request.Context(), uint(id))
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ids, err := h.departments.UserIDs(c.Request.Context(), req.DepartmentID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败:"+err.Error())
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -96,8 +102,12 @@ func (h *Organization) DepartmentUsers(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) SetDepartmentUsers(c *gin.Context) {
|
func (h *Organization) SetDepartmentUsers(c *gin.Context) {
|
||||||
var req dto.SetDepartmentUsersRequest
|
var req dto.SetDepartmentUsersRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.DepartmentID == 0 {
|
||||||
|
httpx.Fail(c, "部门ID不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil {
|
if err := h.departments.SetUsers(c.Request.Context(), &req); err != nil {
|
||||||
|
|
@ -108,8 +118,8 @@ func (h *Organization) SetDepartmentUsers(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) SetUserDepartments(c *gin.Context) {
|
func (h *Organization) SetUserDepartments(c *gin.Context) {
|
||||||
var req dto.SetUserDepartmentsRequest
|
var req dto.SetUserDepartmentsRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil {
|
if err := h.departments.SetUserDepartments(c.Request.Context(), &req); err != nil {
|
||||||
|
|
@ -121,13 +131,13 @@ func (h *Organization) SetUserDepartments(c *gin.Context) {
|
||||||
|
|
||||||
func (h *Organization) ListPositions(c *gin.Context) {
|
func (h *Organization) ListPositions(c *gin.Context) {
|
||||||
var req dto.PositionListRequest
|
var req dto.PositionListRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
items, total, err := h.positions.Positions(c.Request.Context(), &req)
|
items, total, err := h.positions.Positions(c.Request.Context(), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败")
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
httpx.Write(c, httpx.CodeSuccess, httpx.PageResult{List: items, Total: total, Page: req.Page, PageSize: req.PageSize}, "获取成功")
|
||||||
|
|
@ -166,8 +176,8 @@ func (h *Organization) UpdatePosition(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) DeletePosition(c *gin.Context) {
|
func (h *Organization) DeletePosition(c *gin.Context) {
|
||||||
var req dto.DeletePositionRequest
|
var req dto.DeletePositionRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil {
|
if err := h.positions.Delete(c.Request.Context(), req.ID); err != nil {
|
||||||
|
|
@ -177,8 +187,12 @@ func (h *Organization) DeletePosition(c *gin.Context) {
|
||||||
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
httpx.Write(c, httpx.CodeSuccess, nil, "删除成功")
|
||||||
}
|
}
|
||||||
func (h *Organization) FindPosition(c *gin.Context) {
|
func (h *Organization) FindPosition(c *gin.Context) {
|
||||||
id, _ := strconv.ParseUint(c.Query("id"), 10, 64)
|
var req dto.DeletePositionRequest
|
||||||
item, err := h.positions.Position(c.Request.Context(), uint(id))
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
item, err := h.positions.Position(c.Request.Context(), req.ID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败:"+err.Error())
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -186,8 +200,12 @@ func (h *Organization) FindPosition(c *gin.Context) {
|
||||||
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
|
httpx.Write(c, httpx.CodeSuccess, item, "获取成功")
|
||||||
}
|
}
|
||||||
func (h *Organization) PositionUsers(c *gin.Context) {
|
func (h *Organization) PositionUsers(c *gin.Context) {
|
||||||
id, _ := strconv.ParseUint(c.Query("positionId"), 10, 64)
|
var req dto.SetPositionUsersRequest
|
||||||
ids, err := h.positions.UserIDs(c.Request.Context(), uint(id))
|
if err := c.ShouldBindQuery(&req); err != nil {
|
||||||
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ids, err := h.positions.UserIDs(c.Request.Context(), req.PositionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
httpx.Fail(c, "获取失败:"+err.Error())
|
httpx.Fail(c, "获取失败:"+err.Error())
|
||||||
return
|
return
|
||||||
|
|
@ -199,8 +217,12 @@ func (h *Organization) PositionUsers(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) SetPositionUsers(c *gin.Context) {
|
func (h *Organization) SetPositionUsers(c *gin.Context) {
|
||||||
var req dto.SetPositionUsersRequest
|
var req dto.SetPositionUsersRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if req.PositionID == 0 {
|
||||||
|
httpx.Fail(c, "岗位ID不能为空")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil {
|
if err := h.positions.SetUsers(c.Request.Context(), &req); err != nil {
|
||||||
|
|
@ -211,8 +233,8 @@ func (h *Organization) SetPositionUsers(c *gin.Context) {
|
||||||
}
|
}
|
||||||
func (h *Organization) SetUserPositions(c *gin.Context) {
|
func (h *Organization) SetUserPositions(c *gin.Context) {
|
||||||
var req dto.SetUserPositionsRequest
|
var req dto.SetUserPositionsRequest
|
||||||
if c.ShouldBindJSON(&req) != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
httpx.Fail(c, "参数错误")
|
httpx.Fail(c, err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil {
|
if err := h.positions.SetUserPositions(c.Request.Context(), &req); err != nil {
|
||||||
|
|
|
||||||
|
|
@ -123,11 +123,7 @@ func (h *Public) InitializeDatabase(engine *gin.Engine) gin.HandlerFunc {
|
||||||
values = append(values, dto.Route{Path: route.Path, Method: route.Method})
|
values = append(values, dto.Route{Path: route.Path, Method: route.Method})
|
||||||
}
|
}
|
||||||
if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil {
|
if err := h.system.InitializeRoutes(c.Request.Context(), &input, values); err != nil {
|
||||||
if errors.Is(err, biz.ErrTaskRuntimeReload) {
|
httpx.Fail(c, "自动创建数据库失败,请查看后台日志,检查后在进行初始化")
|
||||||
httpx.Fail(c, "数据库已初始化,但定时任务加载失败:"+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpx.Fail(c, "自动创建数据库失败: "+err.Error())
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功")
|
httpx.Write(c, httpx.CodeSuccess, gin.H{}, "自动创建数据库成功")
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,6 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
|
|
||||||
"kra/internal/biz"
|
|
||||||
"kra/internal/server/httpx"
|
"kra/internal/server/httpx"
|
||||||
"kra/internal/service"
|
"kra/internal/service"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
|
@ -67,10 +64,6 @@ func (h *SystemConfig) Set(c *gin.Context) {
|
||||||
|
|
||||||
func (h *SystemConfig) Reload(c *gin.Context) {
|
func (h *SystemConfig) Reload(c *gin.Context) {
|
||||||
if err := h.system.ReloadConfig(c.Request.Context()); err != nil {
|
if err := h.system.ReloadConfig(c.Request.Context()); err != nil {
|
||||||
if errors.Is(err, biz.ErrTaskRuntimeReload) {
|
|
||||||
httpx.Fail(c, "系统配置已重载,但定时任务重载失败:"+err.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpx.Fail(c, "重载系统失败:"+err.Error())
|
httpx.Fail(c, "重载系统失败:"+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,13 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"kra/internal/biz"
|
|
||||||
"kra/internal/server/httpx"
|
"kra/internal/server/httpx"
|
||||||
|
"kra/internal/server/middleware"
|
||||||
"kra/internal/service"
|
"kra/internal/service"
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
|
|
||||||
|
|
@ -31,12 +30,6 @@ func (h *Task) Create(c *gin.Context) {
|
||||||
}
|
}
|
||||||
_, err := h.service.CreateRequest(c.Request.Context(), &req)
|
_, err := h.service.CreateRequest(c.Request.Context(), &req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, biz.ErrTaskSchedule) {
|
|
||||||
var scheduleErr *biz.TaskScheduleError
|
|
||||||
errors.As(err, &scheduleErr)
|
|
||||||
httpx.Fail(c, "调度失败:"+scheduleErr.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpx.Fail(c, "创建失败: "+err.Error())
|
httpx.Fail(c, "创建失败: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -50,12 +43,6 @@ func (h *Task) Update(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
|
if err := h.service.UpdateRequest(c.Request.Context(), &req); err != nil {
|
||||||
if errors.Is(err, biz.ErrTaskSchedule) {
|
|
||||||
var scheduleErr *biz.TaskScheduleError
|
|
||||||
errors.As(err, &scheduleErr)
|
|
||||||
httpx.Fail(c, "调度失败:"+scheduleErr.Error())
|
|
||||||
return
|
|
||||||
}
|
|
||||||
httpx.Fail(c, "更新失败: "+err.Error())
|
httpx.Fail(c, "更新失败: "+err.Error())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -134,8 +121,13 @@ func (h *Task) Methods(c *gin.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *Task) AlertStream(c *gin.Context) {
|
func (h *Task) AlertStream(c *gin.Context) {
|
||||||
|
claims := middleware.Claims(c)
|
||||||
|
if claims == nil || claims.ID == 0 {
|
||||||
|
httpx.Fail(c, "未获取到用户身份")
|
||||||
|
return
|
||||||
|
}
|
||||||
c.Header("Content-Type", "text/event-stream")
|
c.Header("Content-Type", "text/event-stream")
|
||||||
c.Header("Cache-Control", "no-cache")
|
c.Header("Cache-Control", "no-cache, no-transform")
|
||||||
c.Header("Connection", "keep-alive")
|
c.Header("Connection", "keep-alive")
|
||||||
c.Header("X-Accel-Buffering", "no")
|
c.Header("X-Accel-Buffering", "no")
|
||||||
flusher, ok := c.Writer.(http.Flusher)
|
flusher, ok := c.Writer.(http.Flusher)
|
||||||
|
|
@ -145,11 +137,11 @@ func (h *Task) AlertStream(c *gin.Context) {
|
||||||
}
|
}
|
||||||
// Remove the normal server deadline for long-lived SSE connections.
|
// Remove the normal server deadline for long-lived SSE connections.
|
||||||
_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Time{})
|
_ = http.NewResponseController(c.Writer).SetWriteDeadline(time.Time{})
|
||||||
events := h.service.Subscribe()
|
events := h.service.Subscribe(claims.ID)
|
||||||
defer h.service.Unsubscribe(events)
|
defer h.service.Unsubscribe(claims.ID, events)
|
||||||
ticker := time.NewTicker(20 * time.Second)
|
ticker := time.NewTicker(30 * time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
_, _ = fmt.Fprint(c.Writer, "event: connected\ndata: {}\n\n")
|
_, _ = fmt.Fprint(c.Writer, ": connected\n\n")
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
|
|
@ -157,10 +149,10 @@ func (h *Task) AlertStream(c *gin.Context) {
|
||||||
if !open {
|
if !open {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
_, _ = fmt.Fprintf(c.Writer, "event: alert\ndata: %s\n\n", event)
|
_, _ = fmt.Fprintf(c.Writer, "event: timedTask:alert\ndata: %s\n\n", event)
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
_, _ = fmt.Fprint(c.Writer, ": keepalive\n\n")
|
_, _ = fmt.Fprint(c.Writer, ": ping\n\n")
|
||||||
flusher.Flush()
|
flusher.Flush()
|
||||||
case <-c.Request.Context().Done():
|
case <-c.Request.Context().Done():
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -124,6 +124,21 @@ func (s *DictionaryService) DictionaryDetail(ctx context.Context, id uint) (*dto
|
||||||
}
|
}
|
||||||
return detailDTO(v), nil
|
return detailDTO(v), nil
|
||||||
}
|
}
|
||||||
|
func (s *DictionaryService) DictionaryPath(ctx context.Context, id uint) ([]*dto.DictionaryDetailResponse, error) {
|
||||||
|
path := make([]*dto.DictionaryDetailResponse, 0)
|
||||||
|
for id != 0 {
|
||||||
|
value, err := s.uc.FindDictionaryDetail(ctx, id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
path = append([]*dto.DictionaryDetailResponse{detailDTO(value)}, path...)
|
||||||
|
if value.ParentID == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
id = *value.ParentID
|
||||||
|
}
|
||||||
|
return path, nil
|
||||||
|
}
|
||||||
func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]*dto.DictionaryDetailResponse, error) {
|
func (s *DictionaryService) DictionaryTree(ctx context.Context, id uint, typ string) ([]*dto.DictionaryDetailResponse, error) {
|
||||||
items, err := s.uc.DictionaryDetailTree(ctx, id, typ)
|
items, err := s.uc.DictionaryDetailTree(ctx, id, typ)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
||||||
|
|
@ -29,10 +29,10 @@ type MediaCategoryResponse struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type StorageObjectResponse struct {
|
type StorageObjectResponse struct {
|
||||||
Name string `json:"name"`
|
Key string `json:"Key"`
|
||||||
Key string `json:"key"`
|
Size int64 `json:"Size"`
|
||||||
URL string `json:"url"`
|
LastModified time.Time `json:"LastModified"`
|
||||||
Size int64 `json:"size"`
|
ContentType string `json:"ContentType"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type InitUploadResponse struct {
|
type InitUploadResponse struct {
|
||||||
|
|
|
||||||
|
|
@ -15,10 +15,10 @@ type DepartmentListRequest struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
}
|
}
|
||||||
type DeleteDepartmentRequest struct {
|
type DeleteDepartmentRequest struct {
|
||||||
ID uint `json:"ID"`
|
ID uint `json:"ID" form:"id"`
|
||||||
}
|
}
|
||||||
type SetDepartmentUsersRequest struct {
|
type SetDepartmentUsersRequest struct {
|
||||||
DepartmentID uint `json:"deptId"`
|
DepartmentID uint `json:"deptId" form:"deptId"`
|
||||||
UserIDs []uint `json:"userIds"`
|
UserIDs []uint `json:"userIds"`
|
||||||
}
|
}
|
||||||
type SetUserDepartmentsRequest struct {
|
type SetUserDepartmentsRequest struct {
|
||||||
|
|
@ -60,10 +60,10 @@ type PositionListRequest struct {
|
||||||
Status *bool `json:"status"`
|
Status *bool `json:"status"`
|
||||||
}
|
}
|
||||||
type DeletePositionRequest struct {
|
type DeletePositionRequest struct {
|
||||||
ID uint `json:"ID"`
|
ID uint `json:"ID" form:"id"`
|
||||||
}
|
}
|
||||||
type SetPositionUsersRequest struct {
|
type SetPositionUsersRequest struct {
|
||||||
PositionID uint `json:"positionId"`
|
PositionID uint `json:"positionId" form:"positionId"`
|
||||||
UserIDs []uint `json:"userIds"`
|
UserIDs []uint `json:"userIds"`
|
||||||
}
|
}
|
||||||
type SetUserPositionsRequest struct {
|
type SetUserPositionsRequest struct {
|
||||||
|
|
|
||||||
|
|
@ -87,14 +87,20 @@ func (s *ExportService) TemplatesFilter(ctx context.Context, page, size int, nam
|
||||||
return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end})
|
return s.Templates(ctx, page, size, &biz.ExportTemplate{Name: name, TableName: tableName, TemplateID: templateID, StartCreatedAt: start, EndCreatedAt: end})
|
||||||
}
|
}
|
||||||
func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse {
|
func exportDTO(v *biz.ExportTemplate) *dto.ExportTemplateResponse {
|
||||||
conditions := make([]dto.ExportConditionResponse, 0, len(v.Conditions))
|
var conditions []dto.ExportConditionResponse
|
||||||
|
if v.Conditions != nil {
|
||||||
|
conditions = make([]dto.ExportConditionResponse, 0, len(v.Conditions))
|
||||||
for _, x := range v.Conditions {
|
for _, x := range v.Conditions {
|
||||||
conditions = append(conditions, dto.ExportConditionResponse{From: x.From, Column: x.Column, Operator: x.Operator})
|
conditions = append(conditions, dto.ExportConditionResponse{From: x.From, Column: x.Column, Operator: x.Operator})
|
||||||
}
|
}
|
||||||
joins := make([]dto.ExportJoinResponse, 0, len(v.Joins))
|
}
|
||||||
|
var joins []dto.ExportJoinResponse
|
||||||
|
if v.Joins != nil {
|
||||||
|
joins = make([]dto.ExportJoinResponse, 0, len(v.Joins))
|
||||||
for _, x := range v.Joins {
|
for _, x := range v.Joins {
|
||||||
joins = append(joins, dto.ExportJoinResponse{Join: x.Join, Table: x.Table, On: x.On})
|
joins = append(joins, dto.ExportJoinResponse{Join: x.Join, Table: x.Table, On: x.On})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return &dto.ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins}
|
return &dto.ExportTemplateResponse{ID: v.ID, CreatedAt: v.CreatedAt, UpdatedAt: v.UpdatedAt, DeletedAt: nil, DBName: v.DBName, Name: v.Name, TableName: v.TableName, TemplateID: v.TemplateID, TemplateInfo: v.TemplateInfo, SQL: v.SQL, ImportSQL: v.ImportSQL, Limit: v.Limit, Order: v.Order, Conditions: conditions, Joins: joins}
|
||||||
}
|
}
|
||||||
func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error {
|
func (s *ExportService) Create(ctx context.Context, v *biz.ExportTemplate) error {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/xuri/excelize/v2"
|
"github.com/xuri/excelize/v2"
|
||||||
)
|
)
|
||||||
|
|
@ -59,7 +61,23 @@ func (s *ExportService) Export(ctx context.Context, tid string, params map[strin
|
||||||
for rowIndex, row := range rows {
|
for rowIndex, row := range rows {
|
||||||
for column, key := range keys {
|
for column, key := range keys {
|
||||||
cell, _ := excelize.CoordinatesToCellName(column+1, rowIndex+2)
|
cell, _ := excelize.CoordinatesToCellName(column+1, rowIndex+2)
|
||||||
_ = file.SetCellValue(sheet, cell, row[key])
|
lookup := strings.ReplaceAll(strings.ReplaceAll(key, `"`, ""), "`", "")
|
||||||
|
if len(template.Joins) > 0 {
|
||||||
|
if parts := strings.Split(lookup, " as "); len(parts) > 1 {
|
||||||
|
lookup = strings.TrimSpace(parts[1])
|
||||||
|
} else if parts = strings.Split(lookup, "."); len(parts) > 1 {
|
||||||
|
lookup = parts[1]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value := fmt.Sprintf("%v", row[lookup])
|
||||||
|
if timestamp, ok := row[lookup].(time.Time); ok {
|
||||||
|
value = timestamp.Format("2006-01-02 15:04:05")
|
||||||
|
}
|
||||||
|
if number, parseErr := strconv.ParseFloat(value, 64); parseErr == nil {
|
||||||
|
_ = file.SetCellValue(sheet, cell, number)
|
||||||
|
} else {
|
||||||
|
_ = file.SetCellValue(sheet, cell, value)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer, err := file.WriteToBuffer()
|
buffer, err := file.WriteToBuffer()
|
||||||
|
|
@ -108,11 +126,7 @@ func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
sheets := file.GetSheetList()
|
rows, err := file.GetRows("Sheet1")
|
||||||
if len(sheets) == 0 {
|
|
||||||
return errors.New("工作簿没有工作表")
|
|
||||||
}
|
|
||||||
rows, err := file.GetRows(sheets[0])
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
@ -128,6 +142,9 @@ func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader
|
||||||
reverse[labels[key]] = key
|
reverse[labels[key]] = key
|
||||||
}
|
}
|
||||||
headers := rows[0]
|
headers := rows[0]
|
||||||
|
for index := range headers {
|
||||||
|
headers[index] = strings.TrimSpace(headers[index])
|
||||||
|
}
|
||||||
items := make([]map[string]any, 0, len(rows)-1)
|
items := make([]map[string]any, 0, len(rows)-1)
|
||||||
for _, row := range rows[1:] {
|
for _, row := range rows[1:] {
|
||||||
item := map[string]any{}
|
item := map[string]any{}
|
||||||
|
|
@ -142,8 +159,5 @@ func (s *ExportService) Import(ctx context.Context, tid string, reader io.Reader
|
||||||
}
|
}
|
||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
if err = s.uc.ImportExportRows(ctx, template, items); err != nil {
|
return s.uc.ImportExportRows(ctx, template, items)
|
||||||
return fmt.Errorf("导入失败: %w", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ func (s *MediaService) Storage(ctx context.Context, prefix, cursor string, limit
|
||||||
}
|
}
|
||||||
out := make([]*dto.StorageObjectResponse, 0, len(items))
|
out := make([]*dto.StorageObjectResponse, 0, len(items))
|
||||||
for _, v := range items {
|
for _, v := range items {
|
||||||
out = append(out, &dto.StorageObjectResponse{Name: v.Name, Key: v.Path, URL: v.URL, Size: v.Size})
|
out = append(out, &dto.StorageObjectResponse{Key: v.Path, Size: v.Size, LastModified: v.LastModified, ContentType: v.ContentType})
|
||||||
}
|
}
|
||||||
return out, next, more, nil
|
return out, next, more, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -78,8 +78,8 @@ func (s *TaskService) Logs(ctx context.Context, page, size int, taskID uint, sta
|
||||||
|
|
||||||
func (s *TaskService) Trigger(ctx context.Context, id uint) error { return s.uc.Trigger(ctx, id) }
|
func (s *TaskService) Trigger(ctx context.Context, id uint) error { return s.uc.Trigger(ctx, id) }
|
||||||
func (s *TaskService) Reload(ctx context.Context) error { return s.uc.Reload(ctx) }
|
func (s *TaskService) Reload(ctx context.Context) error { return s.uc.Reload(ctx) }
|
||||||
func (s *TaskService) Subscribe() chan []byte { return s.uc.Subscribe() }
|
func (s *TaskService) Subscribe(userID uint) chan []byte { return s.uc.Subscribe(userID) }
|
||||||
func (s *TaskService) Unsubscribe(events chan []byte) { s.uc.Unsubscribe(events) }
|
func (s *TaskService) Unsubscribe(userID uint, events chan []byte) { s.uc.Unsubscribe(userID, events) }
|
||||||
|
|
||||||
func (s *TaskService) RegisteredMethods() []*dto.TaskMethodResponse {
|
func (s *TaskService) RegisteredMethods() []*dto.TaskMethodResponse {
|
||||||
methods := biz.RegisteredTaskMethods()
|
methods := biz.RegisteredTaskMethods()
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ func taskHTTPClient(allowPrivate bool) *http.Client {
|
||||||
func (e *TaskExecutor) runHTTP(ctx context.Context, task *biz.TimedTask) (string, error) {
|
func (e *TaskExecutor) runHTTP(ctx context.Context, task *biz.TimedTask) (string, error) {
|
||||||
parsed, err := url.Parse(task.HTTPURL)
|
parsed, err := url.Parse(task.HTTPURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", fmt.Errorf("URL 非法: %w", err)
|
||||||
}
|
}
|
||||||
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
if parsed.Scheme != "http" && parsed.Scheme != "https" {
|
||||||
return "", fmt.Errorf("仅允许 http/https, 实际为 %q", parsed.Scheme)
|
return "", fmt.Errorf("仅允许 http/https, 实际为 %q", parsed.Scheme)
|
||||||
|
|
@ -70,7 +70,11 @@ func (e *TaskExecutor) runHTTP(ctx context.Context, task *biz.TimedTask) (string
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
headers := map[string]string{}
|
headers := map[string]string{}
|
||||||
_ = json.Unmarshal(task.HTTPHeader, &headers)
|
if len(task.HTTPHeader) > 0 {
|
||||||
|
if err = json.Unmarshal(task.HTTPHeader, &headers); err != nil {
|
||||||
|
return "", fmt.Errorf("http_header 必须是 JSON 对象: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
for key, value := range headers {
|
for key, value := range headers {
|
||||||
request.Header.Set(key, value)
|
request.Header.Set(key, value)
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +122,8 @@ func (e *TaskExecutor) runMethod(task *biz.TimedTask) error {
|
||||||
ttl = int(config.Media.SessionTtl)
|
ttl = int(config.Media.SessionTtl)
|
||||||
}
|
}
|
||||||
err = e.media.CleanupStale(ctx, ttl)
|
err = e.media.CleanupStale(ctx, ttl)
|
||||||
|
default:
|
||||||
|
err = fmt.Errorf("方法 %s 未注册(需在 initialize/timer.go 经 task.Register 注册)", task.MethodName)
|
||||||
}
|
}
|
||||||
done <- err
|
done <- err
|
||||||
}()
|
}()
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import (
|
||||||
|
|
||||||
type TaskScheduler struct {
|
type TaskScheduler struct {
|
||||||
tasks *biz.TaskUsecase
|
tasks *biz.TaskUsecase
|
||||||
|
authorities *biz.AuthorityUsecase
|
||||||
executor *TaskExecutor
|
executor *TaskExecutor
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
standard *cron.Cron
|
standard *cron.Cron
|
||||||
|
|
@ -24,7 +25,7 @@ type TaskScheduler struct {
|
||||||
runContext context.Context
|
runContext context.Context
|
||||||
cancel context.CancelFunc
|
cancel context.CancelFunc
|
||||||
subMu sync.RWMutex
|
subMu sync.RWMutex
|
||||||
subscribers map[chan []byte]struct{}
|
subscribers map[uint]map[chan []byte]struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
type scheduledEntry struct {
|
type scheduledEntry struct {
|
||||||
|
|
@ -32,8 +33,8 @@ type scheduledEntry struct {
|
||||||
entry cron.EntryID
|
entry cron.EntryID
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskScheduler(tasks *biz.TaskUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler {
|
func NewTaskScheduler(tasks *biz.TaskUsecase, authorities *biz.AuthorityUsecase, executor *TaskExecutor, logger *slog.Logger) *TaskScheduler {
|
||||||
return &TaskScheduler{tasks: tasks, executor: executor, logger: logger, standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, subscribers: map[chan []byte]struct{}{}}
|
return &TaskScheduler{tasks: tasks, authorities: authorities, executor: executor, logger: logger, standard: cron.New(), seconds: cron.New(cron.WithSeconds()), entries: map[uint]scheduledEntry{}, subscribers: map[uint]map[chan []byte]struct{}{}}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTaskRuntime(scheduler *TaskScheduler) biz.TaskRuntime { return scheduler }
|
func NewTaskRuntime(scheduler *TaskScheduler) biz.TaskRuntime { return scheduler }
|
||||||
|
|
@ -131,7 +132,12 @@ func (s *TaskScheduler) executionContext() context.Context {
|
||||||
func (s *TaskScheduler) run(task *biz.TimedTask, trigger string) {
|
func (s *TaskScheduler) run(task *biz.TimedTask, trigger string) {
|
||||||
log := s.executor.Run(s.executionContext(), task, trigger)
|
log := s.executor.Run(s.executionContext(), task, trigger)
|
||||||
if log.Status != "success" {
|
if log.Status != "success" {
|
||||||
s.Broadcast(map[string]any{"taskId": task.ID, "taskName": task.Name, "status": log.Status, "errorMsg": log.ErrorMsg, "time": time.Now()})
|
ids, err := s.authorities.AuthorityUserIDs(context.Background(), 888)
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error("query timed task alert recipients failed", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
s.PublishToUsers(ids, map[string]any{"taskId": task.ID, "name": task.Name, "error": log.ErrorMsg, "time": time.Now().Format(time.RFC3339)})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -196,32 +202,44 @@ func (s *TaskScheduler) Trigger(task *biz.TimedTask) {
|
||||||
go s.run(©, "manual")
|
go s.run(©, "manual")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *TaskScheduler) Subscribe() chan []byte {
|
func (s *TaskScheduler) Subscribe(userID uint) chan []byte {
|
||||||
ch := make(chan []byte, 16)
|
ch := make(chan []byte, 16)
|
||||||
s.subMu.Lock()
|
s.subMu.Lock()
|
||||||
s.subscribers[ch] = struct{}{}
|
if s.subscribers[userID] == nil {
|
||||||
|
s.subscribers[userID] = map[chan []byte]struct{}{}
|
||||||
|
}
|
||||||
|
s.subscribers[userID][ch] = struct{}{}
|
||||||
s.subMu.Unlock()
|
s.subMu.Unlock()
|
||||||
return ch
|
return ch
|
||||||
}
|
}
|
||||||
func (s *TaskScheduler) Unsubscribe(ch chan []byte) {
|
func (s *TaskScheduler) Unsubscribe(userID uint, ch chan []byte) {
|
||||||
s.subMu.Lock()
|
s.subMu.Lock()
|
||||||
if _, ok := s.subscribers[ch]; ok {
|
if subscribers := s.subscribers[userID]; subscribers != nil {
|
||||||
delete(s.subscribers, ch)
|
if _, ok := subscribers[ch]; !ok {
|
||||||
|
s.subMu.Unlock()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
delete(subscribers, ch)
|
||||||
|
if len(subscribers) == 0 {
|
||||||
|
delete(s.subscribers, userID)
|
||||||
|
}
|
||||||
close(ch)
|
close(ch)
|
||||||
}
|
}
|
||||||
s.subMu.Unlock()
|
s.subMu.Unlock()
|
||||||
}
|
}
|
||||||
func (s *TaskScheduler) Broadcast(value any) {
|
func (s *TaskScheduler) PublishToUsers(userIDs []uint, value any) {
|
||||||
raw, err := json.Marshal(value)
|
raw, err := json.Marshal(value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
raw = []byte(fmt.Sprint(value))
|
raw = []byte(fmt.Sprint(value))
|
||||||
}
|
}
|
||||||
s.subMu.RLock()
|
s.subMu.RLock()
|
||||||
defer s.subMu.RUnlock()
|
defer s.subMu.RUnlock()
|
||||||
for ch := range s.subscribers {
|
for _, userID := range userIDs {
|
||||||
|
for ch := range s.subscribers[userID] {
|
||||||
select {
|
select {
|
||||||
case ch <- raw:
|
case ch <- raw:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ type Claims struct {
|
||||||
NickName string
|
NickName string
|
||||||
AuthorityID uint `json:"AuthorityId"`
|
AuthorityID uint `json:"AuthorityId"`
|
||||||
BufferTime int64
|
BufferTime int64
|
||||||
|
UserType string
|
||||||
MustChangePwd bool `json:"mustChangePwd"`
|
MustChangePwd bool `json:"mustChangePwd"`
|
||||||
jwt.RegisteredClaims
|
jwt.RegisteredClaims
|
||||||
}
|
}
|
||||||
|
|
@ -31,7 +32,7 @@ func Generate(secret, issuer string, expires, buffer time.Duration, userID, auth
|
||||||
return "", nil, errors.New("empty JWT signing key")
|
return "", nil, errors.New("empty JWT signing key")
|
||||||
}
|
}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
claims := &Claims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, BufferTime: int64(buffer / time.Second), MustChangePwd: mustChange, RegisteredClaims: jwt.RegisteredClaims{Issuer: issuer, IssuedAt: jwt.NewNumericDate(now), NotBefore: jwt.NewNumericDate(now.Add(-time.Second)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}}
|
claims := &Claims{UUID: uuid, ID: userID, Username: username, NickName: nickname, AuthorityID: authorityID, BufferTime: int64(buffer / time.Second), UserType: "admin", MustChangePwd: mustChange, RegisteredClaims: jwt.RegisteredClaims{Audience: jwt.ClaimStrings{"GVA"}, Issuer: issuer, NotBefore: jwt.NewNumericDate(now.Add(-1000)), ExpiresAt: jwt.NewNumericDate(now.Add(expires))}}
|
||||||
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte(secret))
|
||||||
return token, claims, err
|
return token, claims, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue