diff --git a/.dockerignore b/.dockerignore index 3d2a2f0..0ce6c78 100644 --- a/.dockerignore +++ b/.dockerignore @@ -73,19 +73,121 @@ tests/ *.md docs/ -# Docker相关 -Dockerfile* -docker-compose*.yml -.dockerignore +# Docker相关(保留必要的构建文件) +.dockerignore.bak +docker-compose.override.yml # 配置文件(运行时挂载) # global_config.yml -# 其他 +# 环境变量文件 .env -.env.local -.env.*.local +.env.* +!.env.example +*.local.yml +*.dev.yml +*.test.yml +config.*.yml +!global_config.yml + +# 前端相关 node_modules/ npm-debug.log* yarn-debug.log* yarn-error.log* +package-lock.json +yarn.lock + +# 数据目录(运行时挂载) +data/ +backups/ +logs/ +realtime.log + +# 上传文件 +static/uploads/* +!static/uploads/.gitkeep +!static/uploads/images/ +static/uploads/images/* +!static/uploads/images/.gitkeep + +# Excel和数据文件 +*.xlsx +*.xls +*.csv +keywords_*.xlsx +export_*.csv +export_*.json +export_*.xlsx + +# 压缩文件 +*.zip +*.tar.gz +*.rar +*.7z + +# 备份文件 +*.bak +*.backup +*.old + +# 密钥和证书 +*.key +*.pem +*.crt +*.cert +*.p12 +*.pfx +ssl/ +secrets/ +credentials/ + +# 运行时文件 +*.pid +*.sock +*.lock +*.port + +# 性能分析文件 +*.prof +*.profile +*.pstats + +# 缓存文件 +.cache/ +cache/ +*.cache + +# 浏览器相关 +.playwright/ +playwright-report/ +test-results/ + +# 示例和演示文件 +example_*.py +*_example.py +demo_*.py +*_demo.py + +# AI模型文件 +*.model +*.weights +*.h5 +*.pb + +# 大文件 +*.iso +*.dmg +*.img + +# 监控和审计 +monitoring/ +*.access.log +*.error.log +*.audit.log + +# 本地开发文件 +local/ +.local/ +debug.log +*.debug diff --git a/.gitignore b/.gitignore index 90f2fd7..9695d70 100644 --- a/.gitignore +++ b/.gitignore @@ -284,4 +284,102 @@ build/ # 大文件 *.iso *.dmg -*.img \ No newline at end of file +*.img + +# ==================== 项目特定新增文件类型 ==================== +# 测试和示例文件 +test_*.py +*_test.py +example_*.py +*_example.py +demo_*.py +*_demo.py + +# 文档文件(除了README.md) +*.md +!README.md +!CHANGELOG.md +!CONTRIBUTING.md +!LICENSE.md + +# 临时配置文件 +*.local.yml +*.dev.yml +*.test.yml +config.*.yml +!global_config.yml + +# 运行时生成的文件 +*.pid +*.lock +*.sock +*.port + +# 性能和调试文件 +*.profile +*.pstats +*.trace + +# 编译和构建产物 +*.whl +*.egg +*.tar.gz +build/ +dist/ + +# 开发工具配置 +.editorconfig +.flake8 +.pylintrc +pyproject.toml +setup.cfg +tox.ini + +# 容器相关 +.dockerignore +docker-compose.*.yml +!docker-compose.yml +!docker-compose-cn.yml + +# 安全相关 +*.secret +*.token +*.auth +secrets/ +credentials/ + +# 监控和日志 +*.access.log +*.error.log +*.audit.log +monitoring/ + +# 第三方服务配置 +.env +.env.* +!.env.example + +# 数据导出文件 +export_*.csv +export_*.json +export_*.xlsx +dump_*.sql + +# 临时下载文件 +downloads/ +temp_downloads/ + +# 浏览器相关 +.playwright/ +playwright-report/ +test-results/ + +# 系统服务文件 +*.service +*.timer +systemd/ + +# 备份和归档 +archive/ +old/ +deprecated/ \ No newline at end of file diff --git a/README.md b/README.md index bb26088..e944514 100644 --- a/README.md +++ b/README.md @@ -429,6 +429,103 @@ python Start.py - **`requirements.txt`** - Python依赖包列表,精简版本无冗余依赖,按功能分类组织,包含详细说明 - **`.gitignore`** - Git忽略文件配置,完整覆盖Python、Docker、前端等开发文件 - **`.dockerignore`** - Docker构建忽略文件,优化构建上下文大小和构建速度 +- **`Dockerfile-cn`** - 国内优化版Docker镜像构建文件,使用国内镜像源加速构建 +- **`docker-compose-cn.yml`** - 国内优化版Docker Compose配置文件 + +## 🏗️ 详细技术架构 + +### 📊 系统架构图 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ Web前端界面 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 用户管理 │ │ 账号管理 │ │ 关键词管理 │ │ 商品管理 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 日志管理 │ │ 数据管理 │ │ 商品搜索 │ │ 系统监控 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ FastAPI Web服务器 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 用户认证 │ │ 权限管理 │ │ API接口 │ │ 文件上传 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 邮箱验证 │ │ 图形验证码 │ │ 实时日志 │ │ 健康检查 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ CookieManager 多账号管理器 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 任务调度 │ │ 状态监控 │ │ 线程管理 │ │ 异常处理 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ XianyuLive 实例集群 (多实例并行) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 账号A实例 │ │ 账号B实例 │ │ 账号C实例 │ │ ... │ │ +│ │ WebSocket │ │ WebSocket │ │ WebSocket │ │ │ │ +│ │ 消息处理 │ │ 消息处理 │ │ 消息处理 │ │ │ │ +│ │ 自动回复 │ │ 自动回复 │ │ 自动回复 │ │ │ │ +│ │ 自动发货 │ │ 自动发货 │ │ 自动发货 │ │ │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ 辅助服务模块 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ AI回复引擎 │ │ 图片处理 │ │ 商品搜索 │ │ 订单处理 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 日志收集 │ │ 文件管理 │ │ 通知推送 │ │ 数据备份 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────────┐ +│ SQLite数据库 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 用户数据 │ │ 账号数据 │ │ 关键词数据 │ │ 商品数据 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 发货数据 │ │ 系统设置 │ │ 日志数据 │ │ 统计数据 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` + +### 🔄 数据流程图 + +``` +用户消息 → WebSocket接收 → 消息解析 → 关键词匹配 → 回复生成 → 消息发送 + │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ + 商品识别 连接管理 内容过滤 AI处理 模板渲染 发送确认 + │ │ │ │ │ │ + ▼ ▼ ▼ ▼ ▼ ▼ + 数据存储 状态监控 安全检查 上下文 变量替换 日志记录 +``` + +### 🔐 安全架构 + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 安全防护层 │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ JWT认证 │ │ 权限控制 │ │ 数据加密 │ │ 访问控制 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ 图形验证码 │ │ 邮箱验证 │ │ 会话管理 │ │ 操作日志 │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────────┘ +``` ## ⚙️ 配置说明 diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py index 6602eef..f4b908f 100644 --- a/XianyuAutoAsync.py +++ b/XianyuAutoAsync.py @@ -170,6 +170,9 @@ class XianyuLive: self.token_refresh_task = None self.connection_restart_flag = False # 连接重启标志 + # 从数据库获取token信息 + self._load_token_info_from_db() + # 通知防重复机制 self.last_notification_time = {} # 记录每种通知类型的最后发送时间 self.notification_cooldown = 300 # 5分钟内不重复发送相同类型的通知 @@ -191,6 +194,45 @@ class XianyuLive: # 启动定期清理过期暂停记录的任务 self.cleanup_task = None + def _load_token_info_from_db(self): + """从数据库加载token信息""" + try: + from db_manager import db_manager + token_info = db_manager.get_token_info(self.cookie_id) + + if token_info: + self.last_token_refresh_time = token_info.get('last_token_refresh_time', 0) + self.current_token = token_info.get('current_token', None) + logger.info(f"【{self.cookie_id}】从数据库加载token信息成功 - 上次刷新时间: {self.last_token_refresh_time}, token: {'已设置' if self.current_token else '未设置'}") + else: + logger.info(f"【{self.cookie_id}】数据库中未找到token信息,使用默认值") + self.last_token_refresh_time = 0 + self.current_token = None + + except Exception as e: + logger.error(f"【{self.cookie_id}】从数据库加载token信息失败: {self._safe_str(e)}") + # 使用默认值 + self.last_token_refresh_time = 0 + self.current_token = None + + def _save_token_info_to_db(self): + """保存token信息到数据库""" + try: + from db_manager import db_manager + success = db_manager.update_token_info( + self.cookie_id, + self.last_token_refresh_time, + self.current_token + ) + + if success: + logger.debug(f"【{self.cookie_id}】token信息已保存到数据库") + else: + logger.warning(f"【{self.cookie_id}】token信息保存到数据库失败") + + except Exception as e: + logger.error(f"【{self.cookie_id}】保存token信息到数据库异常: {self._safe_str(e)}") + def is_auto_confirm_enabled(self) -> bool: """检查当前账号是否启用自动确认发货""" try: @@ -722,6 +764,10 @@ class XianyuLive: new_token = res_json['data']['accessToken'] self.current_token = new_token self.last_token_refresh_time = time.time() + + # 保存token信息到数据库 + self._save_token_info_to_db() + logger.info(f"【{self.cookie_id}】Token刷新成功") return new_token @@ -750,8 +796,15 @@ class XianyuLive: if hasattr(self, 'user_id') and self.user_id: current_user_id = self.user_id - db_manager.save_cookie(self.cookie_id, self.cookies_str, current_user_id) - logger.debug(f"已更新Cookie到数据库: {self.cookie_id}") + # 保存cookies和token信息 + db_manager.save_cookie( + self.cookie_id, + self.cookies_str, + current_user_id, + self.last_token_refresh_time, + self.current_token + ) + logger.debug(f"已更新Cookie和token信息到数据库: {self.cookie_id}") except Exception as e: logger.error(f"更新数据库Cookie失败: {self._safe_str(e)}") # 发送数据库更新失败通知 diff --git a/db_manager.py b/db_manager.py index 68d50d9..d8420a0 100644 --- a/db_manager.py +++ b/db_manager.py @@ -115,10 +115,27 @@ class DBManager: auto_confirm INTEGER DEFAULT 1, remark TEXT DEFAULT '', pause_duration INTEGER DEFAULT 10, + last_token_refresh_time REAL DEFAULT 0, + current_token TEXT DEFAULT '', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ) ''') + + # 为现有的cookies表添加新字段(如果不存在) + try: + cursor.execute('ALTER TABLE cookies ADD COLUMN last_token_refresh_time REAL DEFAULT 0') + logger.info("已为cookies表添加last_token_refresh_time字段") + except sqlite3.OperationalError: + # 字段已存在,忽略错误 + pass + + try: + cursor.execute('ALTER TABLE cookies ADD COLUMN current_token TEXT DEFAULT ""') + logger.info("已为cookies表添加current_token字段") + except sqlite3.OperationalError: + # 字段已存在,忽略错误 + pass # 创建keywords表 cursor.execute(''' @@ -1063,7 +1080,8 @@ class DBManager: return cursor.executemany(sql, params_list) # -------------------- Cookie操作 -------------------- - def save_cookie(self, cookie_id: str, cookie_value: str, user_id: int = None) -> bool: + def save_cookie(self, cookie_id: str, cookie_value: str, user_id: int = None, + last_token_refresh_time: float = None, current_token: str = None) -> bool: """保存Cookie到数据库,如存在则更新""" with self.lock: try: @@ -1081,10 +1099,38 @@ class DBManager: admin_user = cursor.fetchone() user_id = admin_user[0] if admin_user else 1 - self._execute_sql(cursor, - "INSERT OR REPLACE INTO cookies (id, value, user_id) VALUES (?, ?, ?)", - (cookie_id, cookie_value, user_id) - ) + # 如果提供了token相关信息,则更新这些字段 + if last_token_refresh_time is not None or current_token is not None: + # 先获取现有记录的token信息 + self._execute_sql(cursor, + "SELECT last_token_refresh_time, current_token FROM cookies WHERE id = ?", + (cookie_id,)) + existing_token_info = cursor.fetchone() + + if existing_token_info: + # 如果没有提供新值,使用现有值 + if last_token_refresh_time is None: + last_token_refresh_time = existing_token_info[0] + if current_token is None: + current_token = existing_token_info[1] + else: + # 如果没有现有记录,使用默认值 + if last_token_refresh_time is None: + last_token_refresh_time = 0 + if current_token is None: + current_token = '' + + self._execute_sql(cursor, + "INSERT OR REPLACE INTO cookies (id, value, user_id, last_token_refresh_time, current_token) VALUES (?, ?, ?, ?, ?)", + (cookie_id, cookie_value, user_id, last_token_refresh_time, current_token) + ) + else: + # 如果没有提供token信息,保持现有的token信息不变 + self._execute_sql(cursor, + "INSERT OR REPLACE INTO cookies (id, value, user_id) VALUES (?, ?, ?)", + (cookie_id, cookie_value, user_id) + ) + self.conn.commit() logger.info(f"Cookie保存成功: {cookie_id} (用户ID: {user_id})") @@ -1100,6 +1146,69 @@ class DBManager: logger.error(f"Cookie保存失败: {e}") self.conn.rollback() return False + + def update_token_info(self, cookie_id: str, last_token_refresh_time: float = None, current_token: str = None) -> bool: + """更新Cookie的token信息""" + with self.lock: + try: + cursor = self.conn.cursor() + + # 构建动态SQL语句 + update_fields = [] + params = [] + + if last_token_refresh_time is not None: + update_fields.append("last_token_refresh_time = ?") + params.append(last_token_refresh_time) + + if current_token is not None: + update_fields.append("current_token = ?") + params.append(current_token) + + if not update_fields: + logger.warning(f"没有提供要更新的token信息: {cookie_id}") + return True + + params.append(cookie_id) + sql = f"UPDATE cookies SET {', '.join(update_fields)} WHERE id = ?" + + self._execute_sql(cursor, sql, params) + self.conn.commit() + + if cursor.rowcount > 0: + logger.debug(f"Token信息更新成功: {cookie_id}") + return True + else: + logger.warning(f"未找到要更新的Cookie记录: {cookie_id}") + return False + + except Exception as e: + logger.error(f"Token信息更新失败: {e}") + self.conn.rollback() + return False + + def get_token_info(self, cookie_id: str) -> Optional[Dict[str, any]]: + """获取Cookie的token信息""" + with self.lock: + try: + cursor = self.conn.cursor() + self._execute_sql(cursor, + "SELECT last_token_refresh_time, current_token FROM cookies WHERE id = ?", + (cookie_id,)) + result = cursor.fetchone() + + if result: + return { + 'last_token_refresh_time': result[0] if result[0] is not None else 0, + 'current_token': result[1] if result[1] is not None else '' + } + else: + logger.warning(f"未找到Cookie记录: {cookie_id}") + return None + + except Exception as e: + logger.error(f"获取Token信息失败: {e}") + return None def delete_cookie(self, cookie_id: str) -> bool: """从数据库删除Cookie及其关键字""" diff --git a/global_config.yml b/global_config.yml index f8bd39b..cd66f6f 100644 --- a/global_config.yml +++ b/global_config.yml @@ -59,7 +59,7 @@ MANUAL_MODE: toggle_keywords: [] MESSAGE_EXPIRE_TIME: 300000 TOKEN_REFRESH_INTERVAL: 18000 # 从3600秒(1小时)增加到18000秒(5小时) -TOKEN_RETRY_INTERVAL: 1800 # 从300秒(5分钟)增加到1800秒(30分钟) +TOKEN_RETRY_INTERVAL: 7200 # 从300秒(5分钟)增加到7200秒(2小时) WEBSOCKET_HEADERS: Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q=0.9