From 06e3f0b48967ecb76b96f04e7bdaddcfc69f69ed Mon Sep 17 00:00:00 2001 From: richjohnrambo Date: Fri, 18 Sep 2026 16:29:14 +0800 Subject: [PATCH] fix: resolve meeting note files from oss urls --- internal/store/meeting_note_file_test.go | 24 +++++++++ internal/store/mysql.go | 66 +++++++++++++++++++++--- 2 files changed, 84 insertions(+), 6 deletions(-) diff --git a/internal/store/meeting_note_file_test.go b/internal/store/meeting_note_file_test.go index 312cdd1..53ffc9b 100644 --- a/internal/store/meeting_note_file_test.go +++ b/internal/store/meeting_note_file_test.go @@ -50,3 +50,27 @@ func TestParseUploadFileIDAcceptsExtension(t *testing.T) { t.Fatal("storageName is empty") } } + +func TestParseUploadFileIDAcceptsOSSURL(t *testing.T) { + decodedID, storageName := parseUploadFileID("https://oss.example.com/files/lxzvtUzicEo.pdf?Expires=1") + if decodedID <= 0 { + t.Fatalf("decodedID=%d, want positive", decodedID) + } + if storageName == "" { + t.Fatal("storageName is empty") + } +} + +func TestParseUploadFileIDAcceptsObjectKey(t *testing.T) { + decodedID, storageName := parseUploadFileID("tenants/1/2026/09/s4oo7nkm4jyeu") + if decodedID != 0 { + t.Fatalf("decodedID=%d, want 0 for object key", decodedID) + } + if storageName != "s4oo7nkm4jyeu" { + t.Fatalf("storageName=%q, want s4oo7nkm4jyeu", storageName) + } + candidates := uploadLocationCandidates("tenants/1/2026/09/s4oo7nkm4jyeu", storageName) + if len(candidates) < 2 || candidates[0] != "s4oo7nkm4jyeu" || candidates[1] != "tenants/1/2026/09/s4oo7nkm4jyeu" { + t.Fatalf("candidates=%#v", candidates) + } +} diff --git a/internal/store/mysql.go b/internal/store/mysql.go index b4f932e..c1ed036 100644 --- a/internal/store/mysql.go +++ b/internal/store/mysql.go @@ -10,6 +10,7 @@ import ( "errors" "fmt" "mime" + "net/url" "path/filepath" "strings" "sync" @@ -690,7 +691,8 @@ type fileUploadMetadata struct { func (s *Store) fileUploadMetadata(ctx context.Context, fileID string) fileUploadMetadata { var meta fileUploadMetadata decodedID, storageName := parseUploadFileID(fileID) - if decodedID <= 0 && storageName == "" { + locationCandidates := uploadLocationCandidates(fileID, storageName) + if decodedID <= 0 && len(locationCandidates) == 0 { return meta } if decodedID > 0 { @@ -699,16 +701,18 @@ func (s *Store) fileUploadMetadata(ctx context.Context, fileID string) fileUploa return meta } } - if storageName == "" { - return meta + for _, candidate := range locationCandidates { + err := s.db.GetContext(ctx, &meta, `SELECT IFNULL(mimetype,'') AS mimetype,IFNULL(size,0) AS size,IFNULL(etag,'') AS etag,IFNULL(location,'') AS location + FROM im_fileuploads WHERE location=? OR location LIKE ? ORDER BY createdat DESC LIMIT 1`, candidate, "%/"+candidate) + if err == nil { + return meta + } } - _ = s.db.GetContext(ctx, &meta, `SELECT IFNULL(mimetype,'') AS mimetype,IFNULL(size,0) AS size,IFNULL(etag,'') AS etag,IFNULL(location,'') AS location - FROM im_fileuploads WHERE location=? OR location LIKE ? ORDER BY createdat DESC LIMIT 1`, storageName, "%/"+storageName) return meta } func parseUploadFileID(fileID string) (int64, string) { - name := strings.TrimSpace(fileID) + name := uploadFileName(fileID) if dot := strings.LastIndexByte(name, '.'); dot > 0 { ext := name[dot+1:] if len(ext) == 0 || len(ext) > 16 { @@ -723,6 +727,9 @@ func parseUploadFileID(fileID string) (int64, string) { } decoded, err := base64.URLEncoding.WithPadding(base64.NoPadding).DecodeString(name) if err != nil || len(decoded) < 8 { + if isStorageFileName(name) { + return 0, name + } return 0, "" } storageData := make([]byte, 8) @@ -737,6 +744,53 @@ func parseUploadFileID(fileID string) (int64, string) { return int64(binary.LittleEndian.Uint64(plain)), storageName } +func uploadFileName(fileID string) string { + name := strings.TrimSpace(fileID) + if parsed, err := url.Parse(name); err == nil && parsed.Path != "" { + name = parsed.Path + } + if q := strings.IndexAny(name, "?#"); q >= 0 { + name = name[:q] + } + name = strings.TrimRight(strings.ReplaceAll(name, "\\", "/"), "/") + if slash := strings.LastIndexByte(name, '/'); slash >= 0 { + name = name[slash+1:] + } + return strings.TrimSpace(name) +} + +func uploadLocationCandidates(fileID, storageName string) []string { + seen := map[string]bool{} + var candidates []string + add := func(value string) { + value = strings.Trim(strings.TrimSpace(value), "/") + if value == "" || seen[value] { + return + } + seen[value] = true + candidates = append(candidates, value) + } + add(storageName) + raw := strings.TrimSpace(fileID) + if parsed, err := url.Parse(raw); err == nil && parsed.Path != "" { + add(parsed.Path) + } + add(raw) + return candidates +} + +func isStorageFileName(name string) bool { + if len(name) != 13 { + return false + } + for _, r := range name { + if (r < 'a' || r > 'z') && (r < '2' || r > '7') { + return false + } + } + return true +} + func normalizeMeetingNoteFileMetadata(input MeetingNoteFileInput, meta fileUploadMetadata, fileID string) (string, string, int64, string) { mimeType := firstNonEmpty(input.MIMEType, meta.MIMEType) fileSize := int64(0)