218 lines
6.2 KiB
Go
218 lines
6.2 KiB
Go
package handler
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"html"
|
|
"io"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
|
)
|
|
|
|
// MemoryHandler 记忆查询处理器 — 代理到 Memory-Service
|
|
type MemoryHandler struct {
|
|
memoryServiceURL string
|
|
client *http.Client
|
|
}
|
|
|
|
// NewMemoryHandler 创建记忆处理器
|
|
func NewMemoryHandler(memoryServiceURL string) *MemoryHandler {
|
|
return &MemoryHandler{
|
|
memoryServiceURL: memoryServiceURL,
|
|
client: &http.Client{
|
|
Timeout: 15 * time.Second,
|
|
},
|
|
}
|
|
}
|
|
|
|
// Query 搜索用户记忆 — 代理 POST /api/v1/memories/query
|
|
// 管理员可通过 user_id 查询参数查询任意用户的记忆
|
|
func (h *MemoryHandler) Query(c *gin.Context) {
|
|
authUserID := middleware.GetUserID(c)
|
|
userID := c.Query("user_id")
|
|
|
|
// 非管理员只能查询自己的记忆;管理员可通过查询参数指定目标用户
|
|
if !strings.HasPrefix(authUserID, "admin_") || userID == "" {
|
|
userID = authUserID
|
|
}
|
|
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "查询参数q不能为空"})
|
|
return
|
|
}
|
|
|
|
// 使用 memory-service 的 POST /api/v1/memories/query 端点
|
|
reqBody, _ := json.Marshal(map[string]interface{}{
|
|
"user_id": userID,
|
|
"query_text": query,
|
|
"limit": 10,
|
|
})
|
|
|
|
url := fmt.Sprintf("%s/api/v1/memories/query", h.memoryServiceURL)
|
|
httpReq, err := http.NewRequest("POST", url, bytes.NewReader(reqBody))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "构建请求失败"})
|
|
return
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := h.client.Do(httpReq)
|
|
if err != nil {
|
|
log.Printf("[memory] Memory-Service 不可达 (Query): %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{
|
|
"error": fmt.Sprintf("Memory-Service 不可达: %v", err),
|
|
"errorType": "memory_service_unreachable",
|
|
"hint": "Memory-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Memory-Service",
|
|
})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
var result interface{}
|
|
json.Unmarshal(body, &result)
|
|
c.JSON(resp.StatusCode, result)
|
|
}
|
|
|
|
// List 列出用户所有记忆 — 代理 GET /api/v1/memories?user_id=...
|
|
// 管理员可通过 user_id 查询参数查询任意用户的记忆
|
|
func (h *MemoryHandler) List(c *gin.Context) {
|
|
authUserID := middleware.GetUserID(c)
|
|
userID := c.Query("user_id")
|
|
|
|
// 非管理员只能查询自己的记忆;管理员可通过查询参数指定目标用户
|
|
if !strings.HasPrefix(authUserID, "admin_") || userID == "" {
|
|
userID = authUserID
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/api/v1/memories?user_id=%s", h.memoryServiceURL, userID)
|
|
|
|
resp, err := h.client.Get(url)
|
|
if err != nil {
|
|
log.Printf("[memory] Memory-Service 不可达 (List): %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{
|
|
"error": fmt.Sprintf("Memory-Service 不可达: %v", err),
|
|
"errorType": "memory_service_unreachable",
|
|
"hint": "Memory-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Memory-Service",
|
|
})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
var result interface{}
|
|
json.Unmarshal(body, &result)
|
|
c.JSON(resp.StatusCode, result)
|
|
}
|
|
|
|
// Add 手动添加记忆 — 代理 POST /api/v1/memories
|
|
// 管理员可通过请求体中的 user_id 字段为任意用户添加记忆
|
|
func (h *MemoryHandler) Add(c *gin.Context) {
|
|
authUserID := middleware.GetUserID(c)
|
|
|
|
var req struct {
|
|
UserID string `json:"user_id"`
|
|
Content string `json:"content" binding:"required"`
|
|
Category string `json:"category"`
|
|
Priority int `json:"priority"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
|
return
|
|
}
|
|
|
|
if req.Category == "" {
|
|
req.Category = "other"
|
|
}
|
|
if req.Priority <= 0 {
|
|
req.Priority = 1
|
|
}
|
|
|
|
// 管理员可通过请求体指定目标用户,否则使用认证用户
|
|
userID := authUserID
|
|
if strings.HasPrefix(authUserID, "admin_") && req.UserID != "" {
|
|
userID = req.UserID
|
|
}
|
|
|
|
// 转发到 Memory-Service(对用户输入进行 HTML 转义防 XSS)
|
|
memReq := map[string]interface{}{
|
|
"user_id": userID,
|
|
"content": html.EscapeString(req.Content),
|
|
"category": html.EscapeString(req.Category),
|
|
"priority": req.Priority,
|
|
}
|
|
reqBody, _ := json.Marshal(memReq)
|
|
|
|
url := fmt.Sprintf("%s/api/v1/memories", h.memoryServiceURL)
|
|
httpReq, err := http.NewRequest("POST", url, bytes.NewReader(reqBody))
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "构建请求失败"})
|
|
return
|
|
}
|
|
httpReq.Header.Set("Content-Type", "application/json")
|
|
|
|
resp, err := h.client.Do(httpReq)
|
|
if err != nil {
|
|
log.Printf("[memory] Memory-Service 不可达 (Add): %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{
|
|
"error": fmt.Sprintf("Memory-Service 不可达: %v", err),
|
|
"errorType": "memory_service_unreachable",
|
|
"hint": "Memory-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Memory-Service",
|
|
})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
var result interface{}
|
|
json.Unmarshal(body, &result)
|
|
c.JSON(resp.StatusCode, result)
|
|
}
|
|
|
|
// Delete 删除单条记忆 — 代理 DELETE /api/v1/memories/:id
|
|
func (h *MemoryHandler) Delete(c *gin.Context) {
|
|
memoryID := c.Query("id")
|
|
if memoryID == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "缺少 id 参数"})
|
|
return
|
|
}
|
|
|
|
url := fmt.Sprintf("%s/api/v1/memories/%s", h.memoryServiceURL, memoryID)
|
|
|
|
req, err := http.NewRequest("DELETE", url, nil)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "构建请求失败"})
|
|
return
|
|
}
|
|
|
|
resp, err := h.client.Do(req)
|
|
if err != nil {
|
|
log.Printf("[memory] Memory-Service 不可达 (Delete): %v", err)
|
|
c.JSON(http.StatusBadGateway, gin.H{
|
|
"error": fmt.Sprintf("Memory-Service 不可达: %v", err),
|
|
"errorType": "memory_service_unreachable",
|
|
"hint": "Memory-Service 服务未启动或不可达,请先在「服务管理」面板中启动 Memory-Service",
|
|
})
|
|
return
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
body, _ := io.ReadAll(resp.Body)
|
|
|
|
var result interface{}
|
|
json.Unmarshal(body, &result)
|
|
c.JSON(resp.StatusCode, result)
|
|
}
|