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.jsconst {mySql } = require('../config')const mysql = require('mysql')const { logger } = require('../log4j/logger')const { port, host, pass, userName, database } = mySqlconsole.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.jsconst { mySql } = require('../config')const mysql = require('mysql')const { logger } = require('../log4j/logger')const { port, host, pass, userName, database } = mySqllet connectionfunction 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]);})};}// 初始化DBasync function initDB() {try {const connectionMysql = await mysql.createPool({connectionLimit : 10,host,user: userName,password: pass,port,database})connection = connectionMysqlreturn 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.jsconst { getConnection } = require('./app/db/mysql')app.use(async (ctx, next) => {const connection = await getConnection()ctx.mysql = connectionawait 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 nodeconst 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 = nullif (process.env.HOST) {logger.debug('process.env.HOST '+ process.env.HOST)// @ts-ignoreif (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 pipereturn val}if (port >= 0) {// port numberreturn port}return false}function onError(error) {if (error.syscall !== 'listen') {throw error}const bind = typeof port === 'string'? 'Pipe ' + port: 'Port ' + portswitch (error.code) {case 'EACCES':logger.error(bind + ' requires elevated privileges')process.exit(1)breakcase 'EADDRINUSE':logger.error(bind + ' is already in use')process.exit(1)breakdefault:throw error}}/*** 监听server 事件*/function onListening(server) {return () => {const addr = server.address()const bind = typeof addr === 'string'? 'pipe ' + addr: 'port ' + addr.portlogger.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.jsasync 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.idconst 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.fileconst { 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 地址。
