서버, 관리 및 클라이언트를 포함한 인스턴트 메시징 애플리케이션
이제 배포되어 온라인 상태가 되었습니다. 클라이언트 및 관리 터미널을 경험해 보시기 바랍니다.
기본 역할과 권한을 임의로 변경하지 말고 친절하게 대해주시고 매우 야만적인 이름을 사용하지 마십시오.
@vue/cli를 사용하여 구축된 IM 서비스 클라이언트는 UI 부분에 Vant를 사용합니다.
이 프로젝트는 주로 클라이언트의 기본 응용 프로그램(WeChat과 유사)을 시연하기 위한 것입니다. 등록, 로그인, 개인 정보 편집, 친구 추가, 그룹 가입 신청, 채팅 등과 같은 기능이 있습니다. 백엔드.
모두가 Vue Family Bucket 프로젝트에 익숙합니다(이 점에서는 React보다 낫다고 생각하며 선택의 여지가 많지 않습니다).
먼저 소켓.io-client 패키지를 소개합니다. 이름에서 이것이 소켓.io의 클라이언트 부분임을 알 수 있습니다.
서버와 정보를 주고 받으려면 먼저 링크를 설정해야 합니다.
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连接成功!');
// ....
});
이 프로젝트에서 링크가 성공한 후 메시지를 받을 때 링크가 성공했는지 확인하기 위해 세션 목록과 메시지 기록을 요청하기 시작했습니다.
서버에 연결한 후 정보를 보내고 받을 수 있습니다. 매우 간단합니다.
// 发送消息,message是自己定义的消息体
this.socket.emit('/v1/im/new-message', message);
// 有新消息收到
this.socket.on('/v1/im/new-message', message => {
// 自己进行一系列的处理
});
메시지 보내기 및 받기는 백엔드와 결합될 수 있습니다. 프런트엔드가 자체 메시지를 보내더라도 백그라운드에서 그룹 메시지를 받을 때까지 표시되지 않습니다. 즉, 링크가 끊어지면 자체 메시지는 표시되지 않습니다. 전송됩니다.
입력시에는 [酷]
처럼 순수 문자열인데, 표시되면 해당 아이콘으로 대체할 수 있는 방법을 찾을 수 없는 이유입니다. 입력 상자에. 원래 사장님께서 이렇게 해달라고 하셔서 해보았으나 알고보니 이게 큰 구덩이였습니다. 요즘에는 제가 접한 웨이보의 입력 상자에만 문자열 대신 아이콘이 표시되는 것 같습니다. 이는 또한 많은 질문으로 이어집니다.
하지만 문자열(WeChat과 DingTalk 모두)을 사용하는 것이 좋고, 모든 기본 기능을 사용할 수 있으며, 프런트 엔드가 다루기 쉽습니다(핵심)
위에서부터 소켓.io를 사용하는 것이 얼마나 편리한지 알 수 있습니다. 먼저 링크를 설정한 다음 IM 시나리오와 결합하여 다음과 같이 준비했습니다.
/v1/im/new-message
모바일 앱에서 스크롤하고 클릭하여 입력할 때 일반적으로 표시됩니다.
웹앱을 개발하다 보면 스크롤 가능한 목록 페이지에서 다음 세부정보 페이지로 이동했다가 다시 목록 페이지로 돌아올 때 스크롤 막대의 상태를 복원하기 어렵고 기억하지 못하는 문제가 자주 발생합니다. 입주 당시 위치입니다.
나는 이전에 많은 방법을 시도했습니다.
위의 솔루션 중 어느 것도 이상적이지 않습니다.
나중에 Vue 페이지의 스택, 즉 가상 돔을 Vue에 저장하기 위해 keep-alive를 참고하여 vue-page-stack을 개발했지만 여전히 스크롤바 문제가 해결되지 않았습니다. 가상 DOM은 각 구성 요소의 스크롤 상태를 기록하지 않으므로 복원할 수 없습니다.
나는 Cube-ui를 사용했을 때 이 컴포넌트 라이브러리의 스크롤 컨테이너를 사용하면 스크롤 막대를 복원할 수 있다는 것을 알게 되었고 이것이 Huang Yi 선생님의 스크롤이 더 나은 이유라는 것을 알게 되었습니다.
bs의 소스 코드를 살펴보면 bs의 내부 구현이 기본 스크롤이 아니라 일부 스크롤 정보를 기록한다는 것을 알았습니다. 그 중 가장 중요한 것은 스크롤 값인 x와 y입니다. 변환을 통한 스크롤 동작 구현, 가상 DOM을 복원할 때 스크롤 정보도 복원됩니다.
결국 vue—page-stack + bs를 사용하면 페이지 스택 복원을 완벽하게 구현할 수 있습니다.
이 문제는 메시지 레코드와 같은 쿼리에서 주로 나타납니다. 작은 프로그램에서도 이 문제가 발생합니다.
대부분의 스크롤 장면은 풀업 로딩입니다. 풀업 로딩 중에는 로드된 콘텐츠가 스크롤 영역 아래에 나타납니다. 로딩 후에는 목록에 데이터를 추가하고 새로 로드된 콘텐츠를 계속해서 렌더링합니다. 계속 스크롤하려면 당기세요.
그러나 우리 시나리오에서는 특정 세션에서 메시지 레코드를 탐색할 때 더 많은 메시지를 로드하려면 아래로 당겨야 하며, 계속해서 아래로 끌어서 천천히 스크롤해야 합니다. 이로 인해 매우 심각한 문제가 발생합니다. 드롭다운 로드 후 나타나는 콘텐츠가 스크롤 영역 위에 있습니다. 처리가 수행되지 않으면 스크롤 거리가 충분하지 않기 때문에 로드 후 새로 로드된 콘텐츠의 맨 위로 바로 이동합니다. 변경되어 문제가 발생하며 이는 우리가 달성하려는 목표와 일치하지 않습니다.
새로 추가된 메시지의 전체 길이를 계산한 후 롤백하는 방법도 여러 가지 생각해 보았으나 메시지의 종류와 높이가 일치하지 않아 계산에 오류가 발생합니다.
내가 생각한 최종 해결책은 다음과 같습니다.
위의 두 가지 문제가 아래 그림에 반영되어 있으며 효과는 다음과 같습니다.
내 서버 대역폭이 너무 약하기 때문에 가능한 한 CDN을 사용하고 일부 확장 기능을 사용하면 서버에 훨씬 덜 부담을 줄 수 있습니다. 이 부분은 웹팩 부분에 속합니다.
// 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'
}
이로 인해 내 https가 SameSite로 인해 안전하지 않다는 사실이 표시되었지만 다른 방법은 없었습니다.
프론트엔드 리소스는 역방향 프록시 역할을 하는 nginx를 사용하여 관리됩니다. index.html은 프론트엔드에 캐시되지 않습니다. js 및 css와 같은 정적 파일은 한 달 동안 강력하게 캐시되고 gzip으로 압축됩니다. , /public 및 /socket .io를 서버로 전달해야 합니다. 내 서버는 http://127.0.0.1:7001에서 실행되며 http 설정에 주의해야 하며 websocket으로 업그레이드할 수 있습니다.
https 인증서는 https://freessl.cn/에서 무료로 적용되고 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 {
}
}