一、初始化项目
npm init //生成package.json
npm i --save koa //安装koa
npm i
二、hello world
创建index.js:
const Koa = require('koa');
const app = new Koa();
app.use((ctx) => {
ctx.body = 'Hello World1122';
})
app.listen(3000);
三、修改后自动重启
安装nodemon
npm i --save nodemon
启动:
nodemon index.js
四、koa-router
安装:
npm i --save koa-router
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// koa-router
router.get('/', (ctx) => {
ctx.body = '这是主页';
})
//将中间件插入koa实例
app.use(router.routes());
app.listen(3000);
路由前缀:
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 路由前缀
const usersRouter = new Router({prefix: '/users'});
// koa-router
router.get('/', (ctx) => {
ctx.body = '这是主页';
})
usersRouter.get('/', (ctx) => {
ctx.body = '这是用户';
})
usersRouter.get('/:id', (ctx) => {
ctx.body = `这是用户 ${ctx.params.id}`;
})
//将中间件插入koa实例
app.use(router.routes());
app.use(usersRouter.routes());
app.listen(3000);
多中间件:
const Koa = require('koa');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
// 路由前缀
const usersRouter = new Router({prefix: '/users'});
// 多中间件
const auth = async(ctx, next) => {
if(ctx.url !== '/users') {
ctx.throw(401);
}
await next();
}
// koa-router
router.get('/',auth, (ctx) => {
ctx.body = '这是主页';
})
usersRouter.get('/',auth, (ctx) => {
ctx.body = '这是用户';
})
usersRouter.get('/:id',auth, (ctx) => {
ctx.body = `这是用户 ${ctx.params.id}`;
})
//将中间件插入koa实例
app.use(router.routes());
app.use(usersRouter.routes());
app.listen(3000);
五、获取http请求参数
参数形式:(用url传递参数有长度限制和敏感信息不安全等缺点)
1.查询字符串:?q=keyword
获取:ctx.query
2.路由参数:/users/:id (一般是必选的)
获取:ctx.params
3.请求体:{name: '李磊'} (两种形式:json和form,json:application/json, form:application/x-www-form-urlencoded)
获取:ctx.request.body(需安装中间件koa-bodyparser)
六、HTTP响应
1.发送status: ctx.status
2.发送body: ctx.body
3.发送header: ctx.set('Allow','GET,POST');
七、合理分化路由和控制器
将上方项目中的路由和控制器放到单独的文件中:
创建app文件夹:创建routes文件夹和controllers文件夹,分别存放路由和控制器
controllers:
home.js:
class HomeCtl {
index(ctx) {
ctx.body = '这是首页1';
}
}
module.exports = new HomeCtl();
user.js:
class UserCtl {
find(ctx) {
ctx.body = '获取用户列表';
}
findById(ctx) {
ctx.body = '获取指定用户';
}
create(ctx) {
ctx.body = '新建用户';
}
update(ctx) {
ctx.body = '修改指定用户';
}
delete(ctx) {
ctx.body = '删除指定用户';
}
}
module.exports = new UserCtl();
routes:
home.js:
const Router = require('koa-router');
const router = new Router();
const { index } = require('../controllers/home')
// router.get('/', (ctx) => {
// ctx.body = '首页';
// })
router.get('/', index);
module.exports = router;
user.js:
const Router = require('koa-router');
const router = new Router({ prefix: '/user' });
const { find, findById, create, update, delete: del} = require('../controllers/users');
router.get('/', find)
// 获取用户
router.get('/:id', findById)
// 新建用户
router.put('/:id', create)
// 修改用户
router.put('/:id', update)
// 删除用户
router.delete('/:id', del);
module.exports = router;
index.js:
// 此文件功能:在app中批量注册路由
const fs = require('fs');
module.exports = (app) => {
fs.readdirSync(__dirname).forEach(file => {
if(file === 'index.js') { return; }
const route = require(`./${file}`);
app.use(route.routes()).use(route.allowedMethods());
})
}
app/index.js:
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
const routing = require('./routes');
app.use(bodyparser()); //解析请求体
// app中注册路由
// app.use(router.routes());
// app.use(userRouter.routes());
// app.use(userRouter.allowedMethods()); //允许使用options获取请求方式
routing(app);
app.listen(3333, () => {console.log('程序启动')});
八、错误处理
1.状态码
(1)运行时错误:500
(2)逻辑错误:找不到(404)、先决条件失败(412)、无法处理的实体(参数格式不对422)等
2.koa自带的错误处理
(1)404
(2)412:ctx.throw(412, '先决条件失败:id大于等于数组长度');
(3)500
3.自定义koa错误处理中间件
const Koa = require('koa');
const bodyparser = require('koa-bodyparser');
const app = new Koa();
const routing = require('./routes');
// 自定义错误处理中间件(需写在所有中间件前)
app.use(async(ctx, next) => {
try {
await next();
} catch(err) {
ctx.status = err.status || err.statusCode || 500;
ctx.body = {
message: err.message
}
}
})
app.use(bodyparser()); //解析请求体
// app中注册路由
// app.use(router.routes());
// app.use(userRouter.routes());
// app.use(userRouter.allowedMethods()); //允许使用options获取请求方式
routing(app);
app.listen(3333, () => {console.log('程序启动')});
4.用koa-json-error进行错误处理
安装:npm i --save koa-json-error
在app/index.js中引入,写在所有中间件前:
const error = require('koa-json-error');
app.use(error());
在生产环境中不应该出现信息过多的stack字段,则分别设置分生产和开发环境的报错返回值:
// koa-json-error处理错误
app.use(error({
// 设置报错信息中的stack不在生产环境返回,只在测试环境返回
postFormat: (e, {stack, ...rest}) => process.env.NODE_ENV == 'production' ? rest : {stack, ...rest}
}));
测试:
安装跨平台设置环境变量插件: cross-env. npm i --save -dev cross-env
在package.json中设置环境变量:
{
......
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "cross-env NODE_ENV=production nodemon app",
"dev": "cross-env NODE_ENV=development nodemon app"
},
......
}
九、校验请求参数(koa-parameter)
安装koa-parameter: cnpm i --save koa-parameter
在app/index.js中引入koa-parameter:
const parameter = require('koa-parameter');
// 校验请求参数
app.use(parameter(app));
在app/controllers/users.js中使用,校验传入的参数:
create(ctx) {
ctx.body = '123';
// 使用koa-parameter校验
ctx.verifyParams({
name: {type: 'string', require: true}
})
}
十、数据库(MongoDB Atlas)
1.注册并创建集群:https://cloud.mongodb.com
按提示步骤创建:
2.安装mongoose:cnpm i --save mongoose
创建文件夹存储数据库链接:app/config.js:
module.exports = {
connectionStr: 'mongodb+srv://jixueyuandi:1008686@fangzhihu-jwrwu.mongodb.net/test?retryWrites=true&w=majority'
}
// 真正开发时,要用环境变量等方式获取密码,不要把密码写在代码里
使用mongoose连接云数据库,app/index.js:
const {connectionStr} = require('./config.js');
// 连接mongoose数据库
const mongoose = require('mongoose');
mongoose.connect(connectionStr, () => console.log('数据库连接成功'));
mongoose.connection.on('error', console.error);
3.实现用户的增删改:
(1)创建用户模式及模型
app中新建models/users.js:
// 此文件用来创建用户模型
const mongoose = require('mongoose');
const {Schema, model} = mongoose;
const userSchema = new Schema({
name: {type:String,require:true}
});
module.exports = model('User',userSchema); //model(集合名,模式实例)
(2)app中新建controllers/home.js、user.js
home.js:
class HomeCtl {
index(ctx) {
ctx.body = '这是首页1';
}
}
module.exports = new HomeCtl();
user.js:
const User = require('../models/users');
class UserCtl {
async find(ctx) {
ctx.body = await User.find();
}
async findById(ctx) {
ctx.body = await User.findById(ctx.params.id);
}
async create(ctx) {
// 使用koa-parameter校验
ctx.verifyParams({
name: {type: 'string', require: true},
password: {type: 'string', require: true}
})
// 用户名不重复使用
const {name} = ctx.request.body;
const repeatedUser = await User.findOne({name});
if (repeatedUser) {
ctx.throw(409, '用户名已被占用');
}
const user = await new User(ctx.request.body).save();
ctx.body = user;
}
async update(ctx) {
ctx.verifyParams({
name: {
type: 'string',
require: false
},
password: {type: 'string', require: false}
})
const user = await User.findByIdAndUpdate(ctx.params.id, ctx.request.body);
if(!user) {
ctx.throw(404, '用户不存在')
}
ctx.body = user;
}
async delete(ctx) {
const user = await User.findByIdAndRemove(ctx.params.id);
if (!user) {
ctx.throw(404, '用户不存在')
}
ctx.status = 204;
}
}
module.exports = new UserCtl();
(3)app中新建routes/home.js、user.js、index.js
home.js:
const Router = require('koa-router');
const router = new Router();
const { index } = require('../controllers/home')
// router.get('/', (ctx) => {
// ctx.body = '首页';
// })
router.get('/', index);
module.exports = router;
home.js:
const Router = require('koa-router');
const router = new Router({ prefix: '/users' });
const { find, findById, create, update, delete: del} = require('../controllers/users');
router.get('/', find)
// 获取用户
router.get('/:id', findById)
// 新建用户
router.post('/', create)
// 修改用户
router.patch('/:id', update)
// 删除用户
router.delete('/:id', del);
module.exports = router;
index.js:
// 此文件功能:在app中批量注册路由
const fs = require('fs');
module.exports = (app) => {
fs.readdirSync(__dirname).forEach(file => {
if(file === 'index.js') { return; }
const route = require(`./${file}`);
app.use(route.routes()).use(route.allowedMethods());
})
}
十一、jwt实现登录认证
1.jwt实现过程:在用户登录时实行jwt签名,返回token到客户端,客户端将token存储到sessionStorage或localStorage中,在需要用户验证的接口请求时实行jwt验证
2. 安装jsonwebtoken和koa-jwt
cnpm i --save jsonwebtoken
cnpm i --save koa-jwt
3.实现:
在/config.js中设置签名(真正开发时不能在这设置,应该在不同环境变量中获取):
module.exports = {
secret: 'zhihu-jwt-secret',
connectionStr: 'mongodb://127.0.0.1:27017/myuser' //'mongodb+srv://jixueyuandi:1008686@fangzhihu-jwrwu.mongodb.net/test?retryWrites=true&w=majority'
}
// 真正开发时,要用环境变量等方式获取密码,不要把密码写在代码里
在/app/controllers/user.js中写登录接口:
const User = require('../models/users');
const jsonwebtoken = require('jsonwebtoken');
const {secret} = require('../config.js');
class UserCtl {
......
// 验证是否是该用户
async checkOwner(ctx, next) {
console.log(ctx.params)
if(ctx.params.id !== ctx.state.user._id) {
ctx.throw(403, '没有权限');
}
await next();
}
//登录控制器
async login(ctx) {
ctx.verifyParams({
name: {type: 'string', required: true},
password: {type: 'string', required: true}
})
const user = await User.findOne(ctx.request.body);
if(!user) {
ctx.throw(401, '用户名或密码不正确')
}
const {_id, name} = user;
const token = jsonwebtoken.sign({_id,name}, secret, {expiresIn: '1d'})
ctx.body = token;
}
}
module.exports = new UserCtl();
在/app/routes/user.js中设置登录接口 并对需认证的接口添加认证控制器和验证是否是该用户控制器:
const Router = require('koa-router');
const router = new Router({ prefix: '/users' });
const { find, findById, create, update, delete: del, login, checkOwner} = require('../controllers/users');
// const jsonwebtoken = require('jsonwebtoken');
const {secret} = require('../config')
const jwt = require('koa-jwt');
// 方法一:用koa-jwt创建token认证中间件
const auth = jwt({secret:secret})
// 方法二:用jsonwebtoken创建token认证中间件
// const auth = async (ctx, next) => {
// // 获取客户端header传递过来的authorization中的token
// const {authorization=''} = ctx.request.header;
// const token = authorization.replace('Bearer ', '');
// // 进行token验证
// try {
// const user = jsonwebtoken.verify(token, sercet);
// ctx.state.user = user;
// console.log(ctx.state)
// } catch(err) {
// ctx.throw(401, err.message);
// }
// await next();
// }
router.get('/', find)
// 获取用户
router.get('/:id', findById)
// 新建用户
router.post('/', create)
// 修改用户
router.patch('/:id', auth, checkOwner, update)
// 删除用户
router.delete('/:id', auth, checkOwner, del);
// 用户登录
router.post('/login', login);
module.exports = router;
十二、上传图片
安装;koa-body
cnpm i --save koa-body
在app/index.js中引入:
......
const path = require('path');
const koaBody = require('koa-body');
app.use(koaBody({
multipart: true, //上传文件
formidable: {
uploadDir: path.join(__dirname, 'public/uploads'), //上传位置
keepExtensions: true //保留原后缀名
}
});
在controllers/home.js中引入:
const path = require('path');
class Home{
....
upload(ctx) {
const file = ctx.request.files.file;
//ctx.body = {path: file.path}; //返回上传的本地文件路径
//返回链接路径
const basename = path.basename(file.path);
ctx.body = {url:`${ctx.origin}/uploads/${basename}`};
}
}
module.sxports = new Home();
在router/home.js中写相对应接口:
const Router = require('koa-router');
const router = new Router();
const { index,upload } = require('../controllers/home')
// router.get('/', (ctx) => {
// ctx.body = '首页';
// })
router.get('/', index);
router.post('/upload',upload)
module.exports = router;
将文件转为链接:
安装koa-static
在app/index.js中引入并使用:
const koaStatic = require('koa-static');
app.use(koaStatic(path.join(__dirname, 'public'});
图片路径:http://127.0.0.1:3333/uploads/upload_926df733d5384256a5446f95eee64858.png
html中提交图片:在public/uploads中创建upload.html:
<!DOCTYPE html>
<html>
<head>
<meta charset='utf-8'>
<meta http-equiv='X-UA-Compatible' content='IE=edge'>
<title>Page Title</title>
<meta name='viewport' content='width=device-width, initial-scale=1'>
</head>
<body>
<form enctype="multipart/form-data" action="/upload" method="POST">
<input type="file" name="file" accept="image/*">
<button type="submit">提交</button>
</form>
</body>
</html>