Yii
是一個高性能持痰,基于組件的 PHP 框架,用于快速開發(fā)現(xiàn)代 Web 應(yīng)用程序。
今天殊霞,我本著體驗 Yii2
的想法蒋歌,準備使用 Yii2
從 0 到 1 來搭建一個 Todo List
帅掘,并完成以下功能:
- 可以基于某個
key
創(chuàng)建Todo Item
,然后根據(jù)key
查詢對應(yīng)的Todo Item
堂油。 - 可以置頂修档、完成、刪除單條
Todo Item
府框,置頂?shù)?Todo Item
將排列在最前面吱窝,完成的Todo Item
將排列在最后面。
初始化 YII 倉庫
使用下面的命令即可初始化一個 YII
的倉庫迫靖。
composer create-project --prefer-dist yiisoft/yii2-app-basic basic
但是院峡,我的 mac
通過這個方法老是連不上網(wǎng)絡(luò),安裝某些依賴失敗系宜,所以這里選擇第二種方式照激。(如下)
在 yiiframework 下載歸檔文件,然后解壓到你要放置的項目目錄中盹牧。
在下載解壓完成后实抡,需要先修改 config/web.php
文件欠母,給 cookieValidationKey
配置項添加一個密鑰(隨便輸入一個值就可以),以便項目能夠正常啟動吆寨。
在項目初始化完成以后赏淌,我們使用下面這個命令運行項目吧。
php yii serve --port=8888
然后我們打開 http://localhost:8888啄清,看到我們的頁面已經(jīng)成功啟動啦A(如下圖)
初始化數(shù)據(jù)模型
接下來,我們來初始化我們的數(shù)據(jù)模型辣卒。
我們需要創(chuàng)建的字段有下面這些:
- id:自增主鍵掷贾;
- key:Todo 的 key;
- title:Todo 的標(biāo)題荣茫;
- is_completed:Todo 是否完成想帅;
- is_top:Todo 是否置頂;
- is_deleted:Todo 是否刪除啡莉;
而上面這些字段中港准,我們最多的場景是通過 key
來撈出相關(guān)的 Todo Item
,所以應(yīng)該給 key
建立一個普通索引咧欣。
綜上浅缸,我們的 sql
語句應(yīng)該是這樣的:
CREATE TABLE IF NOT EXISTS `todos` (
`id` int PRIMARY KEY AUTO_INCREMENT,
`key` varchar(64) NOT NULL DEFAULT '',
`title` varchar(64) NOT NULL DEFAULT '',
`is_top` tinyint(1) NOT NULL DEFAULT 0,
`is_completed` tinyint(1) NOT NULL DEFAULT 0,
`is_deleted` tinyint(1) NOT NULL DEFAULT 0,
index `key`(`key`)
) engine=InnoDB CHARSET=utf8;
在數(shù)據(jù)庫中執(zhí)行該條 SQL
,創(chuàng)建對應(yīng)的數(shù)據(jù)表魄咕。
然后衩椒,我們還可以通過下面這條語句查看我們創(chuàng)建的索引。
SHOW INDEX FROM `todos`;
處理 Todo 業(yè)務(wù)邏輯
在數(shù)據(jù)表創(chuàng)建成功后哮兰,我們就準備開始寫 Todo
相關(guān)的業(yè)務(wù)邏輯了毛萌。
在此之前,我們還需要做一些 Yii
的配置初始化工作喝滞。
初始化 Yii 配置
首先阁将,我們的 php
服務(wù)需要連接數(shù)據(jù)庫,所以你需要先配置你的數(shù)據(jù)庫連接囤躁,也就是 config/db.php
:
- 數(shù)據(jù)庫配置
<?php
return [
'class' => 'yii\db\Connection',
'dsn' => 'mysql:host=[mysql服務(wù)器地址];port=[mysql端口];dbname=[數(shù)據(jù)庫名稱]',
'username' => '[數(shù)據(jù)庫用戶名]',
'password' => '[數(shù)據(jù)庫密碼]',
'charset' => 'utf8',
'attributes' => [
// 查詢時將 int 類型按原類型返回
PDO::ATTR_STRINGIFY_FETCHES => false,
PDO::ATTR_EMULATE_PREPARES => false
]
];
- URL 美化配置
然后冀痕,我們再來配置一下 URL
美化,這樣就可以按照標(biāo)準的 restful
風(fēng)格進行訪問了狸演,調(diào)整 config/web.php
中的 urlManager
即可言蛇。(如下)
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => false,
'showScriptName' => false,
'rules' => [
],
],
- JSON 入?yún)⑴渲?/li>
然后,我們還需要修改一下 request
的配置宵距,以便接受 application/json
的入?yún)ⅰ?/p>
'components' => [
...
'request' => [
'parsers' => [
'application/json' => 'yii\web\JsonParser',
]
],
...
]
修改完了配置后腊尚,可以重啟一下你的項目。
創(chuàng)建 TodoModel
+ TodoRepository
+ TodoService
+ TodoController
我們先來創(chuàng)建 Todo
的數(shù)據(jù)實體類 —— TodoModel
满哪,這個模型將會貫穿 Todo List
的整個生命周期婿斥。
<?php
namespace app\models;
use Yii;
use Yii\base\Model;
class TodoModel extends Model {
public $id;
public $key;
public $title;
public $is_top;
public $is_completed;
public $is_deleted;
}
然后劝篷,我們創(chuàng)建 TodoRepository
,用于數(shù)據(jù)持久化民宿。 —— SQL 寫在這里娇妓。
<?php
namespace app\repositories;
use app\models\TodoModel;
class TodoRepository {
public static function selectAll(TodoModel $todo) {
}
public static function insertOne(TodoModel $todo) {
}
public static function deleteOne(TodoModel $todo) {
}
public static function updateOne(TodoModel $todo) {
}
}
接下來,我們來創(chuàng)建 TodoService
活鹰,用于處理業(yè)務(wù)邏輯哈恰。 —— 所有業(yè)務(wù)邏輯放在這里。
<?php
namespace app\services;
use app\models\TodoModel;
use app\repositories\TodoRepository;
class TodoService {
public function getAllTodo(TodoModel $model) {
return TodoRepository::selectAll($model);
}
public function addTodo(TodoModel $model) {
}
public function topTodo(TodoModel $model) {
}
public function completeTodo(TodoModel $model) {
}
public function deleteTodo(TodoModel $model) {
}
}
最后志群,我們創(chuàng)建 TodoController
着绷,用于控制業(yè)務(wù)流程和處理接口請求。 —— 與客戶端交互的邏輯放在這里锌云。
<?php
namespace app\controllers;
use Yii;
use yii\rest\ActiveController;
use yii\web\Response;
use app\services\TodoService;
use app\models\TodoModel;
class TodoController extends ActiveController
{
public TodoService $todoService;
public function __construct($id, $module, $config = [])
{
parent::__construct($id, $module, $config);
this.$todoService = new TodoService();
}
// 將響應(yīng)數(shù)據(jù)轉(zhuǎn)成 JSON
public function behaviors()
{
return [
[
'class' => \yii\filters\ContentNegotiator::className(),
'only' => ['index', 'view'],
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
],
];
}
public function actionGetTodoList() {
}
}
將基礎(chǔ)的 TodoModel
+ TodoRepository
+ TodoService
+ TodoController
荠医,也就是 MVC
模型準備好了以后,我們就準備開始添加真實有效的業(yè)務(wù)邏輯了桑涎。
查詢對應(yīng) key
的 Todo List
我們現(xiàn)在準備根據(jù) key
來查詢對應(yīng)的 todo
列表彬向。
我們首先來編輯 TodoRepository
的 selectAll
,將對應(yīng)的 SQL
查詢邏輯寫好石洗。
class TodoRepository {
/**
* @throws \yii\db\Exception
*/
public static function selectAll(TodoModel $todo) {
$db = Yii::$app->db;
// 組裝 SQL 語句幢泼,查詢對應(yīng) key 且未刪除的數(shù)據(jù)
// 查詢的數(shù)據(jù)按照 `是否完成` 升序排列紧显,按照 `是否置頂` 降序排列
$sql = "SELECT *
FROM `todos`
WHERE `key` = :code AND `is_deleted` = 0
ORDER BY is_completed ASC, is_top DESC";
return $db->createCommand($sql)->bindValue(':code', $todo->key)->queryAll();
}
//...
}
在 TodoRepository
的 SQL
語句編輯完成后讲衫,我們可以在數(shù)據(jù)庫中執(zhí)行試試。(如下圖)
從上圖可以看出孵班,該 SQL
按我們預(yù)想的運行 —— 使用 key
作為索引涉兽,只檢索了 4 條數(shù)據(jù)(此時數(shù)據(jù)庫有 10 條數(shù)據(jù))。
這條
SQL
還涉及到了Using filesort
篙程,我還沒有想到比較好的優(yōu)化方案枷畏,大家可以嘗試一下優(yōu)化這條 SQL。
我們來編輯 TodoController
的 actionGetTodoList
方法即可(TodoService
不需要修改)虱饿。
public function actionGetTodoList() {
$model = new TodoModel();
$params = Yii::$app->request->get();
// 取出 query 參數(shù)中的 key 字段
$model->key = $params['key'];
return $this->todoService->getAllTodo($model);
}
在邏輯補充完后拥诡,打開頁面 http://localhost:8888/todo/get-todo-list?key=test
驗證一下效果吧。(如下圖)
從上圖可以看出氮发,數(shù)據(jù)按照我們預(yù)期的篩選和排序返回了渴肉!
補全剩余業(yè)務(wù)邏輯 —— 增刪改
接下來,就是依次將 增刪改
的邏輯加上就好了爽冕,這應(yīng)該是最簡單也是最經(jīng)典的 CRUD
了仇祭。(如下)
TodoModel.php
<?php
namespace app\models;
use Yii;
use yii\base\Model;
class TodoModel extends Model
{
public $id;
public $key = '';
public $title = '';
public $is_top = 0;
public $is_completed = 0;
public $is_deleted = 0;
public function rules()
{
return [
[['id', 'key', 'title'], 'required']
];
}
}
TodoRepository.php
<?php
namespace app\repositories;
use Yii;
use app\models\TodoModel;
class TodoRepository
{
/**
* @throws \yii\db\Exception
*/
public static function selectAll(TodoModel $todo)
{
$db = Yii::$app->db;
// 組裝 SQL 語句,查詢對應(yīng) key 且未刪除的數(shù)據(jù)
// 查詢的數(shù)據(jù)按照 `是否完成` 升序排列颈畸,按照 `是否置頂` 降序排列
$sql = "SELECT *
FROM `todos`
WHERE `key` = :code AND `is_deleted` = 0
ORDER BY is_completed ASC, is_top DESC";
return $db->createCommand($sql)->bindValue(':code', $todo->key)->queryAll();
}
/**
* @throws \yii\db\Exception
*/
public static function insertOne(TodoModel $todo)
{
$db = Yii::$app->db;
return $db->createCommand()->insert('todos', $todo)->execute();
}
/**
* @throws \yii\db\Exception
*/
public static function updateOne(array $todoData, string $id)
{
$db = Yii::$app->db;
return $db
->createCommand()
->update('todos', $todoData, "id = :id")
->bindValue("id", $id)
->execute();
}
}
TodoService.php
<?php
namespace app\services;
use app\models\TodoModel;
use app\repositories\TodoRepository;
class TodoService
{
public function getAllTodo(TodoModel $model)
{
return TodoRepository::selectAll($model);
}
public function addTodo(TodoModel $model)
{
return TodoRepository::insertOne($model);
}
public function topTodo(TodoModel $model)
{
return TodoRepository::updateOne([
'is_top' => 1
], $model->id);
}
public function completeTodo(TodoModel $model)
{
return TodoRepository::updateOne([
'is_completed' => 1
], $model->id);
}
public function deleteTodo(TodoModel $model)
{
return TodoRepository::updateOne([
'is_deleted' => 1
], $model->id);
}
}
TodoController.php
<?php
namespace app\controllers;
use Yii;
use yii\web\Controller;
use app\services\TodoService;
use app\models\TodoModel;
class TodoController extends Controller
{
public $todoService;
public $enableCsrfValidation = false;
public function __construct($id, $module, $config = [])
{
parent::__construct($id, $module, $config);
$this->todoService = new TodoService();
}
// 將響應(yīng)數(shù)據(jù)轉(zhuǎn)成 JSON
public function behaviors()
{
return [
[
'class' => \yii\filters\ContentNegotiator::className(),
'formats' => [
'application/json' => \yii\web\Response::FORMAT_JSON,
],
],
];
}
public function actionGetTodoList()
{
$model = new TodoModel();
$params = Yii::$app->request->get();
// 取出 query 參數(shù)中的 key 字段
$model->key = $params['key'];
return [
'code' => 0,
'data' => $this->todoService->getAllTodo($model)
];
}
public function actionAdd()
{
$model = new TodoModel();
$params = Yii::$app->request->post();
$model->key = $params['key'];
$model->title = $params['title'];
$this->todoService->addTodo($model);
return ['code' => 0];
}
public function actionTop()
{
$model = new TodoModel();
$params = Yii::$app->request->post();
$model->id = $params['id'];
$this->todoService->topTodo($model);
return ['code' => 0];
}
public function actionComplete()
{
$model = new TodoModel();
$params = Yii::$app->request->post();
$model->id = $params['id'];
$this->todoService->completeTodo($model);
return ['code' => 0];
}
public function actionDelete()
{
$model = new TodoModel();
$params = Yii::$app->request->post();
$model->id = $params['id'];
$this->todoService->deleteTodo($model);
return ['code' => 0];
}
}
如此一來乌奇,我們的 Todo List
系統(tǒng)就基本完成了没讲,它已經(jīng)完成了下面這些功能:
- 可以基于某個
key
創(chuàng)建Todo Item
,然后根據(jù)key
查詢對應(yīng)的Todo Item
礁苗。 - 可以置頂爬凑、完成、刪除單條
Todo Item
试伙,置頂?shù)?Todo Item
將排列在最前面贰谣,完成的Todo Item
將排列在最后面。
當(dāng)然迁霎,我們還需要考慮參數(shù)驗證吱抚、大數(shù)據(jù)查詢的優(yōu)化問題、更簡潔的參數(shù)綁定等等問題考廉,這里就不做展開了秘豹,可能會以一期新的文章進行講解。
部署應(yīng)用
現(xiàn)在昌粤,我們來將我們的 Todo List
系統(tǒng)部署到線上吧既绕。
啟動 Docker 容器
Yii2
的部署非常簡單,因為 Yii 內(nèi)置了 docker-compose
配置文件涮坐。
所以凄贩,我們只需要在文件夾內(nèi)運行 docker-compose up -d
就可以啟動一個 docker 服務(wù)了。(如下圖)
現(xiàn)在袱讹,我們修改一下 docker-compose.yml
的端口映射改一下疲扎,將其改成一個比較特殊的端口 —— 9999
。
ports:
- '9999:80'
然后捷雕,我們在我們的服務(wù)器(我的服務(wù)器是阿里云 ECS)內(nèi)椒丧,把對應(yīng)的倉庫代碼拉下來,運行 docker-compose up -d
啟動容器即可救巷。
配置 Nginx
服務(wù)啟動后壶熏,我們需要配置 nginx
,將我們指定域名的請求 hacker.jt-gmall.com
轉(zhuǎn)發(fā)到 9999
端口浦译。
然后棒假,在 nginx
上加上跨域頭,允許前端跨域請求(最后幾行)精盅。
server {
listen 443;
server_name hacker.jt-gmall.com;
ssl on;
ssl_certificate /https/hacker.jt-gmall.com.pem;
ssl_certificate_key /https/hacker.jt-gmall.com.key;
ssl_session_timeout 5m;
ssl_ciphers ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
ssl_protocols SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
location / {
index index.html index.jsp;
client_max_body_size 300m;
client_body_buffer_size 128k;
proxy_connect_timeout 600;
proxy_read_timeout 600;
proxy_send_timeout 600;
proxy_buffer_size 64k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_pass http://127.0.0.1:9999;
add_header "Access-Control-Allow-Origin" "*"; # 全局變量獲得當(dāng)前請求origin帽哑,帶cookie的請求不支持*
add_header "Access-Control-Allow-Methods" "*"; # 允許請求方法
add_header "Access-Control-Allow-Headers" "*"; # 允許請求的 header
# 如果是 OPTIONS 請求,則返回 204
if ($request_method = 'OPTIONS') {
return 204;
}
}
}
安裝依賴
在服務(wù)啟動并且配置好了 nginx
后進行訪問渤弛,可能會出現(xiàn)下圖這個錯誤祝拯。
這是因為 Git
版本管理中,會忽略 Yii
的 vendor
目錄,我們只需要使用 composer
將依賴重新安裝一遍即可佳头,運行下面這個命令鹰贵。
composer update
composer install
由于
config/db.php
中包含了數(shù)據(jù)庫連接信息,我也沒有放到Git
倉庫中康嘉。如果你在使用我的
demo
碉输,也請將這個文件補齊。
然后亭珍,我們打開瀏覽器敷钾,輸入 https://hacker.jt-gmall.com/todo/get-todo-list?key=test 看看效果吧!(如下圖)
大功告成啦肄梨!
小結(jié)
在本篇文章中阻荒,我針對自己使用 Yii
搭建一個基礎(chǔ) Todo List
服務(wù)的體驗,寫了一篇文章众羡。
實際操作下來侨赡,發(fā)現(xiàn)使用 Yii
搭建一個服務(wù)端業(yè)務(wù)站點還是比較簡單的,經(jīng)典的 MVC
模式也比較淺顯易懂粱侣。
在后續(xù)的文章里羊壹,我可能會針對 Yii
的進階使用再進行歸納總結(jié)。
最后附上本次體驗的 Demo 地址齐婴。
最后一件事
如果您已經(jīng)看到這里了油猫,希望您還是點個贊再走吧~
您的點贊是對作者的最大鼓勵,也可以讓更多人看到本篇文章柠偶!
如果覺得本文對您有幫助情妖,請幫忙在 github 上點亮 star
鼓勵一下吧!