From 1dd4d9b841041344abe990427db9f3f8e269ef83 Mon Sep 17 00:00:00 2001 From: zhinianboke <115088296+zhinianboke@users.noreply.github.com> Date: Thu, 31 Jul 2025 23:17:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=94=AF=E6=8C=81=E5=A4=9A=E8=A7=84=E6=A0=BC?= =?UTF-8?q?=E5=8F=91=E8=B4=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +- XianyuAutoAsync.py | 77 ++++++++-- db_manager.py | 296 +++++++++++++++++++++++++++++++++++--- reply_server.py | 40 +++++- requirements.txt | 21 ++- static/index.html | 345 ++++++++++++++++++++++++++++++++++++++++++--- 6 files changed, 724 insertions(+), 64 deletions(-) diff --git a/README.md b/README.md index a8128fc..2df2ac0 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,12 @@ ### 🚚 自动发货功能 - **智能匹配** - 基于商品信息自动匹配发货规则 +- **多规格支持** - 支持同一商品的不同规格自动匹配对应卡券 +- **精确匹配+兜底机制** - 优先精确匹配规格,失败时自动降级到普通卡券 - **延时发货** - 支持设置发货延时时间(0-3600秒) - **多种触发** - 支持付款消息、小刀消息等多种触发条件 - **防重复发货** - 智能防重复机制,避免重复发货 -- **多种发货方式** - 支持文本内容、卡密文件、API调用等发货方式 +- **多种发货方式** - 支持固定文字、批量数据、API调用等发货方式 - **自动确认发货** - 检测到付款后自动调用闲鱼API确认发货 - **防重复确认** - 智能防重复确认机制,避免重复API调用 - **发货统计** - 完整的发货记录和统计功能 @@ -43,7 +45,8 @@ ### 🛍️ 商品管理 - **自动收集** - 消息触发时自动收集商品信息 - **API获取** - 通过闲鱼API获取完整商品详情 -- **批量管理** - 支持批量查看、编辑商品信息 +- **多规格支持** - 支持多规格商品的规格信息管理 +- **批量管理** - 支持批量查看、编辑、切换多规格状态 - **智能去重** - 自动去重,避免重复存储 ### 🔍 商品搜索功能 @@ -63,6 +66,8 @@ - **模板生成** - 自动生成包含示例数据的导入模板 - **批量操作** - 支持批量添加、更新关键词数据 - **数据验证** - 导入时自动验证数据格式和重复性 +- **多规格卡券管理** - 支持创建和管理多规格卡券 +- **发货规则管理** - 支持多规格发货规则的创建和管理 - **数据备份** - 自动数据备份和恢复 ## 📁 项目结构 diff --git a/XianyuAutoAsync.py b/XianyuAutoAsync.py index 0c99f4d..7811613 100644 --- a/XianyuAutoAsync.py +++ b/XianyuAutoAsync.py @@ -1103,7 +1103,7 @@ class XianyuLive: from utils.order_detail_fetcher import fetch_order_detail_simple # 获取当前账号的cookie字符串 - cookie_string = self.cookie_value + cookie_string = self.cookies_str logger.debug(f"【{self.cookie_id}】使用Cookie长度: {len(cookie_string) if cookie_string else 0}") # 异步获取订单详情(使用当前账号的cookie和无头模式) @@ -1236,19 +1236,74 @@ class XianyuLive: logger.info(f"使用搜索文本匹配发货规则: {search_text[:100]}...") - # 根据商品信息查找匹配的发货规则 - delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text) + # 检查商品是否为多规格商品 + is_multi_spec = db_manager.get_item_multi_spec_status(self.cookie_id, item_id) + spec_name = None + spec_value = None + + # 如果是多规格商品且有订单ID,获取规格信息 + if is_multi_spec and order_id: + logger.info(f"检测到多规格商品,获取订单规格信息: {order_id}") + try: + order_detail = await self.fetch_order_detail_info(order_id) + if order_detail: + spec_name = order_detail.get('spec_name', '') + spec_value = order_detail.get('spec_value', '') + if spec_name and spec_value: + logger.info(f"获取到规格信息: {spec_name} = {spec_value}") + else: + logger.warning(f"未能获取到规格信息,将使用兜底匹配") + else: + logger.warning(f"获取订单详情失败,将使用兜底匹配") + except Exception as e: + logger.error(f"获取订单规格信息失败: {self._safe_str(e)},将使用兜底匹配") + + # 智能匹配发货规则:优先精确匹配,然后兜底匹配 + delivery_rules = [] + + # 第一步:如果有规格信息,尝试精确匹配多规格发货规则 + if spec_name and spec_value: + logger.info(f"尝试精确匹配多规格发货规则: {search_text[:50]}... [{spec_name}:{spec_value}]") + delivery_rules = db_manager.get_delivery_rules_by_keyword_and_spec(search_text, spec_name, spec_value) + + if delivery_rules: + logger.info(f"✅ 找到精确匹配的多规格发货规则: {len(delivery_rules)}个") + else: + logger.info(f"❌ 未找到精确匹配的多规格发货规则") + + # 第二步:如果精确匹配失败,尝试兜底匹配(普通发货规则) + if not delivery_rules: + logger.info(f"尝试兜底匹配普通发货规则: {search_text[:50]}...") + delivery_rules = db_manager.get_delivery_rules_by_keyword(search_text) + + if delivery_rules: + logger.info(f"✅ 找到兜底匹配的普通发货规则: {len(delivery_rules)}个") + else: + logger.info(f"❌ 未找到任何匹配的发货规则") if not delivery_rules: - logger.info(f"未找到匹配的发货规则: {search_text[:50]}...") + logger.warning(f"未找到匹配的发货规则: {search_text[:50]}...") return None # 使用第一个匹配的规则(按关键字长度降序排列,优先匹配更精确的规则) - + rule = delivery_rules[0] + # 保存商品信息到数据库 await self.save_item_info_to_db(item_id, search_text) - rule = delivery_rules[0] - logger.info(f"找到匹配的发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})") + + # 详细的匹配结果日志 + if rule.get('is_multi_spec'): + if spec_name and spec_value: + logger.info(f"🎯 精确匹配多规格发货规则: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]") + logger.info(f"📋 订单规格: {spec_name}:{spec_value} ✅ 匹配卡券规格: {rule['spec_name']}:{rule['spec_value']}") + else: + logger.info(f"⚠️ 使用多规格发货规则但无订单规格信息: {rule['keyword']} -> {rule['card_name']} [{rule['spec_name']}:{rule['spec_value']}]") + else: + if spec_name and spec_value: + logger.info(f"🔄 兜底匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})") + logger.info(f"📋 订单规格: {spec_name}:{spec_value} ➡️ 使用普通卡券兜底") + else: + logger.info(f"✅ 匹配普通发货规则: {rule['keyword']} -> {rule['card_name']} ({rule['card_type']})") # 获取延时设置 delay_seconds = rule.get('card_delay_seconds', 0) @@ -1297,7 +1352,7 @@ class XianyuLive: elif rule['card_type'] == 'text': # 固定文字类型:直接使用文字内容 - delivery_content = rule['card_text_content'] + delivery_content = rule['text_content'] elif rule['card_type'] == 'data': # 批量数据类型:获取并消费第一条数据 @@ -1351,10 +1406,12 @@ class XianyuLive: try: import aiohttp + import json - api_config = rule.get('card_api_config') + api_config = rule.get('api_config') if not api_config: - logger.error("API配置为空") + logger.error(f"API配置为空,规则ID: {rule.get('id')}, 卡券名称: {rule.get('card_name')}") + logger.debug(f"规则详情: {rule}") return None # 解析API配置 diff --git a/db_manager.py b/db_manager.py index 413e5d1..babe2de 100644 --- a/db_manager.py +++ b/db_manager.py @@ -198,6 +198,9 @@ class DBManager: description TEXT, enabled BOOLEAN DEFAULT TRUE, delay_seconds INTEGER DEFAULT 0, + is_multi_spec BOOLEAN DEFAULT FALSE, + spec_name TEXT, + spec_value TEXT, user_id INTEGER NOT NULL DEFAULT 1, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, @@ -244,6 +247,7 @@ class DBManager: item_category TEXT, item_price TEXT, item_detail TEXT, + is_multi_spec BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (cookie_id) REFERENCES cookies(id) ON DELETE CASCADE, @@ -412,6 +416,24 @@ class DBManager: # type列存在,更新NULL值 self._execute_sql(cursor, "UPDATE email_verifications SET type = 'register' WHERE type IS NULL") + # 为cards表添加多规格字段(如果不存在) + try: + self._execute_sql(cursor, "SELECT is_multi_spec FROM cards LIMIT 1") + except sqlite3.OperationalError: + # 多规格字段不存在,需要添加 + self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE") + self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_name TEXT") + self._execute_sql(cursor, "ALTER TABLE cards ADD COLUMN spec_value TEXT") + logger.info("为cards表添加多规格字段") + + # 为item_info表添加多规格字段(如果不存在) + try: + self._execute_sql(cursor, "SELECT is_multi_spec FROM item_info LIMIT 1") + except sqlite3.OperationalError: + # 多规格字段不存在,需要添加 + self._execute_sql(cursor, "ALTER TABLE item_info ADD COLUMN is_multi_spec BOOLEAN DEFAULT FALSE") + logger.info("为item_info表添加多规格字段") + self.conn.commit() logger.info(f"数据库初始化成功: {self.db_path}") except Exception as e: @@ -1740,10 +1762,37 @@ class DBManager: def create_card(self, name: str, card_type: str, api_config=None, text_content: str = None, data_content: str = None, - description: str = None, enabled: bool = True, delay_seconds: int = 0, user_id: int = None): - """创建新卡券""" + description: str = None, enabled: bool = True, delay_seconds: int = 0, + is_multi_spec: bool = False, spec_name: str = None, spec_value: str = None, + user_id: int = None): + """创建新卡券(支持多规格)""" with self.lock: try: + # 验证多规格参数 + if is_multi_spec: + if not spec_name or not spec_value: + raise ValueError("多规格卡券必须提供规格名称和规格值") + + # 检查唯一性:卡券名称+规格名称+规格值 + cursor = self.conn.cursor() + cursor.execute(''' + SELECT COUNT(*) FROM cards + WHERE name = ? AND spec_name = ? AND spec_value = ? AND user_id = ? + ''', (name, spec_name, spec_value, user_id)) + + if cursor.fetchone()[0] > 0: + raise ValueError(f"卡券已存在:{name} - {spec_name}:{spec_value}") + else: + # 检查唯一性:仅卡券名称 + cursor = self.conn.cursor() + cursor.execute(''' + SELECT COUNT(*) FROM cards + WHERE name = ? AND (is_multi_spec = 0 OR is_multi_spec IS NULL) AND user_id = ? + ''', (name, user_id)) + + if cursor.fetchone()[0] > 0: + raise ValueError(f"卡券名称已存在:{name}") + # 处理api_config参数 - 如果是字典则转换为JSON字符串 api_config_str = None if api_config is not None: @@ -1753,16 +1802,21 @@ class DBManager: else: api_config_str = str(api_config) - cursor = self.conn.cursor() cursor.execute(''' INSERT INTO cards (name, type, api_config, text_content, data_content, - description, enabled, delay_seconds, user_id) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, user_id) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ''', (name, card_type, api_config_str, text_content, data_content, - description, enabled, delay_seconds, user_id)) + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, user_id)) self.conn.commit() card_id = cursor.lastrowid - logger.info(f"创建卡券成功: {name} (ID: {card_id})") + + if is_multi_spec: + logger.info(f"创建多规格卡券成功: {name} - {spec_name}:{spec_value} (ID: {card_id})") + else: + logger.info(f"创建卡券成功: {name} (ID: {card_id})") return card_id except Exception as e: logger.error(f"创建卡券失败: {e}") @@ -1776,7 +1830,8 @@ class DBManager: if user_id is not None: cursor.execute(''' SELECT id, name, type, api_config, text_content, data_content, - description, enabled, delay_seconds, created_at, updated_at + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, created_at, updated_at FROM cards WHERE user_id = ? ORDER BY created_at DESC @@ -1784,7 +1839,8 @@ class DBManager: else: cursor.execute(''' SELECT id, name, type, api_config, text_content, data_content, - description, enabled, delay_seconds, created_at, updated_at + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, created_at, updated_at FROM cards ORDER BY created_at DESC ''') @@ -1811,8 +1867,11 @@ class DBManager: 'description': row[6], 'enabled': bool(row[7]), 'delay_seconds': row[8] or 0, - 'created_at': row[9], - 'updated_at': row[10] + 'is_multi_spec': bool(row[9]) if row[9] is not None else False, + 'spec_name': row[10], + 'spec_value': row[11], + 'created_at': row[12], + 'updated_at': row[13] }) return cards @@ -1828,13 +1887,15 @@ class DBManager: if user_id is not None: cursor.execute(''' SELECT id, name, type, api_config, text_content, data_content, - description, enabled, delay_seconds, created_at, updated_at + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, created_at, updated_at FROM cards WHERE id = ? AND user_id = ? ''', (card_id, user_id)) else: cursor.execute(''' SELECT id, name, type, api_config, text_content, data_content, - description, enabled, delay_seconds, created_at, updated_at + description, enabled, delay_seconds, is_multi_spec, + spec_name, spec_value, created_at, updated_at FROM cards WHERE id = ? ''', (card_id,)) @@ -1860,8 +1921,11 @@ class DBManager: 'description': row[6], 'enabled': bool(row[7]), 'delay_seconds': row[8] or 0, - 'created_at': row[9], - 'updated_at': row[10] + 'is_multi_spec': bool(row[9]) if row[9] is not None else False, + 'spec_name': row[10], + 'spec_value': row[11], + 'created_at': row[12], + 'updated_at': row[13] } return None except Exception as e: @@ -1870,7 +1934,8 @@ class DBManager: def update_card(self, card_id: int, name: str = None, card_type: str = None, api_config=None, text_content: str = None, data_content: str = None, - description: str = None, enabled: bool = None, delay_seconds: int = None): + description: str = None, enabled: bool = None, delay_seconds: int = None, + is_multi_spec: bool = None, spec_name: str = None, spec_value: str = None): """更新卡券""" with self.lock: try: @@ -1913,6 +1978,15 @@ class DBManager: if delay_seconds is not None: update_fields.append("delay_seconds = ?") params.append(delay_seconds) + if is_multi_spec is not None: + update_fields.append("is_multi_spec = ?") + params.append(is_multi_spec) + if spec_name is not None: + update_fields.append("spec_name = ?") + params.append(spec_name) + if spec_value is not None: + update_fields.append("spec_value = ?") + params.append(spec_value) if not update_fields: return True # 没有需要更新的字段 @@ -1964,7 +2038,8 @@ class DBManager: cursor.execute(''' SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled, dr.description, dr.delivery_times, dr.created_at, dr.updated_at, - c.name as card_name, c.type as card_type + c.name as card_name, c.type as card_type, + c.is_multi_spec, c.spec_name, c.spec_value FROM delivery_rules dr LEFT JOIN cards c ON dr.card_id = c.id WHERE dr.user_id = ? @@ -1974,7 +2049,8 @@ class DBManager: cursor.execute(''' SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled, dr.description, dr.delivery_times, dr.created_at, dr.updated_at, - c.name as card_name, c.type as card_type + c.name as card_name, c.type as card_type, + c.is_multi_spec, c.spec_name, c.spec_value FROM delivery_rules dr LEFT JOIN cards c ON dr.card_id = c.id ORDER BY dr.created_at DESC @@ -1993,7 +2069,10 @@ class DBManager: 'created_at': row[7], 'updated_at': row[8], 'card_name': row[9], - 'card_type': row[10] + 'card_type': row[10], + 'is_multi_spec': bool(row[11]) if row[11] is not None else False, + 'spec_name': row[12], + 'spec_value': row[13] }) return rules @@ -2047,9 +2126,9 @@ class DBManager: 'delivery_times': row[6], 'card_name': row[7], 'card_type': row[8], - 'card_api_config': api_config, - 'card_text_content': row[10], - 'card_data_content': row[11], + 'api_config': api_config, # 修复字段名 + 'text_content': row[10], + 'data_content': row[11], 'card_enabled': bool(row[12]), 'card_description': row[13], # 卡券备注信息 'card_delay_seconds': row[14] or 0 # 延时秒数 @@ -2173,6 +2252,136 @@ class DBManager: except Exception as e: logger.error(f"更新发货次数失败: {e}") + def get_delivery_rules_by_keyword_and_spec(self, keyword: str, spec_name: str = None, spec_value: str = None): + """根据关键字和规格信息获取匹配的发货规则(支持多规格)""" + with self.lock: + try: + cursor = self.conn.cursor() + + # 优先匹配:卡券名称+规格名称+规格值 + if spec_name and spec_value: + cursor.execute(''' + SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled, + dr.description, dr.delivery_times, + c.name as card_name, c.type as card_type, c.api_config, + c.text_content, c.data_content, c.enabled as card_enabled, + c.description as card_description, c.delay_seconds as card_delay_seconds, + c.is_multi_spec, c.spec_name, c.spec_value + FROM delivery_rules dr + LEFT JOIN cards c ON dr.card_id = c.id + WHERE dr.enabled = 1 AND c.enabled = 1 + AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%') + AND c.is_multi_spec = 1 AND c.spec_name = ? AND c.spec_value = ? + ORDER BY + CASE + WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword) + ELSE LENGTH(dr.keyword) / 2 + END DESC, + dr.delivery_times ASC + ''', (keyword, keyword, spec_name, spec_value, keyword)) + + rules = [] + for row in cursor.fetchall(): + # 解析api_config JSON字符串 + api_config = row[9] + if api_config: + try: + import json + api_config = json.loads(api_config) + except (json.JSONDecodeError, TypeError): + # 如果解析失败,保持原始字符串 + pass + + rules.append({ + 'id': row[0], + 'keyword': row[1], + 'card_id': row[2], + 'delivery_count': row[3], + 'enabled': bool(row[4]), + 'description': row[5], + 'delivery_times': row[6] or 0, + 'card_name': row[7], + 'card_type': row[8], + 'api_config': api_config, + 'text_content': row[10], + 'data_content': row[11], + 'card_enabled': bool(row[12]), + 'card_description': row[13], + 'card_delay_seconds': row[14] or 0, + 'is_multi_spec': bool(row[15]), + 'spec_name': row[16], + 'spec_value': row[17] + }) + + if rules: + logger.info(f"找到多规格匹配规则: {keyword} - {spec_name}:{spec_value}") + return rules + + # 兜底匹配:仅卡券名称 + cursor.execute(''' + SELECT dr.id, dr.keyword, dr.card_id, dr.delivery_count, dr.enabled, + dr.description, dr.delivery_times, + c.name as card_name, c.type as card_type, c.api_config, + c.text_content, c.data_content, c.enabled as card_enabled, + c.description as card_description, c.delay_seconds as card_delay_seconds, + c.is_multi_spec, c.spec_name, c.spec_value + FROM delivery_rules dr + LEFT JOIN cards c ON dr.card_id = c.id + WHERE dr.enabled = 1 AND c.enabled = 1 + AND (? LIKE '%' || dr.keyword || '%' OR dr.keyword LIKE '%' || ? || '%') + AND (c.is_multi_spec = 0 OR c.is_multi_spec IS NULL) + ORDER BY + CASE + WHEN ? LIKE '%' || dr.keyword || '%' THEN LENGTH(dr.keyword) + ELSE LENGTH(dr.keyword) / 2 + END DESC, + dr.delivery_times ASC + ''', (keyword, keyword, keyword)) + + rules = [] + for row in cursor.fetchall(): + # 解析api_config JSON字符串 + api_config = row[9] + if api_config: + try: + import json + api_config = json.loads(api_config) + except (json.JSONDecodeError, TypeError): + # 如果解析失败,保持原始字符串 + pass + + rules.append({ + 'id': row[0], + 'keyword': row[1], + 'card_id': row[2], + 'delivery_count': row[3], + 'enabled': bool(row[4]), + 'description': row[5], + 'delivery_times': row[6] or 0, + 'card_name': row[7], + 'card_type': row[8], + 'api_config': api_config, + 'text_content': row[10], + 'data_content': row[11], + 'card_enabled': bool(row[12]), + 'card_description': row[13], + 'card_delay_seconds': row[14] or 0, + 'is_multi_spec': bool(row[15]) if row[15] is not None else False, + 'spec_name': row[16], + 'spec_value': row[17] + }) + + if rules: + logger.info(f"找到兜底匹配规则: {keyword}") + else: + logger.info(f"未找到匹配规则: {keyword}") + + return rules + + except Exception as e: + logger.error(f"获取发货规则失败: {e}") + return [] + def delete_card(self, card_id: int): """删除卡券""" with self.lock: @@ -2463,6 +2672,49 @@ class DBManager: logger.error(f"获取商品信息失败: {e}") return None + def update_item_multi_spec_status(self, cookie_id: str, item_id: str, is_multi_spec: bool) -> bool: + """更新商品的多规格状态""" + try: + with self.lock: + cursor = self.conn.cursor() + cursor.execute(''' + UPDATE item_info + SET is_multi_spec = ?, updated_at = CURRENT_TIMESTAMP + WHERE cookie_id = ? AND item_id = ? + ''', (is_multi_spec, cookie_id, item_id)) + + if cursor.rowcount > 0: + self.conn.commit() + logger.info(f"更新商品多规格状态成功: {item_id} -> {is_multi_spec}") + return True + else: + logger.warning(f"商品不存在,无法更新多规格状态: {item_id}") + return False + + except Exception as e: + logger.error(f"更新商品多规格状态失败: {e}") + self.conn.rollback() + return False + + def get_item_multi_spec_status(self, cookie_id: str, item_id: str) -> bool: + """获取商品的多规格状态""" + try: + with self.lock: + cursor = self.conn.cursor() + cursor.execute(''' + SELECT is_multi_spec FROM item_info + WHERE cookie_id = ? AND item_id = ? + ''', (cookie_id, item_id)) + + row = cursor.fetchone() + if row: + return bool(row[0]) if row[0] is not None else False + return False + + except Exception as e: + logger.error(f"获取商品多规格状态失败: {e}") + return False + def get_items_by_cookie(self, cookie_id: str) -> List[Dict]: """获取指定Cookie的所有商品信息 diff --git a/reply_server.py b/reply_server.py index d0baa45..e4b5501 100644 --- a/reply_server.py +++ b/reply_server.py @@ -1737,6 +1737,12 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr log_with_user('info', f"创建卡券: {card_name}", current_user) + # 验证多规格字段 + is_multi_spec = card_data.get('is_multi_spec', False) + if is_multi_spec: + if not card_data.get('spec_name') or not card_data.get('spec_value'): + raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值") + card_id = db_manager.create_card( name=card_data.get('name'), card_type=card_data.get('type'), @@ -1746,6 +1752,9 @@ def create_card(card_data: dict, current_user: Dict[str, Any] = Depends(get_curr description=card_data.get('description'), enabled=card_data.get('enabled', True), delay_seconds=card_data.get('delay_seconds', 0), + is_multi_spec=is_multi_spec, + spec_name=card_data.get('spec_name') if is_multi_spec else None, + spec_value=card_data.get('spec_value') if is_multi_spec else None, user_id=user_id ) @@ -1776,6 +1785,12 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)): """更新卡券""" try: from db_manager import db_manager + # 验证多规格字段 + is_multi_spec = card_data.get('is_multi_spec') + if is_multi_spec: + if not card_data.get('spec_name') or not card_data.get('spec_value'): + raise HTTPException(status_code=400, detail="多规格卡券必须提供规格名称和规格值") + success = db_manager.update_card( card_id=card_id, name=card_data.get('name'), @@ -1785,7 +1800,10 @@ def update_card(card_id: int, card_data: dict, _: None = Depends(require_auth)): data_content=card_data.get('data_content'), description=card_data.get('description'), enabled=card_data.get('enabled', True), - delay_seconds=card_data.get('delay_seconds') + delay_seconds=card_data.get('delay_seconds'), + is_multi_spec=is_multi_spec, + spec_name=card_data.get('spec_name'), + spec_value=card_data.get('spec_value') ) if success: return {"message": "卡券更新成功"} @@ -3039,5 +3057,25 @@ def clear_table_data(table_name: str, admin_user: Dict[str, Any] = Depends(requi raise HTTPException(status_code=500, detail=str(e)) +# 商品多规格管理API +@app.put("/items/{cookie_id}/{item_id}/multi-spec") +def update_item_multi_spec(cookie_id: str, item_id: str, spec_data: dict, _: None = Depends(require_auth)): + """更新商品的多规格状态""" + try: + from db_manager import db_manager + + is_multi_spec = spec_data.get('is_multi_spec', False) + + success = db_manager.update_item_multi_spec_status(cookie_id, item_id, is_multi_spec) + + if success: + return {"message": f"商品多规格状态已{'开启' if is_multi_spec else '关闭'}"} + else: + raise HTTPException(status_code=404, detail="商品不存在") + + except Exception as e: + raise HTTPException(status_code=500, detail=str(e)) + + if __name__ == "__main__": uvicorn.run(app, host="0.0.0.0", port=8080) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index ba62f8e..b03858c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,7 @@ # 闲鱼自动回复系统 - Python依赖包 # ================================ -# Web框架和API相关 +# 核心Web框架 fastapi>=0.111.0 uvicorn[standard]>=0.29.0 pydantic>=2.7.0 @@ -11,11 +11,13 @@ pydantic>=2.7.0 loguru>=0.7.0 # 网络通信 -websockets>=10.0,<13.0 +websockets>=12.0 aiohttp>=3.9.0 +requests>=2.31.0 # 配置文件处理 PyYAML>=6.0.0 +python-dotenv>=1.0.1 # JavaScript执行引擎 PyExecJS>=1.5.1 @@ -26,26 +28,21 @@ blackboxprotobuf>=1.0.1 # 系统监控 psutil>=5.9.0 -# HTTP客户端 -requests>=2.31.0 - # 文件上传支持 python-multipart>=0.0.6 -# AI回复相关 +# AI回复引擎 openai>=1.65.5 -python-dotenv>=1.0.1 -# 图像处理(图形验证码) +# 图像处理(验证码生成) Pillow>=10.0.0 -# 浏览器自动化(商品搜索功能) +# 浏览器自动化(商品搜索、订单详情获取) playwright>=1.40.0 # 加密和安全 PyJWT>=2.8.0 -passlib>=1.7.4 -bcrypt>=4.0.1 +passlib[bcrypt]>=1.7.4 cryptography>=41.0.0 # 时间处理 @@ -54,7 +51,7 @@ python-dateutil>=2.8.2 # 正则表达式增强 regex>=2023.10.3 -# Excel文件处理(导入导出功能) +# Excel文件处理(数据导入导出) pandas>=2.0.0 openpyxl>=3.1.0 diff --git a/static/index.html b/static/index.html index be9e777..9110fd0 100644 --- a/static/index.html +++ b/static/index.html @@ -1419,12 +1419,13 @@