package handler import ( "net/http" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "kefu-sys/server/internal/middleware" "kefu-sys/server/internal/model" "kefu-sys/server/internal/ws" ) type SessionHandler struct{} func NewSessionHandler() *SessionHandler { return &SessionHandler{} } type SendMessageReq struct { Content string `json:"content" binding:"required"` Type string `json:"type"` } func (h *SessionHandler) SendMessage(c *gin.Context) { userID := middleware.GetUserID(c) id := c.Param("id") var req SendMessageReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } if req.Type == "" { req.Type = "text" } session, ok := loadTenantSession(c, id) if !ok { return } if !canOperateSession(c, session) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权向该会话发送消息"}) return } if session.Status == "ended" || session.Status == "archived" { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "会话已结束"}) return } msg := model.Message{ SessionID: session.ID, SenderType: "agent", SenderID: &userID, Content: req.Content, Type: req.Type, SentAt: time.Now(), } if err := model.CreateMessage(&msg); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "发送失败"}) return } broadcastSessionMessage(session, msg) middleware.JSON(c, msg) } type CreateSessionReq struct { ChannelID uint `json:"channel_id"` CustomerID uint `json:"customer_id"` Priority string `json:"priority"` } type AssignSessionReq struct { AgentID uint `json:"agent_id"` } func isTenantManager(c *gin.Context) bool { return middleware.HasAnyRole(c, "admin", "supervisor") } func loadTenantSession(c *gin.Context, id string) (*model.Session, bool) { var session model.Session if err := model.DB.First(&session, id).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "会话不存在"}) return nil, false } if session.TenantID != middleware.GetTenantID(c) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权访问其他租户会话"}) return nil, false } return &session, true } func canReadSession(c *gin.Context, session *model.Session) bool { if isTenantManager(c) { return true } if middleware.GetRole(c) == "agent" { userID := middleware.GetUserID(c) return session.Status == "waiting" || (session.AgentID != nil && *session.AgentID == userID) } return false } func canOperateSession(c *gin.Context, session *model.Session) bool { if isTenantManager(c) { return true } return middleware.GetRole(c) == "agent" && session.AgentID != nil && *session.AgentID == middleware.GetUserID(c) } func broadcastSessionMessage(session *model.Session, message model.Message) { payload, err := ws.NewEvent("message", session.ID, message) if err == nil { ws.DefaultHub.BroadcastToSession(session.TenantID, session.ID, session.AgentID, payload) } } func broadcastSessionUpdate(session *model.Session) { payload, err := ws.NewEvent("session_updated", session.ID, gin.H{"status": session.Status}) if err == nil { ws.DefaultHub.BroadcastToTenantStaff(session.TenantID, payload) } } func loadAssignableAgent(tenantID, agentID uint) error { var agent model.User if err := model.DB.First(&agent, agentID).Error; err != nil { return err } if agent.TenantID != tenantID || agent.Role != "agent" || agent.Status == "disabled" { return gorm.ErrRecordNotFound } return nil } func (h *SessionHandler) List(c *gin.Context) { tenantID := middleware.GetTenantID(c) page, pageSize := middleware.GetPageParams(c) status := c.Query("status") priority := c.Query("priority") var sessions []model.Session var total int64 query := model.DB.Where("tenant_id = ?", tenantID) if middleware.GetRole(c) == "agent" { query = query.Where("agent_id = ? OR status = ?", middleware.GetUserID(c), "waiting") } if status != "" { query = query.Where("status = ?", status) } if priority != "" { query = query.Where("priority = ?", priority) } query.Model(&model.Session{}).Count(&total) query.Order("created_at desc").Offset((page - 1) * pageSize).Limit(pageSize).Find(&sessions) middleware.JSONList(c, sessions, total, page, pageSize) } func (h *SessionHandler) Get(c *gin.Context) { id := c.Param("id") session, ok := loadTenantSession(c, id) if !ok { return } if !canReadSession(c, session) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权查看该会话"}) return } var messages []model.Message if err := model.DB.Where("session_id = ?", session.ID).Order("seq asc").Find(&messages).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询消息失败"}) return } var events []model.SessionEvent model.DB.Where("session_id = ?", session.ID).Order("created_at asc").Find(&events) middleware.JSON(c, gin.H{"session": session, "messages": messages, "events": events}) } func (h *SessionHandler) Create(c *gin.Context) { if !isTenantManager(c) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "仅主管或管理员可创建会话"}) return } var req CreateSessionReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } tenantID := middleware.GetTenantID(c) var channel model.Channel if err := model.DB.Where("id = ? AND tenant_id = ? AND status = ?", req.ChannelID, tenantID, "enabled").First(&channel).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "渠道不存在或未启用"}) return } var customer model.Customer if err := model.DB.Where("id = ? AND tenant_id = ?", req.CustomerID, tenantID).First(&customer).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "客户不存在"}) return } session := model.Session{ TenantID: tenantID, ChannelID: req.ChannelID, CustomerID: req.CustomerID, Priority: req.Priority, Status: "waiting", } if session.Priority == "" { session.Priority = "normal" } if err := model.DB.Create(&session).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "创建会话失败"}) return } middleware.JSON(c, session) } func (h *SessionHandler) Assign(c *gin.Context) { id := c.Param("id") session, ok := loadTenantSession(c, id) if !ok { return } var req AssignSessionReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } if session.Status != "waiting" { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "会话已被分配"}) return } if middleware.GetRole(c) == "agent" { req.AgentID = middleware.GetUserID(c) } else if !isTenantManager(c) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权分配会话"}) return } if req.AgentID == 0 { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "请选择目标客服"}) return } if err := loadAssignableAgent(session.TenantID, req.AgentID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "目标客服不存在或不可用"}) return } result := model.DB.Model(&model.Session{}). Where("id = ? AND tenant_id = ? AND status = ?", session.ID, session.TenantID, "waiting"). Updates(map[string]interface{}{"agent_id": req.AgentID, "status": "active"}) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "分配失败"}) return } if result.RowsAffected == 0 { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "会话已被分配"}) return } model.DB.Create(&model.SessionEvent{SessionID: session.ID, OperatorID: middleware.GetUserID(c), Action: "assign", Detail: "会话分配"}) session.AgentID = &req.AgentID session.Status = "active" broadcastSessionUpdate(session) middleware.JSON(c, gin.H{"message": "分配成功"}) } func (h *SessionHandler) Transfer(c *gin.Context) { id := c.Param("id") session, ok := loadTenantSession(c, id) if !ok { return } if !canOperateSession(c, session) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权转接该会话"}) return } var req AssignSessionReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } if err := loadAssignableAgent(session.TenantID, req.AgentID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "目标客服不存在或不可用"}) return } result := model.DB.Model(&model.Session{}). Where("id = ? AND tenant_id = ? AND status = ?", session.ID, session.TenantID, "active"). Update("agent_id", req.AgentID) if result.Error != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "转接失败"}) return } if result.RowsAffected == 0 { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "会话不可转接"}) return } model.DB.Create(&model.SessionEvent{ SessionID: session.ID, OperatorID: middleware.GetUserID(c), Action: "transfer", Detail: "会话转接", }) session.AgentID = &req.AgentID broadcastSessionUpdate(session) middleware.JSON(c, gin.H{"message": "转接成功"}) } func (h *SessionHandler) End(c *gin.Context) { id := c.Param("id") reason := c.Query("reason") session, ok := loadTenantSession(c, id) if !ok { return } if !canOperateSession(c, session) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权结束该会话"}) return } if reason == "" { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "请填写结束原因"}) return } now := time.Now() result := model.DB.Model(&model.Session{}). Where("id = ? AND tenant_id = ? AND status <> ?", session.ID, session.TenantID, "ended"). Updates(map[string]interface{}{"status": "ended", "end_reason": reason, "ended_at": now}) if result.RowsAffected == 0 { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "会话不存在"}) return } model.DB.Create(&model.SessionEvent{ SessionID: session.ID, OperatorID: middleware.GetUserID(c), Action: "end", Detail: "结束会话: " + reason, }) session.Status = "ended" session.EndedAt = &now broadcastSessionUpdate(session) middleware.JSON(c, gin.H{"message": "已结束"}) } func (h *SessionHandler) UpdatePriority(c *gin.Context) { id := c.Param("id") priority := c.Query("priority") session, ok := loadTenantSession(c, id) if !ok { return } if !canOperateSession(c, session) { c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "无权更新该会话"}) return } if priority != "urgent" && priority != "normal" { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "优先级无效"}) return } result := model.DB.Model(&model.Session{}). Where("id = ? AND tenant_id = ?", session.ID, session.TenantID). Update("priority", priority) if result.Error != nil || result.RowsAffected == 0 { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "更新失败"}) return } session.Priority = priority broadcastSessionUpdate(session) middleware.JSON(c, gin.H{"message": "已更新"}) }