Javascript

React全Hook项目实战在线聊天室历程(完结):删帖功能

本文主要是介绍React全Hook项目实战在线聊天室历程(完结):删帖功能,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

前情提要:

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的基本用法以及功能,希望读者看完这系列文章能有所收获。

如果本文中有错误的地方希望读者在评论区直接指出。

这篇关于React全Hook项目实战在线聊天室历程(完结):删帖功能的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!