模块串讲
一. 新人专题快速入手
1.部署配置
在http://gitlab.tools.vipshop.com/mst/mst-admin-front 拉代码,优先选择git原生支持的ssh协议,即git clone git@gitlab.tools.vipshop.com:mst/mst-admin-front.git 优点就是每次push 和pull 不用输入账号密码,而已速度更快,条件是要将开发电脑的ssh key 添加到gitlab
拉下来的代码目录要修改所有者和用户组,根据nginx的worker进程的所有者
和php-fpm 的子进程(与nginx保持一致)
所以这里是 chown -R www-data www-data mst-admin-front
这样才有足够权限读写,例如写日志,而不需要给文件777这种不安全的设置
nginx 的配置,看部分配置文件
server {
listen 80;
server_name mst-admin.vip.vip.com;
proxy_buffer_size 128k;
proxy_buffers 4 256k;
proxy_busy_buffers_size 256k;
#charset koi8-r;
#access_log logs/host.access.log main;
root /Users/liuxing/www/mtms/branches/mtms_0525;
location / {
root /Users/liuxing/www/mtms/branches/mtms_0525;
index admin.php;
}
rewrite ^/css/(.+)$ /client/src/css/$1;
rewrite ^/common/(.+)$ /client/src/common/$1;
rewrite ^/bower_component/(.+)$ /client/src/bower_component/$1;
rewrite ^/api/(.+)$ /client/src/api/$1;
rewrite ^/demo/(.+)$ /client/src/demo/$1;
rewrite ^/module/(.+)$ /client/src/module/$1;
rewrite ^/prototype/(.+)$ /client/src/prototype/$1;
set $flag 0;
if ($request !~ "admin.php"){
set $flag "${flag}1";
}
if (!-e $request_filename)
{
set $flag "${flag}2";
}
if ($request ~ ([0-9]+)\.php(.*)$)
{
set $flag "${flag}3";
rewrite ([0-9]+)\.php(.*)$ /index.php/Special/page?page_type_id=1&id=$1&$2;
}
if ($flag = "012")
{
rewrite ^/(.*)$ /index.php/$1;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
location ~ \.php {
fastcgi_pass 127.0.0.1:9000;
fastcgi_index index.php;
set $real_script_name $fastcgi_script_name;
set $path_info "";
if ( $fastcgi_script_name ~ "^(.+\.php)(/.+)$"){
set $real_script_name $1;
set $path_info $2;
}
fastcgi_param SCRIPT_FILENAME $document_root$real_script_name;
fastcgi_param PATH_INFO $path_info;
include fastcgi_params;
}
}
2.框架结构与开发流程
MobileFramework,较轻量的web框架,阅读源码比较容易,下面看admin的入口文件
调用了 MobileFramework::init(‘admin’);
核心代码是
require SYS_PATH.’/core/Load.class.php’;
require SYS_PATH.’/core/Hooks.class.php’;
Load::init();
Hooks::init();
Hooks::call(‘pre_system’);
# 网站根目录地址
define('ROOT', isset($_SERVER['SCRIPT_NAME']) ? rtrim(dirname($_SERVER['SCRIPT_NAME']), '\\') : NULL);
require SYS_PATH.'/core/Common.php';
require SYS_PATH.'/core/Model.class.php';
Application::init();
Route::init();
Dispatcher::init();
Hooks::call('last_system');12345678910111213141516
Load 这个class 的核心功能就是提供了类的自动加载,自动导入share 和new share 目录下的所有类和框架核心类,不用每次调用都require,可以在config/autoload.php 这个文件配置自动加载的类库
Hooks 钩子功能,可以指定调用控制器方法之前或之后执行的脚本,目前项目没怎么使用该功能
Application:init 利用 register_shutdown_function 函数注册 脚本运行结束的回调函数 功能: 将错误日志写到指定文件
Route 路由器,,整理url参数信息, 支持4种路由规则 专题项目使用的是pathinfo 的url解析方法,若开启路由,先解析路由规则
Dispatcher 框架调度类,调用请求action 入口
主要目录作用
service:调用外部接口
bussiness:编写业务逻辑
helper:辅助类
model: 与数据库交互
前后端交互 :目前一种方式是后端返回json,前端调接口。另外一种是用smarty模板引擎,后面会去掉,没用的东西。
还有一种是没用smarty 使用一个PhpView的简单模板
项目大量使用了Factory::create(),一个简单的工厂,实现了最基本的单例模式
开发流程一般是controller 调bussiness bussiness 里调了service或model,就是这么简单。
二.框架和业务代码编写的不足
底层类DB类的链式操作封装不完善,例如当要实现(where ..and ..and (name = .. or name is null))这种形式的操作只能写原生sql,建议完善DB类的方法封装
SimpleUploadHelper 这个类封装了httpPost 这个方法,重用性不高
public static function httpPost($url,$data, $timeout = 30) {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
curl_setopt($ch, CURLOPT_HEADER, 0);//返回数据不包含头信息
curl_setopt($ch, CURLOPT_FRESH_CONNECT, 1);//刷新链接
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_0);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $data);//POST提交的数据
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_USERAGENT, 'TEST PHP5 Client 1.1 (curl) ' . phpversion());
$result = curl_exec($ch);
$err = curl_error ( $ch );
if (false === $result || !empty ( $err )) {
$errno = curl_errno ( $ch );
$info = curl_getinfo ( $ch );
curl_close ( $ch );
return array (
'result' => $result,
'errno' => $errno,
'msg' => $err,
'info' => $info
);
}
curl_close($ch);
return $result;
}
建议这个方法放在RequestHelper请求类中,发送请求应该是请求类的职责
再看以下函数
public static function saveFile($originalName = false)
{
//uploadType上传类型,0为上传至服务本地,1为上传到图片域服务器
$uploadType = 0;
$data = array();
if (!self::$init || !is_uploaded_file(self::$file['tmp_name'])) {
return false;
}
$filename = md5(uniqid(rand(100000, 999999)));
$levelpath = self::makeLevelPath($filename);
$extension = self::getFileExt();
if (!$levelpath) {
return false;
}
/**
* 上传类型名判断
*/
if(!preg_match("#jpg|jpeg|png|gif|xls|zip|rar|bmp|xlsx#i", $extension)){
return false;
}
if(preg_match("#jpg|jpeg|png|bmp|gif#i", $extension)){
$uploadType = 1;
}
// 如果上传原名
if($originalName) {
$filename = $filename . '-' . self::getOrgName();
} else {
$filename = $filename . ($extension ? '.' . $extension : '');
}
$relative = $levelpath . '/' . $filename;
$absolute = self::$path . '/' . $relative;
if($uploadType == 0){
if (move_uploaded_file(self::$file['tmp_name'], $absolute)) {
file_put_contents(BASE_PATH . "/upload_log/".date("Ymd").".log", date("Y-m-d H:i:s", time()).", add, $absolute", FILE_APPEND);
return array(
'absolute' => $absolute,
'relative' => $relative,
);
}
}else if($uploadType == 1){
if(self::$_isSplit){
$source = 0;
$imgArr = getimagesize(self::$file['tmp_name']);
$ext = end($imgArr);
if(preg_match("#png#i", $ext)){
$source = imagecreatefrompng(self::$file['tmp_name']);
}else if(preg_match("#jpg|jpeg#i", $ext)){
$source = imagecreatefromjpeg(self::$file['tmp_name']);
}
$width = imagesx($source);
$height = imagesy($source);
$tmp = 0;
$pics = array();
$data['width'] = $width;
$data['height'] = $height;
// 剪裁
$num = floor($height/200);
$data['average_height'] = (double)number_format($height/$num, 2);
$start_x = $start_y = 0;
for ($i=0; $i < $num; $i++) {
$croped=imagecreatetruecolor($width, $data['average_height']);
imagecopy($croped,$source,0,0,$start_x,$start_y,$width,$data['average_height']);
exec("mkdir -p " . BASE_PATH . Load::config('base', 'split_pic'));
imagejpeg($croped, BASE_PATH . Load::config('base', 'split_pic') . "split_".$i.$filename);
imagedestroy($croped);
$tmp += $data['average_height'];
$start_y = $tmp;
$pics[] = BASE_PATH . Load::config('base', 'split_pic') . "split_".$i.$filename;
}
$post_url = IMG_SERVER_URL;
foreach ($pics as $key => $value) {
$post_data = array(
'domain'=>IMG_SERVER_DOMAIN,
'apikey'=>IMG_SERVER_AIPKEY,
'desc' => '',
'filename' => $filename,
'tag' => '',
'usage' => 'touch',
'replaceable' => '1',
'md5' => '',
//php5.3版本使用
'imgfile' => '@' . $value
//php5.5版本使用
//"imgfile" =>new \CURLFile(realpath($value))
);
$res = self::httpPost($post_url,$post_data);
$res = json_decode($res, true);
$data['imgs'][] = $res['url'];
}
$post_data = array(
'domain'=>IMG_SERVER_DOMAIN,
'apikey'=>IMG_SERVER_AIPKEY,
'desc' => '',
'filename' => $filename,
'tag' => '',
'usage' => 'touch',
'replaceable' => '1',
'md5' => '',
//php5.3版本使用
'imgfile' => '@' . self::$file['tmp_name']
//php5.5版本使用
//"imgfile" =>new \CURLFile(realpath(self::$file['tmp_name']))
);
$res = self::httpPost($post_url,$post_data);
$res = json_decode($res, true);
$data['source'] = $res['url'];
return $data;
}else{
$post_url = IMG_SERVER_URL;
$post_data = array(
'domain'=>IMG_SERVER_DOMAIN,
'apikey'=>IMG_SERVER_AIPKEY,
'desc' => '',
'filename' => $filename,
'tag' => '',
'usage' => 'touch',
'replaceable' => '1',
'md5' => '',
//php5.3版本使用
'imgfile' => '@' . self::$file['tmp_name']
//php5.5版本使用
//"imgfile" =>new \CURLFile(realpath(self::$file['tmp_name']))
);
$res = self::httpPost($post_url,$post_data);
$res = json_decode($res, true);
return array(
'absolute' => $res['url'],
'relative' => $res['url'],
);
}
}
return false;
}
函数太长,阅读性差,在重构:改善既有代码的设计书中 是建议函数中的代码行数原则上不要多于100行,至少其中的图片切割应该提取出单独函数,而且代码编写未达到性能最大化,有些变量在if中需要,在else中并不需要,造成了多余的计算,类似的代码在整个项目中很多地方存在。
5.附录
1. nginx的配置、虚拟主机、负载均衡和反向代理 https://www.zybuluo.com/phper/note/89391
2. git 操作 http://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000
3. 重构:改善既有代码的设计书 http://www.linuxidc.com/Linux/2014-08/105065.htm
本文对VIP模块进行了全面串讲,包括新人专题快速入手和框架及业务代码的分析。重点介绍了部署配置,如SSH协议的使用、目录权限设置和Nginx配置。讲解了MobileFramework的结构,包括Load类的自动加载、Hooks类的钩子功能以及应用初始化。此外,还指出了框架和业务代码的不足,如DB类的链式操作不完善,并提出了重构建议。

3303

被折叠的 条评论
为什么被折叠?



