@@ -15,6 +15,7 @@ import (
1515 "github.com/Tencent/WeKnora/internal/errors"
1616 apperrors "github.com/Tencent/WeKnora/internal/errors"
1717 "github.com/Tencent/WeKnora/internal/logger"
18+ "github.com/Tencent/WeKnora/internal/storageurl"
1819 "github.com/Tencent/WeKnora/internal/tracing/langfuse"
1920 "github.com/Tencent/WeKnora/internal/types"
2021 "github.com/Tencent/WeKnora/internal/types/interfaces"
@@ -35,6 +36,11 @@ type KnowledgeBaseHandler struct {
3536 // userService 仅在 list 类接口里用于批量回填 creator_name;
3637 // 真正的鉴权由 RBAC 中间件 + Lookup 完成,这里不参与决策。
3738 userService interfaces.UserService
39+ // fileService and storageResolver back the optional `resource_urls=public`
40+ // mode on hybrid-search. Both may be nil in tests, in which case only the
41+ // default handle mode is available.
42+ fileService interfaces.FileService
43+ storageResolver interfaces.StorageBackendResolver
3844}
3945
4046// NewKnowledgeBaseHandler creates a new knowledge base handler instance
@@ -46,6 +52,8 @@ func NewKnowledgeBaseHandler(
4652 asynqClient interfaces.TaskEnqueuer ,
4753 vectorStoreService interfaces.VectorStoreService ,
4854 userService interfaces.UserService ,
55+ fileService interfaces.FileService ,
56+ storageResolver interfaces.StorageBackendResolver ,
4957) * KnowledgeBaseHandler {
5058 return & KnowledgeBaseHandler {
5159 service : service ,
@@ -55,9 +63,27 @@ func NewKnowledgeBaseHandler(
5563 asynqClient : asynqClient ,
5664 vectorStoreService : vectorStoreService ,
5765 userService : userService ,
66+ fileService : fileService ,
67+ storageResolver : storageResolver ,
5868 }
5969}
6070
71+ // resolveResourceRewriter builds the storage-reference rewriter for one response
72+ // from the request's `resource_urls` parameter, falling back to the deployment
73+ // default. The returned error is already an AppError the caller can hand to
74+ // c.Error: a rejected scope is a 403, a typo in the parameter is a 400.
75+ func (h * KnowledgeBaseHandler ) resolveResourceRewriter (c * gin.Context ) (* storageurl.Rewriter , error ) {
76+ ctx := c .Request .Context ()
77+ mode , err := storageurl .ResolveMode (ctx , c .Query (storageurl .QueryParam ))
78+ if err != nil {
79+ if stderrors .Is (err , storageurl .ErrPublicModeForbidden ) {
80+ return nil , apperrors .NewForbiddenError (err .Error ())
81+ }
82+ return nil , apperrors .NewBadRequestError (err .Error ())
83+ }
84+ return storageurl .NewRequestRewriter (ctx , mode , h .fileService , h .storageResolver ), nil
85+ }
86+
6187// buildKBResponse turns a knowledge base into a JSON-ready response shape,
6288// merging the bound vector store's display metadata and any caller-supplied
6389// extras (e.g., my_permission for shared KBs). Returns the kb pointer
@@ -275,10 +301,11 @@ func (h *KnowledgeBaseHandler) resolveKBStoreView(
275301// @Tags 知识库
276302// @Accept json
277303// @Produce json
278- // @Param id path string true "知识库ID"
279- // @Param request body types.SearchParams true "搜索参数"
280- // @Success 200 {object} map[string]interface{} "搜索结果"
281- // @Failure 400 {object} errors.AppError "请求参数错误"
304+ // @Param id path string true "知识库ID"
305+ // @Param request body types.SearchParams true "搜索参数"
306+ // @Param resource_urls query string false "文件引用形式,public 返回可加载直链" Enums(handle, public) default(handle)
307+ // @Success 200 {object} map[string]interface{} "搜索结果"
308+ // @Failure 400 {object} errors.AppError "请求参数错误"
282309// @Security Bearer
283310// @Security ApiKeyAuth
284311// @Router /knowledge-bases/{id}/hybrid-search [post]
@@ -311,6 +338,14 @@ func (h *KnowledgeBaseHandler) HybridSearch(c *gin.Context) {
311338 logger .Infof (ctx , "Executing hybrid search, knowledge base ID: %s, query: %s, effectiveTenantID: %d" ,
312339 secutils .SanitizeForLog (id ), secutils .SanitizeForLog (req .QueryText ), effectiveTenantID )
313340
341+ // Resolve before retrieving so a typo or a rejected scope costs nothing.
342+ rewriter , err := h .resolveResourceRewriter (c )
343+ if err != nil {
344+ logger .Warnf (ctx , "Rejected resource URL mode: %v" , err )
345+ _ = c .Error (err )
346+ return
347+ }
348+
314349 // Execute hybrid search with default search parameters
315350 // Note: For shared KBs, the service uses effectiveTenantID internally via context
316351 results , err := h .service .HybridSearch (ctx , id , req )
@@ -333,7 +368,7 @@ func (h *KnowledgeBaseHandler) HybridSearch(c *gin.Context) {
333368 secutils .SanitizeForLog (id ), len (results ))
334369 c .JSON (http .StatusOK , gin.H {
335370 "success" : true ,
336- "data" : results ,
371+ "data" : rewriter . CopyReferences ( ctx , results ) ,
337372 })
338373}
339374
0 commit comments