概況
Yii2 一個高效安全的高性能PHP框架。mongodb 一個高性能分布式文檔存儲NOSQL數據庫。
關於mongodb與mysql的優缺點,應該都了解過。
mysql傳統關系數據庫,安全穩定、數據完整、資源文檔完善、使用群體多、支持事物,V5.7后支持原生Json速度不遜mongodb、分布式主從集群剛剛的……
mongodb新興NOSQL數據庫,Bson文檔存儲,支持原生Javascript、性能優異百千萬數據不在話下、內置GridFS Sharding海量存儲、熱數據持久化、分片部署……
參考:http://www.wtoutiao.com/p/19an83a.html
那到底mysql與mongodb性能對比如何呢,什么時候選擇mysql什么時候選擇mongodb呢,網上有很多自己search吧,這里我們講的是,將mongodb加入Yii2的擴展中,並使用經典的ActiveRecord操作數據。
等等,再說下什么是ActiveRecord
說ActiveRecord之前吶,先要知道ORM(Object Relational Mapping 對象關系映射)用於實現面向對象編程語言里不同類型系統的數據之間的轉換。從效果上說,它其實是創建了一個可在編程語言里使用的"虛擬對象數據庫"。ActiveRecord就是一個典型的ORM。Yii2 提供了DAO (Data Access Objects 數據訪問對象) ,可以方便的用$db->createCommand()完成任何數據庫相關的任務,包括復雜的業務SQL語句。甚至執行速度比ActiveRecord快很多,但是在這個金(敏)錢(捷)社(開)會(發)時代,開發速度才是我們解決的最主要問題。Yii2的ActiveRecord中,每個AR類代表一個數據表,其字段作為AR類的屬性,一個AR實例代表在表中的一行。常見的CRUD操作被作為AR類的方法執行。 我們使用更面向對象的方法處理我們的數據,so,開發速度就開到5擋了!
一.mongodb安裝
1.Windows安裝
下載最新的mongodb 地址:https://www.mongodb.com/download-center 本人使用版本V3.2
一路"下一步"完成安裝。mongd為服務端,mongo為客戶端。打開終端切換到bin目錄
終端中輸入:
mongod --logpath C:\data\dbConf\mongodb.log --logappend --dbpath C:\data\db --journal --bind_ip 127.0.0.1 --serviceName MongoDB --port 9898 --install
參數說明:
-logpath C:\data\dbConf\mongodb.log #設置日志path --dbpath C:\data\db #設置db path --journal #設置數據存儲格式 --bind_ip 127.0.0.1 #防止其他ip訪問 --port 9888 #27017修改端口 --install #install Windows service
其中bind_ip與port很重要,記得一定要修改,嗯!秘密噢!
當使用了--install后會安裝到Windows的系統服務中,如果要卸載mongodb服務,使用
sc delete mongodb
在Windows服務中開啟mongodb服務
在終端中輸入
mongo --port 9898
出現如下表示成功
2.Linux安裝
同理下載Linux版mongodb
wget https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.4.3.tgz
解壓
tar -zxvf mongodb-linux-x86_64-3.4.3.tgz
移動目錄到/usr/local/mongodb
mv mongodb-linux-x86_64-3.4.3/ /usr/local/mongodb
新建db,與log文件路徑,因為mongodb數據存儲在data的db目錄,但安裝啟動不會自動生成。我們把data新建在mongodb的同級目錄(放哪你自己決定)
mkdir -p data/db mkdir -p data/dbconf
啟動服務
cd bin/
./mongod --logpath /usr/local/mongodb/data/dbconf/mongodb.log --logappend --dbpath /usr/local/mongodb/data/db --bind_ip 127.0.0.1,201.104.104.104 --port 9898
查看data/dbconf/mongodb.log 顯示失敗
2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] MongoDB starting : pid=513 port=9898 dbpath=/usr/local/mongodb/data/db 64-bit host=u23dk88fzrZ 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] db version v3.4.3 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] git version: f07437fb5a6cca07c10bafa78365456eb1d6d5e1 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] allocator: tcmalloc 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] modules: none 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] build environment: 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] distarch: x86_64 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] target_arch: x86_64 2017-03-30T09:45:14.324+0800 I CONTROL [initandlisten] options: { net: { bindIp: "127.0.0.1,201.104.104.104", port: 9898 }, storage: { dbPath: "/usr/local/mongodb/data/db" }, systemLog: { destination: "file", path: "/usr/local/mongodb/data/dbconf/mongodb.log" } } 2017-03-30T09:45:14.347+0800 E NETWORK [initandlisten] listen(): bind() failed Cannot assign requested address for socket: 201.104.104.104:9898 2017-03-30T09:45:14.347+0800 E NETWORK [initandlisten] Failed to set up sockets during startup. 2017-03-30T09:45:14.347+0800 E STORAGE [initandlisten] Failed to set up listener: InternalError: Failed to set up sockets 2017-03-30T09:45:14.347+0800 I NETWORK [initandlisten] shutdown: going to close listening sockets... 2017-03-30T09:45:14.347+0800 I NETWORK [initandlisten] removing socket file: /tmp/mongodb-9898.sock 2017-03-30T09:45:14.347+0800 I NETWORK [initandlisten] shutdown: going to flush diaglog... 2017-03-30T09:45:14.347+0800 I CONTROL [initandlisten] now exiting 2017-03-30T09:45:14.347+0800 I CONTROL [initandlisten] shutting down with code:48
因為沒有讓防火牆放行端口9898,開放端口
iptables -A INPUT -p tcp -m tcp --dport 9898 -j ACCEPT
插曲:linux用的是Centos7,默認使用的firewall防火牆必須啟用iptables 鏈接:http://www.linuxidc.com/Linux/2015-05/117473.htm 、 http://blog.csdn.net/smstong/article/details/39317277
然后我們使用客戶端mongo連接
./mongo --port 9898
已經鏈接成功。
要以守護進程的形式運行mongodb,要添加fork參數:
./mongod --logpath /usr/local/mongodb/data/dbconf/mongodb.log --logappend --dbpath /usr/local/mongodb/data/db --bind_ip 127.0.0.1,201.104.104.104 --port 9898 --fork
3.安裝php mongodb擴展
下載地址:http://pecl.php.net/package/mongodb
*注: pecl提供了兩個package 一個是 http://pecl.php.net/package/mongodb 另外一個是 http://pecl.php.net/package/mongo, 目前后者不在更新已經提示:This package has been superseded, but is still maintained for bugs and security fixes.
Windows版的擴展就不多說了,找到對應的php版本.dll就可以了,我用的是php7.0.8
修改php.ini 加入ext=php_mongodb.dll
說說Linux版本擴展的安裝
這個是mongodb官網安裝插件的說明:https://github.com/mongodb/mongo-php-driver-legacy
下載最新版: 2017-03-20
wget http://pecl.php.net/get/mongodb-1.2.8.tgz
解壓:
tar vxzf mongodb-1.2.8.tgz
進入目錄,此時就要注意了如果php是自己編譯安裝,在編譯mongo之前的要帶上php的配置path
使用phpize命令添加外掛模塊, 什么是phpize? 鏈接: http://www.2cto.com/kf/201111/110140.html
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config make make install
貌似php7.0.8下編譯一直提示:/usr/local/php/bin/php: symbol lookup error: /home/wwwroot/tools/mongodb-1.2.8/modules/mongodb.so: undefined symbol: OPENSSL_init_ssl
解決php安裝mongodb.so 擴展提示 :mongodb.so: undefined symbol: OPENSSL_init_ssl
非php7.0.8版本編譯報錯,找了好久問題是安裝openssl版本的問題
首先我們查看phpinfo,看php -i 顯示的openssl支持的多少版本
OpenSSL support | enabled |
OpenSSL Library Version | OpenSSL 1.0.1e-fips 11 Feb 2013 |
OpenSSL Header Version | OpenSSL 1.0.1e-fips 11 Feb 2013 |
Openssl default config | /etc/pki/tls/openssl.cnf |
然后在終端查看openssl版本
OpenSSL 1.1.0 25 Aug 2016
問題就在這,因為我yum update過openssl,導致php編譯版本與系統現有版本不一致,使mongodb make的時候提示openssl保存有問題。
解決辦法:
恢復之前的openssl版本在編譯mongodb
通過find / -name openssl
/usr/bin/openssl OpenSSL 1.0.1e-fips 11 Feb 2013 /usr/local/bin/openssl OpenSSL 1.1.0 25 Aug 2016 /usr/local/include/openssl ------- /usr/local/ssl/bin/openssl OpenSSL 1.0.2c 12 Jun 2015 /usr/local/ssl/include/openssl -------- /usr/local/share/doc/openssl -------- /usr/include/openssl -------- /usr/lib64/openssl -------- /etc/pki/ca-trust/extracted/openssl--------
要讓終端默認使用1.0.1e版本,(Linux終端bash默認/usr/local/bin)
/usr/local/bin/ mv openssl openssl1.1.0 cp /usr/bin/openssl ./
修改/usr/local/include/openssl目錄中的.h文件為1.0.1e版本的文件,約72個*.h文件 (可查看opensslv.h文件獲取版本)
終端訪問:
# openssl version OpenSSL 1.0.1e-fips 11 Feb 2013
修改php.ini
extension=/usr/local/php/lib/php/extensions/no-debug-non-zts-20151012/mongodb.so
因php由php-fpm控制,因此需要重啟php-fpm,使php.ini配置生效。重啟php-fpm
參考 Linux 安裝基於(PHP5.5)memcache擴展
service php-fpm stop
./php-fpm -c /usr/local/php/etc/php2.ini
二. Yii2 配置mongodb-extensions
可通過composer安裝或手動安裝
1.通過composer安裝
官網安裝說明:http://www.yiiframework.com/doc-2.0/ext-mongodb-index.html
命令行切換到項目vendor同級目錄執行
php composer.phar require –prefer-dist yiisoft/yii2-mongodb “^2.1”
漫長等待…… 執行完成后composer.json文件和在\vendor\yiisoft下有對應的目錄生成
"yiisoft/yii2-mongodb": "~2.1.0"
2.通過手動安裝
下載地址:https://github.com/yiisoft/yii2-mongodb
下載后添加到 /vendor/yiisoft/yii2-mongodb
打開 vendor\yiisoft\extensions.php 在最底部添加:
'yiisoft/yii2-mongodb' => array ( 'name' => 'yiisoft/yii2-mongodb', 'version' => '2.1.0', 'alias' => array ( '@yii/mongodb' => $vendorDir . '/yiisoft/yii2-mongodb', ), ),
打開 vendor\composer\autoload_psr4.php 在最底部添加:
'yii\\mongodb\\' => array($vendorDir . '/yiisoft/yii2-mongodb'),
3.配置main文件
main-local.php 配置連接mongodb
'mongodb' => [ 'class' => 'yii\mongodb\Connection', //'class' => 'backend\models\core\mongodb\Mconnection', # 有賬戶的配置 //'dsn' => 'mongodb://demofancyecommerce:fdaVBDFS#fdfdtyg423DF23#$@localhost:27017/demofancyecommerce', # 無賬戶的配置 'dsn' => 'mongodb://127.0.0.1:9898/mongo_test', //'dsn' => 'mongodb://10.10.10.252:10001/erp,mongodb://10.10.10.252:10002/erp,mongodb://10.10.10.252:10004/erp?replicaSet=terry&readPreference=primaryPreferred', ],
4. 設置gii
設置gii生成器配置
$config['bootstrap'][] = 'mongogii'; $config['modules']['mongogii'] ['class']= 'yii\mongodb\model\Generator'; $config['modules']['mongogii']['allowedIPs'] =['127.0.0.1','201.104.104.104',"::1"];
三. ActiveRecord操作數據
這步我們要將用ActiveRecord進行數據的CURD操作
1.導入測試數據
如果你是在Windows下操作,強烈建議你用MongoVUE管理工具,很NB的一個mongodb管理工具。 好,將mysql中的數據入mongodb。
如果服務器Linux設置僅127.0.0.1可連接mongod服務端,可以參考SecureCRT端口映射轉發,轉發到本地對應的端口,使用 localhost:port 連接
查看插入的集合數據
2.生成models
在上一步,我們已經配置了mian文件和gii,同時也給測試庫的customer集合插入測試數據,現在用gii來生成models
直接訪問項目域+mongogii
生成的路徑在models/Customer.php 為了區分與comm里面的,我們將模型改名為MCustomer.php,查看代碼
namespace app\models; use Yii; /** * This is the model class for collection "customer". * * @property \MongoDB\BSON\ObjectID|string $_id * @property mixed $id * @property mixed $name * @property mixed $province * @property mixed $city * @property mixed $town * @property mixed $address * @property mixed $lng * @property mixed $lat * @property mixed $create_time */ class MCustomer extends \yii\mongodb\ActiveRecord { /** * @inheritdoc */ public static function collectionName() { return ['mongo_test', 'customer']; } /** * @inheritdoc */ public function attributes() { return [ '_id', 'id', 'name', 'province', 'city', 'town', 'address', 'lng', 'lat', 'create_time', 'status', ]; } /** * @inheritdoc * 參考 YII2,rules規則 */ public function rules() { return [ [['name', 'province', 'city', 'town', 'address'], 'required'], [['id', 'name', 'province', 'city', 'town', 'address', 'lng', 'lat', 'create_time', 'status'], 'safe'], [['province', 'city', 'town'], 'integer'], [['name'], 'string', 'max' => 20], ]; } /** * @inheritdoc */ public function attributeLabels() { return [ '_id' => 'ID', 'id' => 'Id', 'name' => 'Name', 'province' => 'Province', 'city' => 'City', 'town' => 'Town', 'address' => 'Address', 'lng' => 'Lng', 'lat' => 'Lat', 'create_time' => 'Create Time', ]; } }
其中rules規則是自己加的,因為mongogii僅生成‘safe’規則,rules驗證規則可參考: http://www.phpxs.com/post/3443/
3.控制器方法
MVC中Models已經有了,就剩Controller與View了,而最主要的就是Controller了,因為邏輯都放在控制器中了(按照大神的建議,邏輯代碼應該放到Models里面)。
其他什么都別說 然后我們吃着火鍋一起唱這歌!!!上代碼!!!
MongoController.php
/** * mongo controller */ namespace app\controllers; use Yii; class MongoController extends BaseController { public $pageSize = 10; /** * Index 列表首頁 */ public function actionIndex() { $search = $this->post('search'); $orderField = $this->post('orderField'); $orderDirection = $this->post('orderDirection'); $where = []; $orderby = 'id desc'; ##################### search ############################## if ($search['name']) { # 模糊搜索 $where['name'] = ['$regex' => $search['name']]; } if ($search['id']) { # int 類型匹配 $where['id'] = intval($search['id']); } ###################### orderby #################################### if ($orderField && $orderDirection) { $orderby = $orderField . " {$orderDirection}"; } ############### page ##################### if ($this->post('pageSize')) { $this->pageSize = $this->post('pageSize'); } $_GET['page'] = ''; if ($this->post('pageCurrent')) { $_GET['page'] = $this->post('pageCurrent'); } ####################### data #################### $data = \app\models\MCustomer::find()->where($where)->orderBy($orderby); $count = $data->count(); $pagesize = $this->pageSize; // 分頁大小 $pages = new \yii\data\Pagination(['totalCount' => $count, 'pageSize' => $pagesize]); $model = $data->offset($pages->offset)->limit($pages->limit)->all(); $pagge = $this->render('//' . 'default/page', ['pageSize' => $pagesize, 'pageTotal' => $count, 'page' => $_GET['page']]); return $this->display([ 'alist' => $model, 'pages' => $pages, 'search' => $search, 'pagge' => $pagge, ]); } /** * 刪除操作 */ public function actionDel() { $id = self::post('id'); # field id 與字段類型必須對應 (int) $_id = self::post('_id'); # mongoid # by _id $model = \app\models\MCustomer::findOne($_id); if ($model) { $model->delete(); # 物理刪除 } # by id if ($id) { $model = \app\models\MCustomer::find()->where(["id" => intval($id)])->one(); if ($model) { $model->status = 3; # 狀態刪除 $model->save(); } } if (!$model->errors) { self::UICallback(200, '刪除成功'); } } /** * 編輯、 新增 操作 */ public function actionEdit() { $id = $this->query('id'); $model = \app\models\MCustomer::find()->where(["id" => intval($id)])->one(); if (!$model) { $model = new \app\models\MCustomer(); } return $this->display('edit', [ 'model' => $model, ]); } /** * 編輯新增 保存save 操作 */ public function actionDataSave() { $_id = self::post('_id'); $post = \Yii::$app->request->post(); # 獲取省市區縣及address, 通過百度解析為lng、lat經緯度保存-- 演示暫無 $post['create_time'] = strtotime($post['create_time']); if ($_id) { # 編輯 $model = \app\models\MCustomer::findOne($_id); $model->attributes = $post; $model->save(); } else { # 新增 $model = new \app\models\MCustomer(); $model->attributes = $post; $model->save(); #$id= $model->attributes['id']; # 獲取插入數據的mongoid } if ($model->errors) { self::UICallback(300, '保存失敗', ['node' => $model->errors]); } else { self::UICallback(200, '保存成功', ['closeCurrent' => TRUE, 'tabid' => 'monnn']); } } }
views/mongo里面代碼:
index.php
<?php use yii\bootstrap\ActiveForm; ?> <div class="bjui-pageHeader"> <?php $form = ActiveForm::begin([ 'id' => 'pagerForm', 'method' => 'post', 'options' => ['data-toggle' => 'ajaxsearch'] ]); ?> <input type="hidden" name="pageSize" value="${model.pageSize}" placeholder=""> <input type="hidden" name="pageCurrent" value="${model.pageCurrent}"> <input type="hidden" name="orderField" value=""> <input type="hidden" name="orderDirection" value=""> <div class="bjui-searchBar"> <label>客戶姓名:</label> <?php echo yii\helpers\Html::input('text', 'search[name]', $search['name'], ['size' => 12, 'placeholder' => '客戶姓名']) ?> <label>客戶id:</label> <?php echo yii\helpers\Html::input('text', 'search[id]', $search['id'], ['size' => 12, 'placeholder' => '客戶ID信息']) ?> <button type="submit" class="btn-default" data-icon="search">查詢</button> <a href="/mongo/edit" class="btn btn-green" data-id="mongo_data" data-toggle="navtab" data-title="新增客戶" data-icon="plus">客戶</a> </div> <?php ActiveForm::end(); ?> </div> <div class="bjui-pageContent tableContent"> <table data-toggle="tablefixed" data-width="100%" data-nowrap="true"> <thead> <tr> <th data-order-field="id" >ID</th> <th data-order-field="name">姓名</th> <th>位置</th> <th>經緯度</th> <th width="370">操作</th> </tr> </thead> <tbody> <?php if (!empty($alist)) { foreach ($alist as $key => $value) { ?> <tr data-id=""> <td><?php echo $value['id']; ?></td> <td><?php echo $value['name']; ?></td> <td><?php echo $value['city']; ?><?php echo $value['address']; ?></td> <td><?php echo $value['lng']; ?>,<?php echo $value['lat']; ?></td> <td> <a href="/mongo/edit?id=<?php echo $value->id;?>" class="btn btn-orange" data-id="mongo_data" data-toggle="navtab" data-reload-warn="本頁已有打開的內容,確定將刷新本頁內容,是否繼續?" data-title="查看客戶信息" data-icon="edit">查看</a> <a href="/mongo/del" data-toggle="doajax" data-data="_id=<?php echo $value->{'_id'}?>&id=<?php echo $value['id'] ?>&_csrf=<?php echo Yii::$app->request->csrfToken; ?>" class="btn btn-red row-del" data-confirm-msg="確定要刪除該行信息嗎?" data-icon="remove">刪</a> </td> </tr> <?php } } ?> </tbody> </table> </div> <?php echo $pagge;
edit.php
<?php use yii\helpers\Html; use yii\bootstrap\ActiveForm; ?> <div class="bjui-pageContent"> <?php $form = ActiveForm::begin([ 'action' => '/mongo/data-save', 'options' => ['data-alertmsg' => 'false', 'data-toggle' => 'validate']]); ?> <?php echo Html::hiddenInput('_id', $model->{'_id'}); ?> <table class="table table-condensed table-hover" width="100%"> <tbody> <tr> <td colspan="2"> <label for="name" class="control-label x85">姓名:</label> <?php echo Html::input('text', 'name', $model->name, ['data-rule' => "required", 'placeholder' => '姓名']); ?> </td> </tr> <tr> <td> <label for="j_custom_birthday" class="control-label x85">現居地址:</label> <?php echo $this->AreaSelect(['name' => 'province', 'chkval' => $model['province'], 'area' => 'province', 'data-rule' => 'required', 'doption' => '請選擇省份', 'data-nextselect' => '#employee_j_form_city', 'data-refurl' => '/comm/area?id={value}']); ?>- <?php echo $this->AreaSelect(['name' => 'city', 'chkval' => $model['city'], 'area' => 'city', 'data-rule' => 'required', 'doption' => '請選擇城市', 'parentid' => $model['province'], 'id' => 'employee_j_form_city', 'data-nextselect' => '#employee_j_form_town', 'data-refurl' => '/comm/area?id={value}']); ?>- <?php echo $this->AreaSelect(['name' => 'town', 'chkval' => $model['town'], 'area' => 'town', 'data-rule' => 'required', 'doption' => '請選擇區縣', 'data-emptytxt' => '請選擇區縣', 'parentid' => $model['city'], 'id' => 'employee_j_form_town']); ?> </td> <td > <label for="address" class="control-label x85">詳細地址:</label> <?php echo Html::input('text', 'address', $model->address, ['data-rule' => "required", 'id' => 'address', 'size' => 25, 'placeholder' => '詳細地址']); ?> </td> </tr> <tr> <td colspan="2"> <label for="url" class="control-label x85">登記時間:</label> <?php echo Html::input('text', 'create_time', $model->create_time ? date("Y-m-d", $model->create_time) : '', ['data-rule' => "required", 'data-toggle' => 'datepicker', 'data-pattern' => 'yyyy-MM-dd H:m', 'placeholder' => '登記時間', 'data-min-date' => date('Y-m-d')]); ?> </td> </tr> </tbody> </table> <?php ActiveForm::end(); ?> </div> <div class="bjui-pageFooter"> <ul> <li><button type="button" class="btn-close" data-icon="close">取消</button></li> <li><button type="submit" class="btn-default" data-icon="save">保存</button></li> </ul> </div>
效果: