github: https://github.com/caohao-php...php
框架由3層架構構成,Controller、Model、View 以及1個可選的Dao層,支持PHP7,優勢以下:mysql
一、框架井井有條,靈活可擴展至4層架構、使用簡潔(開箱即用)、功能強大。nginx
二、基於 yaf 路由和 ycdatabase 框架,二者都是C語言擴展,保證了性能。git
三、ycdatabase 是強大的數據庫 ORM 框架,功能強大,安全可靠,支持便捷的主從配置,支持穩定、強大的數據庫鏈接池。具體參考 https://blog.csdn.net/caohao0...github
四、支持Redis代理,簡便的主從配置,支持穩定的redis鏈接池。具體參考:https://blog.csdn.net/caohao0...web
五、強大的日誌模塊、異常捕獲模塊,便捷高效的類庫、共用函數加載模塊redis
六、基於PHP7,代碼緩存opcache。sql
運行環境: PHP 7 shell
依賴擴展: yaf 、 ycdatabase 擴展 數據庫
建立日誌目錄:/data/app/logs ,目錄權限爲 php 項目可寫。
yaf 介紹以及安裝: https://github.com/laruence/yaf
ycdatabase 介紹以及安裝: https://github.com/caohao-php...
———————————————— |--- system //框架系統代碼 |--- conf //yaf配置路徑 |--- application //業務代碼 |----- config //配置目錄 |----- controller //控制器目錄 |------ User.php //User控制器 |----- core //框架基類目錄 |----- daos //DAO層目錄(可選) |----- errors //錯誤頁目錄 |----- helpers //公共函數目錄 |----- library //公共類庫目錄 |----- models //模型層目錄 |----- plugins //yaf路由插件目錄,路由先後鉤子,(接口驗簽在這裏) |----- third //第三方類庫 |----- views //視圖層
路由配置位於: framework/conf/application.ini
示例: http://localhost/index.php?c=...
詳細參考文檔: http://php.net/manual/zh/book...
參數 | 方式 | 描述 |
---|---|---|
c | GET | 控制器,路由到 /application/controller/User.php 文件 |
m | GET | 入口方法, User.php 裏面的 getUserInfoAction 方法 |
程序將被路由到 framework/application/controllers/User.php文件的 UserController::getUserInfoAction方法,其它路由細節參考Yaf框架
class UserController extends Core_Controller { public function getUserInfoAction() { } }
framework/application/plugins/Filter.php , 在 _auth 中寫入驗籤方法,全部接口都會在這裏校驗, 全部GET、POST等參數放在 $this->params 裏。
class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由以前調用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->_auth(); } //驗簽過程 protected function _auth() { //在這裏寫你的驗籤邏輯 } ... }
全部控制器位於:framework/application/controllers 目錄,全部控制器繼承自Core_Controller方法,裏面主要獲取GET/POST參數,以及返回數據的處理,Core_Controller繼承自 Yaf_Controller_Abstract, init方法會被自動調用,更多細節參考 Yaf 框架控制器。
class UserController extends Core_Controller { public function init() { parent::init(); //必須 $this->user_model = Loader::model('UserinfoModel'); //模型層 $this->util_log = Logger::get_instance('user_log'); //日誌 Loader::helper('common_helper'); //公共函數 $this->sample = Loader::library('Sample'); //加載類庫,加載的就是 framework/library/Sample.php 裏的Sample類 } //獲取用戶信息接口 public function getUserInfoAction() { $userId = $this->params['userid']; $token = $this->params['token']; if (empty($userId)) { $this->response_error(10000017, "user_id is empty"); } if (empty($token)) { $this->response_error(10000016, "token is empty"); } $userInfo = $this->user_model->getUserinfoByUserid($userId); if (empty($userInfo)) { $this->response_error(10000023, "未找到該用戶"); } if (empty($token) || $token != $userInfo['token']) { $this->response_error(10000024, "token 校驗失敗"); } $this->response_success($userInfo); } }
經過 $this->response_error(10000017, 'user_id is empty'); 返回錯誤結果
{ "errno":10000017, "errmsg":"user_id is empty" }
經過 $this->response_success($result); 返回JSON格式成功結果,格式以下:
{ "errno":0, "union":"", "amount":0, "session_key":"ZqwsC+Spy4C31ThvqkhOPg==", "open_id":"oXtwn4_mrS4zIxtSeV0yVT2sAuRo", "nickname":"涼之渡", "last_login_time":"2018-09-04 18:53:06", "regist_time":"2018-06-29 22:03:38", "user_id":6842811, "token":"c9bea5dee1f49488e2b4b4645ff3717e", "updatetime":"2018-09-04 18:53:06", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/xfxHib91BictV8T4ibRQAibD10DfoNpzpB1LBqZvRrz0icPkN0gdibZg62EPJL3KE1Y5wkPDRAhibibymnQCFgBM2nuiavA/132", "city":"Guangzhou", "province":"Guangdong", "country":"China", "appid":"wx385863ba15f573b6", "gender":1, "form_id":"" }
經過 Loader 加載器能夠加載模型層,公共類庫,公共函數,數據庫,緩存等對象, Logger 爲日誌類。
framework/application/models/Userinfo.php ,模型層,你能夠繼承自Core_Model, 也能夠不用,Core_Model 中封裝了許多經常使用SQL操做。最後一章會介紹各個函數用法。
經過 $this->user_model = Loader::model('UserinfoModel') 加載模型層,模型層與數據庫打交道。
class UserinfoModel extends Core_Model { public function __construct() { $this->db = Loader::database('default'); $this->util_log = Logger::get_instance('userinfo_log'); } function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data['appid'] = $appid; $data['user_id'] = $userid; $data['open_id'] = $open_id; $data['session_key'] = $session_key; $data['last_login_time'] = $data['regist_time'] = date('Y-m-d H:i:s', time()); $data['token'] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert("user_info", $data); if ($ret != -1) { return $data['token']; } else { $this->util_log->LogError("error to register_user, DATA=[".json_encode($data)."]"); return false; } } ... }
若是你習慣了4層結構,你能夠加載Dao層,做爲與數據庫交互的層,而model層做爲業務層。這個時候 Model 最好不要繼承 Core_Model,而由 Dao 層來繼承。
framework/application/daos/UserinfoDao.php ,數據庫交互層,你能夠繼承自Core_Model, 也能夠不用,Core_Model 中封裝了許多經常使用SQL操做。最後一章會介紹各個函數用法。
經過 $this->user_dao = Loader::dao('UserinfoDao') 加載dao層,咱們建議一個數據庫對應一個Dao層。
加載 redis 緩存: Loader::redis('default_master'); 參數爲framework/application/config/redis.php 配置鍵值,以下:
$redis_conf['default_master']['host'] = '127.0.0.1'; $redis_conf['default_master']['port'] = 6379; $redis_conf['default_slave']['host'] = '/tmp/redis_pool.sock'; //unix socket redis鏈接池,須要配置 openresty-pool/conf/nginx.conf,並開啓代理,具體參考 https://blog.csdn.net/caohao0591/article/details/85679702 $redis_conf['userinfo']['host'] = '127.0.0.1'; $redis_conf['userinfo']['port'] = 6379; return $redis_conf;
使用例子:
$redis = Loader::redis("default_master"); //主寫 $redis->set("pre_redis_user_${userid}", serialize($result)); $redis->expire("pre_redis_user_${userid}", 3600); $redis = Loader::redis("default_slave"); //從讀 $data = $redis->get("pre_redis_user_${userid}");
鏈接池配置 openresty-pool/conf/nginx.conf :
worker_processes 1; #nginx worker 數量 error_log logs/error.log; #指定錯誤日誌文件路徑 events { worker_connections 1024; } stream { lua_code_cache on; lua_check_client_abort on; server { listen unix:/tmp/redis_pool.sock; content_by_lua_block { local redis_pool = require "redis_pool" pool = redis_pool:new({ip = "127.0.0.1", port = 6380, auth = "password"}) pool:run() } } server { listen unix:/var/run/mysql_sock/mysql_user_pool.sock; content_by_lua_block { local mysql_pool = require "mysql_pool" local config = {host = "127.0.0.1", user = "root", password = "test123123", database = "userinfo", timeout = 2000, max_idle_timeout = 10000, pool_size = 200} pool = mysql_pool:new(config) pool:run() } } }
數據庫加載: Loader::database("default"); 參數爲 framework/application/config/database.php 裏配置鍵值,以下:
$db['default']['unix_socket'] = '/var/run/mysql_sock/mysql_user_pool.sock'; //unix socket 數據庫鏈接池,具體使用參考 https://blog.csdn.net/caohao0591/article/details/85255704 $db['default']['pconnect'] = FALSE; $db['default']['db_debug'] = TRUE; $db['default']['char_set'] = 'utf8'; $db['default']['dbcollat'] = 'utf8_general_ci'; $db['default']['autoinit'] = FALSE; $db['payinfo_master']['host'] = '127.0.0.1'; //地址 $db['payinfo_master']['username'] = 'root'; //用戶名 $db['payinfo_master']['password'] = 'test123123'; //密碼 $db['payinfo_master']['dbname'] = 'payinfo'; //數據庫名 $db['payinfo_master']['pconnect'] = FALSE; //是否鏈接池 $db['payinfo_master']['db_debug'] = TRUE; //debug標誌,線上關閉,打開後,異常SQL會顯示到頁面,不安全,僅在測試時打開,(注意,上線必定得將 db_debug 置爲 FALSE,不然必定機率可能暴露數據庫配置) $db['payinfo_master']['char_set'] = 'utf8'; $db['payinfo_master']['dbcollat'] = 'utf8_general_ci'; $db['payinfo_master']['autoinit'] = FALSE; //自動初始化,Loader的時候就鏈接,建議關閉 $db['payinfo_master']['port'] = 3306; $db['payinfo_slave']['host'] = '192.168.0.7'; $db['payinfo_slave']['username'] = 'root'; $db['payinfo_slave']['password'] = 'test123123'; $db['payinfo_slave']['dbname'] = 'payinfo'; $db['payinfo_slave']['pconnect'] = FALSE; $db['payinfo_slave']['db_debug'] = TRUE; $db['payinfo_slave']['char_set'] = 'utf8'; $db['payinfo_slave']['dbcollat'] = 'utf8_general_ci'; $db['payinfo_slave']['autoinit'] = FALSE; $db['payinfo_slave']['port'] = 3306;
$data = $this->db->query("select * from user_info where country='China' limit 3");
$data = $this->db->get("user_info", ['regist_time[<]' => '2018-06-30 15:48:39', 'gender' => 1, 'country' => 'China', 'city[!]' => null, 'ORDER' => [ "user_id", "regist_time" => "DESC", "amount" => "ASC" ], 'LIMIT' => 10], "user_id,nickname,city"); echo json_encode($data);exit;
[ { "nickname":"芒果", "user_id":6818810, "city":"Yichun" }, { "nickname":"Smile、格調", "user_id":6860814, "city":"Guangzhou" }, { "nickname":"Yang", "user_id":6870818, "city":"Hengyang" }, { "nickname":"涼之渡", "user_id":7481824, "city":"Guangzhou" } ]
$data = $this->db->get("user_info", ['regist_time[<]' => '2018-06-30 15:48:39', 'gender' => 1, 'country' => 'China', 'city[!]' => null, 'ORDER' => [ "user_id", "regist_time" => "DESC", "amount" => "ASC" ], 'LIMIT' => 10], "nickname"); echo json_encode($data);exit;
[ "芒果", "Smile、格調", "Yang", "涼之渡" ]
$data = $this->db->get_one("user_info", ['user_id' => 6818810]);
{ "union":null, "amount":0, "session_key":"Et1yjxbEfRqVmCVsYf5qzA==", "open_id":"oXtwn4wkPO4FhHmkan097DpFobvA", "nickname":"芒果", "last_login_time":"2018-10-04 16:01:27", "regist_time":"2018-06-29 21:24:45", "user_id":6818810, "token":"5a350bc05bbbd9556f719a0b8cf2a5ed", "updatetime":"2018-10-04 16:01:27", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", "city":"Yichun", "province":"Jiangxi", "country":"China", "appid":"wx385863ba15f573b6", "gender":1, "form_id":"" }
function register_user($appid, $userid, $open_id, $session_key) { $data = array(); $data['appid'] = $appid; $data['user_id'] = $userid; $data['open_id'] = $open_id; $data['session_key'] = $session_key; $data['last_login_time'] = $data['regist_time'] = date('Y-m-d H:i:s', time()); $data['token'] = md5(TOKEN_GENERATE_KEY . time() . $userid . $session_key); $ret = $this->db->insert("user_info", $data); if ($ret != -1) { return $data['token']; } else { $this->util_log->LogError("error to register_user, DATA=[".json_encode($data)."]"); return false; } }
function update_user($userid, $update_data) { $redis = Loader::redis("userinfo"); $redis->del("pre_redis_user_info_" . $userid); $ret = $this->db->update("user_info", ["user_id" => $userid], $update_data); if ($ret != -1) { return true; } else { $this->util_log->LogError("error to update_user, DATA=[".json_encode($update_data)."]"); return false; } }
$ret = $this->db->delete("user_info", ["user_id" => 7339820]);
經過 $this->db->get_ycdb(); 能夠獲取ycdb句柄進行更多數據庫操做, ycdb 的使用教程以下:
英文: https://github.com/caohao-php...
中文: https://blog.csdn.net/caohao0...
經過 Loader::config('xxxxx'); 加載 /application/config/xxxxx.php 的配置。例如:
$config = Loader::config('config'); var_dump($config);
全部的公共類庫位於superci/application/library目錄,可是注意的是, 若是你的類位於library子目錄下面,你的類必須用下劃線"_"分隔;
$this->sample = Loader::library('Sample');
加載的就是 framework/application/library/Sample.php 中的 Sample類。
$this->ip_location = Loader::library('Ip_Location');
加載的是 framework/application/library/Ip/Location.php 中的Ip_Location類
全部的公共類庫位於superci/application/helpers目錄,經過 Loader::helper('common_helper'); 方法包含進來。
日誌使用方法以下:
$this->util_log = Logger::get_instance('userinfo'); $this->util_log->LogInfo("register success"); $this->util_log->LogError("not find userinfo");
日誌級別:
const DEBUG = 'DEBUG'; /* 級別爲 1 , 調試日誌, 當 DEBUG = 1 的時候纔會打印調試 */ const INFO = 'INFO'; /* 級別爲 2 , 應用信息記錄, 與業務相關, 這裏能夠添加統計信息 */ const NOTICE = 'NOTICE'; /* 級別爲 3 , 提示日誌, 用戶不當操做,或者惡意刷頻等行爲,比INFO級別高,可是不須要報告*/ const WARN = 'WARN'; /* 級別爲 4 , 警告, 應該在這個時候進行一些修復性的工做,系統能夠繼續運行下去 */ const ERROR = 'ERROR'; /* 級別爲 5 , 錯誤, 能夠進行一些修復性的工做,但沒法肯定系統會正常的工做下去,系統在之後的某個階段, 極可能由於當前的這個問題,致使一個沒法修復的錯誤(例如宕機),但也可能一直工做到中止有不出現嚴重問題 */ const FATAL = 'FATAL'; /* 級別爲 6 , 嚴重錯誤, 這種錯誤已經沒法修復,而且若是系統繼續運行下去的話,能夠確定必然會愈來愈亂, 這時候採起的最好的措施不是試圖將系統狀態恢復到正常,而是儘量的保留有效數據並中止運行 */
FATAL和ERROR級別日誌文件以 .wf 結尾, DEBUG級別日誌文件以.debug結尾,日誌目錄存放於 /data/app/localhost 下面,localhost爲你的項目域名,好比:
[root@gzapi: /data/app/logs/localhost]# ls userinfo.20190211.log userinfo.20190211.log.wf
日誌格式: [日誌級別] [時間] [錯誤代碼] [文件|行數] [ip] [uri] [referer] [cookie] [統計信息] "內容"
[INFO] [2019-02-11 18:57:01] - - [218.30.116.8] - - - [] "register success" [ERROR] [2019-02-11 18:57:01] [0] [index.php|23 => | => User.php|35 => Userinfo.php|93] [218.30.116.8] [/index.php?c=user&m=getUserInfo&userid=6842811&token=c9bea5dee1f49488e2b4b4645ff3717e] [] [] - "not find userinfo"
視圖層參考yaf視圖渲染那部分, 我沒有寫案例。
傳統的Web應用, 一個應用隨着業務快速增加, 開發人員的流轉, 就會慢慢的進入一個惡性循環, 代碼量上只有加法沒有了減法. 由於隨着系統變複雜, 牽一髮就會動全局, 而新來的維護者, 對原有的體系並無那麼多時間給他讓他全面掌握. 即便有這麼多時間, 要想掌握之前那麼多的維護者的思惟的結合, 也不是一件容易的事情…
那麼, 長次以往, 這個系統將會愈來愈不可維護…. 到一個大型應用進入這個惡性循環, 那麼等待他的只有重構了.
那麼, 能不能對這個系統作解耦呢? 咱們已經作了不少解耦了, 數據, 中間件, 業務, 邏輯, 等等, 各類分層. 但到Web應用這塊, 還能怎麼分呢, MVC咱們已經作過了….
目前比較流行的解決方案是微服務,它可讓咱們的系統儘量快地響應變化,微服務是指開發一個單個小型的但有業務功能的服務,每一個服務都有本身的處理和輕量通信機制,能夠部署在單個或多個服務器上。微服務也指一種種鬆耦合的、有必定的有界上下文的面向服務架構。也就是說,若是每一個服務都要同時修改,那麼它們就不是微服務,由於它們緊耦合在一塊兒;若是你須要掌握一個服務太多的上下文場景使用條件,那麼它就是一個有上下文邊界的服務,這個定義來自DDD領域驅動設計。
相對於單體架構和SOA,它的主要特色是組件化、鬆耦合、自治、去中心化,體如今如下幾個方面:
服務粒度要小,而每一個服務是針對一個單一職責的業務能力的封裝,專一作好一件事情。
每一個服務可以獨立被部署並運行在一個進程內。這種運行和部署方式可以賦予系統靈活的代碼組織方式和發佈節奏,使得快速交付和應對變化成爲可能。
技術選型靈活,不受遺留系統技術約束。合適的業務問題選擇合適的技術能夠獨立演化。服務與服務之間採起與語言無關的API進行集成。相對單體架構,微服務架構是更面向業務創新的一種架構模式。
團隊對服務的整個生命週期負責,工做在獨立的上下文中,本身決策本身治理,而不須要統一的指揮中心。團隊和團隊之間經過鬆散的社區部落進行銜接。
咱們能夠看到整個微服務的思想就如咱們如今面對信息爆炸、知識爆炸是同樣的:經過解耦咱們所作的事情,分而治之以減小沒必要要的損耗,使得整個複雜的系統和組織可以快速的應對變化。
微服務包含的東西很是多,這裏咱們只討論RPC服務框架,ycroute框架基於Yar擴展爲咱們提供了RPC跨網絡的服務調用基礎,Yar是一個很是輕量級的RPC框架, 使用很是簡單, 對於Server端和Soap使用方法很像,而對於客戶端,你能夠像調用本地對象的函數同樣,調用遠程的函數。
擴展: yar.so
擴展: msgpack.so 可選,一個高效的二進制打包協議,用於客戶端和服務端之間包傳輸,還能夠選php、json, 若是要使用Msgpack作爲打包協議, 就須要安裝這個擴展。
咱們在 framework/application/controllers/Rpcserver.php 中將 Model 層做爲服務,提供給遠程的其它程序調用,RPC Client 即可以像調用本地函數同樣,調用遠程的服務,以下咱們將 UserinfoModel 和 TradeModel 兩個模型層提供給遠程程序調用。
class RpcserverController extends Core_Controller { public function init() { parent::init(); //必須 } //用戶信息服務 public function userinfoModelAction() { $user_model = Loader::model('UserinfoModel'); //模型層 $yar_server = new Yar_server($user_model); $yar_server->handle(); exit; } //支付服務 public function tradeModelAction() { $trade_model = Loader::model('TradeModel'); //模型層 $yar_server = new Yar_server($trade_model); $yar_server->handle(); exit; } }
上面一共提供了2個服務,UserinfoModel 和 TradeModel 分別經過http://localhost/index.php?c=... 和 http://localhost/index.php?c=... 來訪問,咱們來看看 UserinfoModel 一共有哪些服務:
從上圖能夠看到,UserinfoModel 類的全部 public 方法都會被當作服務提供,包括他繼承的父類 public 方法。
爲了安全,咱們最好對客戶端發起的RPC服務請求作校驗。在 framework/application/plugins/Filter.php 中作校驗:
class FilterPlugin extends Yaf_Plugin_Abstract { var $params; //路由以前調用 public function routerStartUp ( Yaf_Request_Abstract $request , Yaf_Response_Abstract $response) { $this->params = & $request->getParams(); $this->_auth(); if(!empty($this->params['rpc'])) { $this->_rpc_auth(); //rpc 調用校驗 } } //rpc調用校驗 protected function _rpc_auth() { $signature = $this->get_rpc_signature($this->params); if($signature != $this->params['signature']) { $this->response_error(1, 'check failed'); } } //rpc簽名計算,不要改函數名,在RPC客戶端中 system/YarClientProxy.php 咱們也會用到這個函數,作簽名。 public function get_rpc_signature($params) { $secret = 'MJCISDYFYHHNKBCOVIUHFUIHCQWE'; unset($params['signature']); ksort($params); reset($params); unset($auth_params['callback']); unset($auth_params['_']); $str = $secret; foreach ($params as $value) { $str = $str . trim($value); } return md5($str); } ... }
切記不要修改簽名生成函數 get_rpc_signature 的名字和參數,由於在 RPC Client 咱們也會利用這個函數作簽名,若是須要修改,請在 system/YarClientProxy.php 中作相應修改,以保證客戶端和服務器之間的調用正常。
yar 除了支持 http 以外,還支持tcp, unix domain socket傳輸協議,不過ycroute中只用了 http ,固然 http 也能夠開啓 keepalive 以得到更高的傳輸性能,只不過相比 socket, http 協議仍是多了很多的協議頭部的開銷。
擴展: yar.so
擴展: msgpack.so 可選,一個高效的二進制打包協議,用於客戶端和服務端之間包傳輸,還能夠選php、json, 若是要使用Msgpack作爲打包協議, 就須要安裝這個擴展。
例子:
class UserController extends Core_Controller { ... //獲取用戶信息(從遠程) public function getUserInfoByRemoteAction() { $userId = $this->params['userid']; if (empty($userId)) { $this->response_error(10000017, "user_id is empty"); } $model = Loader::remote_model('UserinfoModel'); $userInfo = $model->getUserinfoByUserid($userId); $this->response_success($userInfo); } ... }
經過 $model = Loader::remote_model('UserinfoModel'); 能夠獲取遠程 UserinfoModel,參數是framework/application/config/rpc.php配置裏的鍵值:
$remote_config['UserinfoModel']['url'] = "http://localhost/index.php?c=rpcserver&m=userinfoModel&rpc=true"; //服務地址 $remote_config['UserinfoModel']['packager'] = FALSE; //RPC包類型,FALSE則選擇默認,能夠爲 "json", "msgpack", "php", msgpack 須要安裝擴展 $remote_config['UserinfoModel']['persitent'] = FALSE; //是否長連接,須要服務端支持keepalive $remote_config['UserinfoModel']['connect_timeout'] = 1000; //鏈接超時(毫秒),默認 1秒 $remote_config['UserinfoModel']['timeout'] = 5000; //調用超時(毫秒), 默認 5 秒 $remote_config['UserinfoModel']['debug'] = TRUE; //DEBUG模式,調用異常是否會打印到屏幕,線上關閉 $remote_config['TradeModel']['url'] = "http://localhost/index.php?c=rpcserver&m=tradeModel&rpc=true"; $remote_config['TradeModel']['packager'] = FALSE; $remote_config['TradeModel']['persitent'] = FALSE; $remote_config['TradeModel']['connect_timeout'] = 1000; $remote_config['TradeModel']['timeout'] = 5000; $remote_config['TradeModel']['debug'] = TRUE;
這樣,咱們就能夠把 model 當成本地對象同樣調用遠程 UserinfoModel 的成員方法。
調用遠程服務的時候,system/YarClientProxy.php 會從配置中獲取服務的 url, 而後調用 FilterPlugin::get_rpc_signature 方法對 URL 作簽名,並將簽名參數拼接到 url 結尾,發起調用。
class YarClientProxy { ... public static function get_signatured_url($url) { $get = array(); $t = parse_url($url, PHP_URL_QUERY); parse_str($t, $get); $get['timestamp'] = time(); $get['auth'] = rand(11111111, 9999999999); $signature = FilterPlugin::get_rpc_signature($get); return $url . "×tamp=" . $get['timestamp'] . "&auth=" . $get['auth'] . "&signature=" . $signature; } ... }
日誌位於 /data/app/logs/localhost 下,localhost 爲項目域名。
[root@gzapi: /data/app/logs/localhost]# ls yar_client_proxy.20190214.log.wf
[ERROR] [2019-02-14 18:57:13] [0] [index.php|23 => | => User.php|61 => YarClientProxy.php|46] [218.30.116.3] [/index.php?c=user&m=getUserInfoByRemote&userid=6818810&token=c9bea5dee1f49488e2b4b4645ff3717e1] [] [] - "yar_client_call_error URL=[http://tr.gaoqu.site/index.ph...] , Remote_model=[UserinfoModel] Func=[getUserinfoByUserid] Exception=[server responsed non-200 code '500']"
yar框架支持並行調用,能夠同時調用多個服務,這樣能夠充分利用CPU性能,避免IO等待,提高系統性能,按照yar的流程,你首先得一個個註冊服務,而後發送註冊的調用,而後reset 重置調用。在ycroute 中,一個函數就能夠了。
用 Loader::concurrent_call($call_params); 來並行調用RPC服務, 其中 call_params是調用參數數組。
以下數組包含4個元素,每一個調用都包含 model, method 兩個必輸參數,以及 parameters, callback , error_callback 三個可選參數。
class UserController extends Core_Controller { //獲取用戶信息(並行遠程調用) public function multipleGetUsersInfoByRemoteAction() { $userId = $this->params['userid']; $call_params = array(); $call_params[] = ['model' => 'UserinfoModel', 'method' => 'getUserinfoByUserid', 'parameters' => array($userId), "callback" => array($this, 'callback1')]; $call_params[] = ['model' => 'UserinfoModel', 'method' => 'getUserInUserids', 'parameters' => array(array(6860814, 6870818)), "callback" => array($this, 'callback2'), "error_callback" => array($this, 'error_callback')]; $call_params[] = ['model' => 'UserinfoModel', 'method' => 'getUserByName', 'parameters' => array('CH.smallhow')]; //不存在的方法 $call_params[] = ['model' => 'UserinfoModel', 'method' => 'unknownMethod', 'parameters' => array(), "error_callback" => array($this, 'error_callback')]; Loader::concurrent_call($call_params); echo json_encode($this->retval); exit; } //回調函數1 public function callback1($retval, $callinfo) { $this->retval['callback1']['retval'] = $retval; $this->retval['callback1']['callinfo'] = $callinfo; } //回調函數2 public function callback2($retval, $callinfo) { $this->retval['callback2']['retval'] = $retval; $this->retval['callback2']['callinfo'] = $callinfo; } //錯誤回調 public function error_callback($type, $error, $callinfo) { $tmp['type'] = $type; $tmp['error'] = $error; $tmp['callinfo'] = $callinfo; $this->retval['error_callback'][] = $tmp; } }
我特地將第4個調用的method設置一個不存在的函數,你們能夠看下上面的並行調用的結果:
{ "error_callback":[ { "type":4, "error":"call to undefined api ::unknownMethod()", "callinfo":{ "sequence":4, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=5930400101&signature=fc0ed911c624d9176523544421a0248d", "method":"unknownMethod" } } ], "callback1":{ "retval":{ "user_id":"6818810", "appid":"wx385863ba15f573b6", "open_id":"oXtwn4wkPO4FhHmkan097DpFobvA", "union":null, "session_key":"Et1yjxbEfRqVmCVsYf5qzA==", "nickname":"芒果", "city":"Yichun", "province":"Jiangxi", "country":"China", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/DYAIOgq83epqg7FwyBUGd5xMXxLQXgW2TDEBhnNjPVla8GmKiccP0pFiaLK1BGpAJDMiaoyGHR9Nib2icIX9Na4Or0g/132", "gender":"1", "form_id":"", "token":"5a350bc05bbbd9556f719a0b8cf2a5ed", "amount":"0", "last_login_time":"2018-10-04 16:01:27", "regist_time":"2018-06-29 21:24:45", "updatetime":"2018-10-04 16:01:27" }, "callinfo":{ "sequence":1, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=8384256613&signature=c0f9c944ae070d2eb38c8e9638723a2e", "method":"getUserinfoByUserid" } }, "callback2":{ "retval":{ "6860814":{ "user_id":"6860814", "nickname":"Smile、格調", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTKNE5mFLk33q690Xl1N6mrehQr0ggasgk8Y4cuaUJt4CNHORwq8rVjwET7H06F3aDjU5UiczjpD4nw/132", "city":"Guangzhou" }, "6870818":{ "user_id":"6870818", "nickname":"Yang", "avatar_url":"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTLTKBoU1tdRicImnUHyr43FdMulSHRhAlsQwuYgAyOlrwQaLGRoFEHbgfVuyEV1K1VU2NMmm0slS4w/132", "city":"Hengyang" } }, "callinfo":{ "sequence":2, "uri":"http://tr.gaoqu.site/index.php?c=rpcserver&m=userinfoModel&rpc=true×tamp=1550142590&auth=7249482640&signature=26c419450bb4747ac166fbaa4a242b77", "method":"getUserInUserids" } } }
$this->redis_conf_path = 'default_master'; //用到快速緩存時,須要在 __construct 構造函數中加上 redis 緩存配置
/** * 插入表記錄 * @param string table 表名 * @param array data 表數據 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 */ public function insert_table($table, $data, $redis_key = ""); /** * 更新表記錄 * @param string table 表名 * @param array where 查詢條件 * @param array data 更新數據 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 */ public function update_table($table, $where, $data, $redis_key = ""); /** * 替換表記錄 * @param string table 表名 * @param array data 替換數據 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 */ public function replace_table($table, $data, $redis_key = ""); /** * 刪除表記錄 * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis緩存鍵值, 可空, 非空時清理鍵值緩存 */ public function delete_table($table, $where, $redis_key = ""); /** * 獲取表數據 * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 * @param int redis_expire redis 緩存到期時長(秒) * @param boolean set_empty_flag 是否標註空值,若是標註空值,在表記錄更新以後,必定記得清理空值標記緩存 */ public function get_table_data($table, $where = array(), $redis_key = "", $redis_expire = 600, $set_empty_flag = true); /** * 根據key獲取表記錄 * @param string table 表名 * @param string key 鍵名 * @param string value 鍵值 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 * @param int redis_expire redis 緩存到期時長(秒) * @param boolean set_empty_flag 是否標註空值,若是標註空值,在表記錄更新以後,必定記得清理空值標記緩存 */ public function get_table_data_by_key($table, $key, $value, $redis_key = "", $redis_expire = 300, $set_empty_flag = true); /** * 獲取一條表數據 * @param string table 表名 * @param array where 查詢條件 * @param string redis_key redis 緩存鍵值, 可空, 非空時清理鍵值緩存 * @param int redis_expire redis 緩存到期時長(秒) * @param boolean set_empty_flag 是否標註空值,若是標註空值,在表記錄更新以後,必定記得清理空值標記緩存 */ public function get_one_table_data($table, $where, $redis_key = "", $redis_expire = 600, $set_empty_flag = true);