aliyun sensor server
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
// Copyright 2022 E99p1ant. All rights reserved.
|
||||
// Use of this source code is governed by a MIT-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package user
|
||||
|
||||
import (
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/tamakyi/TamaBox/internal/context"
|
||||
"github.com/tamakyi/TamaBox/internal/db"
|
||||
"github.com/tamakyi/TamaBox/internal/form"
|
||||
"github.com/tamakyi/TamaBox/internal/i18n"
|
||||
"github.com/tamakyi/TamaBox/internal/security/censor"
|
||||
)
|
||||
|
||||
// UpdateModerationStrictness 保存当前账号的「内容审核严格度」档位。
|
||||
//
|
||||
// 该档位同时作用于提问、投票、私聊三条链路(由 internal/security/moderation.Check
|
||||
// 统一裁决),因此只需在账号侧存一份。
|
||||
func UpdateModerationStrictness(ctx context.Context, f form.UpdateModerationStrictness) {
|
||||
// 反序列化失败(如请求体超限)时零值 Strictness="" 会被当成「跟随站点默认」写入,
|
||||
// 把账号已选择的档位静默改回默认档 —— 先中止,不触碰现有设置。
|
||||
if abortOnFormError(ctx, returnTab(ctx)) {
|
||||
return
|
||||
}
|
||||
|
||||
// 非法取值一律拒绝保存:宁可保留原档位,也不要写入一个解释不了的中间状态。
|
||||
if !censor.IsValidStrictness(f.Strictness) {
|
||||
ctx.SetErrorFlash(i18n.T(ctx.Lang(), "admin.flash.moderationStrictnessInvalid"))
|
||||
ctx.Redirect(returnTab(ctx))
|
||||
return
|
||||
}
|
||||
|
||||
if err := db.Users.UpdateModerationStrictness(ctx.Request().Context(), ctx.User.ID, f.Strictness); err != nil {
|
||||
logrus.WithContext(ctx.Request().Context()).WithError(err).Error("Failed to update moderation strictness")
|
||||
ctx.SetInternalErrorFlash()
|
||||
ctx.Redirect(returnTab(ctx))
|
||||
return
|
||||
}
|
||||
|
||||
ctx.SetSuccessFlash(i18n.T(ctx.Lang(), "admin.flash.moderationStrictnessUpdated"))
|
||||
ctx.Redirect(returnTab(ctx))
|
||||
}
|
||||
+39
-25
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user