chore: initial baseline with P0-safety .gitignore
This commit is contained in:
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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())
|
||||
Reference in New Issue
Block a user