Vue + Koa 全栈小demo,切图小子迈向Full stack engineer的第一步!
前言
在程序员的圈子里,存在着这么一条鄙视链,做算法的瞧不上做后端开发的,做后端开发的瞧不上做前端的。做为鄙视链底端的前端工程师素来被人们称之为切图崽,但是前端工程师们要自信,我们也是可以做后端滴。接下来,将展示一个适合前端工程师入门全栈的一个全栈小demo。
实现效果:
技术栈
前端
vue、pina(状态管理神器)、less(css与编译器)、vant(移动端的ui库)
后端
koa、mysql
前端部分技术难点
请求拦截
在编写项目的过程中,我们经常会碰到以下场景;
当客户端向服务端发送请求时,服务端需要鉴定客户端是否有权限访问服务端的接口。在客户端向服务端发送请求时,通常会给客户端加上一个标识用来标记拥有访问服务端的权利。这个过程是一个授权的过程,通常的解决办法是在请求头上加上一个授权码,那么我们总不能每次在发接口请求的时候都手动添加一个授权码吧。为了解决这个问题,通常会对axios进行请求拦截,这样会将每次请求都拦截下来,可以进行添加授权码的这样一个过程。
请求拦截的代码实现:
//请求拦截
axios.interceptors.request.use(req => {
let jwtToken = window.localStorage.getItem('token')
if (jwtToken) {
req.headers.Authorization = jwtToken
}
return req
})
这段代码实现了 Axios 库的请求拦截器功能,在每个 HTTP 请求发出之前都自动添加 JWT(JSON Web Token)到请求头部的 Authorization
字段,以便进行身份验证。
通过这种方式,你不需要在每次发出请求时手动添加 JWT 到请求头,而是可以通过请求拦截器自动化这一过程,简化了代码并提高了安全性。不过,值得注意的是,通常在 Authorization
字段前加上 "Bearer " 前缀是标准做法,例如:
req.headers.Authorization = `Bearer ${jwtToken}`;
这样更符合 HTTP API 的规范。
响应拦截
响应拦截,顾名思义是对服务端响应给客户端的HTTP请求的拦截,用于处理从后端接收到的 HTTP 响应。响应拦截器允许你在响应被传递给 .then()
或 .catch()
处理器之前,对响应进行预处理。
//响应拦截 过滤后端返回来的数据
axios.interceptors.response.use(res => {
//console.log(res,'111')
if (res.status !== 200) {//程序错误
showToast("服务器异常")
return Promise.reject(res)
} else {
if (res.data.state === 401) {//登录失效
showToast(res.data.msg);
router.push('/login')
return Promise.reject(res)
}
if (res.data.code !== '8000') {//逻辑性错误
showToast(res.data.msg)
return Promise.reject(res)
}
//showToast('登录成功')
return res.data
}
})
-
if (res.status !== 200) { ... }
- 检查响应的状态码是否不是 200(OK)。如果不是 200,通常表示服务器端发生了错误,此时会调用
showToast("服务器异常")
显示一个错误消息,并通过Promise.reject(res)
抛出一个 Promise 错误,阻止正常的响应处理流程。
- 检查响应的状态码是否不是 200(OK)。如果不是 200,通常表示服务器端发生了错误,此时会调用
-
if (res.data.state === 401) { ... }
- 如果响应数据中的
state
属性等于 401,这通常表示未授权或登录状态已失效。此时会显示错误消息showToast(res.data.msg)
,将用户重定向到登录页面router.push('/login')
,并同样通过Promise.reject(res)
抛出错误。
- 如果响应数据中的
-
if (res.data.code !== '8000') { ... }
- 如果响应数据中的
code
不等于'8000'
,这可能表示业务逻辑上的错误。此时会显示错误消息showToast(res.data.msg)
,并通过Promise.reject(res)
抛出错误。
- 如果响应数据中的
-
return res.data
- 如果以上条件都不满足,即响应是成功的且没有错误,那么拦截器将直接返回
res.data
,即后端返回的实际数据,以便后续的.then()
处理器可以正常处理这些数据。
- 如果以上条件都不满足,即响应是成功的且没有错误,那么拦截器将直接返回
通过这样的响应拦截器,你可以统一处理各种类型的错误,比如服务器错误、未授权错误以及业务逻辑错误,而无需在每个请求的 .then()
或 .catch()
处理器中重复相同的错误处理代码。
后端部分技术难点
前端鉴权
在一些业务场景中需要对用户的权限进行鉴定,判断其是否可以访问某些页面。比如下面这样一个场景;
张三是一个网购爱好者,他在访问购物网站时,是不需要登录就可以浏览商品信息,但是当他需要下单某件商品时,他必须要登录账号,这样一个过程就是鉴权的过程。
那么怎么实现这样一个鉴权的过程呢?这个时候JWT
就有大作用了。
我们可以在前端访问后端时,为每一个用户都创建一个独特的JWT令牌,在访问后端接口的时候将该令牌携带上,在后端编写代码来识别该令牌的正确性。
如何创建JWT令牌
在后端中我们可以引入,jsonwebtoken库,利用它来生成jwt令牌。
const jwt = require('jsonwebtoken')
通过编写下面的代码可以根据用户传入的数据和自定义的jwt密钥来生成独特的jwt令牌。
前端传给后端的数据:
const res = await axios.post('/user/login', {
username: values.username,
password: values.password
})
后端中的option参数接受前端传来的数据创建jwt令牌
//创建token
function sign(option) {
return jwt.sign(option, '666', {
expiresIn: '86400'//一天后过期
});
}
当接口请求成功后,可以看到,我们根据用户传入的内容创建了一个独一无二的jwt令牌
校验token
在创建了jwt令牌后,之后只要是涉及到鉴权的过程则会调用该函数通过判断jwt是否正确来决定是否可以调用相关的接口。
//检验token
function verify() {
return async (ctx, next) => {
let jwtToken = ctx.req.headers.authorization;
if (jwtToken) {
//判断token是否合法
const decoded = jwt.verify(jwtToken, '666',)
console.log(decoded)
try {
const decoded = jwt.verify(jwtToken, '666')
if (decoded.id) {//合法
ctx.userId = decoded.id
await next()//调用next 去到下一个中间件
}
} catch (e) {
ctx.body = {
status: 401,//权限不足
msg: 'token失效'
}
}
} else {
ctx.body = {
status: 401,
msg: '请提供token'
}
}
}
}
verify
中间件的作用是确保每个到达它的请求都携带了一个有效的 JWT,从而实现基于 JWT 的身份验证。如果 JWT 缺失或无效,中间件会立即终止请求处理并返回相应的错误响应。如果 JWT 合法,中间件将继续请求的处理流程,同时将用户 ID 附加到上下文中,供后续的处理逻辑使用。
数据库操作
node的出现赋予了js这门编程语言操作数据库的能力,那么怎么使用js来对数据库进行增删改查的操作呢? 各位请看下面这段代码:
//封装一个函数用来连接数据库
const mysql = require('mysql2/promise')
const config = require('../config/index.js')
//线程池
const pool = mysql.createPool({
host: config.database.HOST,
user: config.database.USERNAME,
password: config.database.PASSWORD,
database: config.database.DATABASE,
port: config.database.PORT
})
const allServices = {
async query(sql, values) {
try {
//通过线程池连接mysql
const conn = await pool.getConnection()
//对连接执行某些操作
const [rows, fields] = await conn.query(sql, values)
//释放连接
pool.releaseConnection(conn)
return Promise.resolve(rows)
} catch (error) {
return Promise.reject(error)
}
}
}
这段代码封装了一个用于连接 MySQL 数据库的函数,利用 mysql2/promise
模块,该模块提供了基于 Promise 的 API,使异步数据库操作更加简洁和易于管理。
-
引入模块:
const mysql = require('mysql2/promise')
:导入mysql2
模块的 Promise 版本,它提供了异步的数据库操作接口。const config = require('../config/index.js')
:导入配置文件,其中包含了数据库连接的配置信息,如主机名、用户名、密码、数据库名和端口号。
-
创建连接池:
const pool = mysql.createPool({...})
:使用mysql.createPool
方法创建一个连接池。连接池可以复用数据库连接,避免频繁的建立和断开连接,提高应用程序的性能。这里使用了从配置文件中读取的数据库连接参数。
-
定义查询函数:
-
allServices.query(sql, values)
:定义了一个异步方法query
,它接受 SQL 查询语句和参数值作为输入。 -
try { ... } catch (error) { ... }
:使用 try-catch 结构来捕获并处理可能发生的错误。 -
const conn = await pool.getConnection()
:从连接池中获取一个数据库连接。 -const [rows, fields] = await conn.query(sql, values)
:使用获取到的连接执行 SQL 查询。conn.query
方法返回一个 Promise,该 Promise 解析为一个数组,其中第一个元素是查询结果(rows
),第二个元素是字段信息(fields
)。 -
pool.releaseConnection(conn)
:查询完成后,释放数据库连接,将其放回连接池,以便其他请求可以重用。 -
return Promise.resolve(rows)
:如果一切顺利,将查询结果rows
包装成一个 Promise 并返回。
-
通过上述封装,allServices.query
函数提供了一个简单而强大的接口,我们可以调用上述函数,然后传入相应的sql语句就可以进行数据库的增删改查操作了。
总结
该全栈demo综合实现了前端与后端的主流业务,个人觉得是一个很好的从前端迈向后端的练手小项目,如需源码的朋友请到文章开头处自行访问。
希望能对大家有所帮助。
转载自:https://juejin.cn/post/7393700271128264713