统一网关层的实现
给毕业设计搭架子的时候,因为设计的缘故,需要有一个前端后端统一的网关层,来实现自动拦截错误等功能,比如:
{
code:0,
message:'ok',
data:{}
}
前端如果读到code == 0 的情况,可以自动放行,其他数值就会报错并打印错误信息。
这样的话,后端就需要对每种可能性都要写出多余的code和message,不利于代码后期的维护,最后就选择了修改express的错误处理中间件来做一个响应的统一收口处理。
下面是express的默认处理方式:
// app.js
app.use(function (err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get("env") === "development" ? err : {};
// render the error page
res.status(err.status || 500);
res.render("error");
});
在调试的时候发现其实第一个参数是next传递的参数,无论是不是Error类,都会被作为参数传递到最后的这个错误处理中间件中。
这时候就可以考虑把这个err作为一个传递的变量使用,为了语义化,可以重命名为value。
下面是重写后的错误处理中间件,这里只是提供了一下设计思路,以后还会更改。
如果遇到了Error类型的对象网关层会将错误信息作为message传出并且code也会变化,如果遇到一个简单类型,直接作为data返回,如果遇到了一个对象类型
// app.js
app.use(function (value, req, res, next) {
// 直接返回错误
if(value instanceof Error){
res.send({
code:code:errorCodeMap[value.message]?errorCodeMap[value.message]:-1,
message:value.message,
data:value
})
}else{
res.send({
code:0,
data:value.data?value.data:value,
message:value.message?value.message:'ok'
})
}
});
关于如何使用这个中间件:
在router的文件中,需要返回错误或者响应数据,直接next(value)即可,避免了重复编写code和message。
目前的一个疑问就是如果next一个文件,或许这个地方还需要修改一下。
关于不显示错误的原因:
可以在捕获到Error的时候,直接console.log,这样就可以显示报错信息了
自动导入路由
传统的express的默认导入路由的方式,是先require文件,然后再导入,这样不利于扩展和维护,希望可以直接读取目录然后直接自动导入。
这时候就考虑到可以使用文件系统的能力来读取某一目录下的所有文件,并做一些约定(比如目录可以用当前文件名作为请求地址的某一目录),就可以实现这个功能了。
// app.js
const getFolderContent = function(path){
return fs.readdirSync(path).map(files=>files.split('.')[0])
}
const apis = getFolderContent("routes");
for (let i = 0; i < apis.length; i++) {
const api = apis[i];
const router = require(`./routes/${apis[i]}`);
app.use(`/api/${api}`, router);
}
身份验证体系
在login接口中,向session中加入一些标记信息,前端后端都要打开cookies,即可完成。
使用中间件,统一拦截session中的登录态。
// app.js / back end
// 开启session
app.use(
session({
secret: "smart session", //这个随便写,第二次进行加密
saveUninitialized: false,
resave: false,
// cookie和登录态保持60分钟
cookie: {
maxAge: 60 * 60 * 1000,
},
})
);
// 登录态拦截
app.use(function (req, res, next) {
if (
req.session.isLogin ||
req.originalUrl === "/api/users/login" || // 绕开登录接口
req.method === 'OPTIONS' // 绕开预检请求
) {
next();
} else {
next(new Error("未登录"));
}
});
在app.all的位置设置服务端允许前端携带cookies
res.header("Access-Control-Allow-Credentials", true); // 允许携带cookie
前端代码只需要在封装的axios中允许携带cookies
axios.defaults.withCredentials = true
关于设置和清除登录态的两个接口
// 设置登录态 login
router.all("/login", async function (req, res, next) {
const result = await User.findOne({ password: "123", username: "132" }); // 这里要有身份验证的逻辑
if(result){
req.session.isLogin = true
next("log in success");
}else{
next("log in failed")
}
});
// 清除登录态 logout
router.all("/logout", function (req, res, next) {
console.log(req.session)
if(req.session.isLogin){
req.session.isLogin = null
next(123)
}else{
next(new Error(123))
}
});
CORS解决跨域问题
直接复制到app.js文件里就能用
// app.js
app.all("*", function (req, res, next) {
// 对所有源都允许跨域,未来设置成客户端的域名即可
res.header("Access-Control-Allow-Origin", req.headers.origin);
//Access-Control-Allow-Headers ,可根据浏览器的F12查看,把对应的粘贴在这里就行
res.header("Access-Control-Allow-Headers", "Content-Type");
res.header("Access-Control-Allow-Methods", "*");
res.header("Access-Control-Allow-Credentials", true); // 允许携带cookie
res.header("Content-Type", "application/json;charset=utf-8"); // 这一条慎重添加,可能会导致static中间件乱码
next();
});
文件上传下载
后端还是依靠了multer中间件来进行处理,需要设置文件保存地址和文件名的话,需要使用diskStorage属性,然后传入multer方法的options参数中
后端代码:
// file.js
const express = require("express");
const router = express.Router();
const path = require("path");
const multer = require('multer')
const storage = multer.diskStorage({
destination(req,res,cb){
console.log('destination')
cb(null,'public/uploads') // 第二个参数为文件地址,先确保这里文件夹是存在的否则报错
},
filename(req,file,cb){
console.log('filename')
cb(null,file.originalname) // 第二个参数为将要保存的文件名
}
})
const upload = multer({storage})
router.all("/download", function (req, res, next) {
res.set({ "Content-Disposition": "attachment;" });
res.download(path.join(__dirname, "..", "public", "image.jpg"));
});
router.all("/upload", upload.single('file') , function (req, res, next) {
console.log(req.file)
res.send('success')
});
module.exports = router;
前端代码:
这里前端的实现不是很重要,只是需要注意请求的时候一定是formdata格式才能被multer解析
// 下载部分的代码
handleClickDownloadFile() {
downloadFile("http://localhost:3000/api/file/download", "image.jpg");
},
/**
*
* @param { string } url 请求地址
* @param { string } fileName 下载后的文件名
*/
const downloadFile = async function(url, fileName) {
const res = await axios.get(url, { responseType: "blob" });
const href = window.URL.createObjectURL(res.data);
const a = document.createElement("a");
a.href = href;
a.download = fileName;
a.target = "_blank";
a.click();
};
// 上传部分的代码
handleFileUpload(file) {
this.fileList = [...this.fileList, file];
const formdata = new FormData();
formdata.append("file", file);
axios.post("http://localhost:3000/api/file/upload", formdata, {
"Content-Type": "multipart/form-data"
});
}