Application de messagerie instantanée, comprenant le serveur, la gestion et le client
Maintenant déployé et en ligne, bienvenue pour découvrir le terminal client et de gestion
Veuillez ne pas modifier les rôles et les autorisations par défaut à volonté. Soyez gentil et n'utilisez pas de noms très grossiers.
Construit avec @vue/cli, le client du service IM utilise Vant pour la partie UI
Ce projet vise principalement à démontrer l'application de base d'un client (similaire à WeChat). Il a des fonctions telles que l'inscription, la connexion, la modification des informations personnelles, l'ajout d'amis, la demande d'adhésion à un groupe, le chat, etc. le back-end.
Tout le monde connaît le projet Vue Family Bucket (je pense qu'il est meilleur que React à cet égard, et il n'y a pas tellement de choix). Voici quelques extensions :
Introduisez d'abord le package socket.io-client. Vous pouvez savoir par son nom qu'il s'agit de la partie client de socket.io. Voici l'utilisation :
Si vous souhaitez envoyer et recevoir des informations avec le serveur, vous devez d'abord établir un lien.
import io from 'socket.io-client';
// 首先需要链接上后端的 socket.io,query是链接的参数
this.socket = io('http://127.0.0.1:7001', {
query: {
scene: 'im',
userId: '1'
}
});
// socket.on 就是监听事件,connect就是链接上了服务端
this.socket.on('connect', () => {
console.log('socket连接成功!');
// ....
});
Une fois le lien réussi dans ce projet, j'ai commencé à demander la liste des sessions et les enregistrements de messages, afin de m'assurer que le lien réussissait lors de la réception du message.
Après vous être connecté au serveur, vous pouvez envoyer et recevoir des informations. C'est très simple :
// 发送消息,message是自己定义的消息体
this.socket.emit('/v1/im/new-message', message);
// 有新消息收到
this.socket.on('/v1/im/new-message', message => {
// 自己进行一系列的处理
});
L'envoi et la réception de messages peuvent être combinés avec le backend Même si le frontend envoie ses propres messages, il ne sera pas affiché tant qu'il n'aura pas reçu le message de groupe en arrière-plan. être envoyé.
Lors de la saisie, c'est une chaîne pure comme [酷]
. Lors de la sauvegarde, c'est aussi une chaîne Lorsqu'elle est affichée, elle est remplacée par l'icône correspondante. Voici pourquoi je ne trouve pas de moyen de la remplacer par l'icône correspondante. dans la zone de saisie. Le patron m'a initialement demandé de faire ça, et je l'ai essayé, mais il s'est avéré que cette chose était un grand gouffre. De nos jours, il semble que seule la zone de saisie de Weibo avec laquelle j'ai été en contact affiche des icônes au lieu de chaînes, ce qui soulève également de nombreuses questions :
Mais c'est bien d'utiliser des chaînes (à la fois WeChat et DingTalk), toutes les fonctionnalités natives peuvent être utilisées et le front-end est facile à gérer (point clé)
D'après ce qui précède, nous pouvons voir à quel point socket.io est pratique à utiliser. Établissez d'abord un lien, puis vous pouvez envoyer et recevoir des messages. En combinaison avec notre scénario de messagerie instantanée, nous avons pris les dispositions suivantes :
/v1/im/new-message
Couramment vu dans les applications mobiles lorsque vous faites défiler et cliquez pour entrer
Lorsque nous développons des applications Web, nous rencontrons souvent un problème lorsque nous passons d'une page de liste déroulante à la page de détails suivante, puis revenons à la page de liste, il est difficile de restaurer l'état de la barre de défilement et de ne pas s'en souvenir. L'endroit où vous avez emménagé.
J'ai déjà essayé plusieurs méthodes :
Aucune des solutions ci-dessus n'est idéale
Plus tard, j'ai développé vue-page-stack en référence à keep-alive pour sauvegarder la pile de pages Vue, c'est-à-dire le dom virtuel dans Vue, mais le problème de la barre de défilement n'était toujours pas résolu. Le DOM virtuel n'enregistrant pas l'état de défilement de chaque composant, il ne peut pas être restauré.
Lorsque j'ai utilisé cube-ui, j'ai découvert que l'utilisation du conteneur de défilement dans cette bibliothèque de composants pouvait restaurer la barre de défilement. J'ai en outre découvert que c'était la raison du meilleur défilement du professeur Huang Yi.
En regardant le code source de bs, j'ai découvert que l'implémentation interne de bs n'est pas un défilement natif, mais enregistre certaines informations de défilement, dont les plus importantes sont x et y, qui sont les valeurs de défilement que j'ai implémentées. comportements de défilement via la transformation. Lors de la restauration du DOM virtuel, les informations de défilement sont également restaurées.
Au final, vue—page-stack + bs peut parfaitement réaliser la restauration de la pile de pages.
Ce problème apparaît principalement dans les requêtes telles que les enregistrements de messages. Vous rencontrerez également ce problème dans les petits programmes.
La plupart des scènes de défilement sont des chargements pull-up. Lors du chargement pull-up, le contenu chargé apparaît sous la zone de défilement. Après le chargement, nous ajoutons les données à la liste, et Vue et d'autres sont responsables du rendu du contenu nouvellement chargé. pour charger. Tirez pour continuer le défilement.
Mais dans notre scénario, lorsque nous parcourons les enregistrements de messages dans une certaine session, nous devons dérouler vers le bas pour charger plus de messages. Après le chargement, continuez à dérouler et faites défiler lentement pour afficher. Cela conduit à un problème très sérieux : le contenu qui apparaît après le chargement du menu déroulant se trouve au-dessus de la zone de défilement. Si aucun traitement n'est effectué, il passera directement au sommet du contenu nouvellement chargé après le chargement, car la distance de défilement n'a pas été atteinte. changé, ce qui pose un problème, qui est incompatible avec ce que nous voulons réaliser.
J'ai également pensé à de nombreuses méthodes, notamment calculer la longueur totale du message nouvellement ajouté, puis l'annuler. Cependant, le type et la hauteur du message sont incohérents et il y aura des erreurs dans le calcul.
La solution finale à laquelle j'ai pensé était :
Les deux problèmes ci-dessus sont reflétés dans l'image ci-dessous, et l'effet est correct, comme suit :
La bande passante de mon serveur étant trop faible, je souhaite utiliser le plus possible CDN et utiliser certaines extensions, ce qui mettra beaucoup moins de pression sur le serveur. Cette partie du contenu appartient à la partie webpack.
// index.html
<script src="https://api.map.baidu.com/getscript?v=3.0&ak=ZHjk59sSOpM1eNWgNWyj9zpyAFTHdL5z"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vue-router.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/vuex.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/vant.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/browser/index.js"></script>
// vue.config.js
externals: {
BMap: 'BMap',
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
vant: 'vant',
xgplayer: 'Player'
}
Cela a également amené mon https à montrer qu'il n'était pas sûr à cause de SameSite, mais il n'y avait pas d'autre moyen.
Les ressources frontales sont gérées à l'aide de nginx, qui agit comme un proxy inverse. index.html n'est pas mis en cache sur le front-end. Les fichiers statiques tels que js et css sont fortement mis en cache et compressés en gzip pendant un mois, et le reste /api. , /public et /socket .io doivent être transmis au serveur. Mon serveur fonctionne sur http://127.0.0.1:7001. Vous devez faire attention à la configuration de http et il peut être mis à niveau vers websocket.
Le certificat https est un certificat appliqué gratuitement sur https://freessl.cn/ et configuré sur nginx
server {
listen 80;
server_name im-client.hezf.online;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
root /data/static/im-client;
index index.html;
try_files $uri $uri/ /index.html;
}
location ~* .(html)$ {
root /data/static/im-client;
access_log off;
add_header Cache-Control no-store;
}
location /static {
access_log off;
root /data/static/im-client;
gzip on;
gzip_buffers 32 8K;
gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml;
gzip_disable "MSIE [1-6]."; #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_vary on;
add_header Cache-Control max-age=2592000;
}
location /api {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto http;
proxy_set_header X-NginX-Proxy true;
client_max_body_size 100m;
}
location /public {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
}
location /socket.io {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}
server {
listen 443 ssl;
server_name im-client.hezf.online;
ssl_certificate /etc/nginx/conf.d/hezf-online/im-client.hezf.online_chain.crt;
ssl_certificate_key /etc/nginx/conf.d/hezf-online/im-client.hezf.online_key.key;
ssl_session_timeout 5m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2; #按照这个协议配置
ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4:!DH:!DHE;
ssl_prefer_server_ciphers on;
# Load configuration files for the default server block.
include /etc/nginx/default.d/*.conf;
location / {
root /data/static/im-client;
index index.html;
try_files $uri $uri/ /index.html;
}
location ~* .(html)$ {
root /data/static/im-client;
access_log off;
add_header Cache-Control no-store;
}
location /static {
access_log off;
root /data/static/im-client;
gzip on;
gzip_buffers 32 8K;
gzip_comp_level 6;
gzip_min_length 100;
gzip_types application/javascript text/css text/xml;
gzip_disable "MSIE [1-6]."; #配置禁用gzip条件,支持正则。此处表示ie6及以下不启用gzip(因为ie低版本不支持)
gzip_vary on;
add_header Cache-Control max-age=2592000;
}
location /api {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-NginX-Proxy true;
proxy_redirect off;
client_max_body_size 100m;
}
location /public {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
}
location /socket.io {
proxy_pass http://127.0.0.1:7001;
proxy_connect_timeout 3;
proxy_send_timeout 30;
proxy_read_timeout 30;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Server $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
client_max_body_size 100m;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
}
}