koajs mysql_koa+mysql+vue+socket.io全栈开发之web api篇

目标是建立一个 web QQ的项目,使用的技术栈如下:

后端是基于koa2 的 web api 服务层,提供curd操作的http接口,登录验证使用的是 json web token,跨域方案使用的是 cors;

数据库使用的是 mysql;

为了实时通信,使用的是基于websocket协议的 socket.io 框架;

前端则使用的是 vue + vuex。

本篇则讲叙服务端的搭建,之所以使用 koa,而不使用其他封装过的框架,比如 Egg.js, Thinkjs。因为在我看来,koa2 已经够方便,插件也足够多,完全可以根据自己的需求,像搭积木一样构建出最适合业务需求的框架。这样不但摒弃了很多用不到的插件,使整个框架更加精简,也能对整个框架知根知底,减少了很多不可预知因素的影响。

当然我觉得最主要的是我比较懒😄,不想再去学其他框架特有的api,特有的配置。因为前端有太多框架太多api需要掌握了,对于非互联网公认的技术标准,我觉得学习的优先级还是要靠后一点的。因为这些个框架,三天两头就冒出个热门的,简直多不胜数,学不过来啊,而koa基本都是这些框架的底层,明显靠谱多了。

基本框架搭建

这几个koa插件大部分项目八九不离十要用到:

koa-body 解析http数据

koa-compress gzip压缩

koa-router 路由

koa-static 设置静态目录

koa2-cors 跨域cors

log4js 老牌的日志组件

jsonwebtoken jwt 组件

基本的目录结构

public #公共目录

src #前端目录

server #后端目录

├── common #工具

├── config #配置文件

├── controller #控制器

├── daos #数据库访问层

├── logs #日志目录

├── middleware #中间件目录

├── socket #socketio目录

├── app.js #入口文件

└── router.js #路由

入口文件app.js

主要就是几个中间件配置需要注意一下,这里同时还加载了 socket.io 服务。socket.io 相关的基本知识点可以看我之前写的文章关于socket.io的使用。

//app.js

//...

const path = require("path");

const baseDir = path.normalize(__dirname + "/..");

// gzip

app.use(

compress({

filter: function(content_type) {

return /text|javascript/i.test(content_type);

},

threshold: 2048,

flush: require("zlib").Z_SYNC_FLUSH

})

);

// 解析请求

app.use(

koaBody({

jsonLimit: 1024 * 1024 * 5,

formLimit: 1024 * 1024 * 5,

textLimit: 1024 * 1024 * 5,

multipart: true, // 解析FormData数据

formidable: { uploadDir: path.join(baseDir, "public/upload") }//上传文件目录

})

);

// 设置静态目录

app.use(static(path.join(baseDir, "public"), { index: false }));

app.use(favicon(path.join(baseDir, "public/favicon.ico")));

//cors

app.use(

cors({

origin: "http://localhost:" + config.clientPort,

credentials: true,

allowMethods: ["GET", "POST", "DELETE"],

exposeHeaders: ["Authorization"],

allowHeaders: ["Content-Type", "Authorization", "Accept"]

})

);

//json-web-token中间件

app.use(

jwt({

secret: config.secret,

exp: config.exp

})

);

// 登录验证中间件,exclude 表示不验证的页面,include 表示要验证的页面

app.use(

verify({

exclude: ["/login", "/register", "/search"]

})

);

// 错误处理中间件

app.use(errorHandler());

// 路由

addRouters(router);

app.use(router.routes()).use(router.allowedMethods());

// 处理404

app.use(async (ctx, next) => {

log.error(`404 ${ctx.message} : ${ctx.href}`);

ctx.status = 404;

ctx.body = { code: 404, message: "404! not found !" };

});

// 处理中间件和系统错误

app.on("error", (err, ctx) => {

log.error(err); //log all errors

ctx.status = 500;

ctx.statusText = "Internal Server Error";

if (ctx.app.env === "development") {

//throw the error to frontEnd when in the develop mode

ctx.res.end(err.stack); //finish the response

} else {

ctx.body = { code: -1, message: "Server Error" };

}

});

if (!module.parent) {

const { port, socketPort } = config;

/**

* koa app

*/

app.listen(port);

log.info(`=== app server running on port ${port}===`);

console.log("app server running at: http://localhost:%d", port);

/**

* socket.io

*/

addSocket(io);

server.listen(socketPort);

}

跨域cors 和 json web token

这里解释一下 koa-cors 参数的设置,我项目使用的是 json web token,需要把认证字段Authorization添加到header,前端获取该header字段,之后给后台发送http请求的时候,再带上该Authorization。

origin:如果要访问header里面的字段或者设置cookie,要写具体的域名地址,用 星号 * 是不行的;

credentials:主要是给前端获取cookie;

allowMethods:允许访问的方法;

exposeHeaders:前端如果要获取该header字段,必须写明(json web token用);

allowHeaders:添加到header的字段;

至于 json web token的原理,网上资料齐全,这里不再介绍了。

app.use(

cors({

origin: "http://localhost:" + config.clientPort, // 访问header,要写明具体域名才行

credentials: true, //将凭证暴露出来, 前端才能获取cookie

allowMethods: ["GET", "POST", "DELETE"],

exposeHeaders: ["Authorization"], // 将header字段expose出去

allowHeaders: ["Content-Type", "Authorization", "Accept"] // 允许添加到header的字段

})

);

中间件middleware

koa 的中间件就是 web开发的利器,通过它可以非常方便的实现 强类型语言中的 aop 切面编程,而koa2 中间件 的编写也足够简单 koajs。

项目在以下几个地方都用中间件进行了封装,很多重复的样板代码因此得以简化。

json web token(jwt)

登录验证(verify)

错误处理(errorHandler)

就以最简单的错误处理中间件为例子,如果不使用错误处理中间件,我们需要每个控制器方法进行 try{…} catch{…} ,其他中间件编写方式类似,就不再介绍。

/**

* error handler 中间件

*/

module.exports = () => {

return async (ctx, next) => {

try {

await next();//没有错误则进入下一个中间件

} catch (err) {

log.error(err);

let obj = {

code: -1,

message: '服务器错误'

};

if (ctx.app.env === 'development') {

obj.err = err;

}

ctx.body = obj

}

};

};

// 控制器代码使用error handler中间件后,每个方法都不需要 try catch处理错误,记录错误日志,处理逻辑都集中在中间件里面了。

exports.getInfo = async function(ctx) {

// try {

const token = await ctx.verify();

const [users, friends] = await Promise.all([

userDao.getUser({ id: token.uid }),

getFriends([token.uid])

]);

const msgs = applys.map(formatTime);

ctx.body = {

code: 0,

message: "好友列表",

data: {

user: users[0],

friends: mergeReads(friends, reads),

groups,

msgs

}

};

// } catch (err) {

// log.error(err);

// let obj = {

// code: -1,

// message: "服务器错误"

// };

// if (ctx.app.env === "development") {

// obj.err = err;

// }

// ctx.body = obj;

// }

};

路由配置

路由配置只使用了get,post 方法,当然要使用 put,delete也只是改一下名字就行。

// router.js

const { uploadFile } = require('./controller/file')

const { login, register } = require('./controller/sign')

const { addGroup, delGroup, updateGroup } = require('./controller/group')

//...

module.exports = function (router) {

router

.post('/login', login)

.post('/register', register)

.post('/upload', uploadFile)

.post('/addgroup', addGroup)

.post('/delgroup', delGroup)

.post('/updategroup', updateGroup)

//...

};

控制器

以updateInfo方法为例,koa2 已经全面支持 async await,编写方式和同步代码没多大区别。

exports.updateInfo = async function (ctx) {

const form = ctx.request.body;

const token = await ctx.verify();

const ret = await userDao.update([form, token.uid]);

if (!ret.affectedRows) {

return ctx.body = {

code: 2,

message: '更新失败'

};

}

ctx.body = {

code: 0,

message: '更新成功'

};

}

后续

接着下一编就是基于 mysql 构建 数据库访问层。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值