From 5e5fee9d5b8beed6d87574990ae6820b8f12d75e Mon Sep 17 00:00:00 2001
From: zhinianboke <115088296+zhinianboke@users.noreply.github.com>
Date: Mon, 11 Aug 2025 22:48:28 +0800
Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E7=BB=93=E6=9E=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.env | 243 ------
.gitignore | 3 -
README.md | 3 +-
XianyuAutoAsync.py | 92 ++-
db_manager.py | 17 +
docker-compose-cn.yml | 4 +-
docker-compose.yml | 4 +-
docker-deploy.bat | 17 +-
docker-deploy.sh | 16 +-
reply_server.py | 125 +++-
requirements.txt | 1 -
secure_confirm_decrypted.py | 6 +-
secure_freeshipping_decrypted.py | 6 +-
static/css/admin.css | 241 ++++++
static/css/app.css | 1 +
static/css/logs.css | 70 ++
static/index.html | 527 +++++++++++--
static/item_search.html | 123 +++-
static/js/app.js | 1181 +++++++++++++++++++++++++++++-
static/log_management.html | 44 +-
utils/item_search.py | 374 ++++++++--
utils/order_detail_fetcher.py | 16 +-
22 files changed, 2634 insertions(+), 480 deletions(-)
delete mode 100644 .env
create mode 100644 static/css/admin.css
diff --git a/.env b/.env
deleted file mode 100644
index 16a2402..0000000
--- a/.env
+++ /dev/null
@@ -1,243 +0,0 @@
-# 闲鱼自动回复系统 Docker 环境变量配置文件
-# 复制此文件为 .env 并根据需要修改配置
-# 或者直接重命名为 .env 使用默认配置
-
-# ================================
-# 基础配置
-# ================================
-
-# 时区设置
-TZ=Asia/Shanghai
-
-# Python配置
-PYTHONUNBUFFERED=1
-PYTHONDONTWRITEBYTECODE=1
-
-# 日志级别 (DEBUG, INFO, WARNING, ERROR)
-LOG_LEVEL=INFO
-
-# ================================
-# 数据库配置
-# ================================
-
-# 数据库文件路径
-DB_PATH=/app/data/xianyu_data.db
-
-# ================================
-# 服务配置
-# ================================
-
-# Web服务端口
-WEB_PORT=8080
-
-# API服务配置
-API_HOST=0.0.0.0
-API_PORT=8080
-
-# ================================
-# 安全配置
-# ================================
-
-# 管理员账号密码 (建议修改)
-ADMIN_USERNAME=admin
-ADMIN_PASSWORD=admin123
-
-# JWT密钥 (建议修改为随机字符串)
-JWT_SECRET_KEY=your-secret-key-here
-
-# Session超时时间 (秒)
-SESSION_TIMEOUT=3600
-
-# ================================
-# 多用户系统配置
-# ================================
-
-# 多用户功能开关
-MULTIUSER_ENABLED=true
-
-# 用户注册开关
-USER_REGISTRATION_ENABLED=true
-
-# 邮箱验证开关
-EMAIL_VERIFICATION_ENABLED=true
-
-# 图形验证码开关
-CAPTCHA_ENABLED=true
-
-# Token过期时间(秒,默认24小时)
-TOKEN_EXPIRE_TIME=86400
-
-# ================================
-# 闲鱼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
-
-# ==================== AI回复配置 ====================
-# AI回复功能总开关 (true/false)
-AI_REPLY_ENABLED=false
-
-# 默认AI模型
-DEFAULT_AI_MODEL=qwen-plus
-
-# 默认AI API地址
-DEFAULT_AI_BASE_URL=https://dashscope.aliyuncs.com/compatible-mode/v1
-
-# AI请求超时时间 (秒)
-AI_REQUEST_TIMEOUT=30
-
-# AI最大生成token数
-AI_MAX_TOKENS=100
-
-# ================================
-# 自动回复配置
-# ================================
-
-# 是否启用自动回复
-AUTO_REPLY_ENABLED=true
-
-# 默认回复消息
-AUTO_REPLY_DEFAULT_MESSAGE=亲爱的"{send_user_name}" 老板你好!所有宝贝都可以拍,秒发货的哈~不满意的话可以直接申请退款哈~
-
-# 最大重试次数
-AUTO_REPLY_MAX_RETRY=3
-
-# 重试间隔 (秒)
-AUTO_REPLY_RETRY_INTERVAL=5
-
-# ================================
-# 自动发货配置
-# ================================
-
-# 是否启用自动发货
-AUTO_DELIVERY_ENABLED=true
-
-# 自动发货超时时间 (秒)
-AUTO_DELIVERY_TIMEOUT=30
-
-# API卡券请求超时时间 (秒)
-API_CARD_TIMEOUT=10
-
-# 批量数据并发保护
-BATCH_DATA_LOCK_TIMEOUT=5
-
-# ================================
-# 代理配置 (可选)
-# ================================
-
-# HTTP代理
-# HTTP_PROXY=http://proxy.example.com:8080
-
-# HTTPS代理
-# HTTPS_PROXY=https://proxy.example.com:8080
-
-# 不使用代理的地址
-# NO_PROXY=localhost,127.0.0.1
-
-# ================================
-# 监控配置
-# ================================
-
-# 健康检查间隔 (秒)
-HEALTH_CHECK_INTERVAL=30
-
-# 健康检查超时 (秒)
-HEALTH_CHECK_TIMEOUT=10
-
-# ================================
-# 资源限制
-# ================================
-
-# 内存限制 (MB)
-MEMORY_LIMIT=512
-
-# CPU限制 (核心数)
-CPU_LIMIT=0.5
-
-# 内存预留 (MB)
-MEMORY_RESERVATION=256
-
-# CPU预留 (核心数)
-CPU_RESERVATION=0.25
-
-# ================================
-# SQL日志配置
-# ================================
-
-# 是否启用SQL日志
-SQL_LOG_ENABLED=true
-
-# SQL日志级别
-SQL_LOG_LEVEL=INFO
-
-# ================================
-# 备份配置
-# ================================
-
-# 自动备份间隔 (小时)
-BACKUP_INTERVAL=24
-
-# 备份保留天数
-BACKUP_RETENTION_DAYS=7
-
-# 备份目录
-BACKUP_DIR=/app/backups
-
-# ================================
-# 开发配置
-# ================================
-
-# 开发模式 (true/false)
-DEBUG=false
-
-# 热重载 (true/false)
-RELOAD=false
-
-# API文档 (true/false)
-ENABLE_DOCS=true
-
-# ================================
-# 第三方服务配置
-# ================================
-
-# 邮件通知配置 (可选)
-# SMTP_HOST=smtp.example.com
-# SMTP_PORT=587
-# SMTP_USERNAME=your-email@example.com
-# SMTP_PASSWORD=your-password
-# SMTP_FROM=noreply@example.com
-
-# 钉钉机器人通知 (可选)
-# DINGTALK_WEBHOOK=https://oapi.dingtalk.com/robot/send?access_token=your-token
-
-# 企业微信机器人通知 (可选)
-# WECHAT_WEBHOOK=https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=your-key
-
-# ================================
-# 自定义配置
-# ================================
-
-# 自定义配置文件路径
-# CUSTOM_CONFIG_PATH=/app/custom_config.yml
-
-# 插件目录
-# PLUGINS_DIR=/app/plugins
-
-# 模板目录
-# TEMPLATES_DIR=/app/templates
diff --git a/.gitignore b/.gitignore
index afae4e4..4ae8ce0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -193,9 +193,6 @@ dmypy.json
.pyre/
# ==================== 项目特定新增 ====================
-# 环境变量文件
-.env.example
-.env.*.example
# 数据库文件
*.db-journal
diff --git a/README.md b/README.md
index 3b2371e..727e598 100644
--- a/README.md
+++ b/README.md
@@ -409,7 +409,6 @@ python Start.py
- **`docker-deploy.bat`** - Windows版本部署脚本,支持Windows环境一键部署
- **`entrypoint.sh`** - Docker容器启动脚本,处理环境初始化和服务启动
- **`nginx/nginx.conf`** - Nginx反向代理配置,支持负载均衡和SSL终端
-- **`.env`** - 环境变量配置文件,包含所有可配置的系统参数和敏感信息
- **`requirements.txt`** - Python依赖包列表,精简版本无冗余依赖,按功能分类组织
## ⚙️ 配置说明
@@ -517,7 +516,7 @@ python Start.py
## ❓ 常见问题
### 1. 端口被占用
-如果8080端口被占用,可以修改 `.env` 文件中的 `WEB_PORT` 配置。
+如果8080端口被占用,可以修改 `global_config.yml` 文件中的 `AUTO_REPLY.api.port` 配置,或者在 Docker 启动时通过环境变量 `WEB_PORT` 指定端口。
### 2. 数据库连接失败
检查数据库文件权限,确保应用有读写权限。
diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py
index fd60754..6de35e5 100644
--- a/XianyuAutoAsync.py
+++ b/XianyuAutoAsync.py
@@ -457,6 +457,19 @@ class XianyuLive:
item_id: str, chat_id: str, msg_time: str):
"""统一处理自动发货逻辑"""
try:
+ # 检查商品是否属于当前cookies
+ if item_id and item_id != "未知商品":
+ try:
+ from db_manager import db_manager
+ item_info = db_manager.get_item_info(self.cookie_id, item_id)
+ if not item_info:
+ logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 商品 {item_id} 不属于当前账号,跳过自动发货')
+ return
+ logger.debug(f'[{msg_time}] 【{self.cookie_id}】✅ 商品 {item_id} 归属验证通过')
+ except Exception as e:
+ logger.error(f'[{msg_time}] 【{self.cookie_id}】检查商品归属失败: {self._safe_str(e)},跳过自动发货')
+ return
+
# 提取订单ID
order_id = self._extract_order_id(message)
@@ -2183,23 +2196,28 @@ class XianyuLive:
# 插入或更新订单信息到数据库
try:
- success = db_manager.insert_or_update_order(
- order_id=order_id,
- item_id=item_id,
- buyer_id=buyer_id,
- spec_name=spec_name,
- spec_value=spec_value,
- quantity=quantity,
- amount=amount,
- order_status='processed', # 已处理状态
- cookie_id=self.cookie_id
- )
-
- if success:
- logger.info(f"【{self.cookie_id}】订单信息已保存到数据库: {order_id}")
- print(f"💾 【{self.cookie_id}】订单 {order_id} 信息已保存到数据库")
+ # 检查cookie_id是否在cookies表中存在
+ cookie_info = db_manager.get_cookie_by_id(self.cookie_id)
+ if not cookie_info:
+ logger.warning(f"Cookie ID {self.cookie_id} 不存在于cookies表中,丢弃订单 {order_id}")
else:
- logger.warning(f"【{self.cookie_id}】订单信息保存失败: {order_id}")
+ success = db_manager.insert_or_update_order(
+ order_id=order_id,
+ item_id=item_id,
+ buyer_id=buyer_id,
+ spec_name=spec_name,
+ spec_value=spec_value,
+ quantity=quantity,
+ amount=amount,
+ order_status='processed', # 已处理状态
+ cookie_id=self.cookie_id
+ )
+
+ if success:
+ logger.info(f"【{self.cookie_id}】订单信息已保存到数据库: {order_id}")
+ print(f"💾 【{self.cookie_id}】订单 {order_id} 信息已保存到数据库")
+ else:
+ logger.warning(f"【{self.cookie_id}】订单信息保存失败: {order_id}")
except Exception as db_e:
logger.error(f"【{self.cookie_id}】保存订单信息到数据库失败: {self._safe_str(db_e)}")
@@ -2438,17 +2456,23 @@ class XianyuLive:
# 保存订单基本信息到数据库(如果还没有详细信息)
try:
from db_manager import db_manager
- existing_order = db_manager.get_order_by_id(order_id)
- if not existing_order:
- # 插入基本订单信息
- db_manager.insert_or_update_order(
- order_id=order_id,
- item_id=item_id,
- buyer_id=send_user_id,
- order_status='processing', # 处理中状态
- cookie_id=self.cookie_id
- )
- logger.info(f"保存基本订单信息到数据库: {order_id}")
+
+ # 检查cookie_id是否在cookies表中存在
+ cookie_info = db_manager.get_cookie_by_id(self.cookie_id)
+ if not cookie_info:
+ logger.warning(f"Cookie ID {self.cookie_id} 不存在于cookies表中,丢弃订单 {order_id}")
+ else:
+ existing_order = db_manager.get_order_by_id(order_id)
+ if not existing_order:
+ # 插入基本订单信息
+ db_manager.insert_or_update_order(
+ order_id=order_id,
+ item_id=item_id,
+ buyer_id=send_user_id,
+ order_status='processing', # 处理中状态
+ cookie_id=self.cookie_id
+ )
+ logger.info(f"保存基本订单信息到数据库: {order_id}")
except Exception as db_e:
logger.error(f"保存基本订单信息失败: {self._safe_str(db_e)}")
@@ -3346,6 +3370,20 @@ class XianyuLive:
# 检查是否为"我已小刀,待刀成"
if card_title == "我已小刀,待刀成":
logger.info(f'[{msg_time}] 【{self.cookie_id}】【系统】检测到"我已小刀,待刀成",即使在暂停期间也继续处理')
+
+ # 检查商品是否属于当前cookies
+ if item_id and item_id != "未知商品":
+ try:
+ from db_manager import db_manager
+ item_info = db_manager.get_item_info(self.cookie_id, item_id)
+ if not item_info:
+ logger.warning(f'[{msg_time}] 【{self.cookie_id}】❌ 商品 {item_id} 不属于当前账号,跳过免拼发货')
+ return
+ logger.debug(f'[{msg_time}] 【{self.cookie_id}】✅ 商品 {item_id} 归属验证通过')
+ except Exception as e:
+ logger.error(f'[{msg_time}] 【{self.cookie_id}】检查商品归属失败: {self._safe_str(e)},跳过免拼发货')
+ return
+
# 提取订单ID
order_id = self._extract_order_id(message)
if order_id:
diff --git a/db_manager.py b/db_manager.py
index 593624b..5e69a34 100644
--- a/db_manager.py
+++ b/db_manager.py
@@ -1120,6 +1120,8 @@ class DBManager:
logger.error(f"获取所有Cookie失败: {e}")
return {}
+
+
def get_cookie_by_id(self, cookie_id: str) -> Optional[Dict[str, str]]:
"""根据ID获取Cookie信息
@@ -4055,6 +4057,14 @@ class DBManager:
try:
cursor = self.conn.cursor()
+ # 检查cookie_id是否在cookies表中存在(如果提供了cookie_id)
+ if cookie_id:
+ cursor.execute("SELECT id FROM cookies WHERE id = ?", (cookie_id,))
+ cookie_exists = cursor.fetchone()
+ if not cookie_exists:
+ logger.warning(f"Cookie ID {cookie_id} 不存在于cookies表中,拒绝插入订单 {order_id}")
+ return False
+
# 检查订单是否已存在
cursor.execute("SELECT order_id FROM orders WHERE order_id = ?", (order_id,))
existing = cursor.fetchone()
@@ -4189,14 +4199,21 @@ class DBManager:
primary_key_map = {
'users': 'id',
'cookies': 'id',
+ 'cookie_status': 'id',
'keywords': 'id',
'default_replies': 'id',
+ 'default_reply_records': 'id',
+ 'item_replay': 'item_id',
'ai_reply_settings': 'id',
+ 'ai_conversations': 'id',
+ 'ai_item_cache': 'id',
+ 'item_info': 'id',
'message_notifications': 'id',
'cards': 'id',
'delivery_rules': 'id',
'notification_channels': 'id',
'user_settings': 'id',
+ 'system_settings': 'id',
'email_verifications': 'id',
'captcha_codes': 'id',
'orders': 'order_id'
diff --git a/docker-compose-cn.yml b/docker-compose-cn.yml
index b20559a..9d46c6d 100644
--- a/docker-compose-cn.yml
+++ b/docker-compose-cn.yml
@@ -57,9 +57,7 @@ services:
- TOKEN_REFRESH_INTERVAL=${TOKEN_REFRESH_INTERVAL:-3600}
- TOKEN_RETRY_INTERVAL=${TOKEN_RETRY_INTERVAL:-300}
- MESSAGE_EXPIRE_TIME=${MESSAGE_EXPIRE_TIME:-300000}
- env_file:
- - path: .env
- required: false
+
networks:
- xianyu-network
healthcheck:
diff --git a/docker-compose.yml b/docker-compose.yml
index 64d4b60..f45795c 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -57,9 +57,7 @@ services:
- TOKEN_REFRESH_INTERVAL=${TOKEN_REFRESH_INTERVAL:-3600}
- TOKEN_RETRY_INTERVAL=${TOKEN_RETRY_INTERVAL:-300}
- MESSAGE_EXPIRE_TIME=${MESSAGE_EXPIRE_TIME:-300000}
- env_file:
- - path: .env
- required: false
+
networks:
- xianyu-network
healthcheck:
diff --git a/docker-deploy.bat b/docker-deploy.bat
index 0043a51..961af36 100644
--- a/docker-deploy.bat
+++ b/docker-deploy.bat
@@ -10,7 +10,6 @@ title 闲鱼自动回复系统 Docker 部署
REM 项目配置
set PROJECT_NAME=xianyu-auto-reply
set COMPOSE_FILE=docker-compose.yml
-set ENV_FILE=.env
REM 颜色定义(Windows CMD不支持ANSI颜色,使用echo代替)
set "INFO_PREFIX=[INFO]"
@@ -41,13 +40,6 @@ echo %SUCCESS_PREFIX% 系统依赖检查通过
REM 初始化配置
echo %INFO_PREFIX% 初始化配置文件...
-if not exist "%ENV_FILE%" (
- echo %WARNING_PREFIX% %ENV_FILE% 文件不存在,将使用默认配置
- echo %INFO_PREFIX% 如需自定义配置,请创建 %ENV_FILE% 文件
-) else (
- echo %SUCCESS_PREFIX% %ENV_FILE% 配置文件已存在
-)
-
REM 检查关键文件
if not exist "entrypoint.sh" (
echo %ERROR_PREFIX% entrypoint.sh 文件不存在,Docker容器将无法启动
@@ -58,6 +50,15 @@ if not exist "entrypoint.sh" (
echo %SUCCESS_PREFIX% entrypoint.sh 文件已存在
)
+if not exist "global_config.yml" (
+ echo %ERROR_PREFIX% global_config.yml 配置文件不存在
+ echo %INFO_PREFIX% 请确保配置文件存在
+ pause
+ exit /b 1
+) else (
+ echo %SUCCESS_PREFIX% global_config.yml 配置文件已存在
+)
+
REM 创建必要的目录
if not exist "data" mkdir data
if not exist "logs" mkdir logs
diff --git a/docker-deploy.sh b/docker-deploy.sh
index b1e2426..7c45749 100644
--- a/docker-deploy.sh
+++ b/docker-deploy.sh
@@ -15,7 +15,6 @@ NC='\033[0m' # No Color
# 项目配置
PROJECT_NAME="xianyu-auto-reply"
COMPOSE_FILE="docker-compose.yml"
-ENV_FILE=".env"
# 打印带颜色的消息
print_info() {
@@ -55,13 +54,6 @@ check_dependencies() {
init_config() {
print_info "初始化配置文件..."
- if [ ! -f "$ENV_FILE" ]; then
- print_warning "$ENV_FILE 文件不存在,将使用默认配置"
- print_info "如需自定义配置,请创建 $ENV_FILE 文件"
- else
- print_success "$ENV_FILE 配置文件已存在"
- fi
-
# 检查关键文件
if [ ! -f "entrypoint.sh" ]; then
print_error "entrypoint.sh 文件不存在,Docker容器将无法启动"
@@ -71,6 +63,14 @@ init_config() {
print_success "entrypoint.sh 文件已存在"
fi
+ if [ ! -f "global_config.yml" ]; then
+ print_error "global_config.yml 配置文件不存在"
+ print_info "请确保配置文件存在"
+ exit 1
+ else
+ print_success "global_config.yml 配置文件已存在"
+ fi
+
# 创建必要的目录
mkdir -p data logs backups static/uploads/images
print_success "已创建必要的目录"
diff --git a/reply_server.py b/reply_server.py
index 8895cad..cabc6e7 100644
--- a/reply_server.py
+++ b/reply_server.py
@@ -2742,7 +2742,11 @@ async def search_items(
current_user: Optional[Dict[str, Any]] = Depends(get_current_user_optional)
):
"""搜索闲鱼商品"""
+ user_info = f"【{current_user.get('username', 'unknown')}#{current_user.get('user_id', 'unknown')}】" if current_user else "【未登录】"
+
try:
+ logger.info(f"{user_info} 开始单页搜索: 关键词='{search_request.keyword}', 页码={search_request.page}, 每页={search_request.page_size}")
+
from utils.item_search import search_xianyu_items
# 执行搜索
@@ -2752,18 +2756,84 @@ async def search_items(
page_size=search_request.page_size
)
- return {
+ # 检查是否有错误
+ has_error = result.get("error")
+ items_count = len(result.get("items", []))
+
+ logger.info(f"{user_info} 单页搜索完成: 获取到 {items_count} 条数据" +
+ (f", 错误: {has_error}" if has_error else ""))
+
+ response_data = {
"success": True,
"data": result.get("items", []),
"total": result.get("total", 0),
"page": search_request.page,
"page_size": search_request.page_size,
- "keyword": search_request.keyword
+ "keyword": search_request.keyword,
+ "is_real_data": result.get("is_real_data", False),
+ "source": result.get("source", "unknown")
}
- except Exception as e:
- logger.error(f"商品搜索失败: {str(e)}")
- raise HTTPException(status_code=500, detail=f"商品搜索失败: {str(e)}")
+ # 如果有错误信息,也包含在响应中
+ if has_error:
+ response_data["error"] = has_error
+
+ return response_data
+
+ except Exception as e:
+ error_msg = str(e)
+ logger.error(f"{user_info} 商品搜索失败: {error_msg}")
+ raise HTTPException(status_code=500, detail=f"商品搜索失败: {error_msg}")
+
+
+@app.get("/cookies/check")
+async def check_valid_cookies(
+ current_user: Optional[Dict[str, Any]] = Depends(get_current_user_optional)
+):
+ """检查是否有有效的cookies账户(必须是启用状态)"""
+ try:
+ if cookie_manager.manager is None:
+ return {
+ "success": True,
+ "hasValidCookies": False,
+ "validCount": 0,
+ "enabledCount": 0,
+ "totalCount": 0
+ }
+
+ from db_manager import db_manager
+
+ # 获取所有cookies
+ all_cookies = db_manager.get_all_cookies()
+
+ # 检查启用状态和有效性
+ valid_cookies = []
+ enabled_cookies = []
+
+ for cookie_id, cookie_value in all_cookies.items():
+ # 检查是否启用
+ is_enabled = cookie_manager.manager.get_cookie_status(cookie_id)
+ if is_enabled:
+ enabled_cookies.append(cookie_id)
+ # 检查是否有效(长度大于50)
+ if len(cookie_value) > 50:
+ valid_cookies.append(cookie_id)
+
+ return {
+ "success": True,
+ "hasValidCookies": len(valid_cookies) > 0,
+ "validCount": len(valid_cookies),
+ "enabledCount": len(enabled_cookies),
+ "totalCount": len(all_cookies)
+ }
+
+ except Exception as e:
+ logger.error(f"检查cookies失败: {str(e)}")
+ return {
+ "success": False,
+ "hasValidCookies": False,
+ "error": str(e)
+ }
@app.post("/items/search_multiple")
async def search_multiple_pages(
@@ -2771,7 +2841,11 @@ async def search_multiple_pages(
current_user: Optional[Dict[str, Any]] = Depends(get_current_user_optional)
):
"""搜索多页闲鱼商品"""
+ user_info = f"【{current_user.get('username', 'unknown')}#{current_user.get('user_id', 'unknown')}】" if current_user else "【未登录】"
+
try:
+ logger.info(f"{user_info} 开始多页搜索: 关键词='{search_request.keyword}', 页数={search_request.total_pages}")
+
from utils.item_search import search_multiple_pages_xianyu
# 执行多页搜索
@@ -2780,7 +2854,14 @@ async def search_multiple_pages(
total_pages=search_request.total_pages
)
- return {
+ # 检查是否有错误
+ has_error = result.get("error")
+ items_count = len(result.get("items", []))
+
+ logger.info(f"{user_info} 多页搜索完成: 获取到 {items_count} 条数据" +
+ (f", 错误: {has_error}" if has_error else ""))
+
+ response_data = {
"success": True,
"data": result.get("items", []),
"total": result.get("total", 0),
@@ -2790,9 +2871,17 @@ async def search_multiple_pages(
"is_fallback": result.get("is_fallback", False),
"source": result.get("source", "unknown")
}
+
+ # 如果有错误信息,也包含在响应中
+ if has_error:
+ response_data["error"] = has_error
+
+ return response_data
+
except Exception as e:
- logger.error(f"多页商品搜索失败: {str(e)}")
- raise HTTPException(status_code=500, detail=f"多页商品搜索失败: {str(e)}")
+ error_msg = str(e)
+ logger.error(f"{user_info} 多页商品搜索失败: {error_msg}")
+ raise HTTPException(status_code=500, detail=f"多页商品搜索失败: {error_msg}")
@app.get("/items/detail/{item_id}")
@@ -3373,43 +3462,55 @@ def get_system_logs(admin_user: Dict[str, Any] = Depends(require_admin),
# 查找日志文件
log_files = glob.glob("logs/xianyu_*.log")
+ logger.info(f"找到日志文件: {log_files}")
+
if not log_files:
- return {"logs": [], "message": "未找到日志文件"}
+ logger.warning("未找到日志文件")
+ return {"logs": [], "message": "未找到日志文件", "success": False}
# 获取最新的日志文件
latest_log_file = max(log_files, key=os.path.getctime)
+ logger.info(f"使用最新日志文件: {latest_log_file}")
logs = []
try:
with open(latest_log_file, 'r', encoding='utf-8') as f:
all_lines = f.readlines()
+ logger.info(f"读取到 {len(all_lines)} 行日志")
# 如果指定了日志级别,进行过滤
if level:
filtered_lines = [line for line in all_lines if f"| {level.upper()} |" in line]
+ logger.info(f"按级别 {level} 过滤后剩余 {len(filtered_lines)} 行")
else:
filtered_lines = all_lines
# 获取最后N行
recent_lines = filtered_lines[-lines:] if len(filtered_lines) > lines else filtered_lines
+ logger.info(f"取最后 {len(recent_lines)} 行日志")
for line in recent_lines:
logs.append(line.strip())
except Exception as e:
+ logger.error(f"读取日志文件失败: {str(e)}")
log_with_user('error', f"读取日志文件失败: {str(e)}", admin_user)
- return {"logs": [], "message": f"读取日志文件失败: {str(e)}"}
+ return {"logs": [], "message": f"读取日志文件失败: {str(e)}", "success": False}
log_with_user('info', f"返回日志记录 {len(logs)} 条", admin_user)
+ logger.info(f"成功返回 {len(logs)} 条日志记录")
+
return {
"logs": logs,
"log_file": latest_log_file,
- "total_lines": len(logs)
+ "total_lines": len(logs),
+ "success": True
}
except Exception as e:
+ logger.error(f"获取系统日志失败: {str(e)}")
log_with_user('error', f"获取系统日志失败: {str(e)}", admin_user)
- raise HTTPException(status_code=500, detail=str(e))
+ return {"logs": [], "message": f"获取系统日志失败: {str(e)}", "success": False}
@app.get('/admin/stats')
def get_system_stats(admin_user: Dict[str, Any] = Depends(require_admin)):
diff --git a/requirements.txt b/requirements.txt
index 0317c68..bb7edc8 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -18,7 +18,6 @@ httpx>=0.25.0
# ==================== 配置文件处理 ====================
PyYAML>=6.0.0
-python-dotenv>=1.0.1
# ==================== JavaScript执行引擎 ====================
PyExecJS>=1.5.1
diff --git a/secure_confirm_decrypted.py b/secure_confirm_decrypted.py
index 8d1490e..2641559 100644
--- a/secure_confirm_decrypted.py
+++ b/secure_confirm_decrypted.py
@@ -165,12 +165,8 @@ class SecureConfirm:
error_msg = res_json.get('ret', ['未知错误'])[0] if res_json.get('ret') else '未知错误'
logger.warning(f"【{self.cookie_id}】❌ 自动确认发货失败: {error_msg}")
- # 如果是token相关错误,进行重试
- if 'token' in error_msg.lower() or 'sign' in error_msg.lower():
- logger.info(f"【{self.cookie_id}】检测到token错误,准备重试...")
- return await self.auto_confirm(order_id, item_id, retry_count + 1)
+ return await self.auto_confirm(order_id, item_id, retry_count + 1)
- return {"error": error_msg, "order_id": order_id}
except Exception as e:
logger.error(f"【{self.cookie_id}】自动确认发货API请求异常: {self._safe_str(e)}")
diff --git a/secure_freeshipping_decrypted.py b/secure_freeshipping_decrypted.py
index d8e21f9..84bd1b5 100644
--- a/secure_freeshipping_decrypted.py
+++ b/secure_freeshipping_decrypted.py
@@ -115,12 +115,8 @@ class SecureFreeshipping:
error_msg = res_json.get('ret', ['未知错误'])[0] if res_json.get('ret') else '未知错误'
logger.warning(f"【{self.cookie_id}】❌ 自动免拼发货失败: {error_msg}")
- # 如果是token相关错误,进行重试
- if 'token' in error_msg.lower() or 'sign' in error_msg.lower():
- logger.info(f"【{self.cookie_id}】检测到token错误,准备重试...")
- return await self.auto_freeshipping(order_id, item_id, buyer_id, retry_count + 1)
+ return await self.auto_freeshipping(order_id, item_id, buyer_id, retry_count + 1)
- return {"error": error_msg, "order_id": order_id}
except Exception as e:
logger.error(f"【{self.cookie_id}】自动免拼发货API请求异常: {self._safe_str(e)}")
diff --git a/static/css/admin.css b/static/css/admin.css
new file mode 100644
index 0000000..b41a14f
--- /dev/null
+++ b/static/css/admin.css
@@ -0,0 +1,241 @@
+/* ================================
+ 管理员功能样式 - 用户管理和数据管理
+ ================================ */
+
+/* 用户管理样式 */
+.user-card {
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
+ border: 1px solid var(--border-color);
+ border-radius: 12px;
+}
+
+.user-card:hover {
+ transform: translateY(-2px);
+ box-shadow: var(--shadow-md);
+}
+
+.user-card .card-body {
+ padding: 1.25rem;
+}
+
+.user-card .badge {
+ font-size: 0.75rem;
+ padding: 0.375rem 0.75rem;
+}
+
+/* 数据管理样式 */
+.table-container {
+ max-height: 600px;
+ overflow-y: auto;
+ border: 1px solid var(--border-color);
+ border-radius: 8px;
+}
+
+.table th {
+ position: sticky;
+ top: 0;
+ background: var(--dark-color);
+ color: white;
+ z-index: 10;
+ border: none;
+ font-weight: 600;
+ font-size: 0.875rem;
+ padding: 0.75rem;
+}
+
+.table td {
+ padding: 0.75rem;
+ font-size: 0.875rem;
+ border-bottom: 1px solid var(--border-color);
+ vertical-align: middle;
+}
+
+.table tbody tr:hover {
+ background-color: rgba(79, 70, 229, 0.05);
+}
+
+/* 数据统计卡片 */
+.data-stats {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
+ color: white;
+ border: none;
+}
+
+.data-stats .card-body {
+ padding: 0.75rem;
+}
+
+/* 表格状态样式 */
+#loadingTable,
+#noTableSelected,
+#noTableData {
+ padding: 3rem 1rem;
+}
+
+#loadingTable .spinner-border {
+ width: 3rem;
+ height: 3rem;
+ border-width: 0.3em;
+}
+
+#noTableSelected i,
+#noTableData i {
+ font-size: 4rem;
+ color: var(--secondary-color);
+ opacity: 0.5;
+}
+
+/* 按钮组样式 */
+.btn-group .btn {
+ border-radius: 0;
+}
+
+.btn-group .btn:first-child {
+ border-top-left-radius: 6px;
+ border-bottom-left-radius: 6px;
+}
+
+.btn-group .btn:last-child {
+ border-top-right-radius: 6px;
+ border-bottom-right-radius: 6px;
+}
+
+/* 表格选择器样式 */
+#tableSelect {
+ border: 2px solid var(--border-color);
+ border-radius: 8px;
+ padding: 0.75rem;
+ font-size: 0.875rem;
+ transition: border-color 0.2s ease;
+}
+
+#tableSelect:focus {
+ border-color: var(--primary-color);
+ box-shadow: 0 0 0 0.2rem rgba(79, 70, 229, 0.25);
+}
+
+/* 记录计数样式 */
+#recordCount {
+ font-size: 1.5rem;
+ font-weight: 700;
+ color: var(--primary-color);
+}
+
+/* 响应式调整 */
+@media (max-width: 768px) {
+ .user-card {
+ margin-bottom: 1rem;
+ }
+
+ .table-container {
+ max-height: 400px;
+ }
+
+ .table th,
+ .table td {
+ padding: 0.5rem;
+ font-size: 0.8rem;
+ }
+
+ .btn-group {
+ flex-direction: column;
+ }
+
+ .btn-group .btn {
+ border-radius: 6px !important;
+ margin-bottom: 0.25rem;
+ }
+}
+
+/* 加载状态动画 */
+@keyframes fadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
+}
+
+.user-card,
+.table-container {
+ animation: fadeIn 0.3s ease-out;
+}
+
+/* 表格滚动条样式 */
+.table-container::-webkit-scrollbar {
+ width: 8px;
+}
+
+.table-container::-webkit-scrollbar-track {
+ background: #f1f1f1;
+ border-radius: 4px;
+}
+
+.table-container::-webkit-scrollbar-thumb {
+ background: var(--secondary-color);
+ border-radius: 4px;
+}
+
+.table-container::-webkit-scrollbar-thumb:hover {
+ background: var(--primary-color);
+}
+
+/* 文本截断样式 */
+.text-truncate-custom {
+ max-width: 200px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: inline-block;
+}
+
+/* 状态徽章样式 */
+.status-badge {
+ font-size: 0.75rem;
+ padding: 0.25rem 0.5rem;
+ border-radius: 12px;
+}
+
+/* 工具提示样式增强 */
+[title] {
+ cursor: help;
+}
+
+/* 空状态图标样式 */
+.empty-state-icon {
+ font-size: 4rem;
+ color: var(--secondary-color);
+ opacity: 0.3;
+ margin-bottom: 1rem;
+}
+
+/* 操作按钮样式 */
+.action-buttons .btn {
+ margin: 0 0.125rem;
+ padding: 0.375rem 0.75rem;
+}
+
+.action-buttons .btn-sm {
+ padding: 0.25rem 0.5rem;
+ font-size: 0.75rem;
+}
+
+/* 统计卡片渐变背景 */
+.stats-card-primary {
+ background: linear-gradient(135deg, var(--primary-color) 0%, var(--primary-hover) 100%);
+}
+
+.stats-card-info {
+ background: linear-gradient(135deg, #17a2b8 0%, #138496 100%);
+}
+
+.stats-card-success {
+ background: linear-gradient(135deg, var(--success-color) 0%, #0d9488 100%);
+}
+
+.stats-card-warning {
+ background: linear-gradient(135deg, var(--warning-color) 0%, #d97706 100%);
+}
diff --git a/static/css/app.css b/static/css/app.css
index 9a1db23..947a07d 100644
--- a/static/css/app.css
+++ b/static/css/app.css
@@ -7,6 +7,7 @@
@import url('items.css');
@import url('notifications.css');
@import url('components.css');
+@import url('admin.css');
diff --git a/static/css/logs.css b/static/css/logs.css
index 7a592c2..f8de02f 100644
--- a/static/css/logs.css
+++ b/static/css/logs.css
@@ -75,3 +75,73 @@
.log-container::-webkit-scrollbar-thumb:hover {
background: #5a5a5c;
}
+
+/* 日志过滤标签样式 */
+.filter-badge {
+ cursor: pointer;
+ transition: all 0.2s;
+ margin-right: 0.5rem;
+ margin-bottom: 0.25rem;
+}
+
+.filter-badge:hover {
+ transform: scale(1.05);
+}
+
+.filter-badge.active {
+ box-shadow: 0 0 0 2px rgba(255,255,255,0.5);
+}
+
+/* 自动刷新指示器 */
+.auto-refresh-indicator {
+ animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+ 0% { opacity: 1; }
+ 50% { opacity: 0.5; }
+ 100% { opacity: 1; }
+}
+
+/* 日志行悬停效果 */
+.log-entry:hover {
+ background: rgba(255,255,255,0.05);
+}
+
+/* 商品搜索相关样式 */
+.item-card {
+ transition: transform 0.2s;
+ border: none;
+ border-radius: 15px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+}
+
+.item-card:hover {
+ transform: translateY(-2px);
+}
+
+.item-image {
+ width: 100%;
+ height: 200px;
+ object-fit: cover;
+ border-radius: 10px;
+}
+
+.price {
+ color: #e74c3c;
+ font-weight: bold;
+ font-size: 1.2em;
+}
+
+.seller-name {
+ color: #6c757d;
+ font-size: 0.9em;
+}
+
+.want-count {
+ margin-top: 10px;
+}
+
+.want-count .badge {
+ font-size: 0.8em;
+}
diff --git a/static/index.html b/static/index.html
index 8670597..a790a14 100644
--- a/static/index.html
+++ b/static/index.html
@@ -85,7 +85,7 @@
-
+
商品搜索
@@ -103,19 +103,19 @@
管理员功能
-
+
数据管理
@@ -411,7 +411,7 @@
- 加载中... |
+ 加载中... |
@@ -1214,58 +1214,257 @@
-
-
-
-
-
+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ 全部
+
+
+ INFO
+
+
+ WARNING
+
+
+ ERROR
+
+
+ DEBUG
+
+
+
+
+
+
+
+
+
+
+
-
+
+
+
+
+
+
+ 日志文件:
+ -
+
+
+ 显示行数:
+ -
+
+
+ 当前过滤:
+ 全部
+
+
+
+
+
+
-
-
-
-
点击刷新按钮加载日志
+
+
+ 加载中...
+
+
正在加载日志...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
功能说明:
+
+ - 查询总页数:输入要获取的页数(1-20页),系统会一次性获取所有页面的数据
+ - 每页显示:前端分页显示的每页商品数量
+ - 数据来源:使用真实的闲鱼数据,通过浏览器自动化技术获取
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
未找到相关商品
+
请尝试使用其他关键词或调整搜索条件
+
+
@@ -1444,6 +1643,187 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
正在加载用户信息...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 加载中...
+
+
正在加载数据...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
您确定要删除这个用户吗?
+
+
+ 警告:此操作将同时删除该用户的所有数据,包括Cookie、关键词、卡券等,且不可撤销!
+
+
+
+
+
+
+
+
+
+
+
+
+
+
您确定要删除这条记录吗?
+
+
+
+ 警告:此操作不可撤销!
+
+
+
+
+
+
+