头图

第4章. 连接到数据库

在连接数据库之前,我们需要确保我们的PHP容器已安装所有必要的扩展。默认情况下,Docker Hub 上的 PHP 镜像都是非常轻量级的,因此它不包含您可能需要的许多PHP扩展或Linux包。您必须根据项目优先级在较小或者更灵活的镜像之间进行权衡,但是我们知道该项目需要用到MySQL,因此让我们在 PHP 镜像中安装所需的拓展吧

使用基础的 PHP 镜像通常是一个很好的起点,但是我也可以在 Github 中查找使用启用常见扩展的 PHP 镜像。查看此存储库了解更多信息

创建自定义Dockerfile

进入到我们的天气应用项目的根目录,创建一个的新文件命名为Dockerfile。Dockerfile没有扩展名,用于配置和设置 Docker 镜像。Docker Hub 上的所有镜像都有有相应的 Dockerfile 文件,通常您可以在 GitHub 上找到它们。PHP的dockerfile在这里,但它们可能会有点混乱,因为它们是相互关联的的。我们的 Dockerfile 会简单得多:

Dockerfile
FROM php:apache

RUN docker-php-source extract && docker-php-ext-install mysqli pdo pdo_mysql && docker-php-source delete

解疑小课堂

此 Dockerfile 有两行:

  • FROM php:apache - 这告诉 Docker 在开始构建时选择哪个镜像。你可以从一个Linux发行版开始 (比如Ubuntu 或者超轻 alpine ),或者您也可以自己构建一个相同的镜像。在这里,我们选择php:apache镜像作为我们的运行容器。
  • RUN docker-php-source extract... - 这是添加我们需要的 PHP 扩展。在我们的例子中,我们只想使用 mysqli函数 用于操作 Mysql 数据库。由于这是一个非常常见的扩展,当我们选用 PHP 基础镜像时,PHP镜像中有自动添加它的方法。
查看 官方Docker文档 有关扩展现有镜像的更多配置命令。

构建镜像

我们需要通过刚刚创建的 Dockerfile 去构建我们所需的镜像:

cd E:/workplace/docker-app/weather-app
docker build . -t sunmking/weather-app

当我们运行此命令构建 Docker 镜像时,将看到以下输出,具体如下:

[+] Building 0.3s (6/6) FINISHED
 => [internal] load build definition from Dockerfile                                                               0.1s
 => => transferring dockerfile: 31B                                                                                0.0s
 => [internal] load .dockerignore                                                                                  0.1s
 => => transferring context: 2B                                                                                    0.0s
 => [internal] load metadata for docker.io/library/php:apache                                                      0.0s
 => [1/2] FROM docker.io/library/php:apache                                                                        0.0s
 => CACHED [2/2] RUN docker-php-source extract && docker-php-ext-install mysqli && docker-php-source delete        0.0s
 => exporting to image                                                                                             0.1s
 => => exporting layers                                                                                            0.0s
 => => writing image sha256:d92e846df4b0210c29f84c8f5ec8affdb8a823b6de34a24ac460564f160e6207                       0.0s
 => => naming to docker.io/sunmking/weather-app

解疑小课堂

Docker build 通过刚刚创建的 Dockerfile 构建一个可用于运行容器的镜像。在该命令中,我们使了两个参数:

  • . - 点让Docker知道我们构建的 “content” -- 在这种情况下,它是当前目录。您也可以使用绝对路径,如/用户/用户名/天气-应用如果您知道 Dockerfile 的确切路径。Docker会在包含Dockerfile的目录的上下文中自动构建此镜像,因此请确保 Dockerfile 位于项目的根目录。
  • -t sunmking/weather-app - 的-t为您的镜像设置一个 “标签”。此标签可让您更轻松地从镜像中创建容器,或将镜像推送到镜像注册中心以与其他人共享。

我们可以在本地计算机上运行 docker images 来查看所有的 Docker 镜像。当我们运行此命令时,可以在终端中看到如下内容:

REPOSITORY                           TAG       IMAGE ID       CREATED         SIZE
sunmking/weather-app                 latest    d92e846df4b0   2 hours ago     477MB
docker101tutorial                    latest    870f3cf07e24   4 days ago      28.9MB
alpine/git                           latest    b80d2cac43e4   12 days ago     43.6MB
snyk/snyk-docker-desktop-extension   0.6.2     d070900ca2ee   6 weeks ago     71.5kB
composer                             latest    c8d389ce4877   9 months ago    193MB
php                                  apache    b4e8e213b0ec   10 months ago   477MB
php                                  latest    13b9b1961ba3   10 months ago   484MB
mysql                                5.7       c20987f18b13   10 months ago   448MB

运行MySQL容器

在介绍中,我提到Docker容器可以通过 Docker的网络功能 方式 "链接 "起来。为了让我们的 PHP 应用程序从数据库中获取数据,我们需要将其链接到可用的数据库容器。OK,让我们l来启动一个新的 MySQL 容器,以便我们可以将web应用程序链接到它:

docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=weather -e MYSQL_PASSWORD=wt2022@1019 -e MYSQL_RANDOM_ROOT_PASSWORD=true mysql:5.7

解疑小课堂

当我们运行上述 docker run 命令时,Docker 将拉取最新版本的 MySQL 5.7镜像 并启动名为weather-db的容器。跟我们运行 PHP 容器一样,MySQL 在 Docker Hub上有很多版本的 Docker 镜像 ,您可以以几乎相同的方式使用它们。上面的命令包含了一些新的参数,让我来给你们介绍一下:

  • -d - 这将在 “Detached” 模式下运行容器,这意味着您不用保持终端与容器连接,你可以直接关闭你的终端窗口,你的容器依旧在后台安稳的运行着。
  • --name weather-db -命名我们的数据库容器很重要,因为你可以更加方便的通过名称去连接你创建的容器。我们也可以使用这个名称在我们的 PHP 应用程序中连接到 MySQL 数据库,我们将会在后面遇到。
  • -e MYSQL_USER=admin - MySQL镜像 包括几个 -e (环境变量) 选项。第一个设置了我们将在PHP应用程序中使用的 MySQL 用户的用户名。有关环境选择的更多信息,请访问Docker Hub官方镜像
  • -e MYSQL_DATABASE=weather -默认情况下,容器不会创建数据库。虽然您可以通过登录容器来手动创建一个 (这将在下一节中介绍),但是像这样预先创建数据库会更加简便。
  • -e MYSQL_PASSWORD=wt2022@1019 -这将设置数据库的初始密码。不管我们是不是在本地开发,都应该设置为强密码。
  • -e MYSQL_RANDOM_ROOT_PASSWORD=true - 为 root 用户设置一个随机的密码,不管在什么情况下都不要登录 root 账户。
  • mysql:5.7 - 与 PHP 镜像一样,您可以选择要使用的MySQL版本。在这里,我选择使用版本5.7。

就像php:apache镜像,默认的镜像就可以了,所以我们不需要在镜像中添加任何内容。

登录到正在运行的容器

此时,我们想检查新容器中的数据库是否正常工作,但是如何访问正在运行的 Docker 容器呢?下面我们将使用docker exec命令进入新容器的终端,然后我们将使用 MySQL命令行界面 创建我们所需要的数据库或者数据表。

我们可以使用以下命令进入正在运行的数据库容器的 bash 终端:

docker exec -it weather-db bash

你应该看到一条像root@3cad1127aa0d:/#(容器的ID) 在终端上,表示您现在已登录到正在运行的容器。从容器内 (不是本地主机上) 运行:

$ mysql --user=admin --password
Enter password:

输入您为上面创建的MySQL容器选择的密码 (在本例中为p23l % v11p),然后点击 “返回” 键。您将看到一些关于MySQL的信息,如下所示:

PS E:\workplace\docker-app\weather-app> docker exec -it weather-db bash
root@4c9a8b9ebce0:/# mysql -u admin -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.36 MySQL Community Server (GPL)

Copyright (c) 2000, 2021, Oracle and/or its affiliates.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql>

最后,让我们查看一下我们的数据库,是用show databases;命令:

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| weather            |
+--------------------+
2 rows in set (0.01 sec)

解疑小课堂

在本章的的前面部分中,我们登录到正在运行的数据库容器上,然后通过命令行登录到 MySQL,并且查看了有哪些数据库。由于大家可能不熟悉这些命令,我将逐一介绍它们。

Docker Exec
  • docker exec - 这是用于执行 Docker 命令是我们可以进入在正在运行的容器上。docker exec 还有有许多选项,这里我们就不一一介绍了,大家可以去官方文档中了解。
  • -it - -it这两个标志通常是在一起使用的。i表示交互式的,表示[cmd]是一个有用户输入的程序,比如/bin/bash 和 python 等等。-t 产生一个终端。所以说有-i就必须有-t,不然怎么输入呢。这[cmd] 有的镜像是有默认值的,比如centos的镜像的默认值/bin/bash,而python镜像的默认值是python。所以说[cmd]是可以不写的。
  • weather-db -这个是我们要在其上运行命令的正在运行的容器的名称 (您也可以使用容器 ID)。
  • bash - 最后,这是我们要在容器中运行的命令。bash) 是大多数Linux发行版中安装的shell,允许我们在容器上运行其他程序。如果您的容器没有安装bash,您也可以尝试 sh
MySQL CLI

MySQL CLI 还提供了许多选项,以下是我们使用的三个选项:

  • mysql - 这是用于从终端访问MySQL的命令。如果您一直在VM或本地计算机上使用MySQL,则没有什么不同。
  • --user=admin - 我们在此处指定用户名,以便 MySQL 知道我们希望以特定用户身份访问,而不是 root.
  • --password -密码标志指示MySQL给我们密码提示。您也可以直接在命令中输入密码,但它通常不太安全。
退出MySQL和容器

我们现在已经完成了MySQL容器,所以让我们退出并停止该容器:

  • 要退出MySQL CLI,请键入\ q然后按 “Enter”。
  • 通过键入退出容器exit然后按 “Enter”。
  • 通过键入docker stop weather-db来停止容器, 然后再次 “Enter”。

完成后,整个命令行输出应如下所示:


mysql> \q
Bye
root@4c9a8b9ebce0:/# exit
exit
PS E:\workplace\docker-app\weather-app> docker stop weather-db
weather-db

我们现在已经解决了如何运行 MySQL 容器,并且我们可以登录访问数据库,但是我们将如何保存在容器中的数据?当我们停止这个容器时会发生什么?数据的持久性在现实世界的应用中是很重要的,所以在下一节中,我们将深入研究如何在我们的容器持久化保存我们的数据。

保存们数据库容器中的数据

到目前为止,我们已经启动了数据库容器并自动创建了一个数据库和用户。这是很不错的开始,但是当我们把这个数据库连接到我们的 PHP 应用程序时,我们要确保数据库的表和值即使在我们的容器停止后也会被保存。否则,每次我们的数据库容器重新启动时,我们将不得不重建数据库和添加数据。

从容器中保存数据用于本地开发的最好方法是挂载一个数据卷。这对我们的 PHP 代码的本地开发是非常有效的,而我们挂载数据库数据卷的过程也非常相似。在我们启动 PHP 容器是也挂载了一个(在我们的例子中,我们之前创建的weather-app 目录),但你可以从本机系统的任何地方挂载卷,我这里把数据挂载在项目的同级目录 .data 中。

现在我们开始学 如何保存 mysql 容器里的数据, 现在进入到 weather-app 项目下执行以下命令:

docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=weather -e MYSQL_PASSWORD=wt2022@1019 -e MYSQL_RANDOM_ROOT_PASSWORD=true -v E:/workplace/docker-app/.data:/var/lib/mysql mysql:5.7

解疑小课堂

我们在这个docker运行命令中添加的唯一内容是 -v E:/workplace/docker-app/.data:/var/lib/mysql 。该命令是把一个卷从我们的本地目录挂载到 MySQL 容器中。当我们运行上面的命令时在(.data/)目录中会出现很多文件和目录。这些是MySQL用来存储数据的文件,你可以从你的终端查看这些文件。

$ ls -a .data/ # Linux OR MAc
or
$ cd E:/workplace/docker-app/.data/ # win
$ dir # win

显示输出如下:

 目录: E:\workplace\docker-app\.data


Mode                 LastWriteTime         Length Name
----                 -------------         ------ ----
d-----        2022/10/19     11:09                mysql
d-----        2022/10/19     11:09                performance_schema
d-----        2022/10/19     11:09                sys
d-----        2022/10/19     11:10                weather
-a----        2022/10/19     11:09             56 auto.cnf
-a----        2022/10/19     11:09           1676 ca-key.pem
-a----        2022/10/19     11:09           1112 ca.pem
-a----        2022/10/19     11:09           1112 client-cert.pem
-a----        2022/10/19     11:09           1680 client-key.pem
-a----        2022/10/19     14:36       79691776 ibdata1
-a----        2022/10/19     14:36       12582912 ibtmp1
-a----        2022/10/19     11:14            694 ib_buffer_pool
-a----        2022/10/19     14:36       50331648 ib_logfile0
-a----        2022/10/19     11:09       50331648 ib_logfile1
-a----        2022/10/19     11:09           1680 private_key.pem
-a----        2022/10/19     11:09            452 public_key.pem
-a----        2022/10/19     11:09           1112 server-cert.pem
-a----        2022/10/19     11:09           1676 server-key.pem

不要担心这些都是什么意思(尽管如果你有兴趣,你可以阅读一下MySQL的文档)。在这,我们只关心在 MySQL 容器中使用来自我们主机的数据,允许我们在容器关闭后仍能保持数据库数据。

创建数据表

由于这个应用程序需要缓存来自 高德天气 API 的结果,所以我们要创建一个 location 表,并添加列id、weather和last_updated。

diagram3.png

如果MySQL容器没有运行,那么使用上面的方法启动它,挂载数据卷:

docker run -d --rm --name weather-db -e MYSQL_USER=admin -e MYSQL_DATABASE=weather -e MYSQL_PASSWORD=wt2022@1019 -e MYSQL_RANDOM_ROOT_PASSWORD=true -v E:/workplace/docker-app/.data:/var/lib/mysql mysql:5.7

登录到 Mysql 容器并进入到 MySQL CLI 中。这一次,我们将一步到位创建数据表:

docker exec -it weather-db mysql --user=admin --password='wt2022@1019' weather

接下来,运行 Mysql SQL 命令来创建我们上述的数据库表:

mysql> CREATE TABLE locations (id VARCHAR(64) NOT NULL, weather JSON NULL, last_updated TIMESTAMP DEFAULT CURRENT_TIMESTAMP);

应该会得到反馈 Query OK, 你也可以通过运行 SHOW TABLES;命令来查看创建的表. 然后输入 \q退出 mysql 终端.

解疑小课堂

与上次登录 MySQL 命令行不同的是,这次我们把这个过程缩减到只有一个步骤。记住,当运行 docker run或 docker exec 时,后面的参数部分是我们想在容器中运行的命令。这意味着我们不需要先登录到bash会话,再登录到MySQL,我们可以直接登录到 MySQL CLI。

将 PHP 应用程序中的数据保存到数据库

现在,我们的数据库已经准备好了,即使在容器被移除后,它也会保存这些数据,我们需要更新我们的PHP应用程序,以连接到数据库并存储天气结果。

最初,我们是直接从 高德天气 API获取数据,但现在我们已经有了一个存储机制(即:我们的数据库),让我们让应用程序将数据保存到数据库,以便减少 API 请求。

首先进入到项目的根目录 运行 cp .example.env .env 并编辑 .env 文件,内容如下:

APP_DEBUG = true

[APP]
DEFAULT_TIMEZONE = Asia/Shanghai

[DATABASE]
TYPE = mysql
HOSTNAME = weather-db
DATABASE = weather
USERNAME = admin
PASSWORD = 'wt2022@1019'
HOSTPORT = 3306
CHARSET = utf8
DEBUG = true

[LANG]
default_lang = zh-cn

然后,我们开始修改我们的业务逻辑,打开项目根目录下的 app/controller/Weather.php 文件,更新如下:

<?php

namespace app\controller;

use app\BaseController;
use Clydecn\Amap\Weather as AmapWeather;
use think\facade\Db;

class Weather extends BaseController
{
    public $key;
    public $weather;
    // 初始化
    protected function initialize()
    {
        $this->key = "xxxxxxxxxxxxxxxx"; // 高德 KEY
        $this->weather = new AmapWeather($this->key);
    }

    public function get()
    {
        $rid = $this->request->param('location_id', '310000');
        // 通过 Mysql 查询天气信息
        $weather = Db::table('locations')->where(['id' => $rid])->find();
        // 如果查询到了
        if($weather){
            //直接返回
            return json(json_decode($weather['weather'],true),200);
        }
        // 通过 API 查询当天天气
        $res = $this->weather->getLiveWeather($rid);
        //将查询的结果插入数据库
        Db::table('locations')->insert([
            'id'=>$rid,
            'weather'=>json_encode($res),
            'last_updated'=>date('Y-m-d H:i:s'),
        ]);

        return json($res, 200);
    }

    public function delete()
    {
        // todo
        echo "location_id is " . $this->request->param('location_id');
    }
}

解疑小课堂

在这里我们做了两步操作:

  1. 添加数据库配置
ThinkPHP 数据库配置,可以在 .env 里面配置,若果有不明白的可以查看 ThinkPHP6.0完全开发手册
  1. 添加业务逻辑

    1. 查询数据是否存在,存在就响应已经存在的数据
    2. 如果数据不存在,就直接请求接口并存储在数据库中,以待下次查询

虽然代码中的注释可能会有帮助,但如果你不熟悉 ThinkPHP6,还是有一些细节值得关注的:

  • $rid=$this->request->param('location_id', '310000'); - ThinkPHP6 中获取参数的方法.
  • $weather=Db::table('locations')->where(['id' => $rid])->find(); - 数据库查询.
  • returnjson(json_decode($weather['weather'],true),200); - 将结果以 json 的格式展现给用户.

连接 PHP 容器

有了代码,现在我们需要运行 PHP 容器,链接到我们刚刚启动的MySQL数据库容器:

docker run -d --privileged=true --rm --name=weather-app -p 38000:80 -v E:/workplace/docker-app/weather-app/:/var/www/html --link weather-db sunmking/weather-app

解疑课堂

此docker run命令有两个新部分:

  • --link weather-db -链接名为weather-db的容器。你也可以给链接的容器一个别名在PHP容器中使用,但是我们不需要在这里这样做。
  • sunmking/weather-app -我们使用的是本章开头构建的新 Docker 镜像,而不是使用php:apache。这样做的原因是我们需要在自定义 Docker 镜像中添加的 PHP MySQLi PDO PDO_MYSQL扩展。

现在,PHP容器已启动并正在运行,您应该能够导航到位置ID,如下所示:

GET [http://localhost:38000/public/index.php/weather/310000](http://localhost:38000/public/index.php/weather/310000)

第一次加载此URL时,加载可能需要一两秒钟,但是如果刷新页面,可能会更快的看到结果。我的加载时间不到150毫秒! 这要归功于我们通过将结果保存在数据库中。

删除数据

为了从数据库中删除数据,我们将添加第二个路由。添加以后允许用户从数据库中删除添加的数据:

   /**
     * delete
     */
    public function delete()
    {
        $rid = $this->request->param('location_id', '310000');
        // 通过 Mysql 查询天气信息
        $weather = Db::table('locations')->where(['id' => $rid])->find();
        // 如果查询到了
        if($weather){
            Db::table('locations')->where(['id' => $rid])->delete();
            //直接返回
            return json("Location {$rid} deleted.",200);
        }else{
            return json("Location {$rid} Not Found.", 404);
        }
    }

现在您可以使用ApiPostpostman发送一个delete请求。例如:

DELETE http://localhost:38000/public/index.php/locations/310000

你应该看到一条信息Location 310000 已删除。现在当你做一个GET 再次请求该URL,您将等待更长的时间,因为结果来自 高德天气 API。

如果一切工作正常,你现在应该能够像以前一样访问该应用程序


felix_sun
49 声望0 粉丝