项目面经

1.nokosocial社交网站

用户注册

后端创建用户表,创建校验的中间件,用户名密码通过校验后(是否重名),对于用户密码,使用node自带的库crypto进行md5加密然后next,将用户数据插入数据库。

用户登录

  • 后端拿到用户信息,通过校验的中间件,在数据库查找是否有该用户。密码是否正确,通过校验,使用jwt颁发验证token(http是无状态的,需要登录凭证(为什么不用coookie和session,因为coolie会附着在每个请求上,无形中增加了流量,cookie是明文传输的,存在安全性问题,cookie限制4k)。使用的是非对称加密,私钥颁发令牌,公钥验证令牌。(可以设置过期时间)。在返回的数据中携带上token。

  • 前端登录成功后得到token,以及用户信息保存早pina中,并且做持久化存储,可以在axios请求拦截器中在请求头添加token。

  • 对于验证用户是否有登录,后端添加校验的中间件,token在请求头的authorization中,如果用户根本没有传入token,就根本没有authorization这个字段,注意,要去除掉Bearer,jwt.verify(公钥)进行校验,通过将用户信息保存到 ctx.user await next()。

  • 对于用户是否有权限跳转到某一页面,在路由前置守卫中进行判断。

发表动态

  • 后端首先验证用户是否登录(有无token),调用校验token的中间件。通过,拿到用户id和content ,将他们插入数据库。

  • 动态配图上传,后端使用koa-multer处理文件上传,将文件保存到服务器文件夹中,动态配图可以是多张,并且在获取图片的时候可以展示大小不同的图片。要对上传的图片做处理(变化为3张尺寸不同的图片),需要使用到jimp,对fiel进行遍历,resize图片大小给图片尾部拼接small,middle,large。注意要拼接图片的url保存起来。

const pictureResize = async (ctx, next) => {
  console.log(ctx.req.files)
  const files = ctx.req.files
  for (let file of files) {
    const destination = path.join(file.destination, file.filename)
    // 读取到图片,得到一个image对象,写入对应文件夹
    jimp.read(file.path).then(image => {
      // 高度自适应
      image.resize(1280, jimp.AUTO).write(`${destination}-larg`)
      image.resize(720, jimp.AUTO).write(`${destination}-middle`)
      image.resize(320, jimp.AUTO).write(`${destination}-small`)
    })
  }
  await next()
}
  • 获取图片则是通过flename加图片尺寸获取,返回数据前设置响应类型为图片,然后读取文件,返回。图片创建了一个表来保存基本信息。

  • 动态配图是动态发表之后添加的,所以图片表里还应记录动态的id。前端这边使用到element的upload组件,获取到图片后,new一个fomdata,append图片进去,通过data传入整个fomdata,发起请求。

发表评论

主要是在后端建表的时候注意,分为几种情况,一是动态下的评论,二是对于评论的评论,当动态删除,评论也应该删除,评论删除,评论的评论也应该删除。

//分为几种情况,一是动态下的评论,二是对于评论的评论,当动态删除,评论也应该删除,评论删除,评论的评论也应该删除
CREATE TABLE IF NOT EXISTS `comment`(
	id INT PRIMARY KEY AUTO_INCREMENT,
	content VARCHAR(1000) NOT NULL,
	moment_id INT NOT NULL,
	user_id INT NOT NULL,
	comment_id INT DEFAULT NULL,//评论的评论相关信息,有,评论id,无,null
	
	FOREIGN KEY(moment_id) REFERENCES `moment`(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(user_id) REFERENCES `user`(id) ON DELETE CASCADE ON UPDATE CASCADE,
	FOREIGN KEY(comment_id) REFERENCES `comment`(id) ON DELETE CASCADE ON UPDATE CASCADE
);

点赞

主要是后端表设计,是一个多对多的关系,在动态与用户之间创建一个关系表,记录那个用户点赞了那条动态。

2.React新闻发布管理系统

根据后端返回的数据递归动态渲染menu,后端返回有key,点击根据key进行页面的跳转。

function renderMenu(list) {
    let arr = []
    list.map((item) => {
      if (item.children && item.children.length !== 0) {
        return arr.push(getItem(item.title, item.key, renderMenu(item.children)))
      } else {
        return item.pagepermission && arr.push(getItem(item.title, item.key))
      }
    })
    return arr
  }

image

刷新后原来点击项仍然高亮

通过 const location = useLocation() // 接收跳转传过来的值(path,state)获取当前url地址,作为参数传入。
image

角色列表中分配权限

主要就是树形控件的制作,根据后端返回的权限列表数据,渲染初始状态的树形控件。
image
点击显示对话框的时候,获得当前角色信息(里面包含自己拥有的权限)的权限,保存为state(currentRights),用以渲染勾选状态。

// 当前点击的某一角色,获取他具有的权限(来自用户数据)
  const [currentRights, changeCurrentRights] = useState([]) 


  // 处理对话框
  const [isModalOpen, setIsModalOpen] = useState(false)
  // 打开对话框 当前点击的某一角色,获取他具有的权限(来自用户数据)
  const showModal = (item) => {
    setIsModalOpen(true)
    changeCurrentRights(item.rights)
    setRoleId(item.id) // 角色id
  }
  // 点击勾选或取消勾选 返回的是剩余的权限
  const onCheck = (checkKeys) => {
    changeCurrentRights(checkKeys.checked) //赋值给当前应该勾选的权限
  }

点击切换勾选状态,调用组件自身的方法,获得剩余权限,给currentRights。
点击ok,根据先获取到的角色id,更改角色数据里的权限数据。然后更改后端数据。

// 点击确定修改
  const handleOk = () => {
    console.log(rolesData)
    setIsModalOpen(false)
    // 同步前端数据(修改角色数据中的权限)
    setTableData(tableData.map((item) => {
      if (item.id == roleId) {
        return {
          ...item,
          rights: currentRights // 替换
        }
      } else {
        return item
      }
    }))
    // 发起后端修改
    zrequest.request({
      method: 'patch',
      url: `/roles/${roleId}`,
      data: {
        rights: currentRights
      }
    })

用户列表

动态路由

有一个问题,比如区域管理员登录后,自己只能看到自己有权限的东西,侧边栏没有的东西不会显示出来,但是,可以通过地址栏访问到。因为已经把所有的路由匹配关系创建好了。
解决:

  1. 通过路由导航守卫解决。
  2. 通过动态创建路由匹配关系解决。

因为用户的权限是可以随意修改的,所以用动态创建路由匹配比较好。

使用自定义hooks函数,拿到用户具有的权限,与根据所有权限创建出来的路由映射表,做一个匹配,返回匹配后的路由表。放在routes数组中。

import React, { useEffect, useState } from 'react'
import zrequest from '../service'

// 自定义钩子函数
export const useUtiles = () => {

  let { role } = JSON.parse(localStorage.getItem('token')) // 个人具备的权限

  const [allRights, setAllRights] = useState([]) // 所有权限 这里其实没啥用
  useEffect(() => {
    Promise.all([
      zrequest.request({
        method: 'get',
        url: '/rights'
      }),
      zrequest.request({
        method: 'get',
        url: '/children'
      })
    ]).then((res) => {
      setAllRights([...res[0].data, ...res[1].data])
    })
  }, [])

  // 路由懒加载
  const lazyLoad = (path) => {
    const Comp = React.lazy(() => import(`../views/sendBox/${path}`))
    return (
      <React.Suspense fallback={<>加载中...</>}>
        <Comp></Comp>
      </React.Suspense>
    )
  }

  let routRelation = [
    {
      path: 'home',
      element: lazyLoad('home/Home')
    },
    {
      path: 'user-manage/list',
      element: lazyLoad('user-manager/UserList')
    },
    {
      path: 'right-manage/role/list',
      element: lazyLoad('right-manager/RoleList')
    },
    {
      path: 'right-manage/right/list',
      element: lazyLoad('right-manager/RightList')
    },
    {
      path: 'news-manage/add',
      element: lazyLoad('news-manager/WriteNews')
    },
    {
      path: 'news-manage/draft',
      element: lazyLoad('news-manager/DraftBox')
    },
    {
      path: 'news-manage/category',
      element: lazyLoad('news-manager/NewCategory')
    },
    {
      path: 'audit-manage/audit',
      element: lazyLoad('audit-manager/AuditNews')
    },
    {
      path: 'audit-manage/list',
      element: lazyLoad('audit-manager/AuditList')
    },
    {
      path: 'publish-manage/unpublished',
      element: lazyLoad('publish-manager/Released')
    },
    {
      path: 'publish-manage/published',
      element: lazyLoad('publish-manager/ReadyPublish')
    },
    {
      path: 'publish-manage/sunset',
      element: lazyLoad('publish-manager/Offline')
    }
  ]

  let arr = []
  routRelation.map((item) => {
    if (role.rights.includes('/' + item.path)) {
      return arr.push(item)
    }
  })
  return arr
}

export default function YRouter() {
  const element = useRoutes([
    {
      path: '/',
      element: <Navigate to={'/home'}></Navigate>
    },
    {
      path: '/login',
      element: <Login></Login>
    },
    {
      path: '/',
      element: <SendBox></SendBox>,
      children: [
        {
          path: '',
          element: lazyLoad('home/Home')
        },
        // 动态路由
        ...useUtiles(),
        {
          path: '*',
          element: lazyLoad('notFound/NotFound')
        }
      ]
    },
  ])

  return (
    element
  )
}

撰写新闻/审核管理

逻辑:撰写新闻,可保存到草稿箱,或直接提交审核。其实就是修改auditState字段。在草稿箱中显示的都是auditState为1的。草稿箱中可选择提交审核,就是修改auditState为2(审核中),在审核列表可查看。审核列表中可以看到自己有权限(过滤数据)看到的新闻,包括各个状态,已通过,未通过,审核中,可修改。

发布管理

使用了封装的公共组件和自定义hooks

使用插槽传递不同的按钮,使用hooks传入不同type请求不同table数据。

在点击按钮的时候,如何获取每一列的id?

使用插槽的传值。

 {props.children && props.children(item2.id)}
<NewsPublish tabData={tabData}>
        {
          (id) => {
            return <Button type='primary' onClick={() => publish(id)}>发布</Button>
          }
        }
      </NewsPublish>

发起网络请求的方法放在hooks中一起导出,需要使用到的地方导入对应的方法。(学习这种思想)

自定义hook

import { useState, useEffect } from 'react'
import { message } from 'antd'
import zrequest from "../../service"


// 自定义hook
function usePublish(type) {
  const { username } = JSON.parse(localStorage.getItem('token'))
  const [tabData, setTabData] = useState([])
  useEffect(() => {
    zrequest.request({
      method: 'get',
      url: `/news?author=${username}&publishState=${type}&_expand=category`
    }).then((res) => {
      setTabData(res.data)
    })
  }, [username])

  // 发布
  const thePublish = (id) => {
    setTabData(tabData.filter(item => item.id !== id))
    zrequest.request({
      method: 'patch',
      url: `/news/${id}`,
      data: {
        'publishState': 2,
        'publishTime': Date.now()
      }
    }).then(() => {
      message.success('发布成功,您可以到【发布管理/已发布】中查看')
    })
  }
  // 下线
  const sunSet = (id) => {
    setTabData(tabData.filter(item => item.id !== id))
    zrequest.request({
      method: 'patch',
      url: `/news/${id}`,
      data: {
        'publishState': 3,
      }
    }).then(() => {
      message.success('下线成功,您可以到【发布管理/已下线】中查看')
    })
  }
  // 删除
  const theDelete = (id) => {
    setTabData(tabData.filter(item => item.id !== id))
    zrequest.request({
      method: 'delete',
      url: `/news/${id}`,
    }).then(() => {
      message.success('删除成功')
    })
  }
  return {
    tabData,
    thePublish,
    sunSet,
    theDelete
  }
}
export default usePublish
posted @ 2022-11-28 11:20  yunChuans  阅读(29)  评论(0编辑  收藏  举报