fix: H5 发图走 OSS + 后端 REST 代发兜底

This commit is contained in:
XingQue committed 2026-08-05 00:45:37 +08:00
1 parent 7e0796e57d
commit ed41afbbd7
3 files changed
+174 -1

No files matched your search

+2 -1
View File
@@ -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='聊天订单状态'),
+2
View File
@@ -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",
+170
View File
@@ -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):
"""验证用户身份、账号状态、以及是否属于该订单"""