李炎恢-在線商城第三季總結
1) 首先分析一下表結構 (數據庫設計尤為重要)
① 管理員表mall_manage
② 管理員等級表mall_level
解釋:level為外鍵,為管理員等級(一對一)
③ 用戶表mall_user
④ 用戶收貨地址表mall_address
解釋:
1.用戶的收貨地址可以有多個,而只有一個是默認(selected=1);
2.這里的收貨地址表並沒有去關聯用戶表,其實可以關聯一下;
⑤ 商品類型表mall_nav
解釋:
- 商品類型(商品欄目),這里支持無限極分類;
- 其中sid表示其分類id(為0表示頂級父類);
- 只有子類才可以關聯商品品牌(多對多)等外表,頂級父類不可以關聯;
- 而價格區間無論是子類還是父類都可關聯(多對多)。(補充:更合理划分價格區間,應該由該類目下的最高價格與最低價格之間進行比對,然后再自動划分,不太應該由用戶指定)
⑥ 商品品牌表
⑦ 商品屬性表
解釋:商品屬性關聯商品類型,原因是商品類型有限而固定,屬性可以隨時增減,屬性關聯類型更合理;(多對多)
⑧ 商品價格區間表
⑨ 商品表
解釋:
1.商品goods_id-à商品類型nav_id (一對一)(補充:這里商品類型必須是子類)
2.商品goods_id-à商品品牌brand_id(一對一)(一個商品只屬於一個品牌)
3.商品goods_id-à商品屬性attr_id(一對多)(一個商品有多個屬性)
4.關鍵字段說明:商品編號goods_sn、大縮略圖thumbnail、小縮略圖thumbnail2、本店價price_sale、市場價price_market、成本價price_cost、單位計量unit、重量weight、詳細描述content、是否上架is_up、是否免郵費is_freight、庫存量inventory、warn_inventory最小庫存量(小於它,將會報庫存警告)、發布時間date
⑩ 訂單表
解釋:(說明,這里為了方便起見,並沒有將用戶表、用戶收貨地址表關聯起來)
- 訂單表order_id-àuser_id(一對一,該訂單只能屬於一個用戶);
- 訂單表order_id-àuser_address_id(一對一,該訂單只能對應一個收貨地址,可修改);
- 訂單表order_id-à支付方式表pay_id(一對一,該訂單只能對應一種支付方式,可修改);
- 訂單表order_id-à配送方式表delivery_id(一對一,該訂單只能對應一種配送方式,可修改)
- 補充:還有一個缺貨處理方式表,該表是否需要建立,目前未知;
- 關鍵字段說明:用戶備注text、缺貨處理方式ps、訂單編號ordernum、總價格price、商品詳請goods(serialize序列化后存儲)、訂單狀態order_state、配送狀態order_delivery、支付狀態order_pay、下單時間date
數據庫名mall,一共10張表,如下:
數據庫分析總結:需要改進的地方有以下幾點:
- 用戶表應與用戶收貨地址表相關聯,減小重復;
- 訂單表應與用戶收貨地址表相關聯,減少重復;
- 應建立支付方式表pay,再關聯,滿足三范式;
- 應建立配送方式表delivery,再關聯,滿足三范式;
- 其他還在整理中…
2) 然后,分析李炎恢第三季在線商城MVC框架(原理+實例)
① 其目錄結構如下:
② 目錄結構說明:
- 該架構使用smarty作為模板引擎,其對應目錄smarty、cache、compile、view(修改自templates),分別是smarty核心文件、緩存目錄、編譯文件、模板文件(MVC稱之為視圖層);
- 其MVC結構,controller控制器、model模型層、view視圖層、check驗證層(隸屬於model層,為model層服務,負責對請求數據,進行驗證);
- 其他目錄說明,ckeditor開源文本編輯器、configs配置文件、public公共類庫(如工具類、圖片處理類、文件上傳類等)、uploads存放上傳文件、index.php入口文件;
③ 商城框架流程圖(非常重要)
層次結構(李炎恢老師畫)
---------------------------------------------------------------------------------------------------------------------
類結構(李炎恢老師畫)
說明:上圖在此你可能看不懂,下面會以實例說明之
④ 其框架特點總結
- 單一入口,index.php,所有請求都通過index.php入口文件;
- 請求方式index.php?a=mamage&m=add (a表示action,m表示方法method),如上請求ManageAction()->add();
- 實例化action,使用了簡單工廠模式Factory::setAction()->run(),其原理是通過判斷$_GET[‘a’]去實例化action;
- 執行m方法,Factory::setAction()->run(),在頂級父類Action的run()方法中,判斷$_GET[‘m’],去執行相應的action方法;
- Action中持有model、smarty、redirect類的實例,使用了組合模式。調用model,對請求數據進行處理、驗證數據、查詢數據庫等,smarty加載模板文件並解析生成緩存文件(加載視圖),redirect負責頁面跳轉;
- Model中持有request、check、db類的實例,使用了組合模式。調用request對所請求的數據過濾,調用check驗證請求數據,調用db查詢數據庫完成增刪改查操作;
- 使用TPL類繼承Smarty,然后修改Smarty和實現單例,使用了單例模式;這里面很多類都是用了單例模式,請注意;
- 其他說明,controller層中類命名為xxxAction.class.php,model層中類命名為xxxModel.class.php,check層中類命名為xxxCheck.class.php。 如ManageAction.class.php,ManageModel.class.php,ManageCheck.class.php
⑤ 以管理員Manage模塊中的添加管理員為例,詳解執行流程:
首先交代一下本次操作會涉及到的目錄及文件:
- index.php
- configs/run.inc.php à 運行時index.php直接加載的文件
- configs/profile.inc.php- à 系統配置文件
- Smarty/Smarty.class.php- à Smarty模板引擎調用入口類
- Public/幾乎所有文件,其中較為重要Factory.class.php-à工廠類、TPL.class.php-à模板引擎類、DB.class.php-à數據庫工具類、Redirect.class.php-à跳轉類、Request.class.php-à請求處理類、Validate.class.php-à驗證類、Tool.class.php-à工具類等
- Controller/Action.class.php à 控制器頂級父類
- Controller/ManageAction.class.php- à 管理員控制器類
- Model/ManageModel.class.php à 管理員模型類
- Model/LevelModel.class.php à 等級模型類 (此類是對等級表進行增刪改查操作)
詳細執行流程如下:以請求參數http://localhost/Mall/index.php?a=manage&m=add,注意這里的請求時通過點擊新增管理員表單的submit按鈕后,直奔的地址,此時會把用戶名、密碼、確認密碼、等級等一起提交過來。故事就此展開……
首先服務器加載index.php;
里面只做了一件事,加載configs/run.inc.php
代碼:
1 require dirname(__FILE__).'/configs/run.inc.php';
然后run.inc.php文件里面做了以下主要操作:加載configs/profile.inc.php系統配置文件、加載Smarty/Smarty.class.php、自定義自動加載器_autoload()、調用Factory->setAction()實例化,由地址欄參數a=manage指定的ManageAction.class.php、最后再次調用Factory->setAction()->run(),即調用剛剛實例化的ManageAction對象的run()方法(這里的run()方法是繼承自Action.class.php),執行相應的方法,即執行的完成操作是$ManageAction->add()操作;
關鍵性代碼如下:(說明:以下只會貼出本次操作,該類下的相應方法,不是整個類)
Config/run.inc.php代碼如下:
1 <?php 2 3 session_start(); 4 5 //錯誤級別 6 7 error_reporting(E_ALL); 8 9 //網站根目錄 10 11 define('ROOT_PATH', substr(dirname(__FILE__),0,-8)); 12 13 //設置編碼 14 15 header('Content-Type: text/html;charset=utf-8'); 16 17 //設置時區 18 19 date_default_timezone_set('Asia/Shanghai'); 20 21 //引入Smarty配置文件 22 23 require ROOT_PATH.'/configs/profile.inc.php'; 24 25 //引入smarty引擎 26 27 require ROOT_PATH.'/smarty/Smarty.class.php'; 28 29 //自動加載類 30 31 function __autoload($className) { 32 33 if (substr($className,-6) == 'Action') { 34 35 require ROOT_PATH.'/controller/'.$className.'.class.php'; 36 37 } else if(substr($className,-5) == 'Model') { 38 39 require ROOT_PATH.'/model/'.$className.'.class.php'; 40 41 } else if (substr($className, -5) == 'Check') { 42 43 require ROOT_PATH.'/check/'.$className.'.class.php'; 44 45 } else { 46 47 require ROOT_PATH.'/public/'.$className.'.class.php'; 48 49 } 50 51 } 52 53 //單入口 54 55 Factory::setAction()->run(); 56 57 ?>
Factory.class.php代碼如下:
1 //簡單工廠類 2 3 class Factory{ 4 5 private static $_obj = null; 6 7 public static function setAction() { 8 9 $a = self::getA(); 10 11 if(!file_exists(ROOT_PATH.'/controller/'.$a.'Action.class.php')) $a = 'Index'; 12 13 eval('self::$_obj = new '.ucfirst($a).'Action();'); 14 15 return self::$_obj; 16 17 } 18 19 }
Action.class.php代碼如下:
1 //頂級控制器 2 3 class Action { 4 5 //模板對象 6 7 protected $tpl = null; 8 9 //模型對象 10 11 protected $model = null; 12 13 //跳轉對象 14 15 protected $redirect = null; 16 17 protected function __construct() { 18 19 $this->tpl = TPL::getInstance(); 20 21 $this->model = Factory::setModel(); 22 23 $this->redirect = Redirect::getInstance($this->tpl); 24 25 } 26 27 public function run() { 28 29 $m = isset($_GET['m']) ? $_GET['m'] : 'index'; 30 31 method_exists($this, $m) ? $this->$m() : $this->index(); 32 33 } 34 35 }
在實例化ManageAction時,由於與此相對應的Model和其他所需Model如這里LevelModel,同樣會通過Factory工廠類進行實例化(因為實例化Model放在了ManageAction的__construct()中)。
ManageAction.class.php代碼如下:
1 //管理員控制器 2 3 class ManageAction extends Action { 4 5 //等級對象 6 7 private $level = null; 8 9 10 11 public function __construct() { 12 13 parent::__construct(); 14 15 $this->level = new LevelModel(); 16 17 } 18 19 public function add() { 20 21 if (isset($_POST['send'])) $this->model->add() ? $this->redirect->succ('?a=manage', '恭喜您,管理員添加成功!') : $this->redirect->error('對不起,管理員添加失敗!'); 22 23 $this->tpl->assign('allLevel', Tool::setFormItem($this->level->findAll(), 'id', 'level_name')); 24 25 $this->tpl->display(SMARTY_ADMIN.'manage/add.tpl'); 26 27 } 28 29 }
A. 此時在add()方法中,就直接調用了levelModel的findAll()方法即取出所有等級(新增管理員時需要),而且把返回的等級信息通過TPL對象的assign注入到模板中去,最后加載模板顯示。
B. 用戶點擊新增管理員后,調用的是ManageModel的add()方法;
代碼如下:
1 if (isset($_POST['send'])) $this->model->add() ? $this->redirect->succ('?a=manage', '恭喜您,管理員添加成功!') : $this->redirect->error('對不起,管理員添加失敗!');
在實例化ManageModel中,進行了許多操作,如:獲取db的實例對象、Request請求處理類對象、ManageCheck驗證類對象、表字段羅列、數據表名字、請求數據等;
Model.class.php代碼如下:
1 class Model extends DB { 2 3 //數據庫類實例 4 5 protected $db = null; 6 7 //數據表字段 8 9 protected $fields = array(); 10 11 //數據表 12 13 protected $tables = array(); 14 15 //驗證對象 16 17 protected $check = null; 18 19 //limit 20 21 protected $limit = ''; 22 23 //請求數據 24 25 protected $_R = array(); 26 27 28 29 protected function __construct() { 30 31 $this->db = DB::getInstance(); 32 33 } 34 35 36 37 //獲取request對象 38 39 protected function getRequest() { 40 41 return Request::getInstance($this, $this->check); 42 43 } 44 45 46 47 //新增數據 48 49 protected function add(Array $postData) { 50 51 return $this->db->add($this->tables, $postData); 52 53 } 54 55 }
ManageModel.class.php代碼如下:
1 //控制器模型類 2 3 class ManageModel extends Model { 4 5 public function __construct() { 6 7 parent::__construct(); 8 9 $this->fields = array('id', 'user', 'pass', 'level', 'login_count', 'last_ip', 'last_time', 'reg_time'); 10 11 $this->tables = array(DB_PREFIX.'manage'); 12 13 $this->check = new ManageCheck(); 14 15 list( 16 17 $this->_R['id'], 18 19 $this->_R['user'], 20 21 $this->_R['pass'], 22 23 $this->_R['code'] 24 25 ) = $this->getRequest()->getParams(array( 26 27 isset($_GET['id']) ? $_GET['id'] : null, 28 29 isset($_POST['user']) ? $_POST['user'] : null, 30 31 isset($_POST['pass']) ? $_POST['pass'] : null, 32 33 isset($_POST['code']) ? $_POST['code'] : null 34 35 )); 36 37 } 38 39 40 41 public function add() { 42 43 $where = array("user='{$this->_R['user']}'"); 44 45 if (!$this->check->checkAdd($this, $where)) $this->check->error(); 46 47 $requestData = $this->getRequest()->filter($this->fields); 48 49 $requestData['pass'] = sha1($requestData['pass']); 50 51 $requestData['last_ip'] = Tool::getIP(); 52 53 $requestData['reg_time'] = Tool::getDate(); 54 55 return parent::add($requestData); 56 57 } 58 59 }
在執行model層的add()方法時,如上,首先使用驗證層對所提交數據,進行驗證,驗證失敗,跳轉到報錯頁面,否則繼續往下執行。其驗證過程封裝到ManageCheck.class.php類中;
Check.class.php代碼如下:
1 class Check extends Validate { 2 3 //驗證結果狀態 4 5 protected $flag = true; 6 7 //錯誤信息集 8 9 protected $message = array(); 10 11 //模板對象 12 13 private $_tpl = null; 14 15 16 17 public function __construct() { 18 19 $this->_tpl = TPL::getInstance(); 20 21 } 22 23 //顯示錯誤信息並返回 24 25 public function error($url = '') { 26 27 if (empty($url)) { 28 29 $this->_tpl->assign('message', $this->message); 30 31 $this->_tpl->assign('prev', Tool::getPrevPage()); 32 33 $this->_tpl->display(SMARTY_ADMIN.'public/error.tpl'); 34 35 exit; 36 37 } else { 38 39 Redirect::getInstance()->succ($url); 40 41 } 42 43 } 44 45 }
ManageCheck.class.php代碼如下:
1 //管理員驗證類 2 3 class ManageCheck extends Check { 4 5 //驗證新增數據 6 7 public function checkAdd(Model &$model, Array $params) { 8 9 if (self::isNullString($_POST['user'])) { 10 11 $this->message[] = '管理員用戶名不得為空!'; 12 13 $this->flag = false; 14 15 } 16 17 if (!self::checkStrLength($_POST['user'], 2, 'min')) { 18 19 $this->message[] = '管理員用戶名不得小於2位!'; 20 21 $this->flag = false; 22 23 } 24 25 if (!self::checkStrLength($_POST['user'], 20, 'max')) { 26 27 $this->message[] = '管理員用戶名不得大於20位!'; 28 29 $this->flag = false; 30 31 } 32 33 if (!self::checkStrLength($_POST['pass'], 6, 'min')) { 34 35 $this->message[] = '管理員密碼不得小於6位!'; 36 37 $this->flag = false; 38 39 } 40 41 if (!self::checkStrEqual($_POST['pass'], $_POST['notpass'])) { 42 43 $this->message[] = '確認密碼與密碼必須保持一致!'; 44 45 $this->flag = false; 46 47 } 48 49 if (self::isNullString($_POST['level'])) { 50 51 $this->message[] = '必須選擇管理員等級權限!'; 52 53 $this->flag = false; 54 55 } 56 57 if ($model->checkOne($params)) { 58 59 $this->message[] = '管理員用戶名已被占用!'; 60 61 $this->flag = false; 62 63 } 64 65 return $this->flag; 66 67 } 68 69 }
然后使用Request請求處理類,對提交數據,進行入庫前的過濾
代碼如下:
1 $requestData = $this->getRequest()->filter($this->fields);
最后,填充數據庫應有字段,如時間,用戶ip,密碼加密等
代碼如下:
1 $requestData['pass'] = sha1($requestData['pass']); 2 3 $requestData['last_ip'] = Tool::getIP(); 4 5 $requestData['reg_time'] = Tool::getDate();
然后通過父類調用DB對象中的add()方法,新增一條數據;
代碼如下:
1 return parent::add($requestData); 2 3 4 5 //新增數據 6 7 protected function add(Array $postData) { 8 9 return $this->db->add($this->tables, $postData); 10 11 }
DB.class.php關鍵代碼如下:
1 //新增 2 3 protected function add($tables, Array $postData) { 4 5 $addFields = array(); 6 7 $addData = array(); 8 9 foreach ($postData as $key=>$value) { 10 11 $addFields[] = $key; 12 13 $addData[] = $value; 14 15 } 16 17 $addFields = implode(',', $addFields); 18 19 $addData = implode("','", $addData); 20 21 $sql = "INSERT INTO $tables[0] ($addFields) VALUES ('$addData')"; 22 23 return $this->execute($sql)->rowCount(); 24 25 }
最后把是否新增成功的數據的結果,逐層從DB->Model->ManageModel->Controller反饋到ManageAtion的add方法中,如果新增成功跳轉到成功提示頁面,如果失敗跳轉到失敗提示頁面;
代碼如下:
1 if (isset($_POST['send'])) $this->model->add() ? $this->redirect->succ('?a=manage', '恭喜您,管理員添加成功!') : $this->redirect->error('對不起,管理員添加失敗!');
到此,整個執行流程執行完畢!
小結:整個執行流程按照非常嚴格的MVC的設計思想在進行,每一個層次的職責都非常明確。如controller控制器層,專門負責邏輯判斷,頁面跳轉等,是一個調度者。它可以調度model層去獲取數據,驗證數據、填充數據等,它還可以調度view層,把數據給顯示出來。
最后,整個框架的分析到這里就結束,接下來會對其中的重要模塊或功能進行總結。