> For the complete documentation index, see [llms.txt](https://gb.akanote.cn/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://gb.akanote.cn/cms/part3koa-yu-mysql.md).

# Part3-Koa与MySQL

## 一、Koa2安装

创建一个空白目录，然后进入终端，并在终端对koa进行安装：

```shell
# 项目初始化
$ npm init -y

# 安装koa2
$ npm i koa2 -S

# 安装nodemon
$ npm i nodemon -D
```

## 二、入口文件

在项目根目录创建 `app.js` 文件，并在上一步操作中生成的 `package.json` 里配置：

```json
{
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "start": "nodemon app.js"
  },
}
```

在 `app.js` 中：

```js
const Koa = require('koa2');
const app = new Koa();
const port = 9000;

/* 
	解释下面这段代码：
	app.use()方法是：将给定的中间件方法添加到此应用程序。简单说就是调用中间件
	app.use() 返回 this, 因此可以链式表达
*/
app.use(async (ctx)=>{
    ctx.body = "Hello, Koa";
  	// ctx.body是ctx.response.body的简写
})

app.listen(port, ()=>{
    console.log('Server is running at http://localhost:'+port);
})
```

然后运行 `npm run start` ，并在浏览器输入 `http://localhost:9000/` 即可看到页面效果。

> 提示：
>
> 为了以后对端口和IP的管理方便，其实可以将IP与端口放在一个文件导出，然后导入使用，这样就可以方便管理：
>
> ```js
> // 生产环境域名：http://xxx.com    开发环境域名：http://localhost
> const host = "http://localhost";
> // 生产环境端口：自定义         开发环境域名：9000
> const port = 9000;
>
> module.exports = {
>     host, port
> }
> ```
>
> 然后到app.js或者需要使用IP与端口的文件中导入使用：
>
> ```js
> const {host, port} = require("./utils")
>
> app.listen(port, ()=>{
>     console.log(`Server is running at ${host}:${port}`);
> })
> ```

## 三、洋葱模型

学Koa必须要了解 `洋葱模型` :

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwjyczwj30da0c30w0.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwjyczwj30da0c30w0.jpg)

`Koa` 和 `Express` 都会使用到中间件，Express的中间件是顺序执行，从第一个中间件执行到最后一个中间件，发出响应：

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwr650yj30oj08bwep.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwr650yj30oj08bwep.jpg)

Koa是从第一个中间件开始执行，遇到 `next` 进入下一个中间件，一直执行到最后一个中间件，在逆序，执行上一个中间件 `next` 之后的代码，一直到第一个中间件执行结束才发出响应。

[![](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwvfbaaj30mm0fxwfe.jpg)](https://tva1.sinaimg.cn/large/008eGmZEgy1gplrwvfbaaj30mm0fxwfe.jpg)

对于这个洋葱模型，我们用代码来解释一下。假如把上面的代码改写成：

```js
const Koa = require('koa2');
const app = new Koa();
const port = 9000;

app.use(async (ctx, next)=>{
    console.log(1)
    await next();
    console.log(2)
})

app.use(async (ctx, next)=>{
    console.log(3)
    await next();
    console.log(4)
})

app.use(async (ctx)=>{
    console.log(5)
})

app.listen(port, ()=>{
    console.log('Server is running at http://localhost:'+port);
})
```

那么在浏览器刷新后，控制台得到的顺序是：

```
1
3
5
4
2
```

现在可以看到，我们通过 `next`可以先运行下个中间件，等中间件结束后，再继续运行当前 `next()` 之后的代码。

## 四、路由安装

当需要匹配不同路由时，可以安装：

```shell
$ npm i koa-router
```

将 `app.js` 修改：

```js
const Koa = require('koa2');
const Router = require('koa-router');
const app = new Koa();
const router = new Router();
const port = 9000;

router.get('/', async (ctx)=>{
    ctx.body = "根路径";
})

router.get('/manage', async (ctx)=>{
    ctx.body = "管理系统";
})

app.use(router.routes(), router.allowedMethods());

app.listen(port, ()=>{
    console.log(`Server is running at http://localhost:${port}`);
})
```

此时，到浏览器刷新并在地址栏最后添加 `/manage` 即可得到根路径内容和列表模块内容。

备注：

```js
// 调用router.routes()来组装匹配好的路由，返回一个合并好的中间件
// 调用router.allowedMethods()获得一个中间件，当发送了不符合的请求时，会返回 `405 Method Not Allowed` 或 `501 Not Implemented`

allowedMethods方法可以做以下配置：
app.use(router.allowedMethods({ 
    // throw: true, // 抛出错误，代替设置响应头状态
    // notImplemented: () => '不支持当前请求所需要的功能',
    // methodNotAllowed: () => '不支持的请求方式'
}))
```

## 五、路由拆分

当项目较大，路由较多时，我们需要划分模块。此时，就需要对路由进行拆分。这个项目的后端要服务于Web官网和后台管理系统，因此我们将路由划分成两个模块：web与manage。

### 1、创建 `router` 文件夹

创建router文件夹，并在其中创建：`index.js` （路由总入口文件）、`manage/index.js` （manage模块路由入口文件）、`web/index.js` （web模块路由入口文件）：

```js
// app.js
const Koa = require("koa2");
const router = require("./router")
const app = new Koa();
const port = 9000;

// 调用router中间件
app.use(router.routes(), router.allowedMethods());

app.listen(port, ()=>{
    console.log(`Server is running at http://localhost:${port}`);
})

// index.js
const Router = require("koa-router");
const manage = require("./manage");
const web = require("./web");
const router = new Router();

router.get("/", async ctx=>{
    ctx.body = "根路径"
})

router.use("/manage", manage.routes(), manage.allowedMethods());
router.use("/web", web.routes(), web.allowedMethods());

module.exports = router;


// manage/index.js
const Router = require("koa-router")
const router = new Router();

router.get('/', async ctx=>{
    ctx.body = "管理系统"
})

module.exports = router;


// web/index.js
const Router = require("koa-router")
const router = new Router();

router.get('/', async ctx=>{
    ctx.body = "官网"
})

module.exports = router;
```

到浏览器刷新 `localhost:9000/manage` 与 `localhost:9000/web` 即可得到manage和web两个模块返回的数据。

### 2、路由重定向（了解）

那么有同学会问了，如果我想直接从 `localhost:9000` 重定向到 `localhost:9000/home` 该怎么办？

我们可以在 `router/index.js` 中做如下配置：

```js
router.use('/home', home.routes(), home.allowedMethods());
...
router.redirect('/', '/home');
```

### 3、404无效路由

如果被访问到无效路由，那么我们可以统一返回404页面：

在 `router` 下 `errorPage.js` :

```js
const Router = require('koa-router');
const errorPage = new Router();

errorPage.get('/', async (ctx) => {
    ctx.body = "访问页面不存在";
})

module.exports = errorPage;
```

在 `router/index.js` 中：

```js
const errorPage = require("./errorPage")

// 404页面路由
router.use("/404", errorPage.routes(), errorPage.allowedMethods());
```

在 `app.js` 中引用：

```js
// 匹配不到页面的全部跳转去404
app.use(async (ctx, next) => {
    await next();
    if (parseInt(ctx.status) === 404) {
        ctx.response.redirect("/404")
    }
})
app.use(router.routes(), router.allowedMethods());
```

## 六、后端允许跨域（了解）

前端想跨域，可以设置proxy。如果后端允许跨域，可以如下操作：

```js
// 安装koa2-cors
$ cnpm i koa2-cors

// 引入koa2-cors中间件
const cors = require("koa2-cors");
// 这里cors中间件一定要写在路由之前
app.use(cors());

app.use(router.routes(), router.allowedMethods())
```

## 七、读取静态资源文件

首先安装 `koa-static`，命令行代码如下：

```shell
$ yarn add koa-static
```

然后在项目的根目录下创建 `assets` 后，将图片资源（图片自己随便找一张）文件夹 `images` 放到其中。我们假定404页面需要返回一张错误警告图，可以在 `app.js` 中执行以下操作：

```js
// 引入
const path = require('path')
const static = require('koa-static')

// 获取静态资源文件夹
app.use(static(path.join(__dirname, '/assets')));
...
app.use(router.routes(), router.allowedMethods())
```

假设其中有一张图片叫做 `404.gif`，那么我们打开浏览器，访问：`http://localhost:9000/images/404.gif` 即可得到图片。这里注意：

> 路径上不需要写assets，因为我们已经指定了访问资源时， <http://localhost:9000> 自动指向 assets 文件夹。由此，我们知道数据库中图片的地址只需要填写 `/images/404.gif` 即可。

如果我们希望打开404页面就显示这张图，就需要做如下步骤：

### 1、安装mime-types

```shell
$ npm i mime-types
```

### 2、使用fs读取文件

修改errorPage.js：

```js
const Router = require("koa-router")
const router = new Router();
const fs = require("fs")
const path = require("path")
const mime = require("mime-types")

router.get('/', async ctx=>{
    const filePath = path.join(__dirname, "../assets/images/404.gif");
    const file = fs.readFileSync(filePath); // 读取文件
    const mimeType = mime.lookup(filePath)  // 读取文件类型
    ctx.set("content-type", mimeType);  // 设置返回类型（这一步很重要）
    ctx.body = file;    // 返回图片
})

module.exports = router;
```

## 八、MySQL安装【软件还需整理】

### 1、Windows卸载MySQL

如果你曾装过MySQL，请务必先卸载干净。

首先检查电脑是否安装过MySQL，Windows执行：开始->运行->输入 `services.msc`。如果看到有MySQL，就代表安装了，如图：

[![image-20211203162439294](https://i.loli.net/2021/12/03/jeX8dk9sGlqvyS2.png)](https://i.loli.net/2021/12/03/jeX8dk9sGlqvyS2.png)

如果没有，就可以忽略删除MySQL这一步了。但如果看到了这一项，请右键先停止这项服务，然后：

快捷键 `win+r` 输入 `regedit` 进入注册表，找到 `HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Eventlog\Application\MySQL` 文件夹并对其进行删除。

> 如果有以下两个文件夹：
>
> 删除 HKEY\_LOCAL\_MACHINE\SYSTEM\ControlSet002\Services\Eventlog\Application\MySQL文件夹。 删除 HKEY\_LOCAL\_MACHINE\SYSTEM\CurrentControlSet\Services\Eventlog\Application\MySQL的文件夹。 注册表里没有这两个文件，就不用删除了。

然后找到mysql的安装位置，将mysql文件夹删掉：

[![image-20211203163147634](https://i.loli.net/2021/12/03/ucwgfV5kWP6jKCF.png)](https://i.loli.net/2021/12/03/ucwgfV5kWP6jKCF.png)

再到控制面板搜索 `mysql` ，确认已经没有了才可以，否则也进行卸载。

然后是另一个地方的：C:\Program Files (x86)\MySQL

[![这里写图片描述](https://i.loli.net/2021/12/03/S1LG5xvVmTyOiP2.png)](https://i.loli.net/2021/12/03/S1LG5xvVmTyOiP2.png)

还有一个隐藏文件下的，先点击【查看】->勾选【隐藏的项目】：

[![这里写图片描述](https://i.loli.net/2021/12/03/Xjd46eHkxouOcrP.png)](https://i.loli.net/2021/12/03/Xjd46eHkxouOcrP.png)

然后返回 `C:/`，可以看到一个名为ProgramData的文件夹，点开找到里面的MySQL，删除就行了

[![这里写图片描述](https://i.loli.net/2021/12/03/msqdCxkSoXTgQ7r.png)](https://i.loli.net/2021/12/03/msqdCxkSoXTgQ7r.png)

到此位置，恭喜你将mysql删干净了。

### 2、MySQL安装（Mac）

**Mac安装包**：链接：<https://pan.baidu.com/s/1InjXljIzRO945iXlN6Pn3w> 提取码：ndpb

### 3、MySQL安装（Windows）

**Windows64位安装包**：链接：<https://pan.baidu.com/s/1OmdD-QldkvPGl0kQokbxJg> 提取码：ndpb

下载完成后，直接解压并放到指定的目录中。例如：D:\server\mysql-5.7.23-winx64。

在mysql目录中新建 `my.ini`，并进行如下配置：

```
[client]
# 设置mysql客户端默认字符集
default-character-set=utf8

[mysqld]
# skip-grant-tables
#设置3306端口
port = 3306
# 设置mysql的安装目录
basedir=D:\\server\\mysql-5.7.23-winx64
# 设置mysql数据库的数据的存放目录
datadir=D:\\server\\mysql-5.7.23-winx64\\data
# 允许最大连接数
max_connections=200
# 服务端使用的字符集默认为8比特编码的latin1字符集
character-set-server=utf8
# 创建新表时将使用的默认存储引擎
default-storage-engine=INNODB
```

进入 `mysql目录` 下的 `bin目录`，命令行执行：

```shell
# 初始化数据库
$ .\mysqld --initialize --console
```

假如你得到：

```shell
...
2018-04-20T02:35:05.464644Z 5 [Note] [MY-010454] [Server] A temporary password is generated for root@localhost: sQjjZSffo6)C
...
```

代表初始化成功，并且 `sQjjZSffo6)C` 就是你的登录密码，可以在登陆后修改。你可以通过以下命令查询mysql版本：

```shell
$ .\mysqladmin --version

# 得到：mysqladmin  Ver 8.42 Distrib 5.7.23, for Win64 on x86_64
```

然后以管理员身份进入这个bin目录，并输入以下安装命令：

```shell
$ .\mysqld install
```

启动输入以下命令即可启动mysql：

```shell
$ net start mysql
```

[![image-20211203170450386](https://i.loli.net/2021/12/03/mM2qEfAcuDP95IB.png)](https://i.loli.net/2021/12/03/mM2qEfAcuDP95IB.png)

如果你出现了上图的错误，可以执行：

```shell
$ .\mysqld --remove
Service successfully removed.

$ .\mysqld --install
Service successfully installed.

$ net start mysql
MySQL 服务正在启动 .
MySQL 服务已经启动成功。
```

这样就算是mysql安装并启动成功了。

### 4、修改MySQL密码

现在修改mysql密码，重新打开 `my.ini`，把这一行的注释解掉：

```
skip-grant-tables
```

然后重启mysql：

```shell
$ net stop mysql
$ net start mysql
```

此时的mysql密码已经被取消，可以随意输入密码进入：

```shell
$ .\mysql -uroot -p
```

然后修改密码：

```shell
$ mysql> use mysql;
$ mysql> update user set authentication_string=password("123456") where user="root";
$ mysql> flush privileges;  # 刷新权限
$ mysql> quit;				# 退出mysql
```

> 注意：密码字段名 5.7 版本的是 **authentication\_string**，之前的为 **password**。

然后重新回到 `my.ini` 把刚刚那行注释掉：

```
# skip-grant-tables;
```

重启mysql：

```shell
$ net stop mysql
$ net start mysql
```

再一次尝试连接mysql：

```shell
$ .\mysql -uroot -p123456
```

出现以下界面，代表成功：

[![image-20211203171816525](https://i.loli.net/2021/12/03/cXK56281hPJLBOW.png)](https://i.loli.net/2021/12/03/cXK56281hPJLBOW.png)

由于版本等问题，建议在连接mysql成功后，再一次执行：

```shell
$ mysql> alter user user() identified by "123456";
```

## 十、Navicat安装【软件还需整理】

`Navicat for MySQL` 是一套全面的前端工具为数据库管理、开发和维护提供了一款直观而强大的图形界面，能同时连接 MySQL 和 MariaDB 数据库。

有关 `Navicat for MySQL` 软件的下载和安装，大家可以百度搜索，当然，我这里也给大家找到一篇文章：

<https://www.cnblogs.com/yanghongtao/p/10976526.html>

### 1、Windows安装

**Windows64位版本**：链接：<https://pan.baidu.com/s/1fly3pVOE_6oyauOEbLj1Vw> 提取码：ndpb

下载解压后直接双击两个.exe程序进行安装与绿化：

[![image-20211203172743984](https://i.loli.net/2021/12/03/9PdBFVAQzWK8lyE.png)](https://i.loli.net/2021/12/03/9PdBFVAQzWK8lyE.png)

### 2、Mac安装

**M1 Mac版本**：链接：<https://pan.baidu.com/s/14xIcYUKIbSBAynNS7eD15w> 提取码：ndpb

打开直接拽入应用程序即可。

## 十一、数据库基本操作语句

### 1、数据库的连接与断开

如果希望在系统全局均可连接mysql，可以在系统环境变量配置mysql的bin目录。

```shell
# 连接数据库
$ mysql -uroot -p123456

# 断开数据库
$ mysql> quit;
# 或者
$ mysql> exit;
```

### 2、数据库基本操作

数据库的创建、删除、显示与选择。

```shell
# 创建数据库
$ mysql> create database test;

# 删除数据库（不要轻易使用）
$ mysql> drop database test;

# 显示数据库
$ mysql> show databases;

+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test               |
+--------------------+
8 rows in set (0.04 sec)

# 选择数据库
$ mysql> use test;
```

### 3、表操作

每个数据库都是由若干张表构成，每张表都是用来存储字段与数据的。以下是常用的表操作：

#### a. 创建表：

```
# 创建一张用户表（包含id、用户名与密码）
/*
	INT 			整数型
	PRIMARY KEY   	主键约束
	AUTO_INCREMENT	自增约束
	VARCHAR			长字符串
	COMMENT			字段注释
*/
create table user (
	id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(30) COMMENT '用户名称',
    password VARCHAR(30) COMMENT '用户密码'
);

Query OK, 0 rows affected (0.02 sec)
```

什么是自增约束？

> 如果字段id被定义为AUTO\_INCREMENT，在插入一行数据的时候，自增值的行为如下：
>
> 1.如果插入数据时id字段指定为0、null或未指定值，那么就把这个表当前的AUTO\_INCREMENT值填到自增字段
>
> 2.如果插入数据时id字段指定了具体的值，就直接使用语句里指定的值

#### b. 删除表（不要随便使用）：

```
drop table user;

Query OK, 0 rows affected (0.01 sec)
```

#### c. 查看表：

```
show tables;

+----------------+
| Tables_in_test |
+----------------+
| user           |
+----------------+
1 row in set (0.00 sec)
```

#### d. 描述表（查看该表的字段）：

```
describe user;

+----------+-------------+------+-----+---------+----------------+
| Field    | Type        | Null | Key | Default | Extra          |
+----------+-------------+------+-----+---------+----------------+
| id       | int(11)     | NO   | PRI | NULL    | auto_increment |
| username | varchar(30) | YES  |     | NULL    |                |
| password | varchar(30) | YES  |     | NULL    |                |
+----------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
```

### 4、Navicat使用

打开安装好的Navicat软件，连接后其实也可以看到刚刚的这些数据：

[![image-20211203175853171](https://i.loli.net/2021/12/03/QDZy79oHc4z1bTN.png)](https://i.loli.net/2021/12/03/QDZy79oHc4z1bTN.png)

### 5、表数据的增删改查

以上表操作中我们创建了一张user表，现在我们通过对这张表进行CRUD（增删改查）。

#### a. 增加表数据

```
$ mysql> INSERT INTO user VALUES (null, "张三", "123456");
$ mysql> INSERT INTO user VALUES (null, "李四", "234567");
```

这里要注意：id是自增的字段，填写0或null都会从1开始自增。

#### b. 查询表数据

如果想要查询user表中所有的数据：

```
$ mysql> SELECT * FROM user;

+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  1 | 张三     | 123456   |
|  2 | 李四     | 234567   |
+----+----------+----------+
2 rows in set (0.00 sec)
```

#### c. 更新表数据

假设现在想要修改李四的密码：

```
$ mysql> UPDATE user SET password="999999" WHERE username="李四";

# 更新后可以查询李四的密码
$ mysql> SELECT password FROM user WHERE username="李四";

+----------+
| password |
+----------+
| 999999   |
+----------+
1 row in set (0.00 sec)
```

#### d. 删除表数据

假设现在想要删除张三这条数据：

```
$ mysql> DELETE FROM user WHERE username="张三";

# 删除后查询表数据
$ mysql> SELECT * FROM user;

+----+----------+----------+
| id | username | password |
+----+----------+----------+
|  2 | 李四     | 999999   |
+----+----------+----------+
1 row in set (0.00 sec)
```

有关数据库基本操作语句的介绍就到这里，后续在项目中会有更多实战性应用。

## 十二、NodeJs中操作MySQL

### 1、创建连接池

NodeJs中想要连接MySQL，需要先在项目内安装mysql库：

```shell
npm i mysql
```

在操作下面的代码时，先创建一个数据库，本项目将创建一个名为cms的数据库。另外，在cms中添加一张user表，然后填充部分数据：

```
# 创建数据库cms
$ mysql> CREATE DATABASE cms;
# 选择数据库cms
$ mysql> USE DATABASE cms;
# 创建user表
$ mysql> CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(30) COMMENT "用户名称",
    password VARCHAR(30) COMMENT "用户密码"
);
# 新增user表数据
$ mysql> INSERT INTO user VALUES (null, "张三", "123456");
$ mysql> INSERT INTO user VALUES (null, "李四", "aaasss");
```

然后到utils.js中，引入mysql并创建连接池：

```js
// 引入mysql
const mysql = require("mysql");

// 创建连接池
const pool = mysql.createPool({
    host: "localhost",  // 连接的服务器(代码托管到线上后，需改为内网IP，而非外网)
    port: 3306, // mysql服务运行的端口
    database: "cms", // 选择某个数据库
    user: "root",   // 用户名
    password: "123456", // 用户密码
})

//对数据库进行增删改查操作的基础
const query = (sql,callback) => {
    pool.getConnection(function(err,connection){
        connection.query(sql, function (err,rows) {
            callback(err,rows)
            connection.release()
        })
    })
}

module.exports = {
    host, port, query
}
```

### 2、Node中使用连接池

假设用户访问/manage即可查询数据库cms中表user的数据。这样的话，需要找到manage/index.js：

```js
const Router = require("koa-router")
const router = new Router();
const utils = require("../../utils")

router.get('/', async ctx=>{
    let data = await new Promise((resolve, reject)=>{
        // 写一句sql语句
        var sql = `SELECT * FROM user`;
        utils.query(sql, (err, data)=>{
            if(err) reject(err);
            resolve(data);  // 返回拿到的数据
        })
    })
    ctx.body = data;    // 将查询结果返回到页面中
})

module.exports = router;
```

打开浏览器访问<http://localhost:9000>，即可得到返回的数据：

[![image-20211205104329066](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfwyi4xsj305v060dfu.jpg)](https://tva1.sinaimg.cn/large/008i3skNgy1gxdfwyi4xsj305v060dfu.jpg)

### 3、查询指定字段

使用逗号隔开字段，这种选择方式会返回一个数组

```
SELECT username,password 	FROM user
```

至此，我们已经可以实现在Koa中操作数据库。
