From cea4e04bf07458e777924abf93d0c6c7a86df40a Mon Sep 17 00:00:00 2001
From: zhinianboke <115088296+zhinianboke@users.noreply.github.com>
Date: Sat, 26 Jul 2025 14:08:42 +0800
Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=A2=9E=E5=A4=9A=E7=94=A8=E6=88=B7?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
ADMIN_FEATURES_SUMMARY.md | 264 -----------
AI_REPLY_GUIDE.md => AI回复指南.md | 0
COMPLETE_ISOLATION_ANALYSIS.md | 254 -----------
DATABASE_BACKUP_SUMMARY.md | 301 -------------
DOCKER_MULTIUSER_UPDATE.md | 235 ----------
...ER_QUICK_START.md => Docker快速启动指南.md | 2 +-
Docker部署说明.md | 375 ----------------
FINAL_ISOLATION_STATUS.md | 217 ---------
LICENSE | 21 -
LOG_IMPROVEMENT_SUMMARY.md | 196 --------
MULTIUSER_ISOLATION_STATUS.md | 216 ---------
MULTIUSER_SYSTEM_README.md | 277 ------------
README.md | 423 ++++++++++++------
REGISTER_PAGE_OPTIMIZATION.md | 233 ----------
SYSTEM_IMPROVEMENTS_SUMMARY.md | 298 ------------
TOKEN_FIX_SUMMARY.md | 192 --------
UI_IMPROVEMENTS.md | 92 ----
USER_LOGGING_IMPROVEMENT.md | 249 -----------
XianyuAutoAsync.py | 148 +++++-
backup_import_update_summary.md | 193 --------
bargain_demo.py | 180 --------
cookie_manager.py | 12 +-
db_manager.py | 254 +++++++----
demo_captcha_registration.py | 329 --------------
deploy.bat | 298 ------------
deploy.sh | 288 ------------
docker-deploy.bat | 301 -------------
docker_deployment_update.md | 207 ---------
fix-db-permissions.bat | 156 -------
fix-db-permissions.sh | 167 -------
fix-docker-warnings.bat | 144 ------
fix-docker-warnings.sh | 145 ------
fix-websocket-issue.sh | 121 -----
fix_api_isolation.py | 334 --------------
fix_complete_isolation.py | 317 -------------
fix_user_isolation.py | 230 ----------
gitignore_rules_explanation.md | 172 -------
migrate_to_multiuser.py | 237 ----------
qq-group.png | Bin 0 -> 146989 bytes
quick-fix-permissions.bat | 116 -----
quick-fix-permissions.sh | 88 ----
reply_server.py | 217 ++++++---
simple_log_test.py | 84 ----
static/data_management.html | 5 +-
static/index.html | 88 +++-
static/log_management.html | 6 +
static/login.html | 397 +++++++++++++++-
static/register.html | 2 +-
static/test_local_resources.html | 75 ----
static/user_management.html | 6 +
test_admin_features.py | 345 --------------
test_ai_reply.py | 215 ---------
test_ai_reply_fix.py | 166 -------
test_backup_import.py | 282 ------------
test_bargain_limit.py | 267 -----------
test_bargain_limit_direct.py | 183 --------
test_cache_refresh.py | 202 ---------
test_captcha.png | Bin 1658 -> 0 bytes
test_captcha_system.py | 240 ----------
test_complete_isolation.py | 347 --------------
test_cookie_log_display.py | 166 -------
test_database_backup.py | 254 -----------
test_docker_deployment.py | 331 --------------
test_docker_deployment.sh | 192 --------
test_duplicate_notification_fix.py | 168 -------
test_fix.py | 27 --
test_gitignore.py | 149 ------
test_gitignore_db.py | 178 --------
test_improvements.py | 257 -----------
test_keyword_reply.py | 290 ------------
test_multiuser_system.py | 278 ------------
test_notification_deduplication.py | 183 --------
test_page_access.py | 46 --
test_simple_token_filter.py | 169 -------
test_status_display.html | 250 -----------
test_token_expiry_filter.py | 203 ---------
test_token_fix.py | 146 ------
test_user_isolation_complete.py | 333 --------------
test_user_logging.py | 318 -------------
utils/xianyu_utils.py | 35 +-
wechat-group.png | Bin 0 -> 170848 bytes
使用说明.md | 178 --------
商品管理功能说明.md | 199 --------
日志管理功能说明.md | 296 ------------
自动发货功能说明.md | 265 -----------
获取所有商品功能说明.md | 203 ---------
86 files changed, 1252 insertions(+), 15271 deletions(-)
delete mode 100644 ADMIN_FEATURES_SUMMARY.md
rename AI_REPLY_GUIDE.md => AI回复指南.md (100%)
delete mode 100644 COMPLETE_ISOLATION_ANALYSIS.md
delete mode 100644 DATABASE_BACKUP_SUMMARY.md
delete mode 100644 DOCKER_MULTIUSER_UPDATE.md
rename DOCKER_QUICK_START.md => Docker快速启动指南.md (98%)
delete mode 100644 Docker部署说明.md
delete mode 100644 FINAL_ISOLATION_STATUS.md
delete mode 100644 LICENSE
delete mode 100644 LOG_IMPROVEMENT_SUMMARY.md
delete mode 100644 MULTIUSER_ISOLATION_STATUS.md
delete mode 100644 MULTIUSER_SYSTEM_README.md
delete mode 100644 REGISTER_PAGE_OPTIMIZATION.md
delete mode 100644 SYSTEM_IMPROVEMENTS_SUMMARY.md
delete mode 100644 TOKEN_FIX_SUMMARY.md
delete mode 100644 UI_IMPROVEMENTS.md
delete mode 100644 USER_LOGGING_IMPROVEMENT.md
delete mode 100644 backup_import_update_summary.md
delete mode 100644 bargain_demo.py
delete mode 100644 demo_captcha_registration.py
delete mode 100644 deploy.bat
delete mode 100644 deploy.sh
delete mode 100644 docker-deploy.bat
delete mode 100644 docker_deployment_update.md
delete mode 100644 fix-db-permissions.bat
delete mode 100644 fix-db-permissions.sh
delete mode 100644 fix-docker-warnings.bat
delete mode 100644 fix-docker-warnings.sh
delete mode 100644 fix-websocket-issue.sh
delete mode 100644 fix_api_isolation.py
delete mode 100644 fix_complete_isolation.py
delete mode 100644 fix_user_isolation.py
delete mode 100644 gitignore_rules_explanation.md
delete mode 100644 migrate_to_multiuser.py
create mode 100644 qq-group.png
delete mode 100644 quick-fix-permissions.bat
delete mode 100644 quick-fix-permissions.sh
delete mode 100644 simple_log_test.py
delete mode 100644 static/test_local_resources.html
delete mode 100644 test_admin_features.py
delete mode 100644 test_ai_reply.py
delete mode 100644 test_ai_reply_fix.py
delete mode 100644 test_backup_import.py
delete mode 100644 test_bargain_limit.py
delete mode 100644 test_bargain_limit_direct.py
delete mode 100644 test_cache_refresh.py
delete mode 100644 test_captcha.png
delete mode 100644 test_captcha_system.py
delete mode 100644 test_complete_isolation.py
delete mode 100644 test_cookie_log_display.py
delete mode 100644 test_database_backup.py
delete mode 100644 test_docker_deployment.py
delete mode 100644 test_docker_deployment.sh
delete mode 100644 test_duplicate_notification_fix.py
delete mode 100644 test_fix.py
delete mode 100644 test_gitignore.py
delete mode 100644 test_gitignore_db.py
delete mode 100644 test_improvements.py
delete mode 100644 test_keyword_reply.py
delete mode 100644 test_multiuser_system.py
delete mode 100644 test_notification_deduplication.py
delete mode 100644 test_page_access.py
delete mode 100644 test_simple_token_filter.py
delete mode 100644 test_status_display.html
delete mode 100644 test_token_expiry_filter.py
delete mode 100644 test_token_fix.py
delete mode 100644 test_user_isolation_complete.py
delete mode 100644 test_user_logging.py
create mode 100644 wechat-group.png
delete mode 100644 使用说明.md
delete mode 100644 商品管理功能说明.md
delete mode 100644 日志管理功能说明.md
delete mode 100644 自动发货功能说明.md
delete mode 100644 获取所有商品功能说明.md
diff --git a/ADMIN_FEATURES_SUMMARY.md b/ADMIN_FEATURES_SUMMARY.md
deleted file mode 100644
index d65223b..0000000
--- a/ADMIN_FEATURES_SUMMARY.md
+++ /dev/null
@@ -1,264 +0,0 @@
-# 管理员功能总结
-
-## 🎯 新增功能概述
-
-为闲鱼自动回复系统新增了完整的管理员功能,包括用户管理和日志管理,这些功能只有admin用户可以访问。
-
-## 📊 功能详情
-
-### 1. 用户管理功能
-
-#### 🔗 访问路径
-- **URL**: `/user_management.html`
-- **权限**: 仅限admin用户
-- **菜单位置**: 主页侧边栏 → 管理员功能 → 用户管理
-
-#### ✨ 主要功能
-1. **用户列表查看**
- - 显示所有注册用户信息
- - 用户名、邮箱、注册时间
- - Cookie数量、卡券数量统计
- - 用户类型标识(管理员/普通用户)
-
-2. **用户删除功能**
- - 删除普通用户账号
- - 级联删除用户所有数据:
- - 所有Cookie账号
- - 所有卡券
- - 所有关键字和回复设置
- - 所有个人设置
- - 所有相关业务数据
- - 管理员账号保护(不可删除)
-
-3. **系统统计信息**
- - 总用户数
- - 总Cookie数
- - 总卡券数
- - 系统运行状态
-
-#### 🛡️ 安全特性
-- **权限验证**: 只有admin用户可以访问
-- **管理员保护**: 不能删除管理员自己
-- **确认机制**: 删除用户需要二次确认
-- **数据完整性**: 级联删除保证数据一致性
-
-### 2. 日志管理功能
-
-#### 🔗 访问路径
-- **URL**: `/log_management.html`
-- **权限**: 仅限admin用户
-- **菜单位置**: 主页侧边栏 → 管理员功能 → 系统日志
-
-#### ✨ 主要功能
-1. **日志查看**
- - 实时查看系统运行日志
- - 支持50-1000行日志显示
- - 彩色日志级别显示
- - 用户操作追踪
-
-2. **日志过滤**
- - 按日志级别过滤(INFO、WARNING、ERROR、DEBUG)
- - 快速切换过滤条件
- - 实时过滤结果
-
-3. **自动刷新**
- - 可开启5秒自动刷新
- - 实时监控系统状态
- - 自动滚动到最新日志
-
-4. **日志导航**
- - 快速跳转到顶部/底部
- - 日志文件信息显示
- - 最后更新时间显示
-
-#### 🎨 界面特性
-- **终端风格**: 黑色背景,彩色文字
-- **级别颜色**: 不同日志级别不同颜色
-- **响应式设计**: 适配各种屏幕尺寸
-- **用户友好**: 直观的操作界面
-
-## 🔧 技术实现
-
-### 后端API接口
-
-#### 用户管理接口
-```python
-# 获取所有用户
-GET /admin/users
-# 删除用户
-DELETE /admin/users/{user_id}
-# 获取系统统计
-GET /admin/stats
-```
-
-#### 日志管理接口
-```python
-# 获取系统日志
-GET /admin/logs?lines=100&level=info
-```
-
-#### 权限验证
-```python
-def require_admin(current_user: Dict[str, Any] = Depends(get_current_user)):
- """要求管理员权限"""
- if current_user['username'] != 'admin':
- raise HTTPException(status_code=403, detail="需要管理员权限")
- return current_user
-```
-
-### 数据库支持
-
-#### 新增方法
-```python
-# 获取所有用户信息
-def get_all_users(self)
-
-# 根据ID获取用户
-def get_user_by_id(self, user_id: int)
-
-# 删除用户及所有相关数据
-def delete_user_and_data(self, user_id: int)
-```
-
-#### 级联删除逻辑
-1. 用户设置 (user_settings)
-2. 卡券 (cards)
-3. 发货规则 (delivery_rules)
-4. 通知渠道 (notification_channels)
-5. Cookie (cookies)
-6. 关键字 (keywords)
-7. 默认回复 (default_replies)
-8. AI回复设置 (ai_reply_settings)
-9. 消息通知 (message_notifications)
-10. 用户本身 (users)
-
-### 前端实现
-
-#### 权限检查
-```javascript
-// 验证管理员权限
-function checkAdminPermission() {
- // 检查token有效性
- // 验证用户名是否为admin
- // 非管理员自动跳转
-}
-```
-
-#### 菜单显示控制
-```javascript
-// 在主页面中动态显示管理员菜单
-if (result.username === 'admin') {
- document.getElementById('adminMenuSection').style.display = 'block';
-}
-```
-
-## 📱 用户界面
-
-### 用户管理页面
-- **现代化设计**: Bootstrap 5 + 渐变色彩
-- **卡片布局**: 每个用户一个卡片
-- **统计面板**: 顶部显示系统统计
-- **操作确认**: 删除操作需要确认
-- **响应式**: 适配手机和桌面
-
-### 日志管理页面
-- **终端风格**: 模拟命令行界面
-- **实时更新**: 支持自动刷新
-- **过滤控制**: 直观的过滤按钮
-- **导航便利**: 快速跳转功能
-
-## 🔒 安全机制
-
-### 1. 权限验证
-- **后端验证**: 所有管理员接口都需要admin权限
-- **前端检查**: 页面加载时验证用户身份
-- **自动跳转**: 非管理员用户自动跳转到首页
-
-### 2. 操作保护
-- **管理员保护**: 不能删除管理员自己
-- **确认机制**: 危险操作需要二次确认
-- **错误处理**: 完善的错误提示和处理
-
-### 3. 数据安全
-- **事务处理**: 删除操作使用数据库事务
-- **级联删除**: 确保数据完整性
-- **日志记录**: 所有操作都有详细日志
-
-## 📋 使用说明
-
-### 1. 访问管理员功能
-1. 使用admin账号登录系统
-2. 在主页侧边栏查看"管理员功能"菜单
-3. 点击相应功能进入管理页面
-
-### 2. 用户管理操作
-1. 查看用户列表和统计信息
-2. 点击"删除用户"按钮
-3. 在确认对话框中确认删除
-4. 系统自动删除用户及所有相关数据
-
-### 3. 日志管理操作
-1. 选择显示行数(50-1000行)
-2. 选择日志级别过滤
-3. 开启/关闭自动刷新
-4. 使用导航按钮快速跳转
-
-## 🎯 应用场景
-
-### 1. 用户管理
-- **清理无效用户**: 删除不活跃或测试用户
-- **用户统计分析**: 查看用户活跃度和资源使用
-- **系统维护**: 定期清理和优化用户数据
-
-### 2. 日志监控
-- **故障排查**: 实时查看错误日志
-- **性能监控**: 监控系统运行状态
-- **用户行为分析**: 追踪用户操作记录
-- **安全审计**: 监控异常访问和操作
-
-## 🚀 部署说明
-
-### 1. 立即可用
-- 重启服务后功能立即生效
-- 无需额外配置
-- 兼容现有数据
-
-### 2. 访问方式
-```
-用户管理: http://your-domain/user_management.html
-日志管理: http://your-domain/log_management.html
-```
-
-### 3. 权限要求
-- 只有username为'admin'的用户可以访问
-- 其他用户访问会自动跳转到首页
-
-## 📊 功能对比
-
-| 功能 | 普通用户 | 管理员 |
-|------|----------|--------|
-| 查看自己的数据 | ✅ | ✅ |
-| 管理自己的设置 | ✅ | ✅ |
-| 查看所有用户 | ❌ | ✅ |
-| 删除其他用户 | ❌ | ✅ |
-| 查看系统日志 | ❌ | ✅ |
-| 系统统计信息 | ❌ | ✅ |
-
-## 🎉 总结
-
-通过本次更新,闲鱼自动回复系统现在具备了完整的管理员功能:
-
-### ✅ 主要成就
-1. **完整的用户管理**: 查看、删除用户及数据统计
-2. **强大的日志管理**: 实时查看、过滤、监控系统日志
-3. **严格的权限控制**: 只有admin用户可以访问
-4. **现代化界面**: 美观、易用的管理界面
-5. **安全的操作机制**: 完善的确认和保护机制
-
-### 🎯 实用价值
-- **提升管理效率**: 集中化的用户和日志管理
-- **增强系统安全**: 严格的权限控制和操作保护
-- **便于故障排查**: 实时日志监控和过滤功能
-- **优化用户体验**: 直观的界面和操作流程
-
-现在您的多用户闲鱼自动回复系统具备了企业级的管理功能!🎊
diff --git a/AI_REPLY_GUIDE.md b/AI回复指南.md
similarity index 100%
rename from AI_REPLY_GUIDE.md
rename to AI回复指南.md
diff --git a/COMPLETE_ISOLATION_ANALYSIS.md b/COMPLETE_ISOLATION_ANALYSIS.md
deleted file mode 100644
index 3d7c6b0..0000000
--- a/COMPLETE_ISOLATION_ANALYSIS.md
+++ /dev/null
@@ -1,254 +0,0 @@
-# 多用户数据隔离完整分析报告
-
-## 🚨 发现的问题
-
-经过全面检查,发现以下模块**缺乏用户隔离**:
-
-### ❌ 完全未隔离的模块
-
-#### 1. 卡券管理
-- **数据库表**: `cards` 表没有 `user_id` 字段
-- **API接口**: 所有卡券接口都是全局共享
-- **影响**: 所有用户共享同一套卡券库
-
-#### 2. 自动发货规则
-- **数据库表**: `delivery_rules` 表没有 `user_id` 字段
-- **API接口**: 所有发货规则接口都是全局共享
-- **影响**: 所有用户共享同一套发货规则
-
-#### 3. 通知渠道
-- **数据库表**: `notification_channels` 表没有 `user_id` 字段
-- **API接口**: 所有通知渠道接口都是全局共享
-- **影响**: 所有用户共享同一套通知渠道
-
-#### 4. 系统设置
-- **数据库表**: `system_settings` 表没有用户区分
-- **API接口**: 系统设置接口是全局的
-- **影响**: 所有用户共享系统设置(包括主题颜色等)
-
-### ⚠️ 部分隔离的模块
-
-#### 5. 商品管理
-- **已隔离**: 主要CRUD接口
-- **未隔离**: 批量操作接口
-- **影响**: 部分功能存在数据泄露风险
-
-#### 6. 消息通知
-- **已隔离**: 主要配置接口
-- **未隔离**: 删除操作接口
-- **影响**: 删除操作可能影响其他用户
-
-## 📊 详细分析
-
-### 1. 卡券管理模块
-
-#### 当前状态
-```sql
--- 当前表结构(无用户隔离)
-CREATE TABLE cards (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- type TEXT NOT NULL,
- api_config TEXT,
- text_content TEXT,
- data_content TEXT,
- description TEXT,
- enabled BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- -- 缺少 user_id 字段!
-);
-```
-
-#### 未隔离的接口
-- `GET /cards` - 返回所有卡券
-- `POST /cards` - 创建卡券(未绑定用户)
-- `GET /cards/{card_id}` - 获取卡券详情
-- `PUT /cards/{card_id}` - 更新卡券
-- `DELETE /cards/{card_id}` - 删除卡券
-
-#### 安全风险
-- 用户A可以看到用户B创建的卡券
-- 用户A可以修改/删除用户B的卡券
-- 卡券数据完全暴露
-
-### 2. 自动发货规则模块
-
-#### 当前状态
-```sql
--- 当前表结构(无用户隔离)
-CREATE TABLE delivery_rules (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- keyword TEXT NOT NULL,
- card_id INTEGER NOT NULL,
- delivery_count INTEGER DEFAULT 1,
- enabled BOOLEAN DEFAULT TRUE,
- description TEXT,
- delivery_times INTEGER DEFAULT 0,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (card_id) REFERENCES cards(id)
- -- 缺少 user_id 字段!
-);
-```
-
-#### 未隔离的接口
-- `GET /delivery-rules` - 返回所有发货规则
-- `POST /delivery-rules` - 创建发货规则(未绑定用户)
-- `GET /delivery-rules/{rule_id}` - 获取规则详情
-- `PUT /delivery-rules/{rule_id}` - 更新规则
-- `DELETE /delivery-rules/{rule_id}` - 删除规则
-
-#### 安全风险
-- 用户A可以看到用户B的发货规则
-- 用户A可以修改用户B的发货配置
-- 可能导致错误的自动发货
-
-### 3. 通知渠道模块
-
-#### 当前状态
-```sql
--- 当前表结构(无用户隔离)
-CREATE TABLE notification_channels (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- name TEXT NOT NULL,
- type TEXT NOT NULL,
- config TEXT NOT NULL,
- enabled BOOLEAN DEFAULT TRUE,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- -- 缺少 user_id 字段!
-);
-```
-
-#### 未隔离的接口
-- `GET /notification-channels` - 返回所有通知渠道
-- `POST /notification-channels` - 创建通知渠道
-- `GET /notification-channels/{channel_id}` - 获取渠道详情
-- `PUT /notification-channels/{channel_id}` - 更新渠道
-- `DELETE /notification-channels/{channel_id}` - 删除渠道
-
-#### 安全风险
-- 用户A可以看到用户B的通知配置
-- 用户A可以修改用户B的通知渠道
-- 通知可能发送到错误的接收者
-
-### 4. 系统设置模块
-
-#### 当前状态
-```sql
--- 当前表结构(全局设置)
-CREATE TABLE system_settings (
- key TEXT PRIMARY KEY,
- value TEXT,
- description TEXT,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- -- 没有用户区分!
-);
-```
-
-#### 未隔离的接口
-- `GET /system-settings` - 获取系统设置
-- `PUT /system-settings/password` - 更新管理员密码
-- `PUT /system-settings/{key}` - 更新系统设置
-
-#### 安全风险
-- 所有用户共享系统设置
-- 主题颜色等个人偏好无法独立设置
-- 可能存在权限提升风险
-
-## 🔧 修复方案
-
-### 方案A: 完全用户隔离(推荐)
-
-#### 1. 数据库结构修改
-```sql
--- 为所有表添加 user_id 字段
-ALTER TABLE cards ADD COLUMN user_id INTEGER REFERENCES users(id);
-ALTER TABLE delivery_rules ADD COLUMN user_id INTEGER REFERENCES users(id);
-ALTER TABLE notification_channels ADD COLUMN user_id INTEGER REFERENCES users(id);
-
--- 创建用户设置表
-CREATE TABLE user_settings (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER NOT NULL,
- key TEXT NOT NULL,
- value TEXT,
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE(user_id, key)
-);
-```
-
-#### 2. API接口修改
-- 所有接口添加用户权限验证
-- 数据查询添加用户过滤条件
-- 创建操作自动绑定当前用户
-
-#### 3. 数据迁移
-- 将现有数据绑定到admin用户
-- 提供数据迁移脚本
-
-### 方案B: 混合隔离策略
-
-#### 1. 用户隔离模块
-- **卡券管理**: 完全用户隔离
-- **自动发货规则**: 完全用户隔离
-
-#### 2. 全局共享模块
-- **通知渠道**: 保持全局共享(管理员配置)
-- **系统设置**: 区分全局设置和用户设置
-
-## 🚀 实施计划
-
-### 阶段1: 数据库结构升级
-1. 创建数据库迁移脚本
-2. 添加用户隔离字段
-3. 创建用户设置表
-4. 数据迁移和验证
-
-### 阶段2: API接口修复
-1. 修复卡券管理接口
-2. 修复自动发货规则接口
-3. 修复通知渠道接口(如选择隔离)
-4. 创建用户设置接口
-
-### 阶段3: 测试和验证
-1. 单元测试
-2. 集成测试
-3. 安全测试
-4. 性能测试
-
-### 阶段4: 文档和部署
-1. 更新API文档
-2. 更新用户手册
-3. 部署和监控
-
-## 📋 优先级建议
-
-### 高优先级(安全风险)
-1. **卡券管理** - 直接影响业务数据
-2. **自动发货规则** - 可能导致错误发货
-
-### 中优先级(功能完整性)
-3. **通知渠道** - 影响通知准确性
-4. **用户设置** - 影响用户体验
-
-### 低优先级(系统管理)
-5. **系统设置** - 主要影响管理功能
-
-## 🎯 建议采用方案A
-
-**理由**:
-1. **安全性最高** - 完全的数据隔离
-2. **一致性最好** - 所有模块统一的隔离策略
-3. **扩展性最强** - 便于后续功能扩展
-4. **维护性最好** - 统一的权限管理模式
-
-**实施成本**:
-- 数据库迁移:中等
-- 代码修改:中等
-- 测试验证:高
-- 总体可控
diff --git a/DATABASE_BACKUP_SUMMARY.md b/DATABASE_BACKUP_SUMMARY.md
deleted file mode 100644
index 0de6ff7..0000000
--- a/DATABASE_BACKUP_SUMMARY.md
+++ /dev/null
@@ -1,301 +0,0 @@
-# 数据库备份和恢复功能总结
-
-## 🎯 功能概述
-
-为闲鱼自动回复系统添加了直接的数据库文件备份和恢复功能,支持一键下载完整数据库文件和直接上传替换数据库,实现最简单有效的备份方案。
-
-## ✨ 主要特性
-
-### 🔽 数据库备份下载
-- **一键下载**:直接下载完整的SQLite数据库文件
-- **自动命名**:备份文件自动添加时间戳
-- **完整备份**:包含所有用户数据、设置、Cookie、卡券等
-- **快速简单**:无需复杂的导出过程
-
-### 🔼 数据库恢复上传
-- **直接替换**:上传数据库文件直接替换当前数据库
-- **自动验证**:验证文件格式和完整性
-- **安全备份**:替换前自动备份当前数据库
-- **自动重载**:替换后自动重新初始化数据库连接
-
-### 🛡️ 安全机制
-- **权限控制**:只有admin用户可以访问
-- **文件验证**:严格验证上传文件的格式和完整性
-- **大小限制**:限制上传文件大小(100MB)
-- **回滚机制**:失败时自动恢复原数据库
-
-## 🔧 技术实现
-
-### 后端API接口
-
-#### 1. 数据库下载接口
-```python
-@app.get('/admin/backup/download')
-def download_database_backup(admin_user: Dict[str, Any] = Depends(require_admin)):
- """下载数据库备份文件(管理员专用)"""
-```
-
-**功能**:
-- 检查数据库文件存在性
-- 生成带时间戳的文件名
-- 返回FileResponse供下载
-
-#### 2. 数据库上传接口
-```python
-@app.post('/admin/backup/upload')
-async def upload_database_backup(admin_user: Dict[str, Any] = Depends(require_admin),
- backup_file: UploadFile = File(...)):
- """上传并恢复数据库备份文件(管理员专用)"""
-```
-
-**功能**:
-- 验证文件类型和大小
-- 验证SQLite数据库完整性
-- 备份当前数据库
-- 替换数据库文件
-- 重新初始化数据库连接
-
-#### 3. 备份文件列表接口
-```python
-@app.get('/admin/backup/list')
-def list_backup_files(admin_user: Dict[str, Any] = Depends(require_admin)):
- """列出服务器上的备份文件(管理员专用)"""
-```
-
-**功能**:
-- 扫描服务器上的备份文件
-- 返回文件信息(大小、创建时间等)
-
-### 前端界面
-
-#### 1. 系统设置页面集成
-位置:主页 → 系统设置 → 备份管理
-
-#### 2. 数据库备份区域
-```html
-
-
-
- 数据库备份
-
-
-
-
-
-
-
- 数据库恢复
-
-
-
-
-```
-
-#### 3. JavaScript函数
-
-**下载数据库备份**:
-```javascript
-async function downloadDatabaseBackup() {
- // 调用API下载数据库文件
- // 自动触发浏览器下载
-}
-```
-
-**上传数据库备份**:
-```javascript
-async function uploadDatabaseBackup() {
- // 验证文件选择和格式
- // 确认操作风险
- // 上传文件并处理结果
-}
-```
-
-## 🎨 用户界面
-
-### 备份管理布局
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 备份管理 │
-├─────────────────────────┬───────────────────────────────────┤
-│ 数据库备份 │ 数据库恢复 │
-│ │ │
-│ 直接下载完整的数据库文件 │ 上传数据库文件直接替换当前数据库 │
-│ │ │
-│ [下载数据库] │ [选择文件] [恢复数据库] │
-│ │ │
-│ 推荐方式:完整备份,恢复简单│ 警告:将覆盖所有当前数据! │
-└─────────────────────────┴───────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────────┐
-│ JSON格式备份(兼容模式) │
-├─────────────────────────┬───────────────────────────────────┤
-│ 导出JSON格式备份 │ 导入JSON格式备份 │
-│ │ │
-│ [导出JSON备份] │ [选择文件] [导入JSON备份] │
-└─────────────────────────┴───────────────────────────────────┘
-```
-
-## 🔄 备份流程
-
-### 备份流程
-```
-用户点击"下载数据库"
- ↓
-前端调用 /admin/backup/download
- ↓
-后端检查权限和文件存在性
- ↓
-生成带时间戳的文件名
- ↓
-返回FileResponse
- ↓
-浏览器自动下载文件
- ↓
-备份完成
-```
-
-### 恢复流程
-```
-用户选择.db文件
- ↓
-用户点击"恢复数据库"
- ↓
-前端验证文件格式和大小
- ↓
-用户确认操作风险
- ↓
-前端上传文件到 /admin/backup/upload
- ↓
-后端验证文件完整性
- ↓
-备份当前数据库
- ↓
-关闭当前数据库连接
- ↓
-替换数据库文件
- ↓
-重新初始化数据库连接
- ↓
-验证新数据库
- ↓
-返回恢复结果
- ↓
-前端提示刷新页面
- ↓
-恢复完成
-```
-
-## 🛡️ 安全特性
-
-### 1. 权限验证
-- 所有备份API都需要admin权限
-- 前端页面自动检查用户身份
-- 非管理员无法访问备份功能
-
-### 2. 文件验证
-- 严格验证文件扩展名(.db)
-- 验证SQLite数据库格式
-- 检查必要的数据表存在性
-- 限制文件大小(100MB)
-
-### 3. 操作保护
-- 恢复前自动备份当前数据库
-- 失败时自动回滚到原数据库
-- 用户确认机制防止误操作
-- 详细的操作日志记录
-
-### 4. 错误处理
-- 完善的异常捕获和处理
-- 清晰的错误信息提示
-- 自动清理临时文件
-- 数据库连接状态管理
-
-## 💡 使用方法
-
-### 备份数据库
-1. 使用admin账号登录系统
-2. 进入"系统设置"页面
-3. 在"备份管理"区域点击"下载数据库"
-4. 浏览器会自动下载数据库文件
-
-### 恢复数据库
-1. 在"备份管理"区域点击"选择文件"
-2. 选择之前下载的.db文件
-3. 点击"恢复数据库"按钮
-4. 确认操作风险
-5. 等待恢复完成
-6. 刷新页面加载新数据
-
-## 📊 优势对比
-
-| 特性 | 数据库文件备份 | JSON格式备份 |
-|------|---------------|-------------|
-| 备份速度 | ⚡ 极快 | 🐌 较慢 |
-| 文件大小 | 📦 最小 | 📦 较大 |
-| 恢复速度 | ⚡ 极快 | 🐌 较慢 |
-| 数据完整性 | ✅ 100% | ✅ 99% |
-| 操作复杂度 | 🟢 简单 | 🟡 中等 |
-| 兼容性 | 🟢 原生 | 🟡 需要处理 |
-| 推荐程度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
-
-## 🎯 应用场景
-
-### 1. 日常备份
-- 定期下载数据库文件作为备份
-- 简单快速,无需复杂操作
-- 适合自动化脚本调用
-
-### 2. 系统迁移
-- 从一个服务器迁移到另一个服务器
-- 直接复制数据库文件即可
-- 保持数据完整性
-
-### 3. 版本回滚
-- 升级前备份数据库
-- 出现问题时快速回滚
-- 最小化停机时间
-
-### 4. 数据同步
-- 在多个环境间同步数据
-- 开发、测试、生产环境数据一致
-- 便于问题复现和调试
-
-## 🚀 部署说明
-
-### 立即可用
-- 重启服务后功能立即生效
-- 无需额外配置
-- 兼容现有数据
-
-### 文件权限
-确保服务器有足够的文件读写权限:
-```bash
-# 确保数据库文件可读写
-chmod 644 xianyu_data.db
-
-# 确保目录可写(用于备份文件)
-chmod 755 .
-```
-
-### 磁盘空间
-- 备份文件会占用额外磁盘空间
-- 建议定期清理旧的备份文件
-- 监控磁盘使用情况
-
-## 🎉 总结
-
-数据库备份和恢复功能为闲鱼自动回复系统提供了:
-
-### ✅ 核心价值
-- **简单高效**:一键备份和恢复,操作简单
-- **完整可靠**:100%数据完整性保证
-- **安全稳定**:完善的验证和保护机制
-- **快速便捷**:最快的备份和恢复速度
-
-### 🎯 实用性
-- **日常维护**:定期备份保障数据安全
-- **系统迁移**:轻松迁移到新服务器
-- **问题恢复**:快速回滚到正常状态
-- **开发测试**:便于环境数据同步
-
-现在您的多用户闲鱼自动回复系统具备了企业级的数据备份和恢复能力!🎊
diff --git a/DOCKER_MULTIUSER_UPDATE.md b/DOCKER_MULTIUSER_UPDATE.md
deleted file mode 100644
index 331887c..0000000
--- a/DOCKER_MULTIUSER_UPDATE.md
+++ /dev/null
@@ -1,235 +0,0 @@
-# Docker多用户系统部署更新
-
-## 🎯 更新概述
-
-为支持多用户系统和图形验证码功能,Docker部署配置已更新。
-
-## 📦 新增依赖
-
-### Python依赖
-- **Pillow>=10.0.0** - 图像处理库,用于生成图形验证码
-
-### 系统依赖
-- **libjpeg-dev** - JPEG图像支持
-- **libpng-dev** - PNG图像支持
-- **libfreetype6-dev** - 字体渲染支持
-- **fonts-dejavu-core** - 默认字体包
-
-## 🔧 配置文件更新
-
-### 1. requirements.txt
-```diff
-# AI回复相关
-openai>=1.65.5
-python-dotenv>=1.0.1
-
-+ # 图像处理(图形验证码)
-+ Pillow>=10.0.0
-```
-
-### 2. Dockerfile
-```diff
-# 安装系统依赖
-RUN apt-get update && \
- apt-get install -y --no-install-recommends \
- nodejs \
- npm \
- tzdata \
- curl \
-+ libjpeg-dev \
-+ libpng-dev \
-+ libfreetype6-dev \
-+ fonts-dejavu-core \
- && apt-get clean \
- && rm -rf /var/lib/apt/lists/*
-```
-
-### 3. docker-compose.yml
-```diff
-environment:
- - ADMIN_USERNAME=${ADMIN_USERNAME:-admin}
- - ADMIN_PASSWORD=${ADMIN_PASSWORD:-admin123}
- - JWT_SECRET_KEY=${JWT_SECRET_KEY:-default-secret-key}
- - SESSION_TIMEOUT=${SESSION_TIMEOUT:-3600}
-+ # 多用户系统配置
-+ - MULTIUSER_ENABLED=${MULTIUSER_ENABLED:-true}
-+ - USER_REGISTRATION_ENABLED=${USER_REGISTRATION_ENABLED:-true}
-+ - EMAIL_VERIFICATION_ENABLED=${EMAIL_VERIFICATION_ENABLED:-true}
-+ - CAPTCHA_ENABLED=${CAPTCHA_ENABLED:-true}
-+ - TOKEN_EXPIRE_TIME=${TOKEN_EXPIRE_TIME:-86400}
-```
-
-## 🚀 部署步骤
-
-### 1. 更新代码
-```bash
-# 拉取最新代码
-git pull origin main
-
-# 检查更新的文件
-git status
-```
-
-### 2. 重新构建镜像
-```bash
-# 停止现有容器
-docker-compose down
-
-# 重新构建镜像(包含新依赖)
-docker-compose build --no-cache
-
-# 启动服务
-docker-compose up -d
-```
-
-### 3. 验证部署
-```bash
-# 检查容器状态
-docker-compose ps
-
-# 查看日志
-docker-compose logs -f xianyu-app
-
-# 健康检查
-curl http://localhost:8080/health
-```
-
-## 🧪 功能测试
-
-### 1. 访问注册页面
-```bash
-# 打开浏览器访问
-http://localhost:8080/register.html
-```
-
-### 2. 测试图形验证码
-- 页面应该自动显示图形验证码
-- 点击图片可以刷新验证码
-- 输入4位验证码应该能够验证
-
-### 3. 测试用户注册
-- 输入用户名和邮箱
-- 验证图形验证码
-- 发送邮箱验证码
-- 完成注册流程
-
-### 4. 测试数据隔离
-- 注册多个用户
-- 分别登录添加不同的Cookie
-- 验证用户只能看到自己的数据
-
-## 🔍 故障排除
-
-### 1. 图形验证码不显示
-```bash
-# 检查Pillow是否正确安装
-docker-compose exec xianyu-app python -c "from PIL import Image; print('Pillow OK')"
-
-# 检查字体是否可用
-docker-compose exec xianyu-app ls /usr/share/fonts/
-```
-
-### 2. 容器启动失败
-```bash
-# 查看详细错误日志
-docker-compose logs xianyu-app
-
-# 检查依赖安装
-docker-compose exec xianyu-app pip list | grep -i pillow
-```
-
-### 3. 权限问题
-```bash
-# 检查数据目录权限
-ls -la ./data/
-ls -la ./logs/
-
-# 修复权限(如需要)
-sudo chown -R 1000:1000 ./data ./logs
-```
-
-## 📊 资源使用
-
-### 更新后的资源需求
-- **内存**: 512MB → 768MB(推荐)
-- **磁盘**: 1GB → 1.5GB(推荐)
-- **CPU**: 0.5核 → 0.5核(无变化)
-
-### 调整资源限制
-```yaml
-# docker-compose.yml
-deploy:
- resources:
- limits:
- memory: 768M # 增加内存限制
- cpus: '0.5'
- reservations:
- memory: 384M # 增加内存预留
- cpus: '0.25'
-```
-
-## 🔐 安全配置
-
-### 1. 环境变量安全
-```bash
-# 创建 .env 文件
-cat > .env << EOF
-# 修改默认密码
-ADMIN_PASSWORD=your-secure-password
-
-# 使用强JWT密钥
-JWT_SECRET_KEY=your-very-long-and-random-secret-key
-
-# 配置多用户功能
-MULTIUSER_ENABLED=true
-USER_REGISTRATION_ENABLED=true
-EMAIL_VERIFICATION_ENABLED=true
-CAPTCHA_ENABLED=true
-EOF
-```
-
-### 2. 网络安全
-```bash
-# 如果不需要外部访问注册功能,可以禁用
-USER_REGISTRATION_ENABLED=false
-
-# 或者使用Nginx进行访问控制
-# 参考 nginx/nginx.conf 配置
-```
-
-## 📋 迁移检查清单
-
-- [ ] 更新 requirements.txt
-- [ ] 更新 Dockerfile
-- [ ] 更新 docker-compose.yml
-- [ ] 重新构建镜像
-- [ ] 测试图形验证码功能
-- [ ] 测试用户注册流程
-- [ ] 验证数据隔离
-- [ ] 检查资源使用
-- [ ] 更新监控配置
-
-## 🎉 升级完成
-
-升级完成后,您的系统将支持:
-
-1. **多用户注册和登录**
-2. **图形验证码保护**
-3. **邮箱验证码验证**
-4. **完整的数据隔离**
-5. **企业级安全保护**
-
-现在可以安全地支持多个用户同时使用系统,每个用户的数据完全独立!
-
-## 📞 技术支持
-
-如果在部署过程中遇到问题:
-
-1. 查看容器日志:`docker-compose logs -f`
-2. 检查健康状态:`docker-compose ps`
-3. 验证网络连接:`curl http://localhost:8080/health`
-4. 测试功能:访问 `http://localhost:8080/register.html`
-
----
-
-**注意**: 首次部署多用户系统后,建议运行数据迁移脚本将历史数据绑定到admin用户。
diff --git a/DOCKER_QUICK_START.md b/Docker快速启动指南.md
similarity index 98%
rename from DOCKER_QUICK_START.md
rename to Docker快速启动指南.md
index c6a8181..cc02de8 100644
--- a/DOCKER_QUICK_START.md
+++ b/Docker快速启动指南.md
@@ -4,7 +4,7 @@
### 1. 克隆项目
```bash
-git clone
+git clone https://github.com/zhinianboke/xianyu-auto-reply.git
cd xianyu-auto-reply
```
diff --git a/Docker部署说明.md b/Docker部署说明.md
deleted file mode 100644
index 29feda9..0000000
--- a/Docker部署说明.md
+++ /dev/null
@@ -1,375 +0,0 @@
-# 🐳 Docker 部署说明
-
-## 📋 部署概述
-
-本项目支持完整的Docker容器化部署,包含所有必要的依赖和配置。支持单容器部署和多容器编排部署。
-
-## 🆕 多用户系统支持
-
-系统现已支持多用户功能:
-- **用户注册**: 支持邮箱验证码注册
-- **图形验证码**: 防止自动化注册
-- **数据隔离**: 每个用户的数据完全独立
-- **权限管理**: 严格的用户权限控制
-- **安全认证**: JWT Token + 图形验证码双重保护
-
-### 新增依赖
-- **Pillow**: 用于生成图形验证码
-- **系统字体**: 支持验证码文字渲染
-
-## 🚀 快速开始
-
-### 方式一:使用 Docker Compose(推荐)
-
-1. **克隆项目**
- ```bash
- git clone
- cd xianyuapis
- ```
-
-2. **配置环境变量**
- ```bash
- # 复制环境变量模板
- cp .env.example .env
-
- # 编辑配置文件(可选)
- nano .env
- ```
-
-3. **启动服务**
- ```bash
- # 启动基础服务
- docker-compose up -d
-
- # 或者启动包含Nginx的完整服务
- docker-compose --profile with-nginx up -d
- ```
-
-4. **访问系统**
- - 基础部署:http://localhost:8080
- - 带Nginx:http://localhost
-
-### 方式二:使用 Docker 命令
-
-1. **构建镜像**
- ```bash
- docker build -t xianyu-auto-reply:latest .
- ```
-
-2. **运行容器**
- ```bash
- docker run -d \
- --name xianyu-auto-reply \
- -p 8080:8080 \
- -v $(pwd)/data:/app/data \
- -v $(pwd)/logs:/app/logs \
- -v $(pwd)/global_config.yml:/app/global_config.yml:ro \
- -e ADMIN_USERNAME=admin \
- -e ADMIN_PASSWORD=admin123 \
- xianyu-auto-reply:latest
- ```
-
-## 📦 依赖说明
-
-### 新增依赖
-- `python-multipart>=0.0.6` - 文件上传支持(商品管理功能需要)
-
-### 完整依赖列表
-```
-# Web框架和API相关
-fastapi>=0.111
-uvicorn[standard]>=0.29
-pydantic>=2.7
-python-multipart>=0.0.6
-
-# 日志记录
-loguru>=0.7
-
-# 网络通信
-websockets>=10.0,<13.0
-aiohttp>=3.9
-
-# 配置文件处理
-PyYAML>=6.0
-
-# JavaScript执行引擎
-PyExecJS>=1.5.1
-
-# 协议缓冲区解析
-blackboxprotobuf>=1.0.1
-
-# 系统监控
-psutil>=5.9.0
-
-# HTTP客户端(用于测试)
-requests>=2.31.0
-```
-
-## 🔧 配置说明
-
-### 环境变量配置
-
-#### 基础配置
-```bash
-# 时区设置
-TZ=Asia/Shanghai
-
-# 服务端口
-WEB_PORT=8080
-
-# 管理员账号(建议修改)
-ADMIN_USERNAME=admin
-ADMIN_PASSWORD=admin123
-
-# JWT密钥(建议修改)
-JWT_SECRET_KEY=your-secret-key-here
-```
-
-#### 多用户系统配置
-```bash
-# 多用户功能开关
-MULTIUSER_ENABLED=true
-
-# 用户注册开关
-USER_REGISTRATION_ENABLED=true
-
-# 邮箱验证开关
-EMAIL_VERIFICATION_ENABLED=true
-
-# 图形验证码开关
-CAPTCHA_ENABLED=true
-
-# Token过期时间(秒,默认24小时)
-TOKEN_EXPIRE_TIME=86400
-```
-
-#### 功能配置
-```bash
-# 自动回复
-AUTO_REPLY_ENABLED=true
-
-# 自动发货
-AUTO_DELIVERY_ENABLED=true
-AUTO_DELIVERY_TIMEOUT=30
-
-# 商品管理(新功能)
-ENABLE_ITEM_MANAGEMENT=true
-```
-
-### 数据持久化
-
-#### 重要目录
-- `/app/data` - 数据库文件
-- `/app/logs` - 日志文件
-- `/app/backups` - 备份文件
-
-#### 挂载配置
-```yaml
-volumes:
- - ./data:/app/data:rw # 数据库持久化
- - ./logs:/app/logs:rw # 日志持久化
- - ./backups:/app/backups:rw # 备份持久化
- - ./global_config.yml:/app/global_config.yml:ro # 配置文件
-```
-
-## 🏗️ 架构说明
-
-### 容器架构
-```
-┌─────────────────────────────────────┐
-│ Nginx (可选) │
-│ 反向代理 + SSL │
-└─────────────┬───────────────────────┘
- │
-┌─────────────▼───────────────────────┐
-│ Xianyu App Container │
-│ ┌─────────────────────────────────┐ │
-│ │ FastAPI Server │ │
-│ │ (Port 8080) │ │
-│ └─────────────────────────────────┘ │
-│ ┌─────────────────────────────────┐ │
-│ │ XianyuAutoAsync │ │
-│ │ (WebSocket Client) │ │
-│ └─────────────────────────────────┘ │
-│ ┌─────────────────────────────────┐ │
-│ │ SQLite Database │ │
-│ │ (商品信息 + 配置) │ │
-│ └─────────────────────────────────┘ │
-└─────────────────────────────────────┘
-```
-
-### 新功能支持
-- ✅ 商品信息管理
-- ✅ 商品详情编辑
-- ✅ 文件上传功能
-- ✅ 消息通知格式化
-
-## 🔍 健康检查
-
-### 内置健康检查
-```bash
-# 检查容器状态
-docker ps
-
-# 查看健康状态
-docker inspect xianyu-auto-reply | grep Health -A 10
-
-# 手动健康检查
-curl -f http://localhost:8080/health
-```
-
-### 健康检查端点
-- `GET /health` - 基础健康检查
-- `GET /api/status` - 详细状态信息
-
-## 📊 监控和日志
-
-### 日志查看
-```bash
-# 查看容器日志
-docker logs xianyu-auto-reply
-
-# 实时查看日志
-docker logs -f xianyu-auto-reply
-
-# 查看应用日志文件
-docker exec xianyu-auto-reply tail -f /app/logs/xianyu_$(date +%Y%m%d).log
-```
-
-### 性能监控
-```bash
-# 查看资源使用
-docker stats xianyu-auto-reply
-
-# 进入容器
-docker exec -it xianyu-auto-reply bash
-
-# 查看进程状态
-docker exec xianyu-auto-reply ps aux
-```
-
-## 🔒 安全配置
-
-### 生产环境建议
-1. **修改默认密码**
- ```bash
- ADMIN_USERNAME=your-admin
- ADMIN_PASSWORD=your-strong-password
- ```
-
-2. **使用强JWT密钥**
- ```bash
- JWT_SECRET_KEY=$(openssl rand -base64 32)
- ```
-
-3. **启用HTTPS**
- ```yaml
- # 使用Nginx配置SSL
- docker-compose --profile with-nginx up -d
- ```
-
-4. **限制网络访问**
- ```yaml
- # 仅允许本地访问
- ports:
- - "127.0.0.1:8080:8080"
- ```
-
-## 🚨 故障排除
-
-### 常见问题
-
-1. **容器启动失败**
- ```bash
- # 查看详细错误
- docker logs xianyu-auto-reply
-
- # 检查端口占用
- netstat -tlnp | grep 8080
- ```
-
-2. **数据库初始化失败**
- ```bash
- # 数据库会在应用启动时自动初始化
- # 如果需要重新初始化,可以删除数据库文件后重启容器
- docker exec xianyu-auto-reply rm -f /app/data/xianyu_data.db
- docker restart xianyu-auto-reply
- ```
-
-3. **权限问题**
- ```bash
- # 修复目录权限
- sudo chown -R 1000:1000 ./data ./logs ./backups
- ```
-
-4. **依赖安装失败**
- ```bash
- # 重新构建镜像
- docker-compose build --no-cache
- ```
-
-### 调试模式
-```bash
-# 启用调试模式
-docker-compose -f docker-compose.yml -f docker-compose.debug.yml up -d
-
-# 或设置环境变量
-docker run -e DEBUG=true -e LOG_LEVEL=DEBUG ...
-```
-
-## 🔄 更新部署
-
-### 更新步骤
-1. **停止服务**
- ```bash
- docker-compose down
- ```
-
-2. **拉取最新代码**
- ```bash
- git pull origin main
- ```
-
-3. **重新构建**
- ```bash
- docker-compose build --no-cache
- ```
-
-4. **启动服务**
- ```bash
- docker-compose up -d
- ```
-
-### 数据备份
-```bash
-# 备份数据库
-docker exec xianyu-auto-reply cp /app/data/xianyu_data.db /app/backups/
-
-# 备份配置
-cp .env .env.backup
-cp global_config.yml global_config.yml.backup
-```
-
-## 📈 性能优化
-
-### 资源限制
-```yaml
-deploy:
- resources:
- limits:
- memory: 512M # 内存限制
- cpus: '0.5' # CPU限制
- reservations:
- memory: 256M # 内存预留
- cpus: '0.25' # CPU预留
-```
-
-### 优化建议
-1. **调整内存限制**:根据实际使用情况调整
-2. **使用SSD存储**:提高数据库性能
-3. **配置日志轮转**:避免日志文件过大
-4. **定期清理**:清理旧的备份文件
-
----
-
-🎉 **Docker部署配置完善,支持所有新功能!**
diff --git a/FINAL_ISOLATION_STATUS.md b/FINAL_ISOLATION_STATUS.md
deleted file mode 100644
index b8b8fb0..0000000
--- a/FINAL_ISOLATION_STATUS.md
+++ /dev/null
@@ -1,217 +0,0 @@
-# 多用户数据隔离最终状态报告
-
-## 🎯 总体状态
-
-**当前进度**: 核心功能已完成用户隔离,部分功能需要策略确认
-
-**数据库状态**: ✅ 已完成数据库结构升级和数据迁移
-
-**API状态**: ✅ 核心接口已修复,部分接口待完善
-
-## 📊 详细隔离状态
-
-### ✅ 已完全隔离的模块
-
-#### 1. 账号管理 (Cookie管理)
-- ✅ **数据库**: cookies表已添加user_id字段
-- ✅ **API接口**: 所有Cookie相关接口已实现用户隔离
-- ✅ **权限验证**: 跨用户访问被严格禁止
-- ✅ **数据迁移**: 历史数据已绑定到admin用户
-
-#### 2. 自动回复管理
-- ✅ **关键字管理**: 完全隔离
-- ✅ **默认回复设置**: 完全隔离
-- ✅ **权限验证**: 只能操作自己的回复规则
-
-#### 3. 商品管理
-- ✅ **主要CRUD接口**: 已实现用户隔离
-- ✅ **权限验证**: Cookie所有权验证
-- ⚠️ **批量操作**: 部分接口需要进一步修复
-
-#### 4. AI回复设置
-- ✅ **配置管理**: 完全隔离
-- ✅ **权限验证**: 只能配置自己的AI回复
-
-#### 5. 消息通知
-- ✅ **配置管理**: 主要接口已隔离
-- ⚠️ **删除操作**: 部分接口需要修复
-
-#### 6. 卡券管理 (新增隔离)
-- ✅ **数据库**: cards表已添加user_id字段
-- ✅ **API接口**: 主要接口已实现用户隔离
-- ✅ **权限验证**: 跨用户访问被禁止
-- ✅ **数据迁移**: 历史数据已绑定到admin用户
-
-#### 7. 用户设置 (新增功能)
-- ✅ **数据库**: 新建user_settings表
-- ✅ **API接口**: 完整的用户设置管理
-- ✅ **主题颜色**: 每个用户独立的主题设置
-- ✅ **个人偏好**: 支持各种用户个性化设置
-
-### ❓ 需要策略确认的模块
-
-#### 1. 自动发货规则
-- **数据库**: ✅ delivery_rules表已添加user_id字段
-- **API接口**: ❌ 仍使用旧认证方式
-- **建议**: 实现用户隔离(每个用户独立的发货规则)
-
-#### 2. 通知渠道
-- **数据库**: ✅ notification_channels表已添加user_id字段
-- **API接口**: ❌ 仍使用旧认证方式
-- **策略选择**:
- - 选项A: 实现用户隔离(每个用户独立配置)
- - 选项B: 保持全局共享(管理员统一配置)
-
-#### 3. 系统设置
-- **当前状态**: 全局共享
-- **策略选择**:
- - 全局设置: 保持共享(如系统配置)
- - 用户设置: 已实现隔离(如主题颜色)
-
-## 🔧 已完成的修复
-
-### 数据库结构升级
-```sql
--- 为相关表添加用户隔离字段
-ALTER TABLE cards ADD COLUMN user_id INTEGER REFERENCES users(id);
-ALTER TABLE delivery_rules ADD COLUMN user_id INTEGER REFERENCES users(id);
-ALTER TABLE notification_channels ADD COLUMN user_id INTEGER REFERENCES users(id);
-
--- 创建用户设置表
-CREATE TABLE user_settings (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER NOT NULL,
- key TEXT NOT NULL,
- value TEXT,
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE(user_id, key)
-);
-```
-
-### API接口修复
-- ✅ 卡券管理接口: 从`require_auth`升级到`get_current_user`
-- ✅ 用户设置接口: 新增完整的用户设置管理
-- ✅ 数据库方法: 支持用户隔离的查询和操作
-
-### 数据迁移
-- ✅ 历史卡券数据绑定到admin用户
-- ✅ 历史发货规则数据绑定到admin用户
-- ✅ 历史通知渠道数据绑定到admin用户
-- ✅ 为现有用户创建默认设置
-
-## 📋 待修复的接口
-
-### 高优先级(安全风险)
-1. **自动发货规则接口** (5个接口)
- - `GET /delivery-rules`
- - `POST /delivery-rules`
- - `GET /delivery-rules/{rule_id}`
- - `PUT /delivery-rules/{rule_id}`
- - `DELETE /delivery-rules/{rule_id}`
-
-2. **卡券管理剩余接口** (2个接口)
- - `PUT /cards/{card_id}`
- - `DELETE /cards/{card_id}`
-
-### 中优先级(功能完整性)
-3. **通知渠道接口** (6个接口) - 需要策略确认
- - `GET /notification-channels`
- - `POST /notification-channels`
- - `GET /notification-channels/{channel_id}`
- - `PUT /notification-channels/{channel_id}`
- - `DELETE /notification-channels/{channel_id}`
-
-4. **消息通知删除接口** (2个接口)
- - `DELETE /message-notifications/account/{cid}`
- - `DELETE /message-notifications/{notification_id}`
-
-5. **商品管理批量接口** (3个接口)
- - `DELETE /items/batch`
- - `POST /items/get-all-from-account`
- - `POST /items/get-by-page`
-
-## 🧪 测试验证
-
-### 已通过的测试
-- ✅ 用户注册和登录
-- ✅ Cookie数据隔离
-- ✅ 卡券管理隔离
-- ✅ 用户设置隔离
-- ✅ 跨用户访问拒绝
-
-### 测试脚本
-- `test_complete_isolation.py` - 完整的隔离测试
-- `fix_complete_isolation.py` - 数据库修复脚本
-
-## 🎯 建议的隔离策略
-
-### 完全用户隔离(推荐)
-- **自动发货规则**: 每个用户独立的发货规则
-- **卡券管理**: 每个用户独立的卡券库
-- **用户设置**: 每个用户独立的个性化设置
-
-### 混合策略(可选)
-- **通知渠道**: 管理员统一配置,用户选择使用
-- **系统设置**: 区分全局设置和用户设置
-
-## 🚀 下一步行动
-
-### 立即执行(高优先级)
-1. **修复自动发货规则接口**
- ```bash
- # 需要修复的接口模式
- @app.get("/delivery-rules")
- def get_delivery_rules(current_user: Dict[str, Any] = Depends(get_current_user)):
- # 添加用户权限验证
- ```
-
-2. **修复卡券管理剩余接口**
- ```bash
- # 需要添加用户权限验证
- if card.user_id != current_user['user_id']:
- raise HTTPException(status_code=403, detail="无权限操作")
- ```
-
-### 策略确认(中优先级)
-3. **确认通知渠道策略**
- - 与产品团队确认是否需要用户隔离
- - 如需隔离,按照卡券管理模式修复
-
-4. **完善商品管理**
- - 修复批量操作接口的用户权限验证
-
-### 测试和部署(低优先级)
-5. **完整测试**
- - 运行所有隔离测试
- - 验证数据完整性
-
-6. **文档更新**
- - 更新API文档
- - 更新用户手册
-
-## 📊 当前隔离覆盖率
-
-- **已隔离模块**: 6/8 (75%)
-- **已修复接口**: 约70%
-- **数据库隔离**: 100%
-- **核心功能隔离**: 100%
-
-## 🎉 总结
-
-多用户数据隔离项目已基本完成,核心功能已实现完全隔离:
-
-### 主要成就
-- ✅ **数据库层面**: 完整的用户隔离支持
-- ✅ **核心业务**: Cookie、回复、商品、AI设置完全隔离
-- ✅ **新增功能**: 卡券管理和用户设置支持
-- ✅ **安全保障**: 跨用户访问被严格禁止
-
-### 待完善项目
-- ⚠️ **自动发货规则**: 需要修复API接口
-- ❓ **通知渠道**: 需要确认隔离策略
-- 🔧 **批量操作**: 需要完善权限验证
-
-**总体评估**: 系统已具备企业级的多用户数据隔离能力,可以安全地支持多个用户同时使用,剩余工作主要是完善和策略确认。
diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 4501f64..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-MIT License
-
-Copyright (c) 2025 肥极喵
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/LOG_IMPROVEMENT_SUMMARY.md b/LOG_IMPROVEMENT_SUMMARY.md
deleted file mode 100644
index 612ec8b..0000000
--- a/LOG_IMPROVEMENT_SUMMARY.md
+++ /dev/null
@@ -1,196 +0,0 @@
-# 日志显示改进总结
-
-## 🎯 改进目标
-
-在多用户系统中,原有的日志无法区分不同用户的操作,导致调试和监控困难。本次改进为所有重要日志添加了Cookie ID标识。
-
-## 📊 改进对比
-
-### 改进前的日志格式
-```
-2025-07-25 14:23:47.770 | INFO | XianyuAutoAsync:init:1360 - 获取初始token...
-2025-07-25 14:23:47.771 | INFO | XianyuAutoAsync:refresh_token:134 - 开始刷新token...
-2025-07-25 14:23:48.269 | INFO | XianyuAutoAsync:refresh_token:200 - Token刷新成功
-2025-07-25 14:23:49.286 | INFO | XianyuAutoAsync:init:1407 - 连接注册完成
-2025-07-25 14:23:49.288 | INFO | XianyuAutoAsync:handle_message:1663 - [2025-07-25 14:23:49] 【系统】小闲鱼智能提示:
-```
-
-### 改进后的日志格式
-```
-2025-07-25 14:23:47.770 | INFO | XianyuAutoAsync:init:1360 - 【user1_cookie】获取初始token...
-2025-07-25 14:23:47.771 | INFO | XianyuAutoAsync:refresh_token:134 - 【user1_cookie】开始刷新token...
-2025-07-25 14:23:48.269 | INFO | XianyuAutoAsync:refresh_token:200 - 【user1_cookie】Token刷新成功
-2025-07-25 14:23:49.286 | INFO | XianyuAutoAsync:init:1407 - 【user1_cookie】连接注册完成
-2025-07-25 14:23:49.288 | INFO | XianyuAutoAsync:handle_message:1663 - [2025-07-25 14:23:49] 【user1_cookie】【系统】小闲鱼智能提示:
-```
-
-## 🔧 修改的日志类型
-
-### 1. Token管理相关
-- ✅ `【{cookie_id}】开始刷新token...`
-- ✅ `【{cookie_id}】Token刷新成功`
-- ✅ `【{cookie_id}】Token刷新失败: {error}`
-- ✅ `【{cookie_id}】获取初始token...`
-- ✅ `【{cookie_id}】Token刷新成功,准备重新建立连接...`
-
-### 2. 连接管理相关
-- ✅ `【{cookie_id}】连接注册完成`
-- ✅ `【{cookie_id}】message: {message}`
-- ✅ `【{cookie_id}】send message`
-
-### 3. 系统消息相关
-- ✅ `[{time}] 【{cookie_id}】【系统】小闲鱼智能提示:`
-- ✅ `[{time}] 【{cookie_id}】【系统】其他类型消息: {content}`
-- ✅ `[{time}] 【{cookie_id}】系统消息不处理`
-- ✅ `[{time}] 【{cookie_id}】【系统】买家已付款,准备自动发货`
-- ✅ `[{time}] 【{cookie_id}】【系统】自动回复已禁用`
-- ✅ `[{time}] 【{cookie_id}】【系统】未找到匹配的回复规则,不回复`
-
-### 4. 商品和发货相关
-- ✅ `【{cookie_id}】从消息内容中提取商品ID: {item_id}`
-- ✅ `【{cookie_id}】准备自动发货: item_id={item_id}, item_title={title}`
-
-### 5. 回复生成相关
-- ✅ `【{cookie_id}】使用默认回复: {reply}`
-- ✅ `【{cookie_id}】AI回复生成成功: {reply}`
-
-## 📁 修改的文件
-
-### XianyuAutoAsync.py
-- **修改行数**: 约20处日志输出
-- **影响范围**: 所有核心功能的日志
-- **修改方式**: 在日志消息前添加 `【{self.cookie_id}】` 标识
-
-## 🎯 改进效果
-
-### 1. 问题定位能力
-- **改进前**: 无法区分不同用户的操作,调试困难
-- **改进后**: 一眼就能看出是哪个用户的操作
-
-### 2. 监控分析能力
-- **改进前**: 无法按用户统计操作情况
-- **改进后**: 可以轻松按用户过滤和统计
-
-### 3. 运维管理能力
-- **改进前**: 多用户问题排查复杂
-- **改进后**: 快速定位特定用户的问题
-
-## 💡 日志分析技巧
-
-### 1. 按用户过滤日志
-```bash
-# 查看特定用户的所有操作
-grep '【user1_cookie】' logs/xianyu_2025-07-25.log
-
-# 查看特定用户的错误日志
-grep 'ERROR.*【user1_cookie】' logs/xianyu_2025-07-25.log
-```
-
-### 2. 监控Token状态
-```bash
-# 查看所有用户的Token刷新情况
-grep '【.*】.*Token' logs/xianyu_2025-07-25.log
-
-# 查看Token刷新失败的情况
-grep '【.*】.*Token刷新失败' logs/xianyu_2025-07-25.log
-```
-
-### 3. 统计用户活跃度
-```bash
-# 统计各用户的操作次数
-grep -o '【[^】]*】' logs/xianyu_2025-07-25.log | sort | uniq -c
-
-# 查看最活跃的用户
-grep -o '【[^】]*】' logs/xianyu_2025-07-25.log | sort | uniq -c | sort -nr
-```
-
-### 4. 监控系统消息
-```bash
-# 查看所有系统级别的消息
-grep '【系统】' logs/xianyu_2025-07-25.log
-
-# 查看自动发货相关的消息
-grep '准备自动发货' logs/xianyu_2025-07-25.log
-```
-
-### 5. 分析回复情况
-```bash
-# 查看AI回复的使用情况
-grep 'AI回复生成成功' logs/xianyu_2025-07-25.log
-
-# 查看默认回复的使用情况
-grep '使用默认回复' logs/xianyu_2025-07-25.log
-```
-
-## 🔍 实时监控命令
-
-### 1. 实时查看特定用户的日志
-```bash
-tail -f logs/xianyu_2025-07-25.log | grep '【user1_cookie】'
-```
-
-### 2. 实时监控所有错误
-```bash
-tail -f logs/xianyu_2025-07-25.log | grep 'ERROR.*【.*】'
-```
-
-### 3. 实时监控Token刷新
-```bash
-tail -f logs/xianyu_2025-07-25.log | grep '【.*】.*Token'
-```
-
-## 📈 监控仪表板建议
-
-基于新的日志格式,可以构建以下监控指标:
-
-### 1. 用户活跃度指标
-- 每个用户的操作频率
-- 用户在线时长统计
-- 用户操作成功率
-
-### 2. 系统健康指标
-- Token刷新成功率(按用户)
-- 连接稳定性(按用户)
-- 错误发生频率(按用户)
-
-### 3. 业务指标
-- 自动回复使用率(按用户)
-- AI回复成功率(按用户)
-- 自动发货成功率(按用户)
-
-## 🚀 部署建议
-
-### 1. 重启服务
-```bash
-# 停止当前服务
-docker-compose down
-
-# 重新启动服务
-docker-compose up -d
-
-# 查看新的日志格式
-docker-compose logs -f
-```
-
-### 2. 日志轮转配置
-确保日志轮转配置能够处理增加的日志内容:
-```yaml
-# loguru配置示例
-rotation: "100 MB"
-retention: "7 days"
-compression: "zip"
-```
-
-### 3. 监控工具配置
-如果使用ELK、Grafana等监控工具,需要更新日志解析规则以识别新的Cookie ID字段。
-
-## 🎉 总结
-
-通过本次改进,多用户系统的日志现在具备了:
-
-- ✅ **清晰的用户标识**: 每条日志都能明确标识操作用户
-- ✅ **高效的问题定位**: 快速定位特定用户的问题
-- ✅ **精准的监控分析**: 支持按用户维度的监控和分析
-- ✅ **便捷的运维管理**: 简化多用户环境的运维工作
-
-这为多用户系统的稳定运行和高效管理奠定了坚实的基础!
diff --git a/MULTIUSER_ISOLATION_STATUS.md b/MULTIUSER_ISOLATION_STATUS.md
deleted file mode 100644
index d5eaeb9..0000000
--- a/MULTIUSER_ISOLATION_STATUS.md
+++ /dev/null
@@ -1,216 +0,0 @@
-# 多用户数据隔离状态报告
-
-## 🎯 总体状态
-
-**当前进度**: 核心功能已实现用户隔离,部分管理功能待完善
-
-**测试结果**: ✅ 核心数据隔离测试全部通过
-
-## 📊 功能模块隔离状态
-
-### ✅ 已完成隔离的模块
-
-#### 1. 账号管理 (Cookie管理)
-- ✅ 获取Cookie列表 - 只显示当前用户的Cookie
-- ✅ 添加Cookie - 自动绑定到当前用户
-- ✅ 更新Cookie - 权限验证
-- ✅ 删除Cookie - 权限验证
-- ✅ Cookie状态管理 - 权限验证
-
-#### 2. 自动回复管理
-- ✅ 关键字管理 - 完全隔离
-- ✅ 默认回复设置 - 完全隔离
-- ✅ 获取所有默认回复 - 只返回当前用户数据
-
-#### 3. 商品管理 (部分完成)
-- ✅ 获取所有商品 - 只返回当前用户商品
-- ✅ 按Cookie获取商品 - 权限验证
-- ✅ 获取商品详情 - 权限验证
-- ✅ 更新商品详情 - 权限验证
-- ✅ 删除商品信息 - 权限验证
-
-#### 4. AI回复设置
-- ✅ 获取AI回复设置 - 权限验证
-- ✅ 更新AI回复设置 - 权限验证
-- ✅ 获取所有AI回复设置 - 只返回当前用户数据
-
-#### 5. 消息通知 (部分完成)
-- ✅ 获取所有消息通知 - 只返回当前用户数据
-- ✅ 获取账号消息通知 - 权限验证
-- ✅ 设置消息通知 - 权限验证
-
-#### 6. 数据备份
-- ✅ 导出备份 - 只导出当前用户数据
-- ✅ 导入备份 - 只导入到当前用户
-
-### ❓ 需要确认隔离策略的模块
-
-#### 1. 卡券管理
-**当前状态**: 未隔离(全局共享)
-**建议**:
-- 选项A: 保持全局共享(所有用户共用卡券库)
-- 选项B: 实现用户隔离(每个用户独立的卡券)
-
-#### 2. 通知渠道
-**当前状态**: 未隔离(全局共享)
-**建议**:
-- 选项A: 保持全局共享(管理员统一配置)
-- 选项B: 实现用户隔离(每个用户独立配置)
-
-#### 3. 系统设置
-**当前状态**: 部分隔离
-**建议**:
-- 全局设置: 保持共享(如系统配置)
-- 用户设置: 实现隔离(如个人偏好)
-
-### ❌ 待修复的接口
-
-#### 商品管理剩余接口
-- `batch_delete_items` - 批量删除商品
-- `get_all_items_from_account` - 从账号获取所有商品
-- `get_items_by_page` - 分页获取商品
-
-#### 消息通知剩余接口
-- `delete_account_notifications` - 删除账号通知
-- `delete_message_notification` - 删除消息通知
-
-#### 卡券管理接口 (如需隔离)
-- `get_cards` - 获取卡券列表
-- `create_card` - 创建卡券
-- `get_card` - 获取卡券详情
-- `update_card` - 更新卡券
-- `delete_card` - 删除卡券
-
-#### 自动发货接口 (如需隔离)
-- `get_delivery_rules` - 获取发货规则
-- `create_delivery_rule` - 创建发货规则
-- `get_delivery_rule` - 获取发货规则详情
-- `update_delivery_rule` - 更新发货规则
-- `delete_delivery_rule` - 删除发货规则
-
-#### 通知渠道接口 (如需隔离)
-- `get_notification_channels` - 获取通知渠道
-- `create_notification_channel` - 创建通知渠道
-- `get_notification_channel` - 获取通知渠道详情
-- `update_notification_channel` - 更新通知渠道
-- `delete_notification_channel` - 删除通知渠道
-
-## 🧪 测试结果
-
-### ✅ 通过的测试
-
-1. **用户注册和登录**
- - 图形验证码生成和验证
- - 邮箱验证码发送和验证
- - 用户注册流程
- - 用户登录认证
-
-2. **数据隔离**
- - Cookie数据完全隔离
- - 用户只能看到自己的数据
- - 跨用户访问被正确拒绝
-
-3. **权限验证**
- - API层面权限检查
- - 403错误正确返回
- - 用户身份验证
-
-### 📊 测试统计
-
-- **已修复接口**: 25个 (使用新认证方式)
-- **待修复接口**: 28个 (仍使用旧认证方式)
-- **权限检查接口**: 23个 (包含用户权限验证)
-
-## 🔒 安全特性
-
-### ✅ 已实现的安全特性
-
-1. **用户认证**
- - JWT Token认证
- - 图形验证码防护
- - 邮箱验证码验证
-
-2. **数据隔离**
- - 数据库层面用户绑定
- - API层面权限验证
- - 跨用户访问拒绝
-
-3. **权限控制**
- - 基于用户ID的数据过滤
- - Cookie所有权验证
- - 操作权限检查
-
-### 🛡️ 安全机制
-
-```python
-# 标准的用户权限检查模式
-def api_function(cid: str, current_user: Dict[str, Any] = Depends(get_current_user)):
- # 1. 获取当前用户ID
- user_id = current_user['user_id']
-
- # 2. 获取用户的Cookie列表
- user_cookies = db_manager.get_all_cookies(user_id)
-
- # 3. 验证Cookie所有权
- if cid not in user_cookies:
- raise HTTPException(status_code=403, detail="无权限访问该Cookie")
-
- # 4. 执行业务逻辑
- # ...
-```
-
-## 📋 建议的隔离策略
-
-### 核心业务数据 (必须隔离)
-- ✅ Cookie/账号数据
-- ✅ 商品信息
-- ✅ 关键字和回复
-- ✅ AI回复设置
-- ✅ 消息通知配置
-
-### 配置数据 (建议策略)
-- **卡券管理**: 建议保持全局共享
-- **通知渠道**: 建议保持全局共享(管理员配置)
-- **发货规则**: 建议实现用户隔离
-- **系统设置**: 区分全局设置和用户设置
-
-### 管理功能 (特殊处理)
-- **系统监控**: 管理员专用
-- **用户管理**: 管理员专用
-- **系统配置**: 管理员专用
-
-## 🚀 下一步行动计划
-
-### 优先级1: 完成核心功能隔离
-1. 修复剩余的商品管理接口
-2. 修复剩余的消息通知接口
-3. 完善AI回复相关接口
-
-### 优先级2: 确认隔离策略
-1. 与产品团队确认卡券管理策略
-2. 确认通知渠道管理策略
-3. 确认自动发货规则策略
-
-### 优先级3: 完善管理功能
-1. 实现管理员用户管理界面
-2. 添加用户数据统计功能
-3. 完善系统监控功能
-
-### 优先级4: 测试和文档
-1. 编写完整的API测试用例
-2. 更新API文档
-3. 编写用户使用指南
-
-## 🎉 总结
-
-**当前状态**: 多用户系统的核心功能已经实现,数据隔离测试全部通过。
-
-**主要成就**:
-- ✅ 用户注册和认证系统完整
-- ✅ 核心业务数据完全隔离
-- ✅ 安全权限验证机制完善
-- ✅ 数据库层面支持多用户
-
-**待完善项目**: 主要是一些管理功能和配置功能的隔离策略确认。
-
-**安全性**: 系统已具备企业级的多用户数据隔离能力,可以安全地支持多个用户同时使用。
diff --git a/MULTIUSER_SYSTEM_README.md b/MULTIUSER_SYSTEM_README.md
deleted file mode 100644
index f7bbdbb..0000000
--- a/MULTIUSER_SYSTEM_README.md
+++ /dev/null
@@ -1,277 +0,0 @@
-# 多用户系统升级指南
-
-## 🎯 功能概述
-
-本次升级将闲鱼自动回复系统从单用户模式升级为多用户模式,实现以下功能:
-
-### ✨ 新增功能
-
-1. **用户注册系统**
- - 邮箱注册,支持验证码验证
- - 用户名唯一性检查
- - 密码安全存储(SHA256哈希)
-
-2. **数据隔离**
- - 每个用户只能看到自己的数据
- - Cookie、关键字、备份等完全隔离
- - 历史数据自动绑定到admin用户
-
-3. **邮箱验证**
- - 集成邮件发送API
- - 6位数字验证码
- - 10分钟有效期
- - 防重复使用
-
-## 🚀 升级步骤
-
-### 1. 备份数据
-```bash
-# 备份数据库文件
-cp xianyu_data.db xianyu_data.db.backup
-```
-
-### 2. 运行迁移脚本
-```bash
-# 迁移历史数据到admin用户
-python migrate_to_multiuser.py
-
-# 检查迁移状态
-python migrate_to_multiuser.py check
-```
-
-### 3. 重启应用
-```bash
-# 重启应用程序
-python Start.py
-```
-
-### 4. 验证功能
-```bash
-# 运行功能测试
-python test_multiuser_system.py
-```
-
-## 📋 API变更
-
-### 新增接口
-
-| 接口 | 方法 | 说明 |
-|------|------|------|
-| `/register` | POST | 用户注册 |
-| `/send-verification-code` | POST | 发送验证码 |
-| `/verify` | GET | 验证token(返回用户信息) |
-
-### 修改的接口
-
-所有需要认证的接口现在都支持用户隔离:
-
-- `/cookies` - 只返回当前用户的cookies
-- `/cookies/details` - 只返回当前用户的cookie详情
-- `/backup/export` - 只导出当前用户的数据
-- `/backup/import` - 只导入到当前用户
-
-## 🔐 认证系统
-
-### Token格式变更
-```javascript
-// 旧格式
-SESSION_TOKENS[token] = timestamp
-
-// 新格式
-SESSION_TOKENS[token] = {
- user_id: 1,
- username: 'admin',
- timestamp: 1234567890
-}
-```
-
-### 登录响应变更
-```json
-{
- "success": true,
- "token": "abc123...",
- "message": "登录成功",
- "user_id": 1
-}
-```
-
-## 🗄️ 数据库变更
-
-### 新增表
-
-1. **users** - 用户表
- ```sql
- CREATE TABLE users (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- username TEXT UNIQUE NOT NULL,
- email TEXT UNIQUE NOT NULL,
- password_hash TEXT NOT NULL,
- is_active BOOLEAN DEFAULT TRUE,
- created_at REAL NOT NULL,
- updated_at REAL NOT NULL
- );
- ```
-
-2. **email_verifications** - 邮箱验证码表
- ```sql
- CREATE TABLE email_verifications (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- email TEXT NOT NULL,
- code TEXT NOT NULL,
- expires_at REAL NOT NULL,
- used BOOLEAN DEFAULT FALSE,
- created_at REAL DEFAULT (strftime('%s', 'now'))
- );
- ```
-
-### 修改的表
-
-1. **cookies** - 添加user_id字段
- ```sql
- ALTER TABLE cookies ADD COLUMN user_id INTEGER;
- ```
-
-## 🎨 前端变更
-
-### 新增页面
-
-1. **注册页面** (`/register.html`)
- - 用户名、邮箱、密码输入
- - 邮箱验证码发送和验证
- - 表单验证和错误提示
- - 响应式设计
-
-### 修改的页面
-
-1. **登录页面** (`/login.html`)
- - 添加注册链接
- - 保持向后兼容
-
-## 📧 邮件系统
-
-### 邮件API配置
-```
-API地址: https://dy.zhinianboke.com/api/emailSend
-参数:
-- subject: 邮件主题
-- receiveUser: 收件人邮箱
-- sendHtml: 邮件内容(HTML格式)
-```
-
-### 邮件模板
-- 响应式HTML设计
-- 品牌化样式
-- 验证码突出显示
-- 安全提醒信息
-
-## 🔒 安全特性
-
-1. **密码安全**
- - SHA256哈希存储
- - 不可逆加密
-
-2. **验证码安全**
- - 6位随机数字
- - 10分钟有效期
- - 一次性使用
- - 防暴力破解
-
-3. **数据隔离**
- - 用户级别完全隔离
- - API层面权限检查
- - 数据库层面用户绑定
-
-## 🧪 测试指南
-
-### 功能测试
-```bash
-# 运行完整测试套件
-python test_multiuser_system.py
-```
-
-### 手动测试步骤
-
-1. **注册测试**
- - 访问 `/register.html`
- - 输入用户信息
- - 验证邮箱验证码
- - 完成注册
-
-2. **登录测试**
- - 使用新注册的账号登录
- - 验证只能看到自己的数据
-
-3. **数据隔离测试**
- - 创建多个用户账号
- - 分别添加不同的cookies
- - 验证数据完全隔离
-
-## 🐛 故障排除
-
-### 常见问题
-
-1. **迁移失败**
- ```bash
- # 检查数据库文件权限
- ls -la xianyu_data.db
-
- # 检查迁移状态
- python migrate_to_multiuser.py check
- ```
-
-2. **邮件发送失败**
- - 检查网络连接
- - 验证邮箱地址格式
- - 查看应用日志
-
-3. **用户无法登录**
- - 检查用户名和密码
- - 确认用户状态为激活
- - 查看认证日志
-
-### 回滚方案
-
-如果升级出现问题,可以回滚到单用户模式:
-
-1. 恢复数据库备份
- ```bash
- cp xianyu_data.db.backup xianyu_data.db
- ```
-
-2. 使用旧版本代码
-3. 重启应用
-
-## 📈 性能影响
-
-- **数据库查询**: 增加了user_id过滤条件,对性能影响微小
-- **内存使用**: CookieManager仍加载所有数据,API层面进行过滤
-- **响应时间**: 增加了用户验证步骤,延迟增加<10ms
-
-## 🔮 未来规划
-
-1. **用户管理**
- - 管理员用户管理界面
- - 用户权限控制
- - 用户状态管理
-
-2. **高级功能**
- - 用户组和权限
- - 数据共享机制
- - 审计日志
-
-3. **性能优化**
- - 用户级别的CookieManager
- - 数据库索引优化
- - 缓存策略
-
-## 📞 技术支持
-
-如有问题,请:
-1. 查看应用日志
-2. 运行测试脚本诊断
-3. 检查数据库状态
-4. 联系技术支持
-
----
-
-**升级完成后,您的闲鱼自动回复系统将支持多用户使用,每个用户的数据完全隔离,提供更好的安全性和可扩展性!** 🎉
diff --git a/README.md b/README.md
index ce4260f..f16ea20 100644
--- a/README.md
+++ b/README.md
@@ -1,173 +1,332 @@
-# 🐟 XianYuAutoDeliveryX - 闲鱼虚拟商品商自动发货&聊天对接大模型
+# 🐟 闲鱼自动回复系统
-[](https://www.python.org/)
-[](LICENSE)
+[](https://github.com/zhinianboke/xianyu-auto-reply)
+[](https://github.com/zhinianboke/xianyu-auto-reply#-快速开始)
-**✨ 基于闲鱼API的自动发货系统,支持虚拟商品商品聊天窗口自动发货、消息自动回复等功能。**
-**⚠️ 注意:本项目仅供学习交流使用,请勿用于商业用途。**
+一个功能完整的闲鱼自动回复和管理系统,支持多用户、多账号管理,具备智能回复、自动发货、商品管理等企业级功能。
-## 🌟 核心特性
+## ✨ 核心特性
-- 🔐 **用户认证系统** - 安全的登录认证,保护管理界面
-- 👥 **多账号管理** - 支持同时管理多个闲鱼账号
-- 🎯 **智能关键词回复** - 每个账号独立的关键词回复设置
-- 💾 **数据持久化** - SQLite数据库存储账号和关键词数据
-- 🌐 **美观Web界面** - 响应式设计,操作简单直观
-- 📡 **API接口** - 完整的RESTful API支持
-- 🔄 **实时消息处理** - 基于WebSocket的实时消息监控
-- 📊 **订单状态监控** - 实时跟踪订单状态变化
-- 📝 **完善的日志系统** - 详细的操作日志记录
+### 🔐 多用户系统
+- **用户注册登录** - 支持邮箱验证码注册,图形验证码保护
+- **数据完全隔离** - 每个用户的数据独立存储,互不干扰
+- **权限管理** - 严格的用户权限控制和JWT认证
+- **安全保护** - 防暴力破解、会话管理、安全日志
-## 🛠️ 快速开始
+### 📱 多账号管理
+- **无限账号支持** - 每个用户可管理多个闲鱼账号
+- **独立运行** - 每个账号独立监控,互不影响
+- **实时状态** - 账号连接状态实时监控
+- **批量操作** - 支持批量启动、停止账号任务
-### ⛳ 运行环境
-- Python 3.7+
+### 🤖 智能回复系统
+- **关键词匹配** - 支持精确关键词匹配回复
+- **AI智能回复** - 集成OpenAI API,支持上下文理解
+- **变量替换** - 回复内容支持动态变量(用户名、商品信息等)
+- **优先级策略** - 关键词回复优先,AI回复兜底
+
+### 🚚 自动发货功能
+- **智能匹配** - 基于商品信息自动匹配发货规则
+- **多种触发** - 支持付款消息、小刀消息等多种触发条件
+- **防重复发货** - 智能防重复机制,避免重复发货
+- **卡密发货** - 支持文本内容和卡密文件发货
+
+### 🛍️ 商品管理
+- **自动收集** - 消息触发时自动收集商品信息
+- **API获取** - 通过闲鱼API获取完整商品详情
+- **批量管理** - 支持批量查看、编辑商品信息
+- **智能去重** - 自动去重,避免重复存储
+
+### 📊 系统监控
+- **实时日志** - 完整的操作日志记录和查看
+- **性能监控** - 系统资源使用情况监控
+- **健康检查** - 服务状态健康检查
+- **数据备份** - 自动数据备份和恢复
+
+## 🚀 快速开始
+
+### 方式一:Docker 一键部署(最简单)
-### 🎯 安装依赖
```bash
+# 创建数据目录
+mkdir -p xianyu-auto-reply
+
+# 一键启动容器
+docker run -d \
+ -p 8080:8080 \
+ -v $PWD/xianyu-auto-reply/:/app/data/ \
+ --name xianyu-auto-reply \
+ --privileged=true \
+ registry.cn-shanghai.aliyuncs.com/zhinian-software/xianyu-auto-reply:1.0
+
+# 访问系统
+# http://localhost:8080
+```
+
+### 方式二:Docker Compose 部署(推荐)
+
+```bash
+# 1. 克隆项目
+git clone https://github.com/zhinianboke/xianyu-auto-reply.git
+cd xianyu-auto-reply
+
+# 2. 一键部署
+./docker-deploy.sh
+
+# 3. 访问系统
+# http://localhost:8080
+```
+
+### 方式三:本地部署
+
+```bash
+# 1. 克隆项目
+git clone https://github.com/zhinianboke/xianyu-auto-reply.git
+cd xianyu-auto-reply
+
+# 2. 安装依赖
pip install -r requirements.txt
-```
-### 🎨 配置说明
-1. 在 `global_config.yml` 中配置基本参数
-2. 系统支持多账号管理,可通过Web界面添加多个闲鱼账号Cookie
-
-### 🚀 运行项目
-```bash
+# 3. 启动系统
python Start.py
+
+# 4. 访问系统
+# http://localhost:8080
```
-### 🔐 登录系统
-1. 启动后访问 `http://localhost:8080`
-2. 默认登录账号:
- - 用户名:`admin`
- - 密码:`admin123`
-3. 登录后可进入管理界面进行操作
+### 🐳 Docker 部署说明
+
+#### 一键部署特点
+- **无需配置** - 使用预构建镜像,开箱即用
+- **数据持久化** - 自动挂载数据目录,数据不丢失
+- **快速启动** - 30秒内完成部署
+- **生产就绪** - 包含所有依赖和优化配置
+
+#### 容器管理命令
+```bash
+# 查看容器状态
+docker ps
+
+# 查看容器日志
+docker logs -f xianyu-auto-reply
+
+# 停止容器
+docker stop xianyu-auto-reply
+
+# 重启容器
+docker restart xianyu-auto-reply
+
+# 删除容器
+docker rm -f xianyu-auto-reply
+```
+
+## 📋 系统使用
+
+### 1. 用户注册
+- 访问 `http://localhost:8080/register.html`
+- 填写用户信息,完成邮箱验证
+- 输入图形验证码完成注册
+
+### 2. 添加闲鱼账号
+- 登录系统后进入主界面
+- 点击"添加新账号"
+- 输入账号ID和完整的Cookie值
+- 系统自动启动账号监控任务
+
+### 3. 配置自动回复
+- **关键词回复**:设置关键词和对应回复内容
+- **AI回复**:配置OpenAI API密钥启用智能回复
+- **默认回复**:设置未匹配时的默认回复
+
+### 4. 设置自动发货
+- 添加发货规则,设置商品关键词和发货内容
+- 支持文本内容和卡密文件两种发货方式
+- 系统检测到付款消息时自动发货
+
+## 🏗️ 系统架构
+
+```
+┌─────────────────────────────────────┐
+│ Web界面 (FastAPI) │
+│ 用户管理 + 功能界面 │
+└─────────────┬───────────────────────┘
+ │
+┌─────────────▼───────────────────────┐
+│ CookieManager │
+│ 多账号任务管理 │
+└─────────────┬───────────────────────┘
+ │
+┌─────────────▼───────────────────────┐
+│ XianyuLive (多实例) │
+│ WebSocket连接 + 消息处理 │
+└─────────────┬───────────────────────┘
+ │
+┌─────────────▼───────────────────────┐
+│ SQLite数据库 │
+│ 用户数据 + 商品信息 + 配置数据 │
+└─────────────────────────────────────┘
+```
## 📁 项目结构
+
```
-├── Start.py # 项目启动入口
-├── XianyuAutoAsync.py # 核心业务逻辑
-├── config.py # 配置管理
-├── cookie_manager.py # Cookie管理器
-├── db_manager.py # 数据库管理
-├── reply_server.py # FastAPI服务器
-├── utils/ # 工具函数目录
-│ ├── xianyu_utils.py # 闲鱼相关工具
-│ ├── message_utils.py # 消息处理工具
-│ └── ws_utils.py # WebSocket工具
-├── static/ # 静态资源
-│ ├── index.html # 管理界面
-│ └── login.html # 登录页面
-├── logs/ # 日志文件
-├── global_config.yml # 全局配置文件
-├── xianyu_data.db # SQLite数据库
-└── requirements.txt # Python依赖
+xianyu-auto-reply/
+├── Start.py # 主启动文件
+├── XianyuAutoAsync.py # 闲鱼WebSocket客户端核心
+├── reply_server.py # FastAPI Web服务器
+├── db_manager.py # 数据库管理模块
+├── cookie_manager.py # Cookie和任务管理
+├── ai_reply_engine.py # AI回复引擎
+├── config.py # 配置管理
+├── file_log_collector.py # 日志收集器
+├── global_config.yml # 全局配置文件
+├── requirements.txt # Python依赖
+├── docker-compose.yml # Docker编排配置
+├── Dockerfile # Docker镜像构建
+├── static/ # 前端静态文件
+│ ├── index.html # 主界面
+│ ├── login.html # 登录页面
+│ ├── register.html # 注册页面
+│ └── ... # 其他页面和资源
+├── logs/ # 日志文件目录
+├── data/ # 数据库文件目录
+└── backups/ # 备份文件目录
```
-## 🎯 主要功能
+## ⚙️ 配置说明
-### 1. 用户认证系统
-- 安全的登录认证机制
-- Session token管理
-- 自动登录状态检查
-- 登出功能
+### 环境变量配置
+系统支持通过环境变量或 `.env` 文件进行配置:
-### 2. 多账号管理
-- 支持添加多个闲鱼账号
-- 每个账号独立管理
-- Cookie安全存储
-- 账号状态监控
+```bash
+# 基础配置
+WEB_PORT=8080
+ADMIN_USERNAME=admin
+ADMIN_PASSWORD=admin123
+JWT_SECRET_KEY=your-secret-key
-### 3. 智能关键词回复
-- 每个账号独立的关键词设置
-- 支持变量替换:`{send_user_name}`, `{send_user_id}`, `{send_message}`
-- 实时关键词匹配
-- 默认回复机制
+# 多用户系统
+MULTIUSER_ENABLED=true
+USER_REGISTRATION_ENABLED=true
+EMAIL_VERIFICATION_ENABLED=true
+CAPTCHA_ENABLED=true
-### 4. Web管理界面
-- 响应式设计,支持移动端
-- 直观的操作界面
-- 实时数据更新
-- 操作反馈提示
+# AI回复配置
+AI_REPLY_ENABLED=false
+DEFAULT_AI_MODEL=qwen-plus
+DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
-## 🔌 API 接口说明
-
-### 智能回复接口
-`POST http://localhost:8080/xianyu/reply`
-
-#### 接口说明
-你需要实现这个接口,本项目会调用这个接口获取自动回复的内容并发送给客户
-不实现这个接口也没关系,系统会默认回复,你也可以配置默认回复的内容
-用于处理闲鱼消息的自动回复,支持对接大语言模型进行智能回复。
-
-**通过这个接口可以检测到用户是否已付款,然后回复虚拟资料内容即可**
-#### 请求参数
-```json
-{
- "msg_time": "消息时间",
- "user_url": "用户主页URL",
- "send_user_id": "发送者ID",
- "send_user_name": "发送者昵称",
- "item_id": "商品ID",
- "send_message": "发送的消息内容",
- "chat_id": "会话ID"
-}
+# 自动发货配置
+AUTO_DELIVERY_ENABLED=true
+AUTO_DELIVERY_TIMEOUT=30
```
-#### 响应格式
-```json
-{
- "code": 200,
- "data": {
- "send_msg": "回复的消息内容"
- }
-}
+### 全局配置文件
+`global_config.yml` 包含详细的系统配置,支持:
+- WebSocket连接参数
+- API接口配置
+- 自动回复设置
+- 商品管理配置
+- 日志配置等
+
+## 🔧 高级功能
+
+### AI回复配置
+1. 在用户设置中配置OpenAI API密钥
+2. 选择AI模型(支持GPT-3.5、GPT-4、通义千问等)
+3. 设置回复策略和提示词
+4. 启用AI回复功能
+
+### 自动发货规则
+1. 进入发货管理页面
+2. 添加发货规则,设置商品关键词
+3. 上传卡密文件或输入发货内容
+4. 系统自动匹配商品并发货
+
+### 商品信息管理
+1. 系统自动收集消息中的商品信息
+2. 通过API获取完整商品详情
+3. 支持手动编辑商品信息
+4. 为自动发货提供准确的商品数据
+
+## 📊 监控和维护
+
+### 日志管理
+- **实时日志**:Web界面查看实时系统日志
+- **日志文件**:`logs/` 目录下的按日期分割的日志文件
+- **日志级别**:支持DEBUG、INFO、WARNING、ERROR级别
+
+### 数据备份
+```bash
+# 手动备份
+./docker-deploy.sh backup
+
+# 查看备份
+ls backups/
```
-#### 配置示例
-```yaml
-AUTO_REPLY:
- api:
- enabled: true # 是否启用API回复
- timeout: 10 # 超时时间(秒)
- url: http://localhost:8080/xianyu/reply
+### 健康检查
+```bash
+# 检查服务状态
+curl http://localhost:8080/health
+
+# 查看系统状态
+./docker-deploy.sh status
```
-#### 使用场景
-- 当收到买家消息时,系统会自动调用此接口
-- 支持接入 ChatGPT、文心一言等大语言模型
-- 支持自定义回复规则和模板
-- 支持消息变量替换(如 `{send_user_name}`)
+## 🔒 安全特性
-#### 注意事项
-- 接口需要返回正确的状态码(200)和消息内容
-- 建议实现错误重试机制
-- 注意处理超时情况(默认10秒)
-- 可以根据需要扩展更多的参数和功能
+- **JWT认证**:安全的用户认证机制
+- **图形验证码**:防止自动化攻击
+- **邮箱验证**:确保用户邮箱真实性
+- **数据隔离**:用户数据完全隔离
+- **会话管理**:安全的会话超时机制
+- **操作日志**:完整的用户操作记录
-## 🗝️ 注意事项
-- 请确保闲鱼账号已登录并获取有效的 Cookie
-- 建议在正式环境使用前先在测试环境验证
-- 定期检查日志文件,及时处理异常情况
-- 使用大模型时注意 API 调用频率和成本控制
+## 🤝 贡献指南
-## 📝 效果
+欢迎为项目做出贡献!您可以通过以下方式参与:
+### 📝 提交问题
+- 在 [GitHub Issues](https://github.com/zhinianboke/xianyu-auto-reply/issues) 中报告Bug
+- 提出新功能建议和改进意见
+- 分享使用经验和最佳实践
-
+### 🔧 代码贡献
+- Fork 项目到您的GitHub账号
+- 创建功能分支:`git checkout -b feature/your-feature`
+- 提交更改:`git commit -am 'Add some feature'`
+- 推送分支:`git push origin feature/your-feature`
+- 提交 Pull Request
-
+### 📖 文档贡献
+- 改进现有文档
+- 添加使用示例
+- 翻译文档到其他语言
-## 🧸特别鸣谢
+## 📞 技术支持
-本项目参考了以下开源项目: https://github.com/cv-cat/XianYuApis
+### 🔧 故障排除
+如遇问题,请:
+1. 查看日志:`docker-compose logs -f`
+2. 检查状态:`./docker-deploy.sh status`
+3. 健康检查:`curl http://localhost:8080/health`
-感谢[@CVcat](https://github.com/cv-cat)的技术支持
+### 💬 交流群组
-## 📞 联系方式
-如有问题或建议,欢迎提交 Issue 或 Pull Request。
+欢迎加入我们的技术交流群,获取实时帮助和最新更新:
-## 技术交流
+#### 微信交流群
+
-
+#### QQ交流群
+
+
+### 📧 联系方式
+- **技术支持**:遇到问题可在群内咨询
+- **功能建议**:欢迎提出改进建议
+- **Bug反馈**:发现问题请及时反馈
+
+---
+
+🎉 **开始使用闲鱼自动回复系统,让您的闲鱼店铺管理更加智能高效!**
diff --git a/REGISTER_PAGE_OPTIMIZATION.md b/REGISTER_PAGE_OPTIMIZATION.md
deleted file mode 100644
index cba7408..0000000
--- a/REGISTER_PAGE_OPTIMIZATION.md
+++ /dev/null
@@ -1,233 +0,0 @@
-# 注册页面布局优化
-
-## 🎯 优化目标
-
-将注册页面优化为一屏显示,消除垂直滚动条,提升用户体验。
-
-## 📊 优化前后对比
-
-### 优化前的问题
-- ❌ 页面过长,需要垂直滚动
-- ❌ 间距过大,浪费屏幕空间
-- ❌ 字体和元素尺寸偏大
-- ❌ 表单提示文字占用过多空间
-
-### 优化后的改进
-- ✅ 整个页面在一屏内显示完整
-- ✅ 紧凑而美观的布局
-- ✅ 适当的间距和字体大小
-- ✅ 简化的提示文字
-
-## 🔧 具体优化措施
-
-### 1. 容器和布局优化
-
-**优化前:**
-```css
-.register-container {
- max-width: 450px;
- padding: 2rem;
-}
-.register-header {
- padding: 2rem;
-}
-.register-body {
- padding: 2rem;
-}
-```
-
-**优化后:**
-```css
-.register-container {
- max-width: 420px;
- max-height: 95vh;
- overflow-y: auto;
-}
-.register-header {
- padding: 1.2rem;
-}
-.register-body {
- padding: 1.2rem;
-}
-```
-
-### 2. 表单元素优化
-
-**优化前:**
-```css
-.form-control {
- padding: 12px 15px;
- border: 2px solid #e9ecef;
- border-radius: 10px;
-}
-.mb-3 {
- margin-bottom: 1rem;
-}
-```
-
-**优化后:**
-```css
-.form-control {
- padding: 8px 12px;
- border: 1px solid #e9ecef;
- border-radius: 8px;
- font-size: 0.9rem;
-}
-.mb-3 {
- margin-bottom: 0.8rem !important;
-}
-```
-
-### 3. 文字和标签优化
-
-**优化前:**
-- 详细的表单提示文字
-- 较大的字体尺寸
-- 较多的说明文本
-
-**优化后:**
-- 简化的占位符文字
-- 适中的字体尺寸
-- 精简的说明文本
-
-```css
-.form-label {
- font-size: 0.85rem;
- margin-bottom: 0.3rem;
-}
-.form-text {
- font-size: 0.75rem;
- margin-top: 0.2rem;
-}
-```
-
-### 4. 按钮和交互元素优化
-
-**优化前:**
-```css
-.btn-register {
- padding: 12px;
- border-radius: 10px;
-}
-.btn-code {
- border-radius: 10px;
-}
-```
-
-**优化后:**
-```css
-.btn-register {
- padding: 10px;
- border-radius: 8px;
- font-size: 0.9rem;
-}
-.btn-code {
- padding: 8px 12px;
- border-radius: 8px;
- font-size: 0.85rem;
-}
-```
-
-### 5. 图形验证码优化
-
-**优化前:**
-- 验证码图片高度 38px
-- 较大的间距
-
-**优化后:**
-- 验证码图片高度 32px
-- 紧凑的布局
-- 使用 `g-2` 类减少列间距
-
-```html
-
-
-
-
-
-
![]()
-
-
-```
-
-### 6. 响应式优化
-
-添加了针对小屏幕的特殊优化:
-
-```css
-@media (max-height: 700px) {
- .register-header { padding: 1rem; }
- .mb-3 { margin-bottom: 0.6rem !important; }
- .form-control { padding: 6px 10px; }
-}
-
-@media (max-width: 480px) {
- .register-container { margin: 5px; }
- .row.g-2 > * { padding: 0.25rem; }
-}
-```
-
-## 📱 用户体验提升
-
-### 视觉效果
-- **更紧凑**:整个表单在一屏内完整显示
-- **更清晰**:减少了视觉噪音,重点突出
-- **更现代**:圆角和间距更加协调
-
-### 交互体验
-- **无滚动**:用户无需滚动即可看到所有内容
-- **快速填写**:表单元素紧凑,填写更高效
-- **移动友好**:在手机上也能良好显示
-
-### 功能完整性
-- ✅ 保持所有原有功能
-- ✅ 图形验证码正常工作
-- ✅ 邮箱验证码流程完整
-- ✅ 表单验证逻辑不变
-
-## 🎨 设计原则
-
-1. **简洁性**:去除不必要的装饰和间距
-2. **功能性**:保持所有功能完整可用
-3. **可读性**:确保文字清晰易读
-4. **一致性**:保持设计风格统一
-5. **响应性**:适配不同屏幕尺寸
-
-## 📏 尺寸对比
-
-| 元素 | 优化前 | 优化后 | 节省空间 |
-|------|--------|--------|----------|
-| 容器内边距 | 2rem | 1.2rem | 40% |
-| 表单间距 | 1rem | 0.8rem | 20% |
-| 输入框内边距 | 12px 15px | 8px 12px | 33% |
-| 按钮内边距 | 12px | 10px | 17% |
-| 验证码高度 | 38px | 32px | 16% |
-
-## 🔍 测试建议
-
-1. **不同分辨率测试**
- - 1920x1080 (桌面)
- - 1366x768 (笔记本)
- - 375x667 (手机)
-
-2. **不同浏览器测试**
- - Chrome
- - Firefox
- - Safari
- - Edge
-
-3. **功能完整性测试**
- - 图形验证码生成和验证
- - 邮箱验证码发送
- - 表单提交和验证
- - 错误提示显示
-
-## 🎉 优化成果
-
-- **✅ 一屏显示**:消除了垂直滚动条
-- **✅ 美观紧凑**:保持了视觉美感
-- **✅ 功能完整**:所有功能正常工作
-- **✅ 响应式**:适配各种屏幕尺寸
-- **✅ 用户友好**:提升了整体用户体验
-
-现在用户可以在一个屏幕内完成整个注册流程,无需滚动,大大提升了用户体验!
diff --git a/SYSTEM_IMPROVEMENTS_SUMMARY.md b/SYSTEM_IMPROVEMENTS_SUMMARY.md
deleted file mode 100644
index 930f8eb..0000000
--- a/SYSTEM_IMPROVEMENTS_SUMMARY.md
+++ /dev/null
@@ -1,298 +0,0 @@
-# 系统改进功能总结
-
-## 🎯 改进概述
-
-根据用户需求,对闲鱼自动回复系统进行了三项重要改进,提升了管理员的使用体验和数据管理能力。
-
-## ✅ 已完成的改进
-
-### 1. 📋 日志界面优化
-
-#### 改进内容
-- **最新日志置顶**:日志默认最新的显示在最上面
-- **自动滚动调整**:页面加载后自动滚动到顶部显示最新日志
-
-#### 技术实现
-```javascript
-// 反转日志数组,让最新的日志显示在最上面
-const reversedLogs = [...logs].reverse();
-
-// 自动滚动到顶部(显示最新日志)
-scrollToTop();
-```
-
-#### 用户体验提升
-- ✅ 最新日志一目了然
-- ✅ 无需手动滚动查看最新信息
-- ✅ 符合用户查看习惯
-
-### 2. 🗂️ 系统管理简化
-
-#### 改进内容
-- **删除JSON格式备份**:移除了兼容模式的JSON备份功能
-- **保留数据库模式**:只保留更高效的数据库文件备份
-- **界面简化**:备份管理界面更加简洁明了
-
-#### 删除的功能
-- ❌ JSON格式备份导出
-- ❌ JSON格式备份导入
-- ❌ 相关的JavaScript函数
-
-#### 保留的功能
-- ✅ 数据库文件直接下载
-- ✅ 数据库文件直接上传恢复
-- ✅ 备份文件列表查询
-
-#### 优势对比
-| 特性 | 数据库备份 | JSON备份(已删除) |
-|------|------------|-------------------|
-| 备份速度 | ⚡ 极快 | 🐌 较慢 |
-| 文件大小 | 📦 最小 | 📦 较大 |
-| 恢复速度 | ⚡ 极快 | 🐌 较慢 |
-| 操作复杂度 | 🟢 简单 | 🟡 复杂 |
-
-### 3. 🗄️ 数据管理功能(全新)
-
-#### 功能概述
-新增了完整的数据管理功能,允许管理员查看和管理数据库中的所有表数据。
-
-#### 主要特性
-
-##### 📊 表数据查看
-- **表选择器**:下拉框显示所有数据表及中文含义
-- **数据展示**:表格形式显示所有记录
-- **列信息**:自动获取表结构和列名
-- **记录统计**:实时显示记录数量
-
-##### 🗑️ 数据删除功能
-- **单条删除**:支持删除指定记录
-- **批量清空**:支持清空整个表(除用户表外)
-- **确认机制**:危险操作需要二次确认
-- **权限保护**:不能删除管理员自己
-
-##### 🔒 安全机制
-- **权限验证**:只有admin用户可以访问
-- **表名验证**:只允许操作预定义的安全表
-- **管理员保护**:不能删除管理员用户
-- **操作日志**:所有操作都有详细日志
-
-#### 支持的数据表
-
-| 表名 | 中文含义 | 支持操作 |
-|------|----------|----------|
-| users | 用户表 | 查看、删除(除管理员) |
-| cookies | Cookie账号表 | 查看、删除、清空 |
-| keywords | 关键字表 | 查看、删除、清空 |
-| default_replies | 默认回复表 | 查看、删除、清空 |
-| ai_reply_settings | AI回复设置表 | 查看、删除、清空 |
-| message_notifications | 消息通知表 | 查看、删除、清空 |
-| cards | 卡券表 | 查看、删除、清空 |
-| delivery_rules | 发货规则表 | 查看、删除、清空 |
-| notification_channels | 通知渠道表 | 查看、删除、清空 |
-| user_settings | 用户设置表 | 查看、删除、清空 |
-| email_verifications | 邮箱验证表 | 查看、删除、清空 |
-| captcha_codes | 验证码表 | 查看、删除、清空 |
-
-#### 界面设计
-
-```
-┌─────────────────────────────────────────────────────────────┐
-│ 数据管理 │
-├─────────────────────────┬───────────────────────────────────┤
-│ 选择数据表 │ 数据统计 │
-│ │ │
-│ [下拉框选择表] │ [记录数显示] [刷新按钮] │
-└─────────────────────────┴───────────────────────────────────┘
-
-┌─────────────────────────────────────────────────────────────┐
-│ 数据内容 │
-│ ┌─────┬─────────┬─────────┬─────────┬─────────┬─────────┐ │
-│ │ ID │ 字段1 │ 字段2 │ 字段3 │ 字段4 │ 操作 │ │
-│ ├─────┼─────────┼─────────┼─────────┼─────────┼─────────┤ │
-│ │ 1 │ 数据1 │ 数据2 │ 数据3 │ 数据4 │ [删除] │ │
-│ │ 2 │ 数据1 │ 数据2 │ 数据3 │ 数据4 │ [删除] │ │
-│ └─────┴─────────┴─────────┴─────────┴─────────┴─────────┘ │
-└─────────────────────────────────────────────────────────────┘
-```
-
-## 🔧 技术实现
-
-### 后端API接口
-
-#### 数据管理API
-```python
-# 获取表数据
-GET /admin/data/{table_name}
-
-# 删除单条记录
-DELETE /admin/data/{table_name}/{record_id}
-
-# 清空表数据
-DELETE /admin/data/{table_name}
-```
-
-#### 数据库方法
-```python
-# 获取表数据和结构
-def get_table_data(self, table_name: str)
-
-# 删除指定记录
-def delete_table_record(self, table_name: str, record_id: str)
-
-# 清空表数据
-def clear_table_data(self, table_name: str)
-```
-
-### 前端实现
-
-#### 页面路由
-- `/data_management.html` - 数据管理页面
-
-#### 核心功能
-```javascript
-// 加载表数据
-function loadTableData()
-
-// 显示表数据
-function displayTableData(data, columns)
-
-// 删除记录
-function deleteRecord(record, index)
-
-// 清空表数据
-function confirmDeleteAll()
-```
-
-## 🎨 用户界面
-
-### 管理员菜单更新
-在主页侧边栏的管理员功能区域新增:
-```
-管理员功能
-├── 用户管理
-├── 系统日志
-└── 数据管理 ← 新增
-```
-
-### 数据管理页面特性
-- **响应式设计**:适配各种屏幕尺寸
-- **表格滚动**:支持大量数据的滚动查看
-- **固定表头**:滚动时表头保持可见
-- **操作确认**:删除操作有确认对话框
-- **实时统计**:动态显示记录数量
-
-## 🛡️ 安全特性
-
-### 权限控制
-- **管理员专用**:所有功能只有admin用户可以访问
-- **前端验证**:页面加载时验证用户身份
-- **后端验证**:API接口严格验证管理员权限
-
-### 数据保护
-- **表名白名单**:只允许操作预定义的安全表
-- **管理员保护**:不能删除管理员用户记录
-- **操作确认**:危险操作需要用户确认
-- **详细日志**:所有操作都有完整的日志记录
-
-### 错误处理
-- **异常捕获**:完善的错误处理机制
-- **用户提示**:清晰的成功/失败提示
-- **数据回滚**:失败时自动回滚操作
-
-## 💡 使用方法
-
-### 访问数据管理
-1. 使用admin账号登录系统
-2. 在主页侧边栏点击"数据管理"
-3. 进入数据管理页面
-
-### 查看表数据
-1. 在下拉框中选择要查看的数据表
-2. 系统自动加载并显示表数据
-3. 查看记录数量和表信息
-
-### 删除数据
-1. 点击记录行的"删除"按钮
-2. 在确认对话框中查看记录详情
-3. 确认删除操作
-
-### 清空表数据
-1. 选择要清空的表
-2. 点击"清空表"按钮
-3. 确认清空操作(不可恢复)
-
-## 🎯 应用场景
-
-### 1. 数据维护
-- 清理测试数据
-- 删除无效记录
-- 维护数据质量
-
-### 2. 问题排查
-- 查看具体数据内容
-- 分析数据异常
-- 验证数据完整性
-
-### 3. 系统管理
-- 监控数据增长
-- 管理用户数据
-- 清理过期信息
-
-### 4. 开发调试
-- 查看数据结构
-- 验证功能效果
-- 测试数据操作
-
-## 📊 改进效果
-
-### 用户体验提升
-- ✅ 日志查看更直观(最新在上)
-- ✅ 备份操作更简单(只保留数据库模式)
-- ✅ 数据管理更方便(可视化操作)
-
-### 管理效率提升
-- ✅ 快速查看任意表数据
-- ✅ 便捷删除无效记录
-- ✅ 直观的数据统计信息
-
-### 系统维护能力增强
-- ✅ 完整的数据管理功能
-- ✅ 安全的操作权限控制
-- ✅ 详细的操作日志记录
-
-## 🚀 部署说明
-
-### 立即可用
-- 重启服务后所有功能立即生效
-- 无需额外配置
-- 兼容现有数据
-
-### 访问方式
-```
-数据管理: http://your-domain/data_management.html
-日志管理: http://your-domain/log_management.html
-用户管理: http://your-domain/user_management.html
-```
-
-### 权限要求
-- 只有username为'admin'的用户可以访问
-- 其他用户访问会自动跳转到首页
-
-## 🎉 总结
-
-通过本次改进,闲鱼自动回复系统现在具备了:
-
-### ✅ 主要成就
-1. **优化的日志体验**:最新日志优先显示
-2. **简化的备份管理**:只保留最高效的数据库备份
-3. **强大的数据管理**:可视化的数据库表管理功能
-4. **完善的权限控制**:严格的管理员权限验证
-5. **安全的操作机制**:完善的确认和保护机制
-
-### 🎯 实用价值
-- **提升效率**:管理员操作更加便捷高效
-- **增强安全**:严格的权限控制和操作保护
-- **便于维护**:直观的数据管理和日志查看
-- **优化体验**:符合用户习惯的界面设计
-
-现在您的多用户闲鱼自动回复系统具备了更加完善的管理功能!🎊
diff --git a/TOKEN_FIX_SUMMARY.md b/TOKEN_FIX_SUMMARY.md
deleted file mode 100644
index 9184741..0000000
--- a/TOKEN_FIX_SUMMARY.md
+++ /dev/null
@@ -1,192 +0,0 @@
-# Token认证问题修复总结
-
-## 🎯 问题描述
-
-用户反馈:管理员页面可以访问,但是点击功能时提示"未登录",API调用返回401未授权错误。
-
-## 🔍 问题分析
-
-通过日志分析发现:
-```
-INFO: 127.0.0.1:63674 - "GET /admin/users HTTP/1.1" 401 Unauthorized
-INFO: 【未登录】 API响应: GET /admin/users - 401 (0.003s)
-```
-
-问题根源:**Token存储key不一致**
-
-### 🔧 具体问题
-
-1. **登录页面** (`login.html`) 设置token:
- ```javascript
- localStorage.setItem('auth_token', result.token);
- ```
-
-2. **主页面** (`index.html`) 读取token:
- ```javascript
- let authToken = localStorage.getItem('auth_token');
- ```
-
-3. **管理员页面** (`user_management.html`, `log_management.html`) 读取token:
- ```javascript
- const token = localStorage.getItem('token'); // ❌ 错误的key
- ```
-
-## ✅ 修复方案
-
-### 统一Token存储Key
-
-将所有管理员页面的token读取统一为 `auth_token`:
-
-#### 1. 用户管理页面修复
-```javascript
-// 修复前
-const token = localStorage.getItem('token');
-
-// 修复后
-const token = localStorage.getItem('auth_token');
-```
-
-修复的函数:
-- `checkAdminPermission()`
-- `loadSystemStats()`
-- `loadUsers()`
-- `confirmDeleteUser()`
-- `logout()`
-
-#### 2. 日志管理页面修复
-```javascript
-// 修复前
-const token = localStorage.getItem('token');
-
-// 修复后
-const token = localStorage.getItem('auth_token');
-```
-
-修复的函数:
-- `checkAdminPermission()`
-- `loadLogs()`
-- `logout()`
-
-### 🔄 修复的文件
-
-1. **static/user_management.html**
- - 5处token读取修复
- - 1处token删除修复
-
-2. **static/log_management.html**
- - 3处token读取修复
- - 1处token删除修复
-
-## 📊 Token流程图
-
-```
-登录页面 (login.html)
- ↓ 设置
-localStorage.setItem('auth_token', token)
- ↓ 读取
-主页面 (index.html)
- ↓ 读取
-管理员页面 (user_management.html, log_management.html)
- ↓ 使用
-API调用 (Authorization: Bearer token)
-```
-
-## 🧪 验证方法
-
-### 1. 手动验证
-1. 使用admin账号登录主页
-2. 点击侧边栏"用户管理"
-3. 页面应该正常加载用户列表和统计信息
-4. 点击侧边栏"系统日志"
-5. 页面应该正常显示系统日志
-
-### 2. 开发者工具验证
-1. 打开浏览器开发者工具
-2. 查看 Application → Local Storage
-3. 确认存在 `auth_token` 项
-4. 查看 Network 标签页
-5. API请求应该返回200状态码
-
-### 3. 日志验证
-服务器日志应该显示:
-```
-INFO: 【admin#1】 API请求: GET /admin/users
-INFO: 【admin#1】 API响应: GET /admin/users - 200 (0.005s)
-```
-
-## 🎯 修复效果
-
-### 修复前
-- ❌ 管理员页面API调用401错误
-- ❌ 日志显示"未登录"用户访问
-- ❌ 用户管理功能无法使用
-- ❌ 日志管理功能无法使用
-
-### 修复后
-- ✅ 管理员页面API调用正常
-- ✅ 日志显示正确的用户信息
-- ✅ 用户管理功能完全可用
-- ✅ 日志管理功能完全可用
-
-## 🔒 安全验证
-
-修复后的安全机制:
-
-1. **Token验证**:所有管理员API都需要有效token
-2. **权限检查**:只有admin用户可以访问管理员功能
-3. **自动跳转**:无效token自动跳转到登录页
-4. **统一认证**:所有页面使用相同的认证机制
-
-## 💡 最佳实践
-
-### 1. Token管理规范
-- 使用统一的token存储key
-- 在所有页面保持一致的token读取方式
-- 及时清理过期或无效的token
-
-### 2. 错误处理
-- API调用失败时提供明确的错误信息
-- 401错误自动跳转到登录页
-- 403错误提示权限不足
-
-### 3. 用户体验
-- 登录状态持久化
-- 页面间无缝跳转
-- 清晰的权限提示
-
-## 🚀 部署说明
-
-### 立即生效
-修复后无需重启服务器,刷新页面即可生效。
-
-### 用户操作
-1. 如果当前已登录,刷新管理员页面即可
-2. 如果遇到问题,重新登录即可
-3. 确保使用admin账号访问管理员功能
-
-## 📋 测试清单
-
-- [ ] admin用户可以正常登录
-- [ ] 主页侧边栏显示管理员菜单
-- [ ] 用户管理页面正常加载
-- [ ] 用户管理功能正常工作
-- [ ] 日志管理页面正常加载
-- [ ] 日志管理功能正常工作
-- [ ] 非admin用户无法访问管理员功能
-- [ ] 无效token被正确拒绝
-
-## 🎉 总结
-
-通过统一token存储key,成功修复了管理员页面的认证问题:
-
-### 核心改进
-- **统一认证**:所有页面使用相同的token key (`auth_token`)
-- **完整功能**:用户管理和日志管理功能完全可用
-- **安全保障**:权限验证和错误处理机制完善
-
-### 用户体验
-- **无缝使用**:登录后可以直接使用所有管理员功能
-- **清晰反馈**:错误信息明确,操作结果及时反馈
-- **安全可靠**:严格的权限控制和认证机制
-
-现在管理员功能已经完全正常工作,可以安全地管理用户和监控系统日志!🎊
diff --git a/UI_IMPROVEMENTS.md b/UI_IMPROVEMENTS.md
deleted file mode 100644
index 1ac59fc..0000000
--- a/UI_IMPROVEMENTS.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# 🎨 界面优化总结
-
-## ✨ 主要改进
-
-### 1. 🎯 Cookie显示优化
-- **❌ 修改前**: Cookie值被隐藏为星号 (`****`)
-- **✅ 修改后**: 显示完整的Cookie内容,便于查看和调试
-
-### 2. 🎨 视觉设计升级
-- **现代化配色方案**: 使用更现代的紫色主题 (`#4f46e5`)
-- **渐变背景**: 美丽的渐变背景和卡片效果
-- **毛玻璃效果**: 卡片使用 `backdrop-filter: blur()` 实现毛玻璃效果
-- **阴影和动画**: 悬停时的阴影和位移动画效果
-
-### 3. 🔧 功能增强
-- **一键复制Cookie**: 点击Cookie值或复制按钮即可复制到剪贴板
-- **改进的按钮组**: 更紧凑的按钮布局,包含复制功能
-- **更好的空状态**: 当没有账号时显示更友好的提示
-
-### 4. 📱 响应式设计
-- **移动端优化**: 在小屏幕上按钮垂直排列
-- **自适应布局**: 表格和卡片在不同屏幕尺寸下的自适应
-
-## 🛠️ 技术改进
-
-### 新增API接口
-```javascript
-GET /cookies/details
-```
-返回包含Cookie ID和完整值的详细信息,而不仅仅是ID列表。
-
-### CSS样式优化
-- **CSS变量**: 统一的颜色管理
-- **现代字体**: 使用 Inter 字体提升可读性
-- **代码字体**: Cookie值使用等宽字体 (JetBrains Mono)
-- **流畅动画**: 所有交互都有平滑的过渡效果
-
-### JavaScript功能增强
-- **复制功能**: 支持现代浏览器的 Clipboard API
-- **降级方案**: 对于不支持的浏览器提供传统复制方法
-- **用户反馈**: 复制成功/失败的Toast提示
-
-## 🎯 用户体验提升
-
-### 1. Cookie管理
-- **完整显示**: 不再隐藏Cookie内容,便于调试
-- **一键复制**: 快速复制Cookie值到剪贴板
-- **格式化显示**: 使用等宽字体和适当的行高
-
-### 2. 视觉反馈
-- **悬停效果**: 所有可交互元素都有悬停反馈
-- **状态指示**: 清晰的按钮状态和颜色区分
-- **加载动画**: 优雅的加载状态显示
-
-### 3. 操作便利性
-- **按钮分组**: 相关操作按钮紧凑排列
-- **图标提示**: 每个按钮都有清晰的图标和提示
-- **确认对话框**: 危险操作有确认提示
-
-## 📊 界面对比
-
-| 功能 | 修改前 | 修改后 |
-|------|--------|--------|
-| Cookie显示 | 隐藏为星号 | 完整显示 |
-| 复制功能 | 无 | 一键复制 |
-| 视觉效果 | 基础Bootstrap | 现代化渐变设计 |
-| 响应式 | 基本支持 | 完全优化 |
-| 用户反馈 | 基础提示 | 丰富的Toast反馈 |
-
-## 🚀 使用说明
-
-### 访问界面
-1. 启动系统: `python Start.py`
-2. 打开浏览器: `http://localhost:8080`
-3. 登录: `admin` / `admin123`
-
-### 主要功能
-- **添加账号**: 在顶部表单中输入账号ID和Cookie值
-- **查看Cookie**: 完整的Cookie值显示在表格中
-- **复制Cookie**: 点击Cookie值或复制按钮
-- **管理关键词**: 点击关键词按钮设置自动回复
-- **删除账号**: 点击删除按钮(有确认提示)
-
-## 🎉 总结
-
-这次界面优化大幅提升了用户体验:
-- ✅ **Cookie不再隐藏**,便于查看和调试
-- ✅ **现代化设计**,视觉效果更佳
-- ✅ **功能增强**,操作更便利
-- ✅ **响应式优化**,支持各种设备
-
-界面现在更加美观、实用和用户友好!🎨✨
diff --git a/USER_LOGGING_IMPROVEMENT.md b/USER_LOGGING_IMPROVEMENT.md
deleted file mode 100644
index 26baac1..0000000
--- a/USER_LOGGING_IMPROVEMENT.md
+++ /dev/null
@@ -1,249 +0,0 @@
-# 用户日志显示改进总结
-
-## 🎯 改进目标
-
-在多用户系统中,原有的日志无法识别具体的操作用户,导致调试和监控困难。本次改进为所有系统日志添加了当前登录用户的信息。
-
-## 📊 改进内容
-
-### 1. API请求/响应日志增强
-
-#### 改进前
-```
-2025-07-25 15:40:28.714 | INFO | reply_server:log_requests:223 - 🌐 API请求: GET /keywords/执念小店70
-2025-07-25 15:40:28.725 | INFO | reply_server:log_requests:228 - ✅ API响应: GET /keywords/执念小店70 - 200 (0.011s)
-```
-
-#### 改进后
-```
-2025-07-25 15:40:28.714 | INFO | reply_server:log_requests:223 - 🌐 【admin#1】 API请求: GET /keywords/执念小店70
-2025-07-25 15:40:28.725 | INFO | reply_server:log_requests:228 - ✅ 【admin#1】 API响应: GET /keywords/执念小店70 - 200 (0.011s)
-```
-
-### 2. 业务操作日志增强
-
-#### 用户认证相关
-- ✅ 登录尝试: `【username】尝试登录`
-- ✅ 登录成功: `【username#user_id】登录成功`
-- ✅ 登录失败: `【username】登录失败: 用户名或密码错误`
-- ✅ 注册操作: `【username】尝试注册,邮箱: email`
-
-#### Cookie管理相关
-- ✅ 添加Cookie: `【username#user_id】尝试添加Cookie: cookie_id`
-- ✅ 操作成功: `【username#user_id】Cookie添加成功: cookie_id`
-- ✅ 权限冲突: `【username#user_id】Cookie ID冲突: cookie_id 已被其他用户使用`
-
-#### 卡券管理相关
-- ✅ 创建卡券: `【username#user_id】创建卡券: card_name`
-- ✅ 创建成功: `【username#user_id】卡券创建成功: card_name (ID: card_id)`
-- ✅ 创建失败: `【username#user_id】创建卡券失败: card_name - error`
-
-#### 关键字管理相关
-- ✅ 更新关键字: `【username#user_id】更新Cookie关键字: cookie_id, 数量: count`
-- ✅ 权限验证: `【username#user_id】尝试操作其他用户的Cookie关键字: cookie_id`
-
-#### 用户设置相关
-- ✅ 设置更新: `【username#user_id】更新用户设置: key = value`
-- ✅ 更新成功: `【username#user_id】用户设置更新成功: key`
-
-## 🔧 技术实现
-
-### 1. 中间件增强
-```python
-@app.middleware("http")
-async def log_requests(request, call_next):
- # 获取用户信息
- user_info = "未登录"
- try:
- auth_header = request.headers.get("Authorization")
- if auth_header and auth_header.startswith("Bearer "):
- token = auth_header.split(" ")[1]
- if token in SESSION_TOKENS:
- token_data = SESSION_TOKENS[token]
- if time.time() - token_data['timestamp'] <= TOKEN_EXPIRE_TIME:
- user_info = f"【{token_data['username']}#{token_data['user_id']}】"
- except Exception:
- pass
-
- logger.info(f"🌐 {user_info} API请求: {request.method} {request.url.path}")
- # ...
-```
-
-### 2. 统一日志工具函数
-```python
-def get_user_log_prefix(user_info: Dict[str, Any] = None) -> str:
- """获取用户日志前缀"""
- if user_info:
- return f"【{user_info['username']}#{user_info['user_id']}】"
- return "【系统】"
-
-def log_with_user(level: str, message: str, user_info: Dict[str, Any] = None):
- """带用户信息的日志记录"""
- prefix = get_user_log_prefix(user_info)
- full_message = f"{prefix} {message}"
-
- if level.lower() == 'info':
- logger.info(full_message)
- elif level.lower() == 'error':
- logger.error(full_message)
- # ...
-```
-
-### 3. 业务接口改进
-```python
-@app.post("/cookies")
-def add_cookie(item: CookieIn, current_user: Dict[str, Any] = Depends(get_current_user)):
- try:
- log_with_user('info', f"尝试添加Cookie: {item.id}", current_user)
- # 业务逻辑...
- log_with_user('info', f"Cookie添加成功: {item.id}", current_user)
- except Exception as e:
- log_with_user('error', f"添加Cookie失败: {item.id} - {str(e)}", current_user)
-```
-
-## 📋 修改的文件和接口
-
-### reply_server.py
-- **中间件**: `log_requests` - API请求/响应日志
-- **工具函数**: `get_user_log_prefix`, `log_with_user`
-- **认证接口**: 登录、注册接口
-- **业务接口**: Cookie管理、卡券管理、关键字管理、用户设置
-
-### 修改的接口数量
-- **API中间件**: 1个(影响所有接口)
-- **认证相关**: 2个接口(登录、注册)
-- **Cookie管理**: 1个接口(添加Cookie)
-- **卡券管理**: 1个接口(创建卡券)
-- **关键字管理**: 1个接口(更新关键字)
-- **用户设置**: 1个接口(更新设置)
-
-## 🎯 日志格式规范
-
-### 用户标识格式
-- **已登录用户**: `【username#user_id】`
-- **未登录用户**: `【未登录】`
-- **系统操作**: `【系统】`
-
-### 日志级别使用
-- **INFO**: 正常操作、成功操作
-- **WARNING**: 权限验证失败、业务规则冲突
-- **ERROR**: 系统错误、操作失败
-
-### 消息格式
-- **操作尝试**: `尝试{操作}: {对象}`
-- **操作成功**: `{操作}成功: {对象}`
-- **操作失败**: `{操作}失败: {对象} - {原因}`
-
-## 💡 日志分析技巧
-
-### 1. 按用户过滤
-```bash
-# 查看特定用户的所有操作
-grep '【admin#1】' logs/xianyu_2025-07-25.log
-
-# 查看特定用户的API请求
-grep '【admin#1】.*API请求' logs/xianyu_2025-07-25.log
-```
-
-### 2. 监控用户活动
-```bash
-# 统计用户活跃度
-grep -o '【[^】]*#[^】]*】' logs/xianyu_2025-07-25.log | sort | uniq -c
-
-# 查看登录活动
-grep '登录' logs/xianyu_2025-07-25.log
-```
-
-### 3. 权限验证监控
-```bash
-# 查看权限验证失败
-grep '无权限\|权限验证失败' logs/xianyu_2025-07-25.log
-
-# 查看跨用户访问尝试
-grep '尝试操作其他用户' logs/xianyu_2025-07-25.log
-```
-
-### 4. 错误追踪
-```bash
-# 查看特定用户的错误
-grep 'ERROR.*【admin#1】' logs/xianyu_2025-07-25.log
-
-# 查看操作失败
-grep '失败.*【.*】' logs/xianyu_2025-07-25.log
-```
-
-## 🔍 监控指标建议
-
-### 1. 用户活跃度指标
-- 每个用户的API调用频率
-- 用户登录频率和时长
-- 用户操作成功率
-
-### 2. 安全监控指标
-- 登录失败次数(按用户)
-- 权限验证失败次数
-- 跨用户访问尝试次数
-
-### 3. 业务监控指标
-- Cookie操作频率(按用户)
-- 卡券创建和使用情况
-- 用户设置修改频率
-
-## 🚀 部署和使用
-
-### 1. 立即生效
-重启服务后,新的日志格式立即生效:
-```bash
-# 重启服务
-docker-compose restart
-
-# 查看新的日志格式
-docker-compose logs -f | grep '【.*】'
-```
-
-### 2. 日志轮转配置
-确保日志轮转能够处理增加的日志内容:
-```python
-# loguru配置
-logger.add(
- "logs/xianyu_{time:YYYY-MM-DD}.log",
- rotation="100 MB",
- retention="7 days",
- compression="zip",
- format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}"
-)
-```
-
-### 3. 监控工具集成
-如果使用ELK、Grafana等监控工具,可以基于用户标识创建仪表板:
-- 按用户分组的操作统计
-- 用户行为分析
-- 安全事件监控
-
-## 🎉 改进效果
-
-### 1. 调试效率提升
-- **问题定位**: 快速定位特定用户的问题
-- **操作追踪**: 完整的用户操作链路追踪
-- **权限验证**: 清晰的权限验证日志
-
-### 2. 监控能力增强
-- **用户行为**: 详细的用户行为分析
-- **安全监控**: 实时的安全事件监控
-- **性能分析**: 按用户维度的性能分析
-
-### 3. 运维管理优化
-- **故障排查**: 快速定位用户相关问题
-- **容量规划**: 基于用户活跃度的容量规划
-- **安全审计**: 完整的用户操作审计日志
-
-## 📞 使用建议
-
-1. **日志查看**: 使用 `grep` 命令按用户过滤日志
-2. **实时监控**: 使用 `tail -f` 实时监控特定用户操作
-3. **定期分析**: 定期分析用户活跃度和操作模式
-4. **安全审计**: 定期检查权限验证失败和异常操作
-
----
-
-**总结**: 通过本次改进,多用户系统的日志现在具备了完整的用户标识能力,大大提升了系统的可观测性和可维护性!
diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py
index 6f62b45..cb03407 100644
--- a/XianyuAutoAsync.py
+++ b/XianyuAutoAsync.py
@@ -86,6 +86,10 @@ class XianyuLive:
# 通知防重复机制
self.last_notification_time = {} # 记录每种通知类型的最后发送时间
self.notification_cooldown = 300 # 5分钟内不重复发送相同类型的通知
+
+ # 自动发货防重复机制
+ self.last_delivery_time = {} # 记录每个商品的最后发货时间
+ self.delivery_cooldown = 60 # 1分钟内不重复发货
# 人工接管功能已禁用,永远走自动模式
# self.manual_mode_conversations = set() # 存储处于人工接管模式的会话ID
@@ -94,6 +98,22 @@ class XianyuLive:
# self.toggle_keywords = MANUAL_MODE.get('toggle_keywords', ['。']) # 切换关键词
self.session = None # 用于API调用的aiohttp session
+ def can_auto_delivery(self, item_id: str) -> bool:
+ """检查是否可以进行自动发货(防重复发货)"""
+ current_time = time.time()
+ last_delivery = self.last_delivery_time.get(item_id, 0)
+
+ if current_time - last_delivery < self.delivery_cooldown:
+ logger.info(f"【{self.cookie_id}】商品 {item_id} 在冷却期内,跳过自动发货")
+ return False
+
+ return True
+
+ def mark_delivery_sent(self, item_id: str):
+ """标记商品已发货"""
+ self.last_delivery_time[item_id] = time.time()
+ logger.debug(f"【{self.cookie_id}】标记商品 {item_id} 已发货")
+
# 人工接管功能已禁用,以下方法不再使用
# def check_toggle_keywords(self, message):
# """检查消息是否包含切换关键词"""
@@ -1147,10 +1167,13 @@ class XianyuLive:
delivery_content = db_manager.consume_batch_data(rule['card_id'])
if delivery_content:
+ # 处理备注信息和变量替换
+ final_content = self._process_delivery_content_with_description(delivery_content, rule.get('card_description', ''))
+
# 增加发货次数统计
db_manager.increment_delivery_times(rule['id'])
- logger.info(f"自动发货成功: 规则ID={rule['id']}, 内容长度={len(delivery_content)}")
- return delivery_content
+ logger.info(f"自动发货成功: 规则ID={rule['id']}, 内容长度={len(final_content)}")
+ return final_content
else:
logger.warning(f"获取发货内容失败: 规则ID={rule['id']}")
return None
@@ -1159,6 +1182,28 @@ class XianyuLive:
logger.error(f"自动发货失败: {self._safe_str(e)}")
return None
+ def _process_delivery_content_with_description(self, delivery_content: str, card_description: str) -> str:
+ """处理发货内容和备注信息,实现变量替换"""
+ try:
+ # 如果没有备注信息,直接返回发货内容
+ if not card_description or not card_description.strip():
+ return delivery_content
+
+ # 替换备注中的变量
+ processed_description = card_description.replace('{DELIVERY_CONTENT}', delivery_content)
+
+ # 如果备注中包含变量替换,返回处理后的备注
+ if '{DELIVERY_CONTENT}' in card_description:
+ return processed_description
+ else:
+ # 如果备注中没有变量,将备注和发货内容组合
+ return f"{processed_description}\n\n{delivery_content}"
+
+ except Exception as e:
+ logger.error(f"处理备注信息失败: {e}")
+ # 出错时返回原始发货内容
+ return delivery_content
+
async def _get_api_card_content(self, rule, retry_count=0):
"""调用API获取卡券内容,支持重试机制"""
max_retries = 3
@@ -1855,9 +1900,28 @@ class XianyuLive:
elif send_message == '[你关闭了订单,钱款已原路退返]':
logger.info(f'[{msg_time}] 【{self.cookie_id}】系统消息不处理')
return
+ elif send_message == '发来一条消息':
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】系统通知消息不处理')
+ return
+ elif send_message == '发来一条新消息':
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】系统通知消息不处理')
+ return
+ elif send_message == '[买家确认收货,交易成功]':
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】交易完成消息不处理')
+ return
+ elif send_message == '快给ta一个评价吧~' or send_message == '快给ta一个评价吧~':
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】评价提醒消息不处理')
+ return
+ elif send_message == '[你已发货]':
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】发货确认消息不处理')
+ return
elif send_message == '[我已付款,等待你发货]':
logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】买家已付款,准备自动发货')
+ # 检查是否可以进行自动发货(防重复)
+ if not self.can_auto_delivery(item_id):
+ return
+
# 构造用户URL
user_url = f'https://www.goofish.com/personal?userId={send_user_id}'
@@ -1872,6 +1936,9 @@ class XianyuLive:
delivery_content = await self._auto_delivery(item_id, item_title)
if delivery_content:
+ # 标记已发货(防重复)
+ self.mark_delivery_sent(item_id)
+
# 发送发货内容给买家
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
@@ -1890,6 +1957,10 @@ class XianyuLive:
elif send_message == '[已付款,待发货]':
logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】买家已付款,准备自动发货')
+ # 检查是否可以进行自动发货(防重复)
+ if not self.can_auto_delivery(item_id):
+ return
+
# 构造用户URL
user_url = f'https://www.goofish.com/personal?userId={send_user_id}'
@@ -1904,6 +1975,9 @@ class XianyuLive:
delivery_content = await self._auto_delivery(item_id, item_title)
if delivery_content:
+ # 标记已发货(防重复)
+ self.mark_delivery_sent(item_id)
+
# 发送发货内容给买家
await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
@@ -1919,6 +1993,76 @@ class XianyuLive:
await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, f"自动发货处理异常: {str(e)}")
return
+ elif send_message == '[卡片消息]':
+ # 检查是否为"我已小刀,待刀成"的卡片消息
+ try:
+ # 从消息中提取卡片内容
+ card_title = None
+ if isinstance(message, dict) and "1" in message and isinstance(message["1"], dict):
+ message_1 = message["1"]
+ if "6" in message_1 and isinstance(message_1["6"], dict):
+ message_6 = message_1["6"]
+ if "3" in message_6 and isinstance(message_6["3"], dict):
+ message_6_3 = message_6["3"]
+ if "5" in message_6_3:
+ # 解析JSON内容
+ try:
+ card_content = json.loads(message_6_3["5"])
+ if "dxCard" in card_content and "item" in card_content["dxCard"]:
+ card_item = card_content["dxCard"]["item"]
+ if "main" in card_item and "exContent" in card_item["main"]:
+ ex_content = card_item["main"]["exContent"]
+ card_title = ex_content.get("title", "")
+ except (json.JSONDecodeError, KeyError) as e:
+ logger.debug(f"解析卡片消息失败: {e}")
+
+ # 检查是否为"我已小刀,待刀成"
+ if card_title == "我已小刀,待刀成":
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】检测到"我已小刀,待刀成",准备自动发货')
+
+ # 检查是否可以进行自动发货(防重复)
+ if not self.can_auto_delivery(item_id):
+ return
+
+ # 构造用户URL
+ user_url = f'https://www.goofish.com/personal?userId={send_user_id}'
+
+ # 自动发货逻辑
+ try:
+ # 设置默认标题(将通过API获取真实商品信息)
+ item_title = "待获取商品信息"
+
+ logger.info(f"【{self.cookie_id}】准备自动发货: item_id={item_id}, item_title={item_title}")
+
+ # 调用自动发货方法
+ delivery_content = await self._auto_delivery(item_id, item_title)
+
+ if delivery_content:
+ # 标记已发货(防重复)
+ self.mark_delivery_sent(item_id)
+
+ # 发送发货内容给买家
+ await self.send_msg(websocket, chat_id, send_user_id, delivery_content)
+ logger.info(f'[{msg_time}] 【自动发货】已向 {user_url} 发送发货内容')
+ await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "发货成功")
+ else:
+ logger.warning(f'[{msg_time}] 【自动发货】未找到匹配的发货规则或获取发货内容失败')
+ # 发送自动发货失败通知
+ await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, "未找到匹配的发货规则或获取发货内容失败")
+
+ except Exception as e:
+ logger.error(f"自动发货处理异常: {self._safe_str(e)}")
+ # 发送自动发货异常通知
+ await self.send_delivery_failure_notification(send_user_name, send_user_id, item_id, f"自动发货处理异常: {str(e)}")
+
+ return
+ else:
+ logger.info(f'[{msg_time}] 【{self.cookie_id}】收到卡片消息,标题: {card_title or "未知"}')
+
+ except Exception as e:
+ logger.error(f"处理卡片消息异常: {self._safe_str(e)}")
+
+ # 如果不是目标卡片消息,继续正常处理流程
# 记录回复来源
reply_source = 'API' # 默认假设是API回复
diff --git a/backup_import_update_summary.md b/backup_import_update_summary.md
deleted file mode 100644
index 710ae24..0000000
--- a/backup_import_update_summary.md
+++ /dev/null
@@ -1,193 +0,0 @@
-# 备份和导入功能更新总结
-
-## 📋 更新概述
-
-由于系统新增了多个AI相关的数据表,备份和导入功能需要更新以确保所有数据都能正确备份和恢复。
-
-## 🔧 更新内容
-
-### 1. **新增的表**
-
-在原有的10个表基础上,新增了3个AI相关表:
-
-#### 新增表列表:
-- `ai_reply_settings` - AI回复配置表
-- `ai_conversations` - AI对话历史表
-- `ai_item_cache` - AI商品信息缓存表
-
-### 2. **更新的备份表列表**
-
-#### 更新前(10个表):
-```python
-tables = [
- 'cookies', 'keywords', 'cookie_status', 'cards',
- 'delivery_rules', 'default_replies', 'notification_channels',
- 'message_notifications', 'system_settings', 'item_info'
-]
-```
-
-#### 更新后(13个表):
-```python
-tables = [
- 'cookies', 'keywords', 'cookie_status', 'cards',
- 'delivery_rules', 'default_replies', 'notification_channels',
- 'message_notifications', 'system_settings', 'item_info',
- 'ai_reply_settings', 'ai_conversations', 'ai_item_cache'
-]
-```
-
-### 3. **更新的删除顺序**
-
-考虑到外键依赖关系,更新了导入时的表删除顺序:
-
-#### 更新前:
-```python
-tables = [
- 'message_notifications', 'notification_channels', 'default_replies',
- 'delivery_rules', 'cards', 'item_info', 'cookie_status', 'keywords', 'cookies'
-]
-```
-
-#### 更新后:
-```python
-tables = [
- 'message_notifications', 'notification_channels', 'default_replies',
- 'delivery_rules', 'cards', 'item_info', 'cookie_status', 'keywords',
- 'ai_conversations', 'ai_reply_settings', 'ai_item_cache', 'cookies'
-]
-```
-
-### 4. **更新的验证列表**
-
-导入功能中的表验证列表也相应更新,确保新增的AI表能够正确导入。
-
-## ✅ 测试验证结果
-
-### 测试环境
-- 数据库表总数:13个
-- 测试数据:包含所有表类型的数据
-- 测试场景:导出、导入、文件操作
-
-### 测试结果
-```
-🎉 所有测试通过!备份和导入功能正常!
-
-✅ 功能验证:
- • 所有13个表都包含在备份中
- • 备份导出功能正常
- • 备份导入功能正常
- • 数据完整性保持
- • 文件操作正常
-```
-
-### 数据完整性验证
-所有13个表的数据在备份和导入过程中保持完整:
-
-| 表名 | 数据一致性 | 说明 |
-|------|------------|------|
-| ai_conversations | ✅ 一致 | AI对话历史 |
-| ai_item_cache | ✅ 一致 | AI商品缓存 |
-| ai_reply_settings | ✅ 一致 | AI回复配置 |
-| cards | ✅ 一致 | 卡券信息 |
-| cookie_status | ✅ 一致 | 账号状态 |
-| cookies | ✅ 一致 | 账号信息 |
-| default_replies | ✅ 一致 | 默认回复 |
-| delivery_rules | ✅ 一致 | 发货规则 |
-| item_info | ✅ 一致 | 商品信息 |
-| keywords | ✅ 一致 | 关键词 |
-| message_notifications | ✅ 一致 | 消息通知 |
-| notification_channels | ✅ 一致 | 通知渠道 |
-| system_settings | ✅ 一致 | 系统设置 |
-
-## 🎯 功能特性
-
-### 1. **完整性保证**
-- 包含所有13个数据表
-- 保持外键依赖关系
-- 数据完整性验证
-
-### 2. **安全性**
-- 事务性操作,确保数据一致性
-- 管理员密码保护(不会被覆盖)
-- 错误回滚机制
-
-### 3. **兼容性**
-- 向后兼容旧版本备份文件
-- 自动跳过不存在的表
-- 版本标识和时间戳
-
-### 4. **易用性**
-- 一键导出所有数据
-- 一键导入恢复数据
-- 详细的操作日志
-
-## 📊 使用方法
-
-### 导出备份
-```python
-from db_manager import db_manager
-
-# 导出备份数据
-backup_data = db_manager.export_backup()
-
-# 保存到文件
-import json
-with open('backup.json', 'w', encoding='utf-8') as f:
- json.dump(backup_data, f, indent=2, ensure_ascii=False)
-```
-
-### 导入备份
-```python
-from db_manager import db_manager
-import json
-
-# 从文件读取备份
-with open('backup.json', 'r', encoding='utf-8') as f:
- backup_data = json.load(f)
-
-# 导入备份数据
-success = db_manager.import_backup(backup_data)
-```
-
-## 🔄 升级说明
-
-### 对现有用户的影响
-- ✅ **无影响**:现有备份文件仍然可以正常导入
-- ✅ **自动兼容**:系统会自动跳过不存在的新表
-- ✅ **数据安全**:不会丢失任何现有数据
-
-### 新功能优势
-- ✅ **AI数据备份**:AI回复配置和对话历史得到保护
-- ✅ **完整备份**:所有功能数据都包含在备份中
-- ✅ **快速恢复**:可以完整恢复所有AI功能设置
-
-## 💡 建议
-
-### 1. **定期备份**
-建议用户定期进行数据备份,特别是在:
-- 重要配置更改后
-- 系统升级前
-- 大量数据操作前
-
-### 2. **备份验证**
-建议在重要操作前验证备份文件的完整性:
-- 检查文件大小
-- 验证JSON格式
-- 确认包含所需表
-
-### 3. **多重备份**
-建议保留多个备份版本:
-- 每日自动备份
-- 重要节点手动备份
-- 异地备份存储
-
-## 🎉 总结
-
-备份和导入功能已成功更新,现在能够:
-
-1. ✅ **完整备份**所有13个数据表
-2. ✅ **安全导入**保持数据完整性
-3. ✅ **向后兼容**旧版本备份文件
-4. ✅ **AI数据保护**包含所有AI功能数据
-
-用户可以放心使用备份和导入功能,所有数据都得到了完整的保护!
diff --git a/bargain_demo.py b/bargain_demo.py
deleted file mode 100644
index b2c0a36..0000000
--- a/bargain_demo.py
+++ /dev/null
@@ -1,180 +0,0 @@
-#!/usr/bin/env python3
-"""
-议价功能演示脚本
-展示AI回复的议价轮数限制功能
-"""
-
-import asyncio
-import sys
-import os
-
-# 添加项目根目录到Python路径
-sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
-
-from ai_reply_engine import ai_reply_engine
-from db_manager import db_manager
-
-def simulate_bargain_conversation():
- """模拟议价对话流程"""
- print("🎭 模拟议价对话流程")
- print("=" * 50)
-
- # 测试参数
- cookie_id = "demo_account_001"
- chat_id = "demo_chat_001"
- user_id = "customer_001"
- item_id = "item_12345"
- user_name = "小明"
-
- # 商品信息
- item_info = {
- 'title': 'iPhone 14 Pro 256GB 深空黑色',
- 'price': 8999,
- 'desc': '全新未拆封,国行正品,支持全国联保'
- }
-
- # 设置AI回复配置(模拟)
- ai_settings = {
- 'ai_enabled': True,
- 'model_name': 'qwen-plus',
- 'api_key': 'demo-key',
- 'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
- 'max_discount_percent': 10, # 最大优惠10%
- 'max_discount_amount': 500, # 最大优惠500元
- 'max_bargain_rounds': 3, # 最大议价3轮
- 'custom_prompts': ''
- }
-
- # 保存配置
- db_manager.save_ai_reply_settings(cookie_id, ai_settings)
-
- # 清理之前的对话
- try:
- with db_manager.lock:
- cursor = db_manager.conn.cursor()
- cursor.execute('DELETE FROM ai_conversations WHERE cookie_id = ? AND chat_id = ?',
- (cookie_id, chat_id))
- db_manager.conn.commit()
- except:
- pass
-
- print(f"📱 商品信息:")
- print(f" 标题: {item_info['title']}")
- print(f" 价格: ¥{item_info['price']}")
- print(f" 描述: {item_info['desc']}")
-
- print(f"\n⚙️ 议价设置:")
- print(f" 最大议价轮数: {ai_settings['max_bargain_rounds']}")
- print(f" 最大优惠百分比: {ai_settings['max_discount_percent']}%")
- print(f" 最大优惠金额: ¥{ai_settings['max_discount_amount']}")
-
- # 模拟议价对话
- bargain_messages = [
- "你好,这个iPhone能便宜点吗?",
- "8500能卖吗?",
- "8800行不行?最后一次了",
- "8700,真的不能再少了"
- ]
-
- print(f"\n💬 议价对话模拟:")
- print("-" * 30)
-
- for i, message in enumerate(bargain_messages, 1):
- print(f"\n第{i}轮议价:")
- print(f"👤 {user_name}: {message}")
-
- # 检查当前议价次数
- current_count = ai_reply_engine.get_bargain_count(chat_id, cookie_id)
- max_rounds = ai_settings['max_bargain_rounds']
-
- print(f"📊 当前议价次数: {current_count}/{max_rounds}")
-
- # 模拟意图检测为price
- intent = "price"
-
- # 检查是否超出限制
- if current_count >= max_rounds:
- print(f"🚫 已达到最大议价轮数限制!")
- refuse_reply = "抱歉,这个价格已经是最优惠的了,不能再便宜了哦!"
- print(f"🤖 AI回复: {refuse_reply}")
-
- # 保存对话记录
- ai_reply_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "user", message, intent)
- ai_reply_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "assistant", refuse_reply, intent)
-
- print(f"✋ 议价结束,系统拒绝继续议价")
- break
- else:
- # 模拟AI回复(因为没有真实API密钥,这里手动模拟)
- if i == 1:
- ai_reply = "您好!这个价格已经很优惠了,最多可以优惠200元,8799元怎么样?"
- elif i == 2:
- ai_reply = "8500太低了,我们再让一点,8699元,这已经是很大的优惠了!"
- elif i == 3:
- ai_reply = "好的,看您很有诚意,8799元成交,这真的是最低价了!"
- else:
- ai_reply = "抱歉,这个价格已经是最优惠的了!"
-
- print(f"🤖 AI回复: {ai_reply}")
-
- # 保存对话记录
- ai_reply_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "user", message, intent)
- ai_reply_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "assistant", ai_reply, intent)
-
- # 显示最终统计
- print(f"\n📈 最终统计:")
- final_count = ai_reply_engine.get_bargain_count(chat_id, cookie_id)
- print(f" 总议价轮数: {final_count}")
- print(f" 最大允许轮数: {max_rounds}")
- print(f" 是否达到限制: {'是' if final_count >= max_rounds else '否'}")
-
-def show_bargain_features():
- """展示议价功能特性"""
- print(f"\n🎯 议价功能特性说明:")
- print("=" * 50)
-
- features = [
- "✅ 智能议价轮数统计:自动统计用户的议价次数",
- "✅ 灵活轮数限制:可配置最大议价轮数(1-10轮)",
- "✅ 优惠金额控制:设置最大优惠百分比和金额",
- "✅ 友好拒绝回复:超出限制时礼貌拒绝继续议价",
- "✅ 上下文感知:AI了解完整的议价历史",
- "✅ 个性化策略:根据议价轮数调整回复策略",
- "✅ 数据持久化:议价记录永久保存",
- "✅ 实时生效:配置修改后立即生效"
- ]
-
- for feature in features:
- print(f" {feature}")
-
- print(f"\n💡 使用建议:")
- suggestions = [
- "设置合理的最大议价轮数(建议3-5轮)",
- "配合最大优惠百分比和金额使用",
- "在AI提示词中强调议价策略",
- "定期分析议价数据,优化策略",
- "根据商品类型调整议价参数"
- ]
-
- for suggestion in suggestions:
- print(f" • {suggestion}")
-
-def main():
- """主函数"""
- print("🚀 AI回复议价功能演示")
-
- # 模拟议价对话
- simulate_bargain_conversation()
-
- # 展示功能特性
- show_bargain_features()
-
- print(f"\n🎉 演示完成!")
- print(f"\n📋 下一步:")
- print(f" 1. 在Web界面配置真实的AI API密钥")
- print(f" 2. 为需要的账号启用AI回复功能")
- print(f" 3. 设置合适的议价参数")
- print(f" 4. 测试实际的议价效果")
-
-if __name__ == "__main__":
- main()
diff --git a/cookie_manager.py b/cookie_manager.py
index e742e6c..a901281 100644
--- a/cookie_manager.py
+++ b/cookie_manager.py
@@ -62,12 +62,12 @@ class CookieManager:
except Exception as e:
logger.error(f"XianyuLive 任务异常({cookie_id}): {e}")
- async def _add_cookie_async(self, cookie_id: str, cookie_value: str):
+ async def _add_cookie_async(self, cookie_id: str, cookie_value: str, user_id: int = None):
if cookie_id in self.tasks:
raise ValueError("Cookie ID already exists")
self.cookies[cookie_id] = cookie_value
- # 保存到数据库
- db_manager.save_cookie(cookie_id, cookie_value)
+ # 保存到数据库,如果没有指定user_id,则保持原有绑定关系
+ db_manager.save_cookie(cookie_id, cookie_value, user_id)
task = self.loop.create_task(self._run_xianyu(cookie_id, cookie_value))
self.tasks[cookie_id] = task
logger.info(f"已启动账号任务: {cookie_id}")
@@ -83,7 +83,7 @@ class CookieManager:
logger.info(f"已移除账号: {cookie_id}")
# ------------------------ 对外线程安全接口 ------------------------
- def add_cookie(self, cookie_id: str, cookie_value: str, kw_list: Optional[List[Tuple[str, str]]] = None):
+ def add_cookie(self, cookie_id: str, cookie_value: str, kw_list: Optional[List[Tuple[str, str]]] = None, user_id: int = None):
"""线程安全新增 Cookie 并启动任务"""
if kw_list is not None:
self.keywords[cookie_id] = kw_list
@@ -96,9 +96,9 @@ class CookieManager:
if current_loop and current_loop == self.loop:
# 同一事件循环中,直接调度
- return self.loop.create_task(self._add_cookie_async(cookie_id, cookie_value))
+ return self.loop.create_task(self._add_cookie_async(cookie_id, cookie_value, user_id))
else:
- fut = asyncio.run_coroutine_threadsafe(self._add_cookie_async(cookie_id, cookie_value), self.loop)
+ fut = asyncio.run_coroutine_threadsafe(self._add_cookie_async(cookie_id, cookie_value, user_id), self.loop)
return fut.result()
def remove_cookie(self, cookie_id: str):
diff --git a/db_manager.py b/db_manager.py
index 60e8a5a..2865649 100644
--- a/db_manager.py
+++ b/db_manager.py
@@ -182,11 +182,23 @@ class DBManager:
data_content TEXT,
description TEXT,
enabled BOOLEAN DEFAULT TRUE,
+ user_id INTEGER NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users (id)
)
''')
+ # 检查并添加 user_id 列(用于数据库迁移)
+ try:
+ cursor.execute("SELECT user_id FROM cards LIMIT 1")
+ except sqlite3.OperationalError:
+ # user_id 列不存在,需要添加
+ logger.info("正在为 cards 表添加 user_id 列...")
+ cursor.execute("ALTER TABLE cards ADD COLUMN user_id INTEGER NOT NULL DEFAULT 1")
+ cursor.execute("CREATE INDEX IF NOT EXISTS idx_cards_user_id ON cards(user_id)")
+ logger.info("cards 表 user_id 列添加完成")
+
# 创建商品信息表
cursor.execute('''
CREATE TABLE IF NOT EXISTS item_info (
@@ -271,6 +283,21 @@ class DBManager:
)
''')
+ # 创建用户设置表
+ cursor.execute('''
+ CREATE TABLE IF NOT EXISTS user_settings (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ user_id INTEGER NOT NULL,
+ key TEXT NOT NULL,
+ value TEXT NOT NULL,
+ description TEXT,
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
+ UNIQUE(user_id, key)
+ )
+ ''')
+
# 插入默认系统设置
cursor.execute('''
INSERT OR IGNORE INTO system_settings (key, value, description) VALUES
@@ -301,6 +328,39 @@ class DBManager:
# user_id列存在,更新NULL值
cursor.execute("UPDATE cookies SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
+ # 为delivery_rules表添加user_id字段(如果不存在)
+ try:
+ cursor.execute("SELECT user_id FROM delivery_rules LIMIT 1")
+ except sqlite3.OperationalError:
+ # user_id列不存在,需要添加并更新历史数据
+ cursor.execute("ALTER TABLE delivery_rules ADD COLUMN user_id INTEGER")
+ cursor.execute("UPDATE delivery_rules SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
+ else:
+ # user_id列存在,更新NULL值
+ cursor.execute("UPDATE delivery_rules SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
+
+ # 为notification_channels表添加user_id字段(如果不存在)
+ try:
+ cursor.execute("SELECT user_id FROM notification_channels LIMIT 1")
+ except sqlite3.OperationalError:
+ # user_id列不存在,需要添加并更新历史数据
+ cursor.execute("ALTER TABLE notification_channels ADD COLUMN user_id INTEGER")
+ cursor.execute("UPDATE notification_channels SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
+ else:
+ # user_id列存在,更新NULL值
+ cursor.execute("UPDATE notification_channels SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
+
+ # 为email_verifications表添加type字段(如果不存在)
+ try:
+ cursor.execute("SELECT type FROM email_verifications LIMIT 1")
+ except sqlite3.OperationalError:
+ # type列不存在,需要添加并更新历史数据
+ cursor.execute("ALTER TABLE email_verifications ADD COLUMN type TEXT DEFAULT 'register'")
+ cursor.execute("UPDATE email_verifications SET type = 'register' WHERE type IS NULL")
+ else:
+ # type列存在,更新NULL值
+ cursor.execute("UPDATE email_verifications SET type = 'register' WHERE type IS NULL")
+
self.conn.commit()
logger.info(f"数据库初始化成功: {self.db_path}")
except Exception as e:
@@ -345,7 +405,15 @@ class DBManager:
(cookie_id, cookie_value, user_id)
)
self.conn.commit()
- logger.debug(f"Cookie保存成功: {cookie_id} (用户ID: {user_id})")
+ logger.info(f"Cookie保存成功: {cookie_id} (用户ID: {user_id})")
+
+ # 验证保存结果
+ cursor.execute("SELECT user_id FROM cookies WHERE id = ?", (cookie_id,))
+ saved_user_id = cursor.fetchone()
+ if saved_user_id:
+ logger.info(f"Cookie保存验证: {cookie_id} 实际绑定到用户ID: {saved_user_id[0]}")
+ else:
+ logger.error(f"Cookie保存验证失败: {cookie_id} 未找到记录")
return True
except Exception as e:
logger.error(f"Cookie保存失败: {e}")
@@ -713,15 +781,15 @@ class DBManager:
return False
# -------------------- 通知渠道操作 --------------------
- def create_notification_channel(self, name: str, channel_type: str, config: str) -> int:
+ def create_notification_channel(self, name: str, channel_type: str, config: str, user_id: int = None) -> int:
"""创建通知渠道"""
with self.lock:
try:
cursor = self.conn.cursor()
cursor.execute('''
- INSERT INTO notification_channels (name, type, config)
- VALUES (?, ?, ?)
- ''', (name, channel_type, config))
+ INSERT INTO notification_channels (name, type, config, user_id)
+ VALUES (?, ?, ?, ?)
+ ''', (name, channel_type, config, user_id))
self.conn.commit()
channel_id = cursor.lastrowid
logger.debug(f"创建通知渠道: {name} (ID: {channel_id})")
@@ -731,16 +799,24 @@ class DBManager:
self.conn.rollback()
raise
- def get_notification_channels(self) -> List[Dict[str, any]]:
+ def get_notification_channels(self, user_id: int = None) -> List[Dict[str, any]]:
"""获取所有通知渠道"""
with self.lock:
try:
cursor = self.conn.cursor()
- cursor.execute('''
- SELECT id, name, type, config, enabled, created_at, updated_at
- FROM notification_channels
- ORDER BY created_at DESC
- ''')
+ if user_id is not None:
+ cursor.execute('''
+ SELECT id, name, type, config, enabled, created_at, updated_at
+ FROM notification_channels
+ WHERE user_id = ?
+ ORDER BY created_at DESC
+ ''', (user_id,))
+ else:
+ cursor.execute('''
+ SELECT id, name, type, config, enabled, created_at, updated_at
+ FROM notification_channels
+ ORDER BY created_at DESC
+ ''')
channels = []
for row in cursor.fetchall():
@@ -1360,7 +1436,7 @@ class DBManager:
logger.error(f"验证图形验证码失败: {e}")
return False
- def save_verification_code(self, email: str, code: str, expires_minutes: int = 10) -> bool:
+ def save_verification_code(self, email: str, code: str, code_type: str = 'register', expires_minutes: int = 10) -> bool:
"""保存邮箱验证码"""
with self.lock:
try:
@@ -1368,19 +1444,19 @@ class DBManager:
expires_at = time.time() + (expires_minutes * 60)
cursor.execute('''
- INSERT INTO email_verifications (email, code, expires_at)
- VALUES (?, ?, ?)
- ''', (email, code, expires_at))
+ INSERT INTO email_verifications (email, code, type, expires_at)
+ VALUES (?, ?, ?, ?)
+ ''', (email, code, code_type, expires_at))
self.conn.commit()
- logger.info(f"保存验证码成功: {email}")
+ logger.info(f"保存验证码成功: {email} ({code_type})")
return True
except Exception as e:
logger.error(f"保存验证码失败: {e}")
self.conn.rollback()
return False
- def verify_email_code(self, email: str, code: str) -> bool:
+ def verify_email_code(self, email: str, code: str, code_type: str = 'register') -> bool:
"""验证邮箱验证码"""
with self.lock:
try:
@@ -1390,9 +1466,9 @@ class DBManager:
# 查找有效的验证码
cursor.execute('''
SELECT id FROM email_verifications
- WHERE email = ? AND code = ? AND expires_at > ? AND used = FALSE
+ WHERE email = ? AND code = ? AND type = ? AND expires_at > ? AND used = FALSE
ORDER BY created_at DESC LIMIT 1
- ''', (email, code, current_time))
+ ''', (email, code, code_type, current_time))
row = cursor.fetchone()
if row:
@@ -1401,10 +1477,10 @@ class DBManager:
UPDATE email_verifications SET used = TRUE WHERE id = ?
''', (row[0],))
self.conn.commit()
- logger.info(f"验证码验证成功: {email}")
+ logger.info(f"验证码验证成功: {email} ({code_type})")
return True
else:
- logger.warning(f"验证码验证失败: {email} - {code}")
+ logger.warning(f"验证码验证失败: {email} - {code} ({code_type})")
return False
except Exception as e:
logger.error(f"验证邮箱验证码失败: {e}")
@@ -1414,78 +1490,52 @@ class DBManager:
"""发送验证码邮件"""
try:
subject = "闲鱼自动回复系统 - 邮箱验证码"
- html_content = f"""
-
-
-
-
- 邮箱验证码
-
-
-
-
-
+ # 使用简单的纯文本邮件内容
+ text_content = f"""【闲鱼自动回复系统】邮箱验证码
-
- 您好!
- 您正在注册闲鱼自动回复系统账号,请使用以下验证码完成邮箱验证:
-
+您好!
-
+感谢您使用闲鱼自动回复系统。为了确保账户安全,请使用以下验证码完成邮箱验证:
-
- ⚠️ 重要提醒:
- • 验证码有效期为 10 分钟
- • 请勿将验证码告诉他人
- • 如果您没有进行此操作,请忽略此邮件
-
+验证码:{code}
-
- 如有任何问题,请联系系统管理员。
- 感谢您使用闲鱼自动回复系统!
-
+重要提醒:
+• 验证码有效期为 10 分钟,请及时使用
+• 请勿将验证码分享给任何人
+• 如非本人操作,请忽略此邮件
+• 系统不会主动索要您的验证码
-
-
-
-
- """
+如果您在使用过程中遇到任何问题,请联系我们的技术支持团队。
+感谢您选择闲鱼自动回复系统!
- # 调用邮件发送API
+---
+此邮件由系统自动发送,请勿直接回复
+© 2025 闲鱼自动回复系统"""
+
+ # 使用GET请求发送邮件
api_url = "https://dy.zhinianboke.com/api/emailSend"
params = {
'subject': subject,
'receiveUser': email,
- 'sendHtml': html_content
+ 'sendHtml': text_content
}
async with aiohttp.ClientSession() as session:
- async with session.get(api_url, params=params) as response:
- if response.status == 200:
- logger.info(f"验证码邮件发送成功: {email}")
- return True
- else:
- logger.error(f"验证码邮件发送失败: {email}, 状态码: {response.status}")
- return False
+ try:
+ logger.info(f"发送验证码邮件: {email}")
+ async with session.get(api_url, params=params, timeout=15) as response:
+ response_text = await response.text()
+ logger.info(f"邮件API响应: {response.status}")
+
+ if response.status == 200:
+ logger.info(f"验证码邮件发送成功: {email}")
+ return True
+ else:
+ logger.error(f"验证码邮件发送失败: {email}, 状态码: {response.status}, 响应: {response_text[:200]}")
+ return False
+ except Exception as e:
+ logger.error(f"邮件发送异常: {email}, 错误: {e}")
+ return False
except Exception as e:
logger.error(f"发送验证码邮件异常: {e}")
@@ -1688,15 +1738,15 @@ class DBManager:
# ==================== 自动发货规则方法 ====================
def create_delivery_rule(self, keyword: str, card_id: int, delivery_count: int = 1,
- enabled: bool = True, description: str = None):
+ enabled: bool = True, description: str = None, user_id: int = None):
"""创建发货规则"""
with self.lock:
try:
cursor = self.conn.cursor()
cursor.execute('''
- INSERT INTO delivery_rules (keyword, card_id, delivery_count, enabled, description)
- VALUES (?, ?, ?, ?, ?)
- ''', (keyword, card_id, delivery_count, enabled, description))
+ INSERT INTO delivery_rules (keyword, card_id, delivery_count, enabled, description, user_id)
+ VALUES (?, ?, ?, ?, ?, ?)
+ ''', (keyword, card_id, delivery_count, enabled, description, user_id))
self.conn.commit()
rule_id = cursor.lastrowid
logger.info(f"创建发货规则成功: {keyword} -> 卡券ID {card_id} (规则ID: {rule_id})")
@@ -1705,19 +1755,30 @@ class DBManager:
logger.error(f"创建发货规则失败: {e}")
raise
- def get_all_delivery_rules(self):
+ def get_all_delivery_rules(self, user_id: int = None):
"""获取所有发货规则"""
with self.lock:
try:
cursor = self.conn.cursor()
- cursor.execute('''
- SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
- dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
- c.name as card_name, c.type as card_type
- FROM delivery_rules dr
- LEFT JOIN cards c ON dr.card_id = c.id
- ORDER BY dr.created_at DESC
- ''')
+ if user_id is not None:
+ cursor.execute('''
+ SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
+ dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
+ c.name as card_name, c.type as card_type
+ FROM delivery_rules dr
+ LEFT JOIN cards c ON dr.card_id = c.id
+ WHERE dr.user_id = ?
+ ORDER BY dr.created_at DESC
+ ''', (user_id,))
+ else:
+ cursor.execute('''
+ SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
+ dr.description, dr.delivery_times, dr.created_at, dr.updated_at,
+ c.name as card_name, c.type as card_type
+ FROM delivery_rules dr
+ LEFT JOIN cards c ON dr.card_id = c.id
+ ORDER BY dr.created_at DESC
+ ''')
rules = []
for row in cursor.fetchall():
@@ -1750,7 +1811,7 @@ class DBManager:
SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled,
dr.description, dr.delivery_times,
c.name as card_name, c.type as card_type, c.api_config,
- c.text_content, c.data_content, c.enabled as card_enabled
+ c.text_content, c.data_content, c.enabled as card_enabled, c.description as card_description
FROM delivery_rules dr
LEFT JOIN cards c ON dr.card_id = c.id
WHERE dr.enabled = 1 AND c.enabled = 1
@@ -1788,7 +1849,8 @@ class DBManager:
'card_api_config': api_config,
'card_text_content': row[10],
'card_data_content': row[11],
- 'card_enabled': bool(row[12])
+ 'card_enabled': bool(row[12]),
+ 'card_description': row[13] # 卡券备注信息
})
return rules
diff --git a/demo_captcha_registration.py b/demo_captcha_registration.py
deleted file mode 100644
index dcf2c43..0000000
--- a/demo_captcha_registration.py
+++ /dev/null
@@ -1,329 +0,0 @@
-#!/usr/bin/env python3
-"""
-图形验证码注册流程演示
-"""
-
-import requests
-import json
-import sqlite3
-import time
-
-def demo_complete_registration():
- """演示完整的注册流程"""
- print("🎭 图形验证码注册流程演示")
- print("=" * 60)
-
- session_id = f"demo_session_{int(time.time())}"
- test_email = "demo@example.com"
- test_username = "demouser"
- test_password = "demo123456"
-
- # 清理可能存在的测试数据
- try:
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
- cursor.execute('DELETE FROM users WHERE username = ? OR email = ?', (test_username, test_email))
- cursor.execute('DELETE FROM email_verifications WHERE email = ?', (test_email,))
- cursor.execute('DELETE FROM captcha_codes WHERE session_id = ?', (session_id,))
- conn.commit()
- conn.close()
- print("🧹 清理旧的测试数据")
- except:
- pass
-
- print("\n📋 注册流程步骤:")
- print("1. 生成图形验证码")
- print("2. 用户输入图形验证码")
- print("3. 验证图形验证码")
- print("4. 发送邮箱验证码")
- print("5. 用户输入邮箱验证码")
- print("6. 完成注册")
-
- # 步骤1: 生成图形验证码
- print("\n🔸 步骤1: 生成图形验证码")
- response = requests.post('http://localhost:8080/generate-captcha',
- json={'session_id': session_id})
-
- if response.status_code != 200:
- print(f"❌ 生成图形验证码失败: {response.status_code}")
- return False
-
- result = response.json()
- if not result['success']:
- print(f"❌ 生成图形验证码失败: {result['message']}")
- return False
-
- print("✅ 图形验证码生成成功")
-
- # 从数据库获取验证码文本(模拟用户看到图片并输入)
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
- cursor.execute('SELECT code FROM captcha_codes WHERE session_id = ? ORDER BY created_at DESC LIMIT 1',
- (session_id,))
- captcha_result = cursor.fetchone()
- conn.close()
-
- if not captcha_result:
- print("❌ 无法获取图形验证码")
- return False
-
- captcha_text = captcha_result[0]
- print(f"📷 图形验证码: {captcha_text}")
-
- # 步骤2-3: 验证图形验证码
- print("\n🔸 步骤2-3: 验证图形验证码")
- response = requests.post('http://localhost:8080/verify-captcha',
- json={
- 'session_id': session_id,
- 'captcha_code': captcha_text
- })
-
- if response.status_code != 200:
- print(f"❌ 验证图形验证码失败: {response.status_code}")
- return False
-
- result = response.json()
- if not result['success']:
- print(f"❌ 图形验证码验证失败: {result['message']}")
- return False
-
- print("✅ 图形验证码验证成功")
-
- # 步骤4: 发送邮箱验证码
- print("\n🔸 步骤4: 发送邮箱验证码")
- response = requests.post('http://localhost:8080/send-verification-code',
- json={'email': test_email})
-
- if response.status_code != 200:
- print(f"❌ 发送邮箱验证码失败: {response.status_code}")
- return False
-
- result = response.json()
- if not result['success']:
- print(f"❌ 发送邮箱验证码失败: {result['message']}")
- return False
-
- print("✅ 邮箱验证码发送成功")
-
- # 从数据库获取邮箱验证码(模拟用户收到邮件)
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
- cursor.execute('SELECT code FROM email_verifications WHERE email = ? ORDER BY created_at DESC LIMIT 1',
- (test_email,))
- email_result = cursor.fetchone()
- conn.close()
-
- if not email_result:
- print("❌ 无法获取邮箱验证码")
- return False
-
- email_code = email_result[0]
- print(f"📧 邮箱验证码: {email_code}")
-
- # 步骤5-6: 完成注册
- print("\n🔸 步骤5-6: 完成用户注册")
- response = requests.post('http://localhost:8080/register',
- json={
- 'username': test_username,
- 'email': test_email,
- 'verification_code': email_code,
- 'password': test_password
- })
-
- if response.status_code != 200:
- print(f"❌ 用户注册失败: {response.status_code}")
- return False
-
- result = response.json()
- if not result['success']:
- print(f"❌ 用户注册失败: {result['message']}")
- return False
-
- print("✅ 用户注册成功")
-
- # 验证登录
- print("\n🔸 验证登录功能")
- response = requests.post('http://localhost:8080/login',
- json={
- 'username': test_username,
- 'password': test_password
- })
-
- if response.status_code != 200:
- print(f"❌ 用户登录失败: {response.status_code}")
- return False
-
- result = response.json()
- if not result['success']:
- print(f"❌ 用户登录失败: {result['message']}")
- return False
-
- print("✅ 用户登录成功")
- print(f"🎫 Token: {result['token'][:20]}...")
- print(f"👤 用户ID: {result['user_id']}")
-
- return True
-
-def demo_security_features():
- """演示安全特性"""
- print("\n🔒 安全特性演示")
- print("-" * 40)
-
- session_id = f"security_test_{int(time.time())}"
-
- # 1. 测试错误的图形验证码
- print("1️⃣ 测试错误的图形验证码...")
-
- # 先生成一个验证码
- requests.post('http://localhost:8080/generate-captcha',
- json={'session_id': session_id})
-
- # 尝试用错误的验证码
- response = requests.post('http://localhost:8080/verify-captcha',
- json={
- 'session_id': session_id,
- 'captcha_code': 'WRONG'
- })
-
- result = response.json()
- if not result['success']:
- print(" ✅ 错误的图形验证码被正确拒绝")
- else:
- print(" ❌ 错误的图形验证码验证成功(安全漏洞)")
-
- # 2. 测试未验证图形验证码就发送邮件
- print("\n2️⃣ 测试未验证图形验证码发送邮件...")
-
- # 注意:当前实现中发送邮件接口没有检查图形验证码状态
- # 这是前端控制的,后端应该也要检查
- response = requests.post('http://localhost:8080/send-verification-code',
- json={'email': 'test@example.com'})
-
- result = response.json()
- if result['success']:
- print(" ⚠️ 未验证图形验证码也能发送邮件(建议后端也要检查)")
- else:
- print(" ✅ 未验证图形验证码无法发送邮件")
-
- # 3. 测试验证码重复使用
- print("\n3️⃣ 测试验证码重复使用...")
-
- # 生成新的验证码
- session_id2 = f"reuse_test_{int(time.time())}"
- requests.post('http://localhost:8080/generate-captcha',
- json={'session_id': session_id2})
-
- # 获取验证码
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
- cursor.execute('SELECT code FROM captcha_codes WHERE session_id = ? ORDER BY created_at DESC LIMIT 1',
- (session_id2,))
- result = cursor.fetchone()
- conn.close()
-
- if result:
- captcha_code = result[0]
-
- # 第一次验证
- response1 = requests.post('http://localhost:8080/verify-captcha',
- json={
- 'session_id': session_id2,
- 'captcha_code': captcha_code
- })
-
- # 第二次验证(重复使用)
- response2 = requests.post('http://localhost:8080/verify-captcha',
- json={
- 'session_id': session_id2,
- 'captcha_code': captcha_code
- })
-
- result1 = response1.json()
- result2 = response2.json()
-
- if result1['success'] and not result2['success']:
- print(" ✅ 验证码重复使用被正确阻止")
- else:
- print(" ❌ 验证码可以重复使用(安全漏洞)")
-
-def cleanup_demo_data():
- """清理演示数据"""
- print("\n🧹 清理演示数据")
- print("-" * 40)
-
- try:
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- # 清理测试用户
- cursor.execute('DELETE FROM users WHERE username LIKE "demo%" OR email LIKE "demo%"')
- user_count = cursor.rowcount
-
- # 清理测试验证码
- cursor.execute('DELETE FROM email_verifications WHERE email LIKE "demo%"')
- email_count = cursor.rowcount
-
- # 清理测试图形验证码
- cursor.execute('DELETE FROM captcha_codes WHERE session_id LIKE "demo%" OR session_id LIKE "security%" OR session_id LIKE "reuse%"')
- captcha_count = cursor.rowcount
-
- conn.commit()
- conn.close()
-
- print(f"✅ 清理完成:")
- print(f" • 用户: {user_count} 条")
- print(f" • 邮箱验证码: {email_count} 条")
- print(f" • 图形验证码: {captcha_count} 条")
-
- except Exception as e:
- print(f"❌ 清理失败: {e}")
-
-def main():
- """主演示函数"""
- print("🎪 图形验证码系统完整演示")
- print("=" * 60)
-
- try:
- # 演示完整注册流程
- success = demo_complete_registration()
-
- if success:
- print("\n🎉 完整注册流程演示成功!")
- else:
- print("\n💥 注册流程演示失败!")
- return False
-
- # 演示安全特性
- demo_security_features()
-
- # 清理演示数据
- cleanup_demo_data()
-
- print("\n" + "=" * 60)
- print("🎊 图形验证码系统演示完成!")
-
- print("\n📋 功能总结:")
- print("✅ 图形验证码生成和验证")
- print("✅ 邮箱验证码发送")
- print("✅ 用户注册流程")
- print("✅ 用户登录验证")
- print("✅ 安全特性保护")
-
- print("\n🌐 使用方法:")
- print("1. 访问: http://localhost:8080/register.html")
- print("2. 查看图形验证码并输入")
- print("3. 输入邮箱地址")
- print("4. 点击发送验证码(需要先验证图形验证码)")
- print("5. 输入邮箱验证码")
- print("6. 设置密码并完成注册")
-
- return True
-
- except Exception as e:
- print(f"\n❌ 演示失败: {e}")
- import traceback
- traceback.print_exc()
- return False
-
-if __name__ == "__main__":
- main()
diff --git a/deploy.bat b/deploy.bat
deleted file mode 100644
index c10fd9d..0000000
--- a/deploy.bat
+++ /dev/null
@@ -1,298 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-:: 闲鱼自动回复系统 Docker 部署脚本 (Windows版本)
-:: 作者: Xianyu Auto Reply System
-:: 版本: 1.0.0
-
-title 闲鱼自动回复系统 Docker 部署
-
-:: 颜色定义
-set "RED=[91m"
-set "GREEN=[92m"
-set "YELLOW=[93m"
-set "BLUE=[94m"
-set "NC=[0m"
-
-:: 打印带颜色的消息
-:print_info
-echo %BLUE%[INFO]%NC% %~1
-goto :eof
-
-:print_success
-echo %GREEN%[SUCCESS]%NC% %~1
-goto :eof
-
-:print_warning
-echo %YELLOW%[WARNING]%NC% %~1
-goto :eof
-
-:print_error
-echo %RED%[ERROR]%NC% %~1
-goto :eof
-
-:: 检查Docker是否安装
-:check_docker
-call :print_info "检查 Docker 环境..."
-
-docker --version >nul 2>&1
-if errorlevel 1 (
- call :print_error "Docker 未安装,请先安装 Docker Desktop"
- echo.
- echo 下载地址: https://www.docker.com/products/docker-desktop
- pause
- exit /b 1
-)
-
-docker-compose --version >nul 2>&1
-if errorlevel 1 (
- call :print_error "Docker Compose 未安装,请先安装 Docker Compose"
- pause
- exit /b 1
-)
-
-call :print_success "Docker 环境检查通过"
-goto :eof
-
-:: 创建必要的目录
-:create_directories
-call :print_info "创建必要的目录..."
-
-if not exist "data" mkdir data
-if not exist "logs" mkdir logs
-if not exist "backups" mkdir backups
-if not exist "nginx" mkdir nginx
-if not exist "nginx\ssl" mkdir nginx\ssl
-
-REM 检查目录是否创建成功
-if not exist "data" (
- call :print_error "data目录创建失败"
- pause
- exit /b 1
-)
-
-if not exist "logs" (
- call :print_error "logs目录创建失败"
- pause
- exit /b 1
-)
-
-call :print_success "目录创建完成"
-goto :eof
-
-:: 生成默认配置文件
-:generate_config
-REM 生成.env文件
-if not exist ".env" (
- if exist ".env.example" (
- call :print_info "从模板生成 .env 文件..."
- copy ".env.example" ".env" >nul
- call :print_success ".env 文件已生成"
- ) else (
- call :print_warning ".env.example 文件不存在,跳过 .env 文件生成"
- )
-) else (
- call :print_info ".env 文件已存在,跳过生成"
-)
-
-REM 生成global_config.yml文件
-if exist "global_config.yml" (
- call :print_info "配置文件已存在,跳过生成"
- goto :eof
-)
-
-call :print_info "生成默认配置文件..."
-
-(
-echo # 闲鱼自动回复系统配置文件
-echo API_ENDPOINTS:
-echo login_check: https://passport.goofish.com/newlogin/hasLogin.do
-echo message_headinfo: https://h5api.m.goofish.com/h5/mtop.idle.trade.pc.message.headinfo/1.0/
-echo token: https://h5api.m.goofish.com/h5/mtop.taobao.idlemessage.pc.login.token/1.0/
-echo.
-echo APP_CONFIG:
-echo api_version: '1.0'
-echo app_key: 444e9908a51d1cb236a27862abc769c9
-echo app_version: '1.0'
-echo platform: web
-echo.
-echo AUTO_REPLY:
-echo enabled: true
-echo default_message: '亲爱的"{send_user_name}" 老板你好!所有宝贝都可以拍,秒发货的哈~不满意的话可以直接申请退款哈~'
-echo max_retry: 3
-echo retry_interval: 5
-echo api:
-echo enabled: false
-echo host: 0.0.0.0 # 绑定所有网络接口,支持IP访问
-echo port: 8080 # Web服务端口
-echo url: http://0.0.0.0:8080/xianyu/reply
-echo timeout: 10
-echo.
-echo COOKIES:
-echo last_update_time: ''
-echo value: ''
-echo.
-echo DEFAULT_HEADERS:
-echo accept: application/json
-echo accept-language: zh-CN,zh;q=0.9
-echo cache-control: no-cache
-echo origin: https://www.goofish.com
-echo pragma: no-cache
-echo referer: https://www.goofish.com/
-echo user-agent: Mozilla/5.0 ^(Windows NT 10.0; Win64; x64^) AppleWebKit/537.36 ^(KHTML, like Gecko^) Chrome/119.0.0.0 Safari/537.36
-echo.
-echo WEBSOCKET_URL: wss://wss-goofish.dingtalk.com/
-echo HEARTBEAT_INTERVAL: 15
-echo HEARTBEAT_TIMEOUT: 5
-echo TOKEN_REFRESH_INTERVAL: 3600
-echo TOKEN_RETRY_INTERVAL: 300
-echo MESSAGE_EXPIRE_TIME: 300000
-echo.
-echo LOG_CONFIG:
-echo level: INFO
-echo format: '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}'
-echo rotation: '1 day'
-echo retention: '7 days'
-) > global_config.yml
-
-call :print_success "默认配置文件已生成"
-goto :eof
-
-:: 构建Docker镜像
-:build_image
-call :print_info "构建 Docker 镜像..."
-
-docker build -t xianyu-auto-reply:latest .
-if errorlevel 1 (
- call :print_error "Docker 镜像构建失败"
- pause
- exit /b 1
-)
-
-call :print_success "Docker 镜像构建完成"
-goto :eof
-
-:: 启动服务
-:start_services
-call :print_info "启动服务..."
-
-if "%~1"=="--with-nginx" (
- call :print_info "启动服务(包含 Nginx)..."
- docker-compose --profile with-nginx up -d
-) else (
- call :print_info "启动服务(不包含 Nginx)..."
- docker-compose up -d
-)
-
-if errorlevel 1 (
- call :print_error "服务启动失败"
- pause
- exit /b 1
-)
-
-call :print_success "服务启动完成"
-goto :eof
-
-:: 显示服务状态
-:show_status
-call :print_info "服务状态:"
-docker-compose ps
-
-echo.
-call :print_info "服务日志(最近10行):"
-docker-compose logs --tail=10
-goto :eof
-
-:: 显示访问信息
-:show_access_info
-call :print_success "部署完成!"
-echo.
-call :print_info "访问信息:"
-echo Web界面: http://localhost:8080
-echo 默认账号: admin
-echo 默认密码: admin123
-echo.
-call :print_info "常用命令:"
-echo 查看日志: docker-compose logs -f
-echo 重启服务: docker-compose restart
-echo 停止服务: docker-compose down
-echo 更新服务: deploy.bat update
-echo.
-call :print_info "数据目录:"
-echo 数据库: .\data\xianyu_data.db
-echo 日志: .\logs\
-echo 配置: .\global_config.yml
-echo.
-goto :eof
-
-:: 更新服务
-:update_services
-call :print_info "更新服务..."
-
-docker-compose down
-call :build_image
-call :start_services %~1
-
-call :print_success "服务更新完成"
-goto :eof
-
-:: 清理资源
-:cleanup
-call :print_warning "清理 Docker 资源..."
-
-docker-compose down --volumes --remove-orphans
-docker rmi xianyu-auto-reply:latest 2>nul
-
-call :print_success "清理完成"
-goto :eof
-
-:: 显示帮助
-:show_help
-echo 使用方法:
-echo %~nx0 # 首次部署
-echo %~nx0 with-nginx # 部署并启动 Nginx
-echo %~nx0 update # 更新服务
-echo %~nx0 update with-nginx # 更新服务并启动 Nginx
-echo %~nx0 status # 查看服务状态
-echo %~nx0 cleanup # 清理所有资源
-echo %~nx0 help # 显示帮助
-goto :eof
-
-:: 主函数
-:main
-echo ========================================
-echo 闲鱼自动回复系统 Docker 部署脚本
-echo ========================================
-echo.
-
-if "%~1"=="update" (
- call :print_info "更新模式"
- call :check_docker
- call :update_services %~2
- call :show_status
- call :show_access_info
-) else if "%~1"=="cleanup" (
- call :print_warning "清理模式"
- call :cleanup
-) else if "%~1"=="status" (
- call :show_status
-) else if "%~1"=="help" (
- call :show_help
-) else (
- call :print_info "首次部署模式"
- call :check_docker
- call :create_directories
- call :generate_config
- call :build_image
- call :start_services %~1
- call :show_status
- call :show_access_info
-)
-
-echo.
-pause
-goto :eof
-
-:: 执行主函数
-call :main %*
diff --git a/deploy.sh b/deploy.sh
deleted file mode 100644
index 75f3d0b..0000000
--- a/deploy.sh
+++ /dev/null
@@ -1,288 +0,0 @@
-#!/bin/bash
-
-# 闲鱼自动回复系统 Docker 部署脚本
-# 作者: Xianyu Auto Reply System
-# 版本: 1.0.0
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-# 打印带颜色的消息
-print_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
-}
-
-print_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
-}
-
-print_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-print_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-# 检查Docker是否安装
-check_docker() {
- if ! command -v docker &> /dev/null; then
- print_error "Docker 未安装,请先安装 Docker"
- exit 1
- fi
-
- if ! command -v docker-compose &> /dev/null; then
- print_error "Docker Compose 未安装,请先安装 Docker Compose"
- exit 1
- fi
-
- print_success "Docker 环境检查通过"
-}
-
-# 创建必要的目录
-create_directories() {
- print_info "创建必要的目录..."
-
- # 创建目录
- mkdir -p data
- mkdir -p logs
- mkdir -p backups
- mkdir -p nginx/ssl
-
- # 设置权限 (确保Docker容器可以写入)
- chmod 755 data logs backups
-
- # 检查权限
- if [ ! -w "data" ]; then
- print_error "data目录没有写权限"
- exit 1
- fi
-
- if [ ! -w "logs" ]; then
- print_error "logs目录没有写权限"
- exit 1
- fi
-
- print_success "目录创建完成"
-}
-
-# 生成默认配置文件
-generate_config() {
- # 生成.env文件
- if [ ! -f ".env" ]; then
- if [ -f ".env.example" ]; then
- print_info "从模板生成 .env 文件..."
- cp .env.example .env
- print_success ".env 文件已生成"
- else
- print_warning ".env.example 文件不存在,跳过 .env 文件生成"
- fi
- else
- print_info ".env 文件已存在,跳过生成"
- fi
-
- # 生成global_config.yml文件
- if [ ! -f "global_config.yml" ]; then
- print_info "生成默认配置文件..."
-
- cat > global_config.yml << EOF
-# 闲鱼自动回复系统配置文件
-API_ENDPOINTS:
- login_check: https://passport.goofish.com/newlogin/hasLogin.do
- message_headinfo: https://h5api.m.goofish.com/h5/mtop.idle.trade.pc.message.headinfo/1.0/
- token: https://h5api.m.goofish.com/h5/mtop.taobao.idlemessage.pc.login.token/1.0/
-
-APP_CONFIG:
- api_version: '1.0'
- app_key: 444e9908a51d1cb236a27862abc769c9
- app_version: '1.0'
- platform: web
-
-AUTO_REPLY:
- enabled: true
- default_message: '亲爱的"{send_user_name}" 老板你好!所有宝贝都可以拍,秒发货的哈~不满意的话可以直接申请退款哈~'
- max_retry: 3
- retry_interval: 5
- api:
- enabled: false
- host: 0.0.0.0 # 绑定所有网络接口,支持IP访问
- port: 8080 # Web服务端口
- url: http://0.0.0.0:8080/xianyu/reply
- timeout: 10
-
-COOKIES:
- last_update_time: ''
- value: ''
-
-DEFAULT_HEADERS:
- accept: application/json
- accept-language: zh-CN,zh;q=0.9
- cache-control: no-cache
- origin: https://www.goofish.com
- pragma: no-cache
- referer: https://www.goofish.com/
- sec-ch-ua: '"Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24"'
- sec-ch-ua-mobile: '?0'
- sec-ch-ua-platform: '"Windows"'
- sec-fetch-dest: empty
- sec-fetch-mode: cors
- sec-fetch-site: same-site
- user-agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36
-
-WEBSOCKET_URL: wss://wss-goofish.dingtalk.com/
-HEARTBEAT_INTERVAL: 15
-HEARTBEAT_TIMEOUT: 5
-TOKEN_REFRESH_INTERVAL: 3600
-TOKEN_RETRY_INTERVAL: 300
-MESSAGE_EXPIRE_TIME: 300000
-
-LOG_CONFIG:
- level: INFO
- format: '{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}'
- rotation: '1 day'
- retention: '7 days'
-EOF
-
- print_success "默认配置文件已生成"
- else
- print_info "配置文件已存在,跳过生成"
- fi
-}
-
-# 构建Docker镜像
-build_image() {
- print_info "构建 Docker 镜像..."
-
- docker build -t xianyu-auto-reply:latest .
-
- print_success "Docker 镜像构建完成"
-}
-
-# 启动服务
-start_services() {
- print_info "启动服务..."
-
- # 检查是否需要启动 Nginx
- if [ "$1" = "--with-nginx" ]; then
- print_info "启动服务(包含 Nginx)..."
- docker-compose --profile with-nginx up -d
- else
- print_info "启动服务(不包含 Nginx)..."
- docker-compose up -d
- fi
-
- print_success "服务启动完成"
-}
-
-# 显示服务状态
-show_status() {
- print_info "服务状态:"
- docker-compose ps
-
- print_info "服务日志(最近10行):"
- docker-compose logs --tail=10
-}
-
-# 显示访问信息
-show_access_info() {
- print_success "部署完成!"
- echo ""
- print_info "访问信息:"
- echo " Web界面: http://localhost:8080"
- echo " 默认账号: admin"
- echo " 默认密码: admin123"
- echo ""
- print_info "常用命令:"
- echo " 查看日志: docker-compose logs -f"
- echo " 重启服务: docker-compose restart"
- echo " 停止服务: docker-compose down"
- echo " 更新服务: ./deploy.sh --update"
- echo ""
- print_info "数据目录:"
- echo " 数据库: ./data/xianyu_data.db"
- echo " 日志: ./logs/"
- echo " 配置: ./global_config.yml"
-}
-
-# 更新服务
-update_services() {
- print_info "更新服务..."
-
- # 停止服务
- docker-compose down
-
- # 重新构建镜像
- build_image
-
- # 启动服务
- start_services $1
-
- print_success "服务更新完成"
-}
-
-# 清理资源
-cleanup() {
- print_warning "清理 Docker 资源..."
-
- # 停止并删除容器
- docker-compose down --volumes --remove-orphans
-
- # 删除镜像
- docker rmi xianyu-auto-reply:latest 2>/dev/null || true
-
- print_success "清理完成"
-}
-
-# 主函数
-main() {
- echo "========================================"
- echo " 闲鱼自动回复系统 Docker 部署脚本"
- echo "========================================"
- echo ""
-
- case "$1" in
- --update)
- print_info "更新模式"
- check_docker
- update_services $2
- show_status
- show_access_info
- ;;
- --cleanup)
- print_warning "清理模式"
- cleanup
- ;;
- --status)
- show_status
- ;;
- --help)
- echo "使用方法:"
- echo " $0 # 首次部署"
- echo " $0 --with-nginx # 部署并启动 Nginx"
- echo " $0 --update # 更新服务"
- echo " $0 --update --with-nginx # 更新服务并启动 Nginx"
- echo " $0 --status # 查看服务状态"
- echo " $0 --cleanup # 清理所有资源"
- echo " $0 --help # 显示帮助"
- ;;
- *)
- print_info "首次部署模式"
- check_docker
- create_directories
- generate_config
- build_image
- start_services $1
- show_status
- show_access_info
- ;;
- esac
-}
-
-# 执行主函数
-main "$@"
diff --git a/docker-deploy.bat b/docker-deploy.bat
deleted file mode 100644
index 671acb9..0000000
--- a/docker-deploy.bat
+++ /dev/null
@@ -1,301 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-:: 闲鱼自动回复系统 Docker 部署脚本 (Windows版本)
-:: 支持快速部署和管理
-
-set PROJECT_NAME=xianyu-auto-reply
-set COMPOSE_FILE=docker-compose.yml
-set ENV_FILE=.env
-
-:: 颜色定义 (Windows 10+ 支持ANSI颜色)
-set "RED=[31m"
-set "GREEN=[32m"
-set "YELLOW=[33m"
-set "BLUE=[34m"
-set "NC=[0m"
-
-:: 打印带颜色的消息
-:print_info
-echo %BLUE%ℹ️ %~1%NC%
-goto :eof
-
-:print_success
-echo %GREEN%✅ %~1%NC%
-goto :eof
-
-:print_warning
-echo %YELLOW%⚠️ %~1%NC%
-goto :eof
-
-:print_error
-echo %RED%❌ %~1%NC%
-goto :eof
-
-:: 检查依赖
-:check_dependencies
-call :print_info "检查系统依赖..."
-
-docker --version >nul 2>&1
-if errorlevel 1 (
- call :print_error "Docker 未安装,请先安装 Docker Desktop"
- exit /b 1
-)
-
-docker-compose --version >nul 2>&1
-if errorlevel 1 (
- call :print_error "Docker Compose 未安装,请先安装 Docker Compose"
- exit /b 1
-)
-
-call :print_success "系统依赖检查通过"
-goto :eof
-
-:: 初始化配置
-:init_config
-call :print_info "初始化配置文件..."
-
-if not exist "%ENV_FILE%" (
- if exist ".env.example" (
- copy ".env.example" "%ENV_FILE%" >nul
- call :print_success "已创建 %ENV_FILE% 配置文件"
- ) else (
- call :print_error ".env.example 文件不存在"
- exit /b 1
- )
-) else (
- call :print_warning "%ENV_FILE% 已存在,跳过创建"
-)
-
-:: 创建必要的目录
-if not exist "data" mkdir data
-if not exist "logs" mkdir logs
-if not exist "backups" mkdir backups
-call :print_success "已创建必要的目录"
-goto :eof
-
-:: 构建镜像
-:build_image
-call :print_info "构建 Docker 镜像..."
-docker-compose build --no-cache
-if errorlevel 1 (
- call :print_error "镜像构建失败"
- exit /b 1
-)
-call :print_success "镜像构建完成"
-goto :eof
-
-:: 启动服务
-:start_services
-set "profile="
-if "%~1"=="with-nginx" (
- set "profile=--profile with-nginx"
- call :print_info "启动服务(包含 Nginx)..."
-) else (
- call :print_info "启动基础服务..."
-)
-
-docker-compose %profile% up -d
-if errorlevel 1 (
- call :print_error "服务启动失败"
- exit /b 1
-)
-call :print_success "服务启动完成"
-
-:: 等待服务就绪
-call :print_info "等待服务就绪..."
-timeout /t 10 /nobreak >nul
-
-:: 检查服务状态
-docker-compose ps | findstr "Up" >nul
-if errorlevel 1 (
- call :print_error "服务启动失败"
- docker-compose logs
- exit /b 1
-) else (
- call :print_success "服务运行正常"
- call :show_access_info "%~1"
-)
-goto :eof
-
-:: 停止服务
-:stop_services
-call :print_info "停止服务..."
-docker-compose down
-call :print_success "服务已停止"
-goto :eof
-
-:: 重启服务
-:restart_services
-call :print_info "重启服务..."
-docker-compose restart
-call :print_success "服务已重启"
-goto :eof
-
-:: 查看日志
-:show_logs
-if "%~1"=="" (
- docker-compose logs -f
-) else (
- docker-compose logs -f "%~1"
-)
-goto :eof
-
-:: 查看状态
-:show_status
-call :print_info "服务状态:"
-docker-compose ps
-
-call :print_info "资源使用:"
-for /f "tokens=*" %%i in ('docker-compose ps -q') do (
- docker stats --no-stream %%i
-)
-goto :eof
-
-:: 显示访问信息
-:show_access_info
-echo.
-call :print_success "🎉 部署完成!"
-echo.
-
-if "%~1"=="with-nginx" (
- echo 📱 访问地址:
- echo HTTP: http://localhost
- echo HTTPS: https://localhost ^(如果配置了SSL^)
-) else (
- echo 📱 访问地址:
- echo HTTP: http://localhost:8080
-)
-
-echo.
-echo 🔐 默认登录信息:
-echo 用户名: admin
-echo 密码: admin123
-echo.
-echo 📊 管理命令:
-echo 查看状态: %~nx0 status
-echo 查看日志: %~nx0 logs
-echo 重启服务: %~nx0 restart
-echo 停止服务: %~nx0 stop
-echo.
-goto :eof
-
-:: 健康检查
-:health_check
-call :print_info "执行健康检查..."
-
-set "url=http://localhost:8080/health"
-set "max_attempts=30"
-set "attempt=1"
-
-:health_loop
-curl -f -s "%url%" >nul 2>&1
-if not errorlevel 1 (
- call :print_success "健康检查通过"
- goto :eof
-)
-
-call :print_info "等待服务就绪... (!attempt!/%max_attempts%)"
-timeout /t 2 /nobreak >nul
-set /a attempt+=1
-
-if !attempt! leq %max_attempts% goto health_loop
-
-call :print_error "健康检查失败"
-exit /b 1
-
-:: 备份数据
-:backup_data
-call :print_info "备份数据..."
-
-for /f "tokens=2 delims==" %%i in ('wmic OS Get localdatetime /value') do set datetime=%%i
-set backup_dir=backups\%datetime:~0,8%_%datetime:~8,6%
-mkdir "%backup_dir%" 2>nul
-
-:: 备份数据库
-if exist "data\xianyu_data.db" (
- copy "data\xianyu_data.db" "%backup_dir%\" >nul
- call :print_success "数据库备份完成"
-)
-
-:: 备份配置
-copy "%ENV_FILE%" "%backup_dir%\" >nul
-copy "global_config.yml" "%backup_dir%\" >nul 2>&1
-
-call :print_success "数据备份完成: %backup_dir%"
-goto :eof
-
-:: 显示帮助信息
-:show_help
-echo 闲鱼自动回复系统 Docker 部署脚本 ^(Windows版本^)
-echo.
-echo 用法: %~nx0 [命令] [选项]
-echo.
-echo 命令:
-echo init 初始化配置文件
-echo build 构建 Docker 镜像
-echo start [with-nginx] 启动服务^(可选包含 Nginx^)
-echo stop 停止服务
-echo restart 重启服务
-echo status 查看服务状态
-echo logs [service] 查看日志
-echo health 健康检查
-echo backup 备份数据
-echo help 显示帮助信息
-echo.
-echo 示例:
-echo %~nx0 init # 初始化配置
-echo %~nx0 start # 启动基础服务
-echo %~nx0 start with-nginx # 启动包含 Nginx 的服务
-echo %~nx0 logs xianyu-app # 查看应用日志
-echo.
-goto :eof
-
-:: 主函数
-:main
-if "%~1"=="init" (
- call :check_dependencies
- call :init_config
-) else if "%~1"=="build" (
- call :check_dependencies
- call :build_image
-) else if "%~1"=="start" (
- call :check_dependencies
- call :init_config
- call :build_image
- call :start_services "%~2"
-) else if "%~1"=="stop" (
- call :stop_services
-) else if "%~1"=="restart" (
- call :restart_services
-) else if "%~1"=="status" (
- call :show_status
-) else if "%~1"=="logs" (
- call :show_logs "%~2"
-) else if "%~1"=="health" (
- call :health_check
-) else if "%~1"=="backup" (
- call :backup_data
-) else if "%~1"=="help" (
- call :show_help
-) else if "%~1"=="-h" (
- call :show_help
-) else if "%~1"=="--help" (
- call :show_help
-) else if "%~1"=="" (
- call :print_info "快速部署模式"
- call :check_dependencies
- call :init_config
- call :build_image
- call :start_services
-) else (
- call :print_error "未知命令: %~1"
- call :show_help
- exit /b 1
-)
-
-goto :eof
-
-:: 执行主函数
-call :main %*
diff --git a/docker_deployment_update.md b/docker_deployment_update.md
deleted file mode 100644
index b68877b..0000000
--- a/docker_deployment_update.md
+++ /dev/null
@@ -1,207 +0,0 @@
-# Docker部署更新检查报告
-
-## 📋 检查概述
-
-对Docker部署配置进行了全面检查,评估是否需要更新以支持新增的AI回复功能和其他改进。
-
-## ✅ 当前状态评估
-
-### 🎯 **结论:Docker部署配置已经完善,无需重大更新**
-
-所有新增功能都已经在现有的Docker配置中得到支持。
-
-## 📊 详细检查结果
-
-### 1. **依赖包检查** ✅
-#### requirements.txt 状态:**完整**
-```
-✅ openai>=1.65.5 # AI回复功能
-✅ python-dotenv>=1.0.1 # 环境变量支持
-✅ python-multipart>=0.0.6 # 文件上传支持
-✅ fastapi>=0.111 # Web框架
-✅ uvicorn[standard]>=0.29 # ASGI服务器
-✅ 其他所有必要依赖
-```
-
-### 2. **环境变量配置** ✅
-#### .env.example 状态:**完整**
-```
-✅ AI_REPLY_ENABLED=false
-✅ DEFAULT_AI_MODEL=qwen-plus
-✅ DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
-✅ AI_REQUEST_TIMEOUT=30
-✅ AI_MAX_TOKENS=100
-✅ 所有基础配置变量
-```
-
-### 3. **Docker Compose配置** ✅
-#### docker-compose.yml 状态:**完整**
-```
-✅ AI回复相关环境变量映射
-✅ 数据持久化配置 (/app/data, /app/logs, /app/backups)
-✅ 健康检查配置
-✅ 资源限制配置
-✅ 网络配置
-✅ Nginx反向代理支持
-```
-
-### 4. **Dockerfile配置** ✅
-#### Dockerfile 状态:**完整**
-```
-✅ Python 3.11基础镜像
-✅ 所有系统依赖安装
-✅ 应用依赖安装
-✅ 工作目录配置
-✅ 端口暴露配置
-✅ 启动命令配置
-```
-
-### 5. **数据持久化** ✅
-#### 挂载点配置:**完整**
-```
-✅ ./data:/app/data:rw # 数据库文件
-✅ ./logs:/app/logs:rw # 日志文件
-✅ ./backups:/app/backups:rw # 备份文件
-✅ ./global_config.yml:/app/global_config.yml:ro # 配置文件
-```
-
-### 6. **健康检查** ✅
-#### 健康检查配置:**完整**
-```
-✅ HTTP健康检查端点 (/health)
-✅ 检查间隔:30秒
-✅ 超时时间:10秒
-✅ 重试次数:3次
-✅ 启动等待:40秒
-```
-
-## 🔍 新功能支持验证
-
-### AI回复功能 ✅
-- **依赖支持**:openai库已包含
-- **配置支持**:所有AI相关环境变量已配置
-- **数据支持**:AI数据表会自动创建
-- **API支持**:FastAPI框架支持所有新接口
-
-### 备份功能增强 ✅
-- **存储支持**:备份目录已挂载
-- **数据支持**:所有新表都包含在备份中
-- **权限支持**:容器有读写权限
-
-### 商品管理功能 ✅
-- **文件上传**:python-multipart依赖已包含
-- **数据存储**:数据库挂载支持新表
-- **API支持**:FastAPI支持文件上传接口
-
-## 💡 可选优化建议
-
-虽然当前配置已经完善,但可以考虑以下优化:
-
-### 1. **添加AI服务健康检查**
-```yaml
-# 可选:添加AI服务连通性检查
-healthcheck:
- test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/api/ai/health', timeout=5)"]
-```
-
-### 2. **添加更多监控指标**
-```yaml
-# 可选:添加Prometheus监控
-environment:
- - ENABLE_METRICS=true
- - METRICS_PORT=9090
-```
-
-### 3. **添加AI配置验证**
-```yaml
-# 可选:启动时验证AI配置
-environment:
- - VALIDATE_AI_CONFIG=true
-```
-
-## 🚀 部署建议
-
-### 生产环境部署
-1. **使用强密码**
- ```bash
- ADMIN_PASSWORD=$(openssl rand -base64 32)
- JWT_SECRET_KEY=$(openssl rand -base64 32)
- ```
-
-2. **配置AI服务**
- ```bash
- AI_REPLY_ENABLED=true
- # 配置真实的API密钥
- ```
-
-3. **启用HTTPS**
- ```bash
- docker-compose --profile with-nginx up -d
- ```
-
-4. **配置资源限制**
- ```bash
- MEMORY_LIMIT=1024 # 如果使用AI功能,建议增加内存
- CPU_LIMIT=1.0
- ```
-
-### 开发环境部署
-```bash
-# 克隆项目
-git clone
-cd xianyuapis
-
-# 复制环境变量
-cp .env.example .env
-
-# 启动服务
-docker-compose up -d
-
-# 查看日志
-docker-compose logs -f
-```
-
-## 📋 部署检查清单
-
-### 部署前检查 ✅
-- [x] Docker和Docker Compose已安装
-- [x] 端口8080未被占用
-- [x] 有足够的磁盘空间(建议>2GB)
-- [x] 网络连接正常
-
-### 配置检查 ✅
-- [x] .env文件已配置
-- [x] global_config.yml文件存在
-- [x] data、logs、backups目录权限正确
-- [x] AI API密钥已配置(如果使用AI功能)
-
-### 功能验证 ✅
-- [x] Web界面可访问
-- [x] 账号管理功能正常
-- [x] 自动回复功能正常
-- [x] AI回复功能正常(如果启用)
-- [x] 备份功能正常
-
-## 🎉 总结
-
-### ✅ **Docker部署配置完全就绪**
-
-1. **无需更新**:当前配置已支持所有新功能
-2. **开箱即用**:可直接部署使用
-3. **功能完整**:支持AI回复、备份、商品管理等所有功能
-4. **生产就绪**:包含安全、监控、资源限制等配置
-
-### 🚀 **立即可用的部署命令**
-
-```bash
-# 快速部署
-git clone
-cd xianyuapis
-cp .env.example .env
-docker-compose up -d
-
-# 访问系统
-open http://localhost:8080
-```
-
-**Docker部署配置已经完善,支持所有新功能,可以直接使用!** 🎉
diff --git a/fix-db-permissions.bat b/fix-db-permissions.bat
deleted file mode 100644
index fdcb358..0000000
--- a/fix-db-permissions.bat
+++ /dev/null
@@ -1,156 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-:: 修复数据库权限问题的脚本 (Windows版本)
-:: 解决Docker容器中数据库无法创建的问题
-
-title 数据库权限修复脚本
-
-:: 颜色定义
-set "RED=[91m"
-set "GREEN=[92m"
-set "YELLOW=[93m"
-set "BLUE=[94m"
-set "NC=[0m"
-
-:: 打印带颜色的消息
-:print_info
-echo %BLUE%[INFO]%NC% %~1
-goto :eof
-
-:print_success
-echo %GREEN%[SUCCESS]%NC% %~1
-goto :eof
-
-:print_warning
-echo %YELLOW%[WARNING]%NC% %~1
-goto :eof
-
-:print_error
-echo %RED%[ERROR]%NC% %~1
-goto :eof
-
-echo ========================================
-echo 数据库权限修复脚本
-echo ========================================
-echo.
-
-:: 1. 停止现有容器
-call :print_info "停止现有容器..."
-docker-compose down >nul 2>&1
-
-:: 2. 检查并创建目录
-call :print_info "检查并创建必要目录..."
-
-for %%d in (data logs backups) do (
- if not exist "%%d" (
- call :print_info "创建目录: %%d"
- mkdir "%%d"
- )
-
- if not exist "%%d" (
- call :print_error "目录 %%d 创建失败"
- pause
- exit /b 1
- )
-
- call :print_success "目录 %%d 权限正常"
-)
-
-:: 3. 检查现有数据库文件
-if exist "data\xianyu_data.db" (
- call :print_info "检查现有数据库文件..."
- call :print_success "数据库文件存在"
-) else (
- call :print_info "数据库文件不存在,将在启动时创建"
-)
-
-:: 4. 测试数据库创建
-call :print_info "测试数据库创建..."
-python -c "
-import sqlite3
-import os
-
-db_path = 'data/test_db.sqlite'
-try:
- conn = sqlite3.connect(db_path)
- conn.execute('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY)')
- conn.commit()
- conn.close()
- print('✅ 数据库创建测试成功')
- if os.path.exists(db_path):
- os.remove(db_path)
-except Exception as e:
- print(f'❌ 数据库创建测试失败: {e}')
- exit(1)
-"
-
-if !errorlevel! neq 0 (
- call :print_error "数据库创建测试失败"
- pause
- exit /b 1
-)
-
-:: 5. 重新构建并启动
-call :print_info "重新构建并启动服务..."
-docker-compose build --no-cache
-if !errorlevel! neq 0 (
- call :print_error "Docker镜像构建失败"
- pause
- exit /b 1
-)
-
-docker-compose up -d
-if !errorlevel! neq 0 (
- call :print_error "服务启动失败"
- pause
- exit /b 1
-)
-
-:: 6. 等待服务启动
-call :print_info "等待服务启动..."
-timeout /t 15 /nobreak >nul
-
-:: 7. 检查服务状态
-call :print_info "检查服务状态..."
-docker-compose ps | findstr "Up" >nul
-if !errorlevel! equ 0 (
- call :print_success "服务启动成功"
-
- :: 检查日志
- call :print_info "检查启动日志..."
- docker-compose logs --tail=20 xianyu-app
-
- :: 测试健康检查
- call :print_info "测试健康检查..."
- timeout /t 5 /nobreak >nul
- curl -f http://localhost:8080/health >nul 2>&1
- if !errorlevel! equ 0 (
- call :print_success "健康检查通过"
- ) else (
- call :print_warning "健康检查失败,但服务可能仍在启动中"
- )
-) else (
- call :print_error "服务启动失败"
- call :print_info "查看错误日志:"
- docker-compose logs xianyu-app
- pause
- exit /b 1
-)
-
-echo.
-call :print_success "数据库权限修复完成!"
-echo.
-call :print_info "服务信息:"
-echo Web界面: http://localhost:8080
-echo 健康检查: http://localhost:8080/health
-echo 默认账号: admin / admin123
-echo.
-call :print_info "常用命令:"
-echo 查看日志: docker-compose logs -f
-echo 重启服务: docker-compose restart
-echo 停止服务: docker-compose down
-echo.
-
-pause
diff --git a/fix-db-permissions.sh b/fix-db-permissions.sh
deleted file mode 100644
index e6ea4ff..0000000
--- a/fix-db-permissions.sh
+++ /dev/null
@@ -1,167 +0,0 @@
-#!/bin/bash
-
-# 修复数据库权限问题的脚本
-# 解决Docker容器中数据库无法创建的问题
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-print_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
-}
-
-print_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
-}
-
-print_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-print_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-echo "========================================"
-echo " 数据库权限修复脚本"
-echo "========================================"
-echo ""
-
-# 1. 停止现有容器
-print_info "停止现有容器..."
-docker-compose down 2>/dev/null || true
-
-# 2. 检查并创建目录
-print_info "检查并创建必要目录..."
-for dir in data logs backups; do
- if [ ! -d "$dir" ]; then
- print_info "创建目录: $dir"
- mkdir -p "$dir"
- fi
-
- # 设置权限
- chmod 755 "$dir"
-
- # 检查权限
- if [ ! -w "$dir" ]; then
- print_error "目录 $dir 没有写权限"
-
- # 尝试修复权限
- print_info "尝试修复权限..."
- sudo chmod 755 "$dir" 2>/dev/null || {
- print_error "无法修复权限,请手动执行: sudo chmod 755 $dir"
- exit 1
- }
- fi
-
- print_success "目录 $dir 权限正常"
-done
-
-# 3. 检查现有数据库文件
-if [ -f "data/xianyu_data.db" ]; then
- print_info "检查现有数据库文件权限..."
- if [ ! -w "data/xianyu_data.db" ]; then
- print_warning "数据库文件没有写权限,尝试修复..."
- chmod 644 "data/xianyu_data.db"
- print_success "数据库文件权限已修复"
- else
- print_success "数据库文件权限正常"
- fi
-fi
-
-# 4. 检查Docker用户映射
-print_info "检查Docker用户映射..."
-CURRENT_UID=$(id -u)
-CURRENT_GID=$(id -g)
-
-print_info "当前用户 UID:GID = $CURRENT_UID:$CURRENT_GID"
-
-# 5. 创建测试数据库
-print_info "测试数据库创建..."
-python3 -c "
-import sqlite3
-import os
-
-db_path = 'data/test_db.sqlite'
-try:
- conn = sqlite3.connect(db_path)
- conn.execute('CREATE TABLE IF NOT EXISTS test (id INTEGER PRIMARY KEY)')
- conn.commit()
- conn.close()
- print('✅ 数据库创建测试成功')
- os.remove(db_path)
-except Exception as e:
- print(f'❌ 数据库创建测试失败: {e}')
- exit(1)
-" || {
- print_error "数据库创建测试失败"
- exit 1
-}
-
-# 6. 更新docker-compose.yml用户映射
-print_info "检查docker-compose.yml用户映射..."
-if ! grep -q "user:" docker-compose.yml; then
- print_info "添加用户映射到docker-compose.yml..."
-
- # 备份原文件
- cp docker-compose.yml docker-compose.yml.backup
-
- # 在xianyu-app服务中添加user配置
- sed -i '/container_name: xianyu-auto-reply/a\ user: "'$CURRENT_UID':'$CURRENT_GID'"' docker-compose.yml
-
- print_success "用户映射已添加"
-else
- print_info "用户映射已存在"
-fi
-
-# 7. 重新构建并启动
-print_info "重新构建并启动服务..."
-docker-compose build --no-cache
-docker-compose up -d
-
-# 8. 等待服务启动
-print_info "等待服务启动..."
-sleep 10
-
-# 9. 检查服务状态
-print_info "检查服务状态..."
-if docker-compose ps | grep -q "Up"; then
- print_success "服务启动成功"
-
- # 检查日志
- print_info "检查启动日志..."
- docker-compose logs --tail=20 xianyu-app
-
- # 测试健康检查
- print_info "测试健康检查..."
- sleep 5
- if curl -f http://localhost:8080/health >/dev/null 2>&1; then
- print_success "健康检查通过"
- else
- print_warning "健康检查失败,但服务可能仍在启动中"
- fi
-else
- print_error "服务启动失败"
- print_info "查看错误日志:"
- docker-compose logs xianyu-app
- exit 1
-fi
-
-echo ""
-print_success "数据库权限修复完成!"
-echo ""
-print_info "服务信息:"
-echo " Web界面: http://localhost:8080"
-echo " 健康检查: http://localhost:8080/health"
-echo " 默认账号: admin / admin123"
-echo ""
-print_info "常用命令:"
-echo " 查看日志: docker-compose logs -f"
-echo " 重启服务: docker-compose restart"
-echo " 停止服务: docker-compose down"
diff --git a/fix-docker-warnings.bat b/fix-docker-warnings.bat
deleted file mode 100644
index 6810784..0000000
--- a/fix-docker-warnings.bat
+++ /dev/null
@@ -1,144 +0,0 @@
-@echo off
-chcp 65001 >nul
-setlocal enabledelayedexpansion
-
-:: 修复Docker部署警告的快速脚本 (Windows版本)
-:: 解决version过时和.env文件缺失问题
-
-title Docker部署警告修复脚本
-
-:: 颜色定义
-set "RED=[91m"
-set "GREEN=[92m"
-set "YELLOW=[93m"
-set "BLUE=[94m"
-set "NC=[0m"
-
-:: 打印带颜色的消息
-:print_info
-echo %BLUE%[INFO]%NC% %~1
-goto :eof
-
-:print_success
-echo %GREEN%[SUCCESS]%NC% %~1
-goto :eof
-
-:print_warning
-echo %YELLOW%[WARNING]%NC% %~1
-goto :eof
-
-:print_error
-echo %RED%[ERROR]%NC% %~1
-goto :eof
-
-echo ========================================
-echo Docker部署警告修复脚本
-echo ========================================
-echo.
-
-:: 1. 检查并创建.env文件
-call :print_info "检查 .env 文件..."
-if not exist ".env" (
- if exist ".env.example" (
- call :print_info "从 .env.example 创建 .env 文件..."
- copy ".env.example" ".env" >nul
- call :print_success ".env 文件已创建"
- ) else (
- call :print_warning ".env.example 文件不存在"
- call :print_info "创建基本的 .env 文件..."
-
- (
- echo # 闲鱼自动回复系统 Docker 环境变量配置文件
- echo.
- echo # 基础配置
- echo TZ=Asia/Shanghai
- echo PYTHONUNBUFFERED=1
- echo LOG_LEVEL=INFO
- echo.
- echo # 数据库配置
- echo DB_PATH=/app/data/xianyu_data.db
- echo.
- echo # 服务配置
- echo WEB_PORT=8080
- echo.
- echo # 安全配置
- echo ADMIN_USERNAME=admin
- echo ADMIN_PASSWORD=admin123
- echo JWT_SECRET_KEY=xianyu-auto-reply-secret-key-2024
- echo.
- echo # 资源限制
- echo MEMORY_LIMIT=512
- echo CPU_LIMIT=0.5
- echo MEMORY_RESERVATION=256
- echo CPU_RESERVATION=0.25
- echo.
- echo # 自动回复配置
- echo AUTO_REPLY_ENABLED=true
- echo WEBSOCKET_URL=wss://wss-goofish.dingtalk.com/
- echo HEARTBEAT_INTERVAL=15
- echo TOKEN_REFRESH_INTERVAL=3600
- ) > .env
-
- call :print_success "基本 .env 文件已创建"
- )
-) else (
- call :print_success ".env 文件已存在"
-)
-
-:: 2. 检查docker-compose.yml版本问题
-call :print_info "检查 docker-compose.yml 配置..."
-findstr /B "version:" docker-compose.yml >nul 2>&1
-if !errorlevel! equ 0 (
- call :print_warning "发现过时的 version 字段"
- call :print_info "移除 version 字段..."
-
- REM 备份原文件
- copy docker-compose.yml docker-compose.yml.backup >nul
-
- REM 创建临时文件,移除version行
- (
- for /f "tokens=*" %%a in (docker-compose.yml) do (
- echo %%a | findstr /B "version:" >nul
- if !errorlevel! neq 0 (
- echo %%a
- )
- )
- ) > docker-compose.yml.tmp
-
- REM 替换原文件
- move docker-compose.yml.tmp docker-compose.yml >nul
-
- call :print_success "已移除过时的 version 字段"
- call :print_info "原文件已备份为 docker-compose.yml.backup"
-) else (
- call :print_success "docker-compose.yml 配置正确"
-)
-
-:: 3. 验证修复结果
-call :print_info "验证修复结果..."
-
-echo.
-call :print_info "测试 Docker Compose 配置..."
-docker-compose config >nul 2>&1
-if !errorlevel! equ 0 (
- call :print_success "Docker Compose 配置验证通过"
-) else (
- call :print_error "Docker Compose 配置验证失败"
- echo 请检查 docker-compose.yml 文件
- pause
- exit /b 1
-)
-
-echo.
-call :print_success "所有警告已修复!"
-echo.
-call :print_info "现在可以正常使用以下命令:"
-echo docker-compose up -d # 启动服务
-echo docker-compose ps # 查看状态
-echo docker-compose logs -f # 查看日志
-echo.
-call :print_info "如果需要恢复原配置:"
-echo move docker-compose.yml.backup docker-compose.yml
-echo.
-
-pause
diff --git a/fix-docker-warnings.sh b/fix-docker-warnings.sh
deleted file mode 100644
index 63d63e5..0000000
--- a/fix-docker-warnings.sh
+++ /dev/null
@@ -1,145 +0,0 @@
-#!/bin/bash
-
-# 修复Docker部署警告的快速脚本
-# 解决version过时和.env文件缺失问题
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m' # No Color
-
-print_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
-}
-
-print_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
-}
-
-print_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-print_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-echo "========================================"
-echo " Docker部署警告修复脚本"
-echo "========================================"
-echo ""
-
-# 1. 检查并创建.env文件
-print_info "检查 .env 文件..."
-if [ ! -f ".env" ]; then
- if [ -f ".env.example" ]; then
- print_info "从 .env.example 创建 .env 文件..."
- cp .env.example .env
- print_success ".env 文件已创建"
- else
- print_warning ".env.example 文件不存在"
- print_info "创建基本的 .env 文件..."
- cat > .env << 'EOF'
-# 闲鱼自动回复系统 Docker 环境变量配置文件
-
-# 基础配置
-TZ=Asia/Shanghai
-PYTHONUNBUFFERED=1
-LOG_LEVEL=INFO
-
-# 数据库配置
-DB_PATH=/app/data/xianyu_data.db
-
-# 服务配置
-WEB_PORT=8080
-
-# 安全配置
-ADMIN_USERNAME=admin
-ADMIN_PASSWORD=admin123
-JWT_SECRET_KEY=xianyu-auto-reply-secret-key-2024
-
-# 资源限制
-MEMORY_LIMIT=512
-CPU_LIMIT=0.5
-MEMORY_RESERVATION=256
-CPU_RESERVATION=0.25
-
-# 自动回复配置
-AUTO_REPLY_ENABLED=true
-WEBSOCKET_URL=wss://wss-goofish.dingtalk.com/
-HEARTBEAT_INTERVAL=15
-TOKEN_REFRESH_INTERVAL=3600
-EOF
- print_success "基本 .env 文件已创建"
- fi
-else
- print_success ".env 文件已存在"
-fi
-
-# 2. 检查docker-compose.yml版本问题
-print_info "检查 docker-compose.yml 配置..."
-if grep -q "^version:" docker-compose.yml 2>/dev/null; then
- print_warning "发现过时的 version 字段"
- print_info "移除 version 字段..."
-
- # 备份原文件
- cp docker-compose.yml docker-compose.yml.backup
-
- # 移除version行
- sed -i '/^version:/d' docker-compose.yml
- sed -i '/^$/N;/^\n$/d' docker-compose.yml # 移除空行
-
- print_success "已移除过时的 version 字段"
- print_info "原文件已备份为 docker-compose.yml.backup"
-else
- print_success "docker-compose.yml 配置正确"
-fi
-
-# 3. 检查env_file配置
-print_info "检查 env_file 配置..."
-if grep -A1 "env_file:" docker-compose.yml | grep -q "required: false"; then
- print_success "env_file 配置正确"
-else
- print_info "更新 env_file 配置为可选..."
-
- # 备份文件(如果还没备份)
- if [ ! -f "docker-compose.yml.backup" ]; then
- cp docker-compose.yml docker-compose.yml.backup
- fi
-
- # 更新env_file配置
- sed -i '/env_file:/,+1c\
- env_file:\
- - path: .env\
- required: false' docker-compose.yml
-
- print_success "env_file 配置已更新"
-fi
-
-# 4. 验证修复结果
-print_info "验证修复结果..."
-
-echo ""
-print_info "测试 Docker Compose 配置..."
-if docker-compose config >/dev/null 2>&1; then
- print_success "Docker Compose 配置验证通过"
-else
- print_error "Docker Compose 配置验证失败"
- echo "请检查 docker-compose.yml 文件"
- exit 1
-fi
-
-echo ""
-print_success "所有警告已修复!"
-echo ""
-print_info "现在可以正常使用以下命令:"
-echo " docker-compose up -d # 启动服务"
-echo " docker-compose ps # 查看状态"
-echo " docker-compose logs -f # 查看日志"
-echo ""
-print_info "如果需要恢复原配置:"
-echo " mv docker-compose.yml.backup docker-compose.yml"
diff --git a/fix-websocket-issue.sh b/fix-websocket-issue.sh
deleted file mode 100644
index d874a86..0000000
--- a/fix-websocket-issue.sh
+++ /dev/null
@@ -1,121 +0,0 @@
-#!/bin/bash
-
-# 快速修复WebSocket兼容性问题
-# 解决 "extra_headers" 参数不支持的问题
-
-set -e
-
-# 颜色定义
-RED='\033[0;31m'
-GREEN='\033[0;32m'
-YELLOW='\033[1;33m'
-BLUE='\033[0;34m'
-NC='\033[0m'
-
-print_info() {
- echo -e "${BLUE}[INFO]${NC} $1"
-}
-
-print_success() {
- echo -e "${GREEN}[SUCCESS]${NC} $1"
-}
-
-print_warning() {
- echo -e "${YELLOW}[WARNING]${NC} $1"
-}
-
-print_error() {
- echo -e "${RED}[ERROR]${NC} $1"
-}
-
-echo "🔧 WebSocket兼容性问题修复"
-echo "================================"
-
-# 1. 检查当前websockets版本
-print_info "检查当前websockets版本..."
-if command -v python3 &> /dev/null; then
- PYTHON_CMD="python3"
-elif command -v python &> /dev/null; then
- PYTHON_CMD="python"
-else
- print_error "未找到Python解释器"
- exit 1
-fi
-
-CURRENT_VERSION=$($PYTHON_CMD -c "import websockets; print(websockets.__version__)" 2>/dev/null || echo "未安装")
-print_info "当前websockets版本: $CURRENT_VERSION"
-
-# 2. 测试WebSocket兼容性
-print_info "测试WebSocket兼容性..."
-$PYTHON_CMD test-websocket-compatibility.py
-
-# 3. 停止现有服务
-print_info "停止现有Docker服务..."
-docker-compose down 2>/dev/null || true
-
-# 4. 更新websockets版本
-print_info "更新websockets版本到兼容版本..."
-if [ -f "requirements.txt" ]; then
- # 备份原文件
- cp requirements.txt requirements.txt.backup
-
- # 更新websockets版本
- sed -i 's/websockets>=.*/websockets>=10.0,<13.0 # 兼容性版本范围/' requirements.txt
-
- print_success "requirements.txt已更新"
-else
- print_warning "requirements.txt文件不存在"
-fi
-
-# 5. 重新构建Docker镜像
-print_info "重新构建Docker镜像..."
-docker-compose build --no-cache
-
-# 6. 启动服务
-print_info "启动服务..."
-docker-compose up -d
-
-# 7. 等待服务启动
-print_info "等待服务启动..."
-sleep 15
-
-# 8. 检查服务状态
-print_info "检查服务状态..."
-if docker-compose ps | grep -q "Up"; then
- print_success "✅ 服务启动成功!"
-
- # 检查WebSocket错误
- print_info "检查WebSocket连接状态..."
- sleep 5
-
- # 查看最近的日志
- echo ""
- print_info "最近的服务日志:"
- docker-compose logs --tail=20 xianyu-app | grep -E "(WebSocket|extra_headers|ERROR)" || echo "未发现WebSocket相关错误"
-
- # 测试健康检查
- if curl -f http://localhost:8080/health >/dev/null 2>&1; then
- print_success "健康检查通过"
- else
- print_warning "健康检查失败,服务可能仍在启动中"
- fi
-
-else
- print_error "❌ 服务启动失败"
- print_info "查看错误日志:"
- docker-compose logs --tail=30 xianyu-app
- exit 1
-fi
-
-echo ""
-print_success "🎉 WebSocket兼容性问题修复完成!"
-echo ""
-print_info "服务信息:"
-echo " Web界面: http://localhost:8080"
-echo " 健康检查: http://localhost:8080/health"
-echo " 默认账号: admin / admin123"
-echo ""
-print_info "如果仍有WebSocket问题,请:"
-echo " 1. 查看日志: docker-compose logs -f xianyu-app"
-echo " 2. 运行测试: python test-websocket-compatibility.py"
-echo " 3. 检查网络连接和防火墙设置"
diff --git a/fix_api_isolation.py b/fix_api_isolation.py
deleted file mode 100644
index ed6bf2f..0000000
--- a/fix_api_isolation.py
+++ /dev/null
@@ -1,334 +0,0 @@
-#!/usr/bin/env python3
-"""
-修复API接口的用户隔离问题
-"""
-
-import re
-import os
-from loguru import logger
-
-def fix_cards_api():
- """修复卡券管理API的用户隔离"""
- logger.info("修复卡券管理API...")
-
- # 读取reply_server.py文件
- with open('reply_server.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 修复获取卡券列表接口
- content = re.sub(
- r'@app\.get\("/cards"\)\ndef get_cards\(_: None = Depends\(require_auth\)\):',
- '@app.get("/cards")\ndef get_cards(current_user: Dict[str, Any] = Depends(get_current_user)):',
- content
- )
-
- # 修复获取卡券列表的实现
- content = re.sub(
- r'cards = db_manager\.get_all_cards\(\)\s+return cards',
- '''# 只返回当前用户的卡券
- user_id = current_user['user_id']
- cards = db_manager.get_all_cards(user_id)
- return cards''',
- content,
- flags=re.MULTILINE
- )
-
- # 修复创建卡券接口
- content = re.sub(
- r'@app\.post\("/cards"\)\ndef create_card\(card_data: dict, _: None = Depends\(require_auth\)\):',
- '@app.post("/cards")\ndef create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_current_user)):',
- content
- )
-
- # 修复其他卡券接口...
- # 这里需要更多的修复代码
-
- # 写回文件
- with open('reply_server.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 卡券管理API修复完成")
-
-def fix_delivery_rules_api():
- """修复自动发货规则API的用户隔离"""
- logger.info("修复自动发货规则API...")
-
- # 读取reply_server.py文件
- with open('reply_server.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 修复获取发货规则列表接口
- content = re.sub(
- r'@app\.get\("/delivery-rules"\)\ndef get_delivery_rules\(_: None = Depends\(require_auth\)\):',
- '@app.get("/delivery-rules")\ndef get_delivery_rules(current_user: Dict[str, Any] = Depends(get_current_user)):',
- content
- )
-
- # 修复其他发货规则接口...
-
- # 写回文件
- with open('reply_server.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 自动发货规则API修复完成")
-
-def fix_notification_channels_api():
- """修复通知渠道API的用户隔离"""
- logger.info("修复通知渠道API...")
-
- # 读取reply_server.py文件
- with open('reply_server.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 修复通知渠道接口...
-
- # 写回文件
- with open('reply_server.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 通知渠道API修复完成")
-
-def update_db_manager():
- """更新db_manager.py中的方法以支持用户隔离"""
- logger.info("更新数据库管理器...")
-
- # 读取db_manager.py文件
- with open('db_manager.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 修复get_all_cards方法
- if 'def get_all_cards(self, user_id: int = None):' not in content:
- content = re.sub(
- r'def get_all_cards\(self\):',
- 'def get_all_cards(self, user_id: int = None):',
- content
- )
-
- # 修复方法实现
- content = re.sub(
- r'SELECT id, name, type, api_config, text_content, data_content,\s+description, enabled, created_at, updated_at\s+FROM cards\s+ORDER BY created_at DESC',
- '''SELECT id, name, type, api_config, text_content, data_content,
- description, enabled, created_at, updated_at
- FROM cards
- WHERE (user_id = ? OR ? IS NULL)
- ORDER BY created_at DESC''',
- content,
- flags=re.MULTILINE
- )
-
- # 写回文件
- with open('db_manager.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 数据库管理器更新完成")
-
-def create_user_settings_api():
- """创建用户设置API"""
- logger.info("创建用户设置API...")
-
- user_settings_api = '''
-
-# ------------------------- 用户设置接口 -------------------------
-
-@app.get('/user-settings')
-def get_user_settings(current_user: Dict[str, Any] = Depends(get_current_user)):
- """获取当前用户的设置"""
- from db_manager import db_manager
- try:
- user_id = current_user['user_id']
- settings = db_manager.get_user_settings(user_id)
- return settings
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-
-@app.put('/user-settings/{key}')
-def update_user_setting(key: str, setting_data: dict, current_user: Dict[str, Any] = Depends(get_current_user)):
- """更新用户设置"""
- from db_manager import db_manager
- try:
- user_id = current_user['user_id']
- value = setting_data.get('value')
- description = setting_data.get('description', '')
-
- success = db_manager.set_user_setting(user_id, key, value, description)
- if success:
- return {'msg': 'setting updated', 'key': key, 'value': value}
- else:
- raise HTTPException(status_code=400, detail='更新失败')
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-
-@app.get('/user-settings/{key}')
-def get_user_setting(key: str, current_user: Dict[str, Any] = Depends(get_current_user)):
- """获取用户特定设置"""
- from db_manager import db_manager
- try:
- user_id = current_user['user_id']
- setting = db_manager.get_user_setting(user_id, key)
- if setting:
- return setting
- else:
- raise HTTPException(status_code=404, detail='设置不存在')
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-'''
-
- # 读取reply_server.py文件
- with open('reply_server.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 在文件末尾添加用户设置API
- if 'user-settings' not in content:
- content += user_settings_api
-
- # 写回文件
- with open('reply_server.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 用户设置API创建完成")
- else:
- logger.info("用户设置API已存在")
-
-def add_user_settings_methods_to_db():
- """为db_manager添加用户设置相关方法"""
- logger.info("为数据库管理器添加用户设置方法...")
-
- user_settings_methods = '''
-
- # ==================== 用户设置管理方法 ====================
-
- def get_user_settings(self, user_id: int):
- """获取用户的所有设置"""
- with self.lock:
- try:
- cursor = self.conn.cursor()
- cursor.execute('''
- SELECT key, value, description, updated_at
- FROM user_settings
- WHERE user_id = ?
- ORDER BY key
- ''', (user_id,))
-
- settings = {}
- for row in cursor.fetchall():
- settings[row[0]] = {
- 'value': row[1],
- 'description': row[2],
- 'updated_at': row[3]
- }
-
- return settings
- except Exception as e:
- logger.error(f"获取用户设置失败: {e}")
- return {}
-
- def get_user_setting(self, user_id: int, key: str):
- """获取用户的特定设置"""
- with self.lock:
- try:
- cursor = self.conn.cursor()
- cursor.execute('''
- SELECT value, description, updated_at
- FROM user_settings
- WHERE user_id = ? AND key = ?
- ''', (user_id, key))
-
- row = cursor.fetchone()
- if row:
- return {
- 'key': key,
- 'value': row[0],
- 'description': row[1],
- 'updated_at': row[2]
- }
- return None
- except Exception as e:
- logger.error(f"获取用户设置失败: {e}")
- return None
-
- def set_user_setting(self, user_id: int, key: str, value: str, description: str = None):
- """设置用户配置"""
- with self.lock:
- try:
- cursor = self.conn.cursor()
- cursor.execute('''
- INSERT OR REPLACE INTO user_settings (user_id, key, value, description, updated_at)
- VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP)
- ''', (user_id, key, value, description))
-
- self.conn.commit()
- logger.info(f"用户设置更新成功: user_id={user_id}, key={key}")
- return True
- except Exception as e:
- logger.error(f"设置用户配置失败: {e}")
- self.conn.rollback()
- return False
-'''
-
- # 读取db_manager.py文件
- with open('db_manager.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 检查是否已经添加了用户设置方法
- if 'def get_user_settings(self, user_id: int):' not in content:
- # 在类的末尾添加方法
- content = content.rstrip() + user_settings_methods
-
- # 写回文件
- with open('db_manager.py', 'w', encoding='utf-8') as f:
- f.write(content)
-
- logger.info("✅ 用户设置方法添加完成")
- else:
- logger.info("用户设置方法已存在")
-
-def main():
- """主函数"""
- print("🔧 修复API接口的用户隔离问题")
- print("=" * 50)
-
- try:
- # 1. 更新数据库管理器
- print("\n📦 1. 更新数据库管理器")
- update_db_manager()
- add_user_settings_methods_to_db()
-
- # 2. 修复卡券管理API
- print("\n🎫 2. 修复卡券管理API")
- fix_cards_api()
-
- # 3. 修复自动发货规则API
- print("\n🚚 3. 修复自动发货规则API")
- fix_delivery_rules_api()
-
- # 4. 修复通知渠道API
- print("\n📢 4. 修复通知渠道API")
- fix_notification_channels_api()
-
- # 5. 创建用户设置API
- print("\n⚙️ 5. 创建用户设置API")
- create_user_settings_api()
-
- print("\n" + "=" * 50)
- print("🎉 API接口修复完成!")
-
- print("\n📋 修复内容:")
- print("✅ 1. 更新数据库管理器方法")
- print("✅ 2. 修复卡券管理API用户隔离")
- print("✅ 3. 修复自动发货规则API用户隔离")
- print("✅ 4. 修复通知渠道API用户隔离")
- print("✅ 5. 创建用户设置API")
-
- print("\n⚠️ 注意:")
- print("1. 部分接口可能需要手动调整")
- print("2. 建议重启服务后进行测试")
- print("3. 检查前端代码是否需要更新")
-
- return True
-
- except Exception as e:
- logger.error(f"修复过程中出现错误: {e}")
- return False
-
-if __name__ == "__main__":
- main()
diff --git a/fix_complete_isolation.py b/fix_complete_isolation.py
deleted file mode 100644
index bf1987e..0000000
--- a/fix_complete_isolation.py
+++ /dev/null
@@ -1,317 +0,0 @@
-#!/usr/bin/env python3
-"""
-完整的多用户数据隔离修复脚本
-"""
-
-import sqlite3
-import json
-import time
-from loguru import logger
-
-def backup_database():
- """备份数据库"""
- try:
- import shutil
- timestamp = time.strftime("%Y%m%d_%H%M%S")
- backup_file = f"xianyu_data_backup_{timestamp}.db"
- shutil.copy2("xianyu_data.db", backup_file)
- logger.info(f"数据库备份完成: {backup_file}")
- return backup_file
- except Exception as e:
- logger.error(f"数据库备份失败: {e}")
- return None
-
-def add_user_id_columns():
- """为相关表添加user_id字段"""
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- try:
- # 检查并添加user_id字段到cards表
- cursor.execute("PRAGMA table_info(cards)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'user_id' not in columns:
- logger.info("为cards表添加user_id字段...")
- cursor.execute('ALTER TABLE cards ADD COLUMN user_id INTEGER REFERENCES users(id)')
- logger.info("✅ cards表user_id字段添加成功")
- else:
- logger.info("cards表已有user_id字段")
-
- # 检查并添加user_id字段到delivery_rules表
- cursor.execute("PRAGMA table_info(delivery_rules)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'user_id' not in columns:
- logger.info("为delivery_rules表添加user_id字段...")
- cursor.execute('ALTER TABLE delivery_rules ADD COLUMN user_id INTEGER REFERENCES users(id)')
- logger.info("✅ delivery_rules表user_id字段添加成功")
- else:
- logger.info("delivery_rules表已有user_id字段")
-
- # 检查并添加user_id字段到notification_channels表
- cursor.execute("PRAGMA table_info(notification_channels)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'user_id' not in columns:
- logger.info("为notification_channels表添加user_id字段...")
- cursor.execute('ALTER TABLE notification_channels ADD COLUMN user_id INTEGER REFERENCES users(id)')
- logger.info("✅ notification_channels表user_id字段添加成功")
- else:
- logger.info("notification_channels表已有user_id字段")
-
- conn.commit()
- return True
-
- except Exception as e:
- logger.error(f"添加user_id字段失败: {e}")
- conn.rollback()
- return False
- finally:
- conn.close()
-
-def create_user_settings_table():
- """创建用户设置表"""
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- try:
- # 检查表是否已存在
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='user_settings'")
- if cursor.fetchone():
- logger.info("user_settings表已存在")
- return True
-
- logger.info("创建user_settings表...")
- cursor.execute('''
- CREATE TABLE user_settings (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER NOT NULL,
- key TEXT NOT NULL,
- value TEXT,
- description TEXT,
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
- UNIQUE(user_id, key)
- )
- ''')
-
- conn.commit()
- logger.info("✅ user_settings表创建成功")
- return True
-
- except Exception as e:
- logger.error(f"创建user_settings表失败: {e}")
- conn.rollback()
- return False
- finally:
- conn.close()
-
-def migrate_existing_data():
- """迁移现有数据到admin用户"""
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- try:
- # 获取admin用户ID
- cursor.execute("SELECT id FROM users WHERE username = 'admin'")
- admin_result = cursor.fetchone()
-
- if not admin_result:
- logger.error("未找到admin用户,请先创建admin用户")
- return False
-
- admin_id = admin_result[0]
- logger.info(f"找到admin用户,ID: {admin_id}")
-
- # 迁移cards表数据
- cursor.execute("SELECT COUNT(*) FROM cards WHERE user_id IS NULL")
- unbound_cards = cursor.fetchone()[0]
-
- if unbound_cards > 0:
- logger.info(f"迁移 {unbound_cards} 个未绑定的卡券到admin用户...")
- cursor.execute("UPDATE cards SET user_id = ? WHERE user_id IS NULL", (admin_id,))
- logger.info("✅ 卡券数据迁移完成")
-
- # 迁移delivery_rules表数据
- cursor.execute("SELECT COUNT(*) FROM delivery_rules WHERE user_id IS NULL")
- unbound_rules = cursor.fetchone()[0]
-
- if unbound_rules > 0:
- logger.info(f"迁移 {unbound_rules} 个未绑定的发货规则到admin用户...")
- cursor.execute("UPDATE delivery_rules SET user_id = ? WHERE user_id IS NULL", (admin_id,))
- logger.info("✅ 发货规则数据迁移完成")
-
- # 迁移notification_channels表数据
- cursor.execute("SELECT COUNT(*) FROM notification_channels WHERE user_id IS NULL")
- unbound_channels = cursor.fetchone()[0]
-
- if unbound_channels > 0:
- logger.info(f"迁移 {unbound_channels} 个未绑定的通知渠道到admin用户...")
- cursor.execute("UPDATE notification_channels SET user_id = ? WHERE user_id IS NULL", (admin_id,))
- logger.info("✅ 通知渠道数据迁移完成")
-
- conn.commit()
- return True
-
- except Exception as e:
- logger.error(f"数据迁移失败: {e}")
- conn.rollback()
- return False
- finally:
- conn.close()
-
-def verify_isolation():
- """验证数据隔离效果"""
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- try:
- logger.info("验证数据隔离效果...")
-
- # 检查各表的用户分布
- tables = ['cards', 'delivery_rules', 'notification_channels']
-
- for table in tables:
- cursor.execute(f'''
- SELECT u.username, COUNT(*) as count
- FROM {table} t
- JOIN users u ON t.user_id = u.id
- GROUP BY u.id, u.username
- ORDER BY count DESC
- ''')
-
- results = cursor.fetchall()
- logger.info(f"📊 {table} 表用户分布:")
- for username, count in results:
- logger.info(f" • {username}: {count} 条记录")
-
- # 检查是否有未绑定的数据
- cursor.execute(f"SELECT COUNT(*) FROM {table} WHERE user_id IS NULL")
- unbound_count = cursor.fetchone()[0]
- if unbound_count > 0:
- logger.warning(f"⚠️ {table} 表还有 {unbound_count} 条未绑定用户的记录")
- else:
- logger.info(f"✅ {table} 表所有记录都已正确绑定用户")
-
- return True
-
- except Exception as e:
- logger.error(f"验证失败: {e}")
- return False
- finally:
- conn.close()
-
-def create_default_user_settings():
- """为现有用户创建默认设置"""
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- try:
- logger.info("为现有用户创建默认设置...")
-
- # 获取所有用户
- cursor.execute("SELECT id, username FROM users")
- users = cursor.fetchall()
-
- default_settings = [
- ('theme_color', '#1890ff', '主题颜色'),
- ('language', 'zh-CN', '界面语言'),
- ('notification_enabled', 'true', '通知开关'),
- ('auto_refresh', 'true', '自动刷新'),
- ]
-
- for user_id, username in users:
- logger.info(f"为用户 {username} 创建默认设置...")
-
- for key, value, description in default_settings:
- # 检查设置是否已存在
- cursor.execute(
- "SELECT id FROM user_settings WHERE user_id = ? AND key = ?",
- (user_id, key)
- )
-
- if not cursor.fetchone():
- cursor.execute('''
- INSERT INTO user_settings (user_id, key, value, description)
- VALUES (?, ?, ?, ?)
- ''', (user_id, key, value, description))
-
- conn.commit()
- logger.info("✅ 默认用户设置创建完成")
- return True
-
- except Exception as e:
- logger.error(f"创建默认用户设置失败: {e}")
- conn.rollback()
- return False
- finally:
- conn.close()
-
-def main():
- """主函数"""
- print("🚀 完整的多用户数据隔离修复")
- print("=" * 60)
-
- # 1. 备份数据库
- print("\n📦 1. 备份数据库")
- backup_file = backup_database()
- if not backup_file:
- print("❌ 数据库备份失败,停止修复")
- return False
-
- # 2. 添加user_id字段
- print("\n🔧 2. 添加用户隔离字段")
- if not add_user_id_columns():
- print("❌ 添加user_id字段失败")
- return False
-
- # 3. 创建用户设置表
- print("\n📋 3. 创建用户设置表")
- if not create_user_settings_table():
- print("❌ 创建用户设置表失败")
- return False
-
- # 4. 迁移现有数据
- print("\n📦 4. 迁移现有数据")
- if not migrate_existing_data():
- print("❌ 数据迁移失败")
- return False
-
- # 5. 创建默认用户设置
- print("\n⚙️ 5. 创建默认用户设置")
- if not create_default_user_settings():
- print("❌ 创建默认用户设置失败")
- return False
-
- # 6. 验证隔离效果
- print("\n🔍 6. 验证数据隔离")
- if not verify_isolation():
- print("❌ 验证失败")
- return False
-
- print("\n" + "=" * 60)
- print("🎉 多用户数据隔离修复完成!")
-
- print("\n📋 修复内容:")
- print("✅ 1. 为cards表添加用户隔离")
- print("✅ 2. 为delivery_rules表添加用户隔离")
- print("✅ 3. 为notification_channels表添加用户隔离")
- print("✅ 4. 创建用户设置表")
- print("✅ 5. 迁移现有数据到admin用户")
- print("✅ 6. 创建默认用户设置")
-
- print("\n⚠️ 下一步:")
- print("1. 重启服务以应用数据库更改")
- print("2. 运行API接口修复脚本")
- print("3. 测试多用户数据隔离")
- print("4. 更新前端代码")
-
- print(f"\n💾 数据库备份文件: {backup_file}")
- print("如有问题,可以使用备份文件恢复数据")
-
- return True
-
-if __name__ == "__main__":
- main()
diff --git a/fix_user_isolation.py b/fix_user_isolation.py
deleted file mode 100644
index f6fef0d..0000000
--- a/fix_user_isolation.py
+++ /dev/null
@@ -1,230 +0,0 @@
-#!/usr/bin/env python3
-"""
-修复多用户数据隔离的脚本
-"""
-
-import re
-
-def fix_user_isolation():
- """修复用户隔离问题"""
- print("🔧 修复多用户数据隔离问题")
- print("=" * 60)
-
- # 需要修复的接口列表
- interfaces_to_fix = [
- # 商品管理
- {
- 'pattern': r'@app\.put\("/items/\{cookie_id\}/\{item_id\}"\)\ndef update_item_detail\(',
- 'description': '更新商品详情接口',
- 'add_user_check': True
- },
- {
- 'pattern': r'@app\.delete\("/items/\{cookie_id\}/\{item_id\}"\)\ndef delete_item_info\(',
- 'description': '删除商品信息接口',
- 'add_user_check': True
- },
- {
- 'pattern': r'@app\.delete\("/items/batch"\)\ndef batch_delete_items\(',
- 'description': '批量删除商品接口',
- 'add_user_check': True
- },
- {
- 'pattern': r'@app\.post\("/items/get-all-from-account"\)\nasync def get_all_items_from_account\(',
- 'description': '从账号获取所有商品接口',
- 'add_user_check': True
- },
- {
- 'pattern': r'@app\.post\("/items/get-by-page"\)\nasync def get_items_by_page\(',
- 'description': '分页获取商品接口',
- 'add_user_check': True
- },
- # 卡券管理
- {
- 'pattern': r'@app\.get\("/cards"\)\ndef get_cards\(',
- 'description': '获取卡券列表接口',
- 'add_user_check': False # 卡券是全局的,不需要用户隔离
- },
- # AI回复设置
- {
- 'pattern': r'@app\.get\("/ai-reply-settings/\{cookie_id\}"\)\ndef get_ai_reply_settings\(',
- 'description': 'AI回复设置接口',
- 'add_user_check': True
- },
- # 消息通知
- {
- 'pattern': r'@app\.get\("/message-notifications/\{cid\}"\)\ndef get_account_notifications\(',
- 'description': '获取账号消息通知接口',
- 'add_user_check': True
- },
- ]
-
- print("📋 需要修复的接口:")
- for i, interface in enumerate(interfaces_to_fix, 1):
- status = "✅ 需要用户检查" if interface['add_user_check'] else "ℹ️ 全局接口"
- print(f" {i}. {interface['description']} - {status}")
-
- print("\n💡 修复建议:")
- print("1. 将 '_: None = Depends(require_auth)' 替换为 'current_user: Dict[str, Any] = Depends(get_current_user)'")
- print("2. 在需要用户检查的接口中添加用户权限验证")
- print("3. 确保只返回当前用户的数据")
-
- print("\n🔍 检查当前状态...")
-
- # 读取reply_server.py文件
- try:
- with open('reply_server.py', 'r', encoding='utf-8') as f:
- content = f.read()
-
- # 统计还有多少接口使用旧的认证方式
- old_auth_count = len(re.findall(r'_: None = Depends\(require_auth\)', content))
- new_auth_count = len(re.findall(r'current_user: Dict\[str, Any\] = Depends\(get_current_user\)', content))
-
- print(f"📊 认证方式统计:")
- print(f" • 旧认证方式 (require_auth): {old_auth_count} 个接口")
- print(f" • 新认证方式 (get_current_user): {new_auth_count} 个接口")
-
- if old_auth_count > 0:
- print(f"\n⚠️ 还有 {old_auth_count} 个接口需要修复")
-
- # 找出具体哪些接口还没修复
- old_auth_interfaces = re.findall(r'@app\.\w+\([^)]+\)\s*\ndef\s+(\w+)\([^)]*_: None = Depends\(require_auth\)', content, re.MULTILINE)
-
- print("📝 未修复的接口:")
- for interface in old_auth_interfaces:
- print(f" • {interface}")
- else:
- print("\n🎉 所有接口都已使用新的认证方式!")
-
- # 检查是否有用户权限验证
- user_check_pattern = r'user_cookies = db_manager\.get_all_cookies\(user_id\)'
- user_check_count = len(re.findall(user_check_pattern, content))
-
- print(f"\n🔒 用户权限检查统计:")
- print(f" • 包含用户权限检查的接口: {user_check_count} 个")
-
- return old_auth_count == 0
-
- except Exception as e:
- print(f"❌ 检查失败: {e}")
- return False
-
-def generate_fix_template():
- """生成修复模板"""
- print("\n📝 修复模板:")
- print("-" * 40)
-
- template = '''
-# 修复前:
-@app.get("/some-endpoint/{cid}")
-def some_function(cid: str, _: None = Depends(require_auth)):
- """接口描述"""
- # 原有逻辑
- pass
-
-# 修复后:
-@app.get("/some-endpoint/{cid}")
-def some_function(cid: str, current_user: Dict[str, Any] = Depends(get_current_user)):
- """接口描述"""
- try:
- # 检查cookie是否属于当前用户
- user_id = current_user['user_id']
- from db_manager import db_manager
- user_cookies = db_manager.get_all_cookies(user_id)
-
- if cid not in user_cookies:
- raise HTTPException(status_code=403, detail="无权限访问该Cookie")
-
- # 原有逻辑
- pass
- except HTTPException:
- raise
- except Exception as e:
- raise HTTPException(status_code=500, detail=str(e))
-'''
-
- print(template)
-
-def check_database_isolation():
- """检查数据库隔离情况"""
- print("\n🗄️ 检查数据库隔离情况")
- print("-" * 40)
-
- try:
- import sqlite3
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- # 检查cookies表是否有user_id字段
- cursor.execute("PRAGMA table_info(cookies)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'user_id' in columns:
- print("✅ cookies表已支持用户隔离")
-
- # 统计用户数据分布
- cursor.execute('''
- SELECT u.username, COUNT(c.id) as cookie_count
- FROM users u
- LEFT JOIN cookies c ON u.id = c.user_id
- GROUP BY u.id, u.username
- ORDER BY cookie_count DESC
- ''')
-
- user_stats = cursor.fetchall()
- print("\n📊 用户数据分布:")
- for username, cookie_count in user_stats:
- print(f" • {username}: {cookie_count} 个cookies")
-
- # 检查未绑定的数据
- cursor.execute("SELECT COUNT(*) FROM cookies WHERE user_id IS NULL")
- unbound_count = cursor.fetchone()[0]
- if unbound_count > 0:
- print(f"\n⚠️ 发现 {unbound_count} 个未绑定用户的cookies")
- else:
- print("\n✅ 所有cookies已正确绑定用户")
- else:
- print("❌ cookies表不支持用户隔离")
-
- conn.close()
-
- except Exception as e:
- print(f"❌ 检查数据库失败: {e}")
-
-def main():
- """主函数"""
- print("🚀 多用户数据隔离修复工具")
- print("=" * 60)
-
- # 检查修复状态
- all_fixed = fix_user_isolation()
-
- # 生成修复模板
- generate_fix_template()
-
- # 检查数据库隔离
- check_database_isolation()
-
- print("\n" + "=" * 60)
- if all_fixed:
- print("🎉 多用户数据隔离检查完成!所有接口都已正确实现用户隔离。")
- else:
- print("⚠️ 还有接口需要修复,请按照模板进行修复。")
-
- print("\n📋 功能模块隔离状态:")
- print("✅ 1. 账号管理 - Cookie相关接口已隔离")
- print("✅ 2. 自动回复 - 关键字和默认回复已隔离")
- print("❓ 3. 商品管理 - 部分接口需要检查")
- print("❓ 4. 卡券管理 - 需要确认是否需要隔离")
- print("❓ 5. 自动发货 - 需要检查")
- print("❓ 6. 通知渠道 - 需要确认隔离策略")
- print("❓ 7. 消息通知 - 需要检查")
- print("❓ 8. 系统设置 - 需要确认哪些需要隔离")
-
- print("\n💡 下一步:")
- print("1. 手动修复剩余的接口")
- print("2. 测试所有功能的用户隔离")
- print("3. 更新前端代码以支持多用户")
- print("4. 编写完整的测试用例")
-
-if __name__ == "__main__":
- main()
diff --git a/gitignore_rules_explanation.md b/gitignore_rules_explanation.md
deleted file mode 100644
index dacf1e4..0000000
--- a/gitignore_rules_explanation.md
+++ /dev/null
@@ -1,172 +0,0 @@
-# .gitignore 规则说明
-
-## 📋 概述
-
-本项目的 `.gitignore` 文件已经过优化,包含了完整的忽略规则,确保敏感文件和不必要的文件不会被提交到版本控制中。
-
-## 🔧 主要修复
-
-### 1. **数据库文件忽略** ✅
-**问题**: 原来缺少 `*.db` 文件的忽略规则
-**解决**: 添加了完整的数据库文件忽略规则
-
-```gitignore
-# Database files
-*.db
-*.sqlite
-*.sqlite3
-db.sqlite3
-```
-
-### 2. **静态资源例外** ✅
-**问题**: `lib/` 规则会忽略 `static/lib/` 中的本地 CDN 资源
-**解决**: 添加例外规则,允许 `static/lib/` 被版本控制
-
-```gitignore
-# Python lib directories (but not static/lib)
-lib/
-!static/lib/
-```
-
-## 📂 完整规则分类
-
-### Python 相关
-```gitignore
-__pycache__
-*.so
-.Python
-build/
-develop-eggs/
-dist/
-downloads/
-eggs/
-.eggs/
-lib/
-lib64/
-parts/
-sdist/
-var/
-wheels/
-MANIFEST
-*.manifest
-*.spec
-__pypackages__/
-.venv
-venv/
-ENV/
-env.bak/
-venv.bak/
-```
-
-### 数据库文件
-```gitignore
-*.db
-*.sqlite
-*.sqlite3
-db.sqlite3
-```
-
-### 日志和缓存
-```gitignore
-*.log
-.cache
-```
-
-### 临时文件
-```gitignore
-*.tmp
-*.temp
-temp/
-tmp/
-```
-
-### 操作系统生成的文件
-```gitignore
-.DS_Store
-.DS_Store?
-._*
-.Spotlight-V100
-.Trashes
-ehthumbs.db
-Thumbs.db
-```
-
-### IDE 和编辑器文件
-```gitignore
-.vscode/
-.idea/
-*.swp
-*.swo
-*~
-```
-
-### 环境配置文件
-```gitignore
-.env
-.env.local
-.env.*.local
-local_settings.py
-```
-
-### Node.js 相关
-```gitignore
-*node_modules/*
-```
-
-### 静态资源例外
-```gitignore
-!static/lib/
-```
-
-## 🎯 特殊说明
-
-### 数据库文件保护
-- **目的**: 防止敏感的用户数据和配置信息被意外提交
-- **影响**: `xianyu_data.db` 等数据库文件不会被 Git 跟踪
-- **好处**: 保护用户隐私,避免数据泄露
-
-### 静态资源管理
-- **目的**: 允许本地 CDN 资源被版本控制,提升中国大陆访问速度
-- **规则**: `lib/` 被忽略,但 `static/lib/` 不被忽略
-- **包含**: Bootstrap CSS/JS、Bootstrap Icons 等本地资源
-
-### 环境配置保护
-- **目的**: 防止敏感的环境变量和配置被提交
-- **影响**: `.env` 文件和本地设置不会被跟踪
-- **好处**: 保护 API 密钥、数据库连接等敏感信息
-
-## 🧪 验证方法
-
-可以运行以下测试脚本验证规则是否正确:
-
-```bash
-# 测试数据库文件忽略
-python test_gitignore_db.py
-
-# 测试静态资源例外
-python test_gitignore.py
-```
-
-## 📊 当前项目状态
-
-### 被忽略的文件
-- `xianyu_data.db` (139,264 bytes) - 主数据库
-- `data/xianyu_data.db` (106,496 bytes) - 数据目录中的数据库
-- 各种临时文件、日志文件、IDE 配置等
-
-### 不被忽略的重要文件
-- `static/lib/` 目录下的所有本地 CDN 资源 (702 KB)
-- 源代码文件 (`.py`, `.html`, `.js` 等)
-- 配置模板文件 (`.yml.example`, `.env.example` 等)
-- 文档文件 (`.md` 等)
-
-## 🎉 优势总结
-
-1. **数据安全**: 数据库文件不会被意外提交,保护用户数据
-2. **配置安全**: 环境变量和敏感配置得到保护
-3. **仓库整洁**: 临时文件、缓存文件等不会污染仓库
-4. **本地资源**: CDN 资源可以正常版本控制,提升访问速度
-5. **跨平台**: 支持 Windows、macOS、Linux 的常见忽略文件
-6. **IDE 友好**: 支持 VSCode、IntelliJ IDEA 等常见 IDE
-
-现在的 `.gitignore` 配置既保证了项目的安全性,又确保了必要文件的正常版本控制!
diff --git a/migrate_to_multiuser.py b/migrate_to_multiuser.py
deleted file mode 100644
index b760084..0000000
--- a/migrate_to_multiuser.py
+++ /dev/null
@@ -1,237 +0,0 @@
-#!/usr/bin/env python3
-"""
-多用户系统迁移脚本
-将历史数据绑定到admin用户,并创建admin用户记录
-"""
-
-import sqlite3
-import hashlib
-import time
-from loguru import logger
-
-def migrate_to_multiuser():
- """迁移到多用户系统"""
- print("🔄 开始迁移到多用户系统...")
- print("=" * 60)
-
- try:
- # 连接数据库
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- print("1️⃣ 检查数据库结构...")
-
- # 检查users表是否存在
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
- users_table_exists = cursor.fetchone() is not None
-
- if not users_table_exists:
- print(" ❌ users表不存在,请先运行主程序初始化数据库")
- return False
-
- print(" ✅ users表已存在")
-
- # 检查是否已有admin用户
- cursor.execute("SELECT id FROM users WHERE username = 'admin'")
- admin_user = cursor.fetchone()
-
- if admin_user:
- admin_user_id = admin_user[0]
- print(f" ✅ admin用户已存在,ID: {admin_user_id}")
- else:
- print("2️⃣ 创建admin用户...")
-
- # 获取当前的admin密码哈希
- cursor.execute("SELECT value FROM system_settings WHERE key = 'admin_password_hash'")
- password_hash_row = cursor.fetchone()
-
- if password_hash_row:
- admin_password_hash = password_hash_row[0]
- else:
- # 如果没有设置密码,使用默认密码 admin123
- admin_password_hash = hashlib.sha256('admin123'.encode()).hexdigest()
- print(" ⚠️ 未找到admin密码,使用默认密码: admin123")
-
- # 创建admin用户
- cursor.execute('''
- INSERT INTO users (username, email, password_hash, is_active, created_at, updated_at)
- VALUES (?, ?, ?, ?, ?, ?)
- ''', ('admin', 'admin@localhost', admin_password_hash, True, time.time(), time.time()))
-
- admin_user_id = cursor.lastrowid
- print(f" ✅ admin用户创建成功,ID: {admin_user_id}")
-
- print("3️⃣ 检查cookies表结构...")
-
- # 检查cookies表是否有user_id字段
- cursor.execute("PRAGMA table_info(cookies)")
- columns = [column[1] for column in cursor.fetchall()]
-
- if 'user_id' not in columns:
- print(" 🔧 添加user_id字段到cookies表...")
- cursor.execute("ALTER TABLE cookies ADD COLUMN user_id INTEGER")
- print(" ✅ user_id字段添加成功")
- else:
- print(" ✅ user_id字段已存在")
-
- print("4️⃣ 迁移历史数据...")
-
- # 统计需要迁移的数据
- cursor.execute("SELECT COUNT(*) FROM cookies WHERE user_id IS NULL")
- cookies_to_migrate = cursor.fetchone()[0]
-
- if cookies_to_migrate > 0:
- print(f" 📊 发现 {cookies_to_migrate} 个cookies需要绑定到admin用户")
-
- # 将所有没有user_id的cookies绑定到admin用户
- cursor.execute("UPDATE cookies SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
-
- print(f" ✅ 已将 {cookies_to_migrate} 个cookies绑定到admin用户")
- else:
- print(" ✅ 所有cookies已正确绑定用户")
-
- print("5️⃣ 验证迁移结果...")
-
- # 统计各用户的数据
- cursor.execute('''
- SELECT u.username, COUNT(c.id) as cookie_count
- FROM users u
- LEFT JOIN cookies c ON u.id = c.user_id
- GROUP BY u.id, u.username
- ''')
-
- user_stats = cursor.fetchall()
- print(" 📊 用户数据统计:")
- for username, cookie_count in user_stats:
- print(f" • {username}: {cookie_count} 个cookies")
-
- # 检查是否还有未绑定的数据
- cursor.execute("SELECT COUNT(*) FROM cookies WHERE user_id IS NULL")
- unbound_cookies = cursor.fetchone()[0]
-
- if unbound_cookies > 0:
- print(f" ⚠️ 仍有 {unbound_cookies} 个cookies未绑定用户")
- else:
- print(" ✅ 所有cookies已正确绑定用户")
-
- # 提交事务
- conn.commit()
- print("\n6️⃣ 迁移完成!")
-
- print("\n" + "=" * 60)
- print("🎉 多用户系统迁移成功!")
- print("\n📋 迁移总结:")
- print(f" • admin用户ID: {admin_user_id}")
- print(f" • 迁移的cookies数量: {cookies_to_migrate}")
- print(f" • 当前用户数量: {len(user_stats)}")
-
- print("\n💡 下一步操作:")
- print(" 1. 重启应用程序")
- print(" 2. 使用admin账号登录管理现有数据")
- print(" 3. 其他用户可以通过注册页面创建新账号")
- print(" 4. 每个用户只能看到自己的数据")
-
- return True
-
- except Exception as e:
- print(f"❌ 迁移失败: {e}")
- if 'conn' in locals():
- conn.rollback()
- return False
- finally:
- if 'conn' in locals():
- conn.close()
-
-def check_migration_status():
- """检查迁移状态"""
- print("\n🔍 检查多用户系统状态...")
- print("=" * 60)
-
- try:
- conn = sqlite3.connect('xianyu_data.db')
- cursor = conn.cursor()
-
- # 检查users表
- cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='users'")
- if not cursor.fetchone():
- print("❌ users表不存在,需要先运行主程序初始化数据库")
- return
-
- # 检查用户数量
- cursor.execute("SELECT COUNT(*) FROM users")
- user_count = cursor.fetchone()[0]
- print(f"👥 用户数量: {user_count}")
-
- # 检查admin用户
- cursor.execute("SELECT id, username, email, is_active FROM users WHERE username = 'admin'")
- admin_user = cursor.fetchone()
- if admin_user:
- print(f"👑 admin用户: ID={admin_user[0]}, 邮箱={admin_user[2]}, 状态={'激活' if admin_user[3] else '禁用'}")
- else:
- print("❌ admin用户不存在")
-
- # 检查cookies表结构
- cursor.execute("PRAGMA table_info(cookies)")
- columns = [column[1] for column in cursor.fetchall()]
- if 'user_id' in columns:
- print("✅ cookies表已支持用户隔离")
-
- # 统计各用户的cookies
- cursor.execute('''
- SELECT u.username, COUNT(c.id) as cookie_count
- FROM users u
- LEFT JOIN cookies c ON u.id = c.user_id
- GROUP BY u.id, u.username
- ORDER BY cookie_count DESC
- ''')
-
- user_stats = cursor.fetchall()
- print("\n📊 用户数据分布:")
- for username, cookie_count in user_stats:
- print(f" • {username}: {cookie_count} 个cookies")
-
- # 检查未绑定的数据
- cursor.execute("SELECT COUNT(*) FROM cookies WHERE user_id IS NULL")
- unbound_count = cursor.fetchone()[0]
- if unbound_count > 0:
- print(f"\n⚠️ 发现 {unbound_count} 个未绑定用户的cookies,建议运行迁移")
- else:
- print("\n✅ 所有数据已正确绑定用户")
- else:
- print("❌ cookies表不支持用户隔离,需要运行迁移")
-
- except Exception as e:
- print(f"❌ 检查失败: {e}")
- finally:
- if 'conn' in locals():
- conn.close()
-
-if __name__ == "__main__":
- import sys
-
- if len(sys.argv) > 1 and sys.argv[1] == 'check':
- check_migration_status()
- else:
- print("🚀 闲鱼自动回复系统 - 多用户迁移工具")
- print("=" * 60)
- print("此工具将帮助您将单用户系统迁移到多用户系统")
- print("主要功能:")
- print("• 创建admin用户账号")
- print("• 将历史数据绑定到admin用户")
- print("• 支持新用户注册和数据隔离")
- print("\n⚠️ 重要提醒:")
- print("• 迁移前请备份数据库文件")
- print("• 迁移过程中请勿操作系统")
- print("• 迁移完成后需要重启应用")
-
- confirm = input("\n是否继续迁移?(y/N): ").strip().lower()
- if confirm in ['y', 'yes']:
- success = migrate_to_multiuser()
- if success:
- print("\n🎊 迁移完成!请重启应用程序。")
- else:
- print("\n💥 迁移失败!请检查错误信息。")
- else:
- print("取消迁移。")
-
- print(f"\n💡 提示: 运行 'python {sys.argv[0]} check' 可以检查迁移状态")
diff --git a/qq-group.png b/qq-group.png
new file mode 100644
index 0000000000000000000000000000000000000000..f351cf10c7a8cca2b076fae39bddb34ef39269fd
GIT binary patch
literal 146989
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