在Django+Nginx+uwsgi网站Channels+redis+daphne多人在线的基础上(详见Django+Nginx+uwsgi网站使用Channels+redis+daphne实现简单的多人在线聊天及消息存储功能-CSDN博客),实现在输入框粘贴或打开本地图片,上传到网站后返回图片路径,以链接的形式将图片插入到输入框显示,并实现异步发送消息。具体效果如下图所示:
一、实现图片上传
实现图片上传客户端和服务器两边都要配置。
1. 客户端使用fetch实现图片上传
使用嵌入页面的javascript脚本实现fetch上传图片,主要代码如下:
const csrftoken = document.querySelector('[name="csrfmiddlewaretoken"]').value;fetch('/chatjson/upload_image/',{method: 'POST',headers: {'X-CSRFToken': csrftoken},body: imgformData}).then(response => response.json()).then(data => {if (data.image_url) {//console.log('data image path::',data.image_url);const img = document.createElement('img');img.src = data.image_url;editor.appendChild(img);img.style.width = '300px';img.style.height = 'auto';img.style.objectFit = 'contain';img.style.float = 'none';} else {console.error('Error uploading image:', data.error);}}).catch((error) => {console.error('Error:', error);alert('Error uploading the image.');});
2. 服务器端配置
(1) urls.py设置
客户端fetch的路径为'/chatjson/upload_image/',需要在urls.py中配置路径解析,包括聊天页面的路径解析
from myapp import views as channelsview
urlpatterns = [
....path('chatexp/<str:room_name>/', channelsview.chatexp, name='chatexp'),path('chatjson/upload_image/', channelsview.upload_image_json, name='upload_json'),
]
(2) 视图设置 myapp/views.py
包括聊天页面视图响应函数chatexp和文件上传响应upload_image_json
from django.contrib.auth.decorators import login_required@login_required(login_url='/login/')
def chatexp(request,room_name):username = request.session.get('username','游客')msgs = ChatMessage.objects.filter(room=room_name).order_by('-create_time')[0:20]if request.method == 'POST':form = chatimgsForm(request.POST, request.FILES)if form.is_valid():image = form.save()#图片路径image_path = image.image.urlreturn render(request,"channels/chattingexp.html",{'room_name':room_name,'form':form, 'image_path':image_path, 'username':username, 'msgs':msgs})form = chatimgsForm()return render(request,"channels/chattingexp.html",{'room_name':room_name,'form':form, 'image_path':'未上传', 'username':username, 'msgs':msgs})from django.views.decorators.http import require_POST
from django.views.decorators.csrf import csrf_exempt
from django.http import JsonResponse
from django.conf import settings
import os@require_POST
@csrf_exempt
def upload_image_json(request):image_file = request.FILES['image']if image_file :upimg = chatimgs(image=image_file)upimg.save()#返回图片的绝对路径/home/...#image_path = upimg.image.path# 返回图片的相对路径/media/...image_path = upimg.image.urlreturn JsonResponse({'image_url': image_path})else:return JsonResponse({'error': 'No image received!!'}, status=400)
二、客户端配置
1. 聊天页面设置
chatexp视图函数调用聊天页面chattingexp.html,聊天页面输入框由可编辑的div实现,页面内javascript脚本监听输入框的粘贴事件,将其中的图片上传,返回路径,将图片以img元素的形式插入到输入框,字符串转换成文本插入。脚本还实现了打开本地图片文件,同样上传后返回路径,将图片以img元素的形式插入到输入框。然后发送消息,消息文本通过channels异步传输,因文本只有图片链接,提高了传输效率。主要代码如下:
{% extends "newdesign/newbase.html" %}{# 自定义过滤器startswith #}{% load django_bootstrap5 %}{% block mytitle %}<title>{{room_name}}号聊天室</title><style>.chat-window {max-width: 900px;height: 500px;overflow-y: scroll; /* 添加垂直滚动条 */margin: auto;background-color: #f1f1f1;border: 2px solid #09e3f7;border-radius: 5px;padding: 10px;}.chat-message {clear: both;overflow: hidden;margin-bottom: 10px;text-align: left;}.chat-message .message-content {border-radius: 5px;padding: 8px;max-width: 500px;float: left;clear: both;}.chat-message.right .message-content {background-color: #428bca;color: white;float: right;width:420px;}.chat-message.right .user-content {background-color: #f7e91d;border-radius:4px;color: black;float: right;width: auto;text-align: right;padding-left:10px;padding-right:10px;}.chat-message.left .message-content {background-color: #2ef3be;border-color: #ddd;float:left;width:420px;}.chat-message.left .user-content {background-color: #f7e91d;border-radius:4px;border-color: #ddd;float: left;width: auto;text-align: left;padding-left:8px;padding-right:8px;}.inputarea {display:flex;flex-direction: column;justify-content: center;align-items: center;width:900px;margin: 0 auto;}.replyinput {display: inline-block;width: 900px;min-height:120px;background-color: rgb(169, 228, 250);border:2px solid #09e3f7;border-radius: 10px;padding: 10px;font-size: 14px;text-align: left;}.replyarea {width: 900px;height:50px;margin:0 auto;}.sendImg-btn {float:left;border: 0px;background-color: transparent;}.reply-btn {float:right;}</style>{% endblock %}{% block maincontent %} <div class="container"><div id="chat-record" class="chat-window">{% for m in msgs reversed %}{% if m.username == request.user.username %}<div class="chat-message right"><div class="user-content">{{m.username}}</div><div class="message-content"><span>{{m.content|safe}}</span></div></div><br>{% else %}<div class="chat-message left"><div class="user-content">{{m.username}}</div><div class="message-content"><span>{{m.content|safe}}</span></div></div><br>{% endif %}{% endfor %}</div></div><br><form method='post' enctype="multipart/form-data"></form>{% csrf_token %}<div class="inputarea"><divclass="replyinput"contenteditable="true"id="chat-message-input"@focus="onFocusEditableDiv"></div><br><div class="replyarea"> <button id="upload-btn">上传本地图片</button> <input type="file" id="file-input" accept="image/*"/><button class="reply-btn" id="chat-message-submit" type="primary" style="height:40px;background-color: #0d4de1;color:white;border-radius: 4px;">发送消息</button></div></div></form>{{ room_name|json_script:"room-name" }}{{ username|json_script:"username" }}<script>const editor = document.getElementById('chat-message-input'); editor.addEventListener('paste', function(event) {// 阻止默认粘贴操作event.preventDefault();const clipboardData = (event.clipboardData||window.clipboardData);let items = clipboardData.items;const csrftoken = document.querySelector('[name="csrfmiddlewaretoken"]').value;for (const item of items) {if (item.kind === 'string') {item.getAsString((text) => {const regex = /<img src="(.*?)"/;const match = text.match(regex);if (match) {//document.execCommand('insertText', false, "link:<img src='"+match[1]+"'/>");//网页图片复制粘贴除了图片还带有图片链接,如果识别img链接插入图片会出现图片插入两次的问题//const img = document.createElement('img');//img.src = match[1];//editor.appendChild(img);//img.style.width = '300px';//img.style.height = 'auto';//img.style.objectFit = 'contain';//img.style.float = 'none';} else {document.execCommand('insertText', false, text);}})} else if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {var imgfile = item.getAsFile();const imgformData = new FormData();imgformData.append('image',imgfile);imgformData.append('csrfmiddlewaretoken', csrftoken);fetch('/chatjson/upload_image/',{method: 'POST',headers: {'X-CSRFToken': csrftoken},body: imgformData}).then(response => response.json()).then(data => {if (data.image_url) {//console.log('data image path::',data.image_url);const img = document.createElement('img');img.src = data.image_url;editor.appendChild(img);img.style.width = '300px';img.style.height = 'auto';img.style.objectFit = 'contain';img.style.float = 'none';} else {console.error('Error uploading image:', data.error);}}).catch((error) => {console.error('Error:', error);alert('Error uploading the image.');});}}})window.onload = function() {var scrollableDiv = document.getElementById('chat-record');// 设置scrollTop使得滚动条向下翻scrollableDiv.scrollTop = scrollableDiv.scrollHeight;};const roomName = JSON.parse(document.getElementById('room-name').textContent);const username = JSON.parse(document.getElementById('username').textContent);const chatSocket = new WebSocket('wss://abc.com/ws/chat/' + roomName + '/');chatSocket.onmessage = function(e) {const data = JSON.parse(e.data);//data为收到的后端发出来的数据//console.log(data);if (data['message']) {if(data['username'] == username){document.querySelector('#chat-record').innerHTML += ('<div class="chat-message right"><div class="user-content">' + data['username'] + '</div><div class="message-content"><span>' +data['message'] + '</span></div></div><br>');}else{document.querySelector('#chat-record').innerHTML += ('<div class="chat-message left"><div class="user-content">' + data['username'] + '</div><div class="message-content"><span>' + data['message'] + '</span></div></div><br>');}} else {alert('消息为空!')}var scrollableDiv = document.getElementById('chat-record');// 设置scrollTop使得滚动条向下翻scrollableDiv.scrollTop = scrollableDiv.scrollHeight;};chatSocket.onclose = function(e) {console.error('聊天端口非正常关闭!');};document.querySelector('#chat-message-input').focus();document.querySelector('#chat-message-input').onkeyup = function(e) {if (e.keyCode === 13) { // enter, returndocument.querySelector('#chat-message-submit').click();}};document.querySelector('#chat-message-submit').onclick = function(e) {const messageDivDom = document.querySelector('#chat-message-input');const message = messageDivDom.innerHTML;chatSocket.send(JSON.stringify({'message': message,'username':username}));messageDivDom.innerHTML = '';};//打开并上传本地文件document.getElementById('upload-btn').addEventListener('click', function () {const editor = document.getElementById('chat-message-input'); const fileInput = document.getElementById('file-input');const csrftoken = document.querySelector('[name="csrfmiddlewaretoken"]').value;const file = fileInput.files[0];const formData = new FormData();formData.append('csrfmiddlewaretoken', csrftoken);formData.append('image', file);fetch('/chatjson/upload_image/', {method: 'POST',headers: {'X-CSRFToken': csrftoken},body: formData}).then(response => response.json()).then(data => {if (data.image_url) {//console.log('data image path::',data.image_url);const img = document.createElement('img');img.src = data.image_url;editor.appendChild(img);img.style.width = '300px';img.style.height = 'auto';img.style.objectFit = 'contain';img.style.float = 'none';} else {console.error('Error uploading image:', data.error);}}).catch((error) => {console.error('Error:', error);alert('Error uploading the image.');});});</script>
{% endblock %}
2. 存在的问题
无法正确处理html和word内容,拷贝粘贴文字和图片混合内容粘贴显示都不太正常,单独复制粘贴图片没问题,文本内容粘贴时需要粘贴为纯文本。