feat(dev): 本地开发工具集 v0.5.6-dev-tooling

包含本地 dev 链路完整跑通的工具集(不进生产):

backend:
- dev_auth.py: /api/dev/login Mock 企微 OAuth(/dev/* 路由)
- messages.py: dev 模式短路企微推送,避免 invalid corpid 噪音
- main.py: dev 模式启动时建 5 条 demo conversation,让前端有数据可测

frontend:
- PortalSelect.vue: dev 模式 enterRole 跳完整 URL(5173/5174/5175 端口),生产仍走相对路径

infrastructure:
- docker-compose.dev.yml: dev compose(包含 backend/postgres/redis)

scripts(Windows PowerShell):
- dev-frontend-install.ps1: 一次性装 4 个前端依赖
- dev-frontend-start.ps1: 后台起 4 个前端 dev server
- dev-check-schema-drift.ps1: 对比 SQLAlchemy 模型 vs Postgres schema,漂移 exit 1

docs:
- CURRENT-FOCUS.md: 项目状态看板(每次 session 维护)
This commit is contained in:
Simon
2026-06-16 19:24:02 +08:00
parent cec5607c45
commit eee2bcc071
9 changed files with 963 additions and 22 deletions
+165 -1
View File
@@ -12,12 +12,13 @@
import json
import logging
import os
from contextlib import asynccontextmanager
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import text
from sqlalchemy import select, text
# 导入配置(读取环境变量)
from app.config import settings
@@ -179,6 +180,7 @@ async def _init_default_data():
3. quick_reply_templates — 快速回复模板
4. approval_links — 审批流程链接
5. software_downloads — 软件下载入口
6. (dev 模式)demo_conversations — 演示用会话,让前端有数据可发
只在表为空时插入,避免重复插入。
"""
@@ -188,6 +190,7 @@ async def _init_default_data():
from app.models.quick_reply_template import QuickReplyTemplate
from app.models.approval_link import ApprovalLink
from app.models.software_download import SoftwareDownload
from app.config import settings
async_session_factory = _get_session_factory()
async with async_session_factory() as db:
@@ -207,6 +210,11 @@ async def _init_default_data():
# 5. 初始化软件下载入口
await _init_software_downloads(db, SoftwareDownload)
# 6. (dev 模式)初始化 demo 会话,让前端有数据可发
# 真因:之前没建,前端硬编码的 conv-001 调 POST /messages 返 "会话不存在" 3003
if getattr(settings, 'dev_mode', False) or os.getenv('DEV_MODE', '').lower() == 'true':
await _init_demo_conversations(db)
await db.commit()
logger.info("默认数据初始化完成")
@@ -215,6 +223,162 @@ async def _init_default_data():
logger.error(f"默认数据初始化失败: {e}")
async def _init_demo_conversations(db):
"""(dev 模式专用)建 5 条 demo 会话,让前端有数据可测。
涵盖各种状态:
- ai_handling: AI 正在处理(2 条,不同员工)
- queued: 等坐席接手
- serving: 坐席服务中
- resolved: 已结单
只在 conversations 表为空时建,避免重复。
"""
from app.models.conversation import Conversation
existing = (await db.execute(select(Conversation).limit(1))).scalar_one_or_none()
if existing:
logger.info("demo 会话已存在,跳过")
return
import uuid as _uuid
from datetime import datetime, timezone, timedelta
now = datetime.now(timezone.utc)
demo_convs = [
{
"id": "conv-001",
"corp_id": "wwa8c87970b2011f41",
"employee_id": "dev-user-001",
"employee_name": "张三(普通员工)",
"department": "财务部",
"position": "会计",
"level": "P5",
"status": "ai_handling",
"is_vip": False,
"is_pinned": False,
"is_todo": False,
"urgency_score": 30,
"tags": ["财务", "IT"],
"assigned_agent_id": None,
"collaborating_agent_ids": [],
"participants": [],
"ai_substantive_reply_count": 0,
"impact_scope": 1,
"is_blocking": False,
"emotion_state": "normal",
"dify_conversation_id": None,
"last_message_at": now - timedelta(minutes=2),
"last_message_summary": "想问下 VPN 怎么连",
},
{
"id": "conv-002",
"corp_id": "wwa8c87970b2011f41",
"employee_id": "dev-user-001",
"employee_name": "张三(普通员工)",
"department": "财务部",
"position": "会计",
"level": "P5",
"status": "queued",
"is_vip": False,
"is_pinned": True,
"is_todo": True,
"urgency_score": 70,
"tags": ["紧急", "VPN"],
"assigned_agent_id": None,
"collaborating_agent_ids": [],
"participants": [],
"ai_substantive_reply_count": 2,
"impact_scope": 3,
"is_blocking": True,
"emotion_state": "worried",
"dify_conversation_id": "dify-conv-002",
"last_message_at": now - timedelta(minutes=5),
"last_message_summary": "VPN 连不上,影响工作",
},
{
"id": "conv-003",
"corp_id": "wwa8c87970b2011f41",
"employee_id": "dev-multi-001",
"employee_name": "周八(多角色测试)",
"department": "测试部",
"position": "测试工程师",
"level": "P6",
"status": "serving",
"is_vip": True,
"is_pinned": False,
"is_todo": False,
"urgency_score": 50,
"tags": ["软件安装"],
"assigned_agent_id": "dev-agent-001",
"collaborating_agent_ids": [],
"participants": [],
"ai_substantive_reply_count": 1,
"impact_scope": 1,
"is_blocking": False,
"emotion_state": "normal",
"dify_conversation_id": "dify-conv-003",
"last_message_at": now - timedelta(minutes=10),
"last_message_summary": "需要装 WPS 专业版",
},
{
"id": "conv-004",
"corp_id": "wwa8c87970b2011f41",
"employee_id": "dev-supervisor-001",
"employee_name": "王五(部门主管)",
"department": "信息技术部",
"position": "主管",
"level": "M3",
"status": "serving",
"is_vip": True,
"is_pinned": True,
"is_todo": False,
"urgency_score": 80,
"tags": ["系统升级"],
"assigned_agent_id": "dev-agent-001",
"collaborating_agent_ids": ["dev-admin-001"],
"participants": [],
"ai_substantive_reply_count": 3,
"impact_scope": 50,
"is_blocking": True,
"emotion_state": "urgent",
"dify_conversation_id": "dify-conv-004",
"last_message_at": now - timedelta(minutes=15),
"last_message_summary": "ERP 系统升级咨询",
},
{
"id": "conv-005",
"corp_id": "wwa8c87970b2011f41",
"employee_id": "dev-security-001",
"employee_name": "赵六(安全团队)",
"department": "信息安全部",
"position": "安全工程师",
"level": "P7",
"status": "resolved",
"is_vip": False,
"is_pinned": False,
"is_todo": False,
"urgency_score": 20,
"tags": ["安全"],
"assigned_agent_id": "dev-agent-001",
"collaborating_agent_ids": [],
"participants": [],
"ai_substantive_reply_count": 5,
"impact_scope": 1,
"is_blocking": False,
"emotion_state": "normal",
"dify_conversation_id": "dify-conv-005",
"last_message_at": now - timedelta(hours=2),
"last_message_summary": "已处理:密码策略咨询",
},
]
for data in demo_convs:
db.add(Conversation(**data))
logger.info(f"已初始化 {len(demo_convs)} 条 demo 会话(仅 dev 模式)")
async def _init_system_configs(db, SystemConfig):
"""初始化系统配置项。"""
from sqlalchemy import select, func