From 1d5b3eb237e73a8586cf48b10203f10deef04ee0 Mon Sep 17 00:00:00 2001 From: jlj <3042504846@qq.com> Date: Wed, 21 May 2025 15:25:38 +0800 Subject: [PATCH 1/5] =?UTF-8?q?=E6=B7=BB=E5=8A=A0token?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/brands/services/offer_status_service.py | 2 +- apps/expertproducts/views.py | 205 ++++++++++--------- daren/settings.py | 3 - logs/app.log | 11 + 4 files changed, 117 insertions(+), 104 deletions(-) diff --git a/apps/brands/services/offer_status_service.py b/apps/brands/services/offer_status_service.py index 53851c4..ca6ec30 100644 --- a/apps/brands/services/offer_status_service.py +++ b/apps/brands/services/offer_status_service.py @@ -19,7 +19,7 @@ class OfferStatusService: :return: 状态字符串 """ try: - url = "http://127.0.0.1:8000/api/operation/negotiations/offer_status/" + url = "http://81.69.223.133:58099/api/operation/negotiations/offer_status/" payload = { 'creator_id': str(creator_id), diff --git a/apps/expertproducts/views.py b/apps/expertproducts/views.py index e192c58..e4a5208 100644 --- a/apps/expertproducts/views.py +++ b/apps/expertproducts/views.py @@ -11,6 +11,7 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated +from apps.user.authentication import CustomTokenAuthentication from django.db.models import Q import os import subprocess @@ -30,6 +31,8 @@ from apps.brands.models import Product client = Client(host="http://localhost:11434") class ContentAnalysisAPI(APIView): + authentication_classes = [CustomTokenAuthentication] + permission_classes = [IsAuthenticated] parser_classes = (MultiPartParser, FormParser) def post(self, request): @@ -167,79 +170,71 @@ class ContentAnalysisAPI(APIView): class NegotiationViewSet(viewsets.ModelViewSet): queryset = Negotiation.objects.all() serializer_class = NegotiationSerializer + authentication_classes = [CustomTokenAuthentication] + permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): - """创建谈判(事务保护版)""" - try: - # 开启事务:所有数据库操作要么全部成功,要么全部回滚 - with transaction.atomic(): - # 1. 验证请求数据 - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) # 自动返回400错误 + """创建谈判并返回包含初始消息的响应""" + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) - creator = serializer.validated_data['creator'] - product = serializer.validated_data['product'] + # Extract creator and product from validated data + creator = serializer.validated_data['creator'] + product = serializer.validated_data['product'] - # 2. 检查达人是否存在 - if not CreatorProfile.objects.filter(id=creator.id).exists(): - return Response( - {"code": 404, "message": "未找到指定的达人", "data": None}, - status=status.HTTP_404_NOT_FOUND - ) + # 检查该用户是否存在 + if not CreatorProfile.objects.filter(id=creator.id).exists(): + return Response({ + "code": 404, + "message": "未找到指定的达人", + "data": None + }) - # 3. 检查商品是否存在 - if not Product.objects.filter(id=product.id).exists(): - return Response( - {"code": 404, "message": "未找到指定的商品", "data": None}, - status=status.HTTP_404_NOT_FOUND - ) + # Check if the product exists + if not Product.objects.filter(id=product.id).exists(): + return Response({ + "code": 404, + "message": "未找到指定的商品", + "data": None + }) - # 4. 检查是否已存在相同谈判 - if Negotiation.objects.filter(creator=creator, product=product).exists(): - return Response( - { - "code": 400, - "message": "谈判已存在", - "data": {} - }, - status=status.HTTP_400_BAD_REQUEST - ) + # Check if a negotiation already exists for the same creator and product + existing_negotiation = Negotiation.objects.filter(creator=creator, product=product).first() + if existing_negotiation: + return Response({ + "code": 400, + "message": "谈判已存在", + "data": { + "negotiation_id": existing_negotiation.id + } + }) - # 5. 创建谈判记录 - negotiation = serializer.save() + # 1. 创建谈判记录 + negotiation = serializer.save() - # 6. 生成初始消息 - initial_message = self._generate_welcome_message(negotiation) - message = Message.objects.create( - negotiation=negotiation, - role='assistant', - content=initial_message, - stage=negotiation.status - ) + # 2. 生成并保存初始消息 + initial_message = self._generate_welcome_message(negotiation) + message = Message.objects.create( + negotiation=negotiation, + role='assistant', + content=initial_message, + stage=negotiation.status + ) - # 7. 返回成功响应 - return Response( - { - "code": 200, - "message": "谈判已创建", - "data": { - "id": message.id, - **serializer.data, - "content": initial_message, - "created_at": message.created_at - } - }, - status=status.HTTP_201_CREATED, - headers=self.get_success_headers(serializer.data) - ) + # 4. 构建响应数据 + response_data = { + "code": 200, + "message": "谈判已创建", + "data": { + "id": message.id, + **serializer.data, + "content": initial_message, + "created_at": message.created_at + } + } - except Exception as e: - # 记录异常细节(实际生产环境应接入监控系统) - # logger.error(f"谈判创建失败: {str(e)}", exc_info=True) - return Response( - {"code": 500, "message": "服务器内部错误", "data": None}, - status=status.HTTP_500_INTERNAL_SERVER_ERROR - ) + headers = self.get_success_headers(serializer.data) + return Response(response_data, status=status.HTTP_201_CREATED, headers=headers) @action(detail=True, methods=['post']) def chat(self, request, pk=None): @@ -441,6 +436,7 @@ class NegotiationViewSet(viewsets.ModelViewSet): 🔍 商品详情: - 名称:{product.name} + - 类目:{product.category} - 核心卖点:{product.description} 🤝 合作优势: @@ -479,8 +475,8 @@ class NegotiationViewSet(viewsets.ModelViewSet): 你是作为一个专业的商务谈判专家, 现在正在与达人 {negotiation.creator.name} 进行价格谈判。 当前谈判轮次:{negotiation.current_round}/4 - 商品参考价格:{negotiation.product.sales_price_min}元 - 商品最低价格:{negotiation.product.sales_price_max}元 + 商品参考价格:{negotiation.product.max_price}元 + 商品最低价格:{negotiation.product.min_price}元 达人最新回复: {history[-1]['content']} @@ -537,8 +533,7 @@ class NegotiationViewSet(viewsets.ModelViewSet): # history = self._get_stage_messages(negotiation) content = f""" 当前谈判已进入正式合同的准备阶段。 - - 如果用户继续谈跟价格有关的信息的话,我们就表示已经谈好价格了,无法修改。 + 如果用户表示没有收到合同邮件的话,我们就再次向用户发送合同邮件。 如果用户表示接受的话,我们引导用户去查看邮件并签署合同。 如果用户表示拒绝的话,我们引导用户去查看合同邮件然后在邮件中拒绝签署此合同。 @@ -605,7 +600,7 @@ class NegotiationViewSet(viewsets.ModelViewSet): subject = f"合同文件 - {negotiation.product.name}" recipient = '3299361176@qq.com' if not recipient: - # logger.error(f"未找到达人 {negotiation.creator.name} 的邮箱,无法发送合同。") + logger.error(f"未找到达人 {negotiation.creator.name} 的邮箱,无法发送合同。") return False try: email = EmailMessage( @@ -620,15 +615,17 @@ class NegotiationViewSet(viewsets.ModelViewSet): email.attach('商品合同.docx', f.read(), 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') email.send(fail_silently=False) - # logger.info(f"合同已发送到 {recipient}") + logger.info(f"合同已发送到 {recipient}") return True except Exception as e: - # logger.error(f"发送合同邮件失败: {e}") + logger.error(f"发送合同邮件失败: {e}") return False ## 根据接收到的达人列表来查找达人(已弃用) class TopCreatorsAPI(APIView): + authentication_classes = [CustomTokenAuthentication] + permission_classes = [IsAuthenticated] def post(self, request): # Extract filtering criteria and creator list from the request criteria = request.data.get('criteria', "") @@ -715,6 +712,8 @@ class TopCreatorsAPI(APIView): ## 生成sql直接查询达人库 class CreatorSQLSearchAPI(APIView): + authentication_classes = [CustomTokenAuthentication] + permission_classes = [IsAuthenticated] def post(self, request): criteria = request.data.get('criteria', '') top_n = int(request.data.get('top_n', 10)) # 默认为10 @@ -722,48 +721,54 @@ class CreatorSQLSearchAPI(APIView): return Response({"error": "缺少筛选条件"}, status=400) table_schema = ''' - creator_profiles( - id bigint 主键, - name varchar(255) 达人名称, - avatar_url text 头像URL, - email varchar(255) 电子邮箱, - instagram varchar(255) Instagram账号, - tiktok_link varchar(255) TikTok链接, - location varchar(100) 位置, - live_schedule varchar(255) 直播时间表, - category varchar(100) 类别, - e_commerce_level int 电商能力等级, - exposure_level varchar(10) 曝光等级, - followers int 粉丝数, - gmv decimal(12,2) GMV(千美元), - items_sold decimal(12,2) 售出商品数量, - avg_video_views int 平均视频浏览量, - pricing_min decimal(10,2) 最低个人定价, - pricing_max decimal(10,2) 最高个人定价, - pricing_package varchar(100) 套餐定价, - collab_count int 合作次数, - latest_collab varchar(100) 最新合作, - e_commerce_platforms json 电商平台, - gmv_by_channel json GMV按渠道分布, - gmv_by_category json GMV按类别分布, - mcn varchar(255) MCN机构, - create_time datetime 创建时间, - update_time datetime 更新时间 + feishu_creators( + id char(32) 主键, + record_id varchar(100) 记录ID, + contact_person varchar(50) 联系人姓名, + handle longtext 账号/达人昵称, + tiktok_url longtext 抖音主页链接, + fans_count varchar(50) 粉丝数, + gmv varchar(100) GMV, + email varchar(254) 邮箱, + phone varchar(50) 电话, + account_type varchar(50) 账号类型, + price_quote longtext 报价, + response_speed varchar(50) 响应速度, + cooperation_intention varchar(50) 合作意向, + payment_method varchar(50) 支付方式, + payment_account varchar(100) 支付账号, + address longtext 地址, + has_ooin varchar(10) 是否有OOIN, + source varchar(100) 数据来源, + contact_status varchar(50) 联系状态, + cooperation_brands json 合作品牌, + system_categories varchar(100) 系统分类, + actual_categories varchar(100) 实际分类, + human_categories varchar(100) 人工分类, + creator_base varchar(100) 达人基地, + notes longtext 备注, + created_at datetime 创建时间, + updated_at datetime 更新时间 ) ''' prompt = f""" - 你是一个SQL专家。下面是MySQL表creator_profiles的结构: + 你是一个SQL专家。下面是MySQL表feishu_creators的结构: {table_schema} 以下是我对表中每个字段的解释: 方便你写出正确的sql查询语句 - 请根据以下自然语言筛选条件, 生成一条MySQL的SELECT语句, 查询daren_detail.creator_profiles表(注意一定要写数据库名称.表名称), 返回所有字段。不要加任何解释说明, 只输出SQL语句本身。 + 注意: fans_count 字段为字符串,可能为纯数字(如 '1234'),也可能带有 K(千)或 M(百万)后缀(如 '9K', '56.5K', '13.2M')。请在SQL中将其统一转换为数字后再进行比较, K=1000, M=1000000。例如查找粉丝数量大于10000的博主: SELECT * FROM your_table WHERE (CASE WHEN fans_count LIKE '%K' THEN CAST(REPLACE(fans_count, 'K', '') AS DECIMAL(10,2)) * 1000 WHEN fans_count LIKE '%M' THEN CAST(REPLACE(fans_count, 'M', '') AS DECIMAL(10,2)) * 1000000 ELSE CAST(fans_count AS DECIMAL(10,2)) END) > 100000; + 注意: response_speed 字段有以下几个取值: 1、无回复 2、一般 3、正常 4、积极。请在sql中直接使用上述的某个值就好, 不要进行任何转换。例如: SELECT * FROM daren.feishu_creators WHERE response_speed = '积极' + 注意: source 字段有以下几个取值: 1. 线下活动 2、TAP后台。请在sql中直接使用上述的某个值就好, 不要进行任何转换。例如: SELECT * FROM daren.feishu_creators WHERE source = 'TAP后台' + 注意: system_categories 字段是一个varchar类型的值, 是一个列表形式表示的,例如 ['日用百货']、['美妆个护,保健'] 这种。例如查找'美妆个护'的博主: SELECT * FROM daren.feishu_creators WHERE system_categories LIKE '%美妆个护%'; + 注意: gmv 字段是一个varchar类型的值, 例如 $86.89、$8761.98、$15.5K、$12.5M 这种。例如我要查询gmv大于10000美金的达人: SELECT * FROM daren.feishu_creators WHERE gmv NOT LIKE '$0-%' AND (CASE WHEN gmv LIKE '%K' THEN CAST(SUBSTRING(gmv, 2, LENGTH(gmv) - 2) AS DECIMAL(10,2)) * 1000 WHEN gmv LIKE '%M' THEN CAST(SUBSTRING(gmv, 2, LENGTH(gmv) - 2) AS DECIMAL(10,2)) * 1000000 ELSE CAST(SUBSTRING(gmv, 2) AS DECIMAL(10,2)) END) > 10000; + 请根据以下自然语言筛选条件, 生成一条MySQL的SELECT语句, 查询daren.feishu_creators表(注意一定要写数据库名称.表名称), 返回所有字段。不要加任何解释说明, 只输出SQL语句本身。 筛选条件:{criteria} """ # 2. 让大模型生成SQL response = client.chat( - model="qwen2.5:32b", + model="deepseek-r1:70b", messages=[{'role': 'user', 'content': prompt}], ) sql = self._extract_sql(response['message']['content']) diff --git a/daren/settings.py b/daren/settings.py index f3cd9a9..22cd96d 100644 --- a/daren/settings.py +++ b/daren/settings.py @@ -223,6 +223,3 @@ SIMPLE_JWT = { 'JTI_CLAIM': None, # 不在 token 中包含 JWT ID } -# JWT配置 -JWT_SECRET_KEY = 'your-secret-key-here' # 建议使用更安全的密钥 -JWT_EXPIRATION_DELTA = 24 * 60 * 60 # token有效期24小时(秒) \ No newline at end of file diff --git a/logs/app.log b/logs/app.log index ff52b1b..fa1ddaf 100644 --- a/logs/app.log +++ b/logs/app.log @@ -29,3 +29,14 @@ ERROR 2025-05-20 17:40:33,375 offer_status_service ERROR 2025-05-20 17:41:03,425 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ ERROR 2025-05-20 17:41:33,477 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ ERROR 2025-05-20 17:42:03,529 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ +INFO 2025-05-21 15:11:55,810 status_polling_service  1 ״̬ѯ 30 +INFO 2025-05-21 15:11:55,812 consumers  1 ״̬ѯ +INFO 2025-05-21 15:11:55,813 consumers WebSocketѽ: campaign_1 +ERROR 2025-05-21 15:11:55,860 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:12:25,874 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:12:55,889 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:13:25,904 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:13:55,924 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:14:25,949 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:14:55,967 offer_status_service ̸״̬ӿʧ: 401 +ERROR 2025-05-21 15:15:25,985 offer_status_service ̸״̬ӿʧ: 401 From 7ff7e17cf9f0e1c75e4751e4373a211c205cd2e4 Mon Sep 17 00:00:00 2001 From: jlj <3042504846@qq.com> Date: Wed, 21 May 2025 15:54:58 +0800 Subject: [PATCH 2/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=97=A5=E5=BF=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- daren/settings.py | 1 + logs/app.log | 42 ------------------------------------------ 2 files changed, 1 insertion(+), 42 deletions(-) delete mode 100644 logs/app.log diff --git a/daren/settings.py b/daren/settings.py index 22cd96d..856df44 100644 --- a/daren/settings.py +++ b/daren/settings.py @@ -176,6 +176,7 @@ LOGGING = { 'class': 'logging.FileHandler', 'filename': os.path.join(BASE_DIR, 'logs', 'app.log'), 'formatter': 'verbose', + 'encoding': 'utf-8' }, }, 'loggers': { diff --git a/logs/app.log b/logs/app.log deleted file mode 100644 index fa1ddaf..0000000 --- a/logs/app.log +++ /dev/null @@ -1,42 +0,0 @@ -INFO 2025-05-20 17:27:32,071 status_polling_service  1 ״̬ѯ 30 -ERROR 2025-05-20 17:27:32,113 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:28:02,155 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:28:32,197 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:29:02,237 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:29:32,284 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:30:02,328 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:30:32,375 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:31:02,430 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:31:32,476 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:32:02,531 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:32:32,577 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:33:02,632 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:33:32,685 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:34:02,739 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:34:32,792 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:35:02,827 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:35:32,872 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:36:02,924 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:36:32,971 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:37:03,025 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:37:33,071 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:38:03,121 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:38:33,176 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:39:03,225 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:39:33,277 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:40:03,328 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:40:33,375 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:41:03,425 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:41:33,477 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -ERROR 2025-05-20 17:42:03,529 offer_status_service ȡ̸״̬ʧ: ûidΪ14ƷidΪ241a67e0-1c99-44de-a5dd-40622ffa23b6̸ -INFO 2025-05-21 15:11:55,810 status_polling_service  1 ״̬ѯ 30 -INFO 2025-05-21 15:11:55,812 consumers  1 ״̬ѯ -INFO 2025-05-21 15:11:55,813 consumers WebSocketѽ: campaign_1 -ERROR 2025-05-21 15:11:55,860 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:12:25,874 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:12:55,889 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:13:25,904 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:13:55,924 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:14:25,949 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:14:55,967 offer_status_service ̸״̬ӿʧ: 401 -ERROR 2025-05-21 15:15:25,985 offer_status_service ̸״̬ӿʧ: 401 From 235bc9e99dd2e2aa40e1cf333fb1656a7f604c7c Mon Sep 17 00:00:00 2001 From: jlj <3042504846@qq.com> Date: Wed, 21 May 2025 16:06:20 +0800 Subject: [PATCH 3/5] =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=9D=83=E9=99=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/expertproducts/views.py | 2 -- logs/app.log | 18 ++++++++++++++++++ 2 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 logs/app.log diff --git a/apps/expertproducts/views.py b/apps/expertproducts/views.py index e4a5208..a4980fb 100644 --- a/apps/expertproducts/views.py +++ b/apps/expertproducts/views.py @@ -170,8 +170,6 @@ class ContentAnalysisAPI(APIView): class NegotiationViewSet(viewsets.ModelViewSet): queryset = Negotiation.objects.all() serializer_class = NegotiationSerializer - authentication_classes = [CustomTokenAuthentication] - permission_classes = [IsAuthenticated] def create(self, request, *args, **kwargs): """创建谈判并返回包含初始消息的响应""" diff --git a/logs/app.log b/logs/app.log new file mode 100644 index 0000000..9bca53e --- /dev/null +++ b/logs/app.log @@ -0,0 +1,18 @@ +INFO 2025-05-21 15:58:01,038 status_polling_service 已启动活动 1 的状态轮询,间隔 30 秒 +INFO 2025-05-21 15:58:01,039 consumers 已启动活动 1 的状态轮询 +INFO 2025-05-21 15:58:01,040 consumers WebSocket连接已建立: campaign_1 +ERROR 2025-05-21 15:58:01,096 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 15:58:31,379 offer_status_service 请求谈判状态接口失败: 401 +INFO 2025-05-21 15:58:57,453 consumers WebSocket连接已断开: campaign_1, 关闭代码: None +ERROR 2025-05-21 15:59:01,519 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 15:59:31,671 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:00:01,813 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:00:32,020 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:01:02,090 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:01:32,168 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:02:02,327 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:02:32,587 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:03:02,697 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:03:32,791 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:04:02,865 offer_status_service 请求谈判状态接口失败: 401 +ERROR 2025-05-21 16:04:33,384 offer_status_service 获取谈判状态失败: 不存在与用户id为14和商品id为241a67e0-1c99-44de-a5dd-40622ffa23b6的谈判 From 8d923b663c9d3fea4046b5af9f41dbb10ca2f8ce Mon Sep 17 00:00:00 2001 From: Xiaofeng <1169646434@qq.com> Date: Wed, 21 May 2025 16:40:02 +0800 Subject: [PATCH 4/5] =?UTF-8?q?=E4=BF=AE=E6=94=B9sql=E6=8E=A8=E7=90=86?= =?UTF-8?q?=E6=A8=A1=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/expertproducts/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/expertproducts/views.py b/apps/expertproducts/views.py index a4980fb..c10bba9 100644 --- a/apps/expertproducts/views.py +++ b/apps/expertproducts/views.py @@ -766,7 +766,7 @@ class CreatorSQLSearchAPI(APIView): # 2. 让大模型生成SQL response = client.chat( - model="deepseek-r1:70b", + model="qwen2.5:32b", messages=[{'role': 'user', 'content': prompt}], ) sql = self._extract_sql(response['message']['content']) From d67493fdb482f059f5390841e498db327f382485 Mon Sep 17 00:00:00 2001 From: jlj <3042504846@qq.com> Date: Wed, 21 May 2025 16:52:01 +0800 Subject: [PATCH 5/5] new --- apps/expertproducts/views.py | 201 +++++++++++++++++------------------ 1 file changed, 99 insertions(+), 102 deletions(-) diff --git a/apps/expertproducts/views.py b/apps/expertproducts/views.py index c10bba9..e192c58 100644 --- a/apps/expertproducts/views.py +++ b/apps/expertproducts/views.py @@ -11,7 +11,6 @@ from rest_framework import viewsets, status from rest_framework.decorators import action from rest_framework.response import Response from rest_framework.permissions import IsAuthenticated -from apps.user.authentication import CustomTokenAuthentication from django.db.models import Q import os import subprocess @@ -31,8 +30,6 @@ from apps.brands.models import Product client = Client(host="http://localhost:11434") class ContentAnalysisAPI(APIView): - authentication_classes = [CustomTokenAuthentication] - permission_classes = [IsAuthenticated] parser_classes = (MultiPartParser, FormParser) def post(self, request): @@ -172,67 +169,77 @@ class NegotiationViewSet(viewsets.ModelViewSet): serializer_class = NegotiationSerializer def create(self, request, *args, **kwargs): - """创建谈判并返回包含初始消息的响应""" - serializer = self.get_serializer(data=request.data) - serializer.is_valid(raise_exception=True) + """创建谈判(事务保护版)""" + try: + # 开启事务:所有数据库操作要么全部成功,要么全部回滚 + with transaction.atomic(): + # 1. 验证请求数据 + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) # 自动返回400错误 - # Extract creator and product from validated data - creator = serializer.validated_data['creator'] - product = serializer.validated_data['product'] + creator = serializer.validated_data['creator'] + product = serializer.validated_data['product'] - # 检查该用户是否存在 - if not CreatorProfile.objects.filter(id=creator.id).exists(): - return Response({ - "code": 404, - "message": "未找到指定的达人", - "data": None - }) + # 2. 检查达人是否存在 + if not CreatorProfile.objects.filter(id=creator.id).exists(): + return Response( + {"code": 404, "message": "未找到指定的达人", "data": None}, + status=status.HTTP_404_NOT_FOUND + ) - # Check if the product exists - if not Product.objects.filter(id=product.id).exists(): - return Response({ - "code": 404, - "message": "未找到指定的商品", - "data": None - }) + # 3. 检查商品是否存在 + if not Product.objects.filter(id=product.id).exists(): + return Response( + {"code": 404, "message": "未找到指定的商品", "data": None}, + status=status.HTTP_404_NOT_FOUND + ) - # Check if a negotiation already exists for the same creator and product - existing_negotiation = Negotiation.objects.filter(creator=creator, product=product).first() - if existing_negotiation: - return Response({ - "code": 400, - "message": "谈判已存在", - "data": { - "negotiation_id": existing_negotiation.id - } - }) + # 4. 检查是否已存在相同谈判 + if Negotiation.objects.filter(creator=creator, product=product).exists(): + return Response( + { + "code": 400, + "message": "谈判已存在", + "data": {} + }, + status=status.HTTP_400_BAD_REQUEST + ) - # 1. 创建谈判记录 - negotiation = serializer.save() + # 5. 创建谈判记录 + negotiation = serializer.save() - # 2. 生成并保存初始消息 - initial_message = self._generate_welcome_message(negotiation) - message = Message.objects.create( - negotiation=negotiation, - role='assistant', - content=initial_message, - stage=negotiation.status - ) + # 6. 生成初始消息 + initial_message = self._generate_welcome_message(negotiation) + message = Message.objects.create( + negotiation=negotiation, + role='assistant', + content=initial_message, + stage=negotiation.status + ) - # 4. 构建响应数据 - response_data = { - "code": 200, - "message": "谈判已创建", - "data": { - "id": message.id, - **serializer.data, - "content": initial_message, - "created_at": message.created_at - } - } + # 7. 返回成功响应 + return Response( + { + "code": 200, + "message": "谈判已创建", + "data": { + "id": message.id, + **serializer.data, + "content": initial_message, + "created_at": message.created_at + } + }, + status=status.HTTP_201_CREATED, + headers=self.get_success_headers(serializer.data) + ) - headers = self.get_success_headers(serializer.data) - return Response(response_data, status=status.HTTP_201_CREATED, headers=headers) + except Exception as e: + # 记录异常细节(实际生产环境应接入监控系统) + # logger.error(f"谈判创建失败: {str(e)}", exc_info=True) + return Response( + {"code": 500, "message": "服务器内部错误", "data": None}, + status=status.HTTP_500_INTERNAL_SERVER_ERROR + ) @action(detail=True, methods=['post']) def chat(self, request, pk=None): @@ -434,7 +441,6 @@ class NegotiationViewSet(viewsets.ModelViewSet): 🔍 商品详情: - 名称:{product.name} - - 类目:{product.category} - 核心卖点:{product.description} 🤝 合作优势: @@ -473,8 +479,8 @@ class NegotiationViewSet(viewsets.ModelViewSet): 你是作为一个专业的商务谈判专家, 现在正在与达人 {negotiation.creator.name} 进行价格谈判。 当前谈判轮次:{negotiation.current_round}/4 - 商品参考价格:{negotiation.product.max_price}元 - 商品最低价格:{negotiation.product.min_price}元 + 商品参考价格:{negotiation.product.sales_price_min}元 + 商品最低价格:{negotiation.product.sales_price_max}元 达人最新回复: {history[-1]['content']} @@ -531,7 +537,8 @@ class NegotiationViewSet(viewsets.ModelViewSet): # history = self._get_stage_messages(negotiation) content = f""" 当前谈判已进入正式合同的准备阶段。 - + + 如果用户继续谈跟价格有关的信息的话,我们就表示已经谈好价格了,无法修改。 如果用户表示没有收到合同邮件的话,我们就再次向用户发送合同邮件。 如果用户表示接受的话,我们引导用户去查看邮件并签署合同。 如果用户表示拒绝的话,我们引导用户去查看合同邮件然后在邮件中拒绝签署此合同。 @@ -598,7 +605,7 @@ class NegotiationViewSet(viewsets.ModelViewSet): subject = f"合同文件 - {negotiation.product.name}" recipient = '3299361176@qq.com' if not recipient: - logger.error(f"未找到达人 {negotiation.creator.name} 的邮箱,无法发送合同。") + # logger.error(f"未找到达人 {negotiation.creator.name} 的邮箱,无法发送合同。") return False try: email = EmailMessage( @@ -613,17 +620,15 @@ class NegotiationViewSet(viewsets.ModelViewSet): email.attach('商品合同.docx', f.read(), 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') email.send(fail_silently=False) - logger.info(f"合同已发送到 {recipient}") + # logger.info(f"合同已发送到 {recipient}") return True except Exception as e: - logger.error(f"发送合同邮件失败: {e}") + # logger.error(f"发送合同邮件失败: {e}") return False ## 根据接收到的达人列表来查找达人(已弃用) class TopCreatorsAPI(APIView): - authentication_classes = [CustomTokenAuthentication] - permission_classes = [IsAuthenticated] def post(self, request): # Extract filtering criteria and creator list from the request criteria = request.data.get('criteria', "") @@ -710,8 +715,6 @@ class TopCreatorsAPI(APIView): ## 生成sql直接查询达人库 class CreatorSQLSearchAPI(APIView): - authentication_classes = [CustomTokenAuthentication] - permission_classes = [IsAuthenticated] def post(self, request): criteria = request.data.get('criteria', '') top_n = int(request.data.get('top_n', 10)) # 默认为10 @@ -719,48 +722,42 @@ class CreatorSQLSearchAPI(APIView): return Response({"error": "缺少筛选条件"}, status=400) table_schema = ''' - feishu_creators( - id char(32) 主键, - record_id varchar(100) 记录ID, - contact_person varchar(50) 联系人姓名, - handle longtext 账号/达人昵称, - tiktok_url longtext 抖音主页链接, - fans_count varchar(50) 粉丝数, - gmv varchar(100) GMV, - email varchar(254) 邮箱, - phone varchar(50) 电话, - account_type varchar(50) 账号类型, - price_quote longtext 报价, - response_speed varchar(50) 响应速度, - cooperation_intention varchar(50) 合作意向, - payment_method varchar(50) 支付方式, - payment_account varchar(100) 支付账号, - address longtext 地址, - has_ooin varchar(10) 是否有OOIN, - source varchar(100) 数据来源, - contact_status varchar(50) 联系状态, - cooperation_brands json 合作品牌, - system_categories varchar(100) 系统分类, - actual_categories varchar(100) 实际分类, - human_categories varchar(100) 人工分类, - creator_base varchar(100) 达人基地, - notes longtext 备注, - created_at datetime 创建时间, - updated_at datetime 更新时间 + creator_profiles( + id bigint 主键, + name varchar(255) 达人名称, + avatar_url text 头像URL, + email varchar(255) 电子邮箱, + instagram varchar(255) Instagram账号, + tiktok_link varchar(255) TikTok链接, + location varchar(100) 位置, + live_schedule varchar(255) 直播时间表, + category varchar(100) 类别, + e_commerce_level int 电商能力等级, + exposure_level varchar(10) 曝光等级, + followers int 粉丝数, + gmv decimal(12,2) GMV(千美元), + items_sold decimal(12,2) 售出商品数量, + avg_video_views int 平均视频浏览量, + pricing_min decimal(10,2) 最低个人定价, + pricing_max decimal(10,2) 最高个人定价, + pricing_package varchar(100) 套餐定价, + collab_count int 合作次数, + latest_collab varchar(100) 最新合作, + e_commerce_platforms json 电商平台, + gmv_by_channel json GMV按渠道分布, + gmv_by_category json GMV按类别分布, + mcn varchar(255) MCN机构, + create_time datetime 创建时间, + update_time datetime 更新时间 ) ''' prompt = f""" - 你是一个SQL专家。下面是MySQL表feishu_creators的结构: + 你是一个SQL专家。下面是MySQL表creator_profiles的结构: {table_schema} 以下是我对表中每个字段的解释: 方便你写出正确的sql查询语句 - 注意: fans_count 字段为字符串,可能为纯数字(如 '1234'),也可能带有 K(千)或 M(百万)后缀(如 '9K', '56.5K', '13.2M')。请在SQL中将其统一转换为数字后再进行比较, K=1000, M=1000000。例如查找粉丝数量大于10000的博主: SELECT * FROM your_table WHERE (CASE WHEN fans_count LIKE '%K' THEN CAST(REPLACE(fans_count, 'K', '') AS DECIMAL(10,2)) * 1000 WHEN fans_count LIKE '%M' THEN CAST(REPLACE(fans_count, 'M', '') AS DECIMAL(10,2)) * 1000000 ELSE CAST(fans_count AS DECIMAL(10,2)) END) > 100000; - 注意: response_speed 字段有以下几个取值: 1、无回复 2、一般 3、正常 4、积极。请在sql中直接使用上述的某个值就好, 不要进行任何转换。例如: SELECT * FROM daren.feishu_creators WHERE response_speed = '积极' - 注意: source 字段有以下几个取值: 1. 线下活动 2、TAP后台。请在sql中直接使用上述的某个值就好, 不要进行任何转换。例如: SELECT * FROM daren.feishu_creators WHERE source = 'TAP后台' - 注意: system_categories 字段是一个varchar类型的值, 是一个列表形式表示的,例如 ['日用百货']、['美妆个护,保健'] 这种。例如查找'美妆个护'的博主: SELECT * FROM daren.feishu_creators WHERE system_categories LIKE '%美妆个护%'; - 注意: gmv 字段是一个varchar类型的值, 例如 $86.89、$8761.98、$15.5K、$12.5M 这种。例如我要查询gmv大于10000美金的达人: SELECT * FROM daren.feishu_creators WHERE gmv NOT LIKE '$0-%' AND (CASE WHEN gmv LIKE '%K' THEN CAST(SUBSTRING(gmv, 2, LENGTH(gmv) - 2) AS DECIMAL(10,2)) * 1000 WHEN gmv LIKE '%M' THEN CAST(SUBSTRING(gmv, 2, LENGTH(gmv) - 2) AS DECIMAL(10,2)) * 1000000 ELSE CAST(SUBSTRING(gmv, 2) AS DECIMAL(10,2)) END) > 10000; - 请根据以下自然语言筛选条件, 生成一条MySQL的SELECT语句, 查询daren.feishu_creators表(注意一定要写数据库名称.表名称), 返回所有字段。不要加任何解释说明, 只输出SQL语句本身。 + 请根据以下自然语言筛选条件, 生成一条MySQL的SELECT语句, 查询daren_detail.creator_profiles表(注意一定要写数据库名称.表名称), 返回所有字段。不要加任何解释说明, 只输出SQL语句本身。 筛选条件:{criteria} """