1.0 前言
数据库在我们工作中是必不可少的,我们操作的数据基本最后都会放在数据库里存着,其中了 Mysql 是用的最多的关系型数据库,这节我们就一起学习 在 Koa 中 Mysql的基本操作,在看这篇文章之前,需要有些前置知识,特别是数据库的基本知识,比如知道 数据库是什么?Mysql 怎么安装?,基本增删改查SQL?可以参考以下链接:
2.0 Mysql 基本使用
有了上面基本理论知识后,我们先用 Node.js 简单操作下 Mysql,在 Node.js 中我们基本都是用 mysql
这个 npm 包来操作数据库。
2.0.1 安装
npm i mysql -s
2.0.2 基本使用
首先我们是链接数据库,我们上节讲过,一般我们实际开发是分不同环境配置的的, 本地开发我们可以链接本地数据库,生产环境我们肯定是连接其他数据的,所以数据库连接要区分环境。
首先我们配置数据库连接信息。
// app/config/dev.env.js
// 把数据库配置文件抽到配置文件
module.exports = {
baseUrl: '',
mySql: {
port: 3306,
host: 'localhost',
pass: 'root',
userName: 'root',
database: 'koa-mysql-test'
},
logger: {
applicationLevel: "debug",
accessLevel: "debug"
}
}
对应用户表结构
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`img_url` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
SET FOREIGN_KEY_CHECKS = 1;
接下来,我们配置数据库信息。
// app/db/mysql1.js
const {mySql } = require('../config')
const mysql = require('mysql')
const { logger } = require('../log4j/logger')
const { port, host, pass, userName, database } = mySql
console.log(mySql)
async function initDB() {
// 创建连接
const connection = mysql.createConnection({
host,
user: userName,
password: pass,
port,
database
});
// 这里连接数据
return new Promise((resolve, reject) => {
connection.connect(function(err) {
if (err) {
logger.error('error connecting: ' + err.stack)
reject(err)
return;
}
logger.info(`mysql connect success`)
resolve(connection)
});
})
}
async function connectDB() {
const connection = await initDB()
// 我们先插入一条数据
connection.query('insert into user set ?', { name: "张三" }, function(err, data) {
if(err) {
logger.error(err)
} else {
connection.query('SELECT * from user', function(err, data) {
if(err) {
logger.error(err)
} else {
console.log("DB 获取到数据:", data)
connection.end() // 释放连接
}
});
}
});
}
connectDB()
3.0 Koa 中 使用 Mysql
在之前的章节中,我们有有过登录,用户增删改查接口,这次我们把用户增删改成基于数据库的操作。
3.0.1 Mysql 连接放到 Koa 中间件里去
首先我们对 Mysql 的 connection 进行一成包装。
// app/db/mysql.js
const { mySql } = require('../config')
const mysql = require('mysql')
const { logger } = require('../log4j/logger')
const { port, host, pass, userName, database } = mySql
let connection
function wrapQueryMethodPromise(fn, ctx) {
return function(...args) {
return new Promise( (resolve, reject) => {
const callback = (err, data) => {
if(err) {
reject(err)
} else {
resolve(data)
}
}
fn.apply(ctx, [...args, callback]);
})
};
}
// 初始化DB
async function initDB() {
try {
const connectionMysql = await mysql.createPool({
connectionLimit : 10,
host,
user: userName,
password: pass,
port,
database
})
connection = connectionMysql
return Promise.resolve(connectionMysql)
} catch (error) {
logger.error(error)
}
}
// 暴露 connection 对象,同时给 query 方法 新增 promise 返回,方便调用
async function getConnection() {
const queryMethods = ['query']
queryMethods.forEach(function(name) {
connection[name+'ForPromise'] = wrapQueryMethodPromise(connection[name], connection);
});
return connection
}
module.exports = {
initDB,
getConnection
}
接下来 我们把 connection 对象绑定到 Koa 的 ctx 对象上去。
// app.js
const { getConnection } = require('./app/db/mysql')
app.use(async (ctx, next) => {
const connection = await getConnection()
ctx.mysql = connection
await next()
})
...... 省略代码
app.listen(3000, () => {
console.log(' starting at port 3000')
})
3.0.2 数据库启动方式
看完 3.0.1,大家会发现初始化数据库的 initDB 方法没有调用的地方。
首先需要先连上 Mysql 后在启动 Koa Server,相当于在服务启动之前我们需要做一些一些事情,因为数据库都没有连上的话,我们后面的事情是开展不起来的,所以我们对服务启动做一些改造。
把 app.js 里面监听移除,把服务启动放到 bin/www 文件中。
app.js 改造如下:
// app.js
注释以下代码
// app.listen(3000, () => {
// console.log(' starting at port 3000')
// })
// 改造成
module.exports = app
新增 bin/www 文件
#!/usr/bin/env node
const http = require('http')
const validator = require('validator')
const app = require('../app')
const { logger } = require('../app/log4j/logger')
const { initDB } = require('../app/db/mysql')
/**
* 获取端口
*/
const port = normalizePort(process.env.PORT || '3000')
let host = null
if (process.env.HOST) {
logger.debug('process.env.HOST '+ process.env.HOST)
// @ts-ignore
if (validator.isIP(process.env.HOST)) {
logger.trace(process.env.HOST + ' valid')
host = process.env.HOST
} else {
logger.warn('process.env.HOST '+ process.env.HOST + ' invalid, use 0.0.0.0 instead')
}
}
/**
* 在这里进行改造,先连接上数据库,然后再创建 http server
*/
Promise.all([initDB()]).then(connnect => {
const server = http.createServer(app.callback())
server.listen(port, host)
server.on('error', onError)
server.on('listening', onListening(server))
})
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
const port = parseInt(val, 10)
if (isNaN(port)) {
// named pipe
return val
}
if (port >= 0) {
// port number
return port
}
return false
}
function onError(error) {
if (error.syscall !== 'listen') {
throw error
}
const bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port
switch (error.code) {
case 'EACCES':
logger.error(bind + ' requires elevated privileges')
process.exit(1)
break
case 'EADDRINUSE':
logger.error(bind + ' is already in use')
process.exit(1)
break
default:
throw error
}
}
/**
* 监听server 事件
*/
function onListening(server) {
return () => {
const addr = server.address()
const bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port
logger.info('Listening on ' + bind)
}
}
修改 package.json dev 启动文件
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"dev": "cross-env NODE_ENV=dev nodemon bin/www",
"start:qa": "cross-env NODE_ENV=qa && nodemon app.js",
"start:prod": "cross-env NODE_ENV=prod && nodemon app.js"
},
3.0.2 用户接口处理
接下来对用户 controller 做下处理:
// app/controller/user.js
async function list(ctx) {
const data = await ctx.mysql.queryPromise('select * from user')
ctx.body = {
data: data,
success: true
}
}
async function detail(ctx) {
const id = ctx.params.id
const data = await ctx.mysql.queryPromise('select * from user where id = ? ', [id])
ctx.body = {
data: data[0],
success: true
}
}
async function add(ctx) {
const { path } = ctx.request.files.file
const { name, email } = ctx.request.body // 获取 request body 字段
const imgUrl = path.split("/static")[1]
const data = await ctx.mysql.queryPromise(` insert into user set ? `, { name, email, img_url: imgUrl })
ctx.body = {
success: true,
}
}
module.exports = {
detail,
list,
add
}
如上,我们对用户的新增,查询,详情接口做了处理,这样就可以把界面和数据的操作结合起来了,现在就改造完成了。
4.0 小结
这节是讲 Mysql 的基本操作,讲了, Node.js 中如何 和 Mysql 创建连接,如何组织项目代码结构,最后用户一个的例子来实际操作,大家可以对着 Demo,跑起来,理解下,Demo 地址。