一、初始化項目
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:[email protected]/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:[email protected]/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>