修正默认客服分配逻辑
This commit is contained in:
@@ -11,7 +11,6 @@ import (
|
||||
"hfb_sys/backend/internal/model"
|
||||
"hfb_sys/backend/internal/modules/chathub"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
"gorm.io/gorm/clause"
|
||||
@@ -23,9 +22,7 @@ type Repository struct {
|
||||
}
|
||||
|
||||
const (
|
||||
defaultSupportUsername = "admin"
|
||||
defaultSupportPassword = "admin123456"
|
||||
defaultSupportNickname = "超级管理员"
|
||||
defaultSupportRoleCode = "cs"
|
||||
)
|
||||
|
||||
func NewRepository(db *gorm.DB, hub *chathub.Hub) *Repository {
|
||||
@@ -689,21 +686,16 @@ func (row conversationRow) toDTO(participants []ParticipantDTO) ConversationDTO
|
||||
}
|
||||
|
||||
func defaultSupportAdminID(tx *gorm.DB) uint64 {
|
||||
// 查询所有有 chat:view 权限且状态为 active 的管理员
|
||||
var adminIDs []uint64
|
||||
err := tx.Table("admin_users AS au").
|
||||
Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
|
||||
Joins("JOIN role_permissions AS rp ON rp.role_id = aur.role_id").
|
||||
Joins("JOIN permissions AS p ON p.id = rp.permission_id").
|
||||
Where("au.status = ? AND p.code = ?", "active", "chat:view").
|
||||
Distinct("au.id").
|
||||
Pluck("au.id", &adminIDs).Error
|
||||
if err != nil || len(adminIDs) == 0 {
|
||||
// 回退到原有逻辑
|
||||
return fallbackSupportAdminID(tx)
|
||||
if supportID := configuredDefaultSupportAdminID(tx); supportID > 0 {
|
||||
return supportID
|
||||
}
|
||||
|
||||
// 统计每个客服当前负责的会话数,选择负载最少的
|
||||
adminIDs, err := supportAdminIDs(tx)
|
||||
if err != nil || len(adminIDs) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// 未配置默认客服时,按当前会话负载选择客服角色中最空闲的一位。
|
||||
type adminLoad struct {
|
||||
AdminID uint64
|
||||
Count int64
|
||||
@@ -733,48 +725,38 @@ func defaultSupportAdminID(tx *gorm.DB) uint64 {
|
||||
if selectedID > 0 {
|
||||
return selectedID
|
||||
}
|
||||
return fallbackSupportAdminID(tx)
|
||||
return 0
|
||||
}
|
||||
|
||||
func fallbackSupportAdminID(tx *gorm.DB) uint64 {
|
||||
func configuredDefaultSupportAdminID(tx *gorm.DB) uint64 {
|
||||
var cfg model.SystemConfig
|
||||
if err := tx.Where("`key` = ?", "chat.default_support_admin_id").First(&cfg).Error; err == nil {
|
||||
id, parseErr := strconv.ParseUint(cfg.Value, 10, 64)
|
||||
if parseErr == nil && id > 0 && adminActive(tx, id) {
|
||||
if parseErr == nil && id > 0 && adminIsSupport(tx, id) {
|
||||
return id
|
||||
}
|
||||
}
|
||||
var admin model.AdminUser
|
||||
if err := tx.Where("status = ?", "active").Order("id ASC").First(&admin).Error; err != nil {
|
||||
return ensureDefaultSupportAdmin(tx)
|
||||
}
|
||||
return admin.ID
|
||||
return 0
|
||||
}
|
||||
|
||||
func ensureDefaultSupportAdmin(tx *gorm.DB) uint64 {
|
||||
var count int64
|
||||
if err := tx.Model(&model.AdminUser{}).Count(&count).Error; err != nil || count > 0 {
|
||||
return 0
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(defaultSupportPassword), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
admin := model.AdminUser{
|
||||
Username: defaultSupportUsername,
|
||||
PasswordHash: string(hash),
|
||||
Nickname: defaultSupportNickname,
|
||||
Status: "active",
|
||||
}
|
||||
if err := tx.Create(&admin).Error; err != nil {
|
||||
return 0
|
||||
}
|
||||
return admin.ID
|
||||
func supportAdminIDs(tx *gorm.DB) ([]uint64, error) {
|
||||
var adminIDs []uint64
|
||||
err := tx.Table("admin_users AS au").
|
||||
Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
|
||||
Joins("JOIN roles AS r ON r.id = aur.role_id").
|
||||
Where("au.status = ? AND r.code = ?", "active", defaultSupportRoleCode).
|
||||
Order("CASE au.support_status WHEN 'online' THEN 0 WHEN 'busy' THEN 1 ELSE 2 END, au.id ASC").
|
||||
Pluck("au.id", &adminIDs).Error
|
||||
return adminIDs, err
|
||||
}
|
||||
|
||||
func adminActive(tx *gorm.DB, id uint64) bool {
|
||||
func adminIsSupport(tx *gorm.DB, id uint64) bool {
|
||||
var count int64
|
||||
if err := tx.Model(&model.AdminUser{}).Where("id = ? AND status = ?", id, "active").Count(&count).Error; err != nil {
|
||||
if err := tx.Table("admin_users AS au").
|
||||
Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
|
||||
Joins("JOIN roles AS r ON r.id = aur.role_id").
|
||||
Where("au.id = ? AND au.status = ? AND r.code = ?", id, "active", defaultSupportRoleCode).
|
||||
Count(&count).Error; err != nil {
|
||||
return false
|
||||
}
|
||||
return count > 0
|
||||
@@ -826,9 +808,9 @@ func (r *Repository) TransferConversation(principal Principal, conversationID ui
|
||||
if _, err := r.findParticipant(tx, principal, conversationID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
// 验证目标客服存在且活跃
|
||||
if !adminActive(tx, toAdminID) {
|
||||
return fmt.Errorf("目标客服不存在或已禁用")
|
||||
// 验证目标客服存在、活跃且拥有客服角色,避免转接给超级管理员。
|
||||
if !adminIsSupport(tx, toAdminID) {
|
||||
return fmt.Errorf("目标客服不存在、已禁用或不是客服角色")
|
||||
}
|
||||
// 检查目标客服是否已有该会话
|
||||
var count int64
|
||||
@@ -874,7 +856,7 @@ func (r *Repository) TransferConversation(principal Principal, conversationID ui
|
||||
|
||||
// GetAvailableSupportAdmins 获取可用客服列表及其会话数
|
||||
func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) {
|
||||
// 查询所有有 chat:view 权限且状态为 active 的管理员
|
||||
// 仅展示客服角色管理员,超级管理员即使有 chat:view 权限也不作为客服候选。
|
||||
type adminRow struct {
|
||||
ID uint64
|
||||
Nickname string
|
||||
@@ -882,11 +864,11 @@ func (r *Repository) GetAvailableSupportAdmins() ([]SupportAdminDTO, error) {
|
||||
}
|
||||
var admins []adminRow
|
||||
err := r.db.Table("admin_users AS au").
|
||||
Select("DISTINCT au.id, COALESCE(NULLIF(au.nickname, ''), au.username) AS nickname, au.support_status").
|
||||
Select("au.id, COALESCE(NULLIF(au.nickname, ''), au.username) AS nickname, au.support_status").
|
||||
Joins("JOIN admin_user_roles AS aur ON aur.admin_user_id = au.id").
|
||||
Joins("JOIN role_permissions AS rp ON rp.role_id = aur.role_id").
|
||||
Joins("JOIN permissions AS p ON p.id = rp.permission_id").
|
||||
Where("au.status = ? AND p.code = ?", "active", "chat:view").
|
||||
Joins("JOIN roles AS r ON r.id = aur.role_id").
|
||||
Where("au.status = ? AND r.code = ?", "active", defaultSupportRoleCode).
|
||||
Order("CASE au.support_status WHEN 'online' THEN 0 WHEN 'busy' THEN 1 ELSE 2 END, au.id ASC").
|
||||
Scan(&admins).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -34,7 +34,7 @@ var defaultConfigs = []defaultConfig{
|
||||
{Key: listingPublishAgreementsConfigKey, Value: defaultListingPublishAgreementsConfigValue(), Description: "发布账号前协议配置 JSON"},
|
||||
{Key: orderAgreementsConfigKey, Value: defaultOrderAgreementsConfigValue(), Description: "下单前协议配置 JSON"},
|
||||
{Key: postRentalNoticeConfigKey, Value: defaultPostRentalNoticeConfigValue(), Description: "租后须知配置 JSON"},
|
||||
{Key: "chat.default_support_admin_id", Value: "1", Description: "订单群聊回退客服 ID(仅当无可用客服时使用)"},
|
||||
{Key: "chat.default_support_admin_id", Value: "2", Description: "默认客服 ID(必须是启用状态的客服角色)"},
|
||||
{Key: "chat.auto_welcome_message", Value: "欢迎加入订单群聊!如有任何问题,请随时沟通。", Description: "建群后自动发送的欢迎话术"},
|
||||
{Key: homeAnnouncementsConfigKey, Value: defaultHomeAnnouncementsConfigValue(), Description: "移动端首页公告 JSON 数组"},
|
||||
{Key: homeBannersConfigKey, Value: defaultHomeBannersConfigValue(), Description: "移动端首页轮播图 JSON 数组"},
|
||||
@@ -150,7 +150,7 @@ func (r *Repository) ensureDefaults() error {
|
||||
return err
|
||||
}
|
||||
if item.Key == "chat.default_support_admin_id" {
|
||||
if err := updateDefaultDescription(tx, item); err != nil {
|
||||
if err := updateDefaultSupportConfig(tx, item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -164,15 +164,23 @@ func (r *Repository) ensureDefaults() error {
|
||||
})
|
||||
}
|
||||
|
||||
func updateDefaultDescription(tx *gorm.DB, item defaultConfig) error {
|
||||
func updateDefaultSupportConfig(tx *gorm.DB, item defaultConfig) error {
|
||||
var row model.SystemConfig
|
||||
if err := tx.Where("`key` = ?", item.Key).First(&row).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if row.Description == item.Description {
|
||||
changed := false
|
||||
if strings.TrimSpace(row.Value) == "1" {
|
||||
row.Value = item.Value
|
||||
changed = true
|
||||
}
|
||||
if row.Description != item.Description {
|
||||
row.Description = item.Description
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
return nil
|
||||
}
|
||||
row.Description = item.Description
|
||||
return tx.Save(&row).Error
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user