koa框架24 整合1
整合一下服务:
核心模块
- koa 框架
- koa-router 路由
- koa-static 发送静态文件(静态化文件)
- promise-fs 文件读写
- uuid 全局唯一标识符
数据处理
- koa-better-body post数据和文件
- koa-convert koa-better-body的转换async方式的插件
- memorystream 内存流,高速数据生成
- promise-mysql 数据库
session
- koa-session session
- redis和bluebird redis
后端渲染
- koa-ejs
目录结构
- libs 用于存放自己写的通用模块
- log 用于存放日志的
- router 路由文件
- static 静态资源
- template 模板文件
- upload 上传文件
创建一个config.js文件,用于配置服务器基本设置,如mysql的用户名信息那些
一个server.js文件,服务器主文件
初始化项目
npm init -y
npm i koa koa-router koa-static promise-fs uuid koa-better-body koa-convert memorystream promise-mysql koa-session redis bluebird koa-ejs -D
安装完后创建基本的server服务
const Koa = require("koa");
const config = require("./config");
//server
let server = new Koa();
server.listen(config.port);
console.log(`server running at ${config.port}`);
config设置一个端口
module.exports = {
port: 8081
}
数据库及redis
数据库的引入,一般会作为一个通用的模块单独写一个js,丢到libs目录里面,这样的话大大简化server.js服务器主文件
服务器的设置也是写在config.js文件里面
config
module.exports = {
//服务器
port: 8081,
//数据库
sql: {
host: "localhost",
prot: 3306,
user: "root",
password: "123456",
database: "koa"
}
}
libs下的mysql.js
const mysql = require("promise-mysql");
const config = require("../config");
module.exports = mysql.createPool(config.sql);
引入primise-mysql,引入config,然后直接返回一个连接池
server.js
const Koa = require("koa");
const config = require("./config");
//server
let server = new Koa();
//数据库
(async () => {
server.context.db = await require("./libs/mysql");
//监听
server.listen(config.port);
console.log(`server running at ${config.port}`);
})();
由于返回的是一个异步的,通过一个自运行的async配置awite转换,为此,监听需要在数据库连接完毕后才能运行。
数据库连接完成
redis
redis和mysql一样,在libs里面创建一个js模块,然后导出。
redis由于模块的问题,需要bluebrid的promisefyAll方法进行转换旧的异步方法。
redis也需要配置连接设置,也是放在config.js里面
config.js
module.exports = {
//服务器
port: 8081,
//数据库
sql: {
host: "localhost",
prot: 3306,
user: "root",
password: "123456",
database: "koa"
},
//redis
radsi: {
host: "localhost"
}
}
redis.js
const redis = require("redis");
const bluebird = require("bluebird");
const config = require("../config");
//连接redis
let client = redis.createClient(config.redis);
//转换旧方法
bluebird.promisifyAll(redis.RedisClient.prototype);
module.exports = client;
server.js
const Koa = require("koa");
const config = require("./config");
//server
let server = new Koa();
(async () => {
//数据库
server.context.db = await require("./libs/mysql");
//Redis
server.context.redis = await require("./libs/redis");
//测试redis
server.use(async ctx => {
// ctx.redis.setAsync("name", "mulingyuer");
ctx.body = await ctx.redis.getAsync("name");
})
//监听
server.listen(config.port);
console.log(`server running at ${config.port}`);
})();
redis连接完毕
处理redis的报错
如果中途redis服务关闭了,我们会发现整个server服务都会停止运行,并且输出错误。
原因是因为redis他发现他的错误没有人监听,那么就往上抛出,也就是throw了,这样的话整个server就会因为这个throw而停止。
而redis也提供了两个事件,有了事件监听,错误就不会往上throw了。
这样server也不会因为redis的报错而停止运行。
redis.js
const redis = require("redis");
const bluebird = require("bluebird");
const config = require("../config");
//连接redis
let client = redis.createClient(config.redis);
//转换旧方法
bluebird.promisifyAll(redis.RedisClient.prototype);
//监听错误
client.on("error", err => {
console.log("error", err.code);
});
//监听重连
client.on("reconnecting", ev => {
console.log(`try to reconnect to redis server: ${ev.attempt}`);
});
module.exports = client;
监听错误,和监听重连次数,通过ev.attempt
的属性获取的重连的次数,redis重连的话,不设置默认是无限次重连,而且他这个重连也是有间隔的,不会隔几秒连一次,他是连的次数越多,隔的时间就越久。
opn和网络提示
open
opn是一个自动打开浏览器的插件,就是说我们服务器启动后,给opn传入一个地址,然后他就会自动打开浏览器并指向传入的地址页面。
npm i opn
server.js
const Koa = require("koa");
const config = require("./config");
const opn = require("opn");
//server
let server = new Koa();
(async () => {
//数据库
server.context.db = await require("./libs/mysql");
//Redis
server.context.redis = await require("./libs/redis");
//监听
server.listen(config.port);
console.log(`server running at ${config.port}`);
opn(`http://localhost:${config.port}`, {
app: 'chrome'
});
})();
opn官方示例:
const opn = require('opn');
// opens the image in the default image viewer
opn('unicorn.png').then(() => {
// image viewer closed
});
// opens the url in the default browser
opn('http://sindresorhus.com');
// specify the app to open in
opn('http://sindresorhus.com', {
app: 'firefox'
});
// specify app arguments
opn('http://sindresorhus.com', {
app: ['google chrome', '--incognito']
});
网络提示
我们需要一个原生的模块os,这个模块有一个方法networkInterfaces(),他会返回服务器的网卡信息,是一个键值对对象,网卡的名字为key,key对应的信息是一个数组,数组里面存放了很多对象,每个对象都有对应的IPv4或者v6的信息。
我们需要先获取所有网卡信息 ----- 跳过VMware虚拟网卡 ---- 遍历非VMware网卡的数组 ------ 找到对象里面有IPv4的对象,拿到这个对象的address,也就是真实ip地址。
一般有两个,一个是外网ip,一个是本地的127.0.0.1
然后将ip信息保存在一个数组里面,依次console输出就行了。
也是一样,在libs里面创建一个network.js模块
network.js
const os = require("os");
const networkObj = os.networkInterfaces();
let addressArr = [];
for (let key in networkObj) {
if (!key.startsWith("VMware")) {
networkObj[key].forEach(item => {
if (item.family === "IPv4") {
addressArr.push(item.address);
}
});
}
};
module.exports = function(port) {
if (port === 80) {
addressArr.forEach(ip => {
const address = ip === "127.0.0.1" ? "localhost" : ip;
console.log(`server running at http://${address}`);
});
} else {
addressArr.forEach(ip => {
const address = ip === "127.0.0.1" ? "localhost" : ip;
console.log(`server running at http://${address}:${port}`);
});
}
};
我直接把输出也丢里面,弄了一个方法判断端口,如果是默认80端口,就不输出端口,其他则带端口一起输出
server.js
const Koa = require("koa");
const config = require("./config");
const opn = require("opn");
const network = require("./libs/network");
//server
let server = new Koa();
(async () => {
//数据库
server.context.db = await require("./libs/mysql");
//Redis
server.context.redis = await require("./libs/redis");
//监听
server.listen(config.port);
//输出运行地址
network(config.port);
opn(`http://localhost:${config.port}`, {
app: 'chrome'
});
})();
post和上传文件
之前说过post和上传文件的处理方式:
- post的话,multipart和buffer都是false,禁止上传
- 上传文件的话,需要设置上传路径,单个文件大小
而且这两个由于是多个地方需要使用,我们需要封装一下。
config.js
配置一下默认地址和默认文件大小
const path = require("path");
module.exports = {
//服务器
port: 8081,
//数据库
sql: {
host: "localhost",
prot: 3306,
user: "root",
password: "123456",
database: "koa"
},
//redis
radsi: {
host: "localhost",
port: 6379,
pass: undefined, //redis密码为空用undefined表示
},
//upload
up_path: path.resolve(__dirname, "upload"), //默认上传路径
up_size: 20 * 1024 * 1024, //默认文件大小
}
libs/post-body.js
const body = require("koa-better-body");
const convert = require("koa-convert");
const config = require("../config");
module.exports = {
post() {
return convert(body({
multipart: false,
buffer: false
}))
},
upload(options) {
//配置参数
options = options || {};
options.path = options.path || config.up_path;
options.size = options.size || config.up_size;
return [
async (ctx, next) => {
try {
await next();
} catch (e) {
if (e.message.startsWith("maxFileSize exceeded")) {
if (options.sizeError) {
options.sizeError(ctx, e);
} else {
ctx.status = 407;
ctx.body = "文件过大";
}
} else {
if (options.error) {
options.error(ctx, e);
} else {
throw e;
}
}
}
},
convert(body({
uploadDir: options.path,
maxFileSize: options.size
}))
];
}
}
这里post就很简单了,return一个就行了,上传的文件的话,我们要考虑到参数。
用户除了默认的话,还可以自定义文件大小,路径,以及错误回调和文件超出大小的错误回调。
在错误回调里面我们可能需要给浏览器返回内容,所以需要传入ctx对象。
server.js
const Koa = require("koa");
const config = require("./config");
const opn = require("opn");
const network = require("./libs/network");
const {
post,
upload
} = require("./libs/post-body");
const Router = require("koa-router");
//server
let server = new Koa();
(async () => {
//数据库
server.context.db = await require("./libs/mysql");
//Redis
server.context.redis = await require("./libs/redis");
//全局错误处理
server.use(async (ctx, next) => {
try {
await next();
} catch (e) {
ctx.status = 500;
ctx.body = 'internal server error';
}
});
//router
let router = new Router();
//post
router.post("/post", post(), async ctx => {
console.log(ctx.request.fields);
});
//upload
router.post("/upload", ...upload({
path: undefined,
size: undefined,
error: (ctx, e) => {}, //错误回调
sizeError: (ctx, e) => {} //文件过大的错误回调
}), async ctx => {
console.log(ctx.request.fields);
ctx.body = '上传成功';
});
//激活router
server.use(router.routes());
//监听
server.listen(config.port);
//输出运行地址
network(config.port);
opn(`http://localhost:${config.port}`, {
app: 'chrome'
});
})();
引入post-body后,创建路由,通过路由来对post和上传文件进行定义接口。
注意:
由于我们的错误会抛出,所以会导致server运行停止,所以要在最上层,创建一个全局的错误处理函数,将错误捕获,以免导致运行停止。
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
暂无评论数据