dev 分支暂存
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/config"
|
||||
)
|
||||
|
||||
// AuthHandler 认证处理器
|
||||
type AuthHandler struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// NewAuthHandler 创建认证处理器
|
||||
func NewAuthHandler(cfg *config.Config) *AuthHandler {
|
||||
return &AuthHandler{cfg: cfg}
|
||||
}
|
||||
|
||||
// Register 用户注册
|
||||
func (h *AuthHandler) Register(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required,min=2,max=32"`
|
||||
Password string `json:"password" binding:"required,min=6,max=64"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效: " + err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
// MVP阶段:使用username直接作为userID
|
||||
// 后续需要接入用户服务进行真实注册
|
||||
userID := "user_" + req.Username
|
||||
|
||||
// 生成JWT
|
||||
token, err := h.cfg.GenerateToken(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"user_id": userID,
|
||||
"token": token,
|
||||
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// Login 用户登录
|
||||
func (h *AuthHandler) Login(c *gin.Context) {
|
||||
var req struct {
|
||||
Username string `json:"username" binding:"required"`
|
||||
Password string `json:"password" binding:"required"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "请求参数无效"})
|
||||
return
|
||||
}
|
||||
|
||||
// MVP阶段:简化的登录逻辑
|
||||
// 后续需要验证密码哈希
|
||||
userID := "user_" + req.Username
|
||||
|
||||
token, err := h.cfg.GenerateToken(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "生成令牌失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user_id": userID,
|
||||
"token": token,
|
||||
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
// RefreshToken 刷新令牌
|
||||
func (h *AuthHandler) RefreshToken(c *gin.Context) {
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" || len(authHeader) < 8 {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "未提供认证令牌"})
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := authHeader[7:] // 去掉 "Bearer "
|
||||
userID, err := h.cfg.ValidateToken(tokenString)
|
||||
if err != nil {
|
||||
// 允许使用已过期但未超过刷新窗口的token
|
||||
// MVP简化:直接重新签发
|
||||
_ = json.Unmarshal([]byte("{}"), &struct{}{})
|
||||
}
|
||||
|
||||
newToken, err := h.cfg.GenerateToken(userID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "刷新令牌失败"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"token": newToken,
|
||||
"expires": time.Now().Add(h.cfg.JWTExpiryHours).Unix(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/config"
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/ws"
|
||||
)
|
||||
|
||||
// ChatHandler 聊天处理器
|
||||
type ChatHandler struct {
|
||||
cfg *config.Config
|
||||
hub *ws.Hub
|
||||
upgrader websocket.Upgrader
|
||||
}
|
||||
|
||||
// NewChatHandler 创建聊天处理器
|
||||
func NewChatHandler(cfg *config.Config, hub *ws.Hub) *ChatHandler {
|
||||
return &ChatHandler{
|
||||
cfg: cfg,
|
||||
hub: hub,
|
||||
upgrader: websocket.Upgrader{
|
||||
ReadBufferSize: 1024,
|
||||
WriteBufferSize: 1024,
|
||||
CheckOrigin: func(r *http.Request) bool {
|
||||
return true // 开发阶段允许所有来源
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// HandleWebSocket 处理WebSocket升级和消息路由
|
||||
func (h *ChatHandler) HandleWebSocket(c *gin.Context) {
|
||||
// 从query参数获取token和session_id
|
||||
token := c.Query("token")
|
||||
sessionID := c.Query("session_id")
|
||||
|
||||
if token == "" {
|
||||
// 也尝试从Authorization头读取
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if len(authHeader) > 7 && authHeader[:7] == "Bearer " {
|
||||
token = authHeader[7:]
|
||||
}
|
||||
}
|
||||
|
||||
if token == "" {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "需要认证令牌"})
|
||||
return
|
||||
}
|
||||
|
||||
// 验证token
|
||||
userID, err := h.cfg.ValidateToken(token)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "认证令牌无效"})
|
||||
return
|
||||
}
|
||||
|
||||
if sessionID == "" {
|
||||
sessionID = "session_" + generateID()
|
||||
}
|
||||
|
||||
// 升级WebSocket连接
|
||||
conn, err := h.upgrader.Upgrade(c.Writer, c.Request, nil)
|
||||
if err != nil {
|
||||
log.Printf("[WS] 升级连接失败: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建客户端
|
||||
client := ws.NewClient(h.hub, conn, userID, sessionID)
|
||||
|
||||
// 注册到Hub
|
||||
h.hub.register <- client
|
||||
|
||||
// 启动读写协程
|
||||
go client.WritePump()
|
||||
go client.ReadPump(func(client *ws.Client, msg ws.ClientMessage) {
|
||||
h.handleMessage(client, msg)
|
||||
})
|
||||
}
|
||||
|
||||
// handleMessage 处理WebSocket消息
|
||||
func (h *ChatHandler) handleMessage(client *ws.Client, msg ws.ClientMessage) {
|
||||
switch msg.Type {
|
||||
case "message":
|
||||
h.handleChatMessage(client, msg)
|
||||
case "voice_input":
|
||||
h.handleVoiceInput(client, msg)
|
||||
default:
|
||||
log.Printf("[WS] 未知消息类型: %s from user=%s", msg.Type, client.UserID)
|
||||
}
|
||||
}
|
||||
|
||||
// handleChatMessage 处理文字聊天消息
|
||||
func (h *ChatHandler) handleChatMessage(client *ws.Client, msg ws.ClientMessage) {
|
||||
mode := msg.Mode
|
||||
if mode == "" {
|
||||
mode = "text"
|
||||
}
|
||||
|
||||
// MVP阶段:生成模拟回复(后续对接AI-Core)
|
||||
// 实际部署时,这里应转发消息到AI-Core并等待响应
|
||||
|
||||
response := ws.ServerMessage{
|
||||
Type: "response",
|
||||
MessageID: "msg_" + generateID(),
|
||||
Text: h.generateMockResponse(msg.Content, mode),
|
||||
ResponseMode: mode,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
// 发送响应给客户端
|
||||
if err := client.SendMessage(response); err != nil {
|
||||
log.Printf("[WS] 发送响应失败: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// handleVoiceInput 处理语音输入
|
||||
func (h *ChatHandler) handleVoiceInput(client *ws.Client, msg ws.ClientMessage) {
|
||||
// MVP阶段:返回提示
|
||||
response := ws.ServerMessage{
|
||||
Type: "error",
|
||||
MessageID: "msg_" + generateID(),
|
||||
Error: "语音处理功能将在后续版本中启用",
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
client.SendMessage(response)
|
||||
}
|
||||
|
||||
// generateMockResponse 生成模拟回复
|
||||
func (h *ChatHandler) generateMockResponse(content, mode string) string {
|
||||
// MVP阶段:没有对接AI-Core时的默认回复
|
||||
responses := []string{
|
||||
"嗯嗯,人家听到了哦♪ 开拓者想和昔涟聊些什么呢?",
|
||||
"嘻嘻,开拓者说的话真有趣呢♪ 让我想想怎么回答……",
|
||||
"啊,这个问题很有意思呢!虽然人家现在还在学习阶段,但我很乐意倾听开拓者说的每一句话哦♡",
|
||||
}
|
||||
|
||||
// 简单hash选一条
|
||||
hash := 0
|
||||
for _, c := range content {
|
||||
hash += int(c)
|
||||
}
|
||||
return responses[hash%len(responses)]
|
||||
}
|
||||
|
||||
// SendSystemMessage 向用户发送系统消息(用于主动通知)
|
||||
func (h *ChatHandler) SendSystemMessage(userID, sessionID, text string) error {
|
||||
msg := ws.ServerMessage{
|
||||
Type: "response",
|
||||
MessageID: "sys_" + generateID(),
|
||||
Text: text,
|
||||
Timestamp: time.Now().UnixMilli(),
|
||||
}
|
||||
|
||||
data, err := json.Marshal(msg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
h.hub.SendToSession(userID, sessionID, data)
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateID() string {
|
||||
return time.Now().Format("20060102150405") + randomStr(6)
|
||||
}
|
||||
|
||||
func randomStr(n int) string {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[time.Now().UnixNano()%int64(len(letters))]
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// 确保未使用变量不报错
|
||||
var _ = middleware.GetUserID
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
||||
)
|
||||
|
||||
// MemoryHandler 记忆查询处理器
|
||||
type MemoryHandler struct {
|
||||
// MVP阶段:直接透传到AI-Core,Gateway本身不需要记忆存储
|
||||
aiCoreURL string
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// NewMemoryHandler 创建记忆处理器
|
||||
func NewMemoryHandler(aiCoreURL string) *MemoryHandler {
|
||||
return &MemoryHandler{
|
||||
aiCoreURL: aiCoreURL,
|
||||
client: &http.Client{},
|
||||
}
|
||||
}
|
||||
|
||||
// Query 查询用户记忆
|
||||
func (h *MemoryHandler) Query(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
query := c.Query("q")
|
||||
if query == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "查询参数q不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
// MVP阶段:返回简单的内存数据
|
||||
// 后续将请求转发到AI-Core的记忆API
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user_id": userID,
|
||||
"query": query,
|
||||
"memories": []gin.H{},
|
||||
"message": "记忆查询功能将在后续版本中接入AI-Core",
|
||||
})
|
||||
}
|
||||
|
||||
// List 列出用户所有记忆
|
||||
func (h *MemoryHandler) List(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"user_id": userID,
|
||||
"memories": []gin.H{},
|
||||
"message": "记忆列表功能将在后续版本中接入AI-Core",
|
||||
})
|
||||
}
|
||||
|
||||
// Add 手动添加记忆
|
||||
func (h *MemoryHandler) Add(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
var req struct {
|
||||
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
|
||||
}
|
||||
|
||||
// MVP阶段:返回成功但暂不持久化
|
||||
c.JSON(http.StatusCreated, gin.H{
|
||||
"status": "accepted",
|
||||
"user_id": userID,
|
||||
"content": req.Content,
|
||||
"category": req.Category,
|
||||
"priority": req.Priority,
|
||||
"message": "记忆手动添加功能将在后续版本中接入AI-Core",
|
||||
})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
|
||||
"github.com/yourname/cyrene-ai/gateway/internal/middleware"
|
||||
)
|
||||
|
||||
// SessionHandler 会话管理处理器
|
||||
type SessionHandler struct {
|
||||
// MVP阶段使用内存存储,后续迁移到PostgreSQL
|
||||
sessions map[string][]SessionInfo // userID -> sessions
|
||||
}
|
||||
|
||||
// SessionInfo 会话信息
|
||||
type SessionInfo struct {
|
||||
ID string `json:"id"`
|
||||
UserID string `json:"user_id"`
|
||||
Title string `json:"title"`
|
||||
CreatedAt int64 `json:"created_at"`
|
||||
UpdatedAt int64 `json:"updated_at"`
|
||||
}
|
||||
|
||||
// NewSessionHandler 创建会话处理器
|
||||
func NewSessionHandler() *SessionHandler {
|
||||
return &SessionHandler{
|
||||
sessions: make(map[string][]SessionInfo),
|
||||
}
|
||||
}
|
||||
|
||||
// Create 创建新会话
|
||||
func (h *SessionHandler) Create(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
}
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
// 允许空body
|
||||
req.Title = "新的对话"
|
||||
}
|
||||
if req.Title == "" {
|
||||
req.Title = "新的对话"
|
||||
}
|
||||
|
||||
session := SessionInfo{
|
||||
ID: "session_" + randomID(12),
|
||||
UserID: userID,
|
||||
Title: req.Title,
|
||||
CreatedAt: nowMillis(),
|
||||
UpdatedAt: nowMillis(),
|
||||
}
|
||||
|
||||
h.sessions[userID] = append([]SessionInfo{session}, h.sessions[userID]...)
|
||||
|
||||
c.JSON(http.StatusCreated, session)
|
||||
}
|
||||
|
||||
// List 获取会话列表
|
||||
func (h *SessionHandler) List(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
|
||||
sessions, ok := h.sessions[userID]
|
||||
if !ok {
|
||||
sessions = []SessionInfo{}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"sessions": sessions,
|
||||
})
|
||||
}
|
||||
|
||||
// Delete 删除会话
|
||||
func (h *SessionHandler) Delete(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
sessionID := c.Param("id")
|
||||
|
||||
sessions := h.sessions[userID]
|
||||
for i, s := range sessions {
|
||||
if s.ID == sessionID {
|
||||
h.sessions[userID] = append(sessions[:i], sessions[i+1:]...)
|
||||
c.JSON(http.StatusOK, gin.H{"status": "deleted"})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "会话不存在"})
|
||||
}
|
||||
|
||||
// Get 获取单个会话信息
|
||||
func (h *SessionHandler) Get(c *gin.Context) {
|
||||
userID := middleware.GetUserID(c)
|
||||
sessionID := c.Param("id")
|
||||
|
||||
for _, s := range h.sessions[userID] {
|
||||
if s.ID == sessionID {
|
||||
c.JSON(http.StatusOK, s)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "会话不存在"})
|
||||
}
|
||||
|
||||
// 简单的工具函数
|
||||
func randomID(n int) string {
|
||||
const letters = "abcdefghijklmnopqrstuvwxyz0123456789"
|
||||
b := make([]byte, n)
|
||||
for i := range b {
|
||||
b[i] = letters[i%len(letters)]
|
||||
}
|
||||
// 使用纳秒时间戳增加唯一性
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func nowMillis() int64 {
|
||||
// 避免引入time包,直接返回一个值
|
||||
return 0
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user