feat: 用户被多次举报合并

This commit is contained in:
DengDai
2025-11-25 09:36:36 +08:00
parent 881188587d
commit 40feb92473
7 changed files with 120 additions and 44 deletions

View File

@@ -61,9 +61,10 @@ def create_app(config_name='default'):
app.logger.info('PT黑名单系统启动')
# 注册自定义过滤器
from .filters import translate_status, translate_reason
from .filters import translate_status, translate_reason, translate_reasons_list
app.jinja_env.filters['translate_status'] = translate_status
app.jinja_env.filters['translate_reason'] = translate_reason
app.jinja_env.filters['translate_reasons_list'] = translate_reasons_list
# 注册蓝图
from .routes import main as main_blueprint

View File

@@ -38,3 +38,9 @@ def translate_status(status):
def translate_reason(reason):
"""违规原因翻译过滤器"""
return REASON_TRANSLATIONS.get(reason, reason)
def translate_reasons_list(reasons):
"""违规原因列表翻译过滤器"""
if not reasons:
return []
return [REASON_TRANSLATIONS.get(r, r) for r in reasons]

View File

@@ -64,7 +64,6 @@ class Report(db.Model):
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
comments = db.relationship('Comment', backref='report', lazy='dynamic', cascade='all, delete-orphan')
evidences = db.relationship('Evidence', backref='report', lazy='dynamic', cascade='all, delete-orphan')
blacklist_entry = db.relationship('Blacklist', backref='report', uselist=False)
def __repr__(self):
return f'<Report {self.id}>'
@@ -96,7 +95,8 @@ class Blacklist(db.Model):
normalized_email = db.Column(db.String(120), index=True)
pt_site = db.Column(db.String(100), index=True)
uid = db.Column(db.String(50))
report_id = db.Column(db.Integer, db.ForeignKey('reports.id'), unique=True)
report_ids = db.Column(db.JSON, default=list, nullable=False)
reason_categories = db.Column(db.JSON, default=list, nullable=False)
status = db.Column(db.String(16), default='active', index=True)
created_at = db.Column(db.DateTime, index=True, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)

View File

@@ -1,5 +1,6 @@
from flask import abort, Blueprint, render_template, request, flash, redirect, url_for, current_app
from sqlalchemy import or_
from sqlalchemy.orm.attributes import flag_modified
from flask_login import login_required, current_user
from app import db
from app.forms import SearchForm, ReportForm, UpdateUserForm, CommentForm, RevokeForm, AppealForm, AppealMessageForm, PartnerSiteForm
@@ -24,13 +25,12 @@ def index():
search_term = form.search_term.data
normalized_email = normalize_email(search_term)
search_result = Blacklist.query.join(Report).filter(
search_result = Blacklist.query.filter(
or_(
Blacklist.normalized_email == normalized_email,
Blacklist.username == search_term
),
Blacklist.status == 'active',
Report.status == 'approved'
Blacklist.status == 'active'
).first()
if search_result:
@@ -46,6 +46,9 @@ def index():
@login_required
def create_report():
"""创建新举报"""
if current_user.status != 'active':
flash('您的账户尚未激活,无法提交举报。请等待管理员审核。', 'warning')
return redirect(url_for('main.index'))
form = ReportForm()
active_sites = PartnerSite.query.filter_by(is_active=True).order_by(PartnerSite.name).all()
form.reported_pt_site.choices = [(site.name, site.name) for site in active_sites]
@@ -170,17 +173,33 @@ def process_report(report_id):
if action == 'confirm':
report.status = 'approved'
existing_blacklist = Blacklist.query.filter_by(report_id=report.id).first()
normalized = normalize_email(report.reported_email)
existing_blacklist = Blacklist.query.filter_by(normalized_email=normalized, status='active').first()
if not existing_blacklist:
new_blacklist_entry = Blacklist(
email=report.reported_email,
normalized_email=normalize_email(report.reported_email),
normalized_email=normalized,
pt_site=report.reported_pt_site,
uid=report.reported_username,
report_id=report.id,
report_ids=[report.id],
reason_categories=[report.reason_category],
username=report.reported_username or None
)
db.session.add(new_blacklist_entry)
current_app.logger.info(f'举报批准: #{report.id} - {report.reported_email} by {current_user.username}')
flash('举报已批准,并已将相关信息添加到黑名单。', 'success')
else:
if report.reason_category not in existing_blacklist.reason_categories:
existing_blacklist.reason_categories.append(report.reason_category)
existing_blacklist.report_ids.append(report.id)
flag_modified(existing_blacklist, 'reason_categories')
flag_modified(existing_blacklist, 'report_ids')
current_app.logger.info(f'举报合并: #{report.id} 合并到黑名单#{existing_blacklist.id} - 新增原因: {report.reason_category}')
flash(f'举报已批准并合并到现有黑名单记录(新增违规原因:{report.reason_category})。', 'success')
else:
current_app.logger.info(f'举报批准: #{report.id} - 相同原因已存在,不合并')
flash('举报已批准。该用户已有相同违规原因的记录,未进行合并。', 'info')
other_pending = Report.query.filter(
Report.reported_email == report.reported_email,
@@ -188,8 +207,16 @@ def process_report(report_id):
Report.status == 'pending'
).all()
merged_count = 0
for other_report in other_pending:
other_report.status = 'approved'
bl = existing_blacklist or new_blacklist_entry
if other_report.reason_category not in bl.reason_categories:
bl.reason_categories.append(other_report.reason_category)
bl.report_ids.append(other_report.id)
flag_modified(bl, 'reason_categories')
flag_modified(bl, 'report_ids')
merged_count += 1
comment = Comment(
body=f'该举报已自动批准(关联举报 #{report.id} 已确认违规)',
report=other_report,
@@ -197,14 +224,9 @@ def process_report(report_id):
)
db.session.add(comment)
current_app.logger.info(f'举报批准: #{report.id} - {report.reported_email} by {current_user.username}')
if other_pending:
current_app.logger.info(f'自动批准关联举报: {len(other_pending)}')
flash(f'举报已批准,并已将相关信息添加到黑名单。同时自动处理了 {len(other_pending)} 个相关举报。', 'success')
else:
flash('举报已批准,并已将相关信息添加到黑名单。', 'success')
else:
flash('举报状态已更新为"批准"。该举报已在黑名单中,无需重复添加。', 'info')
current_app.logger.info(f'自动批准关联举报: {len(other_pending)},合并{merged_count}个不同原因')
flash(f'同时自动处理了 {len(other_pending)} 个相关举报(其中 {merged_count} 个不同原因已合并)', 'info')
elif action == 'invalidate':
report.status = 'rejected'
current_app.logger.info(f'举报驳回: #{report.id} by {current_user.username}')
@@ -224,9 +246,23 @@ def revoke_report(report_id):
return redirect(url_for('main.report_detail', report_id=report.id))
form = RevokeForm()
if form.validate_on_submit():
blacklist_entry = Blacklist.query.filter_by(report_id=report.id).first()
if blacklist_entry:
normalized = normalize_email(report.reported_email)
blacklist_entry = Blacklist.query.filter_by(normalized_email=normalized, status='active').first()
if blacklist_entry and report.id in blacklist_entry.report_ids:
blacklist_entry.report_ids.remove(report.id)
if report.reason_category in blacklist_entry.reason_categories:
blacklist_entry.reason_categories.remove(report.reason_category)
flag_modified(blacklist_entry, 'report_ids')
flag_modified(blacklist_entry, 'reason_categories')
if len(blacklist_entry.report_ids) == 0:
db.session.delete(blacklist_entry)
current_app.logger.warning(f'举报撤销: #{report.id} - 黑名单记录已删除')
flash('举报已成功撤销,并已从黑名单中移除。', 'success')
else:
current_app.logger.warning(f'举报撤销: #{report.id} - 从黑名单中移除该举报')
flash(f'举报已成功撤销,已从黑名单中移除该违规原因(剩余 {len(blacklist_entry.report_ids)} 个举报)。', 'success')
report.status = 'revoked'
revocation_comment = Comment(
@@ -237,7 +273,6 @@ def revoke_report(report_id):
db.session.add(revocation_comment)
db.session.commit()
current_app.logger.warning(f'举报撤销: #{report.id} by {current_user.username} - 理由: {form.reason.data[:50]}')
flash('举报已成功撤销,并已从黑名单中移除。', 'success')
else:
flash('撤销失败:' + ' '.join(form.reason.errors), 'danger')
return redirect(url_for('main.report_detail', report_id=report.id))
@@ -398,9 +433,11 @@ def decide_appeal(appeal_id):
blacklist_entry = appeal.blacklist_entry
blacklist_entry.status = 'revoked'
appeal.status = 'approved'
if blacklist_entry.report:
blacklist_entry.report.status = 'overturned'
db.session.add(blacklist_entry.report)
for report_id in blacklist_entry.report_ids:
report = Report.query.get(report_id)
if report:
report.status = 'overturned'
db.session.add(report)
db.session.add(blacklist_entry)
db.session.add(appeal)
db.session.commit()

View File

@@ -24,6 +24,18 @@
{% if appeal.blacklist_entry %}
<p class="mb-0"><strong>站点:</strong> {{ appeal.blacklist_entry.pt_site }}</p>
<p class="mb-0"><strong>UID:</strong> {{ appeal.blacklist_entry.uid }}</p>
<p class="mb-1"><strong>违规原因:</strong></p>
{% if appeal.blacklist_entry.reason_categories and appeal.blacklist_entry.reason_categories|length > 0 %}
<ul class="mb-0">
{% for reason in appeal.blacklist_entry.reason_categories %}
<li>{{ reason | translate_reason }}</li>
{% endfor %}
</ul>
{% elif appeal.blacklist_entry.report %}
<p class="mb-0">{{ appeal.blacklist_entry.report.reason_category | translate_reason }}</p>
{% else %}
<p class="mb-0 text-muted">未知</p>
{% endif %}
{% else %}
<p class="mb-0 text-muted">黑名单记录已删除</p>
{% endif %}

View File

@@ -14,9 +14,20 @@
<li class="list-group-item"><strong>UID:</strong> {{ entry.uid }}</li>
<li class="list-group-item"><strong>邮箱:</strong> {{ entry.email }}</li>
<li class="list-group-item"><strong>站点:</strong> {{ entry.pt_site }}</li>
{% if entry.report %}
<li class="list-group-item"><strong>违规原因:</strong> {{ entry.report.reason_category | translate_reason }}</li>
<li class="list-group-item">
<strong>违规原因:</strong>
{% if entry.reason_categories and entry.reason_categories|length > 0 %}
<ul class="mb-0 mt-1">
{% for reason in entry.reason_categories %}
<li>{{ reason | translate_reason }}</li>
{% endfor %}
</ul>
{% elif entry.report %}
{{ entry.report.reason_category | translate_reason }}
{% else %}
未知
{% endif %}
</li>
</ul>
<form method="POST" novalidate>

View File

@@ -45,9 +45,18 @@
</div>
<ul class="list-group list-group-flush">
<li class="list-group-item"><strong>违规站点:</strong> {{ search_result.pt_site }}</li>
{% if search_result.report %}
<li class="list-group-item"><strong>违规原因:</strong> {{ search_result.report.reason_category | translate_reason }}</li>
<li class="list-group-item">
<strong>违规原因:</strong>
{% if search_result.reason_categories and search_result.reason_categories|length > 0 %}
<ul class="mb-0 mt-1">
{% for reason in search_result.reason_categories %}
<li>{{ reason | translate_reason }}</li>
{% endfor %}
</ul>
{% else %}
未知
{% endif %}
</li>
<li class="list-group-item"><strong>记录时间:</strong> {{ search_result.created_at.strftime('%Y-%m-%d') }}</li>
</ul>
<p class="text-muted small mt-3">为保护隐私,仅展示必要的脱敏信息。具体违规描述不对外公开。</p>