文章目录
day1
1. vue cli
与ant design vue
之前端建设
1. vue cli
项目搭建
vue create demo
2. 使用ant deisign vue
3. axios is not defined
cnpm i axios
// main.js
import axios from 'axios'
Vue.prototype.$axios = axios
//使用
this.$axios
.get('/login')
.then(res => {
console.log(res)
this.verify = res.data
})
.catch(err => {
console.error(err)
})
4.localhost 8080
接口访问3000
后台,因为端口不同,同源协议生效,导致报错。
// vue.config.js 没有自行创建
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:3000/',
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
// main.js
axios.defaults.baseURL = '/api'
// 使用
this.$axios
.get('/login')
.then(res => {
console.log(res)
this.verify = res.data
})
.catch(err => {
console.error(err)
})
5.axios
传参post
后台接收不到,前端修改如下,后台通过req.query
获取
this.$axios({
method: 'post',
url: '/login/doLogin',
params: {
username: this.form.name,
psw: this.$md5(this.form.psw),
captcha: this.form.captcha
},
headers: {
'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8'
}
}).then(function (res) {
console.log(res.data)
})
6. 路由router
使用
传送门
7.Cannot read property ‘$router‘ of undefined
传送门
2.npm 应用程序生成器之后台开发
1. 应用程序生成器
express --view=ejs expressGenerator
nodemon .\bin\www
2. app.js
配置
var createError = require('http-errors')
var express = require('express')
const ejs = require('ejs')
const session = require('express-session')
const config = require('./config/config')
var path = require('path')
var logger = require('morgan')
const admin = require('./routes/admin/index')
const login = require('./routes/login')
var app = express()
// 配置模板引擎
app.engine('html', ejs.__express)
app.set('view engine', 'html')
app.use(logger('dev'))
app.use(express.urlencoded({ extended: false }))
app.use(express.static('static'))
// 配置中间件
app.use(
session({
secret: 'keyboard cat', // 服务器端生成session的签名
name: 'captcha', // 修改session对应的cookie的名称
resave: false, // 强制保存session即使它没有变化
saveUninitialized: true, // 强制将未初始化的session存储
cookie: {
maxAge: 1000 * 60,
secure: false // true表示只有https协议才能访问
},
rolling: true // 在每次请求时强行设置cookie,这将重置cookie过期时间(默认:false)
})
)
// app.use('/' + config.adminPath, admin)
app.use('/', admin)
app.use('/login', login)
// 绑定全局模板变量
// app.locals.adminPath = config.adminPath
module.exports = app
3. svg-captcha
使用
cnpm i svg-captcha --save
var svgCaptcha = require('svg-captcha')
router.get('/', function (req, res, next) {
// 此处显示的是加减法运算哦
var captcha = svgCaptcha.createMathExpr({
width: 100,
height: 40,
background: 'white'
})
// 保存验证码
req.session.captcha = captcha.text
res.type('svg')
res.status(200).send(captcha.data)
console.log(captcha)
})
// 此处不知为何 vue前端src无法显示图片 使用v-html
<div v-html='verify'></div>
day2
1.vue ant-design
前端
1. vue router
子路由
const routes=[
{path:"/demo1/:id",component:demo1,props:true},//props为true时,可以将动态路由的值传递给相应的组件
{path:"/demo2",component:demo2},
//{path:"/demo3",component:demo3,name:"test"},//name属性是给路由起别名,方便用js的方式去跳转
{path:"/demo3/:id",component:demo3,name:"test"},//name属性是给路由起别名,方便用js的方式去跳转
{path:"/demo4",component:demo4,children:[
{path:"demo4-1",component:demo4a}
]},//name属性是给路由起别名,方便用js的方式去跳转
]
2. router 下的name属性用法
传送门
3. vue项目使用debugger
在package.json\eslintConfig\rules
里
"rules": {
"no-debugger": "off",
"no-console": "off"
}
2. node 应用程序生成器
1. mongoose
查询返回部分字段
const result = await ManagerModel.find({}, ['_id', 'username', 'email', 'mobile', 'status'])
day3
1. vue ant-design
前端
1. 封装post、get请求
2. axios 请求封装、拦截器
// request.js
import axios from 'axios'
import qs from 'qs'
const showStatus = status => {
let message = ''
switch (status) {
case 400:
message = '请求错误(400)'
break
case 401:
message = '未授权,请重新登录(401)'
break
case 403:
message = '拒绝访问(403)'
break
case 404:
message = '请求出错(404)'
break
case 408:
message = '请求超时(408)'
break
case 500:
message = '服务器错误(500)'
break
case 501:
message = '服务未实现(501)'
break
case 502:
message = '网络错误(502)'
break
case 503:
message = '服务不可用(503)'
break
case 504:
message = '网络超时(504)'
break
case 505:
message = 'HTTP版本不受支持(505)'
break
default:
message = `连接出错(${status})!`
}
return `${message},请检查网络或联系管理员!`
}
const service = axios.create({
// 联调
baseURL: process.env.NODE_ENV === 'production' ? `/` : '/api',
headers: {
get: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
},
post: {
'Content-Type': 'application/x-www-form-urlencoded;charset=utf-8'
}
},
// 是否跨站点访问控制请求
withCredentials: true,
timeout: 30000,
// 此处get请求不需要,因为本就是username=admin&psw=21232f297a57a5a743894a0e4a801fc3&captcha=14形式
transformRequest: [
data => {
data = qs.stringify(data)
return data // username=admin&psw=21232f297a57a5a743894a0e4a801fc3&captcha=14
}
],
validateStatus() {
// 使用async-await,处理reject情况较为繁琐,所以全部返回resolve,在业务代码中处理异常
return true
}
})
// 响应拦截器
service.interceptors.response.use(
response => {
const status = response.status
let msg = ''
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)
}
)
export default service
// api.js
import service from './request'
//login page to show captcha
export const getCaptcha = params => service.get('/login', params)
export const postLoginInfo = params => service.post('/login/doLogin', params)
// managerList
export const getManageList = params => service.get('/', params)
2. qs.stringify()与JSON.stringify()序列化区别
qs.stringify()
将对象 序列化成URL
的形式,以&
进行拼接。
JSON
是正常类型的JSON
let ObjectDemo = {name:'weixin',age:12};
qs.stringify(ObjectDemo)
// 'name=weixin&age=12'
JSON.stringify(ObjectDemo)
// '{"name":"weixin","age":12}'
3.transformRequest
和transformResponse
transformRequest:表示允许在向服务器发送前,修改请求数据
1. 只能用在 ‘PUT’, ‘POST’ 和 ‘PATCH’ 这几个请求方法
2. 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformResponse:在传递给 then/catch 前,允许修改响应数据
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-qQpWuAPG-1639821415972)(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对象安全传输信息,这些信息可以通过加密算法进行加密。
2. Token指访问资源的凭据,是一种身份认证的方式,它是解决跨域认证的最流行的一种方式。
通过session去做身份认证,session是通过服务器中保存会话数据来做身份认证,这种方式会导致在高并发中服务器压力过大的情况,还有就是,如果是服务器集群,那么就需要这些服务器session共享。
-
传统token与JST区别
-
传统Token
用户发起登录请求,登录成功之后返回Token,并且存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后和数据库中的对比。
-
JWT
用户发起登录请求,登录成功之后返回Token,但是不存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后去校验Token的合法性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PBBsQwIF-1639821415974)(C:\Users\26706\AppData\Roaming\Typora\typora-user-images\image-20211218163106332.png)]
HMACSHA256( base64UrlEncode(header) + "." + base64UrlEncode(payload), secret )
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-svtuaW2n-1639821415974)(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失效了,就提示需要重新登录!
无需鉴权 有token 去不了login
需鉴权 有token 首页
功之后返回Token,并且存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后和数据库中的对比。
- JWT
用户发起登录请求,登录成功之后返回Token,但是不存于数据库,用户访问资源的时候需要携带Token,服务端获取Token之后去校验Token的合法性。
[外链图片转存中...(img-PBBsQwIF-1639821415974)]
```javascript
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret
)
```
[外链图片转存中...(img-svtuaW2n-1639821415974)]
6. JWT中token存储在哪里
传送门
7. token相较于session而言的优点
传送门
8. 实现无感刷新token
传送门
评论说:返回一个access_token和refresh_token,每次请求带上这两个token,后端拦截器判断,先判断鉴权token是否有效和过期,如果有效的话,就允许访问。如果过期了,就判断刷新token是否有效,如果有效,就返回指定状态码,然后让前端根据这个状态码去吊用刷新token接口。如果刷新token失效了,就提示需要重新登录!
无需鉴权 有token 去不了login
需鉴权 有token 首页
需鉴权 无token login