feat: P0 commercial readiness — password security, legal, account management

Backend changes for Phase A of v1.0 commercial launch:

P0-3: Password Security
- Remove hardcoded admin/123456 defaults from 34+ bootstrap scripts
- Add PLATFORM_USERNAME/PLATFORM_PASSWORD env vars to config
- Add PUT /api/v1/auth/change-password endpoint (requires old password)

P0-1: User Agreement & Privacy Policy
- Add agreed_terms/agreed_terms_version/agreed_terms_at to User model
- New GET /api/v1/legal/privacy and GET /api/v1/legal/terms endpoints
- New POST /api/v1/legal/agree endpoint to record consent
- Register endpoint now validates agreed_terms must be True

P0-6: Account Management
- Add phone/status/is_email_verified to User model
- Add DELETE /api/v1/auth/account for account deletion (with password confirmation)
- Add PUT /api/v1/auth/phone for phone binding
- Reject authentication for deleted accounts (status=deleted)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 22:01:54 +08:00
parent 96394c4b9f
commit 41ffd31923
46 changed files with 379 additions and 87 deletions

View File

@@ -39,6 +39,8 @@ class UserCreate(BaseModel):
username: str
email: str
password: str
agreed_terms: bool = False
agreed_terms_version: str | None = None
@field_validator("email")
@classmethod
@@ -47,6 +49,13 @@ class UserCreate(BaseModel):
raise ValueError("邮箱格式无效")
return v.lower()
@field_validator("agreed_terms")
@classmethod
def must_agree_terms(cls, v: bool) -> bool:
if not v:
raise ValueError("必须同意用户协议和隐私政策")
return v
class UserResponse(BaseModel):
"""用户响应模型"""
@@ -54,6 +63,7 @@ class UserResponse(BaseModel):
username: str
email: str
role: str
phone: str | None = None
class Config:
from_attributes = True
@@ -65,6 +75,9 @@ class MeResponse(BaseModel):
username: str
email: str
role: str
phone: str | None = None
status: str = "active"
is_email_verified: bool = False
workspaces: list = []
current_workspace_id: str | None = None
@@ -91,7 +104,10 @@ async def register(user_data: UserCreate, db: Session = Depends(get_db)):
user = User(
username=user_data.username,
email=user_data.email,
password_hash=hashed_password
password_hash=hashed_password,
agreed_terms=user_data.agreed_terms,
agreed_terms_version=user_data.agreed_terms_version,
agreed_terms_at=datetime.utcnow() if user_data.agreed_terms else None,
)
db.add(user)
db.commit()
@@ -180,6 +196,8 @@ async def get_current_user(
user = db.query(User).filter(User.id == user_id).first()
if user is None:
raise NotFoundError("用户", user_id)
if user.status == "deleted":
raise UnauthorizedError("账号已注销")
return user
@@ -213,6 +231,9 @@ async def get_me(
"username": user.username,
"email": user.email,
"role": user.role,
"phone": user.phone,
"status": user.status or "active",
"is_email_verified": user.is_email_verified or False,
"workspaces": workspaces,
"current_workspace_id": current_ws_id if current_ws_id else None,
}
@@ -423,6 +444,99 @@ async def reset_password(body: ResetPasswordRequest, db: Session = Depends(get_d
return {"message": "密码重置成功,请使用新密码登录"}
# ─── 修改密码 ───────────────────────────────────────────────
class ChangePasswordRequest(BaseModel):
old_password: str
new_password: str
@field_validator("new_password")
@classmethod
def password_length(cls, v: str) -> str:
if len(v) < 6:
raise ValueError("密码不少于6个字符")
if len(v) > 32:
raise ValueError("密码不超过32个字符")
return v
@router.put("/change-password")
async def change_password(
body: ChangePasswordRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""修改密码(需提供旧密码验证)。"""
if not verify_password(body.old_password, current_user.password_hash):
raise HTTPException(status_code=400, detail="旧密码错误")
current_user.password_hash = get_password_hash(body.new_password)
db.commit()
logger.info("用户 %s 修改密码成功", current_user.username)
return {"message": "密码修改成功"}
# ─── 账号注销 ───────────────────────────────────────────────
class DeleteAccountRequest(BaseModel):
password: str
@router.delete("/account")
async def delete_account(
body: DeleteAccountRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""注销账号(需提供密码确认)。软删除:设置 status='deleted'。"""
if not verify_password(body.password, current_user.password_hash):
raise HTTPException(status_code=400, detail="密码错误")
# 软删除用户
current_user.status = "deleted"
current_user.email = f"deleted_{current_user.id}@deleted.local"
current_user.phone = None
current_user.feishu_open_id = None
db.commit()
logger.info("用户 %s 账号已注销", current_user.username)
return {"message": "账号已注销"}
# ─── 绑定手机号 ──────────────────────────────────────────────
class BindPhoneRequest(BaseModel):
phone: str
code: str | None = None # 验证码(对接短信服务后可启用)
@field_validator("phone")
@classmethod
def phone_format(cls, v: str) -> str:
if not re.match(r"^1[3-9]\d{9}$", v):
raise ValueError("手机号格式无效")
return v
@router.put("/phone")
async def bind_phone(
body: BindPhoneRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""绑定或修改手机号。"""
# 检查手机号是否已被其他用户绑定
existing = db.query(User).filter(User.phone == body.phone, User.id != current_user.id).first()
if existing:
raise ConflictError("该手机号已被其他用户绑定")
current_user.phone = body.phone
db.commit()
logger.info("用户 %s 绑定手机号成功", current_user.username)
return {"message": "手机号绑定成功", "phone": body.phone}
async def get_optional_user(
token: str | None = Depends(oauth2_scheme_optional),
db: Session = Depends(get_db)

161
backend/app/api/legal.py Normal file
View File

@@ -0,0 +1,161 @@
"""
用户协议 & 隐私政策 API
"""
from fastapi import APIRouter, Depends
from pydantic import BaseModel
from sqlalchemy.orm import Session
from datetime import datetime
from app.core.database import get_db
from app.core.exceptions import ConflictError
from app.api.auth import get_current_user
from app.models.user import User
router = APIRouter(
prefix="/api/v1/legal",
tags=["legal"],
)
# ─── 协议版本 ───────────────────────────────────────────────
CURRENT_TERMS_VERSION = "1.0"
CURRENT_PRIVACY_VERSION = "1.0"
# ─── 内容 ───────────────────────────────────────────────────
PRIVACY_POLICY = """# 天工智能体平台 隐私政策
**更新日期:2026年7月1日**
**生效日期:2026年7月1日**
## 一、信息收集
1. 您在注册账户时提供的个人信息,包括用户名、邮箱地址和手机号码。
2. 您在使用智能体对话功能时产生的对话内容和交互记录。
3. 设备信息:我们可能收集您的设备型号、操作系统版本、设备标识符等信息用于优化服务。
## 二、信息使用
1. 为您提供智能体对话、语音识别、推送通知等核心服务。
2. 改善产品质量和用户体验,包括对话模型的训练和优化。
3. 提供客户支持和故障排除。
## 三、信息存储和保护
1. 您的数据存储在中国境内的服务器上。
2. 我们采用加密传输、访问控制、数据备份等安全措施保护您的信息。
3. 对话数据在您主动删除或账号注销后将在30天内清除。
## 四、信息共享
1. 未经您的明确同意,我们不会将您的个人信息共享给第三方。
2. 法律法规要求披露的除外。
## 五、您的权利
1. 您可以随时查看、修改您的个人信息。
2. 您可以导出您的对话数据。
3. 您可以注销账号并要求删除数据。
## 六、联系我们
如有隐私相关问题,请联系:support@tiangong.ai
"""
USER_AGREEMENT = """# 天工智能体平台 用户协议
**更新日期:2026年7月1日**
**生效日期:2026年7月1日**
## 一、总则
1. 天工智能体平台(以下简称"本平台")是一个提供AI智能体服务的在线平台。
2. 您注册和使用本平台即表示您已阅读、理解并同意本协议的全部条款。
## 二、账号管理
1. 您应当提供真实、准确的注册信息。
2. 您对账号下的所有活动负责,请妥善保管账号和密码。
3. 您不得将账号转让、出借或出售给他人。
## 三、使用规范
1. 您不得利用本平台从事违法活动,包括但不限于:
- 生成、传播违法或有害内容
- 侵犯他人知识产权或隐私权
- 干扰或破坏平台正常运行
2. 您不得对智能体进行恶意引导、注入攻击或其他危害系统安全的行为。
## 四、知识产权
1. 平台本身的软件、界面设计、商标等知识产权归平台所有。
2. 您创建的智能体配置归您所有。
3. 您与智能体对话产生的内容归您所有。
## 五、免责声明
1. AI生成内容仅供参考,不构成专业建议。
2. 平台不对AI生成内容的准确性、完整性负责。
3. 因不可抗力或第三方原因导致的服务中断,平台不承担责任。
## 六、协议变更
1. 我们可能会不时更新本协议,更新后会通过应用内通知告知。
2. 如果您不同意更新后的条款,您可以停止使用本平台并注销账号。
## 七、法律适用与争议解决
1. 本协议适用中华人民共和国法律。
2. 因本协议产生的争议,双方应协商解决;协商不成的,提交平台所在地法院管辖。
"""
# ─── Response/Request Models ──────────────────────────────────
class LegalContentResponse(BaseModel):
content: str
version: str
class AgreeRequest(BaseModel):
terms_version: str | None = None
privacy_version: str | None = None
class AgreeResponse(BaseModel):
message: str
agreed_terms: bool
agreed_terms_version: str
# ─── Endpoints ────────────────────────────────────────────────
@router.get("/privacy", response_model=LegalContentResponse)
async def get_privacy_policy():
"""获取隐私政策内容。"""
return LegalContentResponse(content=PRIVACY_POLICY, version=CURRENT_PRIVACY_VERSION)
@router.get("/terms", response_model=LegalContentResponse)
async def get_user_agreement():
"""获取用户协议内容。"""
return LegalContentResponse(content=USER_AGREEMENT, version=CURRENT_TERMS_VERSION)
@router.post("/agree", response_model=AgreeResponse)
async def agree_terms(
body: AgreeRequest,
current_user: User = Depends(get_current_user),
db: Session = Depends(get_db),
):
"""记录用户同意协议。"""
current_user.agreed_terms = True
current_user.agreed_terms_version = body.terms_version or CURRENT_TERMS_VERSION
current_user.agreed_terms_at = datetime.utcnow()
db.commit()
return AgreeResponse(
message="协议同意已记录",
agreed_terms=True,
agreed_terms_version=current_user.agreed_terms_version,
)

View File

@@ -110,6 +110,10 @@ class Settings(BaseSettings):
FEISHU_APP_SECRET: str = ""
FEISHU_VERIFICATION_TOKEN: str = ""
# 平台管理员凭据(所有 bootstrap 脚本从环境变量读取,不再硬编码默认值)
PLATFORM_USERNAME: str = ""
PLATFORM_PASSWORD: str = ""
# 安全加固 — HSTS(生产环境建议启用,开发环境保持关闭)
HSTS_ENABLED: bool = False
HSTS_MAX_AGE: int = 31536000 # 默认 1 年

View File

@@ -510,7 +510,7 @@ async def startup_event():
logger.error(f"定时任务调度器启动失败: {e}")
# 注册路由
from app.api import auth, workspaces, uploads, workflows, executions, websocket, execution_logs, data_sources, agents, platform_templates, model_configs, webhooks, template_market, batch_operations, collaboration, permissions, monitoring, alert_rules, node_test, node_templates, tools, agent_chat, agent_branches, agent_monitoring, knowledge_base, knowledge_dashboard, agent_schedules, notifications, feishu_bind, approval, orchestration_templates, plugins, agent_market, goals, tasks, system_logs, audit_logs, feedback, agent_swarm, push, voice, fcm, scene_contracts, teams, agent_memory
from app.api import auth, workspaces, uploads, workflows, executions, websocket, execution_logs, data_sources, agents, platform_templates, model_configs, webhooks, template_market, batch_operations, collaboration, permissions, monitoring, alert_rules, node_test, node_templates, tools, agent_chat, agent_branches, agent_monitoring, knowledge_base, knowledge_dashboard, agent_schedules, notifications, feishu_bind, approval, orchestration_templates, plugins, agent_market, goals, tasks, system_logs, audit_logs, feedback, agent_swarm, push, voice, fcm, scene_contracts, teams, agent_memory, legal
app.include_router(auth.router)
app.include_router(workspaces.router)
@@ -557,6 +557,7 @@ app.include_router(knowledge_dashboard.router)
app.include_router(scene_contracts.router)
app.include_router(teams.router)
app.include_router(agent_memory.router)
app.include_router(legal.router)
if __name__ == "__main__":
import uvicorn

View File

@@ -1,7 +1,7 @@
"""
用户模型
"""
from sqlalchemy import Column, String, DateTime, func
from sqlalchemy import Boolean, Column, String, DateTime, func
from sqlalchemy.dialects.mysql import CHAR
from sqlalchemy.orm import relationship
from app.core.database import Base
@@ -19,6 +19,12 @@ class User(Base):
role = Column(String(20), default="user", comment="角色: admin/user(保留字段,用于向后兼容)")
feishu_open_id = Column(String(64), nullable=True, comment="飞书用户 open_id,用于推送通知")
feishu_default_agent_id = Column(CHAR(36), nullable=True, comment="飞书对话默认 Agent ID")
phone = Column(String(20), nullable=True, comment="手机号")
status = Column(String(20), default="active", comment="账号状态: active/disabled/deleted")
is_email_verified = Column(Boolean, default=False, comment="邮箱是否已验证")
agreed_terms = Column(Boolean, default=False, comment="是否同意用户协议")
agreed_terms_version = Column(String(20), nullable=True, comment="同意的协议版本")
agreed_terms_at = Column(DateTime, nullable=True, comment="同意协议的时间")
created_at = Column(DateTime, default=func.now(), comment="创建时间")
updated_at = Column(DateTime, default=func.now(), onupdate=func.now(), comment="更新时间")

View File

@@ -22,8 +22,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
TEMPLATES: List[Tuple[str, str, str]] = [
("模板-客服标准", "知你客服14号", "场景模板:标准客服 + 全量工具基线"),

View File

@@ -16,8 +16,8 @@ from typing import Any, Dict, List, Optional
import requests
BACKEND = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NAMES = ["学生作业管理助手", "学生作业管理助手2号"]

View File

@@ -25,8 +25,8 @@ from typing import Any, Dict, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
AGENT_NAME = os.getenv("AGENT_NAME", "AI学习助手")
MODEL = os.getenv("MODEL", "deepseek-v4-flash")
PROVIDER = os.getenv("PROVIDER", "deepseek")

View File

@@ -159,8 +159,8 @@ def _via_requests() -> int:
import requests
base = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
user = os.getenv("PLATFORM_USERNAME", "admin")
pwd = os.getenv("PLATFORM_PASSWORD", "123456")
user = os.getenv("PLATFORM_USERNAME")
pwd = os.getenv("PLATFORM_PASSWORD")
wf = build_complex_workflow()
_validate_local(wf)
@@ -250,8 +250,8 @@ def _via_testclient() -> int:
lr = c.post(
"/api/v1/auth/login",
data={
"username": os.getenv("PLATFORM_USERNAME", "admin"),
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
"username": os.getenv("PLATFORM_USERNAME"),
"password": os.getenv("PLATFORM_PASSWORD"),
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)

View File

@@ -31,8 +31,8 @@ if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
PROVIDER = os.getenv(
"EDU_LLM_PROVIDER",

View File

@@ -506,8 +506,8 @@ def main(argv: Optional[List[str]] = None) -> int:
lr = c.post(
"/api/v1/auth/login",
data={
"username": os.getenv("PLATFORM_USERNAME", "admin"),
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
"username": os.getenv("PLATFORM_USERNAME"),
"password": os.getenv("PLATFORM_PASSWORD"),
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
@@ -534,8 +534,8 @@ def main(argv: Optional[List[str]] = None) -> int:
lr = requests.post(
f"{base}/api/v1/auth/login",
data={
"username": os.getenv("PLATFORM_USERNAME", "admin"),
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
"username": os.getenv("PLATFORM_USERNAME"),
"password": os.getenv("PLATFORM_PASSWORD"),
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=20,

View File

@@ -30,8 +30,8 @@ if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
PROVIDER = os.getenv(
"CROSS_LLM_PROVIDER",

View File

@@ -34,8 +34,8 @@ if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
AGENT_NAME = os.getenv("AGENT_NAME", "学生作业管理助手")
FAST_PROFILE = "2号" in AGENT_NAME or os.getenv("HOMEWORK_FAST_AGENT", "").strip().lower() in (
"1",

View File

@@ -24,8 +24,8 @@ if BACKEND_DIR not in sys.path:
sys.path.insert(0, BACKEND_DIR)
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
AGENT_NAME = os.getenv("AGENT_NAME", "智能助教")
PROVIDER = os.getenv("TUTOR_LLM_PROVIDER", os.getenv("ENTERPRISE_LLM_PROVIDER", "deepseek"))

View File

@@ -29,8 +29,8 @@ from typing import Any, Dict, List, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
AGENT_NAME = os.getenv("AGENT_NAME", "智能学习助手(KG+RAG)")
SUBJECT = os.getenv("SUBJECT", "通用")
LEVEL = os.getenv("LEVEL", "中级")

View File

@@ -33,8 +33,8 @@ from typing import Any, Dict, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
AGENT_NAME = os.getenv("AGENT_NAME", "智能学习助手1号")
SUBJECT = os.getenv("SUBJECT", "通用")
LEVEL = os.getenv("LEVEL", "中级")

View File

@@ -23,8 +23,8 @@ from typing import Any, Dict, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服14号")
TARGET_NAME = os.getenv("TARGET_NAME", "主控台Main Agent")

View File

@@ -29,8 +29,8 @@ from typing import Any, Dict, List, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
CHILD_AGENT_NAME = os.getenv("CHILD_AGENT_NAME", "知你客服16号")
DEMO_AGENT_NAME = os.getenv("DEMO_AGENT_NAME", "演示-主链路委派invoke")

View File

@@ -20,8 +20,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
SOURCE_AGENT_ID = os.getenv("ZHINI_9_AGENT_ID", "de5932d6-3c05-4b27-ab08-f6cb403ce4b9")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服10号"
NEW_DESC = (

View File

@@ -16,8 +16,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
SOURCE_AGENT_ID = os.getenv("ZHINI_10_AGENT_ID", "c853482b-d298-44e4-9862-c84318f71abb")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服11号"
NEW_DESC = (

View File

@@ -16,8 +16,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
SOURCE_AGENT_ID = os.getenv("ZHINI_11_AGENT_ID", "d39748ad-277f-48ac-9eb5-168ad2f1b470")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服12号"
NEW_DESC = (

View File

@@ -27,8 +27,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服12号")
TARGET_NAME = os.getenv("TARGET_NAME", "知你客服13号")

View File

@@ -27,8 +27,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服13号")
TARGET_NAME = os.getenv("TARGET_NAME", "知你客服14号")

View File

@@ -26,8 +26,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服14号")
TARGET_NAME = os.getenv("TARGET_NAME", "知你客服15号")

View File

@@ -29,8 +29,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服15号")
TARGET_NAME = os.getenv("TARGET_NAME", "知你客服16号")

View File

@@ -24,8 +24,8 @@ from typing import Any, Dict, List, Optional, Tuple
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
SOURCE_NAME = os.getenv("SOURCE_AGENT_NAME", "知你客服15号")
TARGET_NAME = os.getenv("TARGET_NAME", "知你客服17号")

View File

@@ -17,8 +17,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
SOURCE_AGENT_ID = os.getenv("ZHINI_6_AGENT_ID", "2acc84d5-814b-4d61-9703-94a4b117375f")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服7号"
NEW_DESC = (

View File

@@ -19,8 +19,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
# 默认从 7 号复制;也可通过环境变量指定
SOURCE_AGENT_ID = os.getenv("ZHINI_7_AGENT_ID", "688c2c41-dcd1-4285-b193-6bed00c485c2")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服8号"
NEW_DESC = (

View File

@@ -20,8 +20,8 @@ import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
SOURCE_AGENT_ID = os.getenv("ZHINI_8_AGENT_ID", "d7b64bf6-c8e3-4dc7-befc-03a98d5ff741")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
NEW_NAME = "知你客服9号"
NEW_DESC = (

View File

@@ -1,3 +1,4 @@
import os
"""单次执行后打印 cache-query 与 llm-unified 输出,用于排查记忆。"""
import json
import requests
@@ -9,7 +10,7 @@ MSG = "我叫李小红"
r = requests.post(
B + "/api/v1/auth/login",
data={"username": "admin", "password": "123456"},
data={"username": os.getenv("PLATFORM_USERNAME"), "password": os.getenv("PLATFORM_PASSWORD")},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=15,
)

View File

@@ -42,8 +42,8 @@ def _login_requests(base: str) -> Dict[str, str]:
r = requests.post(
f"{base}/api/v1/auth/login",
data={
"username": os.getenv("PLATFORM_USERNAME", "admin"),
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
"username": os.getenv("PLATFORM_USERNAME"),
"password": os.getenv("PLATFORM_PASSWORD"),
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=20,
@@ -189,8 +189,8 @@ def _run_testclient() -> int:
r = c.post(
"/api/v1/auth/login",
data={
"username": os.getenv("PLATFORM_USERNAME", "admin"),
"password": os.getenv("PLATFORM_PASSWORD", "123456"),
"username": os.getenv("PLATFORM_USERNAME"),
"password": os.getenv("PLATFORM_PASSWORD"),
},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)

View File

@@ -26,8 +26,8 @@ from typing import Any, Dict
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
def _h() -> Dict[str, str]:

View File

@@ -23,7 +23,7 @@ def main() -> int:
c = TestClient(app)
r = c.post(
"/api/v1/auth/login",
data={"username": os.getenv("PLATFORM_USERNAME", "admin"), "password": os.getenv("PLATFORM_PASSWORD", "123456")},
data={"username": os.getenv("PLATFORM_USERNAME"), "password": os.getenv("PLATFORM_PASSWORD")},
headers={"Content-Type": "application/x-www-form-urlencoded"},
)
if r.status_code != 200:

View File

@@ -19,8 +19,8 @@ from typing import Any, Dict, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
DEMO_AGENT_NAME = os.getenv("DEMO_AGENT_NAME", "演示-主链路委派invoke")

View File

@@ -14,8 +14,8 @@ from typing import Any, Dict, Optional
import requests
BASE = os.getenv("PLATFORM_BASE_URL", "http://127.0.0.1:8037").rstrip("/")
USER = os.getenv("PLATFORM_USERNAME", "admin")
PWD = os.getenv("PLATFORM_PASSWORD", "123456")
USER = os.getenv("PLATFORM_USERNAME")
PWD = os.getenv("PLATFORM_PASSWORD")
CHILD_NAME = "E2E-Subworkflow-Child"
PARENT_NAME = "E2E-Subworkflow-Parent"

View File

@@ -15,7 +15,7 @@ from datetime import datetime
import uuid
def generate_android_agent(db: Session, username: str = "admin"):
def generate_android_agent(db: Session, username: str | None = None):
"""生成Android应用开发助手Agent"""
print("=" * 60)
print("生成Android应用开发助手Agent")
@@ -385,7 +385,7 @@ def main():
"""主函数"""
db = SessionLocal()
try:
generate_android_agent(db, username="admin")
generate_android_agent(db, username=os.getenv("PLATFORM_USERNAME"))
finally:
db.close()

View File

@@ -524,7 +524,7 @@ def generate_creative_writing_agent(db: Session, user: User):
}
def generate_batch_agents(db: Session, username: str = "admin"):
def generate_batch_agents(db: Session, username: str | None = None):
"""批量生成Agent"""
print("=" * 60)
print("批量生成Agent")
@@ -617,7 +617,7 @@ def main():
"""主函数"""
db = SessionLocal()
try:
generate_batch_agents(db, username="admin")
generate_batch_agents(db, username=os.getenv("PLATFORM_USERNAME"))
finally:
db.close()

View File

@@ -490,7 +490,7 @@ def main():
db = SessionLocal()
try:
# 获取或创建测试用户
user = db.query(User).filter(User.username == "admin").first()
user = db.query(User).filter(User.username == os.getenv("PLATFORM_USERNAME")).first()
if not user:
print("请先创建admin用户")
return

View File

@@ -15,7 +15,7 @@ from datetime import datetime
import uuid
def generate_complex_agent_with_templates(db: Session, username: str = "admin"):
def generate_complex_agent_with_templates(db: Session, username: str | None = None):
"""生成包含多个节点模板的复杂Agent"""
print("=" * 60)
print("生成复杂Agent(包含多个节点模板)")
@@ -292,7 +292,7 @@ if __name__ == "__main__":
parser.add_argument(
"--username",
type=str,
default="admin",
default=None,
help="创建Agent的用户名(默认: admin)"
)

View File

@@ -17,7 +17,7 @@ import uuid
import json
def generate_content_agent(db: Session, username: str = "admin"):
def generate_content_agent(db: Session, username: str | None = None):
"""生成内容生成助手Agent"""
print("=" * 60)
print("生成内容生成助手Agent")
@@ -479,7 +479,7 @@ if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description="生成内容生成助手Agent")
parser.add_argument("--username", type=str, default="admin", help="用户名")
parser.add_argument("--username", type=str, default=None, help="用户名")
args = parser.parse_args()

View File

@@ -227,7 +227,7 @@ def generate_workflow_config(agent_type: str) -> dict:
return base_configs["默认"]
def generate_fake_agents(db: Session, username: str = "admin", count: int = None):
def generate_fake_agents(db: Session, username: str | None = None, count: int = None):
"""生成假数据"""
print("=" * 60)
print("生成Agent假数据")
@@ -316,7 +316,7 @@ if __name__ == "__main__":
parser.add_argument(
"--username",
type=str,
default="admin",
default=None,
help="创建Agent的用户名(默认: admin)"
)
parser.add_argument(

View File

@@ -17,7 +17,7 @@ import uuid
import json
def generate_smart_agent(db: Session, username: str = "admin"):
def generate_smart_agent(db: Session, username: str | None = None):
"""生成智能需求分析与解决方案生成Agent"""
print("=" * 60)
print("生成智能需求分析与解决方案生成Agent")
@@ -451,7 +451,7 @@ if __name__ == "__main__":
parser.add_argument(
"--username",
type=str,
default="admin",
default=None,
help="创建Agent的用户名(默认: admin)"
)

View File

@@ -15,7 +15,7 @@ from datetime import datetime
import uuid
def generate_test_agent(db: Session, username: str = "admin"):
def generate_test_agent(db: Session, username: str | None = None):
"""生成测试Agent(左右连接)"""
print("=" * 60)
print("生成测试Agent(左右连接演示)")
@@ -224,7 +224,7 @@ def main():
"""主函数"""
db = SessionLocal()
try:
generate_test_agent(db, username="admin")
generate_test_agent(db, username=os.getenv("PLATFORM_USERNAME"))
finally:
db.close()

View File

@@ -19,8 +19,12 @@ def set_admin():
print("=" * 60)
print()
# 查找admin用户
admin_user = db.query(User).filter(User.username == "admin").first()
target_username = os.getenv("PLATFORM_USERNAME")
if not target_username:
print("❌ 请设置 PLATFORM_USERNAME 环境变量指定要提升为管理员的用户名")
return
# 查找目标用户
admin_user = db.query(User).filter(User.username == target_username).first()
if not admin_user:
print("❌ 未找到admin用户,请先创建admin用户")

View File

@@ -22,8 +22,8 @@ import requests
def parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description="测试学生作业管理助手发送“你好”")
parser.add_argument("--base-url", default="http://127.0.0.1:8037", help="后端地址")
parser.add_argument("--username", default="admin", help="登录用户名")
parser.add_argument("--password", default="123456", help="登录密码")
parser.add_argument("--username", default=None, help="登录用户名")
parser.add_argument("--password", default=None, help="登录密码")
parser.add_argument("--agent-name", default="学生作业管理助手", help="目标 Agent 名称")
parser.add_argument("--message", default="你好", help="发送内容")
parser.add_argument("--timeout-seconds", type=int, default=90, help="轮询超时秒数")

View File

@@ -1,3 +1,4 @@
import os
import re
import requests
@@ -10,7 +11,7 @@ prompt = m.group(1).strip()
r = requests.post(
f"{BASE}/api/v1/auth/login",
data={"username": "admin", "password": "123456"},
data={"username": os.getenv("PLATFORM_USERNAME"), "password": os.getenv("PLATFORM_PASSWORD")},
headers={"Content-Type": "application/x-www-form-urlencoded"},
timeout=15,
)