347 lines
11 KiB
Python
347 lines
11 KiB
Python
from fastapi import FastAPI, HTTPException, Header, Depends
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
from pydantic import BaseModel
|
|
from typing import Optional, List
|
|
from datetime import datetime
|
|
import sqlite3
|
|
from pathlib import Path
|
|
import requests
|
|
|
|
# 数据库路径
|
|
DB_PATH = Path("/root/tuhui/backend/dispatch.db")
|
|
DESIGNER_DB_PATH = Path("/root/tuhui/backend/designer_status.db")
|
|
|
|
# API 密钥
|
|
API_KEY = "tuhui_dispatch_key_2026"
|
|
|
|
# 企业微信 Webhook
|
|
WECHAT_WEBHOOK = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=cc88bdef-a13f-4d7e-bdb6-ee51b68b8205"
|
|
|
|
app = FastAPI(
|
|
title="图汇派单系统 API",
|
|
description="专业的设计师派单管理系统",
|
|
version="1.0.0"
|
|
)
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_credentials=True,
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
class TaskCreate(BaseModel):
|
|
task_name: str
|
|
description: str = ""
|
|
task_type: str = "design"
|
|
priority: int = 1
|
|
deadline: Optional[str] = None
|
|
|
|
class TaskAssign(BaseModel):
|
|
designer_name: str
|
|
notes: Optional[str] = ""
|
|
|
|
class TaskComplete(BaseModel):
|
|
notes: Optional[str] = ""
|
|
|
|
def verify_api_key(x_api_key: str = Header(..., alias="X-API-Key")):
|
|
if x_api_key != API_KEY:
|
|
raise HTTPException(status_code=401, detail="Invalid API Key")
|
|
return x_api_key
|
|
|
|
def get_db_connection(db_path: Path):
|
|
conn = sqlite3.connect(str(db_path))
|
|
conn.row_factory = sqlite3.Row
|
|
return conn
|
|
|
|
def get_online_designers() -> List[str]:
|
|
conn = get_db_connection(DESIGNER_DB_PATH)
|
|
cursor = conn.cursor()
|
|
cursor.execute("SELECT real_name FROM designer_status WHERE status = 'online'")
|
|
designers = [row["real_name"] for row in cursor.fetchall()]
|
|
conn.close()
|
|
return designers
|
|
|
|
def send_wechat_notification(message: str):
|
|
"""发送企业微信通知"""
|
|
try:
|
|
requests.post(WECHAT_WEBHOOK, json={
|
|
"msgtype": "text",
|
|
"text": {"content": message}
|
|
}, timeout=5)
|
|
print(f"✅ 通知已发送")
|
|
except Exception as e:
|
|
print(f"❌ 发送通知失败:{e}")
|
|
|
|
@app.get("/health")
|
|
def health_check():
|
|
return {"status": "ok", "timestamp": datetime.now().isoformat()}
|
|
|
|
@app.get("/online/designers")
|
|
def get_online_designers_api():
|
|
designers = get_online_designers()
|
|
return {
|
|
"online_count": len(designers),
|
|
"online_users": designers,
|
|
"update_time": datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
|
}
|
|
|
|
@app.post("/tasks", tags=["任务管理"])
|
|
def create_task(task: TaskCreate, api_key: str = Depends(verify_api_key)):
|
|
import uuid
|
|
task_id = str(uuid.uuid4())[:8]
|
|
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
INSERT INTO dispatch_tasks (id, task_name, task_description, task_type, priority, status, deadline)
|
|
VALUES (?, ?, ?, ?, ?, 'pending', ?)
|
|
''', (task_id, task.task_name, task.description, task.task_type, task.priority, task.deadline))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
return {
|
|
"task_id": task_id,
|
|
"task_name": task.task_name,
|
|
"status": "pending",
|
|
"created_at": datetime.now().isoformat()
|
|
}
|
|
|
|
@app.get("/tasks/pending", tags=["任务管理"])
|
|
def get_pending_tasks(limit: int = 10, api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
SELECT id, task_name, task_description, task_type, priority, created_at, deadline
|
|
FROM dispatch_tasks
|
|
WHERE status = 'pending'
|
|
ORDER BY priority DESC, created_at ASC
|
|
LIMIT ?
|
|
''', (limit,))
|
|
|
|
tasks = []
|
|
for row in cursor.fetchall():
|
|
tasks.append({
|
|
"task_id": row["id"],
|
|
"task_name": row["task_name"],
|
|
"description": row["task_description"],
|
|
"type": row["task_type"],
|
|
"priority": row["priority"],
|
|
"created_at": row["created_at"],
|
|
"deadline": row["deadline"]
|
|
})
|
|
|
|
conn.close()
|
|
|
|
return {"total": len(tasks), "tasks": tasks}
|
|
|
|
@app.post("/tasks/{task_id}/assign", tags=["任务管理"])
|
|
def assign_task(task_id: str, assign_data: TaskAssign, api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('SELECT id, task_name, status FROM dispatch_tasks WHERE id = ?', (task_id,))
|
|
task = cursor.fetchone()
|
|
|
|
if not task:
|
|
conn.close()
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
if task["status"] != "pending":
|
|
conn.close()
|
|
raise HTTPException(status_code=400, detail=f"Task status is {task['status']}, cannot assign")
|
|
|
|
cursor.execute('''
|
|
UPDATE dispatch_tasks
|
|
SET status = 'assigned', assigned_to = ?, assigned_at = ?
|
|
WHERE id = ?
|
|
''', (assign_data.designer_name, datetime.now(), task_id))
|
|
|
|
cursor.execute('''
|
|
INSERT INTO dispatch_history (task_id, designer_name, action, notes)
|
|
VALUES (?, ?, 'assigned', ?)
|
|
''', (task_id, assign_data.designer_name, assign_data.notes))
|
|
|
|
cursor.execute('''
|
|
INSERT OR REPLACE INTO designer_workload (designer_name, pending_tasks, total_tasks, last_updated)
|
|
VALUES (
|
|
?,
|
|
COALESCE((SELECT pending_tasks FROM designer_workload WHERE designer_name = ?), 0) + 1,
|
|
COALESCE((SELECT total_tasks FROM designer_workload WHERE designer_name = ?), 0) + 1,
|
|
?
|
|
)
|
|
''', (assign_data.designer_name, assign_data.designer_name, assign_data.designer_name, datetime.now()))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# 发送群通知
|
|
notification = f"""📢【新任务派发】
|
|
|
|
🎯 任务:{task['task_name']}
|
|
👤 派给:{assign_data.designer_name}
|
|
📝 备注:{assign_data.notes or '无'}
|
|
|
|
💪 加油,看好你哦!"""
|
|
|
|
send_wechat_notification(notification)
|
|
|
|
return {
|
|
"task_id": task_id,
|
|
"assigned_to": assign_data.designer_name,
|
|
"status": "assigned",
|
|
"assigned_at": datetime.now().isoformat()
|
|
}
|
|
|
|
@app.get("/tasks/{task_id}", tags=["任务管理"])
|
|
def get_task_status(task_id: str, api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
SELECT id, task_name, status, assigned_to, created_at, assigned_at, completed_at, deadline
|
|
FROM dispatch_tasks
|
|
WHERE id = ?
|
|
''', (task_id,))
|
|
|
|
row = cursor.fetchone()
|
|
conn.close()
|
|
|
|
if not row:
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
return {
|
|
"task_id": row["id"],
|
|
"task_name": row["task_name"],
|
|
"status": row["status"],
|
|
"assigned_to": row["assigned_to"],
|
|
"created_at": row["created_at"],
|
|
"assigned_at": row["assigned_at"],
|
|
"completed_at": row["completed_at"],
|
|
"deadline": row["deadline"]
|
|
}
|
|
|
|
@app.post("/tasks/{task_id}/complete", tags=["任务管理"])
|
|
def complete_task(task_id: str, complete_data: TaskComplete, api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('SELECT assigned_to, status FROM dispatch_tasks WHERE id = ?', (task_id,))
|
|
row = cursor.fetchone()
|
|
|
|
if not row:
|
|
conn.close()
|
|
raise HTTPException(status_code=404, detail="Task not found")
|
|
|
|
if row["status"] != "assigned" and row["status"] != "in_progress":
|
|
conn.close()
|
|
raise HTTPException(status_code=400, detail=f"Task status is {row['status']}, cannot complete")
|
|
|
|
designer_name = row["assigned_to"]
|
|
|
|
cursor.execute('''
|
|
UPDATE dispatch_tasks
|
|
SET status = 'completed', completed_at = ?
|
|
WHERE id = ?
|
|
''', (datetime.now(), task_id))
|
|
|
|
cursor.execute('''
|
|
INSERT INTO dispatch_history (task_id, designer_name, action, notes)
|
|
VALUES (?, ?, 'completed', ?)
|
|
''', (task_id, designer_name, complete_data.notes))
|
|
|
|
cursor.execute('''
|
|
UPDATE designer_workload
|
|
SET pending_tasks = pending_tasks - 1,
|
|
completed_tasks = completed_tasks + 1,
|
|
last_updated = ?
|
|
WHERE designer_name = ?
|
|
''', (datetime.now(), designer_name))
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# 发送完成通知
|
|
notification = f"""✅【任务完成】
|
|
|
|
🎯 任务:{row['task_name']}
|
|
👤 设计师:{designer_name}
|
|
📝 备注:{complete_data.notes or '无'}
|
|
|
|
🎉 辛苦啦,棒棒哒!"""
|
|
|
|
send_wechat_notification(notification)
|
|
|
|
return {
|
|
"task_id": task_id,
|
|
"status": "completed",
|
|
"completed_at": datetime.now().isoformat()
|
|
}
|
|
|
|
@app.get("/designers/workload", tags=["设计师管理"])
|
|
def get_designer_workload(api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
|
|
cursor.execute('''
|
|
SELECT designer_name, total_tasks, completed_tasks, pending_tasks, last_updated
|
|
FROM designer_workload
|
|
ORDER BY total_tasks DESC
|
|
''')
|
|
|
|
workload = []
|
|
for row in cursor.fetchall():
|
|
workload.append({
|
|
"designer": row["designer_name"],
|
|
"total_tasks": row["total_tasks"],
|
|
"completed_tasks": row["completed_tasks"],
|
|
"pending_tasks": row["pending_tasks"],
|
|
"last_updated": row["last_updated"]
|
|
})
|
|
|
|
conn.close()
|
|
|
|
return {"total_designers": len(workload), "designers": workload}
|
|
|
|
@app.get("/dispatch/queue", tags=["派单队列"])
|
|
def get_dispatch_queue(api_key: str = Depends(verify_api_key)):
|
|
conn = get_db_connection(DB_PATH)
|
|
cursor = conn.cursor()
|
|
cursor.execute('''
|
|
SELECT id, task_name, task_description, task_type, priority, created_at, deadline
|
|
FROM dispatch_tasks
|
|
WHERE status = 'pending'
|
|
ORDER BY priority DESC, created_at ASC
|
|
LIMIT 20
|
|
''')
|
|
|
|
pending_tasks = []
|
|
for row in cursor.fetchall():
|
|
pending_tasks.append({
|
|
"task_id": row["id"],
|
|
"task_name": row["task_name"],
|
|
"description": row["task_description"],
|
|
"type": row["task_type"],
|
|
"priority": row["priority"],
|
|
"created_at": row["created_at"],
|
|
"deadline": row["deadline"]
|
|
})
|
|
conn.close()
|
|
|
|
online_designers = get_online_designers()
|
|
|
|
return {
|
|
"timestamp": datetime.now().isoformat(),
|
|
"pending_tasks": {"count": len(pending_tasks), "tasks": pending_tasks},
|
|
"online_designers": {"count": len(online_designers), "designers": online_designers},
|
|
"suggestion": online_designers[0] if online_designers and pending_tasks else None
|
|
}
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=8005)
|