<p><code></p> <p><span class="img-wrap"><img data-src="/img/bV4jYr?w=402&amp;h=710" src="/img/bV4jYr?w=402&amp;h=710" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span><span class="img-wrap"><img data-src="/img/bV4jYD?w=401&amp;h=705" src="/img/bV4jYD?w=401&amp;h=705" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span><span class="img-wrap"><img data-src="/img/bV4jYU?w=395&amp;h=709" src="/img/bV4jYU?w=395&amp;h=709" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span><span class="img-wrap"><img data-src="/img/bV4jY2?w=401&amp;h=710" src="/img/bV4jY2?w=401&amp;h=710" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span><span class="img-wrap"><img data-src="/img/bV4jYN?w=400&amp;h=707" src="/img/bV4jYN?w=400&amp;h=707" alt="图片描述" title="图片描述" style="cursor: pointer; display: inline;"></span></p> <h2 id="articleHeader0">项目地址</h2> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="服务器源码地址:https://github.com/ermu592275254/chat-socket 网页源码地址:https://github.com/ermu592275254/chat-socket" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs groovy"><code>服务器源码地址:<span class="hljs-string">https:</span><span class="hljs-comment">//github.com/ermu592275254/chat-socket</span> 网页源码地址:<span class="hljs-string">https:</span><span class="hljs-comment">//github.com/ermu592275254/chat-socket</span></code></pre> <h2 id="articleHeader1">项目设计概述</h2> <h3 id="articleHeader2">相关技术</h3> <h4><a href="http://www.js-code.com/tag/node" title="node" target="_blank">node</a>js</h4> <p>使用<a href="http://www.js-code.com/tag/node" title="node" target="_blank">node</a>js搭建后台,因为是一个单页应用,并且前后端通信使用了webSocket,所有只用<code>http</code>模块搭建一个简单的服务器,未使用<code>koa、express</code>等web框架。</p> <h4>webSocket</h4> <p>使用<code>socket.io</code>实现webSocket,前端通过import socket.io 的方式会出现不断重连的情况,于是使用<code>script</code>方式实现。</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="<a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a> io = require('socket.io-client');<br /> // or with import syntax<br /> import io from 'socket.io-client';</p> <p>// or script<br /> <script src=&quot;/socket.io/socket.io.js&quot;></script><br /> <script> <a href="http://www.js-code.com/tag/const" title="const" target="_blank">const</a> socket = io('http://localhost'); </script><br /> " title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code><span class="hljs-keyword"><a href="http://www.js-code.com/tag/const" title="浏览关于“const”的文章" target="_blank" class="tag_link">const</a></span> io = <span class="hljs-built_in">require</span>(<span class="hljs-string">'socket.io-client'</span>); <span class="hljs-comment">// or with import syntax</span> <span class="hljs-keyword">import</span> io <span class="hljs-keyword">from</span> <span class="hljs-string">'socket.io-client'</span>; <span class="hljs-comment">// or script</span> &lt;script src=<span class="hljs-string">"/socket.io/socket.io.js"</span>&gt;<span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span> &lt;script&gt; <span class="hljs-keyword">const</span> socket = io(<span class="hljs-string">'http://localhost'</span>); <span class="xml"><span class="hljs-tag">&lt;/<span class="hljs-name">script</span>&gt;</span></span> </code></pre> <h4>mongodb</h4> <p>使用<code>mongoose</code>操作mongodb。mongodb这类非关系型数据库,功能较关系型数据库阉割了许多。主要表现在复杂的sql语句、事务支持等。</p> <h4>vue</h4> <p>使用vue以及vue的衍生产品,同时用到bootstarp作为样式框架。简单兼容了PC和移动。(PC仅支持chrome,在firefox、ie等浏览器中,会出现样式、布局混乱的情况)。</p> <h3 id="articleHeader3">功能点实现</h3> <h4>私聊</h4> <p>通过用户名和socketId进行匹配。保存用户每次登录的socketId,当对方在线时,将此信息通过socketId发送给对方。不在线仅保存到数据库,用户上线即可在私聊中查看。<strong>目前不支持消息通知,也不支持未读消息</strong></p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="...// 每次登录都将socketId替换为当前登录的socketId userModel.update({username: data.username}, {socketId: socket.id}).then(res => {<br /> socket.emit('login', user);<br /> }).catch(err => {<br /> console.log(err);<br /> socket.emit('err', 'update user socketId was failed');<br /> });<br /> ..." title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code>...<span class="hljs-comment">// 每次登录都将socketId替换为当前登录的socketId</span> userModel.update({username: data.username}, {socketId: socket.id}).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> { socket.emit(<span class="hljs-string">'login'</span>, user); }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> { <span class="hljs-built_in">console</span>.log(err); socket.emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'update user socketId was failed'</span>); }); ...</code></pre> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text=" chatModel.findOne({sendTime: time}).populate('sender receiver').then(newChat=>{<br /> let receiverData = {<br /> receiver: data.sender,<br /> data: newChat<br /> };<br /> // 如果对方在线就发送给对方<br /> if (io.sockets.connected[user.socketId]) {<br /> io.sockets.connected[user.socketId].emit('newMessage', receiverData);<br /> }<br /> let senderData = {<br /> receiver: data.receiver,<br /> data: newChat<br /> };<br /> // 同时也发送给自己(也可直接在前端添加,后端不发送)<br /> io.sockets.connected[socket.id].emit('newMessage', senderData);<br /> }).catch(err=>{<br /> io.sockets.connected[socket.id].emit('err', 'can`t find the newMessage')<br /> })" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs typescript"><code> chatModel.findOne({sendTime: time}).populate(<span class="hljs-string">'sender receiver'</span>).then(<span class="hljs-function"><span class="hljs-params">newChat</span>=&gt;</span>{ <span class="hljs-keyword"><a href="http://www.js-code.com/tag/let" title="浏览关于“let”的文章" target="_blank" class="tag_link">let</a></span> receiverData = { receiver: data.sender, data: newChat }; <span class="hljs-comment">// 如果对方在线就发送给对方</span> <span class="hljs-keyword">if</span> (io.sockets.connected[user.socketId]) { io.sockets.connected[user.socketId].emit(<span class="hljs-string">'newMessage'</span>, receiverData); } <span class="hljs-keyword">let</span> senderData = { receiver: data.receiver, data: newChat }; <span class="hljs-comment">// 同时也发送给自己(也可直接在前端添加,后端不发送)</span> io.sockets.connected[socket.id].emit(<span class="hljs-string">'newMessage'</span>, senderData); }).catch(<span class="hljs-function"><span class="hljs-params">err</span>=&gt;</span>{ io.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'can`t find the newMessage'</span>) })</code></pre> <h4>群聊</h4> <p>通过<code>broadcast</code>实现组发送。将群、群对应的聊天记录保存在数据库。用户进入群聊,则将其加入到对应的broadcast中。</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text=" socket.on('joinRoom', function(data) { if (!common.checkData(data)) { io.sockets.connected[socket.id].emit('err', 'request params Can`t be empty'); return; } // 加入对应的群聊 socket.join(data.groupName, function() { let roomName = Object.keys(socket.rooms); io.to(data.groupName, `${data.username} has joined the room`); socket.broadcast.in('data.groupName').emit('newUserJoin', { groupName: data.groupName, username: data.username }) }); })" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code> socket.on(<span class="hljs-string">'joinRoom'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data</span>) </span>{ <span class="hljs-keyword">if</span> (!common.checkData(data)) { io.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'request params Can`t be empty'</span>); <span class="hljs-keyword">return</span>; } <span class="hljs-comment">// 加入对应的群聊</span> socket.join(data.groupName, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params"></span>) </span>{ <span class="hljs-keyword">let</span> roomName = <span class="hljs-built_in">Object</span>.keys(socket.rooms); io.to(data.groupName, <span class="hljs-string">`<span class="hljs-subst">${data.username}</span> has joined the room`</span>); socket.broadcast.in(<span class="hljs-string">'data.groupName'</span>).emit(<span class="hljs-string">'newUserJoin'</span>, { <span class="hljs-attr">groupName</span>: data.groupName, <span class="hljs-attr">username</span>: data.username }) }); })</code></pre> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text=" groupChatModel.findOne({'sendTime': time}).populate('sender').then(res=>{<br /> if(res){<br /> // 发送给自己<br /> io.sockets.connected[socket.id].emit('newMsgOfGroup', res);<br /> // 将消息发送给群里的所有人除了自己<br /> socket.broadcast.in(data.groupName).emit('newMsgOfGroup', res);<br /> } else {<br /> io.sockets.connected[socket.id].emit('err', 'the message data is null');<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs lua"><code> groupChatModel.findOne({<span class="hljs-string">'sendTime'</span>: <span class="hljs-built_in">time</span>}).populate(<span class="hljs-string">'sender'</span>).<span class="hljs-keyword">then</span>(res=&gt;{ <span class="hljs-keyword">if</span>(res){ // 发送给自己 <span class="hljs-built_in">io</span>.sockets.connected[socket.id].emit(<span class="hljs-string">'newMsgOfGroup'</span>, res); // 将消息发送给群里的所有人除了自己 socket.broadcast.<span class="hljs-keyword">in</span>(data.groupName).emit(<span class="hljs-string">'newMsgOfGroup'</span>, res); } <span class="hljs-keyword">else</span> { <span class="hljs-built_in">io</span>.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'the message data is null'</span>); }</code></pre> <h4>头像上传</h4> <p>同样使用webSocket,将头像ID保存在用户信息表中,将图片文件保存在服务器static文件夹中。</p> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text=" uploadIcon(){ let file = this.$refs.uploadEl.files[0]; console.log(file); if(file.size > 100000){<br /> this.Toast('文件大小不能超过1M');<br /> this.$refs.uploadEl.value = '';<br /> return;<br /> }<br /> let data = {<br /> username: this.user.username,<br /> file: file,<br /> type: file.type.split('/')[1]<br /> };<br /> socket.emit('uploadUserIcon', data);<br /> this.$refs.uploadEl.value = '';<br /> }" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs kotlin"><code> uploadIcon(){ let file = <span class="hljs-keyword"><a href="http://www.js-code.com/tag/this" title="浏览关于“this”的文章" target="_blank" class="tag_link">this</a></span>.$refs.uploadEl.files[<span class="hljs-number">0</span>]; console.log(file); <span class="hljs-keyword">if</span>(file.size &gt; <span class="hljs-number">100000</span>){ <span class="hljs-keyword">this</span>.Toast(<span class="hljs-string">'文件大小不能超过1M'</span>); <span class="hljs-keyword">this</span>.$refs.uploadEl.value = <span class="hljs-string">''</span>; <span class="hljs-keyword">return</span>; } let <span class="hljs-keyword">data</span> = { username: <span class="hljs-keyword">this</span>.user.username, file: file, type: file.type.split(<span class="hljs-string">'/'</span>)[<span class="hljs-number">1</span>] }; socket.emit(<span class="hljs-string">'uploadUserIcon'</span>, <span class="hljs-keyword">data</span>); <span class="hljs-keyword">this</span>.$refs.uploadEl.value = <span class="hljs-string">''</span>; }</code></pre> <div class="widget-codetool" style="display:none;"> <div class="widget-codetool--inner"> <span class="selectCode code-tool" data-toggle="tooltip" data-placement="top" title="" data-original-title="全选"></span><br /> <span type="button" class="copyCode code-tool" data-toggle="tooltip" data-placement="top" data-clipboard-text="socket.on('uploadUserIcon', function(data) { let time = new Date().getTime(); let savePath = `/static/userIcon/${time}.${data.type}`; let hostPath = 'http://' + host + ':' + port; // 通过fs模块操作 fs.writeFile('.'+ savePath, data.file, function(err) { if (err) { console.log(err); io.sockets.connected[socket.id].emit('err', 'save userIcon failed'); } else { userModel.update({username: data.username}, {$set: {userIcon: hostPath + savePath}}).then(res => {<br /> userModel.findOne({username: data.username}).then(user=>{<br /> io.sockets.connected[socket.id].emit('uploadUserIcon', {<br /> user: user,<br /> message: 'upload userIcon success'<br /> });<br /> }).catch(err =>{<br /> io.sockets.connected[socket.id].emit('err', 'find userInfo failed');<br /> });<br /> }).catch(err => {<br /> io.sockets.connected[socket.id].emit('err', 'save userIcon path failed');<br /> })<br /> }<br /> })<br /> });" title="" data-original-title="复制"></span> </div> </p></div> <pre class="hljs javascript"><code>socket.on(<span class="hljs-string">'uploadUserIcon'</span>, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">data</span>) </span>{ <span class="hljs-keyword">let</span> time = <span class="hljs-keyword">new</span> <span class="hljs-built_in">Date</span>().getTime(); <span class="hljs-keyword">let</span> savePath = <span class="hljs-string">`/static/userIcon/<span class="hljs-subst">${time}</span>.<span class="hljs-subst">${data.type}</span>`</span>; <span class="hljs-keyword">let</span> hostPath = <span class="hljs-string">'http://'</span> + host + <span class="hljs-string">':'</span> + port; <span class="hljs-comment">// 通过fs模块操作</span> fs.writeFile(<span class="hljs-string">'.'</span>+ savePath, data.file, <span class="hljs-function"><span class="hljs-keyword">function</span>(<span class="hljs-params">err</span>) </span>{ <span class="hljs-keyword">if</span> (err) { <span class="hljs-built_in">console</span>.log(err); io.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'save userIcon failed'</span>); } <span class="hljs-keyword">else</span> { userModel.update({<span class="hljs-attr">username</span>: data.username}, {<span class="hljs-attr">$set</span>: {<span class="hljs-attr">userIcon</span>: hostPath + savePath}}).then(<span class="hljs-function"><span class="hljs-params">res</span> =&gt;</span> { userModel.findOne({<span class="hljs-attr">username</span>: data.username}).then(<span class="hljs-function"><span class="hljs-params">user</span>=&gt;</span>{ io.sockets.connected[socket.id].emit(<span class="hljs-string">'uploadUserIcon'</span>, { <span class="hljs-attr">user</span>: user, <span class="hljs-attr">message</span>: <span class="hljs-string">'upload userIcon success'</span> }); }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span>{ io.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'find userInfo failed'</span>); }); }).catch(<span class="hljs-function"><span class="hljs-params">err</span> =&gt;</span> { io.sockets.connected[socket.id].emit(<span class="hljs-string">'err'</span>, <span class="hljs-string">'save userIcon path failed'</span>); }) } }) });</code></pre> <h4>登录注册</h4> <p>将用户名作为唯一值。注册时不能注册已存在的用户名。登录支持自动登录,将密码保存在localStorage中。</p> <h3 id="articleHeader4">待处理bug以及优化</h3> <h4>打包后静态资源路径有问题(有没有大神能帮帮我QAQ)</h4> <h4>需要未读消息小红点</h4> <h4>增加表情、图片发送</h4> <p>最后: 这是本菜鸡陆陆续续做了一年的项目,多次放弃又重新拾起。代码写得不堪入目,没有精力和激情再去做优化了。暂时先这样吧......</p> <p></code></p>

本文固定链接: http://www.js-code.com/ios/ios_54930.html