木灵鱼儿
阅读:1682
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运行停止,所以要在最上层,创建一个全局的错误处理函数,将错误捕获,以免导致运行停止。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
centos 安装最新版 node.js
[hide]先用yum 安装一次node先升级yum update 提示下载程序,回复y安装nodeyum install nodejsyum安装的node一般都不是最新的,所以我们还需要升级node的版本npm i n -g全局安装一个n插件n lts表示安装最新的稳定版!一些常用命令n ls查看已安装的node版本n rm 16.0.3删除16.0.3版本更多功能阅读插件文档:n等待安装完毕后,此时其实版本已经安装好了,但是输入node -v还是低版本,是因为centos的用户缓存问题。我们退出登录再登录就会刷新缓存了,此时的node -v输出的就是最新版本exit 退...
node npm yarn 如何同时运行多个指令
node的bash命令其实是有对应的指令字符的,但是,这个指令只能再linux,mac上才有生效,再windows是无效的,所以了解一下就行。命令说明&&顺序执行多条命令,当碰到执行出错的命令后将不执行后面的命令&并行执行多条命令||顺序执行多条命令,当碰到执行正确的命令后将不执行后面的命令|管道符windows上进行多命令"scripts": { "dev": "cross-env NODE_ENV=development webpack --env development --progress -...
koa教程2 HttpBearerAuth 传递token
前端发送token到后端,一般有三种方式:链接中携带token,query参数post这种请求body中携带token头信息携带token但是事实上http已经为我们提供了传token的方式,是一个相对来说比较安全的方式,就是HttpBearerAuth 其实他也是一个头信息来着,只是相对来说多了一点操作,在apipost软件中,我们可以直接可视化设置如果是axios,则是需要添加一个头信息:axios({ method: 'get', url: url, responseType: 'blob', headers: { 'Authoriz...

koa教程2 微信openid登录
微信的openid,前端登录时,会发送一个code码到后端,后端接收到这个code,然后配合appid、appsecret参数,像wx发送一个get请求。wx的请求地址:GET https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code具体查看文档:微信官方文档-服务端可以看到这段get请求有四个参数:属性类型默认值必填说明appidstring 是小程序 appIdsecretstring 是...
koa教程2 权限分级控制
在之前的代码中,我们使用了一个scope属性来表示用户的权限,但是scope是一个数字,他的阅读性是非常差的,当其他人开发的时候,可能并不知道你这个数字是什么意思?所以,我们需要将它转义成文本。为了方便判断,我们在enum.js中增加一个用户等级的对象enum.js://通用判断是否存在该类型 function isThisType(val) { let flag = false; Object.keys(this).find(key => { if (this[key] === val) { return flag = true; } })...
koa教程2 token鉴权
之前我们在token中传入我们的自定义参数,现在我们要做一个token鉴权,很简单,如果他的token合法,就可以访问这个路由,如果不合法,就打回。这个也不能说是真正意义上的鉴权,因为他还没有判断scope值,所以只能说是简单判断。做这个判断我们就可以通过中间件的形式,于是我们在middlewares目录下创建一个auth.js文件auth.js:const parseBearerToken = require("@utils/paresBearerToken"); const jwt = require("jsonwebtoken"); cons...
koa教程2 用户登录
登录类型用户注册之后,自然是用户登录,登录的时候我们需要考虑到登录的方式,往往在正式的项目里面,登录的方式可能会有很多种:账号密码登录手机号登录小程序一键登录那么我们登录的时候就要进行判断,设置一个type的属性,用于在登录时判断用户是什么类型的登录,这个由前端那边设定参数。type:1为账号密码登录,2为手机号登录,3为小程序一键登录作为判断,我们可以写一个查询表(枚举),在项目根目录创建一个lib目录,在里面创建一个enum.js文件。enum.js://通用判断是否存在该类型 function isThisType(val) { let flag = false; Obje...
node 自定义路径别名
用过vue cli的话,对路径中的@应该不陌生,他表示根目录,但是在node的环境里面,我们编写模块并引入时,并没有这种方便的写法。在node环境中,我们可以通过process.cwd()来表示根目录,但是这样的话,每次使用绝对路径引入,路径总是需要写一大堆拼接。const db = require(`${process.cwd()}/config/config`).database;这样略显麻烦,于是我们可以在全局写一个引入插件的方法:global.myRequire = function (path) { return require(`${process.cwd()}/${pa...
koa教程2 密码加密
默认情况下,我们通过User.create({ email, password, nickname });模型传入的数据,是无加密的,也就是说,在数据库中密码是明文显示的,这样肯定是不行,我们需要使用bcrypt插件进行加密yarn add bcryptjs --dev打开models/user.jsuser.js:const bcrypt = require("bcryptjs"); const { sequelize } = require(`${process.cwd()}/core/db`); const { Sequelize, Model, DataTy...

koa教程2 使用sequelize连接数据库,创建用户模型
数据库的连接处理,我们需要使用sequelize插件。这个插件还有对应的数据库驱动,如果是使用mysql,那么驱动是mysql2,具体可以查看中文版的入门教程:入门yarn add sequelize mysql2 --dev安装完成后我们先需要创建数据库连接。连接数据库在根目录/core目录**下创建一个db.js文件用于连接数据库。在连接数据库之前,我们需要一些数据库的参数,如账号,密码,端口,数据库名字,数据库地址,为了方便管理配置,我们在根目录创建一个config目录,里面存放一config.js文件 ,这个之前的教程我们就已经创建好了。config.js:module.expo...