feat: 新增工具插件、系统日志、workflow配置及前端优化

1. 新增工具插件(edit_file, python_executor, search_file, shell_executor, write_file)
2. 新增系统事件日志模块和API
3. 新增workflow配置文件和详情API
4. 前端增加SSE、错误边界、设置引导等组件
5. 优化认证加密、速率限制、配置加载等工具模块
6. 删除废弃的cluster和health API
7. 补充单元测试和集成测试

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 07:34:43 +00:00
parent f04fef916f
commit a53ffebe0e
57 changed files with 2804 additions and 271 deletions
+59 -8
View File
@@ -28,7 +28,8 @@ if TYPE_CHECKING:
ALGORITHM = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24
ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 2
REFRESH_TOKEN_EXPIRE_DAYS = 7
_INSECURE_SECRETS = {"secret", "114514", "changethiskey12345"}
@@ -84,9 +85,51 @@ class Accessor:
expire = datetime.now(timezone.utc) + timedelta(
minutes=ACCESS_TOKEN_EXPIRE_MINUTES
)
to_encode.update({"exp": int(expire.timestamp())})
to_encode.update({"exp": int(expire.timestamp()), "type": "access"})
return jwt.encode(to_encode, _get_secret_key(), algorithm=ALGORITHM)
@staticmethod
def _create_refresh_token(data: dict) -> str:
"""生成长效 refresh token(默认 7 天有效期)。"""
to_encode = data.copy()
expire = datetime.now(timezone.utc) + timedelta(
days=REFRESH_TOKEN_EXPIRE_DAYS
)
to_encode.update({"exp": int(expire.timestamp()), "type": "refresh"})
return jwt.encode(to_encode, _get_secret_key(), algorithm=ALGORITHM)
@staticmethod
def verify_refresh_token(token: str) -> TokenData:
"""校验 refresh token 有效性并返回用户身份;过期或类型不对抛 401。"""
try:
payload = jwt.decode(token, _get_secret_key(), algorithms=[ALGORITHM])
if payload.get("type") != "refresh":
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的 refresh token",
)
return TokenData(**{k: v for k, v in payload.items() if k != "type"})
except jwt.ExpiredSignatureError:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Refresh token 已过期,请重新登录",
)
except (jwt.InvalidTokenError, ValidationError):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="无效的 refresh token",
)
@staticmethod
def refresh_access_token(refresh_token: str) -> dict:
"""用 refresh token 换取新的 access token + refresh token 对。"""
token_data = Accessor.verify_refresh_token(refresh_token)
payload = {"user_id": token_data.user_id, "username": token_data.username}
return {
"access_token": Accessor._create_access_token(payload),
"refresh_token": Accessor._create_refresh_token(payload),
}
@staticmethod
def verify_password(plain_password: str, hashed_password: str) -> bool:
"""校验明文口令是否匹配数据库中存储的哈希。"""
@@ -105,8 +148,8 @@ class Accessor:
return Accessor._decode_token(token)
@staticmethod
def login_hashed_password(user: "User", password: str) -> str:
"""完成登录核验:找不到用户或密码错误抛 401,否则签发令牌。"""
def login_hashed_password(user: "User", password: str) -> dict:
"""完成登录核验:找不到用户或密码错误抛 401,否则签发 access + refresh 令牌"""
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
@@ -118,13 +161,21 @@ class Accessor:
detail="用户名或密码错误",
)
token_payload = {"user_id": str(user.user_id), "username": user.user_name}
return Accessor._create_access_token(data=token_payload)
return {
"access_token": Accessor._create_access_token(data=token_payload),
"refresh_token": Accessor._create_refresh_token(data=token_payload),
}
@staticmethod
def hash_password(password: str) -> str:
"""对明文口令做强哈希;空值或长度不足 6 位会抛 ValueError。"""
"""对明文口令做强哈希;空值或不满足复杂度要求会抛 ValueError。"""
if not password:
raise ValueError("密码不能为空")
if len(password) < 6:
raise ValueError("密码长度不能小于 6")
if len(password) < 8:
raise ValueError("密码长度不能小于 8")
has_upper = any(c.isupper() for c in password)
has_lower = any(c.islower() for c in password)
has_digit = any(c.isdigit() for c in password)
if not (has_upper and has_lower and has_digit):
raise ValueError("密码必须包含大写字母、小写字母和数字")
return password_hasher.hash(password)