前后端项目框架搭建 总结
前后端框架搭建 总结(vue+eggjs)
前端请求部分
axios 基本案例
我们先从一个普通的 axios 示例开始:
import axios from 'axios'
const url = 'https://test.youbaobao.xyz:18081/book/home/v2?openId=1234'
axios.get(url).then(response => {
console.log(response)
})
上述代码可以改为:
const url = 'https://test.youbaobao.xyz:18081/book/home/v2'
axios.get(url, {
params: { openId: '1234' }
})
如果我们在请求时需要在 http header 中添加一个 token,需要将代码修改为:
const url = 'https://test.youbaobao.xyz:18081/book/home/v2'
axios.get(url, {
params: { openId: '1234' },
headers: { token: 'abcd' }
}).then(response => {
console.log(response)
})
如果要捕获服务端抛出的异常,即返回非 200 请求,需要将代码修改为:
const url = 'https://test.youbaobao.xyz:18081/book/home/v2'
axios.get(url, {
params: { openId: '1234' },
headers: { token: 'abcd' }
}).then(response => {
console.log(response)
}).catch(err => {
console.log(err)
})
这样改动可以实现我们的需求,但是有两个问题:
- 每个需要传入 token 的请求都需要添加 headers 对象,会造成大量重复代码
- 每个请求都需要手动定义异常处理,而异常处理的逻辑大多是一致的,如果将其封装成通用的异常处理方法,那么每个请求都要调用一遍
axios.create 示例
下面我们使用 axios.create 对整个请求进行重构:
const url = '/book/home/v2'
const request = axios.create({
baseURL: 'https://test.youbaobao.xyz:18081',
timeout: 5000
})
request({
url,
method: 'get',
params: {
openId: '1234'
}
})
首先我们通过 axios.create
生成一个函数,该函数是 axios 实例,通过执行该方法完成请求,它与直接调用 axios.get
区别如下:
- 需要传入 url 参数,
axios.get
方法的第一个参数是 url - 需要传入 method 参数,
axios.get
方法已经表示发起 get 请求
axios 请求拦截器
上述代码完成了基本请求的功能,下面我们需要为 http 请求的 headers 中添加 token,同时进行白名单校验,如 /login
不需要添加 token,并实现异步捕获和自定义处理:
const whiteUrl = [ '/login', '/book/home/v2' ]
const url = '/book/home/v2'
const request = axios.create({
baseURL: 'https://test.youbaobao.xyz:18081',
timeout: 5000
})
request.interceptors.request.use(
config => {
// throw new Error('error...')
const url = config.url.replace(config.baseURL, '')
if (whiteUrl.some(wl => url === wl)) {
return config
}
config.headers['token'] = 'abcd'
return config
},
error => {
return Promise.reject(error)
}
)
request({
url,
method: 'get',
params: {
openId: '1234'
}
}).catch(err => {
console.log(err)
})
这里核心是调用了 request.interceptors.request.use
方法,即 axios 的请求拦截器,该方法需要传入两个参数,第一个参数是拦截器方法,包含一个 config 参数,我们可以在这个方法中修改 config 并且进行回传,第二个参数是异常处理方法,我们可以使用 Promise.reject(error)
将异常返回给用户进行处理,所以我们在 request 请求后可以通过 catch 捕获异常进行自定义处理
axios 响应拦截器
下面我们进一步增强 axios 功能,我们在实际开发中除了需要保障 http statusCode 为 200,还需要保证业务代码正确,上述案例中,我定义了 error_code 为 0 时,表示业务返回正常,如果返回值不为 0 则说明业务处理出错,此时我们通过 request.interceptors.response.use
方法定义响应拦截器,它仍然需要2个参数,与请求拦截器类似,注意第二个参数主要处理 statusCode 非 200 的异常请求,源码如下:
const whiteUrl = [ '/login', '/book/home/v2' ]
const url = '/book/home/v2'
const request = axios.create({
baseURL: 'https://test.youbaobao.xyz:18081',
timeout: 5000
})
request.interceptors.request.use(
config => {
const url = config.url.replace(config.baseURL, '')
if (whiteUrl.some(wl => url === wl)) {
return config
}
config.headers['token'] = 'abcd'
return config
},
error => {
return Promise.reject(error)
}
)
request.interceptors.response.use(
response => {
const res = response.data
if (res.error_code != 0) {
alert(res.msg)
return Promise.reject(new Error(res.msg))
} else {
return res
}
},
error => {
return Promise.reject(error)
}
)
request({
url,
method: 'get',
params: {
openId: '1234'
}
}).then(response => {
console.log(response)
}).catch(err => {
console.log(err)
})
request 库源码分析
有了上述基础后,我们再看 request 库源码就非常容易了
import axios from 'axios'
import { MessageBox, Message } from 'element-ui'
import store from '@/store'
import { getToken } from '@/utils/auth'
// create an axios instance
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor
service.interceptors.request.use(
config => {
if (store.getters.token) {
config.headers['Authorization'] = `Bearer ${getToken()}`
}
return config
},
error => {
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor
service.interceptors.response.use(
response => {
const res = response.data
const errMsg = res.message || res.msg || '请求失败'
if (res.code !== 0) {
Message({
message: errMsg,
type: 'error',
duration: 5 * 1000
})
if (res.code === -2) {
MessageBox.confirm('您的登录状态已经失效,请重新登录', '登录失效', {
confirmButtonText: '重新登录',
cancelButtonText: '取消',
type: 'warning'
}).then(() => {
store.dispatch('user/resetToken').then(() => {
location.reload()
})
})
}
return Promise.reject(errMsg)
} else {
return res
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
后端 响应结果封装
创建 /models/Result.js
文件:
'use strict';
const {
CODE_SUCCESS,
CODE_ERROR,
} = require('../utils/constant');
class Result {
constructor(data, msg = '操作成功', options) {
this.data = null;
if (arguments.length === 0) {
this.msg = '操作成功';
} else if (arguments.length === 1) {
this.msg = data;
} else {
this.data = data;
this.msg = msg;
if (options) {
this.options = options;
}
}
}
createResult() {
if (!this.code) this.code = CODE_SUCCESS;
const base = {
code: this.code,
msg: this.msg,
};
if (this.data) {
base.data = this.data;
}
if (this.options) {
base.options = { ...base, ...this.options };
}
return base;
}
success(res) {
this.code = CODE_SUCCESS;
res.body = this.createResult();
}
fail(res) {
this.code = CODE_ERROR;
res.body = this.createResult();
}
}
module.exports = Result;
我们还需要创建 /utils/constant.js
:
module.exports = {
CODE_ERROR: -1,
CODE_SUCCESS: 0
}
Result 使用了 ES6 的 Class,使用方法如下:
// 调用成功时
new Result().success(ctx.response)
new Result('登录成功').success(ctx.response)
// 调用成功时,包含参数
new Result({ token }, '登录成功').success(ctx.response)
// 调用失败时
new Result('用户名或密码不存在').fail(ctx.response)
后端 数据库操作封装
安装
安装 mysql 库:
npm i -S mysql
配置
创建 db 目录,新建两个文件:
index.js
config.js
config.js 源码如下:
module.exports = {
host: 'localhost',
user: 'root',
password: '12345678',
database: 'book'
}
连接
连接数据库需要提供使用 mysql 库的 createConnection 方法,我们在 index.js 中创建如下方法:
function connect() {
return mysql.createConnection({
host,
user,
password,
database,
multipleStatements: true
})
}
multipleStatements:允许每条 mysql 语句有多条查询.使用它时要非常注意,因为它很容易引起 sql 注入(默认:false)
查询
查询需要调用 connection 对象的 query 方法:
function querySql(sql) {
const conn = connect()
debug && console.log(sql)
return new Promise((resolve, reject) => {
try {
conn.query(sql, (err, results) => {
if (err) {
debug && console.log('查询失败,原因:' + JSON.stringify(err))
reject(err)
} else {
debug && console.log('查询成功', JSON.stringify(results))
resolve(results)
}
})
} catch (e) {
reject(e)
} finally {
conn.end()
}
})
}
我们在 constant.js 创建一个 debug 参数控制日志打印:
const debug = require('../utils/constant').debug
这里需要注意 conn 对象使用完毕后需要调用 end 进行关闭,否则会导致内存泄露
调用方法如下:
db.querySql('select * from book').then(result => {
console.log(result)
})