This commit is contained in:
2026-08-16 01:19:10 +08:00
parent c4b114762b
commit c554152f4f
43 changed files with 1311 additions and 318 deletions
+12 -1
View File
@@ -36,9 +36,20 @@ func (uc *AuthorityUsecase) AuthorityTree(ctx context.Context) ([]*Authority, er
if item.ParentID != nil && *item.ParentID != 0 && byID[*item.ParentID] != nil {
parent := byID[*item.ParentID]
parent.Children = append(parent.Children, item)
} else {
} else if item.ParentID == nil || *item.ParentID == 0 {
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
}
+8 -4
View File
@@ -17,10 +17,12 @@ type Cache interface {
}
type StoredFile struct {
Name string
Path string
URL string
Size int64
Name string
Path string
URL string
Size int64
LastModified time.Time
ContentType string
}
// FileStorage owns the persistence boundary for uploaded files.
@@ -50,6 +52,7 @@ type CaptchaSettings struct {
type MediaSettings struct {
SessionTTL int
MaxFileSize int64
ChunkDir string
}
// RuntimeSettings exposes only the active values needed by the application.
@@ -75,6 +78,7 @@ type AuthClaims struct {
Username string
NickName string
AuthorityID uint
UserType string
BufferTime time.Duration
MustChangePwd bool
Issuer string
+4 -3
View File
@@ -22,11 +22,12 @@ type MediaRepo interface {
type MediaUsecase struct {
MediaRepo
files FileStorage
files FileStorage
settings RuntimeSettings
}
func NewMediaUsecase(repo MediaRepo, files FileStorage) *MediaUsecase {
return &MediaUsecase{MediaRepo: repo, files: files}
func NewMediaUsecase(repo MediaRepo, files FileStorage, settings RuntimeSettings) *MediaUsecase {
return &MediaUsecase{MediaRepo: repo, files: files, settings: settings}
}
var allowedMediaExtensions = map[string]bool{
+20 -5
View File
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"path"
"path/filepath"
"sort"
"strings"
@@ -15,6 +16,20 @@ import (
"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) {
if err := validateMediaName(name); err != nil {
return nil, nil, nil, err
@@ -57,7 +72,7 @@ func (uc *MediaUsecase) SaveChunk(ctx context.Context, userID, uploadID uint, in
return errors.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))
if err != nil {
return err
@@ -95,7 +110,7 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
if chunk.Index != index {
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))
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.DeleteChunks(ctx, uploadID)
_ = uc.files.DeletePrefix(ctx, fmt.Sprintf(".chunks/%d", uploadID))
_ = uc.files.DeletePrefix(ctx, uc.chunkPrefix(uploadID))
return media, nil
}
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("上传会话不存在或无权操作")
}
_ = 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)
}
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 {
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
}
}
+9 -7
View File
@@ -91,8 +91,8 @@ type TaskRuntime interface {
TriggerID(context.Context, uint) error
NextRuns() map[uint]time.Time
Reload(context.Context) error
Subscribe() chan []byte
Unsubscribe(chan []byte)
Subscribe(uint) chan []byte
Unsubscribe(uint, chan []byte)
}
type TaskUsecase struct{ TaskRepo }
@@ -115,7 +115,7 @@ func (uc *TaskUsecase) Validate(value *TimedTask) error {
switch value.ExecutorType {
case TaskExecutorMethod:
if !registeredTaskMethod(value.MethodName) {
return errors.New("方法未注册")
return fmt.Errorf("方法 %s 未注册", value.MethodName)
}
if len(value.Params) > 0 && !json.Valid(value.Params) {
return errors.New("params 必须是合法 JSON")
@@ -128,7 +128,7 @@ func (uc *TaskUsecase) Validate(value *TimedTask) error {
if len(value.HTTPHeader) > 0 {
headers := map[string]string{}
if json.Unmarshal(value.HTTPHeader, &headers) != nil {
return errors.New("httpHeader 必须是 JSON 对象")
return errors.New(`httpHeader 必须是 {"Key":"Value"} 形式的 JSON 对象`)
}
}
default:
@@ -229,7 +229,9 @@ func (uc *TaskApplicationUsecase) Reload(ctx context.Context) error {
return uc.runtime.Reload(ctx)
}
func (uc *TaskApplicationUsecase) Subscribe() chan []byte { return uc.runtime.Subscribe() }
func (uc *TaskApplicationUsecase) Unsubscribe(events chan []byte) {
uc.runtime.Unsubscribe(events)
func (uc *TaskApplicationUsecase) Subscribe(userID uint) chan []byte {
return uc.runtime.Subscribe(userID)
}
func (uc *TaskApplicationUsecase) Unsubscribe(userID uint, events chan []byte) {
uc.runtime.Unsubscribe(userID, events)
}