feat: 用户行为信誉分系统

- User 新增 credit_score 字段(0-200,默认100)
- 信誉分影响检测阈值系数:高分降低敏感度,低分提高敏感度
- 发布成功+1分,被拦截-2分;申诉通过+10分,驳回-5分
- 新增手动调整和批量重算信誉分接口
- admin-users 页面显示信誉分进度条,支持编辑调整

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
刘正航
2026-04-21 23:52:47 +08:00
parent 5279816452
commit 6d62120443
8 changed files with 166 additions and 10 deletions

View File

@@ -77,12 +77,22 @@ def _resolve_recipient(payload: dict, visibility: str, current_user_id: int):
return recipient, None
def _predict_and_decide(text: str) -> tuple[dict, float, bool]:
def _predict_and_decide(text: str, user_credit: int = 100) -> tuple[dict, float, bool]:
"""根据用户信誉分调整阈值系数。信誉分越高,阈值越高(降低敏感度)"""
clf = _ensure_ready()
result = clf.predict(text)
threshold = float(_get_config().spam_threshold)
blocked = float(result["spam_probability"]) >= threshold
return result, threshold, blocked
base_threshold = float(_get_config().spam_threshold)
# 信誉分影响阈值系数credit 0-200默认100
# credit > 100阈值提高降低敏感度减少误判
# credit < 100阈值降低提高敏感度加强拦截
# 系数范围0.85 - 1.15
credit_factor = 1.0 + (user_credit - 100) * 0.0015 # 每10分变化1.5%
credit_factor = max(0.85, min(1.15, credit_factor))
adjusted_threshold = base_threshold * credit_factor
blocked = float(result["spam_probability"]) >= adjusted_threshold
return result, adjusted_threshold, blocked
@content_bp.post("/publish")
@@ -103,7 +113,7 @@ def publish_text():
if err:
return fail(err, 400)
result, threshold, blocked = _predict_and_decide(text)
result, threshold, blocked = _predict_and_decide(text, user.credit_score or 100)
post = ContentPost(
user_id=user.id,
@@ -134,6 +144,13 @@ def publish_text():
db.session.add(post)
db.session.add(detect_log)
# 发布成功(未被拦截),小幅增加信誉分;被拦截则小幅减少
if not blocked:
user.credit_score = min(200, (user.credit_score or 100) + 1)
else:
user.credit_score = max(0, (user.credit_score or 100) - 2)
db.session.commit()
feedback = "发布成功" if not blocked else "疑似垃圾信息,系统已拦截,可提交申诉"
@@ -171,7 +188,7 @@ def edit_post(post_id: int):
if err:
return fail(err, 400)
result, threshold, blocked = _predict_and_decide(text)
result, threshold, blocked = _predict_and_decide(text, user.credit_score or 100)
post.text = result["text"]
post.visibility = visibility