1.0 前言

数据库在我们工作中是必不可少的,我们操作的数据基本最后都会放在数据库里存着,其中了 Mysql 是用的最多的关系型数据库,这节我们就一起学习 在 Koa 中 Mysql的基本操作,在看这篇文章之前,需要有些前置知识,特别是数据库的基本知识,比如知道 数据库是什么?Mysql 怎么安装?,基本增删改查SQL?可以参考以下链接:

2.0 Mysql 基本使用

有了上面基本理论知识后,我们先用 Node.js 简单操作下 Mysql,在 Node.js 中我们基本都是用 mysql 这个 npm 包来操作数据库。

2.0.1 安装

  1. npm i mysql -s

2.0.2 基本使用

首先我们是链接数据库,我们上节讲过,一般我们实际开发是分不同环境配置的的, 本地开发我们可以链接本地数据库,生产环境我们肯定是连接其他数据的,所以数据库连接要区分环境。

首先我们配置数据库连接信息。

  1. // app/config/dev.env.js
  2. // 把数据库配置文件抽到配置文件
  3. module.exports = {
  4. baseUrl: '',
  5. mySql: {
  6. port: 3306,
  7. host: 'localhost',
  8. pass: 'root',
  9. userName: 'root',
  10. database: 'koa-mysql-test'
  11. },
  12. logger: {
  13. applicationLevel: "debug",
  14. accessLevel: "debug"
  15. }
  16. }

对应用户表结构

  1. DROP TABLE IF EXISTS `user`;
  2. CREATE TABLE `user` (
  3. `id` int(11) NOT NULL AUTO_INCREMENT,
  4. `name` varchar(255) DEFAULT NULL,
  5. `email` varchar(255) DEFAULT NULL,
  6. `img_url` varchar(255) DEFAULT NULL,
  7. PRIMARY KEY (`id`)
  8. ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
  9. SET FOREIGN_KEY_CHECKS = 1;

接下来,我们配置数据库信息。

  1. // app/db/mysql1.js
  2. const {mySql } = require('../config')
  3. const mysql = require('mysql')
  4. const { logger } = require('../log4j/logger')
  5. const { port, host, pass, userName, database } = mySql
  6. console.log(mySql)
  7. async function initDB() {
  8. // 创建连接
  9. const connection = mysql.createConnection({
  10. host,
  11. user: userName,
  12. password: pass,
  13. port,
  14. database
  15. });
  16. // 这里连接数据
  17. return new Promise((resolve, reject) => {
  18. connection.connect(function(err) {
  19. if (err) {
  20. logger.error('error connecting: ' + err.stack)
  21. reject(err)
  22. return;
  23. }
  24. logger.info(`mysql connect success`)
  25. resolve(connection)
  26. });
  27. })
  28. }
  29. async function connectDB() {
  30. const connection = await initDB()
  31. // 我们先插入一条数据
  32. connection.query('insert into user set ?', { name: "张三" }, function(err, data) {
  33. if(err) {
  34. logger.error(err)
  35. } else {
  36. connection.query('SELECT * from user', function(err, data) {
  37. if(err) {
  38. logger.error(err)
  39. } else {
  40. console.log("DB 获取到数据:", data)
  41. connection.end() // 释放连接
  42. }
  43. });
  44. }
  45. });
  46. }
  47. connectDB()

这样我们就完成了一个数据库最简单的插入和查询操作。

3.0 Koa 中 使用 Mysql

在之前的章节中,我们有有过登录,用户增删改查接口,这次我们把用户增删改成基于数据库的操作。

3.0.1 Mysql 连接放到 Koa 中间件里去

首先我们对 Mysql 的 connection 进行一成包装。

  1. // app/db/mysql.js
  2. const { mySql } = require('../config')
  3. const mysql = require('mysql')
  4. const { logger } = require('../log4j/logger')
  5. const { port, host, pass, userName, database } = mySql
  6. let connection
  7. function wrapQueryMethodPromise(fn, ctx) {
  8. return function(...args) {
  9. return new Promise( (resolve, reject) => {
  10. const callback = (err, data) => {
  11. if(err) {
  12. reject(err)
  13. } else {
  14. resolve(data)
  15. }
  16. }
  17. fn.apply(ctx, [...args, callback]);
  18. })
  19. };
  20. }
  21. // 初始化DB
  22. async function initDB() {
  23. try {
  24. const connectionMysql = await mysql.createPool({
  25. connectionLimit : 10,
  26. host,
  27. user: userName,
  28. password: pass,
  29. port,
  30. database
  31. })
  32. connection = connectionMysql
  33. return Promise.resolve(connectionMysql)
  34. } catch (error) {
  35. logger.error(error)
  36. }
  37. }
  38. // 暴露 connection 对象,同时给 query 方法 新增 promise 返回,方便调用
  39. async function getConnection() {
  40. const queryMethods = ['query']
  41. queryMethods.forEach(function(name) {
  42. connection[name+'ForPromise'] = wrapQueryMethodPromise(connection[name], connection);
  43. });
  44. return connection
  45. }
  46. module.exports = {
  47. initDB,
  48. getConnection
  49. }

接下来 我们把 connection 对象绑定到 Koa 的 ctx 对象上去。

  1. // app.js
  2. const { getConnection } = require('./app/db/mysql')
  3. app.use(async (ctx, next) => {
  4. const connection = await getConnection()
  5. ctx.mysql = connection
  6. await next()
  7. })
  8. ...... 省略代码
  9. app.listen(3000, () => {
  10. console.log(' starting at port 3000')
  11. })

3.0.2 数据库启动方式

看完 3.0.1,大家会发现初始化数据库的 initDB 方法没有调用的地方。
首先需要先连上 Mysql 后在启动 Koa Server,相当于在服务启动之前我们需要做一些一些事情,因为数据库都没有连上的话,我们后面的事情是开展不起来的,所以我们对服务启动做一些改造。

把 app.js 里面监听移除,把服务启动放到 bin/www 文件中。
app.js 改造如下:

  1. // app.js
  2. 注释以下代码
  3. // app.listen(3000, () => {
  4. // console.log(' starting at port 3000')
  5. // })
  6. // 改造成
  7. module.exports = app

新增 bin/www 文件

  1. #!/usr/bin/env node
  2. const http = require('http')
  3. const validator = require('validator')
  4. const app = require('../app')
  5. const { logger } = require('../app/log4j/logger')
  6. const { initDB } = require('../app/db/mysql')
  7. /**
  8. * 获取端口
  9. */
  10. const port = normalizePort(process.env.PORT || '3000')
  11. let host = null
  12. if (process.env.HOST) {
  13. logger.debug('process.env.HOST '+ process.env.HOST)
  14. // @ts-ignore
  15. if (validator.isIP(process.env.HOST)) {
  16. logger.trace(process.env.HOST + ' valid')
  17. host = process.env.HOST
  18. } else {
  19. logger.warn('process.env.HOST '+ process.env.HOST + ' invalid, use 0.0.0.0 instead')
  20. }
  21. }
  22. /**
  23. * 在这里进行改造,先连接上数据库,然后再创建 http server
  24. */
  25. Promise.all([initDB()]).then(connnect => {
  26. const server = http.createServer(app.callback())
  27. server.listen(port, host)
  28. server.on('error', onError)
  29. server.on('listening', onListening(server))
  30. })
  31. /**
  32. * Normalize a port into a number, string, or false.
  33. */
  34. function normalizePort(val) {
  35. const port = parseInt(val, 10)
  36. if (isNaN(port)) {
  37. // named pipe
  38. return val
  39. }
  40. if (port >= 0) {
  41. // port number
  42. return port
  43. }
  44. return false
  45. }
  46. function onError(error) {
  47. if (error.syscall !== 'listen') {
  48. throw error
  49. }
  50. const bind = typeof port === 'string'
  51. ? 'Pipe ' + port
  52. : 'Port ' + port
  53. switch (error.code) {
  54. case 'EACCES':
  55. logger.error(bind + ' requires elevated privileges')
  56. process.exit(1)
  57. break
  58. case 'EADDRINUSE':
  59. logger.error(bind + ' is already in use')
  60. process.exit(1)
  61. break
  62. default:
  63. throw error
  64. }
  65. }
  66. /**
  67. * 监听server 事件
  68. */
  69. function onListening(server) {
  70. return () => {
  71. const addr = server.address()
  72. const bind = typeof addr === 'string'
  73. ? 'pipe ' + addr
  74. : 'port ' + addr.port
  75. logger.info('Listening on ' + bind)
  76. }
  77. }

修改 package.json dev 启动文件

  1. "scripts": {
  2. "test": "echo \"Error: no test specified\" && exit 1",
  3. "dev": "cross-env NODE_ENV=dev nodemon bin/www",
  4. "start:qa": "cross-env NODE_ENV=qa && nodemon app.js",
  5. "start:prod": "cross-env NODE_ENV=prod && nodemon app.js"
  6. },

这样我们数据库改造就成功了

3.0.2 用户接口处理

接下来对用户 controller 做下处理:

  1. // app/controller/user.js
  2. async function list(ctx) {
  3. const data = await ctx.mysql.queryPromise('select * from user')
  4. ctx.body = {
  5. data: data,
  6. success: true
  7. }
  8. }
  9. async function detail(ctx) {
  10. const id = ctx.params.id
  11. const data = await ctx.mysql.queryPromise('select * from user where id = ? ', [id])
  12. ctx.body = {
  13. data: data[0],
  14. success: true
  15. }
  16. }
  17. async function add(ctx) {
  18. const { path } = ctx.request.files.file
  19. const { name, email } = ctx.request.body // 获取 request body 字段
  20. const imgUrl = path.split("/static")[1]
  21. const data = await ctx.mysql.queryPromise(` insert into user set ? `, { name, email, img_url: imgUrl })
  22. ctx.body = {
  23. success: true,
  24. }
  25. }
  26. module.exports = {
  27. detail,
  28. list,
  29. add
  30. }

如上,我们对用户的新增,查询,详情接口做了处理,这样就可以把界面和数据的操作结合起来了,现在就改造完成了。
image.png

4.0 小结

这节是讲 Mysql 的基本操作,讲了, Node.js 中如何 和 Mysql 创建连接,如何组织项目代码结构,最后用户一个的例子来实际操作,大家可以对着 Demo,跑起来,理解下,Demo 地址