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

 在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异步传输,因文本只有图片链接,提高了传输效率。主要代码如下:

    <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链接插入图片会出现图片插入两次的问题} 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>
2. 网页内容的粘贴处理

在Windows下从网页复制粘贴涉及剪切和粘贴 HTML 文档的片段。Windows采用 CF_HTML 剪贴板格式,其将原始 HTML 文本及其上下文(即外部 HTML)片段作为 ASCII 存储在剪贴板上。 这允许应用程序检查 HTML 片段的上下文,该片段由前面所有的周围标记组成,以便可以使用其属性来记录 HTML 片段的周围标记。 CF_HTML 剪贴板的常规布局或语法,如下所示:

<cf-html>                ::= <description-header> <context>
<context>                ::= [<preceding-context>] <fragment> [<trailing-context>]<description-header>     ::= "Version:" <version> <br> ( <header-offset-keyword> ":" <header-offset-value> <br> )*
<header-offset-keyword>  ::= "StartHTML" | "EndHTML" | "StartFragment" | "EndFragment" | "StartSelection" | "EndSelection"
<header-offset-value>    ::= { Base 10 (decimal) integer string with optional _multiple_ leading zero digits (see "Offset syntax" below) }
<version>                ::= "0.9" | "1.0"
<fragment>               ::= <fragment-start-comment> <fragment-text> <fragment-end-comment>
<fragment-start-comment> ::= "<!--StartFragment -->"
<fragment-end-comment>   ::= "<!--EndFragment -->"
<preceding-context>      ::= { Arbitrary HTML }
<trailing-context>       ::= { Arbitrary HTML }
<fragment-text>          ::= { Arbitrary HTML }
<br>                     ::= "\r" | "\n" | "\r\n"

所以从网页单独粘贴一张图片时,会带入上下文,譬如:

<html>
<body>
<!--StartFragment--><img src="https://pics6.baidu.com/feed/a2cc7cd98d1001e9472d4193277785e255e797a5.jpeg@f_auto?token=475ae4ac49d50e247ac05e958799fc88"/><!--EndFragment-->
</body>
</html>

 如果既上传照片,又解析其中的<img>链接,会出现从网页上拷贝的文字图片在粘贴时,图片会被识别两次。所以对网页拷贝的内容,解决办法有两种:

(1)将图片以链接形式插入到文本中

chattingexp.html中Javascript脚本的主要代码如下:

    <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) {let htmlimglink = false;if(item.kind === 'string'&& item.type === 'text/html') {item.getAsString((text) => {const regex = /<img src="(.*?)"/;const match = text.match(regex);if (match) {                //网页图片以链接的形式存在,不上传服务器//document.execCommand('insertText', false, "link:<img src='"+match[1]+"'/>");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';htmlimglink = true;} else {//console.log(text);//新建一个divvar divElement = document.createElement( "div" );divElement.innerHTML = text;//获取文本内容//如果divElement是null或undefined,那么返回为""(一个空字符串)。//如果divElement非null且存在textContent或innerText属性,那么将会返回该属性的值。如果两者都不存在,将会返回""。document.execCommand('insertText', false, divElement.textContent || divElement.innerText || "");}})} else if (item.kind === 'string'&& item.type === 'text/plain'){item.getAsString((text) => {document.execCommand('insertText', false, text);})}else if(htmimglink=false && 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.');});}}})//本地文件上传按钮事件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>

(2)使用fetch获取图片Blob数据后上传到服务器

在前面的基础上,得到图片的链接后,利用正则表达式得到图片名和图片的格式,然后fetch到图片Blob数据后,使用FormData创建一个新的File对象,再使用fetch上传到服务器,返回路径,插入到输入框中。使用效果如下:

3. 聊天页面设计及功能实现

至此,多人在线聊天页面基本实现了粘贴图片和文字混合内容的功能。汇总之前的代码,chattingexp.html的主要内容如下: 

{% 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">&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;var htmlimglink = false;for (const item of items) {if(item.kind === 'string'&& item.type === 'text/html'){//如果是网页内容,因粘贴板格式带有图片链接,直接根据链接下载图片上传,不需要再次上传照片文件htmlimglink = true;}};for (const item of items) {if(item.kind === 'string'&& item.type === 'text/html') {item.getAsString((text) => {const regex = /<img src="(.*?)"/g;let matches = '';matches = text.matchAll(regex);if (matches) {   for (const match of matches) {//网页图片以链接的形式存在,不上传服务器//match[0]为整个匹配组,match[1]为第一个捕获组//document.execCommand('insertHTML', false, "<img style='width:300px;height:auto;object-fit:contain;float:none;' src='"+match[1]+"'/>");fetch(match[1]).then(response =>{if (!response.ok) {throw new Error('Network response was not ok ' + response.statusText);}return response.blob(); // 转换响应为Blob对象}).then(blob => {const fileNameextra = match[1].split('/').pop(); // URL的最后一部分包含文件名和查询参数const regex = /^(.*)\.(png|jpeg|jpg|gif|bmp|webp|svg|tiff|avif)(?:\?|\@|#|$)/i ;let filematches = '';filematches = fileNameextra.match(regex);if(filematches){const fileName = filematches[1]+'.'+ filematches[2];const contentType = 'image/' + filematches[2];//console.log("fileName:::",fileName);const imgformData = new FormData();imgformData.append('image',new File([blob], fileName, { type: contentType }));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);document.execCommand('insertHTML', false, "<img style='width:300px;height:auto;object-fit:contain;float:none;' src='"+data.image_url+"'/>");} else {console.error('Error uploading image:', data.error);}}).catch((error) => {console.error('Error:', error);alert('Error uploading the image.');});}else{throw new Error('图片格式不正确!');}}).catch(error => console.error('Error fetching or processing the image:', error));};} else {//新建一个divvar divElement = document.createElement( "div" );divElement.innerHTML = text;//获取文本内容//如果divElement是null或undefined,那么返回为""(一个空字符串)。//如果divElement非null且存在textContent或innerText属性,那么将会返回该属性的值。如果两者都不存在,将会返回""。document.execCommand('insertText', false, divElement.textContent || divElement.innerText || "");}})} else if (item.kind === 'string'&& item.type === 'text/plain'){item.getAsString((text) => {document.execCommand('insertText', false, text);})}else if(htmlimglink === false && 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 %}
4. 存在的问题

无法正确处理word内容,拷贝粘贴文字和图片混合内容粘贴显示不太正常,单独复制粘贴图片没问题,文本内容粘贴时需要粘贴为纯文本。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

全新AI模型家族登场:完全可复现的开源语言模型OLMo 2

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Java进阶七-网络编程,反射

一 网络编程 网络编程&#xff1a;在网络通信的协议下&#xff0c;不同计算机上运行的程序&#xff0c;进行的数据传输。 一 基础知识 1 常见的软件架构 CS&#xff1a;通过客户端访问服务器。 1&#xff1a;画面可以做的非常好&#xff0c;用户体验好。2&#xff1a;需要…

【C++进阶篇】像传承家族宝藏一样理解C++继承

文章目录 须知 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

Swin-T图像论文复现

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

扫雷-完整源码(C语言实现)

云边有个稻草人-CSDN博客 在学完C语言函数之后&#xff0c;我们就有能力去实现简易版扫雷游戏了&#xff08;成就感满满&#xff09;&#xff0c;下面是扫雷游戏的源码&#xff0c;快试一试效果如何吧&#xff01; 在test.c里面进行扫雷游戏的测试&#xff0c;game.h和game.c…

Spring Web MVC(详解中)

文章目录 Spring MVC&#xff08;中&#xff09;RESTFul风格设计RESTFul风格概述RESTFul风格特点RESTFul风格设计规范RESTFul风格好处RESTFul风格实战需求分析RESTFul风格接口设计后台接口实现 基于RESTFul风格练习&#xff08;前后端分离模式&#xff09;案例功能和接口分析功…

输入json 达到预览效果

下载 npm i vue-json-pretty2.4.0 <template><div class"newBranchesDialog"><t-base-dialogv-if"addDialogShow"title"Json数据配置"closeDialog"closeDialog":dialogVisible"addDialogShow":center"…

STL算法之基本算法<stl_algobase.h>

STL标准规格中没哟区分基本算法或复杂算法&#xff0c;然后SGI却把常用的一些算法定义于<stl_algobase.h>之中&#xff0c;其他算法定义于<stl_algo.h>之中。以下一一列举这些基本算法。 目录 运用实例 equal,fill,fill_n,iter_swap, lexicographical_compare,m…

dns 服务器简单介绍

dns 服务器分类&#xff1a; 根域名服务器顶级域名服务器权威域名服务器本地域名服务器 dns 的查询过程 国内优秀公共域名 腾讯&#xff1a;DNSPod-免费智能DNS解析服务商-电信_网通_教育网,智能DNS-烟台帝思普网络科技有限公司 119.29.29.29 和 182.254.118.118 阿里&#xf…

AI智算-正式上架GPU资源监控概览 Grafana Dashboard

下载链接 https://grafana.com/grafana/dashboards/22424-ai-gpu-20241127/

CAN详解

CAN简介 • CAN 总线&#xff08; Controller Area Network Bus &#xff09;控制器局域网总线 • CAN 总线是由 BOSCH 公司开发的一种简洁易用、传输速度快、易扩展、可靠性高的串行通信总线&#xff0c;广泛应用于汽车、嵌入式、工业控制等领域 • CAN 总线特征&#xff1a; …

透视投影(Perspective projection)与等距圆柱投影(Equirectangular projection)

一、透视投影 1.方法概述 Perspective projection&#xff08;透视投影&#xff09;是一种模拟人眼观察三维空间物体时的视觉效果的投影方法。它通过模拟观察者从一个特定视点观察三维场景的方式来创建二维图像。在透视投影中&#xff0c;远处的物体看起来比近处的物体小&…

(四)Spring Boot学习——整合修改使用druid连接池

我的是使用springboot3的&#xff0c;对应的有整合的druid-spring-boot-3-starter的jar实现对springboot3的兼容。 <!--******************数据库相关配置************************--> <!-- 1.配置数据库相关的jar包,连接池使用druids上&#xff0c;并引入整合spring…

think php处理 异步 url 请求 记录

1、需求 某网站 需要 AI生成音乐&#xff0c;生成mp3文件的时候需要等待&#xff0c;需要程序中实时监听mp3文件是否生成 2、用的开发框架 为php 3、文件结构 配置路由设置 Route::group(/music, function () {Route::post(/musicLyrics, AiMusic/musicLyrics);//Ai生成歌词流式…

Linux八股积累与笔记

1、iptables 是一个用于配置Linux内核防火墙规则的工具。四表五链&#xff1a;在iptables中&#xff0c;有四个表&#xff08;tables&#xff09;和五个链&#xff08;chains&#xff09;&#xff0c;用于管理不同类型的数据包过滤规则。如下&#xff1a; 表&#xff08;Tabl…

乐鑫发布 esp-iot-solution v2.0 版本

今天&#xff0c;乐鑫很高兴地宣布&#xff0c;esp-iot-solution v2.0 版本已经发布&#xff0c;release/v2.0 分支下的正式版本组件将为用户提供为期两年的 Bugfix 维护&#xff08;直到 2027.01.25 ESP-IDF v5.3 EOL&#xff09;。该版本将物联网开发中常用的功能进行了分类整…

【爬虫框架:feapder,管理系统 feaplat】

github&#xff1a;https://github.com/Boris-code/feapder 爬虫管理系统 feaplat&#xff1a;http://feapder.com/#/feapder_platform/feaplat 爬虫在线工具库 &#xff1a;http://www.spidertools.cn &#xff1a;https://www.kgtools.cn/1、feapder 简介 对于学习 Python…

uni-app 蓝牙开发

一. 前言 Uni-App 是一个使用 Vue.js 开发&#xff08;所有&#xff09;前端应用的框架&#xff0c;能够编译到 iOS、Android、快应用以及各种小程序等多个平台。因此&#xff0c;如果你需要快速开发一款跨平台的应用&#xff0c;比如在 H5、小程序、iOS、Android 等多个平台上…

C语言——海龟作图(对之前所有内容复习)

一.问题描述 海龟作图 设想有一只机械海龟&#xff0c;他在C程序控制下在屋里四处爬行。海龟拿了一只笔&#xff0c;这支笔或者朝上&#xff0c;或者朝下。当笔朝下时&#xff0c;海龟用笔画下自己的移动轨迹&#xff1b;当笔朝上时&#xff0c;海龟在移动过程中什么也不画。 …

【Maven】继承和聚合

5. Maven的继承和聚合 5.1 什么是继承 Maven 的依赖传递机制可以一定程度上简化 POM 的配置&#xff0c;但这仅限于存在依赖关系的项目或模块中。当一个项目的多个模块都依赖于相同 jar 包的相同版本&#xff0c;且这些模块之间不存在依赖关系&#xff0c;这就导致同一个依赖…