diff --git a/apps/scheduler/call/api/api.py b/apps/scheduler/call/api/api.py index 743cb40a6332cdbfa0ab199f4cd3e1d041f1dfc9..1d064d0000244d9ee3cca2e662976a9803657970 100644 --- a/apps/scheduler/call/api/api.py +++ b/apps/scheduler/call/api/api.py @@ -12,7 +12,7 @@ from fastapi import status from pydantic import Field from pydantic.json_schema import SkipJsonSchema -from apps.common.oidc import oidc_provider +from apps.common.auth import oidc_provider from apps.models import LanguageType from apps.scheduler.call.core import CoreCall from apps.schemas.enum_var import CallOutputType, ContentType, HTTPMethod diff --git a/apps/scheduler/call/llm/llm.py b/apps/scheduler/call/llm/llm.py index 8ec8ad56cddbf1ceb9f9a1155ee41de11a5bddd7..723df40356a79ede7f5ddf67494ba253714732a4 100644 --- a/apps/scheduler/call/llm/llm.py +++ b/apps/scheduler/call/llm/llm.py @@ -1,16 +1,18 @@ # Copyright (c) Huawei Technologies Co., Ltd. 2023-2025. All rights reserved. """调用大模型""" +import json import logging from collections.abc import AsyncGenerator from datetime import datetime -from typing import TYPE_CHECKING, Any +from typing import Any import pytz from jinja2 import BaseLoader from jinja2.sandbox import SandboxedEnvironment from pydantic import Field +from apps.constants import APP_DEFAULT_HISTORY_LEN from apps.models import LanguageType from apps.scheduler.call.core import CoreCall from apps.schemas.enum_var import CallOutputType @@ -23,18 +25,18 @@ from apps.schemas.scheduler import ( from .schema import LLMInput, LLMOutput -if TYPE_CHECKING: - from apps.models.task import ExecutorHistory - logger = logging.getLogger(__name__) +def _tojson_filter(value: Any, *, ensure_ascii: bool = True, indent: int | None = None) -> str: + """自定义JSON过滤器,支持ensure_ascii和indent参数""" + return json.dumps(value, ensure_ascii=ensure_ascii, indent=indent) + + class LLM(CoreCall, input_model=LLMInput, output_model=LLMOutput): """大模型调用工具""" to_user: bool = Field(default=True) - - # 大模型参数 temperature: float = Field(description="大模型温度(随机化程度)", default=0.7) step_history_size: int = Field(description="上下文信息中包含的步骤历史数量", default=3, ge=0, le=10) history_length: int | None = Field(description="历史对话记录数量", default=None, ge=0) @@ -57,10 +59,80 @@ class LLM(CoreCall, input_model=LLMInput, output_model=LLMOutput): } return i18n_info[language] + def _build_step_memory_messages( + self, + call_vars: CallVars, + env: SandboxedEnvironment, + ) -> list[dict[str, Any]]: + """ + 构建步骤记忆消息 + + 将 step_history 格式化为标准的 OpenAI messages 格式 + """ + if self.step_history_size <= 0: + return [] + + step_history = [call_vars.step_data[ids] for ids in call_vars.step_order[-self.step_history_size:]] + + step_memory_prompt = self._load_prompt("llm_step_memory") + step_memory_tmpl = env.from_string(step_memory_prompt) + + messages: list[dict[str, Any]] = [] + for index, step in enumerate(step_history, start=1): + user_content = step_memory_tmpl.render( + role="user", + step_index=index, + step_goal=step.extraData.get("goal", "未指定目标"), + step_name=step.stepName, + input_data=step.inputData, + ) + messages.append({"role": "user", "content": user_content}) + + assistant_content = step_memory_tmpl.render( + role="assistant", + step_index=index, + step_status=step.stepStatus.value, + output_data=step.outputData, + ) + messages.append({"role": "assistant", "content": assistant_content}) + + return messages + + def _get_history_messages(self, call_vars: CallVars) -> list[dict[str, Any]]: + """获取历史对话消息""" + history_len = self.history_length + if history_len is None and call_vars.app_metadata is not None: + history_len = call_vars.app_metadata.history_len + + if history_len is None: + history_len = APP_DEFAULT_HISTORY_LEN + + if history_len <= 0: + return [] + + conversation = self._sys_vars.background.conversation + return conversation[-history_len * 2:] + + def _render_prompts( + self, + env: SandboxedEnvironment, + formatter: dict[str, Any], + ) -> tuple[str, str]: + """渲染系统提示词和用户提示词""" + try: + system_tmpl = env.from_string(self.system_prompt) + system_input = system_tmpl.render(**formatter) + + user_prompt = self._load_prompt("llm") if self.user_prompt is None else self.user_prompt + user_tmpl = env.from_string(user_prompt) + user_input = user_tmpl.render(**formatter) + except Exception as e: + raise CallError(message=f"用户提示词渲染失败:{e!s}", data={}) from e + else: + return system_input, user_input async def _prepare_message(self, call_vars: CallVars) -> list[dict[str, Any]]: """准备消息""" - # 创建共享的 Environment 实例 env = SandboxedEnvironment( loader=BaseLoader(), autoescape=False, @@ -68,54 +140,22 @@ class LLM(CoreCall, input_model=LLMInput, output_model=LLMOutput): lstrip_blocks=True, extensions=["jinja2.ext.loopcontrols"], ) + env.filters["tojson"] = _tojson_filter - # 上下文信息 - step_history: list[ExecutorHistory] = [] - for ids in call_vars.step_order[-self.step_history_size:]: - step_history += [call_vars.step_data[ids]] - - if self.step_history_size > 0: - context_tmpl = env.from_string(LLM_CONTEXT_PROMPT[self._sys_vars.language]) - context_prompt = context_tmpl.render( - reasoning=call_vars.thinking, - context_data=step_history, - ) - else: - context_prompt = "无背景信息。" - - history_messages = [] - history_len = self.history_length - if history_len is None and call_vars.app_metadata is not None: - history_len = call_vars.app_metadata.history_len - - if history_len is not None and history_len > 0: - conversation = self._sys_vars.background.conversation - # 每对消息包含 2 条记录(user + assistant) - history_messages = conversation[-history_len * 2:] + step_memory_messages = self._build_step_memory_messages(call_vars, env) + history_messages = self._get_history_messages(call_vars) - # 参数 time = datetime.now(tz=pytz.timezone("Asia/Shanghai")).strftime("%Y-%m-%d %H:%M:%S") formatter = { "time": time, - "context": context_prompt, "question": call_vars.question, "history": self._sys_vars.background.conversation, } - try: - # 准备系统提示词 - system_tmpl = env.from_string(self.system_prompt) - system_input = system_tmpl.render(**formatter) - - # 准备用户提示词 - user_prompt = self.user_prompt if self.user_prompt is not None else self._load_prompt("llm") - user_tmpl = env.from_string(user_prompt) - user_input = user_tmpl.render(**formatter) - except Exception as e: - raise CallError(message=f"用户提示词渲染失败:{e!s}", data={}) from e + system_input, user_input = self._render_prompts(env, formatter) - # 构建消息列表,将历史消息放在前面 messages = [{"role": "system", "content": system_input}] + messages.extend(step_memory_messages) messages.extend(history_messages) messages.append({"role": "user", "content": user_input}) diff --git a/apps/scheduler/call/slot/slot.py b/apps/scheduler/call/slot/slot.py index af9afbc14bfc6025ee367826914e64762009b910..a10587d1026f4a218b042f75939c45eacb90e635 100644 --- a/apps/scheduler/call/slot/slot.py +++ b/apps/scheduler/call/slot/slot.py @@ -24,6 +24,11 @@ if TYPE_CHECKING: from apps.scheduler.executor.step import StepExecutor +def _tojson_filter(value: Any, ensure_ascii: bool = True, indent: int | None = None) -> str: + """自定义JSON过滤器,支持ensure_ascii和indent参数""" + return json.dumps(value, ensure_ascii=ensure_ascii, indent=indent) + + class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): """参数填充工具""" @@ -55,6 +60,7 @@ class Slot(CoreCall, input_model=SlotInput, output_model=SlotOutput): trim_blocks=True, lstrip_blocks=True, ) + env.filters["tojson"] = _tojson_filter language = self._sys_vars.language query_template = env.from_string(SLOT_GEN_PROMPT[language]) diff --git a/apps/scheduler/executor/agent.py b/apps/scheduler/executor/agent.py index 8d09f0668762bee852571c7390d55fd32debb265..f49a81d297c268a9cb51e9f2a612141fff8cfdbb 100644 --- a/apps/scheduler/executor/agent.py +++ b/apps/scheduler/executor/agent.py @@ -38,6 +38,10 @@ class MCPAgentExecutor(BaseExecutor): async def init(self) -> None: """初始化MCP Agent""" + # 若question为空,则使用task中的userInput + if not self.question: + self.question = self.task.runtime.userInput + # 初始化必要变量 self._step_cnt: int = 0 self._retry_times: int = 0 diff --git a/apps/scheduler/executor/base.py b/apps/scheduler/executor/base.py index 66f2cc9f398d726c7e3df8b783fc00d6577a0e95..f9c9fa160e8cfa4be47c0e4e9071b00eff280b32 100644 --- a/apps/scheduler/executor/base.py +++ b/apps/scheduler/executor/base.py @@ -59,7 +59,6 @@ class BaseExecutor(BaseModel, ABC): history_questions = [] for i, record in enumerate(records): if i < n: - # 因为要reverse,所以这里要先answer后question context.extend([ { "role": "user", diff --git a/design/services/blacklist.md b/design/services/blacklist.md deleted file mode 100644 index ffdd3a3b6b8ddbbd7f9733957eccb3f0cf189277..0000000000000000000000000000000000000000 --- a/design/services/blacklist.md +++ /dev/null @@ -1,701 +0,0 @@ -# Blacklist模块设计文档 - -## 概述 - -Blacklist 模块是 openEuler Intelligence 框架中的内容审核与风控模块,负责处理问题黑名单、用户黑名单以及用户举报功能。该模块通过三个核心管理器实现内容过滤、用户信用分管理和举报审核流程,确保系统内容安全和用户行为规范。 - -## 架构设计 - -### 模块结构 - -```text -apps/ -├── models/blacklist.py # 黑名单数据模型 -├── services/blacklist.py # 黑名单服务层 -├── routers/blacklist.py # 黑名单路由层 -└── schemas/ - ├── blacklist.py # 黑名单请求响应数据结构 - └── response_data.py # 通用响应数据结构 -``` - -### 数据模型关系 - -```mermaid -erDiagram - Blacklist ||--|| Record : "关联问答对" - Record ||--|| User : "属于用户" - - Blacklist { - int id PK "主键ID" - uuid recordId FK "问答对ID" - text question "黑名单问题" - text answer "固定回答" - boolean isAudited "是否生效" - string reasonType "举报类型" - text reason "举报原因" - datetime updatedAt "更新时间" - } - - Record { - uuid id PK "问答对ID" - string userId FK "用户标识" - text content "加密内容" - text key "加密密钥" - } - - User { - int id PK "用户ID" - string userId UK "用户标识" - int credit "信用分" - boolean isWhitelisted "白名单标识" - } -``` - -## 核心功能 - -### 1. 问题黑名单管理 (QuestionBlacklistManager) - -#### 功能流程图 - -```mermaid -flowchart TD - A[问题黑名单操作] --> B{操作类型} - - B -->|检测问题| C[check_blacklisted_questions] - B -->|获取列表| D[get_blacklisted_questions] - B -->|修改/删除| E[change_blacklisted_questions] - - C --> C1[查询已审核黑名单] - C1 --> C2{输入问题是否包含黑名单内容} - C2 -->|包含| C3[返回False:拦截] - C2 -->|不包含| C4[返回True:放行] - - D --> D1[根据审核状态查询] - D1 --> D2[应用分页参数] - D2 --> D3[返回黑名单列表] - - E --> E1[根据ID查询黑名单记录] - E1 --> E2{记录是否存在} - E2 -->|不存在| E3[返回False] - E2 -->|存在| E4{是否为删除操作} - E4 -->|是| E5[删除记录] - E4 -->|否| E6[更新问题和答案] - E5 --> E7[提交事务] - E6 --> E7 - E7 --> E8[返回True] - - style C3 fill:#ffebee - style C4 fill:#e8f5e8 - style E3 fill:#ffebee - style E8 fill:#e8f5e8 -``` - -#### 主要方法 - -1. **check_blacklisted_questions(input_question)**: 检测给定问题是否触及黑名单。通过模糊匹配方式查询已审核的黑名单记录,如果输入问题包含黑名单问题内容则返回拦截信号。 - -2. **get_blacklisted_questions(limit, offset, is_audited)**: 分页获取黑名单问题列表。根据审核状态筛选记录,支持获取已生效黑名单或待审核举报内容。 - -3. **change_blacklisted_questions(blacklist_id, question, answer, - is_deletion)**: 修改或删除黑名单记录。管理员可以更新问题和答案内容, - 或彻底删除某条黑名单规则。 - -### 2. 用户黑名单管理 (UserBlacklistManager) - -#### 用户信用分更新流程 - -```mermaid -sequenceDiagram - participant Admin as 管理员 - participant Router as 黑名单路由 - participant Manager as UserBlacklistManager - participant DB as 数据库 - - Admin->>Router: POST /api/blacklist/user - Router->>Manager: change_blacklisted_users(user_id, credit_diff) - - Manager->>DB: 查询用户信息 - DB-->>Manager: 返回用户数据 - - alt 用户不存在 - Manager-->>Router: 返回False - Router-->>Admin: 返回500错误 - else 用户在白名单 - Manager-->>Router: 返回False - Router-->>Admin: 返回500错误 - else 用户已处于目标状态 - Manager-->>Router: 返回True(无需操作) - Router-->>Admin: 返回成功 - else 正常更新 - Manager->>Manager: 计算新信用分(0-100范围) - Manager->>DB: 更新用户信用分 - DB-->>Manager: 更新成功 - Manager-->>Router: 返回True - Router-->>Admin: 返回成功响应 - end -``` - -#### 核心逻辑 - -1. **信用分机制**: 用户信用分范围为0-100,信用分小于等于0的用户被视为黑名单用户,无法继续使用系统功能。 - -2. **白名单保护**: 被标记为白名单的用户不受信用分限制,任何信用分变更操作对白名单用户无效。 - -3. **状态校验**: 在执行信用分变更前检查用户当前状态,如果用户已处于目标状态(已封禁/已解禁)则直接返回成功,避免重复操作。 - -4. **边界保护**: 信用分更新时自动限制在0-100范围内,防止数值溢出。 - -#### 用户黑名单主要方法 - -1. **get_blacklisted_users(limit, offset)**: 分页获取所有信用分小于等于0的 - 黑名单用户标识列表。 - -2. **check_blacklisted_users(user_id)**: 检测指定用户是否被拉黑。 - 同时检查信用分和白名单状态,白名单用户即使信用分为0也不会被拦截。 - -3. **change_blacklisted_users(user_id, credit_diff, credit_limit)**: - 修改用户信用分。传入负值用于封禁用户,传入正值用于解禁用户。 - 系统会自动计算新信用分并确保其在有效范围内。 - -### 3. 举报管理 (AbuseManager) - -#### 举报审核完整流程 - -```mermaid -flowchart TD - A[用户发起举报] --> B[abuse_report: 提交举报] - - B --> B1[验证问答对归属] - B1 --> B2{问答对是否存在且属于该用户} - B2 -->|否| B3[返回失败] - B2 -->|是| B4[解密问答对内容] - - B4 --> B5{该问答对是否已被举报} - B5 -->|是| B6[返回成功:避免重复] - B5 -->|否| B7[创建待审核黑名单记录] - B7 --> B8[isAudited=False] - B8 --> B9[保存举报信息] - - B9 --> C[管理员审核] - - C --> D[get_blacklisted_questions: 获取待审核列表] - D --> D1[查询isAudited=False的记录] - D1 --> D2[展示给管理员] - - D2 --> E[管理员决策] - E --> F{审核结果} - - F -->|批准| G[audit_abuse_report: is_deletion=False] - F -->|拒绝| H[audit_abuse_report: is_deletion=True] - - G --> G1[设置isAudited=True] - G1 --> G2[黑名单生效] - - H --> H1[删除举报记录] - H1 --> H2[举报未通过] - - style B3 fill:#ffebee - style B6 fill:#fff3e0 - style G2 fill:#e8f5e8 - style H2 fill:#e1f5fe -``` - -#### 举报数据流转 - -```mermaid -sequenceDiagram - participant User as 用户 - participant Router as 举报路由 - participant AbuseManager as 举报管理器 - participant Security as 安全模块 - participant DB as 数据库 - - User->>Router: POST /api/blacklist/complaint - Note right of User: record_id, reason_type, reason - - Router->>AbuseManager: change_abuse_report() - AbuseManager->>DB: 查询问答对记录 - - alt 问答对不存在或不属于用户 - DB-->>AbuseManager: 返回None - AbuseManager-->>Router: 返回False - Router-->>User: 500错误:举报记录不合法 - else 问答对存在 - DB-->>AbuseManager: 返回加密的问答对 - AbuseManager->>Security: decrypt(content, key) - Security-->>AbuseManager: 返回明文内容 - - AbuseManager->>DB: 检查是否已举报 - - alt 已存在举报记录 - DB-->>AbuseManager: 返回现有记录 - AbuseManager-->>Router: 返回True(幂等) - Router-->>User: 成功响应 - else 首次举报 - AbuseManager->>DB: 创建新黑名单记录 - Note right of AbuseManager: isAudited=False
question, answer - DB-->>AbuseManager: 保存成功 - AbuseManager-->>Router: 返回True - Router-->>User: 成功响应 - end - end -``` - -#### 举报管理核心逻辑 - -1. **归属验证**: 用户只能举报属于自己的问答对记录,系统通过用户标识和 - 问答对ID双重校验确保合法性。 - -2. **内容解密**: 问答对内容在数据库中以加密形式存储,举报时需要解密 - 获取原始问题和答案文本。 - -3. **幂等保护**: 同一问答对只能被举报一次,重复举报请求返回成功但不 - 创建新记录。 - -4. **两阶段审核**: 举报内容首先进入待审核状态(isAudited=False), - 管理员审核后决定是否生效或删除。 - -5. **审核操作**: 管理员可以批准举报使黑名单生效(isAudited=True), - 或拒绝举报并删除记录。 - -#### 举报管理主要方法 - -1. **change_abuse_report(user_id, record_id, reason_type, reason)**: - 用户提交举报。验证问答对归属关系,解密内容后创建待审核黑名单记录, - 记录举报类型和原因。 - -2. **audit_abuse_report(record_id, is_deletion)**: 管理员审核举报。 - 批准时将黑名单记录标记为已审核状态使其生效,拒绝时删除举报记录。 - -## API接口设计 - -### 接口列表 - -| 方法 | 路径 | 权限 | 功能 | 描述 | -|------|------|------|------|------| -| GET | `/api/blacklist/user` | 管理员 | 获取黑名单用户 | 分页获取信用分≤0的用户列表 | -| POST | `/api/blacklist/user` | 管理员 | 操作黑名单用户 | 封禁或解禁指定用户 | -| GET | `/api/blacklist/question` | 管理员 | 获取黑名单问题 | 分页获取已生效的黑名单问题 | -| POST | `/api/blacklist/question` | 管理员 | 操作黑名单问题 | 修改或删除黑名单问题 | -| POST | `/api/blacklist/complaint` | 普通用户 | 提交举报 | 用户举报不当问答对 | -| GET | `/api/blacklist/abuse` | 管理员 | 获取待审核举报 | 分页获取待审核的举报记录 | -| POST | `/api/blacklist/abuse` | 管理员 | 审核举报 | 批准或拒绝用户举报 | - -### 请求响应示例 - -#### 1. 获取黑名单用户 - -**请求**: - -```http -GET /api/blacklist/user?page=0 -Authorization: Bearer -X-Personal-Token: -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": { - "userName": [ - "user-123", - "user-456", - "user-789" - ] - } -} -``` - -#### 2. 封禁/解禁用户 - -**请求 - 封禁用户**: - -```http -POST /api/blacklist/user -Content-Type: application/json -Authorization: Bearer -X-Personal-Token: - -{ - "userName": "user-123", - "is_ban": 1 -} -``` - -**请求 - 解禁用户**: - -```json -{ - "userName": "user-123", - "is_ban": 0 -} -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": {} -} -``` - -**错误响应**: - -```json -{ - "code": 500, - "message": "Change user blacklist error.", - "result": {} -} -``` - -#### 3. 获取黑名单问题 - -**请求**: - -```http -GET /api/blacklist/question?page=0 -Authorization: Bearer -X-Personal-Token: -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": { - "question_list": [ - { - "id": 1, - "recordId": "550e8400-e29b-41d4-a716-446655440000", - "question": "如何破解系统密码", - "answer": "很抱歉,我无法回答此类问题。", - "isAudited": true, - "reasonType": "安全风险", - "reason": "涉及系统安全破解", - "updatedAt": "2024-01-15T10:30:00Z" - }, - { - "id": 2, - "recordId": "660e8400-e29b-41d4-a716-446655440001", - "question": "如何获取他人隐私信息", - "answer": "此类问题违反隐私保护政策,无法提供帮助。", - "isAudited": true, - "reasonType": "隐私侵犯", - "reason": "涉及非法获取他人信息", - "updatedAt": "2024-01-16T14:20:00Z" - } - ] - } -} -``` - -#### 4. 修改/删除黑名单问题 - -**请求 - 修改问题**: - -```http -POST /api/blacklist/question -Content-Type: application/json -Authorization: Bearer -X-Personal-Token: - -{ - "id": "1", - "question": "如何破解系统密码或绕过认证", - "answer": "很抱歉,我无法回答任何与系统安全破解相关的问题。", - "is_deletion": 0 -} -``` - -**请求 - 删除问题**: - -```json -{ - "id": "1", - "question": "", - "answer": "", - "is_deletion": 1 -} -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": {} -} -``` - -**错误响应**: - -```json -{ - "code": 500, - "message": "Modify question blacklist error.", - "result": {} -} -``` - -#### 5. 用户提交举报 - -**请求**: - -```http -POST /api/blacklist/complaint -Content-Type: application/json -Authorization: Bearer -X-Personal-Token: - -{ - "record_id": "770e8400-e29b-41d4-a716-446655440002", - "reason_type": "内容不当", - "reason": "回答内容包含误导性信息,可能对用户造成损失" -} -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": {} -} -``` - -**错误响应**: - -```json -{ - "code": 500, - "message": "Report abuse complaint error.", - "result": {} -} -``` - -#### 6. 获取待审核举报 - -**请求**: - -```http -GET /api/blacklist/abuse?page=0 -Authorization: Bearer -X-Personal-Token: -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": { - "question_list": [ - { - "id": 3, - "recordId": "770e8400-e29b-41d4-a716-446655440002", - "question": "openEuler系统如何配置网络", - "answer": "直接修改/etc/network/interfaces文件即可", - "isAudited": false, - "reasonType": "内容不当", - "reason": "回答内容包含误导性信息,可能对用户造成损失", - "updatedAt": "2024-01-17T09:15:00Z" - } - ] - } -} -``` - -#### 7. 审核举报 - -**请求 - 批准举报(黑名单生效)**: - -```http -POST /api/blacklist/abuse -Content-Type: application/json -Authorization: Bearer -X-Personal-Token: - -{ - "id": "770e8400-e29b-41d4-a716-446655440002", - "is_deletion": 0 -} -``` - -**请求 - 拒绝举报(删除记录)**: - -```json -{ - "id": "770e8400-e29b-41d4-a716-446655440002", - "is_deletion": 1 -} -``` - -**响应**: - -```json -{ - "code": 200, - "message": "ok", - "result": {} -} -``` - -**错误响应**: - -```json -{ - "code": 500, - "message": "Audit abuse question error.", - "result": {} -} -``` - -## 数据流转图 - -### 整体架构数据流 - -```mermaid -graph TB - subgraph "客户端层" - A1[管理员客户端] - A2[用户客户端] - end - - subgraph "路由层" - B1[admin_router
管理员路由] - B2[router
普通用户路由] - end - - subgraph "依赖验证" - C1[verify_session
会话验证] - C2[verify_personal_token
令牌验证] - C3[verify_admin
管理员验证] - end - - subgraph "服务层" - D1[QuestionBlacklistManager
问题黑名单管理] - D2[UserBlacklistManager
用户黑名单管理] - D3[AbuseManager
举报管理] - end - - subgraph "数据层" - E1[(Blacklist表
黑名单记录)] - E2[(User表
用户信息)] - E3[(Record表
问答对记录)] - end - - A1 --> B1 - A2 --> B2 - - B1 --> C1 - B1 --> C2 - B1 --> C3 - B2 --> C1 - B2 --> C2 - - B1 --> D1 - B1 --> D2 - B1 --> D3 - B2 --> D3 - - D1 --> E1 - D2 --> E2 - D3 --> E1 - D3 --> E3 - - style A1 fill:#e3f2fd - style A2 fill:#f3e5f5 - style B1 fill:#fff3e0 - style B2 fill:#e8f5e9 - style D1 fill:#fce4ec - style D2 fill:#f1f8e9 - style D3 fill:#e0f2f1 - style E1 fill:#ede7f6 - style E2 fill:#fff9c4 - style E3 fill:#ffebee -``` - -### 问题检测流程 - -```mermaid -sequenceDiagram - participant User as 用户 - participant System as 业务系统 - participant QManager as QuestionBlacklistManager - participant DB as 数据库 - - User->>System: 提交问题 - System->>QManager: check_blacklisted_questions(question) - - QManager->>DB: 模糊查询黑名单 - Note right of QManager: WHERE question ILIKE '%input%'
AND isAudited = True - - alt 匹配到黑名单 - DB-->>QManager: 返回黑名单记录 - QManager->>QManager: 记录日志:问题在黑名单中 - QManager-->>System: 返回False - System-->>User: 拒绝处理并返回提示 - else 未匹配黑名单 - DB-->>QManager: 返回空结果 - QManager-->>System: 返回True - System->>System: 继续业务处理 - System-->>User: 正常响应 - end -``` - -## 安全机制 - -### 1. 权限隔离 - -- **管理员专属接口**: 用户黑名单、问题黑名单和举报审核功能仅对管理员开放,通过`verify_admin`依赖注入确保权限。 -- **用户举报保护**: 用户只能举报属于自己的问答对,系统通过用户标识和问答对ID双重验证防止恶意举报。 - -### 2. 数据安全 - -- **内容加密**: 问答对内容在数据库中加密存储,举报时需解密读取原始内容。 -- **白名单机制**: 白名单用户不受信用分限制,确保关键用户账户不会被误封。 - -### 3. 业务安全 - -- **幂等性保证**: 重复举报、重复封禁/解禁操作具有幂等性,避免重复操作导致数据异常。 -- **状态校验**: 执行操作前检查目标对象是否存在及当前状态,防止无效操作。 -- **边界保护**: 用户信用分限制在0-100范围内,防止数值溢出。 - -### 4. 审核机制 - -- **两阶段审核**: 用户举报不直接生效,需管理员审核后决定是否加入黑名单,防止恶意举报滥用。 -- **模糊匹配检测**: 黑名单问题检测采用模糊匹配(ILIKE),可拦截包含黑名单关键词的变体问题。 - -## 错误处理 - -### 常见错误场景 - -| 错误场景 | HTTP状态码 | 错误信息 | 处理建议 | -|---------|-----------|---------|---------| -| 用户不存在 | 500 | Change user blacklist error. | 检查userId是否正确 | -| 黑名单记录不存在 | 500 | Modify question blacklist error. | 检查黑名单ID是否有效 | -| 举报记录不合法 | 500 | Report abuse complaint error. | 确认问答对ID正确且属于当前用户 | -| 待审核问题不存在 | 500 | Audit abuse question error. | 检查record_id是否有效且为待审核状态 | -| 权限不足 | 403 | Forbidden | 确认用户具有管理员权限 | -| 会话过期 | 401 | Unauthorized | 重新登录获取会话令牌 | - -### 错误响应格式 - -```json -{ - "code": 500, - "message": "具体错误信息", - "result": {} -} -```