团队项目中的一些思考

最近在公司参与的一个项目,项目开发过程中遇到很多的问题,也开了无数的会议,包括各种需求会议,总结会议等等,现在算是进入了尾声了,不过本来预计今天上线的依然没能上线,虽然我内心感到非常遗憾,但是我依然向领导吐槽了许多不满。

开篇之前,对于项目开发,我要申明下我的几个观点。我这几个观点的前提条件是,在中小型项目中!

1.不需要产品经理

2.不需要测试

3.不需要专职的运维

4.不许要专职的DBA

————关于产品经理

我数次的在知乎上搜寻关于产品经理这一角色的信息,很想获取一些产品经理相关的知识,结果让我很失望。作为互联网界的一个新人(2.5年的开发经验),我所接触到的产品经理,无非是写个PRD,画个原型图,然后催催进度。仅此而已,至于说什么产品创新,ok,在国内不适合谈这个事。好吧,我承认我从主观上已经贬低了产品经理存在的价值。我依稀记得第一次听说产品经理这个词的时候,是一篇文章中提到的,那边文章是对马化腾的采访,马化腾说自己只是一个产品经理而已。具体年月已经不清晰了。

诚然,随着人类社会的进步,社会分工逐渐细化,产品经理这个角色的出现是有其合理性的(我认为在大型项目中此角色的存在是有必要性的,我心目中什么是大项目?比如我的第一家公司中接的摩托罗拉面向整个欧洲的一个通信项目,这个项目的总参与人数是2000+,参与者遍及北美,欧洲,印度和中国,耗时两年多。当然我没有能参与这个项目)。排除我待过的其中一家公司没有产品经理(我们开发要自己写function specification, development plan.),另外公司我都曾经问过产品经理:你们每天的工作是什么?核心答案基本就这3个:写需求文档,画原型,跟进度。

国内互联网公司的产品开发流程基本也差不多:当产品经理从客户或者运营那里收集到了需求,规模烧大点的话,会有BRD的移交,加上自己的一些“微创新”,好吧,很多需求是从BOSS那里来的。然后产品经理再和开发过需求,递交PRD并评审。这其中如果产品经理和需求方的理解有出入,再加上产品经理和开发的理解有出入,那么这边的沟通导致的问题是很大的。ok,多了产品经理,沟通成本上去了,项目风险增加了!为了减少风险,应该是有一个需求确认会议的,但是这个环节,很多公司都没有的,比如我这次参与的项目。然后到了开发手里,首先开发要根据业务逻辑关系确定数据结构,确定开发方案,并定下开发计划和预估开发时间。来看看我现在接手的那些项目,都是在PRD移交的时候,就尼马问我们开发,要!时!间!啊!有时候PRD是没有的,有时候PRD尼马几十上百页doc啊!原型会有一个,尼马很多链接的交互都没有啊!我艹!好了,冷静,不吐槽!反思下,我们做一个CMS性质的项目,做一个app,服务端连通的项目,做一个网站的改版,3-5人的开发小团队……需要这样的流程么?如果只是由开发负责整个过程,会不会能够更高效的执行呢?由开发和设计收集需求,设计人员自己出设计搞,开发自己定方案,然后去确定数据结构,进行开发。不仅减少了沟通成本,而且,开发能更深入的理解业务。何乐而不为?国内有没有这样的公司?我知道的有百姓网,豌豆荚。就我这次的项目来说,产品经理不参与开发,但是产品经理却定方案,直接导致了我这次参与的项目,需要在提交测试的时候由开发来手动往数据库写数据这种滑天下之大稽的事情的发生!PS:产品经理为什么会存在?知乎答案

————关于测试

不得不说我所在的第一家公司是中国的一支开发界的正规军,只可惜它是一家外包公司。公司有专职的QA(quality assurece)团队,他们负责公司项目的黑盒白盒测试,写大量的代码(test case),涵盖各种语言。任何一个测试转到开发都是分分钟的事情,这是酷壳博主口中的QA,当然,如果现在公司有这样的QA,幸甚!我们回到现实,看看国内大部分的互联网公司的测试是什么样的?懂代码的凤毛麟角,每天测试的工作就是,浏览器兼容性,各个功能点的黑盒测试,ok,稍微好点的有边界测试。我曾经在上家公司遇到过最奇葩的测试是什么情况?清!浏!览!器!缓!存!都!不!会!啊!更不要说清dns缓存,造cookie神码的了!!!连这些都需要沟通,更别提数据层面一些问题的沟通导致的成本和风险增加了!我理想的测试便是前面酷壳博主的文章中的情形了。

————关于运维和DBA

首先,我想问问各位如果在一个500人以内的小公司里面,是否有专职的运维和DBA呢?他们忙吗(一天上线十个需求的除外)?就我目前来看,我们开发完全可以吧运维和dba的工作做掉,管理mysql一些账户权限,优化一些sql,OMG这些需要DBA么?看看阿里的DBA都在做什么吧!其实,或许我们需要的只是一个mysql顾问。运维?管理个几十台服务器?搭个集群?配一些运行环境?配置一些代理?Rsync同步下代码?维护代码版本库?这些按道理都是开发分内的事情啊!运维该干什么?当公司项目上到500台服务器,再提运维这个岗位好么?

In a word,中小型项目中,如果开发团队中都是技能合格,有责任心有执行力的同学,完全能够高效的完成项目!作为一个web开发者,个人认为,基本的前端布局要懂的,js效果要会写的,闭包,原型,要理解的!ajax神码的不用说了,http请求流程要清楚的,后端不提,mysql基本的管理能力要有的,基本的使用和常用的函数要会的;主流的debian,红帽系服务器,搭个环境,定位个log,vim操作编辑个文件,这些都应该情理之中要掌握的。我还想强调一点,作为一个程序员,要有责任心,对自己开发过的功能,项目负责到底!你的负责会减轻很多他人工作!不要做码农,要做professional software develop engineer !

写在最后:我很期望我所在的团队,可以做一个短小精干,能独立跟项目,让PM,让DBA,让OP, 让QA都靠边站的团队。Yes, we can!!!

PS1:年内有个想法,在公司内分享下我对web开发的一个愿景和实现:用git做版本控制,xdebug做debug工具,phpDocument做文档工具,测试驱动开发,phpunit写Unit test,用Phing来做自动构建,jenkins/travis来做持续集成和自动部署。。

PS2:各位可以随意喷,尤其是产品狗,博主还年轻,不足之处,可以很快改正!

PS3:维基百科的软件开发流程

CodeIgniter中的配置类

CI中的配置文件默认都在application/config/路径下,当然也可以在你的自定义路径下,其中config.php是自动加载的,如果需要程序初始化的时候自动加载你需要的配置文件,那么在autoload.php中,在$autoload[‘config’] = array();中加入你需要的文件名即可。

在自定义配置文件中需要注意的一点就是,你所有的配置数据都需要放到全局的配置变量$config中,比如在application/config/路径下,新建了一个配置文件customized.php.内容如下:

$customize = array(
    'customize1' => 1,
    'customize2' => 2,
    'customize3' => 3,
    'customize4' => 4,
    'customize5' => 5,
    'customize6' => 6,
    'customize7' => 7,
);

假定手动加载该文件$this->config->load(‘customized’);会发现有如下报错

Your application/config/sns.php file does not appear to contain a valid configuration array.

提示这个配置文件没有包含一个合法的配置数组,那么合法的配置数组是怎样的呢,通过看核心配置类源码system/core/Config.php中的load方法

/**
	 * Load Config File
	 *
	 * @access	public
	 * @param	string	the config file name
	 * @param   boolean  if configuration values should be loaded into their own section
	 * @param   boolean  true if errors should just return false, false if an error message should be displayed
	 * @return	boolean	if the file was loaded correctly
	 */
	function load($file = '', $use_sections = FALSE, $fail_gracefully = FALSE)
	{
		$file = ($file == '') ? 'config' : str_replace('.php', '', $file);
		$found = FALSE;
		$loaded = FALSE;

		$check_locations = defined('ENVIRONMENT')
			? array(ENVIRONMENT.'/'.$file, $file)
			: array($file);

		foreach ($this->_config_paths as $path)
		{
			foreach ($check_locations as $location)
			{
				$file_path = $path.'config/'.$location.'.php';

				if (in_array($file_path, $this->is_loaded, TRUE))
				{
					$loaded = TRUE;
					continue 2;
				}

				if (file_exists($file_path))
				{
					$found = TRUE;
					break;
				}
			}

			if ($found === FALSE)
			{
				continue;
			}

			include($file_path);

			if ( ! isset($config) OR ! is_array($config))
			{
				if ($fail_gracefully === TRUE)
				{
					return FALSE;
				}
				show_error('Your '.$file_path.' file does not appear to contain a valid configuration array.');
			}

			if ($use_sections === TRUE)
			{
				if (isset($this->config[$file]))
				{
					$this->config[$file] = array_merge($this->config[$file], $config);
				}
				else
				{
					$this->config[$file] = $config;
				}
			}
			else
			{
				$this->config = array_merge($this->config, $config);
			}

			$this->is_loaded[] = $file_path;
			unset($config);

			$loaded = TRUE;
			log_message('debug', 'Config file loaded: '.$file_path);
			break;
		}

		if ($loaded === FALSE)
		{
			if ($fail_gracefully === TRUE)
			{
				return FALSE;
			}
			show_error('The configuration file '.$file.'.php does not exist.');
		}

		return TRUE;
	}

加载自定义配置的这个方法,非要在自定义配置文件中找到$config这个变量才罢休,否则就是不合法的。所以,只需要在我们原来的自定义配置文件加上一行就ok了,如下

$customize = array(
    'customize1' => 1,
    'customize2' => 2,
    'customize3' => 3,
    'customize4' => 4,
    'customize5' => 5,
    'customize6' => 6,
    'customize7' => 7,
);
$config['customize'] = $customize;

如此写法,不免觉得有些僵硬。。。

PS:现在Ellislab已经放弃了CI的维护,希望找到到下一个维护CI的组织,CI3.0已经看不到希望了。

如何用linux神器AWK查询开房记录

前一段时间火热的2000w开房记录,加菲同学给了我一份,话说当初拿到文件的时候,我二话没说,写了一个php脚本,在windows下往mysql插,不曾想,插了1w余条便502而死。而后又改进脚本,在cli模式下,开了11个窗口,跑了十几分钟的样子终于都入了mysql,但是在未建索引的情况下,搜索varchar类型的字段,每次搜索都超过2分钟。如此低效,令人发指。正好近期学习完awk章节,期间拿记录文件来测试,效率极高,今日总结此文,权当巩固知识之用了,如若看官能从此文习得一招两式,荣幸之至。

关于AWK,介绍如下:

AWK的作者(Alfred V.Aho && Peter J.Weinberger && Brain W.Kernighan),他是一种模式扫描与处理语言,搜索一个或者多个文件,以查看这些文件中是否存在匹配指定模式的记录(通常是文本行)。每次发现匹配的记录时,它通过执行动作的方式(比如将该记录写到标准输出或者将某个计数器递增)来处理文本行。与过程语言相反,AWK属于数据驱动语言:用户描述想要处理的数据并告诉AWK当它发现这些数据时如何处理他们。

我手里拿到的是一份2000w开放记录的csv文件压缩包(对于其完整性不要太抱期望),因为此次泄漏的记录本身只是部分时间段的部分文件而已。文件列表如下

-rw-------  1 aladdin aladdin 303M Jun 27 21:23 1000W-1200W.csv
-rw-------  1 aladdin aladdin 294M Jun 27 21:30 1200W-1400W.csv
-rw-------  1 aladdin aladdin 333M Jun 27 20:24 1-200W.csv
-rw-------  1 aladdin aladdin 306M Jun 27 21:43 1400W-1600W.csv
-rw-------  1 aladdin aladdin 296M Jun 27 22:07 1600w-1800w.csv
-rw-------  1 aladdin aladdin 285M Jun 27 22:20 1800w-2000w.csv
-rw-------  1 aladdin aladdin 297M Jun 27 20:32 200W-400W.csv
-rw-------  1 aladdin aladdin 297M Jun 27 20:49 400W-600W.csv
-rw-------  1 aladdin aladdin 295M Jun 27 21:02 600W-800W.csv
-rw-------  1 aladdin aladdin 297M Jun 27 21:15 800W-1000W.csv
-rw-------  1 aladdin aladdin 7.2M Jun 27 22:25 last5000.csv

如果你迫不及待想用你的姓名,生日,身份证号等等信息查询你是否在记录中,一条语句便可

awk '/王X/ && /靖江/' 1000W-1200W.csv 1200W-1400W.csv 1-200W.csv 1400W-1600W.csv 1600w-1800w.csv 1800w-2000w.csv 200W-400W.csv 400W-600W.csv 600W-800W.csv 800W-1000W.csv last5000.csv

这语句很易懂,就是在这些数据文件中将匹配模式  ‘/王X/ && /靖江/’  的记录打印出来,我们先来看结果,10秒多的时间,搜寻近3G的文件内容之后,cpu和内存占用没有明显的数据变化,结果已然如列:

王X,,,ID,321XXXXXXXXXXX5028,F,19xxxxxx,江苏省靖江市XXXXXX,,F,,CHN,32,3201,,,,,,,,,,汉,,,,,,,0,2012-12-29 17:50:49,13773207
王X,,,ID,321XXXXXXXXXXX5218,M,19xxxxxx,江苏省靖江市XXXXXXXXXXXX,,F,,CHN,32,321282,,,,,,,,,,汉,,,,,,,0,2012-6-6 13:52:38,14885090
王X,,,ID,321XXXXXXXXXXX043X,M,19xxxxxx,江苏省靖江市XXXXXXXXXXX,,F,,CHN,32,3201,,,,,,,,,,汉,,,,,,,0,2011-1-19 3:05:42,5593856
王X,,,ID,321XXXXXXXXXXX1817,M,19xxxxxx,江苏省靖江市XXXXXXXXXX,,F,,CHN,32,320105,,,,,,,***********,,,汉,,,,,,,0,2011-6-12 13:52:38,8115281
王X,,,ID,320XXXXXXXXXXXX92X,F,19xxxxxx,江苏省靖江市XXXXXXXXXXXX,,F,,CHN,32,3201,,,,,,,,,,汉,,,,,,,0,2011-9-14 7:21:50,9908197

姓名,身份证号,性别,生日,户口住址,开房时间……一目了然,我们来分析下这个语句

awk '/pattern/' file-lists  # 这里的pattern支持与/或等各种逻辑,斜杠表示:这里是个正则表达式

其实这里是有个缺省动作{print}的,打印(即复制到标准输出)匹配出的记录,原理了然。

 

为方便实验,我们下面拷贝下last5000.csv这个文件,用拷贝的文件来做实验。假定拷贝的文件是test.csv.用下面这个命令

awk '{print}' test.csv | less

大概看下文件,列一下字段名,内容截屏就不放了

Name,CardNo,Descriot,CtfTp,CtfId,Gender,Birthday,Address,Zip,Dirty,District1,District2,District3,District4,District5,District6,FirstNm,LastNm,Duty,Mobile,Tel,Fax,EMail,Nation,Taste,Education,Company,CTel,CAddress,CZip,Family,Version,id

将文件中的字段分割符用制表符代替,生成tmp.csv文件

sed 's/,/\t_/g' test.csv > tmp.csv

通过awk得到我们需要的字段,存到dealed.csv文件中

awk '{print $1,$5,$6,$7,$8,$32}' > dealed.csv

好的,现在有了处理好的文件dealed.csv,那么我们来把玩一番

先来看下男女比例

 awk '$3 ~ /_M/' dealed.csv | wc && awk '$3 ~ /_F/' dealed.csv | wc

输出

31179  187081 2792791
16085   96513 1455873

我们看到,是31179:16085,没有性别的忽略了,仅最小的这个文件来看,男比女大概是2:1的样子,额,说明了什么问题?看官自己发挥想象力吧

再来看看年份情况

awk '$4 ~ /^(_196)/' dealed.csv | wc && awk '$4 ~ /^(_197)/' dealed.csv | wc && awk '$4 ~ /^(_198)/' dealed.csv | wc && awk '$4 ~ /^(_199)/' dealed.csv | wc

输出

4885   29310  447630
9706   58239  884200
20533  123201 1831734
8100   48601  784771

明显看出80后占据开房主力地位!

再来看各年龄层女性占比

awk '$4 ~ /^(_196)/ && $3 ~ /_F/' dealed.csv | wc && awk '$4 ~ /^(_197)/ && $3 ~ /_F/' dealed.csv | wc && awk '$4 ~ /^(_198)/ && $3 ~ /_F/' dealed.csv | wc && awk '$4 ~ /^(_199)/ && $3 ~ /_F/' dealed.csv | wc

输出

   1359    8154  122638
   2922   17533  259771
   7124   42746  634798
   3736   22416  360216

擦,90后女生比例明显高了。

ok,这边先这样。

 

来看看awk 带有-f选项的用法,又能对开放记录做什么手术呢?

引入文件ald

{
if ($4 ~ /^(_199)/ && $3 ~ /_F/) $1 = "阿拉丁和"$1"开房了,今天是"$6
if ($1 ~ /阿拉丁/) print
}

然后awk -f之

awk -f ald dealed.csv | less

xx

我靠,楼主一下子和n个90后妹纸开房了,开个玩笑~

楼主表示,不会泄漏任何信息,也别向楼主提出任何查询请求,本文仅用于技术探讨,over。

 

CodeIgniter保留字

控制器

  • Controller
  • CI_Base
  • _ci_initialize
  • Default
  • index

函数

  • is_really_writable()
  • load_class()
  • get_config()
  • config_item()
  • show_error()
  • show_404()
  • log_message()
  • _exception_handler()
  • get_instance()

变量

  • $config
  • $mimes
  • $lang

常量

  • ENVIRONMENT
  • EXT
  • FCPATH
  • SELF
  • BASEPATH
  • APPPATH
  • CI_VERSION
  • FILE_READ_MODE
  • FILE_WRITE_MODE
  • DIR_READ_MODE
  • DIR_WRITE_MODE
  • FOPEN_READ
  • FOPEN_READ_WRITE
  • FOPEN_WRITE_CREATE_DESTRUCTIVE
  • FOPEN_READ_WRITE_CREATE_DESTRUCTIVE
  • FOPEN_WRITE_CREATE
  • FOPEN_READ_WRITE_CREATE
  • FOPEN_WRITE_CREATE_STRICT
  • FOPEN_READ_WRITE_CREATE_STRICT

Nginx,CodeIgniter,PATH_INFO

1.什么是PATH_INFO?

PATH_INFO是一个CGI 1.1的标准,经常用来做为传参载体.

例如www.coderaladdin.com/test.php,并且test.php这个文件存在.当服务器接收这样的请求www.coderaladdin.com/test.php/path/info,那么PATH_INFO的值就是/path/info.

2.Nginx中的PATH_INFO

ngxin中默认配置中式没有PATH_INFO的设置的,在默认配置下,接收到如上请求是则会报404,那么如何解决这个问题呢,网上有多种方法可以参考,nginx官方的方法是通过fastcgi_split_path_info 来配置PATH_INFO

fastcgi_split_path_info ^((?U).+\.php)(/?.+)$;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
fastcgi_param PATH_TRANSLATED $document_root$fastcgi_path_info;

默认配置中的

location ~ .php$ {
    ……
}

要记得改成

location ~ .php {
    ……
}

3.CodeIgniter中的路由原理

具体路由原理不详述,只说在lnmp中CI能够正常使用rewrite的方法有二

  1. 如上所述,配置nginx支持PATH_INFO
  2. 在CI中修改/application/config/config.php,将
    $config['uri_protocol']	= 'AUTO';

    修改为

    $config['uri_protocol']	= 'REQUEST_URI';

     

原生PHP和Zend Framwork中使用SOAP做webservice

soap的两种实现方式:WSDL和Non-WSDL

1.non-wsdl模式利用php的soap扩展实现:

server

<?php
class HandleClass {
    public function test() {
        return '23434';
    }
}

//uri必填
$soap_server = new SoapServer(null, array('uri'=>'http://soap/','location'=>'http://localhost/soap/server.php'));
$soap_server->setClass('HandleClass');
$soap_server->handle();

client

<?php
//location和uri必填
$soap_client = new SoapClient(null, array('location'=>'http://localhost/soap/server.php','uri'=>'http://soap/'));
echo $soap_client->test();

output:

23434

2.wsdl利用php的soap扩展实现

server:

<?php
class service {
	public function HelloWorld() {
		return "Hello";
	}
}
$server = new SoapServer ('helloworld.wsdl');
$server->setClass ( "service" );
$server->handle ();

client:

<?php
$client = new SoapClient ( "helloworld.wsdl" );
echo $client->HelloWorld ();

wsdl:

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<wsdl:definitions xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" xmlns:tns="http://www.example.org/helloworld/" xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/" xmlns:xsd="http://www.w3.org/2001/XMLSchema" name="helloworld" targetNamespace="http://www.example.org/helloworld/">
  <wsdl:types>
    <xsd:schema targetNamespace="http://www.example.org/helloworld/">
      <xsd:element name="HelloWorld">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="in" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
      <xsd:element name="HelloWorldResponse">
        <xsd:complexType>
          <xsd:sequence>
            <xsd:element name="out" type="xsd:string"/>
          </xsd:sequence>
        </xsd:complexType>
      </xsd:element>
    </xsd:schema>
  </wsdl:types>
  <wsdl:message name="HelloWorldRequest">

  </wsdl:message>
  <wsdl:message name="HelloWorldResponse">
    <wsdl:part name="HelloWorldReturn" type="xsd:string"/>
  </wsdl:message>
  <wsdl:portType name="helloworld">
    <wsdl:operation name="HelloWorld">
      <wsdl:input message="tns:HelloWorldRequest"/>
      <wsdl:output message="tns:HelloWorldResponse"/>
    </wsdl:operation>
  </wsdl:portType>
  <wsdl:binding name="helloworldSOAP" type="tns:helloworld">
    <soap:binding style="document" transport="http://schemas.xmlsoap.org/soap/http"/>
    <wsdl:operation name="HelloWorld">
      <soap:operation soapAction="http://www.example.org/helloworld/HelloWorld"/>
      <wsdl:input>
        <soap:body use="literal"/>
      </wsdl:input>
      <wsdl:output>
        <soap:body use="literal"/>
      </wsdl:output>
    </wsdl:operation>
  </wsdl:binding>
  <wsdl:service name="helloworld">
    <wsdl:port binding="tns:helloworldSOAP" name="helloworldSOAP">
      <soap:address location="http://localhost/soap/wsdlServer.php"/>
    </wsdl:port>
  </wsdl:service>
</wsdl:definitions>

output:

Hello

BTW,zendstudio 10中可以通过插件的 方式支持wsdl的图形化编辑,见下图QQ截图20130720164549

3.non-wsdl模式利用zend的soap组件实现

handle:

<?php
class Example_Manager {

	/**
	 * Returns list of all products in database
	 *
	 * @return array
	 */
	public function getProducts() {
		$db = Zend_Registry::get ( 'Zend_Db' );
		$sql = "SELECT * FROM products";
		return $db->fetchAll ( $sql );
	}

	/**
	 * Returns specified product in database
	 *
	 * @param integer $id        	
	 * @return array Exception
	 */
	public function getProduct($id) {
		if (! Zend_Validate::is ( $id, 'Int' )) {
			throw new Exception ( 'Invalid input' );
		}
		$db = Zend_Registry::get ( 'Zend_Db' );
		$sql = "SELECT * FROM products WHERE id = '$id'";
		$result = $db->fetchAll ( $sql );
		if (count ( $result ) != 1) {
			throw new Exception ( 'Invalid product ID: ' . $id );
		}
		return $result;
	}
}

 

server:

public function soapserverAction()
    {
    	$this->getHelper('viewRenderer')->setNoRender(true);

    	$server = new Zend_Soap_Server(null, array('uri' => 'http://zenddemo.com/index/soapserver', 'location'=> 'http://zenddemo.com/index/soapserver'));

    	$server->setClass('Example_Manager');

    	$server->handle();
    }

client:

public function soapclientAction() {
    	$this->getHelper('viewRenderer')->setNoRender(true);
    	$options = array(
    			'location' => 'http://zenddemo.com/index/soapserver',
    			'uri'      => 'http://zenddemo.com/index/soapserver'
    	);

    	try {
    		$client = new Zend_Soap_Client(null, $options);
    		$result = $client->getProducts();
    		print_r($result);
    	} catch (SoapFault $s) {
    		die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
    	} catch (Exception $e) {
    		die('ERROR: ' . $e->getMessage());
    	}
    }

 4.wsdl模式用zend的soap组件实现

handle:同上

server:

public function soapwsdlserverAction() {
    	$this->getHelper('viewRenderer')->setNoRender(true);

    	$server = new Zend_Soap_AutoDiscover();
    	$server->setClass('Example_Manager');
    	$server->setUri('http://zenddemo.com/index/soapserver');
    	$server->handle();
    }

client:

public function soapclientAction() {
    	$this->getHelper('viewRenderer')->setNoRender(true);

    	try {
    		$client = new Zend_Soap_Client('http://zenddemo.com/index/soapwsdlserver');	
    		$result = $client->getProducts();
    		print_r($result);
    	} catch (SoapFault $s) {
    		die('ERROR: [' . $s->faultcode . '] ' . $s->faultstring);
    	} catch (Exception $e) {
    		die('ERROR: ' . $e->getMessage());
    	}
    }

 

 

Little tips

1.昨天遇到的一个问题,session文件无法写入

原因:我在php.ini中设置了一个session路径,但是在windows下用的linux的正斜杠,一直以为windows能正确解析路径,因为我在apache中配虚拟主机的时候都是用的正斜杠,囧

 

2.svn的一个小技巧,当把错误代码提交到服务器时,想要回滚版本,show log->revert to this version(想要回滚到的版本)->commit

开源JS日历控件ESONCalendar的bug修复

Bug description:

在选择月份的日期数小于当前日期数时会跳到下月份,比如当前日期是1月30号,当选择2月时,会自动跳到3月,2月只有28或29天小于当前日期的30号

Code:

/*
 * @use:
 * <input type="text" name="riqi" id="Text1" size="31" style="margin:2px auto 2px auto;" value="2013-01-29">
 * <script type="text/javascript"> 
 *  ESONCalendar.bind("Text1");
 * </script>
 */
var _ESONCalendar = window.ESONCalendar = {
    hasFoot: true,
    weeks: "日一二三四五六",
    months: "一,二,三,四,五,六,七,八,九,十,十一,十二",
    start: 1900,
    end: 2050,
    color: {
        caption: "#A4B9D7",
        border: "#C0D0E8",
        tablebg: "#F6F6F6",
        selectedbg: "#FF9900",
        foot_co: "#003366",
        selectedco: "#ffffff"
    },
    dateBox: [],
    splitChar: "-",
    splitChar2: ":",
    hotInput: null,
    initli: false,
    init: function () {
        return this.addStyle().addUI().hide();
    },
    uanv_tool_CE: function (type, id, parent, className, HTML) {//SELECT, selMonth, _caption
		//console.log(type + '==' + id + '==' + parent + '==' + className + '==' + HTML)
        var obj = document.createElement(type.toUpperCase());
        id && (obj.id = id);
        className && (obj.className = className);
        HTML && (obj.innerHTML = HTML);
        parent || (parent = document.body);
        return parent.appendChild(obj);
    },
    uanv_tool_getWeek: function (date, i) {
        var tmp = new Date(date);
        tmp.setDate(i);
        return tmp.getDay();
    },
    uanv_tool_isIn: function (o, parent) {
        while (o != parent && o != document.body) {
            o = o.parentNode
        };
        return o != document.body;
    },
    onselect: function (d) {
        this.hotInput && (this.hotInput.value = d.y + this.splitChar + d.m + this.splitChar + d.d), this.hide()
    },
    addStyle: function () {
        var cssText = "#ESONCalendar_Win{background:" + this.color.caption + ";position:absolute;z-index:9999}";
        cssText += "#ESONCalendar_caption{padding:3px;background:" + this.color.caption + ";overflow:hidden;}";
        cssText += ".clear{clear:both}";
        cssText += "#selMonth{margin-right:5px;width:80px}";
        cssText += "#selYear{margin-right:3px;}";
        cssText += "#ESONCalendar_table{background:" + this.color.tablebg + ";border-collapse:collapse;border:1px solid " + this.color.border + "}";
        cssText += "#ESONCalendar_table th{border:1px " + this.color.border + " solid}";
        cssText += "#ESONCalendar_week{background:" + this.color.border + "}";
        cssText += "#ESONCalendar_week th{font-size:12px;width:18px;height:18px;}";
        cssText += "#dateBox{font:normal 12px /120% 'arial';}";
        cssText += "#dateBox th{font-weight:normal}";
        cssText += "#dateBox .unselected{cursor:pointer;background:" + this.color.tablebg + ";}";
        cssText += "#dateBox .sunday{cursor:pointer;background:" + this.color.tablebg + ";color:red}";
        cssText += "#dateBox .current,#dateBox .selected{cursor:pointer;background:" + this.color.selectedbg + ";color:" + this.color.selectedco + "}";
        cssText += "#ESONCalendar_foot{padding:2px 0 2px 0;line-height:130%;text-align:center;font-size:11px;color:" + this.color.foot_co + ";background:" + this.color.border + "}";
        cssText += "#ESONCalendar_Win iframe{position:absolute;z-index:-1;top:0;left:0}";
        var STYLE = document.createElement('style');
        STYLE.setAttribute("type", "text/css");
        STYLE.styleSheet && (STYLE.styleSheet.cssText = cssText) || STYLE.appendChild(document.createTextNode(cssText));
        document.getElementsByTagName('head')[0].appendChild(STYLE);
        return this;
    },
    addUI: function () {
        if (this.initli) {
            return;
        }
        this.Win = this.uanv_tool_CE("DIV", "ESONCalendar_Win");
        KillSelectIframe = this.uanv_tool_CE("IFRAME", false, this.Win);
        var _caption = this.uanv_tool_CE("DIV", "ESONCalendar_caption", this.Win);
        var selMonth = this.uanv_tool_CE("SELECT", "selMonth", _caption);
        var selYear = this.uanv_tool_CE("SELECT", "selYear", _caption);
        this.selMonth = selMonth;
        this.selYear = selYear;
        selMonth.onchange = selYear.onchange = function () {
            ESONCalendar.dateUp(new Date(selYear.value, selMonth.value, 1))
        };
        for (var i = 0; i < 12; i++) {
            var tmp = new Option(this.months.split(",")[i] + "月", i);
            selMonth.options.add(tmp);
        };
        for (var i = this.start; i < this.end; i++) {
            var tmp = new Option(i, i);
            selYear.options.add(tmp);
        };
        this.uanv_tool_CE("DIV", false, _caption, "clear");
        var table = this.uanv_tool_CE("TABLE", "ESONCalendar_table", this.Win);
        var tbody = this.uanv_tool_CE("TBODY", false, table);
        var tr = this.uanv_tool_CE("TR", "ESONCalendar_week", tbody);
        for (var i = 0; i < 7; i++) {
            var th = this.uanv_tool_CE("TH", false, tr, false, new String(this.weeks).charAt(i));
        }
        tbody = this.uanv_tool_CE("TBODY", "dateBox", table);
        for (var i = 0; i < 6; i++) {
            tr = this.uanv_tool_CE("TR", false, tbody);
            for (var j = 0; j < 7; j++) {
                var thisBox = this.uanv_tool_CE("TH", false, tr, false, "&nbsp;");
                this.dateBox[i * 7 + j] = thisBox;
            }
        };
        if (this.hasFoot) {
            this.foot = this.uanv_tool_CE("DIV", "ESONCalendar_foot", this.Win, false, this.footText);
        }
        KillSelectIframe.frameBorder = 0;
        KillSelectIframe.width = this.Win.offsetWidth;
        KillSelectIframe.height = this.Win.offsetHeight;
        document.onclick = document.body.onclick = function (e) {
            e || (e = window.event);
            var src = e.target || e.srcElement;
            var tmp = src.nodeName.toUpperCase();
            if (tmp == "HTML" || tmp == "BODY") {
                return ESONCalendar.hide();
            }
            if (src == ESONCalendar.hotInput || ESONCalendar.uanv_tool_isIn(src, ESONCalendar.Win)) {
                return;
            }
            ESONCalendar.hide();
        };
        this.initli = true;
        return this;
    },
    dateUp: function (date, first) {
        var space = this.uanv_tool_getWeek(date, 1);
        var m2d = 31, index = 1;
        this.y = date.getFullYear();
		this.m = date.getMonth() + 1;
		this.d = date.getDate();
        this.h = date.getHours();
        this.mi = date.getMinutes();
        this.s = date.getSeconds();
        this.selMonth.options[this.m - 1].selected = "selected";
        this.selYear.options[this.y - this.start].selected = "selected";
        var isRN = (this.y % 4 == 0 && this.y % 4 != 100 || this.y % 100 == 0 && this.y % 400 == 0);
        if (/-4|-6|-9|-11/.test("-" + this.m)) {
            m2d = 30
        };
        if (this.m == 2) {
            m2d = isRN ? 29 : 28
        };
        for (var i = 0; i < 42; i++) {
            var _this = this.dateBox[i];
            _this.isSunday = _this.className = _this.isInMonth = _this.onmouseover = _this.onmouseout = _this.onclick = null;
            if (i < space || i > (m2d + space - 1)) {
                _this.uanv_tool_isInMonth = false;
                _this.innerHTML = "&nbsp;";
                continue
            };
            _this.innerHTML = index++;
            _this.className = "unselected";
            _this.isInMonth = true;
            var week = this.uanv_tool_getWeek(date, _this.innerHTML);
            if (week == 0 || week == 6) {
                _this.className = "sunday";
                _this.isSunday = true;
            }
            if (first && (index - 1) == this.d) {
                _this.className = "selected";
            }
            _this.onmouseover = function () {
                if (this.className != "selected") this.className = "current"
            };
            _this.onmouseout = function () {
                if (this.className != "selected") this.className = this.isSunday ? "sunday" : "unselected"
            };
            _this.onclick = function () {
                var allD = ESONCalendar.dateBox;
                for (var i = 0; i < allD.length; i++) {
                    var _for = allD[i];
                    _for.className = "";
                    if (_for.isInMonth) {
                        _for.className = "unselected";
                    }
                    if (_for.isSunday) {
                        _for.className = "sunday";
                    }
                };
                this.className = "selected";
                ESONCalendar.d = this.innerHTML;
                ESONCalendar.onselect({
                        y: ESONCalendar.y,
                        m: ESONCalendar.m,
                        d: this.innerHTML,
                        h: ESONCalendar.h,
                        mi: ESONCalendar.mi,
                        s: ESONCalendar.s
                    });
            };
        };
        return this;
    },
    showTo: function (obj) {
        var oldObj = obj;
        for (var pos = {
                x: 0,
                y: 0
            }; obj; obj = obj.offsetParent) {
            pos.x += obj.offsetLeft;
            pos.y += obj.offsetTop
        };
        this.Win.style.left = pos.x + "px";
        this.Win.style.top = (pos.y + 2 + oldObj.offsetHeight) + "px";
        this.Win.style.display = "";
        return this;
    },
    bind: function (input) {
        if (!this.initli) {
            this.init();
        }
        "string" == typeof (input) && (input = document.getElementById(input));
        if (!input.type || input.type.toUpperCase() != "TEXT") {
            return this;
        }
        input.onfocus = function () {
            var dates = this.value.split(ESONCalendar.splitChar);
            var bindD = this.value.length > 0 ? new Date(dates[0], dates[1] - 1, dates[2]) : new Date();
            ESONCalendar.dateUp(bindD, true);
            ESONCalendar.showTo(ESONCalendar.hotInput = this);
        };
        return this;
    },
    hide: function () {
        this.Win.style.display = "none";
        return this
    },
    setInfo: function (v) {
        this.foot.innerHTML = v;
        return this
    }
};

 

高德地图API使用

记录下高德地图api使用中踩到的坑

踩坑代码:

var mapObj=new AMap.Map("div_gmap",{center:position});//创建地图实例
var bounds=mapObj.getBounds();//获取map实例的可是区域地理坐标实例

果断报错了:一个未定义的da方法

 

解决问题的代码:

var mapObj=new AMap.Map("div_gmap",{center:position});//创建地图实例
mapObj.plugin(["AMap.ToolBar"],function(){  
		//加载工具条  
		tool=new AMap.ToolBar({});  
		mapObj.addControl(tool); 
		var bounds = mapObj.getBounds();
})

将获取视窗地理坐标实例的代码放到plugin中来执行,应该是一个异步加载的原理,未深究。

 

nginx php 502 bad gateway

今天配centos6.3+nginx+php5.4.15(fpm),蛋碎了无数次,其中一个问题就是配PMA的时候,访问phpMyAdmin时,出现下面错误。

phpMyAdmin – Error

Cannot start session without errors, please check errors given in your PHP and/or webserver log file and configure your PHP installation properly.第一次打开提示,刷新提示:

502 bad gateway

查看nginx error log:

[error]  recv() failed (104: Connection reset by peer) while reading response header from upstream, client:, server: , request: “GET / HTTP/1.1”, upstream: “fastcgi://127.0.0.1:9000”, host: “mysql.veryi.com”

查看php session.save_path的设置,默认是/var/lib/php/session。修改该目录nginx进程的用户有读写权限。问题解决。

参考