diff --git a/Dockerfile b/Dockerfile index 6d08191..99b1c32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,7 +21,7 @@ FROM python:3.11-slim LABEL maintainer="tmwgsicp" LABEL description="WeChat Official Account Article Download API with RSS Support" -LABEL version="1.0.3" +LABEL version="1.0.4" WORKDIR /app diff --git a/docker-compose.yml b/docker-compose.yml index a02670a..b2ab88e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,11 @@ # 2. Edit .env and set SITE_URL to your actual URL # 3. Run: docker-compose up -d # 4. Visit http://localhost:5000/login.html to scan QR code +# +# Note for NAS users (Synology/QNAP): +# If you encounter permission issues, run on NAS: +# - chmod -R 777 ./data +# - Credentials are automatically saved to ./data directory services: wechat-api: @@ -17,10 +22,10 @@ services: ports: - "5000:5000" volumes: - # Persist SQLite database + # Persist SQLite database and credentials - ./data:/app/data - # Config file (writable - login saves credentials here) - - ./.env:/app/.env + # Config file (read-only - credentials saved to data/) + - ./.env:/app/.env:ro environment: - TZ=Asia/Shanghai healthcheck: diff --git a/utils/auth_manager.py b/utils/auth_manager.py index 56021d5..1813473 100644 --- a/utils/auth_manager.py +++ b/utils/auth_manager.py @@ -34,12 +34,31 @@ class AuthManager: self.base_dir = Path(__file__).parent.parent self.env_path = self.base_dir / ".env" + # Docker环境下的凭证文件(存储在data目录,权限更可靠) + self.credentials_file = self.base_dir / "data" / ".credentials.json" + # 加载环境变量 self._load_credentials() self._initialized = True def _load_credentials(self): - """从.env文件加载凭证""" + """ + 从多个来源加载凭证,优先级: + 1. data/.credentials.json (Docker环境推荐) + 2. .env 文件 (本地部署) + 3. 环境变量 + """ + # 先尝试从 JSON 凭证文件加载(Docker 环境) + if self.credentials_file.exists(): + try: + import json + with open(self.credentials_file, 'r', encoding='utf-8') as f: + self.credentials = json.load(f) + return + except Exception as e: + print(f"Warning: Failed to load credentials from {self.credentials_file}: {e}") + + # 回退到 .env 文件(本地部署) if self.env_path.exists(): load_dotenv(self.env_path, override=True) @@ -54,7 +73,9 @@ class AuthManager: def save_credentials(self, token: str, cookie: str, fakeid: str, nickname: str, expire_time: int) -> bool: """ - 保存凭证到.env文件 + 保存凭证,支持双存储策略: + 1. 优先保存到 data/.credentials.json (Docker环境推荐,权限可靠) + 2. 同时尝试保存到 .env (本地部署兼容) Args: token: 微信Token @@ -66,21 +87,33 @@ class AuthManager: Returns: 保存是否成功 """ + # 更新内存中的凭证 + self.credentials.update({ + "token": token, + "cookie": cookie, + "fakeid": fakeid, + "nickname": nickname, + "expire_time": expire_time + }) + + success = False + + # 策略1: 保存到 data/.credentials.json (Docker 环境优先) + try: + import json + self.credentials_file.parent.mkdir(parents=True, exist_ok=True) + with open(self.credentials_file, 'w', encoding='utf-8') as f: + json.dump(self.credentials, f, indent=2, ensure_ascii=False) + print(f"[OK] 凭证已保存到: {self.credentials_file}") + success = True + except Exception as e: + print(f"[WARN] 无法保存到凭证文件: {e}") + + # 策略2: 同时尝试保存到 .env 文件(本地部署兼容) try: - # 更新内存中的凭证 - self.credentials.update({ - "token": token, - "cookie": cookie, - "fakeid": fakeid, - "nickname": nickname, - "expire_time": expire_time - }) - - # 确保.env文件存在 if not self.env_path.exists(): self.env_path.touch() - # 保存到.env文件 env_file = str(self.env_path) set_key(env_file, "WECHAT_TOKEN", token) set_key(env_file, "WECHAT_COOKIE", cookie) @@ -88,11 +121,17 @@ class AuthManager: set_key(env_file, "WECHAT_NICKNAME", nickname) set_key(env_file, "WECHAT_EXPIRE_TIME", str(expire_time)) - print(f"✅ 凭证已保存到: {self.env_path}") - return True + print(f"[OK] 凭证已同步到: {self.env_path}") + success = True except Exception as e: - print(f"❌ 保存凭证失败: {e}") + print(f"[WARN] 无法写入 .env 文件 (Docker环境正常): {e}") + # Docker 环境下 .env 可能只读,不影响功能 + + if not success: + print(f"[ERROR] 凭证保存完全失败") return False + + return True def get_credentials(self) -> Optional[Dict[str, any]]: """ @@ -155,7 +194,7 @@ class AuthManager: def clear_credentials(self) -> bool: """ - 清除凭证 + 清除凭证(双存储都清除) Returns: 清除是否成功 @@ -178,12 +217,20 @@ class AuthManager: for key in env_keys: os.environ.pop(key, None) + # 删除凭证文件 + if self.credentials_file.exists(): + self.credentials_file.unlink() + print(f"[OK] 凭证文件已删除: {self.credentials_file}") + # 清空 .env 文件中的凭证字段(保留其他配置) - if self.env_path.exists(): - env_file = str(self.env_path) - for key in env_keys: - set_key(env_file, key, "") - print(f"✅ 凭证已清除: {self.env_path}") + try: + if self.env_path.exists(): + env_file = str(self.env_path) + for key in env_keys: + set_key(env_file, key, "") + print(f"[OK] .env 凭证已清除: {self.env_path}") + except Exception as e: + print(f"[WARN] 无法清除 .env 文件 (Docker环境正常): {e}") return True except Exception as e: