fix: H5 发图走 OSS + 后端 REST 代发兜底
This commit is contained in:
1 parent
7e0796e57d
commit
ed41afbbd7
3 files changed
+174
-1
No files matched your search
+2
-1
@@ -11,7 +11,7 @@ from .views import CreateOrderView, WechatPayNotifyView, AlipayPayNotifyView, al
|
||||
ShangjiaLianjieLiuYanZhuanFaView, DingdanXiangqingView2, JiedanView2, ZxsjghdsView, \
|
||||
ShangjiaFakuanApplyView, ShangjiaFakuaiXiugaiView, ShangjiaShujuTongjiView, LianTongDuiHuaZhunBeiView, \
|
||||
LianTongPeiDuiDingDanLieBiaoView, LianTongDingDanZhuangTaiView, LianTongDingDanZhuangTaiPiLiangView, \
|
||||
ZhiDingChaxunView, ZhiDingHuiFuView
|
||||
ZhiDingChaxunView, ZhiDingHuiFuView, H5ChatSendImageView
|
||||
from jituan.services.fubei_notify import FubeiPayNotifyView
|
||||
from jituan.views_order_deal import MiniappMyDealStatView
|
||||
|
||||
@@ -80,6 +80,7 @@ urlpatterns = [
|
||||
path('dsxiugaidd', DashouXiugaiView.as_view(), name='打手修改订单详情'),
|
||||
|
||||
path('kptxxfs', DaiLiQunLiaoXiaoXiView.as_view(), name='前端聊天转发'), # 群聊消息代理发送
|
||||
path('h5ftp', H5ChatSendImageView.as_view(), name='H5发图代发'),
|
||||
path('ltdhzb', LianTongDuiHuaZhunBeiView.as_view(), name='订单群聊准备'),
|
||||
path('ltpddd', LianTongPeiDuiDingDanLieBiaoView.as_view(), name='配对订单列表'),
|
||||
path('ltddzt', LianTongDingDanZhuangTaiView.as_view(), name='聊天订单状态'),
|
||||
|
||||
@@ -36,6 +36,7 @@ from .chat import (
|
||||
LianTongPeiDuiDingDanLieBiaoView,
|
||||
LianTongDingDanZhuangTaiView,
|
||||
LianTongDingDanZhuangTaiPiLiangView,
|
||||
H5ChatSendImageView,
|
||||
)
|
||||
from .dashou import (
|
||||
DashouDingdanHuoquView,
|
||||
@@ -80,6 +81,7 @@ __all__ = [
|
||||
"DaiLiQunLiaoXiaoXiView", "ShangjiaLianjieLiuYanZhuanFaView",
|
||||
"LianTongDuiHuaZhunBeiView", "LianTongPeiDuiDingDanLieBiaoView",
|
||||
"LianTongDingDanZhuangTaiView", "LianTongDingDanZhuangTaiPiLiangView",
|
||||
"H5ChatSendImageView",
|
||||
"DashouDingdanHuoquView", "ShangpinLeixingHuoquView", "QiangdanView",
|
||||
"DashouDingdanHuoquView1", "DashouCOSZhengshuView", "ShangjiaCOSZhengshuView",
|
||||
"DashouTijiaoView", "DashouDingdanXiangqingView", "DashouHuoquLeixingView",
|
||||
|
||||
@@ -189,6 +189,176 @@ def send_group_message(
|
||||
return False
|
||||
|
||||
|
||||
def send_im_message(appkey, secret, sender_id, sender_name, sender_avatar,
|
||||
to_type, to_id, to_data, msg_type, payload, notification=None):
|
||||
"""
|
||||
通过 GoEasy REST 发送私聊/群聊/客服消息。
|
||||
msg_type: text / image / 自定义类型
|
||||
"""
|
||||
url = getattr(settings, 'GOEASY_REST_URL', None) or 'https://rest-hz.goeasy.io/v2/im/message'
|
||||
body = {
|
||||
"appkey": appkey,
|
||||
"senderId": sender_id,
|
||||
"senderData": {"avatar": sender_avatar or '', "name": sender_name or ''},
|
||||
"to": {"type": to_type, "id": to_id, "data": to_data or {}},
|
||||
"type": msg_type,
|
||||
"payload": payload,
|
||||
}
|
||||
if notification:
|
||||
body["notification"] = notification
|
||||
headers = {"Content-Type": "application/json"}
|
||||
if secret:
|
||||
headers["Authorization"] = f"Bearer {secret}"
|
||||
|
||||
last_text = 'send failed'
|
||||
for attempt in range(3):
|
||||
try:
|
||||
r = requests.post(url, headers=headers, json=body, timeout=10)
|
||||
if r.status_code == 200:
|
||||
try:
|
||||
data = r.json()
|
||||
except Exception:
|
||||
data = {}
|
||||
if isinstance(data, dict) and data.get('code') not in (None, 200, 0, '200', '0'):
|
||||
logger.warning(
|
||||
"GoEasy REST 业务失败 (尝试 %s/3):%s", attempt + 1, r.text
|
||||
)
|
||||
last_text = r.text
|
||||
else:
|
||||
logger.info(
|
||||
"IM 消息发送成功:sender=%s, to=%s/%s, type=%s",
|
||||
sender_id, to_type, to_id, msg_type,
|
||||
)
|
||||
return True, data if isinstance(data, dict) else {}
|
||||
logger.warning(
|
||||
"IM 消息发送失败 (尝试 %s/3):状态码 %s, 响应 %s",
|
||||
attempt + 1, r.status_code, r.text,
|
||||
)
|
||||
last_text = r.text
|
||||
except Exception as e:
|
||||
last_text = str(e)
|
||||
logger.warning("IM 消息发送异常 (尝试 %s/3):%s", attempt + 1, e)
|
||||
if attempt < 2:
|
||||
time.sleep(1)
|
||||
return False, {'content': last_text}
|
||||
|
||||
|
||||
class H5ChatSendImageView(APIView):
|
||||
"""
|
||||
H5 发图兜底:图片已上传到自有 OSS 后,由服务端 REST 代发 type=image。
|
||||
路径:POST /dingdan/h5ftp
|
||||
"""
|
||||
permission_classes = [permissions.IsAuthenticated]
|
||||
|
||||
def post(self, request):
|
||||
to_type = str(request.data.get('toType') or '').strip().lower()
|
||||
to_id = str(request.data.get('toId') or '').strip()
|
||||
to_data = request.data.get('toData') or {}
|
||||
sender_id = str(request.data.get('senderId') or '').strip()
|
||||
sender_name = str(request.data.get('senderName') or '').strip()
|
||||
sender_avatar = str(request.data.get('senderAvatar') or '').strip()
|
||||
image_url = str(request.data.get('imageUrl') or '').strip()
|
||||
width = request.data.get('width') or 200
|
||||
height = request.data.get('height') or 200
|
||||
size = request.data.get('size') or 1
|
||||
name = str(request.data.get('name') or 'chat.jpg').strip() or 'chat.jpg'
|
||||
|
||||
if to_type not in ('private', 'group', 'cs'):
|
||||
return Response({'code': 400, 'msg': 'toType 无效'})
|
||||
if not to_id or not sender_id or not image_url:
|
||||
return Response({'code': 400, 'msg': '缺少 toId/senderId/imageUrl'})
|
||||
if not (image_url.startswith('https://') or image_url.startswith('http://')):
|
||||
return Response({'code': 400, 'msg': 'imageUrl 必须是 http(s) 地址'})
|
||||
|
||||
uid = str(getattr(request.user, 'UserUID', '') or '')
|
||||
if not uid or uid not in sender_id:
|
||||
return Response({'code': 403, 'msg': '发送者身份不匹配'})
|
||||
|
||||
if not isinstance(to_data, dict):
|
||||
try:
|
||||
to_data = json.loads(to_data) if isinstance(to_data, str) else {}
|
||||
except Exception:
|
||||
to_data = {}
|
||||
|
||||
appkey = get_self_goeasy_appkey()
|
||||
secret = get_self_goeasy_secret()
|
||||
if not appkey:
|
||||
return Response({'code': 500, 'msg': '系统配置错误(APPKEY)'})
|
||||
|
||||
try:
|
||||
width = int(width) or 200
|
||||
height = int(height) or 200
|
||||
size = int(size) or 1
|
||||
except (TypeError, ValueError):
|
||||
width, height, size = 200, 200, 1
|
||||
|
||||
payload = {
|
||||
'url': image_url,
|
||||
'thumbnail': image_url,
|
||||
'name': name,
|
||||
'contentType': 'image/jpeg',
|
||||
'width': width,
|
||||
'height': height,
|
||||
'size': size,
|
||||
}
|
||||
notification = {'title': '新消息', 'body': '[图片]'}
|
||||
|
||||
ok, data = send_im_message(
|
||||
appkey=appkey,
|
||||
secret=secret,
|
||||
sender_id=sender_id,
|
||||
sender_name=sender_name,
|
||||
sender_avatar=full_avatar(sender_avatar) if sender_avatar else '',
|
||||
to_type=to_type,
|
||||
to_id=to_id,
|
||||
to_data=to_data,
|
||||
msg_type='image',
|
||||
payload=payload,
|
||||
notification=notification,
|
||||
)
|
||||
if not ok:
|
||||
# GoEasy REST 文档写不支持 image;再试自定义 picture(H5 可 normalize,小程序仍只认 image)
|
||||
ok2, data2 = send_im_message(
|
||||
appkey=appkey,
|
||||
secret=secret,
|
||||
sender_id=sender_id,
|
||||
sender_name=sender_name,
|
||||
sender_avatar=full_avatar(sender_avatar) if sender_avatar else '',
|
||||
to_type=to_type,
|
||||
to_id=to_id,
|
||||
to_data=to_data,
|
||||
msg_type='picture',
|
||||
payload=payload,
|
||||
notification=notification,
|
||||
)
|
||||
if not ok2:
|
||||
err = ''
|
||||
if isinstance(data, dict):
|
||||
err = str(data.get('content') or data.get('msg') or '')
|
||||
if isinstance(data2, dict) and not err:
|
||||
err = str(data2.get('content') or data2.get('msg') or '')
|
||||
return Response({'code': 500, 'msg': err or '图片代发失败'})
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': '发送成功',
|
||||
'data': {
|
||||
'url': image_url,
|
||||
'type': 'picture',
|
||||
'goeasy': data2,
|
||||
},
|
||||
})
|
||||
|
||||
return Response({
|
||||
'code': 200,
|
||||
'msg': '发送成功',
|
||||
'data': {
|
||||
'url': image_url,
|
||||
'type': 'image',
|
||||
'goeasy': data,
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
# ----------------- 权限校验 -----------------
|
||||
def check_user_permission(identity_type, uid, order):
|
||||
"""验证用户身份、账号状态、以及是否属于该订单"""
|
||||
|
||||
Reference in new issue
Block a user