From a7e975cf7c630b8e48d50c8025d7cafd5a87eac9 Mon Sep 17 00:00:00 2001 From: zy Date: Sun, 17 Aug 2025 16:15:54 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E9=87=8D=E6=9E=84=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E6=9E=B6=E6=9E=84=EF=BC=8C=E8=A7=A3=E8=80=A6=E5=A4=84?= =?UTF-8?q?=E7=90=86=E5=99=A8=E5=92=8C=E6=9C=8D=E5=8A=A1=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重大架构改进: - 移除全局变量 xiaohongshuService,改用依赖注入 - 将代码按职责分离到不同文件: * app_server.go - 核心服务器结构 * types.go - 统一类型定义 * routes.go - 路由配置 * middleware.go - 中间件 * handlers_api.go - REST API 处理器 * handlers_mcp.go - MCP 协议处理器 - 将通用工具函数从 AppServer 方法改为独立函数 - 删除未使用的类型定义(LoginWaitRequest) - 修复 JSON 编码错误处理 优势: ✅ 更好的依赖注入模式 ✅ 清晰的职责分离 ✅ 提高代码可测试性 ✅ 符合 Go 最佳实践 ✅ 降低耦合度,提高内聚性 🤖 Generated with [Claude Code](https://claude.ai/code) Co-authored-by: Claude --- .claude/settings.local.json | 5 +- app_server.go | 66 +++++++++++++++++++++ handlers.go => handlers_api.go | 60 ++----------------- mcp_server.go => handlers_mcp.go | 99 +++++++++++--------------------- main.go | 8 ++- middleware.go | 34 +++++++++++ routes.go | 37 ++++++++++++ server.go | 89 ---------------------------- types.go | 62 ++++++++++++++++++++ 9 files changed, 248 insertions(+), 212 deletions(-) create mode 100644 app_server.go rename handlers.go => handlers_api.go (54%) rename mcp_server.go => handlers_mcp.go (70%) create mode 100644 middleware.go create mode 100644 routes.go delete mode 100644 server.go create mode 100644 types.go diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 2d4c349..d98b508 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -12,7 +12,10 @@ "Bash(rm:*)", "Bash(gofmt:*)", "Bash(goimports:*)", - "Bash(chmod:*)" + "Bash(chmod:*)", + "Bash(true)", + "Bash(go vet:*)", + "Bash(golangci-lint run:*)" ], "deny": [] } diff --git a/app_server.go b/app_server.go new file mode 100644 index 0000000..83d2e72 --- /dev/null +++ b/app_server.go @@ -0,0 +1,66 @@ +package main + +import ( + "context" + "net/http" + "os" + "os/signal" + "syscall" + "time" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// AppServer 应用服务器结构体,封装所有服务和处理器 +type AppServer struct { + xiaohongshuService *XiaohongshuService + router *gin.Engine + httpServer *http.Server +} + +// NewAppServer 创建新的应用服务器实例 +func NewAppServer(xiaohongshuService *XiaohongshuService) *AppServer { + return &AppServer{ + xiaohongshuService: xiaohongshuService, + } +} + +// Start 启动服务器 +func (s *AppServer) Start(port string) error { + s.router = setupRoutes(s) + + s.httpServer = &http.Server{ + Addr: port, + Handler: s.router, + } + + // 启动服务器的 goroutine + go func() { + logrus.Infof("启动 HTTP 服务器: %s", port) + if err := s.httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logrus.Errorf("服务器启动失败: %v", err) + os.Exit(1) + } + }() + + // 等待中断信号 + quit := make(chan os.Signal, 1) + signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) + <-quit + + logrus.Infof("正在关闭服务器...") + + // 优雅关闭 + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + // 关闭 HTTP 服务器 + if err := s.httpServer.Shutdown(ctx); err != nil { + logrus.Errorf("服务器关闭失败: %v", err) + return err + } + + logrus.Infof("服务器已关闭") + return nil +} diff --git a/handlers.go b/handlers_api.go similarity index 54% rename from handlers.go rename to handlers_api.go index 947865a..5431c1f 100644 --- a/handlers.go +++ b/handlers_api.go @@ -7,25 +7,6 @@ import ( "github.com/sirupsen/logrus" ) -// LoginWaitRequest 等待登录请求 -type LoginWaitRequest struct { - Timeout int `json:"timeout"` -} - -// ErrorResponse 错误响应 -type ErrorResponse struct { - Error string `json:"error"` - Code string `json:"code"` - Details any `json:"details,omitempty"` -} - -// SuccessResponse 成功响应 -type SuccessResponse struct { - Success bool `json:"success"` - Data any `json:"data"` - Message string `json:"message,omitempty"` -} - // respondError 返回错误响应 func respondError(c *gin.Context, statusCode int, code, message string, details any) { response := ErrorResponse{ @@ -54,12 +35,9 @@ func respondSuccess(c *gin.Context, data any, message string) { c.JSON(http.StatusOK, response) } -// XiaohongshuService 全局服务实例 -var xiaohongshuService = NewXiaohongshuService() - // checkLoginStatusHandler 检查登录状态 -func checkLoginStatusHandler(c *gin.Context) { - status, err := xiaohongshuService.CheckLoginStatus(c.Request.Context()) +func (s *AppServer) checkLoginStatusHandler(c *gin.Context) { + status, err := s.xiaohongshuService.CheckLoginStatus(c.Request.Context()) if err != nil { respondError(c, http.StatusInternalServerError, "STATUS_CHECK_FAILED", "检查登录状态失败", err.Error()) @@ -71,7 +49,7 @@ func checkLoginStatusHandler(c *gin.Context) { } // publishHandler 发布内容 -func publishHandler(c *gin.Context) { +func (s *AppServer) publishHandler(c *gin.Context) { var req PublishRequest if err := c.ShouldBindJSON(&req); err != nil { respondError(c, http.StatusBadRequest, "INVALID_REQUEST", @@ -80,7 +58,7 @@ func publishHandler(c *gin.Context) { } // 执行发布 - result, err := xiaohongshuService.PublishContent(c.Request.Context(), &req) + result, err := s.xiaohongshuService.PublishContent(c.Request.Context(), &req) if err != nil { respondError(c, http.StatusInternalServerError, "PUBLISH_FAILED", "发布失败", err.Error()) @@ -91,9 +69,9 @@ func publishHandler(c *gin.Context) { } // listFeedsHandler 获取Feeds列表 -func listFeedsHandler(c *gin.Context) { +func (s *AppServer) listFeedsHandler(c *gin.Context) { // 获取 Feeds 列表 - result, err := xiaohongshuService.ListFeeds(c.Request.Context()) + result, err := s.xiaohongshuService.ListFeeds(c.Request.Context()) if err != nil { respondError(c, http.StatusInternalServerError, "LIST_FEEDS_FAILED", "获取Feeds列表失败", err.Error()) @@ -113,29 +91,3 @@ func healthHandler(c *gin.Context) { "timestamp": "now", }, "服务正常") } - -// corsMiddleware CORS 中间件 -func corsMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") - - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(http.StatusNoContent) - return - } - - c.Next() - } -} - -// errorHandlingMiddleware 错误处理中间件 -func errorHandlingMiddleware() gin.HandlerFunc { - return gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { - logrus.Errorf("服务器内部错误: %v, path: %s", recovered, c.Request.URL.Path) - - respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", - "服务器内部错误", recovered) - }) -} diff --git a/mcp_server.go b/handlers_mcp.go similarity index 70% rename from mcp_server.go rename to handlers_mcp.go index 43ac34f..a9cd8f3 100644 --- a/mcp_server.go +++ b/handlers_mcp.go @@ -9,50 +9,13 @@ import ( "github.com/sirupsen/logrus" ) -// JSON-RPC 结构定义 - -type JSONRPCRequest struct { - JSONRPC string `json:"jsonrpc"` - Method string `json:"method"` - Params interface{} `json:"params,omitempty"` - ID interface{} `json:"id"` -} - -type JSONRPCResponse struct { - JSONRPC string `json:"jsonrpc"` - Result interface{} `json:"result,omitempty"` - Error *JSONRPCError `json:"error,omitempty"` - ID interface{} `json:"id"` -} - -type JSONRPCError struct { - Code int `json:"code"` - Message string `json:"message"` - Data interface{} `json:"data,omitempty"` -} - -type MCPToolCall struct { - Name string `json:"name"` - Arguments map[string]interface{} `json:"arguments"` -} - -type MCPToolResult struct { - Content []MCPContent `json:"content"` - IsError bool `json:"isError,omitempty"` -} - -type MCPContent struct { - Type string `json:"type"` - Text string `json:"text"` -} - // MCP 工具处理函数 // handleCheckLoginStatus 处理检查登录状态 -func handleCheckLoginStatus(ctx context.Context) *MCPToolResult { +func (s *AppServer) handleCheckLoginStatus(ctx context.Context) *MCPToolResult { logrus.Info("MCP: 检查登录状态") - status, err := xiaohongshuService.CheckLoginStatus(ctx) + status, err := s.xiaohongshuService.CheckLoginStatus(ctx) if err != nil { return &MCPToolResult{ Content: []MCPContent{{ @@ -73,7 +36,7 @@ func handleCheckLoginStatus(ctx context.Context) *MCPToolResult { } // handlePublishContent 处理发布内容 -func handlePublishContent(ctx context.Context, args map[string]interface{}) *MCPToolResult { +func (s *AppServer) handlePublishContent(ctx context.Context, args map[string]interface{}) *MCPToolResult { logrus.Info("MCP: 发布内容") // 解析参数 @@ -98,7 +61,7 @@ func handlePublishContent(ctx context.Context, args map[string]interface{}) *MCP } // 执行发布 - result, err := xiaohongshuService.PublishContent(ctx, req) + result, err := s.xiaohongshuService.PublishContent(ctx, req) if err != nil { return &MCPToolResult{ Content: []MCPContent{{ @@ -119,10 +82,10 @@ func handlePublishContent(ctx context.Context, args map[string]interface{}) *MCP } // handleListFeeds 处理获取Feeds列表 -func handleListFeeds(ctx context.Context) *MCPToolResult { +func (s *AppServer) handleListFeeds(ctx context.Context) *MCPToolResult { logrus.Info("MCP: 获取Feeds列表") - result, err := xiaohongshuService.ListFeeds(ctx) + result, err := s.xiaohongshuService.ListFeeds(ctx) if err != nil { return &MCPToolResult{ Content: []MCPContent{{ @@ -154,11 +117,11 @@ func handleListFeeds(ctx context.Context) *MCPToolResult { } // handleMCPRequest 处理 MCP 请求 -func handleMCPRequest(w http.ResponseWriter, r *http.Request) { +func (s *AppServer) handleMCPRequest(w http.ResponseWriter, r *http.Request) { var req JSONRPCRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { logrus.Errorf("解析请求失败: %v", err) - sendJSONRPCError(w, req.ID, -32700, "Parse error", nil) + s.sendJSONRPCError(w, req.ID, -32700, "Parse error", nil) return } @@ -166,19 +129,19 @@ func handleMCPRequest(w http.ResponseWriter, r *http.Request) { switch req.Method { case "initialize": - handleInitialize(w, req) + s.handleInitialize(w, req) case "tools/list": - handleToolsList(w, req) + s.handleToolsList(w, req) case "tools/call": - handleToolsCall(w, r, req) + s.handleToolsCall(w, r, req) default: logrus.Warnf("不支持的方法: %s", req.Method) - sendJSONRPCError(w, req.ID, -32601, "Method not found", nil) + s.sendJSONRPCError(w, req.ID, -32601, "Method not found", nil) } } // handleInitialize 处理初始化请求 -func handleInitialize(w http.ResponseWriter, req JSONRPCRequest) { +func (s *AppServer) handleInitialize(w http.ResponseWriter, req JSONRPCRequest) { result := map[string]interface{}{ "protocolVersion": "2024-11-05", "capabilities": map[string]interface{}{ @@ -190,11 +153,11 @@ func handleInitialize(w http.ResponseWriter, req JSONRPCRequest) { }, } - sendJSONRPCResponse(w, req.ID, result) + s.sendJSONRPCResponse(w, req.ID, result) } // handleToolsList 处理工具列表请求 -func handleToolsList(w http.ResponseWriter, req JSONRPCRequest) { +func (s *AppServer) handleToolsList(w http.ResponseWriter, req JSONRPCRequest) { tools := []map[string]interface{}{ { "name": "check_login_status", @@ -242,16 +205,16 @@ func handleToolsList(w http.ResponseWriter, req JSONRPCRequest) { "tools": tools, } - sendJSONRPCResponse(w, req.ID, result) + s.sendJSONRPCResponse(w, req.ID, result) } // handleToolsCall 处理工具调用请求 -func handleToolsCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest) { +func (s *AppServer) handleToolsCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest) { var toolCall MCPToolCall paramsBytes, _ := json.Marshal(req.Params) if err := json.Unmarshal(paramsBytes, &toolCall); err != nil { logrus.Errorf("解析工具调用参数失败: %v", err) - sendJSONRPCError(w, req.ID, -32602, "Invalid params", nil) + s.sendJSONRPCError(w, req.ID, -32602, "Invalid params", nil) return } @@ -260,22 +223,22 @@ func handleToolsCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest) switch toolCall.Name { case "check_login_status": - result = handleCheckLoginStatus(ctx) + result = s.handleCheckLoginStatus(ctx) case "publish_content": - result = handlePublishContent(ctx, toolCall.Arguments) + result = s.handlePublishContent(ctx, toolCall.Arguments) case "list_feeds": - result = handleListFeeds(ctx) + result = s.handleListFeeds(ctx) default: logrus.Warnf("不支持的工具: %s", toolCall.Name) - sendJSONRPCError(w, req.ID, -32601, "Tool not found", nil) + s.sendJSONRPCError(w, req.ID, -32601, "Tool not found", nil) return } - sendJSONRPCResponse(w, req.ID, result) + s.sendJSONRPCResponse(w, req.ID, result) } // sendJSONRPCResponse 发送JSON-RPC响应 -func sendJSONRPCResponse(w http.ResponseWriter, id interface{}, result interface{}) { +func (s *AppServer) sendJSONRPCResponse(w http.ResponseWriter, id interface{}, result interface{}) { response := JSONRPCResponse{ JSONRPC: "2.0", Result: result, @@ -283,11 +246,13 @@ func sendJSONRPCResponse(w http.ResponseWriter, id interface{}, result interface } w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(response) + if err := json.NewEncoder(w).Encode(response); err != nil { + logrus.Errorf("Failed to encode JSON-RPC response: %v", err) + } } // sendJSONRPCError 发送JSON-RPC错误响应 -func sendJSONRPCError(w http.ResponseWriter, id interface{}, code int, message string, data interface{}) { +func (s *AppServer) sendJSONRPCError(w http.ResponseWriter, id interface{}, code int, message string, data interface{}) { response := JSONRPCResponse{ JSONRPC: "2.0", Error: &JSONRPCError{ @@ -300,11 +265,13 @@ func sendJSONRPCError(w http.ResponseWriter, id interface{}, code int, message s w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) // JSON-RPC错误仍然返回200状态码 - json.NewEncoder(w).Encode(response) + if err := json.NewEncoder(w).Encode(response); err != nil { + logrus.Errorf("Failed to encode JSON-RPC error response: %v", err) + } } // createMCPHandler 创建MCP HTTP处理器 -func createMCPHandler() http.HandlerFunc { +func (s *AppServer) createMCPHandler() http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { // 设置 CORS 头 w.Header().Set("Access-Control-Allow-Origin", "*") @@ -323,6 +290,6 @@ func createMCPHandler() http.HandlerFunc { } // 处理 MCP JSON-RPC 请求 - handleMCPRequest(w, r) + s.handleMCPRequest(w, r) } } diff --git a/main.go b/main.go index 2b2d6a0..914c636 100644 --- a/main.go +++ b/main.go @@ -8,7 +8,6 @@ import ( ) func main() { - var ( headless bool ) @@ -17,7 +16,12 @@ func main() { configs.InitHeadless(headless) - if err := startServer(); err != nil { + // 初始化服务 + xiaohongshuService := NewXiaohongshuService() + + // 创建并启动应用服务器 + appServer := NewAppServer(xiaohongshuService) + if err := appServer.Start(":18060"); err != nil { logrus.Fatalf("failed to run server: %v", err) } } diff --git a/middleware.go b/middleware.go new file mode 100644 index 0000000..b38164a --- /dev/null +++ b/middleware.go @@ -0,0 +1,34 @@ +package main + +import ( + "net/http" + + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +// corsMiddleware CORS 中间件 +func corsMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + c.Header("Access-Control-Allow-Origin", "*") + c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") + c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") + + if c.Request.Method == "OPTIONS" { + c.AbortWithStatus(http.StatusNoContent) + return + } + + c.Next() + } +} + +// errorHandlingMiddleware 错误处理中间件 +func errorHandlingMiddleware() gin.HandlerFunc { + return gin.CustomRecovery(func(c *gin.Context, recovered any) { + logrus.Errorf("服务器内部错误: %v, path: %s", recovered, c.Request.URL.Path) + + respondError(c, http.StatusInternalServerError, "INTERNAL_ERROR", + "服务器内部错误", recovered) + }) +} diff --git a/routes.go b/routes.go new file mode 100644 index 0000000..0401d0a --- /dev/null +++ b/routes.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/gin-gonic/gin" +) + +// setupRoutes 设置路由配置 +func setupRoutes(appServer *AppServer) *gin.Engine { + // 设置 Gin 模式 + gin.SetMode(gin.ReleaseMode) + + router := gin.New() + router.Use(gin.Logger()) + router.Use(gin.Recovery()) + + // 添加中间件 + router.Use(errorHandlingMiddleware()) + router.Use(corsMiddleware()) + + // 健康检查 + router.GET("/health", healthHandler) + + // MCP 端点 - 使用 SSE 协议 + mcpHandler := appServer.createMCPHandler() + router.Any("/mcp", gin.WrapH(mcpHandler)) + router.Any("/mcp/*path", gin.WrapH(mcpHandler)) + + // API 路由组 + api := router.Group("/api/v1") + { + api.GET("/login/status", appServer.checkLoginStatusHandler) + api.POST("/publish", appServer.publishHandler) + api.GET("/feeds/list", appServer.listFeedsHandler) + } + + return router +} diff --git a/server.go b/server.go deleted file mode 100644 index f1e98e2..0000000 --- a/server.go +++ /dev/null @@ -1,89 +0,0 @@ -package main - -import ( - "context" - "net/http" - "os" - "os/signal" - "syscall" - "time" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -// setupRouter 设置路由 -func setupRouter() *gin.Engine { - // 设置 Gin 模式 - gin.SetMode(gin.ReleaseMode) - - router := gin.New() - - router.Use(gin.Logger()) - router.Use(gin.Recovery()) - - // 添加中间件 - router.Use(errorHandlingMiddleware()) - router.Use(corsMiddleware()) - - // 健康检查 - router.GET("/health", healthHandler) - - // MCP 端点 - 使用 SSE 协议 - mcpHandler := createMCPHandler() - router.Any("/mcp", gin.WrapH(mcpHandler)) - router.Any("/mcp/*path", gin.WrapH(mcpHandler)) - - // API 路由组 - api := router.Group("/api/v1") - { - api.GET("/login/status", checkLoginStatusHandler) - - api.POST("/publish", publishHandler) - - // Feeds 相关路由 - api.GET("/feeds/list", listFeedsHandler) - } - - return router -} - -// startServer 启动服务器 -func startServer() error { - router := setupRouter() - - port := ":18060" - server := &http.Server{ - Addr: port, - Handler: router, - } - - // 启动服务器的 goroutine - go func() { - logrus.Infof("启动 HTTP 服务器: %s", port) - if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logrus.Errorf("服务器启动失败: %v", err) - os.Exit(1) - } - }() - - // 等待中断信号 - quit := make(chan os.Signal, 1) - signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) - <-quit - - logrus.Infof("正在关闭服务器...") - - // 优雅关闭 - ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) - defer cancel() - - // 关闭 HTTP 服务器 - if err := server.Shutdown(ctx); err != nil { - logrus.Errorf("服务器关闭失败: %v", err) - return err - } - - logrus.Infof("服务器已关闭") - return nil -} diff --git a/types.go b/types.go new file mode 100644 index 0000000..6f3b3ec --- /dev/null +++ b/types.go @@ -0,0 +1,62 @@ +package main + +// HTTP API 响应类型 + +// ErrorResponse 错误响应 +type ErrorResponse struct { + Error string `json:"error"` + Code string `json:"code"` + Details any `json:"details,omitempty"` +} + +// SuccessResponse 成功响应 +type SuccessResponse struct { + Success bool `json:"success"` + Data any `json:"data"` + Message string `json:"message,omitempty"` +} + +// JSON-RPC 相关类型 + +// JSONRPCRequest JSON-RPC 请求 +type JSONRPCRequest struct { + JSONRPC string `json:"jsonrpc"` + Method string `json:"method"` + Params any `json:"params,omitempty"` + ID any `json:"id"` +} + +// JSONRPCResponse JSON-RPC 响应 +type JSONRPCResponse struct { + JSONRPC string `json:"jsonrpc"` + Result any `json:"result,omitempty"` + Error *JSONRPCError `json:"error,omitempty"` + ID any `json:"id"` +} + +// JSONRPCError JSON-RPC 错误 +type JSONRPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data any `json:"data,omitempty"` +} + +// MCP 相关类型 + +// MCPToolCall MCP 工具调用 +type MCPToolCall struct { + Name string `json:"name"` + Arguments map[string]interface{} `json:"arguments"` +} + +// MCPToolResult MCP 工具结果 +type MCPToolResult struct { + Content []MCPContent `json:"content"` + IsError bool `json:"isError,omitempty"` +} + +// MCPContent MCP 内容 +type MCPContent struct { + Type string `json:"type"` + Text string `json:"text"` +}