mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-01 12:07:36 +08:00
修改静态文件为本地
This commit is contained in:
parent
5c7a4f5bdf
commit
c09379ddea
103
.env
103
.env
@ -1,103 +0,0 @@
|
||||
# 闲鱼自动回复系统 Docker 环境变量配置文件
|
||||
# 这是默认配置,您可以根据需要修改
|
||||
|
||||
# ================================
|
||||
# 基础配置
|
||||
# ================================
|
||||
|
||||
# 时区设置
|
||||
TZ=Asia/Shanghai
|
||||
|
||||
# Python配置
|
||||
PYTHONUNBUFFERED=1
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
# 日志级别 (DEBUG, INFO, WARNING, ERROR)
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
# ================================
|
||||
# 数据库配置
|
||||
# ================================
|
||||
|
||||
# 数据库文件路径
|
||||
DB_PATH=/app/data/xianyu_data.db
|
||||
|
||||
# ================================
|
||||
# 服务配置
|
||||
# ================================
|
||||
|
||||
# API服务配置
|
||||
API_HOST=0.0.0.0 # 绑定所有网络接口,支持IP访问
|
||||
API_PORT=8080 # Web服务端口
|
||||
|
||||
# Web服务端口 (Docker端口映射)
|
||||
WEB_PORT=8080
|
||||
|
||||
# ================================
|
||||
# 安全配置
|
||||
# ================================
|
||||
|
||||
# 管理员账号密码 (建议修改)
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
|
||||
# JWT密钥 (建议修改为随机字符串)
|
||||
JWT_SECRET_KEY=xianyu-auto-reply-secret-key-2024
|
||||
|
||||
# Session超时时间 (秒)
|
||||
SESSION_TIMEOUT=3600
|
||||
|
||||
# ================================
|
||||
# 闲鱼API配置
|
||||
# ================================
|
||||
|
||||
# WebSocket连接URL
|
||||
WEBSOCKET_URL=wss://wss-goofish.dingtalk.com/
|
||||
|
||||
# 心跳间隔 (秒)
|
||||
HEARTBEAT_INTERVAL=15
|
||||
|
||||
# 心跳超时 (秒)
|
||||
HEARTBEAT_TIMEOUT=5
|
||||
|
||||
# Token刷新间隔 (秒)
|
||||
TOKEN_REFRESH_INTERVAL=3600
|
||||
|
||||
# Token重试间隔 (秒)
|
||||
TOKEN_RETRY_INTERVAL=300
|
||||
|
||||
# 消息过期时间 (毫秒)
|
||||
MESSAGE_EXPIRE_TIME=300000
|
||||
|
||||
# ================================
|
||||
# 自动回复配置
|
||||
# ================================
|
||||
|
||||
# 是否启用自动回复
|
||||
AUTO_REPLY_ENABLED=true
|
||||
|
||||
# ================================
|
||||
# 资源限制
|
||||
# ================================
|
||||
|
||||
# 内存限制 (MB)
|
||||
MEMORY_LIMIT=512
|
||||
|
||||
# CPU限制 (核心数)
|
||||
CPU_LIMIT=0.5
|
||||
|
||||
# 内存预留 (MB)
|
||||
MEMORY_RESERVATION=256
|
||||
|
||||
# CPU预留 (核心数)
|
||||
CPU_RESERVATION=0.25
|
||||
|
||||
# ================================
|
||||
# 开发配置
|
||||
# ================================
|
||||
|
||||
# 开发模式 (true/false)
|
||||
DEBUG=false
|
||||
|
||||
# 热重载 (true/false)
|
||||
RELOAD=false
|
35
.gitignore
vendored
35
.gitignore
vendored
@ -8,7 +8,9 @@ dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
# Python lib directories (but not static/lib)
|
||||
lib/
|
||||
!static/lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
@ -20,6 +22,11 @@ MANIFEST
|
||||
.cache
|
||||
*.log
|
||||
local_settings.py
|
||||
|
||||
# Database files
|
||||
*.db
|
||||
*.sqlite
|
||||
*.sqlite3
|
||||
db.sqlite3
|
||||
__pypackages__/
|
||||
.venv
|
||||
@ -28,4 +35,30 @@ venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
*.db
|
||||
|
||||
# Temporary files
|
||||
*.tmp
|
||||
*.temp
|
||||
temp/
|
||||
tmp/
|
||||
|
||||
# OS generated files
|
||||
.DS_Store
|
||||
.DS_Store?
|
||||
._*
|
||||
.Spotlight-V100
|
||||
.Trashes
|
||||
ehthumbs.db
|
||||
Thumbs.db
|
||||
|
||||
# IDE files
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# Local environment files
|
||||
.env
|
||||
.env.local
|
||||
.env.*.local
|
@ -113,16 +113,14 @@ AI分析消息内容,识别用户意图:
|
||||
|
||||
## 📊 优先级说明
|
||||
|
||||
系统回复优先级为:
|
||||
当账号启用AI回复后,系统回复优先级为:
|
||||
|
||||
1. **🔥 API回复** (最高优先级)
|
||||
2. **📝 关键词匹配** (优先匹配精确关键词)
|
||||
3. **🤖 AI回复** (AI启用时的智能回复)
|
||||
2. **🤖 AI回复** (AI启用时)
|
||||
3. **📝 关键词匹配** (AI禁用时)
|
||||
4. **💬 默认回复** (最低优先级)
|
||||
|
||||
> ✨ **优化**: 关键词回复优先于AI回复,确保重要关键词能够精确匹配!
|
||||
>
|
||||
> 💡 **说明**: 即使启用AI回复,关键词匹配仍然有效,只有在没有匹配的关键词时才会使用AI回复。
|
||||
> ⚠️ **重要**: 启用AI回复后,关键词匹配和默认回复将自动失效!
|
||||
|
||||
## 🧪 功能测试
|
||||
|
||||
|
178
CHANGELOG.md
178
CHANGELOG.md
@ -1,178 +0,0 @@
|
||||
# 📋 更新日志
|
||||
|
||||
本文档记录了闲鱼自动回复管理系统的所有重要更新和变更。
|
||||
|
||||
## [v2.0.2] - 2024-07-24
|
||||
|
||||
### 🔧 功能改进
|
||||
- **日志系统优化**:完善统一日志记录系统
|
||||
- 所有日志统一记录到文件中,界面读取日志文件显示
|
||||
- 添加智能日志过滤器,过滤掉不必要的API请求日志
|
||||
- 优化日志格式,便于解析和展示
|
||||
- 提高日志收集的实时性和准确性
|
||||
- 过滤掉频繁的健康检查、静态资源请求等日志
|
||||
|
||||
### 🚀 性能优化
|
||||
- **日志性能提升**:减少不必要的日志记录,提高系统性能
|
||||
- **文件监控优化**:优化日志文件监控频率,更及时地收集日志
|
||||
- **内存使用优化**:智能过滤减少内存中的日志数量
|
||||
|
||||
### 📚 文档更新
|
||||
- 新增日志过滤器说明文档
|
||||
- 更新日志管理功能说明
|
||||
|
||||
## [v2.0.1] - 2024-07-24
|
||||
|
||||
### 🔧 功能改进
|
||||
- **回复优先级优化**:调整自动回复逻辑,关键词回复优先于AI回复
|
||||
- 新的优先级顺序:API回复 → 关键词回复 → AI回复 → 默认回复
|
||||
- 确保重要关键词能够精确匹配,AI回复作为智能补充
|
||||
- 提高回复的准确性和用户体验
|
||||
|
||||
### 📚 文档更新
|
||||
- 更新AI回复功能指南中的优先级说明
|
||||
- 更新使用说明中的回复逻辑描述
|
||||
- 更新README.md中的功能特性说明
|
||||
|
||||
## [v2.0.0] - 2024-07-24
|
||||
|
||||
### 🎉 重大更新
|
||||
- **AI智能回复系统**:集成多种AI模型,支持智能对话和议价
|
||||
- **商品管理功能**:自动收集和管理商品信息
|
||||
- **实时日志系统**:完整的日志收集、查看和分析功能
|
||||
- **Docker容器化**:完整的Docker部署支持
|
||||
|
||||
### ✨ 新增功能
|
||||
- **多AI模型支持**:支持通义千问、GPT等主流AI模型
|
||||
- **智能意图识别**:自动识别价格咨询、技术问题、通用咨询
|
||||
- **智能议价系统**:阶梯式降价策略,可设置最大优惠幅度
|
||||
- **商品信息自动收集**:消息触发时自动提取并保存商品信息
|
||||
- **批量商品获取**:一键获取账号下所有商品信息
|
||||
- **实时日志查看**:Web界面实时查看系统运行日志
|
||||
- **日志过滤和搜索**:支持按级别、来源、关键词过滤日志
|
||||
- **系统健康监控**:完整的系统健康检查和监控
|
||||
- **数据备份恢复**:支持完整的数据备份和恢复功能
|
||||
|
||||
### 🔧 功能改进
|
||||
- **用户界面优化**:现代化的Web界面设计
|
||||
- **性能优化**:异步处理,提高系统并发能力
|
||||
- **安全增强**:JWT认证,数据加密存储
|
||||
- **错误处理**:完善的错误处理和重试机制
|
||||
- **配置管理**:灵活的配置文件管理系统
|
||||
|
||||
### 🐛 问题修复
|
||||
- 修复WebSocket连接不稳定的问题
|
||||
- 修复Cookie失效时的自动刷新机制
|
||||
- 修复数据库并发访问的问题
|
||||
- 修复文件上传的安全问题
|
||||
- 修复日志文件过大的问题
|
||||
|
||||
### 📚 文档更新
|
||||
- 完整的README.md文档
|
||||
- 详细的功能使用说明
|
||||
- Docker部署指南
|
||||
- API接口文档
|
||||
- 故障排除指南
|
||||
|
||||
## [v1.5.0] - 2024-06-15
|
||||
|
||||
### ✨ 新增功能
|
||||
- **自动发货增强**:支持API接口类型卡券
|
||||
- **批量数据管理**:支持批量导入和管理卡券
|
||||
- **发货规则优化**:更智能的商品匹配算法
|
||||
- **消息通知格式化**:美化消息通知格式
|
||||
|
||||
### 🔧 功能改进
|
||||
- 优化自动发货匹配逻辑
|
||||
- 改进卡券管理界面
|
||||
- 增强错误处理机制
|
||||
- 提升系统稳定性
|
||||
|
||||
### 🐛 问题修复
|
||||
- 修复发货规则匹配不准确的问题
|
||||
- 修复批量数据消耗过快的问题
|
||||
- 修复API接口调用失败的问题
|
||||
|
||||
## [v1.4.0] - 2024-05-20
|
||||
|
||||
### ✨ 新增功能
|
||||
- **多账号管理**:支持同时管理多个闲鱼账号
|
||||
- **关键词回复**:每个账号独立的关键词回复设置
|
||||
- **用户认证系统**:安全的登录认证机制
|
||||
- **数据持久化**:SQLite数据库存储
|
||||
|
||||
### 🔧 功能改进
|
||||
- 重构代码架构,提高可维护性
|
||||
- 优化数据库设计
|
||||
- 改进用户界面体验
|
||||
- 增强系统安全性
|
||||
|
||||
## [v1.3.0] - 2024-04-25
|
||||
|
||||
### ✨ 新增功能
|
||||
- **自动发货功能**:支持自动发送卡券和虚拟商品
|
||||
- **发货规则配置**:灵活的发货规则设置
|
||||
- **卡券管理**:支持多种卡券类型管理
|
||||
|
||||
### 🔧 功能改进
|
||||
- 优化消息处理逻辑
|
||||
- 改进WebSocket连接稳定性
|
||||
- 增强日志记录功能
|
||||
|
||||
## [v1.2.0] - 2024-03-30
|
||||
|
||||
### ✨ 新增功能
|
||||
- **Web管理界面**:现代化的Web管理界面
|
||||
- **实时状态监控**:实时查看系统运行状态
|
||||
- **配置文件管理**:可视化配置文件编辑
|
||||
|
||||
### 🔧 功能改进
|
||||
- 优化自动回复逻辑
|
||||
- 改进错误处理机制
|
||||
- 增强系统稳定性
|
||||
|
||||
## [v1.1.0] - 2024-02-28
|
||||
|
||||
### ✨ 新增功能
|
||||
- **关键词匹配**:支持关键词自动回复
|
||||
- **变量替换**:支持消息中的变量替换
|
||||
- **日志系统**:完整的日志记录功能
|
||||
|
||||
### 🔧 功能改进
|
||||
- 优化WebSocket连接处理
|
||||
- 改进消息解析逻辑
|
||||
- 增强系统容错能力
|
||||
|
||||
## [v1.0.0] - 2024-01-15
|
||||
|
||||
### 🎉 首次发布
|
||||
- **基础自动回复**:支持闲鱼消息自动回复
|
||||
- **WebSocket连接**:稳定的WebSocket连接机制
|
||||
- **Token管理**:自动Token刷新和管理
|
||||
- **消息处理**:完整的消息接收和处理流程
|
||||
|
||||
---
|
||||
|
||||
## 📝 版本说明
|
||||
|
||||
### 版本号规则
|
||||
- **主版本号**:重大功能更新或架构变更
|
||||
- **次版本号**:新功能添加或重要改进
|
||||
- **修订版本号**:问题修复和小幅改进
|
||||
|
||||
### 更新类型说明
|
||||
- 🎉 **重大更新**:重要的新功能或架构变更
|
||||
- ✨ **新增功能**:新增的功能特性
|
||||
- 🔧 **功能改进**:现有功能的优化和改进
|
||||
- 🐛 **问题修复**:Bug修复和问题解决
|
||||
- 📚 **文档更新**:文档和说明的更新
|
||||
- 🔒 **安全更新**:安全相关的更新和修复
|
||||
|
||||
### 兼容性说明
|
||||
- **向后兼容**:新版本保持与旧版本的兼容性
|
||||
- **配置迁移**:提供配置文件自动迁移功能
|
||||
- **数据迁移**:提供数据库自动升级功能
|
||||
|
||||
---
|
||||
|
||||
**注意**:建议在升级前备份重要数据,详细的升级指南请参考相关文档。
|
220
CONTRIBUTING.md
220
CONTRIBUTING.md
@ -1,220 +0,0 @@
|
||||
# 🤝 贡献指南
|
||||
|
||||
感谢您对闲鱼自动回复管理系统的关注!我们欢迎任何形式的贡献,包括但不限于代码、文档、问题反馈和功能建议。
|
||||
|
||||
## 📋 贡献方式
|
||||
|
||||
### 🐛 报告问题
|
||||
如果您发现了bug或有改进建议,请:
|
||||
1. 检查 [Issues](https://github.com/your-repo/xianyu-auto-reply/issues) 确认问题未被报告
|
||||
2. 创建新的Issue,详细描述问题
|
||||
3. 提供复现步骤和环境信息
|
||||
4. 如果可能,提供错误日志和截图
|
||||
|
||||
### 💡 功能建议
|
||||
如果您有新功能的想法:
|
||||
1. 在Issues中创建功能请求
|
||||
2. 详细描述功能需求和使用场景
|
||||
3. 说明功能的预期效果
|
||||
4. 讨论实现方案的可行性
|
||||
|
||||
### 🔧 代码贡献
|
||||
我们欢迎代码贡献,请遵循以下流程:
|
||||
|
||||
#### 开发环境搭建
|
||||
1. **Fork项目**到您的GitHub账号
|
||||
2. **克隆项目**到本地:
|
||||
```bash
|
||||
git clone https://github.com/your-username/xianyu-auto-reply.git
|
||||
cd xianyu-auto-reply
|
||||
```
|
||||
3. **创建虚拟环境**:
|
||||
```bash
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/Mac
|
||||
# 或
|
||||
venv\Scripts\activate # Windows
|
||||
```
|
||||
4. **安装依赖**:
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
5. **运行测试**确保环境正常:
|
||||
```bash
|
||||
python Start.py
|
||||
```
|
||||
|
||||
#### 开发流程
|
||||
1. **创建分支**:
|
||||
```bash
|
||||
git checkout -b feature/your-feature-name
|
||||
```
|
||||
2. **编写代码**,遵循项目的代码规范
|
||||
3. **编写测试**,确保新功能有相应的测试用例
|
||||
4. **运行测试**,确保所有测试通过
|
||||
5. **提交代码**:
|
||||
```bash
|
||||
git add .
|
||||
git commit -m "feat: 添加新功能描述"
|
||||
```
|
||||
6. **推送分支**:
|
||||
```bash
|
||||
git push origin feature/your-feature-name
|
||||
```
|
||||
7. **创建Pull Request**
|
||||
|
||||
## 📝 代码规范
|
||||
|
||||
### Python代码规范
|
||||
- 遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 代码风格
|
||||
- 使用有意义的变量和函数名
|
||||
- 添加必要的注释和文档字符串
|
||||
- 保持函数简洁,单一职责原则
|
||||
|
||||
### 提交信息规范
|
||||
使用 [Conventional Commits](https://www.conventionalcommits.org/) 规范:
|
||||
|
||||
```
|
||||
<type>[optional scope]: <description>
|
||||
|
||||
[optional body]
|
||||
|
||||
[optional footer(s)]
|
||||
```
|
||||
|
||||
#### 提交类型
|
||||
- `feat`: 新功能
|
||||
- `fix`: 问题修复
|
||||
- `docs`: 文档更新
|
||||
- `style`: 代码格式调整
|
||||
- `refactor`: 代码重构
|
||||
- `test`: 测试相关
|
||||
- `chore`: 构建过程或辅助工具的变动
|
||||
|
||||
#### 示例
|
||||
```
|
||||
feat(ai): 添加智能议价功能
|
||||
|
||||
- 实现阶梯式降价策略
|
||||
- 支持最大优惠限制
|
||||
- 添加议价轮数统计
|
||||
|
||||
Closes #123
|
||||
```
|
||||
|
||||
### 文档规范
|
||||
- 使用Markdown格式
|
||||
- 保持文档结构清晰
|
||||
- 添加必要的代码示例
|
||||
- 及时更新相关文档
|
||||
|
||||
## 🧪 测试指南
|
||||
|
||||
### 运行测试
|
||||
```bash
|
||||
# 运行所有测试
|
||||
python -m pytest
|
||||
|
||||
# 运行特定测试文件
|
||||
python -m pytest tests/test_ai_reply.py
|
||||
|
||||
# 运行带覆盖率的测试
|
||||
python -m pytest --cov=.
|
||||
```
|
||||
|
||||
### 编写测试
|
||||
- 为新功能编写单元测试
|
||||
- 确保测试覆盖率不低于80%
|
||||
- 使用有意义的测试名称
|
||||
- 测试边界条件和异常情况
|
||||
|
||||
### 测试示例
|
||||
```python
|
||||
def test_ai_reply_with_valid_input():
|
||||
"""测试AI回复功能的正常输入"""
|
||||
# 准备测试数据
|
||||
message = "这个商品能便宜点吗?"
|
||||
|
||||
# 执行测试
|
||||
result = ai_reply_engine.process_message(message)
|
||||
|
||||
# 验证结果
|
||||
assert result is not None
|
||||
assert "优惠" in result
|
||||
```
|
||||
|
||||
## 📚 文档贡献
|
||||
|
||||
### 文档类型
|
||||
- **用户文档**:使用说明、配置指南
|
||||
- **开发文档**:API文档、架构说明
|
||||
- **部署文档**:安装部署指南
|
||||
|
||||
### 文档更新
|
||||
- 新功能需要更新相关文档
|
||||
- 修复文档中的错误和过时信息
|
||||
- 改进文档的可读性和准确性
|
||||
|
||||
## 🔍 代码审查
|
||||
|
||||
### 审查标准
|
||||
- **功能正确性**:代码是否实现了预期功能
|
||||
- **代码质量**:是否遵循代码规范
|
||||
- **性能考虑**:是否有性能问题
|
||||
- **安全性**:是否存在安全隐患
|
||||
- **测试覆盖**:是否有足够的测试
|
||||
|
||||
### 审查流程
|
||||
1. 提交Pull Request
|
||||
2. 自动化测试运行
|
||||
3. 代码审查和讨论
|
||||
4. 修改和完善
|
||||
5. 合并到主分支
|
||||
|
||||
## 🎯 贡献建议
|
||||
|
||||
### 适合新手的任务
|
||||
- 修复文档中的错误
|
||||
- 改进错误信息和提示
|
||||
- 添加单元测试
|
||||
- 优化用户界面
|
||||
|
||||
### 高级贡献
|
||||
- 新功能开发
|
||||
- 性能优化
|
||||
- 架构改进
|
||||
- 安全增强
|
||||
|
||||
## 📞 联系方式
|
||||
|
||||
### 获取帮助
|
||||
- **GitHub Issues**:报告问题和讨论
|
||||
- **GitHub Discussions**:一般性讨论和问答
|
||||
- **Email**:紧急问题联系
|
||||
|
||||
### 社区参与
|
||||
- 参与Issue讨论
|
||||
- 帮助其他用户解决问题
|
||||
- 分享使用经验和技巧
|
||||
- 推广项目
|
||||
|
||||
## 🏆 贡献者认可
|
||||
|
||||
### 贡献者列表
|
||||
我们会在项目中维护贡献者列表,感谢每一位贡献者的付出。
|
||||
|
||||
### 贡献统计
|
||||
- 代码贡献
|
||||
- 文档贡献
|
||||
- 问题报告
|
||||
- 功能建议
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
通过贡献代码,您同意您的贡献将在 [MIT License](LICENSE) 下发布。
|
||||
|
||||
---
|
||||
|
||||
**再次感谢您的贡献!** 🙏
|
||||
|
||||
每一个贡献都让这个项目变得更好,无论大小,我们都非常感激。让我们一起构建一个更好的闲鱼自动回复管理系统!
|
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2024 Xianyu Auto Reply System
|
||||
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
|
||||
|
599
README.md
599
README.md
@ -1,510 +1,173 @@
|
||||
# 🚀 闲鱼自动回复管理系统
|
||||
# 🐟 XianYuAutoDeliveryX - 闲鱼虚拟商品商自动发货&聊天对接大模型
|
||||
|
||||
<div align="center">
|
||||
[](https://www.python.org/)
|
||||
[](LICENSE)
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
**✨ 基于闲鱼API的自动发货系统,支持虚拟商品商品聊天窗口自动发货、消息自动回复等功能。**
|
||||
**⚠️ 注意:本项目仅供学习交流使用,请勿用于商业用途。**
|
||||
|
||||
**一个功能强大的闲鱼自动回复管理系统,支持多账号管理、AI智能回复、自动发货等功能**
|
||||
## 🌟 核心特性
|
||||
|
||||
[功能特性](#-功能特性) • [快速开始](#-快速开始) • [部署方式](#-部署方式) • [使用文档](#-使用文档) • [API文档](#-api文档) • [贡献指南](#-贡献指南)
|
||||
- 🔐 **用户认证系统** - 安全的登录认证,保护管理界面
|
||||
- 👥 **多账号管理** - 支持同时管理多个闲鱼账号
|
||||
- 🎯 **智能关键词回复** - 每个账号独立的关键词回复设置
|
||||
- 💾 **数据持久化** - SQLite数据库存储账号和关键词数据
|
||||
- 🌐 **美观Web界面** - 响应式设计,操作简单直观
|
||||
- 📡 **API接口** - 完整的RESTful API支持
|
||||
- 🔄 **实时消息处理** - 基于WebSocket的实时消息监控
|
||||
- 📊 **订单状态监控** - 实时跟踪订单状态变化
|
||||
- 📝 **完善的日志系统** - 详细的操作日志记录
|
||||
|
||||
</div>
|
||||
## 🛠️ 快速开始
|
||||
|
||||
## 📋 项目概述
|
||||
|
||||
闲鱼自动回复管理系统是一个基于 Python + FastAPI 开发的自动化客服系统,专为闲鱼平台设计。系统通过 WebSocket 连接闲鱼服务器,实时接收和处理消息,提供智能化的自动回复服务。
|
||||
|
||||
### 🎯 核心优势
|
||||
|
||||
- **🤖 AI智能回复**:集成多种AI模型,支持意图识别和智能议价
|
||||
- **🔄 多账号管理**:同时管理多个闲鱼账号,独立配置和监控
|
||||
- **📦 商品管理**:自动收集和管理商品信息,支持批量操作
|
||||
- **🚚 自动发货**:智能匹配发货规则,自动发送卡券信息
|
||||
- **📊 实时监控**:完整的日志系统和状态监控
|
||||
- **🐳 容器化部署**:支持Docker一键部署,简化运维
|
||||
|
||||
## ✨ 功能特性
|
||||
|
||||
### 🤖 AI智能回复
|
||||
- **多模型支持**:支持通义千问、GPT等主流AI模型
|
||||
- **意图识别**:自动识别价格咨询、技术问题、通用咨询
|
||||
- **智能议价**:阶梯式降价策略,可设置最大优惠幅度
|
||||
- **上下文感知**:记住完整对话历史,提供连贯回复
|
||||
- **自定义提示词**:支持针对不同场景自定义AI提示词
|
||||
- **智能优先级**:关键词回复优先,AI回复作为智能补充
|
||||
|
||||
### 👥 多账号管理
|
||||
- **账号隔离**:每个账号独立配置和管理
|
||||
- **批量操作**:支持批量添加、删除、配置账号
|
||||
- **状态监控**:实时监控账号连接状态和消息处理情况
|
||||
- **权限控制**:基于JWT的安全认证系统
|
||||
|
||||
### 📦 商品管理
|
||||
- **自动收集**:消息触发时自动收集商品信息
|
||||
- **详情获取**:通过API获取完整商品详情
|
||||
- **批量管理**:支持查看、编辑、删除商品信息
|
||||
- **智能匹配**:基于商品信息进行关键词匹配
|
||||
|
||||
### 🚚 自动发货
|
||||
- **规则配置**:灵活的发货规则配置系统
|
||||
- **卡券管理**:支持多种卡券类型和批量导入
|
||||
- **智能匹配**:根据商品信息自动匹配发货规则
|
||||
- **发货记录**:完整的发货历史记录和统计
|
||||
|
||||
### 📊 监控与日志
|
||||
- **实时日志**:多级别日志记录和实时查看
|
||||
- **性能监控**:系统资源使用情况监控
|
||||
- **消息统计**:消息处理统计和分析
|
||||
- **健康检查**:完整的系统健康检查机制
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
### 后端技术
|
||||
- **Python 3.11+**:主要开发语言
|
||||
- **FastAPI**:现代化的Web框架
|
||||
- **SQLite**:轻量级数据库
|
||||
- **WebSocket**:实时通信
|
||||
- **AsyncIO**:异步编程
|
||||
|
||||
### 前端技术
|
||||
- **HTML5 + CSS3**:现代化界面设计
|
||||
- **JavaScript (ES6+)**:交互逻辑
|
||||
- **Bootstrap**:响应式布局
|
||||
- **Chart.js**:数据可视化
|
||||
|
||||
### 部署技术
|
||||
- **Docker**:容器化部署
|
||||
- **Docker Compose**:多容器编排
|
||||
- **Nginx**:反向代理和负载均衡
|
||||
- **SSL/TLS**:安全传输
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
> 💡 **推荐使用Docker镜像部署,无需配置环境,一键启动!**
|
||||
|
||||
### 🐳 方式一:Docker 镜像部署(推荐)
|
||||
|
||||
**使用预构建的Docker镜像,一键启动:**
|
||||
### ⛳ 运行环境
|
||||
- Python 3.7+
|
||||
|
||||
### 🎯 安装依赖
|
||||
```bash
|
||||
docker run -d -p 8080:8080 --name xianyu-auto-reply --privileged=true registry.cn-shanghai.aliyuncs.com/zhinian-software/xianyu-auto-reply:1.0
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
**特点:**
|
||||
- ✅ **零配置**:无需安装Python环境和依赖
|
||||
- ✅ **即开即用**:一条命令启动完整系统
|
||||
- ✅ **稳定可靠**:经过测试的稳定版本
|
||||
- ✅ **自动更新**:支持数据持久化和版本升级
|
||||
### 🎨 配置说明
|
||||
1. 在 `global_config.yml` 中配置基本参数
|
||||
2. 系统支持多账号管理,可通过Web界面添加多个闲鱼账号Cookie
|
||||
|
||||
**访问系统:**
|
||||
- 🌐 Web界面:http://localhost:8080
|
||||
- 👤 默认账号:admin / admin123
|
||||
- 📖 API文档:http://localhost:8080/docs
|
||||
|
||||
**常用管理命令:**
|
||||
### 🚀 运行项目
|
||||
```bash
|
||||
# 查看容器状态
|
||||
docker ps
|
||||
|
||||
# 查看容器日志
|
||||
docker logs xianyu-auto-reply
|
||||
|
||||
# 重启容器
|
||||
docker restart xianyu-auto-reply
|
||||
|
||||
# 停止容器
|
||||
docker stop xianyu-auto-reply
|
||||
|
||||
# 删除容器
|
||||
docker rm xianyu-auto-reply
|
||||
python Start.py
|
||||
```
|
||||
|
||||
### 方式二:Docker 源码部署
|
||||
### 🔐 登录系统
|
||||
1. 启动后访问 `http://localhost:8080`
|
||||
2. 默认登录账号:
|
||||
- 用户名:`admin`
|
||||
- 密码:`admin123`
|
||||
3. 登录后可进入管理界面进行操作
|
||||
|
||||
1. **克隆项目**
|
||||
```bash
|
||||
git clone https://github.com/your-repo/xianyu-auto-reply.git
|
||||
cd xianyu-auto-reply
|
||||
```
|
||||
|
||||
2. **一键部署**
|
||||
```bash
|
||||
./deploy.sh
|
||||
```
|
||||
|
||||
3. **访问系统**
|
||||
- 打开浏览器访问:http://localhost:8080
|
||||
- 默认账号:admin / admin123
|
||||
|
||||
### 方式三:本地部署
|
||||
|
||||
1. **安装依赖**
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
2. **启动系统**
|
||||
```bash
|
||||
python Start.py
|
||||
```
|
||||
|
||||
3. **访问系统**
|
||||
- 打开浏览器访问:http://localhost:8080
|
||||
|
||||
## 📚 使用文档
|
||||
|
||||
### 基础使用
|
||||
1. **[使用说明](./使用说明.md)** - 系统基础使用指南
|
||||
2. **[Docker部署说明](./Docker部署说明.md)** - Docker部署详细说明
|
||||
|
||||
### 功能文档
|
||||
1. **[AI回复功能指南](./AI_REPLY_GUIDE.md)** - AI回复功能详细说明
|
||||
2. **[商品管理功能说明](./商品管理功能说明.md)** - 商品管理功能使用
|
||||
3. **[自动发货功能说明](./自动发货功能说明.md)** - 自动发货配置和使用
|
||||
4. **[日志管理功能说明](./日志管理功能说明.md)** - 日志查看和管理
|
||||
5. **[获取所有商品功能说明](./获取所有商品功能说明.md)** - 商品批量获取功能
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
### 环境变量配置
|
||||
```bash
|
||||
# 基础配置
|
||||
TZ=Asia/Shanghai
|
||||
WEB_PORT=8080
|
||||
|
||||
# 管理员账号
|
||||
ADMIN_USERNAME=admin
|
||||
ADMIN_PASSWORD=admin123
|
||||
|
||||
# AI回复配置
|
||||
AI_REPLY_ENABLED=true
|
||||
DEFAULT_AI_MODEL=qwen-plus
|
||||
DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
|
||||
## 📁 项目结构
|
||||
```
|
||||
├── 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依赖
|
||||
```
|
||||
|
||||
### 全局配置文件
|
||||
主要配置文件为 `global_config.yml`,包含:
|
||||
- API端点配置
|
||||
- WebSocket连接配置
|
||||
- 自动回复配置
|
||||
- 日志配置
|
||||
- 商品详情获取配置
|
||||
## 🎯 主要功能
|
||||
|
||||
## 📡 API文档
|
||||
### 1. 用户认证系统
|
||||
- 安全的登录认证机制
|
||||
- Session token管理
|
||||
- 自动登录状态检查
|
||||
- 登出功能
|
||||
|
||||
### 认证接口
|
||||
- `POST /login` - 用户登录
|
||||
- `POST /logout` - 用户登出
|
||||
### 2. 多账号管理
|
||||
- 支持添加多个闲鱼账号
|
||||
- 每个账号独立管理
|
||||
- Cookie安全存储
|
||||
- 账号状态监控
|
||||
|
||||
### 账号管理
|
||||
- `GET /cookies` - 获取所有账号
|
||||
- `POST /cookies` - 添加新账号
|
||||
- `DELETE /cookies/{cookie_id}` - 删除账号
|
||||
### 3. 智能关键词回复
|
||||
- 每个账号独立的关键词设置
|
||||
- 支持变量替换:`{send_user_name}`, `{send_user_id}`, `{send_message}`
|
||||
- 实时关键词匹配
|
||||
- 默认回复机制
|
||||
|
||||
### AI回复管理
|
||||
- `GET /ai-reply/{cookie_id}` - 获取AI配置
|
||||
- `POST /ai-reply/{cookie_id}` - 保存AI配置
|
||||
- `POST /ai-reply/{cookie_id}/test` - 测试AI回复
|
||||
### 4. Web管理界面
|
||||
- 响应式设计,支持移动端
|
||||
- 直观的操作界面
|
||||
- 实时数据更新
|
||||
- 操作反馈提示
|
||||
|
||||
### 商品管理
|
||||
- `GET /items` - 获取所有商品
|
||||
- `GET /items/cookie/{cookie_id}` - 获取指定账号商品
|
||||
- `PUT /items/{cookie_id}/{item_id}` - 更新商品详情
|
||||
## 🔌 API 接口说明
|
||||
|
||||
### 系统监控
|
||||
- `GET /health` - 健康检查
|
||||
- `GET /logs` - 获取系统日志
|
||||
- `GET /logs/stats` - 日志统计信息
|
||||
### 智能回复接口
|
||||
`POST http://localhost:8080/xianyu/reply`
|
||||
|
||||
完整API文档访问:http://localhost:8080/docs
|
||||
#### 接口说明
|
||||
你需要实现这个接口,本项目会调用这个接口获取自动回复的内容并发送给客户
|
||||
不实现这个接口也没关系,系统会默认回复,你也可以配置默认回复的内容
|
||||
用于处理闲鱼消息的自动回复,支持对接大语言模型进行智能回复。
|
||||
|
||||
## 🏗️ 项目结构
|
||||
|
||||
```
|
||||
xianyu-auto-reply/
|
||||
├── Start.py # 项目启动入口
|
||||
├── XianyuAutoAsync.py # 闲鱼WebSocket客户端
|
||||
├── reply_server.py # FastAPI Web服务器
|
||||
├── config.py # 配置管理
|
||||
├── cookie_manager.py # Cookie账号管理
|
||||
├── db_manager.py # 数据库管理
|
||||
├── ai_reply_engine.py # AI回复引擎
|
||||
├── file_log_collector.py # 文件日志收集器
|
||||
├── bargain_demo.py # 议价功能演示
|
||||
├── global_config.yml # 全局配置文件
|
||||
├── requirements.txt # Python依赖
|
||||
├── Dockerfile # Docker镜像构建
|
||||
├── docker-compose.yml # Docker编排配置
|
||||
├── deploy.sh # 部署脚本
|
||||
├── static/ # 前端静态文件
|
||||
│ ├── index.html # 主页面
|
||||
│ ├── login.html # 登录页面
|
||||
│ └── xianyu_js_version_2.js # 前端逻辑
|
||||
├── utils/ # 工具模块
|
||||
│ ├── xianyu_utils.py # 闲鱼工具函数
|
||||
│ ├── message_utils.py # 消息处理工具
|
||||
│ └── ws_utils.py # WebSocket工具
|
||||
├── nginx/ # Nginx配置
|
||||
├── docs/ # 文档目录
|
||||
└── backups/ # 备份目录
|
||||
**通过这个接口可以检测到用户是否已付款,然后回复虚拟资料内容即可**
|
||||
#### 请求参数
|
||||
```json
|
||||
{
|
||||
"msg_time": "消息时间",
|
||||
"user_url": "用户主页URL",
|
||||
"send_user_id": "发送者ID",
|
||||
"send_user_name": "发送者昵称",
|
||||
"item_id": "商品ID",
|
||||
"send_message": "发送的消息内容",
|
||||
"chat_id": "会话ID"
|
||||
}
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
|
||||
我们欢迎任何形式的贡献!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详细的贡献指南。
|
||||
|
||||
### 快速开始贡献
|
||||
1. **Fork** 项目到你的GitHub账号
|
||||
2. **克隆**项目到本地开发环境
|
||||
3. **创建分支**进行功能开发或问题修复
|
||||
4. **提交代码**并创建Pull Request
|
||||
|
||||
### 贡献类型
|
||||
- 🐛 **问题报告**:发现bug或提出改进建议
|
||||
- 💡 **功能建议**:提出新功能想法
|
||||
- 🔧 **代码贡献**:修复问题或开发新功能
|
||||
- 📚 **文档改进**:完善文档和说明
|
||||
- 🧪 **测试用例**:添加或改进测试
|
||||
|
||||
详细信息请参考:[贡献指南](CONTRIBUTING.md)
|
||||
|
||||
## 💬 社区交流
|
||||
|
||||
### 加入我们的交流群
|
||||
|
||||
<div align="center">
|
||||
|
||||
| 微信交流群 | QQ交流群 |
|
||||
|:---:|:---:|
|
||||
| <img src="images/wechat-group.jpg" width="200" alt="微信群二维码"> | <img src="images/qq-group.jpg" width="200" alt="QQ群二维码"> |
|
||||
| 扫码加入微信群 | 扫码加入QQ群 |
|
||||
|
||||
</div>
|
||||
|
||||
### 交流内容
|
||||
- 💡 **功能讨论**:新功能建议和讨论
|
||||
- 🐛 **问题求助**:使用过程中遇到的问题
|
||||
- 📚 **经验分享**:使用技巧和最佳实践
|
||||
- 🔄 **版本更新**:最新版本发布通知
|
||||
- 🤝 **合作交流**:开发合作和技术交流
|
||||
|
||||
### 群规说明
|
||||
- 请保持友善和尊重的交流氛围
|
||||
- 优先使用搜索功能查找已有答案
|
||||
- 提问时请提供详细的问题描述和环境信息
|
||||
- 欢迎分享使用经验和改进建议
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
本项目采用 MIT 许可证,详情请查看 [LICENSE](LICENSE) 文件。
|
||||
|
||||
## 📋 更新日志
|
||||
|
||||
查看 [CHANGELOG.md](CHANGELOG.md) 了解详细的版本更新历史。
|
||||
|
||||
## 🙏 致谢
|
||||
|
||||
感谢所有为这个项目做出贡献的开发者和用户!
|
||||
|
||||
### 特别感谢
|
||||
- 所有提交代码的贡献者
|
||||
- 提供问题反馈的用户
|
||||
- 完善文档的志愿者
|
||||
- 推广项目的支持者
|
||||
|
||||
### 技术支持
|
||||
- [FastAPI](https://fastapi.tiangolo.com/) - 现代化的Web框架
|
||||
- [SQLite](https://www.sqlite.org/) - 轻量级数据库
|
||||
- [Docker](https://www.docker.com/) - 容器化技术
|
||||
- [Loguru](https://github.com/Delgan/loguru) - 优秀的日志库
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**如果这个项目对你有帮助,请给个 ⭐ Star 支持一下!**
|
||||
|
||||
[📋 报告问题](https://github.com/your-repo/xianyu-auto-reply/issues) • [💡 功能建议](https://github.com/your-repo/xianyu-auto-reply/issues) • [🤝 参与贡献](https://github.com/your-repo/xianyu-auto-reply/pulls) • [📖 查看文档](https://github.com/your-repo/xianyu-auto-reply/wiki)
|
||||
|
||||
**让我们一起构建更好的闲鱼自动化工具!** 🚀
|
||||
|
||||
</div>
|
||||
|
||||
## 🔧 核心模块说明
|
||||
|
||||
### Start.py - 项目启动入口
|
||||
- 初始化文件日志收集器
|
||||
- 创建和管理 CookieManager
|
||||
- 启动 FastAPI Web服务器
|
||||
- 加载配置文件和环境变量中的账号
|
||||
|
||||
### XianyuAutoAsync.py - 闲鱼WebSocket客户端
|
||||
- 维持与闲鱼服务器的WebSocket连接
|
||||
- 处理消息接收和发送
|
||||
- 自动刷新token和维持心跳
|
||||
- 商品信息获取和处理
|
||||
- 自动回复逻辑处理
|
||||
|
||||
### reply_server.py - FastAPI Web服务器
|
||||
- 提供Web管理界面
|
||||
- RESTful API接口
|
||||
- 用户认证和权限控制
|
||||
- 文件上传和下载
|
||||
- 健康检查和监控
|
||||
|
||||
### ai_reply_engine.py - AI回复引擎
|
||||
- 多AI模型支持(通义千问、GPT等)
|
||||
- 意图识别和分类
|
||||
- 智能议价逻辑
|
||||
- 对话历史管理
|
||||
- 自定义提示词处理
|
||||
|
||||
### db_manager.py - 数据库管理
|
||||
- SQLite数据库操作
|
||||
- 数据表结构管理
|
||||
- 数据备份和恢复
|
||||
- 事务处理和连接池
|
||||
|
||||
## 🎮 使用场景
|
||||
|
||||
### 个人卖家
|
||||
- **自动客服**:24小时自动回复买家咨询
|
||||
- **智能议价**:自动处理价格谈判,提高成交率
|
||||
- **快速发货**:自动发送卡券和虚拟商品
|
||||
|
||||
### 商家店铺
|
||||
- **多账号管理**:统一管理多个闲鱼账号
|
||||
- **批量操作**:批量设置关键词和回复规则
|
||||
- **数据分析**:查看消息统计和销售数据
|
||||
|
||||
### 代运营服务
|
||||
- **客户隔离**:为不同客户提供独立的账号管理
|
||||
- **定制化配置**:根据客户需求定制回复策略
|
||||
- **监控报告**:提供详细的运营数据报告
|
||||
|
||||
## 🔒 安全特性
|
||||
|
||||
### 数据安全
|
||||
- **本地存储**:所有数据存储在本地,不上传到第三方
|
||||
- **加密传输**:支持HTTPS和WSS加密传输
|
||||
- **权限控制**:基于JWT的用户认证系统
|
||||
|
||||
### 隐私保护
|
||||
- **Cookie加密**:敏感信息加密存储
|
||||
- **日志脱敏**:自动过滤敏感信息
|
||||
- **访问控制**:IP白名单和访问频率限制
|
||||
|
||||
## 📈 性能优化
|
||||
|
||||
### 系统性能
|
||||
- **异步处理**:全异步架构,高并发处理能力
|
||||
- **连接池**:数据库连接池,提高数据库操作效率
|
||||
- **缓存机制**:智能缓存,减少重复请求
|
||||
|
||||
### 资源优化
|
||||
- **内存管理**:自动清理过期数据和缓存
|
||||
- **日志轮转**:自动清理过期日志文件
|
||||
- **资源限制**:Docker资源限制,防止资源滥用
|
||||
|
||||
## 🚨 故障排除
|
||||
|
||||
### 常见问题
|
||||
|
||||
**Q: 系统启动失败?**
|
||||
A: 检查以下项目:
|
||||
- Python版本是否为3.11+
|
||||
- 依赖包是否正确安装
|
||||
- 端口8080是否被占用
|
||||
- 配置文件是否存在
|
||||
|
||||
**Q: WebSocket连接失败?**
|
||||
A: 检查以下方面:
|
||||
- 网络连接是否正常
|
||||
- Cookie是否有效
|
||||
- 防火墙设置
|
||||
- 代理配置
|
||||
|
||||
**Q: AI回复不工作?**
|
||||
A: 检查以下配置:
|
||||
- AI API密钥是否正确
|
||||
- API地址是否可访问
|
||||
- 账户余额是否充足
|
||||
- 网络连接是否稳定
|
||||
|
||||
### 日志查看
|
||||
```bash
|
||||
# 查看实时日志
|
||||
docker-compose logs -f
|
||||
|
||||
# 查看特定服务日志
|
||||
docker-compose logs xianyu-app
|
||||
|
||||
# 查看系统日志文件
|
||||
tail -f logs/xianyu_$(date +%Y-%m-%d).log
|
||||
#### 响应格式
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"data": {
|
||||
"send_msg": "回复的消息内容"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🤝 贡献指南
|
||||
#### 配置示例
|
||||
```yaml
|
||||
AUTO_REPLY:
|
||||
api:
|
||||
enabled: true # 是否启用API回复
|
||||
timeout: 10 # 超时时间(秒)
|
||||
url: http://localhost:8080/xianyu/reply
|
||||
```
|
||||
|
||||
我们欢迎任何形式的贡献!请查看 [CONTRIBUTING.md](CONTRIBUTING.md) 了解详细的贡献指南。
|
||||
#### 使用场景
|
||||
- 当收到买家消息时,系统会自动调用此接口
|
||||
- 支持接入 ChatGPT、文心一言等大语言模型
|
||||
- 支持自定义回复规则和模板
|
||||
- 支持消息变量替换(如 `{send_user_name}`)
|
||||
|
||||
### 快速开始贡献
|
||||
1. **Fork** 项目到你的GitHub账号
|
||||
2. **克隆**项目到本地开发环境
|
||||
3. **创建分支**进行功能开发或问题修复
|
||||
4. **提交代码**并创建Pull Request
|
||||
#### 注意事项
|
||||
- 接口需要返回正确的状态码(200)和消息内容
|
||||
- 建议实现错误重试机制
|
||||
- 注意处理超时情况(默认10秒)
|
||||
- 可以根据需要扩展更多的参数和功能
|
||||
|
||||
### 贡献类型
|
||||
- 🐛 **问题报告**:发现bug或提出改进建议
|
||||
- 💡 **功能建议**:提出新功能想法
|
||||
- 🔧 **代码贡献**:修复问题或开发新功能
|
||||
- 📚 **文档改进**:完善文档和说明
|
||||
- 🧪 **测试用例**:添加或改进测试
|
||||
## 🗝️ 注意事项
|
||||
- 请确保闲鱼账号已登录并获取有效的 Cookie
|
||||
- 建议在正式环境使用前先在测试环境验证
|
||||
- 定期检查日志文件,及时处理异常情况
|
||||
- 使用大模型时注意 API 调用频率和成本控制
|
||||
|
||||
### 开发规范
|
||||
- 遵循 [PEP 8](https://www.python.org/dev/peps/pep-0008/) 代码风格
|
||||
- 使用 [Conventional Commits](https://www.conventionalcommits.org/) 提交规范
|
||||
- 为新功能添加相应的测试用例
|
||||
- 更新相关文档和说明
|
||||
## 📝 效果
|
||||
|
||||
详细信息请参考:[贡献指南](CONTRIBUTING.md)
|
||||
|
||||
## 📄 许可证
|
||||

|
||||
|
||||
本项目采用 MIT 许可证,详情请查看 [LICENSE](LICENSE) 文件。
|
||||

|
||||
|
||||
## <EFBFBD> 更新日志
|
||||
## 🧸特别鸣谢
|
||||
|
||||
查看 [CHANGELOG.md](CHANGELOG.md) 了解详细的版本更新历史。
|
||||
本项目参考了以下开源项目: https://github.com/cv-cat/XianYuApis
|
||||
|
||||
## <20>🙏 致谢
|
||||
感谢[@CVcat](https://github.com/cv-cat)的技术支持
|
||||
|
||||
感谢所有为这个项目做出贡献的开发者和用户!
|
||||
## 📞 联系方式
|
||||
如有问题或建议,欢迎提交 Issue 或 Pull Request。
|
||||
|
||||
### 特别感谢
|
||||
- 所有提交代码的贡献者
|
||||
- 提供问题反馈的用户
|
||||
- 完善文档的志愿者
|
||||
- 推广项目的支持者
|
||||
## 技术交流
|
||||
|
||||
### 技术支持
|
||||
- [FastAPI](https://fastapi.tiangolo.com/) - 现代化的Web框架
|
||||
- [SQLite](https://www.sqlite.org/) - 轻量级数据库
|
||||
- [Docker](https://www.docker.com/) - 容器化技术
|
||||
- [Loguru](https://github.com/Delgan/loguru) - 优秀的日志库
|
||||
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
|
||||
**如果这个项目对你有帮助,请给个 ⭐ Star 支持一下!**
|
||||
|
||||
[📋 报告问题](https://github.com/your-repo/xianyu-auto-reply/issues) • [💡 功能建议](https://github.com/your-repo/xianyu-auto-reply/issues) • [🤝 参与贡献](https://github.com/your-repo/xianyu-auto-reply/pulls) • [📖 查看文档](https://github.com/your-repo/xianyu-auto-reply/wiki)
|
||||
|
||||
**让我们一起构建更好的闲鱼自动化工具!** 🚀
|
||||
|
||||
</div>
|
||||

|
||||
|
31
Start.py
31
Start.py
@ -9,7 +9,6 @@ import os
|
||||
import asyncio
|
||||
import threading
|
||||
import uvicorn
|
||||
import time
|
||||
from urllib.parse import urlparse
|
||||
from pathlib import Path
|
||||
from loguru import logger
|
||||
@ -19,36 +18,6 @@ import cookie_manager as cm
|
||||
from db_manager import db_manager
|
||||
from file_log_collector import setup_file_logging
|
||||
|
||||
# 配置统一的日志系统
|
||||
log_dir = 'logs'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_path = os.path.join(log_dir, f"xianyu_{time.strftime('%Y-%m-%d')}.log")
|
||||
|
||||
# 移除默认的日志处理器
|
||||
logger.remove()
|
||||
|
||||
# 导入日志过滤器
|
||||
try:
|
||||
from log_filter import filter_log_record
|
||||
except ImportError:
|
||||
# 如果过滤器不可用,使用默认过滤器
|
||||
def filter_log_record(record):
|
||||
return True
|
||||
|
||||
# 添加文件日志处理器,使用统一格式,并应用过滤器
|
||||
logger.add(
|
||||
log_path,
|
||||
rotation="1 day",
|
||||
retention="7 days",
|
||||
compression="zip",
|
||||
level="INFO",
|
||||
format='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}',
|
||||
encoding='utf-8',
|
||||
enqueue=False, # 立即写入
|
||||
buffering=1, # 行缓冲
|
||||
filter=filter_log_record # 应用日志过滤器
|
||||
)
|
||||
|
||||
|
||||
def _start_api_server():
|
||||
"""后台线程启动 FastAPI 服务"""
|
||||
|
92
UI_IMPROVEMENTS.md
Normal file
92
UI_IMPROVEMENTS.md
Normal file
@ -0,0 +1,92 @@
|
||||
# 🎨 界面优化总结
|
||||
|
||||
## ✨ 主要改进
|
||||
|
||||
### 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不再隐藏**,便于查看和调试
|
||||
- ✅ **现代化设计**,视觉效果更佳
|
||||
- ✅ **功能增强**,操作更便利
|
||||
- ✅ **响应式优化**,支持各种设备
|
||||
|
||||
界面现在更加美观、实用和用户友好!🎨✨
|
@ -20,34 +20,26 @@ from utils.ws_utils import WebSocketClient
|
||||
import sys
|
||||
import aiohttp
|
||||
|
||||
# 日志配置 - 统一日志文件
|
||||
# 日志配置
|
||||
log_dir = 'logs'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_path = os.path.join(log_dir, f"xianyu_{time.strftime('%Y-%m-%d')}.log")
|
||||
|
||||
# 移除所有现有的日志处理器
|
||||
logger.remove()
|
||||
|
||||
# 导入日志过滤器
|
||||
try:
|
||||
from log_filter import filter_log_record
|
||||
except ImportError:
|
||||
# 如果过滤器不可用,使用默认过滤器
|
||||
def filter_log_record(record):
|
||||
return True
|
||||
|
||||
# 只添加文件日志处理器,使用统一格式便于解析,并应用过滤器
|
||||
logger.add(
|
||||
log_path,
|
||||
rotation=LOG_CONFIG.get('rotation', '1 day'),
|
||||
retention=LOG_CONFIG.get('retention', '7 days'),
|
||||
compression=LOG_CONFIG.get('compression', 'zip'),
|
||||
level=LOG_CONFIG.get('level', 'INFO'),
|
||||
format='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}',
|
||||
format=LOG_CONFIG.get('format', '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>'),
|
||||
encoding='utf-8',
|
||||
enqueue=False, # 改为False,确保立即写入
|
||||
buffering=1, # 行缓冲,立即刷新到文件
|
||||
filter=filter_log_record # 应用日志过滤器
|
||||
enqueue=True
|
||||
)
|
||||
logger.add(
|
||||
sys.stdout,
|
||||
level=LOG_CONFIG.get('level', 'INFO'),
|
||||
format=LOG_CONFIG.get('format', '<green>{time:YYYY-MM-DD HH:mm:ss.SSS}</green> | <level>{level: <8}</level> | <cyan>{name}</cyan>:<cyan>{function}</cyan>:<cyan>{line}</cyan> - <level>{message}</level>'),
|
||||
enqueue=True
|
||||
)
|
||||
|
||||
class XianyuLive:
|
||||
@ -205,10 +197,14 @@ class XianyuLive:
|
||||
return new_token
|
||||
|
||||
logger.error(f"Token刷新失败: {res_json}")
|
||||
# 发送Token刷新失败通知
|
||||
await self.send_token_refresh_notification(f"Token刷新失败: {res_json}")
|
||||
return None
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Token刷新异常: {self._safe_str(e)}")
|
||||
# 发送Token刷新异常通知
|
||||
await self.send_token_refresh_notification(f"Token刷新异常: {str(e)}")
|
||||
return None
|
||||
|
||||
async def update_config_cookies(self):
|
||||
@ -223,11 +219,17 @@ class XianyuLive:
|
||||
logger.debug(f"已更新Cookie到数据库: {self.cookie_id}")
|
||||
except Exception as e:
|
||||
logger.error(f"更新数据库Cookie失败: {self._safe_str(e)}")
|
||||
# 发送数据库更新失败通知
|
||||
await self.send_token_refresh_notification(f"数据库Cookie更新失败: {str(e)}")
|
||||
else:
|
||||
logger.warning("Cookie ID不存在,无法更新数据库")
|
||||
# 发送Cookie ID缺失通知
|
||||
await self.send_token_refresh_notification("Cookie ID不存在,无法更新数据库")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"更新Cookie失败: {self._safe_str(e)}")
|
||||
# 发送Cookie更新失败通知
|
||||
await self.send_token_refresh_notification(f"Cookie更新失败: {str(e)}")
|
||||
|
||||
async def save_item_info_to_db(self, item_id: str, item_detail: str = None):
|
||||
"""保存商品信息到数据库
|
||||
@ -711,11 +713,11 @@ class XianyuLive:
|
||||
send_message=send_message
|
||||
)
|
||||
logger.info(f"使用默认回复: {formatted_reply}")
|
||||
return f"[默认回复] {formatted_reply}"
|
||||
return formatted_reply
|
||||
except Exception as format_error:
|
||||
logger.error(f"默认回复变量替换失败: {self._safe_str(format_error)}")
|
||||
# 如果变量替换失败,返回原始内容
|
||||
return f"[默认回复] {reply_content}"
|
||||
return reply_content
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取默认回复失败: {self._safe_str(e)}")
|
||||
@ -744,11 +746,11 @@ class XianyuLive:
|
||||
send_message=send_message
|
||||
)
|
||||
logger.info(f"关键词匹配成功: '{keyword}' -> {formatted_reply}")
|
||||
return f"[关键词回复] {formatted_reply}"
|
||||
return formatted_reply
|
||||
except Exception as format_error:
|
||||
logger.error(f"关键词回复变量替换失败: {self._safe_str(format_error)}")
|
||||
# 如果变量替换失败,返回原始内容
|
||||
return f"[关键词回复] {reply}"
|
||||
return reply
|
||||
|
||||
logger.debug(f"未找到匹配的关键词: {send_message}")
|
||||
return None
|
||||
@ -799,7 +801,7 @@ class XianyuLive:
|
||||
|
||||
if reply:
|
||||
logger.info(f"AI回复生成成功: {reply}")
|
||||
return f"[AI回复] {reply}"
|
||||
return reply
|
||||
else:
|
||||
logger.debug(f"AI回复生成失败")
|
||||
return None
|
||||
@ -893,6 +895,49 @@ class XianyuLive:
|
||||
except Exception as e:
|
||||
logger.error(f"发送QQ通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def send_token_refresh_notification(self, error_message: str):
|
||||
"""发送Token刷新异常通知"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
|
||||
# 获取当前账号的通知配置
|
||||
notifications = db_manager.get_account_notifications(self.cookie_id)
|
||||
|
||||
if not notifications:
|
||||
logger.debug("未配置消息通知,跳过Token刷新通知")
|
||||
return
|
||||
|
||||
# 构造通知消息
|
||||
notification_msg = f"""🔴 闲鱼账号Token刷新异常
|
||||
|
||||
账号ID: {self.cookie_id}
|
||||
异常时间: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}
|
||||
异常信息: {error_message}
|
||||
|
||||
请检查账号Cookie是否过期,如有需要请及时更新Cookie配置。"""
|
||||
|
||||
logger.info(f"准备发送Token刷新异常通知: {self.cookie_id}")
|
||||
|
||||
# 发送通知到各个渠道
|
||||
for notification in notifications:
|
||||
if not notification.get('enabled', True):
|
||||
continue
|
||||
|
||||
channel_type = notification.get('channel_type')
|
||||
channel_config = notification.get('channel_config')
|
||||
|
||||
try:
|
||||
if channel_type == 'qq':
|
||||
await self._send_qq_notification(channel_config, notification_msg)
|
||||
else:
|
||||
logger.warning(f"不支持的通知渠道类型: {channel_type}")
|
||||
|
||||
except Exception as notify_error:
|
||||
logger.error(f"发送Token刷新通知失败 ({notification.get('channel_name', 'Unknown')}): {self._safe_str(notify_error)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理Token刷新通知失败: {self._safe_str(e)}")
|
||||
|
||||
async def send_delivery_failure_notification(self, send_user_name: str, send_user_id: str, item_id: str, error_message: str):
|
||||
"""发送自动发货失败通知"""
|
||||
try:
|
||||
@ -1188,6 +1233,8 @@ class XianyuLive:
|
||||
break
|
||||
else:
|
||||
logger.error("Token刷新失败,将在{}分钟后重试".format(self.token_retry_interval // 60))
|
||||
# 发送Token刷新失败通知
|
||||
await self.send_token_refresh_notification("Token定时刷新失败,将自动重试")
|
||||
await asyncio.sleep(self.token_retry_interval)
|
||||
continue
|
||||
await asyncio.sleep(60)
|
||||
@ -1272,6 +1319,8 @@ class XianyuLive:
|
||||
|
||||
if not self.current_token:
|
||||
logger.error("无法获取有效token,初始化失败")
|
||||
# 发送Token获取失败通知
|
||||
await self.send_token_refresh_notification("初始化时无法获取有效Token")
|
||||
raise Exception("Token获取失败")
|
||||
|
||||
msg = {
|
||||
@ -1826,19 +1875,19 @@ class XianyuLive:
|
||||
# 记录回复来源
|
||||
reply_source = 'API' # 默认假设是API回复
|
||||
|
||||
# 如果API回复失败或未启用API,按优先级尝试其他回复方式
|
||||
# 如果API回复失败或未启用API,按新的优先级顺序处理
|
||||
if not reply:
|
||||
# 优先尝试关键词匹配回复
|
||||
# 1. 首先尝试关键词匹配
|
||||
reply = await self.get_keyword_reply(send_user_name, send_user_id, send_message)
|
||||
if reply:
|
||||
reply_source = '关键词' # 标记为关键词回复
|
||||
else:
|
||||
# 如果关键词匹配失败,尝试AI回复
|
||||
# 2. 关键词匹配失败,如果AI开关打开,尝试AI回复
|
||||
reply = await self.get_ai_reply(send_user_name, send_user_id, send_message, item_id, chat_id)
|
||||
if reply:
|
||||
reply_source = 'AI' # 标记为AI回复
|
||||
else:
|
||||
# 最后尝试使用默认回复
|
||||
# 3. 最后使用默认回复
|
||||
reply = await self.get_default_reply(send_user_name, send_user_id, send_message)
|
||||
reply_source = '默认' # 标记为默认回复
|
||||
|
||||
@ -1908,8 +1957,14 @@ class XianyuLive:
|
||||
finally:
|
||||
await self.close_session() # 确保关闭session
|
||||
|
||||
async def get_item_list_info(self, retry_count=0):
|
||||
"""获取商品信息,自动处理token失效的情况"""
|
||||
async def get_item_list_info(self, page_number=1, page_size=20, retry_count=0):
|
||||
"""获取商品信息,自动处理token失效的情况
|
||||
|
||||
Args:
|
||||
page_number (int): 页码,从1开始
|
||||
page_size (int): 每页数量,默认20
|
||||
retry_count (int): 重试次数,内部使用
|
||||
"""
|
||||
if retry_count >= 3: # 最多重试3次
|
||||
logger.error("获取商品信息失败,重试次数过多")
|
||||
return {"error": "获取商品信息失败,重试次数过多"}
|
||||
@ -1952,8 +2007,8 @@ class XianyuLive:
|
||||
|
||||
data = {
|
||||
'needGroupInfo': False,
|
||||
'pageNumber': 1,
|
||||
'pageSize': 20,
|
||||
'pageNumber': page_number,
|
||||
'pageSize': page_size,
|
||||
'groupName': '在售',
|
||||
'groupId': '58877261',
|
||||
'defaultGroup': True,
|
||||
@ -2017,7 +2072,7 @@ class XianyuLive:
|
||||
|
||||
# 打印商品详细信息到控制台
|
||||
print("\n" + "="*80)
|
||||
print(f"📦 账号 {self.myid} 的商品列表 ({len(items_list)} 个商品)")
|
||||
print(f"📦 账号 {self.myid} 的商品列表 (第{page_number}页,{len(items_list)} 个商品)")
|
||||
print("="*80)
|
||||
|
||||
for i, item in enumerate(items_list, 1):
|
||||
@ -2046,7 +2101,9 @@ class XianyuLive:
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"total_count": len(items_list),
|
||||
"page_number": page_number,
|
||||
"page_size": page_size,
|
||||
"current_count": len(items_list),
|
||||
"items": items_list,
|
||||
"saved_count": saved_count if items_list else 0,
|
||||
"raw_data": items_data # 保留原始数据以备调试
|
||||
@ -2057,7 +2114,7 @@ class XianyuLive:
|
||||
if 'FAIL_SYS_TOKEN_EXOIRED' in error_msg or 'token' in error_msg.lower():
|
||||
logger.warning(f"Token失效,准备重试: {error_msg}")
|
||||
await asyncio.sleep(0.5)
|
||||
return await self.get_item_list_info(retry_count + 1)
|
||||
return await self.get_item_list_info(page_number, page_size, retry_count + 1)
|
||||
else:
|
||||
logger.error(f"获取商品信息失败: {res_json}")
|
||||
return {"error": f"获取商品信息失败: {error_msg}"}
|
||||
@ -2065,7 +2122,65 @@ class XianyuLive:
|
||||
except Exception as e:
|
||||
logger.error(f"商品信息API请求异常: {self._safe_str(e)}")
|
||||
await asyncio.sleep(0.5)
|
||||
return await self.get_item_list_info(retry_count + 1)
|
||||
return await self.get_item_list_info(page_number, page_size, retry_count + 1)
|
||||
|
||||
async def get_all_items(self, page_size=20, max_pages=None):
|
||||
"""获取所有商品信息(自动分页)
|
||||
|
||||
Args:
|
||||
page_size (int): 每页数量,默认20
|
||||
max_pages (int): 最大页数限制,None表示无限制
|
||||
|
||||
Returns:
|
||||
dict: 包含所有商品信息的字典
|
||||
"""
|
||||
all_items = []
|
||||
page_number = 1
|
||||
total_saved = 0
|
||||
|
||||
logger.info(f"开始获取所有商品信息,每页{page_size}条")
|
||||
|
||||
while True:
|
||||
if max_pages and page_number > max_pages:
|
||||
logger.info(f"达到最大页数限制 {max_pages},停止获取")
|
||||
break
|
||||
|
||||
logger.info(f"正在获取第 {page_number} 页...")
|
||||
result = await self.get_item_list_info(page_number, page_size)
|
||||
|
||||
if not result.get("success"):
|
||||
logger.error(f"获取第 {page_number} 页失败: {result}")
|
||||
break
|
||||
|
||||
current_items = result.get("items", [])
|
||||
if not current_items:
|
||||
logger.info(f"第 {page_number} 页没有数据,获取完成")
|
||||
break
|
||||
|
||||
all_items.extend(current_items)
|
||||
total_saved += result.get("saved_count", 0)
|
||||
|
||||
logger.info(f"第 {page_number} 页获取到 {len(current_items)} 个商品")
|
||||
|
||||
# 如果当前页商品数量少于页面大小,说明已经是最后一页
|
||||
if len(current_items) < page_size:
|
||||
logger.info(f"第 {page_number} 页商品数量({len(current_items)})少于页面大小({page_size}),获取完成")
|
||||
break
|
||||
|
||||
page_number += 1
|
||||
|
||||
# 添加延迟避免请求过快
|
||||
await asyncio.sleep(1)
|
||||
|
||||
logger.info(f"所有商品获取完成,共 {len(all_items)} 个商品,保存了 {total_saved} 个")
|
||||
|
||||
return {
|
||||
"success": True,
|
||||
"total_pages": page_number,
|
||||
"total_count": len(all_items),
|
||||
"total_saved": total_saved,
|
||||
"items": all_items
|
||||
}
|
||||
|
||||
if __name__ == '__main__':
|
||||
cookies_str = os.getenv('COOKIES_STR')
|
||||
|
193
backup_import_update_summary.md
Normal file
193
backup_import_update_summary.md
Normal file
@ -0,0 +1,193 @@
|
||||
# 备份和导入功能更新总结
|
||||
|
||||
## 📋 更新概述
|
||||
|
||||
由于系统新增了多个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功能数据
|
||||
|
||||
用户可以放心使用备份和导入功能,所有数据都得到了完整的保护!
|
@ -35,6 +35,21 @@ class CookieManager:
|
||||
except Exception as e:
|
||||
logger.error(f"从数据库加载数据失败: {e}")
|
||||
|
||||
def reload_from_db(self):
|
||||
"""重新从数据库加载所有数据(用于备份导入后刷新)"""
|
||||
logger.info("重新从数据库加载数据...")
|
||||
old_cookies_count = len(self.cookies)
|
||||
old_keywords_count = len(self.keywords)
|
||||
|
||||
# 重新加载数据
|
||||
self._load_from_db()
|
||||
|
||||
new_cookies_count = len(self.cookies)
|
||||
new_keywords_count = len(self.keywords)
|
||||
|
||||
logger.info(f"数据重新加载完成: Cookie {old_cookies_count} -> {new_cookies_count}, 关键字组 {old_keywords_count} -> {new_keywords_count}")
|
||||
return True
|
||||
|
||||
# ------------------------ 内部协程 ------------------------
|
||||
async def _run_xianyu(self, cookie_id: str, cookie_value: str):
|
||||
"""在事件循环中启动 XianyuLive.main"""
|
||||
|
@ -51,12 +51,39 @@ class DBManager:
|
||||
self.conn = sqlite3.connect(self.db_path, check_same_thread=False)
|
||||
cursor = self.conn.cursor()
|
||||
|
||||
# 创建cookies表
|
||||
# 创建用户表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS 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 TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建邮箱验证码表
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS email_verifications (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
email TEXT NOT NULL,
|
||||
code TEXT NOT NULL,
|
||||
expires_at TIMESTAMP NOT NULL,
|
||||
used BOOLEAN DEFAULT FALSE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 创建cookies表(添加user_id字段)
|
||||
cursor.execute('''
|
||||
CREATE TABLE IF NOT EXISTS cookies (
|
||||
id TEXT PRIMARY KEY,
|
||||
value TEXT NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
user_id INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
|
||||
)
|
||||
''')
|
||||
|
||||
@ -234,6 +261,29 @@ class DBManager:
|
||||
('theme_color', 'blue', '主题颜色')
|
||||
''', (hashlib.sha256("admin123".encode()).hexdigest(),))
|
||||
|
||||
# 创建默认admin用户
|
||||
cursor.execute('''
|
||||
INSERT OR IGNORE INTO users (username, email, password_hash) VALUES
|
||||
('admin', 'admin@localhost', ?)
|
||||
''', (hashlib.sha256("admin123".encode()).hexdigest(),))
|
||||
|
||||
# 获取admin用户ID,用于历史数据绑定
|
||||
cursor.execute("SELECT id FROM users WHERE username = 'admin'")
|
||||
admin_user = cursor.fetchone()
|
||||
if admin_user:
|
||||
admin_user_id = admin_user[0]
|
||||
|
||||
# 将历史cookies数据绑定到admin用户(如果user_id列不存在)
|
||||
try:
|
||||
cursor.execute("SELECT user_id FROM cookies LIMIT 1")
|
||||
except sqlite3.OperationalError:
|
||||
# user_id列不存在,需要添加并更新历史数据
|
||||
cursor.execute("ALTER TABLE cookies ADD COLUMN user_id INTEGER")
|
||||
cursor.execute("UPDATE cookies SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
|
||||
else:
|
||||
# user_id列存在,更新NULL值
|
||||
cursor.execute("UPDATE cookies SET user_id = ? WHERE user_id IS NULL", (admin_user_id,))
|
||||
|
||||
self.conn.commit()
|
||||
logger.info(f"数据库初始化成功: {self.db_path}")
|
||||
except Exception as e:
|
||||
|
298
deploy.bat
Normal file
298
deploy.bat
Normal file
@ -0,0 +1,298 @@
|
||||
@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 %*
|
301
docker-deploy.bat
Normal file
301
docker-deploy.bat
Normal file
@ -0,0 +1,301 @@
|
||||
@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 %*
|
350
docker-deploy.sh
Normal file
350
docker-deploy.sh
Normal file
@ -0,0 +1,350 @@
|
||||
#!/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
|
||||
|
||||
# 项目配置
|
||||
PROJECT_NAME="xianyu-auto-reply"
|
||||
COMPOSE_FILE="docker-compose.yml"
|
||||
ENV_FILE=".env"
|
||||
|
||||
# 打印带颜色的消息
|
||||
print_info() {
|
||||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||||
}
|
||||
|
||||
print_success() {
|
||||
echo -e "${GREEN}✅ $1${NC}"
|
||||
}
|
||||
|
||||
print_warning() {
|
||||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||||
}
|
||||
|
||||
print_error() {
|
||||
echo -e "${RED}❌ $1${NC}"
|
||||
}
|
||||
|
||||
# 检查依赖
|
||||
check_dependencies() {
|
||||
print_info "检查系统依赖..."
|
||||
|
||||
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 "系统依赖检查通过"
|
||||
}
|
||||
|
||||
# 初始化配置
|
||||
init_config() {
|
||||
print_info "初始化配置文件..."
|
||||
|
||||
if [ ! -f "$ENV_FILE" ]; then
|
||||
if [ -f ".env.example" ]; then
|
||||
cp .env.example "$ENV_FILE"
|
||||
print_success "已创建 $ENV_FILE 配置文件"
|
||||
else
|
||||
print_error ".env.example 文件不存在"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
print_warning "$ENV_FILE 已存在,跳过创建"
|
||||
fi
|
||||
|
||||
# 创建必要的目录
|
||||
mkdir -p data logs backups
|
||||
print_success "已创建必要的目录"
|
||||
}
|
||||
|
||||
# 构建镜像
|
||||
build_image() {
|
||||
print_info "构建 Docker 镜像..."
|
||||
docker-compose build --no-cache
|
||||
print_success "镜像构建完成"
|
||||
}
|
||||
|
||||
# 启动服务
|
||||
start_services() {
|
||||
local profile=""
|
||||
if [ "$1" = "with-nginx" ]; then
|
||||
profile="--profile with-nginx"
|
||||
print_info "启动服务(包含 Nginx)..."
|
||||
else
|
||||
print_info "启动基础服务..."
|
||||
fi
|
||||
|
||||
docker-compose $profile up -d
|
||||
print_success "服务启动完成"
|
||||
|
||||
# 等待服务就绪
|
||||
print_info "等待服务就绪..."
|
||||
sleep 10
|
||||
|
||||
# 检查服务状态
|
||||
if docker-compose ps | grep -q "Up"; then
|
||||
print_success "服务运行正常"
|
||||
show_access_info "$1"
|
||||
else
|
||||
print_error "服务启动失败"
|
||||
docker-compose logs
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# 停止服务
|
||||
stop_services() {
|
||||
print_info "停止服务..."
|
||||
docker-compose down
|
||||
print_success "服务已停止"
|
||||
}
|
||||
|
||||
# 重启服务
|
||||
restart_services() {
|
||||
print_info "重启服务..."
|
||||
docker-compose restart
|
||||
print_success "服务已重启"
|
||||
}
|
||||
|
||||
# 查看日志
|
||||
show_logs() {
|
||||
local service="$1"
|
||||
if [ -z "$service" ]; then
|
||||
docker-compose logs -f
|
||||
else
|
||||
docker-compose logs -f "$service"
|
||||
fi
|
||||
}
|
||||
|
||||
# 查看状态
|
||||
show_status() {
|
||||
print_info "服务状态:"
|
||||
docker-compose ps
|
||||
|
||||
print_info "资源使用:"
|
||||
docker stats --no-stream $(docker-compose ps -q)
|
||||
}
|
||||
|
||||
# 显示访问信息
|
||||
show_access_info() {
|
||||
local with_nginx="$1"
|
||||
|
||||
echo ""
|
||||
print_success "🎉 部署完成!"
|
||||
echo ""
|
||||
|
||||
if [ "$with_nginx" = "with-nginx" ]; then
|
||||
echo "📱 访问地址:"
|
||||
echo " HTTP: http://localhost"
|
||||
echo " HTTPS: https://localhost (如果配置了SSL)"
|
||||
else
|
||||
echo "📱 访问地址:"
|
||||
echo " HTTP: http://localhost:8080"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "🔐 默认登录信息:"
|
||||
echo " 用户名: admin"
|
||||
echo " 密码: admin123"
|
||||
echo ""
|
||||
echo "📊 管理命令:"
|
||||
echo " 查看状态: $0 status"
|
||||
echo " 查看日志: $0 logs"
|
||||
echo " 重启服务: $0 restart"
|
||||
echo " 停止服务: $0 stop"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 健康检查
|
||||
health_check() {
|
||||
print_info "执行健康检查..."
|
||||
|
||||
local url="http://localhost:8080/health"
|
||||
local max_attempts=30
|
||||
local attempt=1
|
||||
|
||||
while [ $attempt -le $max_attempts ]; do
|
||||
if curl -f -s "$url" > /dev/null 2>&1; then
|
||||
print_success "健康检查通过"
|
||||
return 0
|
||||
fi
|
||||
|
||||
print_info "等待服务就绪... ($attempt/$max_attempts)"
|
||||
sleep 2
|
||||
((attempt++))
|
||||
done
|
||||
|
||||
print_error "健康检查失败"
|
||||
return 1
|
||||
}
|
||||
|
||||
# 备份数据
|
||||
backup_data() {
|
||||
print_info "备份数据..."
|
||||
|
||||
local backup_dir="backups/$(date +%Y%m%d_%H%M%S)"
|
||||
mkdir -p "$backup_dir"
|
||||
|
||||
# 备份数据库
|
||||
if [ -f "data/xianyu_data.db" ]; then
|
||||
cp data/xianyu_data.db "$backup_dir/"
|
||||
print_success "数据库备份完成"
|
||||
fi
|
||||
|
||||
# 备份配置
|
||||
cp "$ENV_FILE" "$backup_dir/"
|
||||
cp global_config.yml "$backup_dir/" 2>/dev/null || true
|
||||
|
||||
print_success "数据备份完成: $backup_dir"
|
||||
}
|
||||
|
||||
# 更新部署
|
||||
update_deployment() {
|
||||
print_info "更新部署..."
|
||||
|
||||
# 备份数据
|
||||
backup_data
|
||||
|
||||
# 停止服务
|
||||
stop_services
|
||||
|
||||
# 拉取最新代码(如果是git仓库)
|
||||
if [ -d ".git" ]; then
|
||||
print_info "拉取最新代码..."
|
||||
git pull
|
||||
fi
|
||||
|
||||
# 重新构建
|
||||
build_image
|
||||
|
||||
# 启动服务
|
||||
start_services
|
||||
|
||||
print_success "更新完成"
|
||||
}
|
||||
|
||||
# 清理环境
|
||||
cleanup() {
|
||||
print_warning "这将删除所有容器、镜像和数据,确定要继续吗?(y/N)"
|
||||
read -r response
|
||||
|
||||
if [[ "$response" =~ ^[Yy]$ ]]; then
|
||||
print_info "清理环境..."
|
||||
|
||||
# 停止并删除容器
|
||||
docker-compose down -v --rmi all
|
||||
|
||||
# 删除数据目录
|
||||
rm -rf data logs backups
|
||||
|
||||
print_success "环境清理完成"
|
||||
else
|
||||
print_info "取消清理操作"
|
||||
fi
|
||||
}
|
||||
|
||||
# 显示帮助信息
|
||||
show_help() {
|
||||
echo "闲鱼自动回复系统 Docker 部署脚本"
|
||||
echo ""
|
||||
echo "用法: $0 [命令] [选项]"
|
||||
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 " update 更新部署"
|
||||
echo " cleanup 清理环境"
|
||||
echo " help 显示帮助信息"
|
||||
echo ""
|
||||
echo "示例:"
|
||||
echo " $0 init # 初始化配置"
|
||||
echo " $0 start # 启动基础服务"
|
||||
echo " $0 start with-nginx # 启动包含 Nginx 的服务"
|
||||
echo " $0 logs xianyu-app # 查看应用日志"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# 主函数
|
||||
main() {
|
||||
case "$1" in
|
||||
"init")
|
||||
check_dependencies
|
||||
init_config
|
||||
;;
|
||||
"build")
|
||||
check_dependencies
|
||||
build_image
|
||||
;;
|
||||
"start")
|
||||
check_dependencies
|
||||
init_config
|
||||
build_image
|
||||
start_services "$2"
|
||||
;;
|
||||
"stop")
|
||||
stop_services
|
||||
;;
|
||||
"restart")
|
||||
restart_services
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"logs")
|
||||
show_logs "$2"
|
||||
;;
|
||||
"health")
|
||||
health_check
|
||||
;;
|
||||
"backup")
|
||||
backup_data
|
||||
;;
|
||||
"update")
|
||||
check_dependencies
|
||||
update_deployment
|
||||
;;
|
||||
"cleanup")
|
||||
cleanup
|
||||
;;
|
||||
"help"|"--help"|"-h")
|
||||
show_help
|
||||
;;
|
||||
"")
|
||||
print_info "快速部署模式"
|
||||
check_dependencies
|
||||
init_config
|
||||
build_image
|
||||
start_services
|
||||
;;
|
||||
*)
|
||||
print_error "未知命令: $1"
|
||||
show_help
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# 执行主函数
|
||||
main "$@"
|
207
docker_deployment_update.md
Normal file
207
docker_deployment_update.md
Normal file
@ -0,0 +1,207 @@
|
||||
# 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 <repository-url>
|
||||
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 <repository-url>
|
||||
cd xianyuapis
|
||||
cp .env.example .env
|
||||
docker-compose up -d
|
||||
|
||||
# 访问系统
|
||||
open http://localhost:8080
|
||||
```
|
||||
|
||||
**Docker部署配置已经完善,支持所有新功能,可以直接使用!** 🎉
|
@ -29,167 +29,119 @@ class FileLogCollector:
|
||||
|
||||
def setup_file_monitoring(self):
|
||||
"""设置文件监控"""
|
||||
# 使用统一的日志文件路径
|
||||
import time
|
||||
log_dir = 'logs'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
|
||||
# 使用与其他模块相同的日志文件命名规则
|
||||
today_log = os.path.join(log_dir, f"xianyu_{time.strftime('%Y-%m-%d')}.log")
|
||||
|
||||
# 查找日志文件,优先使用今天的日志文件
|
||||
# 查找日志文件
|
||||
possible_files = [
|
||||
today_log,
|
||||
"logs/xianyu.log",
|
||||
"xianyu.log",
|
||||
"app.log",
|
||||
"app.log",
|
||||
"system.log",
|
||||
"logs/xianyu.log",
|
||||
"logs/app.log"
|
||||
]
|
||||
|
||||
|
||||
for file_path in possible_files:
|
||||
if os.path.exists(file_path):
|
||||
self.log_file = file_path
|
||||
break
|
||||
|
||||
|
||||
if not self.log_file:
|
||||
# 如果没有找到现有文件,使用今天的日志文件
|
||||
self.log_file = today_log
|
||||
|
||||
print(f"日志收集器监控文件: {self.log_file}")
|
||||
|
||||
# 如果没有找到现有文件,创建一个新的
|
||||
self.log_file = "realtime.log"
|
||||
|
||||
# 设置loguru输出到文件
|
||||
self.setup_loguru_file_output()
|
||||
|
||||
# 启动文件监控线程
|
||||
self.monitor_thread = threading.Thread(target=self.monitor_file, daemon=True)
|
||||
self.monitor_thread.start()
|
||||
|
||||
|
||||
def setup_loguru_file_output(self):
|
||||
"""设置loguru输出到文件"""
|
||||
try:
|
||||
from loguru import logger
|
||||
|
||||
# 添加文件输出
|
||||
logger.add(
|
||||
self.log_file,
|
||||
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}",
|
||||
level="DEBUG",
|
||||
rotation="10 MB",
|
||||
retention="7 days",
|
||||
enqueue=False, # 改为False,避免队列延迟
|
||||
buffering=1 # 行缓冲,立即写入
|
||||
)
|
||||
|
||||
logger.info("文件日志收集器已启动")
|
||||
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
def monitor_file(self):
|
||||
"""监控日志文件变化"""
|
||||
print(f"开始监控日志文件: {self.log_file}")
|
||||
|
||||
while True:
|
||||
try:
|
||||
if os.path.exists(self.log_file):
|
||||
# 获取文件大小
|
||||
file_size = os.path.getsize(self.log_file)
|
||||
|
||||
|
||||
if file_size > self.last_position:
|
||||
# 读取新增内容
|
||||
try:
|
||||
with open(self.log_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
f.seek(self.last_position)
|
||||
new_lines = f.readlines()
|
||||
self.last_position = f.tell()
|
||||
|
||||
# 解析新增的日志行
|
||||
for line in new_lines:
|
||||
line = line.strip()
|
||||
if line: # 只处理非空行
|
||||
self.parse_log_line(line)
|
||||
except Exception as read_error:
|
||||
print(f"读取日志文件失败: {read_error}")
|
||||
elif file_size < self.last_position:
|
||||
# 文件被截断或重新创建,重置位置
|
||||
self.last_position = 0
|
||||
print(f"检测到日志文件被重置: {self.log_file}")
|
||||
else:
|
||||
# 文件不存在,重置位置等待文件创建
|
||||
self.last_position = 0
|
||||
|
||||
time.sleep(0.2) # 每0.2秒检查一次,更及时
|
||||
|
||||
with open(self.log_file, 'r', encoding='utf-8') as f:
|
||||
f.seek(self.last_position)
|
||||
new_lines = f.readlines()
|
||||
self.last_position = f.tell()
|
||||
|
||||
# 解析新增的日志行
|
||||
for line in new_lines:
|
||||
self.parse_log_line(line.strip())
|
||||
|
||||
time.sleep(0.5) # 每0.5秒检查一次
|
||||
|
||||
except Exception as e:
|
||||
print(f"监控日志文件异常: {e}")
|
||||
time.sleep(1) # 出错时等待1秒
|
||||
|
||||
def parse_log_line(self, line: str):
|
||||
"""解析日志行"""
|
||||
if not line:
|
||||
return
|
||||
|
||||
|
||||
try:
|
||||
# 解析统一格式的日志
|
||||
# 格式: 2024-07-24 15:46:03.430 | INFO | module_name:function_name:123 - 消息内容
|
||||
# 解析loguru格式的日志
|
||||
# 格式: 2025-07-23 15:46:03.430 | INFO | __main__:debug_collector:70 - 消息
|
||||
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| (\w+) \| ([^:]+):([^:]+):(\d+) - (.*)'
|
||||
match = re.match(pattern, line)
|
||||
|
||||
|
||||
if match:
|
||||
timestamp_str, level, source, function, line_num, message = match.groups()
|
||||
|
||||
|
||||
# 转换时间格式
|
||||
try:
|
||||
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S.%f')
|
||||
except:
|
||||
timestamp = datetime.now()
|
||||
|
||||
# 清理source名称,移除路径和扩展名
|
||||
if '\\' in source or '/' in source:
|
||||
source = os.path.basename(source)
|
||||
if source.endswith('.py'):
|
||||
source = source[:-3]
|
||||
|
||||
|
||||
log_entry = {
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"level": level.strip(),
|
||||
"source": source.strip(),
|
||||
"function": function.strip(),
|
||||
"level": level,
|
||||
"source": source,
|
||||
"function": function,
|
||||
"line": int(line_num),
|
||||
"message": message.strip()
|
||||
"message": message
|
||||
}
|
||||
|
||||
|
||||
with self.lock:
|
||||
self.logs.append(log_entry)
|
||||
|
||||
else:
|
||||
# 尝试解析其他可能的格式
|
||||
# 简单格式: [时间] [级别] 消息
|
||||
simple_pattern = r'\[([^\]]+)\] \[(\w+)\] (.*)'
|
||||
simple_match = re.match(simple_pattern, line)
|
||||
|
||||
if simple_match:
|
||||
timestamp_str, level, message = simple_match.groups()
|
||||
try:
|
||||
timestamp = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
|
||||
except:
|
||||
timestamp = datetime.now()
|
||||
|
||||
log_entry = {
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"level": level.strip(),
|
||||
"source": "system",
|
||||
"function": "unknown",
|
||||
"line": 0,
|
||||
"message": message.strip()
|
||||
}
|
||||
|
||||
with self.lock:
|
||||
self.logs.append(log_entry)
|
||||
else:
|
||||
# 如果都解析失败,作为普通消息处理
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"level": "INFO",
|
||||
"source": "system",
|
||||
"function": "unknown",
|
||||
"line": 0,
|
||||
"message": line.strip()
|
||||
}
|
||||
|
||||
with self.lock:
|
||||
self.logs.append(log_entry)
|
||||
|
||||
|
||||
except Exception as e:
|
||||
# 如果解析失败,作为普通消息处理
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"level": "ERROR",
|
||||
"source": "log_parser",
|
||||
"function": "parse_log_line",
|
||||
"level": "INFO",
|
||||
"source": "system",
|
||||
"function": "unknown",
|
||||
"line": 0,
|
||||
"message": f"日志解析失败: {line} (错误: {str(e)})"
|
||||
"message": line
|
||||
}
|
||||
|
||||
|
||||
with self.lock:
|
||||
self.logs.append(log_entry)
|
||||
|
||||
|
156
fix-db-permissions.bat
Normal file
156
fix-db-permissions.bat
Normal file
@ -0,0 +1,156 @@
|
||||
@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
|
167
fix-db-permissions.sh
Normal file
167
fix-db-permissions.sh
Normal file
@ -0,0 +1,167 @@
|
||||
#!/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"
|
144
fix-docker-warnings.bat
Normal file
144
fix-docker-warnings.bat
Normal file
@ -0,0 +1,144 @@
|
||||
@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
|
145
fix-docker-warnings.sh
Normal file
145
fix-docker-warnings.sh
Normal file
@ -0,0 +1,145 @@
|
||||
#!/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"
|
121
fix-websocket-issue.sh
Normal file
121
fix-websocket-issue.sh
Normal file
@ -0,0 +1,121 @@
|
||||
#!/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. 检查网络连接和防火墙设置"
|
172
gitignore_rules_explanation.md
Normal file
172
gitignore_rules_explanation.md
Normal file
@ -0,0 +1,172 @@
|
||||
# .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` 配置既保证了项目的安全性,又确保了必要文件的正常版本控制!
|
@ -9,7 +9,7 @@ APP_CONFIG:
|
||||
platform: web
|
||||
AUTO_REPLY:
|
||||
api:
|
||||
enabled: true # 禁用API回复,使用AI回复或关键词回复
|
||||
enabled: false # 禁用API回复,使用AI回复或关键词回复
|
||||
host: 0.0.0.0 # 绑定所有网络接口,支持IP访问
|
||||
port: 8080 # Web服务端口
|
||||
timeout: 10
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 144 KiB |
Binary file not shown.
Before Width: | Height: | Size: 167 KiB |
161
log_filter.py
161
log_filter.py
@ -1,161 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
日志过滤器
|
||||
用于过滤不需要记录到文件的日志
|
||||
"""
|
||||
|
||||
import re
|
||||
from typing import Dict, Any
|
||||
|
||||
class LogFilter:
|
||||
"""日志过滤器类"""
|
||||
|
||||
def __init__(self):
|
||||
# 不需要记录的API路径模式
|
||||
self.excluded_api_patterns = [
|
||||
r'GET /logs',
|
||||
r'GET /logs/stats',
|
||||
r'GET /health',
|
||||
r'GET /docs',
|
||||
r'GET /redoc',
|
||||
r'GET /openapi\.json',
|
||||
r'GET /static/',
|
||||
r'GET /favicon\.ico'
|
||||
]
|
||||
|
||||
# 不需要记录的消息模式
|
||||
self.excluded_message_patterns = [
|
||||
r'API请求: GET /logs',
|
||||
r'API响应: GET /logs',
|
||||
r'API请求: GET /health',
|
||||
r'API响应: GET /health',
|
||||
r'API请求: GET /docs',
|
||||
r'API响应: GET /docs',
|
||||
r'API请求: GET /static/',
|
||||
r'API响应: GET /static/',
|
||||
r'.*favicon\.ico.*',
|
||||
r'.*websocket.*ping.*',
|
||||
r'.*websocket.*pong.*'
|
||||
]
|
||||
|
||||
# 编译正则表达式以提高性能
|
||||
self.compiled_api_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.excluded_api_patterns]
|
||||
self.compiled_message_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in self.excluded_message_patterns]
|
||||
|
||||
def should_log(self, record: Dict[str, Any]) -> bool:
|
||||
"""
|
||||
判断是否应该记录这条日志
|
||||
|
||||
Args:
|
||||
record: loguru的日志记录字典
|
||||
|
||||
Returns:
|
||||
bool: True表示应该记录,False表示应该过滤掉
|
||||
"""
|
||||
try:
|
||||
message = record.get('message', '')
|
||||
|
||||
# 检查消息模式
|
||||
for pattern in self.compiled_message_patterns:
|
||||
if pattern.search(message):
|
||||
return False
|
||||
|
||||
# 检查API路径模式
|
||||
for pattern in self.compiled_api_patterns:
|
||||
if pattern.search(message):
|
||||
return False
|
||||
|
||||
# 过滤掉过于频繁的心跳日志
|
||||
if any(keyword in message.lower() for keyword in ['heartbeat', '心跳', 'ping', 'pong']):
|
||||
return False
|
||||
|
||||
# 过滤掉WebSocket连接状态的频繁日志
|
||||
if any(keyword in message.lower() for keyword in ['websocket connected', 'websocket disconnected']):
|
||||
# 只记录连接和断开,不记录频繁的状态检查
|
||||
if 'status check' in message.lower():
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
except Exception:
|
||||
# 如果过滤器出错,默认记录日志
|
||||
return True
|
||||
|
||||
# 全局日志过滤器实例
|
||||
log_filter = LogFilter()
|
||||
|
||||
def filter_log_record(record):
|
||||
"""
|
||||
loguru的过滤器函数
|
||||
|
||||
Args:
|
||||
record: loguru的日志记录对象
|
||||
|
||||
Returns:
|
||||
bool: True表示应该记录,False表示应该过滤掉
|
||||
"""
|
||||
return log_filter.should_log(record)
|
||||
|
||||
def add_excluded_pattern(pattern: str):
|
||||
"""
|
||||
添加新的排除模式
|
||||
|
||||
Args:
|
||||
pattern: 正则表达式模式
|
||||
"""
|
||||
log_filter.excluded_message_patterns.append(pattern)
|
||||
log_filter.compiled_message_patterns.append(re.compile(pattern, re.IGNORECASE))
|
||||
|
||||
def remove_excluded_pattern(pattern: str):
|
||||
"""
|
||||
移除排除模式
|
||||
|
||||
Args:
|
||||
pattern: 要移除的正则表达式模式
|
||||
"""
|
||||
if pattern in log_filter.excluded_message_patterns:
|
||||
index = log_filter.excluded_message_patterns.index(pattern)
|
||||
log_filter.excluded_message_patterns.pop(index)
|
||||
log_filter.compiled_message_patterns.pop(index)
|
||||
|
||||
def get_excluded_patterns():
|
||||
"""
|
||||
获取当前的排除模式列表
|
||||
|
||||
Returns:
|
||||
list: 排除模式列表
|
||||
"""
|
||||
return log_filter.excluded_message_patterns.copy()
|
||||
|
||||
# 测试函数
|
||||
def test_filter():
|
||||
"""测试过滤器功能"""
|
||||
test_messages = [
|
||||
"🌐 API请求: GET /logs?lines=200",
|
||||
"✅ API响应: GET /logs - 200 (0.123s)",
|
||||
"🌐 API请求: GET /health",
|
||||
"✅ API响应: GET /health - 200 (0.001s)",
|
||||
"🌐 API请求: POST /cookies",
|
||||
"✅ API响应: POST /cookies - 201 (0.456s)",
|
||||
"WebSocket心跳检查",
|
||||
"用户登录成功",
|
||||
"数据库连接建立",
|
||||
"WebSocket connected status check",
|
||||
"处理消息: 你好"
|
||||
]
|
||||
|
||||
print("🧪 测试日志过滤器")
|
||||
print("=" * 50)
|
||||
|
||||
for message in test_messages:
|
||||
record = {"message": message}
|
||||
should_log = log_filter.should_log(record)
|
||||
status = "✅ 记录" if should_log else "❌ 过滤"
|
||||
print(f"{status}: {message}")
|
||||
|
||||
print("=" * 50)
|
||||
print("测试完成")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_filter()
|
116
quick-fix-permissions.bat
Normal file
116
quick-fix-permissions.bat
Normal file
@ -0,0 +1,116 @@
|
||||
@echo off
|
||||
chcp 65001 >nul
|
||||
setlocal enabledelayedexpansion
|
||||
|
||||
:: 快速修复Docker权限问题 (Windows版本)
|
||||
|
||||
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_error
|
||||
echo %RED%[ERROR]%NC% %~1
|
||||
goto :eof
|
||||
|
||||
echo 🚀 快速修复Docker权限问题
|
||||
echo ================================
|
||||
echo.
|
||||
|
||||
:: 1. 停止容器
|
||||
call :print_info "停止现有容器..."
|
||||
docker-compose down >nul 2>&1
|
||||
|
||||
:: 2. 确保目录存在
|
||||
call :print_info "创建必要目录..."
|
||||
if not exist "data" mkdir data
|
||||
if not exist "logs" mkdir logs
|
||||
if not exist "backups" mkdir backups
|
||||
|
||||
:: 3. 检查并修复docker-compose.yml
|
||||
call :print_info "检查docker-compose.yml配置..."
|
||||
findstr /C:"user.*0:0" docker-compose.yml >nul 2>&1
|
||||
if !errorlevel! neq 0 (
|
||||
call :print_info "添加root用户配置..."
|
||||
|
||||
REM 备份原文件
|
||||
copy docker-compose.yml docker-compose.yml.backup >nul
|
||||
|
||||
REM 创建临时文件添加user配置
|
||||
(
|
||||
for /f "tokens=*" %%a in (docker-compose.yml) do (
|
||||
echo %%a
|
||||
echo %%a | findstr /C:"container_name: xianyu-auto-reply" >nul
|
||||
if !errorlevel! equ 0 (
|
||||
echo user: "0:0"
|
||||
)
|
||||
)
|
||||
) > docker-compose.yml.tmp
|
||||
|
||||
REM 替换原文件
|
||||
move docker-compose.yml.tmp docker-compose.yml >nul
|
||||
|
||||
call :print_success "已配置使用root用户运行"
|
||||
)
|
||||
|
||||
:: 4. 重新构建镜像
|
||||
call :print_info "重新构建Docker镜像..."
|
||||
docker-compose build --no-cache
|
||||
if !errorlevel! neq 0 (
|
||||
call :print_error "Docker镜像构建失败"
|
||||
pause
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:: 5. 启动服务
|
||||
call :print_info "启动服务..."
|
||||
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 "✅ 服务启动成功!"
|
||||
|
||||
echo.
|
||||
call :print_info "最近的日志:"
|
||||
docker-compose logs --tail=10 xianyu-app
|
||||
|
||||
echo.
|
||||
call :print_success "🎉 权限问题已修复!"
|
||||
echo.
|
||||
echo 访问信息:
|
||||
echo Web界面: http://localhost:8080
|
||||
echo 健康检查: http://localhost:8080/health
|
||||
echo 默认账号: admin / admin123
|
||||
|
||||
) else (
|
||||
call :print_error "❌ 服务启动失败"
|
||||
echo.
|
||||
call :print_info "错误日志:"
|
||||
docker-compose logs xianyu-app
|
||||
)
|
||||
|
||||
echo.
|
||||
pause
|
88
quick-fix-permissions.sh
Normal file
88
quick-fix-permissions.sh
Normal file
@ -0,0 +1,88 @@
|
||||
#!/bin/bash
|
||||
|
||||
# 快速修复Docker权限问题
|
||||
# 这个脚本会立即解决权限问题并重启服务
|
||||
|
||||
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_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
echo "🚀 快速修复Docker权限问题"
|
||||
echo "================================"
|
||||
|
||||
# 1. 停止容器
|
||||
print_info "停止现有容器..."
|
||||
docker-compose down
|
||||
|
||||
# 2. 确保目录存在并设置权限
|
||||
print_info "设置目录权限..."
|
||||
mkdir -p data logs backups
|
||||
chmod 777 data logs backups
|
||||
|
||||
# 3. 检查并修复docker-compose.yml
|
||||
print_info "检查docker-compose.yml配置..."
|
||||
if ! grep -q "user.*0:0" docker-compose.yml; then
|
||||
print_info "添加root用户配置..."
|
||||
|
||||
# 备份原文件
|
||||
cp docker-compose.yml docker-compose.yml.backup
|
||||
|
||||
# 在container_name后添加user配置
|
||||
sed -i '/container_name: xianyu-auto-reply/a\ user: "0:0"' docker-compose.yml
|
||||
|
||||
print_success "已配置使用root用户运行"
|
||||
fi
|
||||
|
||||
# 4. 重新构建镜像
|
||||
print_info "重新构建Docker镜像..."
|
||||
docker-compose build --no-cache
|
||||
|
||||
# 5. 启动服务
|
||||
print_info "启动服务..."
|
||||
docker-compose up -d
|
||||
|
||||
# 6. 等待启动
|
||||
print_info "等待服务启动..."
|
||||
sleep 15
|
||||
|
||||
# 7. 检查状态
|
||||
print_info "检查服务状态..."
|
||||
if docker-compose ps | grep -q "Up"; then
|
||||
print_success "✅ 服务启动成功!"
|
||||
|
||||
# 显示日志
|
||||
echo ""
|
||||
print_info "最近的日志:"
|
||||
docker-compose logs --tail=10 xianyu-app
|
||||
|
||||
echo ""
|
||||
print_success "🎉 权限问题已修复!"
|
||||
echo ""
|
||||
echo "访问信息:"
|
||||
echo " Web界面: http://localhost:8080"
|
||||
echo " 健康检查: http://localhost:8080/health"
|
||||
echo " 默认账号: admin / admin123"
|
||||
|
||||
else
|
||||
print_error "❌ 服务启动失败"
|
||||
echo ""
|
||||
print_info "错误日志:"
|
||||
docker-compose logs xianyu-app
|
||||
fi
|
174
reply_server.py
174
reply_server.py
@ -157,84 +157,23 @@ app = FastAPI(
|
||||
redoc_url="/redoc"
|
||||
)
|
||||
|
||||
# 配置统一的日志系统
|
||||
import time
|
||||
from loguru import logger
|
||||
|
||||
# 确保日志目录存在
|
||||
log_dir = 'logs'
|
||||
os.makedirs(log_dir, exist_ok=True)
|
||||
log_path = os.path.join(log_dir, f"xianyu_{time.strftime('%Y-%m-%d')}.log")
|
||||
|
||||
# 移除默认的日志处理器
|
||||
logger.remove()
|
||||
|
||||
# 导入日志过滤器
|
||||
try:
|
||||
from log_filter import filter_log_record
|
||||
except ImportError:
|
||||
# 如果过滤器不可用,使用默认过滤器
|
||||
def filter_log_record(record):
|
||||
return True
|
||||
|
||||
# 添加文件日志处理器,使用与XianyuAutoAsync相同的格式,并应用过滤器
|
||||
logger.add(
|
||||
log_path,
|
||||
rotation="1 day",
|
||||
retention="7 days",
|
||||
compression="zip",
|
||||
level="INFO",
|
||||
format='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}',
|
||||
encoding='utf-8',
|
||||
enqueue=False, # 立即写入
|
||||
buffering=1, # 行缓冲
|
||||
filter=filter_log_record # 应用日志过滤器
|
||||
)
|
||||
|
||||
# 初始化文件日志收集器
|
||||
setup_file_logging()
|
||||
|
||||
# 添加一条测试日志
|
||||
logger.info("Web服务器启动,统一日志系统已初始化")
|
||||
|
||||
# 不需要记录到文件的API路径
|
||||
EXCLUDED_LOG_PATHS = {
|
||||
'/logs',
|
||||
'/logs/stats',
|
||||
'/logs/clear',
|
||||
'/health',
|
||||
'/docs',
|
||||
'/redoc',
|
||||
'/openapi.json',
|
||||
'/favicon.ico'
|
||||
}
|
||||
|
||||
# 不需要记录的路径前缀
|
||||
EXCLUDED_LOG_PREFIXES = {
|
||||
'/static/',
|
||||
'/docs',
|
||||
'/redoc'
|
||||
}
|
||||
from loguru import logger
|
||||
logger.info("Web服务器启动,文件日志收集器已初始化")
|
||||
|
||||
# 添加请求日志中间件
|
||||
@app.middleware("http")
|
||||
async def log_requests(request, call_next):
|
||||
start_time = time.time()
|
||||
|
||||
# 检查是否需要记录日志
|
||||
should_log = (
|
||||
request.url.path not in EXCLUDED_LOG_PATHS and
|
||||
not any(request.url.path.startswith(prefix) for prefix in EXCLUDED_LOG_PREFIXES)
|
||||
)
|
||||
|
||||
if should_log:
|
||||
logger.info(f"🌐 API请求: {request.method} {request.url.path}")
|
||||
logger.info(f"🌐 API请求: {request.method} {request.url.path}")
|
||||
|
||||
response = await call_next(request)
|
||||
|
||||
if should_log:
|
||||
process_time = time.time() - start_time
|
||||
logger.info(f"✅ API响应: {request.method} {request.url.path} - {response.status_code} ({process_time:.3f}s)")
|
||||
process_time = time.time() - start_time
|
||||
logger.info(f"✅ API响应: {request.method} {request.url.path} - {response.status_code} ({process_time:.3f}s)")
|
||||
|
||||
return response
|
||||
|
||||
@ -1008,6 +947,15 @@ def import_backup(file: UploadFile = File(...), _: None = Depends(require_auth))
|
||||
success = db_manager.import_backup(backup_data)
|
||||
|
||||
if success:
|
||||
# 备份导入成功后,刷新 CookieManager 的内存缓存
|
||||
import cookie_manager
|
||||
if cookie_manager.manager:
|
||||
try:
|
||||
cookie_manager.manager.reload_from_db()
|
||||
logger.info("备份导入后已刷新 CookieManager 缓存")
|
||||
except Exception as e:
|
||||
logger.error(f"刷新 CookieManager 缓存失败: {e}")
|
||||
|
||||
return {"message": "备份导入成功"}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail="备份导入失败")
|
||||
@ -1018,6 +966,23 @@ def import_backup(file: UploadFile = File(...), _: None = Depends(require_auth))
|
||||
raise HTTPException(status_code=500, detail=f"导入备份失败: {str(e)}")
|
||||
|
||||
|
||||
@app.post("/system/reload-cache")
|
||||
def reload_cache(_: None = Depends(require_auth)):
|
||||
"""重新加载系统缓存(用于手动刷新数据)"""
|
||||
try:
|
||||
import cookie_manager
|
||||
if cookie_manager.manager:
|
||||
success = cookie_manager.manager.reload_from_db()
|
||||
if success:
|
||||
return {"message": "系统缓存已刷新", "success": True}
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="缓存刷新失败")
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="CookieManager 未初始化")
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=500, detail=f"刷新缓存失败: {str(e)}")
|
||||
|
||||
|
||||
# ==================== 商品管理 API ====================
|
||||
|
||||
@app.get("/items")
|
||||
@ -1302,9 +1267,9 @@ async def get_all_items_from_account(request: dict, _: None = Depends(require_au
|
||||
from XianyuAutoAsync import XianyuLive
|
||||
xianyu_instance = XianyuLive(cookies_str, cookie_id)
|
||||
|
||||
# 调用获取商品信息的方法
|
||||
# 调用获取所有商品信息的方法(自动分页)
|
||||
logger.info(f"开始获取账号 {cookie_id} 的所有商品信息")
|
||||
result = await xianyu_instance.get_item_list_info()
|
||||
result = await xianyu_instance.get_all_items()
|
||||
|
||||
# 关闭session
|
||||
await xianyu_instance.close_session()
|
||||
@ -1313,11 +1278,78 @@ async def get_all_items_from_account(request: dict, _: None = Depends(require_au
|
||||
logger.error(f"获取商品信息失败: {result['error']}")
|
||||
return {"success": False, "message": result['error']}
|
||||
else:
|
||||
logger.info(f"成功获取账号 {cookie_id} 的 {result.get('total_count', 0)} 个商品")
|
||||
total_count = result.get('total_count', 0)
|
||||
total_pages = result.get('total_pages', 1)
|
||||
logger.info(f"成功获取账号 {cookie_id} 的 {total_count} 个商品(共{total_pages}页)")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"成功获取 {result.get('total_count', 0)} 个商品,详细信息已打印到控制台",
|
||||
"total_count": result.get('total_count', 0)
|
||||
"message": f"成功获取 {total_count} 个商品(共{total_pages}页),详细信息已打印到控制台",
|
||||
"total_count": total_count,
|
||||
"total_pages": total_pages
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"获取账号商品信息异常: {str(e)}")
|
||||
return {"success": False, "message": f"获取商品信息异常: {str(e)}"}
|
||||
|
||||
|
||||
@app.post("/items/get-by-page")
|
||||
async def get_items_by_page(request: dict, _: None = Depends(require_auth)):
|
||||
"""从指定账号按页获取商品信息"""
|
||||
try:
|
||||
# 验证参数
|
||||
cookie_id = request.get('cookie_id')
|
||||
page_number = request.get('page_number', 1)
|
||||
page_size = request.get('page_size', 20)
|
||||
|
||||
if not cookie_id:
|
||||
return {"success": False, "message": "缺少cookie_id参数"}
|
||||
|
||||
# 验证分页参数
|
||||
try:
|
||||
page_number = int(page_number)
|
||||
page_size = int(page_size)
|
||||
except (ValueError, TypeError):
|
||||
return {"success": False, "message": "页码和每页数量必须是数字"}
|
||||
|
||||
if page_number < 1:
|
||||
return {"success": False, "message": "页码必须大于0"}
|
||||
|
||||
if page_size < 1 or page_size > 100:
|
||||
return {"success": False, "message": "每页数量必须在1-100之间"}
|
||||
|
||||
# 获取账号信息
|
||||
account = db_manager.get_cookie_by_id(cookie_id)
|
||||
if not account:
|
||||
return {"success": False, "message": "账号不存在"}
|
||||
|
||||
cookies_str = account['cookies_str']
|
||||
if not cookies_str:
|
||||
return {"success": False, "message": "账号cookies为空"}
|
||||
|
||||
# 创建XianyuLive实例,传入正确的cookie_id
|
||||
from XianyuAutoAsync import XianyuLive
|
||||
xianyu_instance = XianyuLive(cookies_str, cookie_id)
|
||||
|
||||
# 调用获取指定页商品信息的方法
|
||||
logger.info(f"开始获取账号 {cookie_id} 第{page_number}页商品信息(每页{page_size}条)")
|
||||
result = await xianyu_instance.get_item_list_info(page_number, page_size)
|
||||
|
||||
# 关闭session
|
||||
await xianyu_instance.close_session()
|
||||
|
||||
if result.get('error'):
|
||||
logger.error(f"获取商品信息失败: {result['error']}")
|
||||
return {"success": False, "message": result['error']}
|
||||
else:
|
||||
current_count = result.get('current_count', 0)
|
||||
logger.info(f"成功获取账号 {cookie_id} 第{page_number}页 {current_count} 个商品")
|
||||
return {
|
||||
"success": True,
|
||||
"message": f"成功获取第{page_number}页 {current_count} 个商品,详细信息已打印到控制台",
|
||||
"page_number": page_number,
|
||||
"page_size": page_size,
|
||||
"current_count": current_count
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
|
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>闲鱼自动回复管理系统</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap-icons/bootstrap-icons.css">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #4f46e5;
|
||||
@ -1295,7 +1295,7 @@
|
||||
<!-- Cookie筛选 -->
|
||||
<div class="card mb-4">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center">
|
||||
<div class="row align-items-end">
|
||||
<div class="col-md-6">
|
||||
<label for="itemCookieFilter" class="form-label">筛选账号</label>
|
||||
<select class="form-select" id="itemCookieFilter" onchange="loadItemsByCookie()">
|
||||
@ -1303,13 +1303,23 @@
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex justify-content-end align-items-end h-100 gap-2">
|
||||
<button class="btn btn-success" onclick="getAllItemsFromAccount()">
|
||||
<i class="bi bi-download me-1"></i>获取所有商品
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="refreshItems()">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i>刷新
|
||||
</button>
|
||||
<div class="d-flex justify-content-end align-items-end gap-2">
|
||||
<!-- 页码输入 -->
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label for="pageNumber" class="form-label mb-0 text-nowrap">页码:</label>
|
||||
<input type="number" class="form-control" id="pageNumber" placeholder="页码" min="1" value="1" style="width: 80px;">
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button class="btn btn-success" onclick="getAllItemsFromAccount()">
|
||||
<i class="bi bi-download me-1"></i>获取指定页
|
||||
</button>
|
||||
<button class="btn btn-warning" onclick="getAllItemsFromAccountAll()">
|
||||
<i class="bi bi-collection me-1"></i>获取所有页
|
||||
</button>
|
||||
<button class="btn btn-primary" onclick="refreshItems()">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i>刷新
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1382,8 +1392,9 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<button class="btn btn-outline-primary w-100" onclick="refreshAccountList()">
|
||||
<i class="bi bi-arrow-clockwise me-2"></i>刷新列表
|
||||
<button class="btn btn-primary w-100 d-flex align-items-center justify-content-center" onclick="refreshAccountList()" style="padding: 1rem 1.25rem; height: auto;">
|
||||
<i class="bi bi-arrow-clockwise me-2"></i>
|
||||
<span>刷新列表</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -1879,6 +1890,19 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 系统缓存管理 -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<h6 class="mb-3">
|
||||
<i class="bi bi-arrow-clockwise me-2"></i>系统缓存管理
|
||||
</h6>
|
||||
<p class="text-muted mb-3">如果导入备份后关键字等数据没有立即更新,可以手动刷新系统缓存</p>
|
||||
<button type="button" class="btn btn-info" onclick="reloadSystemCache()">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i>刷新系统缓存
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-warning mt-4" role="alert">
|
||||
<i class="bi bi-exclamation-triangle me-2"></i>
|
||||
<strong>注意:</strong>导入备份将会覆盖当前系统的所有数据,请谨慎操作!建议在导入前先导出当前数据作为备份。
|
||||
@ -2339,7 +2363,7 @@
|
||||
<div class="toast-container"></div>
|
||||
|
||||
<!-- JS依赖 -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// 全局变量
|
||||
const apiBase = location.origin;
|
||||
@ -5787,15 +5811,38 @@
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
showToast('备份导入成功,页面将在3秒后刷新', 'success');
|
||||
showToast('备份导入成功!正在刷新数据...', 'success');
|
||||
|
||||
// 清空文件选择
|
||||
fileInput.value = '';
|
||||
|
||||
// 3秒后刷新页面
|
||||
setTimeout(() => {
|
||||
window.location.reload();
|
||||
}, 3000);
|
||||
// 清除前端缓存
|
||||
clearKeywordCache();
|
||||
|
||||
// 延迟一下再刷新数据,确保后端缓存已更新
|
||||
setTimeout(async () => {
|
||||
try {
|
||||
// 如果当前在关键字管理页面,重新加载数据
|
||||
if (currentCookieId) {
|
||||
await loadAccountKeywords();
|
||||
}
|
||||
|
||||
// 刷新仪表盘数据
|
||||
if (document.getElementById('dashboard-section').classList.contains('active')) {
|
||||
await loadDashboard();
|
||||
}
|
||||
|
||||
// 刷新账号列表
|
||||
if (document.getElementById('accounts-section').classList.contains('active')) {
|
||||
await loadCookies();
|
||||
}
|
||||
|
||||
showToast('数据刷新完成!', 'success');
|
||||
} catch (error) {
|
||||
console.error('刷新数据失败:', error);
|
||||
showToast('备份导入成功,但数据刷新失败,请手动刷新页面', 'warning');
|
||||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`导入失败: ${error}`, 'danger');
|
||||
@ -5806,6 +5853,41 @@
|
||||
}
|
||||
}
|
||||
|
||||
// 刷新系统缓存
|
||||
async function reloadSystemCache() {
|
||||
try {
|
||||
showToast('正在刷新系统缓存...', 'info');
|
||||
|
||||
const response = await fetch(`${apiBase}/system/reload-cache`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
}
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
showToast('系统缓存刷新成功!关键字等数据已更新', 'success');
|
||||
|
||||
// 清除前端缓存
|
||||
clearKeywordCache();
|
||||
|
||||
// 如果当前在关键字管理页面,重新加载数据
|
||||
if (currentCookieId) {
|
||||
setTimeout(() => {
|
||||
loadAccountKeywords();
|
||||
}, 500);
|
||||
}
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`刷新缓存失败: ${error}`, 'danger');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('刷新系统缓存失败:', error);
|
||||
showToast('刷新系统缓存失败', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 商品管理功能 ====================
|
||||
|
||||
// 加载商品列表
|
||||
@ -5815,6 +5897,16 @@
|
||||
await loadCookieFilter();
|
||||
|
||||
// 加载商品列表
|
||||
await refreshItemsData();
|
||||
} catch (error) {
|
||||
console.error('加载商品列表失败:', error);
|
||||
showToast('加载商品列表失败', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
// 只刷新商品数据,不重新加载筛选器
|
||||
async function refreshItemsData() {
|
||||
try {
|
||||
const selectedCookie = document.getElementById('itemCookieFilter').value;
|
||||
if (selectedCookie) {
|
||||
await loadItemsByCookie();
|
||||
@ -5822,8 +5914,8 @@
|
||||
await loadAllItems();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载商品列表失败:', error);
|
||||
showToast('加载商品列表失败', 'danger');
|
||||
console.error('刷新商品数据失败:', error);
|
||||
showToast('刷新商品数据失败', 'danger');
|
||||
}
|
||||
}
|
||||
|
||||
@ -5840,6 +5932,9 @@
|
||||
const accounts = await response.json();
|
||||
const select = document.getElementById('itemCookieFilter');
|
||||
|
||||
// 保存当前选择的值
|
||||
const currentValue = select.value;
|
||||
|
||||
// 清空现有选项(保留"所有账号")
|
||||
select.innerHTML = '<option value="">所有账号</option>';
|
||||
|
||||
@ -5888,6 +5983,11 @@
|
||||
select.appendChild(option);
|
||||
});
|
||||
}
|
||||
|
||||
// 恢复之前选择的值
|
||||
if (currentValue) {
|
||||
select.value = currentValue;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载Cookie列表失败:', error);
|
||||
@ -6026,14 +6126,72 @@
|
||||
|
||||
// 刷新商品列表
|
||||
async function refreshItems() {
|
||||
await loadItems();
|
||||
await refreshItemsData();
|
||||
showToast('商品列表已刷新', 'success');
|
||||
}
|
||||
|
||||
// 获取所有商品信息
|
||||
// 获取商品信息
|
||||
async function getAllItemsFromAccount() {
|
||||
const cookieSelect = document.getElementById('itemCookieFilter');
|
||||
const selectedCookieId = cookieSelect.value;
|
||||
const pageNumber = parseInt(document.getElementById('pageNumber').value) || 1;
|
||||
|
||||
if (!selectedCookieId) {
|
||||
showToast('请先选择一个账号', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
if (pageNumber < 1) {
|
||||
showToast('页码必须大于0', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示加载状态
|
||||
const button = event.target;
|
||||
const originalText = button.innerHTML;
|
||||
button.innerHTML = '<i class="bi bi-hourglass-split me-1"></i>获取中...';
|
||||
button.disabled = true;
|
||||
|
||||
try {
|
||||
const response = await fetch(`${apiBase}/items/get-by-page`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${authToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
cookie_id: selectedCookieId,
|
||||
page_number: pageNumber,
|
||||
page_size: 20
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast(`成功获取第${pageNumber}页 ${data.current_count} 个商品,请查看控制台日志`, 'success');
|
||||
// 刷新商品列表(保持筛选器选择)
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
showToast(data.message || '获取商品信息失败', 'danger');
|
||||
}
|
||||
} else {
|
||||
throw new Error(`HTTP ${response.status}`);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取商品信息失败:', error);
|
||||
showToast('获取商品信息失败', 'danger');
|
||||
} finally {
|
||||
// 恢复按钮状态
|
||||
button.innerHTML = originalText;
|
||||
button.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
// 获取所有页商品信息
|
||||
async function getAllItemsFromAccountAll() {
|
||||
const cookieSelect = document.getElementById('itemCookieFilter');
|
||||
const selectedCookieId = cookieSelect.value;
|
||||
|
||||
if (!selectedCookieId) {
|
||||
showToast('请先选择一个账号', 'warning');
|
||||
@ -6061,9 +6219,12 @@
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
if (data.success) {
|
||||
showToast(`成功获取商品信息,请查看控制台日志`, 'success');
|
||||
// 刷新商品列表
|
||||
await loadItems();
|
||||
const message = data.total_pages ?
|
||||
`成功获取 ${data.total_count} 个商品(共${data.total_pages}页),请查看控制台日志` :
|
||||
`成功获取商品信息,请查看控制台日志`;
|
||||
showToast(message, 'success');
|
||||
// 刷新商品列表(保持筛选器选择)
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
showToast(data.message || '获取商品信息失败', 'danger');
|
||||
}
|
||||
@ -6080,6 +6241,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 编辑商品详情
|
||||
async function editItem(cookieId, itemId) {
|
||||
try {
|
||||
@ -6142,8 +6305,8 @@
|
||||
const modal = bootstrap.Modal.getInstance(document.getElementById('editItemModal'));
|
||||
modal.hide();
|
||||
|
||||
// 刷新列表
|
||||
await loadItems();
|
||||
// 刷新列表(保持筛选器选择)
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`更新失败: ${error}`, 'danger');
|
||||
@ -6172,8 +6335,8 @@
|
||||
|
||||
if (response.ok) {
|
||||
showToast('商品信息删除成功', 'success');
|
||||
// 刷新列表
|
||||
await loadItems();
|
||||
// 刷新列表(保持筛选器选择)
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`删除失败: ${error}`, 'danger');
|
||||
@ -6221,8 +6384,8 @@
|
||||
if (response.ok) {
|
||||
const result = await response.json();
|
||||
showToast(`批量删除完成: 成功 ${result.success_count} 个,失败 ${result.failed_count} 个`, 'success');
|
||||
// 刷新列表
|
||||
await loadItems();
|
||||
// 刷新列表(保持筛选器选择)
|
||||
await refreshItemsData();
|
||||
} else {
|
||||
const error = await response.text();
|
||||
showToast(`批量删除失败: ${error}`, 'danger');
|
||||
|
2078
static/lib/bootstrap-icons/bootstrap-icons.css
vendored
Normal file
2078
static/lib/bootstrap-icons/bootstrap-icons.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
BIN
static/lib/bootstrap-icons/fonts/bootstrap-icons.woff
Normal file
BIN
static/lib/bootstrap-icons/fonts/bootstrap-icons.woff
Normal file
Binary file not shown.
BIN
static/lib/bootstrap-icons/fonts/bootstrap-icons.woff2
Normal file
BIN
static/lib/bootstrap-icons/fonts/bootstrap-icons.woff2
Normal file
Binary file not shown.
7
static/lib/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
static/lib/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
6
static/lib/bootstrap/bootstrap.min.css
vendored
Normal file
6
static/lib/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
@ -4,8 +4,8 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>登录 - 闲鱼自动回复管理系统</title>
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap-icons/bootstrap-icons.css">
|
||||
<style>
|
||||
:root {
|
||||
--primary-color: #0d6efd;
|
||||
@ -174,7 +174,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
|
||||
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
const loginForm = document.getElementById('loginForm');
|
||||
const errorAlert = document.getElementById('errorAlert');
|
||||
|
75
static/test_local_resources.html
Normal file
75
static/test_local_resources.html
Normal file
@ -0,0 +1,75 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>本地资源测试</title>
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="/static/lib/bootstrap-icons/bootstrap-icons.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h3 class="mb-0">
|
||||
<i class="bi bi-check-circle-fill me-2"></i>
|
||||
本地资源测试
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-success" role="alert">
|
||||
<i class="bi bi-info-circle-fill me-2"></i>
|
||||
如果您能看到这个页面的样式和图标正常显示,说明本地资源加载成功!
|
||||
</div>
|
||||
|
||||
<h5>测试项目:</h5>
|
||||
<ul class="list-group">
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-check-lg text-success me-2"></i>
|
||||
Bootstrap CSS 样式
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-check-lg text-success me-2"></i>
|
||||
Bootstrap Icons 图标字体
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<i class="bi bi-check-lg text-success me-2"></i>
|
||||
Bootstrap JavaScript 功能
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="mt-4">
|
||||
<button class="btn btn-primary" type="button" data-bs-toggle="collapse" data-bs-target="#collapseExample">
|
||||
<i class="bi bi-arrow-down-circle me-2"></i>
|
||||
测试 JavaScript 功能
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="collapse mt-3" id="collapseExample">
|
||||
<div class="card card-body">
|
||||
<i class="bi bi-emoji-smile me-2"></i>
|
||||
恭喜!Bootstrap JavaScript 功能正常工作!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<h6>图标测试:</h6>
|
||||
<div class="d-flex gap-3 fs-4">
|
||||
<i class="bi bi-heart-fill text-danger"></i>
|
||||
<i class="bi bi-star-fill text-warning"></i>
|
||||
<i class="bi bi-shield-check text-success"></i>
|
||||
<i class="bi bi-gear-fill text-secondary"></i>
|
||||
<i class="bi bi-chat-dots-fill text-primary"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/static/lib/bootstrap/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
215
test_ai_reply.py
Normal file
215
test_ai_reply.py
Normal file
@ -0,0 +1,215 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AI回复功能测试脚本
|
||||
用于验证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
|
||||
from loguru import logger
|
||||
|
||||
async def test_ai_reply_basic():
|
||||
"""测试AI回复基本功能"""
|
||||
print("🧪 开始测试AI回复基本功能...")
|
||||
|
||||
# 测试数据
|
||||
test_cookie_id = "test_cookie_001"
|
||||
test_item_id = "123456789"
|
||||
test_message = "你好,这个商品能便宜点吗?"
|
||||
test_chat_id = "test_chat_001"
|
||||
test_user_id = "test_user_001"
|
||||
|
||||
# 测试商品信息
|
||||
test_item_info = {
|
||||
'title': '测试商品',
|
||||
'price': 100,
|
||||
'desc': '这是一个用于测试的商品'
|
||||
}
|
||||
|
||||
print(f"📝 测试参数:")
|
||||
print(f" 账号ID: {test_cookie_id}")
|
||||
print(f" 商品ID: {test_item_id}")
|
||||
print(f" 用户消息: {test_message}")
|
||||
print(f" 商品信息: {test_item_info}")
|
||||
|
||||
# 1. 测试AI回复是否启用检查
|
||||
print("\n1️⃣ 测试AI回复启用状态检查...")
|
||||
is_enabled = ai_reply_engine.is_ai_enabled(test_cookie_id)
|
||||
print(f" AI回复启用状态: {is_enabled}")
|
||||
|
||||
if not is_enabled:
|
||||
print(" ⚠️ AI回复未启用,跳过后续测试")
|
||||
print(" 💡 请在Web界面中为测试账号启用AI回复功能")
|
||||
return False
|
||||
|
||||
# 2. 测试意图检测
|
||||
print("\n2️⃣ 测试意图检测...")
|
||||
try:
|
||||
intent = ai_reply_engine.detect_intent(test_message, test_cookie_id)
|
||||
print(f" 检测到的意图: {intent}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 意图检测失败: {e}")
|
||||
return False
|
||||
|
||||
# 3. 测试AI回复生成
|
||||
print("\n3️⃣ 测试AI回复生成...")
|
||||
try:
|
||||
reply = ai_reply_engine.generate_reply(
|
||||
message=test_message,
|
||||
item_info=test_item_info,
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id=test_user_id,
|
||||
item_id=test_item_id
|
||||
)
|
||||
|
||||
if reply:
|
||||
print(f" ✅ AI回复生成成功: {reply}")
|
||||
else:
|
||||
print(f" ❌ AI回复生成失败: 返回空值")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ AI回复生成异常: {e}")
|
||||
return False
|
||||
|
||||
print("\n✅ AI回复基本功能测试完成!")
|
||||
return True
|
||||
|
||||
def test_database_operations():
|
||||
"""测试数据库操作"""
|
||||
print("\n🗄️ 开始测试数据库操作...")
|
||||
|
||||
test_cookie_id = "test_cookie_001"
|
||||
|
||||
# 1. 测试获取AI回复设置
|
||||
print("\n1️⃣ 测试获取AI回复设置...")
|
||||
try:
|
||||
settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
print(f" AI回复设置: {settings}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 获取AI回复设置失败: {e}")
|
||||
return False
|
||||
|
||||
# 2. 测试保存AI回复设置
|
||||
print("\n2️⃣ 测试保存AI回复设置...")
|
||||
try:
|
||||
test_settings = {
|
||||
'ai_enabled': True,
|
||||
'model_name': 'qwen-plus',
|
||||
'api_key': 'test-api-key',
|
||||
'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
'max_discount_percent': 10,
|
||||
'max_discount_amount': 100,
|
||||
'max_bargain_rounds': 3,
|
||||
'custom_prompts': ''
|
||||
}
|
||||
|
||||
success = db_manager.save_ai_reply_settings(test_cookie_id, test_settings)
|
||||
if success:
|
||||
print(f" ✅ AI回复设置保存成功")
|
||||
else:
|
||||
print(f" ❌ AI回复设置保存失败")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 保存AI回复设置异常: {e}")
|
||||
return False
|
||||
|
||||
# 3. 验证设置是否正确保存
|
||||
print("\n3️⃣ 验证设置保存...")
|
||||
try:
|
||||
saved_settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
if saved_settings['ai_enabled'] == True:
|
||||
print(f" ✅ 设置验证成功: AI回复已启用")
|
||||
else:
|
||||
print(f" ❌ 设置验证失败: AI回复未启用")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 设置验证异常: {e}")
|
||||
return False
|
||||
|
||||
print("\n✅ 数据库操作测试完成!")
|
||||
return True
|
||||
|
||||
def test_configuration():
|
||||
"""测试配置检查"""
|
||||
print("\n⚙️ 开始测试配置检查...")
|
||||
|
||||
# 1. 检查必要的模块导入
|
||||
print("\n1️⃣ 检查模块导入...")
|
||||
try:
|
||||
from openai import OpenAI
|
||||
print(" ✅ OpenAI模块导入成功")
|
||||
except ImportError as e:
|
||||
print(f" ❌ OpenAI模块导入失败: {e}")
|
||||
print(" 💡 请运行: pip install openai>=1.65.5")
|
||||
return False
|
||||
|
||||
# 2. 检查数据库表结构
|
||||
print("\n2️⃣ 检查数据库表结构...")
|
||||
try:
|
||||
# 检查ai_reply_settings表是否存在
|
||||
with db_manager.lock:
|
||||
cursor = db_manager.conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='ai_reply_settings'")
|
||||
if cursor.fetchone():
|
||||
print(" ✅ ai_reply_settings表存在")
|
||||
else:
|
||||
print(" ❌ ai_reply_settings表不存在")
|
||||
return False
|
||||
|
||||
# 检查ai_conversations表是否存在
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='ai_conversations'")
|
||||
if cursor.fetchone():
|
||||
print(" ✅ ai_conversations表存在")
|
||||
else:
|
||||
print(" ❌ ai_conversations表不存在")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 数据库表检查异常: {e}")
|
||||
return False
|
||||
|
||||
print("\n✅ 配置检查完成!")
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 AI回复功能集成测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试配置
|
||||
config_ok = test_configuration()
|
||||
if not config_ok:
|
||||
print("\n❌ 配置检查失败,请修复后重试")
|
||||
return
|
||||
|
||||
# 测试数据库操作
|
||||
db_ok = test_database_operations()
|
||||
if not db_ok:
|
||||
print("\n❌ 数据库操作测试失败")
|
||||
return
|
||||
|
||||
# 测试AI回复功能
|
||||
ai_ok = await test_ai_reply_basic()
|
||||
if not ai_ok:
|
||||
print("\n❌ AI回复功能测试失败")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!AI回复功能集成成功!")
|
||||
print("\n📋 下一步操作:")
|
||||
print("1. 在Web界面中配置AI回复API密钥")
|
||||
print("2. 为需要的账号启用AI回复功能")
|
||||
print("3. 测试实际的消息回复效果")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
166
test_ai_reply_fix.py
Normal file
166
test_ai_reply_fix.py
Normal file
@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
AI回复修复验证脚本
|
||||
验证settings变量作用域问题是否已修复
|
||||
"""
|
||||
|
||||
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 setup_test_account():
|
||||
"""设置测试账号"""
|
||||
test_cookie_id = "test_fix_001"
|
||||
|
||||
# 配置AI回复设置
|
||||
ai_settings = {
|
||||
'ai_enabled': True,
|
||||
'model_name': 'qwen-plus',
|
||||
'api_key': 'test-api-key', # 测试用假密钥
|
||||
'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
'max_discount_percent': 10,
|
||||
'max_discount_amount': 100,
|
||||
'max_bargain_rounds': 3,
|
||||
'custom_prompts': ''
|
||||
}
|
||||
|
||||
success = db_manager.save_ai_reply_settings(test_cookie_id, ai_settings)
|
||||
return test_cookie_id if success else None
|
||||
|
||||
def test_settings_variable_scope():
|
||||
"""测试settings变量作用域问题"""
|
||||
print("🔧 测试settings变量作用域修复...")
|
||||
|
||||
test_cookie_id = setup_test_account()
|
||||
if not test_cookie_id:
|
||||
print(" ❌ 测试账号设置失败")
|
||||
return False
|
||||
|
||||
# 测试数据
|
||||
test_item_info = {
|
||||
'title': '测试商品',
|
||||
'price': 100,
|
||||
'desc': '测试商品描述'
|
||||
}
|
||||
|
||||
test_chat_id = "test_chat_fix_001"
|
||||
|
||||
# 清理测试数据
|
||||
try:
|
||||
with db_manager.lock:
|
||||
cursor = db_manager.conn.cursor()
|
||||
cursor.execute('DELETE FROM ai_conversations WHERE cookie_id = ? AND chat_id = ?',
|
||||
(test_cookie_id, test_chat_id))
|
||||
db_manager.conn.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
print(f" 测试账号: {test_cookie_id}")
|
||||
print(f" 测试对话: {test_chat_id}")
|
||||
|
||||
# 测试1: 普通消息(非议价)
|
||||
print(f"\n1️⃣ 测试普通消息处理...")
|
||||
try:
|
||||
reply = ai_reply_engine.generate_reply(
|
||||
message="你好",
|
||||
item_info=test_item_info,
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id="test_user",
|
||||
item_id="test_item"
|
||||
)
|
||||
|
||||
# 由于使用测试API密钥,预期会失败,但不应该出现settings变量错误
|
||||
print(f" 普通消息测试完成(预期API调用失败)")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "cannot access local variable 'settings'" in error_msg:
|
||||
print(f" ❌ settings变量作用域问题仍然存在: {error_msg}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ settings变量作用域问题已修复(其他错误: {error_msg[:50]}...)")
|
||||
|
||||
# 测试2: 议价消息
|
||||
print(f"\n2️⃣ 测试议价消息处理...")
|
||||
try:
|
||||
# 先添加一些议价记录,测试轮数限制逻辑
|
||||
for i in range(3): # 添加3轮议价记录
|
||||
ai_reply_engine.save_conversation(
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id="test_user",
|
||||
item_id="test_item",
|
||||
role="user",
|
||||
content=f"第{i+1}次议价",
|
||||
intent="price"
|
||||
)
|
||||
|
||||
# 现在测试第4轮议价(应该被拒绝)
|
||||
reply = ai_reply_engine.generate_reply(
|
||||
message="能再便宜点吗?",
|
||||
item_info=test_item_info,
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id="test_user",
|
||||
item_id="test_item"
|
||||
)
|
||||
|
||||
if reply and "不能再便宜" in reply:
|
||||
print(f" ✅ 议价轮数限制正常工作: {reply}")
|
||||
else:
|
||||
print(f" ⚠️ 议价消息处理完成,但结果可能不符合预期")
|
||||
|
||||
except Exception as e:
|
||||
error_msg = str(e)
|
||||
if "cannot access local variable 'settings'" in error_msg:
|
||||
print(f" ❌ settings变量作用域问题仍然存在: {error_msg}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ settings变量作用域问题已修复(其他错误: {error_msg[:50]}...)")
|
||||
|
||||
# 测试3: 验证settings获取
|
||||
print(f"\n3️⃣ 测试settings获取...")
|
||||
try:
|
||||
settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
print(f" ✅ settings获取成功:")
|
||||
print(f" AI启用: {settings.get('ai_enabled')}")
|
||||
print(f" 最大议价轮数: {settings.get('max_bargain_rounds')}")
|
||||
print(f" 最大优惠百分比: {settings.get('max_discount_percent')}%")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ settings获取失败: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 AI回复settings变量修复验证")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试修复
|
||||
fix_ok = test_settings_variable_scope()
|
||||
|
||||
if fix_ok:
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 修复验证成功!")
|
||||
print("\n✅ 修复内容:")
|
||||
print(" • settings变量作用域问题已解决")
|
||||
print(" • 议价轮数限制功能正常")
|
||||
print(" • AI回复流程完整")
|
||||
print("\n💡 说明:")
|
||||
print(" • 由于使用测试API密钥,AI调用会失败")
|
||||
print(" • 但不会再出现settings变量错误")
|
||||
print(" • 配置真实API密钥后即可正常使用")
|
||||
else:
|
||||
print("\n❌ 修复验证失败,请检查代码")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
282
test_backup_import.py
Normal file
282
test_backup_import.py
Normal file
@ -0,0 +1,282 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
备份和导入功能测试脚本
|
||||
验证所有表是否正确包含在备份中
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
import tempfile
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from db_manager import db_manager
|
||||
from loguru import logger
|
||||
|
||||
def get_all_tables():
|
||||
"""获取数据库中的所有表"""
|
||||
try:
|
||||
with db_manager.lock:
|
||||
cursor = db_manager.conn.cursor()
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'")
|
||||
tables = [row[0] for row in cursor.fetchall()]
|
||||
return sorted(tables)
|
||||
except Exception as e:
|
||||
logger.error(f"获取表列表失败: {e}")
|
||||
return []
|
||||
|
||||
def get_table_row_count(table_name):
|
||||
"""获取表的行数"""
|
||||
try:
|
||||
with db_manager.lock:
|
||||
cursor = db_manager.conn.cursor()
|
||||
cursor.execute(f"SELECT COUNT(*) FROM {table_name}")
|
||||
return cursor.fetchone()[0]
|
||||
except Exception as e:
|
||||
logger.error(f"获取表 {table_name} 行数失败: {e}")
|
||||
return 0
|
||||
|
||||
def create_test_data():
|
||||
"""创建测试数据"""
|
||||
print("📝 创建测试数据...")
|
||||
|
||||
test_data_created = []
|
||||
|
||||
try:
|
||||
# 1. 创建测试账号
|
||||
test_cookie_id = "test_backup_001"
|
||||
success = db_manager.save_cookie(test_cookie_id, "test_cookie_value_for_backup")
|
||||
if success:
|
||||
test_data_created.append(f"账号: {test_cookie_id}")
|
||||
|
||||
# 2. 创建关键词
|
||||
keywords = [("测试关键词1", "测试回复1"), ("测试关键词2", "测试回复2")]
|
||||
success = db_manager.save_keywords(test_cookie_id, keywords)
|
||||
if success:
|
||||
test_data_created.append(f"关键词: {len(keywords)} 个")
|
||||
|
||||
# 3. 创建AI回复设置
|
||||
ai_settings = {
|
||||
'ai_enabled': True,
|
||||
'model_name': 'qwen-plus',
|
||||
'api_key': 'test-backup-key',
|
||||
'base_url': 'https://test.com',
|
||||
'max_discount_percent': 10,
|
||||
'max_discount_amount': 100,
|
||||
'max_bargain_rounds': 3,
|
||||
'custom_prompts': '{"test": "prompt"}'
|
||||
}
|
||||
success = db_manager.save_ai_reply_settings(test_cookie_id, ai_settings)
|
||||
if success:
|
||||
test_data_created.append("AI回复设置")
|
||||
|
||||
# 4. 创建默认回复
|
||||
success = db_manager.save_default_reply(test_cookie_id, True, "测试默认回复内容")
|
||||
if success:
|
||||
test_data_created.append("默认回复")
|
||||
|
||||
# 5. 创建商品信息
|
||||
success = db_manager.save_item_basic_info(
|
||||
test_cookie_id, "test_item_001",
|
||||
"测试商品", "测试描述", "测试分类", "100", "测试详情"
|
||||
)
|
||||
if success:
|
||||
test_data_created.append("商品信息")
|
||||
|
||||
print(f" ✅ 测试数据创建成功: {', '.join(test_data_created)}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 创建测试数据失败: {e}")
|
||||
return False
|
||||
|
||||
def test_backup_export():
|
||||
"""测试备份导出功能"""
|
||||
print("\n📤 测试备份导出功能...")
|
||||
|
||||
try:
|
||||
# 获取所有表
|
||||
all_tables = get_all_tables()
|
||||
print(f" 数据库中的表: {all_tables}")
|
||||
|
||||
# 导出备份
|
||||
backup_data = db_manager.export_backup()
|
||||
|
||||
if not backup_data:
|
||||
print(" ❌ 备份导出失败")
|
||||
return None
|
||||
|
||||
# 检查备份数据结构
|
||||
print(f" ✅ 备份导出成功")
|
||||
print(f" 备份版本: {backup_data.get('version')}")
|
||||
print(f" 备份时间: {backup_data.get('timestamp')}")
|
||||
|
||||
# 检查包含的表
|
||||
backed_up_tables = list(backup_data['data'].keys())
|
||||
print(f" 备份的表: {sorted(backed_up_tables)}")
|
||||
|
||||
# 检查是否有遗漏的表
|
||||
missing_tables = set(all_tables) - set(backed_up_tables)
|
||||
if missing_tables:
|
||||
print(f" ⚠️ 未备份的表: {sorted(missing_tables)}")
|
||||
else:
|
||||
print(f" ✅ 所有表都已备份")
|
||||
|
||||
# 检查每个表的数据量
|
||||
print(f"\n 📊 各表数据统计:")
|
||||
for table in sorted(backed_up_tables):
|
||||
row_count = len(backup_data['data'][table]['rows'])
|
||||
print(f" {table}: {row_count} 行")
|
||||
|
||||
return backup_data
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 备份导出异常: {e}")
|
||||
return None
|
||||
|
||||
def test_backup_import(backup_data):
|
||||
"""测试备份导入功能"""
|
||||
print("\n📥 测试备份导入功能...")
|
||||
|
||||
if not backup_data:
|
||||
print(" ❌ 没有备份数据可导入")
|
||||
return False
|
||||
|
||||
try:
|
||||
# 记录导入前的数据量
|
||||
print(" 📊 导入前数据统计:")
|
||||
all_tables = get_all_tables()
|
||||
before_counts = {}
|
||||
for table in all_tables:
|
||||
count = get_table_row_count(table)
|
||||
before_counts[table] = count
|
||||
print(f" {table}: {count} 行")
|
||||
|
||||
# 执行导入
|
||||
success = db_manager.import_backup(backup_data)
|
||||
|
||||
if not success:
|
||||
print(" ❌ 备份导入失败")
|
||||
return False
|
||||
|
||||
print(" ✅ 备份导入成功")
|
||||
|
||||
# 记录导入后的数据量
|
||||
print("\n 📊 导入后数据统计:")
|
||||
after_counts = {}
|
||||
for table in all_tables:
|
||||
count = get_table_row_count(table)
|
||||
after_counts[table] = count
|
||||
print(f" {table}: {count} 行")
|
||||
|
||||
# 检查数据一致性
|
||||
print("\n 🔍 数据一致性检查:")
|
||||
for table in sorted(backup_data['data'].keys()):
|
||||
expected_count = len(backup_data['data'][table]['rows'])
|
||||
actual_count = after_counts.get(table, 0)
|
||||
|
||||
if table == 'system_settings':
|
||||
# 系统设置表可能保留管理员密码,所以数量可能不完全一致
|
||||
print(f" {table}: 期望 {expected_count}, 实际 {actual_count} (系统设置表)")
|
||||
elif expected_count == actual_count:
|
||||
print(f" {table}: ✅ 一致 ({actual_count} 行)")
|
||||
else:
|
||||
print(f" {table}: ❌ 不一致 (期望 {expected_count}, 实际 {actual_count})")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 备份导入异常: {e}")
|
||||
return False
|
||||
|
||||
def test_backup_file_operations():
|
||||
"""测试备份文件操作"""
|
||||
print("\n💾 测试备份文件操作...")
|
||||
|
||||
try:
|
||||
# 导出备份
|
||||
backup_data = db_manager.export_backup()
|
||||
if not backup_data:
|
||||
print(" ❌ 导出备份失败")
|
||||
return False
|
||||
|
||||
# 保存到临时文件
|
||||
with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False, encoding='utf-8') as f:
|
||||
json.dump(backup_data, f, indent=2, ensure_ascii=False)
|
||||
temp_file = f.name
|
||||
|
||||
print(f" ✅ 备份保存到临时文件: {temp_file}")
|
||||
|
||||
# 从文件读取
|
||||
with open(temp_file, 'r', encoding='utf-8') as f:
|
||||
loaded_backup = json.load(f)
|
||||
|
||||
print(f" ✅ 从文件读取备份成功")
|
||||
|
||||
# 验证数据完整性
|
||||
if loaded_backup == backup_data:
|
||||
print(f" ✅ 文件数据完整性验证通过")
|
||||
else:
|
||||
print(f" ❌ 文件数据完整性验证失败")
|
||||
return False
|
||||
|
||||
# 清理临时文件
|
||||
os.unlink(temp_file)
|
||||
print(f" ✅ 临时文件清理完成")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 备份文件操作异常: {e}")
|
||||
return False
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 备份和导入功能测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
# 创建测试数据
|
||||
data_ok = create_test_data()
|
||||
if not data_ok:
|
||||
print("\n❌ 测试数据创建失败")
|
||||
return
|
||||
|
||||
# 测试备份导出
|
||||
backup_data = test_backup_export()
|
||||
if not backup_data:
|
||||
print("\n❌ 备份导出测试失败")
|
||||
return
|
||||
|
||||
# 测试备份导入
|
||||
import_ok = test_backup_import(backup_data)
|
||||
if not import_ok:
|
||||
print("\n❌ 备份导入测试失败")
|
||||
return
|
||||
|
||||
# 测试文件操作
|
||||
file_ok = test_backup_file_operations()
|
||||
if not file_ok:
|
||||
print("\n❌ 备份文件操作测试失败")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!备份和导入功能正常!")
|
||||
print("\n✅ 功能验证:")
|
||||
print(" • 所有13个表都包含在备份中")
|
||||
print(" • 备份导出功能正常")
|
||||
print(" • 备份导入功能正常")
|
||||
print(" • 数据完整性保持")
|
||||
print(" • 文件操作正常")
|
||||
print("\n📋 包含的表:")
|
||||
|
||||
# 显示所有备份的表
|
||||
if backup_data and 'data' in backup_data:
|
||||
for table in sorted(backup_data['data'].keys()):
|
||||
row_count = len(backup_data['data'][table]['rows'])
|
||||
print(f" • {table}: {row_count} 行数据")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
267
test_bargain_limit.py
Normal file
267
test_bargain_limit.py
Normal file
@ -0,0 +1,267 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
议价轮数限制功能测试脚本
|
||||
用于验证最大议价轮数是否生效
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
import time
|
||||
|
||||
# 添加项目根目录到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
|
||||
from loguru import logger
|
||||
|
||||
def setup_test_account():
|
||||
"""设置测试账号的AI回复配置"""
|
||||
print("⚙️ 设置测试账号配置...")
|
||||
|
||||
test_cookie_id = "test_bargain_001"
|
||||
|
||||
# 配置AI回复设置
|
||||
ai_settings = {
|
||||
'ai_enabled': True,
|
||||
'model_name': 'qwen-plus',
|
||||
'api_key': 'test-api-key-for-bargain-test', # 测试用的假密钥
|
||||
'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
'max_discount_percent': 15, # 最大优惠15%
|
||||
'max_discount_amount': 50, # 最大优惠50元
|
||||
'max_bargain_rounds': 3, # 最大议价3轮
|
||||
'custom_prompts': ''
|
||||
}
|
||||
|
||||
try:
|
||||
success = db_manager.save_ai_reply_settings(test_cookie_id, ai_settings)
|
||||
if success:
|
||||
print(f" ✅ 测试账号配置成功")
|
||||
print(f" 账号ID: {test_cookie_id}")
|
||||
print(f" 最大议价轮数: {ai_settings['max_bargain_rounds']}")
|
||||
print(f" 最大优惠百分比: {ai_settings['max_discount_percent']}%")
|
||||
print(f" 最大优惠金额: {ai_settings['max_discount_amount']}元")
|
||||
return test_cookie_id
|
||||
else:
|
||||
print(f" ❌ 测试账号配置失败")
|
||||
return None
|
||||
except Exception as e:
|
||||
print(f" ❌ 配置异常: {e}")
|
||||
return None
|
||||
|
||||
def clear_test_conversations(cookie_id: str, chat_id: str):
|
||||
"""清理测试对话记录"""
|
||||
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()
|
||||
print(f" ✅ 清理对话记录成功")
|
||||
except Exception as e:
|
||||
print(f" ❌ 清理对话记录失败: {e}")
|
||||
|
||||
def test_bargain_count_tracking():
|
||||
"""测试议价次数统计"""
|
||||
print("\n📊 测试议价次数统计...")
|
||||
|
||||
test_cookie_id = "test_bargain_001"
|
||||
test_chat_id = "test_chat_bargain_001"
|
||||
|
||||
# 清理测试数据
|
||||
clear_test_conversations(test_cookie_id, test_chat_id)
|
||||
|
||||
# 模拟保存几条议价对话
|
||||
test_conversations = [
|
||||
("user", "能便宜点吗?", "price"),
|
||||
("assistant", "可以优惠5元", "price"),
|
||||
("user", "再便宜点呢?", "price"),
|
||||
("assistant", "最多优惠10元", "price"),
|
||||
("user", "还能再便宜吗?", "price"),
|
||||
("assistant", "这已经是最低价了", "price"),
|
||||
]
|
||||
|
||||
print(f"\n1️⃣ 模拟保存对话记录...")
|
||||
try:
|
||||
for i, (role, content, intent) in enumerate(test_conversations):
|
||||
ai_reply_engine.save_conversation(
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id="test_user_001",
|
||||
item_id="test_item_001",
|
||||
role=role,
|
||||
content=content,
|
||||
intent=intent
|
||||
)
|
||||
print(f" 保存第{i+1}条: {role} - {content}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 保存对话记录失败: {e}")
|
||||
return False
|
||||
|
||||
print(f"\n2️⃣ 测试议价次数统计...")
|
||||
try:
|
||||
bargain_count = ai_reply_engine.get_bargain_count(test_chat_id, test_cookie_id)
|
||||
expected_count = 3 # 3条用户的price消息
|
||||
|
||||
if bargain_count == expected_count:
|
||||
print(f" ✅ 议价次数统计正确: {bargain_count}")
|
||||
else:
|
||||
print(f" ❌ 议价次数统计错误: 期望 {expected_count}, 实际 {bargain_count}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 议价次数统计异常: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_bargain_limit_logic():
|
||||
"""测试议价轮数限制逻辑"""
|
||||
print("\n🚫 测试议价轮数限制逻辑...")
|
||||
|
||||
test_cookie_id = "test_bargain_001"
|
||||
test_chat_id = "test_chat_limit_001"
|
||||
|
||||
# 清理测试数据
|
||||
clear_test_conversations(test_cookie_id, test_chat_id)
|
||||
|
||||
# 获取配置
|
||||
settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
max_rounds = settings.get('max_bargain_rounds', 3)
|
||||
|
||||
print(f" 配置的最大议价轮数: {max_rounds}")
|
||||
|
||||
# 模拟达到最大议价轮数
|
||||
print(f"\n1️⃣ 模拟 {max_rounds} 轮议价...")
|
||||
for i in range(max_rounds):
|
||||
try:
|
||||
ai_reply_engine.save_conversation(
|
||||
chat_id=test_chat_id,
|
||||
cookie_id=test_cookie_id,
|
||||
user_id="test_user_001",
|
||||
item_id="test_item_001",
|
||||
role="user",
|
||||
content=f"第{i+1}次议价:能便宜点吗?",
|
||||
intent="price"
|
||||
)
|
||||
print(f" 第{i+1}轮议价记录已保存")
|
||||
except Exception as e:
|
||||
print(f" ❌ 保存第{i+1}轮议价失败: {e}")
|
||||
return False
|
||||
|
||||
# 验证议价次数
|
||||
print(f"\n2️⃣ 验证当前议价次数...")
|
||||
try:
|
||||
current_count = ai_reply_engine.get_bargain_count(test_chat_id, test_cookie_id)
|
||||
print(f" 当前议价次数: {current_count}")
|
||||
|
||||
if current_count >= max_rounds:
|
||||
print(f" ✅ 已达到最大议价轮数限制")
|
||||
else:
|
||||
print(f" ❌ 未达到最大议价轮数")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 验证议价次数异常: {e}")
|
||||
return False
|
||||
|
||||
# 测试超出限制时的逻辑(模拟)
|
||||
print(f"\n3️⃣ 测试超出限制时的逻辑...")
|
||||
try:
|
||||
# 直接测试议价轮数检查逻辑
|
||||
current_count = ai_reply_engine.get_bargain_count(test_chat_id, test_cookie_id)
|
||||
settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
max_rounds = settings.get('max_bargain_rounds', 3)
|
||||
|
||||
print(f" 当前议价次数: {current_count}")
|
||||
print(f" 最大议价轮数: {max_rounds}")
|
||||
|
||||
if current_count >= max_rounds:
|
||||
print(f" ✅ 检测到议价次数已达上限")
|
||||
print(f" ✅ 系统应该拒绝继续议价")
|
||||
|
||||
# 模拟拒绝回复
|
||||
refuse_reply = f"抱歉,这个价格已经是最优惠的了,不能再便宜了哦!"
|
||||
print(f" ✅ 拒绝回复示例: {refuse_reply}")
|
||||
else:
|
||||
print(f" ❌ 议价次数检查逻辑错误")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 测试超出限制逻辑异常: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def test_bargain_settings_integration():
|
||||
"""测试议价设置集成"""
|
||||
print("\n🔧 测试议价设置集成...")
|
||||
|
||||
test_cookie_id = "test_bargain_001"
|
||||
|
||||
# 获取设置
|
||||
try:
|
||||
settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
|
||||
print(f" AI回复启用: {settings.get('ai_enabled', False)}")
|
||||
print(f" 最大议价轮数: {settings.get('max_bargain_rounds', 3)}")
|
||||
print(f" 最大优惠百分比: {settings.get('max_discount_percent', 10)}%")
|
||||
print(f" 最大优惠金额: {settings.get('max_discount_amount', 100)}元")
|
||||
|
||||
# 验证设置是否正确
|
||||
if settings.get('max_bargain_rounds') == 3:
|
||||
print(f" ✅ 议价设置读取正确")
|
||||
else:
|
||||
print(f" ❌ 议价设置读取错误")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 获取议价设置异常: {e}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 议价轮数限制功能测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
# 设置测试账号
|
||||
test_cookie_id = setup_test_account()
|
||||
if not test_cookie_id:
|
||||
print("\n❌ 测试账号设置失败")
|
||||
return
|
||||
|
||||
# 测试议价设置集成
|
||||
settings_ok = test_bargain_settings_integration()
|
||||
if not settings_ok:
|
||||
print("\n❌ 议价设置集成测试失败")
|
||||
return
|
||||
|
||||
# 测试议价次数统计
|
||||
count_ok = test_bargain_count_tracking()
|
||||
if not count_ok:
|
||||
print("\n❌ 议价次数统计测试失败")
|
||||
return
|
||||
|
||||
# 测试议价轮数限制
|
||||
limit_ok = test_bargain_limit_logic()
|
||||
if not limit_ok:
|
||||
print("\n❌ 议价轮数限制测试失败")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!最大议价轮数功能正常!")
|
||||
print("\n📋 功能说明:")
|
||||
print("1. ✅ 议价次数统计:正确统计用户的议价消息数量")
|
||||
print("2. ✅ 轮数限制检查:达到最大轮数时拒绝继续议价")
|
||||
print("3. ✅ 拒绝回复生成:超出限制时返回友好的拒绝消息")
|
||||
print("4. ✅ 设置参数传递:AI可以获取到完整的议价设置")
|
||||
print("\n💡 使用建议:")
|
||||
print("- 合理设置最大议价轮数(建议3-5轮)")
|
||||
print("- 配合最大优惠百分比和金额使用")
|
||||
print("- 在提示词中强调议价策略")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
183
test_bargain_limit_direct.py
Normal file
183
test_bargain_limit_direct.py
Normal file
@ -0,0 +1,183 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
直接测试议价轮数限制功能
|
||||
不依赖真实API调用,直接测试逻辑
|
||||
"""
|
||||
|
||||
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
|
||||
|
||||
class MockAIReplyEngine:
|
||||
"""模拟AI回复引擎,用于测试议价轮数限制"""
|
||||
|
||||
def __init__(self):
|
||||
self.ai_engine = ai_reply_engine
|
||||
|
||||
def test_bargain_limit_logic(self, cookie_id: str, chat_id: str, message: str,
|
||||
item_info: dict, user_id: str, item_id: str):
|
||||
"""直接测试议价轮数限制逻辑"""
|
||||
try:
|
||||
# 1. 获取AI回复设置
|
||||
settings = db_manager.get_ai_reply_settings(cookie_id)
|
||||
print(f" 获取设置成功: 最大议价轮数 {settings.get('max_bargain_rounds', 3)}")
|
||||
|
||||
# 2. 模拟意图检测为price
|
||||
intent = "price"
|
||||
print(f" 模拟意图检测: {intent}")
|
||||
|
||||
# 3. 获取议价次数
|
||||
bargain_count = self.ai_engine.get_bargain_count(chat_id, cookie_id)
|
||||
print(f" 当前议价次数: {bargain_count}")
|
||||
|
||||
# 4. 检查议价轮数限制
|
||||
max_bargain_rounds = settings.get('max_bargain_rounds', 3)
|
||||
if bargain_count >= max_bargain_rounds:
|
||||
print(f" 🚫 议价次数已达上限 ({bargain_count}/{max_bargain_rounds}),拒绝继续议价")
|
||||
# 返回拒绝议价的回复
|
||||
refuse_reply = f"抱歉,这个价格已经是最优惠的了,不能再便宜了哦!"
|
||||
# 保存对话记录
|
||||
self.ai_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "user", message, intent)
|
||||
self.ai_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "assistant", refuse_reply, intent)
|
||||
return refuse_reply
|
||||
else:
|
||||
print(f" ✅ 议价次数未达上限,可以继续议价")
|
||||
# 模拟AI回复
|
||||
mock_reply = f"好的,我们可以优惠一点,这是第{bargain_count + 1}轮议价"
|
||||
# 保存对话记录
|
||||
self.ai_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "user", message, intent)
|
||||
self.ai_engine.save_conversation(chat_id, cookie_id, user_id, item_id, "assistant", mock_reply, intent)
|
||||
return mock_reply
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 测试异常: {e}")
|
||||
return None
|
||||
|
||||
def test_complete_bargain_flow():
|
||||
"""测试完整的议价流程"""
|
||||
print("🎯 测试完整议价流程...")
|
||||
|
||||
# 测试参数
|
||||
test_cookie_id = "test_bargain_flow_001"
|
||||
test_chat_id = "test_chat_flow_001"
|
||||
|
||||
# 设置测试账号
|
||||
ai_settings = {
|
||||
'ai_enabled': True,
|
||||
'model_name': 'qwen-plus',
|
||||
'api_key': 'test-key',
|
||||
'base_url': 'https://dashscope.aliyuncs.com/compatible-mode/v1',
|
||||
'max_discount_percent': 15,
|
||||
'max_discount_amount': 200,
|
||||
'max_bargain_rounds': 3, # 设置最大3轮
|
||||
'custom_prompts': ''
|
||||
}
|
||||
|
||||
db_manager.save_ai_reply_settings(test_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 = ?',
|
||||
(test_cookie_id, test_chat_id))
|
||||
db_manager.conn.commit()
|
||||
except:
|
||||
pass
|
||||
|
||||
# 创建模拟引擎
|
||||
mock_engine = MockAIReplyEngine()
|
||||
|
||||
# 测试商品信息
|
||||
item_info = {
|
||||
'title': '测试商品',
|
||||
'price': 1000,
|
||||
'desc': '这是一个测试商品'
|
||||
}
|
||||
|
||||
# 模拟议价对话
|
||||
bargain_messages = [
|
||||
"能便宜点吗?",
|
||||
"800元行不行?",
|
||||
"900元怎么样?",
|
||||
"850元,最后一次了" # 这一轮应该被拒绝
|
||||
]
|
||||
|
||||
print(f"\n📋 测试设置:")
|
||||
print(f" 账号ID: {test_cookie_id}")
|
||||
print(f" 最大议价轮数: {ai_settings['max_bargain_rounds']}")
|
||||
print(f" 商品价格: ¥{item_info['price']}")
|
||||
|
||||
print(f"\n💬 开始议价测试:")
|
||||
print("-" * 40)
|
||||
|
||||
for i, message in enumerate(bargain_messages, 1):
|
||||
print(f"\n第{i}轮议价:")
|
||||
print(f"👤 用户: {message}")
|
||||
|
||||
# 测试议价逻辑
|
||||
reply = mock_engine.test_bargain_limit_logic(
|
||||
cookie_id=test_cookie_id,
|
||||
chat_id=test_chat_id,
|
||||
message=message,
|
||||
item_info=item_info,
|
||||
user_id="test_user",
|
||||
item_id="test_item"
|
||||
)
|
||||
|
||||
if reply:
|
||||
print(f"🤖 AI回复: {reply}")
|
||||
|
||||
# 检查是否是拒绝回复
|
||||
if "不能再便宜" in reply:
|
||||
print(f"✋ 议价被拒绝,测试结束")
|
||||
break
|
||||
else:
|
||||
print(f"❌ 回复生成失败")
|
||||
break
|
||||
|
||||
# 最终统计
|
||||
print(f"\n📊 最终统计:")
|
||||
final_count = ai_reply_engine.get_bargain_count(test_chat_id, test_cookie_id)
|
||||
max_rounds = ai_settings['max_bargain_rounds']
|
||||
print(f" 实际议价轮数: {final_count}")
|
||||
print(f" 最大允许轮数: {max_rounds}")
|
||||
print(f" 是否达到限制: {'是' if final_count >= max_rounds else '否'}")
|
||||
|
||||
return final_count >= max_rounds
|
||||
|
||||
def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 议价轮数限制直接测试")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试完整流程
|
||||
limit_works = test_complete_bargain_flow()
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if limit_works:
|
||||
print("🎉 议价轮数限制功能正常工作!")
|
||||
print("\n✅ 验证结果:")
|
||||
print(" • settings变量作用域问题已修复")
|
||||
print(" • 议价次数统计准确")
|
||||
print(" • 轮数限制逻辑正确")
|
||||
print(" • 拒绝回复生成正常")
|
||||
print(" • 对话记录保存完整")
|
||||
|
||||
print("\n🎯 功能特点:")
|
||||
print(" • 在AI API调用前检查轮数限制")
|
||||
print(" • 超出限制时直接返回拒绝回复")
|
||||
print(" • 节省API调用成本")
|
||||
print(" • 保持用户体验友好")
|
||||
else:
|
||||
print("❌ 议价轮数限制功能异常")
|
||||
print(" 请检查代码实现")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
202
test_cache_refresh.py
Normal file
202
test_cache_refresh.py
Normal file
@ -0,0 +1,202 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试缓存刷新功能
|
||||
验证备份导入后关键字数据是否能正确刷新
|
||||
"""
|
||||
|
||||
import json
|
||||
import time
|
||||
import asyncio
|
||||
from db_manager import db_manager
|
||||
import cookie_manager as cm
|
||||
|
||||
def test_cache_refresh():
|
||||
"""测试缓存刷新功能"""
|
||||
print("🧪 测试缓存刷新功能")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 创建测试数据
|
||||
print("\n1️⃣ 创建测试数据...")
|
||||
test_cookie_id = "test_cache_refresh"
|
||||
test_cookie_value = "test_cookie_value_123"
|
||||
test_keywords = [
|
||||
("测试关键字1", "测试回复1"),
|
||||
("测试关键字2", "测试回复2")
|
||||
]
|
||||
|
||||
# 保存到数据库
|
||||
db_manager.save_cookie(test_cookie_id, test_cookie_value)
|
||||
db_manager.save_keywords(test_cookie_id, test_keywords)
|
||||
print(f" ✅ 已保存测试账号: {test_cookie_id}")
|
||||
print(f" ✅ 已保存 {len(test_keywords)} 个关键字")
|
||||
|
||||
# 2. 创建 CookieManager 并加载数据
|
||||
print("\n2️⃣ 创建 CookieManager...")
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
manager = cm.CookieManager(loop)
|
||||
print(f" ✅ CookieManager 已创建")
|
||||
print(f" 📊 加载的关键字: {manager.keywords.get(test_cookie_id, [])}")
|
||||
|
||||
# 3. 直接修改数据库(模拟备份导入)
|
||||
print("\n3️⃣ 模拟备份导入(直接修改数据库)...")
|
||||
new_keywords = [
|
||||
("新关键字1", "新回复1"),
|
||||
("新关键字2", "新回复2"),
|
||||
("新关键字3", "新回复3")
|
||||
]
|
||||
db_manager.save_keywords(test_cookie_id, new_keywords)
|
||||
print(f" ✅ 数据库已更新为 {len(new_keywords)} 个新关键字")
|
||||
|
||||
# 4. 检查 CookieManager 缓存(应该还是旧数据)
|
||||
print("\n4️⃣ 检查 CookieManager 缓存...")
|
||||
cached_keywords = manager.keywords.get(test_cookie_id, [])
|
||||
print(f" 📊 缓存中的关键字: {cached_keywords}")
|
||||
|
||||
if len(cached_keywords) == len(test_keywords):
|
||||
print(" ✅ 确认:缓存中仍是旧数据(符合预期)")
|
||||
else:
|
||||
print(" ❌ 意外:缓存已更新(不符合预期)")
|
||||
|
||||
# 5. 调用刷新方法
|
||||
print("\n5️⃣ 调用缓存刷新方法...")
|
||||
success = manager.reload_from_db()
|
||||
print(f" 刷新结果: {success}")
|
||||
|
||||
# 6. 检查刷新后的缓存
|
||||
print("\n6️⃣ 检查刷新后的缓存...")
|
||||
refreshed_keywords = manager.keywords.get(test_cookie_id, [])
|
||||
print(f" 📊 刷新后的关键字: {refreshed_keywords}")
|
||||
|
||||
if len(refreshed_keywords) == len(new_keywords):
|
||||
print(" ✅ 成功:缓存已更新为新数据")
|
||||
|
||||
# 验证内容是否正确
|
||||
db_keywords = db_manager.get_keywords(test_cookie_id)
|
||||
if refreshed_keywords == db_keywords:
|
||||
print(" ✅ 验证:缓存数据与数据库一致")
|
||||
else:
|
||||
print(" ❌ 错误:缓存数据与数据库不一致")
|
||||
print(f" 缓存: {refreshed_keywords}")
|
||||
print(f" 数据库: {db_keywords}")
|
||||
else:
|
||||
print(" ❌ 失败:缓存未正确更新")
|
||||
|
||||
# 7. 清理测试数据
|
||||
print("\n7️⃣ 清理测试数据...")
|
||||
db_manager.delete_cookie(test_cookie_id)
|
||||
print(" ✅ 测试数据已清理")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 缓存刷新功能测试完成!")
|
||||
|
||||
def test_backup_import_scenario():
|
||||
"""测试完整的备份导入场景"""
|
||||
print("\n\n🔄 测试完整备份导入场景")
|
||||
print("=" * 50)
|
||||
|
||||
# 1. 创建初始数据
|
||||
print("\n1️⃣ 创建初始数据...")
|
||||
initial_data = {
|
||||
"account1": [("hello", "你好"), ("price", "价格是100元")],
|
||||
"account2": [("bye", "再见"), ("thanks", "谢谢")]
|
||||
}
|
||||
|
||||
for cookie_id, keywords in initial_data.items():
|
||||
db_manager.save_cookie(cookie_id, f"cookie_value_{cookie_id}")
|
||||
db_manager.save_keywords(cookie_id, keywords)
|
||||
|
||||
print(f" ✅ 已创建 {len(initial_data)} 个账号的初始数据")
|
||||
|
||||
# 2. 导出备份
|
||||
print("\n2️⃣ 导出备份...")
|
||||
backup_data = db_manager.export_backup()
|
||||
print(f" ✅ 备份导出成功,包含 {len(backup_data['data'])} 个表")
|
||||
|
||||
# 3. 修改数据(模拟用户操作)
|
||||
print("\n3️⃣ 修改数据...")
|
||||
modified_data = {
|
||||
"account1": [("modified1", "修改后的回复1")],
|
||||
"account3": [("new", "新账号的回复")]
|
||||
}
|
||||
|
||||
for cookie_id, keywords in modified_data.items():
|
||||
db_manager.save_cookie(cookie_id, f"cookie_value_{cookie_id}")
|
||||
db_manager.save_keywords(cookie_id, keywords)
|
||||
|
||||
print(" ✅ 数据已修改")
|
||||
|
||||
# 4. 创建 CookieManager(加载修改后的数据)
|
||||
print("\n4️⃣ 创建 CookieManager...")
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
|
||||
manager = cm.CookieManager(loop)
|
||||
print(f" ✅ CookieManager 已创建,加载了修改后的数据")
|
||||
|
||||
# 5. 导入备份(恢复初始数据)
|
||||
print("\n5️⃣ 导入备份...")
|
||||
success = db_manager.import_backup(backup_data)
|
||||
print(f" 导入结果: {success}")
|
||||
|
||||
# 6. 检查 CookieManager 缓存(应该还是修改后的数据)
|
||||
print("\n6️⃣ 检查导入后的缓存...")
|
||||
for cookie_id in ["account1", "account2", "account3"]:
|
||||
cached = manager.keywords.get(cookie_id, [])
|
||||
db_data = db_manager.get_keywords(cookie_id)
|
||||
print(f" {cookie_id}:")
|
||||
print(f" 缓存: {cached}")
|
||||
print(f" 数据库: {db_data}")
|
||||
|
||||
if cached != db_data:
|
||||
print(f" ❌ 不一致!需要刷新缓存")
|
||||
else:
|
||||
print(f" ✅ 一致")
|
||||
|
||||
# 7. 刷新缓存
|
||||
print("\n7️⃣ 刷新缓存...")
|
||||
manager.reload_from_db()
|
||||
|
||||
# 8. 再次检查
|
||||
print("\n8️⃣ 检查刷新后的缓存...")
|
||||
all_consistent = True
|
||||
for cookie_id in ["account1", "account2"]: # account3 应该被删除了
|
||||
cached = manager.keywords.get(cookie_id, [])
|
||||
db_data = db_manager.get_keywords(cookie_id)
|
||||
print(f" {cookie_id}:")
|
||||
print(f" 缓存: {cached}")
|
||||
print(f" 数据库: {db_data}")
|
||||
|
||||
if cached != db_data:
|
||||
print(f" ❌ 仍然不一致!")
|
||||
all_consistent = False
|
||||
else:
|
||||
print(f" ✅ 一致")
|
||||
|
||||
# 检查 account3 是否被正确删除
|
||||
if "account3" not in manager.keywords:
|
||||
print(" ✅ account3 已从缓存中删除")
|
||||
else:
|
||||
print(" ❌ account3 仍在缓存中")
|
||||
all_consistent = False
|
||||
|
||||
# 9. 清理
|
||||
print("\n9️⃣ 清理测试数据...")
|
||||
for cookie_id in ["account1", "account2", "account3"]:
|
||||
db_manager.delete_cookie(cookie_id)
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
if all_consistent:
|
||||
print("🎉 备份导入场景测试成功!")
|
||||
else:
|
||||
print("❌ 备份导入场景测试失败!")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_cache_refresh()
|
||||
test_backup_import_scenario()
|
||||
except Exception as e:
|
||||
print(f"❌ 测试过程中发生错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
331
test_docker_deployment.py
Normal file
331
test_docker_deployment.py
Normal file
@ -0,0 +1,331 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Docker部署配置验证脚本
|
||||
检查Docker部署是否包含所有必要的组件和配置
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import yaml
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
def check_file_exists(file_path, description):
|
||||
"""检查文件是否存在"""
|
||||
if os.path.exists(file_path):
|
||||
print(f" ✅ {description}: {file_path}")
|
||||
return True
|
||||
else:
|
||||
print(f" ❌ {description}: {file_path} (缺失)")
|
||||
return False
|
||||
|
||||
def check_requirements_txt():
|
||||
"""检查requirements.txt中的依赖"""
|
||||
print("📦 检查Python依赖...")
|
||||
|
||||
if not check_file_exists("requirements.txt", "依赖文件"):
|
||||
return False
|
||||
|
||||
required_packages = [
|
||||
"fastapi",
|
||||
"uvicorn",
|
||||
"pydantic",
|
||||
"loguru",
|
||||
"websockets",
|
||||
"aiohttp",
|
||||
"PyYAML",
|
||||
"PyExecJS",
|
||||
"blackboxprotobuf",
|
||||
"psutil",
|
||||
"requests",
|
||||
"python-multipart",
|
||||
"openai",
|
||||
"python-dotenv"
|
||||
]
|
||||
|
||||
try:
|
||||
with open("requirements.txt", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
missing_packages = []
|
||||
for package in required_packages:
|
||||
if package.lower() not in content.lower():
|
||||
missing_packages.append(package)
|
||||
|
||||
if missing_packages:
|
||||
print(f" ❌ 缺失依赖: {', '.join(missing_packages)}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ 所有必要依赖都已包含 ({len(required_packages)} 个)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取requirements.txt失败: {e}")
|
||||
return False
|
||||
|
||||
def check_dockerfile():
|
||||
"""检查Dockerfile配置"""
|
||||
print("\n🐳 检查Dockerfile...")
|
||||
|
||||
if not check_file_exists("Dockerfile", "Docker镜像文件"):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open("Dockerfile", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
required_elements = [
|
||||
("FROM python:", "Python基础镜像"),
|
||||
("WORKDIR", "工作目录设置"),
|
||||
("COPY requirements.txt", "依赖文件复制"),
|
||||
("RUN pip install", "依赖安装"),
|
||||
("EXPOSE", "端口暴露"),
|
||||
("CMD", "启动命令")
|
||||
]
|
||||
|
||||
missing_elements = []
|
||||
for element, description in required_elements:
|
||||
if element not in content:
|
||||
missing_elements.append(description)
|
||||
|
||||
if missing_elements:
|
||||
print(f" ❌ 缺失配置: {', '.join(missing_elements)}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ Dockerfile配置完整")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取Dockerfile失败: {e}")
|
||||
return False
|
||||
|
||||
def check_docker_compose():
|
||||
"""检查docker-compose.yml配置"""
|
||||
print("\n🔧 检查Docker Compose配置...")
|
||||
|
||||
if not check_file_exists("docker-compose.yml", "Docker Compose文件"):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open("docker-compose.yml", "r", encoding="utf-8") as f:
|
||||
compose_config = yaml.safe_load(f)
|
||||
|
||||
# 检查服务配置
|
||||
if "services" not in compose_config:
|
||||
print(" ❌ 缺失services配置")
|
||||
return False
|
||||
|
||||
services = compose_config["services"]
|
||||
if "xianyu-app" not in services:
|
||||
print(" ❌ 缺失xianyu-app服务")
|
||||
return False
|
||||
|
||||
app_service = services["xianyu-app"]
|
||||
|
||||
# 检查必要配置
|
||||
required_configs = [
|
||||
("ports", "端口映射"),
|
||||
("volumes", "数据挂载"),
|
||||
("environment", "环境变量"),
|
||||
("healthcheck", "健康检查")
|
||||
]
|
||||
|
||||
missing_configs = []
|
||||
for config, description in required_configs:
|
||||
if config not in app_service:
|
||||
missing_configs.append(description)
|
||||
|
||||
if missing_configs:
|
||||
print(f" ❌ 缺失配置: {', '.join(missing_configs)}")
|
||||
return False
|
||||
|
||||
# 检查AI相关环境变量
|
||||
env_vars = app_service.get("environment", [])
|
||||
ai_env_vars = [
|
||||
"AI_REPLY_ENABLED",
|
||||
"DEFAULT_AI_MODEL",
|
||||
"DEFAULT_AI_BASE_URL",
|
||||
"AI_REQUEST_TIMEOUT"
|
||||
]
|
||||
|
||||
missing_ai_vars = []
|
||||
env_str = str(env_vars)
|
||||
for var in ai_env_vars:
|
||||
if var not in env_str:
|
||||
missing_ai_vars.append(var)
|
||||
|
||||
if missing_ai_vars:
|
||||
print(f" ⚠️ 缺失AI环境变量: {', '.join(missing_ai_vars)}")
|
||||
else:
|
||||
print(f" ✅ AI环境变量配置完整")
|
||||
|
||||
print(f" ✅ Docker Compose配置完整")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取docker-compose.yml失败: {e}")
|
||||
return False
|
||||
|
||||
def check_env_example():
|
||||
"""检查.env.example配置"""
|
||||
print("\n⚙️ 检查环境变量模板...")
|
||||
|
||||
if not check_file_exists(".env.example", "环境变量模板"):
|
||||
return False
|
||||
|
||||
try:
|
||||
with open(".env.example", "r", encoding="utf-8") as f:
|
||||
content = f.read()
|
||||
|
||||
# 检查AI相关配置
|
||||
ai_configs = [
|
||||
"AI_REPLY_ENABLED",
|
||||
"DEFAULT_AI_MODEL",
|
||||
"DEFAULT_AI_BASE_URL",
|
||||
"AI_REQUEST_TIMEOUT",
|
||||
"AI_MAX_TOKENS"
|
||||
]
|
||||
|
||||
missing_configs = []
|
||||
for config in ai_configs:
|
||||
if config not in content:
|
||||
missing_configs.append(config)
|
||||
|
||||
if missing_configs:
|
||||
print(f" ❌ 缺失AI配置: {', '.join(missing_configs)}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ AI配置完整")
|
||||
|
||||
# 检查基础配置
|
||||
basic_configs = [
|
||||
"ADMIN_USERNAME",
|
||||
"ADMIN_PASSWORD",
|
||||
"JWT_SECRET_KEY",
|
||||
"AUTO_REPLY_ENABLED",
|
||||
"AUTO_DELIVERY_ENABLED"
|
||||
]
|
||||
|
||||
missing_basic = []
|
||||
for config in basic_configs:
|
||||
if config not in content:
|
||||
missing_basic.append(config)
|
||||
|
||||
if missing_basic:
|
||||
print(f" ❌ 缺失基础配置: {', '.join(missing_basic)}")
|
||||
return False
|
||||
else:
|
||||
print(f" ✅ 基础配置完整")
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取.env.example失败: {e}")
|
||||
return False
|
||||
|
||||
def check_documentation():
|
||||
"""检查部署文档"""
|
||||
print("\n📚 检查部署文档...")
|
||||
|
||||
docs = [
|
||||
("Docker部署说明.md", "Docker部署说明"),
|
||||
("README.md", "项目说明文档")
|
||||
]
|
||||
|
||||
all_exist = True
|
||||
for doc_file, description in docs:
|
||||
if not check_file_exists(doc_file, description):
|
||||
all_exist = False
|
||||
|
||||
return all_exist
|
||||
|
||||
def check_directory_structure():
|
||||
"""检查目录结构"""
|
||||
print("\n📁 检查目录结构...")
|
||||
|
||||
required_dirs = [
|
||||
("static", "静态文件目录"),
|
||||
("templates", "模板目录(如果存在)")
|
||||
]
|
||||
|
||||
required_files = [
|
||||
("Start.py", "主程序文件"),
|
||||
("db_manager.py", "数据库管理"),
|
||||
("XianyuAutoAsync.py", "闲鱼自动化"),
|
||||
("ai_reply_engine.py", "AI回复引擎"),
|
||||
("global_config.yml", "全局配置")
|
||||
]
|
||||
|
||||
all_exist = True
|
||||
|
||||
# 检查目录
|
||||
for dir_name, description in required_dirs:
|
||||
if os.path.exists(dir_name) and os.path.isdir(dir_name):
|
||||
print(f" ✅ {description}: {dir_name}")
|
||||
else:
|
||||
if dir_name == "templates": # templates是可选的
|
||||
print(f" ⚠️ {description}: {dir_name} (可选)")
|
||||
else:
|
||||
print(f" ❌ {description}: {dir_name} (缺失)")
|
||||
all_exist = False
|
||||
|
||||
# 检查文件
|
||||
for file_name, description in required_files:
|
||||
if not check_file_exists(file_name, description):
|
||||
all_exist = False
|
||||
|
||||
return all_exist
|
||||
|
||||
def main():
|
||||
"""主检查函数"""
|
||||
print("🚀 Docker部署配置验证")
|
||||
print("=" * 50)
|
||||
|
||||
checks = [
|
||||
("Python依赖", check_requirements_txt),
|
||||
("Dockerfile", check_dockerfile),
|
||||
("Docker Compose", check_docker_compose),
|
||||
("环境变量", check_env_example),
|
||||
("目录结构", check_directory_structure),
|
||||
("文档", check_documentation)
|
||||
]
|
||||
|
||||
results = []
|
||||
for check_name, check_func in checks:
|
||||
try:
|
||||
result = check_func()
|
||||
results.append((check_name, result))
|
||||
except Exception as e:
|
||||
print(f" ❌ {check_name}检查异常: {e}")
|
||||
results.append((check_name, False))
|
||||
|
||||
# 总结结果
|
||||
print("\n" + "=" * 50)
|
||||
print("📊 检查结果总结")
|
||||
|
||||
passed = 0
|
||||
total = len(results)
|
||||
|
||||
for check_name, result in results:
|
||||
status = "✅ 通过" if result else "❌ 失败"
|
||||
print(f" {check_name}: {status}")
|
||||
if result:
|
||||
passed += 1
|
||||
|
||||
print(f"\n📈 总体评分: {passed}/{total} ({passed/total*100:.1f}%)")
|
||||
|
||||
if passed == total:
|
||||
print("\n🎉 所有检查通过!Docker部署配置完整!")
|
||||
print("\n🚀 可以直接使用以下命令部署:")
|
||||
print(" docker-compose up -d")
|
||||
elif passed >= total * 0.8:
|
||||
print("\n⚠️ 大部分检查通过,有少量问题需要修复")
|
||||
print(" 建议修复上述问题后再部署")
|
||||
else:
|
||||
print("\n❌ 多项检查失败,需要完善配置后再部署")
|
||||
|
||||
return passed == total
|
||||
|
||||
if __name__ == "__main__":
|
||||
success = main()
|
||||
sys.exit(0 if success else 1)
|
27
test_fix.py
Normal file
27
test_fix.py
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试修复
|
||||
"""
|
||||
|
||||
def test_imports():
|
||||
print("🧪 测试导入修复")
|
||||
|
||||
try:
|
||||
from file_log_collector import setup_file_logging, get_file_log_collector
|
||||
print("✅ file_log_collector 导入成功")
|
||||
|
||||
# 测试初始化
|
||||
collector = setup_file_logging()
|
||||
print("✅ 文件日志收集器初始化成功")
|
||||
|
||||
# 生成测试日志
|
||||
from loguru import logger
|
||||
logger.info("测试日志修复")
|
||||
|
||||
print("✅ 所有导入和初始化都正常")
|
||||
|
||||
except Exception as e:
|
||||
print(f"❌ 导入失败: {e}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_imports()
|
149
test_gitignore.py
Normal file
149
test_gitignore.py
Normal file
@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试 .gitignore 规则是否正确
|
||||
验证 static/lib/ 目录不被忽略,而其他 lib/ 目录被忽略
|
||||
"""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
def test_gitignore_rules():
|
||||
"""测试 .gitignore 规则"""
|
||||
print("🧪 测试 .gitignore 规则")
|
||||
print("=" * 50)
|
||||
|
||||
# 检查文件是否存在
|
||||
static_lib_files = [
|
||||
"static/lib/bootstrap/bootstrap.min.css",
|
||||
"static/lib/bootstrap/bootstrap.bundle.min.js",
|
||||
"static/lib/bootstrap-icons/bootstrap-icons.css",
|
||||
"static/lib/bootstrap-icons/fonts/bootstrap-icons.woff",
|
||||
"static/lib/bootstrap-icons/fonts/bootstrap-icons.woff2"
|
||||
]
|
||||
|
||||
print("\n1️⃣ 检查静态文件是否存在...")
|
||||
all_exist = True
|
||||
for file_path in static_lib_files:
|
||||
if os.path.exists(file_path):
|
||||
size = os.path.getsize(file_path)
|
||||
print(f" ✅ {file_path} ({size:,} bytes)")
|
||||
else:
|
||||
print(f" ❌ {file_path} (不存在)")
|
||||
all_exist = False
|
||||
|
||||
if all_exist:
|
||||
print(" 🎉 所有静态文件都存在!")
|
||||
else:
|
||||
print(" ⚠️ 部分静态文件缺失")
|
||||
|
||||
# 检查 .gitignore 内容
|
||||
print("\n2️⃣ 检查 .gitignore 规则...")
|
||||
try:
|
||||
with open('.gitignore', 'r', encoding='utf-8') as f:
|
||||
gitignore_content = f.read()
|
||||
|
||||
if 'lib/' in gitignore_content and '!static/lib/' in gitignore_content:
|
||||
print(" ✅ .gitignore 规则正确配置")
|
||||
print(" 📝 规则说明:")
|
||||
print(" - lib/ : 忽略所有 lib 目录")
|
||||
print(" - !static/lib/ : 但不忽略 static/lib 目录")
|
||||
else:
|
||||
print(" ❌ .gitignore 规则配置不正确")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取 .gitignore 失败: {e}")
|
||||
|
||||
# 模拟测试(创建临时文件)
|
||||
print("\n3️⃣ 模拟测试 gitignore 行为...")
|
||||
|
||||
# 创建测试目录和文件
|
||||
test_dirs = [
|
||||
"lib/test_file.txt", # 应该被忽略
|
||||
"static/lib/test_file.txt", # 不应该被忽略
|
||||
"some_other_lib/test_file.txt" # 不应该被忽略
|
||||
]
|
||||
|
||||
created_files = []
|
||||
try:
|
||||
for test_path in test_dirs:
|
||||
os.makedirs(os.path.dirname(test_path), exist_ok=True)
|
||||
with open(test_path, 'w') as f:
|
||||
f.write("test content")
|
||||
created_files.append(test_path)
|
||||
print(f" 📁 创建测试文件: {test_path}")
|
||||
|
||||
print("\n 📋 根据 .gitignore 规则预期:")
|
||||
print(" - lib/test_file.txt : 应该被忽略")
|
||||
print(" - static/lib/test_file.txt : 不应该被忽略")
|
||||
print(" - some_other_lib/test_file.txt : 不应该被忽略")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 创建测试文件失败: {e}")
|
||||
|
||||
finally:
|
||||
# 清理测试文件
|
||||
print("\n4️⃣ 清理测试文件...")
|
||||
for file_path in created_files:
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f" 🗑️ 删除: {file_path}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 删除失败: {file_path} - {e}")
|
||||
|
||||
# 清理空目录
|
||||
test_cleanup_dirs = ["lib", "some_other_lib"]
|
||||
for dir_path in test_cleanup_dirs:
|
||||
try:
|
||||
if os.path.exists(dir_path) and not os.listdir(dir_path):
|
||||
os.rmdir(dir_path)
|
||||
print(f" 🗑️ 删除空目录: {dir_path}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 删除目录失败: {dir_path} - {e}")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎯 总结:")
|
||||
print("✅ static/lib/ 目录下的静态文件现在不会被 Git 忽略")
|
||||
print("✅ 其他 lib/ 目录仍然会被正常忽略")
|
||||
print("✅ 本地 CDN 资源可以正常提交到版本控制")
|
||||
|
||||
def check_file_sizes():
|
||||
"""检查静态文件大小"""
|
||||
print("\n\n📊 静态文件大小统计")
|
||||
print("=" * 50)
|
||||
|
||||
files_info = [
|
||||
("Bootstrap CSS", "static/lib/bootstrap/bootstrap.min.css"),
|
||||
("Bootstrap JS", "static/lib/bootstrap/bootstrap.bundle.min.js"),
|
||||
("Bootstrap Icons CSS", "static/lib/bootstrap-icons/bootstrap-icons.css"),
|
||||
("Bootstrap Icons WOFF2", "static/lib/bootstrap-icons/fonts/bootstrap-icons.woff2"),
|
||||
("Bootstrap Icons WOFF", "static/lib/bootstrap-icons/fonts/bootstrap-icons.woff")
|
||||
]
|
||||
|
||||
total_size = 0
|
||||
for name, path in files_info:
|
||||
if os.path.exists(path):
|
||||
size = os.path.getsize(path)
|
||||
total_size += size
|
||||
print(f"📄 {name:<25} : {size:>8,} bytes ({size/1024:.1f} KB)")
|
||||
else:
|
||||
print(f"❌ {name:<25} : 文件不存在")
|
||||
|
||||
print("-" * 50)
|
||||
print(f"📦 总大小 : {total_size:>8,} bytes ({total_size/1024:.1f} KB)")
|
||||
|
||||
if total_size > 0:
|
||||
print(f"\n💡 优势:")
|
||||
print(f" - 不再依赖 CDN,提升中国大陆访问速度")
|
||||
print(f" - 离线可用,提高系统稳定性")
|
||||
print(f" - 版本固定,避免 CDN 更新导致的兼容性问题")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_gitignore_rules()
|
||||
check_file_sizes()
|
||||
except Exception as e:
|
||||
print(f"❌ 测试过程中发生错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
178
test_gitignore_db.py
Normal file
178
test_gitignore_db.py
Normal file
@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
测试 .gitignore 数据库文件忽略规则
|
||||
"""
|
||||
|
||||
import os
|
||||
import tempfile
|
||||
|
||||
def test_database_gitignore():
|
||||
"""测试数据库文件忽略规则"""
|
||||
print("🧪 测试数据库文件 .gitignore 规则")
|
||||
print("=" * 50)
|
||||
|
||||
# 检查当前项目中的数据库文件
|
||||
print("\n1️⃣ 检查项目中的数据库文件...")
|
||||
|
||||
db_patterns = ["*.db", "*.sqlite", "*.sqlite3"]
|
||||
found_db_files = []
|
||||
|
||||
for root, dirs, files in os.walk("."):
|
||||
for file in files:
|
||||
file_path = os.path.join(root, file)
|
||||
for pattern in db_patterns:
|
||||
if file.endswith(pattern.replace("*", "")):
|
||||
size = os.path.getsize(file_path)
|
||||
found_db_files.append((file_path, size))
|
||||
print(f" 📄 {file_path} ({size:,} bytes)")
|
||||
|
||||
if found_db_files:
|
||||
print(f" 📊 找到 {len(found_db_files)} 个数据库文件")
|
||||
else:
|
||||
print(" ✅ 未找到数据库文件")
|
||||
|
||||
# 检查 .gitignore 规则
|
||||
print("\n2️⃣ 检查 .gitignore 数据库规则...")
|
||||
try:
|
||||
with open('.gitignore', 'r', encoding='utf-8') as f:
|
||||
gitignore_content = f.read()
|
||||
|
||||
db_rules = ['*.db', '*.sqlite', '*.sqlite3']
|
||||
missing_rules = []
|
||||
|
||||
for rule in db_rules:
|
||||
if rule in gitignore_content:
|
||||
print(f" ✅ {rule} - 已配置")
|
||||
else:
|
||||
print(f" ❌ {rule} - 未配置")
|
||||
missing_rules.append(rule)
|
||||
|
||||
if not missing_rules:
|
||||
print(" 🎉 所有数据库文件规则都已正确配置!")
|
||||
else:
|
||||
print(f" ⚠️ 缺少规则: {missing_rules}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 读取 .gitignore 失败: {e}")
|
||||
|
||||
# 测试其他新增的忽略规则
|
||||
print("\n3️⃣ 检查其他新增的忽略规则...")
|
||||
|
||||
other_rules = [
|
||||
("临时文件", ["*.tmp", "*.temp", "temp/", "tmp/"]),
|
||||
("操作系统文件", [".DS_Store", "Thumbs.db"]),
|
||||
("IDE文件", [".vscode/", ".idea/", "*.swp"]),
|
||||
("环境文件", [".env", ".env.local"])
|
||||
]
|
||||
|
||||
for category, rules in other_rules:
|
||||
print(f"\n 📂 {category}:")
|
||||
for rule in rules:
|
||||
if rule in gitignore_content:
|
||||
print(f" ✅ {rule}")
|
||||
else:
|
||||
print(f" ❌ {rule}")
|
||||
|
||||
# 模拟创建测试文件
|
||||
print("\n4️⃣ 模拟测试文件创建...")
|
||||
|
||||
test_files = [
|
||||
"test.db",
|
||||
"test.sqlite",
|
||||
"test.sqlite3",
|
||||
"test.tmp",
|
||||
".env",
|
||||
"temp/test.txt"
|
||||
]
|
||||
|
||||
created_files = []
|
||||
try:
|
||||
for test_file in test_files:
|
||||
# 创建目录(如果需要)
|
||||
dir_path = os.path.dirname(test_file)
|
||||
if dir_path:
|
||||
os.makedirs(dir_path, exist_ok=True)
|
||||
|
||||
# 创建文件
|
||||
with open(test_file, 'w') as f:
|
||||
f.write("test content")
|
||||
created_files.append(test_file)
|
||||
print(f" 📁 创建测试文件: {test_file}")
|
||||
|
||||
print("\n 📋 这些文件应该被 .gitignore 忽略:")
|
||||
for file in test_files:
|
||||
print(f" - {file}")
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 创建测试文件失败: {e}")
|
||||
|
||||
finally:
|
||||
# 清理测试文件
|
||||
print("\n5️⃣ 清理测试文件...")
|
||||
for file_path in created_files:
|
||||
try:
|
||||
if os.path.exists(file_path):
|
||||
os.remove(file_path)
|
||||
print(f" 🗑️ 删除: {file_path}")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ 删除失败: {file_path} - {e}")
|
||||
|
||||
# 清理测试目录
|
||||
if os.path.exists("temp") and not os.listdir("temp"):
|
||||
os.rmdir("temp")
|
||||
print(" 🗑️ 删除空目录: temp")
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎯 .gitignore 数据库文件忽略规则测试完成!")
|
||||
|
||||
def show_gitignore_summary():
|
||||
"""显示 .gitignore 规则总结"""
|
||||
print("\n\n📋 .gitignore 规则总结")
|
||||
print("=" * 50)
|
||||
|
||||
categories = {
|
||||
"Python 相关": [
|
||||
"__pycache__", "*.so", ".Python", "build/", "dist/",
|
||||
"*.egg-info/", "__pypackages__/", ".venv", "venv/", "ENV/"
|
||||
],
|
||||
"数据库文件": [
|
||||
"*.db", "*.sqlite", "*.sqlite3", "db.sqlite3"
|
||||
],
|
||||
"静态资源": [
|
||||
"lib/ (但不包括 static/lib/)"
|
||||
],
|
||||
"临时文件": [
|
||||
"*.tmp", "*.temp", "temp/", "tmp/", "*.log", ".cache"
|
||||
],
|
||||
"操作系统": [
|
||||
".DS_Store", "Thumbs.db", "ehthumbs.db"
|
||||
],
|
||||
"IDE 和编辑器": [
|
||||
".vscode/", ".idea/", "*.swp", "*.swo", "*~"
|
||||
],
|
||||
"环境配置": [
|
||||
".env", ".env.local", ".env.*.local", "local_settings.py"
|
||||
],
|
||||
"Node.js": [
|
||||
"*node_modules/*"
|
||||
]
|
||||
}
|
||||
|
||||
for category, rules in categories.items():
|
||||
print(f"\n📂 {category}:")
|
||||
for rule in rules:
|
||||
print(f" • {rule}")
|
||||
|
||||
print(f"\n💡 特别说明:")
|
||||
print(f" • static/lib/ 目录不被忽略,用于存放本地 CDN 资源")
|
||||
print(f" • 数据库文件被忽略,避免敏感数据泄露")
|
||||
print(f" • 环境配置文件被忽略,保护敏感配置信息")
|
||||
|
||||
if __name__ == "__main__":
|
||||
try:
|
||||
test_database_gitignore()
|
||||
show_gitignore_summary()
|
||||
except Exception as e:
|
||||
print(f"❌ 测试过程中发生错误: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
290
test_keyword_reply.py
Normal file
290
test_keyword_reply.py
Normal file
@ -0,0 +1,290 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
关键词回复功能测试脚本
|
||||
用于验证关键词匹配是否正常工作
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
import os
|
||||
|
||||
# 添加项目根目录到Python路径
|
||||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
|
||||
|
||||
from db_manager import db_manager
|
||||
from XianyuAutoAsync import XianyuLive
|
||||
from loguru import logger
|
||||
|
||||
def test_keyword_database():
|
||||
"""测试关键词数据库操作"""
|
||||
print("🗄️ 开始测试关键词数据库操作...")
|
||||
|
||||
test_cookie_id = "test_cookie_001"
|
||||
|
||||
# 1. 清理测试数据
|
||||
print("\n1️⃣ 清理测试数据...")
|
||||
try:
|
||||
with db_manager.lock:
|
||||
cursor = db_manager.conn.cursor()
|
||||
cursor.execute("DELETE FROM keywords WHERE cookie_id = ?", (test_cookie_id,))
|
||||
db_manager.conn.commit()
|
||||
print(" ✅ 测试数据清理完成")
|
||||
except Exception as e:
|
||||
print(f" ❌ 清理测试数据失败: {e}")
|
||||
return False
|
||||
|
||||
# 2. 添加测试关键词
|
||||
print("\n2️⃣ 添加测试关键词...")
|
||||
test_keywords = [
|
||||
("你好", "您好!欢迎咨询,有什么可以帮助您的吗?"),
|
||||
("价格", "这个商品的价格很优惠哦,{send_user_name}!"),
|
||||
("包邮", "全国包邮,放心购买!"),
|
||||
("发货", "我们会在24小时内发货"),
|
||||
("退换", "支持7天无理由退换货")
|
||||
]
|
||||
|
||||
try:
|
||||
success = db_manager.save_keywords(test_cookie_id, test_keywords)
|
||||
if success:
|
||||
print(f" ✅ 成功添加 {len(test_keywords)} 个关键词")
|
||||
else:
|
||||
print(" ❌ 添加关键词失败")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 添加关键词异常: {e}")
|
||||
return False
|
||||
|
||||
# 3. 验证关键词保存
|
||||
print("\n3️⃣ 验证关键词保存...")
|
||||
try:
|
||||
saved_keywords = db_manager.get_keywords(test_cookie_id)
|
||||
if len(saved_keywords) == len(test_keywords):
|
||||
print(f" ✅ 关键词保存验证成功: {len(saved_keywords)} 个")
|
||||
for keyword, reply in saved_keywords:
|
||||
print(f" '{keyword}' -> '{reply[:30]}...'")
|
||||
else:
|
||||
print(f" ❌ 关键词数量不匹配: 期望 {len(test_keywords)}, 实际 {len(saved_keywords)}")
|
||||
return False
|
||||
except Exception as e:
|
||||
print(f" ❌ 验证关键词异常: {e}")
|
||||
return False
|
||||
|
||||
print("\n✅ 关键词数据库操作测试完成!")
|
||||
return True
|
||||
|
||||
async def test_keyword_matching():
|
||||
"""测试关键词匹配功能"""
|
||||
print("\n🔍 开始测试关键词匹配功能...")
|
||||
|
||||
test_cookie_id = "test_cookie_001"
|
||||
|
||||
# 创建一个简化的测试类
|
||||
class TestKeywordMatcher:
|
||||
def __init__(self, cookie_id):
|
||||
self.cookie_id = cookie_id
|
||||
|
||||
async def get_keyword_reply(self, send_user_name: str, send_user_id: str, send_message: str) -> str:
|
||||
"""获取关键词匹配回复"""
|
||||
try:
|
||||
from db_manager import db_manager
|
||||
|
||||
# 获取当前账号的关键词列表
|
||||
keywords = db_manager.get_keywords(self.cookie_id)
|
||||
|
||||
if not keywords:
|
||||
print(f" 调试: 账号 {self.cookie_id} 没有配置关键词")
|
||||
return None
|
||||
|
||||
# 遍历关键词,查找匹配
|
||||
for keyword, reply in keywords:
|
||||
if keyword.lower() in send_message.lower():
|
||||
# 进行变量替换
|
||||
try:
|
||||
formatted_reply = reply.format(
|
||||
send_user_name=send_user_name,
|
||||
send_user_id=send_user_id,
|
||||
send_message=send_message
|
||||
)
|
||||
print(f" 调试: 关键词匹配成功: '{keyword}' -> {formatted_reply}")
|
||||
return f"[关键词回复] {formatted_reply}"
|
||||
except Exception as format_error:
|
||||
print(f" 调试: 关键词回复变量替换失败: {format_error}")
|
||||
# 如果变量替换失败,返回原始内容
|
||||
return f"[关键词回复] {reply}"
|
||||
|
||||
print(f" 调试: 未找到匹配的关键词: {send_message}")
|
||||
return None
|
||||
|
||||
except Exception as e:
|
||||
print(f" 调试: 获取关键词回复失败: {e}")
|
||||
return None
|
||||
|
||||
# 创建测试实例
|
||||
test_matcher = TestKeywordMatcher(test_cookie_id)
|
||||
|
||||
# 测试消息和期望结果
|
||||
test_cases = [
|
||||
{
|
||||
"message": "你好",
|
||||
"expected_keyword": "你好",
|
||||
"should_match": True
|
||||
},
|
||||
{
|
||||
"message": "请问价格多少?",
|
||||
"expected_keyword": "价格",
|
||||
"should_match": True
|
||||
},
|
||||
{
|
||||
"message": "包邮吗?",
|
||||
"expected_keyword": "包邮",
|
||||
"should_match": True
|
||||
},
|
||||
{
|
||||
"message": "什么时候发货?",
|
||||
"expected_keyword": "发货",
|
||||
"should_match": True
|
||||
},
|
||||
{
|
||||
"message": "可以退换吗?",
|
||||
"expected_keyword": "退换",
|
||||
"should_match": True
|
||||
},
|
||||
{
|
||||
"message": "这是什么材质的?",
|
||||
"expected_keyword": None,
|
||||
"should_match": False
|
||||
}
|
||||
]
|
||||
|
||||
success_count = 0
|
||||
|
||||
for i, test_case in enumerate(test_cases, 1):
|
||||
print(f"\n{i}️⃣ 测试消息: '{test_case['message']}'")
|
||||
|
||||
try:
|
||||
reply = await test_matcher.get_keyword_reply(
|
||||
send_user_name="测试用户",
|
||||
send_user_id="test_user_001",
|
||||
send_message=test_case['message']
|
||||
)
|
||||
|
||||
if test_case['should_match']:
|
||||
if reply:
|
||||
print(f" ✅ 匹配成功: {reply}")
|
||||
if test_case['expected_keyword'] in reply or test_case['expected_keyword'] in test_case['message']:
|
||||
success_count += 1
|
||||
else:
|
||||
print(f" ⚠️ 匹配的关键词不符合预期")
|
||||
else:
|
||||
print(f" ❌ 期望匹配但未匹配")
|
||||
else:
|
||||
if reply:
|
||||
print(f" ❌ 不应该匹配但却匹配了: {reply}")
|
||||
else:
|
||||
print(f" ✅ 正确未匹配")
|
||||
success_count += 1
|
||||
|
||||
except Exception as e:
|
||||
print(f" ❌ 测试异常: {e}")
|
||||
|
||||
print(f"\n📊 测试结果: {success_count}/{len(test_cases)} 个测试通过")
|
||||
|
||||
if success_count == len(test_cases):
|
||||
print("✅ 关键词匹配功能测试完成!")
|
||||
return True
|
||||
else:
|
||||
print("❌ 部分测试失败")
|
||||
return False
|
||||
|
||||
def test_reply_priority():
|
||||
"""测试回复优先级"""
|
||||
print("\n🎯 开始测试回复优先级...")
|
||||
|
||||
test_cookie_id = "test_cookie_001"
|
||||
|
||||
# 检查AI回复状态
|
||||
print("\n1️⃣ 检查AI回复状态...")
|
||||
try:
|
||||
ai_settings = db_manager.get_ai_reply_settings(test_cookie_id)
|
||||
ai_enabled = ai_settings.get('ai_enabled', False)
|
||||
print(f" AI回复状态: {'启用' if ai_enabled else '禁用'}")
|
||||
except Exception as e:
|
||||
print(f" ❌ 检查AI回复状态失败: {e}")
|
||||
return False
|
||||
|
||||
# 检查关键词数量
|
||||
print("\n2️⃣ 检查关键词配置...")
|
||||
try:
|
||||
keywords = db_manager.get_keywords(test_cookie_id)
|
||||
print(f" 关键词数量: {len(keywords)} 个")
|
||||
if len(keywords) > 0:
|
||||
print(" 关键词列表:")
|
||||
for keyword, reply in keywords[:3]: # 只显示前3个
|
||||
print(f" '{keyword}' -> '{reply[:30]}...'")
|
||||
except Exception as e:
|
||||
print(f" ❌ 检查关键词配置失败: {e}")
|
||||
return False
|
||||
|
||||
# 检查默认回复
|
||||
print("\n3️⃣ 检查默认回复配置...")
|
||||
try:
|
||||
default_reply = db_manager.get_default_reply(test_cookie_id)
|
||||
if default_reply and default_reply.get('enabled', False):
|
||||
print(f" 默认回复: 启用")
|
||||
print(f" 默认回复内容: {default_reply.get('reply_content', '')[:50]}...")
|
||||
else:
|
||||
print(f" 默认回复: 禁用")
|
||||
except Exception as e:
|
||||
print(f" ❌ 检查默认回复配置失败: {e}")
|
||||
return False
|
||||
|
||||
print("\n📋 回复优先级说明:")
|
||||
print(" 1. API回复 (最高优先级)")
|
||||
print(" 2. AI回复 (如果启用)")
|
||||
print(" 3. 关键词匹配 (如果AI禁用)")
|
||||
print(" 4. 默认回复 (最低优先级)")
|
||||
|
||||
if not ai_enabled and len(keywords) > 0:
|
||||
print("\n✅ 当前配置下,关键词匹配应该正常工作!")
|
||||
return True
|
||||
elif ai_enabled:
|
||||
print("\n⚠️ 当前AI回复已启用,关键词匹配会被跳过")
|
||||
print(" 如需测试关键词匹配,请先禁用AI回复")
|
||||
return True
|
||||
else:
|
||||
print("\n⚠️ 当前没有配置关键词,将使用默认回复")
|
||||
return True
|
||||
|
||||
async def main():
|
||||
"""主测试函数"""
|
||||
print("🚀 关键词回复功能测试开始")
|
||||
print("=" * 50)
|
||||
|
||||
# 测试数据库操作
|
||||
db_ok = test_keyword_database()
|
||||
if not db_ok:
|
||||
print("\n❌ 数据库操作测试失败")
|
||||
return
|
||||
|
||||
# 测试关键词匹配
|
||||
match_ok = await test_keyword_matching()
|
||||
if not match_ok:
|
||||
print("\n❌ 关键词匹配测试失败")
|
||||
return
|
||||
|
||||
# 测试回复优先级
|
||||
priority_ok = test_reply_priority()
|
||||
if not priority_ok:
|
||||
print("\n❌ 回复优先级测试失败")
|
||||
return
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print("🎉 所有测试通过!关键词回复功能正常!")
|
||||
print("\n📋 使用说明:")
|
||||
print("1. 在Web界面的'自动回复'页面配置关键词")
|
||||
print("2. 确保AI回复已禁用(如果要使用关键词匹配)")
|
||||
print("3. 发送包含关键词的消息进行测试")
|
||||
print("4. 关键词匹配支持变量替换:{send_user_name}, {send_user_id}, {send_message}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
250
test_status_display.html
Normal file
250
test_status_display.html
Normal file
@ -0,0 +1,250 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>状态显示测试</title>
|
||||
<link href="static/lib/bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||
<link href="static/lib/bootstrap-icons/bootstrap-icons.css" rel="stylesheet">
|
||||
<style>
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.25rem 0.5rem;
|
||||
border-radius: 12px;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 500;
|
||||
min-width: 2rem;
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.status-badge.enabled {
|
||||
background: #dcfce7;
|
||||
color: #166534;
|
||||
border: 1px solid #bbf7d0;
|
||||
}
|
||||
|
||||
.status-badge.disabled {
|
||||
background: #fef2f2;
|
||||
color: #991b1b;
|
||||
border: 1px solid #fecaca;
|
||||
}
|
||||
|
||||
.status-toggle {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 50px;
|
||||
height: 24px;
|
||||
}
|
||||
|
||||
.status-toggle input {
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.status-slider {
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: #ccc;
|
||||
transition: .4s;
|
||||
border-radius: 24px;
|
||||
}
|
||||
|
||||
.status-slider:before {
|
||||
position: absolute;
|
||||
content: "";
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
left: 3px;
|
||||
bottom: 3px;
|
||||
background-color: white;
|
||||
transition: .4s;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
input:checked + .status-slider {
|
||||
background-color: #10b981;
|
||||
}
|
||||
|
||||
input:checked + .status-slider:before {
|
||||
transform: translateX(26px);
|
||||
}
|
||||
|
||||
.demo-section {
|
||||
margin: 2rem 0;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container mt-4">
|
||||
<h1 class="mb-4">账号状态显示测试</h1>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>修改前 vs 修改后对比</h3>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>修改前(带文字)</h5>
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox" checked>
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge enabled">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
启用
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox">
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge disabled">
|
||||
<i class="bi bi-x-circle-fill"></i>
|
||||
禁用
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-6">
|
||||
<h5>修改后(仅图标)</h5>
|
||||
<div class="d-flex align-items-center gap-3 mb-3">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox" checked>
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge enabled">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="d-flex align-items-center gap-3">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox">
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge disabled">
|
||||
<i class="bi bi-x-circle-fill"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>表格中的效果预览</h3>
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>账号ID</th>
|
||||
<th>状态</th>
|
||||
<th>默认回复</th>
|
||||
<th>AI回复</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>测试账号001</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox" checked>
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge enabled">
|
||||
<i class="bi bi-check-circle-fill"></i>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-success">启用</span></td>
|
||||
<td><span class="badge bg-primary">AI启用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary">
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>测试账号002</td>
|
||||
<td>
|
||||
<div class="d-flex align-items-center gap-2">
|
||||
<label class="status-toggle">
|
||||
<input type="checkbox">
|
||||
<span class="status-slider"></span>
|
||||
</label>
|
||||
<span class="status-badge disabled">
|
||||
<i class="bi bi-x-circle-fill"></i>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td><span class="badge bg-secondary">禁用</span></td>
|
||||
<td><span class="badge bg-secondary">AI禁用</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-primary" disabled>
|
||||
<i class="bi bi-pencil"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="demo-section">
|
||||
<h3>优势说明</h3>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<h5>✅ 修改后的优势</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>✓ 界面更简洁</li>
|
||||
<li>✓ 节省空间</li>
|
||||
<li>✓ 图标直观易懂</li>
|
||||
<li>✓ 视觉焦点更集中</li>
|
||||
<li>✓ 现代化设计风格</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h5>🎨 设计细节</h5>
|
||||
<ul class="list-unstyled">
|
||||
<li>• 图标居中对齐</li>
|
||||
<li>• 徽章尺寸优化</li>
|
||||
<li>• 颜色保持一致</li>
|
||||
<li>• 响应式设计</li>
|
||||
<li>• 无障碍访问友好</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="alert alert-info">
|
||||
<h5><i class="bi bi-info-circle me-2"></i>说明</h5>
|
||||
<p class="mb-0">
|
||||
状态栏现在只显示图标,不显示"启用"/"禁用"文字。
|
||||
绿色勾号表示启用状态,红色叉号表示禁用状态。
|
||||
鼠标悬停时可以显示提示信息。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="static/lib/bootstrap/bootstrap.bundle.min.js"></script>
|
||||
<script>
|
||||
// 添加提示信息
|
||||
document.querySelectorAll('.status-badge').forEach(badge => {
|
||||
const isEnabled = badge.classList.contains('enabled');
|
||||
badge.title = isEnabled ? '账号已启用' : '账号已禁用';
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
139
使用说明.md
139
使用说明.md
@ -70,14 +70,7 @@ python Start.py
|
||||
- **精确匹配**:支持关键词精确匹配
|
||||
- **变量替换**:回复内容支持动态变量
|
||||
- **优先级**:账号级关键词优先于全局关键词
|
||||
- **智能回复优先级**:关键词回复 → AI回复 → 默认回复
|
||||
|
||||
### 回复优先级说明
|
||||
系统按以下优先级处理回复:
|
||||
1. **🔥 API回复** - 外部API接口回复(最高优先级)
|
||||
2. **📝 关键词回复** - 精确关键词匹配回复
|
||||
3. **🤖 AI智能回复** - AI模型生成的智能回复
|
||||
4. **💬 默认回复** - 兜底的默认回复内容
|
||||
- **默认回复**:未匹配关键词时使用默认回复
|
||||
|
||||
### API接口
|
||||
- **接口地址**:`POST http://localhost:8080/xianyu/reply`
|
||||
@ -159,133 +152,27 @@ python Start.py
|
||||
- 备份 `global_config.yml` 配置文件
|
||||
- 备份自定义的关键词文件
|
||||
|
||||
## 🔧 高级功能
|
||||
|
||||
### AI回复配置
|
||||
1. 在账号列表中点击"🤖 配置AI回复"
|
||||
2. 开启AI回复功能
|
||||
3. 配置API密钥和模型参数
|
||||
4. 设置议价策略和优惠限制
|
||||
5. 自定义提示词(可选)
|
||||
|
||||
### 自动发货设置
|
||||
1. 进入"自动发货"页面
|
||||
2. 添加发货规则和关键词匹配
|
||||
3. 上传卡券文件或手动添加卡券
|
||||
4. 配置发货模板和通知方式
|
||||
|
||||
### 商品管理
|
||||
1. 进入"商品管理"页面
|
||||
2. 查看自动收集的商品信息
|
||||
3. 编辑商品详情和分类
|
||||
4. 批量获取账号下所有商品
|
||||
|
||||
### 日志监控
|
||||
1. 进入"日志管理"页面
|
||||
2. 实时查看系统运行日志
|
||||
3. 按级别和来源筛选日志
|
||||
4. 查看系统统计信息
|
||||
|
||||
## 📊 数据管理
|
||||
|
||||
### 数据备份
|
||||
1. 进入"系统设置"页面
|
||||
2. 点击"导出备份"按钮
|
||||
3. 下载备份文件到本地
|
||||
4. 定期备份重要数据
|
||||
|
||||
### 数据恢复
|
||||
1. 进入"系统设置"页面
|
||||
2. 点击"导入备份"按钮
|
||||
3. 选择备份文件上传
|
||||
4. 确认恢复操作
|
||||
|
||||
### 数据清理
|
||||
- 定期清理过期日志文件
|
||||
- 删除无效的商品信息
|
||||
- 清理过期的对话记录
|
||||
|
||||
## 📞 技术支持
|
||||
|
||||
### 系统健康检查
|
||||
访问健康检查端点:
|
||||
```
|
||||
http://localhost:8080/health
|
||||
```
|
||||
|
||||
### 查看系统状态
|
||||
### 测试系统
|
||||
运行测试脚本检查系统状态:
|
||||
```bash
|
||||
# 查看容器状态
|
||||
docker-compose ps
|
||||
|
||||
# 查看系统资源使用
|
||||
docker stats
|
||||
|
||||
# 查看实时日志
|
||||
docker-compose logs -f
|
||||
python test_system.py
|
||||
```
|
||||
|
||||
### 常见问题解决
|
||||
### 重新创建配置
|
||||
如果配置文件损坏,运行:
|
||||
```bash
|
||||
python create_config.py
|
||||
```
|
||||
|
||||
**问题1:Cookie失效**
|
||||
- 重新获取Cookie并更新
|
||||
- 检查账号是否被限制
|
||||
- 确认Cookie格式正确
|
||||
## 🎯 使用建议
|
||||
|
||||
**问题2:消息接收异常**
|
||||
- 检查网络连接
|
||||
- 重启WebSocket连接
|
||||
- 查看错误日志
|
||||
|
||||
**问题3:AI回复失败**
|
||||
- 检查API密钥是否正确
|
||||
- 确认API服务可用
|
||||
- 检查账户余额
|
||||
|
||||
## 🎯 最佳实践
|
||||
|
||||
### 安全建议
|
||||
1. **定期更换密码**:修改默认管理员密码
|
||||
2. **限制访问**:仅允许信任的IP访问
|
||||
3. **备份数据**:定期备份重要配置和数据
|
||||
4. **监控日志**:定期查看系统日志
|
||||
|
||||
### 性能优化
|
||||
1. **合理设置**:根据实际需求配置参数
|
||||
2. **定期清理**:清理过期数据和日志
|
||||
3. **监控资源**:关注系统资源使用情况
|
||||
4. **优化配置**:根据使用情况调整配置
|
||||
|
||||
### 使用技巧
|
||||
1. **Cookie获取**:使用浏览器开发者工具获取完整Cookie
|
||||
2. **关键词设置**:设置常用的咨询关键词和回复
|
||||
3. **AI配置**:根据商品类型调整AI提示词
|
||||
4. **发货规则**:设置精确的商品匹配规则
|
||||
|
||||
## 🔄 系统更新
|
||||
|
||||
### 更新步骤
|
||||
1. 备份当前数据
|
||||
2. 停止系统服务
|
||||
3. 拉取最新代码
|
||||
4. 重新构建镜像
|
||||
5. 启动更新后的服务
|
||||
|
||||
### Docker更新
|
||||
```bash
|
||||
# 停止服务
|
||||
docker-compose down
|
||||
|
||||
# 拉取最新代码
|
||||
git pull
|
||||
|
||||
# 重新构建
|
||||
./deploy.sh --update
|
||||
|
||||
# 检查状态
|
||||
docker-compose ps
|
||||
```
|
||||
3. **定期检查**:定期查看日志确保系统正常运行
|
||||
4. **备份数据**:重要数据请及时备份
|
||||
|
||||
---
|
||||
|
||||
**注意**:本系统仅供学习交流使用,请遵守相关法律法规和平台规则。使用前请仔细阅读相关文档,确保正确配置和使用。
|
||||
**注意**:本系统仅供学习交流使用,请遵守相关法律法规和平台规则。
|
||||
|
58
商品管理功能说明.md
58
商品管理功能说明.md
@ -194,62 +194,6 @@ Authorization: Bearer {token}
|
||||
- 监控自动发货匹配情况
|
||||
- 及时处理异常情况
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题及解决方案
|
||||
|
||||
**问题1:商品信息收集失败**
|
||||
- 检查网络连接是否正常
|
||||
- 确认Cookie是否有效
|
||||
- 验证商品ID格式是否正确
|
||||
- 查看详细错误日志
|
||||
|
||||
**问题2:商品详情获取失败**
|
||||
- 检查API服务是否可用
|
||||
- 确认商品是否存在
|
||||
- 验证请求参数是否正确
|
||||
- 检查API配置
|
||||
|
||||
**问题3:数据库操作失败**
|
||||
- 检查数据库文件权限
|
||||
- 确认磁盘空间是否充足
|
||||
- 验证数据格式是否正确
|
||||
- 查看数据库错误日志
|
||||
|
||||
### 性能优化建议
|
||||
1. **定期清理**:清理无效的商品信息
|
||||
2. **索引优化**:为常用查询字段建立索引
|
||||
3. **批量操作**:使用批量操作提高效率
|
||||
4. **缓存机制**:缓存常用的商品信息
|
||||
|
||||
## 📊 数据统计
|
||||
|
||||
### 商品统计信息
|
||||
- **总商品数量**:系统中所有商品的总数
|
||||
- **有效商品数**:包含完整信息的商品数量
|
||||
- **账号分布**:各账号的商品数量分布
|
||||
- **分类统计**:不同分类的商品数量
|
||||
|
||||
### 使用统计
|
||||
- **收集成功率**:商品信息收集的成功率
|
||||
- **API调用次数**:商品详情API的调用统计
|
||||
- **匹配成功率**:自动发货匹配的成功率
|
||||
- **处理速度**:商品信息处理的平均速度
|
||||
|
||||
## 🚀 未来规划
|
||||
|
||||
### 即将推出的功能
|
||||
1. **商品分析**:基于商品数据的深度分析
|
||||
2. **价格监控**:监控商品价格变化趋势
|
||||
3. **库存管理**:集成库存管理功能
|
||||
4. **销量统计**:统计商品销售数据
|
||||
|
||||
### 长期发展方向
|
||||
1. **智能推荐**:基于商品数据的智能推荐
|
||||
2. **自动定价**:根据市场数据自动调整价格
|
||||
3. **竞品分析**:分析同类商品的竞争情况
|
||||
4. **数据挖掘**:深度挖掘商品数据价值
|
||||
|
||||
---
|
||||
|
||||
🎉 **商品管理功能为您的闲鱼自动发货提供了强大的数据支持,让自动化运营更加智能和精准!通过持续优化和功能扩展,将为您带来更好的使用体验。**
|
||||
🎉 **商品管理功能让您的闲鱼自动发货更加智能和准确!**
|
||||
|
147
回复优先级优化说明.md
147
回复优先级优化说明.md
@ -1,147 +0,0 @@
|
||||
# 🔄 回复优先级优化说明
|
||||
|
||||
## 📋 优化概述
|
||||
|
||||
本次更新对自动回复系统的优先级逻辑进行了重要调整,确保关键词回复优先于AI回复,提高回复的准确性和用户体验。
|
||||
|
||||
## 🔧 优化内容
|
||||
|
||||
### 原有逻辑(v2.0.0之前)
|
||||
```
|
||||
API回复 → AI回复 → 关键词回复 → 默认回复
|
||||
```
|
||||
|
||||
**问题**:
|
||||
- AI回复可能覆盖重要的关键词回复
|
||||
- 精确的关键词匹配被AI的通用回复替代
|
||||
- 用户设置的关键词回复优先级过低
|
||||
|
||||
### 新的逻辑(v2.0.1)
|
||||
```
|
||||
API回复 → 关键词回复 → AI回复 → 默认回复
|
||||
```
|
||||
|
||||
**优势**:
|
||||
- ✅ 关键词回复优先级提升,确保精确匹配
|
||||
- ✅ AI回复作为智能补充,处理无关键词匹配的情况
|
||||
- ✅ 保持API回复的最高优先级
|
||||
- ✅ 默认回复作为最后的兜底方案
|
||||
|
||||
## 🎯 使用场景
|
||||
|
||||
### 场景1:价格咨询
|
||||
**用户消息**:`"请问这个商品的价格是多少?"`
|
||||
|
||||
**处理流程**:
|
||||
1. 检查API回复 → 无
|
||||
2. 检查关键词匹配 → 匹配"价格"关键词 ✅
|
||||
3. 返回:`"这个商品价格是100元"`
|
||||
|
||||
**结果**:精确的价格信息,而不是AI的通用回复
|
||||
|
||||
### 场景2:通用咨询
|
||||
**用户消息**:`"这个东西怎么样?质量好吗?"`
|
||||
|
||||
**处理流程**:
|
||||
1. 检查API回复 → 无
|
||||
2. 检查关键词匹配 → 无匹配
|
||||
3. 使用AI回复 ✅
|
||||
4. 返回:`"AI智能回复:根据您的问题,我建议..."`
|
||||
|
||||
**结果**:AI提供智能化的个性回复
|
||||
|
||||
### 场景3:兜底回复
|
||||
**用户消息**:`"随便说点什么"`
|
||||
|
||||
**处理流程**:
|
||||
1. 检查API回复 → 无
|
||||
2. 检查关键词匹配 → 无匹配
|
||||
3. 检查AI回复 → 失败或未启用
|
||||
4. 使用默认回复 ✅
|
||||
5. 返回:`"您好,感谢咨询!"`
|
||||
|
||||
**结果**:确保总是有回复内容
|
||||
|
||||
## 📊 优化效果
|
||||
|
||||
### 回复准确性提升
|
||||
- **关键词匹配率**:100%(优先级最高)
|
||||
- **重要信息覆盖**:避免AI回复覆盖重要关键词
|
||||
- **用户体验**:精确回复 + 智能补充
|
||||
|
||||
### 系统稳定性
|
||||
- **回复成功率**:保持100%(多层备选机制)
|
||||
- **响应速度**:关键词匹配更快
|
||||
- **资源使用**:减少不必要的AI调用
|
||||
|
||||
## 🔧 技术实现
|
||||
|
||||
### 代码修改位置
|
||||
**文件**:`XianyuAutoAsync.py`
|
||||
**行数**:1821-1835
|
||||
|
||||
### 修改前代码
|
||||
```python
|
||||
# 如果API回复失败或未启用API,尝试使用AI回复
|
||||
if not reply:
|
||||
reply = await self.get_ai_reply(send_user_name, send_user_id, send_message, item_id, chat_id)
|
||||
if reply:
|
||||
reply_source = 'AI'
|
||||
else:
|
||||
# 如果AI回复也失败,尝试关键词匹配
|
||||
reply = await self.get_keyword_reply(send_user_name, send_user_id, send_message)
|
||||
if reply:
|
||||
reply_source = '关键词'
|
||||
else:
|
||||
# 最后尝试使用默认回复
|
||||
reply = await self.get_default_reply(send_user_name, send_user_id, send_message)
|
||||
reply_source = '默认'
|
||||
```
|
||||
|
||||
### 修改后代码
|
||||
```python
|
||||
# 如果API回复失败或未启用API,按优先级尝试其他回复方式
|
||||
if not reply:
|
||||
# 优先尝试关键词匹配回复
|
||||
reply = await self.get_keyword_reply(send_user_name, send_user_id, send_message)
|
||||
if reply:
|
||||
reply_source = '关键词'
|
||||
else:
|
||||
# 如果关键词匹配失败,尝试AI回复
|
||||
reply = await self.get_ai_reply(send_user_name, send_user_id, send_message, item_id, chat_id)
|
||||
if reply:
|
||||
reply_source = 'AI'
|
||||
else:
|
||||
# 最后尝试使用默认回复
|
||||
reply = await self.get_default_reply(send_user_name, send_user_id, send_message)
|
||||
reply_source = '默认'
|
||||
```
|
||||
|
||||
## 📚 相关文档更新
|
||||
|
||||
### 更新的文档
|
||||
1. **AI_REPLY_GUIDE.md** - 更新优先级说明
|
||||
2. **使用说明.md** - 添加回复优先级说明
|
||||
3. **README.md** - 更新功能特性描述
|
||||
4. **CHANGELOG.md** - 记录版本更新
|
||||
|
||||
### 新增说明
|
||||
- 关键词回复优先于AI回复的原因
|
||||
- 各种回复方式的适用场景
|
||||
- 优化后的用户体验改进
|
||||
|
||||
## 🎉 总结
|
||||
|
||||
这次优化确保了:
|
||||
- **精确性**:重要关键词得到精确回复
|
||||
- **智能性**:AI回复作为智能补充
|
||||
- **稳定性**:多层备选确保回复成功
|
||||
- **用户体验**:更准确、更贴心的自动回复
|
||||
|
||||
通过合理的优先级设计,系统既保持了智能化特性,又确保了关键信息的准确传达,为用户提供更好的自动回复体验。
|
||||
|
||||
---
|
||||
|
||||
**版本**:v2.0.1
|
||||
**更新时间**:2024-07-24
|
||||
**影响范围**:自动回复逻辑核心功能
|
92
日志管理功能说明.md
92
日志管理功能说明.md
@ -280,91 +280,17 @@
|
||||
## 🎯 使用建议
|
||||
|
||||
### 适用场景
|
||||
- ✅ **开发调试**:实时查看程序运行状态和调试信息
|
||||
- ✅ **问题排查**:快速定位错误和异常,分析问题原因
|
||||
- ✅ **性能监控**:监控系统运行情况和性能指标
|
||||
- ✅ **用户支持**:协助用户解决问题,提供技术支持
|
||||
- ✅ **运维监控**:生产环境的实时监控和告警
|
||||
- ✅ **安全审计**:监控系统安全事件和异常行为
|
||||
- ✅ **开发调试**:实时查看程序运行状态
|
||||
- ✅ **问题排查**:快速定位错误和异常
|
||||
- ✅ **性能监控**:监控系统运行情况
|
||||
- ✅ **用户支持**:协助用户解决问题
|
||||
|
||||
### 最佳实践
|
||||
1. **开启自动刷新**:实时监控系统状态,及时发现问题
|
||||
2. **合理使用过滤器**:根据需要过滤特定级别或来源的日志
|
||||
3. **定期查看统计信息**:了解系统整体运行状况和趋势
|
||||
4. **适时清空内存日志**:避免内存占用过多,保持系统性能
|
||||
5. **结合文件日志**:重要日志同时查看文件日志进行备份
|
||||
6. **设置告警规则**:对ERROR级别日志设置告警通知
|
||||
|
||||
### 日志级别使用指南
|
||||
- **DEBUG**:详细的调试信息,开发阶段使用
|
||||
- **INFO**:一般信息,记录程序正常运行状态
|
||||
- **WARNING**:警告信息,需要注意但不影响运行
|
||||
- **ERROR**:错误信息,需要立即处理的问题
|
||||
- **CRITICAL**:严重错误,可能导致程序崩溃
|
||||
|
||||
## 🔧 高级功能
|
||||
|
||||
### 日志导出
|
||||
1. **实时导出**:将当前显示的日志导出为文件
|
||||
2. **批量导出**:导出指定时间范围的日志
|
||||
3. **格式选择**:支持TXT、JSON、CSV等格式
|
||||
4. **自动压缩**:大文件自动压缩处理
|
||||
|
||||
### 日志分析
|
||||
1. **趋势分析**:分析日志数量和级别的时间趋势
|
||||
2. **异常检测**:自动检测异常日志模式
|
||||
3. **性能分析**:分析系统性能相关日志
|
||||
4. **报表生成**:生成日志分析报表
|
||||
|
||||
### 告警配置
|
||||
1. **级别告警**:ERROR级别日志自动告警
|
||||
2. **频率告警**:异常日志频率过高时告警
|
||||
3. **关键词告警**:包含特定关键词的日志告警
|
||||
4. **通知方式**:支持邮件、短信、webhook等通知
|
||||
|
||||
## 🚨 故障排除
|
||||
|
||||
### 常见问题
|
||||
**Q: 日志不更新?**
|
||||
A: 检查以下项目:
|
||||
- 自动刷新是否开启
|
||||
- 网络连接是否正常
|
||||
- 服务器是否正常运行
|
||||
- 浏览器是否支持WebSocket
|
||||
|
||||
**Q: 日志显示不完整?**
|
||||
A: 可能原因:
|
||||
- 内存缓冲区已满,旧日志被清理
|
||||
- 过滤器设置过于严格
|
||||
- 日志级别设置不当
|
||||
|
||||
**Q: 性能影响?**
|
||||
A: 优化建议:
|
||||
- 适当调整刷新频率
|
||||
- 使用过滤器减少显示数量
|
||||
- 定期清空内存日志
|
||||
- 关闭不必要的DEBUG日志
|
||||
|
||||
### 性能优化
|
||||
1. **合理设置缓冲区大小**:根据系统内存调整
|
||||
2. **优化刷新频率**:平衡实时性和性能
|
||||
3. **使用过滤器**:减少不必要的日志传输
|
||||
4. **定期清理**:避免内存泄漏
|
||||
|
||||
## 📊 监控指标
|
||||
|
||||
### 系统指标
|
||||
- **日志生成速率**:每秒生成的日志数量
|
||||
- **内存使用量**:日志缓冲区内存占用
|
||||
- **处理延迟**:日志从生成到显示的延迟
|
||||
- **错误率**:ERROR级别日志的比例
|
||||
|
||||
### 业务指标
|
||||
- **消息处理量**:处理的消息数量统计
|
||||
- **API调用次数**:各API接口的调用统计
|
||||
- **用户活动**:用户操作和访问统计
|
||||
- **系统健康度**:基于日志的系统健康评分
|
||||
1. **开启自动刷新**:实时监控系统状态
|
||||
2. **使用过滤器**:快速找到关注的日志
|
||||
3. **查看统计信息**:了解系统整体状况
|
||||
4. **定期清空**:避免内存占用过多
|
||||
|
||||
---
|
||||
|
||||
🎉 **实时日志管理功能提供了完整的日志查看、分析和监控能力,是系统运维和问题排查的重要工具!**
|
||||
🎉 **实时日志管理功能已完成,提供了真正的实时日志查看、过滤和分析能力!**
|
||||
|
221
日志系统优化说明.md
221
日志系统优化说明.md
@ -1,221 +0,0 @@
|
||||
# 📋 日志系统优化说明
|
||||
|
||||
## 🎯 优化目标
|
||||
|
||||
本次优化的主要目标是:
|
||||
1. **统一日志记录**:所有日志都记录到文件中
|
||||
2. **界面读取文件**:Web界面从日志文件读取并显示
|
||||
3. **智能过滤**:过滤掉不必要的API请求日志
|
||||
4. **提高性能**:减少日志噪音,提高系统性能
|
||||
|
||||
## 🔧 优化内容
|
||||
|
||||
### 1. 统一日志配置
|
||||
|
||||
#### 修改前的问题
|
||||
- 不同模块的日志配置不一致
|
||||
- 部分日志只输出到控制台,不记录到文件
|
||||
- 日志格式不统一,难以解析
|
||||
|
||||
#### 修改后的改进
|
||||
- **统一日志文件**:所有模块都使用相同的日志文件
|
||||
- **统一格式**:使用标准格式便于解析
|
||||
- **文件优先**:移除控制台输出,只记录到文件
|
||||
|
||||
```python
|
||||
# 统一的日志配置格式
|
||||
format='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}'
|
||||
```
|
||||
|
||||
### 2. 智能日志过滤
|
||||
|
||||
#### 过滤的日志类型
|
||||
- **API请求日志**:`GET /logs`, `GET /health`, `GET /docs` 等
|
||||
- **静态资源请求**:`GET /static/`, `favicon.ico` 等
|
||||
- **心跳检查**:WebSocket心跳、健康检查等
|
||||
- **频繁状态检查**:连接状态检查等
|
||||
|
||||
#### 过滤器实现
|
||||
```python
|
||||
# log_filter.py
|
||||
class LogFilter:
|
||||
def __init__(self):
|
||||
self.excluded_patterns = [
|
||||
r'GET /logs',
|
||||
r'GET /health',
|
||||
r'.*favicon\.ico.*',
|
||||
r'.*websocket.*ping.*'
|
||||
]
|
||||
|
||||
def should_log(self, record):
|
||||
# 智能判断是否应该记录日志
|
||||
return not self._matches_excluded_pattern(record['message'])
|
||||
```
|
||||
|
||||
### 3. 文件监控优化
|
||||
|
||||
#### 监控改进
|
||||
- **实时监控**:从0.5秒优化到0.2秒检查频率
|
||||
- **错误处理**:增强文件读取的错误处理
|
||||
- **编码支持**:支持UTF-8编码,忽略编码错误
|
||||
- **文件重置检测**:检测日志文件被截断或重新创建
|
||||
|
||||
#### 解析优化
|
||||
- **多格式支持**:支持多种日志格式解析
|
||||
- **容错处理**:解析失败时的优雅降级
|
||||
- **性能优化**:预编译正则表达式提高解析速度
|
||||
|
||||
## 📊 优化效果
|
||||
|
||||
### 性能提升
|
||||
- **日志数量减少**:过滤掉约60%的无用日志
|
||||
- **文件大小减少**:日志文件大小减少约50%
|
||||
- **界面响应更快**:减少不必要的日志传输
|
||||
|
||||
### 用户体验改善
|
||||
- **日志更清晰**:只显示有价值的日志信息
|
||||
- **加载更快**:减少日志数量,界面加载更快
|
||||
- **查找更容易**:减少噪音,更容易找到关键信息
|
||||
|
||||
### 系统稳定性
|
||||
- **内存使用优化**:减少内存中的日志缓存
|
||||
- **磁盘空间节省**:减少日志文件占用空间
|
||||
- **网络传输优化**:减少API传输的数据量
|
||||
|
||||
## 🔍 技术实现
|
||||
|
||||
### 1. 模块级配置
|
||||
|
||||
#### XianyuAutoAsync.py
|
||||
```python
|
||||
# 导入日志过滤器
|
||||
from log_filter import filter_log_record
|
||||
|
||||
# 配置文件日志处理器
|
||||
logger.add(
|
||||
log_path,
|
||||
format='{time:YYYY-MM-DD HH:mm:ss.SSS} | {level} | {name}:{function}:{line} - {message}',
|
||||
filter=filter_log_record # 应用过滤器
|
||||
)
|
||||
```
|
||||
|
||||
#### reply_server.py
|
||||
```python
|
||||
# 排除不需要记录的API路径
|
||||
EXCLUDED_LOG_PATHS = {
|
||||
'/logs', '/logs/stats', '/health', '/docs'
|
||||
}
|
||||
|
||||
# 中间件级别的过滤
|
||||
@app.middleware("http")
|
||||
async def log_requests(request, call_next):
|
||||
should_log = request.url.path not in EXCLUDED_LOG_PATHS
|
||||
if should_log:
|
||||
logger.info(f"API请求: {request.method} {request.url.path}")
|
||||
```
|
||||
|
||||
### 2. 文件监控系统
|
||||
|
||||
#### FileLogCollector优化
|
||||
```python
|
||||
def monitor_file(self):
|
||||
while True:
|
||||
if os.path.exists(self.log_file):
|
||||
file_size = os.path.getsize(self.log_file)
|
||||
if file_size > self.last_position:
|
||||
# 读取新增内容
|
||||
with open(self.log_file, 'r', encoding='utf-8', errors='ignore') as f:
|
||||
f.seek(self.last_position)
|
||||
new_lines = f.readlines()
|
||||
self.last_position = f.tell()
|
||||
|
||||
# 解析新增日志
|
||||
for line in new_lines:
|
||||
if line.strip():
|
||||
self.parse_log_line(line.strip())
|
||||
|
||||
time.sleep(0.2) # 更频繁的检查
|
||||
```
|
||||
|
||||
### 3. 日志解析增强
|
||||
|
||||
#### 多格式支持
|
||||
```python
|
||||
def parse_log_line(self, line):
|
||||
# 主格式:统一格式
|
||||
pattern = r'(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3}) \| (\w+) \| ([^:]+):([^:]+):(\d+) - (.*)'
|
||||
|
||||
# 备用格式:简单格式
|
||||
simple_pattern = r'\[([^\]]+)\] \[(\w+)\] (.*)'
|
||||
|
||||
# 容错处理:解析失败时的处理
|
||||
if not match:
|
||||
# 作为普通消息处理
|
||||
log_entry = {
|
||||
"timestamp": datetime.now().isoformat(),
|
||||
"level": "INFO",
|
||||
"source": "system",
|
||||
"message": line.strip()
|
||||
}
|
||||
```
|
||||
|
||||
## 📈 使用指南
|
||||
|
||||
### 1. 查看日志
|
||||
- **Web界面**:访问 http://localhost:8080,点击"日志管理"
|
||||
- **实时更新**:日志会实时显示,无需手动刷新
|
||||
- **过滤功能**:可按级别、来源、关键词过滤
|
||||
|
||||
### 2. 日志文件位置
|
||||
```
|
||||
logs/
|
||||
└── xianyu_2024-07-24.log # 按日期命名的日志文件
|
||||
```
|
||||
|
||||
### 3. 自定义过滤规则
|
||||
```python
|
||||
# 添加新的过滤规则
|
||||
from log_filter import add_excluded_pattern
|
||||
add_excluded_pattern(r'自定义过滤模式')
|
||||
|
||||
# 查看当前过滤规则
|
||||
from log_filter import get_excluded_patterns
|
||||
patterns = get_excluded_patterns()
|
||||
```
|
||||
|
||||
## 🚨 注意事项
|
||||
|
||||
### 1. 日志文件管理
|
||||
- **自动轮转**:日志文件按天轮转,自动压缩
|
||||
- **保留期限**:默认保留7天的日志文件
|
||||
- **磁盘空间**:注意监控磁盘空间使用情况
|
||||
|
||||
### 2. 性能考虑
|
||||
- **过滤器性能**:过滤器使用预编译正则表达式,性能较好
|
||||
- **文件监控**:监控频率为0.2秒,平衡实时性和性能
|
||||
- **内存使用**:日志缓存限制为2000条,避免内存溢出
|
||||
|
||||
### 3. 故障排除
|
||||
- **日志不显示**:检查日志文件是否存在和权限
|
||||
- **过滤过度**:检查过滤规则是否过于严格
|
||||
- **性能问题**:可以调整监控频率和缓存大小
|
||||
|
||||
## 🔮 未来规划
|
||||
|
||||
### 即将推出
|
||||
1. **日志分析**:基于日志的系统分析和报告
|
||||
2. **告警系统**:基于日志的智能告警
|
||||
3. **日志搜索**:全文搜索和高级查询
|
||||
4. **性能监控**:基于日志的性能指标
|
||||
|
||||
### 长期规划
|
||||
1. **分布式日志**:支持多实例的日志聚合
|
||||
2. **日志可视化**:图表和仪表板展示
|
||||
3. **机器学习**:基于日志的异常检测
|
||||
4. **API开放**:提供日志查询API
|
||||
|
||||
---
|
||||
|
||||
**版本**:v2.0.2
|
||||
**更新时间**:2024-07-24
|
||||
**影响范围**:日志系统核心功能
|
92
自动发货功能说明.md
92
自动发货功能说明.md
@ -254,92 +254,12 @@ python test-item-info-delivery.py
|
||||
|
||||
## 💡 最佳实践
|
||||
|
||||
### 规则设计原则
|
||||
1. **关键字精确性**:关键字要具体明确,避免过于宽泛
|
||||
2. **优先级设置**:重要商品设置更长的关键字,提高匹配优先级
|
||||
3. **分类管理**:按商品类型分组管理发货规则
|
||||
4. **定期更新**:根据商品变化及时更新匹配规则
|
||||
|
||||
### 库存管理策略
|
||||
1. **实时监控**:定期检查批量数据库存状态
|
||||
2. **预警机制**:设置库存低于阈值时的告警
|
||||
3. **自动补充**:配置自动补充机制或定期手动补充
|
||||
4. **分批管理**:将卡券分批次管理,避免一次性消耗完
|
||||
|
||||
### 质量控制措施
|
||||
1. **测试验证**:新规则上线前在测试环境充分测试
|
||||
2. **灰度发布**:新规则先在部分商品上试运行
|
||||
3. **监控告警**:设置发货失败率告警,及时处理异常
|
||||
4. **人工审核**:重要商品可设置人工审核环节
|
||||
|
||||
### 数据分析优化
|
||||
1. **日志分析**:定期分析发货日志,识别问题模式
|
||||
2. **成功率统计**:监控各规则的匹配成功率
|
||||
3. **用户反馈**:收集买家反馈,优化发货内容
|
||||
4. **性能监控**:监控发货响应时间,优化处理流程
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题及解决方案
|
||||
|
||||
**问题1:发货规则不匹配**
|
||||
- 检查关键字是否正确
|
||||
- 确认商品信息是否完整
|
||||
- 验证匹配逻辑是否合理
|
||||
- 查看详细的匹配日志
|
||||
|
||||
**问题2:API接口调用失败**
|
||||
- 检查API地址是否正确
|
||||
- 验证请求参数和格式
|
||||
- 确认网络连接状态
|
||||
- 查看API服务状态
|
||||
|
||||
**问题3:批量数据消耗过快**
|
||||
- 检查匹配规则是否过于宽泛
|
||||
- 确认是否有重复发货
|
||||
- 调整匹配策略
|
||||
- 增加库存补充频率
|
||||
|
||||
**问题4:发货消息发送失败**
|
||||
- 检查账号连接状态
|
||||
- 验证消息格式是否正确
|
||||
- 确认网络连接稳定
|
||||
- 查看WebSocket连接日志
|
||||
|
||||
### 调试技巧
|
||||
1. **开启详细日志**:在配置中开启DEBUG级别日志
|
||||
2. **单步测试**:使用测试功能验证单个规则
|
||||
3. **模拟环境**:在测试环境模拟真实场景
|
||||
4. **监控面板**:使用系统监控面板查看实时状态
|
||||
|
||||
## 📈 性能优化
|
||||
|
||||
### 系统性能优化
|
||||
1. **缓存机制**:缓存商品信息,减少API调用
|
||||
2. **异步处理**:使用异步处理提高并发能力
|
||||
3. **连接池**:优化数据库连接池配置
|
||||
4. **资源限制**:合理设置系统资源限制
|
||||
|
||||
### 业务流程优化
|
||||
1. **规则优化**:优化匹配算法,提高匹配效率
|
||||
2. **批量处理**:支持批量发货操作
|
||||
3. **智能调度**:根据系统负载智能调度任务
|
||||
4. **预处理**:预处理商品信息,提高匹配速度
|
||||
|
||||
## 🔐 安全考虑
|
||||
|
||||
### 数据安全
|
||||
1. **敏感信息加密**:卡券内容加密存储
|
||||
2. **访问控制**:严格的权限控制机制
|
||||
3. **审计日志**:完整的操作审计日志
|
||||
4. **备份恢复**:定期备份重要数据
|
||||
|
||||
### 业务安全
|
||||
1. **防重复发货**:严格的重复发货检测
|
||||
2. **异常监控**:实时监控异常发货行为
|
||||
3. **人工干预**:支持紧急情况下的人工干预
|
||||
4. **风险控制**:设置发货频率和数量限制
|
||||
1. **规则设计**:关键字要具体明确,避免过于宽泛
|
||||
2. **库存管理**:定期检查批量数据库存,及时补充
|
||||
3. **监控告警**:设置发货失败告警,及时处理异常
|
||||
4. **测试验证**:新规则上线前充分测试
|
||||
5. **日志分析**:定期分析发货日志,优化匹配规则
|
||||
|
||||
---
|
||||
|
||||
🎉 **自动发货功能让您的闲鱼店铺实现真正的自动化运营!通过合理配置和优化,可以大大提高运营效率和用户体验。**
|
||||
🎉 **自动发货功能让您的闲鱼店铺实现真正的自动化运营!**
|
||||
|
120
获取所有商品功能说明.md
120
获取所有商品功能说明.md
@ -181,120 +181,22 @@ async def get_item_list_info(self, retry_count=0):
|
||||
- 错误信息记录
|
||||
- 便于问题排查
|
||||
|
||||
## 🚀 高级功能
|
||||
## 🚀 扩展可能
|
||||
|
||||
### 1. 批量操作
|
||||
- **多账号批量获取**:一次性获取所有账号的商品信息
|
||||
- **定时自动获取**:设置定时任务自动更新商品信息
|
||||
- **增量更新**:只获取新增或变更的商品信息
|
||||
- **并发处理**:支持多账号并发获取,提高效率
|
||||
- 支持多个账号批量获取
|
||||
- 导出商品信息到文件
|
||||
- 商品信息对比分析
|
||||
|
||||
### 2. 数据处理与分析
|
||||
- **商品信息入库**:自动将获取的商品信息存储到数据库
|
||||
- **商品状态监控**:监控商品上下架状态变化
|
||||
- **价格变化追踪**:跟踪商品价格变化趋势
|
||||
- **销量统计**:统计商品浏览量和销售数据
|
||||
- **数据导出**:支持导出为Excel、CSV等格式
|
||||
### 2. 数据处理
|
||||
- 商品信息入库存储
|
||||
- 商品状态监控
|
||||
- 价格变化追踪
|
||||
|
||||
### 3. 界面优化
|
||||
- **商品信息表格**:以表格形式展示商品详细信息
|
||||
- **商品图片预览**:显示商品主图和详情图
|
||||
- **高级筛选**:按价格、分类、状态等条件筛选
|
||||
- **搜索功能**:支持商品标题、描述的全文搜索
|
||||
- **排序功能**:按时间、价格、浏览量等排序
|
||||
|
||||
### 4. 智能分析
|
||||
- **商品分类统计**:自动分析商品分类分布
|
||||
- **价格区间分析**:分析不同价格区间的商品数量
|
||||
- **热门商品识别**:基于浏览量识别热门商品
|
||||
- **库存预警**:监控商品库存状态,及时预警
|
||||
|
||||
## 📊 数据统计
|
||||
|
||||
### 获取统计信息
|
||||
- **总商品数量**:账号下所有商品的总数
|
||||
- **在售商品数**:当前在售状态的商品数量
|
||||
- **已售出商品数**:已售出的商品数量
|
||||
- **平均价格**:所有商品的平均售价
|
||||
- **价格分布**:不同价格区间的商品分布
|
||||
|
||||
### 性能指标
|
||||
- **获取速度**:每秒获取的商品数量
|
||||
- **成功率**:获取成功的商品比例
|
||||
- **错误率**:获取失败的商品比例
|
||||
- **响应时间**:API响应时间统计
|
||||
|
||||
## 🔧 故障排除
|
||||
|
||||
### 常见问题及解决方案
|
||||
|
||||
**问题1:获取失败**
|
||||
- 检查账号Cookie是否有效
|
||||
- 确认网络连接是否正常
|
||||
- 验证账号是否被限制
|
||||
- 查看详细错误日志
|
||||
|
||||
**问题2:获取速度慢**
|
||||
- 检查网络连接质量
|
||||
- 确认服务器负载情况
|
||||
- 优化获取策略
|
||||
- 考虑分批次获取
|
||||
|
||||
**问题3:数据不完整**
|
||||
- 检查API返回数据格式
|
||||
- 确认商品状态是否正常
|
||||
- 验证解析逻辑是否正确
|
||||
- 查看控制台错误信息
|
||||
|
||||
**问题4:Token频繁失效**
|
||||
- 检查Cookie有效期
|
||||
- 确认账号登录状态
|
||||
- 优化Token刷新策略
|
||||
- 考虑降低获取频率
|
||||
|
||||
### 调试技巧
|
||||
1. **开启详细日志**:查看完整的获取过程
|
||||
2. **单步测试**:先测试单个商品获取
|
||||
3. **网络监控**:监控网络请求和响应
|
||||
4. **数据验证**:验证获取数据的完整性
|
||||
|
||||
## 💡 使用建议
|
||||
|
||||
### 最佳实践
|
||||
1. **合理频率**:避免过于频繁的获取操作
|
||||
2. **错峰使用**:在网络较好的时段进行获取
|
||||
3. **数据备份**:定期备份重要的商品数据
|
||||
4. **监控告警**:设置获取失败的告警机制
|
||||
|
||||
### 性能优化
|
||||
1. **缓存机制**:缓存已获取的商品信息
|
||||
2. **增量更新**:只获取变更的商品信息
|
||||
3. **并发控制**:合理控制并发获取数量
|
||||
4. **资源管理**:及时释放不需要的资源
|
||||
|
||||
### 安全考虑
|
||||
1. **权限控制**:限制获取功能的使用权限
|
||||
2. **频率限制**:设置合理的获取频率限制
|
||||
3. **数据保护**:保护获取的商品数据安全
|
||||
4. **审计日志**:记录所有获取操作的审计日志
|
||||
|
||||
## 🔮 未来规划
|
||||
|
||||
### 即将推出的功能
|
||||
1. **商品同步**:支持与其他平台的商品信息同步
|
||||
2. **智能推荐**:基于商品数据的智能推荐算法
|
||||
3. **自动定价**:根据市场数据自动调整商品价格
|
||||
4. **竞品分析**:分析同类商品的价格和销量
|
||||
|
||||
### 长期发展方向
|
||||
1. **大数据分析**:基于海量商品数据的深度分析
|
||||
2. **机器学习**:使用AI技术优化商品运营策略
|
||||
3. **API开放**:提供开放API供第三方系统集成
|
||||
4. **移动端支持**:开发移动端应用,随时随地管理商品
|
||||
|
||||
---
|
||||
|
||||
🎉 **获取所有商品功能为商品管理提供了强大的数据获取能力,是商品分析和运营的重要工具!通过持续优化和功能扩展,将为用户提供更加完善的商品管理解决方案。**
|
||||
- 商品信息表格显示
|
||||
- 商品图片预览
|
||||
- 筛选和搜索功能
|
||||
|
||||
---
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user