并发场景实践方案优劣评析

近期实践了高并发场景下,如何去实现抢购系统的研究。虽然方案并不算成功,但是本次能将以往只在资料中学习过的知识付诸于现实,也让自己畅快淋漓了一番。本篇文章所阐述的内容都比较基础,重在于将实践过程中所得到的经验和教训分享出来给大家。

以下将分为五个方面来回顾下本次高并发的实践经历。

  • 1.抢购场景特点
  • 2.技术选型
  • 3.方案思路
  • 4.优劣势辨析
  • 5.小结一下

一、抢购场景的特点

在日常生活中,电子商务营销手段中,抢购、促销是最为常用的提高销售的手段。最熟悉不过的当然就是天猫“双十一”购物节了。既然是“抢购”,用通俗的话来概括,那就是手快有手慢无。以下我们做了一些细分:

1.1 限时限量

  • 限用户样本
  • 限用户可购买量
  • 限商品总量
  • 限抢购时间

1.2 防伪防恶意

  • 防虚假用户购买
  • 防恶意系统攻击

而广大群众并不一定都跟着你的步伐走,可能他们会跟抢12306火车票的手段一样,在你的抢购活动中付诸于实践,往往这个会给系统带来四面楚歌般的无形压力,轻者响应延迟,重者宕机。

写到这里,复杂度已经可见一斑,内容涉及了各个方面,例如并发编程、安全、缓存、关系型数据存储、网络流量、防火墙、服务容器、连接池、IO通信。虽说内容很多,但是我们可以从中根据自己所面临的场景,选择其中一些合适的技术手段即可,不必面面俱到追求完美,它们都只是你实现目标的工具。

二、技术选型

了解基本的抢购场景知识之后,就是考虑怎么去解决场景中所遇到的实际问题了。对于抢购场景的特点,我们给每个特点归归类:

  • 限用户样本、限抢购时间,这2项可以算是参加抢购的前置条件,不满足则拒绝请求。

对于用户样本的限定,普通的条件性编码可以满足这项需要,因为这项属于前置条件,条件数据的属性当确定活动用户样本之后,它们就成为了静态数据,没有并发的意外风险。
对于抢购时间,这项条件可以借鉴业界对这方面的设计思路,将静态代码(html等静态页面内容)部署到CDN节点上,而仅保留一项js文件(该文件中包含了判断抢购开始的请求编码,当然里面可以包含一项随机token来保障代码所发出的请求的有效性)执行动态加载,以保障抢购可以准时开始。
对于抢购页面CDN静态之后所出现的无效点击、提前知道抢购页面等问题,这部分可以使用包含随机token串的页面url,用以保证抢购页面的有效性和请求的合法性。

  • 限用户可购买量、限商品总量,这2项是整个抢购场景中的核心前提,即“不可超售”的业务特点。

对于用户可购买量、商品总量的限制,这部分可以考虑使用编程锁的实现,如果考虑到多机部署的分布式需要,可以考虑使用分布式锁,同时加以“分而治之”的思路,可以提高请求的并发量。
关于锁的知识,我们常见的锁有悲观锁和乐观锁,而锁的实现方式可以是中间件、缓存、JDK自带同步锁、关系型数据库等方式。

  • 防虚假用户购买,这一特点旨在于系统如何去辨析真正的消费用户,而非临时创建的僵尸用户。

这部分比较难以实现,并不是简单的判别某个单位时间同一来源请求上限值、验证码校验这么一些常见的指标。如果要根本性解决这方面,可能需要使用一些机器学习方面的知识,分析出那类请求可以判别为“疑似攻击”,同时还需要准确率,这是一门深奥的学问,目前自己还没有太多的涉及就不深入谈论了。

  • 防恶意系统攻击,这项属于系统部署层面上需要考量的架构问题。如若不能及时识别出这是一次攻击而非正常消费请求,那么系统会面临宕机的风险。

关于恶意系统攻击,与前面谈及的“防虚假用户购买”有相同之处,“虚假用户购买”的量达到了一定的阈值,那么就可以归类为“恶意系统攻击”了。在WEB安全领域上,有许多常见的网络攻击都是需要去规避和解决的,无论是编码上还是运维部署层面上,例如DDoS、SQL注入、信息伪造等手段。

本次实践过程中,对于这一块并没有太多的涉及,只是通过一些流量设置上的控制,避免了服务容器由于请求压力过高而产生的宕机风险。这一块待日后自己的知识库成熟之后,再po文分享下自己的实践感触。

三、方案思路

3.1 基本思路和考量点
  • 不可超售,需要使用到线程安全、线程锁、本地线程、事务性、原子CAS特性、阻塞/非阻塞队列等知识;
  • 分而治之,将商品可销售总量分为多条管道同时进行销售,由负载均衡算法代理路由的逻辑;
  • 存储介质上,考量可持久化缓存、关系型数据库;
  • 在组件的配置方面,考量服务容器的并行连接池配置、缓存的连接池配置、关系型数据库连接池配置、编码线程池配置、JVM启动参数(例如JVM PermSize、Xms、Xmx内存项配置);
  • 在流程实现上,将一些有效判断节点分布在真实消费商品的逻辑之前,提高核心逻辑的有效执行占比,提高访问效率;
  • 在消费数据的持久化措施上,采用“生产者-消费者”的队列实现模式,异步的策略将消费数据尽可能快地持久化到日常使用的关系型数据库中,便于查看和其他业务系统的接口读取;
3.2 逻辑流程图

1

3.3 实现过程中可能需要额外关注的部分

Redis使用方面

  • 1.系统Redis的连接池配置,最大可用连接maxTotal要小于Redis服务配置redis.confmaxclients数值;
  • 2.系统Redis的连接超时时长maxWaitMillis要尽可能地根据系统可承载压力进行设置,一般不可超过5s,高并发的场景下建议在2s以下;
  • 3.为保障系统Redis客户端从池子中获取的连接不是broken的,参数testOnBorrow => true一定要加上;
  • 4.Jedis连接池的空闲释放算法采用的是apache common pool作的实现,GenericObjectPool是通过“驱逐者线程Evictor”管理“空闲池对象”的。在高并发场景下,选用LIFO可以更快地通过Evictor驱逐者任务将空闲无占用连接及时释放掉,而避免采用FIFO致使部分idle连接处在starvation状态下久久得不到释放,从而造成leaks of redis resources;
  • 5.如果你使用的不是Spring框架对Redis客户端进行scope="singleton"的集成,那么在创建连接池之时,也需要避免瞬间并发而导致的池子被多建的场景发生,因为每一个池子都会由于配置minIdle而固定占用着redis的连接数量;
  • 6.如果你使用的是分布式集群Redis,那么可以对连接池配置多机,如此可有效提高连接数和Redis服务性能;
  • 7.如果你使用的是Spring+Jedis(注:最受欢迎的Redis Client之一)的Redis编码方案,请留意ShardedJedisPoolJedisPool的区别,后者所取得的连接可以执行事务、多key批量等操作,而前者是为Redis分布式集群而生,每一次所获取的连接并不保证相同和唯一来源;

这里给出一个简单的Spring+Jedis的连接池配置例子:

  • Jedis连接池基础参数配置
<bean id="jedisConfig" class="redis.clients.jedis.JedisPoolConfig">
	<property name="maxTotal" value="10000" />
	<property name="maxIdle" value="10000" />
	<property name="minIdle" value="200" />
	<property name="testOnBorrow" value="true" />
	<property name="TestOnReturn" value="true" />
	<property name="maxWaitMillis" value="300" />
	<property name="testWhileIdle" value="true" />
	<property name="timeBetweenEvictionRunsMillis" value="20000" />
	<property name="minEvictableIdleTimeMillis" value="10000" />
	<property name="lifo" value="true" />
</bean>
  • ShardedJedisPool连接池
<bean id="shardedJedisPool" class="redis.clients.jedis.ShardedJedisPool" scope="singleton">
	<constructor-arg index="0" ref="jedisConfig" />
	<constructor-arg index="1">
		<list>
			<bean class="redis.clients.jedis.JedisShardInfo">
				<constructor-arg index="0" value="127.0.0.1" />
				<constructor-arg index="1" value="6379" />
				<constructor-arg index="2" value="300" />
			</bean>
		</list>
	</constructor-arg>
</bean>
  • JedisPool连接池
<bean id="jedisPool" class="redis.clients.jedis.JedisPool" scope="singleton">
	<constructor-arg index="0" ref="jedisConfig" />
	<constructor-arg index="1" value="127.0.0.1" />
	<constructor-arg index="2" value="6379" />
	<constructor-arg index="3" value="300" />
</bean>

服务容器的运行保障方面(本次所使用到的是Tomcat)

为了降低服务容器因为过载而造成的请求堵塞、容器停滞、宕机等风险,针对Tomcat的连接数方面做了些配置上的调整,以确保可控可监听:

  • Tomcat Executor连接线程池
<Executor name="tomcatThreadPool"  // 线程池名称
	namePrefix="catalina-exec-"  // 线程名称前缀
    maxThreads="3000"  // 最大连接线程数
    minSpareThreads="500" // 最小空闲线程数,初始化会达到该数值
    maxIdleTime="30000"  // 最大线程空闲时长
  />
  • Tomcat Connector连接配置
<Connector executor="tomcatThreadPool"  // 线程池引用
           port="8080" // 请求访问端口
           protocol="org.apache.coyote.http11.Http11NioProtocol" // 启用NIO协议
           connectionTimeout="5000"  // 创建连接时长
           keepAliveTimeout="10000"  // 连接活跃保持时长
           redirectPort="8443" // SSL转发端口
           acceptCount="2000"  // 额外的线程队列
           maxConnections="3000" // 最大连接线程数

           compression="on" compressionMinSize="2048" compressableMimeType="text/html,text/xml,text/plain,application/octet-stream"  // 启用响应报文压缩gzip
           />

与此同时,我们还对服务容器所占用的内存空间进行了分配和调整:

  • Tomcat Startup Properties启动参数
# Configurations of JVM properties.
# In order to increase JVM heap memory.
# 
# Edited by chenjinlong 20161216
# 
JAVA_OPTS="
-Xms512m             // Java Heap堆初始大小,默认是物理内存的1/64
-Xmx1024m            // Java Heap堆初始大小,建议可设为物理内存的一半,不可超过物理内存
-XX:MaxNewSize=128m  // 新生成的池的最大大小,默认32M
-XX:NewSize=64m      // 新生成的池的初始大小,默认2M
-XX:MaxPermSize=256m // 内存的永久保存区最大大小,默认64M
-XX:PermSize=128m    // 内存的永久保存区初始大小,默认64M

-Djava.rmi.server.hostname=127.0.0.1        // JMX监听主机设置
-Dcom.sun.management.jmxremote.port=32123   // JMX监听主机端口
-Dcom.sun.management.jmxremote.ssl=false    // 是否开启SSL开关
-Dcom.sun.management.jmxremote.authenticate=false"  // 是否需要认证的开关

在实践过程中发现,单个Tomcat实例在并行负载线程达到>3000之时,会使用到acceptCount所设置的数值创建队列,将后进的线程排队到该队列内。
但是同时也发现,当并行负载线程>3000之后,CPU和Head占用会大幅升高,响应时长也在爬高并超过实践过程中预设的1s(注:低负载情况下请求响应总时长为200-300ms);
当并行负载线程>5000之后,Tocmat发生了假死状况(注:这里所说的假死,是处于一种Tomcat进程仍然存活,但是请求已不再执行和处理),Heap内存下降,CPU下降,此时的Tomcat其实已经和宕机无异。这些情况所发生的原因,其中有一点毋庸置疑的那就是,你增加Tomcat准入线程数的同时,也在增加CPU对于容器上下文切换频率,就会增加CPU的资源消耗,自然地,请求的响应时间也就越来越慢。所以一个合理的线程连接数是如此的关键。

根据网络查询资料,许多IT从业者给出了近似的一些结论:如果需要考虑Tomcat在支撑并发数>1000以上,则可以引入分布式方案了。确实,系统稳定性是非常重要的。

四、优劣势辨析

本次实践过程中采用的是“分布式事务 + CAS原子性操作函数”所实现的乐观锁解决方案。在阐述其中优劣势之前,我们先来了解下关于抢购系统的外观设计办法有哪些。

  • 方案一:抢购结果滞后反馈

这一方案很典型地采用了异步处理的机制,虽然可以很大层面上解决访问压力的问题,但是会给人一种“暗箱操作”、“用户体验极其不好”的感觉。如下图所示:

* 方案二:抢购结果即刻响应

如方案标题所阐述的那样,即刻给用户反馈抢购操作的结果。该方案有很好的用户体验,但是对于系统也同样有着很高的考验。例如如何杜绝商品超售超发等问题的发生,因为一旦发生了超售超发,也就意味着系统的问题正在引发经济上的亏损。

本次实践中采用的是方案二的做法,如文章前面所述的流程图一样,采用的是乐观锁的实现思路。那么相对应的是否还有其他的实现思路,同样可以实现方案二呢?毋庸置疑,当然是有的。

  • 思路一:悲观锁思路

这里无论你采用的是分布式锁(Distributed lock)还是监视器锁(Monitor lock),他们有一个共通的特点,那就是Mutex(互斥性)。在同一时间只允许一个访问者可以访问共享资源,其他访问者必须等到加锁的访问者释放锁之后才可进入临界区进行资源访问。

在上图形势下,当并发请求上升之时,每个请求都需要请求锁,而线程之间也存在着线程调度的情况,自然也会出现starvation的现象发生,驻留了很多优先级低的线程资源未能及时处理和释放。随着并发时间的流逝,服务容器一定会出现请求阻塞,响应时间延迟,容器资源占用率高,Heap内存溢出等问题的发生。

  • 思路二:队列的思路

如果你对JDK中并发包有一定了解,这一思路一定非常熟悉,就是在阻塞性队列实现过程中,常常所需要用到的“生产者-消费者”的并发设计模式。

如图,这一思路优势是可以很快地处理并响应用户的抢购请求,只需要执行一些必要的过滤逻辑即可,舍去了关系型存储和其他存储的连接资源的请求和读写。劣势当然是有的,它过度依赖队列驻留在内存中的数据,一旦tomcat发生阻塞或者宕机,那么数据将永久丢失,这是灾难性的结果。另外,我们所应对的是并发的场景,队列的长度虽然可以设置为无上限,但是服务容器的资源是有限的,当消费速度小于请求速度之时,系统就逐渐迈向queue内存溢出的悬崖。

  • 思路三:乐观锁思路

本次实践的正是乐观锁思路。所谓乐观,自然与悲观的理念是相反的。悲观的思路会将访问线程用锁这一介质的作用,使得线程进入阻塞和等待。乐观的思路,就是临界区的共享资源不需要锁的介入就可以随意访问和修改,没有任何阻塞性质的限定。在并发级别的分类当中,有一种并发的级别叫做“无等待”,这是终极乐观锁的一种体现。
基本实现流程如下图所示:

很显然,乐观锁的思路,是一种无锁无等待的一种并发实现方式。可以使用事务CAS原子的特性来保障数据的准确性,避免了“超售超发”的问题发生。可以在请求线程并发过程中,无阻塞地进行抢购逻辑的处理。但是,与此同时引入了另外一个问题,它不可避免的会出现操作被事务性回退的现象,放在抢购场景下解释,就是一个请求进入事务之时,数据的无误可占用和消费的,但是当线程执行到提交事务一步时,数据版本出现了不一致(也就是其他的线程将商品占用并成功提交了事务),从而被定性为该请求抢购失败,即无效抢购。

和前面所提及的队列的思路比较起来,乐观锁的思路所实现的结果是无序的。假设你需要去保证按时序进行分配抢购的产品,那么乐观锁并不适合这一诉求,有序队列的实现思路可能更为合适。

五、小结一下

似乎也不需要总结太多,上文已经把整个方案过程中所涉及的考量和技术点都一一覆盖到了,可能会有些片面,或者有些阐述地比较粗糙,但毕竟这是一篇分享实现思路的文章而并非技术方案细节的文档,所以可以根据上述思路自行发散,有所参考,有所改进。

如果你有更好的建议、方案或者是优化策略,不妨在留言板上写下你的想法。

六、参考资料

本篇文章有原创体会,也有网络资料参考,国内外均涉及,这里就不一一罗列了,可以自行根据“抢购”、“分布式锁(distributed lock)”、“高并发(concurrency/parallel computing)”、“阻塞队列(blocking queue)”等关键词进行检索和进一步学习。

在此感谢伟大的Tim Berners-Lee、RMS(Richard Matthew Stallman)等业界大神,给世界带来了互联网,创造了开源的氛围和精神,让我们的信息得以共享。

Git协作之合并代码

继上一次撰写GIT相关文章已经数月有余,半年来在团队内实践GIT的过程中,遇到了很多个人项目所不会接触到的疑问和问题,最为典型的无异于分支、代码之间的合并操作。本篇我们来讲一讲如何在本地仓库中做好多人协作过程中的分支代码合并。

一、前言

  • Git知识回顾

在开始之前,如果你对于GIT的基础知识已经有些遗忘了,可以参考以下这几篇文章,知识都比较基础:

如果你想将GIT流程应用到研发工作当中去,GIT流程是你必须要知道的:

希望上面几篇文章可以帮助到你学习和回顾GIT应用知识。

  • 冲突产生的原因以及合并操作的原则

如果你对Git有了以上方面的了解,那么在学习如何进行冲突解决之前,可以先了解为什么会产生冲突?
冲突产生一般可分为如下四个方面:

  1. workdirectory的修改
  2. index的修改
  3. merge来源的修改
  4. merge之前的修改(Been committed)

在merge manual中已经给出了一条合并操作的原则:

// Command: git merge --help
Warning: Running git merge with non-trivial uncommitted changes is discouraged: while possible, it may leave you in a state that is hard to back out of in the case of a conflict.
// 简而言之,有未提交修改情况下,不要执行可能会冲突的操作!

在执行合并操作之前,保持workdirectory、index区域的clean特性,是非常必要的。当然,满足了这一前提,也并不代表就可以杜绝了冲突的可能。遵循该原则之后,解决的是1、2两点所产生的冲突可能,而前面所提及的3、4两点,是由于合并的操作所引入的冲突。
其实很多操作都可能会产生冲突,但最根本原因都是因为mergepatch,这两者是冲突的直接来源。例如如下操作都可能产生冲突,它们过程中也都包含了合并的性质:

  • rebase:重新设置基准,然后应用补丁
  • pull:会自动merge
  • repo sync:会自动rebase
  • cherry-pick:会应用补丁

如何保持workdirectory、index区域的clean特性,可以参考上文文章已经阐述到的stash操作。在merge过程中一旦发生冲突,往往会停滞在merging状态中,这个时候,我们可以使用:

// 中止merge过程,该命令会尽可能恢复到merge操作之前的状态,但也可能恢复失败。
git merge --abort

// merge manual原文是这么解释的:
--abort
	Abort the current conflict resolution process, and try to reconstruct the pre-merge state.
	If there were uncommitted worktree changes present when the merge started, git merge --abort will in some cases be unable to reconstruct these changes.
	It is therefore recommended to always commit or stash your changes before running git merge.
	git merge --abort is equivalent to git reset --merge when MERGE_HEAD is present.

二、合并代码之前,需要理解的概念

1. FETCH_HEAD

FETCH_HEAD可以视为一个版本链接,或者说是版本commit-id的履历文件,该文件的路径在Git项目下的.git/FETCH_HEAD文件中。
当运行git fetch remote_repo src_branch类似的命令之时,将从远程repo获取分支的末端版本信息,记录到.git/FETCH_HEAD文件中:

➜ /jeromechan/project git:(project) ✗>git fetch origin test
remote: Counting objects: 334, done.
remote: Compressing objects: 100% (211/211), done.
remote: Total 334 (delta 177), reused 167 (delta 51)
Receiving objects: 100% (334/334), 96.42 KiB | 0 bytes/s, done.
Resolving deltas: 100% (177/177), completed with 70 local objects.
From https://github.com/jeromechan/project
 * branch            test       -> FETCH_HEAD
   f5a095a..18b49aa  test       -> origin/test

然后,我们看下.git/FETCH_HEAD文件内容:

➜ /jeromechan/project git:(project) ✗>vim .git/FETCH_HEAD

## 文件内容如下
18b49aaa008731ff17768d6b18905fc5865b3aa7  branch 'test' of https://github.com/jeromechan/project

2. git fetch

该命令可以将远程repo所包含的所有分支(或者指定分支)的最新commit-id记录到.git/FETCH_HEAD文件中。

3. git pull

该命令是GIT更新local repo和working copy的文件。在之前的文章中,提及过git pull = git fetch + git merge FETCH_HEAD(by default),这里就不再重复提及了,如果想复习了解,请看“前言”中列出的几篇GIT基础知识文章。

4. commit-id

commit-id指的是每一次git commit操作所生成的每一个版本号,是一个能唯一标识一个版本的序列号。在使用git push后,这个序列号还会同步到远程repo。

三、如何执行合并操作

这里举例两个常见的场景对合并操作进行解释。

1. 将相同分支下,已经产生分支冲突的代码,合并到本地working copy中

这类场景下,由于分支是相同的,产生冲突无异于因为差异版本之间,相同的代码区块发生了相同位置的变更,然后冲突就这样产生了。
这类冲突不难解决,可以使用交互式命令行git rebase、Git-GUI客户端工具的rebase-from的操作,指定来源分支,借用其中的交互式操作,可以逐个逐个地进行commit-id的代码合并。

2. 将其他分支B的代码差异,合并到当前工作分支A的working copy中

这类场景常常发生在多人协作的实际工作中。例如需要将工作分支A的代码提交到分支B上,但分支B与工作分支A出现了不可合并的代码冲突,此时就需要将分支B的差异代码合并到当前的工作分支A中,然后再做提交与合并。

这个时候,我们可以按照如下几个步骤进行:

(1)git checkout A

将当前工作分支切换到分支A。

(2)git fetch https://github.com/jeromechan/project B

将分支B的末端版本(HEAD)检出到FETCH_HEAD文件中。

(3)git merge FETCH_HEAD

基于本地.git/FETCH_HEAD记录,比对本地工作分支A的版本与FETCH_HEAD所记录的分支B末端版本信息,利用git merge将分支B的差异内容合并更新到工作分支A中。
此操作过程中如果出现conflicts,那么是需要开发者进行自助解决的。这一部分,如果是整体作accept合并,那么一定要留意“theirs”和“mine”的关系,“theirs”通常指的是来源代码分支(即当前示例所述的分支B),而“mine”便是你的本地分支A了。

(4)git commit

将当前合并下来的commit-id及其内容提交到A分支上。

(5)git push https://github.com/jeromechan/project HEAD

将提交的commit-id及其变更内容,推送到远程repo的分支A上。

本篇到这里已阐述完。以上所述内容,一定可以帮助各位解决许多的合并代码的问题了。

几种方法调用思路的比较

编码过程中,每天都在实现的事情,不是对象就是方法。那么如何去封装一个方法的入参呢?如果入参很多,是否有比较不一样的思路可以提高代码的复用性和可读性呢?下面谈谈在这一方面,近期学习到的一点知识。

一、参数逐个罗列

假设有这么一个方法,方法参数很多,但都类似,我们可能需要把所有参数逐个逐个传入方法体内,就像这样:

/**
 * A common style of implementing a function.
 *
 * @param type1
 * @param type2
 * @param type3
 */
public static void doSomething(Byte type1, Byte type2, Byte type3) {
    if (type1 == 1) {
        //
        // do some conditional processes.
        //
    }
    if (type2 == 1) {
        //
        // do some conditional processes.
        //
    }
    if (type3 == 1) {
        //
        // do some conditional processes.
        //
    }
}
/**
 * Test cases for explaining how to invoke a function.
 *
 * @author chenjinlong 20161202
 */
public static void invokerMethod() {
    /**
     * Initial parameters
     */
    final Byte type1 = 1, type2 = 1, type3 = 1;

    /**
     * Call with separated parameters
     */
    doSomething(type1, type2, type3);
}

二、列表形式批量传入

可能有的工程师觉得上面这种办法,会使得方法参数过多,不便于阅读和归类,列表的办法可能会更好,所以上面的逻辑也可以写成这样:

/**
 * Another common style of implementing a function.
 *
 * @param typeList
 */
public static void doSomething(List<Byte> typeList) {
    for (Byte type : typeList) {
        switch (type) {
            case 1:
                //
                // do some conditional processes.
                //
                break;
            case 2:
                //
                // do some conditional processes.
                //
                break;
            default:
                break;
        }
    }
}
/**
 * Test cases for explaining how to invoke a function.
 *
 * @author chenjinlong 20161202
 */
public static void invokerMethod() {
    /**
     * Initial parameters
     */
    final Byte type1 = 1, type2 = 1, type3 = 1;
    
    /**
     * Call with parameter list
     */
    List<Byte> typeList = new ArrayList<Byte>();
    doSomething(typeList);
}

三、对象VO的形式传入参数

可能还有的工程师觉得,塞个列表进来,其实也并不能将释义阐述清楚,每个列表值虽说都是byte,但是每个byte的含义都代表着不通的分支不通的含义。所以,对象的办法就引入进来了:

/**
 * An POJO-bean style of implementing a function.
 *
 * @param typeList
 */
public static void doSomething(TypeExplains typeExplains) {
    if (typeExplains.getTypeOne() == 1) {
        //
        // do some conditional processes.
        //
    }
    if (typeExplains.getTypeTwo() == 1) {
        //
        // do some conditional processes.
        //
    }
    if (typeExplains.getTypeThree() == 1) {
        //
        // do some conditional processes.
        //
    }
}
/**
 * Test cases for explaining how to invoke a function.
 *
 * @author chenjinlong 20161202
 */
public static void invokerMethod() {
    /**
     * Initial parameters
     */
    final Byte type1 = 1, type2 = 1, type3 = 1;
    
    /**
     * Call with parameter bean
     */
    TypeExplains typeExplains = new TypeExplains();
    typeExplains.setTypeOne(type1);
    typeExplains.setTypeTwo(type2);
    typeExplains.setTypeThree(type3);
    doSomething(typeExplains);
}

四、按位与&按位或 的方式传入参数

以上方式其实都可以达到相同的函数想要实现的目标,但是是否考虑过其他更为便捷的办法呢?最近就遇到这么一种比较创新的、特殊的、异类、不一样的、充分展现计算机知识学以致用的调用的模式:

/**
 * Each of Keys must be strictly defined.
 */
public enum TypeEnum {
    TYPE_1(1), // equals to "0001"
    TYPE_2(2), // equals to "0010"
    TYPE_3(4)  // equals to "0100"
    ;

    private Integer key;

    TypeEnum(Integer key) {
        this.key = key;
    }

    public Integer getKey() {
        return key;
    }

    public void setKey(Integer key) {
        this.key = key;
    }
}
/**
 * Using & and | features.
 *
 * @param type
 */
public static void doSomething(Byte type) {
    if ((type & TypeEnum.TYPE_1.getKey()) == 1) {
        //
        // do some conditional processes.
        //
    }
    if ((type & TypeEnum.TYPE_2.getKey()) == 1) {
        //
        // do some conditional processes.
        //
    }
    if ((type & TypeEnum.TYPE_3.getKey()) == 1) {
        //
        // do some conditional processes.
        //
    }
}
/**
 * Test cases for explaining how to invoke a function.
 *
 * @author chenjinlong 20161202
 */
public static void invokerMethod() {
    /**
     * Initial parameters
     */
    final Byte type1 = 1, type2 = 1, type3 = 1;

    /**
     * Call with & and |
     */
    doSomething((byte)(type1 | type2 | type3));
}

这种按位或传入,按位与判断获取的办法,着实为传入参数提供了截然不同的参考。当然,这种办法也不是适用于所有场景的。 按位与按位或的办法,使用于需要传入参数很多的,方法体内针对每个参数会包含着不一样分支逻辑的场景。该方式简化了方法体外“或关系”判断的逻辑,但另一方面也确实降低了代码的易读性。

五、总结

其实以上谈到的四种模式中,没有哪一种是最好的。符合实践场景的、符合团队技术水平现实的、具备高内聚低耦合特点的,那就一定是最适合的。
后续日子里,会就代码内部模式的实践上,发散开来谈谈如何编写可读性强的代码,然后再多po几篇文章分享出来。

[译]Feature Toggle - Martin Fowler

关于FeatureBranch的一个较为争论的问题是,特性分支(Feature Branch)因为待发布功能(pending features)的原因,拉长了单一发布周期(single release cycle)。可以想象一下,你每两个周发布一次生产版本,但是同时又需要去维护一个例如说三个月才能进行发布的特性分支。那么你该如何去保障团队成员们在开发主线(mainline)上的工作不受到衍生出来的、未完全实现的特性分支的影响呢?这是一个非常常见的问题,这个时候,feature toggle就是这么一个应对这种场景的便捷工具。

(曾经见过许多相同含义的概念、名词:feature bits, flags, flippers, switches等等。这在领域上似乎还没有一个统一的概念、名称。)

Feature toggle最基础的思路就是存在一个配置文件,配置文件里定义了许多的toggle,这些toggle都是待发布的特性功能。这一些toggle也正定义了哪一些features需要呈现,哪些并不需要。

这其中许多的toggle大多数都会应用在拥有用户界面的应用上,假设你构建一个网站,其中使用了jsp,你可能会使用一些jsp标签包围在需要待发布的特性功能代码前后。

<toggle name="petSurvey">
    <p>Take our new <a href = 'petSurvey'>pet survey</a></p>
</toggle>

类似以上的代码,当你将配置toggle的开关打开之时,features的功能将会随之打开,否则将会继续忽略features功能代码。虽然不一样的UI技术实现上会存在差异,但是基础的原理都是一样的。

现实上往往还有一些特性功能,例如仅仅是引入一个新的价格算法,而这部分特性并没有对应的用户界面,纯粹是服务器端代码。对于这一情况,我们的toggle埋点可能会较为粗鲁和复杂,会直接将toggle codes耦合到系统应用层代码中,这将是比较忧虑的情况。

Toggle埋点应该以最少的埋点量,实现新特性的完全隐藏或者开放。如上图pet survey的特性,pet survey可能会有非常多的功能界面,但是入口只有一处,就是在首页的地方。也正是单一的入口,toggle的埋点只需要在该处即可,并不需要在所有相关涉及的代码各处都埋点。如此一来,之后移除toggle埋点的工作将会非常地高效。假设发现你移除toggle埋点的时间耗费的非常多,那么请关注,其中所涉及的toggle埋点是否太多太复杂了,代码结构是否已经足够规范和逻辑化。请记住,尽管简单的条件判断来设置toggle是一件非常容易的事情,但是也同时要记得使用一些类似多态替换的办法,去减少一些没有必要、过于复杂的toggle埋点的方案。

以往对于toggle的研究里,业界几乎都统称为“业务toggle”,而前面描述了那么多关于特性toggle的特点和办法,自此,可以将toggle分类为如下几种:

  1. 发布型:出现在生产环境上,用于开放或者隐藏部分特性功能的toggle;
  2. 体验型:即A/B testing,用于区分A、B方案的实际使用情况;
  3. 操作型:提供给操作员工的一系列特殊操作;
  4. 权限型:使得部分指定范围内的用户可以体验到新特性功能,而并不是面向全部用户。

大部分所见到的特性toggle都是在运行时设置的,当然,也有在编译时设置的。在编译时设置toggle,一个较小的优势是,它避免了toggle所涉及的特性功能代码被编译到生产发布的软件产品中。

Toggle不仅仅方便我们对于产品功能的管理,它也是有风险的。例如某位工程师忘了将界面UI的特性功能放置到toggle的tag闭包标签中,那么这一功能将会暴露在外,测试工作将难以进行,设置了toggle将元素隐藏的时候,并没有实际隐藏,开关功能已经处于不正确的状态下。如果这类情况在生产环境上出现,将是不可接受的。

谈到测试,大家比较关系的一个问题例如,如何对实现了toggle的系统进行测试验证?是否需要测试涉及toggle的所有场景?其实,对于一个发布型的toggle场景,只需要测试如下两类toggle使用情况:

  • 下一版本中期望实现的toggle埋点
  • 所有toggle都打开的场景

以上两点是发现多toggle集成bug的一个不错的好办法。

还有一点非常重要的,已经正式投产的toggle功能,一定要今早将toggle代码从生产环境中移除,其中不仅仅包括toggle的配置文件,还包括所有相涉及的系统toggle代码。如若不今早移除,会出现一种情况类似于,某些时候无人可以识别出某toggle是何种用途何时添加的细节情况了。一个较为清晰的案例就是,有一个需要二次编译linux内核的特殊场景,以使得可以获得更多的命令行开关的处理能力,恰巧当时就忘了这一场景,这是非常让人揪心的。

发布型toggles是你最后需要去做的事情

发布型toggles是一个非常有用的技术手段,许多研发团队都在使用它。当你要将特性功能发布到生产之时,它可能是你最后需要去做的事情。

当特性功能上线后的第一步,你需要去按照你的实际需求,打开toggle开关,使之特性功能在生产环境得以显露和使用。这种技术手段的好处在于,不需要你去频繁发布系统产品,便可以控制功能的上线与下线,更有利于在区间时间内收集真实用户的使用体验,收集用户反馈,这对于后续的产品特性功能的后续发展或者优化,都有着至关重要的作用。

如果你的的确确需要隐藏一个局部的特性功能,最好的办法是将界面UI的功能入口关闭,直至最后一次UI代码的发布。对于纯服务端的代码,也可以采用这种方式,知道最后一次发布,再打开特性功能的可见入口。

这种办法是一种优秀的可持续迭代,并且适合密集发布周期的项目。但是依然会有一些不可避免的短柄事实。前后端兼备的应用中,如此一来,在最后一次UI发布,或者后台服务端发布之前,你都将无法通过UI或者服务端入口进行toggle的测试和功能验证。当然,你也可以通过一些特殊的办法或者途径去间接地进行toggle特性的测试,例如服务端单元测试,亦或者是前端UI特性功能的某一后门入口。自然,这是不被推荐的。

总结一下,当你不希望去密集性地为了发布或者下架特性功能而频繁上线的场景下,部署toggle场景将是一个非常不错的技术手段。

进阶阅读材料

如果你想要了解本篇文章图示的feature toggles的实际应用办法,可以参考这篇文章 Pete Hodgson’s article

致谢

感谢Charles Bradley, Kent Beck and Christian Gruber通过tweets提醒我一些差点忘记的知识点。

版本

更新与2016-02-12,以适应于Pete Hodgson’s article所描述的最新细节。

读写分离的原则

一、读写分离是什么?

简单了说,就是数据库一主一从/一主多从/多主多从的部署方式。先简单介绍下mysql的原生同步机制,其是将master的binlog期间的执行日志根据顺序,拷贝至slave的relaylog中,然后逐一解析和执行。

读写分离的好处在于:

  • 硬件处理能力得到了横向的扩增,拿机器和带宽换性能
  • 降低了主库服务器的压力,提高并发性能
  • 缓解读锁(S锁,共享锁,Shared Lock)和写锁(X锁,排他锁,Exclusive Lock)的竞争
  • 系统运行性能得到间接的提升

二、如何判别是否需要读写分离?

谈及读写分离,必定要先了解怎样的应用层场景才需要使用到读写分离模式。以下是几点比较共性的场景特点:

  • 以读操作为主的应用或者服务
  • 可接受时间上短暂的信息延迟
  • 单一线程内同时涵盖读写操作的,使用主库

三、常见的读写分离的方式有哪些?

  1. 中间件,例如java-amoeba
  2. 框架特性,例如spring-aop
  3. 自定义组件,例如在dal层的prepare过程,增加拦截代码,以执行数据源的路由逻辑。

四、读写分离简单原则

五、参考文章

关于PhpStorm+MAMP+xdebug集成失败的处理办法

首先,阐述一下我的开发环境配置:

  • MacOS v10.11.6
  • MAMP PRO v3.5
  • PHP v5.3.29
  • Xdebug v2.2.7
  • PhpStorm 2016.1

近期对mamp作了次重装升级,之后遇到了xdebug无法正常启用的问题,在phpstorm中反馈的Event Log信息如下:

Cannot accept external Xdebug connection: Cannot evaluate expression 'isset($_SERVER['PHP_IDE_CONFIG'])'

看以上描述可以知道,是因为在建立Xdebug connection之时,无法找到变量PHP_IDE_CONFIG的定义。
谈到这里,大家来看看php.ini中Xdebug的参数配置先:

[xdebug]
extension=xdebug.so

[xdebug]
MAMP_Xdebug_MAMP
 xdebug.remote_enable=1
 xdebug.remote_host=localhost
 xdebug.remote_port=9000
 xdebug.remote_autostart=1
 ;xdebug.profiler_enable=0
 ;xdebug.profiler_output_dir="/Applications/MAMP/tmp"

一、核实xdebug与phpstorm已建立关联关系

打开phpinfo()信息页面,xdebug扩展部分如下:

也可以正常进行加载。但是问题来了,为什么phpstorm不能使用其进行debug过程呢?
不难发现,IDE Key上显示的是no value,这一方面说明了xdebug并没有正确地与phpstorm建立关联,即phpstorm即时发起xdebug connections,也无法正常连接并使用xdebug扩展。

修正这个问题比较简单,增加xdebug扩展参数配置即可:

xdebug.idekey="PHPSTORM"

二、确认xdebug使用的是zend_extension引入方式

php的扩展库有很多,主要分为两大类:php extension和zend extension。

有的基于php module开发而成,有的基于zend engine开发而成(xdebug基于的就是zend引擎进行开发的),当然,也有的可以兼容两种引擎(memcache就是一个很好的兼容两种引擎的例子)。而这里要提到一个概念,php内核是基于zend engine的,zend engine从php3开始就在服务php内核的最后一道岗了。

通常我们称php extension为“modules”,称zend extension为“extensions”。

以下为大家扩展一下,php extension和zend extension的基本数据结构上的区别:

  • php extension(modules)
// php extension structures
typedef struct _zend_module_entry zend_module_entry;
typedef struct _zend_module_dep zend_module_dep;
 
struct _zend_module_entry {
	unsigned short size;
	unsigned int zend_api;
	unsigned char zend_debug;
	unsigned char zts;
	const struct _zend_ini_entry *ini_entry;
	const struct _zend_module_dep *deps;
	const char *name;
	const struct _zend_function_entry *functions;
	int (*module_startup_func)(INIT_FUNC_ARGS);
	int (*module_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	int (*request_startup_func)(INIT_FUNC_ARGS);
	int (*request_shutdown_func)(SHUTDOWN_FUNC_ARGS);
	void (*info_func)(ZEND_MODULE_INFO_FUNC_ARGS);
	const char *version;
	size_t globals_size;
#ifdef ZTS
	ts_rsrc_id* globals_id_ptr;
#else
	void* globals_ptr;
#endif
	void (*globals_ctor)(void *global TSRMLS_DC);
	void (*globals_dtor)(void *global TSRMLS_DC);
	int (*post_deactivate_func)(void);
	int module_started;
	unsigned char type;
	void *handle;
	int module_number;
	const char *build_id;
};
 
struct _zend_module_dep {
	const char *name;		/* module name */
	const char *rel;		/* version relationship: NULL (exists), lt|le|eq|ge|gt (to given version) */
	const char *version;	/* version */
	unsigned char type;		/* dependency type */
};
  • zend extension(extensions)
// zend extension structures
typedef void (*message_handler_func_t)(int message, void *arg);
 
typedef void (*op_array_handler_func_t)(zend_op_array *op_array);
 
typedef void (*statement_handler_func_t)(zend_op_array *op_array);
typedef void (*fcall_begin_handler_func_t)(zend_op_array *op_array);
typedef void (*fcall_end_handler_func_t)(zend_op_array *op_array);
 
typedef void (*op_array_ctor_func_t)(zend_op_array *op_array);
typedef void (*op_array_dtor_func_t)(zend_op_array *op_array);
 
typedef struct _zend_extension {
	char *name;
	char *version;
	char *author;
	char *URL;
	char *copyright;
 
	startup_func_t startup;
	shutdown_func_t shutdown;
	activate_func_t activate;
	deactivate_func_t deactivate;
 
	message_handler_func_t message_handler;
 
	op_array_handler_func_t op_array_handler;
 
	statement_handler_func_t statement_handler;
	fcall_begin_handler_func_t fcall_begin_handler;
	fcall_end_handler_func_t fcall_end_handler;
 
	op_array_ctor_func_t op_array_ctor;
	op_array_dtor_func_t op_array_dtor;
 
	int (*api_no_check)(int api_no);
	int (*build_id_check)(const char* build_id);
	void *reserved3;
	void *reserved4;
	void *reserved5;
	void *reserved6;
	void *reserved7;
	void *reserved8;
 
	DL_HANDLE handle;
	int resource_number;
} zend_extension;
 
typedef struct _zend_extension_version_info {
	int zend_extension_api_no;
	char *build_id;
} zend_extension_version_info;

从加载顺序上分析,zend extension要早于php extension进行注册(register),然而在激活(activate)阶段,却是php extension要早于zend extension被激活。
在两者的激活顺序上,有如下区别可以额外了解一下:

PHP extensions in an order that make the dependencies be activated in a specific order. But remember that Zend extensions are never sorted in any way. You must then declare them in the FIFO order in php.ini. The engine wont touch your declaration order.

zend_extension注册是调用的函数zend_register_extension(),而php extension注册是调用的函数zend_register_module_ex(zend_module_entry *module)进行注册。

以下是两类扩展的生命周期图:

前面谈及了许多的关于php两类扩展的一些内部实现问题,其实对于这部分仅仅为了让开发者们更加深刻记得一点:“确认xdebug使用的是zend_extension引入方式”。

这个时候,我们需要继续修改php.ini关于xdebug的引入配置:

;1.移除默认引入xdebug.so的php extension的方式
[xdebug]
;extension=xdebug.so

;2.更改为zend extension的方式对xdebug.so进行引入
[xdebug]
MAMP_Xdebug_MAMP
 zend_extension="/Applications/MAMP/bin/php/php5.3.29/lib/php/extensions/no-debug-non-zts-20090626/xdebug.so"

以上可能你会疑惑,为什么一个写的是绝对路径,而一个写的是相对路径?
其实原因很简单,php extension约定了在加载之时可以使用相对于php.ini公共配置项目 extension_dir的路径作为扩展路径;而zend extension则约定了使用全路径进行引入和加载。

三、配置PhpStorm项目PHP Web Application

这部分不在赘述,正如title中所提到的,配置好PhpStorm项目PHP Web Application,就可以进行debug调试你的php脚本代码了。以下就简单的附上几张图示吧,供各位参考。

  • 配置:Run/Debug Configuration

  • 配置:Servers

  • 操作:Start Listening Xdebug Connections

  • Debug界面一览

备注:若各位看官有疑问或者建议,可以在文章末端的disqus留言,本人看到后会及时回复。感谢读者们阅读本篇文章。

Git使用规范

以下是关于源代码版本控制的一些共通的约定、协议。

一、管理仓库

  • 避免将自己开发环境中的内容添加到版本控制系统中;
  • 每当merge完成之后,删除本地以及远程的相应的特性分支;
  • 只在特性分支中完成编码工作;
  • 经常性的执行Rebase,使得upstream的变更及时合并到本地内容中;
  • 发起pull request,使得代码得以他人评审。

二、在特性分支工作

基于master,在本地创建特性分支。

git checkout master
git pull
git checkout -b <branch-name>

经常性的执行Rebase,使得upstream的变更及时合并到本地内容中。

git fetch origin
git rebase origin/master

如果出现冲突,应在Rebase过程中及时解决。
当特性分支编码完成、通过测试之后,这个时候,需要将内容提交到index storage,即index暂存区。

git add --all

当你完成了内容的暂存,即以上的add操作,这个时候你便可以提交它了。

git status
git commit --verbose

这里有一个Tips,对阅读者友好的commit注释是非常必要的,例如以下,就是一则好的提交注释。

Present-tense summary under 50 characters

* More information about commit (under 72 characters).
* More information about commit (under 72 characters).

http://project.management-system.com/ticket/123

如果你之前创建了多个commit,那么你应该使用交互式的Rebase操作,以更好地进行代码的合并,并且生成更为友好的提交注释。

git rebase -i origin/master

代码提交完成之后,便是要将你提交的内容share给大家。

git push origin <branch-name>

这个时候,便是向你的团队小伙伴们提交pull request的时候了,请团队成员们帮忙评审自己所编写的代码。

三、评审代码

邀请一个团队成员来评审自己的代码,是一个很好的发现缺陷issues的良策,还可以避免沟通上的遗漏和错误的传达。

通过pull request的评审,可以使得评审的工作不仅限于项目组的讨论会议。

另外,评审人员也可以自行作出代码变更和修改,只需要将变更内容检出到自己本地的分支。

git checkout <branch-name>
./bin/setup
git diff staging/master..HEAD

当评审人员在自己的branch上修改完毕,测试通过,提交commit并且push到远程origin分支中。
对所有变更满意之后,就可以在pull request中备注注释:Ready to merge

四、代码合并

使用交互式的Rebase进行代码的衍合,在每一项衍合所产生的提交中,必要的时候将注释丰富,例如增加messageFix whitespace的备注。

git fetch origin
git rebase -i origin/master

倘若你force push你的commit(s)到master,倘若你所使用的平台是GitHub,那么GitHub将自动地关闭你的pull request,同时标记该pull request为”Merged”状态。

git push --force-with-lease origin <branch-name>

说到这里,可能各位看官会疑惑,为什么这里引用了--force-with-lease这么一个git push的option呢?

下面介绍下这个参数是什么用途:

--[no-]force-with-lease
--force-with-lease=<refname>
--force-with-lease=<refname>:<expect>
Usually, "git push" refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it.

This option overrides this restriction if the current value of the remote ref is the expected value. "git push" fails otherwise.

Imagine that you have to rebase what you have already published. You will have to bypass the "must fast-forward" rule in order to replace the history you originally published with the rebased history. If somebody else built on top of your original history while you are rebasing, the tip of the branch at the remote may advance with her commit, and blindly pushing with --force will lose her work.

This option allows you to say that you expect the history you are updating is what you rebased and want to replace. If the remote ref still points at the commit you specified, you can be sure that no other people did anything to the ref. It is like taking a "lease" on the ref without explicitly locking it, and the remote ref is updated only if the "lease" is still valid.

--force-with-lease alone, without specifying the details, will protect all remote refs that are going to be updated by requiring their current value to be the same as the remote-tracking branch we have for them.

--force-with-lease=<refname>, without specifying the expected value, will protect the named ref (alone), if it is going to be updated, by requiring its current value to be the same as the remote-tracking branch we have for it.

--force-with-lease=<refname>:<expect> will protect the named ref (alone), if it is going to be updated, by requiring its current value to be the same as the specified value <expect> (which is allowed to be different from the remote-tracking branch we have for the refname, or we do not even have to have such a remote-tracking branch when this form is used).

Note that all forms other than --force-with-lease=<refname>:<expect> that specifies the expected current value of the ref explicitly are still experimental and their semantics may change as we gain experience with this feature.

"--no-force-with-lease" will cancel all the previous --force-with-lease on the command line.

-f
--force
Usually, the command refuses to update a remote ref that is not an ancestor of the local ref used to overwrite it. Also, when --force-with-lease option is used, the command refuses to update a remote ref whose current value does not match what is expected.

This flag disables these checks, and can cause the remote repository to lose commits; use it with care.

Note that --force applies to all the refs that are pushed, hence using it with push.default set to matching or with multiple push destinations configured with remote.*.push may overwrite refs other than the current branch (including local refs that are strictly behind their remote counterpart). To force a push to only one branch, use a + in front of the refspec to push (e.g git push origin +master to force a push to the master branch). See the <refspec>... section above for details.

以上大致的意思是指,在执行git push之时,增加--force-with-lease参数,可以避免覆盖其他开发者在你fetch了之后,对目标分支做了其他的更新,然后你简单的git push -f的操作强制覆盖了他人的commits内容。
如果想要了解--force-with-lease参数更多的相关知识,可以查看这里Stack Overflow的例子: How do I properly force a Git push?

想要查看新提交的commit(s)列表,想要查看变更的文件列表,以及合并开发分支代码到master,以下命令可以实现这些目的:

git log origin/master..<branch-name>

git diff --stat origin/master

git checkout master
git merge <branch-name> --ff-only
git push

特性分支代码既然都已然成功合并到master上,这个时候我们需要去删除特性分支,无论是本地工程目录,还是远程仓库。

# Delete your remote feature branch.
git push origin --delete <branch-name>

# Delete your local feature branch.
git branch --delete <branch-name>

五、参考资料

XRebel入门与实践

一、XRebel简介

XRebel是一款轻量级的性能优化器,可以帮助开发者们主动去发现和修复一些通用性的问题。XRebel提供了即时的方式,使得开发者可以针对每一个独立的请求进行分析、定位一些诸如DB查询、session变更细节等。
XRebel支持当前众多的Java企业开发平台,只需要耗费一点点的时间,便可以非常简洁地集成到开发环境当中去。

为什么开发者需要使用到XRebel呢?其实原因很简单。 许多开发者们都遇到过很多挣扎的事宜,例如为了寻找系统crash或者发现issues问题,不断地去确认编码逻辑,不断地去核实业务实现细节等等,是否有线程间通信安全问题,是否出现了未捕获异常等等。这些都是非常耗费时间精力的,而这些问题我们可以让其在整个软件开发过程中,暴露地更早。在软件开发周期中,问题暴露地越早,所花费的成本就会越低。

The severity of an issue is higher(obviously) if it is later in the development cycle.

XRebel正是为了让开发者们避免继续低效率地挣扎,遭遇节假日技术支持的困扰而诞生的性能优化工具。
它可以帮助开发者们在开发过程中,实时地检测所编程代码的效率参数和通信细节,XRebel只输出了开发者们所关心的关键监控参数细节,而一些无关的其他参数,它将智能地识别并且隐藏起来。
开发者们使用XRebel,并不需要频繁地切换系统程序和XRebel分析窗口,因为两者是分离开的。你可以在浏览器中的不同tab上打开对应的页面。这一点很好地提高了开发者的开发效率,方便了开发者在不同窗口内查看指定的数据。

相对于其他的性能优化器产品,XRebel实现了开发、生产环境在代码监测层面上的零差异。开发环境所监测到的sql、session、exception、code trace log等等,无论是在development环境,或是在product环境,都是一致的。

而其他的一些产品,诸如JProfiler、APM,所监测到的数据大多基于CPU、Memory、Threads等方面,定位问题需要先找到对应的Thread,然后打开Thread Tree/Stack进行逐一排查,非常繁琐,并不是那么地简易操作。
恰恰XRebel解决了这个排查的技巧性问题。XRebel可以直接捕获到发生异常的代码行和exception detail,同时还可以查看发生该异常的请求之时,所涉及的sql、cache、queue、session等组件的交互情况,使得开发者更易于快速提高解决问题的效率。

二、XRebel安装使用教程

前面介绍了关于XRebel的一些背景和特点,以下介绍XRebel的安装步骤及其Tips。

首先要说明的一点,XRebel是一款收费工具,并非开源免费。 该工具值得你去购买,如果这笔款项不会影响你的生活质量。关于破解的方法,这里不做介绍,请自行Google。

下载与安装

1.从官网上下载XRebel压缩包(.zip)http://zeroturnaround.com/software/xrebel/download/
如果你提前登陆了zeroturnaround.com,那么你所下载的zip包中就非常便捷地包含了你的帐号信息。

2.解压zip包到指定的目录中,zip包中包含的内容如下:

3.将xrebel.jar添加到应用服务容器的JVM参数中,添加时候确认路径是正确无误的。

-javaagent:[path/to/]xrebel.jar

4.启动应用服务容器,XRebel就会自动成功启动。
启动完成之后,打开浏览器,XRebel toolbar就会显示在你服务页面的左下角。

2016-05-20 16:29:42 XRebel: #####################################################
2016-05-20 16:29:42 XRebel:
2016-05-20 16:29:42 XRebel:  XRebel 3.1.1 (201605131009)
2016-05-20 16:29:42 XRebel:  (c) Copyright ZeroTurnaround AS, Estonia, Tallinn.
2016-05-20 16:29:42 XRebel:
2016-05-20 16:29:42 XRebel:  For questions and support,
2016-05-20 16:29:42 XRebel:  contact xrebel@zeroturnaround.com
2016-05-20 16:29:42 XRebel:
2016-05-20 16:29:42 XRebel: #####################################################
License激活

使用XRebel必须先激活,毕竟是收费工具。当然,在你购买前,你会拥有15天的评估试用期。
怎么激活呢?其实很简单,在你安装完XRebel之后,首次打开浏览器,以下窗口便会自动弹跳出来,根据提示进行License注册便是。

将XRebel集成到你的Server中

这里会有两个方面可以实践。

  • IDE集成
  • Server容器集成

以下逐一介绍。首先第一种:IDE集成。

1.Eclipse

Tips: 插件XRebel for Eclipse已经在Marketplace提供下载。目前该插件仅支持Eclipse 3.6版本及以上。

(1)打开Eclipse的 Run > Run Configurations…;
(2)选择Server服务容器,打开Arguments参数设置tab标签;
(3)将以下参数填写到VM arguments配置项目中;

-javaagent:[path/to/]xrebel.jar

(4)点击Apply应用并保存到当前的Run Configuration中。

2.Intellij IDEA

(1)打开IDEA的Run > Edit Configurations…;
(2)选择Server服务容器;
(3)将以下参数填写到VM arguments配置项目中;

-javaagent:[path/to/]xrebel.jar

(4)点击Apply应用并保存到当前的Run Configuration中。

3.NetBeans

(1)打开NetBeans的Tools > Servers;
(2)选择Server服务容器,打开Platform参数设置tab标签;
(3)将以下参数填写到VM arguments配置项目中;

-javaagent:[path/to/]xrebel.jar

(4)点击Close保存并关闭当前窗口。

下面我们介绍第二种:Server容器集成。

由于容器种类繁多,这里仅举例应用最为广泛的Tomcat上的应用,其他的容器配置可参考官方文章Adding-the-xrebel-parameter-without-an-ide

1.Tomcat on Windows Platform

(1)找到安装Tomcat的目录(即Tomcat home folder);
(2)打开bin文件夹;
(3)新增一个可执行.bat文件,例如命名为catalina-xrebel.bat
(4)将以下内容增加到第3步所创建好的.bat文件中;

@echo off
set JAVA_OPTS=-javaagent:[path/to/]xrebel.jar %JAVA_OPTS%
call "%~dp0\catalina.bat" %*

(5)执行以上.bat文件,启动Tomcat。

PS: 需要将Tomcat以服务的方式启动着?可以打开tomcat包装器tomcatXw.exe,在tomcatXw包装器的设置项目Java > Java Options中,新增如下参数:

-javaagent:[path/to/]xrebel.jar

2.Tomcat on Mac OS and Linux

(1)找到安装Tomcat的目录(即Tomcat home folder);
(2)打开bin文件夹;
(3)新增一个可执行.sh文件,例如命名为catalina-xrebel.sh
(4)将以下内容增加到第3步所创建好的.sh文件中;

#!/bin/bash
export JAVA_OPTS="-javaagent:[path/to/]xrebel.jar $JAVA_OPTS"
`dirname $0`/catalina.sh $@

(5)按照如下命令,执行以上.sh启动脚本,启动Tomcat。

./catalina-xrebel.sh run

三、XRebel应用

经过前两部分的折腾,你应该已经可以成功启动XRebel了。下面我们来进一步介绍以下,如何去实际使用XRebel?XRebel到底给我们开发者提供了怎样的便捷特性?下面会逐一展开讲道。

以下主界面非常明显地呈现出了Toolbar的位置,也是XRebel主要的操作菜单功能区。

1.第一大功能:Access application profiling

PS:为了书写方便,这里暂且称之为AAP功能。

AAP功能包含的是系统服务所接收到的请求列表,以及每一个请求的时间消耗细节监测数据。该数据的统计维度是定位到Method级别的。
请跟随以下图示,对AAP功能有一个现状的概览印象,具体事宜可以在真实实践的时候进一步逐一了解。

查看AAP功能的栈信息之时,留意符号的含义。该符号标识了一个异步的执行请求。

2.第二大功能:Review IO query

PS:为了书写方便,这里暂且称之为RIQ功能。

RIQ功能可以支持但不仅限于jdbc的监测。例如RabbitMQ、JMS、Redis等框架的执行数据的监控。
如第3张图示,清晰地监测到了请求中所包含的执行SQL语句,这一点对于开发、测试过程都是及其有帮助的。

3.第三大功能:Find exceptions

PS:为了书写方便,这里暂且称之为FE功能。

FE功能简单易懂,即异常日志的捕获。开发者们除了在slf4log、log4j等日志框架下定义好的路径中查看系统日志以外,还可以在系统程序运行时从XRebel中查看到即时的日志详情。

以上介绍完了XRebel最为常用的三大功能点,关于session监控的部分,这里也截图一张说明一下,有需要的开发者可以自行研究,例如应用到CAS等用户登录相关场景之时,相信Session data这项功能是你非常好的监控助手。

Session data的功能需要从XRebel的Settings中打开配置选项,默认是不勾选的。

此外这里再补充一点,也就是主界面三大功能的右上角Hidden items选项。

将该选项打上勾,便可以查看到操作过程中,开发者们标记了hidden的条目,以及XRebel智能隐藏的其他条目。具体看应用场景的需要。

四、高级配置选项

1.XRebel过滤名单

XRebel允许配置一项黑名单列表,将不需要监测的class路径添加进去,即达到忽略的目的。
黑名单配置文件在这里:

~/.xrebel/traces-blacklist.txt

XRebel还允许配置一项黑名单,以过滤statictransient属性的大小,使之不影响所在对象Object的大小计算。

~/.xrebel/session-blacklist.txt

2.XRebel启动参数

  • -Dxrebel.single_app_mode=true|false (default value: true) – Consider all deployments to be part of a single application. This reports activity from different deployment units to one and the same toolbar.
  • -Dxrebel.injection.log_response=true|false (default: false) – When true, the content of HTTP servlet responses will be logged to xrebel.log. This is meant to help debug cases when XRebel toolbar is not properly injected into the application’s HTML.
  • -Dxrebel.session.include_transient_fields=true|false (default value: false) – When true, fields with the transient modifier will be considered when constructing the session graph. By default, all transient fields are ignored and objects reachable only through transient fields are not considered to be part of the session.
  • -Dxrebel.browser.console_log=true|false (default value: false) – When true, the client side XRebel log will be written into the browser console.
  • -Dxrebel.injection=true|false (default value: true) – Disables the XRebel toolbar injection into application HTML. Add /xrebel to your application URL to view the toolbar in a separate tab (this opens the alternative UI view).
  • -Dxrebel.traces.filter_resources=true|false (default value: true) – Prevents displaying resource requests in the Application profiling view.
  • -Dxrebel.include_all_io=true|false (default value: true) – Enables the display of all detected IO events. Uncategorized IO events are displayed as Unidentified.
  • -Dxrebel.sql.format=true|false (default value: true) – Enables formatting of SQL queries.
  • -Dxrebel.traces.all=true|false (default value: false) – Enables tracing for all contexts.
    • -Dxrebel.traces.jms=true|false (default value: false) – Enables tracing for JMS.
    • -Dxrebel.traces.scheduler=true|false (default value: false) – Enables tracing for ScheduledExecutorService.
    • -Dxrebel.traces.quartz=true|false (default value: false) – Enables tracing for Quartz.
    • -Dxrebel.traces.rabbitmq=true|false (default value: false) – Enables tracing for RabbitMQ.

3.XRebel特性Module配置参数

  • -Dxrebel.module.http.HttpURLConnection=true|false (default value: true) – Disables collecting IQ queries from java.net.HttpURLConnection usage.
  • -Dxrebel.module.http.HttpCore=true|false (default value: true) – Disables collecting IO queries from Apache HttpCore 4.x usage.
  • -Dxrebel.module.http.HttpClient3=true|false (default value: true) – Disables collecting IO queries from Apache HttpClient 3.x usage.
  • -Dxrebel.module.mongodb=true|false (default value: true) – Disables collecting IO queries from MongoDB driver usage.
  • -Dxrebel.module.hbase=true|false (default value: true) – Disables collecting IO queries from HBase driver usage.
  • -Dxrebel.module.redis=true|false (default value: true) – Disables collecting IO queries from Redis driver usage.
  • -Dxrebel.module.cassandra=true|false (default value: true) – Disables collecting IO queries from Cassandra driver usage.
  • -Dxrebel.module.orm=true|false (default value: true) – Disables collecting information about ORM queries, including Hibernate JPA queries.
  • -Dxrebel.module.equinox=true|false (default value: true) – Disables support for Equinox class loaders (boot delegation).
  • -Dxrebel.module.rmi=true|false (default value: true) – Disables collecting IO queries from RMI (java.rmi) remote object invocations.
  • -Dxrebel.module.traces=true|false (default value: true) – Disables collecting all method calls during HTTP requests.
  • -Dxrebel.module.session=true|false (default value: true) – Disables taking session snapshots.
  • -Dxrebel.module.springloaded=true|false (default value: true) – Disables Spring Loaded agent when detected.
  • -Dxrebel.module.async=true|false (default value: true) – Disables collecting IO queries from asynchronously executed threads.
  • -Dxrebel.module.quartz=true|false (default value: true) – Disables collecting IO queries within a Quartz job context.
  • -Dxrebel.module.jms=true|false (default value: true) – Disables collecting IO queries within JMS MessageListener.onMessage() context.
  • -Dxrebel.module.scheduler=true|false (default value: true) – Disables collecting IO queries for ScheduledThreadPoolExecutor tasks.
  • -Dxrebel.module.rabbitmq=true|false (default value: true) – Disables collecting IO events within RabbitMQ consumer.handleDelivery() context and outgoing messages.
  • -Dxrebel.module.remote_events=true|false (default value: true) – Disables collecting and displaying data from remote XRebel-enabled web services (microservices).
  • -Dxrebel.module.jdbc=true|false (default value: true) – Disable collecting IO queries from JDBC usage.
    • -Dxrebel.module.jdbc.sql=true|false (default value: true) – Disables collecting IO queries from relational database JDBC usage. Requires -Dxrebel.module.jdbc to be true.
    • -Dxrebel.module.jdbc.neo4j=true|false (default value: true) – Disables collecting IO queries from Neo4j JDBC usage. Requires -Dxrebel.module.jdbc to be true.
    • -Dxrebel.module.jdbc.cassandra=true|false (default value: true) – Disables collecting IO queries from Cassandra JDBC usage. Requires -Dxrebel.module.jdbc to be true.
    • -Dxrebel.module.jdbc.phoenix=true|false (default value: true) – Disables collecting IO queries from Apache Phoenix (HBase) usage. Requires -Dxrebel.module.jdbc to be true.

五、参考资料

本篇文章基于个人在近期实践XRebel过程中,所用到的知识归纳成文。
XRebel实用性极高,对自己在日常的开发中,方便了发现代码缺陷以及存在的性能隐患,所以作此一文推荐一番。

具体官方资料可查阅:http://manuals.zeroturnaround.com/xrebel/index.html

如何快速选择开源软件协议

一、背景介绍

目前开源协议有很多,同一款协议也衍生出了许多变种。开源世界中,我们可以使用合适的开源协议,保证自己作品的版权。
我们这里不做过多的介绍,只介绍一些自己常用的三个选择及其理由,以供参考。

二、详细描述

2.1 “我需要一个简单宽松的协议”

MIT License是一个简短、宽松、自由的协议。该协议允许人们使用你的代码,但必须要保留你的版权信息。与此同时,并不会给你带来任何责任和风险。jQuery,.NET CoreRails使用的均是MIT License。

The MIT License (MIT)
Copyright (c) <year> <copyright holders>

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

2.2 “我更加关心自己的专利”

Apache License 2.0是一项和MIT License相似的协议,但自己希望自己的专利能在开源免费使用的同时,保留自己在开源产品中的专利权益。同样,该协议要求使用者必须保留你的版权信息。
Android, ApacheSwift使用的均是Apache License 2.0协议。

Apache License Version 2.0
Copyright [yyyy] [name of copyright owner]

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

2.3 “我关心代码的分享以及促进”

如果你希望别人在分享的自己的作品之后,也必须遵循相同的协议,也必须是开源和免费。那么GPLv3是你更好的选择。该协议当中也明确地包含了贡献人的专利权益方面的款项。原作品的版权条款也必须延续保留。GPL协议存在非常强的“传染性” Bash, GIMPPrivacy Badger使用的均是GPLv3协议。

GNU GENERAL PUBLIC LICENSE Version 3
Copyright (C) {year}  {name of author}

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

如果你的产品是基于终端的,你还可以加上如下一段,使得使用者知晓如何可以联系到你。

{project}  Copyright (C) {year}  {fullname}
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.

三、参考资料

解决fastjson反序列化日期0000-00-00失败的方案

一、案例场景复原

示例场景里涉及两个class:TestDemo.java, DateBeanDemo.java

// DateBeanDemo.java
public class DateBeanDemo {
	/**
	 * dateStr field with Date.class
	 */
    private Date dateStr;

    /**
     * Get dateStr <br>
     *
     * @return Returns the dateStr. <br>
     */
    public Date getDateStr() {
        return dateStr;
    }

    /**
     * Set dateStr <br>
     *
     * @param dateStr The dateStr to set. <br>
     */
    public void setDateStr(Date dateStr) {
        this.dateStr = dateStr;
    }
}
// 示例执行例子
public class TestDemo {
    public static String jsonStr = "{\"dateStr\":\"0000-00-00\"}";
    public static void main(String[] args) {
        DateBeanDemo resultObject = JSON.parseObject(TestDemo.jsonStr, DateBeanDemo.class);
    }
}

执行以上的main方法之后,并没有获取预期的结果,而是在fastjson的序列化解析中便发生了异常,如下

Exception in thread "main" com.alibaba.fastjson.JSONException: For input string: "0000-00-00"
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:555)
	at com.alibaba.fastjson.JSON.parseObject(JSON.java:251)
	at com.alibaba.fastjson.JSON.parseObject(JSON.java:227)
	at com.alibaba.fastjson.JSON.parseObject(JSON.java:186)
	at excel.TestDemo.main(TestDemo.java:23)
Caused by: java.lang.NumberFormatException: For input string: "0000-00-00"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
	at java.lang.Long.parseLong(Long.java:419)
	at java.lang.Long.parseLong(Long.java:468)
	at com.alibaba.fastjson.parser.deserializer.DateDeserializer.cast(DateDeserializer.java:56)
	at com.alibaba.fastjson.parser.deserializer.AbstractDateDeserializer.deserialze(AbstractDateDeserializer.java:98)
	at Fastjson_ASM_DateBeanDemo_1.deserialze(Unknown Source)
	at com.alibaba.fastjson.parser.DefaultJSONParser.parseObject(DefaultJSONParser.java:551)
	... 4 more

通过百度查阅了部分网上撰写的方案,可以使用fastjson中的注解@JSONField(format="")来重新定义DateBeanDemo.dateStr,如下:

@JSONField(format = "yyyy-MM-dd",parseFeatures={Feature.AllowISO8601DateFormat})
private Date dateStr;

然而,并没有解决这个exception的问题。同时还怀疑了注解是否在反序列化之时没有被使用到,经过查阅源码,fastjson已将其反序列化的开关定义了true

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})
public @interface JSONField {
    int ordinal() default 0;
    String name() default "";
    String format() default "";
    boolean serialize() default true;
    boolean deserialize() default true; // 默认定义了true,反序列化之时也使用该注解
    SerializerFeature[] serialzeFeatures() default {};
    Feature[] parseFeatures() default {};
    String label() default "";
}

二、原因剖析

经查找fastjson中相关代码发现,当代码执行到com.alibaba.fastjson.parser.deserializer.DateDeserializer.class的执行方法cast()中之时,所使用的JSONParser中所含带的dateFormat依旧是默认的yyyy-MM-dd HH:mm:ss,而并非注解@JSONField中所定义的yyyy-MM-dd。 所以发生了转换字段失败。

再深究一层,为什么不是@JSONField中所定义的yyyy-MM-dd作为JSONParser中的dateFormat呢?其实仔细阅读一遍cast()代码逻辑就会发现,并不是fastjson丢弃了JSONField的扫描,而是在方法中有这么一段:

// 检查格式是否符合ISO8601的DateFormat规范
if(!dateLexer.scanISO8601DateIfMatch(false)) {
    break label122;
}

当执行到以上代码段之时,由于字符串0000-00-00并不是ISO8601的DateFormat规范之内,故而代码便会break label122执行跳出逻辑。紧接着执行的就是DateFormat dateFormat1 = parser.getDateFormat();,此时,parser依然是global定义的parser,DateFormat并没有使用@JSONField中所定义的。

三、解决方案:新增date反序列化解析器

解决方案并非只有一种,在众多解决方案中自己选择了”新增date反序列化解析器”的办法。除此之外还有诸如设置JSON.DEFFAULT_DATE_FORMAT属性的办法,也同样可以解决这一问题。下面作两种办法的对比和阐述。

  • 方案1:设置属性JSON.DEFFAULT_DATE_FORMAT

这一方案只涉及修改main()方法代码即可实现,

// 示例执行例子
public class TestDemo {
    public static String jsonStr = "{\"dateStr\":\"0000-00-00\"}";
    public static void main(String[] args) {
        JSON.DEFFAULT_DATE_FORMAT = "yyyy-MM-dd";
        DateBeanDemo resultObject = JSON.parseObject(TestDemo.jsonStr, DateBeanDemo.class);
    }
}

但是翻阅fastjson中对于JSON.DEFFAULT_DATE_FORMAT可知,该属性属于静态属性,一旦设置影响全局。

// com.alibaba.fastjson.JSON.class
public abstract class JSON implements JSONStreamAware, JSONAware {
    public static String DEFFAULT_DATE_FORMAT;
    // ...
  • 方案2:新增date反序列化解析器

主要思路是以fastjson原生的DateDeserializer.class为基础,定制化一个可以解析0000-00-00的日期反序列化解析器。
该方式是fastjson函数JSON.parseObject()的一个应用场景,通过定制化ParserConfig参数,达到局部改变JSON解析逻辑的目的。
如下:

package jeromechan.fixbug.fastjson;

import com.alibaba.fastjson.parser.DefaultJSONParser;
import com.alibaba.fastjson.parser.deserializer.DateDeserializer;
import java.lang.reflect.Type;

/**
 * Copyright © 2016 Jerome Chan. All rights reserved.
 * An extended DateDeseializer for parsing '0000-00-00'.
 * 
 * @author chenjinlong
 * @CreateDate 7/20/16 5:55 PM
 */
public class JCDateDeserializer extends DateDeserializer {
    public static final JCDateDeserializer instance = new JCDateDeserializer();

    public JCDateDeserializer() {
    }

    protected <T> T cast(DefaultJSONParser parser, Type clazz, Object fieldName, Object val)
    {
        if (val == null) {
            return null;
        } else if (val instanceof String) {
            String strVal = (String) val;
            if (strVal.length() == 0) {
                return null;
            } else if (strVal.equals("0000-00-00")) {
                parser.setDateFormat("yyyy-MM-dd");
            }
        }
        return super.cast(parser, clazz, fieldName, val);
    }
}
// 示例执行例子
public class TestDemo {
    public static String jsonStr = "{\"dateStr\":\"0000-00-00\"}";
    public static void main(String[] args) {        
        ParserConfig jcParserConfig = new ParserConfig();
        jcParserConfig.putDeserializer(Date.class, JCDateDeserializer.instance);
        DateBeanDemo resultObject = JSON.parseObject(TestDemo.jsonStr, DateBeanDemo.class, jcParserConfig, JSON.DEFAULT_PARSER_FEATURE);
    }
}

假设觉得这种解析办法可以作为整个项目内的全局特性,感兴趣的话可以将定制好的JCDateDeserializer利用spring框架注入到项目容器中。这同样是对于方案2很不错的延伸。

四、参考资料

什么是架构

先来看看老外们对于架构是如何诠释的。

What is Architecture?
Architecture is the art,science, and profession of planning, designing, and supervising the construction of new buildings, landscapes, communities, and furnishings in their totality, examining their environment in accordance with the principles of utility, strength, and aesthetics.

以上这段话虽然更适合解释建筑架构的含义,但是我们通过对比计算机工程类的架构的理念和要求,以上所提到的要点皆包含在内,不外乎生态不同罢了,其两者核心理念是相同的。

最近日子在工作中遇到的关于“架构”的探讨上,今天来谈谈自己理解的所谓架构,想以此篇文字来端正一下自己。

架构这词也是神圣不可侵犯的,如果你把一些b格很low的设计细节的东西当作是架构,那么毫无疑问,你正在掩耳盗铃。 设计和架构有着鸿沟一般的区别。而跨越这条鸿沟,需要不断学习,不停积攒天赋,同时伴随着需 要量的积累。 设计,首先它是一个架构范畴中的一个很小的子集。处于一个很单元的层次,但它也同时是不可逾 越不可或缺的阶段。

而设计绝不等于架构。

架构,不仅考量技术实现,还需要诸如:

  • 客户关键业务点的理解
  • 如何将概念具体化
  • 项目目标和周期的估算
  • 如何多元化团队沟通协作

层次高的还有:

  • 关乎项目战略目标的敲定
  • 如何在企业执行中追求更高roi

而技术实现方面也不仅仅是说项目如何搭建如何设计db,如何实现分包分模块,还不可或缺的需要包括:

  • 如何将项目平台化
  • 如何作冗余和可用架构而同时避免缠绕和蔓延
  • 如何用专业手段作架构沟通和透视

对于稳定系统的方面,如何去执行可持续治理也是所谓架构的关键点。

所以,只是挂在嘴边念叨“架构架构”而从不事先勘探落地,这种“理论架构”毫无用处,能体现项目 生产力、技术价值、团队骄傲的,才是我们所需要的真正架构。

真正懂架构的人,并不会把架构挂在嘴边
真正牛逼的架构师,从来都是拿实力和实例说话

说说使用middleman的那些事情

一、什么是静态网站

随着互联网发展的脚步,现在的个人博客网站已经不再局限于国内几大门户网站所提供的博客撰写服务。 记得上大学那会儿,为了阅读一些名人诸如国民岳父韩寒、李承鹏、徐静蕾等博主的文章和思想,每天必定会上博客上转上两遍,翻阅近十篇进行阅读。那个时候的博客页面加载速度,在寝室512k的带宽状况下,简直令人没有了脾气。

那个时候并没有太多纯静态网站的做法,有的是一些根据脚本生成指定的某些html页面,用于解决一些高流量访问的问题,例如门户站点的首页,其中只需要保持少量的动态加载元素,对付高流量高并发并不是困难的。

而个人博客、企业宣传页面不同于上文的需求场景,其更新频率定期,没有过多的注册用户信息的管理,不涉及复杂的交互实现,只需要满足查看和评论的功能即可。

评论功能自然是需要实现r+w操作的,但是现在已经存在了很多第三方协助托管评论信息的平台,例如国内的多说,国外的disqus。只需要开发者简单地嵌入少量html+js代码即可完成评论功能的实现。

故而,纯静态网站应运而生,各类纯静态网站生成工具也出现了较大的发展前景。下面说道的middleman就是一个静态网站的generator工具。

二、放弃jekyll转投middleman

这部分并不是说jekyll技术已经落后,而是从自身角度来看为什么自己会转投middleman。 自己是从2014年开始使用jekyll的,源于阅读了较多的大神yuguo对于前端栈的技术篇章,也就两年的光景,但是自己经历了从2.X版本变迁到3.X版本的过程。 对于为什么自己放弃jekyll转投middleman,下面分2点作出阐述。

第一点,我们先来说说2.X到3.X版本的更迭细节。 以上变迁的情况说明,大家都知道,jekyll 2.X里包含了ruby和python两门技术,而以上的更迭看来,一方面减少了非必需包的依赖,留出了定制空间,移除了python的依赖。或许jekyll作者更喜欢ruby only吧。

这些变更并不是向前兼容的,如果你的jekyll是2.X版本,请留意升级后带来的源代码修改的成本。虽然它不是向前兼容,但是升级过程并不会引入更多的包依赖,而恰恰是因为这点,_config.yml需要作出的变更会较多,例如coderay的highlighter引擎建议要更换为rouge;需要分页插件,则需要增加gems:[jekyll-paginate]作出依赖声明,等等。

而这里包含了一点自己无法接受的变更,看官方声明:

!! Don’t set a permalink
Setting a permalink in the front matter of your blog page will cause pagination to break. Just omit the permalink.

以上意思是说,如果你使用了pagination,那么permalink的使用会阻断pagination的执行。也就是说,如果你有一个分页列表的页面,因为你在该页面中使用了permalink改写路径,那么页面中的paginator.posts将会失效,页面内容也将加载失败。


<!-- This loops through the paginated posts,if you had used permalink settings, the paginator parsing job will fail. -->
{% for post in paginator.posts %}
  <h1><a href="{{ post.url }}">{{ post.title }}</a></h1>
  <p class="author">
    <span class="date">{{ post.date }}</span>
  </p>
  <div class="content">
    {{ post.content }}
  </div>
{% endfor %}
	

第二点,就要说到liquid的语法。 我们来假设这么个场景,在一个列表加载的页面中,我们需要在循环内判断到达指定加载条目预设上限之时,循环执行break跳出。 这个场景使用liquid引擎是这么写的:


{% assign var = 0 %}
{% for post in site.posts %}
    {% assign var = var | plus: 1 %}
    {% if var > 5 %}
        {% break %}
    {% endif %}
{% endfor %}

也可以这么写:


{% assign var = 0 %} // 这句话可有可无,因为increment会将var初始化为0
{% for post in site.posts %}
    {% increment var %}  // 这句话会自动执行一个print语句,将var打印出来
    {% if var > 5 %}
        {% break %}
    {% endif %}
{% endfor %}

前者的写法让人觉得做一个简单的运算都需要去背记liquid的各种filters,让人觉得麻烦不已。 后者的写法会凭空多出一个默认的print效果,这个也是UI界面编写之时无法接受的。

三、为什么不选用WordPress等LAMP架构的博客框架

作为一名php开发者,wordpress是必须鼓捣的框架之一,但是为什么最终放弃了选用wp作为个人博客首选呢? 原因其实不复杂,主要有以下五个方面:
1. 涉及数据库技术,数据的灾备流程过于复杂,维护时间成本高;
2. wp框架体量重,适合开发较大型的cms内容管理站点,对于个人博客有点大材小用,如同杀鸡用了牛刀;
3. 扩展插件多是其亮点,同时也是拖慢wp站点的一大坑点,php的通用单线程处理机制决定了其瓶颈,就算升级了鸟哥的php7估计也根治不了多扩展插件wp站点的加载时长的问题;
4. 需要关注的安全问题较多,可定制化有限,如果不合适的定制会引发无法升级的问题;
5. 需要一台VPS或者Web Host主机,也就是将多出一份硬件方面的开销,如果要求访问稳定、连接速度可接受的主机,购买价格也会逐级上升。

四、简明middleman实践之路

##### 4.1 由于middleman是ruby语言的作品,先了解以下关于ruby语言的关键概念。

  1. ruby:顾名思义,指的就是ruby语言。
  2. rvm:全名ruby version manager,注意它和jvm不是一个含义哦。rvm是管理ruby本身,也包含了ruby插件的管理。
  3. rails:指的是ruby著名开发框架,认识ruby的人都认识它。
  4. rubygems:ruby程序包管理器,可以将ruby程序打包成gem,作为一个独立安装单元安装到计算机中。
  5. gem:指的是封装起来的ruby应用程序,或者代码库。终端中使用的gem命令,是指使用rubygems安装程序应用。
  6. gemfile:配置文件,用于定义指定应用所依赖的包,然后可以提供给bundle命令执行,类似于shell命令中的.sh脚本。
  7. rake:该程序包是ruby所需要安装的包中最为关键的一个。rake是一个ruby的构建工具,类似于Make、ant、maven、gradle不等。rakefile便是其执行的构建任务配置文件,其中涉及特定的DSL撰写方式,类似于groovy在gradle中的应用一样。
  8. rakefile:rake构建任务中所涉及的任务配置文件。
  9. bundle:等同于批量执行gem命令,配置好gemfile之后,使用bundle install可以实现包,及其依赖包的自动下载与安装。
4.2 middleman的安装疑问

middleman依赖于rubygems、bundle、gemfile、rake等工具或框架。在安装一系列ruby依赖库的过程中,会遇到connection fail,error loading,can’t be found等问题,这些问题的原因是因为rubygems.org在国内访问不稳定的缘故。

正所谓兵来将挡,水来土掩,方法总是比困难要多得多。
国内的大淘宝,还有ruby-china官方,都给大家做了一个同步频率为15mins的ruby gems的镜像,我们可以将自己本地的ruby gems、bundle源配置切换到国内。
参见这里:
- ruby-china官方源:http://ruby-china.org
- 大淘宝的镜像源:http://ruby.taobao.org

在安装各项依赖之时,建议先配置好gemfile,然后使用命令bundle install自动实现下载和安装,而不是使用middleman server逐项发现缺失库,因为从操作效率上是得不偿失的。

4.3 slim VS liquid

关于liquid的一些用法和自己不感冒之处,前面已经提及了部分,这里不再大施笔墨赘述。 这里说说slim的情况。

liquid是jekyll默认配置的官方模板引擎,而slim也是middleman首推的前端引擎。 liquid有很多晦涩不易入门的语法结构,而slim相对而言就显得优雅了许多。

slim所倡导的是,一切页面元素、样式、js均可结构化。使用slim可以大大简化前端页面渲染的逻辑实现结构,没有了liquid般的html+js+liquid+css混合搭配在.html文件里使用的尴尬,映入眼帘的均是层次分明,结构清晰的模板语法。

下面作一下两者的对比,孰好各位看官心中自有结论。

  • html+js+css+liquid混合应用

<!-- html+js+css+liquid混合应用 -->
<head>
    <meta name="theme-color" content="{{ page.color }}">
    <link rel="stylesheet" href="{{ "/assets/css/main.css" | prepend:site.baseurl }}">
    <link rel="canonical" href="{{ page.url | replace:'index.html','' | prepend:site.baseurl | prepend: 	site.url }}">
    <link rel="alternate" type="application/rss+xml" title="{{ site.title }}" href="{{ "/feed.xml" | prepend: site.baseurl | prepend: site.url }}" />
</head>

  • slim的前端结构
<!-- html+js+css+slim混合应用 -->
doctype html
html lang="ja"
  head
    meta charset="utf-8"
    title The Elevatorpitch
    meta name="viewport" content="width=device-width, initial-scale=1.0"
    css:
      body {
        padding-top: 20px;
        padding-bottom: 40px;
      }
  body
    div class="container-narrow"
      div class="masthead visible-desktop"
script src="/js/theelevatorpitch.js"

更多slim应用可以关注我的gist:Use slim engine for building html tags

五、Try the way you like

前端技术的不断发展,纯静态网站的性能问题也逐步被放大,在我们使用静态网站生成工具的同时,性能问题也一定需要着重考虑。 例如,gzip压缩、js+css压缩、单次加载批量样式文件和js文件、html页面布局层次简化、js依赖关系处理、css继承关系的整理、前端cache/data等缓冲区域技术的应用、异步ajax请求时长的监控、sass/less的样式结构简化和预处理,等等方面。

只要始终坚持用户至上,读者至上的原则,一定能够找到最适合自己预想的方案。

前面所涉及的参考资料有如下,感兴趣可以自己翻翻看看:
- Liquid官方文档
- Jekyll关于pagination说明
- Middleman官方文档
- Jekyll中paginate与permalink的冲突
- Upgrading Jekyll 2 to 3 on GitHub Pages
- How to use slim engine for building html tags?

学习笔记:项目管理之规划项目

一、回顾基础知识

  • 项目经理:把控“完成”的含义,并带领团队完成项目的人;
  • 项目的关键驱动因素、约束和浮动因素;
  • 传统铁三角:成本 + 时间 + 质量/范围;
  • 通过沟通发掘决定项目的关键驱动因素;
  • 少用“为什么”,避免“怎么做”,项目如何才算成功;
  • 探讨制定项目章程,有助于凝聚团队;
  • 从“摩尔鸿沟”表象,看项目、团队发展历程。

二、引导语

“项目规划是在不断进行的,这只是开始。”

三、纲要

  • 如何踏上项目征程
  • 讲解开发项目规划模版条目
  • 项目如何才适合发布上线
  • 发布条件的管理
  • 最后的几句总结

四、辨析两句短语

五、使项目踏上征程

  • 规划不必完美无缺,实际上这也是做不到的;
  • 采用时间盒子的方式启动项目的规划;
  • 要根据经验而不是预言来规划项目。

“规划毫无用处,但是制定规划必不可少。” ——艾森 · 豪威尔

六、开发项目规划模版

下面介绍一些常用的模版条目 …

  1. 产品意图
  2. 发布历史记录
  3. 发布条件
  4. 项目目标、时间盒子目标
  5. 团队职责、项目运作的组织
  6. 项目日程总览

    • 样式1

    • 样式2

    • 样式3

  7. 人员配备
  8. 建议日程
    • 样式1

    - 样式2

温馨提示:小心过早细化日程

9.制定项目风险列表

七、制定发布条件

发布条件可以告诉我们项目“完成”的含义。

八、拟定步骤

  1. 确定当前发布的最重要的因素(包含关键驱动因素)
  2. 草拟发布条件
  3. 修订发布条件,使其符合SMART原则
  4. 达成多方共识

九、SMART原则

十、使用发布条件

十一、关键知识点

  • 项目规划实在不断进行的,这只是开始;
  • 为项目团队、出资人和项目经理自己制定发布条件,以明确定义“完成”的含义;
  • 项目规划不必完美无瑕,但是它必须存在。

十二、结束语

Git软件开发过程

一、关于Git与Subversion的区别

DraggedImage

二、目前我们用Subversion是怎么执行软件过程的

DraggedImage-1

三、优势与缺点

  1. 架构
* Git:分布式,所有的teammates本地可以clone一份独立完整的仓库,而不仅仅是某一个版本的镜像拷贝;开发者可以在本地clone仓库中完成所有vcs的操作,只有当需要协同工作提交代码到远程仓库的时候,才需要联上网络。


* Subversion:中央集中式,所有的teammates都面向同样一个远程仓库工作;checkout出来的本地工作区代码只是远程仓库某一版本的一份镜像拷贝。
  1. 仓库结构与URL
* Git:对于Git而言,仓库会独立于开发者的本地磁盘中,在仓库的根目录中只包含了一个”.git”文件夹,所有的branches、trunk(PS:git中名称为master)、tags均是通过命令操作而生成的,并非通过URL路径。 在Git中,URL类似于ssh://git@example.com/path/to/git-repo.git,仅仅是指向了仓库的一个标识。


* Subversion:分支的url类似于svn+ssh://svn@example.com/svn/trunk,每一个分支独占一个唯一URL,每一个URL都会直接定位到每一个分支在远程仓库中的路径位置。 对于Subversion,会有一个trunk分支作为开发主线分支,会有很多branches分支作为并行分支,tags则是mark上某一特定的发布版本。
  1. 分支管理
* Git:Git分支相对于其他的vcs是非常不一样的设计理念,一个Git分支仅仅指的是指向某一确定版本的简单指针,因此,Git的分支是无拷贝、无新建目录、几乎无开销的。


* Subversion:正如我们所知道的,SVN中的分支仅仅是项目的一份拷贝,是一个具有特殊含义的普通文件夹;多分支则是多文件夹的形式。
  1. 提交操作
* Git:若你使用的是Git,你的提交操作不受网络的影响,你的提交仅仅影响了本地仓库,仅当你需要于远端仓库同步内容之时,才需要使用到网络; 另外的,在你本地仓库还存在一个so-called Staging Area,并非你的所有文件需要在一次提交中全部commit,你可以选择指定的变更放入staging area中,从而在本次提交中仅仅包含你所选定的变更条目; 关于Git的版本号,大家都知道,Git是分布式的vcs,要想和svn、cvs一般生成revision#5,revision#6类似的递增数值作为唯一版本号是不可取的,但是我们也同样需要一个唯一的标识来辨别每一次提交,而Git的做法是使用了”commit hashes”。


* Subversion:当你使用的是SVN,假设你要提交代码,以下是你的提交过程:


  * 首先设备必须是联网的,可以与远端中央仓库建立连接;


  * 将提交的内容立即传输到远端中央仓库;


  * 远端中央仓库生成递增的版本号,并赋予本地分支。
  1. 协同工作
* Git:若你使用的是Git,你需要决定何时将你的本地仓库的内容同步上传到远端仓库分享出来,而Git不会为你作任何的自动上传的操作; 这样子的分享过程相对于其他的中央仓库式的vcs系统来说是更加安全的,所发生的冲突也只会发生于你的本地(仓库)而非远端服务器的仓库,这将更能帮助你规避打乱teammates工作内容冲突的风险。


* Subversion:当你将本地分支内容作commit操作之时,你的内容便会分享到远端中央仓库中,其他teammates也都能同步到你所提交的内容。

四、集成工具简介

  1. SourceTree:开源的Git源代码管理工具

  2. TortoiseGit:开源的Git源代码管理工具

  3. EGit:Eclipse插件,最新Mars版本已经自带

  4. Gitflow Nightly:Eclipse插件,支持Git-Flow

五、常用Git基础知识

认识Git的几个关键目录

DraggedImage-2

  • Working directory:工作区

  • Index directory:暂存区

  • Local repository:本地仓库

  • Remote repository:远端仓库

常用的Git操作

134623_LGJb_1469576

  1. clone:克隆项目到本地工作区,类似svn checkout

  2. checkout:创建/切换本地仓库的指定分支到工作区中

  3. commit:将本地工作区代码提交到本地仓库

  4. push:将本地仓库代码同步到远端仓库

  5. pull / fetch:将远端仓库的代码同步到本地仓库/工作区

* pull:fetch + merge,该操作会影响工作区


* fetch:从远端仓库获取并更新到本地仓库中,不影响工作区
  1. merge / rebase:从指定分支(PS:分支名称常跟在命令之后)中获取更新并合并到当前分支
* merge:


* rebase:
  1. stash:备份/唤出当前的现场状态(包含工作区和暂存区)
* git stash [save -a “msg”] 备份当前的现场状态


* git stash list 显示已保存的现场状态列表


* git stash pop/apply [--index][<stash>] 恢复工作状态,若不含带参数,则从状态栈中获取最新的。pop在获取完成后,从栈中移除该状态,apply则不会从栈中移除


* git stash clear 清空状态栈中的所有内容


* git drop 删除状态栈中的指定状态

六、可供参考的高阶应用方案

什么是Git-SVN的扩展开发模式,即本地开发应用Git的强大分支特性,当最终push操作的时候,目标仓库设定为SVN远端仓库。 这里点到为止,只提及一下,以便有既想使用Git又纠结无法脱离Subversion的开发者去使用,这确实是一种很赞的“曲线救国”方案。 什么是Git Stash的开发模式,即一个工程师可以并行开发多项内容,要求用到切换分支的操作,而在没有提交到本地仓库之前,可以使用git stash命令将当前分支的工作区和暂存区的状态镜像下来。当回过头来需要继续开发的时候,使用git stash pop将指定的状态唤出后,可以继续未完成的内容。

七、Git-Flow介绍

一图胜过千言万语

DraggedImage-3

关键几个分支的概念全解
  • 主分支

    • branch:保存当前开发成果的分支

    • master:保存当前可供生产部署的代码,在每次发布之时推荐为每次新增发布的代码都打上一个TAG,供后续代码维护使用

  • 辅助分支

    • Feature:开发完整功能、新特性,从develop分支发起的分支

    • Release:用于发布新的产品版本而设计的,支持从develop分支派生

    • Hotfix:属于计划外创建的可供生产部署的代码分支,普遍场景是软件遇到了异常情况或发生了严重必须要立即修复的缺陷之时。支持从master分支(或者其中的某一个TAG版本)中派生出来

  • 分支命名惯例

    • Feature分支:feature-*

    • Release分支:release-*

    • Hotfix分支:hotfix-*

八、GitHub Flow

DraggedImage-4

九、Mike Flow (Base on 《Git in Practice》)

Single Pattern

DraggedImage-5

Multiple Pattern

DraggedImage-6

十、Jerome Flow (I call it this name^_^)

  • Version 1: 该版本适合于团队成员较小,各个泳道分支规范制定严格的项目。

jerome-git-flow-1

  • Version 2:适合于多环境分支、多团队协作的项目,没有太多的规范约束,将最大的自由度释放给开发者们。

jerome-git-flow-2

参考资料

研发人看互联网营销

很荣幸地,昨天参加了公司市场VP(以下简称FW)面向全公司作的关于互联网营销的公开演讲。这些年来,公司的市场营销方面从一个局外人的角度上看来确实发生了非常大的变化、进步和飞跃,加以是FW在公司层面公开分享的为数不多的干货讲座之一,满怀期待,同时感触良多,希望写下来以作为后面需要时作参考。

文章内容准备分为两部分进行记录,第一部分阐述一下的讲座的具体内容,从中挑选出一些自己觉得非常干货的关键点详细撰写一下。第二部分将从讲座中的关键点出发,映射到研发的日常工作上来,分析如何将其中的策略应用到研发工作上来,帮助提高程序员以致于团队层面上的工作效率(注:后续将补发第二部分的博文作扩展分析,请期待~)。

一、学习能力

作为从原研发负责人的角色变为市场部门负责人的角色,FW是非常成功的一个案例,当中FW提到了自己再三强调的一点——学习能力。一个人拥有强有力的学习能力,他将成为或者正走在成为胜利者的路上。有的人虽然在十几年的象牙塔学习生活中并不是最优秀的,但是有一点如果做到了,那么你就是值得被社会所肯定的,那就是“笨鸟先飞”“勤能补拙”“早起的鸟儿有虫吃”。

二、拥抱变化

可能是因为这四个字作为公司里经常秉承的工作态度吧,季节在变化,时代在变化,客户群在变化,消费态度在变化,总而言之,人们是贪婪的,心中的需求将会永无止境地在发生着变化。虽然中学时候老师们都教育我们要学会对生活满足,但是对于有更为远大理想的人们,这句话可以也应该被改变。

年轻人,抱怨多了总是不好的,假设生活给了你不能承受之轻,不要悲伤,不要心急,要坦然地去面对和处理好,这也将毫无疑问地将帮助你更快地踏上成功之路。看到这里,是不是觉得很鸡汤,不过虽然鸡汤,但是道理却还是道理。

三、抓住大面,傍大款

看着这字眼挺晕的对吧,其实当时作者我也晕。不过听完之后就豁然开朗了。

什么是抓住大面?大面,指的是关键点,主要方面,或者说是痛点。任何一件事情,总有这么一个或者几个举足轻重的关键点,就像武侠小说中提及的命门、致命穴道。只要将这一点抓住了,就等于抓住了所有。其中当然,包含了一定方面的避轻就重的处事思路。

那么什么是傍大款?傍大款 == 抱大腿?是的。不过关键在于,你要找到合适的大腿。与携程的合作是傍大款,与京东的合作是傍大款,请林志颖Kimi代言是傍大款,冠名江苏卫视非诚勿扰节目是傍大款…

还有许许多多的类似案例,至于如果你需要确切的文字上的含义来解释傍大腿是什么,那么建议你去百度吧,这篇文章不会去解释这一点。

四、单点营销推广,打穿打透,增加压强

无论是在何种渠道上投放广告或者作品牌宣传,都要打透吃透。什么是打透吃透?无论在推广时间上、广告类型上、还是用户群的覆盖面上,都要做到预期的层面,抓住关键点,甚至更好的是将这一关键点做到极致。

五、线上线下整合

对于当下互联网环境因素决定,在互联网企业中,不能仅仅满足于在互联网的传播上做到脍炙人口,这一点是不够的,还需要在线下的日常生活中继续延伸,使得家喻户晓,人人皆知。只有线上线下整体综合起来,才能更好更为全面地覆盖客户群体,缺了任一方面都是不完整的推广方案。

六、价格战

这里要说的不是推荐大家一味地在和竞争对手拼低廉价格竞品,而是说,我们不仅要关注产品的性价比高,便宜,更要让客户知道我们是真的便宜,并不是噱头。

七、找合适的渠道或者实物作为宣传载体,把推广的理念/概念投射出去

江苏卫视非诚勿扰节目是渠道,林志颖Kimi代言是载体,一个覆盖了客厅互联网用户群体,一个覆盖了亲子旅游的概念,两者都是精挑细选出来最为合适,性价比最好的推广载体/渠道。寻找这一类载体之前,要从各个方面收集关联信息作对比,或者请专业调研机构帮助统计与分析,不建议决策者个人的喜好来决定推广选型。

为什么是林志颖Kimi?其中缘由可以概括为俩字:“够火”。

为什么是江苏卫视非诚勿扰?其中缘由可以概括为仨字:“够普及”。

另外一点需要提示的是,选定的载体或者渠道,一定要与本身产品、企业文化、宣传理念相符合,这一点是决然不能摒弃的。

八、口号一定要喊,喊多了自然就是真的

这一点觉得没必要详细阐述,懂行的人,一看就懂了。可能办法瞅着比较土,但是应用起来还是实在的。什么“引导者”“领航品牌”神马的等等,都是需要喊的,当然,这仍然是要具备基本的统计数据支撑,不能瞎喊,要找准了痛点喊。

九、简单粗暴可能是某些情况下最为合适的

推广方式不一定要多么地复杂,要求每次都具备多么地技术含量。这里并不是推崇没有技术含量的手段,这里想要说明的是,除了技术含量外,我们更要着眼于要点,击中营销推广的“要害”,简单粗暴,不择手段,达到目的的办法,就是当下最好、最有效的方法。如果一味地追求高技术含量,但是却低产能的办法,那么这些办法舍弃也罢。

十、产品即营销,产品的质量仍然是最重要的,没有之一

无论是何种办法,无论得到什么样子的推广效果,要想脍炙人口,人人信赖,忠诚于你的产品,产品的高质量保证是务必不可松懈的。只有好的产品才值得被推广,只有高质量的产品才能更好地被推广,被消费者所接受。

十一、口碑品牌在OTA行业尤其重要

旅游产品不同于一般的实体产品,不像iPhone,不像小米手机,不像乐视电视。旅游产品是虚拟存在的,在出游之前客人都将看不见摸不着,只收到一条出游通知的短信,只能客人自己YY想象。如果产品服务口碑不好,很难让消费者掏腰包下单消费;如果企业口碑不好,很难得到消费者的光临和信赖。旅游产品是体验式产品,口口相传的口碑式传播是非常重要的。

十二、营销社会化

无论是正面积极的营销手段,还是有意无意中收获的在可控范围内的消极影响,从营销推广上看来,都是具有一定积极意义的。营销的PR老大就好比战场上渴望着打仗的将军,如果没有战役,那么这位将军将是孤独的和寂寞的。其中提及的2015年发生的17家供应商联名抵制的案例便是这一内容的参考案例。

十三、擦亮品牌,全员营销

一个人的力量是有限的,但是假设是几个人、一群人、一大伙人、甚至于所有人都在为公司的产品品牌作有意无意地、潜移默化地在亲朋好友间宣传,那么力量可以相信是不可估量的。

十四、追求不会停止

途牛会继续在品牌塑造的道路上继续迈步前进,在2015年的夏秋交际,依然会有重磅举措,鉴于机密考虑,各位请继续关注我们,你会发现我们会让你们继续惊讶惊叫着。

最后,6.16是FW的生日,这里也同时给他送上一份衷心的祝福,Happy birthday to FW ALM!

走进社会生活,在实践中成长

接下去的文字是自己高中时期跟随社会实践导师们和一个team的同学们一起完成的旱情调查报告原文。由于时间已经过去多年了,在互联网上已经几乎找不到了。这里发出来分享给大家,顺带也怀念/纪念一下已经逝去的中学年华。

――“儋州市旱情调查”研究性学习活动工作小结

海南省儋州市那大中学“儋州市旱情调查”小组

指导教师: 曾良 郑培生 陈美娟

2004年以来,海南省遭受到50年来少有的干旱,儋州市作为海南省人口最多的市县和全国农业百强县,农业生产和人民生活均不同程度地受到旱情的影响。为了解儋州市旱情,分析干旱产生的原因及影响,为学校、社会抗旱避灾提出相关建议,培养创新精神和实践能力,提高学生综合素质,2005年3月4日至30日,活动小组72名同学在3名指导教师指导下走进社会生活,到儋州市全部17个乡镇、44个村庄和海南省松涛水利工程管理局、儋州市水务局、儋州市农业局、儋州市气象局等部门进行调查,在实践中积累了大量的材料,撰写了13篇《小组调查报告》和50多篇《个人感想》,分别向学校、政府相关部门提出5点建议和6点建议,向社会发出1000份《节约用水,共抗旱灾》倡议书。这次活动的部分成果在校内外展出后,在社会上引起极大的反响,《海南日报》和儋州市电视台先后对此次活动进行报道。

一、活动的选题目的和活动特点

1、这次活动具有较强的针对性。2004年以来,海南省遭受到50年来最严重的旱灾。作为海南省人口最多,面积最大的市(县)和全国农业百强市(县),儋州市的旱情牵动着全市人民的心。海南西部和北部工农业、居民生活用水的源头,也是海南最大的水库――松涛水库就坐落在儋州市和白沙县、琼中县交界处,海南松涛水利工程管理局就设在儋州市政府所在地――那大镇,松涛水库管理区设在儋州市南丰镇。

2、这次活动具有较强的科学性。在这次活动的过程中,学生在教师的指导下注意学习和运用科学研究的方法进行调查,大多数学生已经初步掌握问卷调查法、访谈调查法等研究方法,并在活动中运用上网查询、查阅文献、访谈当地的专家学者、实地考察等方式进行调查,对资料的整理、分析,形成调查报告等也基本符合科学研究的要求。各活动小组共撰写了13份小组调查报告。活动小组运用相关学科知识,从客观、人为两大角度,从地球环境、气候、社会经济发展、生活习惯等方面科学地分析干旱产生的原因。

3、这次活动参加学生多,调查面广。活动小组是在同学们自愿报名的基础上成立的,有72名学生参加。到儋州市全部17个乡镇、44个村庄和海南省松涛水利工程管理局、儋州市水务局、儋州市农业局、儋州市气象局等部门进行调查,调查面涉及儋州市农业、生活用水情况、松涛水库的蓄水情况、人们对干旱的认识、干旱产生的原因、政府社会抗旱救灾的措施等。

4、这次活动凸显教育性。在这次活动中,学生们对儋州市乃至海南的旱情表现出前所未有的关注,并反思生活上用水习惯,这一点他们所撰写的《小组调查报告》和《个人感想》中体现得尤为突出,他们还行动起来,共同向全校师生发生倡议,并走上街头向市民发放1000份《节约用水,抗击旱灾》倡议书,并积极在市政府所在地――那大镇上进行多种形式的宣传。在活动过程中,学生的团体精神和合作意识得到锻炼和培养。

5、这次活动社会影响大。活动小组的活动在全校的广大师生中引起了强烈的震撼,许多学生通过学校校园网等途径对节约用水、保护水资源进行呼吁。活动小组所到之处,得到各乡镇领导、村民的大力支持和帮助,在社会上引起广泛的关注。活动小组的部分成果在校内外的展出及向全体同学向全市人民发出的倡议书在市民中极大的反响,《海南日报》、儋州电视台等媒体先后对这次活动进行报道。

二、活动的内容

1、科学调查

①调查儋州各乡镇旱情,特别是各乡镇农业受旱情况和居民用水情况

②调查这次旱情发生的原因

③调查儋州市农业生产、农民的生活受到旱情的影响情况

④调查儋州市90万市民的生活受干旱的影响情况

⑤调查当前儋州市抗旱形势

2、自我反思

①撰写活动感受

②小组讨论、交流活动的感受

③各小组撰写调查报告

3、宣传教育

①展示调查成果,进一步扩大活动的影响

②向社会发出倡议书,向学校、政府提出建议

三、活动的实施过程

(一)活动的准备阶段(3月4日-3月18日)

1、活动方案的制定。新学期开学后第一个周(3月4日),在广泛征求师生意见的基础上,儋州市那大中学“儋州市旱情调查”研究性学习活动小组成立,并制定活动方案。(《“儋州市旱情调查”研究性学习活动实施方案》见附件1)

2、确定小组成员、分组。在学生自愿报名的基础上,共确定了72名学生成为小组的成员,并划分为18个小组(按乡镇和部门划分,17个乡镇分成17个小组,松涛水利工程管理局和市政府相关部门为部门组),明确实地调查的主要任务是调查各乡镇的旱情,从各部门了解、调查旱情及相关原因、相关抗旱措施。

3、加强辅导,做好活动的各项准备工作和安全教育工作。

3月12日 召开活动小组全体成员会议,进行动员,并分别举行三场辅导讲座

曾良老师为全体成员作“如何开展调查研究”的辅导报告

郑培生老师为全体成员作“提高调查研究的科学性”辅导报告

陈美娟老师为全体成员作“从地理环境分析、研究旱情”的辅导报告

3月19日 召开全体活动小组成员第二次会议,做好调查所需材料的准备工作和安全教育工作

(二)活动的实施阶段(2005年3月20日―3月21日)

2005年3月20日 活动小组全体成员集中的方式到松涛水库管理区和新州镇、木棠镇等两个乡镇实地考察松涛水库目前的基本情况以及两个镇的旱情及当地的抗旱情况。全部72名学生中除一名因病中途退出外,其他71名学生全部参加。

2005年3月21日 71名学生以分组活动的方式分散到全市17个乡镇及儋州市农业局、水务局、气象局及海南松涛水利工程管理局进行调查。

(三)整理材料阶段(2005年3月22日-3月26日)

2005年3月22日 各小组分组整理材料,上网或到图书馆查阅相关资料

2005年3月23日-3月25日 各小组撰写小组调查报告,小组成员撰写活动个人感想

2005年3月26日 各小组把这次活动的各种材料(包括活动记录、所拍摄的相片、撰写活动的感受和各小组的调查报告等资料)上交指导教师

(四)活动的总结及成果展出阶段(2005年3月27-3月30日)

2005年3月27日 召开活动小组全体成员会议,各小组、各成员交流活动的心得体会,总结这次活动

2005年3月28日 活动小组把这次活动的部分成果在校内外进行展出,并向全校发出“节约用水,共抗旱灾”的倡议,并向学校提出“共抗旱灾,从我做起”的五点建议;活动小组向市政府及相关部门提出“全市动员,形成节约用水、共同抗击旱灾的共识”等六点建议。

3、2005年3月29日―3月30日 活动小组向全市市民发出“节约用水,共抗旱灾”的倡议书,并走上街头发放1000份倡议书。

四、活动的成果

(一)较为全面地了解儋州市旱情、水情及抗旱形势

1、儋州市目前农业生产受到旱情的严重影响。从调查到的材料和数字看,除了两个镇没有调查到相关数字外,其他15个镇都存在不同程度的旱情,受旱面积超过50%的镇有4个,受旱面积超过20%的镇有10个。全市受旱农田面积目前已达64.25万亩,如果干旱持续下去,受旱面积将会继续扩大。(具体见表一)

2、我市目前的生活用水基本还能保持正常,但部分农村的生活用水面临严峻的形势。目前,全市因旱有10个镇40个自然村5万人出现饮水困难,占全市人口的5.56%,比重虽小,但绝对数并不小。而且,如果干旱再持续两个月以上,松涛水库将无水可放,全市居民的生活用水将面临严重威胁。(具体见材料一)

3、广大农村,特别是受灾农村目前生活秩序基本正常,但部分受灾严重农村的农民生活受到干旱较大的影响。

4、大多数村民已感受到旱灾对他们生活的影响,但在市政府所在地――那大镇,居民认为旱灾对他们生活有影响的仅为41.6%,远低于农村的77%,这说明市政府和松涛管理局以人民生活为重的决策已经发挥作用。(具体见附件二《问卷调查统计结果》)

5、大多数村民及市民已经认识到节水的重要性,但落实到行动上明确不够,如被调查市民中有93.19%的人认为应该节约用水,但只有52.69%的人认为身边的人在节约用水。(具体见附件二《问卷调查统计结果》)

6、儋州市各相关政府部门、各乡镇,非常重视领导农民抗击旱灾,引导广大干部群众采取封江堵溪、机械抽水、打田头井、挖深水井等方式解决水源,引导农民改种抗旱作物等,但由于资金、设备等方面的原因,没法解决所有受旱土地的用水问题,相当部分的受旱土地没法耕种。

7、受旱灾的影响,儋州市农业生产和农民的生活将会面临近50年来从未有过的严峻局面,预计今年我市农业生产和农民的收入将会受到严重的影响。

8、儋州市政府所在地――那大镇的广大市民关注旱情,也有较强的社会责任感,如有81.17%的市民了解儋州的旱情,73.73%的市民会为受旱群众伸出援助之手。(具体见附件二《问卷调查统计结果》)

表一:各乡镇农业生产的情况

注:表中数字为各小组调查的结果

表二:松涛水库情况

表三:松涛水库近4年水位

材料一:儋州市当前水情旱情及抗旱动态(资料来源:《儋州三防》[第三期]2005年3月11日)

1、儋州旱情:

全市受旱面积:目前受旱面积64.25万亩。

农作物受旱面积:其中甘蔗22万亩;水稻:0.45万亩;瓜菜:2.5万亩;花生:4.5万亩;番薯:3.5万亩;水果:5万亩;橡胶:26万亩;其他:0.3 万亩;水田无水插秧面积6万亩。

饮水情况:全市因旱有10个镇40个自然村5万人和1.0万头大牲畜出现饮水困难。

2、儋州水情:

至2005年3月11日止,全市水库(不含松涛)总蓄水量为7539万立方米,有效供水量6333万立方米,比2004年同期减少了1652万立方米。

松涛水库今年早造最多再给我市供水8980万立方米,比2004年同期减少5850万立方米。

目前全市有效供水量为15312万立方米,比去年同期减少7502万立方米。

3、儋州抗旱动态:

2004年入秋以来,市农机中心共组织各类抗旱机具1.5万台套,投入抗旱人力11.8万人次,投入作业消耗柴油近200吨,浇灌面积达50多万亩次。2005年3月1日至3月11日,全市投入抗旱人数3.5万人,抗旱泵站4处,抽水机2000台,运水车辆30辆次,抗旱用电1.2万度,抗旱用油17吨,浇灌面积5万亩,临时解决1.632万人和0.52万头牲畜的饮水困难,全市共投入抗旱资金79万元.

(二)旱情原因分析

1、客观原因

(1)全省降水偏少是导致这次旱灾发生的直接原因

海南岛属热带海洋性季风气候,旱季、雨季分明。每年冬春时节即当年11月至次年4月,为干旱期,降水少。而每年的夏秋时节即5月至10月,则是琼岛的多雨时节,降水多,降雨量约占全年总量的80%,这是琼岛气候的一大特点。但就去年而言,在传统的干旱期过后,进入雨季的琼岛降水仍然偏少,并出现了阶段性夏旱和持续性秋旱。2004年以来,儋州市各地高温少雨,1―10月份,全市平均降雨量仅为1077.7毫米,比正常年份平均少418.4毫米, 2004年11月―2005年4月,降雨偏少130-190毫米。2004年是1957年以来年降雨量最少的一年。它直接导致水库蓄水量不会有较大补充,这是导致这次旱灾发生的直接原因。

(2)厄尔尼诺现象

根据以往50年的数据统计,出现厄尔尼诺年时,气温一般偏高,冬天比较暖和。据介绍,2004年也是我国自1961年以来第4个气温偏高年。海南省大部分地区偏高0.7摄氏度,部分地区偏高1摄氏以上。儋州市2004年11月―2005年4月,天气偏暖,气温偏高。气温偏高加快水分蒸发,对加剧琼岛旱情尤如“火上浇油”,这是导致这次旱灾发生、持续的又一原因。

(3)55年一遇“空台年”

2004年海南岛无热带气旋登陆或影响,海南省遭遇55年来的第一个“空台年”。据了解,西北太平洋和南海地区平均每年大约有二十七八个台风生成,平均每年有6.9个台风在我国登陆。去年,热带风暴、台风生成总数和沿海登陆个数均较常年偏多。但在南海仅有3个风暴生成,且热带对流活动的区域相对偏北和偏东, 使大部分生成的台风和热带风暴相对偏北和偏东,2004年台风登陆地点集中于广东珠江口以东到浙江杭州湾一带,而没有进入海南岛。由于没有热带气旋影响,海南岛大范围的强降水过程明显偏少,绝大部分地区年总降水量不同程度偏少,各地降雨量的时空分布不均匀更为明显,这是导致干旱发生的又一原因。

2、人为的原因

(1)天然林破坏

天然林有落叶等长期堆积形成的凋落层,它们覆盖在地表,在降水时可以减缓水流,加大地表水渗透量。同时,天然林里每株植物体内都能蓄积大量水分,在干旱时释放出来,缓减旱情。但近十年,海南一些地区,天然林破坏严重,导致森林对水源的涵养功能降低,从而使得河流、水库、地下水等水位下降,加重了旱情。

(2)恶性开发

海南是农业大省,冬季瓜菜、果树种植等具有得天独厚的自然条件,水库库区由于水源充足,不少果农、开发商都盯上了海南的水库,他们毁掉库区周边的天然林种植芒果、龙眼、香蕉等经济作物,活动小组在松涛水库周边考察时发现大量存在此类现象。

(3)水利基础设施先天不足

儋州市由于松涛水库的灌溉,大部分灌区的农业用水基本得到保证。但儋州市的地形较高,不易储水,而且灌区渠道老化失修,渠道过水能力低,灌溉效益逐年下降。三都、峨蔓、松林等乡镇大量耕地因缺水而丢荒。一些中小水库由于资金的原因年久失修,蓄水能力下降,导致在干旱到来时没能发挥其作用。

(4)不合理的用水习惯

在儋州的许多农村,还存在“漫灌”的习惯,就算在干旱严重的今天,在水库库区上游,农田灌溉现象还存在。在生活用水方面,由于大多数的市民缺乏节约用水的意识,浪费水、污染水的现象十分严重,这些因素都人为地加剧了旱情。

(三)活动小组为学校、市政府相关部门抗旱避灾分别提出5条建议和6条建议,向全社会发出1000份《节约用水,共抗旱灾》的倡议书。

1、给学校的建议

(1)学校要利用各种手段加强对学生的宣传,提高学生的节水意识。

(2)同学们应立即行为起来,从我做起,从现在做起,节约身边的每一滴水。

(3)学生干部、共青团员和广大青少年学生应发挥自身的优势,向自己的亲戚好友、向社会宣传节水意识。

(4)把旱情当成重要的课程资源来利用,组织学生深入进行研究。

(5)组织各种形式的报告会、研讨会,请相关专家、学者到会为广大师生科学的分析旱情,提高学生的科学意识和环境保护意识。

2、给市政府的建议

(1)全市动员,形成节约用水、共同抗击旱灾的共识。

第一、儋州市的主要媒体要向全社会通报当前旱情,每周向全市市民公布本市的旱情、水情及抗旱动态。

第二、政府机关及各部门要动员起来,把抗旱救灾当成是一项关系到人民群众切身利益的大事来抓。

(2)做好干旱持续下去的防范准备工作。

当前,政府相关部门应未雨绸缪,建立防旱预案,做好旱灾持续下去的各种准备工作,形成抗旱救灾的常规机制。

(3)要从“地上、地下、天上”做好利用水资源的各项工作。

政府相关部门要引导广大干部群众采取的封江堵溪、机械抽水、打田头井、挖深水井等途径解决受灾地区农业生产和居民用水问题,并在人力、财力、物力等方面予以支持,要积极采取人工增雨作业等方式缓解旱情,从“地上、地下、天上”多方利用有限的水资源。

(4)各相关部门要协调处理好各镇、各村之间的用水矛盾,防患于未然。

相关部门要制定并科学实施水资源利用工作方案,防止抢水、争水及各种因水资源而引发的各种矛盾的产生,有关部门要协调处理好各镇、各村之间的用水矛盾,防患于未然。

(5)社会、政府要帮助受灾地区农民恢复生产,抗灾自救。

第一、政府在财政上要努力为受灾严重的灾区提供更多的抗灾资金和设备。

第二、社会各界要发起一场为灾区人民捐款的筹款活动,多方筹集抗灾资金。

第三、政府、社会要帮助灾区人民解决生产和生活问题,一方面要千方百计地帮助受灾群众恢复生产,解决他们的基本生活问题;另一方面,要通过劳务输出等方式解决受旱农民工的生活来源问题。

(6)要从长远上保护好我们的水资源,做好松涛水库等水利设施周边环境的整治工作和长远规划。

3、给社会的倡议

活动小组的所有成员向社会各界群众发出“立即行动起来,从我做起,从现在做起,众志成城,万众一心,夺取抗旱斗争的伟大胜利”的倡议:(倡议书见附件五)

(1)增强节水意识,请勿浪费每一滴水。

(2)积极行动起来,改变不良的用水习惯。

(3)当好节水宣传员,做好节水宣传工作。

(4)伸出你的手,为灾区人民献上一份爱心。

五、体会和感受

参加活动的学生,除一名因病中途退出外,其余71名学生按计划完成全部活动。18个调查小组共撰写了13份《小组调查报告》,71名学生共撰写了50多份《个人感想》。从学生撰写的调查报告和活动感受中,可以深切的感受到学生所受到的教育,思想所发生的变化,关注社会、关注人的思维品质得到培养,环境保护意识得到提高,科学研究的精神得到培养。

(一)部分《调查报告》节选

兰洋镇农业受旱情况如下:全镇共有水田7800亩,其中因旱无法耕种的农田有5000亩。旱情最严重的有羊龙村、新辛村、头地村、新福村、上村二队五个自然村。

抽样调查兰洋镇南罗村委会:

(1)南罗村委会五个自然村中,有860亩地因旱无法耕种。

(2)仅有360亩田地可插秧(已插秧);无法插秧的田地改种玉米、番薯等耐旱作物。

(3)五个自然村中大部分水井已干枯无水;农业用水和生活用水主要来源于山沟水。

(4)五个自然村中羊龙村的旱情最严重,村民们几乎无水可用,只能从几公里外的山泉挑水解决生活用水问题。

―― 兰洋镇调查小组

此次旱情对南丰镇各村造成的影响有:

⑴可以正常耕作的土地面积大大减少,为今年的收成带来了困难。

⑵基本口粮已难以维持农民的需求;农民的饮水困难问题越来越严重,难度越来越大。

⑶今年各村农业生产面临严峻的局面,村民的生活面临严峻的局面。

――南丰镇调查小组

镇政府全力组织抗旱,措施得力,如向农民提供一些免费的耐旱种子;合理的分配现有水资源;组织劳动力输出等。

――雅星镇调查小组

几条建议:

(1)镇政府人员应定时向各村进行调查,了解各村的受灾情况,并指导村民抗灾自救。

(2)各村委会要积极号召村民抗旱,并做好组织工作。

(3)村民要理智,要有共抗旱灾的决心,积极投入到生产自救中去。

――海头镇调查小组

(二)部分《个人感想》节选――

面对这种干旱情况我不禁自问:我能做些什么?我们能为他们提供些什么?

――林颖全

关不紧的水龙头意味着什么?节约用水,一个很老的话题,同时也是很严重的问题。一滴水的价值我们知道多少?

――刘钟锴

目睹旱情,我为以前浪费水资源、不爱护环境的行为感到非常惭愧。我对不起那些受灾的父老乡亲!对不起大自然!

――李汉东

我再也不是过去那种“纸上谈兵”了!我领悟到了实践对人生的重要性。人实践了,感悟才最深。

――何烈方

这次调查不仅让我增长了见识,还使我懂得了生活的艰辛,生命的宝贵!

――陈金龙

农民热情、淳朴、善良、待人真诚,让我在调查活动中仍能感受到亲人般的关怀。

――王永慧

我为我现在所做的事情感到自豪,因为我开始替自己赎罪。我要用我的双手,负起保护水资源的责任!

――黄颖

从小害怕雷鸣闪电的我,现在却总是时时在向老天祈祷,求求老天现在就下雨!给干渴的大地和人们喝个痛快吧!

――吴特丽

附件1:《儋州市旱情调查研究性学习活动实施方案》

附件2:《调 查 问 卷(一)》

附件3:《调 查 问 卷(二)》

附件4:《问卷调查统计结果》

附件5:《倡议书》

附件6:《部分小组撰写的调查报告》

二00五年三月三十一日

附件1:

“儋州市旱情调查”研究性学习活动方案

一、课题背景

2004年以来,海南省遭受到50年来最严重的旱灾。作为海南省人口最多,面积最大的市(县)和全国农业百强市(县),儋州市的旱情牵动着全市人民的心。海南西部和北部工农业、居民生活用水的源头,也是海南最大的水库――松涛水库就坐落在儋州市和白沙县、琼中县交界处,海南松涛水利工程管理局就设在儋州市政府所在地――那大镇,松涛水库管理区设在儋州市南丰镇。

旱情的背后是各种深刻的科学道理,现实的背后是理性,是什么原因造成这次儋州市乃至整个海南大旱,我们希望通过调查能弄清背后的原因。同时,通过这项活动让我们深入社会,在活动中提高我们的综合素质。

二、活动的目的

了解儋州市的旱情,分析干旱产生的原因及影响,为学校、社会抗旱避灾提出相关建议。推动研究性学习活动的蓬勃发展,培养青少年的创新精神和实践能力,提高青少年的综合素质,鼓励优秀人才的涌现。

三、活动的时间、地点和人员安排及准备的资料

1、活动时间:3月4日――3月30日

2、活动地点:儋州市各乡镇、市政府相关部门和松涛水利工程管理局及松涛水库库区

3、活动人员安排

(1)儋州市那大中学旱情调查研究性学习活动小组指导教师分工

曾良(学校教研室主任,中学政治一级教师):统筹指导本次活动,并负责指导学生撰写调查报告、活动感受

郑培生(中学物理一级教师,有多年指导青少年科技创新大赛经验):负责指导科学研究方法的指导

陈美娟(中学地理一级教师):负责指导学生分析干旱产生的原因及提出科学性的设想、建议

(2)活动参与学生:原则上划分为18个活动小组,按17个乡镇和各相关部门(包括海南松涛水利工程管理局和儋州市各相关政府部门)划分,每组学生4人,在学生自愿报名的基础上产生。

(3)活动开展所需材料

1、调查材料:普通相机、数码相机、录音机、调查问卷、摄像机、学校证明

2、新闻媒体:在调查过程中,希望借助儋州电视台和儋州通讯等新闻媒体进行宣传,扩大调查影响

四、活动的主要内容和具体实施步骤

(一)活动的主要内容

1、科学调查

①调查儋州各乡镇旱情,特别是农业受旱情况和居民用水情况是这次调查的重点。

②调查这次旱情发生的原因

③调查儋州市农业生产、农民的生活受到旱情的影响情况

④调查儋州市90万市民的生活受干旱的影响情况

⑤调查当前儋州市抗旱形势

2、自我反思

①撰写活动感受

②小组讨论与交流活动的感受

③各小组撰写调查报告

3、宣传教育

①展示调查成果,进一步扩大活动的影响

②向社会发出倡议书、

(二)活动的具体实施步骤

1、3月4日 制定的活动方案。

2、3月4日―11日 进行动员,了解学生的情况,征求师生的意见;确定参加“儋州市旱情调查”研究性学习活动小组成员,并进行分组。

3、3月12日-18日 召开动员大会,分别举行社会调查、科学性实践活动、科学(理性)地看待干旱等三场辅导讲座,做好调查所需材料的准备工作和安全教育工作。如:调查所需的问卷、相机等;召开临行前学生动员大会,落实各项准备工作。

6、3月20日 集中活动阶段

指导教师带领学生深入到松涛水库库区、新州镇和木棠镇调查,请当地的相关领导介绍当时的旱情及当地面对旱情所采取的措施。

7、3月21日 分组活动阶段

学生以小组为单位深入到儋州市各乡镇、农村及松涛水利工程管理局、市政府相关部门,请当地(相关部门)的相关领导介绍当时的旱情及当地对旱情所采取的措施。

8、3月22日―26日整理统计调查数据,组织材料,教师指导学生撰写调查报告和活动个人感想。

9、3月27日-3月29日 展示调查成果,进一步扩大活动的影响

10、3月30日 向社会提出倡议、向学校、政府提出建议

“儋州市旱情调查”

研究性学习活动小组

二00五年三月四日

附件2:

调 查 问 卷(一)

您好,谢谢你在百忙中能接受我们的调查,感谢你对我们工作的支持。

下面有9道问题,请你据实回答。

1、你认为旱情对你的生活和生产有影响吗?

A、影响很大 B、没有 C、有一点 D、说不清

2、在日常生活中,你感到用水有困难吗?

A、困难 B、不困难 C、 说不清

3、你认为应该节约生活用水吗?

A、应该 B、 不应该

4、你身边的人是否节约用水?

A、是 B、否 C、说不清

5、你认为本地农业生产受旱情的影响吗?

A、有影响 B、没有影响 C、说不清

6、你的日常生活受这次旱情的影响吗?

A、有影响 B、没有影响 C、说不清

7、面对旱情,你是否改种其他耐旱作物?

A、是 B、否

8、政府有否帮助你们克服旱灾保持生产?

A、有 B、没有

9、你是否采取措施来解决干旱带来的问题?

A、有 B、没有

再次感谢您对我们工作的支持。

附件3:

调 查 问 卷(二)

您好,谢谢你在百忙中能接受我们的调查,感谢您对我们工作的支持。

下面有7道问题,请您据实回答。

1、你了解儋州市目前的旱情吗?

A、很了解 B、知道一些 C、不清楚

2、你的日常生活受这次旱情的影响吗?

A、有影响 B、没有影响 C、说不清

3、在日常生活中,你感到用水有困难吗?

A、很困难 B、有一点困难 C、 说不清

4、你认为应该节约生活用水吗?

A、应该 B、 不应该

5、你身边的人是否节约用水?

A、是 B、否 C、不清楚

6、你认为本地农业生产受到旱情的影响吗?

A、有影响 B、没有影响 C、说不清

7、你会为受旱群众伸出援助之手吗?

A、会 B、不会 C、说不清

再次感谢您对我们工作的支持!

附件4:

问卷调查统计结果

一、调查小组共向所到乡镇居民(村民)发放调查问卷(一)共1700份,回收1460份,结果统计如下:

1、 你认为旱情对你的生活和生产有影响吗?

93%认为有影响;2%认为没有影响;5%说不清

2、 日常生活中,你感到用水有困难吗?

11%认为不困难;22%认为有一点困难;65%认为很困难

3、 你认为应该节约生活用水吗?

90%认为应该;6%认为不应该

4、 你身边的人是否节约用水?

70%认为身边的人在节约用水;有10%认为身边的人没有节约用水;16%说不清

5、 你认为本地农业生产受旱情的影响吗?

88%认为有影响;2%认为没有影响;5%说不清

6、 你的日常生活受这次旱情的影响吗?

77%认为有影响;7%认为没有影响;11%说不清

7、 面对旱情,你是否改种耐旱作物?

48%改种;47%没有改种

8、政府有否帮助你们克服旱灾保持生产?

25%认为政府帮助他们;62%认为政府没有帮助他们

9、你是否采取措施来解决干旱带来的问题?

38%已采取措施;56%没有采取措施

二、调查小组共向所到市政府所在地那大镇市民发放调查问卷(二)共800份,回收632份,结果如下:

1、 你了解儋州市目前的旱情吗?

23.42%很了解;57.75%知道一些;17.25%不清楚

2、 你的日常生活受这次旱情的影响吗?

41.6%有影响;39.56%;没有影响;15.19%说不清

3、 在日常生活中,你感到用水有困难吗?

23.58%有困难;60.44%一般;15.03% 说不清

4、 你认为应该节约生活用水吗?

93.19%应该;3.6%不应该

5、 你身边的人是否节约用水?

52.69%是;19.30否;20.09%不清楚

6、 你认为本地农业生产受到旱情的影响吗?

73.89%有影响;5.5%没有影响;18.67%说不清

7、 你会为受旱群众伸出援助之手吗?

73.73%会;4.74%不会;13.29%说不清

附件5:

倡 议 书

尊敬的儋州市民:

你知道海南的旱情吗?你了解儋州的旱情吗?明天我们会无水可用吗?

目前,松涛水库只有3.3亿立方米的水能放出,水库水位每天在下降7―9厘米,如果旱情再持续两个月,松涛水库就到了死水位,到那时,松涛水库就再也放不出水!如果两个月后雨季还没出现,我们的生活将会怎样?

目前,我市受旱面积为64.25万亩,全市因干旱有10个镇40个自然村和1.0万头大牲畜出现饮水困难,如果旱情再持续下去,广大农民群众将会面临何种状况呢?

目前,我市广大干部群众正采取有效措施,奋起抗旱。但是,在我们身边,还存在种种随意浪费水的现象,宝贵的水资源被人为地污染的事仍不时发生,不少人对于缺水的危险熟视无睹。

为了我们的生活,为了广大的农民群众,为了夺取这场抗旱斗争的胜利。我们向全市所有市民提出如下倡议:

1、增强节水意识,请勿浪费每一滴水。

2、积极行动起来,改变不良的用水习惯。

3、当好节水宣传员,做好节水宣传工作。

4、伸出你的手,为灾区人民献上一份爱心。

请让我们立即行动起来,从我做起,从现在做起,众志成城,万众一心,夺取抗旱斗争的伟大胜利。

儋州市那大中学

“儋州市旱情调查”研究性学习活动小组

二0 0五年三月二十九日

附件6:《部分小组撰写的调查报告》

儋州市南丰镇旱情调查报告

儋州市那大中学儋州市旱情调查课题组:吴特丽、吴锦正、吴秋燕

指导教师:曾良 郑培生 陈美娟

一、课题背景

2004年以来,海南省遭受到50年来最严重的旱灾。作为海南省人口最多,面积最大的市(县)和全国农业百强市(县),儋州市的旱情牵动着全市人民的心。海南西部和北部工农业、居民生活用水的源头,也是海南最大的水库――松涛水库就坐落在儋州市和白沙县、琼中县交界处,海南松涛水利工程管理局就设在儋州市政府所在地――那大镇,松涛水库管理区设在儋州市南丰镇。

旱情的背后是各种深刻的科学道理,现实的背后是理性,是什么原因造成这次儋州市乃至整个海南大旱,我们希望通过调查能弄清背后的原因。同时,通过这项活动让我们深入社会,在活动中提高我们的综合素质。

二、研究目的

1、了解儋州市旱情发生的原因,为抗灾救灾提供建议。

2、提高综合活动能力

3、培养科学精神和创新能力

三、研究过程

1、准备阶段:准备调查问卷、查阅资料、做好外出活动的各种集备。

2、活动阶段: 深入到儋州市各乡镇、农村及松涛水利工程管理局、市政府相关部门,请当地(相关部门)的相关领导介绍当时的旱情及当地对旱情所采取的措施。

3、整理资料阶段:整理统计调查数据,组织材料,教师指导学生撰写调查报告。

4、展示阶段:展示活动的结果,向社会提出倡议、向学校、政府提出建议。

四、研究方法

1、网上搜集资料

根据所选课题,我们有目的地在网上搜索资料,寻找与研究有关的信息,并把网上和网情有关的资料进行整理。

2、实地调查研究

我们深入到松涛水库、新州镇、木棠镇等地的水库、农村、田地进行实地考察,掌握第一手资料,并拍摄了近50张的像片和资料。

3、查阅图书资料

我们在市图书馆和学校图书馆查阅有关儋州市气象资料,了解相关地理、社会科学等的资料,进一步丰富我们的主题内容。

4、问卷调查

我们小组到南丰镇向居民和村民发放调查问卷150份,收回142份,并进行统计。

5、访谈调查法

在活动的过程中,我们到松涛库区和南丰镇采访相关领导及相关技术人员,了解松涛水库及南丰镇的受旱情况及相关原因。

五、调查结果

1、南丰镇耕地受旱情况

因无水播种的面积有8667亩。其中水道为4785亩,瓜菜为815亩,花生为352亩,甘蔗为2100亩,橡胶为385亩。

重旱的土地面积为6982亩。其中瓜菜为330亩,花生为22亩,甘蔗为3120亩,水果为3120亩,番薯为75亩,橡胶为315亩。因旱情严重无法播种。

旱情较轻的土地面积为5449亩。其中瓜菜450亩,花生为46亩,甘蔗为115亩,水果为4071亩,番薯为352亩,橡胶为415亩。

2、旱情因干旱人畜饮水困难的村委会有:⑴尖岭;⑵头佑;⑶陶江;⑷六门;⑸武教;⑹油麻;⑺南丰;⑻居委;⑼苗族;⑽油文;⑾新村;⑿农场。其中:

⑴尖岭有3个自然村,210户人口,共1300人,45头性畜因干旱饮水困难;

⑵陶口有14个自然村,183户人口,共1100人,65头性畜因干旱饮水困难;

⑶武教有11个自然村,78户人口,共467人,56头性畜因干旱饮水困难;

⑷油麻有7个自然村,52户人口,共316人,63头性畜因干旱饮水困难;

⑸居委有5个自然村,350户人口,共2101人,15头性畜因干旱饮水困难;

⑹苗族有7个自然村,71户人口,共427人,47头性畜因干旱饮水困难;

⑺油文有4个自然村,16户人口,65人,35头性畜因干旱饮水困难;

⑻新村有4个自然村,29户人口,123人,31头性畜因干旱饮水困难。

3、南丰镇地区内水库,山塘降至死水位的主要有:⑴尖岭山塘;⑵南荣山塘;⑶白马山塘;⑷武教山塘;⑸马荷山塘;⑹后岭山塘。

4、旱情对各个村造成的影响有:⑴可以耕作的土地面积大大减少,为今年的收成带来了困难,⑵基本口粮已难以维持农民的需求;农民的饮水问题越来越严重,难度越来越大;⑶使农民的农作物的损失增大。

5、地方政府,村委会及农民自己一起采取的抗旱措施有;⑴投入抗旱人数为4872人;⑵机动抗旱设备161台抽水机;⑶车辆运水15辆;镇府拨款1。6万元;⑷群众自筹3万元;⑸抗旱用油3。7吨;⑹抗旱灌溉面积1576亩;⑺临时解决饮水困难2000人,性畜的饮水困难有162头;⑻抗旱井5口。

6、南丰镇居民解决用水问题的主要途径有:⑴打井,修水利;⑵到水库去拉水;⑶到镇里买水(其中有5元一小车,15元一大车)。例如:我们到过的红苓村,打水的人们排着长队从早上一直打到晚上,甚至用15元买一车水(拖拉机)。

六、南丰镇周边的各村发生旱灾的原因探讨

1、客观原因

(1)全省降水偏少是导致这次旱灾发生的直接原因

海南岛属热带海洋性季风气候,旱季、雨季分明。每年冬春时节即当年11月至次年4月,为干旱期,降水少。而每年的夏秋时节即5月至10月,则是琼岛的多雨时节,降水多,降雨量约占全年总量的80%,这是琼岛气候的一大特点。但就去年而言,在传统的干旱期过后,进入雨季的琼岛降水仍然偏少,并出现了阶段性夏旱和持续性秋旱。2004年以来,儋州市各地高温少雨,1―10月份,全市平均降雨量仅为1077.7毫米,比正常年份平均少418.4毫米, 2004年11月―2005年4月,降雨偏少130-190毫米。2004年是1957年以来年降雨量最少的一年。

(2)厄尔尼诺现象

根据以往50年的数据统计,出现厄尔尼诺年时,气温一般偏高,冬天比较暖和。据介绍,2004年也是我国自1961年以来第4个气温偏高年。海南省大部分地区偏高0.7摄氏度,部分地区偏高1摄氏以上。儋州市2004年11月―2005年4月,天气偏暖,气温偏高。气温偏高加快水分蒸发,对加剧琼岛旱情尤如“火上浇油”,这是导致这次旱灾发生、持续的又一原因。

(3)55年一遇“空台年”

2004年海南岛无热带气旋登陆或影响,海南省遭遇55年来的第一个“空台年”。 2004年台风登陆地点集中于广东珠江口以东到浙江杭州湾一带,而没有进入海南岛。由于没有热带气旋影响,海南岛大范围的强降水过程明显偏少,绝大部分地区年总降水量不同程度偏少,各地降雨量的时空分布不均匀更为明显,这是导致干旱发生的又一原因。

(1)恶性开发

海南是农业大省,冬季瓜菜、果树种植等具有得天独厚的自然条件,南丰镇内的松涛水库库区由于水源充足,不少果农、开发商都盯上了海南的水库,他们毁掉库区周边的天然林种植芒果、龙眼、香蕉等经济作物,活动小组在松涛水库周边考察时发现大量存在此类现象。

(2)不合理的用水习惯

在儋州的许多农村,还存在“漫灌”的习惯,就算在干旱严重的今天,在水库库区上游,农田灌溉现象还存在。在生活用水方面,由于大多数的市民缺乏节约用水的意识,浪费水、污染水的现象十分严重,这些因素都人为地加剧了旱情。

(3)政府对于干旱不够重视,抗旱资金不足,许多措施没有真正实施,水利局供水不足。

七、几条建议:

1、学校要利用各种手段加强对学生的宣传,提高学生的节水意识。同学们应立即行为起来,从我做起,从现在做起,节约身边的每一滴水。

2、政府相关部门应未雨绸缪,建立防旱预案,做好旱灾持续下去的各种准备工作,形成抗旱救灾的常规机制。

3、社会、政府要帮助受灾地区农民恢复生产,抗灾自救。

第一、政府在财政上要努力为受灾严重的灾区提供更多的抗灾资金和设备。

第二、社会各界要发起一场为灾区人民捐款的筹款活动,多方筹集抗灾资金。

第三、政府、社会要帮助灾区人民解决生产和生活问题,一方面要千方百计地帮助受灾群众恢复生产,解决他们的基本生活问题;另一方面,要通过劳务输出等方式解决受旱农民工的生活来源问题。

4、要从长远上保护好我们的水资源,做好松涛水库等水利设施周边环境的整治工作和长远规划。

“旱情调查小组”学生分组名单

那大组:吕珊燕 黄 颖 余雅静 陈 樱 潘 辉 陈金龙

排浦组:刘 清 符启俊 黄万妍 王永慧

长坡组:陈美嵩 李金利 李 玮 汪秀秀 韦海娟

中和组:林颖全 何烈方 鲍系承 万明策 李振扬

木棠组:李汉东 刘钟锴 曾世盛 李巨辉

马井组:曾凡鳞 江 玲 羊艳梅 韩壮恒

东成组:吴红丽 郑文浩 符光凯 曾允茹

光村组:陈红山 刘井女 陈卓东 张再经 陈桂玫 徐伟姣

南丰组:吴特丽 吴秋燕(4) 吴锦正

新州组:林宇兴 肖定和 王广川 吴雄伟

大成组:吴万妃 黎贵福(5) 张丽君

峨蔓组:杜雪兰(16) 王小支(15) 符春红(15) 陈初玲(8)

三都组:张鹏海 羊家聪 张政达 刘明芬 陈玉女

海头组:吴广兵 符凤兰 梁彩梅 吕瑞婷

王五组:吴永宁 麦亮文 吴剑锋

雅星组:符雅丽(6) 吴子良(6) 郭俊鼎(6) 张丽婷(6)

兰洋组:林 莹(8) 谢春榴(8) 张明嘉 刘壁如

注:共分17个小组,总计72人。括号内为除1、2班外的学生的班别,姓名下划线者为各组组长。

PHP正则表达式

一、介绍

正则表达式,大家在开发中应该是经常用到,现在很多开发语言都有正则表达式的应用,比如javascript,java,.net,php等等,我今天就把我对正则表达式的理解跟大家唠唠,不当之处,请多多指教!

需要知道的术语——下面的术语你知道多少?

Δ  定界符
Δ  字符域
Δ  修饰符
Δ  限定符
Δ  脱字符
Δ  通配符(正向预查,反向预查)
Δ  反向引用
Δ  惰性匹配
Δ  注释
Δ  零字符宽

  • 定位
    我们什么时候使用正则表达式呢?不是所有的字符操作都用正则就好了,php在某些方面用正则反而影响效率。当我们遇到复杂文本数据的解析时候,用正则是比较好的选择。

  • 优点 正则表达式在处理复杂字符操作的时候,可以提高工作效率,也在一定程度节省你的代码量。

  • 缺点 我们在使用正则表达式的时候,复杂的正则表达式会加大代码的复杂度,让人很难理解。所以我们有的时候需要在正则表达式内部添加注释。

二、通用模式

¤ 定界符
通常使用 “/”做为定界符开始和结束,也可以使用”#”。
什么时候使用”#”呢?一般是在你的字符串中有很多”/”字符的时候,因为正则的时候这种字符需要转义,比如uri。

使用”/”定界符的代码如下.

$regex = '/^http:\/\/([\w.]+)\/([\w]+)\/([\w]+)\.html$/i';
$str = 'http://www.youku.com/show_page/id_ABCDEFG.html';
$matches = array();
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

preg_match中的$matches[0]将包含与整个模式匹配的字符串。
使用”#”定界符的代码如下.这个时候对”/”就不转义!

$regex = '#^http://([\w.]+)/([\w]+)/([\w]+)\.html$#i';
$str = 'http://www.youku.com/show_page/id_ABCDEFG.html';
$matches = array();
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

¤ 修饰符:用于改变正则表达式的行为。

我们看到的('/^http:\/\/([\w.]+)\/([\w]+)\/([\w]+)\.html/**i**') 中的最后一个”i”就是修饰符,表示忽略大小写,还有一个我们经常用到的是”x”表示忽略空格。
贡献代码:

$regex = '/HELLO/';
$str = 'hello word';
$matches = array();
if(preg_match($regex, $str, $matches)){
echo 'No i:Valid Successful!',"\n";
}
if(preg_match($regex.'i', $str, $matches)){
echo 'YES i:Valid Successful!',"\n";
}

¤ 字符域:[\w]用方括号扩起来的部分就是字符域。
¤ 限定符:如[\w]{3,5}或者[\w]*或者[\w]+这些[\w]后面的符号都表示限定符。现介绍具体意义。

{3,5}表示3到5个字符。{3,}超过3个字符,{,5}最多5个,{3}三个字符。
* 表示0到多个
+ 表示1到多个
¤ 脱字符号(^:)
(1)放在字符域(如:[^\w])中表示否定(不包括的意思)——“反向选择”
(2)放在表达式之前,表示以当前这个字符开始。(/^n/i,表示以n开头)
注意,我们经常管”"叫”跳脱字符”。用于转义一些特殊符号,如”.”,”/”

三、通配符(lookarounds)

断言某些字符串中某些字符的存在与否!
格式:
正向预查:(?=) 相对应的 (?!)表示否定意思
反向预查:(?<=) 相对应的 (?前后紧跟字符

$regex = '/(?<=c)d(?=e)/';  /* d 前面紧跟c, d 后面紧跟e*/
$str = 'abcdefgk';
$matches = array();
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

否定意义:

$regex = '/(?
$str = 'abcdefgk';
$matches = array();
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

字符宽度:零
验证零字符代码

$regex = '/HE(?=L)LO/i';
$str = 'HELLO';
$matches = array();

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";
打印不出结果!

$regex = '/HE(?=L)LLO/i';
$str = 'HELLO';
$matches = array();

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

能打印出结果!

说明:(?=L)意思是HE后面紧跟一个L字符。但是(?=L)本身不占字符,要与(L)区分,(L)本身占一个字符。

**四、捕获数据 **

没有指明类型而进行的分组,将会被获取,供以后使用。

指明类型指的是通配符。所以只有圆括号起始位置没有问号的才能被捕捉。 在同一个表达式内的引用叫做反向引用。 调用格式: \编号(如\1)。

$regex = '/^(Chuanshanjia)[\w\s!]+\1$/';
$str = 'Chuanshanjia thank Chuanshanjia';
$matches = array();

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

避免捕获数据

格式:(?:pattern)

优点:将使有效反向引用数量保持在最小,代码更加、清楚。

命名捕获组

格式:(?P<组名>) 调用方式 (?P=组名)

arrray(3) {

[0] =>
string(28) “chuanshanjia Is chuanshanjia”
[“author”] =>
string(12) “chuanshanjia”
[1] =>
string(12) “chuanshanjia"
}

五、惰性匹配

(记住:会进行两部操作,请看下面的原理部分) 格式:限定符? 原理:”?”:如果前面有限定符,会使用最小的数据。如“*”会取0个,而“+”会取1个,如过是{3,5}会取3个。 先看下面的两个代码: 代码1

$regex = '/heL*/i';
$str = 'heLLLLLLLLLLLLLLLL';
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}

echo "\n";
结果1:
array(1) {
[0] =>
string(8) "heLLLLLLLLLLLLLLLL"
}

代码2

$regex = '/heL*?/i';
$str = 'heLLLLLLLLLLLLLLLL';

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";
结果2:
array(1) {
[0] =>
string(2) "he"
}

代码3,使用“+”

$regex = '/heL+?/i';

$str = 'heLLLLLLLLLLLLLLLL';
if(preg_match($regex, $str, $matches)){
var_dump($matches);
}
echo "\n";

结果3
array(1) {

[0] =
string(3) "heL"
}

代码4,使用{3,5}

$regex = '/heL{3,10}?/i';
$str = 'heLLLLLLLLLLLLLLLL';

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}

echo "\n";
结果4
array(1) {
[0] =>
string(5) "heLLL"
}

六、正则表达式的注释

格式:(?# 注释内容) 用途:主要用于复杂的注释

贡献代码:是一个用于连接MYSQL数据库的正则表达式

$regex = '/
^host=(?<!\.)([\d.]+)(?!\.) (?#主机地址)
\|
([\w!@#$%^&*()_+\-]+) (?#用户名)
\|
([\w!@#$%^&*()_+\-]+) (?#密码)
(?!\|)$/ix';

$str = 'host=192.168.10.221|root|123456';
$matches = array();

if(preg_match($regex, $str, $matches)){
var_dump($matches);
}

echo "\n";

七、特殊字符

特殊字符      解释
*                   0到多次
+                  1到多次还可以写成{1,}
?                  0或1次
.                   匹配除换行符外的所有单个的字符
\w                [a-zA-Z0-9_]
\s                 空白字符(空格,换行符,回车符)[\t\n\r]
\d                 [0-9]

本篇文章为转载文章,因为觉得写得的确是好,二次分享给大家,版权归原作者“川山甲”所有。
原文链接:http://www.cnblogs.com/baochuan/archive/2012/03/12/2391135.html

Openfire Plugin开发指南(一)

一、简介

Openfire plugin使得Openfire自身的功能得到了很好的扩展和增强。这篇文档旨在引导开发者如何进行Openfire plugin的开发。

二、plugin的结构

plugin的源码放置在Openfire源码根目录的plugins目录下。当plugin以jar包或者war包的形式部署了之后,它将会自动解压成原始的文件夹层级结构,类似的plugin目录结构如下:

Plugin Structure

myplugin/	
|- plugin.xml <- Plugin定义文件	
|- readme.html <- 可选,插件说明文件,直接呈现给最终的用户阅读	
|- changelog.html <- 可选,插件变更日志文件,直接呈现给最终的用户阅读	
|- logo_small.gif <- 可选,插件的小号Logo图片(16x16),只支持.gif和.png格式	
|- logo_large.gif <- 可选,插件的大号Logo图片(32x32),只支持.gif和.png格式	
|- classes/ <- 插件所需要的资源文件 (类似地,例如插件的属性配置文件)	
|- database/ <- 可选,插件的数据库模型文件,可内置包含数据库版本升级的模型文件	
|- i18n/ <- 可选,插件的i18n多语言支持文件	
|- lib/ <- 插件所依赖的库(即JAR包)	
|- web <- 可选,插件的控制台页面相关文件	
|- WEB-INF/
|- web.xml <- 生成的web.xml,包含了等待编译的jsp配置
|- web-custom.xml <- 可选,用户自定义的web.xml,包含用户的servlets配置
|- images/ <- 可选,插件的控制台页面所需要的图片资源文件,只支持.gif与.png格式

plugin中的web目录提供的是插件在管理员控制台中的管理功能,下面会撰写更为详细的相关内容。 其中,plugin.xml定义了plugin主要的类,下面是一个例子:

<!-- Sample plugin.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<plugin>
<!-- Main plugin class -->
<class>org.example.ExamplePlugin</class>

<!-- Plugin meta-data -->
<name>Example Plugin</name>
<description>This is an example plugin.</description>
<author>Jive Software</author>

<version>1.0</version>
<date>07/01/2006</date>
<url>http://www.igniterealtime. org/projects/openfire/plugins.jsp</url>
<minServerVersion>3.0.0</minServerVersion>
<licenseType>gpl</licenseType>

<!-- Admin console entries -->
<adminconsole>
<!-- More on this below -->
</adminconsole>
</plugin>

以下的这一些元数据字段可以在plugin.xml被设置:

  • name:plugin的名称

  • description:plugin的描述

  • author:plugin的作者

  • version:plugin的版本号

  • date:发布plugin的日期,格式必须是MM/dd/yyyy,类似地“07/01/2006”

  • url:关于plugin更详尽信息的超链接

  • minServerVersion:plugin所支持的Openfire最低版本号,假如版本号高于当前运行的Openfire版本,plugin将不会被启动

  • databaseKey:假如plugin需要自己的数据库表,那么databaseKey节点必须要设置一个schema name(通常地,schema name设置成与plugin名称相同)。对于每一类Openfire所支持的数据库,数据库的schema文件都必须放置在plugin下的database目录下。例如:plugin名称为“foo”,那么该plugin的schema文件命名应该类似与“foo_mysql.sql”,“foo_oracle.sql”等。我们推荐把数据库表的命名都增加Openfire的前缀“of”,避免与同一数据库中的其他应用的数据库表结构的命名相冲突。在schema脚本文件中,应该在ofVersion表中增加一条记录,使得Openfire可以跟踪plugin的版本信息,类似地:INSERT INTO ofVersion (name, version) VALUES (‘foo’, 0);

  • databaseVersion:数据库schema版本号(PS:当一个数据库被定义之时就具有的)。新的plugin在定义其数据库之时,数据库版本号将从0开始计数。假使在后面版本的plugin需要升级其数据库结构,那么升级的schema脚本文件需要放置到目录database/upgrade的每一个升级版本的文件夹下,目录结构类似地:database/upgrade/1和database/upgrade/2可以包含文件“foo_mysql.sql”与“foo_oracle.sql”,文件内包含了每一个版本的数据库结构的相关变更项目。在每一项数据库发生升级之时,都需要把ofVersion中的plugin版本记录更新,类似地:UPDATE ofVersion set version=1 where name=’foo’;

  • parentPlugin:plugin的父插件的名称()。当plugin拥有一个父插件(parent plugin),那么当前plugin将使用它父插件的类加载器,而不是自己新增一个类加载器。这一机制使得插件之间工作起来变得更加紧密。一个作为子节点的插件,如果没有其父插件的存在,它将无法独立工作。

  • licenseType:定义着当前插件的官方所遵循的协议。有效的数值类似地:(1)“commercial”:插件的发布与传衍遵循着商业的协议

  • “gpl”:the GNU Public License(GPL)

  • “apache”:the Apache License

  • “internal”:限制在内部使用,并且不准备二次发布传衍

  • “other”:plugin所遵循的协议不在所列的协议范围之内,那么便可以使用other项,在其中需要撰写清楚相关协议的细节假设plugin没有指定任何协议,那么插件默认指定为other项。

在plugin中还有一些额外的文件,给用户提供了更多的信息(这些文件都放置在了plugin的根目录下):

  • readme.html:可选,插件的说明文件,呈现给最终用户阅读

  • changelog.html:可选,插件的变更文件,呈现给最终用户阅读

  • logo_small.png:可选,插件的logo小图片,仅限于.png和.gif格式

  • logo_large.png:可选,插件的logo大图片,仅限于.png和.gif格式

  • plugin的类文件必须实现Openfire API中所定义的Plugin接口,自然地,类文件有默认的一个无参数的构造器。plugin的类文件实现了Plugin接口中的initializing和destroying接口。

// plugin的实现例子
package org.example;
import org.jivesoftware.openfire.container.Plugin;
import org.jivesoftware.openfire.container.PluginManager;
import java.io.File;
/**
* A sample plugin for Openfire.
*/
public class ExamplePlugin implements Plugin {
public void initializePlugin(PluginManager manager, File pluginDirectory) {
// Your code goes here
}

public void destroyPlugin() {
// Your code goes here
}
}

常用的plugin最佳实践

在选择一个插件的包名之时,我们推荐可以选择一些具备唯一性的标识,类似地,可以选用你的自定义字符串或者是组织名称来尽可能地避免包或者类命名的冲突。

例如,假设每个人使用的包名都是org.example.PluginName,就算是PluginName是不相同的,但是你依然会遇到这里或那里的类名冲突,这不是玩笑,在集群部署环境之下,该问题尤其突出。

2012的第一批文字

  1. 今年回村子过年的年轻人多了,不过小孩子们的鞭炮声少了。 Screen Shot 2015-04-12 at 12.51.12 PM

  2. X叔说:“春节前夕到城里办事,半路问路,竟然一老头子给指说了”文化东路”,话说城里只有文化南路,文化北路和文化中路,哪里有什么文化东路的嘞,这世界真是,连问路都需要买路钱。 Screen Shot 2015-04-12 at 12.51.25 PM

  3. X爷爷一大早就去银行将到期的银行利息取出来,回来对他爱人和亲爱的儿子报喜说道:“今天赚了,一年的利息只有500左右,银行给了900+,真是走大运啦,哈哈。” Screen Shot 2015-04-12 at 12.51.32 PM

  4. X女孩是念大学放假回家过春节的村里出了名的读书人,一天,他到一个念中职学校的同伴家串门,同伴妈妈X姨说道:“哟,X赚大钱回来啦,每个月赚上万,今年回来有没有买什么大货礼品给父母呀,好找一个老公嘞,一月赚过万的高材生,以后就不用愁嘞,真是厉害噢。”话说X女孩儿今年刚毕业,月薪也不过3K出头的小本科生一位。 Screen Shot 2015-04-12 at 12.51.41 PM

  5. “记得上次还打电话回家里来,试探着问我有没钱咯,老公赌债过十万我有什么办法,我才不帮她还钱。”一家公在年饭的话语中谈到他出嫁在外的女儿。 Screen Shot 2015-04-12 at 12.51.50 PM

  1. “凭什么优秀员工要给她们俩,一个大家都知道为人实在是不怎么样的人,一个是短期的实习生,就因为他们一个在年度评语中给了评委主管好言好语,一个常常帮评委主管熨衣服取行李拿包裹吗?”X上班女抱怨道。 Screen Shot 2015-04-12 at 12.51.57 PM

  2. 客运售票找零是一种生活,看管车辆建议客人是一种生活,几人成行外出聚餐闲谈是一种生活,三轮摩托拉客高价是一种生活,不知觉感性发表几段文字是一种生活。生活多样化发展,世界缤纷式演变。 Screen Shot 2015-04-12 at 12.52.06 PM

  3. 正值花季的X男孩儿载着自己曾经喜欢的女生一块儿摩托车兜风,不时谈谈一年来的收获与失去,说道其实自己还留着她的位置。 Screen Shot 2015-04-12 at 12.52.12 PM

  4. … …

感动,只因为一个女孩

上海的冬天并不冷,就是风刮的,很大。

难得四年的大学生活,难得的人生见与遇,更难得的便是感动。

她,也许出现在童话、历史、教科书,不经雕琢便已是卓就源人。每个人都有一份童稚年少纯真的曾经,但也许都未能经住大大世界的时光年轮,童稚还未能真正发掘分享便匆匆踏入了一些些所谓。

“成熟”,一句便可背负事业、家庭;“长大”,也许感觉未来宽广,无必一个憧憬;“变强”,事业家庭都会质的飞跃,也许就此认为,幸福了。

当值深夜,为连接一道想念,电话线能穿越地球两边,语言能在这一空间散漫,听着带着永远,等到永远的歌曲,可能生活幸福的并不仅限这一种种,至少还缺少一份失去的稚气,曾经拥有的那一份童真。

生活无论地域,这是天降的大任,这都是取回断线风筝的坎坷旅途。家庭需要一杆支柱,更需要一个向导,榜样。永远为榜样,为梦,前进,努力,困难并非如此难以跨越,信念是梦的起始,是旅途的动力。

懵懂来自童稚,许多的无知,更多的好奇,促动着生活要翻越,梦也已经划线,不管怎样,继续就能回归原点,梦,就在三六零转弯。

一丝不倦地折叠着自己的梦帆,放飞着自己的理想 … …

__撰写于2010年冬季

大学音乐系合唱之我的感想

合唱之我的感想

——–陈金龙(10072510352)

这学期,选了合唱课,可能是因为自己从小开始就喜欢音乐,喜欢唱歌,喜欢歌唱时给我的快乐和自信的感觉。小学起,自己就因为唱歌稍微小小的出众,便被学校派与伙伴学校一块在“六.一”儿童节上PK一番。当时拿了什么奖嘛自己已经由于岁月模糊,记得不是很清楚了,但是应该是排的比较前面的奖项,呵呵。但是那是我最为快乐的小小的音乐生涯吧。

上课的时候也听老师讲过,音乐这回事儿,不是一次两次,一年两年能够体会得到它其中的奥秘和快乐所在的,音乐就是根据我们自己的生活体验,静静地去聆听身边的事,细心地去体会生活中德点点滴滴,就像我们小学学习语文课一样,要从一个词,一段话,上下文连接去体会这当中的作者的意境,从而让我们找到更多的生活的所得所获。学期刚开始的时候,一开始就给我们发下了我们都比较熟悉的合唱歌曲—-《半个月亮爬山来》这一首青海民歌,我们在练习合唱的时候,大家都是陌生的,但是当我们都张开口去细心地唱起这首歌的时候,大家的心是一起的,思绪在不大的教室中显得漫无边境。虽然是很熟悉的一首歌,但是心里总觉得这是不寻常的一次旅行。

课上的时候,觉得有几个可能是音乐系的同学吧,实在是太厉害了,特别是老师教授了《噢!苏珊娜》这首福斯特的代表作时,简直就让我们从心里面崇拜这几位同学,太震撼了,竟然把女高女低唱的这么让我们心旷神怡的,而当我们唱起男低音时,呵呵,简直就是比较噩梦型的调子,我们自己都没法忍受咯^_^。到后来,我们几个认识的课后在寝室练了几下自己的发声,后来小小合唱了一小会,呵呵,其实也不赖(⊙o⊙)哦。后来我们之后的学习的《joy to the world》和之前学过的《美丽的梦神》在课上的时候都比较胆大的唱了起来。有好几次,呵呵,唱的把坐在我们前面的几个女生都“雷”到了,哈哈。不过当时大家都很开心,这其实也就是我们应该得到的。合唱就是让我们的生活得到快乐,让我们的心灵得以陶冶和升华。

回想到自己以前对合唱的简单理解,自谭老师学期后面所上的合唱知识以后,自己觉得合唱其实真的是一门很好的很贴近生活的易于人们感受的一门艺术。更而甚之,其实它更能让我们体会到幸福的含义。

记得有一节课老师特地播放了圣地亚哥大学的新年音乐会的现场的片子,我们很是惊讶,竟然圣地亚哥大学竟有8成以上的人参与了合唱及相关的音乐的训练,并非学校的要求,而是完全出于自己的兴趣所在。无论院系,无论年龄,在每年的圣地亚哥大学的新年音乐会的演出现场你都可以找的到。视频中有一位小提琴手,仅看他的小提琴水平,不敢想象他是该大学中最为出色的,并且他不是出身于音乐系,而是化学系,还是个将要毕业了的一位被保送的优秀毕业生。更让自己出乎意料的是,圣地亚哥大学还与我们学校(ECNU)有着20几年的友好关系。所以自己觉得,自己以后有机会一定要去一趟圣地亚哥大学,去聆听他们的震撼的新年音乐会。

后面还有自己比较喜欢的一个合唱团在老师的讲课中出现了—–维也纳童声合唱团。 《天使之音》音乐会,他们比较经典的一场音乐会,老师也播放了,当时我们的确被深深地吸引住了。那一张张可爱的笑脸,一次次的天使般的声音的出现,每一次都使我觉得,世界上,没什么争吵是值得的,没什么困难是不可跨越的,没什么矛盾是不可解决的,没什么不快乐的事情是值得发声的。快乐是我们的权利,是我们的专利,是我们应该要去发现,要去寻觅和挖掘的。

最后老师的一番对课程的总结之语,让我们不禁觉得时间过的太快,合唱课这么快就要结束了,心里不禁对老师很是不舍得,但是又难以言语,发现我左前面的一个女生眼睛红了,我也不禁…但还是忍住了,毕竟自己是个男孩子。只是想从心里对老师说一声:”谢谢谭老师!”自己前面学期选的公选课都是为了多数人认为的”混学分”的思想去选的,但是这次选合唱,自己抱以很大的希望,能够在自己的兴趣方向上有所收获,现在觉得,我的确得到了很多,不仅是知识,更是一份难以忘怀的记忆。谢谢谭老师,谢谢。

__撰写于2009年夏季

盎然间,出发旗舰

盎然间,出发旗舰

无意中,一片丛林,鸟声鸣绝,绿叶飘尽,盎然的林间小路,冒然间,一束昏昏幽幽的,无形四向的光线,撒在自己的已颇有些许苍老的脸颊,感觉并非自然的温和。大自然,有属于它自己的生活方式。它,万物芥蓝,暝其一线也抵现实中的依言一句。

无尽的伪装,无尽的忧郁,在它的怀抱里,尽仅为一丝一线。为了生命中将为曾经的荣誉,挥出的是自己仅有的雾里探花的能量。也许,这之间,有许多的童乐、幸福,渐渐为自己所驰去。

其实,无谓是名就,还是千金,尽不如一份纯真。偶尔,身边可以有个人可以给自己撒撒娇;感到思乡,或是觉得寂寞的时候,自己可以有所依靠的有人聆听自己心中的感情;假日,甚至自己年间的生日,可以有个自己至亲至爱的人,给自己送句并不是很为奢华,但自己觉得分量很重的愉快,那是,你就是这个世界最最最幸福的。自己平凡,而正因为身边拥有可以珍惜的,所以,你将最不平凡。

—至此,盎然间,出发旗舰,ta太辛苦了,希望可以工作顺利,每天开心ing…:-)

Screen Shot 2015-04-12 at 12.36.56 PM

对自己的生命负责,也许就是如此Simple

其实,很久以前,自己的心中就有一种很强烈的冲动,希望暑假军训的到来,那久违的军营式训练的感觉,实在是让人留恋往返。

军训,可以让你改变喜欢拖延时间的坏习惯,可以让你行走的姿势更加正规化,更可以训练你的毅力恒力。我们大学的军训,在我们看来,也许是我们人生中最后一次亲身体验军营生活的最后一次机会了。

每天,6:30,必须让自己的闹铃,甚至是自己的心里闹铃响起来,逼迫自己强力起床。也许,暑假刚过了一个晚睡晚起的“修身”习惯,但是得清楚,那不是你健康体魄所需要的那种元素,所以,这也是我们改去我们很多自身不在意间的一些不良习惯的机会。6:30,或是更早地起床你会感觉到一种成功感—–“我今天竟然可以起得这般early,厉害……”

也不知道是不是有的人有没看过《假如再给我三天光明》这本书,我是没仔细看过的。但是据我所知,书中说的是凯伦想象中的属于自己仅有3天光明时的生活方式。我们不能要求我们做得像她如此出色,也不能要求人人都是发明家爱因斯坦之类的“异型”人,这的确是可观而不可求的。不过,我们可以做到,兰迪教授说过的,我们无法改变手中所我有的牌,但是we play the hand。我们生活给予我们的所有坎坷也许比你获得的精彩和成就要多得多,但是我们不能放弃,Gates在当所有人都认为世人改变黑屏Dos界面下的操作感受的时候,他亦然创造了“蓝天白云”;当西方认为在中国创建研究院是不正确的时候,Kaifu lee说服了Gates,在亚洲最具有IT人才发掘潜力的中国,在西方持着深重怀疑态度的时候,开复用实践和成果证明了,微软亚洲研究院,是新生一代的微软力量,是将要能改变世界的IT未来的推动生产力。

我们的军训也该如此,也必须如此。每天起来,就像体验着生活中唯一的2008的每一天,因为我们的生命,有且只有一次2008,有且只有一次某月某日,我们如果能把握自己的每一天,每一天,做一件认为自己能从中感悟点东西的,能让自己一生留恋的事情,哪怕那是帮别人提提水瓶,给别人递递东西,和自己敬佩喜欢的人说说话,那都是非常非常的有意义。因为,这是属于你自己的一生中的2008年的某月某日^_^ 。

在军训过程中,也许有的人,认为训练很累,但是你试想想,教官们是如何成为我们面前的教官的。我们疲惫,我们的教官也会感同深受。但是,当我们把他交给我们的动作要领都做得较好时,也许只是一点点很小的进步,他也会觉得自己的教授有了回报,他会更加努力的进行下去,我们也可以学的更多,充实的更好。其实生活就是这个样子,有的人,也许还会是你自己,喜欢被别人发现的感觉,就是教官说的“搞特殊”。其实,搞特殊,在我看来,是无比愚蠢,我比弱智的一种做法,这其实是对你自己的生活的不付责任,同时更是对别人的不尊重。教官说过的,只有你尊重了别人,别人才会尊重你,但是如果连自己都无法克制,自己都无法尊重自己的人,如何去正确的尊重别人,这还得等未来的出现在火星上的爱因斯坦来验证。所以,军训其中,不仅是我们躯体上的训练于纠正,更是我们心灵行为举止是那个的提高与升华。我们的一点进步,其实有很多的人会关注,不仅仅是教授我们的教官,还有我们的老师们。我们可能因为自己的疲惫,没太注意到站在道旁的老师他们。每当我们开心时,他们都会露出那作家舒婷文中所指到过的“欣慰”;我们每当有了一点点的进步,他都会不由自主地微点下颚;我们每日的军训,无论是休息时,还是军训进行时,一有空间,她便会忙碌起来,叫上同学给我们搬水拿杯倒水……其实,每当靠近老师,接上话时,你就会发现,其实老师也很是担心我们——同学旷到位,没请假,伤病情况等等,她都看在眼里,这时,我们和她接上话时,我们需要的只是两个字——“聆听”。过程中,也许,就是我们老师的工作中无法诉出的烦恼,也许是老师内心工作,或是生活中的压力,这时,为学子的我们,只需倾听。那是我们无法在生活工作上帮上忙时,我们可以为老师作出的自己可以做到的。其中,这也是自己对于自己生命的一种享受,一种快乐,一种负责。

———Randy Chen

2008/08/28 凌晨1:32军训感怀

This is Randy,世界的骄傲

美国卡耐基梅隆大学,一个著名的教授名字,Randy Pausch,是我见过的,最为深刻的一位学者。我们都生活在同一个世界中,感受着同样的世界气息。但是,正如Randy说的,我们无法改变世界,或是周围的任何事物,但是我们的生活属于我们自己,就犹如自己手中握着一副牌,我们已经无法改变牌的好坏,但是,我们可以改变我们出牌的方式方法。同样,我们可以选择我们的生活方式。

生活,并没有世界表面上演示的如此复杂,我们只要找到了属于自己的生活方式以后,也许,我们就会觉得,我们的生活是多姿多彩的。Randy曾经有过很多儿时的天真梦想,但是,他都一一实现了,虽然没有当时的一致吻合,但是,他从之中体会和学到的比真实去竞争实现list出来的梦想要多得多。他演讲中把人生中的一次次困难险阻比喻成一道道砖墙,我们只要有了梦想,有了追求梦想的实力,顽强拼搏的精神,我们就一定能够实现它。他在业界,也就是VR业界所做的贡献是巨大的。身兼美国艺电顾问,卡耐基梅隆大学终身教授,迪斯尼designer等多职的它,并没有因为忙禄而让自己只是活在工作室与寝室之间,而是当他们取得一定喜人成绩的时候,有一次还将小组里的全部人都邀请到了迪斯尼乐园里痛痛快快玩了一周。

他还是一个懂得享受生活的教授。在自己生命还剩下3~6months的时候,在卡耐基梅隆大学里作了自己的最后的演讲,过程中不忘教导younths如何去实现自己的儿时梦想,不忘感恩。那天正值自己wife的生日。也许,这一生中,陪伴他wife度过的最后一个生日,也许就是那一次了,最后的演讲,最后的wife生日欢度,最后的站在卡耐基梅隆大学那自己曾经创造过开课记录的多媒体教室,最后的为自己的“学生们”教授知识……

卡耐基梅隆大学也为了纪念他,而专门为他修建一座连接gates教学楼等两座著名教学楼之间的连接桥梁并且命名为“Randy Pausch Bridge”。让我们的后人永远记住他,记住他的名字,记住他为卡耐基梅隆所作出的贡献,为世界所作出的贡献,为鼓励女子奋起学习数学理科技术等专业的激情和兴趣所作出的巨大贡献。美国艺电也为了纪念这位杰出的顾问,在卡耐基梅隆设立了“Randy Pausch奖学金”……

他留给我们的是一个个经验、教训,和去如何发现和分享快乐的生活方式。我们爱你,世界为你骄傲,我们敬爱的Randy Pausch 教授!

(此刻,心中充满了无尽的感激与感动,万语千言,无以诉出,愿尽藏于心中,以此铭记我们这位敬爱可亲的Randy Pausch教授。希望以此以铭记和纪念于2008年7月25日因病逝世的伟大的VR教授——Randy Pausch!!!)

———Randy Chen

2008/08/12 Write in Shanghai China

我身边的改革开放

我身边的改革开放

———陈金龙(10072510352)

作为一位家居海南的上海学子,今年幸逢改革开放30年的生日,心中不禁油然而生作文的冲动,希望记录下自己记忆中的海南的既往30年喜苦交加的奋起发展之路。

海南,位居中国的南端,在世人的眼中,它就像一座神秘之岛,一处绿色之城。海南1988年脱离广东管辖而独立建省,当时恰逢本人出生之时。说起婴儿时期的海南,我唯一的印象就是,自己家的背后有好多好多的橡胶树,一眼望不到头。无论天气多么炎热,在林子里也能感到丝丝凉意。冬天,当中的落叶还是家里的很有用的引火工具。自己的家里和当时的很多人家都一样,住的都是自己亲手搭建的简陋瓦房。当时,自己家便是在一个竹林子里搭起的房子,自己也就在这样的家中成长。小时候还经常和俏皮的姐姐嬉戏,偶尔也会闹得天翻地覆。当时,村子里本来是村委会大队的养猪场,但是时遇建省改革,村中的养猪场都被迫停业了。很多青年都只能过海打工。所以现在广东广西到处都有海南人留下的足迹也就理所当然了。

当时还是计划生育出台的时间,妈妈背着我不知道跑了多少路才躲过了对自己的计划生育罚款。其实很多人家也都有超生孩子,因为那种时代村子里还是流行着老传统——-“不孝有三,无后为大”的习俗。所以很多人家怀孕了,都要去医院检查,照什么B超之类的,说是为了早日发现怀孕母亲肚中的是男是女。虽然B超在很多医生都说很伤身体,但是家长为了续香火,就不管那么多了。如果是男的,皆大欢喜;但若是女的,可能会遭到被流产掉的危险。但是又是也会出现些意外状况,比如说医院检查说是男的,但是生产之日生下了女娃,那么家长为了不给自家添麻烦,可能那女婴儿会被人领养。家中如果有些家长很是迷信的话,说这是神灵所为,本是男孩,此时确变成了女孩,定是自家遭遇了不良鬼神或是鬼上身之类的。这些家长会高价找所谓的法人来做法。闹得村子里一夜不得安宁。我记忆中还记得,那些所谓法人口中的貌似念念有词,其实那种语言肯定不是人听的。估计其实他也听不懂。

一些年后,家背后的林子被人卖掉了,收购商把所有的树木都锯掉了。来了好几辆推土机。三下五除二地便在当年内在那儿挖出了几个池塘。说是提高我们乡里的经济。其实在爸爸妈妈那些老一辈的眼中,这都是些村官的馊主意,尽想着怎样让自己的腰包鼓起来。其实这也不是没道理,那几年的确没见那些个官有什么实际行动。但是池塘挖了就挖了,其实也是给了我们这些小孩子一个欢乐的园地。虽然是有人承包的,但是他也管不了我们这些他们口中的兔崽子的行动。每当周末,我们就会三五成群的分散在池塘边钓鱼啊,或是在池塘边的灌木丛里“搭房子”玩耍呀什么的。偶尔夏天时我们也会叫上几个会游泳的大哥哥他们去那边放牛,玩抛石头,还会一块到池塘里游泳什么的。说是危险,但也是在大人的陪同下咯,所以大家都只在水很浅的地方,每个都装作会游泳的能手一样,伏在水面上,其实我们都不会游泳,伏在水面上,那是因为手脚都撑在了水底的泥巴里咯。

不过最让人欣喜的时候那肯定得数开塘(放水开池塘捉鱼,一般在秋天)的时候。没到那时候,我们便会举家出动,到池塘里捞承包商捕捞过以后剩下的小鱼,其实其中也经常捉到大鱼,这也是承包商很郁闷的事情。捉回去的鱼有的煮着吃,大部分嘛,为了保质,便将它们晒干作为鱼干来保存。但是,可能是因为99年的时候,家乡里的经济出现了萧条的情况吧,池塘也便没了承包商,至今池塘便由旱塘变成了现在的有一片小橡胶林(林子还很低,估计还可以凑得上“苗子”这两字吧)。

再后来,再后来就到了我上初中之时。那时候,乡里说搞什么合作医疗所的,每家每户都要合钱来在大队上建立一个名字叫做合作医疗所的机构,就像是上海的区里医院吧,其中的制度还是颇像今天的医保制度的。但是那机构没过完春节就倒了。原因很简单,说是合作医疗,理应便宜才对,但是呢,反而比很多场部上的门诊要贵上好多。农民家庭本来年内收入就少,这时候还要人家附上那么重的医疗费用,这肯定是违反社会发展基本准则的。最近几年,这合作医疗的事宜有再一次开展起来了,但是相对于上次的有很大的改善。其中有了明确的规章制度,有了让老百姓看得懂的相应优惠政策条文,所以全省的合作医疗开展的很顺利,大多数老百姓都纷纷愿意加入这当中来。所以,这让老百姓们也有机会到大医院,正规医疗机构看病了,不然就之前的医疗费用,让很多人望而却步啊,老人们甚至愿意病死在家中也不愿意拿自己辛辛苦苦一辈子节省下来的钱就这么没了,何况,有的老人还想着要留给后代们好好生活发展呢。

还有一点,必须提起的就是免受农业税的政策了。家里的很多乡亲都非常支持温家宝,他们说温家宝是真正懂得农民的好总理。在家里时,会经常到邻居家里转转,听听老人们的一些言辞,提起温家宝总理时,他们总会用很浓重的客家口吻说出这么一句:温家宝好像周总理。

其实说点背后的故事,我家说在的村子里,大部分都是广东人,由于当时文化大革命和大跃进,搞得广东很多地方民不聊生,他们都是迫于生活而流浪到这里来的。乡里有一个很年迈的木工匠,我家里的大部分床、椅子等家具都出自他的手中,工艺让人钦佩,至他离去的那年,他还为一户人家制作了一张新娘床。小时候我们很爱到他的工房里玩,他很和蔼,很喜欢小孩子。不时还给我们讲故事。但当他每每回忆起文革时的老家时,他就会禁不住的停下手中的活,回到自己的房里,让我们自己玩了。只听老爸说过,他家文革遭遇很惨。

至于现在嘛,海南虽然还是最为落后的经济特区,但是近几年来,政策的开明,各种社会制度的完善,都让我们的周围发生了很大的变化:乡里人有人盖起了高楼;家家户户都能买得起了摩托车;家里的电器家具等也变得多种多样;有些村子里还建起了老人休闲室,周末时候,那里便能成为老人们的乐园。家中原来烧柴火的,都改上了烧起了煤气,不过煤气太贵,让很多家里为了节省,只有遇到客人来访的时候,才会用起煤气,其余还是在烧柴火。但是,其中也有让人很是心疼的事情。比如说很多人因此了丧失了乡村人本来简朴可亲的品质,反而金钱主意的观念更重了,搞得本来和和气气的乡里邻间为了土地啊等等问题反目成仇,闹得大家都不开心。就我的观点而言,我们不能丢了好的品质,好的习俗,好的传统,反而我们要更好的让它们发展下去,延续到下一代,下下一代,甚至更远。

海南在外打得就是生态省的名号,水质好,空气好,风景宜人……这也真是海南能建成旅游大省的物质基础,先天条件。很多国内外游客真是这样慕名而来的。海南也没有让他们失望,的确是名副其实。进来海南频频有高污染的企业要进驻海南,但是,海南政府没有让我们这些老百姓失望,全力禁止高污染企业进入海南。这也为保障海南更加绿色、和谐、美丽地发展下去奠定了政策保障。很多游客来到海南旅游,不仅仅是为了看美景,更是为了体验“农家乐”的乐趣。“农家乐”这样的迎客方式,的确是别出心裁,更加让专家们,特别是文学家们称赞的是,它能够给人们以更为深入的心灵方面的洗涤。 改革开放让海南焕发起了青春,让我们的生活蒸蒸日上,让老百姓们深刻地体会到了,国家对他们的关爱与重视。在这改革开放30年盛大节日道来之际,愿祖国未来更加繁荣和富强!

_撰写欲2008年夏季

《做最好的自己》——李开复 读书笔记

《做最好的自己》——李开复 读书笔记 be your personal best 事业进步是成功,给家人快乐是成功,广交朋友是成功,能对他人有所帮助是成功。成功并不遥远,不虚度此生,就是我的成功。 “做最好的自己”是通向多元化成功的必经途径。 复旦附中2004高三女生汤玫捷:“我们是野生植物,不是园林植物。每个人独特的优点就是自信的源泉。”他拒绝出书,因为她不愿意将自己的经历简化成抽象的“成才公式”。 价值观是人生的基石,是成功的前提。 态度是行动的前提,态度受到价值观的指导,态度是为人处世的基本原则。——积极;同理心(人同此心,心同此理);自信;自省;勇气;胸怀(海纳百川,有容乃大);积极和同理心求平衡;自信和自省间求平衡;勇气和胸怀之间求平衡; 行为受价值观和态度的指导,是态度在学习、生活和工作中的具体表现。——追寻理想;发现兴趣;有效执行;努力学习;人际交流;合作沟通;以勇气指导自己追寻理想;以自信培养、发现兴趣;以自省指导有效执行;以积极的态度努力学习;以同理心指导人际交流;以胸怀促进团队合作与相互沟通。 完整与均衡——用智慧选择成功 完整的原则是指要完整地理解成功同心圆的每个方面,要同时培养自己在各方面的素质和能力,以成为完整的、结合中西文化优点的人才。 均衡的原则是指在培养自己的素质和能力时,不能偏激,不能走极端化的路线,应当根据自己的目标和实际情况,在对立统一的逻辑关系里选择最佳的均衡状态。 完整和均衡并不意味着圆滑、世故和骑墙,完整和均衡也不意味着必须赏识自己的个性与激情。 只要完整和均衡地理解了成功同心圆,只要学会了智慧选择的方法,就可以成为“最好的自己”。有勇气来改变可以改变的事情,有胸怀来接受补课改变的事情,有智慧来分辨两者的不同。 成功的秘诀:成功=价值观+态度+行为 成功源于诚信的价值观! 道德践履:传统才是世界的。两千多年前,孟子讲过这样的话:居天下之广居,立天下之正位,行天下之大道。 Google公司的核心价值观是:坚决不做邪恶的事情,无论有多大的商机;专注解决用户问题,赚钱和其他问题以后再说;坚决以网络群体利益为首,无论自身利益如何;坚持“最好还不足够好”的标准,永远提升自己,寻找更好的解决方案。 消除对诚信的误解: 误解一:队企业诚信就是等于要终身依附雇主 在21世纪的而今天,不能期望一个人永远留在一个公司。在美国,一个人一生中平均有四个工作,换工作甚至到竞争对手的公司里工作都是很正常的事。无论解雇、跳槽,或者是多个应聘者竞争一个职位,又或者多个公司争取一个人的加盟,都是健康竞争机制的体现。 误解二:诚信就是犯傻 诚信绝不是不经大脑,不看对象,不分场合,把一切和盘托出。多数情况下,我们在日常生活中遇到的事情往往并不是能用非黑即白、非善即恶的极端二元对立的方式所概括的。诚信的人完全可以灵活地参与沟通,并充分运用自己的智慧和判断力解决问题。 误解三:诚信也要服从利益的需要 但是,如果真的需要一个准绳,即:除非为了生存,除非在迫不得已的情况下,我们对诚信绝不可以有半点妥协。 误解四:社会缺乏诚信,所以只能随波逐流 的确,我们所寄身的这个世界不是完美的,但正是因为社会复杂,年轻人在步入社会之前才更要把握好自己的原则。 误解五:诚信知识过时、迂腐的道德观念,先到人完全可以变通地理解 “自律性的诚信是走向成功的唯一途径,因此,理解自律性的诚信是选择诚信价值观的先决条件。” “从小,我的父母就教导我,诚信是不可妥协的原则。他们对我的品德教育是实实在在的,不讲太多的大道理,不讲太多的套话,而是用自己的言行做示范。” “当时,父亲说:‘希望你不要再让自己失望了。’这是他对我说过的最重的话,也是我会铭记终身的一句话。” “报纸头条测试法”,裁掉师兄,保持自己的诚信原则,留下了有潜力的新员工。

_撰写于2008年

24连,我们永远的骄傲

今天,一大清早的,寝室里,不到6:30,异常热闹。并非无故起喧,而是今天上午我们将迎来我们军训的最后一天,最后的冲刺——军训汇演。大家心情并不是很好,记得昨天我们的队伍被连长点名拉了出去,不是奖励,不是骄傲,而是惩罚的一种终极方式。之后,我们的同学都非常的难过,每个人都尽力找出自己的缺点,自己做得不好的地方,还有就是给给自己身边的,走步走得不是很好的,提提建议,纠正纠正,为的都是今天的终极过场汇演。

自己呢,起得很晚,但总好是没有错过我们的汇演,匆匆忙忙的往楼下奔跑,只是想看看自己的队伍,肯定会出色发挥,让我们骄傲的一面。

也许,今天早上的太阳迟迟没有出来,是为了我们的这天的重大盛会。当赶到共青场时,已经升完了国旗,等待着校长的形式地宣布。难过,自己没能为队伍做出最后的努力,由于和自己一样情况的人,我们的队伍调整了不少,几乎排头,排侧面的人都换了,整个24连,迎来的是一个全新的面孔。我们由队伍中的一员,变成了后勤的一分子。大家都希望我们能够走出我们软院的气势,争得好成绩。

说起我们的队伍,我不得不提的是我们的两个教官。男生的教官,带着一口较为浓重的广东口音,说话的声音同学们都反映听得模糊。而女生的那个呐,刘德华后裔歌神,他总是用尽全力,将声音提到最高(因为我们的队伍是全校区里最大的一个方阵),也许这是刘德华传授给他的高音秘诀吧^_^他们都有共同点,该严肃是准是严肃的,而该放松的时候,他绝对是我们的好哥们呢。开始的时候,教官中有一个,不清楚是教什么学院的了,他是之中出了名的“啦啦歌队长”,拉歌水平准是走在世界最前面的最先进水平。开始时,我们完败给他们,因为我们当时不是很能接近我们的教官,认为他是老顽不化的。但是经过一次,他突然间的夸奖我们正步练习走得好,之后,我们便无限开始喜欢上他,哥们嘛,哈哈。后来,我们的训练终于不再死气沉沉,迎来的是快乐军训的时光。这也是我三次军训中最为快乐和轻松的。我们的拉歌水平随之直线上升,击败了不少连队,更为自豪的是我们团结在一起的感觉,那是从所未有过的感觉。虽然,教官还是念着他口中的“右脚1”口号,但是,那从此变成了我们的特色的适应性。

开始走队列了!按顺序从18连开始。的却,很多队伍都是走得很好,偶尔出现些失误,但是也不足以影响大局。但是,有那么一个队伍,让人更加震撼,那就是我们的24连!口号响越苍穹。。。领奖台上,连长,校长,都已经为之震撼,主席台旁的老师和我们的后勤同志,也不由自主的鼓起掌来。“很整齐。。。规范”,我旁边的一个旁观老人对自己的爱人不禁破言。

接着就是漫长的领导们的关于精神指导之类的讲话。我们每个人的心中,都已经觉得,我们肯定能拿个大奖。但,让我们想不到的是,我们没有得一个奖,没有!没有!

“为什么,为什么如此尽力,如此出色,还是换不来我们心中的期盼,我们只想要一个奖,哪怕很小,我们只想要一个可以报答教官和辅导员的机会和方式,为什么?到底是为什么?”

心情非常复杂,当时,自己无限憧憬,全都成为了泡沫,全都成为虚无。这世界如此不公,如此难以透视。

别的队伍都派人上前领奖,我们也鼓掌,不过此时,这般的鼓掌,无限飘渺,每个人,心情都甚是沉重,我们,世界,无极的矛盾瞬间撞击,队员们的上空,已是漫天灰色。每个人,当别人都解散时,依然站立,也许这里面,有很深的不可言喻的东西,教官在我们队伍前面,开始他在我们面前的最后一次“演讲”,发现,有很多,眼圈红透,表情已是强忍的了。。。

教官要集合了。我们还是没有立即离开,注视我们亲爱敬爱的教官,此时,自己眼睛也不知怎的,涩的难受。随后,看见一些已是满脸“红晕”的女生到教官身边。。。看着大家伙的情况,自己飞快出了共青场。一滴物质,从我的脸颊滑落。。。

之后才知,刚才颁的奖项,是事先已经点好的,是军训各项的总评,而不是刚才表现的评价。

之后还听说,我们辅导员也哭了。

之后的我们,相信,一定能够继续飞翔,头顶的天空一定能再次晴空万里。

24连,我们共同的骄傲。

24~~~~~~~~~~~~~~~~~~~

——Randy Chen

2008/08/30 15:12华东师大

Testin.CN平台调研总结

1.认证要求

(1)注册Testin.CN,成为普通开发者身份。若需要更多的功能,更多的机型支持,请申请更高级别的认证,例如个人开发者、企业开发者。如果你是土豪,可以支持使用VIP支持服务。

(2)建议:对于小型用户群(3W-)的应用,个人开发者的权利已经足以满足你的需要。企业开发者拥有更多的免费机型支持和免费的服务,如果你是企业雇员,就申请企业开发者尽情享受吧。 

2.免费资格限制

(1)在使用下来一段时间,觉得各位关注两点基本就可以了。一是每天测试次数,二是可选机型数量。至于提交云端后自动化测试所花费的时间,一般来说一天就可以给出测试报告。这个时间是Testin平台排队所致,你唯一所能做的便是等待。

(2)Testin平台还提供了众多的付费深度测试服务,如果你邀请足够多的好友成为它们家用户,就可以获得一定量的点券,1点券等于1人民币,等你看完了Testin上所支持的付费价格之后,就会发现这些渠道获取的点券,其实是非常量少的。微信都挂AD了,它们家的价格在企业成本上算是便宜的了。如果你是土豪,可以友情捐赠下它们家的该项服务。

3.平台使用过程中的其他建议

    在账户管理功能中,有一项“常用联系人”的设置,这项设置是平台协作的亮点之一。可以为你的团队提供一份查看私有测试报告的权利。 

    这里要提醒一点,如非必要,请关闭你的测试报告在Testin平台的公开配置,避免应用测试结果的公网泄露。 

4.提交云测试

(1)在提交这一项事情上,作者建议充分使用Testin自己家的工具iTestin,可以自己创建场景脚本本地测试,也同时可以上传到Testin云端进行多机型测试。虽然Web站上也提供了创建兼容测试的功能,但是和iTestin是有较大区别的。Web站上的功能支持的场景比较单一,官方说的是一种叫做“自动遍历测试(3分钟)”,看,还给俺们限制了3分钟呃。 

(2)当然,不是说Web站就一无是处了,它还是很强大以及便捷的,别的不说,对于Mac和*Nux用户来说,iTestin你就是用不起来。因为目前iTestin只支持Windows平台。不过相信Testin的鸿鹄之志绝对不仅限置为盖茨家,只是世态份额的形势所迫罢了。 

(3)还有一点,你的应用如果需要登录,请在提交测试的过程中,将登录信息填写完整。所以在这之前,你需要提供一个可以给Testin君随便搞随便玩的测试帐号及其密码。 

(4)关于机型的选择,如果认证权限内许可,可以一把选完,再选好自己应用所需要支持的Android版本范围和所支持的分辨率,全量提测。如若其他原因无法使用全量的办法,建议可以根据自己应用的统计分析结果,选择统计中覆盖率超70%份额的Top机型,外加超70%份额Top分辨率,样本提测,这也不失为贫民窟过上小康幸福生活的好办法。 

(5)关于测试类型的选择,如果不使用付费服务,目测只有“标准兼容测试”和“功能测试”可供使用。标准兼容测试使用范围更普遍些。功能测试功能,如果时间人力资源允许,可以投入,其中需要在应用源代码中集成Google提供的测试框架Robotium,并且需要额外书写测试断言脚本。 

(6)再多唠叨一句吧,希望对初始入门Testin的提供一些参考。根据多次提测所给出的报告中显示得出,目前中国市场上总额覆盖7成以上份额的安卓机型,大致为三星,小米,华为,魅族,摩托罗拉,联想,酷派,中兴,HTC,LG,索尼,OPPO,TCL(包含阿尔卡特),步步高。为什么说到TCL和步步高呢,步步高在比较偏远的地方,廉价机策略在市面上也呈现着较为重要的作用。如果测试中覆盖了以上所有品牌的机型,也就不用太忧伤了,因为你已经抢占了中国7成以上的安卓手机用户。 

5.核心内容解析

    说起Testin的核心内容,自然就是它提供的独一无二的测试报告。覆盖点几近日常我们所需要关注的所有方面。作为开发者,在浩瀚的信息海洋中,自己需要根据需要筛选出自己需要关注的信息项目即可,不需要面面俱到,也不可能面面俱到,时间不允许,排期也不允许,人无完人。

    下面是作为开发者的一员,觉得应该关注的一些结果参数。

先介绍一些其中涉及到的概念。

(1)覆盖活跃用户数、影响用户数,这两项的数据并不是Testin家的,是和Umeng合作的成果,采的是Umeng家在移动统计分析领域的概况数字。 

(2)机型分辨率,在移动设备中,书写分辨率有一种不成文的规范,长宽(或者称为’宽高’也未必不可),即:分辨率中写着720X1280是手机的分辨率,矩形状短宽长高;写着1280X720的一般是宽屏的Pad设备。 

作者目前只尝试了兼容测试过程,下面介绍下兼容测试报告的内容分布。

(1)概况:简而言之,就是测试结果的概览,分别有多少成功和失败机型,测试通过率多少,饼图如何,使用Monkey性能测试之后的各项机器物理参数数值分布如何,还有发生的错误所影响用户数的估算数值,请留意,用户估算的数值的单位是万,个这个单位已经无以言表智能手机用户群之磅礴盛况。 

(2)问题报告:这个栏目很重要,特别是对于开发者的你,它是为了你而存在的。在这项报告中,你可以迅速阅读到报出异常的代码堆栈信息,提交问题响应效率。当然,前提是你的测试应用没有作代码混淆,如果做了代码混淆,你需要找出混淆的Mapping文件,定位到混淆前的具体代码部分即可。

(3)性能报告:该项内容在你作应用的性能优化之时,绝对可以称得上是手边文档案头书,它并非无所不能,但它会尽其所能给你提供你需要的性能分析结果。

(4)终端列表:与其说是终端列表,不如说是具备搜索功能的测试机型搜索功能。每一行是每一台测试终端,可以根据需要查看在该终端下的测试用例详情,过程日志及其跑屏的截图。

    前面谈及到测试报告在Testin平台的公开配置问题,这里补充一项好用的平台功能。在每一项报告的右上角,可以发现两项功能:“分享报告”和“下载报告”。

    作者比较常用的是“邮件分享”和“下载Excel报告”。 

    在提交测试中填写的表单有这么一项,建议填写报告发送人。每一项测试需要耗费1-N小时不等的宝贵时间,这项功能可以很好地通知需要知道结果的所有人。目前每一项测试的结果邮件会发送两封,第一封是在测试快结束的时候(貌似是>80%的时候)发送,还有一份是测试完全结束的时候(进度=100%)发送。

6.关于Testin提供的一项快速查看功能

    如果你足够仔细,会发现在左侧菜单栏的下方,放置了一项“快速查看”。这项功能点开后发现,天生就是为程序员而作的。Testin团队真的很用心!

    你如果只是想透过Testin了解应用具体哪些地方发生了异常,那么前面第5点中说的大部分都是额外的话,这一项“快速查看”足以满足你所需要的。 

7.其他一些你可能需要知道的

    作者发现提交测试之时选定的机型是A台,测试报告中“已通过”的机型,加上“问题机型”并不等于A台。所以关于这一点如果发现,不要惊讶,Testin已经尽力了。或许是这些机型的电源线被老鼠咬断了也说不定哦^_^

我的2014年度总结

        许久没有写文字了,这一次终于抽出空来写一些文字,纪念自己即将过去的2014年。 相对于2013年的情况,2014年依然是收获颇丰的一年。回顾刚踏出校园,进入社会的当年的自己,凭借着无由的缘分,进入了软件领域,一板一眼书写代码的岁月却是最令人回味的,很庆幸遇到了当年的一大伙导师同伴。 还是回到今天,说说2014年的经历。

(1)一项新方向的尝试

        年初学习并带领了小研发团队进行广告项目研发。从中回顾了当年在大学期间所学习到的软件工程理论,确认了当年被大学生普遍淡忘的课程在实际工作生活中仍然有着重要的作用。通过阅读精益团队管理书籍,同时在具备多年管理经验的同事的启蒙和领航下,对研发团队中使用到的一些常用的管理技巧和策略有了深入的认识。不过,对于尽有几月的管理经历,很显然,这一块还需要在往后的日子中勤加学习和讨教,经验不仅来自于学习和岁月积累,更需要丰富的实践。

(2)一项独立系统管理及其维护的成长

        虽然以往的经历已经让自己对独立维护系统已经积攒了非常熟悉的感觉,今年依然有相似的机遇。本次经历和以往的情况依然有着很大的成长。要说前面的经历积累的事对系统整体把握的有韧性,那么今年所经历的,便是在产品设计及其发展思路上做了主导和设计。自己所憧憬向往的工程师主导产品发展趋势的FB成功经验终于被自己简单地实践了一番,受到一些外界条件的情况,虽然跌跌撞撞,坎坎坷坷,但作为只有略略几年经验的小本,能有如此珍贵的机会,已属不易。过程中在产品设计上,工程资源的跨团队的协调能力上,沟通能力上都获得了不少的提高。对如何提高团队效率的感想上也有了些许自己的领悟。

(3)一项持之以恒的日常事务

        大多团队中对于cm,多数认为是属于专门团队或是专业工程师的职责,普通工程师无需了解也无时间的现象。而今年自己在年中,接手了一项帮助团队协调及管理发布任务的日常事项。外人看来,会觉得这是一项冗余的重复性的劳动,简单了说就喝apple产品机床人员无异。不过,从自己的角度看来,这是一项可以长久发展,有益团队资源统一,有益自身协调沟通能力成长,有益规范流程统一化的一项工作。一个大的团队中,应该也必须拥有自己的一个缩略版cm。山寨的,虽小,聊胜于无。自己年中许下的两项cm目标都未能抽出时间完成,这一点确确实实让自己常常心怀失落之意,希望下一年度能抽出时间将其完备。

(4)一项看似艰巨的任务

        对于安卓开发陌生的自己,临危拉上充了志愿的壮丁,一星期入门,两星期进入开发,三星期完成开发,四星期发布产品的安卓紧急项目开发日程,看着着实棘手。但最终在团队的资源支持下,顺利完成了产品的发布。非常感谢与自己一起加入该项任务的另一同事zqs,他的丰富编程经验让项目更为顺利地发展着,自己透过代码及其沟通中学习了不少,虽然还是有很多自己还不能完全理解的代码。在这个经历中,自己又遇见了当年的自己,一种拼搏冲刺的感觉,哈哈,久逢了青春。

(5)摄影技术提升

        自2013年起,自己接触了摄影,有了自己的一台单反,给自己的女神拍了片子。今年初正赶三月初春,和朋友结伴扬州一行,拍得了五亭桥,游览了何园个园,瞻仰了大明寺,盐商之富,景致之美,扬州之美,美在三月!另外还幸运地发现了一处来南京多年都未曾听闻的失落之园–颐和公馆。一座大隐隐于市的民国建筑群,一片珍贵的文艺风情,一片遗失了许久的美好。清明时节回了趟母校,依然发展很快,变化很大,特别是河西的食堂。图书馆正在整修,据说现在已经变得逼格很高,只叫校友们无限憧憬和羡慕,恨不得再重返校园,回到过去。

(6)阅读

        在书籍的阅读上,一如往年,技术书籍占多数。精益管理、CSS、mac元编程、android权威指南等等。今年实体书买的不多,倒是电子书库剧增,也读了不少好书,例如闾丘露薇的利比亚战地日记,不分东西。其中自然,肯定包含了一本自己喜爱作家的书,那便是和自己年龄相仿的嘉倩的“我/想和这个世界不一样”,文艺一下,感悟一下,能让自己的嘈杂生活简单重铸一下。很遗憾不及的事,嘉倩年中到南京作访大学城做访谈,自己当时却在加班上线,错过了这一宝贵的交流机会。希望能够未来再次遇见嘉倩,谈谈生活,沟通理想。

(7)最后一些需要说的

        今年得到了很多,当然也有自己未能珍惜实现的,逝者如斯夫,不舍昼夜。对于明年的一些展望,后续会作文一篇加以论述,简单而言就是,实现未实现的,把握当下可以把握的,创造身边美好的。

        另外,年底了,自己最为要好的朋友s新婚,祝他们俩新婚快乐,白头偕老!也祝自己所爱的所有亲朋好友们身体健康,万事如意,事业蒸蒸日上!

上海 · 华东师大 · 陆家嘴

上海 · 华东师大 · 中北校区

(上海 · 华东师大 · 中北校区 · 双子楼)

上海 · 华东师大 · 中北校区

(上海 · 华东师大 · 中北校区 · 体育场)

上海 · 陆家嘴

(上海 · 陆家嘴 · 夜景)

钟山风景 · 灵谷寺

钟山风景区·灵谷寺

(钟山风景区 · 灵谷寺 · 大仁大义)

钟山风景区·灵谷寺

(钟山风景区 · 灵谷寺 · 无极殿)

钟山风景区·灵谷寺

(钟山风景区 · 灵谷寺 · 灵谷塔)

南京 · 总统府

南京总统府

(南京 · 总统府 · 太平天国百年纪念碑)

南京总统府

 (南京 · 总统府 · 高行清粹)

软件版本发布之旅

一、版本变更轨迹图

软件版本轨迹图

二、各版本解释

  • Alpha 面向于测试人员提供的一个内部测试版本,该版本存在许多待修复缺陷

  • Beta 属于测试版本,该阶段的发生是由于在Alpha阶段发生了功能新增等需求变更

  • RC(Release Candidate) 用作发布的候选版本,该版本不会再发生功能新增,主要用于排查缺陷和异常

  • EVAL(Evaluate) 评估版,与正式发布的版本在功能上没有差别,EVAL仅提供给特定的用户群来测试使用,在该版本下,已经消除了软件大部分的不完善之处,但仍然存在未被发现的缺陷或漏洞

  • PREVIEW 预览版,类似于EVAL版,其用途与EVAL版相同,不同厂商会根据自己的习惯,将评估版命名为EVAL或者PREVIEW

  • GA(General Availability) 正式发布的版本,常用于国外的开源项目的版本发布Roadmap中

  • OEM(Original Equipment Manufacturer) 提供给计算机厂商随着计算机售卖的版本,亦称随机版。该版本不能使用于单独零售

  • RTM(Release To Manufacture) 提供给生产厂商大量压片的版本,内容与正式版相同

  • RTL(Retail) 零售版,即真正的正式版,正式面向于全体用户群售卖

上图中涉及了软件测试过程的三个阶段:α、β、λ,下面作一些简单的解释:

  • α:第一阶段,该阶段下的软件产品仅提供给内部测试使用;

  • β:第二阶段,该阶段下,软件产品的大部分缺陷漏洞均已修复,但仍然会存在未修正的问题;

  • λ:第三阶段,该阶段的软件产品已经基本完善,相当成熟,至多需要个别地方作些优化工作即可发布使用

缺省ServerName的Apache Http Server主机解析流程

第一部分:了解ServerName、ServerAlias区别

ServerName:主机域名的解析入口,每一个虚拟主机在配置之时是必须的;
ServerAlias:域名别名,在配置了ServerName的基础上,想要集成“一主机多域名”的结构,ServerAlias是不错的选择。

第二部分:如果主机缺省了ServerName的配置会如何发展

官方是这样解释的:

    # Ensure that Apache listens on port 80
    Listen 80
    # Listen for virtual host requests on all IP addresses
    NameVirtualHost *:80
    
    < VirtualHost *:80>
        DocumentRoot /www/example1
        ServerName www.example.com
        # Other directives here
    < /VirtualHost>
The asterisks match all addresses, so the main server serves no requests. Due to the fact that www.example.com is first in the configuration file, it has the highest priority and can be seen as the default or primary server. That means that if a request is received that does not match one of the specified ServerName directives, it will be served by this first VirtualHost. [Reference Link](http://httpd.apache.org/docs/2.2/vhosts/examples.html)

最后一句话已经提及,如果缺省了ServerName,则该请求域名会被映射到第一台虚拟主机配置中。 那么,哪一台是第一台呢?难道就是配置过程中,或者Include引入过程中的排在第一位置的吗?

第三部分:哪台主机是第一位置

我们准备以下用例进行分析:

第一步:重现错误解析现象
    >>vim /etc/hosts
    # 当前服务器IP是192.168.1.100
    192.168.1.100 aa.example.com
    192.168.1.100 bb.example.com
    192.168.1.100 ab.example.com
    192.168.1.100 none.servername.com
    
    >>vim /apache2/config/extra/httpd-vhosts.conf
    Include conf/extra/ab.example.com-vhosts.conf
    Include conf/extra/none.servername.com-vhosts.conf ##未配置ServrName
    Include conf/extra/aa.example.com-vhosts.conf
    Include conf/extra/bb.example.com-vhosts.conf
    
    >>/apache2/bin/apachectl -S
    VirtualHost configuration:
    wildcard NameVirtualHosts and _default_ servers:
    *:80                   is a NameVirtualHost
         default server aa.example.com (/apache2/conf/extra/aa.example.com-vhosts.conf:1)
         port 80 namevhost aa.example.com (/apache2/conf/extra/aa.example.com-vhosts.conf:1)
         port 80 namevhost ab.example.com (/apache2/conf/extra/ab.example.com-vhosts.conf:1)
         port 80 namevhost aa.example.com (/apache2/conf/extra/none.servername.com-vhosts.conf:1) ## 此行:none.servername.com-vhosts.conf被错误映射到了aa.example.com
         port 80 namevhost bb.example.com (/apache2/conf/extra/bb.example.com-vhosts.conf:1)
第二步:将hosts解析顺序作部分调整,观察修改后的解析结果
    >>vim /etc/hosts
    # 当前服务器IP是192.168.1.100
    192.168.1.100 bb.example.com ## 将该域名修改置于最前面
    192.168.1.100 aa.example.com
    192.168.1.100 ab.example.com
    192.168.1.100 none.servername.com
    
    >>/apache2/bin/apachectl -S
    VirtualHost configuration:
    wildcard NameVirtualHosts and _default_ servers:
    *:80                   is a NameVirtualHost
         default server aa.example.com (/apache2/conf/extra/aa.example.com-vhosts.conf:1)
         port 80 namevhost aa.example.com (/apache2/conf/extra/aa.example.com-vhosts.conf:1)
         port 80 namevhost ab.example.com (/apache2/conf/extra/ab.example.com-vhosts.conf:1)
         port 80 namevhost bb.example.com (/apache2/conf/extra/none.servername.com-vhosts.conf:1) ## 此行:none.servername.com-vhosts.conf被错误映射到了bb.example.com
         port 80 namevhost bb.example.com (/apache2/conf/extra/bb.example.com-vhosts.conf:1)
第三步:分析前两步的现象

从前面两步可以发现,未配置ServerName属性的虚拟主机,会优先根据本服务器的DNS解析机制依次解析,然后会将未配置ServerName的主机配置项,分配给第一条被DNS解析遇到的本服务器虚拟主机域名。

通过以上的分析,相信大家已经了解了缺省了ServerName的主机会如何解析了吧。这也就是为什么常常我们发现自己的配置没有错误抛出,但是域名解析路径紊乱的原因之一。

详解Switch语句的使用

与 if-then和if-then-else语句不同,switch语句可以有许多可以执行的分支。一个switch可以使用的类型包括如下类型:

基本数据类型:byte, short, char, int 枚举类型:enum 对象类型:Charater, Byte, Short, Integer

以下这个例子SwitchDemo,声明了一个int类型的month变量,使用了switch语句,根据月份的数值,输出月份的英文单词。

    public class SwitchDemo {
    public static void main(String[] args) {
        int month = 8;
        String monthString;
        switch (month) {
            case 1:  monthString = "January";
                     break;
            case 2:  monthString = "February";
                     break;
            case 3:  monthString = "March";
                     break;
            case 4:  monthString = "April";
                     break;
            case 5:  monthString = "May";
                     break;
            case 6:  monthString = "June";
                     break;
            case 7:  monthString = "July";
                     break;
            case 8:  monthString = "August";
                     break;
            case 9:  monthString = "September";
                     break;
            case 10: monthString = "October";
                     break;
            case 11: monthString = "November";
                     break;
            case 12: monthString = "December";
                     break;
            default: monthString = "Invalid month";
                     break;
        }
        System.out.println(monthString);
    }
    }

在以上的例子当中,结果输出了

    August

一个switch的case主体被称为switch block,一个switch block可以包含多个case,或者仅仅是default分支。 Switch语句将验证它的表达式,验证通过后,将执行验证通过的case分支之后的所有语句。

类似地,你也可以使用if-then-else语句实现SwitchDemo效果:

    int month = 8;
    if (month == 1) {
        System.out.println("January");
    } else if (month == 2) {
        System.out.println("February");
    }
    ...  // and so on

关于到底是使用if-then-else语句,还是switch语句,需要根据实际的可读性和测试程序的表达式情况而定。if-then-else适用于数值范围的判断条件,而switch则适用于单数值,枚举数值和简单对象的判断。

另外,switch还有一个有趣的关键语句——break。每一个break语句的出现,跳出了整个switch循环结构。 控制流会逐个switch block顺序执行。

break语句是重要的,因为假设没有break,所有switch block中的语句都会跳过不判断。自匹配正确的第一个case起,其他在该case之后的switch block会按顺序逐个执行,直到遇到break语句。

以下程序SwitchDemoFallThrough展示了在没有书写break语句的时候,代码的执行结果:

    public class SwitchDemoFallThrough {
    
    public static void main(String[] args) {
        java.util.ArrayList<string> futureMonths =
            new java.util.ArrayList<string>();
    
        int month = 8;
    
        switch (month) {
            case 1:  futureMonths.add("January");
            case 2:  futureMonths.add("February");
            case 3:  futureMonths.add("March");
            case 4:  futureMonths.add("April");
            case 5:  futureMonths.add("May");
            case 6:  futureMonths.add("June");
            case 7:  futureMonths.add("July");
            case 8:  futureMonths.add("August");
            case 9:  futureMonths.add("September");
            case 10: futureMonths.add("October");
            case 11: futureMonths.add("November");
            case 12: futureMonths.add("December");
                     break;
            default: break;
        }
    
        if (futureMonths.isEmpty()) {
            System.out.println("Invalid month number");
        } else {
            for (String monthName : futureMonths) {
               System.out.println(monthName);
            }
        }
    }
    }

以上程序输出结果为:

    August
    September
    October
    November
    December

对于以上例子,从技术上分析,代码最后的一个break语句是多余的,因为此时,尽管没有break语句,程序依然会跳出switch循环。这里推荐童鞋们善于使用break语句,如此一来,可以使得我们的代码结构可读性更高,更容易维护,更少的异常错误的发生。

default这类switch block,处理的是当前面所有case分支都没有匹配的情况下,则switch进入default进行处理。

以下示例代码SwitchDemp2,给我们呈现了一个statement可以拥有多个case标签。 (示例代码实现了计算指定月份所拥有的自然天数)

    class SwitchDemo2 {
    public static void main(String[] args) {
    
        int month = 2;
        int year = 2000;
        int numDays = 0;
    
        switch (month) {
            case 1: case 3: case 5:
            case 7: case 8: case 10:
            case 12:
                numDays = 31;
                break;
            case 4: case 6:
            case 9: case 11:
                numDays = 30;
                break;
            case 2:
                if (((year % 4 == 0) && 
                     !(year % 100 == 0))
                     || (year % 400 == 0))
                    numDays = 29;
                else
                    numDays = 28;
                break;
            default:
                System.out.println("Invalid month.");
                break;
        }
        System.out.println("Number of Days = "
                           + numDays);
    }
    }

以上程序输出结果为:

    Number of Days = 29

在Switch中使用String

从Java SE 7版本开始,我们就可以在switch循环中使用String Object作为判断的表达式了。接下来的代码片段StringSwitchDemo,实现了根据String对象类型的month变量,输出指定月份的序号。

    public class StringSwitchDemo {
    
    public static int getMonthNumber(String month) {
    
        int monthNumber = 0;
    
        if (month == null) {
            return monthNumber;
        }
    
        switch (month.toLowerCase()) {
            case "january":
                monthNumber = 1;
                break;
            case "february":
                monthNumber = 2;
                break;
            case "march":
                monthNumber = 3;
                break;
            case "april":
                monthNumber = 4;
                break;
            case "may":
                monthNumber = 5;
                break;
            case "june":
                monthNumber = 6;
                break;
            case "july":
                monthNumber = 7;
                break;
            case "august":
                monthNumber = 8;
                break;
            case "september":
                monthNumber = 9;
                break;
            case "october":
                monthNumber = 10;
                break;
            case "november":
                monthNumber = 11;
                break;
            case "december":
                monthNumber = 12;
                break;
            default: 
                monthNumber = 0;
                break;
        }
    
        return monthNumber;
    }
    
    public static void main(String[] args) {
    
        String month = "August";
    
        int returnedMonthNumber =
            StringSwitchDemo.getMonthNumber(month);
    
        if (returnedMonthNumber == 0) {
            System.out.println("Invalid month");
        } else {
            System.out.println(returnedMonthNumber);
        }
    }
    }

以上程序输出结果为:

    8

关于以上例子的编写,在每一个switch block中的case判断,等同于执行了在每一次匹配判断之时,使用了String.equals方法。以上的例子也正是使用了String.toLowerCase方法,确保了在每一次case的判断中,都是以小写形式的字符串进行匹配。

Note: 在以上StringSwitchDemo例子中包含了month == null的判断,确保了在每一个case判断语句中不会抛出空指针NullPointerException异常。

以下作一些本人对switch使用基础的理解 含带break语句的switch block

    switch(X)  
    {  
      case A:  
          System.out.println("A");
          break;
      case B:
          System.out.println("B");
          break;
      case C:
          System.out.println("C");
          break;
      default:
          System.out.println("Default");
          break;
    }

等同于

    if(X == A){
        System.out.println("A");
    }else if(X == B){
        System.out.println("B");
    }else if(X == C){
        System.out.println("C");
    }else{
        System.out.println("Default");
    }

未含带break语句的switch block

    switch(X)  
    {  
      case A:  
          System.out.println("A");
          break;
      case B:
          System.out.println("B");
      case C:
          System.out.println("C");
          break;
      default:
          System.out.println("Default");
          break;
    }

等同于

    if(X == A){
        System.out.println("A");
    }else if(X == B || X == C){
        System.out.println("B");
        System.out.println("C");
    }else{
        System.out.println("Default");
    }

阅读完以上,童鞋们应该已经明白了switch的使用方法了。哈哈,如果对你有用,可以分享给更多身边的朋友(可以使用右侧板块的文章分享功能)。

声明:除文章结尾处本人的理解观点描述外,本篇文章主要内容由本人翻译自Docs.Oracle.COM,欲阅读原版,请跳转至 The switch Statement

Markdown之旅

markdown-logo

Part 1

让我们开始学习Markdown基本语法吧。 首先我们先学习两种文字格式:_斜体(italics)_和粗体(bold)

  • 斜体(italics)  Tips: 在需要改变为斜体的文字前后,各增加两条下划线,例如:斜体文本

  • 粗体(bold)  Tips: 在需要改变为粗体的文字前后,各增加两个星号,例如:粗体文本

  • 斜体(italics) + 粗体(bold)  Tips: 在需要改变为斜体+粗体的文字前后,先增加下划线使得文本成为斜体,然后再增加星号,使其在斜体的前提下,增加粗体的效果,例如:斜体文本+粗体文本

Part 2

让我们来看看另外一种Markdown格式——“Header”。“Header”经常被用作网站、杂志文章和提示弹窗,渲染出能够体现重点关注的标题部分。其中,“Header”总共区分有六级,如下所示:

    # Header one

    ## Header two

    ### Header three

    #### Header four

    ##### Header five

    ###### Header six

样式如下所示:

Header one

Header two

Header three

Header four

Header five
Header six

Part 3

  • 为文本添加直接超链接(Inline Link)   Tips: “超链接标题”,例如:Search for it.

  • 为文本添加引用超链接(Reference Link)   Tips: 例如: Do you want to [see something fun][REF LINK VAR HERE]? [REF LINK VAR HERE]: www.google.com 需要注意的是,引用超链接的定义需要放置在引用上下文的下方。

Part 4

  • 添加直接图片

Tips:添加图片的Markdown语法与文本添加超链接相似,不同在于,需要在其最前面增加一个感叹号“!”(Exclamation Point),例如:

!["替换文本 Alt Text"]("图片地址")

替换文本可以解决当图片地址受损之时,在图片位置上呈现出相应的说明文字。

  • 添加引用图片

Tips: 其Markdown语法类似于Part 3讲解过的为文本添加引用超链接,不同在于,需要在其最前面添加一个感叹号“!”。 例如:

![The first father][First Father]    
![The second first father][Second Father]    
[First Father]: http://octodex.github.com/images/founding-father.jpg    
[Second Father]: http://octodex.github.com/images/foundingfather_v2.png    

Part 5

如果你需要对引述的文字内容作一些特殊的标识,或者是为杂志文章设计醒目的引文格式,Markdown的“块引用(blockquote)”将会对你非常有用处。块引用会使得读者对引述的句子或者段落更容易注意到。例如:

“The sin of doing nothing is the deadliest of all the seven sins. It has been said that for evil men to accomplish their purpose it is only necessary that good men should do nothing.”

那么,如何为你的文字段落添加块引用呢?

  • 添加块引用

Tips:创建一个块引用,你只需要在引述的段落之前,放置一个大于号(greater than)”>”,例如: This is a blockquote sentence.

Part 6

本部分为童鞋们介绍如何使用Markdown书写列表格式。 众所周知的是,列表主要区分为两类:有序(ordered),无序(unordered)。另外,有一种更为时髦的叫法,无序列表称为“Lists with bullet points”,有序列表称为“Lists with numbers”。

那么,如何使用Markdown语法创建两类列表呢?下面紧接着介绍书写的方法。

  • 创建无需列表

Tips:在列表的每一项条目之前,放置一个星号(asterist)“*”,例如:

* Milk
* Eggs
* Rice
  • 创建无序列表

Tips:在列表的每一项条目之前,放置一个数字,加上一个点号(period)“.”,例如:

1. Buy shoes
2. Do shopping
3. Finish homework

Part 7

我们有很多地方式实现段落的格式化。下面我们来介绍Markdown在创建段落和列表对其段落的两种方式。

  • Hard Break

Tips:为非列表的段落文字划分段落格式,HB的切分方式是向需要截成段落的文字处,插入回车(return)2次。例如:

Do I contradict myself?
“回车1次”
“回车2次”I am large, I contain multitudes.
  • 为列表的段落文字进行分行并对齐,即上文所述的“列表对其段落”,HB的切分方式是向需要截成段落的文字处,插入回车(return)2次,并在第2次回车之后,增加一个空格(blank),例如:
1. Do I contradict myself?
“回车1次”
“回车2次”“空格1个”I am large, I contain multitudes.
2. Anything else here.
  • Soft Break

Tips:为非列表的段落文字划分段落格式,SB的切分方式是向需要截成段落的文字处,插入空格(blank)2次。例如:

Do I contradict myself?“空格1次”“空格2次”I am large, I contain multitudes.为列表的段落文字进行分行并对齐,即上文所述的“列表对其段落”,SB的切分方式是向需要截成段落的文字处,插入空格(blank)2次之后,增加一个回车(return),例如:
1. Do I contradict myself?“空格1次”“空格2次”
“回车1个”I am large, I contain multitudes.
2. Anything else here.

效果图如下:

【段落】

Do I contradict myself? Very well then I contradict myself, (I am large, I contain multitudes.)

【列表对其段落】

  1. Crack three eggs over a bowl. Now, you’re going to want to crack the eggs in such a way that you don’t make a mess. If you do make a mess, use a towel to clean it up!

  2. Pour a gallon of milk into the bowl. Basically, take the same guidance as above: don’t be messy, but if you are, clean it up!

Part 8

恭喜你,阅读至此,如果前面谈到的知识你都掌握了,那么你已经具备了编写Markdown文档的基本能力。

不管你是否相信,但是你也只是刚刚踏入Markdown的学习门槛,而Markdown的扩展性是非常强大的,Markdown目前已经可以支持编写类似于表格(table)、预定义列表(definition list)、页脚备注(footnotes)等等。

因为那是Markdown的扩展内容,并不是必不可少的,所以本篇文章只介绍了Markdown的基础知识。

如果你想要学习到更多的Markdown的应用知识,欢迎跳转至其他的一些Markdown教程和应用平台进行知识提升。以下列出了部分可供各位参考:

声明:本篇文章内容由本人翻译自MarkdownTutorial.COM,欲阅读原版,请跳转至Markdown Tutorial

关于PressWork增加导航面包屑的实现

很开心,周末为自己的博客更换了新装,没错,就是PressWork。它是一个自定义很强的开源wp模板项目,但是还是缺少很多实用的模块,这一部分还是需要童鞋们自己根据自己的需求去定制。

这次我先来介绍对于PressWork的导航面包屑(Breadcrumb)的定制实现。

一、由于默认样式的情况,作者决定在header中新增面包屑的option

<?php
    /**
     * Add breadcrumb to header option
     * @author chenjinlong
     * @description presswork/functions.php
     */
    if(empty($pw_default_options)) {
    	$pw_default_options = array(
                      // 新增breadcrumb选项
    		"header_option" => "header_logo,nav,breadcrumb"
                      // 其他配置项目 …
             );
    }

二、新增面包屑实现函数

<?php
    /**
     * Deal with breadcrumb for single post | page. 
     * @author chenjinlong
     * @description presswork/functions.php
     */
    function pw_get_breadcrumb(){
        $home = '<a href="'.home_url().'">首页</a>' . ' » ';
        $category_parent_top = '';
        if(is_single()){
            $categorys = get_the_category();
            $category_parent_top = get_category_parents($categorys[0]->term_id, true, ' » ');
            $title = the_title('', '', false);
        }elseif(is_page()){
            $title = the_title('', '', false);
        }else{
            $title = '';
        }
        return $home . $category_parent_top . $title;
    }

三、增加元素输出逻辑分支

<?php
    /**
     * Add page breadcrumb for single post top. 
     * @author chenjinlong
     * @description presswork/functions.php, ‘pw_get_element’ function
     */
    if($pw_add_name=="breadcrumb"){
        echo '<li>';
        if(is_single() || is_page()){
            $handle = pw_get_breadcrumb();
            echo $handle;
        }
        echo '</li>';
    }

参照以上的步骤,简单版的面包屑导航就弄好了,如果需要提高视觉美感,再自己增加css样式的支持即可,因为每个人要求不一,这里就不作建议样式了。

Nanjing 2014 Youth Olympic Games

南京 · 奥体中心 · 2014青奥会

(南京 · 奥体中心 · 2014年青奥会)

南京 · 青奥会 · 百米飞人

(南京 · 青奥会 · 百米飞人)

南京奥体中心

(南京 · 奥体中心 · 东门)

南京奥体中心

(南京 · 奥体中心 · 东门)

青奥会 · 田径赛场

(南京 · 奥体中心 · 青奥会)

南京 · 奥体中心 · 三星品牌

(南京 · 奥体中心 · 韩国三星)

Linux下的PHP安装

Tips:在安装php之前,请确认服务环境的httpd服务已经安装成功。若还没有安装apache http serer,参考这里:Linux下的Apache安装

1.检查机器环境

首先要确认机器环境中的php是否已经安装了其他版本,是否需要共存多版本,机器版本是否已经符合要求…

可以参考的shell命令:

whereis php #环境变量已配置的php
ls /usr/local/php #常用php安装目录(或软链接目录)

2.PHP基本安装

(a)下载php的tar包
官方地址:http://www.php.net

(b) 解压下载好的php压缩包

tar zxvf php-5.3.3.tar.gz

(c)生成makefile并编译安装

Tips:./configure —help可以查看到配置选项,如果需要定制,选择适合的选项作为安装配置也未尝不可。

./configure —prefix=/usr/local/php5 —with-apxs2=/usr/local/apache2/bin/apxs —with-config-file-path=/usr/local/php5/lib 

—prefix=PATH:指定安装目录

—with-apxs2=PATH:指定已安装好的apache apxs文件,关联Apache http server,生成libphp5.so

—with-config-file-path=PATH:指定php.ini的文件存放目录

Tips:安装过程中,视每个人的机器环境不同,会出现一些依赖包缺少的异常发生,致使安装失败。这时候不要着急,按照提示信息安装对应的依赖包就好,好事多磨嘛,haha

(d)关于php.ini文件的配置,可以自行Google或者百度就好,上面的相关说明文章多如牛毛,这里就不啰嗦了。

3.Apache HTTP Server与PHP的关联配置

(a)配置入口文件

    <ifmodule dir_module="">
    	DirectoryIndex index.html index.php
    </ifmodule>

(b)配置php解析模块

    LoadModule php5_module modules/libphp5.so

Tips:这里容易混淆的是,libphp5.so其实是php源码编译安装之时生成的,而非apache自己包含的。</blockquote>

(c)添加解析文件类型

 AddType application/x-httpd-php .php
  1. PHP扩展安装

(a)扩展的认识

php的扩展区分为php自带扩展和自定义扩展包,php自带的扩展位于php安装包下的 /ext 文件夹下,而额外的自定义扩展,则需要通过PECL平台下载,地址为:http://pecl.php.net/packages.php

(b)安装扩展

安装的过程中,我们需要认识一个命令:phpize

Tips:phpize命令位于php安装目录的/bin文件夹下,是用来扩展php扩展模块的,通过phpize可以新增php的外挂模块。</blockquote>

以下举例(安装pdo_mysql扩展)说明:

cd pdo_mysql
/usr/local/php5/bin/phpize
./configure —prefix=/usr/local/php5/extensions —with-php-config=/usr/local/php5/bin/php-config
make
make test
make install

此时,当make install执行完成后,在terminal中会打印出.so扩展文件存放的目录路径。然后再进一步配置php.ini文件中的“extension_dir=”和“extension=pdo_mysql.so”即可。

  1. 启动apache
    >> /usr/local/apache2/bin/apachectl -k start
    >> /usr/local/apache2/bin/apachectl -k stop
    >> /usr/local/apache2/bin/apachectl -k restart

此时,可以通过浏览器访问指定的虚拟主机,通过php函数phpinfo()可以查看到当前php安装的情况,包括php.ini配置文件路径、已安装扩展配置情况和apache http server的关联配置情况等。

关于Sublime Text 2 支持执行PHP、JAVA单文件的配置说明

Sublime Text目前非常流行,其功能强大到许多的web开发者爱不释手无法自拔,哈哈。最近自己也由原来的BBEdit转战为Sublime Text,由于自己常常会使用到单文件调试代码片段的执行情况,所以让Sublime Text支持单文件调试php、java文件的需求迫在眉睫。

Tips:本人使用的是Sublime Text 2,version 3没有尝试过,不过在版本更新说明中对于Build System的变更不大,也是可以支持的。

Sublime Text 的构建系统(Build System)可以让我们通过外部程序来运行文件,并可以在Sublime Text 的Console中查看输出,其功能配置在“Tools > Build System”。 构建系统包括三部分:

(1)使用JSON格式保存配置文件(.sublime-build)

(2)使用Sublime Text命令驱动构建过程

(3)可选的,还可以包括一个外部的可执行文件(脚本或二进制文件)

文件格式:

    {
        "cmd": ["python", "-u", "$file"],
        "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)",
        "selector": "source.python"
    }

cmd: 包括命令及其参数数组,若非指定绝对路径,优先从系统PATH变量中获得;

file_regrex: 可选选项,Perl格式的Regular Expression可以获取cmd的错误输出;

selector: 可选选项,在选定Tools > Build System > Automatic时生效,由系统根据当前文件情况尝试自动选择构建系统;

target: 可选选项。运行的Sublime Text命令,缺省为exec(Packages/Default/exec.py)。该命令从*.build-system中获取配置的选项数据;

variants: 可选选项。该项配置下的内容是主构建系统配置项目的备选内容。如果构建系统中的selector与激活的文件相匹配,variants中的变量“name”则会出现在Command Palette(Tools > Build System)下;

name: 仅适用于variant中。用来识别系统中的不同构建系统。如果“name”为“Run”,则会显示在Command Palette下,并且通过快捷键“Command + shift + B”调用;

以下是含带variants的构建系统示例:

    {
        "selector": "source.python",
        "cmd": ["date"],
    
        "variants": [
            { "cmd": ["ls -l *.py"],
              "name": "List Python Files",
              "shell": true
            },
            { "cmd": ["wc", "$file"],
              "name": "Word Count (current file)"
            },
            { "cmd": ["python", "-u", "$file"],
              "name": "Run"
            }
        ]
    }

根据以上的设定,按 Ctrl + B 会运行date命令, 按 Crtl + Shift + B 会运行Python 解释器,并且在构建系统激活时将剩余的备选项显示在Command Palette中

常见的Sublime Text构建系统内置变量有:

  • $file_path 当前文件所在路径, 比如 C:\Files.

  • $file 当前文件的完整路径, 比如 C:\Files\Chapter1.txt.

  • $file_name 当前文件的文件名, 比如 Chapter1.txt.

  • $file_extension 当前文件的扩展名, 比如 txt.

  • $file_base_name 当前文件仅包含文件名的部分, 比如 Document.

  • $packages Packages 文件夹的完整路径.

  • $project 当前项目文件的完整路径.

  • $project_path 当前项目文件的路径.

  • $project_name 当前项目文件的名称.

  • $project_extension 当前项目文件的扩展部分.

  • $project_base_name 当前项目仅包括名的部分.

下面是本人的Build System的配置细节,提供给各位童鞋参考:

1、php

快捷键:Command + B

配置详情:

    {
        "cmd": ["php", "$file"],
        "file_regex": "php$",
        "selector": "source.php"
    }

2、Java

快捷键:Command + B(编译) => Command + shift + B(执行)

配置详情:

    {
        "cmd": ["javac", "-encoding", "UTF-8", "$file"],
        "file_regex": "^(...*?):([0-9]*):?([0-9]*)",
        "selector": "source.java",
        "encoding": "UTF-8",
        "variants": [{
            "name": "Run",
            "cmd": ["java", "$file_base_name"],
            "encoding": "UTF-8"
        }]
    }

参考资料链接:

非官方Build System说明:http://sublime-text-unofficial-documentation.readthedocs.org/en/sublime-text-2/reference/build_systems.html

windows下的PHP与Apache安装

站内的关联的其他几篇文章讲述了LAMP环境搭建的过程与建议Tips。今天要来讲讲的是关于WAMP,毕竟环境是Windows的童鞋们还是居多的。

1.安装Apache HTTP SERVER + PHP

关于如何安装Apache HTTP SERVER和PHP的过程,这一步在Windows环境下可以简单点到略过,官方都提供了.exe安装包或portable的zip包,双击按提示安装好,然后将PHP-Install-DIR/bin配置到环境变量Path中即可。

Tips:先安装Apache,再安装PHP,再作二者关联。

2.PHP配置

说到PHP配置,首先要知道PHP的配置文件是php.ini,再安装好的PHP目录内,会包含几个不同用途的php.ini文件,不同的php版本之间会有所差异,大致的文件如:php.ini-development, php.ini-production, php.ini-test, php.ini,而php.ini是默认读取的配置文件。

php.ini-development:开发环境常用的属性配置概况
php.ini-test:测试环境常用的属性配置概况
php.ini-production:生产环境常用的属性配置概况

读者可以根据需要,学习其中的配置含义,配置出自己系统最合适的配置项目。

开发者最常用到的配置,便是扩展配置了,没有扩展,php无法实现curl远程调用,无法实现mysql,pgsql连接,更无法实现gd开发。

在扩展配置中,我们必须认识“extension_dir”和“extension=”配置项目,前者指向扩展链接库文件所在的目录,后者配置所需要的扩展链接库文件。 例如,我们配置pdo_mysql的扩展:

extension_dir=“D:/php/ext”
extension=pdo_mysql.dll

Tips:Windows下的PHP安装好之后很友好,都为大家根据相应的vc版本编译好了dll扩展文件,可以为大家省下不少时间。找不到的扩展可以到官方的pecl上找:http://pecl.php.net/packages.php</blockquote>

配置好了php,此时apache容器仍然不能解析运行php文件,因为我们还差将两者关联起来的其他一些简单配置,下面我们来讲讲这一块。

3.在Apache HTTP Server中配置关联php语言解析模块

php是以module的方式与apache关联起来的。

Tips:Apache HTTP Server主配置文件是httpd.conf。

// 在httpd.conf文件中,新增module加载项:
LoadModule php5_module D:/php/php5apache2_2.dll //关联并加载php解析模块
PHPIniDir “D:/php”//关联PHP安装目录及其php.ini,该项配置后,可以在phpinfo()中的属性“Loaded Configuration File”检查是否关联php.ini成功

然后,告诉apache它现在可以执行.php文件了:

AddType application/x-httpd-php .php //让.php格式文件存储php代码并可以被执行
AddType application/x-httpd-php .html //让.html格式文件存储php代码并可以被执行
AddType application/x-httpd-php .txt //让.txt格式文件存储php代码并可以被执行

到这里,Apache与php之间的关联关系就建立起来了。

4.启动Apache,准备执行php文件,你可能会遇到如下情况:

1)php扩展配置没有生效,在phpinfo()中没有发现新增的扩展 答:a. 检查扩展配置项目按上文要求正确; b. 是否按官方要求将libeay32.dll, ssleay32.dl拷贝到C:/windows/system32/下;

官方原文如下:

vatozana at yahoo dot com 3 years ago I had problems registering Curl extentions and it was cause by the old dll of libeay32.dll and ssleay32.dll which were already in my c:\windows\system32 I replaced them and all is now fine!!

5.其他参考:

最官方的莫过于php.net,该站旗下有关联的许多对php很有好处的二级站点,这里可以找到:http://cn2.php.net/sites.php

Linux下的Apache安装

说到LAMP/WAMP服务器环境的搭建,相信PHPer都无一不会,但是对于Freshman来说,这一块的内容还是需要认真学习,从中可以学习到许多环境配置上的基础常识,自己动手捣腾过的东西才印象深刻,犹如“好记性不如烂笔头”的说法一般。

关于LAMP/WAMP环境的搭建,准备细分为几篇文章进行讲解,希望能为更多需要该项知识的人提供更为全面的配置流程规范。

本篇文章,我们先Apache在Linux服务器环境下的搭建。

Tips:在搭建PHP之前,一定要先安装搭建好Apache/Nginx的容器环境。

1.检查环境中Apache的依赖包是否已经安装齐全

(a)APR(Apache Portable Runtime) Project:apr, apr-util, apr-iconv(optional) Download:http://apr.apache.org/

(b)PERL Language Support Download:http://www.perl.org/

(c)PCRE Library:A set of functions that implement regular expression pattern matching using the same syntax and semantics as Perl 5. Download:http://sourceforge.net/projects/pcre/

2.安装APR (a)解压下载的tar包

tar zxvf apr.tar.gz -C [TARGET_DIR]
tar zxvf apr-util.tar.gz -C [TARGET_DIR]

(b)安装

#先安装apr.tar.gz    
./configure —prefix=[INSTALL_DIR]
make
make install

#再安装apr-util.tar.gz
./configure —prefix=[INSTALL_DIR] —with-apr=[APR_INSTALL_DIR]
make
make install

备注:若需要规划安装目录和系统/usr/local目录之间的关系,可以参考软链接(symbolic link)的创建,语法如

ln -s [SOURCE_FILE_OR_FOLDER] [TARGET_REF_SYMBOLIC_LINK_DIR]

3.安装PERL与PCRE PERL的安装参照官方网站教程进行安装即可,关于PCRE的安装简单如下:

./configure —prefix=[INSTALL_DIR]
make
make install

4.安装Apache HTTP Server (a)解压缩httpd.tar.gz (b)进入解压后的目录,定制需要开启的httpd模块,类似:

./configure —prefix=[INSTALL_DIR] —enable-so —enable-rewrite=shared —with-mpm=prefork —with-apr=[APR_INSTALL_DIR] —with-apr-util=[APR_UTIL_INSTALL_DIR] —with-pcre=[PCRE_INSTALL_DIR] 
make 
make install

5.启动httpd服务

[HTTPD_INSTALL_DIR]/bin/apachectl -k start 
ps -ef | grep httpd (或者ps aux | grep httpd)#查看是否启动httpd成功

备注:可以拷贝apachectl到服务目录(/etc/init.d)中,作为服务项(service命令)启动,例如

cp [HTTPD_INSTALL_DIR]/bin/apachectl /etc/init.d/httpd
service https start

6.操作快捷Tips

(a)Apache .configure命令范例

./configure --prefix=/opt/tuniu/apache2 --with-apr=/usr/local/apr/bin/apr-1-config --with-apr-util=/usr/local/apr/bin/apu-1-config --enable-so --enable-rewrite --enable-ssl --enable-cgi --enable-mods-shared=all --with-ssl=/usr/lib64/openssl --enable-proxy --enable-proxy-http

(b)安装mod_ssl依赖

在安装mod_ssl过程中需要依赖SSL/TLS Encryption库,建议使用开源的OpenSSL。下载官方见这里:https://www.openssl.org/

资料提示:许多童鞋们会被搜索引擎导向ModSSL.COM,其实不然,ModSSL的扩展tar包安装不支持Apache Http Server 2.

谈谈Linux用户管理

谈起Linux,没有接触过的童鞋们可能会望而却步,其实不然。今天恰好遇到好些童鞋询问相关于Linux的一些终端命令操作的问题,自己正好有一想法,接下来会陆续写一些关于Linux的日常应用的博客文章。使用Linux系统,首先你一定要是Linux的用户。今天咱们就先来谈谈Linux关于用户的一些事情。

Linux的用户管理有两个很重要的概念,一个是用户,一个是组。用户与组是一对多的关系。当你创建用户之时,会自然产生一个与你用户名相同的一个组,是新增用户所建立的默认组关系。

在用户的权限管理上,一个用户可以独立具备多项权限,一个组也可以具备多项权限,当一个用户属于一个组之时,它便自然地拥有了整个组的权限,因为它成为了这个组的成员。

下面来逐一解释用户和组的常用操作:

组(group):

1.查看系统的组信息 Linux中的信息都以文件形式存储,权限配置文件当然也不例外。

more /etc/group

2.查看当前用户所属的组

groups

3.新增组

groupadd [GROUP_NAME]

4.删除组

groupdel [GROUP_NAME]

5.修改组信息 看到这里,可能大家会自然地联想到,groupmod这样子的一个命令,当然,linux是强大的,这个命令不出意料地存在着,但groupmod并不能修改所有的group信息,只能修改GID、重命名、修改组密码和unique属性

groupmod -g [NEW_GID] #修改GID
groupmod -n [NEW_GROUP_NAME] #重命名
groupmod -o #unique属性,-o全称为--non-unique
groupmod -p [NEW_PASSWORD] #修改组密码

如果需要修改组所包含的用户列表,可以打开文件/etc/group文件,找到相应的组名进行编辑,/etc/group文件内容格式说明如下,可供参考和阅读:

[GROUP_NAME] : [GROUP_PASSWORD] : [GID] : [GROUP_USER_LIST]
示例:root:x:511:user1,user2,user3

Tips:密码段的“x”(或者“*”)是特殊的字符,表示没有设置密码(或者是设定了但是被shadow技术隐藏了,口令被加密后实际存放到了/etc/shadow,只有超级用户才能读取该文件),同时,修改/etc/group或者/etc/passwd文件,都需要root权限,请确保su - 切换到root下,再作该类操作。

6.其他 现在的linux/mac的UI功能界面都发展地非常完善,可以找找自己的发行版OS是否有用户管理的界面操作功能(普遍都会存在的),界面操作和windows操作习惯是相似地,那样子可以更容易让人理解和入门,也不失为一种便捷的linux用户管理的方式。

用户(user):

1.新增用户

useradd [USER_NAME] #新增用户,默认在/home下生成与用户名同名的用户主目录
useradd -d /home/[USER_HOME_DIR_NAME] [USER_NAME] #新增用户,同时指定自定义的用户主目录
useradd -g [EXIST_GROUP_NAME] [USER_NAME] #新增用户,同时关联一个已存在的组
useradd -G [EXIST_GROUP_NAME] [USER_NAME] #新增用户,同时增量关联一个已存在的组
useradd -s /bin/false [USER_NAME] #新增用户,同时禁用其默认登录的shell权限

2.修改用户信息

方式一:终端命令

usermod -d /home/[USER_HOME_DIR_NAME] [USER_NAME] #指定自定义的用户主目录
usermod -g [EXIST_GROUP_NAME] [USER_NAME] #强制关联一个已存在的组为默认组
usermod -G [EXIST_GROUP_NAME] [USER_NAME] #增量关联一个已存在的组
usermod -s /bin/false [USER_NAME] #禁用用户默认登录的shell权限

方式二:编辑/etc/passwd配置

先说说/etc/passwd文件内容格式说明:

[USER_NAME] : [USER_PASSWORD] : [UID] : [GID] : [USER_FULL_NAME] : [USER_HOME_DIR] : [USER_LOGIN_SHELL]
示例:root:x:0:0:root:/root:/bin/bash

3.修改密码

passwd [USER_NAME] 

若只是修改当前登录用户的密码,直接

passwd 

即可。

4.增加sudoer权限

su -
visudo

此时切换到了sudo配置文件的编辑页面,在文件底,增加一行配置:

[USER_NAME]   ALL=(ALL)   ALL #切换sudo需要输入当前登录的具备root权限用户的密码
或者
[USER_NAME]   ALL=(ALL)   NOPASSWD:ALL #切换sudo不需要重新输入密码

,编辑完后 :wq 即可。 备注:以上格式做一些说明吧,避免有的童鞋不知所以然哈

[USER_NAME]    [MACHINE_NAME]=([SWITCH_IDENTITY/USER_NAME])   [COMMAND]
示例:%wheel    ALL=(ALL)   NOPASSWD: ALL #允许wheel用户组中的用户在不输入该用户的密码的情况下使用所有命令 

浅谈sitemap协议及其应用

很多人都知道,一个站点建立起来之后,不仅内容要丰富,还有另外一点非常重要,那就是搜索引擎的爬取与收录。

众所周知的著名搜索引擎,国际上有Google,Yahoo!,Bing,国内通用的有百度,搜狗,360搜索等。当前国情决定,国际上著名的在国内发展地都不怎么样,要么发展路线左倾,与方针精神不相符合然后各种关键词词库过滤,要么特例独行直接放弃陪你们这么玩。这其中著名的产物应该当属伟大的墙无疑。不知道伟大的墙是什么?点击这里 进一步了解。

前面分析了国际著名Search Engine的国内发展情况,“革命尚未成功,仍需更加努力”。接着我们来聊聊国内的引擎。在作者高中刚接触计算机的时候,听闻的是“百谷虎”是身边最多人使用的,因为他是三个引擎的结果集合。直到高三之后才发现,原来那是一个山寨的网站,呵呵。

百度,当属目前互联网最大的中文搜索引擎无疑,在Google“回家”之后,更是肆无忌惮地称霸中原了,搜狗,搜搜啥的,只能喝喝菜汤啥的就觉得相比往年已是N*100%的增长,啥狗啥更懂你的,其实等你真正用起来的时候就会发现,其实他们更多的是其合作方提供的外卖。

360搜索算是异军突起,凭借着安全领域揽到的N亿群体,直接进军百度帝国的搜索领域,其初期不遵循robots协议的做法为同行业所谴责,但与此同时也为其快速发展提供了很大的帮助,这是否能说是不择手段呢?这或许每个人都会有不同的看法。

360搜索的强势也带来了很高的回报,发布1年之后,保守估计,确实快速蚕食了百度的超2-3成左右的份额。

好了,好像已经偏题了,其实前面说那么多也无非是让大家都了解了解我们身边的搜索引擎的发展态势。继续今天的主题,就是sitemap。

那么什么是sitemap?我们来看下wikipedia的定义:

A site map (or sitemap) is a list of pages of a web site accessible to crawlers or users. It can be either a document in any form used as a planning tool for Web design, or a Web page that lists the pages on a Web site, typically organized in hierarchical fashion.

简单了说,就是一个站点的树形层级结构,站点的脑图,根据它可以遍历整个站点的角角落落。

那么,我们为什么需要它呢?因为sitemap可以提供给搜索引擎,提供给spider一个内容爬取的大道入口。搜索引擎如何知道你上线了一个站点,如何知道你发布了一篇博客,如何知道你的站点里含了哪些其他存档的文章等等。

如果你的站点有了sitemap,spider会在它的周期内很快收录你站点中的所有文章,按照权重提供到用户的搜索结果中。

以上只是说明了sitemap的好处和作用,不要误解sitemap是引擎收录文章的唯一入口。当然不是,只是说,如果你有了sitemap,符合指定搜索引擎所适配的sitemap,那么会收录地更快更好。具体可以抽空学习SEO知识,你会收获更多。

接下来介绍一下sitemap协议。

sitemap通用的做法是使用XML Schema书写的文档形式,必须包含XML tags,同时文档中包含的内容必须是被处理(Entity escaping)过的,同时文档必须符合UTF-8编码。

其中,sitemap必须满足以下条件:

  • 包含所有其他XML节点;
  • 在urlset中参照协议标准定义相应的命名空间;
  • 至少包含一个以父节点存在的有效的
  • 节点中至少包含一个节点,以说明需要被爬取内容的链接;
  • sitemap中仅允许包含相同域名的站点,如果是多个域名,则需要书写多个sitemap;

前面谈到了Entity escaping是什么呢?给大家一张图相信大家就知道了,简单说就是把一些特殊字符用其他的形式表示起来。如下图所示:

Character Escape Code
Ampersand    &         & amp;
Single Quote   ‘          & apos;
Double Quote “          & quot;
Greater Than >          & gt;
Less Than      <          & lt;

以下提供一个简单的sitemap.xml示例供大家参考:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
   <url>
      <loc>http://www.example.com/</loc>
      <lastmod>2005-01-01</lastmod>
      <changefreq>monthly</changefreq>
      <priority>0.8</priority>
   </url>
</urlset> 

如果需要在同一个站点中维护不同subdomain的sitemap,那么可以参考如下做法:

http://www.sitemaphost.com/sitemap-host1.xml http://www.sitemaphost.com/sitemap-host2.xml http://www.sitemaphost.com/sitemap-host3.xml

以下做一些关于wordpress的sitemap的介绍。

wordpress制作sitemap,最为简单的途径,就是可以借助其平台强大的plugin功能,但是不管你是否相信,wordpress的plugin开启地越多,对wp系统的运行效率的影响越大。

所以,能手工添加还是尽量走手工添加定期更新为佳,毕竟搜索引擎也不会每天日以继夜地抓取你的小站点,你也不会日以继夜分秒必争地更新着你的博客文章。所以个人小博客站点类,建议手工添加sitemap文件。

在手工添加这条道路上,Google和Baidu提供了许许多多地做法,都可以达到不错的效果。我这里只简单介绍本站博客的做法。

1.在wp根目录下新增sitemap.php文件,填充以下代码:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <url>
        <loc></loc>
        <lastmod></lastmod>
        <changefreq>always</changefreq>
        <priority>1.0</priority>
    </url>
    get_results("SELECT * FROM $wpdb->posts WHERE post_status = 'publish' ORDER by post_modified DESC"); ?>
        <url>
            <loc>ID); ?></loc>
            <lastmod>post_modified, false); ?></lastmod>
            <changefreq>daily</changefreq>
            <priority>0.8</priority>
        </url>
</urlset>

2.新增sitemap_gen.php,填充以下代码,用以生成XML文件:

<?php   
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "http://DOMAIN:PORT/sitemap.php");
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$output = curl_exec($ch);
file_put_contents('./sitemap.xml', $output);
curl_close($ch);
  1. 上传到站点根目录,浏览器执行“http://DOMAIN:PORT/sitemap_gen.php,就这样sitemap.xml就在站点根目录生成成功了。

  2. 剩下的步骤,就是向Google和Baidu等各大搜索引擎提交sitemap.xml访问地址。

参考资料:
- Sitemap Protocol:http://www.sitemaps.org
- Simply Making Sitemap:http://www.xml-sitemaps.com
- Google Sitemap Generator WP Plugin:http://wordpress.org/plugins/google-sitemap-generator/
- Sitemap Formats And Guidelines:https://support.google.com/webmasters/answer/183668

GIT基础知识讲解(二)

七、Git分支级别的基本操作

  • 查看/编辑 本地(远程)分支信息

$ git branch -[option]

另,该命令后紧接英文短语,执行的是创建分支的操作(仅创建而不切换)

  • 分支的合并(详见PPT-10)
$ git checkout master
$ git merge iss53

分支的合并如下图流程示例

image

  • 分支的衍合(详见PPT-11)
$ git checkout experiment
$ git rebase master

分支的衍合如下图流程示例

image

八、想做就做,听你的

下面是一个有趣的例子,你也来实战一下吧

image

注意点:Merge和Rebase的区别:

merge是执行2个待合并版本以及公共基线版本的3方合并;

rebase是将待合并版本之一的变动作为补丁应用到另一个待合并分支上。

九、分支衍合行为规范

Do not rebase commits that you have pushed to a public repository.

(一旦分支中的提交对象发布到公共仓库,就千万不要对该分支进行衍合操作。)

如果你遵循这条金科玉律,就不会出差错。否则,人民群众会仇恨你,你的朋友和家人也会嘲笑你,唾弃你。

再谈merge 和 rebase 的区别:

1)从文件结果看,merge 和 rebase 可以达到同样效果。

2)从历史纪录看,他们存在差异:merge 显示合并后的多父结点,呈现环形,而 rebase 呈现线性结果,即它对分支历史进行合并操作。rebase 提供更好的历史呈现方式(似分支从未曾发生过),但有风险。掌握一条原则:Do not rebase commits that you have pushed to a public repository. 即,不要对你提交过的分支进行 rebase。因为本地的改变会造成远端的困惑。

十、分支衍合的协作风险

如下图示例

image

After you do that, your commit history will contain both the C4 and C4’ commits, which have

different SHA-1 hashes but introduce the same work and have the same commit message.

十一、漫步在项目协作中的Mr.Git

如何给项目贡献代码?

如果你是没有Git的可怜人,通过邮件提交补丁可以是普通patch或format-patch,后者为佳。

经过apply或am将代码合并到目标分支上进行验证,确认稳定后,再将其并入master分发主干上。

拥有Git的人,这一块的操作就甭提有多方便了。

如果你拥有Mr.Git,那么通往罗马的大道有如下几条(寡人称之为Git合并模式):

(1)直接合并进入本地的master主干分支进行验证;

(2)为特性分支提供一个用于合并用途的develop-branch;

(3)复杂项目的并行贡献

(4)大项目的特性并入长期分支

(5)衍合流程引入代码

(6)挑拣(cherry-pick)流程引入补丁代码

PS: Git项目的合并方式采用的是模式(4)

十二、常见Git的合并模式示例

(1)直接合并进入本地的master主干分支进行验证;

image

(2)为特性分支提供一个用于合并用途的develop-branch;

image

(3)复杂项目的并行贡献

image

(4)大项目的特性并入长期分支

image

(5)衍合流程引入代码

image

(6)挑拣(cherry-pick)流程引入补丁代码

image

This pulls the same change introduced in e43a6, but you get a new commit SHA-1 value, because the date applied is different. Now your history looks like Figure 5-27.

十三、推荐:Github.com,一个神奇的网站

当前世界首屈一指,最大没有之一的Git项目代码托管网站

image

十四、参考链接

  1. Git官方文档 http://www.git-scm.com/documentation/

  2. TortoiseGit https://code.google.com/p/tortoisegit/

  3. Git下载 http://www.git-scm.com/downloads

  4. msysGit, another GUI Client http://msysgit.github.io

十五、结束语

image

和吉他谈一场恋爱

它,可能是吸引暗恋对象注意最快的捷径;

它,可能是成为下一个 陈绮贞 / 押尾光太郎 / Jimmy Hendrix 的第一步;

它,可能让你手指酸疼,嘴里还喃喃自语地背着和弦;

它,可能让你半夜不睡,反复研究网上的弹唱视频。

然后,随着时间流逝,人事变迁,

有一天,它从生活中消失了。

而你,也不会注意到。

要到很久以后,又听见吉他的旋律时,才会想起曾经的疯狂。

希望,还能够把它找出来,重温珍贵的回忆。


梦。幻之清新。静思音符基地:

曲目列表:

关于文档的版本号理解

版本号在生活中,研发工作中经常会遇到,但是是否有深入了解过为什么文档的版本号的后面一长串数字的代表含义吗?

下面来详细讲讲关于文档版本号的那些事儿。

以下文摘了来自于百度百科对于文档版本的释义:

“ 通常指[软件](http://baike.baidu.com/view/37.htm)程序发行的次数[版本号](http://baike.baidu.com/view/421712.htm),一般文件版本形如 x.x.x 其中第一个X极有可能是软件[内核](http://baike.baidu.com/view/1366.htm)的版本,第二个X就是发行的正式版本的版本号,而第三个X代表的通常是软件更改修正的次数,软件版本的类别。文件版本可以分为以下几种:1.文件源版本(文件的核心开发版本) 2.文件修改版(其他用户或者产品生产单位更新或者升级的版本) 3.文件[破解版](http://baike.baidu.com/view/10096.htm)(其他用户或者技术组为了自己使用对[源文件](http://baike.baidu.com/view/385166.htm)所做的反编译版本) 4.文件合作版(与其他单位或者院校的合作版本) 5.文件定制版 (特指某个企业为定制版) 6.文件内部版本(仅限生产单位内部使用) ”

然后,对于自己涉及的研发文档作了如下的版本号规范(仅供参考):

“ 关于版本号的说明: 规范版本号格式为"A.B.C.D",以下列出各个数值发生变更的条件。 A:核心版本号,当发生核心机制、新增重大内容、变更项涉及面广的时候(仅发生在成功上线后); B:一般发行的正式版本号,当发生变更项系一般的非重大变更内容的时候(仅发生在成功上线后); C:文档修订次数,当文档发生了任意变更,该版本号按1递增; D:变更日期,例如"911"(9月11日),"1109"(11月09日)。 ”

以上是对于文档的版本号管理,而互联网产品的版本号与文档的版本号规范有一定区分,后面的文章会对互联网产品的版本号常用规范作出分析。

Windows环境下使用命令行管理服务

一、创建服务

1.1 命令”sc create”官方说明

描述: 在注册表和服务数据库中创建服务项。 用法:

sc <server> create [service name] [binPath= ] <option1> <option2>...

选项:
> 注意: 选项名称包括等号。等号和值之间需要一个空格。

type= <own|share|interact|kernel|filesys|rec> (默认 = own)

start= <boot|system|auto|demand|disabled|delayed-auto> (默认 = demand)

error= <normal|severe|critical|ignore> (默认 = normal)

binPath= <BinaryPathName>

group= <LoadOrderGroup>

tag= <yes|no>

depend= <依存关系(以 / (斜杠) 分隔)>

obj= <AccountName|ObjectName> (默认 = LocalSystem)

DisplayName= <显示名称>

password= <密码>

1.2 使用示例

sc create "Memcached Master" start=auto binPath="D:\memcached\memcached.exe -d install -m 32 -p 11211" DisplayName= "Memcached Master"
(图示:添加指定目录的memcache服务)`

二、删除服务 2.1 命令”sc delete”官方说明

描述: 从注册表删除服务项。如果服务正在运行,或另一进程已经打开到此服务的句柄,服务将简单地标记为删除。 用法:
sc <server> delete [service name]

2.2 使用示例

sc delete “Memcached Master”
(图示:添加指定目录的Memcached Master服务)

三、手工操作简述

步骤1. 打开注册表编辑器(Win+R打开运行 → regedit → 回车
步骤2. 寻找服务键值目录(HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services),一般服务会以相同的名字在这里显示一个主健,对应其键值进行CURD操作即便可。

Git基础知识讲解(一)

备注:本文内容来源于作者分享的PPT之“Finding Mr.Git”

一、 一览众山,随便看看

  1. 版本控制的那些事儿,简单谈谈历史痕迹

  2. Git是什么神器

  3. 文件级别的基本操作

  4. 分支级别基本操作

  5. 项目管理中Git的使用

  6. 托管商Github.com平台介绍

  7. 其他你们想问而我知道的

  8. 参考资料链接

二、 侬晓得使用Git经常说的一句话是什么吗? image

三、 那些年,版本控制的那些事儿

其实,可以简单归结成以下几幅图 image

四、 Git是领军人物 实现外观上,Git是这样子做到与众不同的 image

五、什么是Git

  1. 宏观上看,跟其他的VCS系统用途区别不大,都是代码仓库管理员,>=BAND4的
  2. 微观上看,具备如下特点: 1. 近乎所有操作都是本地执行
    1. 时刻保持数据完整性
    2. 多数操作仅添加数据
  3. Git仓库文件的三种状态:已提交,已修改,已暂存
  4. Git的三个工作区域,如下图:

image

六、 Git文件级别的基本操作

  • 初始化仓库

$ git init

  • 添加文件

$ git add *.php

  • 克隆分支

$ git clone git://github.com/schacon/grit.git

  • 检查当前检出分支的文件状态

$ git status

  • 忽略指定文件

$ cat .gitignore *.[oa] *~

  • 查看提交历史

$ git log –[option] XXX

  • 比较已暂存和上次提交快照间的区别

$ git diff –cached

  • 提交更新

$ git commit -m “XXX“

例外,暂存步骤可以跳过,如下

$ git commit -a -m "XXX“

  • 移除文件
$ rm grit.gemspec
$ git rm grit.gemspec
  • 移动文件

$ git mv file_from file_to

等同于

$ mv README.txt README
$ git rm README.txt
$ git add README
  • 撤销上一次提交并重新提交

$git commit –amend –m ‘XXX’

  • 取消已经暂存的文件

$git reset HEAD benchmarks.rb

  • 取消对文件的修改

$git checkout -- benchmarks.rb

  • 推送数据到Remotes

    $git push origin master

  • 从Remotes抓取数据

$git fetch [remote-name]
$git pull origin master

Clean Code: A Handbook of Agile Software Craftsmanship (b)

(1)The grand redesign in the sky

  • Eventually the team rebels and informs management that:
  • image
  • “If you have experienced even one small part of the story I just told, then you already know that spending time keeping your code clean is not just cost effective; it’s a matter of professional survival.”

(2)Attitude

  • We are deeply complicit in the planning of the project and share a great deal of the responsibility for any failures; especially if those failures have to do with bad code!
  • The manager will defend the schedule and requirements with passion. And you, it’s absolutely your job to defend the code with equal passion.
  • It’s unprofessional for programmers to bend to the will of managers who don’t understand the risks of making messes, too.

(3)Temporary conclusion

  • “You will not make the deadline by making the mess.”
  • The only right way to reach the goal—is to keep the code as clean as possible at all times.

(4)The art of clean code

  • “Being able to recognize good art from bad does not mean that we know how to paint.”
  • Writing clean code requires:
  • image

(5)What is clean code

  • Do you really know what is clean code?

  • And what do the very well-known and deeply experienced programmers think about it?

(6)Bjarne Stroustrup(Inventor of C++)

  • “I like my code to be elegant and efficient.”

  • “Clean code does one thing well.”

  • image

(7)Grady Booch(Author of Object Oriented Analysis and Design with Applications)

  • “Clean code is simple and direct.”

  • “Clean code reads like well-written prose.”

  • image

(8)Dave Thomas(”Founder of OTI, godfather of the Eclipse strategy”)

  • “Clean code can be read.”

  • “Clean code should be literate”

  • image

(9)Michael Feathers(”Author of Working Effectively with Legacy Code”)

  • “clean code always looks like it was written by someone who cares.”

  • image

(10)Ron Jeffries(“Author of Extreme Programming Installed”)

  • “Reduced duplication”

  • “High expressiveness”

  • “Early building of simple abstraction”

  • image

(11)Ward Cunningham(“Inventor of Wiki”)

  • “You know you are working on clean code when each routine you reads turns out to be pretty much what you expected.”

  • image

  • “You know you are working on clean code when each routine you reads turns out to be pretty much what you expected.”

(12)The boy scout rule

  • It’s not enough to write the code well. The code has to be kept clean over time.

  • image

(13)Bibliography

Clean Code: A Handbook of Agile Software Craftsmanship (a)

Wrote by Robert C. Martin

(1) Can you Spot the Differences?

image

(2) A tale of two apps below

  • One enormous class. => 1300 lines
  • 26 data members
  • 40 public methods named “setFlagsForLaterProcessing”
  • Long functions
  • Badly named variables
  • Tens of classes
  • Several packages
  • “Board”, “Game”, “Strategy” classes
  • Small functions
  • Comprehensive unit tests

(3) The User can’t tell

image

(4) Clean code == happy coding jobs

  • ”If by life you were deceived, Don’t be dismal, don’t be wild!” –Pushkin
  • If horrible code sucks the life out of you, what would you do?
  • image

(5) Remember it!

image

(6) Two reasons for you

image

(7) Foreword

image

(8) There will be code

image
“Nonsense! We will never be rid of code, because code represents the details of the requirements.”

(9) Bad code

  • Kent Beck’s book Implementation Patterns. He says, “…this book is based on a rather fragile premise: that good code matters….”
  • image
  • 1.“It was the bad code that brought the company down.”
  • 2.“Of course you have been impeded by bad code. So then—why did you write it?”
  • 3.“LeBlanc’s law: Later equals never.”

(10) The total cost of owning a mess

  • No change is trivial
  • The productivity of the team continues to decrease
  • Add more new staff to the project in hopes of increasing productivity
  • There is no way at all in the end
  • image

 

入门JAVA,你需要知道的一些简单的不能再简单了的知识点

编程离不开参考物,Java的强大正在于它的海纳百川

一、Java的参考文档形式:

  1. 原生HTML形式
  2. 排版CHM形式
  3. 其它…

二、从Java doc里看ooad世界

  1. 包-接口-类-异常-错误的阐述,应有尽有
  2. 类的方法和属性的说明和使用说明,让你一如既往都感觉是在书写hello world

三、package的分类

  1. java.*:基础语言包
  2. javax.*:jdk扩展包
  3. org.*:第三方功能包
  4. com.sun.*:sun公司提供的功能包,因其不具备兼容性,变更大,并不在std-api-doc内公开

四、java.lang包

  1. 默认自动引入
  2. 包含了Java语言所需要的基本功能类和接口的信息,是进行Java编程的基础
  3. Object类

    <1> Object类是Java语言的灵魂,所有的Java类(除了Object类)都是Object类的儿子,它的所有方法将出现在所有类的内部,这就是Java继承树的唯一根,这就是独具Java语言特色的单根继承体系

    <2> equals方法
    判断两个对象内容是否相同。Object.equals()实现如下:

    clip_image002

    <3> finalize方法 概念与构造函数相反,如同C++中的析构函数一般,该方法会被JVM自动调用执行垃圾回收。

    <4> hashcode方法 获取一个散列码数值,使用这个散列码可以快速判断对象是否不相同。 两个内容相同的对象,hashcode返回值必须相同,而对于两个不相同的对象,返回的hashcode也有可能相同。

    <5> toString方法
    显示对象内容时会被系统自动调用的方法

    clip_image004

  4. Math类 <1> 放置常用的数学常数和方法 <2> 类的常数和方法皆是静态类型的

  5. String和StringBuffer 都是代表任意多个字符串组成的序列,两者实现的区别,一般把String看成固定的字符串,而StringBuffer看成可变的字符串。并不是说前者不能变更,而是因为前者变更如同品牌笔记本维修主板一般,直接取新替换,浪费机器资源;而后者则是维修自身。 String类的声明

    clip_image005

    其实String类还有好多方法,例如charAt(),compareTo(),compareToIgnoreCase(),concat(),equals(),length()等等 StringBuffer类的声明

    clip_image006

    String与StringBuffer间的转换关系

    clip_image008

    诸如此类的方法多如绵绵春雨,还是等待大家慢慢深入研究吧:D

五、java.util包

  1. 时间日期类
    • Date类(deprecated)

    clip_image009

    以下是绝对时间与相对时间的互转 绝对时间->相对时间:

    getTime()

    相对时间->绝对时间:

    clip_image010

    • Calendar类(recommend)

    clip_image012

    要提一下的是,它家的add(Calendar.YEAR, 1)方法可以实现计算某字段之后添加或减少一定的数值

  2. 集成框架(CF)
    • List系列类:按索引值操作数据,允许存放重复元素
    • Set系列类:按索引值操作数据,不允许存放重复数据
    • Map系列类:按照名称操作数据,名称不允许重复,值可以重复,一个名称对应唯一的值 简而言之,CF类的复杂性在于它们使用了数据结构类型进行存储和实现,具备了该种数据结构的特点。 示例1:LIST

    clip_image013

    示例2:SET

    clip_image014

    示例3:MAP

    clip_image015

其实大家也都会发现,以上谈到的都只是些边边角角的JAVA最基本的知识。 所谓学海无涯苦作舟,大家就乘着风,扬起帆,继续前进吧。

UML基础建模知识小结(三)

3. UML行为建模图

3.1 用例图(Use Case Diagram)

(1)参与者(Actor)

在寻找用例图中的参与者时候,先要确定系统的边界,然后确定系统的涉众,从中确认出驱动用例的动作来源,从而确定参与者。参与者是位于边界之外的。参与者并非只能是人,也可以非人,因为有些需求是没有人参与的。如果没有人参与,这是就需要去找到指令或者计算机动作的来源。

(2)用例(Use Case)

判断用例是否准确的依据有:

  1. 用例是相对独立的:这意味着它不需要与其他用例交互而独自完成参与者的目的,即用例从功能上说是完备的。

  2. 用例的执行结果对参与者来说是可观测的和有意义的。 image image

  3. 用例必须由一个参与者发起,不存在没有参与者的用例,用例不应该自动启动,也不应该主动启动另一个用例。 image

  4. 用例必然是以动宾短语形式出现的。 image

  5. 参与者是用例驱动的来源,然而,用例的粒度大小则由需求的要求而定,最适合需求的用例和用例描述才是最适合的。 但是,容易步入一些分析中的误区。

    (1)目标和步骤的误区

    image

    应该以完整目标为用例,而不应该以步骤作为用例。

    (2)边界的超越

    参与者对于用例的操作都应该位于合法系统边界之内,不可以越界驱动用例。 image 右图中的“应征立减活动”用例并非属于“促销产品部管理员”职权范围之内,此处为越界的用例,是不正确的。

3.2 活动图

(1)用例活动图

用例活动图是最经常使用的。用例表达了参与者的一个目标,用例场景则描述了如何来达到这个目标。活动图用来描述用例场景,也就是通常所说的业务流程。

(2)对象活动图

详见图示与解说。

(3)泳道

详见图示与解说。 下图是根据业务场景所进行的建模。 image

3.3 时序图

image

3.4 状态图

状态图显示一个状态机,状态机主要用于描述对象的状态变化以确定何种行为改变了对象状态,以及对象状态变化对系统的影响。通常,状态机用于描述实体类对象的整个生命周期内的状态变迁以获得对这个实体对象的理解,同时获得系统和实体对象互相影响的关系。

需要注意的是,状态图通常只用于描述单个对象的行为,如果要描述对象间的交互,最好采用时序图或者协作图。

状态图包含的元素有:

  1. 初始状态

  2. 状态:

包含entry/入口行为,do/执行的行为,event/事件-事件行为,exit/退出行为。

  1. 复合状态

  2. 转移

  3. 事件

  4. 条件:常常是一个布尔表达式

  5. 最终状态

如下例子详述: image

当然,UML的知识不止于以上所述,正所谓“路漫漫其修远兮,吾将上下而求索,不断努力地去求索…” Thank you very much!

UML基础建模知识小结(二)

二、UML结构建模图

结构图定义了一个模型的静态架构,例如类,对象,接口和物理组件。另,结构图也被用作对元素间关联和依赖关系进行建模。

2.1 包

包常用来在较高的层次描述着类或用例之间的交互关系,同时将模型根据不同的思维逻辑,划分成为了不同的容器。 image 上图包含了关于包的合并,包的导入,嵌套连接符。关于每个包内的元素要求,尽量要做到,同一个包中,赋予这些类或引入包相同的继承层次,通常认为把通过符合相关联的类、以及与它们相协作的类放在同一个包内。

2.2 类与类图

“类”是对一组具有相同属性、操作、关系和语意的对象的描述,在图形上常常把它画为一个矩形。如下图: image

以上的图属于类图类别中的“设计类”,而还有一个不为人知的“分析类”并未包含阐述,如下: image

分析类主要应用于Conceptual Modeling阶段,旨在RUP用例驱动的过程中进一步将需求以更为贴近开发的角度去描述。

“分析类”是从业务需求向系统设计转化的过程中最为主要的元素,它们在高层次抽象出系统实现业务需求的原型,业务需求通过分析类被逻辑化,成为可以被计算机可以理解的语意。 分析类的抽象层次具有三高的特点:

(1)高于设计实现:专注解决需求问题,避免实现问题的干扰

(2)高于语言实现:不依赖于任一语言的限制

(3)高于实现方式:不需提前考虑实现的方式

2.3 关系

(1)关联(Association) image 可以使用“知道”二字描述两者间的关系。 在很多的画图中并不需要特意地强调关联关系的方向性,而Rose中提供了额外的带方向的关联关系画图方法。

(2)依赖(Dependency)

image 可以使用“知道并使用”描述两者间的关系。 依赖关系统统都是带箭头的,在OOD中,双向依赖是一种非常不好的结构,我们在设计的时候总应该保持单向依赖,杜绝双向依赖关系的发生。

(3)包含(Include)与扩展(Extends)

image 包含关系用来把一个较复杂的用例所表示的功能分解成较小的步骤。(箭头指向分解出来的功能子用例) 扩展关系是指用例功能的延伸,相当于为基础用例提供一个附加功能。(箭头指向基础用例)

(4)泛化(Generalization)

泛化关系是程序中继承关系的UML表示法。 image 参见程序设计中的继承关系的详细阐述。

(5)实现(Realize)

实现关系特别用于在用例模型中连接用例和用例实现,说明基本用例的一个实现方式。开发中常见的如类与接口的关系,表示类是接口所有特征和行为的实现。 image

(6)聚合(Aggregation)与组合(Composition)

聚合关系用于类图,特别用于表示实体对象之间的关系,表达整体由部分构成的语义。 组合关系用于类图,用于表示实体对象的关系,表达整体拥有部分的语意。 组合关系是一种强依赖的特殊聚合关系,如果整体不存在了,则部分也等同于消亡。而与组合关系不同的是,聚合关系的整体和部分不是强依赖的,即使整体不复存在了,部分仍然存在。 image

(7)精化(Refine)

精化关系用于用例模型,一个基本用力可以分解出许多更小的关键精化用例。这些更小的精化用例更细致地展示了基本用例的核心业务。

与泛化关系不同的是,精化关系表示由基本对象可以分解为更明确的、更为精细的子对象,这些子对象并没有增加、减少、改变基本对象的行为和属性,仅仅是更加细致和明确化了。在泛化关系中,基本对象被泛化成为子对象之后,子对象继承了基本对象的所有特征,并且子对象可以增加、改变基本对象的行为和属性。另一方面,精化关系仅用于建模阶段,在实现语言中是没有精化这一概念的。 image

(总结)关系的强弱顺序 泛化==实现>组合>聚合>关联>依赖

UML基础建模知识小结(一)

问:为什么我们需要使用UML建模?
答:UML英文全称“Unified Modeling Language”,即统一建模语言。

在软件需求和设计阶段,使用UML建模,可以使得不同领域的开发者都可以了解到你的软件需求和架构是怎样子的,UML即采用了图形化的方式来定义语言,让人和机器都可以读得懂你在写些什么。

使用UML可以将现实世界的需求抽象成业务模型,进而转化成概念模型、设计模型,最后通过程序员的双手,将模型转变成工件(或者交付物),最后投入使用。

严格来说,UML只是一种语言,其定义了基本元素,定义了语法,但是在执行一个软件项目的过程中,还需要方法的引导。RUP(Rational Unified Process)自然就是当前和UML集成和应用最好的,最完整的软件方法。

到了此时此刻,可能都会认为,学习UML就必须先学习RUP统一过程,其实不然。RUP和UML本就是两块不同的研究领域。

UML是一种语言,重在如何描述在软件生产过程中要产生的文档,统一过程则是知道如何产生这些文档,以及这些文档要讲述些什么的方法。

自然,如果要更加深入的研究UML为何在软件开发过程中起着如此重要的作用,建议先找些关于软件过程的书籍去了解整个软件开发过程。在了解了各个不可分割的软件迭代过程之后,想必肯定会收获不少。

代码规范之驼峰vs下划线风格转换方案

转换书写风格的方案均基于递归深度遍历,对key值重新建构,将value值重新赋值。

让我们一起来看看以下详细代码实现。

  1. 下划线风格=>驼峰风格
<?php
/**
 * @static 数组键名翻译成驼峰写法
 * @param $in 源数组
 * @return array 输出数组
 */
public static function arrKeysToCamelCase($in)
{
    if(empty($in)||!is_array($in))
        return $in;
    $reCopyRes = array();
    foreach($in as $key=>$val)
    {
        $reKey = self::ucFirstWord($key);
        if(!is_array($val)){
            $reCopyRes[$reKey] = $val;
        }else{
            $reCopyRes[$reKey] = self::arrKeysToCamelCase($val);
        }
    }
    return $reCopyRes;
}

/**
 * @static 将单词的首字母转换成大写(首单词除外)
 * @param $word 源单词字符串
 * @return string 输出单词字符串
 */
public static function ucFirstWord($word)
{
    if(!is_string($word)){
        return $word;
    }else{
        $wordArr = explode('_',$word);
        if(!empty($wordArr)){
            $index = 0;
            foreach($wordArr as &$wd)
            {
                if($index==0){
                    $index++;
                    continue;
                }
                $wd = ucfirst($wd);
            }
            $outStr = implode('',$wordArr);
            return $outStr;
        }else{
            return $word;
        }
    }
}
  1. 驼峰风格=>下划线风格
<?php
/**
 * @static 数组键名翻译成下划线写法
 * @param $in 源数组
 * @return array 输出数组
 */
public static function arrKeysToUnderlineCase($in)
{
    if(empty($in)||!is_array($in))
        return $in;
    $reCopyRes = array();
    foreach($in as $key=>$val)
    {
        $reKey = self::lcFirstWord($key);
        if(!is_array($val)){
            $reCopyRes[$reKey] = $val;
        }else{
            $reCopyRes[$reKey] = self::arrKeysToUnderlineCase($val);
        }
    }
    return $reCopyRes;
}

/**
 * @static 将单词的首字母转换成小写
 * @param $word 源单词字符串
 * @return string 输出单词字符串
 */
public static function lcFirstWord($word)
{
    if(!is_string($word)){
        return $word;
    }else{
        preg_match_all('/([a-z0-9_]*)([A-Z][a-z0-9_]*)?/',$word,EG_PATTERN_ORDER);
        if(!empty($matches)){
            $strPattern1 = !empty($matches[1][0])?trim($matches[1][0]):'';
            $subMatch = array_filter($matches[2]);
            $strPattern2 = !empty($subMatch)?trim(implode('_',$subMatch)):'';
            $strPattern2 = !empty($strPattern2) && !empty($strPattern1)?'_'.2:$strPattern2;
            $outStr = strtolower($strPattern1.$strPattern2);
        }else{
            $outStr = $word;
        }
        return $outStr;
    }
}

关于Yii路由大小写敏感的解决方案

示例:默认情况下,以下路由

URI-1: http://www.w3c.com/StoreHouse/show

URI-2: http://www.w3c.com/storehouse/show

在windows开发环境下没有区别,而源码部署到linux/unix服务器环境下之时,倘若该控制器的路径是root/project/StoreHouseController.php中的actionShow(),则URI-1可正常访问,URI-2访问出现http-404异常。

究其原因:

在yii框架的framework/web/CUrlManager.php中定义了开放属性

<?php
/**
 * @var boolean whether routes are case-sensitive. Defaults to true. By setting this to false,
 * the route in the incoming request will be turned to lower case first before further processing.
 * As a result, you should follow the convention that you use lower case when specifying
 * controller mapping ({@link CWebApplication::controllerMap}) and action mapping
 * ({@link CController::actions}). Also, the directory names for organizing controllers should
 * be in lower case.
 */
public $caseSensitive=true;

默认true值表示路由地址大小写敏感,false值表示路由不区分大小写。

据Yii官方参考描述:

Note: By default, routes are case-sensitive. It is possible to make routes case-insensitive by settingCUrlManager::caseSensitive to false in the application configuration. When in case-insensitive mode, make sure you follow the convention that directories containing controller class files are in lowercase, and both controller map and action map have lowercase keys.

当在大小写不敏感模式中时,要确保你遵循了相应的规则约定,即:包含控制器类文件的目录名小写,且控制器映射和动作映射 中使用的键为小写。

所以,除了配置main.php中的组件配置项urlManage属性caseSensitive=true以外,还应该确保以下几点:

  1. 控制器所在目录名称为小写;

  2. 控制器文件名和类名仅限于形如“StorehouseController.php”,杜绝“StoreHouseController.php” ;

  3. 如若控制器文件上层目录结构中有modules,则所属的模块目录名称也需要为小写,模块入口文件XyzModule.php遵循第2条约定。

出现以上的约束情况的原因如下:

在yii框架中framework/web/CWebApplication.php之方法createController()创建控制器方法中,

<?php
if(!$caseSensitive)
   $id=strtolower($id); //若然等于false,则控制器ID转换成小写形式

首先会读取配置信息,其中包含检查了caseSensitive属性,若然等于false,则控制器ID即转换成全小写形式。

继而,每当创建控制器方法被调用的同时,控制器的首字母皆转换成大写形式。

<?php
//每当创建控制器实例,即时将控制器ID首字母转为大写形式
$className=ucfirst($id).'Controller';

如此,便造成了以上异常现象。