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) { // console.log('-----------error---------'); // console.error(err); callback(undefined); } else { // console.log('------------success------------'); // console.log(res); 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) { // console.log('-----------error---------'); // console.error(err); callback(undefined); } else { // console.log('------------success------------'); // console.log(res); 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) { // console.log('-----------error---------'); // console.error(err); callback(undefined); } else { // console.log('------------success------------'); // console.log(res); callback(res); } }) } catch (error) { } }
修改ws.js
/* ...... */ // 引入jsonwebtoken var jwt = require('jsonwebtoken'); /* ...... */ // jsonwebtoken加密需要用到的密钥 const secret = "hello" /* ...... */ // 删除POST方法 router.post("/deleteMsg", (req, res) => { const token = req.headers.token // 请求头的token如果校验不通过 // 会抛出错误需要捕获 try { var decode = jwt.verify(token, secret) db.deleteMessage(req.body.no, (result) => { res.end(JSON.stringify({ success: true, code: 200, msg: 'success' })) // 广播被删除的No号 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") { // 如果话题tag是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
// 增加全局跨域处理,因为前端将会使用json作为请求内容,那么会先发送OPTIONS请求 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);/*让options请求快速返回*/ else next(); });
token可以储存在sessionStorage,localStorage,我这边继续使用React.Context来储存token。
修改context.js
export const UserTokenContext = createContext(null);
修改app.js
// 引入UserTokenContext 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"]) // 如果传回的消息有token则储存token if (res.token) { setToken(res.token) } }) } /* ...... */ // 初始化websocket 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的基本用法以及功能,希望读者看完这系列文章能有所收获。
如果本文中有错误的地方希望读者在评论区直接指出。