package sys import ( "context" "hotgo/internal/consts" "hotgo/internal/dao" "hotgo/internal/library/contexts" "hotgo/internal/library/immoderation" "hotgo/internal/library/imtenant" "hotgo/internal/model/entity" "hotgo/internal/model/input/sysin" "hotgo/internal/service" "encoding/json" "fmt" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/encoding/gjson" "github.com/gogf/gf/v2/errors/gerror" "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/os/gtime" "github.com/gogf/gf/v2/util/gconv" "strings" "time" ) type sSysImExternalConfig struct{} func NewSysImExternalConfig() *sSysImExternalConfig { return &sSysImExternalConfig{} } func init() { service.RegisterSysImExternalConfig(NewSysImExternalConfig()) } func (s *sSysImExternalConfig) List(ctx context.Context, in *sysin.ImExternalConfigListInp) (list []*sysin.ImExternalConfigListModel, totalCount int, err error) { if err = in.Filter(ctx); err != nil { return } tenant, err := s.resolveTenant(ctx, in.TenantId) if err != nil { return } if err = s.ensureRows(ctx, tenant.Id); err != nil { return } if in.Service == consts.ImExternalServiceModeration && tenant.Code != "default" { return nil, 0, gerror.New("第一阶段仅默认商户可配置内容审核服务") } mod := s.model(ctx).Where("tenant_id", tenant.Id) if tenant.Code != "default" { mod = mod.WhereNot("service", consts.ImExternalServiceModeration) } if in.Service != "" { mod = mod.Where("service", in.Service) } if in.Provider != "" { mod = mod.WhereLike("provider", "%"+in.Provider+"%") } if in.Enabled != nil { mod = mod.Where("enabled", *in.Enabled) } mod = mod.Page(in.Page, in.PerPage).OrderAsc("service") if err = mod.ScanAndCount(&list, &totalCount, false); err != nil { err = gerror.Wrap(err, "获取IM外部服务配置失败,请稍后重试!") return } for _, item := range list { item.TenantName = tenant.Name } return } func (s *sSysImExternalConfig) View(ctx context.Context, in *sysin.ImExternalConfigViewInp) (res *sysin.ImExternalConfigViewModel, err error) { if err = in.Filter(ctx); err != nil { return } tenant, err := s.resolveTenant(ctx, in.TenantId) if err != nil { return nil, err } if err = s.ensureRows(ctx, tenant.Id); err != nil { return nil, err } if in.Service == consts.ImExternalServiceModeration && tenant.Code != "default" { return nil, gerror.New("第一阶段仅默认商户可查看内容审核服务") } if err = s.model(ctx).Where("tenant_id", tenant.Id).Where("service", in.Service).Scan(&res); err != nil { return nil, gerror.Wrap(err, "获取IM外部服务配置详情失败,请稍后重试!") } if res != nil { res.TenantName = tenant.Name } return } func (s *sSysImExternalConfig) Update(ctx context.Context, in *sysin.ImExternalConfigUpdateInp) (err error) { if err = in.Filter(ctx); err != nil { return } if !s.validService(in.Service) { return gerror.New("服务类型不正确") } tenant, err := s.resolveTenant(ctx, in.TenantId) if err != nil { return err } if err = s.ensureRows(ctx, tenant.Id); err != nil { return err } if in.Service == consts.ImExternalServiceModeration && tenant.Code != "default" { return gerror.New("第一阶段仅默认商户可配置内容审核服务") } if in.Service == consts.ImExternalServiceModeration && !isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) { return gerror.New("只有平台管理员可以维护内容审核服务") } if in.Service == consts.ImExternalServiceModeration { if in.Provider != "aliyun_green" { return gerror.New("内容审核服务商必须为aliyun_green") } in.Endpoint = "" if in.ConfigJson == nil { in.ConfigJson = gjson.New(map[string]any{"nickname_service": "nickname_detection_pro", "chat_service": "chat_detection_pro", "timeout_ms": 1500, "failure_mode": "allow"}) } config := in.ConfigJson.Map() if timeout := gconv.Int(config["timeout_ms"]); timeout < 200 || timeout > 10000 { return gerror.New("内容审核超时必须为200至10000毫秒") } if mode := gconv.String(config["failure_mode"]); mode != "allow" && mode != "block" { return gerror.New("内容审核异常策略不合法") } in.ConfigJson = gjson.New(map[string]any{ "nickname_service": firstExternalValue(gconv.String(config["nickname_service"]), "nickname_detection_pro"), "chat_service": firstExternalValue(gconv.String(config["chat_service"]), "chat_detection_pro"), "timeout_ms": gconv.Int(config["timeout_ms"]), "failure_mode": gconv.String(config["failure_mode"]), }) } configJson := "{}" if in.ConfigJson != nil && in.ConfigJson.String() != "" && in.ConfigJson.String() != "null" { configJson = in.ConfigJson.String() } data := g.Map{ "provider": in.Provider, "endpoint": in.Endpoint, "enabled": in.Enabled, "config_json": configJson, "updated_at": gtime.Now(), "updated_by": contexts.GetUserId(ctx), } if in.SecretJson != nil && in.SecretJson.String() != "" && in.SecretJson.String() != "null" && in.SecretJson.String() != "{}" { data["secret_json"] = in.SecretJson.String() } if in.Service == consts.ImExternalServiceModeration { data["secret_json"] = "{}" } if _, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx). Where("tenant_id", tenant.Id).Where("service", in.Service).Data(data).Update(); err != nil { return gerror.Wrap(err, "更新IM外部服务配置失败,请稍后重试!") } _ = writeImAdminAudit(ctx, tenant.Id, "im_external_config_update", "im_external_config", 0, "success", g.Map{ "service": in.Service, "provider": in.Provider, "enabled": in.Enabled, }) if in.Service == consts.ImExternalServiceModeration { _ = publishImPolicyEvent(ctx, tenant.Id, tenant.Code, "third_party.updated", "moderation", 0) } return nil } func (s *sSysImExternalConfig) Check(ctx context.Context, in *sysin.ImExternalConfigCheckInp) (err error) { if err = in.Filter(ctx); err != nil { return } tenant, err := s.resolveTenant(ctx, in.TenantId) if err != nil { return err } if err = s.ensureRows(ctx, tenant.Id); err != nil { return err } if in.Service == consts.ImExternalServiceModeration && tenant.Code != "default" { return gerror.New("第一阶段仅默认商户可检查内容审核服务") } if in.Service == consts.ImExternalServiceModeration && !isImPlatformAdmin(ctx, contexts.GetUserId(ctx)) { return gerror.New("只有平台管理员可以检查内容审核服务") } var row gdb.Record if row, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx). Where("tenant_id", tenant.Id).Where("service", in.Service).One(); err != nil { return gerror.Wrap(err, "检查IM外部服务配置失败,请稍后重试!") } status := consts.ImExternalConfigStatusConfigured result := "配置完整" if row == nil || row["enabled"].Int() != 1 || row["provider"].String() == "" { status = consts.ImExternalConfigStatusUnconfigured result = "未启用或服务商为空" } else if in.Service == consts.ImExternalServiceModeration { status, result = s.checkModeration(ctx, row) } if _, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx). Where("tenant_id", tenant.Id).Where("service", in.Service). Data(g.Map{"status": status, "checked_at": gtime.Now(), "check_result": result, "updated_at": gtime.Now()}). Update(); err != nil { return gerror.Wrap(err, "更新IM外部服务检查结果失败,请稍后重试!") } _ = writeImAdminAudit(ctx, tenant.Id, "im_external_config_check", "im_external_config", 0, "success", g.Map{ "service": in.Service, "status": status, }) return nil } func (s *sSysImExternalConfig) checkModeration(ctx context.Context, row gdb.Record) (string, string) { if row["provider"].String() != "aliyun_green" { return consts.ImExternalConfigStatusUnconfigured, "内容审核服务商必须为aliyun_green" } config := map[string]any{} _ = json.Unmarshal([]byte(row["config_json"].String()), &config) globalConfig, err := immoderation.LoadAliyunGlobalConfig(ctx) if err != nil { return consts.ImExternalConfigStatusUnconfigured, sanitizeExternalError(err) } timeoutMS := gconv.Int(config["timeout_ms"]) if timeoutMS <= 0 { timeoutMS = 1500 } check, err := immoderation.CheckAliyunText(ctx, immoderation.AliyunConfig{ AccessKeyID: globalConfig.AccessKeyID, AccessKeySecret: globalConfig.AccessKeySecret, RegionID: globalConfig.RegionID, Endpoint: globalConfig.Endpoint, Service: firstExternalValue(gconv.String(config["nickname_service"]), "nickname_detection_pro"), Timeout: time.Duration(timeoutMS) * time.Millisecond, }) if err != nil { return consts.ImExternalConfigStatusUnconfigured, "连通性检查失败:" + sanitizeExternalError(err) } return consts.ImExternalConfigStatusConfigured, fmt.Sprintf("检查成功,耗时%dms,RequestID=%s", check.Latency.Milliseconds(), check.RequestID) } func firstExternalValue(value, fallback string) string { if strings.TrimSpace(value) == "" { return fallback } return strings.TrimSpace(value) } func sanitizeExternalError(err error) string { value := strings.ReplaceAll(err.Error(), "\n", " ") if len([]rune(value)) > 120 { value = string([]rune(value)[:120]) } return value } func (s *sSysImExternalConfig) model(ctx context.Context) *gdb.Model { return imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).Fields( "tenant_id", "id", "service", "provider", "endpoint", "enabled", "status", "CAST(config_json AS CHAR) config_json", "CASE WHEN secret_json IS NULL OR CAST(secret_json AS CHAR)='{}' THEN 0 ELSE 1 END has_secret", "checked_at", "check_result", "created_at", "updated_at", ) } func (s *sSysImExternalConfig) resolveTenant(ctx context.Context, selectedTenantId int64) (tenant *entity.ImTenant, err error) { scope, err := resolveImAdminScope(ctx, selectedTenantId) if err != nil { return nil, err } if err = imtenant.RegistryDB().Model(dao.ImTenant.Table()).Safe().Ctx(ctx).WherePri(scope.TenantId).Scan(&tenant); err != nil { return nil, gerror.Wrap(err, "获取IM租户失败") } if tenant == nil { return nil, gerror.New("IM租户不存在") } return tenant, nil } func (s *sSysImExternalConfig) ensureRows(ctx context.Context, tenantId int64) error { services := []string{ consts.ImExternalServiceSms, consts.ImExternalServicePush, consts.ImExternalServiceTurn, consts.ImExternalServiceStorage, consts.ImExternalServiceModeration, } for _, serviceName := range services { count, err := imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx). Where("tenant_id", tenantId).Where("service", serviceName).Count() if err != nil { return gerror.Wrap(err, "初始化IM外部服务配置失败") } if count > 0 { continue } _, err = imtenant.RegistryDB().Exec(ctx, `INSERT INTO im_external_config (tenant_id,service,provider,endpoint,enabled,status,config_json,secret_json,created_by,updated_by) VALUES(?,?,?,?,?,?,CAST(? AS JSON),CAST(? AS JSON),?,?)`, tenantId, serviceName, "", "", 0, consts.ImExternalConfigStatusUnconfigured, "{}", "{}", contexts.GetUserId(ctx), contexts.GetUserId(ctx)) if err != nil { return gerror.Wrap(err, "初始化IM外部服务配置失败") } } return nil } func (s *sSysImExternalConfig) validService(serviceName string) bool { switch serviceName { case consts.ImExternalServiceSms, consts.ImExternalServicePush, consts.ImExternalServiceTurn, consts.ImExternalServiceStorage, consts.ImExternalServiceModeration: return true default: return false } } func externalConfigJson(value string) *gjson.Json { if value == "" { return gjson.New("{}") } return gjson.New(value) }