我剛開始接觸前後端分離的時候,正值它開始慢慢擴散的時候,也還沒有意識到它帶來的好處。覺得它甚是麻煩,當我改一個接口的時候,我需要同時修改兩部分的代碼,以及對應的測試。反而,還不如直接修改原有的模板來得簡單。
可是當我去使用這個,由前後端分離做成的單頁面應用時,我開始覺得這些是值得。當頁面加載完後,每打開一個新的鏈接時,不再需要等網絡返回給我結果;我也能快速的回到上一個頁面,像一個 APP 一樣的體現這樣的應用。整個過程裡,我們只是不斷地從後台去獲取數據,不需要重複地請求頁面——因為這些頁面的模板已經存在本地了,我們所缺少的只是實時的數據。
後來,當我從架構去考慮這件事時,我才發現這種花費是值得的。
《Web研發模式演變》
如果這個時候,我們還需要做各種頁面交互,如填寫表單、 Popup 、動態數據等等,就不再是簡單的和模板引擎打交道了。我們需要編寫大量的JavaScript 代碼,因為業務的不斷增加,僅使用jQuery 無法管理如此復雜的代碼。
可是當我去使用這個,由前後端分離做成的單頁面應用時,我開始覺得這些是值得。當頁面加載完後,每打開一個新的鏈接時,不再需要等網絡返回給我結果;我也能快速的回到上一個頁面,像一個 APP 一樣的體現這樣的應用。整個過程裡,我們只是不斷地從後台去獲取數據,不需要重複地請求頁面——因為這些頁面的模板已經存在本地了,我們所缺少的只是實時的數據。
後來,當我從架構去考慮這件事時,我才發現這種花費是值得的。
《Web研發模式演變》
傳統的 MVC 架構裡,因為某些原因有相當多的商業邏輯,會被放置到View 層,也就是模板層裡。換句話來說,就是這些邏輯都會被放到前端。我們看到的可能就不是各種if 、else 還有簡單的equal 判斷,還會包含一些複雜的商業邏輯,比如說對某些產品進行特殊的處理。
如果這個時候,我們還需要做各種頁面交互,如填寫表單、 Popup 、動態數據等等,就不再是簡單的和模板引擎打交道了。我們需要編寫大量的JavaScript 代碼,因為業務的不斷增加,僅使用jQuery 無法管理如此復雜的代碼。
一、前端取得資料使用令牌進行身份驗證過程
客戶端接收到令牌後對其進行儲存,每次訪問時需攜帶的令牌
服務端在接受到客戶端請求時需驗證令牌,驗證成功則回傳資料。
生成與驗證令牌的方法有很多種,這裡使用的是jwt(Json Web Token)。
Use composer to manage your dependencies and download PHP-JWT:
例如:
使用 HTTP 動詞當然是可選的(非必要)。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
</body>
</html>
//簽發Token
composer require firebase/php-jwt
二、定義後端API接口
這是有可能的使用 HTTP 動詞(request method)去定義你的路由規則。這是特別有用的當建立 RESTful 應用程式的時後。你可以使用標準的 HTTP 動詞(GET、PUT、POST、DELETE、PATCH)或者客製化的動詞像是(e.g. PURGE)。 HTTP 動詞規則不區分大小寫。所有你需要做的路由,就是將動詞增加到你的陣列索引裡面。例如:
$route['api']['put'] = 'product/insert';
上述例子,PUT 請求到 URI“products” 稱之為 Product::insert() 控制器方法。$route['api/(:num)']['DELETE'] = 'product/delete/$1';
DELETE 請求到 URL“products”第一個片段,數字在第二個片段將會重新映射到 Product::delete() 方法,傳入數值到第一個參數上。使用 HTTP 動詞當然是可選的(非必要)。
三、建立基本Layout前端HTML
JQuery、Bootstrap、Awesome、DataTables、Datepicker<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Page Title</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
</body>
</html>
四、非SPA(Single Page Application)也沒有Node.js 內建的Web Server
Web Server用的是XAMPP開發環境
前端使用Purl (A JavaScript URL parser)判斷uri決定js要Include哪一個HTML
$.get(`view/${uri}.html`,function(data){
Codeigniter 3負責Layout的Server side render,還有api跟jwt管理
前端使用Purl (A JavaScript URL parser)判斷uri決定js要Include哪一個HTML
$.get(`view/${uri}.html`,function(data){
$("body").html(data);
});
Codeigniter 3負責Layout的Server side render,還有api跟jwt管理
//簽發Token
public function getLssue()
{
$key = $this->CI->config->item('KEY'); //key
$time = time(); //當前時間
$token = [
'iss' => $this->CI->config->item('DOMAIN'), //簽發者 可選
'aud' => $this->CI->config->item('DOMAIN'), //接收該JWT的一方,可選
'iat' => $time, //簽發時間
'nbf' => $time , //(Not Before):某個時間點後才能訪問,比如設置time+30,表示當前時間30秒後才能使用
'exp' => $time+5, //過期時間,這裡設置5秒
];
echo JWT::encode($token, $key); //輸出Token
exit;
}
public function verification($jwt='')
{
$key = $this->CI->config->item('KEY'); //key
try {
JWT::$leeway = 60;//當前時間減去60,把時間留點餘地
$decoded = JWT::decode($jwt, $key, ['HS256']); //HS256方式,這裡要和簽發的時候對應
$arr = (array)$decoded;
return $arr;
} catch(\Firebase\JWT\SignatureInvalidException $e) { //簽名不正確
log_message('debug', $e->getMessage());
}catch(\Firebase\JWT\BeforeValidException $e) { // 簽名在某個時間點之後才能用
log_message('debug', $e->getMessage());
}catch(\Firebase\JWT\ExpiredException $e) { // token過期
log_message('debug', $e->getMessage());
}catch(Exception $e) { //其他錯誤
log_message('debug', $e->getMessage());
}
//Firebase定義了多個 throw new,我們可以捕獲多個catch來定義問題,catch加入自己的業務,比如token過期可以用當前Token刷新一個新Token
}
public function verToken()
{
$headers = $this->CI->input->request_headers();
$mate = preg_match('/Bearer\s(\S+)/', $headers['Authorization'], $matches); //判斷Authorization格式是否正確
if (!empty($mate)){
$auth = $this->verification(str_replace('Bearer ','',$headers['Authorization']));
return ($auth['aud']==$this->CI->config->item('DOMAIN')) ? 1 : 0; //判斷來源與Config設定值是否相同
}
}
is_ajax_request() 判斷是否由ajax發出的請求
Returns: TRUE if it is an Ajax request, FALSE if not
Return type: bool
過去測試都是用Postman發出請求看回傳值是否正確
這次直接用CI3提供的單元測試類別 驗證後端API是否能夠正確產出所需資料
$test = 1 + 1;
Returns: TRUE if it is an Ajax request, FALSE if not
Return type: bool
過去測試都是用Postman發出請求看回傳值是否正確
這次直接用CI3提供的單元測試類別 驗證後端API是否能夠正確產出所需資料
$test = 1 + 1;
$expected_result = 2;
$test_name = 'Adds one plus one';
$this->unit->run($test, $expected_result, $test_name);
五、前端AJAX向後端發出請求並帶JWT令牌
$.ajax({
type: "POST", //GET, POST, PUT
url: '/authenticatedService' //the url to call
data: yourData, //Data sent to server
contentType: contentType,
beforeSend: function (xhr) { //Include the bearer token in header
}).done(function (response) {
//Response ok. process reuslt
}).fail(function (err) {
//Error during request
});
jquery的async:false,這個屬性
type: "POST", //GET, POST, PUT
url: '/authenticatedService' //the url to call
data: yourData, //Data sent to server
contentType: contentType,
beforeSend: function (xhr) { //Include the bearer token in header
xhr.setRequestHeader("Authorization", 'Bearer '+ jwt);
}}).done(function (response) {
//Response ok. process reuslt
}).fail(function (err) {
//Error during request
});
jquery的async:false,這個屬性
預設是true:非同步,false:同步。
六、表單Submit Form to Ajax Api
let form = $(e.target);
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: form.serialize(),
cache: false,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + token);
},
success: function (data) {
return true;
},
error: function(xhr) {
return false;
}
});
$.ajax({
url: form.attr("action"),
type: form.attr("method"),
data: form.serialize(),
cache: false,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + token);
},
success: function (data) {
return true;
},
error: function(xhr) {
return false;
}
});
七、表單Submit Form to Ajax Api & File Upload
$.ajax({
url: form.attr("action"),
type:form.attr("method"),
data: new FormData($('form')[0]),
cache: false,
contentType : false,
processData : false,
beforeSend: function (xhr) {
xhr.setRequestHeader("Authorization", "Bearer " + token);
},
success: function (data) {
return true;
},
error: function(xhr) {
return false;
}
});
totalPages: 100,
visiblePages: 10,
currentPage: 1,
onPageChange: function (num, type) {
$('#text').html('當前第' + num + '頁');
}
});
八、jQuery分頁組件 jqPaginator
$('#id').jqPaginator({totalPages: 100,
visiblePages: 10,
currentPage: 1,
onPageChange: function (num, type) {
$('#text').html('當前第' + num + '頁');
}
});
九、Bootstrap 3 Modal(互動視窗)
Use Bootstrap’s JavaScript modal plugin to add dialogs to your site for lightboxes, user notifications, or completely custom content.
沒有留言:
張貼留言