学习笔记,前端jwt的代码实现

这是一篇学习笔记,记录课程中前端JWT的实现代码
慕课网 vue3+ts 仿知乎专栏项目

首先页面大概分为三类

  • redirectAlreadyLogin:登录后下无法访问,包括登录页和注册页,如果登录后访问需要跳转到首页。
  • requiredLogin: 只有登录后才能访问的页面
  • 其他: 谁都能看的页面,比如首页

有两个 action 来发起请求

  • login: 用户提交登录,获取token,将token存入vuex(vue的全局状态管理工具),localStorage 和Authorization请求头
  • fetchCurrentUser: 携带token获取用户信息
    但是因为用户登录后必定需要获取用户信息,所以创建一个额外的action loginAndFetch 来同时完成login和fetchCurrentUser

// vuex
import { createStore, Commit } from 'vuex'
import axios from 'axios'
// ts描述用户信息
export interface UserProps {
  isLogin: boolean;
  nickName?: string;
  _id?: string;
  column?: string;
  email?: string;
}
// ts描述错误信息
export interface GlobalErrorProps {
  status: boolean;
  message?: string;
}
// ts描述store信息
export interface GlobalDataProps {
  error: GlobalErrorProps;
  token: string;
  loading: boolean;
  user: UserProps;
}
// 用于发送get信息,并commit mutations
const getAndCommit = async (url: string, mutationName: string, commit: Commit) => {
  const { data } = await axios.get(url)
  commit(mutationName, data)
  return data
}
// 用于发送post信息,并commit mutations
const postAndCommit = async (url: string, mutationName: string, commit: Commit, payload: any) => {
  const { data } = await axios.post(url, payload)
  commit(mutationName, data)
  return data
}
const store = createStore<GlobalDataProps>({
  state: {
    error: { status: false }, // 错误信息
    token: localStorage.getItem('token') || '', // 从localStorage中获取token
    loading: false,  // loading状态
    user: { isLogin: false } // 用户信息
  },
  mutations: {
// setLoading 和 setError: 分别用于设置 loading 和 error
// login:用户登录后,将token写入 state,localStorage 和 Authorization请求头 中
// fetchCurrentUser: 请求用户信息成功后,将登录状态切换为true, 用户信息写入 state
// logout: token伪造或者用户退出登录时执行,与login的操作相反
    setLoading(state, status) {
      state.loading = status
    },
    setError(state, e: GlobalErrorProps) {
      state.error = e
    },
    fetchCurrentUser(state, rawData) {
      state.user = { isLogin: true, ...rawData.data }
    },
    login(state, rawData) {
      const { token } = rawData.data
      state.token = token
      localStorage.setItem('token', token)
      axios.defaults.headers.Authorization = `Bearer ${token}`
    },
    logout(state) {
      state.token = ''
      state.user = { isLogin: false }
      localStorage.removeItem('token')
      delete axios.defaults.headers.common.Authorization
    }
  },
  actions: {
// 从上面的流程图就可以发现,用户登录,服务器返回token后,必定需要请求用户信息
// 那么在 loginAndFetch 中将 login 和 fetchCurrentUser 两个 action 合并
// 这样,只要 store.dispatch('loginAndFetch') 就可以同时完成登录加获取信息两个请求了
    fetchCurrentUser({ commit }) {
      return getAndCommit('/user/current', 'fetchCurrentUser', commit)
    },
    login({ commit }, payload) {
      return postAndCommit('/user/login', 'login', commit, payload)
    },
    async loginAndFetch({ dispatch }, loginData) {
      await dispatch('login', loginData)
      return dispatch('fetchCurrentUser')
    }
  }
})

export default store

// router 管理路由,使用 meta 将页面进行分类,设置路由导航守卫
import Home from './views/Home.vue'
import Login from './views/Login.vue'
import Signup from './views/Signup.vue'
import ColumnDetail from './views/ColumnDetail.vue'
import CreatePost from './views/CreatePost.vue'
import store from './store'
import axios from 'axios'
const routerHistory = createWebHistory()
const router = createRouter({
  history: routerHistory,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/login',
      name: 'login',
      component: Login,
      meta: { redirectAlreadyLogin: true }
    },
    {
      path: '/signup',
      name: 'signup',
      component: Signup,
      meta: { redirectAlreadyLogin: true }
    },
    {
      path: '/create',
      name: 'create',
      component: CreatePost,
      meta: { requiredLogin: true }
    },
    {
      path: '/column/:id',
      name: 'column',
      component: ColumnDetail
    }
  ]
})
router.beforeEach((to, from, next) => {
  const { user, token } = store.state
  const { requiredLogin, redirectAlreadyLogin } = to.meta
  if (!user.isLogin) {
    if (token) {
      axios.defaults.headers.common.Authorization = `Bearer ${token}`
      store.dispatch('fetchCurrentUser').then(() => {
        if (redirectAlreadyLogin) {
          next('/')
        } else {
          next()
        }
      }).catch(e => {
        store.commit('logout')
        next('login')
      })
    } else {
      if (requiredLogin) {
        next('login')
      } else {
        next()
      }
    }
  } else {
    if (redirectAlreadyLogin) {
      next('/')
    } else {
      next()
    }
  }
})


export default router

// Login.vue 用户登录
<script lang="ts">
import { defineComponent, ref } from 'vue'
import { useStore } from 'vuex'
import { useRouter } from 'vue-router'
import createMessage from '../components/createMessage'

export default defineComponent({
  name: 'Login',
  setup() {
    const router = useRouter()
    const store = useStore()
    const userName = ref('')
    const password = ref('')
    // 用户点击登录
    // loginAndFetch 会依次执行 login 和 fetchCurrentUser
    const onFormSubmit = async (result: boolean) => {
      if (result) {
        const payload = {
          email: emailVal.value,
          password: passwordVal.value
        }
        try {
          await store.dispatch('loginAndFetch', payload)
          createMessage('登录成功', 'success')
          router.push('/')
        } catch (e) {
          console.log('登录失败了...')
        }
      }
    }
    return {
    //.......
    }
  }
})
</script>

// 退出登录只需要commit('logout')就行了
store.commit('logout')
// main.ts
// axios 的配置项,主要是通过拦截器设置 loading 和 error 状态
import { createApp } from 'vue'
import axios from 'axios'
import router from './router'
import store from './store'

import App from './App.vue'
axios.defaults.baseURL = '********'

axios.interceptors.request.use(config => {
// request时 开启loading, 复位 error
  store.commit('setLoading', true)
  store.commit('setError', { status: false, message: '' })
  return config
})

axios.interceptors.response.use(config => {
// response时 关闭 Loading, 如果失败设置 error
  store.commit('setLoading', false)
  return config
}, e => {
  const { error } = e.response.data
  store.commit('setError', { status: true, message: error })
  store.commit('setLoading', false)
  return Promise.reject(error)
})
const app = createApp(App)
app.use(router)
app.use(store)
app.mount('#app')

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值