cms之day4(vue父子传值、watch;model form自定义验证、路由守卫;session、token鉴权登录)

本文探讨了Vue.js中的父子组件传值、watch与computed的区别,以及Ant Design Vue表单验证。同时,详细介绍了JWT鉴权的实现,包括在前端存储token、路由守卫和无感刷新token的策略。
摘要由CSDN通过智能技术生成

day4

1. axios ant-design vue前端

1. vue父子组件传值
  1. 子传父

    // 子组件 search.vue 提交时emit
    
    handleSubmit() {
          // 传递name至父组件
          this.$emit('searchName', this.search.name)
        }
    
    // 父组件 manageList.vue
     <search v-on:searchName="getSearchName" />
       getSearchName(name) {
          console.log(name)
        }
    
  2. 父传子

    今天暂时没用到,链接粘在这里 可可爱爱

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrJEgOmT-1639822573992)(C:\Users\26706\Desktop\20210415222059_1bd06.webp)]

    传送门

2. vuewatch

一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。Vue 实例将会在实例化时调用 $watch(),遍历 watch 对象的每一个属性。

 watch: {
            num(newVal, oldVal) {
            // 监听 num 属性的数据变化
    		// 作用 : 只要 num 的值发生变化,这个方法就会被调用
    		// 第一个参数 : 新值
    		// 第二个参数 : 旧值,之前的值
                console.log('oldVal:',oldVal)
                console.log('newVal:',newVal)
            }
        }
 watch: {
            num: {
            	// 数据发生变化就会调用这个函数  
                handler(newVal, oldVal) {
                    console.log('oldVal:', oldVal)
                    console.log('newVal:', newVal)
                },
                // 立即处理 进入页面就触发
                immediate: true
            }
        }

对象和数组都是引用类型,引用类型变量存的是地址,地址没有变,所以不会触发watch。这时我们需要进行深度监听,就需要加上一个属性 deep,值为 true

 watch: {
            num: {
            	// 数据发生变化就会调用这个函数  
                handler(newVal, oldVal) {
                    console.log('oldVal:', oldVal)
                    console.log('newVal:', newVal)
                },
                // 立即处理 进入页面就触发
                immediate: true
                deep:true
            }
        }

3. watch和computed区别
watch 观察监听页面上的vue实例. eg.const vm=new vue({}),当需要在数据变化响应时,执行异步操作,或高性能操作,选择watch。

computed:关联多个实时计算的对象,当这个对象中的其中一个改变时都会触发这个属性;具有缓存能力。
4. ant deisgn vue使用model form时自定义验证
// addManager.vue 
rules: {
        mobile: [{ required: true, trigger: ['change', 'blur'], validator: tools.validatorMobile }],
        email: [{ required: true, trigger: ['change', 'blur'], validator: tools.validatorEmail }]
      },
       psw: [{ required: true, trigger: ['blur', 'change'], validator: tools.validatorPsw }],

// too.js
// 验证手机号 FormModel
  validatorMobile(rule, value, callback) {
    // 如果为空值,就抛出错误
    if (!value) {
      callback(new Error('请输入手机号!'))
    } else {
      // 正则判断手机号格式的格式,正则判断失败抛出错误,否则直接callback()
      if (!/^1[2-9]\d{9}$/.test(value)) {
        callback(new Error('手机号格式不正确!'))
      } else {
        callback()
      }
    }
  },
  //   验证邮箱 FormModel
  validatorEmail(rule, value, callback) {
    // 正则判断手机号格式的格式,正则判断失败抛出错误,否则直接callback()
    if (value) {
      if (!/^[a-zA-Z0-9_.-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z0-9]{2,6}$/.test(value)) {
        callback(new Error('邮箱格式不正确!'))
      } else {
        callback()
      }
    }
  },
 // 密码至少包含 数字和英文,长度6-20
  validatorPsw(rule, value, callback) {
    if (value) {
      if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/.test(value)) {
        callback(new Error('密码至少包含 数字和英文,长度6-20!'))
      } else {
        callback()
      }
    } else {
      callback(new Error('请输入密码!'))
    }
  },

切记:如果对表单信息设置validator,其必须为required,否则submit失败

简单密码正则表达式

5. 为每条post请求增加add_time
// request.js
transformRequest: [
    data => {
        // 判断data原因是 get请求的data为undefined,无法增加add_time
      if (data) {
        data['add_time'] = tools.getUnix()
      }
      data = qs.stringify(data)
      console.log(data)
      return data
    }
  ],
    
6. 路由守卫已登录未退出不能去login
router.beforeEach((to, from, next) => {
  // 获取token
  const tokenStr = window.localStorage.getItem('token')
  if ((to.path === '/login' || to.path === '/') && tokenStr) {
    Vue.prototype.$message.warn('您已经登录')
    next('/main')
  } else {
    next()
  }
  if (!tokenStr) {
    return next('/login')
  } else {
    if (to.path !== '/login') {
      next()
    }
  }
})
7. ant deisgn vue 对话框使用
// main.js
import { Modal} from 'ant-design-vue'
import 'ant-design-vue/dist/antd.css'
Vue.prototype.$confirm = Modal.confirm

 // main.vue
 const that = this
      this.$confirm({
        title: '您确定要退出该账户吗?',
        content: '当退出后,您需要重新登录',
        okText: '确定',
        cancelText: '取消',
        onOk() {
          localStorage.removeItem('token')
          that.$router.replace('/login')
        },
        onCancel() {}
      })

2. node js应用程序生成器

1. get请求带参数,前端传参
// 前端 api.js 参数为{params}以使得在后台以req.query形式获取 此时不经过transformRequest
export const getManagerList = params => service.get('/', { params })
// 后端 index.js
router.get('/', async function (req, res, next) {
  let result
  if (req.query.username) {
    result = await ManagerModel.find({ username: req.query.username }, ['_id', 'username', 'email', 'mobile', 'status'])
  } else {
    result = await ManagerModel.find({}, ['_id', 'username', 'email', 'mobile', 'status'])
  }
  res.send(result)
})
2. session负载均衡,保存至数据库
const MongoStore = require('connect-mongo')
app.use(
  session({
    secret: 'keyboard cat', // 服务器端生成session的签名
    resave: false, // 强制保存session即使它没有变化
    saveUninitialized: true, // 强制将未初始化的session存储
    cookie: {
      maxAge: 1000 * 60 * 60,
      secure: false // true表示只有https协议才能访问
    },
    rolling: true, // 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
    store: MongoStore.create({
      mongoUrl: config.dbUrl
    })
  })
)
3. 使用session,进而后台根据session判断 处理未登录返回401 拦截器拦截
app.use('/', (req, res, next) => {
  var pathname = url.parse(req.url).pathname
  if (req.session.userinfo) {
  	// 还需与后台数据库比对
    next()
  } else {
    if (pathname === '/login' || pathname === '/doLogin') {
      next()
    } else {
      res.send(401)
    }
  }
})
  1. 在拦截器里进行res拦截。

    service.interceptors.response.use(
      response => {
        const status = response.status
        let msg = ''
        // 坏了,你没登录
        if (status === 401) {
            // 此处页面会闪动,并非实现真正拦截
          router.replace({ path: '/login' })
          return
        }
        if (status < 200 || status >= 300) {
          // 处理http错误,抛到业务代码
          msg = showStatus(status)
          if (typeof response.data === 'string') {
            response.data = { msg }
          } else {
            response.data.msg = msg
          }
        }
        return response
      },
      error => {
        // 错误抛到业务代码
        error.data = {}
        error.data.msg = '请求超时或服务器异常,请检查网络或联系管理员!'
        return Promise.resolve(error)
      }
    )
    

    登录后才能进入首页,以上结果其效果不太好,因为实际还是去了操作者想去的页面,也去后台获取了数据。只不过被强制转到login页面了。

4. token JWT 路由守卫 中间件 localStorage 鉴权
// jwt.js
const jwt = require('jsonwebtoken')
// 秘钥
const jwtSecret = '110futurenodemongodbvue'
// 加密 参数是要存入到token中的信息
exports.generateToken = (userName, userId) => {
  const token = jwt.sign({ userName, userId }, jwtSecret, { expiresIn: '24h' })
  return token
}
exports.getToken = token => {
  return new Promise((resolve, reject) => {
    if (!token) {
      reject({ error: 'token是空的' })
    } else {
      const info = jwt.verify(JSON.parse(token), jwtSecret)
      resolve(info) //解析返回的值
    }
  })
}

  1. 登录成功后,设置返回token
// login.js
router.post('/doLogin', async (req, res) => {
  let username = req.body.username
  let psw = req.body.psw
  let captcha = req.body.captcha
  if (Number(captcha) !== Number(req.session.captcha)) {
    res.send({ err: 1, msg: '验证码错误' })
    return
  }
  let result = await ManagerModel.find({ username: username })
  if (result.length > 0) {
    if (psw !== result[0].password) {
      res.send({ err: 1, msg: '密码错误' })
      return
    }
    const token = jwt.generateToken(username, result[0]._id)

    res.send({ token, err: 0, msg: '登录成功' })
  } else {
    res.send({ err: 1, msg: '用户不存在' })
    return
  }
})
  1. 在前端获取token,保存至本地
 postLoginInfo({ username: this.form.name, psw: this.$md5(this.form.psw), captcha: this.form.captcha }).then(function (res) {
            console.log(res)
            if (res.data.err !== 0) {
              // 刷新验证码
              that.$message.error(res.data.msg)
              return
            }
            localStorage.setItem('token', JSON.stringify(res.data.token))
            that.$message.success(res.data.msg)
            // 组件传name至main
            that.$router.push({ path: '/main' })
          })
        
  1. 在登录成功后,进入页面请求数据需要在headers里携带token返回,让服务器解密判断登录状态

    // request.js 拦截器
    service.interceptors.request.use(
      config => {
        if (localStorage.token) {
          config.headers.Authorization = localStorage.token //将token设置成请求头
        }
        return config
      },
      error => {
        // 错误抛到业务代码
        error.data = {}
        error.data.msg = '服务器异常,请联系管理员!'
        return Promise.resolve(error)
      }
    )
    
  2. 后台在调用接口时都会去看token是否存在,并解密是否成功,只有成功后才会返回需要的数据

    app.use((req, res, next) => {
      // 不需要进行验证的api,白名单
      var urlArr = ['/login', '/login/doLogin', '/register']
      if (urlArr.indexOf(req.url) >= 0) {
        next()
        return false
      }
      const token = req.headers['authorization']
      getToken(token)
        .then(data => {
          console.log(data)
          if (!data) {
            res.send({ err: 401, msg: 'token信息错误,不存在,过期' })
          } else {
            // 验证成功  放行
            next()
          }
        })
        .catch(error => {
          //  解析是空的
          res.send({ err: 401, msg: 'token信息错误,不存在,过期' })
        })
    })
    
  3. 在使用session时,在前端的拦截器里进行err判断,进而使用router.replace进行路由跳转至login,但其实该方法用户体验不好。

    所以决定使用路由守卫+message提示来跳转至login.

    /* 配置路由 */
    const routes = [
      { path: '/login', component: login, alias: '/' }, // /的别名是login
      {
        path: '/main',
        meta: {
          requireAuth: true // 该路由项需要权限校验
        },
        redirect: '/manager/managerList',
        component: main,
        children: [
          { path: '/manager/managerList', component: ManagerList },
          { path: '/manager/addManager', component: AddManager },
          { path: '/nav/navList', component: NavList },
          { path: '/nav/addNav', component: AddNav },
          { path: '/slide/slideList', component: SlideList },
          { path: '/slide/addSlide', component: AddSlide },
          { path: '/content/contentList', component: ContentList },
          { path: '/content/addContent', component: AddContent }
        ]
      }
    ]
    
    router.beforeEach((to, from, next) => {
      // if (to.path === '/login') return next()
      // 或者可以使用
      if (!to.meta.requireAuth) {
        return next()
      }
      // 获取token
      const tokenStr = window.localStorage.getItem('token')
      if (!tokenStr) return next('/login')
      next()
    })
    
5 JWT Token Session
  1. JSON Web Token,用户会话信息存储在客户端浏览器中,各方之间通过JSON对象安全传输信息,这些信息可以通过加密算法进行加密。

  2. Token指访问资源的凭据,是一种身份认证的方式,它是解决跨域认证的最流行的一种方式。

    ​ 通过session去做身份认证,session是通过服务器中保存会话数据来做身份认证,这种方式会导致在高并发中服务器压力过大的情况,还有就是,如果是服务器集群,那么就需要这些服务器session共享。

  3. 传统token与JST区别

- 传统Token

  用户发起登录请求,登录成功之后返回Token,并且存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后和数据库中的对比。

- JWT

  用户发起登录请求,登录成功之后返回Token,但是不存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后去校验Token的合法性。

```javascript
HMACSHA256(
    base64UrlEncode(header) + "." +
    base64UrlEncode(payload),
    secret
)
```

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5r3hmV2R-1639822574000)(C:\Users\26706\AppData\Roaming\Typora\typora-user-images\image-20211218163126566.png)]
6. JWT中token存储在哪里

传送门

7. token相较于session而言的优点

传送门

8. 实现无感刷新token

传送门

​ 评论说:返回一个access_token和refresh_token,每次请求带上这两个token,后端拦截器判断,先判断鉴权token是否有效和过期,如果有效的话,就允许访问。如果过期了,就判断刷新token是否有效,如果有效,就返回指定状态码,然后让前端根据这个状态码去吊用刷新token接口。如果刷新token失效了,就提示需要重新登录!

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值