feat: add tenant content moderation controls
This commit is contained in:
@@ -5,16 +5,22 @@ import (
|
||||
"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{}
|
||||
@@ -36,7 +42,13 @@ func (s *sSysImExternalConfig) List(ctx context.Context, in *sysin.ImExternalCon
|
||||
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)
|
||||
}
|
||||
@@ -68,6 +80,9 @@ func (s *sSysImExternalConfig) View(ctx context.Context, in *sysin.ImExternalCon
|
||||
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外部服务配置详情失败,请稍后重试!")
|
||||
}
|
||||
@@ -91,6 +106,36 @@ func (s *sSysImExternalConfig) Update(ctx context.Context, in *sysin.ImExternalC
|
||||
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")
|
||||
}
|
||||
if strings.TrimSpace(in.Endpoint) == "" {
|
||||
in.Endpoint = "green-cip.cn-shanghai.aliyuncs.com"
|
||||
}
|
||||
if in.ConfigJson == nil {
|
||||
in.ConfigJson = gjson.New(map[string]any{"region_id": "cn-shanghai", "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("内容审核异常策略不合法")
|
||||
}
|
||||
if in.SecretJson != nil && in.SecretJson.String() != "" && in.SecretJson.String() != "null" && in.SecretJson.String() != "{}" {
|
||||
secret := in.SecretJson.Map()
|
||||
if strings.TrimSpace(gconv.String(secret["access_key_id"])) == "" || strings.TrimSpace(gconv.String(secret["access_key_secret"])) == "" {
|
||||
return gerror.New("AccessKey ID和AccessKey Secret必须同时填写")
|
||||
}
|
||||
}
|
||||
}
|
||||
configJson := "{}"
|
||||
if in.ConfigJson != nil && in.ConfigJson.String() != "" && in.ConfigJson.String() != "null" {
|
||||
configJson = in.ConfigJson.String()
|
||||
@@ -113,6 +158,9 @@ func (s *sSysImExternalConfig) Update(ctx context.Context, in *sysin.ImExternalC
|
||||
_ = 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
|
||||
}
|
||||
|
||||
@@ -127,6 +175,12 @@ func (s *sSysImExternalConfig) Check(ctx context.Context, in *sysin.ImExternalCo
|
||||
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 {
|
||||
@@ -137,6 +191,8 @@ func (s *sSysImExternalConfig) Check(ctx context.Context, in *sysin.ImExternalCo
|
||||
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).
|
||||
@@ -150,6 +206,43 @@ func (s *sSysImExternalConfig) Check(ctx context.Context, in *sysin.ImExternalCo
|
||||
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{}
|
||||
secret := map[string]any{}
|
||||
_ = json.Unmarshal([]byte(row["config_json"].String()), &config)
|
||||
_ = json.Unmarshal([]byte(row["secret_json"].String()), &secret)
|
||||
timeoutMS := gconv.Int(config["timeout_ms"])
|
||||
if timeoutMS <= 0 {
|
||||
timeoutMS = 1500
|
||||
}
|
||||
check, err := immoderation.CheckAliyunText(ctx, immoderation.AliyunConfig{
|
||||
AccessKeyID: gconv.String(secret["access_key_id"]), AccessKeySecret: gconv.String(secret["access_key_secret"]),
|
||||
RegionID: firstExternalValue(gconv.String(config["region_id"]), "cn-shanghai"), Endpoint: row["endpoint"].String(),
|
||||
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",
|
||||
|
||||
Reference in New Issue
Block a user