aliyun sensor server
This commit is contained in:
@@ -6,6 +6,8 @@ package censor
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@@ -17,8 +19,49 @@ import (
|
||||
|
||||
var (
|
||||
censorCacheNoMoreThan = 31 * 24 * time.Hour // 1 month
|
||||
|
||||
// warnNoCensorCredentialsOnce 保证「没有可用凭据」的告警只打一次,避免按请求刷屏。
|
||||
warnNoCensorCredentialsOnce sync.Once
|
||||
)
|
||||
|
||||
// censorChain 返回按配置选定的审核通道列表,顺序即优先级:
|
||||
// 靠前的通道判定「通过」即返回;未通过则继续交给后面的通道复核。
|
||||
//
|
||||
// 取值来源 [security] text_censor_source(见 conf.CensorSource*):
|
||||
// - qiniu → 只用七牛(凭据齐备性已由 conf.Init 启动校验)
|
||||
// - aliyun → 只用阿里云(同上)
|
||||
// - 其它(含留空 / auto)→ 七牛优先、阿里云兜底,与引入该配置前完全一致
|
||||
func censorChain() []TextCensor {
|
||||
qiniu := NewQiniuTextCensor(conf.App.QiniuAccessKey, conf.App.QiniuAccessSecret)
|
||||
aliyun := NewAliyunTextCensor(conf.App.AliyunAccessKey, conf.App.AliyunAccessKeySecret)
|
||||
|
||||
switch strings.ToLower(strings.TrimSpace(conf.Security.TextCensorSource)) {
|
||||
case conf.CensorSourceQiniu:
|
||||
return []TextCensor{qiniu}
|
||||
case conf.CensorSourceAliyun:
|
||||
return []TextCensor{aliyun}
|
||||
default:
|
||||
return []TextCensor{qiniu, aliyun}
|
||||
}
|
||||
}
|
||||
|
||||
// warnIfNoCensorCredentials 在「配置里一条可用凭据都没有」时告警一次。
|
||||
//
|
||||
// 这种情况下审核调用必然失败,而调用方是 fail-closed(拒绝提交),表现为
|
||||
// 全站发不出内容/私信——现象隐蔽、只在日志可见,因此第一次触发就留下明确线索。
|
||||
// 显式选定通道但缺凭据的情况已由 conf.Init 直接挡住,这里兜的是 auto 的漏网情况。
|
||||
func warnIfNoCensorCredentials(ctx context.Context) {
|
||||
qiniuReady := conf.App.QiniuAccessKey != "" && conf.App.QiniuAccessSecret != ""
|
||||
aliyunReady := conf.App.AliyunAccessKey != "" && conf.App.AliyunAccessKeySecret != ""
|
||||
if qiniuReady || aliyunReady {
|
||||
return
|
||||
}
|
||||
warnNoCensorCredentialsOnce.Do(func() {
|
||||
logrus.WithContext(ctx).WithField("text_censor_source", conf.Security.TextCensorSource).
|
||||
Error("Text censor is enabled but no usable credentials are configured; all submissions will be rejected (fail-closed)")
|
||||
})
|
||||
}
|
||||
|
||||
// Text checks the text for sensitive content.
|
||||
// It will save the censor log to the database and invoke the callback function.
|
||||
func Text(ctx context.Context, text string) (*TextCensorResponse, error) {
|
||||
@@ -28,11 +71,8 @@ func Text(ctx context.Context, text string) (*TextCensorResponse, error) {
|
||||
|
||||
var responses []*TextCensorResponse
|
||||
|
||||
for _, censor := range []TextCensor{
|
||||
//No, we need to use qiniu's API.
|
||||
NewQiniuTextCensor(conf.App.QiniuAccessKey, conf.App.QiniuAccessSecret),
|
||||
NewAliyunTextCensor(conf.App.AliyunAccessKey, conf.App.AliyunAccessKeySecret),
|
||||
} {
|
||||
warnIfNoCensorCredentials(ctx)
|
||||
for _, censor := range censorChain() {
|
||||
sourceName := censor.String()
|
||||
|
||||
// Try to get the censor from the database log.
|
||||
@@ -46,19 +86,33 @@ func Text(ctx context.Context, text string) (*TextCensorResponse, error) {
|
||||
// We got the previous censor log cache, seems like we saved money for the API call.
|
||||
switch censorLog.SourceName {
|
||||
case "qiniu":
|
||||
// HACK: Qiniu's API response is not accurate, so we need to check the text again with aliyun API.
|
||||
// 七牛缓存命中:只有「通过」才可直接复用(省一次 API 调用)。
|
||||
// 未通过时不在此下最终结论,而是交给链上的下一个通道复核(auto 模式下即阿里云,
|
||||
// 这是引入通道配置前就有的行为)。
|
||||
// text_censor_source = qiniu 时链上只有七牛:continue 后循环结束,
|
||||
// 仍会由函数末尾返回这份七牛结果,判定不会丢失。
|
||||
response, err := QiniuTextCensorParser(raw)
|
||||
if err == nil {
|
||||
if response.Pass {
|
||||
return response, nil
|
||||
}
|
||||
|
||||
responses = append(responses, response) // If the text is not passed, we need to check it with aliyun API.
|
||||
responses = append(responses, response)
|
||||
continue
|
||||
}
|
||||
|
||||
case "aliyun":
|
||||
return AliyunTextCensorParser(raw)
|
||||
// 阿里云缓存命中:能解析就直接复用(省一次 API 调用)。
|
||||
// 解析失败必须退回一次实时调用,而不是把错误往上抛:
|
||||
// 最典型的失败源是「迁移到内容安全 2.0 之前落库的 1.0 缓存」,
|
||||
// 它无法按 2.0 语义解释;若在此返回错误,fail-closed 会让这些
|
||||
// 历史文本在缓存过期(31 天)前一直发不出去。
|
||||
response, err := AliyunTextCensorParser(raw)
|
||||
if err == nil {
|
||||
return response, nil
|
||||
}
|
||||
logrus.WithContext(ctx).WithError(err).WithField("censor_source", sourceName).
|
||||
Warn("Failed to replay cached aliyun censor result; falling back to a live call")
|
||||
|
||||
default:
|
||||
logrus.WithContext(ctx).WithField("censor_source", censorLog.SourceName).Error("Unknown censor source")
|
||||
|
||||
Reference in New Issue
Block a user