iT邦幫忙

DAY 24
3

網站系統規劃實務系列 第 24

網站系統開發 - 發文系統實作(完)

本篇文章作為網站實戰開發的第十篇,
我們將繼續實作發表文章,將系統基礎建置完成。

--------系列簡介--------

網站系統可說是現在最多學子與新人想要入門的一個領域,
這個原本屬於新興的領域,這幾年來也累積許多年的知識與 pattern ,
在有限的環境(stateless)與有限的伺服器端、瀏覽器資源下,
成為許多人進入程式的一塊入門鐵板(?)。

這個系列希望能夠就網站系統設計幾個門檻著手,
這是設定給初心者作為學習,給同好們作為回顧,

重新認識有關網站系統的每個環節。
經過重重的實作之後,我們在迄今的二十三篇文章,

順利的講完 html 基礎知識、資料庫基本知識,
甚至我們還使用 CI、bootstrap 做了基本的登入、註冊跟發文。

雖然說畫面還是很基礎、需要調整,但至少功能面上已經完成了。

想想還蠻不可思議的,平常真的是沒有機會好好的去詳述這些細節!XD

當然這些都還只是基礎中的基礎,而且大多還是偏向於後端的部份,
對於一個前端設計師,會這樣的基礎知識還是遠遠不夠的。

但沒關係,我們接著將系統目前的全貌設計完,
之後我們會繼續談論如何強化這個系統。

如果您是剛閱讀這篇並且尚未閱讀本系列其他文章,
請至本系列首篇開始閱讀,否則可能會遺漏部份細節:

http://ithelp.ithome.com.tw/ironman5/player/tony1223/tech/3


目前我們已經實作了:

1.會員系統
* 登入
* 註冊

2.文章系統
* 發文

今天我們要實作的是:

1.作者文章列表:列出特定作者的所有文章的頁面

2.編輯文章

3.刪除文章

4.首頁的熱門文章列表

5.首頁的熱門作者列表


到目前為止從我們之前的開發的過程中,不曉得讀者有沒有發現,
我們是遵循 controller -> model -> view 的開發過程。

假設是開發新功能,理想的狀況下,
應該是先建置好 controller / model / view ,

取得一個可以透過網址正常獨立運作的功能,
有關外部的相依資料,盡量透過 parameter 來由外部傳遞。

最後再將其與外部系統連接,這就是模組化開發的過程。

而大家應該也有發現,同一個 view 在不同 controller 中,
被重用的狀況其實也偶爾會出現。

比較常見的例子就是發文跟發文時,
要顯示的畫面其實很像,另外發文跟編輯也會很像。

所以透過 controller 將功能拆開,
讓網址、邏輯跟實際的 html 有個緩衝區,是很有幫助的。

另外讓資料庫的資料邏輯就留在 model 裡面,
方便其他有需要類似邏輯的 controller 取用。

這就是 MVC 這種模型的一種表現。


接下來我們繼續進行實作:

首先是文章列表,一樣先建立 controller ,

@ 從哪開始?

這時候我們會碰到一個問題,作者文章清單,
到底是該歸屬在 article 或 user 哪一個 controller 呢?

這類型的問題就是開發時,對我們的架構模型的挑戰囉,
基本上是哪一邊並無所謂,只是你還是會需要思考這個問題,

而這個問題會影響以後的人在查程式碼時會怎麼找的問題,
算是程式碼的風格與慣例的一環,程式寫久了多少會由這種不成文的慣例組成。

在這裡筆者是將他歸在 article controller 裡面,
因為我覺得這個資料的主體是作者,不過這很主觀的。

你可以有你自己的想法。:)

@ 分頁

在做清單類的功能時,特別是可預期資料量無上限的狀況,
我們一定會考慮的一種畫面選項就是分頁,有第一頁、第二頁、第三頁等,
所以這裡我們就是要做一個有分頁的清單。

分頁的原理要從資料面來看,因為視覺上呈現所有資料,

一來對資料庫跟伺服器負擔太大,
二來是使用者一次心理上能瀏覽的資料量很有限,

像是 gmail 好了,如果將過去收到的信跟寄出的信不用分頁呈現,
對使用者而言用這麼多時間去呈現、瀏覽一個複雜頁面,
這不僅不太經濟的這也會造成頻寬上的消耗,

而事實上大部分的人有興趣的往往只有前幾則,所以才產生分頁的機制,

分頁有幾個要點:

1.排序條件,有清單都會有排序,
假設你採取隨機排列的話,做分頁是沒有意義的。

在這裡為了發文方便,我們設定順序就是依照發文順序。

2.每頁數量(PageSize)

每頁數量可以決定頁面有多少,也透過每頁頁數,
去決定第 n 頁要呈現哪些資料區間。

3.頁序,也就是第幾頁。(PageIndex)

要判斷有多少頁,你需要的資料是「總數量」、「一頁顯示數量」,
一除就知道有多少頁。


判斷頁數的作法是很簡單的,

假設每頁 20 個,總資料 305 筆,
實際頁數就是 305 /20 = 15.25 ,無條件進位 16 頁。

至於取得該頁資料也很簡單,
像第三頁的資料就會是從 20 * 3 + 1 筆到 20 * 4 筆 (61筆~80筆)。

這是從資料面來推演,分頁這件事情並沒有什麼特別的魔術,
唯一比較複雜的就是該如何根據不同頁數顯示不同連結。

這部份有許多種不同實作作法,
在這裡我們先不多說,直接進入實例操作:

這裡我們直接使用 CI 的 Pagination Class 的作法

他的設定中比較特別的是 PageIndex 是從 0 開始算起,
而且他不是頁數,而是當前第幾筆(offset),

這算是比較特別的作法,請特別注意。

@ controller

application\controllers\article.php

public function author($author = null,$offset = 0)
{
if($author == null){
show_404("Author not found !");
return true;
}

//引入 model
$this->load->model("UserModel");
$this->load->model("ArticleModel");

//先查詢使用者是否存在
$user = $this->UserModel->getUserByAccount($author);
if($user == null){
show_404("Author not found !");
}
$pageSize = 20;

$this->load->library('pagination');
$config['uri_segment'] = 4;
$config['base_url'] = site_url('/article/author/'.$author.'/');
//取得總數量
$config['total_rows'] = $this->ArticleModel->countArticlesByUserID($user->UserID);

$config['per_page'] = $pageSize;

$this->load->library('pagination');
$this->pagination->initialize($config);

$results = $this->ArticleModel->getArticlesByUserID($user->UserID,$offset,$pageSize);

$this->load->view('article_author',
Array(
"pageTitle" => "發文系統 - ".$user->Account." 的文章列表",
"results" => $results,
"user" => $user,
"pageLinks" => $this->pagination->create_links()
)
);
}

完整原始碼:https://gist.github.com/3906537

@ Model

接著實作 Model ,需要實作三個 method ,驗證使用者存在並取得使用者資料,
取使用者文章總數、取得使用者文章該分頁資料。

一個一個來:

application\models\usermodel.php

public function getUserByAccount($account){
$this->db->select("*");
$query = $this->db->get_where("user",Array("account" => $account));

if ($query->num_rows() <= 0){ //如果找不到
return null;
}

return $query->row(); //回傳第一筆
}

完整原始碼:https://gist.github.com/3906413

application\models\articlemodel.php

function countArticlesByUserID($userID){
$this->db->select("count(articleID) as ArticleCount");
$this->db->from('article');
$this->db->where(Array("author" => $userID));
$query = $this->db->get();

if ($query->num_rows() <= 0){
return null; //無資料時回傳 null
}
return $query->row()->ArticleCount;
}

function getArticlesByUserID($userID,$offset = 0,$pageSize = 20){
$this->db->select("article.*,user.Account");
$this->db->from('article');
$this->db->join('user', 'article.author = user.userID', 'left');
$this->db->where(Array("author" => $userID));
$this->db->limit($pageSize,$offset);
$this->db->order_by("ArticleID","desc");//由大到小排序
$query = $this->db->get();

return $query->result(); //無資料時回傳 null
}

完整原始碼:https://gist.github.com/3906560

@ View

接著是結果畫面的實作,建立 view:

application\views\article_author.php

<?php include("_site_header.php"); ?>
<div class="container article-view">
	<?php include("_content_nav.php") ?>	
	<!-- content -->
	<div class="content">

		<h1> <?=$user->Account ?></h1>

		<?php foreach($results as $article){ ?>
		<table class="table table-bordered">
			<tr>
				<td width="50">標題</td>
				<td>
					<a href="<?=site_url("article/view/".$article->ArticleID)?>">
					<?=htmlspecialchars($article->Title)?></a>
				</td>
			</tr>
			<tr>
				<td> 內容 </td>
				<td><?=nl2br(htmlspecialchars($article->Content))?></td>
			</tr>
		</table>
		<?php } ?>
		<p>
			<?=$pageLinks?>
		</p>

	</div>
</div>
<?php include("_site_footer.php"); ?>

完整原始碼 https://gist.github.com/3906722

這是包含 CI Pagination 結果的大概畫面:


這程式碼裡面,還是有許多需要解釋的地方,我們稍微介紹一點,
主要還是跟 pagination 的用法有關,如 controller 裡面的:

$config['base_url'] = site_url('/article/author/'.$author.'/');

$config['total_rows'] = $this->ArticleModel->countArticlesByUserID($user->UserID);

$config['per_page'] = $pageSize;

$config['uri_segment'] = 4;

base_url 顧名思義就是分頁連結該連往哪裡的網址,
它會以這個網址為基礎在之後加入用來作為分頁的 offset。

total_rows 就是計算用的總物件數量

per_page 則是每頁數量( pageSize)

uri_segment 是告訴 Pagination 這個 lib ,分頁用的 offset 資料來自哪裡,

uri_segment 指的是第幾個參數,這是 CI 中很常用的表達式,

如 "index.php/artlcle/author/TonyQ/1" 這樣一個網址,對應的 uri_segment :
1 指的就是 "article" , 2 是 "author", 3 是 "TonyQ" , 4 是 "1"

所以這裡我們就是說他的網址第四位可以表示目前位置,
透過設定這個可以讓 CI Pagination 瞭解目前位置。

另外,這裡比較特別的是採取 offset 的作法,
也就是取得當頁第一筆資料的順序作為起點,往後撈一定數量。

假設一頁五筆,他的 uri 會是:

第一頁 index.php/artlcle/author/TonyQ
第二頁 index.php/artlcle/author/TonyQ/5
第三頁 index.php/artlcle/author/TonyQ/10
第四頁 index.php/artlcle/author/TonyQ/15

這樣有優點就是資料庫查詢比較簡單,
不用自己換算 offset ,但缺點就是要自己換算 Page Index ,

筆者自己比較常實作的作法還是以 Page Index 為主進行的換頁。

不過這裡秉持介紹 CI 的精神,加上 CI 提供產出分頁連結的功能很友善,
所以就以 CI 的 Pagination 作為介紹。

@ 連結至本頁面

接著我們就可以用 index.php?article/aurhot/<作者> 瀏覽該作者文章,
首先要做的,當然就是將 My Articles 這個連結實作上去囉!

application\views\_content_nav.php

<!-- Content Header -->
<div class="content-header">
	<div class="navbar navbar-inverse">
	  <div class="navbar-inner">
	    <a class="brand" href="<?=site_url("/")?>">The Articles</a>
	    <ul class="nav">
	      <li><a href="<?=site_url("/")?>">Home</a></li>
	      	<?php if(isset($_SESSION["user"]) && $_SESSION["user"] != null){?>
	      		<li><a href="<?=site_url("article/author/".$_SESSION["user"]->Account)?>">
	      			My Articles</a></li>
	  		<?php } ?>
	    </ul>
	    <!-- login status -->
	    <?php if(isset($_SESSION["user"]) && $_SESSION["user"] != null){ ?>
		<ul class="nav pull-right">
          <li><a href="#">Hi <?=$_SESSION["user"]->Account?></a></li>
          <li class="divider-vertical"></li>
          <li><a href="<?=site_url("user/logout")?>">登出</a></li>
          <li><a href="<?=site_url("article/post")?>">發文</a></li>
        </ul>
        <?php }else{ ?> 
		<ul class="nav pull-right">
          <li><a href="<?=site_url("user/login")?>">登入</a></li>
          <li class="divider-vertical"></li>
          <li><a href="<?=site_url("user/register")?>">註冊</a></li>
        </ul>        
        <?php } ?>
	  </div>
	</div>
</div>

詳細原始碼 https://gist.github.com/3906584

這樣就可以將全站的 My Article 連結都換成登入者本人的文章清單連結了!


@ 修改文章

快快快,接下來進入修改文章!修改文章開始就比較沒那麼簡單了!

首先要考慮權限問題,修改文章不是所有人都可以修改文章的,
一般而言能修改文章的人,主要是文章的作者。

下一個考慮的問題是,在哪裡可以編輯文章。

一般而言,文章單頁通常會允許已登入的作者對文章進行動作,
另外文章清單也可以考慮加入,但是也要考慮到非作者時瀏覽要正常。

以上是大概的前言,接下來進行實作。

@ Controller

一開始我們仍然從 controller 進入,我們需要的資料是文章編號,
這裡再度強調,重點在權限檢核:

application\controllers\article.php

public function edit($articleID = null){
if (!isset($_SESSION["user"]) || $_SESSION["user"] == null ){
//沒有登入的,直接送他去登入。
redirect(site_url("/user/login"));
return true;
}

if ( $articleID == null){
show_404("Article not found !");
return true;
}

$this->load->model("ArticleModel");
//完成取資料動作
$article = $this->ArticleModel->get($articleID);

if ($article->Author != $_SESSION["user"]->UserID ){
show_404("Article not found !");
//不是作者又想編輯,顯然是來亂的,送他回首頁。
redirect(site_url("/"));
return true;
}

$this->load->view('article_edit',Array(
"pageTitle" => "修改文章 [".$article->Title."]",
"article" => $article
));
}

@ Model

做完 Controller ,接著就是 Model 囉,這裡我們用到的是用 ID 取資料,
但是這個查詢我們在顯示單頁時,就已經作過了,

因為目的一樣,所以我們可以直接重新利用舊的程式碼,
直接調用 $this->ArticleModel->get($articleID) 進行取資料就好。

有沒有再一次體會到 MVC 拆分後,容易有意外的好處可以撿便宜呢。^^

接著就是畫面更新囉,這裡為了撰文方便與降低複雜度,
我們不重用 article_post, 直接建一個新的:

application\views\article_edit.php

<?php include("_site_header.php"); ?>
<div class="container post">
<?php include("_content_nav.php") ?>	
<!-- content -->
<div class="content">
	<form action="<?=site_url("article/update")?>" method="post" > 
		<input type="hidden" name="articleID" value="<?=$article->ArticleID?>" />
		<?php if(isset($errorMessage)){ ?>
		<div class="alert alert-error"><?=$errorMessage?></div>
		<?php } ?>
		<table>
			<tr>
				<td>標題</td>
					<td><input type="text" name="title" 
						value="<?=htmlspecialchars($article->Title)?>" />
					</td>
			</tr>
			<tr>
				<td> 內容 </td>
				<td><textarea name="content" rows="10" cols="60"><?php 
						echo htmlspecialchars($article->Content);
				?></textarea></td>
			</tr>
			<tr>
				<td colspan="2"> 
					<a class="btn" href="<?=site_url("/")?>">取消</a>
					<input type="submit" class="btn" value="送出" />
				</td>
			</tr>
		</table>
	</form>
</div>
</div>
<?php include("_site_footer.php"); ?>

@ 最後我們還要再補一個送出後更新資料的 Controller 操作:

送出時也一樣要檢查權限,千萬不要只做半套喔,系統程式的安全性就在你我手上!

application\controllers\article.php

public function update(){
	$articleID = $this->input->post("articleID");

	//就算是進行更新動作,該做的檢查還是都不能少
	if (!isset($_SESSION["user"]) || $_SESSION["user"] == null ){
		//沒有登入的,直接送他去登入。
		redirect(site_url("/user/login")); 
		return true;
	}		

	if ( $articleID == null){
		show_404("Article not found !");
		return true;
	}

	$this->load->model("ArticleModel");
	//完成取資料動作
	$article = $this->ArticleModel->get($articleID);  

	if ($article->Author != $_SESSION["user"]->UserID ){
		show_404("Article not found !");
		//不是作者又想編輯,顯然是來亂的,送他回首頁。
		redirect(site_url("/")); 
		return true;
	}

	$this->ArticleModel->updateArticle(
		$articleID,
		$this->input->post("title"),
		$this->input->post("content")
	);

	//更新完後送他回文章檢視頁面
	redirect(site_url("article/view/".$articleID));

}

完整原始碼: https://gist.github.com/3906803

@ Model

其中有更新資料的部份,所以要建立對應方法:

application\models\articlemodel.php

function updateArticle($id,$title,$content){
$data = array(
'Title' => $title,
'Content' => $content
);

$this->db->where('ArticleID', $id);
$this->db->update('article', $data);
}

完整原始碼 https://gist.github.com/3906800


透過這樣的步驟,我們就完成了編輯資料的操作,
並且要先試著從資料庫挑一兩筆符合條件的測試資料從網址進行測試。

最後,當然是要把它接到使用者看得到的畫面上囉。:P

我們預計會接在文章瀏覽頁面跟文章列表,實作如後:

1.修改文章瀏覽,再次提醒別忘了權限控管!

application\views\article_view.php

<tr>
<td> 內容 </td>
<td><?=nl2br(htmlspecialchars($article->Content))?></td>
</tr>
<?php if(isset($_SESSION["user"]) && $_SESSION["user"]!= null
&& $_SESSION["user"]->UserID == $article->Author ) { ?>
<tr>
<td colspan="2">
<a href="<?=site_url("article/edit/".$article->ArticleID)?>">編輯此文章</a>
</td
</tr>
<?php } ?>

詳細原始碼: https://gist.github.com/3906856

2.修改文章列表

這裡也是一樣的判斷邏輯:

application\views\article_author.php

<tr>
<td> 內容 </td>
<td><?=nl2br(htmlspecialchars($article->Content))?></td>
</tr>
<?php if(isset($_SESSION["user"]) && $_SESSION["user"]!= null
&& $_SESSION["user"]->UserID == $article->Author ) { ?>
<tr>
<td colspan="2">
<a href="<?=site_url("article/edit/".$article->ArticleID)?>">編輯此文章</a>
</td>
</tr>
<?php } ?>

詳細原始碼:https://gist.github.com/3906933


@ 刪除文章

好的,對於編輯文章已經瞭解的你,相信對於刪除文章就更沒有問題了!

接著我們繼續實作,一樣是進行 controller/model/view 三個關卡:

主要的主角一樣還是權限檢查,這裡其實你會發現權限檢查得部份很像,
所以這個是否為作者其實可以寫成一個獨立函式進行檢查,在這裡我們先不做。

application\controllers\article.php

public function del($articleID = null){
//就算是進行更新動作,該做的檢查還是都不能少
if (!isset($_SESSION["user"]) || $_SESSION["user"] == null ){
//沒有登入的,直接送他去登入。
redirect(site_url("/user/login"));
return true;
}

if ( $articleID == null){
show_404("Article not found !");
return true;
}

$this->load->model("ArticleModel");
//完成取資料動作
$article = $this->ArticleModel->get($articleID);

if ($article->Author != $_SESSION["user"]->UserID ){
show_404("Article not found !");
//不是作者又想編輯,顯然是來亂的,送他回首頁。
redirect(site_url("/"));
return true;
}

$this->ArticleModel->del(
$articleID
);

//更新完後送他回個人文章頁面
redirect(site_url("article/author/".$_SESSION["user"]->Account));
}

詳細原始碼:https://gist.github.com/3906922

@ Model

application\models\articlemodel.php

function del($id){
$this->db->delete('article', array('ArticleID' => $id));
}

非常簡單的示範 CI Active Record 怎麼用來刪除文章。XD


最後當然就是加上畫面上的進入點,修改跟剛剛一樣的兩個 view:

特別注意幾點:

一是這裡我們比較偷懶的透過 GET 直接進行刪除動作,

一般而言有修改資料的動作盡量是要用 POST 進行,
不過有原則就有例外,要不要為了這個畫面去寫一個 form 讀者可以自行考量。

二是一般因為怕使用者誤點,所以都會加上確認訊息,
讓使用者 double check 再進行刪除,這裡為了撰文簡便我們省去這一步驟,

之後前端篇時我們會再介紹更多作法。


1.修改文章瀏覽,還是要提醒別忘了權限控管!

application\views\article_view.php

<?php /*........*/ ?>
<tr>
<td> 內容 </td>
<td><?=nl2br(htmlspecialchars($article->Content))?></td>
</tr>
<?php if(isset($_SESSION["user"]) && $_SESSION["user"]!= null
&& $_SESSION["user"]->UserID == $article->Author ) { ?>
<tr>
<td colspan="2">
<a href="<?=site_url("article/edit/".$article->ArticleID)?>">編輯此文章</a>
<a class="btn" href="<?=site_url("article/del/".$article->ArticleID)?>">刪除文章</a>
</td
</tr>
<?php } ?>
<?php /*........*/ ?>

詳細原始碼: https://gist.github.com/3906941

2.修改文章列表

application\views\article_author.php

<tr>
	<td> 內容 </td>
	<td><?=nl2br(htmlspecialchars($article->Content))?></td>
</tr>
<?php if(isset($_SESSION["user"]) && $_SESSION["user"]!= null 
	&& $_SESSION["user"]->UserID == $article->Author ) { ?>
<tr>
	<td colspan="2">
		<a href="<?=site_url("article/edit/".$article->ArticleID)?>">編輯此文章</a>

		<a class="btn" href="<?=site_url("article/del/".$article->ArticleID)?>">刪除文章</a>
	</td>
</tr>
<?php } ?>

詳細原始碼:https://gist.github.com/3906946


這樣就實作完刪除文章囉,當然我們修改、刪除文章,都略去提示訊息沒有實作,
這個部份的實作策略因為解釋起來比較複雜,

常見的作法,主要還是建立一頁像是註冊成功那樣的頁面,
或者在文章列表頁進行提示這樣,這裡我們僅帶過而不實作。


不要麻煩了、不要麻煩了、我在趕時間、一起上好了,前端工程師很忙的;(喂)

接下來這兩個就一起做吧!

* 熱門文章
* 熱門作者

其實鋪這兩個元素在這裡是有梗的,因為常常有人會做什麼「熱門」功能,
但是這個熱門當然不會是憑空生出來的,他有兩種可能性,

一種是透過客服人員自己指定(人工作業、操作資料庫,又名黑箱作業)。

另一種就是進行各種資料的紀錄,這裡我們要做的效果是,
每當活動單頁有人開啟過,我們就將活動單頁的 views +1 並儲存回資料庫。

並以此進行熱門文章跟作者的排序。

@ Controller

首先一樣從 controller 開始,
我們要先修改文章單頁瀏覽加入 views 的處理。

application\controllers\article.php

public function view($articleID = null){
if($articleID == null){
show_404("Article not found !");
return true;
}

$this->load->model("ArticleModel");
//完成取資料動作
$article = $this->ArticleModel->get($articleID);
if($article == null){
show_404("Article not found !");
return true;
}

//更新文章計數
$this->ArticleModel->updateViews($articleID,$article->Views +1 );

$this->load->view('article_view',Array(
//設定網頁標題
"pageTitle" => "發文系統 - 文章 [".$article->Title."] ",
"article" => $article
));
}

詳細原始碼:https://gist.github.com/3907025

@ Model

接著實作數字更新的部份:

application\models\articlemodel.php

function updateViews($articleID,$views){
$data = array(
'views' => $views,
);

$this->db->where('ArticleID', $articleID);
$this->db->update('article', $data);
}


這樣就差不多算是完成了,當然還是有一些要注意的地方:

因為我們是採取將目前的數字 +1 的情況,所以如果很多人同時存取,
他們可能會拿到一樣的數字,並且加到一樣的數字,
但是因為這裡數字並沒有很敏感,所以我們採取這樣的實作。

也就是有五個人因為進來的時間太接近,
所以他們進來時 views 都是 5,所以都只更新成 6 。

這種狀態與可能性是存在的,這稱之為 race condition 。

看起來間隔時間這麼短好像很不可思議,
但對於日理萬 User 的 Web 而言是很有可能發生的。

如果你對這件事情很敏感,就得採用別的作法了,
像是 table lock 之類的。

寫完 code 一定要養成驗證資料的好習慣,
所以我們開 PHPMyAdmin 來檢查一下。;)


接下來我們要接著實作的就只有首頁的畫面跟查詢了!

@ Controller

當然一樣還是從 Controller 開始~

application\controllers\welcome.php

public function index()
{
$this->load->model("ArticleModel");
$this->load->model("ArticleModel");
$hotArticles = $this->ArticleModel->getHotArticles();
$hotUsers = $this->ArticleModel->getHotAuthors();

$this->load->view('welcome_message',
Array("pageTitle" => "首頁",
"hotArticles" => $hotArticles,
"hotUsers" => $hotUsers
)
);
}

詳細原始碼:https://gist.github.com/3907177

@ Model

一樣,還是寫 code 吧!

application\models\articlemodel.php

function getHotArticles($count = 5){
$this->db->select("article.*,user.Account");
$this->db->from('article');
$this->db->join('user', 'article.author = user.userID', 'left');
$this->db->limit($count, 0);//offset = 0
$this->db->order_by("Views","desc");//由大到小排序
$query = $this->db->get();

return $query->result();
}

function getHotAuthors($count = 5){
$this->db->select("user.*,max(views) as Views");
$this->db->from('article');
$this->db->join('user', 'article.author = user.userID', 'left');
$this->db->limit($count, 0);//offset = 0
$this->db->group_by("author");

//根據該作者所有文章裡面,
//由最大的 views 進行大到小排序
$this->db->order_by("max(Views) desc");
$query = $this->db->get();

return $query->result();
}

這次的 model 稍嫌複雜了點,就查詢熱門文章的部份,
只有改變 order by 的條件所以還好。

就查詢熱門作者的部份,我們用到了 group by 、max ,
這兩個我們還沒有討論過的 db 用法。

大概用口語簡述邏輯,首先我們先將 article 資料表的文章,
以 Author (作者的使用者編號) 進行分群( Group by ),

也就是,現在的資料結構可能是這樣:

user 1
article 1
article 2
article 3
article 4
user 2
article 1
article 2
article 3
article 4
user 3
article 1
article 2
article 3
article 4

但是我們知道資料庫的查詢結果必須要是二維的表格形狀,
所以這時候我唯一能對 Article 資料表做的,就是使用 author 這個欄位。

如這個例子
select author from article group by author

在上面的結構裡面我會拿回以下的資料

user 1
user 2
user 3

這樣就是符合表格的規範的,我會拿回所有不重複的 author 。

但是如果我下
select author,Title,content from article group by author

這樣在 mysql 裡面,他將回傳給你不確定哪一筆的 title,content,
原則上也不建議這樣查詢。

group by 很適合分群統計,如統計每位作者最大文章數的 SQL:

select author,max(views) from article group by author
這指的是將每位作者自己的文章中,取出 views 這欄位的最大值(max) 。

另外還有像是 sum(views) 加總他自己每個文章的總數等。

所以這裡我們的作法是,先找出所有作者跟他最大的文章數,
並用最大的文章數排序,接著透過 left join 拿出這個作者所有資料並回傳。

上面的說明可能有一點小複雜,可以再參考 Group by 相關資料 會更清楚。

@ View

最後終於到最後一個 View 啦!

毫無反應,就是一堆 php code!

application\views\welcome_message.php

<?php include("_site_header.php"); ?>
<div class="container home">
	<?php include("_content_nav.php") ?>	

	<!-- content -->
	<div class="content">
		<div>
			<?php if(count($hotArticles) == 0 ){ ?>
				很抱歉,目前尚無熱門文章
			<?php }else{ ?>
			<h1>熱門文章</h1>
			<table class="table"> 
				<tr>
					<td>標題</td>
					<td>作者</td>
					<td>點閱次數</td>
				</tr>
				<?php foreach ($hotArticles as $article) {	?>
				<tr>
					<td>
						<a href="<?=site_url("article/view/".$article->ArticleID)?>">
							<?=htmlspecialchars($article->Title)?>
						</a>
					</td>
					<td>
						<?=htmlspecialchars($article->Account)?>
					</td>
					<td>
						<?=htmlspecialchars($article->Views)?>
					</td>
				<?php } ?>
			</table>
			<?php }   ?>
			
		</div>
		<div>
			<?php if(count($hotUsers) == 0 ){ ?>
				很抱歉,目前尚無熱門作者
			<?php }else{ ?>
			<h1>熱門作者</h1>
			<table class="table"> 
				<tr>
					<td>作者</td>
					<td>文章最高點閱次數</td>
				</tr>
				<?php foreach ($hotUsers as $user) {	?>
				<tr>
					<td>
						<a href="<?=site_url("article/author/".$user->Account)?>">
							<?=htmlspecialchars($user->Account)?>
						</a>
					</td>
					<td>
						<?=htmlspecialchars($user->Views)?>
					</td>
				<?php } ?>
			</table>
			<?php }   ?>
		</div>
	</div>
</div>
<?php include("_site_footer.php"); ?>

詳細原始碼: https://gist.github.com/3907272

@ 透過本週豐富的實作進度後,我們總算有一個比較正常跟基本功能的網站了!

接下來我們將從前端層面,繼續探討如何增強這個網站的內容與更多細節,
敬請期待~*

明天見^^


上一篇
網站系統開發 - 發文系統實作(發文篇)
下一篇
網站系統開發 - 使用者角色與網頁以外的服務
系列文
網站系統規劃實務27

1 則留言

0
ted99tw
iT邦高手 1 級 ‧ 2012-10-18 00:03:15

沙發

好讚的內容,一定要抽空從頭拜讀到尾~~

讚讚讚

janet1 iT邦新手 5 級 ‧ 2013-09-24 11:53:47 檢舉

是阿
正在讀!!!
****************
application\models\articlemodel.php
程式碼有問題,這個不完整了
https://gist.github.com/3906560

janet1 iT邦新手 5 級 ‧ 2013-09-24 12:12:27 檢舉

2.修改文章列表
這頁的程式碼也沒了~
application\views\article_author.php
詳細原始碼:https://gist.github.com/3906946

我要留言

立即登入留言