diff --git a/internal/pkg/utils/content_chunk.go b/internal/pkg/utils/content_chunk.go index 154974a..52b9799 100644 --- a/internal/pkg/utils/content_chunk.go +++ b/internal/pkg/utils/content_chunk.go @@ -16,6 +16,7 @@ const ( type ContentChunk struct { Type ContentChunkType // Content string // text content or image url + AssetID string // image asset id } func SplitHTMLContentChunks(content string) ([]ContentChunk, error) { @@ -57,12 +58,14 @@ func SplitHTMLContentChunks(content string) ([]ContentChunk, error) { case "img": flushText() src := strings.TrimSpace(findContentChunkHTMLAttr(node, "src")) - if src == "" { + assetID := strings.TrimSpace(findContentChunkHTMLAttr(node, "data-asset-id")) + if src == "" && assetID == "" { return } chunks = append(chunks, ContentChunk{ Type: ContentChunkTypeImage, Content: src, + AssetID: assetID, }) return } diff --git a/internal/pkg/utils/content_chunk_test.go b/internal/pkg/utils/content_chunk_test.go index a44bd70..32a4463 100644 --- a/internal/pkg/utils/content_chunk_test.go +++ b/internal/pkg/utils/content_chunk_test.go @@ -51,6 +51,13 @@ func TestSplitHTMLContentChunks(t *testing.T) { {Type: ContentChunkTypeText, Content: "后文"}, }, }, + { + name: "image with asset metadata only", + content: `

a

`, + want: []ContentChunk{ + {Type: ContentChunkTypeImage, AssetID: "asset_1"}, + }, + }, { name: "empty html returns empty chunks", content: "


", diff --git a/internal/pkg/utils/message.go b/internal/pkg/utils/message.go index c6944e0..fa181e6 100644 --- a/internal/pkg/utils/message.go +++ b/internal/pkg/utils/message.go @@ -3,13 +3,11 @@ package utils import ( "bytes" "cs-agent/internal/models" - "cs-agent/internal/pkg/config" "cs-agent/internal/pkg/enums" "cs-agent/internal/repositories" "cs-agent/internal/services/storage" "encoding/json" "fmt" - "net/url" "strings" "github.com/microcosm-cc/bluemonday" @@ -335,7 +333,6 @@ func normalizeHTMLImageAsset(node *html.Node) (*models.Asset, error) { assetID := strings.TrimSpace(findHTMLAttr(node, "data-asset-id")) provider := enums.AssetProvider(strings.TrimSpace(findHTMLAttr(node, "data-provider"))) storageKey := strings.TrimSpace(findHTMLAttr(node, "data-storage-key")) - src := strings.TrimSpace(findHTMLAttr(node, "src")) hasAssetID := assetID != "" hasProvider := provider != "" @@ -353,94 +350,5 @@ func normalizeHTMLImageAsset(node *html.Node) (*models.Asset, error) { } return asset, nil } - if src == "" { - return nil, fmt.Errorf("html message image is missing asset metadata") - } - asset := findAssetByMessageImageURL(src) - if asset == nil { - return nil, fmt.Errorf("html message image must reference an uploaded asset") - } - return asset, nil -} - -func findAssetByMessageImageURL(rawURL string) *models.Asset { - storageKey, err := resolveStorageKeyFromMessageImageURL(rawURL) - if err != nil { - return nil - } - return repositories.AssetRepository.GetByStorageKey(sqls.DB(), storageKey) -} - -func FindAssetByMessageImageURL(rawURL string) *models.Asset { - return findAssetByMessageImageURL(rawURL) -} - -func resolveStorageKeyFromMessageImageURL(rawURL string) (string, error) { - cfg := config.Current().Storage - candidates := make([]string, 0, 3) - if baseURL := strings.TrimSpace(cfg.Local.BaseURL); baseURL != "" { - candidates = append(candidates, baseURL) - } - if baseURL := strings.TrimSpace(cfg.OSS.BaseURL); baseURL != "" { - candidates = append(candidates, baseURL) - } - if ossBucketBaseURL := buildOSSBucketBaseURL(cfg.OSS); ossBucketBaseURL != "" { - candidates = append(candidates, ossBucketBaseURL) - } - for _, baseURL := range candidates { - if storageKey, err := resolveStorageKeyFromAssetURL(baseURL, rawURL); err == nil && storageKey != "" { - return storageKey, nil - } - } - return "", fmt.Errorf("image url does not match any storage base url") -} - -func buildOSSBucketBaseURL(cfg config.OSSStorageConfig) string { - endpoint := strings.TrimSpace(cfg.Endpoint) - bucket := strings.TrimSpace(cfg.Bucket) - if endpoint == "" || bucket == "" { - return "" - } - if !strings.Contains(endpoint, "://") { - endpoint = "https://" + endpoint - } - u, err := url.Parse(endpoint) - if err != nil || strings.TrimSpace(u.Host) == "" { - return "" - } - scheme := strings.TrimSpace(u.Scheme) - if scheme == "" { - scheme = "https" - } - return fmt.Sprintf("%s://%s.%s", scheme, bucket, u.Host) -} - -func resolveStorageKeyFromAssetURL(baseURL, rawURL string) (string, error) { - baseURL = strings.TrimRight(strings.TrimSpace(baseURL), "/") - rawURL = strings.TrimSpace(rawURL) - if baseURL == "" || rawURL == "" { - return "", fmt.Errorf("invalid image url") - } - if strings.HasPrefix(rawURL, baseURL+"/") { - return strings.TrimLeft(strings.TrimPrefix(rawURL, baseURL), "/"), nil - } - - baseParsed, baseErr := url.Parse(baseURL) - rawParsed, rawErr := url.Parse(rawURL) - if baseErr != nil || rawErr != nil { - return "", fmt.Errorf("invalid image url") - } - if !strings.EqualFold(baseParsed.Host, rawParsed.Host) { - return "", fmt.Errorf("image url host mismatch") - } - basePath := strings.TrimRight(baseParsed.Path, "/") - rawPath := strings.TrimLeft(rawParsed.Path, "/") - if basePath == "" { - return rawPath, nil - } - basePath = strings.TrimLeft(basePath, "/") - if !strings.HasPrefix(rawPath, basePath+"/") { - return "", fmt.Errorf("image url path mismatch") - } - return strings.TrimLeft(strings.TrimPrefix(rawPath, basePath), "/"), nil + return nil, fmt.Errorf("html message image must include asset metadata") } diff --git a/internal/pkg/utils/message_test.go b/internal/pkg/utils/message_test.go index 04e7110..b87c8a4 100644 --- a/internal/pkg/utils/message_test.go +++ b/internal/pkg/utils/message_test.go @@ -100,7 +100,7 @@ func TestBuildRenderableMessageTransformsPayloadAndHTML(t *testing.T) { } } -func TestNormalizeMessageHTMLAssetsEnrichesImageDataAttrs(t *testing.T) { +func TestNormalizeMessageHTMLAssetsKeepsValidAttrsAndRemovesSrc(t *testing.T) { setupMessageTestDB(t) config.SetCurrent(&config.Config{ Storage: config.StorageConfig{ @@ -120,7 +120,7 @@ func TestNormalizeMessageHTMLAssetsEnrichesImageDataAttrs(t *testing.T) { Status: enums.AssetStatusSuccess, }) - got, err := NormalizeMessageHTMLAssets(`

demo

`) + got, err := NormalizeMessageHTMLAssets(`

demo

`) if err != nil { t.Fatalf("expected normalization success, got error: %v", err) } @@ -139,7 +139,7 @@ func TestNormalizeMessageHTMLAssetsEnrichesImageDataAttrs(t *testing.T) { } } -func TestNormalizeMessageHTMLAssetsKeepsUnknownImageSrc(t *testing.T) { +func TestNormalizeMessageHTMLAssetsRejectsMissingAssetMetadata(t *testing.T) { setupMessageTestDB(t) config.SetCurrent(&config.Config{ Storage: config.StorageConfig{ @@ -152,7 +152,7 @@ func TestNormalizeMessageHTMLAssetsKeepsUnknownImageSrc(t *testing.T) { _, err := NormalizeMessageHTMLAssets(`

demo

`) if err == nil { - t.Fatalf("expected unknown image src rejected") + t.Fatalf("expected missing image asset metadata rejected") } } diff --git a/internal/services/wxwork_kf_outbound_service.go b/internal/services/wxwork_kf_outbound_service.go index c2b816f..f084cae 100644 --- a/internal/services/wxwork_kf_outbound_service.go +++ b/internal/services/wxwork_kf_outbound_service.go @@ -454,8 +454,8 @@ func (s *wxWorkKFOutboundService) buildHTMLChunks(content string) ([]wxWorkKFOut Content: text, }) case utils.ContentChunkTypeImage: - assetID, resolveErr := s.resolveAssetIDFromImageSrc(chunk.Content) - if resolveErr != nil { + assetID := strings.TrimSpace(chunk.AssetID) + if assetID == "" { chunks = append(chunks, wxWorkKFOutboundChunk{ MessageType: enums.IMMessageTypeText, Content: "[图片]", @@ -473,11 +473,3 @@ func (s *wxWorkKFOutboundService) buildHTMLChunks(content string) ([]wxWorkKFOut } return chunks, nil } - -func (s *wxWorkKFOutboundService) resolveAssetIDFromImageSrc(src string) (string, error) { - asset := utils.FindAssetByMessageImageURL(src) - if asset == nil { - return "", fmt.Errorf("未找到图片资源") - } - return strings.TrimSpace(asset.AssetID), nil -}