react+react-router+redux+Node.js+socket.io寫一個(gè)聊天webapp

一捞高、項(xiàng)目預(yù)覽

之前看一個(gè)寫聊天器的教程养筒,自己也跟著教程做了一遍,由于懶得去找圖片和一些圖標(biāo)我就用教程中的素材來做斩披,主要是用了react+react-router+redux+Node.js+socket.io的技術(shù)棧,接下來就是項(xiàng)目的預(yù)覽

1.首先在/login下能看到有登錄和注冊按鈕
登錄注冊頁
2.點(diǎn)擊注冊按鈕讹俊,路由跳到/register垦沉,注冊一個(gè)賬號(hào),用戶和密碼都為LHH仍劈,選擇“牛人”厕倍,點(diǎn)擊注冊,之后路由會(huì)跳到/geniusinfo贩疙,即牛人完善信息頁讹弯,選擇一個(gè)頭像并完善信息后點(diǎn)擊保存按鈕
注冊頁

完善信息頁
3.可以看到已經(jīng)進(jìn)入有三個(gè)tab選項(xiàng)的內(nèi)容頁面了,點(diǎn)擊“我”这溅,路由跳轉(zhuǎn)到/me即可看到個(gè)人中心內(nèi)容组民,但此時(shí)boss和消息的tab頁仍沒有內(nèi)容,可以按照之前步驟注冊一個(gè)Boss賬號(hào)悲靴,只需在注冊的時(shí)候選擇Boss選項(xiàng)
個(gè)人中心
4.現(xiàn)在在LHH和LCE賬號(hào)分別能看到的列表
列表
5.點(diǎn)擊進(jìn)入聊天室臭胜,輸入內(nèi)容
聊天室

二、接下來對項(xiàng)目的主要內(nèi)容進(jìn)行解釋

1.項(xiàng)目的除掉node_modules后的目錄
├─build
│  └─static
│      ├─css
│      └─js
├─config
│  └─jest
├─public
├─scripts
├─server
└─src
    ├─component
    │  ├─authroute
    │  ├─avatar-selector
    │  ├─boss
    │  ├─chat
    │  ├─dashboard
    │  ├─genius
    │  ├─img
    │  ├─logo
    │  ├─msg
    │  ├─navlink
    │  │  └─img
    │  ├─user
    │  └─usercard
    ├─container
    │  ├─bossinfo
    │  ├─geniusinfo
    │  ├─login
    │  └─register
    └─redux

其中build文件夾的內(nèi)容為npm run build打包后的內(nèi)容癞尚,在項(xiàng)目中如果啟用后端接口也可訪問

2.入口頁面
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import thunk from 'redux-thunk';
import { Provider } from 'react-redux';
// eslint-disable-next-line
import { BrowserRouter } from 'react-router-dom';
import App from './app'

import reducers from './reducer'
import './config'
import './index.css'

const store = createStore(reducers, compose(
    applyMiddleware(thunk),
    window.devToolsExtension?window.devToolsExtension():f=>f
))

// boss genius me msg 4個(gè)頁面
ReactDOM.render(
    (<Provider store={store}>
        <BrowserRouter>
            <App></App>
        </BrowserRouter>
    </Provider> ),
    document.getElementById('root')
 )

使用react-redux的Provider耸三,可實(shí)現(xiàn)全局的狀態(tài)存儲(chǔ),子組件可通過props獲得存儲(chǔ)在全局的狀態(tài)

const store = createStore(reducers, compose(
    applyMiddleware(thunk),
    window.devToolsExtension?window.devToolsExtension():f=>f
))

上面代碼的主要作用是關(guān)于配置瀏覽器的redux插件的浇揩,可以通過這個(gè)插件在控制臺(tái)中查看state中的數(shù)據(jù)仪壮。
來看下app.js中的代碼

import React from 'react'
import Login from './container/login/login.js';
import Register from './container/register/register.js';
import AuthRoute from './component/authroute/authroute.js';
import BossInfo from './container/bossinfo/bossinfo.js';
import Geniusinfo from './container/geniusinfo/geniusinfo';
import Dashboard from './component/dashboard/dashboard';
import Chat from './component/chat/chat'
import {  Route,  Switch } from 'react-router-dom';

class App extends React.Component{
    render() {
        return (
            <div>
                <AuthRoute></AuthRoute>
                <Switch>
                    <Route path='/bossinfo' component={BossInfo}></Route>
                    <Route path='/geniusinfo' component={Geniusinfo}></Route>
                    <Route path='/login' component={Login}></Route>
                    <Route path='/register' component={Register}></Route>
                    <Route path='/chat/:user' component={Chat}></Route>
                    <Route component={Dashboard}></Route>
                </Switch>
                
            </div>
        )
    }
}
export default App

這里主要是講主頁面中的代碼分割出來。
authroute.js中是路由跳轉(zhuǎn)的邏輯判斷
頁面中的UI組件也用到了antd-mobile插件
客戶端接收和傳送數(shù)據(jù)得引入socket.io-client胳徽,代碼在chat.redux.js中积锅。
聊天器中需要存儲(chǔ)在數(shù)據(jù)庫的內(nèi)容主要為from(發(fā)送端)爽彤、to(接收端)、read(是否已讀)乏沸、content(聊天內(nèi)容)淫茵、create_time(聊天時(shí)間)而且還需要一個(gè)唯一的chatid來代表這個(gè)聊天室的唯一性,可以用fromto拼接蹬跃,拼接函數(shù)寫在util.js中匙瘪。

3.Server

后端接口用到了node.jsexpress框架,數(shù)據(jù)庫用到了mongodb蝶缀,在server文件夾中存放連接數(shù)據(jù)庫的文件丹喻,model.js在直接與mongodb數(shù)據(jù)庫連接,

const mongoose = require('mongoose');
// 連接mongo,并且使用my_app這個(gè)集合
const DB_URL = "mongodb://localhost:27017/chat_app";
mongoose.connect(DB_URL);

const models = {
    user: {
        'user': { 'type': String, 'require': true },
        'pwd': { 'type': String, 'require': true },
        'type': { 'type': String, 'require': true },
        // 頭像
        'avatar': { 'type': String },
        // 個(gè)人簡介或者職位簡介
        'desc': { 'type': String },
        // 職位名
        'title': { 'type': String },
        // 如果是boss翁都,還有兩個(gè)字段
        'company': { 'type': String },
        'money': { 'type': String }
    },
    chat: {
        'chatid': { 'type': String, 'require': true },
        'from': { 'type': String, 'rewuire': true },
        'to': { 'type': String, 'require': true },
        'read': { 'type': String, 'require': true },
        'content': { 'type': String, 'require': true, 'default': '' },
        'create_time': { 'type': Number, 'default': new Date().getTime() }
    }
}
for (let m in models) {
    mongoose.model(m, new mongoose.Schema(models[m]))
}
module.exports = {
    getModel: function(name) {
        return mongoose.model(name)
    }
}

連接的數(shù)據(jù)庫端口號(hào)為27017碍论,這個(gè)視自己電腦的數(shù)據(jù)庫端口號(hào)而定。
server.js中引入了http柄慰、express鳍悠、socket.io插件,服務(wù)端用的是9093端口坐搔,

const express = require('express');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const model = require('./model')
    // const User = model.getModel('user');
const Chat = model.getModel('chat');
const path = require('path')
const app = express();
//work with express
const server = require('http').Server(app);
const io = require('socket.io')(server);
io.on('connection', function(socket) {
    // console.log('user login')
    socket.on('sendmsg', function(data) {
        const { from, to, msg } = data;
        const chatid = [from, to].sort().join('_');
        Chat.create({ chatid, from, to, content: msg }, function(err, doc) {
                // console.log(doc._doc)
                io.emit('recvmsg', Object.assign({}, doc._doc))
            })
            // console.log(data);
            // io.emit('recvmsg', data)
    })
})
const userRouter = require('./user');
app.use(cookieParser());
app.use(bodyParser.json())
app.use('/user', userRouter);
app.use(function(req, res, next) {
    if (req.url.startsWith('/user/') || req.url.startsWith('/static/')) {
        return next()
    }
    return res.sendFile(path.resolve('build/index.html'))
})
app.use('/', express.static(path.resolve('build')))
server.listen(9093, function() {
    console.log('Node app start at port 9093')
});

客戶端用到的接口寫在user.js

const express = require('express')
const Router = express.Router();
const model = require('./model')
const User = model.getModel('user');
const Chat = model.getModel('chat');
const _filter = { 'pwd': 0, '__v': 0 };

// 刪除所有聊天記錄
// Chat.remove({}, function(e, d) {})
// 加密
const utils = require('utility');
Router.get('/list', function(req, res) {
    const { type } = req.query
    // 刪除所有用戶
    // User.remove({}, function(e, d) {})
    User.find({ type }, _filter, function(err, doc) {
        return res.json({ code: 0, data: doc })
    })
});
Router.get('/getmsglist', function(req, res) {
    const user = req.cookies.userid;
    User.find({}, function(err, userdoc) {
        let users = {};
        userdoc.forEach(v => {
            users[v._id] = { name: v.user, avatar: v.avatar }
        })
        Chat.find({ '$or': [{ from: user }, { to: user }] }, function(err, doc) {
            // console.log(doc)
            if (!err) {
                return res.json({ code: 0, msgs: doc, users: users })
            }
        })
    })
})
Router.post('/readmsg', function(req, res) {
    const userid = req.cookies.userid;
    const { from } = req.body;
    // console.log(userid, from)
    Chat.update({ from, to: userid }, { '$set': { read: true } }, { 'multi': true },

        function(err, doc) {
            if (!err) {
                return res.json({ code: 0, num: doc.nModified })
            }
            return res.json({ code: 1, msg: '修改失敗' })
        })
})
Router.post('/update', function(req, res) {
    const userid = req.cookies.userid;
    if (!userid) {
        return json.dumps({ code: 1 });
    }
    const body = req.body;
    User.findByIdAndUpdate(userid, body, function(err, doc) {
        const data = Object.assign({}, {
            user: doc.user,
            type: doc.type
        }, body)
        return res.json({ code: 0, data })
    })
});
Router.post('/login', function(req, res) {
    const { user, pwd } = req.body;
    User.findOne({ user, pwd: md5Pwd(pwd) }, _filter, function(err, doc) {
        if (!doc) {
            return res.json({ code: 1, msg: '用戶名或者密碼錯(cuò)誤' });
        }
        res.cookie('userid', doc._id)
        return res.json({ code: 0, data: doc })
    })
});
Router.post('/register', function(req, res) {
    console.log(req.body);
    const { user, pwd, type } = req.body;
    User.findOne({ user }, function(err, doc) {
        if (doc) {
            return res.json({ code: 1, msg: '用戶名重置' })
        }
        const userModel = new User({ user, pwd: md5Pwd(pwd), type });
        userModel.save(function(e, d) {
            if (e) {
                return res.json({ code: 1, msg: '后端出錯(cuò)了' })
            }
            const { user, type, _id } = d;
            res.cookie('userid', _id)
            return res.json({ code: 0, data: { user, type, _id } })
        })
    })
})
Router.get('/info', function(req, res) {
    const { userid } = req.cookies;
    if (!userid) {
        return res.json({ code: 1 })
    }
    User.findOne({ _id: userid }, _filter, function(err, doc) {
            if (err) {
                return res.json({ code: 1, msg: '后端出錯(cuò)了' })
            }
            if (doc) {
                return res.json({ code: 0, data: doc })
            }
        })
        // 用戶有沒有cookie

});
// 密碼加鹽
function md5Pwd(pwd) {
    const salt = 'lhh_is_good_1310486!@#5^%~*';
    return utils.md5(utils.md5(pwd + salt))
}
module.exports = Router

三藏研、總結(jié)

本項(xiàng)目實(shí)現(xiàn)了獲取數(shù)據(jù)和表現(xiàn)的代碼分離,也是對于學(xué)習(xí)React概行、Node和WebSocket的一次更進(jìn)一步提升蠢挡,當(dāng)然還有很多可以改進(jìn)的地方,比如可以用asyncawait進(jìn)行異步獲取數(shù)據(jù)等等凳忙。
作為一名前端菜鳥业踏,還是希望前輩能給一些學(xué)習(xí)的建議和指點(diǎn)迷津
最后附上本項(xiàng)目的代碼鏈接
github鏈接

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市涧卵,隨后出現(xiàn)的幾起案子勤家,更是在濱河造成了極大的恐慌,老刑警劉巖柳恐,帶你破解...
    沈念sama閱讀 217,185評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件却紧,死亡現(xiàn)場離奇詭異,居然都是意外死亡胎撤,警方通過查閱死者的電腦和手機(jī)晓殊,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,652評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來伤提,“玉大人巫俺,你說我怎么就攤上這事≈啄校” “怎么了介汹?”我有些...
    開封第一講書人閱讀 163,524評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵却嗡,是天一觀的道長。 經(jīng)常有香客問我嘹承,道長窗价,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,339評(píng)論 1 293
  • 正文 為了忘掉前任叹卷,我火速辦了婚禮撼港,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘骤竹。我一直安慰自己帝牡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,387評(píng)論 6 391
  • 文/花漫 我一把揭開白布蒙揣。 她就那樣靜靜地躺著靶溜,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懒震。 梳的紋絲不亂的頭發(fā)上罩息,一...
    開封第一講書人閱讀 51,287評(píng)論 1 301
  • 那天,我揣著相機(jī)與錄音个扰,去河邊找鬼瓷炮。 笑死,一個(gè)胖子當(dāng)著我的面吹牛锨匆,可吹牛的內(nèi)容都是我干的崭别。 我是一名探鬼主播冬筒,決...
    沈念sama閱讀 40,130評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼恐锣,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了舞痰?” 一聲冷哼從身側(cè)響起土榴,我...
    開封第一講書人閱讀 38,985評(píng)論 0 275
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎响牛,沒想到半個(gè)月后玷禽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,420評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡呀打,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,617評(píng)論 3 334
  • 正文 我和宋清朗相戀三年矢赁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贬丛。...
    茶點(diǎn)故事閱讀 39,779評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡撩银,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出豺憔,到底是詐尸還是另有隱情额获,我是刑警寧澤够庙,帶...
    沈念sama閱讀 35,477評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站抄邀,受9級(jí)特大地震影響耘眨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜境肾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,088評(píng)論 3 328
  • 文/蒙蒙 一剔难、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧准夷,春花似錦钥飞、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,716評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至楔绞,卻和暖如春结闸,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酒朵。 一陣腳步聲響...
    開封第一講書人閱讀 32,857評(píng)論 1 269
  • 我被黑心中介騙來泰國打工桦锄, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人蔫耽。 一個(gè)月前我還...
    沈念sama閱讀 47,876評(píng)論 2 370
  • 正文 我出身青樓结耀,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匙铡。 傳聞我的和親對象是個(gè)殘疾皇子图甜,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,700評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容