系统中接口调用的单例化(third_party中的一路财富接口)

场景介绍:
1、以前每次调用接口时,都需要new一个接口对象,再调用接口,这样不仅浪费资源,代码也比较繁琐;
2、理想的调用方法是:接口全静态。这样不仅运行效率高,而且调用方便;
思路整理:
1、首先静态方法无法调用非静态方法,如果把接口静态化,调用日志记录接口时会出现问题(CiLogs);
2、基于第一条,接口全静态的想法不可取。转而求其次,接口全部单例化,用静态类来调用;这样既可以记录日志,调用也非常方便。
改造:
1、首先看third_party代码结构(特指ylcf, xinyi类似):
com ----存放工具类,比如加密、字符串操作函数
entity ----未使用,可以删除
service ----存放了所有接口,按文件分类
config.php ----配置文件,主要是接口的转换
demo.php ----接口测试文件
ylcfClass.php ----引入service并实例化,对外提供统一的接口访问

PS:config.php担负接口管理的职责,但是在service中仍然出现了具体的接口,config没发挥好管理职责。
    更好的做法是,config.php中用键值对(可以是属性名-值,也可以是数组键名-值等等,属性名和键名都算“键”)保存接口,service中只出现config中提供的“键”,不接触接口。

2、把ylcfClass.php所有方法都静态化,用静态属性保存service的实例,保证每个service实例的单一性;

3、把com工具类静态化;

4、以前在 controller 或 model 里面调用接口是这样的:
        include_once("xxxx/ylcfClass.php");
        class A
        {
            ......
            $yc = new YlcfClass();
            $yc->xxx()->yyy();
            ......
        }
    为了美观,我们先创建一个 library: YlcfApiTool.php, 里面使用静态方法,调用 YlcfClass。
    然后在 controller 或 model 里这样调用:
        class A
        {
            public __construst()
            {
                $this->load->library('YlcfApiTool');
            }
            ......
            YlcfApiTool::xxx()->yyy();
            ......
        }
5、日志处理:
    整个过程没有修改日志处理流程,所以不受影响。

总结:
1、静态化可以提高代码效率,特别是在非脚本语言里;
2、用 library 包装了一遍接口,好处是可以用 load library 优雅地引入接口,调用也无需创建实例。

软件测试-大家都知道的

  1. 为什么要做测试(必答题)
    测试不能提高代码质量;
    自己写的代码自己一般检查不出来BUG;
    流程中复杂的数据结构,第三方模块和方法,内部细节不够详细(类似于黑盒),必须要测试才能发现潜在的bug;
    bug可能因为两块正常的代码耦合在一起而产生,所以通畅地测试整个流程很有必要;

  2. web开发的测试包括哪些
    宏观的测试内容包括:
    用户感受:页面样式正确、文案通顺、交互正常、流程逻辑合理;
    运营需求:流程(样式、文案、引导等等)和目的一致,数据能随时统计;
    开发人员:代码重用率、需求扩展是否容易、文档是否齐全;

  3. 测试需要遵循哪些规则

  4. 有哪些常规的测试方法
    代码评审
    黑盒测试

  5. 有哪些测试工具,自动化测试工具
    Chrome DevTools
    postman(模拟请求)
    fiddler(抓包)
    Selenium(模拟访问 & 结果存储分析)

  6. 如何编写测试文档(范例)
    用例:
    描述:
    前置条件:
    后置条件:
    预期结果:
    是否通过:
    附链接参考:http://www.ibm.com/support/knowledgecenter/zh/SSYMRC_4.0.0/com.ibm.rational.test.qm.doc/topics/t_testcase_template_ref.html

  7. 在以往项目开发过程中,测试的时候遇到的问题(必答题)
    调用api错误(调了别人的分支、调用了测试分支);
    数据解析错误(传参不一样数据结构不一样的接口);
    sql错误:表缺少字段,或者存储了一个null到非null字段;

spl_autoload_register 入门级讲解

标签(空格分隔): PHP自动加载


__autoload()

了解 spl_autoload_register() 前先了解一下 __autoload(),或者直接 跳过此节

function __autoload($c)
{
    var_dump($c);
}
new Say();
//输出
string(3) "Say"
Fatal error: Class 'Say' not found in E:\Dropbox\liubole\htdocs\test\index.php on line xx

当Say类不存在的时候,自动执行了 __autoload() 函数,并将类名当做参数传入了 __autoload()。

如果在 __autoload() 里面 include 一下包含 Say 类的文件,可以“亡羊补牢”,代码继续正常执行:

function __autoload($c)
{
    echo "not fund class \"{$c}\", auto loading...\n";

    //libs下的say.php,大小写无影响
    include_once __DIR__ . "/libs/{$c}.php";
}

$say = new Say();
$say->hello();

包含 Say 类(libs/say.php)的文件内容:

class Say
{
    function __construct()
    {
        echo "init class \"Say\"...\n";
    }

    function hello()
    {
        echo "say: hello~\n";
    }
}

再次执行,结果如下:

not fund class "Say", auto loading...
init class "Say"...
say: hello~

至此我们大概了解了 __autoload() 的实际执行情况。


spl_autoload_register()

PS:前缀 SPL 的全称是 Standard PHP Library。

当PHP找不到类文件会调用 __autoload() 方法,当注册了自己的函数或方法时,PHP不会调用 __autoload() 函数,而会调用自定义的函数。

如果在你的程序中已经实现了__autoload函数,它必须显式注册到__autoload栈中。因为spl_autoload_register()函数会将Zend Engine中的__autoload函数取代为spl_autoload()或spl_autoload_call()。

上面的话来自 php.net。OK,那怎样算 显式注册到__autoload栈中 呢?

◔ ‸◔ ... ◔ ‸◔ ..... ◔ ‸◔ ...... ◔ ‸◔ ........◔ ‸◔

答案是:spl_autoload_register("__autoload") 。以下是我找来的一点摘要,描述了为何需要这样做:

  • autoload机制的主要执行过程为:
    (1) 检查执行器全局变量函数指针autoload_func是否为NULL。
    (2) 如果autoload_func==NULL,则查找系统中是否定义有__autoload()函数,如果没有,则报告错误并退出。
    (3) 如果定义了__autoload()函数,则执行__autoload()尝试加载类,并返回加载结果。
    (4) 如果autoload_func不为NULL,则直接执行autoload_func指针指向的函数用来加载类。注意此时并不检查__autoload()函数是否定义。

  • 如果既实现了__autoload()函数,又实现了autoload_func(将autoload_func指向某一PHP函数),那么只执行autoload_func函数。

  • SPL autoload机制的实现是通过将函数指针autoload_func指向自己实现的具有自动装载功能的函数来实现的。SPL有两个不同的函数spl_autoload, spl_autoload_call,通过将autoload_func指向这两个不同的函数地址来实现不同的自动加载机制。spl_autoload的功能比较简单,而且它是在SPL扩展中实现的,我们无法扩充它的功能。所以我们需要spl_autoload_register将autoload_func指向spl_autoload_call的这个功能。

  • 在SPL模块内部,有一个全局变量autoload_functions,它本质上是一个HashTable,不过我们可以将其简单的看作一个链表,链表中的每一个元素都是一个函数指针,指向一个具有自动加载类功能的函数。spl_autoload_call按顺序执行这个链表中每个函数,在每个函数执行完成后都判断一次需要的类是否已经加载,如果加载成功就直接返回,不再继续执行链表中的其它函数。

  • 自动加载函数链表autoload_functions由spl_autoload_register函数维护,它可以将用户定义的自动加载函数注册到这个链表中,并将autoload_func函数指针指向spl_autoload_call函数。

欲知详情请移步 __autoload机制详解以及与spl_autoload_register的区别, 文章把原因讲的很详细,时间匆忙我仅囫囵吞枣瞄了眼。


本文涉及资料:
PHP: spl_autoload_register - Manual
__autoload机制详解以及与spl_autoload_register的区别


判断点是否在矩形内的算法


声明:想获取更多更详细的知识请移步:计算几何算法概览

需求描述:
有一个 4*4 的方格,用户可以选择起点和落点形成矩形,每次形成的矩形不能重叠,起点可以和落点坐标一样。现已知起点、未选点集合和已选点集合,求落点的集合。

算法重点:转换为判断点P是否在一个矩形内。


方法一:建立平面直角坐标系,根据横坐标和纵坐标判断点是否在矩形内。


/** * 判断点是否在矩形内(含边)。p1、p2是矩形的两个对角点。 * 该算法和平面直角坐标系怎么建立无关 * @param $rc * @param $p * @return bool */ function isInRect($rc, $p) { $xi = ($p->x - $rc->p1->x) * ($p->x - $rc->p2->x); $yi = ($p->y - $rc->p1->y) * ($p->y - $rc->p2->y); return ($xi <= 0) && ($yi <= 0); }

方法二:假设矩形左、右、上、下分别在直线 L1、L2、L3、L4 上,只需要证明点 P 同时在上下、左右直线之间即可。

过P作一条直线相交于 L1、L2 的 C1 、C2 ,向量PC1 、PC2 同向则P在 L1、L2 之外,反向则在 L1、L2 之内。同理可判断P是否在 L3、L4 之间。

$$ PC_1 = (C_1x - P_x , C_1y - P_y); PC_2 = (C_2x - P_x , C_2y - P_y);$$
$$ \frac{C_2x - P_x}{C_1x - P_x } > 0 或 \frac{C_2y - P_y}{C_1y - P_y } > 0 => PC_1、PC_2 同向 $$
$$(C_1x-P_x)^2 + (C_1y-P_y)^2 = 0 => PC_1为零向量 => PC_1、PC_2 同向$$
$$(C_2x-P_x)^2 + (C_2y-P_y)^2 = 0 => PC_2为零向量 => PC_1、PC_2 同向$$

如果:
$$ L_1: y = mx + t_1; $$
$$ L_2: y = mx + t_2; $$
$$ C_1C_2: y = (m+1)x; $$
$$ P_1:(P_1x, P_1y); P_2:(P_3x, P_3y) 是L_1上的两个矩形顶点;$$
$$ P_3:(P_3x, P_3y); P_4:(P_4x, P_4y) 是L_2上的两个矩形顶点;$$

则:

$$ 若点P和{P_1,P_2,P_3,P_4}中的某一点重合,则P在矩形内; $$

$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$

$$ 若 (t_2 - P_x)*(t_1 - P_x)>0, t_1=\frac{P_1yP_2x-P_1xP_2y}{P_2x-P_1x},t_2=\frac{P_3yP_4x-P_3xP_4y}{P_4x-P_3x} 则P在矩形内;$$

/**
 * 此处省略n行代码
 */

方法三:以点P为起点,向左作射线,如果射线和矩形的焦点个数是奇数,则P在矩形内。

PS:对以下特例不做考虑:1、对于矩形的水平边不作考虑;2、对于矩形的顶点和L相交的情况。

/**
 * 此处省略m行代码
 */

总结:方法一简单实用,方法三可以推广到判断点是否在任意多边形内。


RabbitMQ在项目中的使用方式+简略介绍

声明:文章写于2016-11-10,项目中RabbitMQ的使用方式在后面可能会发生变化。

消息队列(MessageQueue)的好处:

  • 简化业务流程
  • 异步处理消息
  • 减少组件耦合
  • 消减数据峰值

消息队列的应用场景:

  • 场景1:
    支付成功以后,给用户发送优惠券or红包;
  • 场景2:
    用户注册成功后发送通知邮件or短信;

  • 场景3:
    活动里面的站内信发送;
    . . . . . .
    总结起来就是,把单个业务简洁化,通过消息系统把不相关的业务连接起来,实现更复杂的业务链,降低整个系统的复杂度。

安装

安装方法自行google,如果要在生产环境中安装请先联系运维,涉及到安全问题。

项目中使用

//发布消息
use CI123\Shop\Tool\RabbitMq\Publisher;
Publisher::publish('routing_key', $message);

//监听消息
use Api\Tool\RabbitMq\Subscriber;
Subscriber::subscribe('routing_key', function ($msgObj)    {
            $message = $msgObj->body;
            Subscriber::ack($msgObj);
});

以上是对RabbitMQ极其简陋的介绍,下面是重点:

一、我们项目中怎么设置RabbitMQ的:

1. 持久化

真正的持久保存消息,需要设置三个地方:
1.1 将交换机设置成durable;
1.2 将队列设置成durable;
1.3 将消息的delivery_mode设置成2;

PS:
缺少任何一个设置,都无法真正持久化;
持久化会消耗额外的性能;

2. 自动删除

2.1 消息在队列里存在时间是5分钟;
2.2 队列本身存在时间是30分钟;
2.3 过期的消息会被移出到“死信队列”;
2.4 死信队列里的消息存在7天;
2.5 死信队列本身存在10天;

3. 消费者应答机制(ack/nack机制)

3.1 如果消息已经被正确消费了,你可以通过ack方法告诉rabbitmq,rabbitmq会删除这条消息;
3.2 反之,你可以调用nack告诉RabbitMQ消费失败,rabbitmq会立马将消息移到“死信队列”;
3.3 或者什么都不做,等待消息过期后被移动到“死信队列”。

4. 生产者确认机制(Confirm机制)

4.1 发布消息后,发送者可以通过异步的回调知道消息是否发送成功;

PS:目前项目中基本没有使用。

二、初步理解RabbitMQ

1. 四个比较重要的概念

  • 虚拟主机(vhost):每个vhost都有单独一套交换器、队列、绑定规则
  • 交换器(exchange):将消息发送到队列
    常见相关名词:
    + 名称(exchange):交换器名字,不能重复
    + 类型(type):fanout/direct/topic/headers
    + 持久化(durable):服务重启后是否要恢复
    + 自动删除(auto-delete):没有队列使用交换器时自动删除
  • 队列(queue):存取消息
    常见相关名词:
    + 名称(queue):队列名字,不能重复
    + 持久化(durable):服务重启后是否要恢复
    + 自动删除(auto-delete):没有消费者时自动删除
    + 声明(arguments): 消息存在时间(x-message-ttl)、队列存在时间(x-expires)
  • 绑定(binding):将队列和交换机绑定,以通过routing_key来收发消息;

2. 四种Exchange

  • fanout :广播
  • direct:routing_key全部匹配
  • topic:可以理解为在direct的基础上给routing_key增加了正则匹配,当然肯定不是正则;
  • headers:消息里包含headers,如果消息的headers和队列能匹配上就将消息投递到该headers,可以指定是完全匹配(all)还是只需要匹配单个(one);

3. 其他概念和结论

  • 消息的获取有主动拉取、被动接收,发布/订阅模式是被动接收;
  • 交换器、队列的属性一旦声明就不可更改,但是绑定是可以解绑的(unbind);
  • 当一个 message 被路由到多个 queue 中时,在某个 queue 中的某个 message 的“死亡”不会对相同 message 在其他 queue 中的生存状况;
  • 同时设置了队列生存时间T1和消息生存时间T2后,消息在队列中的存在时间是T1/T2之中的最小值;
  • 如果没有设置死信队列,即便你设置了durable,消息到期后也会被丢弃;

4.目前的使用状况

  • 按照普通的消息队列来使用:佣金、理财、保险;
  • 改造为延时队列:微信模板消息,通过延时队列来重发上次发送失败的消息;
  • 可以改造为任务队列使用:待定;

禁止用户滑动屏幕的js设置

整   理:晋 哲

时   间:2017-01-06

说   明:弹出框弹出后,当用户滑动屏幕时主体页面应该设置为不能滚动的

一、移动端
禁止滚动设置
$('body').bind('touchmove',function(){return false;});

恢复滚动设置
$('body').unbind('touchmove');

二、PC端
禁止滚动设置
$('body').css({overflow:"hidden"});

恢复滚动设置
$('body').css({overflow:"auto"});

深入理解CI框架:框架如何运转起来(原理篇)

标签(空格分隔): codeigniter 源码

一、核心

index.php:入口文件

入口文件主要定义路径和环境,包括BASEPATHFCPATHAPPPATH等常量表示的路径和applicationsystem等变量表示的路径;

index.php通过$_SERVER['CI_ENV']来分辨当前的执行环境,目前支持developmenttestingproduction三种(默认development),如果你在apache里配置CI_ENV为其他值(比如mytest),项目会输出错误信息并停止执行;

core/CodeIgniter.php:分派请求

CodeIgniter.php加载了各种各样的类和方法,它解析用户的请求(URI类),判断请求对应哪个控制器的哪个方法(Router类),创建控制器对象,执行方法,得到结果并输出(Output类),期间还会加载并执行预先设置好的各种钩子(Hooks类)。

  1. 很显然,CodeIgniter.php是CI框架最重要的文件之一,它根据用户请求调用不同类处理并输出结果。
  2. CI框架解析用户请求时,主要依据$_SERVER这个变量($_SERVER['REQUEST_METHOD']
  3. 除了各种类,CodeIgniter.php还加载了很多公共方法,详见core/Common.phpcore/compat

二、提取核心代码

$RTR =& load_class('Router', 'core');
$class = ucfirst($RTR->class);
require_once BASEPATH.'core/Controller.php';
$CI = new $class();

三、细枝末节

core/Common.php:公共方法

Common Functions——这是Common.php定位,用来存放各种公共方法,比如is_phpis_httpsis_clishow_404function_usable等等。

core/compat目录下也存放有很多公共方法文件,比如hash.phpmbstring.phppassword.phpstandard.php,我们经常用的array_column函数就是在standard.php里定义的。

四、黑科技

extract:从数组中将变量导入到当前的符号表

//index.php
$vars = array('name' => 'jane', 'age' => 20);
extract($vars);
var_dump($name);

ob_xxx函数:把输出放到缓冲区、从缓冲区获取内容

ob_start:打开输出控制缓冲
ob_end_flush:冲刷出(送出)输出缓冲区内容并关闭缓冲
ob_end_clean:清空(擦除)缓冲区并关闭输出缓冲

//index2.php
//将一个php文件保存到文本文件里
//load->view 如何实现的
ob_start();
include('./index.php');
$buffer = ob_get_contents();
file_put_contents('index.php.txt', $buffer);
@ob_end_clean();
ob_end_flush();

ReflectionMethod:反射类

//检查方法是否可以访问
//如何在访问一个private方法或不存在的方法的时候显示404
$reflection = new ReflectionMethod($class, $method);
if ( ! $reflection->isPublic() OR $reflection->isConstructor())
{
    $e404 = TRUE;
}

弹性布局Flexbox

整   理:晋 哲

时   间:2017-01-06

说   明:使用弹性布局,设置自适应布局、水平垂直居中等样式

弹性布局很早就出来了,但之前一直没有使用Flexbox主要还是考虑兼容性,且兼容性写法太冗余。目前,旧版的属性都已被重新修改,所有浏览器的兼容性也得到很大支持,移动端可以放心使用了,当然浏览器前缀还是要加上的。

能够被运用到的布局结构,如响应式布局、选项卡分布、水平垂直居中等,优先考虑使用flex,代替之前一些特殊布局所使用的传统解决方案:使用display属性或position属性或float属性。

flex常用属性

display:将元素设置为内联或者块flexbox容器元素
flex:复合属性。设置或检索弹性盒模型对象的子元素如何分配空间。
justify-content:控制flex容器内item的水平对齐方式
align-items:控制flex容器内item的垂直对齐方式
flex-direction:定义弹性盒子元素的排列方向

其属性值:

display:flex | inline-flex
flex:none | < flex-grow > | < flex-shrink > | < flex-basis >
justify-content:flex-start | flex-end | center | space-between | space-around
align-items:    flex-start | flex-end | center | baseline | stretch
flex-direction:row | row-reverse | column | column-reverse

常见用法:
一、自适应布局

<!-- HTML -->
<div class="box">
    <div class="subnav"></div>
    <div class="content"></div>
</div>

<!-- CSS -->
.box{
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
}
.subnav{
    width: 100px;
    height: 100px;
    background: #000;
}
.content{
    background: #f60;
    -webkit-box-flex: 1;
    -webkit-flex: 1;
    flex: 1;
}

解释:subnav固定宽度,content自适应撑满剩余空间

二、使用 Flexbox 的水平垂直居中布局

<!-- HTML -->
<div class="box">
    <div class="content"></div>
</div>

<!-- CSS -->
.box{
    height: 200px;
    background: #eee;
    display: -webkit-box;
    display: -webkit-flex;
    display: flex;
    -webkit-align-items: center;
    align-items: center;
    -webkit-justify-content: center;
    justify-content: center;
}
.content{
    width: 100px;
    height: 100px;
    background: #f60;
}

Sass mixin制作水平垂直居中

整   理:晋 哲

时   间:2017-01-06

说   明:使用Sass mixin 的方式将一些固定样式用法封装起来,能够提高样式复用性。

如水平垂直居中的示例:
常用的水平垂直居中方式是使用CSS transform属性或负向margin的方式,将这两种方法集成起来,通过判断是否有宽高值生成对应的样式。

/*定义mixin*/
@mixin center($width: null, $height: null){
     position: absolute;
     top: 50%;
     left: 50%;

     @if not $width and not $height{
          transform: translate(-50%, -50%);
     }
     @else if $width and $height{
          width: $width;
          height: $height;
          margin: -($height/2) #{0 0} -($width/2);
     }
     @else if not $height{
          width: $width;
          margin-left: -($width/2);
          transform: translate(0, -50%);
     }
     @else{
          height: $height;
          margin-top: -($height/2);
          transform: translate(-50%, 0);
     }
}

/*使用*/
.box1{
     @include center;
}
.box2{
     @include center(200px);
}
.box3{
     @include center(null, 300px);
}
.box4{
     @include center(200px, 300px);
}

CSS3 Animation steps()函数属性运用

整   理:晋 哲

时   间:2017-01-06

说   明:利用steps()函数的特效可以制作类gif动画的动态效果,搭配sprite图使用。

作用优势:代替gif动图的繁琐制作,调整动画便捷,较gif更清晰,图片进行压缩,文件可更小。

使用说明:animation的steps()函数内部的第一个参数设置为正整数,可以计算等分@keyframes规则内from到to的差值,最终进行动画切换。

如示例代码:

.dragon{
    width: 178px;
    height: 188px;
    background: url(images/dragon.png) 0 0 no-repeat;
    animation: run_dragon 2s steps(10) infinite;
}
@keyframes run_dragon{
    0%  {background-position: 0 0;}
    100%{background-position: -1780px 0;}
}

dragon