PHP 與 MySQL 配合的小案例
進行WEB后端開發,難免用到PHP
與MySQL
這對好基友。這幾天一直在煩惱做一個什么樣的示例來簡單有效地說明這哥倆的配合方式,既然要用到MySQL
,那么一個簡單的數據庫表是要有的。簡單的數據庫表很好設計,但數據從哪里來呢?一開始筆者想過不然寫個表單,手動輸入插入數據庫?
數據數據...然后筆者就想到了干脆用數據接口獲取數據存表,省去了自己手動輸入數據的繁瑣(筆者...筆者畢業設計就是這么干的)。說做就做,懶得找其他數據網站了,還是用之前做天氣的那個網站:極速數據,翻了翻它的一些接口,看到 謎語 這個接口,感覺還是很有趣的嘛,可以做一個小型的猜謎語程序。很好,為自己的聰明機智點贊!
第一步:創建數據庫
不對,第一步應該是搭建自己的本地環境:Apache + PHP + MySQL
或者Nginx + PHP + MySQL
。這里就不再贅述,網上也有各種教程。筆者自己的WEB環境為:
- Nginx 1.17.0
- PHP 7.3.6
- MySQL 8.0。16
配置好服務器的網站根目錄和端口,確認服務器可以正常使用PHP;配置好MySQL的各項參數。
根據 謎語 這個接口的返回數據,這里筆者創建了一個名為test
的數據庫,並建立了一個名為riddle
的數據庫表,建表語句如下:
CREATE TABLE `riddle` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '主鍵:自增ID',
`content` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '謎語',
`anwser` varchar(255) COLLATE utf8_bin NOT NULL COMMENT '謎底',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='謎語大全';
由於只是一個小案例,這里不過多追究數據庫的設計問題。現在,我們開始使用PHP連接數據庫!
注:本次服務器的地址為:localhost
第二步:連接數據庫
為了方便配置,筆者將數據庫連接的相關配置寫在了/app/cfg/
目錄下,並將其命名為db_config.php
,內容如下:
<?php
// 文件:/app/cfg/db_config.php
define('type', 'mysql');// 數據庫類型:MySQL
define('host', 'localhost');// 服務器地址:本地為 localhost 或 127.0.0.1
define('port', 3306);// 數據庫端口:一般默認為 3306
define('dbname', 'test');// 數據庫名稱:這里使用的是 test 數據庫
define('username', 'root');// 用戶名:具有相關權限的用戶,這里為省事,使用了 root 用戶
define('userpwd', 'mysql');// 數據庫密碼:這是在安裝MySQL時就定義的,root 用戶對應的密碼
define('charset', 'utf8');// 客戶端字符集:始終推薦使用 utf8 而不是 gbk
當連接數據庫時,只需引入該文件並使用已定義的常量進行數據庫連接。一個簡單的測試數據庫配置文件是否能正確連接數據庫test
:
<?php
// 文件:/test/connect_test.php
require_once '../app/cfg/db_config.php';
try {
$conn = new PDO(
type.':host='.host.';port='.port.';dbname='.dbname,
username,
userpwd,
[PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);
$conn->query('SET NAMES '.charset);
echo '數據庫連接成功!';
} catch (PDOException $e) {
echo self::ERROR_UNABLE;
error_log($e->getMessage());
return FALSE;
}
此時在瀏覽器中訪問 http://localhost/test/connect_test.php ,查看結果。如果配置無誤將輸出數據庫連接成功!
。
多次操作數據庫時,每次都連接一下容易造成資源浪費,也造成代碼冗余。這里我們將數據庫連接封裝為一個單例模式(該類對象僅允許創建一次)的類,在/app/db/
目錄下創建文件Connection.php
,代碼如下:
<?php
namespace db;
use PDO;
use PDOException;
class Connection
{
const ERROR_UNABLE = '<br>數據庫連接失敗!<br>';
private static $_instance = NULL;
private $conn;
const DB_CONFIG = 'root_test.php';// 數據庫配置文件
private function __construct($config)
{
// $config = require(realpath('../').'/app/cfg/'.$config);
$config = require(dirname(__DIR__).'/cfg/'.$config);
$opt = [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
];
try {
$this->conn = new PDO(
type.':host='.host.';port='.port.';dbname='.dbname,
username,
userpwd,
$opt
);
$this->conn->query('SET NAMES '.charset);
// echo '數據庫連接成功!';
} catch (PDOException $e) {
echo self::ERROR_UNABLE;
error_log($e->getMessage());
return FALSE;
}
return $this->conn;
}
private function __clone()
{
return $this;
}
static public function getInstance($config = self::DB_CONFIG)
{
if(!self::$_instance)
{
self::$_instance = new self($config);
}
return self::$_instance->conn;// 應該返回數據庫連接,而不是連接實例,否則在外部無法使用PDO類方法
}
}
單例模式的特點為:構造方法(__construct()
)和對象克隆方法(__clone()
)被定義為private
,禁止外部訪問,同時公開一個靜態方法(getInstance()
)來獲取類實例。
在這個類中,筆者也將數據庫連接配置文件設置為默認參數,如果想使用別的配置文件,也可以將其存放在/app/cfg/
目錄下,並向Connection::getInstance()
方法中傳入該配置文件名。
現在,我們來嘗試使用Connection
類來連接數據庫吧!
<?php
// 文件:/test/connect.php
require_once '../app/db/Connection.php';
use db\Connection;
$conn = Connection::getInstance();
if($conn) echo '數據庫連接成功!';
第三步:使用謎語接口獲取數據
連接數據庫后,我們需要先獲取謎語的數據來填充數據庫。訪問 https://www.jisuapi/com/ ,注冊會員(如果已注冊可直接跳過),找到謎語接口(在【娛樂購物】分類),仔細閱讀其API接口的說明,了解其請求的參數,查看其示例代碼。
可以看到PHP的示例代碼中引入了一個curl.func.php
文件,我們可以從旁邊的查看代碼按鈕中看到該文件的下載,如圖:
筆者將該文件的代碼復制到/app/lib/
目錄下的curl.func.php
文件中,不想下載的朋友可以直接在這里復制:
<?php
function curlOpen($url, $config = array())
{
$arr = array('post' => false,'referer' => $url,'cookie' => '', 'useragent' => 'Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 3.0.04506; customie8)', 'timeout' => 20, 'return' => true, 'proxy' => '', 'userpwd' => '', 'nobody' => false,'header'=>array(),'gzip'=>true,'ssl'=>false,'isupfile'=>false);
$arr = array_merge($arr, $config);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, $arr['return']);
curl_setopt($ch, CURLOPT_NOBODY, $arr['nobody']);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_USERAGENT, $arr['useragent']);
curl_setopt($ch, CURLOPT_REFERER, $arr['referer']);
curl_setopt($ch, CURLOPT_TIMEOUT, $arr['timeout']);
if($arr['gzip']) curl_setopt($ch, CURLOPT_ENCODING, 'gzip,deflate');
if($arr['ssl'])
{
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
}
if(!empty($arr['cookie']))
{
curl_setopt($ch, CURLOPT_COOKIEJAR, $arr['cookie']);
curl_setopt($ch, CURLOPT_COOKIEFILE, $arr['cookie']);
}
if(!empty($arr['proxy']))
{
curl_setopt ($ch, CURLOPT_PROXY, $arr['proxy']);
if(!empty($arr['userpwd']))
{
curl_setopt($ch,CURLOPT_PROXYUSERPWD,$arr['userpwd']);
}
}
//ip比較特殊,用鍵值表示
if(!empty($arr['header']['ip']))
{
array_push($arr['header'],'X-FORWARDED-FOR:'.$arr['header']['ip'],'CLIENT-IP:'.$arr['header']['ip']);
unset($arr['header']['ip']);
}
$arr['header'] = array_filter($arr['header']);
if(!empty($arr['header']))
{
curl_setopt($ch, CURLOPT_HTTPHEADER, $arr['header']);
}
if ($arr['post'] != false)
{
curl_setopt($ch, CURLOPT_POST, true);
if(is_array($arr['post']) && $arr['isupfile'] === false)
{
$post = http_build_query($arr['post']);
}
else
{
$post = $arr['post'];
}
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
}
$result = curl_exec($ch);
curl_close($ch);
return $result;
}
這個文件很詳盡地寫明了curl
的用法,可以好好看看。再查看這個接口還需要什么數據,噢對了,APPKEY
。我們可以直接在登錄之后的“會員中心”中“申請API”,申請后可免費試用100次,對於一個小項目100次足夠了。每個接口的使用都需要APPKEY
,這個值可以在“會員中心”的“基本資料”中看到,如圖:
這里筆者的APPKEY做了模糊處理,請不要輕易暴露自己的APPKEY,以免數據被盜刷。
准備工作已完成,接下來是獲取數據了。
在/app/api/
目錄下,新建文件riddle.php
,代碼如下(其實只是改了一下接口的示例):
<?php
require_once '../lib/curl.func.php';
require_once '../db/Connection.php';
use db\Connection;
$appkey = '51c0adda6dsdsb9d4f3';
$keyword = '酒';
$pagesize = 1;// 每頁最大數據為2,這里干脆設為 1
$url = "https://api.jisuapi.com/miyu/search?appkey=$appkey&keyword=$keyword&pagesize=$pagesize&pagenum=&classid=";
$result = curlOpen($url, ['ssl'=>true]);
$jsonarr = json_decode($result, true);
if($jsonarr['status'] != 0) {
echo '該關鍵字下沒有相關謎語!';
}
$arr = $jsonarr['result']['list'];
// // 避免出錯時浪費API次數,可先使用測試數據檢查行插入是否成功
// $arr = [
// 'content'=>1,
// 'anwser'=>2
// ];
// 連接數據庫
$conn = Connection::getInstance();
$sql = 'INSERT INTO `riddle`(`content`,`anwser`) VALUES (:content, :anwser)';
$sth = $conn->prepare($sql);// 使用預處理語句,防止SQL注入(雖然這里沒有多大必要
$sth->bindParam(':content', $arr['content']);
$sth->bindParam(':anwser', $arr['anwser']);
$res = $sth->execute();// 執行預處理語句,成功則返回TRUE
if($res) {
echo '插入數據成功!';
}
在瀏覽器中訪問[http://localhost/app/api/riddle.php],看到插入數據成功!
並在數據庫中看到該記錄,則說明數據獲取成功!
如何一次插入多條記錄?
上面的代碼只能靠關鍵字獲取一條數據,這就有點慢了。再去看該接口說明,可以發現這個接口有11個謎語分類,那么我們可以靠循環傳遞參數來獲取多條記錄,修改后的代碼如下:
<?php
require_once '../lib/curl.func.php';
require_once '../db/Connection.php';
use db\Connection;
$appkey = '51c0adda6dsdsb9d4f3';
$keyword = '酒';
$pagesize = 1; // 每頁最大數據為2,這里設為 1
$arr = [];
for ($classid = 1; $classid < 12; $classid++) {
$url = "https://api.jisuapi.com/miyu/search?appkey=$appkey&keyword=$keyword&pagesize=$pagesize&pagenum=&classid=$classid";
$result = curlOpen($url, ['ssl' => true]);
$jsonarr = json_decode($result, true);
if ($jsonarr['status'] != 0) {
break;// 沒有結果則跳到下一個分類
}
$arr[] = $jsonarr['result']['list'];
}
// var_dump($arr);exit;// 檢查結果是否符合預期
// 連接數據庫
$conn = Connection::getInstance();
$sql = 'INSERT INTO `riddle`(`content`,`anwser`) VALUES (:content, :anwser)';
$sth = $conn->prepare($sql); // 使用預處理語句,防止SQL注入(雖然這里沒有多大必要
// 使用循環來插入數據
foreach ($arr as $key => $value) {
$sth->bindParam(':content', $value['content']);
$sth->bindParam(':anwser', $value['anwser']);
$res = $sth->execute(); // 執行預處理語句,成功則返回TRUE
}
在瀏覽器中訪問[http://localhost/app/api/riddle.php],並查看數據庫riddle
表,可以看到數據成功插入!