aliyun sensor server

This commit is contained in:
tamakyi
2026-09-10 18:04:19 +08:00
parent e3e59e7c69
commit 8e2325eda5
38 changed files with 3147 additions and 344 deletions
+39 -25
View File
@@ -26,6 +26,7 @@ import (
"github.com/tamakyi/TamaBox/internal/db"
"github.com/tamakyi/TamaBox/internal/i18n"
"github.com/tamakyi/TamaBox/internal/security/censor"
"github.com/tamakyi/TamaBox/internal/security/moderation"
votepkg "github.com/tamakyi/TamaBox/route/vote"
)
@@ -58,17 +59,13 @@ type voteForm struct {
}
// censorCheck 对单段文本做内容审核。
// fail-closed:审核服务异常时拒绝发布(与提问/回答流程一致);
// 未开启 EnableTextCensor 时 censor.Text 直接放行,不影响开发环境。
//
// 与提问 / 私聊共用同一个守卫(internal/security/moderation.Check):是否拦截由
// 投票发起人本人的「审核严格度」档位决定。
// fail-closed:审核服务异常时拒绝发布;未开启 EnableTextCensor 时底层直接放行。
func censorCheck(ctx context.Context, text string) error {
resp, err := censor.Text(ctx.Request().Context(), text)
if err != nil {
return errors.New(i18n.T(ctx.Lang(), "censor.unavailable"))
}
if !resp.Pass {
return errors.New(resp.ErrorMessage())
}
return nil
_, err := moderation.Check(ctx.Request().Context(), moderation.StrictnessFor(ctx.User.ModerationStrictness), ctx.Lang(), text)
return err
}
// parseTimeInput 解析 "YYYY-MM-DDTHH:MM"(HTML datetime-local 格式),空字符串返回 nil。
@@ -159,12 +156,9 @@ func parseVoteMeta(ctx context.Context, draft bool) (*voteForm, error) {
// 内容审核(fail-closed):非草稿执行;草稿跳过(见上说明)
var censorMeta datatypes.JSON
if !draft {
titleCensor, err := censor.Text(ctx.Request().Context(), title)
titleCensor, err := moderation.Check(ctx.Request().Context(), moderation.StrictnessFor(ctx.User.ModerationStrictness), ctx.Lang(), title)
if err != nil {
return nil, errors.New("内容审核服务暂时不可用,请稍后再试")
}
if !titleCensor.Pass {
return nil, errors.New(titleCensor.ErrorMessage())
return nil, err
}
censorMeta = datatypes.JSON(titleCensor.ToJSON())
if description != "" {
@@ -996,32 +990,52 @@ func validateForPublish(ctx context.Context, vote *db.Vote) error {
return nil
}
// voteOwnerStrictness 读取投票发起人本人的「审核严格度」档位。
//
// 这里不用 ctx.User 的原因:发布动作未必由发起人本人触发(后台管理、补发布等场景),
// ctx.User 有可能是操作者而非发起人,直接沿用会导致「按别人的档位审核别人箱子的内容」。
// 取不到发起人时回退最严档位 —— 宁可沿用严格判定,也不要因为查不到就把判定放宽。
func voteOwnerStrictness(ctx context.Context, ownerID uint) censor.Strictness {
owner, err := db.Users.GetByID(ctx.Request().Context(), ownerID)
if err != nil {
logrus.WithContext(ctx.Request().Context()).WithError(err).WithField("owner_id", ownerID).
Error("Failed to get vote owner for moderation strictness")
return censor.StrictnessStrict
}
return moderation.StrictnessFor(owner.ModerationStrictness)
}
// censorVoteForPublish 发布前对投票全部可见文本做内容审核(fail-closed)。
// 草稿保存阶段跳过了审核,发布(对外公开)时必须补齐,确保公开内容受管控。
func censorVoteForPublish(ctx context.Context, vote *db.Vote) error {
// 同一次发布的全部可见文本必须用「同一个」档位裁决:这里只解析一次发起人的档位并复用。
// 否则标题走发起人档位、描述/选项走 ctx.User 档位(censorCheck 内部读 ctx.User),
// 一旦发布动作不是发起人本人触发,同一条投票的两段文本就会落到两个账号的松紧档上。
strictness := voteOwnerStrictness(ctx, vote.UserID)
check := func(text string) error {
_, err := moderation.Check(ctx.Request().Context(), strictness, ctx.Lang(), text)
return err
}
if vote.Title != "" {
resp, err := censor.Text(ctx.Request().Context(), vote.Title)
if err != nil {
return errors.New(i18n.T(ctx.Lang(), "censor.unavailable"))
}
if !resp.Pass {
return errors.New(resp.ErrorMessage())
if err := check(vote.Title); err != nil {
return err
}
}
if vote.Description != "" {
if err := censorCheck(ctx, vote.Description); err != nil {
if err := check(vote.Description); err != nil {
return err
}
}
options, _ := db.VoteOptions.ListByVoteID(ctx.Request().Context(), vote.ID)
for _, o := range options {
if err := censorCheck(ctx, o.Content); err != nil {
if err := check(o.Content); err != nil {
return err
}
}
fields, _ := db.VoteFormFields.ListByVoteID(ctx.Request().Context(), vote.ID)
for _, f := range fields {
if err := censorCheck(ctx, f.Label); err != nil {
if err := check(f.Label); err != nil {
return err
}
// 预置选项(select/radio/checkbox/rank)也纳入审核
@@ -1029,7 +1043,7 @@ func censorVoteForPublish(ctx context.Context, vote *db.Vote) error {
if fo == "" {
continue
}
if err := censorCheck(ctx, fo); err != nil {
if err := check(fo); err != nil {
return err
}
}