即時通訊應用程式, 包含服務端、管理端和客戶端
現已部署上線,歡迎體驗客戶端和管理端
請不要隨意更改預設角色和權限,請有點愛心,別整一些很不文明的名字
使用@vue/cli 搭建,IM 服務的客戶端,UI 部分使用了Vant
這個專案主要是展示一個client(類似微信)的基礎應用,具有註冊、登入、編輯個人資訊、新增好友、申請入群、聊天等功能,需要配合後端使用
Vue 全家桶的專案大家很熟悉的了(這點我覺得比React 好一些,沒有了那麼多的選擇困難),這裡說下擴展的幾個地方:
首先引入socket.io-client 這個包,透過名字可以知道是socket.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 => {
// 自己进行一系列的处理
});
收發訊息這塊可以和後端結合在一起看,前端即使是發送自己的訊息也是在收到後台的群發後才會顯示,也就是說如果斷開了鏈接,那麼自己的訊息也發不出來
輸入的時候是[酷]
這種的純字串,保存的時候也是字串,在顯示的時候替換為對應的圖標,這裡說下為什麼不想辦法在輸入框裡面也替換為對應的圖標。老闆原本要求這麼做,我也嘗試了一下,結果就是這玩意是個大坑。現如今我接觸過的好像只有微博的輸入框顯示的是圖標而不是字串,也是引出了很多的問題:
但是使用字串的話就很好了(微信和釘子都是),所有原生的特性都能夠用,前端也好處理(重點)
以上可以看出來socket.io 的使用多麼的方便了,首先建立鏈接,然後就可以進行收發消息了,結合我們的IM 場景,做瞭如下的安排:
/v1/im/new-message
統一監聽常見於行動裝置App 在滾動點擊進入的時候
當我們在開發web app 的時候,經常會遇到一個問題,就是當從一個可滾動的列表頁進入到下一個詳情頁面,然後返回列表頁面的時候,很難去還原滾動條的狀態,無法記住進來時候的位置。
以前我嘗試過很多方法:
以上解決方案都不理想
後來我參考keep-alive 開發了vue-page-stack 來保存Vue 頁面的堆疊,也就是Vue 中的虛擬dom,但是捲軸的問題仍然沒解決。因為虛擬dom 沒有記錄各個元件的滾動狀態,所以無法恢復。
在我使用cube-ui 的時候發現,使用這個元件庫裡面的滾動容器,是可以還原出捲軸的,進一步發現是黃軼事老師的better-scroll 的原因。
透過查看bs 的原始碼發現,原來是bs 的內部實現不是原生滾動,而是記錄一些滾動的信息,其中最重要的就是x 和y,也就是滾動的值,自己實現了一套滾動行為通過transform 去實現,在還原虛擬dom 的時候,滾動的資訊也被還原了。
最終就是vue—page-stack + bs 可以完美實現頁面堆疊的還原
這個問題多見於訊息記錄等查詢,在小程式中也會遇到這樣的問題
絕大多數滾動場景都是上拉加載,上拉加載的時候,加載的內容在滾動區域的下方出現,加載之後,我們將數據添加到列表,由Vue 等負責渲染新加載的內容,我們繼續上拉就可以繼續滾動查看。
但在我們的場景下,在某一會話中翻閱訊息記錄的時候,是下拉載入更多訊息,載入後,繼續下拉慢慢滾動查看。這就導致了一個很嚴重的問題:下拉加載後出現的內容在滾動區域的上方,不做任何處理的話加載後會直接跳到新加載內容的最上方,因為滾動距離沒變,這就出問題了,和我們想實現的不一致。
也想了很多的方法,包括計算新增加訊息的總長度,然後滾回來,但是訊息的類型和高度不一致,計算會有誤差。
最終想到的處理辦法就是:
以上兩個問題在下圖都有體現,效果還可以,如下:
由於我的伺服器頻寬太弱,所以想著盡可能的使用CDN,使用了一些擴展,這樣對伺服器壓力小了很多,這部分內容屬於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'
}
這也導致了我的https 顯示是不安全的,因為SameSite ,但也沒別的辦法了
前端資源使用nginx 管理,由nginx 做反向代理,index.html 在前端不做緩存,js 和css 等靜態文件做了一個月的強緩存和gzip 壓縮,剩下的/api、/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 {
}
}