一捞高、項(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)
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è)聊天室的唯一性,可以用from
和to
拼接蹬跃,拼接函數(shù)寫在util.js
中匙瘪。
3.Server
后端接口用到了node.js
的express
框架,數(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)的地方,比如可以用async
和await
進(jìn)行異步獲取數(shù)據(jù)等等凳忙。
作為一名前端菜鳥业踏,還是希望前輩能給一些學(xué)習(xí)的建議和指點(diǎn)迷津
最后附上本項(xiàng)目的代碼鏈接
github鏈接