feat: add tenant content moderation controls
This commit is contained in:
@@ -0,0 +1,30 @@
|
|||||||
|
-- 商户级内容审核开关。
|
||||||
|
-- 可重复执行;新商户默认关闭,现有 default 商户保持当前审核行为。
|
||||||
|
|
||||||
|
SET @im_sql = IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config'
|
||||||
|
AND COLUMN_NAME = 'aliyun_moderation_enabled') = 0,
|
||||||
|
'ALTER TABLE `im_tenant_config` ADD COLUMN `aliyun_moderation_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否启用阿里云内容审核'' AFTER `max_group_members`',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE im_stmt FROM @im_sql;
|
||||||
|
EXECUTE im_stmt;
|
||||||
|
DEALLOCATE PREPARE im_stmt;
|
||||||
|
|
||||||
|
SET @im_sql = IF(
|
||||||
|
(SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||||
|
WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'im_tenant_config'
|
||||||
|
AND COLUMN_NAME = 'local_moderation_enabled') = 0,
|
||||||
|
'ALTER TABLE `im_tenant_config` ADD COLUMN `local_moderation_enabled` TINYINT(1) NOT NULL DEFAULT 0 COMMENT ''是否启用本地内容审核'' AFTER `aliyun_moderation_enabled`',
|
||||||
|
'SELECT 1'
|
||||||
|
);
|
||||||
|
PREPARE im_stmt FROM @im_sql;
|
||||||
|
EXECUTE im_stmt;
|
||||||
|
DEALLOCATE PREPARE im_stmt;
|
||||||
|
|
||||||
|
UPDATE `im_tenant_config` c
|
||||||
|
JOIN `im_tenant` t ON t.`id` = c.`tenant_id`
|
||||||
|
SET c.`aliyun_moderation_enabled` = 1,
|
||||||
|
c.`local_moderation_enabled` = 1
|
||||||
|
WHERE t.`code` = 'default';
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -58,6 +58,64 @@ type ThirdPartyRes struct {
|
|||||||
Values map[string]string `json:"values" dc:"配置值"`
|
Values map[string]string `json:"values" dc:"配置值"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ModerationProviderReq struct {
|
||||||
|
g.Meta `path:"/internal/im-config/moderation/provider" method:"get" tags:"IM内部配置" summary:"获取内容审核服务配置"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModerationProviderRes struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
RegionID string `json:"region_id"`
|
||||||
|
NicknameService string `json:"nickname_service"`
|
||||||
|
ChatService string `json:"chat_service"`
|
||||||
|
TimeoutMS int `json:"timeout_ms"`
|
||||||
|
FailureMode string `json:"failure_mode"`
|
||||||
|
AccessKeyID string `json:"access_key_id"`
|
||||||
|
AccessKeySecret string `json:"access_key_secret"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModerationPolicyReq struct {
|
||||||
|
g.Meta `path:"/internal/im-config/tenants/{code}/moderation-policy" method:"get" tags:"IM内部配置" summary:"获取商户内容审核策略"`
|
||||||
|
Code string `json:"code" in:"path" dc:"商户编码"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ModerationPolicyRes struct {
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
TenantCode string `json:"tenant_code"`
|
||||||
|
PolicyVersion uint `json:"policy_version"`
|
||||||
|
Enabled bool `json:"enabled"`
|
||||||
|
AliyunEnabled bool `json:"aliyun_enabled"`
|
||||||
|
LocalWordsEnabled bool `json:"local_words_enabled"`
|
||||||
|
Scenes map[string]bool `json:"scenes"`
|
||||||
|
RiskActions map[string]string `json:"risk_actions"`
|
||||||
|
FailureMode string `json:"failure_mode"`
|
||||||
|
BlockMessage string `json:"block_message"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SensitiveWordsReq struct {
|
||||||
|
g.Meta `path:"/internal/im-config/tenants/{code}/sensitive-words" method:"get" tags:"IM内部配置" summary:"获取商户有效敏感词"`
|
||||||
|
Code string `json:"code" in:"path" dc:"商户编码"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SensitiveWordsRes struct {
|
||||||
|
TenantID int64 `json:"tenant_id"`
|
||||||
|
TenantCode string `json:"tenant_code"`
|
||||||
|
Version int64 `json:"version"`
|
||||||
|
Items []*SensitiveWordModel `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SensitiveWordModel struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Word string `json:"word"`
|
||||||
|
MatchType string `json:"match_type"`
|
||||||
|
Scenes []string `json:"scenes"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Replacement string `json:"replacement"`
|
||||||
|
RiskLevel string `json:"risk_level"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
}
|
||||||
|
|
||||||
type TenantLoginIPWhitelistReq struct {
|
type TenantLoginIPWhitelistReq struct {
|
||||||
g.Meta `path:"/internal/im-config/tenants/by-id/{id}/login-ip-whitelist" method:"get" tags:"IM内部配置" summary:"获取租户渠道登录IP白名单"`
|
g.Meta `path:"/internal/im-config/tenants/by-id/{id}/login-ip-whitelist" method:"get" tags:"IM内部配置" summary:"获取租户渠道登录IP白名单"`
|
||||||
Id int64 `json:"id" in:"path" dc:"租户ID"`
|
Id int64 `json:"id" in:"path" dc:"租户ID"`
|
||||||
|
|||||||
+4
-3
@@ -4,10 +4,11 @@ go 1.24.4
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/IBM/sarama v1.45.2
|
github.com/IBM/sarama v1.45.2
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.4
|
||||||
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6
|
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6
|
||||||
github.com/alibabacloud-go/tea v1.3.9
|
github.com/alibabacloud-go/green-20220302/v3 v3.5.4
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7
|
github.com/alibabacloud-go/tea v1.5.2
|
||||||
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9
|
||||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||||
github.com/apache/rocketmq-client-go/v2 v2.1.2
|
github.com/apache/rocketmq-client-go/v2 v2.1.2
|
||||||
github.com/casbin/casbin/v2 v2.108.0
|
github.com/casbin/casbin/v2 v2.108.0
|
||||||
|
|||||||
+8
-7
@@ -23,8 +23,8 @@ github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F
|
|||||||
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc=
|
||||||
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.0.2/go.mod h1:5JHVmnHvGzR2wNdgaW1zDLQG8kOC4Uec8ubkMogW7OQ=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7 h1:ASXSBga98QrGMxbIThCD6jAti09gedLfvry6yJtsoBE=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.4 h1:o6veen0IZ/Fe1JawwhwQMZcbw67CVDY1pQwXcNWPyQo=
|
||||||
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.7/go.mod h1:TBpgqm3XofZz2LCYjZhektGPU7ArEgascyzbm4SjFo4=
|
github.com/alibabacloud-go/darabonba-openapi/v2 v2.2.4/go.mod h1:eHjVxrT9g8uVYN/nyAwOFQEfkVA154ChiqRc2XnNKYU=
|
||||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg=
|
||||||
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ=
|
||||||
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo=
|
||||||
@@ -37,6 +37,8 @@ github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6 h1:UTl97mt2qfavxveqCkaVg4
|
|||||||
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6/go.mod h1:UWpcGrWwTbES9QW7OQ7xDffukMJ/l7lzioixIz8+lgY=
|
github.com/alibabacloud-go/dysmsapi-20170525/v3 v3.0.6/go.mod h1:UWpcGrWwTbES9QW7OQ7xDffukMJ/l7lzioixIz8+lgY=
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q=
|
||||||
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE=
|
||||||
|
github.com/alibabacloud-go/green-20220302/v3 v3.5.4 h1:ZnlHri7tTLl28JWB5S2J6MYvD0ewJFI7hedK4E7+gSk=
|
||||||
|
github.com/alibabacloud-go/green-20220302/v3 v3.5.4/go.mod h1:PQTtoEuWOsuq7H7sgTRcXDw4Ut/Hekgy5z2mWGtO9OQ=
|
||||||
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
github.com/alibabacloud-go/openapi-util v0.0.11/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
github.com/alibabacloud-go/openapi-util v0.1.0 h1:0z75cIULkDrdEhkLWgi9tnLe+KhAFE/r5Pb3312/eAY=
|
||||||
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws=
|
||||||
@@ -48,16 +50,15 @@ github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy
|
|||||||
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.19/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A=
|
||||||
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk=
|
||||||
github.com/alibabacloud-go/tea v1.3.8/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
github.com/alibabacloud-go/tea v1.5.2 h1:DUzV/cRTbJC++rmADfqVu//GuWbaD64ZJ3yW3v2ukNU=
|
||||||
github.com/alibabacloud-go/tea v1.3.9 h1:bjgt1bvdY780vz/17iWNNtbXl4A77HWntWMeaUF3So0=
|
github.com/alibabacloud-go/tea v1.5.2/go.mod h1:hgSs82CkOiehSQMoiFN79dL6zsGX7pVGvnn9SIEs8/0=
|
||||||
github.com/alibabacloud-go/tea v1.3.9/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg=
|
|
||||||
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
|
github.com/alibabacloud-go/tea-utils v1.3.1 h1:iWQeRzRheqCMuiF3+XkfybB3kTgUXkXX+JMrqfLeB2I=
|
||||||
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.0/go.mod h1:U5MTY10WwlquGPS34DOeomUGBB0gXbLueiq5Trwu0C4=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.3/go.mod h1:sj1PbjPodAVTqGTA3olprfeeqqmwD0A5OQz94o9EuXQ=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9 h1:y6pUIlhjxbZl9ObDAcmA1H3c21eaAxADHTDQmBnAIgA=
|
||||||
github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
github.com/alibabacloud-go/tea-utils/v2 v2.0.9/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I=
|
||||||
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
github.com/alibabacloud-go/tea-xml v1.1.2/go.mod h1:Rq08vgCcCAjHyRi/M7xlHKUykZCEtyBy9+DPF6GgEu8=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
|
||||||
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
|
||||||
|
|||||||
@@ -2,7 +2,9 @@ package imconfig
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
api "hotgo/api/admin/imconfiginternal"
|
api "hotgo/api/admin/imconfiginternal"
|
||||||
|
"hotgo/internal/consts"
|
||||||
"hotgo/internal/dao"
|
"hotgo/internal/dao"
|
||||||
"hotgo/internal/library/imtenant"
|
"hotgo/internal/library/imtenant"
|
||||||
"hotgo/internal/model/entity"
|
"hotgo/internal/model/entity"
|
||||||
@@ -14,6 +16,18 @@ import (
|
|||||||
"github.com/gogf/gf/v2/util/gconv"
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const defaultModerationTenantCode = "default"
|
||||||
|
|
||||||
|
var defaultModerationScenes = map[string]bool{
|
||||||
|
"user_nickname": true, "user_signature": true, "group_name": true,
|
||||||
|
"group_nickname": true, "private_message": true, "group_message": true,
|
||||||
|
"group_announcement": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultModerationRiskActions = map[string]string{
|
||||||
|
"high": "block", "medium": "block", "low": "audit_only", "none": "allow",
|
||||||
|
}
|
||||||
|
|
||||||
var Config = cConfig{}
|
var Config = cConfig{}
|
||||||
|
|
||||||
type cConfig struct{}
|
type cConfig struct{}
|
||||||
@@ -91,6 +105,177 @@ func (c *cConfig) ThirdParty(ctx context.Context, req *api.ThirdPartyReq) (res *
|
|||||||
return &api.ThirdPartyRes{Values: values}, nil
|
return &api.ThirdPartyRes{Values: values}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *cConfig) ModerationProvider(ctx context.Context, req *api.ModerationProviderReq) (res *api.ModerationProviderRes, err error) {
|
||||||
|
tenant, err := getTenant(ctx, dao.ImTenant.Columns().Code, defaultModerationTenantCode)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var row struct {
|
||||||
|
Provider string `json:"provider"`
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Enabled int `json:"enabled"`
|
||||||
|
ConfigJSON string `json:"config_json"`
|
||||||
|
SecretJSON string `json:"secret_json"`
|
||||||
|
}
|
||||||
|
if err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).
|
||||||
|
Fields("provider", "endpoint", "enabled", "CAST(config_json AS CHAR) config_json", "CAST(secret_json AS CHAR) secret_json").
|
||||||
|
Where("tenant_id", tenant.Id).Where("service", consts.ImExternalServiceModeration).Scan(&row); err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "获取内容审核服务配置失败")
|
||||||
|
}
|
||||||
|
config := map[string]any{}
|
||||||
|
secret := map[string]any{}
|
||||||
|
_ = json.Unmarshal([]byte(row.ConfigJSON), &config)
|
||||||
|
_ = json.Unmarshal([]byte(row.SecretJSON), &secret)
|
||||||
|
res = &api.ModerationProviderRes{
|
||||||
|
Provider: row.Provider, Endpoint: row.Endpoint, Enabled: row.Enabled == 1,
|
||||||
|
RegionID: stringValue(config, "region_id", "cn-shanghai"),
|
||||||
|
NicknameService: stringValue(config, "nickname_service", "nickname_detection_pro"),
|
||||||
|
ChatService: stringValue(config, "chat_service", "chat_detection_pro"),
|
||||||
|
TimeoutMS: intValue(config, "timeout_ms", 1500),
|
||||||
|
FailureMode: moderationFailureMode(stringValue(config, "failure_mode", "allow")),
|
||||||
|
AccessKeyID: stringValue(secret, "access_key_id", ""),
|
||||||
|
AccessKeySecret: stringValue(secret, "access_key_secret", ""),
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cConfig) ModerationPolicy(ctx context.Context, req *api.ModerationPolicyReq) (res *api.ModerationPolicyRes, err error) {
|
||||||
|
tenant, err := getTenant(ctx, dao.ImTenant.Columns().Code, strings.TrimSpace(req.Code))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = defaultModerationPolicy(tenant)
|
||||||
|
var tenantConfig struct {
|
||||||
|
AliyunModerationEnabled int `json:"aliyun_moderation_enabled"`
|
||||||
|
LocalModerationEnabled int `json:"local_moderation_enabled"`
|
||||||
|
}
|
||||||
|
if err = imtenant.RegistryDB().Model(dao.ImTenantConfig.Table()).Safe().Ctx(ctx).
|
||||||
|
Fields("aliyun_moderation_enabled", "local_moderation_enabled").
|
||||||
|
Where(dao.ImTenantConfig.Columns().TenantId, tenant.Id).Scan(&tenantConfig); err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "获取商户内容审核开关失败")
|
||||||
|
}
|
||||||
|
res.AliyunEnabled = tenantConfig.AliyunModerationEnabled == 1
|
||||||
|
res.LocalWordsEnabled = tenantConfig.LocalModerationEnabled == 1
|
||||||
|
res.Enabled = res.AliyunEnabled || res.LocalWordsEnabled
|
||||||
|
columns := dao.ImSecurityPolicy.Columns()
|
||||||
|
var row struct {
|
||||||
|
PolicyVersion uint `json:"policy_version"`
|
||||||
|
SensitiveWordPolicy string `json:"sensitive_word_policy"`
|
||||||
|
}
|
||||||
|
if err = imtenant.RegistryDB().Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).
|
||||||
|
Fields(columns.PolicyVersion, "CAST("+columns.SensitiveWordPolicy+" AS CHAR) sensitive_word_policy").
|
||||||
|
Where(columns.TenantId, tenant.Id).Where(columns.PublishStatus, consts.ImSecurityPublishPublished).
|
||||||
|
WhereNull(columns.DeletedAt).OrderDesc(columns.PolicyVersion).Limit(1).Scan(&row); err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "获取商户内容审核策略失败")
|
||||||
|
}
|
||||||
|
if row.PolicyVersion == 0 || strings.TrimSpace(row.SensitiveWordPolicy) == "" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
var policy struct {
|
||||||
|
Scenes map[string]bool `json:"scenes"`
|
||||||
|
RiskActions map[string]string `json:"risk_actions"`
|
||||||
|
FailureMode string `json:"failure_mode"`
|
||||||
|
BlockMessage string `json:"block_message"`
|
||||||
|
}
|
||||||
|
if err = json.Unmarshal([]byte(row.SensitiveWordPolicy), &policy); err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "商户内容审核策略格式错误")
|
||||||
|
}
|
||||||
|
res.PolicyVersion = row.PolicyVersion
|
||||||
|
for scene, enabled := range policy.Scenes {
|
||||||
|
res.Scenes[scene] = enabled
|
||||||
|
}
|
||||||
|
for risk, action := range policy.RiskActions {
|
||||||
|
if action == "allow" || action == "block" || action == "audit_only" {
|
||||||
|
res.RiskActions[risk] = action
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res.FailureMode = moderationFailureMode(policy.FailureMode)
|
||||||
|
if strings.TrimSpace(policy.BlockMessage) != "" {
|
||||||
|
res.BlockMessage = strings.TrimSpace(policy.BlockMessage)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *cConfig) SensitiveWords(ctx context.Context, req *api.SensitiveWordsReq) (res *api.SensitiveWordsRes, err error) {
|
||||||
|
tenant, err := getTenant(ctx, dao.ImTenant.Columns().Code, strings.TrimSpace(req.Code))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
res = &api.SensitiveWordsRes{TenantID: tenant.Id, TenantCode: tenant.Code, Items: []*api.SensitiveWordModel{}}
|
||||||
|
columns := dao.ImSensitiveWords.Columns()
|
||||||
|
groupColumns := dao.ImSensitiveWordGroups.Columns()
|
||||||
|
var rows []struct {
|
||||||
|
ID int64 `json:"id"`
|
||||||
|
Word string `json:"word"`
|
||||||
|
MatchType string `json:"match_type"`
|
||||||
|
SceneJSON string `json:"scene_json"`
|
||||||
|
Action string `json:"action"`
|
||||||
|
Replacement string `json:"replacement"`
|
||||||
|
RiskLevel string `json:"risk_level"`
|
||||||
|
Priority int `json:"priority"`
|
||||||
|
UpdatedAt *gtime.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
err = imtenant.RegistryDB().Model(dao.ImSensitiveWords.Table()+" w").Safe().Ctx(ctx).
|
||||||
|
LeftJoin(dao.ImSensitiveWordGroups.Table()+" g", "g."+groupColumns.Id+"=w."+columns.GroupId).
|
||||||
|
Fields("w."+columns.Id, "w."+columns.Word, "w."+columns.MatchType, "CAST(w."+columns.SceneJson+" AS CHAR) scene_json", "w."+columns.Action, "w."+columns.Replacement, "w."+columns.RiskLevel, "w."+columns.Priority, "w."+columns.UpdatedAt).
|
||||||
|
Where("w."+columns.TenantId, tenant.Id).Where("w."+columns.Status, consts.StatusEnabled).WhereNull("w."+columns.DeletedAt).
|
||||||
|
Where("g."+groupColumns.Status, consts.StatusEnabled).WhereNull("g." + groupColumns.DeletedAt).
|
||||||
|
OrderDesc("w." + columns.Priority).OrderAsc("w." + columns.Id).Scan(&rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, gerror.Wrap(err, "获取商户有效敏感词失败")
|
||||||
|
}
|
||||||
|
for _, row := range rows {
|
||||||
|
var scenes []string
|
||||||
|
_ = json.Unmarshal([]byte(row.SceneJSON), &scenes)
|
||||||
|
res.Items = append(res.Items, &api.SensitiveWordModel{ID: row.ID, Word: row.Word, MatchType: row.MatchType, Scenes: scenes, Action: row.Action, Replacement: row.Replacement, RiskLevel: row.RiskLevel, Priority: row.Priority})
|
||||||
|
if row.UpdatedAt != nil && row.UpdatedAt.TimestampMilli() > res.Version {
|
||||||
|
res.Version = row.UpdatedAt.TimestampMilli()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func defaultModerationPolicy(tenant *entity.ImTenant) *api.ModerationPolicyRes {
|
||||||
|
return &api.ModerationPolicyRes{TenantID: tenant.Id, TenantCode: tenant.Code, Enabled: false,
|
||||||
|
AliyunEnabled: false, LocalWordsEnabled: false, Scenes: cloneBoolMap(defaultModerationScenes),
|
||||||
|
RiskActions: cloneStringMap(defaultModerationRiskActions), FailureMode: "allow",
|
||||||
|
BlockMessage: "内容不符合平台规范,请修改后重试"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneBoolMap(src map[string]bool) map[string]bool {
|
||||||
|
dst := make(map[string]bool, len(src))
|
||||||
|
for k, v := range src {
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
func cloneStringMap(src map[string]string) map[string]string {
|
||||||
|
dst := make(map[string]string, len(src))
|
||||||
|
for k, v := range src {
|
||||||
|
dst[k] = v
|
||||||
|
}
|
||||||
|
return dst
|
||||||
|
}
|
||||||
|
func stringValue(values map[string]any, key, fallback string) string {
|
||||||
|
if value, ok := values[key].(string); ok && strings.TrimSpace(value) != "" {
|
||||||
|
return strings.TrimSpace(value)
|
||||||
|
}
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
func intValue(values map[string]any, key string, fallback int) int {
|
||||||
|
value := gconv.Int(values[key])
|
||||||
|
if value <= 0 {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
func moderationFailureMode(value string) string {
|
||||||
|
if strings.TrimSpace(value) == "block" {
|
||||||
|
return "block"
|
||||||
|
}
|
||||||
|
return "allow"
|
||||||
|
}
|
||||||
|
|
||||||
func (c *cConfig) TenantLoginIPWhitelist(ctx context.Context, req *api.TenantLoginIPWhitelistReq) (res *api.TenantLoginIPWhitelistRes, err error) {
|
func (c *cConfig) TenantLoginIPWhitelist(ctx context.Context, req *api.TenantLoginIPWhitelistReq) (res *api.TenantLoginIPWhitelistRes, err error) {
|
||||||
if _, err = getTenant(ctx, dao.ImTenant.Columns().Id, req.Id); err != nil {
|
if _, err = getTenant(ctx, dao.ImTenant.Columns().Id, req.Id); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package immoderation
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
openapi "github.com/alibabacloud-go/darabonba-openapi/v2/client"
|
||||||
|
green "github.com/alibabacloud-go/green-20220302/v3/client"
|
||||||
|
"github.com/alibabacloud-go/tea/dara"
|
||||||
|
)
|
||||||
|
|
||||||
|
type AliyunConfig struct {
|
||||||
|
AccessKeyID string
|
||||||
|
AccessKeySecret string
|
||||||
|
RegionID string
|
||||||
|
Endpoint string
|
||||||
|
Service string
|
||||||
|
Timeout time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type CheckResult struct {
|
||||||
|
RequestID string
|
||||||
|
Latency time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckAliyunText(ctx context.Context, config AliyunConfig) (*CheckResult, error) {
|
||||||
|
if strings.TrimSpace(config.AccessKeyID) == "" || strings.TrimSpace(config.AccessKeySecret) == "" {
|
||||||
|
return nil, errors.New("AccessKey ID或AccessKey Secret未配置")
|
||||||
|
}
|
||||||
|
clientConfig := &openapi.Config{AccessKeyId: dara.String(config.AccessKeyID), AccessKeySecret: dara.String(config.AccessKeySecret), RegionId: dara.String(config.RegionID), Protocol: dara.String("https")}
|
||||||
|
if endpoint := strings.TrimSpace(config.Endpoint); endpoint != "" {
|
||||||
|
clientConfig.Endpoint = dara.String(endpoint)
|
||||||
|
}
|
||||||
|
client, err := green.NewClient(clientConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if config.Timeout <= 0 {
|
||||||
|
config.Timeout = 1500 * time.Millisecond
|
||||||
|
}
|
||||||
|
ctx, cancel := context.WithTimeout(ctx, config.Timeout)
|
||||||
|
defer cancel()
|
||||||
|
params, _ := json.Marshal(map[string]string{"content": "内容审核连通性测试", "dataId": "admin-config-check"})
|
||||||
|
started := time.Now()
|
||||||
|
resp, err := client.TextModerationPlusWithContext(ctx, &green.TextModerationPlusRequest{Service: dara.String(config.Service), ServiceParameters: dara.String(string(params))}, &dara.RuntimeOptions{})
|
||||||
|
latency := time.Since(started)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if resp == nil || resp.Body == nil || dara.Int32Value(resp.Body.Code) != 200 {
|
||||||
|
return nil, errors.New("阿里云内容审核返回失败")
|
||||||
|
}
|
||||||
|
return &CheckResult{RequestID: dara.StringValue(resp.Body.RequestId), Latency: latency}, nil
|
||||||
|
}
|
||||||
@@ -5,16 +5,22 @@ import (
|
|||||||
"hotgo/internal/consts"
|
"hotgo/internal/consts"
|
||||||
"hotgo/internal/dao"
|
"hotgo/internal/dao"
|
||||||
"hotgo/internal/library/contexts"
|
"hotgo/internal/library/contexts"
|
||||||
|
"hotgo/internal/library/immoderation"
|
||||||
"hotgo/internal/library/imtenant"
|
"hotgo/internal/library/imtenant"
|
||||||
"hotgo/internal/model/entity"
|
"hotgo/internal/model/entity"
|
||||||
"hotgo/internal/model/input/sysin"
|
"hotgo/internal/model/input/sysin"
|
||||||
"hotgo/internal/service"
|
"hotgo/internal/service"
|
||||||
|
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"github.com/gogf/gf/v2/database/gdb"
|
"github.com/gogf/gf/v2/database/gdb"
|
||||||
"github.com/gogf/gf/v2/encoding/gjson"
|
"github.com/gogf/gf/v2/encoding/gjson"
|
||||||
"github.com/gogf/gf/v2/errors/gerror"
|
"github.com/gogf/gf/v2/errors/gerror"
|
||||||
"github.com/gogf/gf/v2/frame/g"
|
"github.com/gogf/gf/v2/frame/g"
|
||||||
"github.com/gogf/gf/v2/os/gtime"
|
"github.com/gogf/gf/v2/os/gtime"
|
||||||
|
"github.com/gogf/gf/v2/util/gconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type sSysImExternalConfig struct{}
|
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 {
|
if err = s.ensureRows(ctx, tenant.Id); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if in.Service == consts.ImExternalServiceModeration && tenant.Code != "default" {
|
||||||
|
return nil, 0, gerror.New("第一阶段仅默认商户可配置内容审核服务")
|
||||||
|
}
|
||||||
mod := s.model(ctx).Where("tenant_id", tenant.Id)
|
mod := s.model(ctx).Where("tenant_id", tenant.Id)
|
||||||
|
if tenant.Code != "default" {
|
||||||
|
mod = mod.WhereNot("service", consts.ImExternalServiceModeration)
|
||||||
|
}
|
||||||
if in.Service != "" {
|
if in.Service != "" {
|
||||||
mod = mod.Where("service", 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 {
|
if err = s.ensureRows(ctx, tenant.Id); err != nil {
|
||||||
return nil, err
|
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 {
|
if err = s.model(ctx).Where("tenant_id", tenant.Id).Where("service", in.Service).Scan(&res); err != nil {
|
||||||
return nil, gerror.Wrap(err, "获取IM外部服务配置详情失败,请稍后重试!")
|
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 {
|
if err = s.ensureRows(ctx, tenant.Id); err != nil {
|
||||||
return err
|
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 := "{}"
|
configJson := "{}"
|
||||||
if in.ConfigJson != nil && in.ConfigJson.String() != "" && in.ConfigJson.String() != "null" {
|
if in.ConfigJson != nil && in.ConfigJson.String() != "" && in.ConfigJson.String() != "null" {
|
||||||
configJson = in.ConfigJson.String()
|
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{
|
_ = writeImAdminAudit(ctx, tenant.Id, "im_external_config_update", "im_external_config", 0, "success", g.Map{
|
||||||
"service": in.Service, "provider": in.Provider, "enabled": in.Enabled,
|
"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
|
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 {
|
if err = s.ensureRows(ctx, tenant.Id); err != nil {
|
||||||
return err
|
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
|
var row gdb.Record
|
||||||
if row, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).
|
if row, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).
|
||||||
Where("tenant_id", tenant.Id).Where("service", in.Service).One(); err != nil {
|
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() == "" {
|
if row == nil || row["enabled"].Int() != 1 || row["provider"].String() == "" {
|
||||||
status = consts.ImExternalConfigStatusUnconfigured
|
status = consts.ImExternalConfigStatusUnconfigured
|
||||||
result = "未启用或服务商为空"
|
result = "未启用或服务商为空"
|
||||||
|
} else if in.Service == consts.ImExternalServiceModeration {
|
||||||
|
status, result = s.checkModeration(ctx, row)
|
||||||
}
|
}
|
||||||
if _, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).
|
if _, err = imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).
|
||||||
Where("tenant_id", tenant.Id).Where("service", in.Service).
|
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
|
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 {
|
func (s *sSysImExternalConfig) model(ctx context.Context) *gdb.Model {
|
||||||
return imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).Fields(
|
return imtenant.RegistryDB().Model("im_external_config").Safe().Ctx(ctx).Fields(
|
||||||
"tenant_id",
|
"tenant_id",
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol
|
|||||||
if err = in.Filter(ctx); err != nil {
|
if err = in.Filter(ctx); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
tenantID, _, err := resolveImPolicyTenant(ctx, in.TenantId)
|
tenantID, tenantCode, err := resolveImPolicyTenant(ctx, in.TenantId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
@@ -123,7 +123,7 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol
|
|||||||
}
|
}
|
||||||
version = uint(maxValue) + 1
|
version = uint(maxValue) + 1
|
||||||
}
|
}
|
||||||
data := g.Map{c.TenantId: tenantID, c.PolicyVersion: version, c.PolicyName: in.PolicyName, c.PublishStatus: consts.ImSecurityPublishDraft, c.PasswordPolicy: policyJSON(in.PasswordPolicy, "{}"), c.LoginRateLimit: policyJSON(in.LoginRateLimit, "{}"), c.SensitiveWordPolicy: policyJSON(in.SensitiveWordPolicy, "{}"), c.FileReviewPolicy: policyJSON(in.FileReviewPolicy, "{}"), c.RealnamePolicy: policyJSON(in.RealnamePolicy, "{}"), c.RiskChallengePolicy: policyJSON(in.RiskChallengePolicy, "{}"), c.GlobalFriendEnabled: in.GlobalFriendEnabled, c.FriendDisabledPolicy: policyJSON(in.FriendDisabledPolicy, `{"hide_profile":true,"block_qrcode":true,"block_group_member_entry":true,"blocked_entries":["search","profile_card","group_member","qrcode","invite_auto_add","recommendation"],"message":"当前时段暂不允许添加好友"}`), c.MessageCurfewEnabled: in.MessageCurfewEnabled, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: operator}
|
data := g.Map{c.TenantId: tenantID, c.PolicyVersion: version, c.PolicyName: in.PolicyName, c.PublishStatus: consts.ImSecurityPublishDraft, c.PasswordPolicy: policyJSON(in.PasswordPolicy, "{}"), c.LoginRateLimit: policyJSON(in.LoginRateLimit, "{}"), c.SensitiveWordPolicy: normalizedModerationPolicy(in.SensitiveWordPolicy, tenantCode), c.FileReviewPolicy: policyJSON(in.FileReviewPolicy, "{}"), c.RealnamePolicy: policyJSON(in.RealnamePolicy, "{}"), c.RiskChallengePolicy: policyJSON(in.RiskChallengePolicy, "{}"), c.GlobalFriendEnabled: in.GlobalFriendEnabled, c.FriendDisabledPolicy: policyJSON(in.FriendDisabledPolicy, `{"hide_profile":true,"block_qrcode":true,"block_group_member_entry":true,"blocked_entries":["search","profile_card","group_member","qrcode","invite_auto_add","recommendation"],"message":"当前时段暂不允许添加好友"}`), c.MessageCurfewEnabled: in.MessageCurfewEnabled, c.Remark: in.Remark, c.UpdatedAt: now, c.UpdatedBy: operator}
|
||||||
if in.Id > 0 {
|
if in.Id > 0 {
|
||||||
if _, updateErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update(); updateErr != nil {
|
if _, updateErr := tx.Model(dao.ImSecurityPolicy.Table()).Safe().Ctx(ctx).Where(c.Id, in.Id).Data(data).Update(); updateErr != nil {
|
||||||
return updateErr
|
return updateErr
|
||||||
@@ -158,6 +158,20 @@ func (s *sSysImSecurityPolicy) Save(ctx context.Context, in *sysin.ImSecurityPol
|
|||||||
return id, nil
|
return id, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func normalizedModerationPolicy(value *gjson.Json, _ string) string {
|
||||||
|
policy := map[string]any{
|
||||||
|
"scenes": map[string]bool{"user_nickname": true, "user_signature": true, "group_name": true, "group_nickname": true, "private_message": true, "group_message": true, "group_announcement": true},
|
||||||
|
"risk_actions": map[string]string{"high": "block", "medium": "block", "low": "audit_only", "none": "allow"},
|
||||||
|
"failure_mode": "allow", "block_message": "内容不符合平台规范,请修改后重试",
|
||||||
|
}
|
||||||
|
if value != nil && value.String() != "" && value.String() != "null" {
|
||||||
|
for key, item := range value.Map() {
|
||||||
|
policy[key] = item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return gjson.New(policy).String()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *sSysImSecurityPolicy) Publish(ctx context.Context, in *sysin.ImSecurityPolicyPublishInp) error {
|
func (s *sSysImSecurityPolicy) Publish(ctx context.Context, in *sysin.ImSecurityPolicyPublishInp) error {
|
||||||
if err := in.Filter(ctx); err != nil {
|
if err := in.Filter(ctx); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ import (
|
|||||||
|
|
||||||
type sSysImTenant struct{}
|
type sSysImTenant struct{}
|
||||||
|
|
||||||
|
const (
|
||||||
|
tenantConfigAliyunModerationEnabled = "aliyun_moderation_enabled"
|
||||||
|
tenantConfigLocalModerationEnabled = "local_moderation_enabled"
|
||||||
|
)
|
||||||
|
|
||||||
|
type imTenantConfigModeration struct {
|
||||||
|
Id int64 `json:"id"`
|
||||||
|
TenantId int64 `json:"tenant_id"`
|
||||||
|
MaxUsers uint64 `json:"max_users"`
|
||||||
|
MaxGroups uint64 `json:"max_groups"`
|
||||||
|
MaxGroupMembers uint `json:"max_group_members"`
|
||||||
|
AliyunModerationEnabled int `json:"aliyun_moderation_enabled"`
|
||||||
|
LocalModerationEnabled int `json:"local_moderation_enabled"`
|
||||||
|
}
|
||||||
|
|
||||||
func NewSysImTenant() *sSysImTenant {
|
func NewSysImTenant() *sSysImTenant {
|
||||||
return &sSysImTenant{}
|
return &sSysImTenant{}
|
||||||
}
|
}
|
||||||
@@ -104,13 +119,13 @@ func (s *sSysImTenant) fillListConfig(ctx context.Context, list []*sysin.ImTenan
|
|||||||
ids = append(ids, item.Id)
|
ids = append(ids, item.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
var configs []*entity.ImTenantConfig
|
var configs []*imTenantConfigModeration
|
||||||
if err = s.configModel(ctx).
|
if err = s.configModel(ctx).
|
||||||
WhereIn(dao.ImTenantConfig.Columns().TenantId, ids).
|
WhereIn(dao.ImTenantConfig.Columns().TenantId, ids).
|
||||||
Scan(&configs); err != nil {
|
Scan(&configs); err != nil {
|
||||||
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
|
return gerror.Wrap(err, "获取IM租户配置失败,请稍后重试!")
|
||||||
}
|
}
|
||||||
configMap := make(map[int64]*entity.ImTenantConfig, len(configs))
|
configMap := make(map[int64]*imTenantConfigModeration, len(configs))
|
||||||
for _, item := range configs {
|
for _, item := range configs {
|
||||||
configMap[item.TenantId] = item
|
configMap[item.TenantId] = item
|
||||||
}
|
}
|
||||||
@@ -132,6 +147,8 @@ func (s *sSysImTenant) fillListConfig(ctx context.Context, list []*sysin.ImTenan
|
|||||||
item.MaxUsers = conf.MaxUsers
|
item.MaxUsers = conf.MaxUsers
|
||||||
item.MaxGroups = conf.MaxGroups
|
item.MaxGroups = conf.MaxGroups
|
||||||
item.MaxGroupMembers = conf.MaxGroupMembers
|
item.MaxGroupMembers = conf.MaxGroupMembers
|
||||||
|
item.AliyunModerationEnabled = conf.AliyunModerationEnabled
|
||||||
|
item.LocalModerationEnabled = conf.LocalModerationEnabled
|
||||||
}
|
}
|
||||||
item.DbDsnConfigured = dsnMap[item.Id] != ""
|
item.DbDsnConfigured = dsnMap[item.Id] != ""
|
||||||
}
|
}
|
||||||
@@ -144,7 +161,8 @@ func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
tenantID := in.Id
|
||||||
|
err = imtenant.RegistryDB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
|
||||||
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
if err = s.checkCodeUnique(ctx, in.Id, in.Code); err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -157,7 +175,7 @@ func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err
|
|||||||
WherePri(in.Id).Data(in).Update(); err != nil {
|
WherePri(in.Id).Data(in).Update(); err != nil {
|
||||||
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
return gerror.Wrap(err, "修改IM租户失败,请稍后重试!")
|
||||||
}
|
}
|
||||||
return s.saveConfig(ctx, in.Id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId)
|
return s.saveConfig(ctx, in.Id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, in.AliyunModerationEnabled, in.LocalModerationEnabled, userId)
|
||||||
}
|
}
|
||||||
|
|
||||||
in.CreatedBy = userId
|
in.CreatedBy = userId
|
||||||
@@ -168,8 +186,14 @@ func (s *sSysImTenant) Edit(ctx context.Context, in *sysin.ImTenantEditInp) (err
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
|
return gerror.Wrap(err, "新增IM租户失败,请稍后重试!")
|
||||||
}
|
}
|
||||||
return s.saveConfig(ctx, id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, userId)
|
tenantID = id
|
||||||
|
return s.saveConfig(ctx, id, in.MaxUsers, in.MaxGroups, in.MaxGroupMembers, in.AliyunModerationEnabled, in.LocalModerationEnabled, userId)
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_ = publishImPolicyEvent(ctx, tenantID, in.Code, "tenant.features.updated", "moderation", 0)
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
|
func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code string) (err error) {
|
||||||
@@ -187,7 +211,7 @@ func (s *sSysImTenant) checkCodeUnique(ctx context.Context, id int64, code strin
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers uint64, maxGroups uint64, maxGroupMembers uint, userId int64) (err error) {
|
func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers uint64, maxGroups uint64, maxGroupMembers uint, aliyunModerationEnabled, localModerationEnabled int, userId int64) (err error) {
|
||||||
var conf *entity.ImTenantConfig
|
var conf *entity.ImTenantConfig
|
||||||
if err = s.configModel(ctx).
|
if err = s.configModel(ctx).
|
||||||
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).
|
Where(dao.ImTenantConfig.Columns().TenantId, tenantId).
|
||||||
@@ -199,6 +223,8 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
|
|||||||
dao.ImTenantConfig.Columns().MaxUsers: maxUsers,
|
dao.ImTenantConfig.Columns().MaxUsers: maxUsers,
|
||||||
dao.ImTenantConfig.Columns().MaxGroups: maxGroups,
|
dao.ImTenantConfig.Columns().MaxGroups: maxGroups,
|
||||||
dao.ImTenantConfig.Columns().MaxGroupMembers: maxGroupMembers,
|
dao.ImTenantConfig.Columns().MaxGroupMembers: maxGroupMembers,
|
||||||
|
tenantConfigAliyunModerationEnabled: aliyunModerationEnabled,
|
||||||
|
tenantConfigLocalModerationEnabled: localModerationEnabled,
|
||||||
}
|
}
|
||||||
if conf != nil && conf.Id > 0 {
|
if conf != nil && conf.Id > 0 {
|
||||||
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
|
data[dao.ImTenantConfig.Columns().UpdatedBy] = userId
|
||||||
@@ -207,6 +233,8 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
|
|||||||
dao.ImTenantConfig.Columns().MaxUsers,
|
dao.ImTenantConfig.Columns().MaxUsers,
|
||||||
dao.ImTenantConfig.Columns().MaxGroups,
|
dao.ImTenantConfig.Columns().MaxGroups,
|
||||||
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
||||||
|
tenantConfigAliyunModerationEnabled,
|
||||||
|
tenantConfigLocalModerationEnabled,
|
||||||
dao.ImTenantConfig.Columns().UpdatedBy,
|
dao.ImTenantConfig.Columns().UpdatedBy,
|
||||||
).
|
).
|
||||||
WherePri(conf.Id).Data(data).Update(); err != nil {
|
WherePri(conf.Id).Data(data).Update(); err != nil {
|
||||||
@@ -224,6 +252,8 @@ func (s *sSysImTenant) saveConfig(ctx context.Context, tenantId int64, maxUsers
|
|||||||
dao.ImTenantConfig.Columns().MaxUsers,
|
dao.ImTenantConfig.Columns().MaxUsers,
|
||||||
dao.ImTenantConfig.Columns().MaxGroups,
|
dao.ImTenantConfig.Columns().MaxGroups,
|
||||||
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
dao.ImTenantConfig.Columns().MaxGroupMembers,
|
||||||
|
tenantConfigAliyunModerationEnabled,
|
||||||
|
tenantConfigLocalModerationEnabled,
|
||||||
dao.ImTenantConfig.Columns().CreatedBy,
|
dao.ImTenantConfig.Columns().CreatedBy,
|
||||||
dao.ImTenantConfig.Columns().UpdatedBy,
|
dao.ImTenantConfig.Columns().UpdatedBy,
|
||||||
).
|
).
|
||||||
@@ -246,7 +276,7 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var conf *entity.ImTenantConfig
|
var conf *imTenantConfigModeration
|
||||||
if err = s.configModel(ctx).
|
if err = s.configModel(ctx).
|
||||||
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
|
Where(dao.ImTenantConfig.Columns().TenantId, in.Id).
|
||||||
Scan(&conf); err != nil {
|
Scan(&conf); err != nil {
|
||||||
@@ -257,6 +287,8 @@ func (s *sSysImTenant) View(ctx context.Context, in *sysin.ImTenantViewInp) (res
|
|||||||
res.MaxUsers = conf.MaxUsers
|
res.MaxUsers = conf.MaxUsers
|
||||||
res.MaxGroups = conf.MaxGroups
|
res.MaxGroups = conf.MaxGroups
|
||||||
res.MaxGroupMembers = conf.MaxGroupMembers
|
res.MaxGroupMembers = conf.MaxGroupMembers
|
||||||
|
res.AliyunModerationEnabled = conf.AliyunModerationEnabled
|
||||||
|
res.LocalModerationEnabled = conf.LocalModerationEnabled
|
||||||
}
|
}
|
||||||
res.DbDsnConfigured = res.DbDsn != ""
|
res.DbDsnConfigured = res.DbDsn != ""
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -43,27 +43,33 @@ type ImTenantInsertFields struct {
|
|||||||
|
|
||||||
// ImTenantConfigUpdateFields 修改IM租户配置字段过滤
|
// ImTenantConfigUpdateFields 修改IM租户配置字段过滤
|
||||||
type ImTenantConfigUpdateFields struct {
|
type ImTenantConfigUpdateFields struct {
|
||||||
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
||||||
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
||||||
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
||||||
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
|
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
|
||||||
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
|
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImTenantConfigInsertFields 新增IM租户配置字段过滤
|
// ImTenantConfigInsertFields 新增IM租户配置字段过滤
|
||||||
type ImTenantConfigInsertFields struct {
|
type ImTenantConfigInsertFields struct {
|
||||||
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
TenantId int64 `json:"tenantId" dc:"租户ID"`
|
||||||
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
||||||
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
||||||
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
||||||
CreatedBy int64 `json:"createdBy" dc:"创建者"`
|
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
|
||||||
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
|
CreatedBy int64 `json:"createdBy" dc:"创建者"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImTenantEditInp 修改/新增IM租户
|
// ImTenantEditInp 修改/新增IM租户
|
||||||
type ImTenantEditInp struct {
|
type ImTenantEditInp struct {
|
||||||
entity.ImTenant
|
entity.ImTenant
|
||||||
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
||||||
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
||||||
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
||||||
|
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
|
||||||
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
||||||
@@ -79,6 +85,10 @@ func (in *ImTenantEditInp) Filter(ctx context.Context) (err error) {
|
|||||||
if !dict.HasOptionKey(consts.ImTenantStateOptions, in.State) {
|
if !dict.HasOptionKey(consts.ImTenantStateOptions, in.State) {
|
||||||
return gerror.New("租户状态不正确")
|
return gerror.New("租户状态不正确")
|
||||||
}
|
}
|
||||||
|
if (in.AliyunModerationEnabled != 0 && in.AliyunModerationEnabled != 1) ||
|
||||||
|
(in.LocalModerationEnabled != 0 && in.LocalModerationEnabled != 1) {
|
||||||
|
return gerror.New("内容审核开关值不正确")
|
||||||
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,14 +105,16 @@ func (in *ImTenantViewInp) Filter(ctx context.Context) (err error) {
|
|||||||
|
|
||||||
type ImTenantViewModel struct {
|
type ImTenantViewModel struct {
|
||||||
entity.ImTenant
|
entity.ImTenant
|
||||||
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
||||||
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
||||||
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
||||||
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
|
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
|
||||||
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
|
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
|
||||||
TenantDbReachable bool `json:"tenantDbReachable" dc:"业务库是否可达"`
|
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
|
||||||
TenantDbCheckError string `json:"tenantDbCheckError" dc:"业务库检查错误"`
|
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
|
||||||
|
TenantDbReachable bool `json:"tenantDbReachable" dc:"业务库是否可达"`
|
||||||
|
TenantDbCheckError string `json:"tenantDbCheckError" dc:"业务库检查错误"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImTenantListInp 获取IM租户列表
|
// ImTenantListInp 获取IM租户列表
|
||||||
@@ -121,22 +133,24 @@ func (in *ImTenantListInp) Filter(ctx context.Context) (err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ImTenantListModel struct {
|
type ImTenantListModel struct {
|
||||||
Id int64 `json:"id" dc:"ID"`
|
Id int64 `json:"id" dc:"ID"`
|
||||||
Code string `json:"code" dc:"租户编码"`
|
Code string `json:"code" dc:"租户编码"`
|
||||||
Name string `json:"name" dc:"租户名称"`
|
Name string `json:"name" dc:"租户名称"`
|
||||||
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
|
TenantDesc string `json:"tenantDesc" dc:"租户描述"`
|
||||||
DbName string `json:"dbName" dc:"业务库名"`
|
DbName string `json:"dbName" dc:"业务库名"`
|
||||||
State int `json:"state" dc:"状态"`
|
State int `json:"state" dc:"状态"`
|
||||||
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
MaxUsers uint64 `json:"maxUsers" dc:"最大用户数"`
|
||||||
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
MaxGroups uint64 `json:"maxGroups" dc:"最大群组数"`
|
||||||
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
MaxGroupMembers uint `json:"maxGroupMembers" dc:"最大群成员数"`
|
||||||
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
|
AliyunModerationEnabled int `json:"aliyunModerationEnabled" dc:"是否启用阿里云内容审核"`
|
||||||
CreatedBy int64 `json:"createdBy" dc:"创建者"`
|
LocalModerationEnabled int `json:"localModerationEnabled" dc:"是否启用本地内容审核"`
|
||||||
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
|
DbDsnConfigured bool `json:"dbDsnConfigured" dc:"是否配置独立连接串"`
|
||||||
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
|
CreatedBy int64 `json:"createdBy" dc:"创建者"`
|
||||||
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
|
CreatedBySumma *hook.MemberSumma `json:"createdBySumma" dc:"创建者摘要信息"`
|
||||||
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
UpdatedBy int64 `json:"updatedBy" dc:"更新者"`
|
||||||
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
UpdatedBySumma *hook.MemberSumma `json:"updatedBySumma" dc:"更新者摘要信息"`
|
||||||
|
CreatedAt *gtime.Time `json:"createdAt" dc:"创建时间"`
|
||||||
|
UpdatedAt *gtime.Time `json:"updatedAt" dc:"修改时间"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ImTenantStatusInp 更新IM租户状态
|
// ImTenantStatusInp 更新IM租户状态
|
||||||
|
|||||||
@@ -8,7 +8,11 @@
|
|||||||
>
|
>
|
||||||
<n-form ref="formRef" :model="formValue" label-placement="left" label-width="90">
|
<n-form ref="formRef" :model="formValue" label-placement="left" label-width="90">
|
||||||
<n-form-item label="服务商">
|
<n-form-item label="服务商">
|
||||||
<n-input v-model:value="formValue.provider" placeholder="请输入服务商" />
|
<n-input
|
||||||
|
v-model:value="formValue.provider"
|
||||||
|
:disabled="isModeration"
|
||||||
|
placeholder="请输入服务商"
|
||||||
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="端点">
|
<n-form-item label="端点">
|
||||||
<n-input v-model:value="formValue.endpoint" placeholder="请输入服务端点" />
|
<n-input v-model:value="formValue.endpoint" placeholder="请输入服务端点" />
|
||||||
@@ -16,7 +20,42 @@
|
|||||||
<n-form-item label="启用">
|
<n-form-item label="启用">
|
||||||
<n-switch v-model:value="enabledValue" />
|
<n-switch v-model:value="enabledValue" />
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="普通配置">
|
<template v-if="isModeration">
|
||||||
|
<n-form-item label="地域"
|
||||||
|
><n-input v-model:value="moderationConfig.region_id"
|
||||||
|
/></n-form-item>
|
||||||
|
<n-form-item label="昵称服务"
|
||||||
|
><n-input v-model:value="moderationConfig.nickname_service"
|
||||||
|
/></n-form-item>
|
||||||
|
<n-form-item label="消息服务"
|
||||||
|
><n-input v-model:value="moderationConfig.chat_service"
|
||||||
|
/></n-form-item>
|
||||||
|
<n-form-item label="超时毫秒"
|
||||||
|
><n-input-number v-model:value="moderationConfig.timeout_ms" :min="200" :max="10000"
|
||||||
|
/></n-form-item>
|
||||||
|
<n-form-item label="异常策略"
|
||||||
|
><n-radio-group v-model:value="moderationConfig.failure_mode"
|
||||||
|
><n-space
|
||||||
|
><n-radio value="allow">放行</n-radio><n-radio value="block">拦截</n-radio></n-space
|
||||||
|
></n-radio-group
|
||||||
|
></n-form-item
|
||||||
|
>
|
||||||
|
<n-form-item label="AccessKey ID"
|
||||||
|
><n-input
|
||||||
|
v-model:value="moderationSecret.access_key_id"
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
placeholder="留空保持原值"
|
||||||
|
/></n-form-item>
|
||||||
|
<n-form-item label="AccessKey Secret"
|
||||||
|
><n-input
|
||||||
|
v-model:value="moderationSecret.access_key_secret"
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
placeholder="留空保持原值"
|
||||||
|
/></n-form-item>
|
||||||
|
</template>
|
||||||
|
<n-form-item v-else label="普通配置">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="formValue.configJson"
|
v-model:value="formValue.configJson"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -24,7 +63,7 @@
|
|||||||
placeholder="请输入JSON配置"
|
placeholder="请输入JSON配置"
|
||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
<n-form-item label="敏感配置">
|
<n-form-item v-if="!isModeration" label="敏感配置">
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="formValue.secretJson"
|
v-model:value="formValue.secretJson"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
@@ -68,6 +107,15 @@
|
|||||||
formValue.value.enabled = value ? 1 : 0;
|
formValue.value.enabled = value ? 1 : 0;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
const isModeration = computed(() => formValue.value.service === 'moderation');
|
||||||
|
const moderationConfig = ref<any>({
|
||||||
|
region_id: 'cn-shanghai',
|
||||||
|
nickname_service: 'nickname_detection_pro',
|
||||||
|
chat_service: 'chat_detection_pro',
|
||||||
|
timeout_ms: 1500,
|
||||||
|
failure_mode: 'allow',
|
||||||
|
});
|
||||||
|
const moderationSecret = ref<any>({ access_key_id: '', access_key_secret: '' });
|
||||||
|
|
||||||
function closeForm() {
|
function closeForm() {
|
||||||
showModal.value = false;
|
showModal.value = false;
|
||||||
@@ -86,8 +134,14 @@
|
|||||||
async function handleSubmit() {
|
async function handleSubmit() {
|
||||||
submitLoading.value = true;
|
submitLoading.value = true;
|
||||||
try {
|
try {
|
||||||
const configJson = parseJson(formValue.value.configJson || '{}', '普通配置') || {};
|
const configJson = isModeration.value
|
||||||
const secretJson = parseJson(formValue.value.secretJson, '敏感配置');
|
? moderationConfig.value
|
||||||
|
: parseJson(formValue.value.configJson || '{}', '普通配置') || {};
|
||||||
|
const secretJson = isModeration.value
|
||||||
|
? moderationSecret.value.access_key_id || moderationSecret.value.access_key_secret
|
||||||
|
? moderationSecret.value
|
||||||
|
: null
|
||||||
|
: parseJson(formValue.value.secretJson, '敏感配置');
|
||||||
await Update({
|
await Update({
|
||||||
tenantId: formValue.value.tenantId,
|
tenantId: formValue.value.tenantId,
|
||||||
service: formValue.value.service,
|
service: formValue.value.service,
|
||||||
@@ -111,6 +165,15 @@
|
|||||||
formValue.value = newState(state);
|
formValue.value = newState(state);
|
||||||
formValue.value.secretJson = '';
|
formValue.value.secretJson = '';
|
||||||
if (!formValue.value.configJson) formValue.value.configJson = '{}';
|
if (!formValue.value.configJson) formValue.value.configJson = '{}';
|
||||||
|
if (formValue.value.service === 'moderation') {
|
||||||
|
formValue.value.provider = 'aliyun_green';
|
||||||
|
formValue.value.endpoint ||= 'green-cip.cn-shanghai.aliyuncs.com';
|
||||||
|
moderationConfig.value = {
|
||||||
|
...moderationConfig.value,
|
||||||
|
...(parseJson(formValue.value.configJson, '普通配置') || {}),
|
||||||
|
};
|
||||||
|
moderationSecret.value = { access_key_id: '', access_key_secret: '' };
|
||||||
|
}
|
||||||
showModal.value = true;
|
showModal.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -82,6 +82,24 @@
|
|||||||
/>
|
/>
|
||||||
</n-form-item>
|
</n-form-item>
|
||||||
</n-gi>
|
</n-gi>
|
||||||
|
<n-gi span="1">
|
||||||
|
<n-form-item label="阿里云内容审核" path="aliyunModerationEnabled">
|
||||||
|
<n-switch
|
||||||
|
v-model:value="formValue.aliyunModerationEnabled"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
|
<n-gi span="1">
|
||||||
|
<n-form-item label="本地内容审核" path="localModerationEnabled">
|
||||||
|
<n-switch
|
||||||
|
v-model:value="formValue.localModerationEnabled"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="0"
|
||||||
|
/>
|
||||||
|
</n-form-item>
|
||||||
|
</n-gi>
|
||||||
<n-gi span="2">
|
<n-gi span="2">
|
||||||
<n-form-item label="商户描述" path="tenantDesc">
|
<n-form-item label="商户描述" path="tenantDesc">
|
||||||
<n-input
|
<n-input
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export class State {
|
|||||||
public maxUsers = 0; // 最大用户数
|
public maxUsers = 0; // 最大用户数
|
||||||
public maxGroups = 0; // 最大群组数
|
public maxGroups = 0; // 最大群组数
|
||||||
public maxGroupMembers = 0; // 最大群成员数
|
public maxGroupMembers = 0; // 最大群成员数
|
||||||
|
public aliyunModerationEnabled = 0; // 是否启用阿里云内容审核
|
||||||
|
public localModerationEnabled = 0; // 是否启用本地内容审核
|
||||||
public dbDsnConfigured = false; // 是否配置独立连接串
|
public dbDsnConfigured = false; // 是否配置独立连接串
|
||||||
public createdBy = 0; // 创建者
|
public createdBy = 0; // 创建者
|
||||||
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
public createdBySumma?: null | MemberSumma = null; // 创建者摘要信息
|
||||||
@@ -208,6 +210,32 @@ export const columns = [
|
|||||||
return row.maxGroupMembers > 0 ? row.maxGroupMembers : '不限';
|
return row.maxGroupMembers > 0 ? row.maxGroupMembers : '不限';
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: '阿里云审核',
|
||||||
|
key: 'aliyunModerationEnabled',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
render(row: State) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{ type: row.aliyunModerationEnabled === 1 ? 'success' : 'default', bordered: false },
|
||||||
|
{ default: () => (row.aliyunModerationEnabled === 1 ? '已启用' : '未启用') }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '本地审核',
|
||||||
|
key: 'localModerationEnabled',
|
||||||
|
align: 'left',
|
||||||
|
width: 120,
|
||||||
|
render(row: State) {
|
||||||
|
return h(
|
||||||
|
NTag,
|
||||||
|
{ type: row.localModerationEnabled === 1 ? 'success' : 'default', bordered: false },
|
||||||
|
{ default: () => (row.localModerationEnabled === 1 ? '已启用' : '未启用') }
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: '创建者',
|
title: '创建者',
|
||||||
key: 'createdBy',
|
key: 'createdBy',
|
||||||
|
|||||||
Reference in New Issue
Block a user