diff --git a/orders/views.py b/orders/views.py index 022ff66..eee08d8 100644 --- a/orders/views.py +++ b/orders/views.py @@ -39,7 +39,6 @@ from tencentcloud.sts.v20180813 import sts_client, models from utils.weixin_broadcast import WeixinBroadcastSender from utils.chat_utils import _send_group_message, _subscribe_users_to_group, establish_order_chat -from utils.link_sender_meta import build_link_sender_meta from utils.fadan_utils import check_fadan_qiangdan_eligible from orders.utils import ( @@ -3508,14 +3507,12 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): 商家链接留言转发接口 路径:POST /dingdan/sjljlbxxzf 固定以 B{商家ID} 身份发送群消息。 - 附带 _linkMeta(发消息者 IP/定位/设备指纹等),仅 H5 链接页解析展示,不读取客服 JWT。 """ permission_classes = [] def post(self, request): token = request.data.get('token') text = (request.data.get('text') or '').strip() - client_info = request.data.get('clientInfo') if not token or not text: return Response({'code': 400, 'msg': '缺少参数'}) @@ -3538,9 +3535,6 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): sender_name = "老板" sender_avatar = '' - sender_meta = build_link_sender_meta(request, link_token=token, client_info=client_info) - custom_payload = {'text': text, '_linkMeta': sender_meta} - appkey = get_self_goeasy_appkey() secret = get_self_goeasy_secret() if not appkey: @@ -3560,18 +3554,14 @@ class ShangjiaLianjieLiuYanZhuanFaView(APIView): sender_name=sender_name, sender_avatar=sender_avatar, message_text=text, - custom_payload=custom_payload, + custom_payload=None, group_name=group_name, order_id=order.OrderID, ) if not local_ok: return Response({'code': 500, 'msg': '消息发送失败'}) - return Response({ - 'code': 200, - 'msg': '发送成功', - 'data': {'senderMeta': sender_meta}, - }) + return Response({'code': 200, 'msg': '发送成功'}) class QiangdanView(APIView): diff --git a/utils/chat_utils.py b/utils/chat_utils.py index 206c527..37faacb 100644 --- a/utils/chat_utils.py +++ b/utils/chat_utils.py @@ -69,15 +69,7 @@ def _send_group_message(appkey, secret, group_id, sender_id, sender_name, sender if order_id: to_data["orderId"] = order_id - if custom_payload: - if isinstance(custom_payload, dict) and custom_payload.get('_linkMeta') is not None: - payload_str = json.dumps(custom_payload, ensure_ascii=False) - elif isinstance(custom_payload, dict): - payload_str = custom_payload.get('text', message_text) - else: - payload_str = str(custom_payload) - else: - payload_str = message_text + payload_str = custom_payload.get("text", message_text) if custom_payload else message_text request_body = { "appkey": appkey, diff --git a/utils/link_sender_meta.py b/utils/link_sender_meta.py deleted file mode 100644 index 0bf9cef..0000000 --- a/utils/link_sender_meta.py +++ /dev/null @@ -1,179 +0,0 @@ -# utils/link_sender_meta.py -"""商家订单链接页:采集实际发消息者(浏览器/网络)信息,不涉及客服 JWT 或账号隐私。""" -import logging -import re - -import requests -from django.utils import timezone - -from config.views import IPUtils - -logger = logging.getLogger(__name__) - -_TRACKING_HEADER_KEYS = ( - 'HTTP_ACCEPT', - 'HTTP_ACCEPT_ENCODING', - 'HTTP_ACCEPT_LANGUAGE', - 'HTTP_REFERER', - 'HTTP_ORIGIN', - 'HTTP_HOST', - 'HTTP_USER_AGENT', - 'HTTP_X_FORWARDED_FOR', - 'HTTP_X_REAL_IP', - 'HTTP_VIA', - 'HTTP_CF_CONNECTING_IP', - 'HTTP_TRUE_CLIENT_IP', - 'HTTP_X_CLIENT_IP', - 'HTTP_SEC_CH_UA', - 'HTTP_SEC_CH_UA_MOBILE', - 'HTTP_SEC_CH_UA_PLATFORM', - 'HTTP_SEC_FETCH_SITE', - 'HTTP_SEC_FETCH_MODE', - 'HTTP_SEC_FETCH_DEST', - 'HTTP_DNT', - 'HTTP_CONNECTION', -) - - -def _lookup_ip_geo(ip): - """通过 ip-api.com 查询 IP 归属(免费,无需 key)""" - if not ip or not IPUtils.validate_ip(ip) or IPUtils.is_private_ip(ip): - return {'note': '内网或非公网 IP,无法定位'} - - try: - fields = ( - 'status,message,country,regionName,city,district,zip,lat,lon,' - 'timezone,isp,org,as,query,mobile,proxy,hosting' - ) - resp = requests.get( - f'http://ip-api.com/json/{ip}?lang=zh-CN&fields={fields}', - timeout=3, - ) - if resp.status_code != 200: - return {} - data = resp.json() - if data.get('status') != 'success': - return {'note': data.get('message') or '定位查询失败'} - return { - 'country': data.get('country'), - 'region': data.get('regionName'), - 'city': data.get('city'), - 'district': data.get('district'), - 'zip': data.get('zip'), - 'lat': data.get('lat'), - 'lon': data.get('lon'), - 'timezone': data.get('timezone'), - 'isp': data.get('isp'), - 'org': data.get('org'), - 'as': data.get('as'), - 'is_mobile_network': data.get('mobile'), - 'is_proxy': data.get('proxy'), - 'is_hosting': data.get('hosting'), - } - except Exception as e: - logger.warning('IP 归属查询失败: %s', e) - return {} - - -def _parse_ip_chain(xff): - if not xff: - return [] - return [p.strip() for p in xff.split(',') if p.strip()] - - -def _build_ip_geo_chain(primary_ip, xff): - """主 IP + 代理链上最多 2 个公网 IP 的归属""" - chain = [] - seen = set() - candidates = [primary_ip] + _parse_ip_chain(xff or '') - for ip in candidates: - if not ip or ip in seen: - continue - seen.add(ip) - if not IPUtils.validate_ip(ip): - continue - chain.append({'ip': ip, 'geo': _lookup_ip_geo(ip)}) - if len(chain) >= 2: - break - return chain - - -def _parse_user_agent(ua): - if not ua: - return {} - ua = ua[:500] - info = {'raw': ua} - if 'MicroMessenger' in ua: - info['browser'] = '微信内置浏览器' - elif 'Edg/' in ua: - info['browser'] = 'Microsoft Edge' - elif 'Chrome/' in ua and 'Safari/' in ua: - info['browser'] = 'Chrome' - elif 'Firefox/' in ua: - info['browser'] = 'Firefox' - elif 'Safari/' in ua: - info['browser'] = 'Safari' - else: - info['browser'] = '未知浏览器' - - if 'Windows NT 10' in ua: - info['os'] = 'Windows 10/11' - elif 'Windows' in ua: - info['os'] = 'Windows' - elif 'Mac OS X' in ua or 'Macintosh' in ua: - info['os'] = 'macOS' - elif 'Android' in ua: - m = re.search(r'Android\s+([\d.]+)', ua) - info['os'] = f"Android {m.group(1)}" if m else 'Android' - elif 'iPhone' in ua or 'iPad' in ua: - info['os'] = 'iOS' - elif 'Linux' in ua: - info['os'] = 'Linux' - else: - info['os'] = '未知系统' - - info['device_type'] = 'mobile' if ('Mobile' in ua or 'Android' in ua or 'iPhone' in ua) else 'desktop' - return info - - -def _collect_tracking_headers(request): - out = {} - for key in _TRACKING_HEADER_KEYS: - val = request.META.get(key) - if val: - name = key.replace('HTTP_', '').lower().replace('_', '-') - out[name] = str(val)[:800] - return out - - -def build_link_sender_meta(request, link_token='', client_info=None): - """ - 构建实际发消息者(浏览器访问者)的追踪元数据。 - 不读取 JWT / 客服账号,避免泄露客服隐私。 - client_info: 前端 H5 上报的设备指纹与环境信息。 - """ - ip = IPUtils.get_client_ip(request) - xff = request.META.get('HTTP_X_FORWARDED_FOR', '') or '' - cf_ip = request.META.get('HTTP_CF_CONNECTING_IP', '') or None - true_client_ip = request.META.get('HTTP_TRUE_CLIENT_IP', '') or None - - meta = { - 'send_time': timezone.now().strftime('%Y-%m-%d %H:%M:%S'), - 'ip': ip, - 'x_forwarded_for': xff or None, - 'x_real_ip': request.META.get('HTTP_X_REAL_IP', '') or None, - 'remote_addr': request.META.get('REMOTE_ADDR', '') or None, - 'cf_connecting_ip': cf_ip, - 'true_client_ip': true_client_ip, - 'ip_geo_chain': _build_ip_geo_chain(ip, xff), - 'geo': _lookup_ip_geo(ip), - 'user_agent': request.META.get('HTTP_USER_AGENT', ''), - 'browser': _parse_user_agent(request.META.get('HTTP_USER_AGENT', '')), - 'headers': _collect_tracking_headers(request), - 'link_token_prefix': (link_token[:8] + '...') if link_token else None, - } - - if isinstance(client_info, dict) and client_info: - meta['device'] = client_info - - return meta