-
CI框架从3.0开始,输入类Input开始支持用数组的方式去取变量值。例如:
以前(2.x):
$user_id = $this->input->post('user_id', true);
$nickname = $this->input->post('nickname', true);
$version = $this->input->post('version', true);
...
$address = $this->input->post('address', true);
改进(3.x):
$keys = array('user_id', 'nickname', 'version');
list($user_id, $nickname, $version, $address) = $this->input->post($keys, true); -
使用 input_stream() 来获取php://input流数据,例如 PUT 请求
以前:
$rsv = file_get_contents("php://input");
$data = json_decode($rsv,1);
$user_id = intval($data['user_id']);
$nickname = stripSql($data['nickname']);
$version = stripSql($data['version']);
改进:
$keys = array('user_id', 'nickname', 'version');
list($user_id, $nickname, $version, $address) = $this->input->input_stream($keys, true); -
在http和https的兼容上面,$this->config->base_url() 添加了第二个参数 $protocol,可以传入http/https。如果传入【""】,会得到//开头的链接,表示自适应。
示例:
$CI =& get_instance();
$url = $CI->config->base_url("yilucaifu/buy/1024", "");
/**
* window.location="//shop.ci123.com/flashsale/yilucaifu/buy/1024";
*/ -
get(), post(), get_post(), cookie(), server(), user_agent()在没有取到数据的时候,返回NULL而非FALSE。
意义:
在以前(2.x),$p = $this->input->get('p', true); isset($p) 始终为真;到3.x后,如果没有p参数,isset($p)为假。 -
BASEPATH, APPPATH 被定义为了绝对路径,在脚本里可以直接使用这些变量了。
-
CI 3.x原生支持composer。新建composer.json,填写内容后update就可以使用了。
Js 中的数组和对象
示例一:
var x = [1,2];
x.k = 'v';
console.log(x);
console.log(x.length);
结果是:
[1, 2, k: "v"]
2
示例二:
var x = [1, 2];
x.get = function(){
alert('function get');
}
console.log(x);
console.log(x.length);
结果是:
[1,2] --注释1
2
注释1:展开后是:
0:1
1:2
get:function()
示例三:
var x = new Object();
x[0] = 1;
x[1] = 2;
x.get = function(){
alert('function get');
}
console.log(x);
console.log(x.length);
结果是:
Object {0: 1, 1: 2} --注释2
undefined
注释2:展开后是:
0:1
1:2
get:function()
示例四:
var m = [];
var n = new Object();
console.log(m instanceof Array, m instanceof Object);
console.log(n instanceof Array, n instanceof Object);
结果是:
true true
false true
【结论1】:从上面可以看出来,JS中的数组仅仅是一种特殊的对象,对象和数组都可以具有属性和方法。数组具有 length 属性,而对象没有。当数组具有属性或方法时,属性和方法不会让数组的 length 增加。
示例五:
var p = [];
console.log(p.length);
p[100] = 1; --注释3
console.log(p.length);
结果是:
0
101
注释3:或者p['100']
示例六:
var p = [1,2,3,4,5]
console.log(p, p.length);
p.length = 3;
console.log(p, p.length);
p.length = 5;
console.log(p, p.length);
结果是:
[1, 2, 3, 4, 5] 5
[1, 2, 3] 3
[1, 2, 3] 5
【结论2】:当值被分配给大于数组本身长度的索引时,length 会自动增加到新的长度;当 length 变小时,索引超过数组长度的元素将被删除。(恢复length也无法恢复数组元素)
Js小知识点——setTimeout
一:下面看一个腾讯面试题,主要知识是Js的线程问题。(有删减)
var cnt = 0;
for(var i = 0; i < 10; ++i) {
setTimeout(function() {
cnt += i;
}, 0);
}
setTimeout(function(){
console.log(cnt);
}, 0);
由于Js是单线程,遇到循环的时候setTimeout会在循环结束后才执行,所以cnt所加的i总是10,最后cnt的值是100。
var cnt = 0;
for(var i = 0; i < 10; ++i) {
setTimeout(function() {
cnt += i;
}, 0);
}
console.log(cnt);
稍作改动,代码的运行结果是0,很容易理解。
二:向 setTimeout 传递字符串字面量
var s = 1;
function test()
{
var s = 2;
setTimeout('console.log(s)', 0);
}
function test2()
{
var s = 2;
setTimeout(function(){
console.log(s);
}, 0);
}
test();
test2();
传入字符串字面量时,字面量会在全局作用域内被解析,所以上面输出 1 和 2 ;
系统中接口调用的单例化(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 优雅地引入接口,调用也无需创建实例。
软件测试-大家都知道的
-
为什么要做测试(必答题)
测试不能提高代码质量;
自己写的代码自己一般检查不出来BUG;
流程中复杂的数据结构,第三方模块和方法,内部细节不够详细(类似于黑盒),必须要测试才能发现潜在的bug;
bug可能因为两块正常的代码耦合在一起而产生,所以通畅地测试整个流程很有必要; -
web开发的测试包括哪些
宏观的测试内容包括:
用户感受:页面样式正确、文案通顺、交互正常、流程逻辑合理;
运营需求:流程(样式、文案、引导等等)和目的一致,数据能随时统计;
开发人员:代码重用率、需求扩展是否容易、文档是否齐全; -
测试需要遵循哪些规则
-
有哪些常规的测试方法
代码评审
黑盒测试 -
有哪些测试工具,自动化测试工具
Chrome DevTools
postman(模拟请求)
fiddler(抓包)
Selenium(模拟访问 & 结果存储分析) -
如何编写测试文档(范例)
用例:
描述:
前置条件:
后置条件:
预期结果:
是否通过:
附链接参考:http://www.ibm.com/support/knowledgecenter/zh/SSYMRC_4.0.0/com.ibm.rational.test.qm.doc/topics/t_testcase_template_ref.html -
在以往项目开发过程中,测试的时候遇到的问题(必答题)
调用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:入口文件
入口文件主要定义路径和环境,包括BASEPATH、FCPATH、APPPATH等常量表示的路径和application、system等变量表示的路径;
index.php通过$_SERVER['CI_ENV']来分辨当前的执行环境,目前支持development、testing、production三种(默认development),如果你在apache里配置CI_ENV为其他值(比如mytest),项目会输出错误信息并停止执行;
core/CodeIgniter.php:分派请求
CodeIgniter.php加载了各种各样的类和方法,它解析用户的请求(URI类),判断请求对应哪个控制器的哪个方法(Router类),创建控制器对象,执行方法,得到结果并输出(Output类),期间还会加载并执行预先设置好的各种钩子(Hooks类)。
- 很显然,
CodeIgniter.php是CI框架最重要的文件之一,它根据用户请求调用不同类处理并输出结果。- CI框架解析用户请求时,主要依据
$_SERVER这个变量($_SERVER['REQUEST_METHOD'])- 除了各种类,
CodeIgniter.php还加载了很多公共方法,详见core/Common.php和core/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_php、is_https、is_cli、show_404、function_usable等等。
core/compat目录下也存放有很多公共方法文件,比如hash.php、mbstring.php、password.php、standard.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;
}