Commit 23aee4c4 authored by uuo's avatar uuo

fix: 修复多个服务配置问题并增强安全功能

- 修复Zabbix URL配置,移除冗余路径以支持不同部署方式
- 为auth-service添加RoleLevel字段和godotenv依赖,改进用户注册逻辑
- 在security-service中增强Zabbix API错误处理和重试机制
- 为llm-service助手回复添加敏感词过滤和审计日志
- 统一各服务环境变量加载逻辑,使用pydantic-settings替代直接os.getenv
- 在edu-service的分配教师接口中添加管理员权限检查
- 更新security-service Dockerfile以支持代理头部
- 删除auth-service中已编译的二进制文件
parent 6e2b6222
...@@ -147,7 +147,7 @@ services: ...@@ -147,7 +147,7 @@ services:
- REDIS_PORT=${REDIS_PORT} - REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB} - REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD}
- ZABBIX_URL=${ZABBIX_URL:-http://localhost/zabbix/api_jsonrpc.php} - ZABBIX_URL=${ZABBIX_URL:-http://localhost}
- ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin} - ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin}
- ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix} - ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix}
- ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600} - ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600}
......
...@@ -150,7 +150,7 @@ services: ...@@ -150,7 +150,7 @@ services:
- REDIS_PORT=${REDIS_PORT} - REDIS_PORT=${REDIS_PORT}
- REDIS_DB=${REDIS_DB} - REDIS_DB=${REDIS_DB}
- REDIS_PASSWORD=${REDIS_PASSWORD} - REDIS_PASSWORD=${REDIS_PASSWORD}
- ZABBIX_URL=${ZABBIX_URL:-http://localhost/zabbix/api_jsonrpc.php} - ZABBIX_URL=${ZABBIX_URL:-http://localhost}
- ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin} - ZABBIX_USERNAME=${ZABBIX_USERNAME:-Admin}
- ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix} - ZABBIX_PASSWORD=${ZABBIX_PASSWORD:-zabbix}
- ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600} - ZABBIX_SYNC_INTERVAL=${ZABBIX_SYNC_INTERVAL:-3600}
......
module auth-service module auth-service
go 1.24.3 go 1.24.2
require github.com/gin-gonic/gin v1.11.0 require github.com/gin-gonic/gin v1.11.0
...@@ -12,6 +12,7 @@ require ( ...@@ -12,6 +12,7 @@ require (
github.com/go-openapi/jsonreference v0.19.6 // indirect github.com/go-openapi/jsonreference v0.19.6 // indirect
github.com/go-openapi/spec v0.20.4 // indirect github.com/go-openapi/spec v0.20.4 // indirect
github.com/go-openapi/swag v0.19.15 // indirect github.com/go-openapi/swag v0.19.15 // indirect
github.com/joho/godotenv v1.5.1 // indirect
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/mailru/easyjson v0.7.6 // indirect github.com/mailru/easyjson v0.7.6 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
......
...@@ -59,6 +59,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD ...@@ -59,6 +59,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
......
...@@ -60,11 +60,12 @@ func (s *AuthService) Register(req *RegisterRequest) (*model.User, error) { ...@@ -60,11 +60,12 @@ func (s *AuthService) Register(req *RegisterRequest) (*model.User, error) {
// 创建用户 // 创建用户
user := &model.User{ user := &model.User{
Username: req.Username, Username: req.Username,
Email: req.Email, Email: req.Email,
Password: hashedPwd, Password: hashedPwd,
Role: "user", Role: "user",
Edition: "edu", RoleLevel: 1, // 默认普通用户等级
Edition: "edu",
} }
if err := s.repo.Create(user); err != nil { if err := s.repo.Create(user); err != nil {
......
...@@ -10,10 +10,12 @@ import ( ...@@ -10,10 +10,12 @@ import (
"fmt" "fmt"
"log" "log"
"os" "os"
"path/filepath"
_ "auth-service/docs" // docs is generated by Swag CLI _ "auth-service/docs" // docs is generated by Swag CLI
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/joho/godotenv"
swaggerFiles "github.com/swaggo/files" swaggerFiles "github.com/swaggo/files"
ginSwagger "github.com/swaggo/gin-swagger" ginSwagger "github.com/swaggo/gin-swagger"
"gorm.io/driver/postgres" "gorm.io/driver/postgres"
...@@ -30,6 +32,19 @@ import ( ...@@ -30,6 +32,19 @@ import (
// @name Authorization // @name Authorization
func main() { func main() {
// 尝试加载根目录 .env 文件 (用于本地开发)
// 优先级:当前目录 .env > 向上两级 .env > 向上三级 .env
// 注意:Docker 环境下通常不包含这些 .env 文件,而是直接通过 environment 注入,
// 所以这里的加载失败不应该阻断程序运行,只作为开发辅助。
// 1. 尝试当前目录
godotenv.Load()
// 2. 尝试项目根目录 (假设在 microservices/auth-service 下运行)
// 根目录在 ../../.env
rootEnvPath := filepath.Join("..", "..", ".env")
godotenv.Load(rootEnvPath)
// 获取环境变量配置 // 获取环境变量配置
dbHost := os.Getenv("DB_HOST") dbHost := os.Getenv("DB_HOST")
dbUser := os.Getenv("DB_USER") dbUser := os.Getenv("DB_USER")
......
...@@ -2,6 +2,7 @@ package utils ...@@ -2,6 +2,7 @@ package utils
import ( import (
"fmt" "fmt"
"log"
"os" "os"
"time" "time"
......
...@@ -21,6 +21,12 @@ import org.springframework.context.annotation.Bean; ...@@ -21,6 +21,12 @@ import org.springframework.context.annotation.Bean;
public class EduServiceApplication { public class EduServiceApplication {
public static void main(String[] args) { public static void main(String[] args) {
// 尝试加载根目录 .env 文件 (用于本地开发)
// 注意:生产环境 Docker 会直接注入环境变量,这里仅作为本地开发辅助
// Spring Boot 默认不加载 .env,这里使用 System.setProperty 模拟或推荐使用插件
// 为了简单起见,这里不做复杂的 .env 解析,建议本地开发使用 IDE 插件或手动设置环境变量
// 或者使用 java-dotenv 库
SpringApplication.run(EduServiceApplication.class, args); SpringApplication.run(EduServiceApplication.class, args);
} }
......
...@@ -6,6 +6,8 @@ import com.llmfilter.edu.service.ScheduleService; ...@@ -6,6 +6,8 @@ import com.llmfilter.edu.service.ScheduleService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import com.llmfilter.edu.security.UserContextHolder;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
...@@ -23,9 +25,18 @@ public class ScheduleController { ...@@ -23,9 +25,18 @@ public class ScheduleController {
@PutMapping("/assign-teacher") @PutMapping("/assign-teacher")
@Operation(summary = "分配任课教师", description = "为特定课程分配任课教师") @Operation(summary = "分配任课教师", description = "为特定课程分配任课教师")
public ResponseEntity<Map<String, Boolean>> assignTeacher(@RequestBody AssignTeacherPayload payload) { public ResponseEntity<Map<String, Object>> assignTeacher(@RequestBody AssignTeacherPayload payload) {
// 权限检查:仅管理员可分配教师
String role = UserContextHolder.getContext().getRole();
if (!"administrator".equals(role)) {
Map<String, Object> error = new HashMap<>();
error.put("success", false);
error.put("message", "Permission denied: Administrator role required");
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(error);
}
scheduleService.assignTeacher(payload); scheduleService.assignTeacher(payload);
Map<String, Boolean> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("success", true); result.put("success", true);
return ResponseEntity.ok(result); return ResponseEntity.ok(result);
} }
......
...@@ -21,7 +21,7 @@ import java.security.Key; ...@@ -21,7 +21,7 @@ import java.security.Key;
@Component @Component
public class JwtAuthenticationFilter implements Filter { public class JwtAuthenticationFilter implements Filter {
@Value("${jwt.secret:your_secret_key_here}") @Value("${jwt.secret}")
private String jwtSecret; private String jwtSecret;
private Key key; private Key key;
......
import os from pathlib import Path
from pydantic_settings import BaseSettings from pydantic import Field
from dotenv import load_dotenv from pydantic_settings import BaseSettings, SettingsConfigDict
# 加载环境变量 (尝试向上查找 .env)
load_dotenv(verbose=True) # 默认查找当前目录
if not os.getenv("MONGODB_URL"):
# 如果没找到,尝试向上级目录查找(本地开发场景)
load_dotenv(dotenv_path="../../.env")
load_dotenv(dotenv_path="../../../.env") # 备用:防止层级变动
class Settings(BaseSettings): class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file_encoding="utf-8",
case_sensitive=True,
extra="ignore"
)
# 应用配置 # 应用配置
APP_NAME: str = "LLM过滤系统" APP_NAME: str = "LLM过滤系统"
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
APP_BASE_URL: str = os.getenv("APP_BASE_URL", "http://localhost:8000") APP_BASE_URL: str = "http://localhost:8000"
# 数据库配置 # 数据库配置
MONGODB_URL: str = os.getenv("MONGODB_URL", "mongodb://localhost:27017") MONGODB_URL: str = "mongodb://localhost:27017"
DB_NAME: str = os.getenv("DB_NAME", "llm_filter_db") DB_NAME: str = "llm_filter_db"
# JWT配置 # JWT配置
SECRET_KEY: str = os.getenv("SECRET_KEY", "") SECRET_KEY: str = ""
ALGORITHM: str = os.getenv("ALGORITHM", "HS256") ALGORITHM: str = "HS256"
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "30")) ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
# Ollama配置 # Ollama配置
OLLAMA_BASE_URL: str = os.getenv("OLLAMA_BASE_URL", "http://192.168.6.6:11434/") OLLAMA_BASE_URL: str = "http://192.168.6.6:11434/"
OLLAMA_MODEL: str = os.getenv("OLLAMA_MODEL", "deepseek-r1:14b") OLLAMA_MODEL: str = "deepseek-r1:14b"
# Dify配置 # Dify配置
DIFY_API_URL: str = os.getenv("DIFY_API_URL", "http://192.168.6.6/v1") DIFY_API_URL: str = "http://192.168.6.6/v1"
DIFY_API_KEY: str = os.getenv("DIFY_API_KEY", "") # 使用别名从环境变量 DIFY_API_KEY_LLM 读取,代码中仍通过 settings.DIFY_API_KEY 访问
DIFY_RESPONSE_MODE: str = os.getenv("DIFY_RESPONSE_MODE", "streaming") DIFY_API_KEY: str = Field("", alias="DIFY_API_KEY_LLM")
DIFY_MESSAGE_ENDPOINT: str = os.getenv("DIFY_MESSAGE_ENDPOINT", "chat-messages") DIFY_RESPONSE_MODE: str = "streaming"
DIFY_MESSAGE_ENDPOINT: str = "chat-messages"
# 应用运行模式开关:仅运行教育版或企业版之一 # 应用运行模式开关:仅运行教育版或企业版之一
# 允许的值:"edu" / "biz";若未设置则默认使用 "edu" # 允许的值:"edu" / "biz";若未设置则默认使用 "edu"
# 注意:不再提供混合模式(mixed),如需混合请显式设置并在依赖中放行 # 注意:不再提供混合模式(mixed),如需混合请显式设置并在依赖中放行
APP_MODE: str = os.getenv("APP_MODE", "edu") APP_MODE: str = "edu"
CORS_ALLOWED_ORIGINS: str = os.getenv("CORS_ALLOWED_ORIGINS", "*") CORS_ALLOWED_ORIGINS: str = "*"
GITHUB_DEFAULT_REPO: str = os.getenv("GITHUB_DEFAULT_REPO", "") GITHUB_DEFAULT_REPO: str = ""
GITHUB_TOKEN: str = os.getenv("GITHUB_TOKEN", "") GITHUB_TOKEN: str = ""
# 学期配置 # 学期配置
TERM_START_DATE: str = os.getenv("TERM_START_DATE", "2025-09-01") # 默认开学日期 TERM_START_DATE: str = "2025-09-01" # 默认开学日期
# Redis 配置 # Redis 配置
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") REDIS_HOST: str = "localhost"
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379")) REDIS_PORT: int = 6379
REDIS_DB: int = int(os.getenv("REDIS_DB", "0")) REDIS_DB: int = 0
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "") REDIS_PASSWORD: str = ""
settings = Settings() settings = Settings()
...@@ -236,12 +236,30 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[ ...@@ -236,12 +236,30 @@ async def add_message(conversation_id: str, user_id: str, content: str) -> Dict[
# 调用模型生成回复 # 调用模型生成回复
assistant_response = await generate_response(model_messages) assistant_response = await generate_response(model_messages)
# 检查助手回复是否包含敏感词
response_check = await sensitive_word_filter.check_text(assistant_response)
if response_check["contains_sensitive_words"]:
# 记录敏感词审计日志
sensitive_record = {
"user_id": user_id,
"conversation_id": ObjectId(conversation_id),
"message_content": assistant_response, # 记录原始违规内容
"source": "assistant", # 标记来源为助手
"sensitive_words_found": response_check["sensitive_words_found"],
"highest_severity": response_check["highest_severity"],
"timestamp": datetime.now()
}
await db.db.sensitive_records.insert_one(sensitive_record)
# 屏蔽回复内容
assistant_response = "(系统拦截)生成的内容包含敏感信息,已屏蔽。"
# 创建助手回复消息 # 创建助手回复消息
assistant_message = { assistant_message = {
"role": "assistant", "role": "assistant",
"content": assistant_response, "content": assistant_response,
"timestamp": datetime.now(), "timestamp": datetime.now(),
"contains_sensitive_words": False, "contains_sensitive_words": False, # 既然已屏蔽,标记为不包含(或者是 True 但内容已安全化)
"sensitive_words_found": [] "sensitive_words_found": []
} }
......
...@@ -21,5 +21,4 @@ USER appuser ...@@ -21,5 +21,4 @@ USER appuser
EXPOSE 8000 EXPOSE 8000
# 启动命令(生产环境,多worker) # 启动命令(生产环境,多worker)
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"] CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4", "--proxy-headers", "--forwarded-allow-ips", "*"]
from pydantic_settings import BaseSettings from pathlib import Path
import os
from pydantic import Field
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings): class Settings(BaseSettings):
_ROOT_ENV_PATH = Path(__file__).resolve().parents[4] / ".env"
_SERVICE_ENV_PATH = Path(__file__).resolve().parents[2] / ".env"
model_config = SettingsConfigDict(
env_file=(_ROOT_ENV_PATH, _SERVICE_ENV_PATH),
env_file_encoding="utf-8",
case_sensitive=True,
)
API_V1_STR: str = "/api/v1" API_V1_STR: str = "/api/v1"
PROJECT_NAME: str = "Security Service" PROJECT_NAME: str = "Security Service"
# 鉴权配置 (与 Auth Service 保持一致) # 鉴权配置 (与 Auth Service 保持一致)
JWT_SECRET: str = os.getenv("JWT_SECRET", "") JWT_SECRET: str = ""
ALGORITHM: str = os.getenv("ALGORITHM", "HS256") ALGORITHM: str = "HS256"
# Dify 配置 # Dify 配置
DIFY_API_URL: str = os.getenv("DIFY_API_URL", "http://192.168.6.6/v1") DIFY_API_URL: str = "http://192.168.6.6/v1"
DIFY_API_KEY: str = os.getenv("DIFY_API_KEY", "") # 使用别名从环境变量 DIFY_API_KEY_SECURITY 读取
DIFY_RESPONSE_MODE: str = os.getenv("DIFY_RESPONSE_MODE", "streaming") DIFY_API_KEY: str = Field("", alias="DIFY_API_KEY_SECURITY")
DIFY_RESPONSE_MODE: str = "streaming"
# MongoDB 配置 # MongoDB 配置
MONGODB_URL: str = os.getenv("MONGODB_URL", "mongodb://localhost:27017") MONGODB_URL: str = "mongodb://localhost:27017"
MONGODB_DB_NAME: str = os.getenv("MONGODB_DB_NAME", "security_service_db") MONGODB_DB_NAME: str = "security_service_db"
# Redis 配置 # Redis 配置
REDIS_HOST: str = os.getenv("REDIS_HOST", "localhost") REDIS_HOST: str = "localhost"
REDIS_PORT: int = int(os.getenv("REDIS_PORT", "6379")) REDIS_PORT: int = 6379
REDIS_DB: int = int(os.getenv("REDIS_DB", "0")) REDIS_DB: int = 0
REDIS_PASSWORD: str = os.getenv("REDIS_PASSWORD", "") REDIS_PASSWORD: str = ""
# Zabbix 配置 # Zabbix 配置
ZABBIX_URL: str = os.getenv("ZABBIX_URL", "http://localhost/zabbix/api_jsonrpc.php") ZABBIX_URL: str = "http://localhost"
ZABBIX_USERNAME: str = os.getenv("ZABBIX_USERNAME", "Admin") ZABBIX_USERNAME: str = "Admin"
ZABBIX_PASSWORD: str = os.getenv("ZABBIX_PASSWORD", "zabbix") ZABBIX_PASSWORD: str = "zabbix"
# 数据同步配置 # 数据同步配置
ZABBIX_SYNC_INTERVAL: int = int(os.getenv("ZABBIX_SYNC_INTERVAL", "3600")) # 1小时 ZABBIX_SYNC_INTERVAL: int = 3600
ZABBIX_AUTO_SYNC: bool = os.getenv("ZABBIX_AUTO_SYNC", "true").lower() == "true" ZABBIX_AUTO_SYNC: bool = True
class Config:
case_sensitive = True
settings = Settings() settings = Settings()
...@@ -24,6 +24,10 @@ class ZabbixDataCollector: ...@@ -24,6 +24,10 @@ class ZabbixDataCollector:
def _get_api_url(self): def _get_api_url(self):
"""返回完整的 API 地址""" """返回完整的 API 地址"""
if self.zabbix_url.endswith("api_jsonrpc.php"):
return self.zabbix_url
if self.zabbix_url.endswith("/zabbix"):
return f"{self.zabbix_url}/api_jsonrpc.php"
return f"{self.zabbix_url}/zabbix/api_jsonrpc.php" return f"{self.zabbix_url}/zabbix/api_jsonrpc.php"
def login(self): def login(self):
...@@ -87,6 +91,10 @@ class ZabbixDataCollector: ...@@ -87,6 +91,10 @@ class ZabbixDataCollector:
def _call_api(self, method: str, params: dict): def _call_api(self, method: str, params: dict):
"""通用 API 调用""" """通用 API 调用"""
return self._call_api_with_retry(method, params, retry_count=1)
def _call_api_with_retry(self, method: str, params: dict, retry_count: int = 0):
"""带重试机制的 API 调用"""
payload = { payload = {
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": method, "method": method,
...@@ -105,7 +113,19 @@ class ZabbixDataCollector: ...@@ -105,7 +113,19 @@ class ZabbixDataCollector:
data = response.json() data = response.json()
if "error" in data: if "error" in data:
err = data["error"] err = data["error"]
raise Exception(f"[{err.get('code')}] {err.get('message')} - {err.get('data', '')}") error_msg = err.get('message', '')
error_data = err.get('data', '')
# 检查是否是认证相关错误
# Zabbix API 错误码: -32602 (Invalid params) 有时也用于 session 失效
# 常见 Session 错误信息: "Session terminated", "Not authorized"
if retry_count > 0 and ("Session" in error_data or "authorized" in error_data or "auth" in error_msg.lower()):
logger.warning(f"Zabbix API 认证失败 ({error_msg} - {error_data}),尝试重新登录...")
self.login()
# 更新 auth token 后重试
return self._call_api_with_retry(method, params, retry_count=retry_count - 1)
raise Exception(f"[{err.get('code')}] {error_msg} - {error_data}")
return data["result"] return data["result"]
except Exception as e: except Exception as e:
raise Exception(f"API 调用失败 ({method}): {e}") raise Exception(f"API 调用失败 ({method}): {e}")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment