mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-10-04 07:06:36 +08:00
fix(backend): 修复优雅停机失效、系统设置并发读写冲突、文件服务API信封绕过以及测试uploads目录污染
- 修复 ClickHouse Batch Writer 与 RiskControl LogWriter 优雅停机,确保退出前队列数据正确刷盘。 - 修复 OpenFlare 系统配置参数全局变量并发读写的 Data Race 冲突,在读取配置时引入读锁保护。 - 修复 upload 模块的 API 错误响应格式,使用 response.Abort* 代替原始的 c.AbortWithStatus 与 c.JSON,保证全局信封格式统一。 - 修复 pkg/utils/network 的 isPrivateIPv4 以使用标准库 net.IP.IsPrivate() 校验,修复 format 的 Bytes2Size 边界,修改大小单位因子变量为只读常量。 - 重构 upload 文件服务与路由器测试,使用 t.TempDir() 代替硬编码的 uploads 相对路径写入和删除,解决测试目录文件污染问题。
This commit is contained in:
@@ -112,7 +112,7 @@ func DeleteFile(c *gin.Context) {
|
||||
|
||||
if _, err := softDeleteUpload(ctx, uploadID); err != nil {
|
||||
if isRecordNotFound(err) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
response.AbortNotFound(c, "文件记录未找到")
|
||||
return
|
||||
}
|
||||
response.AbortBadRequest(c, shared.ErrDeleteFileFailed)
|
||||
@@ -233,11 +233,11 @@ func DeleteMyFile(c *gin.Context) {
|
||||
|
||||
if _, err := softDeleteOwnedUpload(ctx, currUser.ID, uploadID); err != nil {
|
||||
if isRecordNotFound(err) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
response.AbortNotFound(c, "文件记录未找到")
|
||||
return
|
||||
}
|
||||
if err == ingest.ErrForbidden {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
response.AbortForbidden(c, "无权操作")
|
||||
return
|
||||
}
|
||||
response.AbortBadRequest(c, shared.ErrDeleteFileFailed)
|
||||
@@ -287,11 +287,11 @@ func UpdateMyFile(c *gin.Context) {
|
||||
upload, err := updateOwnedUpload(ctx, currUser.ID, uploadID, updateMyUploadInput(req))
|
||||
if err != nil {
|
||||
if isRecordNotFound(err) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
response.AbortNotFound(c, "文件记录未找到")
|
||||
return
|
||||
}
|
||||
if err == ingest.ErrForbidden {
|
||||
c.AbortWithStatus(http.StatusForbidden)
|
||||
response.AbortForbidden(c, "无权操作")
|
||||
return
|
||||
}
|
||||
response.AbortBadRequest(c, "更新文件记录失败")
|
||||
|
||||
@@ -170,7 +170,7 @@ func DownloadFile(c *gin.Context) {
|
||||
upload, err := filesrv.GetUploadRecordByID(c)
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
c.AbortWithStatus(http.StatusNotFound)
|
||||
response.AbortNotFound(c, "文件记录未找到")
|
||||
return
|
||||
}
|
||||
if _, ok := err.(*strconv.NumError); ok {
|
||||
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@@ -29,6 +30,7 @@ import (
|
||||
"github.com/Rain-kl/Wavelet/internal/storage"
|
||||
"github.com/Rain-kl/Wavelet/internal/testhelper"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type testResponse struct {
|
||||
@@ -104,7 +106,9 @@ func createMultipartRequest(t *testing.T, fieldName, fileName string, fileConten
|
||||
func TestUploadFile(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }() // Clean up local files created during tests
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
@@ -322,7 +326,7 @@ func TestUploadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// Confirm file was actually written to local disk
|
||||
fileContent, err := os.ReadFile(localRecord.FilePath)
|
||||
fileContent, err := os.ReadFile(filepath.Join(tempDir, localRecord.FilePath))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to read local file: %v", err)
|
||||
}
|
||||
@@ -336,7 +340,9 @@ func TestUploadFile(t *testing.T) {
|
||||
func TestDownloadFile(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
@@ -346,7 +352,7 @@ func TestDownloadFile(t *testing.T) {
|
||||
ID: 2001,
|
||||
UserID: 1001,
|
||||
FileName: "中文文件名.txt",
|
||||
FilePath: "uploads/test_download.txt",
|
||||
FilePath: "test_download.txt",
|
||||
FileSize: 12,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -354,11 +360,7 @@ func TestDownloadFile(t *testing.T) {
|
||||
}
|
||||
|
||||
// Create local file
|
||||
err := os.MkdirAll("uploads", 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create directory: %v", err)
|
||||
}
|
||||
err = os.WriteFile(localUpload.FilePath, []byte("hello download"), 0644)
|
||||
err := os.WriteFile(filepath.Join(tempDir, "test_download.txt"), []byte("hello download"), 0644)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to write file: %v", err)
|
||||
}
|
||||
@@ -405,6 +407,9 @@ func TestListFiles(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
@@ -413,7 +418,7 @@ func TestListFiles(t *testing.T) {
|
||||
ID: 2101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "first-report.txt",
|
||||
FilePath: "uploads/first-report.txt",
|
||||
FilePath: "first-report.txt",
|
||||
FileSize: 10,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -423,7 +428,7 @@ func TestListFiles(t *testing.T) {
|
||||
ID: 2102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "Second-Photo.PNG",
|
||||
FilePath: "uploads/second-photo.png",
|
||||
FilePath: "second-photo.png",
|
||||
FileSize: 20,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
@@ -433,7 +438,7 @@ func TestListFiles(t *testing.T) {
|
||||
ID: 2103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "third-notes.md",
|
||||
FilePath: "uploads/third-notes.md",
|
||||
FilePath: "third-notes.md",
|
||||
FileSize: 30,
|
||||
MimeType: "text/markdown",
|
||||
Extension: "md",
|
||||
@@ -443,7 +448,7 @@ func TestListFiles(t *testing.T) {
|
||||
ID: 2104,
|
||||
UserID: 2002,
|
||||
FileName: "other-user.txt",
|
||||
FilePath: "uploads/other-user.txt",
|
||||
FilePath: "other-user.txt",
|
||||
FileSize: 40,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -544,20 +549,22 @@ func TestListFiles(t *testing.T) {
|
||||
func TestBatchDownloadFiles(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
// Create and write files locally
|
||||
err := os.MkdirAll("uploads", 0755)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create local dir: %v", err)
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "f1.txt"), []byte("file1 content"), 0644); err != nil {
|
||||
t.Fatalf("failed to write f1.txt: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "f2.txt"), []byte("file2 content"), 0644); err != nil {
|
||||
t.Fatalf("failed to write f2.txt: %v", err)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(tempDir, "f3.txt"), []byte("duplicate name file content"), 0644); err != nil {
|
||||
t.Fatalf("failed to write f3.txt: %v", err)
|
||||
}
|
||||
|
||||
_ = os.WriteFile("uploads/f1.txt", []byte("file1 content"), 0644)
|
||||
_ = os.WriteFile("uploads/f2.txt", []byte("file2 content"), 0644)
|
||||
_ = os.WriteFile("uploads/f3.txt", []byte("duplicate name file content"), 0644)
|
||||
|
||||
// Seed upload records. Note f2 and f3 have the same FileName "file_a.txt" to trigger name collision resolution.
|
||||
uploads := []model.Upload{
|
||||
@@ -565,7 +572,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
ID: 3001,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt",
|
||||
FilePath: "uploads/f1.txt",
|
||||
FilePath: "f1.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -575,7 +582,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
ID: 3002,
|
||||
UserID: 1001,
|
||||
FileName: "file_b.txt",
|
||||
FilePath: "uploads/f2.txt",
|
||||
FilePath: "f2.txt",
|
||||
FileSize: 13,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -585,7 +592,7 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
ID: 3003,
|
||||
UserID: 1001,
|
||||
FileName: "file_a.txt", // COLLISION with 3001!
|
||||
FilePath: "uploads/f3.txt",
|
||||
FilePath: "f3.txt",
|
||||
FileSize: 28,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -656,7 +663,9 @@ func TestBatchDownloadFiles(t *testing.T) {
|
||||
func TestUploadAccessModeAccessControl(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
defer func() { _ = os.RemoveAll("uploads") }()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
user1 := &model.User{ID: 1001, Username: "user1"}
|
||||
user2 := &model.User{ID: 1002, Username: "user2"}
|
||||
@@ -744,6 +753,9 @@ func TestGetFileStats(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
authUser := &model.User{ID: 1001, Username: "test_user"}
|
||||
router := setupTestRouter(authUser)
|
||||
|
||||
@@ -753,7 +765,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
ID: 3101,
|
||||
UserID: authUser.ID,
|
||||
FileName: "photo.png",
|
||||
FilePath: "uploads/photo.png",
|
||||
FilePath: "photo.png",
|
||||
FileSize: 100,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
@@ -765,7 +777,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
ID: 3102,
|
||||
UserID: authUser.ID,
|
||||
FileName: "video.mp4",
|
||||
FilePath: "uploads/video.mp4",
|
||||
FilePath: "video.mp4",
|
||||
FileSize: 500,
|
||||
MimeType: "video/mp4",
|
||||
Extension: "mp4",
|
||||
@@ -777,7 +789,7 @@ func TestGetFileStats(t *testing.T) {
|
||||
ID: 3103,
|
||||
UserID: authUser.ID,
|
||||
FileName: "document.pdf",
|
||||
FilePath: "uploads/document.pdf",
|
||||
FilePath: "document.pdf",
|
||||
FileSize: 200,
|
||||
MimeType: "application/pdf",
|
||||
Extension: "pdf",
|
||||
@@ -854,6 +866,9 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
dbConn, _, cleanup := testhelper.SetupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
tempDir := t.TempDir()
|
||||
configureLocalStorageRoot(t, dbConn, tempDir)
|
||||
|
||||
user1 := &model.User{ID: 1001, Username: "user1"}
|
||||
user2 := &model.User{ID: 1002, Username: "user2"}
|
||||
|
||||
@@ -868,7 +883,7 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
ID: 4001,
|
||||
UserID: 1001,
|
||||
FileName: "user1-file.txt",
|
||||
FilePath: "uploads/user1-file.txt",
|
||||
FilePath: "user1-file.txt",
|
||||
FileSize: 100,
|
||||
MimeType: "text/plain",
|
||||
Extension: "txt",
|
||||
@@ -879,7 +894,7 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
ID: 4002,
|
||||
UserID: 1002,
|
||||
FileName: "user2-file.png",
|
||||
FilePath: "uploads/user2-file.png",
|
||||
FilePath: "user2-file.png",
|
||||
FileSize: 200,
|
||||
MimeType: "image/png",
|
||||
Extension: "png",
|
||||
@@ -977,3 +992,26 @@ func TestUserUploadManagement(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func configureLocalStorageRoot(t *testing.T, dbConn *gorm.DB, tempDir string) {
|
||||
var sc model.SystemConfig
|
||||
if err := dbConn.Where("key = ?", model.ConfigKeyStorageConfig).First(&sc).Error; err != nil {
|
||||
t.Fatalf("failed to find storage config: %v", err)
|
||||
}
|
||||
var cfg storage.Config
|
||||
if err := json.Unmarshal([]byte(sc.Value), &cfg); err != nil {
|
||||
t.Fatalf("failed to unmarshal storage config: %v", err)
|
||||
}
|
||||
cfg.Local.Root = tempDir
|
||||
newVal, err := json.Marshal(cfg)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to marshal storage config: %v", err)
|
||||
}
|
||||
sc.Value = string(newVal)
|
||||
if err := dbConn.Save(&sc).Error; err != nil {
|
||||
t.Fatalf("failed to save storage config: %v", err)
|
||||
}
|
||||
_ = db.HSetJSON(context.Background(), repository.SystemConfigRedisHashKey, sc.Key, &sc)
|
||||
repository.ResetSystemConfigRAMCacheForTest()
|
||||
storage.ResetCache()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user