当前位置: 首页 > 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;技术选型&#…...

基于大模型的 UI 自动化系统

基于大模型的 UI 自动化系统 下面是一个完整的 Python 系统,利用大模型实现智能 UI 自动化,结合计算机视觉和自然语言处理技术,实现"看屏操作"的能力。 系统架构设计 #mermaid-svg-2gn2GRvh5WCP2ktF {font-family:"trebuchet ms",verdana,arial,sans-…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

系统设计 --- MongoDB亿级数据查询优化策略

系统设计 --- MongoDB亿级数据查询分表策略 背景Solution --- 分表 背景 使用audit log实现Audi Trail功能 Audit Trail范围: 六个月数据量: 每秒5-7条audi log&#xff0c;共计7千万 – 1亿条数据需要实现全文检索按照时间倒序因为license问题&#xff0c;不能使用ELK只能使用…...

DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”

目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

初学 pytest 记录

安装 pip install pytest用例可以是函数也可以是类中的方法 def test_func():print()class TestAdd: # def __init__(self): 在 pytest 中不可以使用__init__方法 # self.cc 12345 pytest.mark.api def test_str(self):res add(1, 2)assert res 12def test_int(self):r…...

SAP学习笔记 - 开发26 - 前端Fiori开发 OData V2 和 V4 的差异 (Deepseek整理)

上一章用到了V2 的概念&#xff0c;其实 Fiori当中还有 V4&#xff0c;咱们这一章来总结一下 V2 和 V4。 SAP学习笔记 - 开发25 - 前端Fiori开发 Remote OData Service(使用远端Odata服务)&#xff0c;代理中间件&#xff08;ui5-middleware-simpleproxy&#xff09;-CSDN博客…...

纯 Java 项目(非 SpringBoot)集成 Mybatis-Plus 和 Mybatis-Plus-Join

纯 Java 项目&#xff08;非 SpringBoot&#xff09;集成 Mybatis-Plus 和 Mybatis-Plus-Join 1、依赖1.1、依赖版本1.2、pom.xml 2、代码2.1、SqlSession 构造器2.2、MybatisPlus代码生成器2.3、获取 config.yml 配置2.3.1、config.yml2.3.2、项目配置类 2.4、ftl 模板2.4.1、…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...

给网站添加live2d看板娘

给网站添加live2d看板娘 参考文献&#xff1a; stevenjoezhang/live2d-widget: 把萌萌哒的看板娘抱回家 (ノ≧∇≦)ノ | Live2D widget for web platformEikanya/Live2d-model: Live2d model collectionzenghongtu/live2d-model-assets 前言 网站环境如下&#xff0c;文章也主…...