Django+Nginx+uwsgi网站Channels+redis+daphne多人在线聊天实现粘贴上传图片

news2024/11/26 6:57:01

 在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.url
            return 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.url
        return 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">
        <div
            class="replyinput"
            contenteditable="true"
            id="chat-message-input"
            @focus="onFocusEditableDiv"
            >
        </div>
        <br>
        <div class="replyarea">
            &nbsp;&nbsp;<button id="upload-btn">上传本地图片</button>&nbsp;&nbsp;<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, return
                document.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内容,拷贝粘贴文字和图片混合内容粘贴显示都不太正常,单独复制粘贴图片没问题,文本内容粘贴时需要粘贴为纯文本。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2247694.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[ubuntu]编译共享内存读取出现read.c:(.text+0x1a): undefined reference to `shm_open‘问题解决方案

问题log /tmp/ccByifPx.o: In function main: read.c:(.text0x1a): undefined reference to shm_open read.c:(.text0xd9): undefined reference to shm_unlink collect2: error: ld returned 1 exit status 程序代码 #include <stdio.h> #include <stdlib.h> #…

Otter 安装流程

优质博文&#xff1a;IT-BLOG-CN 一、背景 随着公司的发展&#xff0c;订单库的数据目前已达到千万级别&#xff0c;需要进行分表分库&#xff0c;就需要对数据进行迁移&#xff0c;我们使用了otter&#xff0c;这里简单整理下&#xff0c;otter 的安装过程&#xff0c;希望对…

wsl2的Ubuntu18.04安装ros和anaconda

参考&#xff1a;超详细 WSL2 安装 ros 和 anaconda_wsl2安装anaconda-CSDN博客 一.安装ros 1. 更换系统源 输入 wget http://fishros.com/install -O fishros && . fishros 和上面的链接一样&#xff0c;依次输入5-2-1 2. 安装ros 输入 wget http://fishros.c…

鸿蒙NEXT开发案例:字数统计

【引言】 本文将通过一个具体的案例——“字数统计”组件&#xff0c;来探讨如何在鸿蒙NEXT框架下实现这一功能。此组件不仅能够统计用户输入文本中的汉字、中文标点、数字、以及英文字符的数量&#xff0c;还具有良好的用户界面设计&#xff0c;使用户能够直观地了解输入文本…

【经典】抽奖系统(HTML,CSS、JS)

目录 1、添加参与者 2、多次添加 3、点击抽奖 功能介绍&#xff1a; 使用方法&#xff1a; 完整代码&#xff1a; 一个简单但功能强大的抽奖系统的示例&#xff0c;用于在网页上实现抽奖。 1、添加参与者 2、多次添加 3、点击抽奖 功能介绍&#xff1a; 参与者添加&…

用树莓派Pico控制8×8 LED点阵屏:深入解析C++核心知识与动态显示实现

88 LED点阵屏是一种直观的硬件显示工具,广泛应用于嵌入式开发中。本项目结合树莓派Pico和HT16K33驱动芯片,通过C++编程实现动态图案和文字的显示功能。本文将全面解析项目中的C++核心知识点,帮助读者深入理解C++在硬件编程中的实际应用。 一、项目背景与硬件简介 1. 项目目…

什么是 WPF 中的依赖属性?有什么作用?

依赖属性&#xff08;Dependency Property&#xff09;是 WPF 的一个核心概念&#xff0c;它为传统的 .NET 属性提供了增强功能&#xff0c;支持绑定、样式、动画和默认值等功能。通过依赖属性&#xff0c;WPF 提供了一种灵活的数据驱动的方式来处理 UI 属性。 1. 什么是依赖属…

视频分析设备平台EasyCVR视频设备轨迹回放平台与应急布控球的视频监控方案

在现代社会&#xff0c;随着城市化进程的加快和信息技术的不断进步&#xff0c;对于公共安全、交通管理、城市管理以及环境保护等领域的监控需求日益增长。应急布控球与EasyCVR视频监控方案的结合&#xff0c;正是为了满足这些领域对实时监控和快速响应的需求。这一组合利用最新…

MySQL原理简介—12.MySQL主从同步

大纲 1.异步复制为MySQL搭建一套主从复制架构 2.半同步复制为MySQL搭建一套主从复制架构 3.GTID为MySQL搭建一套主从复制架构 4.并行复制降低主从同步延迟或强制读主库 1.异步复制为MySQL搭建一套主从复制架构 (1)MySQL主从复制的原理 (2)搭建主从复制架构的配置 (1)MySQ…

Node报错:npm error code ETIMEDOUT

1、报错详细信息 npm error code ETIMEDOUT npm error syscall connect npm error errno ETIMEDOUT npm error network request to https://registry.npmjs.org/express failed, reason: connect ETIMEDOUT 104.16.1.35:443 npm error network This is a problem related to ne…

一篇文章了解Linux

目录 一&#xff1a;命令 1 ls命令作用 2 目录切换命令&#xff08;cd/pwd&#xff09; &#xff08;1)cd切换工作目录命令 3 相对路径、绝对路径和特殊路径 (1)相对路径和绝对路径的概念和写法 (2)几种特殊路径的表示符 (3)练习题&#xff1a; 4 创建目录命令&#x…

用Matlab和SIMULINK实现DPCM仿真和双边带调幅系统仿真

1、使用SIMULINK或Matlab实现DPCM仿真 1.1 DPCM原理 差分脉冲编码调制&#xff0c;简称DPCM&#xff0c;主要用于将模拟信号转换为数字信号&#xff0c;同时减少数据的冗余度以实现数据压缩。在DPCM中&#xff0c;信号的每个抽样值不是独立编码的&#xff0c;而是通过预测前一…

BERT的工作原理

BERT的工作原理 BERT的工作原理&#xff1a; Transformer的编码器是双向的&#xff0c;它可以从两个方向读取一个句子。因此&#xff0c;BERT由Transformer获得双向编码器特征。 我们把句子A&#xff08;He got bit by Python&#xff09;送入Transformer的编码器&#xff0c…

5.STM32之通信接口《精讲》之IIC通信---软件IIC与外设MPU6050通信《深入浅出》面试必备

上一节&#xff0c;我们完成对IIC通信的时序以及IIC的通信的讲解和代码实现&#xff0c;接下来&#xff0c;我们正式进入&#xff0c;利用上一节软件实现的IIC通信协议来对外设MPU6050进行读写操作。(本节IIC代码在上节) 本节&#xff0c;目的很明确&#xff0c;就是利用软件I…

解决k8s拉取私有镜像401 Unauthorized 问题

拉取镜像时未指定账户和密码通常是因为需要访问的镜像仓库启用了认证&#xff0c;但 Kubernetes 默认配置中未提供访问凭据。要解决此问题&#xff0c;可以按照以下步骤配置镜像仓库的认证信息&#xff1a; 1. 创建 Kubernetes Secret 为镜像仓库配置访问凭据&#xff0c;使用…

【Linux课程学习】:环境变量:HOME,su与su - 的区别,让程序在哪些用户下能运行的原理,环境变量具有全局性的原因?

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;Linux课程学习 &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 目录 HOME环境变量&#xff1a; PWD环境变量&#…

不只是请求和响应:使用Fiddler抓包HTTP协议全指南(上)

欢迎浏览高耳机的博客 希望我们彼此都有更好的收获 感谢三连支持! &#x1f649;你是一名侦探 ! 正在追踪一条条数字化的线索。从简单的网页浏览到复杂的在线交易&#xff0c;每一次点击和滑动背后都隐藏着复杂的数据交换。每一个HTTP请求和响应都像是现场留下的指纹&#xf…

代码纪元——源神重塑无序

简介 源神&#xff0c;真名为张晨斌&#xff0c;原为代码宇宙创世四神之一。代码宇宙在创造之初时空无一物&#xff0c;只有复杂且繁琐的底层代码&#xff0c;智慧神灵每日都困在诸如脚本等复杂的底层框架之中&#xff0c;源神面对这种局面非常不满意&#xff0c;于是源神通过大…

Docker pull镜像拉取失败

因为一些原因&#xff0c;很多镜像仓库拉取镜像失败&#xff0c;所以需要更换不同的镜像&#xff0c;这是2024/11/25测试可用的仓库。 标题1、 更换镜像仓库的地址&#xff0c;编辑daemon.json文件 vi /etc/docker/daemon.json标题2、然后将下面的镜像源放进去或替换掉都可以…

Vue3+SpringBoot3+Sa-Token+Redis+mysql8通用权限系统

sa-token支持分布式token 前后端代码&#xff0c;地球号: bright12389