Files
ai-agent/internal/bootstrap/server.go
T
mlogclub 79614b2d07 Refactor import paths to use internal/pkg/httpx/params
- Updated multiple repository and service files to replace imports from "github.com/mlogclub/simple/web/params" with "cs-agent/internal/pkg/httpx/params".
- Adjusted context handling in auth_service and ws_service to use gin.Context instead of iris.Context.
- Ensured consistent usage of HTTP status responses across websocket handlers.
2026-05-23 22:10:20 +08:00

164 lines
6.6 KiB
Go

package bootstrap
import (
"log/slog"
"net/http"
"os"
"path/filepath"
"strings"
"time"
"cs-agent/internal/ai/mcps"
_ "cs-agent/internal/ai/runtime"
"cs-agent/internal/controllers/api"
"cs-agent/internal/controllers/dashboard"
"cs-agent/internal/controllers/third"
"cs-agent/internal/middleware"
"cs-agent/internal/pkg/config"
"cs-agent/internal/pkg/ginx"
"cs-agent/internal/services"
"github.com/gin-gonic/gin"
_ "cs-agent/internal/services/wx_callback_handlers"
)
func NewServer() (*gin.Engine, error) {
cfg := config.Current()
app := gin.New()
app.Use(corsMiddleware())
app.Use(gin.Recovery())
app.Use(requestLogMiddleware())
app.Use(maxBodySizeMiddleware(cfg.Storage.MaxRequestBodySizeBytes()))
addRouter(app)
app.StaticFS(cfg.Storage.Local.BaseURL, http.Dir(cfg.Storage.Local.Root))
registerDashboardStatic(app, "web/out")
return app, nil
}
func corsMiddleware() gin.HandlerFunc {
allowHeaders := "Origin, Content-Type, Accept, Authorization, X-Requested-With, X-Guest-Id, X-Channel-Id, X-External-Id, X-External-Name, X-Customer-Session-Token, X-Customer-Session-Expires-At"
exposeHeaders := "Content-Length, Content-Type, Authorization, X-Guest-Id, X-Channel-Id, X-External-Id, X-External-Name, X-Customer-Session-Token, X-Customer-Session-Expires-At"
return func(ctx *gin.Context) {
if isWebsocketUpgrade(ctx) {
ctx.Next()
return
}
ctx.Header("Access-Control-Allow-Origin", "*")
ctx.Header("Access-Control-Allow-Headers", allowHeaders)
ctx.Header("Access-Control-Expose-Headers", exposeHeaders)
ctx.Header("Access-Control-Max-Age", "600")
if ctx.Request.Method == http.MethodOptions {
ctx.AbortWithStatus(http.StatusNoContent)
return
}
ctx.Next()
}
}
func requestLogMiddleware() gin.HandlerFunc {
return func(ctx *gin.Context) {
start := time.Now()
path := ctx.Request.URL.Path
method := ctx.Request.Method
ctx.Next()
slog.Info("http request",
"method", method,
"path", path,
"status", ctx.Writer.Status(),
"elapsed", time.Since(start).Milliseconds(),
"clientIp", ctx.ClientIP(),
)
}
}
func maxBodySizeMiddleware(limit int64) gin.HandlerFunc {
return func(ctx *gin.Context) {
ctx.Request.Body = http.MaxBytesReader(ctx.Writer, ctx.Request.Body, limit)
ctx.Next()
}
}
func isWebsocketUpgrade(ctx *gin.Context) bool {
if !strings.EqualFold(ctx.GetHeader("Upgrade"), "websocket") {
return false
}
return strings.Contains(strings.ToLower(ctx.GetHeader("Connection")), "upgrade")
}
func addRouter(app *gin.Engine) {
app.Any("/api/mcp", gin.WrapH(mcps.NewHTTPHandler()))
apiGroup := app.Group("/api")
ginx.HandleController(apiGroup, "/auth", new(api.AuthController))
ginx.HandleController(apiGroup, "/channel", new(api.ChannelController))
ginx.HandleController(apiGroup, "/customer", new(api.CustomerController))
ginx.HandleController(apiGroup, "/conversation", new(api.ConversationController), middleware.ExternalUserMiddleware)
ginx.HandleController(apiGroup, "/message", new(api.MessageController), middleware.ExternalUserMiddleware)
wsGroup := app.Group("/api/ws")
wsGroup.GET("/dashboard", middleware.AuthMiddleware, services.WsService.HandleDashboardWS)
wsGroup.GET("/dashboard/notification", middleware.AuthMiddleware, services.WsService.HandleDashboardNotificationWS)
wsGroup.GET("/open", services.WsService.HandleOpenWS)
dashboardGroup := app.Group("/api/dashboard", middleware.AuthMiddleware)
ginx.HandleController(dashboardGroup, "/dashboard", new(dashboard.DashboardController))
ginx.HandleController(dashboardGroup, "/user", new(dashboard.UserController))
ginx.HandleController(dashboardGroup, "/company", new(dashboard.CompanyController))
ginx.HandleController(dashboardGroup, "/customer", new(dashboard.CustomerController))
ginx.HandleController(dashboardGroup, "/customer-contact", new(dashboard.CustomerContactController))
ginx.HandleController(dashboardGroup, "/role", new(dashboard.RoleController))
ginx.HandleController(dashboardGroup, "/permission", new(dashboard.PermissionController))
ginx.HandleController(dashboardGroup, "/session", new(dashboard.SessionController))
ginx.HandleController(dashboardGroup, "/tag", new(dashboard.TagController))
ginx.HandleController(dashboardGroup, "/conversation", new(dashboard.ConversationController))
ginx.HandleController(dashboardGroup, "/ticket", new(dashboard.TicketController))
ginx.HandleController(dashboardGroup, "/notification", new(dashboard.NotificationController))
ginx.HandleController(dashboardGroup, "/quick-reply", new(dashboard.QuickReplyController))
ginx.HandleController(dashboardGroup, "/channel", new(dashboard.ChannelController))
ginx.HandleController(dashboardGroup, "/agent", new(dashboard.AgentController))
ginx.HandleController(dashboardGroup, "/agent-team", new(dashboard.AgentTeamController))
ginx.HandleController(dashboardGroup, "/agent-team-schedule", new(dashboard.AgentTeamScheduleController))
ginx.HandleController(dashboardGroup, "/ai-agent", new(dashboard.AIAgentController))
ginx.HandleController(dashboardGroup, "/ai-config", new(dashboard.AIConfigController))
ginx.HandleController(dashboardGroup, "/asset", new(dashboard.AssetController))
ginx.HandleController(dashboardGroup, "/knowledge-base", new(dashboard.KnowledgeBaseController))
ginx.HandleController(dashboardGroup, "/knowledge-document", new(dashboard.KnowledgeDocumentController))
ginx.HandleController(dashboardGroup, "/knowledge-faq", new(dashboard.KnowledgeFAQController))
ginx.HandleController(dashboardGroup, "/knowledge-retrieve", new(dashboard.KnowledgeRetrieveController))
ginx.HandleController(dashboardGroup, "/knowledge-retrieve-log", new(dashboard.KnowledgeRetrieveLogController))
ginx.HandleController(dashboardGroup, "/agent-run-log", new(dashboard.AgentRunLogController))
ginx.HandleController(dashboardGroup, "/skill-definition", new(dashboard.SkillDefinitionController))
ginx.HandleController(dashboardGroup, "/mcp", new(dashboard.MCPController))
thirdGroup := app.Group("/api/third")
ginx.HandleController(thirdGroup, "/wechat", new(third.WechatController))
}
func registerDashboardStatic(app *gin.Engine, root string) {
app.NoRoute(func(ctx *gin.Context) {
if strings.HasPrefix(ctx.Request.URL.Path, "/api/") {
ctx.JSON(http.StatusNotFound, gin.H{"success": false, "message": "not found"})
return
}
requestPath := filepath.Clean(strings.TrimPrefix(ctx.Request.URL.Path, "/"))
if strings.HasPrefix(requestPath, "..") {
ctx.Status(http.StatusBadRequest)
return
}
if requestPath == "." {
requestPath = "index.html"
}
fullPath := filepath.Join(root, requestPath)
if stat, err := os.Stat(fullPath); err == nil && !stat.IsDir() {
ctx.File(fullPath)
return
}
ctx.File(filepath.Join(root, "index.html"))
})
}