当前位置: 首页 > news >正文

Django+Nginx+uwsgi网站使用Channels+redis+daphne实现简单的多人在线聊天及消息存储功能

网站部署在华为云服务器上,Debian系统,使用Django+Nginx+uwsgi搭建。最终效果如下图所示。

一、响应逻辑顺序

1. 聊天页面请求

客户端请求/chat/(输入聊天室房间号界面)和/chat/room_name(某个聊天室页面)链接时,由Nginx转到Django由urls.py解析并返回相应页面,在返回的聊天室页面内置了javascript程序,请求建立wss:/ws/chat/room_name的websocket连接。

2. websocket连接请求

客户端向Nginx发送websocket连接请求,根据Ngnix配置文件location /ws {}中设置的反向代理,请求被转到本地的7001端口( http://127.0.0.1:7001),将由运行在该端口上的由daphne托管的asgi服务解析并响应(配置文件为myproject/asgi.py)。

3. asgi.py解析并响应

asgi.py中定义的application定义了websocket的解析规则,具体由/myproject/routing.py中的websocket_urlpatterns进行地址解析,通过Channels由myapp/comsumers.py进行异步信息处理并响应。

二、配置Channels和asgi

1. 安装Channels
sudo pip3 install channels
2. settings.py中添加应用并配置channels和asgi
INSTALLED_APPS = ['django.contrib.auth','django.contrib.admin', 'django.contrib.contenttypes','django.contrib.sessions','django.contrib.messages','django.contrib.staticfiles','channels','myapp',
]ASGI_APPLICATION = 'myproject.asgi.application'# Channels_Layers相关配置
CHANNEL_LAYERS = {'default': {'BACKEND': 'channels_redis.core.RedisChannelLayer','CONFIG': {"hosts": [('127.0.0.1', 6379)],},},
}# 配置认证后端
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
)

Django Channels利用 Redis 作为通道层来管理客户端和服务器之间的通信。当客户端通过 WebSocket 连接发送消息时,Django Channels 会使用 Redis 将消息分发到所有连接的客户端。Redis 充当消息代理,实时有效地处理消息的路由和传递。

ASGI_APPLICATION指向Django项目下的asgi.py文件中的application。

3.  配置asgi.py文件
import os
import django
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings')
django.setup()from django.core.asgi import get_asgi_application
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.auth import AuthMiddlewareStack
from myproject import routingsapplication = ProtocolTypeRouter({'http':get_asgi_application(),                          #支持http请求'websocket':AuthMiddlewareStack(URLRouter(routings.websocket_urlpatterns)  #支持webscoket请求),
})

此处django.setup()和DJANGO_SETTINGS_MODULE设置应放在导入模块前,否则容易出现如下报错: 

django.core.exceptions.AppRegistryNotReady: Apps aren't loaded yet.
Requested setting DEBUG, but settings are not configured. 
You must either define the environment variable DJANGO_SETTINGS_MODULE 
or call settings.configure() before accessing settings...

 WSGI和ASGI 的主要区别是同步VS异步,WSGI是同步的,每个请求必须等待前一个请求完成。而ASGI是异步的,可以同时处理多个请求。 WSGI主要用于HTTP协议,ASGI旨在支持WebSocket、HTTP2等协议。ASGI的主要特点是异步非阻塞,它能够更好地处理并发请求。

上述设置中websocket地址解析由Django项目下的routings.py完成,其功能与urls.py类似。

4. 配置routings.py
from django.urls import path
from myapp import consumerswebsocket_urlpatterns = [path(r'ws/chat/<str:room_name>/', consumers.ChatConsumer.as_asgi())
]

 myapp为新建的应用,其下的consumers.py文件定义了websocket的响应函数ChatConsumer。

5. 配置myapp/consumers.py
import json
from channels.generic.websocket import AsyncWebsocketConsumer
from asgiref.sync import sync_to_async
from myapp.models import ChatMessageclass ChatConsumer(AsyncWebsocketConsumer):async def connect(self):self.room_name = self.scope["url_route"]["kwargs"].get("room_name") #获取传输的房间号if self.room_name:self.room_group_name = f"chat_{self.room_name}"# Join room groupawait self.channel_layer.group_add(self.room_group_name, self.channel_name)await self.accept()else:await self.close()async def disconnect(self, close_code):# Leave room groupawait self.channel_layer.group_discard(self.room_group_name, self.channel_name)# Receive message from WebSocketasync def receive(self, text_data):text_data_json = json.loads(text_data)message = text_data_json["message"]username = text_data_json["username"]#保存消息await self.save_message(username, self.room_name, message)# 向群组内的所有客户端发送消息,type为“chat_message"await self.channel_layer.group_send(self.room_group_name, {"type": "chat_message", "message": message,'username':username})# Receive message from room groupasync def chat_message(self, event):message = event["message"]username = event['username']# Send message to WebSocketawait self.send(text_data=json.dumps({"message": message,'username':username}))# 信息写入数据库@sync_to_async()def save_message(self, username, room, message):if len(message) !=0 :newmsg = ChatMessage.objects.create(username=username, room=room, content=message)newmsg.save()

文件中定义了websocket的连接,消息的接收、发送和存储,每条消息在发送给群组内的所有客户端前先存储到数据库。

三、消息存储

1. 配置myapp/models.py,用于消息存储
from django.db import modelsclass ChatMessage(models.Model):username = models.CharField(max_length=100,verbose_name='用户名')room = models.CharField(max_length=100)content = models.TextField(verbose_name='内容')create_time = models.DateTimeField(auto_now_add=True,verbose_name='写入时间')def __str__(self):return f"房间号:{self.room}--用户:{self.username}({self.content})"
2. 配置myapp/admin.py,便于在Django后台处理存储的消息
from django.contrib import admin# Register your models here.
from .models import ChatMessage
admin.site.register(ChatMessage)

 效果如下:

 四、安装redis并启动

1. 安装redis
sudo apt-get install redis-server

安装后redis-server会自动启动,可以使用sudo systemctl start/stop/restart/status redis.service来启动/停止/重启/查看服务器。

$sudo systemctl status redis.service ● redis-server.service - Advanced key-value storeLoaded: loaded (/lib/systemd/system/redis-server.service; enabled; vendor preset: enabled)Active: active (running) since Tue 2024-11-19 23:38:28 CST; 20h agoDocs: http://redis.io/documentation,man:redis-server(1)Main PID: 123767 (redis-server)Status: "Ready to accept connections"Tasks: 5 (limit: 2321)Memory: 7.2MCPU: 1min 36.022sCGroup: /system.slice/redis-server.service└─123767 /usr/bin/redis-server 127.0.0.1:6379

五、安装daphne,并启动asgi服务

1. 安装daphne

 Daphne 是 Django Channels 项目的一部分,专门用于为 Django 提供支持 WebSocket和 ASGI 协议的异步服务器。

sudo pip3 install daphne

启动asgi服务的命令如下,监听端口为7001。

daphne -p 7001 myproject.asgi:application

asgi服务的配置文件即为前面已经完成的asgi.py。

为了管理方便,将其做成系统服务开机启动。

2. 设置daphne.service服务

新建/etc/systemd/system/daphne.service文件

sudo nano /etc/systemd/system/daphne.service

内容如下: 

[Unit]
Description=Daphne Server
After=network.target[Service]
Type=exec
WorkingDirectory=/home/yislwll/Django/myproject
ExecStart=/usr/bin/daphne -p 7001 myproject.asgi:application
Restart=always
User=yislwll
Group=Yisl
Environment=PYTHONPATH=/home/yislwll/Django/myproject[Install]
WantedBy=multi-user.target

然后启动daphne服务:

sudo systemctl enable daphne.service
sudo systemctl start daphne.service

需要特别注意的是,修改consumers.py文件后一定要重启daphne服务,否则对websocket通讯内容的更改无法更新。

sudo systemctl restart daphne.service

 六、配置Nginx

根据响应逻辑,Ngnix应该对websocket的连接“wss://域名/ws/chat/room_name”做出响应,请求将被代理到本地的7001端口,由运行在该端口上由daphne托管的asgi服务解析并响应。

1. 配置/etc/nginx/nginx.conf

网站为https://连接,所以webscoket连接为wss://

	server {listen       443  ssl;server_name  xxxx.com;charset      utf-8;ssl_certificate /path/to/fullchain.pem;  # SSL证书的路径ssl_certificate_key /path/to/privkey.pem;  # 私钥的路径# SSL 配置ssl_session_timeout 1d;ssl_session_cache shared:MozSSL:10m;  # about 4000 sessionsssl_session_tickets off;# 指定加密套件ssl_protocols TLSv1.2 TLSv1.3;ssl_ciphers 'xxxxxxxxx';ssl_prefer_server_ciphers on;location / {#由django响应uwsgi_pass 127.0.0.1:8080;include uwsgi_params;include /etc/nginx/uwsgi_params;uwsgi_param UWSGI_SCRIPT iCourse.wsgi;uwsgi_param UWSGI_CHDIR /iCourse;index index.html index.htm;client_max_body_size 35m;index index.html index.htm;}location /ws {#wss协议由asgi服务响应proxy_http_version 1.1;proxy_set_header Host  $host;proxy_set_header X-Real-Ip $remote_addr;proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;proxy_set_header X-Nginx-Proxy true;proxy_redirect off;client_max_body_size 10m;proxy_pass http://127.0.0.1:7001;proxy_set_header Upgrade $http_upgrade;proxy_set_header Connection "upgrade";proxy_connect_timeout 300s;proxy_read_timeout 300s;proxy_send_timeout 300s; }}
2. 重启Nginx服务
sudo systemctl restart nginx.service

七、客户端界面

1. 配置urls.py,响应客户端界面请求 
from myapp import views as channelsviewurlpatterns = [......path('chat/',channelsview.chat, name='webchat'),path('chat/<str:room_name>/', channelsview.chatroom, name='chatroom'),......
]

客户端页面请求将由myapp/views.py来处理。

2. 配置views.py
from django.shortcuts import render
from django.contrib.auth.decorators import login_required
from .models import ChatMessage# Create your views here.
@login_required(login_url='/login/')
def chat(request):return render(request,"channels/chattingindex.html")@login_required(login_url='/login/')
def chatroom(request,room_name):username = request.session.get('username','游客')msgs = ChatMessage.objects.filter(room=room_name).order_by('create_time')[0:20]return render(request,"channels/chattingroom.html",{'room_name':room_name,'username':username, 'msgs':msgs})

在chatroom函数中,对当前聊天室历史消息的最近20条进行了查询,并由页面展示出来。

3. 聊天页面设计及通讯

chattingroom.html文件内容大致如下:

{% extends "newdesign/newbase.html" %}{% 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;}</style>{% endblock %}{% block maincontent %} <div class="container"><div id="chat-record" class="chat-window">{% for m in msgs %}{% 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}}</span></div></div><br>{% else %}<div class="chat-message left"><div class="user-content">{{m.username}}</div><div class="message-content"><span>{{m.content}}</span></div></div><br>{% endif %}{% endfor %}</div></div><br><div class="mx-auto px-auto" style="text-align: center;"><input id="chat-message-input" type="text" style="width:900px;" size="150"><br><br><input id="chat-message-submit" type="button" value="发送消息"></div>{{ room_name|json_script:"room-name" }}{{ username|json_script:"username" }}<script>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://scybbd.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 messageInputDom = document.querySelector('#chat-message-input');const message = messageInputDom.value;chatSocket.send(JSON.stringify({'message': message,'username':username}));messageInputDom.value = '';};</script>
{% endblock %}

聊天界面比较简陋,客户端与服务器的wss://连接和通讯由页面中javacript脚本完成。

至此,一个简单的多人在线聊天的页面基本完成。

相关文章:

Django+Nginx+uwsgi网站使用Channels+redis+daphne实现简单的多人在线聊天及消息存储功能

网站部署在华为云服务器上&#xff0c;Debian系统&#xff0c;使用DjangoNginxuwsgi搭建。最终效果如下图所示。 一、响应逻辑顺序 1. 聊天页面请求 客户端请求/chat/&#xff08;输入聊天室房间号界面&#xff09;和/chat/room_name&#xff08;某个聊天室页面&#xff09;链…...

数据结构在二叉树Oj中利用子问题思路来解决问题

二叉树Oj题 获取二叉树的节点数获取二叉树的终端节点个数获取k层节点的个数获取二叉树的高度检测为value的元素是否存在判断两颗树是否相同判断是否是另一棵的子树反转二叉树判断一颗二叉树是否是平衡二叉树时间复杂度O(n*n)复杂度O(N) 二叉树的遍历判断是否是对称的二叉树二叉…...

华为openEuler考试真题演练(附答案)

【单选题】 以下关于互联网的描述&#xff0c;哪个选项是正确的? A:Nginx 在万维网中可以作为 ftp 服务器的反向代理&#xff0c;并与ftp服务器的数量--对应 B:Nginx 在互联网中可以作为 web服务器端&#xff0c;成为万维网的一个节点 C:互联网上的的资源需使用 Nginx进行七层…...

生成自签名证书并配置 HTTPS 使用自签名证书

生成自签名证书 1. 运行 OpenSSL 命令生成证书和私钥 在终端中输入以下命令&#xff0c;生成自签名证书和私钥文件&#xff1a; sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout self_signed.key -out self_signed.pem-x509&#xff1a;生成自签名证书。…...

物联网核心安全系列——智能汽车安全防护的重要性

汽车行业引入的智能硬件技术已经越来越多&#xff0c;早先设计者更多考虑到的是硬件成本和软件用户体验等因素&#xff0c;但随着国外两位技术人员成功实现远程控制汽车的视频曝出后&#xff0c;智能汽车安全便成为了一个热议话题。 汽车总线架构及原理比较复杂&#xff0c;日…...

数据库视图

数据库视图&#xff08;Database View&#xff09;是数据库中的虚拟表&#xff0c;其内容由查询定义&#xff0c;通常用来简化复杂的查询或提供安全访问数据。视图并不存储实际数据&#xff0c;而是将查询的结果集作为一个“虚拟表”呈现。用户通过查询视图&#xff0c;就可以看…...

从传统分析到智能问数,打造零门槛数据分析方案

众所周知&#xff0c;传统报表和自助分析工具存在使用门槛&#xff0c;且早期智能分析不够智能。随着AI技术发展&#xff0c;现有数据应用模式难以满足多样化、快速变化的需求&#xff0c;数据驱动、敏捷决策、精细运营成为了各大企业的新课题。 01企业数据应用挑战 业务人员的…...

java 设计模式 模板方法模式

模板方法模式&#xff08;Template Method Pattern&#xff09;是一种行为型设计模式&#xff0c;它在父类中定义一个算法的框架&#xff0c;允许子类在不改变算法结构的情况下重写算法的某些特定步骤。这种模式非常适合于那些有一定公共流程&#xff0c;但某些步骤需要子类定制…...

基于UDP和TCP实现回显服务器

目录 一. UDP 回显服务器 1. UDP Echo Server 2. UDP Echo Client 二. TCP 回显服务器 1. TCP Echo Server 2. TCP Echo Client 回显服务器 (Echo Server) 就是客户端发送什么样的请求, 服务器就返回什么样的响应, 没有任何的计算和处理逻辑. 一. UDP 回显服务器 1. UD…...

在 CentOS 系统上直接安装 MongoDB 4.0.25

文章目录 步骤 1&#xff1a;配置 MongoDB 官方源步骤 2&#xff1a;安装 MongoDB步骤 3&#xff1a;启动 MongoDB 服务步骤 4&#xff1a;验证安装步骤 5&#xff1a;可选配置注意事项 以下是在 CentOS 系统上直接安装 MongoDB 4.0.25 的详细步骤&#xff1a; 步骤 1&#x…...

Android和IOS的区别

一、系统区别 1、系统和框架的区别 &#xff08;1&#xff09;Android系统的底层建立在Linux系统之上&#xff1b;而ios基于UNIX系统 Android完全开放&#xff0c;iOS完全封源开发 &#xff08;2&#xff09;编程语言:Android的编程语言是Java和KotLin&#xff1b;而ios的则为O…...

数据库基础(MySQL)

1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以下几个缺点&#xff1a; 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质&#xff1a; 磁盘内存 为…...

Vue前端开发子组件向父组件传参

在父组件中&#xff0c;如果需要获取子组件中的数据&#xff0c;有两种方式&#xff0c;一种是在子组件中自定义事件&#xff0c;父组件绑定该事件&#xff0c;当触发自定义事件时&#xff0c;向父组件传入参数;另一种是先通过ref属性给子组件命名&#xff0c;然后在父组件中就…...

javaScript语法基础(函数,对象,常用类Array,String,Math和Date)

# 本文详细结束了JavaScript中函数、对象、常用类Array&#xff0c;String&#xff0c;Math和Date的用法。 一、函数 1、概述 将程序中多次要用到的代码块封装起来&#xff0c;就是函数。函数使代码块的重复使用更方便&#xff0c;且功能独立&#xff0c;便于维护。 2、函数的…...

WebStorm 2022.3.2/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理

WebStorm 2022.3.2/IntelliJ IDEA 2024.3出现elementUI提示未知 HTML 标记、组件引用爆红等问题处理 1. 标题识别elementUI组件爆红 这个原因是&#xff1a; 在官网说明里&#xff0c;才版本2024.1开始&#xff0c;默认启用的 Vue Language Server&#xff0c;但是在 Vue 2 项…...

k8s-NetworkPolicy

NetworkPolicy 是k8s中的网络策略可以限制pod以及namespace之间的访问流量 演示一下名称空间之间基于端口的访问限制 官方对networkpolicy的介绍 官方网址&#xff1a; 网络策略 |Kubernetes &#xff08;简体中文&#xff09; 一&#xff1a;创建NetworkPolicy vim…...

【C++】踏上C++学习之旅(九):深入“类和对象“世界,掌握编程的黄金法则(四)(包含四大默认成员函数的练习以及const对象)

文章目录 前言1. 实现Date类的构造函数2. 实现Date类的拷贝构造函数3. 实现Date类的赋值运算符重载4. 实现各Date对象之间的比较接口5. 实现Date对象的加减接口6. const成员7. 取地址及const取地址操作符重载 前言 在我们前面学习到了"类和对象"的四大默认成员函数(…...

C++——智能指针剖析

参考&#xff1a; 恋恋风辰官方博客 动态内存管理 - cppreference.com SRombauts/shared_ptr&#xff1a; 一个最小的 shared/unique_ptr 实现&#xff0c;用于处理 boost/std&#xff1a;&#xff1a;shared/unique_ptr 不可用的情况。 C智能指针_c 智能指针-CSDN博客 当…...

241119.LeetCode——383.赎金信

题目描述 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以&#xff0c;返回 true &#xff1b;否则返回 false 。 magazine 中的每个字符只能在 ransomNote 中使用一次。 示例 1&#xff1a; 输…...

基于SSM的农家乐管理系统+论文示例参考

1.项目介绍 功能模块&#xff1a;管理员&#xff08;农家乐管理、美食信息管理、住宿信息管理、活动信息、用户管理、活动报名、论坛等&#xff09;&#xff0c;普通用户&#xff08;注册登录、活动报名、客房预订、用户评价、收藏管理、模拟支付等&#xff09;技术选型&#…...

XCTF-web-easyupload

试了试php&#xff0c;php7&#xff0c;pht&#xff0c;phtml等&#xff0c;都没有用 尝试.user.ini 抓包修改将.user.ini修改为jpg图片 在上传一个123.jpg 用蚁剑连接&#xff0c;得到flag...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

黑马Mybatis

Mybatis 表现层&#xff1a;页面展示 业务层&#xff1a;逻辑处理 持久层&#xff1a;持久数据化保存 在这里插入图片描述 Mybatis快速入门 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/6501c2109c4442118ceb6014725e48e4.png //logback.xml <?xml ver…...

理解 MCP 工作流:使用 Ollama 和 LangChain 构建本地 MCP 客户端

&#x1f31f; 什么是 MCP&#xff1f; 模型控制协议 (MCP) 是一种创新的协议&#xff0c;旨在无缝连接 AI 模型与应用程序。 MCP 是一个开源协议&#xff0c;它标准化了我们的 LLM 应用程序连接所需工具和数据源并与之协作的方式。 可以把它想象成你的 AI 模型 和想要使用它…...

智能仓储的未来:自动化、AI与数据分析如何重塑物流中心

当仓库学会“思考”&#xff0c;物流的终极形态正在诞生 想象这样的场景&#xff1a; 凌晨3点&#xff0c;某物流中心灯火通明却空无一人。AGV机器人集群根据实时订单动态规划路径&#xff1b;AI视觉系统在0.1秒内扫描包裹信息&#xff1b;数字孪生平台正模拟次日峰值流量压力…...

如何在最短时间内提升打ctf(web)的水平?

刚刚刷完2遍 bugku 的 web 题&#xff0c;前来答题。 每个人对刷题理解是不同&#xff0c;有的人是看了writeup就等于刷了&#xff0c;有的人是收藏了writeup就等于刷了&#xff0c;有的人是跟着writeup做了一遍就等于刷了&#xff0c;还有的人是独立思考做了一遍就等于刷了。…...

OPENCV形态学基础之二腐蚀

一.腐蚀的原理 (图1) 数学表达式&#xff1a;dst(x,y) erode(src(x,y)) min(x,y)src(xx,yy) 腐蚀也是图像形态学的基本功能之一&#xff0c;腐蚀跟膨胀属于反向操作&#xff0c;膨胀是把图像图像变大&#xff0c;而腐蚀就是把图像变小。腐蚀后的图像变小变暗淡。 腐蚀…...

Python ROS2【机器人中间件框架】 简介

销量过万TEEIS德国护膝夏天用薄款 优惠券冠生园 百花蜂蜜428g 挤压瓶纯蜂蜜巨奇严选 鞋子除臭剂360ml 多芬身体磨砂膏280g健70%-75%酒精消毒棉片湿巾1418cm 80片/袋3袋大包清洁食品用消毒 优惠券AIMORNY52朵红玫瑰永生香皂花同城配送非鲜花七夕情人节生日礼物送女友 热卖妙洁棉…...

Java求职者面试指南:Spring、Spring Boot、MyBatis框架与计算机基础问题解析

Java求职者面试指南&#xff1a;Spring、Spring Boot、MyBatis框架与计算机基础问题解析 一、第一轮提问&#xff08;基础概念问题&#xff09; 1. 请解释Spring框架的核心容器是什么&#xff1f;它在Spring中起到什么作用&#xff1f; Spring框架的核心容器是IoC容器&#…...

【网络安全】开源系统getshell漏洞挖掘

审计过程&#xff1a; 在入口文件admin/index.php中&#xff1a; 用户可以通过m,c,a等参数控制加载的文件和方法&#xff0c;在app/system/entrance.php中存在重点代码&#xff1a; 当M_TYPE system并且M_MODULE include时&#xff0c;会设置常量PATH_OWN_FILE为PATH_APP.M_T…...