第三章。创建一个ThinkPHP应用程序 在本教程的其余部分,我们将基于ThinkPHP框架完成一个天气查询的应用。使用天气查询API文档开发指南Web服务API高德地图API的接口来实现我们的功能。把查询数据缓存到MySql中,这样就不用每次频繁的请求第三方的接口了(有请求次数限制) 选择高德开放平台天气查询API主要是因为它是免费的。当然你也可以使用其他的第三方天气查询接口,看个人喜好。 该应用是一个非常简单的RESTAPI应用,主要实现两个接口。 GETweather:locationid从数据库中获取天气(如果存在的话),如果不存在,则从第三方接口获取。DELETEweather:locationid从数据库中删除已缓存的天气信息。再次查询该地点的天气时,将从高德的天气接口获取。 在我们进行应用编码之前,首先使用Docker安装并运行ThinkPHP 安装ThinkPHPFramework ThinkPHP是一个免费开源的,快速、简单的面向对象的轻量级PHP开发框架,是为了敏捷WEB应用开发和简化企业应用开发而诞生的。ThinkPHP从诞生以来一直秉承简洁实用的设计原则,在保持出色的性能和至简代码的同时,更注重易用性。遵循Apache2开源许可协议发布,意味着你可以免费使用ThinkPHP,甚至允许把你基于ThinkPHP开发的应用开源或商业产品发布销售。 这就是为什么我选择它作为本教程的教学框架。我不想让你因为一个框架而放弃,但我也不想从头开始建立所有的东西,因为该教程的重点是Docker,而不是我们的PHP应用。 用Docker创建ThinkPHP应用实际上比用本地配置PHP环境所需的操作少。并且为我们还需要使用Composer,多亏了Docker,我们甚至不需要在主机上安装它。 首先打开你的终端,创建一个项目目录。 mkdirdockerapp 并进入到该目录中 cddockerapp 现在使用〔官方ComposerDocker镜像〕(https:hub。docker。comcomposer)安装ThinkPHP。 dockerrunrminteractivettyvE:workplacedockerapp:appcomposer:latestcreateprojecttopthinkthinkweatherapp 如果你查看weatherapp目录,你会看到ThinkPHP6的项目目录,如下所示: dirE:workplacedockerappweatherapp的目录2022021116:31DIR。2022021116:30DIR。。2021121621:06231。example。env2021121621:0634。gitignore2021121621:062,038。travis。yml2021121621:06DIRapp2021121621:061,094composer。json2022021116:3136,134composer。lock2021121621:06DIRconfig2021121621:06DIRextend2021121621:061,822LICENSE。txt2021121621:06DIRpublic2021121621:061,459README。md2021121621:06DIRroute2021121621:06DIRruntime2021121621:06180think2022021116:31DIRvendor2021121621:06DIRview 创建ThinkPHP应用的过程中的命令梳理 我们的docker运行命令与第二章中的命令相似,但我们使用了不同的镜像。我们没有使用运行hello。php脚本的PHP镜像,而是使用了一个Composer镜像。让我们来看看有什么变化。 composer:latest这表明我们在这个容器中使用的镜像。如果需要,你可以指定Composer的特定版本;只要查看DockerHub上支持的镜像标签列表。createprojecttopthinkthinkweatherapp这是实际将ThinkPHP6安装到容器工作目录中的命令。因为该工作目录是我们主机目录的一个卷,所以composer安装的文件现在在主机和容器中都存在。在这一点上,你的应用程序实际上没有做任何事情,但ThinkPHP6已经被安装了,我们现在对composer在Docker中如何与PHP一起工作有了一些了解。 新建路由与Controller(逻辑处理)文件 项目创建完成后,我们需要添加几个路由URL和Controller文件。让我们打开weatherapp目录下的appcontroller,然后新建Weather。php文件,内容如下: lt;?phpnamespaceappcontroller;useappBaseController;classWeatherextendsBaseController{publicfunctionget(){todoecholocationidis。thisrequestparam(locationid);}publicfunctiondelete(){todoecholocationidis。thisrequestparam(locationid);}} 然后打开weatherapp目录下的approute,在app。php文件中追加如下内容: Route::get(weather:locationid,weatherget);Route::delete(weather:locationid,weatherdelete); 运行我们的应用程序 现在我们可以在Docker容器中运行我们的应用程序,只是为了验证我们的程序是否运行正常,因为我们只添加了两个路由URL。打开命令行,运行。dockerrunprivilegedtruermp38000:80vE:workplacedockerapp:varwwwhtmlphp:apache 现在,在浏览器中打开http:localhost:38000weatherapppublicindex。phpweather1,你应该看到一个空页面,上面有以下文字:locationidis1 那么恭喜你,你刚刚已经成功地在Docker中运行了你的第一个ThinkPHP应用程序。运行ThinkPHP应用的命令梳理 这次我们使用的dockerrun命令与我们用来运行hello。php脚本和composercreateproject。。。的两个命令不同。原因是这次我们想获得包含Apache的最新版本的PHP,这样我们就可以为我们的Web应用提供服务。让我们更详细地了解新增的命令部分。 p38000:80这里我们在容器和主机系统之间定义了端口映射。这个命令说Docker应该把容器上的80号端口映射到我们主机上的38000号端口。你可以在你的主机上选择任何有效的端口,但使用一个高的端口(10000以上)可能是一个好主意,因为许多低的端口被保留给大多数标准机器上的内置进程。对于容器,你必须使用80端口,因为那是Docker镜像暴露的端口,Apache在上面运行。privilegedtrue容器内是否使用root权限vE:workplacedockerapp:varwwwhtml这一次,我们将代码从主机挂载到容器的varwwwhtml目录。原因是,PHP的Apache镜像是从这个目录中提供代码的。这是我们之前运行的PHP脚本之间的一个微妙但关键的区别。请务必仔细阅读官方PHPDocker镜像文档以避免遗漏这样的事情。php:apache该镜像是官方的PHPApache容器。它包含PHP和Apache(一种流行的Web服务器)在同一个容器中,允许你快速提供代码用于本地开发。当然你也可以选择是使用php:fpm镜像链接一个nginx的镜像,这些我不会在本书中介绍,需要你自己去了解。最后,你会注意到,这次在镜像名称后面没有命令。这可能会让新的Docker用户感到困惑,但在默认情况下,即使你没有指定命令,大多数镜像也会运行一些命令。在PHPApache镜像的例子中,默认的命令是运行一个shell脚本来启动Apache。这个脚本正是我们想要的,所以没有理由去改变它。 在这一点上,你的终端将显示任何进来的Apache请求,所以当你加载第一个URL时,你可能看到类似的东西。 172。17。0。1〔11Feb2022:09:14:020000〕GETweatherapppublicindex。phpweather1HTTP1。1200245Mozilla5。0(WindowsNT10。0;Win64;x64)AppleWebKit537。36(KHTML,likeGecko)Chrome98。0。4758。80Safari537。36Edg98。0。1108。43 你可以通过向终端发送一个中断信号来停止和退出终端。在windows上,这可以通过按Ctrl和按c来实现。 更多Docker运行选项 在Detached模式下运行 运行你的新网络应用程序的另一个选择是在Detached模式中运行容器。这意味着你在终端将不会看到来自你的容器的输出。这可以通过在我们之前的命令中添加d标志来实现。 dockerrunprivilegedtruedrmp38000:80vE:workplacedockerapp:varwwwhtmlphp:apache 停止一个容器 在分离模式下启动容器后,你的终端将显示新容器的完整ID类似于a70d25c2a7cedae673f8ab。。。如果你想停止这个容器,你可以使用dockerstop命令,用容器的ID告诉Docker。比如说 dockerstopa70d25c2a7cedae673f8ab 因为输入整个ID是很麻烦的,如果你愿意,Docker允许你只输入前三个或更多的字符。 dockerstopa70 命名容器 最后,我建议为你的容器命名。我们在本书后面的许多例子中都会这样做,因为用名字来记住一个容器比用随机分配的ID要容易得多,再加上ID是随机的,所以每次你运行一个新版本的容器时,它都会得到一个新的ID。只要不是已经有一个同名的容器,名字就可以多次发出来。为了给我们的新应用容器命名,我们可以用传入的name标志重新创建它。 dockerrunprivilegedtruedrmnameweatherappp38000:80vE:workplacedockerapp:varwwwhtmlphp:apache 在使用dockerrun命令时,还有许多可用的选项,所以你可能想更详细地阅读文档。在我们开发其余的应用程序时,我们会涉及其中的一些选项。添加高德天气SDK 现在我们要引入高德的天气SDK,在使用该SDK之前你需要阅读高德开放平台天气查询的技术文档,再添加SDK之前我们首先要确保所有现有的容器都停止了。 dockerps之后在运行上面的命令停止容器dockerstop容器ID 这个命令将列出所有正在运行的容器。你也可以通过添加a标志来查看停止的容器。 如果有任何容器正在运行,那么在我们继续前进之前,使用dockerstop来停止它们。添天气SDK 执行命令添加高德天气SDK,欢迎点赞星。这是作者之前封装的SDK,当然你也可以自己重新封装或者用其他的天气SDK,现在在你的终端运行: dockerrunrminteractivettyvE:workplacedockerappweatherapp:appcomposer:latestrequireclydecnphpamapweather 该命令将在你的项目中装新的软件包。在这个过程中,你应该在终端看到一些类似这样的输出。 Usingversion1。0forclydecnphpamapweather。composer。jsonhasbeenupdatedRunningcomposerupdateclydecnphpamapweatherLoadingcomposerrepositorieswithpackageinformationUpdatingdependenciesLockfileoperations:7installs,0updates,0removalsLockingclydecnphpamapweather(v1。0。0)Lockingguzzlehttpguzzle(6。5。5)Lockingguzzlehttppromises(1。5。1)Lockingguzzlehttppsr7(1。8。3)Lockingralouphiegetallheaders(3。0。3)Lockingsymfonypolyfillintlidn(v1。24。0)Lockingsymfonypolyfillintlnormalizer(v1。24。0)WritinglockfileInstallingdependenciesfromlockfile(includingrequiredev)Packageoperations:7installs,0updates,0removalsDownloadingsymfonypolyfillintlnormalizer(v1。24。0)Downloadingsymfonypolyfillintlidn(v1。24。0)Downloadingralouphiegetallheaders(3。0。3)Downloadingguzzlehttppsr7(1。8。3)Downloadingguzzlehttppromises(1。5。1)Downloadingguzzlehttpguzzle(6。5。5)Downloadingclydecnphpamapweather(v1。0。0)07〔〕0Downloadingsymfonypolyfillintlidn(v1。24。0)Downloadingralouphiegetallheaders(3。0。3)Downloadingguzzlehttppsr7(1。8。3)Downloadingclydecnphpamapweather(v1。0。0)Downloadingsymfonypolyfillintlnormalizer(v1。24。0)Downloadingguzzlehttppromises(1。5。1)Downloadingguzzlehttpguzzle(6。5。5)Installingsymfonypolyfillintlnormalizer(v1。24。0):ExtractingarchiveInstallingsymfonypolyfillintlidn(v1。24。0):ExtractingarchiveInstallingralouphiegetallheaders(3。0。3):ExtractingarchiveInstallingguzzlehttppsr7(1。8。3):ExtractingarchiveInstallingguzzlehttppromises(1。5。1):ExtractingarchiveInstallingguzzlehttpguzzle(6。5。5):ExtractingarchiveInstallingclydecnphpamapweather(v1。0。0):Extractingarchive3packagesuggestionswereaddedbynewdependencies,usecomposersuggesttoseedetails。Generatingautoloadfilesphpthinkservice:discoverSucceed!phpthinkvendor:publishFileappconfigtrace。phpexist!Succeed!10packagesyouareusingarelookingforfunding。Usethecomposerfundcommandtofindoutmore! 现在SDK已经安装完毕,可以使用了。调用SDKAPI 我们将使用刚刚添加的高德天气SDK来完善我们的业务逻辑,打开controller目录下的Weather。php添加以下内容: lt;?phpnamespaceappcontroller;useappBaseController;useClydecnAmapWeatherasAmapWeather;classWeatherextendsBaseController{publickey;publicweather;初始化protectedfunctioninitialize(){thiskeyKEY;高德KEYthisweathernewAmapWeather(thiskey);}publicfunctionget(){ridthisrequestparam(locationid,310000);查询当天天气resthisweathergetLiveWeather(rid);returnjson(res,200);}publicfunctiondelete(){todoecholocationidis。thisrequestparam(locationid);}} 解疑 我们做了一些更新主要是对引入天气API初始化天气类 thisweathernewAmapWeather(thiskey);实例化天气API类resthisweathergetLiveWeather(310000);获取天气信息。returnjson(res,200);最后,返回Json数据测试 我们的应用程序已经初步完成了向API传递一个真实的位置ID并返回一些数据。首先,使用这个高德位置查询找到一个位置ID。我使用的是上海的ID进行测试。310000,当然你直接传上海也是可以的。ok,让我们再次运行Docker容器。 dockerrundprivilegedtruermnameweatherappp38000:80vE:workplacedockerapp:varwwwhtmlphp:apache 并在你的浏览器中访问正在运行的应用程序,地址是http:localhost:38000weatherapppublicindex。phpweather310000。你应该可以看到一个JSON数据,看起来像这样。 {status:1,count:1,info:OK,infocode:10000,lives:〔{province:上海,city:上海市,adcode:310000,weather:晴,temperature:11,winddirection:东,windpower:3,humidity:38,reporttime:2022021414:01:45}〕} 你的Docker化的PHP应用程序现在正从外部数据源返回真实数据,并在Apache中提供服务,但你可能会注意到,它的速度并不快(我的页面加载时间为1。92秒!)。 高德天气API是一个免费的服务,其他国家可能无法访问。为了解决这个问题,我们将把查询的数据保存在我们自己的MySQL数据库中,可以再下次访问的时候可以快速地响应。这将极大地提高性能,下个章节我们将学习如何用Docker将MySql与PHP应用程序相结合。