feat: add customer service IM bindings
This commit is contained in:
@@ -31,6 +31,9 @@ type CustomerServiceColumns struct {
|
||||
ServiceUrl string // 客服跳转URL
|
||||
BackupContact string // 备用联系方式
|
||||
SessionMapping string // 第三方会话映射
|
||||
ImTenantCode string // IM租户代码
|
||||
ImUserId string // IM客服用户ID
|
||||
ImUsername string // IM客服登录名
|
||||
Status string // 状态
|
||||
Sort string // 排序
|
||||
Remark string // 备注
|
||||
@@ -54,6 +57,9 @@ var customerServiceColumns = CustomerServiceColumns{
|
||||
ServiceUrl: "service_url",
|
||||
BackupContact: "backup_contact",
|
||||
SessionMapping: "session_mapping",
|
||||
ImTenantCode: "im_tenant_code",
|
||||
ImUserId: "im_user_id",
|
||||
ImUsername: "im_username",
|
||||
Status: "status",
|
||||
Sort: "sort",
|
||||
Remark: "remark",
|
||||
|
||||
@@ -70,21 +70,36 @@ func (s *sSysCustomerService) Edit(ctx context.Context, in *sysin.CustomerServic
|
||||
if count > 0 {
|
||||
return gerror.New("客服编码已存在")
|
||||
}
|
||||
data := g.Map{c.ServiceCode: in.ServiceCode, c.Name: in.Name, c.AvatarUrl: in.AvatarUrl, c.WorkTime: in.WorkTime, c.OnlineStatus: in.OnlineStatus, c.ServiceUrl: in.ServiceUrl, c.BackupContact: in.BackupContact, c.SessionMapping: in.SessionMapping, c.Status: in.Status, c.Sort: in.Sort, c.Remark: in.Remark, c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
if in.ImUserId != "" {
|
||||
count, err = s.model(ctx).Where(c.ImTenantCode, in.ImTenantCode).Where(c.ImUserId, in.ImUserId).WhereNot(c.Id, in.Id).Count()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if count > 0 {
|
||||
return gerror.New("IM客服账号已被其他席位绑定")
|
||||
}
|
||||
}
|
||||
data := g.Map{c.ServiceCode: in.ServiceCode, c.Name: in.Name, c.AvatarUrl: in.AvatarUrl, c.WorkTime: in.WorkTime, c.OnlineStatus: in.OnlineStatus, c.ServiceUrl: in.ServiceUrl, c.BackupContact: in.BackupContact, c.SessionMapping: in.SessionMapping, c.ImTenantCode: in.ImTenantCode, c.ImUserId: gdb.Raw("NULL"), c.ImUsername: gdb.Raw("NULL"), c.Status: in.Status, c.Sort: in.Sort, c.Remark: in.Remark, c.UpdatedBy: contexts.GetUserId(ctx)}
|
||||
if in.ImUserId != "" {
|
||||
data[c.ImUserId] = in.ImUserId
|
||||
}
|
||||
if in.ImUsername != "" {
|
||||
data[c.ImUsername] = in.ImUsername
|
||||
}
|
||||
if in.Id > 0 {
|
||||
if _, err = s.model(ctx).WherePri(in.Id).Data(data).Update(); err != nil {
|
||||
return gerror.Wrap(err, "更新客服配置失败")
|
||||
}
|
||||
} else {
|
||||
data[c.CreatedBy] = contexts.GetUserId(ctx)
|
||||
result, e := s.model(ctx).Fields(c.ServiceCode, c.Name, c.AvatarUrl, c.WorkTime, c.OnlineStatus, c.ServiceUrl, c.BackupContact, c.SessionMapping, c.Status, c.Sort, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
||||
result, e := s.model(ctx).Fields(c.ServiceCode, c.Name, c.AvatarUrl, c.WorkTime, c.OnlineStatus, c.ServiceUrl, c.BackupContact, c.SessionMapping, c.ImTenantCode, c.ImUserId, c.ImUsername, c.Status, c.Sort, c.Remark, c.CreatedBy, c.UpdatedBy).Hook(hook.SaveTenant).Data(data).Insert()
|
||||
if e != nil {
|
||||
return gerror.Wrap(e, "创建客服配置失败")
|
||||
}
|
||||
id, _ := result.LastInsertId()
|
||||
in.Id = uint64(id)
|
||||
}
|
||||
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "customer_service", Action: "customer_service.edit", TargetType: "customer_service", TargetId: in.Id, Before: before, After: g.Map{"name": in.Name, "workTime": in.WorkTime, "onlineStatus": in.OnlineStatus, "serviceUrl": in.ServiceUrl, "status": in.Status}, Reason: in.Remark})
|
||||
return service.SysBizAudit().Record(ctx, &sysin.BizAuditRecordInp{Module: "customer_service", Action: "customer_service.edit", TargetType: "customer_service", TargetId: in.Id, Before: before, After: g.Map{"name": in.Name, "workTime": in.WorkTime, "onlineStatus": in.OnlineStatus, "serviceUrl": in.ServiceUrl, "imTenantCode": in.ImTenantCode, "imUserId": in.ImUserId, "status": in.Status}, Reason: in.Remark})
|
||||
}
|
||||
|
||||
func (s *sSysCustomerService) Status(ctx context.Context, in *sysin.CustomerServiceStatusInp) error {
|
||||
|
||||
@@ -23,6 +23,9 @@ type CustomerService struct {
|
||||
ServiceUrl any // 客服跳转URL
|
||||
BackupContact any // 备用联系方式
|
||||
SessionMapping *gjson.Json // 第三方会话映射
|
||||
ImTenantCode any // IM租户代码
|
||||
ImUserId any // IM客服用户ID
|
||||
ImUsername any // IM客服登录名
|
||||
Status any // 状态
|
||||
Sort any // 排序
|
||||
Remark any // 备注
|
||||
|
||||
@@ -21,6 +21,9 @@ type CustomerService struct {
|
||||
ServiceUrl string `json:"serviceUrl" orm:"service_url" description:"客服跳转URL"`
|
||||
BackupContact string `json:"backupContact" orm:"backup_contact" description:"备用联系方式"`
|
||||
SessionMapping *gjson.Json `json:"sessionMapping" orm:"session_mapping" description:"第三方会话映射"`
|
||||
ImTenantCode string `json:"imTenantCode" orm:"im_tenant_code" description:"IM租户代码"`
|
||||
ImUserId string `json:"imUserId" orm:"im_user_id" description:"IM客服用户ID"`
|
||||
ImUsername string `json:"imUsername" orm:"im_username" description:"IM客服登录名"`
|
||||
Status int `json:"status" orm:"status" description:"状态"`
|
||||
Sort int `json:"sort" orm:"sort" description:"排序"`
|
||||
Remark string `json:"remark" orm:"remark" description:"备注"`
|
||||
|
||||
@@ -302,19 +302,27 @@ type CustomerServiceEditInp struct {
|
||||
AvatarUrl string `json:"avatarUrl" v:"max-length:1000#头像地址不能超过1000个字符"`
|
||||
WorkTime string `json:"workTime" v:"required|max-length:200#工作时间不能为空|工作时间不能超过200个字符"`
|
||||
OnlineStatus int `json:"onlineStatus" v:"required|in:1,2#在线状态不能为空|在线状态不合法"`
|
||||
ServiceUrl string `json:"serviceUrl" v:"required|max-length:1000#客服链接不能为空|客服链接不能超过1000个字符"`
|
||||
ServiceUrl string `json:"serviceUrl" v:"max-length:1000#客服链接不能超过1000个字符"`
|
||||
BackupContact string `json:"backupContact" v:"required|max-length:500#备用联系方式不能为空|备用联系方式不能超过500个字符"`
|
||||
SessionMapping *gjson.Json `json:"sessionMapping"`
|
||||
ImTenantCode string `json:"imTenantCode" v:"required|max-length:64#IM租户代码不能为空|IM租户代码不能超过64个字符"`
|
||||
ImUserId string `json:"imUserId" v:"max-length:64#IM用户ID不能超过64个字符"`
|
||||
ImUsername string `json:"imUsername" v:"max-length:64#IM登录名不能超过64个字符"`
|
||||
Status int `json:"status" d:"1" v:"required|in:1,2#状态不能为空|状态不合法"`
|
||||
Sort int `json:"sort"`
|
||||
Remark string `json:"remark" v:"max-length:500#备注不能超过500个字符"`
|
||||
}
|
||||
|
||||
func (in *CustomerServiceEditInp) Filter(ctx context.Context) error {
|
||||
u, err := url.Parse(in.ServiceUrl)
|
||||
if err != nil || !strings.EqualFold(u.Scheme, "https") || u.Host == "" {
|
||||
return gerror.New("客服链接必须是有效的 HTTPS 地址")
|
||||
if strings.TrimSpace(in.ServiceUrl) != "" {
|
||||
u, err := url.Parse(in.ServiceUrl)
|
||||
if err != nil || !strings.EqualFold(u.Scheme, "https") || u.Host == "" {
|
||||
return gerror.New("客服链接必须是有效的 HTTPS 地址")
|
||||
}
|
||||
}
|
||||
in.ImTenantCode = strings.ToLower(strings.TrimSpace(in.ImTenantCode))
|
||||
in.ImUserId = strings.TrimSpace(in.ImUserId)
|
||||
in.ImUsername = strings.ToLower(strings.TrimSpace(in.ImUsername))
|
||||
if in.OnlineStatus != consts.CustomerServiceOnline && in.OnlineStatus != consts.CustomerServiceOffline {
|
||||
return gerror.New("客服在线状态不合法")
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
-- 客服系统接入自建 IM:客服席位绑定、C 端会员 IM 身份映射。
|
||||
-- im_user_id 使用 NULL 表示尚未绑定,避免唯一索引把多个空值误判为重复。
|
||||
|
||||
ALTER TABLE `hg_customer_service`
|
||||
ADD COLUMN `im_tenant_code` varchar(64) NOT NULL DEFAULT 'allrich' COMMENT 'IM租户代码' AFTER `session_mapping`,
|
||||
ADD COLUMN `im_user_id` varchar(64) DEFAULT NULL COMMENT 'IM客服用户ID' AFTER `im_tenant_code`,
|
||||
ADD COLUMN `im_username` varchar(64) DEFAULT NULL COMMENT 'IM客服登录名' AFTER `im_user_id`,
|
||||
ADD UNIQUE KEY `uk_customer_service_im_user` (`im_tenant_code`, `im_user_id`);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `hg_app_member_im_identity` (
|
||||
`id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`tenant_id` bigint unsigned NOT NULL DEFAULT 0 COMMENT '业务租户ID',
|
||||
`member_id` bigint unsigned NOT NULL COMMENT '会员ID',
|
||||
`im_tenant_code` varchar(64) NOT NULL COMMENT 'IM租户代码',
|
||||
`im_user_id` varchar(64) NOT NULL COMMENT 'IM公开用户ID',
|
||||
`im_username` varchar(64) NOT NULL COMMENT 'IM登录名',
|
||||
`sync_status` tinyint NOT NULL DEFAULT 1 COMMENT '同步状态:1正常、2待重试、3失败',
|
||||
`last_error` varchar(500) NOT NULL DEFAULT '' COMMENT '最近同步错误',
|
||||
`created_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT '创建时间',
|
||||
`updated_at` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ON UPDATE CURRENT_TIMESTAMP(3) COMMENT '更新时间',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_member_im_identity_member` (`tenant_id`, `member_id`, `im_tenant_code`),
|
||||
UNIQUE KEY `uk_member_im_identity_user` (`im_tenant_code`, `im_user_id`),
|
||||
KEY `idx_member_im_identity_member` (`member_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='会员_IM身份映射';
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Olons 复用 IM 现有 Basic 登录协议,随机 IM 密码仅以 AES-GCM 密文保存。
|
||||
|
||||
ALTER TABLE `hg_app_member_im_identity`
|
||||
ADD COLUMN `im_password_cipher` varchar(512) NOT NULL DEFAULT '' COMMENT 'IM登录密码密文' AFTER `im_username`;
|
||||
Reference in New Issue
Block a user