package handler import ( "crypto/rand" "encoding/hex" "fmt" "net/http" "regexp" "strings" "github.com/gin-gonic/gin" "kefu-cloud/server/internal/middleware" "kefu-cloud/server/internal/model" ) type ChannelHandler struct{} func NewChannelHandler() *ChannelHandler { return &ChannelHandler{} } var scriptIDPattern = regexp.MustCompile(`data-id="([A-Za-z0-9_-]+)"`) type channelView struct { ID uint `json:"id"` TenantID uint `json:"tenant_id"` Type string `json:"type"` Name string `json:"name"` Status string `json:"status"` Config string `json:"config"` ScriptCode string `json:"script_code"` ChannelKey string `json:"channel_key"` } func extractChannelKey(script string) string { m := scriptIDPattern.FindStringSubmatch(script) if len(m) == 2 { return m[1] } return "" } func buildWebScript(channelKey string) string { return fmt.Sprintf(``, channelKey) } func newChannelKey(prefix string) (string, error) { buf := make([]byte, 4) if _, err := rand.Read(buf); err != nil { return "", err } return fmt.Sprintf("%s_%s", prefix, hex.EncodeToString(buf)), nil } func toChannelView(ch model.Channel) channelView { return channelView{ ID: ch.ID, TenantID: ch.TenantID, Type: ch.Type, Name: ch.Name, Status: ch.Status, Config: ch.Config, ScriptCode: ch.ScriptCode, ChannelKey: extractChannelKey(ch.ScriptCode), } } func requireTenantAdmin(c *gin.Context) bool { if middleware.HasAnyRole(c, "admin") { return true } c.JSON(http.StatusForbidden, gin.H{"code": 403, "message": "仅租户管理员可操作"}) return false } func (h *ChannelHandler) List(c *gin.Context) { tenantID := middleware.GetTenantID(c) var channels []model.Channel if err := model.DB.Where("tenant_id = ?", tenantID).Order("id asc").Find(&channels).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "查询渠道失败"}) return } views := make([]channelView, 0, len(channels)) for _, ch := range channels { views = append(views, toChannelView(ch)) } middleware.JSON(c, views) } type updateChannelReq struct { Name *string `json:"name"` Status *string `json:"status"` } func (h *ChannelHandler) Update(c *gin.Context) { if !requireTenantAdmin(c) { return } tenantID := middleware.GetTenantID(c) id := c.Param("id") var channel model.Channel if err := model.DB.Where("id = ? AND tenant_id = ?", id, tenantID).First(&channel).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"code": 404, "message": "渠道不存在"}) return } var req updateChannelReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } updates := map[string]interface{}{} if req.Name != nil { name := strings.TrimSpace(*req.Name) if name == "" || len([]rune(name)) > 50 { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "渠道名称无效"}) return } updates["name"] = name } if req.Status != nil { status := strings.TrimSpace(*req.Status) if status != "enabled" && status != "disabled" { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "状态仅支持 enabled/disabled"}) return } updates["status"] = status } if len(updates) == 0 { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "没有可更新字段"}) return } if err := model.DB.Model(&channel).Updates(updates).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "更新失败"}) return } model.DB.First(&channel, channel.ID) middleware.JSON(c, toChannelView(channel)) } type createChannelReq struct { Type string `json:"type" binding:"required"` Name string `json:"name"` } func (h *ChannelHandler) Create(c *gin.Context) { if !requireTenantAdmin(c) { return } var req createChannelReq if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "参数错误"}) return } channelType := strings.TrimSpace(req.Type) allowed := map[string]string{ "web": "网页聊天", "wechat": "微信公众号", "app": "APP 内嵌", "phone": "电话客服", "email": "邮件工单", } defaultName, ok := allowed[channelType] if !ok { c.JSON(http.StatusBadRequest, gin.H{"code": 400, "message": "不支持的渠道类型"}) return } tenantID := middleware.GetTenantID(c) var exists int64 model.DB.Model(&model.Channel{}).Where("tenant_id = ? AND type = ?", tenantID, channelType).Count(&exists) if exists > 0 { c.JSON(http.StatusConflict, gin.H{"code": 409, "message": "该类型渠道已存在"}) return } name := strings.TrimSpace(req.Name) if name == "" { name = defaultName } prefix := map[string]string{"web": "WK", "wechat": "WX", "app": "AP", "phone": "PH", "email": "EM"}[channelType] key, err := newChannelKey(prefix) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "生成渠道标识失败"}) return } channel := model.Channel{ TenantID: tenantID, Type: channelType, Name: name, Status: "disabled", } if channelType == "web" { channel.Status = "enabled" channel.ScriptCode = buildWebScript(key) } else { channel.ScriptCode = fmt.Sprintf(`data-id="%s"`, key) } if err := model.DB.Create(&channel).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": 500, "message": "创建失败"}) return } middleware.JSON(c, toChannelView(channel)) }