chore: initial baseline with P0-safety .gitignore

This commit is contained in:
Simon
2026-06-14 16:49:18 +08:00
commit 63262292d7
510 changed files with 146008 additions and 0 deletions
+152
View File
@@ -0,0 +1,152 @@
#!/usr/bin/env python3
"""
为用户分配角色
运行方式:
cd backend
python scripts/assign_role.py <employee_id> <role_name>
示例:
python scripts/assign_role.py zhangsan agent
python scripts/assign_role.py lisi admin
"""
import sys
import os
import uuid
from datetime import datetime
# 添加 backend 目录到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from app.config import settings
from app.models import Role, UserRole
def assign_role(employee_id: str, role_name: str):
"""为指定用户分配角色"""
# 本地开发使用 aiosqlite 异步驱动,脚本是同步的,需要替换
db_url = settings.database_url.replace("sqlite+aiosqlite://", "sqlite://")
engine = create_engine(db_url)
with Session(engine) as session:
# 1. 查找角色
role = session.execute(select(Role).where(Role.name == role_name)).scalars().first()
if not role:
print(f"[FAIL] 角色 '{role_name}' 不存在")
print("可用角色: user, agent, admin")
return False
# 2. 检查是否已有该角色
existing = session.execute(
select(UserRole).where(
UserRole.employee_id == employee_id,
UserRole.role_id == role.id,
)
).scalars().first()
if existing:
print(f"[WARN] 用户 {employee_id} 已拥有角色 {role_name}")
return True
# 3. 分配角色
user_role = UserRole(
id=str(uuid.uuid4()),
employee_id=employee_id,
role_id=role.id,
source="manual", # 手动分配
assigned_at=datetime.now(),
)
session.add(user_role)
session.commit()
print(f"[OK] 已为用户 {employee_id} 分配角色 {role.display_name} ({role_name})")
return True
def remove_role(employee_id: str, role_name: str):
"""移除用户的指定角色"""
db_url = settings.database_url.replace("sqlite+aiosqlite://", "sqlite://")
engine = create_engine(db_url)
with Session(engine) as session:
# 查找角色
role = session.execute(select(Role).where(Role.name == role_name)).scalars().first()
if not role:
print(f"[FAIL] 角色 '{role_name}' 不存在")
return False
# 查找用户角色关联
user_role = session.execute(
select(UserRole).where(
UserRole.employee_id == employee_id,
UserRole.role_id == role.id,
)
).scalars().first()
if not user_role:
print(f"[WARN] 用户 {employee_id} 未拥有角色 {role_name}")
return True
# 移除角色
session.delete(user_role)
session.commit()
print(f"[OK] 已移除用户 {employee_id} 的角色 {role.display_name} ({role_name})")
return True
def list_user_roles(employee_id: str):
"""列出用户的所有角色"""
db_url = settings.database_url.replace("sqlite+aiosqlite://", "sqlite://")
engine = create_engine(db_url)
with Session(engine) as session:
# 查询用户的所有角色
user_roles = session.execute(
select(UserRole, Role)
.join(Role, UserRole.role_id == Role.id)
.where(UserRole.employee_id == employee_id)
).all()
if not user_roles:
print(f"用户 {employee_id} 暂无分配角色(默认为 user")
return
print(f"用户 {employee_id} 的角色列表:")
for user_role, role in user_roles:
print(f" - {role.name}: {role.display_name} (分配方式: {user_role.source})")
if __name__ == "__main__":
if len(sys.argv) < 2:
print("用法:")
print(" 分配角色: python assign_role.py <employee_id> <role_name>")
print(" 移除角色: python assign_role.py <employee_id> <role_name> --remove")
print(" 查看角色: python assign_role.py <employee_id> --list")
print("")
print("示例:")
print(" python assign_role.py zhangsan agent")
print(" python assign_role.py lisi admin")
print(" python assign_role.py zhangsan --list")
sys.exit(1)
employee_id = sys.argv[1]
if "--list" in sys.argv:
list_user_roles(employee_id)
elif "--remove" in sys.argv and len(sys.argv) >= 4:
role_name = sys.argv[2]
remove_role(employee_id, role_name)
elif len(sys.argv) >= 3 and not sys.argv[2].startswith("--"):
role_name = sys.argv[2]
assign_role(employee_id, role_name)
else:
print("[FAIL] 参数错误,请查看用法")
sys.exit(1)
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
初始化角色系统默认数据
运行方式:
cd backend
python scripts/init_roles.py
"""
import sys
import os
# 添加 backend 目录到 Python 路径
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from sqlalchemy import create_engine, select
from sqlalchemy.orm import Session
from app.config import settings
from app.models import Role
def init_roles():
"""初始化三个默认角色:user / agent / admin"""
# 使用配置中的数据库 URL
# 注意:本地开发使用 aiosqlite 异步驱动,但脚本是同步的
# 需要将 sqlite+aiosqlite:// 替换为 sqlite://
db_url = settings.database_url.replace("sqlite+aiosqlite://", "sqlite://")
engine = create_engine(db_url)
with Session(engine) as session:
# 检查是否已有角色数据
existing = session.execute(select(Role)).scalars().all()
if existing:
print(f"角色表已有 {len(existing)} 条数据,跳过初始化")
for role in existing:
print(f" - {role.name}: {role.display_name} (is_default={role.is_default})")
return
# 创建默认角色
roles = [
Role(
name="user",
display_name="用户",
description="所有在职员工默认角色,可提交工单、查看知识库、与 AI 对话",
permissions=["submit_ticket", "view_knowledge", "chat_with_ai", "view_own_tickets"],
is_default=True,
),
Role(
name="agent",
display_name="坐席",
description="IT 坐席人员,可处理工单、查看所有会话、使用坐席工具",
permissions=[
"submit_ticket", "view_knowledge", "chat_with_ai", "view_own_tickets",
"handle_tickets", "view_all_conversations", "use_agent_tools",
"transfer_conversations", "manage_quick_replies",
],
is_default=False,
),
Role(
name="admin",
display_name="管理员",
description="系统管理员,可管理所有配置、用户、角色和数据",
permissions=[
"submit_ticket", "view_knowledge", "chat_with_ai", "view_own_tickets",
"handle_tickets", "view_all_conversations", "use_agent_tools",
"transfer_conversations", "manage_quick_replies",
"manage_users", "manage_roles", "manage_system_config",
"view_analytics", "manage_knowledge_base",
],
is_default=False,
),
]
session.add_all(roles)
session.commit()
print("[OK] 角色初始化完成:")
for role in roles:
print(f" - {role.name}: {role.display_name}")
if __name__ == "__main__":
init_roles()
+116
View File
@@ -0,0 +1,116 @@
"""企微设备管理API验证脚本
验证公司的企微是否启用了"设备管理"功能,以及IT服务台应用是否有权限调用。
"""
import httpx
import asyncio
import json
import os
from dotenv import load_dotenv
# 加载环境变量
load_dotenv(os.path.join(os.path.dirname(__file__), ".env"))
CORP_ID = os.getenv("WECOM_CORP_ID", "")
CORP_SECRET = os.getenv("WECOM_SECRET", "")
async def main():
print("=" * 60)
print("企微设备管理API验证")
print("=" * 60)
print(f"Corp ID: {CORP_ID[:10]}***")
print()
async with httpx.AsyncClient(timeout=15.0) as client:
# === 第1步:获取 access_token ===
print("[1/3] 获取 access_token ...")
resp = await client.get(
"https://qyapi.weixin.qq.com/cgi-bin/gettoken",
params={"corpid": CORP_ID, "corpsecret": CORP_SECRET},
)
result = resp.json()
errcode = result.get("errcode", -1)
errmsg = result.get("errmsg", "")
if errcode != 0:
print(f" ❌ 获取token失败: errcode={errcode}, errmsg={errmsg}")
return
token = result["access_token"]
expires_in = result.get("expires_in", "?")
print(f" ✅ 成功 (有效期 {expires_in}秒)")
print()
# === 第2步:试探 trustdevice/list ===
print("[2/3] 试探 trustdevice/list 接口 ...")
resp2 = await client.post(
"https://qyapi.weixin.qq.com/cgi-bin/security/trustdevice/list",
params={"access_token": token},
json={"type": 1, "offset": 0, "limit": 1},
)
r2 = resp2.json()
ec2 = r2.get("errcode", -1)
em2 = r2.get("errmsg", "")
data2 = r2.get("data", {})
if ec2 == 0:
total = data2.get("total", data2.get("count", "?"))
print(f" ✅ 设备管理已启用!API可正常调用")
print(f" 设备总数: {total}")
# 显示一条设备数据样例
devices = data2.get("devices", data2.get("data", []))
if devices:
sample = json.dumps(devices[0], ensure_ascii=False, indent=2)
print(f" 设备样例数据(第1条):")
print(f" {sample[:400]}")
elif ec2 == 600001:
print(f" ❌ 设备管理未启用 或 应用无权限")
print(f" errcode={ec2}, errmsg={em2}")
else:
print(f" ⚠️ 未知返回: errcode={ec2}, errmsg={em2}")
print()
# === 第3步:试探 trustdevice/get_by_user ===
print("[3/3] 试探 trustdevice/get_by_user 接口 ...")
resp3 = await client.post(
"https://qyapi.weixin.qq.com/cgi-bin/security/trustdevice/get_by_user",
params={"access_token": token},
json={"userid": "test_user_not_exist_12345", "offset": 0, "limit": 1},
)
r3 = resp3.json()
ec3 = r3.get("errcode", -1)
em3 = r3.get("errmsg", "")
if ec3 == 0:
print(f" ✅ get_by_user 接口可调用")
print(f" 返回数据: {json.dumps(r3.get('data', {}), ensure_ascii=False)[:200]}")
elif ec3 == 600001:
print(f" ❌ 应用无权限调用此接口")
print(f" errcode={ec3}, errmsg={em3}")
elif ec3 == 60101:
# userid不存在,但接口本身可用
print(f" ✅ 接口存在且可调用 (userid不存在是预期的)")
print(f" errcode=60101 表示用户不存在,接口本身可用")
else:
print(f" ⚠️ 返回: errcode={ec3}, errmsg={em3}")
print()
# === 结论 ===
print("=" * 60)
print("验证结论")
print("=" * 60)
if ec2 == 0:
print("🎉 企微设备管理已启用,可直接集成!")
print(" 下一步: 用真实userid调用get_by_user验证映射数据")
elif ec2 == 600001:
print("📋 设备管理功能未启用或应用未授权")
print(" 需要企微管理员操作:")
print(" 1. 登录管理后台 → 安全与管理 → 设备管理 → 开启功能")
print(" 2. 在设备管理设置中,将IT服务台应用添加到「可调用接口的应用」")
else:
print(f"🔍 结果不确定 (errcode={ec2}),需进一步排查")
if __name__ == "__main__":
asyncio.run(main())