目录
- 框架介绍
- 运行环境
- 代码结构
- 路由配置
- 过滤验签
- 控制层
- 加载器
- 模型层
- 数据交互Dao层(可选)
- Redis缓存操作
- 数据库操作
- 配置加载
- 公共类加载
- 公共函数
- 日志模块
- 视图层
- RPC 介绍 - 像调用本地函数一样调用远程函数
- RPC Server
- RPC Client
- RPC 并行调用
- 附录 - Core_Model 中的辅助极速开发函数
框架介绍
框架由3层架构构成,Controller、Model、View 以及1个可选的Dao层,支持PHP7,优点如下:
1、框架层次分明,灵活可扩展到4层架构、使用简洁(开箱即用)、功能强大。
2、基于 yaf 路由和 ycdatabase 框架,两者都是C语言扩展,保证了性能。
3、ycdatabase 是强大的数据库 ORM 框架,功能强大,安全可靠,支持便捷的主从配置,支持稳定、强大的数据库连接池。参考 https://blog.csdn.net/caohao0591/article/details/85255704
4、支持Redis代理,简便的主从配置,稳定的redis连接池支持。https://blog.csdn.net/caohao0591/article/details/85679702
5、强大的日志模块、异常捕获模块,便捷高效的类库、共用函数加载模块
6、基于PHP7,代码缓存opcache。
GitHub下载地址: https://github.com/caohao-php/ycroute
运行环境
运行环境: PHP 7
依赖扩展: yaf 、 ycdatabase 扩展
创建日志目录:/data/app/logs ,目录权限为 php 项目可写。
yaf 介绍以及安装: https://github.com/laruence/yaf
ycdatabase 介绍以及安装: https://github.com/caohao-php/ycdatabase
代码结构
————————————————
|--- 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=user&m=getUserInfo&userid=6842811&token=c9bea5dee1f49488e2b4b4645ff3717e
详细参考文档: http://php.net/manual/zh/book.yaf.php
控制器由参数c决定,动作有 m 决定。
参数 | 方式 | 描述 |
---|---|---|
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;
}
}
...
}
数据交互Dao层(可选)
如果你习惯了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 缓存操作
加载 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'][0]['host'] = '/tmp/redis_pool.sock';
//unix socket redis连接池,需要配置 openresty-pool/conf/nginx.conf,并开启代理,具体参考 https://blog.csdn.net/caohao0591/article/details/85679702
$redis_conf['userinfo']['master']['host'] = '127.0.0.1';
$redis_conf['userinfo']['master']['port'] = 6379;
$redis_conf['userinfo']['master']['auth'] = 'password';
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;
原生SQL:
$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/ycdatabase
中文: https://blog.csdn.net/caohao0591/article/details/84390713
配置加载
通过 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"
VIEW层
视图层参考yaf视图渲染那部分, 我没有写案例。
附录 - Core_Model 中的辅助极速开发函数(不关心可以跳过)
$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 string $column 数据库表字段,可空
* @param boolean set_empty_flag 是否将空值写入缓存,防止数据库击穿,默认为是
*/
public function get_table_data($table, $where = array(), $redis_key = "", $redis_expire = 600, $column = "*", $set_empty_flag = true);
/**
* 分页获取表数据
* @param string table 表名
* @param array where 查询条件
* @param array page - 页数,从 1 开始
* @param array page_size - 每页条数,默认为 10 条
* @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存
* @param int redis_expire redis 缓存到期时长(秒)
* @param array column 请求列
* @param boolean set_empty_flag 是否将空值写入缓存,防止数据库击穿,默认为是
*/
public function get_table_data_by_page($table, $where = null, $page = 1, $page_size = 10, $redis_key = "", $redis_expire = 600, $column = "*", $set_empty_flag = true)
/**
* 获取一条表数据
* @param string table 表名
* @param array where 查询条件
* @param string redis_key redis 缓存键值, 可空, 非空时清理键值缓存
* @param int redis_expire redis 缓存到期时长(秒)
* @param string $column 数据库表字段,可空
* @param boolean set_empty_flag 是否将空值写入缓存,防止数据库击穿,默认为是
*/
public function get_one_table_data($table, $where, $redis_key = "", $redis_expire = 600, $column = "*", $set_empty_flag = true);
```y_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);