转:或许你也患上了开发瘫痪症

亲爱的开发者们:你们是否因为自己只精通于三大设备平台的八种编程语言而惴惴不安?又发现一个JavaScript框架是否会让你不寒而栗、愁眉苦脸?你是否曾经因为无法确定哪个云平台最适合而把业余项目一再推迟?

或许你也换上了开发瘫痪症(Developaralysis)。颤抖吧,这个病是治不好的。

如今开发者们可选的技术方案多到令人发指,让人眼花缭乱,透不过气来。过去几年里,我拿着别人给我的酬劳,写过Java、Objective-C、C、C++、Python、Ruby、JavaScript、PHP(对不起,这个也算),用过各种各样的SQL/键值/文件数据存储技术(MySQL、PostgreSQL、MongoDB、BigTable、Redis、Memcached等等)。我是否自我感觉良好?上帝啊,一点也没有。我反而感觉到愧疚,因为我还没有用过Erlang、Clojure、Rust、Go、C#、Scala、Haskell、Julia、Scheme、Swift或者是OCaml。

我就是一名开发瘫痪症患者:软件产业发展太快、任何一个人都无法跟上,从而对我的意识造成了毁灭性打击。

上面提到的几乎任何一种语言,你都会找到无数可选的框架、套件和库——看得头都要爆炸了。如今仅仅是把JavaScript的框架和库的所有排列认真评估一遍就要花上几个月的时间。另外,你知道Ruby语言有多少种gem包吗?有多少种iOS框架吗?有多少种NewSQL或NoSQL的数据库技术吗?更不用说从Hadoop、Spark与Google Dataflow中进行选择了,究竟是用Avro,还是Thrift,还是协议缓冲区,等等,等等……

还好,移动领域已经简化到了Android、iOS两大垄断平台——尽管也隐藏着一些交叉代替方案,比如Xarmarin或者PhoneGap、Sencha这样的跨平台HTML技术——但是确定在哪个平台上部署后端、如何部署又会让你头大。我开发过的各个系统部署在Heroku、Amazon Web Services、Google App Engine、Google Compute Engine以及Parse上面……这让我感觉非常糟糕,因为我对OpenStack、Force.com、Azure、Appfog一无所知,好多AWS服务我也从来没有真正用过,说多了都是泪。

I Am Devloper @iamdevloper

2014年编写简单网站的步骤:

  1. 安装Node

  2. 安装Bower

  3. 选择CSS框架

  4. 选择敏捷的方法

……

  1. 写几行HTML代码

如今的开发者面临着太多的选择,以至于使用的许多工具仅仅是用来管理另一堆工具:比如Bundler、Bower、CocoaPods、Pip等等。这些东西太棒了!我完全离不开它们!别高兴得太早。你开始使用另一堆工具后,等用到一半的时候你真正理解了它们的内容,你开始觉得这样的配置还不够用,你有点想要把它们重写一遍了……也许再找另一个工具代替……

可悲的是,如今开发者可用的语言、工具、框架以及平台的多样性和绝对数量庞大到让人畏惧。当然没人会承认这一点。所有人都想装作精通所有语言的编程大师。然而事实却是,我们都已经深陷于开发瘫痪症之中无法自拔。

即便收集了各种信息做出了最明智的选择,结果往往也适得其反。比如说,在项目开始前,你真的花时间分析了所有的可能,克服了由此产生的学习曲线,结果却被一些用着PHP、Swift这样易于上手的语言,写着表情符号变量名的小屁孩抢了市场先机——

——不过另一方面,如果你选择使用Swift和PHP的话,你就会生活在无休止的恐惧之中,担心一些C#/Haskell程序员高手很快就会做出更好的产品几百你,就像保罗·格拉汉姆(Paul Graham)很多年前用Lisp语言编写Viaweb那样。回忆往昔,他这样写道:

当你选择了技术之后,你必须要忽略其他人在做什么,全心全意思考怎样做到最好……事实上我们确实有一个秘密武器……我们开发软件的速度超出了所有人的想象……我们用一种全是括号的语法奇怪的AI语言编写出了我们的软件。

再说回开发瘫痪症。我们应该选择已经掌握的技术吗?这样我们就能立刻动手开发,无需克服学习曲线,但是需要生活在恐惧之中,担心其他人在以更好、更快、更优雅的方式实现同样的产品,担心到了明年我们的技能就跟不上时代、丧失竞争力了。还是应该选择未知的新技术?因为我们热爱学习,更好的工具使用起来不仅更加有趣,而且能够带来巨大的竞争优势……付出的代价则是大量的时间、精力和认知负荷。

这个问题太难回答了!不存在绝对正确的答案。因此每个月都是开发瘫痪症发作月。我马上会做好五颜六色、闪闪发光的丝带给你们佩戴,只要等我选好用哪种3D打印机、哪种微控制器、哪种LED软件、哪种无人机系统就行了。恐怕要麻烦你们期待很久很久了。

翻译:顾秋实

使用CodeIgniter框架搭建RESTful API服务

我记得这是在很早之前看的,需要翻墙才能看到,为了方便查看,所以给记录到笔记了,转载:

在2011年8月的时候,我写了一篇博客《使用CodeIgniter框架搭建RESTful API服务》,介绍了RESTful的设计概念,以及使用CodeIgniter框架实现RESTful API的方法。转眼两年过去了,REST在这两年里有了很大的改进。我对于前一篇博客中的某些方面不是很满意,所以希望能利用这次机会写一个更加完善的版本。

我的项目基于Phil SturgeonCodeIgniter REST Server,遵循他自己的DBAD协议。Phil的这个项目很棒,干净利落,简单实用,并且极具特色,解决了我自己项目中两个问题:在请求中查询语句的使用,以及复杂的身份验证方法。正如我前面所说,我的项目基于Phil的项目开发,并且只有一个方面不同:我给每个资源分别分配一个控制器,而不是用一个独立庞大的控制器进行统一管理。使用多个独立控制器的好处是维护起来更加简单方便。

完整的项目源码可以在awhitney42/codeigniter-restserver-resources下载。

RESTful

在深入探讨之前,我们先来回顾一下TESTful的概念。REST全名Representational State Transfer,中文可以译为:表现层状态转化,是一种网络服务的架构工具,而不仅仅是一套接口规范。

使用名词而不是动词

你的RESTful接口应该提供访问而不是方法。
所以,这样是不可取的:

1 
2
/createCustomer 
/getCustomer?id=666

应该这样:

1 
2
POST /customers 
GET /customers/666

所有东西都应该有ID

REST的优点之一就是简洁,一定程度上来讲,REST只是一个URI的集合,每个资源都需要一个独一无二的ID作为一个标识。

1 
2
GET /customers/666 
GET /products/4234

使用动词进行操作

在REST里,使用不同的HTTP动词进行增删改查的操作:

动词 操作
POST 新建一个资源
GET 读取一个资源
PUT 更新一个资源
DELETE 删除一个资源

使用这个REST服务的映射表来设计接口,在开发客户端的时候可以轻松上手,不用深入理解接口的含义。这套标准十分适用于REST,符合面向服务架构(SOA)的核心设计思想:服务抽离、松耦合、可复用、可发现、健全性。

通过这个一致的映射关系,客户端也知道哪个动词是幂等(Idempotent)的。幂等是指这个操作可以重复多次,并且每次都会得到相同的结果。参照HTTP规范,GET、PUT和DELETE操作是幂等的,POST操作不是幂等的。这也就是为什么在REST中使用POST来进行添加操作,而使用PUT来进行更新操作。

把东西链接起来

REST中的一个核心概念是HATEOAS(Hypermedia As The Engine Of Application State),即“超媒体即应用状态引擎”。这意味着应该始终使用链接(超媒体)来获取资源信息,然后客户端通过这个链接就可以获取到不同的资源。

所以,相关的资源应当返回一条链接,而不是返回整个资源的内容。
也就是,该这样:

1
2 
3 
4 
5 
6 
7 
8
<officer id="1">  
    <name>James T. Kirk</name>  
    <rank>Captain</rank>  
    <subordinates>  
        <link ref="http://ncc1701/api/officers/2">  
        <link ref="http://ncc1701/api/officers/3">
    </subordinates> 
</officer>   

而不该这样:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14
<officer id="1">  
    <name>James T. Kirk</name>  
    <rank>Captain</rank>  
    <subordinates>  
        <officer id="2">  
            <name>Spock</name>  
            <rank>Commander</rank>  
        </officer>  
        <officer id="3">  
            <name>Doctor Leonard McCoy</name>  
            <rank>Commander</rank>  
        </officer>  
    </subordinates> 
</officer>

返回资源的链接应该用完整的URI地址而不是相对路径,这要求客户端请求这些资源的当前状态,维持HATEOAS原则。使用完整的URI地址的好处是客户端不需要额外的了解API相关的内容,只需要简单的访问这些链接就可以了。

提供多种资源表现方式

REST中的R代表Representational,即表现层,这意味着REST服务应该提供不同的表现方式,以全面支持不同的客户端请求。在一次HTTP请求中,服务器可以通过指定的表现方式返回资源,REST服务应该使用标准的表现方式,以便客户端之间的信息互通。

比如可以通过如下请求XML格式的数据:

1 
2
GET /customers/666 
Accept: application/xml

或者vcard格式:

1 
2
GET /customers/666 
Accept: application/vcard

或者pdf格式:

1 
2
GET /customers/666 
Accept: application/pdf

或者自定义格式:

1 
2
GET /customers/666 
Accept: application/vnd.mycompany.customer-v1+json

使用状态码作为回复

HTTP的状态码提供了一套标准化方案,用来反馈请求的状态。

含义 解释
200 OK 确认GET、PUT和DELETE操作成功
201 Created 确认POST操作成功
304 Not Modified 用于条件GET访问,告诉客户端资源没有被修改
400 Bad Request 通常用于POST或者PUT请求,表明请求的内容是非法的
401 Unauthorized 需要授权
403 Forbidden 没有访问权限
404 Not Found 服务器上没有资源
405 Method Not Allowed 请求方法不能被用于请求相应的资源
409 Conflict 访问和当前状态存在冲突

CodeIgniter

CodeIgniter是一个流行的MVC框架,很适合用来进行RESTful API开发。控制器(Controller)处理客户端的请求并返回内容,模型(Model)进行增删改查(CRUD)的操作,视图(View)用来处理资源的表现格式。不过在这个例子里,我们没有用模型,而是直接用控制器进行格式处理,这样整个项目更干净更简单。

代码可以直接在Github中获取:codeigniter-restserver-resources

在接下来的例子里,我们用到了REST_Controller这个库,继承自原生的CI_Controller。它可以完成绝大部分繁杂的工作:处理请求、调用模块、格式化内容、返回数据。你的每个资源控制器都应该继承自REST_Controller这个类。

下面我们来看一个例子,我们假设要提供一个Widgets资源的RESTful接口:

1
class Widgets extends REST_Controller

get()

Widgets类中的第一个函数是get(),用来响应HTTP的GET请求。这个函数调用了父类的protected函数_get(),用来获取请求中的参数ID。然后这个函数根据是否有参数ID使用widgets_model调用getWidgets()或者getWidget($id)方法,模型的返回值将会通过父类的response()函数返回,返回的内容包含对应的状态码和符合格式的数据。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17
function get() {
   $id = $this->_get('id');
   if(!$id)  {
      $widgets = $this->widgets_model->getWidgets();
         if($widgets)  
             $this->response($widgets, 200);
             // 200 being the HTTP response code  
         else  
             $this->response(array('error' => 'Couldn't find any widgets!'), 404);
    }  
    $widget = $this->widgets_model->getWidget($id);  
    if($widget)  
        $this->response($widget, 200); 
        // 200 being the HTTP response code  
    else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

cURL是一个很好的命令行工具,我们可以用来测试REST服务,返回一个JSON格式的数据:

1
2
$ curl -i -H "Accept: application/json" -X GET 
http://foo.com/index.php/api/widgets

则会返回如下内容:

1 
2 
3 
4 
5
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/json  
{"1":{"id":1,"name":"sprocket"},"2":{"id":2,"name":"gear"}}

请求特定的资源也很简单,这次我们去请求ID为2的widget并且通过XML格式返回:

1
2
$ curl -i -H "Accept: application/xml" -X GET 
http://foo.com/index.php/api/widgets/id/2

返回如下内容:

1 
2 
3 
4 
5 
6
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>2</id><name>gear</name></xml>

如果请求一个不存在的资源就会返回404的错误码:

1 
2 
3 
4 
5 
6
HTTP/1.1 
404 Not Found 
Status: 404 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><error>Widget could not be found</error></xml>

post()

接下来的函数是post(),用来处理创建widget的POST请求。可以通过$this->_post_args获取请求的数据。父类通过Format.php对请求的数据进行处理并把它们放到了$this->_post_args里。接下来post()方法使用widgets_model模型调用createWidgets($data)函数,如果数据非法或者请求冲突,widgets_model模型会抛出异常并且返回异常的内容。如果调用成功,则会调用getWidget($id)函数获取最新的widget,在返回的时候会将返回值和201 (Created)的状态码一起返回。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21
function post() 
{  
    $data = $this->_post_args;  
    try {  
        $id = $this->widgets_model->createWidget($data);
    } catch (Exception $e) {  
        // Here the model can throw exceptions like the following:  
        // * Invalid input data:  
        // throw new Exception('Invalid request data', 400);  
        // * Conflict when attempting to create, like a resubmit:  
        // throw new Exception('Widget already exists', 409)  
        $this->response(array('error' => $e->getMessage()),  $e->getCode());  
    }  
    if ($id) {  
        $widget = $this->widgets_model->getWidget($id);  
        $this->response($widget, 201); 
        // 201 is the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be created'),  404); 
    }
}

为了测试通过POST请求新建资源的操作,我们需要把我们的cURL包裹在PHP代码里,创建一个rest_client.php文件:

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
print "n-----TESTING REST POST-----n"; 
test_post(); 
function test_post() 
{  
    $data = array("name" => "bolt");  
    $data_string = json_encode($data);  
    $ch = curl_init('http://foo.com/index.php/api/widgets');  
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");  
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(  
       'Content-Type: application/json',  'Content-Length: ' . strlen($data_string))  
    );  
    $result = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);  
    print "Status: $httpcode" . "n";  
    print "Content-Type: $contenttype" . "n";  
    print "n" . $result . "n"; 
}

在命令行中通过PHP命令执行:

1
$ php rest_client.php

返回内容如下:

1 
2 
3 
4 
5 
6
-----TESTING REST POST----- 
Status: 201 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 

<xml><id>3</id><name>bolt</name></xml>

put()

接下来的方法是put(),用来处理PUT请求,更新已经存在的资源数据。处理过程和POST的处理十分相似,最大的区别就在于使用$this->_put_args而不是$this->_post_args,以及返回200而不是201。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
21
22
public function put() {  
    $data = $this->_put_args;  
    try {  
        //$id = $this->widgets_model->updateWidget($data);  
        $id = $data['id']; 
        // test code  
        //throw new Exception('Invalid request data', 400); 
        // test code  
    } catch (Exception $e) {  
        // Here the model can throw exceptions like the following:  
        // * For invalid input data: new Exception('Invalid request data', 400)  
        // * For a conflict when attempting to create, like a resubmit: new Exception('Widget already exists', 409)  
        $this->response(array('error' => $e->getMessage()), $e->getCode());  
    }  
    if ($id) {  
        $widget = array('id' => $data['id'], 'name' => $data['name']); 
        // test code  //$widget = $this->widgets_model->getWidget($id);  
        $this->response($widget, 200); 
        // 200 being the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

为了测试UPDATE更新资源的功能,我们依旧使用PHP进行cURL的操作,大多数的网络服务器默认没有开启PUT和DELETE,我们可以在header中使用X-HTTP-Method-Override,通过POST来发送PUT请求。这样的话,服务器会把它当做一个POST请求,而REST服务器会把它作为PUT操作处理。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22
print "n-----TESTING REST PUT-----n"; 
test_put();  
function test_put() 
{  
    $data = array("id" => "3", "name" => "nut");  
    $data_string = json_encode($data);  
    $ch = curl_init('http://foo.com/index.php/api/widgets');  
    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "POST");  
    curl_setopt($ch, CURLOPT_POSTFIELDS, $data_string);  
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);  
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'X-HTTP-Method-Override: PUT',  
        'Content-Type: application/json',  
        'Content-Length: ' . strlen($data_string))  
    );      
    $result = curl_exec($ch);  
    $httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);  
    $contenttype = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);  
    print "Status: $httpcode" . "n";  
    print "Content-Type: $contenttype" . "n";  
    print "n" . $result . "n"; 
}

同样使用PHP命令测试:

1
$ php rest_client.php

返回结果:

1 
2 
3 
4 
5 
6
-----TESTING REST PUT----- 

Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>3</id><name>nut</name></xml>

delete()

最后的函数就是delete了,请求中必须包含ID的参数否则就会返回400(Bad Request)的状态码,因为我们不希望用户删除所有的资源内容。如果ID没有对应的资源则会返回404(Not Found) 错误码。如果对应的widget存在,则会尝试调用deleteWidget($id)删除对应资源。操作中的所有异常都会被捕获到并且返回。如果删除成功,返回200(Success)状态码。

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23
function delete() 
{  
    $id = $this->_get('id');  
    if(!$id)  
    {  
        $this->response(array('error' =>  'An ID must be supplied to delete a widget'), 400);
    }  
    //$widget = $this->widgets_model->getWidget($id);  
    $widget = @$widgets[$id]; 
    // test code  
    if($widget) {  
        try {  
            $this->widgets_model->deleteWidget($id);
       } catch (Exception $e) {  
           // Here the model can throw exceptions like the following:  
           // * Client is not authorized: new Exception('Forbidden', 403)  
           $this->response(array('error' => $e->getMessage()),  $e->getCode());
       }  
       $this->response($widget, 200); 
       // 200 being the HTTP response code  
    } else  
        $this->response(array('error' => 'Widget could not be found'), 404); 
}

我们可以用命令行工具cURL进行测试,参数ID像GET一样放在URL地址中,和PUT相同,我们通过X-HTTP-Method-Override使服务器把请求当做POST处理,REST服务则会把这次请求当做DELETE操作处理。

1
2
$ curl -i -H "Accept: application/xml" -H "X-HTTP-Method-Override: DELETE" 
-X POST http://foo.com/index.php/api/widgets/id/1

返回200 (Success)说明id为1的资源已经被成功删除:

1 
2 
3 
4 
5 
6
HTTP/1.1 
200 OK 
Status: 200 
Content-Type: application/xml  
<?xml version="1.0" encoding="utf-8"?> 
<xml><id>1</id><name>sprocket</name></xml>

如果ID非法,则会返回404(Not Found)错误码,如果没有删除的权限,则会返回403 (Forbidden) 状态码。

Authentication

正如前面提到的,Phil的REST框架和我的原来的设计相比,在用户权限认证上有了很大的改进, 提供basicdigest两种认证方式。还有很多其他特性,比如可以用LDAP字典将授权融为一体,具体可以在config/rest.php中设置。

除了传统的认证方式,你还可以使用API keys或者IP地址的白名单进行用户权限管理。

DTO

在原先的设计中,我使用DTO(Data Transfer Object)在不同格式之间传输数据,Phil使用Format类来解决这个问题,从Widget的例子中我们可以看到,使用数据是多么的方便,只需要$this->_post_args或者$this->response()就可以解决问题。当然,这并不意味着DTO不好用,但是在很多场合下它会显得十分复杂和庞大。
在我原来的项目里,我对客户端需要使用和服务器端同样的DTO库来传输数据很不满意,因为它违反了KISS的原则。

总结

REST目前已经是比较成熟的网络服务的框架模型方案,是API产品目录网站programmableweb的基石,是各式各样应用和服务器的数据传输的基础,同时对于SOA来说也是十分重要,促进网络端和移动端的接口技术日趋成熟。

正如前面所看到的,用CodeIgniter框架实现RESTful接口十分简单,可以下载我的项目源码awhitney42/codeigniter-restserver-resources,从现在就开始开发吧!

后记

REST是一种遵从传统设计模式的架构风格,在过去的几年中一直都在改进,并且会一直坚持下去。所以如果对于REST或者是本文有什么意见,或者对于其中的概念有什么困惑,请在文章后面留言。

看到那把破木吉他

学习吉他,碰到的朋友大部分说不好学,的确如此,是理论方法困难吗?不,是实践!

我之前学习过吉他,目前水平仅就会弹几个和弦而已,但由此有些体会想分享一下。

简要来说,你只要知道吉他琴颈上每个格子之间代表的音,你就会了。

什么?你说我在扯蛋(可能确实是),我来说说我的理解,每个格子代表的音类似1234567,那么多格子,音当然很多,甭管具体是什么音,反正就是很多,理论上,你看着一个乐谱,能按照节奏用双手配合弹出谱子上写的响来,你就会了!

举个例子,你学习英语,理论上会了26个英文字母,你就会英语了,因为所有的词都是这26字母排列组合出来的,你记得的组合越多,英语越好,即使不会说和听,起码你会看啊,当然结合上语法读起来就会没什么障碍!

想到自己的本行,做程序员也如此,不管前端后端,什么编程语言,都是这26个字母在特定语法语义的不同实现。

加上特定程序的语义,就能和计算机交流,加上人能理解的语义,就能和英语系人种交流。说回音乐,底层知识,加上不同“语义”(乐器用法),就能和不同乐器交流,弹出动人心弦的声音。不是吗?

以上三样都是自己有所经历的事情,啊咧,说到这里,好像所有的事情都是这样,感觉自己说的都是废话呢!

ps:要买把好吉他,天知道你的左手指头会有多疼!

逆波兰表达式

今天在搜索PHP算法的时候,无意间进入了知乎,有人问,PHP需要算法?有人说需要,有人说不需要,这里不说结论,只谈看到的一个回复,里面提到了逆波兰表达式,原谅我搞PHP两年了还是第一次听到这个词(非计算机专业,算法知识也很弱,正在学习中),觉得很新鲜,所以想分享一下!

逆波兰表达式又称后缀表达式,它的解释器一般都是基于堆栈的,操作数入栈,遇到操作符时,操作数出栈,求值,将结果入栈;写法是运算符在数字的后面,表达式中无需使用小括号”()”,运算顺序也清楚明了,如中缀表达式1 + 2用逆波兰表达式则为 1 2 +,中缀表达式1 + 2 * 3用逆波兰表达式则为 1 2 3 * +,PS:在搜索中缀表达式时,看到百度百科的词条里面好像有错误,理解了这个例子能更好的理解逆波兰表达式,如下图(截图时间是2015-08-30 20:40):

它举了一个例子:中缀表达式8 + 4 – 6 * 2(=0)用逆波兰表达式则为6 2 * 8 4 + -(=0),按照这个逆波兰表达式,如果转为中缀表达式其实是(6 * 2) – (8 + 4)(=0),计算结果没有问题,但计算顺序错误。如果把中缀表达式换为8 + 4 – 6 * 3(= 负6),按照图中写法逆波兰表达式则为6 3 * 8 4 + -(= 正6),所以正确写法应是8 4 + 6 2 * – 或是8 4 + 6 3 * -(如果有理解不对的地方,请快速提醒我,以免误导他人)。

逆波兰表达式的优点(摘自维基百科,自由的百科全书):

用于表达式求值,以利用堆栈结构和减少计算机内存访问。

当有操作符时就计算,因此,表达式并不是从右至左整体计算而是每次由中心向外计算一部分,这样在复杂运算中就很少导致操作符错误。

堆栈自动记录中间结果,这就是为什么逆波兰计算器能容易对任意复杂的表达式求值。与普通科学计算器不同,它对表达式的复杂性没有限制。

逆波兰表达式中不需要括号,用户只需按照表达式顺序求值,让堆栈自动记录中间结果;同样的,也不需要指定操作符的优先级。逆波兰计算器中,没有“等号”键用于开始计算。

逆波兰计算器需要“确认”键用于区分两个相邻的操作数。

机器状态永远是一个堆栈状态,堆栈里是需要运算的操作数,栈内不会有操作符。

教育意义上,逆波兰计算器的使用者必须懂得要计算的表达式的含义。

https与http的简要区别

http是一种无状态协议,在OSI七层模型(由底到高:物理层,数据链路层,传输层,网络层,会话层,表示层,应用层)的最上层:应用层。

https是在http下加入了SSL层。

浏览器向服务器通过http协议发送请求(request),然后服务器响应(response),其中在网络层会有TCP协议,有大家熟悉的“三次握手”,相关的还有UDP协议,大家可以了解一下(OSI七层模型以后会单写)。

一次成功的http请求包含四步:

1,建立连接;

2,客户端发送http请求头;

3,服务器响应生成结果返回;

4,服务器端关闭连接,客户端解析,渲染页面;

https与http类似,只是在传递信息过程中采用了非对称和对称两种加密方式,使用前提是你申请了免费的或是购买了受浏览器信任的证书,其实就是一对密钥(公钥和私钥,不知我这么理解有没有错误),配置完成之后(这也可以写个小博文),打开网页就可以看到浏览器左上角显示https且是绿色的;

一次成功的https请求:

1,浏览器请求服务器;

2,服务器返回公钥(下面简称pub);

3,浏览器检查pub是否有效,有效,生成一个随机密钥(下面简称key),用pub把key加密后传递给服务器(data1);

4,服务器用私钥(下面简称pri)解密data1,获取key,把要返回的数据有key加密(data2)后返回给客户端; 5,客户端用key解密data2,获取返回的数据;

可以用下面这张图来说明(图是找的):

MySQL里group_concat函数的含义

MySQL里这个函数主要是在group时能看出它的作用,一般在我们使用group by时,同样的数据,只会显示一条,但某些时刻,我们却需要把同样的数据进行规类到一起,那么group_concat这个函数就派上用场了,例子如下:
CREATE TABLE test(
id int(11) unsigned not null auto_increment,
pid int(11) unsigned not null,
name varchar(20) not null,
primary key(id)
)ENGING=myisam default charset=utf8;
insert into `test`(`pid`, `name`) values (1, 'z1');
insert into `test`(`pid`, `name`) values (1, 'z2');
insert into `test`(`pid`, `name`) values (1, 'z3');
insert into `test`(`pid`, `name`) values (2, 'z4');
table name is test
id pid name
1 1 z1
2 1 z2
3 1 z3
4 2 z4
select name from test group by pid;
select group_concat(name) as name from test group by pid;

MySQL数据库表字段类型varchar存储中文和英文所占长度对比

CREATE TABLE `t1` (
  `str` varchar(10) DEFAULT NULL
) ENGINE=MyISAM DEFAULT CHARSET=utf8;
insert into `t1`(`str`) values ('一二三四五六七八九十');
insert into `t1`(`str`) values ('一二三四五六七八九十十一');
insert into `t1`(`str`) values ('abcdefghijklmnopqrst');
insert into `t1`(`str`) values ('1234567890123456');

结果是在varchar设置为10的长度时,中文和英文存储的长度是一样的。

git与git-svn的对比使用

上个公司,在我推广和普及git时,他们使用的是svn进行版本管理,我当时为了配合使用svn,就是选择使用的git-svn.

git及git-svn使用:

下载svn源码:git svn clone http://xxxx myproject (相当于svn checkout)

建立临时分支:git branch aaa
切换到临时分支:git checkout aaa
提交:git add .
提交确认:git commit
提交及确认:git commit -a (git add . + git commit,但新增文件必须要git add .)

切换回master分支:git checkout master
合并临时分支:git merge aaa
删除临时分支:git branch -d aaa

从svn更新: git svn rebase (相当于svn update)
提交至svn: git svn dcommit (相当于svn commit)

查看状态:git status
查看分支:git branch (*号代表现在在哪个分支上)
查看diff:git diff,git diff head

wordpress注册与修改密码发送邮件验证提示invalidkey

在网上搜索了一下,别人都已经提出并解决,如下:

一,注册

新用户注册邮件相关代码在网站安装目录的/wp-includes/pluggable.php

$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user->user_login), 'login') . ">\r\n\r\n";

修改为

$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user->user_login), 'login') . "\r\n\r\n";

找到上面那行,删除红色部分(大于号 >)一个字符即可。

二,修改

其实是邮箱发送的地址后面多了个”>”号,本来是WordPress为了美观,前后加上了尖括号,结果适得其反,被邮箱解析到地址里面去了,点击后自然会是无效的了。解决的方法很简单,把下面的代码加入当前主题的functions.php里面就可以了。

1,

/**
 * 修复WordPress找回密码提示“抱歉,该key似乎无效”问题
 */
function reset_password_message( $message, $key ) {
	if ( strpos($_POST['user_login'], '@') ) {
		$user_data = get_user_by('email', trim($_POST['user_login']));
	} else {
		$login = trim($_POST['user_login']);
		$user_data = get_user_by('login', $login);
	}
	$user_login = $user_data->user_login;
	$msg = __('有人要求重设如下帐号的密码:'). "\r\n\r\n";
	$msg .= network_site_url() . "\r\n\r\n";
	$msg .= sprintf(__('用户名:%s'), $user_login) . "\r\n\r\n";
	$msg .= __('若这不是您本人要求的,请忽略本邮件,一切如常。') . "\r\n\r\n";
	$msg .= __('要重置您的密码,请打开下面的链接:'). "\r\n\r\n";
	$msg .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ;
	return $msg;
}
add_filter('retrieve_password_message', reset_password_message, null, 2);

这种方法的缺点就是更换主题后需要重新添加代码。

当然,网上也有另外一种方法那就是修改WordPress目录下的wp-login.php。

2,

$message .= '<' . network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') . ">\r\n";

修改为

$message .= network_site_url("wp-login.php?action=rp&key=$key&login=" . rawurlencode($user_login), 'login') ;

其实也就是把'<‘ .和. “>\r\n”去掉,但是这种方法在升级Wordpress后会失效,因为升级后wp-login.php会被替换,需要重新修改wp-login.php,所以推荐使用第一种方法。

MongoDB与RDBMS概念对比

  1. 数据库:MongoDB中的database有着和你熟知的”数据库”一样的概念 (对Oracle来说就是schema,对MySQL来说也是database)。一个 MongoDB 实例中,可以有零个或多个数据库,每个都作为一个高等容器,用于存储数据。
  2. 集合:数据库中可以有零个或多个collections(集合)。集合和传统意义上的table基本一致,你可以简单的把两者看成是一样的东西。
  3. 文档:集合是由零个或多个documents(文档)组成。同样,一个文档可以看成是一row
  4. 字段:文档是由零个或多个fields(字段)组成。对应的是columns
  5. 索引:Indexes(索引)在MongoDB中扮演着和它们在RDBMS中一样的角色。
  6. 游标:Cursors(游标)和上面的五个概念都不一样,但是它非常重要,并且经常被忽视,因此我觉得它们值得单独讨论一下。其中最重要的你要理解的一点是,游标是当你问MongoDB拿数据的时候,它返回的一个结果集的指针,而不是真正的数据,我们可以拿游标做我们任何想做的事,比如说计数或者跨行之类的,而无需把真正的数据拖下来,在真正的数据上操作。