前情提要:
React全Hook项目实战在线聊天室历程(一):基本功能 React全Hook项目实战在线聊天室历程(二):引用与话题功能 React全Hook项目实战在线聊天室历程(三):加个音乐直播?
管理员的权限
只要是在线提供服务的网站一定会有管理员的存在,那我们也给聊天室加一个管理员,不过简单一些,这个管理员只有删帖的功能。
实现思路
为了简单,直接使用SubmitContainer来作为登录窗口,后端根据“tag”属性是否等于特定字符串,来判断前端发来的消息是否是登录的账号密码。登录成功后,后端生产token,前端保存,使用删帖操作时需要带上token,否则无法执行删帖操作。
后端
后端引入jsonwebtoken
npm install jsonwebtoken
修改数据库message表,增加一个字段deleted 默认值为0,被删除时值为1。 修改db.js
function deleteMessage(no, callback) {
let sql = "UPDATE message SET deleted=1 where no=?"
try {
conn.query(sql, [no], (err, res) => {
if (err) {
callback(undefined);
} else {
callback(res);
}
})
} catch (error) {
}
}
function getMessage(callback) {
let sql = "select * from ( SELECT * FROM message where deleted=0 order by no desc limit 50) as tmp order by no ;"
try {
conn.query(sql, (err, res) => {
if (err) {
callback(undefined);
} else {
callback(res);
}
})
} catch (error) {
}
}
function getMessageByNo(no, callback) {
let sql = "select * from message where no=? and deleted != 1;"
try {
conn.query(sql, [no], (err, res) => {
if (err) {
callback(undefined);
} else {
callback(res);
}
})
} catch (error) {
}
}
修改ws.js
var jwt = require('jsonwebtoken');
const secret = "hello"
router.post("/deleteMsg", (req, res) => {
const token = req.headers.token
try {
var decode = jwt.verify(token, secret)
db.deleteMessage(req.body.no, (result) => {
res.end(JSON.stringify({
success: true,
code: 200,
msg: 'success'
}))
bc(clientList, JSON.stringify({
type: 'delete',
no: req.body.no
}))
})
} catch (error) {
console.log("解读出错", token, error)
res.end(JSON.stringify({
success: false,
code: 401,
msg: 'Token 无效'
}))
}
})
router.post("/post", (req, res) => {
form.parse(req, (err, fields, files) => {
try {
if (fields.tag[0] === "Login") {
if (fields.name[0] === 'admin' && fields.msg[0] === 'admin') {
var token = jwt.sign({ name: fields.name[0] }, secret, { expiresIn: 60 });
console.log("进入登录模式", token)
res.end(JSON.stringify({
"success": true,
"token": token
}))
return
}
}
还需要修改app.js
app.all('*', function (req, res, next) {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "Content-Type,Content-Length,Authorization,token,Accept,X-Requested-With");
res.setHeader("Access-Control-Allow-Methods", "PUT,POST,GET,DELETE,OPTIONS");
if (req.method == "OPTIONS") res.send(200);
else next();
});
前端
token可以储存在sessionStorage,localStorage,我这边继续使用React.Context来储存token。
修改context.js
export const UserTokenContext = createContext(null);
修改app.js
import { HandleClickNoContext, HandleChangeTagContext, UserTokenContext } from './util/contexts';
function msgListReducer(state, action) {
switch (action.type) {
case 'delete':
return { list: state.list.filter(x => x.no !== action.data) }
}
}
const [token, setToken] = useState("");
function handleSubmit(msg) {
let $data = new FormData();
for (let k in msg) {
$data.append(k, msg[k]);
}
fetch("http://localhost:8080/ws/post", {
method: "post",
body: $data
}).then(resp => {
console.log(resp)
return resp.json()
})
.then(res => {
handleChangeTag(msg["tag"])
if (res.token) {
setToken(res.token)
}
})
}
const ws = useRef(null);
useEffect(() => {
let randomId = createRandomId()
setRandomId(randomId);
if (Object.is(ws.current, null)) {
ws.current = new ReconnectingWebSocket("ws://localhost:8080/ws?id=" + randomId)
ws.current.onmessage = function (msg) {
let data = JSON.parse(msg.data);
switch (data.type) {
case 'msg':
setMsgList({ type: 'add', data: data });
break;
case 'onlineList':
setUserList(data.data.map(x => { return { id: x } }))
break;
case 'song':
myRadioRef.current?.setMusic(data.song)
break;
case 'delete':
setMsgList({ type: 'delete', data: data.no });
break;
default:
break;
}
}
<UserTokenContext.Provider value={[token, setToken]}>
<MsgContainer className="grid-item1" msgList={msgListWithTag} ></MsgContainer>
<UserListContainer userList={userList}></UserListContainer>
<MyRadio ref={myRadioRef}></MyRadio>
<SubmitContainer ref={submitRef} onSubmit={handleSubmit} id={randomId}></SubmitContainer>
<TagListContainer tagList={tagList}></TagListContainer>
</UserTokenContext.Provider>
修改MsgBox.js
import { HandleClickNoContext, UserTokenContext } from "../../util/contexts";
const [token,] = useContext(UserTokenContext)
function delMsg(no) {
fetch("http://localhost:8080/ws/deleteMsg", {
method: 'post',
headers: {
"Content-Type": "application/json",
token: token,
},
body: JSON.stringify({ no: no })
}).then(resp => {
console.log(resp)
return resp.json()
}).then(res => {
console.log(res)
}).catch(e => {
console.log(e)
})
}
<div className="msg-box_head">
<span className="msg-box_name">{props.name || "无名氏"}</span>
<span className="msg-box_time">{props.time}</span>
<span className="msg-box_id">ID:{props.id}</span>
<span className="msg-box_tag">#{props.tag || '综合'}</span>
<span className="msg-box_no" onClick={() => handleClickNo(props.no)}
>No.{props.no}</span>
{token && (<span className="msg-box_no" onClick={() => delMsg(props.no)}>[删除]</span>)}
</div>
后记
这系列文章是希望读者(包括我)能快速了解熟悉React 官方Hook的基本用法以及功能,希望读者看完这系列文章能有所收获。
如果本文中有错误的地方希望读者在评论区直接指出。
|