refactor: 重构代码架构,解耦处理器和服务 (#13)
重大架构改进: - 移除全局变量 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 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,10 @@
|
|||||||
"Bash(rm:*)",
|
"Bash(rm:*)",
|
||||||
"Bash(gofmt:*)",
|
"Bash(gofmt:*)",
|
||||||
"Bash(goimports:*)",
|
"Bash(goimports:*)",
|
||||||
"Bash(chmod:*)"
|
"Bash(chmod:*)",
|
||||||
|
"Bash(true)",
|
||||||
|
"Bash(go vet:*)",
|
||||||
|
"Bash(golangci-lint run:*)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|||||||
66
app_server.go
Normal file
66
app_server.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
@@ -7,25 +7,6 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"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 返回错误响应
|
// respondError 返回错误响应
|
||||||
func respondError(c *gin.Context, statusCode int, code, message string, details any) {
|
func respondError(c *gin.Context, statusCode int, code, message string, details any) {
|
||||||
response := ErrorResponse{
|
response := ErrorResponse{
|
||||||
@@ -54,12 +35,9 @@ func respondSuccess(c *gin.Context, data any, message string) {
|
|||||||
c.JSON(http.StatusOK, response)
|
c.JSON(http.StatusOK, response)
|
||||||
}
|
}
|
||||||
|
|
||||||
// XiaohongshuService 全局服务实例
|
|
||||||
var xiaohongshuService = NewXiaohongshuService()
|
|
||||||
|
|
||||||
// checkLoginStatusHandler 检查登录状态
|
// checkLoginStatusHandler 检查登录状态
|
||||||
func checkLoginStatusHandler(c *gin.Context) {
|
func (s *AppServer) checkLoginStatusHandler(c *gin.Context) {
|
||||||
status, err := xiaohongshuService.CheckLoginStatus(c.Request.Context())
|
status, err := s.xiaohongshuService.CheckLoginStatus(c.Request.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(c, http.StatusInternalServerError, "STATUS_CHECK_FAILED",
|
respondError(c, http.StatusInternalServerError, "STATUS_CHECK_FAILED",
|
||||||
"检查登录状态失败", err.Error())
|
"检查登录状态失败", err.Error())
|
||||||
@@ -71,7 +49,7 @@ func checkLoginStatusHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// publishHandler 发布内容
|
// publishHandler 发布内容
|
||||||
func publishHandler(c *gin.Context) {
|
func (s *AppServer) publishHandler(c *gin.Context) {
|
||||||
var req PublishRequest
|
var req PublishRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
respondError(c, http.StatusBadRequest, "INVALID_REQUEST",
|
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 {
|
if err != nil {
|
||||||
respondError(c, http.StatusInternalServerError, "PUBLISH_FAILED",
|
respondError(c, http.StatusInternalServerError, "PUBLISH_FAILED",
|
||||||
"发布失败", err.Error())
|
"发布失败", err.Error())
|
||||||
@@ -91,9 +69,9 @@ func publishHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// listFeedsHandler 获取Feeds列表
|
// listFeedsHandler 获取Feeds列表
|
||||||
func listFeedsHandler(c *gin.Context) {
|
func (s *AppServer) listFeedsHandler(c *gin.Context) {
|
||||||
// 获取 Feeds 列表
|
// 获取 Feeds 列表
|
||||||
result, err := xiaohongshuService.ListFeeds(c.Request.Context())
|
result, err := s.xiaohongshuService.ListFeeds(c.Request.Context())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
respondError(c, http.StatusInternalServerError, "LIST_FEEDS_FAILED",
|
respondError(c, http.StatusInternalServerError, "LIST_FEEDS_FAILED",
|
||||||
"获取Feeds列表失败", err.Error())
|
"获取Feeds列表失败", err.Error())
|
||||||
@@ -113,29 +91,3 @@ func healthHandler(c *gin.Context) {
|
|||||||
"timestamp": "now",
|
"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)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
@@ -9,50 +9,13 @@ import (
|
|||||||
"github.com/sirupsen/logrus"
|
"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 工具处理函数
|
// MCP 工具处理函数
|
||||||
|
|
||||||
// handleCheckLoginStatus 处理检查登录状态
|
// handleCheckLoginStatus 处理检查登录状态
|
||||||
func handleCheckLoginStatus(ctx context.Context) *MCPToolResult {
|
func (s *AppServer) handleCheckLoginStatus(ctx context.Context) *MCPToolResult {
|
||||||
logrus.Info("MCP: 检查登录状态")
|
logrus.Info("MCP: 检查登录状态")
|
||||||
|
|
||||||
status, err := xiaohongshuService.CheckLoginStatus(ctx)
|
status, err := s.xiaohongshuService.CheckLoginStatus(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &MCPToolResult{
|
return &MCPToolResult{
|
||||||
Content: []MCPContent{{
|
Content: []MCPContent{{
|
||||||
@@ -73,7 +36,7 @@ func handleCheckLoginStatus(ctx context.Context) *MCPToolResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handlePublishContent 处理发布内容
|
// 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: 发布内容")
|
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 {
|
if err != nil {
|
||||||
return &MCPToolResult{
|
return &MCPToolResult{
|
||||||
Content: []MCPContent{{
|
Content: []MCPContent{{
|
||||||
@@ -119,10 +82,10 @@ func handlePublishContent(ctx context.Context, args map[string]interface{}) *MCP
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleListFeeds 处理获取Feeds列表
|
// handleListFeeds 处理获取Feeds列表
|
||||||
func handleListFeeds(ctx context.Context) *MCPToolResult {
|
func (s *AppServer) handleListFeeds(ctx context.Context) *MCPToolResult {
|
||||||
logrus.Info("MCP: 获取Feeds列表")
|
logrus.Info("MCP: 获取Feeds列表")
|
||||||
|
|
||||||
result, err := xiaohongshuService.ListFeeds(ctx)
|
result, err := s.xiaohongshuService.ListFeeds(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &MCPToolResult{
|
return &MCPToolResult{
|
||||||
Content: []MCPContent{{
|
Content: []MCPContent{{
|
||||||
@@ -154,11 +117,11 @@ func handleListFeeds(ctx context.Context) *MCPToolResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// handleMCPRequest 处理 MCP 请求
|
// handleMCPRequest 处理 MCP 请求
|
||||||
func handleMCPRequest(w http.ResponseWriter, r *http.Request) {
|
func (s *AppServer) handleMCPRequest(w http.ResponseWriter, r *http.Request) {
|
||||||
var req JSONRPCRequest
|
var req JSONRPCRequest
|
||||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||||
logrus.Errorf("解析请求失败: %v", err)
|
logrus.Errorf("解析请求失败: %v", err)
|
||||||
sendJSONRPCError(w, req.ID, -32700, "Parse error", nil)
|
s.sendJSONRPCError(w, req.ID, -32700, "Parse error", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,19 +129,19 @@ func handleMCPRequest(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
switch req.Method {
|
switch req.Method {
|
||||||
case "initialize":
|
case "initialize":
|
||||||
handleInitialize(w, req)
|
s.handleInitialize(w, req)
|
||||||
case "tools/list":
|
case "tools/list":
|
||||||
handleToolsList(w, req)
|
s.handleToolsList(w, req)
|
||||||
case "tools/call":
|
case "tools/call":
|
||||||
handleToolsCall(w, r, req)
|
s.handleToolsCall(w, r, req)
|
||||||
default:
|
default:
|
||||||
logrus.Warnf("不支持的方法: %s", req.Method)
|
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 处理初始化请求
|
// handleInitialize 处理初始化请求
|
||||||
func handleInitialize(w http.ResponseWriter, req JSONRPCRequest) {
|
func (s *AppServer) handleInitialize(w http.ResponseWriter, req JSONRPCRequest) {
|
||||||
result := map[string]interface{}{
|
result := map[string]interface{}{
|
||||||
"protocolVersion": "2024-11-05",
|
"protocolVersion": "2024-11-05",
|
||||||
"capabilities": map[string]interface{}{
|
"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 处理工具列表请求
|
// handleToolsList 处理工具列表请求
|
||||||
func handleToolsList(w http.ResponseWriter, req JSONRPCRequest) {
|
func (s *AppServer) handleToolsList(w http.ResponseWriter, req JSONRPCRequest) {
|
||||||
tools := []map[string]interface{}{
|
tools := []map[string]interface{}{
|
||||||
{
|
{
|
||||||
"name": "check_login_status",
|
"name": "check_login_status",
|
||||||
@@ -242,16 +205,16 @@ func handleToolsList(w http.ResponseWriter, req JSONRPCRequest) {
|
|||||||
"tools": tools,
|
"tools": tools,
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONRPCResponse(w, req.ID, result)
|
s.sendJSONRPCResponse(w, req.ID, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleToolsCall 处理工具调用请求
|
// 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
|
var toolCall MCPToolCall
|
||||||
paramsBytes, _ := json.Marshal(req.Params)
|
paramsBytes, _ := json.Marshal(req.Params)
|
||||||
if err := json.Unmarshal(paramsBytes, &toolCall); err != nil {
|
if err := json.Unmarshal(paramsBytes, &toolCall); err != nil {
|
||||||
logrus.Errorf("解析工具调用参数失败: %v", err)
|
logrus.Errorf("解析工具调用参数失败: %v", err)
|
||||||
sendJSONRPCError(w, req.ID, -32602, "Invalid params", nil)
|
s.sendJSONRPCError(w, req.ID, -32602, "Invalid params", nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,22 +223,22 @@ func handleToolsCall(w http.ResponseWriter, r *http.Request, req JSONRPCRequest)
|
|||||||
|
|
||||||
switch toolCall.Name {
|
switch toolCall.Name {
|
||||||
case "check_login_status":
|
case "check_login_status":
|
||||||
result = handleCheckLoginStatus(ctx)
|
result = s.handleCheckLoginStatus(ctx)
|
||||||
case "publish_content":
|
case "publish_content":
|
||||||
result = handlePublishContent(ctx, toolCall.Arguments)
|
result = s.handlePublishContent(ctx, toolCall.Arguments)
|
||||||
case "list_feeds":
|
case "list_feeds":
|
||||||
result = handleListFeeds(ctx)
|
result = s.handleListFeeds(ctx)
|
||||||
default:
|
default:
|
||||||
logrus.Warnf("不支持的工具: %s", toolCall.Name)
|
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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
sendJSONRPCResponse(w, req.ID, result)
|
s.sendJSONRPCResponse(w, req.ID, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// sendJSONRPCResponse 发送JSON-RPC响应
|
// 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{
|
response := JSONRPCResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
Result: result,
|
Result: result,
|
||||||
@@ -283,11 +246,13 @@ func sendJSONRPCResponse(w http.ResponseWriter, id interface{}, result interface
|
|||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
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错误响应
|
// 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{
|
response := JSONRPCResponse{
|
||||||
JSONRPC: "2.0",
|
JSONRPC: "2.0",
|
||||||
Error: &JSONRPCError{
|
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.Header().Set("Content-Type", "application/json")
|
||||||
w.WriteHeader(http.StatusOK) // JSON-RPC错误仍然返回200状态码
|
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处理器
|
// createMCPHandler 创建MCP HTTP处理器
|
||||||
func createMCPHandler() http.HandlerFunc {
|
func (s *AppServer) createMCPHandler() http.HandlerFunc {
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
// 设置 CORS 头
|
// 设置 CORS 头
|
||||||
w.Header().Set("Access-Control-Allow-Origin", "*")
|
w.Header().Set("Access-Control-Allow-Origin", "*")
|
||||||
@@ -323,6 +290,6 @@ func createMCPHandler() http.HandlerFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 处理 MCP JSON-RPC 请求
|
// 处理 MCP JSON-RPC 请求
|
||||||
handleMCPRequest(w, r)
|
s.handleMCPRequest(w, r)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
main.go
8
main.go
@@ -8,7 +8,6 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var (
|
var (
|
||||||
headless bool
|
headless bool
|
||||||
)
|
)
|
||||||
@@ -17,7 +16,12 @@ func main() {
|
|||||||
|
|
||||||
configs.InitHeadless(headless)
|
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)
|
logrus.Fatalf("failed to run server: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
34
middleware.go
Normal file
34
middleware.go
Normal file
@@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
37
routes.go
Normal file
37
routes.go
Normal file
@@ -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
|
||||||
|
}
|
||||||
89
server.go
89
server.go
@@ -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
|
|
||||||
}
|
|
||||||
62
types.go
Normal file
62
types.go
Normal file
@@ -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"`
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user