day4
1. axios ant-design vue
前端
1. vue
父子组件传值
-
子传父
// 子组件 search.vue 提交时emit handleSubmit() { // 传递name至父组件 this.$emit('searchName', this.search.name) }
// 父组件 manageList.vue <search v-on:searchName="getSearchName" /> getSearchName(name) { console.log(name) }
-
父传子
今天暂时没用到,链接粘在这里 可可爱爱
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrJEgOmT-1639822573992)(C:\Users\26706\Desktop\20210415222059_1bd06.webp)]
2. vue
之watch
一个对象,键是需要观察的表达式,值是对应回调函数。值也可以是方法名,或者包含选项的对象。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)
}
}
})
-
在拦截器里进行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) //解析返回的值
}
})
}
- 登录成功后,设置返回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
}
})
- 在前端获取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' })
})
-
在登录成功后,进入页面请求数据需要在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) } )
-
后台在调用接口时都会去看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信息错误,不存在,过期' }) }) })
-
在使用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
-
JSON Web Token,用户会话信息存储在客户端浏览器中,各方之间通过JSON对象安全传输信息,这些信息可以通过加密算法进行加密。
-
Token指访问资源的凭据,是一种身份认证的方式,它是解决跨域认证的最流行的一种方式。
通过session去做身份认证,session是通过服务器中保存会话数据来做身份认证,这种方式会导致在高并发中服务器压力过大的情况,还有就是,如果是服务器集群,那么就需要这些服务器session共享。
-
传统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失效了,就提示需要重新登录!