mirror of
https://github.com/zhinianboke/xianyu-auto-reply.git
synced 2025-08-26 07:37:35 +08:00
新增多种通知渠道,支持ai模型自定义
This commit is contained in:
parent
49693760b8
commit
789870b334
58
Dockerfile
58
Dockerfile
@ -2,10 +2,12 @@
|
||||
FROM python:3.11-slim
|
||||
|
||||
# 设置标签信息
|
||||
LABEL maintainer="Xianyu Auto Reply System"
|
||||
LABEL maintainer="zhinianboke"
|
||||
LABEL version="2.0.0"
|
||||
LABEL description="闲鱼自动回复系统 - 企业级多用户版本"
|
||||
LABEL repository="https://github.com/zhinianboke/xianyu-auto-reply"
|
||||
LABEL license="仅供学习使用,禁止商业用途"
|
||||
LABEL author="zhinianboke"
|
||||
|
||||
# 设置工作目录
|
||||
WORKDIR /app
|
||||
@ -20,14 +22,18 @@ ENV PLAYWRIGHT_BROWSERS_PATH=/ms-playwright
|
||||
# 安装系统依赖(包括Playwright浏览器依赖)
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends \
|
||||
# 基础工具
|
||||
nodejs \
|
||||
npm \
|
||||
tzdata \
|
||||
curl \
|
||||
ca-certificates \
|
||||
# 图像处理依赖
|
||||
libjpeg-dev \
|
||||
libpng-dev \
|
||||
libfreetype6-dev \
|
||||
fonts-dejavu-core \
|
||||
fonts-liberation \
|
||||
# Playwright浏览器依赖
|
||||
libnss3 \
|
||||
libnspr4 \
|
||||
@ -50,31 +56,15 @@ RUN apt-get update && \
|
||||
libx11-6 \
|
||||
libxft2 \
|
||||
libxinerama1 \
|
||||
libxrandr2 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
ca-certificates \
|
||||
fonts-liberation \
|
||||
libappindicator3-1 \
|
||||
libasound2 \
|
||||
libatk-bridge2.0-0 \
|
||||
libdrm2 \
|
||||
libgtk-3-0 \
|
||||
libnspr4 \
|
||||
libnss3 \
|
||||
libx11-xcb1 \
|
||||
libxcomposite1 \
|
||||
libxcursor1 \
|
||||
libxdamage1 \
|
||||
libxfixes3 \
|
||||
libxi6 \
|
||||
libxrandr2 \
|
||||
libxrender1 \
|
||||
libxss1 \
|
||||
libxtst6 \
|
||||
xdg-utils \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& rm -rf /tmp/* \
|
||||
&& rm -rf /var/tmp/*
|
||||
|
||||
# 设置时区
|
||||
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
|
||||
@ -110,18 +100,22 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
# 创建启动脚本
|
||||
RUN echo '#!/bin/bash' > /app/entrypoint.sh && \
|
||||
echo 'set -e' >> /app/entrypoint.sh && \
|
||||
echo '' >> /app/entrypoint.sh && \
|
||||
echo 'echo "🚀 启动闲鱼自动回复系统..."' >> /app/entrypoint.sh && \
|
||||
echo '' >> /app/entrypoint.sh && \
|
||||
echo '# 数据库将在应用启动时自动初始化' >> /app/entrypoint.sh && \
|
||||
echo 'echo "📊 数据库将在应用启动时自动初始化..."' >> /app/entrypoint.sh && \
|
||||
echo '' >> /app/entrypoint.sh && \
|
||||
echo '# 启动主应用' >> /app/entrypoint.sh && \
|
||||
echo 'echo "🎯 启动主应用..."' >> /app/entrypoint.sh && \
|
||||
echo 'exec python Start.py' >> /app/entrypoint.sh && \
|
||||
chmod +x /app/entrypoint.sh
|
||||
COPY <<EOF /app/entrypoint.sh
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
echo "🚀 启动闲鱼自动回复系统..."
|
||||
echo "📊 数据库将在应用启动时自动初始化..."
|
||||
echo "🎯 启动主应用..."
|
||||
|
||||
# 确保数据目录存在
|
||||
mkdir -p /app/data /app/logs /app/backups
|
||||
|
||||
# 启动主应用
|
||||
exec python Start.py
|
||||
EOF
|
||||
|
||||
RUN chmod +x /app/entrypoint.sh
|
||||
|
||||
# 启动命令
|
||||
CMD ["/app/entrypoint.sh"]
|
183
README.md
183
README.md
@ -160,14 +160,132 @@ cd xianyu-auto-reply
|
||||
# http://localhost:8080
|
||||
```
|
||||
|
||||
#### 🔧 Docker部署故障排除
|
||||
### 方式三:本地部署(开发环境)
|
||||
|
||||
如果遇到构建问题,请参考 [Docker修复指南](DOCKER_FIX.md)
|
||||
```bash
|
||||
# 1. 克隆项目
|
||||
git clone https://github.com/zhinianboke/xianyu-auto-reply.git
|
||||
cd xianyu-auto-reply
|
||||
|
||||
**常见问题**:
|
||||
- **sqlite3错误**:已修复,sqlite3是Python内置模块,无需安装
|
||||
- **Docker未运行**:确保Docker Desktop正在运行
|
||||
- **端口冲突**:修改docker-compose.yml中的端口映射为其他端口
|
||||
# 2. 创建虚拟环境(推荐)
|
||||
python -m venv venv
|
||||
source venv/bin/activate # Linux/macOS
|
||||
# 或 venv\Scripts\activate # Windows
|
||||
|
||||
# 3. 安装Python依赖
|
||||
pip install --upgrade pip
|
||||
pip install -r requirements.txt
|
||||
|
||||
# 4. 安装Playwright浏览器
|
||||
playwright install chromium
|
||||
playwright install-deps chromium # Linux需要
|
||||
|
||||
# 5. 启动系统
|
||||
python Start.py
|
||||
|
||||
# 6. 访问系统
|
||||
# http://localhost:8080
|
||||
```
|
||||
|
||||
### 📋 环境要求
|
||||
|
||||
- **Python**: 3.11+
|
||||
- **Node.js**: 16+ (用于JavaScript执行)
|
||||
- **系统**: Windows/Linux/macOS
|
||||
- **内存**: 建议2GB+
|
||||
- **存储**: 建议10GB+
|
||||
- **Docker**: 20.10+ (Docker部署)
|
||||
- **Docker Compose**: 2.0+ (Docker部署)
|
||||
|
||||
### 🌐 访问系统
|
||||
|
||||
部署完成后,您可以通过以下方式访问系统:
|
||||
|
||||
- **Web管理界面**:http://localhost:8080
|
||||
- **默认管理员账号**:
|
||||
- 用户名:`admin`
|
||||
- 密码:`admin123`
|
||||
- **API文档**:http://localhost:8080/docs
|
||||
- **健康检查**:http://localhost:8080/health
|
||||
|
||||
> ⚠️ **安全提示**:首次登录后请立即修改默认密码!
|
||||
|
||||
### 🔧 Docker部署管理
|
||||
|
||||
使用 `docker-deploy.sh` 脚本可以方便地管理Docker部署:
|
||||
|
||||
```bash
|
||||
# 查看所有可用命令
|
||||
./docker-deploy.sh help
|
||||
|
||||
# 初始化配置
|
||||
./docker-deploy.sh init
|
||||
|
||||
# 构建镜像
|
||||
./docker-deploy.sh build
|
||||
|
||||
# 启动服务
|
||||
./docker-deploy.sh start
|
||||
|
||||
# 启动包含Nginx的完整服务
|
||||
./docker-deploy.sh start with-nginx
|
||||
|
||||
# 查看服务状态
|
||||
./docker-deploy.sh status
|
||||
|
||||
# 查看实时日志
|
||||
./docker-deploy.sh logs
|
||||
|
||||
# 备份数据
|
||||
./docker-deploy.sh backup
|
||||
|
||||
# 更新部署
|
||||
./docker-deploy.sh update
|
||||
|
||||
# 停止服务
|
||||
./docker-deploy.sh stop
|
||||
|
||||
# 重启服务
|
||||
./docker-deploy.sh restart
|
||||
|
||||
# 清理环境
|
||||
./docker-deploy.sh cleanup
|
||||
```
|
||||
|
||||
### 🛠️ 故障排除
|
||||
|
||||
**常见问题及解决方案**:
|
||||
|
||||
1. **Docker未运行**
|
||||
```bash
|
||||
# 启动Docker Desktop或Docker服务
|
||||
sudo systemctl start docker # Linux
|
||||
```
|
||||
|
||||
2. **端口冲突**
|
||||
```bash
|
||||
# 修改.env文件中的WEB_PORT
|
||||
WEB_PORT=8081
|
||||
```
|
||||
|
||||
3. **权限问题**
|
||||
```bash
|
||||
# 确保数据目录有正确权限
|
||||
sudo chown -R $USER:$USER ./data ./logs ./backups
|
||||
```
|
||||
|
||||
4. **内存不足**
|
||||
```bash
|
||||
# 调整.env文件中的资源限制
|
||||
MEMORY_LIMIT=1024
|
||||
CPU_LIMIT=1.0
|
||||
```
|
||||
|
||||
5. **Playwright浏览器安装失败**
|
||||
```bash
|
||||
# 手动安装浏览器
|
||||
playwright install chromium --with-deps
|
||||
```
|
||||
- **权限问题**:Linux系统下使用 `sudo ./docker-deploy.sh`
|
||||
|
||||
### 方式三:本地部署
|
||||
@ -270,6 +388,59 @@ docker rm -f xianyu-auto-reply
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
## ✨ 核心功能特性
|
||||
|
||||
### 🚀 自动回复系统
|
||||
- **智能关键词匹配** - 支持精确匹配和模糊匹配,灵活配置回复规则
|
||||
- **AI智能回复** - 集成多种AI模型(通义千问、GPT等),智能理解用户意图
|
||||
- **多账号管理** - 支持同时管理多个闲鱼账号,独立配置和运行
|
||||
- **实时消息处理** - WebSocket长连接,毫秒级响应用户消息
|
||||
- **自定义回复模板** - 支持占位符和动态内容,个性化回复体验
|
||||
|
||||
### 🛒 自动发货系统
|
||||
- **智能订单识别** - 自动识别虚拟商品订单,精准匹配发货规则
|
||||
- **多重安全验证** - 超级加密保护,防止误操作和数据泄露
|
||||
- **批量处理能力** - 支持批量确认发货,提高处理效率
|
||||
- **异常处理机制** - 完善的错误处理和重试机制,确保发货成功
|
||||
- **多渠道通知** - 支持QQ、钉钉、邮件等多种发货通知方式
|
||||
|
||||
### 👥 多用户系统
|
||||
- **用户注册登录** - 支持邮箱验证和图形验证码,安全可靠
|
||||
- **权限管理** - 管理员和普通用户权限分离,精细化权限控制
|
||||
- **数据隔离** - 每个用户的数据完全隔离,保护隐私安全
|
||||
- **会话管理** - JWT Token认证,支持自动续期和安全登出
|
||||
|
||||
### 📊 数据管理
|
||||
- **商品信息管理** - 自动获取和同步商品信息,实时更新状态
|
||||
- **订单数据统计** - 详细的订单数据分析和可视化图表
|
||||
- **关键词管理** - 灵活的关键词配置,支持正则表达式
|
||||
- **数据导入导出** - 支持Excel格式的批量数据操作
|
||||
- **自动备份** - 定期自动备份重要数据,防止数据丢失
|
||||
|
||||
### 🔍 商品搜索
|
||||
- **真实数据获取** - 基于Playwright技术,获取真实闲鱼商品数据
|
||||
- **多页搜索** - 支持分页搜索和批量获取,无限制数据采集
|
||||
- **数据可视化** - 美观的商品展示界面,支持排序和筛选
|
||||
- **搜索历史** - 保存搜索历史和结果,方便数据分析
|
||||
|
||||
### 📱 通知系统
|
||||
- **多渠道支持** - QQ、钉钉、邮件、微信、Telegram等6种通知方式
|
||||
- **智能配置** - 可视化配置界面,支持复杂参数和加密设置
|
||||
- **实时推送** - 重要事件实时通知,及时了解系统状态
|
||||
- **通知模板** - 自定义通知内容和格式,个性化消息推送
|
||||
|
||||
### 🔐 安全特性
|
||||
- **Cookie安全管理** - 加密存储用户凭证,定期自动刷新
|
||||
- **Token自动刷新** - 智能检测和刷新过期Token,保持连接稳定
|
||||
- **操作日志** - 详细记录所有操作日志,支持审计和追踪
|
||||
- **异常监控** - 实时监控系统异常和错误,主动预警
|
||||
|
||||
### 🎨 用户界面
|
||||
- **现代化设计** - 基于Bootstrap 5的响应式界面,美观易用
|
||||
- **多主题支持** - 支持明暗主题切换,个性化界面体验
|
||||
- **移动端适配** - 完美适配手机和平板设备,随时随地管理
|
||||
- **实时更新** - 界面数据实时更新,无需手动刷新
|
||||
|
||||
## 📁 核心文件功能说明
|
||||
|
||||
### 🚀 启动和核心模块
|
||||
|
@ -918,11 +918,22 @@ class XianyuLive:
|
||||
channel_config = notification.get('channel_config')
|
||||
|
||||
try:
|
||||
# 解析配置数据
|
||||
config_data = self._parse_notification_config(channel_config)
|
||||
|
||||
match channel_type:
|
||||
case 'qq':
|
||||
await self._send_qq_notification(channel_config, notification_msg)
|
||||
case 'ding_talk':
|
||||
await self._send_ding_talk_notification(channel_config, notification_msg)
|
||||
await self._send_qq_notification(config_data, notification_msg)
|
||||
case 'ding_talk' | 'dingtalk':
|
||||
await self._send_dingtalk_notification(config_data, notification_msg)
|
||||
case 'email':
|
||||
await self._send_email_notification(config_data, notification_msg)
|
||||
case 'webhook':
|
||||
await self._send_webhook_notification(config_data, notification_msg)
|
||||
case 'wechat':
|
||||
await self._send_wechat_notification(config_data, notification_msg)
|
||||
case 'telegram':
|
||||
await self._send_telegram_notification(config_data, notification_msg)
|
||||
case _:
|
||||
logger.warning(f"不支持的通知渠道类型: {channel_type}")
|
||||
|
||||
@ -932,13 +943,25 @@ class XianyuLive:
|
||||
except Exception as e:
|
||||
logger.error(f"处理消息通知失败: {self._safe_str(e)}")
|
||||
|
||||
async def _send_qq_notification(self, config: str, message: str):
|
||||
def _parse_notification_config(self, config: str) -> dict:
|
||||
"""解析通知配置数据"""
|
||||
try:
|
||||
import json
|
||||
# 尝试解析JSON格式的配置
|
||||
return json.loads(config)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# 兼容旧格式(直接字符串)
|
||||
return {"config": config}
|
||||
|
||||
async def _send_qq_notification(self, config_data: dict, message: str):
|
||||
"""发送QQ通知"""
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
# 解析配置(QQ号码)
|
||||
qq_number = config.strip()
|
||||
qq_number = config_data.get('qq_number') or config_data.get('config', '')
|
||||
qq_number = qq_number.strip() if qq_number else ''
|
||||
|
||||
if not qq_number:
|
||||
logger.warning("QQ通知配置为空")
|
||||
return
|
||||
@ -961,17 +984,35 @@ class XianyuLive:
|
||||
except Exception as e:
|
||||
logger.error(f"发送QQ通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def _send_ding_talk_notification(self, config: str, message: str):
|
||||
async def _send_dingtalk_notification(self, config_data: dict, message: str):
|
||||
"""发送钉钉通知"""
|
||||
try:
|
||||
import aiohttp
|
||||
import json
|
||||
# 解析配置(钉钉机器人Webhook URL)
|
||||
webhook_url = config.strip()
|
||||
import hmac
|
||||
import hashlib
|
||||
import base64
|
||||
import time
|
||||
|
||||
# 解析配置
|
||||
webhook_url = config_data.get('webhook_url') or config_data.get('config', '')
|
||||
secret = config_data.get('secret', '')
|
||||
|
||||
webhook_url = webhook_url.strip() if webhook_url else ''
|
||||
if not webhook_url:
|
||||
logger.warning("钉钉通知配置为空")
|
||||
return
|
||||
|
||||
# 如果有加签密钥,生成签名
|
||||
if secret:
|
||||
timestamp = str(round(time.time() * 1000))
|
||||
secret_enc = secret.encode('utf-8')
|
||||
string_to_sign = f'{timestamp}\n{secret}'
|
||||
string_to_sign_enc = string_to_sign.encode('utf-8')
|
||||
hmac_code = hmac.new(secret_enc, string_to_sign_enc, digestmod=hashlib.sha256).digest()
|
||||
sign = base64.b64encode(hmac_code).decode('utf-8')
|
||||
webhook_url += f'×tamp={timestamp}&sign={sign}'
|
||||
|
||||
data = {
|
||||
"msgtype": "markdown",
|
||||
"markdown": {
|
||||
@ -983,13 +1024,165 @@ class XianyuLive:
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(webhook_url, json=data, timeout=10) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"钉钉通知发送成功: {webhook_url}")
|
||||
logger.info(f"钉钉通知发送成功")
|
||||
else:
|
||||
logger.warning(f"钉钉通知发送失败: {response.status}")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送钉钉通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def _send_email_notification(self, config_data: dict, message: str):
|
||||
"""发送邮件通知"""
|
||||
try:
|
||||
import smtplib
|
||||
from email.mime.text import MIMEText
|
||||
from email.mime.multipart import MIMEMultipart
|
||||
|
||||
# 解析配置
|
||||
smtp_server = config_data.get('smtp_server', '')
|
||||
smtp_port = int(config_data.get('smtp_port', 587))
|
||||
email_user = config_data.get('email_user', '')
|
||||
email_password = config_data.get('email_password', '')
|
||||
recipient_email = config_data.get('recipient_email', '')
|
||||
|
||||
if not all([smtp_server, email_user, email_password, recipient_email]):
|
||||
logger.warning("邮件通知配置不完整")
|
||||
return
|
||||
|
||||
# 创建邮件
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = email_user
|
||||
msg['To'] = recipient_email
|
||||
msg['Subject'] = "闲鱼自动回复通知"
|
||||
|
||||
# 添加邮件正文
|
||||
msg.attach(MIMEText(message, 'plain', 'utf-8'))
|
||||
|
||||
# 发送邮件
|
||||
server = smtplib.SMTP(smtp_server, smtp_port)
|
||||
server.starttls()
|
||||
server.login(email_user, email_password)
|
||||
server.send_message(msg)
|
||||
server.quit()
|
||||
|
||||
logger.info(f"邮件通知发送成功: {recipient_email}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送邮件通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def _send_webhook_notification(self, config_data: dict, message: str):
|
||||
"""发送Webhook通知"""
|
||||
try:
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
# 解析配置
|
||||
webhook_url = config_data.get('webhook_url', '')
|
||||
http_method = config_data.get('http_method', 'POST').upper()
|
||||
headers_str = config_data.get('headers', '{}')
|
||||
|
||||
if not webhook_url:
|
||||
logger.warning("Webhook通知配置为空")
|
||||
return
|
||||
|
||||
# 解析自定义请求头
|
||||
try:
|
||||
custom_headers = json.loads(headers_str) if headers_str else {}
|
||||
except json.JSONDecodeError:
|
||||
custom_headers = {}
|
||||
|
||||
# 设置默认请求头
|
||||
headers = {'Content-Type': 'application/json'}
|
||||
headers.update(custom_headers)
|
||||
|
||||
# 构建请求数据
|
||||
data = {
|
||||
'message': message,
|
||||
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
'source': 'xianyu-auto-reply'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
if http_method == 'POST':
|
||||
async with session.post(webhook_url, json=data, headers=headers, timeout=10) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"Webhook通知发送成功")
|
||||
else:
|
||||
logger.warning(f"Webhook通知发送失败: {response.status}")
|
||||
elif http_method == 'PUT':
|
||||
async with session.put(webhook_url, json=data, headers=headers, timeout=10) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"Webhook通知发送成功")
|
||||
else:
|
||||
logger.warning(f"Webhook通知发送失败: {response.status}")
|
||||
else:
|
||||
logger.warning(f"不支持的HTTP方法: {http_method}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送Webhook通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def _send_wechat_notification(self, config_data: dict, message: str):
|
||||
"""发送微信通知"""
|
||||
try:
|
||||
import aiohttp
|
||||
import json
|
||||
|
||||
# 解析配置
|
||||
webhook_url = config_data.get('webhook_url', '')
|
||||
|
||||
if not webhook_url:
|
||||
logger.warning("微信通知配置为空")
|
||||
return
|
||||
|
||||
data = {
|
||||
"msgtype": "text",
|
||||
"text": {
|
||||
"content": message
|
||||
}
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(webhook_url, json=data, timeout=10) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"微信通知发送成功")
|
||||
else:
|
||||
logger.warning(f"微信通知发送失败: {response.status}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送微信通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def _send_telegram_notification(self, config_data: dict, message: str):
|
||||
"""发送Telegram通知"""
|
||||
try:
|
||||
import aiohttp
|
||||
|
||||
# 解析配置
|
||||
bot_token = config_data.get('bot_token', '')
|
||||
chat_id = config_data.get('chat_id', '')
|
||||
|
||||
if not all([bot_token, chat_id]):
|
||||
logger.warning("Telegram通知配置不完整")
|
||||
return
|
||||
|
||||
# 构建API URL
|
||||
api_url = f"https://api.telegram.org/bot{bot_token}/sendMessage"
|
||||
|
||||
data = {
|
||||
'chat_id': chat_id,
|
||||
'text': message,
|
||||
'parse_mode': 'HTML'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.post(api_url, json=data, timeout=10) as response:
|
||||
if response.status == 200:
|
||||
logger.info(f"Telegram通知发送成功")
|
||||
else:
|
||||
logger.warning(f"Telegram通知发送失败: {response.status}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送Telegram通知异常: {self._safe_str(e)}")
|
||||
|
||||
async def send_token_refresh_notification(self, error_message: str, notification_type: str = "token_refresh"):
|
||||
"""发送Token刷新异常通知(带防重复机制)"""
|
||||
try:
|
||||
@ -1036,11 +1229,30 @@ class XianyuLive:
|
||||
channel_config = notification.get('channel_config')
|
||||
|
||||
try:
|
||||
if channel_type == 'qq':
|
||||
await self._send_qq_notification(channel_config, notification_msg)
|
||||
notification_sent = True
|
||||
else:
|
||||
logger.warning(f"不支持的通知渠道类型: {channel_type}")
|
||||
# 解析配置数据
|
||||
config_data = self._parse_notification_config(channel_config)
|
||||
|
||||
match channel_type:
|
||||
case 'qq':
|
||||
await self._send_qq_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case 'ding_talk' | 'dingtalk':
|
||||
await self._send_dingtalk_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case 'email':
|
||||
await self._send_email_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case 'webhook':
|
||||
await self._send_webhook_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case 'wechat':
|
||||
await self._send_wechat_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case 'telegram':
|
||||
await self._send_telegram_notification(config_data, notification_msg)
|
||||
notification_sent = True
|
||||
case _:
|
||||
logger.warning(f"不支持的通知渠道类型: {channel_type}")
|
||||
|
||||
except Exception as notify_error:
|
||||
logger.error(f"发送Token刷新通知失败 ({notification.get('channel_name', 'Unknown')}): {self._safe_str(notify_error)}")
|
||||
@ -1106,9 +1318,34 @@ class XianyuLive:
|
||||
channel_type = notification.get('channel_type', 'qq')
|
||||
channel_config = notification.get('channel_config', '')
|
||||
|
||||
if channel_type == 'qq':
|
||||
await self._send_qq_notification(channel_config, notification_message)
|
||||
logger.info(f"已发送自动发货通知到QQ: {channel_config}")
|
||||
try:
|
||||
# 解析配置数据
|
||||
config_data = self._parse_notification_config(channel_config)
|
||||
|
||||
match channel_type:
|
||||
case 'qq':
|
||||
await self._send_qq_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到QQ")
|
||||
case 'ding_talk' | 'dingtalk':
|
||||
await self._send_dingtalk_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到钉钉")
|
||||
case 'email':
|
||||
await self._send_email_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到邮箱")
|
||||
case 'webhook':
|
||||
await self._send_webhook_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到Webhook")
|
||||
case 'wechat':
|
||||
await self._send_wechat_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到微信")
|
||||
case 'telegram':
|
||||
await self._send_telegram_notification(config_data, notification_message)
|
||||
logger.info(f"已发送自动发货通知到Telegram")
|
||||
case _:
|
||||
logger.warning(f"不支持的通知渠道类型: {channel_type}")
|
||||
|
||||
except Exception as notify_error:
|
||||
logger.error(f"发送自动发货通知失败: {self._safe_str(notify_error)}")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"发送自动发货通知异常: {self._safe_str(e)}")
|
||||
|
@ -371,6 +371,13 @@ class DBManager:
|
||||
self.set_system_setting("db_version", "1.1", "数据库版本号")
|
||||
logger.info("数据库升级到版本1.1完成")
|
||||
|
||||
# 升级到版本1.2 - 支持更多通知渠道类型
|
||||
if current_version < "1.2":
|
||||
logger.info("开始升级数据库到版本1.2...")
|
||||
self.upgrade_notification_channels_types(cursor)
|
||||
self.set_system_setting("db_version", "1.2", "数据库版本号")
|
||||
logger.info("数据库升级到版本1.2完成")
|
||||
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"数据库版本检查或升级失败: {e}")
|
||||
@ -527,6 +534,70 @@ class DBManager:
|
||||
except Exception as e:
|
||||
logger.error(f"升级notification_channels表失败: {e}")
|
||||
raise
|
||||
|
||||
def upgrade_notification_channels_types(self, cursor):
|
||||
"""升级notification_channels表支持更多渠道类型"""
|
||||
try:
|
||||
logger.info("开始升级notification_channels表支持更多渠道类型...")
|
||||
|
||||
# 检查表是否存在
|
||||
cursor.execute("SELECT name FROM sqlite_master WHERE type='table' AND name='notification_channels'")
|
||||
if not cursor.fetchone():
|
||||
logger.info("notification_channels表不存在,无需升级")
|
||||
return True
|
||||
|
||||
# 检查表中是否有数据
|
||||
cursor.execute("SELECT COUNT(*) FROM notification_channels")
|
||||
count = cursor.fetchone()[0]
|
||||
|
||||
# 获取现有数据
|
||||
existing_data = []
|
||||
if count > 0:
|
||||
cursor.execute("SELECT * FROM notification_channels")
|
||||
existing_data = cursor.fetchall()
|
||||
logger.info(f"备份 {count} 条通知渠道数据")
|
||||
|
||||
# 创建新表,支持更多渠道类型
|
||||
cursor.execute('''
|
||||
CREATE TABLE notification_channels_new (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
type TEXT NOT NULL CHECK (type IN ('qq','ding_talk','dingtalk','email','webhook','wechat','telegram')),
|
||||
config TEXT NOT NULL,
|
||||
enabled BOOLEAN DEFAULT TRUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
''')
|
||||
|
||||
# 复制数据,同时处理类型映射
|
||||
if existing_data:
|
||||
logger.info(f"迁移 {len(existing_data)} 条通知渠道数据到新表")
|
||||
for row in existing_data:
|
||||
# 处理类型映射:ding_talk -> dingtalk
|
||||
channel_type = row[3] # type字段
|
||||
if channel_type == 'ding_talk':
|
||||
channel_type = 'dingtalk'
|
||||
|
||||
# 插入到新表
|
||||
cursor.execute('''
|
||||
INSERT INTO notification_channels_new
|
||||
(id, name, user_id, type, config, enabled, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
''', (row[0], row[1], row[2], channel_type, row[4], row[5], row[6], row[7]))
|
||||
|
||||
# 删除旧表
|
||||
cursor.execute("DROP TABLE notification_channels")
|
||||
|
||||
# 重命名新表
|
||||
cursor.execute("ALTER TABLE notification_channels_new RENAME TO notification_channels")
|
||||
|
||||
logger.info("notification_channels表类型升级完成")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"升级notification_channels表类型失败: {e}")
|
||||
raise
|
||||
|
||||
def _migrate_keywords_table_constraints(self, cursor):
|
||||
"""迁移keywords表的约束,支持基于商品ID的唯一性校验"""
|
||||
|
148
requirements.txt
148
requirements.txt
@ -2,68 +2,174 @@
|
||||
# 闲鱼自动回复系统 - Python依赖包
|
||||
# ================================
|
||||
|
||||
# 核心Web框架
|
||||
# ==================== 核心Web框架 ====================
|
||||
fastapi>=0.111.0
|
||||
uvicorn[standard]>=0.29.0
|
||||
pydantic>=2.7.0
|
||||
|
||||
# 日志记录
|
||||
# ==================== 日志记录 ====================
|
||||
loguru>=0.7.0
|
||||
|
||||
# 网络通信
|
||||
# ==================== 网络通信 ====================
|
||||
websockets>=10.0,<13.0
|
||||
aiohttp>=3.9.0
|
||||
requests>=2.31.0
|
||||
httpx>=0.25.0
|
||||
|
||||
# 配置文件处理
|
||||
# ==================== 配置文件处理 ====================
|
||||
PyYAML>=6.0.0
|
||||
python-dotenv>=1.0.1
|
||||
|
||||
# JavaScript执行引擎
|
||||
# ==================== JavaScript执行引擎 ====================
|
||||
PyExecJS>=1.5.1
|
||||
|
||||
# 协议缓冲区解析
|
||||
# ==================== 协议缓冲区解析 ====================
|
||||
blackboxprotobuf>=1.0.1
|
||||
|
||||
# 系统监控
|
||||
# ==================== 系统监控 ====================
|
||||
psutil>=5.9.0
|
||||
|
||||
# 文件上传支持
|
||||
# ==================== 文件上传支持 ====================
|
||||
python-multipart>=0.0.6
|
||||
|
||||
# AI回复引擎
|
||||
# ==================== AI回复引擎 ====================
|
||||
openai>=1.65.5
|
||||
|
||||
# 图像处理(验证码生成、二维码生成)
|
||||
# ==================== 图像处理 ====================
|
||||
# 验证码生成、二维码生成
|
||||
Pillow>=10.0.0
|
||||
qrcode[pil]>=7.4.2
|
||||
|
||||
# 浏览器自动化(商品搜索、订单详情获取)
|
||||
# ==================== 浏览器自动化 ====================
|
||||
# 商品搜索、订单详情获取
|
||||
playwright>=1.40.0
|
||||
|
||||
# 加密和安全
|
||||
# ==================== 加密和安全 ====================
|
||||
PyJWT>=2.8.0
|
||||
passlib[bcrypt]>=1.7.4
|
||||
cryptography>=41.0.0
|
||||
|
||||
# 时间处理
|
||||
# ==================== 时间处理 ====================
|
||||
python-dateutil>=2.8.2
|
||||
|
||||
# 正则表达式增强
|
||||
# ==================== 正则表达式增强 ====================
|
||||
regex>=2023.10.3
|
||||
|
||||
# Excel文件处理(数据导入导出)
|
||||
# ==================== Excel文件处理 ====================
|
||||
# 数据导入导出功能
|
||||
pandas>=2.0.0
|
||||
openpyxl>=3.1.0
|
||||
|
||||
# 邮件发送(用户注册验证)
|
||||
# ==================== 邮件发送 ====================
|
||||
# 用户注册验证
|
||||
email-validator>=2.0.0
|
||||
|
||||
# 其他工具库
|
||||
# ==================== 其他工具库 ====================
|
||||
typing-extensions>=4.7.0
|
||||
|
||||
# 注意:
|
||||
# - sqlite3 是Python内置模块,无需安装
|
||||
# - smtplib 是Python内置模块,无需安装
|
||||
# - email 是Python内置模块,无需安装
|
||||
# ==================== 说明 ====================
|
||||
# 以下模块是Python内置模块,无需安装:
|
||||
# - sqlite3 (数据库)
|
||||
# - smtplib (邮件发送)
|
||||
# - email (邮件处理)
|
||||
# - json (JSON处理)
|
||||
# - base64 (编码解码)
|
||||
# - hashlib (哈希算法)
|
||||
# - hmac (消息认证码)
|
||||
# - time (时间处理)
|
||||
# - datetime (日期时间)
|
||||
# - os (操作系统接口)
|
||||
# - sys (系统相关)
|
||||
# - re (正则表达式)
|
||||
# - urllib (URL处理)
|
||||
# - asyncio (异步编程)
|
||||
# - threading (多线程)
|
||||
# - multiprocessing (多进程)
|
||||
# - pathlib (路径处理)
|
||||
# - uuid (UUID生成)
|
||||
# - random (随机数)
|
||||
# - secrets (安全随机数)
|
||||
# - traceback (异常追踪)
|
||||
# - logging (日志记录)
|
||||
# - collections (集合类型)
|
||||
# - itertools (迭代工具)
|
||||
# - functools (函数工具)
|
||||
# - operator (操作符函数)
|
||||
# - copy (对象复制)
|
||||
# - pickle (对象序列化)
|
||||
# - gzip (压缩)
|
||||
# - zipfile (ZIP文件)
|
||||
# - tarfile (TAR文件)
|
||||
# - shutil (文件操作)
|
||||
# - tempfile (临时文件)
|
||||
# - io (输入输出)
|
||||
# - csv (CSV文件)
|
||||
# - xml (XML处理)
|
||||
# - html (HTML处理)
|
||||
# - http (HTTP客户端/服务器)
|
||||
# - socket (网络编程)
|
||||
# - ssl (SSL/TLS)
|
||||
# - ftplib (FTP客户端)
|
||||
# - poplib (POP3客户端)
|
||||
# - imaplib (IMAP客户端)
|
||||
# - telnetlib (Telnet客户端)
|
||||
# - subprocess (子进程)
|
||||
# - signal (信号处理)
|
||||
# - atexit (退出处理)
|
||||
# - weakref (弱引用)
|
||||
# - gc (垃圾回收)
|
||||
# - inspect (对象检查)
|
||||
# - ast (抽象语法树)
|
||||
# - dis (字节码反汇编)
|
||||
# - keyword (关键字)
|
||||
# - token (令牌)
|
||||
# - tokenize (词法分析)
|
||||
# - parser (语法分析)
|
||||
# - symbol (符号)
|
||||
# - code (代码对象)
|
||||
# - codeop (代码编译)
|
||||
# - py_compile (Python编译)
|
||||
# - compileall (批量编译)
|
||||
# - importlib (导入机制)
|
||||
# - pkgutil (包工具)
|
||||
# - modulefinder (模块查找)
|
||||
# - runpy (运行Python模块)
|
||||
# - argparse (命令行参数)
|
||||
# - getopt (命令行选项)
|
||||
# - optparse (选项解析)
|
||||
# - configparser (配置文件)
|
||||
# - fileinput (文件输入)
|
||||
# - linecache (行缓存)
|
||||
# - glob (文件名模式匹配)
|
||||
# - fnmatch (文件名匹配)
|
||||
# - difflib (差异比较)
|
||||
# - textwrap (文本包装)
|
||||
# - string (字符串)
|
||||
# - struct (二进制数据)
|
||||
# - codecs (编解码器)
|
||||
# - unicodedata (Unicode数据)
|
||||
# - stringprep (字符串预处理)
|
||||
# - readline (行编辑)
|
||||
# - rlcompleter (自动补全)
|
||||
# - pprint (美化打印)
|
||||
# - reprlib (repr替代)
|
||||
# - enum (枚举)
|
||||
# - numbers (数字抽象基类)
|
||||
# - math (数学函数)
|
||||
# - cmath (复数数学)
|
||||
# - decimal (十进制浮点)
|
||||
# - fractions (分数)
|
||||
# - statistics (统计函数)
|
||||
# - array (数组)
|
||||
# - bisect (二分查找)
|
||||
# - heapq (堆队列)
|
||||
# - queue (队列)
|
||||
# - types (动态类型)
|
||||
# - contextlib (上下文管理)
|
||||
# - abc (抽象基类)
|
||||
# - atexit (退出处理)
|
||||
# - traceback (异常追踪)
|
||||
# - __future__ (未来特性)
|
||||
# - warnings (警告)
|
||||
# - dataclasses (数据类)
|
||||
# - typing (类型提示)
|
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user