宠物商店

196 阅读3分钟

前言

本人基本没有写过文章,所以文章写得烂。这算是第一次尝试为了写给别人看的文章了。 仓库地址

技术栈

typescript + socket.io + antd-mobile + react-group-transition + node + express

功能

  1. 实现登录,注册,持久化登录
  2. 头像上传和更换
  3. 实现主页宠物信息展示和信息提交
  4. 宠物点赞
  5. 实现了少量的页面过渡动画
  6. 实现用户信息通讯

功能实现

登录 持久化登录 注册

简要描述

  1. 我设计的mysql的user表字段为useranme password。username字段为主键。
  2. 注册页是上传信息,服务器检查username是否相同。相同则注册成功,否则失败。
  3. 登录页面,负责上传信息,服务器检查是否账号密码正确来确定是否通过jwt生成token返回给用户。

客户端

//注册逻辑
function finish(val: any) {
    const { username, password, verify } = val;
    if (password !== verify) {
        Toast.show({
            icon: 'fail',
            content: "两次密码不一致"
        })
    } else {
        setRequesting(true);
        registerRequest(username, password).then(({ data }) => {
            const { state } = data;
            console.log(data);
            if (state) {
                Toast.show({
                    icon: "success",
                    content: "注册成功",
                    afterClose() {
                        navigate("/login")
                    }
                })
            } else {
                Toast.show({
                    content: "用户名已存在"
                })
            }
            setRequesting(false);
        })
    }
}

//登录逻辑
const onFunished = (val: any) => {
    const { username, password } = val;
    setRequesting(true);
    loginRequest(username, password).then(({ data }) => {
        const { state, token } = data;
        if (state) {
            Toast.show({
                content: "登录成功",
                afterClose() {
                    navigate("/home");
                }
            });
            dispatch(changeUserInfo({
                username,
                token
            }))
             //自己封装了个本地储存类
            localCache.set("userInfo", {
                username,
                token
            });
         
        } else {
            Toast.show({
                content: "账号或密码错误",
            })
        }
        setRequesting(false);
    })
}

服务器端

async function userRegister(username, password) {
  const _sql = `INSERT INTO user SET username="${username}" , password="${password}";`;
  try {
    await query(_sql);
    return true;
  } catch (err) {
    return false;
  }
}
Router.post("/register", async (req, res) => {
  console.log("register");
  const { username, password } = req.body;
  const ans = await userRegister(username, password);
  res.send({
    state: ans,
  });
});
}

async function userLogin(username, password) {
  const _sql = `SELECT * FROM user WHERE username="${username}" AND password="${password}";`;
  const ans = await query(_sql);
  return ans;
}
Router.post("/login", async (req, res) => {
  const { username, password } = req.body;
  const ans = await userLogin(username, password);
  if (ans.length) {
    const token = generate(username);
    res.send({
      state: true,
      token,
    });
  } else {
    res.send({
      state: false,
    });
  }
});

//对于服务的前提条件是登录的请求的中间件
module.exports = (req, res, next) => {
  const token = req.header("Authorization");
  console.log("token", token);
  const username = verify(token);
  console.log(username);
  if (!username) {
    res.send({
      state: -1,
      message: "请登录",
    });
  } else {
    req.global = {
      username: username,
    };
    next();
  }
};

头像上传与更换

  • 头像更换中,我将头像存入静态数据文件夹中,图片名为用户名。用户上传时更换文件夹图片即可。
  • 服务端解析图片使用 connect-multiparty

客户端

//文件上传
export function changeAvatorRequest(file: File) {
  const Form = new FormData();
  Form.append("avator", file);
  return service.post("/user/changeAvator", Form, {
    headers: {
      "content-type": "application/x-www-form-urlencoded",
    },
  });
}

<input
    style={{ display: 'none' }}
    id="ipt"//负责与label联动
    type='file'
    accept='image/*'
    onChange={e => {
        e.preventDefault();
        const file = e.target.files![0];
        changeAvatorRequest(file).then(() => {
            setUpdataAvator(Date.now()) //责告诉客户端要更新头像照片了
        })
    }}
/>

服务端

//app文件
const app = express();
app.use(bodyParser.urlencoded({ extended: true })); // 解析form表单提交的数据application/x-www-form-urlencoded
app.use(multipart()); //解析form-data提交数据


module.exports.copyFile = function (from, to) {
  const file = fs.readFileSync(from);
  fs.writeFileSync(path.resolve(__dirname, to), file);
};
Router.post("/changeAvator", auth, async (req, res) => {
  const files = req.files;
  const { username } = req.global;
  utils.copyFile(
    files.avator.path,
    path.resolve(__dirname, `../public/avator/${username}.jpeg`)
  );
  console.log("changeAvator");
  console.log(files);
  res.send({
    state: 1,
  });
});

socket.io 实现连接和身份认证

  • 这里使用socket.io第三方库。并且新开了个服务器端口。
  • 客户端只存储提醒信息的消息
  • 我最开始使用的是express-ws。使用起来麻烦,并没有像http请求这样每次必有一个来回。所以我在发送消息方面仍然使用的是http发送消息

服务端

const express = require("express");
const app = express();
const http = require("http");
const server = http.createServer(app);
const { Server } = require("socket.io");
const { verify } = require("../utils/jwt");
const { receiverList } = require("../mysql/sqlService");
const io = new Server(server, {
  allowEIO3: true,
  cors: true,
});
const username_ws_hash = new Map();//用于存储在线用户的名字和socket, 用户名 - socket 
io.on("connection", async (socket) => {//这个是每次socket.io连接成功后触发的函数
  socket.global = {};
  socket.on("login", async (data, callback) => {
    const { token } = data;
    const username = verify(token);
    console.log(username, socket.id);
    if (!username) {
      callback({
        state: 0,
      });
    } else {
      username_ws_hash.set(username, socket);
      socket.global.username = username;
      const list = await receiverList(username);
      callback({
        state: 1,
        list,
      });
    }
  });
  socket.on("disconnection", (data) => {
    username_ws_hash.delete(socket.global.username);//下线时删除用户的在线列表
  });
  socket.on("connect_error", (e) => {//报错监听
    console.log("connect_error", e);
  });
});

客户端

import store from "@/store";
import io, { Socket } from "socket.io-client";

async function socketStart() {
  const socket = io("http://127.0.0.2:7070");
  let isLogin = false;
  socket.on("connect", () => {
    console.log(socket.id, "监听客户端连接成功-connect");
  });
  const userInfo = store.getState().userInfo;
  const token = userInfo.token;
  return new Promise<{ socket: Socket; userMessage: any }>(
    (resolve, reject) => {
      socket.emit("login", { token }, (data: any) => {
        const { state, list } = data;
        if (state == 1) {
          console.log("登录成功");
          isLogin = true;
        } else if (state == 0) {
          console.log("登录失败");
          isLogin = false;
        }
        resolve({
          socket,
          userMessage: list,
        });
      });
    }
  );
}
export default socketStart;