NodeJS

Web框架

Koa

代码示例:

// index.js

const koa = require('koa')

const app = new koa()

app.use((ctx, next) => {
    // 支持省略response与request(不会冲突)
    // ctx.response.body = '<h1>Hello World!</h1>'
    ctx.response.body = {ok: 1}
    // console.log(ctx.request.path)
})

app.listen(3000)

洋葱模型

next可回弹继续执行而不是一直往下执行

代码示例:

// index.js

const koa = require('koa')

const app = new koa()

app.use(async (ctx, next) => {
    if (ctx.url === '/favicon.ico') return
    console.log('aaa')
    const token = await next()
    console.log('ddd', token)
    ctx.body = 'Hello World!'
})

function delay(time) {
    return new Promise((resolve, reject) => {
        setTimeout(resolve, time)
    })
}

app.use(async (ctx, next) => {
    console.log('bbb')
    await delay(1000)
    ctx.token = 'Foo'
    console.log('ccc')
    return 'Bar'
})

app.listen(3000)

路由

代码示例:

// index.js

const koa = require('koa')
const app = new koa()
const Router = require('koa-router')
const router = new Router()

router.post('/list', (ctx, next) => {
    ctx.body = {
        ok: 1,
        info: 'add list success'
    }
})
.get('/list', (ctx, next) => {  // 链式写法
    ctx.body = ['aaa', 'bbb', 'ccc']
})
.put('/list/:id', (ctx, next) => {
    // console.log(ctx.params)
    ctx.body = {
        ok: 1,
        info: 'put list success'
    }
})
.delete('/list/:id', (ctx, next) => {
    // console.log(ctx.params)
    ctx.body = {
        ok: 1,
        info: 'delete list success'
    }
})

app.use(router.routes()).use(router.allowedMethods())  // 第二个use:仅响应相应的请求,如果出现其它的请求则返回405而不是404

app.listen(3000)

拆分路由

代码示例:

// index.js

const koa = require('koa')
const app = new koa()

const router = require('./routes')  // index可以省略

app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
// routes/index.js

const Router = require('koa-router')
const router = new Router()

const userRouter = require('./user')
const listRouter = require('./list')
const homeRouter = require('./home')

// router.prefix('/api')  // 统一配置前缀
// 注册成路由级的组件(或中间件)
router.use('/user', userRouter.routes(), userRouter.allowedMethods())
router.use('/list', listRouter.routes(), listRouter.allowedMethods())

router.use('/home', homeRouter.routes(), homeRouter.allowedMethods())
router.redirect('/', '/home')  // 重定向

module.exports = router
// routes/home.js

const Router = require("koa-router");
const router = new Router()

router.get('/', (ctx, next) => {
    ctx.body = `
    <h1>h1</h1>
    `
})

module.exports = router
// routes/user.js

const Router = require("koa-router");
const router = new Router()

router.post('/', (ctx, next) => {
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

module.exports = router
// routes/list.js

const Router = require("koa-router");
const router = new Router()

router.post('/', (ctx, next) => {
    ctx.body = {
        ok: 1,
        info: 'add list success'
    }
})
    .get('/', (ctx, next) => {
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put list success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete list success'
        }
    })

module.exports = router

静态资源

代码示例:

/* public/css/center.css */

div {
    background-color: #30363d;
}
// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')

const router = require('./routes')

app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
<!-- public/center.html -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Center</title>
    <link rel="stylesheet" href="/css/center.css">
</head>
<body>
    <div>Center</div>
</body>
</html>

获取请求参数

代码示例:

<!-- static/login.html -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <div>
        用户名:<input type="text" id="useName"/>
    </div>
    <div>
        密码:<input type="password" id="pwd"/>
    </div>
    <div>
        <button id="loginGet">登录-GET</button>
        <button id="loginPost">登录-POST</button>
    </div>
    <script>
        // const useName = document.querySelector('#useName')
        // const pwd = document.querySelector('#pwd')
        // const loginGet = document.querySelector('#loginGet')
        // const loginPost = document.querySelector('#loginPost')
        // loginGet.onclick = () => {
        //     fetch(`/user?username=${useName.value}&pwd=${pwd.value}`)
        //         .then(res => res.text())
        //         .then(res => console.log(res))
        //         .catch(err => console.log(err))
        // }
        // loginPost.onclick = () => {
        //     fetch(`/user`, {
        //         method: 'POST',
        //         body: JSON.stringify({
        //             usename: useName.value,
        //             pwd: pwd.value
        //         }),
        //         headers: {
        //             'Content-Type': 'application/json'
        //         }
        //     })
        //         .then(res => res.text())
        //         .then(res => console.log(res))
        //         .catch(err => console.log(err))
        // }

        const useName = document.querySelector('#useName')
        const pwd = document.querySelector('#pwd')
        const loginGet = document.querySelector('#loginGet')
        const loginPost = document.querySelector('#loginPost')
        loginGet.onclick = () => {
            fetch(`/user?username=${useName.value}&pwd=${pwd.value}`)
                .then(res => res.text())
                .then(res => console.log(res))
                .catch(err => console.log(err))
        }
        loginPost.onclick = () => {
            fetch(`/user`, {
                method: 'POST',
                body: `username=${useName.value}&pwd=${pwd.value}`,
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded'
                }
            })
                .then(res => res.text())
                .then(res => console.log(res))
                .catch(err => console.log(err))
        }
    </script>
</body>
</html>
// routes/user.js

const Router = require("koa-router");
const router = new Router()

router.post('/', (ctx, next) => {
    console.log(ctx.request.body)  // 省略掉request是有区别的
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        console.log(ctx.query, ctx.querystring)
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

module.exports = router
// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')
const bodyParser = require('koa-bodyparser')

const router = require('./routes')

app.use(bodyParser())  // 获取POST参数
app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)

ejs模板

代码示例:

// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')

const router = require('./routes')

// 配置模板引擎
app.use(views(path.join(__dirname, 'views'), {extension: 'ejs'}))
app.use(bodyParser())
app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
// routes/home.js

const Router = require("koa-router");
const router = new Router()

router.get('/', async (ctx, next) => {
    // 必须使用async/await(因为是异步执行的)
    await ctx.render('home', {username: 'HQSY'})  // 自动寻找views目录(配置模板引擎时指定的第二个实参)下的home.ejs
})

module.exports = router
<!-- views/home.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
    <h1><%= username%></h1>
</body>
</html>

鉴权

Cookie与Session

代码示例:

// routes/home.js

const Router = require("koa-router");
const router = new Router()

router.get('/', async (ctx, next) => {
    // ctx.cookies.set('name', 'HQSY')
    // console.log(ctx.cookies.get('name'))
    await ctx.render('home', {username: 'HQSY'})
})

module.exports = router
// routes/login.js

const Router = require("koa-router");
const router = new Router()

router.get('/', async (ctx, next) => {
    await ctx.render('login')
})

module.exports = router
// routes/user.js

const Router = require("koa-router");
const router = new Router()

router.post('/', (ctx, next) => {
    console.log(ctx.request.body)
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        console.log(ctx.query, ctx.querystring)
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

router.post('/login', (ctx) => {
    // console.log(ctx.request.body)
    const {username, pwd} = ctx.request.body
    if (username === 'HQSY' && pwd === '123') {
        ctx.session.user = {
            username: 'HQSY'
        }
        ctx.body = {ok: 1}
    } else {
        ctx.body = {ok: 0}
    }
})

module.exports = router
// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')
const session = require('koa-session-minimal')

const router = require('./routes')

app.use(views(path.join(__dirname, 'views'), {extension: 'ejs'}))

app.use(session({
    key: 'HQSY_Session',
    cookie: {
        maxAge: 1000 * 60 * 60
    }
}))

app.use(async (ctx, next) => {
    if (ctx.url.includes('login')) {
        await next()  // 等待执行权的回弹
        return
    }
    if (ctx.session.user) {
        ctx.session.date = Date.now()  // 重置有效期
        await next()
    } else {
        ctx.redirect('/login')
    }
})

app.use(bodyParser())
app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
<!-- views/login.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<div>
    用户名:<input type="text" id="useName"/>
</div>
<div>
    密码:<input type="password" id="pwd"/>
</div>
<div>
    <button id="loginPost">登录-POST</button>
</div>
<script>
    const useName = document.querySelector('#useName')
    const pwd = document.querySelector('#pwd')
    const loginPost = document.querySelector('#loginPost')
    loginPost.onclick = () => {
        fetch(`/user/login`, {
            method: 'POST',
            body: `username=${useName.value}&pwd=${pwd.value}`,
            headers: {
                'Content-Type': 'application/x-www-form-urlencoded'
            }
        })
            .then(res => res.json())
            .then(res => {
                if (res.ok === 1) {
                    location.href = '/home'
                } else {
                    alert('用户名或密码错误!')
                }
            })
            .catch(err => console.log(err))
    }
</script>
</body>
</html>

JWT

代码示例:

// routes/home.js

const Router = require("koa-router");
const router = new Router()

router.get('/', async (ctx, next) => {
    // ctx.cookies.set('name', 'HQSY')
    // console.log(ctx.cookies.get('name'))
    await ctx.render('home', {username: 'HQSY'})
})

router.get('/list', async (ctx) => {
    ctx.body = [
        {
            _id: 1,
            username: 'Foo',
            age: 12
        },
        {
            _id: 2,
            username: 'HQSY',
            age: 24
        },
        {
            _id: 3,
            username: 'Bar',
            age: 18
        }
    ]
})

module.exports = router
// routes/user.js

const Router = require("koa-router");
const JWT = require("../util/JWT");
const router = new Router()

router.post('/', (ctx, next) => {
    console.log(ctx.request.body)
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        console.log(ctx.query, ctx.querystring)
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

router.post('/login', (ctx) => {
    const {username, pwd} = ctx.request.body
    if (username === 'HQSY' && pwd === '123') {
        const token = JWT.generate({
            _id: 'aaa',
            username: 'HQSY'
        }, '1h')
        ctx.set('Authorization', token)
        ctx.body = {ok: 1}
    } else {
        ctx.body = {ok: 0}
    }
})

module.exports = router
// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')
const session = require('koa-session-minimal')

const router = require('./routes')
const JWT = require('./util/JWT')

app.use(views(path.join(__dirname, 'views'), {extension: 'ejs'}))

app.use(session({
    key: 'HQSY_Session',
    cookie: {
        maxAge: 1000 * 60 * 60,
    }
}))

app.use(async (ctx, next) => {
    if (ctx.url.includes('login')) {
        await next()
        return
    }
    const token = ctx.headers['authorization']?.split(' ')[1]
    if (token) {
        const payload = JWT.verify(token)
        if (payload) {
            const newToken = JWT.generate({
                _id: payload._id,
                username: payload.username
            }, '1h')
            ctx.set('Authorization', newToken)
            await next()
        } else {
            ctx.status = 401
            ctx.body = {errCode: -1, errInfo: 'token无效'}
        }
    } else {
        await next()
    }
})

app.use(bodyParser())
app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)
// util/JWT.js

const jwt = require('jsonwebtoken')

const secret = 'HQSY'

const JWT = {
    generate(value, expires) {
        return jwt.sign(value, secret, {expiresIn: expires})
    },
    verify(token) {
        try {
            return jwt.verify(token, secret)
        } catch (err) {
            return false
        }
    }
}

module.exports = JWT
<!-- views/login.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Login</title>
</head>
<body>
<div>
    <div>用户名:<input type="text" id="username"></div>
    <div>密码:<input type="password" id="pwd"></div>
    <div><button id="login">登录</button></div>
</div>
<script src="/dist/axios.min.js"></script>
<script>
    axios.interceptors.request.use(function (config) {
        return config
    }, function (error) {
        return Promise.reject((error))
    })

    axios.interceptors.response.use(function (response) {
        const {authorization} = response.headers
        authorization && localStorage.setItem('token', authorization)
        return response
    }, function (error) {
        return Promise.reject(error)
    })
</script>
<script>
    const username = document.querySelector('#username')
    const pwd = document.querySelector('#pwd')
    const login = document.querySelector('#login')
    login.onclick = () => {
        axios.post('/user/login', {
            username: username.value,
            pwd: pwd.value
        }).then(res => {
            if (res.data.ok === 1){
                location.href = '/'
            } else {
                alert('用户名或密码错误!')
            }
        }).catch(err => console.log(err))
    }
</script>
</body>
</html>
<!-- views/home.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Home</title>
</head>
<body>
<div>
    <div>用户名:<input type="text" id="username"></div>
    <div>密码:<input type="password" id="pwd"></div>
    <div>年龄:<input type="number" id="age"></div>
    <div>头像:<input type="file" id="avatar"></div>
    <div><button id="register">添加用户</button></div>
</div>
<table>
    <thead>
    <tr>
        <td>id</td>
        <td>用户名</td>
        <td>年龄</td>
    </tr>
    </thead>
    <tbody>

    </tbody>
</table>
<div>
    <button id="exit">退出登录</button>
</div>
<script src="/dist/axios.min.js"></script>
<script>
    axios.interceptors.request.use(function (config) {
        const token = localStorage.getItem('token')
        config.headers.Authorization = `Bearer ${token}`
        return config
    }, function (error) {
        return Promise.reject((error))
    })

    axios.interceptors.response.use(function (response) {
        const {authorization} = response.headers
        authorization && localStorage.setItem('token', authorization)
        return response
    }, function (error) {
        if (error.response.status === 401) {
            localStorage.removeItem('token')
            location.href = '/login'
        }
        return Promise.reject(error)
    })
</script>
<script>
    const register = document.querySelector('#register')
    const exit = document.querySelector('#exit')

    exit.onclick = () => {
        localStorage.removeItem('token')
        location.href = '/login'
    }
    axios.get('/home/list').then(res => {
        const tbody = document.querySelector('tbody')
        tbody.innerHTML = res.data.map(item => `
                <tr>
                    <td>${item._id}</td>
                    <td>${item.username}</td>
                    <td>${item.age}</td>
                </tr>
                `).join('')
    }).catch(err => console.log(err))
</script>
</body>
</html>

文件上传

代码示例:

// routes/index.js

const Router = require('koa-router')
const router = new Router()

const userRouter = require('./user')
const listRouter = require('./list')
const homeRouter = require('./home')
const loginRouter = require('./login')
const uploadRouter = require('./upload')

router.use('/user', userRouter.routes(), userRouter.allowedMethods())
router.use('/list', listRouter.routes(), listRouter.allowedMethods())

router.use('/home', homeRouter.routes(), homeRouter.allowedMethods())
router.use('/login', loginRouter.routes(), loginRouter.allowedMethods())
router.use('/upload', uploadRouter.routes(), uploadRouter.allowedMethods())
router.redirect('/', '/home')  // 重定向

module.exports = router
// routes/user.js

const Router = require("koa-router");
const JWT = require("../util/JWT");
const router = new Router()
const multer = require('@koa/multer')
const upload = multer({dest: 'public/uploads'})

router.post('/', (ctx, next) => {
    console.log(ctx.request.body)
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        console.log(ctx.query, ctx.querystring)
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

router.post('/login', (ctx) => {
    const {username, pwd} = ctx.request.body
    if (username === 'HQSY' && pwd === '123') {
        const token = JWT.generate({
            _id: 'aaa',
            username: 'HQSY'
        }, '1h')
        ctx.set('Authorization', token)
        ctx.body = {ok: 1}
    } else {
        ctx.body = {ok: 0}
    }
})

router.post('/upload', upload.single('avatar'), (ctx) => {
    console.log(ctx.request.body, ctx.file)
    ctx.body = {ok: 1}
})

module.exports = router
// routes/upload.js

const Router = require("koa-router");
const router = new Router()

router.get('/', async (ctx, next) => {
    await ctx.render('upload')
})

module.exports = router
<!-- /views/upload.ejs -->

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>Upload</title>
</head>
<body>
<form action="/user/upload" method="POST" enctype="multipart/form-data">
    <div>
        用户名:<input type="text" name="username"/>
    </div>
    <div>
        密码:<input type="password" name="pwd"/>
    </div>
    <div>
        年龄:<input type="number" name="age"/>
    </div>
    <div>
        头像:<input type="file" name="avatar"/>
    </div>
    <div>
        <input type='submit' value="提交">
    </div>
</form>
</body>
</html>

MongoDB

代码示例:

// config/db.config.js

const mongoose = require('mongoose')

mongoose.connect('mongodb://127.0.0.1:27017/test')
// routes/user.js

const Router = require("koa-router");
const JWT = require("../util/JWT");
const router = new Router()
const multer = require('@koa/multer')
const upload = multer({dest: 'public/uploads'})
const UserModel = require('../model/UserModel')

router.post('/', (ctx, next) => {
    console.log(ctx.request.body)
    ctx.body = {
        ok: 1,
        info: 'add user success'
    }
})
    .get('/', (ctx, next) => {
        console.log(ctx.query, ctx.querystring)
        ctx.body = ['aaa', 'bbb', 'ccc']
    })
    .put('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'put user success'
        }
    })
    .delete('/:id', (ctx, next) => {
        ctx.body = {
            ok: 1,
            info: 'delete user success'
        }
    })

router.post('/login', (ctx) => {
    const {username, pwd} = ctx.request.body
    if (username === 'HQSY' && pwd === '123') {
        const token = JWT.generate({
            _id: 'aaa',
            username: 'HQSY'
        }, '1h')
        ctx.set('Authorization', token)
        ctx.body = {ok: 1}
    } else {
        ctx.body = {ok: 0}
    }
})

router.post('/upload', upload.single('avatar'), async (ctx) => {
    // console.log(ctx.request.body, ctx.file)
    const {username, age, pwd} = ctx.request.body
    const avatar = ctx.file ? `/uploads/${ctx.file.filename}` : `默认头像.png`
    await UserModel.create({
        username,
        age,
        pwd,
        avatar
    })
    ctx.body = {ok: 1}
})

module.exports = router
// model/UserModel.js

const mongoose = require('mongoose')

const UserType = {
    username: String,
    pwd: String,
    age: Number,
    avatar: String
}

const Schema = new mongoose.Schema(UserType)

const UserModel = mongoose.model('user', Schema)

module.exports = UserModel
// index.js

const koa = require('koa')
const app = new koa()
const koaStatic = require('koa-static')
const path = require('path')
const bodyParser = require('koa-bodyparser')
const views = require('koa-views')
const session = require('koa-session-minimal')

const router = require('./routes')
const JWT = require('./util/JWT')

require('./config/db.config')

app.use(views(path.join(__dirname, 'views'), {extension: 'ejs'}))

app.use(session({
    key: 'HQSY_Session',
    cookie: {
        maxAge: 1000 * 60 * 60,
    }
}))

app.use(async (ctx, next) => {
    if (ctx.url.includes('login')) {
        await next()
        return
    }
    const token = ctx.headers['authorization']?.split(' ')[1]
    if (token) {
        const payload = JWT.verify(token)
        if (payload) {
            const newToken = JWT.generate({
                _id: payload._id,
                username: payload.username
            }, '1h')
            ctx.set('Authorization', newToken)
            await next()
        } else {
            ctx.status = 401
            ctx.body = {errCode: -1, errInfo: 'token无效'}
        }
    } else {
        await next()
    }
})

app.use(bodyParser())
app.use(koaStatic(path.join(__dirname, 'public')))
app.use(router.routes()).use(router.allowedMethods())
app.listen(3000)