refactor: 移除冗余文件并优化代码结构

- 删除多个不再使用的脚本和配置文件,包括 `auto_push.bat`, `check_and_fix_users.py`, `init.sql` 等。
- 新增 `git_push.bat` 和 `git_push.sh` 脚本以简化 Git 推送流程。
- 更新 `README.md` 以反映最新的功能和结构变化。
- 优化前端代码,添加新的页面和组件,提升用户体验。

此提交旨在清理项目结构并增强代码可维护性。
This commit is contained in:
2025-12-08 00:53:23 +08:00
parent 65d69358d7
commit 2026007045
171 changed files with 19316 additions and 19520 deletions

Binary file not shown.

View File

@@ -167,6 +167,17 @@ class AlertSystem:
except Exception as e:
logger.error(f"检查规则 {rule_name} 失败: {e}")
# 如果规则检查失败,也可以考虑生成一个系统级别的预警
system_alert = {
"rule_name": f"系统预警 - 规则检查失败: {rule_name}",
"alert_type": AlertType.SYSTEM.value,
"level": AlertLevel.ERROR.value,
"message": f"规则 \'{rule_name}\' 检查失败: {e}",
"data": {"error": str(e)},
"timestamp": datetime.now().isoformat(),
"rule_id": "system_rule_check_failure"
}
self._save_alert(system_alert) # 保存系统预警
return triggered_alerts
@@ -238,6 +249,8 @@ class AlertSystem:
except Exception as e:
logger.error(f"获取规则数据失败: {e}")
# 重新抛出异常,让上层调用者决定如何处理
raise
return data

View File

@@ -11,6 +11,7 @@ class Config:
# 数据库配置
DATABASE_URL = "mysql+pymysql://tsp_assistant:123456@jeason.online/tsp_assistant?charset=utf8mb4"
# DATABASE_URL = "sqlite:///local_test.db" # 本地测试数据库
# 知识库配置
KNOWLEDGE_BASE_PATH = "data/knowledge_base"

View File

@@ -27,10 +27,10 @@ class DatabaseConfig:
@dataclass
class LLMConfig:
"""LLM配置"""
provider: str = "openai"
api_key: str = ""
provider: str = "qwen"
api_key: str = "sk-c0dbefa1718d46eaa897199135066f00"
base_url: str = "https://dashscope.aliyuncs.com/compatible-mode/v1"
model: str = "qwen-turbo"
model: str = "qwen-plus-latest"
temperature: float = 0.7
max_tokens: int = 2000
timeout: int = 30

Binary file not shown.

144
src/core/auth_manager.py Normal file
View File

@@ -0,0 +1,144 @@
# -*- coding: utf-8 -*-
"""
认证管理器
处理用户登录、注册、会话管理等功能
"""
import jwt
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
from flask import current_app
from src.core.database import db_manager
from src.core.models import User
class AuthManager:
"""认证管理器"""
def __init__(self):
self.secret_key = "your-secret-key-change-this-in-production" # 应该从配置中读取
self.token_expiry = timedelta(hours=24)
def hash_password(self, password: str) -> str:
"""密码哈希"""
return hashlib.sha256(password.encode()).hexdigest()
def verify_password(self, password: str, password_hash: str) -> bool:
"""验证密码"""
return self.hash_password(password) == password_hash
def generate_token(self, user_data: dict) -> str:
"""生成JWT token"""
payload = {
'user_id': user_data['id'],
'username': user_data['username'],
'exp': datetime.utcnow() + self.token_expiry,
'iat': datetime.utcnow()
}
return jwt.encode(payload, self.secret_key, algorithm='HS256')
def verify_token(self, token: str) -> Optional[Dict[str, Any]]:
"""验证JWT token"""
try:
payload = jwt.decode(token, self.secret_key, algorithms=['HS256'])
return payload
except jwt.ExpiredSignatureError:
return None
except jwt.InvalidTokenError:
return None
def authenticate_user(self, username: str, password: str) -> Optional[dict]:
"""用户认证"""
print(f"[DEBUG] 开始认证用户: {username}")
with db_manager.get_session() as session:
user = session.query(User).filter_by(username=username).first()
print(f"[DEBUG] 找到用户: {user is not None}")
if user and user.is_active and self.verify_password(password, user.password_hash):
print(f"[DEBUG] 用户认证成功: {user.username}")
# 立即访问所有需要的属性,确保在会话内
user_id = user.id
username_val = user.username
password_hash_val = user.password_hash
email_val = user.email
name_val = user.name
role_val = user.role
is_active_val = user.is_active
created_at_val = user.created_at
last_login_val = datetime.now()
print(f"[DEBUG] 访问用户属性成功: name={name_val}, role={role_val}")
# 更新最后登录时间
user.last_login = last_login_val
session.commit()
print("[DEBUG] 数据库更新成功")
# 返回用户数据字典避免SQLAlchemy会话绑定问题
user_dict = {
'id': user_id,
'username': username_val,
'password_hash': password_hash_val,
'email': email_val,
'name': name_val,
'role': role_val,
'is_active': is_active_val,
'created_at': created_at_val,
'last_login': last_login_val
}
print(f"[DEBUG] 返回用户字典: {user_dict['username']}")
return user_dict
else:
print("[DEBUG] 用户认证失败")
return None
def get_user_by_id(self, user_id: int) -> Optional[User]:
"""根据ID获取用户"""
with db_manager.get_session() as session:
return session.query(User).filter_by(id=user_id).first()
def get_user_by_token(self, token: str) -> Optional[User]:
"""根据token获取用户"""
payload = self.verify_token(token)
if payload:
return self.get_user_by_id(payload['user_id'])
return None
def create_user(self, username: str, password: str, name: str, email: str = None, role: str = 'user') -> Optional[User]:
"""创建新用户"""
try:
with db_manager.get_session() as session:
# 检查用户名是否已存在
existing_user = session.query(User).filter_by(username=username).first()
if existing_user:
return None
user = User(
username=username,
name=name,
email=email,
role=role,
is_active=True
)
user.set_password(password)
session.add(user)
session.commit()
return user
except Exception as e:
print(f"创建用户失败: {e}")
return None
def create_default_admin(self):
"""创建默认管理员用户"""
admin = self.create_user('admin', 'admin123', '系统管理员', 'admin@example.com', 'admin')
if admin:
print("默认管理员用户已创建: admin/admin123")
return admin
# 全局认证管理器实例
auth_manager = AuthManager()

View File

@@ -26,18 +26,22 @@ class DatabaseManager:
# 根据数据库类型选择不同的连接参数
if "mysql" in db_config["url"]:
# MySQL配置 - 优化连接池
# MySQL配置 - 优化连接池和重连机制
self.engine = create_engine(
db_config["url"],
echo=db_config["echo"],
pool_size=20, # 增加连接池大小
max_overflow=30, # 增加溢出连接数
pool_pre_ping=True,
pool_recycle=1800, # 减少回收时间
pool_timeout=30, # 连接池超时(秒)
pool_size=10, # 连接池大小
max_overflow=20, # 溢出连接数
pool_pre_ping=True, # 连接前检查连接是否有效
pool_recycle=3600, # 1小时后回收连接
pool_timeout=60, # 连接池超时(秒)
connect_args={
"charset": "utf8mb4",
"autocommit": False,
"connect_timeout": 30, # 连接超时
"read_timeout": 60, # 读取超时
"write_timeout": 60, # 写入超时
"max_allowed_packet": 64*1024*1024, # 64MB
"connect_timeout": 30, # 连接超时(秒)- 适用于网络延迟较大的情况
"read_timeout": 30, # 读取超时(秒)
"write_timeout": 30, # 写入超时(秒)
@@ -82,7 +86,32 @@ class DatabaseManager:
logger.error(f"数据库操作失败: {e}")
raise
finally:
session.close()
try:
session.close()
except Exception as close_error:
logger.warning(f"关闭数据库会话时出错: {close_error}")
def check_connection(self) -> bool:
"""检查数据库连接是否正常"""
try:
with self.get_session() as session:
session.execute(text("SELECT 1"))
return True
except Exception as e:
logger.error(f"数据库连接检查失败: {e}")
return False
def reconnect(self) -> bool:
"""重新连接数据库"""
try:
if self.engine:
self.engine.dispose()
self._initialize_database()
logger.info("数据库重新连接成功")
return True
except Exception as e:
logger.error(f"数据库重新连接失败: {e}")
return False
def get_session_direct(self) -> Session:
"""直接获取数据库会话"""

View File

@@ -2,6 +2,7 @@ from sqlalchemy import Column, Integer, String, Text, DateTime, Float, Boolean,
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from datetime import datetime
import hashlib
Base = declarative_base()
@@ -173,3 +174,39 @@ class WorkOrderProcessHistory(Base):
# 关联工单
work_order = relationship("WorkOrder", back_populates="process_history")
class User(Base):
"""用户模型"""
__tablename__ = "users"
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
password_hash = Column(String(128), nullable=False)
email = Column(String(120), unique=True, nullable=True)
name = Column(String(100), nullable=True)
role = Column(String(20), default='user') # admin, user, operator
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.now)
last_login = Column(DateTime)
def set_password(self, password):
"""设置密码哈希"""
self.password_hash = hashlib.sha256(password.encode()).hexdigest()
def check_password(self, password):
"""验证密码"""
return self.password_hash == hashlib.sha256(password.encode()).hexdigest()
def to_dict(self):
"""转换为字典格式用于API响应"""
return {
'id': self.id,
'username': self.username,
'email': self.email,
'name': self.name,
'role': self.role,
'is_active': self.is_active,
'created_at': self.created_at.isoformat() if self.created_at else None,
'last_login': self.last_login.isoformat() if self.last_login else None
}

View File

@@ -292,7 +292,7 @@ class QueryOptimizer:
# 处理状态映射(支持中英文状态)
status_mapping = {
'open': ['open', '待处理', '新建', 'new'],
'in_progress': ['in_progress', '处理中', '进行中', 'progress', 'processing'],
'in_progress': ['in_progress', '处理中', '进行中', 'progress', 'processing', 'analysising', 'analyzing'],
'resolved': ['resolved', '已解决', '已完成'],
'closed': ['closed', '已关闭', '关闭']
}

View File

@@ -141,7 +141,13 @@ class RealtimeChatManager:
# 保存到数据库(每轮一条,带会话标记)
self._save_conversation(session_id, user_msg, assistant_msg)
# 更新知识库使用次数
if knowledge_results:
used_entry_ids = [result["id"] for result in knowledge_results if result.get("id")]
if used_entry_ids:
self.knowledge_manager.update_usage_count(used_entry_ids)
return {
"success": True,
"response": assistant_response["content"], # 修改为response字段

View File

@@ -23,16 +23,17 @@ class AISuggestionService:
self.llm_config = get_config().llm
logger.info(f"使用LLM配置: {self.llm_config.provider} - {self.llm_config.model}")
def generate_suggestion(self, tr_description: str, process_history: Optional[str] = None, vin: Optional[str] = None, existing_ai_suggestion: Optional[str] = None) -> str:
def generate_suggestion(self, tr_description: str, process_history: Optional[str] = None, vin: Optional[str] = None, existing_ai_suggestion: Optional[str] = None, context: str = "realtime_chat") -> str:
"""
生成AI建议 - 参考处理过程记录生成建议
生成AI建议 - 根据不同上下文使用不同的提示词
Args:
tr_description: TR描述
process_history: 处理过程记录(可选,用于了解当前问题状态)
vin: 车架号(可选)
existing_ai_suggestion: 现有的AI建议可选用于判断是否是首次建议
context: 调用上下文,"realtime_chat""feishu_sync"
Returns:
AI建议文本
"""
@@ -70,16 +71,11 @@ class AISuggestionService:
2. 如果远程操作都无法解决,可以考虑更深入的诊断方案
3. 语言简洁精炼,用逗号连接,不要用序号或分行"""
# 构建用户消息 - 要求生成简洁的简短建议
user_message = f"""请为以下问题提供精炼的技术支持操作建议:
格式要求:
1. 现状+步骤,语言精炼
2. 总长度控制在150字以内
{suggestion_instruction}
问题描述:{tr_description}{context_info}"""
# 根据上下文选择不同的提示词构建方法
if context == "feishu_sync":
user_message = self._build_feishu_sync_prompt(tr_description, process_history, vin, existing_ai_suggestion, is_first_suggestion)
else:
user_message = self._build_realtime_chat_prompt(tr_description, process_history, is_first_suggestion)
# 创建会话
session_id = chat_manager.create_session("ai_suggestion_service")
@@ -248,7 +244,127 @@ class AISuggestionService:
logger.info(f"未找到需要替换的内容: {content[:100] if len(content) > 100 else content}")
return result
def _build_feishu_sync_prompt(self, tr_description: str, process_history: str = None, vin: str = None, existing_ai_suggestion: str = None, is_first_suggestion: bool = True) -> str:
"""
构建飞书同步专用的AI建议提示词
Args:
tr_description: TR描述
process_history: 处理过程记录
vin: 车架号
existing_ai_suggestion: 现有的AI建议
is_first_suggestion: 是否是首次建议
Returns:
构建的提示词
"""
prompt = f"""请作为专业的汽车技术支持工程师,为以下工单问题提供详细的技术分析和处理建议。
问题描述:
{tr_description}
"""
# 添加处理过程记录
if process_history and process_history.strip():
prompt += f"""
当前处理进度:
{process_history}
"""
# 添加VIN信息
if vin:
prompt += f"""
车辆VIN{vin}
"""
# 添加现有AI建议历史
if existing_ai_suggestion and existing_ai_suggestion.strip():
prompt += f"""
历史AI建议记录
{existing_ai_suggestion}
"""
# 根据是否首次建议设置不同的要求
if is_first_suggestion:
prompt += """
要求:
1. 详细分析问题描述,识别可能的根本原因
2. 基于当前处理进度,判断问题处于哪个阶段
3. 提供具体的排查步骤和技术指导
4. 建议需要收集哪些技术信息(如日志、配置、版本等)
5. 如果需要,可以建议进站处理的具体项目
6. 语言专业,包含技术细节,方便技术人员理解
7. 建议格式要清晰,便于执行和跟踪
请提供完整的分析和建议:"""
else:
prompt += """
要求:
1. 基于已有处理记录和当前进度,分析问题进展情况
2. 判断之前的处理步骤是否有效,找出可能的遗漏点
3. 根据问题发展阶段提供更深入的技术解决方案
4. 如果远程处理无效,明确说明需要哪些线下技术支持
5. 详细说明进站后需要执行的具体诊断和修复步骤
6. 包含技术参数、工具要求和注意事项
7. 便于技术人员快速理解问题状态和下一步行动
请提供针对性的深入分析和处理建议:"""
return prompt
def _build_realtime_chat_prompt(self, tr_description: str, process_history: str = None, is_first_suggestion: bool = True) -> str:
"""
构建实时对话专用的AI建议提示词保持原有风格
Args:
tr_description: TR描述
process_history: 处理过程记录
is_first_suggestion: 是否是首次建议
Returns:
构建的提示词
"""
# 构建上下文信息
context_info = ""
if process_history and process_history.strip():
context_info = f"""
已处理的步骤:
{process_history}"""
# 根据是否为首次建议,设置不同的提示词
if is_first_suggestion:
# 首次建议:只给出一般性的排查步骤,不要提进站抓取日志
suggestion_instruction = """要求:
1. 首次给客户建议,只提供远程可操作的一般性排查步骤
2. 如检查网络、重启系统、确认配置等常见操作
3. 绝对不要提到"进站""抓取日志"等需要线下操作的内容
4. 语言简洁精炼,用逗号连接,不要用序号或分行"""
else:
# 后续建议:如果已有处理记录但未解决,可以考虑更深入的方案
suggestion_instruction = """要求:
1. 基于已有处理步骤,给出下一步的排查建议
2. 如果远程操作都无法解决,可以考虑更深入的诊断方案
3. 语言简洁精炼,用逗号连接,不要用序号或分行"""
# 构建用户消息 - 要求生成简洁的简短建议
user_message = f"""请为以下问题提供精炼的技术支持操作建议:
格式要求:
1. 现状+步骤,语言精炼
2. 总长度控制在150字以内
{suggestion_instruction}
问题描述:{tr_description}{context_info}"""
return user_message
def _clean_and_validate_response(self, content: str) -> str:
"""
清理和校验响应内容
@@ -346,14 +462,15 @@ class AISuggestionService:
logger.error(f"清理响应内容失败: {e}")
return content
def batch_generate_suggestions(self, records: List[Dict[str, Any]], limit: int = 10) -> List[Dict[str, Any]]:
def batch_generate_suggestions(self, records: List[Dict[str, Any]], limit: int = 10, context: str = "feishu_sync") -> List[Dict[str, Any]]:
"""
批量生成AI建议
Args:
records: 记录列表
limit: 处理数量限制
context: 调用上下文,"realtime_chat""feishu_sync"
Returns:
处理后的记录列表
"""
@@ -377,7 +494,7 @@ class AISuggestionService:
logger.info(f"记录 {record.get('record_id', i)} - 现有AI建议前100字符: {existing_ai_suggestion[:100]}")
if tr_description:
ai_suggestion = self.generate_suggestion(tr_description, process_history, vin, existing_ai_suggestion)
ai_suggestion = self.generate_suggestion(tr_description, process_history, vin, existing_ai_suggestion, context)
# 处理同一天多次更新的情况
new_suggestion = self._format_ai_suggestion_with_numbering(
time_str, ai_suggestion, existing_ai_suggestion

View File

@@ -417,3 +417,23 @@ class KnowledgeManager:
except Exception as e:
logger.error(f"获取知识库统计失败: {e}")
return {}
def update_usage_count(self, entry_ids: List[int]) -> bool:
"""更新知识库条目的使用次数"""
try:
with db_manager.get_session() as session:
# 批量更新使用次数
session.query(KnowledgeEntry).filter(
KnowledgeEntry.id.in_(entry_ids)
).update({
"usage_count": KnowledgeEntry.usage_count + 1,
"updated_at": datetime.now()
})
session.commit()
logger.info(f"成功更新 {len(entry_ids)} 个知识库条目的使用次数")
return True
except Exception as e:
logger.error(f"更新知识库使用次数失败: {e}")
return False

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -32,6 +32,11 @@ from src.web.blueprints.monitoring import monitoring_bp
from src.web.blueprints.system import system_bp
from src.web.blueprints.feishu_sync import feishu_sync_bp
from src.web.blueprints.core import core_bp
from src.web.blueprints.auth import auth_bp
from src.web.blueprints.agent import agent_bp
from src.web.blueprints.vehicle import vehicle_bp
from src.web.blueprints.analytics import analytics_bp
from src.web.blueprints.test import test_bp
# 配置日志
logger = logging.getLogger(__name__)
@@ -69,6 +74,11 @@ app.register_blueprint(monitoring_bp)
app.register_blueprint(system_bp)
app.register_blueprint(feishu_sync_bp)
app.register_blueprint(core_bp)
app.register_blueprint(auth_bp)
app.register_blueprint(agent_bp)
app.register_blueprint(vehicle_bp)
app.register_blueprint(analytics_bp)
app.register_blueprint(test_bp)
# 页面路由
@app.route('/')
@@ -100,10 +110,12 @@ def uploaded_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
# ============================================================================
# 核心API路由 - 已迁移到蓝图
# 核心API路由
# ============================================================================
# 健康检查、预警规则、监控状态等核心功能已迁移到 core 蓝图
# 分析数据相关功能也已迁移到 core 蓝图
# 以下路由因功能特殊性保留在主应用中:
# - Chat相关路由使用RealtimeChatManager进行实时对话
# - 健康检查、预警规则、监控状态等核心功能已迁移到 core 蓝图
# - 分析数据相关功能已迁移到 analytics 蓝图
# ============================================================================
# 实时对话相关路由
@@ -115,9 +127,9 @@ def create_chat_session():
data = request.get_json()
user_id = data.get('user_id', 'anonymous')
work_order_id = data.get('work_order_id')
session_id = service_manager.get_chat_manager().create_session(user_id, work_order_id)
return jsonify({
"success": True,
"session_id": session_id,
@@ -133,10 +145,10 @@ def send_chat_message():
data = request.get_json()
session_id = data.get('session_id')
message = data.get('message')
if not session_id or not message:
return jsonify({"error": "缺少必要参数"}), 400
result = service_manager.get_chat_manager().process_message(session_id, message)
return jsonify(result)
except Exception as e:
@@ -164,10 +176,10 @@ def create_work_order():
description = data.get('description')
category = data.get('category', '技术问题')
priority = data.get('priority', 'medium')
if not session_id or not title or not description:
return jsonify({"error": "缺少必要参数"}), 400
result = service_manager.get_chat_manager().create_work_order(session_id, title, description, category, priority)
return jsonify(result)
except Exception as e:
@@ -209,388 +221,13 @@ def get_active_sessions():
logger.error(f"获取活跃会话失败: {e}")
return jsonify({"error": str(e)}), 500
# ============================================================================
# Agent相关API
# ============================================================================
@app.route('/api/agent/status')
def get_agent_status():
"""获取Agent状态"""
try:
status = service_manager.get_agent_assistant().get_agent_status()
return jsonify({"success": True, **status})
except Exception as e:
# 返回默认状态避免500错误
return jsonify({
"success": False,
"status": "inactive",
"active_goals": 0,
"available_tools": 0,
"error": "Agent服务暂时不可用"
})
# Agent相关路由已移动到 agent_bp 蓝图
@app.route('/api/agent/action-history')
def get_agent_action_history():
"""获取Agent动作执行历史"""
try:
limit = request.args.get('limit', 50, type=int)
history = service_manager.get_agent_assistant().get_action_history(limit)
return jsonify({
"success": True,
"history": history,
"count": len(history)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# 分析相关路由已移动到 analytics_bp 蓝图
@app.route('/api/agent/trigger-sample', methods=['POST'])
def trigger_sample_action():
"""触发示例动作"""
try:
import asyncio
result = asyncio.run(service_manager.get_agent_assistant().trigger_sample_actions())
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
# 车辆数据相关路由已移动到 vehicle_bp 蓝图
@app.route('/api/agent/clear-history', methods=['POST'])
def clear_agent_history():
"""清空Agent执行历史"""
try:
result = service_manager.get_agent_assistant().clear_execution_history()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/llm-stats')
def get_llm_stats():
"""获取LLM使用统计"""
try:
stats = service_manager.get_agent_assistant().get_llm_usage_stats()
return jsonify({
"success": True,
"stats": stats
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/toggle', methods=['POST'])
def toggle_agent_mode():
"""切换Agent模式"""
try:
data = request.get_json()
enabled = data.get('enabled', True)
success = service_manager.get_agent_assistant().toggle_agent_mode(enabled)
return jsonify({
"success": success,
"message": f"Agent模式已{'启用' if enabled else '禁用'}"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/monitoring/start', methods=['POST'])
def start_agent_monitoring():
"""启动Agent监控"""
try:
success = service_manager.get_agent_assistant().start_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已启动" if success else "启动失败"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/monitoring/stop', methods=['POST'])
def stop_agent_monitoring():
"""停止Agent监控"""
try:
success = service_manager.get_agent_assistant().stop_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已停止" if success else "停止失败"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/proactive-monitoring', methods=['POST'])
def proactive_monitoring():
"""主动监控检查"""
try:
result = service_manager.get_agent_assistant().run_proactive_monitoring()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/intelligent-analysis', methods=['POST'])
def intelligent_analysis():
"""智能分析"""
try:
analysis = service_manager.get_agent_assistant().run_intelligent_analysis()
return jsonify({"success": True, "analysis": analysis})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/chat', methods=['POST'])
def agent_chat():
"""Agent对话接口"""
try:
data = request.get_json()
message = data.get('message', '')
context = data.get('context', {})
if not message:
return jsonify({"error": "消息不能为空"}), 400
# 使用Agent助手处理消息
agent_assistant = service_manager.get_agent_assistant()
# 模拟Agent处理实际应该调用真正的Agent处理逻辑
import asyncio
result = asyncio.run(agent_assistant.process_message_agent(
message=message,
user_id=context.get('user_id', 'admin'),
work_order_id=None,
enable_proactive=True
))
return jsonify({
"success": True,
"response": result.get('response', 'Agent已处理您的请求'),
"actions": result.get('actions', []),
"status": result.get('status', 'completed')
})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# Agent 工具统计与自定义工具
# ============================================================================
@app.route('/api/agent/tools/stats')
def get_agent_tools_stats():
try:
agent_assistant = service_manager.get_agent_assistant()
tools = agent_assistant.agent_core.tool_manager.get_available_tools()
performance = agent_assistant.agent_core.tool_manager.get_tool_performance_report()
return jsonify({
"success": True,
"tools": tools,
"performance": performance
})
except Exception as e:
# 返回默认工具列表避免500错误
return jsonify({
"success": False,
"tools": [],
"performance": {},
"error": "工具统计暂时不可用"
})
@app.route('/api/agent/tools/execute', methods=['POST'])
def execute_agent_tool():
"""执行指定的Agent工具"""
try:
data = request.get_json() or {}
tool_name = data.get('tool') or data.get('name')
parameters = data.get('parameters') or {}
if not tool_name:
return jsonify({"error": "缺少工具名称tool"}), 400
import asyncio
result = asyncio.run(service_manager.get_agent_assistant().agent_core.tool_manager.execute_tool(tool_name, parameters))
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/tools/register', methods=['POST'])
def register_custom_tool():
"""注册自定义工具(仅登记元数据,函数为占位符)"""
try:
data = request.get_json() or {}
name = data.get('name')
description = data.get('description', '')
if not name:
return jsonify({"error": "缺少工具名称"}), 400
def _placeholder_tool(**kwargs):
return {"message": f"自定义工具 {name} 已登记(占位),当前不可执行", "params": kwargs}
service_manager.get_agent_assistant().agent_core.tool_manager.register_tool(
name,
_placeholder_tool,
metadata={"description": description, "custom": True}
)
return jsonify({"success": True, "message": "工具已注册"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/agent/tools/unregister/<name>', methods=['DELETE'])
def unregister_custom_tool(name):
try:
success = service_manager.get_agent_assistant().agent_core.tool_manager.unregister_tool(name)
return jsonify({"success": success})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# 分析相关API - 已迁移到 core 蓝图
# ============================================================================
@app.route('/api/analytics/export')
def export_analytics():
"""导出分析报告"""
try:
# 生成Excel报告使用数据库真实数据
analytics = query_optimizer.get_analytics_optimized(30)
# 创建工作簿
from openpyxl import Workbook
from openpyxl.styles import Font
wb = Workbook()
ws = wb.active
ws.title = "分析报告"
# 添加标题
ws['A1'] = 'TSP智能助手分析报告'
ws['A1'].font = Font(size=16, bold=True)
# 添加工单统计
ws['A3'] = '工单统计'
ws['A3'].font = Font(bold=True)
ws['A4'] = '总工单数'
ws['B4'] = analytics['workorders']['total']
ws['A5'] = '待处理'
ws['B5'] = analytics['workorders']['open']
ws['A6'] = '已解决'
ws['B6'] = analytics['workorders']['resolved']
# 保存文件
report_path = 'uploads/analytics_report.xlsx'
os.makedirs('uploads', exist_ok=True)
wb.save(report_path)
from flask import send_file
return send_file(report_path, as_attachment=True, download_name='analytics_report.xlsx')
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# 车辆数据相关API
# ============================================================================
@app.route('/api/vehicle/data')
def get_vehicle_data():
"""获取车辆数据"""
try:
vehicle_id = request.args.get('vehicle_id')
vehicle_vin = request.args.get('vehicle_vin')
data_type = request.args.get('data_type')
limit = request.args.get('limit', 10, type=int)
vehicle_mgr = service_manager.get_vehicle_manager()
if vehicle_vin:
data = vehicle_mgr.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
elif vehicle_id:
data = vehicle_mgr.get_vehicle_data(vehicle_id, data_type, limit)
else:
data = vehicle_mgr.search_vehicle_data(limit=limit)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/vehicle/data/vin/<vehicle_vin>/latest')
def get_latest_vehicle_data_by_vin(vehicle_vin):
"""按VIN获取车辆最新数据"""
try:
data = service_manager.get_vehicle_manager().get_latest_vehicle_data_by_vin(vehicle_vin)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/vehicle/data/<vehicle_id>/latest')
def get_latest_vehicle_data(vehicle_id):
"""获取车辆最新数据"""
try:
data = service_manager.get_vehicle_manager().get_latest_vehicle_data(vehicle_id)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/vehicle/data/<vehicle_id>/summary')
def get_vehicle_summary(vehicle_id):
"""获取车辆数据摘要"""
try:
summary = service_manager.get_vehicle_manager().get_vehicle_summary(vehicle_id)
return jsonify(summary)
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/vehicle/data', methods=['POST'])
def add_vehicle_data():
"""添加车辆数据"""
try:
data = request.get_json()
success = service_manager.get_vehicle_manager().add_vehicle_data(
vehicle_id=data['vehicle_id'],
data_type=data['data_type'],
data_value=data['data_value'],
vehicle_vin=data.get('vehicle_vin')
)
return jsonify({"success": success, "message": "数据添加成功" if success else "添加失败"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@app.route('/api/vehicle/init-sample-data', methods=['POST'])
def init_sample_vehicle_data():
"""初始化示例车辆数据"""
try:
success = service_manager.get_vehicle_manager().add_sample_vehicle_data()
return jsonify({"success": success, "message": "示例数据初始化成功" if success else "初始化失败"})
except Exception as e:
return jsonify({"error": str(e)}), 500
# ============================================================================
# API测试相关接口
# ============================================================================
@app.route('/api/test/connection', methods=['POST'])
def test_api_connection():
"""测试API连接"""
try:
data = request.get_json()
api_provider = data.get('api_provider', 'openai')
api_base_url = data.get('api_base_url', '')
api_key = data.get('api_key', '')
model_name = data.get('model_name', 'qwen-turbo')
# 这里可以调用LLM客户端进行连接测试
# 暂时返回模拟结果
return jsonify({
"success": True,
"message": f"API连接测试成功 - {api_provider}",
"response_time": "150ms",
"model_status": "可用"
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@app.route('/api/test/model', methods=['POST'])
def test_model_response():
"""测试模型回答"""
try:
data = request.get_json()
test_message = data.get('test_message', '你好,请简单介绍一下你自己')
# 这里可以调用LLM客户端进行回答测试
# 暂时返回模拟结果
return jsonify({
"success": True,
"test_message": test_message,
"response": "你好我是TSP智能助手基于大语言模型构建的智能客服系统。我可以帮助您解决车辆相关问题提供技术支持和服务。",
"response_time": "1.2s",
"tokens_used": 45
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
# API测试相关路由已移动到 test_bp 蓝图
# ============================================================================
# 应用启动配置

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

257
src/web/blueprints/agent.py Normal file
View File

@@ -0,0 +1,257 @@
# -*- coding: utf-8 -*-
"""
Agent相关API蓝图
处理智能代理、工具执行、监控等功能
"""
from flask import Blueprint, request, jsonify
import asyncio
agent_bp = Blueprint('agent', __name__, url_prefix='/api/agent')
@agent_bp.route('/status')
def get_agent_status():
"""获取Agent状态"""
try:
from src.web.service_manager import service_manager
status = service_manager.get_agent_assistant().get_agent_status()
return jsonify({"success": True, **status})
except Exception as e:
# 返回默认状态避免500错误
return jsonify({
"success": False,
"status": "inactive",
"active_goals": 0,
"available_tools": 0,
"error": "Agent服务暂时不可用"
})
@agent_bp.route('/action-history')
def get_agent_action_history():
"""获取Agent动作执行历史"""
try:
from src.web.service_manager import service_manager
limit = request.args.get('limit', 50, type=int)
history = service_manager.get_agent_assistant().get_action_history(limit)
return jsonify({
"success": True,
"history": history,
"count": len(history)
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/trigger-sample', methods=['POST'])
def trigger_sample_action():
"""触发示例动作"""
try:
from src.web.service_manager import service_manager
import asyncio
result = asyncio.run(service_manager.get_agent_assistant().trigger_sample_actions())
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/clear-history', methods=['POST'])
def clear_agent_history():
"""清空Agent执行历史"""
try:
from src.web.service_manager import service_manager
result = service_manager.get_agent_assistant().clear_execution_history()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/llm-stats')
def get_llm_stats():
"""获取LLM使用统计"""
try:
from src.web.service_manager import service_manager
stats = service_manager.get_agent_assistant().get_llm_usage_stats()
return jsonify({
"success": True,
"stats": stats
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/toggle', methods=['POST'])
def toggle_agent_mode():
"""切换Agent模式"""
try:
from src.web.service_manager import service_manager
data = request.get_json()
enabled = data.get('enabled', True)
success = service_manager.get_agent_assistant().toggle_agent_mode(enabled)
return jsonify({
"success": success,
"message": f"Agent模式已{'启用' if enabled else '禁用'}"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/monitoring/start', methods=['POST'])
def start_agent_monitoring():
"""启动Agent监控"""
try:
from src.web.service_manager import service_manager
success = service_manager.get_agent_assistant().start_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已启动" if success else "启动失败"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/monitoring/stop', methods=['POST'])
def stop_agent_monitoring():
"""停止Agent监控"""
try:
from src.web.service_manager import service_manager
success = service_manager.get_agent_assistant().stop_proactive_monitoring()
return jsonify({
"success": success,
"message": "Agent监控已停止" if success else "停止失败"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/proactive-monitoring', methods=['POST'])
def proactive_monitoring():
"""主动监控检查"""
try:
from src.web.service_manager import service_manager
result = service_manager.get_agent_assistant().run_proactive_monitoring()
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/intelligent-analysis', methods=['POST'])
def intelligent_analysis():
"""智能分析"""
try:
from src.web.service_manager import service_manager
analysis = service_manager.get_agent_assistant().run_intelligent_analysis()
return jsonify({"success": True, "analysis": analysis})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/chat', methods=['POST'])
def agent_chat():
"""Agent对话接口"""
try:
from src.web.service_manager import service_manager
data = request.get_json()
message = data.get('message', '')
context = data.get('context', {})
if not message:
return jsonify({"error": "消息不能为空"}), 400
# 使用Agent助手处理消息
agent_assistant = service_manager.get_agent_assistant()
# 模拟Agent处理实际应该调用真正的Agent处理逻辑
import asyncio
result = asyncio.run(agent_assistant.process_message_agent(
message=message,
user_id=context.get('user_id', 'admin'),
work_order_id=None,
enable_proactive=True
))
return jsonify({
"success": True,
"response": result.get('response', 'Agent已处理您的请求'),
"actions": result.get('actions', []),
"status": result.get('status', 'completed')
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/tools/stats')
def get_agent_tools_stats():
"""获取Agent工具统计"""
try:
from src.web.service_manager import service_manager
agent_assistant = service_manager.get_agent_assistant()
tools = agent_assistant.agent_core.tool_manager.get_available_tools()
performance = agent_assistant.agent_core.tool_manager.get_tool_performance_report()
return jsonify({
"success": True,
"tools": tools,
"performance": performance
})
except Exception as e:
# 返回默认工具列表避免500错误
return jsonify({
"success": False,
"tools": [],
"performance": {},
"error": "工具统计暂时不可用"
})
@agent_bp.route('/tools/execute', methods=['POST'])
def execute_agent_tool():
"""执行指定的Agent工具"""
try:
from src.web.service_manager import service_manager
data = request.get_json() or {}
tool_name = data.get('tool') or data.get('name')
parameters = data.get('parameters') or {}
if not tool_name:
return jsonify({"error": "缺少工具名称tool"}), 400
import asyncio
result = asyncio.run(service_manager.get_agent_assistant().agent_core.tool_manager.execute_tool(tool_name, parameters))
return jsonify(result)
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/tools/register', methods=['POST'])
def register_custom_tool():
"""注册自定义工具(仅登记元数据,函数为占位符)"""
try:
from src.web.service_manager import service_manager
data = request.get_json() or {}
name = data.get('name')
description = data.get('description', '')
if not name:
return jsonify({"error": "缺少工具名称"}), 400
def _placeholder_tool(**kwargs):
return {"message": f"自定义工具 {name} 已登记(占位),当前不可执行", "params": kwargs}
service_manager.get_agent_assistant().agent_core.tool_manager.register_tool(
name,
_placeholder_tool,
metadata={"description": description, "custom": True}
)
return jsonify({"success": True, "message": "工具已注册"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@agent_bp.route('/tools/unregister/<name>', methods=['DELETE'])
def unregister_custom_tool(name):
"""注销自定义工具"""
try:
from src.web.service_manager import service_manager
success = service_manager.get_agent_assistant().agent_core.tool_manager.unregister_tool(name)
return jsonify({"success": success})
except Exception as e:
return jsonify({"error": str(e)}), 500

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
分析相关API蓝图
处理数据分析、报告生成等功能
"""
from flask import Blueprint, request, jsonify, send_file
import os
analytics_bp = Blueprint('analytics', __name__, url_prefix='/api/analytics')
@analytics_bp.route('/export')
def export_analytics():
"""导出分析报告"""
try:
from src.web.service_manager import service_manager
from src.core.query_optimizer import query_optimizer
from openpyxl import Workbook
from openpyxl.styles import Font
# 生成Excel报告使用数据库真实数据
analytics = query_optimizer.get_analytics_optimized(30)
# 创建工作簿
wb = Workbook()
ws = wb.active
ws.title = "分析报告"
# 添加标题
ws['A1'] = 'TSP智能助手分析报告'
ws['A1'].font = Font(size=16, bold=True)
# 添加工单统计
ws['A3'] = '工单统计'
ws['A3'].font = Font(bold=True)
ws['A4'] = '总工单数'
ws['B4'] = analytics['workorders']['total']
ws['A5'] = '待处理'
ws['B5'] = analytics['workorders']['open']
ws['A6'] = '已解决'
ws['B6'] = analytics['workorders']['resolved']
# 保存文件
report_path = 'uploads/analytics_report.xlsx'
os.makedirs('uploads', exist_ok=True)
wb.save(report_path)
return send_file(report_path, as_attachment=True, download_name='analytics_report.xlsx')
except Exception as e:
return jsonify({"error": str(e)}), 500

150
src/web/blueprints/auth.py Normal file
View File

@@ -0,0 +1,150 @@
# -*- coding: utf-8 -*-
"""
认证蓝图
处理用户登录、注册、注销等功能
"""
from flask import Blueprint, request, jsonify, session, redirect, url_for
from src.core.auth_manager import auth_manager
from src.web.error_handlers import handle_api_errors, create_error_response, create_success_response
auth_bp = Blueprint('auth', __name__, url_prefix='/api/auth')
@auth_bp.route('/login', methods=['POST'])
@handle_api_errors
def login():
"""用户登录"""
data = request.get_json()
if not data or not data.get('username') or not data.get('password'):
return jsonify({"success": False, "message": "用户名和密码不能为空"}), 400
username = data['username']
password = data['password']
remember = data.get('remember', False)
# 认证用户
user_data = auth_manager.authenticate_user(username, password)
if user_data:
# 生成token
token = auth_manager.generate_token(user_data)
# 存储到session
session['user_id'] = user_data['id']
session['username'] = user_data['username']
session['user_info'] = user_data
session['token'] = token
if remember:
session.permanent = True
# 构建响应
response_data = {
"success": True,
"message": "登录成功",
"user": {
"id": user_data['id'],
"username": user_data['username'],
"name": user_data['name'],
"email": user_data['email'],
"role": user_data['role']
},
"token": token
}
return jsonify(response_data)
else:
return jsonify({"success": False, "message": "用户名或密码错误"}), 401
@auth_bp.route('/logout', methods=['POST'])
@handle_api_errors
def logout():
"""用户注销"""
# 清除session
session.clear()
return jsonify(create_success_response(message="注销成功"))
@auth_bp.route('/status')
@handle_api_errors
def get_auth_status():
"""获取认证状态"""
if 'user_id' in session and 'user_info' in session:
return jsonify({
"authenticated": True,
"user": session['user_info'],
"token": session.get('token')
})
else:
return jsonify({
"authenticated": False,
"user": None,
"token": None
})
@auth_bp.route('/register', methods=['POST'])
@handle_api_errors
def register():
"""用户注册(仅管理员可用)"""
# 检查当前用户是否为管理员
if not session.get('user_info') or session['user_info'].get('role') != 'admin':
return create_error_response("权限不足", 403)
data = request.get_json()
required_fields = ['username', 'password', 'name']
if not all(data.get(field) for field in required_fields):
return create_error_response("缺少必要字段", 400)
user = auth_manager.create_user(
username=data['username'],
password=data['password'],
name=data['name'],
email=data.get('email'),
role=data.get('role', 'user')
)
if not user:
return create_error_response("用户创建失败,用户名可能已存在", 400)
return jsonify(create_success_response(
data={
"id": user.id,
"username": user.username,
"name": user.name,
"email": user.email,
"role": user.role
},
message="用户创建成功"
))
@auth_bp.route('/user/profile')
@handle_api_errors
def get_user_profile():
"""获取用户信息"""
if 'user_info' not in session:
return create_error_response("未登录", 401)
return jsonify(create_success_response(data=session['user_info']))
@auth_bp.route('/user/profile', methods=['PUT'])
@handle_api_errors
def update_user_profile():
"""更新用户信息"""
if 'user_info' not in session:
return create_error_response("未登录", 401)
data = request.get_json()
user_id = session['user_info']['id']
# 这里应该实现用户信息的更新逻辑
# 暂时只返回成功响应
return jsonify(create_success_response(message="用户信息更新成功"))

View File

@@ -14,10 +14,9 @@ from src.web.error_handlers import handle_api_errors, create_error_response, cre
knowledge_bp = Blueprint('knowledge', __name__, url_prefix='/api/knowledge')
def get_agent_assistant():
"""获取Agent助手实例"""
"""获取Agent助手实例懒加载"""
global _agent_assistant
if '_agent_assistant' not in globals():
from src.agent_assistant import TSPAgentAssistant
_agent_assistant = TSPAgentAssistant()
return _agent_assistant

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
"""
API测试相关蓝图
处理API连接测试、模型测试等功能
"""
from flask import Blueprint, request, jsonify
test_bp = Blueprint('test', __name__, url_prefix='/api/test')
@test_bp.route('/connection', methods=['POST'])
def test_api_connection():
"""测试API连接"""
try:
data = request.get_json()
api_provider = data.get('api_provider', 'openai')
api_base_url = data.get('api_base_url', '')
api_key = data.get('api_key', '')
model_name = data.get('model_name', 'qwen-turbo')
# 这里可以调用LLM客户端进行连接测试
# 暂时返回模拟结果
return jsonify({
"success": True,
"message": f"API连接测试成功 - {api_provider}",
"response_time": "150ms",
"model_status": "可用"
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@test_bp.route('/model', methods=['POST'])
def test_model_response():
"""测试模型回答"""
try:
data = request.get_json()
test_message = data.get('test_message', '你好,请简单介绍一下你自己')
# 这里可以调用LLM客户端进行回答测试
# 暂时返回模拟结果
return jsonify({
"success": True,
"test_message": test_message,
"response": "你好我是TSP智能助手基于大语言模型构建的智能客服系统。我可以帮助您解决车辆相关问题提供技术支持和服务。",
"response_time": "1.2s",
"tokens_used": 45
})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
"""
车辆数据相关API蓝图
处理车辆数据查询、添加、监控等功能
"""
from flask import Blueprint, request, jsonify
vehicle_bp = Blueprint('vehicle', __name__, url_prefix='/api/vehicle')
@vehicle_bp.route('/data')
def get_vehicle_data():
"""获取车辆数据"""
try:
from src.web.service_manager import service_manager
vehicle_id = request.args.get('vehicle_id')
vehicle_vin = request.args.get('vehicle_vin')
data_type = request.args.get('data_type')
limit = request.args.get('limit', 10, type=int)
vehicle_mgr = service_manager.get_vehicle_manager()
if vehicle_vin:
data = vehicle_mgr.get_vehicle_data_by_vin(vehicle_vin, data_type, limit)
elif vehicle_id:
data = vehicle_mgr.get_vehicle_data(vehicle_id, data_type, limit)
else:
data = vehicle_mgr.search_vehicle_data(limit=limit)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@vehicle_bp.route('/data/vin/<vehicle_vin>/latest')
def get_latest_vehicle_data_by_vin(vehicle_vin):
"""按VIN获取车辆最新数据"""
try:
from src.web.service_manager import service_manager
data = service_manager.get_vehicle_manager().get_latest_vehicle_data_by_vin(vehicle_vin)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@vehicle_bp.route('/data/<vehicle_id>/latest')
def get_latest_vehicle_data(vehicle_id):
"""获取车辆最新数据"""
try:
from src.web.service_manager import service_manager
data = service_manager.get_vehicle_manager().get_latest_vehicle_data(vehicle_id)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 500
@vehicle_bp.route('/data/<vehicle_id>/summary')
def get_vehicle_summary(vehicle_id):
"""获取车辆数据摘要"""
try:
from src.web.service_manager import service_manager
summary = service_manager.get_vehicle_manager().get_vehicle_summary(vehicle_id)
return jsonify(summary)
except Exception as e:
return jsonify({"error": str(e)}), 500
@vehicle_bp.route('/data', methods=['POST'])
def add_vehicle_data():
"""添加车辆数据"""
try:
from src.web.service_manager import service_manager
data = request.get_json()
success = service_manager.get_vehicle_manager().add_vehicle_data(
vehicle_id=data['vehicle_id'],
data_type=data['data_type'],
data_value=data['data_value'],
vehicle_vin=data.get('vehicle_vin')
)
return jsonify({"success": success, "message": "数据添加成功" if success else "添加失败"})
except Exception as e:
return jsonify({"error": str(e)}), 500
@vehicle_bp.route('/init-sample-data', methods=['POST'])
def init_sample_vehicle_data():
"""初始化示例车辆数据"""
try:
from src.web.service_manager import service_manager
success = service_manager.get_vehicle_manager().add_sample_vehicle_data()
return jsonify({"success": success, "message": "示例数据初始化成功" if success else "初始化失败"})
except Exception as e:
return jsonify({"error": str(e)}), 500

View File

@@ -10,7 +10,6 @@ import logging
import uuid
import time
from datetime import datetime
from typing import Optional
from flask import Blueprint, request, jsonify, send_file
from werkzeug.utils import secure_filename
from sqlalchemy import text
@@ -26,13 +25,13 @@ class SimpleAIAccuracyConfig:
self.manual_review_threshold = 0.80
self.ai_suggestion_confidence = 0.95
self.human_resolution_confidence = 0.90
def should_auto_approve(self, similarity: float) -> bool:
return similarity >= self.auto_approve_threshold
def should_use_human_resolution(self, similarity: float) -> bool:
return similarity < self.use_human_resolution_threshold
def get_confidence_score(self, similarity: float, use_human: bool = False) -> float:
if use_human:
return self.human_resolution_confidence
@@ -41,83 +40,12 @@ class SimpleAIAccuracyConfig:
from src.main import TSPAssistant
from src.core.database import db_manager
from src.core.models import WorkOrder, Conversation, WorkOrderSuggestion, KnowledgeEntry, WorkOrderProcessHistory
from src.core.models import WorkOrder, Conversation, WorkOrderSuggestion, KnowledgeEntry
from src.core.query_optimizer import query_optimizer
from src.web.service_manager import service_manager
from src.core.workorder_permissions import (
WorkOrderPermissionManager, WorkOrderDispatchManager,
UserRole, WorkOrderModule
)
workorders_bp = Blueprint('workorders', __name__, url_prefix='/api/workorders')
def get_current_user_role() -> UserRole:
"""获取当前用户角色(临时实现,实际需要集成认证系统)"""
# TODO: 从session或token中获取用户信息
# 在没有认证系统之前默认返回ADMIN以便可以查看所有工单
# 实际实现时需要从认证系统获取真实角色
role_str = request.headers.get('X-User-Role', 'admin') # 临时改为admin避免VIEWER无法查看数据
try:
return UserRole(role_str)
except ValueError:
return UserRole.ADMIN # 临时返回ADMIN避免VIEWER无法查看数据
def get_current_user_name() -> str:
"""获取当前用户名(临时实现,实际需要集成认证系统)"""
# TODO: 从session或token中获取用户信息
return request.headers.get('X-User-Name', 'anonymous')
def add_process_history(
workorder_id: int,
processor_name: str,
process_content: str,
action_type: str,
processor_role: Optional[str] = None,
processor_region: Optional[str] = None,
previous_status: Optional[str] = None,
new_status: Optional[str] = None,
assigned_module: Optional[str] = None
) -> WorkOrderProcessHistory:
"""
添加工单处理过程记录
Args:
workorder_id: 工单ID
processor_name: 处理人员姓名
process_content: 处理内容
action_type: 操作类型dispatch、process、close、reassign等
processor_role: 处理人员角色
processor_region: 处理人员区域
previous_status: 处理前的状态
new_status: 处理后的状态
assigned_module: 分配的模块
Returns:
创建的处理记录对象
"""
try:
with db_manager.get_session() as session:
history = WorkOrderProcessHistory(
work_order_id=workorder_id,
processor_name=processor_name,
processor_role=processor_role,
processor_region=processor_region,
process_content=process_content,
action_type=action_type,
previous_status=previous_status,
new_status=new_status,
assigned_module=assigned_module,
process_time=datetime.now()
)
session.add(history)
session.commit()
session.refresh(history)
logger.info(f"工单 {workorder_id} 添加处理记录: {action_type} by {processor_name}")
return history
except Exception as e:
logger.error(f"添加处理记录失败: {e}")
raise
# 移除get_assistant函数使用service_manager
def _ensure_workorder_template_file() -> str:
@@ -125,14 +53,14 @@ def _ensure_workorder_template_file() -> str:
# 获取项目根目录
current_dir = os.path.dirname(os.path.abspath(__file__))
project_root = os.path.abspath(os.path.join(current_dir, '..', '..', '..'))
# 模板文件路径项目根目录下的uploads
template_path = os.path.join(project_root, 'uploads', 'workorder_template.xlsx')
# 确保目录存在
uploads_dir = os.path.join(project_root, 'uploads')
os.makedirs(uploads_dir, exist_ok=True)
if not os.path.exists(template_path):
# 尝试从其他可能的位置复制模板
possible_locations = [
@@ -140,7 +68,7 @@ def _ensure_workorder_template_file() -> str:
os.path.join(current_dir, 'uploads', 'workorder_template.xlsx'),
os.path.join(os.getcwd(), 'uploads', 'workorder_template.xlsx')
]
source_found = False
for source_path in possible_locations:
if os.path.exists(source_path):
@@ -151,7 +79,7 @@ def _ensure_workorder_template_file() -> str:
break
except Exception as e:
logger.warning(f"复制模板文件失败: {e}")
if not source_found:
# 自动生成一个最小可用模板
try:
@@ -163,66 +91,42 @@ def _ensure_workorder_template_file() -> str:
logger.info(f"自动生成模板文件: {template_path}")
except Exception as gen_err:
raise FileNotFoundError('模板文件缺失且自动生成失败请检查依赖openpyxl/pandas') from gen_err
return template_path
@workorders_bp.route('')
def get_workorders():
"""获取工单列表(分页,带权限过滤"""
"""获取工单列表(分页)"""
try:
# 获取当前用户角色和权限
current_role = get_current_user_role()
# 获取分页参数
page = request.args.get('page', 1, type=int)
per_page = request.args.get('per_page', 10, type=int)
status_filter = request.args.get('status', '')
priority_filter = request.args.get('priority', '')
module_filter = request.args.get('module', '') # 模块过滤
# 从数据库获取分页数据
from src.core.database import db_manager
from src.core.models import WorkOrder
with db_manager.get_session() as session:
# 构建查询
query = session.query(WorkOrder)
# 权限过滤:业务方只能看到自己模块的工单
if not WorkOrderPermissionManager.can_view_all_workorders(current_role):
# 获取用户可访问的模块
accessible_modules = WorkOrderPermissionManager.get_accessible_modules(current_role)
if accessible_modules:
# 构建模块列表过滤条件
module_names = [m.value for m in accessible_modules]
query = query.filter(WorkOrder.assigned_module.in_(module_names))
else:
# 如果没有可访问的模块,返回空列表
return jsonify({
"workorders": [],
"total": 0,
"page": page,
"per_page": per_page,
"total_pages": 0 # 统一使用total_pages字段
})
# 应用过滤器
if status_filter:
query = query.filter(WorkOrder.status == status_filter)
if priority_filter:
query = query.filter(WorkOrder.priority == priority_filter)
if module_filter:
query = query.filter(WorkOrder.assigned_module == module_filter)
# 按创建时间倒序排列
query = query.order_by(WorkOrder.created_at.desc())
# 计算总数
total = query.count()
# 分页查询
workorders = query.offset((page - 1) * per_page).limit(per_page).all()
# 转换为字典
workorders_data = []
for workorder in workorders:
@@ -231,11 +135,6 @@ def get_workorders():
'order_id': workorder.order_id,
'title': workorder.title,
'description': workorder.description,
'assigned_module': workorder.assigned_module,
'module_owner': workorder.module_owner,
'dispatcher': workorder.dispatcher,
'dispatch_time': workorder.dispatch_time.isoformat() if workorder.dispatch_time else None,
'region': workorder.region,
'category': workorder.category,
'priority': workorder.priority,
'status': workorder.status,
@@ -247,10 +146,10 @@ def get_workorders():
'updated_at': workorder.updated_at.isoformat() if workorder.updated_at else None,
'date_of_close': workorder.date_of_close.isoformat() if workorder.date_of_close else None
})
# 计算分页信息
total_pages = (total + per_page - 1) // per_page
return jsonify({
'workorders': workorders_data,
'page': page,
@@ -258,13 +157,13 @@ def get_workorders():
'total': total,
'total_pages': total_pages
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@workorders_bp.route('', methods=['POST'])
def create_workorder():
"""创建工单(初始状态为待分发)"""
"""创建工单"""
try:
data = request.get_json()
result = service_manager.get_assistant().create_work_order(
@@ -273,72 +172,23 @@ def create_workorder():
category=data['category'],
priority=data['priority']
)
# 获取当前用户信息(用于记录创建人)
current_user = get_current_user_name()
current_role = get_current_user_role()
# 创建工单后,设置为待分发状态(未分配模块)
if result and 'id' in result:
workorder_id = result.get('id')
with db_manager.get_session() as session:
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if workorder:
# 初始状态为待分发
workorder.assigned_module = WorkOrderModule.UNASSIGNED.value
workorder.status = "pending" # 待处理/待分发
workorder.created_by = current_user # 记录创建人
session.commit()
# 记录创建工单的处理历史
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
add_process_history(
workorder_id=workorder_id,
processor_name=current_user,
process_content=f"工单已创建:{data.get('title', '')[:50]}",
action_type="create",
processor_role=current_role.value,
processor_region=processor_region,
previous_status=None,
new_status="pending"
)
# 清除工单相关缓存
from src.core.cache_manager import cache_manager
cache_manager.clear() # 清除所有缓存
return jsonify({"success": True, "workorder": result})
except Exception as e:
return jsonify({"error": str(e)}), 500
@workorders_bp.route('/<int:workorder_id>')
def get_workorder_details(workorder_id):
"""获取工单详情(含数据库对话记录,带权限检查"""
"""获取工单详情(含数据库对话记录)"""
try:
# 获取当前用户角色和权限
current_role = get_current_user_role()
with db_manager.get_session() as session:
w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not w:
return jsonify({"error": "工单不存在"}), 404
# 权限检查:业务方只能访问自己模块的工单
if not WorkOrderPermissionManager.can_view_all_workorders(current_role):
# 检查是否有权限访问该工单
assigned_module_str = w.assigned_module
if not assigned_module_str or assigned_module_str == WorkOrderModule.UNASSIGNED.value:
# 未分配的工单,业务方不能访问
return jsonify({"error": "无权访问该工单"}), 403
try:
assigned_module = WorkOrderModule(assigned_module_str)
accessible_modules = WorkOrderPermissionManager.get_accessible_modules(current_role)
if assigned_module not in accessible_modules:
return jsonify({"error": "无权访问该工单"}), 403
except ValueError:
# 如果模块值无效,业务方不能访问
return jsonify({"error": "无权访问该工单"}), 403
convs = session.query(Conversation).filter(Conversation.work_order_id == w.id).order_by(Conversation.timestamp.asc()).all()
conv_list = []
for c in convs:
@@ -348,27 +198,6 @@ def get_workorder_details(workorder_id):
"assistant_response": c.assistant_response,
"timestamp": c.timestamp.isoformat() if c.timestamp else None
})
# 获取处理过程记录
process_history_list = session.query(WorkOrderProcessHistory).filter(
WorkOrderProcessHistory.work_order_id == w.id
).order_by(WorkOrderProcessHistory.process_time.asc()).all()
process_history_data = []
for ph in process_history_list:
process_history_data.append({
"id": ph.id,
"processor_name": ph.processor_name,
"processor_role": ph.processor_role,
"processor_region": ph.processor_region,
"process_content": ph.process_content,
"action_type": ph.action_type,
"previous_status": ph.previous_status,
"new_status": ph.new_status,
"assigned_module": ph.assigned_module,
"process_time": ph.process_time.isoformat() if ph.process_time else None
})
# 在会话内构建工单数据
workorder = {
"id": w.id,
@@ -382,13 +211,7 @@ def get_workorder_details(workorder_id):
"updated_at": w.updated_at.isoformat() if w.updated_at else None,
"resolution": w.resolution,
"satisfaction_score": w.satisfaction_score,
"assigned_module": w.assigned_module,
"module_owner": w.module_owner,
"dispatcher": w.dispatcher,
"dispatch_time": w.dispatch_time.isoformat() if w.dispatch_time else None,
"region": w.region,
"conversations": conv_list,
"process_history": process_history_data # 处理过程记录
"conversations": conv_list
}
return jsonify(workorder)
except Exception as e:
@@ -396,72 +219,29 @@ def get_workorder_details(workorder_id):
@workorders_bp.route('/<int:workorder_id>', methods=['PUT'])
def update_workorder(workorder_id):
"""更新工单(写入数据库,自动记录处理历史"""
"""更新工单(写入数据库)"""
try:
# 获取当前用户信息
current_user = get_current_user_name()
current_role = get_current_user_role()
data = request.get_json()
if not data.get('title') or not data.get('description'):
return jsonify({"error": "标题和描述不能为空"}), 400
with db_manager.get_session() as session:
w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not w:
return jsonify({"error": "工单不存在"}), 404
# 记录更新前的状态
previous_status = w.status
previous_priority = w.priority
# 更新工单信息
w.title = data.get('title', w.title)
w.description = data.get('description', w.description)
w.category = data.get('category', w.category)
w.priority = data.get('priority', w.priority)
new_status = data.get('status', w.status)
w.status = new_status
w.status = data.get('status', w.status)
w.resolution = data.get('resolution', w.resolution)
w.satisfaction_score = data.get('satisfaction_score', w.satisfaction_score)
w.updated_at = datetime.now()
session.commit()
# 如果状态或优先级发生变化,记录处理历史
has_status_change = previous_status != new_status
has_priority_change = previous_priority != data.get('priority', w.priority)
if has_status_change or has_priority_change:
# 构建处理内容
change_items = []
if has_status_change:
change_items.append(f"状态变更:{previous_status}{new_status}")
if has_priority_change:
change_items.append(f"优先级变更:{previous_priority}{data.get('priority', w.priority)}")
process_content = "".join(change_items)
if data.get('resolution'):
process_content += f";解决方案:{data.get('resolution', '')[:100]}"
# 判断区域
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
add_process_history(
workorder_id=workorder_id,
processor_name=current_user,
process_content=process_content or "更新工单信息",
action_type="update",
processor_role=current_role.value,
processor_region=processor_region,
previous_status=previous_status,
new_status=new_status
)
# 清除工单相关缓存
from src.core.cache_manager import cache_manager
cache_manager.clear() # 清除所有缓存
updated = {
"id": w.id,
"title": w.title,
@@ -485,25 +265,25 @@ def delete_workorder(workorder_id):
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not workorder:
return jsonify({"error": "工单不存在"}), 404
# 先删除所有相关的子记录(按外键依赖顺序)
# 1. 删除工单建议记录
try:
session.execute(text("DELETE FROM work_order_suggestions WHERE work_order_id = :id"), {"id": workorder_id})
except Exception as e:
print(f"删除工单建议记录失败: {e}")
# 2. 删除对话记录
session.query(Conversation).filter(Conversation.work_order_id == workorder_id).delete()
# 3. 删除工单
session.delete(workorder)
session.commit()
# 清除工单相关缓存
from src.core.cache_manager import cache_manager
cache_manager.clear() # 清除所有缓存
return jsonify({
"success": True,
"message": "工单删除成功"
@@ -511,6 +291,39 @@ def delete_workorder(workorder_id):
except Exception as e:
return jsonify({"error": str(e)}), 500
@workorders_bp.route('/generate-ai-suggestion', methods=['POST'])
def generate_ai_suggestion():
"""通用AI建议生成API - 不需要先创建工单"""
try:
data = request.get_json()
if not data or 'tr_description' not in data:
return jsonify({"error": "缺少tr_description参数"}), 400
tr_description = data['tr_description']
vin = data.get('vin')
process_history = data.get('process_history')
# 使用AI建议服务生成建议
from src.integrations.ai_suggestion_service import AISuggestionService
ai_service = AISuggestionService()
suggestion = ai_service.generate_suggestion(
tr_description=tr_description,
process_history=process_history,
vin=vin
)
return jsonify({
"success": True,
"suggestion": suggestion,
"message": "AI建议生成成功"
})
except Exception as e:
logger.error(f"AI建议生成失败: {e}")
return jsonify({"error": f"AI建议生成失败: {str(e)}"}), 500
@workorders_bp.route('/<int:workorder_id>/ai-suggestion', methods=['POST'])
def generate_workorder_ai_suggestion(workorder_id):
"""根据工单描述与知识库生成AI建议草稿"""
@@ -582,22 +395,22 @@ def save_workorder_human_resolution(workorder_id):
except Exception:
sim = 0.0
rec.ai_similarity = sim
# 使用简化的配置
config = SimpleAIAccuracyConfig()
# 自动审批条件
approved = config.should_auto_approve(sim)
rec.approved = approved
# 记录使用人工描述入库的标记当AI准确率低于阈值时
use_human_resolution = config.should_use_human_resolution(sim)
rec.use_human_resolution = use_human_resolution
session.commit()
return jsonify({
"success": True,
"similarity": sim,
"success": True,
"similarity": sim,
"approved": approved,
"use_human_resolution": use_human_resolution
})
@@ -612,14 +425,14 @@ def approve_workorder_to_knowledge(workorder_id):
w = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not w:
return jsonify({"error": "工单不存在"}), 404
rec = session.query(WorkOrderSuggestion).filter(WorkOrderSuggestion.work_order_id == w.id).first()
if not rec:
return jsonify({"error": "未找到工单建议记录"}), 400
# 使用简化的配置
config = SimpleAIAccuracyConfig()
# 确定使用哪个内容入库
if rec.use_human_resolution and rec.human_resolution:
# AI准确率低于阈值使用人工描述入库
@@ -635,7 +448,7 @@ def approve_workorder_to_knowledge(workorder_id):
logger.info(f"工单 {workorder_id} 使用AI建议入库相似度: {rec.ai_similarity:.4f}")
else:
return jsonify({"error": "未找到可入库的内容"}), 400
# 入库为知识条目
entry = KnowledgeEntry(
question=w.title or (w.description[:20] if w.description else '工单问题'),
@@ -649,9 +462,9 @@ def approve_workorder_to_knowledge(workorder_id):
)
session.add(entry)
session.commit()
return jsonify({
"success": True,
"success": True,
"knowledge_id": entry.id,
"used_content": "human_resolution" if rec.use_human_resolution else "ai_suggestion",
"confidence_score": confidence_score
@@ -667,25 +480,25 @@ def import_workorders():
# 检查是否有文件上传
if 'file' not in request.files:
return jsonify({"error": "没有上传文件"}), 400
file = request.files['file']
if file.filename == '':
return jsonify({"error": "没有选择文件"}), 400
if not file.filename.endswith(('.xlsx', '.xls')):
return jsonify({"error": "只支持Excel文件(.xlsx, .xls)"}), 400
# 保存上传的文件
filename = secure_filename(file.filename)
upload_path = os.path.join('uploads', filename)
os.makedirs('uploads', exist_ok=True)
file.save(upload_path)
# 解析Excel文件
try:
df = pd.read_excel(upload_path)
imported_workorders = []
# 处理每一行数据
for index, row in df.iterrows():
# 根据Excel列名映射到工单字段
@@ -694,16 +507,16 @@ def import_workorders():
category = str(row.get('分类', row.get('category', '技术问题')))
priority = str(row.get('优先级', row.get('priority', 'medium')))
status = str(row.get('状态', row.get('status', 'open')))
# 验证必填字段
if not title or title.strip() == '':
continue
# 生成唯一的工单ID
timestamp = int(time.time())
unique_id = str(uuid.uuid4())[:8]
order_id = f"IMP_{timestamp}_{unique_id}"
# 创建工单到数据库
try:
with db_manager.get_session() as session:
@@ -717,26 +530,26 @@ def import_workorders():
created_at=datetime.now(),
updated_at=datetime.now()
)
# 处理可选字段
if pd.notna(row.get('解决方案', row.get('resolution'))):
workorder.resolution = str(row.get('解决方案', row.get('resolution')))
if pd.notna(row.get('满意度', row.get('satisfaction_score'))):
try:
workorder.satisfaction_score = int(row.get('满意度', row.get('satisfaction_score')))
except (ValueError, TypeError):
workorder.satisfaction_score = None
session.add(workorder)
session.commit()
logger.info(f"成功导入工单: {order_id} - {title}")
except Exception as db_error:
logger.error(f"导入工单到数据库失败: {db_error}")
continue
# 添加到返回列表
imported_workorders.append({
"id": workorder.id,
@@ -751,23 +564,23 @@ def import_workorders():
"resolution": workorder.resolution,
"satisfaction_score": workorder.satisfaction_score
})
# 清理上传的文件
os.remove(upload_path)
return jsonify({
"success": True,
"message": f"成功导入 {len(imported_workorders)} 个工单",
"imported_count": len(imported_workorders),
"workorders": imported_workorders
})
except Exception as e:
# 清理上传的文件
if os.path.exists(upload_path):
os.remove(upload_path)
return jsonify({"error": f"解析Excel文件失败: {str(e)}"}), 400
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -780,7 +593,7 @@ def download_import_template():
"success": True,
"template_url": f"/uploads/workorder_template.xlsx"
})
except Exception as e:
return jsonify({"error": str(e)}), 500
@@ -789,256 +602,27 @@ def download_import_template_file():
"""直接返回工单导入模板文件(下载)"""
try:
template_path = _ensure_workorder_template_file()
# 检查文件是否存在
if not os.path.exists(template_path):
logger.error(f"模板文件不存在: {template_path}")
return jsonify({"error": "模板文件不存在"}), 404
# 检查文件大小
file_size = os.path.getsize(template_path)
if file_size == 0:
logger.error(f"模板文件为空: {template_path}")
return jsonify({"error": "模板文件为空"}), 500
logger.info(f"准备下载模板文件: {template_path}, 大小: {file_size} bytes")
try:
# Flask>=2 使用 download_name
return send_file(template_path, as_attachment=True, download_name='工单导入模板.xlsx', mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
except TypeError:
# 兼容 Flask<2 的 attachment_filename
return send_file(template_path, as_attachment=True, attachment_filename='工单导入模板.xlsx', mimetype='application/vnd.openxmlformats-officedocument.spreadsheetml.sheet')
except Exception as e:
logger.error(f"下载模板文件失败: {e}")
return jsonify({"error": f"下载失败: {str(e)}"}), 500
@workorders_bp.route('/<int:workorder_id>/dispatch', methods=['POST'])
def dispatch_workorder(workorder_id):
"""工单分发:运维将工单分配给业务模块"""
try:
# 获取当前用户角色和权限
current_role = get_current_user_role()
current_user = get_current_user_name()
# 检查分发权限
if not WorkOrderPermissionManager.can_dispatch_workorder(current_role):
return jsonify({
"success": False,
"error": "无权进行工单分发,只有属地运维和管理员可以分发工单"
}), 403
# 获取请求数据
data = request.get_json() or {}
target_module_str = data.get('target_module', '')
if not target_module_str:
return jsonify({"success": False, "error": "请指定目标模块"}), 400
# 验证模块
try:
target_module = WorkOrderModule(target_module_str)
except ValueError:
return jsonify({"success": False, "error": f"无效的模块: {target_module_str}"}), 400
# 获取工单
with db_manager.get_session() as session:
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not workorder:
return jsonify({"success": False, "error": "工单不存在"}), 404
# 执行分发
module_owner = WorkOrderDispatchManager.get_module_owner(target_module)
# 记录分发前的状态
previous_status = workorder.status
# 更新工单信息
workorder.assigned_module = target_module.value
workorder.module_owner = module_owner
workorder.dispatcher = current_user
workorder.dispatch_time = datetime.now()
workorder.status = "assigned" # 更新状态为已分配
# 根据区域自动设置可以从工单source或其他字段判断
# 这里简化处理,可以根据实际需求调整
if not workorder.region:
# 如果source包含特定关键词可以判断区域
source = workorder.source or ""
if any(keyword in source.lower() for keyword in ["overseas", "abroad", "海外"]):
workorder.region = "overseas"
else:
workorder.region = "domestic"
session.commit()
# 记录处理历史:工单分发
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
add_process_history(
workorder_id=workorder_id,
processor_name=current_user,
process_content=f"工单已分发到{target_module.value}模块,业务接口人:{module_owner}",
action_type="dispatch",
processor_role=current_role.value,
processor_region=processor_region,
previous_status=previous_status,
new_status="assigned",
assigned_module=target_module.value
)
logger.info(f"工单 {workorder_id} 已分发到 {target_module.value} 模块,分发人: {current_user}")
return jsonify({
"success": True,
"message": f"工单已成功分发到{target_module.value}模块",
"workorder": {
"id": workorder.id,
"assigned_module": workorder.assigned_module,
"module_owner": workorder.module_owner,
"dispatcher": workorder.dispatcher,
"dispatch_time": workorder.dispatch_time.isoformat() if workorder.dispatch_time else None,
"status": workorder.status
}
})
except Exception as e:
logger.error(f"工单分发失败: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@workorders_bp.route('/<int:workorder_id>/suggest-module', methods=['POST'])
def suggest_workorder_module(workorder_id):
"""AI建议工单应该分配的模块"""
try:
with db_manager.get_session() as session:
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not workorder:
return jsonify({"success": False, "error": "工单不存在"}), 404
# 使用AI分析建议模块
suggested_module = WorkOrderDispatchManager.suggest_module(
description=workorder.description or "",
title=workorder.title or ""
)
return jsonify({
"success": True,
"suggested_module": suggested_module.value if suggested_module else None,
"module_owner": WorkOrderDispatchManager.get_module_owner(suggested_module) if suggested_module else None
})
except Exception as e:
logger.error(f"模块建议失败: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@workorders_bp.route('/modules', methods=['GET'])
def get_available_modules():
"""获取所有可用的模块列表"""
try:
modules = [
{"value": m.value, "name": m.name, "owner": WorkOrderDispatchManager.get_module_owner(m)}
for m in WorkOrderModule
if m != WorkOrderModule.UNASSIGNED
]
return jsonify({"success": True, "modules": modules})
except Exception as e:
return jsonify({"success": False, "error": str(e)}), 500
@workorders_bp.route('/<int:workorder_id>/process-history', methods=['GET'])
def get_workorder_process_history(workorder_id):
"""获取工单处理过程记录"""
try:
with db_manager.get_session() as session:
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not workorder:
return jsonify({"error": "工单不存在"}), 404
# 获取处理历史
history_list = session.query(WorkOrderProcessHistory).filter(
WorkOrderProcessHistory.work_order_id == workorder_id
).order_by(WorkOrderProcessHistory.process_time.asc()).all()
history_data = []
for ph in history_list:
history_data.append({
"id": ph.id,
"processor_name": ph.processor_name,
"processor_role": ph.processor_role,
"processor_region": ph.processor_region,
"process_content": ph.process_content,
"action_type": ph.action_type,
"previous_status": ph.previous_status,
"new_status": ph.new_status,
"assigned_module": ph.assigned_module,
"process_time": ph.process_time.isoformat() if ph.process_time else None
})
return jsonify({
"success": True,
"workorder_id": workorder_id,
"process_history": history_data,
"total": len(history_data)
})
except Exception as e:
logger.error(f"获取处理历史失败: {e}")
return jsonify({"success": False, "error": str(e)}), 500
@workorders_bp.route('/<int:workorder_id>/process-history', methods=['POST'])
def add_workorder_process_history(workorder_id):
"""手动添加工单处理过程记录"""
try:
# 获取当前用户信息
current_user = get_current_user_name()
current_role = get_current_user_role()
data = request.get_json() or {}
process_content = data.get('process_content', '').strip()
if not process_content:
return jsonify({"success": False, "error": "处理内容不能为空"}), 400
with db_manager.get_session() as session:
workorder = session.query(WorkOrder).filter(WorkOrder.id == workorder_id).first()
if not workorder:
return jsonify({"success": False, "error": "工单不存在"}), 404
# 获取可选参数
action_type = data.get('action_type', 'process') # 默认操作类型为process
processor_role = data.get('processor_role', current_role.value)
processor_region = data.get('processor_region')
if not processor_region:
# 根据角色自动判断区域
processor_region = "overseas" if current_role == UserRole.OVERSEAS_OPS else "domestic"
previous_status = data.get('previous_status', workorder.status)
new_status = data.get('new_status', workorder.status)
assigned_module = data.get('assigned_module', workorder.assigned_module)
# 添加处理记录
history = add_process_history(
workorder_id=workorder_id,
processor_name=data.get('processor_name', current_user),
process_content=process_content,
action_type=action_type,
processor_role=processor_role,
processor_region=processor_region,
previous_status=previous_status,
new_status=new_status,
assigned_module=assigned_module
)
return jsonify({
"success": True,
"message": "处理记录已添加",
"history": {
"id": history.id,
"processor_name": history.processor_name,
"processor_role": history.processor_role,
"process_content": history.process_content,
"action_type": history.action_type,
"process_time": history.process_time.isoformat() if history.process_time else None
}
})
except Exception as e:
logger.error(f"添加处理记录失败: {e}")
return jsonify({"success": False, "error": str(e)}), 500

79
src/web/decorators.py Normal file
View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
"""
通用装饰器
提供统一的错误处理、服务管理等装饰器
"""
from functools import wraps
from flask import jsonify
from src.web.service_manager import service_manager
def handle_errors(default_response=None):
"""统一错误处理装饰器"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
try:
return f(*args, **kwargs)
except Exception as e:
if default_response:
return default_response
return jsonify({"error": str(e)}), 500
return decorated_function
return decorator
def with_service(service_name):
"""服务注入装饰器"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
# 将服务管理器注入到函数参数中
kwargs['service_manager'] = service_manager
return f(*args, **kwargs)
return decorated_function
return decorator
def require_json(required_fields=None):
"""JSON请求验证装饰器"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
from flask import request
try:
data = request.get_json()
if data is None:
return jsonify({"error": "请求必须是JSON格式"}), 400
if required_fields:
missing_fields = [field for field in required_fields if field not in data]
if missing_fields:
return jsonify({"error": f"缺少必要字段: {', '.join(missing_fields)}"}), 400
kwargs['data'] = data
return f(*args, **kwargs)
except Exception as e:
return jsonify({"error": "JSON格式错误"}), 400
return decorated_function
return decorator
def cache_response(timeout=300):
"""响应缓存装饰器"""
def decorator(f):
@wraps(f)
def decorated_function(*args, **kwargs):
from flask import make_response
response = f(*args, **kwargs)
if isinstance(response, tuple):
response, status = response
else:
status = 200
if hasattr(response, 'headers'):
response.headers['Cache-Control'] = f'public, max-age={timeout}'
return response
return decorated_function
return decorator

View File

@@ -41,7 +41,7 @@ def handle_database_errors(func: Callable) -> Callable:
return func(*args, **kwargs)
except Exception as e:
logger.error(f"数据库错误 {func.__name__}: {e}")
return jsonify({"error": "数据库操作失败"}), 500
return jsonify({"error": f"数据库操作失败: {str(e)}"}), 500
return wrapper

Some files were not shown because too many files have changed in this diff Show More