www.blogways.net
2021-08-28T07:26:41+00:00
http://www.blogways.net/
www.blogways.net
tangzhi@asiainfo-linkage
学习简记1
2021-08-27T00:00:00+00:00
http://www.blogways.net/blog/2021/08/27/学习简记2
<h1 id="java基础">Java基础</h1>
<p>##一、java的基础语法
一个java程序可以看做一系列对象的集合,对象则通过调用彼此的方法协同工作。一些基础概念:
对象:对象是类的一个实例,有状态和行为。类:类是一类模板,它描述一类对象的状态和行为。方法:方法就是行为,一个类可以有很多种方法。各种逻辑运算等的动作都是在方法中完成的。实例变量:每个对象都有自己独特的实例变量,对象的状态由实例变量的值决定。</p>
<h3 id="1基本语法">1、基本语法</h3>
<p>编写java时,有几点需要注意:
①、Java对大小写敏感,意味着“Hello”和“hello”两个标识符不同。
②、类名首字母应大写,若类名由若干单词组成,则每个单词首字母大写。
③、方法名都应以小写字母开头,若由多个单词组成,则每个单词首字母大写。
④、源文件名必须和类名相同,保存文件时,应该使用类名作为文件名保存。文件名后缀.java。
⑤、主方法入口特定,即所有的Java程序由 public static void main(String[] args)方法开始执行。</p>
<h3 id="2标识符">2、标识符</h3>
<p>java的所有组成部分都需要名字,类名、变量名和方法名统称为标识符。注意:
①、所有标识符都应该以字母,$或者_开始。
②、首字符之后可以是以上三种和数字共四种字符的任意组合。
③、关键字不能作为标识符。
④、标识符同样大小写敏感。</p>
<h3 id="3修饰符">3、修饰符</h3>
<p>java和其他语言一样可以用修饰符修饰类中的方法和属性。主要有两种:
①、访问控制修饰符:default,public,protected,private。
②、非访问控制修饰符:final,abstract,static,synchronized。</p>
<h3 id="4变量">4、变量</h3>
<p>主要有以下几种类型的变量:局部变量、类变量(静态变量)、成员变量(非静态变量)。</p>
<h3 id="5数组">5、数组</h3>
<p>数组是储存在堆上的对象,可以保存多个同类型变量。</p>
<h3 id="6枚举">6、枚举</h3>
<p>枚举限制了变量,使其只能是预先设定好的值的某种或多种,比如一个枚举型预先设定值可以是1,2,3,那么变量就只能是这三者中的一个或多个。</p>
<h3 id="7关键字">7、关键字</h3>
<p>关键字有很多,主要分为以下几种类别:访问控制,类、方法和变量修饰符,程序控制语句,错误处理,包相关,基本类型,变量引用,保留关键字这些种类。</p>
<h3 id="8注释">8、注释</h3>
<p>java中的注释格式和c++类似。/<em>、/</em> */、//三种。</p>
<h3 id="9继承">9、继承</h3>
<p>java中,一个类可以由其他类派生,如果要创建一个类,而且已经存在一个类具有你所需要的属性或方法,那么可以将新创建的类继承该类。利用继承的方法,可以重用已存在类的方法和属性,而不用重写代码。被继承的类成为超类,派生类成为子类。</p>
<h3 id="10接口">10、接口</h3>
<p>java中,接口可以理解为对象间相互通信的协议,接口在继承中作用比较重要。接口只定义派生要用到的方法,但方法的具体实现完全取决于派生类。</p>
<h2 id="二java对象和类">二、java对象和类</h2>
<p>java是面向对象语言,它支持以下的基本概念:多态、继承、封装、抽象、类、对象、实例、方法、重载。首先重点研究对象和类的概念。
举例:男孩、女孩各作为一个类存在,而具体到每个人则是类的对象;汽车作为一个类,而每辆汽车作为汽车类的对象。</p>
<h3 id="1对象">1、对象</h3>
<p>软件对象有状态和行为,状态就是属性,行为通过方法体现。方法操作对象内部状态的改变,对象的相互调用也通过方法实现。</p>
<h3 id="2类">2、类</h3>
<p>类可以近似看作创建java对象的模板。一个类可以包含以下类型的变量:
①、局部变量:在方法、构造方法或者语句块中定义的变量被称为局部变量。变量声明和初始化都在方法中,方法结束后,变量自动销毁。
②、成员变量:定义在类中,方法体之外。在创建对象的时候实例化。成员变量可以被类中方法、构造方法和特定类的语句块访问。
③、类变量:类变量声明在类中,方法体之外,但必须为static型。
一个类可以有多种方法。</p>
<h3 id="3构造方法">3、构造方法</h3>
<p>对象的创建通过构造方法来完成的,是定义在java类中的一个用来初始化对象的方法,用new+构造方法,创建一个新的对象,并可以给对象中的实例进行赋值。每个类都有构造方法,若没有显式地为类定义构造方法,java编辑器将会为该类提供一个默认构造方法。创建一个对象的时候,至少调用一个构造方法。构造方法名称必须与类同名,一个类可以有多个构造方法。</p>
<h3 id="4创建对象">4、创建对象</h3>
<p>对象根据类创建,在java中,使用关键字new创建新对象。创建步骤:
①、声明:声明一个对象,包括对象名称和对象类型。
②、实例化:使用关键字new创建一个对象。
③、初始化:使用new创建对象时,会调用构造方法初始化对象。</p>
<h3 id="5访问实例变量和方法">5、访问实例变量和方法</h3>
<p>通过已创建的对象来访问成员变量和成员方法。</p>
<h3 id="6实例">6、实例</h3>
<p>通过一个完整的实例,展示如何访问实例变量和调用成员方法:
public class Puppy{
int puppyAge;
public Puppy(String name){
// 这个构造方法仅有一个参数:name
System.out.println(“小狗的名字是 : “ + name );
}</p>
<p>public void setAge( int age ){
puppyAge = age;
}</p>
<p>public int getAge( ){
System.out.println(“小狗的年龄为 : “ + puppyAge );
return puppyAge;
}</p>
<p>public static void main(String[] args){
/* 创建对象 <em>/
Puppy myPuppy = new Puppy( “tommy” );
/</em> 通过方法来设定age <em>/
myPuppy.setAge( 2 );
/</em> 调用另一个方法获取age <em>/
myPuppy.getAge( );
/</em>你也可以像下面这样访问成员变量 */
System.out.println(“变量值 : “ + myPuppy.puppyAge );
}
}
可以看出构造的过程。</p>
<h3 id="7源文件声明规则">7、源文件声明规则</h3>
<p>一个源文件中有多个定义的类,并且还有import语句和package语句时,需特别注意:
①、一个源文件中只能有一个public类;
②、一个源文件可以有多个非public类;
③、源文件的名称应该和public类的类名保持一致;
④、一个类定义再包中,package语句则应该在源文件首行;
⑤、若源文件含有import语句,则应该放在package语句和类定义之前,若没有,则放在源文件最前面;
⑥、import语句和package语句对源文件中定义的所有类都有效。同一源文件中,不可给不同的类不同的包声明。
类本身有数种访问级别,类型也不同。</p>
<h3 id="8java包">8、java包</h3>
<p>包主要用来对类和接口进行分类。</p>
<h3 id="9import语句">9、import语句</h3>
<p>java中,如果给出一个完整的限定名,比如包名、类名,那么编译器就可以很容易地定位到源代码或者类。import语句就用来提供一个合理的路径,使编译器可以找到某个类。</p>
学习简记1
2021-08-25T00:00:00+00:00
http://www.blogways.net/blog/2021/08/25/学习简记1
<p>—<br />
layout: post<br />
title: 学习简记1<br />
category: 学习简记1<br />
tags: 学习简记1<br />
author: 关云泽<br />
email: gyz18643266515@163.com<br />
description: 学习简记1<br />
—<br />
<br /></p>
<h1 id="了解熟悉基本的数据库和相对简单的数据表操作后数据表和记录的增删查改操作">了解熟悉基本的数据库和相对简单的数据表操作后,数据表和记录的增删查改操作:<br /></h1>
<h2 id="一增">一、增<br /></h2>
<p>INSERT插入语句是主要的记录增加语句,基本语法:<br />
{insert into 表名 (字段1, 字段2, …) values (值1, 值2, …)}<br />
可见insert语句也可以一次插入多条记录,只需要字段对应多条即可。注意,记录的字段部分如果碰巧有关键字或保留字,需要在字段部分加英文的反引号“`”,否则报错。<br />
对于数据表的字段增加,使用<br />
{alter table 表名 add 字段名 类型 first|after} 语句。<br /></p>
<h2 id="二删">二、删<br /></h2>
<p>{drop table 表名}为删除数据表的语句。<br />
DELETE语句为数据表中记录的删除语句,格式<br />
{delete from 表名 where and|or|not 条件},and或者or或者not的选择可理解为与或非的区别。<br /></p>
<h2 id="三查">三、查<br /></h2>
<p>对于数据表,基本的查询语句就是SELECT语句,语法:<br />
{select * from 表名},其中“*”实际意义表示查询所有列。对于select语句,查询的类型有很多种,除了基本查询,还有条件查询,投影查询,分页查询,聚合查询,多表查询,连接查询,模糊查询,排序,分组等。<br /></p>
<h3 id="1">1、<br /></h3>
<p>基本语句:{select 列 from 表where}(条件),<br /></p>
<h3 id="2">2、<br /></h3>
<p>limit offset(分页查询):{select 列 from 表名 limit 条件1 offset 条件2},<br /></p>
<h3 id="3">3、<br /></h3>
<p>order by(排序):{select 列 from 表名 order by 条件},<br /></p>
<h3 id="4">4、<br /></h3>
<p>like(模糊查询):{select列 from 表名 where 列 like 条件 },注意like子句中可有%,代表任意字符,比如需要搜索含0的数据,那么可以用%0代表。<br /></p>
<h3 id="5">5、<br /></h3>
<p>union(联合查询):{select 列 from 表名 where 条件 union all|distinct select 列 from 表名 where 条件}可见,用于将两个以上的select语句结果组合到一个结果的集合中。distinct和all是可选项,用于删除结果中的重复数据/返回所有结果数据。<br /></p>
<h3 id="6">6、<br /></h3>
<p>函数有count()(聚合查询)<br /></p>
<h3 id="7">7、<br /></h3>
<p>对于投影查询和多表查询,使用select语句的特殊格式:<br />
{select 列 from …}和{select * from 表1, 表2…}<br /></p>
<h3 id="8">8、<br /></h3>
<p>group by(分组){select * from 表名 group by条件},可以根据条件分组。<br /></p>
<h3 id="9">9、<br /></h3>
<p>join(连接查询):join连接有多种类型,大致分为inner join,left join,right join,outer join。<br /><br /><br />
inner join:获取两个表中字段匹配关系的记录。<br /><br />
left join:获取左表所有记录,即使右表无对应的匹配记录,即两个表的交集外加左表剩下的数据。<br /><br />
right join:获取右表所有记录,即使左表没有对应匹配的记录,即两个表的交集外加右表剩下的数据。<br />
outer join:实际上是求两个集合的并集。<br /></p>
<h2 id="四改">四、改<br /></h2>
<p>数据表记录的修改部分主要是INSERT,UPDATE和DELETE三个语句。INSERT已经放在增部分,DELETE放在删的部分,UPDATE<br />
数据表本身的表名修改用<br />
{alter table 旧表名 to 新表名}这个语句,字段修改则使用<br />
{alter table 表名 modify 字段新属性}。<br /></p>
<h1 id="一些重要的点">一些重要的点<br /></h1>
<h2 id="五mysql-null值的处理">五、MySQL NULL值的处理<br /></h2>
<p>在使用select查询的字段为NULL时,该命令很可能无法正常工作,这时就需要使用{IS NULL}、{IS NOT NULL}{<=>}三种运算符。这的意思是说,查询时不可以使用类似“=NULL”或者“!=NULL”的运算符,而是用“IS NULL”或“IS NOT NULL”代替。 另外,NULL值与任何其他的值比较,无论大小,结果都返回NULL。<br /></p>
<h2 id="一正则表达式">一、正则表达式<br /></h2>
<p>除了可以使用“like…%”进行模糊的查询,还可以使用正则表达式进行类似的模糊查询。MySQL使用REGEXP操作符进行正则表达式的匹配,使用方法:{SELECT 列 FROM 表名 WHERE 列 REGEXP 正则模式}。常用正则模式有这些:<br />
^:匹配输入字符串的开始位置。<br />
$:匹配输入字符串的结束位置。<br />
.:匹配除“\n”之外的任何单个字符。<br />
[…]:字符集合,匹配“[]”中包含的任意字符,“[]”不用打出来。<br />
[^…]:负值字符集合,匹配未包含的任意字符。<br />
p1|p2|p3:匹配p1或p2或p3。<br />
*:匹配前面的子表达式零或多次。<br />
+:匹配前面的子表达式一次或多次。<br />
{n}:匹配非负整数n次,n值确定。<br />
{n,m}:m、n均非负整数,n<=m。最少匹配n且最多匹配m次。<br />
使用REGEXP时,注意后面的条件需要加’’号。<br /></p>
<h2 id="二mysql事务">二、MySQL事务<br /></h2>
<p>主要用来处理操作量大、复杂度高的数据。一般情况下,事务满足4个条件:不可分割性、一致性、独立性、持久性。只有使用了Innodb数据库引擎的数据库或表才支持事务。事务的控制语句:<br />
BEGIN或START TRANSACTION:显式地开启事务。<br />
COMMIT或COMMIT WORK:提交事务,并使已对数据库进行的所有修改成为永久性的。<br />
ROLLBACK或ROLLBACKWORK:回滚,结束用户的事务,并撤销正在进行的所有未提交的修改。<br />
SAVEPOINT identifier:在事务中创造一个保存节点,一个事务中与许多个。<br />
RELEASE SAVEPOINT identifier:删除事务保存点,若无指定保存点则返回一个异常。<br />
RLOOBACK TO identifier:将事务回滚到标记点。<br />
SET TRANSACTION:设置事务的隔离级别。有四种隔离级别。<br /></p>
<h2 id="三mysql-alter命令">三、MySQL ALTER命令<br /></h2>
<p>在之前的增删查改总结中已经出现过ALTER命令的身影。实际上,ALTER命令可以用来修改数据表名或者修改数据表字段。添加/删除字段:{alter table 表名 add/drop 字段名}<br />
修改字段类型及名称:{ALTER TABLE 表名 MODIFY/CHANGE 字段 类型};<br />
修改表名:{ ALTER TABLE 表名 RENAME TO 新表名}<br /></p>
<h2 id="四mysql索引">四、MySQL索引<br /></h2>
<p>索引系统可以大大提高MySQL的检索速度,包含单列索引和组合索引。单列索引是一个索引只包含一个列,组合索引包含多个列。索引需要注意使用的必要性,因为索引会建立单独的索引文件,从而造成磁盘空间的更大占用。<br />
索引创建:{CREATE INDEX 索引名 ON 表名 (列名)}<br />
添加索引(修改表结构):{ALTER table 表名 ADD INDEX 索引名(列名)}<br />
删除索引:{DROP INDEX [索引名] ON 表名}<br /></p>
<h2 id="五mysql临时表">五、MySQL临时表<br /></h2>
<p>临时表只在当前连接可见,关闭连接时自动删除并释放所有空间。<br /></p>
<h2 id="六mysql复制表">六、MySQL复制表<br /></h2>
<p>如果想完全复制一个完整的表,只使用create table…select命令是不可行的。具体的操作步骤是这样的:<br />
1、使用 SHOW CREATE TABLE 命令获取创建数据表(CREATE TABLE) 语句,该语句包含了原数据表的结构,索引等。<br />
2、复制以下命令显示的SQL语句,修改数据表名,并执行SQL语句,通过以上命令 将完全的复制数据表结构。<br />
3、如果你想复制表的内容,你就可以使用 INSERT INTO … SELECT 语句来实现。<br /></p>
<h2 id="七mysql序列">七、MySQL序列<br /></h2>
<p>MySQL的序列是一组整数。一张数据表只能有一个字段自增主键,要想实现其他字段也自动增加,就需要用到序列。最简单的就是使用AUTO_INCREMENT来定义序列。<br /></p>
<h2 id="八mysql重复数据">八、MySQL重复数据<br /></h2>
<p>重复记录的出现是很常见的,有时我们需要对重复数据进行各种操作。<br />
统计重复数据:确定哪一列包含的值可能重复;在列选择列表使用COUNT(*)列出这些列;GROUP BY子句中列出的列;HAVING子句设置重复数大于1。<br />
过滤重复数据:SELECT语句中使用DISTINCT关键字。<br />
删除重复数据:可以接连使用以下语句:{CREATE TABLE tmp SELECT 列名 FROM 表名 GROUP BY (列名);DROP TABLE 表名;ALTER TABLE tmp RENAME TO 表名}。<br /></p>
<h2 id="九mysql导入导出数据">九、MySQL导入、导出数据<br /></h2>
<p>命令提示符中导入数据的命令:{mysql -uroot -p密码 < 要导入的数据库文件}<br />
数据库中导入命令:{source 详细路径/文件名}<br />
从当前目录中读取文件并将文件的数据插入到当前数据库的数据表中:{LOAD DATA LOCAL INFILE ‘文件名.格式’ INTO TABLE 表名}<br />
也可以使用mysqlimport导入数据。<br />
至于数据导出,可以使用select语句:{select * from 表名 into outfile ‘路径/文件’}。<br /></p>
限流方案
2019-06-30T00:00:00+00:00
http://www.blogways.net/blog/2019/06/30/限流方案
<h1 id="限流方案">限流方案</h1>
<h2 id="1限流方案简介">1限流方案简介</h2>
<p>每个系统都有服务的上线,所以当流量超过服务极限能力时,系统可能会出现卡死、崩溃的情况,所以就有了降级和限流。
限流其实就是:当高并发或者瞬时高并发时,为了保证系统的稳定性、可用性,系统以牺牲部分请求为代价或者延迟处理
请求为代价,保证系统整体服务可用。限流主要限制请求流量,保证当前服务、依赖服务不会被大流量彻底压死。
举个例子:电商网站大促期间,如果服务能力实在有限,可以对社区功能进行降级,全力保促销、交易流程;如果交易系统
还是无法承担流量的压力,通过限制并发可以让部分用户无法下单,因为部分人不能用总比让整个网站挂掉好。
在自动化规则模块运行时遇到这样一个问题:邮件、短信接口在短时间内被多次调用导致邮件发送失败。此时就需要对邮件、
短信工单进行限流处理。</p>
<h2 id="2三种限流方案介绍">2三种限流方案介绍</h2>
<p>目前有几种常见的限流方式:
1.通过限制单位时间段内调用量来限流
2.使用漏桶(Leaky Bucket)算法来进行限流
3.使用令牌桶(Token Bucket)算法来进行限流</p>
<h2 id="21滑动窗口算法">2.1滑动窗口算法</h2>
<p>通过限制单位时间段内调用量来限流。它是限流算法中最简单最容易的一种算法,比如我们要求某一个接口,1分钟内的请求不能超过10次,我们可以在开始时设置
一个计数器,每次请求,该计数器+1;如果该计数器的值大于10并且与第一次请求的时间间隔在1分钟内,那么说明请求过多;如
果该请求与第一次请求的时间间隔大于1分钟,并且该计数器的值还在限流范围内,那么重置该计数器。
自动化工单引擎采用了这种限流方式。由于自动化模块是分布式的环境,分布式限流最关键的是要将限流服务做成原子化,所
以计数器、时间等值存在redis中。
<img src="/images/wuyatong/limit.png" alt="" /></p>
<h2 id="21使用漏桶leaky-bucket">2.1使用漏桶(Leaky Bucket)</h2>
<p>这个算法很简单。首先,我们有一个固定容量的桶,有水进来,也有水出去。对于流进来的水,我们无法预计共有多少水流进
来,也无法预计流水速度,但对流出去的水来说,这个桶可以固定水流的速率,而且当桶满的时候,多余的水会溢出来。
<img src="/images/wuyatong/bucket.png" alt="" /></p>
<h2 id="21使用令牌桶token-bucket">2.1使用令牌桶(Token Bucket)</h2>
<p>桶一开始是空的,token以固定的速率r往桶里面填充,直到达到桶的容量,多余的token会被丢弃。每当一个请求过来时,就会
尝试着移除一个token,如果没有token,请求无法通过。
<img src="/images/wuyatong/tokenbucket.png" alt="" /></p>
自动化测试之Cucumber
2019-05-09T00:00:00+00:00
http://www.blogways.net/blog/2019/05/09/自动化测试之cucumber(一)
<h1 id="一简介">一、简介</h1>
<p>cucumber是BDD(Behavior-driven development,行为驱动开发)的一个自动化测试的副产品。它使用自然语言来描述测试,使得非程序员可以理解他们。Gherkin是这种自然语言测试的简单语法,而Cucumber是可以执行它们的工具。关于BDD有兴趣自行了解。附cucumber<a href="https://cucumber.io/" title="cucumber官网">官网链接</a>,里面也有关于BDD的信息。
cucumber本质上是使用根据正则表达式匹配自然语言,然后依次执行对应的方法,以达到测试的目的。</p>
<h1 id="二安装">二、安装</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cucumber支持JDK8,创建一个mvn工程,在pom.xml文件引入以下依赖即可。
另外如果测试框架采用的是junit,则需要多一个cucumber-junit。
如果集成spring,则还需要引入cucumber-spring ```
</code></pre></div></div>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-java</artifactId>
<version>1.2.4</version>
<scope>test</scope>
</dependency>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-junit</artifactId>
<version>1.2.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>info.cukes</groupId>
<artifactId>cucumber-spring</artifactId>
<version>1.2.4</version>
<scope>test</scope>
</dependency> ```
也可以根据mvn骨架创建cucumber项目。打开终端,转到要创建项目的目录(比如本文是hellocucumber),运行以下命令 ```
mvn archetype:generate -DarchetypeGroupId=io.cucumber -DarchetypeArtifactId=cucumber-archetype -DarchetypeVersion=2.3.1.2 -DgroupId=hellocucumber -DartifactId=hellocucumber -Dpackage=hellocucumber -Dversion=1.0.0-SNAPSHOT -DinteractiveMode=false ```
应该得到如下结果:
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [INFO] Project created from Archetype in dir: hellocucumber/cucumber
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>但是不支持jdk8 U51之后的新版本!每次都报错说"Wrong type at constant pool index"。
</code></pre></div></div>
<h1 id="三基本语法">三、基本语法</h1>
<h2 id="31-gherkin">3.1 Gherkin</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Cucumber 执行 .feature 文件,而这些文件包含可执行的规范,用被称为 Gherkin 的语言写成。.featur文件一般放在src/test/resources/[项目名称xxx]目录下或者其子目录下。
Gherkin 是带有一点额外结构的纯文本的英文(或者替他60多种语言). Gherkin 设计为容易被非编程人员学习,但有足够的组织结构来容许简洁的范例描述,以说明大多数实际领域中的业务规则。
下面是一个简单的.feature 文件的例子 ```
Feature: Refund item
Scenario: Jeff returns a faulty microwave
Given Jeff has bought a microwave for $100
And he has a receipt
When he returns the microwave
Then Jeff should be refunded $100 ```
在 Gherkin 中, 每行必须以 Gherkin 关键字开头, 然后跟随有任意的文本。主要的关键字有:
</code></pre></div></div>
<ul>
<li>Feature / 特性</li>
<li>Scenario / 场景</li>
<li>Given, When, Then, And, But (Steps/步骤)</li>
<li>Background / 背景</li>
<li>Scenario Outline / 场景大纲</li>
<li>
<p>Examples / 示例</p>
<p>还有其他一些额外的关键字:</p>
</li>
<li>””” (Doc Strings)</li>
<li>
<table>
<tbody>
<tr>
<td>(Data Tables)</td>
</tr>
</tbody>
</table>
</li>
<li>@ (Tags)</li>
<li># Comments</li>
</ul>
<hr />
<h2 id="32-feature--特性">3.2 Feature / 特性</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.feature 文件用来描述系统的一个单一特性,或者某个特性的一个独特方面。这仅仅是一个提供软件特性的高级描述的方法,并用于组织相关的场景(scenarios)。
feature有三个基本元素:
1.Feature: 关键字
2.name: 名称, 在同一行
3.description:描述, 可选(但是强烈推荐),可以占据多行
例如:
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Feature: Refund item
Sales assistants should be able to refund customers' purchases.
This is required by the law, and is also essential in order to
keep customers happy.
Rules:
- Customer must present proof of purchase
- Purchase must be less than 30 days ago
</code></pre></div></div>
<hr />
<h2 id="33-scenario--场景">3.3 Scenario / 场景</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Scenario 是具体的实例,描述一个业务规则。它由步骤列表组成。
可以有任意多个步骤,但是推荐数量保持在每个场景3-5个步骤。如果太长,他们将丧失作为规范和文档的表单能力。
在作为规范和文档之外,场景也同样是测试。作为一个整体,场景是系统的可执行规范。
场景遵循同样的模式:
1、 描述一个初始化上下文
2、 描述一个时间
3、 描述一个期望的产出
</code></pre></div></div>
<hr />
<h2 id="34-steps--步骤">3.4 Steps / 步骤</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>步骤通常以 Given, When 或 Then 开头。如果有多个 Given 或者 When 步骤连在一
起,可以使用 And 或者 But。Cucumber不区分这些关键字,但是选择正确的关键字对于
场景整体的可读性很重要。
</code></pre></div></div>
<hr />
<h2 id="35-given--假设">3.5 Given / 假设</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Given 步骤用于描述系统的初始化上下文 - 场景的一幕(scene of Scenario)。它通常是某些已经发生在过去的东西。
当cucumber执行 Given 步骤时,它将配置系统到一个定义良好的状态,例如创建并配置对象或者添加数据到测试数据库。
可以有多个 Given 步骤(可以使用 And 或者 But 来变的更可读)
</code></pre></div></div>
<hr />
<h2 id="36-then--那么">3.6 Then / 那么</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Then 步骤用于描述期望的产出,或者结果。
Then 步骤的 步骤定义 应该使用断言来比较实际产出(系统实际行为)和期待产出(步骤所述的系统应有的行为)
</code></pre></div></div>
<hr />
<h2 id="37-background--背景">3.7 Background / 背景</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>发现一个feature文件中的所有场景都在重复同样的 Given 步骤。既然它在每个场景
可以将这样的 Given 步骤移动到background中,在第一个场景之前,用一个 Background 块组织他们: ```
Background:
Given a $100 microwave was sold on 2015-11-03
And today is 2015-11-18 ```
</code></pre></div></div>
<hr />
<h2 id="38-scenario-outline--场景大纲">3.8 Scenario Outline / 场景大纲</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>当有复杂业务规则,带有多个输入或者输出,可以最终创建仅仅是值有差别的多个场景。举个例子: ```
Scenario: feeding a small suckler cow
Given the cow weighs 450 kg
When we calculate the feeding requirements
Then the energy should be 26500 MJ
And the protein should be 215 kg
Scenario: feeding a medium suckler cow
Given the cow weighs 500 kg
When we calculate the feeding requirements
Then the energy should be 29500 MJ
And the protein should be 245 kg
# 还有两个例子 --- 已经令人厌烦了 ```
如果有很多例子,将会很乏味。可以通过使用场景大纲来简化: ```
Scenario Outline: feeding a suckler cow
Given the cow weighs <weight> kg
When we calculate the feeding requirements
Then the energy should be <energy> MJ
And the protein should be <protein> kg
Examples:
| weight | energy | protein |
| 450 | 26500 | 215 |
| 500 | 29500 | 245 |
| 575 | 31500 | 255 |
| 600 | 37000 | 305 | ```
这更易于阅读。场景大纲步骤中的变量通过使用 < 和 > 来标记。
</code></pre></div></div>
<hr />
<h2 id="39-examples--示例">3.9 Examples / 示例</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>场景大纲部分总被带有一个或者多个 Examples / 示例 部分,用于包含一个表格。
表格必须有header 行,对应场景大纲步骤中的变量。
下面的每一行将创建一个新的场景,使用变量的值填充。
</code></pre></div></div>
<hr />
<h2 id="310-执行步骤">3.10 执行步骤</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>当cucumber执行场景中的步骤时,它将查找匹配的步骤定义来执行。
步骤定义是带有正则表达式的小段代码。正则表达式用于连接步骤定义到所有匹配的步骤,而代码是cucumber要执行的内容。
例如一下场景: ```
Scenario: Some cukes
Given I have 48 cukes in my belly ```
步骤的 I have 48 cukes in my belly 部分(Given关键字后面的文本)将匹配下面的步骤定义: ```
@Given("I have (\\d+) cukes in my belly")
public void I_have_cukes_in_my_belly(int cukes){
System.out.format("Cukes: %n\n", cukes);
} ```
当cucumber匹配步骤到一个步骤定义中的正则表达式时,它传递所有捕获组(capture group)的值到步骤定义的参数。捕获组是字符串(即使他们匹配数字如 \d+ )。对于静态类型语言,cucumber将自动转换这些字符串到合适的类型。对于动态类型语言,默认不转换,因为他们没有类型信息。
Cucumber不区分这五个步骤关键字 Given, When, Then, And 和 But。
</code></pre></div></div>
<h1 id="四实践">四、实践</h1>
<h2 id="41编写feature文件">4.1编写.feature文件</h2>
<p><img src="/images/linmingxing/cucumber1.png" alt="cucumber1" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.features文件一般放在test/resources/features目录下。
图中使用的是中文来编写的,这样写的前提是在首行加上 # language: zh-CN
一个场景为一个执行单位,一般用于测试一个接口。每个步骤都会通过正则匹配一个方法。
步骤写好后,鼠标点击黄色色块上,按下自动提示快捷键,编译器会提示定义step。按提示创建即可。
</code></pre></div></div>
<h2 id="42编写步骤steps类">4.2编写步骤steps类</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>如下图:
</code></pre></div></div>
<p><img src="/images/linmingxing/cucumber2.png" alt="cucumber2" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 图中@cucumber.api.java.zh_CN报红,只要将cucumber.api.java.zh_CN删除,重新导入即可。或者将CN改为小写的cn。
steps类一般放在test/java/项目包名/目录下的一个文件夹中。
每个方法中编写测试代码,去测试某个接口。接口的访问可以使用RestTemplate。使用Assert判断接口返回是否与预期的一样。
一个完整的场景,包括以下几个步骤:
1、新建测试数据
2、调用接口进行测试
3、删除测试数据
这样可以重复测试,不会产生多余的测试数据。
</code></pre></div></div>
<h2 id="43-启动测试">4.3 启动测试</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>启动测试有两种方法,
1、右键.feature文件,选择Debug Feature执行
2、如下图,编写测试类去启动@CucumberOptions注解的features属性对应.feature文件
</code></pre></div></div>
<p><img src="/images/linmingxing/cucumber3.png" alt="cucumber3" /></p>
分布式事务框架seata(1)
2019-05-06T00:00:00+00:00
http://www.blogways.net/blog/2019/05/06/分布式事务框架seata(1)
<h1 id="seata"><a href="https://github.com/seata/seata">seata</a></h1>
<h2 id="1分布式事务">1.分布式事务</h2>
<p>在学习seata之前,先了解一些分布式事务的理论背景。既有的分布式事务解决方案按照对业务的侵入性分为两类,即:对业务无侵入的和对业务有侵入的。对业务无侵入的只有基于XA的方案,侵入业务的方案有TCC,Saga,基于可靠消息的最终一致性方案等。</p>
<h3 id="11-xa">1.1 XA</h3>
<p>XA中大致分为两部分:事务管理器和本地资源管理器。其中本地资源管理器往往由数据库实现,比如Oracle、DB2这些商业数据库都实现了XA接口,而事务管理器作为全局的调度者,负责各个本地资源的提交和回滚。XA实现分布式事务的原理如下:
<img src="/images/wangtianwen/seata/XA1.png" alt="" />
总的来说,XA协议比较简单,而且一旦商业数据库实现了XA协议,使用分布式事务的成本也比较低。但是,XA也有致命的缺点,那就是性能不理想,特别是在交易下单链路,往往并发量很高,XA无法满足高并发场景。XA目前在商业数据库支持的比较理想,在mysql数据库中支持的不太理想,mysql的XA实现,没有记录prepare阶段日志,主备切换会导致主库与备库数据不一致。许多nosql也没有支持XA,这让XA的应用场景变得非常狭隘。</p>
<h3 id="12-两阶段提交2pc">1.2 两阶段提交(2PC)</h3>
<p>两阶段提交协议(Two Phase Commitment Protocol)是分布式事务最基本的协议。在两阶段提交协议中,有一个事务管理器和多个资源管理器,事务管理器分两阶段协调资源管理器。在第一阶段,事务管理器询问所有资源管理器准备是否成功。如果所有资源均准备成功,那么在第二阶段事务管理器会要求所有资源管理器执行提交操作;如果任一资源管理器在第一阶段返回准备失败,那么事务管理器会要求所有资源管理器在第二阶段执行回滚操作。通过事务管理器的两阶段协调,最终所有资源管理器要么全部提交,要么全部回滚,最终状态都是一致的。</p>
<p><img src="/images/wangtianwen/seata/2PC.png" alt="" /></p>
<h3 id="13-补偿事务tcc">1.3 补偿事务(TCC)</h3>
<p>资源管理器有很多实现方式,其中 TCC(Try-Confirm-Cancel)是资源管理器的一种服务化的实现。TCC 是一种比较成熟的分布式事务解决方案,可用于解决跨数据库、跨服务业务操作的数据一致性问题。TCC 其 Try、Confirm、Cancel 3 个方法均由业务编码实现,故 TCC 可以被称为是服务化的资源管理器。</p>
<p>TCC 的 Try 操作作为一阶段,负责资源的检查和预留;Confirm 操作作为二阶段提交操作,执行真正的业务;Cancel 是二阶段回滚操作,执行预留资源的取消,使资源回到初始状态。</p>
<p>如下图所示,用户实现 TCC 服务之后,该 TCC 服务将作为分布式事务的其中一个资源,参与到整个分布式事务中。事务管理器分两个阶段协调 TCC 服务,在第一阶段调用所有 TCC 服务的 Try 方法,在第二阶段执行所有 TCC 服务的 Confirm 或者 Cancel 方法,最终所有 TCC 服务要么全部都是提交的、要么全部都是回滚的。</p>
<p><img src="/images/wangtianwen/seata/TCC.png" alt="" /></p>
<h3 id="14-本地消息表异步确保">1.4 本地消息表(异步确保)</h3>
<p>本地消息表这种实现方式应该是业界使用最多的,其核心思想是将分布式事务拆分成本地事务进行处理,这种思路是来源于ebay。我们可以从下面的流程图中看出其中的一些细节:</p>
<p><img src="/images/wangtianwen/seata/本地消息表.png" alt="" /></p>
<p>基本思路就是:</p>
<p>消息生产方,需要额外建一个消息表,并记录消息发送状态。消息表和业务数据要在一个事务里提交,也就是说他们要在一个数据库里面。然后消息会经过MQ发送到消息的消费方。如果消息发送失败,会进行重试发送。</p>
<p>消息消费方,需要处理这个消息,并完成自己的业务逻辑。此时如果本地事务处理成功,表明已经处理成功了,如果处理失败,那么就会重试执行。如果是业务上面的失败,可以给生产方发送一个业务补偿消息,通知生产方进行回滚等操作。</p>
<p>生产方和消费方定时扫描本地消息表,把还没处理完成的消息或者失败的消息再发送一遍。如果有靠谱的自动对账补账逻辑,这种方案还是非常实用的。</p>
<h3 id="15-mq事务消息">1.5 MQ事务消息</h3>
<p>有一些第三方的MQ是支持事务消息的,比如RocketMQ,他们支持事务消息的方式也是类似于采用的二阶段提交,但是市面上一些主流的MQ都是不支持事务消息的,比如 RabbitMQ 和 Kafka 都不支持。</p>
<p>以阿里的 RocketMQ 中间件为例,其思路大致为:</p>
<p>第一阶段Prepared消息,会拿到消息的地址。
第二阶段执行本地事务,第三阶段通过第一阶段拿到的地址去访问消息,并修改状态。</p>
<p>也就是说在业务方法内要想消息队列提交两次请求,一次发送消息和一次确认消息。如果确认消息发送失败了RocketMQ会定期扫描消息集群中的事务消息,这时候发现了Prepared消息,它会向消息发送者确认,所以生产方需要实现一个check接口,RocketMQ会根据发送端设置的策略来决定是回滚还是继续发送确认消息。这样就保证了消息发送与本地事务同时成功或同时失败。</p>
<p><img src="/images/wangtianwen/seata/MQ.png" alt="" /></p>
<h3 id="16-sagas事务模型">1.6 Sagas事务模型</h3>
<p>Saga事务模型又叫做长时间运行的事务(Long-running-transaction),它描述的是另外一种在没有两阶段提交的的情况下解决分布式系统中复杂的业务事务问题。</p>
<p>该模型其核心思想就是拆分分布式系统中的长事务为多个短事务,或者叫多个本地事务,然后由 Sagas 工作流引擎负责协调,如果整个流程正常结束,那么就算是业务成功完成,如果在这过程中实现失败,那么Sagas工作流引擎就会以相反的顺序调用补偿操作,重新进行业务回滚。</p>
<p>比如我们一次关于购买旅游套餐业务操作涉及到三个操作,他们分别是预定车辆,预定宾馆,预定机票,他们分别属于三个不同的远程接口。可能从我们程序的角度来说他们不属于一个事务,但是从业务角度来说是属于同一个事务的。</p>
<p><img src="/images/wangtianwen/seata/Sagas.png" alt="" /></p>
<p>他们的执行顺序如上图所示,所以当发生失败时,会依次进行取消的补偿操作</p>
<h2 id="2-seata">2 seata</h2>
<h3 id="21-简介">2.1 简介</h3>
<p>seata(原名Fescar)是阿里巴巴开源的分布式事务中间件,以高效并且对业务0侵入的方式,解决微服务场景下面临的分布式事务问题。</p>
<h3 id="22-微服务中的分布式事务问题">2.2 微服务中的分布式事务问题</h3>
<p>在传统的单体应用中,业务由三个模块构建而成,使用了一个单一本地数据源,所以本地事务可以保证数据一致性。</p>
<p><img src="/images/wangtianwen/seata/传统单体应用.png" alt="" /></p>
<p>在微服务架构中,以上的三个模块被设计为三个不同数据源之上的三个服务,并且在本地事务能够保证数据一致性。</p>
<p><img src="/images/wangtianwen/seata/独立服务.png" alt="" /></p>
<h3 id="23-seata解决办法">2.3 seata解决办法</h3>
<p><img src="/images/wangtianwen/seata/seata解决办法.png" alt="" /></p>
<p>一个分布式事务是一个包含了若干分支事务的全局事务,全局事务的职责是协调其下管辖的分支事务达成一致,要么一起成功提交,要么一起失败回滚。此外,通常分支事务本身就是一个满足ACID的本地事务。与XA是一致的。
<img src="/images/wangtianwen/seata/全局事务和分支事务.png" alt="" /></p>
<p>其次,与XA的模型类似,定义三个组件来协议分布式事务的处理过程。</p>
<p><img src="/images/wangtianwen/seata/三个组件.png" alt="" /></p>
<ul>
<li>Transaction Coordinator (TC): 事务协调器,维护全局事务的运行状态,负责协调并驱动全局事务的提交或回滚。</li>
<li>Transaction Manager (TM): 控制全局事务的边界,负责开启一个全局事务,并最终发起全局提交或全局回滚的决议。</li>
<li>Resource Manager (RM): 控制分支事务,负责分支注册、状态汇报,并接收事务协调器的指令,驱动分支(本地)事务的提交和回滚。</li>
</ul>
<p>一个典型的分布式事务过程:</p>
<ol>
<li>TM 向 TC 申请开启一个全局事务,全局事务创建成功并生成一个全局唯一的 XID。</li>
<li>XID 在微服务调用链路的上下文中传播。</li>
<li>RM 向 TC 注册分支事务,将其纳入 XID 对应全局事务的管辖。</li>
<li>TM 向 TC 发起针对 XID 的全局提交或回滚决议。</li>
<li>TC 调度 XID 下管辖的全部分支事务完成提交或回滚请求。</li>
</ol>
<p><img src="/images/wangtianwen/seata/分布式事务过程.png" alt="" /></p>
<h2 id="3-运行demo">3 运行demo</h2>
<p>例子是用户购买商品的业务逻辑,由三个微服务支持:</p>
<ol>
<li>存储服务:扣除指定商品的存储数量</li>
<li>订单服务:根据采购要求创建订单</li>
<li>账户服务:从用户账户余额中扣除</li>
</ol>
<h3 id="31-运行server">3.1 运行<a href="https://github.com/seata/seata/releases">server</a></h3>
<p>下载并解压,windows下直接运行bin目录下的seata-server.bat,其他运行命令 sh seata-server.sh 8091 file</p>
<h3 id="32-下载demo">3.2 下载<a href="https://github.com/seata/seata-samples">demo</a></h3>
<p>我运行的是Dubbo + seata,建表并配置数据源。</p>
<pre><code class="language-mysql">-- 注意此处0.3.0+ 增加唯一索引 ux_undo_log
CREATE TABLE `undo_log` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`branch_id` bigint(20) NOT NULL,
`xid` varchar(100) NOT NULL,
`rollback_info` longblob NOT NULL,
`log_status` int(11) NOT NULL,
`log_created` datetime NOT NULL,
`log_modified` datetime NOT NULL,
`ext` varchar(100) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `ux_undo_log` (`xid`,`branch_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `storage_tbl`;
CREATE TABLE `storage_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
PRIMARY KEY (`id`),
UNIQUE KEY (`commodity_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `order_tbl`;
CREATE TABLE `order_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`commodity_code` varchar(255) DEFAULT NULL,
`count` int(11) DEFAULT 0,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
DROP TABLE IF EXISTS `account_tbl`;
CREATE TABLE `account_tbl` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` varchar(255) DEFAULT NULL,
`money` int(11) DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</code></pre>
<p>依次运行</p>
<ul>
<li>DubboAccountServiceStarter</li>
<li>DubboStorageServiceStarter</li>
<li>DubboOrderServiceStarter</li>
<li>DubboBusinessTester</li>
</ul>
<p>在BusinessServiceImpl中手动抛出了异常,所以余额和库存不会发生变化。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Override
@GlobalTransactional(timeoutMills = 300000, name = "dubbo-demo-tx")
public void purchase(String userId, String commodityCode, int orderCount) {
LOGGER.info("purchase begin ... xid: " + RootContext.getXID());
storageService.deduct(commodityCode, orderCount);
orderService.create(userId, commodityCode, orderCount);
throw new RuntimeException("xxx");
}
</code></pre></div></div>
Elastic-Job入门(二)
2018-09-18T00:00:00+00:00
http://www.blogways.net/blog/2018/09/18/Elastic-Job(2)
<h1 id="11通过spring配置启动">1.1通过Spring配置启动</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-spring</artifactId>
<version>2.1.5</version>
</dependency>
</code></pre></div></div>
<p>在xml文件中配置作业</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"
xmlns:job="http://www.dangdang.com/schema/ddframe/job"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
<!--配置作业注册中心 -->
<reg:zookeeper id="regCenter" server-lists="yourhost:2181" namespace="dd-job" base-sleep-time-milliseconds="1000" max-sleep-time-milliseconds="3000" max-retries="3" />
<!-- 配置作业-->
<job:simple id="demoSimpleSpringJob" class="xxx.MyElasticJob" registry-center-ref="regCenter" cron="0/10 * * * * ?" sharding-total-count="3" sharding-item-parameters="0=A,1=B,2=C" />
</beans>
</code></pre></div></div>
<p>将该xml通过Spring启动,作业将自动挂载。</p>
<p>#1.2运维平台</p>
<ul>
<li>
<p>解压缩elastic-job-lite-console-${version}.tar.gz并执行bin\start.sh</p>
</li>
<li>
<p>打开浏览器访问<code class="language-plaintext highlighter-rouge">http://localhost:8899/</code>即可访问控制台</p>
<p><img src="/images/wangtianwen/Elastic-Job/console.png" alt="" /></p>
<p>启动任务后,就可看到下图</p>
<p><img src="/images/wangtianwen/Elastic-Job/job.png" alt="" /></p>
<p>修改页面</p>
<p><img src="/images/wangtianwen/Elastic-Job/modify.png" alt="" /></p>
</li>
</ul>
<h1 id="21简述注册中心">2.1简述注册中心</h1>
<p>涉及的类:</p>
<p><img src="/images/wangtianwen/Elastic-Job/class.png" alt="" /></p>
<p>ZookeeperRegistryCenter,基于Zookeeper注册中心。实现CoordinatorRegistryCenter接口,CoordinatorRegistryCenter继承RegistryCenter接口。</p>
<ul>
<li>CoordinatorRegistryCenter:用于协调分布式服务的注册中心,定义了持久节点、临时节点、持久顺序节点、临时顺序节点等目录服务接口方法,隐性的要求提供事务、分布式锁、数据订阅等特性。</li>
<li>RegistryCenter:注册中心,定义了简单的增删改查注册数据和查询事件的接口方法。</li>
</ul>
<p>##2.1.1初始化</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override
public void init() {
log.debug("Elastic job: zookeeper registry center init, server lists is: {}.", zkConfig.getServerLists());
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(zkConfig.getServerLists())
.retryPolicy(new ExponentialBackoffRetry(zkConfig.getBaseSleepTimeMilliseconds(), zkConfig.getMaxRetries(), zkConfig.getMaxSleepTimeMilliseconds()))
.namespace(zkConfig.getNamespace()); // 命名空间
if (0 != zkConfig.getSessionTimeoutMilliseconds()) {
builder.sessionTimeoutMs(zkConfig.getSessionTimeoutMilliseconds()); // 会话超时时间,默认 60 * 1000 毫秒
}
if (0 != zkConfig.getConnectionTimeoutMilliseconds()) {
builder.connectionTimeoutMs(zkConfig.getConnectionTimeoutMilliseconds()); // 连接超时时间,默认 15 * 1000 毫秒
}
// 认证
if (!Strings.isNullOrEmpty(zkConfig.getDigest())) {
builder.authorization("digest", zkConfig.getDigest().getBytes(Charsets.UTF_8))
.aclProvider(new ACLProvider() {
@Override
public List<ACL> getDefaultAcl() {
return ZooDefs.Ids.CREATOR_ALL_ACL;
}
@Override
public List<ACL> getAclForPath(final String path) {
return ZooDefs.Ids.CREATOR_ALL_ACL;
}
});
}
client = builder.build();
client.start();
// 连接 Zookeeper
try {
if (!client.blockUntilConnected(zkConfig.getMaxSleepTimeMilliseconds() * zkConfig.getMaxRetries(), TimeUnit.MILLISECONDS)) {
client.close();
throw new KeeperException.OperationTimeoutException();
}
} catch (final Exception ex) {
RegExceptionHandler.handleException(ex);
}
}
</code></pre></div></div>
Elastic-Job入门(一)
2018-09-08T00:00:00+00:00
http://www.blogways.net/blog/2018/09/08/Elastic-Job(1)
<h1 id="elastic-job">Elastic-Job</h1>
<h2 id="11简介">1.1简介</h2>
<p><strong>Elastic-Job</strong>:Elastic-Job(<a href="https://github.com/elasticjob">项目开源地址</a>)是ddframe中dd-job的作业模块中分离出来的分布式弹性作业框架,去掉了dd-job中的监控和ddframe接入规范部分。该项目基于成熟的开源产品Quartz和Zookeeper及其客户端Curator进行二次开发。由两个相互独立的子项目Elastic-Job-Lite和Elastic-Job-Cloud组成。Elastic-Job-lite定位为轻量级无中心化解决方案,使用jar包的形式提供分布式任务的协调服务。Elastic-Job-Cloud基于mesos运行,是mesos的Framework。这里介绍的是Elastic-Job-Lite。</p>
<h2 id="12基本概念">1.2基本概念</h2>
<ul>
<li>分片概念
任务的分布式执行,需要将一个任务拆分为多个独立的任务项,然后由分布式的服务器分别执行某一个或几个分片项。例如:有一个遍历数据库某张表的作业,现有2台服务器。为了快速的执行作业,那么每台服务器应执行作业的50%。 为满足此需求,可将作业分成2片,每台服务器执行1片。作业遍历数据的逻辑应为:服务器A遍历ID以奇数结尾的数据;服务器B遍历ID以偶数结尾的数据。 如果分成10片,则作业遍历数据的逻辑应为:每片分到的分片项应为ID%10,而服务器A被分配到分片项0,1,2,3,4;服务器B被分配到分片项5,6,7,8,9,直接的结果就是服务器A遍历ID以0-4结尾的数据;服务器B遍历ID以5-9结尾的数据。</li>
<li>分片项与业务处理解耦
Elastic-Job并不直接提供数据处理的功能,框架只会将分片项分配至各个运行中的作业服务器,开发者需要自行处理分片项与真实数据的对应关系。</li>
<li>个性化参数的使用场景
个性化参数即shardingItemParameter,可以和分片项匹配对应关系,用于将分片项的数字转换为更加可读的业务代码。例如:按照地区水平拆分数据库,数据库A是北京的数据;数据库B是上海的数据;数据库C是广州的数据。 如果仅按照分片项配置,开发者需要了解0表示北京;1表示上海;2表示广州。 合理使用个性化参数可以让代码更可读,如果配置为0=北京,1=上海,2=广州,那么代码中直接使用北京,上海,广州的枚举值即可完成分片项和业务逻辑的对应关系。</li>
</ul>
<h2 id="13快速入门">1.3快速入门</h2>
<p>引入maven依赖</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-lite-core</artifactId>
<version>2.1.5</version>
</dependency>
</code></pre></div></div>
<h4 id="131作业开发">1.3.1作业开发</h4>
<p>作业开发:Elastic-Job提供Simple、Dataflow和Script(文中没介绍)3种作业类型。</p>
<h5 id="asimple类型作业">a.Simple类型作业</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class MySimpleJob implements SimpleJob{
/**
* 执行作业.
* @param shardingContext 分片上下文
*/
@Override
public void execute(ShardingContext context) {
System.out.println(new SimpleDateFormat("HH:mm:ss").format(new Date())
+ " 分片项 : "+context.getShardingItem()
+ " 总片数 : " + context.getShardingTotalCount());
}
}
</code></pre></div></div>
<h5 id="bdataflow类型作业">b.Dataflow类型作业</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class MyDataflowJob implements DataflowJob{
/**
* 获取待处理数据.
* @param shardingContext 分片上下文
* @return 待处理的数据集合
*/
@Override
public List fetchData(ShardingContext shardingContext) {
return Arrays.asList("1","2","3");
}
/**
* 处理数据.
* @param shardingContext 分片上下文
* @param data 待处理数据集合
*/
@Override
public void processData(ShardingContext shardingContext, List data) {
System.out.println("处理数据:" + data.toString());
}
}
</code></pre></div></div>
<p>流式处理,可通过DataflowJobConfiguration配置是否为流式处理。流式处理数据只有fetchData方法的返回值为null或集合长度为空时,作业才停止抓取,否则作业将一直运行下去; 非流式处理数据则只会在每次作业执行过程中执行一次fetchData方法和processData方法,随即完成本次作业。</p>
<h5 id="c启动作业">c.启动作业</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class JobDemo {
public static void main(String[] args) {
//作业调度器初始化作业
new JobScheduler(createRegistryCenter(),createJobConfiguration()).init();
//启动DataflowJob
setUpDataflowJob(createRegistryCenter());
}
//注册中心
private static CoordinatorRegistryCenter createRegistryCenter(){
//ZookeeperConfiguration构造方法两个参数,serverLists(连接Zookeeper服务器的列表,包括IP地址和端口号,,多个地址用逗号分隔)和namespace(命名空间)
CoordinatorRegistryCenter registryCenter = new ZookeeperRegistryCenter(new ZookeeperConfiguration("0.0.0.0:2181","elastic-job-demo"));
registryCenter.init();
return registryCenter;
}
//配置SimpleJob
private static LiteJobConfiguration createJobConfiguration(){
//创建简单作业配置构建器,三个参数为:jobName(作业名称),cron(作业启动时间的cron表达式),shardingTotalCount(作业分片总数)
JobCoreConfiguration simpleCoreConfig = JobCoreConfiguration.newBuilder("mySimpleJob","0/10 * * * * ?",12).build();
//简单作业配置,第二个参数为jobClass
SimpleJobConfiguration simpleJobConfig = new SimpleJobConfiguration(simpleCoreConfig, MySimpleJob.class.getCanonicalName());
//创建Lite作业配置构建器,参数jobConfig(作业配置)
LiteJobConfiguration simpleJobRootConfig = LiteJobConfiguration.newBuilder(simpleJobConfig).build();
return simpleJobRootConfig;
}
//配置DataflowJob
private static void setUpDataflowJob(final CoordinatorRegistryCenter registryCenter){
JobCoreConfiguration coreConfiguration = JobCoreConfiguration.newBuilder("myDataflowJob","0/10 * * * * ?",2).build();
//数据流作业配置,第三个参数为streamingProcess(是否为流式处理)
DataflowJobConfiguration dataflowJobConfiguration = new DataflowJobConfiguration(coreConfiguration,MyDataflowJob.class.getCanonicalName(),true);
new JobScheduler(registryCenter,LiteJobConfiguration.newBuilder(dataflowJobConfiguration).build()).init();
}
}
</code></pre></div></div>
<h4 id="132作业配置">1.3.2作业配置</h4>
<p>从JobDemo类中可以看出配置分为3个层级,分别是Core,Type和Root,每个层级使用相似于装饰者模式的方式装配。</p>
<p>Core对应JobCoreConfiguration,用于提供作业核心配置信息。</p>
<p>Type对应JobTypeConfiguration,有3个子类分别对应SIMPLE,DATAFLOW,SCRIPT类型作业,提供3种作业需要的不同配置。</p>
<p>Root对应JobRootConfiguration,有2个子类分别对应Lite和Cloud部署类型,提供不同部署类型所需的配置。</p>
Java使用poi将office文件转为html
2018-08-10T00:00:00+00:00
http://www.blogways.net/blog/2018/08/10/java-poi-officeToHtml
<p>#Java使用poi将office文件转为html
##一、前言
功能需求:上传office文档,并提供文件在线预览。</p>
<p>解决方案:</p>
<ul>
<li>使用Aspose.cells.jar包,将文档转换为pdf格式;</li>
<li>使用libreOffice,将文档转换为pdf格式;</li>
<li>使用poi将文档转换为html格式。</li>
</ul>
<p>方案一,通过Aspose的方式,该功能是付费版,需要破解,所以是能抛弃。
方案二,使用libreOffice,需要安装使用libreOffice,linux还需要装unoconv,需要使用commons-io的pom依赖,之前maven官方库查询不到这个pom依赖所以放弃了这个方案,刚才准备查询资料时发现这个依赖已经可以使用,估计是前段时间maven官方库出现问题。
方案三,只需要添加所需的依赖就可以使用,但是转换出的html会有一些格式问题,等下会再下面讲到。</p>
<h2 id="二添加依赖">二、添加依赖</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-scratchpad</artifactId>
<version>3.12</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.document</artifactId>
<version>1.0.5</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>org.apache.poi.xwpf.converter.xhtml</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>fr.opensagres.xdocreport</groupId>
<artifactId>fr.opensagres.xdocreport.core</artifactId>
<version>2.0.1</version>
</dependency>
</dependencies>
</code></pre></div></div>
<h2 id="三word文档转html">三、word文档转html</h2>
<p>一般word文件后缀有doc、docx两种。docx是word2007以及以后版本文档的扩展名,doc是word2003文档保存的扩展名。对于这两种格式的word转换成html需要使用不同的方法。</p>
<p>1、转换问题:
转换后,对于2003版本word,自动生成的目录会显示有错误;2003版本和2007版本对于特殊字符都有可能显示不出来,不过问题不是很明显;文章中的图片一定要是常用的图片格式(jpeg,jpg,png等),不然无法显示。</p>
<p>2、2003版本word转换成html(.doc)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static boolean word2003ToHtml(Map params) {
logger.debug("***** word2003ToHtml start params:{}", params);
try {
//图片存放路径
String fileImg = params.get("fileImg").toString();
//转换html后,html中图片的url前缀
String viewImgPath = params.get("viewImgPath").toString();
//html文件
File htmlFile = new File(params.get("htmlFile").toString());
File file = new File(params.get("filePath").toString() + params.get("FILE_NAME").toString());
// 1) 加载word文档生成 HWPFDocument对象
InputStream inputStream = new FileInputStream(file);
HWPFDocument wordDocument = new HWPFDocument(inputStream);
WordToHtmlConverter wordToHtmlConverter =
new WordToHtmlConverter(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
//设置图片存放的位置
wordToHtmlConverter.setPicturesManager(new PicturesManager() {
public String savePicture(byte[] content, PictureType pictureType, String suggestedName, float widthInches, float heightInches) {
File imgPath = new File(fileImg);
if (!imgPath.exists()) {//图片目录不存在则创建
imgPath.mkdirs();
}
File file = new File(fileImg + suggestedName);
try {
OutputStream os = new FileOutputStream(file);
os.write(content);
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
//这里可以指定word文档中图片的路径。
return viewImgPath + "/" + suggestedName;
}
});
//解析word文档
wordToHtmlConverter.processDocument(wordDocument);
Document htmlDocument = wordToHtmlConverter.getDocument();
OutputStream outputStream = new FileOutputStream(htmlFile);
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(outputStream);
TransformerFactory factory = TransformerFactory.newInstance();
Transformer serializer = factory.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
outputStream.close();
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
</code></pre></div></div>
<p>3、2007版本word转换成html(.docx)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static boolean word2007ToHtml(Map params) throws Exception {
logger.debug("***** word2007ToHtml start params:{}", params);
try {
//转换html后,html中图片的url前缀
String viewImgPath = params.get("viewImgPath").toString();
//图片存放路径
String fileImg = params.get("fileImg").toString();
File file = new File(params.get("filePath").toString() + params.get("FILE_NAME").toString());
// 1) 加载word文档生成 XWPFDocument对象
InputStream inputStream = new FileInputStream(file);
XWPFDocument document = new XWPFDocument(inputStream);
// 2) 解析 XHTML配置 (URIResolver来设置图片存放的目录)
XHTMLOptions options = XHTMLOptions.create();
options.URIResolver(new BasicURIResolver(viewImgPath));
FileImageExtractor extractor = new FileImageExtractor(new File(fileImg));
options.setExtractor(extractor);
// 3) 将 XWPFDocument转换成XHTML
File htmlFile = new File(params.get("htmlFile").toString());
OutputStream outputStream = new FileOutputStream(htmlFile);
XHTMLConverter.getInstance().convert(document, outputStream, options);
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
</code></pre></div></div>
<h2 id="四excel文档转html">四、Excel文档转Html</h2>
<p>POI中将Excel转换为HTML方法仅能转换HSSFWorkBook类型(即03版xls),故可以先将读取的xlsx文件转换成xls文件再调用该方法统一处理</p>
<p>1、2003版本excel转换html(excel中不可包括图片,因为poi没提供将excel中图片转换的方法)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static boolean excelToHtml(Map params) {
try {
String file = params.get("FILE_NAME").toString();
String filePath = params.get("filePath").toString() + file;
InputStream input = new FileInputStream(filePath);
HSSFWorkbook excelBook = new HSSFWorkbook();
//判断Excel文件将07+版本转换为03版本
if (file.endsWith(EXCEL_XLS)) { //Excel 2003
excelBook = new HSSFWorkbook(input);
} else if (file.endsWith(EXCEL_XLSX)) { // Excel 2007/2010
ExcelTransFormUtil xls = new ExcelTransFormUtil();
XSSFWorkbook workbookOld = new XSSFWorkbook(input);
xls.transformXSSF(workbookOld, excelBook);
}
ExcelToHtmlConverter excelToHtmlConverter = new ExcelToHtmlConverter(DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument());
//去掉Excel头行
excelToHtmlConverter.setOutputColumnHeaders(false);
//去掉Excel行号
excelToHtmlConverter.setOutputRowNumbers(false);
excelToHtmlConverter.processWorkbook(excelBook);
Document htmlDocument = excelToHtmlConverter.getDocument();
ByteArrayOutputStream outStream = new ByteArrayOutputStream();
DOMSource domSource = new DOMSource(htmlDocument);
StreamResult streamResult = new StreamResult(outStream);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
outStream.close();
String content = new String(outStream.toByteArray());
FileUtils.writeStringToFile(new File(params.get("htmlFile").toString()), content, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
</code></pre></div></div>
<p>2、将2007版本转换成2003版本</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void transformXSSF(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew) {
HSSFSheet sheetNew;
XSSFSheet sheetOld;
workbookNew.setMissingCellPolicy(workbookOld.getMissingCellPolicy());
for (int i = 0; i < workbookOld.getNumberOfSheets(); i++) {
sheetOld = workbookOld.getSheetAt(i);
sheetNew = workbookNew.getSheet(sheetOld.getSheetName());
sheetNew = workbookNew.createSheet(sheetOld.getSheetName());
this.transform(workbookOld, workbookNew, sheetOld, sheetNew);
}
}
private void transform(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew,
XSSFSheet sheetOld, HSSFSheet sheetNew) {
sheetNew.setDisplayFormulas(sheetOld.isDisplayFormulas());
sheetNew.setDisplayGridlines(sheetOld.isDisplayGridlines());
sheetNew.setDisplayGuts(sheetOld.getDisplayGuts());
sheetNew.setDisplayRowColHeadings(sheetOld.isDisplayRowColHeadings());
sheetNew.setDisplayZeros(sheetOld.isDisplayZeros());
sheetNew.setFitToPage(sheetOld.getFitToPage());
sheetNew.setHorizontallyCenter(sheetOld.getHorizontallyCenter());
sheetNew.setMargin(Sheet.BottomMargin,
sheetOld.getMargin(Sheet.BottomMargin));
sheetNew.setMargin(Sheet.FooterMargin,
sheetOld.getMargin(Sheet.FooterMargin));
sheetNew.setMargin(Sheet.HeaderMargin,
sheetOld.getMargin(Sheet.HeaderMargin));
sheetNew.setMargin(Sheet.LeftMargin,
sheetOld.getMargin(Sheet.LeftMargin));
sheetNew.setMargin(Sheet.RightMargin,
sheetOld.getMargin(Sheet.RightMargin));
sheetNew.setMargin(Sheet.TopMargin, sheetOld.getMargin(Sheet.TopMargin));
sheetNew.setPrintGridlines(sheetNew.isPrintGridlines());
sheetNew.setRightToLeft(sheetNew.isRightToLeft());
sheetNew.setRowSumsBelow(sheetNew.getRowSumsBelow());
sheetNew.setRowSumsRight(sheetNew.getRowSumsRight());
sheetNew.setVerticallyCenter(sheetOld.getVerticallyCenter());
HSSFRow rowNew;
for (Row row : sheetOld) {
rowNew = sheetNew.createRow(row.getRowNum());
if (rowNew != null)
this.transform(workbookOld, workbookNew, (XSSFRow) row, rowNew);
}
for (int i = 0; i < this.lastColumn; i++) {
sheetNew.setColumnWidth(i, sheetOld.getColumnWidth(i));
sheetNew.setColumnHidden(i, sheetOld.isColumnHidden(i));
}
for (int i = 0; i < sheetOld.getNumMergedRegions(); i++) {
CellRangeAddress merged = sheetOld.getMergedRegion(i);
sheetNew.addMergedRegion(merged);
}
}
private void transform(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew,
XSSFRow rowOld, HSSFRow rowNew) {
HSSFCell cellNew;
rowNew.setHeight(rowOld.getHeight());
for (Cell cell : rowOld) {
cellNew = rowNew.createCell(cell.getColumnIndex(),
cell.getCellType());
if (cellNew != null)
this.transform(workbookOld, workbookNew, (XSSFCell) cell,
cellNew);
}
this.lastColumn = Math.max(this.lastColumn, rowOld.getLastCellNum());
}
private void transform(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew,
XSSFCell cellOld, HSSFCell cellNew) {
cellNew.setCellComment(cellOld.getCellComment());
Integer hash = cellOld.getCellStyle().hashCode();
if (this.styleMap != null && !this.styleMap.containsKey(hash)) {
this.transform(workbookOld, workbookNew, hash,
cellOld.getCellStyle(),
(HSSFCellStyle) workbookNew.createCellStyle());
}
cellNew.setCellStyle(this.styleMap.get(hash));
switch (cellOld.getCellType()) {
case Cell.CELL_TYPE_BLANK:
break;
case Cell.CELL_TYPE_BOOLEAN:
cellNew.setCellValue(cellOld.getBooleanCellValue());
break;
case Cell.CELL_TYPE_ERROR:
cellNew.setCellValue(cellOld.getErrorCellValue());
break;
case Cell.CELL_TYPE_FORMULA:
cellNew.setCellValue(cellOld.getCellFormula());
break;
case Cell.CELL_TYPE_NUMERIC:
cellNew.setCellValue(cellOld.getNumericCellValue());
break;
case Cell.CELL_TYPE_STRING:
cellNew.setCellValue(cellOld.getStringCellValue());
break;
default:
}
}
private void transform(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew,
Integer hash, XSSFCellStyle styleOld, HSSFCellStyle styleNew) {
styleNew.setAlignment(styleOld.getAlignment());
styleNew.setBorderBottom(styleOld.getBorderBottom());
styleNew.setBorderLeft(styleOld.getBorderLeft());
styleNew.setBorderRight(styleOld.getBorderRight());
styleNew.setBorderTop(styleOld.getBorderTop());
styleNew.setDataFormat(this.transform(workbookOld, workbookNew,
styleOld.getDataFormat()));
styleNew.setFillBackgroundColor(styleOld.getFillBackgroundColor());
styleNew.setFillForegroundColor(styleOld.getFillForegroundColor());
styleNew.setFillPattern(styleOld.getFillPattern());
styleNew.setFont(this.transform(workbookNew,
(XSSFFont) styleOld.getFont()));
styleNew.setHidden(styleOld.getHidden());
styleNew.setIndention(styleOld.getIndention());
styleNew.setLocked(styleOld.getLocked());
styleNew.setVerticalAlignment(styleOld.getVerticalAlignment());
styleNew.setWrapText(styleOld.getWrapText());
this.styleMap.put(hash, styleNew);
}
private short transform(XSSFWorkbook workbookOld, HSSFWorkbook workbookNew,
short index) {
DataFormat formatOld = workbookOld.createDataFormat();
DataFormat formatNew = workbookNew.createDataFormat();
return formatNew.getFormat(formatOld.getFormat(index));
}
private HSSFFont transform(HSSFWorkbook workbookNew, XSSFFont fontOld) {
HSSFFont fontNew = workbookNew.createFont();
fontNew.setBoldweight(fontOld.getBoldweight());
fontNew.setCharSet(fontOld.getCharSet());
fontNew.setColor(fontOld.getColor());
fontNew.setFontName(fontOld.getFontName());
fontNew.setFontHeight(fontOld.getFontHeight());
fontNew.setItalic(fontOld.getItalic());
fontNew.setStrikeout(fontOld.getStrikeout());
fontNew.setTypeOffset(fontOld.getTypeOffset());
fontNew.setUnderline(fontOld.getUnderline());
return fontNew;
} 手动捂脸,有点太多了。大家凑合着看。。。
</code></pre></div></div>
<h2 id="五ppt文档转html">五、PPT文档转Html</h2>
<p>就是将ppt转换成一张张图片再放入html。</p>
<p>注:
(1)ppt中文字会有中文显示问题,可以将中文文字库加入到服务器中即可。
(2)pptx2007版本文件不能显示表格</p>
<p>1、2003版本ppt转html</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static boolean ppt2003Tohtml(Map params) {
try {
String imgPath = params.get("fileImg").toString();
File file = new File(params.get("filePath").toString() + params.get("FILE_NAME").toString());
InputStream inputStream = new FileInputStream(file);
SlideShow ppt = new SlideShow(inputStream);
inputStream.close();
Dimension pgsize = ppt.getPageSize();
org.apache.poi.hslf.model.Slide[] slide = ppt.getSlides();
FileOutputStream out = null;
String imghtml = "";
String viewImgPath = params.get("viewImgPath").toString();
for (int i = 0; i < slide.length; i++) {
logger.debug("第" + i + "页。");
TextRun[] truns = slide[i].getTextRuns();
for (int k = 0; k < truns.length; k++) {
RichTextRun[] rtruns = truns[k].getRichTextRuns();
for (int l = 0; l < rtruns.length; l++) {
rtruns[l].setFontIndex(1);
rtruns[l].setFontName("宋体");
}
}
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
graphics.setPaint(Color.BLUE);
graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
slide[i].draw(graphics);
// 这里设置图片的存放路径和图片的格式(jpeg,png,bmp等等)
out = new FileOutputStream(imgPath + (i + 1) + ".jpeg");
javax.imageio.ImageIO.write(img, "jpeg", out);
//图片在html加载路径
String imgs = viewImgPath + "/" + (i + 1) + ".jpeg";
imghtml += "<img src=\'" + imgs + "\' style=\'width:960px;height:530px;vertical-align:text-bottom;\'><br><br><br><br>";
DOMSource domSource = new DOMSource();
StreamResult streamResult = new StreamResult(out);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
out.close();
String ppthtml = "<html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body>" + imghtml + "</body></html>";
FileUtils.writeStringToFile(new File(params.get("htmlFile").toString()), ppthtml, "utf-8");
}
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
</code></pre></div></div>
<p>2、2007版本ppt转换html</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static boolean ppt2007Tohtml(Map params) {
try {
String imgPath = params.get("fileImg").toString();
File file = new File(params.get("filePath").toString() + params.get("FILE_NAME").toString());
InputStream inputStream = new FileInputStream(file);
XMLSlideShow ppt = new XMLSlideShow(inputStream);
inputStream.close();
Dimension pgsize = ppt.getPageSize();
XSLFSlide[] pptPageXSLFSLiseList = ppt.getSlides();
FileOutputStream out = null;
String imghtml = "";
String viewImgPath = params.get("viewImgPath").toString();
for (int i = 0; i < pptPageXSLFSLiseList.length; i++) {
try {
for (XSLFShape shape : pptPageXSLFSLiseList[i].getShapes()) {
if (shape instanceof XSLFTextShape) {
XSLFTextShape tsh = (XSLFTextShape) shape;
for (XSLFTextParagraph p : tsh) {
for (XSLFTextRun r : p) {
r.setFontFamily("宋体");
}
}
}
}
BufferedImage img = new BufferedImage(pgsize.width, pgsize.height, BufferedImage.TYPE_INT_RGB);
Graphics2D graphics = img.createGraphics();
// clear the drawing area
graphics.setPaint(Color.white);
graphics.fill(new Rectangle2D.Float(0, 0, pgsize.width, pgsize.height));
// render
pptPageXSLFSLiseList[i].draw(graphics);
//
String Imgname = imgPath + (i + 1) + ".jpeg";
out = new FileOutputStream(Imgname);
javax.imageio.ImageIO.write(img, "jpeg", out);
//图片在html加载路径
String imgs = viewImgPath + "/" + (i + 1) + ".jpeg";
imghtml += "<img src=\'" + imgs + "\' style=\'width:960px;height:530px;vertical-align:text-bottom;\'><br><br><br><br>";
} catch (Exception e) {
System.out.println(e);
System.out.println("第" + i + "张ppt转换出错");
}
}
DOMSource domSource = new DOMSource();
StreamResult streamResult = new StreamResult(out);
TransformerFactory tf = TransformerFactory.newInstance();
Transformer serializer = tf.newTransformer();
serializer.setOutputProperty(OutputKeys.ENCODING, "utf-8");
serializer.setOutputProperty(OutputKeys.INDENT, "yes");
serializer.setOutputProperty(OutputKeys.METHOD, "html");
serializer.transform(domSource, streamResult);
out.close();
String ppthtml = "<html><head><META http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"></head><body>" + imghtml + "</body></html>";
FileUtils.writeStringToFile(new File(params.get("htmlFile").toString()), ppthtml, "utf-8");
} catch (Exception e) {
e.printStackTrace();
return false;
}
return true;
}
</code></pre></div></div>
<h2 id="六预览">六、预览</h2>
<p>1、每次预览将源文件转换成html后,将html文件上传nginx服务器目录下,用nginx代理访问,以防出现浏览器缓存问题。
我这里是将html文件和图片路径打包,通过sftp上传nginx服务器,然后解压。
2、使用iframe标签访问html文件。这里需要注意的是预览pdf文件,会出现下载打印的按钮,如果需求是文件只可缓存不可下载,就不可以使用iframe标签预览pdf。</p>
使用libreOffice将office文件转为pdf
2018-08-10T00:00:00+00:00
http://www.blogways.net/blog/2018/08/10/java-poi-officeToHtml-2
<p>#使用libreOffice将office文件转为pdf
##一、前言
功能需求:上传office文档,并提供文件在线预览。</p>
<p>之前提到了使用poi将文档转换html去预览。这篇文章讲下方案二中使用libreOffice将office文件转为pdf。</p>
<p>使用libreOffice,需要安装使用libreOffice,linux还需要装unoconv,需要使用commons-io的pom依赖,之前maven官方库查询不到这个pom依赖所以放弃了这个方案,刚才准备查询资料时发现这个依赖已经可以使用,估计是前段时间maven官方库出现问题。</p>
<h2 id="二安装libreoffice">二、安装libreOffice</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install libreoffice
yum install libreoffice-headless
tar -zxvf LibreOffice_5.4.0_Linux_x86-64_rpm.tar.gz
cd LibreOffice_5.4.0.3_Linux_x86-64_rpm/RPMS
yum install *.rpm
</code></pre></div></div>
<h2 id="三安装中文字体库">三、安装中文字体库</h2>
<p>转换过程中可能会出现中文乱码问题。拷贝window的文字库拷贝到服务器下</p>
<p>1、进入c:\windows\Fonts ,复制所需要的字体;</p>
<p>2、将复制的文件放入服务器 /usr/share/font/ 目录下;</p>
<p>3、刷新系统即刻生效,输入命令:sudo fc-cache -fv。</p>
<h2 id="四转换成pdf">四、转换成pdf</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/libreoffice --invisible --convert-to pdf --outdir /root/out/ zzz.docx
</code></pre></div></div>
<p>–outdir后面的参数是转换后的pdf文件保存的目录,最后的文件绝对路径也可以放在–outdir前面。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/libreoffice --invisible --convert-to pdf /root/out/ --outdir zzz.docx
</code></pre></div></div>
<p>如果是自行编译需要带版本号:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/libreoffice5.4 --invisible --convert-to pdf --outdir /root/out/ zzz.docx
</code></pre></div></div>
Bootstrap 简介
2018-06-28T00:00:00+00:00
http://www.blogways.net/blog/2018/06/28/Bootstrap-summary
<h1 id="bootstrap学习总结">Bootstrap学习总结</h1>
<h2 id="简介">简介</h2>
<p>Bootstrap由twitter的Mark Otto和Jacob Thornton编写,该程序最初是为了保持团队开发中使用的工具与框架的一致性,并减少维护的负担。
但随后由于其出色的性能,Bootstrap被作为一个开源项目发布,由Mark Otto, Jacob Thornton 和一个核心开发小组来维护。</p>
<p>Bootstrap的整个框架是以HTML和CSS为基础,并加以一些Javascript插件的辅助。整个框架保留整理了CSS设置与基本的HTML的元素样式,并提供了很多便于开发的工作的组件以提高工作效率。此外,Bootstrap自带了12种jQuery插件,并允许开发者自行添加跟多的插件来扩展跟多的功能。</p>
<p>Bootstrap在网站及网络应用开发的工具主要集中在了四个模块:排版(layout),内容(content),部件(component),以及使用性(utilities)。</p>
<h3 id="1layout">1.Layout</h3>
<p>Bootstrap 提供了一个十二网格系统,并拥有响应式的网络系统,其内容可根据显示器窗口大小调整。这对于网站的排版提供了巨大的便利,特别是当网站需要同时满足不同大小或分辨率的显示器,也为网站在移动端平台的开发提供了便利。
<img src="/images/12grids.png" alt="12" /></p>
<p>开发者亦可以通过十二网格系统的规则来决定网页内容在不同大小的显示屏中如何呈现,例如</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="row">
<div class='col-xs-12 col-sm-6 col-md-4 col-lg-4'>
</code></pre></div></div>
<p>其中col-xs适用于小于768像素的显示器,例如手机屏幕,而col-sm,col-md以及col-lg分别适用于768px,992px,1200px及以上的显示屏,开发者可以通过实际需求自行调节。</p>
<h3 id="2content">2.Content</h3>
<p>Bootstrap对于一些HTML的基础元素的参数进行了更改使其变得简洁美观,并将所有的CSS文档保存在一份文件中。Bootstrap对于版面设计,多行代码编写,以及图片和表格的显示做出了规范,以求不同元素在不同类别的显示中最优化的显现</p>
<p>例如,Headings和List同时的margin数值都被调整,基础的form与table形式也被调整的更简洁,对于显示规则也作出了一些调整,比如<label>的显示形式被调整为inline-block。
具体的规范可去官网查阅</label></p>
<h3 id="3component">3.Component</h3>
<p>Bootstrap对于原CSS的组件进行了整理和改进,特别是对于一些常用的组件,Bootstrap提供了跟简便的适用方法并添加了新的元素,整个Bootstrap包含了模拟框,滚动监听,标签页,轮播,折叠,表单,警告框,弹出框,胶囊式菜单,多媒体对象,进度条等网络开发时经常用到的元素,大大提高了开发的效率。如需要更详细的了解所有组件可以去官网或源代码内查阅。</p>
<p>以Navigation Bar来举一个例子,在bootstrap框架中仅运用以下的代码就可以建立一个菜单导航页,相比于利用自己定义CSS来制作便捷了很多,省去了很多对于格式的调整。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <nav class="navbar navbar-default">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="#">WebSiteName</a>
</div>
<ul class="nav navbar-nav">
<li class="active"><a href="#">Home</a></li>
<li><a href="#">Page 1</a></li>
<li><a href="#">Page 2</a></li>
<li><a href="#">Page 3</a></li>
</ul>
</div>
</nav>
</code></pre></div></div>
<p>但是,Bootstrap自带的工具也提供了一定的局限性,譬如Bootstrap自带的菜单栏的折叠按钮只能在自带的菜单栏中使用,无法应用到自定义的菜单栏中,因此开发者需要自行定义CSS来满足需求。同理,如果开发者希望调整框架自带的一些基础设定,可以利用CSS工具或到源代码处自行调整。</p>
<h3 id="4utilities">4.Utilities</h3>
<p>Bootstrap增加了许多不同风格的实用性的工具以提高网站开发的效率,减少开发者自定义的CSS class的数量,并减少文件大小。
实用工具以class的形式来表达特定的属性,这些实用工具和组件一样用class的形式添加,无需适用任何CSS代码,例如边框,边框颜色,圆角半径,框架内自带的工具有很多,也在官网上列举了出来
例如:在bootstrap框架中可以简单的通过</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="fixed-top">
</code></pre></div></div>
<p>让整个 <div> 区间保持在显示器的上方</p>
<h2 id="其他">其他</h2>
<p>Bootstrap4 相比于Bootstrap3还是做出了明显的改变。CSS的源文件从LESS变成了SASS格式,并对整个表单系统做出了调整,使开发者可以便捷的加入自定义的元素。此外Bootstrap4对于原bootstrap3的组件实用规范做出了一系列的调整,例如菜单导航,折叠,响应式表格/图片等。</p>
Spark基础简易教程(Scala)
2018-06-08T00:00:00+00:00
http://www.blogways.net/blog/2018/06/08/spark-basic
<h1 id="spark简单教学">Spark简单教学#</h1>
<h2 id="一spark简介">一、Spark简介</h2>
<p>Spark最初由美国加州伯克利大学(UCBerkeley)的AMP(Algorithms, Machines and People)实验室于2009年开发,是基于内存计算的大数据并行计算框架,可用于构建大型的、低延迟的数据分析应用程序。Spark在诞生之初属于研究性项目,其诸多核心理念均源自学术研究论文。2013年,Spark加入Apache孵化器项目后,开始获得迅猛的发展,如今已成为Apache软件基金会最重要的三大分布式计算系统开源项目之一(即Hadoop、Spark、Storm)。</p>
<p>Spark具有如下几个主要特点:</p>
<ul>
<li>运行速度快:Spark使用先进的DAG(Directed Acyclic Graph,有向无环图)执行引擎,以支持循环数据流与内存计算,基于内存的执行速度可比Hadoop MapReduce快上百倍,基于磁盘的执行速度也能快十倍;</li>
<li>容易使用:Spark支持使用Scala、Java、Python和R语言进行编程,简洁的API设计有助于用户轻松构建并行程序,并且可以通过Spark Shell进行交互式编程;</li>
<li>通用性:Spark提供了完整而强大的技术栈,包括SQL查询、流式计算、机器学习和图算法组件,这些组件可以无缝整合在同一个应用中,足以应对复杂的计算;</li>
<li>运行模式多样:Spark可运行于独立的集群模式中,或者运行于Hadoop中,也可运行于Amazon EC2等云环境中,并且可以访问HDFS、Cassandra、HBase、Hive等多种数据源。</li>
</ul>
<h2 id="二spark运行架构">二、Spark运行架构</h2>
<p>Spark架构的组成图如下:</p>
<p><img src="/images/shenbin3/spark/saprk_framework.png" alt="" /></p>
<ul>
<li>Cluster Manager:在standalone模式中即为Master主节点,控制整个集群,监控worker。在YARN模式中为资源管理器</li>
<li>Worker节点:从节点,负责控制计算节点,启动Executor或者Driver。</li>
<li>Driver: 运行Application 的main()函数</li>
<li>Executor:执行器,是为某个Application运行在worker node上的一个进程</li>
</ul>
<p>在Spark中,一个应用(Application)由一个任务控制节点(Driver)和若干个作业(Job)构成,一个作业由多个阶段(Stage)构成,一个阶段由多个任务(Task)组成。当执行一个应用时,任务控制节点会向集群管理器(Cluster Manager)申请资源,启动Executor,并向Executor发送应用程序代码和文件,然后在Executor上执行任务,运行结束后,执行结果会返回给任务控制节点,或者写到HDFS或者其他数据库中。</p>
<p>Spark的基本运行流程如下:</p>
<p><img src="/images/shenbin3/spark/spark_flow.png" alt="" /></p>
<ol>
<li>当一个Spark应用被提交时,首先需要为这个应用构建起基本的运行环境,即由任务控制节点(Driver)创建一个SparkContext,由SparkContext负责和资源管理器(Cluster Manager)的通信以及进行资源的申请、任务的分配和监控等。SparkContext会向资源管理器注册并申请运行Executor的资源;</li>
<li>资源管理器为Executor分配资源,并启动Executor进程,Executor运行情况将随着“心跳”发送到资源管理器上;</li>
<li>SparkContext根据RDD的依赖关系构建DAG图,DAG图提交给DAG调度器(DAGScheduler)进行解析,将DAG图分解成多个“阶段”(每个阶段都是一个任务集),并且计算出各个阶段之间的依赖关系,然后把一个个“任务集”提交给底层的任务调度器(TaskScheduler)进行处理;Executor向SparkContext申请任务,任务调度器将任务分发给Executor运行,同时,SparkContext将应用程序代码发放给Executor;</li>
<li>任务在Executor上运行,把执行结果反馈给任务调度器,然后反馈给DAG调度器,运行完毕后写入数据并释放所有资源。</li>
</ol>
<h2 id="三rdd">三、RDD</h2>
<p>一个RDD就是一个分布式对象集合,本质上是一个只读的分区记录集合,每个RDD可以分成多个分区,每个分区就是一个数据集片段,并且一个RDD的不同分区可以被保存到集群中不同的节点上,从而可以在集群中的不同节点上进行并行计算。RDD提供了一种高度受限的共享内存模型,即RDD是只读的记录分区的集合,不能直接修改,只能基于稳定的物理存储中的数据集来创建RDD,或者通过在其他RDD上执行确定的转换操作(如map、join和groupBy)而创建得到新的RDD。RDD提供了一组丰富的操作以支持常见的数据运算,分为“行动”(Action)和“转换”(Transformation)两种类型,前者用于执行计算并指定输出的形式,后者指定RDD之间的相互依赖关系。两类操作的主要区别是,转换操作(比如map、filter、groupBy、join等)接受RDD并返回RDD,而行动操作(比如count、collect等)接受RDD但是返回非RDD(即输出一个值或结果)。RDD提供的转换接口都非常简单,都是类似map、filter、groupBy、join等粗粒度的数据转换操作,而不是针对某个数据项的细粒度修改。因此,RDD比较适合对于数据集中元素执行相同操作的批处理式应用,而不适合用于需要异步、细粒度状态的应用,比如Web应用系统、增量式的网页爬虫等。正因为这样,这种粗粒度转换接口设计,会使人直觉上认为RDD的功能很受限、不够强大。但是,实际上RDD已经被实践证明可以很好地应用于许多并行计算应用中,可以具备很多现有计算框架(比如MapReduce、SQL、Pregel等)的表达能力,并且可以应用于这些框架处理不了的交互式数据挖掘应用。</p>
<p>Spark用Scala语言实现了RDD的API,程序员可以通过调用API实现对RDD的各种操作。RDD典型的执行过程如下:</p>
<ol>
<li>RDD读入外部数据源(或者内存中的集合)进行创建;</li>
<li>RDD经过一系列的“转换”操作,每一次都会产生不同的RDD,供给下一个“转换”使用;</li>
<li>最后一个RDD经“行动”操作进行处理,并输出到外部数据源(或者变成Scala集合或标量)。</li>
</ol>
<p>需要说明的是,RDD采用了惰性调用,即在RDD的执行过程中,真正的计算发生在RDD的“行动”操作,对于“行动”之前的所有“转换”操作,Spark只是记录下“转换”操作应用的一些基础数据集以及RDD生成的轨迹,即相互之间的依赖关系,而不会触发真正的计算。</p>
<h2 id="四-使用intellij-idea编写spark应用程序scalamaven">四、 使用IntelliJ IDEA编写Spark应用程序(Scala+Maven)</h2>
<p>使用IntelliJ IDEA编写scala程序需要安装scala插件和sdk,安装方法可以参考另一篇博文:Scala速学。</p>
<p>在有了scala插件和sdk之后,便可以新建一个maven项目,pom文件中可以使用依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><properties>
<spark.version>2.1.0</spark.version>
<scala.version>2.11</scala.version>
</properties>
<dependencies>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-streaming_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-hive_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-mllib_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
</dependencies>
</code></pre></div></div>
<p>导入依赖后可以新建一个测试类WordCount.scala测试能否正常调试,测试类代码如下:
import org.apache.spark.sql.SparkSession</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>object WordCount
{
def main(args: Array[String]): Unit =
{
val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
val result = spark.read.textFile("file:///D:/Test/test.txt") //读取的文件,支持正则如test.*
.rdd
.flatMap(line => line.split(" "))
.map((_, 1))
.reduceByKey(_+_)
.collect()
println(result.toBuffer)
}
}
</code></pre></div></div>
<p>进行debug,会启动单机模式的spark,运行结果如下:</p>
<p><img src="/images/shenbin3/spark/result.png" alt="" /></p>
<p>到此就能进行spark程序的开发了。</p>
<h2 id="五spark编程基础">五、Spark编程基础</h2>
<h4 id="1读写文件">1.读写文件</h4>
<p>除了可以对本地文件系统进行读写以外,Spark还支持很多其他常见的文件格式(如文本文件、JSON、SequenceFile等)和文件系统(如HDFS、Amazon S3等)和数据库(如MySQL、HBase、Hive等)。数据库的读写将在Spark SQL部分介绍,这里只介绍hdfs文件系统的读写和不同文件格式的读写。</p>
<p>本地文本文件可以使用textFile()函数进行读取,用法如下:
val spark = SparkSession.builder()
.master(“local”) //本地模式
.appName(“WordCount”)
.getOrCreate()</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val result = spark.read.textFile("file:///D:/Test/test.txt") //读取的文件,支持正则如test.*
.rdd
.flatMap(line => line.split(" "))
result.saveAsTextFile("file:///D:/Test/out.txt")
</code></pre></div></div>
<p>test.txt文件内容如下:</p>
<p><img src="/images/shenbin3/spark/test.png" alt="" /></p>
<p>运行以上代码之后会在D盘Test文件夹下生成一个out.txt 文件夹,内容如下:</p>
<p><img src="/images/shenbin3/spark/out_dir.png" alt="" /></p>
<p>查看part-00000文件,内容如下:</p>
<p><img src="/images/shenbin3/spark/part000000.png" alt="" /></p>
<p>可以发现part-00000文件中的内容为程序的预期结果,那如果想要再次把数据加载到RDD时候要使用该文件吗?其实不需要,只要使用spark.read.textFile(“D:\Test\out.txt”)刚才生成的文件夹即可。</p>
<p>分布式文件系统HDFS数据读取和本地文件相同只需指定hdfs地址,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
val result = spark.read.textFile("hdfs://localhost:9000/user/hadoop/word.txt")
</code></pre></div></div>
<h2 id="六sparksql">六、SparkSQL</h2>
<h4 id="61-sparksql-简介">6.1 SparkSQL 简介</h4>
<p>Spark SQL是Spark生态系统中非常重要的组件,其前身为Shark。Shark是Spark上的数据仓库,最初设计成与Hive兼容,但是该项目于2014年开始停止开发,转向Spark SQL。Spark SQL全面继承了Shark,并进行了优化。</p>
<p>Spark SQL增加了SchemaRDD(即带有Schema信息的RDD),使用户可以在Spark SQL中执行SQL语句,数据既可以来自RDD,也可以来自Hive、HDFS、Cassandra等外部数据源,还可以是JSON格式的数据。从Spark1.2 升级到Spark1.3以后,Spark SQL中的SchemaRDD变为了DataFrame,DataFrame相对于SchemaRDD有了较大改变,同时提供了更多好用且方便的API。</p>
<p>Spark SQL可以很好地支持SQL查询,可以编写Spark应用程序使用SQL语句进行数据查询,另一方面,也可以使用标准的数据库连接器(比如JDBC或ODBC)连接Spark进行SQL查询。</p>
<h4 id="62-sparksql使用">6.2 SparkSQL使用</h4>
<p>SparkSQL可以把DataFrame当作临时表,只需使用sql语句便可完成对数据的操作。</p>
<p>要使用DataFrame需要引入隐式转换:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
import spark.implicits._//隐式转换
val result = spark.read.textFile("hdfs://localhost:9000/user/hadoop/word.txt") //读取的文件,支持正则如test.*
.rdd
.flatMap(line => line.split(" "))
.toDF()
result.createOrReplaceTempView("tf_f_word")
spark.sql("select * from tf_f_word").show()
</code></pre></div></div>
<p>引入隐式转换后便可将RDD转换成DataFrame,调用createOrReplaceTempView 方法可将数据作为临时表使用sql语句进行操作。以上代码结果如下:</p>
<p><img src="/images/shenbin3/spark/table_result.png" alt="" /></p>
<p>上面把每行数据都为string,直接转换为DataFrame是没有定义字段名的,当然你可以为他定义字段名,不过还有更简单的方法,把数据map成case class即可,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> case class Word(word:String,count:Int)
def main(args: Array[String]): Unit =
{
val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
import spark.implicits._//隐式转换
val result = spark.read.textFile("file:///D:/Test/test.txt") //读取的文件,支持正则如test.*
.rdd
.flatMap(line => line.split(" "))
.map(word => Word(word,1))
.toDF()
result.createOrReplaceTempView("tf_f_word")
spark.sql("select * from tf_f_word").show()
spark.sql("select word,sum(count) as wordCounts from tf_f_word group by word").show()
}
</code></pre></div></div>
<p>代码结果如下:</p>
<p>tf_f_word 的内容:
<img src="/images/shenbin3/spark/word_table.png" alt="" /></p>
<p>计数结果:
<img src="/images/shenbin3/spark/word_counts.png" alt="" /></p>
<h4 id="63-连接数据库">6.3 连接数据库</h4>
<p>在计算完后许多时候需要把计算结果存储到数据库,DataFrame可以非常方便的存入数据库,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.util.Properties
import org.apache.spark.sql.{SaveMode, SparkSession}
object WordCount
{
case class Word(word:String,count:Int)
def main(args: Array[String]): Unit =
{
val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
import spark.implicits._//隐式转换
val result = spark.read.textFile("file:///D:/Test/test.txt") //读取的文件,支持正则如test.*
.rdd
.flatMap(line => line.split(" "))
.map(word => Word(word,1))
.toDF()
result.createOrReplaceTempView("tf_f_word")
spark.sql("select * from tf_f_word").show()
val wordCounts = spark.sql("select word,sum(count) as count from tf_f_word group by word")
val properties = new Properties()
properties.put("user", "root")//数据库连接用户名密码
properties.put("password", "root")
Class.forName("com.mysql.jdbc.Driver").newInstance()//加载jdbc
wordCounts.write.mode(SaveMode.Append).jdbc("jdbc:mysql://localhost:3306/db","tf_f_word_counts",properties)//写入数据库
}
}
</code></pre></div></div>
<p>运行以上代码,会将处理结果插入本地数据库db库中的tf_f_word_counts表中,表中数据如下:</p>
<p><img src="/images/shenbin3/spark/db_result.png" alt="" /></p>
<p>有时我们也需要从数据库中读取数据,sprkSQL可以直接把表读取成DataFrame,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val spark = SparkSession.builder()
.master("local") //本地模式
.appName("WordCount")
.getOrCreate()
import spark.implicits._//隐式转换
val properties = new Properties()
properties.put("user", "root")//数据库连接用户名密码
properties.put("password", "root")
Class.forName("com.mysql.jdbc.Driver").newInstance()//加载jdbc
val table = spark.read.jdbc("jdbc:mysql://localhost:3306/db","tf_f_word_counts",properties)//查询数据库
table.show()
</code></pre></div></div>
<p>以上代码便能读取刚刚存入数据库的tf_f_word_counts表,运行结果如下:</p>
<p><img src="/images/shenbin3/spark/from_db.png" alt="" /></p>
阿里云服务上Elasticsearch的安装及简单使用(三)
2018-06-08T00:00:00+00:00
http://www.blogways.net/blog/2018/06/08/阿里云服务上Elasticsearch的安装及简单使用(三)
<p> 通过前两篇文章我们知道了如何部署Elasticsearch以及它的一些简单操作,下面我们就模拟一些数据进行操作练习,每一个数据包含个体的id、年龄、性别、地址、邮箱等信息,格式如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"acctId": 1,
"age": 18,
"gender": "M",
"balance": 10000000,
"address": "中国江苏南京",
"email": "test@test.com",
"city": "nj",
"state": "js"
}
</code></pre></div></div>
<p> 通过如下命令进行加载到我们到服务中,其中person.json是我们模拟到数据:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/acct/_bulk?pretty&refresh' -H 'Content-Type:application/json' --data-binary "@person.json"
</code></pre></div></div>
<p>在通过curl ‘localhost:9200/_cat/indices?v’查看索引结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open person 7rAoSSNZQRqtyDb56wR3fQ 5 1 1000 0 474kb 474kb
yellow open god z-R0aYrcTTSCTI4ZdbPv3Q 5 1 1 0 4.4kb 4.4kb
</code></pre></div></div>
<p>其中person是我们刚刚添加到1000数据,数据大小为474kb.<br />
我们用分curl命令和http请求查看。<br />
<strong>1)http方式:</strong><code class="language-plaintext highlighter-rouge">http://47.104.94.172:9200/person/_search?q=*&sort=acctId:asc&pretty</code><br />
<strong>2)curl方式:</strong><code class="language-plaintext highlighter-rouge">curl -XGET 'localhost:9200/person/_search?q=*&sort=acctId:asc&pretty&pretty'</code><br />
在这里使用 _search 端点,然后 q=*(q为query的缩写) 参数命令Elasticsearch匹配索引中的全部文档。sort=acctId:asc 参数表示按 acctId 属性升序排列返回的结果。pretty 参数将返回结果以美观的格式返回。<br />
返回结果默认10条,下面为我们截取开头以及一条数据: :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"took" : 219,
"timed_out" : false,
"_shards" : {
"total" : 5,
"successful" : 5,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : 1000,
"max_score" : null,
"hits" : [
{
"_index" : "person",
"_type" : "acct",
"_id" : "0",
"_score" : null,
"_source" : {
"acctId" : 0,
"age" : 24,
"gender" : "F",
"address" : "中国上海",
"balance" : 9999,
"email" : "qqq@qq.com",
"city" : "sh",
"state" : "sh"
},
"sort" : [
0
]
},{........}
</code></pre></div></div>
<p>返回结果参数解释:<br />
<strong>took :</strong> Elasticsearch执行查询所用的时间(单位:毫秒)
<strong>timed_out :</strong> 是否超时<br />
<strong>_shards : **搜索的分片数量,它的参数包含总数、成功和失败的分片数<br />
</strong>hits :** 搜索结果<br />
<strong>hits.total :</strong> 符合搜索条件的文档数量<br />
<strong>hits.hits :</strong> 实际返回的搜索结果对象数组(默认只返回前10条)<br />
<strong>hits.sort :</strong> 返回结果的排序字段值(如果是按score进行排序,则没有)<br />
<strong>hits._score :</strong>返回文档的匹配得分(得分越高,匹配程度越高,越靠前)<br />
<strong>hits.max_score :</strong> 最大匹配得分<br />
我们也可以使用请求体的方式:
<img src="/images/chenlong/e14.png" alt="e14.png" /><br />
或者curl命令的请求体方式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match_all":{}},
"sort":[
{"acctId":"asc"}
]
}
'
</code></pre></div></div>
<p> 查询的思路类似于sql。query定义查询,match_all全匹配即在索引中搜索所有的文档。在未指定size的时候默认查询10条数据,所以可以通过定义size来控制返回数据的多少。也可以通过from和size来指定返回结果开始和大小,如返回20-40:<img src="/images/chenlong/e15.png" alt="e15.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match_all":{}},
"sort":[
{"acctId":"asc"}
],
"from":20,
"size":20
}
'
</code></pre></div></div>
<p> 对于排序,我们也可以指定某个字段排序之后在返回结果,例如先根据个体的收入排序之后在返回前30条数据:
<img src="/images/chenlong/e16.png" alt="e16.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match_all":{}},
"sort":{"balance":{"order":"asc"}},
"size":30
}
'
</code></pre></div></div>
<p> 通过上面,我们可以看出Elasticsearch提供了一种JSON格式的领域特定语言(Query DSL),可以使用它来执行查询。在上面我们已经进行了简单的使用,下面我们在相对深入一点学习一下这门语言。<br />
在之前的查询结果中,可以看出_source包含了所有属性,在这里我们只要求出查询50-80的acctId,age和gender三个属性:<br />
<img src="/images/chenlong/e17.png" alt="e17.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match_all":{}},
"_source":["acctId","age","gender"],
"from":50,
"size":30
}
'
</code></pre></div></div>
<p> 对于精确查找match_all似乎已经不能满足我们的需求了,我们可以使用match来进行查找。例如查找age为24的个体:<br />
<img src="/images/chenlong/e18.png" alt="e18.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XGET 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match":{"age":24}}
}
'
</code></pre></div></div>
<p> 查询email含有qq.com的信息:<br />
<img src="/images/chenlong/e19.png" alt="e19.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XGET 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match":{"email": "@qq.com"}}
}
'
</code></pre></div></div>
<p> 查询email含有@qq.com或@163.com的信息:
<img src="/images/chenlong/e20.png" alt="e20.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XGET 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match":{"email": "@qq.com @163.com"}}
}
'
</code></pre></div></div>
<p> 另外,match还有叫做match_phrase的亲戚,这个亲戚可以帮我们查处包含“中国江苏”的所有文档:<br />
<img src="/images/chenlong/e21.png" alt="e21.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XGET 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{"match_phrase": { "address": "中国江苏" }}
}
'
</code></pre></div></div>
<p> 介绍完match_full, match_phrase, match之后,我们在介绍一个bool查询,该查询允许使用布尔逻辑将小的查询组成大的查询。在这里我们查询addrss包含“江苏”,“南京”的文档信息:
<img src="/images/chenlong/e22.png" alt="e22.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{
"bool":{
"must":[
{"match":{"address":"江苏"}},
{"match":{"address":"南京"}}
]
}
}
}
'
</code></pre></div></div>
<p> 在上面的例子中,bool和must组成来一个文档的必备条件,有点类似与sql语句的多条件查询,那么假如要查询或的关系时,我们只需要将上面的例子中的must换成should即可。bool和should只要满足一个条件即可。<br />
但是假如我们查询的时候需要多条件不满足时就需要用到bool和must_not了,如查询既不包含“江苏”也不包含“山东”的文档信息:<br />
<img src="/images/chenlong/e23.png" alt="e23.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{
"bool":{
"must_not":[
{"match":{"address":"江苏"}},
{"match":{"address":"山东"}}
]
}
}
}
'
</code></pre></div></div>
<p> 在这里,我们把must和must_not做一下组合,查询address含有“中国江苏”但是age不等于30的文档信息:<br />
<img src="/images/chenlong/e24.png" alt="e24.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{
"bool":{
"must":[
{"match":{"address": "中国江苏"}}
],
"must_not":[
{"match":{"age":30}}
]
}
}
}
'
</code></pre></div></div>
<p> 关于bool组合filter起到过滤的作用。查询age在20-33之间的文档信息:<br />
<img src="/images/chenlong/e25.png" alt="e25.png" /><br />
curl命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/person/_search?pretty' -H 'Content-Type:application/json' -d '
{
"query":{
"bool":{
"must":{"match_all":{}},
"filter":{
"range":{
"age":{
"gte":20,
"lte":33
}
}
}
}
}
}
'
</code></pre></div></div>
<p> 在上面的例子中bool查询在查询部分使用match_all,在过滤部分使用range。可以使用任何的查询来代替查询部分和过滤部分。</p>
阿里云服务上Elasticsearch的安装及简单使用(二)
2018-06-06T00:00:00+00:00
http://www.blogways.net/blog/2018/06/06/阿里云服务上Elasticsearch的安装及简单使用(二)
<p> 在上篇文章中我们介绍了在阿里云上进行Elasticsearch的安装和运行,下面我们接着进行简单的操作。<br />
首先我们先要了解一下Elasticsearch中访问数据的模式。为了能够快速的学习Elasticsearch,我们需要记住这个模式。这个模式可以总结为以下形式:<code class="language-plaintext highlighter-rouge"><REST Verb> /<Index>/<Type>/<ID></code>。</p>
<p><strong>创建一个索引</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPUT 'localhost:9200/god?pretty&pretty'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "god"
}
</code></pre></div></div>
<p><strong>查看索引</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XGET 'localhost:9200/_cat/indices?v&pretty'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> health status index uuid pri rep docs.count docs.deleted store.size pri.store.size
yellow open god Nw4tzWBUTTOPLgKig5wpjw 5 1 0 0 1.1kb 1.1kb
</code></pre></div></div>
<p><strong>删除索引</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl -XDELETE 'localhost:9200/god?pretty&pretty'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"acknowledged" : true
}
</code></pre></div></div>
<p><strong>文档查询</strong><br />
首先我们在上面新建的god的索引中进行添加</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPUT 'localhost:9200/god/doc/1?pretty&pretty' -H 'Content-Type: application/json' -d '{"name": "Tom"}'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"_index" : "god",
"_type" : "doc",
"_id" : "1",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
</code></pre></div></div>
<p>查询:<code class="language-plaintext highlighter-rouge">curl -XGET 'localhost:9200/god/doc/1?pretty&pretty'</code><br />
返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"_index" : "god",
"_type" : "doc",
"_id" : "1",
"_version" : 1,
"found" : true,
"_source" : {
"name" : "Tom"
}
}
</code></pre></div></div>
<p> 首先我们在上面新建的god的索引中进行添加此处由于我们指定了id,所以在查询结果中的id是确定的,假如我们没有指定id,在存储的过程中系统会随机为我们的数据生成一个id,但是当未指定id的时候,需要使用<code class="language-plaintext highlighter-rouge">POST</code>代替<code class="language-plaintext highlighter-rouge">PUT</code>请求。<br />
<strong>文档更新</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/god/doc/1/_update?pretty&pretty' -H 'Content-Type:application/json' -d '{"doc":{"name":"Tom to Tom2", "age":18}}'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"_index" : "god",
"_type" : "doc",
"_id" : "1",
"_version" : 2,
"result" : "updated",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 2
}
</code></pre></div></div>
<p> 有时候为了简便操作,也可以使用脚本进行更新,例如把年龄+10。(<code class="language-plaintext highlighter-rouge">ctx._source</code>指代的是当前需要被更新的source文档。)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl -XPOST 'localhost:9200/god/doc/1/_update?pretty&pretty' -H 'Content-Type:application/json' -d '{"script":"ctx._source.age += 10"}'
</code></pre></div></div>
<p>返回:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {
"_index" : "god",
"_type" : "doc",
"_id" : "1",
"_version" : 3,
"found" : true,
"_source" : {
"name" : "Tom to Tom2",
"age" : 28
}
}
</code></pre></div></div>
<p><strong>文档删除</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> curl -XDELETE 'localhost:9200/customer/doc/2?pretty&pretty'
</code></pre></div></div>
<p><strong>批量处理</strong>
通过使用_bulk API来完成批处理。为了简便我们使用postman来调用es。<br />
<img src="/images/chenlong/e6.png" alt="e6.png" /><br />
<img src="/images/chenlong/e7.png" alt="e7.png" /><br />
<img src="/images/chenlong/e8.png" alt="e8.png" /><br />
也可以使用curl来调用<br />
<img src="/images/chenlong/e9.png" alt="e9.png" /><br />
另外,我们在做一个更新id=1删除id=2的操作,具体操作如下:<br />
首先我们先创建id=2的文档<br />
<img src="/images/chenlong/e10.png" alt="e10.png" /><br />
查询一下2的内容<br />
<img src="/images/chenlong/e11.png" alt="e11.png" /><br />
之前,id=1的文档<br />
<img src="/images/chenlong/e12.png" alt="e12.png" /><br />
更新1删除2
<img src="/images/chenlong/e13.png" alt="e13.png" />
返回结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> {"took":18,"errors":false,"items":[{"update":{"_index":"god","_type":"doc","_id":"1","_version":5,"result":"updated","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":4,"_primary_term":3,"status":200}},{"delete":{"_index":"god","_type":"doc","_id":"2","_version":2,"result":"deleted","_shards":{"total":2,"successful":1,"failed":0},"_seq_no":1,"_primary_term":3,"status":200}}]}
</code></pre></div></div>
阿里云服务上Elasticsearch的安装及简单使用(一)
2018-06-05T00:00:00+00:00
http://www.blogways.net/blog/2018/06/05/阿里云服务上Elasticsearch的安装及简单使用(一)
<p> Elasticsearch是一个高度可伸缩的开源全文搜索和分析引擎。它允许你以近实时的方式快速存储、搜索和分析大量的数据。它通常被用作基础的技术来赋予应用程序复杂的搜索特性和需求。<br />
关于Elasticsearch的一些几本概念,在此不做过多的描述,感兴趣的小伙伴可以自行问度娘。</p>
<h3 id="安装">安装</h3>
<p> Elasticsearch需要至少Java 8。本文写的时候安装的1.8.0_161。Java的安装在不同的平台下是不一样,所以在这里就不再详细介绍。你可以在Oracle官网找到官方推荐的装文档。所以说,当你在安装Elasticsearch之前,请先通过以下命令检查你的Java版本(java -version,然后根据需要安装或升级)。</p>
<h4 id="linux-zip包安装示例">Linux zip包安装示例</h4>
<p> 为了简便,我们使用wget获取zip安装包,命令如下:</p>
<p><code class="language-plaintext highlighter-rouge">wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-6.2.4.zip</code></p>
<p> 使用如下命令进行解压:</p>
<p><code class="language-plaintext highlighter-rouge">unzip elasticsearch-6.2.4.zip </code></p>
<p> 解压之后可以看到很多文件夹,其中bin目录中有启动命令,config文件夹中有配置文件,本文主要讲述单节点的安装,对于集群模式的安装主要是通过配置文件的配置等相关操作进行安装,在此不做过多等描述。</p>
<p> 然后我们通过如下命令进入bin目录:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd elasticsearch-6.2.4/bin/
</code></pre></div></div>
<p> 接下来我们就可以启动我们的单节点集群:<code class="language-plaintext highlighter-rouge"> ./elasticsearch</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>注意:elasticsearch不支持root用户进行启动,所以我们需要在重新添加一个普通用户并赋予相应等权限,命令如下:
useradd elasticUser
chown -R elasticUser:elasticUser /data/elasticsearch-6.2.4
</code></pre></div></div>
<p> 成功运行之后的信息现实如下:<br />
<img src="/images/chenlong/e1.png" alt="e1.png" /><br />
到此我们可以进行本机测试了,查看es的信息。</p>
<p>` curl -XGET ‘localhost:9200/_cat/health?v&pretty’`</p>
<p> 显示信息如下:</p>
<p><img src="/images/chenlong/e2.png" alt="e2.png" /></p>
<p> 我们可以看到我们的名称为“elasticsearch”的集群正在运行,状态标识为<code class="language-plaintext highlighter-rouge">yellow</code>。<br />
无论何时查看集群健康状态,我们会得到中<code class="language-plaintext highlighter-rouge">green</code>、<code class="language-plaintext highlighter-rouge">yellow</code>、<code class="language-plaintext highlighter-rouge">red</code>的任何一个。</p>
<ul>
<li><strong>Green</strong> - 一切运行正常(集群功能齐全)</li>
<li><strong>Yellow</strong> - 所有数据是可以获取的,但是一些复制品还没有被分配(集群功能齐全)</li>
<li><strong>Red</strong> - 一些数据因为一些原因获取不到(集群部分功能不可用)</li>
</ul>
<p>注意:当一个集群处于red状态时,它会通过可用的分片继续提供搜索服务,但是当有未分配的分片时,你需要尽快的修复它。</p>
<p> 为了能够使用外网进行访问我们es服务,我们还需要进行如下的配置:<br />
<code class="language-plaintext highlighter-rouge">vim config/elasticsearch.yml</code></p>
<p> 修改项为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>network.host: 0.0.0.0
http.port: 9200
</code></pre></div></div>
<p> 同时我们可以修改它的发布地址:<code class="language-plaintext highlighter-rouge">network.publish_host: 要发布的IP地址</code><br />
修改完成之后重启es发现我们使用浏览器仍然不能访问我们的es服务,通过查看,原来是阿里云默认没有对外开放端口,在阿里云控制台的安全组配置中添加新的配置。<br />
重启之后发现还是放不了,这个时候忍不住说一声万恶的服务为什么还访问不了!!!<br />
再次检查发现防火墙的原因。打开防火墙端口!打开防火墙端口!打开防火墙端口!重要的事情说三遍!<br />
添加的命令如下:
<code class="language-plaintext highlighter-rouge">firewall-cmd --zone=public --add-port=9200/tcp</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>防火墙的几个简单命令:
启动: systemctl start firewalld 查看状态: systemctl status firewalld
停止: systemctl disable firewalld 禁用: systemctl stop firewalld)
</code></pre></div></div>
<p> 到目前为止才算是真正到完成了。<br />
<img src="/images/chenlong/e3.png" alt="e3.png" /> <br />
或者使用postman查看也可以。<br />
<img src="/images/chenlong/e4.png" alt="e4.png" /><br />
<img src="/images/chenlong/e5.png" alt="e5.png" /></p>
Scala速学
2018-05-28T00:00:00+00:00
http://www.blogways.net/blog/2018/05/28/scala
<h1 id="scala速学">Scala速学#</h1>
<h2 id="一scala简介">一、Scala简介</h2>
<p>Scala是一门现代的多范式编程语言,平滑地集成了面向对象和函数式语言的特性,旨在以简练、优雅的方式来表达常用编程模式。Scala的设计吸收借鉴了许多种编程语言的思想,只有很少量特点是Scala自己独有的。Scala语言的名称来自于“可伸展的语言”,从写个小脚本到建立个大系统的编程任务均可胜任。Scala运行于Java平台(JVM,Java 虚拟机)上,并兼容现有的Java程序,Scala代码可以调用Java方法,访问Java字段,继承Java类和实现Java接口。在面向对象方面,Scala是一门非常纯粹的面向对象编程语言,也就是说,在Scala中,每个值都是对象,每个操作都是方法调用。</p>
<p>Spark的设计目的之一就是使程序编写更快更容易,这也是Spark选择Scala的原因所在。总体而言,Scala具有以下突出的优点:
Scala具备强大的并发性,支持函数式编程,可以更好地支持分布式系统;
Scala语法简洁,能提供优雅的API;
Scala兼容Java,运行速度快,且能融合到Hadoop生态圈中。</p>
<h2 id="二intellij-idea-安装scala插件">二、IntelliJ IDEA 安装scala插件</h2>
<p>首先在 IDEA 中安装scala插件</p>
<p><img src="/images/shenbin3/scala/scala_plugin.jpg" alt="" /></p>
<p>然后下载sdk(选择需要的版本)</p>
<p><img src="/images/shenbin3/scala/sdk.jpg" alt="" />
<img src="/images/shenbin3/scala/sdk_download.jpg" alt="" /></p>
<p>下载完后就可以新建scala项目了,选择jdk和sdk</p>
<p><img src="/images/shenbin3/scala/new_scala_project_1.jpg" alt="" />
<img src="/images/shenbin3/scala/new_scala_project_2.jpg" alt="" /></p>
<p>新建一个Object写一个hello world</p>
<p><img src="/images/shenbin3/scala/hello_world.jpg" alt="" />
<img src="/images/shenbin3/scala/console_hello_world.jpg" alt="" /></p>
<h2 id="三scala基础">三、scala基础</h2>
<h4 id="1申明值和变量">1.申明值和变量</h4>
<p>Scala有两种类型的变量,一种是val,是不可变的,在声明时就必须被初始化,而且初始化以后就不能再赋值;另一种是var,是可变的,声明的时候需要进行初始化,初始化以后还可以再次对其赋值。
如下:</p>
<p><img src="/images/shenbin3/scala/var_val.png" alt="" /></p>
<h4 id="2基本数据类型和操作">2.基本数据类型和操作</h4>
<p>Scala的数据类型包括:Byte、Char、Short、Int、Long、Float、Double和Boolean。和Java不同的是,在Scala中,这些类型都是“类”,并且都是包scala的成员,比如,Int的全名是scala.Int。对于字符串,Scala用java.lang.String类来表示字符串</p>
<p>这里要明确什么是“字面量”?字面量包括整数字面量、浮点数字面量、布尔型字面量、字符字面量、字符串字面量、符号字面量、函数字面量和元组字面量。举例如下:</p>
<p><img src="/images/shenbin3/scala/value.jpg" alt="" /></p>
<p>Scala允许对“字面量”直接执行方法,比如:</p>
<p><img src="/images/shenbin3/scala/pic_1.jpg" alt="" /></p>
<p>在Scala中,可以使用加(+)、减(-) 、乘(*) 、除(/) 、余数(%)等操作符,而且,这些操作符就是方法。例如,5 + 3和(5).+(3)是等价的.</p>
<p>需要注意的是,和Java不同,在Scala中并没有提供++和–-操作符,当需要递增和递减时,可以采用如下+= 和-=</p>
<h4 id="3range">3.Range</h4>
<p>在执行for循环时,我们经常会用到数值序列,比如,i的值从1循环到5,这时就可以采用Range来实现。Range可以支持创建不同数据类型的数值序列,包括Int、Long、Float、Double、Char、BigInt和BigDecimal等。
写法如下:</p>
<p><img src="/images/shenbin3/scala/range.jpg" alt="" /></p>
<h4 id="4打印语句">4.打印语句</h4>
<p>类似c语言,有print , println ,printlnf <br />
写法如下:</p>
<p><img src="/images/shenbin3/scala/print.jpg" alt="" /></p>
<h4 id="5控制结构">5.控制结构</h4>
<p>if语句是许多编程语言中都会用到的控制结构。在Scala中,执行if语句和java类似。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if(true){
println(true)
}else{
println(false)
}
</code></pre></div></div>
<p>Scala中也有和Java类似的while循环语句和for循环语句。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var n = -1
while(n<=0){
println(n)
n+=1
}
for(i <- 1 to 0 by -1){
println(i)
}
</code></pre></div></div>
<h4 id="6数据结构">6.数据结构</h4>
<h6 id="61-数组">6.1 数组</h6>
<p>数组是编程中经常用到的数据结构,一般包括定长数组和变长数组。本教程旨在快速掌握最基础和常用的知识,因此,只介绍定长数组。
定常数组写法如下:
val intValueArr = new Array<a href="3">Int</a> //声明一个长度为3的整型数组,每个数组元素初始化为0
intValueArr(0) = 12 //给第1个数组元素赋值为12
intValueArr(1) = 45 //给第2个数组元素赋值为45
intValueArr(2) = 33 //给第3个数组元素赋值为33</p>
<p>scala中也有和java类似的List,当然你也可以使用java的List,使用时需要主要类型
写法如下:
val list1 = List(1,2,3)
//::可以连接元素与List,:::可以连接List与List</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val list2 = 0::list1:::list1//结果为ArrayBuffer(0, 1, 2, 3, 1, 2, 3)
println(list2.toBuffer)
val javaList = new util.ArrayList[String]()
javaList.add("1")
javaList.add("2")
</code></pre></div></div>
<h6 id="62-元组">6.2 元组</h6>
<p>元组是不同类型的值的聚集。元组和列表不同,列表中各个元素必须是相同类型,而元组可以包含不同类型的元素。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val tuple = ("BigData",2015,45.0)
</code></pre></div></div>
<h6 id="63-set">6.3 Set</h6>
<p>scala中有类似java中的HashSet的set,写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var set = Set(1,2,3,3,4)
set += 1
set += 5
println(set.toBuffer)//结果为ArrayBuffer(5, 1, 2, 3, 4)
</code></pre></div></div>
<h6 id="64-map">6.4 Map</h6>
<p>scala中也有类似java的Map,写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var map = Map("1" ->3,"2" -> 2,"3" -> 1)
map += ("4" -> 0)
println(map.toBuffer)//结果为:ArrayBuffer((1,3), (2,2), (3,1), (4,0))
println(map("4"))//结果为:0
</code></pre></div></div>
<h6 id="65-类">6.5 类</h6>
<p>scala中也有类似java中的class 的class
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class People{
private val secret: String = "xxx"
var name: String = "Jone"
val age: Int = 30
def run(): Unit ={
println(s"${name} is running!")
}
def getSecret(): String ={
secret
}
}
</code></pre></div></div>
<p>使用时类似java:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val pepple = new People()
pepple.name = "Jack"
pepple.run()//结果为:Jack is running!
println(pepple.getSecret())//结果为xxx
</code></pre></div></div>
<h6 id="66-特质">6.6 特质</h6>
<p>Java中提供了接口,允许一个类实现任意数量的接口。在Scala中没有接口的概念,而是提供了“特质(trait)”,它不仅实现了接口的功能,还具备了很多其他的特性。Scala的特质,是代码重用的基本单元,可以同时拥有抽象方法和具体方法。Scala中,一个类只能继承自一个超类,却可以实现多个特质,从而重用特质中的方法和字段,实现了多重继承。</p>
<p>特质的定义和类的定义非常相似,有区别的是,特质定义使用关键字trait。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> trait CarId{
var id: Int
def currentId(): Int //定义了一个抽象方法
}
</code></pre></div></div>
<p>上面定义了一个特质,里面包含一个抽象字段id和抽象方法currentId。注意,抽象方法不需要使用abstract关键字,特质中没有方法体的方法,默认就是抽象方法。</p>
<p>特质定义好以后,就可以使用extends或with关键字把特质混入类中。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class BYDCarId extends CarId{ //使用extends关键字
override var id = 10000 //BYD汽车编号从10000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
class BMWCarId extends CarId{ //使用extends关键字
override var id = 20000 //BMW汽车编号从20000开始
def currentId(): Int = {id += 1; id} //返回汽车编号
}
</code></pre></div></div>
<p>###### 6.7 Object
Scala并没有提供Java那样的静态方法或静态字段,但是,可以采用object关键字实现单例对象,具备和Java静态方法同样的功能。
下面是单例对象的定义:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> object Person {
private var lastId = 0 //一个人的身份编号
def newPersonId() = {
lastId +=1
lastId
}
}
</code></pre></div></div>
<p>从上面的定义可以看出,单例对象的定义和类的定义很相似,明显的区分是,用object关键字,而不是用class关键字。
假设有一个班级人员管理系统,每当新来一个班级成员,就给分配一个身份编号。当第一个人加入班级时,你就可以调用Person.newPersonId()获得身份编号。</p>
<p>在Java中,我们经常需要用到同时包含实例方法和静态方法的类,在Scala中可以通过伴生对象来实现。当单例对象与某个类具有相同的名称时,它被称为这个类的“伴生对象”。类和它的伴生对象必须存在于同一个文件中,而且可以相互访问私有成员(字段和方法)。
示例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class Person {
private val id = Person.newPersonId() //调用了伴生对象中的方法
private var name = ""
def this(name: String) {
this()
this.name = name
}
def info() { printf("The id of %s is %d.\n",name,id)}
}
object Person {
private var lastId = 0 //一个人的身份编号
private def newPersonId() = {
lastId +=1
lastId
}
def main(args: Array[String]){
val person1 = new Person("Ziyu")
val person2 = new Person("Minxing")
person1.info()
person2.info()
}
}
</code></pre></div></div>
<p>从上面结果可以看出,伴生对象中定义的newPersonId()实际上就实现了Java中静态(static)方法的功能,所以,实例化对象person1调用newPersonId()返回的值是1,实例化对象person2调用newPersonId()返回的值是2。我们说过,Scala源代码编译后都会变成JVM字节码,实际上,在编译上面的源代码文件以后,在Scala里面的class和object在Java层面都会被合二为一,class里面的成员成了实例成员,object成员成了static成员。</p>
<h4 id="7模式匹配">7.模式匹配</h4>
<p>Java中有switch-case语句,但是,只能按顺序匹配简单的数据类型和表达式。相对而言,Scala中的模式匹配的功能则要强大得多,可以应用到switch语句、类型检查、“解构”等多种场合。
Scala的模式匹配最常用于match语句中。下面是一个简单的整型值的匹配实例。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val colorNum = 1
val colorStr = colorNum match {
case 1 => "red"
case 2 => "green"
case 3 => "yellow"
case _ => "Not Allowed"
}
println(colorStr)
</code></pre></div></div>
<p>Scala可以对表达式的类型进行匹配。写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> for (elem <- List(9,12.3,"Spark","Hadoop",'Hello)){
val str = elem match{
case i: Int => i + " is an int value."
case d: Double => d + " is a double value."
case "Spark"=> "Spark is found."
case s: String => s + " is a string value."
case _ => "This is an unexpected value."
}
println(str)
}
</code></pre></div></div>
<h4 id="8匿名函数lambda表达式">8.匿名函数、Lambda表达式</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>传统类型的函数,定义的语法和我们之前介绍过的定义“类中的方法”类似(实际上,定义函数最常用的方法是作为某个对象的成员,这种函数被称为方法),写法如下:
def counter(value: Int): Int = { value += 1}
</code></pre></div></div>
<p>函数的也可以作为函数的入参,示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> def sum(f: Int => Int, a: Int, b: Int): Int ={
if(a > b) 0 else f(a) + sum(f, a+1, b)
} 在调用以上函数时需要传入一个函数,此时便可以传入一个没有定义的匿名函数,示例如下:
var sumValue = sum(a => a+1 ,2, 3)
</code></pre></div></div>
<p>入参: a => a+1 便是一个匿名函数,这种结构的匿名函数我们经常称为“Lambda表达式”。“Lambda表达式”的形式如下:
(参数) => {表达式} //如果参数只有一个,参数的圆括号可以省略,如果表达式只有一行,花括号可以省略</p>
<p>为了让函数字面量更加简洁,我们可以使用下划线作为一个或多个参数的占位符,只要每个参数在函数字面量内仅出现一次。
例如在调用刚才的sum()方法是可以这么写:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var sumValue = sum(_+1 ,2, 3)
</code></pre></div></div>
<h4 id="9集合操作">9.集合操作</h4>
<h6 id="91遍历">9.1遍历</h6>
<p>List,Set,Tuple 等 数据结构最常用的操作就是遍历操作了
List,Set等的遍历都可以用foreach方法遍历,写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val list = List(1, 2, 3, 4, 5)
list.foreach(elem => println(elem)) //本行语句甚至可以简写为list.foreach(println),或者写成:list foreach println
</code></pre></div></div>
<p>Map的遍历方法,写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val mapx = Map (1 ->"a",2 -> "b" ,3 -> "c")
mapx foreach {case(k,v) => println(k+":"+v)}
mapx.foreach( kv => println("key:"+kv._1+",value:"+kv._2) )
mapx foreach {kv => println("key:"+kv._1+",value:"+kv._2)}
</code></pre></div></div>
<h6 id="92map操作">9.2map操作</h6>
<p>map操作是针对集合的典型变换操作,它将某个函数应用到集合中的每个元素,并产生一个结果集合。
下面有个示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val list = List(1, -2, 3, -4, 5)
val list_positive = list.map( v => {
var res = v
if(v<0){
res= -v
}
res
})
println(list_positive.toBuffer)//结果为:ArrayBuffer(1, 2, 3, 4, 5)
</code></pre></div></div>
<p>flatMap是map的一种扩展。在flatMap中,我们会传入一个函数,该函数对每个输入都会返回一个集合(而不是一个元素),然后,flatMap把生成的多个集合“拍扁”成为一个集合。
示例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val l = List("1,2,3,4","5","6,7,8","9")
val res = l.flatMap(v => v match {
case str:String => str.split(",")
})
println(res.toBuffer)//结果为ArrayBuffer(1, 2, 3, 4, 5, 6, 7, 8, 9) 以上代码会把l的每个元素都进行split然后把结果连成一个list
</code></pre></div></div>
<h6 id="93filter操作">9.3filter操作</h6>
<p>在实际编程中,我们经常会用到一种操作,遍历一个集合并从中获取满足指定条件的元素组成一个新的集合。Scala中可以通过filter操作来实现。
用法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val list = List(1, -2, 3, -4, 5)
val result = list.filter(v => v>0).sum
println(result)//结果为9
</code></pre></div></div>
<p>以上代码就是把list中所有的大于0 的数进行sum</p>
<h6 id="94reduce">9.4reduce</h6>
<p>在Scala中,我们可以使用reduce这种二元操作对集合中的元素进行归约。
reduce包含reduceLeft和reduceRight两种操作,前者从集合的头部开始操作,后者从集合的尾部开始操作。
写法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> val list = List(1, -2, 3, -4, 5)
val result_left = list.reduceLeft(_-_)
val result_right = list.reduceRight(_-_)
println(result_left)//结果为-1
println(result_right)//结果为15
</code></pre></div></div>
<p>以上代码中,reduceLeft是从list的左侧开始往右进行相减,而reduceRight则是从最右侧开始往左进行相减。</p>
webservice入门(二)
2018-04-28T00:00:00+00:00
http://www.blogways.net/blog/2018/04/28/Webservice入门(二)
<h3 id="一服务端开发">一、服务端开发</h3>
<ol>
<li>编写SEI(Service Endpoint Interface),SEI在webservice中称为portType,在java中称为接口,代码如下:</li>
</ol>
<p><img src="/images/chenlong/wb_02.png" alt="webservice_02.png" /></p>
<ol>
<li>编写SEI实现类,并作为webservice提供服务类,代码如下:</li>
</ol>
<p><img src="/images/chenlong/wb_03.png" alt="webservice_03.png" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>SEI实现类中至少要有一个非静态的公开方法需要作为webservice服务方法。
public class 上边要加上@WebService
</code></pre></div></div>
<ol>
<li>查看wsdl</li>
</ol>
<p>地址栏中输入<code class="language-plaintext highlighter-rouge">http://127.0.0.1:1234/weather?wsdl</code>查看</p>
<h3 id="二wsimport生产客户端调用类">二、wsimport生产客户端调用类</h3>
<p>wsimport具体使用可以使用命令 <code class="language-plaintext highlighter-rouge">wsimport -help</code>或者自行百度学习。</p>
<p>新建一个名为wsimport的工程,cmd命令进入该工程的src目录,输入如下命令:<code class="language-plaintext highlighter-rouge">wsimport -s . http://127.0.0.1:1234/weather?wsdl</code>,刷新该工程,将src下生成.java文件代码Copy到webservice客户端工程中。</p>
<h3 id="三客户端编写">三、客户端编写</h3>
<p>代码如下:</p>
<p><img src="/images/chenlong/wb_04.png" alt="webservice_04.png" /></p>
<p>运行结果:</p>
<p><img src="/images/chenlong/wb_06.png" alt="webservice_06.png" /></p>
<p>最后,附上上述三个工程的结构:</p>
<p><img src="/images/chenlong/wb_05.png" alt="webservice_05.png" /></p>
webservice入门(一)
2018-04-25T00:00:00+00:00
http://www.blogways.net/blog/2018/04/25/Webservice入门(一)
<h3 id="一webservice基本概念">一、webservice基本概念</h3>
<p>Web Service,即web服务,也叫XML Web Service WebService,它是一种跨编程语言和跨操作系统平台的远程调用技术即跨平台远程调用技术。是一种可以接收从Internet或者Intranet上的其它系统中传递过来的请求,轻量级的独立的通讯技术。是通过SOAP在Web上提供的软件服务,使用WSDL文件进行说明,并通过UDDI进行注册。</p>
<ul>
<li>
<p><strong>XML</strong>:(Extensible Markup Language)扩展型可标记语言。面向短期的临时数据处理、面向万维网络,是Soap的基础。</p>
</li>
<li>
<p><strong>Soap</strong>:(Simple Object Access Protocol)简单对象存取协议。是XML Web Service 的通信协议。当用户通过UDDI找到你的WSDL描述文档后,他通过可以SOAP调用你建立的Web服务中的一个或多个操作。SOAP是XML文档形式的调用方法的规范,它可以支持不同的底层接口,像HTTP(S)或者SMTP。</p>
</li>
<li>
<p><strong>WSDL</strong>:(Web Services Description Language) WSDL 文件是一个 XML 文档,用于说明一组 SOAP 消息以及如何交换这些消息。大多数情况下由软件自动生成和使用。</p>
</li>
<li>
<p><strong>UDDI</strong> (Universal Description, Discovery, and Integration) 是一个主要针对Web服务供应商和使用者的新项目。在用户能够调用Web服务之前,必须确定这个服务内包含哪些商务方法,找到被调用的接口定义,还要在服务端来编制软件,UDDI是一种根据描述文档来引导系统查找相应服务的机制。UDDI利用SOAP消息机制(标准的XML/HTTP)来发布,编辑,浏览以及查找注册信息。它采用XML格式来封装各种不同类型的数据,并且发送到注册中心或者由注册中心来返回需要的数据。</p>
</li>
</ul>
<h3 id="二调用原理">二、调用原理</h3>
<p><img src="/images/chenlong/wb_01.png" alt="webservice_01.png" /></p>
<p>完整webservices的实现步骤如下:</p>
<ul>
<li>
<p>Web服务提供者设计实现Web服务,并将调试正确后的Web服务通过Web服务中介者发布,并在UDDI注册中心注册; (发布)</p>
</li>
<li>
<p>Web服务请求者向Web服务中介者请求特定的服务,中介者根据请求查询UDDI注册中心,为请求者寻找满足请求的服务; (发现)</p>
</li>
<li>
<p>Web服务中介者向Web服务请求者返回满足条件的Web服务描述信息,该描述信息用WSDL写成,各种支持Web服务的机器都能阅读;(发现)</p>
</li>
<li>
<p>利用从Web服务中介者返回的描述信息生成相应的SOAP消息,发送给Web服务提供者,以实现Web服务的调用;(绑定)</p>
</li>
<li>
<p>Web服务提供者按SOAP消息执行相应的Web服务,并将服务结果返回给Web服务请求者。(绑定)</p>
</li>
</ul>
<h3 id="三webservice开发规范">三、webservice开发规范</h3>
<p>JAVA 中共有三种WebService 规范,分别是JAX-WS(JAX-RPC)、JAXM&SAAJ、JAX-RS。</p>
<p>下面来分别简要的介绍一下这三个规范。</p>
<h5 id="1jax-ws">1、JAX-WS</h5>
<p><code class="language-plaintext highlighter-rouge">JAX-WS</code> 的全称为 Java API for XML-Based Webservices ,早期的基于SOAP 的JAVA 的Web 服务规范JAX-RPC(Java API For XML-Remote Procedure Call)目前已经被JAX-WS 规范取代。从java5开始支持JAX-WS2.0版本,Jdk1.6.0_13以后的版本支持2.1版本,jdk1.7支持2.2版本。</p>
<h5 id="21111--jaxmsaaj">2、1.1.1.1 JAXM&SAAJ</h5>
<p><code class="language-plaintext highlighter-rouge">JAXM(JAVA API For XML Message)</code>主要定义了包含了发送和接收消息所需的API,SAAJ(SOAP With Attachment API For Java,JSR 67)是与JAXM 搭配使用的API,为构建SOAP 包和解析SOAP 包提供了重要的支持,支持附件传输等,JAXM&SAAJ 与JAX-WS 都是基于SOAP 的Web 服务,相比之下JAXM&SAAJ 暴漏了SOAP更多的底层细节,编码比较麻烦,而JAX-WS 更加抽象,隐藏了更多的细节,更加面向对象,实现起来你基本上不需要关心SOAP 的任何细节。</p>
<h5 id="31111-jax-rs">3、1.1.1.1 JAX-RS</h5>
<p><code class="language-plaintext highlighter-rouge">JAX-RS </code>是JAVA 针对REST(Representation State Transfer)风格制定的一套Web 服务规范,由于推出的较晚,该规范(JSR 311,目前JAX-RS 的版本为1.0)并未随JDK1.6 一起发行。</p>
<h3 id="三webservice的优缺点及应用场景">三、webservice的优缺点及应用场景</h3>
<h5 id="优点-">优点 :</h5>
<ul>
<li>采用xml支持跨平台远程调用。</li>
<li>基于http的soap协议,可跨越防火墙。</li>
<li>支持面向对象开发。</li>
<li>有利于软件和数据重用,实现松耦合。</li>
</ul>
<h5 id="缺点-">缺点 :</h5>
<p>由于soap是基于xml传输,本身使用xml传输会传输一些无关的东西从而效率不高,随着soap协议的完善,soap协议增加了许多内容,这样就导致了使用soap协议进行数据传输的效率不高。</p>
<h5 id="应用场景-">应用场景 :</h5>
<ol>
<li>宏观
<blockquote>
<p>用于软件集成和复用</p>
</blockquote>
</li>
<li>微观
<blockquote>
<p>用于公开接口服务,如:便民网站的天气查询接口、火车时刻查询接口等。
用于内部接口服务,一个大的系统平台是由若干个系统组成,系统与系统之间存在数据访问需求,为了减少系统与系统之间的耦合性可以将接口抽取出来提供单独的接口服务供它系统调用。
服务端已经确定使用webservice,客户端无法选择,只能使用webservice。</p>
</blockquote>
</li>
</ol>
Liquid模板学习
2018-03-13T00:00:00+00:00
http://www.blogways.net/blog/2018/03/13/liquid
<h1 id="liquid模板的学习和使用">Liquid模板的学习和使用#</h1>
<h2 id="一liquid的认识">一、Liquid的认识</h2>
<p>第一次接触<code class="language-plaintext highlighter-rouge">Liquid</code>是在公司博客修改上,刚开始使用觉得很陌生,也查阅了很多资料去学习,在折腾博客的时候, 遇到一些<code class="language-plaintext highlighter-rouge">jekyll</code>问题,正确来说应该是<code class="language-plaintext highlighter-rouge">Liquid</code>用法的问题。</p>
<p><code class="language-plaintext highlighter-rouge">Liquid</code>有两种标记类型:<code class="language-plaintext highlighter-rouge">Output</code> 和 <code class="language-plaintext highlighter-rouge"> Tag</code>.</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">Output</code>标记,用于输出文本,格式采用 ``</li>
<li><code class="language-plaintext highlighter-rouge">Tag</code>标记,用于执行命令或者处理 格式: <code class="language-plaintext highlighter-rouge">{\% 一对尖括号内一对百分号 \%}</code></li>
</ul>
<p>我的理解就是: 对比<code class="language-plaintext highlighter-rouge">jsp</code>格式来说,
<code class="language-plaintext highlighter-rouge">Output</code>相当于 <code class="language-plaintext highlighter-rouge"> <%=variable%></code>,就是用来输出变量值展示在页面上;
<code class="language-plaintext highlighter-rouge">Tag</code>相当于<code class="language-plaintext highlighter-rouge"><% int i=2; %></code>,一种数据处理和定义,但不做输出效果.</p>
<p>通过<code class="language-plaintext highlighter-rouge">Tag</code>的数据处理得到想要的数据再通过<code class="language-plaintext highlighter-rouge">Output</code>输出达到使用者需要的效果和<code class="language-plaintext highlighter-rouge">jsp</code>相似;</p>
<h2 id="二output">二、Output</h2>
<p>例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello
Hello
Hello tobi
</code></pre></div></div>
<h4 id="高级output-filters过滤器">高级Output: Filters//过滤器</h4>
<p>Filters过滤器,数据处理的操作方法.
过滤器的第一个参数,往往是过滤器运算符’|’左边的<code class="language-plaintext highlighter-rouge">Output</code>,而过滤器的返回值,是通过过滤运算符右边的操作所得到的,过滤器可以叠加操作,最终得到该<code class="language-plaintext highlighter-rouge">Output</code>所要输出的值。(这段我见解,翻译不过来
= =)
如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hello TOBI
Hello tobi has 4 letters!
Hello 2021 Aug
</code></pre></div></div>
<h4 id="标准过滤器">标准过滤器</h4>
<ul>
<li><code class="language-plaintext highlighter-rouge">date</code> - 格式化时间</li>
<li><code class="language-plaintext highlighter-rouge">capitalize</code> - 输出字符串,字符串(句子)首字母大写 e.g. 假设tb为”hello world”<code class="language-plaintext highlighter-rouge"> #=> 'Hello world'</code></li>
<li><code class="language-plaintext highlighter-rouge">downcase</code> - 转换小写</li>
<li><code class="language-plaintext highlighter-rouge">upcase</code> - 转换大写</li>
<li><code class="language-plaintext highlighter-rouge">first</code> - 获取数组的第一个元素</li>
<li><code class="language-plaintext highlighter-rouge">last</code> - 获取数组的最后一个元素</li>
<li><code class="language-plaintext highlighter-rouge">join</code> - 用指定的字符拼接数组元素</li>
<li><code class="language-plaintext highlighter-rouge">sort</code> - 排序数组</li>
<li><code class="language-plaintext highlighter-rouge">map</code> - map/collect an array on a given property</li>
<li><code class="language-plaintext highlighter-rouge">size</code> - 返回数组大小</li>
<li><code class="language-plaintext highlighter-rouge">escape</code> - 转移字符串</li>
<li><code class="language-plaintext highlighter-rouge">escape_once</code> - returns an escaped version of html without affecting existing escaped entities</li>
<li><code class="language-plaintext highlighter-rouge">strip_html</code> - 除去字符串中的html标签?</li>
<li><code class="language-plaintext highlighter-rouge">strip_newlines</code> - 除去字符串中的回车?</li>
<li><code class="language-plaintext highlighter-rouge">newline_to_br</code> - 将所有的回车”\n” 转换成”<br />”?</li>
<li><code class="language-plaintext highlighter-rouge">replace</code> - 替换所有匹配内容 e.g.<code class="language-plaintext highlighter-rouge">barbar #=> 'barbar'</code></li>
<li><code class="language-plaintext highlighter-rouge">replace_first</code> - 替换第一个匹配内容 e.g.<code class="language-plaintext highlighter-rouge">barfor #=> 'barfor'</code></li>
<li><code class="language-plaintext highlighter-rouge">remove</code> - 移除所有匹配内容 e.g.<code class="language-plaintext highlighter-rouge">barbar #=> 'barbar'</code></li>
<li><code class="language-plaintext highlighter-rouge">remove_first</code> - 移除第一个匹配内容 e.g.<code class="language-plaintext highlighter-rouge">barforbar #=> 'barforbar'</code></li>
<li><code class="language-plaintext highlighter-rouge">truncate</code> - truncate a string down to x characters</li>
<li><code class="language-plaintext highlighter-rouge">truncatewords</code> - truncate a string down to x words</li>
<li><code class="language-plaintext highlighter-rouge">prepend</code> - 在字符串前面加上内容 e.g.<code class="language-plaintext highlighter-rouge">farbar #=> 'farbar'</code></li>
<li><code class="language-plaintext highlighter-rouge">append</code> - 字符串后面加上内容 e.g.<code class="language-plaintext highlighter-rouge">barfoo#=> 'barfoo'</code></li>
<li><code class="language-plaintext highlighter-rouge">minus</code> - 减法 e.g. <code class="language-plaintext highlighter-rouge">2 #=>2</code></li>
<li><code class="language-plaintext highlighter-rouge">plus</code> - 加法 e.g. <code class="language-plaintext highlighter-rouge">6 #=> 6</code></li>
<li><code class="language-plaintext highlighter-rouge">times</code> - 乘法 e.g. <code class="language-plaintext highlighter-rouge">20 #=> 20</code></li>
<li><code class="language-plaintext highlighter-rouge">divided_by</code> - 除法 e.g. <code class="language-plaintext highlighter-rouge">5 #=> 5</code></li>
<li><code class="language-plaintext highlighter-rouge">split</code> - 分割字符串 e.g.<code class="language-plaintext highlighter-rouge">ab #=> ['a','b']</code></li>
<li><code class="language-plaintext highlighter-rouge">modulo</code> - 取余 e.g. <code class="language-plaintext highlighter-rouge">1 #=> 1</code></li>
</ul>
<h2 id="三tags">三、Tags</h2>
<p><code class="language-plaintext highlighter-rouge">Tag</code>在模板中起到处理逻辑的作用。</p>
<p>下面是目前支持的<code class="language-plaintext highlighter-rouge">Tag</code>:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">assign</code> - 定义变量 e.g. `` 定义了变量<code class="language-plaintext highlighter-rouge">tt</code>数值为1</li>
<li><code class="language-plaintext highlighter-rouge">capture</code> - <code class="language-plaintext highlighter-rouge">Block tag</code>为变量赋值 e.g.`` 将<code class="language-plaintext highlighter-rouge">tt</code>的值赋给 <code class="language-plaintext highlighter-rouge">dont</code></li>
<li><code class="language-plaintext highlighter-rouge">case</code> - <code class="language-plaintext highlighter-rouge">Block tag</code> its the standard case…when block</li>
<li><code class="language-plaintext highlighter-rouge">comment</code> - <code class="language-plaintext highlighter-rouge">Block tag</code> 注释</li>
<li><code class="language-plaintext highlighter-rouge">cycle</code> - Cycle is usually used within a loop to alternate between values, like colors or DOM classes.</li>
<li><code class="language-plaintext highlighter-rouge">for</code> - for循环<code class="language-plaintext highlighter-rouge">block</code></li>
<li><code class="language-plaintext highlighter-rouge">if</code> - 判断<code class="language-plaintext highlighter-rouge">block</code></li>
<li><code class="language-plaintext highlighter-rouge">include</code> - 引入模板</li>
<li><code class="language-plaintext highlighter-rouge">raw</code> - 转义内容<code class="language-plaintext highlighter-rouge">tag</code> e.g.<code class="language-plaintext highlighter-rouge">{{ this }} #=> ''</code></li>
<li><code class="language-plaintext highlighter-rouge">unless</code> - Mirror of if statement</li>
</ul>
<h4 id="comments">Comments</h4>
<p>注释隐藏</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>We made 1 million dollars this year #### Raw
</code></pre></div></div>
<p>当包裹内容出现冲突语法时,不会执行其处理。</p>
<p><img src="/images/weixc1.png" alt="" /></p>
<h4 id="ifelse">if/else</h4>
<p>和java逻辑很相似就是多了大括号和百分号<code class="language-plaintext highlighter-rouge">&&</code>变成了<code class="language-plaintext highlighter-rouge">and</code>,<code class="language-plaintext highlighter-rouge">||</code>变成了<code class="language-plaintext highlighter-rouge">or</code> ;具体的用法如下所示:</p>
<p>e.g.</p>
<p><img src="/images/weixc2.png" alt="" /></p>
<p><img src="/images/weixc3.png" alt="" /></p>
<h4 id="case-statement">Case Statement</h4>
<p>多条件查询,创建一个开关表达式,用于将一个变量和多个不同值进行比较。<code class="language-plaintext highlighter-rouge">case</code> 用于初始化一个开关表达式,<code class="language-plaintext highlighter-rouge">when</code> 用于比较他们的值,如下所示:</p>
<p><img src="/images/weixc4.png" alt="" /></p>
<h4 id="cycle">Cycle</h4>
<p>循环列举</p>
<p><img src="/images/weixc5.png" alt="" /></p>
<p>结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>one
two
three
one
</code></pre></div></div>
<p>可以通过命名分组:</p>
<p><img src="/images/weixc6.png" alt="" /></p>
<p>结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>one
two
one
two
</code></pre></div></div>
<h4 id="for-循环">for 循环</h4>
<p>循环集合:</p>
<p><img src="/images/weixc7.png" alt="" /></p>
<p>遍历<code class="language-plaintext highlighter-rouge">hash</code>时:<code class="language-plaintext highlighter-rouge">item[0]</code>包含键,<code class="language-plaintext highlighter-rouge">item[1]</code>包含值</p>
<p><img src="/images/weixucheng.png" alt="" /></p>
<p>for循环时,下列变量可以辅助使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>forloop.length # => length of the entire for loop
forloop.index # => index of the current iteration
forloop.index0 # => index of the current iteration (zero based)
forloop.rindex # => how many items are still left?
forloop.rindex0 # => how many items are still left? (zero based)
forloop.first # => is this the first iteration?
forloop.last # => is this the last iteration?
</code></pre></div></div>
<p>还有一些变量可以用来处理循环时选择性处理:
<code class="language-plaintext highlighter-rouge">limit:int</code> - 限制遍历个数
<code class="language-plaintext highlighter-rouge">offset:int</code> - 从第n个数开始遍历</p>
<p><img src="/images/weixucheng1.png" alt="" /></p>
<p>反序遍历:</p>
<p><img src="/images/weixucheng2.png" alt="" /></p>
<p>除了遍历集合,还可以定义一个范围的数字来遍历:</p>
<p><img src="/images/weixucheng3.png" alt="" /></p>
<h4 id="变量赋值">变量赋值</h4>
<p>赋值变量:</p>
<p><img src="/images/weixucheng4.png" alt="" /></p>
<p>还可以赋值布尔值:</p>
<p><img src="/images/weixucheng5.png" alt="" /></p>
<p>赋值处理过的数据:可以用<code class="language-plaintext highlighter-rouge">capture</code></p>
<p><img src="/images/weixucheng6.png" alt="" /></p>
LESS简述
2018-03-13T00:00:00+00:00
http://www.blogways.net/blog/2018/03/13/LESS-introduction
<h1 id="less简述">LESS简述</h1>
<hr />
<h2 id="什么是less">什么是LESS</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Less 是一门 CSS 预处理语言,它扩展了 CSS 语言,增加了变量、Mixin、函数等特性。
</code></pre></div></div>
<h2 id="变量">变量</h2>
<ol>
<li>书写形式: @变量名 例如:@fjore,@var</li>
<li>
<p>可用变量名定义为变量即变量名作为变量的值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @fnord:"I am fnord";@var:"fnord";
则 @@var 输出"I am fnord";
</code></pre></div> </div>
</li>
<li>LESS中的变量为完全的‘常量’,所以只能定义一次</li>
<li>
<p>@arguments 变量:@arguments包含了所有传递进来的参数</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .box-shadow (@x: 0, @y: 0, @blur: 1px, @color: #000) {
box-shadow: @arguments;
-moz-box-shadow: @arguments;
-webkit-box-shadow: @arguments;
}
</code></pre></div> </div>
</li>
</ol>
<h2 id="混合">混合</h2>
<ol>
<li>定义: 定义一些通用的属性集为一个class,然后在另一个class中去调用这些属性</li>
<li>
<p>形式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .bordered {border-top: dotted 1px black;
border-bottom: solid 2px black;
}
#menu a {
color: #111;
.bordered;
} a标签就会包含.bordered的属性 通用属性要定义为Class属性
</code></pre></div> </div>
</li>
</ol>
<h2 id="带参数的混合">带参数的混合</h2>
<ol>
<li>定义:像函数一样定义一个带参数的属性集合:</li>
<li>
<p>形式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .border-radius (@radius) {
border-radius: @radius;
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
}
</code></pre></div> </div>
<p>调用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #header {
.border-radius(4px);
}
.button {
.border-radius(6px);
} 3.默认值的形参:
.border-radius (@radius: 5px) {
border-radius: @radius;
-moz-border-radius: @radius;
-webkit-border-radius: @radius;
} 调用时
#header {
.border-radius;//不带参数默认5px;
} 4.定义不带参数属性集合,如果你想隐藏这个属性集合,不让它暴露到CSS中去,但是你还想在其他的属性集合中引用
.wrap () {
text-wrap: wrap;
white-space: pre-wrap;
white-space: -moz-pre-wrap;
word-wrap: break-word;
} **即在类名后加括号,不加括号会被认为是一般类的属性集合,编译css时会显示在css文件中;**
</code></pre></div> </div>
</li>
</ol>
<h2 id="模式匹配">模式匹配</h2>
<ol>
<li>定义:有些情况下,我们想根据传入的参数来改变混合的默认呈现</li>
<li>
<p>形式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .mixin (dark, @color) {
color: darken(@color, 10%);
}
.mixin (light, @color) {
color: lighten(@color, 10%);
}
.mixin (@_, @color) {
display: block;
}
</code></pre></div> </div>
</li>
<li>只有被匹配的混合才会被使用。变量可以匹配任意的传入值,而变量以外的固定值就仅仅匹配与其相等的传入值。</li>
<li>
<p>也可以匹配多个参数:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .mixin (@a) {
color: @a;
}
.mixin (@a, @b) {
color: fade(@a, @b);
}
</code></pre></div> </div>
</li>
</ol>
<h2 id="导引">导引</h2>
<ol>
<li>根据表达式进行匹配,而非根据值和参数匹配时;when关键字用以定义一个导引序列。</li>
<li>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.mixin (@a) when (lightness(@a) >= 50%) {
background-color: black;
}
.mixin (@a) when (lightness(@a) < 50%) {
background-color: white;
}
.mixin (@a) {
color: @a;
}
</code></pre></div> </div>
</li>
<li>导引中可用的全部比较运算有: > >= = =< <。此外,关键字true只表示布尔真值,除去关键字true以外的值都被视示布尔假:</li>
<li>导引序列使用逗号‘,’—分割,当且仅当所有条件都符合时,才会被视为匹配成功。<br />
例:.mixin (@a) when (@a > 10), (@a < -10) { … }</li>
<li>想基于值的类型进行匹配,我们就可以使用is*函式:<br />
.mixin (@a, @b: 0) when (isnumber(@b)) { … };<br />
常见的检测函式:
iscolor<br />
isnumber<br />
isstring<br />
iskeyword<br />
isurl<br />
ispixel<br />
ispercentage<br />
isem</li>
<li>在导引序列中可以使用and关键字实现与条件;使用not关键字实现或条件</li>
</ol>
<h2 id="嵌套规则">嵌套规则</h2>
<ol>
<li>
<p>LESS 可以让我们以嵌套的方式编写层叠样式</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #header { color: black; }
#header .navigation {
font-size: 12px;
}
#header .logo {
width: 300px;
}
#header .logo:hover {
text-decoration: none;
} 可写成:
#header {
color: black;
.navigation {
font-size: 12px;
}
.logo {
width: 300px;
&:hover { text-decoration: none }
}
}
</code></pre></div> </div>
</li>
<li>
<p>注意 & 符号的使用—如果你想写串联选择器,而不是写后代选择器,就可以用到&了. 这点对伪类尤其有用如 :hover 和 :focus.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .bordered {
&.float {
float: left;
}
.top {
margin: 5px;
}
} 会输出:
.bordered.float {
float: left;
}
.bordered .top {
margin: 5px;
}
</code></pre></div> </div>
</li>
<li>
<p>串联选择器与后代选择器的区别:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 串联选择器:作用在同一个标签上
<div class=”a” id ="qq"><span>look at the color</span></div>
css: #qq.a{
….
}
后代选择器:作用在不同标签上
<div id ="qq"><span class=”a”>look at the color</span></div>
css: #qq .a{
}
注意#qq .a 之前有空格
</code></pre></div> </div>
</li>
</ol>
<h2 id="运算">运算</h2>
<p>1.任何数字、颜色或者变量都可以参与运算</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@base: 5%;
@filler: @base * 2;
@other: @base + @filler;
color: #888 / 4;
background-color: @base-color + #111;
height: 100% / 2 + @filler;
</code></pre></div></div>
<h2 id="命名空间">命名空间</h2>
<p>1.命名空间与混合的区别
混合类似于 类选择器 以.开头
命名空间 以 # 开头</p>
<p>2.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#bundle {
.button () {
display: block;
border: 1px solid black;
background-color: grey;
&:hover { background-color: white }
}
.tab { ... }
.citation { ... }
} 使用时
#header a {
color: orange;
#bundle > .button;
}
</code></pre></div></div>
<h2 id="作用域">作用域</h2>
<ol>
<li>
<p>LESS 中的作用域跟其他编程语言非常类似,首先会从本地查找变量或者混合模块,如果没找到的话会去父级作用域中查找,直到找到为止.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @var: red;
#page {
@var: white;
#header {
color: @var; // white
}
}
#footer {
color: @var; // red
}
</code></pre></div> </div>
</li>
</ol>
<h2 id="避免编译">避免编译</h2>
<ol>
<li>有时候我们需要输出一些不正确的CSS语法或者使用一些 LESS不认识的专有语法.</li>
</ol>
<p>要输出这样的值我们可以在字符串前加上一个 ~ :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> .class {
filter: ~"ms:alwaysHasItsOwnSyntax.For.Stuff()";
} 我们可以将要避免编译的值用 “”包含起来,输出结果为:
.class {
filter: ms:alwaysHasItsOwnSyntax.For.Stuff();
}
</code></pre></div></div>
<h2 id="注释">注释</h2>
<p>CSS 形式的注释在 LESS 中是依然保留的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/* Hello, I'm a CSS-style comment */
.class { color: black } LESS 同样也支持双斜线的注释, 但是编译成 CSS 的时候自动过滤掉:
// Hi, I'm a silent comment, I won't show up in your CSS
.class { color: white }
</code></pre></div></div>
<h2 id="字符串插值">字符串插值</h2>
<p>变量可以用类似ruby和php的方式嵌入到字符串中,像@{name}这样的结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@base-url: "http://assets.fnord.com";
background-image: url("@{base-url}/images/bg.png");
</code></pre></div></div>
<h2 id="javascript-表达式">JavaScript 表达式</h2>
<p>JavaScript 表达式也可以在.less 文件中使用. 可以通过反引号的方式使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@var: `"hello".toUpperCase() + '!'`; 输出:
@var: "HELLO!"; 注意你也可以同时使用字符串插值和避免编译:
@str: "hello";
@var: ~`"@{str}".toUpperCase() + '!'`; 输出:
@var: HELLO!; 它也可以访问JavaScript环境:
@height: `document.body.clientHeight`; 如果你想将一个JavaScript字符串解析成16进制的颜色值, 你可以使用 color 函数:
@color: color(`window.colors.baseColor`);
@darkcolor: darken(@color, 10%);
</code></pre></div></div>
jekyll搭建个人博客
2018-03-08T00:00:00+00:00
http://www.blogways.net/blog/2018/03/08/jekyll
<h1 id="jekyll">Jekyll</h1>
<h2 id="1介绍">1.介绍</h2>
<p> Jekyll 是一个简单的博客形态的静态站点生产机器。它有一个模版目录,其中包含原始文本格式的文档,通过 Markdown (或者 Textile) 以及 Liquid 转化成一个完整的可发布的静态网站,你可以发布在任何你喜爱的服务器上。Jekyll 也可以运行在 GitHub Page 上,也就是说,你可以使用 GitHub 的服务来搭建你的项目页面、博客或者网站,而且是完全免费的。</p>
<p> 使用 Jekyll 搭建博客之前要确认下本机环境,Git 环境(用于部署到远端)、Ruby 环境(Jekyll 是基于 Ruby 开发的)、包管理器 RubyGems 如果你是 Mac 用户,你就需要安装 Xcode 和 Command-Line Tools了。下载方式 Preferences → Downloads → Components。</p>
<p> Jekyll 是一个免费的简单静态网页生成工具,可以配合第三方服务例如: Disqus(评论)、多说(评论) 以及分享 等等扩展功能,Jekyll 可以直接部署在 Github(国外) 或 Coding(国内) 上,可以绑定自己的域名。Jekyll中文文档、Jekyll英文文档、Jekyll主题列表。</p>
<h2 id="2jekyll环境配置">2.Jekyll环境配置</h2>
<p>安装 jekyll</p>
<p>$ gem install jekyll <br />
1
创建博客</p>
<p>$ jekyll new myBlog <br />
1
进入博客目录</p>
<p>$ cd myBlog<br />
1
启动本地服务</p>
<p>$ jekyll serve
1
在浏览器里输入: http://localhost:4000,就可以看到你的博客效果了。
<img src="/images/linmingxing/jekyll/index.png" alt="" />
so easy !</p>
<h2 id="3目录结构">3.目录结构</h2>
<p>Jekyll 的核心其实是一个文本转换引擎。它的概念其实就是: 你用你最喜欢的标记语言来写文章,可以是 Markdown,也可以是 Textile,或者就是简单的 HTML, 然后 Jekyll 就会帮你套入一个或一系列的布局中。在整个过程中你可以设置URL路径, 你的文本在布局中的显示样式等等。这些都可以通过纯文本编辑来实现,最终生成的静态页面就是你的成品了。</p>
<p>一个基本的 Jekyll 网站的目录结构一般是像这样的:
<img src="/images/linmingxing/jekyll/category.png" alt="" /></p>
<p>这些目录结构以及具体的作用可以参考 官网文档</p>
<p>进入 _config.yml 里面,修改成你想看到的信息,重新 jekyll server ,刷新浏览器就可以看到你刚刚修改的信息了。</p>
<p>到此,博客初步搭建算是完成了。</p>
<h2 id="4博客部署到远端">4.博客部署到远端</h2>
<p>我这里讲的是部署到 Github Page 创建一个 github 账号,然后创建一个跟你账户名一样的仓库,如我的 github 账户名叫 1456132848,我的 github 仓库名就叫 1456132848.github.io,创建好了之后,把刚才建立的 myBlog 项目 push 到 username.github.io仓库里去(username指的是你的github用户名),检查你远端仓库已经跟你本地 myBlog 同步了,然后你在浏览器里输入 username.github.io ,就可以访问你的博客了。</p>
<h2 id="5编写文章">5.编写文章</h2>
<p>所有的文章都是 _posts 目录下面,文章格式为 mardown 格式,文章文件名可以是 .mardown 或者 .md。</p>
<p>编写一篇新文章很简单,你可以直接从 _posts/ 目录下复制一份出来 2017-03-23-welcome-to-jekyll副本.markdown ,修改名字为 2017-03-23-article1.markdown ,注意:文章名的格式前面必须为 2017-03-23- ,日期可以修改,但必须为 年-月-日- 格式,后面的 article1 是整个文章的连接 URL,如果文章名为中文,那么文章的连接URL就会变成这样的:http://xiaohange.io/2017/03/%E6%90%AD%E5/ , 所以建议文章名最好是英文的或者阿拉伯数字。 双击 2017-03-23-article1.markdown 打开
<img src="/images/linmingxing/jekyll/post.png" alt="" />
正文…</p>
<p>title: 显示的文章名, 如:title: 我的第一篇文章
date: 显示的文章发布日期,如:date: 2017-03-23
categories: tag标签的分类,如:categories: 随笔</p>
<p>注意:文章头部格式必须为上面的,…. 就是文章的正文内容。</p>
<h2 id="6使用我的博客模板">6.使用我的博客模板</h2>
<p>虽然博客部署完成了,你会发现博客太简单不是你想要的,如果你喜欢我的模板的话,可以使用我的模板。
首先你要获取的我博客,Github项目地址,进去https://github.com/1456132848/1456132848.github.io目录下, 使用命令部署本地服务$ jekyll server。
如果你本机没配置过任何jekyll的环境,可能会报错:</p>
<p><img src="/images/linmingxing/jekyll/error.png" alt="" /></p>
<p>原因: 没有安装 bundler ,执行安装 bundler 命令$ gem install bundler
提示:</p>
<p><img src="/images/linmingxing/jekyll/tip1.png" alt="" /></p>
<p>再次执行 $ jekyll server ,提示Could not find proper version of jekyll (3.1.1) in any of the sources Run <code class="language-plaintext highlighter-rouge">bundle install</code> to install missing gems.
跟着提示运行命令 $ bundle install
这个时候你可能会发现 bundle install 运行卡主不动了。
如果很长时间都没任何提示的话,你可以尝试修改 gem 的 source</p>
<p><img src="/images/linmingxing/jekyll/tip2.png" alt="" /></p>
<p>再次执行命令 $ bundle install,发现开始有动静了</p>
<p><img src="/images/linmingxing/jekyll/tip3.png" alt="" /></p>
<p>bundler安装完成,后再次启动本地服务
$ jekyll server</p>
<p>提示</p>
<p><img src="/images/linmingxing/jekyll/success.png" alt="" /></p>
<p>表示本地服务部署成功。
在浏览器输入 127.0.0.1:4000 , 就可以看到blog26.com博客效果了。</p>
<h2 id="7修改成你自己的博客">7.修改成你自己的博客</h2>
<p>如果你想使用我的模板请把 _posts/ 目录下的文章都去掉。
修改 _config.yml 文件里面的内容为你自己的。
然后使用 git push 到你自己的仓库里面去,检查你远端仓库,在浏览器输入 username.github.io 就会发现,你有一个漂亮的主题模板了。</p>
初识JStorm
2018-01-17T00:00:00+00:00
http://www.blogways.net/blog/2018/01/17/jstorm
<h1 id="jstorm">JStorm</h1>
<h2 id="11基本概念">1.1基本概念</h2>
<p><img src="/images/wangtianwen/JStorm/topology.png" alt="" /></p>
<p><strong>Topology(拓扑)</strong>:通过Stream Groupings将Spouts和Bolts连接起来构成一个Topology,是Storm中实时应用的一种封装。一个Topology会一直执行,无数据流等待,有数据流执行,知道被kill progress。</p>
<p><strong>Streams(流)</strong>:消息流Stream是Storm里的关键抽象,一个消息流是一个没有边界的tuple序列,而这些tuple序列会以一种分布式的方式并行地创建和处理。</p>
<p><strong>Spout(数据源)</strong>:每一个Spout都是一个数据源,通常情况Spout会从外部源读取tuple,并输入到Topology中。Spout可以是可靠的也可以是不可靠的,如果这个tuple没有被Storm成功处理,可靠的消息源可以重新发射,但是不可靠的一旦发出一个tuple就不能重发了。</p>
<p><strong>Bolt(数据处理组件)</strong>:所有的处理都会在Bolt中被执行,可以做很多事情:过滤、聚合、查询数据库等。Bolt最主要的方法是execute,它以一个tuple作为输入,使用OutputCollector来发射tuple。</p>
<p><strong>Stream groupings(数据流分组)</strong>:定义一个Topology的每个Bolt接受什么样的流作为输入。Stream Grouping就是用来定义一个Stream应该如何分配数据给Bolt上的多个task。Strom里有7中类型的Stream Grouping。</p>
<ul>
<li>Shuffle Grouping:随机分组,随机派发Stream里面的tuple,保证每个Bolt接收到的tuple数目大致相同。</li>
<li>Fields Grouping:按字段分组,比如按userid分组,相同userid的tuple会被分到同一个Bolt的同一个task中。</li>
<li>All Grouping:广播发送,对于每一个tuple,所有的Bolt都会收到。</li>
<li>Global Grouping:全局分组,这个tuple被分配到Storm中的一个Bolt的其中一个task,再具体一点就是分配给id最低的那个。</li>
<li>Non Grouping:不分组,意思是说Stream不关心到底谁会收到他的tuple。目前这种分组和Shuffle Grouping是一样的效果,有一点不同的是Storm会把这个Bolt放到这个Bolt的订阅者的同一个线程里面去执行。</li>
<li>Direct Grouping:直接分组,这是一种比较特别的分组方法,用这种分组意味着消息的发送者需要指定由消息接收者的哪个task处理这个消息。只有被声明为Direct Stream的消息流可以声明这种分组方法,而且这种消息的tuple必须使用emitDirect方法发射。消息处理这可以通过TopologyContext获取处理它的消息的task的id。</li>
<li>Local or Shuffle Grouping:如果目标Bolt中有一个或者多个task在同一个工作进程中,tuple将会被随机分配给这些task。否则和普通的Shuffle Grouping行为一直。</li>
</ul>
<p><strong>Task(任务)</strong>:每一个Spout和Bolt都会被当作很多task在整个集群里执行。每一个executor对应到一个线程,在这个线程上运行多个task。Stream Grouping是定义怎么从一堆task发射tuple到另一堆task,我们可以调用TopologyBuilder类的setSpout和setBolt来设置并行度,也就是多少个task。</p>
<ul>
<li>
<p>并行度:</p>
<p><img src="/images/wangtianwen/JStorm/并行度.png" alt="并行度" /></p>
<p></p>
</li>
</ul>
<p><strong>Worker(工作进程)</strong>:一个Topology可能会在一个或多个Worker里面执行,每个Worker是一个物理JVM并且执行整个Topology的一部分。</p>
<h2 id="12单词计数">1.2单词计数</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
public class WordSpout implements IRichSpout{
private String [] sentences = {
"a b c",
"c d e"
};
private int index = 0;
private SpoutOutputCollector collector;
public void open(Map map, TopologyContext topologyContext, SpoutOutputCollector spoutOutputCollector) {
this.collector = spoutOutputCollector;
}
public void close() {
}
public void activate() {
}
public void deactivate() {
}
public void nextTuple() {
try{
this.collector.emit(new Values(sentences[index]));
index++;
if(index>=sentences.length){
index = 0;
}
}catch (Exception e){
}
}
public void ack(Object o) {
}
public void fail(Object o) {
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("sentence"));
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
</code></pre></div></div>
<p>WordSpout发出一连串的元组,名字为“sentence”和一个字符串值。比如:{”sentence“:”a b c”}。我们的数据来源是一个String数组,遍历这个数组,发射出每个元组。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
public class WordSplit implements IRichBolt{
private OutputCollector collector;
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
}
public void execute(Tuple tuple) {
try{
String sentence = tuple.getStringByField("sentence");
String[] words = sentence.split(" ");
for(String word:words){
this.collector.emit(new Values(word));
}
}catch (Exception e){
}
}
public void cleanup() {
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
outputFieldsDeclarer.declare(new Fields("word"));
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
</code></pre></div></div>
<p>WordSplit订阅WordSpout的输出对收到的每个元组,进行分割操作,每个单词发射出一个元组:</p>
<p>{”word“:”a“}</p>
<p>{“word”:”b”}</p>
<p>{“word”:”c“}</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
public class WordCount implements IRichBolt{
private HashMap<String,Long>counts = null;
private OutputCollector collector;
public void prepare(Map map, TopologyContext topologyContext, OutputCollector outputCollector) {
this.collector = outputCollector;
this.counts = new HashMap<String, Long>();
}
public void execute(Tuple tuple) {
try{
String word = tuple.getStringByField("word");
Long count = this.counts.get(word);
if(null == count){
count = 0L;
}
count++;
this.counts.put(word,count);
}catch (Exception e){
}
}
public void cleanup() {
try {
Thread.sleep(10000);
System.out.println("=====================");
for (Map.Entry<String, Long> entry : counts.entrySet()) {
System.out.println(entry.getKey() + "=" + entry.getValue());
}
System.out.println("=======================");
}catch (Exception e){
System.out.print("cleancleancleancleancleanclean");
return;
}
}
public void declareOutputFields(OutputFieldsDeclarer outputFieldsDeclarer) {
}
public Map<String, Object> getComponentConfiguration() {
return null;
}
}
</code></pre></div></div>
<p>WordCount订阅WordSplit的输出,并对相应的单词和出现的次数进行统计。每收到一个元组,就会更新数量,最终打印结果。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
public class WordTopology {
public static void main(String[]args) throws InterruptedException {
try {
TopologyBuilder builder = new TopologyBuilder();
builder.setSpout("spout", new WordSpout());
builder.setBolt("split", new WordSplit()).allGrouping("spout");
builder.setBolt("count", new WordCount()).allGrouping("split");
Config config = new Config();
config.put(Config.TOPOLOGY_MAX_SPOUT_PENDING, 1);
LocalCluster cluster = new LocalCluster();
cluster.submitTopology("topo", config, builder.createTopology());
Utils.sleep(1000);
cluster.killTopology("topo");
cluster.shutdown();
}catch (Exception e){
}
}
}
</code></pre></div></div>
<h2 id="13定时任务">1.3定时任务</h2>
<p>利用系统自带的定时tuple来完成,相当于系统自动发一个带有特殊标记的tuple,然后在bolt中判断,若为此特殊tuple,则执行定时函数。</p>
<p>第一种方法:如果所有的bolt都需要定时,可在topology入口处通过config设置。</p>
<p>第二种方法:如果只有某一类bolt需要定时,可在该bolt内部重写getComponenrConfiguration 方法,在里面设置定时间隔。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TopologyTimer01</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MySpout</span> <span class="kd">extends</span> <span class="nc">BaseRichSpout</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span> <span class="n">conf</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">SpoutOutputCollector</span> <span class="n">collector</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">open</span><span class="o">(</span><span class="nc">Map</span> <span class="n">map</span><span class="o">,</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">SpoutOutputCollector</span> <span class="n">collector</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">conf</span> <span class="o">=</span> <span class="n">conf</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span> <span class="o">=</span> <span class="n">collector</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">nextTuple</span><span class="o">()</span> <span class="o">{</span>
<span class="n">num</span><span class="o">++;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"spout:"</span><span class="o">+</span><span class="n">num</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span><span class="o">.</span><span class="na">emit</span><span class="o">(</span><span class="k">new</span> <span class="nc">Values</span><span class="o">(</span><span class="n">num</span><span class="o">));</span>
<span class="nc">Utils</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">declareOutputFields</span><span class="o">(</span><span class="nc">OutputFieldsDeclarer</span> <span class="n">declarer</span><span class="o">)</span> <span class="o">{</span>
<span class="n">declarer</span><span class="o">.</span><span class="na">declare</span><span class="o">(</span><span class="k">new</span> <span class="nc">Fields</span><span class="o">(</span><span class="s">"num"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBolt</span> <span class="kd">extends</span> <span class="nc">BaseRichBolt</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span> <span class="n">stormConf</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">sum</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">prepare</span><span class="o">(</span><span class="nc">Map</span> <span class="n">stormConf</span><span class="o">,</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">stormConf</span> <span class="o">=</span> <span class="n">stormConf</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span> <span class="o">=</span> <span class="n">collector</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">Tuple</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">getSourceComponent</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="nc">Constants</span><span class="o">.</span><span class="na">SYSTEM_COMPONENT_ID</span><span class="o">)){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"时间到了"</span><span class="o">);</span>
<span class="o">}</span><span class="k">else</span> <span class="o">{</span>
<span class="nc">Integer</span> <span class="n">num</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">getIntegerByField</span><span class="o">(</span><span class="s">"num"</span><span class="o">);</span>
<span class="n">sum</span> <span class="o">+=</span> <span class="n">num</span><span class="o">;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"sum="</span><span class="o">+</span><span class="n">sum</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">declareOutputFields</span><span class="o">(</span><span class="nc">OutputFieldsDeclarer</span> <span class="n">outputFieldsDeclarer</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span><span class="n">args</span><span class="o">){</span>
<span class="nc">TopologyBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TopologyBuilder</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">spout_id</span> <span class="o">=</span> <span class="nc">MySpout</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">bolt_id</span> <span class="o">=</span> <span class="nc">MyBolt</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="n">builder</span><span class="o">.</span><span class="na">setSpout</span><span class="o">(</span><span class="n">spout_id</span><span class="o">,</span><span class="k">new</span> <span class="nc">MySpout</span><span class="o">());</span>
<span class="n">builder</span><span class="o">.</span><span class="na">setBolt</span><span class="o">(</span><span class="n">bolt_id</span><span class="o">,</span><span class="k">new</span> <span class="nc">MyBolt</span><span class="o">());</span>
<span class="nc">Config</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Config</span><span class="o">();</span>
<span class="n">config</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Config</span><span class="o">.</span><span class="na">TOPOLOGY_TICK_TUPLE_FREQ_SECS</span><span class="o">,</span><span class="mi">10</span><span class="o">);</span><span class="c1">//每隔10秒给topology所有bolt发送一个系统级别的tuple</span>
<span class="nc">String</span> <span class="n">topology_name</span> <span class="o">=</span> <span class="nc">TopologyTimer01</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">LocalCluster</span> <span class="n">cluster</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LocalCluster</span><span class="o">();</span>
<span class="n">cluster</span><span class="o">.</span><span class="na">submitTopology</span><span class="o">(</span><span class="n">topology_name</span><span class="o">,</span><span class="n">config</span><span class="o">,</span><span class="n">builder</span><span class="o">.</span><span class="na">createTopology</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/wangtianwen/JStorm/storm定时任务02.png" alt="定时任务01结果" /></p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">TopologyTimer02</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MySpout</span> <span class="kd">extends</span> <span class="nc">BaseRichSpout</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span> <span class="n">conf</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">SpoutOutputCollector</span> <span class="n">collector</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">num</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">open</span><span class="o">(</span><span class="nc">Map</span> <span class="n">map</span><span class="o">,</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">SpoutOutputCollector</span> <span class="n">collector</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">conf</span> <span class="o">=</span> <span class="n">conf</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span> <span class="o">=</span> <span class="n">collector</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">nextTuple</span><span class="o">()</span> <span class="o">{</span>
<span class="n">num</span><span class="o">++;</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"spout:"</span><span class="o">+</span><span class="n">num</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span><span class="o">.</span><span class="na">emit</span><span class="o">(</span><span class="k">new</span> <span class="nc">Values</span><span class="o">(</span><span class="n">num</span><span class="o">));</span>
<span class="nc">Utils</span><span class="o">.</span><span class="na">sleep</span><span class="o">(</span><span class="mi">1000</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">declareOutputFields</span><span class="o">(</span><span class="nc">OutputFieldsDeclarer</span> <span class="n">declarer</span><span class="o">)</span> <span class="o">{</span>
<span class="n">declarer</span><span class="o">.</span><span class="na">declare</span><span class="o">(</span><span class="k">new</span> <span class="nc">Fields</span><span class="o">(</span><span class="s">"num"</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBolt1</span> <span class="kd">extends</span> <span class="nc">BaseRichBolt</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span> <span class="n">stormConfig</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">prepare</span><span class="o">(</span><span class="nc">Map</span> <span class="n">stormConfig</span><span class="o">,</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">stormConfig</span> <span class="o">=</span> <span class="n">stormConfig</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span> <span class="o">=</span> <span class="n">collector</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">Tuple</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span><span class="o">(</span><span class="n">input</span><span class="o">.</span><span class="na">getSourceComponent</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="nc">Constants</span><span class="o">.</span><span class="na">SYSTEM_COMPONENT_ID</span><span class="o">)){</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyBolt 01 定时时间到"</span><span class="o">);</span>
<span class="o">}</span><span class="k">else</span> <span class="o">{</span>
<span class="nc">Integer</span> <span class="n">num</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">getIntegerByField</span><span class="o">(</span><span class="s">"num"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyBolt 01:"</span> <span class="o">+</span> <span class="n">num</span><span class="o">);</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span><span class="o">.</span><span class="na">emit</span><span class="o">(</span><span class="k">new</span> <span class="nc">Values</span><span class="o">(</span><span class="n">num</span><span class="o">));</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">declareOutputFields</span><span class="o">(</span><span class="nc">OutputFieldsDeclarer</span> <span class="n">declarer</span><span class="o">)</span> <span class="o">{</span>
<span class="n">declarer</span><span class="o">.</span><span class="na">declare</span><span class="o">(</span><span class="k">new</span> <span class="nc">Fields</span><span class="o">(</span><span class="s">"num01"</span><span class="o">));</span>
<span class="o">}</span>
<span class="nd">@Override</span>
<span class="kd">public</span> <span class="nc">Map</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span><span class="nc">Object</span><span class="o">></span> <span class="nf">getComponentConfiguration</span><span class="o">(){</span>
<span class="c1">//给当前bolt设置定时任务</span>
<span class="nc">HashMap</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span><span class="nc">Object</span><span class="o">></span><span class="n">hashMap</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o"><</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">>();</span>
<span class="n">hashMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Config</span><span class="o">.</span><span class="na">TOPOLOGY_TICK_TUPLE_FREQ_SECS</span><span class="o">,</span><span class="mi">10</span><span class="o">);</span>
<span class="k">return</span> <span class="n">hashMap</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">MyBolt2</span> <span class="kd">extends</span> <span class="nc">BaseRichBolt</span><span class="o">{</span>
<span class="kd">private</span> <span class="nc">Map</span> <span class="n">stormConfig</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">;</span>
<span class="kd">private</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">;</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">prepare</span><span class="o">(</span><span class="nc">Map</span> <span class="n">stormConfig</span><span class="o">,</span> <span class="nc">TopologyContext</span> <span class="n">context</span><span class="o">,</span> <span class="nc">OutputCollector</span> <span class="n">collector</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">stormConfig</span> <span class="o">=</span> <span class="n">stormConfig</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">context</span> <span class="o">=</span> <span class="n">context</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">collector</span> <span class="o">=</span> <span class="n">collector</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">execute</span><span class="o">(</span><span class="nc">Tuple</span> <span class="n">input</span><span class="o">)</span> <span class="o">{</span>
<span class="nc">Integer</span> <span class="n">num</span> <span class="o">=</span> <span class="n">input</span><span class="o">.</span><span class="na">getIntegerByField</span><span class="o">(</span><span class="s">"num01"</span><span class="o">);</span>
<span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"MyBolt 02:"</span><span class="o">+</span><span class="n">num</span><span class="o">);</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">declareOutputFields</span><span class="o">(</span><span class="nc">OutputFieldsDeclarer</span> <span class="n">declarer</span><span class="o">)</span> <span class="o">{</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span><span class="n">args</span><span class="o">){</span>
<span class="nc">TopologyBuilder</span> <span class="n">builder</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">TopologyBuilder</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">spout_id</span> <span class="o">=</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">MySpout</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">bolt01_id</span> <span class="o">=</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">MyBolt1</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">bolt02_id</span> <span class="o">=</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">MyBolt2</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="nc">String</span> <span class="n">topology_name</span> <span class="o">=</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">class</span><span class="o">.</span><span class="na">getSimpleName</span><span class="o">();</span>
<span class="n">builder</span><span class="o">.</span><span class="na">setSpout</span><span class="o">(</span><span class="n">spout_id</span><span class="o">,</span> <span class="k">new</span> <span class="nc">MySpout</span><span class="o">());</span>
<span class="n">builder</span><span class="o">.</span><span class="na">setBolt</span><span class="o">(</span><span class="n">bolt01_id</span><span class="o">,</span><span class="k">new</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">MyBolt1</span><span class="o">()).</span><span class="na">shuffleGrouping</span><span class="o">(</span><span class="n">spout_id</span><span class="o">);</span>
<span class="n">builder</span><span class="o">.</span><span class="na">setBolt</span><span class="o">(</span><span class="n">bolt02_id</span><span class="o">,</span><span class="k">new</span> <span class="nc">TopologyTimer02</span><span class="o">.</span><span class="na">MyBolt2</span><span class="o">()).</span><span class="na">shuffleGrouping</span><span class="o">(</span><span class="n">bolt01_id</span><span class="o">);</span>
<span class="nc">Config</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Config</span><span class="o">();</span>
<span class="nc">LocalCluster</span> <span class="n">cluster</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">LocalCluster</span><span class="o">();</span>
<span class="n">cluster</span><span class="o">.</span><span class="na">submitTopology</span><span class="o">(</span><span class="n">topology_name</span><span class="o">,</span><span class="n">config</span><span class="o">,</span><span class="n">builder</span><span class="o">.</span><span class="na">createTopology</span><span class="o">());</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/images/wangtianwen/JStorm/storm定时任务02.png" alt="定时任务02结果" /></p>
<h2 id="14系统架构">1.4系统架构</h2>
<p>JStorm系统中有三种不同的Daemon进程:</p>
<p>Nimbus:JStorm中的主控节点,负责接收和验证客户端提交的Topology,分配任务,向ZK写入任务相关的元信息,此外,Nimbus还负责通过ZK来监控节点和任务健康情况,当有Supervisor节点变化或者Worker进程出现问题时及时进行任务重新分配。Nimbus分配任务的结果不是直接下发给Supervisor,也是通过ZK维护分配数据进行过渡。特别地,JStorm 0.9.0领先Apache Storm实现了Nimbus HA(HA指的是双机主备模式,如果主机出现宕机,备用机会顶替上来,从而使整个集群继续工作)。</p>
<p>Supervisor:JStorm中的工作节点,Supervisor类似于MR的TT,subscribe ZK分配到该节点的任务数据,根据Nimbus的任务分配情况启动/停止工作进程Worker。Supervisor需要定期向ZK写入活跃端口信息以便Nimbus及时监控。Supervisor不执行具体的数据处理工作,所有的数据处理工作都交给Worker完成。</p>
<p>Worker:JStorm中任务执行者,Worker类似于MR的Task,所有实际的数据处理工作最后都在Worker内执行完成。Worker需要定期向Supervsior汇报心跳,由于在同一节点,同时为保持节点的无状态,Worker定期将状态信息写入本地磁盘,Supervisor通过读本地磁盘状态信息完成心跳交互过程。Worker绑定一个独立端口,Worker内所有单元共享Worker的通信能力。</p>
<p><img src="/images/wangtianwen/Jstorm/jstorm集群框架图.jpg" alt="" /></p>
Flyway 在蜂窝系统的应用
2018-01-08T00:00:00+00:00
http://www.blogways.net/blog/2018/01/08/flyway-2
<h2 id="一前言">一、前言</h2>
<p>本文主要介绍 Flyway 在管理蜂窝系统数据库结构和数据中的应用。在蜂窝项目中应用 Flyway 主要要解决两个问题:</p>
<ul>
<li>如何管理多个数据库;</li>
<li>如何管理不同环境的数据库。</li>
</ul>
<p>本文仍然使用 Flyway maven 插件,Command line 和 gradle 实际上和 maven 是类似的,Java API 能提供一些<code class="language-plaintext highlighter-rouge">钩子</code>。</p>
<h2 id="二flyway-管理多个数据库">二、Flyway 管理多个数据库</h2>
<h3 id="1建立迁移脚本目录">1、建立迁移脚本目录</h3>
<p>蜂窝要管理两个数据库,为每个数据库建立一个 SQL 迁移脚本<code class="language-plaintext highlighter-rouge">目录</code>。如图在 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录中,为 info 和 cen 库各自建立一个目录:</p>
<p><img src="/images/jyjsjd/flyway_dir2.png" alt="flyway_dir2.png" /></p>
<h3 id="2修改-pomxml">2、修改 pom.xml</h3>
<p>(1)添加 Flyway 插件</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.flywaydb<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flyway-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.0.5<span class="nt"></version></span>
<span class="nt"></plugin></span>
</code></pre></div></div>
<p>(2)添加 executions
在 <code class="language-plaintext highlighter-rouge">executions</code> 标签中为两个数据库实例各添加一个 <code class="language-plaintext highlighter-rouge">execution</code>,用 <code class="language-plaintext highlighter-rouge">id</code> 区分。注意指定 SQL 迁移脚本的目录位置 <code class="language-plaintext highlighter-rouge">location</code>。如下指定了 info 库的参数;cen 库类似,只要修改连接参数、用户名和密码即可:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><execution></span>
<span class="nt"><id></span>info<span class="nt"></id></span>
<span class="nt"><phase></span>compile<span class="nt"></phase></span>
<span class="nt"><goals></span>
<span class="nt"><goal></span>migrate<span class="nt"></goal></span>
<span class="nt"></goals></span>
<span class="nt"><configuration></span>
<span class="nt"><url></span>jdbc:mysql://hostname:port#/db_name<span class="nt"></url></span>
<span class="nt"><user></span>username<span class="nt"></user></span>
<span class="nt"><password></span>password<span class="nt"></password></span>
<span class="nt"><locations></span>
<span class="nt"><location></span>filesystem:src/main/resources/db/migration/info<span class="nt"></location></span>
<span class="nt"></locations></span>
<span class="nt"></configuration></span>
<span class="nt"></execution></span>
</code></pre></div></div>
<p>(3)执行数据库迁移</p>
<ul>
<li>导出 info 和 cen 库原有数据和结构,分别放入各自的 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录,命名为 <code class="language-plaintext highlighter-rouge">V1__Base_line.sql</code>,运行命令:<code class="language-plaintext highlighter-rouge">mvn flyway:baseline</code>;</li>
<li>执行数据库结构或内容修改:<code class="language-plaintext highlighter-rouge">mvn flyway:migrate</code>;</li>
<li>注意:为了明确指定执行某个数据的迁移脚本,必须加上 <code class="language-plaintext highlighter-rouge">execution</code> 中的 <code class="language-plaintext highlighter-rouge">id</code>,如要执行 info 库的迁移:<code class="language-plaintext highlighter-rouge">mvn flyway:migrate@info</code>。</li>
</ul>
<p>运行完成之后,info 库中会建立表 <code class="language-plaintext highlighter-rouge">flyway_schema_history</code> 跟踪数据库版本,并插入第一次迁移记录。cen 库同理。</p>
<h2 id="三flyway-管理不同环境的数据库">三、Flyway 管理不同环境的数据库</h2>
<p>在实际项目中,不仅有多个数据库实例,还有多个环境,如开发环境、测试环境和生产环境。为了能够统一管理,需要为不同环境配置不同参数。</p>
<p>(1)profile
在 pom.xml 中,添加 <code class="language-plaintext highlighter-rouge">profiles</code>,并添加三个 <code class="language-plaintext highlighter-rouge">profile</code>:<code class="language-plaintext highlighter-rouge">dev</code>、<code class="language-plaintext highlighter-rouge">qa</code> 和 <code class="language-plaintext highlighter-rouge">prod</code>,分别管理不同环境的<code class="language-plaintext highlighter-rouge">数据库连接</code>、<code class="language-plaintext highlighter-rouge">用户名</code>和<code class="language-plaintext highlighter-rouge">密码</code>:</p>
<p><img src="/images/jyjsjd/profile.png" alt="profile.png" /></p>
<p>默认激活 <code class="language-plaintext highlighter-rouge">dev</code> 环境,添加:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><activation></span>
<span class="nt"><activeByDefault></span>true<span class="nt"></activeByDefault></span>
<span class="nt"></activation></span>
</code></pre></div></div>
<p>(2)占位符
把 configuration 中的参数替换为<code class="language-plaintext highlighter-rouge">占位符</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><configuration></span>
<span class="nt"><url></span>${info.url}<span class="nt"></url></span>
<span class="nt"><user></span>${info.user}<span class="nt"></user></span>
<span class="nt"><password></span>${info.password}<span class="nt"></password></span>
<span class="nt"><locations></span>
<span class="nt"><location></span>filesystem:src/main/resources/db/migration/info<span class="nt"></location></span>
<span class="nt"></locations></span>
<span class="nt"></configuration></span>
</code></pre></div></div>
<p>(3)在不同环境执行脚本迁移
给 mvn 命令添加参数 <code class="language-plaintext highlighter-rouge">-P</code>,<code class="language-plaintext highlighter-rouge">mvn flyway:migrate@info -P 环境id</code>。</p>
<p>如在生产环境执行迁移:<code class="language-plaintext highlighter-rouge">mvn flyway:migrate@info -P prod</code>。</p>
<h2 id="四配置钩子">四、配置钩子</h2>
<p>Flyway 提供多种钩子,可以在执行 migrate,info,clean,validate,baseline 等命令<code class="language-plaintext highlighter-rouge">前后</code>执行。</p>
<p>Java API 提供了 <code class="language-plaintext highlighter-rouge">FlywayCallback</code> 接口,或者可以继承 <code class="language-plaintext highlighter-rouge">BaseFlywayCallback</code>,实现所需方法。</p>
<p>最后在 pom.xml 文件配置 <code class="language-plaintext highlighter-rouge">callback</code>。在 <code class="language-plaintext highlighter-rouge">configuration</code> 标签里添加:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><callbacks>
<callback>CallbackClassName</callback>
</callbacks>
</code></pre></div></div>
<h2 id="五参考文献">五、参考文献</h2>
<p><a href="https://stackoverflow.com/questions/23545657/how-to-use-flyway-configuration-to-handle-multiple-databases">Stack Overflow</a> “How to use Flyway configuration to handle multiple databases”</p>
<p><a href="https://flywaydb.org/documentation/faq#multiple-schemas">Flyway FAQ</a> “Does Flyway support multiple schemas?”</p>
<p><a href="https://flywaydb.org/documentation/api/hooks">Flyway Hooks</a> “Hooks”</p>
kafka-manager安装与使用
2018-01-07T00:00:00+00:00
http://www.blogways.net/blog/2018/01/07/kafka-manager
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#kafka-manager-desc">kafka manager简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#kafka-manager-install">kafka manager安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#kafka-manager-use">kafka manager使用</a></td>
</tr>
</tbody>
</table>
<h2 id="一kafka-manager简介">一、kafka manager简介<a href="kafka-manager-desc"></a></h2>
<p>为了简化开发者和服务工程师维护Kafka集群的工作,yahoo构建了一个叫做Kafka管理器的基于Web工具,叫做 Kafka Manager。
它有如下功能:</p>
<ul>
<li>管理多个kafka集群</li>
<li>便捷的检查kafka集群状态(topics,brokers,备份分布情况,分区分布情况)</li>
<li>删除topic(只支持0.8.2+ 且须设置delete.topic.enable=true)</li>
<li>为已存在的topic增加分区</li>
<li>为已存在的topic更新配置</li>
</ul>
<p>等等,具体可见kafka-manager项目地址<a href="https://github.com/yahoo/kafka-manager">https://github.com/yahoo/kafka-manager</a></p>
<h2 id="二kafka-manager安装">二、kafka manager安装<a href="kafka-manager-install"></a></h2>
<h3 id="21-下载kafka-manager">2.1 下载kafka-manager</h3>
<p><code class="language-plaintext highlighter-rouge">git clone https://github.com/yahoo/kafka-managercd kafka-manager</code></p>
<h3 id="22-构建kafka-manager">2.2 构建kafka-manager</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cd kafka-manager
./sbt clean dist
</code></pre></div></div>
<p>由于一些众所周知的原因,编译的操作比较耗时,如果有国外的服务器,最好在国外的服务器上完成。</p>
<h3 id="23-配置kafka-manager">2.3 配置kafka-manager</h3>
<p>构建成功后,在目录target/universal下可以看到<code class="language-plaintext highlighter-rouge">kafka-manager-1.3.3.13.zip</code></p>
<ol>
<li>解压文件<br />
<code class="language-plaintext highlighter-rouge">unzip kafka-manager-1.3.3.13.zip</code></li>
<li>修改配置
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cd kafka-manager-1.3.3.13
vi conf/application.conf
</code></pre></div> </div>
<p>将<code class="language-plaintext highlighter-rouge">kafka-manager.zkhosts</code>属性修改为您的zk集群地址
将<code class="language-plaintext highlighter-rouge">akka</code>的loglevel设置为<code class="language-plaintext highlighter-rouge">error</code>,否则日志文件较多。</p>
</li>
<li>启动
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> nohup bin/kafka-manager -Dconfig.file=conf/application.conf -Dhttp.port=9000 &
</code></pre></div> </div>
<p>这样就启动成功了。</p>
</li>
</ol>
<h2 id="三kafka-manager使用">三、kafka manager使用<a href="kafka-manager-use"></a></h2>
<ul>
<li>点击<a href="http://localhost:9000">http://localhost:9000</a>导航栏上的”Add Cluster”</li>
<li>配置kafka集群名称、kafka集群的zk等:<br />
<img src="/images/qianwx/kafka-manager/kafka-manager-add-cluster.png" alt="kafka-manager-add-cluster.png" /></li>
<li>点击保存后,可以进入到集群列表页面:<br />
<img src="/images/qianwx/kafka-manager/kafka-manager-clusters.png" alt="kafka-manager-clusters.png" /></li>
<li>topics列表页和详情页:<br />
<img src="/images/qianwx/kafka-manager/kafka-manager-topics.png" alt="kafka-manager-topics.png" />
<img src="/images/qianwx/kafka-manager/kafka-manager-topic-detail.png" alt="kafka-manager-topic-detail.png" /></li>
<li>consumers信息:<br />
<img src="/images/qianwx/kafka-manager/kafka-manager-consumer.png" alt="kafka-manager-consumer.png" /></li>
</ul>
Flyway 使用入门
2018-01-06T00:00:00+00:00
http://www.blogways.net/blog/2018/01/06/flyway-1
<p>Flyway是一款开源的数据库版本管理工具,包含社区版、专业版和企业版,可以独立于应用实现管理并跟踪数据库变更。支持多种配置,包括<code class="language-plaintext highlighter-rouge">Java API</code>、<code class="language-plaintext highlighter-rouge">Command line</code>、<code class="language-plaintext highlighter-rouge">maven</code> 和 <code class="language-plaintext highlighter-rouge">gradle</code>。</p>
<h2 id="一入门">一、入门</h2>
<p>本文使用<code class="language-plaintext highlighter-rouge">社区版 Flyway</code> 的<code class="language-plaintext highlighter-rouge">maven 插件</code>来迁移数据库,数据库为 <code class="language-plaintext highlighter-rouge">MySQL</code>。</p>
<h3 id="1新建-maven-项目">1、新建 maven 项目</h3>
<p>在 <code class="language-plaintext highlighter-rouge">resources</code> 目录下建立 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录。</p>
<p><img src="/images/jyjsjd/flyway_dir.png" alt="flyway_dir.png" /></p>
<h3 id="2修改-pomxml">2、修改 pom.xml</h3>
<p>添加 Flyway 插件,并配置 mysql 的<code class="language-plaintext highlighter-rouge">连接字符串</code>、<code class="language-plaintext highlighter-rouge">用户名</code>和<code class="language-plaintext highlighter-rouge">密码</code>:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><build></span>
<span class="nt"><plugins></span>
<span class="nt"><plugin></span>
<span class="nt"><groupId></span>org.flywaydb<span class="nt"></groupId></span>
<span class="nt"><artifactId></span>flyway-maven-plugin<span class="nt"></artifactId></span>
<span class="nt"><version></span>5.0.5<span class="nt"></version></span>
<span class="nt"><configuration></span>
<span class="nt"><url></span>jdbc:mysql://hostname:port#/db_name<span class="nt"></url></span>
<span class="nt"><user></span>username<span class="nt"></user></span>
<span class="nt"><password></span>password<span class="nt"></password></span>
<span class="nt"></configuration></span>
<span class="nt"></plugin></span>
<span class="nt"></plugins></span>
<span class="nt"></build></span>
</code></pre></div></div>
<h3 id="3迁移数据库">3、迁移数据库</h3>
<p>(1)建立一个<code class="language-plaintext highlighter-rouge">空</code>数据库用于测试。</p>
<p>(2)在 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录下建立 SQL 脚本 <code class="language-plaintext highlighter-rouge">V1__Create_person_table.sql</code>:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">create</span> <span class="k">table</span> <span class="n">PERSON</span> <span class="p">(</span>
<span class="n">ID</span> <span class="nb">int</span> <span class="k">not</span> <span class="k">null</span><span class="p">,</span>
<span class="n">NAME</span> <span class="nb">varchar</span><span class="p">(</span><span class="mi">100</span><span class="p">)</span> <span class="k">not</span> <span class="k">null</span>
<span class="p">);</span>
</code></pre></div></div>
<p>(3)运行 maven 命令:<code class="language-plaintext highlighter-rouge">mvn flyway:migrate</code>,数据库中会建立表 <code class="language-plaintext highlighter-rouge">flyway_schema_history</code> 跟踪数据库版本,插入第一次迁移记录,并执行 SQL 脚本。</p>
<p><img src="/images/jyjsjd/migrate.png" alt="migrate.png" /></p>
<h3 id="4第二次迁移">4、第二次迁移</h3>
<p>(1)在 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录下建立 SQL 脚本 <code class="language-plaintext highlighter-rouge">V2__Add_people.sql</code>:</p>
<div class="language-sql highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">insert</span> <span class="k">into</span> <span class="n">PERSON</span> <span class="p">(</span><span class="n">ID</span><span class="p">,</span> <span class="n">NAME</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="s1">'Axel'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">PERSON</span> <span class="p">(</span><span class="n">ID</span><span class="p">,</span> <span class="n">NAME</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="s1">'Mr. Foo'</span><span class="p">);</span>
<span class="k">insert</span> <span class="k">into</span> <span class="n">PERSON</span> <span class="p">(</span><span class="n">ID</span><span class="p">,</span> <span class="n">NAME</span><span class="p">)</span> <span class="k">values</span> <span class="p">(</span><span class="mi">3</span><span class="p">,</span> <span class="s1">'Ms. Bar'</span><span class="p">);</span>
</code></pre></div></div>
<p>(2)运行 maven 命令:<code class="language-plaintext highlighter-rouge">mvn flyway:migrate</code>,<code class="language-plaintext highlighter-rouge">flyway_schema_histry</code> 会插入第二条记录,并执行 SQL 脚本:</p>
<p><img src="/images/jyjsjd/migrate2.png" alt="migrate2.png" /></p>
<h2 id="二命令">二、命令</h2>
<h3 id="1flyway-迁移">1、Flyway 迁移</h3>
<p>(1)带版本的迁移:迁移脚本带版本号,每一次带版本的迁移都有<code class="language-plaintext highlighter-rouge">版本号</code>、<code class="language-plaintext highlighter-rouge">描述</code>和<code class="language-plaintext highlighter-rouge">校验和</code>,并且只能执行<em>一次</em>。典型用于数据表结构变更等。</p>
<p>(2)可重复的迁移:和带版本的迁移不同,它没有版本号,只有<code class="language-plaintext highlighter-rouge">描述</code>和<code class="language-plaintext highlighter-rouge">校验和</code>,而且可以<em>多次</em>执行。典型用于插入数据等操作。</p>
<p>(3)脚本命名规则:</p>
<p><img src="/images/jyjsjd/migration_naming.png" alt="migration_naming.png" /></p>
<h3 id="2flyway-命令">2、Flyway 命令</h3>
<ul>
<li>migrate:把数据库迁移到最新版本,迁移是根据 <code class="language-plaintext highlighter-rouge">db/migration</code> 目录下的脚本<code class="language-plaintext highlighter-rouge">顺序</code>执行。</li>
<li>clean:<code class="language-plaintext highlighter-rouge">清空</code>数据库中的数据,<em>不能在生产环境使用此命令</em>。</li>
<li>info:打印出版本迁移信息。</li>
<li>validate:验证要执行的迁移脚本。</li>
<li>baseline:为已经存在的数据库建立基线,迁移数据库将建立在基线的基础上。</li>
<li>repair:修复 <code class="language-plaintext highlighter-rouge">flyway_schema_histry</code> 表。</li>
</ul>
<h2 id="三flyway-管理已存在的数据库">三、Flyway 管理已存在的数据库</h2>
<ul>
<li>导出脚本:把现有数据库的结构和数据导出为 SQL 脚本,起名如 <code class="language-plaintext highlighter-rouge">V1__Base_version.sql</code>。</li>
<li>清理数据:这步可以跳过,如果数据不再需要可以运行清理命令:<code class="language-plaintext highlighter-rouge">mvn flyway:clean</code>。</li>
<li>建立基线:<code class="language-plaintext highlighter-rouge">mvn flyway:baseline</code>。</li>
</ul>
<p>之后对数据库的修改就可以通过 Flyway 来管理了。</p>
cordova插件的使用与开发
2018-01-04T00:00:00+00:00
http://www.blogways.net/blog/2018/01/04/cordova-plugin
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#use_plugin">使用插件</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#dev_plugin">开发插件</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>使用cordova可以开发手机App的绝大部分功能。如想给你的App增加更多的偏手机级别特性功能,可以通过cordova插件实现。cordova插件很多,有官方的,也有社区开源的,还可以自己定制。</p>
<p>下面简单介绍一下,插件的使用与开发。</p>
<h2 id="一使用插件-">一、使用插件 <a href="use_plugin"></a></h2>
<p>两种方法都可以帮你的cordova项目添加插件:</p>
<h3 id="11-方法一">1.1 方法一</h3>
<p>使用<code class="language-plaintext highlighter-rouge">cordova plugin</code>命令。属于套装命令,快速搞定所有平台。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova {plugin | plugins} [
add <plugin-spec> [..] {--searchpath=<directory> | --noregistry | --link | --save | --browserify | --force | --nofetch} |
{remove | rm} {<pluginid> | <name>} --save --nofetch |
{list | ls} |
search [<keyword>] |
save |
]
</code></pre></div></div>
<p>举例:</p>
<ul>
<li>
<p>安装 <code class="language-plaintext highlighter-rouge">cordova-plugin-camera</code>和<code class="language-plaintext highlighter-rouge">cordova-plugin-file</code>两个插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin add cordova-plugin-camera cordova-plugin-file
</code></pre></div> </div>
</li>
<li>
<p>从指定git仓库安装插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin add https://git-wip-us.apache.org/repos/asf/cordova-plugin-device.git
</code></pre></div> </div>
</li>
<li>
<p>从指定本地目录安装插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin add ../cordova-plugin-camera
</code></pre></div> </div>
</li>
<li>
<p>从指定tar包安装插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin add ../cordova-plugin-camera.tgz
</code></pre></div> </div>
</li>
<li>
<p>删除插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin rm camera
</code></pre></div> </div>
</li>
<li>
<p>查看项目中已安装插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova plugin ls
</code></pre></div> </div>
</li>
</ul>
<h3 id="12-方法二">1.2 方法二:</h3>
<p>使用底层命令<code class="language-plaintext highlighter-rouge">Plugman</code>去管理插件,可以更灵活地实现安装。</p>
<h4 id="121-安装plugman">1.2.1 安装Plugman</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g plugman
</code></pre></div></div>
<h4 id="122-安装插件">1.2.2 安装插件</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugman install --platform <ios|android|blackberry10|wp8> --project <directory> --plugin <name|url|path> [--plugins_dir <directory>] [--www <directory>] [--variable <name>=<value> [--variable <name>=<value> ...]]
</code></pre></div></div>
<p>举例,在<code class="language-plaintext highlighter-rouge">android</code>平台下安装<code class="language-plaintext highlighter-rouge">cordova-plugin-camera</code>插件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugman install --platform android --project myProject --plugin cordova-plugin-battery-status
</code></pre></div></div>
<h4 id="123-删除插件">1.2.3 删除插件</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugman uninstall --platform <ios|android|blackberry10|wp8> --project <directory> --plugin <id> [--www <directory>] [--plugins_dir <directory>]
</code></pre></div></div>
<h2 id="二开发插件-">二、开发插件 <a href="dev_plugin"></a></h2>
<h3 id="21-插件目录结构">2.1 插件目录结构</h3>
<p>一般,一个插件的目录结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
├── src
│ ├── android
│ ├── ios
│ └── windows
├── www
├── package.json
└── plugin.xml
</code></pre></div></div>
<h4 id="211-pluginxml">2.1.1 <code class="language-plaintext highlighter-rouge">plugin.xml</code></h4>
<p>plugin.xml 内容大约如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<plugin xmlns="http://apache.org/cordova/ns/plugins/1.0"
id="cordova-plugin-device" version="0.2.3">
<name>Device</name>
<description>Cordova Device Plugin</description>
<license>Apache 2.0</license>
<keywords>cordova,device</keywords>
<js-module src="www/device.js" name="device">
<clobbers target="device" />
</js-module>
<platform name="ios">
<config-file target="config.xml" parent="/*">
<feature name="Device">
<param name="ios-package" value="CDVDevice"/>
</feature>
</config-file>
<header-file src="src/ios/CDVDevice.h" />
<source-file src="src/ios/CDVDevice.m" />
</platform>
</plugin>
</code></pre></div></div>
<p>其中:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">xmlns</code>设置命名空间,一般为:<code class="language-plaintext highlighter-rouge">http://apache.org/cordova/ns/plugins/1.0</code></li>
<li><code class="language-plaintext highlighter-rouge">id</code>为插件的标识</li>
<li><code class="language-plaintext highlighter-rouge">version</code>为插件版本</li>
<li><code class="language-plaintext highlighter-rouge">name</code>、<code class="language-plaintext highlighter-rouge">description</code>、<code class="language-plaintext highlighter-rouge">license</code>、<code class="language-plaintext highlighter-rouge">keywords</code>,顾名思义,不解释。</li>
<li><code class="language-plaintext highlighter-rouge">js-module</code>.大部分插件都包含一到多个Javascript文件,每个<code class="language-plaintext highlighter-rouge">js-module</code>包含一个js文件。<code class="language-plaintext highlighter-rouge">src</code>是文件路径,<code class="language-plaintext highlighter-rouge">name</code>是可以通过<code class="language-plaintext highlighter-rouge">cordova.require</code>在其他js文件中导入这个js文件。</li>
<li><code class="language-plaintext highlighter-rouge">clobbers</code>是js模块导出到<code class="language-plaintext highlighter-rouge">window</code>对象下的命名空间。</li>
<li><code class="language-plaintext highlighter-rouge">platform</code>是指定对应平台下的代码文件及相关设置。</li>
</ul>
<p>更多约定可以查看<a href="http://cordova.apache.org/docs/en/latest/plugin_ref/spec.html">plugin.xml规范</a>。</p>
<h4 id="212-www目录">2.1.2 <code class="language-plaintext highlighter-rouge">www</code>目录</h4>
<p>一般插件的Js文件会放在这个目录下。</p>
<h3 id="213-src目录">2.1.3 <code class="language-plaintext highlighter-rouge">src</code>目录</h3>
<p>一般下面会按支持的平台新建对应的子目录,比如:<code class="language-plaintext highlighter-rouge">android</code>、<code class="language-plaintext highlighter-rouge">ios</code>、<code class="language-plaintext highlighter-rouge">windows</code>…</p>
<p>具体各平台上功能实现代码都放在对应的子目录内,层次清晰。</p>
<h3 id="214-packagejson">2.1.4 <code class="language-plaintext highlighter-rouge">package.json</code></h3>
<p>编辑完<code class="language-plaintext highlighter-rouge">plugin.xml</code>后,可以通过命令生成<code class="language-plaintext highlighter-rouge">package.json</code>,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugman createpackagejson /path/to/your/plugin
</code></pre></div></div>
<h3 id="22-javascript接口">2.2 <code class="language-plaintext highlighter-rouge">JavaScript</code>接口</h3>
<p>你可以按需设计你的JavaScript接口,但是需要调用<code class="language-plaintext highlighter-rouge">cordova.exec</code>来和原生平台进行交互。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova.exec(<successFunction>, <failFunction>, <service>, <action>, [<args>]);
</code></pre></div></div>
<p>其中:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">successFunction</code>:成功后回调函数;</li>
<li><code class="language-plaintext highlighter-rouge">failFunction</code> : 错误回调函数;</li>
<li><code class="language-plaintext highlighter-rouge">service</code>: 原生侧程序服务名;</li>
<li><code class="language-plaintext highlighter-rouge">action</code>: 原生侧程序动作名;</li>
<li><code class="language-plaintext highlighter-rouge">args</code>: 传给原生侧程序的参数;</li>
</ul>
<p>举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>window.echo = function(str, callback) {
cordova.exec(callback, function(err) {
callback('Nothing to echo.');
}, "Echo", "echo", [str]);
};
</code></pre></div></div>
<h3 id="23-android侧实现代码">2.3 Android侧实现代码</h3>
<h4 id="231-service">2.3.1 service</h4>
<p><code class="language-plaintext highlighter-rouge">plugin.xml</code>里配置service对应的源码文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><feature name="<service_name>">
<param name="android-package" value="<full_name_including_namespace>" />
</feature>
</code></pre></div></div>
<p>举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><platform name="android">
<config-file target="config.xml" parent="/*">
<feature name="Echo">
<param name="android-package" value="org.apache.cordova.plugin.Echo"/>
</feature>
</config-file>
<source-file src="src/android/Echo.java" target-dir="src/org/apache/cordova/plugin" />
</platform>
</code></pre></div></div>
<h4 id="232-原生代码">2.3.2 原生代码</h4>
<ol>
<li>
<p>首先,根据需要,可以选择添加<code class="language-plaintext highlighter-rouge">initialize</code>、<code class="language-plaintext highlighter-rouge">onResume</code>、<code class="language-plaintext highlighter-rouge">onDestroy</code>等方法,举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Override
public void initialize(CordovaInterface cordova, CordovaWebView webView) {
super.initialize(cordova, webView);
// your init code here
}
</code></pre></div> </div>
</li>
<li>
<p>JavaScript接口中的 <code class="language-plaintext highlighter-rouge">cordova.exec</code> 会执行 Android代码中的 <code class="language-plaintext highlighter-rouge">execute</code>方法。举例(<code class="language-plaintext highlighter-rouge">src/android/Echo.java</code>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package org.apache.cordova.plugin;
import org.apache.cordova.CordovaPlugin;
import org.apache.cordova.CallbackContext;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class Echo extends CordovaPlugin {
@Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
if (action.equals("echo")) {
String message = args.getString(0);
this.echo(message, callbackContext);
return true;
}
return false;
}
private void echo(String message, CallbackContext callbackContext) {
if (message != null && message.length() > 0) {
callbackContext.success(message);
} else {
callbackContext.error("Expected one non-empty string argument.");
}
}
}
</code></pre></div> </div>
</li>
</ol>
<h2 id="三参考文献">三、参考文献<a name="reference"></a></h2>
<ul>
<li>http://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/index.html</li>
<li>http://cordova.apache.org/docs/en/latest/reference/cordova-cli/index.html#cordova-plugin-command</li>
<li>http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html</li>
<li>http://cordova.apache.org/docs/en/latest/plugin_ref/plugman.html</li>
<li>http://cordova.apache.org/docs/en/latest/guide/platforms/android/plugin.html#echo-android-plugin-example</li>
</ul>
Git 版本管理流程
2017-12-21T00:00:00+00:00
http://www.blogways.net/blog/2017/12/21/git
<h2 id="命名规范">命名规范</h2>
<ul>
<li>master 分支:<code class="language-plaintext highlighter-rouge">master</code>;</li>
<li>develop 分支:<code class="language-plaintext highlighter-rouge">develop</code>;</li>
<li>release 稳定分支:<code class="language-plaintext highlighter-rouge">release_stable_YYYYMMDD_n</code>,<em>YYYYMMDD</em> 为当前日期(年月日),<em>n</em> 为递增序列号;</li>
<li>release 测试分支:<code class="language-plaintext highlighter-rouge">release_nightly_YYYYMMDD_n</code>,<em>YYYYMMDD</em> 为当前日期(年月日),<em>n</em> 为递增序列号;</li>
<li>开发人员版分支:<em>bug单号</em>为 bugfree 编号,<em>n</em> 为递增序列号。
<ul>
<li>修复 bug:<code class="language-plaintext highlighter-rouge">fixbug_bug单号_邮箱前缀_n</code>;</li>
<li>需求功能:<code class="language-plaintext highlighter-rouge">feature_bug单号_邮箱前缀_n</code>。</li>
</ul>
</li>
</ul>
<h2 id="基本命令">基本命令</h2>
<p>以下所有命令的开发分支号均以<code class="language-plaintext highlighter-rouge">fixbug_12345_jingyang_1</code>举例,合并均以合并到 <code class="language-plaintext highlighter-rouge">develop</code> 举例。</p>
<ul>
<li>切换分支:<code class="language-plaintext highlighter-rouge">git checkout fixbug_12345_jingyang_1</code></li>
<li>建立新分支并切换到该分支:<code class="language-plaintext highlighter-rouge">git checkout –b fixbug_12345_jingyang_1</code></li>
<li>查看所有分支:<code class="language-plaintext highlighter-rouge">git branch -a</code></li>
<li>推送分支到服务器:<code class="language-plaintext highlighter-rouge">git push origin fixbug_12345_jingyang_1</code></li>
<li>合并分支:<code class="language-plaintext highlighter-rouge">git merge origin/fixbug_12345_jingyang_1</code></li>
<li>删除分支:
<ul>
<li>本地删除:<code class="language-plaintext highlighter-rouge">git branch -d fixbug_12345_jingyang_1</code></li>
<li>远程删除:<code class="language-plaintext highlighter-rouge">git branch –r –d fixbug_12345_jingyang_1</code>,删除后推送到服务器 <code class="language-plaintext highlighter-rouge">git push origin :fixbug_12345_jingyang_1</code></li>
</ul>
</li>
<li>回退版本:<code class="language-plaintext highlighter-rouge">git reset --hard HEAD^</code></li>
</ul>
<h2 id="基本思路">基本思路</h2>
<ul>
<li>开发人员分支:
<ul>
<li>从<code class="language-plaintext highlighter-rouge">release 稳定分支</code>签出新分支;</li>
<li>自测通过的分支会被合并到<code class="language-plaintext highlighter-rouge">release 测试分支</code>(<strong>合并专员不会合并有冲突的分支</strong>);</li>
<li>最终被合并的无冲突<code class="language-plaintext highlighter-rouge">release 测试分支</code>会合并到 <code class="language-plaintext highlighter-rouge">develop 分支</code>。</li>
</ul>
</li>
<li>测试(QA)环境:测试<code class="language-plaintext highlighter-rouge">develop 分支</code>;
<ul>
<li>测试结束后重新合并一个<code class="language-plaintext highlighter-rouge">release 测试分支</code>,剔除所有测试不通过的分支;</li>
<li>版本会合并到<code class="language-plaintext highlighter-rouge">release 稳定分支</code>。</li>
</ul>
</li>
<li>生产(Prod)环境:进行<em>冒烟测试</em>;
<ul>
<li>测试结束后重新合并一个<code class="language-plaintext highlighter-rouge">release 稳定分支</code>,剔除所有测试不通过的分支;</li>
<li>通过的<code class="language-plaintext highlighter-rouge">release 稳定分支</code>会同步到局方环境;</li>
</ul>
</li>
<li>局方 CUC 环境:运行最终的 <code class="language-plaintext highlighter-rouge">release 稳定分支</code>。</li>
</ul>
<p><img src="/images/jyjsjd/git.png" alt="git.png" /></p>
<h2 id="注意事项">注意事项</h2>
<ul>
<li>合并<code class="language-plaintext highlighter-rouge">release 测试分支</code>过程中<strong>不进行冲突合并</strong>,遇到冲突即回退到上次提交;</li>
<li>开发人员未被合并的分支,将在下一次另建分支合并;</li>
<li>release 稳定版应保留<strong>多个</strong>历史版本;</li>
<li>应在版本被推送到局方环境<strong>之后</strong>再考虑删除开发人员分支。</li>
</ul>
<h2 id="场景">场景</h2>
<p>以下场景均假设有最近稳定版本<code class="language-plaintext highlighter-rouge">release_stable_20171221_1</code>,修改 bug 编号<code class="language-plaintext highlighter-rouge">12345</code>.</p>
<h3 id="1新建分支修改-bug">1、新建分支修改 bug</h3>
<ul>
<li>拉取代码,确保获得最新版本:<code class="language-plaintext highlighter-rouge">git pull</code>;</li>
<li>签出<code class="language-plaintext highlighter-rouge">release 稳定分支</code>分支:<code class="language-plaintext highlighter-rouge">git checkout release_stable_20171221_1</code>;</li>
<li>在稳定分支基础上,新建自己的分支:<code class="language-plaintext highlighter-rouge">git checkout -b fixbug_12345_jingyang_1</code>;</li>
<li>在自己的分支上修改代码,并提交。</li>
</ul>
<h3 id="2合并分支冲突">2、合并分支冲突</h3>
<p>合并过程中遇到冲突的分支将不会被合并。由于项目文件较多,冲突并不常见。如果遇有冲突,开发人员应该自己解决。</p>
<p>以下设想了两种场景,但无论是哪种场景,开发人员都应该找到冲突原因,协商解决。</p>
<ul>
<li>如果今天还有合并分支的机会:找到冲突原因,协商解决,确保下次合并没有冲突;如果已经发布了新的稳定版本,合并稳定版本到自己的版本</li>
<li>如果赶不上今天的合并:签出最新<code class="language-plaintext highlighter-rouge">release 稳定分支</code>,并合并到自己的分支中,确保代码是最新的。</li>
<li>合并<code class="language-plaintext highlighter-rouge">release 稳定分支</code>到自己的分支:<code class="language-plaintext highlighter-rouge">git merge release_stable_20171221_1 fixbug_12345_jingyang_1</code></li>
</ul>
<h3 id="3测试人员测试不通过">3、测试人员测试不通过</h3>
<ul>
<li>放弃之前的分支<code class="language-plaintext highlighter-rouge">fixbug_12345_jingyang_1</code>;</li>
<li>从最新的<code class="language-plaintext highlighter-rouge">release 稳定分支</code>签出新的分支:<code class="language-plaintext highlighter-rouge">git checkout -b fixbug_12345_jingyang_2</code>;</li>
<li>把前一个分支合并到新分支 <code class="language-plaintext highlighter-rouge">git merge fixbug_12345_jingyang_1 fixbug_12345_jingyang_2</code>,并重新开始修改。</li>
</ul>
<h3 id="4局方测试不通过">4、局方测试不通过</h3>
<p>局方不通过时,通常代码已经被合并到最新<code class="language-plaintext highlighter-rouge">release 稳定分支</code>中。</p>
<ul>
<li>从最新<code class="language-plaintext highlighter-rouge">release 稳定分支</code>签出新分支:<code class="language-plaintext highlighter-rouge">git checkout -b fixbug_12345_jingyang_2</code>;</li>
<li>在新分支上进行修改。</li>
</ul>
<h3 id="5bug修改经历多日">5、bug修改经历多日</h3>
<p>记得在最终合并分支之前把最新的<code class="language-plaintext highlighter-rouge">release 稳定分支</code>合并到自己的分支。</p>
以用户体验为导向的设计表现
2017-12-19T00:00:00+00:00
http://www.blogways.net/blog/2017/12/19/user-experience-oriented-design-performance
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#overview">概述</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#chapter1">表达清晰,浏览舒适</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#chapter2">有新更要有心</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#chapter3">化繁为简,引导性好</a></td>
</tr>
<tr>
<td>7</td>
<td><a href="#summary">总结</a></td>
</tr>
</tbody>
</table>
<h2 id="一概述">一、概述<i id="overview"></i></h2>
<p><img src="/images/chenwt/user-experience/1.0.jpg" alt="" />
国内的互联网起步很晚,早期的互联网设计也都较为“简陋”,甚至多数界面纯粹由程序同学自行搭建,所以相对来说界面视觉设计上呈现的效果都不尽人意。随着发展之迅速,人才喷涌,界面设计上越来越美观好看,在经历由拟物化到扁平化的转变过程中,人们也不再单纯关注于表象层面的美观与否,更加重视浏览、操作、使用过程中的体验效果,所以“用户体验”越来越被用户所关注!</p>
<p>引用看到过的一句话:”海底捞的服务是设计出来的,沃尔玛等超市的商品摆位也是设计出来的,苹果的产品就更不用说了。 而这一切的设计都要以“用户体验”为导向,要从满足用户最基本的心里需求出发,要不断的差异化 创新产品和服务得到用户的认可。”</p>
<p>“用户体验”之于产品是从用户了解到宣传信息到使用该产品一直到结束整个过程中都一直存在,而设计作为产品的表现层,是用户有形之中能看的到的,所以以用户体验为导向的设计所应有的表现我归纳为以下三点(当然并非只有这三点):</p>
<ol>
<li>表达清晰,浏览舒适</li>
<li>有新更要有“心”</li>
<li>化繁为简,引导性好</li>
</ol>
<p><i id="chapter1"></i></p>
<h2 id="二表达清晰浏览舒适">二、表达清晰,浏览舒适</h2>
<p>设计的核心意义和价值在于传达信息、引导用户,以此来达到解决用户的根本需求,设计不是纯艺术和炫技,所以视觉层面上信息传达是否清晰,浏览是否舒服尤为重要。</p>
<p>当设计之前的用研、交互等都做到位之后,到了设计呈现上却界面混乱,层次不清,元素杂乱,那么体验直接就落了下风,这是致命的,会直接导致用户情绪的不舒适,很难快速找到自己根本性需求的点,可能会直接摒弃离开!
<img src="/images/chenwt/user-experience/2.0.jpg" alt="" /></p>
<p><i id="chapter2"></i></p>
<h2 id="三有新更要有心">三、有新更要有“心”</h2>
<p>随着技术革新的加快,越来越多新的交互设计形式实现都不再困难,也都会被应用到实际项目中,这些交互可能是更有趣更生动或者能起到一些特别的作用,但是有时候却不一定被所有用户了解,用户绝大多数并不懂技术,所有浏览操作上会相对难以找到需求点,往往会在体验上大打折扣。比如现在经常用的导航折叠设计,当导航内容隐藏时,显示的是一个“三条横线”的形式,当用户点击时,展开菜单。</p>
<p>这种交互可以最大化的利用界面内的空间,更好的展示内容,但是有部分用户不了解这“三条横线”的意义,会找不到菜单所在,无法跳转到其他界面,这就在体验上造成了一定困扰,而如果我们这时候在“三条横线”这个设计形式后面或者下面增加一个“菜单”或者“menu”的词语,就可以很好的解决上述的问题!所以当我们在考虑为用户更好的服务的时候,更应该用“心”考虑下,是否另一方面对用户造成了困扰,并加以优化在设计呈现上给用户更好的体验!
<img src="/images/chenwt/user-experience/3.0.jpg" alt="" /></p>
<p><i id="chapter3"></i></p>
<h2 id="四化繁为简引导性好">四、化繁为简,引导性好</h2>
<p>没有最好的设计,只有更好的设计。</p>
<p>用户在使用过程中,会有很多操作,界面上多余的元素很有可能就会形成误导和阻碍,所以没有意义的元素设计应该直接舍弃,化繁为简,留下最合适最有意义的东西,可以使得页面更纯粹,用户引导性更好,体验感自然随之提高!
<img src="/images/chenwt/user-experience/4.0.jpg" alt="" /></p>
<p>(界面简洁明快,没有多余的设计元素,在色彩,文字,按钮,图片上都精细处理)</p>
<p><i id="summary"></i></p>
<h2 id="七总结">七、总结</h2>
<p>在这体验为王的时代,处处产生互动,也处处都有“用户体验存在”,设计作为其中一环,也显得尤为重要。</p>
<ul>
<li>用户的视线首先落在什么地方?</li>
<li>哪个设计要素在第一时间吸引用户的注意力?</li>
<li>它们对战略目标来讲是很重要的东西吗?</li>
<li>用户第一时间注意到的东西与你的目标是否一致?</li>
<li>你的设计和排版是如何影响用户的?</li>
</ul>
<p>我们做设计之前,如果能多思考一些类似的问题,并且站在用户的角度来看待这些问题,然后再加以解决优化,才能尽可能做到更好的体验效果。</p>
让你的设计更精致
2017-12-19T00:00:00+00:00
http://www.blogways.net/blog/2017/12/19/much-more-better-design
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#overview">概述</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#picture">图片,高质量图片的选择</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#font">文字,重点在哪里</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#color">色彩,如何成为“色”计师</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#icon">图标,从会画到画好之间的距离</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#enough-blank">留白,足够的空间</a></td>
</tr>
<tr>
<td>7</td>
<td><a href="#summary">小结</a></td>
</tr>
</tbody>
</table>
<h2 id="一概述">一、概述<i id="overview"></i></h2>
<p>当我们打开一个APP时,从视觉层面分析,影响用户对APP整体感官体验的元素主要有:图片、文字、色彩、图标、留白等。</p>
<p>图片的合理运用、清晰的信息层级、舒适的色彩搭配都将会提高整个APP的美感,从而为整个的产品体验加分。一个成功的产品,视觉层面只是其中的一部分,但是作为一个合格的UI设计师,我们要做的就是把这其中的一部分做到极致。</p>
<p>下面我们通过图片、文字、色彩、图标、留白这几个维度来解剖APP设计,发现那些微妙的细节。只需要比别人多提高1px的细节,你的APP设计就会更精致。</p>
<p><img src="/images/chenwt/better-design/1.0.png" alt="" /></p>
<h2 id="二图片高质量图片的选择">二、图片,高质量图片的选择<i id="picture"></i></h2>
<p>图片在APP中是非常常见的,图片的展现形式和图片的质量8.011都影响着用户对产品的感官体验。图片的定位就如同一个人的衣着品味,不同的穿衣风格会使别人对你作出不同的判断,为你打上不同社会属性的标签。
下面我们一起来看看图片在APP设计中需要注意的关键点,将会从图片比例、一致性、图片质量与真实性等方面进行分析。</p>
<h3 id="21-图片比例有什么讲究">2.1 图片比例有什么讲究?</h3>
<p>不同比例的图片所传达的信息主体不尽相同,根据产品属性我们会选择与之相符的图片比例进行整体的框架布局。</p>
<p>通过体验一些主流的APP,我们会发现一些比较常用的图片比例,如 <code class="language-plaintext highlighter-rouge">1:1</code>、<code class="language-plaintext highlighter-rouge">4:3</code>、<code class="language-plaintext highlighter-rouge">16:9</code>、<code class="language-plaintext highlighter-rouge">16:10</code>等等;也会发现一些打破常规比例的设计,我们需要分析它们的性格,结合自身产品的特点,才能在自己的APP设计中合理的加以运用。</p>
<p><img src="/images/chenwt/better-design/2.0.png" alt="" /></p>
<h4 id="211-11">2.1.1 <code class="language-plaintext highlighter-rouge">1:1</code></h4>
<p><img src="/images/chenwt/better-design/3.0.png" alt="" />
<code class="language-plaintext highlighter-rouge">1:1</code> 的图片比例强调主体的存在感-常用于产品展示、头像、特写展示等场景,在电商类APP中尤为常见。</p>
<h4 id="212-43-图像紧凑更易构图">2.1.2 <code class="language-plaintext highlighter-rouge">4:3</code> 图像紧凑、更易构图</h4>
<p><img src="/images/chenwt/better-design/4.0.png" alt="" />
<code class="language-plaintext highlighter-rouge">4:3</code> 的图片比例可以使图像更紧凑,更易构图,方便设计师发挥。由于手机屏幕容量较小,作为全屏展示时,该比例在App设计布局上面占用空间较大。作为设计师来说,这个比例经常接触,站酷、UI中国的作品封面、Dribbble作品展示等都采用这个比例。</p>
<h4 id="212-169-电影场景般的效果">2.1.2 <code class="language-plaintext highlighter-rouge">16:9</code> 电影场景般的效果</h4>
<p><img src="/images/chenwt/better-design/5.0.png" alt="" />
<code class="language-plaintext highlighter-rouge">16:9</code> 的图片比例可以呈现电影场景般的效果,多用于横向构图,是应用非常广泛的尺寸比例之一,能给用户一种视野开阔的体验。在很多影视娱乐类APP设计中运用广泛,如腾讯视频、网易云音乐等。</p>
<h4 id="213-1610-拥抱黄金比例">2.1.3 <code class="language-plaintext highlighter-rouge">16:10</code> 拥抱黄金比例</h4>
<p>黄金比例就像金字塔上的明珠,越接近她越有魅力,反之会魅力减弱,16:10的图片比例最为接近。设计没有绝对的标准,我们可以遵循一些优秀的经验规则,但是也要敢于突破规则,尝试更多的可能性。</p>
<h3 id="22-图片比例选择方式">2.2 图片比例选择方式</h3>
<ol>
<li>以商品展示效果为准,选择能够充分表现商品特点的图片展示比例;</li>
<li>以产品气质为准,选择符合产品内容气质的图片展示比例;</li>
<li>结合产品特点选择合适的常用比例;</li>
<li>根据版面布局灵活的自定义特殊的比例值;</li>
<li>分析→打破→创新,创造出符合某种规律或者美学概念的比例值。</li>
</ol>
<h2 id="三文字重点在哪里">三、文字,重点在哪里<i id="font"></i></h2>
<p>文字设计的层次感决定了信息的高效传达,通过对文字信息的层次处理可以有效的帮助用户获取信息,提高用户对产品的操作效率。</p>
<h3 id="31-对文字信息进行层级区分">3.1 对文字信息进行层级区分</h3>
<p>当我们拿到交互原型或者别的需求文档时,我们需要对文字的信息层级进行有效的区分,这样才能让用户快速的获取和理解信息传达的内容。文字信息可以简单划分为重要信息、次要信息、辅助信息等。在进行文字排版时,需要明确的梳理好信息之间的层级关系,提高用户对产品的整体体验。</p>
<p>通过对字体大小、颜色、留白、层级划分等处理,把相同属性的信息归类设计,让整个信息排列主次分明,层级清晰。
<img src="/images/chenwt/better-design/6.0.png" alt="" /></p>
<p>设计师在对文字进行视觉表现时,为了达到整体界面的视觉平衡也需要减少对文字样式的运用,不可为了突出文字信息而采用过多的表现样式。
<img src="/images/chenwt/better-design/7.0.png" alt="" /></p>
<h3 id="32-预估好信息呈现的最大值">3.2 预估好信息呈现的最大值</h3>
<p>当我们在进行界面设计时,初级设计师往往会忽略文字信息的最大值,只是按照自己的习惯进行完美的布局,最终进入到测试环节时才发现为什么比自己预期的字数多出这么多信息,此时就会出现返工的情况,给整体的产品开发进度带来风险。
<img src="/images/chenwt/better-design/8.0.png" alt="" /></p>
<p>作为一名合格的UI设计师,我们需要预估好信息呈现的最大值,而不是取最小值或者随意进行设计,这样将会在执行的过程中遇到更多不可控的风险。</p>
<h2 id="四色彩如何成为色计师">四、色彩,如何成为“色”计师<i id="color"></i></h2>
<p>色彩给人的感受是最直观的,不同性格的配色传达不同的情感。关于配色有一些方法可寻,但是也存在一定的感性判断。作为视觉设计师,我们需要学习理性的方法技巧,也要不断欣赏优秀的作品,提高自身的审美能力。</p>
<h3 id="41-色彩基础知识">4.1 色彩基础知识</h3>
<p>色彩分为无彩色系和有彩色系,无彩色系是指白色、黑色、各种深浅不同的灰色;有彩色系是指红、橙、黄、绿、青、蓝、紫等颜色。</p>
<p>关于色彩的更多理论知识这里不做展开,大家自行脑补色相、纯度、明度、对比、性格等等方面的理论知识。</p>
<h3 id="42-建立色彩库">4.2 建立色彩库</h3>
<p>作为初级设计师我们对配色的把控不是很稳定,为了提高工作效率,我们需要通过一些理性的方式建立大量的色彩库,应对不同的需求。
<img src="/images/chenwt/better-design/9.0.png" alt="" /></p>
<p>下面列举部分个人比较常用的方式供大家参考,色彩收集的方法有很多,我们只需要掌握几个比较适合自己的即可,只要养成习惯并长期坚持,哪怕只运用一种方式,也是收获颇丰的。</p>
<h3 id="43-通过各类app采集色彩">4.3 通过各类APP采集色彩</h3>
<p>体验不同领域的APP,建立不同领域对APP色彩组合的选择,为后期项目设计奠定基础。根据主色进行分类,如红色系列:网易云音乐、京东、网易严选、网易考拉等等;也可以根据产品气质分类,如文艺、时尚、科技、可爱等等。
<img src="/images/chenwt/better-design/10.0.png" alt="" /></p>
<h3 id="44-从电影中采集色彩">4.4 从电影中采集色彩</h3>
<p>相信大家都喜欢看大片,这部片子之所以能得到大家的追捧,必定有太多值得大家学习的元素。作为神经敏感的设计师群体,那些刺激到我们神经元的优秀影片场景总是不能错过的。
<img src="/images/chenwt/better-design/11.0.png" alt="" /></p>
<h3 id="45-提高审美增强感性判断力">4.5 提高审美,增强感性判断力</h3>
<p>配色能力虽然可以通过一些理性的方法提高,但是也存在一定的感性判断。配色中细微的差异往往都是感性的判断,我们需要不断的欣赏摄影、绘画、设计作品等等,综合的提高自身的审美,才能不断增强感性的判断力。
作为UI设计师,你不能只关注界面设计,你可以看平面作品、摄影绘画、影视动效,体验手工艺制作、运动娱乐、细心的体验生活中的每一次变化。
<img src="/images/chenwt/better-design/12.0.png" alt="" /></p>
<h2 id="五图标从会画到画好之间的距离">五、图标,从会画到画好之间的距离<i id="icon"></i></h2>
<p>图标是APP设计中的点睛之笔,既能辅助文字信息的传达,也能作为信息载体被高效的识别。图标也有一定的界面装饰作用,提高界面整体的美观度。</p>
<p>很多初级设计师都会忽略图标的重要性,也养成去素材网站下载复用的习惯,当这样的习惯养成后便会逐步丧失自己动手的驱动力,什么元素都希望能找到素材下载,工作数年之后很快就遇到了自己的瓶颈期。</p>
<p>设计师对图标设计的态度与把控能力,将会是拉开你与其他设计师差距的因素之一。图标设计有下载复用 → 动手设计 → 规范设计 → 融入品牌基因等几个阶段,你现在属于哪个阶段呢?</p>
<h3 id="51-下载复用">5.1 下载复用</h3>
<p>下载复用是很多初入行业的设计师习惯的工作方式之一,由于自身对软件技法、设计技巧、创意能力等方面的不足,无法从创意到标准制图完成一个合格的图标设计。</p>
<p>缺点:图标设计风格与细节处理都完全不统一,这样的习惯一旦养成就会逐步丧失自己的动手能力。
<img src="/images/chenwt/better-design/13.0.png" alt="" /></p>
<h3 id="52-动手设计">5.2 动手设计</h3>
<p>对于大部分有设计追求的设计师,都会意识到图标设计的重要性,也会结合产品特点绘制统一风格的图标。</p>
<p>注意事项:图标设计风格有:线性图标、填充图标、面型图标、扁平图标、手绘风格图标和拟物图标等。无论我们选择何种表现形式,在进行设计的时候都要保持风格的统一性,由于图标的体量不同,相同尺寸下不同体量的图标视觉平衡不尽相同,例如相同尺寸的正方形会比圆形显大。因此,我们需要根据图标的体量对其大小做出相应的调整。
<img src="/images/chenwt/better-design/14.0.png" alt="" /></p>
<h3 id="53-规范设计">5.3 规范设计</h3>
<p>当设计师养成自己动手的习惯以后,恭喜你已经进步了,保持这样的习惯。随着软件技法的成熟我们需要严格控制自己的随性,运用标准的规范进行图标设计。在标准设计的基础上面我们可以发挥自己的创意,也不一定要局限在标准里面,但是总体的本质需要符合设计规范。
<img src="/images/chenwt/better-design/15.0.png" alt="" /></p>
<h2 id="六留白足够的空间">六、留白,足够的空间<i id="enough-blank"></i></h2>
<p>适当的留白可以让你的界面更有灵性,给信息之间预留更多的空间,也能更好的表达信息之间的层次感,相比拥挤的信息布局更能给人舒适的体验。</p>
<p>当设计师的留白意愿被产品或运营以“希望放更多内容”拒绝时,作为设计师我们可以从不同的方向试着表达自己的观点:</p>
<ol>
<li>设计出对比稿,把产品需要的方案和你觉得完美的方案进行对比;</li>
<li>筛选出这样处理的优秀案例,以成功的案例说服产品接受你的方案;</li>
<li>进行用户测试,选择一些目标用户进行体验,从用户心声入手设计最佳的方案;</li>
<li>更多沟通的方法有待你去挖掘,最终的目的都是希望做出更好的产品。</li>
</ol>
<h2 id="七小结">七、小结<i id="summary"></i></h2>
<ol>
<li>不同的图片比例反应不同的特征,根据产品特点合理的选择;</li>
<li>设计中保持相同的图片比例,不仅使视觉表达一致,也能给后期运营维护带来便利;</li>
<li>通过提高图片的质量来提高设计作品的美感度,但是也要保证图片的真实还原;</li>
<li>文字排版需要注意信息的层次、信息容量的最大值、巧妙的运用提示符等;</li>
<li>养成不断建立和丰富色彩库的习惯;</li>
<li>提高审美,增强感性判断力,养成分析的习惯;</li>
<li>图标设计经历的几个环节:下载复用 → 动手设计 → 规范设计 → 融入品牌基因;</li>
<li>适当的留白可以给人更加舒适的体验。</li>
</ol>
三种技巧搞定背景
2017-12-19T00:00:00+00:00
http://www.blogways.net/blog/2017/12/19/background-skill
<p><img src="/images/chenwt/background-skill.jpg" alt="" /></p>
spring-cloud1:服务注册与发现
2017-12-17T00:00:00+00:00
http://www.blogways.net/blog/2017/12/17/spring-cloud-1
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#spring-cloud">spring cloud简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#mircro-service">微服务架构</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#service-register">服务注册与发现</a></td>
</tr>
</tbody>
</table>
<h2 id="一spring-cloud简介">一、spring cloud简介<a href="spring-cloud"></a></h2>
<p>Spring Cloud是一个基于Spring Boot实现的云应用开发工具,它为基于JVM的云应用开发中涉及的配置管理、服务发现、断路器、智能路由、微代理、
控制总线、全局锁、决策竞选、分布式会话和集群状态管理等操作提供了一种简单的开发方式。</p>
<p>Spring Cloud包含了多个子项目(针对分布式系统中涉及的多个不同开源产品),比如:Spring Cloud Config、Spring Cloud Netflix、Spring
Cloud0 CloudFoundry、Spring Cloud AWS、Spring Cloud Security、Spring Cloud Commons、Spring Cloud Zookeeper、Spring
Cloud CLI等项目。</p>
<h2 id="二微服务架构">二、微服务架构<a href="mircro-service"></a></h2>
<p>“微服务架构”在这几年非常的火热,以至于关于微服务架构相关的开源产品被反复的提及(比如:netflix、dubbo),Spring Cloud也因Spring社区
的强大知名度和影响力也被广大架构师与开发者备受关注。</p>
<p>那么什么是“微服务架构”呢?简单的说,微服务架构就是将一个完整的应用从数据存储开始垂直拆分成多个不同的服务,每个服务都能独立部署、独立
维护、独立扩展,服务与服务间通过诸如RESTful API的方式互相调用。</p>
<p>对于“微服务架构”,大家在互联网可以搜索到很多相关的介绍和研究文章来进行学习和了解。也可以阅读始祖Martin Fowler的<a href="https://martinfowler.com/articles/microservices.html">《Microservices》</a>
(中文版翻译<a href="https://www.cnblogs.com/zgynhqf/p/5323056.html">点击查看</a>),本文不做更多的介绍和描述。</p>
<h2 id="三服务注册与发现">三、服务注册与发现<a href="service-register"></a></h2>
<p>Spring Cloud为服务治理做了一层抽象接口,所以在Spring Cloud应用中可以支持多种不同的服务治理框架,比如:Netflix Eureka、Consul、
Zookeeper。在Spring Cloud服务治理抽象层的作用下,我们可以无缝地切换服务治理实现,并且不影响任何其他的服务注册、服务发现、
服务调用等逻辑。
下面我们介绍用eureka和consul来实现服务治理。</p>
<h3 id="31-spring-cloud-eureka">3.1 Spring Cloud Eureka</h3>
<p>Spring Cloud Eureka是Spring Cloud Netflix项目下的服务治理模块。而Spring Cloud Netflix项目是Spring Cloud的子项目之一,主要内容
是对Netflix公司一系列开源产品的包装,它为Spring Boot应用提供了自配置的Netflix OSS整合。通过一些简单的注解,开发者就可以快速的在应用
中配置一下常用模块并构建庞大的分布式系统。它主要提供的模块包括:服务发现(Eureka),断路器(Hystrix),智能路由(Zuul),客户端
负载均衡(Ribbon)等。</p>
<p>下面,就来具体看看如何使用Spring Cloud Eureka实现服务治理。</p>
<ol>
<li>创建服务中心
创建一个maven工程,pom文件里写入
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.6.RELEASE</version>
</parent>
<groupId>sc.learn.dalston</groupId>
<artifactId>micro-service01</artifactId>
<version>1.0-SNAPSHOT</version>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Dalston.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</code></pre></div> </div>
<p>在此工程下新建module,eureka-server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <parent>
<artifactId>micro-service01</artifactId>
<groupId>sc.learn.dalston</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>eureka-server</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka-server</artifactId>
</dependency>
</dependencies>
</code></pre></div> </div>
<p>通过<code class="language-plaintext highlighter-rouge">@EnableEurekaServer</code>注解启动一个服务注册中心提供给其他应用进行对话。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@SpringBootApplication
@EnableEurekaServer
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
</code></pre></div> </div>
<p>在<code class="language-plaintext highlighter-rouge">application.properties</code>配置文件中增加如下信息:
```
server.port=1111</p>
</li>
</ol>
<p>eureka.instance.hostname=localhost
eureka.client.register-with-eureka=false
eureka.client.fetch-registr=false
eureka.client.serviceUrl.defaultZone=http://${eureka.instance.hostname}:${server.port}/eureka/</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>在默认设置下,该服务注册中心也会将自己作为客户端来尝试注册它自己,我们通过
`eureka.client.register-with-eureka=false`配置禁止客户端注册自己。
启动程序后访问:[http://localhost:1111/](http://localhost:1111/),可以看到下面的页面。此时还没有发现任何服务注册。
![1](/images/qianwx/sc-learn/sc-learn-eureka-no-ins.png)
2. 创建provider
下面我们创建提供服务的客户端,并向服务注册中心注册自己。
创建新module:provider-eureka
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><parent>
<artifactId>micro-service01</artifactId>
<groupId>sc.learn.dalston</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>provider-eureka</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
</dependencies> ``` 创建controller: ``` @RestController public class HelloController {
private final Logger logger = Logger.getLogger(getClass().getName());
@Autowired
DiscoveryClient discoveryClient;
@RequestMapping(value = "/hello", method = RequestMethod.GET)
public String index() {
logger.info("/hello service_id:" + discoveryClient.getServices());
return "Hello world!";
} } ``` 在应用主类中通过加上`@EnableDiscoveryClient`注解,该注解能激活Eureka中的DiscoveryClient实现。 ``` @SpringBootApplication @EnableDiscoveryClient public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class,args);
} } ``` 在`application.properties`中增加配置 ``` server.port=2222 spring.application.name=hello-service eureka.client.serviceUrl.defaultZone=http://localhost:1111/eureka/ ``` 启动应用,并刷新[http://localhost:1111](http://localhost:1111),可以看到新增了HELLO-SERVICE实例。 ![2](/images/qianwx/sc-learn/sc-learn-eureka-ins.png)
</code></pre></div></div>
<h3 id="32-spring-cloud-consul">3.2 Spring Cloud Consul</h3>
<p>Spring Cloud Consul项目是针对Consul的服务治理实现。Consul是一个分布式高可用的系统,它包含多个组件,但是作为一个整体,
在微服务架构中为我们的基础设施提供服务发现和服务配置的工具。它包含了下面几个特性:
* 服务发现
* 健康检查
* Key/Value存储
* 多数据中心</p>
<ol>
<li>服务中心
consul自身提供了服务端,我们不需要创建类似于eureka-server的module。通过命令启动consul服务端:
<code class="language-plaintext highlighter-rouge">consul agent -dev</code>
访问<a href="http://localhost:8500">http://localhost:8500</a>
<img src="/images/qianwx/sc-learn/sc-learn-consul-no-ins.png" alt="3" /></li>
<li>provider
同上面的<code class="language-plaintext highlighter-rouge">provider-eureka</code>一样,我们只需更改pom文件里的依赖和application.properties的一些配置即可。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-consul-discovery</artifactId>
</dependency>
</code></pre></div> </div>
<p>pom里将eureka的依赖改为consul的依赖。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>server.port=3333
spring.application.name=hello-service
spring.cloud.consul.host=127.0.0.1
spring.cloud.consul.port=8500
</code></pre></div> </div>
<p>application。properties修改端口为3333,同时将注册中心改为consul的服务。
启动应用,刷新<a href="http://localhost:8500">http://localhost:8500</a>,结果如图:
<img src="/images/qianwx/sc-learn/sc-learn-consul-ins.png" alt="4" />
可以看到hello-service注册成功。</p>
</li>
</ol>
网页设计师必备整理术
2017-12-16T00:00:00+00:00
http://www.blogways.net/blog/2017/12/16/necessary-tech-of-webdesigner
<p><img src="/images/chenwt/necessary-tech.jpg" alt="" /></p>
多种风格的版式改造
2017-12-16T00:00:00+00:00
http://www.blogways.net/blog/2017/12/16/multi-style-layout-improve
<p><img src="/images/chenwt/multi-style-improve.jpg" alt="" /></p>
使用idea对jstorm拓扑进行远程调试
2017-12-16T00:00:00+00:00
http://www.blogways.net/blog/2017/12/16/jstorm-remote-debug
<p>在测试过程中,出现沙箱环境中提交的拓扑和本地启动的拓扑运行的结果不一致。查看log日志也没发现出问题原因,
不得已祭出远程debugg大招。下面是操作步骤:</p>
<ol>
<li>配置yaml
在yaml里增加配置 topology.work。childopts,
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> topology.worker.childopts : "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=13006"
</code></pre></div> </div>
<p>-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=13006,参数是jdk1.5+后的开启jdwp(Java Debug Wire Protocol)
的命令,这里指定使用socket传输,端口使用13006(端口可以自行指定)。</p>
</li>
<li>
<p>提交拓扑
提交拓扑命令是jstorm提交拓扑的命令,记得配置文件要更换为修改后的配置。</p>
</li>
<li>
<p>进入jstorm nimbus监控页面,查看拓扑的worker运行在哪些机器上。
<img src="/images/qianwx/nimbus_view1.png" alt="1" />
找到自己的拓扑,点击进去,
<img src="/images/qianwx/nimbus_view2.png" alt="2" />
点击Task Stats,
<img src="/images/qianwx/nimbus_view3.png" alt="3" />
可以看到自己的tasks,选择其中一个,记录下ip</p>
</li>
<li>
<p>在idea中创建remote
<img src="/images/qianwx/idea_remote.png" alt="4" />
把host修改成task记录的ip,端口填写13006(自定义),点击ok保存。</p>
</li>
<li>点击debug按钮
<img src="/images/qianwx/idea_remote_succ.png" alt="5" />
出现如图,就可以成功debug了</li>
</ol>
手机界面扁平化设计案例赏析
2017-12-16T00:00:00+00:00
http://www.blogways.net/blog/2017/12/16/flat-design-appr
<h2 id="一概述">一、概述</h2>
<p>古语常说:去粗取精,去伪存真。自小念念不忘的句子,不仅是教会我们为人处世的道理,更是准确的揭示了事物的本质和规律。自13年兴起的Flat设计趋势,也恰恰好符合了这一规律。去除冗余、厚重和繁杂的装饰效果,这是Flat设计的核心意义。扁平化设计尤其是在移动端上可以充分发挥其优势,可以更加简单直接的将信息和事物的工作方式展示出来,减少用户认知障碍的产生。</p>
<p>如何去掉多余的透视、纹理、渐变以及能做出3D效果的元素让“信息”本身重新作为核心被凸显出来?如何设计出抽象、极简和符号化的设计元素?这些都是扁平化设计需要斟酌的问题,小到一个ICON,大到整体的设计理念和风格,扁平化设计绝不是简单的“简单化设计”。</p>
<h2 id="二案例赏析">二、案例赏析</h2>
<p>近期整理了一些优秀的手机端扁平化设计案例,这些作品大多出自Behance和Dribbble上的设计达人之手:</p>
<h3 id="1-wedo">1. <em><a href="https://www.wedo.com">wedo</a></em></h3>
<p>亮点:集合形状的元素,温软饱和的配色,都能达到舒适的视觉效果。单一背景色,几乎以白色纯色为主,配合集合图形和简单线条。衬线字体,色彩和大小形成对比,强化重要信息。</p>
<p><img src="/images/chenwt/flatdesign/1.0.png" alt="" /></p>
<h3 id="2vault-financial-app-design">2. <em><a href="https://www.behance.net/gallery/55376655/Vault-financial-app-design">Vault financial app design</a></em></h3>
<p>亮点:logo “ V ” 的设计非常巧妙,凸显主题Vault Financial app design,且配色简单。纯白背景色,留白空间。排版中的重点突出,信息层次分明,界面很干净。</p>
<p><img src="/images/chenwt/flatdesign/2.0.png" alt="" /></p>
<h3 id="3intimate">3. <em><a href="https://dribbble.com/shots/3903572-Intimate">Intimate</a></em></h3>
<p>亮点:纯白背景留白,空间点十足。文本排版合理,字体大小对比鲜明且不突兀,突出主要信息。配图简洁,风格一致,整体界面和谐干净,交互合理,不会让用户产生误解。 </p>
<p><img src="/images/chenwt/flatdesign/3.0.png" alt="" /> </p>
<h3 id="4upper-app">4. <em><a href="https://tower.im/projects/4d5e9e452f424e3aa5f06b52f45382a1/docs/e6982f41ac9149ffbb0dcf22167bb954">Upper APP</a></em></h3>
<p>亮点:色彩,红黑白三种色彩,对比鲜明,明快鲜亮,且所占比例符合6:3:1的UI配色黄金比例。字体色彩选择与其背景形成对比,突出文本信息,不会产生阅读障碍。</p>
<p><img src="/images/chenwt/flatdesign/4.0.png" alt="" /></p>
<h3 id="5sea-schedule-app">5. <em><a href="https://www.behance.net/gallery/38120159/Sea-Schedule-App">Sea Schedule App</a></em></h3>
<p>亮点:采用UI安全色蓝色为纯色背景,易于接近用户,界面简洁。界面元素为蓝色背景的颜变色,但仍旧采用梯度式渐变。</p>
<p><img src="/images/chenwt/flatdesign/5.0.png" alt="" /></p>
<h3 id="6-analytics">6. <em><a href="https://dribbble.com/shots/3113887-Analytics">Analytics</a></em></h3>
<p>亮点:紫色纯色背景色,界面整齐统一, icon简单流畅。</p>
<p><img src="/images/chenwt/flatdesign/6.0.png" alt="" /></p>
<h3 id="7-lines-activity-tracker">7. <em><a href="https://www.behance.net/gallery/40518179/Lines-activity-tracker">Lines activity tracker</a></em></h3>
<p>亮点:元素简洁,文本排突出重要信息,不会给用户造成视觉疲劳。图标简洁,线条干净。</p>
<p><img src="/images/chenwt/flatdesign/7.0.png" alt="" /></p>
<h3 id="8foly-mobile-app">8. <em><a href="https://www.behance.net/gallery/50570935/Foly-Mobile-App">Foly Mobile App</a></em></h3>
<p>亮点:logo设置简洁贴合主题,且色彩对比鲜明。神色纯色背景,具有高级感。 界面元素色彩和背景色对比鲜明。</p>
<p><img src="/images/chenwt/flatdesign/8.0.png" alt="" />
</p>
<h3 id="9smart-home-app---iphone-x">9. <em><a href="https://www.behance.net/gallery/57084317/Smart-Home-App-iPhone-X">Smart Home App - iPhone X</a></em></h3>
<p>亮点:浅色纯色背景,绿色给人舒适愉悦的体验。图标是亮点,元素生动简洁。</p>
<p><img src="/images/chenwt/flatdesign/9.0.png" alt="" /></p>
<h3 id="10weather-app">10. <em><a href="https://www.behance.net/gallery/53996035/Weather-app">Weather app</a></em></h3>
<p>亮点:深紫色配色大胆时尚,几何元素的使用引人注目。</p>
<p><img src="/images/chenwt/flatdesign/10.0.png" alt="" /></p>
<h2 id="三总结">三、总结</h2>
<p>以上是10个优秀的手机端扁平化设计界面,总结不难发现,这些作品里的共同点。那么,在进行手机Flat界面设计的时候,有4点选需要注意:</p>
<h3 id="1简约">1. 简约</h3>
<p>任何出现在界面上的元素,都必须不脱离Flat设计的中心思想:简约。比如可以使用含义明确且被大众认知接受的图标。使用无衬线字体而不用过度花哨的字体。</p>
<h3 id="2配色方案">2. 配色方案</h3>
<p>使用纯色和比较生动的色彩强调突出想要展示的重要信息。但值得注意的是,Flat设计的色彩选择不一定总是依赖于明亮生动的色彩。
以上是20 个优秀的手机端扁平化设计界面,希望能给你的设计带来灵感和启发。相信在欣赏了这么多的优秀设计后,不难发现,这些作品里的共同点。那么,在进行手机Flat界面设计的时候,究竟有那些要点选需要注意呢?</p>
<h3 id="3文本排版">3. 文本排版</h3>
<p>文字使用无衬线字体,这种字体能呈现简洁干脆的界面效果,可以和页面的其他元素很好的配合,营造舒适的视觉。
文本标题和文本主题可通过字体大小和色彩产生对比效果,以突出中心信息。但不可夸张,对比要适度。</p>
<h3 id="4交互设计">4. 交互设计</h3>
<p>Flat设计的用户体验不仅是机遇其简约干净的视觉效果,还有其清晰简洁的交互设计,不能导致用户在使用过程中产生迷惑和不知所措。</p>
使用nginx搭建tomcat集群
2017-12-10T00:00:00+00:00
http://www.blogways.net/blog/2017/12/10/nginx
<p>刚进公司的时候,一直是使用nginx作为静态页面web服务器。其实nginx另一个更大作用是软负载均衡。
优点:</p>
<p>1、高吞吐,官方称单台nginx服务最大能支持100000并发连接,这是理论值实际可能达不到,看网上人家测试一般单台30000以上并发连接是没问题的。</p>
<p>2、廉价:和动辄10多万F5相比,nginx免费开源,对于几台机器的集群来讲性价比更高更合适。</p>
<p>3、低内存消耗:nginx处理请求是异步非阻塞的,在高并发下nginx 能保持低资源低消耗高性能。一般情况下,10 000个非活跃的HTTP Keep-Alive连接在Nginx中仅消耗2.5MB的内存,这是Nginx支持高并发连接的基础。</p>
<p>动手感受一把吧!
准备工作:三台centos虚拟机,两台用于部署tomcat,一台用于部署nginx。</p>
<h2 id="一部署两台tomcat">一、部署两台tomcat</h2>
<blockquote>
<p>tomcat部署需要jdk环境,此处不再赘述jdk如何部署。
准备了192.168.127.10和192.168.127.11两台主机部署tomcat</p>
</blockquote>
<h4 id="1下载tomcat">1.下载tomcat</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://mirrors.hust.edu.cn/apache/tomcat/tomcat-8/v8.5.24/bin/apache-tomcat-8.5.24.tar.gz
</code></pre></div></div>
<h4 id="2解压启动">2.解压、启动</h4>
<p>解压后修改默认主页,方便识别</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar -zxvf apache-tomcat-8.5.24.tar.gz
cd apache-tomcat-8.5.24
./bin/start.sh
</code></pre></div></div>
<h4 id="3scp一份到另一台主机启动">3.scp一份到另一台主机,启动</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>scp -r apache-tomcat-8.5.24 root@192.168.127.11:/usr/local/src/
</code></pre></div></div>
<p><img src="/images/gaoxiaobo/tomcat1.png" alt="1" /></p>
<p><img src="/images/gaoxiaobo/tomcat2.png" alt="2" /></p>
<h2 id="二部署nginx">二、部署nginx</h2>
<blockquote>
<p>192.168.127.12用于部署nginx</p>
</blockquote>
<p><strong>1、gcc 安装</strong>
安装 nginx 需要先将官网下载的源码进行编译,编译依赖 gcc 环境,如果没有 gcc 环境,则需要安装:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install gcc-c++
</code></pre></div></div>
<p><strong>2、PCRE pcre-devel 安装</strong>
PCRE(Perl Compatible Regular Expressions) 是一个Perl库,包括 perl 兼容的正则表达式库。nginx 的 http 模块使用 pcre 来解析正则表达式,所以需要在 linux 上安装 pcre 库,pcre-devel 是使用 pcre 开发的一个二次开发库。nginx也需要此库。命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install -y pcre pcre-devel
</code></pre></div></div>
<p><strong>3、zlib 安装</strong>
zlib 库提供了很多种压缩和解压缩的方式, nginx 使用 zlib 对 http 包的内容进行 gzip ,所以需要在 Centos 上安装 zlib 库。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install -y zlib zlib-devel
</code></pre></div></div>
<p><strong>4、OpenSSL 安装</strong>
OpenSSL 是一个强大的安全套接字层密码库,囊括主要的密码算法、常用的密钥和证书封装管理功能及 SSL 协议,并提供丰富的应用程序供测试或其它目的使用。 nginx 不仅支持 http 协议,还支持 https(即在ssl协议上传输http),所以需要在 Centos 安装 OpenSSL 库。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install -y openssl openssl-devel
</code></pre></div></div>
<p><strong>5、下载nginx、解压</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget -c https://nginx.org/download/nginx-1.10.1.tar.gz
tar -zxvf nginx-1.10.1.tar.gz
cd nginx-1.10.1
</code></pre></div></div>
<p><strong>6、配置</strong>
此处我们使用默认配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure
</code></pre></div></div>
<p><strong>7、编译安装</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make
make install
</code></pre></div></div>
<p>至此,nginx已经安装完毕。</p>
<h2 id="三配置nginx">三、配置nginx</h2>
<p>修改配置文件nginx.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
#user nobody;
worker_processes 1;
#error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info;
#pid logs/nginx.pid;
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
#log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"';
#access_log logs/access.log main;
sendfile on;
#tcp_nopush on;
#keepalive_timeout 0;
keepalive_timeout 65;
#gzip on;
upstream my_project_nginx{
# ip_hash;
server 192.168.127.10:8080 weight=1; #weight是权重的意思,权重越大分配概率越大。
server 192.168.127.11:8080 weight=1;
}
server {
listen 80;
server_name localhost;
#charset koi8-r;
#access_log logs/host.access.log main;
location / {
proxy_set_header Host $host:80; #端口跟上面的一致
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://my_project_nginx; #此处my_project_nginx和上面的upstream my_project_nginx对应
client_max_body_size 10m;
client_body_buffer_size 256k;
proxy_connect_timeout 600;
proxy_send_timeout 600;
proxy_read_timeout 600;
proxy_buffer_size 32k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 512k;
}
#error_page 404 /404.html;
# redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
# proxy the PHP scripts to Apache listening on 127.0.0.1:80
#
#location ~ \.php$ {
# proxy_pass http://127.0.0.1;
#}
# pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
#
#location ~ \.php$ {
# root html;
# fastcgi_pass 127.0.0.1:9000;
# fastcgi_index index.php;
# fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name;
# include fastcgi_params;
#}
# deny access to .htaccess files, if Apache's document root
# concurs with nginx's one
#
#location ~ /\.ht {
# deny all;
#}
}
}
</code></pre></div></div>
<p><strong>启动</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./sbin/nginx
</code></pre></div></div>
<h3 id="四运行集群效果">四、运行集群效果</h3>
<blockquote>
<p>打开nginx服务地址http://192.168.127.12/,刷新页面发现没有任何变化,看了半天的配置文件没有发现问题,原来是浏览器缓存在作怪。Chrome调试模式下清除缓存重新加载出现预期效果,如下图:</p>
</blockquote>
<p><img src="/images/gaoxiaobo/nginx1.png" alt="3" /></p>
<p><img src="/images/gaoxiaobo/nginx2.png" alt="4" /></p>
<h2 id="五拓展-负载均衡规则">五、拓展 负载均衡规则</h2>
<p>1、轮询(默认)
每个请求按时间顺序逐一分配到不同的后端服务器,如果后端服务器down掉,能自动剔除。</p>
<p>2、weight
指定轮询几率,weight和访问比率成正比,用于后端服务器性能不均的情况。
例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>upstream bakend {
server 192.168.127.10 weight=1;
server 192.168.127.11 weight=1;
}
</code></pre></div></div>
<p>3、ip_hash
每个请求按访问ip的hash结果分配,这样每个访客固定访问一个后端服务器,可以解决session的问题。
例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>upstream resinserver{
ip_hash;
server 192.168.127.10;
server 192.168.127.11;
}
</code></pre></div></div>
<p>4、fair(第三方)
按后端服务器的响应时间来分配请求,响应时间短的优先分配。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>upstream resinserver{
server 192.168.127.10;
server 192.168.127.11;
fair;
}
</code></pre></div></div>
<p>5、url_hash(第三方)</p>
<p>按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,后端服务器为缓存时比较有效。
例:在upstream中加入hash语句,server语句中不能写入weight等其他的参数,hash_method是使用的hash算法</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>upstream resinserver{
server 192.168.127.10;
server 192.168.127.11;
hash $request_uri;
hash_method crc32;
}
</code></pre></div></div>
Spring中Bean的一生
2017-12-10T00:00:00+00:00
http://www.blogways.net/blog/2017/12/10/lifecycle
<p><img src="/images/jyjsjd/bean.png" alt="bean.png" /></p>
<h3 id="一bean-实例化策略beanwrapper">一、Bean 实例化策略——BeanWrapper</h3>
<p>容器通过策略模式决定以何种方式实例化 Bean,通常通过<em>反射</em>或<em>CGLIB</em>。实例化策略模式的接口是 <code class="language-plaintext highlighter-rouge">InstantiationStrategy</code>。</p>
<ul>
<li>SimpleInstantiationStrategy:通过反射实例化对象,但<strong>不支持</strong><em>方法注入式</em>的对象实例化。</li>
<li>CglibSubclassingInstantiationStrategy:以 CGLIB 动态字节码方式实现实例化,并不是直接返回对象实例,而是对象的包装,返回 <code class="language-plaintext highlighter-rouge">BeanWrapper</code> 实例。</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">BeanWrapper</code> 的实现类 <code class="language-plaintext highlighter-rouge">BeanWrapperImpl</code> 同时直接或间接地继承了 <code class="language-plaintext highlighter-rouge">PropertyEditorRegistry</code> 和 <code class="language-plaintext highlighter-rouge">TypeConverter</code>,可以用到 <code class="language-plaintext highlighter-rouge">CustomEditorConfigurer</code> 转换类型。</p>
<p><img src="/images/jyjsjd/beanwrapper.png" alt="beanwrapper.png" /></p>
<h3 id="二aware-接口">二、Aware 接口</h3>
<p>当对象实例化完成并且相关属性和依赖设置完成之后,Spring 容器会检查当前实例对象是否实现了一系列 <em>Aware</em> 接口,并把 Aware 接口定义的依赖注入进去。</p>
<h4 id="1beanfactory">1、BeanFactory</h4>
<ul>
<li>BeanNameAware:将该对象实例的 bean 定义对应的 <em>BeanName</em> 设置到当前对象实例。</li>
<li>BeanClassLoaderAware:将对应加载当前 bean 的 <em>ClassLoader</em> 注入到当前实例。</li>
<li>BeanFactoryAware:BeanFactory 会将自身注入到当前对象。</li>
</ul>
<h4 id="2applicationcontext">2、ApplicationContext</h4>
<p>以下所有都会注入 <em>ApplicationContext</em> 容器本身:</p>
<ul>
<li>ResourceLoaderAware</li>
<li>ApplicationEventPublisherware</li>
<li>MessageSourceAware</li>
<li>ApplicationContextAware</li>
</ul>
<h3 id="三beanpostprocessor">三、BeanPostProcessor</h3>
<p>BeanPostProcessor 存在于对象<em>实例化</em>阶段。接口定义了两个方法,分别在不同的时机执行:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">BeanPostProcessor</span> <span class="o">{</span>
<span class="c1">// 前置处理</span>
<span class="nc">Object</span> <span class="nf">postProcessBeforeInitialization</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>
<span class="c1">// 后置处理</span>
<span class="nc">Object</span> <span class="nf">postProcessAfterInitialization</span><span class="o">(</span><span class="nc">Object</span> <span class="n">bean</span><span class="o">,</span> <span class="nc">String</span> <span class="n">beanName</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">BeansException</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>前文所述的 ApplicationContext 的 <em>Aware 接口</em>实际上就是利用了 BeanPostProcessor 的方式进行处理。</p>
<p>ApplicationContext 中对象实例化过程走到 BeanPostProcessor 前置处理时,容器会检测注册的 <code class="language-plaintext highlighter-rouge">ApplicationContextAwareProcessor</code> (实现了 BeanPostProcessor),调用 <code class="language-plaintext highlighter-rouge">postProcessBeforeInitialization</code>,检查并设置 Aware 相关依赖。</p>
<h3 id="四initializingbean和init-method">四、InitializingBean和init-method</h3>
<ul>
<li>
<p>InitializingBean 是容器使用的对象生命周期标识接口:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">InitializingBean</span> <span class="o">{</span>
<span class="kt">void</span> <span class="nf">afterPropertiesSet</span><span class="o">()</span> <span class="kd">throws</span> <span class="nc">Exception</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>它的作用在于,在对象实例化过程中调用 BeanPostProcessor 的前置处理之后,会检测对象是否实现了 InitializingBean,如果是,则会进一步调用 <code class="language-plaintext highlighter-rouge">afterPropertiesSet</code> 方法,调整对象状态。</p>
</li>
<li>
<p>init-method 是对象的自定义初始化操作,可以以任意命名。</p>
</li>
</ul>
<h3 id="五disposablebean和destroy-method">五、DisposableBean和destroy-method</h3>
<p>DisposableBean、destroy-method 和 InitializingBean、init-method 相对应,给对象提供了执行自定义销毁逻辑的功能。</p>
<p>在对象调用完成之后,容器会检查 <strong>singleton</strong>对象是否实现了 DisposableBean 接口或定义了 destroy-method,如果是,则为对象注册一个用于对象销毁的<em>回调</em>。</p>
Linux yum源的搭建
2017-12-06T00:00:00+00:00
http://www.blogways.net/blog/2017/12/06/yum
<h2 id="应用场景">应用场景</h2>
<p>局域网内多台服务器都需要安装应用,但是只有一台机器可以连接公网,那么需要把这台机器作为yum源使用,其他不能连接公网的机器将yum源的地址映射到这台机器,以便实现与连接公网一样的效果。</p>
<h2 id="搭建步骤">搭建步骤</h2>
<p>【在yum源服务器(192.168.137.131)上安装】</p>
<p>安装yum源库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install createrepo
</code></pre></div></div>
<p>安装http服务器并启动</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum install httpd
service httpd start
</code></pre></div></div>
<p>创建存放rpm的yum源仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createrepo /var/www/html/iot/
</code></pre></div></div>
<p>下载rpm包到仓库目录下的“downloaddir”中</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum -y install telnet --downloadonly --downloaddir=telnet
</code></pre></div></div>
<p>查看一下rpm包是否正常下载</p>
<p><img src="/images/zhaojiajun/2017-12-06-yum-1.png" alt="1" /></p>
<p>更新仓库索引,客户端的机器才能获取到,每次新增了rpm包都需要执行一下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>createrepo --update /var/www/html/iot/
</code></pre></div></div>
<p>测试一下http服务文件下载是否成功 http://192.168.137.131/iot/telnet/</p>
<p><img src="/images/zhaojiajun/2017-12-06-yum-2.png" alt="2" /></p>
<p>【在客户端机器上(192.168.137.132)配置】</p>
<p>编辑仓库地址文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /etc/yum.repos.d/
</code></pre></div></div>
<p>把原有的备份</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mv *.repo /bak
</code></pre></div></div>
<p>新增repo文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi CentOS-Base.repo
</code></pre></div></div>
<p>内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[iot]
name=iot
baseurl=http://192.168.137.131/iot/
enabled=1
gpgcheck=0
</code></pre></div></div>
<p>清空yum源的缓存,每次新增了rpm包都需要执行一下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>yum clean all
</code></pre></div></div>
<p><img src="/images/zhaojiajun/2017-12-06-yum-3.png" alt="3" /></p>
<p>可能出现的问题解决方法</p>
<p>yum解锁</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rm -rf /var/run/yum.pid
</code></pre></div></div>
<p>配置httpd</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /etc/httpd/conf/httpd.conf
</code></pre></div></div>
<p>修改如下内容</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ServerAdmin root@192.168.137.131
ServerName 192.168.137.131
</code></pre></div></div>
<p>测试一下httpd的配置脚本</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>httpd -t
</code></pre></div></div>
<p>显示</p>
<p>Syntax OK</p>
<p>You have new mail in /var/spool/mail/root</p>
Storm(5)-JStorm UI环境搭建
2017-12-06T00:00:00+00:00
http://www.blogways.net/blog/2017/12/06/storm-5
<h2 id="应用场景">应用场景</h2>
<p>jstorm的UI相对于storm提供了更为丰富的监控项。UI本身是在tomcat中运行的一个war包,进行二次开发也相对容易。Web UI 可以和Nimbus不在同一个节点,一个UI支持多个storm集群。</p>
<h2 id="示例">示例</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir ~/.jstorm
</code></pre></div></div>
<p>如果不在一台主机上则将配置文件拷贝到UI所在的主机</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/.jstorm
vi storm.yaml
</code></pre></div></div>
<p>内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ui.clusters:
- {
name: "cb",
zkRoot: "/cb",
zkServers:
[ "172.18.50.130", "172.18.50.131", "172.18.50.132", "172.18.50.133", "172.18.50.134"],
zkPort: 2181,
}
- {
name: "f2m",
zkRoot: "/f2m",
zkServers:
[ "172.30.126.219", "172.30.126.218", "172.30.126.217", "172.30.126.216", "172.30.126.215"],
zkPort: 2181,
}
</code></pre></div></div>
<p>进入tomcat,配置启动war包</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd apache-tomcat-7.0.37
cd webapps
cp $JSTORM_HOME/jstorm-ui-0.9.6.3.war ./
mv ROOT ROOT.old
ln -s jstorm-ui-0.9.6.3 ROOT
</code></pre></div></div>
<p><em>另外不是 ln -s jstorm-ui-0.9.6.3.war ROOT 这个要小心</em></p>
<p>启动tomcat上UI服务</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ../bin
./startup.sh
</code></pre></div></div>
<p>启动对应Jstorm集群上的拓扑并查看结果</p>
<h2 id="结果">结果</h2>
<p><img src="/images/zhaojiajun/2017-12-06-storm-5img1.png" alt="1" /></p>
<p><img src="/images/zhaojiajun/2017-12-06-storm-5img2.png" alt="2" /></p>
Kafka集群搭建
2017-12-06T00:00:00+00:00
http://www.blogways.net/blog/2017/12/06/kafka-1
<h2 id="应用场景">应用场景</h2>
<p>首先一个zookeeper集群,kafka集群中机器之间的通信是通过zk来实现。</p>
<h2 id="示例">示例</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /usr/local/sbin/
</code></pre></div></div>
<p>下载kafka</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>wget http://apache.fayea.com/kafka/0.8.2.1/kafka_2.10-0.8.2.1.tgz
</code></pre></div></div>
<p>解压</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar -zxf kafka_2.10-0.8.2.1.tgz
</code></pre></div></div>
<p>修改配置文件/billing/tools/kafka-2.10-0.10.2.0/config/server.properties</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>broker.id=1
listeners=PLAINTEXT://:9092
log.dirs=/billing/tools/kafka-2.10-0.10.2.0/logs
zookeeper.connect=host-103:2181,host-104:2181,host-105:2181
</code></pre></div></div>
<p><em>说明集群中BROKER.ID的值不能相同</em></p>
<p>启动kafka</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-server-start.sh config/server.properties &
</code></pre></div></div>
<p>创建topic(名为:mytopic,1个备份,4个分区)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-topics.sh --create --zookeeper host1:2181 --replication-factor 1 --partitions 4 --topic mytopic
</code></pre></div></div>
<p>查看结果</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-topics.sh --list --zookeeper host1:2181
</code></pre></div></div>
<p><img src="/images/zhaojiajun/2017-12-06-kafka-1img1.png" alt="1" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-topics.sh --describe --zookeeper host1:2181
</code></pre></div></div>
<p>删除</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-topics.sh --delete --zookeeper host1:2181 --topic mytopic
</code></pre></div></div>
<p>Zookeeper操作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /usr/local/sbin/zookeeper-3.4.6/bin
./zkCli.sh -server 192.168. 137.131:2181
ls /brokers/topics
rmr /brokers/topics/mytopic
</code></pre></div></div>
<p>启动生产者和消费者,在生产者控制台输入内容,在消费者控制台可显示,验证kafka集群搭建是否正确</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./bin/kafka-console-producer.sh --broker-list 192.168.194.131:9091,192.168.194.131:9092 --topic mytopic
./bin/kafka-console-consumer.sh --zookeeper 192.168.194.131:2181 --topic mytopic --from-beginning
</code></pre></div></div>
jQuery表单验证插件Validate
2017-12-02T00:00:00+00:00
http://www.blogways.net/blog/2017/12/02/validate
<h2 id="一问题场景">一、问题场景</h2>
<p>现在做的项目说起来应该算我的处女项目。首次处理复杂表单,参数封装已经让我有点措手不及,参数的合法性验证更是让人头疼。</p>
<p>之前简单表单参数校验都是徒手写js,简单表单验证尚可,复杂表单的验证可能是要另辟蹊径了。</p>
<p>网上一搜,表单验证插件琳琅满目,要不怎么说咱们现在都是站在巨人的肩膀上开发。</p>
<p>今天要介绍的是jQuery的validate插件。Validate给我的使用感受是:使用简单粗暴,规则内置丰富,随心所欲易于扩展。</p>
<h2 id="二-快速开始">二、 快速开始</h2>
<p><strong>扯了那么多,动手试试吧!</strong></p>
<h3 id="21-引入jquery和validate">2.1 引入jQuery和Validate</h3>
<ul>
<li>本项目中jQuery版本为v1.12.4,validate版本为v1.17.0</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript" src="${resourceRootPath}/static/frame/jquery/dist/jquery.min.js"></script>
<script stype="text/javascript" src="${resourceRootPath}/static/frame/jquery-validation/dist/jquery.validate.js"></script>
</code></pre></div></div>
<h3 id="22-使用前热身">2.2 使用前热身</h3>
<ul>
<li>表单验证核心无非以下两点:</li>
</ul>
<h5 id="1-规则rule">1. 规则Rule</h5>
<p>validate内置默认规则:</p>
<table>
<thead>
<tr>
<th>序号</th>
<th>规则</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>required:true</td>
<td>必须输入的字段。</td>
</tr>
<tr>
<td>2</td>
<td>remote:”check.php”</td>
<td>使用 ajax 方法调用 check.php 验证输入值。</td>
</tr>
<tr>
<td>3</td>
<td>email:true</td>
<td>必须输入正确格式的电子邮件。</td>
</tr>
<tr>
<td>4</td>
<td>url:true</td>
<td>必须输入正确格式的网址。</td>
</tr>
<tr>
<td>5</td>
<td>date:true</td>
<td>必须输入正确格式的日期。日期校验 ie6 出错,慎用。</td>
</tr>
<tr>
<td>6</td>
<td>dateISO:true</td>
<td>必须输入正确格式的日期(ISO),例如:2009-06-23,1998/01/22。只验证格式,不验证有效性。</td>
</tr>
<tr>
<td>7</td>
<td>number:true</td>
<td>必须输入合法的数字(负数,小数)。</td>
</tr>
<tr>
<td>8</td>
<td>digits:true</td>
<td>必须输入整数。</td>
</tr>
<tr>
<td>9</td>
<td>creditcard:</td>
<td>必须输入合法的信用卡号。</td>
</tr>
<tr>
<td>10</td>
<td>equalTo:”#field”</td>
<td>输入值必须和 #field 相同。</td>
</tr>
<tr>
<td>11</td>
<td>accept:</td>
<td>输入拥有合法后缀名的字符串(上传文件的后缀)。</td>
</tr>
<tr>
<td>12</td>
<td>maxlength:5</td>
<td>输入长度最多是 5 的字符串(汉字算一个字符)。</td>
</tr>
<tr>
<td>13</td>
<td>minlength:10</td>
<td>输入长度最小是 10 的字符串(汉字算一个字符)。</td>
</tr>
<tr>
<td>14</td>
<td>rangelength:[5,10]</td>
<td>输入长度必须介于 5 和 10 之间的字符串(汉字算一个字符)。</td>
</tr>
<tr>
<td>15</td>
<td>range:[5,10]</td>
<td>输入值必须介于 5 和 10 之间。</td>
</tr>
<tr>
<td>16</td>
<td>max:5</td>
<td>输入值不能大于 5。</td>
</tr>
<tr>
<td>17</td>
<td>min:10</td>
<td>输入值不能小于 10。</td>
</tr>
</tbody>
</table>
<h5 id="2-提示信息message">2. 提示信息message</h5>
<p>validate内置默认提示消息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>messages: {
required: "This field is required.",
remote: "Please fix this field.",
email: "Please enter a valid email address.",
url: "Please enter a valid URL.",
date: "Please enter a valid date.",
dateISO: "Please enter a valid date ( ISO ).",
number: "Please enter a valid number.",
digits: "Please enter only digits.",
creditcard: "Please enter a valid credit card number.",
equalTo: "Please enter the same value again.",
maxlength: $.validator.format( "Please enter no more than {0} characters." ),
minlength: $.validator.format( "Please enter at least {0} characters." ),
rangelength: $.validator.format( "Please enter a value between {0} and {1} characters long." ),
range: $.validator.format( "Please enter a value between {0} and {1}." ),
max: $.validator.format( "Please enter a value less than or equal to {0}." ),
min: $.validator.format( "Please enter a value greater than or equal to {0}." )
}
</code></pre></div></div>
<p>validate默认提示位置是为表单元素后面:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>errorPlacement: function(error, element) {
error.appendTo(element.parent());
}
</code></pre></div></div>
<ul>
<li>其他一些参数:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ignore: ":hidden",//忽略的元素
doNotHideMessage: true, // this option enables to show the error/success messages on tab switch.
//错误消息容器
errorElement: 'span', // default input error message container
//错误消息class
errorClass: 'help-block help-block-error', // default input error message class
focusInvalid: false, // do not focus the last invalid input
//错误消息高亮显示
highlight: function highlightCbFunc(element) { // hightlight error inputs
$(element)
.closest('.form-group')
.removeClass('has-success')
.addClass('has-error');
// set error class to the control group
},
//输入合法内容后取消高亮显示
unhighlight: function unhighlightCbFunc(element) { // revert the change done by hightlight
$(element)
.closest('.form-group')
.removeClass('has-error'); // set error class to the control group
},
//失去焦点事件
onfocusout: function(element) {
$(element).valid();
},
//按键弹起事件
onkeyup: function(element) {
var currentValue = $(element).val();
if (!$(element).valid()){
$(element).val(currentValue.substr(0,currentValue.length-1));
}
}
</code></pre></div></div>
<h3 id="23-简单使用">2.3 简单使用</h3>
<p>了解上面三要素和一些参数后我们就可以开始简单使用了(其实甚至可以只知道一些默认的规则就可以拿来使用了)。</p>
<p>项目中提交表单多为ajax提交,用法如下:</p>
<ul>
<li>将校验规则写到控件中</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><form id="baseInfoForm">
<div class="form-group">
<label class="control-label col-md-5">账户名(必填)</label>
<div class="col-md-7">
<select class="form-control input-sm select-remote" data-selectconf="account" name="account" required>
<option value=""></option>
</select>
</div>
</div>
<div class="form-group">
<label class="control-label col-md-5">计划名称(必填)</label>
<div class="col-md-7">
<input type="text" class="form-control-static" name="rateplanName" required />
</div>
</div>
<div class="form-group">
<label class="control-label col-md-5">E-mail(必填)</label>
<div class="col-md-7">
<input type="email" class="form-control-static" name="email" required />
</div>
</div>
<div class="form-group">
<label class="control-label col-md-5">账户费用:</label>
<div class="col-mc-7">
<input id="account_charge" type="number" name="ACCOUNT_CHARGE" min="0" step="0.01"/>
</div>
</div>
</form>
</code></pre></div></div>
<ul>
<li>提交前验证</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('#baseInfoForm').valid();//通过验证返回true,不通过返回false
</code></pre></div></div>
<p><strong>是不是简单粗暴?</strong></p>
<h3 id="24-扩展">2.4 扩展</h3>
<blockquote>
<p>业务中某个表单元素只允许填IP地址或者域名,默认规则里面没有怎么办?没关系,validate具有非常良好的扩展性。</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><form id="exampleForm">
<div class="form-group">
<label class="control-label col-md-5">IP地址范围(必填)</label>
<div class="col-md-7">
<input id="account_charge" type="text" name="ipRange" required />
</div>
</div>
</form>
</code></pre></div></div>
<ul>
<li>首先添加方法(method)
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> jQuery.validator.addMethod("ipValidate", function (value, element) {
var ipReg = /((?:(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d)))\.){3}(?:25[0-5]|2[0-4]\d|((1\d{2})|([1-9]?\d))))/;
var domainReg = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/;
return this.optional(element) || (ipReg.test(value) || (domainReg.test(value));
}, '请输入ip或者域名');
</code></pre></div> </div>
</li>
<li>添加规则(rule)
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rules : {
ipRange : {//ip地址范围
ipValidate:'请输入ip或者域名'
},
</code></pre></div> </div>
</li>
<li>添加消息
其实我们在上面定义方法和规则都已经定义了提示消息,此步骤可选。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> messages: {
ipRange:"请输入IP地址或者域名"
}
</code></pre></div> </div>
</li>
<li>将上述扩展内容写入 jquery.validate.js或者自定一个js文件validate.extend.js,建议后者
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //变量myValidateOpts包含了自定义的一些属性
var validateOpts = $.extend(true, {}, myValidateOpts, {});
//初始化
$('#exampleForm').validate(validateOpts);
//验证
$('#exampleForm').valid();
</code></pre></div> </div>
</li>
</ul>
<h2 id="三参考文献">三、参考文献</h2>
<ul>
<li>http://www.runoob.com/jquery/jquery-plugin-validate.html</li>
</ul>
jmockit简介
2017-11-28T00:00:00+00:00
http://www.blogways.net/blog/2017/11/28/jmockit
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#whatsmock">什么是mock</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#usecase">自动化规则用例介绍</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>自动化规则需要依赖于mdb(内存数据加载),但是在开发时,mdb功能并未完全实现。这样在进行单元测试时,由于无法构造mdb对象,
而出现单元测试无法进行的情况。所以决定采用mock测试。下面是自动化规则采用的jmockit简介。</p>
<h2 id="一什么是mock-">一、什么是mock <a href="whatsmock"></a></h2>
<p>mock测试就是在测试过程中,对于某些不容易构造或者不容易获取的对象,用一个虚拟的对象来创建以便测试的测试方法。
在单元测试时,对于一个类,我们仅仅希望测试这个类的功能,而不是同时测试这个类的依赖,这时我们也需要模拟一个对象。</p>
<h3 id="11-jmockit简介">1.1 jmockit简介</h3>
<p>JMockit 是用以帮助开发人员编写测试程序的一组工具和API,该项目完全基于 Java 5 SE 的 java.lang.instrument 包开发,内部使用
ASM 库来修改Java的Bytecode。相比于其他的一些mock测试框架,他的功能更强大,具体可见stackoverflow上的一个问题的回答。<a href="https://stackoverflow.com/questions/4105592/comparison-between-mockito-vs-jmockit-why-is-mockito-voted-better-than-jmockit">comparison-between-mockito-vs-jmockit</a></p>
<p>JMockit的测试方式可以通过下面2个途径实现</p>
<ul>
<li>根据用例的测试路径,测试代码内部逻辑
对于这种情景,可以使用jmockit的基于行为的mock方式。在这种方式中,目的是测试单元测试及其依赖代码的调用过程,验证代码逻辑是否满足
测试路径。 由于被依赖代码可能在自己单测中已测试过,或者难以测试,就需要把这些被依赖代码的逻辑用预定期待的行为替换掉,也就是mock掉,
从而把待测是代码隔离开,这也是单元测试的初衷。 这种方式和白盒测试接近。</li>
<li>根据测试用例的输入输出数据,测试代码是否功能运行正常
对于这种情景,可以使用jmockit基于状态的mock方式。目的是从被测代码的使用角度出发,结合数据的输入输出来检验程序运行的这个正确性。
使用这个方式,需要把被依赖的代码mock掉,实际上相当于改变了被依赖的代码的逻辑。通常在集成测试中,如果有难以调用的外部接口,就通过
这个方式mock掉,模拟外部接口。 这种方式有点像黑盒测试。</li>
</ul>
<h3 id="12-jmockit使用步骤">1.2 jmockit使用步骤</h3>
<p>当你使用Jmockit,你就有了固定的套路来进行mock测试,总结分为如下步骤:</p>
<ol>
<li>record
record,录制,使用new Expectations{}代码块进行录制,在这个代码块里,你可以对期望mock的对象的行为进行定义</li>
<li>replay
replay,播放,对录制的mock对象的行为在测试代码中进行调用。</li>
<li>verify
verify,验证,使用new Verifications{}代码块中进行校验。在这个代码块里,你可以对mock的对象定义校验。</li>
</ol>
<h3 id="13-一个例子">1.3 一个例子</h3>
<h4 id="131-考虑到一个业务服务类提供了包括如下步骤的业务操作">1.3.1 考虑到一个业务服务类提供了包括如下步骤的业务操作:</h4>
<ol>
<li>找到需要被操作所需要的持久化的实体</li>
<li>持久化一个新的实体状态</li>
<li>发送一个提醒消息给一个有兴趣的部门</li>
</ol>
<p>第一二步需要使用应用数据库,其通过使用简单的 API 来使用持久化系统。 第三步骤可以通过使用一个第三方的API来发送邮件,例子中是使用
Apache的 通用邮件库。
因此,这个服务类包含了两个独立的依赖,一个是持久化,另外一个是邮件。为了进行业务操作的单元测试来验证恰当的和这些依赖进行合作,我们
使用了模拟API。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tutorial.domain;
import java.math.*;
import java.util.*;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;
public final class MyBusinessService
{
private final EntityX data;
public MyBusinessService(EntityX data) { this.data = data; }
public void doBusinessOperationXyz() throws EmailException {
List<EntityX> items =
(1) find("select item from EntityX item where item.someProperty = ?1", data.getSomeProperty());
// Compute or obtain from another service a total value for the new persistent entity:
BigDecimal total = ...
data.setTotal(total);
(2) persist(data);
sendNotificationEmail(items);
}
private void sendNotificationEmail(List<EntityX> items) throws EmailException {
Email email = new SimpleEmail();
email.setSubject("Notification about processing of ...");
(3) email.addTo(data.getCustomerEmail());
// Other e-mail parameters, such as the host name of the mail server, have defaults defined
// through external configuration.
String message = buildNotificationMessage(items);
email.setMsg(message);
(4) email.send();
}
private String buildNotificationMessage(List<EntityX> items) { ... }
}
</code></pre></div></div>
<p>数据库类包括了一些静态方法和一个私有的构造函数;查找和持久化的方法是必须的,因此我们这里就没有罗列出来 (假设他们是通过 ORM API来实
现的, 比如 JPA)。
因此,我们改如何不需要任何修改已经存在应用代码的情况下进行 “doBusinessOperationXyz” 方法的单元测试呢?JMockit 提供了两种不同的模拟
APIs, 每一种都能满足你的需求。我们将看到每种的写法的不同例子。 在每一个情况下, 一个 JUnit测试例子将会验证单元测试对外部依赖所兴趣的
调用。这些调用都是通过点 (1)-(4) 如上进行标注。</p>
<h4 id="132-使用期望-api">1.3.2 使用期望 API</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tutorial.domain;
import org.apache.commons.mail.*;
import static tutorial.persistence.Database.*;
import org.junit.*;
import org.junit.rules.*;
import static org.junit.Assert.*;
import mockit.*;
public final class MyBusinessServiceTest
{
@Rule public final ExpectedException thrown = ExpectedException.none();
@Tested final EntityX data = new EntityX(1, "abc", "someone@somewhere.com");
@Tested(fullyInitialized = true) MyBusinessService businessService;
@Mocked SimpleEmail anyEmail;
@Test
public void doBusinessOperationXyz() throws Exception {
EntityX existingItem = new EntityX(1, "AX5", "abc@xpta.net");
(1) persist(existingItem);
businessService.doBusinessOperationXyz();
(2) assertNotEquals(0, data.getId()); // implies "data" was persisted
(4) new Verifications() ;
}
@Test
public void doBusinessOperationXyzWithInvalidEmailAddress() throws Exception {
String email = "invalid address";
data.setCustomerEmail(email);
(3) new Expectations() ;
thrown.expect(EmailException.class);
businessService.doBusinessOperationXyz();
}
}
</code></pre></div></div>
<p>在行为导向的模拟 API像 JMockit 期望那样, 每一个测试都可以被分为三个连续的步骤: 录制,重放,验证。 在期望的录制块中定义录制, 在期望
验证块中进行验证; 重放指的是在测试的内部代码中进行调用。注意,只有模拟方法的调用次数; 隶属于类或者实例中的方法 (或 构造方法)和相关的
模拟属性或者模拟参数将不被模拟,因此不能被录制,更不能进行验证或者是重放了。+</p>
<p>正如上面的实例测试,录制和验证期望可以通过调用所需要方法在一个录制或者是验证的块中来实现 (包括构造方法,及时没有在这里显示)。 这些方法
的参数匹配可以通过 API的属性例如 “any” 和 “anyString”, 或者是通过 API 方法 比如 “withNotNull()”。 在重放中所匹配的调用返回值
(或 抛出的异常)可以在录制阶段通过result属性进行定义。调用次数的约束可以被定义,不仅仅可以在录制阶段也可以在验证阶段, 通过 API 属性赋
值比如 “times = 1”</p>
<h4 id="132-使用jmockit进行测试">1.3.2 使用Jmockit进行测试</h4>
<ol>
<li>在pom中增加依赖
```</li>
</ol>
<properties>
<jmockit.version>desired version</jmockit.version>
</properties>
<dependencies>
<dependency>
<groupId>org.jmockit</groupId>
<artifactId>jmockit</artifactId>
<version>${jmockit.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2.启动JUNIT测试任务
## 二、自动化规则用例介绍<a href="usecase"></a>
自动化规则的数据主要从redis中读取,但是在开发时,mdb还未完全实现好,这时使用mock测试很好的体现了优势
### 2.1 模拟mdb对象获取
</code></pre></div></div>
<p>@RunWith(JMockit.class)
public abstract class BaseTest {</p>
<p>@Mocked
protected MMDevice mmDevice;
@Mocked
protected MMAutorule mmAutorule;
@Mocked
protected BillManager billManager;</p>
<p>@SuppressWarnings(“rawtypes”)
@Before
public void init() {
new Expectations(RuleUtils.class) {
{
RuleUtils.getAllRules(anyLong);
List<AutoRule> autoRules = mockAutoRules();
result = autoRules;
}
};</AutoRule></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>new Expectations(RuleUtils.class) {
{
RuleUtils.getRuleOper(anyLong);
result = new Delegate() {
public List<AutoRuleOper> getRuleOper(long ruleId) {
return mockRuleOpers(ruleId);
}
};
}
}; } } ```
</code></pre></div></div>
<p>这里模拟了从内存中读取自动化规则和自动化规则对应的操作。这样就可以验证代码功能的正确性,而不需要一大段代码,来创建
对象,对逻辑的校验形成干扰。</p>
<h2 id="三参考文献">三、参考文献<a name="reference"></a></h2>
<ul>
<li>http://jmockit.org/</li>
<li>https://stackoverflow.com/questions/4105592/comparison-between-mockito-vs-jmockit-why-is-mockito-voted-better-than-jmockit</li>
</ul>
cordova快速上手
2017-11-25T00:00:00+00:00
http://www.blogways.net/blog/2017/11/25/cordova
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#getstarted">快速开始</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#problem">问题与解决</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>近来太忙,事情多且杂。原本在本机上已经搭建了cordova环境,现在想要再使用一下,却好像失忆似完全记不起来,不知道都有哪些步骤了。这再次说明了写博文的重要性。</p>
<p>没办法只能再重新学习,不过这次一定要把学习所得记录下来,利人利己!</p>
<p>下文记录Cordova的简易使用。</p>
<h2 id="一快速开始-">一、快速开始 <a href="getstarted"></a></h2>
<h3 id="11-安装-cordova-命令行工具">1.1 安装 Cordova 命令行工具</h3>
<p>Cordova命令行工具运行在<code class="language-plaintext highlighter-rouge">Node.js</code>环境下。</p>
<p>先下载<code class="language-plaintext highlighter-rouge">Node.js</code>,安装后就可以在命令行执行<code class="language-plaintext highlighter-rouge">node</code>与<code class="language-plaintext highlighter-rouge">npm</code>命令了。使用<code class="language-plaintext highlighter-rouge">npm</code>就可以下载安装<code class="language-plaintext highlighter-rouge">cordova</code>模块了。</p>
<ul>
<li>
<p>在OS X或者Linux环境下可以执行以下命令,安装<code class="language-plaintext highlighter-rouge">cordova</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ sudo npm i -g cordova
</code></pre></div> </div>
</li>
<li>
<p>在Windows环境下安装:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>C:\>npm install -g cordova
</code></pre></div> </div>
</li>
</ul>
<h3 id="12-创建一个项目">1.2 创建一个项目</h3>
<p>可以使用命令行工具创建一个空项目。进入你想创建项目的目录下,输入一下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova create <PATH> [ID [NAME [CONFIG]]] [options]
</code></pre></div></div>
<p>(你可以通过 <code class="language-plaintext highlighter-rouge">cordova help create</code> 来了解这个命令)</p>
<p>这个命令会根据模板创建cordova应用所需的目录。默认情况下,项目中<code class="language-plaintext highlighter-rouge">www/index.html</code>文件是这个WebApp的Home页面。</p>
<p>举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova create hello com.example.hello HelloWorld
</code></pre></div></div>
<p>创建出来的目录结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hello/
|-- config.xml
|-- package.json
|-- hooks/
|-- res/
| |-- icon/
| |-- screen/
|-- www/
| |-- index.html
| |-- css/
| |-- img/
| |-- js/
|-- platforms/
|-- plugins/
</code></pre></div></div>
<p>当然了,你也可以通过特定的模板创建项目,特定的模板可以是NPM包、Git仓库或者本地目录,相关命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova create hello com.example.hello HelloWorld --template <npm-package-name>
cordova create hello com.example.hello HelloWorld --template <git-remote-url>
cordova create hello com.example.hello HelloWorld --template <path-to-template>
</code></pre></div></div>
<p>你可以尝试在<a href="https://www.npmjs.com/">www.npmjs.com</a>上,通过关键字<code class="language-plaintext highlighter-rouge">cordova:template</code>来搜索npmjs仓库已有的模板。</p>
<p>如果想在本地创建一个模板,可以参考<a href="https://github.com/carynbear/cordova-template">这里</a></p>
<h3 id="13-添加平台">1.3 添加平台</h3>
<p>现在App还无法运行在手机上,你需要给你的App添加将要支持的平台。</p>
<p>Cordova支持的平台有很多:</p>
<ul>
<li>android</li>
<li>blackberry10 ~3.8.0 (deprecated)</li>
<li>browser</li>
<li>ios</li>
<li>osx</li>
<li>ubuntu ~4.3.4 (deprecated)</li>
<li>webos</li>
<li>windows</li>
<li>www</li>
</ul>
<p>可以使用如下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova {platform | platforms} [
add <platform-spec> [...] {--save | link=<path> | --fetch } |
{remove | rm} platform [...] {--save | --fetch}|
{list | ls} |
check |
save |
update ]
</code></pre></div></div>
<p>这个命令需要在项目目录下执行,举例:</p>
<ul>
<li>进入项目目录</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd hello
</code></pre></div></div>
<ul>
<li>添加平台</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova platform add android ios
</code></pre></div></div>
<ul>
<li>删除平台</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova platform remove ios
</code></pre></div></div>
<p>你可以通过命令查看,目前可以支持及已经支持哪些手机平台</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova platform ls
</code></pre></div></div>
<h3 id="14-编译前准备">1.4 编译前准备</h3>
<p>为了能编译运行程序,你需要安装各个平台所必须的SDK。</p>
<p>可以通过命令查看,是否具备这些环境:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova requirements
</code></pre></div></div>
<p>下面依次介绍 Android 、 iOS 、 Windows 平台的所需环境.</p>
<h4 id="141-android平台所需环境">1.4.1 Android平台所需环境</h4>
<ul>
<li>
<p>JDK</p>
<p>需要安装<a href="http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html">JDK8</a>或更新版。</p>
</li>
<li>
<p>Android SDK</p>
<p>需要安装<a href="https://developer.android.com/studio/index.html">Android Studio</a>.</p>
</li>
<li>
<p>Android SDK 包</p>
<p>安装完Android Studio后,还需要安装SDK包,推荐安装最高版本。Cordova最新Android包最高只能支持到Android API Level 25,目前不支持更高级别的API了。具体支持详情见下表:</p>
<table>
<thead>
<tr>
<th>cordova-android版本</th>
<th>所支持的Android API-Levels</th>
<th>对应的 Android 版本</th>
</tr>
</thead>
<tbody>
<tr>
<td>6.X.X</td>
<td>16 - 25</td>
<td>4.1 - 7.1.1</td>
</tr>
<tr>
<td>5.X.X</td>
<td>14 - 23</td>
<td>4.0 - 6.0.1</td>
</tr>
<tr>
<td>4.1.X</td>
<td>14 - 22</td>
<td>4.0 - 5.1</td>
</tr>
<tr>
<td>4.0.X</td>
<td>10 - 22</td>
<td>2.3.3 - 5.1</td>
</tr>
<tr>
<td>3.7.X</td>
<td>10 - 21</td>
<td>2.3.3 - 5.0.2</td>
</tr>
</tbody>
</table>
</li>
<li>
<p>设置环境变量</p>
<ol>
<li>设置环境变量<code class="language-plaintext highlighter-rouge">JAVA_HOME</code>,指向JDK安装目录;</li>
<li>设置环境变量<code class="language-plaintext highlighter-rouge">ANDROID_HOME</code>,指向Android SDK安装目录;</li>
<li>推荐将Android SDK安装目录下的<code class="language-plaintext highlighter-rouge">tools</code>、<code class="language-plaintext highlighter-rouge">tools/bin</code>和<code class="language-plaintext highlighter-rouge">platform-tools</code>目录添加到环境变量<code class="language-plaintext highlighter-rouge">PATH</code>中。</li>
</ol>
<p>举例:</p>
<ul>
<li>
<p>在OS X(或者 Linux)下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home
export ANDROID_HOME=/Users/yourname/Library/Android/sdk
export PATH=${PATH}:${ANDROID_HOME}/platform-tools:${ANDROID_HOME}/tools:${ANDROID_HOME}/tools/bin
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h4 id="142-ios平台所需环境">1.4.2 iOS平台所需环境</h4>
<p>编译iOS程序的环境必须是:</p>
<ol>
<li>使用 Intel芯片的电脑</li>
<li>OS X操作系统,且版本 >= 10.10.4</li>
<li>Xcode版本 >= 7.0</li>
<li>iOS 9 SDK</li>
</ol>
<ul>
<li>Xcode</li>
</ul>
<p>两种下载Xcode的方式:一、从App Store下载安装;二、从Apple 开发者<a href="https://developer.apple.com/downloads/index.action">下载页面</a>下载(需要注册成为Apple开发者)。</p>
<p>从App Store下载的是最新版Xcode,其对操作系统的版本是有要求的。如果操作系统的版本比较低,可以从Apple开发者下载页面,找到<a href="https://developer.apple.com/download/more/">低版本的Xcode</a>安装。我本机操作系统很久没升级了(版本:<code class="language-plaintext highlighter-rouge">10.11.6</code>),选择安装了Xcode的<code class="language-plaintext highlighter-rouge">8.2</code>版本。</p>
<p>Xcode下载安装后,还需要安装Xcode的命令行工具。也可以通过下述命令安装:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>xcode-select --install
</code></pre></div></div>
<ul>
<li>部署工具</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">ios-deploy</code>工具可以通过命令行在iOS设备上启动app。通过以下命令安装:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install -g ios-deploy
</code></pre></div></div>
<h3 id="15-编译">1.5 编译</h3>
<p>编译所有平台:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova build
</code></pre></div></div>
<p>编译某个特定平台,比如Android:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova build android
</code></pre></div></div>
<p>我第一次编译时报错,见下文“问题与解决”章节。</p>
<h3 id="16-测试app">1.6 测试App</h3>
<p>下面依次介绍 Android 、 iOS 、 Windows 平台的所需环境.</p>
<h4 id="161-android平台的测试">1.6.1 Android平台的测试</h4>
<ul>
<li>
<p><strong>创建及管理Android虚拟设备(AVD)</strong></p>
<p>Android Studio提供一个AVD管理界面,可以进行AVD的创建与管理。可以通过工具栏上对应的按钮打开这个界面,也可以通过菜单(<code class="language-plaintext highlighter-rouge">Tools</code> > <code class="language-plaintext highlighter-rouge">Android</code> > <code class="language-plaintext highlighter-rouge">AVD Manager</code>)打开。界面操作简单,不再赘述。</p>
</li>
<li>
<p><strong>在模拟器上测试</strong></p>
<p>配置好AVD后,可通过以下命令发布Cordova程序至模拟器运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova run android --emulator
</code></pre></div> </div>
<p>我在这里遇到问题,解决经过见下文“问题与解决”章节</p>
</li>
<li>
<p><strong>在手机上测试</strong></p>
<p>手机连上电脑,可以通过以下命令发布cordova程序至手机运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova run android --device
</code></pre></div> </div>
<p>我在这里遇到问题,解决经过见下文“问题与解决”章节</p>
</li>
</ul>
<h4 id="162-ios平台的测试">1.6.2 iOS平台的测试</h4>
<ul>
<li><strong>在模拟器上测试</strong></li>
</ul>
<p>安装好Xcode,可通过以下命令发布Cordova程序至模拟器运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cordova run ios --emulator
</code></pre></div></div>
<p>也可以使用Xcode,发布到模拟器上调试:</p>
<ol>
<li>使用Xcode打开工程文件(<code class="language-plaintext highlighter-rouge">platforms/ios/HelloWorld.xcworkspace</code>),也可以使用下面命令打开</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> open ./platforms/ios/HelloWorld.xcworkspace/
</code></pre></div></div>
<ol>
<li>按下图示①②步骤确认,然后点击③运行。程序就在模拟器中启动了。</li>
</ol>
<p><img src="/images/post/select_xcode_scheme.png" alt="图示" /></p>
<ul>
<li><strong>在手机上测试</strong></li>
</ul>
<p>手机连上电脑,可以通过以下命令发布cordova程序至手机运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cordova run ios --device
</code></pre></div></div>
<p>需要确保手机版本能被Xcode版本所支持。</p>
<h2 id="二问题与解决">二、问题与解决<a href="problem"></a></h2>
<p>不同环境不同版本会遇到不同的情况,下面仅记录我所遇到的若干问题:</p>
<h3 id="21-编译报错与解决过程">2.1 编译报错与解决过程</h3>
<p>执行<code class="language-plaintext highlighter-rouge">cordova build android</code>报错如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cordova build
ANDROID_HOME=/Users/yourname/Library/Android/sdk
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_25.jdk/Contents/Home
Error: spawn EACCES
</code></pre></div></div>
<p>打开日志选型,重新编译:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cordova build android -verbose
...
...
Running command: "/Applications/Android Studio.app/Contents/gradle/gradle-4.1/bin/gradle" -p /Users/tangzhi/webroot/running-man/dist/android/hello/platforms/android wrapper -b /Users/tangzhi/webroot/running-man/dist/android/hello/platforms/android/wrapper.gradle
Error: spawn EACCES
</code></pre></div></div>
<p>通过编译日志,发现是”/Applications/Android Studio.app/Contents/gradle/gradle-4.1/bin/gradle”没有权限。那就好办了,赋个执行权限就ok了。</p>
<p>赋权命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>chmod +x /Applications/Android\ Studio.app/Contents/gradle/gradle-4.1/bin/gradle
</code></pre></div></div>
<h3 id="22-在模拟器上测试出错与解决a">2.2 在模拟器上测试出错与解决A</h3>
<ul>
<li>
<p>执行以下命令出错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova run android --emulator
</code></pre></div> </div>
</li>
<li>
<p>出错提示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Failed to execute shell command "getprop,dev.bootcomplete"" on device: Error: adb: Command failed with exit code 1 Error output:
error: device unauthorized.
This adb server's $ADB_VENDOR_KEYS is not set
Try 'adb kill-server' if that seems wrong.
Otherwise check for a confirmation dialog on your device.
</code></pre></div> </div>
</li>
<li>
<p>解决方法:网上说了四种可能的解决方案,我使用了第一个方案就解决了,四个方案如下:</p>
<ol>
<li>
<p>杀了并重启adb服务,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> adb kill-server
adb forward --remove-all
adb start-server
</code></pre></div> </div>
</li>
<li>删除并重建AVD;</li>
<li>删除Android平台并重建及编译</li>
<li>在<code class="language-plaintext highlighter-rouge">config.xml</code>文件中Android平台下添加一条配置:<code class="language-plaintext highlighter-rouge"><preference name="loadUrlTimeoutValue" value="700000" /></code>
1.</li>
</ol>
</li>
</ul>
<h3 id="23-在ios模拟器上测试出错与解决b">2.3 在iOS模拟器上测试出错与解决B</h3>
<ul>
<li>
<p>在xcode里编译报错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The app icon set named "AppIcon" did not have any applicable content.
</code></pre></div> </div>
</li>
<li>
<p>解决方法:iOS严格检查图标的尺寸。在<code class="language-plaintext highlighter-rouge">config.xml</code>文件中按需配置图标,问题解决。图标配置,参考如下:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="ni">&lt;</span>platform name="ios">
<span class="c"><!-- iOS 8.0+ --></span>
<span class="c"><!-- iPhone 6 Plus --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-60@3x.png"</span> <span class="na">width=</span><span class="s">"180"</span> <span class="na">height=</span><span class="s">"180"</span> <span class="nt">/></span>
<span class="c"><!-- iOS 7.0+ --></span>
<span class="c"><!-- iPhone / iPod Touch --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-60.png"</span> <span class="na">width=</span><span class="s">"60"</span> <span class="na">height=</span><span class="s">"60"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-60@2x.png"</span> <span class="na">width=</span><span class="s">"120"</span> <span class="na">height=</span><span class="s">"120"</span> <span class="nt">/></span>
<span class="c"><!-- iPad --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-76.png"</span> <span class="na">width=</span><span class="s">"76"</span> <span class="na">height=</span><span class="s">"76"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-76@2x.png"</span> <span class="na">width=</span><span class="s">"152"</span> <span class="na">height=</span><span class="s">"152"</span> <span class="nt">/></span>
<span class="c"><!-- Spotlight Icon --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-40.png"</span> <span class="na">width=</span><span class="s">"40"</span> <span class="na">height=</span><span class="s">"40"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-40@2x.png"</span> <span class="na">width=</span><span class="s">"80"</span> <span class="na">height=</span><span class="s">"80"</span> <span class="nt">/></span>
<span class="c"><!-- iOS 6.1 --></span>
<span class="c"><!-- iPhone / iPod Touch --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon.png"</span> <span class="na">width=</span><span class="s">"57"</span> <span class="na">height=</span><span class="s">"57"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon@2x.png"</span> <span class="na">width=</span><span class="s">"114"</span> <span class="na">height=</span><span class="s">"114"</span> <span class="nt">/></span>
<span class="c"><!-- iPad --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-72.png"</span> <span class="na">width=</span><span class="s">"72"</span> <span class="na">height=</span><span class="s">"72"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-72@2x.png"</span> <span class="na">width=</span><span class="s">"144"</span> <span class="na">height=</span><span class="s">"144"</span> <span class="nt">/></span>
<span class="c"><!-- iPad Pro --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-167.png"</span> <span class="na">width=</span><span class="s">"167"</span> <span class="na">height=</span><span class="s">"167"</span> <span class="nt">/></span>
<span class="c"><!-- iPhone Spotlight and Settings Icon --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-small.png"</span> <span class="na">width=</span><span class="s">"29"</span> <span class="na">height=</span><span class="s">"29"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-small@2x.png"</span> <span class="na">width=</span><span class="s">"58"</span> <span class="na">height=</span><span class="s">"58"</span> <span class="nt">/></span>
<span class="c"><!-- iPad Spotlight and Settings Icon --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-50.png"</span> <span class="na">width=</span><span class="s">"50"</span> <span class="na">height=</span><span class="s">"50"</span> <span class="nt">/></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-50@2x.png"</span> <span class="na">width=</span><span class="s">"100"</span> <span class="na">height=</span><span class="s">"100"</span> <span class="nt">/></span>
<span class="c"><!-- iPad Pro --></span>
<span class="nt"><icon</span> <span class="na">src=</span><span class="s">"res/ios/icon-83.5@2x.png"</span> <span class="na">width=</span><span class="s">"167"</span> <span class="na">height=</span><span class="s">"167"</span> <span class="nt">/></span>
<span class="nt"></platform></span>
</code></pre></div> </div>
</li>
</ul>
<h3 id="24-在手机上测试出错与解决a">2.4 在手机上测试出错与解决A</h3>
<ul>
<li>
<p>执行以下命令出错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova run android --device
</code></pre></div> </div>
</li>
<li>
<p>出错提示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Failed to execute shell command "input,keyevent,82"" on device: Error: adb: Command failed with exit code 137
</code></pre></div> </div>
</li>
<li>
<p>解决方法:在手机的开发者选项中打开“USB调试(安全设置)”选项。</p>
</li>
</ul>
<h3 id="25-在手机上测试出错与解决b">2.5 在手机上测试出错与解决B</h3>
<ul>
<li>
<p>执行以下命令出错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cordova run android --device
</code></pre></div> </div>
</li>
<li>
<p>出错提示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Error: Failed to deploy to device, no devices found.
</code></pre></div> </div>
</li>
<li>
<p>解决方法:执行以下命令后,电脑重连手机。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>adb kill-server
adb start-server
</code></pre></div> </div>
</li>
</ul>
<h2 id="三参考文献">三、参考文献<a name="reference"></a></h2>
<ul>
<li>http://cordova.apache.org</li>
<li>https://github.com/carynbear/cordova-template</li>
<li>https://stackoverflow.com/questions/tagged/cordova</li>
</ul>
使用docker搭建自动化规则测试环境
2017-11-05T00:00:00+00:00
http://www.blogways.net/blog/2017/11/05/docker
<h2 id="一问题场景">一、问题场景</h2>
<p>自动化规则测试环境,使用python2编写,依赖于redis-py-cluster,pykafka,futures,mysql-python。在本地搭建测试环境,python安装
没有出现问题,但是在安装python插件时,遇到问题较多,解决花费时间较长,且未能完全解决,导致测试环境迟迟不能运行。所以决定采用docker来
搭建测试环境。</p>
<h2 id="二搭建步骤">二、搭建步骤</h2>
<p>以下为搭建环境的步骤</p>
<ol>
<li>安装docker环境不提</li>
<li>创建基本python环境,并安装插件
<ul>
<li>创建Dockerfile文件</li>
<li>在Dockerfile中输入如下代码
```
FROM centos:7
MAINTAINER Augustus “qianwx@asiainfo.com”</li>
</ul>
</li>
</ol>
<p>RUN yum -y update
RUN yum -y install epel-release
RUN yum -y install python-pip
RUN yum -y install mysql-devel
RUN yum -y install python-devel
RUN yum -y install MySQL-python
RUN pip install –upgrade pip
RUN pip install pykafka
RUN pip install mysql
RUN pip install redis-py-cluster
RUN pip install futures</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* build 基本镜像,执行命令
</code></pre></div></div>
<p>docker build py_auto:v1 .</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3. 创建支持java命令环境
* 创建Dockerfile
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir v2 && cd v2 && vi Dockerfile ``` * 从oracle下载linux-jdk8到当前目录 * 在Dockerfile中输入如下命令 ``` FROM py_auto_base:v2 MAINTAINER Augustus "qianwx@asiainfo.com"
</code></pre></div></div>
<p>ADD ./jdk-8u151-linux-x64.rpm /opt/
RUN rpm -ivh /opt/jdk-8u151-linux-x64.rpm
RUN rm -f /opt/jdk-8u151-linux-x64.rpm</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* 执行如下命令
</code></pre></div></div>
<p>docker build py_auto:v2 .</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>4. 创建测试环境
* 在项目目录下创建Dockerfile
* 在Dickerfile中输入如下命令
</code></pre></div></div>
<p>FROM py_auto_base:v2
MAINTAINER Augustus “qianwx@asiainfo.com”</p>
<p>RUN mkdir /py_auto
VOLUME [“/py_auto”]</p>
<p>CMD [“/bin/bash”]</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>* 运行命令
</code></pre></div></div>
<p>docker build auto_test:v1 .</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
## 三、运行并执行测试
如果上面一切顺利,那我们这边已经成功构建了auto_test:v1镜像。下面我们来启动镜像并运行测试环境。
* 启动镜像
</code></pre></div></div>
<p>docker run -v /Users/augustus/asiawork/pytest/py-autotest-frame:/py_auto –name auto_test -d -it auto_test:v1</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> * 运行测试
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker exec auto_test python /py_auto/pyautotestframe/pyautotestframe.py -m sample-test ``` 这样成功运行了测试实例。
</code></pre></div></div>
<h2 id="四一点心得">四、一点心得</h2>
<ol>
<li>采用docker,快速的搭建了测试环境,在新增人员的情况下,可以节省不少时间在环境搭建上。就是对于开发
者也可以避免很多搭建环境的问题。</li>
<li>本示例采用的是每次一执行,所以使用docker exec,在有些情况下,需要启动镜像便运行环境的时候,可以
在Dockerfile中使用ENTRTPOINT来以使镜像启动便运行。</li>
</ol>
Lombok 介绍
2017-10-01T00:00:00+00:00
http://www.blogways.net/blog/2017/10/01/lombok
<p>Lombok 这个开源项目提供了一系列注解简化 Java 开发,帮助生成模板代码。本文对 Lombok 提供的注解进行简单的介绍。</p>
<h3 id="1gettersetter">1、@Getter/@Setter</h3>
<p>注解使用在类变量上,帮助生成默认的 <code class="language-plaintext highlighter-rouge">getter/setter</code> 方法。假设字段 <code class="language-plaintext highlighter-rouge">foo</code> 使用了这两个注解,则命名模式为:</p>
<ul>
<li>如果 foo 不是 boolean 类型,则方法会被命名为:getFoo()/setFoo();</li>
<li>如果 foo 是 boolean 类型,则方法会被命名为:isFoo()/setFoo()。</li>
</ul>
<p>访问级别默认都是 <code class="language-plaintext highlighter-rouge">public</code>,当然还可以通过 <code class="language-plaintext highlighter-rouge">AccessLevel</code> 来自定义,访问级别分为四种:<code class="language-plaintext highlighter-rouge">PUBLIC</code>,<code class="language-plaintext highlighter-rouge">PROTECTED</code>,<code class="language-plaintext highlighter-rouge">PACKAGE</code> 和 <code class="language-plaintext highlighter-rouge">PRIVATE</code>。</p>
<h3 id="2noargsconstructor-requiredargsconstructor-and-allargsconstructor">2、@NoArgsConstructor, @RequiredArgsConstructor and @AllArgsConstructor</h3>
<p>这三个注解使用在类级别上,会为类生成构造器。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">@NoArgsConstructor</code> 会生成一个没有参数的构造器,如果类有 <code class="language-plaintext highlighter-rouge">final</code> 修饰的属性,会抛出编译时异常 —— 除非使用了<code class="language-plaintext highlighter-rouge">@NoArgsConstructor(force = true)</code>;</li>
<li><code class="language-plaintext highlighter-rouge">@RequiredArgsConstructor</code> 会为所有用 <code class="language-plaintext highlighter-rouge">final</code> 或 <code class="language-plaintext highlighter-rouge">@NonNull</code> 修饰的属性生成一个构造器;</li>
<li><code class="language-plaintext highlighter-rouge">@AllArgsConstructor</code> 顾名思义会生成一个包含所有类变量的构造器。</li>
</ul>
<h3 id="3tostring">3、@ToString</h3>
<p>这个注解使用在类级别上,会为类生成 <code class="language-plaintext highlighter-rouge">toString</code> 方法,它有两个属性:</p>
<ul>
<li>callSuper:会调用父类的 toString 方法;</li>
<li>includeFieldNames:在 toString 方法中会输出字段名。</li>
</ul>
<h3 id="4equalsandhashcode">4、@EqualsAndHashCode</h3>
<p>《Effective Java》第9条建议介绍过,实现 <code class="language-plaintext highlighter-rouge">equals</code> 方法的同时<strong>必须</strong>实现 <code class="language-plaintext highlighter-rouge">hashCode</code> 方法 —— 如果不实现 <code class="language-plaintext highlighter-rouge">hashCode</code>,这个类就<strong>不能</strong>与 <code class="language-plaintext highlighter-rouge">HashMap</code> 等基于<code class="language-plaintext highlighter-rouge">哈希</code>的集合类正常工作;然而编写一个正确的 <code class="language-plaintext highlighter-rouge">hashCode</code> 方法是有一定难度的。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">equals</span><span class="o">(</span><span class="nc">Object</span> <span class="n">o</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">o</span> <span class="o">==</span> <span class="k">this</span><span class="o">)</span> <span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!(</span><span class="n">o</span> <span class="k">instanceof</span> <span class="nc">EqualsAndHashCodeExample</span><span class="o">))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="nc">EqualsAndHashCodeExample</span> <span class="n">other</span> <span class="o">=</span> <span class="o">(</span><span class="nc">EqualsAndHashCodeExample</span><span class="o">)</span> <span class="n">o</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="n">other</span><span class="o">.</span><span class="na">canEqual</span><span class="o">((</span><span class="nc">Object</span><span class="o">)</span><span class="k">this</span><span class="o">))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="n">other</span><span class="o">.</span><span class="na">getName</span><span class="o">()</span> <span class="o">!=</span> <span class="kc">null</span> <span class="o">:</span> <span class="o">!</span><span class="k">this</span><span class="o">.</span><span class="na">getName</span><span class="o">().</span><span class="na">equals</span><span class="o">(</span><span class="n">other</span><span class="o">.</span><span class="na">getName</span><span class="o">()))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(</span><span class="nc">Double</span><span class="o">.</span><span class="na">compare</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">score</span><span class="o">,</span> <span class="n">other</span><span class="o">.</span><span class="na">score</span><span class="o">)</span> <span class="o">!=</span> <span class="mi">0</span><span class="o">)</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">if</span> <span class="o">(!</span><span class="nc">Arrays</span><span class="o">.</span><span class="na">deepEquals</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">tags</span><span class="o">,</span> <span class="n">other</span><span class="o">.</span><span class="na">tags</span><span class="o">))</span> <span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">int</span> <span class="nf">hashCode</span><span class="o">()</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">int</span> <span class="no">PRIME</span> <span class="o">=</span> <span class="mi">59</span><span class="o">;</span>
<span class="kt">int</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span>
<span class="kd">final</span> <span class="kt">long</span> <span class="n">temp1</span> <span class="o">=</span> <span class="nc">Double</span><span class="o">.</span><span class="na">doubleToLongBits</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">score</span><span class="o">);</span>
<span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="n">result</span><span class="o">*</span><span class="no">PRIME</span><span class="o">)</span> <span class="o">+</span> <span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="mi">43</span> <span class="o">:</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">.</span><span class="na">hashCode</span><span class="o">());</span>
<span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="n">result</span><span class="o">*</span><span class="no">PRIME</span><span class="o">)</span> <span class="o">+</span> <span class="o">(</span><span class="kt">int</span><span class="o">)(</span><span class="n">temp1</span> <span class="o">^</span> <span class="o">(</span><span class="n">temp1</span> <span class="o">>>></span> <span class="mi">32</span><span class="o">));</span>
<span class="n">result</span> <span class="o">=</span> <span class="o">(</span><span class="n">result</span><span class="o">*</span><span class="no">PRIME</span><span class="o">)</span> <span class="o">+</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">deepHashCode</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">tags</span><span class="o">);</span>
<span class="k">return</span> <span class="n">result</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这个注解使用在类级别上,会为类生成 <code class="language-plaintext highlighter-rouge">equals</code> 和 <code class="language-plaintext highlighter-rouge">hashCode</code> 方法,极大地简化了程序员的工作。</p>
<h3 id="5cleanup">5、@Cleanup</h3>
<p>这个注解会自动调用类的 <code class="language-plaintext highlighter-rouge">close()</code> 方法和那一系列 <code class="language-plaintext highlighter-rouge">try-catch</code> 代码,用在方法的临时变量上 —— 比如输入输出流。程序员经常忘记在使用完毕之后调用 close() 方法导致内存泄漏的风险。</p>
<ul>
<li>value:如果没有 close() 方法,可以在 <code class="language-plaintext highlighter-rouge">value</code> 中指明方法名。</li>
</ul>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">CleanupExample</span> <span class="o">{</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kt">void</span> <span class="nf">main</span><span class="o">(</span><span class="nc">String</span><span class="o">[]</span> <span class="n">args</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">IOException</span> <span class="o">{</span>
<span class="nc">InputStream</span> <span class="n">in</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileInputStream</span><span class="o">(</span><span class="n">args</span><span class="o">[</span><span class="mi">0</span><span class="o">]);</span> <span class="c1">// 可用 @Cleanup 注解</span>
<span class="k">try</span> <span class="o">{</span>
<span class="nc">OutputStream</span> <span class="n">out</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">FileOutputStream</span><span class="o">(</span><span class="n">args</span><span class="o">[</span><span class="mi">1</span><span class="o">]);</span> <span class="c1">// 可用 @Cleanup 注解</span>
<span class="k">try</span> <span class="o">{</span>
<span class="kt">byte</span><span class="o">[]</span> <span class="n">b</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">byte</span><span class="o">[</span><span class="mi">10000</span><span class="o">];</span>
<span class="k">while</span> <span class="o">(</span><span class="kc">true</span><span class="o">)</span> <span class="o">{</span>
<span class="kt">int</span> <span class="n">r</span> <span class="o">=</span> <span class="n">in</span><span class="o">.</span><span class="na">read</span><span class="o">(</span><span class="n">b</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">r</span> <span class="o">==</span> <span class="o">-</span><span class="mi">1</span><span class="o">)</span> <span class="k">break</span><span class="o">;</span>
<span class="n">out</span><span class="o">.</span><span class="na">write</span><span class="o">(</span><span class="n">b</span><span class="o">,</span> <span class="mi">0</span><span class="o">,</span> <span class="n">r</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">out</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">out</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span> <span class="k">finally</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">in</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="n">in</span><span class="o">.</span><span class="na">close</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="6nonnull">6、@NonNull</h3>
<p>这个注解可以使用在构造器或是方法的<code class="language-plaintext highlighter-rouge">变量</code>上。它会为这个变量生成一段<code class="language-plaintext highlighter-rouge">非空检查</code>的代码。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">NonNullExample</span> <span class="kd">extends</span> <span class="nc">Something</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">public</span> <span class="nf">NonNullExample</span><span class="o">(</span><span class="nc">Person</span> <span class="n">person</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// @NonNull 可以用于 person</span>
<span class="kd">super</span><span class="o">(</span><span class="s">"Hello"</span><span class="o">);</span>
<span class="k">if</span> <span class="o">(</span><span class="n">person</span> <span class="o">==</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nf">NullPointerException</span><span class="o">(</span><span class="s">"person"</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">person</span><span class="o">.</span><span class="na">getName</span><span class="o">();</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="7data">7、@Data</h3>
<p>这个注解用于类级别上,是一系列注解的集合:等于同时在类级别使用了 <code class="language-plaintext highlighter-rouge">@ToString</code>,<code class="language-plaintext highlighter-rouge">@EqualsAndHashCode</code>, <code class="language-plaintext highlighter-rouge">@RequiredArgsConstructor</code>,为所有类变量上加上 <code class="language-plaintext highlighter-rouge">@Getter</code>, 为所有非 final 变量加上 <code class="language-plaintext highlighter-rouge">@Setter</code>。</p>
<h3 id="8value">8、@Value</h3>
<p>和 <code class="language-plaintext highlighter-rouge">@Data</code> 注解类似,不同的地方在于它为类和所有类变量加上了 <code class="language-plaintext highlighter-rouge">final</code> 修饰 - 除非类变量已经有 <code class="language-plaintext highlighter-rouge">@NonFinal</code> 修饰了。</p>
<h3 id="9builder">9、@Builder</h3>
<p>这个注解使用在类级别上,它实现了《Effective Java》书中的第2条建议或者说是 <code class="language-plaintext highlighter-rouge">Builder 模式</code>。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Builder</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BuilderExample</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="o">}</span>
</code></pre></div></div>
<p>相当于:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">BuilderExample</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="nc">BuilderExample</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">,</span> <span class="kt">int</span> <span class="n">age</span><span class="o">,</span> <span class="nc">Set</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">occupations</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">occupations</span> <span class="o">=</span> <span class="n">occupations</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="nc">BuilderExampleBuilder</span> <span class="nf">builder</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="k">new</span> <span class="nf">BuilderExampleBuilder</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">static</span> <span class="kd">class</span> <span class="nc">BuilderExampleBuilder</span> <span class="o">{</span>
<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
<span class="kd">private</span> <span class="kt">int</span> <span class="n">age</span><span class="o">;</span>
<span class="kd">private</span> <span class="n">java</span><span class="o">.</span><span class="na">util</span><span class="o">.</span><span class="na">ArrayList</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="n">occupations</span><span class="o">;</span>
<span class="nc">BuilderExampleBuilder</span><span class="o">()</span> <span class="o">{</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">BuilderExampleBuilder</span> <span class="nf">name</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="nc">BuilderExampleBuilder</span> <span class="nf">age</span><span class="o">(</span><span class="kt">int</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
<span class="k">return</span> <span class="k">this</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
Spring MVC 全局错误处理
2017-09-23T00:00:00+00:00
http://www.blogways.net/blog/2017/09/23/spring-error-hander
<h2 id="一默认情况defaulthandlerexceptionresolver">一、默认情况:DefaultHandlerExceptionResolver</h2>
<p>DefaultHandlerExceptionResolver 是 DispatcherServlet <strong>默认</strong>的错误处理类,它会把 Spring MVC 抛出的标准异常对应一个状态码写入到 response 中,并返回默认的错误页面。</p>
<p>如果应用是一个 RESTful API 或者需要自定义错误,可以实现 HandlerExceptionResolver 或者使用 ExceptionHandler 注解等方法。</p>
<p>默认状态下 Spring MVC 异常和状态码的对应关系表:
<img src="/images/jyjsjd/exception.png" alt="exception.png" /></p>
<h2 id="二自定义情况">二、自定义情况</h2>
<h3 id="1自定义错误页面">1、自定义错误页面</h3>
<p>可以在 web.xml 中自定义错误状态码对应的错误页面:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><error-page></span>
<span class="nt"><error-code></span>404<span class="nt"></error-code></span>
<span class="nt"><location></span>/WEB-INF/jsp/errors/404.jsp<span class="nt"></location></span>
<span class="nt"></error-page></span>
</code></pre></div></div>
<p>实际上 <code class="language-plaintext highlighter-rouge">error-code</code> 也可以是具体的异常类:</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><error-page></span>
<span class="nt"><error-code></span>java.lang.NullPointerException<span class="nt"></error-code></span>
<span class="nt"><location></span>/WEB-INF/jsp/errors/error.jsp<span class="nt"></location></span>
<span class="nt"></error-page></span>
</code></pre></div></div>
<h3 id="2自定义错误处理">2、自定义错误处理</h3>
<p>Spring MVC 的文档里给出了多个自定义错误处理的方法,我在这里总结为以下几种。</p>
<h4 id="1实现-handlerexceptionresolver-接口">(1)实现 HandlerExceptionResolver 接口</h4>
<p>HandlerExceptionResolver 接口可以处理所有 Controller <code class="language-plaintext highlighter-rouge">映射</code>(mapping)或<code class="language-plaintext highlighter-rouge">执行</code>(execution)时抛出的异常。</p>
<ul>
<li>
<p>实现HandlerExceptionResolver 接口,实现 <code class="language-plaintext highlighter-rouge">resolveException</code> 方法,并返回一个 <code class="language-plaintext highlighter-rouge">ModelAndView</code>。方法中的参数 <code class="language-plaintext highlighter-rouge">Exception</code>,就是要处理的异常,可以在方法体中判断异常的类型,采取不同的措施。</p>
</li>
<li>
<p>SimpleMappingExceptionResolver 是一个 HandlerExceptionResolver 的实现类,它可以把 Exception 和 View 一一对应,对不同的异常返回不同的页面。它的内部存放了一个 <strong>Properties</strong>,存储对应关系。</p>
</li>
</ul>
<h4 id="2exceptionhandler注解">(2)@ExceptionHandler注解</h4>
<p>ExceptionHandler 给 RESTful API 提供了错误处理方法。它的 <code class="language-plaintext highlighter-rouge">Value</code> 属性可以被设置为一个或一组异常类型——即方法体要处理的异常。</p>
<p>它可以被用在 @Controller 或 @ControllerAdvice 注解的类的方法上,当用在@Controller 方法上时,它会处理这个 Controller 中抛出的异常。</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Controller</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">SimpleController</span> <span class="o">{</span>
<span class="nd">@ExceptionHandler</span><span class="o">(</span><span class="nc">IOException</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="nc">ResponseEntity</span><span class="o"><</span><span class="nc">String</span><span class="o">></span> <span class="nf">handleIOException</span><span class="o">(</span><span class="nc">IOException</span> <span class="n">ex</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// prepare responseEntity</span>
<span class="k">return</span> <span class="n">responseEntity</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<h4 id="3controlleradvice-restcontrolleradvice">(3)@ControllerAdvice @RestControllerAdvice</h4>
<p>使用 @ControllerAdvice 注解的类可以包含 @ExceptionHandler、@InitBinder 或 @ModelAttribute 注解的方法,它可以被 Spring MVC 自动发现和注册,可以处理所有 Controller 中用 @RequestMapping 的方法。</p>
<p>它也可以被限定只处理某些特定 Controller 的异常:</p>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 限定为 @RestController 注解的方法</span>
<span class="nd">@ControllerAdvice</span><span class="o">(</span><span class="n">annotations</span> <span class="o">=</span> <span class="nc">RestController</span><span class="o">.</span><span class="na">class</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AnnotationAdvice</span> <span class="o">{}</span>
<span class="c1">// 限定为指定包中的 Controller </span>
<span class="nd">@ControllerAdvice</span><span class="o">(</span><span class="s">"org.example.controllers"</span><span class="o">)</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">BasePackageAdvice</span> <span class="o">{}</span>
<span class="c1">// 限定为指定的类</span>
<span class="nd">@ControllerAdvice</span><span class="o">(</span><span class="n">assignableTypes</span> <span class="o">=</span> <span class="o">{</span><span class="nc">ControllerInterface</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">AbstractController</span><span class="o">.</span><span class="na">class</span><span class="o">})</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">AssignableTypesAdvice</span> <span class="o">{}</span>
</code></pre></div></div>
disconf 的安装和使用
2017-07-23T00:00:00+00:00
http://www.blogways.net/blog/2017/07/23/disconf
<h2 id="一概述">一、概述</h2>
<p>disconf 是一个分布式配置文件管理平台,提供统一的方式管理所有配置文件。主要有两个部件:</p>
<ul>
<li>disconf-web:管理配置文件的网络平台。</li>
<li>disconf-client:使用配置的项目,通常是自己的项目。</li>
</ul>
<h2 id="二项目依赖">二、项目依赖</h2>
<ul>
<li>MySQL</li>
<li>Tomcat</li>
<li>Nginx</li>
<li>zookeeper</li>
<li>Redis</li>
</ul>
<h2 id="三安装-disconf-web">三、安装 disconf-web</h2>
<p>以下步骤构建项目。</p>
<ol>
<li>克隆 disconf 项目到本地,这里假设是 <code class="language-plaintext highlighter-rouge">/home</code>。把 disconf-web 拷贝到 <code class="language-plaintext highlighter-rouge">/home</code> 目录。</li>
<li>新建目录:
<ul>
<li><code class="language-plaintext highlighter-rouge">/home/work/dsp/disconf-rd/online-resources</code>(存放自定义配置文件)。</li>
<li><code class="language-plaintext highlighter-rouge">/home/work/dsp/disconf-rd/war</code>(deploy 目录)。</li>
</ul>
</li>
<li>拷贝 <code class="language-plaintext highlighter-rouge">/disconf-web/profile/rd/</code> 目录下的文件到 <code class="language-plaintext highlighter-rouge">/home/work/dsp/disconf-rd/online-resources</code>。
<ul>
<li>把 <code class="language-plaintext highlighter-rouge">application-demo.properties</code> 更名为 <code class="language-plaintext highlighter-rouge">application.properties</code>。</li>
<li>把 <code class="language-plaintext highlighter-rouge">application.properties</code> 的 <code class="language-plaintext highlighter-rouge">domain</code> 改为服务器地址,本地就是 <code class="language-plaintext highlighter-rouge">localhost</code>。</li>
<li>一定要配置<strong>两个</strong> redis client(可以配置同样的两个,名字不同就行了)。</li>
<li>修改文件。</li>
</ul>
</li>
<li>建构项目:</li>
</ol>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nv">ONLINE_CONFIG_PATH</span><span class="o">=</span>/home/work/dsp/disconf-rd/online-resources
<span class="nv">WAR_ROOT_PATH</span><span class="o">=</span>/home/work/dsp/disconf-rd/war
<span class="nb">export </span>ONLINE_CONFIG_PATH
<span class="nb">export </span>WAR_ROOT_PATH
<span class="nb">cd </span>disconf-web
sh deploy/deploy.sh
</code></pre></div></div>
<hr />
<p>下面步骤部署项目。</p>
<ol>
<li>数据库:参考 <code class="language-plaintext highlighter-rouge">sql/readme.md</code> 来进行数据库的初始化。</li>
<li>部署 war:修改 <code class="language-plaintext highlighter-rouge">server.xml</code>, 在 <code class="language-plaintext highlighter-rouge">Host</code> 节点下添加 <code class="language-plaintext highlighter-rouge"><Context path="" docBase="/home/work/dsp/disconf-rd/war"></Context></code>。</li>
<li>修改 Nginx 配置文件 <code class="language-plaintext highlighter-rouge">nginx.conf</code>:
<ul>
<li>上游服务器是 Tomcat,默认端口8080。</li>
<li><code class="language-plaintext highlighter-rouge">server_name</code> 改为服务器名,本地就是 <code class="language-plaintext highlighter-rouge">localhost</code>。</li>
</ul>
</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> `upstream disconf {
server 127.0.0.1:8080;
}
server {
include mime.types;
default_type application/octet-stream;
listen 8081;
server_name localhost;
access_log /home/work/var/logs/disconf/access.log;
error_log /home/work/var/logs/disconf/error.log;
location / {
root /home/work/dsp/disconf-rd/war/html;
if ($query_string) {
expires max;
}
}
location ~ ^/(api|export) {
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_pass http://disconf;
}
}
</code></pre></div></div>
<hr />
<p>启动项目</p>
<ol>
<li>启动 Tomcat、Nginx。</li>
<li>访问 <code class="language-plaintext highlighter-rouge">http://localhost:8081/</code>。</li>
</ol>
<h2 id="四配置-disconf-client">四、配置 disconf-client</h2>
<p>1、添加 maven 依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>com.baidu.disconf</groupId>
<artifactId>disconf-client</artifactId>
<version>2.6.36</version>
</dependency>
</code></pre></div></div>
<p>2、disconf 启动文件,disconf.properties:</p>
<ul>
<li>conf_server_host:配置服务器,也就是 <strong>disconf-web</strong> 的地址</li>
<li>app:<code class="language-plaintext highlighter-rouge">App</code> 名字。</li>
<li>version:版本,和 <code class="language-plaintext highlighter-rouge">APP</code> 的版本一致。</li>
<li>env:环境,和 <code class="language-plaintext highlighter-rouge">APP</code> 的版本一致。</li>
<li>debug:和 <code class="language-plaintext highlighter-rouge">APP</code> 的版本一致。</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # 是否使用远程配置文件
# true(默认)会从远程获取配置 false则直接获取本地配置
enable.remote.conf=true
# 配置服务器的 HOST,用逗号分隔 127.0.0.1:8000,127.0.0.1:8000
conf_server_host=127.0.0.1:8080
# 版本, 请采用 X_X_X_X 格式
version=1_0_0_0
# APP 请采用 产品线_服务名 格式
app=disconf_demo
# 环境
env=rd
# debug
debug=true
# 忽略哪些分布式配置,用逗号分隔
ignore=
# 获取远程配置 重试次数,默认是3次
conf_server_url_retry_times=3
# 获取远程配置 重试时休眠时间,默认是5秒
conf_server_url_retry_sleep_seconds=5
</code></pre></div></div>
<p>3、配置文件添加 disconf 支持:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><context:component-scan base-package="com.example"/>
<aop:aspectj-autoproxy proxy-target-class="true"/> <!-- 必须支持 AOP -->
<!-- 使用disconf必须添加以下配置 -->
<bean id="disconfMgrBean" class="com.baidu.disconf.client.DisconfMgrBean"
destroy-method="destroy">
<property name="scanPackage" value="com.example.disconf.demo"/> <!-- 要扫描的包 -->
</bean>
<bean id="disconfMgrBean2" class="com.baidu.disconf.client.DisconfMgrBeanSecond"
init-method="init" destroy-method="destroy">
</bean>
</code></pre></div></div>
<p>4、添加要托管的文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- 使用托管方式的disconf配置(无代码侵入, 配置更改会自动reload)-->
<bean id="configproperties_disconf" class="com.baidu.disconf.client.addons.properties.ReloadablePropertiesFactoryBean">
<property name="locations">
<list>
<!-- 要托管的文件列表 -->
<value>classpath:/autoconfig.properties</value>
</list>
</property>
</bean>
<bean id="propertyConfigurer" class="com.baidu.disconf.client.addons.properties.ReloadingPropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true" />
<property name="ignoreUnresolvablePlaceholders" value="true" />
<property name="propertiesArray">
<list>
<ref bean="configproperties_disconf" />
</list>
</property>
</bean>
</code></pre></div></div>
<p>5、把文件上传到 disconf-web。</p>
ehcache 的配置和存储结构
2017-07-22T00:00:00+00:00
http://www.blogways.net/blog/2017/07/22/ehcache
<h2 id="一xml配置文件">一、XML配置文件</h2>
<ol>
<li><code class="language-plaintext highlighter-rouge">ehcache</code> 默认会在 <code class="language-plaintext highlighter-rouge">classpath</code> 下查找名为 <code class="language-plaintext highlighter-rouge">ehcache.xml</code> 的文件,当然也可以自定义名称。</li>
<li>如果没有找到 <code class="language-plaintext highlighter-rouge">ehcache.xml</code>,ehcache 会默认试用 <code class="language-plaintext highlighter-rouge">ehcache-failsafe.xml</code>,它被打包在 ehcache 的 <code class="language-plaintext highlighter-rouge">jar</code> 文件中。如果用户使用的是这个文件,ehcache 会报一个<code class="language-plaintext highlighter-rouge">警告</code>,让用户自己定义一个配置文件。</li>
<li><code class="language-plaintext highlighter-rouge">defaultCache</code> 配置会被应用到所有没有被显示声明的缓存中。这个配置不是必需的。</li>
</ol>
<h2 id="二动态改变配置">二、动态改变配置</h2>
<ol>
<li><code class="language-plaintext highlighter-rouge">禁用</code>动态改变配置:
<ul>
<li>在 XML 文件中把属性 dynamicConfig 设置为 false。</li>
<li>在代码中禁用:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">Cache</span> <span class="n">cache</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="na">getCache</span><span class="o">(</span><span class="s">"sampleCache"</span><span class="o">);</span>
<span class="n">cache</span><span class="o">.</span><span class="na">disableDynamicFeatures</span><span class="o">();</span>
</code></pre></div> </div>
</li>
</ul>
</li>
<li>可以动态改变的配置:
<ul>
<li>timeToLive:一个 element 在<code class="language-plaintext highlighter-rouge">缓存中存在</code>的最长时间(秒),不论它是否被访问过都会被清除。</li>
<li>timeToIdle:一个 element <code class="language-plaintext highlighter-rouge">未被访问</code>的最长时间(秒),经过这段时间后被清除。</li>
<li>maxEntriesLocalHeap</li>
<li>maxBytesLocalHeap</li>
<li>maxEntriesLocalDisk</li>
<li>maxBytesLocalDisk
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nc">Cache</span> <span class="n">cache</span> <span class="o">=</span> <span class="n">manager</span><span class="o">.</span><span class="na">getCache</span><span class="o">(</span><span class="s">"sampleCache"</span><span class="o">);</span>
<span class="nc">CacheConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="n">cache</span><span class="o">.</span><span class="na">getCacheConfiguration</span><span class="o">();</span>
<span class="n">config</span><span class="o">.</span><span class="na">setTimeToIdleSeconds</span><span class="o">(</span><span class="mi">60</span><span class="o">);</span>
<span class="n">config</span><span class="o">.</span><span class="na">setTimeToLiveSeconds</span><span class="o">(</span><span class="mi">120</span><span class="o">);</span>
<span class="n">config</span><span class="o">.</span><span class="na">setmaxEntriesLocalHeap</span><span class="o">(</span><span class="mi">10000</span><span class="o">);</span>
<span class="n">config</span><span class="o">.</span><span class="na">setmaxEntriesLocalDisk</span><span class="o">(</span><span class="mi">1000000</span><span class="o">);</span>
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<h2 id="三传递拷贝而非引用">三、传递拷贝而非引用</h2>
<p>默认情况下 <code class="language-plaintext highlighter-rouge">get()</code> 方法会取得缓存中数据的<strong>引用</strong>,之后对这个数据的所有改变都会<strong>立刻</strong>反映到缓存中。有些时候用户想要获得一个缓存数据的<code class="language-plaintext highlighter-rouge">拷贝</code>,对这个拷贝的操作不会影响到缓存。</p>
<ol>
<li>XML 配置: 把 <code class="language-plaintext highlighter-rouge">copyOnRead</code> 和 <code class="language-plaintext highlighter-rouge">copyOnWrite</code> 设置为 <code class="language-plaintext highlighter-rouge">true</code></li>
</ol>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><cache</span> <span class="na">name=</span><span class="s">"copyCache"</span>
<span class="na">maxEntriesLocalHeap=</span><span class="s">"10"</span>
<span class="na">eternal=</span><span class="s">"false"</span>
<span class="na">timeToIdleSeconds=</span><span class="s">"5"</span>
<span class="na">timeToLiveSeconds=</span><span class="s">"10"</span>
<span class="na">copyOnRead=</span><span class="s">"true"</span>
<span class="na">copyOnWrite=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><copyStrategy</span> <span class="na">class=</span><span class="s">"com.company.ehcache.MyCopyStrategy"</span><span class="nt">/></span>
<span class="nt"></cache></span>
</code></pre></div></div>
<ol>
<li>Java 代码中:</li>
</ol>
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CacheConfiguration</span> <span class="n">config</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CacheConfiguration</span><span class="o">(</span><span class="s">"copyCache"</span><span class="o">,</span> <span class="mi">1000</span><span class="o">).</span><span class="na">copyOnRead</span><span class="o">(</span><span class="kc">true</span><span class="o">).</span><span class="na">copyOnWrite</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span>
<span class="nc">Cache</span> <span class="n">copyCache</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Cache</span><span class="o">(</span><span class="n">config</span><span class="o">);</span>
</code></pre></div></div>
<hr />
<p>在 <code class="language-plaintext highlighter-rouge">get()</code> 或者 <code class="language-plaintext highlighter-rouge">put()</code> 方法获得拷贝的时候,可以自定义<code class="language-plaintext highlighter-rouge">拷贝策略</code>。</p>
<ol>
<li>实现接口 <code class="language-plaintext highlighter-rouge">net.sf.ehcache.store.compound.CopyStrategy</code>。</li>
<li>XML 中配置 <code class="language-plaintext highlighter-rouge"><copyStrategy class="com.company.ehcache.MyCopyStrategy"/></code>。</li>
<li>Java 代码中:
<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">CacheConfiguration</span> <span class="n">cacheConfiguration</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CacheConfiguration</span><span class="o">(</span><span class="s">"copyCache"</span><span class="o">,</span> <span class="mi">10</span><span class="o">);</span>
<span class="nc">CopyStrategyConfiguration</span> <span class="n">copyStrategyConfiguration</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">CopyStrategyConfiguration</span><span class="o">();</span>
<span class="n">copyStrategyConfiguration</span><span class="o">.</span><span class="na">setClass</span><span class="o">(</span><span class="s">"com.company.ehcache.MyCopyStrategy"</span><span class="o">);</span>
<span class="n">cacheConfiguration</span><span class="o">.</span><span class="na">addCopyStrategy</span><span class="o">(</span><span class="n">copyStrategyConfiguration</span><span class="o">);</span>
</code></pre></div> </div>
</li>
</ol>
<h2 id="四存储层级">四、存储层级</h2>
<ol>
<li>内存存储:在<code class="language-plaintext highlighter-rouge">堆内存</code>里存储。从属于 Java GC。</li>
<li>非堆存储:受限于 <code class="language-plaintext highlighter-rouge">RAM</code> 的可用空间。
<ul>
<li>不从属于 Java GC。</li>
<li>只能存储<strong>序列化</strong>的数据。</li>
<li>为内存存储提供了溢出能力。</li>
</ul>
</li>
<li>磁盘存储。
<ul>
<li>备份内存存储。</li>
<li>为非堆存储提供溢出能力。</li>
<li>只能存储<strong>序列化</strong>的数据。</li>
</ul>
</li>
</ol>
<h2 id="五存储详细介绍">五、存储详细介绍</h2>
<ol>
<li>内存存储:在堆内存分配空间。在不引起 GC 停顿的前提下,尽可能分配空间。利用<code class="language-plaintext highlighter-rouge">非堆内存</code>存储溢出的数据(为了不引起 GC 停顿)。
<ul>
<li>速度是<code class="language-plaintext highlighter-rouge">最快</code>的。</li>
<li>接受所有数据,无论有没有实现 <code class="language-plaintext highlighter-rouge">Serializable</code>。</li>
<li><code class="language-plaintext highlighter-rouge">线程安全</code>。</li>
<li>如果数据量超过了存储最大值:(1)配置了溢出策略,数据可以被保存到其他层级;(2)没有配置,一部分数据被删除。</li>
<li>内存回收策略:
<ul>
<li>LRU(最近最少使用):<strong>默认策略</strong>。缓存的时间戳<code class="language-plaintext highlighter-rouge">离当前时间最远</code>的将被回收。</li>
<li>LFU(最少被使用):缓存有一个 hit 值,<code class="language-plaintext highlighter-rouge">值最小</code>的被回收。</li>
<li>FIFO(先进先出)</li>
</ul>
</li>
</ul>
</li>
<li>磁盘存储
<ul>
<li>仅能存储实现了 <code class="language-plaintext highlighter-rouge">Serializable</code> 接口的数据。其他数据会抛出 <code class="language-plaintext highlighter-rouge">NotSerializableException</code> 异常。</li>
<li>磁盘存储是<strong>可选</strong>的,不一定要配置;如果有多个 CacheManager,也没有必要配置多个磁盘存储路径。</li>
<li>磁盘存储选项:
<ul>
<li>localTempSwap:允许缓存存放到磁盘,但重启之后这些<strong>数据就会丢失</strong>。</li>
<li>localRestartable:<strong>重启之后数据不会丢失</strong>,会自动加载到内存中。</li>
</ul>
</li>
</ul>
</li>
</ol>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nt"><persistence</span> <span class="na">strategy=</span><span class="s">"localTempSwap"</span> <span class="nt">/></span>
</code></pre></div></div>
<ul>
<li>磁盘存储路径:
<ul>
<li>user.home:用户 <code class="language-plaintext highlighter-rouge">home</code> 目录。</li>
<li>user.dir:用户当前的活动目录。</li>
<li>java.io.tmpdir:默认的临时目录。</li>
<li>ehcache.disk.store.dir:命令行中指定的系统属性。</li>
</ul>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><diskStore</span> <span class="na">path=</span><span class="s">"/path/to/store/data"</span><span class="nt">/></span>
</code></pre></div> </div>
</li>
<li>禁用磁盘存储:不要在文件里配置 <code class="language-plaintext highlighter-rouge">diskStore</code>。</li>
</ul>
当当 Elastic-job定时调度
2017-07-16T00:00:00+00:00
http://www.blogways.net/blog/2017/07/16/elastic-job
<h2 id="为什么需要作业调度">为什么需要作业调度</h2>
<p>作业即定时任务。一般来说,系统可使用消息传递代替部分使用作业的场景。两者确有相似之处。可互相替换的场景,如队列表。将待处理的数据放入队列表,然后使用频率极短的定时任务拉取队列表的数据并处理。这种情况使用消息中间件的推送模式可更好的处理实时性数据。而且基于数据库的消息存储吞吐量远远小于基于文件的顺序追加消息存储。</p>
<p><img src="/images/chenfan/elastic.jpg" alt="elastic1" /></p>
<h2 id="elastic-job与其他定时框架比较">elastic-job与其他定时框架比较</h2>
<p>当当之前使用的作业系统比较散乱,各自为战,大致分为以下4种:</p>
<ul>
<li>
<p>Quartz:Java事实上的定时任务标准。但Quartz关注点在于定时任务而非数据,并无一套根据数据处理而定制化的流程。虽然Quartz可以基于数据库实现作业的高可用,但缺少分布式并行执行作业的功能。</p>
</li>
<li>
<p>TBSchedule:阿里早期开源的分布式任务调度系统。代码略陈旧,使用timer而非线程池执行任务调度。众所周知,timer在处理异常状况时是有缺陷的。而且TBSchedule作业类型较为单一,只能是获取/处理数据一种模式。还有就是文档缺失比较严重。</p>
</li>
<li>
<p>Crontab:Linux系统级的定时任务执行器。缺乏分布式和集中管理功能。</p>
</li>
<li>
<p>Perl:遗留系统使用,目前已不符合公司的Java化战略。</p>
</li>
</ul>
<h2 id="elastic-job的特点">elastic-job的特点</h2>
<ul>
<li>
<p>定时任务: 基于成熟的定时任务作业框架Quartz cron表达式执行定时任务。</p>
</li>
<li>
<p>作业注册中心: 基于Zookeeper和其客户端Curator实现的全局作业注册控制中心。用于注册,控制和协调分布式作业执行。</p>
</li>
<li>
<p>作业分片: 将一个任务分片成为多个小任务项在多服务器上同时执行。</p>
</li>
<li>
<p>弹性扩容缩容: 运行中的作业服务器崩溃,或新增加n台作业服务器,作业框架将在下次作业执行前重新分片,不影响当前作业执行。</p>
</li>
<li>
<p>支持多种作业执行模式: 支持OneOff,Perpetual和SequencePerpetual三种作业模式。</p>
</li>
<li>
<p>失效转移: 运行中的作业服务器崩溃不会导致重新分片,只会在下次作业启动时分片。启用失效转移功能可以在本次作业执行过程中,监测其他作业服务器空闲,抓取未完成的孤儿分片项执行。</p>
</li>
<li>
<p>运行时状态收集: 监控作业运行时状态,统计最近一段时间处理的数据成功和失败数量,记录作业上次运行开始时间,结束时间和下次运行时间。</p>
</li>
<li>
<p>作业停止,恢复和禁用:用于操作作业启停,并可以禁止某作业运行(上线时常用)。</p>
</li>
<li>
<p>被错过执行的作业重触发:自动记录错过执行的作业,并在上次作业完成后自动触发。可参考Quartz的misfire。</p>
</li>
<li>
<p>多线程快速处理数据:使用多线程处理抓取到的数据,提升吞吐量。</p>
</li>
<li>
<p>幂等性:重复作业任务项判定,不重复执行已运行的作业任务项。由于开启幂等性需要监听作业运行状态,对瞬时反复运行的作业对性能有较大影响。</p>
</li>
<li>
<p>容错处理:作业服务器与Zookeeper服务器通信失败则立即停止作业运行,防止作业注册中心将失效的分片分项配给其他作业服务器,而当前作业服务器仍在执行任务,导致重复执行。</p>
</li>
<li>
<p>Spring支持:支持spring容器,自定义命名空间,支持占位符。</p>
</li>
<li>
<p>运维平台:提供运维界面,可以管理作业和注册中心。</p>
</li>
</ul>
<h2 id="elastic-job简单定时任务">elastic-job简单定时任务</h2>
<ol>
<li>
<p>引入maven</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <!-- 引入elastic-job核心模块 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-core</artifactId>
<version>1.0.1</version>
</dependency>
<!-- 使用springframework自定义命名空间时引入 -->
<dependency>
<groupId>com.dangdang</groupId>
<artifactId>elastic-job-spring</artifactId>
<version>1.0.1</version>
</dependency>
</code></pre></div> </div>
</li>
</ol>
<p>2.设置job.properties
用于设置定时任务的属性</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 加载数据库
event.rdb.driver=org.h2.Driver
event.rdb.url=jdbc:h2:mem:job_event_storage
event.rdb.username=chenfan
event.rdb.password=123456
listener.simple=com.dangdang.ddframe.job.example.listener.SpringSimpleListener
listener.distributed=com.dangdang.ddframe.job.example.listener.SpringSimpleDistributeListener
listener.distributed.startedTimeoutMilliseconds=1000
listener.distributed.completedTimeoutMilliseconds=3000
# 简单定时任务设置
simple.id=springSimpleJob
simple.class=com.dangdang.ddframe.job.example.job.simple.SpringSimpleJob
simple.cron=0/5 * * * * ?
simple.shardingTotalCount=3
simple.shardingItemParameters=0=Beijing,1=Shanghai,2=Guangzhou
simple.monitorExecution=false
simple.failover=true
simple.description=\u53EA\u8FD0\u884C\u4E00\u6B21\u7684\u4F5C\u4E1A\u793A\u4F8B
simple.disabled=false
simple.overwrite=true
simple.monitorPort=9888
</code></pre></div></div>
<ol>
<li>reg.properties</li>
</ol>
<p>设置命名空间,定义zookeeper的端口等</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>serverLists=localhost:2181
namespace=elastic-job-example-lite-spring
baseSleepTimeMilliseconds=1000
maxSleepTimeMilliseconds=3000
maxRetries=3
</code></pre></div></div>
<ol>
<li>
<p>配置spring</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:reg="http://www.dangdang.com/schema/ddframe/reg"
xmlns:job="http://www.dangdang.com/schema/ddframe/job"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.dangdang.com/schema/ddframe/reg
http://www.dangdang.com/schema/ddframe/reg/reg.xsd
http://www.dangdang.com/schema/ddframe/job
http://www.dangdang.com/schema/ddframe/job/job.xsd
">
<context:component-scan base-package="com.dangdang.ddframe.job.example" />
<context:property-placeholder location="classpath:conf/*.properties" />
<bean id="elasticJobLog" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${event.rdb.driver}"/>
<property name="url" value="${event.rdb.url}"/>
<property name="username" value="${event.rdb.username}"/>
<property name="password" value="${event.rdb.password}"/>
</bean>
<!--配置作业注册中心 -->
<reg:zookeeper id="regCenter" server-lists="${serverLists}" namespace="${namespace}" base-sleep-time-milliseconds="${baseSleepTimeMilliseconds}" max-sleep-time-milliseconds="${maxSleepTimeMilliseconds}" max-retries="${maxRetries}" />
<!-- 配置作业-->
<job:simple id="${simple.id}" class="${simple.class}" registry-center-ref="regCenter" sharding-total-count="${simple.shardingTotalCount}" cron="${simple.cron}" sharding-item-parameters="${simple.shardingItemParameters}" monitor-execution="${simple.monitorExecution}" monitor-port="${simple.monitorPort}" failover="${simple.failover}" description="${simple.description}" disabled="${simple.disabled}" overwrite="${simple.overwrite}" event-trace-rdb-data-source="elasticJobLog" />
<!-- use absolute path to run script job -->
<!--
<job:script id="${script.id}" registry-center-ref="regCenter" script-command-line="${script.scriptCommandLine}" sharding-total-count="${script.shardingTotalCount}" cron="${script.cron}" sharding-item-parameters="${script.shardingItemParameters}" description="${script.description}" overwrite="${script.overwrite}" />
-->
</code></pre></div> </div>
<p></beans></p>
</li>
<li>
<p>加载spring的xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package com.dangdang.ddframe.job.example;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class SpringMain {
private static final int EMBED_ZOOKEEPER_PORT = 5181;
// CHECKSTYLE:OFF
public static void main(final String[] args) {
// CHECKSTYLE:ON
new ClassPathXmlApplicationContext("classpath:META-INF/applicationContext.xml");
}
}
</code></pre></div> </div>
</li>
<li>
<p>结果</p>
</li>
</ol>
<p><img src="/images/chenfan/elastic1.png" alt="elastic1" /></p>
Storm(4)-JStorm自定义个性化线程
2017-07-02T00:00:00+00:00
http://www.blogways.net/blog/2017/07/02/storm-4
<h2 id="应用场景">应用场景</h2>
<p>在计费账务流程中,bolt线程用来做相同业务逻辑的任务处理,而对资料信息的更新逻辑需要一个线程定时扫描更新,该线程需要与bolt的线程并列共同在一个worker中,但又要达到个性化控制的要求。</p>
<h2 id="设计原则">设计原则</h2>
<ul>
<li>
<p>不能影响原有的spout、bolt业务逻辑</p>
</li>
<li>
<p>该处理线程的业务逻辑要跟bolt的区分开来</p>
</li>
<li>
<p>该线程的生命周期与worker的一致</p>
</li>
<li>
<p>该线程的业务处理更新bolt中的数据源</p>
</li>
<li>
<p>该线程只需要启动一个即可,不能多,多了浪费资源</p>
</li>
</ul>
<h2 id="示例">示例</h2>
<p>bolt中定义线程,模拟计费流程中的资料更新任务</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//自定义线程
class TestThread extends Thread {
public void run() {
try {
while (true) {
//模拟修改资料信息的变化
step = step + 5;
logger.info("worker name: {} | define thread id: {} | datetime: {} | step: {}", workerName,
Thread.currentThread().getId(), formatter.format(new Date()), step);
Thread.currentThread().sleep(5000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
</code></pre></div></div>
<p>在bolt流程中启动自定义线程</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override
public void prepare(Map stormConf, TopologyContext context, OutputCollector collector) {
workerName = ManagementFactory.getRuntimeMXBean().getName();
this.collector = collector;
//使用锁,保证一个bolt流程中只启动一个自定义线程即可,不能多
if(lock==0){
lock++;
TestThread test = new TestThread();
test.start();
}
}
</code></pre></div></div>
<p>在bolt流程中测试step值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override
public void execute(Tuple input) {
//测试bolt的业务流程中取到的step值是否在不断的更新
logger.info("worker name: {} | bolt thread id: {} | datetime: {} | step: {}", workerName,
Thread.currentThread().getId(), formatter.format(new Date()), step);
}
</code></pre></div></div>
<h2 id="测试结果">测试结果</h2>
<p><img src="/images/zhaojiajun/2017-07-02-storm-4img1.png" alt="1" /></p>
<p>从结果可以看出:</p>
<p>(1)thread id为60的自定义线程更新step值为60后,thread id为57和55的两个bolt线程取到的值都为60;</p>
<p>(2)自定义线程的更新频率为5秒,而bolt的更新频率收spout的影响为2秒,两者业务逻辑完全不同;</p>
<p>(3)停止该topology后worker停止,日志不在打印,说明自定义线程也结束。</p>
Storm(3)-JStorm介绍
2017-07-02T00:00:00+00:00
http://www.blogways.net/blog/2017/07/02/storm-3
<h1 id="应用场景">应用场景</h1>
<p>JStorm处理数据的方式是基于消息的流水线处理,因此特别适合无状态计算,也就是计算单元的依赖的数据全部在接收的消息中可以找到,并且最好一个数据流不依赖另外一个数据流。</p>
<p>因此,常常用于</p>
<ul>
<li>
<p>日志分析,从日志中分析出特定的数据,并将分析的结果存入外部存储器如数据库。</p>
</li>
<li>
<p>管道系统,将一个数据从一个系统传输到另外一个系统,比如将数据库同步到Hadoop。</p>
</li>
<li>
<p>消息转化器,将接受到的消息按照某种格式进行转化,存储到另外一个系统如消息中间件。</p>
</li>
<li>
<p>统计分析器,从日志或消息中,提炼出某个字段,然后做count或sum计算,最后将统计值存入外部存储器。中间处理过程可能更复杂。</p>
</li>
</ul>
<h2 id="现有storm无法满足的一些需求">现有storm无法满足的一些需求:</h2>
<ul>
<li>
<p>现有的storm调度太粗暴简单,无法定制化</p>
</li>
<li>
<p>雪崩问题一直没有解决</p>
</li>
<li>
<p>监控太简单</p>
</li>
<li>
<p>对ZK访问太频繁</p>
</li>
</ul>
<h2 id="jstorm比storm更稳定更快">JStorm比Storm更稳定,更快!</h2>
<p>1.Nimbus实现HA:当一台nimbus挂了,自动热切到备份nimbus</p>
<p>示例:</p>
<p>nimbus在两个主机上都有:</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img1.png" alt="1" /></p>
<p>停止131主机上nimbus进程:</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img2.png" alt="2" /></p>
<p>原nimbus的slave主机自动变为master:</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img3.png" alt="3" /></p>
<p>2.原生Storm RPC:Zeromq使用堆外内存,导致OS内存不够,Netty导致OOM;JStorm底层RPC采用netty + disruptor,保证发送速度和接收速度是匹配的,彻底解决雪崩问题。</p>
<p>3.现有Strom,在添加supervisor或者supervisor shutdown时,会触发任务rebalance;提交新任务时,当worker数不够时,触发其他任务做rebalance。——在JStorm中不会发生,使得数据流更稳定。</p>
<p>4.新上线的任务不会冲击老的任务:新调度从cpu,memory,disk,net 四个角度对任务进行分配;已经分配好的新任务,无需去抢占老任务的cpu,memory,disk和net ——任务之间影响小。</p>
<p>5.Supervisor主线 ——more catch</p>
<p>6.Spout/Bolt 的open/prepare ——more catch</p>
<p>7.所有IO, 序列化,反序列化 ——more catch</p>
<p>8.减少对ZK的访问量:去掉大量无用的watch;task的心跳时间延长一倍;Task心跳检测无需全ZK扫描。</p>
<h2 id="jstorm相比storm调度更强大">JStorm相比Storm调度更强大</h2>
<p>1.彻底解决了storm 任务分配不均衡问题</p>
<p>2.从4个维度进行任务分配:CPU、Memory、Disk、Net</p>
<p>3.默认一个task,一个cpu slot。当task消耗更多的cpu时,可以申请更多cpu slot
解决新上线的任务去抢占老任务的cpu
一淘有些task内部起很多线程,单task消耗太多cpu</p>
<p>4.默认一个task,一个memory slot。当task需要更多内存时,可以申请更多内存slot
先海狗项目中,slot task 需要8G内存,而且其他任务2G内存就够了</p>
<p>5.默认task,不申请disk slot。当task 磁盘IO较重时,可以申请disk slot
海狗/实时同步项目中,task有较重的本地磁盘读写操作</p>
<p>6.可以强制某个component的task 运行在不同的节点上
聚石塔,海狗项目,某些task提供web Service服务,为了端口不冲突,因此必须强制这些task运行在不同节点上</p>
<p>7.可以强制topology运行在单独一个节点上
节省网络带宽
Tlog中大量小topology,为了减少网络开销,强制任务分配到一个节点上</p>
<p>8.可以自定义任务分配:提前预约任务分配到哪台机器上,哪个端口,多少个cpu slot,多少内存,是否申请磁盘
海狗项目中,部分task期望分配到某些节点上</p>
<p>9.可以预约上一次成功运行时的任务分配:上次task分配了什么资源,这次还是使用这些资源
CDO很多任务期待重启后,仍使用老的节点,端口</p>
<p>10.Spout nextTuple和ack/fail运行在不同线程</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img4.png" alt="4" /></p>
<h2 id="高级功能">高级功能</h2>
<h3 id="jstorm-任务的动态伸缩">JStorm 任务的动态伸缩</h3>
<p>动态调整包括Task和Worker两个维度,</p>
<ul>
<li>Task维度: Spout, Bolt,Acker并发数的动态调整</li>
<li>Worker维度: Worker数的动态调整</li>
</ul>
<p>JStorm rebalance</p>
<p>USAGE: jstorm rebalance [-r] TopologyName [DelayTime] [NewConfig]
e.g. jstorm rebalance TestTopology conf.yaml</p>
<p>参数说明:
-r: 对所有task做重新调度
TopologyName: Topology任务的名字
DelayTime: 开始执行动态调整的延迟时间
NewConfig: Spout, Bolt, Acker, Worker的新配置(当前仅支持yaml格式的配置文件)</p>
<p>配置文件例子</p>
<p>topology.workers : 1
topology.acker.executors : 1</p>
<p>topology.spout.parallelism:
TestSpout : 2
topology.bolt.parallelism:
TestBolt : 4</p>
<p>【平衡之前】:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>topologyBuilder.setBolt(bolt, new TestBolt(),2).localOrShuffleGrouping(spout);
config.setNumWorkers(1);
</code></pre></div></div>
<p>启动一个worker,两个TestBolt task</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img5.png" alt="5" /></p>
<p>【平衡之后】(Bolt task个数由2个变更为4个,Spout task有1个变为2个):</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img6.png" alt="6" /></p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img7.png" alt="7" /></p>
<h3 id="jstorm-支持动态更新配置文件">Jstorm 支持动态更新配置文件</h3>
<p>用户事先实现好的接口update()决定的。当用户提交动态更新配置文件的命令后,该函数会被回调。更新配置文件是以component为级别的,每个component都有自己的update,如果哪个component不需要实现配置文件动态更新,那它就无需继续该接口。</p>
<p>命令行方式</p>
<p>USAGE: jstorm update_topology TopologyName -conf configPath
e.g. jstorm update_topology TestTopology –conf conf.yaml
参数说明:
TopologyName: Topology任务的名字
configPath: 配置文件名称(暂时只支持yaml格式)</p>
<p>示例:</p>
<p>spout需要继承IDynamicComponent接口</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public void nextTuple() {
collector.emit(new Values("abc"), UUID.randomUUID().toString());
logger.info("############# test_update_topology value is : {}", this.conf.get("test_update_topology"));
JStormUtils.sleepMs(2000);
}
......
@Override
public void update(Map conf) {
this.conf.put("test_update_topology", "2");
logger.info("############# test_update_topology value has been modified to : {}", this.conf.get("test_update_topology"));
}
</code></pre></div></div>
<p>执行更新命令:./jstorm update_topology TestTopology -conf ../jar/storm.yaml,通过日志可以看出更新前后的变化</p>
<p><img src="/images/zhaojiajun/2017-07-02-storm-3img8.png" alt="8" /></p>
mysql的批量入库
2017-07-02T00:00:00+00:00
http://www.blogways.net/blog/2017/07/02/mysql-1
<h2 id="应用场景">应用场景</h2>
<p>批量入库适用于实时性要求不是很高,但一批入库数据量很大的情况。</p>
<p>有时候批量数据类型并不都是文件类型,比如:计费流程中从Kafka获取到的list<String>类型,如果将list或map等类型的数据转换成文件之后再入库,这种方法效率大大降低。</String></p>
<h2 id="mysql批量入库语法">mysql批量入库语法</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>LOAD DATA [LOW_PRIORITY | CONCURRENT] [LOCAL] INFILE 'file_name.txt'
[REPLACE | IGNORE]
INTO TABLE tbl_name
[FIELDS
[TERMINATED BY 'string']
[[OPTIONALLY] ENCLOSED BY 'char']
[ESCAPED BY 'char' ]
]
[LINES
[STARTING BY 'string']
[TERMINATED BY 'string']
]
[IGNORE number LINES]
[(col_name_or_user_var,...)]
[SET col_name = expr,...)]
</code></pre></div></div>
<p><strong>该处主要讲解一下LOCAL关键字的含义,其他不做讲解。</strong></p>
<p><strong>如果指定了LOCAL,则被认为与连接的客户端有关:</strong></p>
<p>1.如果指定了LOCAL,则文件会被客户主机上的客户端读取,并被发送到服务器。文件会被给予一个完整的路径名称,以指定确切的位置。如果给定的是一个相对的路径名称,则此名称会被理解为相对于启动客户端时所在的目录。</p>
<p>2.如果LOCAL没有被指定,则文件必须位于服务器主机上,并且被服务器直接读取。</p>
<p>当在服务器主机上为文件定位时,服务器使用以下规则:</p>
<p>1)如果给定了一个绝对的路径名称,则服务器使用此路径名称。</p>
<p>2)如果给定了带有一个或多个引导组件的相对路径名称,则服务器会搜索相对于服务器数据目录的文件。</p>
<p>3)如果给定了一个不带引导组件的文件名称,则服务器会在默认数据库的数据库目录中寻找文件。</p>
<blockquote>
<p><strong>1.IGNORE忽略 情形</strong></p>
</blockquote>
<p><em>使用IO流的方式直接将流数据导入mysql</em></p>
<p><strong>批量入库源码</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* 批量导入mysql
* @param conn 数据库连接
* @param tableName 数据库表名
* @param dataList 数据
* @param coverType 覆盖类型
* @param fieldSplit 字段分隔符
* @param fields 如:(field1, field3) 定义导入字段
* @param SQLException 设定文件
* @return void 返回类型
*/
public static void loadFile(Connection conn, String tableName, List<String> dataList, String coverType, String fieldSplit, String fields) throws SQLException {
StringBuffer loadDataSql = new StringBuffer();
loadDataSql.append("LOAD DATA LOCAL INFILE 'sql.csv' ");
loadDataSql.append(coverType);
loadDataSql.append(" INTO TABLE ");
loadDataSql.append(tableName);
loadDataSql.append(" FIELDS TERMINATED BY '");
loadDataSql.append(fieldSplit);
loadDataSql.append("' LINES TERMINATED BY '");
loadDataSql.append(lineSplit);
loadDataSql.append("' ");
loadDataSql.append(fields);
System.out.println(loadDataSql.toString());
conn.setAutoCommit(false);
PreparedStatement pt = conn.prepareStatement(loadDataSql.toString());
com.mysql.jdbc.PreparedStatement sqlStatement = null;
if (pt.isWrapperFor(com.mysql.jdbc.Statement.class)) {
sqlStatement = pt.unwrap(com.mysql.jdbc.PreparedStatement.class);
}
StringBuilder sb = new StringBuilder();
for(String line : dataList){
sb.append(line);
sb.append(lineSplit);
}
byte[] bytes = sb.toString().getBytes();
InputStream in = new ByteArrayInputStream(bytes);
sqlStatement.setLocalInfileInputStream(in);
int rows = sqlStatement.executeUpdate();
try {
sqlStatement.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("importing "+rows+" rows data into mysql ");
}
</code></pre></div></div>
<p>(1)【操作】 针对4个字段的批量入库,field1为tb_test表的主键</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Connection conn = CdrDBMgr.getConnection("xx");
List<String> dataList = new ArrayList<String>();
for(int i=0;i<5;i++){
String s = "A"+i+",b,c,d";
dataList.add(s);
}
String fieldSql = "(field1,field2,field3,field4)";
System.out.println(DataStore2.storeCdr(conn, "tb_test", dataList, fieldSql));
</code></pre></div></div>
<p>【SQL】 LOAD DATA LOCAL INFILE ‘sql.csv’ IGNORE INTO TABLE tb_test FIELDS TERMINATED BY ‘,’ LINES TERMINATED BY ‘’ (field1,field2,field3,field4)</p>
<p>【结果】</p>
<p><img src="/images/zhaojiajun/2017-07-02-mysql-1img1.png" alt="1" /></p>
<p>(2)【操作】 修改 for(int i=0;i<7;i++)…String s = “A”+i+”,bb,cc,dd”; 预期结果是已有数据不会变化,新插入两条数据</p>
<p>【结果】</p>
<p><img src="/images/zhaojiajun/2017-07-02-mysql-1img2.png" alt="2" /></p>
<p>(3)【操作】 修改 String fieldSql = “(field1,@2,field3,field4,@5) set field2=concat(@2,10),field6=str_to_date(@5,’%Y-%m-%d %H:%i:%s’)”;</p>
<p>【SQL】 LOAD DATA LOCAL INFILE ‘sql.csv’ IGNORE INTO TABLE tb_test FIELDS TERMINATED BY ‘,’ LINES TERMINATED BY ‘
‘ (field1,@2,field3,field4,@5) set field2=concat(@2,10),field6=str_to_date(@5,’%Y-%m-%d %H:%i:%s’)</p>
<p>【结果】</p>
<p><img src="/images/zhaojiajun/2017-07-02-mysql-1img3.png" alt="3" /></p>
<blockquote>
<p><strong>2.REPLACE覆盖 情形</strong></p>
</blockquote>
<p><em>使用IO流的方式直接将流数据导入mysql</em></p>
<p>(1)【操作】 针对4个字段的批量入库,field1为tb_test表的主键</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Connection conn = CdrDBMgr.getConnection("xx");
List<String> dataList = new ArrayList<String>();
for(int i=0;i<3;i++){
String s = "A"+i+",b,c,d,2008-4-2 15:3:28,f,g";
dataList.add(s);
}
String fieldSql = "(field1,field2,field3,field4)";
System.out.println(DataStore2.replaceCdr(conn, "tb_test", dataList, fieldSql));
</code></pre></div></div>
<p>【SQL】 LOAD DATA LOCAL INFILE ‘sql.csv’ REPLACE INTO TABLE tb_test FIELDS TERMINATED BY ‘,’ LINES TERMINATED BY ‘’ (field1,field2,field3,field4)</p>
<p>【结果】 [ WARN] batch storage failure: Row 1 was truncated; it contained more data than there were input columns</p>
<p>说明:使用关键字时,入库数据字段必须与定义的数据库字段个数一致,否则报错</p>
<p>上面问题修改 String fieldSql = “(field1,@2,field3,field4,@5,@6,@7)”;</p>
<p><img src="/images/zhaojiajun/2017-07-02-mysql-1img4.png" alt="4" /></p>
<p>原因分析:</p>
<p><strong>从打印的日志看 importing 6 rows data into mysql,入库了3条记录但是数据库执行了6次,实际上REPLACE情形下是通过先delete后再执行insert的方法,所以不导入的字段会变为空,这一点使用时需要特别注意。</strong></p>
<blockquote>
<p><strong>3.UPDATE更新 情形</strong></p>
</blockquote>
<p>**批量入库源码 **</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* 批量更新mysql
* @param conn 数据库连接
* @param tableName 数据库表名
* @param dataList 数据
* @param fields 如:(field1, field3) 定义更新字段
* @param SQLException 设定文件
* @return void 返回类型
*/
public static void loadFile(Connection conn, String tableName, List<String> dataList, String fields) throws SQLException {
StringBuffer loadDataSql = new StringBuffer();
loadDataSql.append("insert into ");
loadDataSql.append(tableName);
loadDataSql.append(fields);
loadDataSql.append(" values ");
for(int i=0;i<dataList.size();i++){
loadDataSql.append("(");
loadDataSql.append(dataList.get(i));
loadDataSql.append(")");
if(i != dataList.size()-1){
loadDataSql.append(",");
}
}
loadDataSql.append(" on duplicate key update ");
loadDataSql.append(" field2=values(field2), field3=values(field3), field4=values(field4)");
System.out.println(loadDataSql.toString());
conn.setAutoCommit(false);
PreparedStatement pt = conn.prepareStatement(loadDataSql.toString());
System.out.println(pt.execute());
}
</code></pre></div></div>
<p>(1)【操作】 针对4个字段的批量入库,field1为tb_test表的主键</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Connection conn = CdrDBMgr.getConnection("xx");
List<String> dataList = new ArrayList<String>();
for(int i=0;i<5;i++){
String s = "'A"+i+"','bxx','cxx','dxx'";
dataList.add(s);
}
String fieldSql = "(field1,field2,field3,field4)";
System.out.println(DataStore2.updateCdr(conn, "tb_test", dataList, fieldSql));
</code></pre></div></div>
<p>【SQL】</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>insert into tb_test(field1,field2,field3,field4) values
('A0','bxx','cxx','dxx'),
('A1','bxx','cxx','dxx'),
('A2','bxx','cxx','dxx'),
('A3','bxx','cxx','dxx'),
('A4','bxx','cxx','dxx')
on duplicate key update field2=values(field2), field3=values(field3), field4=values(field4)
</code></pre></div></div>
<p>【结果】</p>
<p><img src="/images/zhaojiajun/2017-07-02-mysql-1img5.png" alt="5" /></p>
<p>原有的字段field6中有两行值并没有被置为空,可以满足业务需要,但该方法效率比REPLACE略慢,需要结合情形使用。</p>
<p>另外:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- 可以只更新部分字段
insert into tb_test(field1,field2,field3,field4)
values ('A0','b1','c1','d1'),('A1','b1','c1','1')
on duplicate key update field2=values(field2)
-- 同样支持函数表达式
insert into tb_test(field1,field2,field3,field4)
values ('A0','b11','c11','2008-4-1 15:3:21'),('A1','b1','c1','2008-4-2 15:3:26')
on duplicate key update field2=values(field2), field3=values(field3), field6=str_to_date(values(field4),'%Y-%m-%d %H:%i:%s')
</code></pre></div></div>
JVM 基本结构
2017-07-02T00:00:00+00:00
http://www.blogways.net/blog/2017/07/02/jvm-memory
<h2 id="一虚拟机的基本结构">一、虚拟机的基本结构</h2>
<p>Java 虚拟机的基本结构如图:</p>
<p><img src="/images/jyjsjd/jvm.png" alt="jvm.png" /></p>
<p><strong>绿色代表所有线程共享区域,黄色代表每个线程的私有区域</strong></p>
<hr />
<h3 id="1方法区或者叫永久代包括类信息和运行时常量信息等">1、方法区:或者叫<code class="language-plaintext highlighter-rouge">永久代</code>,包括类信息和运行时常量信息等。</h3>
<ul>
<li>类信息:从文件或网络系统中加载的 class 信息。</li>
<li>静态变量、即时编译器编译后的代码。</li>
<li>运行时常量池:包括字符串字面量、数字常量。
<ul>
<li>整数类型会提前缓存<code class="language-plaintext highlighter-rouge">[-128,127]</code>之间的数字。</li>
<li>String的<code class="language-plaintext highlighter-rouge">intern()</code>方法会去常量池找相同的字符串并返回。</li>
</ul>
</li>
</ul>
<hr />
<h3 id="2java-堆是-java-最主要的内存工作区域几乎所有的对象实例及数组都在堆上分配">2、Java 堆:是 Java 最主要的内存工作区域,几乎所有的<code class="language-plaintext highlighter-rouge">对象实例</code>及<code class="language-plaintext highlighter-rouge">数组</code>都在堆上分配。</h3>
<h3 id="3java-栈是线程私有的描述的是java-方法-执行的内存模型保存着局部变量表操作数栈帧数据区等信息">3、Java 栈:是线程私有的。描述的是<code class="language-plaintext highlighter-rouge">Java 方法</code> 执行的内存模型,保存着局部变量表、操作数栈、帧数据区等信息。</h3>
<h3 id="4本地方法栈为-native-方法的执行服务">4、本地方法栈:为 <code class="language-plaintext highlighter-rouge">Native 方法</code>的执行服务。</h3>
<h3 id="5程序计数器主要代表当前线程所执行的字节码行号指示器字节码解释器工作时通过改变这个计数器的值来选取下一条需要执行的字节码指令">5、程序计数器:主要代表<code class="language-plaintext highlighter-rouge">当前线程</code>所执行的<code class="language-plaintext highlighter-rouge">字节码行号指示器</code>。字节码解释器工作时,通过改变这个计数器的值来选取<code class="language-plaintext highlighter-rouge">下一条</code>需要执行的<code class="language-plaintext highlighter-rouge">字节码指令</code>。</h3>
<h2 id="二java-栈">二、Java 栈</h2>
<p>Java 栈是一块<code class="language-plaintext highlighter-rouge">先进后出</code>的数据结构,方法只有<code class="language-plaintext highlighter-rouge">入栈</code>和<code class="language-plaintext highlighter-rouge">出栈</code>。</p>
<p>它所操作的对象是<code class="language-plaintext highlighter-rouge">栈帧</code>。每个方法执行的时候都会创建一个<code class="language-plaintext highlighter-rouge">栈帧</code>,它保存着局部变量表、操作数栈、帧数据区等信息。</p>
<p>当方法被调用时,它对应的栈帧就会入栈,当方法返回时,栈帧就会出栈。</p>
<p><img src="/images/jyjsjd/stack.png" alt="stack.png" /></p>
<h2 id="三java-堆">三、Java 堆</h2>
<p>几乎所有的对象都放在 Java 堆中。堆是自动管理的,通过垃圾回收,对象会被自动释放。</p>
<p><img src="/images/jyjsjd/heap.png" alt="heap.png" /></p>
<hr />
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>堆大小 = 新生代 + 老年代
新生代 = eden + from survivor + to survivor
eden : from : to = 8 : 1 : 1
new : old = 2 : 1
(比例是可以通过参数调整的)
</code></pre></div></div>
<ul>
<li>eden(伊甸园):大多数新对象会在 eden 中产生,但是大对象会直接进入老年代。“朝生夕死” 的对象占大多数,很多新对象用完一次就可以被回收。</li>
<li>from/to survivor(幸存者):JVM 仅会使用 eden 和一个幸存者区(这里假设是 from)进行对象分配,另一个幸存者(这里假设是 to)区则会保持清空。在一次 GC(minor GC)之后,幸存的对象会被复制到 to 区域。如果在幸存者区(from)中的对象年龄达到一定阈值就会直接进入老年代; 下次对象分配的时候,eden 和 to 会被使用,而 from 保持清空,循环上一步的过程。</li>
<li>老年代:老年代发生的 GC 是 Full GC(或者叫 stop the world),这里的 GC 动作不像新生代那么频繁。在每次 minor GC 之前,系统会判断老年代的空闲连续空间是否大于新生代所有对象之和,如果这个条件成立,那么MinorGC可以确保是安全的。如果不成立,则虚拟机会查看 HandlePromotionFailure 设置值是否允许担保失败。如果允许,那么会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,如果大于,将尝试这进行一次MinorGC,尽管这次MinorGC 是有风险的;如果小于,或者 HandlePromptionFailure 设置不允许冒险,那这是也要改为进行一次Full GC。</li>
</ul>
quartz时间调度xml配置
2017-07-01T00:00:00+00:00
http://www.blogways.net/blog/2017/07/01/quartz-2
<h2 id="使用xml配置文件加载quartz">使用xml配置文件加载quartz</h2>
<p>在上节介绍中并没有用到任何的配置文件,而在我们项目工程中常使用xml来加载。Quartz支持配置文件,它的好处是比编写代码简单,且修改后不需要重新编译源码</p>
<ul>
<li>配置quartz.properties文件</li>
</ul>
<p>该文件来配置quartz的各种属性,包括线程数,线程优先级,加载的job文件,扫描的时间间隔等一系列属性</p>
<p>新建quartz.properties在项目的classpath下,加载必要的jar包</p>
<p>项目代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#============================================================================
# Configure Main Scheduler Properties
#============================================================================
# 实例名
org.quartz.scheduler.instanceName = QuartzScheduler
# 实例ID
org.quartz.scheduler.instanceId = AUTO
#============================================================================
# Configure ThreadPool
#============================================================================
org.quartz.threadPool.class = org.quartz.simpl.SimpleThreadPool
# 线程个数
org.quartz.threadPool.threadCount = 3
org.quartz.threadPool.threadPriority = 5
#============================================================================
# Configure JobStore
#============================================================================
org.quartz.jobStore.misfireThreshold = 60000
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
#============================================================================
# Configure Plugins
#============================================================================
org.quartz.plugin.triggHistory.class = org.quartz.plugins.history.LoggingJobHistoryPlugin
# org.quartz.plugins.xml.JobInitializationPlugin是Quartz自带的插件,
# 默认时,这个插件会在 classpath 中搜索名为 quartz_jobs.xml
# 的文件并从中加载 Job 和 Trigger 信息
# v1.8之前用JobInitializationPlugin
#org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.JobInitializationPlugin
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames = quartz_jobs.xml
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.scanInterval =10
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
# 关闭quartz新版本检测功能
org.quartz.scheduler.skipUpdateCheck = true
</code></pre></div></div>
<ul>
<li>配置job.xml</li>
</ul>
<p>加载触发器类型,定义触发器属性等一系列jobDetail操作</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xmlversion="1.0"encoding="UTF-8"?>
<job-scheduling-dataxmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.ogr/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<pre-processing-commands>
<!--在执行作业和触犯器之前执行的命令-->
<delete-jobs-in-group>*</delete-jobs-in-group>
<!--删除标示组中的所有作业,如果是“*”,则删除所有组中的作业,同时也会删除与作业有关的触犯器 -->
<delete-triggers-in-group>*</delete-triggers-in-group>
<!--删除标示组中的所有触犯器,如果是“*”,则删除所有组中的触发器 -->
<delete-job>
<!--删除指定的作业,同时也会删除与它关联的触犯器 -->
<name></name>
<group></group>
</delete-job>
<delete-trigger>
<!--删除指定的触犯器 -->
<name></name>
<group></group>
</delete-trigger>
</pre-processing-commands>
<processing-directives>
<!--在计划作业和触发器是应遵循的命令和原则 -->
<overwrite-existing-data>true or false</overwrite-existing-data>
<!--是否复写已经存在的任务计划数据,如果为false并且ingore-duplicates非false,那么文件中同名的触发器或作业将会继续存在,则会产生错误-->
<ignore-duplicates>true or false</ignore-duplicates>
<!--如果为true,计划中的任何同名的作业/触发器将会被忽略,不会产生错误-->
</processing-directives>
<schedule>
<job>
<name>JobName</name>
<group>JobGroup</group>
<description></description>
<job-class></job-class>
<job-listener-ref></job-listener-ref>
<!-- volatility,durability,recover必须按顺序设定 -->
<volatility></volatility>
<durability></durability>
<recover></recover>
<job-data-map>
<!-- entry可以设定多个-->
<entry>
<key></key>
<value></value>
</entry>
</job-data-map>
</job>
<trigger>
<!-- Trigger分为simple,cron,date-interval三种类型,一个trigger中只能指定一种类型-->
<simple>
<name></name>
<group></group>
<description></description>
<job-name></job-name>
<job-group></job-group>
<calendar-name></calendar-name>
<volatility></volatility>
<job-data-map>
<entry>
<key></key>
<value></value>
</entry>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfire-instruction>
<repeat-count></repeat-count>
<repeat-interval></repeat-interval>
</simple>
<cron>
<name></name>
<group></group>
<description></description>
<job-name></job-name>
<job-group></job-group>
<calendar-name></calendar-name>
<volatility></volatility>
<job-data-map>
<entry>
<key></key>
<value></value>
</entry>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfire-instruction>
<cron-expression></cron-expression>
<time-zone></time-zone>
</cron>
<date-interval>
<name></name>
<group></group>
<description></description> <job-group></job-group>
</code></pre></div></div>
<volatility></volatility>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <job-data-map>
<entry>
<key></key>
<value></value>
</entry>
</job-data-map>
<start-time></start-time>
<end-time></end-time>
<misfire-instruction></misfire-instruction>
<repeat-interval></repeat-interval>
<repeat-interval-unit></repeat-interval-unit>
</date-interval>
</trigger>
</schedule>
</job-scheduling-data>
</code></pre></div></div>
<p>本例使用的quartz版本为1.8.6,现在已经更新到2.0以上版本</p>
<p>本例quartz_job.xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xmlversion="1.0"encoding="UTF-8"?>
<job-scheduling-dataxmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<pre-processing-commands>
<delete-jobs-in-group>*</delete-jobs-in-group> <!-- clear all jobs in scheduler -->
<delete-triggers-in-group>*</delete-triggers-in-group><!-- clear all triggers in scheduler -->
</pre-processing-commands>
<processing-directives>
<overwrite-existing-data>true</overwrite-existing-data>
<ignore-duplicates>false</ignore-duplicates>
</processing-directives>
<schedule>
<job>
<name>helloQuartzJob</name>
<group>DEFAULT</group>
<description>简单的quartz使用</description>
<job-class>HelloQuartzJob</job-class>
<volatility>false</volatility>
<durability>true</durability>
<recover>false</recover>
</job>
<trigger>
<cron>
<name>trigger</name>
<group>DEFAULT</group>
<job-name>helloQuartzJob</job-name>
<job-group>DEFAULT</job-group>
<cron-expression>30/5 * * * * ?</cron-expression>
</cron>
</trigger>
</schedule>
</job-scheduling-data>
</code></pre></div></div>
<ul>
<li>
<p>建立测试用例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class HelloQuartzScheduling {
@Test
public void testQuartz()
throws SchedulerException, ParseException {
// 加载quartz容器
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
scheduler.start();
}
}
</code></pre></div> </div>
</li>
</ul>
<h2 id="和spring相结合">和spring相结合</h2>
<p>在spring-content里加入bean</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <!--使用MethodInvokingJobDetailFactoryBean,任务类可以不实现Job接口,通过targetMethod指定调用方法-->
<!-- 定义目标bean和bean中的方法 -->
<bean id="SpringQtzJob" class="com.chenfan.server.HelloQuartzJob"/>
<bean id="SpringQtzJobMethod" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean">
<property name="targetObject">
<ref bean="SpringQtzJob"/>
</property>
<property name="targetMethod"> <!-- 要执行的方法名称 -->
<value>execute</value>
</property>
</bean>
<!-- ======================== 调度触发器 ======================== -->
<bean id="CronTriggerBean" class="org.springframework.scheduling.quartz.CronTriggerBean">
<property name="jobDetail" ref="SpringQtzJobMethod"></property>
<property name="cronExpression" value="0/5 * * * * ?"></property>
</bean>
<!-- ======================== 调度工厂 ======================== -->
<bean id="SpringJobSchedulerFactoryBean" class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers">
<list>
<ref bean="CronTriggerBean"/>
</list>
</property>
</bean>
</code></pre></div></div>
<p>在web.xml里加载spring-content.xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-config.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
</code></pre></div></div>
Quartz定时调度
2017-06-28T00:00:00+00:00
http://www.blogways.net/blog/2017/06/28/quartz-1
<h2 id="quartz简介">quartz简介</h2>
<p>Quartz的概念并不是很复杂,简单说就是一个java的定时调度器,我们只需要搞明白几个概念,然后知道如何去开启和关闭一个定时任务</p>
<ul>
<li>Job</li>
</ul>
<p>表示一个工作,要执行的具体内容。此接口中只有一个方法
void execute(JobExecutionContext context)我们要去重写这个方法,来放置自己的任务逻辑。</p>
<ul>
<li>JobDetail</li>
</ul>
<p>JobDetail表示一个具体的可执行的调度程序,Job是这个可执行程调度程序所要执行的内容,另外JobDetail还包含了这个任务调度的方案和策略。</p>
<ul>
<li>Trigger</li>
</ul>
<p>代表一个调度参数的配置,什么时候去调</p>
<ul>
<li>Scheduler</li>
</ul>
<p>代表一个调度容器,一个调度容器中可以注册多个JobDetail和Trigger。当Trigger与JobDetail组合,就可以被Scheduler容器调度了</p>
<ul>
<li>Calendar</li>
</ul>
<p>它是一些日历特定时间点的集合(可以简单地将org.quartz.Calendar看作java.util.Calendar的集合。一个Trigger可以和多个Calendar关联, 以便排除或包含某些时间点。</p>
<ul>
<li>ThreadPool</li>
</ul>
<p>Scheduler使用一个线程池作为任务运行的基础设施,任务通过共享线程池中的线程提高运行效率。</p>
<p>下图描述了Scheduler的内部组件结构,SchedulerContext提供Scheduler全局可见的上下文信息,每一个任务都对应一个JobDataMap,虚线表达的JobDataMap表示对应有状态的任务:</p>
<p><img src="/images/chenfan/quartz1.png" alt="quartz1" /></p>
<h2 id="quartz简单例子">quartz简单例子</h2>
<p>1.引入maven</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>1.8.6</version>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>jta</artifactId>
<version>1.1</version>
</dependency>
</code></pre></div></div>
<p>2.创建job任务</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.chenfan.service;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Job;
import java.util.Date;
public class HelloQuartzJob implements Job{
public void execute(JobExecutionContext context)
throws JobExecutionException {
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss E");
System.out.println("Hello, Quartz! - executing its JOB at "+ dateFormat.format(new Date()) + " by " + context.getTrigger().getCalendarName());
}
}
</code></pre></div></div>
<p>3.调度任务</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.sql.Date;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.SimpleTrigger;
import org.quartz.impl.StdSchedulerFactory;
public class HelloQuartzScheduling {
public static void main(String[] args)throws SchedulerException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler(); //创建scheduler容器
JobDetail jobDetail = new JobDetail("helloQuartzJob",
Scheduler.DEFAULT_GROUP, HelloQuartzJob.class); // 加载可执行调度程序
SimpleTrigger simpleTrigger = new SimpleTrigger("simpleTrigger",
Scheduler.DEFAULT_GROUP); // 加载触发器,这里是简单触发器
simpleTrigger.setStartTime(new Date(System.currentTimeMillis()));
simpleTrigger.setRepeatInterval(5000); // 5s执行一次
simpleTrigger.setRepeatCount(10); // 重复执行10次
scheduler.scheduleJob(jobDetail, simpleTrigger);
scheduler.start();
}
}
</code></pre></div></div>
<p>4.执行结果</p>
<p><img src="/images/chenfan/quartz2.png" alt="quartz2" /></p>
<h2 id="trigger触发器">Trigger触发器</h2>
<p>Quartz有两大触发器,除了上面使用的SimpleTrigger外,就是CronTrigger。CronTrigger能够提供复杂的触发器表达式的支持。CronTrigger是基于Unix Cron守护进程,它是一个调度程序,支持简单而强大的触发器语法。</p>
<p>使用CronTrigger主要的是要掌握Cron表达式。Cron表达式包含6个必要组件和一个可选组件,如下表所示。</p>
<p><img src="/images/chenfan/quartz3.png" alt="quartz3" /></p>
<p>特殊字符含义见下表</p>
<p><img src="/images/chenfan/quartz4.png" alt="quartz4" /></p>
<p>Cron表达式举例</p>
<p>“30 * * * * ?” 每半分钟触发任务</p>
<p>“30 10 * * * ?” 每小时的10分30秒触发任务</p>
<p>“30 10 1 * * ?” 每天1点10分30秒触发任务</p>
<p>“30 10 1 20 * ?” 每月20号1点10分30秒触发任务</p>
<p>“30 10 1 20 10 ? * “ 每年10月20号1点10分30秒触发任务</p>
<p>“30 10 1 20 10 ? 2011” 2011年10月20号1点10分30秒触发任务</p>
<p>“30 10 1 ? 10 * 2011” 2011年10月每天1点10分30秒触发任务</p>
<p>“30 10 1 ? 10 SUN 2011” 2011年10月每周日1点10分30秒触发任务</p>
<p>“15,30,45 * * * * ?” 每15秒,30秒,45秒时触发任务</p>
<p>“15-45 * * * * ?” 15到45秒内,每秒都触发任务</p>
<p>“15/5 * * * * ?” 每分钟的每15秒开始触发,每隔5秒触发一次</p>
<p>“15-30/5 * * * * ?” 每分钟的15秒到30秒之间开始触发,每隔5秒触发一次</p>
<p>“0 0/3 * * * ?” 每小时的第0分0秒开始,每三分钟触发一次</p>
<p>“0 15 10 ? * MON-FRI” 星期一到星期五的10点15分0秒触发任务</p>
<p>“0 15 10 L * ?” 每个月最后一天的10点15分0秒触发任务</p>
<p>“0 15 10 LW * ?” 每个月最后一个工作日的10点15分0秒触发任务</p>
<p>“0 15 10 ? * 5L” 每个月最后一个星期四的10点15分0秒触发任务</p>
<p>“0 15 10 ? * 5#3” 每个月第三周的星期四的10点15分0秒触发任务</p>
<p><strong>将上面HelloQuartz例子中SimpleTrigger换成CronTrigger,代码如下。</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class HelloQuartzScheduling {
public static void main(String[] args) throws SchedulerException, ParseException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = new JobDetail("helloQuartzJob",
Scheduler.DEFAULT_GROUP, HelloQuartzJob.class);
String cronExpression = "30/4 * * * * ? 2017";
CronTrigger cronTrigger = new CronTrigger("cronTrigger",
Scheduler.DEFAULT_GROUP, cronExpression);
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
//scheduler.shutdown(true);
}
}
</code></pre></div></div>
<h2 id="排除一段时间">排除一段时间</h2>
<p>使用Calender可以排除一些不需要定时的日期</p>
<ul>
<li>
<p>CronCalendar:使用表达式排除某些时间段不执行</p>
</li>
<li>
<p>DailyCalendar:指定的时间范围内的每一天不执行</p>
</li>
<li>
<p>HolidayCalendar:排除节假日</p>
</li>
<li>
<p>MonthlyCalendar:排除月份中的数天</p>
</li>
<li>
<p>WeeklyCalendar:排除星期中的一天或多天</p>
</li>
</ul>
<p>例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class HelloQuartzScheduling {
public static void main(String[] args)
throws SchedulerException, ParseException {
SchedulerFactory schedulerFactory = new StdSchedulerFactory();
Scheduler scheduler = schedulerFactory.getScheduler();
JobDetail jobDetail = new JobDetail("helloQuartzJob",
Scheduler.DEFAULT_GROUP, HelloQuartzJob.class);
Calendar cal = Calendar.getInstance();
cal.set(2017, Calendar.OCTOBER, 1); // 国庆节
HolidayCalendar holidayCal = new HolidayCalendar();
holidayCal.addExcludedDate(cal.getTime()); // 排除该日期
// addCalendar(String calName, Calendar calendar,
// boolean replace, boolean updateTriggers)
scheduler.addCalendar("calendar", holidayCal, true, false);
String cronExpression = "30/5 * * * * ?"; // 每5s触发任务
CronTrigger cronTrigger = new CronTrigger("cronTrigger",
Scheduler.DEFAULT_GROUP, cronExpression);
cronTrigger.setCalendarName("calendar");
scheduler.scheduleJob(jobDetail, cronTrigger);
scheduler.start();
}
}
</code></pre></div></div>
<h2 id="jobstore-任务持久化">JobStore: 任务持久化</h2>
<p>Quartz支持任务持久化,这可以让你在运行时增加任务或者对现存的任务进行修改,并为后续任务的执行持久化这些变更和增加的部分。中心概念是JobStore接口。默认的是RAMJobStore,它的优点是速度。因为所有的 Scheduler 信息都保存在计算机内存中,访问这些数据随着电脑而变快。而无须访问数据库或IO等操作,但它的缺点是将 Job 和 Trigger 信息存储在内存中的。因而我们每次重启程序,Scheduler 的状态,包括 Job 和 Trigger 信息都丢失了</p>
<p><img src="/images/chenfan/quartz5.png" alt="quartz5" /></p>
dubbo框架简单使用-服务提供者和消费者的创建和使用
2017-06-26T00:00:00+00:00
http://www.blogways.net/blog/2017/06/26/dubbo简单实例创建
<p>本dubbo实例是基于zookeeper-3.4.6搭建,下面首先进行zookeeper环境的搭建。</p>
<h3 id="一zookeeper安装和使用-windows环境">一、zookeeper安装和使用 windows环境</h3>
<blockquote>
<p><strong>下载</strong></p>
</blockquote>
<p>本实例下载的zookeeper版本为3.4.6,下载地址为: <a href="http://apache.fayea.com/zookeeper/zookeeper-3.4.6/">http://apache.fayea.com/zookeeper/zookeeper-3.4.6/</a></p>
<blockquote>
<p><strong>安装</strong></p>
</blockquote>
<p>解压到指定目录下 D:\Tools2\zookeeper-3.4.6</p>
<p>修改zoo_sample.cfg 文件名(D:\Tools2\zookeeper-3.4.6) 为 zoo.cfg
主要修改一下日志位置,具体配置文件如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=10
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=5
# the directory where the snapshot is stored.
dataDir=D:\\Tools2\\zookeeper-3.4.6\\data
dataLogDir=D:\\Tools2\\zookeeper-3.4.6\\log
# the port at which the clients will connect
clientPort=2181
</code></pre></div></div>
<p>同时,在D:\Tools2\zookeeper-3.4.6目录下分别新建data和log两个文件夹。
配置文件简单解析:</p>
<ul>
<li><strong>tickTime</strong>:这个时间是作为 Zookeeper 服务器之间或客户端与服务器之间维持心跳的时间间隔,也就是每个 tickTime 时间就会发送一个心跳。</li>
<li><strong>dataDir</strong>:顾名思义就是 Zookeeper 保存数据的目录,默认情况下,Zookeeper 将写数据的日志文件也保存在这个目录里。</li>
<li><strong>dataLogDir</strong>:顾名思义就是 Zookeeper 保存日志文件的目录</li>
<li><strong>clientPort</strong>:这个端口就是客户端连接 Zookeeper 服务器的端口,Zookeeper 会监听这个端口,接受客户端的访问请求。</li>
</ul>
<p>ok , 到现在zookeeper的简易环境搭建完毕。</p>
<blockquote>
<p><strong>启动</strong></p>
</blockquote>
<p>进入到bin目录,并且启动zkServer.cmd,这个脚本中会启动一个java进程。</p>
<p><img src="/images/chenlong/zkServer.png" alt="zkServer.png" /></p>
<h3 id="二dubbo-admin的部署">二、dubbo-admin的部署</h3>
<p>首先先看一下dubbo的实现原理图:
<img src="/images/chenlong/dubbo.png" alt="dubbo.png" />
节点角色说明:</p>
<ul>
<li>Provider: 暴露服务的服务提供方。</li>
<li>Consumer: 调用远程服务的服务消费方。</li>
<li>Registry: 服务注册与发现的注册中心。</li>
<li>Monitor: 统计服务的调用次调和调用时间的监控中心。</li>
<li>Container: 服务运行容器。</li>
</ul>
<p>调用关系说明:</p>
<ol>
<li>(start)服务容器负责启动,加载,运行服务提供者。</li>
<li>(register)服务提供者在启动时,向注册中心注册自己提供的服务。</li>
<li>(subscribe)服务消费者在启动时,向注册中心订阅自己所需的服务。</li>
<li>(notify)注册中心返回服务提供者地址列表给消费者,如果有变更,注册中心将基于长连接推送变更数据给消费者。</li>
<li>(invoke) 服务消费者,从提供者地址列表中,基于软负载均衡算法,选一台提供者进行调用,如果调用失败,再选另一台调用。</li>
<li>(count)服务消费者和提供者,在内存中累计调用次数和调用时间,定时每分钟发送一次统计数据到监控中心。</li>
</ol>
<p>dubbo-admin的下载,可自行到官网下载,本例子下载的为dubbo-master.zip,并自行打包dubbo-admin。</p>
<p>解压dubbo-master.zip之后进入dubbo-admin的文件夹,并在此文件夹下打开cmd命令窗口,使用mvn进行打包,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install -Dmaven.test.skip=true
</code></pre></div></div>
<p>然而,出现报错:</p>
<p><img src="/images/chenlong/mvn1.png" alt="mvn1.png" />
这是由于com.alibaba:dubbo-admin:war:2.5.4-SNAPSHOT找不到导致报错,为此,我们做版本降级处理,在pom.xml文件中将dubbo的版本修改为2.5.3</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
</dependency>
</code></pre></div></div>
<p>在运行上述命令,成功打出war包:</p>
<p><img src="/images/chenlong/mvn2.png" alt="mvn2.png" />
打包成功之后,就会发现dubbo-admin下多了个target文件夹,打开target文件夹,发现里面有个war包:</p>
<p><img src="/images/chenlong/mvn3.png" alt="mvn3.png" /></p>
<p>将此war包拷贝到tomcat的webapps文件夹中,启动tomcat后,打开浏览器输入http://localhost:9999/dubbo-admin-2.5.4-SNAPSHOT/,会出现如下弹框:</p>
<p><img src="/images/chenlong/dubbo2.png" alt="dubbo2.png" /></p>
<p>用户名和密码都输入root进入</p>
<p><img src="/images/chenlong/dubbo3.png" alt="dubbo3.png" /></p>
<p>到此,dubbo-admin可以正常运行了。</p>
<h3 id="三消费者和服务使用者的创建">三、消费者和服务使用者的创建</h3>
<h2 id="1服务接口创建">1、服务接口创建</h2>
<blockquote>
<p>新建一个maven工程</p>
</blockquote>
<p><img src="/images/chenlong/mvn_interface.png" alt="mvn_interface.png" /></p>
<blockquote>
<p><strong>创建一个服务接口</strong></p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.cl.dubbo_Interface;
public interface DemoService {
public void sayHello(String content);
}
</code></pre></div></div>
<blockquote>
<p><strong>运行 clean install打包</strong></p>
</blockquote>
<p><img src="/images/chenlong/mvn4.png" alt="mvn4.png" /></p>
<h2 id="2服务使用者创建">2、服务使用者创建</h2>
<blockquote>
<p>新建一个maven工程</p>
</blockquote>
<p><img src="/images/chenlong/mvn5.png" alt="mvn5.png" /></p>
<blockquote>
<p><strong>在pom.xml里面引用刚才的服务接口的jar包,同时引入spring等的使用到的相关jar包</strong></p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>com.cl</groupId>
<artifactId>dubbo_Interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
</dependency>
<!-- 连接zookeeper的客户端 -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
</code></pre></div></div>
<blockquote>
<p><strong>编写服务实现类</strong></p>
</blockquote>
<p><img src="/images/chenlong/dubbo_p.png" alt="dubbo_p.png" /></p>
<blockquote>
<p>在resource目录下编写配置文件</p>
</blockquote>
<p><img src="/images/chenlong/dubbo_px.png" alt="dubbo_px.png" />
<img src="/images/chenlong/spring-context.png" alt="spring-context.png" /></p>
<p>log4j.properties配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># Configure logging for testing: optionally with log file
log4j.rootLogger=WARN, stdout
# log4j.rootLogger=WARN, stdout, logfile
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=target/spring.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d %p [%c] - %m%n
</code></pre></div></div>
<p>到此,先在src/test/java路径下编写测试类启动dubbo服务</p>
<p><img src="/images/chenlong/dubbo_tp.png" alt="dubbo_tp.png" /></p>
<p>运行测试类,在dubbo控制台看到我们暴露的服务</p>
<p><img src="/images/chenlong/dubbo_pT.png" alt="dubbo_pT.png" /></p>
<h2 id="3消费者创建">3、消费者创建</h2>
<blockquote>
<p>新建一个maven工程</p>
</blockquote>
<p><img src="/images/chenlong/mvn6.png" alt="mvn6.png" /></p>
<blockquote>
<p><strong>在pom.xml里面引用刚才的服务接口的jar包,同时引入使用到的相关jar包</strong></p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>com.cl</groupId>
<artifactId>dubbo_Interface</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.5.3</version>
</dependency>
<!-- 连接zookeeper的客户端 -->
<dependency>
<groupId>com.github.sgroschupf</groupId>
<artifactId>zkclient</artifactId>
<version>0.1</version>
</dependency>
</code></pre></div></div>
<blockquote>
<p><strong>编写consumer的测试类</strong></p>
</blockquote>
<p><img src="/images/chenlong/dubbo_ct.png" alt="dubbo_ct.png" /></p>
<blockquote>
<p>运行测试类</p>
</blockquote>
<p><img src="/images/chenlong/dubbo_cc.png" alt="dubbo_cc.png" /></p>
<blockquote>
<p><strong>查看dubbo控制台相关信息</strong></p>
</blockquote>
<p><img src="/images/chenlong/dubbo_cb.png" alt="dubbo_cb.png" /></p>
SpringMVC 整合Tiles框架的简单使用
2017-06-22T00:00:00+00:00
http://www.blogways.net/blog/2017/06/22/SpringMVC+tiles
<h2 id="tiles简介">Tiles简介</h2>
<p><code class="language-plaintext highlighter-rouge">Tiles</code>是一个JSP布局框架,为创建Web页面提供了一种模板机制,它能将网页的布局和内容分离。它用模板定义网页布局,每个页面模板都是一个简单的 JSP 页,它定义了一些由占位符组成的外形,以放置内容。执行时,Tiles 将会用相应的内容来替换占位符,因此,创建整个页面即形成布局。Tiles框架是建立在JSP的include指令基础上的,但它提供了比JSP的include指令更强大的功能。Tiles框架具有如下特性:</p>
<ul>
<li>创建可重用的模板</li>
<li>动态构建和装载页面</li>
<li>定义可重用的Tiles组件</li>
<li>支持国际化</li>
</ul>
<p>Tiles的配置文件中的<tiles-definitions>标签内主要的子节点就是<definition>标签,这个标签属性如下:</definition></tiles-definitions></p>
<table>
<thead>
<tr>
<th>名称</th>
<th>是否必须</th>
<th>值必须</th>
<th>值类型</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>true</td>
<td>true</td>
<td>java.lang.String</td>
<td>指定将要创建的一个definition bean的访问名称。这个必须有的。</td>
</tr>
<tr>
<td>template</td>
<td>false</td>
<td>true</td>
<td>java.lang.String</td>
<td>用于指定模板文件</td>
</tr>
<tr>
<td>role</td>
<td>false</td>
<td>true</td>
<td>java.lang.String</td>
<td>如果配置了这个值的话,需要role的值相等,这个definition才被有效访问</td>
</tr>
<tr>
<td>extends</td>
<td>false</td>
<td>true</td>
<td>java.lang.String</td>
<td>继承哪一个definition,值是你要继承的definition的name的值。高使用率的属性。</td>
</tr>
<tr>
<td>preparer</td>
<td>false</td>
<td>true</td>
<td>java.lang.String</td>
<td>使用时,要写一个实现他的Prepare接口的类,作用就是在展现你定义的页面前会先执行你的prepare。</td>
</tr>
</tbody>
</table>
<h2 id="springmvc与tiles的整合">SpringMVC与Tiles的整合</h2>
<p>首先,新建一个maven工程demo-tiles,在pom文件中引入该项目所需要的jar(tiles-extras和spring-webmvc),工程的结构如下:</p>
<p><img src="/images/chenlong/stru1.png" alt="stru1.png" /></p>
<p>我们要实现的页面布局如下:</p>
<p><img src="/images/chenlong/pic1.gif" alt="pic1.gif" /></p>
<h3 id="1-配置文件">1. 配置文件</h3>
<p>在resources目录下新建spring和tiles的配置文件,并在web.xml中进行sringmvc的相应配置。</p>
<p><img src="/images/chenlong/web1.png" alt="web1.png" />
<img src="/images/chenlong/servletcontext.png" alt="servletcontext.png" />
<img src="/images/chenlong/tilesdefinitions.png" alt="tilesdefinitions.png" /></p>
<h3 id="2-页面文件">2. 页面文件</h3>
<p><img src="/images/chenlong/header.png" alt="header.png" />
<img src="/images/chenlong/menu.png" alt="menu.png" />
<img src="/images/chenlong/footer.png" alt="footer.png" />
<img src="/images/chenlong/template.png" alt="template.png" />
<img src="/images/chenlong/home.png" alt="home.png" />
<img src="/images/chenlong/about.png" alt="about.png" /></p>
<h3 id="3-java后台处理">3. java后台处理</h3>
<p><img src="/images/chenlong/HomeController.png" alt="HomeController.png" />
<img src="/images/chenlong/AboutController.png" alt="AboutController.png" /></p>
<h3 id="4-页面效果展示">4. 页面效果展示</h3>
<p><img src="/images/chenlong/pic2.png" alt="pic2.png" />
<img src="/images/chenlong/pic3.png" alt="pic3.png" /></p>
Apache Commons Chain 简单介绍
2017-06-20T00:00:00+00:00
http://www.blogways.net/blog/2017/06/20/ApacheCommonsChain
<h2 id="一commons-chain-介绍">一、Commons Chain 介绍</h2>
<p><code class="language-plaintext highlighter-rouge">Chain of Responsibility(CoR)</code>模式也叫职责链模式或者职责连锁模式,是由GoF提出的23种软件设计模式的一种。Chain of Responsibility模式是行为模式之一,该模式构造一系列分别担当不同的职责的类的对象来共同完成一个任务,这些类的对象之间像链条一样紧密相连,所以被称作职责链模式。</p>
<p><code class="language-plaintext highlighter-rouge">Apache Commons Chain</code> 提供了对CoR模式的基础支持,简化和促进了实际应用CoR模式。CommonsChain实现了Chain of Responsebility和Command模式,其中的Catalog + 配置文件的方式使得调用方和Command的实现方的耦合度大大的降低,提高了灵活性。</p>
<h2 id="二apache-commons-chain-核心组件">二、Apache Commons Chain 核心组件</h2>
<p><img src="/images/chenlong/apacheCommonsChain1.jpg" alt="apacheCommonsChain1 .jpg" /></p>
<h3 id="1-context接口">1、 Context接口</h3>
<p><code class="language-plaintext highlighter-rouge">Context</code>表示命令执行的上下文,在命令间实现共享信息的传递。
extends Map,父接口是Map,它只是一个标记接口。ContextBase实现了Context。对于web环境,可以使用WebContext类及其子类(FacesWebContext、PortletWebContext和ServletWebContext)。</p>
<h3 id="2command接口">2、Command接口</h3>
<p>Commons Chain中最重要的接口,表示在Chain中的具体某一步要执行的命令。它只有一个方法:boolean execute(Context context)。如果返回true,那么表示Chain的处理结束,Chain中的其他命令不会被调用;返回false,则Chain会继续调用下一个Command,直到:</p>
<ul>
<li>Command返回true;</li>
<li>Command抛出异常;</li>
<li>Chain的末尾;
<h3 id="3chain接口">3、Chain接口</h3>
<p>它表示“命令链”,chain of command,要在其中执行的命令,需要先添加到Chain中,父接口是Command , ChainBase实现了它。</p>
<h3 id="4filter接口">4、Filter接口</h3>
<p>extends Command,它是一种特殊的Command。除了Command的execute方法之外,还包括了一个方法:boolean postProcess(Context context, Exception exception)。Commons Chain会在执行了Filter的execute方法之后,执行postprocess(不论Chain以何种方式结束)。Filter的执行execute的顺序与Filter出现在Chain中出现的位置一致,但是执行postprocess顺序与之相反。如:如果连续定义了filter1和filter2,那么execute的执行顺序是:filter1 -> filter2;而postprocess的执行顺序是:filter2 -> filter1。</p>
<h3 id="5catalog接口">5、Catalog接口</h3>
<p>它是逻辑命名的Chain和Command集合。通过使用它,Command的调用者不需要了解具体实现Command的类名,通过配置文件类加载chain of command 或者command。通过catalog.getCommand(commandName)获取Command。</p>
<h2 id="三commons-chain-基本使用">三、Commons Chain 基本使用</h2>
<p>现在,我们模拟一个购车的例子来看一下chain是工作实现。购车分为:<code class="language-plaintext highlighter-rouge">用户信息的获取</code>、<code class="language-plaintext highlighter-rouge">试车</code>、<code class="language-plaintext highlighter-rouge">销售讨论</code>、<code class="language-plaintext highlighter-rouge">付款</code>以及<code class="language-plaintext highlighter-rouge">结束交易</code>。
五个工作类如下:</p>
</li>
</ul>
<p><strong>1. GetUserInfo.class</strong></p>
<p><img src="/images/chenlong/getUserInfo.png" alt="getUserInfo.png" /></p>
<p><strong>2. TestDriver.class</strong></p>
<p><img src="/images/chenlong/testDriver.png" alt="testDriver.png" /></p>
<p><strong>3. NegotiateSale.class</strong></p>
<p><img src="/images/chenlong/NegotiateSale.png" alt="NegotiateSale.png" /></p>
<p><strong>4. ArrangeFinancing.class</strong></p>
<p><img src="/images/chenlong/ArrangeFinancing.png" alt="ArrangeFinancing.png" /></p>
<p><strong>5. CloseSale.class</strong></p>
<p><img src="/images/chenlong/closeSale.png" alt="CloseSale.png" /></p>
<p>另外,我们也顺便在添加两个Filter进去,filter代码如下:</p>
<ul>
<li>Filter1.class</li>
</ul>
<p><img src="/images/chenlong/filter1.png" alt="filter1.png" /></p>
<ul>
<li>Filter2.class</li>
</ul>
<p><img src="/images/chenlong/filter2.png" alt="filter2.png" /></p>
<h3 id="要运行上述的流程可以有两种方式分别是采用配置文件和使用注册命令来运行chain">要运行上述的流程可以有两种方式,分别是采用配置文件和使用注册命令来运行chain。</h3>
<p>首先,我们采用注册命令来运行,代码如下:</p>
<p><img src="/images/chenlong/commandChain.png" alt="commandChain.png" /></p>
<p>运行结果为:</p>
<p><img src="/images/chenlong/result1.png" alt="result1.png" /></p>
<p>通过运行结果可以看出chain链运行的顺序是按照添加command的顺序执行的,而且Filter的执行execute的顺序与Filter出现在Chain中出现的位置一致,但是执行postprocess顺序与之相反。如:如果连续定义了filter1和filter2,那么execute的执行顺序是:filter1 -> filter2;而postprocess的执行顺序是:filter2 -> filter1。</p>
<p>其次,我们再使用配置文件加载Command。
对于复杂的Chain,可能需要使用内嵌的Chain,内嵌Chain可以类比一个子过程。此时,可以使用LookupCommand。假设其中的testCommand成为一个子过程,其代码为</p>
<p><img src="/images/chenlong/testCommand.png" alt="testCommand.png" /></p>
<p>扩展后的配置文件为:</p>
<p><img src="/images/chenlong/chaincfg.png" alt="chaincfg.png" /></p>
<p>装配文件的代码如下:</p>
<p><img src="/images/chenlong/cataLogLoader1.png" alt="cataLogLoader1.png" /></p>
<p>运行结果为:</p>
<p><img src="/images/chenlong/result2.png" alt="result2.png" /></p>
<p>配置文件的引入,使得Commons Chain的灵活性大大的提高。在实际的使用过程中,存在着同一个Command被多个Chain使用的情形。如果每次都书写Command的类名,尤其是前面的包名特别长的情况下,是一件比较麻烦而又费时费力的一件事。而<code class="language-plaintext highlighter-rouge"><define></code>的使用就解决这样的麻烦。通过定义Command和Chain的别名,来简化书写。上面的配置文件,可以书写成:</p>
<p><img src="/images/chenlong/chain-cfg2.png" alt="chain-cfg2.png" /></p>
<h2 id="总结">总结:</h2>
<p>Commons Chain实现了Chain of Responsebility和Command模式,其中的Catalog + 配置文件的方式使得调用方和Command的实现方的耦合度大大的降低,提高了灵活性。对于配置文件,通常可以:</p>
<ul>
<li>作为Command的索引表,需要时按名字索引创建实例。</li>
<li>利用Chain以及内嵌Chain,完成一组连续任务和Command的复用,引入Filter可以获得与Servlet Filter一样的好处。</li>
<li>使用<define>定义别名,简化书写。</define></li>
</ul>
常用垃圾回收算法
2017-06-10T00:00:00+00:00
http://www.blogways.net/blog/2017/06/10/jvm-gc
<h2 id="一什么是垃圾回收">一、什么是垃圾回收</h2>
<p><code class="language-plaintext highlighter-rouge">垃圾回收</code>(Garbage Collection,简称 <code class="language-plaintext highlighter-rouge">GC</code>),就是将存在于内存中的、不会再被使用的对象占有的空间进行清理。清理出来的空间可以供其他对象再利用。如果一直不进行垃圾回收,则有可能会导致内存溢出。</p>
<h2 id="二常用的垃圾回收算法">二、常用的垃圾回收算法</h2>
<h3 id="1引用计数法">1、引用计数法</h3>
<p><code class="language-plaintext highlighter-rouge">引用计数法</code>是最经典也是最古老的垃圾回收算法。它的实现很简单,对于一个对象,只要有任何一个对象引用了它,那么它的计数器就加1;引用失效时,就减1。如果对象的引用计数器为0,则表示该对象可以被回收。</p>
<p>引用计数法有两个严重的问题:</p>
<ul>
<li>
<p>无法处理循环引用的问题。<code class="language-plaintext highlighter-rouge">循环引用</code>指的是两个对象互相引用,如对象 A 引用对象 B,同时对象 B 也引用了对象 A,但是 A 和 B 都没有被其他任何对象引用,这说明 A 和 B 其实是可以被回收的。由于 A 和 B 现在的引用计数都是1,所以它们不能被回收。</p>
</li>
<li>
<p>引用计数会伴随一次加法或减法运算,会对系统性能造成一定影响。</p>
</li>
</ul>
<p><img src="/images/jyjsjd/ref.png" alt="ref.png" /></p>
<h3 id="2标记清除法">2、标记清除法</h3>
<p><code class="language-plaintext highlighter-rouge">标记清除法</code>把垃圾回收分为两个阶段:<code class="language-plaintext highlighter-rouge">标记阶段</code>和<code class="language-plaintext highlighter-rouge">清除阶段</code>。在标记阶段,标记所有从根节点可达的对象;在清除阶段清除所有未被标记的对象。</p>
<p>标记清除法存在的问题是,垃圾回收之后的内存空间会产生很多碎片。</p>
<ul>
<li>回收前</li>
</ul>
<p><img src="/images/jyjsjd/mark1.png" alt="mark1.png" /></p>
<p>(蓝色是可达对象,红色是不可达对象)</p>
<ul>
<li>回收后</li>
</ul>
<p><img src="/images/jyjsjd/mark2.png" alt="mark2.png" /></p>
<p>可以看到,回收之后的空间是不连续的,这样对分配大对象非常不利。</p>
<h3 id="3复制算法">3、复制算法</h3>
<p><code class="language-plaintext highlighter-rouge">复制算法</code>的核心是将内存分为两块,每次只使用其中一块。在垃圾回收时,将正在使用的内存中的存活对象复制到另一块对象中,然后清除其他对象。之后再次进行回收时则交换两个内存区域的角色。</p>
<p>复制算法可以保证回收之后的内存空间是连续的;如果在垃圾回收过程中存活对象较少,复制算法效率比较高。但是它的代价是将可用内存折半,这样也很难让人接受。</p>
<ul>
<li>回收前</li>
</ul>
<p><img src="/images/jyjsjd/copy1.png" alt="copy1.png" /></p>
<p>(蓝色是可达对象,红色是不可达对象)</p>
<ul>
<li>回收后</li>
</ul>
<p><img src="/images/jyjsjd/copy2.png" alt="copy2.png" /></p>
<h3 id="4标记压缩法">4、标记压缩法</h3>
<p><code class="language-plaintext highlighter-rouge">标记压缩法</code>在标记清除法和复制算法的基础上做了一些改进。</p>
<p>首先,它也是从根节点开始标记所有可达对象,然后,它不是简单地清除不可达对象,而是将所有的存活对象压缩到内存的一端,然后开始清除垃圾。这样既避免了内存空间碎片,又不需要把内存空间折半。</p>
<ul>
<li>标记,压缩</li>
</ul>
<p><img src="/images/jyjsjd/compact1.png" alt="compact1.png" /></p>
<p>(蓝色是可达对象,红色是不可达对象)</p>
<ul>
<li>清除</li>
</ul>
<p><img src="/images/jyjsjd/compact2.png" alt="compact2.png" /></p>
Apache_Kafka入门(二)
2017-05-14T00:00:00+00:00
http://www.blogways.net/blog/2017/05/14/kafka-2
<h1 id="apache_kafka入门二">Apache_Kafka入门(二)</h1>
<blockquote>
<p>前面我们搭建了3个broker的kafka集群
本篇我们介绍一下kafka的一些配置以及java代码,从中学习和理解kafka的一些设计。</p>
</blockquote>
<h2 id="一producer和consumer主要配置">一、Producer和Consumer主要配置</h2>
<p>我们先来学习一下kafka关于producer和consumer的一些配置,通过配置信息对producer和consumer有个大体了解,同时也是在此做个记录,方便日后使用时查阅。</p>
<ul>
<li>Producer主要配置:<a href="http://kafka.apache.org/documentation.html#producerconfigs">官方文档</a></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 用于建立与kafka集群的连接,这个list仅仅影响用于初始化的hosts,来发现全部的servers。
##格式:host1:port1,host2:port2,…,数量尽量不止一个,以防其中一个down了
bootstrap.servers=192.168.127.10:9092,192.168.127.11:9092,192.168.127.12:9092
# Producer用于压缩数据的压缩类型,取值:none, gzip, snappy, or lz4
compression.type=none
# 消息序列化类,默认为kafka.serializer.DefaultEncoder,将消息实体转化为byte[]
serializer.class=kafka.serializer.StringEncoder
# producer接收消息ack的时机,默认为0
# 0:producer不会等待确认,直接添加到socket等待发送;
# 1:这意味着至少要等待leader已经成功将数据写入本地log,但是并没有等待所有follower是否成功写入。这种情况下,如果follower没有成功备份数据,而此时leader又挂掉,则消息会丢失。
# all:这意味着leader需要等待所有备份都成功写入日志,这种策略会保证只要有一个备份存活就不会丢失数据。这是最强的保证。
acks=0
# Producer可以用来缓存数据的内存大小。
##如果数据产生速度大于向broker发送的速度,producer会阻塞max.block.ms,超时则抛出异常
buffer.memory=
# Producer默认会把两次发送时间间隔内收集到的所有Requests进行一次聚合然后再发送,以此提高吞吐量,而linger.ms则更进一步,这个参数为每次发送增加一些delay,以此来聚合更多的Message。
linger.ms=
# 请求的最大字节数。这也是对最大消息大小的有效限制。
# 注意:server具有自己对消息大小的限制,这些大小和这个设置不同。此项设置将会限制producer每次批量发送请求的数目,以防发出巨量的请求。
max.request.size=
# TCP的接收缓存 SO_RCVBUF 空间大小,用于读取数据
receive.buffer.bytes=
# client等待请求响应的最大时间,如果在这个时间内没有收到响应,客户端将重发请求,超过重试次数发送失败
request.timeout.ms=
# TCP的发送缓存 SO_SNDBUF 空间大小,用于发送数据
send.buffer.bytes=
# 指定server等待来自followers的确认的最大时间,根据acks的设置,超时则返回error
timeout.ms=
# 在block前一个connection上允许最大未确认的requests数量。
# 当设为1时,即是消息保证有序模式,注意:这里的消息保证有序是指对于单个Partition的消息有顺序,因此若要保证全局消息有序,可以只使用一个Partition,当然也会降低性能
max.in.flight.requests.per.connection=
# 在第一次将数据发送到某topic时,需先fetch该topic的metadata,得知哪些服务器持有该topic的partition,该值为最长获取metadata时间
metadata.fetch.timeout.ms=
# 连接失败时,当我们重新连接时的等待时间
reconnect.backoff.ms=
# 在重试发送失败的request前的等待时间,防止若目的Broker完全挂掉的情况下Producer一直陷入死循环发送,折中的方法
retry.backoff.ms=
# metrics系统维护可配置的样本数量,在一个可修正的window size
metrics.sample.window.ms=30000
# 用于维护metrics的样本数
metrics.num.samples=2
# 类的列表,用于衡量指标。实现MetricReporter接口
metric.reporters=[]
# 强制刷新metadata的周期,即使leader没有变化
metadata.max.age.ms=300000
# 与broker会话协议,取值:LAINTEXT, SSL, SASL_PLAINTEXT, SASL_SSL
security.protocol=PLAINTEXT
# 分区类,实现Partitioner接口
partitioner.class=class org.apache.kafka.clients.producer.internals.DefaultPartitioner
# 控制block的时长,当buffer空间不够或者metadata丢失时产生block
max.block.ms=60000
# 关闭达到该时间的空闲连接
connections.max.idle.ms=540000
# 当向server发出请求时,这个字符串会发送给server,目的是能够追踪请求源
client.id=""
# 发生错误时,重传次数。当开启重传时,需要将`max.in.flight.requests.per.connection`设置为1,否则可能导致失序
retries=0
# key 序列化方式,类型为class,需实现Serializer interface
key.serializer=
# value 序列化方式,类型为class,需实现Serializer interface
value.serializer=
</code></pre></div></div>
<ul>
<li>Consumer主要配置:<a href="http://kafka.apache.org/documentation.html#newconsumerconfigs">官方文档</a></li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 用于建立与kafka集群的连接,这个list仅仅影响用于初始化的hosts,来发现全部的servers。
bootstrap.servers=
# 用来唯一标识consumer进程所在组的字符串
group.id=
# 单次拉取的最大消息条数
max.poll.records=
# consumer失效时间,当长时间未收到心跳,并且大于这个时间,broker会将这个consumer从group中移除
session.timeout.ms
# 心跳间隔,需低于session.timeout.ms时间,建议不高于其1/3
heartbeat.interval.ms
# 如果设为true,consumer的offset将在后台定期自动提交
enable.auto.commit
# 当enable.auto.commit设为true时,定期提交的时间间隔(ms)
auto.commit.interval.ms
# 分区策略,取值为range或roundrobin
partition.assignment.strategy
# 自动重置offset,取值earliest、latest、none
# earliest将offset设置为开始位置
# latest将offset设置为开始位置
auto.offset.reset
# 每次最小拉取的消息大小(byte)。Consumer会等待消息积累到一定尺寸后进行批量拉取。默认为1,代表有一条就拉一条
fetch.min.bytes
# 拉取消息的最大值
fetch.max.bytes
# 当消息没有积累到fetch.min.bytes值的最大等待时间
fetch.max.wait.ms
# 元数据刷新时间(无任何改变的情况下)
metadata.max.age.ms
# 每次从单个分区中拉取的消息最大尺寸(byte),默认为1M
max.partition.fetch.bytes
# tcp发送buffer数据的大小,如果值为-1,则为os默认值
send.buffer.bytes
# tcp读取buffer数据的大小,如果值为-1,则为os默认值
receive.buffer.bytes
# 客户端id
client.id
# 连接时间间隔
reconnect.backoff.ms
# 连接失败,重连时间间隔
retry.backoff.ms
# 是否开启CRC32校验,默认为开启
check.crcs
# 指定key的反序列化方式
key.deserializer
# 指定value的反序列化方式
value.deserializer
# 关闭连接的最大空闲时间,默认为540000ms
connections.max.idle.ms
# 客户端等待响应最大时间
request.timeout.ms
# 拦截器列表,默认没有拦截器
interceptor.classes
# 内部topics信息是否可访问,默认为true
exclude.internal.topics
</code></pre></div></div>
<h2 id="二通过java代码进入对kafka的学习">二、通过JAVA代码进入对Kafka的学习</h2>
<ul>
<li>构建maven项目,引入依赖</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.apache.kafka</groupId>
<artifactId>kafka-clients</artifactId>
<version>0.10.2.1</version>
</dependency>
</code></pre></div></div>
<h3 id="21-producer-发布消息">2.1 producer 发布消息</h3>
<ul>
<li>Producer JAVA代码</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Properties;
import java.util.concurrent.Future;
/**
* Created by Gao on 2017/5/13.
*/
public class ProducerDemo {
private Producer<String,String> producer;
private Logger logger= LoggerFactory.getLogger(ProducerDemo.class);
public ProducerDemo() {
//producer配置信息
Properties config=new Properties();
config.setProperty("bootstrap.servers","192.168.127.10:9092,192.168.127.11:9092,192.168.127.12:9092");
config.setProperty("value.serializer","org.apache.kafka.common.serialization.StringSerializer");
config.setProperty("key.serializer","org.apache.kafka.common.serialization.StringSerializer");
config.setProperty("acks","0");
producer=new KafkaProducer<String, String>(config);
}
//这里我们并没有指定partit
public void send(String topicName,String message){
if(topicName==null||message==null){
return;
}
//创建消息实体
ProducerRecord<String,String> record=new ProducerRecord<String, String>(topicName,message);
Future<RecordMetadata> future = producer.send(record);
System.out.println("send message ["+message+"] success");
producer.flush();
}
public void close(){
if(producer!=null){
producer.close();
}
}
public static void main(String[] args) {
int i = 1;
while(true) {
new ProducerDemo().send("kafka_study", "hello" + i++);
try {
Thread.sleep(600);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
</code></pre></div></div>
<h4 id="211-写入方式">2.1.1 写入方式</h4>
<blockquote>
<p>producer 采用 push 模式将消息发布到 broker,每条消息都被 append 到 patition 中,属于顺序写磁盘(顺序写磁盘效率比随机写内存要高,保障 kafka 吞吐率)。</p>
</blockquote>
<h4 id="212-消息路由">2.1.2 消息路由</h4>
<blockquote>
<p>producer 发送消息到 broker 时,会根据分区算法选择将其存储到哪一个 partition。其路由机制为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 指定了 patition,则直接使用;
2. 未指定 patition 但指定 key,通过对 key 的 value 进行hash 选出一个 patition,通过这样的方案,kafka能够确保相同key值的数据可以写入同一个partition
3. patition 和 key 都未指定,使用轮询选出一个 patition。
</code></pre></div> </div>
<p>下面是一些kafka client 对于分区的一些源码片段</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//创建消息实例
public ProducerRecord(String topic, Integer partition, Long timestamp, K key, V value) {
if (topic == null)
throw new IllegalArgumentException("Topic cannot be null.");
if (timestamp != null && timestamp < 0)
throw new IllegalArgumentException(
String.format("Invalid timestamp: %d. Timestamp should always be non-negative or null.", timestamp));
if (partition != null && partition < 0)
throw new IllegalArgumentException(
String.format("Invalid partition: %d. Partition number should always be non-negative or null.", partition));
this.topic = topic;
this.partition = partition;
this.key = key;
this.value = value;
this.timestamp = timestamp;
}
</code></pre></div> </div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private int partition(ProducerRecord<K, V> record, byte[] serializedKey, byte[] serializedValue, Cluster cluster) {
Integer partition = record.partition();
//如果partition不为null就直接使用,如果为null则进行相应计算
return partition != null ?
partition :
partitioner.partition(
record.topic(), record.key(), serializedKey, record.value(), serializedValue, cluster);
}
</code></pre></div> </div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//计算写入的partition。
public int partition(String topic, Object key, byte[] keyBytes, Object value, byte[] valueBytes, Cluster cluster) {
List<PartitionInfo> partitions = cluster.partitionsForTopic(topic);
int numPartitions = partitions.size();
//如果key为null,轮询选出一个 patition
if (keyBytes == null) {
int nextValue = nextValue(topic);
List<PartitionInfo> availablePartitions = cluster.availablePartitionsForTopic(topic);
if (availablePartitions.size() > 0) {
int part = Utils.toPositive(nextValue) % availablePartitions.size();
return availablePartitions.get(part).partition();
} else {
// no partitions are available, give a non-available partition
return Utils.toPositive(nextValue) % numPartitions;
}
} else {
//KEY不为null则通过对key的keyBytes进行hash计算出一个partition
// hash the keyBytes to choose a partition
return Utils.toPositive(Utils.murmur2(keyBytes)) % numPartitions;
}
}
</code></pre></div> </div>
</blockquote>
<h4 id="213-消息写入流程">2.1.3 消息写入流程</h4>
<blockquote>
<ol>
<li>producer 先从 zookeeper 的 “/brokers/…/state” 节点找到该 partition 的 leader</li>
<li>producer 将消息发送给该 leader</li>
<li>leader 将消息写入本地 log</li>
<li>followers 从 leader pull 消息,写入本地 log 后向 leader 发送 ACK</li>
<li>leader 收到所有 ISR (In-Sync Replicas) 中的 replica 的 ACK 后,增加 HW(high watermark,最后 commit 的 offset) 并向 producer 发送 ACK</li>
</ol>
</blockquote>
<h3 id="22-broker-保存消息">2.2 broker 保存消息</h3>
<h4 id="221-存储方式">2.2.1 存储方式</h4>
<p>物理上把 topic 分成一个或多个 patition(对应 server.properties 中的 num.partitions=3 配置),每个 patition 物理上对应一个文件夹(该文件夹存储该 patition 的所有消息和索引文件以及kafka的具体时间日志),单个broker和多个broker的partition略有区别:</p>
<ul>
<li>
<p>单个Broker:
创建一个partition为3,Replica为1,Topic名字为kafka_study的topic。我们得到的分布式在配置好的LOG文件夹中生成三个分别为:kafka_study-0、kafka_study-1、kafka_study-2的文件夹用来存储Partition下的信息的.index文件.log文件和.timeindex文件。</p>
</li>
<li>
<p>多个Broker:
创建一个partition为3,Replica为1,Topic名字为kafka_study的topic。我们在Broker0中对应的LOG文件夹中只是发现了kafka_study-0的文件夹,在其他Broker中分别发现了Partition的文件夹。如果Broker数大于Partition数,那么有Broker中没有对应的Partition;如果Broker小于Partition数,Broker中会存在多个Partition。</p>
</li>
</ul>
<p>我们搭建的是三个broker的集群,故partition分布如下图所示:</p>
<p><img src="/images/gaoxiaobo/kafka3.png" alt="" /></p>
<p>进入partition对应的文件夹可以看到三个文件,如下图:
<img src="/images/gaoxiaobo/kafka4.png" alt="" /></p>
<ul>
<li>index文件为索引文件,命名规则为从0开始到,后续的由上一个文件的最大的offset偏移量来开头(19位数字字符长度)</li>
<li>log文件为数据文件,存放具体消息数据</li>
<li>timeindex文件,是kafka的具体时间日志</li>
</ul>
<p>分区算法:</p>
<ul>
<li>分区数=Tt/Max(Tp,Tc)</li>
<li>Tp:producer吞吐量 Tc:consumer吞吐量 Tt目标的吞吐量</li>
</ul>
<p>####2.2.2 消息的存储与删除
无论消息是否被消费,kafka 都会保留所有消息。有两种策略可以删除旧数据:</p>
<blockquote>
<ol>
<li>基于时间:log.retention.hours=168</li>
<li>基于大小:log.retention.bytes=1073741824</li>
</ol>
</blockquote>
<p>需要注意的是,因为Kafka读取特定消息的时间复杂度为O(1),即与文件大小无关,所以这里删除过期文件与提高 Kafka 性能无关。</p>
<p>###2.3 consumer 消费消息
Kafka 0.10.2.1提供了两套<strong><em>Consumer</em></strong> API共我们使用:</p>
<ol>
<li><strong><em>MockConsumer</em></strong></li>
<li><strong><em>KafkaConsumer</em></strong></li>
</ol>
<p>MockConsumer主要供开发测试时使用,这里使用的是KafkaConsumer。</p>
<ul>
<li>Consumer JAVA代码</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import org.apache.kafka.clients.consumer.*;
import java.util.*;
/**
* Created by Gao on 2017/5/13.
*/
public class ConsumerDemo {
private Consumer<String,String> consumer;
private String topicName;
private String groupId;
public ConsumerDemo(String topicName,String groupId) {
this.topicName=topicName;
this.groupId=groupId;
//consumer配置信息
Properties config=new Properties();
config.setProperty("group.id",groupId);
config.setProperty("bootstrap.servers","192.168.127.10:9092,192.168.127.11:9092,192.168.127.12:9092");
config.setProperty("enable.auto.commit","true");
//设置offset从头开始
config.setProperty("auto.offset.reset","earliest");
config.setProperty("key.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
config.setProperty("value.deserializer","org.apache.kafka.common.serialization.StringDeserializer");
config.setProperty("auto.commit.interval.ms","60000");
//创建Consumer实例
this.consumer = new KafkaConsumer<String, String>(config);
}
public void receive(){
List<String> topics=new ArrayList<String>();
topics.add(topicName);
//订阅消息
consumer.subscribe(topics);
while (true){
//拉取消息
ConsumerRecords<String, String> records = consumer.poll(1000);
for (ConsumerRecord<String,String> record:records) {
System.out.println(record.toString());
}
System.out.println(records.count());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new ConsumerDemo("kafka_study","test-group").receive();
}
}
</code></pre></div></div>
<p>####2.3.1 Consumer Group</p>
<blockquote>
<p>kafka 的分配单位是 patition。每个 consumer 都属于一个 group,一个 partition 只能被同一个 group 内的一个 consumer 所消费(也就保障了一个消息只能被 group 内的一个 consuemr 所消费),但是多个 group 可以同时消费这个 partition。</p>
</blockquote>
<p>####2.3.2 消费方式</p>
<blockquote>
<p>consumer 采用 pull 模式从 broker 中读取数据。
push 模式很难适应消费速率不同的消费者,因为消息发送速率是由 broker 决定的。它的目标是尽可能以最快速度传递消息,但是这样很容易造成 consumer 来不及处理消息,典型的表现就是拒绝服务以及网络拥塞。而 pull 模式则可以根据 consumer 的消费能力以适当的速率消费消息。
对于 Kafka 而言,pull 模式更合适,它可简化 broker 的设计,consumer 可自主控制消费消息的速率,同时 consumer 可以自己控制消费方式——即可批量消费也可逐条消费,同时还能选择不同的提交方式从而实现不同的传输语义。</p>
</blockquote>
<p>####2.3.3消息传递保障</p>
<p>现在我们了解一些关于生产者和消费者是如何工作的,接下来我们来讨论kafka提供了生产者和消费者之间的传输语义:<a href="http://kafka.apache.org/documentation.html#semantics">官方文档</a></p>
<ul>
<li><strong>At most once</strong>
最多一次 — 消息可能丢失,但绝不会重发。</li>
<li><strong>At least once</strong>
至少一次 — 消息绝不会丢失,但有可能重新发送。</li>
<li><strong>Exactly once</strong>
正好一次 — 这是人们真正想要的,每个消息传递一次且仅一次。</li>
</ul>
<p>如果将 consumer 设置为 autocommit,consumer 一旦读到数据立即自动 commit。如果只讨论这一读取消息的过程,那 Kafka 确保了 Exactly once。</p>
<p>但实际使用中应用程序并非在 consumer 读取完数据就结束了,而是要进行进一步处理,而数据处理与 commit 的顺序在很大程度上决定了consumer delivery guarantee:</p>
<ol>
<li>读完消息 先commit 再处理消息。
这种模式下,如果 consumer 在 commit 后还没来得及处理消息就 crash 了,下次重新开始工作后就无法读到刚刚已提交而未处理的消息,这就对应于 At most once</li>
<li>读完消息先处理再 commit。
这种模式下,如果在处理完消息之后 commit 之前 consumer crash 了,下次重新开始工作时还会处理刚刚未 commit 的消息,实际上该消息已经被处理过了。这就对应于 At least once。</li>
<li>如果一定要做到 Exactly once,就需要协调 offset 和实际操作的输出。
精典的做法是引入两阶段提交。如果能让 offset 和操作输入存在同一个地方,会更简洁和通用。这种方式可能更好,因为许多输出系统可能不支持两阶段提交。比如,consumer 拿到数据后可能把数据放到 HDFS,如果把最新的 offset 和数据本身一起写到 HDFS,那就可以保证数据的输出和 offset 的更新要么都完成,要么都不完成,间接实现 Exactly once。</li>
</ol>
<p>参考文章:</p>
<ol>
<li><a href="http://kafka.apache.org/documentation.html">Kafka 0.10.2 Documentation</a></li>
<li><a href="http://www.cnblogs.com/cyfonly/p/5954614.html">kafka学习笔记:知识点整理</a></li>
<li><a href="http://www.cnblogs.com/wangb0402/p/6182707.html">kafka-分布式消息系统</a></li>
<li><a href="http://orchome.com/21">kafka消息传递保障</a></li>
</ol>
Apache_Kafka入门(一)
2017-05-02T00:00:00+00:00
http://www.blogways.net/blog/2017/05/02/kafka-1
<h1 id="一kafka是什么">一、kafka是什么?</h1>
<blockquote>
<p>kafka是一个分布式的、可分区的、可复制的消息系统。它使用Scala编写,它以可水平扩展和高吞吐率而被广泛使用。</p>
</blockquote>
<h1 id="二产生背景">二、产生背景</h1>
<blockquote>
<p>Kafka是一个消息系统,用作LinkedIn的活动流(Activity Stream)和运营数据处理管道(Pipeline)的基础。活动流数据是几乎所有站点在对其网站使用情况做报表时都要用到的数据中最常规的部分。活动数据包括页面访问量(Page View)、被查看内容方面的信息以及搜索情况等内容。这种数据通常的处理方式是先把各种活动以日志的形式写入某种文件,然后周期性地对这些文件进行统计分析。运营数据指的3是服务器的性能数据(CPU、IO使用率、请求时间、服务日志等等数据)。运营数据的统计方法种类繁多。</p>
</blockquote>
<h1 id="三基本架构图">三、基本架构图</h1>
<p><img src="/images/gaoxiaobo/kafka1.png" alt="kafka" /></p>
<h1 id="四基本概念的解释">四、基本概念的解释</h1>
<ol>
<li>
<p><strong>Broker</strong>
Kafka集群包含一个或多个服务器,这种服务器被称为broker。broker端不维护数据的消费状态,提升了性能。直接使用磁盘进行存储,线性读写,速度快:避免了数据在JVM内存和系统内存之间的复制,减少耗性能的创建对象和垃圾回收。</p>
</li>
<li>
<p><strong>Producer</strong>
负责发布消息到Kafka broker</p>
</li>
<li>
<p><strong>Consumer</strong>
消息消费者,向Kafka broker读取消息的客户端,consumer从broker拉取(pull)数据并进行处理。</p>
</li>
<li>
<p><strong>Topic</strong>
每条发布到Kafka集群的消息都有一个类别,这个类别被称为Topic。(物理上不同Topic的消息分开存储,逻辑上一个Topic的消息虽然保存于一个或多个broker上但用户只需指定消息的Topic即可生产或消费数据而不必关心数据存于何处)</p>
</li>
<li>
<p><strong>Partition</strong>
Parition是物理上的概念,每个Topic包含一个或多个Partition.</p>
</li>
<li>
<p><strong>Consumer Group</strong>
每个Consumer属于一个特定的Consumer Group(可为每个Consumer指定group name,若不指定group name则属于默认的group)</p>
</li>
</ol>
<h1 id="五kafka集群环境搭建">五、kafka集群环境搭建</h1>
<h2 id="1-zookeeper集群搭建">1) Zookeeper集群搭建</h2>
<blockquote>
<p>Kafka集群是把状态保存在Zookeeper中的,首先要搭建Zookeeper集群。
Zookeeper环境的搭建请参照<a href="http://www.blogways.net/blog/2016/11/20/storm-2.html">《Storm(2)-Storm集群的搭建》</a>。需要注意的是zookeeper需要java环境,所以记得先安装JDK。</p>
</blockquote>
<h2 id="2-kafka集群搭建">2) Kafka集群搭建</h2>
<ul>
<li>
<p><strong>软件环境</strong>
1、linux一台或多台,大于等于2
2、已经搭建好的zookeeper集群
3、软件版本kafka_2.11-0.9.0.1.tgz</p>
</li>
<li>
<p><strong>创建目录并下载安装软件</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #下载软件
cd /usr/local/src/
wget http://apache.opencas.org/kafka/0.9.0.1/kafka_2.11-0.9.0.1.tgz
#解压软件
tar -zxvf kafka_2.11-0.9.0.1.tgz
</code></pre></div> </div>
</li>
<li>
<p><strong>修改配置文件</strong></p>
</li>
</ul>
<p>进入config目录,我们可以发现在目录下有很多文件,这里可以发现有Zookeeper文件,我们可以根据Kafka内带的zk集群来启动,但是建议使用独立的zk集群。这里主要关注:server.properties 这个文件即可。</p>
<p>server.properties配置注解:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>broker.id=0 #当前机器在集群中的唯一标识,和zookeeper的myid性质一样
port=19092 #当前kafka对外提供服务的端口默认是9092
host.name=192.168.7.100 #这个参数默认是关闭的,在0.8.1有个bug,DNS解析问题,失败率的问题。
num.network.threads=3 #这个是borker进行网络处理的线程数
num.io.threads=8 #这个是borker进行I/O处理的线程数
log.dirs=/opt/kafka/kafkalogs/ #消息存放的目录,这个目录可以配置为“,”逗号分割的表达式,上面的num.io.threads要大于这个目录的个数这个目录,如果配置多个目录,新创建的topic他把消息持久化的地方是,当前以逗号分割的目录中,那个分区数最少就放那一个
socket.send.buffer.bytes=102400 #发送缓冲区buffer大小,数据不是一下子就发送的,先回存储到缓冲区了到达一定的大小后在发送,能提高性能
socket.receive.buffer.bytes=102400 #kafka接收缓冲区大小,当数据到达一定大小后在序列化到磁盘
socket.request.max.bytes=104857600 #这个参数是向kafka请求消息或者向kafka发送消息的请请求的最大数,这个值不能超过java的堆栈大小
num.partitions=1 #默认的分区数,一个topic默认1个分区数
log.retention.hours=168 #默认消息的最大持久化时间,168小时,7天
message.max.byte=5242880 #消息保存的最大值5M
default.replication.factor=2 #kafka保存消息的副本数,如果一个副本失效了,另一个还可以继续提供服务
replica.fetch.max.bytes=5242880 #取消息的最大直接数
log.segment.bytes=1073741824 #这个参数是:因为kafka的消息是以追加的形式落地到文件,当超过这个值的时候,kafka会新起一个文件
log.retention.check.interval.ms=300000 #每隔300000毫秒去检查上面配置的log失效时间(log.retention.hours=168 ),到目录查看是否有过期的消息如果有,删除
log.cleaner.enable=false #是否启用log压缩,一般不用启用,启用的话可以提高性能
zookeeper.connect=192.168.7.100:12181,192.168.7.101:12181,192.168.7.107:1218 #设置zookeeper的连接端口
</code></pre></div></div>
<p>实际的修改项为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>broker.id=0 #每台服务器的broker.id都不能相同
listeners=PLAINTEXT://192.168.43.40:9092
port=9092
在log.retention.hours=168 下面新增下面三项
message.max.byte=5242880
default.replication.factor=2
replica.fetch.max.bytes=5242880
设置zookeeper的连接端口
zookeeper.connect=192.168.43.40:2181,192.168.43.41:2181,192.168.43.42:2181
</code></pre></div></div>
<ul>
<li><strong>启动zookeeper、kafka集群并测试</strong>
启动服务:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>##启动zookeeper
[root@host1 config]# cd /usr/local/src/zookeeper-3.4.6
[root@host1 zookeeper-3.4.6]# ./bin/zkServer.sh start ./conf/zoo.cfg
##启动kafka
[root@host1 zookeeper-3.4.6]# cd /usr/local/src/kafka_2.11-0.9.0.1
[root@host1 kafka_2.11-0.9.0.1]# ./bin/kafka-server-start.sh -daemon ./config/server.properties
</code></pre></div> </div>
<p>查看服务是否正常启动:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[root@host1 kafka_2.11-0.9.0.1]# jps
2356 QuorumPeerMain
2550 Jps
2413 Kafka
</code></pre></div> </div>
</li>
</ul>
<p>创建Topic、Producer、Consumer测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#创建Topic
./kafka-topics.sh --create --zookeeper 192.168.43.40:2181 --replication-factor 2 --partitions 1 --topic test
#解释
--replication-factor 2 #复制两份
--partitions 1 #创建1个分区
--topic #主题为test
#在一台服务器上创建一个发布者
#创建一个broker,发布者
./kafka-console-producer.sh --broker-list 192.168.43.40:9092 --topic test
#在一台服务器上创建一个订阅者
./kafka-console-consumer.sh --zookeeper 192.168.43.40:2181 --topic test --from-beginning
</code></pre></div></div>
<p>测试(在发布者那里发布消息看看订阅者那里是否能正常收到~)
<img src="/images/gaoxiaobo/kafka2.png" alt="kafka_sendMsg" /></p>
基于netty的socket.io通信服务
2017-04-04T00:00:00+00:00
http://www.blogways.net/blog/2017/04/04/web-socket
<h2 id="1各类web推送技术优缺点">1.各类web推送技术优缺点</h2>
<blockquote>
<p>不断地轮询(俗称“拉”,polling)是获取实时消息的一个手段</p>
</blockquote>
<p>Ajax 隔一段时间(通常使用 JavaScript 的 setTimeout 函数)就去服务器查询是否有改变,从而进行增量式的更新。但是间隔多长时间去查询成了问题,因为性能和即时性造成了严重的反比关系。间隔太短,连续不断的请求会冲垮服务器,间隔太长,服务器上的新数据就需要越多的时间才能到达客户机。</p>
<p>优点:服务端逻辑简单;</p>
<p>缺点:其中大多数请求可能是无效请求,在大量用户轮询很频繁的情况下对服务器的压力很大;</p>
<p>应用:并发用户量少,而且要求消息的实时性不高,一般很少采用;</p>
<blockquote>
<p>长轮询技术(long-polling)</p>
</blockquote>
<p>客户端向服务器发送Ajax请求,服务器接到请求后hold住连接,直到有新消息或超时(设置)才返回响应信息并关闭连接,客户端处理完响应信息后再向服务器发送新的请求。</p>
<p>优点:实时性高,无消息的情况下不会进行频繁的请求;</p>
<p>缺点:服务器维持着连接期间会消耗资源;</p>
<blockquote>
<p>基于Iframe及htmlfile的流(streaming)方式</p>
</blockquote>
<p>iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长链接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。</p>
<p>优点:消息能够实时到达;</p>
<p>缺点:服务器维持着长连接期会消耗资源;</p>
<blockquote>
<p>插件提供socket方式</p>
</blockquote>
<p>比如利用Flash XMLSocket,Java Applet套接口,Activex包装的socket。</p>
<p>优点:原生socket的支持,和PC端和移动端的实现方式相似;</p>
<p>缺点:浏览器端需要装相应的插件;</p>
<blockquote>
<p>WebSocket</p>
</blockquote>
<p>是HTML5开始提供的一种浏览器与服务器间进行全双工通讯的网络技术。</p>
<p>优点:更好的节省服务器资源和带宽并达到实时通讯;</p>
<p>缺点:目前还未普及,浏览器支持不好;</p>
<p><strong>综上,考虑到浏览器兼容性和性能问题,采用长轮询(long-polling)是一种比较好的方式。</strong></p>
<p><strong>netty-socketio是一个开源的Socket服务器端的一个java的实现, 它基于Netty框架。</strong></p>
<h2 id="2使用场景">2.使用场景</h2>
<ul>
<li>web版聊天室应用</li>
<li>股票交易走势</li>
<li>车辆实时监控</li>
</ul>
<p><strong>示例:模拟车联网应用中车辆在地图上动态移动,移动数据从后台获取,然后通过socket通信推送到前端页面,
使用百度地图API实现前端覆盖物的动态移动。</strong></p>
<p><img src="/images/zhaojiajun/20170404img01.jpg" alt="20170404img01" /></p>
<h2 id="3server端实现">3.Server端实现</h2>
<h3 id="引入jar包">引入jar包</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- netty-socket.io -->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.7</version>
</dependency>
</code></pre></div></div>
<h3 id="server服务实现">server服务实现</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(9092);
final SocketIOServer server = new SocketIOServer(config);
//客户端断开连接监听
server.addDisconnectListener(new DisconnectListener() {
@Override
public void onDisconnect(SocketIOClient client) {
System.out.println("**************onDisconnect****************");
//监听断开的连接存放在set中,用于后续判断结束对应的后台线程
disconnectSet.add(client.getSessionId());
}
});
//根据不同的命名空间,单个客户端触发
server.addNamespace("/position").addEventListener("msgevent", MsgObject.class, new DataListener<MsgObject>() {
@Override
public void onData(SocketIOClient client, MsgObject data, AckRequest ackRequest) {
//每个客户端连接启动一个后台线程
MsgThread msgThread = new MsgThread(client, data);
msgThread.start();
}
});
//广播式消息
server.addNamespace("/notice").addEventListener("msgevent", MsgObject.class, new DataListener<MsgObject>() {
@Override
public void onData(SocketIOClient client, MsgObject data, AckRequest ackRequest) {
server.getRoomOperations("/notice").sendEvent("msgevent", data);
}
});
//启动服务
server.start();
</code></pre></div></div>
<h3 id="定义一个内部类用于处理消息发送的线程">定义一个内部类,用于处理消息发送的线程</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class MsgThread extends Thread {
private SocketIOClient client;
private MsgObject data;
//初始化方法
public MsgThread(SocketIOClient client, MsgObject data){
this.client = client;
this.data = data;
}
public void run() {
String temp = data.getMessage();
PositionObject posArray[] = new PositionObject[Integer.parseInt(data.getMessage())];
//此处模拟后端服务不断获取数据向客户端发送消息
while(true){
if(MsgServer.disconnectSet.contains(client.getSessionId())){
MsgServer.disconnectSet.remove(client.getSessionId());
//如果客户端断开则后台停止对应的线程
client.disconnect();
Thread.interrupted();
break;
}
data.setMessage(temp+":"+String.valueOf(Math.random()));
for(int i=0; i<posArray.length; i++){
//模拟多车GPS经纬度变化
PositionObject position = new PositionObject();
position.setX(116.380967+Math.random()*0.01);
position.setY(39.913285+Math.random()*0.01);
posArray[i] = position;
}
data.setPosArray(posArray);
//先客户端发送消息
client.sendEvent("msgevent", data);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
</code></pre></div></div>
<h2 id="4client端web实现">4.Client端(Web实现)</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<style type="text/css">
body, html,#allmap {width: 100%;height: 80%;overflow: hidden;margin:0;font-family:"微软雅黑";}
</style>
<script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=百度地图AK"></script>
<script src="js/socket.io/socket.io.js"></script>
<script src="http://code.jquery.com/jquery-1.10.1.min.js"></script>
<title>模拟车辆根据GPS数据实时移动</title>
</head>
<body>
<div id="allmap"></div>
<div id="status"></div>
<div id="console"></div>
<button type="button" onClick="connect('http://localhost:9092/position','msgevent')" id="send">connect</button>
<button type="button" onClick="disconnect()">disconnect</button>
<input id="msg" class="input-xlarge" type="text" placeholder="Type something..." />
<button type="button" onClick="sendMsg()" id="send">Send</button>
</body>
</html>
<script type="text/javascript">
// 百度地图API功能
var map = new BMap.Map("allmap");
map.centerAndZoom(new BMap.Point(116.404, 39.915), 15);
var myIcon = new BMap.Icon("http://developer.baidu.com/map/jsdemo/img/Mario.png", new BMap.Size(32, 70), { //小车图片
//offset: new BMap.Size(0, -5), //相当于CSS精灵
imageOffset: new BMap.Size(0, 0) //图片的偏移量。为了是图片底部中心对准坐标点。
});
var socket;
var eventN;
var carMkes=new Array();
var userName = 'user' + Math.floor((Math.random() * 1000) + 1);
var message;
function connect(url, eventName){
socket = io.connect(url, {
'force new connection': true,
reconnect: true,
'connect timeout': 5000,
'reconnection delay': 200
});
eventN = eventName;
socket.on('connect', function() {
$('#status').html('Client has connected to the server!');
});
socket.on(eventName, function(data) {
$('#console').html(data.userName + '=' + data.message);
doPosition(data);
});
socket.on('disconnect',function() {
$('#status').html('The client has disconnected!');
clearMap();
});
socket.on('reconnect', function() {
$('#status').html('Client has reconnected to the server!');
});
if(message!=null){
sendMsg();
}
}
//断开socket连接
function disconnect() {
socket.disconnect();
}
//向服务端发送数据
function emitMsg(param) {
socket.emit(eventN, param);
}
function sendMsg(){
message = $('#msg').val();
var param = {userName : userName,message : message};
emitMsg(param);
//模拟多辆车子
for(var i=0; i<message; i++){
var point = new BMap.Point(116.380967,39.913285);
var carMk = new BMap.Marker(point,{icon:myIcon});
carMkes[i] = carMk;
map.addOverlay(carMk);
}
}
//地图覆盖物定位显示,相当于车辆位置移动
function doPosition(data){
var posArray = data.posArray;
for(var j=0; j<posArray.length; j++){
var point = new BMap.Point(posArray[j].x,posArray[j].y);
carMkes[j].setPosition(point);
}
}
//清空地图覆盖物
function clearMap(){
map.clearOverlays();
carMkes = [];
}
</script>
</code></pre></div></div>
百度自定义图层应用
2017-04-04T00:00:00+00:00
http://www.blogways.net/blog/2017/04/04/web-map
<h2 id="1百度地图自定义图层说明">1.百度地图自定义图层说明</h2>
<p>百度地图API可以将用户上传到LBS云里的位置数据实时渲染成图层,然后通过CustomLayer对象叠加到地图上。目前LBS云支持用户存储poi数据,存储的字段除经纬度坐标外还包括名称、地址等属性信息。CustomLayer类提供读取LBS云数据接口,并自动渲染用户数据生成数据图层,同时提供单击叠加图层返回poi数据的功能。</p>
<h2 id="2用户数据上传">2.用户数据上传</h2>
<p>用户数据的上传有两种方式:</p>
<ul>
<li>HTTP接口上传方式</li>
<li>可视化标注方式</li>
</ul>
<ol>
<li>
<p>HTTP接口上传方式:HTTP接口方式首先需要创建数据存储空间(databox),然后再上传用户的poi数据。创建存储空间及poi发送的是POST请求,可以借助chrome浏览器下的Postman插件可视化发送请求。</p>
</li>
<li>
<p>可视化标注方式:用户进入云存储编辑页面后设置标注模式进行poi数据的录入。该方式的特点是简单、直观,但是数据量大时效率低下。</p>
</li>
</ol>
<p>方法二示例:</p>
<p>(1)进入LBS云,上传LBS数据到百度云存储。</p>
<p><img src="/images/zhaojiajun/20170404img02.png" alt="20170404img02" /></p>
<p>(2)点击“数据管理平台”</p>
<p><img src="/images/zhaojiajun/20170404img03.png" alt="20170404img03" /></p>
<p>(3)首先需要先创建数据存储空间(databox),创建完成后显示ID(该ID对应代码中的geotableId)</p>
<p><img src="/images/zhaojiajun/20170404img04.png" alt="20170404img04" /></p>
<p>(4)点击“表属性管理”可以添加自定义表字段</p>
<p><img src="/images/zhaojiajun/20170404img05.png" alt="20170404img05" /></p>
<p>(5)点击“表数据管理”可以新增或者批量新增POI数据</p>
<p><img src="/images/zhaojiajun/20170404img06.png" alt="20170404img06" /></p>
<p>(6)保存并发布</p>
<h2 id="3用户数据图层展示">3.用户数据图层展示</h2>
<h4 id="百度地图ak">百度地图AK</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript" src="http://api.map.baidu.com/api?v=2.0&ak=对应该LBS云存储的用户AK"></script>
</code></pre></div></div>
<p>CustomLayer构造函数可以通过接收数据存储空间id(geotable id)参数生成用户数据图层,存储空间id可以在创建数据存储时获得。</p>
<h4 id="代码如下">代码如下:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//根据daboxId创建自定义图层,用户可用自己创建的geotableid替换30960
var customLayer=new BMap.CustomLayer({
geotableId: 165288,
q: '', //检索关键字
tags: '', //空格分隔的多字符串
filter: '' //过滤条件,参考http://developer.baidu.com/map/lbs-geosearch.htm#.search.nearby
});
</code></pre></div></div>
<p>将用户自定义图层添加到地图上的方法跟添加Tilelayer对象方式相同,即:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>map.addTileLayer(customLayer);//添加自定义图层
</code></pre></div></div>
<h4 id="显示效果如下">显示效果如下:</h4>
<p><img src="/images/zhaojiajun/20170404img08.png" alt="20170404img08" /></p>
<p>JSAPI v1.5提供单击用户数据图层事件,并支持返回点击poi点的信息。</p>
<h4 id="代码如下-1">代码如下:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>customLayer.addEventListener('onhotspotclick', callback);//单击图层事件
function callback(e)//单击热点图层
{
var customPoi = e.customPoi; //获取poi对象
var contentPoi=e.content;//poi的自定义字段
var content = '<p style="width:280px;margin:0;line-height:20px;">地址:' + customPoi.address + '<br>电话:' + customPoi.title + '</p>';
alert(contentPoi.field1);//自定义字段
alert(contentPoi.field2);
var searchInfoWindow = new BMapLib.SearchInfoWindow(map, content, { //带检索的信息窗口
title : customPoi.title, //标题
width : 290, //宽度
height : 40, //高度
panel : "panel", //检索结果面板
enableAutoPan : true, //自动平移
enableSendToPhone : true, //是否显示发送到手机按钮
searchTypes : [
BMAPLIB_TAB_SEARCH, //周边检索
BMAPLIB_TAB_TO_HERE, //到这里去
BMAPLIB_TAB_FROM_HERE //从这里出发
]
});
var point = new BMap.Point(customPoi.point.lng, customPoi.point.lat);
searchInfoWindow.open(point);
}
</code></pre></div></div>
<h4 id="自定义字段值的展示">自定义字段值的展示</h4>
<p><img src="/images/zhaojiajun/20170404img07.png" alt="20170404img07" /></p>
百度地图API使用入门二
2017-03-22T00:00:00+00:00
http://www.blogways.net/blog/2017/03/22/baidu-map-2
<p>上一篇主要介绍了百度地图的基本组件,这一篇主要探讨一下百度地图可以提供的服务及扩展。</p>
<h2 id="一服务">一、服务</h2>
<h3 id="1搜索">1、搜索</h3>
<p><code class="language-plaintext highlighter-rouge">LocalSearch</code>用于位置检索、周边检索和范围检索,可以自定义结果的显示设置。包括:</p>
<ul>
<li>普通搜索:<code class="language-plaintext highlighter-rouge">search</code>方法。</li>
<li>周边搜索:<code class="language-plaintext highlighter-rouge">searchNearby</code>方法,可指定坐标点和半径。</li>
<li>矩形范围搜索:<code class="language-plaintext highlighter-rouge">searchInBounds</code>方法,可指定一个<code class="language-plaintext highlighter-rouge">Bounds</code>做为搜索的矩形区域。</li>
</ul>
<p>下例是在地图上搜索“联通营业厅”,并在地图上显示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var local = new BMap.LocalSearch(map, {
renderOptions: {
map: map
}
});
local.search('联通营业厅');
</code></pre></div></div>
<h3 id="2导航和路况">2、导航和路况</h3>
<ul>
<li>
<p>公交导航:<code class="language-plaintext highlighter-rouge">TransitRoute</code> 导航要提供起点和终点。返回的结果是<code class="language-plaintext highlighter-rouge">TransitRouteResult</code>。</p>
<p>下例是公交导航结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var transit = new BMap.TransitRoute(map, {
renderOptions: {
map: map
}
});
transit.search("古平岗", "雨花大数据园");
</code></pre></div> </div>
</li>
<li>驾车导航:<code class="language-plaintext highlighter-rouge">DrivingRoute</code> 导航要提供起点和终点。返回的结果是<code class="language-plaintext highlighter-rouge">DrivingRouteResult</code>。</li>
<li>
<p>步行导航:<code class="language-plaintext highlighter-rouge">WalkingRoute</code> 导航要提供起点和终点。返回的结果是<code class="language-plaintext highlighter-rouge">WalkingRouteResult</code>。</p>
<p>下例是把导航结果显示在“r-result”面板上:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var walking = new BMap.WalkingRoute(map, {
renderOptions: {
map: map,
panel: "r-result", // 显示结果的面板
autoViewport: true
}
});
walking.search("古平岗", "新模范马路");
</code></pre></div> </div>
</li>
</ul>
<h3 id="3位置信息服务">3、位置信息服务</h3>
<h4 id="地址解析-geocoder">地址解析 <code class="language-plaintext highlighter-rouge">Geocoder</code></h4>
<ul>
<li>根据地址描述获得坐标:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var myGeo = new BMap.Geocoder();
// 将地址解析结果显示在地图上,并调整地图视野
myGeo.getPoint("南京市古平岗4号", function(point){
if (point) {
map.centerAndZoom(point, 16);
map.addOverlay(new BMap.Marker(point));
}
}, "南京市");
</code></pre></div> </div>
</li>
<li>反向地理编码:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var myGeo = new BMap.Geocoder();
// 根据坐标得到地址描述
myGeo.getLocation(new BMap.Point(116.364, 39.993), function(result){
if (result){
alert(result.address);
}
});
</code></pre></div> </div>
</li>
</ul>
<h4 id="本地城市-localcity">本地城市 <code class="language-plaintext highlighter-rouge">LocalCity</code></h4>
<p>获取用户所在的城市位置信息。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var myCity = new BMap.LocalCity();
</code></pre></div></div>
<h2 id="二扩展工具">二、扩展工具</h2>
<p>工具是百度地图提供的开源库,要使用工具中的功能要把开源库导入。按照我的理解我把它们分成几类:</p>
<h3 id="1地图标注类这些工具主要是对地图的标注信息功能进行扩展如">1、地图标注类:这些工具主要是对地图的标注信息功能进行扩展。如:</h3>
<ul>
<li>MarkerTool:自定义标注点和标注点信息。</li>
<li>MarkerCluster:解决加载大量点要素到地图上产生覆盖现象的问题。</li>
<li>TextIconOverley:自定义覆盖物扩展。</li>
<li>等等。</li>
</ul>
<h3 id="2路线类">2、路线类</h3>
<ul>
<li>LuShu:百度地图的路书。实现marker沿路线运动,并有暂停等功能。</li>
<li>DistanceTool:测量地图上自定义线段的距离。</li>
<li>DrawingManager:提供鼠标绘制点、线、面、多边形(矩形、圆)的编辑工具条的开源代码库。</li>
</ul>
<h3 id="3可视化扩展">3、可视化扩展</h3>
<ul>
<li>大数据可视化库:用来展示大量地理信息数据,可以自定义类型。</li>
<li>热力图:提供热力图可视化展现功能。</li>
</ul>
<h2 id="三实例">三、实例</h2>
<p>现在我们完成一个功能:在地图上添加一个搜索框,用户可以自定义标记点,并提供从用户坐标到标注点的公交路线服务。</p>
<h3 id="1首先注册百度开发者创建一个应用拿到应用密钥">1、首先注册百度开发者,创建一个应用,拿到应用密钥。</h3>
<h3 id="2创建一个html文件引入百度地图api">2、创建一个HTML文件,引入百度地图API</h3>
<p>注意替换掉<code class="language-plaintext highlighter-rouge">密钥</code>。由于我们还用到三个扩展,所以还用引入<code class="language-plaintext highlighter-rouge">SearchControl</code>,<code class="language-plaintext highlighter-rouge">MarkerTool</code>和<code class="language-plaintext highlighter-rouge">SearchInforWindow</code>。
<img src="/images/jyjsjd/secret_key.png" alt="secret_key.png" /></p>
<h3 id="3在一个单独的js文件中写代码">3、在一个单独的<code class="language-plaintext highlighter-rouge">js</code>文件中写代码</h3>
<p>以下只介绍几个主要功能的实现:</p>
<ul>
<li>
<p>获取用户坐标</p>
<p>坐标可以在<code class="language-plaintext highlighter-rouge">GeoLocation</code>对象的<code class="language-plaintext highlighter-rouge">getCurrentPosition</code>方法中的回调函数中得到:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var geolocation = new BMap.Geolocation();
geolocation.getCurrentPosition(function getCoords(r) {
if (this.getStatus() === BMAP_STATUS_SUCCESS) {
start = new BMap.Point(r.point.lng, r.point.lat);
}
</code></pre></div> </div>
</li>
<li>添加搜索框
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var searchControl = new BMapLib.SearchControl({
container: 'searchbox', //存放控件的容器
map: map, //关联地图对象
type: LOCAL_SEARCH //检索类型
});
</code></pre></div> </div>
</li>
<li>添加自定义信息窗口
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var infoWindow = new BMapLib.SearchInfoWindow(map, '', {
width: 290,
height: 105,
title: '您的位置', // 信息窗口标题
panel: 'panel', //检索结果面板
enableAutoPan: true,
searchType: [
BMAPLIB_TAB_SEARCH, //周边检索
BMAPLIB_TAB_TO_HERE, //到这里去
BMAPLIB_TAB_FROM_HERE //从这里出发
]
});
</code></pre></div> </div>
<p>效果:</p>
<p><img src="/images/jyjsjd/infowindow2.png" alt="infowindow2.png" /></p>
</li>
<li>添加自定义标注
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 图上标注
var mkrTool = new BMapLib.MarkerTool(map, {
autoClose: true,
followText: '标注'
});
// 点击右键表示标注功能开启
map.addEventListener('rightclick', function (e) {
mkrTool.open();
});
</code></pre></div> </div>
</li>
<li>公交路线获取和展示
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>walking = new BMap.TransitRoute(map, {
renderOptions: {
map: map,
panel: 'result' // 信息展示区域ID
},
onResultsHtmlSet: function () { // 得到公交路线之后的回调函数:显示结果到result面板
$('#result').show();
}
});
</code></pre></div> </div>
<p>完整代码<a href="http://10.20.16.78:3000/jingyang/baidu_map">请看</a>。</p>
</li>
</ul>
百度地图API使用入门一
2017-03-21T00:00:00+00:00
http://www.blogways.net/blog/2017/03/21/baidu-map-1
<h2 id="一百度地图api简介">一、百度地图API简介</h2>
<p><code class="language-plaintext highlighter-rouge">百度地图API</code>是一套为开发者免费提供的基于百度地图的应用程序接口,包括JavaScript、iOS、Andriod、静态地图、Web服务等多种版本,提供基本地图、位置搜索、周边搜索、公交驾车导航、定位服务、地理编码及逆地理编码等丰富功能。</p>
<p>开发者要使用百度地图API首先要在百度地图开放平台注册,建立应用并取得相应的<code class="language-plaintext highlighter-rouge">应用密匙</code>。密匙在引入百度地图依赖的时候要用到。</p>
<p>本文基于百度地图的<code class="language-plaintext highlighter-rouge">JavaScript API</code>来介绍它的主要功能和组件。</p>
<h2 id="二地图核心类">二、地图核心类</h2>
<p>百度地图中直接和地图相关的类称为<code class="language-plaintext highlighter-rouge">核心类</code>。</p>
<ul>
<li>Map:用来实例化一个<code class="language-plaintext highlighter-rouge">地图</code>。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map = new BMap.Map("container"); // 初始化地图
</code></pre></div> </div>
</li>
<li>PanOptions:用来控制地图的中心点的移动。它没有构造函数,可通过对象字面量形式表示,以参数形式传递给Map的panTo,panBy方法。</li>
<li>MapOptions:用来表示Map构造函数的可选项。它没有构造函数,可通过对象字面量形式表示。</li>
<li>MapStyle:用来设置地图样式,使用对象字面量形式表示,不可实例化。</li>
<li>Viewport:表示视野,不可实例化,通过对象字面量形式表示。</li>
<li>Viewport:作为 map.getViewport 与 map.setViewport 方法的可选参数,不可实例化。</li>
</ul>
<p>大家可以看出来,虽然在上面列了这么多类,实际上有用的类只有Map一个,其他的类都是作为实例化Map的参数或者Map的方法的参数。</p>
<h2 id="三地图基础类">三、地图基础类</h2>
<ul>
<li>Point:表示一个<code class="language-plaintext highlighter-rouge">地理坐标点</code>。它的构造函数有两个参数,即点的经度和纬度。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var point = new BMap.Point(116.027143, 39.772348);
</code></pre></div> </div>
</li>
<li>Pixel:表示地图上的一个<code class="language-plaintext highlighter-rouge">像素点</code>。</li>
<li>Bounds:表示<code class="language-plaintext highlighter-rouge">地理坐标</code>的<code class="language-plaintext highlighter-rouge">矩形区域</code>。该类可以把地图的显示范围设定在指定区域。它的构造函数有两个参数,即两个表示坐标的Point。
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var bound = new BMap.Bounds(new BMap.Point(116.027143, 39.772348),new BMap.Point(116.832025, 40.126349));
</code></pre></div> </div>
</li>
<li>Size:表示以<code class="language-plaintext highlighter-rouge">像素</code>表示一个<code class="language-plaintext highlighter-rouge">矩形区域</code>的大小。</li>
</ul>
<h2 id="四地图控件类">四、地图控件类</h2>
<p>百度地图上负责与地图交互的UI元素称为<code class="language-plaintext highlighter-rouge">控件</code>。</p>
<ul>
<li>Control:控件的<code class="language-plaintext highlighter-rouge">抽象基类</code>,所有控件均继承此类的方法、属性。通过此类可实现自定义控件。</li>
<li>
<p>NavigationControl:地图<code class="language-plaintext highlighter-rouge">平移缩放控件</code>,PC端默认位于地图左上方,它包含控制地图的平移和缩放的功能。</p>
<p><img src="/images/jyjsjd/navigationcontrol.png" alt="navigationcontrol.png" /></p>
</li>
<li>
<p>OverviewMapControl:<code class="language-plaintext highlighter-rouge">缩略地图</code>控件,默认位于地图右下方,是一个可折叠的缩略地图。</p>
<p><img src="/images/jyjsjd/overviewmapcontrol.png" alt="overviewmapcontrol.png" /></p>
</li>
<li>
<p>ScaleControl:<code class="language-plaintext highlighter-rouge">比例尺</code>控件,默认位于地图左下方,显示地图的比例关系。</p>
<p><img src="/images/jyjsjd/scalecontrol.png" alt="scalecontrol.png" /></p>
</li>
<li>
<p>MapTypeControl:<code class="language-plaintext highlighter-rouge">地图类型</code>控件,默认位于地图右上方。</p>
<p><img src="/images/jyjsjd/maptypecontrol.png" alt="maptypecontrol.png" /></p>
</li>
<li>CopyrightControl:<code class="language-plaintext highlighter-rouge">版权</code>控件,默认位于地图左下方。</li>
<li>
<p>GeolocationControl:<code class="language-plaintext highlighter-rouge">定位</code>控件,默认位于地图左下方。</p>
<p><img src="/images/jyjsjd/geolocationcontrol.png" alt="geolocationcontrol.png" /></p>
</li>
</ul>
<p>要在地图上添加这些控件非常简单,创建好自己的地图然后直接往地图上添加就行了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map = new BMap.Map("container"); // 初始化地图
map.addControl(new BMap.NavigationControl());
map.addControl(new BMap.ScaleControl());
map.addControl(new BMap.OverviewMapControl());
map.addControl(new BMap.MapTypeControl());
</code></pre></div></div>
<h2 id="五地图覆盖物类">五、地图覆盖物类</h2>
<p>所有叠加或覆盖到地图的内容,统称为地图<code class="language-plaintext highlighter-rouge">覆盖物</code>。</p>
<ul>
<li>Overlay:覆盖物的<code class="language-plaintext highlighter-rouge">抽象基类</code>,所有的覆盖物均继承此类的方法。</li>
<li>
<p>Marker:标注表示地图上的<code class="language-plaintext highlighter-rouge">点</code>,可自定义标注的图标。</p>
<p><img src="/images/jyjsjd/marker.png" alt="marker.png" /></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var marker = new BMap.Marker(point);
map.addOverlay(marker);
</code></pre></div></div>
<ul>
<li>
<p>Label:表示地图上的<code class="language-plaintext highlighter-rouge">文本标注</code>,可以自定义标注的文本内容。</p>
<p><img src="/images/jyjsjd/label.png" alt="label.png" /></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var opts = {
position: point, // 指定文本标注所在的地理位置
offset: new BMap.Size(30, -30) //设置文本偏移量
}
var label = new BMap.Label("文本标注", opts); // 创建文本标注对象
map.addOverlay(label);
</code></pre></div></div>
<ul>
<li>
<p>Polyline:表示地图上的<code class="language-plaintext highlighter-rouge">折线</code>。</p>
<p><img src="/images/jyjsjd/polyline.png" alt="polyline.png" /></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var polyline = new BMap.Polyline([
new BMap.Point(116.399, 39.910),
new BMap.Point(116.405, 39.920)
],
{strokeColor:"blue", strokeWeight:6, strokeOpacity:0.5}
);
map.addOverlay(polyline);
</code></pre></div></div>
<ul>
<li>
<p>Polygon:表示地图上的<code class="language-plaintext highlighter-rouge">多边形</code>。多边形类似于<code class="language-plaintext highlighter-rouge">闭合的折线</code>。</p>
</li>
<li>
<p>Circle: 表示地图上的<code class="language-plaintext highlighter-rouge">圆</code>。</p>
<p><img src="/images/jyjsjd/circle.png" alt="circle.png" /></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var circle = new BMap.Circle();
circle.setCenter(point); // 设置圆心
circle.setRadius(1000); // 设置半径(米)
circle.setStrokeColor('blue');
circle.setStrokeStyle('dashed');
map.addOverlay(circle);
</code></pre></div></div>
<ul>
<li>
<p>InfoWindow:表示地图上的<code class="language-plaintext highlighter-rouge">信息窗口</code>,可以展示更为丰富的文字和多媒体信息。注意:同一时刻只能有一个信息窗口在地图上打开。</p>
<p><img src="/images/jyjsjd/infowindow.png" alt="infowindow.png" /></p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var opts = {
width: 100, // 信息窗口宽度
height: 80, // 信息窗口高度
title: "亚信科技", // 信息窗口标题
}
var infoWindow = new BMap.InfoWindow("地址:江苏省南京市古平岗4号紫金智梦园B座亚信科技", opts); // 创建信息窗口对象
marker.addEventListener("click", function () {
map.openInfoWindow(infoWindow, point); //开启信息窗口
});
</code></pre></div></div>
<h2 id="六事件">六、事件</h2>
<p>百度地图API拥有一个自己的事件模型,程序员可监听地图API对象的自定义事件,使用方法和DOM事件类似。但请注意,地图API事件是独立的,与标准DOM事件不同。</p>
<h3 id="1事件监听">1、事件监听</h3>
<p>百度地图API中的大部分对象都含有addEventListener方法,可以通过该方法来监听对象事件。</p>
<p>addEventListener方法有两个参数:监听的事件名称和事件触发时调用的函数。在下面示例中,用户点击地图时,会弹出一个警告框。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map = new BMap.Map("container");
map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
map.addEventListener("click", function(){
alert("您点击了地图。");
});
</code></pre></div></div>
<h3 id="2移除监听事件">2、移除监听事件</h3>
<p>每个API对象提供了removeEventListener用来移除事件监听函数。</p>
<p>下面示例中,用户第一次点击地图会触发事件监听函数,在函数内部对事件监听进行了移除,因此后续的点击操作则不会触发监听函数。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map = new BMap.Map("container");
map.centerAndZoom(new BMap.Point(116.404, 39.915), 11);
function showInfo(e){
alert(e.point.lng + ", " + e.point.lat);
map.removeEventListener("click", showInfo);
}
map.addEventListener("click", showInfo);
</code></pre></div></div>
Scrapy 爬虫框架入门
2017-01-08T00:00:00+00:00
http://www.blogways.net/blog/2017/01/08/scrapy
<h2 id="一网络爬虫">一、网络爬虫</h2>
<p>网络爬虫或者叫网络机器人是用来自动采集网络内容的工具,通常被用于搜索引擎。基于我自己的理解,要写一个网络爬虫通常要解决以下几个问题:</p>
<ul>
<li>有哪些网络资源是需要被爬取的。网络上的资源多种多样,有单纯的HTML页面,也有各种格式的文件、音频或视频等等,在本文中我仅爬取HTML页面中的文本,所以这里的首要问题是哪些URL是爬虫要处理的。</li>
<li>如何从文本中提取到我想要的信息。HTML文本中包含了很多无用的信息,包括标签等,网络爬虫最终提供给我的应该是我感兴趣的内容。</li>
<li>如何把爬取到的信息保存下来。爬虫得到的有用信息要通过数据库或其他有效途径保存下来。</li>
<li>其他问题:包括网站的反爬虫策略、爬虫策略、错误处理能力等等。</li>
</ul>
<p>以上几个问题如果单独提出来可能大家都有解决的办法,但是整合到一起处理,还是有一些麻烦的。Scrapy这个爬虫框架就提供给我们一个简单的解决方案。</p>
<h2 id="二scrapy">二、Scrapy</h2>
<p>Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。它包含了几大组件:</p>
<ul>
<li>Scrapy engine:它负责数据流在系统中所有组件中流动,相当于爬虫的“大脑”,是调度中心。</li>
<li>Downloader:负责获取页面并提供给引擎。</li>
<li>Spider:是真正的爬虫,负责URL和解析HTML提供item。</li>
<li>Pipeline:负责处理spider提供的item。典型的应用有清理错误、持久化等。</li>
<li>Middlewares:处理spider的输入和输出。典型的应用有网络代理验证。</li>
</ul>
<p><img src="/images/jyjsjd/scrapy_workflow.png" alt="scrapy_workflow.png" /></p>
<h2 id="三新建一个项目">三、新建一个项目</h2>
<p>在写爬虫之前我们要新建一个项目,Scrapy非常贴心的为我们提供了一个命令帮助我们建立项目框架:</p>
<p><code class="language-plaintext highlighter-rouge">scrapy startproject [project_name]</code></p>
<p>这个命令建立的项目框架如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>project_name/
scrapy.cfg
db/
__init__.py
config.py
spiders/
__init__.py
spider.py
items.py
pipelines.py
settings.py
spiders/
__init__.py
...
</code></pre></div></div>
<ul>
<li>scrapy.cfg: 项目的配置文件。</li>
<li>db/config.py:数据库的设置文件。</li>
<li>spiders/:爬虫所在的目录。</li>
<li>project_name/items.py: 项目中的item文件。</li>
<li>project_name/pipelines.py: 项目中的pipelines文件。</li>
<li>project_name/settings.py: 项目的设置文件。包括数据库设置等。</li>
<li>project_name/spiders/: 放置spider代码的目录。</li>
</ul>
<h2 id="四编写一个爬虫">四、编写一个爬虫</h2>
<p>房子这两年大热,这里我写一个爬取链家网二手房的爬虫为例。打开<a href="http://nj.lianjia.com/ershoufang/">网页</a>来观察一下结构:</p>
<p><img src="/images/jyjsjd/page_example.png" alt="page_example.png" /></p>
<p>网页看上去十分规整,包含了很多我们想要的信息,包括总价、单价、面积和位置等。</p>
<h3 id="声明item">声明item</h3>
<p>Item是spider得到的结构化数据。</p>
<p>打开items.py,用Python简单的class定义语法来新建一个类:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import scrapy
from scrapy.loader.processors import MapCompose, TakeFirst
class HomeLinkItem(scrapy.Item):
hid = scrapy.Field()
url = scrapy.Field()
title = scrapy.Field()
price = scrapy.Field()
room = scrapy.Field()
htype = scrapy.Field()
area = scrapy.Field()
areaName = scrapy.Field()
</code></pre></div></div>
<h3 id="爬虫">爬虫</h3>
<p>在<code class="language-plaintext highlighter-rouge">spiders/</code>目录下新建home_link.py,作为爬虫。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class HomeSpider(CrawlSpider):
name = "home_link"
def parse_item(self, response):
...
</code></pre></div></div>
<p>爬虫要知道从哪里爬取,所以第一步是告诉爬虫域名和开始的URL:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>allow_domains = ['nj.lianjia.com']
start_urls = ['http://nj.lianjia.com/ershoufang/']
</code></pre></div></div>
<p>爬虫开始抓取网页之后还有告诉爬虫哪些URL是要处理的,也就是爬取的规则:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>rules = (
Rule(LinkExtractor(allow=(r'\d+\.html', r'pg\d+\\')), callback='parse_item', follow=True),
)
</code></pre></div></div>
<p>大家可以打开刚才那个网页中的任意一个房子,观察它的URL。可以发现,URL都是域名加一串数字构成的。如:
<code class="language-plaintext highlighter-rouge">http://nj.lianjia.com/ershoufang/103100443605.html</code>。</p>
<p>这里可以用正则表达式写多个规则,我在这里只写了一个。</p>
<h3 id="解析网页">解析网页</h3>
<p>爬虫中的<code class="language-plaintext highlighter-rouge">parse_item</code>方法就是用来解析网页,并返回一个item的。</p>
<p>Python有很多解析HTML的优秀库,如<code class="language-plaintext highlighter-rouge">BeautifulSoup</code>,我在这里专注于爬虫就不用这些库了。实际上我们可以非常简单地通过XPath语法来获取网页上我们感兴趣的内容。XPath非常简单,只要熟悉CSS选择器就可以快速上手。</p>
<p>我们再来看一下网页的源码,如我们感兴趣的<em>总价</em>:</p>
<p><img src="/images/jyjsjd/price_tag.png" alt="price_tag.png" /></p>
<p>可以看到<em>总价</em>位于<code class="language-plaintext highlighter-rouge">class</code>为<code class="language-plaintext highlighter-rouge">price</code>的<code class="language-plaintext highlighter-rouge">div</code>中的class为<code class="language-plaintext highlighter-rouge">total</code>的<code class="language-plaintext highlighter-rouge">div</code>中(为看起来方便没有截更多,实际上他们还被<code class="language-plaintext highlighter-rouge">class</code>为<code class="language-plaintext highlighter-rouge">content</code>的<code class="language-plaintext highlighter-rouge">div</code>包含)。</p>
<p>那么要取得总价,XPath写成:</p>
<p><code class="language-plaintext highlighter-rouge">//div[@class="content"]/div[@class="price "]/span[@class="total"]/text()</code></p>
<p>其他的字段都是类似我就不再赘述。</p>
<p>最终<code class="language-plaintext highlighter-rouge">parse_item</code>方法写成:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>def parse_item(self, response):
l = ItemLoader(item=HomeLinkItem(), response=response)
l.add_xpath('hid', '//div[@class="content"]/span[@class="sharethis"]/span[@class="erweibox"]/img')
l.add_value('url', response.url)
l.add_xpath('title', '//title/text()')
l.add_xpath('price', '//div[@class="content"]/div[@class="price "]/span[@class="total"]/text()')
l.add_xpath('room', '//div[@class="content"]/div[@class="houseInfo"]/div[@class="room"]'
'/div[@class="mainInfo"]/text()')
l.add_xpath('htype', '//div[@class="content"]/div[@class="houseInfo"]/div[@class="type"]'
'/div[@class="mainInfo"]/text()')
l.add_xpath('area', '//div[@class="content"]/div[@class="houseInfo"]/div[@class="area"]'
'/div[@class="mainInfo"]/text()')
l.add_xpath('areaName', '//div[@class="content"]/div[@class="aroundInfo"]/div[@class="communityName"]'
'/a[@class="info"]/text()')
return l.load_item()
</code></pre></div></div>
<p>返回一个item。</p>
<h3 id="表的定义">表的定义</h3>
<p>最终我们想把数据保存在数据库中。Scrapy使用SQLAlchemy来帮助完成持久化,它相当于Hibernate,工作原理类似。</p>
<p>定义一个类<code class="language-plaintext highlighter-rouge">house</code>来保存数据,它对应数据库表<code class="language-plaintext highlighter-rouge">house</code>。sqlalchemy.Column()这句代码定义字段和它的类型等。
在db/下新建house.py:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import sqlalchemy
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
class House(Base):
__tablename__ = 'house'
hid = sqlalchemy.Column(sqlalchemy.String, primary_key=True, name='id')
url = sqlalchemy.Column(sqlalchemy.String)
title = sqlalchemy.Column(sqlalchemy.String)
price = sqlalchemy.Column(sqlalchemy.String)
room = sqlalchemy.Column(sqlalchemy.String)
htype = sqlalchemy.Column(sqlalchemy.String, name='type')
area = sqlalchemy.Column(sqlalchemy.String)
areaName = sqlalchemy.Column(sqlalchemy.String, name='area_name')
</code></pre></div></div>
<p>数据库表<code class="language-plaintext highlighter-rouge">house</code>的定义如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CREATE TABLE `house` (
`id` varchar(30) NOT NULL DEFAULT '',
`url` varchar(256) DEFAULT NULL,
`title` varchar(200) DEFAULT NULL,
`price` varchar(10) DEFAULT NULL,
`room` varchar(100) DEFAULT NULL,
`type` varchar(100) DEFAULT NULL,
`area` varchar(50) DEFAULT NULL,
`area_name` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
</code></pre></div></div>
<p>这里我使用的是MySQL,打开db/config.py,写入数据库连接字符串:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>engine = create_engine('mysql+mysqlconnector://user:pasword@localhost:3306/test')
DBSession = sessionmaker(bind=engine)
</code></pre></div></div>
<h3 id="保存结果">保存结果</h3>
<p>Pipelines的典型应用就是持久化数据。它接收Spider返回的item对象并对item进行处理。在这里我们把item包装成House类,并持久化。
打开<code class="language-plaintext highlighter-rouge">pipelines.py</code>,并加入以下内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class DataBasePipeline(object):
def open_spider(self, spider):
self.session = DBSession()
def process_item(self, item, spider):
h = House(
hid=item.get('hid'),
url=item.get('url'),
title=item.get('title'),
price=item.get('price'),
room=item.get('room'),
htype=item.get('htype'),
area=item.get('area'),
areaName=item.get('areaName')
)
self.session.add(h)
self.session.commit()
def close_spider(self, spider):
self.session.close()
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">open_spider</code>方法取得数据库session。<code class="language-plaintext highlighter-rouge">process_item</code>方法处理对象,这里是将House对象存到数据库。</p>
<h3 id="运行爬虫">运行爬虫</h3>
<p>CD到项目目录,运行命令:<code class="language-plaintext highlighter-rouge">scrapy crawl home_link</code>。(home_link是爬虫的名字)</p>
<p><img src="/images/jyjsjd/scrapy_run.png" alt="scrapy_run.png" /></p>
<p>可以看到Scrapy取得了一个<code class="language-plaintext highlighter-rouge">house</code>。</p>
PM2-NodeJs生产系统进程管理器
2016-12-19T00:00:00+00:00
http://www.blogways.net/blog/2016/12/19/pm2
<h2 id="一node程序异常解决方案">一、node程序异常解决方案</h2>
<p>由于nodeJs是单线程机制,在实际生产中开发人员一个小小的bug就可能导致整个系统的崩溃,这样导致整个系统的稳定性大大降低。如在我们自己开发的凤来平台日常维护中目前经常就会碰到前台程序无法访问的情况。对于此类问题,我总结了大概能有以下几种解决方案:</p>
<ul>
<li>程序bug问题就直接修复bug,解决让系统异常停止的bug,但是这个无法最终解决问题,因为我们不能保证我们系统一定没有bug了;</li>
<li>通过捕捉uncaughtException系统异常来阻止Node进程结束,这种做法无法获取异常明细,而且有内存泄露的风险,不建议这样做;</li>
<li>通过forever、PM2等工具实时监控node进程和集群,并能保证系统进程正常运行及实时故障日志排查。PM2较forever支持更多功能,所以本文也就主要介绍PM2。</li>
</ul>
<h2 id="二为什么选择pm2">二、为什么选择PM2</h2>
<h3 id="pm2的主要特性有">PM2的主要特性有:</h3>
<ul>
<li>内建负载均衡</li>
<li>后台运行</li>
<li>0秒停机重载,能够保证服务进程时刻运行</li>
<li>支持自定义启动脚本</li>
<li>自动停止不稳定的进程</li>
<li>控制台检测</li>
<li>提供 HTTP API</li>
<li>支持第三方扩展监控</li>
</ul>
<h2 id="三pm2安装及使用">三、PM2安装及使用</h2>
<h3 id="pm2安装">PM2安装</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> npm install pm2 -g
</code></pre></div></div>
<h3 id="pm2-常用命令">PM2 常用命令</h3>
<ul>
<li>
<p>General</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 start app.js # Start, Daemonize and auto-restart application (Node)
$ pm2 start app.py # Start, Daemonize and auto-restart application (Python)
$ pm2 start npm -- start # Start, Daemonize and auto-restart Node application
</code></pre></div> </div>
</li>
<li>
<p>Cluster Mode (Node.js only)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 start app.js -i 4 # Start 4 instances of application in cluster mode
# it will load balance network queries to each app
$ pm2 reload all # Zero Second Downtime Reload
$ pm2 scale [app-name] 10 # Scale Cluster app to 10 process
# Process Monitoring
$ pm2 list # List all processes started with PM2
$ pm2 monit # Display memory and cpu usage of each app
$ pm2 show [app-name] # Show all informations about application
</code></pre></div> </div>
</li>
<li>
<p>Log management</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 logs # Display logs of all apps
$ pm2 logs [app-name] # Display logs for a specific app
$ pm2 logs --json # Logs in JSON format
$ pm2 flush
$ pm2 reloadLogs
</code></pre></div> </div>
</li>
<li>
<p>Process State Management</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 start app.js --name="api" # Start application and name it "api"
$ pm2 start app.js -- -a 34 # Start app and pass option "-a 34" as argument
$ pm2 start app.js --watch # Restart application on file change
$ pm2 start script.sh # Start bash script
$ pm2 start app.json # Start all applications declared in app.json
$ pm2 reset [app-name] # Reset all counters
$ pm2 stop all # Stop all apps
$ pm2 stop 0 # Stop process with id 0
$ pm2 restart all # Restart all apps
$ pm2 gracefulReload all # Graceful reload all apps in cluster mode
$ pm2 delete all # Kill and delete all apps
$ pm2 delete 0 # Delete app with id 0
</code></pre></div> </div>
</li>
<li>
<p>Startup/Boot management</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 startup # Detect init system, generate and configure pm2 boot on startup
$ pm2 save # Save current process list
$ pm2 resurrect # Restore previously save processes
$ pm2 unstartup # Disable and remove startup system
$ pm2 update # Save processes, kill PM2 and restore processes
$ pm2 generate # Generate a sample json configuration file
</code></pre></div> </div>
</li>
<li>
<p>Deployment</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 deploy app.json prod setup # Setup "prod" remote server
$ pm2 deploy app.json prod # Update "prod" remote server
$ pm2 deploy app.json prod revert 2 # Revert "prod" remote server by 2
</code></pre></div> </div>
</li>
<li>
<p>Module system</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ pm2 module:generate [name] # Generate sample module with name [name]
$ pm2 install pm2-logrotate # Install module (here a log rotation system)
$ pm2 uninstall pm2-logrotate # Uninstall module
$ pm2 publish # Increment version, git push and npm publish
</code></pre></div> </div>
</li>
</ul>
<h2 id="四pangu-web部署例子">四、pangu-web部署例子</h2>
<h3 id="shell方式启动这里只是简单例子实际可以改造成node-集群方式">shell方式启动(这里只是简单例子,实际可以改造成node 集群方式)</h3>
<p>1 新建startWeb.sh:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #!/bin/bash
node web_server.js --host 127.0.0.1 -xx 27017 -oo 27017 --noauth --port 5000
</code></pre></div></div>
<p>2 启动web进程 pm2 start startWeb.sh</p>
<p>3 查看pm2列表</p>
<p><img src="/images/tangsz/pm2-list.png" alt="pm2-list" /></p>
<p>4 PM2监控进程cpu、内存</p>
<p><img src="/images/tangsz/pm2-monit.png" alt="pm2-monit" /></p>
<p>5 PM2 http api</p>
<p><img src="/images/tangsz/pm2-api.png" alt="pm2-api" /></p>
<h2 id="五pm2-扩展应用">五、PM2 扩展应用</h2>
<h3 id="使用keymetrics实时监控nodejs程序">使用keymetrics实时监控Node.js程序</h3>
<p>PM2配合keymetrics能实时监控node.js程序的运行,达到监控node.js程序的目的,以下为PM2官网监控截图:</p>
<p><img src="/images/tangsz/pm2-Keymetrics.png" alt="pm2-Keymetrics" /></p>
<h3 id="pm25-开源node服务监控平台">PM25 开源node服务监控平台</h3>
<p>由于Keymetrics是一款商业服务且价格不菲,虽有两台服务器的免费配额但对于有着众多服务器的团队或者公司来说运用免费的显然不够。而且大多数公司也不想将自己的数据存放在第三方平台,这时Keymetrics监控就不太适合了。</p>
<p>PM25是美团开源的基于PM2开发的一款node服务监控平台,其支持的功能有:</p>
<ul>
<li>支持用户的管理(登录作为中间件)</li>
<li>被监控机器的分桶管理</li>
<li>机器列表、快速过滤和主机的指标信息(进程数、CPU数、负载、上线时长、内存占用)</li>
<li>进程的详细指标信息(PID、进程名、重启次数、上线时长、状态、CPU占用、内存占用、错误日志)</li>
<li>同Falcon整合,支持监控报警管理(核心指标同步Falcon,可以查看历史图表或者配置监控报警)</li>
<li>支持扩展包,引入扩展包后可以收集统计服务端慢路由信息</li>
<li>支持进程的远程控制,可以在云端对进程进行远程操作(比如重启、重载)</li>
</ul>
<p>关于PM25的详细信息可查看:https://github.com/PaulGuo/PM25</p>
测试框架 Mocha入门
2016-12-18T00:00:00+00:00
http://www.blogways.net/blog/2016/12/18/mocha-introduction
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">Mocha测试框架简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">安装Mocha</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link3">测试脚本的写法</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#link3">测试脚本的写法</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#link3">心得体会</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<h2 id="一mocha测试框架简介">一.Mocha测试框架简介</h2>
<p>Mocha是一款功能丰富的javascript单元测试框架,它既可以运行在nodejs环境中,也可以运行在浏览器环境中。</p>
<p>javascript是一门单线程语言,最显著的特点就是有很多异步执行。同步代码的测试比较简单,直接判断函数的返回值是否符合预期就行了,而异步的函数,就需要测试框架支持回调、promise或其他的方式来判断测试结果的正确性了。mocha可以良好的支持javascript异步的单元测试。</p>
<p>mocha会串行地执行我们编写的测试用例,可以在将未捕获异常指向对应用例的同时,保证输出灵活准确的测试结果报告。</p>
<p><a id="link2"></a></p>
<h2 id="二mocha测试框架安装">二.Mocha测试框架安装</h2>
<ul>
<li>安装nodejs</li>
<li>npm install –global mocha</li>
</ul>
<p><a id="link3"></a></p>
<h2 id="三mocha测试脚本的写法">三.Mocha测试脚本的写法</h2>
<p>Mocha的作用是运行测试脚本,首先必须学会写测试脚本。所谓”测试脚本”,就是用来测试源码的脚本。</p>
<p>下面是一个加载模块add.js的代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function add(x, y) {
return x + y;
}
module.exports = add;
</code></pre></div></div>
<p>要测试这个加法模块是否正确,就要写测试脚本。</p>
<p>通常,测试脚本与所要测试的源码脚本同名,但是后缀名为.test.js(表示测试)或者.spec.js(表示规格)。比如,add.js的测试脚本名字就是add.test.js。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var add = require('./add.js');
var expect = require('chai').expect;
describe('加法函数的测试', function() {
it('1 加 1 应该等于 2', function() {
expect(add(1, 1)).to.be.equal(2);
});
});
</code></pre></div></div>
<p>上面这段代码,就是测试脚本,它可以独立执行。测试脚本里面应该包括一个或多个<code class="language-plaintext highlighter-rouge">describe</code>块,每个describe块应该包括一个或多个it块。
describe块称为”测试套件”(test suite),表示一组相关的测试。它是一个函数,第一个参数是测试套件的名称(”加法函数的测试”),第二个参数是一个实际执行的函数。</p>
<p>it块称为”测试用例”(test case),表示一个单独的测试,是测试的最小单位。它也是一个函数,第一个参数是测试用例的名称(”1 加 1 应该等于 2”),第二个参数是一个实际执行的函数。</p>
<p>写完测试脚本后,使用Mocha来测试它。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mocha add.test.js
加法函数的测试
✓ 1 加 1 应该等于 2
1 passing (9ms)
</code></pre></div></div>
<p>上面的运行结果表示,测试脚本通过了测试,一共只有1个测试用例,耗时是9毫秒。</p>
<p><a id="link4"></a></p>
<h2 id="四mocha命令参数">四.mocha命令参数</h2>
<p>mocha支持任何可以抛出一个错误的断言模块。例如:should.js、better-assert、expect.js、unexpected、chai等。这些断言库各有各的特点,大家可以了解一下它们的特点,根据使用场景来选择断言库。</p>
<p>mocha命令的基本格式是:mocha [debug] [options] [files]
options包括下面这些,我翻译了一部分目前能理解的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> -h, --help 输出帮助信息
-V, --version 输出mucha版本
-A, --async-only 强制让所有测试用例必须使用callback或者返回promise的方式来异步判断正确性
-c, --colors 启用报告中颜色
-C, --no-colors 禁用报告中颜色
-G, --growl enable growl notification support
-O, --reporter-options <k=v,k2=v2,...> reporter-specific options
-R, --reporter <name> specify the reporter to use
-S, --sort 排序测试文件
-b, --bail bail after first test failure
-d, --debug enable node's debugger, synonym for node --debug
-g, --grep <pattern> 只执行满足 <pattern>格式的用例
-f, --fgrep <string> 只执行含有 <string> 的用例
-gc, --expose-gc 展示gc回收的log
-i, --invert 让 --grep 和 --fgrep 的匹配取反
-r, --require <name> require一下<name>指定的模块
-s, --slow <ms> 指定slow时间(单位ms,默认75ms)
-t, --timeout <ms> 指定超时时间(单位ms,默认2000ms)
-u, --ui <name> 指定user-interface (bdd|tdd|exports)
-w, --watch 观察用例文件变化,并重新执行
--check-leaks 检测未回收global变量泄露
--compilers <ext>:<module>,... 用指定的模块来编译文件
--debug-brk 启用node的debug模式
--delay 等待异步的用例集(见前边的)
--es_staging enable all staged features
--full-trace display the full stack trace
--globals <names> allow the given comma-delimited global [names]
--harmony enable all harmony features (except typeof)
--harmony-collections enable harmony collections (sets, maps, and weak maps)
--harmony-generators enable harmony generators
--harmony-proxies enable harmony proxies
--harmony_arrow_functions enable "harmony arrow functions" (iojs)
--harmony_classes enable "harmony classes" (iojs)
--harmony_proxies enable "harmony proxies" (iojs)
--harmony_shipping enable all shipped harmony features (iojs)
--inline-diffs 显示预期和实际结果的string差异比较
--interfaces display available interfaces
--no-deprecation silence deprecation warnings
--no-exit require a clean shutdown of the event loop: mocha will not call process.exit
--no-timeouts 禁用timeout,可通过--debug隐式指定
--opts <path> 定义option文件路径
--prof 显示统计信息
--recursive 包含子目录
--reporters 展示可用报告
--retries 设置失败用例重试次数
--throw-deprecation 每次调用deprecated函数的时候都抛出一个异常
--trace 显示函数调用栈
--trace-deprecation 启用的时候显示调用栈
--watch-extensions <ext>,... --watch监控的扩展
</code></pre></div></div>
<p>下面是官方文档对部分命令的详细说明:</p>
<ul>
<li>-W, –WATCH</li>
</ul>
<p>用例一旦更新立即执行</p>
<ul>
<li>–COMPILERS</li>
</ul>
<p>例如–compilers coffee:coffee-script编译CoffeeScript 1.6,或者–compilers coffee:coffee-script/register编译CoffeeScript 1.7+</p>
<ul>
<li>-B, –BAIL</li>
</ul>
<p>如果只对第一个抛出的异常感兴趣,可以使用此命令。</p>
<ul>
<li>-D, –DEBUG</li>
</ul>
<p>开启nodejs的debug模式,可以在debugger语句处暂停执行。</p>
<ul>
<li>–GLOBALS</li>
</ul>
<p>names是一个以逗号分隔的列表,如果你的模块需要暴露出一些全局的变量,可以使用此命令,例如mocha –globals app,YUI。
这个命令还可以接受通配符,例如–globals ‘*bar。参数传入 * 的话,会忽略所有全局变量。</p>
<ul>
<li>–CHECK-LEAKS</li>
</ul>
<p>默认情况下,mocha并不会去检查应用暴露出来的全局变量,加上这个配置后就会去检查,此时某全局变量如果没有用上面的–GLOBALS去配置为可接受,mocha就会报错</p>
<ul>
<li>-R, –REQUIRE</li>
</ul>
<p>这个命令可以用来引入一些依赖的模块,比如should.js等,这个命令相当于在测试目录下每个js文件头部运行一下require(‘should.js’),模块中对Object、Array等对象的扩展会生效,也可以用–require ./test/helper.js这样的命令去引入指定的本地模块。
但是… 很鸡肋的是,如果要引用模块导出的对象,还是需要require,var should = require(‘should’)这样搞。</p>
<ul>
<li>-U, –UI</li>
</ul>
<p>–ui选项可以用来指定所使用的测试接口,默认是“bdd”</p>
<p>-R, –REPORTER</p>
<p>这个命令可以用来指定报告格式,默认是“spec”。可以使用第三方的报告样式,例如:
npm install mocha-lcov-reporter,–reporter mocha-lcov-reporter</p>
<ul>
<li>-T, –TIMEOUT</li>
</ul>
<p>用来指定用例超时时间</p>
<ul>
<li>-S, –SLOW</li>
</ul>
<p>用来指定慢用例判定时间,默认是75ms</p>
<ul>
<li>-G, –GREP</li>
</ul>
<p>grep pattern可以用来筛选要执行的用例或用例集,pattern参数在mocha内部会被编译成一个正则表达式。
假如有下面的测试用例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>describe('api', function() {
describe('GET /api/users', function() {
it('respond with an array of users', function() {
// ...
});
});
});
describe('app', function() {
describe('GET /users', function() {
it('respond with an array of users', function() {
// ...
});
});
});
</code></pre></div></div>
<p>可以用–grep api、–grep app、–grep users、–grep GET,来筛选出要执行的用例。</p>
<h2 id="五心得与体会">五.心得与体会</h2>
<p>接触Mocha是在上年实习的时候,感觉这个针对nodejs下的单元测试还是很好用的,但是用好Mocha要对断言库有一定的了解,断言接触不多,以后还会继续分析Mocha的一些比较好的测试案例。</p>
canvas学习[3]--文本和图片
2016-12-18T00:00:00+00:00
http://www.blogways.net/blog/2016/12/18/canvas-3
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#part1">文本 Text</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#part2">图像 Images</a></td>
</tr>
</tbody>
</table>
<p><a id="part1"></a></p>
<h2 id="1-文本-text">1. 文本 Text</h2>
<h3 id="11-绘制文本">1.1 绘制文本</h3>
<p>canvas 提供了两种方法来渲染文本:</p>
<ul>
<li>
<p>fillText(text, x, y [, maxWidth])</p>
<p>在指定的(x,y)位置填充指定的文本,绘制的最大宽度是可选的.</p>
</li>
<li>
<p>strokeText(text, x, y [, maxWidth])</p>
<p>在指定的(x,y)位置绘制文本边框,绘制的最大宽度是可选的.</p>
</li>
</ul>
<p>下面是绘制文本和文本边框的示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw1() {
var ctx = document.getElementById('cvs1').getContext('2d');
ctx.font = "48px serif";
ctx.fillText("Blog Ways", 10, 50);
ctx.font = "48px serif";
ctx.strokeText("Blog Ways", 10, 100);
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">font</code>用于指定文本的大小和字体,具体效果如下:</p>
<div style="text-align: center;">
<canvas id="cvs1"></canvas>
</div>
<script>
function draw1() {
var ctx = document.getElementById('cvs1').getContext('2d');
ctx.font = "48px serif";
ctx.fillText("Blog Ways", 10, 50);
ctx.font = "48px serif";
ctx.strokeText("Blog Ways", 10, 100);
}
draw1();
</script>
<h3 id="12-文本样式">1.2 文本样式</h3>
<p>在上面的例子使用了 font 来改变文本的大小和字体,除此外还有更多属性可以改变canvas显示文本的方式:</p>
<ul>
<li>
<p>font = value</p>
<p>当前用来绘制文本的样式,value的使用和 CSS font 属性相同的语法. 默认的字体是 10px sans-serif。</p>
</li>
<li>
<p>textAlign = value</p>
<p>文本对齐选项. 可选项包括:start, end, left, right 或者 center. 默认值是 start。</p>
</li>
<li>
<p>textBaseline = value</p>
<p>基线对齐选项. 可选项包括:top, hanging, middle, alphabetic, ideographic, bottom。默认值是 alphabetic。</p>
</li>
<li>
<p>direction = value</p>
<p>文本方向。可选项包括:ltr, rtl, inherit。默认值是 inherit。</p>
</li>
</ul>
<p>可以在下面例子的textarea中编辑尝试不同选项的效果。</p>
<p><code class="language-plaintext highlighter-rouge">textBaseLine 可选值 top, hanging, middle, alphabetic, ideographic, bottom;</code></p>
<p><code class="language-plaintext highlighter-rouge">direction 可选值 ltr, rtl, inherit;</code></p>
<div style="text-align: center;">
<canvas id="cvs2" width="800px"></canvas>
<textarea id="area1" style="width:80%; height: 150px; font: 16px serif">
ctx.font = "48px serif";
ctx.textAlign = "start";
ctx.textBaseline = "hanging";
ctx.direction = "inherit";
</textarea>
</div>
<script>
var area1 = document.getElementById('area1');
function draw2() {
var ctx = document.getElementById('cvs2').getContext('2d');
var option = document.getElementById('area1').value;
ctx.clearRect(0, 0, 800, 200);
eval(option);
ctx.fillText("Blog Way", 200, 50);
}
area1.oninput = draw2;
area1.onpropertychange = draw2;
draw2();
</script>
<p><a id="part2"></a></p>
<h2 id="2-图像-images">2. 图像 Images</h2>
<p>canvas 对图像的处理功能可以用于动态的图片合成或者作背景,以及用于游戏界面(Sprites)等等。浏览器支持的任意格式的外部图片都可以使用,比如PNG、GIF或者JPEG,也可以将同一个页面中其他canvas元素生成的图片作为图片源。</p>
<p>引入图像到canvas里需要以下两步基本操作:</p>
<ol>
<li>获得一个指向HTMLImageElement的对象或者另一个canvas元素的引用作为源,也可以通过提供一个URL的方式来使用图片;</li>
<li>使用drawImage()函数将图片绘制到画布上。</li>
</ol>
<h3 id="21-图像获取方式">2.1 图像获取方式</h3>
<p>canvas可以使用下面的方法来获取图片:</p>
<ul>
<li>
<p>HTMLImageElement</p>
<p>这些图片是由Image()函数构造出来的,或者任何的img元素;</p>
</li>
<li>
<p>HTMLVideoElement</p>
<p>选择一个HTML的video元素作为图片源,此方法可以从视频中抓取当前帧作为一个图像;</p>
</li>
<li>
<p>HTMLCanvasElement</p>
<p>使用同一页面中的canvas元素作为你的图片源。</p>
</li>
</ul>
<p>下面来分别介绍。</p>
<h3 id="22-绘制图像">2.2 绘制图像</h3>
<p>使用脚本创建一个新的 HTMLImageElement 对象,设置图片路径。</p>
<p><code class="language-plaintext highlighter-rouge">
var img = new Image(); // 创建一个<img>元素
img.src = '/images/wanghui/canvas3/img.jpg'; // 设置图片源地址
</code></p>
<p>当脚本执行后,图片开始加载。</p>
<p>若调用drawImage()的时候图片还没加载完,那么将不会生成图片(在一些旧的浏览器中可能会抛出异常)。因此应该用load事件来保证图片加载完毕。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var img = new Image(); // 创建img元素
img.onload = function(){
// 执行drawImage语句
}
img.src = 'myImage.png'; // 设置图片源地址
</code></pre></div></div>
<p>出了使用路径加载外,还可以通过 data:url 的方式来引用图像。Data urls 允许用一串 Base64 编码的字符串的方式来定义一个图片,例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>img.src = 'data:image/gif;base64,R0lGODlhCwALAIAAAAAA3pn/ZiH5BAEAAAEALAAAAAALAAsAAAIUhA+hkcuO4lmNVindo7qyrIXiGBYAOw==';
</code></pre></div></div>
<p>这种方式的优点是图片内容即时可用,无须再到服务器兜一圈。(还有一个优点是,可以将 CSS,JavaScript,HTML 和 图片全部封装在一起,迁移起来十分方便。)缺点就是图像没法缓存,图片大的话内嵌的 url 数据会相当的长。</p>
<p>下面来看看具体的绘制示例。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw4() {
var ctx = document.getElementById('cvs4').getContext('2d');
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = '/images/wanghui/canvas3/img1.png';
}
</code></pre></div></div>
<p>这是基本的加载并绘制图片的方式,通过监听load事件确保图片加载完毕。</p>
<div style="text-align: center;">
<canvas id="cvs4" height="200px"></canvas>
</div>
<script>
function draw4() {
var ctx = document.getElementById('cvs4').getContext('2d');
var img = new Image();
img.onload = function() {
ctx.drawImage(img, 0, 0);
};
img.src = '/images/wanghui/canvas3/img1.png';
}
draw4();
</script>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw5() {
var ctx = document.getElementById('cvs5').getContext('2d');
var img = new Image();
img.onload = function() {
for (var i=0;i<3;i++){
for (var j=0;j<3;j++){
ctx.drawImage(img,j*50,i*50,50,38);
}
}
};
img.src = '/images/wanghui/canvas3/img1.png';
}
</code></pre></div></div>
<p>本例使用了drawImage方法的一种重载,增加了两个用于控制图像在canvas中缩放的参数。</p>
<ul>
<li>
<p>drawImage(image, x, y, width, height)</p>
<p>这个方法多了2个参数:width 和 height,这两个参数用来控制 当像canvas画入时应该缩放的大小。</p>
</li>
</ul>
<p>使用此方式可以实现图像的平铺,具体示例如下:</p>
<div style="text-align: center;">
<canvas id="cvs5" height="200px"></canvas>
</div>
<script>
function draw5() {
var ctx = document.getElementById('cvs5').getContext('2d');
var img = new Image();
img.onload = function() {
for (var i=0;i<3;i++){
for (var j=0;j<3;j++){
ctx.drawImage(img,j*50,i*50,50,38);
}
}
};
img.src = '/images/wanghui/canvas3/img1.png';
}
draw5();
</script>
<p>drawImage 方法的另一个重载,也是最后一个变种,共有8个参数,用于控制做切片显示。</p>
<ul>
<li>
<p>drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)</p>
<p>第一个参数和其它的是相同的,都是一个图像或者另一个 canvas 的引用。前4个是定义图像源的切片位置和大小,后4个则是定义切片的目标显示位置和大小。</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw67() {
var ctx6 = document.getElementById('cvs6').getContext('2d');
var ctx7 = document.getElementById('cvs7').getContext('2d');
var img = new Image();
img.onload = function() {
ctx6.drawImage(img, 0, 0);
ctx7.drawImage(img, 55,65, 85,85, 10,45, 100,100);
};
img.src = '/images/wanghui/canvas3/img1.png';
}
</code></pre></div></div>
<p>可以对比两张图片来看,8个参数中:</p>
<ul>
<li>sx - 定义切片的x坐标;</li>
<li>sy - 定义切片的y坐标;</li>
<li>sWidth - 定义切片的长度;</li>
<li>sHeight - 定义切片的高度;</li>
<li>dx - 定义切片生成后,位于canvas中的x坐标位置;</li>
<li>dy - 定义切片生成后,位于canvas中的y坐标位置;</li>
<li>dWidth - 定义切片后生成图片的长度;</li>
<li>dHeight - 定义切片后生成图片的高度。</li>
</ul>
<div style="text-align: center;">
<canvas id="cvs6" height="200px"></canvas>
<canvas id="cvs7" height="200px"></canvas>
</div>
<script>
function draw67() {
var ctx6 = document.getElementById('cvs6').getContext('2d');
var ctx7 = document.getElementById('cvs7').getContext('2d');
var img = new Image();
img.onload = function() {
ctx6.drawImage(img, 0, 0);
ctx7.drawImage(img, 55,65, 85,85, 10,45, 100,100);
};
img.src = '/images/wanghui/canvas3/img1.png';
}
draw67();
</script>
<p>除了引用图片,还可以引用 video中的视频帧。例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw3() {
var ctx = document.getElementById('cvs3').getContext('2d');
var video = document.getElementById('v1');
video.addEventListener("timeupdate", function(){
ctx.drawImage(video, 0, 0);
}, true);
}
</code></pre></div></div>
<p>代码监听了视频的timeudate事件,并在canvas里实时绘制视频图像。第一个是video元素加载的视频,第二个是canvas实时绘制的内容。</p>
<div style="text-align: center;">
<video id="v1" src="/images/wanghui/canvas3/video1.mp4" controls="controls"></video>
<canvas id="cvs3" width="568px" height="320px"></canvas>
</div>
<script>
function draw3() {
var ctx = document.getElementById('cvs3').getContext('2d');
var video = document.getElementById('v1');
video.addEventListener("timeupdate", function(){
ctx.drawImage(video, 0, 0);
}, true);
}
draw3();
</script>
<p>文本与图像的内容就介绍到这里,下一篇将介绍变形、合成与裁剪。</p>
canvas学习[2]--样式与颜色
2016-12-18T00:00:00+00:00
http://www.blogways.net/blog/2016/12/18/canvas-2
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#part1">色彩 Color</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#part2">透明度 Transparency</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#part3">线型 Line styles</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#part4">渐变 Gradients</a></td>
</tr>
</tbody>
</table>
<p><a id="part1"></a></p>
<h2 id="1-色彩-color">1. 色彩 Color</h2>
<p>如果想要给图形上色,有两个重要的属性可以做到:fillStyle 和 strokeStyle。</p>
<ul>
<li>
<p>fillStyle = color</p>
<p>设置图形的填充颜色。</p>
</li>
<li>
<p>strokeStyle = color</p>
<p>设置图形轮廓的颜色。</p>
</li>
</ul>
<p>color 可以是表示 CSS 颜色值的字符串,渐变对象或者图案对象。默认情况下,线条和填充颜色都是黑色(CSS 颜色值 #000000)。</p>
<p>一旦设置了 strokeStyle 或者 fillStyle 的值,那么这个新值就会成为新绘制的图形的默认值。如果要给每个图形上不同的颜色,需要重新设置 fillStyle 或 strokeStyle 的值。</p>
<p>canvas接受所有符合 CSS3 颜色值标准 的有效字符串。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 以下 fillStyle 的值均为 '橙色'
ctx.fillStyle = "orange";
ctx.fillStyle = "#FFA500";
ctx.fillStyle = "rgb(255,165,0)";
ctx.fillStyle = "rgba(255,165,0,1)";
</code></pre></div></div>
<p>下面来看一个fillStyle的例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw() {
var ctx = document.getElementById('canvas').getContext('2d');
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
}
}
</code></pre></div></div>
<p>在本示例里使用了两层 for 循环来绘制方格阵列,每个方格不同的颜色,结果如下图所示。</p>
<p>代码中使用了两个变量 i 和 j 来为每一个方格产生唯一的 RGB 色彩值,其中仅修改红色和绿色通道的值,而保持蓝色通道的值不变。</p>
<div style="text-align: center">
<canvas id="cvs1"></canvas>
</div>
<script>
function draw1() {
var ctx = document.getElementById('cvs1').getContext('2d');
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.fillStyle = 'rgb(' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ',0)';
ctx.fillRect(j*25,i*25,25,25);
}
}
}
draw1()
</script>
<p>了解了fillStyle后,下面来看看strokeStyle。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw2() {
var ctx = document.getElementById('cvs2').getContext('2d');
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ')';
ctx.beginPath();
ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
ctx.stroke();
}
}
}
</code></pre></div></div>
<p>与上一个例子类似,只不过本例中使用的是 strokeStyle 属性,保持不变的是红色通道值,画的不是方格,而是用 arc 方法来画圆。</p>
<div style="text-align: center">
<canvas id="cvs2"></canvas>
</div>
<script>
function draw2() {
var ctx = document.getElementById('cvs2').getContext('2d');
for (var i=0;i<6;i++){
for (var j=0;j<6;j++){
ctx.strokeStyle = 'rgb(0,' + Math.floor(255-42.5*i) + ',' +
Math.floor(255-42.5*j) + ')';
ctx.beginPath();
ctx.arc(12.5+j*25,12.5+i*25,10,0,Math.PI*2,true);
ctx.stroke();
}
}
}
draw2()
</script>
<p><a id="part2"></a></p>
<h2 id="2-透明度-transparency">2. 透明度 Transparency</h2>
<p>除了可以绘制实色图形,还可以用 canvas 来绘制半透明的图形。通过设置 globalAlpha 属性或者使用一个半透明颜色作为轮廓或填充的样式。</p>
<ul>
<li>globalAlpha = transparencyValue</li>
</ul>
<p>这个属性影响到 canvas 里所有图形的透明度,有效的值范围是 0.0 (完全透明)到 1.0(完全不透明),默认是 1.0。</p>
<p>globalAlpha 属性在需要绘制大量拥有相同透明度的图形时候相当高效。在绘制透明度比较多变的图形时,下面的方法可操作性更强一点。</p>
<p>因为 strokeStyle 和 fillStyle 属性接受符合 CSS 3 规范的颜色值,那我们可以用下面的写法来设置具有透明度的颜色。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 指定透明颜色,用于描边和填充样式
ctx.strokeStyle = "rgba(255,0,0,0.5)";
ctx.fillStyle = "rgba(255,0,0,0.5)";
</code></pre></div></div>
<p>rgba() 方法与 rgb() 方法类似,就多了一个用于设置色彩透明度的参数。它的有效范围是从 0.0(完全透明)到 1.0(完全不透明)。</p>
<p>下面来分别看看两种方式的示例。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw3() {
var ctx = document.getElementById('cvs3').getContext('2d');
ctx.fillStyle = '#FD0';
ctx.fillRect(0,0,75,75);
ctx.fillStyle = '#6C0';
ctx.fillRect(75,0,75,75);
ctx.fillStyle = '#09F';
ctx.fillRect(0,75,75,75);
ctx.fillStyle = '#F30';
ctx.fillRect(75,75,75,75);
ctx.fillStyle = '#FFF';
// 设置透明度值
ctx.globalAlpha = 0.2;
// 画半透明圆
for (var i=0;i<7;i++){
ctx.beginPath();
ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
ctx.fill();
}
}
</code></pre></div></div>
<p>先来看看globalAlpha。</p>
<p>在本例中,将用四色格作为背景,设置 globalAlpha 为 0.2 后,在上面画一系列半径递增的半透明圆。最终结果是一个径向渐变效果。圆叠加得越更多,原先所画的圆的透明度会越低。通过增加循环次数,画更多的圆,背景图的中心部分会完全消失。</p>
<div style="text-align: center">
<canvas id="cvs3"></canvas>
</div>
<script>
function draw3() {
var ctx = document.getElementById('cvs3').getContext('2d');
ctx.fillStyle = '#FD0';
ctx.fillRect(0,0,75,75);
ctx.fillStyle = '#6C0';
ctx.fillRect(75,0,75,75);
ctx.fillStyle = '#09F';
ctx.fillRect(0,75,75,75);
ctx.fillStyle = '#F30';
ctx.fillRect(75,75,75,75);
ctx.fillStyle = '#FFF';
// 设置透明度值
ctx.globalAlpha = 0.2;
// 画半透明圆
for (var i=0;i<7;i++){
ctx.beginPath();
ctx.arc(75,75,10+10*i,0,Math.PI*2,true);
ctx.fill();
}
}
draw3()
</script>
<p>下面是rgba()的示例。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw4() {
var ctx = document.getElementById('cvs4').getContext('2d');
ctx.fillStyle = 'rgb(255,221,0)';
ctx.fillRect(0,0,150,37.5);
ctx.fillStyle = 'rgb(102,204,0)';
ctx.fillRect(0,37.5,150,37.5);
ctx.fillStyle = 'rgb(0,153,255)';
ctx.fillRect(0,75,150,37.5);
ctx.fillStyle = 'rgb(255,51,0)';
ctx.fillRect(0,112.5,150,37.5);
// 画半透明矩形
for (var i=0;i<10;i++){
ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')';
for (var j=0;j<4;j++){
ctx.fillRect(5+i*14,5+j*37.5,14,27.5)
}
}
}
</code></pre></div></div>
<p>与前一个例子类似,不过不是画圆,而是画矩形。这里还可以看出,rgba() 可以分别设置轮廓和填充样式,因而具有更好的可操作性和使用灵活性。</p>
<div style="text-align: center">
<canvas id="cvs4"></canvas>
</div>
<script>
function draw4() {
var ctx = document.getElementById('cvs4').getContext('2d');
ctx.fillStyle = 'rgb(255,221,0)';
ctx.fillRect(0,0,150,37.5);
ctx.fillStyle = 'rgb(102,204,0)';
ctx.fillRect(0,37.5,150,37.5);
ctx.fillStyle = 'rgb(0,153,255)';
ctx.fillRect(0,75,150,37.5);
ctx.fillStyle = 'rgb(255,51,0)';
ctx.fillRect(0,112.5,150,37.5);
// 画半透明矩形
for (var i=0;i<10;i++){
ctx.fillStyle = 'rgba(255,255,255,'+(i+1)/10+')';
for (var j=0;j<4;j++){
ctx.fillRect(5+i*14,5+j*37.5,14,27.5)
}
}
}
draw4()
</script>
<p><a id="part3"></a></p>
<h2 id="3-线型-line-styles">3. 线型 Line styles</h2>
<p>上一篇博文中的线条十分单调,这里将介绍一系列属性用于修饰线条的样式。</p>
<ul>
<li>
<p>lineWidth = value</p>
<p>设置线条宽度。</p>
</li>
<li>
<p>lineCap = type</p>
<p>设置线条末端样式。</p>
</li>
<li>
<p>lineJoin = type</p>
<p>设定线条与线条间接合处的样式。</p>
</li>
<li>
<p>getLineDash()
返回一个包含当前虚线样式,长度为非负偶数的数组。</p>
</li>
<li>
<p>setLineDash(segments)</p>
<p>设置当前虚线样式。</p>
</li>
<li>
<p>lineDashOffset = value</p>
<p>设置虚线样式的起始偏移量。</p>
</li>
</ul>
<p>下面来分别看看各属性的例子,首先是lineWidth。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw5() {
var ctx = document.getElementById('cvs5').getContext('2d');
for (var i = 0; i < 10; i++){
ctx.lineWidth = 1+i;
ctx.beginPath();
ctx.moveTo(5+i*14,5);
ctx.lineTo(5+i*14,140);
ctx.stroke();
}
}
</code></pre></div></div>
<p>lineWidth属性设置当前绘线的粗细。属性值必须为正数。默认值是1.0。</p>
<p>lineWidth是指给定路径的中心到两边的粗细。换句话说就是在路径的两边各绘制线宽的一半。因为画布的坐标并不和像素直接对应,当需要获得精确的水平或垂直线的时候要特别注意。</p>
<p>本例中使用递增的宽度绘制了10条直线。最左边的线宽1.0单位。</p>
<p>最左边的以及所有宽度为奇数的线颜色出现偏差,这就是因为路径的定位问题导致。</p>
<div style="text-align: center">
<canvas id="cvs5"></canvas>
</div>
<script>
function draw5() {
var ctx = document.getElementById('cvs5').getContext('2d');
for (var i = 0; i < 10; i++){
ctx.lineWidth = 1+i;
ctx.beginPath();
ctx.moveTo(5+i*14,5);
ctx.lineTo(5+i*14,140);
ctx.stroke();
}
}
draw5()
</script>
<p>想要获得精确的线条,就得知道线条是如何描绘出来。</p>
<p>下图用网格来代表 canvas 的坐标格,每一格对应屏幕上一个像素点。在第一个图中,填充了 (2,1) 至 (5,5) 的矩形,整个区域的边界刚好落在像素边缘上,这样就可以得到的矩形有着清晰的边缘。</p>
<p><img src="/images/wanghui/canvas2/img1.png" alt="img1" /></p>
<p>如果想要绘制一条从 (3,1) 到 (3,5),宽度是 1.0 的线条,结果将会是第二幅图。</p>
<p>实际填充区域(深蓝色部分)仅仅延伸至路径两旁各一半像素。而另外半个像素会以笔触颜色一半的色调进行渲染,于是线条颜色被渲染为浅蓝和深蓝两个部分。这就是上例中为何宽度为 1.0 的线并不准确的原因。</p>
<p>要解决这个问题,必须对路径施以更加精确的控制。已知粗 1.0 的线条会在路径两边各延伸半像素,那么像第三幅图那样绘制从 (3.5,1) 到 (3.5,5) 的线条,其边缘正好落在像素边界,填充出来就是准确的宽为 1.0 的线条。</p>
<p>以上是lineWidth,下面来看看lineCap属性。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw6() {
var ctx = document.getElementById('cvs6').getContext('2d');
var lineCap = ['butt','round','square'];
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(140,10);
ctx.moveTo(10,140);
ctx.lineTo(140,140);
ctx.stroke();
// 画线条
ctx.strokeStyle = 'black';
for (var i=0;i<lineCap.length;i++){
ctx.lineWidth = 15;
ctx.lineCap = lineCap[i];
ctx.beginPath();
ctx.moveTo(25+i*50,10);
ctx.lineTo(25+i*50,140);
ctx.stroke();
}
}
</code></pre></div></div>
<p>lineCap属性决定了线条端点显示的样子。可选值为:butt,round 和 square。默认是 butt。</p>
<p>本例中绘制了三条直线,分别赋予不同的 lineCap 值。还有两条辅助线是为了看清它们之间的区别,三条线的起点终点都落在辅助线上。</p>
<p>最左边的线用了默认的 butt 。可以注意到它是与辅助线齐平的。中间的是 round 的效果,端点处加上了半径为一半线宽的半圆。右边的是 square 的效果,端点处加上了等宽且高度为一半线宽的方块。</p>
<div style="text-align: center">
<canvas id="cvs6"></canvas>
</div>
<script>
function draw6() {
var ctx = document.getElementById('cvs6').getContext('2d');
var lineCap = ['butt','round','square'];
ctx.strokeStyle = '#09f';
ctx.beginPath();
ctx.moveTo(10,10);
ctx.lineTo(140,10);
ctx.moveTo(10,140);
ctx.lineTo(140,140);
ctx.stroke();
// 画线条
ctx.strokeStyle = 'black';
for (var i=0;i<lineCap.length;i++){
ctx.lineWidth = 15;
ctx.lineCap = lineCap[i];
ctx.beginPath();
ctx.moveTo(25+i*50,10);
ctx.lineTo(25+i*50,140);
ctx.stroke();
}
}
draw6()
</script>
<p>接下来是lineJoin属性。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw7() {
var ctx = document.getElementById('cvs7').getContext('2d');
var lineJoin = ['round','bevel','miter'];
ctx.lineWidth = 10;
for (var i=0;i<lineJoin.length;i++){
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(-5,5+i*40);
ctx.lineTo(35,45+i*40);
ctx.lineTo(75,5+i*40);
ctx.lineTo(115,45+i*40);
ctx.lineTo(155,5+i*40);
ctx.stroke();
}
}
</code></pre></div></div>
<p>lineJoin的属性决定了图形中两线段连接处所显示的样子。可选值为:round, bevel 和 miter。默认是 miter。</p>
<p>同样用三条折线来做例子,分别设置不同的 lineJoin 值。最上面一条是 round 的效果,边角处被磨圆了,圆的半径等于线宽。中间和最下面一条分别是 bevel 和 miter 的效果。当值是 miter 的时候,线段会在连接处外侧延伸直至交于一点。</p>
<div style="text-align: center">
<canvas id="cvs7"></canvas>
</div>
<script>
function draw7() {
var ctx = document.getElementById('cvs7').getContext('2d');
var lineJoin = ['round','bevel','miter'];
ctx.lineWidth = 10;
for (var i=0;i<lineJoin.length;i++){
ctx.lineJoin = lineJoin[i];
ctx.beginPath();
ctx.moveTo(-5,5+i*40);
ctx.lineTo(35,45+i*40);
ctx.lineTo(75,5+i*40);
ctx.lineTo(115,45+i*40);
ctx.lineTo(155,5+i*40);
ctx.stroke();
}
}
draw7();
</script>
<p>最后介绍虚线的使用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var canvas8 = document.getElementById('cvs8');
var ctx8 = document.getElementById('cvs8').getContext('2d');
var offset = 0;
function draw8() {
ctx8.clearRect(0,0, canvas8.width, canvas8.height);
ctx8.setLineDash([4, 2]);
ctx8.lineDashOffset = -offset;
ctx8.strokeRect(10,10, 100, 100);
}
function march() {
offset++;
if (offset > 16) {
offset = 0;
}
draw8();
setTimeout(march, 20);
}
march();
</code></pre></div></div>
<p>用 setLineDash 方法和 lineDashOffset 属性来制定虚线样式。</p>
<p>setLineDash 方法接受一个数组,来指定线段与间隙的交替,数组第一个值表示虚线的长度,第二个值表示虚线之前的空隙。</p>
<p>lineDashOffset 属性设置起始偏移量。</p>
<div style="text-align: center">
<canvas id="cvs8"></canvas>
</div>
<script>
var canvas8 = document.getElementById('cvs8');
var ctx8 = document.getElementById('cvs8').getContext('2d');
var offset = 0;
function draw8() {
ctx8.clearRect(0,0, canvas8.width, canvas8.height);
ctx8.setLineDash([4, 2]);
ctx8.lineDashOffset = -offset;
ctx8.strokeRect(10,10, 100, 100);
}
function march() {
offset++;
if (offset > 16) {
offset = 0;
}
draw8();
setTimeout(march, 20);
}
march();
</script>
<p><a id="part4"></a></p>
<h2 id="4-渐变-gradients">4. 渐变 Gradients</h2>
<p>与CSS3和SVG一样,canvas也有自己的渐变功能,同样分为 线性渐变 和 径向渐变。</p>
<p>使用下面的方法可以新建一个 canvasGradient 对象,并且赋给图形的 fillStyle 或 strokeStyle 属性,从而实现渐变效果。</p>
<ul>
<li>
<p>createLinearGradient(x1, y1, x2, y2)</p>
<p>createLinearGradient 线性渐变方法接受 4 个参数,表示渐变的起点 (x1,y1) 与终点 (x2,y2)。</p>
</li>
<li>
<p>createRadialGradient(x1, y1, r1, x2, y2, r2)</p>
<p>createRadialGradient 径向渐变方法接受 6 个参数,前三个定义一个以 (x1,y1) 为原点,半径为 r1 的圆,后三个参数则定义另一个以 (x2,y2) 为原点,半径为 r2 的圆。</p>
</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var lineargradient = ctx.createLinearGradient(0,0,150,150);
var radialgradient = ctx.createRadialGradient(75,75,0,75,75,100);
</code></pre></div></div>
<p>创建出 canvasGradient 对象后,可以用 addColorStop 方法上色。</p>
<ul>
<li>
<p>gradient.addColorStop(position, color)</p>
<p>addColorStop 方法接受 2 个参数,position 参数必须是一个 0.0 与 1.0 之间的数值,表示渐变中颜色所在的相对位置。例如,0.5 表示颜色会出现在正中间。color 参数必须是一个有效的 CSS 颜色值(如 #FFF, rgba(0,0,0,1),等等)。</p>
</li>
</ul>
<p>可以根据需要添加任意多个色标(color stops)。下面是最简单的线性黑白渐变的例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw9() {
var ctx = document.getElementById('cvs9').getContext('2d');
var lineargradient = ctx.createLinearGradient(0,0,130,130);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(0.5,'grey');
lineargradient.addColorStop(1,'black');
ctx.fillStyle = lineargradient;
// draw shapes
ctx.fillRect(10,10,130,130);
}
</code></pre></div></div>
<div style="text-align: center">
<canvas id="cvs9"></canvas>
</div>
<script>
function draw9() {
var ctx = document.getElementById('cvs9').getContext('2d');
var lineargradient = ctx.createLinearGradient(0,0,130,130);
lineargradient.addColorStop(0,'white');
lineargradient.addColorStop(0.5,'grey');
lineargradient.addColorStop(1,'black');
ctx.fillStyle = lineargradient;
// draw shapes
ctx.fillRect(10,10,130,130);
}
draw9();
</script>
<p>然后来看看径向渐变。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw10() {
var ctx = document.getElementById('cvs10').getContext('2d');
var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
radgrad.addColorStop(0, '#A7D30C');
radgrad.addColorStop(0.9, '#019F62');
radgrad.addColorStop(1, 'rgba(1,159,98,0)');
var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
radgrad2.addColorStop(0, '#FF5F98');
radgrad2.addColorStop(0.75, '#FF0188');
radgrad2.addColorStop(1, 'rgba(255,1,136,0)');
var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
radgrad3.addColorStop(0, '#00C9FF');
radgrad3.addColorStop(0.8, '#00B5E2');
radgrad3.addColorStop(1, 'rgba(0,201,255,0)');
var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
radgrad4.addColorStop(0, '#F4F201');
radgrad4.addColorStop(0.8, '#E4C700');
radgrad4.addColorStop(1, 'rgba(228,199,0,0)');
// 画图形
ctx.fillStyle = radgrad4;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad3;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad2;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,150,150);
}
</code></pre></div></div>
<p>本例定义了4个不同的径向渐变。由于可以控制渐变的起始与结束点,所以可以实现稍微复杂的效果。</p>
<p>比如这里让起点稍微偏离终点,这样达到了一种球状3D效果,但最好不要让里圆与外圆部分交叠。</p>
<div style="text-align: center">
<canvas id="cvs10"></canvas>
</div>
<script>
function draw10() {
var ctx = document.getElementById('cvs10').getContext('2d');
var radgrad = ctx.createRadialGradient(45,45,10,52,50,30);
radgrad.addColorStop(0, '#A7D30C');
radgrad.addColorStop(0.9, '#019F62');
radgrad.addColorStop(1, 'rgba(1,159,98,0)');
var radgrad2 = ctx.createRadialGradient(105,105,20,112,120,50);
radgrad2.addColorStop(0, '#FF5F98');
radgrad2.addColorStop(0.75, '#FF0188');
radgrad2.addColorStop(1, 'rgba(255,1,136,0)');
var radgrad3 = ctx.createRadialGradient(95,15,15,102,20,40);
radgrad3.addColorStop(0, '#00C9FF');
radgrad3.addColorStop(0.8, '#00B5E2');
radgrad3.addColorStop(1, 'rgba(0,201,255,0)');
var radgrad4 = ctx.createRadialGradient(0,150,50,0,140,90);
radgrad4.addColorStop(0, '#F4F201');
radgrad4.addColorStop(0.8, '#E4C700');
radgrad4.addColorStop(1, 'rgba(228,199,0,0)');
// 画图形
ctx.fillStyle = radgrad4;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad3;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad2;
ctx.fillRect(0,0,150,150);
ctx.fillStyle = radgrad;
ctx.fillRect(0,0,150,150);
}
draw10();
</script>
<p>样式的介绍就到这里,下一篇博文将介绍文本的绘制和图片的使用。</p>
canvas学习[1]--绘制基本图形
2016-12-18T00:00:00+00:00
http://www.blogways.net/blog/2016/12/18/canvas-1
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#part1">canvas简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#part2">基本用法</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#part3">绘制基本图形</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#part4">组合使用</a></td>
</tr>
</tbody>
</table>
<p><a id="part1"></a></p>
<h2 id="1canvas简介">1.canvas简介</h2>
<p>canvas 是 HTML5 新增的元素,与JavaScript结合使用来绘制图形。例如:画图,合成照片,创建动画甚至实时视频处理与渲染。很多js图标插件都是基于canvas来实现的,比如charts.js、echats3。</p>
<ul>
<li>canvas 首先是由 Apple 引入的,用于 OS X Dashboard 和 Safari。</li>
<li>Mozilla 程序从 Gecko 1.8 (Firefox 1.5) 开始支持 canvas。</li>
<li>Internet Explorer 从IE9开始canvas ,更旧版本的IE可以引入 Google 的 Explorer Canvas 项目中的脚本来获得canvas支持。</li>
<li>Chrome和Opera 9+ 也支持 canvas。</li>
</ul>
<p><a id="part2"></a></p>
<h2 id="2基本用法">2.基本用法</h2>
<h3 id="21-canvas元素">2.1 canvas元素</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><canvas id="myCanvas" width="150" height="150"></canvas>
</code></pre></div></div>
<p>canvas 标签只有两个属性—— width和height。当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。canvas可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS的尺寸与初始画布的比例不一致,canvas中的图像会被扭曲。</p>
<h3 id="22-替换内容">2.2 替换内容</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><canvas id="currentTime" width="150" height="150">
<img src="images/currentTime.png" width="150" height="150" alt=""/>
</canvas>
</code></pre></div></div>
<p>可在canvas的标签内部定义替换内容。</p>
<p>当某些较老的浏览器不支持 canvas 时,浏览器会加载canvas标签内部的元素展示。</p>
<p>当浏览器支持 canvas 是,浏览器会忽略canvas标签包含的内容,正常渲染canvas。</p>
<h3 id="23-渲染上下文the-rendering-context">2.3 渲染上下文(The rendering context)</h3>
<p>canvas 元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,用来绘制和处理要展示的内容。</p>
<p>canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。canvas 元素的 getContext() 的方法,就是是用来获得渲染上下文和它的绘画功能。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext('2d');
// 虽然参数2d会让人有想法,但是很遗憾目前html5不提供3d服务。
</code></pre></div></div>
<p>代码的第一行通过使用 document.getElementById() 方法来为 canvas 元素得到DOM对象。一旦有了元素对象,便可以通过使用它的getContext() 方法来访问绘画上下文。</p>
<p><a id="part2"></a></p>
<h2 id="3-绘制基本图形">3. 绘制基本图形</h2>
<h3 id="31-栅格">3.1 栅格</h3>
<p>在开始画图之前,需要了解一下画布栅格(canvas grid)以及坐标空间。</p>
<p><img src="/images/wanghui/canvas1/img1.png" alt="img1" /></p>
<p>如上图所示,canvas元素默认被网格所覆盖。通常网格中的一个单元相当于canvas元素中的一像素。</p>
<p>栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。</p>
<p>所以图中蓝色方形左上角的坐标为距离左边(Y轴)x像素,距离上边(X轴)y像素(坐标为(x,y))。</p>
<h3 id="32-绘制矩形">3.2 绘制矩形</h3>
<p>不同于SVG,HTML中的元素canvas只支持一种原生的图形绘制:矩形。所有其他的图形的绘制都至少需要生成一条路径。不过很多的路径生成方法让复杂图形的绘制成为了可能。下面来看一下矩形的绘制。</p>
<p>canvas提供了三个矩形绘制相关的方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 绘制一个填充的矩形
fillRect(x, y, width, height)
// 绘制一个矩形的边框
strokeRect(x, y, width, height)
// 清除指定矩形区域,让清除部分完全透明。
clearRect(x, y, width, height)
</code></pre></div></div>
<p>上面提供的方法之中每一个都包含了相同的参数。x与y指定了在canvas画布上所绘制的矩形的左上角(相对于原点)的坐标。width和height设置矩形的尺寸。</p>
<p>下面来看一个具体的例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script>
var canvas = document.getElementById('cvs1');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
// fillRect()绘制了一个边长为100px的黑色正方形
ctx.fillRect(25,25,100,100);
// clearRect()从正方形的中心开始擦除了一个60*60px的正方形
ctx.clearRect(45,45,60,60);
// strokeRect()在清除区域内生成一个50*50的正方形边框。
ctx.strokeRect(50,50,50,50);
}
</script>
</code></pre></div></div>
<p>该例子的输出如下图所示。</p>
<div style="text-align: center">
<canvas id="cvs1"></canvas>
</div>
<script>
function draw1() {
var canvas = document.getElementById('cvs1');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.fillRect(25,25,100,100);
ctx.clearRect(45,45,60,60);
ctx.strokeRect(50,50,50,50);
}
}
draw1();
</script>
<p>不同于下面所要介绍的路径函数(path function),以上的三个函数绘制之后会马上显现在canvas上,即时生效。</p>
<h3 id="33-绘制路径">3.3 绘制路径</h3>
<h4 id="331-路径简介">3.3.1 路径简介</h4>
<p>图形的基本元素是路径。路径是通过不同颜色和宽度的线段或曲线相连形成的不同形状的点的集合。一个路径,甚至一个子路径,都是闭合的。使用路径绘制图形需要一些额外的步骤。</p>
<ol>
<li>首先,需要创建路径起始点。</li>
<li>然后使用画图命令去画出路径。</li>
<li>之后把路径封闭。</li>
<li>一旦路径生成,就能通过描边或填充路径区域来渲染图形。</li>
</ol>
<p>以下是所要用到的函数:</p>
<ul>
<li>
<p>beginPath()</p>
<p>新建一条路径,生成之后,图形绘制命令被指向到路径上生成路径。</p>
</li>
<li>
<p>closePath()</p>
<p>闭合路径之后图形绘制命令又重新指向到上下文中。</p>
</li>
<li>
<p>stroke()</p>
<p>通过线条来绘制图形轮廓。</p>
</li>
<li>
<p>fill()</p>
<p>通过填充路径的内容区域生成实心的图形。</p>
</li>
</ul>
<p>生成路径的第一步叫做beginPath()。本质上,路径是由很多子路径构成,这些子路径都是在一个列表中,所有的子路径(线、弧形、等等)构成图形。而每次这个方法调用之后,列表清空重置,然后我们就可以重新绘制新的图形。</p>
<p>第二步就是调用函数指定绘制路径。</p>
<p>第三,就是闭合路径closePath(),此步不是必需的。这个方法会通过绘制一条从当前点到开始点的直线来闭合图形。如果图形是已经闭合了的,即当前点为开始点,该函数什么也不做。</p>
<p>而当调用fill()函数时,没有闭合的形状都会自动闭合,所以不需要调用closePath()函数。但是调用stroke()时不会自动闭合。</p>
<p>下面来通过路径绘制一个三角形。</p>
<h4 id="332-绘制三角形">3.3.2 绘制三角形</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw2() {
var canvas = document.getElementById('canvas');
if (canvas.getContext){
var ctx = canvas.getContext('2d');
// 新建路径
ctx.beginPath();
// 移动笔触
ctx.moveTo(75,50);
// 划一条直线至(x,y)点
ctx.lineTo(100,75);
ctx.lineTo(100,25);
// 填充
ctx.fill();
}
}
</code></pre></div></div>
<p>输出如下:</p>
<div style="text-align: center">
<canvas id="cvs2"></canvas>
</div>
<script>
function draw2() {
var canvas = document.getElementById('cvs2');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(100,45);
ctx.lineTo(75,75);
ctx.lineTo(125,75);
ctx.fill();
}
}
draw2();
</script>
<p>例子所用到的moveTo()函数和lineTo()函数,下面来分别介绍。</p>
<h4 id="323-移动笔触">3.2.3 移动笔触</h4>
<p>moveTo()函数是移动笔触的函数。</p>
<p>这个函数实际上并不能画出任何东西,仅仅是将绘制点移动至某个坐标。可以想象一下在纸上作业,一支笔的笔尖从一个点到另一个点的移动过程。</p>
<ul>
<li>moveTo(x, y)</li>
</ul>
<p>将笔触移动到指定的坐标x以及y上。</p>
<p>当canvas初始化或者beginPath()调用后,通常会使用moveTo()函数设置起点。</p>
<p>也可以使用moveTo()绘制一些不连续的路径,比如下面这个例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw3() {
var canvas = document.getElementById('cvs3');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // 绘制
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // 口(顺时针)
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // 左眼
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // 右眼
ctx.stroke();
}
}
function draw4() {
var canvas = document.getElementById('cvs4');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // 绘制
// ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // 口(顺时针)
// ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // 左眼
// ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // 右眼
ctx.stroke();
}
}
</code></pre></div></div>
<p>左边是使用moveTo()的效果,右边是不使用moveTo()的效果,至于例子中的arc()函数会在下面介绍。</p>
<div style="text-align: center">
<canvas id="cvs3"></canvas>
<canvas id="cvs4"></canvas>
</div>
<script>
function draw3() {
var canvas = document.getElementById('cvs3');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // 绘制
ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // 口(顺时针)
ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // 左眼
ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // 右眼
ctx.stroke();
}
}
function draw4() {
var canvas = document.getElementById('cvs4');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(75,75,50,0,Math.PI*2,true); // 绘制
// ctx.moveTo(110,75);
ctx.arc(75,75,35,0,Math.PI,false); // 口(顺时针)
// ctx.moveTo(65,65);
ctx.arc(60,65,5,0,Math.PI*2,true); // 左眼
// ctx.moveTo(95,65);
ctx.arc(90,65,5,0,Math.PI*2,true); // 右眼
ctx.stroke();
}
}
draw3();
draw4();
</script>
<h4 id="324-直线">3.2.4 直线</h4>
<p>绘制直线,用到的方法是lineTo()。</p>
<ul>
<li>
<p>lineTo(x, y)</p>
<p>绘制一条从当前位置到指定x以及y位置的直线。</p>
</li>
</ul>
<p>该方法有两个参数:x以及y,代表坐标系中直线结束的点。</p>
<p>开始点和之前的绘制路径有关,之前路径的结束点就是接下来的开始点,开始点也可以通过moveTo()函数改变。</p>
<p>下面的例子绘制两个三角形,一个是填充的,另一个是描边的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw5() {
var canvas = document.getElementById('cvs5');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
// 填充三角形
ctx.beginPath();
ctx.moveTo(25,25);
ctx.lineTo(105,25);
ctx.lineTo(25,105);
ctx.fill();
// 描边三角形
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke();
}
}
</code></pre></div></div>
<p>可以注意到填充与描边三角形步骤有所不同。</p>
<p>正如上面所提到的,因为路径使用填充(filled)时,路径自动闭合,使用描边(stroked)则不会闭合路径。</p>
<p>如果没有添加闭合路径closePath()到描述三角形函数中,则只绘制了两条线段,并不是一个完整的三角形。</p>
<p>例子输出如下:</p>
<div style="text-align: center">
<canvas id="cvs5"></canvas>
</div>
<script>
function draw5() {
var canvas = document.getElementById('cvs5');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
// 填充三角形
ctx.beginPath();
ctx.moveTo(25,25);
ctx.lineTo(105,25);
ctx.lineTo(25,105);
ctx.fill();
// 描边三角形
ctx.beginPath();
ctx.moveTo(125,125);
ctx.lineTo(125,45);
ctx.lineTo(45,125);
ctx.closePath();
ctx.stroke();
}
}
draw5();
</script>
<h4 id="325-圆弧">3.2.5 圆弧</h4>
<p>绘制圆弧或者圆,可以使用arc()函数和arcTo()函数,然而根据MDN的说法,arcTo()函数的实现不是很靠谱,所以这里不作详细介绍。</p>
<ul>
<li>
<p>arc(x, y, radius, startAngle, endAngle, anticlockwise)</p>
<p>画一个以(x,y)为圆心的以radius为半径的圆弧(圆),从startAngle开始到endAngle结束,按照anticlockwise给定的方向(默认为顺时针)来生成。</p>
</li>
<li>
<p>arcTo(x1, y1, x2, y2, radius)</p>
<p>根据给定的控制点和半径画一段圆弧,再以直线连接两个控制点。</p>
</li>
</ul>
<p>arc()函数有五个参数:</p>
<ul>
<li>x,y为绘制圆弧所在圆上的圆心坐标。</li>
<li>radius为半径。</li>
<li>startAngle以及endAngle参数用弧度定义了开始以及结束的弧度。这些都是以x轴为基准。</li>
<li>参数anticlockwise 为一个布尔值。为true时,是逆时针方向,否则顺时针方向。</li>
</ul>
<font style="color: red;">arc()函数中的角度单位是弧度,不是度数。角度与弧度的js表达式:radians=(Math.PI/180)*degrees。</font>
<p>下面的例子比上面的要复杂一下,下面绘制了12个不同的角度以及填充的圆弧。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw6() {
var canvas = document.getElementById('cvs6');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
for(var i=0;i<4;i++){
for(var j=0;j<3;j++){
ctx.beginPath();
var x = 25+j*50; // x 坐标值
var y = 25+i*50; // y 坐标值
var radius = 20; // 圆弧半径
var startAngle = 0; // 开始点
var endAngle = Math.PI+(Math.PI*j)/2; // 结束点
var anticlockwise = i%2==0 ? false : true; // 顺时针或逆时针
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}
</code></pre></div></div>
<p>上面两个for循环,生成圆弧的行列(x,y)坐标。每一段圆弧的开始都调用beginPath()。</p>
<p>x,y坐标是可变的。半径(radius)和开始角度(startAngle)都是固定的。结束角度(endAngle)在第一列开始时是180度(半圆)然后每列增加90度。最后一列形成一个完整的圆。</p>
<p>clockwise 语句作用于第一、三行是顺时针的圆弧,anticlockwise作用于二、四行为逆时针圆弧。if 语句让一、二行描边圆弧,下面两行填充路径。</p>
<div style="text-align: center">
<canvas id="cvs6" width="150px" height="195px"></canvas>
</div>
<script>
function draw6() {
var canvas = document.getElementById('cvs6');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
for(var i=0;i<4;i++){
for(var j=0;j<3;j++){
ctx.beginPath();
var x = 25+j*50; // x 坐标值
var y = 25+i*50; // y 坐标值
var radius = 20; // 圆弧半径
var startAngle = 0; // 开始点
var endAngle = Math.PI+(Math.PI*j)/2; // 结束点
var anticlockwise = i%2==0 ? false : true; // 顺时针或逆时针
ctx.arc(x, y, radius, startAngle, endAngle, anticlockwise);
if (i>1){
ctx.fill();
} else {
ctx.stroke();
}
}
}
}
}
draw6();
</script>
<h4 id="326-贝塞尔bezier以及二次贝塞尔">3.2.6 贝塞尔(bezier)以及二次贝塞尔</h4>
<p>贝塞尔曲线在之前的svg介绍中也有提及,下面来看看canvas中的贝塞尔曲线。</p>
<ul>
<li>
<p>quadraticCurveTo(cp1x, cp1y, x, y)</p>
<p>绘制贝塞尔曲线,cp1x,cp1y为控制点,x,y为结束点。</p>
</li>
<li>
<p>bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)</p>
<p>绘制二次贝塞尔曲线,cp1x,cp1y为控制点一,cp2x,cp2y为控制点二,x,y为结束点。</p>
</li>
</ul>
<p>下面的图能够很好的描述两者的关系,贝塞尔曲线有一个开始、结束点(蓝色)以及一个控制点(红色),而二次贝塞尔曲线使用两个控制点。</p>
<p><img src="/images/wanghui/canvas1/img2.png" alt="img2" /></p>
<p>参数x、y在这两个方法中都是结束点坐标。cp1x,cp1y为坐标中的第一个控制点,cp2x,cp2y为坐标中的第二个控制点。</p>
<p>下面来看两个个简单的例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 一次贝塞尔
function draw7() {
var canvas = document.getElementById('cvs7');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75,25);
ctx.quadraticCurveTo(25,25,25,62.5);
ctx.quadraticCurveTo(25,100,50,100);
ctx.quadraticCurveTo(50,120,30,125);
ctx.quadraticCurveTo(60,120,65,100);
ctx.quadraticCurveTo(125,100,125,62.5);
ctx.quadraticCurveTo(125,25,75,25);
ctx.stroke();
}
}
// 二次贝塞尔
function draw8() {
var canvas = document.getElementById('cvs8');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fill();
}
}
</code></pre></div></div>
<div style="text-align: center">
<canvas id="cvs7" width="200px"></canvas>
<canvas id="cvs8" width="200px"></canvas>
</div>
<script>
function draw7() {
var canvas = document.getElementById('cvs7');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75,25);
ctx.quadraticCurveTo(25,25,25,62.5);
ctx.quadraticCurveTo(25,100,50,100);
ctx.quadraticCurveTo(50,120,30,125);
ctx.quadraticCurveTo(60,120,65,100);
ctx.quadraticCurveTo(125,100,125,62.5);
ctx.quadraticCurveTo(125,25,75,25);
ctx.stroke();
}
}
function draw8() {
var canvas = document.getElementById('cvs8');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(75,40);
ctx.bezierCurveTo(75,37,70,25,50,25);
ctx.bezierCurveTo(20,25,20,62.5,20,62.5);
ctx.bezierCurveTo(20,80,40,102,75,120);
ctx.bezierCurveTo(110,102,130,80,130,62.5);
ctx.bezierCurveTo(130,62.5,130,25,100,25);
ctx.bezierCurveTo(85,25,75,37,75,40);
ctx.fill();
}
}
draw7();
draw8();
</script>
<p>使用一次以及二次贝塞尔曲线是有一定的难度的,因为不同于像Adobe Illustrators这样的矢量软件,canvas所绘制的曲线没有直接的视觉反馈。这让绘制复杂的图形十分的困难。即使这样,只要有时间和耐心,很多复杂的图形都可以绘制出来。</p>
<p><a id="part4"></a></p>
<h2 id="4组合使用">4.组合使用</h2>
<p>目前为止,每一个例子中的每个图形都只用到一种类型的函数,所以下面来看看将这些函数组合绘图的效果。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> function draw9() {
var canvas = document.getElementById('cvs9');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(150, 0);
ctx.lineTo(75, 75);
ctx.lineTo(150, 150);
ctx.lineTo(225, 75);
ctx.lineTo(150, 0);
ctx.stroke();
ctx.closePath();
ctx.strokeRect(75,0, 150, 150);
ctx.beginPath();
ctx.arc(150, 75, 52, Math.PI / 2, Math.PI * 3 / 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(150,23);
ctx.bezierCurveTo(190,30,190,70,150,75);
ctx.bezierCurveTo(110,82,110,122,150,127);
ctx.bezierCurveTo(220,127,220,23,150,23);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 49, 5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 101, 5, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
}
</code></pre></div></div>
<p>上面例子中的fillStyle属性可以用来设置fill填充颜色,颜色与样式的内容会在下一章介绍。</p>
<div style="text-align: center">
<canvas id="cvs9" width="300px" height="300"></canvas>
</div>
<script>
function draw9() {
var canvas = document.getElementById('cvs9');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(150, 0);
ctx.lineTo(75, 75);
ctx.lineTo(150, 150);
ctx.lineTo(225, 75);
ctx.lineTo(150, 0);
ctx.stroke();
ctx.closePath();
ctx.strokeRect(75,0, 150, 150);
ctx.beginPath();
ctx.arc(150, 75, 52, Math.PI / 2, Math.PI * 3 / 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.moveTo(150,23);
ctx.bezierCurveTo(190,30,190,70,150,75);
ctx.bezierCurveTo(110,82,110,122,150,127);
ctx.bezierCurveTo(220,127,220,23,150,23);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 49, 5, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(150, 101, 5, 0, Math.PI * 2);
ctx.fillStyle = 'white';
ctx.fill();
}
}
draw9();
</script>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function draw10() {
var canvas = document.getElementById('cvs10');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
roundedRect(ctx,12,12,150,150,15);
roundedRect(ctx,19,19,150,150,9);
roundedRect(ctx,53,53,49,33,10);
roundedRect(ctx,53,119,49,16,6);
roundedRect(ctx,135,53,49,33,10);
roundedRect(ctx,135,119,25,49,10);
ctx.beginPath();
ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false);
ctx.lineTo(31,37);
ctx.fill();
for(var i=0;i<8;i++){
ctx.fillRect(51+i*16,35,4,4);
}
for(i=0;i<6;i++){
ctx.fillRect(115,51+i*16,4,4);
}
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,99,4,4);
}
ctx.beginPath();
ctx.moveTo(83,116);
ctx.lineTo(83,102);
ctx.bezierCurveTo(83,94,89,88,97,88);
ctx.bezierCurveTo(105,88,111,94,111,102);
ctx.lineTo(111,116);
ctx.lineTo(106.333,111.333);
ctx.lineTo(101.666,116);
ctx.lineTo(97,111.333);
ctx.lineTo(92.333,116);
ctx.lineTo(87.666,111.333);
ctx.lineTo(83,116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91,96);
ctx.bezierCurveTo(88,96,87,99,87,101);
ctx.bezierCurveTo(87,103,88,106,91,106);
ctx.bezierCurveTo(94,106,95,103,95,101);
ctx.bezierCurveTo(95,99,94,96,91,96);
ctx.moveTo(103,96);
ctx.bezierCurveTo(100,96,99,99,99,101);
ctx.bezierCurveTo(99,103,100,106,103,106);
ctx.bezierCurveTo(106,106,107,103,107,101);
ctx.bezierCurveTo(107,99,106,96,103,96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101,102,2,0,Math.PI*2,true);
ctx.fill();
ctx.beginPath();
ctx.arc(89,102,2,0,Math.PI*2,true);
ctx.fill();
}
}
function roundedRect(ctx,x,y,width,height,radius){
ctx.beginPath();
ctx.moveTo(x,y+radius);
ctx.lineTo(x,y+height-radius);
ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
ctx.lineTo(x+width-radius,y+height);
ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
ctx.lineTo(x+width,y+radius);
ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
ctx.lineTo(x+radius,y);
ctx.quadraticCurveTo(x,y,x,y+radius);
ctx.stroke();
}
</code></pre></div></div>
<p>这个例子是MDN上的一个例子,这个例子看着复杂,只要耐下性子看看,其实很容易理解,这里就不详细说明。</p>
<p>重要的一点是绘制上下文中使用到了fillStyle属性,以及封装函数(例子中的 roundedRect())。</p>
<p>fillStyle属性会在下次介绍,而使用封装函数对于减少代码量以及复杂度十分有用,在canvas的使用中,有效的封装函数才是重中之重。</p>
<div style="text-align: center">
<canvas id="cvs10" width="300px" height="300"></canvas>
</div>
<script>
function draw10() {
var canvas = document.getElementById('cvs10');
var ctx = canvas.getContext('2d');
if (canvas.getContext) {
var ctx = canvas.getContext('2d');
roundedRect(ctx,12,12,150,150,15);
roundedRect(ctx,19,19,150,150,9);
roundedRect(ctx,53,53,49,33,10);
roundedRect(ctx,53,119,49,16,6);
roundedRect(ctx,135,53,49,33,10);
roundedRect(ctx,135,119,25,49,10);
ctx.beginPath();
ctx.arc(37,37,13,Math.PI/7,-Math.PI/7,false);
ctx.lineTo(31,37);
ctx.fill();
for(var i=0;i<8;i++){
ctx.fillRect(51+i*16,35,4,4);
}
for(i=0;i<6;i++){
ctx.fillRect(115,51+i*16,4,4);
}
for(i=0;i<8;i++){
ctx.fillRect(51+i*16,99,4,4);
}
ctx.beginPath();
ctx.moveTo(83,116);
ctx.lineTo(83,102);
ctx.bezierCurveTo(83,94,89,88,97,88);
ctx.bezierCurveTo(105,88,111,94,111,102);
ctx.lineTo(111,116);
ctx.lineTo(106.333,111.333);
ctx.lineTo(101.666,116);
ctx.lineTo(97,111.333);
ctx.lineTo(92.333,116);
ctx.lineTo(87.666,111.333);
ctx.lineTo(83,116);
ctx.fill();
ctx.fillStyle = "white";
ctx.beginPath();
ctx.moveTo(91,96);
ctx.bezierCurveTo(88,96,87,99,87,101);
ctx.bezierCurveTo(87,103,88,106,91,106);
ctx.bezierCurveTo(94,106,95,103,95,101);
ctx.bezierCurveTo(95,99,94,96,91,96);
ctx.moveTo(103,96);
ctx.bezierCurveTo(100,96,99,99,99,101);
ctx.bezierCurveTo(99,103,100,106,103,106);
ctx.bezierCurveTo(106,106,107,103,107,101);
ctx.bezierCurveTo(107,99,106,96,103,96);
ctx.fill();
ctx.fillStyle = "black";
ctx.beginPath();
ctx.arc(101,102,2,0,Math.PI*2,true);
ctx.fill();
ctx.beginPath();
ctx.arc(89,102,2,0,Math.PI*2,true);
ctx.fill();
}
}
function roundedRect(ctx,x,y,width,height,radius){
ctx.beginPath();
ctx.moveTo(x,y+radius);
ctx.lineTo(x,y+height-radius);
ctx.quadraticCurveTo(x,y+height,x+radius,y+height);
ctx.lineTo(x+width-radius,y+height);
ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius);
ctx.lineTo(x+width,y+radius);
ctx.quadraticCurveTo(x+width,y,x+width-radius,y);
ctx.lineTo(x+radius,y);
ctx.quadraticCurveTo(x,y,x,y+radius);
ctx.stroke();
}
draw10();
</script>
zabbix安装部署
2016-12-17T00:00:00+00:00
http://www.blogways.net/blog/2016/12/17/zabbix-intro
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#part1">zabbix简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#part2">搭建依赖环境</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#part3">zabbix安装配置</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#part4">问题汇总</a></td>
</tr>
</tbody>
</table>
<p><a id="part1"></a></p>
<h2 id="1-zabbix简介">1. zabbix简介</h2>
<h3 id="11-zabbix自述">1.1 zabbix自述</h3>
<p>Zabbix是一个企业级的、开源的、分布式的监控套件;</p>
<p>Zabbix 可以监控网络和服务的运行状况。 Zabbix 利用灵活的告警机制,允许用户对事件发送基于 Email 的告警。</p>
<p>Zabbix利用存储数据提供杰出的报告及图表,这一特性将帮助用户完成配置规划;</p>
<p>Zabbix支持polling和trapping两种方式. 所有的Zabbix报告都可以通过配置参数在WEB前端进行访问。Web 前端将帮助你在任何区域都能够迅速获得你的网络及服务状况。</p>
<h3 id="12-功能特点">1.2 功能特点</h3>
<ul>
<li>分布式监控,天生具有的功能,适合于构建分布式监控系统,具有node,proxy2种分布式模式;</li>
<li>自动化功能,自动发现,自动注册主机,自动添加模板,自动添加分组,是适应于自动化运维利器的首选;</li>
<li>自定义监控比较方便,自定义监控项非常简单,支持变量;</li>
<li>支持多种监控方式,agentd,snmp,ipmi,jmx;</li>
<li>提供api功能,二次开发方便,可以进行二次深度开发。</li>
</ul>
<h3 id="13-进程构成">1.3 进程构成</h3>
<ul>
<li>
<p>zabbix_server:
zabbix 服务端守护进程,获取的主机数据最终都是提交到 server(server可主动访问agentd获取数据,也可配置agentd主动推送数据至server)。</p>
</li>
<li>
<p>zabbix_agentd:
客户端守护进程,此进程收集客户端数据,例如 cpu 负载、内存、硬盘使用情况等。</p>
</li>
<li>
<p>zabbix_get:
zabbix 工具,单独使用的命令,通常在 server 或者 proxy 端执行获取远程客户端信息的命令,排查客户端与服务端是否连接故障。</p>
</li>
<li>
<p>zabbix_sender:
zabbix 工具,用于发送数据给 server 或者 proxy,通常用于耗时比较长的检查。很多检查非常耗时间,导致zabbix 超时。可在脚本执行完毕之后,使用 sender 主动提交数据。</p>
</li>
<li>
<p>zabbix_proxy:
zabbix 代理守护进程。功能类似 server,唯一不同的是它只是一个中转站,它需要把收集到的数据提交(或被提交)到 server 里。</p>
</li>
<li>
<p>zabbix_java_gateway:
Java 网关,类似 agentd,但是只用于 Java 方面。它只能主动去获取数据,而不能被动获取数据。它的数据最终会给到 server 或者 proxy。</p>
</li>
</ul>
<h3 id="14-操作系统支持">1.4 操作系统支持</h3>
<p>以下操作系统支持所有进程:</p>
<ul>
<li>Linux</li>
<li>IBM AIX</li>
<li>FreeBSD</li>
<li>NetBSD</li>
<li>OpenBSD</li>
<li>HP-UX</li>
<li>Mac OSX</li>
<li>Solaris</li>
</ul>
<p>以下操作系统仅支持客户端agentd:</p>
<ul>
<li>Windows 2000</li>
<li>Windows Server 2003</li>
<li>Windows Server 2008</li>
<li>Windows Server 2012</li>
<li>Windows XP</li>
<li>Windows Vista</li>
<li>Windows 7</li>
<li>Windows 8</li>
</ul>
<h3 id="15-磁盘占用量计算">1.5 磁盘占用量计算</h3>
<p>zabbix的数据有以下几种数据构成:</p>
<ul>
<li>配置数据:zabbix的配置文件等,一般固定大小在10MB以下;</li>
<li>历史数据:zabbix会保存一定时间内监控数据,记录时间段内的主机运行情况;</li>
<li>趋势数据:即监控指标的最大值、最小值、平均值、计数等此类数据;</li>
<li>时间记录:当监控项指标超过某阀值时zabbix会产生事件提示运维人员,zabbix会保存此事件的数据直至被删除。</li>
</ul>
<p>####场景:</p>
<p>假设要监控100台服务器,每台30个监控项,每个监控项60秒刷新一次,即每秒会有50(30*100/60)条数据入库。</p>
<h4 id="配置数据大小">配置数据大小:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zabbix配置数据: 固定大小,一般<10MB。
</code></pre></div></div>
<h4 id="历史数据大小">历史数据大小:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>历史数据(保留30天,一条历史数据约50字节):
30天*24小时*3600秒*50条数据*50字节 ≈ 6.5G
</code></pre></div></div>
<h4 id="趋势数据大小">趋势数据大小:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>趋势数据(保留5年,当查看一周或一个月的图表,其中的MAX/MIN/AVG/COUNT取自趋势数据,一条趋势数据约128字节):
5年*365天*24小时*3000个监控项*128字节 ≈ 16.8G
</code></pre></div></div>
<h4 id="事件数据大小">事件数据大小:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>事件记录(一个事件约占130个字节,每秒产生一个事件,保留1年):
1年*365天*24小时*3600秒*130字节 ≈ 4.1G
</code></pre></div></div>
<h4 id="结论">结论:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>此场景下需要的数据库硬盘容量 ≈ 27.41G 。
</code></pre></div></div>
<p><a id="part2"></a></p>
<h2 id="2-搭建依赖环境">2. 搭建依赖环境</h2>
<h3 id="21-zabbix依赖环境">2.1 zabbix依赖环境</h3>
<p>zabbix后端程序是用c语言写的,前台web页面是用php语言写的,需要数据库的支持。</p>
<p>操作系统前面已介绍,此处不再赘述。</p>
<p>zabbix可使用以下数据库:</p>
<ul>
<li>MySQL 5.0.3 及以上,推荐使用InnoDB引擎</li>
<li>Oracle 10g 及以上</li>
<li>PostgreSQL 8.1 及以上</li>
<li>SQLite 3.3.5 及以上</li>
<li>IBM DB2 9.7 及以上</li>
</ul>
<p>web环境依赖如下:</p>
<ul>
<li>Apache HTTP Server 1.3.12 及以上</li>
<li>PHP 5.3.0 及以上</li>
</ul>
<p>本例操作系统为Mac OS 10.12.1,数据库采用MySQL 5.7.16,此处主要介绍Apache HTTP Server 和 PHP 的安装。</p>
<h3 id="22-安装-apache-http-server">2.2 安装 Apache HTTP Server</h3>
<ul>
<li>下载源码包<code class="language-plaintext highlighter-rouge">httpd-2.4.23.tar.gz</code>至<code class="language-plaintext highlighter-rouge">/usr/local/src</code>目录并解压,的到<code class="language-plaintext highlighter-rouge">/usr/local/src/httpd-2.4.23</code>目录</li>
<li><code class="language-plaintext highlighter-rouge">cd httpd-2.4.23</code>,执行命令<code class="language-plaintext highlighter-rouge">./configure --prefix=/usr/local/httpd</code>,配置完毕后执行<code class="language-plaintext highlighter-rouge">make && make install</code>,无报错即安装成功。</li>
<li><code class="language-plaintext highlighter-rouge">cp /usr/local/httpd/bin/apachectl /usr/sbin</code>,之后可通过命令:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo apachectl start -- 启动服务
sudo apachectl stop -- 停止服务
sudo apachectl status -- 查看状态
sudo apachectl restart -- 重启服务
</code></pre></div></div>
<p>来管理服务的启停。</p>
<ul>
<li>编辑 <code class="language-plaintext highlighter-rouge">/usr/local/httpd/conf/httpd.conf</code> 修改配置项:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.去掉
#LoadModule php5_module modules/libphp5.so
此行的注释
2.<Directory />标签内部内容做如下修改:
<Directory />
Options Includes ExecCGI FollowSymLinks
AllowOverride All
Order deny,allow
Deny from all
</Directory>
3.修改DocumentRoot内容为:
DocumentRoot "/usr/local/httpd/htdocs"
4.将<Directory "/usr/local/httpd/htdocs">标签内容修改为:
<Directory "/usr/local/httpd/htdocs">
Options FollowSymLinks
AllowOverride None
Order allow,deny
Allow from all
Require all granted
</Directory>
5. 将<IfModule dir_module>标签内容修改为:
<IfModule dir_module>
DirectoryIndex index.html index.php
</IfModule>
</code></pre></div></div>
<p>以上修改主要是添加对php的支持,至此Apache HTTP Server安装完毕。</p>
<h3 id="23-安装php">2.3 安装PHP</h3>
<p>php的安装会比较麻烦,因为php有很多扩展需要额外配置安装,zabbix所需要的php扩展支持如下:</p>
<ul>
<li>bcmath</li>
<li>socket</li>
<li>mbstring</li>
<li>gettext</li>
<li>snmp</li>
<li>libxml</li>
<li>gd(gd扩展最为麻烦,除gd本身以外,还需要向gd添加png、jpeg、freetype三种支持)</li>
</ul>
<p>其中,<code class="language-plaintext highlighter-rouge">bcmath</code>、<code class="language-plaintext highlighter-rouge">socket</code>、<code class="language-plaintext highlighter-rouge">mbstring</code>只需要在configure的时候加上<code class="language-plaintext highlighter-rouge">--enable-xxx</code>的参数就能在编译安装的同时添加。</p>
<p>而<code class="language-plaintext highlighter-rouge">gettext</code>、<code class="language-plaintext highlighter-rouge">snmp </code>、<code class="language-plaintext highlighter-rouge">libxml</code>、<code class="language-plaintext highlighter-rouge">gd</code>则需要在configure时指定这些扩展的目录路径,也就是说在本机要先安装这些扩展,配置php时指定路径才能给php添加上所需扩展。</p>
<p>所以此时先不急着安装php,而应先安装php扩展的依赖扩展。</p>
<p><code class="language-plaintext highlighter-rouge">snmp </code>、<code class="language-plaintext highlighter-rouge">gettext</code>和<code class="language-plaintext highlighter-rouge">libxml</code>的安装比较简单,下载源码包编译安装即可,这里简单介绍相对比较麻烦的<code class="language-plaintext highlighter-rouge">gd</code>扩展的安装。</p>
<p>libgd是一个开源的图像处理的库程序,全称是GD Graphics Library,php开发中对于图像的处理会使用到。libgd可在官网下载源码安装,但是各种图片类型的支持需要额外添加,zabbix必须的图片支持有<code class="language-plaintext highlighter-rouge">libjpeg</code>、<code class="language-plaintext highlighter-rouge">libpng</code>、<code class="language-plaintext highlighter-rouge">freetype</code>三种。</p>
<p>所以整个php的安装顺序应是:</p>
<p>1、安装<code class="language-plaintext highlighter-rouge">libjpeg</code>、<code class="language-plaintext highlighter-rouge">libpng</code>、<code class="language-plaintext highlighter-rouge">freetype</code>三种gd库需要的支持;
2、安装<code class="language-plaintext highlighter-rouge">gd</code>、<code class="language-plaintext highlighter-rouge">gettext</code>、<code class="language-plaintext highlighter-rouge">snmp </code>、<code class="language-plaintext highlighter-rouge">libxml</code>四个php需要指定路径的扩展;
3、安装<code class="language-plaintext highlighter-rouge">php</code>,在配置阶段把所需扩展添加上。</p>
<p>以上扩展的安装此处不赘述,可自行查阅其他资料进行安装,本文只介绍php的安装。</p>
<p>最终,php的configure命令应是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure \
--prefix=/usr/local/php \
--with-apxs2=/usr/local/httpd/bin/apxs \
--enable-bcmath \
--enable-socket \
--with-mysql=/Users/mysql/mysql \
--with-mysqli=/Users/mysql/mysql/bin/mysql_config \
--with-mysql-sock=/tmp/mysql.sock \
--with-libxml-dir=libxml安装目录 \
--with-snmp=libsnmp安装目录 \
--with-gettext=libgettext安装目录 \
--with-gd=libgd安装目录;
make && make test;
make install;
</code></pre></div></div>
<p>php安装完毕后,拷贝源码包内的<code class="language-plaintext highlighter-rouge">php.ini-production</code>至安装目录的<code class="language-plaintext highlighter-rouge">lib</code>目录下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp /usr/local/src/php-5.6.28/php.ini-production /usr/local/php/lib/php.ini
</code></pre></div></div>
<p>并根据zabbix的需要编辑修改:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>max_execution_time = 300
max_input_time = 300
memory_limit = 128M
post_max_size = 32M
date.timezone = Asia/Shanghai
mbstring.func_overload=2
</code></pre></div></div>
<p>至此,php安装完毕。</p>
<p><a id="part3"></a></p>
<h2 id="3-安装zabbix">3. 安装zabbix</h2>
<p>下载源码包<code class="language-plaintext highlighter-rouge">zabbix-3.2.1.tar.gz</code>至<code class="language-plaintext highlighter-rouge">/Users/zabbix/src</code>并解压。</p>
<h3 id="31-数据库初始化">3.1 数据库初始化</h3>
<p>连接mysql并创建名为zabbix的database。</p>
<p><code class="language-plaintext highlighter-rouge">cd /Users/zabbix/src/zabbix-3.2.1/database/mysql</code></p>
<p>在此目录下有三个sql脚本文件<code class="language-plaintext highlighter-rouge">schema.sql、images.sql、data.sql</code>,将这三个文件导入zabbix database即完成了初始化:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysql -uzabbix -p123456 -hlocalhost zabbix < /usr/local/src/zabbix-3.2.0/database/mysql/schema.sql
mysql -uzabbix -p123456 -hlocalhost zabbix < /usr/local/src/zabbix-3.2.0/database/mysql/images.sql
mysql -uzabbix -p123456 -hlocalhost zabbix < /usr/local/src/zabbix-3.2.0/database/mysql/data.sql
</code></pre></div></div>
<p>注意导入顺序,需要按照此顺序导入,否则失败。</p>
<h3 id="32-安装zabbix">3.2 安装zabbix</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#配置
./configure \
--prefix=/Users/zabbix/zabbix \
--enable-server \ # 安装server进程服务
--enable-agent \ # 安装agentd进程服务
--with-net-snmp \ # 安装net-snmp监控接口
--with-libcurl \
--with-mysql=/usr/bin/mysql_config # mysql数据库配置
#编译
make
#安装
make install
#添加系统软连接
ln -s /Users/zabbix/zabbix/sbin/* /usr/local/sbin/
#添加系统软连接
ln -s /Users/zabbix/zabbix/bin/* /usr/local/bin/
</code></pre></div></div>
<p>在配置时候不需要的组件可以不进行配置,如只想在主机上安装agentd进程,只需要<code class="language-plaintext highlighter-rouge">./configure --enable-agent</code>即可。</p>
<h3 id="33-修改配置">3.3 修改配置</h3>
<p>编辑 <code class="language-plaintext highlighter-rouge">/etc/services</code> 在最后一行添加:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zabbix-agent 10050/tcp # Zabbix Agent
zabbix-agent 10050/udp # Zabbix Agent
zabbix-trapper 10051/tcp # Zabbix Trapper
zabbix-trapper 10051/udp # Zabbix Trapper
</code></pre></div></div>
<p>由于安装了server和agentd两个进程,所以在<code class="language-plaintext highlighter-rouge">/Users/zabbix/zabbix/etc</code>目录下有<code class="language-plaintext highlighter-rouge">zabbix_server.conf、zabbix_agentd.conf</code>对应两个进程的配置文件,下面就要修改这两个文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi zabbix_server.conf
DBName=zabbix #数据库名称
DBUser=zabbix #数据库用户名
DBPassword=123456 #数据库密码
ListenIP=127.0.0.1 #数据库ip地址
AlertScriptsPath=/Users/zabbix/zabbix/share/zabbix/alertscripts #zabbix运行脚本存放目录
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi zabbix_agentd.conf
Server=127.0.0.1 #服务端Server的IP,配置此项agentd会被动等待服务端拉去监控数据
ListenIP=127.0.0.1 #客户端agentd的IP
ServerActive=127.0.0.1 #服务端Server的IP,配置此项agentd会主动向服务端推送监控数据
</code></pre></div></div>
<p>添加系统软连接</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ln -s /Users/zabbix/zabbix/sbin/* /usr/sbin/
</code></pre></div></div>
<h3 id="34-配置web">3.4 配置Web</h3>
<p>在zabbix源码包的<code class="language-plaintext highlighter-rouge">frontends</code>目录下存放zabbix的前端php文件,此时需将这些文件复制到Apache HTTP Server服务维护的目录。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp -r /User/zabbix/src/ zabbix-3.2.1/frontends/php /usr/local/httpd/htdocs/zabbix
</code></pre></div></div>
<p>然后启动服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apachectl start; # 启动Apache HTTP Server服务
sudo zabbix_server; # 启动zabbix server服务
sudo zabbix_agentd; # 启动zabbix agentd服务
</code></pre></div></div>
<p>此时,可在浏览器访问<code class="language-plaintext highlighter-rouge">localhost/zabbix</code>进行Web配置。</p>
<p><img src="/images/wanghui/zabbix/img1.png" alt="img1" /></p>
<p>访问可看到上面的欢迎页面,点击下一步。</p>
<p><img src="/images/wanghui/zabbix/img2.png" alt="img2" /></p>
<p>从欢迎页面来的上面的页面,此页面展示zabbix检测必要配置的结果,主要检测PHP对于zabbix的各项支持,当所有必要项都检测通过(即最后字段显示OK),方可进行下一步,否则需要重新配置。</p>
<p><img src="/images/wanghui/zabbix/img3.png" alt="img3" /></p>
<p>配置检查通过点击下一步来到第三个页面,此处主要是配置数据库连接,填写上mysql数据库的信息后点击下一步。</p>
<p><img src="/images/wanghui/zabbix/img4.png" alt="img4" /></p>
<p>此处主要填写server的IP、端口和名称。</p>
<p><img src="/images/wanghui/zabbix/img5.png" alt="img5" /></p>
<p>来到最后一个配置页面,这里是zabbix自动生成的php页面配置文件,下载此文件放置在zabbix页面目录下的conf目录下。</p>
<p><img src="/images/wanghui/zabbix/img6.png" alt="img6" /></p>
<p>点击finish后,跳转至登录页面,默认账号/密码是<code class="language-plaintext highlighter-rouge">admin/zabbix</code>,至此,zabbix安装完毕。</p>
<p><a id="part4"></a></p>
<h2 id="4-问题汇总">4. 问题汇总</h2>
<p>在这里列举了几个安装过程中遇到的问题和解决办法,以便以后查阅。</p>
<h3 id="41-mysql-lib-缺失">4.1 mysql lib 缺失</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error while loading shared libraries: libmysql.so.20: cannot open shared object file: No such file or directory
</code></pre></div></div>
<p>在mac os中,mysql的安装目录中的lib目录下有<code class="language-plaintext highlighter-rouge">libmysqlclient.20.dylib</code>,可将此文件复制到<code class="language-plaintext highlighter-rouge">/usr/sbin</code>中,创建软链接解决。</p>
<h3 id="42-libjpeg相关问题">4.2 libjpeg相关问题</h3>
<h4 id="421-configure时报错">4.2.1 configure时报错</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-bash: ./configure: /bin/sh^M:
</code></pre></div></div>
<p>此问题是系统编码不同而引起的,libjpeg貌似是在windows系统下开发的,configure文件的系统编码是dos,此时在mac下执行需要改为unix。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi configure # vi 编辑configure文件
:set ff=unix # 修改系统编码
:wq # 保存退出
</code></pre></div></div>
<h4 id="422-目录不存在">4.2.2 目录不存在</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-c -m 644 ./cjpeg.1 /usr/local/man/man1/cjpeg.1 /usr/bin/install: cannot create regular file `/usr/local/man/man1/cjpeg.1': No such file or directory
</code></pre></div></div>
<p>此问题是安装目录未创建导致,与大多数库不同,libjpeg不会自动创建安装目录,需要自己手动创建。</p>
<h4 id="423-libtool库缺失">4.2.3 libtool库缺失</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./libtool --mode=compile mipsel-linux-gcc -O2 -I. -c ./jcapimin.c \ make: ./libtool: Command not found \make: *** [jcapimin.lo] Error 127
</code></pre></div></div>
<p>缺少<code class="language-plaintext highlighter-rouge">libtool</code>库,需要自行安装。</p>
<p>安装<code class="language-plaintext highlighter-rouge">libtool</code>后,需要复制<code class="language-plaintext highlighter-rouge">libtool</code>的配置文件覆盖到<code class="language-plaintext highlighter-rouge">libjpeg</code>目录下的,即:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp /usr/local/share/libtool/config.sub /usr/local/src/jpeg-6b/
cp /usr/local/share/libtool/config.guess /usr/local/src/jpeg-6b/
</code></pre></div></div>
<p>之后才可安装<code class="language-plaintext highlighter-rouge">libjpeg</code>。</p>
<h3 id="43-动态安装php扩展">4.3 动态安装php扩展</h3>
<p>有时安装了php后,发现忘记安装某项扩展,此时可使用<code class="language-plaintext highlighter-rouge">phpize</code>命令进行动态安装,避免重新整体安装php。</p>
<p>在php的源码包的<code class="language-plaintext highlighter-rouge">ext</code>目录下存放着大部分扩展的源文件,本机是在<code class="language-plaintext highlighter-rouge">/usr/local/src/php-5.6.28/ext</code>目录中,例如此时要动态安装<code class="language-plaintext highlighter-rouge">gd</code>库。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /usr/local/src/php-5.6.28/ext/gd; # 进入gd扩展源码目录
/usr/local/php/bin/phpize; # 使用phpize命令建立外挂模块
# 配置
./configure \
--with-php-config=/usr/local/php/bin/php-config \ # 指定php-config目录
--with-jpeg-dir=/usr/local/jpeg/ \ # 添加jpeg支持
--with-freetype-dir=/usr/local/freetype/ \ # 添加freetype支持
--with-png-dir=/usr/local/png/; # 添加png支持
make && make install
</code></pre></div></div>
<p>安装完毕后要在<code class="language-plaintext highlighter-rouge">php.ini</code>配置文件中加入扩展模块的信息:<code class="language-plaintext highlighter-rouge">extension = “memcache.so”</code></p>
Google地图入门
2016-12-16T00:00:00+00:00
http://www.blogways.net/blog/2016/12/16/Google-Map-introduction
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">Google地图简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">创建一个简单的 Google 地图</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link3">总结与心得</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<h2 id="一google地图简介">一.Google地图简介</h2>
<p>GoogleMapsAPI除了帮助开发者将地图嵌入到Web应用中之外,还允许开发者利用JavaScript脚本进行应用开发拓展,给地图添加标注和路径及其他图层覆盖物,或者响应用户的点击动作,并显示包含内容信息在内的气泡提示窗口。</p>
<p>通过GoogleMaps为开发者提供的地图API,可以开发出各种各样有趣的地图Mash-up应用,还可以将不同地图图层加载到应用中,如卫星影像、根据海拔高度绘制的高山和植被地形图、街道视图等,从而帮助开发者打造个性化的地图应用站点。</p>
<p>Google 地图 API 是一种通过 JavaScript 将 Google 地图嵌入到您的网页的 API。该 API 提供了大量实用工具用以处理地图,并通过各种服务向地图添加内容,从而使您能够在您的网站上创建功能全面的地图应用程序。</p>
<p>地图 API 是一项免费的服务,任何非盈利性网站均可使用。</p>
<p><a id="link2"></a></p>
<h2 id="二创建一个简单的-google-地图">二.创建一个简单的 Google 地图</h2>
<p>我们通过引入插件的方式创建Google地图</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html>
<head>
<script src="http://maps.googleapis.com/maps/api/js?key=AIzaSyDY0kkJiTPVd2U7aTOAwhc9ySH6oHxOIYM&sensor=false">
</script>
<script>
function initialize()
{
var mapProp = {
center:new google.maps.LatLng(51.508742,-0.120850),
zoom:5,
mapTypeId:google.maps.MapTypeId.ROADMAP
};
var map=new google.maps.Map(document.getElementById("googleMap")
,mapProp);
}
google.maps.event.addDomListener(window, 'load', initialize);
</script>
</head>
<body>
<div id="googleMap" style="width:500px;height:380px;"></div>
</body>
</html>
</code></pre></div></div>
<h3 id="1应用为什么要声明-html5">1.应用为什么要声明 HTML5?</h3>
<p><code class="language-plaintext highlighter-rouge"><!DOCTYPE html></code>大多数浏览器使用 “标准模式” 的 HTML5 文档渲染页面,这就意味着你的应用是兼容各大浏览器的。</p>
<p>另外,如果没有DOCTYPE标签,浏览器则使用混杂模式 (quirks mode)进行渲染页面内容。</p>
<p><strong>提示</strong>: 应该注意的是一些”混杂模式 “中的CSS并不能使用与标准模式中。在具体的应用中,所有基于百分比的大小都必须从父块元素继承 。如果在父模块中没有指定大小,默认值为 0 x 0 像素。如果你想使用百分比,可以在<style> 标签中声明,如下所示:</style></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><style type="text/css">
html {height:100%}
body {height:100%;margin:0;padding:0}
#googleMap {height:100%}
</style> 这个样式声明表明地图模块的(GoogleMap)应 HTML高度为100%。
</code></pre></div></div>
<h3 id="2添加-google-地图-api-key">2.添加 Google 地图 API Key</h3>
<p>在以下实例中第一个<script> 标签中必须包含 Google 地图 API:</script></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="http://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&sensor=TRUE_OR_FALSE">
</script>
</code></pre></div></div>
<p>将google生成的 API key 放置于 key 参数中(key=YOUR_API_KEY)。
<strong>sensor</strong>
这个参数是必须的,该参数用于指明应用程序是否使用一个传感器 (类似 GPS 导航) 来定位用户的位置。参数值可以设置为 true 或者 false。
<strong>HTTPS</strong>
如果你的应用是安全的HTTP(HTTPS:HTTP Secure)应用,你可以使用 HTTPS 来加载 Google 地图 API:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="https://maps.googleapis.com/maps/api/js?key=YOUR_API_KEY&sensor=TRUE_OR_FALSE">
</script>
</code></pre></div></div>
<h3 id="3异步加载">3.异步加载</h3>
<p>同样我们也可以在页面完全载入后再加载 Google 地图 API。
以下实例使用了 window.onload 来实现页面完全载入后加载 Google 地图 。 loadScript() 函数创建了加载 Google 地图 API <code class="language-plaintext highlighter-rouge"><script></code> 标签。此外在标签的末尾添加了 callback=initialize 参数, initialize()作为回调函数会在API完全载入后执行:</p>
<p><strong>实例</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function loadScript()
{
var script = document.createElement("script");
script.src = "http://maps.googleapis.com/maps/api/js? key=AIzaSyDY0kkJiTPVd2U7aTOAwhc9ySH6oHxOIYM&sensor=false&callback=initialize"; document.body.appendChild(script);
}
window.onload = loadScript;
</code></pre></div></div>
<h3 id="4定义地图属性">4.定义地图属性</h3>
<p>在初始化地图前,我们需要先创建一个 Map 属性对象来定义一些地图的属性:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var mapProp = {
center:new google.maps.LatLng(51.508742,-0.120850),
zoom:7,
mapTypeId: google.maps.MapTypeId.ROADMAP
};
</code></pre></div></div>
<p><strong>center(中心点)</strong>
中心属性指定了地图的中心,该中心通过坐标(纬度,经度)在地图上创建一个中心点。</p>
<p><strong>Zoom(缩放级数)</strong>
zoom 属性指定了地图的 缩放级数。zoom: 0
显示了整个地球地图的完全缩放。</p>
<p>MapTypeId(地图的初始类型)</p>
<p>mapTypeId 属性指定了地图的初始类型。</p>
<p>mapTypeId包括如下四种类型:</p>
<ul>
<li>
<p>google.maps.MapTypeId.HYBRID:显示卫星图像的主要街道透明层</p>
</li>
<li>
<p>google.maps.MapTypeId.ROADMAP:显示普通的街道地图</p>
</li>
<li>
<p>google.maps.MapTypeId.SATELLITE:显示卫星图像</p>
</li>
<li>
<p>google.maps.MapTypeId.TERRAIN:显示带有自然特征(如地形和植被)的地图</p>
</li>
</ul>
<p><strong>在哪里显示 Google 地图</strong></p>
<p>通常 Google 地图使用于<code class="language-plaintext highlighter-rouge"> <div></code> 元素中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div id="googleMap" style="width:500px;height:380px;">
</div> >注意: 地图将以div中设置的大小来显示地图的大小,所以我们可以在 `<div>`元素中设置地图的大小。
</code></pre></div></div>
<h3 id="5创建一个-map-对象">5.创建一个 Map 对象</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map=new google.maps.Map(document.getElementById("googleMap")
,mapProp); 以上代码使用参数(mapProp)在<div> 元素 (id为googleMap) 创建了一个新的地图。 >提示:如果想在页面中创建多个地图,你只需要添加新的地图对象即可。
</code></pre></div></div>
<p>以下实例定义了四个地图实例 (四个地图使用了不同的地图类型):
实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var map = new google.maps.Map(document.getElementById("googleMap"),mapProp);
var map2 = new google.maps.Map(document.getElementById("googleMap2"),mapProp2);
var map3 = new google.maps.Map(document.getElementById("googleMap3"),mapProp3);
var map4 = new google.maps.Map(document.getElementById("googleMap4"),mapProp4);
</code></pre></div></div>
<h3 id="6加载地图">6.加载地图</h3>
<p>窗口载入后通过执行 initialize() 函数来初始化 Map 对象,这样可以确保在页面完全载入后再加载 Google 地图:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>google.maps.event.addDomListener(window, 'load', initialize);
</code></pre></div></div>
<p><a id="link3"></a></p>
<h3 id="三总结与心得">三.总结与心得</h3>
<p>最近在做凤来的临柜时长,涉及了山西地图等问题,用的是echart地图来画,偶然看到了Google地图API插件,做出来的效果不错,如果有兴趣大家可以深入的了解一下。</p>
新设计趋势观察
2016-12-14T00:00:00+00:00
http://www.blogways.net/blog/2016/12/14/Design-Trends
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#intro">概述</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#names">卡片式设计</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#replace1">微交互将会大量主导产品设计细节</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#replace2">颜色,字体和质感将成为设计师追捧利器</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#replace3">The internet of everything 人的距离将会无限被拉近</a></td>
</tr>
</tbody>
</table>
<h2 id="概述--">概述 <a id="intro"></a></h2>
<p>用户体验已经迎来了它的“黄金时代”。在未来,当对微小细节的关注渗入用户体验设计的每一寸土壤;当对像素级完美的追求成为设计的基本要求;当将简单便捷的设计理念融入用户的日常生活,那么我们就能够帮助用户体验全新的生活。</p>
<h2 id="一卡片式设计-">一、卡片式设计 <a id="names"></a></h2>
<p>卡片式设计在所有的媒介和设备上,已经成为设计常用的一种方式,卡片设计的应用不仅仅只是一种趋势。有数据显示2014年,移动端使用率远远高于桌面端,网页设计都能很好的适应小屏幕。这个结论就是:简单的界面风格,比如:扁平化设计,极简设计,尤其是卡片设计比往年都要流行。</p>
<p><img src="/images/chenwt/Design-Trends/1.jpg" alt="" /></p>
<p><strong>1. UI卡片在移动端和响应式设计中的应用</strong></p>
<p>卡片为响应式框架提供了出色的兼容性,有人把它称为“网页的未来”。这种设计能够在移动端随意很好的转换。</p>
<p><img src="/images/chenwt/Design-Trends/2.jpg" alt="" /></p>
<p>首先,它能够自己扩展或者收缩去适应任何屏幕的尺寸。设计师可以很灵活的控制卡片的比例,还可以设计固定的宽度和一致的间距来适应可变动的高度,几组卡片能够更好的适应彼此。下面的移动端截图:</p>
<p><img src="/images/chenwt/Design-Trends/3.jpg" alt="" /></p>
<p>他们两个的文本信息、图像和颜色的风格都是一致的。卡片设计让用户在不同的设备上有一致性的体验。</p>
<p><strong>2. 卡片设计的未来</strong></p>
<p>卡片设计模式不断适应新的挑战,而响应式和移动端也许会受最大的影响。这种设计流行是未来发展趋势。</p>
<p><img src="/images/chenwt/Design-Trends/4.jpg" alt="" /></p>
<p>2.1 <strong>技术</strong></p>
<p>卡片不可能永远是静态的,随着网络性能的提高,也能够支持更丰富的多媒体内容。可能会看到更多细节的元素,比如自动更新内容但是不会影响整个用户体验。视频能更换图片也许会变得很流行,更具有创意,一些GIF动画功能让整个页面体验非常愉悦。</p>
<p><img src="/images/chenwt/Design-Trends/5.jpg" alt="" /></p>
<p><img src="/images/chenwt/Design-Trends/6.jpg" alt="" /></p>
<p>2.2 <strong>尺寸</strong></p>
<p>图片并列的布局趋势,也让卡片更好的使用大尺寸屏幕。大卡片上能布局更多的细节和复杂的字体,它从视觉上让用户感觉很舒服。大小卡片交替的时候,大卡片在视觉上更让人感觉舒服。</p>
<p><img src="/images/chenwt/Design-Trends/7.jpg" alt="" /></p>
<p>有些卡片有链接,有些卡片是静态的信息。大小不同的卡片呈现方式,让整个页面更有呼吸感。</p>
<p>2.3 <strong>“年龄响应式”设计</strong></p>
<p>根据不同消费者群体的兴趣差异来“定制”内容。网页广告已经在这方面试水了一段时间,是时候轮到网站内容本身了:一个8岁的孩子和一位80岁的老人显然不会对同一本书、同一块表感兴趣,也不太会看同一栏电视节目,那么为什么要让他们拥有完全一致的上网体验呢?网站应该告别一成不变的“成衣”,走向“私人订制”。</p>
<p>到2017年,大量的元数据将成为“年龄响应式网站”的基本特征:</p>
<ol>
<li>
<p>导航目录的长短可以根据用户的理解能力进行伸缩;那些接收大量信息相对困难的人将会看到简约的交互界面,从而更方便地从有限但更为熟悉的信息入手。</p>
</li>
<li>
<p>网站字体、字号与间距能够为了照顾老年人的视力而自然变大。</p>
</li>
<li>
<p>配色方案也会调整:年轻人会体验到饱和度更高的色彩;而老年人则会看到相对柔和的颜色。</p>
</li>
</ol>
<h2 id="二微交互将会大量主导产品设计细节-">二、微交互将会大量主导产品设计细节 <a id="replace1"></a></h2>
<p>微交互这个概念其实早在2013年底就被提出了。 Designing for details-微交互,不是只在小范围收到空间时间概念限制的交互,而是细微的,细节的,有人性化的交互,通过细节对于用户进行引导。</p>
<p><img src="/images/chenwt/Design-Trends/1.gif" alt="" /></p>
<p><img src="/images/chenwt/Design-Trends/2.gif" alt="" /></p>
<h2 id="三颜色字体和质感将成为设计师追捧的视觉利器-">三、颜色、字体和质感将成为设计师追捧的视觉利器 <a id="replace2"></a></h2>
<p>Gradients(渐变色),火了去年整整一年了。但是仅仅是渐变,不能满足设计的要求。这一年里,层出不穷更是各样的文章表示现在是终结纯扁平化的时候了。</p>
<p><img src="/images/chenwt/Design-Trends/8.png" alt="" /></p>
<p>现下很多视觉方案,已经在努力突破扁平的桎梏,向着更有意思的趋势发展起来了。比如大字体,强对比,新的iOS 10阴影等等。毕竟,审美会疲劳,而用户面对如此大的信息量,这是对于视觉设计,排版,字体研究等的真正考验。</p>
<p><img src="/images/chenwt/Design-Trends/3.gif" alt="" /></p>
<h2 id="四the-internet-of-everything-人的距离将会无限被拉近-">四、The internet of everything 人的距离将会无限被拉近 <a id="replace3"></a></h2>
<p>物联网,智能家居,人工智能,O2O都不是新词汇了,但现实中我们还不会在短时间之内真正做到普及,但是在这个概念先行设计紧随其后的时代,应该快速的适应现在的技术并延伸到生活的方方面面,我们今后的设计不会再简单的是个登录页面,而是你连接实体和虚无的高复杂度抽象交互。</p>
<p><img src="/images/chenwt/Design-Trends/9.png" alt="" /></p>
<p>作为设计师的我们,应该感谢无数工程师的努力,技术的革新,使得我们的很多设计理想都成为了现实,那么下一个即将实现的梦想,就是人和人之间的距离。靠更稳定的沟通桥梁技术,和我们在设计中引入情感化设计元素并加以思考,将人与人之间的距离缩短。</p>
<p><img src="/images/chenwt/Design-Trends/10.jpg" alt="" /></p>
移动APP导航设计的选择及利弊对比
2016-12-13T00:00:00+00:00
http://www.blogways.net/blog/2016/12/13/app-navigation
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#chapter">概述</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#chapter1">标签式导航(tab式导航)</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#chapter2">抽屉式导航</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#chapter3">列表式导航</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#chapter4">平铺式导航</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#chapter5">宫格式导航</a></td>
</tr>
<tr>
<td>7</td>
<td><a href="#chapter6">悬浮icon导航</a></td>
</tr>
<tr>
<td>8</td>
<td><a href="#chapter7">更多自由导航</a></td>
</tr>
<tr>
<td>9</td>
<td><a href="#chapter8">最后总结</a></td>
</tr>
</tbody>
</table>
<h2 id="概述">概述<a id="chapter"></a></h2>
<p>不同的产品需求和商业目标决定了不同的导航框架的设计模式。当确定了移动APP的设计需求和APP产品设计流程之后,就开始着手设计APP界面UI或是APP原型图。这个时候要面临的第一个问题就是如何将信息以最优的方式组合起来,使之逐渐丰富血肉。从我们一些常用的产品去分析和了解APP导航设计是非常必要的。适合的导航模式,是产品设计中的关键一环,对整个app的核心体验起到关键作用。</p>
<p>首先,要给组织信息分层,要做好信息层级的扁平化,不能把所有的组织信息都铺出来,这样做只会让用户心烦意乱找不到想要的重要操作,也不能把层级做的很深,用户没有那么多耐心跟你玩躲猫猫。要将做核心、最稳固、最根本的功能要素放在第一层页面,其他得内容收在第二层、第三层、甚至更深。</p>
<p>移动端的导航模式一般分为以下几种(很多优秀的app都是基于这些模式做了一些创新的优化方案),以下总结了目前通用且流行的模式,及模式适用的场景,以方便设计师更快的作出较合理的信息组织决策。</p>
<h2 id="一标签式导航tab式导航-">一、标签式导航(tab式导航) <a id="chapter1"></a></h2>
<p>tab式导航分为底部tab式导航和顶部tab式导航,是移动应用中最普遍、最常用的导航模式,观察一下自己手机中常用的APP你就会发现QQ、微信、淘宝、微博、美团、京东等全部都是tab式导航。</p>
<p><img src="/images/chenwt/app-navigation/1.jpg" alt="" /></p>
<p>tab式导航设计之所以这么设计是由我们操作手机的习惯决定的,我们单手拿手机的时候,拇指的可控范围有限,缺乏灵活度。尤其是在如今的大屏手机上,拇指的可控范围还不到整个屏幕的三分之一——主要集中在屏幕底部、与拇指相对的另外一边。如拇指的热区如上图图所示:</p>
<p><img src="/images/chenwt/app-navigation/2.png" alt="" /></p>
<p><img src="/images/chenwt/app-navigation/3.png" alt="" /></p>
<p><img src="/images/chenwt/app-navigation/4.png" alt="" /></p>
<p><img src="/images/chenwt/app-navigation/5.png" alt="" /></p>
<p><strong>总结:</strong></p>
<ol>
<li>
<p>tab模式导航在iOS和Android上一直是最安全的一种导航模式,它不怎么出彩,但是绝对不会犯错。在大屏幕时代,底部Tab模式的导航更能适应,也更好设计。</p>
</li>
<li>
<p>适用于:入口分类数目不多,可以控制在5个以内,且用户需要在入口间频繁切换来执行多个任务</p>
</li>
<li>
<p>需要注意:结构太过复杂而且不稳定的应用不适合标签式导航。</p>
</li>
</ol>
<h2 id="二抽屉式导航-">二、抽屉式导航 <a id="chapter2"></a></h2>
<p>经常和底部tab式导航结合使用的抽屉式导航,称之为优雅的隐喻。抽屉式导航将部分信息内容进行隐藏,突出了应用的核心功能。</p>
<p><img src="/images/chenwt/app-navigation/6.png" alt="" /></p>
<p>但是,抽屉栏式导航有两大缺陷:</p>
<ol>
<li>在大屏幕手机上,单手持握时处于操作盲区,难以点击;</li>
<li>抽屉式导航可能会降低你产品一半的用户参与度;
到底什么时候适合用侧导航呢?</li>
</ol>
<p><strong>总结:</strong></p>
<ol>
<li>
<p>如果应用主要的功能和内容都在一个页面里面。只是一些用户设置这类低频操作内容需要显示在其他页面里。为了让主页面看上去干净美观,可以把这些辅助功能放在抽屉栏里。</p>
</li>
<li>
<p>如果应用有不同的视图,且他们是平级的,需要用户同等地对待,抽屉栏将会浪费掉大多数的用户对于侧边栏中入口的潜在参与度和交互程度。</p>
</li>
<li>
<p>在大屏时代使用抽屉栏,手势操作显得尤为重要,从屏幕边缘唤出抽屉栏是个不错的选择。</p>
</li>
</ol>
<h2 id="三列表式导航-">三、列表式导航 <a id="chapter3"></a></h2>
<p>列表式导航结构简单清晰、易于理解、冷静高效,能够帮助用户快速定位到对应内容。</p>
<p>列表式导航在APP中的应用也分为两种:</p>
<h3 id="1-作为主导航使用">1. 作为主导航使用</h3>
<p>如果这款APP主要表达的信息层级较为单一,且并不会在入口间频繁且反复跳转,那么将列表式导航作为主导航是一种不错的选择。例如杂志Elle,作为一个杂志APP,他以文字、图片表达为主,层级浅且内容评级,用列表式导航作为主导航来构架内容,简单而直接。</p>
<p><img src="/images/chenwt/app-navigation/7.png" alt="" /></p>
<h3 id="2-作为辅助导航来展示二级甚至更深层级的内容">2. 作为辅助导航来展示二级甚至更深层级的内容</h3>
<p>几乎在所以APP中都能看到它的应用,作为信息梳理条目,列表数量尽量保持在一屏以内,超过一屏最好再分一级,而且按照人一次只能记住4项事物的心理法则最重要的内容归纳在前4个列表更容易被人们记住。如果不同种类的内容放在同一页面,那么要注意为这些内容分类,比如微信的设置页面,用留白的方式来区分内容的不同。</p>
<p><img src="/images/chenwt/app-navigation/8.png" alt="" /></p>
<p><strong>总结:</strong></p>
<p>列表式导航大多作为辅助导航来展示二级甚至更深层次的内容,若要作为主导航,必须满足层级浅且内容平级的条件</p>
<p>需要注意的是:</p>
<ol>
<li>
<p>列表式导航的数量保持在一屏以内,超过一屏最好再分一级,</p>
</li>
<li>
<p>将最终要的内容归纳在前4个列表更容易被人们记住</p>
</li>
<li>
<p>要注意为列表内容分类。</p>
</li>
</ol>
<h2 id="四平铺式导航-">四、平铺式导航 <a id="chapter4"></a></h2>
<p>当信息足够扁平,可以尝试平铺式导航。这种导航方式很容易带来高大上的视觉体验,最大程度的保证了页面的简洁性和内容的完整性,且一般都会结合滑动切换的手势,操作起来也非常方便。PChouse是一个家居杂志的APP,杂志休闲随意的特质,非常适合平铺式导航,最大限度的保持了图片的完整性。</p>
<p><img src="/images/chenwt/app-navigation/9.png" alt="" /></p>
<p>但缺点是用户只能切换的相邻页面,很难跳转到非相邻的页面,很容易迷失位置,因此平铺式导航都会添加几个小点来指示当前位置。如墨迹天气中切换城市的操作就运用了平铺式导航,优点是可以在一个页面完整展示当前城市的情况,快速切换到其他城市。但如果你设置的城市比较多,就很难快速定位到某个城市,但所幸手势操作切换方便,且正常情况下,用户最多只会设置2-3个页面,不会造成太大的负担。</p>
<p>淘宝中的店铺推荐也使用了平铺式导航,推荐店铺虽然有40个之多,但用数字表达出了明确位置的同时,也加入了手势切换,增加了易用性和趣味性。</p>
<p><img src="/images/chenwt/app-navigation/10.png" alt="" /></p>
<p><strong>总结:</strong></p>
<ol>
<li>
<p>轮播式导航比较适用于足够扁平化的内容和随意浏览的阅读模式</p>
</li>
<li>
<p>需要注意的是:无法跳转至费相邻页面,如果入口间需要反复跳转,则不适合这种模式</p>
</li>
</ol>
<h2 id="五宫格式导航-">五、宫格式导航 <a id="chapter5"></a></h2>
<p>这种导航模式非常常见,但是却不常用。
常见是因为,无论你用的是Android还是iOS,每天一打开手机就是宫格式导航。</p>
<p><img src="/images/chenwt/app-navigation/11.png" alt="" /></p>
<p>每一个APP都是一个宫格,这些宫格聚集在中心页面,用户只能在中心页面进入其中一个宫格,如果想要进入另一个宫格,必须要先回到中心页面,再进入另一个宫格。每个宫格相互独立,它们的信息间也没有任何交集,无法跳转互通。</p>
<p><img src="/images/chenwt/app-navigation/12.png" alt="" /></p>
<p>APP的主导航用了宫格式导航只能找到一个最常用的-美图秀秀。美图秀秀给我的体验是这样的:进入美图秀秀-打开一张图片-进入人像美容-祛斑祛痘、放大眼睛、瘦个脸-保存图片-再进入美化图片-加个特效-再次保存。这样做的结果是流程复杂,而且需要多存了一张没用的照片,还要到照片里进行删除。</p>
<p>这就是宫格式导航的缺陷,信息互斥,无法相互通达,只能给用户带来了更多的操作步骤。</p>
<p><strong>总结:</strong></p>
<ol>
<li>
<p>宫格式导航适合入口相互独立互斥,且不需要交叉使用的信息归类</p>
</li>
<li>
<p>一旦入口需要有所交集,必然导致更多的操作负累,这个时候只能根据产品特性做出权衡,如果不适合,建议果断拒绝这种方式。</p>
</li>
</ol>
<h2 id="六悬浮icon导航-">六、悬浮icon导航 <a id="chapter6"></a></h2>
<p>悬浮icon导航,是将导航页面分层,无论你到达APP的哪个页面,悬浮icon永远悬浮在上面,你依靠悬浮层随时可以去想要去的地方,同时,为了让悬浮icon不遮挡某个页面的操作,通常悬浮的icon都可以在屏幕边缘自由移动。</p>
<p><img src="/images/chenwt/app-navigation/13.png" alt="" /></p>
<p>iOS系统就运用了这种导航模式-Assistive Touch,悬浮icon在大屏幕时代发挥的作用,当你单手持握手机,拇指在手机中部浏览,你想回到主屏幕,但是手指却难以到达屏幕底部,悬浮方块在这个时候就能为你提供快捷操作。</p>
<p>在Android的Material Design中,同样提出了悬浮icon的设计概念。</p>
<p><img src="/images/chenwt/app-navigation/14.png" alt="" /></p>
<p><strong>总结:</strong></p>
<ol>
<li>
<p>悬浮式icon是一个非常便捷的操作入口,也适应大屏幕时代。</p>
</li>
<li>
<p>需要注意的是:悬浮式icon会遮挡某些页面的操作,在设计的时候应该考虑进去,比如无论在那个页面永远为悬浮icon留有位置。</p>
</li>
<li>
<p>在某些信息层级繁多且复杂的APP,让用户自主决定是否需要调出悬浮式icon,或者让用户自定义菜单会更加符合用户的心理预期。</p>
</li>
</ol>
<h2 id="七更多自由-">七、更多自由 <a id="chapter7"></a></h2>
<p>总结的导航其实只有6种,但大家一定不能被现有的导航模式所束缚,未来会有新的导航模式,新的操作手势…设计的心应该是自由的,可突破规则,甚至建立自己的规则。</p>
<h2 id="最后总结-">最后总结 <a id="chapter8"></a></h2>
<p>总结的导航其实只有6种,但大家一定不能被现有的导航模式所束缚,未来会有新的导航模式,新的操作手势…设计的心应该是自由的,可突破规则,甚至建立自己的规则。</p>
<p><strong>标签式导航</strong>:最常用、最不易出错,请第一时间考虑它;</p>
<p><strong>抽屉式导航</strong>:如果你的信息层级繁多,可以考虑将辅助类内容放在抽屉中;</p>
<p><strong>列表式导航</strong>:作为辅助导航来展示二级甚至更深层级的内容,每个APP必不可少,但请注意数量与分类;</p>
<p><strong>平铺式导航</strong>:如果你的内容是随意浏览,无需来回跳转的,可以考虑它;</p>
<p><strong>宫格式导航</strong>:不建议在APP中作为主导航使用,如果非使用不可,请增加跳转的关联性;</p>
<p><strong>悬浮式导航</strong>:更适应大屏的导航模式,不妨试一试,但注意不要让它遮挡住某些页面的操作;</p>
Vert.x-web
2016-12-12T00:00:00+00:00
http://www.blogways.net/blog/2016/12/12/vert.x-web
<h2 id="一vertx-web介绍">一、Vert.x-web介绍</h2>
<p>1、Vert.x-web基于Vert.x core,可以非常简单的构建web应用程序的功能集;</p>
<p>2、Yoke从Node.js中的Express框架和Ruby世界中的Sinatra获得灵感出来的一个基于Vert.x 2.x框架;</p>
<p>3、Vertx-web并不是容器,Vert.x-Web强在设计,内部功能并不是完全嵌入,你只需构建应用时只需使用你所需要的;</p>
<p>4、Vert.x-web可以创建经典的服务器侧Web应用程序、RESTfulweb应用程序、实时(服务器推送)Web应用,或者其他你要实现的某种web应用,应用的类型完全由你决定;</p>
<p>5、Vert.x-Web非常适合编写RESTful HTTP微服务,但是Vert.x不强制你这样编写应用程序</p>
<h2 id="二vertx-web包含的功能">二、Vert.x-web包含的功能</h2>
<ul>
<li>路由(基于方法【get,post】,路径,等)</li>
<li>路径的正则表达式匹配</li>
<li>从路径中抽取参数</li>
<li>内容协商</li>
<li>请求体处理</li>
<li>请求和响应体大小限制</li>
<li>Cookie解析与处理</li>
<li>多部分表单(指有表单域和上传的表单)</li>
<li>多部分文件上传</li>
<li>子路由器</li>
<li>会话支持——包括本地(粘滞会话)和集群(非粘滞)</li>
<li>CORS(跨区域资源共享)支持</li>
<li>错误页处理</li>
<li>Basic认证</li>
<li>基于认证的重定向</li>
<li>JWt (Java Web Toolkit) 基本认证</li>
<li>用户/角色/权限认证</li>
<li>favicon(指浏览器页面上的图标)处理</li>
<li>
<p>模板支持服务端渲染,支持下列模板引擎</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Handlebars
Jade
MVEL
Thymelea
Apache FreeMarker
</code></pre></div> </div>
</li>
<li>响应时间处理器</li>
<li>静态文件提供,包括缓存逻辑和目录列表</li>
<li>请求超时处理</li>
<li>SockJS支持</li>
<li>事件总线桥</li>
<li>CSRF虚拟跨站请求</li>
<li>虚拟主机</li>
</ul>
<p>Vert.x-web的大多数特性被实现成处理器,所以要以自己编写。随着时间推移,会在vert.x-web中添加更多内容。我们将在此手册中讨认所有这些特性</p>
<h2 id="三vertx-web使用情况调查">三、Vert.x-web使用情况调查</h2>
<p>下图为来自网络调查所得web框架排行榜:</p>
<p><img src="/images/tangsz/vert-web.png" alt="vert-web" /></p>
<p>可以看出Vert.x是仅次于spring MVC排名在第二位的框架,可见Vert.x的活跃程度已经很高了。</p>
<h2 id="四vertx-web简单例子">四、Vert.x-web简单例子</h2>
<p>Vert.x-web本身包含功能很多,这里的例子只是通过Vert.x-web 基于java8(还有ruby、groovy、JavaScript等)实现简单的功能,详细例子可见:https://github.com/vert-x3/vertx-examples/tree/master/web-examples</p>
<h3 id="简单hello-word">简单Hello Word</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class Server extends AbstractVerticle {
public static void main(String[] args) {
Runner.runExample(Server.class);
}
@Override
public void start() throws Exception {
Router router = Router.router(vertx);
router.route().handler(routingContext -> {
routingContext.response().putHeader("content-type", "text/html").end("Hello World!");
});
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}
}
</code></pre></div></div>
<p>页面展现:</p>
<p><img src="/images/tangsz/vert-http.png" alt="vert-http" /></p>
<h3 id="简单rest微服务">简单rest微服务</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class SimpleREST extends AbstractVerticle {
public static void main(String[] args) {
Runner.runExample(SimpleREST.class);
}
private Map<String, JsonObject> products = new HashMap<>();
@Override
public void start() {
setUpInitialData();
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.get("/products/:productID").handler(this::handleGetProduct);
router.put("/products/:productID").handler(this::handleAddProduct);
router.get("/products").handler(this::handleListProducts);
vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}
private void handleGetProduct(RoutingContext routingContext) {
String productID = routingContext.request().getParam("productID");
HttpServerResponse response = routingContext.response();
if (productID == null) {
sendError(400, response);
} else {
JsonObject product = products.get(productID);
if (product == null) {
sendError(404, response);
} else {
response.putHeader("content-type", "application/json").end(product.encodePrettily());
}
}
}
private void handleAddProduct(RoutingContext routingContext) {
String productID = routingContext.request().getParam("productID");
HttpServerResponse response = routingContext.response();
if (productID == null) {
sendError(400, response);
} else {
JsonObject product = routingContext.getBodyAsJson();
if (product == null) {
sendError(400, response);
} else {
products.put(productID, product);
response.end();
}
}
}
private void handleListProducts(RoutingContext routingContext) {
JsonArray arr = new JsonArray();
products.forEach((k, v) -> arr.add(v));
routingContext.response().putHeader("content-type", "application/json").end(arr.encodePrettily());
}
private void sendError(int statusCode, HttpServerResponse response) {
response.setStatusCode(statusCode).end();
}
private void setUpInitialData() {
addProduct(new JsonObject().put("id", "prod3568").put("name", "Egg Whisk").put("price", 3.99).put("weight", 150));
addProduct(new JsonObject().put("id", "prod7340").put("name", "Tea Cosy").put("price", 5.99).put("weight", 100));
addProduct(new JsonObject().put("id", "prod8643").put("name", "Spatula").put("price", 1.00).put("weight", 80));
}
private void addProduct(JsonObject product) {
products.put(product.getString("id"), product);
}
}
</code></pre></div></div>
<p>页面展现:</p>
<p><img src="/images/tangsz/vert-rest.png" alt="vert-rest" /></p>
微信小程序
2016-12-11T00:00:00+00:00
http://www.blogways.net/blog/2016/12/11/wchart-introduction
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">微信小程序简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">工具安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link3">基本介绍</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#link4">代码编写</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#link5">demo</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<h2 id="一微信小程序介绍">一.微信小程序介绍</h2>
<p>微信应用号是一个APP应用推广平台,微信应用号目前暂定名为“小程序”,使用微信应用号平台,用户关注一个应用号就如同安装一个App一样,而微信应用号就相当于另一个App Store,主要功能就是应用推广。用户关注一个应用号就如同安装一个App一样,而微信应用号就相当于另一个App Store,主要功能就是应用推广。微信的目的似乎很简单,就是希望把用户使用App的动作都集中在微信上。应用号有两大特色:首先APP功能可以直接通过关注应用号来实现,所以用户就省去了安装下载卸载等等一系列动作,对那些使用频率不高的软件来说,你完全可以用“应用号”代替;另外,用户也免去了不定时下载软件更新包的困扰。</p>
<p>然而微信应用号要能获取足够多的用户,还得要开发者的支持。毫无疑问,开发者将是微信应用号的最大获益群体。对于开发者而言,应用号可以节省开发成本,并且可以提升研发效率,开发人员只需要研发出一款适用于浏览器应用的产品,就可满足不同操作系统的使用需求。另外,入驻应用号的APP营销推广工作也能取到事半功倍的效果。</p>
<p><a id="link2"></a></p>
<h2 id="二工具安装">二.工具安装</h2>
<p>点击https://mp.weixin.qq.com,点击微信小程序,点击工具下载,选择对应的版本,即可安装,这是官方提供编辑工具</p>
<p><a id="link3"></a></p>
<h2 id="三-基本介绍">三. 基本介绍</h2>
<p>开发者工具安装完成后,打开并使用微信扫码登录。选择创建“项目”,填入上文获取到的 AppID ,设置一个本地项目的名称(非小程序名称),比如“我的第一个项目”,并选择一个本地的文件夹作为代码存储的目录,点击“新建项目”就可以了。</p>
<p>为方便初学者了解微信小程序的基本代码结构,在创建过程中,如果选择的本地文件夹是个空文件夹,开发者工具会提示,是否需要创建一个 quick start 项目。选择“是”,开发者工具会帮助我们在开发目录里生成一个简单的 demo。</p>
<p><img src="/images/chenfan/wchart1.png" alt="" /></p>
<p>项目创建成功后,我们就可以点击该项目,进入并看到完整的开发者工具界面,点击左侧导航,在“编辑”里可以查看和编辑我们的代码,在“调试”里可以测试代码并模拟小程序在微信客户端效果,在“项目”里可以发送到手机里预览实际效果。</p>
<p><a id="link4"></a></p>
<h2 id="四代码编写">四.代码编写</h2>
<h4 id="创建小程序实例">创建小程序实例</h4>
<p>点击开发者工具左侧导航的“编辑”,我们可以看到这个项目,已经初始化并包含了一些简单的代码文件。最关键也是必不可少的,是 app.js、app.json、app.wxss 这三个。其中,.js后缀的是脚本文件,.json后缀的文件是配置文件,.wxss后缀的是样式表文件。微信小程序会读取这些文件,并生成小程序实例。</p>
<p>下面我们简单了解这三个文件的功能,方便修改以及从头开发自己的微信小程序。</p>
<ul>
<li>app.js</li>
</ul>
<p>app.js是小程序的脚本代码。我们可以在这个文件中监听并处理小程序的生命周期函数、声明全局变量。调用框架提供的丰富的API,如本例的同步存储及同步读取本地数据。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>App({
onLaunch: function () {
//调用API从本地缓存中获取数据
var logs = wx.getStorageSync('logs') || []
logs.unshift(Date.now())
wx.setStorageSync('logs', logs)
},
getUserInfo:function(cb){
var that = this;
if(this.globalData.userInfo){
typeof cb == "function" && cb(this.globalData.userInfo)
}else{
//调用登录接口
wx.login({
success: function () {
wx.getUserInfo({
success: function (res) {
that.globalData.userInfo = res.userInfo;
typeof cb == "function" && cb(that.globalData.userInfo)
}
})
}
});
}
},
globalData:{
userInfo:null
}
})
</code></pre></div></div>
<ul>
<li>app.json</li>
</ul>
<p>app.json 是对整个小程序的全局配置。我们可以在这个文件中配置小程序是由哪些页面组成,配置小程序的窗口背景色,配置导航条样式,配置默认标题。注意该文件不可添加任何注释。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"pages":[
"pages/index/index",
"pages/logs/logs"
],
"window":{
"backgroundTextStyle":"light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "WeChat",
"navigationBarTextStyle":"black"
}
}
</code></pre></div></div>
<ul>
<li>app.wxss</li>
</ul>
<p>app.wxss 是整个小程序的公共样式表。如同web编写的CSS一样,我们可以在页面组件的 class 属性上直接使用 app.wxss中声明的样式规则。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.container {
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
padding: 200rpx 0;
box-sizing: border-box;
}
</code></pre></div></div>
<blockquote>
<p>基本写作规则和css一样</p>
</blockquote>
<ul>
<li>创建页面</li>
</ul>
<p>在这个教程里,我们有两个页面,index 页面和 logs 页面,即欢迎页和小程序启动日志的展示页,他们都在 pages 目录下。微信小程序中的每一个页面的【路径+页面名】都需要写在 app.json 的 pages 中,且 pages 中的第一个页面是小程序的首页。</p>
<p>每一个小程序页面是由同路径下同名的四个不同后缀文件的组成,如:index.js、index.wxml、index.wxss、index.json。.js后缀的文件是脚本文件,.json后缀的文件是配置文件,.wxss后缀的是样式表文件,.wxml后缀的文件是页面结构文件。</p>
<p>index.wxml 是页面的结构文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!--index.wxml-->
<view class="container">
<view bindtap="bindViewTap" class="userinfo">
<image class="userinfo-avatar" src="" background-size="cover"></image>
<text class="userinfo-nickname"></text>
</view>
<view class="usermotto">
<text class="user-motto"></text>
</view>
</view> 本例中使用了 `<view/>`、`<image/>`、`<text/>`来搭建页面结构,绑定数据和交互处理函数。
</code></pre></div></div>
<ul>
<li>index.js</li>
</ul>
<p>index.js 是页面的脚本文件,在这个文件中我们可以监听并处理页面的生命周期函数、获取小程序实例,声明并处理数据,响应页面交互事件等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//index.js
//获取应用实例
var app = getApp()
Page({
data: {
motto: 'Hello World',
userInfo: {}
},
//事件处理函数
bindViewTap: function() {
wx.navigateTo({
url: '../logs/logs'
})
},
onLoad: function () {
console.log('onLoad')
var that = this
//调用应用实例的方法获取全局数据
app.getUserInfo(function(userInfo){
//更新数据
that.setData({
userInfo:userInfo
})
})
}
})
</code></pre></div></div>
<ul>
<li>index.wxss</li>
</ul>
<p>index.wxss 是页面的样式表:
<br />
/<strong>index.wxss</strong>/
.userinfo {
display: flex;
flex-direction: column;
align-items: center;
}</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.userinfo-avatar {
width: 128rpx;
height: 128rpx;
margin: 20rpx;
border-radius: 50%;
}
.userinfo-nickname {
color: #aaa;
}
.usermotto {
margin-top: 200px;
} 页面的样式表是非必要的。当有页面样式表时,页面的样式表中的样式规则会层叠覆盖 app.wxss 中的样式规则。如果不指定页面的样式表,也可以在页面的结构文件中直接使用 app.wxss 中指定的样式规则。
</code></pre></div></div>
<ul>
<li>index.json</li>
</ul>
<p>index.json 是页面的配置文件:</p>
<p>页面的配置文件是非必要的。当有页面的配置文件时,配置项在该页面会覆盖 app.json 的 window 中相同的配置项。如果没有指定的页面配置文件,则在该页面直接使用 app.json 中的默认配置。</p>
<p>logs 的页面结构</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!--logs.wxml-->
<view class="container log-list">
<block wx:for="" wx:for-item="log">
<text class="log-item">. </text>
</block>
</view> logs 页面使用 `<block/>` 控制标签来组织代码,在 `<block/>` 上使用 wx:for 绑定 logs 数据,并将 logs 数据循环展开节点
//logs.js
var util = require('../../utils/util.js')
Page({
data: {
logs: []
},
onLoad: function () {
this.setData({
logs: (wx.getStorageSync('logs') || []).map(function (log) {
return util.formatTime(new Date(log))
})
})
}
})
</code></pre></div></div>
<p><a id="link5"></a></p>
<h2 id="五demo编写">五.demo编写</h2>
<p>我在网上找了个比较简单上手的小例子,一个读书的demo</p>
<p>先上个效果图</p>
<p><img src="/images/chenfan/wchart2.png" alt="" /></p>
<ol>
<li>
<p>首先我们先要在最下面添加导航,即在app.json中添加tabBar的配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "tabBar": {
"color": "#dddddd",
"selectedColor": "#d92121",
"borderStyle": "white",
"backgroundColor": "#fff",
"list": [{
"pagePath": "pages/index",
"iconPath": "images/main.png",
"selectedIconPath": "images/main-s.png",
"text": "主页"
},{
"pagePath": "pages/layout/hot",
"iconPath": "images/hot.png",
"selectedIconPath": "images/hot-s.png",
"text": "最热"
},{
"pagePath": "pages/layout/new",
"iconPath": "images/new.png",
"selectedIconPath": "images/new-s.png",
"text": "最新"
}]
}
</code></pre></div> </div>
</li>
</ol>
<p>pagePath,就是此tabBar对应的页面链接</p>
<ol>
<li>然后开始我们的主要工作,即制作主页</li>
</ol>
<p>先看下目录结构,</p>
<p><img src="/images/chenfan/wchart3.png" alt="" /></p>
<p>即在pages下写我们的js,html和样式表</p>
<p>我们打开首页index页面</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = getApp();
Page({
data: {
indexList:app.getBoookList()
},
onLoad: function(options) {
// Do some initialize when page load.
},
onReady: function() {
// Do something when page ready.
},
onShow: function() {
// Do something when page show.
},
onHide: function() {
// Do something when page hide.
},
onUnload: function() {
// Do something when page close.
},
// Event handler.
viewTap: function() {
this.setData({
text: 'Set some data for updating view.'
})
}
})
</code></pre></div></div>
<p>可以看到上面的页面生命周期,我们可以在事件中写我们自己要处理的事件。</p>
<p>其中getApp();方法获取全局实例。</p>
<p>我们打开视图页面:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><view class="banner">
<image src="../images/banner.jpg" />
</view>
<view class="list clearfix">
<view class="list-item" wx:for="">
<navigator url="bookList/bookDetails?title=&id=" hover-class="navigator-hover"> <image src="" /></navigator>
<view class="book-name">
<navigator url="bookList/bookDetails?title=&id=" hover-class="navigator-hover"> </navigator>
</view>
</view>
<view class="menus">
<image src="../images/menu.png" class="menu-btn" />
<!--<view class="menu-list" id="menu-list">
<view>本机书架</view>
<view>wifi传书</view>
<view>云书架</view>
</view>-->
</view>
</view>
</code></pre></div></div>
<p>这里看到箭头指向的 wx:for=“”,这个是一个出来数组或列表对象的循环方法,而item是默认(又是默认)的单个列表元素。用不不想用item也可以起别名。</p>
<p>navigator就是导航标签了,这里,类似于html中的<a>标签,就不在说了。点击navigator的内容页面跳转对应页面,同样是用url传递数据。</a></p>
<p>打开index.wxss</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.list{
width: 100%;
background: #fff;
}
.list-item{
width: 33.333333%;
height: 370rpx;
text-align: center;
padding-top: 5px;
float: left;
}
.list-item image {
width: 80%;
height: 300rpx;
}
.book-name{
font-size: 28rpx;
color: #000;
}
.menus{
position: fixed;
bottom: 5px;
right: 5px;
border-radius:100%;
background-color: rgba(0, 0, 0, 0.5);
text-align: center;
}
.menus image{
width: 64rpx;
height: 64rpx;
vertical-align: middle
}
.banner image {
width:750rpx;
height: auto;
}
</code></pre></div></div>
<p>可以看下后台代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = getApp();
Page({
onLoad: function(options) {
this.setData({
title: options.title,
id:options.id,
bookDetails:app.getOneBook(options.id)
})
}
})
</code></pre></div></div>
<p>数据可以通过url传递,目标页面通过onLoad方法中的参数( 对象)获取。这里还可以看到书的详情是通过全局getApp获取全局实例,获取数据。这个数据就是在全局app.js里面。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>App( {
getBanner:function(){
var bannerUrl=["../images/banner.jpg"];
return bannerUrl;
},
getOneBook:function(id){
var abook;
var books = [
{ id:"1",
bookUrl:"../images/img1.jpg",
bookName:"西方哲学史",
bookInfor:"关于哲学"
}
];
for(i=0;i<books.length;i++){
if(books[i].id == id){
abook = books[i];
}
}
return abook;
},
getBoookList:function(){
var indexList = [
{ id:"1",
bookUrl:"../images/img1.jpg",
bookName:"西方哲学史",
bookInfor:"关于哲学"
}
];
return indexList;
}
})
</code></pre></div></div>
vert.x介绍
2016-12-11T00:00:00+00:00
http://www.blogways.net/blog/2016/12/11/vert.x-01
<h2 id="一背景">一、背景</h2>
<p>在传统J2EE开发中,用户对生产系统的部署都需依赖昂贵的服务器中间件(如WebLogic、WebSphere,当然也有免费的tomcat等),这些服务器上面部署了大型的程序包,它们运行缓慢,消耗大量的内存,运维人员也需要对服务器有足够了解。随着异步非阻塞、微服务等概念的出现,近年来也涌现了很多不依赖传统容器的服务框架。2009年,Node.js项目启动,它支持异步非阻塞的、基于事件驱动的I/O。如果服务器的线程使用得当,Node.js可以极大地提升响应速度,单个服务器的吞吐量可以媲美一个Java EE服务器集群。同时基于JAVA NIO、AIO(jdk1.7后)原理,也涌现出了一批如Netty、mina等优秀网络框架。其中vert.x、undertow等就是基于这些优秀框架(undertow基于XNIO)诞生的异步无阻塞的网络框架,</p>
<h2 id="二vertx-简介">二、vert.x 简介</h2>
<p>vert.x,是一个JVM下、轻量级、高性能的应用平台,非常适用于最新的移动端后台、互联网、企业应用,基于Netty实现的异步无阻塞的网络框架,诞生于2011年,当时叫node.x,不过后来因为某些原因改名位Vert.x。经过5年多的发展,现在已经到了3.3.3版本。</p>
<p>Vert.x是一个异步无阻塞的网络框架,其参照物是node.js。基本上node.js能干的事情,Vert.x都能干。而且Vert.x还支持分布式,与多核利用。通过Hazelcast管理各个Vert.x节点的信息,然后通过EventBus在节点之间互相发消息,于此同时Vert.x还能支持应用的高可用。</p>
<h3 id="vertx体系架构">vert.x体系架构</h3>
<p><img src="/images/tangsz/vert-frame.jpg" alt="vert-frame" /></p>
<p>我们的业务逻辑其实都是基于verticle来实现的,然后Vert.x框架会将你的verticle绑定到相关的线程模型上,这里verticle1,verticle2是I/O密集型项目,所有的逻辑都会跑在NIO Worker上。而Verticle3会有一些同步的耗时的请求,则会被绑定到Worker线程模型上。另外两个Vert.x节点则通过EventBus互相通信,而EventBus通过HazelCast来获取整个集群里的节点信息。注意这里每一个verticle其实都是一个线程(启动的时候指定实例数目参数即可),这样可以充分的利用多核。而node.js其实只能通过Cluster来提升多核利用</p>
<p>一个典型的Vert.x部署场景:
<img src="/images/tangsz/vert-deploy.jpg" alt="vert-deploy" /></p>
<p>图中业务逻辑被拆成很多小的verticle。这里你可以把这些小的verticle看成是微服务,然后水平扩展这些服务,同时也可以把自己的业务按CPU密集与I/O密集型拆分。服务与服务之间可以通过EventBus互相调用,另外Vert.x的EventBus调用目标verticle的时候会按RoundRobin算法来做balance</p>
<h2 id="三vertx中基本概念">三、vert.x中基本概念</h2>
<p><img src="/images/tangsz/vert-module.png" alt="vert-module" /></p>
<h3 id="verticle">Verticle</h3>
<p>Vert.x的代码执行包,它可以用JS、Ruby等多语言来编写,在同一个Vert.x实例中可以同时执行多个Verticle,一个应用可能由多个Verticle组成,他们被部署到不同的网络节点上,彼此之间通过在Vert.x的事件总线(event bus)上交换信息来通信。对于具体的verticle可以直接从命令行启动,但是通常会将它打包成modules</p>
<h3 id="module">Module</h3>
<p>Vert.x应用由一个或多个modules来实现.一个模块由多个verticles来实现.你可以把module想象出一个个Java package.里面可能是特定业务的实现,或者公共的服务实现(那些可以重用的服务).Vert.x编写好的module,可以发布到maven的仓库里.以zip包装成二进制格式.或者发布到vert.x module 注册中心.实际上这种以模块方式的开发,支撑着整个Vert.x生态系统</p>
<h3 id="vertx-实例">Vert.x 实例</h3>
<p>Verticles 其实是跑在 Vert.x实例上的.所谓Vert.x实例其实就是一个运行在某一个JVM上的Vert.x对象实例.可以将多个Verticles运行在一个Vert.x实例上,而vert.x实例可以跑在单个JVM上,也可以跑在其他JVM上,通过分布式event bus来维持通信.注意vert.x实例其实是由vertx命令行启动的.你可以指定实例数目在单个JVM上</p>
<h3 id="event-loop-vertical">Event Loop Vertical</h3>
<p>事件的业务处理线程,存在于Event Loop中,用于处理非阻塞短任务</p>
<h3 id="event-bus">Event Bus</h3>
<p>它是Vert.X的核心,在集群中容器之间的通信,各个Verticle之间的通讯都是经过Event Bus来实现的</p>
<h3 id="shared-data共享数据">Shared Data(共享数据)</h3>
<p>它是Vert.X提供的一个简单共享Map和Set,用来解决各个Verticle之间的数据共享。</p>
<h3 id="worker-vertical-阻塞处理">Worker Vertical (阻塞处理)</h3>
<p>事件的业务处理线程,用于处理长任务阻塞任务。
事件处理之外肯定会发生其长时间数据处理请求.比如处理一个图片上传,然后转存到磁盘上等.或者一次长时间的排序计算等.</p>
<p>在Verticle类型里,有一种特别的verticle叫做Worker.不同于标准的verticle,他不使用event loop.而是采用vert.x内部的另一个线程池叫做worker pool.其区别在于,worker verticles不会并行的执行Handler.而是阻塞式的,等待上一个Handler处理完了,才会再执行后面的请求.你可以想象出队列的方式执行某些请求.所以为了支持标准的与阻塞式的worker verticles, Vert.x提供了一种混合线程模型,你可以选择适当的模型用于你的应用.</p>
<h2 id="四vertx特性">四、vert.x特性</h2>
<h3 id="多语言支持">多语言支持</h3>
<p>能在JVM上运行的常见语言,都可以直接编写基于Vert.X的应用,目前官方支持的有 Java, JavaScript, CoffeeScript, Ruby, Python or Groovy</p>
<h3 id="多服务器客户端支持">多服务器客户端支持</h3>
<p>支持编写TCP、UDP客户端和服务器,HTTP客户端和服务器和Websocket支持,Vert.x-Web模块能够实现基于http的微服务框架</p>
<h3 id="支持多异步客户端">支持多异步客户端</h3>
<p>Vert.x提供了不同的异步客户端,用于从应用程序访问各种数据存储。 当然Vert.x也没有限制你必须用vert.x的客户端,你也可以到下载各软件自己的客户端(如mongoDB自己的),不过vert.x的异步API,提供多了多种语言支持Java、JavaScript、Groovy、Ruby等多语言的支持。从目前官网列出的异步API有:MongoDB client、JDBC client、SQL common、Redis client、MySQL / PostgreSQL client 等。</p>
<p>除了数据访问层API支持,Vert.x还实现了Mail Client、STOMP Client & Server、TCP 客户端A等PI,同时vert.x也对Eventbus、apache Camel、RabbitMQ等桥接集成了实现,的并支持Java、JavaScript、Groovy、Ruby等多语言。</p>
<h3 id="认证和授权">认证和授权</h3>
<p>Vert.x目前已经支持Auth common、JDBC auth、JWT auth、Shiro auth、MongoDB auth、OAuth 2等多种认证和授权API,并支持Java、JavaScript、Groovy、Ruby等多语言。</p>
<h3 id="微服务支持">微服务支持</h3>
<p>在Vert.x3.3.0中,vert.x提供了两个微服务相关特性:服务发现和断路器。其中服务发现是在服务运行时发现,服务位置不要硬编码,可以使用Consul、Kubernetes、redis Docker links等扩展服务发现的支持;另外断路器</p>
<h3 id="集群支持">集群支持</h3>
<p>vert.x Verticle支持集群部署、支持Verticle的高可用和故障备份。在一个Verticle突然崩溃时,Verticle会被迁移到另外的vert.x实例上。</p>
<h2 id="五vertx的使用">五、vert.x的使用</h2>
<p>vert.x社区目前已经很活跃,在国内vert.x的用还很少见到,但是在国外已经有很多大公司将vert.x运用到自己的项目中了,以下为vert.x官网列出的部分公司:
<img src="/images/tangsz/vert-company.png" alt="vert-company" /></p>
响应式设计学习
2016-12-11T00:00:00+00:00
http://www.blogways.net/blog/2016/12/11/responsive-design
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">概述</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">布局类型</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link3">布局实现</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link4">布局响应</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<h2 id="一概述">一、概述</h2>
<p>响应式设计作为近几年大热的设计趋势,已然是设计师的必备技能之一了。响应式网站设计(Responsive Web design)的理念是:集中创建页面的图片排版大小,可以智能地根据用户行为以及使用的设备环境(系统平台、屏幕尺寸、屏幕定向等)进行相对应的布局。</p>
<p>响应式设计的发展趋势在2012年被提的比较多,但是响应式设计仍然在不断变化,不断创新。比如,新的设备不断出来(iPad Mini),这让以前的设计想法土崩瓦解。而各种Web的响应式设计也获得了越来越多的注意,“让人们忘记设备尺寸”的理念将更快地驱动响应式设计,所以Web设计也将迎来更多的响应式设计元素。</p>
<p><a id="link2"></a></p>
<h2 id="二布局类型">二、布局类型</h2>
<p>在了解响应式布局前,先了解下网页设计中整体页面排版布局,常见的主要有如下几种类型:</p>
<p><img src="/images/chenwt/responsive-design-1.png" alt="" /></p>
<p><a id="link3"></a></p>
<h2 id="三布局实现">三、布局实现</h2>
<p>采用不同方式实现布局设计,也有不同的方式,这里基于页面的实现单位而言,分为四种类型:固定布局、可切换的固定布局、弹性布局、混合布局。</p>
<p><img src="/images/chenwt/responsive-design-2.png" alt="" /></p>
<ol>
<li>固定布局:以像素作为页面的基本单位,不管设备屏幕及浏览器宽度,只设计一套尺寸;</li>
<li>可切换的固定布局:同样以像素作为页面单位,参考主流设备尺寸,设计几套不同宽度的布局。通过设别的屏幕尺寸或浏览器宽度,选择最合适的那套宽度布局;</li>
<li>弹性布局:以百分比作为页面的基本单位,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;</li>
<li>混合布局:同弹性布局类似,可以适应一定范围内所有尺寸的设备屏幕及浏览器宽度,并能完美利用有效空间展现最佳效果;只是混合像素、和百分比两种单位作为页面单位。</li>
</ol>
<p>可切换的固定布局、弹性布局、混合布局都是目前可被采用的响应式布局方式。</p>
<p>其中可切换的固定布局的实现成本最低,但拓展性比较差;而弹性布局与混合布局效果具响应性,都是比较理想的响应式布局实现方式。</p>
<p>只是对于不同类型的页面排版布局实现响应式设计,需要采用不用的实现方式。通栏、等分结构的适合采用弹性布局方式、而对于非等分的多栏结构往往需要采用混合布局的实现方式。</p>
<p><img src="/images/chenwt/responsive-design-3.png" alt="" /></p>
<p><a id="link4"></a></p>
<h2 id="四布局响应">四、布局响应</h2>
<p>布局响应对页面进行响应式的设计实现,需要对相同内容进行不同宽度的布局设计,有两种方式:</p>
<ul>
<li>桌面优先(从桌面端开始向下设计);</li>
<li>移动优先(从移动端向上设计);</li>
</ul>
<p>无论基于那种模式的设计,要兼容所有设备,布局响应时不可避免地需要对模块布局做一些变化(发生布局改变的临界点称之为断点,这里作为一个设计就只能为理解为一个区间了),数据员通过JS获取设备的屏幕宽度,来改变网页的布局,这一过程称之为布局响应屏幕。常见的主要有如下几种方式:</p>
<h3 id="41-布局不变">4.1 布局不变</h3>
<p>布局不变,即页面中整体模块布局不发生变化,主要有:</p>
<ul>
<li>
<p>模块中内容:挤压-拉伸;</p>
<p><img src="/images/chenwt/responsive-design-4.png" alt="" /></p>
</li>
<li>
<p>模块中内容:换行-平铺;</p>
<p><img src="/images/chenwt/responsive-design-5.png" alt="" /></p>
</li>
<li>
<p>模块中内容:删减-增加;</p>
<p><img src="/images/chenwt/responsive-design-6.png" alt="" /></p>
</li>
</ul>
<h3 id="42-布局改变">4.2 布局改变</h3>
<p>布局改变,即页面中的整体模块布局发生变化,主要有:</p>
<ul>
<li>
<p>模块位置变换;</p>
<p><img src="/images/chenwt/responsive-design-7.png" alt="" /></p>
</li>
<li>
<p>模块展示方式改变:隐藏-展开;</p>
<p><img src="/images/chenwt/responsive-design-8.png" alt="" /></p>
</li>
<li>
<p>模块数量改变:删减-增加;</p>
<p><img src="/images/chenwt/responsive-design-9.png" alt="" /></p>
</li>
</ul>
<p>很多时候,单一方式的布局响应无法满足理想效果,需要结合多种组合方式,但原则上尽可能时保持简单轻巧,而且同一断点内保持统一逻辑。否则页面实现得太过复杂,也会影响整体体验。从设计布局方面了解的响应式设计还很片面浅显,希望以后可以跟同事们交流更深入了解响应式设计。</p>
色彩攻略
2016-12-11T00:00:00+00:00
http://www.blogways.net/blog/2016/12/11/color-strategy
<p><img src="/images/chenwt/color-strategy.jpg" alt="" /></p>
Esper复杂事件处理引擎 - 入门简介
2016-12-10T00:00:00+00:00
http://www.blogways.net/blog/2016/12/10/esper-introduction
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">Esper简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">事件类型</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link3">处理模型</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#link4">未完待续…</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<style>
.red { color: red; }
.blue { color: blue; }
</style>
<p><a id="link1"></a></p>
<h2 id="一esper简介">一、Esper简介</h2>
<p>将<code class="language-plaintext highlighter-rouge">Espser</code>之前,首先要讲<code class="language-plaintext highlighter-rouge">CEP</code>,是一种实时事件处理并从大量事件数据流中挖掘复杂模式的技术,全称为Complex Event Processing,即复杂事件处理。<code class="language-plaintext highlighter-rouge">CEP</code>是一种标准,<code class="language-plaintext highlighter-rouge">Esper</code>只是对这个标准的一种开源实现。</p>
<p>听起来好像很复杂,实际上就是基于事件流进行数据处理,把要分析的数据抽象成事件,然后将数据发送到<code class="language-plaintext highlighter-rouge">CEP</code>引擎,引擎就会根据事件的输入和最初注册的处理模型,得到事件处理结果。</p>
<p><code class="language-plaintext highlighter-rouge">CEP</code>的一个重要特点就是他是一个<i class="red">内存计算</i>工具和<i class="red">类SQL</i>语句。</p>
<p>有朋友可能会想到目前很流行的一个实时计算框架——<code class="language-plaintext highlighter-rouge">Storm</code>,但这两个完全不是一类东西。<code class="language-plaintext highlighter-rouge">Esper</code>的特点在于它的复杂处理逻辑可以用类SQL语句(EPL)开发,而<code class="language-plaintext highlighter-rouge">Storm</code>的特点在于<strong><em>并行计算</em></strong>和<strong><em>快速恢复</em></strong>,但是计算逻辑是需要自己用代码实现的。</p>
<p>前面说了,<code class="language-plaintext highlighter-rouge">Esper</code>,是<code class="language-plaintext highlighter-rouge">CEP</code>的一个开源实现,它是一个Java的事件流处理和复杂事件处理的引擎(.NET为NEsper)。</p>
<p><code class="language-plaintext highlighter-rouge">Esper</code>引擎可应用于如下等领域:</p>
<ul>
<li>网络入侵探测</li>
<li>SLA监测</li>
<li>RFID读取</li>
<li>航空运输调控</li>
<li>金融方面(风险管理,欺诈探测)等</li>
</ul>
<p>其特点是能够快速开发出复杂的实时计算策略,并且有着高吞吐量以及低延迟的特点,特别适合<i class="red">大量数据的实时计算</i>。</p>
<p><i class="red"><strong>注意</strong>:</i></p>
<ul>
<li>后续所有测试例子中的<code class="language-plaintext highlighter-rouge">Java Bean</code>都没有写出<code class="language-plaintext highlighter-rouge">getter</code>,<code class="language-plaintext highlighter-rouge">setter</code>,<code class="language-plaintext highlighter-rouge">toString</code>,如有需要请自行添加。</li>
</ul>
<h3 id="11-epl-简述">1.1 EPL 简述</h3>
<p><code class="language-plaintext highlighter-rouge">EPL</code>,即Event Proess Language,<code class="language-plaintext highlighter-rouge">CEP</code>的类SQL语句,可以理解为处理模型的定义与描述。</p>
<p>这是运行在<code class="language-plaintext highlighter-rouge">CEP</code>引擎中的特殊语句,之所以叫他类SQL,是因为它和SQL确实很像,除了<code class="language-plaintext highlighter-rouge">select</code>,<code class="language-plaintext highlighter-rouge">insert</code>,<code class="language-plaintext highlighter-rouge">delete</code>,<code class="language-plaintext highlighter-rouge">update</code>,而且也有<code class="language-plaintext highlighter-rouge">avg</code>,<code class="language-plaintext highlighter-rouge">count</code>等函数。所以对于会SQL的人来说,他的语法结构大致还是能猜出一二的。</p>
<p>工作模式有点类似于一个数据库的 <i class="red"><strong>倒置</strong></i>,它允许应用程序存储查询并让数据通过,而不是存储数据并在存储的数据上运行查询语句(应用程序存储查询,并让数据运行通过)。当匹配查询的条件时,响应逻辑触发。</p>
<p>数据库是先存储数据,通过编译解析SQL,完成已存储数据的查询,Esper则是先编译EPL语句,形成一个过滤(或处理)层(或者网),实时过来的数据,通过这个过滤层完成有效事件的筛选或形成有效事件。</p>
<p>如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from Apple
select avg(price) from Apple.win:length_batch(3)
</code></pre></div></div>
<p>测试代码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* Created by wanzhou on 16/8/1.
*/
import com.espertech.esper.client.*;
class Apple
{
private int id;
private int price;
}
class AppleListener implements UpdateListener
{
public void update(EventBean[] newEvents, EventBean[] oldEvents)
{
if (newEvents != null)
{
// Double avg = (Double) newEvents[0].get("avg(price)");
// System.out.println("Apple's average price is " + avg);
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
}
public class AverageTest {
public static void main(String[] args) throws InterruptedException {
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
EPRuntime runtime = epService.getEPRuntime();
String product = Apple.class.getName();
String epl = "select avg(price) from " + product + ".win:length_batch(2)";
// String epl = "select * from Apple.win:length_batch(2)";
EPStatement state = admin.createEPL(epl);
state.addListener(new AppleListener());
Apple apple1 = new Apple();
apple1.setId(1);
apple1.setPrice(5);
runtime.sendEvent(apple1);
Apple apple2 = new Apple();
apple2.setId(2);
apple2.setPrice(2);
runtime.sendEvent(apple2);
Apple apple3 = new Apple();
apple3.setId(3);
apple3.setPrice(5);
runtime.sendEvent(apple3);
Apple apple4 = new Apple();
apple4.setId(4);
apple4.setPrice(6);
runtime.sendEvent(apple4);
Apple apple5 = new Apple();
apple5.setId(5);
apple5.setPrice(7);
runtime.sendEvent(apple5);
}
}
</code></pre></div></div>
<h3 id="12-事件类型">1.2 事件类型</h3>
<p><code class="language-plaintext highlighter-rouge">Esper</code> 对处理的数据结构有明确的要求,能处理的数据结构:</p>
<ul>
<li>POJO ( java.lang.Object )</li>
<li>java.util.Map</li>
<li>Object[]</li>
<li>XML</li>
</ul>
<p>特点:</p>
<ul>
<li>支持nested、indexed、mapped属性(嵌套没有层数限制)</li>
<li>需要告知引擎类型元数据,包括嵌套的数据。</li>
<li>能改变属性顺序,或抽取部分属性到一个新的事件。</li>
<li>Object、Map 和 Object[] 类型支持超类( Supertype )</li>
</ul>
<h3 id="13-事件属性">1.3 事件属性</h3>
<p>| <strong><em>类型</em></strong>| <strong><em>说明</em></strong> | <strong><em>语法</em></strong> | <strong><em>实例</em></strong>|
| — | — | — | — |
| Simple | 只包含单个值得属性 | name | price |
| Indexed | 索引 | name[index] | price[0] |
| Mapped | map属性 | name[‘key’] | apple(‘price’) |
| nested | 属性嵌套 | name.nestedname | apple.price|</p>
<p>组合上述所有的属性,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>person.address(‘home’).street[0]
</code></pre></div></div>
<ul>
<li>
<p>事件属性示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class MyEventType {
String myMapKey;
int myIndexValue;
int myInnerIndexValue;
Map<String, InnerType> innerTypesMap; // mapped property
InnerType[] innerTypesArray; // indexed property
}
public class InnerType {
String name;
int[] ids;
}
select innerTypesMap('somekey').ids[1],
// innerTypesMap(myMapKey).getIds(myIndexValue),
innerTypesArray[1].ids[2],
// innerTypesArray(myIndexValue).getIds(myInnerIndexValue)
from MyEventType
</code></pre></div> </div>
</li>
</ul>
<h3 id="14-动态事件属性">1.4 动态事件属性</h3>
<p>| <strong><em>类型</em></strong> | <strong><em>语法</em></strong> |
| — | — |
| Dynamic Simple | name? |
| Dynamic Indexed | name[index]? |
| Dynamic Mapped | name(‘key’)? |
| Dynamic Nested | name?.nestedname |</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select timestamp? from OrderEvent
select detail?.driection from OrderEvent
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">OrderEvent</code> 拥有多个接口类,其中某个或某些有<code class="language-plaintext highlighter-rouge">timestamp</code>属性,则其动态属性</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select timestamp? from OrderEvent
</code></pre></div></div>
<p>如果动态属性是嵌套属性,则</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select detail?.driection from OrderEvent 等价于
select detail?.direction? from OrderEvent
</code></pre></div></div>
<ul>
<li>
<p>示例</p>
<p>动态属性返回值总是 <code class="language-plaintext highlighter-rouge">java.lang.Object</code> 类型,在事件进程运行时,动态属性不存在,则返回 <code class="language-plaintext highlighter-rouge">null</code>。</p>
<p>如:事件<code class="language-plaintext highlighter-rouge">OrderEvent</code>拥有一个<code class="language-plaintext highlighter-rouge">item</code>属性,通过动态属性指定一个查询来取得<code class="language-plaintext highlighter-rouge">Service</code>或<code class="language-plaintext highlighter-rouge">Product</code>的价格,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select item.price? from OrderEvent
</code></pre></div> </div>
<p>如果<code class="language-plaintext highlighter-rouge">OrderEvent</code> 拥有多个接口类,其中某个或某些有<code class="language-plaintext highlighter-rouge">timestamp</code>属性,则其动态属性:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select timestamp? from OrderEvent
</code></pre></div> </div>
<p>如果动态属性是嵌套属性,则动态属性下的所有嵌套属性都为动态属性:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select detail?.driection from OrderEvent 等价于
select detail?.direction? from OrderEvent
</code></pre></div> </div>
</li>
<li>
<p>测试代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class OrderEvent {
Object item;
}
class Serivce {
String name;
int price;
}
class Product {
String name;
int price;
String info;
}
class DynamicListener implements UpdateListener
{
public void update(EventBean[] newEvents, EventBean[] oldEvents)
{
if (newEvents != null)
{
// Double avg = (Double) newEvents[0].get("item.price?");
// System.out.println("Apple's average price is " + avg);
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
}
public class DynamicProps {
public static void main(String[] args) throws InterruptedException {
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
String epl = "select item.info? from OrderEvent";
EPStatement state = admin.createEPL(epl);
state.addListener(new AppleListener());
EPRuntime runtime = epService.getEPRuntime();
Product product = new Product();
product.setName("pdt1");
product.setPrice(5);
product.setInfo("Product Info.");
OrderEvent oe1 = new OrderEvent();
oe1.setItem(product);
runtime.sendEvent(oe1);
Serivce svr = new Serivce();
svr.setName("svr");
svr.setPrice(10);
OrderEvent oe2 = new OrderEvent();
oe2.setItem(svr);
runtime.sendEvent(oe2);
}
}
</code></pre></div> </div>
</li>
</ul>
<p><a id="link2"></a></p>
<h2 id="二事件类型">二、事件类型</h2>
<h3 id="21-pojojavalangobject">2.1 POJO(<code class="language-plaintext highlighter-rouge">java.lang.Object</code>)</h3>
<p>对于POJO,<code class="language-plaintext highlighter-rouge">Esper</code>要求对每一个私有属性要有<code class="language-plaintext highlighter-rouge">getter</code>方法。<code class="language-plaintext highlighter-rouge">Esper</code>允许不必按照<code class="language-plaintext highlighter-rouge">JavaBean</code>规定的格式,但是<code class="language-plaintext highlighter-rouge">getter</code>方法是必须的。又或者可以在配置文件中配置可访问的方法来代替<code class="language-plaintext highlighter-rouge">getter</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Person
{
String name;
int age;
public String getName()
{
return name;
}
public int getAge()
{
return age;
}
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Esper</code>对POJO的支持基本上就是上面所说的,另外他还支持实现了多个接口类或者抽象类的POJO,使用方法和普通的POJO没什么区别,这里就不列举了。</p>
<p>当<code class="language-plaintext highlighter-rouge">Person</code>的name属性为test时,得到对应的age,所有children和address.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select age,children,address from Person where name="test" 当Person的name属性为test时,得到对应的第二个孩子,家里的电话和家庭住址在哪条路上
select children[1], phones('home'), address.road from Person where name="test”
public Child getChildren(int index) { // 返回对应下标的children信息
return children.get(index);
}
</code></pre></div></div>
<p>当<code class="language-plaintext highlighter-rouge">Person</code>的name属性为test时,更新家里的电话</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>update Person set phones('home') = 123456789 where name="test"
public void setPhones(String name, Integer number) {
phones.put(name, number); // 用于 phones 属性的更新
}
</code></pre></div></div>
<ul>
<li>
<p>PojoPersonTest 测试示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class PojoPerson implements Serializable {
String pname;
int age;
List<Child> children;
Map<String, Integer> phones;
Address address;
public String getPname() {
return pname;
}
public void setPname(String pname) {
this.pname = pname;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public List<Child> getChildren() {
return children;
}
public Child getChildren(int index) {
return children.get(index);
}
public void setChildren(List<Child> children) {
this.children = children;
}
public Map<String, Integer> getPhones() {
return phones;
}
public void setPhones(Map<String, Integer> phones) {
this.phones = phones;
}
public void setPhones(String name, Integer number) {
System.out.println("=======");
phones.put(name, number);
}
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public String toString() {
return "PojoPerson{" +
"name='" + pname + '\'' +
", age=" + age +
", children=" + children +
", phones=" + phones +
", address=" + address +
'}';
}
}
class Child
{
String cname;
int gender;
public String getCname() {
return cname;
}
public void setCname(String cname) {
this.cname = cname;
}
public int getGender() {
return gender;
}
@Override
public String toString() {
return "Child{" +
"name='" + cname + '\'' +
", gender=" + gender +
'}';
}
public void setGender(int gender) {
this.gender = gender;
}
}
class Address
{
String road;
String street;
int houseNo;
public String getRoad() {
return road;
}
public void setRoad(String road) {
this.road = road;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public int getHouseNo() {
return houseNo;
}
public void setHouseNo(int houseNo) {
this.houseNo = houseNo;
}
@Override
public String toString() {
return "Address{" +
"road='" + road + '\'' +
", street='" + street + '\'' +
", houseNo=" + houseNo +
'}';
}
}
public class PojoPersonTest {
public static void main(String[] args) {
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
EPRuntime runtime = epService.getEPRuntime();
String qry1 = "select age,children,address from PojoPerson where pname = \"张三\"";
String qry2 = "select children[1], phones('home'), address.road from PojoPerson where pname=\"李四\"";
String qry = "select * from PojoPerson";
EPStatement state1 = admin.createEPL(qry1);
state1.addListener(new UpdateListener() {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("qry1===== ");
System.out.println(newEvents[0].getUnderlying());
}
}
});
EPStatement state2 = admin.createEPL(qry2);
state2.addListener(new UpdateListener() {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("qry2+++++ ");
System.out.println(newEvents[0].getUnderlying());
}
}
});
EPStatement state = admin.createEPL(qry);
state.addListener(new UpdateListener() {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("qry---- ");
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
});
PojoPerson pp1 = new PojoPerson();
Address a1 = new Address();
a1.setHouseNo(100);
a1.setRoad("Address 1");
a1.setStreet("Street 1");
pp1.setAddress(a1);
pp1.setPname("张三");
pp1.setAge(30);
Map<String, Integer> p1 = new HashMap<String, Integer>();
p1.put("home", 1333333333);
p1.put("work", 1333333334);
pp1.setPhones(p1);
List<Child> lc1 = new ArrayList<Child>();
Child ch1 = new Child();
ch1.setCname("张四");
ch1.setGender(1);
lc1.add(ch1);
pp1.setChildren(lc1);
runtime.sendEvent(pp1);
PojoPerson pp2 = new PojoPerson();
Address a2 = new Address();
a2.setHouseNo(100);
a2.setRoad("Address 1");
a2.setStreet("Street 1");
pp2.setAddress(a2);
pp2.setPname("李四");
pp2.setAge(30);
Map<String, Integer> p2 = new HashMap<String, Integer>();
p2.put("home", 1444444444);
p2.put("work", 1444444445);
pp2.setPhones(p2);
List<Child> lc2 = new ArrayList<Child>();
Child ch21 = new Child();
ch21.setCname("李五");
ch21.setGender(1);
lc2.add(ch21);
Child ch22 = new Child();
ch22.setCname("李六");
ch22.setGender(0);
lc2.add(ch22);
pp2.setChildren(lc2);
runtime.sendEvent(pp2);
PojoPerson pp3 = new PojoPerson();
Address a3 = new Address();
a3.setHouseNo(100);
a3.setRoad("Address 1");
a3.setStreet("Street 1");
pp3.setAddress(a3);
pp3.setPname("test");
pp3.setAge(30);
Map<String, Integer> p3 = new HashMap<String, Integer>();
p3.put("home", 1555555555);
p3.put("work", 1555555556);
pp3.setPhones(p3);
List<Child> lc3 = new ArrayList<Child>();
Child ch31 = new Child();
ch31.setCname("王六");
ch31.setGender(1);
lc3.add(ch31);
pp3.setChildren(lc3);
runtime.sendEvent(pp3);
}
}
</code></pre></div> </div>
</li>
<li>
<p>UpdateTest 测试代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class UpdateTest {
public static void main(String[] args) {
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
EPRuntime runtime = epService.getEPRuntime();
String ctr = "create window UPerson.win:keepall() as Person";
String update = "on Person as p1 update UPerson as p2 set p2.age = 40 where p2.name = 'test'";
// String update = "update Person set age = 40 where name = 'test'";
String qry = "select * from Person";
admin.createEPL(ctr);
admin.createEPL("insert into UPerson select * from Person");
EPStatement state3 = admin.createEPL(update);
state3.addListener(new UpdateListener() {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("update==> ");
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
});
EPStatement state = admin.createEPL(qry);
state.addListener(new UpdateListener() {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("qry==> ");
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
});
Person p1 = new Person();
p1.setName("test");
p1.setAge(22);
runtime.sendEvent(p1);
Person p2 = new Person();
p2.setName("test2");
p2.setAge(23);
runtime.sendEvent(p2);
}
}
</code></pre></div> </div>
</li>
</ul>
<h3 id="22-javautilmap">2.2 <code class="language-plaintext highlighter-rouge">java.util.Map</code></h3>
<p><code class="language-plaintext highlighter-rouge">Esper</code>支持原生<code class="language-plaintext highlighter-rouge">Java Map</code>结构的事件。相对于POJO来说,<code class="language-plaintext highlighter-rouge">Map</code>的结构更利于事件类型的热加载,毕竟不是<code class="language-plaintext highlighter-rouge">class</code>,所以不需要重启JVM。</p>
<p>所以如果系统对重启比较敏感,建议使用Map来定义事件的结构。<code class="language-plaintext highlighter-rouge">Map</code>的结构很简单,主要分为事件定义名和事件属性列表。</p>
<p>通过 <code class="language-plaintext highlighter-rouge">sendEvent(Map map, String eventTypeName)</code> 方法发送事件。</p>
<p>对于<code class="language-plaintext highlighter-rouge">Map</code>,<code class="language-plaintext highlighter-rouge">Esper</code>只支持增量更新,也就是说只能增加<code class="language-plaintext highlighter-rouge">Map</code>中的属性定义,而不能修改或者删除某个属性。</p>
<p>实际上属性增多并不影响其处理性能,所以没有删除在我看来也没什么。至于修改,也只能是先注销再注册了。</p>
<ul>
<li>
<p>定义</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // Person定义
Map<String,Object> person = new HashMap<String,Object>();
person.put("name", String.class);
person.put("age", int.class);
person.put("children", List.class);
person.put("phones", Map.class);
// 注册Person到Esper
admin.getConfiguration().addEventType("PersonEvent", person);
select name, age, phones from PersonEvent.win:time(1 min) where age = 20
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">java.util.Map</code>发送</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Map<String, Object> p1 = new HashMap<String, Object>();
p1.put("name", "test");
p1.put("age", 20);
List<Person> child = new ArrayList<Person>();
p1.put("children", child);
Map<String, Object> phones = new HashMap<String, Object>();
phones.put("home", "051088744796");
phones.put("self", "15051818371");
p1.put("phones", phones);
epService.getEPRuntime().sendEvent(p1, "PersonEvent");
select name, age, phones from PersonEvent.win:time(1 min) where age = 20
{name=test, phones={self=15051818371, home=051088744796}, age=20}
</code></pre></div> </div>
</li>
<li>
<p>PersonMap 示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class Person implements Serializable
{
String name;
int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
class PersonMapListener implements UpdateListener
{
public void update(EventBean[] newEvents, EventBean[] oldEvents)
{
if (newEvents != null)
{
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
}
public class PersonMap {
public static void main(String[] args) throws InterruptedException {
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
// Person 定义
Map<String, Object> person = new HashMap<String, Object>();
person.put("name", String.class);
person.put("age", int.class);
person.put("children", List.class);
person.put("phones", Map.class);
// 注册Person到Esper
admin.getConfiguration().addEventType("PersonEvent", person);
String epl = "select name, age, phones from PersonEvent.win:time(1 min) where age = 20";
EPStatement state = admin.createEPL(epl);
state.addListener(new PersonMapListener());
EPRuntime runtime = epService.getEPRuntime();
Map<String, Object> p1 = new HashMap<String, Object>();
p1.put("name", "test");
p1.put("age", 20);
List<Person> child = new ArrayList<Person>();
Person pc1 = new Person();
pc1.setName("Child1");
pc1.setAge(20);
child.add(pc1);
p1.put("children", child);
Map<String, Object> phones = new HashMap<String, Object>();
phones.put("home", "051088744796");
phones.put("self", "15051818371");
p1.put("phones", phones);
runtime.sendEvent(p1, "PersonEvent");
// 新增一个gender属性
// person.put("gender", int.class);
// admin.getConfiguration().updateMapEventType("PersonEvent", person);
}
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="221-javautilmap超类supertype">2.2.1 <code class="language-plaintext highlighter-rouge">java.util.Map</code>超类(Supertype)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epService.getEPAdministrator().getConfiguration().addEventType(
"AccountUpdate", accountUpdateDef, new String[] {"BaseUpdate"}
);
</code></pre></div></div>
<p>当继承之后,对超类进行<code class="language-plaintext highlighter-rouge">select</code>时,不管是子类、或超类事件到达,都会进行查询输出。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from BaseUpdate
</code></pre></div></div>
<p>每个Map 事件可以有多个超类!</p>
<h4 id="222-map-嵌套">2.2.2 Map 嵌套</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Map<String, Object> updatedFieldDef = new HashMap<String, Object>();
updatedFieldDef.put("name", String.class);
updatedFieldDef.put("addressLine1", String.class);
updatedFieldDef.put("history", UpdateHistory.class);
epService.getEPAdministrator().getConfiguration().
addEventType("UpdatedFieldType", updatedFieldDef);
Map<String, Object> accountUpdateDef = new HashMap<String, Object>();
accountUpdateDef.put("accountId", long.class);
accountUpdateDef.put("fields", "UpdatedFieldType");
// the latter can also be: accountUpdateDef.put("fields", updatedFieldDef);
epService.getEPAdministrator().getConfiguration().
addEventType("AccountUpdate", accountUpdateDef);
Map<String, Object> updatedField = new HashMap<String, Object>();
updatedField.put("name", "Joe Doe");
updatedField.put("addressLine1", "40 Popular Street");
updatedField.put("history", new UpdateHistory());
Map<String, Object> accountUpdate = new HashMap<String, Object>();
accountUpdate.put("accountId", 10009901);
accountUpdate.put("fields", updatedField);
epService.getEPRuntime().sendEvent(accountUpdate, "AccountUpdate");
select accountId, fields.name, fields.addressLine1, fields.history.lastUpdate
from AccountUpdate
</code></pre></div></div>
<h4 id="223-map-数组">2.2.3 Map 数组</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Map<String, Object> sale = new HashMap<String, Object>();
sale.put("userids", int[].class);
sale.put("salesPersons", SalesPerson[].class);
sale.put("items", "OrderItem[]"); // The property type is the name itself appended by []
epService.getEPAdministrator().getConfiguration().
addEventType("SaleEvent", sale);
</code></pre></div></div>
<p>查询语句:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select userids[0], salesPersons[1].name,
items[1], items[1].price.amount from SaleEvent
</code></pre></div></div>
<h3 id="23-object-array">2.3 Object Array</h3>
<p><code class="language-plaintext highlighter-rouge">Object</code> 数组跟<code class="language-plaintext highlighter-rouge">Map</code>类似,只是定义的方式有些区别,同时也只支持增量更新。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String[] propertyNames = {"carId", "direction"}; // order is important
Object[] propertyTypes = {String.class, int.class}; // type order matches name order
epService.getEPAdministrator().getConfiguration().
addEventType("CarLocUpdateEvent", propertyNames, propertyTypes);
epRuntime.sendEvent(new Object[]{carId, direction}, “CarLocUpdateEvent”);
</code></pre></div></div>
<ul>
<li>
<p>查询语句:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select carId from CarLocUpdateEvent .win:time(1 min) where direction = 1
</code></pre></div> </div>
</li>
<li>
<p>增量更新:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> admin.getConfiguration().updateObjectArrayEventType("Person", new String[] { "gender" }, new Object[] { int.class });
</code></pre></div> </div>
</li>
<li>
<p>ObjectArray 实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class CommonUpdateListener implements UpdateListener {
public void update(EventBean[] newEvents, EventBean[] oldEvents) {
if (null != newEvents) {
System.out.print("common update method: ");
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
}
public class ObjectArray {
/**
* @param args
*/
public static void main(String[] args)
{
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
EPRuntime runtime = epService.getEPRuntime();
// Address定义
String[] addressPropNames = { "road", "street", "houseNo" };
Object[] addressPropTypes = { String.class, String.class, int.class };
// Child定义
String[] childPropNames = { "name", "age" };
Object[] childPropTypes = { String.class, int.class };
// Person定义
String[] personPropNames = { "name", "age", "children", "phones", "address" };
Object[] personPropTypes = { String.class, int.class, "Child[]", Map.class, "Address" };
// 注册Address到Esper
admin.getConfiguration().addEventType("Address", addressPropNames, addressPropTypes);
// 注册Child到Esper
admin.getConfiguration().addEventType("Child", childPropNames, childPropTypes);
// 注册Person到Esper
admin.getConfiguration().addEventType("Person", personPropNames, personPropTypes);
// 新增一个gender属性
admin.getConfiguration().updateObjectArrayEventType("Person", new String[] { "gender" }, new Object[] { int.class });
/** 输出结果:
* Person props: [name, age, children, phones, address, gender]
*/
EventType event = admin.getConfiguration().getEventType("Person");
System.out.println("Person props: " + Arrays.asList(event.getPropertyNames()));
String epl = "select name, age, phones, address.road, address.street, address.houseNo, children[0].name from Person";
EPStatement state = admin.createEPL(epl);
state.addListener(new CommonUpdateListener());
Object[] addr = {"road1", "street1", 1};
Object[][] child = child;
Map<String, Object> phones = new HashMap<String, Object>();
phones.put("home", "051088744796");
phones.put("self", "15051818371");
Object[] person = {"person", 30, child, phones, addr};
runtime.sendEvent(person, "Person");
}
}
</code></pre></div> </div>
</li>
</ul>
<h4 id="231-object-array-超类supertype">2.3.1 Object Array 超类(Supertype)</h4>
<p><code class="language-plaintext highlighter-rouge">Object[]</code> 可以在引擎初始化、或运行时通过administrator接口,定义一个超类。</p>
<p>超类必须也是<code class="language-plaintext highlighter-rouge">Object[]</code> 事件,且所有的属性会对子类可见,同名属性将会被覆盖。定义于超类上的EPL查询语法,子类亦会触发。</p>
<p>定义:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>create objectarray schema SuperType (p0 String)
create objectarray schema SubType (p1 String, p2 String) inherits SuperType
</code></pre></div></div>
<p>发送:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>epRuntime.sendEvent(new Object[] {"p0_value", "p1_value"}, "SubType");
epRuntime.sendEvent(new Object[] {"p0_value"}, "SuperType");
</code></pre></div></div>
<h4 id="232-object-array嵌套属性">2.3.2 Object Array(嵌套属性)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String[] propertyNamesUpdField = {"name", "addressLine1", "history"};
Object[] propertyTypesUpdField = {String.class, String.class, UpdateHistory.class};
epService.getEPAdministrator().getConfiguration().
addEventType("UpdatedFieldType", propertyNamesUpdField, propertyTypesUpdField);
String[] propertyNamesAccountUpdate = {"accountId", "fields"};
Object[] propertyTypesAccountUpdate = {long.class, "UpdatedFieldType"};
epService.getEPAdministrator().getConfiguration().
addEventType("AccountUpdate", propertyNamesAccountUpdate, propertyTypesAccountUpdate);
Object[] updatedField = {"Joe Doe", "40 Popular Street", new UpdateHistory()};
Object[] accountUpdate = {10009901, updatedField};
epService.getEPRuntime().sendEvent(accountUpdate, "AccountUpdate");
select accountId, fields.name, fields.addressLine1, fields.history.lastUpdate
from AccountUpdate
</code></pre></div></div>
<h4 id="233-object-array一对多属性">2.3.3 Object Array(一对多属性)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String[] propertyNames = {"userids", "salesPersons", "items"};
Object[] propertyTypes = {int[].class, SalesPerson[].class, "OrderItem[]");
epService.getEPAdministrator().getConfiguration().addEventType("SaleEvent", propertyNames, propertyTypes);
</code></pre></div></div>
<p>查询语句:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select userids[0], salesPersons[1].name,
items[1], items[1].price.amount from SaleEvent
</code></pre></div></div>
<h3 id="24-xml">2.4 XML</h3>
<ul>
<li>
<p>ParseXmlSchema 示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> class ParseXMLListener implements UpdateListener
{
public void update(EventBean[] newEvents, EventBean[] oldEvents)
{
if (newEvents != null)
{
for (int i = 0; i < newEvents.length; i++) {
System.out.println(newEvents[i].getUnderlying());
}
}
}
}
public class ParseXmlSchema {
public static void main(String[] args) throws InterruptedException, IOException, ParserConfigurationException, SAXException {
String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
"<Sensor xmlns=\"SensorSchema\">" +
" <ID>urn:epc:1:4.16.36</ID>" +
" <Observation Command=\"READ_PALLET_TAGS_ONLY\">" +
" <ID>00000001</ID>" +
" <Tag>" +
" <ID>urn:epc:1:2.24.400</ID>" +
" </Tag>" +
" <Tag>" +
" <ID>urn:epc:1:2.24.401</ID>" +
" </Tag>" +
" </Observation>" +
"</Sensor>";
URL schemaURL = ParseXmlSchema.class.getResource("sensor.xsd");
EPServiceProvider epService = EPServiceProviderManager.getDefaultProvider();
EPAdministrator admin = epService.getEPAdministrator();
epService = EPServiceProviderManager.getDefaultProvider();
ConfigurationEventTypeXMLDOM sensorcfg = new ConfigurationEventTypeXMLDOM();
sensorcfg.setRootElementName("Sensor");
sensorcfg.setSchemaResource(schemaURL.toString());
epService.getEPAdministrator().getConfiguration()
.addEventType("SensorEvent", sensorcfg);
String epl = "select ID, Observation.Command, Observation.ID, Observation.Tag[0].ID, Observation.Tag[1].ID from SensorEvent";
EPStatement state = admin.createEPL(epl);
state.addListener(new ParseXMLListener());
InputSource source = new InputSource(new StringReader(xml));
DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
builderFactory.setNamespaceAware(true);
Document doc = builderFactory.newDocumentBuilder().parse(source);
epService.getEPRuntime().sendEvent(doc);
}
}
</code></pre></div> </div>
</li>
<li>
<p>对应的<code class="language-plaintext highlighter-rouge">schema</code>文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Sensor">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string"/>
<xs:element ref="Observation" />
</xs:sequence>
</xs:complexType>
</xs:element>
<xs:element name="Observation">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string"/>
<xs:element ref="Tag" maxOccurs="unbounded" />
</xs:sequence>
<xs:attribute name="Command" type="xs:string" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="Tag">
<xs:complexType>
<xs:sequence>
<xs:element name="ID" type="xs:string"/>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
</code></pre></div> </div>
</li>
</ul>
<h3 id="25-事件类型对比">2.5 事件类型对比</h3>
<p><img src="/images/esper/compare.png" alt="" /></p>
<p><a id="link3"></a></p>
<h2 id="三处理模型">三、处理模型</h2>
<p><code class="language-plaintext highlighter-rouge">Esper</code> 进程模型是连续不间断的:根据<code class="language-plaintext highlighter-rouge">event stream</code>、<code class="language-plaintext highlighter-rouge">views</code>、<code class="language-plaintext highlighter-rouge">filters</code>和<code class="language-plaintext highlighter-rouge">output rates</code>的语句选择范围,</p>
<p><code class="language-plaintext highlighter-rouge">UpdaterListener</code>是Esper提供的一个接口,用于监听某个EPL在引擎中的运行情况,即事件进入并产生结果后会通知<code class="language-plaintext highlighter-rouge">UpdateListener</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.espertech.esper.client;
import com.espertech.esper.client.EventBean;
public interface UpdateListener
{
public void update(EventBean[] newEvents, EventBean[] oldEvents);
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">Esper</code>是怎么处理事件的,即<code class="language-plaintext highlighter-rouge">Esper</code>的进程模型。</p>
<p>一个update方法,其中包括两个EventBean数组。EventBean中有一个最常用的get方法,是用来得到EPL中某个字段的值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select name from User
//假设newEvents长度为一
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">newEvents[0].get("name")</code>能得到进入的User事件的name属性值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select count(*) from User.win:time(5 sec)
//假设newEvents长度为一
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">newEvents[0].get("count(*))</code>能得到5秒内进入引擎的User事件数量有多少。</p>
<h3 id="31-insert-and-remove-stream">3.1 Insert And Remove Stream</h3>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">IR Stream</code></p>
<p><img src="/images/esper/irstream.png" alt="" /></p>
<p>从此图可以看出,随着时间推移,每个进入到引擎的<code class="language-plaintext highlighter-rouge">W</code>事件都是<code class="language-plaintext highlighter-rouge">newEvents</code>,即<code class="language-plaintext highlighter-rouge">Insert Stream</code>。<code class="language-plaintext highlighter-rouge">W</code>后括号里的值为属性值,可忽略。</p>
<p><code class="language-plaintext highlighter-rouge">Insert</code>表示事件进入引擎,<code class="language-plaintext highlighter-rouge">Remove Stream</code>表示移出引擎,对应于<code class="language-plaintext highlighter-rouge">UpdateListener</code>中的 <code class="language-plaintext highlighter-rouge">newEvents</code> 和 <code class="language-plaintext highlighter-rouge">oldEvents</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select * from Withdrawal
</code></pre></div> </div>
<p>每当引擎处理一个 <code class="language-plaintext highlighter-rouge">Withdrawal</code> 或 <code class="language-plaintext highlighter-rouge">Withdrawal</code> 子类型的事件时,会触发对应所有的 <code class="language-plaintext highlighter-rouge">update listener</code>,并将该事件传递给每个 <code class="language-plaintext highlighter-rouge">EPL</code> 语句的监听器。</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">Window Length</code></p>
<p>队列窗口(length window)告知引擎只保留最新的 <code class="language-plaintext highlighter-rouge">N</code> 个事件,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select * from Withdrawal.win:length(5)
</code></pre></div> </div>
<p>开放了一个大小为 5 的空间,可同时存放 5 个事件。</p>
<p>引擎将所有接收到的事件,让入到一个长度为 5 的空间中,当空间满了之后,最老的事件将会置换出队列,新到的事件即为<code class="language-plaintext highlighter-rouge">newEvent</code>,置换出去的即为<code class="language-plaintext highlighter-rouge">oldEvent</code>。</p>
<p><img src="/images/esper/irstream-win-length.png" alt="" /></p>
<p>实际上这个EPL触发监听器都只能看到<code class="language-plaintext highlighter-rouge">newEvents</code>,看不到<code class="language-plaintext highlighter-rouge">oldEvents</code>。如果想看到<code class="language-plaintext highlighter-rouge">oldEvents</code>,EPL要改写一下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select irstream * from Withdrawal.win:length(5)
</code></pre></div> </div>
<p>默认情况下,Esper认为你只想让<code class="language-plaintext highlighter-rouge">newEvents</code>触发监听器,即<code class="language-plaintext highlighter-rouge">istream</code>(insert stream)。如果想让<code class="language-plaintext highlighter-rouge">oldEvents</code>触发监听器,那么为<code class="language-plaintext highlighter-rouge">rstream</code>(remove stream)。如果两个都想,那么为<code class="language-plaintext highlighter-rouge">irstream</code>。当然这个默认情况是可以配置的,以后会说到这个问题。</p>
<p>当只用<code class="language-plaintext highlighter-rouge">rstream</code>时,过期的<code class="language-plaintext highlighter-rouge">oldEvents</code>是放松到<code class="language-plaintext highlighter-rouge">newEvents</code>中的。</p>
</li>
</ul>
<h3 id="32-filter-and-where-causes">3.2 Filter And Where-causes</h3>
<p>EPL有两种过滤事件的方式:</p>
<ul>
<li>过滤事件进入view(可以把view理解为一个窗口),即Filter。</li>
<li>让事件都进入view,但不触发UpdateListener,即Where子句。</li>
</ul>
<p><i class="red"><strong>Filter:</strong></i></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from Withdrawal(amount>=200).win:length(5)
</code></pre></div></div>
<p>所有<code class="language-plaintext highlighter-rouge">Withdrawal</code>事件中,只有<code class="language-plaintext highlighter-rouge">amount</code>属性值 <code class="language-plaintext highlighter-rouge">>= 200</code>的才可以进入 <code class="language-plaintext highlighter-rouge">win:length</code>,且<code class="language-plaintext highlighter-rouge">win:length</code>大小为 5.</p>
<p><img src="/images/esper/irstream-filter-where.png" alt="" /></p>
<p><i class="red"><strong>Where-causes:</strong></i></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>select * from Withdrawal.win:length(5) where amount >= 200
</code></pre></div></div>
<p>所有<code class="language-plaintext highlighter-rouge">Withdrawal</code>事件中,都可以进入 <code class="language-plaintext highlighter-rouge">win:length</code>,且<code class="language-plaintext highlighter-rouge">win:length</code>大小为 5,只有<code class="language-plaintext highlighter-rouge">amount</code>属性 <code class="language-plaintext highlighter-rouge">>= 200</code> 的才可以触发 <code class="language-plaintext highlighter-rouge">UpdateListener</code>。</p>
<p><img src="/images/esper/irstream-where.png" alt="" /></p>
<h3 id="33-time-windows">3.3 Time Windows</h3>
<ul>
<li>
<p>Time Windows</p>
<p>Time Window 是基于过去系统时间上的,一个移动的指定时间间隔的窗口,Time Window 能够限制一次查询中事件的个数,类似于length window。</p>
<p>例如,要查询所有过去4秒的account中,amount大于1000的withdrawal的平均值,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select account, avg(amount)
from Withdrawal.win:time(4 sec)
group by account
having amount > 1000
select * from Withdrawal.win:time(4 sec)
</code></pre></div> </div>
<p><img src="/images/esper/time-window.png" alt="" /></p>
<ol>
<li>t+4 秒,W1到达并进入window,引擎将之反馈给 update listeners</li>
<li>t+5 秒,W2到达并进入window,引擎将之反馈给 update listeners</li>
<li>t+6.5 秒,W3到达并进入window,引擎将之反馈给 update listeners</li>
<li>t+8 秒,W1离开time window,引擎将之作为一个old event 告知update listeners</li>
</ol>
</li>
<li>
<p>Time Batch</p>
<p>Time Batch视图缓存事件,并在每个指定的时间间隔更新时释放它们,可以理解为批、批处理。length batch也类似。</p>
<p>例如,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select * from Withdrawal.win:time_batch(4 sec)
</code></pre></div> </div>
<p>上述EPL语句可以理解为通过时间分批查询,每一批为4秒钟。</p>
<p><img src="/images/esper/time-batch.png" alt="" /></p>
<ol>
<li>在 t + 1秒,W1 到达并进入batch,不触发调用 update listeners</li>
<li>在 t + 3秒,W2 到达并进入batch,不触发调用 update listeners</li>
<li>在 t + 4秒,引擎处理batch事件,并开始一个新的batch,引擎触发W1和W2给update listeners</li>
<li>在 t + 6.5秒,W3 引擎处理batch事件,并开始一个新的batch,引擎触发W1和W2给update listeners</li>
<li>在 t + 8秒,引擎处理batch事件,并开始一个新的batch,引擎触发W3给update listeners,并将W1和W2作为old data(前一个batch)发给update listeners</li>
</ol>
<p>收集1秒钟之内到达的Withdrawal事件,并在1秒钟结束之后,将之作为new events(insert stream)发送给引擎的每个listener,将上1秒的作为old events(remove stream)发送给每个listener</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select account, amount from Withdrawal.win:time_batch(1 sec)
</code></pre></div> </div>
<p>1秒内所有Withdrawal时间的amount属性和</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select sum(amount) as mysum from Withdrawal.win:time_batch(1 sec)
</code></pre></div> </div>
</li>
</ul>
<h3 id="34-分组聚合">3.4 分组聚合</h3>
<ul>
<li>
<p>IStream</p>
<p>当聚合属性值发生改变的时候,聚合事件语句,能够通过聚合函数传递(post)remove steam(即聚合函数值也能作为触发update listener的条件)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select count(*) as mycount from Withdrawal having count(*) = 2 当接收到 2 个Withdrawal事件时,输出。
select istream count(*) as mycount form Withdrawal having count(*) = 2
</code></pre></div> </div>
<p><code class="language-plaintext highlighter-rouge">istream</code> 或 <code class="language-plaintext highlighter-rouge">rstream</code>关键字能被用来指定传递(post) <code class="language-plaintext highlighter-rouge">new events</code> 或 <code class="language-plaintext highlighter-rouge">old events</code> 给<code class="language-plaintext highlighter-rouge">update listeners</code>。上述语句,表示当且仅当第二个<code class="language-plaintext highlighter-rouge">Withdrawal</code>事件到达时,引擎才会调用<code class="language-plaintext highlighter-rouge">listener</code>;若<code class="language-plaintext highlighter-rouge">istream</code>改为<code class="language-plaintext highlighter-rouge">rstream</code>,则仅当第三个<code class="language-plaintext highlighter-rouge">Withdrawal</code>事件到达时,引擎才会调用<code class="language-plaintext highlighter-rouge">listener</code>。</p>
</li>
<li>
<p>IR Stream</p>
<p>监听器有两个参数 newEvents 和 oldEvents,newEvents 表示通常的计算结果,oldEvents 可以理解为上一次计算结果。默认情况下,newEvents 有值,oldEvnets 为null。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select rstream * from Withdrawal 上述结果会将上一次的计算结果放入到 newEvents,而不是 oldEvents,且无法获取当前计算结果!
select irstream * from Withdrawal 会将当前计算结果放入 newEvents,上次计算结果放入到 oldEvents。
select istream * from Withdrawal
select * from Withdrawal 会将当前计算结果放入newEvents,且无法得到上次计算结果,默认设置。
</code></pre></div> </div>
</li>
<li>
<p>Aggregate and Group</p>
<p>不聚合、不分组</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> select * from Withdrawal.win:time_batch(1 sec) 只有聚合,没有分组
select sum(amount)
from Withdrawal.win:time_batch(1 sec) 非聚合属性、聚合属性,当不分组
select account, sum(amount)
from Withdrawlal.win:time_batch(1 sec) 查询语句中的聚合属性、所有非聚合属性,都被group by语句列出。
select account, sum(amount)
from Withdrawal.win:time_batch(1 sec)
group by account 查询 非聚合属性和聚合属性,仅用group by分组了部分属性。
select account, accountName, sum(amount)
from Withdrawal.win:time_batch(1 sec)
group by account
</code></pre></div> </div>
</li>
</ul>
<p><a id="link4"></a></p>
<h3 id="未完待续">未完待续…</h3>
Docker 入门简介
2016-12-10T00:00:00+00:00
http://www.blogways.net/blog/2016/12/10/docker-introduction
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#link1">Docker简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#link2">Docker安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#link3">端口映射、网络</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#link4">自定义容器</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#link5">容器数据卷管理</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#link6">其它</a></td>
</tr>
</tbody>
</table>
<p><a id="link1"></a></p>
<style>
.red { color: red; }
.blue { color: blue; }
</style>
<h2 id="一docker简介">一、Docker简介</h2>
<p>Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的 <code class="language-plaintext highlighter-rouge">Linux</code>、<code class="language-plaintext highlighter-rouge">Windows</code>机器上,也可以实现虚拟化。</p>
<h3 id="11-什么是-docker">1.1 什么是 Docker</h3>
<p><code class="language-plaintext highlighter-rouge">Docker</code>使用<strong>客户端-服务器</strong> (C/S) 架构模式。<code class="language-plaintext highlighter-rouge">Docker客户端</code>会与<code class="language-plaintext highlighter-rouge">Docker守护进程</code>进行通信。<code class="language-plaintext highlighter-rouge">Docker</code> 守护进程会处理复杂繁重的任务,例如建立、运行、发布你的<code class="language-plaintext highlighter-rouge">Docker</code>容器。<code class="language-plaintext highlighter-rouge">Docker</code>客户端和守护进程可以运行在同一个系统上,当然你也可以使用<code class="language-plaintext highlighter-rouge">Docker</code>客户端去连接一个远程的<code class="language-plaintext highlighter-rouge">Docker</code>守护进程。<code class="language-plaintext highlighter-rouge">Docker</code>客户端和守护进程之间通过 <code class="language-plaintext highlighter-rouge">socket</code> 或者 <code class="language-plaintext highlighter-rouge">RESTful API</code> 进行通信。</p>
<p>首先,<code class="language-plaintext highlighter-rouge">Docker</code> 容器的启动可以在 <span class="red"><strong><em>秒级</em></strong></span> 实现,这相比传统的虚拟机方式要快得多。 其次,<code class="language-plaintext highlighter-rouge">Docker</code> 对系统资源的<span class="red"><strong><em>利用率很高</em></strong></span>,一台主机上可以同时运行数千个 <code class="language-plaintext highlighter-rouge">Docker</code> 容器。
容器除了运行其中应用外,基本不消耗额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而<code class="language-plaintext highlighter-rouge">Docker</code> 只需要启动 10 个隔离的应用即可。</p>
<h3 id="12-与传统虚拟化的差异">1.2 与传统虚拟化的差异</h3>
<p>容器是在 <strong><em>操作系统层面</em></strong> 上实现虚拟化,直接<span class="red"><strong><em>复用</em></strong></span>本地主机的操作系统,而传统方式则是在<span class="red"><strong><em>硬件层面</em></strong></span>实现。</p>
<p><img src="/images/docker/vm-arch.png" alt="" /></p>
<p>对于传统的虚拟机来说,每个虚拟的应用包括不仅仅一个应用程序(可能只有数十<code class="language-plaintext highlighter-rouge">MB</code>),还需要<code class="language-plaintext highlighter-rouge">bins</code>和<code class="language-plaintext highlighter-rouge">libs</code>,但是一个完整的操作系统可能有数十<code class="language-plaintext highlighter-rouge">GB</code>。</p>
<p><img src="/images/docker/docker-arch.png" alt="" /></p>
<p>而<code class="language-plaintext highlighter-rouge">Docker Engine</code>仅包括应用程序和它的依赖。在宿主机操作系统上,用户空间,独立的运行,和其它容器分享内核。但是有更高的可移植性和高效性。</p>
<h3 id="13-为什么使用docker">1.3 为什么使用Docker</h3>
<p>前面简单介绍了下<code class="language-plaintext highlighter-rouge">Docker</code>,说了很多<code class="language-plaintext highlighter-rouge">Docker</code>的相关概念,可是为什么我们要使用它呢?它给我们带来了什么具体的好处呢?</p>
<ul>
<li>
<p>首先,前面也说了<code class="language-plaintext highlighter-rouge">Docker</code> 容器的启动可以在<strong><em>秒级</em></strong>实现,这相比传统的虚拟机方式要快得多。</p>
</li>
<li>
<p>其次,Docker 对系统资源的利用率很高,一台主机上可以同时运行 <strong class="red">数千个</strong><code class="language-plaintext highlighter-rouge">Docker</code> 容器。</p>
<p>容器除了运行其中应用外,<strong class="red">基本不消耗</strong> 额外的系统资源,使得应用的性能很高,同时系统的开销尽量小。传统虚拟机方式运行 10 个不同的应用就要起 10 个虚拟机,而<code class="language-plaintext highlighter-rouge">Docker</code> 只需要启动 10 个隔离的应用即可。</p>
</li>
<li>
<p>快速交付和部署</p>
<p>对开发和运维人员来说,最希望的就是一次创建或配置,可以在任意地方正常运行。</p>
<p>开发者可以使用一个标准镜像来构建一套开发容器,开发完成之后,运维人员可以直接使用这个容器来部署代码。</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 可以快速创建容器,<strong><em>快速迭代</em></strong>应用程序,并让整个过程全程可见,使团队中的其他成员更容易理解应用程序是如何创建和工作的。</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 容器很轻很快!容器的启动时间是秒级的,<strong><em>大量</em></strong> 地节约开发、测试、部署的时间。</p>
</li>
<li>
<p>高效虚拟化、迁移、扩展</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 容器的运行不需要额外的 <code class="language-plaintext highlighter-rouge">hypervisor</code> 支持,它是内核级的虚拟化,因此可以实现更高的性能和效率。</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 容器几乎可以在任意的平台上运行,包括物理机、虚拟机、公有云、私有云、个人电脑、服务器等。 这种兼容性可以让用户把一个应用程序从一个平台直接迁移到另外一个。</p>
<p>使用 <code class="language-plaintext highlighter-rouge">Docker</code>,只需要小小的修改,就可以替代以往大量的更新工作。所有的修改都以增量的方式被分发和更新,从而实现自动化并且高效的管理。</p>
</li>
<li>
<p>对比传统虚拟机</p>
<p><img src="/images/docker/compare.png" alt="" /></p>
</li>
</ul>
<h3 id="14-docker-镜像">1.4 Docker 镜像</h3>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 容器运行时的<i class="red"><strong>只读模板</strong></i>,每一个镜像由一系列的层 (layers) 组成,最底层是<code class="language-plaintext highlighter-rouge">bootfs</code>,接着是<code class="language-plaintext highlighter-rouge">rootfs</code>。<code class="language-plaintext highlighter-rouge">Docker</code> 使用 <code class="language-plaintext highlighter-rouge">UnionFS</code> 来将这些层联合到单独的镜像中。</p>
<p><code class="language-plaintext highlighter-rouge">UnionFS</code> 允许独立文件系统中的文件和文件夹(称之为分支)被<strong><em>透明覆盖</em></strong>,形成一个单独连贯的文件系统。</p>
<p><img src="/images/docker/docker-images-fs.png" alt="" /></p>
<p><code class="language-plaintext highlighter-rouge">bootfs</code>是<code class="language-plaintext highlighter-rouge">Docker</code>镜像最底层的 <strong><em>引导文件</em></strong> 系统,包括<code class="language-plaintext highlighter-rouge">bootloader</code>和<code class="language-plaintext highlighter-rouge">操作系统的内核</code>。在容器启动完毕之后,为了节省内存空间,<code class="language-plaintext highlighter-rouge">bootfs</code>会被卸载。</p>
<p><code class="language-plaintext highlighter-rouge">rootfs</code>位于<code class="language-plaintext highlighter-rouge">bootfs</code>之上,是<code class="language-plaintext highlighter-rouge">Docker</code>容器启动时内部进程课件的文件系统,即<code class="language-plaintext highlighter-rouge">Docker</code>容器的根目录。通常包含一个操作系统运行所需的文件系统。如<code class="language-plaintext highlighter-rouge">/dev</code> <code class="language-plaintext highlighter-rouge">/proc</code> <code class="language-plaintext highlighter-rouge">/bin</code> <code class="language-plaintext highlighter-rouge">/etc</code> <code class="language-plaintext highlighter-rouge">/lib</code> <code class="language-plaintext highlighter-rouge">/usr</code> <code class="language-plaintext highlighter-rouge">/tmp</code> 等。</p>
<p>将<code class="language-plaintext highlighter-rouge">rootfs</code>设为只读模式,在挂载完成之后,利用<code class="language-plaintext highlighter-rouge">UnionFS</code>,在<code class="language-plaintext highlighter-rouge">rootfs</code>上方创建一个<i class="red"><strong>读写层</strong></i>,只有在<code class="language-plaintext highlighter-rouge">Docke</code>容器运行过程中文件系统发生改变,才会把变化的文件内容写到可读可写层,并隐藏只读成的老版本文件(写时复制)。</p>
<p>当修改镜像内的某个文件时,只对处于最上方的 <strong><em>读写层</em></strong> 进行了变动,不覆盖下层已有的文件系统的内容,已有文件在只读层的原始版本仍然存在,但会被读写层的新版本文件隐藏,当<code class="language-plaintext highlighter-rouge">docker commit</code>这个修改过的容器文件系统为一个新的镜像时,保存的内容仅为上层读写文件系统中被更新过的文件。</p>
<h3 id="15-docker-仓库容器">1.5 Docker 仓库、容器</h3>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 仓库用来保存镜像,可以理解为代码控制中的代码仓库。</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 仓库有公有和私有的概念。公有的 <code class="language-plaintext highlighter-rouge">Docker</code> 仓库名字是 <code class="language-plaintext highlighter-rouge">Docker Hub</code>。</p>
<p>容器是从镜像创建的运行实例。它可以被启动、开始、停止、删除。每个容器都是相互隔离的、保证安全的平台。</p>
<p>可以把容器看做是一个 <i class="red"><strong>简易版</strong></i> 的 <code class="language-plaintext highlighter-rouge">Linux</code> 环境(包括<code class="language-plaintext highlighter-rouge">root</code>用户权限、进程空间、用户空间和网络空间等)和运行在其中的应用程序。</p>
<h3 id="16-命名空间">1.6 命名空间</h3>
<p><code class="language-plaintext highlighter-rouge">Docker</code>通过以下 <strong><em>6</em></strong> 种 <code class="language-plaintext highlighter-rouge">namespace</code> 从进程、网络、IPC、文件系统、UTS 和用户角度的隔离,一个 container 就可以对外展现出一个独立计算机的能力,并且不同 container 从 OS 层面实现了隔离。 然而不同 namespace 之间资源还是相互竞争的,仍然需要类似 ulimit 来管理每个 container 所能使用的资源 - <code class="language-plaintext highlighter-rouge">cgroup</code>。</p>
<h4 id="161-pid-namespace">1.6.1 pid namespace</h4>
<p>不同用户的进程就是通过 <code class="language-plaintext highlighter-rouge">pid namespace</code> 隔离开的,且不同 <code class="language-plaintext highlighter-rouge">namespace</code> 中可以有相同 PID。具有以下特征:</p>
<ul>
<li>每个 <code class="language-plaintext highlighter-rouge">namespace</code> 中的 pid 是有各自的 pid=1 的进程(类似 <code class="language-plaintext highlighter-rouge">/sbin/init</code> 进程)</li>
<li>每个 <code class="language-plaintext highlighter-rouge">namespace</code> 中的进程只能影响自己的同一个 <code class="language-plaintext highlighter-rouge">namespace</code> 或子 <code class="language-plaintext highlighter-rouge">namespace</code> 中的进程</li>
<li>因为 <code class="language-plaintext highlighter-rouge">/proc</code> 包含正在运行的进程,因此在 <code class="language-plaintext highlighter-rouge">container</code> 中的 <code class="language-plaintext highlighter-rouge">pseudo-filesystem</code> 的 <code class="language-plaintext highlighter-rouge">/proc</code> 目录只能看到自己 <code class="language-plaintext highlighter-rouge">namespace</code> 中的进程</li>
<li>因为 <code class="language-plaintext highlighter-rouge">namespace</code> 允许嵌套,父 <code class="language-plaintext highlighter-rouge">namespace</code> 可以影响子 <code class="language-plaintext highlighter-rouge">namespace</code> 的进程,所以子 <code class="language-plaintext highlighter-rouge">namespace</code> 的进程可以在父 <code class="language-plaintext highlighter-rouge">namespace</code> 中看到,但是具有不同的 pid</li>
</ul>
<h4 id="162-mnt-namespace">1.6.2 mnt namespace</h4>
<p>类似<code class="language-plaintext highlighter-rouge">chroot</code>,将一个进程放到一个特定的目录执行。</p>
<p><code class="language-plaintext highlighter-rouge">mnt namespace</code> 允许不同 <code class="language-plaintext highlighter-rouge">namespace</code>的进程看到的文件结构不同,这样每个 <code class="language-plaintext highlighter-rouge">namespace</code>中的进程说看到的文件目录就被隔离开了。</p>
<p>同<code class="language-plaintext highlighter-rouge">chroot</code>不同,每个 <code class="language-plaintext highlighter-rouge">namespace</code> 中的容器在<code class="language-plaintext highlighter-rouge">/proc/mounts</code> 的信息只包含所在 <code class="language-plaintext highlighter-rouge">namespace</code> 的 <code class="language-plaintext highlighter-rouge">mount point</code>。</p>
<h4 id="163-net-namespace">1.6.3 net namespace</h4>
<p>网络隔离是通过<code class="language-plaintext highlighter-rouge">net namespace</code> 实现的,内有<code class="language-plaintext highlighter-rouge">net namespace</code> 有独立的network devices,IP addresses,IP routeing tables, <code class="language-plaintext highlighter-rouge">/proc/net</code> 目录,这样每个容器的网络就能隔离开来。</p>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 默认采用<code class="language-plaintext highlighter-rouge">veth</code> 的方式将容器中的虚拟网卡同 宿主机host 上的一个<code class="language-plaintext highlighter-rouge">docker bridge</code> 连接在一起。</p>
<h4 id="164-utsipc-namespace">1.6.4 uts/ipc namespace</h4>
<p><code class="language-plaintext highlighter-rouge">UTS("Unix Time-sharding System") namespace</code> 允许每个 container 拥有独立的 <code class="language-plaintext highlighter-rouge">hostname</code> 和 <code class="language-plaintext highlighter-rouge">domain name</code>,使其在网络上可以被视作一个独立的节点,而非宿主机上的一个进程。</p>
<p>container 中进程交互采用 <code class="language-plaintext highlighter-rouge">Linux</code> 常见的进程间交互方法 (interprocess communication - IPC), 包括常见的信号量、消息队列和共享内存。然而同 VM 不同,container 的进程间交互实际上还是宿主机上具有相同 pid namespace 中的进程间交互,因此需要在IPC资源申请时加入 namespace 信息 - 每个 IPC 资源有一个唯一的 32bit ID。</p>
<h4 id="165-user-namespace">1.6.5 user namespace</h4>
<p>每个 container 可以有不同的 user 和 group id, 也就是说可以以 container 内部的用户在 container 内部执行程序而非 Host 上的用户。</p>
<p><a id="link2"></a></p>
<h2 id="二docker-安装">二、Docker 安装</h2>
<h3 id="21-centos--red-hat-enterprice-linux">2.1 CentOS / Red Hat Enterprice Linux</h3>
<p>要求是<code class="language-plaintext highlighter-rouge">64</code>位操作系统,内核版本<code class="language-plaintext highlighter-rouge">>=3.0</code>。</p>
<ul>
<li>
<p>添加<code class="language-plaintext highlighter-rouge">Docker</code>的<code class="language-plaintext highlighter-rouge">yum</code>源:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo tee /etc/yum.repos.d/docker.repo <<-'EOF'
[dockerrepo]
name=Docker Repository
baseurl=https://yum.dockerproject.org/repo/main/centos/$releasever/
enabled=1
gpgcheck=1
gpgkey=https://yum.dockerproject.org/gpg
EOF 具体可以参考:
https://docs.docker.com/engine/installation/linux/centos/
</code></pre></div> </div>
</li>
<li>
<p>安装命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> yum install docker-engine -y
</code></pre></div> </div>
</li>
<li>
<p>设置开机启动:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl enable docker
</code></pre></div> </div>
</li>
<li>
<p>启动配置文件路径</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /usr/lib/systemd/system/docker.service
[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network.target docker.socket
Requires=docker.socket
[Service]
Type=notify
ExecStart=/usr/bin/docker daemon -H 0.0.0.0:2375 -H unix:///var/run/docker.sock
MountFlags=slave
LimitNOFILE=1048576
LimitNPROC=1048576
LimitCORE=infinity
TimeoutStartSec=0
[Install]
WantedBy=multi-user.target
</code></pre></div> </div>
</li>
<li>
<p>加载</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl daemon-reload
</code></pre></div> </div>
</li>
<li>
<p>重启<code class="language-plaintext highlighter-rouge">docker</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> systemctl restart docker
</code></pre></div> </div>
</li>
<li>
<p>免 <code class="language-plaintext highlighter-rouge">sudo</code> 使用<code class="language-plaintext highlighter-rouge">docker</code>(没权限访问 <code class="language-plaintext highlighter-rouge">/var/run/docker.sock</code>)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo groupadd docker
sudo gpasswd -a ${USER} docker
sudo service docker restart
</code></pre></div> </div>
</li>
</ul>
<p><i class="red"><strong>注意:</strong></i></p>
<ul>
<li>
<p>在安装的过程中,可能会提示有一个已安装的包冲突,先把冲突的卸载,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> yum remove lvm-2.02
yum install docker-engine –y
yum install lvm-2.02 -y
</code></pre></div> </div>
</li>
</ul>
<h3 id="22-mac-osx--windows">2.2 Mac OSX / Windows</h3>
<p>下载安装即可,或者通过<code class="language-plaintext highlighter-rouge">docker-machine</code>安装,后续会有介绍,下载链接:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://www.docker.com/products/docker-toolbox
</code></pre></div></div>
<h3 id="23-centos--red-hat-enterprice-linux-测试">2.3 CentOS / Red Hat Enterprice Linux 测试</h3>
<ul>
<li>
<p>运行<code class="language-plaintext highlighter-rouge">hello-world</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker run hello-world
</code></pre></div> </div>
</li>
<li>
<p>搜索镜像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker search centos
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">pull</code>镜像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # [:tag]指定pull的版本,不指定默认为latest
docker pull centos[:7]
# 查看当前本地镜像列表
docker images
</code></pre></div> </div>
</li>
<li>
<p>交互式运行容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker run --name test -i -t -d centos /bin/bash
</code></pre></div> </div>
</li>
<li>
<p>查看当前运行的程序</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker ps
# 查看所有容器
docker ps -a
</code></pre></div> </div>
</li>
<li>
<p>连接到运行的容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker exec -it test /bin/bash
# or
docker attach test
# 或容器的container id,退出 Ctrl +P,Ctrl +Q(exit或Ctrl+D退出容器) ### 2.4 常用命令介绍
</code></pre></div> </div>
</li>
<li>
<p>查看所有<code class="language-plaintext highlighter-rouge">docker</code>命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker help
</code></pre></div> </div>
</li>
<li>
<p>查看启动<code class="language-plaintext highlighter-rouge">docker server</code>的参数</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker daemon --help
</code></pre></div> </div>
</li>
<li>
<p>查看某个具体命令的介绍</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker <command> --help
如: docker search --help
</code></pre></div> </div>
</li>
<li>
<p>pull 拉取镜像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker pull --help
</code></pre></div> </div>
</li>
<li>
<p>push 推送指定镜像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker push --help
</code></pre></div> </div>
</li>
<li>
<p>查看所有镜像列表</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker images --help
</code></pre></div> </div>
</li>
<li>
<p>移除一个或多个镜像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker rmi --help
</code></pre></div> </div>
</li>
<li>
<p>启动容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker run --help
</code></pre></div> </div>
</li>
<li>
<p>创建容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker create --help
</code></pre></div> </div>
</li>
<li>
<p>启动容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker start --help
</code></pre></div> </div>
</li>
<li>
<p>停止容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker stop --help
</code></pre></div> </div>
</li>
<li>
<p>kill 容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker kill --help
</code></pre></div> </div>
</li>
<li>
<p>删除容器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> docker rm --help
</code></pre></div> </div>
</li>
</ul>
<p><a id="link3"></a></p>
<h2 id="三端口映射网络">三、端口映射、网络</h2>
<p>顾名思义,就是将容器内部服务的端口,映射到宿主机上,以便在宿主机、外部网络能访问容器内部发布的服务。映射的方式有两种:自动映射和绑定映射端口。</p>
<p>自动映射:在容器启动的时候,通过指定 <code class="language-plaintext highlighter-rouge">-P</code> 参数( <code class="language-plaintext highlighter-rouge">docker run -P</code> ),自动绑定所有对外提供服务的容器端口,映射的端口会在没有使用的端口池中(<code class="language-plaintext highlighter-rouge">49000-49900</code>)自动选择。</p>
<p>绑定映射:跟自动映射的区别在于,可以指定映射到宿主主机上的端口号,其基本语法如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -p [([<host_interface>:[host_port]])|(<host_port>):]<container_port>[/udp] <image> <cmd>
</code></pre></div></div>
<p><i class="red"><strong>注意:</strong></i></p>
<ul>
<li>默认不指定绑定ip,则监听所有网络端口。</li>
<li><code class="language-plaintext highlighter-rouge">-P</code> 使用时需要指定<code class="language-plaintext highlighter-rouge">--expose</code> 选项,用于指定需要对外监听的端口。</li>
<li>可以通过<code class="language-plaintext highlighter-rouge">docker ps</code>、<code class="language-plaintext highlighter-rouge">docker inspect <contain id></code> 或<code class="language-plaintext highlighter-rouge">docker port <contain id> <port></code>等确定绑定信息。</li>
</ul>
<h3 id="31-四种网络模式">3.1 四种网络模式</h3>
<p>在通过 <code class="language-plaintext highlighter-rouge">docker run</code> 命令创建并启动一个容器的时候,可以通过 <code class="language-plaintext highlighter-rouge">--net</code> 选项指定容器的网络模式,可选网络模式:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">host</code>模式,使用 <code class="language-plaintext highlighter-rouge">--net=host</code> 指定。</li>
<li><code class="language-plaintext highlighter-rouge">container</code>模式,使用 <code class="language-plaintext highlighter-rouge">--net=container:name or id</code>指定。</li>
<li><code class="language-plaintext highlighter-rouge">none</code>模式,使用 <code class="language-plaintext highlighter-rouge">--net=none</code> 指定。</li>
<li><code class="language-plaintext highlighter-rouge">bridge</code>模式,使用 <code class="language-plaintext highlighter-rouge">--net=bridge</code> 指定。</li>
</ul>
<p>不指定,默认使用的是<code class="language-plaintext highlighter-rouge">bridge</code>模式(桥接)。</p>
<h4 id="311-host-模式">3.1.1 host 模式</h4>
<p>如果容器启动的时候指定了 <code class="language-plaintext highlighter-rouge">host</code> 模式,那么容器不会获取一个独立的 <code class="language-plaintext highlighter-rouge">network namespace</code>,而是和宿主机 <i class="red"><strong>共用</strong></i> 一个 <code class="language-plaintext highlighter-rouge">network namespace</code>。且不会虚拟自己的网卡、ip等,而是使用宿主机的 ip 和端口。</p>
<p>形象一点描述,在 <code class="language-plaintext highlighter-rouge">10.211.55.2/24</code> 主机上用 host 模式启动了一个容器,对外监听 <code class="language-plaintext highlighter-rouge">27017</code> 端口。此时若在容器中用 <code class="language-plaintext highlighter-rouge">ifconfig</code>、<code class="language-plaintext highlighter-rouge">ip addr</code> 等查看网络信息的命令查看,看到的是宿主机的网络信息。而外界可以直接通过 <code class="language-plaintext highlighter-rouge">10.211.55.2:27017</code>访问即可,不用进行任何 <code class="language-plaintext highlighter-rouge">NAT</code> 转换,对于其他应用或人而言,就像一个 <strong><em>直接运行在宿主机上的进程</em></strong>。</p>
<p>相对的,容器的文件系统,进程等还是跟宿主机 <strong><em>隔离</em></strong> 的。</p>
<h4 id="312-container-模式">3.1.2 container 模式</h4>
<p>指定此模式创建启动容器,则新创建的容器会和指定的容器共享同一个 <code class="language-plaintext highlighter-rouge">network namespace</code>,新创建的容器不会配置自己的网络环境、ip端口等,而是和指定的容器共享同一套ip、端口池等。</p>
<p>跟host模式一样,两个容器的文件系统、进程列表等还是相互 <strong><em>隔离</em></strong> 的,两个进程和通过 <code class="language-plaintext highlighter-rouge">lo</code> 网卡进行通信,<code class="language-plaintext highlighter-rouge">127.0.0.1</code>、<code class="language-plaintext highlighter-rouge">0.0.0.0</code>。</p>
<h4 id="313-none-模式">3.1.3 none 模式</h4>
<p>此种模式下,容器拥有自己独立的 <code class="language-plaintext highlighter-rouge">network namespace</code>,但是并不会创建任何网络配置,也就是说该容器并没有网卡、ip、路由等。</p>
<p>也就是说,如果有网络配置要求,需要 <i class="red"><strong>人为</strong></i> 去进行配置。</p>
<h4 id="314-bridge-模式">3.1.4 bridge 模式</h4>
<p><code class="language-plaintext highlighter-rouge">docker</code> 默认的网络模式,此模式为每个容器分配<code class="language-plaintext highlighter-rouge">network namespace</code>、设置ip等,并将所有的容器通过 <code class="language-plaintext highlighter-rouge">veth</code> 连接到宿主机的虚拟网桥上 <code class="language-plaintext highlighter-rouge">docker0</code>(<code class="language-plaintext highlighter-rouge">docker daemon</code> 启动时,会自动创建该网桥)。</p>
<p>虚拟网桥的工作方式跟物理交换机类似,这样所有宿主机上的容器都通过交换机连接在一个二层网络中。</p>
<p>在创建 <code class="language-plaintext highlighter-rouge">docker0</code> 网桥时,会自动为其分配一个和宿主机不同的ip地址和子网,连接到 <code class="language-plaintext highlighter-rouge">docker0</code> 的容器就从其子网内选择一个未被使用的ip分配给容器。</p>
<p><code class="language-plaintext highlighter-rouge">docker</code> 一般会使用<code class="language-plaintext highlighter-rouge">172.17.0.0/16</code>这个网段,并将<code class="language-plaintext highlighter-rouge">172.17.42.1/16</code>分配给<code class="language-plaintext highlighter-rouge">docker0</code>网桥。也可以自己创建一个网桥,为其分配指定的网段,然后通过 <code class="language-plaintext highlighter-rouge">docker daemon</code> 的 <code class="language-plaintext highlighter-rouge">-b</code> 参数指定为<code class="language-plaintext highlighter-rouge">docker</code>默认的网桥。</p>
<p><img src="/images/docker/bridge.png" alt="" /></p>
<h3 id="32-跨主机容器互联">3.2 跨主机容器互联</h3>
<p>可行的方式:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">kubernetes</code> 集群</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">swarm</code> 集群 + <code class="language-plaintext highlighter-rouge">overlay</code></p>
</li>
<li>
<p>直接路由 – 要求所有主机</p>
</li>
</ul>
<h2 id="四自定义容器">四、自定义容器</h2>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 可以通过 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 的内容来自动构建镜像,<code class="language-plaintext highlighter-rouge">Dockerfile</code>是一个包含创建镜像所有命令的 <strong><em>文本文件</em></strong>,通过 <code class="language-plaintext highlighter-rouge">docker build</code> 可以根据编写的 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 来构建镜像。</p>
<p><code class="language-plaintext highlighter-rouge">Dockerfile</code> 的指令选项:</p>
<ul>
<li>FROM</li>
<li>MAINTAINER</li>
<li>RUN</li>
<li>CMD</li>
<li>EXPOSE</li>
<li>ENV</li>
<li>ADD</li>
<li>COPY</li>
<li>ENTRYPOINT</li>
<li>VOLUME</li>
<li>USER</li>
<li>WORKDIR</li>
<li>ONBUILD</li>
</ul>
<h3 id="41-from">4.1 FROM</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM <image>[:tag] <i class='blue'>说明:</i>
</code></pre></div></div>
<p>指定构建镜像的基础镜像,一般来说就是提供运行环境的基础镜像(<code class="language-plaintext highlighter-rouge">Linux</code>环境、<code class="language-plaintext highlighter-rouge">python</code>运行环境等),若本地不存在,这自动去共有仓库<code class="language-plaintext highlighter-rouge">pull</code>。</p>
<p><i class="red">注意:</i></p>
<ul>
<li><code class="language-plaintext highlighter-rouge">FROM</code> 必须是 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 中非注释的第一个命令,即 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 中<code class="language-plaintext highlighter-rouge">FROM</code> 开始。</li>
<li><code class="language-plaintext highlighter-rouge">FROM</code> 可以出现多次,比如说需要构建多个镜像。</li>
<li>如果没有指定 <code class="language-plaintext highlighter-rouge">[tag]</code>,则默认使用 <code class="language-plaintext highlighter-rouge">latest</code>作为<code class="language-plaintext highlighter-rouge">tag</code>。</li>
</ul>
<h3 id="42-maintainer">4.2 MAINTAINER</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MAINTAINER <name> <i class='blue'>说明:</i>
</code></pre></div></div>
<p>指定创建镜像的用户。</p>
<h3 id="43-run">4.3 RUN</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RUN <cmd> 命令会在一个shell中运行 /bin/sh -c
RUN ["cmd", "param1", "param2"]
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>每条命令会在当前镜像上执行,并提交为新的镜像,后续的RUN 会在前面 RUN 提交的镜像为基础,镜像是分层的(UnionFS)。</p>
<p><i class="red">注意:</i></p>
<ul>
<li>
<p>第二种方式会被解析为一个 <code class="language-plaintext highlighter-rouge">JSON</code> 数组,必须使用双引号,且不会调用一个命令 <code class="language-plaintext highlighter-rouge">shell</code>,所以也就不会继承相应的变量,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> RUN ["echo", "$HOME"] 上述命令不会输出 `HOME` 变量,正确的应该,
RUN ["sh", "-c", "echo", "$HOME"]
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">RUN</code> 命令产生的缓存在下一次构建时并不会失效,会被重用,可以使用 <code class="language-plaintext highlighter-rouge">--no-cache</code> 禁用。</p>
</li>
</ul>
<h3 id="44-cmd">4.4 CMD</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CMD ["executable", "param1", "param2"] 优先选择
CMD ["param1", "param2"] 作为 ENTRYPOINT 的参数
CMD command param1 param2
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>为在容器启动的时候,提供一个默认的命令执行选项。如果用户启动容器的时候指定了运行的命令,这会覆盖 <code class="language-plaintext highlighter-rouge">CMD</code> 指定的命令。</p>
<p><i class="red">注意:</i></p>
<ul>
<li><code class="language-plaintext highlighter-rouge">CMD</code> 在 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 中能使用一次,如果使用多个,只有最后一个生效。</li>
</ul>
<p>与<code class="language-plaintext highlighter-rouge">RUN</code>的差异:</p>
<p><code class="language-plaintext highlighter-rouge">CMD</code>会在容器启动的时候执行,在容器构建的时候不执行,而RUN在容器构建时运行,待容器构建完成之后,后续的所有操作均与<code class="language-plaintext highlighter-rouge">RUN</code>无关。</p>
<h3 id="45-expose">4.5 EXPOSE</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>EXPOSE <port> [<port> …] <i class='blue'>说明:</i>
</code></pre></div></div>
<p>告诉 <code class="language-plaintext highlighter-rouge">Docker</code> 服务端容器对外映射的端口号,需要在容器运行的时候,通过 <code class="language-plaintext highlighter-rouge">docker run -p</code> 或 <code class="language-plaintext highlighter-rouge">-P</code> 对外映射生效。</p>
<h3 id="46-env">4.6 ENV</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENV <key> <value> # 只能设置一个变量
ENV <key>=<value> … # 可以设置多个变量 <i class='blue'>说明:</i>
</code></pre></div></div>
<p>指定一个或多个环境变量,会被后续的 <code class="language-plaintext highlighter-rouge">RUN</code> 命令所使用,并在容器运行时保留。</p>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENV env1=test1 env2=test2
等同于
ENV env1 test1
ENV env2 test2
</code></pre></div></div>
<h3 id="47-add">4.7 ADD</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ADD <src> … <dest> <i class='blue'>说明:</i>
</code></pre></div></div>
<p>复制本地主机文件、目录或者远程文件 <code class="language-plaintext highlighter-rouge">URLS</code> 从 <code class="language-plaintext highlighter-rouge"><src></code> 并且添加到容器指定路径中 <code class="language-plaintext highlighter-rouge"><dest></code>。<code class="language-plaintext highlighter-rouge"><src></code> 支持通过 <code class="language-plaintext highlighter-rouge">GO</code> 的正则模糊匹配,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ADD hom* /mydir/
ADD hom?.txt /mydir/ <i class='red'>注意:</i>
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge"><dest></code> 路径必须是绝对路径,如果 <code class="language-plaintext highlighter-rouge"><dest></code> 不存在,会自动创建对应目录</li>
<li><code class="language-plaintext highlighter-rouge"><src></code> 路径必须是 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 所在路径的相对路径</li>
<li><code class="language-plaintext highlighter-rouge"><src></code> 如果是一个目录,只会复制目录下的内容,而目录本身则不会被复制</li>
</ul>
<h3 id="48-copy">4.8 COPY</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>COPY <src> … <dest>
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p><code class="language-plaintext highlighter-rouge">COPY</code> 复制新文件或者目录从 <src> 添加到容器指定路径中 <dest>。</dest></src></p>
<p><i class="red">注意:</i></p>
<p>用法同 <code class="language-plaintext highlighter-rouge">ADD</code>,与 <code class="language-plaintext highlighter-rouge">ADD</code> 的区别是,<code class="language-plaintext highlighter-rouge">COPY</code> 不能指定远程文件。</p>
<h3 id="49-entrypoint">4.9 ENTRYPOINT</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>创建容器启动后执行的命令,并且不可以被 <code class="language-plaintext highlighter-rouge">docker run</code>提供的参数覆盖,如需替换,可以使用 <code class="language-plaintext highlighter-rouge">docker run --entrypoint</code>选项。</p>
<p><i class="red">注意:</i></p>
<ul>
<li>每个<code class="language-plaintext highlighter-rouge">Dockerfile</code> 只能有一个<code class="language-plaintext highlighter-rouge">ENTRYPOINT</code>,如指定多个,只有最后一个生效。</li>
</ul>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM centos:7
ENTRYPOINT [“top”, “-b”]
CMD [“-c”] 使用 `exec form`来指定默认启动命令,通过`CMD`添加默认启动命令之外,经常被更改的参数项。
FROM centos:7
ENTRYPOINT exec top -b
</code></pre></div></div>
<p>这种方式会在 <code class="language-plaintext highlighter-rouge">/bin/sh -c</code> 中执行,会忽略任何 <code class="language-plaintext highlighter-rouge">CMD</code> 或者 <code class="language-plaintext highlighter-rouge">docker run</code> 命令行选项,为了确保 <code class="language-plaintext highlighter-rouge">docker stop</code> 能够停止长时间运行 <code class="language-plaintext highlighter-rouge">ENTRYPOINT</code> 的容器,确保执行的时候使用 <code class="language-plaintext highlighter-rouge">exec</code> 选项.</p>
<h3 id="410-volume">4.10 VOLUME</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VOLUME [“/data”]
VOLUME /data/db /data/logs <i class='blue'>说明:</i>
</code></pre></div></div>
<p>创建一个可以从本地主机或其他容器挂载的挂载点</p>
<h3 id="411-user">4.11 USER</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>USER daemon
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>指定运行容器时的用户名或 UID,后续的 <code class="language-plaintext highlighter-rouge">RUN</code>、<code class="language-plaintext highlighter-rouge">CMD</code>、<code class="language-plaintext highlighter-rouge">ENTRYPOINT</code> 也会使用指定用户。</p>
<h3 id="412-workdir">4.12 WORKDIR</h3>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WORKDIR /path/to/your/dir
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>为后续的 <code class="language-plaintext highlighter-rouge">RUN</code>、<code class="language-plaintext highlighter-rouge">CMD</code>、<code class="language-plaintext highlighter-rouge">ENTRYPOINT</code> 指令配置工作目录。可以使用多个 <code class="language-plaintext highlighter-rouge">WORKDIR</code> 指令,后续命令如果参数是相对路径,则会基于之前命令指定的路径。</p>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>WORKDIR /a
WORKDIR b
WORKDIR c
最后的路径为 /a/b/c ### 4.13 ONBUILD 用法:
ONBUILD [INSTRUCTION]
</code></pre></div></div>
<p><i class="blue">说明:</i></p>
<p>配置当所创建的镜像作为其它新创建镜像的基础镜像时,所执行的操作指令。</p>
<h3 id="414-mongodb实例">4.14 mongodb实例</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>FROM centos:7
RUN mkdir -p /data/db /data/logs /keys /config
ADD source/mongodb-bin.tar.gz /usr/local/bin
COPY source/mongod.conf /config/
COPY source/mongodb-keyfile /keys/
COPY source/authAdmin.ms /config/
RUN chmod 600 /keys/mongodb-keyfile \
&& ls -l /keys \
&& mongo --version
COPY docker-entrypoint.sh /entrypoint.sh
ENTRYPOINT ["/entrypoint.sh"]
EXPOSE 27017
WORKDIR /config
CMD ["mongod", "-f", "mongod.conf"]
</code></pre></div></div>
<p><a id="link5"></a></p>
<h2 id="五容器数据卷管理">五、容器数据卷管理</h2>
<p><code class="language-plaintext highlighter-rouge">Docker</code> 镜像 <==> 只读层,虽然提高了镜像构建、存储和分发效率,但是也有如下的缺点:</p>
<ul>
<li>容器文件在宿主机上保存形式复杂,不易在宿主机上进行访问。</li>
<li>多个容器间数据无法共享。</li>
<li>删除容器时,容器产生的数据会丢失。</li>
</ul>
<p>因而,<code class="language-plaintext highlighter-rouge">Docker</code> 引入了数据卷(<code class="language-plaintext highlighter-rouge">volume</code>)机制,存在于一个或多个容器中的特定文件或文件夹,能独立于联合文件系统的形式存在于宿主机中,并能为多个容器所共享。</p>
<ul>
<li>在容器创建时初始化,容器运行时就可以使用其中的文件。</li>
<li>能在不同容器之间共享和重用。</li>
<li>对<code class="language-plaintext highlighter-rouge">volume</code>中数据的操作会马上生效。</li>
<li>对<code class="language-plaintext highlighter-rouge">volume</code>的操作不会影响到镜像本身。</li>
<li><code class="language-plaintext highlighter-rouge">volume</code>的生命周期独立于容器的生存周期。(即使删除容器,<code class="language-plaintext highlighter-rouge">volume</code>仍然存在,没有任何容器使用的<code class="language-plaintext highlighter-rouge">volume</code>也不会被<code class="language-plaintext highlighter-rouge">Docker</code>删除)</li>
</ul>
<h3 id="51-添加数据卷">5.1 添加数据卷</h3>
<p>使用 <code class="language-plaintext highlighter-rouge">-v</code> 选项添加一个数据卷,或者可以使用多次 <code class="language-plaintext highlighter-rouge">-v</code> 选项为一个 <code class="language-plaintext highlighter-rouge">docker</code> 容器运行挂载多个数据卷。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name test -v /data -i -t -d centos /bin/bash
</code></pre></div></div>
<p>创建的数据卷可以通过 <code class="language-plaintext highlighter-rouge">docker inspect</code> 获取宿主机对应路径。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker inspect test
</code></pre></div></div>
<p>或者直接通过 <code class="language-plaintext highlighter-rouge">--format</code> 只显示需要的结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker inspect --format=“” data
</code></pre></div></div>
<p><i class="red">注意:</i></p>
<ul>
<li>此种方式添加的数据卷,数据默认保存在 <code class="language-plaintext highlighter-rouge">/var/lib/docker/volumes</code>。</li>
</ul>
<h3 id="52-挂载宿主机目录">5.2 挂载宿主机目录</h3>
<p>除了直接挂载一个数据卷到容器之外,还可以将宿主机中的一个文件或目录挂载到容器当中。格式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name mongod -itd -p 27017:27017 -v /data/db:/data phoenix-mongo
</code></pre></div></div>
<p>默认挂载是读写的,可以在挂载的时候指定只读。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name mongod -itd -p 27017:27017 -v /data/db:/data:ro phoenix-mongo
</code></pre></div></div>
<p>但是实际在运用过程中,会遇到一些问题,即挂载之后在容器中没有权限读取挂载的目录,只要将上述的 <code class="language-plaintext highlighter-rouge">ro</code> 指定为 <code class="language-plaintext highlighter-rouge">Z</code> 或 <code class="language-plaintext highlighter-rouge">z</code> 即可,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -name mongod -itd -p 27017:27017 -v /data/db:/data:Z phoenix-mongo
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">stackoverflow</code>上关于 <code class="language-plaintext highlighter-rouge">Z</code>、<code class="language-plaintext highlighter-rouge">z</code> 的说明:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://stackoverflow.com/questions/24288616/permission-denied-on-accessing-host-directory-in-docker
</code></pre></div></div>
<h3 id="53-数据卷容器">5.3 数据卷容器</h3>
<p>如果需要在容器(或非持久性容器)间共享一些持久性数据,最好的办法是创建一个数据卷容器,然后从此容器上挂载数据。</p>
<p>创建数据卷容器:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run -itd -v /data/test:/test:Z --name data centos echo hello
</code></pre></div></div>
<p>使用 <code class="language-plaintext highlighter-rouge">--volume-from</code> 选项在另一个容器上挂载 <code class="language-plaintext highlighter-rouge">/test</code> 卷,不管 <code class="language-plaintext highlighter-rouge">data</code> 容器是否运行,其它容器都可以挂载该容器数据卷,若只是一个单独的数据卷(容器中没有任何服务逻辑)是不需要创建容器的,直接通过 <code class="language-plaintext highlighter-rouge">docker volume create</code> 创建即可。</p>
<p>使用容器数据卷:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name test1 -itd --volume-form data centos /bin/bash
docker run --name test2 -itd --volume-form data centos /bin/bash 也可以继承其它挂载 `/test` 卷的容器,
docker run --name test3 -itd --volume-form test2 centos /bin/bash
</code></pre></div></div>
<p>创建为按成之后的结构如下图所示:</p>
<p><img src="/images/docker/container-volume.png" alt="" /></p>
<p>删除数据卷<code class="language-plaintext highlighter-rouge">volume</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker rm -v,删除容器时指定 -v 参数。
docker run --rm ,启动容器的时候指定 --rm 参数。
</code></pre></div></div>
<p>若不像上述方式,在删除容器的时候删除其数据卷的话,会在 <code class="language-plaintext highlighter-rouge">/var/lib/docker/volumes</code> 目录下遗留很多文件及目录。</p>
<p><a id="link6"></a></p>
<h2 id="六其它">六、其它</h2>
<p>很多部署<code class="language-plaintext highlighter-rouge">Docker</code> 服务的时候,会遇到容器间需要交互的情况,如展示WEB服务、数据库容器之间。</p>
<p>容器见的交互,是在<code class="language-plaintext highlighter-rouge">docker daemon</code> 启动参数 <code class="language-plaintext highlighter-rouge">--icc</code> 控制,特殊情况为了保证容器及宿主机的安全,<code class="language-plaintext highlighter-rouge">--icc</code> 通常设置为<code class="language-plaintext highlighter-rouge">false</code>,即容器间不能进行交互。如果直接向外映射端口,虽然可以达到交互的条件,但是也可能不能满足安全性的要求。</p>
<p>链接就是为了解决这一问题的,<code class="language-plaintext highlighter-rouge">Docker</code> 的连接系统可以在两个容器间建立一个安全的通道,斯特接收容器可以通过此通道访问源容器指定的相关服务信息(数据库访问等)。</p>
<p>用法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>--link <name or id>:alias
</code></pre></div></div>
<h3 id="61-示例">6.1 示例</h3>
<p>现有两个容器 <code class="language-plaintext highlighter-rouge">web</code> 和 <code class="language-plaintext highlighter-rouge">db</code>,分别提供展示和数据库服务。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker run --name db -itd phoenix-mongo
docker run --name web --link db:webdb phoenix-web
</code></pre></div></div>
<p>如此一个<code class="language-plaintext highlighter-rouge">link</code>就创建完成了,<code class="language-plaintext highlighter-rouge">web</code>容器可以从<code class="language-plaintext highlighter-rouge">db</code>容器中获取数据,<code class="language-plaintext highlighter-rouge">web</code>容器叫作接收容器或父容器,<code class="language-plaintext highlighter-rouge">db</code>容器叫作源容器或子容器。链接的具体实现:</p>
<ul>
<li>设置接收容器的环境变量。</li>
<li>更新接收容器的 <code class="language-plaintext highlighter-rouge">/etc/hosts</code> 文件。</li>
<li>添加<code class="language-plaintext highlighter-rouge">iptables</code>规则,使容器链接的两个容器可以通讯。</li>
</ul>
<p><i class="red">注意:</i></p>
<ul>
<li>一个接收容器可以设置多个源容器,一个源容器也可以有多个接收容器。</li>
</ul>
<h3 id="62-swarm-集群介绍">6.2 swarm 集群介绍</h3>
<p>将一系列 <code class="language-plaintext highlighter-rouge">Docker</code> 宿主机变为一个集群,对外而言就像一个单一的宿主机一样。</p>
<p><code class="language-plaintext highlighter-rouge">swarm</code>由一个<code class="language-plaintext highlighter-rouge">swarm manager</code>和若干个<code class="language-plaintext highlighter-rouge">swarm node</code> 组成,<code class="language-plaintext highlighter-rouge">swarm manager</code>上运行<code class="language-plaintext highlighter-rouge">swarm daemon</code>,用户跟<code class="language-plaintext highlighter-rouge">manager</code>通信,<code class="language-plaintext highlighter-rouge">manager</code>将操作转发到对应的<code class="language-plaintext highlighter-rouge">node</code>上。</p>
<p><code class="language-plaintext highlighter-rouge">swarm daemon</code>只是一个调度器(<code class="language-plaintext highlighter-rouge">scheduler</code>)和路由器(<code class="language-plaintext highlighter-rouge">router</code>)的组合,其本身并不会运行任何 <code class="language-plaintext highlighter-rouge">Docker</code> 容器,它只是接受 <code class="language-plaintext highlighter-rouge">Docker</code> 客户端发来的请求,调度合适的 <code class="language-plaintext highlighter-rouge">swarm node</code> 来运行<code class="language-plaintext highlighter-rouge">container</code>。</p>
<p>若<code class="language-plaintext highlighter-rouge">swarm daemon</code>意外挂掉了,已经运行的容器不会受到任何影响。其架构如下所示:</p>
<p><img src="/images/docker/swarm-arch.png" alt="" /></p>
<p>三个独立的主机,分别装了<code class="language-plaintext highlighter-rouge">docker daemon</code>:</p>
<p><img src="/images/docker/single-docker.png" alt="" /></p>
<p>通过<code class="language-plaintext highlighter-rouge">swarm</code>创建集群:</p>
<p><img src="/images/docker/swarm-cluster.png" alt="" /></p>
<h3 id="63-docker-compose-简介">6.3 docker-compose 简介</h3>
<p><code class="language-plaintext highlighter-rouge">Dockerfile</code> 重现一个容器,<code class="language-plaintext highlighter-rouge">Compose</code>重现容器的配置和集群。</p>
<p>编排<code class="language-plaintext highlighter-rouge">Orchestration</code>,定义被部署对象的各个组成部分之间的耦合关系,部署流程中各个动作的执行顺序,部署过程说需要的依赖文件,被部署文件的存储位置和获取方式,以及如何验证部署成功。</p>
<p>部署<code class="language-plaintext highlighter-rouge">Deployment</code>,安装编排指定的内容和流程,在目标主机上执行编排指定的环境初始化,存放指定的依赖和文件,运行指定的部署动作,最终安装编排的规则确认部署成功与否。</p>
<p>说的简单点,就是安装一个特定的顺序,完成其指定的容器启动顺序,并校验部署的成功与否。</p>
<h4 id="631-示例">6.3.1 示例</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-- docker-compose.yml
web:
images: phoenix-web
ports:
- “3000:3000”
links:
- db
db:
images: phoenix-mongo
volumes:
- /data/db:/data:Z
</code></pre></div></div>
<p>运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>docker-compose up
</code></pre></div></div>
<h3 id="64-docker-machine-简介">6.4 docker-machine 简介</h3>
<p>对于 <code class="language-plaintext highlighter-rouge">Docker</code> 集群而言,需要每台宿主机有安装并启动 <code class="language-plaintext highlighter-rouge">docker daemon</code> 进程,而安装启动<code class="language-plaintext highlighter-rouge">docker</code>进程的过程是重复,<code class="language-plaintext highlighter-rouge">Docker Machine</code>把用户搭建<code class="language-plaintext highlighter-rouge">Docker</code> 环境的各种方案汇集在一起,简化了 <code class="language-plaintext highlighter-rouge">Docker</code> 环境搭建过程,让用户和把时间花在开发上。</p>
<p><code class="language-plaintext highlighter-rouge">Machine</code> 主要帮助用户在不同的云主机等上,创建并管理虚拟机,并在虚拟机中安装Docker,而 Docker本身就是客户端程序与守护进程之间的交互,启动了<code class="language-plaintext highlighter-rouge">docker daemon</code>即搭建了 <code class="language-plaintext highlighter-rouge">Docker</code> 运行环境。</p>
<p>目前已经支持十余种运品台和虚拟机软件,包括<code class="language-plaintext highlighter-rouge">Amazon Web Services</code>、<code class="language-plaintext highlighter-rouge">Google Compute Engine</code>、<code class="language-plaintext highlighter-rouge">OpenStack</code>等。</p>
<p><code class="language-plaintext highlighter-rouge">Machine</code> 支持多种虚拟机软件和众多主流的<code class="language-plaintext highlighter-rouge">IaaS</code>平台,支持的虚拟机软件有<code class="language-plaintext highlighter-rouge">VirtualBox</code>、<code class="language-plaintext highlighter-rouge">Vmware Fusion</code>和<code class="language-plaintext highlighter-rouge">Hyper-V</code>,涵盖了<code class="language-plaintext highlighter-rouge">Linux</code>、<code class="language-plaintext highlighter-rouge">Mac</code>和<code class="language-plaintext highlighter-rouge">Windows</code>三大平台。</p>
Zen Coding:快速编写HTML
2016-12-08T00:00:00+00:00
http://www.blogways.net/blog/2016/12/08/zen-coding
<h2 id="一概述">一、概述</h2>
<p>HTML 语言很简单,但是却比较难写。各种标签、符号实在是太多了,标签又包含很多属性、class、id。想写出一个好看的静态页面确实是非常耗时而且麻烦。</p>
<p>下面要介绍的 Zen Coding 是一种借助 CSS 选择器的方式帮你快速编写 HTML 代码的方法。它能用很短的语句写出很长的 HTML 代码。</p>
<p>让我们来见识一下它的威力。</p>
<h2 id="二实例介绍">二、实例介绍</h2>
<p>在开始使用 Zen Coding 之前,介绍一下它支持的 CSS 选择器:</p>
<ul>
<li>E:
元素名称(div, p);</li>
<li>E#id:
使用id的元素(div#content, p#intro, span#error);</li>
<li>E.class:
使用类的元素(div.header, p.error.critial). 你也可以联合使用class和idID: div#content.column.width;</li>
<li>E>N:
子代元素(div>p, div#footer>p>span);</li>
<li>E+N:
兄弟元素(h1+p, div#header+div#content+div#footer);</li>
<li>E<em>N:
元素倍增(ul#nav>li</em>5>a);</li>
<li>E$<em>N:
条目编号 (ul#nav>li.item-$</em>5);</li>
</ul>
<p>Zen Coding 其实就是基于这样的语法来快速生成 HTML 的。</p>
<h3 id="编辑器">编辑器</h3>
<p>下面所做的演示全部都来自 <em>Atom</em>,<em>Brackets</em> 同样也支持 Zen Coding。</p>
<h3 id="实例一">实例一</h3>
<p>在编辑器中输入:
<code class="language-plaintext highlighter-rouge">!</code>
然后按<em>tab</em>键。
最后生成的结果是:</p>
<p><img src="/images/jyjsjd/example1.png" alt="example1.png" /></p>
<p>没错就是这么<em>神奇</em>。</p>
<h3 id="实例二">实例二</h3>
<p>在页面中添加一个 class 为 <em>col-sm-6</em> 的 div 标签,只需要输入
<code class="language-plaintext highlighter-rouge">div.col-sm-6</code>,然后按 tab 键。</p>
<p>如果想添加12个 class 为 <em>col-sm-1</em> 的 div 标签呢?</p>
<p>只需要输入<code class="language-plaintext highlighter-rouge">div.col-sm-1*12</code>。</p>
<p>同理,如果想要输出 id 为 test 的 div 标签只需要把<code class="language-plaintext highlighter-rouge">.</code>换成<code class="language-plaintext highlighter-rouge">#</code>就行了。</p>
<h3 id="实例四">实例四</h3>
<p>在页面中添加</p>
<p><img src="/images/jyjsjd/example2.png" alt="example2.png" /></p>
<p>输入<code class="language-plaintext highlighter-rouge">h1+p</code>即可。</p>
<h3 id="实例五">实例五</h3>
<p>想要下面这种效果?</p>
<p><img src="/images/jyjsjd/example3.png" alt="example3.png" /></p>
<p>输入<code class="language-plaintext highlighter-rouge">div.item$*3</code>!</p>
nodejs的异步流程控制
2016-12-07T00:00:00+00:00
http://www.blogways.net/blog/2016/12/07/nodejs-async
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#begin">为什么使用异步流程控制</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#1st">Async的介绍和安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#2nd">Async的函数介绍</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#3rd">使用案例</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#end">总结与心得</a></td>
</tr>
</tbody>
</table>
<p><a id="begin"></a></p>
<h2 id="1为什么使用异步流程控制">1.为什么使用异步流程控制</h2>
<p>“流程控制”本来是件比较简单的事,但是由于Nodejs的异步架构的实现方法,对于需要同步的业务逻辑,实现起来就比较麻烦。嵌套3-4层,代码就会变得的支离破碎了!
如何让代码看起来简介,而且过程可控,这里我们就需要引入异步流程控制。</p>
<p><a id="1st"></a></p>
<h2 id="2async的介绍和安装">2.Async的介绍和安装</h2>
<p>Async是一个流程控制工具包,提供了直接而强大的异步功能。基于Javascript为Node.js设计,同时也可以直接在浏览器中使用。</p>
<p>Async提供了大约20个函数,包括常用的 map, reduce, filter, forEach 等,异步流程控制模式包括,串行(series),并行(parallel),瀑布(waterfall)等。</p>
<p>(1). 安装环境</p>
<p>Npm:1.2.19<br /></p>
<p>nodejs</p>
<p>(2) 安装方式</p>
<p>npm install async</p>
<p><a id="2nd"></a></p>
<h2 id="3async的函数介绍">3.Async的函数介绍</h2>
<p><strong>async主要实现了三个部分的流程控制功能:</strong></p>
<ul>
<li>集合: Collections</li>
<li>流程控制: Control Flow</li>
<li>工具类: Utils</li>
</ul>
<p>1). 集合: Collections</p>
<p>each: 如果想对同一个集合中的所有元素都执行同一个异步操作。</p>
<p><strong>map</strong>: 对集合中的每一个元素,执行某个异步操作,得到结果。所有的结果将汇总到最终的callback里。与each的区别是,each只关心操作不管最后的值,而map关心的最后产生的值。</p>
<p><strong>filter</strong>: 使用异步操作对集合中的元素进行筛选, 需要注意的是,iterator的callback只有一个参数,只能接收true或false。</p>
<p><strong>reject</strong>: reject跟filter正好相反,当测试为true时则抛弃</p>
<p><strong>reduce</strong>: 可以让我们给定一个初始值,用它与集合中的每一个元素做运算,最后得到一个值。reduce从左向右来遍历元素,如果想从右向左,可使用reduceRight。</p>
<p><strong>detect</strong>: 用于取得集合中满足条件的第一个元素。</p>
<p><strong>sortBy</strong>: 对集合内的元素进行排序,依据每个元素进行某异步操作后产生的值,从小到大排序。</p>
<p><strong>some</strong>: 当集合中是否有至少一个元素满足条件时,最终callback得到的值为true,否则为false。</p>
<p><strong>every</strong>: 如果集合里每一个元素都满足条件,则传给最终回调的result为true,否则为false。</p>
<p><strong>concat</strong>: 将多个异步操作的结果合并为一个数组。</p>
<p>2). 流程控制: Control Flow</p>
<p><strong>series</strong>: 串行执行,一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。</p>
<p><strong>parallel</strong>: 并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。</p>
<p><strong>whilst</strong>: 相当于while,但其中的异步调用将在完成后才会进行下一次循环。</p>
<p><strong>doWhilst</strong>: 相当于do…while, doWhilst交换了fn,test的参数位置,先执行一次循环,再做test判断。</p>
<p><strong>until</strong>: until与whilst正好相反,当test为false时循环,与true时跳出。其它特性一致。</p>
<p><strong>doUntil</strong>: doUntil与doWhilst正好相反,当test为false时循环,与true时跳出。其它特性一致。</p>
<p><strong>forever</strong>: 无论条件循环执行,如果不出错,callback永远不被执行。</p>
<p><strong>waterfall</strong>: 按顺序依次执行一组函数。每个函数产生的值,都将传给下一个。</p>
<p><strong>compose</strong>: 创建一个包括一组异步函数的函数集合,每个函数会消费上一次函数的返回值。把f(),g(),h()异步函数,组合成f(g(h()))的形式,通过callback得到返回值。</p>
<p><strong>applyEach</strong>: 实现给一数组中每个函数传相同参数,通过callback返回。如果只传第一个参数,将返回一个函数对象,我可以传参调用。</p>
<p><strong>queue</strong>: 是一个串行的消息队列,通过限制了worker数量,不再一次性全部执行。当worker数量不够用时,新加入的任务将会排队等候,直到有新的worker可用。</p>
<p><strong>cargo</strong>: 一个串行的消息队列,类似于queue,通过限制了worker数量,不再一次性全部执行。不同之处在于,cargo每次会加载满额的任务做为任务单元,只有任务单元中全部执行完成后,才会加载新的任务单元。</p>
<p><strong>auto</strong>: 用来处理有依赖关系的多个任务的执行。</p>
<p><strong>iterator</strong>: 将一组函数包装成为一个iterator,初次调用此iterator时,会执行定义中的第一个函数并返回第二个函数以供调用。</p>
<p><strong>apply</strong>: 可以让我们给一个函数预绑定多个参数并生成一个可直接调用的新函数,简化代码。</p>
<p><strong>nextTick</strong>: 与nodejs的nextTick一样,再最后调用函数。</p>
<p><strong>times</strong>: 异步运行,times可以指定调用几次,并把结果合并到数组中返回。</p>
<p><strong>timesSeries</strong>: 与time类似,唯一不同的是同步执行</p>
<p>3). 工具类: Utils</p>
<p><strong>memoize</strong>: 让某一个函数在内存中缓存它的计算结果。对于相同的参数,只计算一次,下次就直接拿到之前算好的结果。</p>
<p><strong>unmemoize</strong>: 让已经被缓存的函数,返回不缓存的函数引用。</p>
<p><strong>log</strong>: 执行某异步函数,并记录它的返回值,日志输出。</p>
<p><strong>dir</strong>: 与log类似,不同之处在于,会调用浏览器的console.dir()函数,显示为DOM视图。</p>
<p><strong>noConflict</strong>: 如果之前已经在全局域中定义了async变量,当导入本async.js时,会先把之前的async变量保存起来,然后覆盖它。仅仅用于浏览器端,在nodejs中没用,这里无法演示。</p>
<p><a id="3rd"></a></p>
<h2 id="3async的使用案例">3.Async的使用案例</h2>
<h3 id="1-seriestasks-callback-多个函数依次执行之间没有数据交换">1. series(tasks, [callback]) (多个函数依次执行,之间没有数据交换)</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var async = require('async')
async.series([
step1, step2, step3
], function(err, values) {
// do somethig with the err or values v1/v2/v3
});
</code></pre></div></div>
<p>(1)依次执行一个函数数组中的每个函数,每一个函数执行完成之后才能执行下一个函数。</p>
<p>(2)如果任何一个函数向它的回调函数中传了一个error,则后面的函数都不会被执行,并且将会立刻会将该error以及已经执行了的函数的结果,传给series中最后那个callback。</p>
<p>(3)当所有的函数执行完后(没有出错),则会把每个函数传给其回调函数的结果合并为一个数组,传给series最后的那个callback。</p>
<p>(4)还可以json的形式来提供tasks。每一个属性都会被当作函数来执行,并且结果也会以json形式传给series最后的那个callback。这种方式可读性更高一些。</p>
<h3 id="2-paralleltasks-callback-多个函数并行执行">2. parallel(tasks, [callback]) (多个函数并行执行)</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>async.parallel([
function(cb) { t.fire('a400', cb, 400) },
function(cb) { t.fire('a200', cb, 200) },
function(cb) { t.fire('a300', cb, 300) }
], function (err, results) {
log('1.1 err: ', err); // -> undefined
log('1.1 results: ', results); // ->[ 'a400', 'a200', 'a300' ]
});
</code></pre></div></div>
<p>(1)并行执行多个函数,每个函数都是立即执行,不需要等待其它函数先执行。传给最终callback的数组中的数据按照tasks中声明的顺序,而不是执行完成的顺序。</p>
<p>(2)如果某个函数出错,则立刻将err和已经执行完的函数的结果值传给parallel最终的callback。其它未执行完的函数的值不会传到最终数据,但要占个位置。</p>
<p>(3)同时支持json形式的tasks,其最终callback的结果也为json形式</p>
<h3 id="3-waterfalltasks-callback-多个函数依次执行且前一个的输出为后一个的输入">3. waterfall(tasks, [callback]) (多个函数依次执行,且前一个的输出为后一个的输入)</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>async.waterfall([
function(cb) { log('1.1.1: ', 'start'); cb(null, 3); },
function(n, cb) { log('1.1.2: ',n); t.inc(n, cb); },
function(n, cb) { log('1.1.3: ',n); t.fire(n*n, cb); }
], function (err, result) {
log('1.1 err: ', err); // -> null
log('1.1 result: ', result); // -> 16
});
</code></pre></div></div>
<p>(1)与seires相似,按顺序依次执行多个函数。不同之处,每一个函数产生的值,都将传给下一个函数。如果中途出错,后面的函数将不会被执行。错误信息以及之前产生的结果,将传给waterfall最终的callback。</p>
<p>(2)这个函数名为waterfall(瀑布),可以想像瀑布从上到下,中途冲过一层层突起的石头。注意,该函数不支持json格式的tasks。</p>
<h3 id="4autotasks-callback-多个函数有依赖关系有的并行执行有的依次执行">4.auto(tasks, [callback]) (多个函数有依赖关系,有的并行执行,有的依次执行)</h3>
<p>这里假设我要写一个程序,它要完成以下几件事:
从某处取得数据</p>
<ol>
<li>在硬盘上建立一个新的目录</li>
<li>将数据写入到目录下某文件</li>
<li>发送邮件,将文件以附件形式发送给其它人</li>
</ol>
<p>分析该任务,可以知道1与2可以并行执行,3需要等1和2完成,4要等3完成。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>async.auto({
getData: function (callback) {
setTimeout(function(){
console.log('1.1: got data');
callback();
}, 300);
},
makeFolder: function (callback) {
setTimeout(function(){
console.log('1.1: made folder');
callback();
}, 200);
},
writeFile: ['getData', 'makeFolder', function(callback) {
setTimeout(function(){
console.log('1.1: wrote file');
callback(null, 'myfile');
}, 300);
}],
emailFiles: ['writeFile', function(callback, results) {
log('1.1: emailed file: ', results.writeFile); // -> myfile
callback(null, results.writeFile);
}]
}, function(err, results) {
log('1.1: err: ', err); // -> null
log('1.1: results: ', results); // -> { makeFolder: undefined,
// getData: undefined,
// writeFile: 'myfile',
// emailFiles: 'myfile' }
});
</code></pre></div></div>
<p>(1)用来处理有依赖关系的多个任务的执行。比如某些任务之间彼此独立,可以并行执行;但某些任务依赖于其它某些任务,只能等那些任务完成后才能执行。</p>
<p>(2)虽然我们可以使用async.parallel和async.series结合起来实现该功能,但如果任务之间关系复杂,则代码会相当复杂,以后如果想添加一个新任务,也会很麻烦。这时使用async.auto,则会事半功倍。</p>
<p>(3)如果有任务中途出错,则会把该错误传给最终callback,所有任务(包括已经执行完的)产生的数据将被忽略。</p>
<p><a id="end"></a></p>
<h2 id="5总结与心得">5.总结与心得</h2>
<p>在凤来平台开发过程,对于数据库连续操作,由于nodejs异步的原因,我们不好对结果出现的顺序进行控制,或者回调函数使用过多,维护起来十分麻烦,使用异步操作可以很方便的解决开发过程中出现的一系列问题。</p>
apache tiles布局框架的简单使用
2016-12-06T00:00:00+00:00
http://www.blogways.net/blog/2016/12/06/apache-apacheTiles
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">apache tiles简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2st">apache tiles的优势</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3st">apache tiles的简单使用</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一apache-tiles的简介">一、apache tiles的简介</h2>
<h3 id="apache-tiles是一个javaee应用的页面布局框架tiles框架提供了一种模板机制可以为某一类页面定义一个通用的模板该模板定义了页面的整体布局布局由可以复用的多个块组成每个页面可以有选择性的重新定义块而达到组件的复用">Apache Tiles是一个JavaEE应用的页面布局框架。Tiles框架提供了一种模板机制,可以为某一类页面定义一个通用的模板,该模板定义了页面的整体布局。布局由可以复用的多个块组成,每个页面可以有选择性的重新定义块而达到组件的复用。</h3>
<h3 id="apache-tiles一开始是apache-struts框架的组件之一后来才被apache独立为一个独立项目">apache tiles一开始是apache Struts框架的组件之一,后来才被apache独立为一个独立项目。</h3>
<h3 id="tiles主要有以下几个特点">tiles主要有以下几个特点:</h3>
<ul>
<li>1.模板机制的页面布局功能。</li>
<li>2.页面布局的重构机制,使用模板的页面,可以直接在JSP里使用Tiles提供的标签重新定义块元素,也可以使用类似tiles.xml等配置文件定义。</li>
<li>3.易于与Struts,Spring,SpringMVC,Shale,JSF等框架集成 。</li>
</ul>
<h2 id="二apache-tiles的优势">二、apache tiles的优势</h2>
<h3 id="大家都知道在web开发中我们可以通过include标签来动态的插入其它的jsp页面这样能够让多个jsp页面共用一个jsp界面的内容这个功能能够让我们在开发中节省很多时间并且实用">大家都知道,在web开发中、我们可以通过include标签来动态的插入其它的jsp页面,这样能够让多个jsp页面共用一个jsp界面的内容,这个功能能够让我们在开发中节省很多时间,并且实用。</h3>
<h3 id="如果有一天我们需要把这个界面删除掉的话或者说添加修改一个界面需要在每个jsp的引入位置把引入删除掉添加修改一个jsp界面当然这个处理听起来比较方便但是如果有几百几千个界面估计人就想吐了当然时间久了还是可以把他实现">如果有一天、我们需要把这个界面删除掉的话(或者说添加/修改一个界面),需要在每个jsp的引入位置把引入删除掉(添加/修改一个jsp界面),当然、这个处理听起来比较方便,但是如果有几百,几千个界面,估计人就想吐了,当然时间久了还是可以把他实现,</h3>
<h3 id="而tiles则很方便的就可以让我们去实现这个操作下面就一起来去看看这个tiles怎么去简单的实现">而tiles则很方便的就可以让我们去实现这个操作,下面就一起来去看看这个tiles怎么去简单的实现,</h3>
<h2 id="二apache-tiles的简单使用">二、apache tiles的简单使用</h2>
<h3 id="在这里我使用的是springmvctileseclipsemaven">在这里我使用的是SpringMVC+Tiles+Eclipse+Maven;</h3>
<h3 id="非maven环境需要手动导入tiles和springmvc的互相依赖的包">非maven环境需要手动导入tiles和springmvc的互相依赖的包</h3>
<h3 id="首先新建一个maven工程并且使用pomxml导入包">首先新建一个maven工程并且使用pom.xml导入包</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.apache.tiles</groupId>
<artifactId>tiles-extras</artifactId>
<version>3.0.5</version>
</dependency>'
SpringMVC
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>4.3.3.RELEASE</version>
</dependency>
</code></pre></div></div>
<h3 id="webxml配置文件">web.xml配置文件</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">
<servlet>
<servlet-name>tile</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:main.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>tile</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
</web-app>
</code></pre></div></div>
<h3 id="spring配置文件">Spring配置文件</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- 配置组件扫描 -->
<context:component-scan base-package="main"/>
<!-- 配置mvc扫描 -->
<mvc:annotation-driven/>
<!-- 配置视图解析器 -->
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
</bean>
<bean id="tilesViewResolver" class="org.springframework.web.servlet.view.tiles3.TilesViewResolver">
<!--视图解析器的优先级-->
<property name="order" value="1" />
</bean>
<!--加载tiles配置文件-->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>classpath:layout.xml</value>
</list>
</property>
</bean>
</beans>
</code></pre></div></div>
<h3 id="tiles配置文件layoutxml">tiles配置文件layout.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE tiles-definitions PUBLIC
"-//Apache Software Foundation//DTD Tiles Configuration 3.0//EN"
"http://tiles.apache.org/dtds/tiles-config_3_0.dtd">
<tiles-definitions>
<!-- 主布局 -->
<definition name="layout" template="mainLayout.jsp">
</definition>
<!-- 主布局 -->
<!-- 项目 -->
<definition name="myView" extends="layout">
<put-attribute name="a" value="/a.jsp" />
<put-attribute name="b" value="/b.jsp" />
</definition>
<!--项目-->
</tiles-definitions>
</code></pre></div></div>
<h3 id="配置文件做完之后java代码处理">配置文件做完之后java代码处理</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RequestMapping("/test.do")
protected String method(){
return "myView";
}
</code></pre></div></div>
<h3 id="在webapp写上jsp页面当然也可以在其他文件下面写上jsp界面相应的配置文件的路径需要处理">在webapp写上jsp页面(当然也可以在其他文件下面写上jsp界面,相应的配置文件的路径需要处理)</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mainLayout.jsp
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles-extras" prefix="tilesx" %>
<div>
<tiles:insertAttribute name="a" />
<tiles:insertAttribute name="b" />
</div>
</code></pre></div></div>
<h3 id="另外写上ajsp--bjsp文件这是我们需要引入的两个jsp文件其中内容可以随便写">另外写上a.jsp b.jsp文件,这是我们需要引入的两个jsp文件,其中内容可以随便写</h3>
<h3 id="最后部署到tomcat服务器之上访问相应的路径这里我的路径是httplocalhost8080tilestestdo就可以看到组合的成的网页了这是我自己组成的简单的界面">最后部署到tomcat服务器之上,访问相应的路径(这里我的路径是http://localhost:8080/tiles/test.do),就可以看到组合的成的网页了!这是我自己组成的简单的界面</h3>
<p><img src="/images/zhshyong/111.png" alt="111" /></p>
<h3 id="当然这是死的界面如果要灵活应用则需要修改下配置文件">当然、这是死的界面、如果要灵活应用、则需要修改下配置文件</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- 主布局 -->
<!-- 项目 -->
<definition name="myView" extends="layout">
<put-attribute name="a" value="/a.jsp" />
<!--${item}.jsp item为传递过来的参数--!>
<put-attribute name="item" expression="/${item}.jsp" />
</definition>
<!--项目-->
</tiles-definitions> ### 组合界面mainLayout.jsp文件也需要修改一下
<%@ page language="java" import="java.util.*" pageEncoding="utf-8"%>
<%@ taglib uri="http://tiles.apache.org/tags-tiles" prefix="tiles" %>
<%@ taglib uri="http://tiles.apache.org/tags-tiles-extras" prefix="tilesx" %>
<div>
<tiles:insertAttribute name="a" />
<tiles:insertAttribute name="item" />
</div>
</code></pre></div></div>
<h3 id="请求也需要修改一下">请求也需要修改一下</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RequestMapping("/test1.do")
protected String ee(Model model){
//传递的参数tigger则是对应的jsp文件的名称
//也只需要修改这个位置就可以达到组合成其他的页面
//不需要修改其它位置以及文件
model.addAttribute("item", "tigger");
return "myView";
}
</code></pre></div></div>
<h3 id="这样再访问一次路径httplocalhost8080tilestest1do">这样再访问一次路径(http://localhost:8080/tiles/test1.do)</h3>
<p><img src="/images/zhshyong/222.png" alt="222" /></p>
<h3 id="当修改参数">当修改参数</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RequestMapping("/test1.do")
protected String ee(Model model){
//传递的参数tigger则是对应的jsp文件的名称
//也只需要修改这个位置就可以达到组合成其他的页面
//不需要修改其它位置以及文件
model.addAttribute("item", "rabbit");
return "myView";
}
</code></pre></div></div>
<h3 id="再次访问httplocalhost8080tilestest1do时候就有下图">再次访问http://localhost:8080/tiles/test1.do时候就有下图</h3>
<p><img src="/images/zhshyong/333.png" alt="333" /></p>
<h3 id="这样拼成的界面会比较方便而且修改起来也特别方便以前干很久的事现在几分钟就可以做完了">这样拼成的界面会比较方便,而且修改起来也特别方便!以前干很久的事,现在几分钟就可以做完了。</h3>
UI设计中下拉刷新有什么讲究?
2016-12-06T00:00:00+00:00
http://www.blogways.net/blog/2016/12/06/UI-appDesign-05
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">UI设计中下拉刷新有什么讲究</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h4 id="最早的下拉刷新设计源自于tweetie-这款应用的设计设计师loren-brichter-是这种令人欲罢不能的交互的缔造者现在下拉刷新的交互几乎无处不在但即便如此也让人无法轻易忽视它带来的快感从twitter-到-gmail从qq到新浪微博它几乎存在于你触手可及的每一个应用当中">最早的下拉刷新设计源自于Tweetie 这款应用的设计,设计师Loren Brichter 是这种令人欲罢不能的交互的缔造者。现在,下拉刷新的交互几乎无处不在,但即便如此也让人无法轻易忽视它带来的快感。从Twitter 到 Gmail,从QQ到新浪微博,它几乎存在于你触手可及的每一个应用当中。</h4>
<p><img src="/images/huangmai/902176.gif" alt="902176" /></p>
<h4 id="下拉刷新是怎么运行的">下拉刷新是怎么运行的</h4>
<p>简而言之,下拉刷新是刷新指示器于下拉手势想结合的产物,同时借助下拉动效完善整个交互。</p>
<p><img src="/images/huangmai/902206.gif" alt="902206" /></p>
<p>下拉刷新本质上是一种特定的手动刷新交互,和其他的同类操作不同的地方在于它采用了更加直觉的下拉操作。它的交互足够清晰明显,不过有的时候设计师依然会使用刷新指示器来显示自动更新的内容。不过,由于自动刷新的机制不需要用户进行任何操作,是因此自动刷新最好还是不要使用刷新指示器了。</p>
<h4 id="什么时候使用下拉刷新">什么时候使用下拉刷新</h4>
<p>滑动刷新(包括下拉刷新)非常适合于列表、栅格列表和卡片集合之类界面(按照时间降序排列)。这类界面通常会随着时间推移,优先展示最新的内容,并且通常不会采用自动更新来刷新内容。不采用自动刷新的机制的原因很简单,当你滚动到顶部的时候,如果采用自动刷新,内容会不断自动下载显示,对于用户而言,这样的自动显示的机制并不可控。而下拉刷新则很好的解决了这个问题,只有在你使用下拉手势的时候,触发刷新指示器,再行更新,这样也给了用户选择和退出的机会。常见的下拉刷新用例:</p>
<p><img src="/images/huangmai/902209.jpg" alt="902209" /></p>
<p>内容流(Twitter,微博,RSS)
收件箱(电子邮件,短信)
以Twitter 为例,推文是按照时间顺序排列,最新的推文在顶部,当用户下拉刷新之后,能够看到最新更新的推文。
####什么时候不适合下拉刷新
下拉刷新并非万能的,它有不适宜使用的场景:
屏幕小插件。这类小插件是需要自动更新的。</p>
<p><img src="/images/huangmai/902211.png" alt="902211" /></p>
<p>地图应用。地图上的内容通常不会以时序排列,也没有明确的方向和内容来源,用户也无法直觉推断出下拉刷新的含义。</p>
<p><img src="/images/huangmai/902213.png" alt="902213" /></p>
<p>无序列表。有序列表通常能够给用户以下拉更新的期望,而无序列表在这方便则不明显,也无法直观地让用户看出其中的更新。
低更新率的内容。如果列表中的内容并不经常更新,下拉刷新的手势几乎没有存在的意义,因为用户去刷新列表的机率很低,也没有太大的必要。
按照时间先后顺序排列的内容。按照时间先后顺序排列的列表中,最新更新的内容排在最后,用户下拉刷新之后无法立刻看到最新更新的内容,这样列表使用下拉刷新就相当尴尬了。
特殊类型的内容。许多需要实时更新的内容会在短短一分钟甚至更短的时间内失去时效,诸如股票、服务器后台进程之类的信息,最好使用实时自动更新。</p>
<p>####如何设计刷新指示器的过渡效果
下拉刷新前后两种状态需要借助过渡动效连接到一起,让用户了解界面到底发生了什么改变。刷新指示器在两种状态过渡过程中一直存在,它一直保持可见,直到刷新完成,新的内容更新出来。</p>
<p><img src="/images/huangmai/902214.gif" alt="902214" /></p>
<p>值得注意的是,在刷新过程中,哪怕用户针对界面有所操作,刷新指示器也不能隐藏,否则它作为指示器的作用就失去意义了。</p>
<p><img src="/images/huangmai/902217.gif" alt="902217" /></p>
<p>####自然或改变</p>
<p><img src="/images/huangmai/902293.jpg" alt="902293" /></p>
<p>####下拉刷新 vs. 刷新按钮
许多设计师和开发者将下拉刷新视作为一种节省屏幕空间的方法,毕竟它无需像刷新按钮一样占据一个固定按钮的空间。但是它的问题也同样很明显,下拉刷新没有刷新按钮那么明显直观,实现起来也更加麻烦。</p>
<p><img src="/images/huangmai/902219.png" alt="902219" /></p>
<p>####刷新时长
当用户手指在屏幕上向下滑动,手势触发刷新指示器,以此为视觉反馈告知用户系统已经收到请求了。下拉刷新的交互设计很大程度上是想让用户明白发生了什么,并确保程序能够正常运行。当用户熟悉下拉刷新的操作和交互的结果之后,会逐步信任这种交互。刷新指示器会持续旋转,直到数据完全更新至可用,这样可以防止混乱。</p>
<p><img src="/images/huangmai/902222.jpg" alt="902222" /></p>
<p>####可用的动效
下拉刷新的过渡动效是一个非常适合发挥创意的地方。由于它是连接两个不同状态的中间态,过渡动效可用帮助用户理解屏幕上发生了什么,精准地理解两个不同UI状态之间的差别。</p>
<p><img src="/images/huangmai/902243.gif" alt="902243" /></p>
如何在你的UI设计项目中叠加色彩
2016-12-06T00:00:00+00:00
http://www.blogways.net/blog/2016/12/06/UI-appDesign-04
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">如何在你的UI设计项目中叠加色彩</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h4 id="在任何设计中颜色都是一个非常重要的组成部分无论你是喜欢明亮大胆的色彩或者是喜欢简约的黑白色调你如何使用颜色如何进行颜色搭配都会对你的整体的设计有很大的影响">在任何设计中,颜色都是一个非常重要的组成部分。无论你是喜欢明亮大胆的色彩,或者是喜欢简约的黑白色调,你如何使用颜色,如何进行颜色搭配,都会对你的整体的设计有很大的影响。</h4>
<p>在设计中使用色彩的方法很多,现在最常见的是,在设计项目中运用一个颜色叠加。这意味着你所覆盖的图像或视频,有着一个透明的色块,类似于一个半透明的彩色盒子,这种效果可以强化设计的形象含义,提升设计氛围,让观看者增强一种艺术选择感。
今天,我们所要探讨的就是,如何在在你的设计项目中灵活应用色彩叠加。</p>
<p>####尝试渐变</p>
<p><img src="/images/huangmai/902272.jpg" alt="902272" /></p>
<h4 id="渐变再一次的回归了">渐变再一次的回归了!</h4>
<p>当你想使用色彩叠加时,它们是一个极佳的方式,你可以轻易的创建一种引人注目的(醒目)的色彩效果。
渐变有什么好处?你可以使用几种不同的颜色,把它们搭配在一起,形成渐变色彩(品牌用色更常见)。或者你也可以使用一种颜色,通过颜色上的明亮对比,创建视觉焦点。明亮的色彩搭配,可以帮助你更轻易的吸引用户,为你的设计和图像加分。
Spotify 在播放列表中,使用了韵味十足的渐变和双色调(点击可跳转链接)效果,开始引领了这种设计趋势,赋予图片一种新的生命,让用户看到更有艺术效果的音乐家图片。
这虽然是一个设计概念,但也是容易复制的。
选择一张照片
使用渐变颜色叠加
完成
纯色基调</p>
<p><img src="/images/huangmai/902277.jpg" alt="902277" /></p>
<h4 id="单独的纯色叠加照样可以跟渐变叠加一样引人注目但纯色叠加其选择的颜色往往更具有独特的意义比如说创建一个深褐色纯色叠加它会呈现一种回忆往事和历史的感觉厚重感十足">单独的纯色叠加,照样可以跟渐变叠加一样引人注目,但纯色叠加其选择的颜色,往往更具有独特的意义。比如说,创建一个深褐色纯色叠加,它会呈现一种回忆往事和历史的感觉,厚重感十足。</h4>
<p>一样的道理,你选择一个时髦的颜色(流行色),效果也是一样的。通过使用一种明亮、艳美的颜色,结合扁平化设计,或者与之相对应的材料设计,可以为你的设计增添一种现代主义或时尚的感觉。
当使用一种单一颜色叠加时,我们应当认真的考虑一下颜色的饱和度和透明度,因为这些因素都是有其本身的意义的。颜色的饱和度和透明度,对你的设计有很大的影响。当叠加的颜色透明度低或饱和度高时,观看者将更容易被色彩本身所吸引,当叠加的颜色透明度高或饱和度低时,观看者会更容易被图像本身所吸引,而不是叠加的颜色本身。</p>
<p>####考虑明暗</p>
<p><img src="/images/huangmai/902279.jpg" alt="902279" /></p>
<p>####你不要总是使用颜色来创建一个叠加,有时它可以是黑色,白色或灰色。使用这些颜色和色调,有时候真的可以改变一个设计项目的情绪。
正如你所期望的,一个偏暗的色彩叠加,可以营造出一个喜怒无常的环境氛围,反之,一个偏亮的色彩叠加,往往会显得更轻松与俏皮。
上面的图片是一个叫 Call me lsh 的网站,它使用了一个白色叠加,帮助网站把重点聚焦到屏幕上的黑白照片上的单词上。但是请注意,摄影师脸上的表情:他咧嘴微笑。颜色和照片的组合显得非常有趣,会不会让你想要与摄影师一起互动,甚至有雇佣他工作的可能?</p>
<p>####选择高对比度的图像</p>
<p><img src="/images/huangmai/902283.jpg" alt="902283" /></p>
<p><img src="/images/huangmai/902288.jpg" alt="902288" /></p>
<p>####当你计划在一个设计项目上使用色彩叠加时,你选择的图像(或视频)是很重要的。一张淡而无味的图像,会导致你的设计出现一个乏味的颜色叠加的结果。你想要得到最好的结果,那么从你刚开始选择照片时开始,注意图像本身的质量,选择高对比的图像。
如果你的图像没有足够的对比度,那么可以考虑用软件(比如PS)强化对比,或者直接选择另一张图像。否则,色彩叠加的可能会不好,导致你的设计失败。
还有一个特别简单的技巧,那就是使用黑白图像。尤其对于初学者来说,它可以更容易的控制黑白图像,增强对比度。上面的 Just Actions 网站,提供了一个很好的示例,一个使用色彩叠加的黑白图像。(它没有鲜艳的色彩对比,但依旧是一个很酷的效果)</p>
<p>####自然或改变</p>
<p><img src="/images/huangmai/902293.jpg" alt="902293" /></p>
<p>####当涉及到使用色彩叠加时,你有两种选择:
1、图片看起来应该仍然自然。颜色、明暗和阴影,都应该出现在一个自然的位置。叠加应该尽量微妙,比如 Abednego Coffee。(上图)
2、图片看起来完全改变了。不需要猜颜,一看就知道是使用了色彩叠加。(本文的示例基本都是这样)
这个没有“中立”的选择,如果图片不落入其中一个“极端”,用户可能会更专注于你的色彩选择,而不是专注于网站的内容。如果你不想因为使用了色彩叠加,而导致用户的注意力分散,那么,你应该加强你的整体设计。</p>
<p>####部分叠加</p>
<p><img src="/images/huangmai/902294.jpg" alt="902294" /></p>
<p><img src="/images/huangmai/902295.jpg" alt="902295" /></p>
<p>####前面的例子展示了如何使用大范围的色彩叠加,但这并不是唯一的方法,尝试部分的叠加,色彩效应依旧可以很好。
上面的两个例子,利用部分的色彩叠加,有效地实现了设计目的。
Knot Clothing 网站,顶部导航栏使用了部分色彩叠加,它使用了一个明亮的绿色,降低了透明度,它进一步凸显了上面的线条,帮助维持品牌的颜色影响,同时在整个设计项目中,展示其它的颜色。效果很简单,但效果非常好。
Nuts and Woods 网站使用的是另一种方法。它在网站的设计上,使用了一个色彩叠加的悬停效果,告诉你关于具体项目的更多信息。任何被红色覆盖的元素也是一个可点击的元素。这个设计值得注意的是,色彩叠加作为给用户的视觉提示,它提供了一个路径链接,告诉用户他们想知道的相关内容。</p>
<p>####结语
色彩叠加只是一种技术,并不是所有设计项目都适合。大多数的设计师发现他们只能侥幸的在一个或两个设计项目上取得成功。(这就是为什么,部分色彩叠加可能是一个更好的选择)
对于任何设计技术,应当确保在正确的设计理念的引导下去使用。你不应该仅仅因为你的灵感来自于另外一个使用了色彩叠加的项目,就决定使用色彩叠加。</p>
关于登录界面的设计观点
2016-12-06T00:00:00+00:00
http://www.blogways.net/blog/2016/12/06/UI-appDesign-03
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">关于登录界面的设计观点</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h4 id="关键字路径清晰主次分明专注眼前分场景">关键字:路径清晰,主次分明,专注眼前,分场景。</h4>
<h4 id="路径清晰">路径清晰</h4>
<p>登录页面通常包含多个入口:注册、登录方式A、登录方式B、第三方账号登录等,我们无法准确预测用户会究竟要选择哪一个,因此,各个路径必须一目了然。以google、大众点评和美团为例:Google较简洁(因为它不支持第三方账号登录),功能说明和输入区域挤在同一区域,上半部屏幕直接浪费掉,也没有区分清楚登录和注册两个路径;大众点评和美团虽然界面信息更多,但比起google登录和注册路径区分更明确。</p>
<p><img src="/images/huangmai/917115.jpg" alt="917115" /></p>
<h4 id="主次分明">主次分明</h4>
<p>贪心是全人类的通病,设计师要压制好自己和产品经理的欲望。手机号快速登录逐渐成为主流,但某些产品依然不愿放弃传统登录,鱼与熊掌不可兼得,取舍不够决绝会导致功能主次不分。设计之前请选好一种主推的登录方式,哪种是当前最急需的?哪种是符合业务长远利益的?分清优先级的同时也要在界面上体现。对比大众点评和美团:大众点评已经把账密登录方式弱化成一个二级入口,而美团依然想保持两种登录方式直接切换的便利性(也许是数据上暂时没有明显的偏向,产品经理无法拍板)。突出一种登录方式,其他该弱化的弱化,该抛弃的抛弃。</p>
<h4 id="专注眼前">专注眼前</h4>
<p>既然做了选择,就不要三心二意。用户选择了一种登录方式后,其他的方式可以大胆放一边去了。然而某些设计师(或PM)一直把用户当成是购物中的选择困难症女性,“登录方式要切换方便,万一用户要更换。。。万一用户选错怎么办?”。也许有5%的用户会选错,为此就要降低剩下95%的用户的体验?假如遵循好上述的两个观点,所谓的万一会更加少。美团在这一点做的比较好,调起键盘输入并隐去第三方账号登录。</p>
<p><img src="/images/huangmai/917127.jpg" alt="917127" /></p>
<h4 id="输出">输出</h4>
<p>综合上述观点,默认登录界面仅保留登录入口、切换登录方式、注册入口和第三方账号登录,尽可能保持路径清晰。账密登录仅作为次要登录方式弱化成跳转入口,主次分明。调起键盘后(确认登录方式)才显示验证码、登录按钮等控件,同时第三方账号登录下滑隐去,专注当前任务。</p>
<p><img src="/images/huangmai/917130.png" alt="917130" /></p>
<p><img src="/images/huangmai/917129.gif" alt="917129" /></p>
<h4 id="ps分场景">PS:分场景</h4>
<p>登录及注册界面,其流程该是一次性展现还是按任线性务流一步步展现?线性任务流的好处是:分别对每个提交的信息检测并反馈,用户理解起来清晰安全,属于“can’t go wrong” 的设计,但其一步步的跳转容易给用户一种漫长的感觉。对比之下大众点评及美团拿任务流清晰度换取了便捷性。</p>
<h4 id="取舍">取舍</h4>
<p>线性任务流和集成任务究竟如何取舍,取决设计师如何平衡清晰度和便捷性。我的理解是:登录是留给老用户的,用户对应用有一定的熟悉度,另外结合场景分析,无论是主动触发或是在购买商品时被动触发登录,用户总是不耐烦的希望尽快结束此流程,因此登录流程约简短越好,选用集成任务;注册面对的是新用户,用户此刻更需要的是安全感,明确的说明和清晰的步骤比操作便捷更重要,因此选用线性任务流。</p>
APP界面设计(下)
2016-12-06T00:00:00+00:00
http://www.blogways.net/blog/2016/12/06/UI-appDesign-02
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">IOS字体规范与多屏幕适配</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<p><img src="/images/huangmai/20161205img01.jpg" alt="20161205img01" /></p>
mui前端APP之四-android签名机制介绍及Hbuilder打包
2016-12-05T00:00:00+00:00
http://www.blogways.net/blog/2016/12/05/web-mui-04
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">签名文件的作用</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2st">生成及查看签名证书</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3st">使用Hbuider云打包</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一签名文件keystore的作用">一、签名文件(*.keystore)的作用</h2>
<h4 id="开发android的人这么多完全有可能大家都把类名包名起成了一个同样的名字这时候如何区分签名这时候就是起区分作用的">开发Android的人这么多,完全有可能大家都把类名,包名起成了一个同样的名字,这时候如何区分?签名这时候就是起区分作用的。</h4>
<h4 id="由于开发商可能通过使用相同的package-name来混淆替换已经安装的程序签名可以保证相当名字但是签名不同的包不被替换">由于开发商可能通过使用相同的Package Name来混淆替换已经安装的程序,签名可以保证相当名字,但是签名不同的包不被替换。</h4>
<h4 id="apk如果使用一个key签名发布时另一个key签名的文件将无法安装或覆盖老的版本这样可以防止你已安装的应用被恶意的第三方覆盖或替换掉">APK如果使用一个key签名,发布时另一个key签名的文件将无法安装或覆盖老的版本,这样可以防止你已安装的应用被恶意的第三方覆盖或替换掉。</h4>
<h4 id="这样签名其实也是开发者的身份标识交易中抵赖等事情发生时签名可以防止抵赖的发生">这样签名其实也是开发者的身份标识。交易中抵赖等事情发生时,签名可以防止抵赖的发生。</h4>
<h3 id="签名的注意事项">签名的注意事项</h3>
<h4 id="android系统要求所有的程序经过数字签名才能安装如果没有可用的数字签名系统将不许安装运行此程序不管是模拟器还是真实手机因此在设备或者是模拟器上运行调试程序之前必须为应用程序设置数字签名">Android系统要求所有的程序经过数字签名才能安装,如果没有可用的数字签名,系统将不许安装运行此程序。不管是模拟器还是真实手机。因此,在设备或者是模拟器上运行调试程序之前,必须为应用程序设置数字签名;</h4>
<h4 id="android签名的数字证书不需要权威机构来认证是开发者自己产生的数字证书即所谓的自签名数字证书用来标识应用程序的作者和在应用程序之间建立信任关系而不是用来决定最终用户可以安装哪些应用程序">Android签名的数字证书不需要权威机构来认证,是开发者自己产生的数字证书,即所谓的自签名。数字证书用来标识应用程序的作者和在应用程序之间建立信任关系,而不是用来决定最终用户可以安装哪些应用程序</h4>
<h4 id="系统仅仅会在安装的时候测试签名证书的有效期如果应用程序的签名是在安装之后才到期那么应用程序仍然可以正常启用">系统仅仅会在安装的时候测试签名证书的有效期,如果应用程序的签名是在安装之后才到期,那么应用程序仍然可以正常启用</h4>
<h4 id="可以使用标准工具-keytool-and-jarsigner-生成密钥来签名应用程序的apk文件">可以使用标准工具-Keytool and Jarsigner-生成密钥,来签名应用程序的.apk文件</h4>
<h4 id="正式发布一个android应用时必须使用一个合适的私钥生成的数字证书来给程序签名不能使用adt插件或者ant工具生成的调试证书来发布">正式发布一个Android应用时,必须使用一个合适的私钥生成的数字证书来给程序签名,不能使用ADT插件或者ANT工具生成的调试证书来发布</h4>
<h2 id="二生成及查看签名证书">二、生成及查看签名证书</h2>
<h3 id="1-生成keystore">1. 生成keystore</h3>
<h4 id="创建keystore需要用到keytoolexe-位于jdk_xxjrebin目录下具体做法如下mac在终端下可直接敲入keytool">创建keystore,需要用到keytool.exe (位于jdk_xx\jre\bin目录下),具体做法如下,mac在终端下可直接敲入keytool</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -genkey -alias mykey -keyalg RSA -validity 40000 -keystore demo.keystore
#说明:
# -genkey 产生密钥
# -alias mykey 别名 mykey
# -keyalg RSA 使用RSA算法对签名加密
# -validity 40000 有效期限4000天
# -keystore demo.keystore
然后按回车键
按回车后首先会提示你输入的密码:这个在签名时要用的,要记住
然后会再确认你的密码。
之后会依次叫你输入姓名、组织单位、组织名称、城市区域、省份名称、国家代码(CN)等
Keytool的详细参数请参考
http://www.android123.com.cn/androidkaifa/173.html
</code></pre></div></div>
<h3 id="2查看签名信息">2.查看签名信息</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -list -keystore demo.keystore -alias mykey -v
</code></pre></div></div>
<h4 id="3查看keystore的公钥证书信息">3.查看keystore的公钥证书信息</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>keytool -list -keystore demo.keystore -alias mykey -rfc
</code></pre></div></div>
<h2 id="三使用hbuider云打包">三、使用Hbuider云打包</h2>
<h4 id="前面我们得到了keystore签名文件即可使用hbuilder来进行apk的签名如下图">前面我们得到了keystore签名文件,即可使用Hbuilder来进行apk的签名,如下图</h4>
<p><img src="/images/liuyw6/20161205img01.png" alt="20161205img01" /></p>
<h4 id="1android包名可自己定义">1.Android包名:可自己定义;</h4>
<h4 id="2证书别名生成keystore时的-alias后面部分即-mykey">2.证书别名:生成keystore时的-alias后面部分,即 “mykey”</h4>
<h4 id="3私钥密码生成时输入的密码">3.私钥密码:生成时输入的密码</h4>
<h4 id="4证书文件keystore的文件位置">4.证书文件:keystore的文件位置</h4>
<h4 id="点击打包即可进行云端打包">点击打包即可进行云端打包</h4>
<h2 id="结束语">结束语</h2>
<h4 id="本章主要结合android的签名文件来进行了整过程的描述能够对android如何进行打包有个详细的了解">本章主要结合Android的签名文件来进行了整过程的描述,能够对android如何进行打包有个详细的了解;</h4>
Django入门
2016-12-05T00:00:00+00:00
http://www.blogways.net/blog/2016/12/05/Django-introduction
<h2 id="一概述">一、概述</h2>
<p><img src="/images/jyjsjd/Django_logo.png" alt="Django_logo.png" /></p>
<p><a href="https://www.djangoproject.com/">Django</a>是一个开放源代码的Web应用框架,由Python写成,本身是专门用作开发新闻管理系统的。采用了MVC的软件设计模式,即模型M,视图V和控制器C。</p>
<p>Django框架的核心包括:一个面向对象的<em>映射器</em>,用作数据模型(以Python类的形式定义)和关系性数据库间的媒介;一个基于正则表达式的<em>URL分发器</em>;一个用于处理请求的<em>视图系统</em>;以及一个<em>模板系统</em>。</p>
<h2 id="二环境要求">二、环境要求</h2>
<p>从 <a href="https://www.python.org/">Python</a> 官网下载安装适合机器系统的版本,Python >= 2.7。
用 Python 的 pip 命令安装 Django:</p>
<p><code class="language-plaintext highlighter-rouge">$ pip install Django==1.10.4</code></p>
<p>安装完成之后运行命令查看是否安装成功:</p>
<p><img src="/images/jyjsjd/Django_version.png" alt="Django_version.png" /></p>
<h2 id="三一个实例">三、一个实例</h2>
<h3 id="1-新建项目">(1) 新建项目</h3>
<p>用<em>django-admin</em>命令新建一个项目:</p>
<p><code class="language-plaintext highlighter-rouge">$ django-admin startproject mysite</code></p>
<p>会得到如下的目录结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mysite/
manage.py
mysite/
__init__.py
settings.py
urls.py
wsgi.py
</code></pre></div></div>
<ul>
<li>manage.py:是一个命令行工具,它允许你用多种方式和 Django 项目交互。</li>
<li>mysite/settings.y:包含项目的基本设置,如数据库,静态文件目录等。</li>
<li>mysite/urls.py:项目的 URL 的分发器。</li>
<li>mysite/wsgi.py:WSGI 服务器入口。</li>
</ul>
<p>运行命令启动项目:</p>
<p><code class="language-plaintext highlighter-rouge">$ python manage.py runserver</code></p>
<p>打开浏览器输入:</p>
<p><code class="language-plaintext highlighter-rouge">http://localhost:8000</code></p>
<p>查看项目是否正确创建,如果项目创建成功,能看到成功页面:</p>
<p><img src="/images/jyjsjd/startpage.png" alt="startpage.png" /></p>
<h3 id="2-新建-app">(2) 新建 app</h3>
<p>进入 <em>mysite</em> 目录,运行命令:</p>
<p><code class="language-plaintext highlighter-rouge">$ python manage.py startapp polls</code></p>
<p>在<em>mysite</em>项目下会生成一个目录<em>polls</em>,我们所有的代码都是基于这个 app。
我们再往文件夹里加一些文件夹,最终目录结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>polls/
__init__.py
admin.py
apps.py
migrations/
__init__.py
static/
templates/
admin/
polls/
models.py
tests.py
urls.py
views.py
</code></pre></div></div>
<ul>
<li>admin.py:修改 admin 页面。</li>
<li>apps.py:注册 app,如这里的<em>polls</em>。</li>
<li>static/:静态文件目录,如 css,JavaScript 文件。</li>
<li>templates/:模板文件目录。</li>
<li>models.py:所有数据模型。</li>
<li>test.py:测试文件。</li>
<li>urls.py:URL分发器。</li>
<li>views.py:包含所有的视图。</li>
</ul>
<p>运行命令,创建管理员用户:</p>
<p><code class="language-plaintext highlighter-rouge">$ python manage.py createsuperuser</code></p>
<p>根据提示一步步创建。</p>
<h3 id="3-数据模型">(3) 数据模型</h3>
<p>Django 支持大部分主流数据库,为简单起见在这里使用 <em>sqlite</em>。</p>
<p><em>polls</em>中的数据模型都定义在 polls/models.py中。这里定义了两个数据模型,Question 和 Choice:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Question(models.Model):
question_text = models.CharField(max_length=200)
pub_date = models.DateTimeField('date published')
def was_published_recently(self):
now = timezone.now()
return now - datetime.timedelta(days=1) <= self.pub_date <= now
def __str__(self):
return self.question_text
class Choice(models.Model):
question = models.ForeignKey(Question, on_delete=models.CASCADE)
choice_text = models.CharField(max_length=200)
votes = models.IntegerField(default=0)
def __str__(self):
return self.choice_text
</code></pre></div></div>
<p>在 mysite/settings.py 中注册项目:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>INSTALLED_APPS = [
'polls.apps.PollsConfig', # 注册polls
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
</code></pre></div></div>
<p>数据库迁移:</p>
<p><code class="language-plaintext highlighter-rouge">$ python manage.py makemigrations polls</code></p>
<p>根据数据模型在数据库中创建表:</p>
<p><code class="language-plaintext highlighter-rouge">$ python manage.py sqlmigrate polls 0001</code></p>
<p>保存项目之后在浏览器中打开<code class="language-plaintext highlighter-rouge">http://localhost:8000/admin/</code>,首先输入管理员的用户名密码,然后就进入了后台管理页面:</p>
<p><img src="/images/jyjsjd/Admin_page.png" alt="Admin_page.png" /></p>
<p>可以看到 Django 自动为我们生成了一个管理页面,它能对定义的数据模型进行 <em>CRUD</em> 的操作,并且提供了一些<em>权限控制</em>的功能,非常方便。</p>
<h3 id="4-自定义视图">(4) 自定义视图</h3>
<p>视图定义在 views.py 中,可以基于方法或者类。下面定义了一个简单的视图,它会返回全部问题的列表:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class IndexView(generic.ListView):
template_name = 'polls/index.html'
context_object_name = 'latest_question_list'
def get_queryset(self):
return Question.objects.order_by('-pub_date')[:5]
</code></pre></div></div>
<p>在模板目录 templates 中的 polls,添加模板文件 index.html:</p>
<p><img src="/images/jyjsjd/index_page.png" alt="index_page.png" /></p>
<h3 id="5-url分发">(5) URL分发</h3>
<p>Django的URL分发基于正则表达式,访问这些页面时会去 templates 目录中去找相应的 template,这些定义在 urls.py中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
]
</code></pre></div></div>
<p>上面定义了当 URL 中什么都没写的时候,直接访问 index 页面。</p>
<h3 id="6-自定义样式">(6) 自定义样式</h3>
<p>可以在 static 目录中存放 css、JavaScript 或者图片文件。在 template 首部使用:</p>
<p><img src="/images/jyjsjd/load_command.png" alt="load_command.png" /></p>
<p>就可以引用这些文件。
如在 static 目录中添加文件 style.css,增加下面的样式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>li a {
color: green;
}
</code></pre></div></div>
<p>在 index.html 中引入这个样式:</p>
<p><img src="/images/jyjsjd/load_example.png" alt="load_example.png" /></p>
<p>index 页面下的所有超链接都会变成绿色:</p>
<p><img src="/images/jyjsjd/Django_style.png" alt="Django_style.png" /></p>
mui前端APP之三-IOS证书申请
2016-12-02T00:00:00+00:00
http://www.blogways.net/blog/2016/12/02/web-mui-03
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">证书是什么</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2st">申请流程</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3st">使用Hbuider云打包</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一证书是什么">一、证书是什么</h2>
<h3 id="ios打包需要证书及描述文件可是证书是什么东西呢">IOS打包需要证书及描述文件,可是证书是什么东西呢?</h3>
<p><img src="/images/liuyw6/20161203img01.jpeg" alt="20161203img01" /></p>
<h4 id="上面这个就是我们申请好证书后下载到本地的cer文件也就是常说的开发证书与发布证书的样式这cer文件格式的证书是让开发者使用的设备也就是你的mac有真机调试发布app的权限">上面这个就是我们申请好证书后,下载到本地的.cer文件,也就是常说的开发证书与发布证书的样式。这.cer文件格式的证书是让开发者使用的设备(也就是你的Mac)有真机调试,发布APP的权限;</h4>
<h4 id="但是hbuilder需要的是p12文件不是cer文件先别急我们一步一步的讲解你就能明白了">但是Hbuilder需要的是p12文件,不是cer文件;先别急,我们一步一步的讲解你就能明白了</h4>
<p><img src="/images/liuyw6/20161203img02.png" alt="20161203img02" /></p>
<h4 id="上面这个就是我们配置好证书后下载到本地的mobileprovision文件也就是配置文件的样式这mobileprovision文件格式的配置文件是让开发者的项目app能有真机调试发布的权限">上面这个就是我们配置好证书后,下载到本地的.mobileprovision文件,也就是配置文件的样式。这.mobileprovision文件格式的配置文件是让开发者的项目(APP)能有真机调试,发布的权限。</h4>
<h2 id="二申请流程">二、申请流程</h2>
<h3 id="1-申请钥匙串文件">1. 申请钥匙串文件</h3>
<h4 id="进入launchpad找到钥匙串访问运行后再左上角如下图">进入Launchpad,找到“钥匙串访问”,运行后再左上角,如下图</h4>
<p><img src="/images/liuyw6/20161203img03.png" alt="20161203img03" />
<img src="/images/liuyw6/20161203img04.png" alt="20161203img04" />
<img src="/images/liuyw6/20161203img05.png" alt="20161203img05" /></p>
<h4 id="存储在桌面就好了然后就完成退出钥匙串工具就可以了">存储在桌面就好了,然后就完成退出钥匙串工具就可以了。</h4>
<h3 id="2申请开发证书发布证书">2.申请开发证书,发布证书</h3>
<h4 id="打开苹果开发者中心httpsdeveloperapplecom">打开苹果开发者中心(https://developer.apple.com)</h4>
<p><img src="/images/liuyw6/20161203img06.png" alt="20161203img06" />
<img src="/images/liuyw6/20161203img07.png" alt="20161203img07" />
<img src="/images/liuyw6/20161203img08.png" alt="20161203img08" />
<img src="/images/liuyw6/20161203img09.png" alt="20161203img09" />
<img src="/images/liuyw6/20161203img10.png" alt="20161203img10" />
<img src="/images/liuyw6/20161203img11.png" alt="20161203img11" />
<img src="/images/liuyw6/20161203img12.png" alt="20161203img12" /></p>
<h4 id="此处下载的cer文件即为申请的证书文件请一定要区分开发证书和发布证书">此处下载的cer文件即为申请的证书文件,请一定要区分“开发证书”和“发布证书”</h4>
<h4 id="开发证书仅能用于开发者设备的安装-发布证书可以上架到app-store">“开发证书”,仅能用于开发者设备的安装; “发布证书”,可以上架到APP Store</h4>
<h3 id="3导出p12文件">3.导出p12文件</h3>
<h4 id="双击下载的cer文件即导入了证书文件我们重新打开钥匙串访问如下图选择左侧的证书">双击下载的cer文件,即导入了证书文件,我们重新打开“钥匙串访问”,如下图,选择左侧的“证书”</h4>
<p><img src="/images/liuyw6/20161203img13.png" alt="20161203img13" />
<img src="/images/liuyw6/20161203img14.png" alt="20161203img14" />
<img src="/images/liuyw6/20161203img15.png" alt="20161203img15" />
<img src="/images/liuyw6/20161203img16.png" alt="20161203img16" /></p>
<h4 id="需要注意的是1p12文件可以给其他的开发者使用但是只有申请证书的mac才能导出p12其他mac双击cer看不见liuyw6也无法导出">需要注意的是:1、p12文件可以给其他的开发者使用,但是只有申请证书的mac才能导出p12;其他mac双击cer看不见liuyw6,也无法导出</h4>
<h4 id="至此我们已经得到了ios打包需要的第一个文件-p12接下来我们来获得描述文件mobileversion">至此我们已经得到了IOS打包需要的第一个文件 p12;接下来我们来获得描述文件mobileversion</h4>
<h3 id="3注册项目的bundle-id">3.注册项目的Bundle ID</h3>
<h4 id="bundle-id为你app的唯一标示id不允许与其它的app重复申请如下">Bundle ID为你APP的唯一标示ID,不允许与其它的APP重复,申请如下</h4>
<p><img src="/images/liuyw6/20161203img17.png" alt="20161203img17" />
<img src="/images/liuyw6/20161203img18.png" alt="20161203img18" />
<img src="/images/liuyw6/20161203img19.png" alt="20161203img19" /></p>
<h3 id="4添加测试设备">4.添加测试设备</h3>
<h4 id="一个app开发者账户允许配置100台测试设备测试设备仅对开发者证书有用发布证书不需要设置测试设备">一个APP开发者账户允许配置100台测试设备,测试设备仅对开发者证书有用;发布证书不需要设置测试设备</h4>
<p><img src="/images/liuyw6/20161203img20.png" alt="20161203img20" />
<img src="/images/liuyw6/20161203img21.png" alt="20161203img21" /></p>
<h4 id="上图中填写有一个uuiduuid是iphoneipad等的唯一标示可以通过itunes查看如下图">上图中填写有一个UUID,UUID是iphone、ipad等的唯一标示,可以通过iTunes查看,如下图</h4>
<p><img src="/images/liuyw6/20161203img22.png" alt="20161203img22" />
<img src="/images/liuyw6/20161203img23.png" alt="20161203img23" /></p>
<h3 id="4配置证书即mobileversion描述文件">4.配置证书(即mobileversion描述文件)</h3>
<p><img src="/images/liuyw6/20161203img24.png" alt="20161203img24" />
<img src="/images/liuyw6/20161203img25.png" alt="20161203img25" />
<img src="/images/liuyw6/20161203img26.png" alt="20161203img26" />
<img src="/images/liuyw6/20161203img27.png" alt="20161203img27" /></p>
<h4 id="这里不用担心会选择错误你配置开发证书那这里就只有开发证书供你选择配置发布证书这里就只有发布证书供你选择">这里不用担心会选择错误,你配置开发证书,那这里就只有开发证书供你选择。配置发布证书,这里就只有发布证书供你选择。</h4>
<h4 id="tips同一个开发者账户可以创建2个开发者证书3个发布证书如果是同一团队创建一个即可通用所有的产品">TIPS:同一个开发者账户可以创建2个开发者证书、3个发布证书;如果是同一团队,创建一个即可通用所有的产品</h4>
<h4 id="如果是配置开发证书就还会需要你设置在这个项目中添加哪些设备作为真机调试的设备">如果是配置开发证书,就还会需要你设置,在这个项目中添加哪些设备作为真机调试的设备</h4>
<p><img src="/images/liuyw6/20161203img28.png" alt="20161203img28" />
<img src="/images/liuyw6/20161203img29.png" alt="20161203img29" /></p>
<h2 id="三使用hbuider云打包">三、使用Hbuider云打包</h2>
<h4 id="完成创建后下载下载下来的即为mobileversion文件至此我们已经得到了ios打包的2个文件接下来我们使用hbuilder进行打包">完成创建后下载,下载下来的即为mobileversion文件,至此,我们已经得到了IOS打包的2个文件,接下来我们使用Hbuilder进行打包</h4>
<p><img src="/images/liuyw6/20161203img30.png" alt="20161203img30" /></p>
<h4 id="1appid与bundle-id-保持一致即可">1.AppID:与Bundle id 保持一致即可;</h4>
<h4 id="2私钥密码导出p12时设置的密码">2.私钥密码:导出p12时设置的密码</h4>
<h4 id="3profile文件mobileversion">3.profile文件:mobileversion</h4>
<h4 id="4私钥证书p12文件">4.私钥证书:p12文件</h4>
<h4 id="点击打包即可进行云端打包">点击打包即可进行云端打包</h4>
<h2 id="结束语">结束语</h2>
<h4 id="本章主要结合ios的证书描述文件来进行了整过程的描述能够了解如何去下载相应的打包文件提示app开发人员需要交费开通个人开发者99年">本章主要结合IOS的证书、描述文件来进行了整过程的描述,能够了解如何去下载相应的打包文件;提示:APP开发人员需要交费开通:个人开发者$99/年</h4>
mui前端APP开发实践-热点新闻
2016-12-02T00:00:00+00:00
http://www.blogways.net/blog/2016/12/02/web-mui-02
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">热点新闻APP界面认识</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2st">一步步搭建应用</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3st">热点新闻内容代码详解</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#4st">APP打包及真机查看</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一热点新闻app界面认识">一、热点新闻APP界面认识</h2>
<h3 id="上一章我们大概了解了mui的概念以及他提供的强大的工具和框架这一章我们用一个简单的例子来快速构建我们的app应用">上一章我们大概了解了mui的概念,以及他提供的强大的工具和框架,这一章我们用一个简单的例子来快速构建我们的APP应用</h3>
<h4 id="我们要构建的应用非常简单就是仿一个简单的新闻类的app其中包含了2个界面一个新闻列表页可以上拉加载更多下拉刷新一个新闻详情页界面如下">我们要构建的应用非常简单,就是仿一个简单的新闻类的APP,其中包含了2个界面,一个新闻列表页(可以上拉加载更多、下拉刷新),一个新闻详情页,界面如下:</h4>
<p><img src="/images/liuyw6/20161202img01.png" alt="20161202img01" />
<img src="/images/liuyw6/20161202img02.png" alt="20161202img02" /></p>
<h4 id="我们已经知道了我们的目标接下来我们就要开始创建工程了">我们已经知道了我们的目标,接下来我们就要开始创建工程了。</h4>
<h2 id="二一步步搭建应用">二、一步步搭建应用</h2>
<h4 id="打开开发工具hbuilder在左侧工程栏中右键新建移动app项目输入应用名称news默认选择空模版点击完成如下图">打开开发工具“HBuilder”,在左侧工程栏中“右键”-“新建”-“移动APP”项目,输入应用名称“News”,默认选择空模版,点击完成,如下图</h4>
<p><img src="/images/liuyw6/20161202img03.png" alt="20161202img03" />
<img src="/images/liuyw6/20161202img04.png" alt="20161202img04" /></p>
<p>建好工程后,按照下图的方式创建好相应的目录,同时导入mui相关的js,css等,其中工程目录的一些说明如下图</p>
<p><img src="/images/liuyw6/20161202img05.png" alt="20161202img05" /></p>
<h4 id="ps此工程已经放置于gogs上有兴趣的同学可以去下载">PS:此工程已经放置于gogs上,有兴趣的同学可以去下载</h4>
<h4 id="按照上图中的目录建设完成我们可以进行相应的界面内容开发">按照上图中的目录建设完成,我们可以进行相应的界面内容开发</h4>
<h2 id="三app内容代码详解">三、APP内容代码详解</h2>
<h3 id="a数据源">A、数据源</h3>
<h4 id="百度api-store中有许多的新闻接口我们就选择使用百度的api接口如下code段">百度API Store中有许多的新闻接口,我们就选择使用百度的API接口,如下code段:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.ajax({
type: "GET",
url: "http://apis.baidu.com/showapi_open_bus/channel_news/search_news?channelId=" + selectedType + "&page=" + pageObj[selectedType],
beforeSend: function(request) {
request.setRequestHeader("apikey", "7f6dfa583fe9406f73f2830a5c2fb99c");
},
success: function(d) {
if(d.showapi_res_body.pagebean) {
var newsArray = d.showapi_res_body.pagebean.contentlist;
var singleModel = '<div class="news-item">' + $('.news-item-model-single').html() + '</div>';
var multiModel = '<div class="news-item">' + $('.news-item-model-multi').html() + '</div>';
var textModel = '<div class="news-item">' + $('.news-item-model-none').html() + '</div>';
for(var i = 0; i < newsArray.length; i++) {
//判断新闻是否已经显示过
if(refreshArt.indexOf(newsArray[i].title) == -1) {
//设置当前新闻格式,无图片格式、单图片格式、多图片格式
var model, type;
if(!newsArray[i].havePic) {
model = textModel;
type = 'text';
} else {
if(newsArray[i].imageurls.length > 1) {
model = multiModel;
type = 'multi';
} else {
model = singleModel;
type = 'single';
}
}
refreshArt.push(newsArray[i].title);
detailObj[selectedType].push(newsArray[i]);
$('.' + newId).append(model);
$('.' + newId).find('.news-item:last').attr('iden', preLength + i);
$('.' + newId).find('.news-item:last').find('.news-title').html(newsArray[i].title);
//设置图片
if(type == 'single') {
$('.' + newId).find('.news-item:last').find('.news-image img').attr('src', newsArray[i].imageurls[0].url);
} else {
for(var j = 0; j < (newsArray[i].imageurls.length > 3 ? 3 : newsArray[i].imageurls.length); j++) {
$('.' + newId).find('.news-item:last').find('.news-image-multi').find('.flex-element-no-padding:eq(' + j + ')').html('<img src="' + newsArray[i].imageurls[j].url + '"/>')
}
}
$('.' + newId).find('.news-item:last').find('.author-name').html(newsArray[i].source);
$('.' + newId).find('.news-item:last').find('.pub-date').html(newsArray[i].pubDate);
//绑定新闻点击事件,点击后进入新闻详情页面
$('.' + newId).find('.news-item:last').on('tap', function() {
storage.setItem('newsContent', JSON.stringify(detailObj[selectedType][$(this).attr('iden')]));
_tl.toUrl('newsDetail.html');
})
}
}
} else {
mui.toast('加载失败,请稍后尝试');
}
//设置上拉、下拉结束标志
mui('#pullrefresh').pullRefresh().endPulldownToRefresh(); //refresh completed
mui('#pullrefresh').pullRefresh().endPullupToRefresh(false);
}
});
</code></pre></div></div>
<h4 id="这一段的作用则是从百度api中抓取数据展示至界面中">这一段的作用则是从百度API中抓取数据展示至界面中</h4>
<h3 id="b-list界面由外部的框架页面和内部列表界面">B. List界面由外部的框架页面和内部列表界面</h3>
<h4 id="外部框架页">外部框架页</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1,maximum-scale=1, user-scalable=no">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
<link rel="stylesheet" href="../css/mui.min.css" />
<link rel="stylesheet" href="../css/common/base.css" />
<link rel="stylesheet" href="css/index.css" />
<title></title>
<script type="text/javascript" src="../js/common/jquery.js"></script>
<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
<script type="text/javascript" src="../js/mui.min.js"></script>
<style>
.android-body .mui-pull-top-pocket{top:1.1rem;margin-top:1.1rem;display: block;height: 1.2rem !important;line-height: 1.2rem;background: #f1f1f1;}
.android-body .mui-pull{top: 0.1rem;}
</style>
</head>
<body>
<header class="mui-bar mui-bar-nav">
<h1 id="title" class="mui-title">热点新闻</h1>
<div class="news-types">
<div class="news-types-wrap">
</div>
</div>
</header>
<script>
var storage = window.localStorage;
var selectType;
function loadChannel() {
$.getJSON('../data/chanel.json', function(d) {
$('.news-types-wrap').html('');
for(var i = 0; i < d.channelList.length; i++) {
var ar = d.channelList[i];
$('.news-types-wrap').append('<span type="' + ar.channelId + '">' + ar.name + '</span>');
}
$('.news-types-wrap').find('span:first').addClass('selected');
storage.setItem('selectType', $('.selected').attr('type'));
//绑定点击事件
$('.news-types span').on('tap', function() {
$('.selected').removeClass('selected');
$(this).addClass('selected');
storage.setItem('selectType', $(this).attr('type'));
})
})
}
mui.init({
statusBarBackground: '#fc3434',
subpages: [{
url: 'index.html',
id: 'index',
styles: {
top: parseInt(2.3 * parseInt($('html').css('font-size'))) + 'px', //mui标题栏默认高度为45px;
bottom: '0px' //默认为0px,可不定义;
}
}]
});
mui.plusReady(function() {
//仅支持竖屏显示
plus.screen.lockOrientation("portrait-primary");
if(mui.os.android){
$('body').addClass('android-body');
}
})
$(function() {
loadChannel();
})
</script>
</body>
</html>
</code></pre></div></div>
<h4 id="外部框架页定义了新闻的种类列表同时使用了muiinit定义了子页面indexhtml作为内容列表的提供页">外部框架页定义了,新闻的种类列表,同时使用了mui.init,定义了子页面index.html作为内容列表的提供页</h4>
<h4 id="内容列表页indexhtml">内容列表页index.html</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
<link rel="stylesheet" href="../js/common/swiper/css/swiper.min.css" />
<link rel="stylesheet" href="../css/mui.min.css" />
<link rel="stylesheet" href="css/index.css" />
<title></title>
<script type="text/javascript" src="../js/common/jquery.js"></script>
<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
<script type="text/javascript" src="../js/common/swiper/js/swiper.jquery.min.js"></script>
<script type="text/javascript" src="../js/common/circle-progress.js"></script>
<script type="text/javascript" src="../js/common/tools.js"></script>
<script type="text/javascript" src="../js/mui.min.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</head>
<body>
<div class="x-panel-content-full">
<!--下拉刷新容器-->
<div id="pullrefresh" class="mui-content mui-scroll-wrapper">
<div class="mui-scroll">
<!--数据列表-->
<ul class="mui-table-view mui-table-view-chevron">
</ul>
</div>
</div>
</div>
<div class="news-item-model-single hide">
<div class="flex-div">
<div class="news-title flex-element3-no-padding">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
<div class="news-image flex-element-no-padding">
<img class="img-01" src="http://05.imgmini.eastday.com/mobile/20161011/20161011123754_349328718cfee5b4d04cd614f1033e3a_1_mwpm_03200403.jpeg" />
</div>
</div>
<div class="news-other flex-div">
<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
</div>
</div>
<div class="news-item-model-multi hide">
<div class="news-title">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
<div class="news-image-multi flex-div">
<div class="flex-element-no-padding">
</div>
<div class="flex-element-no-padding">
</div>
<div class="flex-element-no-padding">
</div>
</div>
<div class="news-other flex-div">
<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
</div>
</div>
<div class="news-item-model-none hide">
<div class="news-title">当你最穷的时候,这样做,不成百万富翁至少也是土豪!</div>
<div class="news-other flex-div">
<div class="flex-element-no-padding author-name">中国企业家俱乐部</div>
<div class="flex-element-no-padding pub-date text-right">2016-10-11 12:37</div>
</div>
</div>
</body>
</html>
</code></pre></div></div>
<h4 id="内容列表是一个常规的html5界面定义了一个上拉下拉刷新的容器我们来看看index的js定义">内容列表是一个常规的html5界面,定义了一个上拉下拉刷新的容器,我们来看看index的js定义</h4>
<h4 id="indexjs">index.js</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var storage = window.localStorage;
var _tl = getTLInstance();
var detailObj = {}, //新闻内容
pageObj = {}, //每个模块的分页数
transObj = {}; //每个模块的滚动条位置
var refreshArt = []; //存储新闻标题的临时数组,防止新闻重复出现
//初始化加载栏
mui.init({
pullRefresh: {
container: '#pullrefresh',
up: {
contentrefresh: '正在加载更多...',
callback: doUpLoading
},
down: {
contentrefresh: '正在刷新...',
callback: doDownLoading
}
}
});
</code></pre></div></div>
<h4 id="这一部分定义了此页面拥有上拉和下拉的功能并且指定了相应的容器同时指定了上拉和下拉的回调函数douploadingdodownloading">这一部分定义了此页面拥有上拉和下拉的功能,并且指定了相应的容器,同时指定了上拉和下拉的回调函数doUpLoading、doDownLoading</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
$(function() {
//延迟加载,等待WebView初始化完成
if(mui.os.plus) {
mui.plusReady(function() {
setTimeout(function() {
triggerUp();
tempSolution();
}, 500);
});
} else {
mui.ready(function() {
setTimeout(function() {
triggerUp();
tempSolution();
}, 100);
});
}
//纪录每个模块的滚动条变化
setTimeout(function() {
if(mui.os.android) {
//Android不识别webkitTransform,因此还是使用的滚动条
$(window).scroll(function(e) {
transObj[storage.getItem('selectType')] = document.body.scrollTop;
})
} else {
//IOS、Html识别webkitTransform
$('.mui-scroll').on('touchend touchcancel', function(e) {
var str = document.querySelector('.mui-scroll').style.webkitTransform;
transObj[storage.getItem('selectType')] = $.trim(str.substring(str.indexOf(',') + 1, str.lastIndexOf(',')));
if(parseInt(transObj[storage.getItem('selectType')]) > 0) {
transObj[storage.getItem('selectType')] = '0px';
}
})
}
}, 2100)
})
</code></pre></div></div>
<h4 id="这一部分定义了页面初始化时要做的事情触发一个上拉刷新加载内容同时绑定滚动事件记录当前频道的阅读位置并且存储起来">这一部分定义了页面初始化时要做的事情:触发一个上拉刷新加载内容、同时绑定滚动事件记录当前频道的阅读位置并且存储起来</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//触发下拉刷新事件
function triggerUp() {
var selectedType = storage.getItem('selectType');
pageObj[selectedType] = 1;
var newId = 'news-list-' + selectedType;
$('.news-list').hide();
//模块初始化后保留上次的加载纪录,避免切换时再次刷新,提高友好度
if(!$('.' + newId).html()) {
$('.mui-table-view').append('<div class="news-list ' + newId + '"><div></div></div>');
mui('#pullrefresh').pullRefresh().pulldownLoading();
} else {
$('.' + newId).show();
//自动定位到每个模块的滚动条位置
if(mui.os.android) {
document.body.scrollTop = transObj[selectedType];
} else {
mui('.mui-scroll-wrapper').scroll().scrollTo(0, parseInt(transObj[selectedType]), 100);
}
}
}
//下拉刷新操作
function doDownLoading() {
pageObj[storage.getItem('selectType')] = 1;
loadNews();
}
//上拉加载更多操作
function doUpLoading() {
pageObj[storage.getItem('selectType')]++;
loadNews();
}
</code></pre></div></div>
<h4 id="这一段定义了函数上拉下拉要做的事情其中triggerup做了2件事情1当点击的频道为第一次加载时从服务端获取数据2当点击的频道为已加载时自动定位到上次阅读的位置其中loadnews为第一段ajax获取数据的部分">这一段定义了函数上拉下拉要做的事情;其中triggerUp做了2件事情:1、当点击的频道为第一次加载时,从服务端获取数据;2、当点击的频道为已加载时,自动定位到上次阅读的位置,其中loadNews()为第一段ajax获取数据的部分</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//临时解决方案,定时扫描type值,如发生变化则执行刷新操作(IOS、Android无法调用iframe方法)
var lastType;
function tempSolution() {
setInterval(function() {
if(!lastType) {
lastType = storage.getItem('selectType');
} else {
if(lastType != storage.getItem('selectType')) {
lastType = storage.getItem('selectType');
triggerUp();
}
}
}, 300)
}
</code></pre></div></div>
<h3 id="c新闻详情页由于百度的接口返回的列表中包含了新闻的主体内容因此在点击新闻时直接将内容传入展示">C、新闻详情页,由于百度的接口返回的列表中包含了新闻的主体内容,因此在点击新闻时,直接将内容传入展示</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="#fc3434">
<link rel="stylesheet" href="../js/common/swiper/css/swiper.min.css" />
<link rel="stylesheet" href="../css/mui.min.css" />
<link rel="stylesheet" href="css/newsDetail.css" />
<title></title>
<script type="text/javascript" src="../js/common/jquery.js"></script>
<script type="text/javascript" src="../js/common/flexible/flexible.js"></script>
<script type="text/javascript" src="../js/common/swiper/js/swiper.jquery.min.js"></script>
<script type="text/javascript" src="../js/common/circle-progress.js"></script>
<script type="text/javascript" src="../js/common/tools.js"></script>
<script type="text/javascript" src="../js/mui.min.js"></script>
<script type="text/javascript" src="js/newsDetail.js"></script>
</head>
<body>
<div class="x-panel-top">
<div class="x-panel-top-left" onclick="mui.back()">
<span class="mui-icon mui-icon-back"></span>
</div>
<div class="x-panel-top-center">
<div class="x-title-bar">
热点新闻
</div>
</div>
<div class="x-panel-top-right">
</div>
</div>
<div class="x-panel-content">
<article id="J_article" class="J-article article">
<div id="title">
<div class="article-title">
<h1 class="title"></h1>
</div>
<div class="article-src-time">
<span class="src"></span>
</div>
</div>
<div id="content" class="J-article-content article-content" data-pswp-uid="1">
</div>
</article>
</div>
</body>
</html>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var storage = window.localStorage;
var _tl = getTLInstance();
var _plus;
mui.plusReady(function() {
_plus = plus;
})
$(function() {
var news = JSON.parse(storage.getItem('newsContent'));
$('.title').html(news.title);
$('.src').html(news.pubDate+'&nbsp;&nbsp;&nbsp;&nbsp;来源:'+news.source);
var newsContent = news.allList;
for(var i=0;i<newsContent.length;i++){
if(typeof(newsContent[i]) == 'string'){
$('.article-content').append('<p class="section txt">'+newsContent[i]+'</p>');
}else{
$('.article-content').append('<figure class="section img"><a class="img-wrap"><img src="'+newsContent[i].url+'"></a></figure>');
}
}
})
</code></pre></div></div>
<h2 id="四app打包及真机查看">四、APP打包及真机查看</h2>
<h3 id="a-真机调试">A 真机调试</h3>
<h4 id="mac电脑安装xcode后即可使用模拟器进行ios调试连接iphone后也可以进行真机运行">mac电脑安装xcode后,即可使用模拟器进行IOS调试、连接iphone后也可以进行真机运行</h4>
<h4 id="mac电脑连接android手机也可以进行真机调试android手机务必打开开发人员选项usb调试">mac电脑连接android手机也可以进行真机调试,android手机务必打开“开发人员选项”-“USB调试”</h4>
<h4 id="window电脑暂时不能使用ios模拟器可使用真机运行">window电脑暂时不能使用IOS模拟器,可使用真机运行</h4>
<h3 id="b-打包应用">B 打包应用</h3>
<h4 id="右键应用选择发行发行为原生安装包按操作要求进行即可需说明">右键应用选择“发行”-“发行为原生安装包”按操作要求进行即可,需说明:</h4>
<h4 id="android打包需要定义keystore即签名文件">Android打包需要定义keystore,即签名文件</h4>
<h4 id="ios打包需要从apple开发者平台下载证书和描述文件才能打包在下面的章节我会详细的说明如何下载苹果证书创建安卓签名文件">IOS打包需要从apple开发者平台下载证书和描述文件才能打包,在下面的章节我会详细的说明如何下载苹果证书、创建安卓签名文件</h4>
<h2 id="结束语">结束语</h2>
<h4 id="通过上面的例子结合实例和mui官网的介绍可以很快速的入门进行h5的app开发">通过上面的例子、结合实例和mui官网的介绍,可以很快速的入门进行h5的APP开发</h4>
Storm(2)-Storm集群的搭建
2016-11-20T00:00:00+00:00
http://www.blogways.net/blog/2016/11/20/storm-2
<blockquote>
<p>安装Storm集群,需要依赖以下组件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Zookeeper
Python
Zeromq
Storm
JDK
JZMQ
</code></pre></div> </div>
</blockquote>
<blockquote>
<p>我准备了3台机器,并在每台机器上配置主机别名,$ vi /etc/hosts</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161120img01.png" alt="20161120img01" /></p>
<h2 id="1搭建zookeeper集群">1.搭建Zookeeper集群</h2>
<h4 id="1安装zookeeper">(1)安装zookeeper</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tar zxvf zookeeper-3.4.6.tar.gz
</code></pre></div></div>
<h4 id="2修改zoocfg">(2)修改zoo.cfg</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ cp -rf conf/zoo_sample.cfg conf/zoo.cfg
$ vi zoo.cfg
</code></pre></div></div>
<blockquote>
<p>内容:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tickTime=2000
dataDir=/usr/local/software/zookeeper-3.4.6/data
clientPort=2181
initLimit=5
syncLimit=2
server.1=host1:2888:3888
server.2=host2:2889:3889
server.3=host3:2890:3890
</code></pre></div></div>
<h4 id="3在datadir的目录中添加myid并且编辑内容分别对应server的序号分别是123">(3)在”dataDir”的目录中添加myid,并且编辑内容分别对应server的序号,分别是1、2、3</h4>
<h4 id="4在三台机器上分别启动">(4)在三台机器上分别启动</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./bin/zkServer.sh start ./conf/zoo.cfg
</code></pre></div></div>
<blockquote>
<p>通过$ ./bin/zkServer.sh status 查看zookeeper的leader</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161120img02.png" alt="20161120img02" /></p>
<p><img src="/images/zhaojiajun/20161120img03.png" alt="20161120img03" /></p>
<p><img src="/images/zhaojiajun/20161120img04.png" alt="20161120img04" /></p>
<h2 id="2安装jdk">2.安装JDK</h2>
<h4 id="1-tar-zxvf-jdk-7u79-linux-x64targz">(1) tar zxvf jdk-7u79-linux-x64.tar.gz</h4>
<h4 id="2-配置环境变量">(2) 配置环境变量</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ vi /etc/profile
</code></pre></div></div>
<p><img src="/images/zhaojiajun/20161120img05.png" alt="20161120img05" /></p>
<blockquote>
<p>运行$ source /etc/profile,使配置环境生效</p>
</blockquote>
<h4 id="3查看java版本">(3)查看java版本</h4>
<p><img src="/images/zhaojiajun/20161120img06.png" alt="20161120img06" /></p>
<h2 id="3安装jzmq">3.安装JZMQ</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone git://github.com/nathanmarz/jzmq.git
$ cd jzmq
$ ./autogen.sh
$ ./configure
$ make & make install
</code></pre></div></div>
<h2 id="4安装zeromq">4.安装Zeromq</h2>
<blockquote>
<p>安装ZeroMQ所需组件及工具:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ yum install gcc gcc-c++ make uuid-devel libuuid-devel libtool
$ tar zxvf zeromq-4.2.0.tar.gz
$ cd zeromq-4.2.0
</code></pre></div></div>
<blockquote>
<p>手动创建classdist_noinst.stamp空文件</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ touch src/classdist_noinst.stamp
</code></pre></div></div>
<blockquote>
<p>进入文件夹 jzmq/src/org/zeromq,手动编译Java代码 $ javac *.java</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./configure
$ make & make install
</code></pre></div></div>
<h2 id="5搭建storm集群">5.搭建Storm集群</h2>
<blockquote>
<p>(1) 安装包</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ tar zxvf apache-storm-1.0.2.tar.gz
</code></pre></div></div>
<blockquote>
<p>(2) 配置环境变量</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161120img07.png" alt="20161120img07" /></p>
<blockquote>
<p>(3) 建立storm存储目录</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ mkdir /tmp/storm
</code></pre></div></div>
<blockquote>
<p>(4) 把host1作为nimbus和ui的服务器。host1、host2、host3为supervisor服务器,修改配置文件/conf/storm.yaml, 如下配置:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>storm.zookeeper.servers:
- "host1"
- "host2"
- "host3"
ui.port: 8081
nimbus.host: "host1"
storm.local.dir: "/tmp/storm"
supervisor.slots.ports:
- 6700
- 6701
- 6702
- 6703
</code></pre></div></div>
<blockquote>
<p><strong>其中,配置参数说明:</strong></p>
<p>• storm.zookeeper.servers:Storm集群使用的Zookeeper集群地址,如果Zookeeper集群使用的不是默认端口,那么还需要storm.zookeeper.port选项。</p>
<p>• ui.port:Storm UI的服务端口</p>
<p>• storm.local.dir:Nimbus和Supervisor进程用于存储少量状态,如jars、confs等的本地磁盘目录</p>
<p>• nimbus.host: Storm集群Nimbus机器地址</p>
<p>• supervisor.slots.ports: 对于每个Supervisor工作节点,需要配置该工作节点可以运行的worker数量。每个worker占用一个单独的端口用于接收消息,该配置选项即用于定义哪些端口是可被worker使用的。默认情况下,每个节点上可运行4个workers,分别在6700、6701、6702和6703端口</p>
</blockquote>
<h2 id="6启动storm集群">6.启动Storm集群</h2>
<h4 id="1-nimbus">(1) Nimbus:</h4>
<blockquote>
<p>在Storm主控节点(本文中为host1)上运行”bin/storm nimbus >/dev/null 2>&1 &”启动Nimbus后台程序,并放到后台执行;</p>
</blockquote>
<h4 id="2-supervisor">(2) Supervisor:</h4>
<blockquote>
<p>在Storm各个工作节点(本文中为host1/host2/host3)上运行”bin/storm supervisor >/dev/null 2>&1 &”启动Supervisor后台程序,并放到后台执行;</p>
</blockquote>
<h4 id="3-ui">(3) UI:</h4>
<blockquote>
<p>在Storm主控节点(本文中为host1)上运行”bin/storm ui >/dev/null 2>&1 &”启动UI后台程序,并放到后台执行。</p>
</blockquote>
<h2 id="7查看storm-ui">7.查看Storm UI</h2>
<blockquote>
<p>请求 <em>http://192.168.137.131:8081/</em></p>
</blockquote>
<p><img src="/images/zhaojiajun/20161120img08.png" alt="20161120img08" /></p>
<p><strong>自此storm集群环境就搭建完毕了。</strong></p>
Storm(1)-Storm基本概念
2016-11-19T00:00:00+00:00
http://www.blogways.net/blog/2016/11/19/storm-1
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">Storm简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">Storm集群的组成</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">Storm基本概念</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">Storm流分组</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一storm简介">一、Storm简介</h2>
<h4 id="storm是什么"><strong>Storm是什么?</strong></h4>
<blockquote>
<p>Storm是一个开源的分布式实时计算系统,它提供了一系列的基本元素用于进行计算:Topology(拓扑)、Stream、Spout、Bolt等等。</p>
</blockquote>
<h4 id="storm的解决的是什么"><strong>Storm的解决的是什么?</strong></h4>
<blockquote>
<p>在Storm之前,实时计算的实现用的是标准队列和worker的方法。比如, 我们向一个队列集合里面写入data, 再用worker从这个队列集合读取data并处理他们。 通常情况下这些worker需要通过另一个队列集合向另一个worker集合发送消息来进一步处理这些data。</p>
</blockquote>
<blockquote>
<p>我们非常不满意这种处理方式,这种方法不稳定–我们必须要保证所有的队列和worker一直处于工作状态,并且在构建应用时它也显得很笨重。 应用的大部分逻辑都集中在从哪发送/获取信息和怎样序列化/反序列化这些消息等等。但是在实际的业务逻辑里面它只是代码库的一小部分。再加上一个应用的正确逻辑应该是可以跨多个worker,并且这些worker之间是可以独立部署的,一个应用的逻辑也应该是自我约束的。</p>
</blockquote>
<blockquote>
<p>Storm通过对 之前需要处理的繁重工作-发送/接收消息,序列化,部署等 这些操作的抽象实现了自动化。</p>
</blockquote>
<p><a id="2nd"></a></p>
<h2 id="二storm集群的组成">二、Storm集群的组成</h2>
<blockquote>
<p>Storm集群里有两种节点: 控制节点和工作节点。控制节点上运行一个叫Nimbus后台程序,Nimbus负责在集群里面分发代码,分配计算任务给机器和监控状态。工作节点上运行一个叫做Supervisor的进程,Supervisor监听分配到机器的任务,根据需要启动/关闭工作进程worker。</p>
</blockquote>
<blockquote>
<p>每一个工作进程执行一个topology的一个子集,一个运行的topology由运行在很多机器上的很多工作进程worker组成。(一个supervisor里面有多个workder,可以配置worker的数量,对应的是conf/storm.yaml中的upervisor.slot的数量)</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161119img01.png" alt="20161119img01" /></p>
<blockquote>
<p>Nimbus和Supervisor之间的所有协调工作都是通过Zookeeper集群完成,除此之外,Nimbus后台程序和Supervisor后台程序是快速失效和无状态的。所有的状态保持在Zookeeper中或者是本地磁盘中,这意味着你能关闭Nimbus或者Supervisor而他们将会再次启动就像什么事情都没有发生过一样,这个设计使得Storm异常的稳定。</p>
</blockquote>
<p><a id="3nd"></a></p>
<h2 id="三storm基本概念">三、Storm基本概念</h2>
<h4 id="数据流topology"><strong>数据流Topology</strong></h4>
<blockquote>
<p>在Storm中,一个实时应用的计算任务被打包作为Topology发布,Topology任务一旦提交后永远不会结束,除非你显示去停止任务。计算任务Topology是由不同的Spouts和Bolts,通过数据流(Stream)连接起来的图。</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161119img02.png" alt="20161119img02" /></p>
<blockquote>
<p><strong>其中包含有:</strong></p>
</blockquote>
<blockquote>
<p>Spout:Storm中的消息源,用于为Topology生产消息(数据),一般是从外部数据源(如Message Queue、RDBMS、NoSQL、Realtime Log )不间断地读取数据并发送给Topology消息(tuple元组)。</p>
</blockquote>
<blockquote>
<p>Bolt:Storm中的消息处理者,用于为Topology进行消息的处理,Bolt可以执行过滤,聚合,查询数据库等操作,而且可以一级一级的进行处理。</p>
</blockquote>
<h4 id="数据模型tuple"><strong>数据模型Tuple</strong></h4>
<blockquote>
<p>Storm使用tuple来作为它的数据模型,每个tuple是一堆有名字的值,每个值可以是任何类型,可以理解为一个tuple是一个java对象。如果使用自定义类型来作为值类型,需要对值类型进行序列化。</p>
</blockquote>
<blockquote>
<p>一个Tuple代表数据流中的一个基本的处理单元,它可以包含多个Field,每个Field表示一个属性。如:三个字段(taskID:int; StreamID:String; ValueList: List):</p>
</blockquote>
<h4 id="worker上执行的task"><strong>Worker上执行的Task</strong></h4>
<blockquote>
<p>worker中每一个spout/bolt的线程称为一个task,同一个spout/bolt的task可能会共享一个物理线程,该线程称为executor。</p>
</blockquote>
<h4 id="storm的记录级容错"><strong>Storm的记录级容错</strong></h4>
<blockquote>
<p>相比于其他实时计算系统,Storm最大的亮点在于其记录级容错和能够保证消息精确处理的事务功能。Storm中记录级容错的意思是,Storm会告知用户每一个消息单元是否在指定时间内被完全处理了。如下图:</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161119img03.png" alt="20161119img03" /></p>
<blockquote>
<p>在Storm的topology中有一个系统级组件,叫做acker。这个acker的任务就是追踪从spout中流出来的每一个message id绑定的若干tuple的处理路径,如果在用户设置的最大超时时间内这些tuple没有被完全处理,那么acker就会告知spout该消息处理失败了,相反则会告知spout该消息处理成功了。</p>
</blockquote>
<h4 id="storm的事务拓扑"><strong>Storm的事务拓扑</strong></h4>
<blockquote>
<p>事务拓扑简单来说就是将消息分为一个个的批(batch),同一批内的消息以及批与批之间的消息可以并行处理,另一方面,用户可以设置某些bolt为committer,storm可以保证committer的finishBatch()操作是按严格不降序的顺序执行的。用户可以利用这个特性通过简单的编程技巧实现消息处理的精确。</p>
</blockquote>
<h2 id="四storm流分组">四、Storm流分组</h2>
<blockquote>
<p>Stream Grouping定义了一个流在Bolt任务间该如何被切分,就是哪个Task来处理哪些数据,按什么规则来分配。</p>
<p><strong>Storm提供的6种类型:</strong></p>
<ol>
<li>
<p>随机分组(Shuffle grouping):随机分发tuple到Bolt的任务,保证每个任务获得相等数量的tuple。</p>
</li>
<li>
<p>字段分组(Fields grouping):根据指定字段分割数据流,并分组。例如,根据“color”字段,相同“color”的元组总是分发到同一个任务,不同“color”的元组可能分发到不同的任务。</p>
</li>
<li>
<p>全部分组(All grouping):tuple被复制到bolt的所有任务,广播方式。</p>
</li>
<li>
<p>全局分组(Global grouping):全部流都分配到bolt的同一个任务。明确地说,是分配给ID最小的那个task。</p>
</li>
<li>
<p>无分组(None grouping):你不需要关心流是如何分组。目前,无分组等效于随机分组。</p>
</li>
<li>
<p>直接分组(Direct grouping):这是一个特别的分组类型。元组生产者决定tuple由哪个元组处理者任务接收。</p>
</li>
</ol>
</blockquote>
Mysql索引使用
2016-11-11T00:00:00+00:00
http://www.blogways.net/blog/2016/11/11/mysql-index
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">基本原理</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">索引类型</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">索引方式</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">注意事项</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一基本原理">一、基本原理</h2>
<blockquote>
<p>数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。</p>
<p>当数据保存在磁盘类存储介质上时,它是作为数据块存放。这些数据块是被当作一个整体来访问的,这样可以保证操作的原子性。硬盘数据块存储结构类似于链表,都包含数据部分,以及一个指向下一个节点(或数据块)的指针,不需要连续存储。</p>
<p>记录集只能在某个关键字段上进行排序,所以如果需要在一个无序字段上进行搜索,就要执行一个线性搜索的过程,平均需要访问N/2的数据块,N是表所占据的数据块数目。如果这个字段是一个非主键字段(也就是说,不包含唯一的访问入口),那么需要在N个数据块上搜索整个表格空间。</p>
<p>但是对于一个有序字段,可以运用二分查找,这样只要访问log2(N)的数据块。这就是为什么性能能得到本质上的提高。</p>
</blockquote>
<p><a id="2nd"></a></p>
<h2 id="二索引类型">二、索引类型</h2>
<blockquote>
<p><strong>Normal(普通索引)</strong></p>
<p>最基本的索引,没有任何限制。索引长度必须是固定的,MySQL所允许的最大索引长度是255个字符。</p>
<p><strong>Unique(唯一索引)</strong></p>
<p>与普通索引的不同就是索引列的值必须唯一,但允许有空值。</p>
<p>(1)单列唯一索引。</p>
<p>(2)主键索引,是一种特殊的唯一索引,不允许有空值。</p>
<p>(3)组合索引,多列组合成一个索引,组合值必须唯一。</p>
<p><strong>Full Text(全文检索)</strong></p>
<p>普通索引只能加快字段内容开头字符的检索,如果字段是多个单词构成 的较大段文字,普通索引就没什么作用。
这种检索往往以LIKE %word%的形式出现,如果需要处理的数据量很大,响应时间就会很长。
MySQL将把在文本中出现的所有单词创建为一份清单,查询操作将根据这份清单去检索有关的数据记录。</p>
</blockquote>
<p><a id="3nd"></a></p>
<h2 id="三索引方式">三、索引方式</h2>
<blockquote>
<p><strong>1.Hash方式</strong></p>
<p>Hash 索引就是把这一列进行哈希算法计算,得到哈希值,排序在哈希数组上,索引的检索可以一次定位。 其效
率很高,不像BTree 索引需要从根节点到枝节点,所以 Hash 索引的查询效率要远高于 BTree 索引。</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161111img01.png" alt="20161006img07" /></p>
<blockquote>
<p>(1)不能使用hash索引排序。</p>
<p>(2)Hash索引不支持键的部分匹配,因为是通过整个索引值来计算hash值的。</p>
<p>(3)Hash索引只支持等值比较查询,如:=,in(),<=精确查询。对于WHERE age>20并不能加速查询。</p>
</blockquote>
<blockquote>
<p><strong>2.BTree方式</strong></p>
<p>Btree索引是以B+树为存储结构实现的,但是Btree索引的存储结构在Innodb和MyISAM中有很大区别。
MyISAM的索引方式也称为非聚集,Innodb的索引方式成为聚集索引。</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161111img02.png" alt="20161006img07" /></p>
<h4 id="存储引擎-myisam和innodb的区别"><strong>存储引擎-MyIsAM和InnoDB的区别</strong></h4>
<blockquote>
<p>(1)MyISAM类型不支持事务处理等高级处理,而InnoDB类型支持。</p>
<p>(2)MyISAM是表级锁,而InnoDB是行级锁。</p>
<p>(3)InnoDB不支持FULLTEXT类型的索引,不保存表的具体行数。</p>
<p>(4)MyISAM表不支持外键</p>
<p>(5)对于自增长类型的字段,InnoDB中必须包含只有该字段的索引,但是在MyISAM表中,可以和其他字段一起建立联合索引。</p>
<p>因此,当你的数据库有大量的写入、更新操作而查询比较少或者数据完整性要求比较高的时候就选择InnoDB表。当你的数据库主要以查询为主,相比较而言更新和写入比较少,并且业务方面数据完整性要求不那么严格,就选择MyISAM表。因为MyISAM表的查询操作效率和速度都比InnoDB要快。</p>
</blockquote>
<p><a id="end"></a></p>
<h2 id="四注意事项">四、注意事项</h2>
<blockquote>
<p><strong>数据库能同时使用多个索引</strong></p>
<p>SELECT * FROM TB WHERE A=5 AND B=6</p>
<p>能分别使用索引(A) 和 (B);</p>
<p>对于这个语句来说,创建组合索引(A,B) 更好;</p>
<p>最终是采用组合索引,还是两个单列索引?主要取决于应用系统中是否存在这类语句:</p>
<p>SELECT * FROM TB WHERE B=6</p>
<p>SELECT * FROM TB WHERE A=5 OR B=6</p>
<p>组合索引(A, B)不能用于此查询,很明显,分别创建索引(A) 和 (B)会更好;</p>
<p><strong>删除无效的冗余索引</strong></p>
<p>TB表有两个索引(A, B) 和 (A),对应两种SQL语句:SELECT * FROM TB WHERE A=5 AND B=6 和 SELECT * FROM TB WHERE A=5</p>
<p>执行时,并不是WHERE A=5 就用 (A); WHERE A=5 AND B=6 就用 (A, B);</p>
<p>其查询优化器会使用其中一个以前常用索引,要么都用(A, B), 要么都用 (A)。</p>
<p>所以应该删除索引(A),它已经被(A, B)包含了,没有任何存在的必要。</p>
</blockquote>
<blockquote>
<p><strong>使用场景需要注意:</strong></p>
<p>(1) 频繁的作为查询条件的字段应该创建为索引。</p>
<p>(2) 唯一性很差的字段不适合做索引(如:性别),因为就算建立了索引,二叉树也就只有一层,还是要大规模的进行表的扫描。</p>
<p>(3) 更新很频繁的字段不适合作为索引,因为每次操作的时候都会遍历,修改或者删除索引,这样会降低更新表的速度。</p>
<p>(4) 在列中有复合索引时,只要查询条件有使用最左边的列,索引一般就会被使用到。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>复合索引:alter table dept add index my_index(dname,loc);//dname是左边的列。
</code></pre></div> </div>
<p>(5) 模糊查询 like ‘%a’(%为前缀)、not in、<>条件 不会用到索引,’a%’(%为后缀)会用到索引。</p>
<p>(6) 索引的数据类型越小越简单越好,整型数据比起字符,处理开销更小。</p>
<p>(7) 只要列中包含有NULL值都将不会被包含在索引中,复合索引中只要有一列含有NULL值,那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为NULL。</p>
<p>(8) 不要在列上进行函数运算,如select * from users where YEAR(xxdate)<2007;应该写为:select * from users where xxdate<’2007-01-01’;</p>
</blockquote>
使用SourceTree进行gogs的操作
2016-10-22T00:00:00+00:00
http://www.blogways.net/blog/2016/10/22/git-sourceTree
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#begin">什么是sourceTree</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#1st">如何安装sourceTree</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#2nd">如何在gogs上创建仓库</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#3rd">如何克隆gogs上的工程</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#4ur">如何在gogs仓库中添加目录和文件</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#end">如何新增或修改文件,并提交到gogs上去</a></td>
</tr>
</tbody>
</table>
<p><a id="begin"></a></p>
<h2 id="1什么sourcetree">1.什么sourceTree</h2>
<p>SourceTree 是 Windows 和Mac OS X 下免费的 Git 和 Hg 客户端,拥有可视化界面,容易上手操作。同时它也是Mercurial和Subversion版本控制系统工具。支持创建、提交、clone、push、pull 和merge等操作。</p>
<p><a id="1st"></a></p>
<h2 id="2如何安装sourcetree">2.如何安装sourceTree</h2>
<p>(1) 点击连接<a href="https://www.sourcetreeapp.com/">sourceTree</a>下载对应版本的sourceTree,官网里有Mac和win版本的,根据自己的电脑下载对应版本,下载之前确认电脑是否已经安装的git工具。</p>
<blockquote>
<p>若没有安装可以点击<a href="https://git-scm.com/downloads">git下载</a>,进行安装。</p>
</blockquote>
<p>(2)双击下载的.exe文件,可以看到下面界面
<img src="/images/chenfan/sourceTree-install-1.png" alt="sourceTree-install-1.png" /></p>
<p>点击Next,选择安装的本机路径,最后点击install即可</p>
<p><img src="/images/chenfan/sourceTree-install-2.png" alt="sourceTree-install-2.png" /></p>
<p><img src="/images/chenfan/sourceTree-install-3.png" alt="sourceTree-install-3.png" /></p>
<p>(3)安装完成,会弹出如下对话框,你可以选择自动下载。我用的是git 直接选择跳过就可以了</p>
<p><img src="/images/chenfan/sourceTree-install-4.png" alt="sourceTree-install-4.png" /></p>
<p>会显示正在下载文件</p>
<p>(4)若没有在github进行注册,建议注册后使用github进行登陆,安装到此结束。</p>
<p><a id="2nd"></a></p>
<h2 id="3如何在gogs上创建仓库">3.如何在gogs上创建仓库</h2>
<p>(1)首先在gogs上你的用户,联系gogs管理员为你分配权限</p>
<p>(2)登陆gogs<a href="http://10.20.16.78:3000">http://10.20.16.78:3000</a>,进入你的组织</p>
<p>(3)点击<img src="/images/chenfan/sourceTree-used-1.png" alt="sourceTree-used-1.png" />创建你自己的远程仓库</p>
<p><a id="3rd"></a></p>
<h2 id="4如何克隆gogs上的工程">4.如何克隆gogs上的工程</h2>
<p>点击克隆/新建命令,弹出以下对话框</p>
<p><img src="/images/chenfan/sourceTree-used-2.png" alt="sourceTree-used-2.png" /></p>
<p>源路径为要克隆的gogs工程的url,url地址可以登陆gogs进行查找</p>
<p>目标路径为本机的工程存放路径,点击克隆即可</p>
<p><a id="4ur"></a></p>
<h2 id="5如何在gogs仓库中添加目录和文件">5.如何在gogs仓库中添加目录和文件</h2>
<p>(1)点击配置选项,点击添加,添加远程仓库
<img src="/images/chenfan/sourceTree-used-3.png" alt="sourceTree-used-3.png" /></p>
<p>远程仓库指的是在gogs上创建的仓库,点击确定</p>
<p>(2)方式1(远程仓库为空的情况):</p>
<p>1.点击克隆/新建,选择创建新仓库,
<img src="/images/chenfan/sourceTree-used-4.png" alt="sourceTree-used-4.png" /></p>
<p>目标路径为你本地存放路径,在这个路径下你可以存放你要上传的文件目录等,点击创建,可以看到左侧出现,<img src="/images/chenfan/sourceTree-used-6.png" alt="sourceTree-used-6.png" />,把要上传的工程放在此本机目录下,即为</p>
<p><img src="/images/chenfan/sourceTree-used-7.png" alt="sourceTree-used-7.png" /></p>
<p>点击未暂存文件,可以看到未暂存的文件已经存放到以暂存文件中</p>
<p>2.点击</p>
<p><img src="/images/chenfan/sourceTree-used-8.png" alt="sourceTree-used-8.png" /></p>
<p>提交按钮,即出现</p>
<p><img src="/images/chenfan/sourceTree-used-9.png" alt="sourceTree-used-9.png" /></p>
<p>点击提交即可</p>
<p>3.在最上面工具栏点击推送按钮,即
<img src="/images/chenfan/sourceTree-used-10.png" alt="sourceTree-used-10.png" /></p>
<p>点击确定,本地的要上传的东西提交到gogs远程仓库中,可以登陆gogs查看是否提交成功</p>
<p>(3)方式2(克隆)</p>
<p>1.点击克隆,克隆你在gogs创建的远程仓库,在本地该目录中存放你需要上传的东西</p>
<p>2.点击未暂存文件,可以看到未暂存的文件已经存放到以暂存文件中,即重复方式1,点击提交,推送</p>
<p><a id="end"></a></p>
<h2 id="6如何新增或修改文件并提交到gogs上去">6.如何新增或修改文件,并提交到gogs上去</h2>
<p>(1)克隆工程,在本地仓库中修改,添加文件,打开sourceTree</p>
<p>会出现下图</p>
<p><img src="/images/chenfan/sourceTree-used-11.png" alt="sourceTree-used-11.png" /></p>
<p><img src="/images/chenfan/sourceTree-used-12.png" alt="sourceTree-used-12.png" /></p>
<p>点击未暂存的文件,将其加入已暂存文件,重复5中的方式1,点击提交、推送,即将修改的文件工程添加到远程gogs中</p>
初识APP混合应用开发框架 -- mui
2016-10-09T00:00:00+00:00
http://www.blogways.net/blog/2016/10/09/web-mui-01
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">初识DCloud\HBuilder\5+\mui</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一什么是mui">一、什么是mui</h2>
<h3 id="mui是dcloud推出的一款最接近原生app体验的高性能前端框架">mui是DCloud推出的一款最接近原生APP体验的高性能前端框架</h3>
<h4 id="以下是dcloud官方的介绍">以下是DCloud官方的介绍:</h4>
<h4 id="html5自出现以来几经风雨虽看似很有前途但实际使用问题太多dcloud为此踩了无数坑但我们从未放弃我们加入了w3c发起了html5中国产业联盟推出了hbuilderhtml5plus-runtimemui框架等产品直到我们终于可以使用html5开发出原生体验的app并且把这些技术公开给开发者">HTML5自出现以来,几经风雨,虽看似很有前途,但实际使用问题太多,DCloud为此踩了无数坑。但我们从未放弃,我们加入了W3C,发起了HTML5中国产业联盟,推出了HBuilder、HTML5plus runtime、mui框架等产品,直到我们终于可以使用HTML5开发出原生体验的App,并且把这些技术公开给开发者。</h4>
<h4 id="html5过去被称为有性工能障碍即性能不如原生工具不如原生功能不如原生">HTML5过去被称为有“性工能”障碍,即性能不如原生,工具不如原生、功能不如原生。</h4>
<p><img src="/images/liuyw6/20161009img01.png" alt="20161009img01" /></p>
<h4 id="为了解决这些问题dcloud在以下方面做了升级优化hbuilder-和-html5plus-runtimemui框架等等下面我们就围绕性工能开始介绍">为了解决这些问题,DCloud在以下方面做了升级优化:HBuilder 和 HTML5plus runtime、mui框架等等,下面我们就围绕“性工能”开始介绍</h4>
<h2 id="工具---hbuilder">工具 - HBuilder</h2>
<h3 id="a起因">A、起因</h3>
<h4 id="html最开始其实不是一个编程语言确实用不着什么ide">HTML最开始其实不是一个编程语言,确实用不着什么ide。</h4>
<h4 id="但是发展到现在7w多个语法js越来越庞大真开发一个达到原生水准的app不是用以前的文本编辑器能搞定的">但是发展到现在,7w多个语法,js越来越庞大,真开发一个达到原生水准的App,不是用以前的文本编辑器能搞定的。</h4>
<h4 id="目前竟然没有一个开发工具能把7w多html5语法提示齐全这不科学在原生开发里这是不可想象的xcode之于ioseclipse之于androidvs之于winphone在语法提示转到定义重构调试等方面都非常高效">目前竟然没有一个开发工具能把7w多HTML5语法提示齐全,这不科学,在原生开发里这是不可想象的,xcode之于iOS,eclipse之于Android,vs之于winphone,在语法提示、转到定义、重构、调试等方面都非常高效。</h4>
<h4 id="作为同时熟悉原生和html5开发的我们我们在开发html5时明显感受到效率低下">作为同时熟悉原生和HTML5开发的我们,我们在开发HTML5时明显感受到效率低下。</h4>
<h3 id="b强大的语法提示">B、强大的语法提示</h3>
<h4 id="dcloud花费了很大心血建成了最全的html5及浏览器扩展前缀语法库也开发了强大的语法解析引擎然后推出了开发工具hbuilder使得开发者可以准确高效的编写html5代码而且全免费">DCloud花费了很大心血建成了最全的HTML5及浏览器扩展前缀语法库,也开发了强大的语法解析引擎,然后推出了开发工具HBuilder,使得开发者可以准确、高效的编写HTML5代码。而且全免费</h4>
<h3 id="c最快的开发工具">C、最快的开发工具</h3>
<h4 id="由于dcloud的极客特质hbuilder同时被打造成了最快的前端开发工具代码输入法的创新代码块的优化emmet的集成快捷键语法设计无鼠标操作hbuilder有句口号为极客为懒人为你">由于DCloud的极客特质,HBuilder同时被打造成了最快的前端开发工具,代码输入法的创新、代码块的优化、emmet的集成、快捷键语法设计、无鼠标操作;HBuilder有句口号:为极客、为懒人、为你。</h4>
<h3 id="d环保健康的主题设计">D、环保健康的主题设计</h3>
<h4 id="由于我们天天面对屏幕眼睛很受伤所以设计了绿柔主题以保护开发者的视力健康">由于我们天天面对屏幕眼睛很受伤,所以设计了绿柔主题以保护开发者的视力健康</h4>
<h3 id="eapp开发及部署">E、App开发及部署</h3>
<h4 id="当然移动app开发也是hbuilder的优势run-in-device真机调试打包发行这些功能并非普通的html4开发工具会涉及的如果只是做个网站html4就够了搞html5而不做app太糟蹋这个技术了以及云打包还能使得没有mac电脑的程序员可以开发ios应用">当然移动App开发也是HBuilder的优势,Run in device真机调试、打包发行这些功能并非普通的HTML4开发工具会涉及的。如果只是做个网站,HTML4就够了,搞HTML5而不做App,太糟蹋这个技术了。以及云打包还能使得没有mac电脑的程序员可以开发iOS应用。</h4>
<h2 id="能力---html5plus-runtime">能力 - HTML5plus Runtime</h2>
<h4 id="说完性工能里的工具再说说能力">说完性工能里的工具,再说说能力。</h4>
<h4 id="html5plus-runtime简称5-runtime是运行于手机端的强化web引擎除了支持标准html5外还支持更多扩展的js-api使得js的能力不输于原生5-runtime内置于hbuilder在真机运行打包时自动挂载">HTML5plus Runtime,简称5+ Runtime,是运行于手机端的强化web引擎,除了支持标准HTML5外,还支持更多扩展的js api,使得js的能力不输于原生。5+ Runtime内置于HBuilder,在真机运行、打包时自动挂载。</h4>
<h4 id="业内之前有phonegapcordova方案但是他们自带js-api太少了扩展api需要用原生语言开发更致命的是这类方案的性能不足">业内之前有phonegap/Cordova方案,但是他们自带js api太少了,扩展api需要用原生语言开发,更致命的是这类方案的性能不足。</h4>
<h3 id="a常用的api--html5plus">A、常用的API – HTML5plus</h3>
<h4 id="装成跨平台的html5plus规范并将规范公开于wwwhtml5plusorg不做厂商私有api包括二维码摇一摇语音输入地图支付分享文件系统通讯录等常用api可以方便简单的编写并且可跨平台">装成跨平台的HTML5plus规范,并将规范公开于www.HTML5plus.org,不做厂商私有API。包括二维码、摇一摇、语音输入、地图、支付、分享、文件系统、通讯录等常用API,可以方便简单的编写,并且可跨平台。</h4>
<h3 id="b其他原生api--nativejs">B、其他原生API – Native.js</h3>
<h4 id="原生api在ios和android上各自有40多万有些api并不常用而且不具有跨平台特性比如ios的game-center-api太多的api封装到html5plus里会过多增加runtime的体积但若有需求要使用这些api又很麻烦">原生API在iOS和Android上各自有40多万,有些API并不常用,而且不具有跨平台特性,比如ios的game center api。太多的API封装到HTML5plus里,会过多增加runtime的体积,但若有需求要使用这些api又很麻烦。</h4>
<h4 id="dcloud使用突破性的技术解决上述烦恼nativejs一种把40w原生api映射为js-api的技术">DCloud使用突破性的技术解决上述烦恼—Native.js,一种把40w原生API映射为JS API的技术。</h4>
<h4 id="如果说nodejs把js的战火烧到了服务器端那么nativejs把js战火烧到了原生应用战场但我们可以使用js直接调原生api语法是js语法api命名是原生命名">如果说node.js把js的战火烧到了服务器端,那么Native.js把js战火烧到了原生应用战场。但我们可以使用js直接调原生API,语法是js语法,API命名是原生命名。</h4>
<h4 id="比如var-obj--plusandroidimport-androidosbundle--然后objxxx这个xxx属性就完全是原生对象的属性命名">比如var obj = plus.android.import( “android.os.Bundle” ); 然后obj.xxx,这个xxx属性就完全是原生对象的属性命名。</h4>
<h4 id="对于jser他一下就有40w-api可以用瞬间感觉无所不能">对于JSer,他一下就有40w API可以用,瞬间感觉无所不能</h4>
<h4 id="nativejs的教程详见httpaskdcloudnetcnarticle88">Native.js的教程详见:http://ask.dcloud.net.cn/article/88</h4>
<h3 id="c更多原生sdk插件引入--5-runtime插件">C、更多原生SDK插件引入 – 5+ Runtime插件</h3>
<h4 id="假使有一些原生的三方sdk想引入到5-runtime比如身份证扫描sdk可以通过5-runtime的插件机制进行扩展或者5-runtime预置的地图是百度地图开发者想换成高德地图也是类似的做法">假使有一些原生的三方SDK想引入到5+ Runtime,比如身份证扫描SDK,可以通过5+ Runtime的插件机制进行扩展。或者5+ Runtime预置的地图是百度地图,开发者想换成高德地图,也是类似的做法。</h4>
<h4 id="dcloud也支持5-sdk把5-runtime作为一个sdk放入到其他原生app中用5-sdk替代webview可以得到更强大的功能和性能">DCloud也支持5+ SDK,把5+ runtime作为一个SDK放入到其他原生App中,用5+ SDK替代webview可以得到更强大的功能和性能。</h4>
<h4 id="ios插件开发教程详见httpaskdcloudnetcnarticle67">iOS插件开发教程详见:http://ask.dcloud.net.cn/article/67</h4>
<h4 id="android插件开发教程详见httpaskdcloudnetcnarticle66">Android插件开发教程详见:http://ask.dcloud.net.cn/article/66</h4>
<h4 id="通过html5plus规范nativejs技术以及原生插件这3种机制使得5-runtime拥有完全不输于原生app的能力">通过HTML5plus规范、Native.js技术以及原生插件,这3种机制使得5+ Runtime拥有完全不输于原生App的能力。</h4>
<h2 id="性能---html5plus-runtime和mui框架">性能 - HTML5plus runtime和mui框架</h2>
<h4 id="性工能里最后的重头戏是性能在低端android手机上过去的html5无法商用切页白屏转场卡顿下拉刷新不流畅侧滑菜单不流畅众多问题逼迫开发者只能使用原生技术来做应用">性工能里最后的重头戏是性能。在低端Android手机上,过去的HTML5无法商用,切页白屏、转场卡顿、下拉刷新不流畅、侧滑菜单不流畅。。。众多问题逼迫开发者只能使用原生技术来做应用。</h4>
<p>HTML5 App的性能低下,有webview自身的性能问题,也有前端框架的性能问题。</p>
<h3 id="awebview性能问题">A、Webview性能问题:</h3>
<h4 id="webview性能低主要体现在动画效果不流畅之前举例的转场动画下拉回弹动画侧滑动画均是此类既然js和css的动画不行我们就调用原生api换成原生动画dcloud设计了很多原生动画来解决之前的各种动画不流畅问题">Webview性能低主要体现在动画效果不流畅,之前举例的转场动画、下拉回弹动画、侧滑动画均是此类。既然js和css的动画不行,我们就调用原生API换成原生动画。DCloud设计了很多原生动画,来解决之前的各种动画不流畅问题。</h4>
<h4 id="5-runtime还支持预载技术以加快页面的加载速度减少白屏和用户等待事实上原生语言都可以自己开发预载但html5标准api不足以完成此任务dcloud提供单独的preload-api同时支持对内存占用的管理协助开发者在低端手机上优化性能">5+ Runtime还支持预载技术,以加快页面的加载速度,减少白屏和用户等待。事实上原生语言都可以自己开发预载,但HTML5标准API不足以完成此任务,DCloud提供单独的preload API。同时支持对内存占用的管理,协助开发者在低端手机上优化性能。</h4>
<h3 id="b前端框架问题">B、前端框架问题:</h3>
<h4 id="由于html5的默认控件无法直视我们只能用css把按钮输入框修饰成原生样式以及html5的控件比原生控件少很多比如listtabmenuwaiting等常见控件以往都要写很多div和css拼装这引发了一个前端框架存在的市场但目前的前端框架性能都非常低在低端手机上很难达到商用要求更不用提pk原生效果">由于HTML5的默认控件无法直视,我们只能用css把按钮、输入框修饰成原生样式,以及HTML5的控件比原生控件少很多,比如list、tab、menu、waiting等常见控件,以往都要写很多div和css拼装。这引发了一个前端框架存在的市场。但目前的前端框架性能都非常低,在低端手机上很难达到商用要求,更不用提pk原生效果。</h4>
<h4 id="jquery-mobile比较知名但有3个硬伤">Jquery mobile比较知名,但有3个硬伤:</h4>
<h4 id="1-体积高达500k">1. 体积高达500k;</h4>
<h4 id="2-data-的写法虽然写起来简单但在运行时需要js去解析html5标签并替换为新的dom结构这是非常消耗手机资源和影响加载速度的">2. data-的写法虽然写起来简单,但在运行时需要js去解析HTML5标签并替换为新的dom结构,这是非常消耗手机资源和影响加载速度的;</h4>
<h4 id="3-样式风格自成一派不是用户所熟悉的原生样式">3. 样式风格自成一派,不是用户所熟悉的原生样式。</h4>
<h4 id="基于这种情况dcloud推出了开源的mui框架httpdcloudiogithubiomui它是目前最高性能和最接近原生体验的手机端框架它的3个特点与jquery-mobile正好对应">基于这种情况,DCloud推出了开源的mui框架(http://dcloudio.github.io/mui/),它是目前最高性能和最接近原生体验的手机端框架。它的3个特点与Jquery mobile正好对应:</h4>
<h4 id="1-体积小不到100k">1. 体积小,不到100k;</h4>
<h4 id="2-直接使用class编写性能远高于data-方式又通过代码块的编写方式降低了开发者编码的复杂度在hbuilder里敲m会拉出一排控件mlistmbutton等选一个回车就会自动产生div和class">2. 直接使用class编写,性能远高于data-方式,又通过代码块的编写方式降低了开发者编码的复杂度,在HBuilder里敲m,会拉出一排控件mList、mButton等,选一个回车,就会自动产生div和class;</h4>
<h4 id="3-mui的风格样式是最接近原生样式的如下图">3. mui的风格样式是最接近原生样式的,如下图。</h4>
<p><img src="/images/liuyw6/20161009img02.png" alt="20161009img02" /></p>
<h2 id="结束语">结束语</h2>
<h4 id="hbuilder5-runtimemui为前端开发人员提供了一个很好的工具框架能做到接近原生app的功能和体验同时为未来移动端应用带来巨大的变化">HBuilder、5+ Runtime、mui为前端开发人员提供了一个很好的工具、框架,能做到接近原生App的功能和体验,同时为未来移动端应用带来巨大的变化</h4>
APP界面设计(上)
2016-10-08T00:00:00+00:00
http://www.blogways.net/blog/2016/10/08/UI-appDesign-01
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">APP界面设计</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<p><img src="/images/huangmai/20161008img01.jpg" alt="20161008img01" /></p>
CAS单点登录
2016-10-06T00:00:00+00:00
http://www.blogways.net/blog/2016/10/06/web-cas
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">CAS原理</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">cas-server配置</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">cas-client配置</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">客户端自定义登录界面</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一cas-原理">一、CAS 原理</h2>
<blockquote>
<ol>
<li>
<p>访问服务: SSO 客户端发送请求访问应用系统提供的服务资源。</p>
</li>
<li>
<p>定向认证: SSO 客户端会重定向用户请求到 SSO 服务器。</p>
</li>
<li>
<p>用户认证:用户身份认证。</p>
</li>
<li>
<p>发放票据: SSO 服务器会产生一个随机的 Service Ticket 。</p>
</li>
<li>
<p>验证票据: SSO 服务器验证票据 Service Ticket 的合法性,验证通过后,允许客户端访问服务。</p>
</li>
<li>
<p>传输用户信息: SSO 服务器验证票据通过后,传输用户认证结果信息给客户端。</p>
</li>
</ol>
<p>下面是 CAS 最基本的协议过程:</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161006img05.jpg" alt="20161006img05" /></p>
<blockquote>
<p>如上图: CAS Client 与受保护的客户端应用部署在一起,以Filter方式保护Web应用的受保护资源,过滤从客户端过来的每一个 Web 请求,同 时, CAS Client 会分析 HTTP 请求中是否包含请求 Service Ticket( ST 上图中的 Ticket) ,如果没有,则说明该用户是没有经过认证的;于是 CAS Client 会重定向用户请求到 CAS Server ( Step 2 ),并传递 Service (要访问的目的资源地址)。 Step 3 是用户认证过程,如果用户提供了正确的 Credentials , CAS Server 随机产生一个相当长度、唯一、不可伪造的 Service Ticket ,并缓存以待将来验证,并且重定向用户到 Service 所在地址(附带刚才产生的 Service Ticket ) , 并为客户端浏览器设置一个 Ticket Granted Cookie ( TGC ) ; CAS Client 在拿到 Service 和新产生的 Ticket 过后,在 Step 5 和 Step6 中与 CAS Server 进行身份核实,以确保 Service Ticket 的合法性。</p>
<p>在该协议中,所有与 CAS Server 的交互均采用 SSL 协议,以确保 ST 和 TGC 的安全性。协议工作过程中会有 2次重定向 的过程。但是 CAS Client 与 CAS Server 之间进行 Ticket 验证的过程对于用户是透明的(使用 HttpsURLConnection )。</p>
<p>CAS 请求认证时序图如下:</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161006img06.jpg" alt="20161006img06" /></p>
<p><a id="2nd"></a></p>
<h2 id="二cas-server配置">二、cas-server配置</h2>
<blockquote>
<p>本文所用测试地址说明:</p>
<p>cas-server地址:http://localhost:8080/cas</p>
<p>cas-client地址:http://localhost:7080/test_consumer</p>
</blockquote>
<h4 id="1下载cas包">1.下载cas包</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://github.com/apereo/cas/releases/tag/v3.5.2
</code></pre></div></div>
<h4 id="2cas-server-352-releasezip解压将modules下面的cas-server-webapp-352war部署到tomcat服务器重命名为caswar并解压">2.cas-server-3.5.2-release.zip解压,将modules下面的cas-server-webapp-3.5.2.war部署到tomcat服务器,重命名为cas.war并解压。####</h4>
<h4 id="3导入modules中的cas-server-support-jdbc-352jar包导入数据库驱动mysql-connector-java-5129jar包">3.导入modules中的cas-server-support-jdbc-3.5.2.jar包,导入数据库驱动mysql-connector-java-5.1.29.jar包</h4>
<h4 id="4由于不采用https方式需要修改配置文件">4.由于不采用https方式,需要修改配置文件</h4>
<blockquote>
<p>WEB-INF/DEPLOYERCONFIGCONTEXT.XML</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>增加参数 p:requireSecure="false" ,是否需要安全验证,即 HTTPS , false 为不采用 如下:
<bean class = "org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" p:httpClient-ref = "httpClient" p:requireSecure= "false"/>
</code></pre></div></div>
<blockquote>
<p>WEB-INF/spring-configuration/ticketGrantingTicketCookieGenerator.xml 修改</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bean id = "ticketGrantingTicketCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASTGC"
p:cookiePath = "/cas"/>
</code></pre></div></div>
<blockquote>
<p>WEB-INF\spring-configuration\warnCookieGenerator.xml 修改</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bean id = "warnCookieGenerator" class = "org.jasig.cas.web.support.CookieRetrievingCookieGenerator"
p:cookieSecure = " false "
p:cookieMaxAge = "-1"
p:cookieName = "CASPRIVACY"
p:cookiePath = "/cas"/>
</code></pre></div></div>
<h4 id="5自定义验证将原来的测试用验证注释掉然后添加查询数据库方式验证对于数据库表tb_user">5.自定义验证,将原来的测试用验证注释掉,然后添加查询数据库方式验证,对于数据库表tb_user</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!--<bean class="org.jasig.cas.authentication.handler.support.SimpleTestUsernamePasswordAuthenticationHandler"/>-->
<bean class="org.jasig.cas.adaptors.jdbc.QueryDatabaseAuthenticationHandler">
<property name="dataSource" ref="dataSource"></property>
<property name="sql" value="select password from tb_user where username=?"></property>
<property name="passwordEncoder" ref="myPasswordEncoder"></property>
</bean>
</code></pre></div></div>
<h4 id="6自定义md5验证">6.自定义MD5验证</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bean id="myPasswordEncoder" class="org.jasig.cas.authentication.handler.MyPasswordEncoder"/>
</code></pre></div></div>
<blockquote>
<p>自定义MD5 java代码如下:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package org.jasig.cas.authentication.handler;
import java.security.MessageDigest;
public class MyPasswordEncoder implements PasswordEncoder
{
public static void main(String[] args) {
MyPasswordEncoder t = new MyPasswordEncoder();
System.out.println(t.encode("123456"));
}
public String encode(String content) {
return md5(md5(content) + "asiainfo");
}
public String md5(String content){
if(content == null){
return null;
}
StringBuffer sbReturn = new StringBuffer();
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update((content).getBytes("utf-8"));
for (byte b : md.digest()) {
sbReturn.append(Integer.toString((b & 0xff) + 0x100, 16).substring(1));
}
return sbReturn.toString();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
}
</code></pre></div></div>
<h4 id="测试cas-server环境">测试cas-server环境</h4>
<p><img src="/images/zhaojiajun/20161006img01.jpg" alt="20161006img01" /></p>
<p><img src="/images/zhaojiajun/20161006img02.jpg" alt="20161006img02" /></p>
<p><a id="3nd"></a></p>
<h2 id="三cas-client配置">三、cas-client配置</h2>
<blockquote>
<p>web.xml配置</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- cas client begin -->
<!-- 用于单点退出,该过滤器用于实现单点登出功能,可选配置-->
<listener>
<listener-class>org.jasig.cas.client.session.SingleSignOutHttpSessionListener</listener-class>
</listener>
<!-- CAS Server 通知 CAS Client,删除session,注销登录信息 -->
<filter>
<filter-name>CAS Single Sign Out Filter</filter-name>
<filter-class>org.jasig.cas.client.session.SingleSignOutFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Single Sign Out Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责用户的认证工作,必须启用它 -->
<filter>
<filter-name>CASFilter</filter-name>
<!-- 将AuthenticationFilter代码复制一份自定义为ClientAuthenticationFilter.java文件 -->
<filter-class>org.jasig.cas.client.authentication.ClientAuthenticationFilter</filter-class>
<init-param>
<param-name>casServerLoginUrl</param-name>
<param-value>http://localhost:8080/cas/login</param-value>
</init-param>
<!-- 添加各客户端对应的登录界面 -->
<init-param>
<param-name>login_from</param-name>
<param-value>http://localhost:7080/test_consumer/login.jsp</param-value>
</init-param>
<init-param>
<!--这里的server是服务端的IP-->
<param-name>serverName</param-name>
<param-value>http://localhost:7080/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- 该过滤器负责对Ticket的校验工作,必须启用它 -->
<filter>
<filter-name>CAS Validation Filter</filter-name>
<filter-class>
org.jasig.cas.client.validation.Cas20ProxyReceivingTicketValidationFilter
</filter-class>
<init-param>
<param-name>casServerUrlPrefix</param-name>
<param-value>http://localhost:8080/cas</param-value>
</init-param>
<init-param>
<param-name>serverName</param-name>
<param-value>http://localhost:7080/</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CAS Validation Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器负责实现HttpServletRequest请求的包裹,
比如允许开发者通过HttpServletRequest的getRemoteUser()方法获得SSO登录用户的登录名,可选配置。
-->
<filter>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<filter-class>org.jasig.cas.client.util.HttpServletRequestWrapperFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS HttpServletRequest Wrapper Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!--
该过滤器使得开发者可以通过org.jasig.cas.client.util.AssertionHolder来获取用户的登录名。
比如AssertionHolder.getAssertion().getPrincipal().getName()。
-->
<filter>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<filter-class>org.jasig.cas.client.util.AssertionThreadLocalFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CAS Assertion Thread Local Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<!-- cas client end -->
</code></pre></div></div>
<blockquote>
<p>自定义ClientAuthenticationFilter.java文件添加:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private String login_from;
......
//initInternal方法中添加:
setCasServerLoginUrl(getPropertyFromInitParams(filterConfig, "login_from", null));
log.trace("Loaded login_from parameter: " + this.login_from);
//doFilter方法中添加:
//如果是登录界面则跳过
String loginUrl = request.getRequestURL().toString();
if(this.casServerLoginUrl.equals(loginUrl)){
filterChain.doFilter(request, response);
return;
}
</code></pre></div></div>
<p><a id="end"></a></p>
<h2 id="四客户端自定义登录界面">四、客户端自定义登录界面</h2>
<h4 id="1自定义loginjsp登录界面">1.自定义login.jsp登录界面</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<html>
<head>
<title>自定义登录界面</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
</head>
<body>
<script type="text/javascript">
function getQueryStringByName(name) {
var result = location.search.match(new RegExp("[\?\&]" + name+ "=([^\&]+)","i"));
if(result == null || result.length < 1){
return "";
}
return result[1];
}
var info = getQueryStringByName('info');
if (info == "error")
alert("用户名密码错误!");
</script>
<form method="GET" action="http://localhost:8080/cas/login">
<p>用户名 : <input type="text" name="username"/></p>
<p>密码 : <input type="password" name="password"/></p>
<p><input type="submit" value="登录"/></p>
<input type="hidden" name="auto" value="true"/>
<input type="hidden" name="service" value="<%=request.getParameter("service") %>" />
</form>
</body>
</html>
</code></pre></div></div>
<h4 id="2定义相同的路径包orgjasigcaswebflow">2.定义相同的路径包org.jasig.cas.web.flow</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>将cas-server-core-3.5.2中的AuthenticationViaFormAction.java复制到该包下就行改写,修改:
修改submit方法中
try {
WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
putWarnCookieIfRequestParameterPresent(context);
return "success";
} catch (final TicketException e) {
//添加验证失败后的跳转页面 begin
String login_from = context.getRequestParameters().get("login_from");
if (login_from != null && login_from.length() > 0) {
context.getRequestScope().put("redirectUrl", login_from + "?info=error");
return "customizedRedirect";
}
//添加验证失败后的跳转页面 end
populateErrorsInstance(e, messageContext);
if (isCauseAuthenticationException(e))
return getAuthenticationExceptionEventId(e);
return "error";
}
将生成的AuthenticationViaFormAction.class替换cas-server-core-3.5.2.jar中的class文件
</code></pre></div></div>
<h4 id="3修改客户端过滤规则将loginjsp排除在外">3.修改客户端过滤规则,将login.jsp排除在外</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><filter-mapping>
<filter-name>CASFilter</filter-name>
<url-pattern>/jsp/*</url-pattern>
</filter-mapping>
</code></pre></div></div>
<h4 id="4修改cas的默认登录页面-web-infviewjspdefaultuicasloginviewjsp">4.修改cas的默认登录页面 WEB-INF/view/jsp/default/ui/casLoginView.jsp</h4>
<p><img src="/images/zhaojiajun/20161006img07.jpg" alt="20161006img07" /></p>
<h4 id="5cas-server-webapp工程中修改web-inflogin-webflowxml">5.cas-server-webapp工程中,修改WEB-INF/login-webflow.xml</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><action-state id="realSubmit">
......
<transition on="error" to="generateLoginTicket"/>
<!--加入下面这句话该transition , 当验证失败之后转到自定义页面 -->
<transition on="customizedRedirect" to="customizedRedirectView"/>
......
</action-state>
</code></pre></div></div>
<h4 id="6添加客户端跳转页面">6.添加客户端跳转页面</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><end-state id="customizedRedirectView" view="externalRedirect:${requestScope.redirectUrl}"/>
</code></pre></div></div>
<h4 id="测试自定义登录界面">测试自定义登录界面</h4>
<blockquote>
<p>在客户端浏览器中输入http://localhost:7080/test_consumer/jsp/home.jsp 回车,cas server验证失败
后重定向到客户端传过来的login_form地址</p>
</blockquote>
<p><img src="/images/zhaojiajun/20161006img03.jpg" alt="20161006img03" /></p>
<p><img src="/images/zhaojiajun/20161006img04.jpg" alt="20161006img04" /></p>
Nginx+Mongodb 文件存储方案
2016-09-16T00:00:00+00:00
http://www.blogways.net/blog/2016/09/16/nginx-grids
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">Nginx-gridfs和Nginx配置</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">应用示例</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">注意事项</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一简介">一、简介</h2>
<blockquote>
<p>MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。是一个基于分布式文件存储的数据库,<strong>Gridfs适合存储海量大文件,特别是视频,音频,大型图片超过16MB大小的文件</strong>。</p>
<p>Nginx-gridfs是一个 Nginx 的扩展模块,用于支持直接访问 MongoDB 的 GridFS 文件系统上的文件并提供 HTTP 访问,可理解为就是MongoDB的客户端。</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GridFS实现原理
GridFS在数据库中,默认使用fs.chunks和fs.files来存储文件。
其中fs.files集合存放文件的信息,fs.chunks存放文件数据。
一个fs.files集合中的一条记录内容如下,即一个file的信息如下:
{
"_id" : ObjectId("4f4608844f9b855c6c35e298"), //唯一id,可以是用户自定义的类型
"filename" : "CPU.txt", //文件名
"length" : 778, //文件长度
"chunkSize" : 262144,//chunk的大小
"uploadDate" : ISODate("2012-02-23T09:36:04.593Z"), //上传时间
"md5" : "e2c789b036cfb3b848ae39a24e795ca6", //文件的md5值
"contentType" : "text/plain" //文件的MIME类型
"meta" : null//文件的其它信息,默认是没有”meta”这个key,用户可以自己定义为任意BSON对象
}
对应的fs.chunks中的chunk如下:
{
"_id" : ObjectId("4f4608844f9b855c6c35e299"),//chunk的id
"files_id" : ObjectId("4f4608844f9b855c6c35e298"), //文件的id,对应fs.files中的对象,相当于fs.files集合的外键
"n" : 0, //文件的第几个chunk块,如果文件大于chunksize的话,会被分割成多个chunk块
"data" : BinData(0,"QGV...") //文件的二进制数据,这里省略了具体内容
}
</code></pre></div></div>
<p><a id="2nd"></a></p>
<h2 id="二nginx-gridfs和nginx配置">二、Nginx-gridfs和Nginx配置</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>进入目录 /usr/local/server/ 下
</code></pre></div></div>
<h4 id="安装依赖库工具">安装依赖库、工具</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># yum -y install pcre-devel openssl-devel zlib-devel
# yum -y install gcc gcc-c++
</code></pre></div></div>
<h4 id="下载nginx-gridfs源码">下载nginx-gridfs源码</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># git clone https://github.com/mdirolf/nginx-gridfs.git
# cd nginx-gridfs
# git checkout v0.8
# git submodule init
# git submodule update
</code></pre></div></div>
<h4 id="下载nginx源码编译安装">下载nginx源码,编译安装</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># wget http://nginx.org/download/nginx-1.4.7.tar.gz
# tar zxvf nginx-1.4.7.tar.gz
# cd nginx-1.4.7
# ./configure --prefix=/usr/local/server/nginx --with-pcre=/usr/local/server/pcre-8.38 --with-http_ssl_module --with-http_stub_status_module --with-http_flv_module --with-http_gzip_static_module --add-module=/usr/local/server/nginx-gridfs --with-poll_module --without-select_module --with-http_realip_module --with-cc-opt=-Wno-error;
# make -j8 && make install -j8
</code></pre></div></div>
<h4 id="修改usrlocalnginxconfnginxconf配置文件">修改/usr/local/nginx/conf/nginx.conf配置文件</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location /static/ {
gridfs GridFS # GridFS指的是mongodb的数据库名称
field=filename # [field]:查询字段,保证mongdb里有这个字段名,支持_id, filename, 可省略, 默认是_id
type=string; # [type]:解释field的数据类型,支持objectid, int, string, 可省略, 默认是int
mongo 127.0.0.1:27017;
}
</code></pre></div></div>
<p><a id="3nd"></a></p>
<h2 id="三应用示例">三、应用示例</h2>
<blockquote>
<p>要保证系统启动过程中,MongoDB比nginx先启动,否则nginx-gridfs初始化的时候不能正确链接MongoDB数据库。</p>
</blockquote>
<h4 id="启动-mongodb">启动 mongodb</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /usr/local/server/mongodb/bin/mongod --dbpath=/usr/local/server/mongodb/data/db --logpath=/usr/local/server/mongodb/data/logs/mongodb.log &
</code></pre></div></div>
<h4 id="启动nginx服务">启动nginx服务</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /usr/local/server/nginx/sbin/nginx &
</code></pre></div></div>
<h4 id="java代码从本地上传一个图片到mongodb服务器">Java代码从本地上传一个图片到mongodb服务器</h4>
<h4 id="1引入依赖的jar包">(1)引入依赖的jar包</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>1.2.0.RELEASE</version>
</dependency>
</code></pre></div></div>
<h4 id="2java代码实现">(2)java代码实现</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /**
* 存储文件
*/
public static void saveFile() throws Exception{
//连接服务器
MongoClient mongoClient = new MongoClient("192.168.137.129", 27017);
// 连接到数据库.如果库不存在则创建
DB db = mongoClient.getDB("GridFS");
//文件操作是在DB的基础上实现的,与表和文档没有关系
GridFS gridFS = new GridFS(db);
String fileName="asiainfo.jpg";
File readFile=new File("e:/"+fileName);
GridFSInputFile mongofile=gridFS.createFile(readFile);
//可以再添加属性
mongofile.put("path","e:/"+fileName);
mongofile.setFilename(fileName);
//保存
mongofile.save();
}
</code></pre></div></div>
<h4 id="使用客户端工具查看一下上传的结果">使用客户端工具查看一下上传的结果</h4>
<p><img src="/images/zhaojiajun/20160916img01.jpg" alt="20160916img01" /></p>
<h4 id="测试图片">测试图片</h4>
<p><img src="/images/zhaojiajun/20160916img02.jpg" alt="20160916img02" /></p>
<h4 id="测试文件">测试文件</h4>
<p><img src="/images/zhaojiajun/20160916img03.jpg" alt="20160916img03" /></p>
<p><img src="/images/zhaojiajun/20160916img04.jpg" alt="20160916img04" /></p>
<p><a id="end"></a></p>
<h2 id="四注意事项">四、注意事项</h2>
<blockquote>
<p>1)mongodb工作集
伴随数据库内容的GridFS文件会显著地搅动MongoDB的内存工作集。如果你不想让GridFS的文件影响到你的内存工作集,那么可以把GridFS的文件存储到不同的MongoDB服务器上。</p>
<p>2)nginx-gridfs的不足
没有实现http的range support,也就是断点续传,分片下载的功能。</p>
</blockquote>
Spring Boot 入门(一) -- 5分钟构建Spring Web Rest风格的Hello World
2016-09-12T00:00:00+00:00
http://www.blogways.net/blog/2016/09/12/spring-boot-share01
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">什么是Spring Boot</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">快速搭建Spring Web</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">融合Swagger</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一什么是spring-boot">一、什么是Spring Boot</h2>
<h4 id="spring-boot-的目的在于快速创建可以独立运行的-spring-应用通过-spring-boot-可以根据相应的模板快速创建应用并运行spring-boot-可以自动配置-spring-的各种组件并不依赖代码生成和-xml-配置文件spring-boot-可以大大提升使用-spring-框架时的开发效率">Spring Boot 的目的在于快速创建可以独立运行的 Spring 应用。通过 Spring Boot 可以根据相应的模板快速创建应用并运行。Spring Boot 可以自动配置 Spring 的各种组件,并不依赖代码生成和 XML 配置文件。Spring Boot 可以大大提升使用 Spring 框架时的开发效率。</h4>
<h3 id="而spring-boot-包含的特性如下">而Spring Boot 包含的特性如下:</h3>
<h4 id="1创建可以独立运行的-spring-应用">1.创建可以独立运行的 Spring 应用。</h4>
<h4 id="2直接嵌入-tomcat-或-jetty-服务器不需要部署-war-文件">2.直接嵌入 Tomcat 或 Jetty 服务器,不需要部署 WAR 文件。</h4>
<h4 id="3提供推荐的基础-pom-文件来简化-apache-maven-配置">3.提供推荐的基础 POM 文件来简化 Apache Maven 配置。</h4>
<h4 id="4尽可能的根据项目依赖来自动配置-spring-框架">4.尽可能的根据项目依赖来自动配置 Spring 框架。</h4>
<h4 id="5提供可以直接在生产环境中使用的功能如性能指标应用信息和应用健康检查">5.提供可以直接在生产环境中使用的功能,如性能指标、应用信息和应用健康检查。</h4>
<h4 id="6没有代码生成也没有-xml-配置文件">6.没有代码生成,也没有 XML 配置文件。</h4>
<p><a id="2nd"></a></p>
<h2 id="二快速搭建spring-web">二、快速搭建Spring Web</h2>
<h4 id="第一节描述了spring-boot的作用此节将通过一个实例快速搭建spring-web">第一节描述了Spring Boot的作用,此节将通过一个实例快速搭建Spring Web</h4>
<h3 id="首先通过maven新建一个工程并在pomxml中添加如下代码">首先通过Maven新建一个工程,并在pom.xml中添加如下代码</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.asiainfo.springboot</groupId>
<artifactId>com.asiainfo.springboot</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<properties>
<spring.boot.version>1.1.4.RELEASE</spring.boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring.boot.version}</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger-ui</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-swagger2</artifactId>
<version>2.2.2</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
</code></pre></div></div>
<h4 id="1其中spring-boot-starter-web为web应用所需要的依赖相比与其它的应用简化了太多并不是声明了spring-boot-starter-web就不需要其它的包而是spring-boot自动做了关联打开工程的关联library即可查看到">1.其中spring-boot-starter-web为Web应用所需要的依赖,相比与其它的应用简化了太多;并不是声明了spring-boot-starter-web就不需要其它的包,而是Spring boot自动做了关联;打开工程的关联library即可查看到</h4>
<h4 id="2springfox-swagger-uispringfox-swagger2-为swagger依赖包在下面的章节中会具体讲解">2.springfox-swagger-ui\springfox-swagger2 为Swagger依赖包,在下面的章节中会具体讲解</h4>
<h3 id="配置好pomxml后开始创建程序主入口代码如下">配置好pom.xml后,开始创建程序主入口,代码如下</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RestController
@EnableAutoConfiguration
public class Application {
@RequestMapping("/")
String home() {
return "Hello World!";
}
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
</code></pre></div></div>
<h4 id="通过运行application即可运行一个独立的-web-应用默认使用的tomcat服务器访问httplocalhost8080可以看到页面上显示hello-world也就是说只需要简单的-2-个文件就可以启动一个独立运行的-web-应用并不需要额外安装-tomcat-这样的应用服务器也不需要打包成-war-文件">通过运行Application,即可运行一个独立的 Web 应用(默认使用的Tomcat服务器),访问“http://localhost:8080”可以看到页面上显示“Hello World!”,也就是说,只需要简单的 2 个文件就可以启动一个独立运行的 Web 应用。并不需要额外安装 Tomcat 这样的应用服务器,也不需要打包成 WAR 文件。</h4>
<h4 id="enableautoconfiguration注解的作用在于让-spring-boot-根据应用所声明的依赖来对-spring-框架进行自动配置">“@EnableAutoConfiguration”注解的作用在于让 Spring Boot 根据应用所声明的依赖来对 Spring 框架进行自动配置</h4>
<h4 id="注解restcontroller和requestmapping由-spring-mvc-提供用来创建-rest-服务这两个注解和-spring-boot-本身并没有关系">注解“@RestController”和”@RequestMapping”由 Spring MVC 提供,用来创建 REST 服务。这两个注解和 Spring Boot 本身并没有关系。</h4>
<h3 id="spring-boot-除了spring-boot-starter-web标签以外还有很多标签详细如下表格">Spring Boot 除了spring-boot-starter-web标签以外,还有很多标签,详细如下表格</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>名称 说明
spring-boot-starter 核心 POM,包含自动配置支持、日志库和对 YAML 配置文件的支持。
spring-boot-starter-amqp 通过 spring-rabbit 支持 AMQP。
spring-boot-starter-aop 包含 spring-aop 和 AspectJ 来支持面向切面编程(AOP)。
spring-boot-starter-batch 支持 Spring Batch,包含 HSQLDB。
spring-boot-starter-data-jpa 包含 spring-data-jpa、spring-orm 和 Hibernate 来支持 JPA。
spring-boot-starter-data-mongodb 包含 spring-data-mongodb 来支持 MongoDB。
spring-boot-starter-data-rest 通过 spring-data-rest-webmvc 支持以 REST 方式暴露 Spring Data 仓库。
spring-boot-starter-jdbc 支持使用 JDBC 访问数据库。
spring-boot-starter-security 包含 spring-security。
spring-boot-starter-test 包含常用的测试所需的依赖,如 JUnit、Hamcrest、Mockito 和 spring-test 等。
spring-boot-starter-velocity 支持使用 Velocity 作为模板引擎。
spring-boot-starter-web 支持 Web 应用开发,包含 Tomcat 和 spring-mvc。
spring-boot-starter-websocket 支持使用 Tomcat 开发 WebSocket 应用。
spring-boot-starter-ws 支持 Spring Web Services。
spring-boot-starter-actuator 添加适用于生产环境的功能,如性能指标和监测等功能。
spring-boot-starter-remote-shell 添加远程 SSH 支持。
spring-boot-starter-jetty 使用 Jetty 而不是默认的 Tomcat 作为应用服务器。
spring-boot-starter-log4j 添加 Log4j 的支持。
spring-boot-starter-logging 使用 Spring Boot 默认的日志框架 Logback。
spring-boot-starter-tomcat 使用 Spring Boot 默认的 Tomcat 作为应用服务器。
</code></pre></div></div>
<h4 id="其中每项功能的作用及demo会在后续的博文中继续完善">其中每项功能的作用及Demo会在后续的博文中继续完善</h4>
<p><a id="3nd"></a></p>
<h2 id="三融合swagger实现api可视化">三、融合Swagger实现API可视化</h2>
<h4 id="上一节谈到-pomxml中引入了springfox-swagger-uispringfox-swagger2-这两个包这2个包即为swagger基础依赖包引入后即可正常使用swagger相关的功能并且可以使用可视化界面">上一节谈到 pom.xml中引入了springfox-swagger-ui\springfox-swagger2 这两个包,这2个包即为Swagger基础依赖包,引入后即可正常使用Swagger相关的功能,并且可以使用可视化界面</h4>
<h4 id="这里将通过一个例子了解如何添加api至swagger中首先先看工程结构图">这里将通过一个例子,了解如何添加API至Swagger中,首先先看工程结构图</h4>
<p><img src="/images/liuyw6/20160912img01.png" alt="20160912img01" /></p>
<h3 id="创建一个user对象uservo">创建一个User对象,UserVo</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.asiainfo.user.domain;
import io.swagger.annotations.ApiModelProperty;
/**
* Created by liuyw on 16/9/7.
*/
public class UserVo{
@ApiModelProperty(value="账户",required = true)
private String account;
@ApiModelProperty(value="姓名",required = true)
private String name;
@ApiModelProperty(value="昵称",required = true)
private String nickName;
public UserVo(String account,String name,String nickName){
this.account = account;
this.name = name;
this.nickName = nickName;
}
public String getAccount() {
return account;
}
public void setAccount(String account) {
this.account = account;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getNickName() {
return nickName;
}
public void setNickName(String nickName) {
this.nickName = nickName;
}
}
</code></pre></div></div>
<h4 id="apimodelpropertyvalue昵称required--true此语句为swagger提供给property的解释在可视化界面中用于属性注视">@ApiModelProperty(value=”昵称”,required = true),此语句为Swagger提供给property的解释,在可视化界面中用于属性注视</h4>
<h3 id="创建一个controller暴露api接口并返回user对象">创建一个Controller,暴露API接口,并返回User对象</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.asiainfo.user;
import com.asiainfo.user.domain.UserVo;
import io.swagger.annotations.*;
import org.springframework.web.bind.annotation.*;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import java.util.ArrayList;
import java.util.List;
/**
* Created by liuyw on 16/9/7.
*/
@RestController
@RequestMapping("/user")
@Api("userController相关api")
@EnableSwagger2
public class UserController {
@ApiOperation("获取用户信息")
@ApiImplicitParams({
@ApiImplicitParam(paramType = "header", name = "username", dataType = "String", required = true, value = "用户的姓名", defaultValue = "zhaojigang"),
@ApiImplicitParam(paramType = "query", name = "password", dataType = "String", required = true, value = "用户的密码", defaultValue = "wangna")
})
@ApiResponses({
@ApiResponse(code = 400, message = "请求参数没填好"),
@ApiResponse(code = 404, message = "请求路径没有或页面跳转路径不对")
})
@RequestMapping(value = "/getUser", method = RequestMethod.GET)
public List<UserVo> getUser(@RequestHeader("username") String username, @RequestParam("password") String password) {
// return userService.getUser(username,password);
UserVo user1 = new UserVo("test01", "测试账户一", "真仙");
UserVo user2 = new UserVo("test02", "测试账户二", "仙王");
UserVo user3 = new UserVo("test03", "测试账户三", "仙帝");
List<UserVo> list = new ArrayList<UserVo>();
list.add(user1);
list.add(user2);
list.add(user3);
return list;
}
}
</code></pre></div></div>
<h4 id="usercontroller提供了getuser方法并对外暴露使用">UserController提供了getUser方法,并对外暴露使用</h4>
<h4 id="apiusercontroller相关apiapioperationapiimplicitparamsapiresponses均为swagger语法用以注视接口的作用参数的传值说明以及异常返回的说明等">@Api(“userController相关api”)、ApiOperation、ApiImplicitParams、ApiResponses均为Swagger语法,用以注视接口的作用,参数的传值说明,以及异常返回的说明等</h4>
<h4 id="enableswagger2-标记了此controller可以使用swagger若不填写在可视化视图中将无法正常查询使用">@EnableSwagger2 标记了此Controller可以使用Swagger,若不填写,在可视化视图中将无法正常查询使用</h4>
<h3 id="创建运行主函数">创建运行主函数</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.asiainfo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
/**
* Created by liuyw on 16/9/7.
*/
@EnableAutoConfiguration
@ComponentScan
public class Application {
public static void main(String[] args) throws Exception {
SpringApplication.run(Application.class, args);
}
}
</code></pre></div></div>
<h4 id="运行application然后访问httplocalhost8080swagger-uihtml即可查看到usercontroller如下图">运行Application,然后访问http://localhost:8080/swagger-ui.html,即可查看到UserController,如下图</h4>
<p><img src="/images/liuyw6/20160912img02.png" alt="20160912img02" /></p>
<h2 id="结束语">结束语</h2>
<h4 id="本文主要从最基本的spring-boot进行了描述其中涉及到深层次的内容如服务器部署集群数据库连接spring-boot其它的标签介绍等这些内容会在后续的博文中依次介绍谢谢">本文主要从最基本的Spring Boot进行了描述,其中涉及到深层次的内容,如服务器部署、集群、数据库连接;spring boot其它的标签介绍等;这些内容会在后续的博文中依次介绍,谢谢</h4>
D3.js基本简介[2]--在D3中使用SVG和Canvas
2016-09-10T00:00:00+00:00
http://www.blogways.net/blog/2016/09/10/web-d3-2
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">什么是画布</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">D3中使用Canvas</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3rd">D3中使用SVG</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">小结</a></td>
</tr>
</tbody>
</table>
<p>上节中,介绍了D3.js的基本概念,其中突出了一点其可视化功能十分强大,可以很方便与汪回介绍的SVG结合使用,本节将会重点讲解一些结合的例子。</p>
<p><a id="1st"></a></p>
<h2 id="1什么是画布">1.什么是画布?</h2>
<p>要绘图,首要需要的是一块绘图的“画布”。</p>
<p>HTML 5 提供两种强有力的“画布”:SVG 和 Canvas。</p>
<p><a id="2nd"></a></p>
<h2 id="2d3中使用canvas">2.D3中使用Canvas</h2>
<p>Canvas 是通过 JavaScript 来绘制 2D 图形,是 HTML 5 中新增的元素。</p>
<blockquote>
<p><strong>Canvas 有如下特点:</strong></p>
</blockquote>
<ul>
<li>绘制的是位图,图像放大后会失真。</li>
<li>不支持事件处理器。</li>
<li>能够以 .png 或 .jpg 格式保存图像</li>
<li>适合游戏应用</li>
</ul>
<p><a id="3rd"></a></p>
<h2 id="3d3中使用svg">3.D3中使用SVG</h2>
<blockquote>
<p><strong>SVG 是什么</strong></p>
</blockquote>
<p>SVG,指可缩放矢量图形(Scalable Vector Graphics),是用于描述二维矢量图形的一种图形格式,是由万维网联盟制定的开放标准。 SVG 使用 XML 格式来定义图形,除了 IE8 之前的版本外,绝大部分浏览器都支持 SVG,可将 SVG 文本直接嵌入 HTML 中显示。</p>
<blockquote>
<p><strong>SVG 有如下特点:</strong></p>
</blockquote>
<ul>
<li>SVG 绘制的是矢量图,因此对图像进行放大不会失真。</li>
<li>基于 XML,可以为每个元素添加 JavaScript 事件处理器。</li>
<li>每个图形均视为对象,更改对象的属性,图形也会改变。</li>
<li>不适合游戏应用。</li>
</ul>
<blockquote>
<p><strong>添加画布</strong></p>
</blockquote>
<p>D3 提供了很多的 SVG 图形的生成器,由于它们只支持 SVG 。所以,这里我建议使用 SVG 画布,但是并不是其他的不能用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var width = 300; //画布的宽度
var height = 400; //画布的高度
var svg = d3.select("#svgbody") //选择文档中的id='svgbody'的div元素
.append("svg") //添加一个svg元素
.attr("width", width) //设定宽度
.attr("height", height); //设定高度
</code></pre></div></div>
<p>这样我们的画布就做好啦。接下来我们将在画布上画些东西了,简单起见,这里就介绍绘制矩形的基本方法,其他图形可以借鉴汪回写的SVG技术博客的内容。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg>
<rect></rect>
<rect></rect>
</svg>
</code></pre></div></div>
<p>上面的 rect 里没有矩形的属性。矩形的属性,常用的有四个:</p>
<ul>
<li>x:矩形左上角的 x 坐标</li>
<li>y:矩形左上角的 y 坐标</li>
<li>width:矩形的宽度</li>
<li>height:矩形的高度</li>
</ul>
<p>要注意,在 SVG 中,x 轴的正方向是水平向右,y 轴的正方向是垂直向下的。</p>
<p>现在我们实现D3简介中的可视化数据。数据如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var dataset = [ 250 , 210 , 170 , 130 , 90 ]; //数据(表示矩形的宽度)
</code></pre></div></div>
<p>为简单起见,我们直接用数值的大小来表示矩形的像素宽度</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var rectHeight = 25; //每个矩形所占的像素高度(包括空白)
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x",20)
.attr("y",function(d,i){
return i * rectHeight;
})
.attr("width",function(d){
return d;
})
.attr("height",rectHeight-2)
.attr("fill","blue");
</code></pre></div></div>
<p>这段代码添加了与 dataset 数组的长度相同数量的矩形,所使用的语句是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svg.selectAll("rect") //选择svg内所有的矩形
.data(dataset) //绑定数组
.enter() //指定选择集的enter部分
.append("rect") //添加足够数量的矩形元素
</code></pre></div></div>
<p><strong>有数据,而没有足够图形元素的时候,使用此方法可以添加足够的元素</strong></p>
<p>添加了元素之后,就需要分别给各元素的属性赋值。在这里用到了 function(d, i),d 代表与当前元素绑定的数据,i 代表索引号。给属性赋值的时候,是需要用到被绑定的数据,以及索引号的。</p>
<p>最后一行的<code class="language-plaintext highlighter-rouge">attr("fill","blue")</code>用来给矩形添加颜色属性,一般来说我们可以写成css属性,方便查找和修改。</p>
<p><a id="end"></a></p>
<h2 id="4小结">4.小结</h2>
<p>本文主要介绍了在D3中如何使用SVG,并做了简单的举例。这里可以和汪回的SVG基本介绍结合使用,画出一些复杂的图形。</p>
Swagger介绍及应用实例
2016-09-08T00:00:00+00:00
http://www.blogways.net/blog/2016/09/08/web-swagger
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">Swagger介绍</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">Swagger UI与SpringMVC的整合</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">应用实例</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">注意事项</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一swagger介绍">一、Swagger介绍</h2>
<blockquote>
<p>Swagger API框架用于管理项目中API接口,属当前最流行的API接口管理工具。Swagger是一个开源框架(Web框架),是一个规范和完整的框架,用于生成、描述、调用和可视化RESTful风格的Web服务,方便的管理项目中API接口,功能强大,UI界面漂亮,并且支持在线测试等。</p>
<p>后端通过提供一套标准的RESTful API,让网站、移动端和第三方系统都可以基于API进行数据交互和对接,极大的提高系统的开发效率,也使得前后端分离架构成为可能。</p>
</blockquote>
<p><a id="2nd"></a></p>
<h2 id="二swagger-ui与springmvc的整合">二、Swagger UI与SpringMVC的整合</h2>
<h3 id="1从githubhttpsgithubcomwordnikswagger-ui上下载swagger-ui">1.从github(<a href="https://github.com/wordnik/swagger-ui">https://github.com/wordnik/swagger-ui</a>)上下载Swagger-UI</h3>
<p><img src="/images/zhaojiajun/20160908img01.jpg" alt="20160908img01" /></p>
<blockquote>
<p>修改dist目录下index.html文件,将url修改为应用服务url</p>
</blockquote>
<p><img src="/images/zhaojiajun/20160908img07.jpg" alt="20160908img07" /></p>
<blockquote>
<p>把该项目dist目录的内容拷贝到项目的webapp的目录下,修改dist目录的名称(也可以不修改),如改为“swagger”。</p>
</blockquote>
<p><img src="/images/zhaojiajun/20160908img02.jpg" alt="20160908img02" /></p>
<h3 id="2与springmvc整合搭建">2.与SpringMVC整合搭建</h3>
<blockquote>
<p>Maven引入swagger所需的包,SpringMVC的配置这里不做说明。</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-mapper-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>org.codehaus.jackson</groupId>
<artifactId>jackson-core-asl</artifactId>
<version>1.9.13</version>
</dependency>
<dependency>
<groupId>com.mangofactory</groupId>
<artifactId>swagger-springmvc</artifactId>
<version>1.0.2</version>
</dependency>
</code></pre></div></div>
<blockquote>
<p>SpringMVC通过注解说明API接口的功能描述、参数含义以及校验等</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package home.action;
import home.model.CrmCar;
import home.service.CrmCarService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
import base.action.BaseAction;
import base.exception.BusinessException;
import com.wordnik.swagger.annotations.ApiOperation;
import com.wordnik.swagger.annotations.ApiParam;
@Controller
@RequestMapping("/crmCar/*") // 父request请求url
public class CrmCarAction extends BaseAction {
@Autowired
private CrmCarService crmCarService;
@ApiOperation(value = "车辆信息", notes = "根据ID获取车辆对象信息,返回页面对象")
@RequestMapping(value = "view/{carId}", method = RequestMethod.GET)
public ModelAndView getCrmCarView(@ApiParam(value = "填写车辆ID",allowableValues = "range[1,5]",required = true) @PathVariable("carId") int carId){
ModelAndView mav = new ModelAndView("index");
CrmCar crmCar = crmCarService.getCrmCar(carId);
mav.addObject("crmCar", crmCar);
return mav;
}
@ApiOperation(value = "车辆信息 GET", notes = "根据ID获取车辆对象信息,返回JSON格式")
@ResponseBody
@RequestMapping(value = "get/{carId}", method = RequestMethod.GET)
public CrmCar getCrmCarGet(@ApiParam(value = "填写车辆ID",allowableValues = "range[1,5]",required = true) @PathVariable("carId") int carId){
CrmCar crmCar = crmCarService.getCrmCar(carId);
return crmCar;
}
@ApiOperation(value = "车辆信息 POST", notes = "根据ID获取车辆对象信息,返回JSON格式")
@ResponseBody
@RequestMapping(value = "post/{carId}", method = RequestMethod.POST)
public CrmCar getCrmCarPost(@ApiParam(value = "填写车辆ID",allowableValues = "range[1,5]",required = true) @PathVariable("carId") int carId){
CrmCar crmCar = crmCarService.getCrmCar(carId);
return crmCar;
}
}
</code></pre></div></div>
<h4 id="api相关注解参数说明如下">API相关注解参数说明如下:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@ApiOperation(value = "接口名称", notes = "接口功能详细说明")
@RequestMapping(value = "接口请求URL", method = "接口请求方式get/post")
@ApiParam(value = "参数名称",allowableValues = "参数约束",required = "是否必须参数")
@PathVariable("绑定参数")
@ResponseBody注解一般在异步获取数据时使用,在springMVC框架时,使用@RequestMapping后,返回值通常解析为跳转路径,加上@ResponseBody后返回结果不会被解析为跳转路径,而是直接写入HTTP response body中。比如异步获取json数据,加上@ResponseBody后,会直接返回json数据。
</code></pre></div></div>
<h4 id="springswagger的配置有两种方式本文以第二种方式实现案例">SpringSwagger的配置有两种方式,本文以第二种方式实现案例</h4>
<h4 id="1自定义配置类">(1)自定义配置类</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package common;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.mangofactory.swagger.configuration.SpringSwaggerConfig;
import com.mangofactory.swagger.models.dto.ApiInfo;
import com.mangofactory.swagger.plugin.EnableSwagger;
import com.mangofactory.swagger.plugin.SwaggerSpringMvcPlugin;
@Configuration
@EnableSwagger
public class MySwaggerConfig
{
private SpringSwaggerConfig springSwaggerConfig;
/**
* Required to autowire SpringSwaggerConfig
*/
@Autowired
public void setSpringSwaggerConfig(SpringSwaggerConfig springSwaggerConfig)
{
this.springSwaggerConfig = springSwaggerConfig;
}
/**
* Every SwaggerSpringMvcPlugin bean is picked up by the swagger-mvc
* framework - allowing for multiple swagger groups i.e. same code base
* multiple swagger resource listings.
*/
@Bean
public SwaggerSpringMvcPlugin customImplementation()
{
return new SwaggerSpringMvcPlugin(this.springSwaggerConfig).apiInfo(apiInfo()).includePatterns(
".*?");
}
private ApiInfo apiInfo()
{
ApiInfo apiInfo = new ApiInfo(
"My Apps API Title",
"My Apps API Description",
"My Apps API terms of service",
"My Apps API Contact Email",
"My Apps API Licence Type",
"My Apps API License URL");
return apiInfo;
}
}
</code></pre></div></div>
<h4 id="在-spring-配置文件中添加">在 spring 配置文件中添加</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><bean class="common.MySwaggerConfig" />
</code></pre></div></div>
<h4 id="2自己不写配置类直接使用默认的实现类在spring的配置文件中配置">(2)自己不写配置类,直接使用默认的实现类,在Spring的配置文件中配置</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><mvc:annotation-driven/>
<!-- Required so swagger-springmvc can access spring‘s RequestMappingHandlerMapping -->
<bean class="com.mangofactory.swagger.configuration.SpringSwaggerConfig" />
</code></pre></div></div>
<p><a id="3nd"></a></p>
<h3 id="3启动服务如果swagger-ui是单独的工程需要保证swagger-ui和web-应用服务在同一个容器中否则不能显示具体原因在后面说明">3.启动服务,如果swagger ui是单独的工程,需要保证swagger ui和web 应用服务在同一个容器中,否则不能显示,具体原因在后面说明。###</h3>
<p><img src="/images/zhaojiajun/20160908img06.jpg" alt="20160908img06" /></p>
<h3 id="4查看web应用服务的api并进行测试">4.查看web应用服务的API并进行测试</h3>
<h4 id="1接口列表">(1)接口列表</h4>
<p><img src="/images/zhaojiajun/20160908img08.jpg" alt="20160908img08" /></p>
<h4 id="2接口说明及测试">(2)接口说明及测试</h4>
<p><img src="/images/zhaojiajun/20160908img09.jpg" alt="20160908img09" /></p>
<h4 id="3测试结果">(3)测试结果</h4>
<p><img src="/images/zhaojiajun/20160908img10.jpg" alt="20160908img10" /></p>
<p><img src="/images/zhaojiajun/20160908img11.jpg" alt="20160908img11" /></p>
<p><a id="end"></a></p>
<h3 id="4注意事项">4.注意事项</h3>
<blockquote>
<p>(1)swagger ui读取应用服务的接口描述是通过JSON方式传输,所以存在跨域问题,这就是上面提到需要放到同一个容器的原因。当然,不是说不能跨域就不能分离swagger ui,可以通过nginx代理URL,将swagger和应用服务保持在同一个域名下即可。</p>
<p>(2)swagger ui是纯静态的,可以不必放到服务容器中使用,可以使用nginx配置站点访问,后续再做讲解说明。</p>
</blockquote>
<h2 id="结束语">结束语</h2>
<blockquote>
<p>本文主要就swagger ui和springmvc整合做了说明,除此swagger ui之外还有其他应用,
Swagger是一组开源项目,其中主要要项目如下:</p>
<p>Swagger-tools:提供各种与Swagger进行集成和交互的工具。例如模式检验、Swagger 1.2文档转换成Swagger 2.0文档等功能。</p>
<p>Swagger-core: 用于Java/Scala的的Swagger实现。与JAX-RS(Jersey、Resteasy、CXF…)、Servlets和Play框架进行集成。</p>
<p>Swagger-js: 用于JavaScript的Swagger实现。</p>
<p>Swagger-node-express: Swagger模块,用于node.js的Express web应用框架。</p>
<p>Swagger-ui:一个无依赖的HTML、JS和CSS集合,可以为Swagger兼容API动态生成优雅文档。</p>
<p>可请各位一起研究探讨,谢谢!</p>
</blockquote>
D3.js基本简介[1]--概念与基本使用方法
2016-09-03T00:00:00+00:00
http://www.blogways.net/blog/2016/09/03/web-d3-1
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">什么是D3.js</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">D3.js安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3rd">第一个小程序</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">小结</a></td>
</tr>
</tbody>
</table>
<p>近年来,可视化越来越流行,许多报刊杂志、门户网站、新闻、媒体都大量使用可视化技术,使得复杂的数据和文字变得十分容易理解,有一句谚语“一张图片价值于一千个字”,的确是名副其实。各种数据可视化工具也如井喷式地发展,D3 正是其中的佼佼者。</p>
<p><a id="1st"></a></p>
<h2 id="1什么是d3js">1.什么是D3.js</h2>
<p>D3.js是一个JavaScript库,它可以通过数据来操作文档。D3可以通过使用HTML、SVG和CSS把数据鲜活形象地展现出来。D3严格遵循Web标准,因而可以让你的程序轻松兼容现代主流浏览器并避免对特定框架的依赖。同时,它提供了强大的可视化组件,可以让使用者以数据驱动的方式去操作DOM。</p>
<h4 id="为什么要数据可视化">为什么要数据可视化</h4>
<p>现在有一组数据, 【 4 , 32 , 15 , 16 , 42 , 25 】 ,你能一眼看出它们的大小关系吗?当然这里的数据不算多,有那眼疾手快的家伙站出来说我能一眼看出来!但更直观的是用图形显示,如下图:
<img src="/images/d3-1-1.png" alt="d3-1-1.png" title="d3-1-1" /></p>
<h4 id="怎么学习d3">怎么学习D3</h4>
<p>以下是几个学习 D3 的站点:</p>
<p>官方网站
http://d3js.org/</p>
<p>包含有很多示例和 API,要想得心应手的使用 D3,熟悉 API 是避不开的。</p>
<p>Mike Bostock 的博客和作品展示板
http://bost.ocks.org/mike/</p>
<p><a id="2nd"></a></p>
<h2 id="2d3js的安装">2.D3.js的安装</h2>
<p>使用D3.js有两种方式,一种是官方下载D3.js文件,另外一种是直接使用网络连接</p>
<h4 id="1下载d3js文件">(1)下载D3.js文件</h4>
<p>https://github.com/mbostock/d3/releases/download/v3.4.8/d3.zip</p>
<h4 id="2直接包含网络的链接">(2)直接包含网络的链接</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
</code></pre></div></div>
<p><a id="3rd"></a></p>
<h2 id="3第一个小程序">3.第一个小程序</h2>
<p>学习一切语言的开头:Hello World!</p>
<h4 id="1使用html输出hello-world">(1)使用HTML输出Hello World!</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
</head>
<body>
<p>Hello World 1</p>
<p>Hello World 2</p>
</body>
</html>
</code></pre></div></div>
<h4 id="2使用js更改hello-world">(2)使用JS更改Hello World</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
</head>
<body>
<p>Hello World 1</p>
<p>Hello World 2</p>
<script>
var paragraphs = document.getElementsByTagName("p");
for (var i = 0; i < paragraphs.length; i++) {
var paragraph = paragraphs.item(i);
paragraph.innerHTML = "I want to say Hello world";
}
</script>
</body>
</html>
</code></pre></div></div>
<p>会输出两行 Iwant to say Hello world!</p>
<h4 id="3使用d3js更改hello-world">(3)使用D3.js更改Hello World</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<meta charset="utf-8">
<title>HelloWorld</title>
</head>
<body>
<p>Hello World 1</p>
<p>Hello World 2</p>
<script type="text/javascript" src="d3.v3.js](</script>
<script>
d3.select("body").selectAll("p").text("I want to say Hello world");
</script>
</body>
</html> 从上述代码可以看出使用D3.js可以很方便的简化代码,这点有点类似于jQuery,语法结构是链式语法。
</code></pre></div></div>
<h4 id="4使用d3js更改字体和颜色">(4)使用D3.js更改字体和颜色</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>p.style("color","red")
.style("font-size","72px");
</code></pre></div></div>
<p>d3.select() 或 d3.selectAll() 选择元素后返回的对象,就是选择集。</p>
<h2 id="4小结">4.小结</h2>
<p>本文主要介绍了d3.js概念,安装方法和简单的小例子,它可以很方便简化JS语法,当然D3的功能不仅仅是这样,接下我会对D3选择器和在SVG画布中制作可视化图标进行讲解。</p>
8招教你玩转[视觉引导]
2016-08-31T00:00:00+00:00
http://www.blogways.net/blog/2016/08/31/ued-design-share
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">第一招</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">第二招</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">第三招</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#4nd">第四招</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#5nd">第五招</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#6nd">第六招</a></td>
</tr>
<tr>
<td>7</td>
<td><a href="#7nd">第七招</a></td>
</tr>
<tr>
<td>8</td>
<td><a href="#End">第八招</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一先要有个视觉点靠这些点指引或者聚集视线">一、先要有个视觉点,靠这些点指引或者聚集视线</h2>
<p>点的形状并非都是圆,或者是某种具象的元素。</p>
<h4 id="比如在整张页面中出现的大小对比关系那么小的元素可以称之为点大的元素也可称之为点">比如:在整张页面中出现的大小对比关系,那么小的元素可以称之为点。大的元素也可称之为点。</h4>
<h4 id="它是一种相对而言的说法其实在页面中出现一点时就-可以很快吸引住用户的眼球当再出现一点时此刻用户观察画面时的注意力就分散了">它是一种相对而言的说法。其实,在页面中出现一点时,就 可以很快吸引住用户的眼球。当再出现一点时,此刻用户观察画面时的注意力就分散了。</h4>
<h4 id="当遇到大小不同的点同时出现时用户首先会关注较大的那个点再接着是-较小的点">当遇到大小不同的点同时出现时,用户首先会关注较大的那个点,再接着是 较小的点。</h4>
<p><img src="/images/huangmai/20160831/02efef57c41f830000018c1ba882f2.jpg" alt="02efef57c41f830000018c1ba882f2" /></p>
<h4 id="左上1视线会首先集中在右侧鲜红的草莓上1是因为颜色的对比2是因为面积的对比点在这里是相对的关系你可以把整体出现的元素都可以看做点但是首先会有一个焦点去吸引用户的视线">左上1:视线会首先集中在右侧鲜红的草莓上,1是因为颜色的对比,2是因为面积的对比。点在这里是相对的关系,你可以把整体出现的元素都可以看做点。但是首先会有一个焦点去吸引用户的视线;</h4>
<h4 id="左上2点的焦中设计居中的位置使用户直接关注到正中的内容">左上2:点的焦中设计,居中的位置使用户直接关注到正中的内容;</h4>
<h4 id="左下3这里的点甚至说是页面中的亮点就是右侧冲出画面的骑行者这种表现打破画面的平稳使用户最先关注到破格出画面的人物同时也体现出户外骑行的自然和冒险">左下3:这里的点,甚至说是页面中的亮点。就是右侧冲出画面的骑行者,这种表现打破画面的平稳,使用户最先关注到破格出画面的人物,同时也体现出户外骑行的自然和冒险;</h4>
<h4 id="左下4点的集中设计中间的圆为点二边的餐具为线属于典型的居中构图">左下4:点的集中设计,中间的圆为点,二边的餐具为线,属于典型的居中构图;</h4>
<h4 id="在实际应用中又可分为">在实际应用中又可分为</h4>
<h4 id="焦点">焦点</h4>
<h4 id="第一时间吸引人眼球的那个元素画面中的点有着明显的大小颜色形状的对比">第一时间吸引人眼球的那个元素。画面中的点有着明显的大小、颜色、形状的对比。</h4>
<h4 id="亮点">亮点</h4>
<h4 id="是指画面中点的形状或者细节区别于其他元素成为画面中的主视觉">是指画面中点的形状或者细节区别于其他元素,成为画面中的主视觉。</h4>
<p><img src="/images/huangmai/20160831/0285ee57c41fd20000012e7ef20522.jpg" alt="0285ee57c41fd20000012e7ef20522" /></p>
<h4 id="当你在设计时">当你在设计时</h4>
<h4 id="1画面元素的对比不明显整体视觉很平均时">1、画面元素的对比不明显,整体视觉很平均时:</h4>
<p>你可以根据设计需要和活动目的,对设计元素调整大小、或者颜色等形式,使要突出的点对比更为突出。</p>
<h4 id="2当版式中的文字太多时用户不知道先看什么">2、当版式中的文字太多时,用户不知道先看什么?</h4>
<p>这时你可以尝试把主要文案的字形、颜色等进行处理,使用户视线有个聚焦点。亦或者你可以尝试对文字的层次进行调整,使该突出的突出,该弱化的弱化。</p>
<h2 id="二人物动作设计多手眼身脚都要使">二、人物动作设计多,手眼身脚都要使!</h2>
<p>动作,举止的指向性。</p>
<h4 id="这种指向可以通过模特摆出的各种姿势去体现也可以通过调整模特元素的方向位置和排版共同完成对指定内容的指向在做一些以模特类为主要元素的设计时可以好好利用下模特的一举一动">这种指向可以通过模特摆出的各种姿势去体现,也可以通过调整模特元素的方向、位置、和排版共同完成对指定内容的指向。在做一些以模特类为主要元素的设计时,可以好好利用下模特的一举一动。</h4>
<p><img src="/images/huangmai/20160831/0262f857c420290000018c1be9f9de.jpg" alt="0262f857c420290000018c1be9f9de" /></p>
<h4 id="实际应用中">实际应用中</h4>
<h4 id="如在表现一些运动活动专题时动感活力是这类需求的主要切入点">如在表现一些运动活动专题时:动感、活力是这类需求的主要切入点。</h4>
<h4 id="在人物动作的挑选上就尽量找一些动作幅度较大夸张的模特来完成对于动起来的基本诉求">在人物动作的挑选上,就尽量找一些动作幅度较大、夸张的模特,来完成对于“动”起来的基本诉求。</h4>
<p><img src="/images/huangmai/20160831/02cc5057c420560000012e7efba633.jpg" alt="02cc5057c420560000012e7efba633" /></p>
<h4 id="左图1模特人物展开双臂运动的动作中其中拿球的左手位置正好置于文字上加之文字颜色为白色使得用户第一时间去关注那部分内容其中右手所示的位置有一个扶地的动作而这个动作也正好巧妙的结合于推荐的商品上使用户的视觉顺着向下进行浏览关注">左图1:模特人物展开双臂运动的动作中,其中拿球的左手位置正好置于文字上,加之文字颜色为白色,使得用户第一时间去关注那部分内容。其中右手所示的位置,有一个扶地的动作,而这个动作也正好巧妙的结合于推荐的商品上,使用户的视觉顺着向下进行浏览关注。</h4>
<h4 id="右图2第一屏图像中模特的动作有秀一下的暗示向用户展示出他的8块腹肌嘻嘻二是人物所展示的拳头指向也使用户的视觉焦点向画面左侧的文字靠拢第二屏模特眼神所示的方向正好指向了右侧内容">右图2:第一屏,图像中模特的动作有“秀一下”的暗示,向用户展示出他的8块腹肌(嘻嘻)。二是人物所展示的拳头指向,也使用户的视觉焦点向画面左侧的文字靠拢。第二屏,模特眼神所示的方向正好指向了右侧内容。</h4>
<h4 id="当你在设计时-1">当你在设计时</h4>
<h4 id="1-选择大片模特时首先要考虑到使用模特做为主视觉元素时模特的动作是否要去结合文案需要构图需要等需求然后再去挑选或者去拍摄适合活动目的并带有指向的动作素材">1、 选择大片模特时,首先要考虑到使用模特做为主视觉元素时,模特的动作是否要去结合文案需要、构图需要等需求。然后再去挑选或者去拍摄适合活动目的并带有指向的动作素材;</h4>
<h4 id="2-在使用模特素材时充分利用好模特的动作表情和眼神的视觉指向按着浏览轨迹安排合适的贩内容到正确的位置上">2、 在使用模特素材时,充分利用好模特的动作、表情和眼神的视觉指向,按着浏览轨迹安排合适的贩内容到正确的位置上。</h4>
<h4 id="眼神">眼神</h4>
<p>眼神的作用,暗示出模特所指的具体内容中,它有着明确的方向指向。</p>
<h4 id="当你看我当我看你当你看它时随着人们注意视线的移动人们关注的焦点也会做出相应的调整">当你看我、当我看你,当你看它时,随着人们注意视线的移动,人们关注的焦点也会做出相应的调整。</h4>
<p><img src="/images/huangmai/20160831/0273a557c420b80000012e7e7ef3a5.jpg" alt="0273a557c420b80000012e7e7ef3a5" /></p>
<h4 id="在实际应用中">在实际应用中</h4>
<h4 id="当你用不同的眼神观察事物时其实你的眼神中就带有了指向目的的作用">当你用不同的眼神观察事物时,其实你的眼神中,就带有了指向目的的作用。</h4>
<h4 id="人们会随着你的眼神所指朝你眼神所示的方向关注过去它会暗示用户的眼睛跟随模特或者元素的指引方向关注到指定的信息这是一种利用模特眼神引起用户关注的一个方法">人们会随着你的眼神所指,朝你眼神所示的方向关注过去。它会暗示用户的眼睛,跟随模特或者元素的指引方向,关注到指定的信息。这是一种利用模特眼神,引起用户关注的一个方法。</h4>
<p><img src="/images/huangmai/20160831/02ae4357c420e50000018c1bbfb56b.jpg" alt="02ae4357c420e50000018c1bbfb56b" /></p>
<h4 id="左图1人物向上的眼神引导用户先将视线聚焦在人物上身然后再跟随人物眼神向上移动关注人物顶端的四块信息">左图1:人物向上的眼神引导用户先将视线聚焦在人物上身,然后再跟随人物眼神向上移动,关注人物顶端的四块信息。</h4>
<h4 id="左图2人物正面向上向右的眼神都把用户的眼神引导到相应的信息上">左图2:人物正面、向上、向右的眼神,都把用户的眼神引导到相应的信息上。</h4>
<h4 id="当你在设计时-2">当你在设计时</h4>
<h4 id="1跟着模特脸的方向眼神所指的方向安排重要次要信息到相应的位置上这会暗示用户的眼球朝所示的方向看过去">1、跟着模特脸的方向、眼神所指的方向,安排重要、次要信息到相应的位置上。这会暗示用户的眼球朝所示的方向看过去。</h4>
<h4 id="2不要忽略模特的表情哦它其实也很重要哦试想一下一个要表达天真无邪充满童趣的画面时你选的模特表情僵硬目光呆滞像欠了500工资一样是不是显得傻了一些呢">2、不要忽略模特的表情哦,它其实也很重要哦。试想一下,一个要表达天真无邪,充满童趣的画面时,你选的模特表情僵硬、目光呆滞,像欠了500工资一样,是不是显得傻了一些呢?</h4>
<h2 id="03上下左右和中间前面后面和侧面">03、上下左右和中间,前面后面和侧面!</h2>
<p>方向,用于提示某种位置。</p>
<h4 id="箭头直线斜线都具有明确的指向性它们是引导用户视线的好元素它不仅有着明确的指向性而且还可以对画面进行分隔对信息进行层次的间隔">箭头、直线、斜线都具有明确的指向性,它们是引导用户视线的好元素。它不仅有着明确的指向性,而且还可以对画面进行分隔,对信息进行层次的间隔。</h4>
<h4 id="在一些场景中指示方向最多的元素当属各式方向的箭头了它是提示用户并暗示行动的一种设计元素">在一些场景中,指示方向最多的元素当属各式方向的箭头了。它是提示用户、并暗示行动的一种设计元素。</h4>
<p><img src="/images/huangmai/20160831/0274ec57c421210000012e7ed3fc21.jpg" alt="0274ec57c421210000012e7ed3fc21" /></p>
<h4 id="在实际应用中-1">在实际应用中</h4>
<h4 id="如果要给用户点明要关注的内容最好的方法就是利用线箭头元素的指向可以很方便的对用户眼睛进行方向上的引导">如果要给用户点明要关注的内容,最好的方法就是利用线、箭头元素的指向,可以很方便的对用户眼睛,进行方向上的引导。</h4>
<p><img src="/images/huangmai/20160831/0227b857c421470000012e7e187dc9.jpg" alt="0227b857c421470000012e7e187dc9" /></p>
<h4 id="左图1线条具有明确的指向性节奏感直线具有男性的特征它有力度相对稳定曲线具有柔美流畅的印象">左图1:线条具有明确的指向性、节奏感。直线具有男性的特征,它有力度、相对稳定。曲线具有柔美、流畅的印象;</h4>
<h4 id="左图2垂直的线有着明确的上下指向作用">左图2:垂直的线,有着明确的上下指向作用。</h4>
<h4 id="当你在设计时-3">当你在设计时</h4>
<h4 id="1在做时尚类服装类的设计需求中灵活运用小短线元素可以帮你加强文字层次修饰画面细节和提示标重的地方中不同的线有着不同的视觉感觉例如粗线给人男人有力的印象曲线给人柔美飘逸的感受">1、在做时尚类、服装类的设计需求中,灵活运用小短线元素可以帮你加强文字层次,修饰画面细节,和提示标重的地方中。不同的线,有着不同的视觉感觉。例如:粗线给人男人、有力的印象;曲线给人柔美、飘逸的感受;</h4>
<h4 id="2在使用线短线箭头元素时一定要加在要进行明确指向的目的上不要为了加而加">2、在使用线、短线、箭头元素时,一定要加在要进行明确指向的目的上。不要为了加,而加。</h4>
<h2 id="04万柳丛中一点红诱导聚焦和突出">04、万柳丛中一点红,诱导聚焦和突出</h2>
<p>万柳丛中一点红,从这句话中折射出了色彩所具有的指向性。</p>
<h4 id="它的优点是直接让主体从万千元素干扰中非常突出并且马上抓住用户视觉">它的优点是直接让主体从万千元素干扰中,非常突出并且马上抓住用户视觉。</h4>
<p><img src="/images/huangmai/20160831/02986857c4218c0000018c1b943180.jpg" alt="02986857c4218c0000018c1b943180" /></p>
<h4 id="左上1人物向上慢慢撕开的红色内容成为用户关注的第一焦点面无表情人物给画面营造出一种诡异恐怖的感觉">左上1:人物向上慢慢撕开的红色内容,成为用户关注的第一焦点,面无表情人物给画面营造出一种诡异、恐怖的感觉。</h4>
<h4 id="左上2科比的湖人黄色首先成为视觉焦点引导用户关注其经典的扣蓝动作中">左上2:科比的湖人黄色首先成为视觉焦点,引导用户关注其经典的扣蓝动作中。</h4>
<h4 id="左下3红色的线条运用提示用户向下继续浏览分散的纤细短红色线条自由无序的摆放在页面的主要内容上使用户的视觉焦点集中在这些线条所指示的内容上">左下3:红色的线条运用提示用户向下继续浏览,分散的纤细短红色线条自由无序的摆放在页面的主要内容上,使用户的视觉焦点,集中在这些线条所指示的内容上。</h4>
<h4 id="左下4红黑的对比使得人物左侧的红色更为明显">左下4:红黑的对比,使得人物左侧的红色更为明显。</h4>
<h4 id="在实际应用可分为二种情况">在实际应用可分为二种情况</h4>
<h4 id="诱导">诱导</h4>
<p>在面对大幅面文字、元素、图像信息时,人的眼睛是需要引导的,而色彩是最为直白的形式之一。</p>
<h4 id="通过对色彩的合理运用着重突出最主要的信息把色彩的反差最大化如果在色彩的引导上加配以元素的指向动作的指向那么色彩指向将会更加的具有冲击力">通过对色彩的合理运用着重突出最主要的信息,把色彩的反差最大化。如果在色彩的引导上,加配以元素的指向、动作的指向,那么色彩指向将会更加的具有冲击力。</h4>
<p><img src="/images/huangmai/20160831/022ddc57c421c30000012e7e1065a4.jpg" alt="022ddc57c421c30000012e7e1065a4" /></p>
<h4 id="左图1是典型的绿红配整体色调的绿色化使得页面最底部的红瓶子显得格外醒目在用户按照从上到下的浏览习惯看完后视线就被定在了这个红色的元素上">左图1:是典型的绿红配,整体色调的绿色化,使得页面最底部的红瓶子显得格外醒目。在用户按照从上到下的浏览习惯看完后,视线就被定在了这个红色的元素上。</h4>
<h4 id="左图2明快对比强烈的色块分别置于画面的左右侧让用户的视线跟着这些色块进行移动既活跃了画面又丰富了整体效果">左图2:明快、对比强烈的色块分别置于画面的左右侧,让用户的视线跟着这些色块进行移动,既活跃了画面,又丰富了整体效果。</h4>
<h4 id="通过上面的例子说明色彩诱导的作用类似于色彩方案中的点睛色把你要做突出的内容用对比色互被色进行表现">通过上面的例子说明,色彩诱导的作用类似于,色彩方案中的点睛色。把你要做突出的内容,用对比色、互被色进行表现。</h4>
<h4 id="聚集">聚集</h4>
<h4 id="当画面出现多二种颜色时用户的眼神首先会聚焦在那些对比性强面积大的突出的颜色上然后再找另外一种颜色然后全部浏览完成">当画面出现多二种颜色时,用户的眼神首先会聚焦在那些对比性强、面积大的突出的颜色上,然后再找另外一种颜色,然后全部浏览完成。</h4>
<p><img src="/images/huangmai/20160831/0208a457c421f40000012e7eb20817.jpg" alt="0208a457c421f40000012e7eb20817" /></p>
<h4 id="通过上面的例子说明色彩聚集的核心作用就是把你要突出的内容突出化对比化聚焦化让用户的视线停留于此阅读你想要传达给他们的信息">通过上面的例子说明,色彩聚集的核心作用就是把你要突出的内容突出化、对比化、聚焦化,让用户的视线停留于此,阅读你想要传达给他们的信息。</h4>
<h4 id="当你在设计时-4">当你在设计时</h4>
<h4 id="1-用反差比较强烈的互补色去突出主要内容时这种聚焦的处理方法更有效">1、 用反差比较强烈的互补色去突出主要内容时,这种聚焦的处理方法更有效;</h4>
<h4 id="2-如果画面中的颜色较多时可以利用色彩的面积大小去给页面出现的所有信息进行重要层次的分级">2、 如果画面中的颜色较多时,可以利用色彩的面积大小,去给页面出现的所有信息进行重要层次的分级。</h4>
<h2 id="05留白设计空白多主角旁边面积大">05、留白设计空白多,主角旁边面积大</h2>
<p>留白,常见的逼格神器,突出主角的必备招数。</p>
<h4 id="如果说上述的几种方法是在做加法的设计那么留白其实是在做减法的设计当主角元素四周的留白很多时人眼的视线首先会从整页复杂的环境中优先发现那个没有任何障碍物的设计元素">如果说上述的几种方法是在做加法的设计,那么留白其实是在做减法的设计。当主角元素四周的留白很多时,人眼的视线首先会从整页复杂的环境中,优先发现那个没有任何障碍物的设计元素。</h4>
<p><img src="/images/huangmai/20160831/02c4e857c423d90000018c1b7dc800.jpg" alt="02c4e857c423d90000018c1b7dc800.jpg" /></p>
<h4 id="在实际应用中-2">在实际应用中</h4>
<h4 id="留白就是留出主角空白层次逼格的一种简洁的设计方法">留白就是留出主角、空白、层次、逼格的一种简洁的设计方法。</h4>
<h4 id="留白的白指的不是颜色的白而是空白的白留白指的是某一区域无多余元素四周处于大面积空白的状态">留白的”白”指的不是颜色的”白”,而是空白的”白”,留白指的是某一区域无多余元素、四周处于大面积空白的状态。</h4>
<p><img src="/images/huangmai/20160831/02437057c424040000012e7ec8d361.jpg" alt="02437057c424040000012e7ec8d361" /></p>
<h4 id="左图1以产品为主的专题且只有的少量信息页面产品四周大面积的留白处理使产品的形态细节更加的聚焦同时留白的处理也提升品牌的品质感和气质">左图1:以产品为主的专题且只有的少量信息页面,产品四周大面积的留白处理,使产品的形态,细节更加的聚焦。同时留白的处理,也提升品牌的品质感和气质。</h4>
<h4 id="右图2整体页面非常多的留白除了一些扁平的设计元素外主画面以外多是以白的形式出现的">右图2:整体页面非常多的留白,除了一些扁平的设计元素外,主画面以外多是以“白”的形式出现的。</h4>
<h4 id="当你在设计时-5">当你在设计时</h4>
<h4 id="1有效的留白可以提升画面的逼格而且可以使要突出的主角更突出">1、有效的留白可以提升画面的逼格,而且可以使要突出的主角更突出;</h4>
<h4 id="2敢留白大量留白会使页面有空间感不会因为页面内容密密麻麻而产生厌恶">2、敢留白:大量留白会使页面有空间感,不会因为页面内容密密麻麻而产生厌恶。</h4>
<h4 id="3使用最精简的元素不添加无谓的设计元素给用户带去视觉干扰只保留核心和必要的关键关素并且对关键元素进行细节的刻画">3、使用最精简的元素:不添加无谓的设计元素,给用户带去视觉干扰,只保留核心和必要的关键关素,并且对关键元素进行细节的刻画。</h4>
<h2 id="06一二三四五六七7654321">06、一二三四五六七,7654321</h2>
<p>数字指向,顺序的指向性。</p>
<h4 id="一般人都会有这样的体验当看到数字1时就要去寻找数字2有些需求里在需要用视觉牵引用户眼睛时把数字当标头使用户视线在页面中合理跳跃">一般人都会有这样的体验,当看到数字1时,就要去寻找数字2。有些需求里,在需要用视觉牵引用户眼睛时,把数字当标头,使用户视线在页面中合理跳跃。</h4>
<p><img src="/images/huangmai/20160831/02fe9957c4244e0000018c1b1e74b5.jpg" alt="02fe9957c4244e0000018c1b1e74b5" /></p>
<h4 id="在实际应用中-3">在实际应用中</h4>
<h4 id="数字元素的运用往往出现在以目录页发展历程或者产品排列展示中">数字元素的运用往往出现在以目录页、发展历程、或者产品排列展示中。</h4>
<p><img src="/images/huangmai/20160831/028caf57c424710000012e7eac0185.jpg" alt="028caf57c424710000012e7eac0185" /></p>
<h4 id="左图1鲁尼踢球的动作和方向正好指向了右侧的内容区以数字为代表的标头又指示用户逐个阅读或者分散阅读">左图1:鲁尼踢球的动作和方向正好指向了右侧的内容区,以数字为代表的标头又指示用户逐个阅读或者分散阅读。</h4>
<h4 id="右图1数字的作用就是用来指示用户继续向下阅读黄色与黑色的强烈火对比">右图1:数字的作用就是用来指示用户继续向下阅读,黄色与黑色的强烈火对比。</h4>
<h4 id="左图2产品周围大面积的留白使得主角先被人关注到右侧泛白的标题数字起到了突出产品折扣的目的">左图2:产品周围大面积的留白,使得主角先被人关注到。右侧泛白的标题数字,起到了突出产品折扣的目的。</h4>
<h4 id="当你在设计时-6">当你在设计时</h4>
<h4 id="1做时尚搭配的需求时可以用数字去引导产品的顺序">1、做时尚搭配的需求时,可以用数字去引导产品的顺序;</h4>
<h4 id="2以用运数字为元素时对数字的大小和字形要进行整体的把控切不可让数字影响主体内容当然如果是以数字为主的就要尽量突出数字的视觉性">2、以用运数字为元素时,对数字的大小和字形要进行整体的把控。切不可让数字影响主体内容,当然如果是以数字为主的,就要尽量突出数字的视觉性。</h4>
<h2 id="07动的太大显得闹静的太死显得闷">07、动的太大显得闹,静的太死显得闷</h2>
<p>动静,就是画面的静与动。</p>
<h4 id="动的元素在整体静态的页面中更能吸引用户的眼球当然这其中包括页面中有gif图视觉元素的动感表现等">动的元素在整体静态的页面中更能吸引用户的眼球。当然这其中包括页面中有gif图、视觉元素的动感表现等。</h4>
<p><img src="/images/huangmai/20160831/0242c857c424bb0000018c1b9ef21d.jpg" alt="0242c857c424bb0000018c1b9ef21d" /></p>
<h4 id="在实际应用中-4">在实际应用中</h4>
<h4 id="动的表现可以用人物的动作产品的动作gif图来完成对动的执行但是画面中同一屏的页面中同时动的元素最好不要超过3处或更多因为每动一处都会引起人们视觉的噪动如果动的太多势必会分散用户的注意力使主要内容的传达弱化">动的表现可以用人物的动作、产品的动作、gif图来完成对动的执行。但是画面中同一屏的页面中,同时动的元素最好不要超过3处或更多,因为每动一处都会引起人们视觉的噪动,如果动的太多,势必会分散用户的注意力。使主要内容的传达,弱化。</h4>
<p><img src="/images/huangmai/20160831/02fb5657c424dd0000012e7e3074d1.gif" alt="02fb5657c424dd0000012e7e3074d1" /></p>
<h4 id="大家注意细节手的动作">大家注意细节:手的动作。</h4>
<h4 id="当你在设计时-7">当你在设计时</h4>
<h4 id="1---瞬间让元素有动感的处理方法给元素增加动感模糊给画面增加飞着的元素如五彩纸屑红包倾斜着的线等">1、 瞬间让元素有动感的处理方法:给元素增加动感模糊;给画面增加飞着的元素如:五彩纸屑、红包、倾斜着的线等。</h4>
<h4 id="2---动的页面还可以通过用现在较流行的h5视差滚动等技术让页面中的不同元素根据用户的鼠标滚动相应的出现">2、 动的页面还可以通过用现在较流行的h5、视差滚动等技术让页面中的不同元素根据用户的鼠标滚动相应的出现;</h4>
<h2 id="08文案设计想法多紧扣主题有内涵">08、文案设计想法多,紧扣主题有内涵</h2>
<p>文案,用文笔引导用户产生画面感。</p>
<h4 id="在实际应用中-5">在实际应用中</h4>
<h4 id="相比较而言文字虽没有色彩那么实在也没人物动作那么显而易见但是它却是最能走进人们内心的形式">相比较而言,文字虽没有色彩那么实在,也没人物动作那么显而易见,但是它却是最能走进人们内心的形式。</h4>
<p><img src="/images/huangmai/20160831/02de5757c4258f0000012e7eb142af.jpg" alt="02de5757c4258f0000012e7eb142af" /></p>
<h4 id="左图1像土豪一样表白设计师完全找到了文案与形象之间的切入点结合卡通的表现形式使得绘制的形象活生生的表现了某些土豪的豪气">左图1:像土豪一样表白,设计师完全找到了文案与形象之间的切入点,结合卡通的表现形式,使得绘制的形象活生生的表现了某些土豪的“豪气”。</h4>
<h4 id="右图1五折封顶-不留余力设计师通过文案的发散绘制了一个正在点燃的机器像是要引爆全场的感觉">右图1:五折封顶 不留余力,设计师通过文案的发散,绘制了一个正在点燃的机器,像是要引爆全场的感觉。</h4>
<h4 id="当你在设计时-8">当你在设计时</h4>
<h4 id="1好文案往往会联想到一些元素的色彩的风格的关键字这样的好文案往往和图像的贴合度更高">1、好文案往往会联想到一些元素的、色彩的、风格的关键字,这样的好文案往往和图像的贴合度更高。</h4>
<h4 id="2文案的视觉设计往往通过对文案意思具象的抽象型的表达引起用户的共鸣">2、文案的视觉设计,往往通过对文案意思具象的、抽象型的表达引起用户的共鸣。</h4>
<h2 id="结束语">结束语</h2>
<h4 id="以上的8点就是我对视觉元素引导的八个方法">以上的8点就是我对视觉元素引导的八个方法。</h4>
H5终端自适应解决方案 -- flexible + rem
2016-08-26T00:00:00+00:00
http://www.blogways.net/blog/2016/08/26/web-rem-share
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">目前移动端现状</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">像素的概念</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3nd">初识rem与flexible</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">制作rem小例子</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一目前移动端现状">一、目前移动端现状</h2>
<h4 id="随着移动设备的普及不同的手机屏幕与尺寸接连出现作为前端开发人员虽然h5的页面与pc的web页面相比简单了不少但让我们头痛的事情是要想尽办法让页面能适配众多不同的终端设备让我们来看看目前市场上的情况">随着移动设备的普及,不同的手机屏幕与尺寸接连出现;作为前端开发人员,虽然H5的页面与PC的Web页面相比简单了不少,但让我们头痛的事情是要想尽办法让页面能适配众多不同的终端设备,让我们来看看目前市场上的情况</h4>
<p><img src="/images/liuyw6/20160826img01.png" alt="20160826img01" /></p>
<h4 id="而作为前端开发人员需要适配终端设备数据如下图">而作为前端开发人员,需要适配终端设备数据如下图</h4>
<p><img src="/images/liuyw6/20160826img02.png" alt="20160826img02" /></p>
<h4 id="看到这些数据是否死的心都有了或者说为此捏了一把汗出来">看到这些数据,是否死的心都有了,或者说为此捏了一把汗出来。</h4>
<h4 id="为了应对如此多的终端设备设计师和前端开发之间又应该采用什么协作模式">为了应对如此多的终端设备,设计师和前端开发之间又应该采用什么协作模式?</h4>
<h4 id="目前淘宝的设计思路为">目前淘宝的设计思路为:</h4>
<h4 id="1选择一种尺寸作为设计和开发基准">1.选择一种尺寸作为设计和开发基准</h4>
<h4 id="2定义一套适配规则自动适配剩下的两种尺寸其实不仅这两种你懂的">2.定义一套适配规则,自动适配剩下的两种尺寸(其实不仅这两种,你懂的)</h4>
<h4 id="3特殊适配效果给出设计效果">3.特殊适配效果给出设计效果</h4>
<h4 id="如下图为手淘的协作模式">如下图为手淘的协作模式</h4>
<p><img src="/images/liuyw6/20160826img03.jpg" alt="20160826img03" /></p>
<h4 id="通过上图可以看出手淘设计师常选择iphone6作为基准设计尺寸交付给前端的设计尺寸是按750px--1334px为准高度会随着内容多少而改变前端开发人员通过一套适配规则flexiblerem自动适配到其他的尺寸flexiblerem会在后面详解">通过上图可以看出,手淘设计师常选择iPhone6作为基准设计尺寸,交付给前端的设计尺寸是按750px * 1334px为准(高度会随着内容多少而改变)。前端开发人员通过一套适配规则(flexible+rem)自动适配到其他的尺寸,flexible+rem会在后面详解。</h4>
<h2 id="二像素的概念">二、像素的概念</h2>
<h4 id="做为前端开发人员对于像素的概念是不可或缺的其中包含了物理像素physical-pixel设备独立像素density-independent-pixelcss像素屏幕密度设备像素比device-pixel-ratio等基本概念让我们一个个的理解">做为前端开发人员,对于像素的概念是不可或缺的,其中包含了“物理像素(physical pixel)”、“设备独立像素(density-independent pixel)”、“CSS像素”、“屏幕密度”、“设备像素比(device pixel ratio)”等基本概念,让我们一个个的理解</h4>
<h3 id="1物理像素physical-pixel">1.物理像素(physical pixel)</h3>
<h4 id="物理像素又被称为设备像素他是显示设备中一个最微小的物理部件每个像素可以根据操作系统设置自己的颜色和亮度正是这些设备像素的微小距离欺骗了我们肉眼看到的图像效果">物理像素又被称为设备像素,他是显示设备中一个最微小的物理部件。每个像素可以根据操作系统设置自己的颜色和亮度。正是这些设备像素的微小距离欺骗了我们肉眼看到的图像效果。</h4>
<h3 id="2设备独立像素density-independent-pixel">2.设备独立像素(density-independent pixel)</h3>
<h4 id="设备独立像素也称为密度无关像素可以认为是计算机坐标系统中的一个点这个点代表一个可以由程序使用的虚拟像素比如说css像素然后由相关系统转换为物理像素">设备独立像素也称为密度无关像素,可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如说CSS像素),然后由相关系统转换为物理像素。</h4>
<h3 id="3css像素">3.CSS像素</h3>
<h4 id="css像素是一个抽像的单位主要使用在浏览器上用来精确度量web页面上的内容一般情况之下css像素称为与设备无关的像素device-independent-pixel简称dips">CSS像素是一个抽像的单位,主要使用在浏览器上,用来精确度量Web页面上的内容。一般情况之下,CSS像素称为与设备无关的像素(device-independent pixel),简称DIPs。</h4>
<h3 id="4屏幕密度">4.屏幕密度</h3>
<h4 id="屏幕密度是指一个设备表面上存在的像素数量它通常以每英寸有多少像素来计算ppi">屏幕密度是指一个设备表面上存在的像素数量,它通常以每英寸有多少像素来计算(PPI)。</h4>
<h3 id="5设备像素比device-pixel-ratio">5.设备像素比(device pixel ratio)</h3>
<h4 id="设备像素比简称为dpr其定义了物理像素和设备独立像素的对应关系它的值可以按下面的公式计算得到">设备像素比简称为dpr,其定义了物理像素和设备独立像素的对应关系。它的值可以按下面的公式计算得到:</h4>
<h4 id="设备像素比--物理像素--设备独立像素">设备像素比 = 物理像素 / 设备独立像素</h4>
<h4 id="在javascript中可以通过windowdevicepixelratio获取到当前设备的dpr而在css中可以通过-webkit-device-pixel-ratio-webkit-min-device-pixel-ratio和--webkit-max-device-pixel-ratio进行媒体查询对不同dpr的设备做一些样式适配这里只针对webkit内核的浏览器和webview">在JavaScript中,可以通过window.devicePixelRatio获取到当前设备的dpr。而在CSS中,可以通过-webkit-device-pixel-ratio,-webkit-min-device-pixel-ratio和 -webkit-max-device-pixel-ratio进行媒体查询,对不同dpr的设备,做一些样式适配(这里只针对webkit内核的浏览器和webview)。</h4>
<h4 id="dip或dpdevice-independent-pixels设备独立像素与屏幕密度有关dip可以用来辅助区分视网膜设备还是非视网膜设备">dip或dp,(device independent pixels,设备独立像素)与屏幕密度有关。dip可以用来辅助区分视网膜设备还是非视网膜设备</h4>
<h4 id="众所周知iphone6的设备宽度和高度为375pt--667pt可以理解为设备的独立像素而其dpr为2根据上面公式我们可以很轻松得知其物理像素为750pt--1334pt">众所周知,iPhone6的设备宽度和高度为375pt * 667pt,可以理解为设备的独立像素;而其dpr为2,根据上面公式,我们可以很轻松得知其物理像素为750pt * 1334pt。</h4>
<h2 id="三初识rem与flexible">三、初识rem与flexible</h2>
<h3 id="1css3新成员rem">1.CSS3新成员:rem</h3>
<h4 id="在w3c规范中是这样描述rem的">在W3C规范中是这样描述rem的:</h4>
<h4 id="font-size-of-the-root-element">font size of the root element.</h4>
<h4 id="简单的理解rem就是相对于根元素html的font-size来做计算而我们的方案中使用rem单位是能轻易的根据html的font-size计算出元素的盒模型大小而这个特色对我们来说是特别的有益处">简单的理解,rem就是相对于根元素<html>的font-size来做计算。而我们的方案中使用rem单位,是能轻易的根据<html>的font-size计算出元素的盒模型大小。而这个特色对我们来说是特别的有益处。</h4>
<h4 id="在整个手淘团队我们有一个名叫lib-flexible的库而这个库就是用来解决h5页面终端适配的">在整个手淘团队,我们有一个名叫lib-flexible的库,而这个库就是用来解决H5页面终端适配的。</h4>
<h3 id="2lib-flexible是什么">2.lib-flexible是什么?</h3>
<h4 id="lib-flexible是一个制作h5适配的开源库获取需要的javascript和css文件可以直接下载或直接通过阿里cdn云">lib-flexible是一个制作H5适配的开源库,获取需要的JavaScript和CSS文件,可以直接下载或直接通过阿里CDN云</h4>
<h4 id="下载地址httpsgithubcomamfelib-flexiblearchivemasterzip">下载地址:https://github.com/amfe/lib-flexible/archive/master.zip</h4>
<h4 id="下载后解压同时引入js库如">下载后解压,同时引入js库,如</h4>
<h4><script src="build/flexible_css.debug.js"></script></h4>
<h4 id="-1"><script src="build/flexible.debug.js"></script></h4>
<h4 id="云引用">云引用:<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script></h4>
<h4 id="3lib-flexible有什么用">3.lib-flexible有什么用?</h4>
<h4 id="lib-flexible加入执行后会在html元素上增加一个data-dpr属性以及一个font-size样式">lib-flexible加入执行后,会在<html>元素上增加一个data-dpr属性,以及一个font-size样式。</h4>
<h4 id="js会根据不同的设备添加不同的data-dpr值比如说2或者3同时会给html加上对应的font-size的值比如说75px">JS会根据不同的设备添加不同的data-dpr值,比如说2或者3,同时会给html加上对应的font-size的值,比如说75px;</h4>
<h4 id="如此一来页面中的元素都可以通过rem单位来设置他们会根据html元素的font-size值做相应的计算从而实现屏幕的适配效果">如此一来,页面中的元素,都可以通过rem单位来设置。他们会根据html元素的font-size值做相应的计算,从而实现屏幕的适配效果。</h4>
<h2 id="四rem小例子分析">四、rem小例子分析</h2>
<h4 id="请用手机扫下面的二维码查看最终实现效果">请用手机扫下面的二维码查看最终实现效果</h4>
<p><img src="/images/liuyw6/20160826img04.jpg" alt="20160826img04" /></p>
<h4 id="该界面是如何实现的首先我们来看一张750的设计图稿">该界面是如何实现的?首先我们来看一张750的设计图稿</h4>
<p><img src="/images/liuyw6/20160826img05.png" alt="20160826img05" /></p>
<h4 id="通过该设计稿可知每个元素的边距大小在7501334上的比例前端开发人员可在750设备上完成框架设计即iphone6上进行开发之后通过将px转化为rem实现终端适配详细流程如下">通过该设计稿可知每个元素的边距、大小在750*1334上的比例;前端开发人员可在750设备上完成框架设计(即iphone6上进行开发),之后通过将px转化为rem实现终端适配,详细流程如下</h4>
<h3 id="1创建html模版">1.创建HTML模版</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta content="yes" name="apple-mobile-web-app-capable">
<meta content="yes" name="apple-touch-fullscreen">
<meta content="telephone=no,email=no" name="format-detection">
<script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
<link rel="apple-touch-icon" href="favicon.png">
<link rel="Shortcut Icon" href="favicon.png" type="image/x-icon">
<title>flexible+rem实战用例</title>
</head>
<body>
<!-- 页面结构写在这里 -->
</body>
</html>
</code></pre></div></div>
<h4 id="首先需要加载flexible所需的配置">首先需要加载Flexible所需的配置</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script src="http://g.tbcdn.cn/mtb/lib-flexible/0.3.4/??flexible_css.js,flexible.js"></script>
</code></pre></div></div>
<h3 id="2根据设计图编写body中的内容具体如下">2.根据设计图编写<body>中的内容,具体如下</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="item-section" data-repeat="sections">
<div class="item-section_header">
<h2><img src="http://xxx.cdn.com/B1PNLZKXXXXXaTXXXXXXXXXXXX-750-481.jpg" alt=""></h2>
</div>
<ul>
<li data-repeat="items" class="flag" role="link" href="##">
<a class="figure flag-item" href="##">
<img src="https://placeimg.com/350/350/people/grayscale" alt="">
</a>
<div class="figcaption flag-item">
<div class="flag-title"><a href="##" title="">Carter's1年式灰色长袖连体衣包脚爬服全棉鲸鱼男婴儿童装115G093</a></div>
<div class="flag-price"><span>双11价</span><strong>¥299.06</strong><small>(满400减100)</small></div>
<div class="flag-type">1小时内热卖5885件</div>
<a class="flag-btn" href="##">马上抢!</a>
</div>
</li>
</ul>
</div>
</code></pre></div></div>
<h3 id="3将px转化为rem以适配不同终端">3.将px转化为rem,以适配不同终端</h3>
<h4 id="在实际生产当中如果每一次计算px转换rem或许会觉得非常麻烦或许直接影响大家平时的开发效率为了能让大家更快进行转换我们团队内的同学各施所长为px转换rem写了各式各样的小工具">在实际生产当中,如果每一次计算px转换rem,或许会觉得非常麻烦,或许直接影响大家平时的开发效率。为了能让大家更快进行转换,我们团队内的同学各施所长,为px转换rem写了各式各样的小工具。</h4>
<h4 id="1cssrem">(1)CSSREM</h4>
<h4 id="cssrem是一个css的px值转rem值的sublime-text3自动完成插件这个插件是由正霖编写先来看看插件的效果">CSSREM是一个CSS的px值转rem值的Sublime Text3自动完成插件。这个插件是由@正霖编写。先来看看插件的效果:</h4>
<p><img src="/images/liuyw6/20160826img06.gif" alt="20160826img06" /></p>
<h4 id="2在线转换器">(2)在线转换器</h4>
<h4 id="地址http520uedcomtoolsrem--该转换器上传工程css文件然后定义html-font-size即可转换">地址:http://520ued.com/tools/rem ; 该转换器上传工程CSS文件,然后定义html font size,即可转换</h4>
<h2 id="结束语">结束语</h2>
<h4 id="本文主要就目前移动终端的适配做了一个简单的阐述目的在于认识移动终端知晓目前主流的适配方法详细的学习请参考丰富的网络资源谢谢">本文主要就目前移动终端的适配做了一个简单的阐述,目的在于认识移动终端,知晓目前主流的适配方法;详细的学习请参考丰富的网络资源,谢谢!</h4>
SVG学习[2]--基本数据及path元素
2016-08-18T00:00:00+00:00
http://www.blogways.net/blog/2016/08/18/web-svg-2
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">基本属性</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">path路径(SVG)</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#end">小结</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一基本属性">一、基本属性</h2>
<h3 id="1笔划与填充">1、笔划与填充</h3>
<p>第一篇博文中的示例已经演示了围绕对象的笔划或线以及对象内部区域的填充.这些属性实际上还有子属性,也可以设置子属性来创建不同的效果.这些属性包括:</p>
<p>fill:该属性指定用来填充对象内部区域的颜料.大多数情况下,该属性只是一种颜色,但它也可以是渐变或图案(会在图案中介绍).这个值通常是关键字、颜色说明或指向预定义元素的 URI.</p>
<p>fill-opacity:该属性指定元素的透明性.值的范围从完全透明(0)到完全不透明(1).</p>
<p>stroke:该属性指定元素外边框的外观.象 fill 一样,它引用颜料,尽管通常将它指定为一种简单颜色.</p>
<p>stroke-width:该属性指定笔划线的宽度.</p>
<p>stroke-linecap:该属性确定线末端的形状,可取的值有粗端(butt)、圆(round)和正方形(square).</p>
<p>stroke-linejoin:该属性确定对象各角的外观.允许的值有直角(缺省值)、圆和斜角,它如示例中所示将尖角的边缘“剪掉”.</p>
<p>stroke-dasharray:该属性是一个整数系列(如 3、2、3、2、4、3、2 和 3),它允许对虚线中每一划的相对长度进行控制.</p>
<p>stroke-opacity:类似于 fill-opacity,该属性确定元素笔划线的相对透明性.</p>
<p><a href="http://runjs.cn/code/pyifisys">Fill And Stroke Example</a></p>
<h3 id="2颜色">2、颜色</h3>
<p>颜色对于 SVG 图像是极其重要的.单个颜色可以直接使用它们的 RGB 值指定,或者使用差不多 150 个颜色关键字中的一个来间接指定,该关键字也引用 RGB 值.</p>
<p>RGB 值在 0 到 255 数值范围内指定一种颜色的红、绿、蓝成分的相对亮度,例如:</p>
<p>fill=”darkorchid”;fill=”lightgrey”;</p>
<p>fill=”rgb(255,0,0)”;fill=”rgb(0,255,0)”; fill=”rgb(0,0,255)”;</p>
<p>fille=”#000000”; fill=”#ffffff”.</p>
<p><a href="http://runjs.cn/code/2turdmcq">具体颜色示例</a></p>
<h3 id="3渐变">3、渐变</h3>
<p>渐变提供了将颜色混合在一起的能力.</p>
<p>渐变有两种,<linearGradient>(线性渐变) 和 <radialGradient>(放射渐变)</p>
<p>对于每种情况,代码都指定沿着渐变向量的颜色“停止”或颜色点,渐变到这些点就成为某种颜色.</p>
<p>例如,指定红色在 0% 停止,白色在 50% 停止而蓝色在 100% 停止的渐变将逐渐由红色变为白色再变为蓝色,白色在渐变向量的中心.</p>
<p><linearGradient> 和 <radialGradient> 标签必须嵌套在 <defs> 中.<defs> 标签是 definitions 的缩写,它允许对诸如渐变等特殊元素进行定义.</defs></defs></p>
<h4 id="alineargradient">A、<linearGradient></h4>
<p><linearGradient>标签属性有:</p>
<p>id 属性是渐变定义一个唯一的名称,图形元素可通过fill=”url(linearGradientId)”来引用该线性渐变效果;</p>
<p>x1、x2、y1、y2 属性可定义渐变的开始和结束位置, x1,y1指定起点坐标, x2,y2指定结束坐标;</p>
<p>渐变的颜色范围可由两种或多种颜色组成.每种颜色通过一个 <stop> 标签来规定.offset 属性用来定义渐变的开始和结束位置.</stop></p>
<p><a href="http://runjs.cn/code/zls7bjin">线性渐变示例</a></p>
<h4 id="bradialgradient">B、<radialGradient></h4>
<p><radialGradient> 标签属性有:</p>
<p>id 属性是渐变定义一个唯一的名称,图形元素可通过fill=”url(linearGradientId)”来引用该放射渐变效果;</p>
<p>cx、cy 和 r 属性定义外圈,而 fx 和 fy 定义内圈;</p>
<p>渐变的颜色范围可由两种或多种颜色组成.每种颜色通过一个 <stop> 标签来规定.offset 属性用来定义渐变的开始和结束位置.</stop></p>
<p><a href="http://runjs.cn/code/mqw7yjwc">放射渐变示例</a></p>
<h3 id="4图案pattern">4、图案(<pattern>)</h3>
<p>图案 一般用于SVG图形对象的填充 fill 或描边 stroke .这个图形可以是一个SVG元素,也可以是像素图,通过 <pattern> 元素在 x 轴或 y 轴方向以固定的间隔平铺.</p>
<p>可以在 <pattern> 元素内定义图案,然后通过 id 引用.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg width="660" height="220">
<defs>
<pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
<circle cx="10" cy="10" r="10" stroke="none" fill="#393" />
</pattern>
</defs>
<rect x="10" y="10" width="600" height="200" stroke="#630" stroke-width="5px" fill="url(#pattern)" />
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/twae9n9z">图案示例</a></p>
<p>在 <defs> 标签内定义图案, <pattern> 元素中的内容直到引用的时候才会显示.</p>
<p><pattern> 中定义一个 id,方便在后面被矩形引用.</p>
<p><rect> 元素使用图案作为填充,然后再使用纯色来作为描边.</p>
<p>在 <pattern> 元素内 ,定义了一个半径为 10(px) 的绿色圆,cx 和 cy 的值(分别为 10px )设置了 circle 元素的初始圆的原点.</p>
<p>具体效果点击示例查看,下面来看看<pattern> 元素的属性:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><pattern id="pattern" x="0" y="0" width="20" height="20" patternUnits="userSpaceOnUse">
</code></pre></div></div>
<p>x 和 y 属性定义了图案的起点.</p>
<p>这里把它们都设置为0,这样在图案开始前就不会有偏移,使圆形的中心和图案的起点对齐.</p>
<p>width 和 height 属性定义了重复图案的宽度和高度,因为图例中是一个圆,所以设置了相同的宽高.</p>
<p>patternUnits 定义图案的展现方式,它的值有两个:</p>
<p>userSpaceOnUse: x 、 y 、 width 和 height 表示的值都是当前用户坐标系统的值,也就是说,这些值没有缩放,都是绝对值.</p>
<p>objectBoundingBox(默认值): x 、 y 、 width 和 height 表示的值都是外框的坐标系统(包裹pattern的元素),也就是说,图案的单位进行了一个缩放,比如:pattern中为 1 的值,会变成和包裹元素的外框的 width 和 height 一样的大小.</p>
<p><a id="2nd"></a></p>
<h2 id="二path路径">二、path路径</h2>
<p>path可能是SVG中最常见的形状.你可以用path元素绘制矩形(直角矩形或者圆角矩形)、圆形、椭圆、折线形、多边形,以及一些其他的形状,例如贝塞尔曲线、2次曲线等曲线.</p>
<p>path只需要设定很少的点,就可以创建平滑流畅的线条(比如曲线).虽然polyline元素也能实现类似的效果,但是必须设置大量的点(点越密集,越接近连续,看起来越平滑流畅),并且这种做法不能够放大(放大后,点的离散更明显.所以在绘制SVG时,对路径的良好理解很重要.</p>
<p>path元素的形状是通过属性d定义的,属性d的值是一个“命令+参数”的序列</p>
<p>每一个命令都用一个关键字母来表示,比如,字母“M”表示的是“Move to”命令,当解析器读到这个命令时,它就知道你是打算移动到某个点.</p>
<p>跟在命令字母后面的,是你需要移动到的那个点的x和y轴坐标.比如移动到(10,10)这个点的命令,应该写成“M 10 10”.这一段字符结束后,解析器就会去读下一段命令.</p>
<p>每一个命令都有两种表示方式,一种是用大写字母,表示采用绝对定位.另一种是用小写字母,表示采用相对定位.</p>
<p>path标准命令:</p>
<table>
<thead>
<tr>
<th>命令</th>
<th>名称</th>
<th>参数</th>
</tr>
</thead>
<tbody>
<tr>
<td>M</td>
<td>moveto(移动到)</td>
<td>(x,y)+</td>
</tr>
<tr>
<td>Z</td>
<td>closepath(关闭路径)</td>
<td>none</td>
</tr>
<tr>
<td>L</td>
<td>lineto(画线到)</td>
<td>(x,y)+</td>
</tr>
<tr>
<td>H</td>
<td>horizontal lineto(水平线到)</td>
<td>x+</td>
</tr>
<tr>
<td>V</td>
<td>vertical lineto(垂直线到)</td>
<td>y+</td>
</tr>
<tr>
<td>C</td>
<td>Bézier curveto(三次贝塞尔曲线到)</td>
<td>(x1 y1 x2 y2 x y)+</td>
</tr>
<tr>
<td>S</td>
<td>smooth curveto(光滑三次贝塞尔曲线到)</td>
<td>(x2 y2 x y)+</td>
</tr>
<tr>
<td>Q</td>
<td>quadratic Bézier curveto (二次贝塞尔曲线到)</td>
<td>(x1 y1 x y)+</td>
</tr>
<tr>
<td>T</td>
<td>smooth quadratic Bézier curveto(光滑二次贝塞尔曲线到)</td>
<td>(x y)+</td>
</tr>
<tr>
<td>A</td>
<td>elliptical arc(椭圆弧)</td>
<td>(rx ry x-axis-rotation large-arc-flag sweep-flag x y)+</td>
</tr>
</tbody>
</table>
<h3 id="1直线命令">1、直线命令</h3>
<p><path>元素里有5个画直线的命令,顾名思义,直线命令就是在两个点之间画直线.</p>
<p>首先是“Move to”命令,M,前面已经提到过,它需要两个参数,分别是需要移动到的点的x轴和y轴的坐标.</p>
<p>假设,你的画笔当前位于一个点,在使用M命令移动画笔后,只会移动画笔,但不会在两点之间画线.</p>
<p>因为M命令仅仅是移动画笔,但不画线.所以M命令经常出现在路径的开始处,用来指明从何处开始画.</p>
<p>能够真正画出线的命令有三个,最常用的是“Line to”命令,L,L需要两个参数,分别是一个点的x轴和y轴坐标,L命令将会在当前位置和新位置(L前面画笔所在的点)之间画一条线段.</p>
<p>另外还有两个简写命令,用来绘制平行线和垂直线.</p>
<p>H,绘制平行线.V,绘制垂直线.</p>
<p>这两个命令都只带一个参数,标明在x轴或y轴移动到的位置,因为它们都只在坐标轴的一个方向上移动.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <svg width="400px" height="400px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<!-- 用L命令画一条直线 -->
<path d="M10 10 L 90,20 " stroke="red" stroke-width="2" />
<!-- 用H命令画一条水平直线 -->
<path d="M10 30 H 90 " stroke="green" stroke-width="2" />
<!-- 用V命令画一条水平直线 -->
<path d="M10 50 V 90 " stroke="yellow" stroke-width="2" />
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/hjfppt2d">简单线条示例</a></p>
<p>现在已经掌握了一些命令,可以开始画一些东西.</p>
<p>先从简单的地方开始,画一个简单的矩形(同样的效果用<rect></rect>元素可以更简单的实现.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg width="400px" height="400px" version="1.1" xmlns="http://www.w3.org/2000/svg">
<path d="M10 10 H 90 V 90 H 10 L 10 10" fill='white' stroke='red' stroke-width='2'/>
<!-- 通过“闭合路径命令”Z来简化上面的path,Z命令会从当前点画一条直线到路径的起点,Z命令不用区分大小写.-->
<path d="M110 10 H 200 V 90 H 110 Z" fill="gray" stroke="yellow" stroke-width='2'/>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/w7bmzd1t">简单矩形示例</a></p>
<h3 id="2贝塞尔曲线命令">2、贝塞尔曲线命令</h3>
<p>贝塞尔曲线(Bézier curve),又称贝兹曲线或贝济埃曲线,是应用于二维图形应用程序的数学曲线,Canvas,CSS3动画函数以及许多画图应用都离不开它.</p>
<p>由于数学不是很好,不能很好解释贝塞尔曲线,只好放几张图帮助理解,有兴趣可以自行Google,本文只讨论贝塞尔曲线在SVG中的用法.</p>
<p>线性贝塞尔曲线演示动画,t在[0,1]区间</p>
<p><img src="/images/Bezier_1.gif" alt="Bezier_1" /></p>
<p>二次贝塞尔曲线演示动画,t在[0,1]区间</p>
<p><img src="/images/Bezier_2.gif" alt="Bezier_2" /></p>
<p>三次贝塞尔曲线演示动画,t在[0,1]区间</p>
<p><img src="/images/Bezier_3.gif" alt="Bezier_3" /></p>
<p>四次贝塞尔曲线演示动画,t在[0,1]区间</p>
<p><img src="/images/Bezier_4.gif" alt="Bezier_4" /></p>
<p>五次贝塞尔曲线演示动画,t在[0,1]区间</p>
<p><img src="/images/Bezier_5.gif" alt="Bezier_5" /></p>
<p>path标签中,与贝塞尔曲线相关的命令有C命令 S命令 Q命令 T命令.</p>
<h4 id="acs组合三次贝塞尔曲线命令组合">A、CS组合(三次贝塞尔曲线命令组合)</h4>
<p>C命令和S命令是专门绘制三次贝塞尔曲线的.</p>
<p><img src="/images/svg-3curveto.png" alt="svg-3curveto" /></p>
<p>上图是Photoshop中使用钢笔工具时候的截图再加工.</p>
<p>所谓钢笔工具抠图实际上就是一个一个贝塞尔曲线连接的结果,各类图形绘制软件类似工具本质上都是贝塞尔曲线.</p>
<p>图片有4个点出现,曲线的两个端点,以及两个控制点,这就是典型的三次贝塞尔曲线.</p>
<p>对应的path代码中就是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <!-- 其中,d="M20,20 C90,40 130,40 180,20" -->
<!-- M20,20 表示曲线的起点 -->
<!-- C90,40 130,40 180,20 对应上图的2个控制点和1个关键点 -->
<path d="M20,20 C90,40 130,40 180,20" stroke="#000000" fill="none" style="stroke:red" >
</code></pre></div></div>
<p>上面代码只用到了C命令,那么S命令呢?</p>
<p><img src="/images/ShortCut_Cubic_Bezier.png" alt="ShortCut_Cubic_Bezier" /></p>
<p>把关注点放在蓝线上面.S指令会自动补出一个对称的控制点(蓝线部分),于是就会有连续的平滑曲线.</p>
<p>对应path代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><path d="M20,150 C60,40 80,40 120,150 S180 260 220 80" fill="none" style="stroke:yellow"></path>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/oemt7zmq">三次赛贝尔示例</a></p>
<h4 id="bqt组合二次次贝塞尔曲线命令组合">B、QT组合(二次次贝塞尔曲线命令组合)</h4>
<p>Q命令和T命令是专门绘制三次贝塞尔曲线的.</p>
<p><img src="/images/svg-2curveto-1.png" alt="svg-2curveto-1" /></p>
<p>可以看到,跟三次贝塞尔曲线相比,Q命令中就是2个控制点合为1个.</p>
<p><img src="/images/svg-2curveto-2.png" alt="svg-2curveto-2" /></p>
<p>与S命令类似,T命令也会自动补出一个对称的控制点(蓝线部分),生成连续的平滑曲线.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <svg id="svg" width="500" height="400">
<path d="M20 10 Q120 150 220 10 " stroke="red" fill="none" style="stroke-width: 2px;"></path>
<path d="M20 110 Q120 250 220 110 T420 110" stroke="red" fill="none" style="stroke-width: 2px;"></path>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/mh3hd7pw">二次赛贝尔示例</a></p>
<p><a name="end"></a></p>
<h2 id="三小结">三、小结</h2>
<p>本文介绍了SVG的基本属性和<path>元素的使用.</p>
<p>值得一提的是<path>的塞贝尔曲线指令,塞贝尔曲线属于数学范畴,或许学习成本过高,使用画图软件画出图片并用CSS控制展示不是很好吗?</p>
<p>然而在实际开发中,绘图是小,控制绘图为大,CSS对图片的控制力几乎没有,一旦需求出现细微变更,有可能就得重新绘图.</p>
<p>如果能用SVG+JS控制曲线的动画效果替代,便可以对图片进行随意的操控,将大大减少重绘的概率,项目也显得”优雅”.</p>
<p>图形动画领域很多东西都已一脉相承,一通百通的.动画算法也都是一致,图像处理算法也都是一致的.掌握SVG的贝塞尔曲线,肯定对于CSS3动画,Canvas的处理有很大帮助.</p>
<p>下篇将介绍滤镜和动画(animation).</p>
SVG学习[1]--简介及基本元素
2016-08-18T00:00:00+00:00
http://www.blogways.net/blog/2016/08/18/web-svg-1
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#1st">像素图与矢量图</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#2nd">可伸缩矢量图形(SVG)</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#3rd">基本SVG元素</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">小结</a></td>
</tr>
</tbody>
</table>
<p><a id="1st"></a></p>
<h2 id="一像素图与矢量图">一、像素图与矢量图</h2>
<h3 id="1像素图与矢量图的比较">1、像素图与矢量图的比较</h3>
<p>在万维网历史的大部分时间里,浏览器显示的图形都是像素图格式的。在像素图(如 GIF 或 JPEG 图像)中,文件包含图像中每个像素的颜色值。浏览器读取这些值并做出相应行动。它仅认识到单独的部分,而没有整体概念。</p>
<p>总的说来,这一系统有其优势,例如忠实再现了摄影图像,但它在某些情形下显得不足。例如,尽管浏览器能以不同大小显示一个图像,但通常会产生锯齿边缘,在这些地方,浏览器不得不为那些在原始图像中不存在的像素插入或猜测数值。此外,像素图格式的二进制性质使得难以(尽管不是不可能)基于数据库信息动态地创建图像,并且动画最多也仅限于“翻动书本”类型的动画,即快速连续地显示单独图像。</p>
<p>矢量图,通过指定为确定每个像素的值所需的指令而不是指定这些值本身,克服了这些困难中的一部分。例如,矢量图形不再为一个直径100px的圆提供像素值,而是告诉浏览器创建一个直径100px的圆,然后让浏览器(或插件)做其余事情。</p>
<p>这消除了像素图的许多限制;使用矢量图,浏览器只要知道它必须画一个圆。如果图像需要以正常大小的三倍来显示,那么浏览器只要按正确的大小画圆而不必执行像素图通常的插入法。类似地,浏览器接收的指令可以更容易地与外部信息源(如应用程序和数据库)绑定,要对图像制作动画,浏览器只要接收有关如何操纵属性(如半径或颜色)的指令即可。</p>
<h3 id="2web上的矢量图">2、web上的矢量图</h3>
<p>Web 上的第一个矢量图可能是虚拟现实标记语言(VRML)图像。VRML 寻求将 HTML 的简易性带到图像创建中来,然而尽管有一些示例给人以深刻的印象,但它的本来目的是为了 3D 造型,而且它太过复杂以至于从未真正流行起来。</p>
<p>接着是 Macromedia Flash 的介入。Flash 电影是用 Macromedia 的 Flash 应用程序所创建,它允许创建相当复杂的动画,并且将动画与声音和交互性绑定在一起。因为 Flash 文件主要包含有关如何创建图像的指令,所以它们比传统的 Web 电影小得多(例如 QuickTime 电影)— 而且它们可以缩放。</p>
<p>但是,Flash 文件仍然是二进制文件,这使得动态创建它们比较困难(尽管不是不可能)。而且对从浏览器可以进行的脚本编制有所限制。</p>
<p>直到出现可伸缩矢量图形(SVG)。</p>
<p><a id="2nd"></a></p>
<h2 id="二可伸缩矢量图形svg">二、可伸缩矢量图形(SVG)</h2>
<h3 id="1使用文本定义图像">1、使用文本定义图像</h3>
<p>SVG通过使用 XML 定义图像、动画和交互性解决了这些问题中的许多问题。浏览器读取(或者更准确地说,浏览器的插件读取)这些基于文本的指令,然后执行这些指令。例如,一个简单的 SVG 矩形图像可能看起来如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" standalone="no" ?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg width="300" height="100" xmlns="http://www.w3.org/2000/svg">
<rect x="25" y="10" width="280" height="50"
fill="red" stroke="blue" stroke-width="3"/>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/c7jyb0yh">效果展示</a></p>
<p>这个文档指示浏览器创建一个矩形,并提供属性信息,如位置(x, y)、大小(height, width)、颜色(fill, stroke)和线宽(stroke-width)。</p>
<h3 id="2在web浏览器中显示svg">2、在Web浏览器中显示SVG</h3>
<p>SVG文件是纯粹的XML,那是如何在Web浏览器中让SVG显示。要在浏览器中显示(前提是浏览器支持),可以通过几种方法来实现:</p>
<p>假设我们有一个girls.svg文件:
<img src="/images/girls.svg" alt="girl" /></p>
<p>使用编辑器打开,可以看到一大串的代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" ?><!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
<svg enable-background="new 0 0 145 145" id="Layer_1" version="1.1" viewBox="0 0 145 145" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M95.727,56.11c-2.29-3.814-4.565-6.092-4.617-6.146c-0.48-0.48-2.289,1.668-2.791,2.309 c-0.762,0.981-2.563,2.625-6.367,4.876c-3.802,2.255-9.599,5.132-18.35,8.687c-3.747,1.524-6.766,3.085-9.192,4.666 c3.136-0.364,6.856-0.784,7.613-0.815c2.007-0.082-0.404,4.203-9.474,2.116c-1.186,0.895-2.195,1.796-3.047,2.699 c-1.388,1.474-2.355,2.959-2.971,4.422c-0.617,1.463-0.877,2.9-0.876,4.246c0.005,3.039,1.285,3.753,2.512,5.495 c1.234,1.746,3.872,2.962,3.872,2.962s-0.704-1.33-1.719-2.789c-1.022-1.463-1.976-3.455-1.971-5.668 c0.001-1.004,0.188-2.065,0.665-3.201c0.275-0.653,0.652-1.335,1.149-2.038c0.466,2.206,1.478,6.081,3.454,10.021 c1.499,2.98,3.555,4.208,6.406,6.524c2.844,2.317,6.521,5.686,11.017,5.679c0.11,0,0.221-0.001,0.332-0.003 c3.876-0.057,7.15-3.391,9.724-5.757c3.87-3.555,6.308-7.082,7.847-12.521c1.531-5.446,2.713-11.542,3.009-15.689 c0.522-7.306,0.163-10.061-0.246-11.266c0.572,0.787,1.188,1.696,1.808,2.743c2.096,3.534,4.127,8.399,4.123,13.856 c-0.002,3.122-0.653,6.447-2.35,9.907c-1.698,3.459-4.452,7.06-8.7,10.68c0,0,9.238-5.66,11.119-9.493 c1.882-3.831,2.628-7.595,2.626-11.095C100.33,65.29,98.012,59.922,95.727,56.11z M77.582,69h11.677C89.259,69,89.259,75,77.582,69 z"/>
<path d="M53.943,97.604c-0.348-0.031-0.705-0.008-1.062-0.028c-0.212-0.012-0.425-0.001-0.633-0.02 c-3.854-0.352-6.887-1.923-8.909-4.354c-2.018-2.434-3.053-5.744-2.744-9.682l0.018-0.214c0.262-2.885,1.129-5.415,2.495-7.631 c1.367-2.215,3.437-3.863,5.531-5.702c7.384-6.483,14.57-10.075,21.95-13.905c4.245-2.203,8.488-4.594,12.651-7.22 c0.93-0.589,1.652-1.372,2.303-2.16c0.65-0.79,1.234-1.593,1.838-2.262c0,0-8.906,4.272-12.152,5.812 c-9.81,4.656-19.593,9.548-28.099,16.587c-3.033,2.512-5.808,5.679-7.739,9.131c-1.279,2.286-2.037,4.649-2.252,7.027 c-0.347,3.803,0.713,7.618,3.108,11.164c1.28,1.9,2.797,3.31,4.487,4.276c1.689,0.967,3.541,1.487,5.471,1.66 c1.797,0.162,3.675-0.072,5.585-0.411l7.056-1.355l-7.128-0.644C55.143,97.622,54.545,97.659,53.943,97.604z"/>
<path d="M49.823,71.043c0.97,0.317,1.875,0.565,2.726,0.76c0.576-0.435,1.197-0.869,1.86-1.301 C51.934,70.79,49.823,71.043,49.823,71.043z" fill="#FFFFFF"/>
</g>
</svg>
</code></pre></div></div>
<p>暂且不需要知道这个代码怎么来的,后面会懂的。接下来,使用不同的方式,让浏览器能正常显示SVG图像。</p>
<h4 id="aiframe">A、iframe</h4>
<p>自从浏览器支持SVG,可以通过url来加载SVG图像。其中使用<iframe>嵌入SVG就是其中一种方式。如下面的示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><iframe src="/images/girls.svg" width="200" height="200" ></iframe>
</code></pre></div></div>
<h4 id="bimg">B、img</h4>
<p>嵌入SVG图像还可以使用<img>元素加载图像一样。只需要将src的属性值更换成SVG图像对应的url,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><img src="/images/girls.svg" width="300" />
</code></pre></div></div>
<h4 id="cbackground-image">C、background-image</h4>
<p>自从浏览器支持SVG图像时,SVG图像就像像素图一样,可以通过background-image属性将SVG图像当做背景图片一样嵌入到HTML页面中。如下面的例子所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>div {
background: url('/images/girls.svg') no-repeat center;
background-size : 200px 200px;
}
</code></pre></div></div>
<p>可以通过background-size设置背景图像大小,告诉浏览器SVG图像以多大的尺寸显示。</p>
<h4 id="dsvg">D、SVG</h4>
<p>嵌入SVG图像到HTML页中,还可以直接使用<svg>元素,通过代码将SVG图像嵌入到HTML代码中。如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div>
<svg enable-background="new 0 0 145 145" id="Layer_1" version="1.1" viewBox="0 0 145 145" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g>
<path d="M95.727,56.11c-2.29-3.814-4.565-6.092-4.617-6.146c-0.48-0.48-2.289,1.668-2.791,2.309 c-0.762,0.981-2.563,2.625-6.367,4.876c-3.802,2.255-9.599,5.132-18.35,8.687c-3.747,1.524-6.766,3.085-9.192,4.666 c3.136-0.364,6.856-0.784,7.613-0.815c2.007-0.082-0.404,4.203-9.474,2.116c-1.186,0.895-2.195,1.796-3.047,2.699 c-1.388,1.474-2.355,2.959-2.971,4.422c-0.617,1.463-0.877,2.9-0.876,4.246c0.005,3.039,1.285,3.753,2.512,5.495 c1.234,1.746,3.872,2.962,3.872,2.962s-0.704-1.33-1.719-2.789c-1.022-1.463-1.976-3.455-1.971-5.668 c0.001-1.004,0.188-2.065,0.665-3.201c0.275-0.653,0.652-1.335,1.149-2.038c0.466,2.206,1.478,6.081,3.454,10.021 c1.499,2.98,3.555,4.208,6.406,6.524c2.844,2.317,6.521,5.686,11.017,5.679c0.11,0,0.221-0.001,0.332-0.003 c3.876-0.057,7.15-3.391,9.724-5.757c3.87-3.555,6.308-7.082,7.847-12.521c1.531-5.446,2.713-11.542,3.009-15.689 c0.522-7.306,0.163-10.061-0.246-11.266c0.572,0.787,1.188,1.696,1.808,2.743c2.096,3.534,4.127,8.399,4.123,13.856 c-0.002,3.122-0.653,6.447-2.35,9.907c-1.698,3.459-4.452,7.06-8.7,10.68c0,0,9.238-5.66,11.119-9.493 c1.882-3.831,2.628-7.595,2.626-11.095C100.33,65.29,98.012,59.922,95.727,56.11z M77.582,69h11.677C89.259,69,89.259,75,77.582,69 z"/>
<path d="M53.943,97.604c-0.348-0.031-0.705-0.008-1.062-0.028c-0.212-0.012-0.425-0.001-0.633-0.02 c-3.854-0.352-6.887-1.923-8.909-4.354c-2.018-2.434-3.053-5.744-2.744-9.682l0.018-0.214c0.262-2.885,1.129-5.415,2.495-7.631 c1.367-2.215,3.437-3.863,5.531-5.702c7.384-6.483,14.57-10.075,21.95-13.905c4.245-2.203,8.488-4.594,12.651-7.22 c0.93-0.589,1.652-1.372,2.303-2.16c0.65-0.79,1.234-1.593,1.838-2.262c0,0-8.906,4.272-12.152,5.812 c-9.81,4.656-19.593,9.548-28.099,16.587c-3.033,2.512-5.808,5.679-7.739,9.131c-1.279,2.286-2.037,4.649-2.252,7.027 c-0.347,3.803,0.713,7.618,3.108,11.164c1.28,1.9,2.797,3.31,4.487,4.276c1.689,0.967,3.541,1.487,5.471,1.66 c1.797,0.162,3.675-0.072,5.585-0.411l7.056-1.355l-7.128-0.644C55.143,97.622,54.545,97.659,53.943,97.604z"/>
<path d="M49.823,71.043c0.97,0.317,1.875,0.565,2.726,0.76c0.576-0.435,1.197-0.869,1.86-1.301 C51.934,70.79,49.823,71.043,49.823,71.043z" fill="#FFFFFF"/>
</g>
</svg>
</div>
</code></pre></div></div>
<p>上面的示例将<svg>放在一个<div>元素中,只是用来说明SVG图像可以通过<svg>元素嵌入到HTML页面中。其实<svg>元素可以不放在一个<div>元素中。</p>
<p>使用<svg>元素可以直接在HTML页面中嵌入SVG,而不是像前面几种方式那样,将SVG图像文件嵌入到页面当中。你可以设置width和height值,用来控制<svg>元素的大小,从而控制SVG图像的宽度和高度。</p>
<p>使用<svg>元素嵌入SVG图像,还可以通过CSS给其定义一些样式,实现一些样式效果。</p>
<h4 id="eembed">E、embed</h4>
<p>早期将SVG图像嵌入到HTML页面中都是通过<embed>元素。当时并不是所有的浏览器都支持原生SVG。来看看怎么使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><embed src="/images/girls.svg" width="300" height="220" type="image/svg+xml" pluginspage="http://www.adobe.com/svg/viewer/install/" />
</code></pre></div></div>
<h4 id="fobject">F、object</h4>
<p><object>元素是HTML4的标准标签元素,被所有较新的流星器支持。它只不过是不允许使用脚本。这个刚好与<embed>标签元素相反:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><object data="/images/girls.svg" width="300" height="200" type="image/svg+xml" codebase="http://www.adobe.com/svg/viewer/install/" />
</code></pre></div></div>
<p>虽然将SVG图像嵌入到HTML页面中,让浏览器能显示。方法有很多种,但更建议使用<img>和<svg>这两种方式。当然,如果SVG图像是给元素做背景图时,可以使用background-image方式引入。</p>
<h3 id="3动画和交互性">3、动画和交互性</h3>
<p>因为xml文件这一结构,SVG 非常适合于动画和交互性。要更改图形元素的大小、位置或颜色,脚本只要调整相应的属性即可。</p>
<p>事实上,SVG 有为事件处理而专门设计的属性(很象 HTML),甚至还有特别适合于动画的元素。例如,下面这一代码创建一个在 8 秒期间沿一条特定路线来回移动并无限重复的动画效果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg width="500" height="300" xmlns="http://www.w3.org/2000/svg">
<!-- Box around the image -->
<rect x="1" y="1" width="498" height="298"
fill="none" stroke="blue" stroke-width="2"/>
<!-- Visible path -->
<path d="M0,300 S150,100 200,200 S400,400 500,0"
fill="none" stroke="red" stroke-width="2"/>
<!-- Group of elements to animate -->
<g stroke-width="5" stroke="black">
<!-- Stick figure pieces -->
<circle cx="0" cy="-45" r="10" fill="black"/>
<line x1="-20" y1="-30" x2="0" y2="-25"/>
<line x1="20" y1="-30" x2="0" y2="-25"/>
<line x1="-20" y1="0" x2="0" y2="-10"/>
<line x1="20" y1="0" x2="0" y2="-10"/>
<line x1="0" y1="-10" x2="0" y2="-45"/>
<!-- Animation controls -->
<animateMotion path="M0,300 S150,100 200,200 S400,400 500,0"
dur="8s" repeatCount="indefinite"
rotate="auto"/>
</g>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/pccgzhed">效果展示</a></p>
<p><a name="3rd"></a></p>
<h2 id="三基本-svg-元素">三、基本 SVG 元素</h2>
<h3 id="1svg基本形状">1、SVG基本形状</h3>
<p>SVG 定义了六种基本形状,这些基本形状和路径(路径相对复杂,放到下一章单独讨论)一起,可以组合起来形成任何可能的图像。每个基本形状都带有指定其位置和大小的属性。它们的颜色和轮廓分别由 fill 和 stroke 属性确定。这些形状是:</p>
<p>圆(circle):显示一个圆心在指定点、半径为指定长度的标准的圆。</p>
<p>椭圆(ellipse):显示中心在指定点、长轴和短轴半径为指定长度的椭圆。</p>
<p>矩形(rect):显示左上角在指定点并且高度和宽度为指定值的矩形(包括正方形)。也可以通过指定边角圆的 x 和 y 半径画成圆角矩形。</p>
<p>线条(line):显示两个坐标之间的连线。</p>
<p>折线(polyline):显示顶点在指定点的一组线。</p>
<p>多边形(polygon):类似于 polyline,但增加了从最末点到第一点的连线,从而创建了一个闭合形状。</p>
<p>下面示例演示了这些图形:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- 生成SVG画布 -->
<svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
<g>
<!-- 圆 -->
<circle cx="50" cy="50" r="25" />
<!-- 椭圆 -->
<ellipse cx="75" cy="125" rx="50" ry="25" />
<!-- 矩形 -->
<rect x="155" y="5" width="75" height="100"/>
<!-- 矩形 -->
<rect x="250" y="5" width="75" height="100" rx="30" ry="20" />
<!-- 线条 -->
<line x1="0" y1="150" x2="400" y2="150"
stroke-width="2" stroke="blue"/>
<!-- 折线 -->
<polyline points="50,175 150,175 150,125 250,200" />
<!-- 多边形 -->
<polygon points="350,75 379,175 355,175 355,200 345,200
345,175 321,175" />
<!-- 矩形 -->
<rect x="0" y="0" width="400" height="200"
fill="none" stroke="red" stroke-width="3" />
</g>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/8x9vna8i">效果展示</a></p>
<h3 id="2添加文本">2、添加文本</h3>
<p>除了形状以外,SVG 图像还可以包含文本。SVG 给予设计人员和开发人员对文本的大量控制,可以获得很好的图形效果而不必借助失去真实纹理信息的图像(.gif或.jpg图像),下面示例演示了文本的定义:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg width="400" height="125" xmlns="http://www.w3.org/2000/svg">
<desc>Basic text</desc>
<g>
<rect x="0" y="0" width="400" height="125" fill="none"
stroke="blue" stroke-width="3"/>
<text x="10" y="50" font-size="30">Welcome to the world of</text>
<text x="10" y="100" font-size="40"
font-family="Monotype Corsiva"
fill="yellow" stroke="red">Scalable Vector Graphics!</text>
</g>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/7zol2wxr">效果展示</a></p>
<h3 id="3渲染顺序">3、渲染顺序</h3>
<p>当组合多种不同元素时,正象 SVG 图像一样,重要的是牢记各项在页面上的放置顺序,因为这关系到谁“在上面”出现。在一个 HTML 页面上,使用z-index属性来控制这一分层效果,而对于 SVG 图像,则严格按顺序放置各项。每个后继层放置在那些已放置层的上面。</p>
<p>如果指定一个元素没有填充色(使用fill=”none””),那么在它下面的各项会显现出来,就象这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><svg width="400" height="200" xmlns="http://www.w3.org/2000/svg">
<g>
<ellipse cx="125" cy="50" rx="50" ry="25"
fill="none" stroke="black" />
<circle cx="125" cy="50" r="25" fill="dodgerblue" />
<circle cx="125" cy="50" r="10" fill="black" />
<ellipse cx="250" cy="50" rx="50" ry="25"
fill="none" stroke="black" />
<circle cx="250" cy="50" r="25" fill="dodgerblue" />
<circle cx="250" cy="50" r="10" fill="black" />
<polygon points="65,50 185,50 185,75, 150,100
100,100 65,75"
fill="none" stroke="purple" stroke-width="4"/>
<polygon points="190,50 310,50 310,75, 275,100
225,100 190,75"
fill="none" stroke="purple" stroke-width="4"/>
<line x1="65" y1="50" x2="310" y2="50"
stroke="plum" stroke-width="2"/>
</g>
</svg>
</code></pre></div></div>
<p><a href="http://runjs.cn/code/f6bhbbeu">效果展示</a></p>
<p>请注意每个元素会覆盖在它之前出现的元素。</p>
<h3 id="4编组元素">4、编组元素</h3>
<p>最后,SVG 不仅仅可以定义单个元素,为兼顾可读性和方便性,将元素安排在一组中通常是个好办法。针对这一目的,SVG 提供 <g></g> 元素,它创建一个可以将元素置于其中的容器。这个容器可以用来标识元素,或提供一个公共属性(本地定义的属性将会覆盖公共属性)。例如上面渲染顺序使用的示例中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ...
<g stroke="red" stroke-width="3">
<ellipse cx="125" cy="50" rx="50" ry="25"
fill="none" stroke="black" />
<circle cx="125" cy="50" r="25" fill="url(#irisGradient)" />
<circle cx="125" cy="50" r="10" fill="black" />
</g>
...
</code></pre></div></div>
<p>为<g><\g>添加属性,则该属性将作用于g标签内部所有元素。</p>
<p><a name="end"></a></p>
<h2 id="四小结">四、小结</h2>
<p>本文大致介绍了:</p>
<p>像素图和矢量图的原理及优缺点;</p>
<p>SVG本质(.XML);</p>
<p>SVG的web显示方式(推荐使用<svg>标签、<img>标签、background-image);</p>
<p>SVG的六种基本形状(<rect>、<circle>、<ellipse>、<line>、<polyline>、<polygon>);</p>
<p>SVG的文本元素<text>和渲染顺序.</p>
<p>下一篇将介绍元素的基本属性和复杂而强大的路径元素<path>.</p>
CentOS下安装部署iot
2016-07-22T00:00:00+00:00
http://www.blogways.net/blog/2016/07/22/install-iot-on-CentOS-6.5
<h1 id="一iot平台部署概述">一、iot平台部署概述</h1>
<p>亚信iot平台由:iot-web、iot-server、zookeeper、kafka、comsumer等模块组成。</p>
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>IOT-web</td>
</tr>
<tr>
<td>2</td>
<td>IOT-server</td>
</tr>
<tr>
<td>3</td>
<td>zookeeper</td>
</tr>
<tr>
<td>4</td>
<td>kafka</td>
</tr>
<tr>
<td>5</td>
<td>consumer</td>
</tr>
</tbody>
</table>
<h1 id="二iot-web部署">二、iot-web部署</h1>
<h2 id="21手工编译部署配置">2.1、手工编译、部署、配置</h2>
<p>2.1.1. git获取源代码:git clone http://10.20.16.78:3000/iot/iot-web.git</p>
<p>2.1.2. 编译源代码:mvn clean install 得到war包:iot-web-1.0-SNAPSHOT.war</p>
<p>2.1.3. 将war包部署到tomcat根【部署方案请参见网络tomcat根部署方法】</p>
<p>2.1.4. 配置$IOT-WEB-SETUP-PATH/WEB-INF/classes/spring/consumer-dubbo.xml,修改zookeeper正确地址即可</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dubbo:registry address="zookeeper://192.168.10.149:2181?backup=192.168.10.154:2181"/>
</code></pre></div></div>
<p>2.1.5. 停启tomcat即可</p>
<p>2.1.6. 浏览器打开:http://192.168.10.149:8080/createuser/index.html 看页面是否打开成功</p>
<h2 id="22自动化编译部署配置">2.2、自动化编译、部署、配置</h2>
<p>自动化构建只要遵守jenkins相关方法即可,为了使自动化部署相对直观,需要添加构建后的触发器,当构建:成功、失败、不稳定时,发送相应邮件到指定人员(自动化构建平台管理员、开发人员、测试人员)。</p>
<h1 id="三iot-server部署">三、iot-server部署</h1>
<h2 id="31手工编译部署配置">3.1、手工编译、部署、配置</h2>
<p>3.1.1. git获取源代码:git clonehttp://10.20.16.78:3000/iot/iot-server.git</p>
<p>3.1.2. 编译源代码:mvn clean install 得到war包:iot-server.jar</p>
<p>3.1.3. copy目标文件到运行目录:</p>
<p>3.1.4. iot-server服务的启停:./operateServer.sh start、./operateServer.sh stop</p>
<p>3.1.5. 检测服务是否在zookeeper中注册成功</p>
<blockquote>
<p>注:注意在生产环境中调整启动脚本中的java虚拟机的内存分配。</p>
</blockquote>
<h2 id="32自动化编译部署配置">3.2、自动化编译、部署、配置</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>自动化构建只要遵守jenkins相关方法即可,为了使自动化部署相对直观,需要添加构建后操作的触发器,当构建:成功、失败、不稳定时,发送相应邮件到指定人员(自动化构建平台管理员、开发人员、测试人员)。
</code></pre></div></div>
<h1 id="四zookeeper安装部署">四、zookeeper安装部署</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ZooKeeper是一个分布式的,开放源码的分布式应用程序协调服务,是Google的Chubby一个开源的实现,是Hadoop和Hbase的重要组件。它是一个为分布式应用提供一致性服务的软件,提供的功能包括:配置维护、域名服务、分布式同步、组服务等。
ZooKeeper的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。【百度百科】
</code></pre></div></div>
<blockquote>
<p><strong>建议:目前部署只使用2台zookeeper服务器,部署版本为zookeeper-3.3.6,理论zookeeper的安装部署集群主机数量需要大于等于3台,建议zookeeper集群主机数量为奇数【因为集群涉及选举leader,部署数量为奇数有利于提高整个系统平台的稳定性与健壮性,zookeeper主机的数量越多整个集群的稳定性越强】。</strong></p>
</blockquote>
<h2 id="41zookeeper安装步骤">4.1、zookeeper安装步骤</h2>
<ol>
<li>
<p>下载zookeeper,版本:zookeeper-3.3.6</p>
</li>
<li>
<p>解压zookeeper到指定目录,如:/root/iot/zookeeper-3.3.6</p>
</li>
<li>
<p>配置zookeeper的配置文件,路径及文件名为:$ZOOKEEPER_HOME/conf/zoo.conf,<strong>见:注1</strong>;</p>
</li>
<li>
<p>配置myid文件,根据zoo.conf文件配置myid文件,myid文件只需要配置一个数字编号;<strong>见:注2;</strong></p>
</li>
<li>
<p>zookeeper的启停方法、及状态查询;<strong>见:注3</strong>;</p>
</li>
</ol>
<blockquote>
<p>注1:zoo.cfg内容如下:</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=5
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=2
# the directory where the snapshot is stored.
# the port at which the clients will connect
dataDir=/root/iot/data
dataLogDir=/root/iot/dataLog
clientPort=2181
server.1=192.168.10.149:2888:3888
server.2=192.168.10.154:2889:3889
</code></pre></div></div>
<blockquote>
<p>注2:</p>
</blockquote>
<p>您看到配置文件中有如下内容:server.1=192.168.10.149:2888:3888,说明如下:192.168.10.149主机需要根据server后的编号配置:/root/iot/data/myid文件;文件中写入:server.x中的x编号;例如本配置,只需在myid文件写入1,即可;同理:192.168.10.154的相应的myid文件写入2;如果配置不正确,则整个zookeeper集群可能无法正常运行。</p>
<blockquote>
<p>注3:</p>
</blockquote>
<p>启动:$ZOOKEEPER_HOME/bin/zkServer.sh start</p>
<p>停止:$ZOOKEEPER_HOME/bin/zkServer.sh stop</p>
<p>状态:$ZOOKEEPER_HOME/bin/zkServer.sh status</p>
<p>日志:tail -f zookeeper.log</p>
<p><strong>zookeeper.log文件刚开始可能不正确认,因为当整个zookeeper集群未完全启动完成时,每个zookeeper节点会去尝试连接配置文件中配置的主机,固会出现连接错误,只有在所有主机节点主机全部启动完成后,如果没有错误信息,则表示整个zookeeper集群成功运行。</strong></p>
<h2 id="42zookeeper启停及进程查看">4.2、zookeeper启停及进程查看</h2>
<p>启动:$ZOOKEEPER_HOME/bin/zkServer.sh start</p>
<p>停止:$ZOOKEEPER_HOME/bin/zkServer.sh stop</p>
<p>状态:$ZOOKEEPER_HOME/bin/zkServer.sh status</p>
<h2 id="43zookeeper运行日志查看">4.3、zookeeper运行日志查看</h2>
<p>启动zookeeper后,默认在启动中径下生成zookeeper.log日志文件;可以通过more、vi、tail查看日志。</p>
<h2 id="44zookeeper配置文件说明">4.4、zookeeper配置文件说明</h2>
<p>zookeeper配置文件路径:$ZOOKEEPER_HOME/conf/zoo.cfg</p>
<p>配置文件zoo.cfg内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># The number of milliseconds of each tick
tickTime=2000
# The number of ticks that the initial
# synchronization phase can take
initLimit=5
# The number of ticks that can pass between
# sending a request and getting an acknowledgement
syncLimit=2
# the directory where the snapshot is stored.
# the port at which the clients will connect
dataDir=/root/iot/data
dataLogDir=/root/iot/dataLog
clientPort=2181
# zookeeper集群主机信息
server.1=192.168.10.149:2888:3888
server.2=192.168.10.154:2889:3889
</code></pre></div></div>
<blockquote>
<p>zookeeper提供log4j的日志方式,请参见zookeeper的说明文档。</p>
</blockquote>
<h1 id="五kafka">五、kafka</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>待补充
</code></pre></div></div>
<h1 id="六consumer">六、consumer</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>待补充
</code></pre></div></div>
<h1 id="七整体iot部署说明">七、整体iot部署说明</h1>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>由于整个iot系统由多个子模块组成,这其中涉及软件模块的编译、主机的启停、单元测试、系统检验(启停是否成功),故使用jenkins插件:pipeline可以串联整个模块。通过jenkins插件创建pipeline项目之后,设定一个开始项目后,再设定该项目的:构建后操作指定执行的下一步骤即可。
pipeline例程如下:
</code></pre></div></div>
<p><img src="/images/pipeline-flow.jpg" alt="pipeline" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>触发其它构建后动作如下:
</code></pre></div></div>
<p><img src="/images/triger-other-project.jpg" alt="triger-other-project" /></p>
<blockquote>
<p>注:pipeline插件并不创建真正的一个jenkins项目,它只是把之前创建的jenkins项目作一个串联并可视化,通过可视化的流程可以看到一个workflow工程经历哪些步骤。</p>
</blockquote>
<h1 id="八其它">八、其它</h1>
<p>暂无</p>
CentOS下安装keepalived
2016-07-13T00:00:00+00:00
http://www.blogways.net/blog/2016/07/13/install-keepalived-on-CentOS
<h3 id="一keepalived概述">一、keepalived概述</h3>
<blockquote>
<p>keepalived是一个类似于layer3, 4 & 7交换机制的软件,也就是我们平时说的第3层、第4层和第7层交换。</p>
</blockquote>
<blockquote>
<p>Keepalived的作用是检测服务器的状态,如果有一台web服务器死机,或工作出现故障,Keepalived将检测到,并将有故障的服务器从系统中剔除,当服务器工作正常后Keepalived自动将服务器加入到服务器群中,这些工作全部自动完成,不需要人工干涉,需要人工做的只是修复故障的服务器。</p>
</blockquote>
<blockquote>
<p>Layer3,4&7工作在IP/TCP协议栈的IP层,TCP层,及应用层,原理分别如下:
Layer3:Keepalived使用Layer3的方式工作式时,Keepalived会定期向服务器群中的服务器发送一个ICMP的数据包(既我们平时用的Ping程序),如果发现某台服务的IP地址没有激活,Keepalived便报告这台服务器失效,并将它从服务器群中剔除,这种情况的典型例子是某台服务器被非法关机。Layer3的方式是以服务器的IP地址是否有效作为服务器工作正常与否的标准。</p>
</blockquote>
<blockquote>
<p>Layer4:如果您理解了Layer3的方式,Layer4就容易了。Layer4主要以TCP端口的状态来决定服务器工作正常与否。如web server的服务端口一般是80,如果Keepalived检测到80端口没有启动,则Keepalived将把这台服务器从服务器群中剔除。</p>
</blockquote>
<blockquote>
<p>Layer7:Layer7就是工作在具体的应用层了,比Layer3,Layer4要复杂一点,在网络上占用的带宽也要大一些。Keepalived将根据用户的设定检查服务器程序的运行是否正常,如果与用户的设定不相符,则Keepalived将把服务器从服务器群中剔除。</p>
</blockquote>
<blockquote>
<p>主要用作RealServer的健康状态检查以及LoadBalance主机和BackUP主机之间failover的实现。</p>
</blockquote>
<p>注:摘自百度全科</p>
<h3 id="二keepalived在centos安装步骤">二、keepalived在CentOS安装步骤</h3>
<blockquote>
<p><strong>注:以上安装请以root用户操作,当然您也可以使用有root权限的其它用户操作</strong></p>
</blockquote>
<p>1、安装gcc(g++)
安装步骤:略;</p>
<p>2、安装openssl</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2.1、下载源码:https://www.openssl.org/source/openssl-1.0.1t.tar.gz
2.2、静态库编译:./configure --prefix=/usr/local/ssl --openssldir=/usr/local/ssl、make、make install
2.3、动态库编译:./configure shared --prefix=/usr/local/ssl --openssldir=/usr/local/ssl、make、make install
注意:一定要编译2次哦,否则keepalived无法找到动态库文件。
2.4、配置环境变量:LD_LIBRARY_PATH=/usr/local/lib64/,【可以ll查看:/usr/local/lib64目录的内容】
2.5、配置系统环境变量:.bash_profile,export LD_LIBRARY_PATH
注意:别忘了让系统环境变更生效(. .bash_profile)
</code></pre></div></div>
<p>3、安装pcre 【Perl Compatible Regular Expressions】</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3.1、下载pcre,地址:http://sourceforge.net/projects/pcre/files/
3.2、常规安装,步骤:./configure、make、make install
3.3、配置环境变量:PCRE_HOME、LD_LIBRARY_PATH、export PCRE_HOME LD_LIBRARY_HOME
3.4、. .bash_profile使环境变量生效
</code></pre></div></div>
<p>4、安装keepalived</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>4.1、下载keeepalived,下载地址:
4.2、安装步骤:./configure --prefix=/usr/local/keepalived & make & make instal
4.3、keepalived常规配置
4.3.1、cp /usr/local/keepalived/sbin/keepalived /usr/sbin/
4.3.2、cp /usr/local/keepalived/etc/sysconfig/keepalived /etc/sysconfig/
4.3.3、cp /usr/local/keepalived/etc/rc.d/init.d/keepalived /etc/init.d/
4.3.4、chkconfig --add keepalived
4.3.5、chkconfig keepalived on
4.3.6、mkdir /etc/keepalived
4.3.7、ln -s /usr/local/sbin/keepalived /usr/sbin/
4.3.8、cp /usr/local/keepalived/etc/keepalived/keepalived.conf /etc/keepalived/keepalived.conf
4.4、keepalived配置文件配置
修改keepalived配置文件:/etc/keepalived/keepalived.conf,如下:
global_defs {
notification_email {
xuwn@asiainfo.com
}
notification_email_from xuwn@asiainfo.com
smtp_server 127.0.0.1
smtp_connect_timeout 30
router_id lnmp_node1
}
vrrp_instance lnmp {
state MASTER
interface eth0
virtual_router_id 100
priority 170
advert_int 5
track_interface {
eth0
}
authentication {
auth_type PASS
auth_pass 123456
}
virtual_ipaddress {
192.168.10.2
}
}
</code></pre></div></div>
<p>5、keepalived系统日志配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5.1、修改文件:/etc/sysconfig/keepalived,把KEEPALIVED_OPTIONS="-D" 修改为KEEPALIVED_OPTIONS="-D -d -S 0"
5.2、修改文件:/etc/rsyslog.conf 在最后添加:
# keepalived -S 0
local0.* /var/log/keepalived.log
5.3、重新启动操作系统日志:/etc/init.d/rsyslog restart
</code></pre></div></div>
<p>6、keepalived启停方法和查看日志</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>6.1、/etc/init.d/keepalived start
6.2、/etc/init.d/keepalived stop
6.3、/etc/init.d/keepalived restart
6.4、tailf /var/log/keepalived.log
</code></pre></div></div>
<p>7、关闭CentOS系统防火墙</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>7.1、service iptables stop #停止
7.2、chkconfig iptables off #禁用
</code></pre></div></div>
<p>8、nginx安装及配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>8.1、安装步骤:略; > **注意编译命令应为如下:./configure --prefix=/usr/local/nginx --without-http_gzip_module**
8.2、配置
server {
listen 80;
server_name localhost:8080;
location / {
proxy_pass http://localhost:8080 ;
}
}
</code></pre></div></div>
<blockquote>
<p><strong>注:以上配置为将对浏览器中:localhost的访问映射为:http://localhost:8080,即tomcat的web服务</strong></p>
</blockquote>
RHEL5.5下安装jekyll
2016-07-07T00:00:00+00:00
http://www.blogways.net/blog/2016/07/07/install-jekyll-on-RHEL
<h3 id="一概述">一、概述</h3>
<p>[jekyll] 是一款简单的博客系统,静态网站生成器。她有一个模版目录,存放整个静态网站的模版文件,可以通过[Liquid]处理模版文件,把使用标记语言[Textile]或[Markdown]编写的内容文件,按照模版格式,转换成最终的静态网站页面。大名鼎鼎的GitHub Pages就是通过她实现的。废话少说了,经过好几天的弯路,终于明白之前安装不上是因为公司RHEL系统的问题。感谢公司同事及时的帮助,让我少走很多的弯路。</p>
<h3 id="二安装步骤">二、安装步骤</h3>
<p>注:以上安装请以root用户操作。</p>
<p>1、安装:ruby-2.3.1</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.1、下载ruby-2.3.1源代码
1.2、./configure --prefix=/usr/local/ruby
1.3、make
1.4、make install
1.5、设置ruby环境变量
</code></pre></div></div>
<p>2、生成key</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>2.1、gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
</code></pre></div></div>
<p>3、安装稳定版RVM</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3.1、curl -L -k https://raw.githubusercontent.com/wayneeseguin/rvm/master/binscripts/rvm-installer | bash -s stable
</code></pre></div></div>
<p>4、删除源</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>4.1、gem sources --remove https://rubygems.org/
</code></pre></div></div>
<p>5、添加国内镜像源</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5.1、gem sources -a http://ruby.sdutlinux.org/
</code></pre></div></div>
<p>6、安装nodejs,并设置nodejs的环境变量</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>6.1、NODEJS_HOME=安装路径
6.2、PATH=$PATH:$NODEJS_HOME/bin
</code></pre></div></div>
<h3 id="三检查rhel55安装jekyll是否成功">三、检查RHEL5.5安装jekyll是否成功</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll -v
如果打印出版本信息,则表示安装成功。
</code></pre></div></div>
<h3 id="四jekyll操作">四、jekyll操作</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>3.1、获取源码,并运行jekyll,命令如下:
cd ~
mkdir webroot
cd webroot
git clone https://github.com/mojombo/tpw.git
cd tpw
jekyll --server
3.2、在浏览器访问`localhost:4000`,显示博客列表。
</code></pre></div></div>
<h3 id="五其它">五、其它</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>4.1、安装ruby时无需安装openssl;
4.2、使用jekyll生成的表态HTML文件,在运行:jekyll --server后只能在本机访问,如果需要通过http://ip:4000来访问,请使用nginx或tomcat。
</code></pre></div></div>
jQuery.dataTables 自定义排序
2016-07-06T00:00:00+00:00
http://www.blogways.net/blog/2016/07/06/datatables-sort
<table>
<thead>
<tr>
<th> </th>
<th><strong>目 录</strong></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#intro">遇到问题</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#names">官方更新说明</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>最近在项目中用到了 jQuery.dataTables, 这是一个很强大的 jQuery 插件,调用方便,支持回调对数据进行排序、查询、分页等操作,并且 bootstrap 框架也有对其封装,省了我们界面设计的活。dataTables 自带了string,date,numeric 的排序,但当遇到比较特殊的排序需求时,就得另寻出路了。</p>
<h2 id="一遇到问题-">一、遇到问题 <a name="intro"></a></h2>
<p>这几天做项目时,正好碰到一个需求,要对下面的分秒的形式进行排序</p>
<p><img src="\images\post\MM-SS.jpg" alt="mm-ss" /></p>
<p>而 dataTables 的自带排序会将这一列视为 string 排序。 显然是不满足我们需求的。一开始以为要大动干戈,后来看了API文档后发现,dataTables 的第三方扩展支持还是很灵活的。</p>
<h2 id="二解决方案">二、解决方案<a name="names"></a></h2>
<p>官方文档中提供了两种方法:</p>
<p>(1) Type based column sorting ;</p>
<p>(2) Custom data source sorting</p>
<p>还有一种是在服务端在查数据库时进行排序处理</p>
<p>这里我主要使用第一种和第三种</p>
<h3 id="一type-based-column-sorting">(一)Type based column sorting</h3>
<p><strong>主要思路</strong>:主要思路就是将单元格内容转成可排序的 int 类型</p>
<p>1.首先创建一个文件叫 dataTables.sort.plungin.js ,加入以下代码。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend(jQuery.fn.dataTableExt.oSort, {
"MM-SS-pre": function (a) {
var x = String(a).replace(/<[\s\S]*?>/g, ""); //去除html标记
x = x.replace(/&nbsp;/ig, ""); //去除空格
x = x.split('分')[0]*60 + x.split('分')[1].split('秒')[0]
return x;
},
"MM-SS-asc": function (a, b) { //正序排序引用方法
return ((a < b) ? -1 : ((a > b) ? 1 : 0));
},
"MM-SS-desc": function (a, b) { //倒序排序引用方法
return ((a < b) ? 1 : ((a > b) ? -1 : 0));
}
});
</code></pre></div></div>
<p>2.在前台页面中加入以下的 js 引用</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script type="text/javascript" src="jquery.dataTables.js"></script>
</code></pre></div></div>
<p>3.前台JS里添加</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"aoColumnDefs": [{ "sType": "html-percent", "aTargets": [8] }]
</code></pre></div></div>
<h3 id="二在服务端添加排序">(二)在服务端添加排序</h3>
<p>因为表里数据为Number格式,存的是秒,可以直接进行排序</p>
<p>从前端可以获得两个参数</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var sortIdx = req.query.iSortCol_0 || -1;
var sortDir = req.query.sSortDir_0 || '';
</code></pre></div></div>
<p>这俩个参数分别代表所点击列的索引号和该列是升序还是逆序</p>
if (sortIdx !== -1 && sortDir !== ‘‘&& baseFieldNames !== undefined && baseFieldNames.length >= sortIdx) {
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> if (sortIdx === '0') {
sort = { 'departId-avg': -1 };
} else {
sortDir = sortDir === 'asc' ? 1 : -1;
sort[baseFieldNames[sortIdx]] = sortDir;
}
}
</code></pre></div></div>
<p>整合的sort在查表时添加即可。</p>
<h2 id="四参考文献">四、参考文献<a name="reference"></a></h2>
<p><a href="https://datatables.net/plug-ins/sorting/#how_to_data_source">https://datatables.net/plug-ins/sorting/#how_to_data_source</a></p>
bootstrap的响应式布局
2016-06-05T00:00:00+00:00
http://www.blogways.net/blog/2016/06/05/web-bootstrap
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#responsive">响应式设计(Responsive Web Design)</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#tools">栅格系统和媒体查询</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#bootstrap">Bootstrap的响应式布局</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#end">小结</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#doc">参考文献</a></td>
</tr>
</tbody>
</table>
<h2 id="一响应式设计responsive-web-design-">一、响应式设计(Responsive Web Design) <a name="responsive"></a></h2>
<h3 id="起源">起源</h3>
<p>响应式网页设计最初是出自 Ethan Marcotte 在A List Apart发表过一篇文章”Responsive Web Design”,文中援引了响应式建筑设计的概念:</p>
<p>最近出现了一门新兴的学科——”响应式建筑(responsive architecture)”——提出,物理空间应该可以根据存在于其中的人的情况进行响应。结合嵌入式机器人技术以及可拉伸材料的应用,建筑师们正在尝试建造一种可以根据周围人群的情况进行弯曲、伸缩和扩展的墙体结构;还可以使用运动传感器配合气候控制系统,调整室内的温度及环境光。已经有公司在生产”智能玻璃”:当室内人数达到一定的阀值时,这种玻璃可以自动变为不透明,确保隐私。</p>
<p>将这个思路延伸到Web设计的领域,我们就得到了一个全新的概念。
为什么一定要为每个用户群各自打造一套设计和开发方案?Web设计应该做到根据不同设备环境自动响应及调整。当然响应式Web设计不仅仅是关于屏幕分辨率自适应以及自动缩放的图片等等,它更像是一种对于设计的全新思维模式;我们应当向下兼容、移动优先。</p>
<h3 id="背景">背景</h3>
<p>PC互联网加速向移动端迁移:2012年12月底我国网民规模达到5.64亿,互联网普及率为42.1%,手机用户占网民总数的74.5%。预计到2015年,移动互联网的数据流量将超越PC端的流量。</p>
<p>移动端入口:当用户希望通过手机来完成PC页的操作时,常见的是商家的运营微博,期文案足够吸引用户点击链接参加活动,如果该活动页没做响应式处理:页面体积大、请求多、体验差、兼容性差,层层阻碍最终导致用户放弃参加。</p>
<h3 id="目的">目的</h3>
<p>因为越来越多的智能移动设备( mobile, tablet device )加入到互联网中来,移动互联网不再是独立的小网络了,而是成为了 Internet 的重要组成部分。响应式网络设计 ( RWD / AWD)的出现,目的是为移动设备提供更好的体验,并且整合从桌面到手机的各种屏幕尺寸和分辨率,用技术来使网页适应从小到大(现在到超大)的不同分辨率的屏幕。</p>
<p><em>注: Responsive Web Design = RWD,Adaptive Web Design = AWD</em></p>
<h3 id="方法论">方法论</h3>
<p>RWD:</p>
<ul>
<li>采用 CSS 的 media query 技术</li>
<li>流体布局( fluid grids )</li>
<li>自适应的图片/视频等资源素材
(为小、中、大屏幕做一些优化,目的是让任何尺寸的屏幕空间都能得到充分利用)</li>
</ul>
<p>AWD:</p>
<ul>
<li>CSS media query 技术(仅针对有限几种预设的屏幕尺寸设计)</li>
<li>用 Javascript 来操作 HTML 内容</li>
<li>在服务器端操作 HTML 内容(比如为移动端减少内容,为桌面端提供更多内容)</li>
</ul>
<h3 id="设计思路">设计思路</h3>
<p>Mobile First(从移动端开始,RWD ):
一切从最小屏幕的移动端开始(比如 iPhone 的 320px ),先确定内容,然后逐级往大屏幕设计。
不同于原来网页设计,总是从桌面电脑的 1024px 开始的。
之所以从最小的屏幕开始设计,并不是因为移动端的重要性高于PC端,而是因为以最小的屏幕开始,可以从一开始就知道,哪些内容是必须的,哪些内容是次要的,减少开发受到的阻碍。</p>
<h2 id="二栅格系统和媒体查询-">二、栅格系统和媒体查询 <a name="tools"></a></h2>
<p>如我们需要兼容不同屏幕分辨率、清晰度以及屏幕定向方式竖屏(portrait)、横屏(landscape),怎样才能做到让一种设计方案满足所有情况?</p>
<p>那么我们的布局应该是一种弹性的栅格布局,不同尺寸下弹性适应,如以下页面中各模块在不同尺寸下的位置:</p>
<p><img src="/images/diffSize.jpg" alt="gridSystem" /></p>
<p>那么,我们应该如何实现呢?</p>
<h3 id="栅格系统grid-system">栅格系统(Grid System)</h3>
<p>栅格系统英文为“grid systems”,也有人翻译为“网格系统”,运用固定的格子设计版面布局,其风格工整简洁,是web页面设计的主流风格之一,是响应式设计的一种实现。</p>
<p>从上图中可以看到,一个页面可以拆分成多个区块来理解,而正是这些区块共同构成了这个页面的布局。根据不同的屏幕尺寸情况,调整这些区块的排版,就可以实现响应式设计。另外,屏幕宽度较大的时候,区块倾向于水平分布,而屏幕宽度较小的时候,区块倾向于竖直堆叠。
这些方方正正的区块是不是和栅格系统的格子挺相似?对的,为了让响应式设计更简单易用,于是有了很多称为“栅格”(grid)的样式库。
栅格样式库一般是这样做的:将页面划分为若干等宽的列(column),然后推荐你通过等宽列来创建响应式的页面区块。
虽然看起来都是这样的思路,但不同的栅格样式库,在方法和表现上有各自的特点。
在第三节,本文将以bootstrap为例,介绍它的Grid System简要原理和用法。</p>
<h3 id="媒体查询media-queries">媒体查询(Media Queries)</h3>
<p>在CSS中,有一个极其实用的功能:@media 响应式布局。具体来说,就是可以根据客户端的介质和屏幕大小,提供不同的样式表或者只展示样式表中的一部分。通过响应式布局,可以达到只使用单一文件提供多平台的兼容性,省去了诸如浏览器判断之类的代码。</p>
<p>下面大致介绍一些Media的用法。</p>
<blockquote>
<p>在 head 链接CSS文件时提供判断语句,选择性加载不同的CSS文件</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><link rel="stylesheet" href="middle.css" media="screen and (min-width: 400px)">
</code></pre></div></div>
<p>这句意味在满足 media 的判断语句 screen and (min-width: 400px) 即 屏幕并且最小宽度不小于400px 的介质上面使用 middle.css 。</p>
<blockquote>
<p>在CSS文件中分段书写不同设备的代码</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@media screen and (min-width: 600px) { /* CSS Code */ }
@media screen and (max-width: 1200px) { /* CSS Code */ }
</code></pre></div></div>
<p>写在 @media 语句段外的是共用代码,第一个 @media 当屏幕宽度以及大于最小宽度600px时执行内部CSS代码 ,第二个 @media 当屏幕宽度小于最大宽度1200px时执行内部CSS代码。</p>
<p>反应灵敏的栅格系统,搭配上合理的媒体查询,就可以让web页面具备良好的响应性。</p>
<p>下面将以bootstrap为例,介绍bootstrap的响应式设计。</p>
<h2 id="三bootstrap的响应式布局-">三、Bootstrap的响应式布局 <a name="bootstrap"></a></h2>
<p>Bootstrap是Twitter推出的一个用于前端开发的开源工具包。它由Twitter的设计师Mark Otto和Jacob Thornton合作开发,是一个CSS/HTML框架,目前最新版本是v3.3.6(bootstap 4.0即将推出)。
Bootstrap的内容包括三大部分,分别是:全局CSS样式、通用组件以及Javascript插件。
由于响应式设计的实现主要是基于栅格系统和媒体查询,所以直接了解bootstrap的Grid System 和 Media Query。</p>
<h3 id="31-布局容器container和栅格系统grid-system">3.1 布局容器(container)和栅格系统(Grid System)</h3>
<h3 id="布局容器container">布局容器(container)</h3>
<p>页面内容和栅格系统需要用到一个容器将其包裹起来,为此,bootstrap提供了两个作此用处的类: <font color="red">.container .container-fluid</font>。</p>
<p>注意,由于 padding 等属性的原因,这两种 容器类不能互相嵌套。</p>
<font color="red">.container</font>
<p>类用于固定宽度并支持响应式布局的容器。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="container">
...
</div>
</code></pre></div></div>
<font color="red">.container-fluid</font>
<p>类用于 100% 宽度,占据全部视区(viewport)的容器。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="container-fluid">
...
</div>
</code></pre></div></div>
<h3 id="栅格系统grid-system-1">栅格系统(Grid System)</h3>
<p>Bootstrap提供了一套响应式的流式栅格系统,随着屏幕或视区(viewport)尺寸的增加,系统会自动分为最多12列。它包含了易于使用的预定义类,还有强大的mixin用于生成更具语义的布局。</p>
<p>栅格系统用于通过一系列的行(row)与列(column)的组合来创建页面布局,你的内容就可以放入这些创建好的布局中。下面就介绍一下 Bootstrap 栅格系统的工作原理:</p>
<ul>
<li>“行(row)”必须包含在 .container (固定宽度)或 .container-fluid (100% 宽度)中,以便为其赋予合适的排列(aligment)和内补(padding)。</li>
<li>通过“行(row)”在水平方向创建一组“列(column)”。</li>
<li>你的内容应当放置于“列(column)”内,并且,只有“列(column)”可以作为行(row)”的直接子元素。</li>
<li>类似 .row 和 .col-xs-4 这种预定义的类,可以用来快速创建栅格布局。Bootstrap 源码中定义的 mixin 也可以用来创建语义化的布局。</li>
<li>通过为“列(column)”设置 padding 属性,从而创建列与列之间的间隔(gutter)。通过为 .row 元素设置负值 margin 从而抵消掉为 .container 元素设置的 padding,也就间接为“行(row)”所包含的“列(column)”抵消掉了padding。</li>
<li>栅格系统中的列是通过指定1到12的值来表示其跨越的范围。例如,三个等宽的列可以使用三个 .col-xs-4 来创建。</li>
<li>如果一“行(row)”中包含了的“列(column)”大于 12,多余的“列(column)”所在的元素将被作为一个整体另起一行排列。</li>
<li>栅格类适用于与屏幕宽度大于或等于分界点大小的设备 , 并且针对小屏幕设备覆盖栅格类。 因此,在元素上应用任何 .col-md-* 栅格类适用于与屏幕宽度大于或等于分界点大小的设备,并且针对小屏幕设备覆盖栅格类。因此,在元素上应用任何 .col-lg-* 不存在,也影响大屏幕设备。</li>
</ul>
<p>容器(container),行(row)和列(column)之间的层级关系。一个正确的写法示例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="container">
<div class="row">
<div class="col-md-6"></div>
<div class="col-md-6"></div>
</div>
</div>
</code></pre></div></div>
<p>row(.row)必须位于container的内部,column(如.col-md-6)必须位于row的内部。也就是说,container、row、column必须保持特定的层级关系,栅格系统才可以正常工作。
为什么需要这样?查看这些元素的样式,会发现container有15px的水平内边距,row有-15px的水平负外边距,column则有15px的水平内边距。这些边距是故意的、相互关联的,也因此就像齿轮啮合那样,限定了层级结构。
这些边距其实也是Bootstrap栅格的精巧之处。
如果要嵌套使用栅格,正确的做法是在column内直接续接row,然后再继续接column,而不再需要container:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="container">
<div class="row">
<div class="col-md-8">
<div class="row">
<div class="col-md-6"></div>
<div class="col-md-6"></div>
</div>
</div>
<div class="col-md-4"></div>
</div>
</div>
</code></pre></div></div>
<h3 id="32-媒体查询media-grid">3.2 媒体查询(Media Grid)</h3>
<p>Bootstrap栅格的column对应的类名形如.col-xx-y。
y是数字,表示该元素的宽度占据12列中的多少列。而xx只有特定的几个值可供选择,分别是xs、sm、md、lg,它们就是断点类型。
在Bootstrap栅格的设计中,断点的意义是,当视口(viewport)宽度小于断点时,column将竖直堆叠(display: block的默认表现),而当视口宽度大于或等于断点时,column将水平排列(float的效果)。按照xs、sm、md、lg的顺序,断点像素值依次增大,其中xs表示极小,即认为视口宽度永远不小于xs断点,column将始终水平浮动。
有时候,会需要将多种断点类型组合使用,以实现更细致的响应式设计。此时不同的断点类型之间会有怎样的相互作用呢?
先看看Bootstrap的sass源码是如何定义栅格的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@include make-grid-columns;
@include make-grid(xs);
@media (min-width: $screen-sm-min) {
@include make-grid(sm);
}
@media (min-width: $screen-md-min) {
@include make-grid(md);
}
@media (min-width: $screen-lg-min) {
@include make-grid(lg);
}
</code></pre></div></div>
<p>可以看到,用了min-width的写法,而且断点像素值越大的,对应代码越靠后。所以,如果有这样的一些元素:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="container">
<div class="row">
<div class="col-sm-6 col-lg-3">1</div>
<div class="col-sm-6 col-lg-3">2</div>
<div class="col-sm-6 col-lg-3">3</div>
<div class="col-sm-6 col-lg-3">4</div>
</div>
</div>
</code></pre></div></div>
<p>那么它们应该是这样的效果:
<img src="/images/gridSystem.png" alt="gridSystem" /></p>
<p>结合前面的源码,可以想到,在上面这样视口宽度由小变大的过程中,首先是保持默认的竖直堆叠,然后超过了sm的断点,sm的样式生效,变为一行两列的排版,再继续超过lg的断点后,lg的样式也生效,由于lg的样式代码定义在sm之后,所以会覆盖掉sm的样式,从而得到一行四列的排版。
所以,结合使用多个断点类型,就可以引入多个断点变化,把响应式做得更加细致。</p>
<p>通过下表可以详细查看Bootstrap的栅格系统和媒体查询是如何在多种屏幕设备上工作的。</p>
<table>
<thead>
<tr>
<th> </th>
<th>超小屏幕 手机 (<768px)</th>
<th>小屏幕 平板 (≥768px)</th>
<th>中等屏幕 桌面显示器 (≥992px)</th>
<th>大屏幕 大桌面显示器 (≥1200px)</th>
</tr>
</thead>
<tbody>
<tr>
<td>栅格系统行为</td>
<td>总是水平排列</td>
<td>开始是堆叠在一起的,当大于这些阈值时将变为水平排列</td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>.container最大宽度</td>
<td>None(自动</td>
<td>750px</td>
<td>970px</td>
<td>1170px</td>
</tr>
<tr>
<td>类前缀</td>
<td>.col-xs-</td>
<td>.col-sm-</td>
<td>.col-md-</td>
<td>.col-lg-</td>
</tr>
<tr>
<td>列(column)数</td>
<td>12</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>最大列(column)宽</td>
<td>自动</td>
<td>~62px</td>
<td>~81px</td>
<td>~97px</td>
</tr>
<tr>
<td>槽(gutter)宽</td>
<td>30px(每列左右均有 15px)</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>可嵌套</td>
<td>是</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>偏移(Offsets)</td>
<td>是</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
<tr>
<td>列排序</td>
<td>是</td>
<td> </td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<p>当然,bootstrap只预定义了基本的媒体查询,bootstrap鼓励用户对其进行扩展,用户可结合项目实际情况,添加、修改媒体查询的规则,让自己的页面在不同的设备环境准确响应。</p>
<h2 id="四小结-">四、小结 <a name="end"></a></h2>
<p>完整且层次分明的栅格系统,可扩展的媒体查询,bootstrap基于这两者,构建了自己的强大的响应式布局功能,让用户能够轻松的为自己的页面实现响应式设计。
当然,这也仅仅是技术上的实现,而响应式设计是一种设计方式和理念,还更多的包括前期对页面展示的规划和设计,如何让页面展示得自然、全面,让用户在不同的设备上得到到同样优质的体验,也是响应式设计的重点内容。</p>
<h2 id="五参考文献-">五、参考文献 <a name="doc"></a></h2>
<p><a href="http://getbootstrap.com/">http://getbootstrap.com/</a></p>
<p><a href="http://www.jb51.net/css/362199.html/">http://www.jb51.net/css/362199.html/</a></p>
<p><a href="http://isux.tencent.com/responsive-web-design.html">http://isux.tencent.com/responsive-web-design.html/</a></p>
怎样设计才能让网站看起来高大上
2016-05-23T00:00:00+00:00
http://www.blogways.net/blog/2016/05/23/web-design
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#intro">高大上页面的定义</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#names">网页设计的切入点</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#replace">总结4个方面做到高大上的网页</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>最近在构思重新设计博客的网页,想追求页面高大上有质感让人感觉耳目一新的感觉。所以最近在翻看学习很多国内外的优秀网站,从而也总结出了一些关于高大上网页设计的一些观点和想法。</p>
<h2 id="一高大上网页的定义-">一、高大上网页的定义 <a name="intro"></a></h2>
<p>现在说到高大上的页面大家的定义基本趋向于极简风,就是用最少的图片和文字能够表达突出网页的所有关键信息。不仅仅理解为视觉层面上如何更好,一个网站不仅仅只有视觉,最终的结果是多方因素的完美结合。核心就是图片的选择编辑,字体的选择和排版。</p>
<h2 id="二网页设计的切入点">二、网页设计的切入点<a name="names"></a></h2>
<p>让人感受“高大上”,首先设计需要具备一定的冲击力,高大上,设计感,是最后结果给人的一种感觉。这种评价其实反映出设计的精准感:内容上不多不少,刚刚好;风格上不偏不倚,很舒服。要达到这种精准,首先需要明确这个网站是用来干什么的,满足了用户那些需求,满足了商业上的那些要求。了解了解行业和市场,研究研究同类产品和竞争对手等删去不需要的内容,从而让整个网站更加简洁。</p>
<h2 id="三总结以下4个方面做到高大上的网页-">三、总结以下4个方面做到高大上的网页 <a name="replace"></a></h2>
<h3 id="1-质感美图">1 质感美图</h3>
<p>图片是渲染气氛最有效的方式,一张高质量的图片本身具备的美感已经足以提网页的质量。
传说中的「黑白噪点压大字,高端大气上档次」就抓住了精髓。</p>
<p><img src="/images/0e189b48a0cd7f750c18657f3633ae84_r.png" alt="Alt text" /></p>
<h3 id="2-字体变化">2 字体变化</h3>
<p>字体排版地好可以画面有层次感,仅仅字体的对比就能产生很多美感。一个设计画面上出现两种字体是标配。但是超过两种字体会让观众分心,这样传递的效果就不强了。一般来说,一个酷炫的字体会搭配一个比较普通的字体,一个做标题,一个做正文。</p>
<p><img src="/images/9193c9e42577b21733411543cf8979e3_r.png" alt="Alt text" /></p>
<h3 id="3-遵循简单">3 遵循简单</h3>
<p>网站并不需要用到很多的特效和大量过分夸张的动画,遵循简单有效的布局,利用细节去改变网站,通过段落的排版,字体的选择,图文的穿插等细节的方式提升美感。</p>
<p><img src="/images/6b1d1d94bf124949e2c75425255f8ea8_r.png" alt="Alt text" /></p>
<h3 id="4-恰当留白">4 恰当留白</h3>
<p>留白给人以奢侈的感觉,页面寸土寸金,不到万不得已就没必要把它塞得过满。留白可以更好的凸显主体,适当的留白还能产生别样的意味。</p>
<p><img src="/images/1f536769af2146ca11e42eed0fb5b8d7_b.jpg" alt="Alt text" /></p>
<h2><a name="intro"></a></h2>
<p>简单布局、恰当留白、对比关系、就连宋小宝也可以变成国际大片的感觉~~~ 还有什么是设计不能拯救的呢?</p>
<p><img src="/images/06ccb6f63f20bd26b86550e99f198c15_b.jpg" alt="Alt text" /></p>
<h2 id="四参考文献">四、参考文献<a name="reference"></a></h2>
<p><a href="http://baike.baidu.com/link?url=3qsr0wpF6bpD1mZX8HV7ADuuxLHaxn-Ygxa7h0Rdnm9eBUt_or9Cq_uEn_6Z2A8Is4RkT1NcJ7O4ZspX3PvJIMUa4dCkYxaRGbPXrSAEbkq7Lrq0w49ZV-juQCtu341oihS8vkJ4ONYvQejmCBiYVC7GY_V6LB8xxaGXgRwczhARBTJFy3QmEeDCPAcDMLAB9W1AeCkCvllHYSec0ewflCkFD1-vfvn-1Eq1SxnYf8YmQyHULcRhLhYmadr8ww3E2vfOfliys4Rojt2fueux_a">破茧成蝶:用户体验设计师的成长之路</a></p>
<p><a href="http://www.yixieshi.com/16085.html/">https://http://www.yixieshi.com/16085.html/</a></p>
AIX主机性能监控命令
2016-05-01T00:00:00+00:00
http://www.blogways.net/blog/2016/05/01/AIX-HostMonitor
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#cpu">获取CPU数据</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#mem">获取内存/SWAP数据</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#disk">获取磁盘数据</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#dir">获取目录数据</a></td>
</tr>
</tbody>
</table>
<h2 id="一获取cpu数据-">一、获取CPU数据 <a name="cpu"></a></h2>
<p>mpstat是MultiProcessor Statistics的缩写,用于收集和显示系统中的所有处理器的性能统计,并且得到详细的单个处理器的运行状况。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $mpstat
System configuration: lcpu=16 mode=Capped
cpu min maj mpc int cs ics rq mig lpa sysc us sy wa id pc
0 184046638 214424 274 3908673210 3782119157 1326252330 0 26139925 100 6707390946 1 1 0 98 0.14
1 7171412 14613 254 193245713 154941530 78622875 0 1126081 100 693934930 0 0 0 100 0.14
2 170845575 230472 101 1308931839 2167522165 818427086 0 26083149 100 4900640467 1 1 0 99 0.14
3 3193293 7611 101 236575152 198239437 153835438 0 1207732 100 139876648 0 0 0 100 0.14
4 159868990 227718 129 1130251601 1850725273 692517872 0 12009046 100 2854042300 1 1 0 99 0.14
5 5647834 30697 101 195761607 162561260 82402841 0 1132055 100 362309277 0 0 0 100 0.14
6 157000962 211598 128 1099788787 1740814939 640685578 0 11993602 100 2992005605 1 1 0 99 0.14
......
ALL 1370435922 2192633 3012 14099748600 18474769270 6889638308 1 139959495 100 36629470849 0 0 0 99 2.29
#该命令检测系统中全部处理器的利用情况,并且给出各项的总和。下面是几个常用的输出项
#lcpu: 工作的逻辑处理器的个数
#us: 运行的用户程序所占用的 CPU 百分比
#sy: 运行的内核程序所占用的 CPU 百分比
#wa: CPU 用来等待 IO 所占用的百分比
#id: CPU 空闲且不等待 IO 所占用的百分比
</code></pre></div></div>
<p>但在实际使用中,往往会加上 -a 参数,即 mpstat -a ,以宽输出模式显示所有的统计信息,下面来感受一下……</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $mpstat -a
System configuration: lcpu=16 mode=Capped
cpu min maj mpcs mpcr dev soft dec ph cs ics bound rq push S3pull S3grd S0rd S1rd S2rd S3rd S4rd S5rd sysc us sy wa id pc ilcs vlcs istol bstol S3hrd S4hrd S5hrd
0 184061070 214424 150 124 4310333 590469189 3306344682 8099285 3782890620 1326486636 0 0 0 7911 172073 98.9 0.0 0.0 1.0 0.0 0.0 6707965194 0.7 1.2 0.0 98.1 0.14 0 2681118967 0.0 0.0 100.0 0.0 0.0
1 7171412 14613 167 87 4313856 1079722 186685547 1177054 154944893 78624637 0 0 0 579 21 98.6 1.4 0.0 0.0 0.0 0.0 693937155 0.0 0.1 0.0 99.9 0.14 17 200960974 0.0 0.0 99.4 0.0 0.6
2 170859397 230472 3 98 4311630 24093376 1267078359 13630503 2167851281 818525974 1 1 0 3487 23722 98.1 0.0 0.0 1.9 0.0 0.0 4900880379 0.6 0.8 0.0 98.6 0.14 68 1049149272 0.0 0.0 100.0 0.0 0.0
3 3193904 7611 3 98 4314206 1526924 229574778 1176611 198256684 153848135 0 0 0 266 44 99.2 0.7 0.0 0.1 0.0 0.0 139878645 0.0 0.1 0.0 99.9 0.14 30 275388781 0.0 0.0 99.7 0.0 0.3
4 159881624 227718 33 96 4312115 20541062 1104121880 1401356 1850937520 692592197 0 0 0 4393 27574 99.0 0.0 0.0 0.9 0.0 0.1 2854195957 0.5 0.6 0.0 98.8 0.14 60 905205613 0.0 0.0 99.9 0.0 0.1
5 5647840 30697 3 98 4314215 1797627 188492566 1175045 162579018 82411800 0 0 0 129 978 98.6 1.4 0.0 0.0 0.0 0.0 362318106 0.0 0.1 0.0 99.9 0.14 27 203720025 0.0 0.0 99.3 0.0 0.7
6 157024188 211598 32 96 4313516 22509032 1071873739 1262410 1741141601 640807004 0 0 0 4196 10466 98.9 0.0 0.0 1.1 0.0 0.0 2992221777 0.5 0.8 0.0 98.7 0.14 56 876887437 0.0 0.0 99.9 0.0 0.1
......
ALL 1370536873 2192649 1506 1506 69017506 783158186 13210473966 38730670 18477226175 6890502684 1 1 0 38922 271366 98.8 0.1 0.0 1.1 0.0 0.0 36632650675 0.3 0.4 0.0 99.3 2.29 596 10830086125 0.0 0.0 99.9 0.0 0.1
</code></pre></div></div>
<p>输出的结果很详细,但也很混乱,让人眼花缭乱,没关系,我们可以将结果格式化处理,得到我们想要的数据.</p>
<h4 id="例1获取当前主机总cpu使用率">例1:获取当前主机总CPU使用率</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $mpstat -a|grep ALL|awk '{printf "当前主机总CPU使用率=%s",$24+$25}'
当前主机总CPU使用率=0.7
#通过 grep ALL 仅输出总计一行的数据,再使用 awk 命令,将第24、25域,也就是us和sy两列的数据相加获得完整的CPU使用率
</code></pre></div></div>
<h4 id="例2显示所有处理器的cpu使用率">例2:显示所有处理器的CPU使用率</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $mpstat -a|sed -n '5,$'p|sed '$d'|awk '{print "cpu"$1"使用率:",$24+$25}'
cpu0使用率: 1.9
cpu1使用率: 0.1
cpu2使用率: 1.4
cpu3使用率: 0.1
cpu4使用率: 1.1
cpu5使用率: 0.1
cpu6使用率: 1.3
cpu7使用率: 0.1
cpu8使用率: 1.2
cpu9使用率: 0.2
cpu10使用率: 1.3
cpu11使用率: 0.2
cpu12使用率: 1.2
cpu13使用率: 0.1
cpu14使用率: 1.3
cpu15使用率: 0.1
# sed -n '5,$'p 命令表示取第5行到最后一行的数据
# sed '$d' 命令表示删除最后一行
</code></pre></div></div>
<h2 id="二获取内存swap数据-">二、获取内存/SWAP数据 <a name="mem"></a></h2>
<p>想要获取主机当前内存使用情况,可通过svmon -G来显示。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $svmon -G
size inuse free pin virtual mmode
memory 65667072 12659615 53007457 3591255 7719427 Ded
pg space 8388608 20086
work pers clnt other
pin 1719367 0 0 1871888
in use 7719427 0 4940188
PageSize PoolSize inuse pgsp pin virtual
s 4 KB - 10953407 20086 2153575 6013219
m 64 KB - 106638 0 89855 106638
# 其中,内存情况可查看memory一行
# pg space一行即SWAP的情况
# size -- 总容量
# inuse -- 已使用量
# free -- 空闲量
# 另外,这里还有一个要注意的地方
# 比如上图中,pg space的size为8388608,而这里的单位是页,并不是平常的KB、MB等,这涉及到内存段(segment)的知识,这里就不加以赘述,仅说明换算方式
# 1页=4KB 1MB=256页
# 所以,此处pg space的size实际上是 33554432KB 32768MB
</code></pre></div></div>
<p>知道了命令,下面来看看具体的例子。</p>
<h4 id="例1获取当前主机内存和swap的总容量已使用量和使用率">例1:获取当前主机内存和SWAP的总容量、已使用量和使用率</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $svmon -G|grep -E 'memory|pg space'|awk '{if($1=="memory"){print $1,$2/256"M",$3/256"M",$3/$2} else {print $1$2,$3/256"M",$4/256"M",$4/$3}}'
memory 256512M 49452.9M 0.19279
pgspace 32768M 78.4609M 0.00239444
# grep -E 'memory|pg space' 仅输出带有 memory 和 pg space 的两行
# awk 命令中,首先判断一下当前行是memory还是pg space,因为pg space中有个空格,此处占了两个域,所以必须先判断
# 之后根据不同情况,输出结果就可以了
# 值得一提的是,awk的if条件语句与shell的语句是不一样的
# awk 的if条件语句格式:
# if(表达式){
# 语句1
# }else if{
# 语句2
# }else{
# 语句3
# }
</code></pre></div></div>
<h4 id="例2获取当前主机内存占用top10的进程的id名称内存占用量">例2:获取当前主机内存占用top10的进程的id、名称、内存占用量</h4>
<p>查看进程情况可使用ps命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ps -elf
F S UID PID PPID C PRI NI ADDR SZ WCHAN STIME TTY TIME CMD
200003 A root 1 0 0 60 20 1070187480 1688 Dec 24 - 0:00 /etc/init
240001 A root 1114204 918412 0 60 20 80288480 992 Dec 24 - 0:00 /usr/bin/X11/aixconsole
240001 A root 1245188 1 0 60 20 15b01db480 980 Dec 24 - 0:00 /usr/sbin/srcmstr
240001 A root 1310854 1 0 60 20 2f00af590 428 f1000000a05fc098 Dec 24 - 0:00 /usr/ccs/bin/shlap64
240001 A root 1638646 1377066 0 60 20 4b04cb480 940 f1000e00002a0878 Dec 24 vty2 0:00 /bin/ksh /usr/lib/assist/assist_main
240001 A root 1704120 1245188 0 60 20 a00020480 2452 Dec 24 - 2:13 sendmail: accepting connections
40001 A root 1769708 1 0 60 20 3702b7480 308 Dec 24 - 0:00 /opt/freeware/cimom/pegasus/bin/CIM_diagd
240001 A root 1835192 1 0 60 20 3102b1480 5224 f1000a0a003be4b0 Dec 24 - 0:09 /opt/ibm/director/cimom/bin/tier1slp
41001 A root 1900728 1 0 60 20 1890309480 4500 Dec 24 - 0:11 ./slp_srvreg -D
......
#options:
# -e 将除内核进程以外所有进程的信息写到标准输出。
# -l 生成长列表
# -f 生成一个完整列表
#字段:
#PID -- 进程ID
#SZ -- 内存占用量(单位仍是页)
#CMD -- 内存名
#由此可见,通过ps命令再加上一些格式化处理命令应该可以得到我们想要的结果
</code></pre></div></div>
<p>下面开始尝试输出进程的id、名称、内存占用量</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ps -elf|sort -rn +9|head -10|awk 'BEGIN{print "PID","PNAME","SZ"}{print $4,$15,$10}'
PID PNAME SZ
2621660 2031:33 3885164
1639166 ora_lgwr_rhjftst 108252
1376752 ora_mmon_rhjftst 98324
1245552 ora_cjq0_rhjftst 97868
1704826 ora_dbw0_rhjftst 97824
917888 ora_pmon_rhjftst 97660
2687194 ora_ckpt_rhjftst 97584
1049374 ora_smon_rhjftst 95628
1311046 ora_dbw1_rhjftst 95232
2752842 0:35 94932
</code></pre></div></div>
<p>结果让人失望,由于各行的域数量不一致,导致$15无法一直指向进程名所在的域。在经过一系列尝试后,仍然不能单用命令得到进程名,所以改用脚本来获取。</p>
<h4 id="用脚本的方式获取">用脚本的方式获取</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $vi test.sh
# !/bin/sh
tops='';
rst=`ps -ealf|sort -rn +9|head -10|awk '{print $4,$10/256}'`
cnt=0
for item in ${rst[*]}
do
if [[ $((cnt%2)) -eq 0 ]]
then
tops="${tops}${item},"`ps $item|grep -v PID|awk '{printf "%s,",$5}'`
else
tops="${tops}""${item}""\`"
fi
let cnt=$cnt+1
done
tops=${tops%\`}
echo $tops
# 整体思路是先得到top10的进程id和所占内存大小,循环通过id去获得对应的进程名称
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # 输出结果
$./test.sh
2621660,/altibase/bin/altibase,15176.4`1639166,ora_lgwr_rhjftst,422.859`1376752,
ora_mmon_rhjftst,384.078`1245552,ora_cjq0_rhjftst,382.297`1704826,ora_dbw0_rhjftst,
382.125`917888,ora_pmon_rhjftst,381.484`2687194,ora_ckpt_rhjftst,381.188`1049374,
ora_smon_rhjftst,373.547`1311046,ora_dbw1_rhjftst,372`2752842,oraclerhjftst,370.828
# 为了展示效果,稍微调整一下格式
#2621660,/altibase/bin/altibase,3885164`
#1639166,ora_lgwr_rhjftst,108252`
#1376752,ora_mmon_rhjftst,98324`
#1245552,ora_cjq0_rhjftst,97868`
#1704826,ora_dbw0_rhjftst,97824`
#917888,ora_pmon_rhjftst,97660`
#2687194,ora_ckpt_rhjftst,97584`
#1049374,ora_smon_rhjftst,95628`
#1311046,ora_dbw1_rhjftst,95232`
#2752842,oraclerhjftst,94932
</code></pre></div></div>
<p>不得不承认这是一个笨方法,但对于效率没有多少影响,在找到新方法之前,可以先用着。</p>
<h2 id="三获取磁盘数据-">三、获取磁盘数据 <a name="disk"></a></h2>
<p>lvps命令可以显示物理卷的相关信息</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $lspv
hdisk0 00c5cc969e7f0175 rootvg active
hdisk1 00c5cc96a1c7d95e datavg active
hdisk2 00c5cc96a1c7e1a9 datavg active
hdisk3 00c5cc96a1c7e863 dicvg active
hdisk4 00c5cc96a1c7ef0b dicvg active
# 单独使用lspv可以得到所有物理卷的物理卷名称(pvname)、物理卷ID(pvid)、卷组名称(vgname)以及状态(status)
$lspv hdisk0
PHYSICAL VOLUME: hdisk0 VOLUME GROUP: rootvg
PV IDENTIFIER: 00c5cc969e7f0175 VG IDENTIFIER 00c5cc9600004c000000012e9e7f11e5
PV STATE: active
STALE PARTITIONS: 0 ALLOCATABLE: yes
PP SIZE: 512 megabyte(s) LOGICAL VOLUMES: 16
TOTAL PPs: 558 (285696 megabytes) VG DESCRIPTORS: 2
FREE PPs: 414 (211968 megabytes) HOT SPARE: no
USED PPs: 144 (73728 megabytes) MAX REQUEST: 1 megabyte
FREE DISTRIBUTION: 111..03..77..111..112
USED DISTRIBUTION: 01..109..34..00..00
MIRROR POOL: None
# 使用lspv pvname 则会显示该pv的详细信息
</code></pre></div></div>
<p>pp(physial partition)是一个物理分区,vg由pv组成,pv里边的最小分配单位就是pp,pp size就是vg里最小分配单位大小,
该物理卷的总容量=TOTAL PPS*PP SIZE/1024,单位是GB。
所以,想要得到所有物理卷的总容量、已使用量、已使用百分比,还是得通过脚本。</p>
<h4 id="例1物理卷的总容量已使用量已使用百分比">例1:物理卷的总容量、已使用量、已使用百分比</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $vi test.sh
#!/bin/sh
disks=`lspv|awk '{print $1}'`
for disk in $disks
do
printf $disk": "
lspv $disk|sed -n '5,8p'|awk '{if(NR==1){size=$3}else if(NR==2){total=$3*size/1024}else if(NR==4){used=$3*size/1024}else{}}END{print "总容量:"total,"已使用量"used,"已使用百分比"used/total}'
done
$./test.sh
hdisk0: 总容量:279 已使用量72 已使用百分比0.258065
hdisk1: 总容量:279 已使用量279 已使用百分比1
hdisk2: 总容量:279 已使用量276.5 已使用百分比0.991039
hdisk3: 总容量:279.25 已使用量279.25 已使用百分比1
hdisk4: 总容量:279.25 已使用量271 已使用百分比0.970457
</code></pre></div></div>
<h2 id="四获取目录数据-">四、获取目录数据 <a name="dir"></a></h2>
<p>查看目录情况,一般使用du命令
下面是一些常用选项
-a 显示目录下所有子目录和文件的磁盘使用情况,与-s相对
-g 用 GB 单位统计;
-k 用 1024 字节单位统计;
-m 用 MB 单位统计;
-r 报告不可访问的文件或者目录名,此为缺省设置;
-s 仅显示指定目录的整体磁盘使用情况,不显示子目录及文件使用情况,与-a相对。</p>
<h4 id="例1仅获取目录整体磁盘使用情况用gb单位统计">例1:仅获取目录整体磁盘使用情况,用GB单位统计</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $du -sg $dirname
3.65 /ngbss/crmdev/.pangu
</code></pre></div></div>
<h4 id="例2显示指定目录下所有子目录及文件磁盘使用情况用mb单位统计">例2:显示指定目录下所有子目录及文件磁盘使用情况,用MB单位统计</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $du -am $dirname
......
0.14 /ngbss/crmdev/.pangu/test/pangu-monitor/test/main.o
0.00 /ngbss/crmdev/.pangu/test/pangu-monitor/test/monitor.template
8.13 /ngbss/crmdev/.pangu/test/pangu-monitor/test
75.58 /ngbss/crmdev/.pangu/test/pangu-monitor
0.00 /ngbss/crmdev/.pangu/test/runmon.sh
163.58 /ngbss/crmdev/.pangu/test
3734.79 /ngbss/crmdev/.pangu
#此命令会在最后一行显示指定目录的整体磁盘使用情况
</code></pre></div></div>
<h4 id="例3显示指定目录下大文件top10">例3:显示指定目录下大文件Top10</h4>
<p>本以为会很简单,只要使用降序,再取前10行就可以了</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $du -am $dirname|sort -rn|head -10
3734.79 /ngbss/crmdev/.pangu
3347.17 /ngbss/crmdev/.pangu/log
2045.48 /ngbss/crmdev/.pangu/log/4Glog-20150210
326.80 /ngbss/crmdev/.pangu/log/134_32_28_198
310.17 /ngbss/crmdev/.pangu/log/134_32_28_197
300.05 /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.2
300.04 /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.0
300.02 /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.1
300.02 /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.0
300.01 /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.2
</code></pre></div></div>
<p>然而并不是,上面的结果除了文件,还包含着目录。
如果想要只输出文件,必须另寻他法。
尝试过其他很多方法,比如find,比如ls,虽然能达到想要的结果,但是执行效率都不高,没有du来得快,若想用du,关键在于判断该行地址是否是目录。
观察整个命令流程,最有可能可以判断的地方只能是在awk环节。
于是研究了awk的用法后,发现awk的’{}’中,是可以可以执行系统命令及shell命令的。
通过system(cmd)结合getline[var]便可以实现。
于是……</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $du -am $dirname|sort -rn|awk '{cmd="test -d "$2";echo $?";cmd|getline rst;if(rst==1){print "路径: "$2 ,"占用空间"$1"MB"}}'|head -10
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.2 文件大小: 300.05MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.0 文件大小: 300.04MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.1 文件大小: 300.02MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.0 文件大小: 300.02MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.2 文件大小: 300.01MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.1 文件大小: 300.01MB
路径: /ngbss/crmdev/.pangu/log/trade_4G_127_01.log 文件大小: 170.82MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_127_01.log.2015-02-10.3 文件大小: 124.88MB
路径: /ngbss/crmdev/.pangu/log/134_32_28_198/tux20141228.log 文件大小: 122.79MB
路径: /ngbss/crmdev/.pangu/log/4Glog-20150210/trade_4G_129_01.log.2015-02-10.3 文件大小: 120.45MB
</code></pre></div></div>
<p>终于得到了期望的输出结果。</p>
DataTables新旧版本对比
2016-04-17T00:00:00+00:00
http://www.blogways.net/blog/2016/04/17/datatables-contrast
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#intro">DataTables简介</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#names">官方更新说明</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#replace">实际开发中的更替</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>我也是近期才接触到DataTables这一款jQuery插件,项目中使用的版本是1.9.4,而目前最新的版本的是1.10.11,官网的DOC和API也是基于1.10以上版本,所以当我参考官网的帮助文档来学习项目中老版本的DataTables时,感觉区别比较大.但当我看了两个版本的源码后才发现,从使用的角度来看,它仅仅是换了个马甲.</p>
<h2 id="一datatables简介-">一、DataTables简介 <a name="intro"></a></h2>
<p>DataTables是一款灵活jQuery表格插件,通过它可以轻易地实现table的数据填充、分页、排序、查询、隐藏列等功能。</p>
<h3 id="11-构造方法">1.1 构造方法</h3>
<p>DataTables在被加载时,就已经定义了默认的功能(包括查询、排序、分页),所以当要为table初始化DataTables功能是,只需要调用构造方法$().DataTable(),这些功能将会立刻被添加到指定的table中。当然,也可以在调用时传入配置参数定制需要的功能,如:$().DataTable(options)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> /**
* 调用DataTables的构造方法
* @param {options} 初始化参数,如果不设置初始化参数,DataTables将使用默认的初始化
* @example:
* //生成一个关闭分页和排序功能的DataTable实例
* $('#table').DataTable({
* 'bPaginate' : false,
* 'bSort' : false
* });
*/
$('#table').DataTable([options]);
</code></pre></div></div>
<p>一个看似简单的构造方法就能使table获得诸多功能,如此性感,我自然想看看它的源码。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // version 1.9.4
var DataTable = function( oInit )
{
...
...
...
return this;
}
...
$.fn.DataTable = DataTable;
$.fn.dataTable = DataTable;
</code></pre></div></div>
<p>上面代码是1.9.4版本的DataTables。
DataTables在被加载时,就向jQuery的原型$.fn添加了一个DataTable和dataTable方法,指向DataTables的构造方法,将传入的table做一个一系列加工,返回一个加工后的JQuery对象.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // version 1.10.10
//下面两个代码实现了循环引用,使JQuery和DataTable成为了
//我中有你、你中有我的关系,水平有限,暂不明白此举的意义
$.fn.dataTable = DataTable;
DataTable.$ = $;
$.fn.DataTable = function ( opts ) {
return $(this).dataTable( opts ).api();
};
</code></pre></div></div>
<p>而在1.10.10版本,$().DataTable不再返回JQuery对象,而是返回一个对象_Api,该对象是DataTables的私有变量,使用该对象对table进行操作,可以有效避免命名冲突,并且不用担心DataTables属性泄露到全局变量中.
新版本推荐使用_Api代替旧版本的JQuery对象来进行table操作,但也提供了$().dataTable()方法获得JQuery对象。</p>
<h3 id="12-配置对象解析和分页">1.2 配置对象解析和分页</h3>
<p>既然DataTables是把JQuery的table实例进行加工,那么这个加工过程是怎么样的呢?关于这一点,新旧版本是一个思路,下面以1.9.4版本进行说明。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // version 1.9.4
// DataTables的构造函数,参数oInit就是$().DataTable(options)时传入的初始化参数
var DataTable = function( oInit )
{
...
...
//此处声明了很多方法,用于解析oSettings,并根据配置项加载所需功能.比如:
_fnInitialise( oSettings ){}
...
...
//创建一个配置对象,并在创建时就配置了一些默认的参数,此对象将决定Table是否启用、如何启用DataTables提供的一系列功能
//而DataTable.models相当于一个预定义的oSettiongs模型,每新建一个oSettings,就以DataTable.models为模板
var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
"nTable": this,
"oApi": _that.oApi,
"oInit": oInit,
"sDestroyWidth": $(this).width(),
"sInstance": sId,
"sTableId": sId
});
...
...
//如果没有传入oInit,则声明oInit为一个空对象
if ( !oInit ){
oInit = {};
}
//DataTable.defaults是DataTables在加载时就定义好的默认初始化参数对象,
//而_fnExtend()类似于jQuery.extend(),
//此方法就是将传入的初始化参数覆盖默认的初始化参数对象
oInit = _fnExtend( $.extend(true, {}, DataTable.defaults), oInit );
//_fnMap方法 将oInit的参数覆盖oSettings的参数,实现定制
_fnMap( oSettings.oFeatures, oInit, "bPaginate" );
_fnMap( oSettings.oFeatures, oInit, "bLengthChange" );
......
......
_fnMap( oSettings, oInit, "fnStateLoad" );
_fnMap( oSettings, oInit, "fnStateSave" );
_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
...
...
}
</code></pre></div></div>
<p>以上是设置功能配置对象oSettings的大致过程,当oSettings生成并配置完毕后,会执行_fnInitialise(oSettings)方法,该方法会解析传入的oSettings,根据配置项绘制table,添加所需功能,下面以分页功能进行说明。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> function _fnInitialise ( oSettings )
{
...
...
//判断是否使用分页功能
//通过oSettings.oFeatures.bPaginate是否设置为真判断
else if ( cOption == 'p' && oSettings.oFeatures.bPaginate )
{
//执行加载分页功能的方法
nTmp = _fnFeatureHtmlPaginate( oSettings );
iPushFeature = 1;
}
...
...
}
function _fnFeatureHtmlPaginate ( oSettings )
{
//如果采用了无限分页,return null
if ( oSettings.oScroll.bInfinite )
{
return null;
}
//创建一个<div>,容纳翻页栏
var nPaginate = document.createElement( 'div' );
//设置样式
nPaginate.className = oSettings.oClasses.sPaging+oSettings.sPaginationType;
//DataTable.ext放置了一些功能的具体实现,比如排序分页
//根据sPaginationType执行对应的fnInit方法,此方法在上面的<div>中生成了用于点击翻页<a>,并定义了如当当前页、首页、末页等参数
DataTable.ext.oPagination[ oSettings.sPaginationType ].fnInit( oSettings, nPaginate,
function( oSettings ) {
//此方法用于计算页数及末页的页码
_fnCalculateEnd( oSettings );
//此方法用于生成并插入<tr>,插入前会清空当前所有的<tr>
_fnDraw( oSettings );
}
);
//为第一次分页功能初始化添加一个回调函数,用于翻页
if ( !oSettings.aanFeatures.p )
{
oSettings.aoDrawCallback.push( {
"fn": function( oSettings ) {
//fnUpdate 用于翻页的方法,根据在接收到的参数调整DataTable.ext中的当前页、首页、末页等参数
DataTable.ext.oPagination[ oSettings.sPaginationType ].fnUpdate( oSettings, function( oSettings ) {
_fnCalculateEnd( oSettings );
_fnDraw( oSettings );
} );
},
"sName": "pagination"
} );
}
return nPaginate;
}
</code></pre></div></div>
<p>由于篇幅有限,许多方法的代码没有贴出来,等到以后针对某项功能进行介绍时,再一一阐述.</p>
<p>DataTables在加载时就会定义一系列变量和方法,如:</p>
<p><code class="language-plaintext highlighter-rouge">DataTable.default</code> – 默认初始化参数对象;
<code class="language-plaintext highlighter-rouge">DataTable.models</code> – 默认table模型;
<code class="language-plaintext highlighter-rouge">DataTable.ext</code> – 分页、排序等功能的具体实现方法;
<code class="language-plaintext highlighter-rouge">_fnInitialise(oSettings)</code> – 解析oSettings的属性,判读调用哪些方法,启用什么功能;
<code class="language-plaintext highlighter-rouge">_fnDraw(oSettings)</code> – 根据oSettings._iDisplayStart、oSettings._iDisplayEnd等属性生成tr.</p>
<p>当调用其构造方法时,就会将JQuery对象和配置参数传入进行加工,使用$.extend或_fnExtend将DataTable.models、DataTable.default以及传入的配置参数options合并成oSettiong对象,再将此对象传入_fnInitialise(oSettings)并执行,定制出自己的table.</p>
<p>以上是DataTables的简介,下面来看看官方声明的1.10版本的更新变动.</p>
<h2 id="二官方更新说明">二、官方更新说明<a name="names"></a></h2>
<p>主要区别有两点:初始化方法和参数名称.</p>
<h3 id="21-初始化方法">2.1 初始化方法</h3>
<p>1.10版本以前
$(…).DataTable() 创建一个DataTable并返回一个jQuery对象</p>
<p>1.10版本以后
$(…).DataTable() 创建一个DataTable并返回一个DataTables API实例
$(…).dataTable() 创建一个dataTable并返回一个jQuery对象</p>
<p>此区别上文中已提及,这里不再赘述.</p>
<h3 id="22-参数名称">2.2 参数名称</h3>
<p>在1.10版本以前,DataTables的各配置参数采用的是匈牙利命名法,而在1.10之后,改为了驼峰命名法.
虽然新版本也兼容匈牙利命名法的参数,但是其官网的帮助文档均用新版驼峰命名法的参数进行说明,这也是造成我误以为新老版本天差地别的原因.
下面列举了一些常用参数的新旧版本对照:</p>
<table>
<thead>
<tr>
<th> </th>
<th> </th>
<th> </th>
</tr>
</thead>
<tbody>
<tr>
<td>aaData</td>
<td>data</td>
<td>用于显示的数据</td>
</tr>
<tr>
<td>aaSorting</td>
<td>order</td>
<td>表格初始化排序</td>
</tr>
<tr>
<td>aoColumns</td>
<td>columns</td>
<td>列配置数组</td>
</tr>
<tr>
<td>aoColumns</td>
<td>columnDefs</td>
<td>定义多列排序</td>
</tr>
<tr>
<td>aoSearchCols</td>
<td>searchCols</td>
<td>定义初始化查询栏</td>
</tr>
<tr>
<td>asSorting</td>
<td>columns.orderSequence</td>
<td>定义升降序序列</td>
</tr>
<tr>
<td>bAutoWidth</td>
<td>autoWidth</td>
<td>自动设置table宽度</td>
</tr>
<tr>
<td>bInfo</td>
<td>info</td>
<td>设置表格信息展示功能</td>
</tr>
<tr>
<td>bPaginate</td>
<td>paging</td>
<td>是否启用分页功能</td>
</tr>
<tr>
<td>bProcessing</td>
<td>processing</td>
<td>显示加载信息</td>
</tr>
<tr>
<td>bScrollCollapse</td>
<td>scrollCollapse</td>
<td>是否启用滚动条</td>
</tr>
<tr>
<td>bSearchable</td>
<td>columns.searchable</td>
<td>是否启用搜索功能</td>
</tr>
<tr>
<td>bServerSide</td>
<td>serverSide</td>
<td>是否启用服务模式</td>
</tr>
<tr>
<td>bSort</td>
<td>ordering</td>
<td>是否启用排序</td>
</tr>
<tr>
<td>bSortCellsTop</td>
<td>columns.orderable</td>
<td>开关某列的排序</td>
</tr>
<tr>
<td>bStateSave</td>
<td>stateSave</td>
<td>是否启用浏览器缓存功能</td>
</tr>
<tr>
<td>fnServerData</td>
<td>ajax</td>
<td>从Ajax源加载数据</td>
</tr>
<tr>
<td>fnServerParams</td>
<td>ajax</td>
<td>设置Ajax传递参数</td>
</tr>
</tbody>
</table>
<p>篇幅有限,若想查看全部内容请访问官网.
<a href="https://datatables.net/upgrade/1.10-convert.html">https://datatables.net/upgrade/1.10-convert.html</a></p>
<h2 id="三实际开发中的更替-">三、实际开发中的更替 <a name="replace"></a></h2>
<p>项目中使用的DataTables的版本是1.9.4,当我将其换成1.10.10版本后,所使用的功能一切正常,除了这个地方:</p>
<p><code class="language-plaintext highlighter-rouge">这是原来旧版的呈现</code>
<img src="/images/old_Version.png" alt="old" /></p>
<p><code class="language-plaintext highlighter-rouge">这是替换成新版后的呈现</code>
<img src="/images/new_Version.png" alt="new" /></p>
<p>我试着点击本应该disable的按钮,发现并没有发出任何请求,于是想会不会是样式问题,于是在源码中找到了分页按钮样式的名称.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // version 1.9.4
/* Full numbers paging buttons */
"sPageButton": "paginate_button",
"sPageButtonActive": "paginate_active",
"sPageButtonStaticDisabled": "paginate_button paginate_button_disabled",
"sPageFirst": "first",
"sPagePrevious": "previous",
"sPageNext": "next",
"sPageLast": "last"
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // version 1.10.10
/* Paging buttons */
"sPageButton": "paginate_button",
"sPageButtonActive": "current",
"sPageButtonDisabled": "disabled"
</code></pre></div></div>
<p>发现原因,新旧版本的css文件不一样,class名称自然也会有所不同.
sPageButton的样式名称相同,区别在于Active和Disabled,于是把项目中的plugin.css文件改了一下,把”.paginate_active”改为”.current”,”.paginate_button_disabled”改为”.disables”.
或者反过来,把DataTables的js文件改为与项目css文件一致,都能够回复正常.</p>
<p><img src="/images/old_Version.png" alt="old" /></p>
<p>排序、分页、Ajax等功能一切正常,或许还有其他的一些错误没有排除,等遇到异常再说吧。</p>
<h2 id="四参考文献">四、参考文献<a name="reference"></a></h2>
<p><a href="https://www.datatables.net/">https://www.datatables.net/</a></p>
ESLint使用介绍
2016-02-02T00:00:00+00:00
http://www.blogways.net/blog/2016/02/02/javascript-eslint
<table>
<thead>
<tr>
<th> </th>
<th><em>目 录</em></th>
</tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td><a href="#comparison">JavaScript Linting工具的比较</a></td>
</tr>
<tr>
<td>2</td>
<td><a href="#install">ESLint的安装</a></td>
</tr>
<tr>
<td>3</td>
<td><a href="#usage">ESLint的使用说明</a></td>
</tr>
<tr>
<td>4</td>
<td><a href="#editor">与编辑工具的集成</a></td>
</tr>
<tr>
<td>5</td>
<td><a href="#airbnb">不可不知的airbnb编码规范与配置</a></td>
</tr>
<tr>
<td>6</td>
<td><a href="#reference">参考文献</a></td>
</tr>
</tbody>
</table>
<p>之前用的是JSHint,感觉挺不错的。最近发现了ESLint,试用了一下,发现更好!特此,把所了解的信息分享一下。</p>
<h2 id="一linting工具的比较-">一、Linting工具的比较 <a href="comparison"></a></h2>
<p>不用说,大家最常用的JavaScript Linting工具应该是下面四个之一:</p>
<p><strong>JSLint - JSHint - JSCS - ESLint</strong></p>
<p>这四种工具,工作方式基本相同。他们都定义了一些规则去分析报告JavaScript文件中的问题;可以通过<code class="language-plaintext highlighter-rouge">npm</code>进行安装;可以在命令行运行;可以作为Grunt等工具的插件;可以集成到常用编辑工具中去;可以在文件中通过注释进行配置。</p>
<h3 id="11-jslint">1.1 JSLint</h3>
<p>四个工具中最老的一个。<code class="language-plaintext highlighter-rouge">Douglas Crockford</code>在2002年创建,根据他的经验,强制校验了JavaScript编码中一些更合理的限制规则。其使用最大的缺点,就是不支持个性化配置,但优点也是无需配置,如果你认同这些规则,那么用起来就很顺手。</p>
<h3 id="12-jshint">1.2 JSHint</h3>
<p>JSHint是JSLint的改良版,支持通过配置文件配置规则级别。对ES6的规范也做了简单的支持。很好用!缺点就是所有规则都是内置的,无法自定义新规则。实际上在大部分项目中,就已经足够用了。</p>
<h3 id="13-jscs">1.3 JSCS</h3>
<p>JSCS和前两个最大的不同就是没有配置文件就不能工作。他已经有了超过90个内置规则,并且你可以自定义新规则。但是他只是对编码风格进行检查,无法对代码中潜在的bug(如未使用的变量/突兀的全局变量等等)或者错误进行判断。</p>
<h3 id="14-eslint">1.4 ESLint</h3>
<p>四个工具中最新的。借鉴了前辈的经验。每个规则都可以开关,很多规则都有多个选项,供微调;易于扩展,可以自定义新规则,有很多有用的插件;包含了很多另外三个没有的规则;不但可以发现问题,在某些规则上面,还支持自动纠错;对ES6的支持最到位;是四者中唯一一个支持JSX的。</p>
<h2 id="二eslint的安装">二、ESLint的安装<a name="install"></a></h2>
<p>在你的项目目录下,输入命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i eslint <span class="nt">--save-dev</span>
</code></pre></div></div>
<p>如果之前安装了低版本,需要升级,可以如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i eslint@latest <span class="nt">--save-dev</span>
</code></pre></div></div>
<p>这样,你就在当前目录下安装了<code class="language-plaintext highlighter-rouge">eslint</code>。不过,<code class="language-plaintext highlighter-rouge">ESLint</code>作为一个常用工具,我建议最好还是全局安装,这样使用起来会很方便。如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm i eslint <span class="nt">-g</span>
</code></pre></div></div>
<h2 id="三eslint使用说明">三、ESLint使用说明<a name="usage"></a></h2>
<h3 id="31-创建配置文件">3.1 创建配置文件</h3>
<p>如果全局模式安装的<code class="language-plaintext highlighter-rouge">eslint</code>,那么进入项目目录,键入:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eslint <span class="nt">--init</span>
</code></pre></div></div>
<p>如果只是在当前目录下安装了<code class="language-plaintext highlighter-rouge">eslint</code>,那么键入:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./node_modules/.bin/eslint <span class="nt">--init</span>
</code></pre></div></div>
<p>然后,根据提示,使用方向键、空格键或者回车,进行回答。</p>
<p>比如:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>eslint <span class="nt">--init</span>
? How would you like to configure ESLint? Answer questions about your style
? What style of indentation <span class="k">do </span>you use? Spaces
? What quotes <span class="k">do </span>you use <span class="k">for </span>strings? Single
? What line endings <span class="k">do </span>you use? Unix
? Do you require semicolons? Yes
? Are you using ECMAScript 6 features? No
? Where will your code run? Node, Browser
? Do you use JSX? No
? What format <span class="k">do </span>you want your config file to be <span class="k">in</span>? JSON
Successfully created .eslintrc.json file <span class="k">in</span> /Users/<username>/path/to/yourdir
</code></pre></div></div>
<p>我们可以看看刚生成的配置文件:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">cat</span> .eslintrc.json
<span class="o">{</span>
<span class="s2">"rules"</span>: <span class="o">{</span>
<span class="s2">"indent"</span>: <span class="o">[</span>
2,
4
<span class="o">]</span>,
<span class="s2">"quotes"</span>: <span class="o">[</span>
2,
<span class="s2">"single"</span>
<span class="o">]</span>,
<span class="s2">"linebreak-style"</span>: <span class="o">[</span>
2,
<span class="s2">"unix"</span>
<span class="o">]</span>,
<span class="s2">"semi"</span>: <span class="o">[</span>
2,
<span class="s2">"always"</span>
<span class="o">]</span>
<span class="o">}</span>,
<span class="s2">"env"</span>: <span class="o">{</span>
<span class="s2">"node"</span>: <span class="nb">true</span>,
<span class="s2">"browser"</span>: <span class="nb">true</span>
<span class="o">}</span>,
<span class="s2">"extends"</span>: <span class="s2">"eslint:recommended"</span>
<span class="o">}</span>
</code></pre></div></div>
<p>这里,你看到的配置文件有三块内容:<code class="language-plaintext highlighter-rouge">rules</code>、<code class="language-plaintext highlighter-rouge">env</code>和<code class="language-plaintext highlighter-rouge">extends</code>.</p>
<p>其中,<code class="language-plaintext highlighter-rouge">"extends": "eslint:recommended"</code>,指我们使用eslint的推荐配置。你也可以使用自己定制的配置。自定义,也很简单,<a href="https://github.com/feross/eslint-config-standard">这里</a>有个例子,你也可以看<a href="http://eslint.org/docs/developer-guide/shareable-configs">ESLint Shareable Configs</a>的规则,进行了解,简单来说就是分三步:</p>
<ol>
<li>正常创建一个Node.js模块,模块的名字,需要前缀为<code class="language-plaintext highlighter-rouge">eslint-config-</code>(比如全名<code class="language-plaintext highlighter-rouge">eslint-config-myconfig</code>),创建一个<code class="language-plaintext highlighter-rouge">index.js</code>文件,输出你的配置。</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- 比如`index.js`内容如下:
```javascript
module.exports = {
rules: {
quotes: [2, "double"];
}
};
```
</code></pre></div></div>
<ol>
<li>
<p>把这个模块发布到npmjs上供人使用。可以看<a href="https://docs.npmjs.com/getting-started/publishing-npm-packages">这里</a>.</p>
</li>
<li>
<p>使用<code class="language-plaintext highlighter-rouge">npm install</code>安装<code class="language-plaintext highlighter-rouge">eslint-config-myconfig</code>,并在<code class="language-plaintext highlighter-rouge">.eslintrc</code>文件中,通过<code class="language-plaintext highlighter-rouge">extends</code>来使用:</p>
</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>- 比如:
```json
{
"extends": "eslint-config-myconfig"
}
```
- 或者,省略前缀,如下使用:
```json
{
"extends": "myconfig"
}
```
</code></pre></div></div>
<p>继续前面话题,<code class="language-plaintext highlighter-rouge">"env"</code>指程序运行所在环境,可以是:<code class="language-plaintext highlighter-rouge">Node</code>, <code class="language-plaintext highlighter-rouge">Browser</code>,<code class="language-plaintext highlighter-rouge">amd</code>,<code class="language-plaintext highlighter-rouge">mocha</code>,<code class="language-plaintext highlighter-rouge">jasmine</code>,<code class="language-plaintext highlighter-rouge">jquery</code>等等,设定了这些环境后,这些环境的全局变量就可以被识别。你可以在文件中采用注释的方式来告诉单个文件的环境变量,比如:<code class="language-plaintext highlighter-rouge">/*eslint-env node, mocha */</code>.</p>
<p><code class="language-plaintext highlighter-rouge">"rules"</code>,只一些不包括在<code class="language-plaintext highlighter-rouge">"extends"</code>中的配置,或者重新定义<code class="language-plaintext highlighter-rouge">"extends"</code>中部分配置。我们注意到,每个规则的值都是一个数组。数组的第一个值是错误级别,第二个值是规则值。错误级别分三个:<code class="language-plaintext highlighter-rouge">0</code> - 不报错; <code class="language-plaintext highlighter-rouge">1</code> - 告警; <code class="language-plaintext highlighter-rouge">2</code> - 报错。 各种规则的详细说明,可见<a href="http://eslint.org/docs/user-guide/configuring#configuring-rules">ESLint Rules</a>.</p>
<h3 id="32-查错">3.2 查错</h3>
<p>查错命令:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eslint <span class="o">[</span>options] <span class="o">[</span>file|dir]<span class="k">*</span>
</code></pre></div></div>
<p>比如:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eslint file1.js file2.js
</code></pre></div></div>
<p>或者</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eslint lib/<span class="k">**</span>
</code></pre></div></div>
<p>其中,相关参数可以通过命令<code class="language-plaintext highlighter-rouge">eslint -h</code>获知。</p>
<p>欲知各种规则的含义,可以看<a href="http://eslint.org/docs/rules/">ESLint Rules</a>.</p>
<h3 id="33-局部禁止查错">3.3 局部禁止查错</h3>
<p>在实际使用中,有些代码需要局部禁止查错,比如:可能是已经存在的依赖的模块,再或者是调试语句。</p>
<p>这就需要局部禁止查错。支持四种方式:</p>
<ol>
<li>
<p>语句块,全部所有规则</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cm">/* eslint-disable */</span>
<span class="c1">//suppress all warnings between comments</span>
<span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">);</span>
<span class="cm">/* eslint-enable */</span>
</code></pre></div> </div>
</li>
<li>
<p>语句块,禁止指定规则</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="cm">/* eslint-disable no-alert, no-console */</span>
<span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">'</span><span class="s1">bar</span><span class="dl">'</span><span class="p">);</span>
<span class="cm">/* eslint-enable no-alert */</span>
</code></pre></div> </div>
</li>
<li>
<p>单行,禁止所有规则</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// eslint-disable-line</span>
</code></pre></div> </div>
</li>
<li>
<p>单行,禁止指定规则</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">alert</span><span class="p">(</span><span class="dl">'</span><span class="s1">foo</span><span class="dl">'</span><span class="p">);</span> <span class="c1">// eslint-disable-line no-alert</span>
</code></pre></div> </div>
</li>
</ol>
<h3 id="34-修正">3.4 修正</h3>
<p>光发现错误,还不行,程序员希望可以自动修正错误。<code class="language-plaintext highlighter-rouge">ESLint</code>使用<code class="language-plaintext highlighter-rouge">--fix</code>选项,支持对一部分规则进行纠错。命令如下:</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>eslint file.js <span class="nt">--fix</span>
</code></pre></div></div>
<p>执行一下,你就会发现文件内容的缩进按照规则自动纠正了…</p>
<h3 id="35-高级">3.5 高级</h3>
<ol>
<li>支持<a href="http://eslint.org/docs/developer-guide/working-with-rules">自定义规则</a>;</li>
<li>支持<a href="http://eslint.org/docs/developer-guide/working-with-plugins">自定义插件</a>;</li>
<li>支持<a href="http://eslint.org/docs/user-guide/configuring#specifying-parser">自定义解析器</a>.ESLint默认的解析器是<code class="language-plaintext highlighter-rouge">Espree</code>,你还可以选择其他的解析器,比如webpack在用的<code class="language-plaintext highlighter-rouge">Esprima</code>,或者支持<code class="language-plaintext highlighter-rouge">ES2016/ES7</code>的<code class="language-plaintext highlighter-rouge">babel-eslint</code>解析器;</li>
<li>支持通过<a href="http://eslint.org/docs/user-guide/configuring#ignoring-files-and-directories"><code class="language-plaintext highlighter-rouge">.eslintignore</code></a>文件配置不检查某些文件或者目录;</li>
</ol>
<h2 id="四与编辑工具集成">四、与编辑工具集成<a name="editor"></a></h2>
<h3 id="41-webstorm">4.1 WebStorm</h3>
<p>在配置界面: Languanges & Frameworks -> JavaScript -> Code Quality Tools -> ESLint,开启。</p>
<p>Windows系统下:</p>
<p><img src="/images/post/20160202-01.png" alt="webstorm-eslint-enable" /></p>
<h3 id="42-atom">4.2 Atom</h3>
<ol>
<li>
<p>安装atom包(<a href="https://atom.io/packages/linter">linter</a>,<a href="https://atom.io/packages/linter-eslint">linter-eslint</a>):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> apm <span class="nb">install </span>linter linter-eslint
</code></pre></div> </div>
</li>
<li>
<p>在当前项目目录下安装npm包(<a href="https://www.npmjs.com/package/eslint">eslint</a>):</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> npm <span class="nb">install</span> <span class="nt">--save-dev</span> eslint
</code></pre></div> </div>
</li>
</ol>
<h2 id="五airbnb的编码风格与配置">五、airbnb的编码风格与配置<a name="airbnb"></a></h2>
<h3 id="51-javascript编码风格">5.1 JavaScript编码风格</h3>
<p>airbnb在github上分享了他们的的编码规范。可见<a href="https://github.com/airbnb/javascript/blob/master/README.md">Airbnb JavaScript Style Guide</a>. 这个规范,现在已经或者即将,成为国际JavaScript编码规范了。</p>
<h3 id="52-规则配置">5.2 规则配置</h3>
<p>他们除了分享了编码风格,还提供了与之对应的<code class="language-plaintext highlighter-rouge">eslint</code>配置文件。他们提供的配置文件,根据使用环境不同,分三种(总有一款适合你:D):</p>
<ol>
<li>
<p><strong>eslint-config-airbnb</strong></p>
<p>这块默认的配置,包括<code class="language-plaintext highlighter-rouge">EcmaScript 6+</code>和<code class="language-plaintext highlighter-rouge">React</code>。使用如下:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">npm install --save-dev eslint-config-airbnb eslint-plugin-react eslint</code></li>
<li>添加<code class="language-plaintext highlighter-rouge">"extends": "airbnb"</code>至你的<code class="language-plaintext highlighter-rouge">.eslintrc</code>文件。</li>
</ol>
</li>
<li>
<p><strong>eslint-config-airbnb/base</strong></p>
<p>包括<code class="language-plaintext highlighter-rouge">ES6+</code>但是不需要<code class="language-plaintext highlighter-rouge">React</code>,使用如下:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">npm install --save-dev eslint-config-airbnb eslint</code></li>
<li>添加<code class="language-plaintext highlighter-rouge">"extends": "airbnb/base"</code>至你的<code class="language-plaintext highlighter-rouge">.eslintrc</code>文件。</li>
</ol>
</li>
<li>
<p><strong>eslint-config-airbnb/legacy</strong></p>
<p>支持<code class="language-plaintext highlighter-rouge">ES5</code>,使用如下:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">npm install --save-dev eslint-config-airbnb eslint</code></li>
<li>添加<code class="language-plaintext highlighter-rouge">"extends": "airbnb/legacy"</code>至你的<code class="language-plaintext highlighter-rouge">.eslintrc</code>文件。</li>
</ol>
</li>
</ol>
<h3 id="53-小瑕疵">5.3 小瑕疵</h3>
<p>使用了一下,发现他们提供的配置和他们的规范,也有一点小参差。所以,我在使用他们的配置的同时,做了一处修正。<code class="language-plaintext highlighter-rouge">.eslintrc.json</code>如下:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span><span class="w">
</span><span class="err">'extends':</span><span class="w"> </span><span class="err">'airbnb/legacy'</span><span class="p">,</span><span class="w">
</span><span class="err">'rules':</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">'comma-dangle':</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="err">'never'</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<p>是的,他们分享的配置中的规则是<code class="language-plaintext highlighter-rouge">'comma-dangle': [2, 'always-multiline']</code>,真是不习惯。</p>
<h2 id="六参考文献">六、参考文献<a name="reference"></a></h2>
<ul>
<li>http://www.sitepoint.com/comparison-javascript-linting-tools/</li>
<li>http://devnull.guru/get-started-with-eslint/</li>
<li>http://eslint.org/docs/user-guide/configuring</li>
<li>http://eslint.org/docs/user-guide/command-line-interface</li>
<li>http://eslint.org/docs/rules/</li>
<li>https://www.npmjs.com/package/eslint-config-airbnb</li>
<li>https://github.com/kriasoft/react-starter-kit/blob/master/docs/how-to-configure-text-editors.md</li>
</ul>
webpack的几个常用loader
2016-01-19T00:00:00+00:00
http://www.blogways.net/blog/2016/01/19/webpack-loader
<p>使用webpack对原有web工程进行改造,遇到各种问题,适当地使用各种loader加以解决。感觉有些问题具备普遍性,还是有必要记录一下的。故有此文!</p>
<p>在webpack中对javascript的管理也是模块化管理,所以需要使用<code class="language-plaintext highlighter-rouge">require</code>和<code class="language-plaintext highlighter-rouge">exports</code>或者<code class="language-plaintext highlighter-rouge">define</code>来对js模块进行引用或者导出。</p>
<p>当你的前端web工程没有采用js模块化编码,那么就会遇到一些问题。问题的根源分为两种:js的调用者,没有<code class="language-plaintext highlighter-rouge">require</code>;js的提供者,没有<code class="language-plaintext highlighter-rouge">exports</code>。</p>
<p>此时,你一个个文件去修改,按规范添加<code class="language-plaintext highlighter-rouge">require</code>和<code class="language-plaintext highlighter-rouge">exports</code>,当然可以解决这些问题。你也可以使用webpack提供的各种<code class="language-plaintext highlighter-rouge">loader</code>去解决这些问题。</p>
<p>下面,我们分情况讨论解决。</p>
<h2 id="一和jquery相关的那些事儿">一、和jQuery相关的那些事儿</h2>
<p><code class="language-plaintext highlighter-rouge">jQuery</code>本身并没有问题,自身代码里兼容了<code class="language-plaintext highlighter-rouge">exports</code>和<code class="language-plaintext highlighter-rouge">define</code>方法。</p>
<p>只是,很多依赖<code class="language-plaintext highlighter-rouge">jQuery</code>模块的js文件,都没有<code class="language-plaintext highlighter-rouge">require('jquery')</code>,导致文件内的<code class="language-plaintext highlighter-rouge">jQuery</code>对象不可识别。</p>
<p>比如<code class="language-plaintext highlighter-rouge">Bootstrap v3.3.2</code>,走上来,就是如下代码:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="k">typeof</span> <span class="nx">jQuery</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">undefined</span><span class="dl">'</span><span class="p">)</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nb">Error</span><span class="p">(</span><span class="dl">'</span><span class="s1">Bootstrap</span><span class="se">\'</span><span class="s1">s JavaScript requires jQuery</span><span class="dl">'</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>作为模块化调用,当然无法识别<code class="language-plaintext highlighter-rouge">jQuery</code>了。</p>
<p>解决方法有三个:</p>
<h3 id="1-方法一采用import-loader">1. 方法一:采用<code class="language-plaintext highlighter-rouge">import-loader</code></h3>
<p><strong>1.1 解决方法</strong></p>
<p>在加载<code class="language-plaintext highlighter-rouge">bootstrap.js</code>时,使用<code class="language-plaintext highlighter-rouge">imports-loader</code>,
代码如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">imports?jQuery=jquery!bootstrap/dist/js/bootstrap</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div></div>
<p>好了,可以正常使用<code class="language-plaintext highlighter-rouge">bootstrap.js</code>了!</p>
<p><strong>1.2 原理</strong></p>
<p>imports-loader会在bootstrap的源码前面,注入如下代码:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*** IMPORTS FROM imports-loader ***/</span>
<span class="kd">var</span> <span class="nx">jQuery</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">jquery</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>
<p><strong>1.3 使用说明:</strong></p>
<table>
<thead>
<tr>
<th>查询参数</th>
<th>等效代码</th>
</tr>
</thead>
<tbody>
<tr>
<td><code class="language-plaintext highlighter-rouge">angular</code></td>
<td><code class="language-plaintext highlighter-rouge">var angular = require('angular');</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">$=jquery</code></td>
<td><code class="language-plaintext highlighter-rouge">var $ = require('jquery');</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">config=>{size:50}</code></td>
<td><code class="language-plaintext highlighter-rouge">var config = {size:50};</code></td>
</tr>
<tr>
<td><code class="language-plaintext highlighter-rouge">this=>window</code></td>
<td><code class="language-plaintext highlighter-rouge">(function(){...}).call(window);</code></td>
</tr>
</tbody>
</table>
<p>支持一次多个查询参数,之间通过逗号分隔,比如:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">imports?$=jquery,jQuery=jquery,angular,config=>{size:50}!./file.js</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>
<h3 id="2-方法二采用expose-loader">2. 方法二:采用<code class="language-plaintext highlighter-rouge">expose-loader</code></h3>
<p><code class="language-plaintext highlighter-rouge">expose-loader</code>的思路是将某个对象暴露成一个全局变量。</p>
<p>具体到这个问题中,就是把<code class="language-plaintext highlighter-rouge">jQuery</code>对象暴露成全局变量。这样,那些<code class="language-plaintext highlighter-rouge">bootstrap.js</code>之类的文件就都能访问这个变量了。</p>
<p>具体做法,修改<code class="language-plaintext highlighter-rouge">webpack.config.js</code>文件:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">module:</span><span class="w"> </span><span class="p">{</span><span class="w">
</span><span class="err">loaders:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="p">{</span><span class="w"> </span><span class="err">test:</span><span class="w"> </span><span class="err">require.resolve(</span><span class="s2">"jquery"</span><span class="err">)</span><span class="p">,</span><span class="w"> </span><span class="err">loader:</span><span class="w"> </span><span class="s2">"expose?$!expose?jQuery"</span><span class="w"> </span><span class="p">},</span><span class="w">
</span><span class="p">]</span><span class="w">
</span><span class="p">}</span><span class="w">
</span></code></pre></div></div>
<h3 id="3-方法三使用webpackprovideplugin">3. 方法三:使用<code class="language-plaintext highlighter-rouge">webpack.ProvidePlugin</code></h3>
<p>具体做法,修改<code class="language-plaintext highlighter-rouge">webpack.config.js</code>文件:</p>
<div class="language-json highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">plugins:</span><span class="w"> </span><span class="p">[</span><span class="w">
</span><span class="err">new</span><span class="w"> </span><span class="err">webpack.ProvidePlugin(</span><span class="p">{</span><span class="w">
</span><span class="err">$:</span><span class="w"> </span><span class="s2">"jquery"</span><span class="p">,</span><span class="w">
</span><span class="err">jQuery:</span><span class="w"> </span><span class="s2">"jquery"</span><span class="p">,</span><span class="w">
</span><span class="nl">"window.jQuery"</span><span class="p">:</span><span class="w"> </span><span class="s2">"jquery"</span><span class="w">
</span><span class="p">}</span><span class="err">)</span><span class="w">
</span><span class="p">]</span><span class="w">
</span></code></pre></div></div>
<h3 id="4-小节">4. 小节</h3>
<p>从上面的介绍可以看出,三种解决方法的思路分两类:方法一,是修改调用者;方法二、三是修改提供者。</p>
<h2 id="二自定义的js文件">二、自定义的js文件</h2>
<p>这里问题,属于模块提供方没有调用<code class="language-plaintext highlighter-rouge">export</code>导出模块,或者没有<code class="language-plaintext highlighter-rouge">define</code>定义模块。</p>
<p>好办,使用<code class="language-plaintext highlighter-rouge">exports-loader</code>!这个作用和前面提到的<code class="language-plaintext highlighter-rouge">imports-loader</code>类似,他是在文件的最后添加一行,类似如下的代码:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*** EXPORTS FROM exports-loader ***/</span>
<span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="nx">YouModule</span>
</code></pre></div></div>
<p>同样,如果需要一次导出多个对象,如同<code class="language-plaintext highlighter-rouge">imports-loader</code>调用,多个对象间也是逗号分隔。比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require("exports?file,parse=helpers.parse!./file.js");
</code></pre></div></div>
<p>那么,<code class="language-plaintext highlighter-rouge">./file.js</code>文件的最后会添加如下代码:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cm">/*** EXPORTS FROM exports-loader ***/</span>
<span class="nx">exports</span><span class="p">[</span><span class="dl">"</span><span class="s2">file</span><span class="dl">"</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">file</span><span class="p">);</span>
<span class="nx">exports</span><span class="p">[</span><span class="dl">"</span><span class="s2">parse</span><span class="dl">"</span><span class="p">]</span> <span class="o">=</span> <span class="p">(</span><span class="nx">helpers</span><span class="p">.</span><span class="nx">parse</span><span class="p">);</span>
</code></pre></div></div>
<h2 id="三稍微复杂一点的情况">三、稍微复杂一点的情况</h2>
<p>自定义的多个js模块直接存在相互调用的情况。</p>
<p>比如,<code class="language-plaintext highlighter-rouge">b.js</code>里面调用了<code class="language-plaintext highlighter-rouge">a.js</code>提供的对象,但是都没有<code class="language-plaintext highlighter-rouge">exports</code>或者<code class="language-plaintext highlighter-rouge">require</code>。见示例:</p>
<p>文件: <code class="language-plaintext highlighter-rouge">a.js</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">var</span> <span class="nx">A</span> <span class="o">=</span> <span class="p">{</span> <span class="p">...</span> <span class="p">};</span>
</code></pre></div></div>
<p>文件: <code class="language-plaintext highlighter-rouge">b.js</code></p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">A</span><span class="p">.</span><span class="nx">exec</span><span class="p">(....)</span>
</code></pre></div></div>
<p>解决办法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>require([
'expose?A!exports?A!./a.js',
'./b.js'
], ...);
</code></pre></div></div>
<p>如上,把<code class="language-plaintext highlighter-rouge">a.js</code>中的A定义成全景对象公布出去,这样<code class="language-plaintext highlighter-rouge">b.js</code>里面就可以正常使用<code class="language-plaintext highlighter-rouge">A</code>对象了。</p>
<p>除此之外,还有一个解决方案(不推荐):使用<code class="language-plaintext highlighter-rouge">script-loader</code>,它可以在全局环境下执行一次指定的脚本,比如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">require</span><span class="p">([</span>
<span class="dl">'</span><span class="s1">script!./a.js</span><span class="dl">'</span><span class="p">,</span>
<span class="dl">'</span><span class="s1">./b.js</span><span class="dl">'</span>
<span class="p">],</span> <span class="p">...);</span>
</code></pre></div></div>
<p>这里,会把<code class="language-plaintext highlighter-rouge">./a.js</code>直接作为脚本执行一次。在<code class="language-plaintext highlighter-rouge">node.js</code>环境下,<code class="language-plaintext highlighter-rouge">script-loader</code>什么都不做。</p>
<h2 id="四其他">四、其他</h2>
<ol>
<li>
<p><code class="language-plaintext highlighter-rouge">file-loader</code> : 修改文件名,放在输出目录下,并返其对应的 url .</p>
<p>默认修改后的文件名,是文件内容的MD5哈希串。你也可以自定义文件名。比如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=js/[hash].script.[ext]!./javascript.js</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => js/0dcbbaa701328a3c262cfd45869e351f.script.js</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=html-[hash:6].html!./page.html</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => html-109fa8.html</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=[hash]!./flash.txt</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => c31e9820c001c9c4a86bce33ce43b679</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=[sha512:hash:base64:7].[ext]!./image.png</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => gdyb21L.png</span>
<span class="c1">// use sha512 hash instead of md5 and with only 7 chars of base64</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=img-[sha512:hash:base64:7].[ext]!./image.jpg</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => img-VqzT5ZC.jpg</span>
<span class="c1">// use custom name, sha512 hash instead of md5 and with only 7 chars of base64</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=picture.png!./myself.png</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => picture.png</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">file?name=[path][name].[ext]?[hash]!./dir/file.png</span><span class="dl">"</span><span class="p">)</span>
<span class="c1">// => dir/file.png?e43b20c069c4a01867c31e98cbce33c9</span>
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">url-loader</code> : 这个加载器的工作方式很像<code class="language-plaintext highlighter-rouge">file-loader</code>。只是当文件大小小于限制值时,它可以返回一个<code class="language-plaintext highlighter-rouge">Data Url</code>。限制值可以作为查询参数传入。默认不限制。比如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">url?limit=10000!./file.png</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => 如果"file.png"小于10kb,则转成一个DataUrl</span>
<span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">url?mimetype=image/png!./file.png</span><span class="dl">"</span><span class="p">);</span>
<span class="c1">// => 指定文件的mimetype (不指定,则根据文件后缀推测.)</span>
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">style-loader</code>、<code class="language-plaintext highlighter-rouge">css-loader</code>: 这个很常用了,不解释。推荐用法是:<code class="language-plaintext highlighter-rouge">require("style!css!./file.css");</code>,类似的还有<code class="language-plaintext highlighter-rouge">less-loader</code>,推荐使用:<code class="language-plaintext highlighter-rouge">require("style!css!less!./file.less");</code>;</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">raw-loader</code>: 把文件内容作为字符串返回。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">var</span> <span class="nx">fileContent</span> <span class="o">=</span> <span class="nx">require</span><span class="p">(</span><span class="dl">'</span><span class="s1">raw!./file.txt</span><span class="dl">'</span><span class="p">);</span>
</code></pre></div> </div>
<p>这里,把<code class="language-plaintext highlighter-rouge">./file.txt</code>的内容作为字符串返回。</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">html-loader</code>: 把Html文件输出成字符串。与<code class="language-plaintext highlighter-rouge">raw-loader</code>不同的是,它默认处理html中的<code class="language-plaintext highlighter-rouge"><img src="image.png"></code>为<code class="language-plaintext highlighter-rouge">require("./image.png")</code>,你同时需要在你的配置中指定image文件的加载器,比如:<code class="language-plaintext highlighter-rouge">url-loader</code>或者<code class="language-plaintext highlighter-rouge">file-loader</code>。</p>
<ul>
<li>
<p>你可以通过加载器的查询参数<code class="language-plaintext highlighter-rouge">attrs</code>来指定哪些html标签可以被处理。比如:<code class="language-plaintext highlighter-rouge">attrs=img:src</code>。<code class="language-plaintext highlighter-rouge">html-loader</code>默认只处理图片,你也可以通过<code class="language-plaintext highlighter-rouge">attrs</code>,告诉他也处理javascript文件,比如:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">html?attrs=attrs=script:src img:src!./file.html</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div> </div>
<p>或者</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">html?attrs[]=script:src&attrs[]=img:src!./file.html</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div> </div>
<p>多个标签用空格分隔或者使用数组设定。</p>
</li>
<li>
<p>你也可以告诉加载器,什么都不转换,包括默认处理的<code class="language-plaintext highlighter-rouge">image</code>。如下设置:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">html?attrs=false!./file.html</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div> </div>
<p>或者</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">require</span><span class="p">(</span><span class="dl">"</span><span class="s2">html?-attrs!./file.html</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div> </div>
</li>
<li>
<p>当使用<code class="language-plaintext highlighter-rouge">webpack --optimize-minimize</code>时,<code class="language-plaintext highlighter-rouge">html-loader</code>会对加载的html内容最小化。</p>
</li>
</ul>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">source-map-loader</code>: 方便调试,不解释。</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">module</span><span class="p">.</span><span class="nx">exports</span> <span class="o">=</span> <span class="p">{</span>
<span class="na">module</span><span class="p">:</span> <span class="p">{</span>
<span class="na">preLoaders</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="na">test</span><span class="p">:</span> <span class="sr">/</span><span class="se">[\.</span><span class="sr">-</span><span class="se">]</span><span class="sr">min</span><span class="se">\.</span><span class="sr">js$/</span><span class="p">,</span>
<span class="na">loader</span><span class="p">:</span> <span class="dl">"</span><span class="s2">source-map-loader</span><span class="dl">"</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
<span class="p">};</span>
</code></pre></div> </div>
<p>上例,是对后缀为<code class="language-plaintext highlighter-rouge">.min.js</code>或者<code class="language-plaintext highlighter-rouge">-min.js</code>结尾的文件,做<code class="language-plaintext highlighter-rouge">source-map</code>操作。不过,你要确保<code class="language-plaintext highlighter-rouge">min.js</code>文件中按规范指定了<code class="language-plaintext highlighter-rouge">//# sourceMappingURL=xxxx.map</code>才行!</p>
</li>
</ol>
<h2 id="五总结">五、总结</h2>
<p>我们讨论了常用的<code class="language-plaintext highlighter-rouge">loader</code>,包括:<code class="language-plaintext highlighter-rouge">imports-loader</code>、<code class="language-plaintext highlighter-rouge">exports-loader</code>、<code class="language-plaintext highlighter-rouge">expose-loader</code>、<code class="language-plaintext highlighter-rouge">script-loader</code>、<code class="language-plaintext highlighter-rouge">file-loader</code>、<code class="language-plaintext highlighter-rouge">url-loader</code>、<code class="language-plaintext highlighter-rouge">css-loader</code>、<code class="language-plaintext highlighter-rouge">style-loader</code>、<code class="language-plaintext highlighter-rouge">raw-loader</code>、<code class="language-plaintext highlighter-rouge">html-loader</code>、<code class="language-plaintext highlighter-rouge">source-map-loader</code>。</p>
<p>官网还提供了一些其他的<code class="language-plaintext highlighter-rouge">loader</code>,你也可以根据个性化需要,自定义一个<code class="language-plaintext highlighter-rouge">loader</code>。这里就不再继续讨论了。</p>
<p>更多<code class="language-plaintext highlighter-rouge">webpack</code>的知识可以从官网获取。网上也有一些不错的第三方入门的博文,比如下面两个:</p>
<ul>
<li><a href="https://github.com/petehunt/webpack-howto">github.com/petehunt/webpack-howto</a></li>
<li><a href="http://christianalfoni.github.io/react-webpack-cookbook/">christianalfoni.github.io/react-webpack-cookbook/</a></li>
</ul>
lamp下安装joomla和joomla模板
2016-01-07T00:00:00+00:00
http://www.blogways.net/blog/2016/01/07/joomla-templates-install
<h2 id="一joomla简介">一、joomla简介</h2>
<p>joomla是一个内容管理系统,Joomla是使用PHP语言加上MySQL数据库所开发的软件系统,可以在Linux、 Windows、MacOSX等各种不同的平台上执行。</p>
<h2 id="二joomla安装环境">二、joomla安装环境</h2>
<p>lamp环境,先前安装wordpress时已经在主机上搭建好。</p>
<h2 id="三安装joomla">三、安装joomla</h2>
<h3 id="1下载安装包">1.下载安装包</h3>
<p>进入官网下载<a href="https://www.joomla.org/download.html">https://www.joomla.org/download.html</a></p>
<p>安装包Joomla_3.4.8-Stable-Full_Package.zip</p>
<h3 id="2解压并上传到指定根目录">2.解压,并上传到指定根目录</h3>
<p><code class="language-plaintext highlighter-rouge">sudo scp -r joomla/ spdev@10.20.16.79:/var/www/html</code></p>
<h3 id="3建立数据库">3.建立数据库</h3>
<p>在安装之前最好先建立一个数据库,在安装网站时会用到。</p>
<p>建库地址:<a href="10.20.16.79/phpmyadmin/">10.20.16.79/phpmyadmin/</a></p>
<h3 id="4进入安装">4.进入安装</h3>
<p>安装地址:<a href="10.20.16.79/joomla/">10.20.16.79/joomla/</a></p>
<p>进入安装界面开始安装:</p>
<p><strong>(1) 第一步是基本配置</strong></p>
<p>选择语言,输入网站的名称,管理员的邮箱,用户名和密码,然后点击下一步。</p>
<p><strong>(2) 第二步是配置网站的数据库</strong></p>
<p>数据库类型是默认的MySQLi,数据库的主机名:localhost,数据库的用户名:root,数据库名:joomla(提前用phpmyadmin为Joomla创建一个数据库)。Joomla会随机自动我们添加一个数据表前缀。</p>
<p><strong>注意:</strong></p>
<p>旧数据库的处理这里,如果你现在使用的数据库里以前安装过Joomla,并且你正在安装的Joomla与之前的Joomla的数据表前缀是一样的。那么这里的设置可能影响到之前安装的Joomla的数据库。选择备份或者删除以前的数据表。设置好以后点击下一步。</p>
<p><strong>(3) ftp设置</strong></p>
<p>可以跳过,点下一步。</p>
<p><strong>(4) 第四步安装选择</strong></p>
<p>选择安装示范数据的类型。</p>
<p><strong>(5) 检查配置</strong></p>
<p>预览安装之前填入的一些信息,另外还有服务器环境的相关的配置,如果有某些配置不符合Joomla的要求,需要修改服务器的配置。</p>
<p><strong>(6) 点击安装</strong></p>
<p>安装成功会提示删除installation目录,需手动删除。
同时安装成功也会显示警告:在创建配置文件的过程中发生错误,根据警告提示要在网站根目录下创建configuration.php文件,代码在警告下的代码框中可供复制。</p>
<p>测试安装成果:<a href="10.20.16.79/joomla">10.20.16.79/joomla</a></p>
<p>后台的地址:<a href="10.20.16.79/joomla/administrator/">10.20.16.79/joomla/administrator/</a></p>
<h2 id="四安装joomla模板">四、安装joomla模板</h2>
<h3 id="1快速安装">1.快速安装</h3>
<p>从weidea.net网站上下载的joomla模板都会提供快速安装包。只需把安装包解压到<code class="language-plaintext highlighter-rouge">/var/www/html</code>目录,安装过程和安装joomla是一样的。</p>
<p><strong>要特别注意的是:</strong></p>
<p>对于网站的根目录,文件或者是文件夹的拥有者不能是root和用户组不能是root,如果网站的根目录和根目录下面的文件和目录的拥有者是root的话,会导致不能网站正常的运行,出现500服务器错误。</p>
<p>进入网站的根目录,输入<code class="language-plaintext highlighter-rouge">chmod -R 775 html</code>,网站方可正常运行。</p>
<h3 id="2登录joomla后台安装">2.登录joomla后台安装</h3>
<p>(1) 主菜单下点击Extensions,在下拉列表中点击manage,会看到<code class="language-plaintext highlighter-rouge">Upload & Install Joomla Extension</code>这里提供了一个入口可以上传joomla主题模板包。</p>
<p>(2)如果下载下来的joomla模板文件里有框架包,则必须在上传主题前先上传框架包,框架是主题能得以应用的前提。</p>
<p>(3) 在同样的入口,上传插件包。这些插件使得主题某些特效能显示的关键。</p>
<p><strong>个人体会:</strong>以上两种安装joomla模板的方法,推荐用第一种方法,比较靠谱。</p>
在Drupal上部署主题
2016-01-05T00:00:00+00:00
http://www.blogways.net/blog/2016/01/05/drupal-arrange-themes
<h1 id="在drupal上部署主题">在Drupal上部署主题</h1>
<h2 id="一简单粗暴法profile文件">一.简单粗暴法(profile文件)</h2>
<p>以Avira主题为例</p>
<h4 id="1系统需求">1.系统需求</h4>
<ul>
<li>
<p>Drupal 7</p>
</li>
<li>
<p>主题文件:avira_install_profile.zip</p>
</li>
<li>
<p>Web server:Apache,Nginx</p>
</li>
<li>
<p>PHP 5.2.5 or higher</p>
</li>
<li>
<p>MySQL 5.0.15 or higher</p>
</li>
</ul>
<h4 id="2主题安装">2.主题安装</h4>
<p>将主题文件解压到服务器目录下,用浏览器打开该目录,接下来的步骤和安装drupal的步骤类似,可根据视频所示安装即可。</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/I9sScpIcO7E" frameborder="0" allowfullscreen=""></iframe>
<blockquote>
<p>该视屏在youtobe上,可能需要翻墙观看</p>
</blockquote>
<h2 id="二修改数据库法">二.修改数据库法</h2>
<ol>
<li>
<p>进入phpmyadmin,新建数据库,当然也可以使用命令新建数据库。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mysql -u root -p
create database Ariva;
</code></pre></div> </div>
</li>
<li>
<p>进入该数据库,将主题的数据库文件导入数据库。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> use Ariva;
source /data/spdev/chenfan/Ariva/Ariva.sql;
</code></pre></div> </div>
</li>
<li>
<p>然后把主题包解压放到服务器目录下,修改主题配置文件。主题配置文件在主题目录/sites/default/setting.php,并且修改该文件。然后用浏览器打开该主题目录即可</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> sudo vi /data/spdev/chenfan/Ariva/sites/default/settings.php
</code></pre></div> </div>
</li>
</ol>
<p> 修改下图部分:</p>
<p><img src="\images\post\database.jpg" alt="database" /></p>
<h2 id="三在已有drupal中安装主题">三.在已有drupal中安装主题</h2>
<ol>
<li>
<p>下载主题文件</p>
</li>
<li>
<p>使用FTP工具将其放在drupal的<code class="language-plaintext highlighter-rouge">sites/all/themes/</code>目录下</p>
</li>
<li>
<p>打开drupal admin toolbar,点击 Appearance,找到导入的主题</p>
</li>
<li>
<p>然后点击主题下的Enable and set default,设置成默认主题</p>
</li>
<li>
<p>点击Theme settings,可以设置一些主题的基本操作</p>
</li>
</ol>
<h2 id="四在已有的drupal中部署主题">四.在已有的drupal中部署主题</h2>
<p>在三中安装后的主题,是没有任何内容,这时我们需要加载一些模块来充实内容,使用这种方式也可以比较自由的设计自己心目当中想要的模块</p>
<p>因为这里的内容较多,我只对常用的几大模块做些介绍</p>
<h4 id="1slider幻灯片模块">1.slider(幻灯片模块)</h4>
<p>在<a href="http://your-site.com/admin/modules">drupal官网</a>下载 jquery_update、imce、Layer slider,并将其解压放到<code class="language-plaintext highlighter-rouge">sites/all/modules/</code>中</p>
<p>打开 drupal admin toolbar ,点击Modules,使下载的模块enable</p>
<p>然后在drupal admin toolbar 中会显示layer slider,点击设计幻灯片</p>
<p>在Home » Administration » Structure 中将该幻灯片放在你想放的位置,一般我们作为首页展示,会放在主页位置</p>
<p>如下图所示</p>
<p><img src="\images\post\slider.jpg" alt="slider" /></p>
<p>便可以在主页前端看到出现该幻灯片</p>
<h4 id="2菜单栏">2.菜单栏</h4>
<p>在<a href="http://your-site.com/admin/modules">drupal官网</a>下载 Superfish 模块在设计菜单栏,并将其解压放到<code class="language-plaintext highlighter-rouge">sites/all/modules/</code>中</p>
<p>打开 drupal admin toolbar ,点击Modules,使下载的模块enable</p>
<p>在Home » Administration » Structure 中,有 “Superfish 1 (Superfish)” 点击”Configure”</p>
<ul>
<li>
<p>在模块 title field 输入 : <none></none></p>
</li>
<li>
<p>在模块 description 输入: Main menu</p>
</li>
<li>
<p>在 Region settings -> Nevia 选择 “Main menu”</p>
</li>
<li>
<p>拉到底部点击”save block”</p>
</li>
</ul>
<h4 id="3主页模块">3.主页模块</h4>
<p>打开 drupal admin toolbar ,点击Content,然后add content,选择Basic page在这里你可以添加自己的页面,然后在settings里连接到想到链接的页面</p>
<h4 id="4blog">4.blog</h4>
<p>在<a href="http://your-site.com/admin/modules">drupal官网</a>下载 blog page 模块在设计菜单栏,并将其解压放到<code class="language-plaintext highlighter-rouge">sites/all/modules/</code>中</p>
<p>打开 drupal admin toolbar ,点击Modules,使下载的模块enable</p>
<p>然后点击Content ,add content选择blog,该模块可以添加标签等功能</p>
Drupal安装教程
2016-01-04T00:00:00+00:00
http://www.blogways.net/blog/2016/01/04/drupal-install
<h1 id="drupal安装教程">Drupal安装教程</h1>
<h2 id="一下载drupal">一.下载drupal</h2>
<p>访问Drupal官方网站下载Drupal程序(http://drupal.org/download),同时把下载到的压缩包进行解压并上传到空间根目录,在浏览器中输入网站域名,将会自动进入Drupal的安装界面。</p>
<h2 id="二新建数据库">二.新建数据库</h2>
<p>打开phpmyadmin,点击新建数据库,名字为drupal</p>
<p><img src="\images\post\shujuku.jpg" alt="shujuku" /></p>
<h2 id="三选择安装类型">三.选择安装类型</h2>
<p>如图,标准Standard和迷你Minimal,区别是安装系统模块的多少,如果只是普通文章应用选择Minimal即可,此处选择标准型,然后点击Save and continue进入下一步。</p>
<p><img src="\images\post\profile.jpg" alt="shujuku" /></p>
<h2 id="四选择程序的语言">四.选择程序的语言,</h2>
<p>默认即提供英文版,如需其它语言,点击“Learn how to install Drupal in other languages”进入新界面</p>
<p><img src="\images\post\yuyan.jpg" alt="shujuku" /></p>
<blockquote>
<p>如需要安装中文可以下载根据说明下载,并将下载的文件放到/profiles/standard/translations/中即可</p>
</blockquote>
<p><img src="\images\post\zhongwen.jpg" alt="shujuku" /></p>
<h2 id="五安装需求">五.安装需求</h2>
<p>选择语言后,点击确定,会出现下图需求问题</p>
<p><img src="\images\post\requirement.jpg" alt="xuqiu" /></p>
<p>首先目录sites/default/files 不存在,则需要在该目录下建立该文件夹。</p>
<p>进入sites/default,输入:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$mkdir files
</code></pre></div></div>
<p>并将该文件权限设置为可写</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$sudo chmod -R 777 files
</code></pre></div></div>
<p>然后复制 ./sites/default/default.settings.php 文件到./sites/default/settings.php</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cp default.settings.php settings.php
</code></pre></div></div>
<p>并将其设置成可写</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$sudo chmod 777 settings.php
</code></pre></div></div>
<h2 id="六设置数据库">六.设置数据库</h2>
<p>设置数据库信息,根据提示设置MYSQL或sqlite数据库的用户名、数据库名和密码。Localhost一般保持默认即可,数据库端口和表前缀没有强制要求,如果同一数据库下安装多个Drupal,要设置表前缀。</p>
<p><img src="\images\post\mysql1.jpg" alt="mysql1" /></p>
<p><img src="\images\post\mysql2.jpg" alt="mysql2" /></p>
<p>进入下一步,系统会自动安装Drupal相关模块,等待安装完成</p>
<h2 id="七设置站点信息">七.设置站点信息</h2>
<p>根据自己的需要自主设置</p>
<p><img src="\images\post\site.jpg" alt="site" /></p>
<h2 id="八安装完成">八.安装完成</h2>
<p>drupal安装完成后会出现如下界面</p>
<p><img src="\images\post\site.jpg" alt="Drupal_Complete" /></p>
<h2 id="九常见问题">九.常见问题</h2>
<p>1.在安装数据库时,没有mysql选项</p>
<p>原因:在没有php没有打开mysql扩展</p>
<p>解决办法:进入php的安装目录</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /usr/local/bin/phpize
./configure --with-php-config=/usr/local/bin/php-config --with-mysql=/usr/local/mysql/
make
make install
</code></pre></div></div>
<blockquote>
<p>在<code class="language-plaintext highlighter-rouge">mysql/modules</code>下会看到mysql.so</p>
</blockquote>
<p>修改php.ini</p>
<p>将<code class="language-plaintext highlighter-rouge">extensions = "mysql.so"</code>前的分号去掉,打开mysql扩展</p>
<p>将mysql.so拷贝到extension_dir目录下,extension_dir在php.ini中设置</p>
<p>重启apache即可</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apacheclt restart
</code></pre></div></div>
linux下源码安装wordpress全过程
2015-12-22T00:00:00+00:00
http://www.blogways.net/blog/2015/12/22/wordpress-install
<h2 id="搭建lamp环境">搭建lamp环境</h2>
<h3 id="一-准备安装包">一 准备安装包</h3>
<h4 id="库文件">库文件:</h4>
<ol>
<li>libxml2-2.6.30.tar.gz</li>
<li>libmcrypt-2.5.8.tar.gz</li>
<li>zlib-1.2.3.tar.gz</li>
<li>libpng-1.2.31.tar.gz</li>
<li>jpegsrc.v6b.tar.gz</li>
<li>freetype-2.3.5.tar.gz</li>
<li>autoconf-2.61.tar.gz</li>
<li>gd-2.0.35.tar.gz</li>
<li>apr-1.4.6.tar.gz</li>
<li>apr-util-1.5.1.tar.gz</li>
</ol>
<h4 id="主要文件">主要文件:</h4>
<ol>
<li>httpd-2.2.9.tar.gz</li>
<li>mysql-5.1.59.tar.gz</li>
<li>php-5.2.6.tar.gz</li>
<li>phpMyAdmin-3.0.0</li>
</ol>
<h4 id="将安装包都上传到usrlocalsrc目录">将安装包都上传到/usr/local/src目录</h4>
<h3 id="二-安装apache前期准备库文件安装">二 安装apache前期准备(库文件安装)</h3>
<h4 id="1-解包">1. 解包</h4>
<h5 id="编写一个shell脚本tarsh进行解包">编写一个shell脚本tar.sh进行解包。</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/sh
cd /usr/local/src
ls *.tar.gz > ls.list
for TAR in ’cat ls.list‘
do
tar -zxvf $TAR
done
</code></pre></div></div>
<h5 id="执行脚本tarsh进行解包">执行脚本tar.sh进行解包</h5>
<h4 id="2-按顺序安装">2. 按顺序安装</h4>
<h5 id="安装libxml2">【安装libxml2】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd libxml2-2.6.30
./configure --prefix=/usr/local/libxml2/
make
make install
</code></pre></div></div>
<h5 id="安装libmcrypt">【安装libmcrypt】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd libmcrypt-2.5.8
./configure --enable-ltdl-install
make
make install
</code></pre></div></div>
<h5 id="安装zlib">【安装zlib】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd zlib-1.2.3
./configure
make
make install
</code></pre></div></div>
<h5 id="安装libpng">【安装libpng】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd libpng-1.2.31
./configure --prefix=/usr/local/libpng/
make
make install
</code></pre></div></div>
<h5 id="安装jpegsrcv6b">【安装jpegsrc.v6b】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir /usr/local/jpeg6
mkdir /usr/local/jpeg6/bin
mkdir /usr/local/jpeg6/lib
mkdir /usr/local/jpeg6/include
mkdir -p /usr/local/jpeg6/man/man1
cd jpeg-6b
./configure --prefix=/usr/local/jpeg6/ --enable-shared --enable-static
make
make install
</code></pre></div></div>
<h5 id="安装freetype">【安装freetype】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd freetype-2.3.5
./configure --prefix=/usr/local/freetype/
make
make install
</code></pre></div></div>
<h5 id="安装autoconf">【安装autoconf】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd autoconf-2.61
./configure
make
make install
</code></pre></div></div>
<h5 id="安装gd">【安装gd】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd gd-2.0.35
./configure --prefix=/usr/local/gd2/ --with-jpeg=/usr/local/jpeg6/ --with-freetype=/usr/local/freetype/
make
make install
</code></pre></div></div>
<h5 id="安装apr">【安装apr】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure
make
make install
</code></pre></div></div>
<h5 id="安装apr-util">【安装apr-util】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure --with-apr=/usr/local/apr/
make
make install
</code></pre></div></div>
<h3 id="三-安装apache">三 安装apache</h3>
<h4 id="apache安装与配置">apache安装与配置</h4>
<h5 id="安装apache">【安装apache】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd httpd-2.2.9
./configure --prefix=/usr/local/apache2/ --sysconfdir=/etc/httpd/ --with-included-apr --disable-userdir --enable-so --enable-deflate=shared --enable-expires=shared --enable-rewrite=shared --enable-static-support
make
make install
</code></pre></div></div>
<h6 id="启动apache">启动apache</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/local/apache2/bin/apachectl start
</code></pre></div></div>
<h6 id="修改配置文件">修改配置文件</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /etc/httpd/httpd.conf
#查找ServerName,将注释去掉
ServerName 'www.example.com:80'
</code></pre></div></div>
<h6 id="将apache添加到系统服务中">将apache添加到系统服务中</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp /usr/local/apache2/bin/apachectl /etc/init.d/httpd
vi /etc/rc.d/init.d/httpd
#在#!/bin/sh后添加下面两行(包含"#")
# chkconfig:2345 85 15
# description:Apache
#添加执行权限
chmod 755 /etc/init.d/httpd
#添加到系统服务中
chkconfig --add httpd
</code></pre></div></div>
<h6 id="开启apache">开启apache</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service httpd start
</code></pre></div></div>
<h3 id="四-安装mysql">四 安装mysql</h3>
<h4 id="mysql安装与配置">mysql安装与配置</h4>
<h5 id="安装mysql">【安装mysql】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>groupadd mysql
useradd -g mysql mysql
cd mysql-5.1.59
./configure --prefix=/usr/local/mysql/ --with-extra-charsets=all
make
make install
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp support-files/my-medium.cnf /etc/my.cnf
/usr/local/mysql/bin/mysql_install_db --user=mysql
chown -R root /usr/local/mysql
chown -R mysql /usr/local/mysql/var
chgrp -R mysql /usr/local/mysql
/usr/local/mysql/bin/mysqld_safe --user=mysql &
cp /lamp/src/mysql-5.1.59/support-files/mysql.server /etc/rc.d/init.d/mysqld
chown root.root /etc/rc.d/init.d/mysqld
chmod 755 /etc/rc.d/init.d/mysqld
chkconfig --add mysqld
chkconfig --list mysqld
chkconfig --levels 245 mysqld off
</code></pre></div></div>
<h6 id="配置mysql">配置mysql</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /usr/local/mysql
bin/mysqladmin version //简单的测试
bin/mysqladmin Variables //查看所有mysql参数
bin/mysql -uroot //没有密码可以直接登录本机服务器
DELETE FROM mysql.user WHERE Host='localhost' AND User='';
FLUSH PRIVILEGES;
#设置root密码为123456
SET PASSWORD FOR 'root'@'localhost' = PASSWORD('123456');
#配置可远程连接mysql
use mysql
SELECT user,password,host FROM user;
DELETE FROM user WHERE host='localhsot.localdomain'
DELETE FROM user WHERE host='10.20.16.79';
UPDATE user SET host='%' WHERE user='root';
</code></pre></div></div>
<h6 id="重启mysql">重启mysql</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service mysqld restart
</code></pre></div></div>
<h3 id="五-安装php">五 安装php</h3>
<h4 id="php安装与配置">php安装与配置</h4>
<h5 id="安装php">【安装php】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd php-5.2.6
./configure --prefix=/usr/local/php/ --with-config-file-path=/usr/local/php/etc/ --with-apxs2=/usr/local/apache2/bin/apxs --with-mysql=/usr/local/mysql/ --with-libxml-dir=/usr/local/libxml2/ --with-jpeg-dir=/usr/local/jpeg6/ --with-freetype-dir=/usr/local/freetype/ --with-gd=/usr/local/gd2/ --with-mcrypt=/usr/local/libmcrypt/ --with-mysqli=/usr/local/mysql/bin/mysql_config --enable-soap --enable-mbstring=all --enable-sockets
make
make install
</code></pre></div></div>
<h6 id="创建配置文件">创建配置文件</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># cp php.ini-dist /usr/local/php/etc/php.ini
</code></pre></div></div>
<h6 id="使用vi编辑apache配置文件">使用vi编辑apache配置文件</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># vi /etc/httpd/httpd.conf
</code></pre></div></div>
<p>添加这一条代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Addtype application/x-httpd-php .php .phtml
</code></pre></div></div>
<h6 id="重启apache">重启Apache</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># /usr/local/apache2/bin/apachectl restart
</code></pre></div></div>
<h5 id="apache配置">【apache配置】</h5>
<p>######建立工作目录</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir -p /var/www/html
</code></pre></div></div>
<h6 id="修改httpdconf">修改httpd.conf</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /etc/httpd/httpd.conf
#功能: 设置工作目录
#说明: 搜索DocumentRoot, 修改为
DocumentRoot "/var/www/html"
#功能: 设置目录选项
搜索<Directory "/usr/local/apache2//htdocs">, 修改为
<Directory "/var/www/html">
#功能: 设置默认文档
搜索<IfModule dir_module>, 修改为
DirectoryIndex index.html index.php
#功能: 增加php类型
搜索 AddType application/x-gzip .gz .tgz在后面添加
AddType application/x-httpd-php .html .php
功能: 不允许访问目录
说明: 搜索Options Indexes FollowSymLinks项并注释
#Options Indexes FollowSymLinks
#注意: 修改配置文件后, 重启apache才能生效
</code></pre></div></div>
<h6 id="重启apache-1">重启apache</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service httpd restart
</code></pre></div></div>
<h5 id="添加pdo_mysql扩展">【添加PDO_MYSQL扩展】</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /lamp/src/php-5.2.6/ext/pdo_mysql
/usr/local/php/bin/phpize
./configure --with-php-config=/usr/local/php/bin/php-config --with-pdo-mysql=/usr/local/mysql
make
make install
</code></pre></div></div>
<h6 id="执行完make-install后会生成">执行完make install后会生成</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#Installing shared extensions: /usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/
</code></pre></div></div>
<h6 id="修改phpini">修改php.ini</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vi /usr/local/php/etc/php.ini
#查找extension_dir,修改为
extension_dir = "/usr/local/php/lib/php/extensions/no-debug-non-zts-20060613/"
#添加pdo_mysql
extension = pdo_mysql.so
</code></pre></div></div>
<p>######重启apache</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>service httpd restart
</code></pre></div></div>
<h3 id="六-安装phpmyadmin">六 安装phpmyadmin</h3>
<h4 id="phpmyadmin安装与配置">phpmyadmin安装与配置</h4>
<h5 id="安装phpmyadmin">【安装phpmyadmin】</h5>
<h6 id="拷贝目录到指定位置并改名为phpmyadmin">拷贝目录到指定位置并改名为phpmyadmin</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#cp -a phpMyAdmin-3.0.0-rc1-all-languages /var/www/html/phpmyadmin
#cd /var/www/html/phpmyadmin/
#cp config.sample.inc.php config.inc.php
</code></pre></div></div>
<h5 id="配置phpmyadmin">配置phpMyAdmin</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#vi /var/www/html/phpmyadmin/config.inc.php
</code></pre></div></div>
<p>将auth_type 改为http</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cfg['Servers'][$i]['auth_type'] = 'http';
</code></pre></div></div>
<h5 id="测试">测试</h5>
<h6 id="编写infophp文件查看php配置详细">编写info.php文件,查看php配置详细</h6>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> vi /var/www/html/info.php
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?php
phpinfo();
?>
</code></pre></div></div>
<h5 id="通过浏览器访问http10201679infophp获得php的详细配置信息">通过浏览器访问http://10.20.16.79/info.php,获得php的详细配置信息</h5>
<h2 id="安装wordpress">安装wordpress</h2>
<ol>
<li>将解压后的wordpress移动到/var/www/html目录下</li>
<li>在浏览器中访问 http://localhost/wordpress/wp-admin/install.php,会访问失败,问题解决方法是修改wp-config.php配置信息,提供数据库的名(wordpress),用户名(root),密码()。然后刷新浏览器就能成功访问</li>
<li>根据提示注册账号,登录即可进入</li>
</ol>
<h2 id="安装过程参考网址">安装过程参考网址</h2>
<ol>
<li><a href="http://www.cnblogs.com/BTMaster/p/3551073.html">http://www.cnblogs.com/BTMaster/p/3551073.html</a></li>
<li><a href="http://www.cnblogs.com/mchina/archive/2012/11/28/2778779.html">http://www.cnblogs.com/mchina/archive/2012/11/28/2778779.html</a></li>
</ol>
公司主机装载apache+PHP+phpmyadmin+wordpress
2015-12-22T00:00:00+00:00
http://www.blogways.net/blog/2015/12/22/wordpress-install-78
<h1 id="wordpress-公司主机安装教程">wordpress 公司主机安装教程</h1>
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">介绍</a></li>
<li><a href="#2">安装前准备</a></li>
<li><a href="#3">配置变量环境</a></li>
<li><a href="#4">安装Apache</a></li>
<li><a href="#5">安装MySql</a></li>
<li><a href="#6">安装PHP</a></li>
<li><a href="#7">安装phpMyAdmin</a></li>
<li><a href="#8">安装WordPress</a></li>
</ol>
</dd>
</dl>
</div>
<h2 id="一-介绍">一.<a name="1"></a> 介绍</h2>
<p> WordPress是一个注重美学、易用性和网络标准的个人信息发布平台。WordPress虽为免费的开源软件,但其价值无法用金钱来衡量。当前WordPress插件数据库中有超过18000个插件,包括SEO、控件等等。个人可以根据它的核心程序提供的规则自己开发模板和插件。这些插件可以快速地把你的博客改变成cms、forums、门户等各种类型的站点。</p>
<h2 id="二-安装前准备">二. <a name="2"></a>安装前准备</h2>
<p>Apache版本:</p>
<ul>
<li>httpd-2.4.17.tar.gz</li>
</ul>
<p>Mysql 版本:</p>
<ul>
<li>mysql-5.0.41.tar.gz</li>
</ul>
<p>Php版本:</p>
<ul>
<li>php-7.0.0.tar.gz</li>
</ul>
<p>库文件准备:</p>
<ul>
<li>autoconf-2.61.tar.gz</li>
<li>freetype-2.3.5.tar.gz</li>
<li>gd-2.1.1.tar.gz<br /></li>
<li>jpegsrc.v6b.tar.gz</li>
<li>libmcrypt-2.5.8.tar.gz</li>
<li>libpng-1.2.31.tar.gz</li>
<li>libxml2-2.6.30.tar.gz</li>
<li>zlib-1.2.3.tar.gz</li>
<li>phpMyAdmin-4.5.2-rc1-all-languages.tar.gz</li>
</ul>
<h2 id="三-配置变量环境">三. <a name="3"></a>配置变量环境</h2>
<h3 id="1-安装libxml2">1. 安装libxml2</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/libxml2-2.6.30
$./configure --prefix=/usr/local/libxml2
$make && make install
</code></pre></div></div>
<h3 id="2-安装libmcrypt">2. 安装libmcrypt</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/libmcrypt-2.5.8
$./configure --prefix=/usr/local/libmcrypt
$make && make install
</code></pre></div></div>
<h3 id="3-安装zlib">3. 安装zlib</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/zlib-1.2.3
$./configure
$make && make install
</code></pre></div></div>
<h3 id="4-安装libpng">4. 安装libpng</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/libpng-1.2.31
$./configure --prefix=/usr/local/libpng
$make && make install
</code></pre></div></div>
<h3 id="5-安装jpeg6">5. 安装jpeg6</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$mkdir /usr/local/jpeg6
$mkdir /usr/local/jpeg6/bin
$mkdir /usr/local/jpeg6/lib
$mkdir /usr/local/jpeg6/include
$mkdir -p /usr/local/jpeg6/man/ma
</code></pre></div></div>
<blockquote>
<p>这个软件包安装有些特殊,其它软件包安装时如果目录不存在,会自动创建,但这个软件包安装时需要手动创建。</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/jpeg-6b
$./configure --prefix=/usr/local/jpeg6/ --enable-shared --enable-static
$make && make install
</code></pre></div></div>
<h3 id="6-安装freetype">6. 安装freetype</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/freetype-2.3.5
$./configure --prefix=/usr/local/freetype
$make
$make install
</code></pre></div></div>
<h3 id="7-安装autoconf">7. 安装autoconf</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/autoconf-2.61
$./configure
$make && make install
</code></pre></div></div>
<h3 id="8-安装gd库">8. 安装GD库</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/gd-2.1.1
$./configure \
--prefix=/usr/local/gd2/ \
--enable-m4_pattern_allow \
--with-zlib=/usr/local/zlib/ \
--with-jpeg=/usr/local/jpeg6/ \
--with-png=/usr/local/libpng/ \
--with-freetype=/usr/local/freetype/
$make
$make install
</code></pre></div></div>
<p><strong>1)</strong> 执行 make 报错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make[2]: *** [gd_png.lo] Error 1
make[2]: Leaving directory /usr/local/src/gd-2.1.1
make[1]: *** [all-recursive] Error 1
make[1]: Leaving directory /usr/local/src/gd-2.1.1'
make: *** [all] Error 2
</code></pre></div></div>
<p><strong>分析</strong>:这个问题是由于 gd 库中的 gd_png.c 源文件包含 png.h 时,png.h 没有找到导致的。</p>
<p><strong>解决</strong>:在编译文件里 <code class="language-plaintext highlighter-rouge">vi gd_png.c</code> 将 <code class="language-plaintext highlighter-rouge">include "png.h"</code> 改成 <code class="language-plaintext highlighter-rouge">include "/usr/local/libpng/include/png.h"</code></p>
<p><strong>2)</strong> 出现</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>X--tag=CC: command not found
</code></pre></div></div>
<p><strong>解决</strong>:修改 aclocal.m4 文件,将上面的 <code class="language-plaintext highlighter-rouge">LIBTOOL='$(SHELL) $(top _builddir)/libtool'</code> 改成 <code class="language-plaintext highlighter-rouge">LIBTOOL='$(SHELL) /usr/bin/libtool' </code>后重新执行 <code class="language-plaintext highlighter-rouge">./configure</code></p>
<p><strong>3)</strong> Invalid libtool wrapper script when make installing Apache</p>
<p><strong>解决</strong>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make clean
--with-pcre=/usr/local/pcre
</code></pre></div></div>
<h2 id="四-安装apache"><a name="4"></a>四. 安装Apache</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd /usr/local/src/httpd-2.2.9
$./configure \
--prefix=/usr/local/apache2 \
--sysconfdir=/etc/httpd \
--with-z=/usr/local/zlib \
--with-included-apr \
--with-apr=/usr/local/apr \
--with-apr-util=/usr/local/apr-util /
--with-pcre=/usr/local/pcre \
--enable-so \
--enable-deflate=shared \
--enable-expires=shared \
--enable-rewrite=shared \
$make && make install
</code></pre></div></div>
<p><strong>1)</strong> 执行 configure 出错:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>configure: error: Bundled APR requested but not found at ./srclib/.
Download and unpack the corresponding apr and apr-util packages to ./srclib/.
</code></pre></div></div>
<p><strong>解决</strong>: 下载 api 和 api-util 安装到 apache 的 ./srclib/ 目录.</p>
<p><strong>2)</strong> <code class="language-plaintext highlighter-rouge">./configure</code> 命令后在执行 make 命令的时候报如下错误:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/bin/ld: /usr/local/lib/libz.a(crc32.o): relocation RX86\_64\_32 against `.rodata' can not be used when making a shared object; recompile with -fPIC **解决**:下载 zlib-1.2.3.tar.gz 放在 /usr/local 目录执行以下命令:
tar -zxvf zlib-1.2.3.tar.gz
cd zlib-1.2.3
./configure
vi Makefile
</code></pre></div></div>
<p>找到 <code class="language-plaintext highlighter-rouge">CFLAGS=-O3 -DUSE\_MMAP</code>
在后面加入-fPIC,即变成 <code class="language-plaintext highlighter-rouge">CFLAGS=-O3 -DUSE\_MAP -fPIC</code></p>
<hr />
<p><strong>启动Apache</strong></p>
<p>/usr/local/apache2/bin/apachectl start</p>
<p><strong>关闭Apache</strong></p>
<p>/usr/local/apache2/bin/apachectl stop</p>
<p><strong>查看80端口是否开启</strong></p>
<table>
<tbody>
<tr>
<td>netstat -tnl</td>
<td>grep 80</td>
</tr>
</tbody>
</table>
<hr />
<p><strong>配置Apache</strong></p>
<blockquote>
<p>标红部分为需要需改的地方</p>
</blockquote>
<p><font color=#DC143C>ServerRoot “/usr/local/apache2”</font></p>
<blockquote>
<p>你的apache软件安装的位置。其它指定的目录如果没有指定绝对路径,则目录是相对于该目录。</p>
</blockquote>
<p>PidFile logs/httpd.pid</p>
<blockquote>
<p>第一个httpd进程(所有其他进程的父进程)的进程号文件位置。</p>
</blockquote>
<p>Listen 80</p>
<blockquote>
<p>服务器监听的端口号。</p>
</blockquote>
<p><font color=#DC143C>ServerName 10.20.16.78:80</font><br /></p>
<blockquote>
<p>主站点名称(网站的主机名)。</p>
</blockquote>
<p>ServerAdmin admin@clusting.com</p>
<blockquote>
<p>管理员的邮件地址。</p>
</blockquote>
<p>DocumentRoot “/data/spdev/chenfan”</p>
<blockquote>
<p>主站点的网页存储位置。</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 变更路径
<Directory "/data/spdev/chenfan ">
Options FollowSymLinks
AllowOverride None
Order allow,deny
#修改权限
Allow from all
</Directory>
</code></pre></div></div>
<h2 id="五-安装mysql"><a name="5"></a>五. 安装Mysql</h2>
<h3 id="1-添加一个mysql标准组">1. 添加一个mysql标准组</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$groupadd mysql
</code></pre></div></div>
<h3 id="2-添加mysql用户并加到mysql组中">2. 添加mysql用户并加到mysql组中</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>&useradd -g mysql mysql
&cd /usr/local/src/mysql-5.0.41
&./configure \
--prefix=/usr/local/mysql/ \
--with-extra-charsets=all
</code></pre></div></div>
<h2 id="六-安装php"><a name="6"></a>六. 安装PHP</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$./configure \
--prefix=/usr/local/php \
--with-config-file-path=/usr/local/php/etc \
--with-apxs2=/usr/local/apache2/bin/apxs \
--with-mysql=/usr/local/mysql/mysql-cluster-gpl-7.2.8-linux2.6-x86_64/ \
--with-libxml-dir=/usr/local/libxml2/ \
--with-png-dir=/usr/local/libpng/ \
--with-jpeg-dir=/usr/local/jpeg6/ \
--with-freetype-dir=/usr/local/freetype/ \
-with-gd=/usr/local/gd2/ \
--with-zlib-dir=/usr/local/zlib/ \
--with-mcrypt=/usr/local/libmcrypt/ \
--with-mysqli=/usr/local/mysql/mysql-cluster-gpl-7.2.8-linux2.6-x86_64/bin/mysql_config \
--enable-soap \
--enable-mbstring=all \
--enable-sockets
</code></pre></div></div>
<p><strong>配置PHP</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cp /usr/local/src/php-5.3.16/php.ini-development /usr/local/php/lib/php.ini
</code></pre></div></div>
<p>编辑 apache 配置文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$vi /etc/httpd/httpd.conf
</code></pre></div></div>
<p>添加这一条代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Addtype application/x-httpd-php .php .phtml
</code></pre></div></div>
<p><strong>测试</strong></p>
<p>在 apache 的 htdocs 下建立一个 php 文件 test.php,里面的内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?php
phpinfo();
?>
</code></pre></div></div>
<p>然后在浏览器里输入 <a href="http://10.20.16.78/test.php">http://10.20.16.78/test.php</a>,出现 php 信息则为安装正确。</p>
<h2 id="七-装载phpmyadmin"><a name="7"></a>七. 装载phpmyadmin</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$tar -zxvf phpMyAdmin-7.0.0-all-languages.tar.gz >此时路径 /data/spdev/chenfan/phpMyAdmin-7.0.0-all-languages.tar.gz
</code></pre></div></div>
<p><strong>配置phpmyadmin</strong></p>
<ol>
<li>重启apache 找到 /libraries/config.default.php文件(config.default.php复制到phpmyadmin目录下,然后更名为config.inc.php),在linux下直接用vi编辑.</li>
<li>查找 <code class="language-plaintext highlighter-rouge">$cfg['PmaAbsoluteUri']</code> 修改为你将上传到空间的phpMyAdmin的网址
如:<code class="language-plaintext highlighter-rouge">$cfg['PmaAbsoluteUri'] = 'http://10.20.16.78/admin/';</code></li>
<li>查找 <code class="language-plaintext highlighter-rouge">$cfg['Servers'][$i]['host'] = '10.20.16.78';</code>(通常用默认,也有例外,可以不用修改)</li>
<li>查找 ` $cfg[‘Servers’][$i][‘auth_type’] = ‘config’;`
在自己的机子里调试用config;如果在网络上的空间用cookie,这里我们既然在前面已经添加了网址,就修改成cookie ,这里建议使用cookie.\</li>
<li>查找 <code class="language-plaintext highlighter-rouge">$cfg['Servers'][$i]['user'] = 'root';</code> // MySQL user(mysql用户名,自己机里用root;)</li>
<li>查找 <code class="language-plaintext highlighter-rouge">$cfg['Servers'][$i]['password'] = '123456'; </code>// MySQL password (mysql用户的密码,自己的服务器一般都是mysql用户root的密码)</li>
<li>查找 ` $cfg[‘Servers’][$i][‘only_db’] = ‘’;` // If set to a db-name, only(你只有一个数据就设置一下;如果你在本机或想架设服务器,那么建议留空)</li>
<li>查找 ` $cfg[‘DefaultLang’] = ‘zh’; `(这里是选择语言,zh代表简体中文的意思,这里不知道填gbk对否)</li>
<li>设置完毕后保存</li>
</ol>
<hr />
<p><strong>出现错误</strong>:数据库连接错误</p>
<p><strong>原因</strong>:绝对路径没有配置对</p>
<p><strong>解决</strong>:修改 <code class="language-plaintext highlighter-rouge">$cfg['PmaAbsoluteUri'] = 'http://10.20.16.78/phpmyadmin/';</code></p>
<h2 id="八装载wordpress"><a name="8"></a>八.装载wordpress</h2>
<p>解压 wordpress</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$tar -zxvf wordpress-4.3.1-zh_CN.tar.gz
</code></pre></div></div>
<p>修改配置文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cp wp-config-sample.php wp-config.php
$vi wp-config.php
</code></pre></div></div>
<p>define(‘DB_NAME’, ‘wordpress’);</p>
<blockquote>
<p>数据库</p>
</blockquote>
<p>define(‘DB_USER’, ‘root’);</p>
<blockquote>
<p>数据库名称</p>
</blockquote>
<p>define(‘DB_PASSWORD’, ‘<em>**</em>’);</p>
<blockquote>
<p>数据库密码</p>
</blockquote>
<hr />
<p><strong>出现错误</strong>:装载 php 时错误找不到 mysql_config</p>
<p><strong>原因</strong>:指定的路径出错</p>
<p><strong>解决办法</strong>:找到 <code class="language-plaintext highlighter-rouge">mysql\_config</code> 改成 <code class="language-plaintext highlighter-rouge">with-mysqli=/usr/local/mysql/mysql-cluster-gpl-7.2.8-linux2.6-x86\_64/bin/mysql_config \</code></p>
linux下搭建属于自己的博客
2015-12-21T00:00:00+00:00
http://www.blogways.net/blog/2015/12/21/wordpress-install-on-linux
<h1 id="linux下安装wordpress">linux下安装Wordpress</h1>
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">安装Apache</a></li>
<li><a href="#2">安装PHP服务</a></li>
<li><a href="#3">安装mysql</a></li>
<li><a href="#4">安装phpMyAdmin</a></li>
<li><a href="#5">安装Wordpress</a></li>
<li><a href="#6">常见错误</a></li>
</ol>
</dd>
</dl>
</div>
<h2 id="一安装apache服务器">一.<a name="1"></a>安装Apache服务器</h2>
<p>在linux终端输入<br /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install apache2 **测试**:安装后在浏览器中打开:http://localhost/或者http://127.0.0.1,如果出现It works!则说明安装成功。
</code></pre></div></div>
<h2 id="二安装php服务">二.<a name="2"></a>安装PHP服务</h2>
<p>在linux终端输入</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sudo apt-get install php5 **测试**:打开sudo gedit /var/www/testphp.php<br> 然后随意输入字符,这里我输入的是chenfan,在浏览器中输入localhost/testphp.php,如果浏览器出现chenfan则说明安装成功 ## 三.<a name="3"></a>安装mysql 在linux终端输入
sudo apt-get install mysql-server
sudo apt-get install mysql-admin
sudo apt-get install mysql-client 安装过程中提示要求输入root密码。 ## 四.<a name="4"></a>安装phpMyAdmin
sudo apt-get install phpmyadmin 此时的phpmyadmin文件夹被安装在/usr/share/phpmyadmin下,为了能在浏览器中访问到phpmyadmin,需要在/var/www下做一个软连接到该文件夹: 进入/var/www文件夹,在该目录下执行如下操作:
sudo ln -s /usr/share/phpmyadmin 安装完毕后别忘了重启apache 和 mysql:
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/mysql restart **测试**:在浏览器中输入localhost/phpmyadmin,如果出现 ![phpmyadmin](\images\post\phpmyadmin.jpg) 则说明安装成功! 成功后点击新建数据库,建立一个wordpress数据库。 ## 五.<a name="5"></a>安装Wordpress ### 1. 下载下载wordpress(WordPress) 下载地址: http://wordpress.org/download/ ### 2.解压Wordpress 在linux终端输入
$sudo tar -zxvf wordpress-3.2.1.tar.gz 得到wordpress文件夹,然后按要求编辑wp-config.php文件,主要是提供数据库的名字(如这里的wordpress),用户名(如root),密码(如安装mysql时键入的密码)。 ### 3.将Wordpress文件夹拷贝到Apache服务器目录/var/www下
sudo cp -a ./wordpress /var/www 安装完毕后重启apache 和 mysql:
sudo /etc/init.d/apache2 restart
sudo /etc/init.d/mysql restart **测试**:此时在浏览器中访问http://localhost/wordpress/wp-admin/install.php就可以正常安装wordpress ## 六.<a name="6"></a>常见错误 ### 1.php装好 输入在浏览器中打开:http://localhost/或者http://127.0.0.1出现:Not FoundThe requested URL /testphp.php was not found on this server.Apache/2.4.7 (Ubuntu) Server at 127.0.0.1 Port 80 **原因:**apache的根目录里并不包含test.php文件 **解决办法:**修改Apache配置文件 在/etc/apache2/sites-available中修改000-default.conf 输入代码:
sudo vi /etc/apache2/sites-available/000-default.conf 修改 DocumentRoot 修改成你想好存放的目录
</code></pre></div></div>
meteor入门学习笔记二:一个基础的例子
2015-12-16T00:00:00+00:00
http://www.blogways.net/blog/2015/12/16/meteor-tutorial-2
<p>这几天在学习Meteor,当前版本为:<code class="language-plaintext highlighter-rouge">1.2.1</code>。学习的主要资料来自官网,笔记如下.</p>
<h2 id="一创建一个应用">一、创建一个应用</h2>
<p>在这里,我们将要创建一个简单的应用,管理一个任务清单。这样就可以和其他人一起使用这个任务清单,进行合作了。</p>
<p>为了创建这个应用,需要打开终端,输入命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor create simple-todos
</code></pre></div></div>
<p>这个命令会创建一个名为<code class="language-plaintext highlighter-rouge">simple-todos</code>的目录,目录里面有Meteor应用所需要的一些文件,列表如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>simple-todos.js #一个被服务器和客户端都使用的javascript文件
simple-todos.html #一个html文件,里面定义了页面视图模版
simple-todos.css #一个css文件,定义了应用的式样
.meteor #一个隐藏目录,里面有meteor运行需要的文件
</code></pre></div></div>
<p>运行这个应用,可以输入命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd </span>simple-todos
meteor
</code></pre></div></div>
<p>好了,打开你的浏览器,然后输入<code class="language-plaintext highlighter-rouge">http://localhost:3000</code>。</p>
<p>如果你修改了simple-todos.html,你会发现几秒后,浏览器上打开的页面内容也会自动发生相应的变化。这就是Meteor所谓的“代码热部署”。</p>
<h2 id="二使用模版来定义页面视图">二、使用模版来定义页面视图</h2>
<p>好了,我们继续制作我们的任务清单程序。</p>
<h3 id="21-修改代码">2.1 修改代码</h3>
<p>我们用下面的代码替代之前Meteor默认生成的代码。</p>
<p><code class="language-plaintext highlighter-rouge">simple-todos.html</code>的代码修改如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span>
<span class="nt"><title></span>Todo List<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"><header></span>
<span class="nt"><h1></span>Todo List<span class="nt"></h1></span>
<span class="nt"></header></span>
<span class="nt"><ul></span>
{{#each tasks}}
{{> task}}
{{/each}}
<span class="nt"></ul></span>
<span class="nt"></div></span>
<span class="nt"></body></span>
<span class="nt"><template</span> <span class="na">name=</span><span class="s">"task"</span><span class="nt">></span>
<span class="nt"><li></span>{{text}}<span class="nt"></li></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">simple-todos.js</code>的代码修改如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">This is task 1</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">This is task 2</span><span class="dl">"</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">This is task 3</span><span class="dl">"</span> <span class="p">}</span>
<span class="p">]</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>替换完上面的代码,静待几秒,浏览器中展现的内容就会发生变化,类似如下:</p>
<blockquote>
<h1 id="todo-list">Todo List</h1>
<ul>
<li>This is task 1</li>
<li>This is task 2</li>
<li>This is task 3</li>
</ul>
</blockquote>
<p>挺有意思的,不是吗?好吧,让我们看看它们是怎么工作的。</p>
<h3 id="22-meteor使用html文件来定义模版">2.2 Meteor使用HTML文件来定义模版</h3>
<p>Meteor解析你的应用目录下的所有HTML文件。识别出三个顶级标签:<code class="language-plaintext highlighter-rouge"><head></code>、<code class="language-plaintext highlighter-rouge"><body></code>和<code class="language-plaintext highlighter-rouge"><template></code>。</p>
<p>其中,<code class="language-plaintext highlighter-rouge"><head></code>标签和<code class="language-plaintext highlighter-rouge"><body></code>标签里的内容都会被发送到客户端页面中,对应的标签下面去。</p>
<p>而<code class="language-plaintext highlighter-rouge"><template></code>标签里面的内容,会被编译成Meteor模版。Meteor模版或者被HTML中的<code class="language-plaintext highlighter-rouge">{{>templateName}}</code>引用,或者被JavaScript程序中的<code class="language-plaintext highlighter-rouge">Template.templateName</code>所引用。</p>
<h3 id="23-给模版添加逻辑和数据">2.3 给模版添加逻辑和数据</h3>
<p>Meteor使用Spacebars来编译HTML文件中的代码。Spacebars用双括号将语句括起来,比如:<code class="language-plaintext highlighter-rouge">{{#each}}</code>和<code class="language-plaintext highlighter-rouge">{{#if}}</code>。使用这种方式给模版添加逻辑和数据。</p>
<p>我们可以借用<code class="language-plaintext highlighter-rouge">helpers</code>从JavsScript代码中把数据传给模版。在上面的代码中,我们在<code class="language-plaintext highlighter-rouge">Template.body</code>上定义了一个名为<code class="language-plaintext highlighter-rouge">tasks</code>的帮助器(helper)。它返回的是一个数组。在HTML的body标签内,我们可以使用<code class="language-plaintext highlighter-rouge">{{#each}}</code>来遍历整个数组,插入一个<code class="language-plaintext highlighter-rouge">task</code>模版来显示数组中的每个值。在<code class="language-plaintext highlighter-rouge">#each</code>语句块内,我们可以使用<code class="language-plaintext highlighter-rouge">{{text}}</code>来显示数组中每项的<code class="language-plaintext highlighter-rouge">text</code>属性值。</p>
<p>关于模版的更多内容:https://github.com/meteor/meteor/blob/devel/packages/spacebars/README.md</p>
<h3 id="24-添加css">2.4 添加CSS</h3>
<p>这个应用不添加额外的css式样,也可以正常运行,但是为了更加美观,我们给这个应用添加一些CSS式样。</p>
<p><code class="language-plaintext highlighter-rouge">simple-todos.css</code>文件内容修改如下:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* CSS declarations go here */</span>
<span class="nt">body</span> <span class="p">{</span>
<span class="nl">font-family</span><span class="p">:</span> <span class="nb">sans-serif</span><span class="p">;</span>
<span class="nl">background-color</span><span class="p">:</span> <span class="m">#315481</span><span class="p">;</span>
<span class="nl">background-image</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span><span class="n">to</span> <span class="nb">bottom</span><span class="p">,</span> <span class="m">#315481</span><span class="p">,</span> <span class="m">#918e82</span> <span class="m">100%</span><span class="p">);</span>
<span class="nl">background-attachment</span><span class="p">:</span> <span class="nb">fixed</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
<span class="nl">top</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">bottom</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">left</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">right</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="m">14px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.container</span> <span class="p">{</span>
<span class="nl">max-width</span><span class="p">:</span> <span class="m">600px</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span> <span class="nb">auto</span><span class="p">;</span>
<span class="nl">min-height</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">header</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#d2edf4</span><span class="p">;</span>
<span class="nl">background-image</span><span class="p">:</span> <span class="n">linear-gradient</span><span class="p">(</span><span class="n">to</span> <span class="nb">bottom</span><span class="p">,</span> <span class="m">#d0edf5</span><span class="p">,</span> <span class="m">#e1e5f0</span> <span class="m">100%</span><span class="p">);</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">20px</span> <span class="m">15px</span> <span class="m">15px</span> <span class="m">15px</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">relative</span><span class="p">;</span>
<span class="p">}</span>
<span class="nf">#login-buttons</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">block</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">h1</span> <span class="p">{</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="m">1.5em</span><span class="p">;</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="nl">display</span><span class="p">:</span> <span class="n">inline-block</span><span class="p">;</span>
<span class="nl">margin-right</span><span class="p">:</span> <span class="m">1em</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">form</span> <span class="p">{</span>
<span class="nl">margin-top</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="nl">margin-bottom</span><span class="p">:</span> <span class="m">-10px</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">relative</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.new-task</span> <span class="nt">input</span> <span class="p">{</span>
<span class="nl">box-sizing</span><span class="p">:</span> <span class="n">border-box</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">10px</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="nb">transparent</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">100%</span><span class="p">;</span>
<span class="nl">padding-right</span><span class="p">:</span> <span class="m">80px</span><span class="p">;</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="m">1em</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.new-task</span> <span class="nt">input</span><span class="nd">:focus</span><span class="p">{</span>
<span class="nl">outline</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">ul</span> <span class="p">{</span>
<span class="nl">margin</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">0</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="no">white</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.delete</span> <span class="p">{</span>
<span class="nl">float</span><span class="p">:</span> <span class="nb">right</span><span class="p">;</span>
<span class="nl">font-weight</span><span class="p">:</span> <span class="nb">bold</span><span class="p">;</span>
<span class="nl">background</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="nl">font-size</span><span class="p">:</span> <span class="m">1em</span><span class="p">;</span>
<span class="nl">border</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">relative</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">li</span> <span class="p">{</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">relative</span><span class="p">;</span>
<span class="nl">list-style</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">15px</span><span class="p">;</span>
<span class="nl">border-bottom</span><span class="p">:</span> <span class="m">#eee</span> <span class="nb">solid</span> <span class="m">1px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">li</span> <span class="nc">.text</span> <span class="p">{</span>
<span class="nl">margin-left</span><span class="p">:</span> <span class="m">10px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">li</span><span class="nc">.checked</span> <span class="p">{</span>
<span class="nl">color</span><span class="p">:</span> <span class="m">#888</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">li</span><span class="nc">.checked</span> <span class="nc">.text</span> <span class="p">{</span>
<span class="nl">text-decoration</span><span class="p">:</span> <span class="nb">line-through</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">li</span><span class="nc">.private</span> <span class="p">{</span>
<span class="nl">background</span><span class="p">:</span> <span class="m">#eee</span><span class="p">;</span>
<span class="nl">border-color</span><span class="p">:</span> <span class="m">#ddd</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">header</span> <span class="nc">.hide-completed</span> <span class="p">{</span>
<span class="nl">float</span><span class="p">:</span> <span class="nb">right</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.toggle-private</span> <span class="p">{</span>
<span class="nl">margin-left</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
<span class="p">}</span>
<span class="k">@media</span> <span class="p">(</span><span class="n">max-width</span><span class="p">:</span> <span class="m">600px</span><span class="p">)</span> <span class="p">{</span>
<span class="nt">li</span> <span class="p">{</span>
<span class="nl">padding</span><span class="p">:</span> <span class="m">12px</span> <span class="m">15px</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.search</span> <span class="p">{</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">150px</span><span class="p">;</span>
<span class="nl">clear</span><span class="p">:</span> <span class="nb">both</span><span class="p">;</span>
<span class="p">}</span>
<span class="nc">.new-task</span> <span class="nt">input</span> <span class="p">{</span>
<span class="nl">padding-bottom</span><span class="p">:</span> <span class="m">5px</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h2 id="三使用集合来存储数据">三、使用集合来存储数据</h2>
<p>集合是Meteor用来存储持久化数据的方法。其特殊之处在于,它既可被服务端访问,也可被客户端访问。这样就很容易做到,无需编写大量服务端代码就可以实现页面逻辑。他们也可以自动更新,所以由集合支持的页面视图组件可以自动显示最新数据。</p>
<p>在你的JavaScript代码里,通过调用<code class="language-plaintext highlighter-rouge">MyCollection = new Mongo.Collection("my-collection");</code>,可以很容易地创建一个新的集合。在服务器端,这行代码会设置一个名为<code class="language-plaintext highlighter-rouge">my-collection</code>的MongoDB集合;在客户端,这行代码会创建一个缓存,这个缓存和服务器端集合存在连接。后面,我们会了解的更多详情,现在就让我们假设整个数据库都存在于客户端。</p>
<p>下面我们修改JavaScript代码,从数据库集合中获取任务:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Tasks</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Mongo</span><span class="p">.</span><span class="nx">Collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>使用上面代码后,静待几秒钟,等待Meteor“热部署”完成。我们会发现任务列表中的记录消失了,这是因为数据库集合是空的。我们需要向数据库集合中插入一些任务数据。</p>
<h3 id="从服务器端数据库控制台插入任务数据">从服务器端数据库控制台插入任务数据</h3>
<p>数据库集合里的数据被称为文档。下面,我们在服务器端使用数据库的控制台插入一些文档到任务集合中去。</p>
<p>打开一个新的终端页,进入应用所在目录,然后输入命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor mongo
</code></pre></div></div>
<p>当前控制台会连上应用的本地开发数据库,在数据库的交互模式下,输入命令:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">db</span><span class="p">.</span><span class="nx">tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span> <span class="na">text</span><span class="p">:</span> <span class="dl">"</span><span class="s2">Hello world!</span><span class="dl">"</span><span class="p">,</span> <span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="p">});</span>
</code></pre></div></div>
<p>再看看浏览器,你将发现应用界面立刻显示出了新任务记录。你会发现我们在客户端和服务端之间并没有写什么连接代码,但是数据恰恰自动更新了。</p>
<p>在数据库控制台上,用相同的方法,再插入一些不同内容的任务记录。</p>
<p>下面,我们看看怎么给应用页面增加功能,不通过后端数据库控制台,直接通过前端页面增加任务记录。</p>
<h2 id="四通过页面添加任务">四、通过页面添加任务</h2>
<p>在这一环节,我们要提供一个输入框给用户,以便向任务清单中添加任务记录。</p>
<p>首先,我们在HTML里添加一个form。完整的simple-todos.html如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><head></span>
<span class="nt"><title></span>Todo List<span class="nt"></title></span>
<span class="nt"></head></span>
<span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"><header></span>
<span class="nt"><h1></span>Todo List<span class="nt"></h1></span>
<span class="nt"><form</span> <span class="na">class=</span><span class="s">"new-task"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"text"</span> <span class="na">placeholder=</span><span class="s">"Type to add new tasks"</span> <span class="nt">/></span>
<span class="nt"></form></span>
<span class="nt"></header></span>
<span class="nt"><ul></span>
{{#each tasks}}
{{> task}}
{{/each}}
<span class="nt"></ul></span>
<span class="nt"></div></span>
<span class="nt"></body></span>
<span class="nt"><template</span> <span class="na">name=</span><span class="s">"task"</span><span class="nt">></span>
<span class="nt"><li></span>{{text}}<span class="nt"></li></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p>在Javascript代码中,我们需要增加对页面form的<code class="language-plaintext highlighter-rouge">submit</code>事件的监听方法。完整的simple-todos.js文件如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Tasks</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Mongo</span><span class="p">.</span><span class="nx">Collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({});</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">submit .new-task</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Prevent default browser form submit</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="c1">// Get value from form element</span>
<span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="c1">// Insert a task into the collection</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="c1">// current time</span>
<span class="p">});</span>
<span class="c1">// Clear form</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>现在应用有了一个新的输入框。要添加任务记录,只需要在输入框输入内容,然后点击回车就好了。如果你另外打开一个浏览器窗口,并新窗口中打开应用,你会发现多个客户端上的任务记录是自动同步的!</p>
<p>###给模版绑定事件</p>
<p>给模版添加事件的方式如同helpers方法的使用:调用<code class="language-plaintext highlighter-rouge">Template.templateName.events(...)</code>,传入一个key-value字典类型参数。其中key描述监听的事件,其中value是事件句柄方法。</p>
<p>在上面的例子中,我们监听CSS选择器<code class="language-plaintext highlighter-rouge">.new-task</code>匹配的任意元素的<code class="language-plaintext highlighter-rouge">submit</code>事件。当用户在输入框内按下回车键时,将会触发这个事件,我们设置的事件方法就会被调用。</p>
<p>被调用的事件方法有一个输入参数<code class="language-plaintext highlighter-rouge">event</code>,这个参数包含了被触发的事件的一些信息。在这里,<code class="language-plaintext highlighter-rouge">event.target</code>是我们这个页面上的form元素,我们可以通过<code class="language-plaintext highlighter-rouge">event.target.text.value</code>来获取输入框中的输入值。你可以在浏览器的控制台,通过<code class="language-plaintext highlighter-rouge">console.log(event)</code>来查看<code class="language-plaintext highlighter-rouge">event</code>的各个属性。</p>
<p>最好,在这个事件方法的最后一行,我们清除了输入框中的内容,准备下一次输入。</p>
<p>###向集合中插入数据</p>
<p>在这个事件方法中,我们通过调用<code class="language-plaintext highlighter-rouge">Tasks.insert()</code>来向<code class="language-plaintext highlighter-rouge">tasks</code>集合中添加一条任务记录。我们不需要事先定义集合的结构,就可以向集合中的记录添加各种属性字段,比如:被创建的时间。</p>
<p>###排序查询结果</p>
<p>现在,所有新的任务记录都显示在页面的底部。这种体验不是太好,我们更希望先看到最新的任务。</p>
<p>我们可以利用<code class="language-plaintext highlighter-rouge">createdAt</code>字段来排序查询结果。所需做的,仅是给<code class="language-plaintext highlighter-rouge">find</code>方法添加一个排序选项。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// This code only runs on the client</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Show newest tasks at the top</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<h2 id="五已处理与删除任务">五、已处理与删除任务</h2>
<p>前面,我们学习了怎么向集合中插入数据,下面学习如何更新及删除数据。</p>
<p>我们先给<code class="language-plaintext highlighter-rouge">task</code>模版增加两个页面元素:一个复选框和一个删除按钮。</p>
<p>替换simple-todos.html文件中的<code class="language-plaintext highlighter-rouge">task</code>模版,内容如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template</span> <span class="na">name=</span><span class="s">"task"</span><span class="nt">></span>
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"{{#if checked}}checked{{/if}}"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">class=</span><span class="s">"delete"</span><span class="nt">></span><span class="ni">&times;</span><span class="nt"></button></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">checked=</span><span class="s">"{{checked}}"</span> <span class="na">class=</span><span class="s">"toggle-checked"</span> <span class="nt">/></span>
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"text"</span><span class="nt">></span>{{text}}<span class="nt"></span></span>
<span class="nt"></li></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p>仅添加UI元素,页面发生了变化,但是新元素不能使用。我们需要添加相应的事件。</p>
<p>修改simple-todos.js文件,增加相关事件:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Template</span><span class="p">.</span><span class="nx">task</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">click .toggle-checked</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Set the checked property to the opposite of its current value</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">,</span> <span class="p">{</span>
<span class="na">$set</span><span class="p">:</span> <span class="p">{</span><span class="na">checked</span><span class="p">:</span> <span class="o">!</span> <span class="k">this</span><span class="p">.</span><span class="nx">checked</span><span class="p">}</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">click .delete</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>好了,静待Meteor“热部署”后,点击页面上的复选框或者删除按钮。看看效果吧!</p>
<h3 id="在事件方法中获取数据">在事件方法中获取数据</h3>
<p>在事件方法中,<code class="language-plaintext highlighter-rouge">this</code>指向当前这个任务对象。在数据库集合中,每个插入的文档都有一个唯一值<code class="language-plaintext highlighter-rouge">_id</code>字段,可以使用这个字段找到每个文档。我们可以使用<code class="language-plaintext highlighter-rouge">this.id</code>来获取当前任务记录的<code class="language-plaintext highlighter-rouge">_id</code>字段。一旦有了<code class="language-plaintext highlighter-rouge">_id</code>,那么我们就可以使用<code class="language-plaintext highlighter-rouge">update</code>或者<code class="language-plaintext highlighter-rouge">remove</code>方法来修改对应的任务记录了。</p>
<h3 id="更新">更新</h3>
<p>集合的<code class="language-plaintext highlighter-rouge">update</code>方法有两个参数。第一个参数,是选择器,可以筛选出集合的子集;第二个参数是个更新参数,列出匹配的结果都做如何修改。</p>
<p>在上面这个例子中,选择器就是任务的<code class="language-plaintext highlighter-rouge">_id</code>字段值;更新参数使用<code class="language-plaintext highlighter-rouge">$set</code>去切换<code class="language-plaintext highlighter-rouge">checked</code>字段的值,来代表当前任务记录是否处理完成。</p>
<h3 id="删除">删除</h3>
<p>集合的<code class="language-plaintext highlighter-rouge">remove</code>方法只有一个参数——选择器,它决定了集合中的哪项记录被删除。</p>
<p>###使用对象的属性(或者使用helpers)去添加/删除页面式样</p>
<p>你如果标记某些任务已经完成,你会发现被标记处理完成的任务都有一条删除线。这个效果由下面代码实现:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><li</span> <span class="na">class=</span><span class="s">"{{#if checked}}checked{{/if}}"</span><span class="nt">></span>
</code></pre></div></div>
<p>如果任务的<code class="language-plaintext highlighter-rouge">checked</code>属性为<code class="language-plaintext highlighter-rouge">true</code>,那么<code class="language-plaintext highlighter-rouge">checked</code>式样类,就要加到这个<code class="language-plaintext highlighter-rouge">li</code>元素上。使用这个类,我们可以让完成处理的任务项很容易识别出来。</p>
<h2 id="六发布应用">六、发布应用</h2>
<p>现在,我们已经有了一个可以工作的任务清单应用了。我们可以把他分享给朋友们。Meteor很容易地支持把应用发布到网络上,让网络上的其他人使用。</p>
<p>进入你的应用所在目录,输入:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor deploy my_app_name.meteor.com
</code></pre></div></div>
<p>一旦你回答完所有交互问题,并上传成功。你就可以通过访问<code class="language-plaintext highlighter-rouge">http://my_app_name.meteor.com</code>,来在互联网上使用你的应用了。</p>
<h2 id="七在android或ios上运行你的应用">七、在Android或iOS上运行你的应用</h2>
<blockquote>
<p>目前,Meteor不支持在Windows上创建移动端应用。如果,你是在Windows上使用Meteor,请跳过本节。</p>
</blockquote>
<p>此处省去若干内容,有待日后另起篇章记录。</p>
<h2 id="八使用session变量去存储临时的ui状态">八、使用Session变量去存储临时的UI状态</h2>
<p>在这里,我们要给应用增加一个客户端筛选功能,以便用户可以点击一个复选框去查看待处理的任务。我们学着在客户端使用<code class="language-plaintext highlighter-rouge">Session</code>变量去存储临时变化的状态。</p>
<p>首先,我们需要在页面模版中增加一个复选框,simple-todos.html页面中<code class="language-plaintext highlighter-rouge">body</code>模版的代码如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><body></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"container"</span><span class="nt">></span>
<span class="nt"><header></span>
<span class="nt"><h1></span>Todo List<span class="nt"></h1></span>
<span class="nt"><label</span> <span class="na">class=</span><span class="s">"hide-completed"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">checked=</span><span class="s">"{{hideCompleted}}"</span> <span class="nt">/></span>
Hide Completed Tasks
<span class="nt"></label></span>
<span class="nt"><form</span> <span class="na">class=</span><span class="s">"new-task"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"text"</span> <span class="na">placeholder=</span><span class="s">"Type to add new tasks"</span> <span class="nt">/></span>
<span class="nt"></form></span>
<span class="nt"></header></span>
<span class="nt"><ul></span>
{{#each tasks}}
{{> task}}
{{/each}}
<span class="nt"></ul></span>
<span class="nt"></div></span>
<span class="nt"></body></span>
</code></pre></div></div>
<p>接着,我们需要添加一个事件处理方法,在复选框的状态发生变化时,去更新<code class="language-plaintext highlighter-rouge">Session</code>变量。<code class="language-plaintext highlighter-rouge">Session</code>是一个非常好的可以存放临时UI状态的地方。如同集合一样,可以在helpers方法中被调用。</p>
<p>修改simple-todos.js文件中的<code class="language-plaintext highlighter-rouge">Template.body.events(...)</code>,修改后内容如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">submit .new-task</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Prevent default browser form submit</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="c1">// Get value from form element</span>
<span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="c1">// Insert a task into the collection</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">()</span> <span class="c1">// current time</span>
<span class="p">});</span>
<span class="c1">// Clear form</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">change .hide-completed input</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Session</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">checked</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>现在,我们需要修改<code class="language-plaintext highlighter-rouge">Template.body.helpers</code>。下面这段代码增加了一个新的<code class="language-plaintext highlighter-rouge">if</code>语句块,去实现当复选框被选中时对任务清单的过滤;增加了一个新的方法,去获取Session变量中记录的复选框状态。</p>
<p>修改simple-todos.js文件中的<code class="language-plaintext highlighter-rouge">Template.body.helpers(...)</code>,修改为:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// If hide completed is checked, filter tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return all of the tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">hideCompleted</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>现在如果你选中复选框,那么任务清单中只显示没有完成的任务了!</p>
<h3 id="session是客户端的一个响应式数据存储">Session是客户端的一个响应式数据存储</h3>
<p>截止目前,我们已经把所有数据都存储到集合中去了,当数据库集合中的数据发生变化,前端页面也会自动更新。这是因为Mongo的集合是Meteor公认的响应式数据源,这意味着,一旦其中的数据发生变化,Meteor就知道了。Session也同样如此,但它不和服务器端通讯,这点与集合不同。因此,Session非常适合存放UI的一些临时状态,比如上例中的复选框。如同集合,当Session变量发生变化后,我们无需编写太多的编码。仅需要在帮助器(helper)方法里调用<code class="language-plaintext highlighter-rouge">Session.get(...)</code>就足够了。</p>
<h3 id="更多显示待处理任务总数">更多:显示待处理任务总数</h3>
<p>现在我们写了一个查询,可以筛选待处理的任务,我们也可以使用同样的查询,去显示待处理任务的总数。在JavaScript文件中增加一个方法,修改HTML一行代码就可以实现了。</p>
<p>在simple-todos.js中修改<code class="language-plaintext highlighter-rouge">Template.body.helpers(...)</code>,增加一个<code class="language-plaintext highlighter-rouge">incompleteCount</code>方法,修改后的如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// If hide completed is checked, filter tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return all of the tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">hideCompleted</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">incompleteCount</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}}).</span><span class="nx">count</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>修改simple-todos.html文件,显示待处理记录总数:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h1></span>Todo List ({{incompleteCount}})<span class="nt"></h1></span>
</code></pre></div></div>
<h2 id="九添加账号管理功能">九、添加账号管理功能</h2>
<p>Meteor自带一个账号系统,以及下拉式登录页面。可以让你的应用在几分钟内添加多用户功能。</p>
<p>为了可以使用账号系统以及相关的UI,我们需要添加相关的包。在你的应用所在目录,执行下面命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor add accounts-ui accounts-password
</code></pre></div></div>
<p>上述命令运行完后,原先的meteor服务会自动更新部署。我们继续下面操作。</p>
<p>在HTML文件中,复选框的正下方,添加一个用户登录代码,代码片段如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><header></span>
<span class="nt"><h1></span>Todo List ({{incompleteCount}})<span class="nt"></h1></span>
<span class="nt"><label</span> <span class="na">class=</span><span class="s">"hide-completed"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">checked=</span><span class="s">"{{hideCompleted}}"</span> <span class="nt">/></span>
Hide Completed Tasks
<span class="nt"></label></span>
{{> loginButtons}}
<span class="nt"><form</span> <span class="na">class=</span><span class="s">"new-task"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"text"</span> <span class="na">placeholder=</span><span class="s">"Type to add new tasks"</span> <span class="nt">/></span>
<span class="nt"></form></span>
<span class="nt"></header></span>
</code></pre></div></div>
<p>默认的登录界面是使用邮箱及密码登录,我们修改JavaScript文件,增加下面的代码来配置登录界面,使用用户名代替邮箱登录。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Accounts</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">config</span><span class="p">({</span>
<span class="na">passwordSignupFields</span><span class="p">:</span> <span class="dl">"</span><span class="s2">USERNAME_ONLY</span><span class="dl">"</span>
<span class="p">});</span>
</code></pre></div></div>
<p>现在,使用者可以创建账号登录你的应用了!只是登录登出并没有什么效果。让我们增加两个功能:</p>
<ol>
<li>只对登陆用户显示新任务输入框;</li>
<li>显示我们创建的每个任务</li>
</ol>
<p>为此,我们需要在<code class="language-plaintext highlighter-rouge">task</code>集合中增加两个新的字段:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">owner</code> - 创建此任务的账号的<code class="language-plaintext highlighter-rouge">_id</code>;</li>
<li><code class="language-plaintext highlighter-rouge">username</code> - 创建此任务的账号的<code class="language-plaintext highlighter-rouge">username</code>。我们直接把<code class="language-plaintext highlighter-rouge">username</code>保存在任务对象中,以便在显示任务时,无需每次都去关联账号信息查询用户名。</li>
</ol>
<p>首先,我们在<code class="language-plaintext highlighter-rouge">submit .new-task</code>事件处理方法中,增加一些代码去保存新增的字段,修改的代码片段如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(),</span> <span class="c1">// current time</span>
<span class="na">owner</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">(),</span> <span class="c1">// _id of logged in user</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">username</span> <span class="c1">// username of logged in user</span>
<span class="p">});</span>
</code></pre></div></div>
<p>接着,修改HTML文件,增加一个<code class="language-plaintext highlighter-rouge">#if</code>判断语句,仅当账号登录后才显示任务添加框。simple-todos.html中相关代码片段如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{{#if currentUser}}
<span class="nt"><form</span> <span class="na">class=</span><span class="s">"new-task"</span><span class="nt">></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"text"</span> <span class="na">name=</span><span class="s">"text"</span> <span class="na">placeholder=</span><span class="s">"Type to add new tasks"</span> <span class="nt">/></span>
<span class="nt"></form></span>
{{/if}}
</code></pre></div></div>
<p>最后,在每个任务信息的左边增加一个Spacebars语句去显示用户名(<code class="language-plaintext highlighter-rouge">username</code>)字段。</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><span</span> <span class="na">class=</span><span class="s">"text"</span><span class="nt">><strong></span>{{username}}<span class="nt"></strong></span> - {{text}}<span class="nt"></span></span>
</code></pre></div></div>
<p>好了,大功告成了!</p>
<h3 id="自动账号ui">自动账号UI</h3>
<p>如果应用添加了<code class="language-plaintext highlighter-rouge">accounts-ui</code>包,想增加一个下拉式登录窗口,只需使用<code class="language-plaintext highlighter-rouge">{{> loginButtons}}</code>语句来调用<code class="language-plaintext highlighter-rouge">loginButtons</code>模版。这个模版会自动判断支持哪些登录方式及显示相关的控制。在这个例子中,我们仅开启了账号密码(<code class="language-plaintext highlighter-rouge">accounts-password</code>)登录方式,所以下拉窗口中只有密码字段。如果你想做更多的尝试,你可以添加<code class="language-plaintext highlighter-rouge">accounts-facebook</code>包,来给你的应用开启Facebook账号登录功能,这样,Facebook的案例就会自动出现在下拉界面中了。</p>
<p>想尝试就执行下面的命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor add accounts-facebook
</code></pre></div></div>
<h3 id="关于登录用户的更多信息">关于登录用户的更多信息</h3>
<p>在HTML中,我们可以使用内置的帮助器(helper)<code class="language-plaintext highlighter-rouge">{{currentUser}}</code>去检查账号是否登录,及获取账号相关信息。比如:<code class="language-plaintext highlighter-rouge">{{currentUser.username}}</code>可以显示登录账号的用户名。</p>
<p>在JavaScript代码中,可以使用<code class="language-plaintext highlighter-rouge">Meteor.userId()</code>获取当前账号的<code class="language-plaintext highlighter-rouge">_id</code>,使用<code class="language-plaintext highlighter-rouge">Meteor.user()</code>获取整个账号信息。</p>
<h2 id="十使用methods方法实现安全控制">十、使用<code class="language-plaintext highlighter-rouge">methods</code>方法实现安全控制</h2>
<p>在这之前,应用的每个账号都能编辑数据库中的信息。这对一个内部小应用或者实例而言,可能没有什么问题。但任何一个实时应用都需要对它的数据进行权限控制。在Meteor中,最好的办法就是定义<code class="language-plaintext highlighter-rouge">methods</code>方法,代替客户端直接调用<code class="language-plaintext highlighter-rouge">insert</code>、<code class="language-plaintext highlighter-rouge">update</code>和<code class="language-plaintext highlighter-rouge">remove</code>方法。它将检查账号是否有权限进行当前操作,有权限则以客户端名义修改数据库中数据。</p>
<h3 id="移除insecure包">移除<code class="language-plaintext highlighter-rouge">insecure</code>包</h3>
<p>每个新创建的Meteor工程都会默认添加<code class="language-plaintext highlighter-rouge">insecure</code>包。这个包容许用户从客户端修改数据库数据。</p>
<p>使用下面命令可以删除这个包:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor remove insecure
</code></pre></div></div>
<p>移除这个包后,再使用应用,你将发现输入框和按钮不再正常工作了。这是因为客户端所有数据库权限都被终止了。我们需要使用<code class="language-plaintext highlighter-rouge">methods</code>重写应用的部分功能。</p>
<h3 id="定义methods">定义methods</h3>
<p>首先,我们需要定义一些方法.我们需要为在客户端执行的每个数据库操作定义一个方法。这些方法被定义在即被服务端执行,也被客户端执行的代码中。</p>
<p>修改simple-todos.js,增加以下代码片段:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Meteor</span><span class="p">.</span><span class="nx">methods</span><span class="p">({</span>
<span class="na">addTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Make sure the user is logged in before inserting a task</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(),</span>
<span class="na">owner</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">(),</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">username</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="na">deleteTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">setChecked</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="nx">setChecked</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="p">{</span> <span class="na">checked</span><span class="p">:</span> <span class="nx">setChecked</span><span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>好了,方法都定义好了。我们把之前对集合操作的代码都用这些方法替换。</p>
<p>修改后的完整的simple-todos.js文件内容如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Tasks</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Mongo</span><span class="p">.</span><span class="nx">Collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// If hide completed is checked, filter tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return all of the tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">hideCompleted</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">incompleteCount</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}}).</span><span class="nx">count</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">submit .new-task</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Prevent default browser form submit</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="c1">// Get value from form element</span>
<span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="c1">// Insert a task into the collection</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">addTask</span><span class="dl">"</span><span class="p">,</span> <span class="nx">text</span><span class="p">);</span>
<span class="c1">// Clear form</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">change .hide-completed input</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Session</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">checked</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">task</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">click .toggle-checked</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Set the checked property to the opposite of its current value</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">setChecked</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">,</span> <span class="o">!</span> <span class="k">this</span><span class="p">.</span><span class="nx">checked</span><span class="p">);</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">click .delete</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">deleteTask</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Accounts</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">config</span><span class="p">({</span>
<span class="na">passwordSignupFields</span><span class="p">:</span> <span class="dl">"</span><span class="s2">USERNAME_ONLY</span><span class="dl">"</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">methods</span><span class="p">({</span>
<span class="na">addTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Make sure the user is logged in before inserting a task</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(),</span>
<span class="na">owner</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">(),</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">username</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="na">deleteTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">setChecked</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="nx">setChecked</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="p">{</span> <span class="na">checked</span><span class="p">:</span> <span class="nx">setChecked</span><span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>好了,我们的输入框和按钮又可以正常工作了!稍微总结一下本章节收获:</p>
<ol>
<li>当我们向数据库插入任务时,我们可以做一些安全性校验,比如:用户是否登录;创建时间、用户名等字段是否正确。使用者无法假冒任何人。</li>
<li>当任务被私有化时,我们可以在<code class="language-plaintext highlighter-rouge">setChecked</code>和<code class="language-plaintext highlighter-rouge">deleteTask</code>方法中增加一些逻辑校验。(后文会介绍)</li>
<li>客户端代码与数据库逻辑更加分离了。我们提炼一些方法可以在各处被调用,而不再是大量逻辑都放在页面的事件处理方法里了。</li>
</ol>
<h3 id="optimistic-ui">Optimistic UI</h3>
<p>那么我们为什么要同时定义我们的方法在客户端和服务器端?我们这样做是为了实现一个我们称之为“optimistic UI”的特效。</p>
<p>当我们在客户端使用<code class="language-plaintext highlighter-rouge">Meteor.call</code>调用一个方法时,Meteor会同时做两件事:</p>
<ol>
<li>客户端发送请求至服务器端,在一个安全的环境下去运行这个方法,如同AJAX请求一样工作;</li>
<li>试图预测服务器端正常的处理结果,而在客户端模仿这个方法运行。</li>
</ol>
<p>这就意味着,一个新创建的任务,还未从服务器端接收反馈结果,就立刻出现在页面上了。</p>
<p>如果服务端返回的结果和客户端模拟的结果一致,那么不再做任何处理;不一致,那么UI将按照服务端返回结果进行修正。</p>
<p>利用Meteor的方法(methods)和optimistic UI,我们可以鱼与熊掌兼得——服务端代码安全与无延迟交互!</p>
<h2 id="十一利用发布与订阅来过滤数据">十一、利用发布与订阅来过滤数据</h2>
<p>我们已经把应用的所有敏感代码都移到了Methods里了,我们还需要了解一些Meteor其他的安全知识。截止目前,我们的工作都是基于假设整个数据库都在客户端的基础上的,这意味着,如果调用<code class="language-plaintext highlighter-rouge">Tasks.find()</code>,我们将从集合中查询所有数据。如果应用的使用者想存储一些隐私数据,这个机制就不合适了。我们需要一个方案去控制哪些数据可以发送到客户端数据库。</p>
<p>如同<code class="language-plaintext highlighter-rouge">insecure</code>包一样,Meteor新创建的每个新应用都会自带一个<code class="language-plaintext highlighter-rouge">autopublish</code>包。</p>
<p>我们删除这个包,看看发生了什么:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor remove autopublish
</code></pre></div></div>
<p>当应用刷新后,任务清单就空了。没有了这个包,我们需要显示地列出需要服务器端发送哪些数据到客户端。Meteor中实现这个功能的函数是<code class="language-plaintext highlighter-rouge">Meteor.publish</code>和<code class="language-plaintext highlighter-rouge">Meteor.subscrib</code>.</p>
<p>在simple-todos.js文件中增加以下代码片段:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isServer</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the server</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">publish</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>等待代码”热部署“后,页面的任务清单又出现了。</p>
<p>服务器端通过调用<code class="language-plaintext highlighter-rouge">Meteor.publish</code>方法,注册了一个名为<code class="language-plaintext highlighter-rouge">"tasks"</code>的发布。在客户端,使用这个发布名调用<code class="language-plaintext highlighter-rouge">Meteor.subscribe</code>方法,这个客户端就订阅了所有来自发布发布的数据,在这个例子中,就所有的任务清单。</p>
<p>为了真实地展示发布/订阅模式的强大之处,我们来实现一个功能,容许账号去标记任务为”私人的”,以便不被其他账号看见。</p>
<h3 id="实现私人任务">实现私人任务</h3>
<p>首先,我们给任务记录增加一个名为”private”的属性,给用户提供一个按钮,去标记任务是否是私人的。这个按钮只显示给任务的所有者,并且将显示任务当前所处的状态。</p>
<p>另外,我们还要给任务记录增加一个式样类,用来标记这个任务是否为私人的。</p>
<p>simple-todos.html中task模版修改后的代码如下:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><template</span> <span class="na">name=</span><span class="s">"task"</span><span class="nt">></span>
<span class="nt"><li</span> <span class="na">class=</span><span class="s">"{{#if checked}}checked{{/if}} {{#if private}}private{{/if}}"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">class=</span><span class="s">"delete"</span><span class="nt">></span><span class="ni">&times;</span><span class="nt"></button></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"checkbox"</span> <span class="na">checked=</span><span class="s">"{{checked}}"</span> <span class="na">class=</span><span class="s">"toggle-checked"</span> <span class="nt">/></span>
{{#if isOwner}}
<span class="nt"><button</span> <span class="na">class=</span><span class="s">"toggle-private"</span><span class="nt">></span>
{{#if private}}
Private
{{else}}
Public
{{/if}}
<span class="nt"></button></span>
{{/if}}
<span class="nt"><span</span> <span class="na">class=</span><span class="s">"text"</span><span class="nt">><strong></span>{{username}}<span class="nt"></strong></span> - {{text}}<span class="nt"></span></span>
<span class="nt"></li></span>
<span class="nt"></template></span>
</code></pre></div></div>
<p>结合页面所做的修改,我们需要同时修改三处JavaScript代码:</p>
<ol>
<li>
<p>增加一个名为<code class="language-plaintext highlighter-rouge">isOwner</code>的帮助器(helper)</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">Template</span><span class="p">.</span><span class="nx">task</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">isOwner</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">owner</span> <span class="o">===</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div> </div>
</li>
<li>
<p>增加一个名为<code class="language-plaintext highlighter-rouge">setPrivate</code>的Meteor方法。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nx">setPrivate</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="nx">setToPrivate</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">task</span> <span class="o">=</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="c1">// Make sure only the task owner can make a task private发布</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">task</span><span class="p">.</span><span class="nx">owner</span> <span class="o">!==</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="p">{</span> <span class="na">private</span><span class="p">:</span> <span class="nx">setToPrivate</span> <span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
<li>
<p>增加一个按钮点击事件方法。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="dl">"</span><span class="s2">click .toggle-private</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">setPrivate</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">,</span> <span class="o">!</span> <span class="k">this</span><span class="p">.</span><span class="kr">private</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
</ol>
<h3 id="基于隐私状态有选择地发布任务">基于隐私状态有选择地发布任务</h3>
<p>我们已经可以设置哪些任务是私人的了,我们继续完善我们的发布程序,只发送数据给有权限的用户浏览。</p>
<p>修改simple-todos.js中相关代码,相关代码片段如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isServer</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the server</span>
<span class="c1">// Only publish tasks that are public or belong to the current user</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">publish</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span>
<span class="na">$or</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">private</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">owner</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">userId</span> <span class="p">}</span>
<span class="p">]</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
</code></pre></div></div>
<p>我们可以打开两个浏览器,使用不同的账号登录,来测试效果。</p>
<h3 id="完善安全控制">完善安全控制</h3>
<p>为了完善我们的私人任务功能,我们需要给<code class="language-plaintext highlighter-rouge">deleteTask</code>和<code class="language-plaintext highlighter-rouge">setChecked</code>俩方法增加检查,以便任务的所有者可以删除或完成一个私人任务。</p>
<p>完整的simple-todos.js文件代码如下:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Tasks</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">Mongo</span><span class="p">.</span><span class="nx">Collection</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isServer</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the server</span>
<span class="c1">// Only publish tasks that are public or belong to the current user</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">publish</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">,</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span>
<span class="na">$or</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span> <span class="na">private</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}</span> <span class="p">},</span>
<span class="p">{</span> <span class="na">owner</span><span class="p">:</span> <span class="k">this</span><span class="p">.</span><span class="nx">userId</span> <span class="p">}</span>
<span class="p">]</span>
<span class="p">});</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// This code only runs on the client</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">subscribe</span><span class="p">(</span><span class="dl">"</span><span class="s2">tasks</span><span class="dl">"</span><span class="p">);</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">tasks</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">))</span> <span class="p">{</span>
<span class="c1">// If hide completed is checked, filter tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="c1">// Otherwise, return all of the tasks</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({},</span> <span class="p">{</span><span class="na">sort</span><span class="p">:</span> <span class="p">{</span><span class="na">createdAt</span><span class="p">:</span> <span class="o">-</span><span class="mi">1</span><span class="p">}});</span>
<span class="p">}</span>
<span class="p">},</span>
<span class="na">hideCompleted</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Session</span><span class="p">.</span><span class="kd">get</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">incompleteCount</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">find</span><span class="p">({</span><span class="na">checked</span><span class="p">:</span> <span class="p">{</span><span class="na">$ne</span><span class="p">:</span> <span class="kc">true</span><span class="p">}}).</span><span class="nx">count</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">body</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">submit .new-task</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Prevent default browser form submit</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">preventDefault</span><span class="p">();</span>
<span class="c1">// Get value from form element</span>
<span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span><span class="p">;</span>
<span class="c1">// Insert a task into the collection</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">addTask</span><span class="dl">"</span><span class="p">,</span> <span class="nx">text</span><span class="p">);</span>
<span class="c1">// Clear form</span>
<span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">text</span><span class="p">.</span><span class="nx">value</span> <span class="o">=</span> <span class="dl">""</span><span class="p">;</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">change .hide-completed input</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">event</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">Session</span><span class="p">.</span><span class="kd">set</span><span class="p">(</span><span class="dl">"</span><span class="s2">hideCompleted</span><span class="dl">"</span><span class="p">,</span> <span class="nx">event</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">checked</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">task</span><span class="p">.</span><span class="nx">helpers</span><span class="p">({</span>
<span class="na">isOwner</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span><span class="p">.</span><span class="nx">owner</span> <span class="o">===</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Template</span><span class="p">.</span><span class="nx">task</span><span class="p">.</span><span class="nx">events</span><span class="p">({</span>
<span class="dl">"</span><span class="s2">click .toggle-checked</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// Set the checked property to the opposite of its current value</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">setChecked</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">,</span> <span class="o">!</span> <span class="k">this</span><span class="p">.</span><span class="nx">checked</span><span class="p">);</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">click .delete</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">deleteTask</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">);</span>
<span class="p">},</span>
<span class="dl">"</span><span class="s2">click .toggle-private</span><span class="dl">"</span><span class="p">:</span> <span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">call</span><span class="p">(</span><span class="dl">"</span><span class="s2">setPrivate</span><span class="dl">"</span><span class="p">,</span> <span class="k">this</span><span class="p">.</span><span class="nx">_id</span><span class="p">,</span> <span class="o">!</span> <span class="k">this</span><span class="p">.</span><span class="kr">private</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
<span class="nx">Accounts</span><span class="p">.</span><span class="nx">ui</span><span class="p">.</span><span class="nx">config</span><span class="p">({</span>
<span class="na">passwordSignupFields</span><span class="p">:</span> <span class="dl">"</span><span class="s2">USERNAME_ONLY</span><span class="dl">"</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="nx">Meteor</span><span class="p">.</span><span class="nx">methods</span><span class="p">({</span>
<span class="na">addTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">text</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">// Make sure the user is logged in before inserting a task</span>
<span class="k">if</span> <span class="p">(</span><span class="o">!</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">insert</span><span class="p">({</span>
<span class="na">text</span><span class="p">:</span> <span class="nx">text</span><span class="p">,</span>
<span class="na">createdAt</span><span class="p">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">(),</span>
<span class="na">owner</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">(),</span>
<span class="na">username</span><span class="p">:</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">user</span><span class="p">().</span><span class="nx">username</span>
<span class="p">});</span>
<span class="p">},</span>
<span class="na">deleteTask</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">task</span> <span class="o">=</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">task</span><span class="p">.</span><span class="kr">private</span> <span class="o">&&</span> <span class="nx">task</span><span class="p">.</span><span class="nx">owner</span> <span class="o">!==</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// If the task is private, make sure only the owner can delete it</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">remove</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="p">},</span>
<span class="na">setChecked</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="nx">setChecked</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">task</span> <span class="o">=</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">task</span><span class="p">.</span><span class="kr">private</span> <span class="o">&&</span> <span class="nx">task</span><span class="p">.</span><span class="nx">owner</span> <span class="o">!==</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="c1">// If the task is private, make sure only the owner can check it off</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="p">{</span> <span class="na">checked</span><span class="p">:</span> <span class="nx">setChecked</span><span class="p">}</span> <span class="p">});</span>
<span class="p">},</span>
<span class="na">setPrivate</span><span class="p">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="nx">setToPrivate</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">var</span> <span class="nx">task</span> <span class="o">=</span> <span class="nx">Tasks</span><span class="p">.</span><span class="nx">findOne</span><span class="p">(</span><span class="nx">taskId</span><span class="p">);</span>
<span class="c1">// Make sure only the task owner can make a task private</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">task</span><span class="p">.</span><span class="nx">owner</span> <span class="o">!==</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nx">userId</span><span class="p">())</span> <span class="p">{</span>
<span class="k">throw</span> <span class="k">new</span> <span class="nx">Meteor</span><span class="p">.</span><span class="nb">Error</span><span class="p">(</span><span class="dl">"</span><span class="s2">not-authorized</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="nx">Tasks</span><span class="p">.</span><span class="nx">update</span><span class="p">(</span><span class="nx">taskId</span><span class="p">,</span> <span class="p">{</span> <span class="na">$set</span><span class="p">:</span> <span class="p">{</span> <span class="na">private</span><span class="p">:</span> <span class="nx">setToPrivate</span> <span class="p">}</span> <span class="p">});</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<blockquote>
<p>“注意现在任何人都可以删除公共任务,代码再做一些微调,就可以实现仅任务的所有者才能删除他们”</p>
</blockquote>
<p>好了,我们完成了个人任务功能!现在我们的应用已经安全了,可以防止攻击者浏览或者修改他人的任务了!</p>
meteor入门学习笔记一:开始
2015-12-09T00:00:00+00:00
http://www.blogways.net/blog/2015/12/09/meteor-tutorial-1
<p>这几天在学习Meteor,当前版本为:<code class="language-plaintext highlighter-rouge">1.2.1</code>。学习的主要资料来自官网,笔记如下.</p>
<h2 id="一安装">一、安装</h2>
<p>Meteor目前支持OS X,Windows,和Linux主机,安装非常方便。
在Mac OS X 10.7(Lion)以上版本,和基于x86/x86_64架构的Linux主机上,可以通过命令行进行安装。
Window版本的支持Windows 7,Windows 8.1,Windows Server 2008和Windows Server 2012。</p>
<h3 id="11-os-x-或-linxu系统">1.1 OS X 或 Linxu系统</h3>
<p>使用下面命令安装:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl https://install.meteor.com/ |sh
</code></pre></div></div>
<p>可以使用下面命令删除:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">rm</span> /usr/local/bin/meteor
<span class="nb">rm</span> <span class="nt">-rf</span> ~/.meteor
</code></pre></div></div>
<h3 id="12-windows系统">1.2 Windows系统</h3>
<p>猛戳<a href="https://install.meteor.com/windows">这里</a>.</p>
<h2 id="二快速开始">二、快速开始</h2>
<p>安装Meteor之后,创建一个应用程序非常简单。由于Meteor提供免费的托管,你也可以通过命令,很容易地在线部署到它的免费服务器上,供世界各地的人来浏览。</p>
<p>创建一个新的应用程序:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor create ~/my_cool_app
</code></pre></div></div>
<p>在本地运行:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/my_cool_app
meteor
</code></pre></div></div>
<p>本地访问: <a href="http://localhost:3000">http://localhost:3000</a></p>
<p>在线部署到他提供的免费服务器上:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor deploy myapp.meteor.com
</code></pre></div></div>
<h2 id="三原则">三、原则</h2>
<ul>
<li><strong>只传输数据。</strong> Meteor不通过网络发送Html,服务器端仅发送数据,由客户端负责渲染页面。</li>
<li><strong>Javascript是唯一的开发语言。</strong>整个程序,无论服务器端还是客户端都由Javascript开发。</li>
<li><strong>随处都可访问数据库。</strong>无论服务端还是客户端,都使用相同的方法访问数据库。</li>
<li><strong>延迟补偿。</strong> 在客户端,采用预读数据和模拟模型,使与服务器的交互如同立即返回.</li>
<li><strong>全栈响应式。</strong> Meteor中,默认是实时处理。从数据库到模版层,所有的层级,都是按需自动更新。</li>
<li><strong>拥抱开源。</strong>Meteor本身就是开源的,集成了很多开源的工具与框架.</li>
<li><strong>简单而高效。</strong>让每一点都很简单,Meteor的主要功能代码都是一些简练而经典的API。</li>
</ul>
<h2 id="四目录结构">四、目录结构</h2>
<p>Meteor有固定的目录结构,它可以据此自动加载工程中的各类文件。</p>
<h3 id="41-默认的加载方式">4.1 默认的加载方式</h3>
<p>下文<code class="language-plaintext highlighter-rouge">4.2 特定目录</code>以外的文件,按以下逻辑处理:</p>
<ol>
<li>HTML模版被编译,发送给客户端。</li>
<li>CSS文件也被发送至客户端。在生产模式下,他们会把合并及压缩。</li>
<li>Javascript文件会被客户端及服务端调用。可以使用<code class="language-plaintext highlighter-rouge">Meteor.isClient</code>和<code class="language-plaintext highlighter-rouge">Meteor.isServer</code>,在代码中区分执行的环境。</li>
</ol>
<p>如果你想做更多的控制,可以使用Meteor的特定目录。</p>
<h3 id="42-特定目录">4.2 特定目录</h3>
<ul>
<li><strong><code class="language-plaintext highlighter-rouge">/client</code></strong> 这个目录下的所有文件都会被发送到客户端。你可以将 HTML、CSS及和UI有关的Javascript文件都放在这里。</li>
<li><strong><code class="language-plaintext highlighter-rouge">/server</code></strong> 这个目录下的所有文件只在服务器端使用,不会暴露给客户端</li>
<li><strong><code class="language-plaintext highlighter-rouge">/public</code></strong> 这个目录下的文件也是给客户端使用的,你可以存放诸如图片的一些资源。例如:存放一个图片于<code class="language-plaintext highlighter-rouge">/public/background.png</code>。那么你可以在HTML模版中,如此引用:<code class="language-plaintext highlighter-rouge"><img src='/background.png'/></code>;或者在css文件中,如此引用:<code class="language-plaintext highlighter-rouge">background-image:
url(/background.png)</code>。需要注意的是:<strong><code class="language-plaintext highlighter-rouge">/public</code>不要出现在图片资源URL中。</strong></li>
<li><strong><code class="language-plaintext highlighter-rouge">/private</code></strong> 这个目录下的文件只能在服务器端,被<code class="language-plaintext highlighter-rouge">Assets</code>API所使用。不对客户端开放。</li>
</ul>
<p>文件的加载顺序依次按如下规则:</p>
<ol>
<li>HTML模版文件总是最先被加载;</li>
<li><code class="language-plaintext highlighter-rouge">main.</code>开头的文件,在同类文件中最后被加载;</li>
<li>路径中包含<code class="language-plaintext highlighter-rouge">lib/</code>的文件被接着加载;</li>
<li>子目录中的文件被接着加载(也就是说,父目录中的文件后加载);</li>
<li>同一目录下的不同文件按文件名的字母顺序加载;</li>
</ol>
<p>加载顺序,举例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nav.html
main.html
client/lib/methods.js
client/lib/styles.js
lib/feature/styles.js
lib/collections.js
client/feature-y.js
feature-x.js
client/main.js
</code></pre></div></div>
<h2 id="五构建移动端应用">五、构建移动端应用</h2>
<p>如果使用了Meteor构建了你的web应用,那么你可以很容易地给你的应用打一个原始的包,发布到Google Play商店或者iOS应用商店,做到这些仅仅需要几个命令而已。Meteor在桌面版和移动版之间,已经定制了很多相同的包及API,所以,你不需要太关心移动端应用开发的一些边边角角的琐事。</p>
<h3 id="51-环境准备">5.1 环境准备</h3>
<p><strong>iOS</strong></p>
<p>要想使你构建的应用能够在iOS设备或者模拟器上运行,你需要安装<a href="https://itunes.apple.com/us/app/xcode/id497799835?ls=1&mt=12">Xcode</a>.</p>
<p><strong>Android</strong></p>
<p>你需要安装Android开发工具以及Java JDK。
Mac环境下的安装可以看<a href="https://github.com/meteor/meteor/wiki/Mobile-Development-Install:-Android-on-Mac">这里</a>,Linux下的安装细节看<a href="">这里</a>.</p>
<h3 id="52-添加删除平台">5.2 添加删除平台</h3>
<p>每个Meteor工程都可以设置自己的可以适配的平台。使用<code class="language-plaintext highlighter-rouge">meteor add-platform</code>命令来向工程中添加平台。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">meteor add-platform ios</code> 在工程中添加iOS平台</li>
<li><code class="language-plaintext highlighter-rouge">meteor add-platform android</code> 在工程中添加Android平台</li>
<li><code class="language-plaintext highlighter-rouge">meteor remove-platform ios android</code> 从工程中删除iOS和Android平台</li>
<li><code class="language-plaintext highlighter-rouge">meteor list-platforms</code> 列出工程的目标平台</li>
</ul>
<h3 id="53-运行">5.3 运行</h3>
<p><strong>在模拟器中运行</strong></p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor run android <span class="c"># for Android</span>
meteor run ios <span class="c"># for iOS</span>
</code></pre></div></div>
<p><strong>在设备上运行</strong></p>
<p>用USB线连接设备与电脑,输入命令:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor run android-device <span class="c"># for Android</span>
meteor run ios-device <span class="c"># for iOS</span>
</code></pre></div></div>
<p>可以指定本地服务器的端口:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor run android-device <span class="nt">-p</span> <<span class="nb">local </span>port>
meteor run ios-device <span class="nt">-p</span> <<span class="nb">local </span>port>
</code></pre></div></div>
<p>你也可以使用<code class="language-plaintext highlighter-rouge">--mobile-server</code>参数来指定客户端访问的服务器ip:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor run android-device <span class="nt">--mobile-server</span> <host>:<port>
meteor run io-device <span class="nt">--mobile-server</span> <host>:<port>
</code></pre></div></div>
<p>如果运行出错,可以通过<code class="language-plaintext highlighter-rouge">--verbose</code>打开详细日志:</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>meteor run android-device <span class="nt">--verbose</span>
</code></pre></div></div>
<h3 id="54-cordova侧javascript代码">5.4 Cordova侧Javascript代码</h3>
<p>在Javascript文件中,类似可以<code class="language-plaintext highlighter-rouge">Meteor.isServer</code>和<code class="language-plaintext highlighter-rouge">Meteor.isClient</code>来区分服务器端代码和客户端代码,我们也可以使用<code class="language-plaintext highlighter-rouge">Meteor.isCordova</code>来区分Cordova侧的代码,这些代码就只会在移动设备中运行。</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isServer</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">仅在服务器中运行</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isClient</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">在浏览器和移动端App中运行</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">if</span> <span class="p">(</span><span class="nx">Meteor</span><span class="p">.</span><span class="nx">isCordova</span><span class="p">)</span> <span class="p">{</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="dl">"</span><span class="s2">仅在移动端App中运行</span><span class="dl">"</span><span class="p">);</span>
<span class="p">}</span>
</code></pre></div></div>
<p>另外,有些函数依赖Cordova插件,那么需要包在<code class="language-plaintext highlighter-rouge">Meteor.startup()</code>代码块内。比如:</p>
<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">Meteor</span><span class="p">.</span><span class="nx">startup</span><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
<span class="c1">// 正确的方式</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">geolocation</span><span class="p">.</span><span class="nx">getCurrentPosition</span><span class="p">(</span><span class="nx">success</span><span class="p">);</span>
<span class="p">});</span>
<span class="c1">// 错误,无法正常工作</span>
<span class="nb">navigator</span><span class="p">.</span><span class="nx">geolocation</span><span class="p">.</span><span class="nx">getCurrentPosition</span><span class="p">(</span><span class="nx">success</span><span class="p">);</span>
</code></pre></div></div>
<p>更多细节看<a href="https://github.com/meteor/mobile-packages">这里</a>。</p>
<h3 id="55-配置应用的图标和元数据">5.5 配置应用的图标和元数据</h3>
<p>可以在<code class="language-plaintext highlighter-rouge">mobile-config.js</code>文件中配置移动App的图标、标题、版本号,启动界面等等元数据。</p>
<h3 id="56-更多">5.6 更多</h3>
<p>更多信息可以查看<a href="https://github.com/meteor/meteor/wiki/Meteor-Cordova-integration">这里</a>。</p>
<h2 id="六参考">六、参考</h2>
<ul>
<li>http://docs.meteor.com/</li>
<li>http://stackoverflow.com/questions/24686971/how-can-i-completely-uninstall-and-then-reinstall-meteor-js</li>
<li>https://github.com/meteor/meteor/wiki/Meteor-Cordova-integration</li>
</ul>
jQuery form plugin中文使用说明
2015-11-29T00:00:00+00:00
http://www.blogways.net/blog/2015/11/29/jQuery-form-plugin
<h1 id="jquery插件form表单插件">jQuery插件–Form表单插件</h1>
<p>jQuery Form插件是一个优秀的Ajax表单插件,可以非常容易地、无侵入地升级HTML表单以支持Ajax。 插件里面主要的方法, ajaxForm 和 ajaxSubmit, 能够从form组件里采集信息确定如何处理表单的提交过程。两个方法都支持众多的可选参数,能够让你对表单里数据的提交做到完全的控制。这让采用AJAX方式提交一个表单的过程简单的不能再简单了!</p>
<h2 id="一入门指导">一、入门指导</h2>
<ol>
<li>
<p>在你的页面里写一个简单的表单,不需要任何特殊标记</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <form id="myForm" action="comment.php" method="post">
Name: <input type="text" name="name" />
Comment: <textarea name="comment"></textarea>
<input type="submit" value="Submit Comment" />
</form>
</code></pre></div> </div>
</li>
<li>
<p>引入jQuery和Form Plugin Javascript脚本文件并且添加几句简单的代码让页面在DOM加载完成后初始化表单:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <head>
<script type="text/javascript" src="path/to/jquery.js"></script>
<script type="text/javascript" src="path/to/form.js"></script>
<script type="text/javascript">
// wait for the DOM to be loaded
$(document).ready(function() {
// bind 'myForm' and provide a simple callback function
$('#myForm').ajaxForm(function() {
alert("Thank you for your comment!");
});
});
</script>
</head> 这就行了! 当表单提交后 `name` 和 `comment` 的值就会被提交给 `comment.php`. 如果服务器端返回成功的状态,用户将会看到一句提示信息 `"Thank you"` 。
</code></pre></div> </div>
</li>
</ol>
<h2 id="二form-plugin-api">二、Form Plugin API</h2>
<p>Form Plugin API 里提供了很多有用的方法可以让你轻松的处理表单里的数据和表单的提交过程。</p>
<ul>
<li>
<p><strong>ajaxForm</strong><br />
预处理将要使用 <code class="language-plaintext highlighter-rouge">AJAX</code> 方式提交的表单,将所有需要用到的事件监听器添加到其中。它不是提交这个表单。 在页面的 <code class="language-plaintext highlighter-rouge">ready</code> 函数里使用 <code class="language-plaintext highlighter-rouge">ajaxForm</code> 来给你页面上的表单做这些 <code class="language-plaintext highlighter-rouge">AJAX</code> 提交的准备工作。 <code class="language-plaintext highlighter-rouge">ajaxForm</code> 需要零个或一个参数。这唯一的一个参数可以是一个回调函数或者是一个可选参数对象。并支持<strong><em>连环调用</em></strong><br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $('#myFormId').ajaxForm(); 此方法适用于以表单提交方式处理 `ajax `技术(需要提供表单的`action`、`id`、`method`,最好在表单中提供 `submit` 按钮)它大大简化了使用 `ajax` 技术提交表单时的数据传递问题,使用`ajaxForm()`你不需要逐个的以 `JavaScript` 的方式获取每个表单属性的值,并且也不需要在请求路径后面通过 `url` 重写的方式传递数据。`ajaxForm()` 会自动收集当前表单中每个属性的值,然后将其以表单提交的方式提交到目标 `url`。这种方式提交数据较安全,并且使用起来更简单,不必写过多冗余的 `JavaScript` 代码:
$(document).ready(function(){
registerForm'表单id
data回调数据
$('#registerForm').ajaxForm(function(data){
alert(data);//弹出ajax请求后的回调结果
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>ajaxSubmit</strong> <br />
立即通过AJAX方式提交表单。最常见的用法是对用户提交表单的动作进行响应时调用它。 ajaxForm 需要零个或一个参数。唯一的一个参数可以是一个回调函数或者是一个可选参数对象。支持连环调用。<br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 绑定表单提交事件处理器t
$('#myFormId').submit(function() {
// 提交表单
$(this).ajaxSubmit();
// return false,这样可以阻止正常的浏览器表单提交和页面转向
return false;
}); 适用于以事件的机制以 `ajax` 提交 `form` 表单(超链接、图片的 click 事件),该方法作用与 `ajaxForm()` 类似,但它更为灵活,因为他依赖于事件机制,只要有事件存在就能使用该方法。你只需指定该 `form` 的 `action` 属性即可,不需要提供 `submit` 按钮。
$(document).ready(function(){
$('#btn').click(function(){
$('#registerForm').ajaxSubmit(function(data){
alert(data);
});
return false;
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>formSerialize()</strong> <br />
将表单序列化成一个查询字符串,这个方法将返回以下格式的字符串:name1=value1&name2=value2。不能连环调用,此方法返回一个字符串。<br />
实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var queryString = $('#myFormId').formSerialize();
// 现在可以使用$.get、$.post、$.ajax等来提交数据
$.post('myscript.php', queryString);
</code></pre></div> </div>
</li>
<li>
<p><strong>fieldSerialize()</strong> <br />
将表单里的元素序列化成字符串。当你只需要将表单的部分元素序列化时可以用到这个方法。 这个方法将返回一个形如: <code class="language-plaintext highlighter-rouge">name1=value1&name2=value2</code> 的字符串。不能连环调用, 这个方法返回的是一个字符串。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var queryString = $('#myFormId .specialFields').fieldSerialize();
</code></pre></div> </div>
</li>
<li>
<p><strong>fieldValue()</strong> <br />
返回匹配插入数组中的表单元素值。从0.91版起,该方法将总是以数组的形式返回数据。如果元素值被判定可能无效,则数组为空,否则它将包含一个或多于一个的元素值。不能连环调用,此方法返回一个数组。
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 取得密码输入值
var value = $('#myFormId :password').fieldValue();
alert('The password is: ' + value[0]);
</code></pre></div> </div>
</li>
<li>
<p><strong>resetForm()</strong> <br />
通过调用表单元素原有的 <code class="language-plaintext highlighter-rouge">DOM</code> 方法,将表单恢复到初始状态。此方法可以连环调用,<br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $('#myFormId').resetForm();
</code></pre></div> </div>
</li>
<li>
<p><strong>clearForm()</strong> <br />
清除表单元素。该方法将所有的文本(text)输入字段、密码(password)输入字段和文本区域(textarea)字段置空,清除任何<code class="language-plaintext highlighter-rouge">select</code>元素中的选定,以及将所有的单选(radio)按钮和多选(checkbox)按钮重置为非选定状态。此方法能连环调用。<br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $('#myFormId').clearForm();
</code></pre></div> </div>
</li>
<li>
<p><strong>clearFields()</strong> <br />
清除字段元素。只有部分表单元素需要清除时才方便使用。此方法能连环调用。 <br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $('#myFormId .specialFields').clearFields(); **Options对象** `ajaxForm `和 `ajaxSubmit` 都支持众多的选项参数,这些选项参数可以使用一个 `Options` 对象来提供。`Options` 只是一个 `JavaScript` 对象,它包含了如下一些属性与值的集合:
</code></pre></div> </div>
</li>
<li>
<p><strong>target</strong> <br />
指明页面中由服务器响应进行更新的元素。元素的值可能被指定为一个 <code class="language-plaintext highlighter-rouge">jQuery</code> 选择器字符串,一个 <code class="language-plaintext highlighter-rouge">jQuery</code> 对象,或者一个 <code class="language-plaintext highlighter-rouge">DOM</code> 元素。 <br />
默认值:null。</p>
</li>
<li>
<p><strong>url</strong> <br />
表单提交的地址。 <br />
缺省值: 表单的 <code class="language-plaintext highlighter-rouge">action</code> 的值</p>
</li>
<li>
<p><strong>type</strong><br />
表单提交的方式,<code class="language-plaintext highlighter-rouge">'GET'</code> 或 <code class="language-plaintext highlighter-rouge">'POST'</code>.
缺省值: 表单的 <code class="language-plaintext highlighter-rouge">method</code> 的值 (如果没有指明则认为是 ‘GET’)</p>
</li>
<li>
<p><strong>beforeSubmit</strong><br />
表单提交前执行的方法。这个可以用在表单提交前的预处理,或表单校验。如果 <code class="language-plaintext highlighter-rouge">'beforeSubmit'</code> 指定的函数返回 <code class="language-plaintext highlighter-rouge">false</code>,则表单不会被提交。 <code class="language-plaintext highlighter-rouge">'beforeSubmit'</code> 函数调用时需要3个参数:数组形式的表单数据,<code class="language-plaintext highlighter-rouge">jQuery</code> 对象形式的表单对象,可选的用来传递给 <code class="language-plaintext highlighter-rouge">ajaxForm/ajaxSubmit </code>的对象。 数组形式的表单数据是下面这样的格式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [ { name: 'username', value: 'jresig' }, { name: 'password', value: 'secret' } ] 缺省值:null
</code></pre></div> </div>
</li>
<li>
<p><strong>success</strong> <br />
当表单提交后执行的函数。 如果<code class="language-plaintext highlighter-rouge">'success'</code> 回调函数被指定,当<code class="language-plaintext highlighter-rouge">server</code>端返回对表单提交的响应后,这个方法就会被执行。 <code class="language-plaintext highlighter-rouge">responseText</code> 和 <code class="language-plaintext highlighter-rouge">responseXML</code> 的值会被传进这个参数 (这个要依赖于dataType的类型).<br />
缺省值: <code class="language-plaintext highlighter-rouge">null </code></p>
</li>
<li>
<p><strong>dataType</strong> <br />
指定服务器响应返回的数据类型。其中之一: null, <code class="language-plaintext highlighter-rouge">'xml'</code>, <code class="language-plaintext highlighter-rouge">'script'</code>, 或者 <code class="language-plaintext highlighter-rouge">'json'</code>. 这个 <code class="language-plaintext highlighter-rouge">dataType</code> 选项用来指示你如何去处理server端返回的数据。 这个和 <code class="language-plaintext highlighter-rouge">jQuery.httpData</code> 方法直接相对应。 下面就是可以用的选项: <br />
<strong><em>‘xml’:</em></strong> 如果 <code class="language-plaintext highlighter-rouge">dataType == 'xml'</code> 则 <code class="language-plaintext highlighter-rouge">server</code> 端返回的数据被当作是 XML 来处理, 这种情况下’success’指定的回调函数会被传进去 <code class="language-plaintext highlighter-rouge">responseXML</code> 数据 <br />
<strong><em>‘json’</em></strong>: 如果 <code class="language-plaintext highlighter-rouge">dataType == 'json'</code> 则 <code class="language-plaintext highlighter-rouge">server</code> 端返回的数据将会被执行,并传进<code class="language-plaintext highlighter-rouge">'success'</code>回调函数 <br />
<strong><em>‘script’</em></strong>: 如果 <code class="language-plaintext highlighter-rouge">dataType == 'script'</code> 则<code class="language-plaintext highlighter-rouge">server</code>端返回的数据将会在上下文的环境中被执行 <br />
缺省值: <code class="language-plaintext highlighter-rouge">null</code></p>
</li>
<li>
<p><strong>semantic</strong><br />
一个布尔值,用来指示表单里提交的数据的顺序是否需要严格按照语义的顺序。一般表单的数据都是按语义顺序序列化的,除非表单里有一个 <code class="language-plaintext highlighter-rouge">type="image"</code> 元素. 所以只有当表单里必须要求有严格顺序并且表单里有 <code class="language-plaintext highlighter-rouge">type="image"</code> 时才需要指定这个。<br />
缺省值: <code class="language-plaintext highlighter-rouge">false</code></p>
</li>
<li>
<p><strong>resetForm</strong><br />
布尔值,指示表单提交成功后是否需要重置。<br />
缺省值: <code class="language-plaintext highlighter-rouge">null</code></p>
</li>
<li>
<p><strong>clearForm</strong><br />
布尔值,指示表单提交成功后是否需要清空。<br />
缺省值: <code class="language-plaintext highlighter-rouge">null</code></p>
</li>
<li>
<p><strong>iframe</strong><br />
布尔值,用来指示表单是否需要提交到一个<code class="language-plaintext highlighter-rouge">iframe</code>里。 这个用在表单里有<code class="language-plaintext highlighter-rouge">file</code>域要上传文件时。更多信息请参考 代码示例 页面里的<code class="language-plaintext highlighter-rouge">File Uploads</code> 文档。 <br />
缺省值: false<br />
例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 准备好Options对象
var options = {
target: '#divToUpdate',
url: 'comment.php',
success: function() {
alert('Thanks for your comment!');
} };
// 将options传给ajaxForm
$('#myForm').ajaxForm(options); **注意**:`Options` 对象还可以用来将值传递给 `jQuery` 的`$.ajax` 方法。如果你熟悉 `$.ajax` 所支持的 `options` ,你可以利用它们来将 `Options` 对象传递给 `ajaxForm` 和 `ajaxSubmit`。
</code></pre></div> </div>
</li>
</ul>
jQuery-datatable中文文档
2015-11-25T00:00:00+00:00
http://www.blogways.net/blog/2015/11/25/jQuery-datatableAPI
<h2 id="一datatable中文api实例及基本参数">一、Datatable中文API—实例及基本参数</h2>
<h3 id="一实例">一、实例</h3>
<ol>
<li>
<p><strong>前端JS:</strong></p>
<script type="text/javascript" language="javascript">
</script> <div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function() {
$("#example").dataTable({
"bPaginate": true, //开关,是否显示分页器
"bInfo": true, //开关,是否显示表格的一些信息
"bFilter": true, //开关,是否启用客户端过滤器
"sDom": "<>lfrtip<>",
"bAutoWith": false,
"bDeferRender": false,
"bJQueryUI": false, //开关,是否启用JQueryUI风格
"bLengthChange": true, //开关,是否显示每页大小的下拉框
"bProcessing": true,
"bScrollInfinite": false,
"sScrollY": "800px", //是否开启垂直滚动,以及指定滚动区域大小,可设值:'disabled','2000px'
"bSort": true, //开关,是否启用各列具有按列排序的功能
"bSortClasses": true,
"bStateSave": false, //开关,是否打开客户端状态记录功能。这个数据是记录在cookies中的,打开了这个记录后,即使刷新一次页面,或重新打开浏览器,之前的状态都是保存下来的- ------当值为true时aoColumnDefs不能隐藏列
"sScrollX": "50%", //是否开启水平滚动,以及指定滚动区域大小,可设值:'disabled','2000%'
"aaSorting": [[0, "asc"]],
"aoColumnDefs": [{ "bVisible": false, "aTargets": [0]}]//隐藏列
"sDom": '<"H"if>t<"F"if>',
"bAutoWidth": false, //自适应宽度
"aaSorting": [[1, "asc"]],
"sPaginationType": "full_numbers",
"oLanguage": {
"sProcessing": "正在加载中......",
"sLengthMenu": "每页显示 _MENU_ 条记录",
"sZeroRecords": "对不起,查询不到相关数据!",
"sEmptyTable": "表中无数据存在!",
"sInfo": "当前显示 _START_ 到 _END_ 条,共 _TOTAL_ 条记录",
"sInfoFiltered": "数据表中共为 _MAX_ 条记录",
"sSearch": "搜索",
"oPaginate": {
"sFirst": "首页",
"sPrevious": "上一页",
"sNext": "下一页",
"sLast": "末页"
}
} //多语言配置
});
}); </script>
</code></pre></div> </div>
<p><strong>最简单的就是零配置方式</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable();
});
</code></pre></div> </div>
</li>
<li>
<p><strong>html代码:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <div class="row-fluid">
<table id="example" class="table table-striped table-hover">
<thead>
<tr>
<% for (var i = 0; i < tabColNames.length; i++) { %>
<th style="text-align: center"><%= tabColNames[i] %></th>
<% } %>
</tr>
</thead>
<tbody>
</tbody>
</table>
</div>
</code></pre></div> </div>
</li>
<li>
<p><strong>后端JS</strong></p>
<p>后端就是处理前端发来的请求,取数据、处理数据。还有一种方式是不需要从向后端发送请求去取数据,可以直接在前端直接加载已有的数据,需要<strong><em>注意</em></strong>的是在页面在进行下一次加载页面的时候需要对上一次的datatable进行清除,用以解决数据缓存带来的问题。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $("#example").dataTable().fnClearTable();
$("#example").dataTable().fnDestroy();
</code></pre></div> </div>
</li>
</ol>
<h3 id="二参数说明">二、参数说明</h3>
<ul>
<li>
<p><strong>bAutoWidth:</strong><br />
默认值:true<br />
类型:boolean<br />
启用或禁用自动列宽度的计算。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function () {
$('#example').dataTable( {
"bAutoWidth": false //关闭后,表格将不会自动计算表格大小,在浏览器大化小化的时候会挤在一坨
} );
} );
</code></pre></div> </div>
</li>
<li>
<p><strong>bDeferRender:</strong><br />
默认值:true<br />
延期渲染,可以有个速度的提升,当datatable使用Ajax或者JS源表的数据。这个选项设置为true,将导致datatable推迟创建表元素的每个元素,直到他们都创建完成–目的就是节省大量的时间。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function() {
var oTable = $('#example').dataTable( {
"sAjaxSource": "sources/arrays.txt",
"bDeferRender": true
} );
} );
</code></pre></div> </div>
</li>
<li>
<p><strong>bFilter:</strong><br />
默认值是:true<br />
是否对数据进行过滤,数据过滤十分灵活,允许终端用户输入多个用空格分隔开的关键字。匹配包含这些关键字的行,即使管子的顺序不是用户输入的顺序,过滤操作会跨列进行匹配,关键字可以分布在一行中的不同列。需要<strong>注意</strong>的是如果你想在DataTable中使用过滤,该选项必须设置为true,如果想移除默认过滤输入框但是保留过滤功能,请设置为false(API没写,推测是false)</p>
</li>
<li>
<p><strong>bInfo:</strong><br />
默认值:true<br />
是否显示表格信息,是指当前页面上显示的数据的信息,如果有过滤操作执行,也会显示过滤操作的信息</p>
</li>
<li>
<p><strong>bJQueryUI:</strong><br />
默认值:false<br />
是否开启jQuery UI ThemeRoller支持,需要一些ThemeRoller使用的标记,这些标记有些与DataTable传统使用的有轻微的差异,有些是额外附加的</p>
</li>
<li>
<p><strong>bLengthChange:</strong><br />
默认值:true<br />
允许终端用户从一个选择列表中选择分页的页数,页数为10,25和100,需要分页组件bPaginate的支持</p>
</li>
<li>
<p><strong>bPaginage:</strong><br />
默认值:true<br />
是否开启分页功能,即使设置为false,仍然会有一个默认的<前进,后退>分页组件</p>
</li>
<li>
<p><strong>bProcessing:</strong><br />
默认值:false<br />
当表格在处理的时候(比如排序操作)是否显示“处理中…”<br />
当表格的数据中的数据过多以至于对其中的记录进行排序时会消耗足以被察觉的时间的时候,该选项会有些用处</p>
</li>
<li>
<p><strong>bScrollInfinite:</strong><br />
默认值:false<br />
是否开启不限长度的滚动条(和sSrolly属性结合使用),不限制长度的滚动条意味着当用户拖动滚动条的时候datatable会不断加载数据。<br />
当数据集十分大的时候会有些用处,该选项无法和分页选项同时使用,分页选项会被自动禁止,</p>
</li>
<li>
<p><strong>bServerSide:</strong><br />
默认值:false<br />
配置DataTable使用服务器端处理,注意,sAjaxSource参数必须指定,以便给DataTable一个为每一行获取数据的数据源</p>
</li>
<li>
<p><strong>bSort:</strong><br />
默认值:true<br />
是否开启列排序,对单独列的设置在每一列的bSortable选项中指定</p>
</li>
<li>
<p><strong>bSortClasses:</strong><br />
默认值:true<br />
是否在当前被排序的列上额外添加sorting_1,sorting_2,sorting_3三个class,当该列被排序的时候,可以切换其背景颜色<br />
该选项作为一个来回切换的属性会增加执行时间(当class被移除和添加的时候)<br />
当对大数据集进行排序的时候你或许希望关闭该选项</p>
</li>
<li>
<p><strong>bStateSave:</strong><br />
默认值:false<br />
是否开启状态保存,当选项开启的时候会使用一个cookie保存表格展示的信息的状态,例如分页信息,展示长度,过滤和排序等<br />
这样当终端用户重新加载这个页面的时候可以使用以前的设置</p>
</li>
<li>
<p><strong>sScrollX:</strong>“100%”<br />
默认值为空字符串,即无效<br />
是否开启垂直滚动,垂直滚动会驱使DataTable设置为给定的长度,任何溢出到当前视图之外的数据可以通过垂直滚动进行察看<br />
当在小范围区域中显示大量数据的时候,可以在分页和垂直滚动中选择一种方式,该属性可以是css设置,或者一个数字(作为像素量度来使用)</p>
</li>
<li>
<p><strong>自定义语言设计</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> "oLanguage":
{
"oAria":{
"sSortAscending": " - click/return to sort ascending",
/*
默认值为activate to sort column ascending
当一列被按照升序排序的时候添加到表头的ARIA标签,注意列头是这个字符串的前缀
*/
"sSortDescending": " - click/return to sort descending"
/*
默认值为activate to sort column ascending
当一列被按照升序降序的时候添加到表头的ARIA标签,注意列头是这个字符串的前缀 */
}
"oPaginate": {
"sFirst": "First page",
/*
默认值为First
当使用全数字类型的分页组件的时候,到第一页按钮上的文字
*/
"sLast": "Last page",
/*
默认值为Last
当使用全数字类型的分页组件的时候,到最后一页按钮上的文字
*/
"sNext": "Next page",
/*
默认值为Next
当使用全数字类型的分页组件的时候,到下一页按钮上的文字
*/
"sPrevious": "Previous page"
/*
默认值为Previous
当使用全数字类型的分页组件的时候,到前一页按钮上的文字
*/
}
"sEmptyTable": "No data available in table",
/*
默认值activate to sort column ascending
当表格中没有数据(无视因为过滤导致的没有数据)时,该字符串年优先与sZeroRecords显示
注意这是个可选参数,如果没有指定,sZeroRecrods会被使用(既不是默认值也不是给定的值)
*/
"sInfo": "Got a total of _TOTAL_ entries to show (_START_ to _END_)",
/*
默认值为Showing _START_ to _END_ of _TOTAL_ entries
该属性给终端用户提供当前页面的展示信息,字符串中的变量会随着表格的更新被动态替换,而且可以被任意移动和删除
*/
"sInfoEmpty": "No entries to show",
/*
默认值为Showing 0 to 0 of 0 entries
当表格中没有数据时展示的表格信息,通常情况下格式会符合sI默认值为空字符串nfo的格式
*/
"sInfoFiltered": " - filtering from _MAX_ records",
/*
默认值为(filtered from _MAX_ total entries)
当用户过滤表格中的信息的时候,该字符串会被附加到信息字符串的后面,从而给出过滤器强度的直观概念
*/
"sInfoPostFix": "All records shown are derived from real information.",
/*
默认值为空字符串
使用该属性可以很方便的向表格信息字符串中添加额外的信息,被添加的信息在任何时候都会被附加到表格信息组件的后面
sInfoEmpty和sInfoFiltered可以以任何被使用的方式进行结合
*/
"sInfoThousands": "'",
/*
默认值为','
DataTable有内建的格式化数字的工具,可以用来格式化表格信息中较大的数字
默认情况下会自动调用,可以使用该选项来自定义分割的字符
*/
"sLengthMenu": "Display _MENU_ records",
/*
默认值为Show _MENU_ entries
描述当分页组件的下拉菜单的选项被改变的时候发生的动作,'_MENU_'变量会被替换为默认的10,25,50,100
如果需要的话可以被自定义的下拉组件替换
*/
"sLoadingRecords": "Please wait - loading...",
/*
默认值为Loading...
当使用Ajax数据源和表格在第一次被加载搜集数据的时候显示的字符串,该信息在一个空行显示
向终端用户指明数据正在被加载,注意该参数在从服务器加载的时候无效,只有Ajax和客户端处理的时候有效
*/
"sProcessing": "DataTables is currently busy",
/*
默认值为Processing...
当表格处理用户动作(例如排序或者类似行为)的时候显示的字符串
*/
"sSearch": "Apply filter _INPUT_ to table",
/*
默认为Search:
描述用户在输入框输入过滤条件时的动作,变量'_INPUT_',如果用在字符串中
DataTable会使用用户输入的过滤条件替换_INPUT_为HTML文本组件,从而可以支配它(即用户输入的过滤条件)出现在信息字符串中的位置
如果变量没有指定,用户输入会自动添加到字符串后面
*/
"sUrl": "http://www.sprymedia.co.uk/dataTables/lang.txt",
/*
默认值为空字符串,即:无效
所有语言信息可以被存储在服务器端的文件中,DataTable可以根据该参数指定的URL去寻找
必须保存语言文件的URL信息,必须是JSON格式,对象和初始化中使用的oLanguage对象具有相同的属性
*/
"sZeroRecords": "No records to display"
/*
默认值为No matching records found
当对数据进行过滤操作后,如果没有要显示的数据,会在表格记录中显示该字符串
sEmptyTable只在表格中没有数据的时候显示,忽略过滤操作
*/
}
</code></pre></div> </div>
</li>
<li>
<p><strong>bRetrieve:</strong><br />
默认值:false<br />
使用指定的选择器检索表格,<strong>注意</strong>,如果表格已经被初始化,该参数会直接返回已经被创建的对象 <br />
并不会顾及你传递进来的初始化参数对象的变化,将该参数设置为true说明你确认已经明白这一点<br />
如果你需要的话,bDestroy可以用来重新初始化表格</p>
</li>
<li>
<p><strong>bScrollAutoCss:</strong><br />
默认值:true<br />
指明DataTable中滚动的标题元素是否被允许设置内边距和外边距等</p>
</li>
<li>
<p><strong>bScrollCollapse</strong><br />
默认值:false<br />
当垂直滚动被允许的时候,DataTable会强制表格视图在任何时候都是给定的高度(对布局有利)<br />
不过,当把数据集过滤到十分小的时候看起来会很古怪,而且页脚会留在最下面。当结果集的高度比给定的高度小时该参数会使表格高度自适应</p>
</li>
<li>
<p><strong>bSortCellsTop:</strong><br />
默认值:false<br />
是否允许DataTable使用顶部(默认为true)的单元格,或者底部(默认为false)的单元格,当使用复合表头的时候会有些用处</p>
</li>
<li>
<p><strong>iDeferLoading:</strong><br />
默认值:null<br />
当选项被开启的时候,DataTable在非加载第一次的时候不会向服务器请求数据,而是会使用页面上的已有数据(不会应用排序等),因此在加载的时候保留一个XmlHttpRequest,iDeferLoading被用来指明需要延迟加载,而且也用来通知DataTable一个满的表格有多少条数据。信息元素和分页会被正确保留</p>
</li>
<li>
<p><strong>iDisplayLength:</strong><br />
默认值:10<br />
单页显示的数据的条数,如果bLengthChange属性被开启,终端用户可以通过一个弹出菜单重写该数值</p>
</li>
<li>
<p><strong>iDisplayStart:</strong><br />
默认值:0<br />
当开启分页的时候,定义展示的记录的起始序号,不是页数,因此如果你每个分页有10条记录而且想从第三页开始,需要把该参数指定为20</p>
</li>
<li>
<p><strong>oSearch:</strong><br />
无默认值<br />
该参数允许你在初始化的时候使用已经定义的全局过滤状态,sSearch对象必须被定义,但是所有的其它选项都是可选的,当bRegex为true的时候,搜索字符串会被当作正则表达式,当为false(默认)的时候,会被直接当作一个字符串。当bSmart为true的时候,DataTable会使用使用灵活过滤策略(匹配任何可能的数据),为false的时候不会这样做</p>
</li>
<li>
<p><strong>sAjaxDataProp:</strong><br />
默认值:aadata<br />
当使用Ajax数据源或者服务器端处理的时候,DataTable会默认搜索aaData属性作为数据源,该选项允许变更数据源的名称,你可以使用JavaScript的点号对象表示法去访问多级网状数据源</p>
</li>
<li>
<p><strong>sAjaxSource:</strong> <br />
默认值:null<br />
该参数用来向DataTable指定加载的外部数据源(如果想使用现有的数据,请使用aData),可以简单的提供一个可以用来获得数据url或者JSON对象,该对象必须包含aaData,作为表格的数据源</p>
</li>
<li>
<p><strong>sDom:</strong><br />
默认值为<code class="language-plaintext highlighter-rouge">lfrtip (when bJQueryUI is false) or <"H"lfr>t<"F"ip> (when bJQueryUI is true)</code><br />
该初始化属性用来指定你想把各种控制组件注入到dom节点的位置(比如你想把分页组件放到表格的顶部),DIV元素(带或者不带自定的class)可以添加目标样式,下列语法被使用<br />
可供使用的选项</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 'l' - 长度改变
'f' - 过滤输入
't' - 表格
'i' - 信息
'p' - 分页
'r' - 处理 可供使用的常量
'H' - jQueryUI theme "header" classes('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')
'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix') 需要的语法
'<' 和 '>' - div元素
'<"class" and '>' - 带有class属性的div元素
'<"#id" and '>' - 带有id属性的div元素 例子
'<"wrapper"flipt>'
'<lf<t>ip>'
$(document).ready(function(){
$('#example').dataTable( {
"sDom": '<"top"i>rt<"bottom"flp><"clear"&lgt; '
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>sServerMethod:</strong><br />
默认值:GET<br />
设置使用Ajax方式调用的服务器端的处理方法或者Ajax数据源的HTTP请求方式</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable({
"bServerSide": true,
"sAjaxSource": "scripts/post.php",
"sServerMethod": "POST"
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnCreatedRow</strong>
无默认值<br />
当一个新的TR元素(并且所有TD子元素被插入)被创建或者被作为一个DOM资源被注册时调用该函数,允许操作该TR元素</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable({
"fnCreatedRow": function( nRow, aData, iDataIndex ){
// Bold the grade for all 'A' grade browsers
if ( aData[4] == "A" )
{
$('td:eq(4)', nRow).html( '<b>A</b>' );
}
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnDrawCallback</strong><br />
无默认值<br />
每当draw事件发生时调用该函数,允许你动态编辑新建的dom对象的任何属性</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function(){
$('#example').dataTable({
"fnDrawCallback": function(){
alert( 'DataTables has redrawn the table' );
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnInitComplete</strong><br />
无默认值<br />
当表格被初始化后调用该函数,通常DataTable会被持续初始化,并不需要该函数,可是,当使用异步的XmlHttpRequest从外部获得语言信息时,初始化并不是持续的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function(){
$('#example').dataTable({
"fnInitComplete": function(oSettings, json) {
alert( 'DataTables has finished its initialisation.' );
}
});
})
</code></pre></div> </div>
</li>
<li>
<p><strong>fnPreDrawCallback</strong><br />
无默认值<br />
在每一个表格draw事件发生前调用该函数,通过返回false来取消draw事件,其它任何的返回值,包括undefined都会导致draw事件的发生</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function(){
$('#example').dataTable({
"fnPreDrawCallback": function( oSettings ) {
if ( $('#test').val() == 1 ) {
return false;
}
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnRowCallback</strong><br />
无默认值
你可以通过该函数在每一个表格绘制事件发生之后,渲染到屏幕上之前,向表格里的每一行传递’处理过程’,该函数可以用来设置行的class名字等</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable({
"fnRowCallback": function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) {
// Bold the grade for all 'A' grade browsers
if ( aData[4] == "A" )
{
$('td:eq(4)', nRow).html( '<b>A</b>' );
}
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnServerData</strong><br />
无默认值<br />
你可以使用该参数重写从服务器获取数据的方法($.getJSON),从而使其更适合你的应用,例如你可以使用POST方式提交,或者从Google Gears或者AIR数据库获取数据</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // POST data to server
$(document).ready(function(){
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "xhr.php",
"fnServerData": function ( sSource, aoData, fnCallback ) {
$.ajax( {
"dataType": 'json',
"type": "POST",
"url": sSource,
"data": aoData,
"success": fnCallback
} );
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>fnServerParams</strong><br />
无默认值<br />
用来在向服务器发送Ajax请求时发送额外的数据,例如自定义的过滤信息,该函数使向服务器发送额外参数变得简单,传递进来的参数是DataTable建立的数据集合,你可以根据需要添加或者修改该集合</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable( {
"bProcessing": true,
"bServerSide": true,
"sAjaxSource": "scripts/server_processing.php",
"fnServerParams": function ( aoData ) {
aoData.push( { "name": "more_data", "value": "my_value" } );
}
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>aDataSort</strong><br />
默认为null,自动使用列序号作为默认<br />
在排序一列的时候同时将其它几列也排序,例如名和姓作为多列排序</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // Using aoColumnDefs
$(document).ready(function(){
$('#example').dataTable({
"aoColumnDefs": [
{ "aDataSort": [ 0, 1 ], "aTargets": [ 0 ] },
{ "aDataSort": [ 1, 0 ], "aTargets": [ 1 ] },
{ "aDataSort": [ 2, 3, 4 ], "aTargets": [ 2 ] }
]
});
});
// Using aoColumns
$(document).ready(function(){
$('#example').dataTable({
"aoColumns": [
{ "aDataSort": [ 0, 1 ] },
{ "aDataSort": [ 1, 0 ] },
{ "aDataSort": [ 2, 3, 4 ] },
null,
null
]
});
});
</code></pre></div> </div>
</li>
<li>
<p><strong>bSearchable</strong><br />
默认值:true<br />
是否在列上应用过滤</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function(){
$('#example').dataTable({
"aoColumnDefs": [
{ "bSearchable": false, "aTargets": [ 0 ] }
]} );
});
</code></pre></div> </div>
</li>
<li>
<p><strong>bSortable</strong><br />
默认值:true<br />
是否在某一列上开启排序</p>
</li>
<li>
<p><strong>bVisible</strong><br />
默认值:true<br />
是否展示某一列</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function() {
$('#example').dataTable( {
"aoColumnDefs": [
{ "bVisible": false, "aTargets": [ 0 ] }
] } );
} );
</code></pre></div> </div>
</li>
<li>
<p><strong>fnRender</strong><br />
无默认值<br />
自定义列中每个单元格被展示的时候调用的展示函数</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready(function() {
$('#example').dataTable( {
"aoColumnDefs": [
{
"fnRender": function ( o, val ) {
return o.aData[0] +' '+ o.aData[3];
},
"aTargets": [ 0 ]
}
]
} );
} );
</code></pre></div> </div>
</li>
<li>
<p><strong>iDataSort</strong><br />
默认值为-1,使用自动计算的列标<br />
当选择该列进行排序的时候,你希望调用排序操作的列的列号,该参数可以用来按隐藏列排序</p>
</li>
<li>
<p><strong>bServerSide</strong><br />
默认值:false<br />
配置使用服务器端处理的DataTable,注意sAjaxSource参数必须指定,以便给DataTable一个获取每行数据的数据源</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $(document).ready( function () {
$('#example').dataTable( {
"bServerSide": true,
"sAjaxSource": "xhr.php"
} );
} );
</code></pre></div> </div>
</li>
</ul>
express 4.x的迁移
2015-10-24T00:00:00+00:00
http://www.blogways.net/blog/2015/10/24/moving-to-express4.x
<p>原文:<a href="http://expressjs.com/en/guide/migrating-4.html">http://expressjs.com/en/guide/migrating-4.html</a></p>
<p>Express 4.x较Express 3.x进行了较大的改动,原来基于Express 3.x的项目无法直接升级到 4.x。本文对Express网站的内容进行了简单的总结,希望能够帮助大家更好的了解Express 4.x,同时方便大家迁移。</p>
<h2 id="一express-4x的主要变化">一、Express 4.x的主要变化</h2>
<p>1、重新撰写Express内核,取消了原来对于Connect的依赖<br />
2、移除了大部分Build-in的Middleware<br />
3、Middleware可以仅对特定url前缀的请求执行,并且支持url中的参数<br />
4、对Routing系统的扩展</p>
<h3 id="取消connect的依赖">取消Connect的依赖</h3>
<p>Express 3.x是基于Connect构建的。重构后的Express 4.x取消了对Connect的依赖,变成了完全独立的模块。但由于采用一致的Middleware处理方法,新的Express 4.x仍然对Connect的所有Middleware向下兼容,所以在Express中仍然可以使用Connect的Middleware。</p>
<h3 id="移除绝大多数build-in的middleware">移除绝大多数Build-in的Middleware</h3>
<p>Express 4.x的理念是仅专注于最核心的routing功能,而将其他组建的选择全部交由用户配置,一方面提供更好的灵活和定制性,另一方面可以始终让用户使用最新的Middleware而将其和Express的更新独立开来。其核心中仅保留了express.static,其余Middleware均需要通过npm安装并且require。下表中是主要移除的模块列表:</p>
<table>
<thead>
<tr>
<th>express 3</th>
<th>express 4</th>
</tr>
</thead>
<tbody>
<tr>
<td>express.bodyParser</td>
<td>body-parser+mnlter</td>
</tr>
<tr>
<td>express.comperss</td>
<td>compression</td>
</tr>
<tr>
<td>express.cookieSession</td>
<td>cookie-session</td>
</tr>
<tr>
<td>express.cookieParser</td>
<td>cookie-parser</td>
</tr>
<tr>
<td>express.logger</td>
<td>morgan</td>
</tr>
<tr>
<td>express.session</td>
<td>express.session</td>
</tr>
<tr>
<td>express.favicon</td>
<td>serve-favicon</td>
</tr>
<tr>
<td>express.responseTime</td>
<td>response-time</td>
</tr>
<tr>
<td>express.errorHandler</td>
<td>errorhandler</td>
</tr>
<tr>
<td>express.methodOverride</td>
<td>method-override</td>
</tr>
<tr>
<td>express.timeout</td>
<td>connect-timeout</td>
</tr>
<tr>
<td>express.vhost</td>
<td>vhost</td>
</tr>
<tr>
<td>express.csrf</td>
<td>csurf</td>
</tr>
<tr>
<td>express.directory</td>
<td>serve-index</td>
</tr>
<tr>
<td>express.static</td>
<td>serve-static</td>
</tr>
</tbody>
</table>
<p>下面是Express 4的中间件的完整列表。
在大多数情况下,你可以简单地用express 4替换旧版本express 3中对应的中间件。有关详细信息,请参阅GitHub的模块文档。</p>
<h3 id="appuse-accepts-parameters">app.use accepts parameters</h3>
<p>在第4版,你现在可以在具有可变参数的路径上加载中间件,并从路由处理程序中读取参数值。 例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use('/book/:id', function(req, res, next) {
console.log('ID:', req.params.id);
next();
}); ### The routing system 应用隐式加载路由中间件,所以,现在你不必担心 router 路由中间件加载的次序问题。
</code></pre></div></div>
<p>定义路由的方式没有发生变化,现在增加了两个新的特性来帮助组织路由系统。</p>
<blockquote>
<ul>
<li>新的方法 route,针对一个路由路径创建链式的路由处理器。</li>
<li>新的类 express.Router,创建模块化的路由处理器。</li>
</ul>
</blockquote>
<h3 id="approute-method">app.route() method</h3>
<p>新的 app.route 方法对特定的路由路径创建链式的路由处理器。由于可以在一个地方定义路径,这样有助于创建模块话的路由规则,减少重复。</p>
<p>路由的详细信息,可以参见 Route() 的文档。<br />
下面的示例演示了路由的链式定义</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
}) ### express.Router class 另外一个帮助组织路由的特性是新的类 express.Router,可以帮助创建模块话的路由处理器。一个 Router 的实例就是一个完整的中间件和路由系统,由于这个原因,它经常被称为 迷你应用。
</code></pre></div></div>
<p>下面演示了创建一个名为 bird.js 的路由文件,内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var router = express.Router();
// middleware specific to this router
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
})
// define the home page route
router.get('/', function(req, res) {
res.send('Birds home page');
})
// define the about route
router.get('/about', function(req, res) {
res.send('About birds');
})
module.exports = router; 然后在应用中加载这个路由模块。
var birds = require('./birds');
...
app.use('/birds', birds); 这个应用现在可以处理请求 /birds 和 /birds/about,还可以调用 timeLog 中间件。 ### Other changes 下表列出了其它小的但是重要的修改
</code></pre></div></div>
<table class="table-striped table-condensed">
<tr>
<td>对象</td>
<td>说明</td>
</tr>
<tr>
<td>Node</td>
<td>Express 4 需要 Node 0.10.x 及其以上版本,已经不支持 0.8.x</td>
</tr>
<tr>
<td>http.createServer</td>
<td>不再需要 http 模块,应用使用 app.listen() 启动</td>
</tr>
<tr>
<td>app.configure()</td>
<td>app.configure() 已经被移除,使用 process.env.NODE_ENV 或者 app.get(“env”) 来检测环境、配置应用</td>
</tr>
<tr>
<td>json.spaces()</td>
<td>在 Express 4 中默认禁用了 json spaces</td>
</tr>
<tr>
<td>Req.location()</td>
<td>不再能获取相对 url</td>
</tr>
<tr>
<td>Req.params</td>
<td>原来是一个数组,现在是对象</td>
</tr>
<tr>
<td>Res.locals</td>
<td>原来是函数,现在是对象</td>
</tr>
<tr>
<td>Res.headerSent</td>
<td>修改为 res.headersSent</td>
</tr>
<tr>
<td>App.route</td>
<td>现在作为 app.mountpath 存在</td>
</tr>
<tr>
<td>Res.on(“header”)</td>
<td>删除</td>
</tr>
<tr>
<td>Res.charset</td>
<td>删除</td>
</tr>
<tr>
<td>Res.setHeader(“Set-Cookie”, val)</td>
<td>这个功能现在限制为设置基本的 cookie 值,使用 res.cookie() 的添加功能</td>
</tr>
</table>
<h2 id="二-示例">二 、示例</h2>
<p>这里是一个从 Express 3 升级到 Express 4 的示例。</p>
<h3 id="version-3-app">Version 3 app</h3>
<h4 id="appjs">app.js</h4>
<p>原来的 app.js 如下:<br />
var express = require(‘express’);
var routes = require(‘./routes’);
var user = require(‘./routes/user’);
var http = require(‘http’);
var path = require(‘path’);</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(express.favicon());
app.use(express.logger('dev'));
app.use(express.methodOverride());
app.use(express.session({ secret: 'your secret here' }));
app.use(express.bodyParser());
app.use(app.router);
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(express.errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
http.createServer(app).listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
}); package.json 如下所示 附带的第3版的package.json文件可能看起来像这样:
{
"name": "application-name",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"express": "3.12.0",
"jade": "*"
}
}
</code></pre></div></div>
<h3 id="迁移过程">迁移过程</h3>
<p>在迁移之前,先安装 Express 4 需要的中间件,还要更新 express,Jade 到最新的版本。</p>
<blockquote>
<p>$ npm install serve-favicon morgan method-override express-session body-parser multer errorhandler express@latest jade@latest –save</p>
</blockquote>
<p>对 app.js 进行一下的修改。</p>
<ol>
<li>
<p>http 模块已经使用了,所以,删除 var http = require( “http” );</p>
</li>
<li>
<p>内建的中间件 express.favicon, express.logger, express.methodOverride, express.session, express.bodyParser 和 express.errorHandler 已经不存在了,你必须手动安装,然后在应用中替换它们。</p>
</li>
<li>
<p>不再需要加载 app.router ,实际上,它也已经不是 Express 4 中的对象,所以,删除 app.use( app.router );</p>
</li>
<li>
<p>使用 app.listen() 来取代 http.createServer 启动。</p>
</li>
</ol>
<h3 id="version-4-app">Version 4 app</h3>
<h4 id="packagejson">package.json</h4>
<p>执行上面的命令,会如下更新 package.json 文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"name": "application-name",
"version": "0.0.1",
"private": true,
"scripts": {
"start": "node app.js"
},
"dependencies": {
"body-parser": "^1.5.2",
"errorhandler": "^1.1.1",
"express": "^4.8.0",
"express-session": "^1.7.2",
"jade": "^1.5.0",
"method-override": "^2.1.2",
"morgan": "^1.2.2",
"multer": "^0.1.3",
"serve-favicon": "^2.0.1"
}
} #### app.js 然后,删除无效的代码,加载需要的中间件,完成其它必须的修改,最终的 app.js 看起来如下所示:
var express = require('express');
var routes = require('./routes');
var user = require('./routes/user');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var methodOverride = require('method-override');
var session = require('express-session');
var bodyParser = require('body-parser');
var multer = require('multer');
var errorHandler = require('errorhandler');
var app = express();
// all environments
app.set('port', process.env.PORT || 3000);
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'jade');
app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev'));
app.use(methodOverride());
app.use(session({ resave: true,
saveUninitialized: true,
secret: 'uwotm8' }));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(multer());
app.use(express.static(path.join(__dirname, 'public')));
// development only
if ('development' == app.get('env')) {
app.use(errorHandler());
}
app.get('/', routes.index);
app.get('/users', user.list);
app.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
}); 除非你需要直接与HTTP模块(socket.io/SPDY/HTTPS)工作,加载它不是必需的,并且app可以使用这种方式简单的启动:
app.listen(app.get('port'), function(){
console.log('Express server listening on port ' + app.get('port'));
}); ### 运行app 现在,迁移已经完成了,使用如下命令启动。
$ node app
</code></pre></div></div>
<p>使用浏览器访问 http://localhost:3000,查看使用 Express 4 生成的页面。</p>
<h2 id="升级到-express-4-的应用生成器">升级到 Express 4 的应用生成器</h2>
<p>生成一个 Express 4 的命令行工具还是 express,但是,升级到新版本的话,需要先卸载 Express 3 的生成器,然后安装新的生成器。</p>
<h3 id="安装">安装</h3>
<p>如果你已经安装过 Express 3 的生成器,必须先卸载</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm uninstall –g express
</code></pre></div></div>
<p>依赖与你的目录权限和配置,可能需要先执行 sudo 提升权限。
现在,安装新的生成器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Npm install –g express-generator
</code></pre></div></div>
<p>现在,你系统中的 express 命令已经升级为 Express 4 的生成器了.</p>
<h3 id="生成器的变化">生成器的变化</h3>
<p>除了下面的变化,基本上与以前相同。</p>
<p>–sessions 选项被删除了<br />
–jshtml 选项被删除了<br />
–hogan 被添加,以便支持 Hogan.js</p>
<h3 id="示例">示例</h3>
<p>执行下面的命令,创建 app4 应用</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>express app4
</code></pre></div></div>
<p>如果你查看 app4 文件夹中的 app.js 文件,你会发现所有的中间件被替换为独立的中间件加载,router 中间件不再显式加载。你还会注意到 app.js 现在是一个 Node 模块。<br />
安装依赖的文件之后,使用下面的命令启动应用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm start
</code></pre></div></div>
<p>如果你查看 package.json 中的启动脚本,你会注意到实际的启动脚本是 node ./bin/www,在 Express 3 中是 node app.js.由于 Express 4 新生成的 app.js 已经是一个 Node 模块,它可以不再需要通过一个独立的应用来启动,它可以在 Node 文件中加载,通过 Node 文件启动,这里就是 ./bin/www.不管是 bin 文件夹,还是 www 文件,他们都是手工由 Express 生成器生成的,所以,需要的话,都可以进行修改。为了与 Express 3 保持一致,删除 module.experts = app;在 app.js 的最后,添加下面的代码。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('port', process.env.PORT || 3000);
var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port);
}); 确认加载 debug 模块。
var debug = require('debug')('app4'); 然后将 package.json 文件中的 start: "node ./bin/www" 修改为 "start": "node app.js"。现在,已经从 ./bin/www 回到了 app.js。
</code></pre></div></div>
express3.x API
2015-10-24T00:00:00+00:00
http://www.blogways.net/blog/2015/10/24/express3.x-API
<p>原文:<a href="http://expressjs.com/en/3x/api.html">http://expressjs.com/en/3x/api.html</a></p>
<h2 id="一express">一、express()</h2>
<p>创建一个express应用程序,express()函数是一个顶层函数express模块的导出。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
</code></pre></div></div>
<h2 id="二application应用">二、Application(应用)</h2>
<h3 id="appsetnamevalue">app.set(name,value)</h3>
<p>用用于指定变量的值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('title', 'My Site');
app.get('title');
// => "My Site"
</code></pre></div></div>
<h3 id="appgetname">app.get(name)</h3>
<p>获得设置项的值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('title');
// => undefined
app.set('title', 'My Site');
app.get('title');
// => "My Site"
</code></pre></div></div>
<h3 id="appenablename">app.enable(name)</h3>
<p>将设置项 name 的值设为 true 。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.enable('trust proxy');
app.get('trust proxy');
// => true
</code></pre></div></div>
<h3 id="appdisablename">app.disable(name)</h3>
<p>将设置项 name 的值设为 false 。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.disable('trust proxy');
app.get('trust proxy');
// => false
</code></pre></div></div>
<h3 id="appenabledname">app.enabled(name)</h3>
<p>检查设置项 name 是否已启用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.enabled('trust proxy');
// => false
app.enable('trust proxy');
app.enabled('trust proxy');
// => true
</code></pre></div></div>
<h3 id="appdisabledname">app.disabled(name)</h3>
<p>检查设置项 name 是否已禁用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.disabled('trust proxy');
// => true
app.enable('trust proxy');
app.disabled('trust proxy');
// => false
</code></pre></div></div>
<h3 id="appconfigureenv-callback">app.configure([env], callback)</h3>
<p>当 env 和 app.get(‘env’) (也就是 process.env.NODE_ENV) 匹配时, 调用 callback 。
保留这个方法是出于历史原因,后面列出的 if 语句的代码其实更加高效、直接。
使用 app.set 配合其它一些配置方法后,没有必要再使用这个方法。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 所有环境
app.configure(function(){
app.set('title', 'My Application');
})
// 开发环境
app.configure('development', function(){
app.set('db uri', 'localhost/dev');
})
// 只用于生产环境
app.configure('production', function(){
app.set('db uri', 'n.n.n.n/prod');
})
</code></pre></div></div>
<p>更高效且直接的代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 所有环境
app.set('title', 'My Application');
// 只用于开发环境
if ('development' == app.get('env')) {
app.set('db uri', 'localhost/dev');
}
// 只用于生产环境
if ('production' == app.get('env')) {
app.set('db uri', 'n.n.n.n/prod');
}
</code></pre></div></div>
<h3 id="appusepath-function">app.use([path], function)</h3>
<p>使用中间件 function,可选参数 path 默认是 “/”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var express = require('express');
var app = express();
// 一个简单的 logger
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
// 响应
app.use(function(req, res, next){
res.send('Hello World');
});
app.listen(3000);
</code></pre></div></div>
<p>挂载路径被剥离出来,对于中间件函数来说是不可见的。
这么设计是为了让中间件在不用修改任何代码的情况下就可以在任意前缀的路径下执行。</p>
<p>路由将匹配任何路径,以遵循“/”、“./”。
例如:app.use(‘/apple’,…)将会匹配 /apple, /apple/images, /apple/images/news, /apple.html,/apple.html.txt等等。</p>
<p>这里有一个具体的例子,通过express.static()方法使用./public来管理文件服务用例的中间件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /javascripts/jquery.js
// GET /style.css
// GET /favicon.ico
app.use(express.static(__dirname + '/public'));
</code></pre></div></div>
<p>例如你想为自有的静态文件增加前缀’/static’,你可以使用’mounting’功能。
挂载的中间件函数不会被调用,除非req.url包含这个前缀,当函数被调用时,前缀是被剥离出去的。
当然这只会影响到这个函数,挂载好后随后的中间件还是会通过包含/static的req.url查看到。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /static/javascripts/jquery.js
// GET /static/style.css
// GET /static/favicon.ico
app.use('/static', express.static(__dirname + '/public'));
</code></pre></div></div>
<p>中间件使用app.use()定义的顺序是非常重要的,它们依次被调用,因此这个决定了中间件的优先级。
例如,一般来说日志中间件是你要用到的第一个中间件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.logger());
app.use(express.static(__dirname + '/public'));
app.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>现在假设你想忽略静态文件的请求日志,
但又想在logger()定义之后继续使用日志路由,你只需要将static()移动前面就可以了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.static(__dirname + '/public'));
app.use(logger());
app.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>另一个具体的例子是从众多的目录文件服务中,给予”./public”最高的优先级:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads'));
</code></pre></div></div>
<h3 id="settings">settings</h3>
<p>下面的内建的可以改变 Express 行为的设置:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>env 运行时环境,默认为 process.env.NODE_ENV 或者 “development”
trust proxy 激活反向代理,默认是未激活状态
jsonp callback name 修改 ?callback= 的默认 callback 的名字
json replacer JSON 替换时的回调, 默认为 null
json spaces JSON 响应被格式化时的空格数量,开发环境下是 2 ,生产环境是 0
case sensitive routing 路由的大小写敏感, 默认是关闭状态,”/Foo” 和 “/foo” 被认为是一样的
strict routing路由的严格格式, 默认情况下 “/foo” 和 “/foo/” 被路由认为是一样的
view cache E模板缓存,在生产环境中是默认开启的
view engine 默认的模板引擎
views 模板的目录, 默认是 “process.cwd() + ‘/views’”
</code></pre></div></div>
<h3 id="appengineext-callback">app.engine(ext, callback)</h3>
<p>注册模板引擎的 callback 用来处理 ext 扩展名的文件。
注册给定的模板引擎的callback默认用来处理扩展名为ext的文件。
例如,如果你试图渲染一个”foo.jade”文件,Express将在内部调用以下代码,并缓存require()给后续调用以提高性能。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.engine('jade', require('jade').__express);
</code></pre></div></div>
<p>引擎没有提供._express渲染方法,或者你想映射一个不一样的扩展名在模板引擎上,你可以用这个方法。
例如映射EJS模板引擎来渲染”.html”文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.engine('html', require('ejs').renderFile);
</code></pre></div></div>
<p>在这种情况下,EJS提供.renderFile()方法使用Express定义的参数:(path, options, callback),
但要注意这个方法是在内部给ejs._express取一个别名,如果你使用”.ejs”可以什么都不要做。</p>
<p>有些模板引擎并不遵循这一规则,consolidate.js库的建立是为了映射所有的node流行模板引擎遵循这一规则,
从而使得他们在Express内无缝工作。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var engines = require('consolidate');
app.engine('haml', engines.haml);
app.engine('html', engines.hogan);
</code></pre></div></div>
<h3 id="appparamname-callback">app.param([name], callback)</h3>
<p>映射路由参数规则。<br />
例如当:user存在于一个路由路径中,你需要自动提供req.user给路由映射启动逻辑,或者执行输入参数验证。</p>
<p>下面的代码说明了如果callback很像中间件,从而支持异常操作,
但却增加了一个参数,这里命名为id。然后尝试执行加载用户信息,赋值给req.user,否则传递一个错误到next(err)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
</code></pre></div></div>
<p>另外,你可能只传递一个回调函数,
在这种情况下你有机会改变app.param()API。例如express-params定义了下面的回调函数,它允许你使用给定的正则表达式限制参数。</p>
<p>这个例子有点更先进,检查当第二个参数是正则表达式,返回回调函数很像”user”参数例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
}); 该方法可以被用来有效的验证参数,或者解析提供匹配分组:
app.param('id', /^\d+$/);
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
app.param('range', /^(\w+)\.\.(\w+)?$/);
app.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
</code></pre></div></div>
<h3 id="appverbpath-callback-callback">app.VERB(path, [callback…], callback)</h3>
<p>Express中App.WEB()方法提供了路由功能,其中VERB是一个HTTP动作,跟app.post()类似。
可提供多个回调函数,都是一视同仁,表现跟中间件一样,唯一不一样的是通过调用next(‘route’)来继续其余的路由回调。
这个机制可以用来执行路由的前提条件,然后将控制权传递给随后的路由,没有理由进行路由的匹配。</p>
<p>下面的代码说明了多个简单路由定义的可行性。Express将路径字符串转换成正由表达式,用来在内部匹配到来的请求。
在执行这些匹配时查询字符串不考虑,例如”GET /”将匹配以下的路由,如”GET /?name=tobi”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/', function(req, res){
res.send('hello world');
});
</code></pre></div></div>
<p>正由表达式也可以使用,如果你有非常特殊的限制可能是有用的,
例如下面的”GET /commits/71dbb9c”表达式将很好的匹配”GET /commits/71dbb9c..4c084f9”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
</code></pre></div></div>
<p>可以传递一些回调函数,对于利用中间件加载资源、执行验证等很有用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/user/:id', user.load, function(){
// ...
})
</code></pre></div></div>
<p>如果你有多个共同的中间件路由,可以使用路由api的all。两个中间件将用来处理GET和POST请求</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var middleware = [loadForum, loadThread];
app.route('/forum/:fid/thread/:tid')
.all(loadForum)
.all(loadThread)
.get(function() { //... });
.post(function() { //... });
</code></pre></div></div>
<h3 id="appallpath-callback-callback">app.all(path, [callback…], callback)</h3>
<p>此方法功能就像app.VERB()方法,但它匹配所有HTTP的动作。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('*', requireAuthentication, loadUser);
</code></pre></div></div>
<p>等价于:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('*', requireAuthentication)
app.all('*', loadUser);
</code></pre></div></div>
<p>另一个非常赞的例子是“全局”白名单函数。
这里有一个例子跟前一个很像,但是它限制前缀为 “/api”:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('/api/*', requireAuthentication);
</code></pre></div></div>
<h3 id="applocals">app.locals</h3>
<p>应用本地变量会附加给所有的在这个应用程序内渲染的模板。这是一个非常有用的模板函数,就像应用程序级数据一样。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.locals.title = 'My App';
app.locals.strftime = require('strftime');
</code></pre></div></div>
<p>app.locals 对象是一个 JavaScript Function,执行的时候它会把属性合并到它自身,
提供了一种简单展示已有对象作为本地变量的方法。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.locals({
title: 'My App',
phone: '1-250-858-9990',
email: 'me@myapp.com'
});
app.locals.title
// => 'My App'
app.locals.email
// => 'me@myapp.com'
</code></pre></div></div>
<p>app.locals 对象最终会是一个 Javascript 函数对象,你不可以使用 Functions 和 Objects 内置的属性,
比如 name、apply、bind、call、arguments、length、constructor。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.locals({name: 'My App'});
app.locals.name
// => 返回 'app.locals' 而不是 'My App' (app.locals 是一个函数 !)
// => 如果 name 变量用在一个模板里,则返回一个 ReferenceError 。
</code></pre></div></div>
<p>全部的保留字列表可以在很多规范里找到。 JavaScript 规范介绍了原来的属性,
有一些还会被现代的 JavaScript 引擎识别,EcmaScript 规范在它的基础上,
统一了值,添加了一些,删除了一些废弃的。如果感兴趣,可以看看 Functions 和 Objects 的属性值。</p>
<p>默认情况下Express只有一个应用程序级本地变量,它是 settings。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('title', 'My App');
// 在 view 里使用 settings.title
</code></pre></div></div>
<h3 id="apprenderview-options-callback">app.render(view, [options], callback)</h3>
<p>使用回调函数返回的渲染字符串渲染视图。这是res.render()的应用程序级别的版本,它们的行为是一样的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.render('email', function(err, html){
// ...
});
app.render('email', { name: 'Tobi' }, function(err, html){
// ...
});
</code></pre></div></div>
<h3 id="approutes">app.routes</h3>
<p>The app.routes 对象存储了所有的被 HTTP 动作定义的路由。这个对象可以用在一些内部功能上,
比如 Express 不仅用它来做路由分发,同时在没有 app.options() 定义的情况下用它来处理默认的
OPTIONS
行为。你的应用程序或者框架也可以很轻松的通过在这个对象里移除路由来达到删除路由的目的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log(app.routes)
{ get:
[ { path: '/',
method: 'get',
callbacks: [Object],
keys: [],
regexp: /^\/\/?$/i },
{ path: '/user/:id',
method: 'get',
callbacks: [Object],
keys: [{ name: 'id', optional: false }],
regexp: /^\/user\/(?:([^\/]+?))\/?$/i } ],
delete:
[ { path: '/user/:id',
method: 'delete',
callbacks: [Object],
keys: [Object],
regexp: /^\/user\/(?:([^\/]+?))\/?$/i } ] }
app.listen()
</code></pre></div></div>
<p>在给定的主机和端口上监听请求,这个和 node 文档中的 http.Server#listen() 是一致的。
var express = require(‘express’);
var app = express();
app.listen(3000);</p>
<p>express() 返回的 app 实际上是一个 JavaScript Function,它被设计为传给 node 的 http servers 作为处理请求的回调函数。
因为 app 不是从 HTTP 或者 HTTPS 继承来的,它只是一个简单的回调函数,你可以以同一份代码同时处理 HTTP 和 HTTPS 版本的服务。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var https = require('https');
var http = require('http');
var app = express();
http.createServer(app).listen(80);
https.createServer(options, app).listen(443);
</code></pre></div></div>
<h2 id="三request">三、Request</h2>
<h3 id="reqparams">req.params</h3>
<p>此属性是一个包含映射路由”parameters”的对象。例如你使用/user/:name路由,那么”name”属性对你来说就是一个req.params.name变量。
该对象默认为{}。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /user/tj
req.params.name
// => "tj"
</code></pre></div></div>
<p>当在定义路由规则时使用了正则表达式,使用req.params[N]获取所有参数匹配数组,其中N表示数组的第几个。此规则适用于包含未定义的通配符的路由字符串,例如/file/*:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /file/javascripts/jquery.js
req.params[0]
// => "javascripts/jquery.js"
req.query 此属性是一个包含解析查询字符串的对象,默认为{}。
// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse"
</code></pre></div></div>
<h3 id="reqbody">req.body</h3>
<p>此属性是一个包含解析的请求体的对象。它的特点是提供一个bodyParser()中间件。虽然其他的体解析中间件也遵循此约定,
当使用bodyparser()使用时,默认值为{}。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// POST user[name]=tobi&user[email]=tobi@learnboost.com
req.body.user.name
// => "tobi"
req.body.user.email
// => "tobi@learnboost.com"
// POST { "name": "tobi" }
req.body.name
// => "tobi"
</code></pre></div></div>
<h3 id="reqfiles">req.files</h3>
<p>这个属性是一个上传文件的对象,它的特点是提供一个bodyParser()中间件。虽然其他的体解析中间件也遵循此约定,
当使用bodyparser()使用时,默认值为{}。</p>
<p>例如一个文件字段被命名为“image”,和一个文件被上传,req.files.image包含如下文件对象:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ size: 74643,
path: '/tmp/8ef9c52abe857867fd0a4e9a819d1876',
name: 'edge.png',
type: 'image/png',
hash: false,
lastModifiedDate: Thu Aug 09 2012 20:07:51 GMT-0700 (PDT),
_writeStream:
{ path: '/tmp/8ef9c52abe857867fd0a4e9a819d1876',
fd: 13,
writable: false,
flags: 'w',
encoding: 'binary',
mode: 438,
bytesWritten: 74643,
busy: false,
_queue: [],
_open: [Function],
drainable: true },
length: [Getter],
filename: [Getter],
mime: [Getter] }
</code></pre></div></div>
<p>bodyparser()中间件利用节点强大的模块内部,并接受相同的选项。一个例子是keepextensions强大的选项,
在给出文件名“/tmp/8ef9c52abe857867fd0a4e9a819d1876”.png扩展名的情况下默认值是false。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.bodyParser({ keepExtensions: true, uploadDir: '/my/files' }));
</code></pre></div></div>
<h3 id="reqparamname">req.param(name)</h3>
<p>返回当前参数name的值
// ?name=tobi
req.param(‘name’)
// => “tobi”</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // POST name=tobi
req.param('name')
// => "tobi"
// /user/tobi for /user/:name
req.param('name')
// => "tobi"
</code></pre></div></div>
<p>查找优先级如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> req.params
req.body
req.query
</code></pre></div></div>
<p>直接使用req.body,req.params,和req.query应该更新清晰,除非你确实需要接收每个对象的输入。</p>
<h3 id="reqroute">req.route</h3>
<p>当前匹配的路由包含多个属性,如路由的原始路径字符串以及转换后的正则表达式等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/user/:id?', function(req, res){
console.log(req.route);
});
上面的代码输出结果:
{ path: '/user/:id?',
keys: [ { name: 'id', optional: true } ],
regexp: /^\/user(?:\/([^\/]+?))?\/?$/i,
params: [ id: '12' ] }
</code></pre></div></div>
<h3 id="reqcookies">req.cookies</h3>
<p>当cookieParser()中间件使用时该对象默认为{},除此之外还包含由用户代理发送的cookies。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Cookie: name=tj
req.cookies.name
// => "tj" 如有任何问题或者疑问请参阅cookie-parser附加文档。
</code></pre></div></div>
<h3 id="reqsignedcookies">req.signedCookies</h3>
<p>当cookieParser(secret)中间件使用该对象默认为{},还包括用户代理发送的签名cookies,未签名以及准备使用的。签名cookies存放于一个单独的对象,以显示开发者的意图,否则可以通过在req.cookie设置值发起恶意攻击,从而很轻易的欺骗。需要注意的是签名的cookie并不意味着它是隐藏的或者是加密的,这个防止篡改的秘密只是简单的将签名私有化。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
req.signedCookies.user
// => "tobi" 如有任何问题或者疑问请参阅cookie-parser附加文档。
</code></pre></div></div>
<h3 id="reqgetfield">req.get(field)</h3>
<p>获取请求头内的field字段,不区分大小写。Referrer和Referer字段可以互换。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined 别名为req.header(field)。
</code></pre></div></div>
<p>检查给定的types是不是可以接受的,当结果为true时返回最佳匹配,否则返回undefined,在这种情况下你应该返回406”Not Acceptable”。</p>
<p>type可以是单一的mine类型的字符串,比如”application/json”,扩展名如”json”,也可以是以逗号分隔的列表或者数组。当为列表或数组时将返回最佳匹配。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Accept: text/html
req.accepts('html');
// => "html"
// Accept: text/*, application/json
req.accepts('html');
// => "html"
req.accepts('text/html');
// => "text/html"
req.accepts('json, text');
// => "json"
req.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*;q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => "json" 如有任何问题或者疑问,请参阅accepts附加文档。
</code></pre></div></div>
<h3 id="reqacceptscharsetcharset">req.acceptsCharset(charset)</h3>
<p>检查给定的字符集是否可以支持。
如有任何问题或者疑问,请参阅accepts附加文档。</p>
<h3 id="reqacceptslanguagelang">req.acceptsLanguage(lang)</h3>
<p>检查给定的lang是否支持。
如有任何问题或者疑问,请参阅accepts附加文档。</p>
<h3 id="reqistype">req.is(type)</h3>
<p>检查传入请求字符串是否包含了”Content-Type”头字段,并且给出匹配的mine类型。
// With Content-Type: text/html; charset=utf-8
req.is(‘html’);
req.is(‘text/html’);
req.is(‘text/*’);
// => true</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false 如有任何问题或者疑问,请参阅type-is附加文档。
</code></pre></div></div>
<h3 id="reqip">req.ip</h3>
<p>返回远程地址,或者当信任代理已启用时返回代理地址。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.ip
// => "127.0.0.1"
</code></pre></div></div>
<h3 id="reqips">req.ips</h3>
<p>当信任代理为true时,解析”X-Forwarded-For”ip地址列表返回一个数组,否则返回一个空数组。
例如当值为”client, proxy1, proxy2”时你会获得[“client”, “proxy1”, “proxy2”]数组,其中”proxy2”是最远的下游地址。</p>
<h3 id="reqpath">req.path</h3>
<p>返回请求的URL路径名。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// example.com/users?sort=desc
req.path
// => "/users"
</code></pre></div></div>
<h3 id="reqfresh">req.fresh</h3>
<p>检查请求是否刷新,通过对Last-Modified和/或ETag进行匹配,表明资源是不是最新的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.fresh
// => true 如有任何问题或者疑问,请参阅fresh附加文档。
</code></pre></div></div>
<h3 id="reqstale">req.stale</h3>
<p>检查请求是否过期,如果Last-Modified和/或ETag不匹配,表有资源是过期的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.stale
// => true
</code></pre></div></div>
<h3 id="reqxhr">req.xhr</h3>
<p>检查请求头里是否包含”X-Requested-With”字段并且值为”XMLHttpRequest”(jQuery等)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.xhr
// => true
</code></pre></div></div>
<h3 id="reqprotocol">req.protocol</h3>
<p>当使用TLS请求时返回”http”或”https”协议字符串。当信任路由设置为开启时”X-Forwarded-Proto”头字段将被信任。
如果你正在运行一个支持https协议的反向代理,那么这个是支持的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.protocol
// => "http"
</code></pre></div></div>
<h3 id="reqsecure">req.secure</h3>
<p>检查TLS连接是否建立。这是一个简写:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'https' == req.protocol;
</code></pre></div></div>
<h3 id="reqsubdomains">req.subdomains</h3>
<p>返回子域数组。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Host: "tobi.ferrets.example.com"
req.subdomains
// => ["ferrets", "tobi"]
</code></pre></div></div>
<h3 id="reqoriginalurl">req.originalUrl</h3>
<p>此属性很像req.url,但它保留了原始请求的url,允许你在做内部路由时自由重写req.url。
例如app.use()中间件将重写req.url重新定义挂载点。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /search?q=something
req.originalUrl
// => "/search?q=something"
</code></pre></div></div>
<h2 id="四response">四、Response</h2>
<h3 id="resstatuscode">res.status(code)</h3>
<p>res.statusCode=可链接节点的别名</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.status(404).sendfile('path/to/404.png');
</code></pre></div></div>
<h3 id="ressetfield-value">res.set(field, [value])</h3>
<p>设置响应头内字段值,或者通过一个对象一次设置多个字段。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.set('Content-Type', 'text/plain');
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
}) res.header(field, [value])别名。
</code></pre></div></div>
<h3 id="resgetfield">res.get(field)</h3>
<p>获取响应头内字段值,不区分大小写。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> res.get('Content-Type');
// => "text/plain"
</code></pre></div></div>
<h3 id="rescookiename-value-options">res.cookie(name, value, [options])</h3>
<p>设置cookie名称和值,可以是字符串或者对象转换成的JSON。路径选项默认为”/”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
</code></pre></div></div>
<p>maxAge选项可以很方便的设置从当前时间开始以毫秒为单位的过期时间。下面的写法等同于上一个例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
</code></pre></div></div>
<p>一个对象可以通过序列化成JSON传递,它由bodyParser()中间件自动解析。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('cart', { items: [1,2,3] });
res.cookie('cart', { items: [1,2,3] }, { maxAge: 900000 }); 这种方法也支持签名cookie。添加一个简单的signed选项。 res.cookie()将隐藏传递给cookieParser(secret)对值签名。
res.cookie('name', 'tobi', { signed: true });
</code></pre></div></div>
<p>然后你可以使用req.signedCookie来访问这个值。</p>
<h3 id="resclearcookiename-options">res.clearCookie(name, [options])</h3>
<p>删除cookie里面值。默认路径为”/”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('name', 'tobi', { path: '/admin' });
res.clearCookie('name', { path: '/admin' });
</code></pre></div></div>
<h3 id="resredirectstatus-url">res.redirect([status], url)</h3>
<p>重定向到给定的url,可选状态编码默认为302”Found”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');
</code></pre></div></div>
<p>Express支持几种形式的重定向,首先一个完整合格的URI重定向到不同的域名:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('http://google.com');
</code></pre></div></div>
<p>第二种形式是相对路径的重定向,例如你正在http://example.com/admin/post/new,
接着重定向到/admin,你将会登录http://example.com/admin:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('/admin');
</code></pre></div></div>
<h3 id="reslocation">res.location</h3>
<p>设置位置头</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.location('/foo/bar');
res.location('foo/bar');
res.location('http://example.com');
res.location('../login');
res.location('back'); 你可以使用res.redirect()相同的urls。 例如你的应用挂载在/blog下,使用下面的代码设置location头为/blog/admin:
res.location('admin')
</code></pre></div></div>
<h3 id="rescharset">res.charset</h3>
<p>分配字符集。默认的为“utf-8”.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.charset = 'value';
res.send('<p>some html</p>');
// => Content-Type: text/html; charset=value
</code></pre></div></div>
<h3 id="ressendbodystatus-body">res.send([body|status], [body])</h3>
<p>发送一个响应</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('<p>some html</p>');
res.send(404, 'Sorry, we cannot find that!');
res.send(500, { error: 'something blew up' });
res.send(200);
</code></pre></div></div>
<p>此方法适用于执行大量的简单非流式的响应任务,例如在未提前定义和提供自动HEAD和HTTP缓存刷新支持的情况下自动设定Content-Length。 <br />
当传入的内容为Buffer,那么Content-Type会被设置为”application/octet-stream”,除非预先定义如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.set('Content-Type', 'text/html');
res.send(new Buffer('<p>some html</p>')); 当发送字符串时Content-Type设置默认为"text/html":
res.send('<p>some html</p>'); 当发送数组或者对象时Express将会转换成JSON格式:
res.send({ user: 'tobi' })
res.send([1,2,3])
</code></pre></div></div>
<p>最后如果返回的是一个数字,没有前面提到的任何一个响应体,Express会为你设置一个响应字符串。例如200将会响应文本”OK”,400响应”Not Found”等等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.send(200)
res.send(404)
res.send(500)
</code></pre></div></div>
<h3 id="resjsonstatusbody-body">res.json([status|body], [body])</h3>
<p>发送一个JSON响应,当返回对象或者数组时该方法与res.send()相同,然而它可以用来将非对象(null, undefined, 等等)转换成精准的JSON,尽管严格来说这些并不是有效的JSON。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.json(null)
res.json({ user: 'tobi' })
res.json(500, { error: 'message' })
</code></pre></div></div>
<h3 id="resjsonpstatusbody-body">res.jsonp([status|body], [body])</h3>
<p>使用JSONP发送JSON响应。该方法与res.json()相同,但多了对JSONP回调的支持。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.jsonp(null)
// => null
res.jsonp({ user: 'tobi' })
// => { "user": "tobi" }
res.jsonp(500, { error: 'message' })
// => { "error": "message" }
</code></pre></div></div>
<p>默认JSONP回调函数名是callback,但你可以通过修改jsonp callback name参数重新定义。以下是JSONP响应的一些例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// ?callback=foo
res.jsonp({ user: 'tobi' })
// => foo({ "user": "tobi" })
app.set('jsonp callback name', 'cb');
// ?cb=foo
res.jsonp(500, { error: 'message' })
// => foo({ "error": "message" })
</code></pre></div></div>
<h3 id="restypetype">res.type(type)</h3>
<p>设置Content-Type类型为mime查找的类型,或者当”/”存在时Content-Type被简单的设置成该类型。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png');
</code></pre></div></div>
<h3 id="resformatobject">res.format(object)</h3>
<p>执行请求时存在请求Accept头上下文转换。该方法使用req.accepted,这是一个按可接受类型重要性排序的数组,否则第一个回调函数被调用。当没有匹配的回调函数执行时服务器返回406 “Not Acceptable”,或者调用默认的回调函数。</p>
<p>设置Content-Type为你选择一个回调函数,但你可以在回调函数中使用res.set()或者res.type()等修改。</p>
<p>下例当Accept头字段设置成”application/json”或”/json”时响应{ “message”: “hey” },但如果设置成”/*“时将会响应”hey”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.format({
'text/plain': function(){
res.send('hey');
},
'text/html': function(){
res.send('<p>hey</p>');
},
'application/json': function(){
res.send({ message: 'hey' });
}
});
</code></pre></div></div>
<p>除了规范化的MIME类型你还可以使用扩展名映射这些类型,提供一个冗长实施:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.format({
text: function(){
res.send('hey');
},
html: function(){
res.send('<p>hey</p>');
},
json: function(){
res.send({ message: 'hey' });
}
});
</code></pre></div></div>
<h3 id="resattachmentfilename">res.attachment([filename])</h3>
<p>设置Content——disposition头字段为“attachment”。如果给定一个文件名,那么Content-Type将会通过res.type()自动设置成基于扩展名的类型,Content-Disposition的”filename=”参数同时也被设置。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.attachment();
// Content-Disposition: attachment
res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png
</code></pre></div></div>
<h3 id="ressendfilepath-options-fn">res.sendfile(path, [options], [fn])</h3>
<p>传输文件到给定的路径。<br />
自动设置默认基于文件扩展名的Content-Type响应头。当传输发生错误时fn(err)回调函数被调用。<br />
选项:</p>
<blockquote>
<ul>
<li>maxAge 以毫秒为单位默认为0</li>
<li>root 相对文件名根目录</li>
</ul>
</blockquote>
<p>在下例中该方法为文件服务提供细粒度支持:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> app.get('/user/:uid/photos/:file', function(req, res){
var uid = req.params.uid
, file = req.params.file;
req.user.mayViewFilesFrom(uid, function(yes){
if (yes) {
res.sendfile('/uploads/' + uid + '/' + file);
} else {
res.send(403, 'Sorry! you cant see that.');
}
});
});
</code></pre></div></div>
<h3 id="resdownloadpath-filename-fn">res.download(path, [filename], [fn])</h3>
<p>传输路径中的文件作为附件,通常浏览器会提醒用户下载。Content-Disposition “filename=”参数,也就是显示在浏览器对话框的默认文件名,你也可以提供一个自定义文件名。</p>
<p>当传输完成或者中途发生错误时将会调用fn回调函数,该方法使用res.sendfile()来传输文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> res.download('/report-12345.pdf');
res.download('/report-12345.pdf', 'report.pdf');
res.download('/report-12345.pdf', 'report.pdf', function(err){
if (err) {
// handle error, keep in mind the response may be partially-sent
// so check res.headersSent
} else {
// decrement a download credit etc
}
});
</code></pre></div></div>
<h3 id="reslinkslinks">res.links(links)</h3>
<p>加入给定的链接来填充”Link”响应头字段。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
}); 处理后
Link: &lt;http://api.example.com/users?page=2&gt;; rel="next",
&lt;http://api.example.com/users?page=5&gt;; rel="last"
</code></pre></div></div>
<h3 id="reslocals">res.locals</h3>
<p>响应本地化变量作用域为request,因此只适用于在该request/response周期内呈现的视图,如果有的话。其实该API跟app.locals是等同的。<br />
这个对象适用于的request级别的信息,例如request路径,用户认证,用户设置等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next();
});
</code></pre></div></div>
<h3 id="resrenderview-locals-callback">res.render(view, [locals], callback)</h3>
<p>渲染一个视图,同时向回调函数传递渲染后的字符串。发生错误时内部调用next(err)。回调函数传入可能发生的错误以及渲染后的页面,这样就不会自动执行响应了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> res.render('index', function(err, html){
// ...
});
res.render('user', { name: 'Tobi' }, function(err, html){
// ...
});
</code></pre></div></div>
<h2 id="五middleware">五、Middleware</h2>
<h3 id="basicauth">basicAuth()</h3>
<p>基本身份验证的中间件,在req.user里添加用户名<br />
用户名字和密码的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.basicAuth('username', 'password')); 校验回调:
app.use(express.basicAuth(function(user, pass){
return 'tj' == user && 'wahoo' == pass;
}));
</code></pre></div></div>
<p>异步校验接受参数fn(err, user), 下面的例子req.user 将会作为user对象传递.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(connect.basicAuth(function(user, pass, fn){
User.authenticate({ user: user, pass: pass }, fn);
}))
</code></pre></div></div>
<h3 id="bodyparser">bodyParser()</h3>
<p>支持 JSON, urlencoded和multipart requests的请求体解析中间件。 这个中间件是json(), urlencoded(),和multipart() 这几个中间件的简单封装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.bodyParser());
// 等同于:
app.use(express.json());
app.use(express.urlencoded());
app.use(express.multipart());
</code></pre></div></div>
<p>从安全上考虑,如果你的应用程序不需要文件上传功能,最好关闭它。我们只使用我们需要的中间件。例如:我们不使用bodyParser、multipart() 这两个中间件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.json());
app.use(express.urlencoded());
</code></pre></div></div>
<h3 id="compress">compress()</h3>
<p>通过gzip / deflate压缩响应数据. 这个中间件应该放置在所有的中间件最前面以保证所有的返回都是被压缩的</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.logger());
app.use(express.compress());
app.use(express.methodOverride());
app.use(express.bodyParser());
</code></pre></div></div>
<h3 id="cookieparser">cookieParser()</h3>
<p>解析请求头里的Cookie, 并用cookie名字的键值对形式放在 req.cookies 你也可以通过传递一个secret 字符串激活签名了的cookie</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.cookieParser());
app.use(express.cookieParser('some secret'));
</code></pre></div></div>
<h3 id="cookiesession">cookieSession()</h3>
<p>提供一个以cookie为基础的sessions, 设置在req.session里。 这个中间件有以下几个<br />
选项:</p>
<blockquote>
<ul>
<li>key cookie 的名字,默认是 connect.sess</li>
<li>secret 防止篡改</li>
<li>cookie session cookie 设置, 默认是 { path: ‘/’, httpOnly: true, maxAge: null }</li>
<li>proxy 当设置安全cookies时信任反向代理 (通过 “x-forwarded-proto”)</li>
</ul>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.cookieSession()); 清掉一个cookie, 只需要在响应前把null赋值给session:
req.session = null
</code></pre></div></div>
<h3 id="csrf">csrf()</h3>
<p>CSRF防护中间件<br />
默认情况下这个中间件会产生一个名为”_csrf”的标志,这个标志应该添加到那些需要服务器更改的请求里,可以放在一个表单的隐藏域,请求参数等。这个标志可以通过 req.csrfToken()方法进行校验。</p>
<p>bodyParser() 中间件产生的 req.body , query()产生的req.query,请求头里的”X-CSRF-Token”是默认的 value 函数检查的项</p>
<p>这个中间件需要session支持,因此它的代码应该放在session()之后.</p>
<h3 id="directory">directory()</h3>
<p>文件夹服务中间件,用 path 提供服务。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.directory('public'))
app.use(express.static('public'))
</code></pre></div></div>
<p>这个中间件接收如下参数:</p>
<blockquote>
<ul>
<li>hidden 显示隐藏文件,默认是false</li>
<li>icon 显示图标,默认值是false</li>
<li>filter 在文件上应用这些过滤函数,默认值是false</li>
</ul>
</blockquote>
为express设置代理
2015-10-24T00:00:00+00:00
http://www.blogways.net/blog/2015/10/24/express-behind-proxies
<p>原文:<a href="http://expressjs.com/en/guide/behind-proxies.html">http://expressjs.com/en/guide/behind-proxies.html</a></p>
<p>当在代理服务器之后运行 <code class="language-plaintext highlighter-rouge">Express</code> 时,请将应用变量 <code class="language-plaintext highlighter-rouge">trust proxy</code> 设置(使用 <code class="language-plaintext highlighter-rouge">app.set()</code>)为下述序列中的一项。</p>
<blockquote>
<p>如果没有设置应用变量 <code class="language-plaintext highlighter-rouge">trust proxy</code>,应用将不会运行,除非 <code class="language-plaintext highlighter-rouge">trust proxy</code> 设置正确,否则应用会误将代理服务器的 <code class="language-plaintext highlighter-rouge">IP</code> 地址注册为客户端 <code class="language-plaintext highlighter-rouge">IP</code> 地址.</p>
</blockquote>
<h2 id="1boolean">1、Boolean</h2>
<p>如果为 <code class="language-plaintext highlighter-rouge">true</code>,客户端 <code class="language-plaintext highlighter-rouge">IP</code> 地址为 <code class="language-plaintext highlighter-rouge">X-Forwarded-*</code> 头最左边的项。<br />
如果为 <code class="language-plaintext highlighter-rouge">false</code>, 应用直接面向互联网,客户端 <code class="language-plaintext highlighter-rouge">IP</code> 地址从 <code class="language-plaintext highlighter-rouge">req.connection.remoteAddress</code> 得来,这是默认的设置。</p>
<h2 id="2ip-地址">2、IP 地址</h2>
<p>IP 地址、子网或 <code class="language-plaintext highlighter-rouge">IP</code> 地址数组和可信的子网。下面是预配置的子网列表。</p>
<blockquote>
<ul>
<li>loopback - 127.0.0.1/8, ::1/128</li>
<li>linklocal - 169.254.0.0/16, fe80::/10</li>
<li>uniquelocal - 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, fc00::/7</li>
</ul>
</blockquote>
<p>使用如下方式设置 <code class="language-plaintext highlighter-rouge">IP</code> 地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('trust proxy', 'loopback') // 指定唯一子网
app.set('trust proxy', 'loopback, 123.123.123.123') // 指定子网和 IP 地址
app.set('trust proxy', 'loopback, linklocal, uniquelocal') // 指定多个子网
app.set('trust proxy', ['loopback', 'linklocal', 'uniquelocal']) // 使用数组指定多个子网 当指定地址时,`IP` 地址或子网从地址确定过程中被除去,离应用服务器最近的非受信 `IP` 地址被当作客户端 `IP` 地址。
</code></pre></div></div>
<h2 id="3number">3、Number</h2>
<p>将代理服务器前第 n 跳当作客户端。</p>
<h2 id="4function">4、Function</h2>
<p>定制实现,只有在您知道自己在干什么时才能这样做。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('trust proxy', function (ip) {
if (ip === '127.0.0.1' || ip === '123.123.123.123') return true; // 受信的 IP 地址
else return false;
}) 以上就是 `trust proxy` 设置项
</code></pre></div></div>
<p>设置 <code class="language-plaintext highlighter-rouge">trust proxy</code> 为非假值会带来三个重要变化:</p>
<blockquote>
<ul>
<li>反向代理可能设置 <code class="language-plaintext highlighter-rouge">X-Forwarded-Proto</code> 来告诉应用使用 <code class="language-plaintext highlighter-rouge">https</code> 或简单的 <code class="language-plaintext highlighter-rouge">http</code> 协议。请参考 <code class="language-plaintext highlighter-rouge">req.protocol</code>。</li>
<li>无论是 <code class="language-plaintext highlighter-rouge">HTTP</code>、<code class="language-plaintext highlighter-rouge">HTTPS</code> 或者是无效的名称,都可以通过反向代理来设置 <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> 通知应用程序。这个值是通过 <code class="language-plaintext highlighter-rouge">req.protocol</code> 来反应的。</li>
<li><code class="language-plaintext highlighter-rouge">req.ip</code> 和 <code class="language-plaintext highlighter-rouge">req.ips</code> 的值将会由 <code class="language-plaintext highlighter-rouge">X-Forwarded-For</code> 中列出的 IP 地址构成。</li>
</ul>
</blockquote>
<p><code class="language-plaintext highlighter-rouge">trust proxy</code> 设置由 <code class="language-plaintext highlighter-rouge">proxy-addr</code> 软件包实现,请参考其文档了解更多信息。</p>
express3.x-模板引擎
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-using-template-engines
<p>原文:
<a href="http://expressjs.com/guide/using-template-engines.html">http://expressjs.com/guide/using-template-engines.html</a></p>
<h1 id="在-express-中使用模板引擎">在 Express 中使用模板引擎</h1>
<p>需要在应用中进行如下设置才能让 Express 渲染模板文件:</p>
<p>·views, 放模板文件的目录,比如: app.set(‘views’, ‘./views’)</p>
<p>·view engine, 模板引擎,比如: app.set(‘view engine’, ‘jade’)
然后安装相应的模板引擎 npm 软件包。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install jade --save
</code></pre></div></div>
<blockquote>
<p>和 Express 兼容的模板引擎,比如 Jade,通过 res.render() 调用其导出方法__express(filePath,options, callback) 渲染模板。有一些模板引擎不遵循这种约Consolidate.js 能将 Node 中所有流行的模板引擎映射为这种约定,这样就可以和 Express 无缝衔接。</p>
</blockquote>
<p>一旦 view engine 设置成功,就不需要显式指定引擎,或者在应用中加载模板引擎模块,Express 已经在内部加载,如下所示。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('view engine', 'jade'); 在 views 目录下生成名为 index.jade 的 Jade 模板文件,内容如下:
html
head
title!= title
body
h1!= message 然后创建一个路由渲染 index.jade 文件。如果没有设置 view engine,您需要指明视图文件的后缀,否则就会遗漏它。
app.get('/', function (req, res) {
res.render('index', { title: 'Hey', message: 'Hello there!'});
}); 此时向主页发送请求,“index.jade” 会被渲染为 HTML。
</code></pre></div></div>
<p>请阅读 “为 Express 开发模板引擎” 了解模板引擎在 Express 中是如何工作的。</p>
express3.x-中间件
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-using-middlewar
<p>原文 <a href="http://expressjs.com/guide/using-middleware.html">http://expressjs.com/guide/using-middleware.html</a></p>
<h1 id="使用中间件">使用中间件</h1>
<p>Express 是一个自身功能极简,完全是由路由和中间件构成一个的 web 开发框架:从本质上来说,一个 Express 应用就是在调用各种中间件。</p>
<p>中间件(Middleware) 是一个函数,它可以访问请求对象(request object (req)), 响应对象(response object (res)), 和 web 应用中处于请求-响应循环流程中的中间件,一般被命名为 next 的变量。</p>
<p>中间件的功能包括:</p>
<p>·执行任何代码。</p>
<p>·修改请求和响应对象。</p>
<p>·终结请求-响应循环。</p>
<p>·调用堆栈中的下一个中间件。</p>
<p>如果当前中间件没有终结请求-响应循环,则必须调用 next() 方法将控制权交给下一个中间件,否则 请求就会挂起。</p>
<p>Express 应用可使用如下几种中间件:</p>
<p>·应用级中间件</p>
<p>·路由级中间件</p>
<p>·错误处理中间件</p>
<p>·内置中间件</p>
<p>·第三方中间件</p>
<p>使用可选则挂载路径,可在应用级别或路由级别装载中间件。另外,你还可以同时装在一系列中间件函数,从而在一个挂载点上创建一个子中间件栈。</p>
<h1 id="应用级中间件">应用级中间件</h1>
<p>应用级中间件绑定到 app 对象 使用 app.use() 和 app.METHOD(), 其中, METHOD 是需要处理的 HTTP 请求的方法,例如 GET, PUT, POST 等等,全部小写。例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = express();
// 没有挂载路径的中间件,应用的每个请求都会执行该中间件
app.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 挂载至 /user/:id 的中间件,任何指向 /user/:id 的请求都会执行它
app.use('/user/:id', function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 路由和句柄函数(中间件系统),处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
res.send('USER');
}); 下面这个例子展示了在一个挂载点装载一组中间件。
// 一个中间件栈,对任何指向 /user/:id 的 HTTP 请求打印出相关信息
app.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
}); 作为中间件系统的路由句柄,使得为路径定义多个路由成为可能。在下面的例子中,为指向 /user/:id 的 GET 请求定义了两个路由。第二个路由虽然不会带来任何问题,但却永远不会被调用,因为第一个路由已经终止了请求-响应循环。
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
console.log('ID:', req.params.id);
next();
}, function (req, res, next) {
res.send('User Info');
});
// 处理 /user/:id, 打印出用户 id
app.get('/user/:id', function (req, res, next) {
res.end(req.params.id);
}); 如果需要在中间件栈中跳过剩余中间件,调用 next('route') 方法将控制权交给下一个路由。 注意: next('route') 只对使用 app.VERB() 或 router.VERB() 加载的中间件有效。
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
app.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 否则将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('regular');
});
// 处理 /user/:id, 渲染一个特殊页面
app.get('/user/:id', function (req, res, next) {
res.render('special');
}); # 路由级中间件 路由级中间件和应用级中间件一样,只是它绑定的对象为 express.Router()。
var router = express.Router(); 路由级使用 router.use() 或 router.VERB() 加载。
</code></pre></div></div>
<p>上述在应用级创建的中间件系统,可通过如下代码改写为路由级:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = express();
var router = express.Router();
// 没有挂载路径的中间件,通过该路由的每个请求都会执行该中间件
router.use(function (req, res, next) {
console.log('Time:', Date.now());
next();
});
// 一个中间件栈,显示任何指向 /user/:id 的 HTTP 请求的信息
router.use('/user/:id', function(req, res, next) {
console.log('Request URL:', req.originalUrl);
next();
}, function (req, res, next) {
console.log('Request Type:', req.method);
next();
});
// 一个中间件栈,处理指向 /user/:id 的 GET 请求
router.get('/user/:id', function (req, res, next) {
// 如果 user id 为 0, 跳到下一个路由
if (req.params.id == 0) next('route');
// 负责将控制权交给栈中下一个中间件
else next(); //
}, function (req, res, next) {
// 渲染常规页面
res.render('regular');
});
// 处理 /user/:id, 渲染一个特殊页面
router.get('/user/:id', function (req, res, next) {
console.log(req.params.id);
res.render('special');
});
// 将路由挂载至应用
app.use('/', router); # 错误处理中间件
</code></pre></div></div>
<blockquote>
<p>错误处理中间件有 4 个参数,定义错误处理中间件时必须使用这 4 个参数。即使不需要 next 对象,也必须在签名中声明它,否则中间件会被识别为一个常规中间件,不能处理错误。</p>
</blockquote>
<p>错误处理中间件和其他中间件定义类似,只是要使用 4 个参数,而不是 3 个,其签名如下: (err, req, res, next)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
}); 请参考 错误处理 一章了解更多关于错误处理中间件的内容。
</code></pre></div></div>
<h1 id="内置中间件">内置中间件</h1>
<p>从 4.x 版本开始,, Express 已经不再依赖 Connect 了。除了 express.static, Express 以前内置的中间件现在已经全部单独作为模块安装使用了。请参考 中间件列表。</p>
<h4 id="expressstaticroot-options">express.static(root, [options])</h4>
<p>express.static 是 Express 唯一内置的中间件。它基于 serve-static,负责在 Express 应用中提托管静态资源。</p>
<p>参数 root 指提供静态资源的根目录。</p>
<p>可选的 options 参数拥有如下属性。</p>
<table>
<thead>
<tr>
<th>属性</th>
<th>描述</th>
<th>类型</th>
<th>缺省值</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>dotfiles</code></td>
<td>是否对外输出文件名以点(<code>.</code>)开头的文件。可选值为 “allow”、“deny” 和 “ignore”</td>
<td>String</td>
<td>“ignore”</td>
</tr>
<tr>
<td><code>etag</code></td>
<td>是否启用 etag 生成</td>
<td>Boolean</td>
<td>true</td>
</tr>
<tr>
<td><code>extensions</code></td>
<td>设置文件扩展名备份选项</td>
<td>Array</td>
<td>[]</td>
</tr>
<tr>
<td><code>index</code></td>
<td>发送目录索引文件,设置为 false 禁用目录索引。</td>
<td>Mixed</td>
<td>“index.html”</td>
</tr>
<tr>
<td><code>lastModified</code></td>
<td>设置 Last-Modified 头为文件在操作系统上的最后修改日期。可能值为 true 或 false。</td>
<td>Boolean</td>
<td>true</td>
</tr>
<tr>
<td><code>maxAge</code></td>
<td>以毫秒或者其字符串格式</a>设置 Cache-Control 头的 max-age 属性。</td>
<td>Number</td>
<td>0</td>
</tr>
<tr>
<td><code>redirect</code></td>
<td>当路径为目录时,重定向至 “/”。</td>
<td>Boolean</td>
<td>true</td>
</tr>
<tr>
<td><code>setHeaders</code></td>
<td>设置 HTTP 头以提供文件的函数。</td>
<td>Function</td>
<td> </td>
</tr>
</tbody>
</table>
<p>下面的例子使用了 express.static 中间件,其中的 options 对象经过了精心的设计。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var options = {
dotfiles: 'ignore',
etag: false,
extensions: ['htm', 'html'],
index: false,
maxAge: '1d',
redirect: false,
setHeaders: function (res, path, stat) {
res.set('x-timestamp', Date.now());
}
}
app.use(express.static('public', options)); 每个应用可有多个静态目录。
app.use(express.static('public'));
app.use(express.static('uploads'));
app.use(express.static('files')); 更多关于 serve-static 和其参数的信息,请参考 serve-static 文档。
</code></pre></div></div>
<p>第三方中间件
通过使用第三方中间件从而为 Express 应用增加更多功能。</p>
<p>安装所需功能的 node 模块,并在应用中加载,可以在应用级加载,也可以在路由级加载。</p>
<p>下面的例子安装并加载了一个解析 cookie 的中间件: cookie-parser</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install cookie-parser
</code></pre></div></div>
<p>····</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
var cookieParser = require('cookie-parser');
// 加载用于解析 cookie 的中间件
app.use(cookieParser()); 请参考 第三方中间件 获取 Express 中经常用到的第三方中间件列表。
</code></pre></div></div>
express3.x-路由
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-routing
<p>原文:
<a href="http://expressjs.com/guide/routing.html">http://expressjs.com/guide/routing.html</a></p>
<h1 id="路由">路由</h1>
<p>路由是指如何定义应用的端点(URIs)以及如何响应客户端的请求。</p>
<p>路由是由一个 URI、HTTP 请求(GET、POST等)和若干个句柄组成,它的结构如下: app.METHOD(path, [callback…], callback), app 是 express 对象的一个实例, METHOD 是一个 HTTP 请求方法, path 是服务器上的路径, callback 是当路由匹配时要执行的函数。</p>
<p>下面是一个基本的路由示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
// respond with "hello world" when a GET request is made to the homepage
app.get('/', function(req, res) {
res.send('hello world');
});
</code></pre></div></div>
<h1 id="路由方法">路由方法</h1>
<p>路由方法源于 HTTP 请求方法,和 express 实例相关联。</p>
<p>下面这个例子展示了为应用跟路径定义的 GET 和 POST 请求:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET method route
app.get('/', function (req, res) {
res.send('GET request to the homepage');
});
// POST method route
app.post('/', function (req, res) {
res.send('POST request to the homepage');
});
</code></pre></div></div>
<p>Express 定义了如下和 HTTP 请求对应的路由方法: get, post, put, head, delete, options, trace, copy, lock, mkcol, move, purge, propfind, proppatch, unlock, report, mkactivity, checkout, merge, m-search, notify, subscribe, unsubscribe, patch, search, 和 connect。</p>
<blockquote>
<p>有些路由方法名不是合规的 JavaScript 变量名,此时使用括号记法,比如: app[‘m-search’](‘/’, function …</p>
</blockquote>
<p>app.all() 是一个特殊的路由方法,没有任何 HTTP 方法与其对应,它的作用是对于一个路径上的所有请求加载中间件。</p>
<p>在下面的例子中,来自 “/secret” 的请求,不管使用 GET、POST、PUT、DELETE 或其他任何 http 模块支持的 HTTP 请求,句柄都会得到执行。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('/secret', function (req, res, next) {
console.log('Accessing the secret section ...');
next(); // pass control to the next handler
});
</code></pre></div></div>
<h1 id="路由路径">路由路径</h1>
<p>路由路径和请求方法一起定义了请求的端点,它可以是字符串、字符串模式或者正则表达式。</p>
<blockquote>
<p>Express 使用 path-to-regexp 匹配路由路径,请参考文档查阅所有定义路由路径的方法。 Express Route Tester 是测试基本 Express 路径的好工具,但不支持模式匹配.</p>
</blockquote>
<p>```</p>
<blockquote>
<p>查询字符串不是路由路径的一部分。</p>
</blockquote>
<p>使用字符串的路由路径示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 匹配根路径的请求
app.get('/', function (req, res) {
res.send('root');
});
// 匹配 /about 路径的请求
app.get('/about', function (req, res) {
res.send('about');
});
// 匹配 /random.text 路径的请求
app.get('/random.text', function (req, res) {
res.send('random.text');
}); 使用字符串模式的路由路径示例:
// 匹配 acd 和 abcd
app.get('/ab?cd', function(req, res) {
res.send('ab?cd');
});
// 匹配 abcd、abbcd、abbbcd等
app.get('/ab+cd', function(req, res) {
res.send('ab+cd');
});
// 匹配 abcd、abxcd、abRABDOMcd、ab123cd等
app.get('/ab*cd', function(req, res) {
res.send('ab*cd');
});
// 匹配 /abe 和 /abcde
app.get('/ab(cd)?e', function(req, res) {
res.send('ab(cd)?e');
}); ···
</code></pre></div></div>
<blockquote>
<p>字符 ?、+、* 和 () 是正则表达式的子集,- 和 . 在基于字符串的路径中按照字面值解释。</p>
</blockquote>
<p>使用正则表达式的路由路径示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // 匹配任何路径中含有 a 的路径:
app.get(/a/, function(req, res) {
res.send('/a/');
});
// 匹配 butterfly、dragonfly,不匹配 butterflyman、dragonfly man等
app.get(/.*fly$/, function(req, res) {
res.send('/.*fly$/');
}); #路由句柄 可以为请求处理提供多个回调函数,其行为类似 中间件。唯一的区别是这些回调函数有可能调用 next('route') 方法而略过其他路由回调函数。可以利用该机制为路由定义前提条件,如果在现有路径上继续执行没有意义,则可将控制权交给剩下的路径。
</code></pre></div></div>
<p>路由句柄有多种形式,可以是一个函数、一个函数数组,或者是两者混合,如下所示.</p>
<p>使用一个回调函数处理路由:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/example/a', function (req, res) {
res.send('Hello from A!');
}); 使用多个回调函数处理路由(记得指定 next 对象):
app.get('/example/b', function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from B!');
}); 使用回调函数数组处理路由:
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
var cb2 = function (req, res) {
res.send('Hello from C!');
}
app.get('/example/c', [cb0, cb1, cb2]); 混合使用函数和函数数组处理路由:
var cb0 = function (req, res, next) {
console.log('CB0');
next();
}
var cb1 = function (req, res, next) {
console.log('CB1');
next();
}
app.get('/example/d', [cb0, cb1], function (req, res, next) {
console.log('response will be sent by the next function ...');
next();
}, function (req, res) {
res.send('Hello from D!');
}); #响应方法 下表中响应对象(res)的方法向客户端返回响应,终结请求响应的循环。如果在路由句柄中一个方法也不调用,来自客户端的请求会一直挂起。
</code></pre></div></div>
<table>
<thead>
<tr>
<th>方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>res.download</a></td>
<td>提示下载文件。</td>
</tr>
<tr>
<td>res.end</a></td>
<td>终结响应处理流程。</td>
</tr>
<tr>
<td>res.json</a></td>
<td>发送一个 JSON 格式的响应。</td>
</tr>
<tr>
<td>res.jsonp</a></td>
<td>发送一个支持 JSONP 的 JSON 格式的响应。</td>
</tr>
<tr>
<td>res.redirect</a></td>
<td>重定向请求。</td>
</tr>
<tr>
<td>res.render</a></td>
<td>渲染视图模板。</td>
</tr>
<tr>
<td>res.send</a></td>
<td>发送各种类型的响应。</td>
</tr>
<tr>
<td>res.sendFile</a></td>
<td>以八位字节流的形式发送文件。</td>
</tr>
<tr>
<td>res.sendStatus</a></td>
<td>设置响应状态代码,并将其以字符串形式作为响应体的一部分发送。</td>
</tr>
</tbody>
</table>
<p>###app.route()<br />
可使用 app.route() 创建路由路径的链式路由句柄。由于路径在一个地方指定,这样做有助于创建模块化的路由,而且减少了代码冗余和拼写错误。请参考 Router() 文档 了解更多有关路由的信息。</p>
<p>下面这个示例程序使用 app.route() 定义了链式路由句柄。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.route('/book')
.get(function(req, res) {
res.send('Get a random book');
})
.post(function(req, res) {
res.send('Add a book');
})
.put(function(req, res) {
res.send('Update the book');
</code></pre></div></div>
<p>###express.Router
可使用 express.Router 类创建模块化、可挂载的路由句柄。Router 实例是一个完整的中间件和路由系统,因此常称其为一个 “mini-app”。</p>
<p>下面的实例程序创建了一个路由模块,并加载了一个中间件,定义了一些路由,并且将它们挂载至应用的路径上。</p>
<p>在 app 目录下创建名为 birds.js 的文件,内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var router = express.Router();
// 该路由使用的中间件
router.use(function timeLog(req, res, next) {
console.log('Time: ', Date.now());
next();
});
// 定义网站主页的路由
router.get('/', function(req, res) {
res.send('Birds home page');
});
// 定义 about 页面的路由
router.get('/about', function(req, res) {
res.send('About birds');
});
module.exports = router;
</code></pre></div></div>
<p>然后在应用中加载路由模块:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var birds = require('./birds');
...
app.use('/birds', birds); 应用即可处理发自 /birds 和 /birds/about 的请求,并且调用为该路由指定的 timeLog 中间件。
</code></pre></div></div>
express3.x-错误处理
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-error-handling
<p>原文:<a href="http://expressjs.com/guide/error-handling.html">http://expressjs.com/guide/error-handling.html</a></p>
<h2 id="错误处理">错误处理</h2>
<p>定义错误处理中间件和定义其他中间件一样,除了需要 4 个参数,而不是 3 个,其格式如下 (err, req, res, next)。例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(function(err, req, res, next) {
console.error(err.stack);
res.status(500).send('Something broke!');
}); 在其他 app.use() 和路由调用后,最后定义错误处理中间件,比如:
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(function(err, req, res, next) {
// 业务逻辑
}); 中间件返回的响应是随意的,可以响应一个 HTML 错误页面、一句简单的话、一个 JSON 字符串,或者其他任何您想要的东西。
</code></pre></div></div>
<p>为了便于组织(更高级的框架),您可能会像定义常规中间件一样,定义多个错误处理中间件。比如您想为使用 XHR 的请求定义一个,还想为没有使用的定义一个,那么:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var bodyParser = require('body-parser');
var methodOverride = require('method-override');
app.use(bodyParser());
app.use(methodOverride());
app.use(logErrors);
app.use(clientErrorHandler);
app.use(errorHandler);
</code></pre></div></div>
<p>logErrors 将请求和错误信息写入标准错误输出、日志或类似服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function logErrors(err, req, res, next) {
console.error(err.stack);
next(err);
} clientErrorHandler 的定义如下(注意这里将错误直接传给了 next):
function clientErrorHandler(err, req, res, next) {
if (req.xhr) {
res.status(500).send({ error: 'Something blew up!' });
} else {
next(err);
}
} errorHandler 能捕获所有错误,其定义如下:
function errorHandler(err, req, res, next) {
res.status(500);
res.render('error', { error: err });
} 如果向 next() 传入参数(除了 ‘route’ 字符串),Express 会认为当前请求有错误的输出,因此跳过后续其他非错误处理和路由/中间件函数。如果需做特殊处理,需要创建新的错误处理路由,如下节所示。
</code></pre></div></div>
<p>如果路由句柄有多个回调函数,可使用 ‘route’ 参数跳到下一个路由句柄。比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/a_route_behind_paywall',
function checkIfPaidSubscriber(req, res, next) {
if(!req.user.hasPaid) {
// 继续处理该请求
next('route');
}
}, function getPaidContent(req, res, next) {
PaidContent.find(function(err, doc) {
if(err) return next(err);
res.json(doc);
});
}); 在这个例子中,句柄 getPaidContent 会被跳过,但 app 中为 /a_route_behind_paywall 定义的其他句柄则会继续执行。
</code></pre></div></div>
<blockquote>
<p>next() 和 next(err) 类似于 Promise.resolve() 和 Promise.reject()。它们让您可以向 Express 发信号,告诉它当前句柄执行结束并且处于什么状态。next(err) 会跳过后续句柄,除了那些用来处理错误的句柄。</p>
</blockquote>
<h2 id="缺省错误处理句柄">缺省错误处理句柄</h2>
<p>Express 内置了一个错误处理句柄,它可以捕获应用中可能出现的任意错误。这个缺省的错误处理中间件将被添加到中间件堆栈的底部。</p>
<p>如果你向 next() 传递了一个 error ,而你并没有在错误处理句柄中处理这个 error,Express 内置的缺省错误处理句柄就是最后兜底的。最后错误将被连同堆栈追踪信息一同反馈到客户端。堆栈追踪信息并不会在生产环境中反馈到客户端。</p>
<blockquote>
<p>设置环境变量 NODE_ENV 为 “production” 就可以让应用运行在生产环境模式下。</p>
</blockquote>
<p>如果你已经开始向 response 输出数据了,这时才调用 next() 并传递了一个 error,比如你在将向客户端输出数据流时遇到一个错误,Express 内置的缺省错误处理句柄将帮你关闭连接并告知 request 请求失败。</p>
<p>因此,当你添加了一个自定义的错误处理句柄后,如果已经向客户端发送包头信息了,你还可以将错误处理交给 Express 内置的错误处理机制。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function errorHandler(err, req, res, next) {
if (res.headersSent) {
return next(err);
}
res.status(500);
res.render('error', { error: err });
}
</code></pre></div></div>
express3.x-调试
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-debugging
<p>原文:<a href="http://expressjs.com/guide/debugging.html">http://expressjs.com/guide/debugging.html</a></p>
<h1 id="调试-express">调试 Express</h1>
<p>Express 内部使用 debug 模块记录路由匹配、使用到的中间件、应用模式以及请求-响应循环。</p>
<blockquote>
<p>debug 有点像改装过的 console.log,不同的是,您不需要在生产代码中注释掉 debug。它会默认关闭,而且使用一个名为 DEBUG 的环境变量还可以打开。</p>
</blockquote>
<p>在启动应用时,设置 DEBUG 环境变量为 express:*,可以查看 Express 中用到的所有内部日志。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ DEBUG=express:* node index.js 在 Windows 系统里,使用如下的命令。
> set DEBUG=express:* & node index.js 在由 express 应用生成器 生成的默认应用中执行,会打印出如下信息:
$ DEBUG=express:* node ./bin/www
express:router:route new / +0ms
express:router:layer new / +1ms
express:router:route get / +1ms
express:router:layer new / +0ms
express:router:route new / +1ms
express:router:layer new / +0ms
express:router:route get / +0ms
express:router:layer new / +0ms
express:application compile etag weak +1ms
express:application compile query parser extended +0ms
express:application compile trust proxy false +0ms
express:application booting in development mode +1ms
express:router use / query +0ms
express:router:layer new / +0ms
express:router use / expressInit +0ms
express:router:layer new / +0ms
express:router use / favicon +1ms
express:router:layer new / +0ms
express:router use / logger +0ms
express:router:layer new / +0ms
express:router use / jsonParser +0ms
express:router:layer new / +1ms
express:router use / urlencodedParser +0ms
express:router:layer new / +0ms
express:router use / cookieParser +0ms
express:router:layer new / +0ms
express:router use / stylus +90ms
express:router:layer new / +0ms
express:router use / serveStatic +0ms
express:router:layer new / +0ms
express:router use / router +0ms
express:router:layer new / +1ms
express:router use /users router +0ms
express:router:layer new /users +0ms
express:router use / <anonymous> +0ms
express:router:layer new / +0ms
express:router use / <anonymous> +0ms
express:router:layer new / +0ms
express:router use / <anonymous> +0ms
express:router:layer new / +0ms 当应用收到请求时,能看到 Express 代码中打印出的日志。
express:router dispatching GET / +4h
express:router query : / +2ms
express:router expressInit : / +0ms
express:router favicon : / +0ms
express:router logger : / +1ms
express:router jsonParser : / +0ms
express:router urlencodedParser : / +1ms
express:router cookieParser : / +0ms
express:router stylus : / +0ms
express:router serveStatic : / +2ms
express:router router : / +2ms
express:router dispatching GET / +1ms
express:view lookup "index.jade" +338ms
express:view stat "/projects/example/views/index.jade" +0ms
express:view render "/projects/example/views/index.jade" +1ms 设置 DEBUG 的值为 express:router,只查看路由部分的日志;设置 DEBUG 的值为 express:application,只查看应用部分的日志,依此类推。
</code></pre></div></div>
<h1 id="通过-express-生成应用">通过 express 生成应用</h1>
<p>通过 express 命令行生成的应用也使用了 debug 模块,它的命名空间限制在应用中。</p>
<p>如果您通过下述命令生成应用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ express sample-app 则可通过下述命令打开调试信息:
$ DEBUG=sample-app node ./bin/www 可通过逗号隔开的名字列表来指定多个调试命名空间,如下所示:
$ DEBUG=http,mail,express:* node index.js 请查阅 调试指南 获取更多有关 debug 的文档。
</code></pre></div></div>
express3.x-数据库集成
2015-10-18T00:00:00+00:00
http://www.blogways.net/blog/2015/10/18/express-database-integration
<p>原文
<a href="http://expressjs.com/guide/database-integration.html">http://expressjs.com/guide/database-integration.html</a></p>
<h1 id="集成数据库">集成数据库</h1>
<p>为 Express 应用添加连接数据库的能力,只需要加载相应数据库的 Node.js 驱动即可。这里将会简要介绍如何为 Express 应用添加和使用一些常用的数据库 Node 模块。</p>
<p>·Cassandra</p>
<p>·CouchDB</p>
<p>·LevelDB</p>
<p>·MySQL</p>
<p>·MongoDB</p>
<p>·Neo4j</p>
<p>·PostgreSQL</p>
<p>·Redis</p>
<p>·SQLite</p>
<p>·ElasticSearch</p>
<blockquote>
<p>这些数据库驱动只是其中一部分,可在 npm 官网 查找更多驱动。</p>
</blockquote>
<p>Cassandra
模块: cassandra-driver
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install cassandra-driver 示例
var cassandra = require('cassandra-driver');
var client = new cassandra.Client({ contactPoints: ['localhost']});
client.execute('select key from system.local', function(err, result) {
if (err) throw err;
console.log(result.rows[0]);
});
</code></pre></div></div>
<h3 id="couchdb">CouchDB</h3>
<p>模块: nano
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install nano 示例
var nano = require('nano')('http://localhost:5984');
nano.db.create('books');
var books = nano.db.use('books');
//Insert a book document in the books database
books.insert({name: 'The Art of war'}, null, function(err, body) {
if (!err){
console.log(body);
}
});
//Get a list of all books
books.list(function(err, body){
console.log(body.rows);
}
</code></pre></div></div>
<h3 id="leveldb">LevelDB</h3>
<p>模块: levelup
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install level levelup leveldown 示例
var levelup = require('levelup');
var db = levelup('./mydb');
db.put('name', 'LevelUP', function (err) {
if (err) return console.log('Ooops!', err);
db.get('name', function (err, value) {
if (err) return console.log('Ooops!', err);
console.log('name=' + value)
});
});
</code></pre></div></div>
<h3 id="mysql">MySQL</h3>
<p>模块: mysql
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install mysql 示例
var mysql = require('mysql');
var connection = mysql.createConnection({
host : 'localhost',
user : 'dbuser',
password : 's3kreee7'
});
connection.connect();
connection.query('SELECT 1 + 1 AS solution', function(err, rows, fields) {
if (err) throw err;
console.log('The solution is: ', rows[0].solution);
});
connection.end();
</code></pre></div></div>
<h3 id="mongodb">MongoDB</h3>
<p>模块: mongoskin
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install mongoskin 示例
var db = require('mongoskin').db('localhost:27017/animals');
db.collection('mamals').find().toArray(function(err, result) {
if (err) throw err;
console.log(result);
});
If you want a object model driver for MongoDB, checkout Mongoose.
</code></pre></div></div>
<h3 id="neo4j">Neo4j</h3>
<p>模块: apoc
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install apoc 示例
var apoc = require('apoc');
apoc.query('match (n) return n').exec().then(
function (response) {
console.log(response);
},
function (fail) {
console.log(fail);
}
);
</code></pre></div></div>
<h3 id="postgresql">PostgreSQL</h3>
<p>模块: pg
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install pg 示例
var pg = require('pg');
var conString = "postgres://username:password@localhost/database";
pg.connect(conString, function(err, client, done) {
if (err) {
return console.error('error fetching client from pool', err);
}
client.query('SELECT $1::int AS number', ['1'], function(err, result) {
done();
if (err) {
return console.error('error running query', err);
}
console.log(result.rows[0].number);
});
});
</code></pre></div></div>
<h3 id="redis">Redis</h3>
<p>模块: redis
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install redis 示例
var client = require('redis').createClient();
client.on('error', function (err) {
console.log('Error ' + err);
});
client.set('string key', 'string val', redis.print);
client.hset('hash key', 'hashtest 1', 'some value', redis.print);
client.hset(['hash key', 'hashtest 2', 'some other value'], redis.print);
client.hkeys('hash key', function (err, replies) {
console.log(replies.length + ' replies:');
replies.forEach(function (reply, i) {
console.log(' ' + i + ': ' + reply);
});
client.quit();
});
</code></pre></div></div>
<h3 id="sqlite">SQLite</h3>
<p>模块: sqlite3
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install sqlite3 示例
var sqlite3 = require('sqlite3').verbose();
var db = new sqlite3.Database(':memory:');
db.serialize(function() {
db.run('CREATE TABLE lorem (info TEXT)');
var stmt = db.prepare('INSERT INTO lorem VALUES (?)');
for (var i = 0; i < 10; i++) {
stmt.run('Ipsum ' + i);
}
stmt.finalize();
db.each('SELECT rowid AS id, info FROM lorem', function(err, row) {
console.log(row.id + ': ' + row.info);
});
});
db.close();
</code></pre></div></div>
<h3 id="elasticsearch">ElasticSearch</h3>
<p>模块: elasticsearch
安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install elasticsearch 示例
var elasticsearch = require('elasticsearch');
var client = elasticsearch.Client({
host: 'localhost:9200'
});
client.search({
index: 'books',
type: 'book',
body: {
query: {
multi_match: {
query: 'express js',
fields: ['title', 'description']
}
}
}
}).then(function(response) {
var hits = response.hits.hits;
}, function(error) {
console.trace(error.message);
});
</code></pre></div></div>
jQuery2.1.1源码学习[2] -- 核心设计
2015-09-18T00:00:00+00:00
http://www.blogways.net/blog/2015/09/18/jquery-211-study-2
<p>##一、静态与实力方法共享的设计
学习jqeury的时候会发现也和其它编程语言一样存在有实例方法和静态方法,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(".a").each() //作为实例方法存在
$.each() //作为静态方法存在 上面是对class=a的进行遍历,下面是可以遍历任何想要遍历的东西,在jQuery中都是通过一个方法实现的
jQuery.prototype = {
each: function( callback, args ) {
return jQuery.each( this, callback, args );
}
} 可以看到这样就能公用一个方法
ajQuery.fn = ajQuery.prototype = {
name: 'aaron',
init: function(selector) {
this.selector = selector;
return this;
},
constructor: ajQuery
}
ajQuery.fn.init.prototype = ajQuery.fn
</code></pre></div></div>
<p>方法都是要构造才行的,静态的each 可以直接添加进jquery构造器,但是实力方法不行,通过上面的代码将jQuery的原型对象覆盖了init构造器的原型对象,这样也可以直接构造实例方法了。</p>
<p>##二、方法链式调用的设计
使用jQuery的时候经常链式调用,可以一直操作你取出来的一段东西,方便,代码量少</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>map: function( callback ) {
return this.pushStack( jQuery.map(this, function( elem, i ) {
return callback.call( elem, i, elem );
}));
},
slice: function() {
return this.pushStack( slice.apply( this, arguments ) );
},
first: function() {
return this.eq( 0 );
},
last: function() {
return this.eq( -1 );
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
},
end: function() {
return this.prevObject || this.constructor(null);
} 比如说以上的各种 return this 这样就可以一直调用了。
</code></pre></div></div>
<p>##三、插件接口的设计
jQuery 可以自己扩展方法,一般方法存在于下面两个地方</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
data:function(){},
removeData:function(){}
})
jQuery.fn.extend({
data:function(){},
removeData:function(){}
}) 就相当于自己扩展其它语言里的实例方法和静态方法。
</code></pre></div></div>
Prototype.js 源码学习
2015-09-09T00:00:00+00:00
http://www.blogways.net/blog/2015/09/09/Prototype-js
<h2 id="一整体架构学习">一、整体架构学习</h2>
<p>总的来说prototype.js主要给js原生的类扩展了各的方法,通过两种方式扩展,主要用到下面这个函数扩展。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Object.extend = function(destination, source) {
for (property in source) {
destination[property] = source[property];
}
return destination;
}
</code></pre></div></div>
<p>第一种就是直接扩展,这样就直接扩展进了某个类的方法中,可以直接调用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Object.extend(String.prototype, {
/*
* 将Html转换为纯文本,例如:
* var s="<font color='red'>hello</font>";
* s.stripTags()将得到“hello”。
*/
stripTags: function() {
return this.replace(/<//?[^>]+>/gi, '');
}
});
</code></pre></div></div>
<p>第二种就是现在源码中定义一个类,然后直接扩展进去比如定义的Enumerable类可以扩展进Array 和 Hash 等,方式如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var YourObject = Class.create();
Object.extend(YourObject.prototype, Enumerable);
Object.extend(YourObject.prototype, {
initialize: function() {
// 构造函数
},
_each: function(iterator) {
// 迭代代码,每次循环时调用 iterator
},
// 其它自定义方法,包括需要重写的 Enumerable 方法
}); 要用到Enumerable 必须添加进 _each方法。 prototype.js 就是通过这种方式扩展的方法,这个源码都遵循这两个规则。
</code></pre></div></div>
<h2 id="二-方法学习">二 方法学习</h2>
<p>最常用的就是this.each(function(value, index)这样进行迭代,相比下面总是省下了很多代码。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>for (var index = 0; index < myArray.length; ++index) {
var value = myArray[index];
// 你的代码...
};
</code></pre></div></div>
<p>方法一个个也说不完,总的来说就是掌握好源码内部定义的工具类和方法 然后再掌握好JS里原生方法,再掌握好一些判断的知识就能很好的学习Prototype.js ,有很多方法仅仅对原生方法进行简单的封装使之更容易调用。学习的时候代码中如何进行变量名命名,注意代码的格式等。</p>
jQuery2.1.1源码学习[1] -- 整体架构
2015-09-08T00:00:00+00:00
http://www.blogways.net/blog/2015/09/08/jquery-211-study-1
<h2 id="一整体架构">一、整体架构</h2>
<p>jQuery一共13个模块,从2.1版开始jQuery支持通过AMD模块划分,jQuery在最开始发布的1.0版本是很简单的,只有CSS选择符、事件处理和AJAX交互3大块,所以源码学习很重要,浏览器兼容、各种属性的获取、逻辑流程、性能等都可以在学习源码的过程中体会到。
<img src="/images/zk-jq-1.png" alt="架构表" /></p>
<p>从这个架构图里面可可以清晰的看出jquery内部是如何工作的, 整体架构一目了然。</p>
<h2 id="二-代码开始如何进行整体架构">二、 代码开始如何进行整体架构###</h2>
<p>首先是jquery代码的开始</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(function(window, undefined) {
var jQuery = function() {}
// ...
window.jQuery = window.$ = jQuery;
})(window); 这里首先用了“JS高级程序中”关于第7章,模仿块级作用域的知识,js中是没有块级作用域的这和其它面向对象的语言有很大区别。而这里为了不污染全局变量采用了这种写 法,而且这个函数是自执行的。undefined 并不是作为关键字,因此可以允许用户对其赋值。同时这里也用了立即调用表达式 上面的代码等同于
var factory = function(){
return function(){
//执行方法
}
}
var jQuery = factory();
</code></pre></div></div>
<p>看到这里会发现 JQuery中有很多类似的东西:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function() {
// ...代码...
})
//document ready 简写
$(function() {
// ...代码...
})
$(document).load(function() {
// ...代码...
})。 这些都是加载文档的方式,需要记住的是ready先执行,load后执行。 在源码中还会经常出现:
jQuery.extends()
jQuery.fn.extends()
jQuery.extend = jQuery.fn.extend 分别扩展jquery 以及jquery 原型 类似面向对象语言里面的类方法和实例方法
</code></pre></div></div>
<p>还会出现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.prototype = jQuery.fn jQuery.fn.init.prototype
return jQuery.fn.init 这些都是方便jquery直接创建实例。
</code></pre></div></div>
<p>jquery对象是可以像数组一样被操作的,下面是常用调用的方式,他们都可以被当做数组或对象操作</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1. jQuery([selector,[context]])
2. jQuery(element)
3. jQuery(elementArray)
4. jQuery(object)
5. jQuery(jQuery object)
6. jQuery(html,[ownerDocument])
7. jQuery(html,[attributes])
8. jQuery()
9. jQuery(callback) 为什么可以这样操作,因为在jquery内部通过一种叫做类数组对象的方式存储数据,既可以当做数组操作也可以当做对象处理。
</code></pre></div></div>
在linux上安装oracle client
2015-08-20T00:00:00+00:00
http://www.blogways.net/blog/2015/08/20/install-oracleclient-on-linux
<h2 id="一资源">一、资源</h2>
<p>登录Oracle官网 <a href="http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html">http://www.oracle.com/technetwork/topics/linuxx86-64soft-092277.html</a> 。</p>
<h2 id="二选择">二、选择</h2>
<p>首先,选择你所需要的版本。</p>
<p>我选择的是 Version 11.2.0.4.0.</p>
<p>可以选择zip格式的,也可以选择rpm格式的。</p>
<p>我想自定义安装的路径,所以选择的是zip格式的。</p>
<p>根据说明,我选择了三个:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#Basic: All files required to run OCI, OCCI, and JDBC-OCI applications
instantclient-basic-linux.x64-12.1.0.1.0.zip
#SDK: Additional header files and an example makefile for developing Oracle applications with Instant Client
instantclient-sdk-linux.x64-12.1.0.1.0.zip
#SQL*Plus: Additional libraries and executable for running SQL*Plus with Instant Client
instantclient-sqlplus-linux.x64-12.1.0.1.0.zip
</code></pre></div></div>
<p>如果仅仅需要运行环境,下载第一个就可以了。如果还需要开发编译环境,还需要下载第二个。想用sqlplus,要下第三个。</p>
<h2 id="三安装">三、安装</h2>
<p>把三个zip包传到要安装的目录下,比如<code class="language-plaintext highlighter-rouge">/home/oracle</code>。执行下面命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>unzip instantclient-basic-linux.x64-12.1.0.1.0.zip
unzip instantclient-sdk-linux.x64-12.1.0.1.0.zip
unzip instantclient-sqlplus-linux.x64-12.1.0.1.0.zip
</code></pre></div></div>
<p>解压后的文件都在<code class="language-plaintext highlighter-rouge">/home/oracle/instantclient_11_2</code>目录下面。</p>
<p>如果需要编译环境,还要设置:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd /home/oracle/instantclient_11_2
ln -s libclntsh.so.11.1 libclntsh.so
</code></pre></div></div>
<p>设置环境变量:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export ORACLE_HOME=/home/oracle/instantclient_11_2
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$ORACLE_HOME/lib
export PATH=$PATH:$ORACLE_HOME/bin
export TNS_ADMIN=$ORACLE_HOME
export NLS_LANG=american_america.ZHS16GBK
</code></pre></div></div>
<p>在<code class="language-plaintext highlighter-rouge">$ORACLE_HOME</code>目录下配置你的<code class="language-plaintext highlighter-rouge">tnsnames.ora</code>文件</p>
<p>在编译oci/occi程序时,编译命令需要添加 <code class="language-plaintext highlighter-rouge">-I$ORACLE_HOME/sdk/include</code> ,链接命令需要添加 <code class="language-plaintext highlighter-rouge">-L$ORACLE_HOME</code></p>
<h2 id="四测试">四、测试</h2>
<p>在命令行输入:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sqlplus user/passwd@sid
</code></pre></div></div>
<p>可以看到熟悉的提示,就表示大功告成了!</p>
<h2 id="五回顾">五、回顾</h2>
<p>我当时按上面步骤安装后,运行<code class="language-plaintext highlighter-rouge">sqlplus</code>会报一个<code class="language-plaintext highlighter-rouge">ORA-21561</code>错误。后来在<code class="language-plaintext highlighter-rouge">/etc/hosts</code>文件中配置上的主机的名字,问题就解决了。</p>
Git Stat
2015-07-14T00:00:00+00:00
http://www.blogways.net/blog/2015/07/14/git-stat
<h2 id="git-stat">Git Stat</h2>
<p>最近项目在搞人力资源配置优化,其中一个就是对代码提交量做统计,项目使用的是 Git 仓库,本来想偷懒去网上找个现成的工具,结果有点失望,没有找到能用的,绝大部分都是 shell 脚本,虽然也能执行,但想要达到要求的结果还得大改,而且显示也不直观,再加上我 shell 水平比较弱,最后决定用 java 来写个分析工具,将分析结果放到 web 页面来展示。</p>
<h3 id="实现原理">实现原理</h3>
<p>由于是做一个全局的 Git 提交分析,这边只对 <code class="language-plaintext highlighter-rouge">git log</code> 的结果分析,分析的结果只显示增加的行数以及删除的行数,代码贡献以增加的行数为基础统计,个人提交明细显示统计时间段内每天提交量,横轴以[1-N]天来显示,并不显示具体的日期、周或者月份。</p>
<p>具体统计的执行脚本,例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git log --pretty=format:"%cn;%ad;%d" --numstat --date=iso --since=2015-07-06 --until=2015-07-11
</code></pre></div></div>
<p>本来想把<code class="language-plaintext highlighter-rouge">committer</code>参数带进去的,但项目在配 git 用户的时候用户名跟提交名并没有完全配成一样的,所以这个功能没有实现,目前的做法为一次性取出指定时间段内的所有提交结果,统一分析,将分析结果一起返回到前台页面,这样的话只要执行一次命令就可以了,但结果分析需要多花一些时间,尤其是统计时间长,提交人员多的时候响应会有点延时。几十个人的团队建议统计半年以内的结果,再大的我也没测过,如果有需求的话也可以跟我联系。</p>
<h3 id="界面展示">界面展示</h3>
<p>界面部分我也搞得比较素,没有用 UI,图表展示的时候用到了百度的 echarts,echarts 确实做得很好,渲染也很快,免费而且没有让人讨厌的水印。</p>
<p>下面看看几个主要的界面:</p>
<p>统计列表</p>
<p><img src="/images/git-stat-1.png" alt="统计列表" /></p>
<p>贡献饼图</p>
<p><img src="/images/git-stat-2.png" alt="统计饼图" /></p>
<p>个人提交明细</p>
<p><img src="/images/git-stat-3.png" alt="个人提交明细" /></p>
<p>前台 js 框架用的是 zepto,所以不兼容一些低版本的浏览器,推荐使用 chrome。</p>
<h3 id="操作说明">操作说明</h3>
<p>目前只提供一天内、一周内、一月内、自定义四种统计方式,自定义需要选择起始、结束日期,还有一个右上角的 <code class="language-plaintext highlighter-rouge">local git path</code> 第一次需要指定下,直接拷贝你本地的 git 仓库目录,Windows、Linux 都是支持的。</p>
<p>需要注意的是统计只是在本地的 git 项目仓库目录下执行 <code class="language-plaintext highlighter-rouge">git log</code> 并对结果进行统计分析,也就是说,分析只限于已经 <code class="language-plaintext highlighter-rouge">git pull</code> 代码,如果想要统计最新的提交记录,需要拉取最新项目代码再统计,如果是 Windows ,首先要确保 <code class="language-plaintext highlighter-rouge">git</code> 命令在 <code class="language-plaintext highlighter-rouge">cmd</code> 命令行里面可以执行。</p>
<p>我已经把应用打成 war 包放在 csdn 上,下下来直接部署在 tomcat 服务器就能跑。</p>
<p>下载:<a href="http://download.csdn.net/download/yajunshen/8899423">statgit.war</a></p>
<p>最后说明下,类似的统计只是一个辅助手段,并不能完全确定个人的工作量。同一个功能实现方式不同代码量可能差异比较大,而且 git log 命令本身并不能确定修改的行数以及修改的字节数,只能统计增加和删除行数,也不能简单的相减来算工作量,所以我对结果没有做合并,大家有好的思路可以交流下。E-mail: <a href="mailto:jhkgogpl@163.com">JackyShen</a></p>
Java SE 7 Third Edition 规范翻译之-异常
2015-06-12T00:00:00+00:00
http://www.blogways.net/blog/2015/06/12/jls7-3-11
<h1 id="java-se-7-third-edition-规范翻译之异常">Java SE 7 Third Edition 规范翻译之-异常</h1>
<p>当一个程序违反了Java语言的语义约束,Java虚拟机将抛出异常。</p>
<p>一个典型的违反约束的例子是企图索引一个越界的数组。一些语言通过强制终止程序来处理这种类型的错误;另一些语言允许这种实现,但处理方式是随意的或者不可预知的。不同于这两种实现,Java SE平台的设计目标是:提供可移值性和健壮性。</p>
<p>Java编程语言指出当语义约束遭到违反时将抛出一个异常,同时程序将指出导致异常的代码,并交出控制权。</p>
<p>异常被认为从导致它的地方抛出,并且在这个地方交出控制权。</p>
<p>程序也可以使用throw语句抛出自定义异常。自定义异常通过返回值来替代老式风格的错误处理机制,例如一个整数不希望出现负值时,经验表明调用者经常忽略了这样的验证,导致程序不健壮,产生不希望的结果,或者两者兼有。</p>
<p>每个异常都是Throwable或者它的子类的一个实例(§11.1)。异常对象可以用来承载异常发生时的信息,提供给处理程序,由try语句抛出,catch语句创建处理程序(§14.20)。</p>
<p>在抛出异常的过程中,Java虚拟机意外中止,一个接一个,任何当前线程已经开始的表达式,语句,方法,构造函数,初始化,属性初始化表达式将挂起,直到找到这个异常处理类或者异常父类的处理类(§11.2)。如果没有对应的处理类,那么会当成未被处理的异常处理,因为所做的努力都是避免让异常没有对应的处理方法。</p>
<p>Java SE平台的异常机制集成了其同步模式(§17.1),使监视器异常中止时开启为同步声明(§14.19)以及同步方法调用(§8.4.3.6, §15.12)</p>
<h2 id="111-the-kinds-and-causes-of-exceptions">11.1 The Kinds and Causes of Exceptions</h2>
<h3 id="1111-the-kinds-of-exceptions">11.1.1 The Kinds of Exceptions</h3>
<p>一个异常代表Throwable(Object的直接子类)或者它子类的一个实例</p>
<p>Throwable和它所有的子类统称为异常类。需要注意的是Throwable的子类一定是不能通用的(§8.1.2)。</p>
<p>类Exception和Error都是Throwable的直接子类。</p>
<p>Exception是程序希望可以继续运行时抛出的所有异常的父类。</p>
<p>Error是不希望程序继续运行时抛出的所有异常的父类。</p>
<p>Error和它的子类被统称为错误类。</p>
<p>Error类是Throwable的独立的子类,和Exception是两个不同的分支,允许程序使用语法 “} catch (Exception e) {“ (§11.2.3) 捕获所有的异常从而恢复运行是可行的,想通过捕获错误来恢复运行是不可行的。</p>
<p>RuntimeException类是Exception的一个直接子类。</p>
<p>RuntimeException是所有表达式赋值运行时各种异常的父类,但仍可以继续运行。</p>
<p>RuntimeException和它的所有子类统称为运行时异常类。</p>
<p>未检查异常类包括运行时异常类和错误类。</p>
<p>非检查异常类之外的异常都属于检查异常类。也就是说,检查异常类都继承自Throwable类,而不是RuntimeException和它子类或者Error和它子类。</p>
<p>程序可以在throw语句里使用Java SE平台API定义好的异常类,或者自定义的Throwable或者它子类的子类。利用编译时检查异常处理(§11.2),这个是典型的大部分检查异常类的定义方法,换句话说检查异常类是Exception的子类而不是RuntimeException的子类。</p>
<h3 id="1112-the-causes-of-exceptions">11.1.2 The Causes of Exceptions</h3>
<p>抛出异常的三个原因:</p>
<ul>
<li>throw语句(§14.18)被执行。</li>
<li>由Java虚拟机同步检测的异常执行情况,即:
<ul>
<li>运算表达式违反Java程序主义,例如整数除以0。</li>
<li>程序加载,连接,或者初始化部分导致的错误(§12.2, §12.3, §12.4);在这种情况下抛出一个LinkageError子类的实例。</li>
<li>
<p>内部错误或者资源访问限制阻止Java虚拟机实现Java程序方法;在这种情况下抛出一个VirtualMethodError子类的实例。</p>
<p>这些异常不需要在程序里显式的抛出,而是在运算表达式或者语句执行时可能导致的异常。</p>
</li>
</ul>
</li>
<li>发生异步异常(§11.1.3)</li>
</ul>
<h3 id="1113-asynchronous-exceptions">11.1.3 Asynchronous Exceptions</h3>
<p>大部分异常在线程执行时同步抛出,并且指出导致这个异常可能的地方。相比之下异步异常可能发生在程序执行的任何地方。</p>
<p>异步异常仅发生在:</p>
<ul>
<li>调用线程或者线程组(过期的)stop方法。一个线程调用(过期的)stop方法影响到另外一个线程或者一个特别线程组里面所有的线程。他们是异步的,因为他们可以在其他线程或者线程组里面任何地方执行。</li>
<li>Java虚拟机一个内部错误或者资源访问阻止了Java语义的实现。在这种情况下,抛出VirtualMethodError子类的一个实例。</li>
</ul>
<p>需要注意的是StackOverflowError,VirtualMethodError的一个子类,既可以在方法调用时同步抛出,也可以执行本地方法或者Java虚拟机资源访问限制时异步抛出。同样,OutOfMemoryError,另一个VirtualMethodError的子类,可以在对象创建(§12.5),数组创建(§15.10.1, §10.6),类初始化(§12.4.2),和装箱转换(§5.1.7)期间同步或者异步抛出。</p>
<p>Java SE平台在异步异常抛出前允许一个小的但数量有限的执行。</p>
<p>异步异常是罕见的,但如果要生成高品质的机器代码,正确理解其语义是必要的。</p>
<p>上面提到的延迟允许优化代码,以检测和抛出这些异常点是遵守Java编程语义可行的处理。一个简单的实现可能轮循在每个控制转移指令点的异步异常。因为程序大小有限制,这提供了一个检测异步异常的总延时。因为控制转换不会导致异步异常,代码解释器拥有一定的灵活性,在控制转换时重新排列达到更好的性能。Marc Feeley 在 Proc. 1993 发表的论文 Polling Efficiently on Stock Hardware 讨论了函数式编程和计算机架构,哥本哈根,丹麦,第179-187页,建议详细阅读。</p>
<h2 id="112-compile-time-checking-of-exceptions">11.2 Compile-Time Checking of Exceptions</h2>
<p>Java编程语言要求程序为可能导致异常的执行方法或者构造函数提供检查处理方法,对每个可能的异常检查,方法(§8.4.6)或者构造函数(§8.8.5)中的throws子句必须提到该异常类或者该异常类的一个超类(§11.2.3)。</p>
<p>编译时检查异常处理设计是为了减少不确当处理异常数量。检查在throws子句中指定的异常类(§11.1.1)是方法或者构造函数实现者和使用者之间约定的一部分。重写方法的throws子句可以不用指定此方将导致抛出任何检查异常,不允许在重写方法中通过throws子句抛出异常(§8.4.8.3)。</p>
<p>当涉及到多个接口,多个方法的声明可能被一个方法重写。在这种情况下,重写声明必须有一个throws子句覆盖所有的被重写的声明(§9.4.1)。</p>
<p>非检查异常类(§11.1.1)在编译时不检测。</p>
<p>未检查异常类中的错误类不校验,因为他们可以发生在程序的很多地方,恢复起来很困难或者不可能。声明这种异常将使程序很混乱,毫无意义。复杂的程序可能希望捕获这些异常并企图从其中的一些条件恢复。</p>
<p>未检查异常类中的运行时异常类不校验,因为在Java程序语言的设计者们的判断,申报此类异常不会在建立程序正确性方面有显著的帮助。Java程序的许多操作和构造都能导致运行时异常。Java编译器所拥有的信息,以及分析编译器的水平,通常不足以保证运行时异常不发生,即使对程序员来说是很明显的。需要声明这种异常对程序员来简直是悲剧。</p>
<p>例如,某些代码可能通过构造器执行一个循环的数据结构,不能包含空引用;程序员可以肯定不会发生NullPointerException异常,但对Java编译器来说想要证明它就很困难。想要证明需要建立一个全局的数据结构属性,已经超出了本规范的范围。</p>
<p>我们说如果一个声明或者表达式可能抛出一个检查异常类 E,根据§11.2.1和§11.2.2的规则,声明或者表达式执行可能导致一个异常类 E 被抛出。</p>
<p>我们说一个catch子句可以捕获它对应的异常类。单catch子句需要声明该异常类型参数(§14.20)。</p>
<p>多catch子句用来处理多个异常,每个catch需要指定其对应的异常类型参数(§14.20)。</p>
<h3 id="1121-exception-analysis-of-expressions">11.2.1 Exception Analysis of Expressions</h3>
<p>一个类实例创建表达式(§15.9)可能抛出异常类 E,仅当:</p>
<ul>
<li>表达式是一个正确的类实例创建表达式,正确的表达式可能抛出异常 E;或</li>
<li>参数列表的一些表达式可能抛出 E;或者</li>
<li>E 在被调用的构造器最后作为异常类通过throws子句抛出(§15.12.2.6);或</li>
<li>类实例创建表达式包含一个类体,类体的一些实例初始化块或者实例变量初始化表达式可能抛出 E。</li>
</ul>
<p>一个方法调用表达式(§15.12)可能抛出异常类 E,仅当:</p>
<ul>
<li>方法调用是形式,标签符和主要的表达式可能抛出 E;或</li>
<li>参数列表的一些表达式可能抛出 E;或</li>
<li>E 在被调用的构造器最后作为异常类通过throws子句抛出(§15.12.2.6);</li>
</ul>
<p>对于每一种表达式,仅当其中一个表达式可能抛出异常类 E,表达式才可能抛出 E。</p>
<h3 id="1122-exception-analysis-of-statements">11.2.2 Exception Analysis of Statements</h3>
<p>throw语句(§14.18),其thrown表达式具有静态类型 E,并且是非final或者effectively final表达式参数,可以抛出 E 或者任意可以抛出的异常类。</p>
<p>例如,声明抛出new java.io.FileNotFoundException(),只能抛出java.io.FileNotFoundException。形式上来说它不是这样的,它能够抛出java.io.FileNotFoundException的子类或者超类。</p>
<p>throw语句抛出的异常作为catch语句C的一个final和effectively final异常参数,可以抛出异常类 E 仅当:</p>
<ul>
<li>E 是一个异常类,C 声明的try语句的try块中可以抛出;和</li>
<li>E 与 C 的任何可捕获的异常类兼容;和</li>
<li>E 不能与 C 里面同一个 try 声明对应的 catch 语句任何可捕获的异常类冲突。</li>
</ul>
<p>一个 try 语句可以抛出异常类 E,仅当:</p>
<ul>
<li>该 try 块可以抛出 E,或者一个资源初始化表达式(在try-with-resources语句)可以抛出E,或者自动调用资源的close()方法(在try-with-resources语句)可以抛出E,同时 E 与任何该 try 语句对应的的 catch 子句中可捕获的异常类不能冲突,并且没有finally块存在,或者finally块可以正常完成;或</li>
<li>try语句的一些catch块可以抛出 E,并且没有finally块存在或者finally块可以正常完成;或</li>
<li>finally块存在并且可以抛出E。</li>
</ul>
<p>一个显式的构造函数调用语句(§8.8.7.1)可以抛出异常类 E,仅当:</p>
<ul>
<li>构造函数的参数列表的一些表达式可以抛出 E;或</li>
<li>被调用的构造函数内部使用throws子句抛出异常类 E。</li>
</ul>
<p>任何其他的声明 S 可以抛出异常类 E,仅当 S 直接包含的表达式或者声明可以抛出 E。</p>
<h3 id="1123-exception-checking">11.2.3 Exception Checking</h3>
<p>这是一个编译时错误,如果一个方法或者构造体可以抛出一些异常类 E,当 E 是一个检查异常类,并且 E 不是方法或者构造体throws子句声明的一些类的子类。</p>
<p>这是一个编译时错误,如果一个类变量初始化(§8.3.2),或指定类或接口的静态初始化(§8.7)可以抛出一个检查异常类。</p>
<p>这是一个编译时错误,如果一个实例变量初始化或者指定类的实例变量初始化可以抛出一个检查异常类,除非异常类或者它的超类在其对应类的每个构造函数中throws子句里显式声明,并且该类至少拥有一个显示声明的构造函数。</p>
<p>注意,没有编译时抛出异常类是由于匿名类的变量初始化或者实例初始化引起的。在非匿名类中,程序有义务通过在任何显式的构造函数声明中定义一个合适的throws子句去传播初始化抛出的异常类相关的信息。类初始化抛出的检查异常类和匿名类构造函数隐式初始化声明的检查异常类之间存在一定的联系,因为不可能有明确的显式的构造函数声明,Java编译器总是基于初始化可能抛出的检查异常类,为匿名类声明生成一个拥有合适的throws子句构造函数。</p>
<p>这是一个编译时错误,如果catch子句可以捕获了一个检查异常类 E1,并且try对应的catch子句不可以抛出一个 E1 检查异常类的子类或者父类,除非 E1 是Exception或者Exception的超类。</p>
<p>这是一个编译时错误,如果一个catch子句可以捕获(§11.2)检查异常类 E1,上述的catch子句可以立即捕获try语句中的 E1 或者 E1 的超类。</p>
<p>Java编译器鼓励发出警告,当catch子句可以捕获(§11.2)检查异常类 E1,并且try块对应的catch子句可能抛出检查异常类 E1 的子类 E2,上述的catch子句可以捕获try语句中的检查异常类 E3,其中 E2 <:E3 <: E1。</p>
<p>Example 11.2.3-1. Catching Checked Exceptions</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.io.*;
class StaticallyThrownExceptionsIncludeSubtypes {
public static void main(String[] args) {
try {
throw new FileNotFoundException();
} catch (IOException ioe) {
// Legal in Java SE 6 and 7. "catch IOException"
// catches IOException and any subtype.
}
try {
throw new FileNotFoundException();
// Statement "can throw" FileNotFoundException.
// It is not the case that statement "can throw"
// a subtype or supertype of FileNotFoundException.
} catch (FileNotFoundException fnfe) {
// Legal in Java SE 6 and 7.
} catch (IOException ioe) {
// Legal in Java SE 6 and 7, but compilers are
// encouraged to throw warnings as of Java SE 7.
// All subtypes of IOException that the try block
// can throw have already been caught.
}
try {
m();
// Method m's declaration says "throws IOException".
// m "can throw" IOException. It is not the case
// that m "can throw" a subtype or supertype of
// IOException, e.g. Exception, though Exception or
// a supertype of Exception can always be caught.
} catch (FileNotFoundException fnfe) {
// Legal in Java SE 6 and 7, because the dynamic type
// of the IOException might be FileNotFoundException.
} catch (IOException ioe) {
// Legal in Java SE 6 and 7.
} catch (Throwable t) {
// Legal in Java SE 6 and 7.
}
}
static void m() throws IOException {
throw new FileNotFoundException();
}
}
</code></pre></div></div>
<p>根据上述原则,在多个catch子句(§14.20)中的每一个catch子句必须捕获try块中抛出并且在前面的catch没有被捕获的一些异常。例如,下面的第二个catch子句可能导致一个编译时错误,因为异常分析确定SubclassOfFoo已经被第一个catch子句捕获过了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>try { ... }
catch (Foo f) { ... }
catch (Bar | SubclassOfFoo e) { ... }
</code></pre></div></div>
<h2 id="113-run-time-handling-of-an-exception">11.3 Run-Time Handling of an Exception</h2>
<p>当一个异常被抛出(§14.18),控制权从导致异常的代码转移到最近的动态catch子句块,如果有的话,try语句就可以处理该异常。</p>
<p>一个声明或者表达式被一个catch子句动态隔离,当它出现在try语句的try块中的话catch子句就是其中的一部分,或者当声明或者表达式的调用者被catch子句动态隔离。</p>
<p>声明或者表达式的调用者取决于它发生在哪里:</p>
<ul>
<li>如果在方法中,那么调用者是一个方法调用表达式(§15.12),执行方法调用。</li>
<li>如果在构造函数或者实例初始化或者实例变量初始化时,那么调用者就是类实例创建表达式(§15.9)或者一个新实例的方法调用,执行创建一个新对象。</li>
<li>如果在一个静态的初始化或者一个静态变量初始化时,那么调用者就是使用的类或者接口,以便其初始化(§12.4)。</li>
</ul>
<p>一个特定的catch子句能否处理一个异常由抛出的对象跟catch子句能捕获的异常类比较决定。catch子句可以处理如果它可捕获的异常类是抛出的异常类或者抛出异常类的父类。</p>
<p>同样的,一个catch子句将会捕获任何其可捕获的异常类的实例(§15.20.2)。</p>
<p>当有异常被抛出时,表达式(§15.6)和声明(§14.1)意外中止,控制权转移,直到遇到一个能处理这个异常的catch子句;继续执行catch块。导致异常的代码将不会恢复执行。</p>
<p>所有的异常(同步或者异步)是精确的:当转换控制发生,在抛出异常之前的所有语句执行和表达式运算都会受到影响。表达式,语句,或其他部分在抛出异常后都不会被执行。</p>
<p>如果优化代码期望在异常发生点之后执行一些表达式或者语句,这样的代码必须准备从程序的用户可见状态隐藏推测执行。</p>
<p>如果没有可以处理这个异常的catch子句,那么当前线程(遇到异常的线程)终止。终止前,所有的finally语句将被执行,并且未被捕获的异常根据以下规则处理:</p>
<ul>
<li>如果当前线程有一个未被捕获的异常处理集,那么该处理程序被执行。</li>
<li>否则,当前线程的父线程ThreadGroup将调用方法uncaughtException。如果ThreadGroup和它的父类ThreadGroups没有重写uncaughtException方法,那么默认的处理程序uncaughtException方法将被调用。</li>
</ul>
<p>在某此情况下可能需要确保一个代码块在另一个代码块之后始终被执行,即使另一个代码块意外中止,可以使用try语句的finally子句(§14.20.2)实现。</p>
<p>如果try-finally或者try-catch-finally语句的一个try块或catch块意外中断,那么finally子句在异常传播的过程中执行,即使最终没有发现匹配的catch子句。</p>
<p>如果一个finally子句由于try块的意外中断而执行,并且finally块自身执行时意外中断,那么try块意外中断的异常被丢弃,一个新的意外中断的异常从这里开始传播。</p>
<p>意外中断和异常捕获的确切规则指定§14中每个语句详细描述以及§15(特别是§15.6)中的异常。</p>
<p>Example 11.3-1. Throwing and Catching Exceptions</p>
<p>下面的程序声明了一个异常类TestException。Test类的main方法调用了thrower四次,导致异常三次被抛出。方法中的try语句捕获了thrower抛出的每一个异常。无论thrower调用正常结束或者异常中断,都会打出一条消息,描述做了什么。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class TestException extends Exception {
TestException() { super(); }
TestException(String s) { super(s); }
}
class Test {
public static void main(String[] args) {
for (String arg : args) {
try {
thrower(arg);
System.out.println("Test \"" + arg +
"\" didn't throw an exception");
} catch (Exception e) {
System.out.println("Test \"" + arg +
"\" threw a " + e.getClass() +
"\n with message: " +
e.getMessage());
}
}
}
static int thrower(String s) throws TestException {
try {
if (s.equals("divide")) {
int i = 0;
return i/i;
}
if (s.equals("null")) {
s = null;
return s.length();
}
if (s.equals("test")) {
throw new TestException("Test message");
}
return 0;
} finally {
System.out.println("[thrower(\"" + s + "\") done]");
}
}
}
</code></pre></div></div>
<p>输入4个参数执行程序:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>divide null not test
</code></pre></div></div>
<p>执行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[thrower("divide") done]
Test "divide" threw a class java.lang.ArithmeticException
with message: / by zero
[thrower("null") done]
Test "null" threw a class java.lang.NullPointerException
with message: null
[thrower("not") done]
Test "not" didn't throw an exception
[thrower("test") done]
Test "test" threw a class TestException
with message: Test message
</code></pre></div></div>
<p>thrower方法的声明必须有一个throws子句,因为它可以抛出TestException检查异常类(§11.1.1)的实例。如果没有throws子句将会导致一个编译时错误。</p>
<p>需要注意的是finally在每次thrower调用后都会执行,无论有没有异常抛出,就像每次调用后”[thrower(…) done]”输出。</p>
git权限管理工具gitolite使用教程
2015-06-09T00:00:00+00:00
http://www.blogways.net/blog/2015/06/09/git-gitolite
<h3 id="一gitolite实现功能">一、gitolite实现功能</h3>
<p>1.通过远程连接能够对服务器上仓库进行读写操作;</p>
<p>2.安全的权限管理,控制特定用户只能访问仓库,并能限制读写权限;</p>
<p>3.权限控制不只作用于仓库,而同样于仓库中的每个branch和tag name。</p>
<h3 id="二gitolite安装">二、gitolite安装</h3>
<p>Gitolite 是一款 Perl 语言开发的 Git 服务管理工具,通过公钥对用户进行认证,并能够通过配置文件对写操作进行基于分支和路径的的精细授权。Gitolite 采用的是 SSH 协议并且使用 SSH 公钥认证,以下为安装前提条件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> UNIX/LINUX操作系统
shell环境
git 1.6.6+
perl 5.8.8+
openssh 5.0+(好像低于这个版本也行)
gitolite使用的账号(本文用的git)
</code></pre></div></div>
<ol>
<li>以git账号登入主机;</li>
<li>确保~/.ssh/authorized_keys为空或不存在;</li>
<li>主机上执行git clone https://github.com/sitaramc/gitolite.git(不能上网可上传实现);</li>
<li>mkdir -p /home/git/bin</li>
<li>./gitolite/install -to /home/git/bin/</li>
<li>选择一台机器作为client机器(我这里就选择本机),并将这台客户端机器的公钥上传并保存在主机的/home/git/YourName.pub(我这里用的我本机的,起名tangsz.pub)位置</li>
<li>
<p>~/bin/gitolite setup -pk ~/YourName.pub
8.测试安装是否成功</p>
<p>本机(windows要到git Bash下执行)执行ssh git@10.20.16.78显示如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> hello git, this is git@slave2 running gitolite3 v3.6.3-10-g4be7ac5-dt on git 1.8.1.6
R W gitolite-admin
R W testing
</code></pre></div> </div>
</li>
</ol>
<p>注:安装过程中可能出现的错误:</p>
<ol>
<li>
<p>/gitolite/install: /usr/bin/perl^M: bad interpreter: No such file or directory</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 解决:install 文件转unix格式
</code></pre></div> </div>
</li>
<li>
<p>git describe failed; cannot deduce version number</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 解决:由于主机不能上网,我是把文件copy到主机,然后建立仓库的,这样不行,可以把本地下载的带.git目录的文件直接上传主机。
</code></pre></div> </div>
</li>
<li>
<p>WARNING: Can’t exec “/data/git/bin/triggers/post-compile/ssh-authkeys”: No such file or directory at /data/git/bin/lib/Gitolite/Common.pm line 146, <DATA> line 1.</DATA></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 解决:dos2unix /data/git/bin/triggers/post-compile/*
</code></pre></div> </div>
</li>
<li>
<p>ssh git@10.20.16.78 报错:WARNING: Can’t exec “/data/git/sbin/commands/info”: No such file or directory</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 解决:dos2unix /data/git/sbin/commands/info
</code></pre></div> </div>
</li>
</ol>
<h3 id="三gitolite添加用户和仓库">三、gitolite添加用户和仓库</h3>
<ol>
<li>
<p>clone 管理仓库gitolite-admin(我本机操作):</p>
<ul>
<li>
<p>git clone git@server-host:gitolite-admin (如:git clone git@10.20.16.78:gitolite-admin.git)</p>
</li>
<li>
<p>gitolite-admin有conf和keydir两个子文件夹,keydir文件夹就是管理用户公钥的地方,如果有一位新用户(spdoop)希望申请账户并申请一个新的代码仓库(代码仓库叫做test),那么让他提供他的账号、他用的电脑的公钥给git管理员(可以通过email/qq等方式),然后由git管理员在keydir下创建spdoop.pub文件并将公钥内容复制其中。</p>
</li>
<li>
<p>conf/gitolite.conf设置权限文件:</p>
</li>
</ul>
repo test
RW+ = spdoop
<ul>
<li>
<p>添加完配置后将本地仓库修改push到服务器仓库,服务会自动创建仓库test(当然这里也可以自己先到主机上建仓库,gitolite管理的仓库都在用户目录repositories下,也可修改配置),spdoop具有读写权限。</p>
</li>
<li>
<p>如果想添加spdoop为管理员,需管理员tangsz修改gitolite.conf</p>
</li>
</ul>
repo gitolite-admin
RW+ = git tangsz
</li>
</ol>
<p>注:push过程中可能出现的错误:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. error: cannot run hooks/update: No such file or directory 解决方法: 此问题我是在windows机器传gitolite源码到linux机器上安装出现的,linux、mac不知道会不会出现,找了好久才发现要将update、post-update等文件dos2unix。具体命令,参考如下:
cd ~/.gitolite/hooks/common
dos2unix *
cd ~/.gitolite/hooks/gitolite-admin
dos2unix *
</code></pre></div></div>
<h3 id="四gitolite配置实例">四、gitolite配置实例</h3>
<p>这里对gitolite详细的权限配置就不做说明,通过一个简单的实例介绍下gitolite的使用:</p>
<ol>
<li>
<p>添加新的仓库pangu-la-web,管理员tangsz具有读写、强制push操作权限:</p>
<ul>
<li>
<p>修改gitolite.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> repo pangu-la-web
RW+ = tangsz
</code></pre></div> </div>
</li>
<li>
<p>提交本地仓库并push到服务器</p>
</li>
<li>
<p>查看主仓库已经建立空仓库pangu-la-web</p>
</li>
<li>
<p>本机clone下pangu-la-web仓库(git clone git@10.20.16.78:pangu-la-web)</p>
</li>
<li>
<p>添加文件test,push到服务器仓库master分支</p>
</li>
</ul>
</li>
<li>
<p>用户spdoop添加pangu-la-web中dev开头的分支有读/写/强制更新的权限,test分支(严格匹配)具有读/写权限</p>
<ul>
<li>
<p>修改gitolite.conf</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> repo pangu-la-web
RW+ = tangsz
RW+ dev = spdoop
RW test$ = spdoop
</code></pre></div> </div>
</li>
<li>
<p>提交并push到服务器仓库</p>
</li>
<li>
<p>spdoop clone pangu-la-web仓库到本地,修改test,执行git push origin master后错误如下(没有权限提交master):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> remote: FATAL: W refs/heads/master pangu-la-web spdoop DENIED by fallthru
</code></pre></div> </div>
</li>
<li>
<p>spdoop新建分支dev1(git branch dev1) checkout到dev1分支(git checkout dev1),执行git push origin dev1成功</p>
</li>
<li>
<p>spdoop新建分支test(git branch test) checkout到test分支(git checkout test),执行git push origin test成功</p>
</li>
</ul>
</li>
<li>
<p>这里用户spdev试图clone仓库pangu-la-web(git clone git@10.20.16.78:pangu-la-web),出现错误如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> fatal: 'pangu-la-web' does not appear to be a git repository
fatal: Could not read from remote repository.
</code></pre></div> </div>
</li>
</ol>
<h3 id="五外部人员参与现有项目开发条件">五、外部人员参与现有项目开发条件</h3>
<ol>
<li>
<p>保证本机电脑安装了git,windows下安装的是msysgit(windows安装后会生成git Bash命令窗口),其中git安装可以参阅本博客git篇;</p>
</li>
<li>
<p>命令窗口下生成用户公钥(windows下安装msysgit后在git Bash下生成),命令为:ssh-keygen,生成后在用户目录.ssh 下,文件名为id_rsa.pub;</p>
</li>
<li>
<p>向项目仓库管理员申请操作某项目权限,将id_rsa.pub文件发给管理员,如开发员tom需要开发项目pangu-la-web,则需要邮件或qq等方式将id_rsa.pub发给管理员tangsz,并申明需要有pangu-la-web项目开发权限;</p>
</li>
<li>
<p>管理员会根据tom的权限为其赋予pangu-la-web项目特定权限(如只能新建dev开头的分支进行开发,不允许tom操作master分支),然后通过tom可以进行clone代码开发了;</p>
</li>
<li>
<p>tom接到通知后clone下pangu-la-web代码及可以进行开发了,clone命令为:git clone git@10.20.16.78:pangu-la-web (windows下在git Bash 窗口执行);</p>
</li>
<li>
<p>tom clone完代码后可以自己新建个分支如dev-tom(git branch dev-tom)进行开发,然后git checkout dev-tom,待功能开发完后push到dev-tom分支(git push origin dev-tom)</p>
</li>
<li>
<p>tom 在分支dev-tom上开发完后就可以通知管理员tom的功能点已经开发完了,申请合并到master分支;</p>
</li>
<li>
<p>管理员接到tom通知后做合并处理。</p>
</li>
</ol>
Java SE 7 Third Edition 规范翻译之-数组
2015-06-01T00:00:00+00:00
http://www.blogways.net/blog/2015/06/01/jls7-3-10
<h1 id="java-se-7-third-edition-规范翻译之数组">Java SE 7 Third Edition 规范翻译之-数组</h1>
<p>在 Java 编程语言中,数组是动态创建的对象(§4.3.1),并且可以分配给 Object 类型的变量。类对象的所有方法可以基于数组调用。</p>
<p>一个数组对象包含特定数值个变量。这个数值可以是0,但如果这也就意味着数组为空。数组中的变量没有变量名;相反他们通过使用非负整数索引值来访问数组引用的表达式。假设一个数组拥有n个元素,那么我们说这个数组的长度就是n;数组的这些元素使用整数下标引用从0到n-1。</p>
<p>数组内所有的元素拥有统一的类型,称为数组的元素类型。假设数组的元素类型是T,那么这个类型的数组定义成T[]。</p>
<p>对于浮点数类型的数组元素始终是浮点数类型值集(§4.2.3);同样的,对于双精度类型的数组元素始终是双精度类型值集。不允许浮点数类型的数组的元素不是浮点数类型的值集,同样不允许双精度类型的数组的元素不是双精度类型的值集。</p>
<p>一个数组元素类型的本身可以是数组类型。这种数组元素可以包含子数组的引用。假设开始于任何数组类型,考虑到其元素类型,然后子数组的元素类型,以至类推,最终必须有子数组的元素类型不是数组类型;这个就是原始数组的元素类型,并且这一层的数组结构的元素被称为原始数组。</p>
<p>在某些情况下,一个数组的元素可以是一个数组:当这个元素的类型是对象或者Cloneable或者java.io.Serializable,那么部分或者所有的元素均可以是数组,因为任何数组对象都可以指定这些类型的变量。</p>
<h2 id="101-array-types">10.1 Array Types</h2>
<p>数组类型用于声明和映射表达式中(§15.16)。</p>
<p>数组类型写法遵循一个元素类型后面跟一些空的方括号[]。方括号的数量表示数组的维度。</p>
<p>数组的长度不是类型的一部分。</p>
<p>数组元素的类型可以是任意类型,是原始数据类型或者引用类型。特别是:</p>
<ul>
<li>数组拥有一个接口类型作为允许的元素类型。该数组元素可以是它的值的空引用或者任意实现该接口的类型实例。</li>
<li>数组允许一个抽象类做为其元素类型。该数组的元素可以是它的值的空引用或者是抽象类实现子类的实例。</li>
</ul>
<p>数组类型的父类型在§4.10.3规定。
数组类型的父类是Object。
每一个数组都实现了Cloneable和java.io.Serializable接口。</p>
<h2 id="102-array-variables">10.2 Array Variables</h2>
<p>数组类型的变量是一个对象的引用。声明数组类型的变量但不创建数组对象或者说不给数组元素分配任何内存空间,那么它只创建变量本身,变量包含一个数组引用。然而声明初始化部分(§8.3, §9.3, §14.4.1)可以创建一个数组,数组的引用将成为该变量的初始值。</p>
<p>Example 10.2-1. Declarations of Array Variables</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int[] ai; // array of int
short[][] as; // array of array of short
short s, // scalar short
aas[][]; // array of array of short
Object[] ao, // array of Object
otherAo; // array of Object
Collection<?>[] ca; // array of Collection of unknown type
</code></pre></div></div>
<p>上面的定义没有创建数组对象。下面举例说明几个创建数组对象的数组变量声明方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Exception ae[] = new Exception[3];
Object aao[][] = new Exception[2][3];
int[] factorial = { 1, 1, 2, 6, 24, 120, 720, 5040 };
char ac[] = { 'n', 'o', 't', ' ', 'a', ' ',
'S', 't', 'r', 'i', 'n', 'g' };
String[] aas = { "array", "of", "String", };
</code></pre></div></div>
<p>[]可以出现在声明类型开始部分,或者也可以出现在声明的变量部分,例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>byte[] rowvector, colvector, matrix[];
</code></pre></div></div>
<p>等价于:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>byte rowvector[], colvector[], matrix[][];
</code></pre></div></div>
<p>在变量声明(§8.3, §8.4.1, §9.3, §14.14, §14.20)时除了一个可变的参数,在声明开头就要指定变量的数组类型,后面跟着变量标识符以及中括号。</p>
<p>例如,局部变量声明:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int a, b[], c[][];
</code></pre></div></div>
<p>等价于一系列的声明:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>int a;
int[] b;
int[][] c;
</code></pre></div></div>
<p>声明时使用中括号也是C和C++的传统,一般为变量声明的规则。但是Java允许中括号出现在类型或者声明部分,所以局部变量可以这样定义:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>float[][] f[][], g[][][], h[]; // Yechh!
is equivalent to the series of declarations:
float[][][][] f;
float[][][][][] g;
float[][][] h;
</code></pre></div></div>
<p>我们不推荐在数组变量定义部分使用“混合标记”,即中括号既出现在类型部分又出现在声明部分。</p>
<p>一旦数组对象被创建,它的长度不会发生变化。为了让数组变量指向一个不同长度的数组,必须要将这个不同长度数组的引用赋给这个变量。</p>
<p>数组类型的单个变量包含对不同长度数组的引用,因为这个数组的长度不属于其类型的一部分。</p>
<p>假设一个数组变量v拥有A[]类型,其中A是引用类型,然后v可以持有任意数组类型B[]的一个实例的引用,假设B可以被分配给A(§5.2)。这可能会导致以后的任务运行时异常;详见§10.5。</p>
<h2 id="103-array-creation">10.3 Array Creation</h2>
<p>创建一个数组通过数组创建表达式(§15.10)或者数组初始化。</p>
<p>数组创建表达式中指定元素类型,嵌套数组的维度,至少一个嵌套数组的长度。数组的长度可作为最终实例变量的长度。</p>
<p>数组初始化创建一个数组,并提供所有元素的初始值。</p>
<h2 id="104-array-access">10.4 Array Access</h2>
<p>数组元素的访问通过数组访问表达式(§15.13),表达式由数组的引用后跟[]括起来的索引值,如A[i]。</p>
<p>所有的数组都是从0开始,长度为n的数组可以通过整数0至n-1来索引。</p>
<p>Example 10.4-1. Array Access</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Gauss {
public static void main(String[] args) {
int[] ia = new int[101];
for (int i = 0; i < ia.length; i++) ia[i] = i;
int sum = 0;
for (int e : ia) sum += e;
System.out.println(sum);
}
}
</code></pre></div></div>
<p>这个程序的输出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>5050
</code></pre></div></div>
<p>这个程序定义了一个int数组类型的变量ia,即int[]。变量ia被初始化为一个新创建数组对象的引用,由数组创建表达式创建(§15.10)。数组创建表达式指定数组应该有101个元素。数组的长度是可以使用的字段长度,如上所示。程序使用0-100填充数组,最终将这些整数求和,并且打印出结果。</p>
<p>数组必须被int类型值索引;short,byte,或者char值也可以用作索引值,因为他们可以转换成int类型值。</p>
<p>企图通过long值索引访问数组将导致编译时错误。</p>
<p>所有数组访问在运行时检查;企图使用小于0或者大于等于实际数组长度的索引将会抛出ArrayIndexOutOfBoundsException异常。</p>
<h2 id="105-array-store-exception">10.5 Array Store Exception</h2>
<p>对于一个类型为A[]的数组,其中A即为引用类型,给数组元素赋值将在运行时检查以确保分配给元素的值符合数组元素类型。</p>
<p>如果分配的值与数组元素的类型不兼容(§5.2)将抛出ArrayStoreException异常。</p>
<p>如果数组的元素类型不是具体化类型,Java虚拟机将不能执行前面段落描述的存储检查。这就是为什么数组创建表达式禁止使用非具体化元素类型(§15.10)。可以声明一个数组元素类型为非具体化类型的变量,但数组变量创建表达式的结果必将导致一个非检查告警(§5.1.9)。</p>
<p>Example 10.5-1. ArrayStoreException</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Point { int x, y; }
class ColoredPoint extends Point { int color; }
class Test {
public static void main(String[] args) {
ColoredPoint[] cpa = new ColoredPoint[10];
Point[] pa = cpa;
System.out.println(pa[1] == null);
try {
pa[0] = new Point();
} catch (ArrayStoreException e) {
System.out.println(e);
}
}
}
</code></pre></div></div>
<p>这个程序的输出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>true
java.lang.ArrayStoreException: Point
</code></pre></div></div>
<p>变量pa为Point[]类型,变量cpa的值是ColoredPoint[]类型的一个引用。一个ColoredPoint对象能转成为一个Point对象;因此cpa的值可以赋给pa。</p>
<p>参考这个数组pa,例如,测试pa[1]是否为空,不会导致一个运行时类型错误。这是因为数组元素的类型ColoredPoint[]是ColoredPoint,每个ColoredPoint都能代替Point,因为Point是ColoredPoint的父类。</p>
<p>另一方面,分配数组pa可能导致运行时错误。在编译时,分配到pa的元素被检查以确保分配的值是一个Point。但由于pa指向一个ColoredPoint数组的引用,只有运行时分配该类型的值时该分配才是有效的,更具体的说,即ColoredPoint。Java虚拟机在运行时会检查这样的情况确保分配是有效的;如果不是将抛出一个ArrayStoreException异常。</p>
<h2 id="106-array-initializers">10.6 Array Initializers</h2>
<p>数组初始化可以在声明时指定(§8.3, §9.3, §14.4),或者作为一个数组创建表达式的一部分(§15.10),创建一个数组并提供一些初始值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ArrayInitializer:
{ VariableInitializersopt ,opt }
VariableInitializers:
VariableInitializer
VariableInitializers , VariableInitializer
</code></pre></div></div>
<p>以下重复§8.3,介绍更详细:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>VariableInitializer:
Expression
ArrayInitializer
</code></pre></div></div>
<p>数组初始化写成一个逗号分隔的表达式列表,使用大括号封装。</p>
<p>数组初始化时最后一个表达式后面的逗号将被忽略。</p>
<p>每个变量初始化赋值必须兼容数组的元素类型,否则将会出现一个编译时错误。</p>
<p>如果数组元素类型初始化时没有指定具体的类型将会出现编译时错误(§4.7)。</p>
<p>数组的长度等于数组初始化时大括号里面变量的个数。虚拟机将为这个新数组分配这个长度的空间。如果没有足够的空间分配给这个数组,数组初始化意外终止并抛出OutOfMemoryError错误。否则一个一维特定长度的数组将被创建,并且每个数组的元素都被初始化为其默认值(§4.12.5)。</p>
<p>变量的初始化立即由数组初始化设定的括号按源码从左到右执行。第n个变量初始化指定数组第n-1个元素的值。如果执行变量初始化意外中止,整个数组的初始化也将以同样的原因意外中止。如果所有的变量初始化表达式正常完成,那么数组初始化正常完成,一个有值的新初始化的数组诞生了。</p>
<p>如果一个数组元素类型为数组类型,则变量初始化时指定元素本身就是一个数组初始化的过程;也就是说,数组初始化是可以嵌套的。在这种情况下,执行嵌套数组初始化构造和通过递归算法初始化一个数组对象,并将其分配给元素。</p>
<p>Example 10.6-1. Array Initializers</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test {
public static void main(String[] args) {
int ia[][] = { {1, 2}, null };
for (int[] ea : ia) {
for (int e: ea) {
System.out.println(e);
}
}
}
}
</code></pre></div></div>
<p>程序输出:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1
2
</code></pre></div></div>
<p>试图索引到数组ia的第二个空引用元素导致NullPointerException异常。</p>
<h2 id="107-array-members">10.7 Array Members</h2>
<p>数组类型的成员有如下几种情况:</p>
<ul>
<li>公共的final字段length,表明数组元素的数量,length 是0或者正整数。</li>
<li>公共的方法clone,重写了Object类的clone方法,并且抛出没有检查异常。数组T[] clone方法的返回类型还是T[]。一个多维数组的克隆是浅拷贝,那就是说它只创建了一个新数组,子数组共享。</li>
<li>所有的方法继承自Object类;唯一没有继承Object类的方法就是它的clone方法。</li>
</ul>
<p>在下面的类中数组因此拥有同样的公共字段和方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class A<T> implements Cloneable, java.io.Serializable {
public final int length = X ;
public T[] clone() {
try {
return (T[])super.clone(); // unchecked warning
} catch (CloneNotSupportedException e) {
throw new InternalError(e.getMessage());
}
}
}
</code></pre></div></div>
<p>需要注意的是在上面的示例中,如果数组真是这样实现的话将会产生一个非检查警告(§5.1.9)</p>
<p>另一种情况见§9.6.3.4,对象的公共方法和非公共方法的区别,需要特别小心。</p>
<p>Example 10.7-1. Arrays Are Cloneable</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test1 {
public static void main(String[] args) {
int ia1[] = { 1, 2 };
int ia2[] = ia1.clone();
System.out.print((ia1 == ia2) + " ");
ia1[1]++;
System.out.println(ia2[1]);
}
}
</code></pre></div></div>
<p>程序执行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>false 2
</code></pre></div></div>
<p>表明ia1和ia2变量分别引用了两个不同的数组。</p>
<p>实际上当一个多维数组被克隆时子数组是共享的,看这个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test2 {
public static void main(String[] args) throws Throwable {
int ia[][] = { {1,2}, null };
int ja[][] = ia.clone();
System.out.print((ia == ja) + " ");
System.out.println(ia[0] == ja[0] && ia[1] == ja[1]);
}
}
</code></pre></div></div>
<p>程序执行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>false true
</code></pre></div></div>
<p>结果显示ia[0]数组跟ja[0]是同一个数组。</p>
<p>##10.8 Class Objects for Arrays
每个数组都有一个关联的类对象,跟所有其他的数组共享同样的元素类型。</p>
<p>Example 10.8-1. Class Object Of Array</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test {
public static void main(String[] args) {
int[] ia = new int[3];
System.out.println(ia.getClass());
System.out.println(ia.getClass().getSuperclass());
}
}
</code></pre></div></div>
<p>程序输出结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class [I
class java.lang.Object
</code></pre></div></div>
<p>这里面的字符串”[I”是int类型数组类对象在运行时的类型签名。</p>
<p>Example 10.8-2. Array Class Objects Are Shared</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>class Test {
public static void main(String[] args) {
int[] ia = new int[3];
int[] ib = new int[6];
System.out.println(ia.getClass() == ib.getClass());
System.out.println("ia has length=" + ia.length);
}
}
</code></pre></div></div>
<p>这个程序输出结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>true
ia has length=3
</code></pre></div></div>
<p>该程序使用继承自Object的getClass方法,和length属性。第一个println打印两个数组类对象比较结果,结果说明所有int类型的数组都是int[]的实例。</p>
<h2 id="109-an-array-of-characters-is-not-a-string">10.9 An Array of Characters is Not a String</h2>
<p>与C语言不同,在Java编程语言中,char数组即不是字符串也不是一个以’\u0000’ (the NUL character)结尾的字符数组。</p>
<p>一个字符串对象是不可变的,也就是说它的内容不会改变,而char数组元素是可变的。</p>
<p>在String类中toCharArray方法返回一个包含字符串相同字符序列的字符数组。StringBuffer类实现了可变字符数组的有效方法。</p>
Solr 应用(六)-API 操作
2015-05-07T00:00:00+00:00
http://www.blogways.net/blog/2015/05/07/solr-usage-6
<p>前面几节我们对 Solr 的运行有了个大概的了解,本文将介绍如何通过操作 API 来实现前面 web 控制台演示的那些功能的。</p>
<h3 id="创建-java-项目">创建 Java 项目</h3>
<p>使用 eclipse 新建一个 Java 项目,导入 apache-tomcat-7.0.42\webapps\solr\WEB-INF\lib 下面所有的 jar 包,再添加一个 junit4.8.1 的 jar 包</p>
<p><img src="/images/solr-6-1.png" alt="Solr 工程导入的 jar 包" /></p>
<h3 id="新建一个测试类">新建一个测试类</h3>
<p>废话不多说,直接上代码,API 操作还是比较简单的,就不分开说了。</p>
<p><code class="language-plaintext highlighter-rouge">SolrTest.java</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrQuery.ORDER;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.response.FacetField;
import org.apache.solr.client.solrj.response.FacetField.Count;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrDocument;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.SolrInputDocument;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class SolrTest {
private static String url = "http://localhost:8080/solr/test";
private SolrClient client;
@Before
public void init() {
client = new HttpSolrClient(url);
}
@After
public void destroy() {
client = null;
}
// 单文档插入
public void addDoc() throws Exception {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", 10000001);
doc.addField("name", "Solr Input Document");
doc.addField("manu", "this is SolrInputDocument content");
client.add(doc);
client.commit();
}
// 批量文档插入
public void addDocList() throws Exception {
Collection<SolrInputDocument> docs = new ArrayList<SolrInputDocument>();
for (int i = 1; i < 11; i++) {
SolrInputDocument doc = new SolrInputDocument();
doc.addField("id", 1000000 + i);
doc.addField("name", "batch solr document insert " + i);
doc.addField("manu", "batch solr document content insert " + i);
docs.add(doc);
}
client.add(docs);
client.commit();
}
@Test
// 单个文档对应的 Java Bean 插入
public void addDocBean() throws Exception {
Index doc = new Index();
doc.setId("100000000");
doc.setName("add bean document");
doc.setManu("index bean manu 中文测试");
doc.setCat(new String[] { "a1", "b2" });
client.addBean(doc);
client.commit();
}
// 批量文档 Bean 插入
public void addDocBeanList() throws Exception {
Collection<Index> docs = new ArrayList<Index>();
for (int i = 1; i < 11; i++) {
Index doc = new Index();
doc.setId("10000000" + i);
doc.setName("add bean document " + i);
doc.setManu("bean document manu " + i);
docs.add(doc);
}
client.addBeans(docs);
client.commit();
}
// 根据 id 删除单个文档
public void remove() throws Exception {
client.deleteById("100000000");
client.commit();
}
// 根据批量 id 删除文档
public void removeList() throws Exception {
List<String> ids = new ArrayList<String>();
for (int i = 1; i < 6; i++) {
ids.add("10000000" + i);
}
client.deleteById(ids);
client.commit();
}
// 根据查询条件删除文档
public void removeByQuery() throws Exception {
client.deleteByQuery("id:100000007");
client.commit();
}
// 查询操作
public void queryAll() throws Exception {
SolrQuery query = new SolrQuery();
query.set("q", "*:*");
query.setStart(0);
query.setRows(Integer.MAX_VALUE);
// query.set("start", 0);
// query.set("row", Integer.MAX_VALUE);
query.set("id", "id desc");
query.set("fl", "*, score");
QueryResponse response = client.query(query);
SolrDocumentList docs = response.getResults();
for (SolrDocument doc : docs) {
log(doc);
}
}
// 一些附属操作
public void otherMethod() throws Exception {
client.getBinder();
client.optimize(); // 合并索引文件,可以优化索引、提供性能,但需要一定的时间
client.ping(); // ping服务器是否连接成功
client.rollback();
client.commit();
}
// query 基本用法测试
public void queryCase() {
// AND 并且
SolrQuery params = new SolrQuery("name:apple AND manu:inc");
// OR 或者
params.setQuery("name:apple OR manu:apache");
// 空格 等同于 OR
params.setQuery("name:server manu:dell");
// params.setQuery("name:solr - manu:inc");
// params.setQuery("name:server + manu:dell");
// 查询name包含solr apple
params.setQuery("name:solr,apple");
// manu不包含inc
params.setQuery("name:solr,apple NOT manu:inc");
// 50 <= price <= 200
params.setQuery("price:[50 TO 200]");
params.setQuery("popularity:[5 TO 6]");
// 50 <= price <= 200 AND 5 <= popularity <= 6
params.setQuery("price:[50 TO 200] AND popularity:[5 TO 6]");
params.setQuery("price:[50 TO 200] OR popularity:[5 TO 6]");
// 过滤器查询,可以提高性能 filter 类似多个条件组合,如and
// params.addFilterQuery("id:VA902B");
// params.addFilterQuery("price:[50 TO 200]");
// params.addFilterQuery("popularity:[* TO 5]");
// params.addFilterQuery("weight:*");
// 0 < popularity < 6 没有等于
// params.addFilterQuery("popularity:{0 TO 6}");
// 排序
params.addSort("id", ORDER.asc);
// 分页:start开始页,rows每页显示记录条数
// params.add("start", "0");
// params.add("rows", "200");
// params.setStart(0);
// params.setRows(200);
// 设置高亮
params.setHighlight(true); // 开启高亮组件
params.addHighlightField("name");// 高亮字段
params.setHighlightSimplePre("<font color='red'>");// 标记,高亮关键字前缀
params.setHighlightSimplePost("</font>");// 后缀
params.setHighlightSnippets(1);// 结果分片数,默认为1
params.setHighlightFragsize(1000);// 每个分片的最大长度,默认为100
// 分片信息
params.setFacet(true).setFacetMinCount(1).setFacetLimit(5)// 段
.addFacetField("name")// 分片字段
.addFacetField("inStock");
// params.setQueryType("");
try {
QueryResponse response = client.query(params);
/*
* List<Index> indexs = response.getBeans(Index.class); for (int i =
* 0; i < indexs.size(); i++) { fail(indexs.get(i)); }
*/
// 输出查询结果集
SolrDocumentList list = response.getResults();
log("query result nums: " + list.getNumFound());
for (int i = 0; i < list.size(); i++) {
log(list.get(i));
}
// 输出分片信息
List<FacetField> facets = response.getFacetFields();
for (FacetField facet : facets) {
log(facet);
List<Count> facetCounts = facet.getValues();
for (FacetField.Count count : facetCounts) {
System.out.println(count.getName() + ": "
+ count.getCount());
}
}
} catch (SolrServerException e) {
e.printStackTrace();
}
}
// 分片查询, 可以统计关键字及出现的次数、或是做自动补全提示
public void facetQueryCase() {
SolrQuery params = new SolrQuery("*:*");
// 排序
params.addSort("id", ORDER.asc);
params.setStart(0);
params.setRows(200);
// Facet为solr中的层次分类查询
// 分片信息
params.setFacet(true).setQuery("*:*").setFacetMinCount(1)
.setFacetLimit(5) // 段
// .setFacetPrefix("electronics", "cat")
.setFacetPrefix("cor") // 查询manu、name中关键字前缀是cor的
.addFacetField("manu").addFacetField("name"); // 分片字段
try {
QueryResponse response = client.query(params);
// 输出查询结果集
SolrDocumentList list = response.getResults();
log("Query result nums: " + list.getNumFound());
for (int i = 0; i < list.size(); i++) {
log(list.get(i));
}
log("All facet filed result: ");
// 输出分片信息
List<FacetField> facets = response.getFacetFields();
for (FacetField facet : facets) {
log(facet);
List<Count> facetCounts = facet.getValues();
for (FacetField.Count count : facetCounts) {
// 关键字 - 出现次数
log(count.getName() + ": " + count.getCount());
}
}
log("Search facet [name] filed result: ");
// 输出分片信息
FacetField facetField = response.getFacetField("name");
List<Count> facetFields = facetField.getValues();
for (Count count : facetFields) {
// 关键字 - 出现次数
log(count.getName() + ": " + count.getCount());
}
} catch (SolrServerException e) {
e.printStackTrace();
}
}
private void log(Object obj) {
System.out.println(obj);
}
}
</code></pre></div></div>
<p>里面用到一个 java bean <code class="language-plaintext highlighter-rouge">Index.java</code>,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import org.apache.solr.client.solrj.beans.Field;
public class Index {
@Field
private String id;
@Field
private String name;
@Field
private String manu;
@Field
private String[] cat;
@Field
private String[] features;
@Field
private float price;
@Field
private int popularity;
@Field
private boolean inStock;
public String getId() {
return id;
}
@Field
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getManu() {
return manu;
}
public void setManu(String manu) {
this.manu = manu;
}
public String[] getCat() {
return cat;
}
public void setCat(String[] cat) {
this.cat = cat;
}
public String[] getFeatures() {
return features;
}
public void setFeatures(String[] features) {
this.features = features;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
}
public int getPopularity() {
return popularity;
}
public void setPopularity(int popularity) {
this.popularity = popularity;
}
public boolean isInStock() {
return inStock;
}
public void setInStock(boolean inStock) {
this.inStock = inStock;
}
public String toString() {
return this.id + "#" + this.name + "#" + this.manu + "#" + this.cat;
}
}
</code></pre></div></div>
Solr 应用(五)-中文分词
2015-05-06T00:00:00+00:00
http://www.blogways.net/blog/2015/05/06/solr-usage-5
<p>上一节我们在演示查询的时候只使用一个 <code class="language-plaintext highlighter-rouge">title:step</code> 就查出了所有 title 包含 step 的记录,感觉有点类似数据库的 like 功能,主要是分词的匹配的原因,实际上分词匹配功能要远强于 sql 的 like 功能。既然分词这么重要在国内使用那肯定离不开中文分词,本文介绍如何配置使用(smartcn)</p>
<h3 id="简介">简介</h3>
<p>solr5.0 默认的分词器是一元分词器,这个本来就是对英文进行分词的,英文大部分就是典型的根据空格进行分词,而中文如果按照这个规则,那么显然是要有很多的冗余词被分出来,一些没有用的虚词,数词,都会被分出来,影响效率不说,关键是分词效果不好,所以可以利用 solr 的同步发行包 smartcn 进行中文切词,smartcn 的分词准确率不错,但就是不能自己定义新的词库,不过 smartcn 是跟 solr 同步的,所以不需要额外的下载,只需在 solr 的例子中拷贝进去即可,下面给出路径图和安装 solr5.0 的 smartcn 分词过程</p>
<p>无论安装那种分词器,大部分都有2个步骤</p>
<ol>
<li>
<p>拷贝jar包到solr的lib中</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 将 `solr-5.0.0\server\solr\test\contrib\analysis-extras\lucene-libs\lucene-analyzers-smartcn-5.0.0.jar` 拷贝到 `solr-5.0.0\server\solr\test\contrib\analysis-extras\lib` 目录下
</code></pre></div> </div>
</li>
<li>
<p>修改相关配置</p>
</li>
</ol>
<h4 id="solr-500serversolrtestconfsolrconfigxml"><code class="language-plaintext highlighter-rouge">solr-5.0.0\server\solr\test\conf\solrconfig.xml</code></h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- annotating for smartcn, replaced by the blow config
<lib dir="${solr.install.dir:../../../..}/contrib/extraction/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-cell-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/clustering/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-clustering-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/langid/lib/" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-langid-\d.*\.jar" />
<lib dir="${solr.install.dir:../../../..}/contrib/velocity/lib" regex=".*\.jar" />
<lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-velocity-\d.*\.jar" />
-->
<!-- smartcn config, instead of the previous code -->
<lib dir="E:\tools\solr-5.0.0\server\solr\test\contrib\analysis-extras\lib" regex=".*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\contrib\extraction\lib" regex=".*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\dist\" regex="solr-cell-\d.*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\contrib\clustering\lib\" regex=".*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\dist\" regex="solr-clustering-\d.*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\contrib\langid\lib\" regex=".*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\dist\" regex="solr-langid-\d.*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\contrib\velocity\lib" regex=".*\.jar" />
<lib dir="E:\tools\solr-5.0.0\server\solr\test\dist\" regex="solr-velocity-\d.*\.jar" />
</code></pre></div></div>
<h4 id="solr-500serversolrtestconfschemaxml"><code class="language-plaintext highlighter-rouge">solr-5.0.0\server\solr\test\conf\schema.xml</code></h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><!-- Smartcn -->
<fieldType name="text_smartcn" class="solr.TextField" positionIncrementGap="0">
<analyzer type="index">
<tokenizer class="org.apache.lucene.analysis.cn.smart.SmartChineseSentenceTokenizerFactory"/>
<filter class="org.apache.lucene.analysis.cn.smart.SmartChineseWordTokenFilterFactory"/>
</analyzer>
<analyzer type="query">
<tokenizer class="org.apache.lucene.analysis.cn.smart.SmartChineseSentenceTokenizerFactory"/>
<filter class="org.apache.lucene.analysis.cn.smart.SmartChineseWordTokenFilterFactory"/>
</analyzer>
</fieldType>
</code></pre></div></div>
<p>将需要使用中文分词的字段类型改成 <code class="language-plaintext highlighter-rouge">text_smartcn</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><field name="goods_name" type="text_smartcn" indexed="true" stored="true"/>
</code></pre></div></div>
<p>最后来验证下中文分词安装是否成功</p>
<p><img src="/images/solr-5-1.png" alt="Web 控制台 中文分词测试" /></p>
<p>除了中文分词,另外还有一个拼音分词,这个网上可以搜到,需要下载一个 jar 包,其他配置、使用都跟上面类似,有兴趣的可以自己去试下。</p>
Solr 应用(四)-企业应用
2015-05-05T00:00:00+00:00
http://www.blogways.net/blog/2015/05/05/solr-usage-4
<p>通过前面几节的了解,我们对 solr 有了一些了解,对一些基本概念、语法也有了初步的认识,那么很多没有接触过搜索引擎的小伙伴可能会问,这东西到底有什么用,在自己的项目中能发挥什么作用,结合我们自己的项目,简要做个演示。</p>
<h3 id="需求分析">需求分析</h3>
<p>相信很多人都用过类似淘宝或者京东的产品搜索功能,输入想要查找的商品,点搜索很快列出一堆你想要的东东,我们的产品也想实现这个功能。</p>
<p>我们都知道数据库的瓶颈主要在 IO 这块,虽然通过分库分表能分担一部分负载,但当网站访问量比较大的时候,不应该也不可能搜索一次都去执行一次数据库 IO 操作,这个时候 Solr 也就派上用途了,我们可以通过将产品相关数据刷到 Solr 的搜索实例中,通过 Solr 应用服务器对外提供 Rest 查询接口,定时去更新数据,刷新搜索索引,这样就大大降低了查询数据库服务器的压力。</p>
<h3 id="数据导入">数据导入</h3>
<p>那么问题来了,如果将数据库相关数据导入搜索实例中呢?前面我们使用了 <code class="language-plaintext highlighter-rouge">bin/post</code> 将 json 文档直接导入搜索实例,接下来我们看下如何将 mysql 中表数据导入到搜索实例中。</p>
<h4 id="使用dihdataimporthandler从数据库导入数据"><em>使用DIH(DataImportHandler)从数据库导入数据</em></h4>
<ol>
<li>
<p>首先数据库准备一张表</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> CREATE TABLE `comment` (
`id` INT(11) NULL DEFAULT NULL,
`writetime` VARCHAR(50) NULL DEFAULT NULL,
`title` VARCHAR(500) NULL DEFAULT NULL
)
</code></pre></div> </div>
</li>
<li>
<p>往表里面插入几条测试数据</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> INSERT INTO `test`.`comment` (`id`, `writetime`, `title`) VALUES (10001, '2015-04-11', 'Step 1: Set up a personal account');
INSERT INTO `test`.`comment` (`id`, `writetime`, `title`) VALUES (10002, '2015-04-12', 'Step 2: Choose your plan');
INSERT INT3 `test`.`comment` (`id`, `writetime`, `title`) VALUES (10003, '2015-04-13', 'Step 3: Go to your dashboard');
INSERT INTO `test`.`comment` (`id`, `writetime`, `title`) VALUES (10004, '2015-04-14', 'There were problems creating your account.');
INSERT INTO `test`.`comment` (`id`, `writetime`, `title`) VALUES (10005, '2015-04-15', 'You will occasionally receive account related emails. We promise not to share your email with anyone.');
INSERT INTO `test`.`comment` (`id`, `writetime`, `title`) VALUES (10006, '2015-04-16', 'Use at least one lowercase letter, one numeral, and seven characters.');
</code></pre></div> </div>
</li>
<li>
<p>导入相关jar包
在 <code class="language-plaintext highlighter-rouge">solr-5.0.0/server/solr/test/conf/solrconfig.xml</code> 加入如下代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <lib dir="${solr.install.dir:../../../..}/dist/" regex="solr-dataimporthandler-.*\.jar" />
</code></pre></div> </div>
</li>
<li>
<p>配置handler
在 <code class="language-plaintext highlighter-rouge">solr-5.0.0/server/solr/test/conf/solrconfig.xml</code> 加入如下代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <requestHandler name="/dataimport" class="solr.DataImportHandler">
<lst name="defaults">
<str name="config">db-data-config.xml</str>
</lst>
</requestHandler>
</code></pre></div> </div>
</li>
<li>
<p>配置数据源,源数据与索引的隐射关系
在 <code class="language-plaintext highlighter-rouge">solr-5.0.0/server/solr/test/conf/下新建db-data-config.xml</code> ,配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <dataConfig>
<dataSource type="JdbcDataSource"
driver="com.mysql.jdbc.Driver"
url="jdbc:mysql://localhost/test"
user="root"
password="root"/>
<document>
<entity name="comment"
query="SELECT id, DATE_FORMAT(writetime, '%Y-%m-%dT%TZ') as 'writetime', title from comment"
deltaImportQuery="SELECT id, DATE_FORMAT(writetime, '%Y-%m-%dT%TZ') as 'writetime', title from comment where id='${dih.delta.id}'"
deltaQuery="SELECT id FROM comment WHERE DATE(updatetime) >= '${dih.last_index_time}' OR DATE(writetime) >= '${dih.last_index_time}'">
<field column="id" name="id"/>
<field column="writetime" name="writetime"/>
<field column="title" name="title"/>
</entity>
</document>
</dataConfig>
</code></pre></div> </div>
</li>
</ol>
<p>注:查询 sql 可以是多表关联查询,只要查询字段跟下面的 field 对应起来就可以。另外 field 对应的name值必须在 config 目录下的 schema.xml 文件中存在。如上述的 writetime 不存在,则在schema.xml中添加</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <field name="writetime" type="tdate" indexed="true" stored="true"/>
</code></pre></div></div>
<p>indexed 表示需不需要建立索引,以便之后对这个 field 进行查询;</p>
<p>stored 表示需不需要随索引同时存储这个 field 本身的内容,以便查询时直接从结果中获取该内容,一般大数据(比如文件内容本身)不会和索引一起保存,节省资源,防止索引过大。 索引本身和被索引的内容要分清,不理解的话,倒排索引:<code class="language-plaintext highlighter-rouge">http://zh.wikipedia.org/wiki/</code>倒排索引</p>
<p>6.Reload test 搜索实例,利用图形界面执行dataimport</p>
<p><img src="/images/solr-4-1.png" alt="Web 控制台 dataimport" /></p>
<p>因为这个文件我已经导过了,所以上面显示是 reload,第一次使用时有一点点差别。导完数据以后我们查询验证下 <code class="language-plaintext highlighter-rouge">http://localhost:8080/solr/test/select?q=title%3Astep&wt=json&indent=true</code></p>
<p><em>返回结果:</em></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"responseHeader":{
"status":0,
"QTime":2,
"params":{
"indent":"true",
"q":"title:step",
"wt":"json"}},
"response":{"numFound":3,"start":0,"docs":[
{
"id":"10002",
"title":["Step 2: Choose your plan"],
"writetime":"2015-04-10T00:00:00Z",
"_version_":1498574060831899648},
{
"id":"10001",
"title":["Step 1: Set up a personal account"],
"writetime":"2015-04-10T00:00:00Z",
"_version_":1498574060739624960},
{
"id":"10003",
"title":["Step 3: Go to your dashboard"],
"writetime":"2015-04-10T00:00:00Z",
"_version_":1498574060832948224}]
}}
</code></pre></div></div>
<p>这里面我们使用的是 <code class="language-plaintext highlighter-rouge">title:step</code> 将 title 包含 step 的三条记录都查了出来。</p>
<h4 id="编程的方式来自定义导入这个编程部分再讲"><em>编程的方式来自定义导入(这个编程部分再讲)</em></h4>
<p>当然除了数据库数据,Solr 还能导入 csv,json,word,pdf,这部分有兴趣可以自己去试验下。</p>
<p>至此,一个简单的 Solr 的应用演示算是完成了。</p>
MongoDB 入库速度和分片的关系
2015-05-05T00:00:00+00:00
http://www.blogways.net/blog/2015/05/05/mongodb-sharding-io-rate
<h2 id="一背景">一、背景</h2>
<p>实时采集主机的日志文件,发送到 node.js 处理程序( 暂时叫做 <strong>load</strong> ),经过一些数据处理后插入到 MongoDB 持久存储。由于采集的主机数量较多,导致了如下问题:</p>
<ul>
<li>每秒需要入库的数据量很大,单个 MongoDB 无法满足这些数据的入库要求,导致插入的拥堵,导致 MongoDB 中的不是实时的数据;</li>
<li>MongoDB 缓存到内存中的热数据量太多,单台主机无法满足如此大的内存需求;</li>
<li>MongoDB 持久化存储到本地磁盘的数据非常大( 有些表一天的数据量有上千万条 ),需要的存储空间很大,单台无法满足要求。</li>
</ul>
<p>考虑到<strong>纵向扩展</strong>的代价较高,决定采用 MongoDB 的分片机制来实现,需要探究的问题:</p>
<ol>
<li>单个 MongoDB 实例的入库速率极限是多少;</li>
<li><strong>load</strong>( 处理接收到的日子文件,并入库到MongoDB )的数量与入库速率的关系;</li>
<li>分片的片数与入库速率的关系;</li>
<li>单台主机( 8核心,16G )启动<strong>load</strong>和 MongoDB 实例的最合适个数;</li>
</ol>
<h2 id="二测试单个mongodb入库极限">二、测试单个MongoDB入库极限</h2>
<p>启动一个 MongoDB 实例,分别测试启动多个<strong>load</strong>时,MongoDB的入库速率( 10min中入库的记录条数 ),发往<strong>load</strong>的日子数量充足;</p>
<p>MongoDB 实例启动:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongod --dbpath /data/mongodb/db --logpath /data/mongodb/logs/mongod.log --nojournal --fork 具体命令吃处不在讲解,详见[MongoDB 安装与分布式部署](http://www.blogways.net/blog/2015/05/04/mongodb-install-and-distribution-deploy.html)
</code></pre></div></div>
<h3 id="测试结果">测试结果</h3>
<table width="100%">
<tr><th>load 个数</th><th>十分钟入库数</th><th>入库速率( 条/sec )</th></tr>
<tr><td>1</td><td>222902</td><td>372</td></tr>
<tr><td>2</td><td>260933</td><td>435</td></tr>
<tr><td>3</td><td>259581</td><td>433</td></tr>
<tr><td>4</td><td>260966</td><td>435</td></tr>
</table>
<h3 id="结论">结论</h3>
<p>针对当前<strong>load</strong> 应用处理程序,单个 MongoDB 实例的入库极限速度为:<code class="language-plaintext highlighter-rouge">435 条/sec</code>。</p>
<h3 id="注意">注意</h3>
<ul>
<li>测试中还记录并且计算了其它时间长度的入库速度,有一些差异,但都在误差的范围内;</li>
<li>针对不同的处理入库逻辑,测出的入库极限速度都不同,本内容的所有数据都是基于自己的<strong>load</strong>,仅供参考。</li>
</ul>
<h2 id="三分片数与入库速率的关系-python-">三、分片数与入库速率的关系( python )</h2>
<p>考虑到日志的抓去、<strong>load</strong>的处理和数据入库中间存在很多的不确定性,因而首先只选择测试每秒钟插入记录数来表面存在的关系。</p>
<h3 id="测试方案">测试方案</h3>
<p>分别在 3 台不同的主机上部署3个配置服务器(Config Server),在其中的一台上面部署 3 个查询路由(Query Router),最后在另一台主机上( 不包含在部署Config Server 和 Query Router主机内 ),分别启动1、2、3、4个分片(按照 <code class="language-plaintext highlighter-rouge">host</code>字段,<strong><em>Hash 分片</em></strong>);</p>
<p>通过<code class="language-plaintext highlighter-rouge">ruby</code>语言编写插入记录程序( 需要pymongo插件 ),部分代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#!/bin/env python
from pymongo import MongoClient
import time,datetime,random
# 启动的3个查询路由
db1 = MongoClient('10.20.16.78:27017').test
db2 = MongoClient('10.20.16.78:27018').test
db3 = MongoClient('10.20.16.78:27019').test
def insert(num):
for i in range(num):
rand = random.randint(1,3)
host = random.randint(1,3)
if rand == 1:
db = db1
elif rand == 2:
db = db2
else:
db = db3
obj = {
"host": host,
# 生成插入的对象
}
db.objInsert.insert_one(obj)
insert(300000) 上面的每个脚本表示插入 300000 条记录到分片表中,此处为测试分三片时候的入库速率;
</code></pre></div></div>
<h3 id="配置">配置</h3>
<p>将<code class="language-plaintext highlighter-rouge">test.objInsert</code>表按照<code class="language-plaintext highlighter-rouge">host</code>字段哈希分片:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sh.shardCollection('test.objInsert', {'host': 'hashed'}) 启动`python`脚本向数据库插入数据,记录10s、1min的插入速率。
</code></pre></div></div>
<h3 id="测试数据">测试数据</h3>
<p>应该很多次测试,此处只贴出一次结果:</p>
<table width="100%">
<tr><th>分片 片数</th><th>10s入库数</th><th>入库速率( 条/sec )</th></tr>
<tr><td>1 (不分片)</td><td>120263</td><td>12026</td></tr>
<tr><td>2</td><td>148543</td><td>14854</td></tr>
<tr><td>3</td><td>185022</td><td>18502</td></tr>
</table>
<p>绘制的平滑标记散点连线图,如下所示:</p>
<p><img src="/images/post/sharding-rate-result.png" alt="sharding-rate-result" /></p>
<h3 id="结论-1">结论</h3>
<p>由上图可得到一个大概的猜想:<strong><em>入库速率与分片片数呈线性关系!</em></strong></p>
<h3 id="注意-1">注意</h3>
<p>由于主机数、主机内存等愿意的限制,没法启动太多的<code class="language-plaintext highlighter-rouge">python</code>脚本用于数据插入,实际测试用用的两台机器来插入测试;</p>
<p>测试主机还有其它程序占用CPU等,没法长时间处于<code class="language-plaintext highlighter-rouge">python</code>脚步执行状态,测试只记录 10s 中的数据;还有可能涉及到其它原因,长时间插入会出现速率波动;</p>
<h2 id="四入库速度与load数量的关系">四、入库速度与load数量的关系</h2>
<p>由于 node.js 连接 MongoDB 的所有数据库操作都是异步调用的,所以很多操作只能在回调函数中进行,或通过其它方式将其转换为同步操作( 如:<code class="language-plaintext highlighter-rouge">async</code>、<code class="language-plaintext highlighter-rouge">EventProxy</code> )。</p>
<p>在测试的初期,发现有时候<strong>load</strong>占用的内存会不断的升高,有时候甚至会导致<strong>load</strong>程序崩溃,后来实验发现,是因为MongoDB 的入库速率达到了瓶颈,而又有不断的数据插入,导致回掉函数无法立即执行并积压在内存中,是的占用内存越来越高,最后程序崩溃。</p>
<p>根据上述原因,可以通过load的内存占用来侧面反应<strong>load</strong>和MongoDB的工作状态:</p>
<ul>
<li>内存占用过高,<strong>load</strong>对接收到的日志处理不过来,或MongoDB的入库速率到达极限;</li>
<li>内存占用正常或稍微偏高( 10 ~ 100M之内 ),<strong>load</strong>和 MongoDB都处在比较理想的工作状态;</li>
<li>内存占用偏低,<strong>load</strong>或 MongoDB的性能部分处于闲置状态。</li>
</ul>
<p>在做本项测试的时候,需要保证 MongoDB的出来能力能满足所有<strong>load</strong>个数的限制,观察不同个数时的入库记录条数;因而在必要的时候需要启动多个分片;</p>
<h3 id="测试方案-1">测试方案</h3>
<p>分别启动1、2、3、4个<strong>load</strong>,并启动相应的分片数( 使得每个<strong>load</strong>的内存占用正常 ),启动3个配置服务器;考虑到多个<strong>load</strong>的情况,传递过来的日子数据通过<code class="language-plaintext highlighter-rouge">nginx</code>按 <strong>响应时间</strong> 做负载均衡处理。</p>
<h3 id="测试数据-1">测试数据</h3>
<table width="100%">
<tr><th>load 个数</th><th>10min入库数</th><th>入库速率( 条/sec )</th></tr>
<tr><td>1</td><td>227079</td><td>379</td></tr>
<tr><td>2</td><td>447980</td><td>747</td></tr>
<tr><td>3</td><td>614332</td><td>1024</td></tr>
<tr><td>4</td><td>782492</td><td>1304</td></tr>
</table>
<p>绘制的平滑标记散点连线图,如下所示:</p>
<p><img src="/images/post/sharding-rate-load-num.png" alt="" /></p>
<p>多次测量的过程中,<strong>load</strong>的内存占用都在100 ~ 250 M之间,CPU 占用率100%左右( 完全占用一个物理CPU核心 )。</p>
<h3 id="结论-2">结论</h3>
<ul>
<li>由上图可发现,<strong>load</strong>个数和入库速率所呈现的基本是一条直线,因而得到结论<strong>load</strong>的个数与入库速率成线性关系;</li>
<li>由于<strong>load</strong>在正常工作情况下内存占用补不超过300M,这相对于一台主机的16G的内存来说很小,因而一台机器能启动的<strong>load</strong>的个数取决于其核心数,还要留一些用作它用,因而8核心CPU主机,启动 <strong>4 ~ 7</strong>个<strong>load</strong>是比较合适的。</li>
</ul>
<h3 id="注意-2">注意</h3>
<ul>
<li>在测试4个<strong>load</strong>的时候,开始使用了3个配置服务器,4个分片,结果每个<strong>load</strong>的内存占用都高达600M以上,说明分4片没法满足4个<strong>load</strong>的入库请求;</li>
<li>然后使用3个配置服务器,6个分片,<strong>load</strong>的内存占用在400M以上,同样6个分片也无法满足要求;</li>
<li>最后在启用8个分片后,内存占用降到了100M多一点,说明此时的MongoDB的处理入库速度完全够4个<strong>load</strong>使用。</li>
</ul>
<h2 id="五分片数与入库速率的关系-load-">五、分片数与入库速率的关系( load )</h2>
<p>有测试四的注意可知,4个<strong>load</strong>,3个配置服务器即可完成测试不分片、分2、3、4片时的入库速率。</p>
<h3 id="测试数据-2">测试数据</h3>
<table width="100%">
<tr><th>分片 片数</th><th>20min入库数</th><th>入库速率( 条/sec )</th></tr>
<tr><td>不分片</td><td>-</td><td>435</td></tr>
<tr><td>2</td><td>854605</td><td>712</td></tr>
<tr><td>3</td><td>1127365</td><td>940</td></tr>
<tr><td>4</td><td>14220( 2min )</td><td>1185</td></tr>
</table>
<p>绘制的平滑标记散点连线图,如下所示:</p>
<p><img src="/images/post/sharding-rate-result-load.png" alt="" /></p>
<p>每个<code class="language-plaintext highlighter-rouge">mongod</code>进程( 分片实例 )的CPU占用在繁忙的时候会接近100%,有时候甚至超过100%;</p>
<h3 id="结论-3">结论</h3>
<ul>
<li>由上图可知,分片的片数与入库速率亦是成线性关系;</li>
<li>由于CPU会超过100%的考虑,单台8核心的服务器,最多启动6个分片;</li>
</ul>
<h3 id="注意-3">注意</h3>
<ul>
<li>单台主机的MongoDB 分片的片数选择主要考虑 CPU 的核心数;</li>
<li>MongoDB 分片进程会讲插入的数据都保存到内存中,直到内存完全被耗光,后续的数据会持久化到磁盘空间,内存中只会保存热数据和索引。</li>
<li>如果主机内存吃紧的话,启动分片实例的时候最好带<code class="language-plaintext highlighter-rouge">--nojurounal</code>启动。</li>
</ul>
<h2 id="六总结">六、总结</h2>
<ul>
<li><strong>load</strong> 的个数和入库速率成线性关系;</li>
<li>MongoDB 分片的片数和入库速率成线性关系;</li>
<li>单台主机启动<strong>load</strong>的个数主要考虑因素是 CPU 核心数,最多启动 <code class="language-plaintext highlighter-rouge">n - 2 (n 为 cpu 核心数)</code>个;</li>
<li>单台主机启动MongoDB 分片实例的个数,参考因素为 CPU 核心数,最多启动 <code class="language-plaintext highlighter-rouge">n - 2 (n 为 cpu 核心数)</code>个;</li>
</ul>
<p></br>
</br></p>
<p>===</p>
<p><strong><em>后续修改中。。。</em></strong></p>
MongoDB 分片介绍
2015-05-05T00:00:00+00:00
http://www.blogways.net/blog/2015/05/05/mongodb-sharding-introduction-by-docs
<h2 id="一分片介绍">一、分片介绍</h2>
<p>分片是一个遍及多台机器存储数据的方法,MongoDB 使用分片来实现对大数据集合部署与高吞吐量操纵的支持。</p>
<h3 id="分片的目的">分片的目的</h3>
<p>对一个单一数据库系统服务来说,大数据集合和高吞吐量的应用程序将会是一个非常严峻的考验。高的查询速率会“吃光”服务器的所有CPU,更大的数据集合超出一个单一机器的存储空间。最后,工作集合的大小比系统内存容量( 磁盘驱动的I/O性能 )大。</p>
<p>解决上诉问题的方式有两个:</p>
<ol>
<li>纵向扩展 ( vertical scaling )</li>
<li>分片 ( sharding )</li>
</ol>
<p><strong>Vertical scaling</strong>,增加更多的CPU数和存储资源来增加容纳量,但是扩展存在限制:更多核心数的CPU和更多RAM的高性能系统比更小的系统昂贵,而且其 CPU 和 RAM 的增加与得到的性能不是曾比例的。另外,云服务提供商可能只允许用户提供更小的例子。因此,纵向扩展存在一个性能的最大极限。</p>
<p><strong>Sharding</strong>,或者说是水平扩展,相反地将大数据集合拆分并将数据分布到多个服务器,或分片上。每个分片都是一个独立的数据库,所有的分片组成一个逻辑数据库。</p>
<p><img src="/images/post/sharded-collection.png" alt="" /></p>
<p>分片地址扩展到支持高吞吐率和大数据集合的难点:</p>
<ul>
<li>分片降低了每个分片处理操作的数量,随着集群的增长,每个分片将会处理更少的操作。其结果是,一个集群能够<em>水平地</em>增加容纳量和高吞吐量。例如:插入数据时,应用程序只需要访问负责该记录的碎片。</li>
<li>分片减少了每个服务器需要存储的数据量,随着集群的增长,每个分片将会保存更少的数据。例如:一个数据库有 1TB 的数据,分4个分片后,每个分片只要保存 256GB 的数据;如果有40个分片的话,每个分片只需要保存 25GB 的数据。</li>
</ul>
<h2 id="二mongodb-分片">二、MongoDB 分片</h2>
<p>MongoDB 支持通过配置一个分片集群来支持分片。</p>
<p><img src="/images/post/sharded-cluster-production-architecture.png" alt="" /></p>
<p>分片集群由分片、查询路由和配置服务器三个组件构成:</p>
<ul>
<li><strong>Shards</strong> 用于保存数据。为了提供高可用性和数据一致性,在一个生产环境分片集群中,每个分片都有一个复制集( Replica Set )。关于复制集更多的信息,见<a href="http://docs.mongodb.org/manual/core/replication/" title="http://docs.mongodb.org/manual/core/replication/">Replica Sets</a></li>
<li><strong>Query Routers</strong>,或者说<code class="language-plaintext highlighter-rouge">mongos</code>实例,客户端应用程序操作的接口,直接操作合适的分片或分片集。查询路由处理并将操作定位到分片,然后将结果返回给客户端。一个分片集群能够包含多个查询路由( Query Router ),用以分担客户端请求的负载。一个客户端和一个查询路由一一对应,更多的分片集群需要很多的查询路由。</li>
<li><strong>Config Server</strong> 保存集群的元数据( metadata ),包含集群数据集到分片的映射,查询路由通过元数据将操作定位到指定的分片上。生产环境下,拥有确切的 <strong><em>3</em></strong>个配置服务器。</li>
</ul>
<h3 id="数据分割">数据分割</h3>
<p>MongoDB 在集合的水平上分割数据和分片,通过一个分片键( shard key )来分割分片。</p>
<h4 id="分片键-shard-keys-">分片键( Shard Keys )</h4>
<p>为了将一个集合分片,需要选择一个分片关键字。一个分片关键字是一个索引字段,或存在于每个集合文档中的一个复合索引字段。MongoDB 按分片字段将集合划分为块( chunks ),然后讲所有的块均匀的分布到所有分片上。为了划分集合为区块,MongoDB 提供了<strong><em>范围分片(Range Based Sharding)</em></strong> 和 <strong><em>哈希分片(Hash Based Sharding)</em></strong>。</p>
<h4 id="范围分片-range-based-sharding-">范围分片( Range Based Sharding )</h4>
<p>在<em>范围分片</em>中,MongoDB 通过范围划分字段值提供的范围分割,将数据集合划分为不同的范围边界。就像一个数字分片字段:将负无穷到正无穷的所有数形象化为一条线,每个分片字段的值都落在该线上的某个点上。此线段被 MongoDB 划分为的更小的,没有重叠的范围叫做块( chunks ),一个块是某最小值到某最大值之间值的范围。</p>
<p>已知的一个范围分割系统,分片字段值“相近”的文档分布在同一个块上,因此也在同一个分片上。</p>
<p><img src="/images/post/sharding-range-based.png" alt="" /></p>
<h4 id="哈希分片-hash-based-sharding-">哈希分片( Hash Based Sharding )</h4>
<p>在<em>哈希分片</em>中,MongoDB 计算一个字段值的哈希值,然后使用此哈希值创建一个区块。</p>
<p>如果使用哈希分割,分片字段值“相近”的两个文档可能不在同一个区块中,这确保了一个集合在集群中更大限度的随机分布。</p>
<p><img src="/images/post/sharding-hash-based.png" alt="" /></p>
<h4 id="范围分片和哈希分片的性能差异">范围分片和哈希分片的性能差异</h4>
<p>基于范围的分片方式提供了更高效的范围查询,给定一个片键的范围,分发路由可以很简单地确定哪个数据块存储了请求需要的数据,并将请求转发到相应的分片中。</p>
<p>不过,基于范围的分片会导致数据在不同分片上的不均衡,有时候,带来的消极作用会大于查询性能的积极作用。比如,如果片键所在的字段是线性增长的,一定时间内的所有请求都会落到某个固定的数据块中,最终导致分布在同一个分片中。在这种情况下,一小部分分片承载了集群大部分的数据,系统并不能很好地进行扩展。</p>
<p>与此相比,基于哈希的分片方式以范围查询性能的损失为代价,保证了集群中数据的均衡。哈希值的随机性使数据随机分布在每个数据块中,因此也随机分布在不同分片中。但是也正由于随机性,一个范围查询很难确定应该请求哪些分片,通常为了返回需要的结果,需要请求所有分片。</p>
<h4 id="使用标记自定义集群中数据的分布">使用标记自定义集群中数据的分布</h4>
<p>MongoDB允许管理员使用 <strong>Tag Aware Sharding</strong> 直接决定集群的均衡策略。管理员使用标记与片键的范围做绑定,并将标记与分片直接绑定,之后,均衡器会将满足标记的数据直接分发到与之绑定的分片上,并且确保之后满足标记的数据一直存储在相应的分片上。</p>
<p>标记是控制均衡器行为和数据块分布的首要条件,一般来讲,在拥有多个数据中心时,才会使用标记自定义集群中数据块的分布,以提高不同地域之间数据访问的效率。</p>
<p>参考 <strong><a href="http://docs.mongodb.org/manual/core/tag-aware-sharding/" title="http://docs.mongodb.org/manual/core/tag-aware-sharding/">Tag Aware Sharding</a></strong> 获得更多相关信息。</p>
<h4 id="数据分布均衡的维护">数据分布均衡的维护</h4>
<p>新数据的加入或者新分片的加入可能会导致集群中数据的不均衡,即表现为有些分片保存的数据块数目显著地大于其他分片保存的数据块数。</p>
<p>MongoBD使用两个过程维护集群中数据的均衡:</p>
<ol>
<li>
<p>分裂;</p>
<p>分裂是防止某个数据块过大而进行的一个后台任务。当一个数据块的大小超过<a href="http://docs.mongodb.org/manual/core/sharding-chunk-splitting/#sharding-chunk-size" title="http://docs.mongodb.org/manual/core/sharding-chunk-splitting/#sharding-chunk-size">设定的数据块大小</a>时,MongoDB会将其一分为二,插入与更新触发分裂过程。分裂改变了元信息。但是效率很高,进行分裂时,MongoDB 不会迁移任何数据,对集群性能也没有影响。</p>
<p><img src="/images/post/sharding-splitting.png" alt="sharding-splitting" /></p>
</li>
</ol>
<ol>
<li>
<p>均衡器。</p>
<p>均衡器是一个管理区块迁移的后台进程,均衡器在一个集群中所有的查询路由上运行。</p>
<p>当集群中数据的不均衡发生时,均衡器会将数据块从数据块数目最多的分片迁移到数据块最少的分片上,举例来讲:如果集合 <strong>users</strong> 在 <em>shard1</em> 上有100个数据块,在 <em>shard2</em> 上有50个数据块,均衡器会将数据块从 <em>shard1</em> 一直向 <em>shard2</em> 迁移,一直到数据均衡为止。</p>
<p>分片管理在后台管理从 <em>源分片</em> 到 <em>目标分片</em> 的数据块迁移,在迁移过程中,<em>目标分片</em>首先会接收源分片在迁移数据块上的所有数据,之后,目标分片应用在上一迁移步骤之间发生在源分片上的迁移数据块的更改,最后,存储在<em>配置服务器</em> 上的元信息被更新。</p>
<p>如果迁移中发生错误,源分片上的数据不会被修改,迁移会停止。在迁移成功结束之后MongoDB才会在源分片上将数据删除。</p>
<p><img src="/images/post/sharding-migrating.png" alt="sharding-migrating" /></p>
</li>
</ol>
<h4 id="在集群中增加或者删除分片">在集群中增加或者删除分片</h4>
<p>在集群中增加分片时,由于新的分片上并没有数据块,会造成数据的不均衡。此时MongoDB会立即开始向新分片迁移数据,集群达到数据均衡的状态需要花费一些时间。</p>
<p>当删除一个分片时,均衡器需要将被删除的分片上的数据全部迁移到其他分片上,在全部迁移结束且元信息更新完毕之后,你可以安全地将这个分片移除。</p>
JAXB使用教程一:简介与基本用法
2015-05-05T00:00:00+00:00
http://www.blogways.net/blog/2015/05/05/jaxb-tutorial-1
<h2 id="一简介">一、简介</h2>
<p>JAXB(Java Architecture for XML Binding简称JAXB)允许Java开发人员将Java类映射为XML表示方式。JAXB提供两种主要特性:将一个Java对象序列化为XML,以及反向操作,将XML解析成Java对象。换句话说,JAXB允许以XML格式存储和读取数据,而不需要程序的类结构实现特定的读取XML和保存XML的代码!</p>
<p>JAXB 已经是 Java SE平台的一部分,Java EE平台API之一,也是Java Web服务开发包(JWSDP)的一部分。</p>
<p>JAXB在Java SE平台上,对应版本分别为:</p>
<ul>
<li>
<p>Java SE 8: JAXB 2.2.8</p>
</li>
<li>
<p>Java SE 7: JAXB 2.2.3 (JSR 222, maintenance release 2)</p>
</li>
<li>
<p>Java SE 6: JAXB 2.0 (JSR 222)</p>
</li>
</ul>
<h2 id="二jaxb和domsaxjdomdom4j对比">二、JAXB和Dom/SAX/JDOM/Dom4J对比</h2>
<p>其实,JAXB不能直接拿来和Dom/SAX/Dom4j进行比较。他们的设计理念不同。后者提供的API都是解析XML的底层API,而前者设计的目的是将XML文件中的元素及属性和JAVA对象建立绑定关系,可以自动相互转换。</p>
<p>JAXB没有提供解析XML的新方法,它是调用后者(Dom/SAX/JDOM/Dom4J)来解析XML的。(JAXB默认是调用SAX来实现XML解析的)</p>
<p>如果,你仅仅是想把XML的内容转换成Java对象,那么JAXB比起后者更容易实现,它可以让你少些一些代码。特别是,当XML的结构非常复杂时,他的代码量比后者要少很多。</p>
<p>当然,他们之间的取舍,主要还是以你的使用目的所决定。</p>
<p><em>另外,关于DOM和SAX的更多信息,可以看看我的另一篇博文:<a href="javax-xml-parsers.html">javax.xml.parsers使用简介及源码探究:DOM与SAX</a></em></p>
<hr />
<p>下面,我们就来看看,如何在xml文件和java对象间,使用JAXB进行转换。</p>
<h2 id="三编组marshal">三、编组(marshal)</h2>
<p>将Java对象,转换为Xml文件,我们称之为编组(marshal).</p>
<p>转换代码很简单:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main( String[] args )
{
try
{
/* 初始化java对象 */
Person person = new Person();
person.setFirstName("net");
person.setLastName("blogways");
person.setCity("NanJing");
person.setPostalCode(210000);
person.setBirthday(LocalDate.of(2013, 10, 11));
/* 初始化 jaxb marshaler */
JAXBContext jaxbContext = JAXBContext.newInstance( Person.class );
Marshaller jaxbMarshaller = jaxbContext.createMarshaller();
/* 设置为格式化输出 */
jaxbMarshaller.setProperty( Marshaller.JAXB_FORMATTED_OUTPUT, true );
/* 将java对象 编组 为xml (输出到文件或标准输出) */
jaxbMarshaller.marshal( person, new File( "person.xml" ) );
jaxbMarshaller.marshal( person, System.out );
}
catch( JAXBException e )
{
e.printStackTrace();
}
}
</code></pre></div></div>
<p>而在<code class="language-plaintext highlighter-rouge">Person.java</code>中,你只需要通过注释,就可以告诉程序,要转换为什么格式的Xml文件。</p>
<p>比如, <strong>Person.java:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package net.blogways.jaxb.example.model;
import java.time.LocalDate;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement( name = "Person" )
public class Person {
private String firstName;
private String lastName;
private Integer postalCode;
private String city;
private LocalDate birthday;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public Integer getPostalCode() {
return postalCode;
}
public void setPostalCode(Integer postalCode) {
this.postalCode = postalCode;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
public LocalDate getBirthday() {
return birthday;
}
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
}
</code></pre></div></div>
<p>在这里:</p>
<ul>
<li>@XmlRootElement 定义了根节点的名字</li>
</ul>
<p>是不是很简单?!</p>
<p>另外,需要说明的是:</p>
<ul>
<li>在<code class="language-plaintext highlighter-rouge">Person.java</code>中,你可以通过更多的<a href="#5">注释</a>,来定义编组后的xml格式。</li>
<li>在一个复杂的项目中,XML文件结构可能很复杂。这时,不需要手工编写对应的Java类文件,我们可以通过工具从xml schema文件获得java类文件,甚至可以从xml文件中转换得到java类文件。这些我们在后续的文章中介绍。</li>
</ul>
<h2 id="四反编组un-marshal">四、反编组(Un-marshal)</h2>
<p>将Xml文件的内容,转换为Java对象,我们称之为反编组(un-marshal).</p>
<p>反编组,也很简单。代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main(String[] args) {
try
{
File file = new File( "list.xml" );
JAXBContext jaxbContext = JAXBContext.newInstance( Persons.class );
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
Persons persons = (Persons)jaxbUnmarshaller.unmarshal( file );
System.out.println( persons );
}
catch( JAXBException e )
{
e.printStackTrace();
}
}
</code></pre></div></div>
<p>转换的对应关系,都在Persons.java里面。这里为了示例更简单清晰,我们只加了一行注释<code class="language-plaintext highlighter-rouge">XmlRootElement</code>,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package net.blogways.jaxb.example.model;
import java.util.List;
import javax.xml.bind.annotation.XmlRootElement;
@XmlRootElement( name = "Persons" )
public class Persons {
private List<Person> persons;
public List<Person> getPersons() {
return persons;
}
public void setPersons(List<Person> persons) {
this.persons = persons;
}
@Override
public String toString()
{
StringBuffer str = new StringBuffer();
for( Person person : this.persons )
{
str.append( person.toString() );
str.append("\n");
}
return str.toString();
}
}
</code></pre></div></div>
<p>list.xml内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Persons>
<persons>
<city>NanJing</city>
<firstName>Li</firstName>
<lastName>si</lastName>
<postalCode>210000</postalCode>
</persons>
<persons>
<city>NanJing</city>
<firstName>zhang</firstName>
<lastName>san</lastName>
<postalCode>210000</postalCode>
</persons>
</Persons>
</code></pre></div></div>
<p><a name="5"></a></p>
<h2 id="五注释">五、注释</h2>
<p>在本文前面部分,为了简化示例。我们仅使用了JAXB中的<code class="language-plaintext highlighter-rouge">@XmlRootElement</code>注解来编组和反编组XML。</p>
<p>我们在下面列出了最重要的一些注解:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">XmlAccessorOrder</code>: 本注解控制类里的字段和属性在XML中出现的顺序。更多信息请看: <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlAccessorOrder.html">https://docs.oracle.com/javase/8/docs/…/XmlAccessOrder.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlAccessorType</code>: 表示一个元素是否可以被序列化。它可以和 <code class="language-plaintext highlighter-rouge">javax.xml.bind.annotation.XMLAccessType</code>一起使用。 更多信息请看: <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlAccessorType.html">https://docs.oracle.com/javase/8/docs/…/XmlAccessorType.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlAnyAttribute</code>: 映射一个元素到通配符(wildcard )属性的Map。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlAnyAttribute.html">https://docs.oracle.com/javase/8/docs/…/XmlAnyAttribute.html</a> .</li>
<li><code class="language-plaintext highlighter-rouge">XmlAnyElement</code>: 在没有映射被预定义时,作为反编组操作的一个缺省。 更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlAnyElement.html">https://docs.oracle.com/javase/8/docs/…/XmlAnyElement.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlAttribute</code>: 本注解是最基础和最常使用的一个。它映射一个Java元素(property, attribute, field) 到一个XML节点属性. 本教程中多个例子中用到了它。 更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlAttribute.html">https://docs.oracle.com/javase/8/docs/…/XmlAttribute.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlElement</code>: 使用name映射一个Java元素到XML节点。 更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElement.html">https://docs.oracle.com/javase/8/docs/…/XmlElement.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlElementRef</code>: 使用type (不同于上者, name被用来做映射)映射一个Java元素到一个XML节点 . 更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElementRef.html">https://docs.oracle.com/javase/8/docs/…/XmlElementRef.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlElementRefs</code>: 标记一个指向XmlElement 和JAXBElement所注解的类。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElementRefs.html">https://docs.oracle.com/javase/8/docs/…/XmlElementRefs.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlElements</code>: 这是一个包含多个XMLElement 注解的容器。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElements.html">https://docs.oracle.com/javase/8/docs/…/XmlElements.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlElementWrapper</code>: 它生成一个围绕XML结构的包装器,旨在和集合一起使用,本教程中我们看到了有不同的方式来处理集合。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElementWrapper.html">https://docs.oracle.com/javase/8/docs/…/XmlElementWrapper.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlEnum</code>: 提供emum到XML的映射。它和XmlEnumValue一起工作。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlEnum.html">https://docs.oracle.com/javase/8/docs/…/XmlEnum.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlEnumValue</code>:映射一个enum常量到一个XML元素。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlEnumValue.html">https://docs.oracle.com/javase/8/docs/…/XmlEnumValue.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlID</code>: 映射一个属性到XML id。更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlID.html">https://docs.oracle.com/javase/8/docs/…/XmlID.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlList</code>: 另一种在JAXB中处理list的方式。更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlList.html">https://docs.oracle.com/javase/8/docs/…/XmlList.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlMimeType</code>: 控制被注解的属性的表现形式。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlMimeType.html">https://docs.oracle.com/javase/8/docs/…/XmlMimeType.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlMixed</code>: 被注解的元素包含混合的内容。内容可以是文本或未知的(unknown)。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlMixed.html">https://docs.oracle.com/javase/8/docs/…/XmlMixed.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlRootElement</code>: 这可能是JAXB中使用最多的注解了。它用于映射一个类到XML元素。它基本上是每一个JAXB的入口点。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlRootElement.html">https://docs.oracle.com/javase/8/docs/…/XmlRootElement.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlSchema</code>: 映射一个package到XML命名空间。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlSchema.html">https://docs.oracle.com/javase/8/docs/…/XmlSchema.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlSchemaType</code>: 映射一个Java类型到一个内置的simple schema。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlSchemaType.html">https://docs.oracle.com/javase/8/docs/…/XmlSchemaType.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlSeeAlso</code>: 告诉JAXB在绑定被注解类时,去绑定其他类。这是必须的,因为Java很难列出一个类的所有子类,使用这种机制,你可以告诉JAXB在处理一个特定类的时候哪一个子类(或其他类)应该被绑定。更多信息请看 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlSeeAlso.html">https://docs.oracle.com/javase/8/docs/…/XmlSeeAlso.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlType</code>: 用于map一个类或enum到XML Schema中的一个type。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlType.html">https://docs.oracle.com/javase/8/docs/…/XmlType.html</a></li>
<li><code class="language-plaintext highlighter-rouge">XmlValue</code>: 允许map一个类到一个包含simpleContent 的XML Schema复杂类型或一个XML Schema的简单类型。更多信息请看<a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlValue.html">https://docs.oracle.com/javase/8/docs/…/XmlValue.html</a></li>
</ul>
<p>这是一个很长的列表,但是并不是所有的JAXB的注解。要查看JAXB的所有注解的列表,请查看package 的 <a href="https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/package-summary.html">summary</a>.</p>
<h2 id="六适配器adapters">六、适配器(Adapters)</h2>
<p>细心的读者,可能发现在前面编组的例子中,<code class="language-plaintext highlighter-rouge">birthday</code>是<code class="language-plaintext highlighter-rouge">LocalDate</code>类型,作为复杂类型,不能很好地被编组和反编组。这时需要一个适配器。</p>
<p><strong>DateAdapter.java:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package net.blogways.jaxb.example.adapter;
import java.time.LocalDate;
import javax.xml.bind.annotation.adapters.XmlAdapter;
public class DateAdapter extends XmlAdapter<String, LocalDate>
{
public LocalDate unmarshal( String date ) throws Exception
{
return LocalDate.parse( date );
}
public String marshal( LocalDate date ) throws Exception
{
return date.toString();
}
}
</code></pre></div></div>
<p>修改Person.java,添加<code class="language-plaintext highlighter-rouge">@XmlJavaTypeAdapter</code>注释,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@XmlJavaTypeAdapter( DateAdapter.class )
public void setBirthday(LocalDate birthday) {
this.birthday = birthday;
}
</code></pre></div></div>
<p>再次运行本文前面部分的编组程序,标准输出如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Person>
<birthday>2013-10-11</birthday>
<city>NanJing</city>
<firstName>net</firstName>
<lastName>blogways</lastName>
<postalCode>210000</postalCode>
</Person>
</code></pre></div></div>
<p>完美了,日期也可以显示出来了!</p>
<h2 id="七总结">七、总结</h2>
<p>好了,至此,你已经掌握了基本的JAXB的使用方法。可以编组、反编组、编写适配器,并且对相关注释也有了一个基本的了解,至少,你知道可以去哪查询都有哪些注释了。:)</p>
<p>后面,我们将会继续介绍关于JAXB的一些复杂的应用。比如:</p>
<ul>
<li>介绍如何通过XML Schema文件来校验XML文件格式的正确与否</li>
<li>介绍JAXBContext.newInstance的逻辑</li>
<li>介绍对XML文件中名字空间的操作</li>
<li>介绍对XML文件中processing instruction的操作</li>
<li>介绍一些和JAXB有关的常见工具的使用</li>
</ul>
<p>本文,所使用的示例完整代码,可以在本系列文章完结时提供。</p>
<h2 id="八参考文献">八、参考文献</h2>
<ol>
<li><a href="http://zh.wikipedia.org/zh/JAXB">http://zh.wikipedia.org/zh/JAXB</a></li>
<li><a href="http://en.wikipedia.org/wiki/Java_Architecture_for_XML_Binding">http://en.wikipedia.org/wiki/Java_Architecture_for_XML_Binding</a></li>
<li><a href="http://stackoverflow.com/questions/7709928/jaxb-vs-dom-and-sax">http://stackoverflow.com/questions/7709928/jaxb-vs-dom-and-sax</a></li>
<li><a href="http://stackoverflow.com/questions/607141/what-is-jaxb-and-why-would-i-use-it">http://stackoverflow.com/questions/607141/what-is-jaxb-and-why-would-i-use-it</a></li>
<li><a href="http://www.javacodegeeks.com/2014/12/jaxb-tutorial-xml-binding.html">http://www.javacodegeeks.com/2014/12/jaxb-tutorial-xml-binding.html</a></li>
<li><a href="http://www.javacodegeeks.com/zh-hans/2015/04/%E7%94%A8%E4%BA%8Ejava%E5%92%8Cxml%E7%BB%91%E5%AE%9A%E7%9A%84jaxb%E6%95%99%E7%A8%8B.html">http://www.javacodegeeks.com/zh-hans/2015/04/%E7%94%A8%E4%BA%8Ejava%E5%92%8Cxml%E7%BB%91%E5%AE%9A%E7%9A%84jaxb%E6%95%99%E7%A8%8B.html</a></li>
</ol>
javax.xml.parsers使用简介及源码探究:DOM与SAX
2015-05-05T00:00:00+00:00
http://www.blogways.net/blog/2015/05/05/javax-xml-parsers
<h2 id="一简介">一、简介</h2>
<p>Java SE 平台提供的 XML 处理主要包括两个功能:JAXP(XML 处理,Java Architecture XML Processing)和 JAXB(XML 绑定,Java Architecture XML Binding)。</p>
<h3 id="11-关于jaxb">1.1 关于JAXB</h3>
<p>JAXB 则是负责将 XML 文件和 Java 对象绑定,在新版 JDK 中,被大量的使用在 Web 服务技术中。</p>
<h3 id="12-关于jaxp">1.2 关于JAXP</h3>
<p>JAXP 包括 SAX 框架 —— 遍历元素,做出处理;DOM 框架 —— 构造 XML 文件的树形表示;StAX 框架 —— 拖拽方式的解析;XSLT 框架 —— 将 XML 数据转换成其他格式。</p>
<ul>
<li>
<p><strong>SAX 框架(Simple API for XML)</strong></p>
<p>SAX 全称 Simple API for XML,该框架使用了事件处理机制来处理 XML 文件。</p>
</li>
<li>
<p><strong>DOM 框架(Document Object Model)</strong></p>
<p>DOM 框架的全称是 Document Object Model。顾名思义,这个框架会建立一个对象模型。针对每个节点,以及节点之间的关系在内存中生成一个树形结构。这个特点与 SAX 框架截然相反。需要注意的是,DOM 框架提供的对象树模型与我们通常理解的 XML 文件结构树模型是有一定的区别的。</p>
</li>
<li>
<p><strong>StAX 框架(Streaming API for XML)</strong></p>
<p>SAX 框架的缺点是不能记录正在处理元素的上下文。但是优点是运行时占内存空间比较小,效率高。DOM 框架由于在处理 XML 时需要为其构造一棵树,所以特点正好相反。StAX 框架出现于 Java SE 6 中,它的设计目标就是要结合 SAX 框架和 DOM 框架的优点。既要求运行时效率,也要求保持元素的上下文状态。</p>
</li>
<li>
<p><strong>XSLT 数据转换框架(The Extensible Stylesheet Language Transformations APIs)</strong></p>
<p>一般来说 XML 文件格式被认为是一种很好的数据交换格式。于是 Java SE 6 SDK 基于以上介绍的三种 XML 处理机制,提供了一个 XML 转换框架。XSLT 框架负责进行转换 —— 包括将 XML 文件转换成其他形式如 HTML,和将其他形式的文件转换成 XML 文件。更进一步说,这个框架可以接受 DOM 作为其输入和输出;可以接受 SAX 解析器作为输入或者产生 SAX 事件作为输出;可以接受 I/O Stream 作为输入和输出;当然也支持用户自定义形式的输入和输出。</p>
</li>
</ul>
<p>===</p>
<p>JDK中<code class="language-plaintext highlighter-rouge">javax.xml.parsers</code>包下是SAX和DOM的实现。下面,我们将简单介绍该包的使用。</p>
<h2 id="二对比sax与dom">二、对比(SAX与DOM)</h2>
<ol>
<li>
<p>DOM思想:将整个XML加载内存中,形成文档对象,所以对XML操作都对内存中文档对象进行。</p>
</li>
<li>
<p>SAX思想:一边解析,一边处理,一边释放内存资源。不允许在内存中保留大规模XML数据。</p>
</li>
<li>DOM和SAX的区别:
<ul>
<li>DOM:支持回写,会将整个XML载入内存,以树形结构方式存储;XML比较复杂的时候,或者当你需要随机处理文档中数据的时候不建议使用</li>
<li>SAX:相比DOM是一种更为轻量级的方案;无法在读取过程中修改XML数据</li>
</ul>
</li>
<li>解析速度: SAX > StAX > DOM</li>
</ol>
<h2 id="三使用示例">三、使用示例</h2>
<h3 id="31-staffsxml">3.1 staffs.xml</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0"?>
<company>
<staff>
<firstname>yong</firstname>
<lastname>mook kim</lastname>
<nickname>mkyong</nickname>
<salary>100000</salary>
</staff>
<staff>
<firstname>low</firstname>
<lastname>yin fong</lastname>
<nickname>fong fong</nickname>
<salary>200000</salary>
</staff>
</company>
</code></pre></div></div>
<h3 id="32-dom的使用示例">3.2 DOM的使用示例</h3>
<p>DOM的使用,分为两步:</p>
<ol>
<li>将XML文件解析为Document对象</li>
<li>对Document对象进行处理</li>
</ol>
<p><strong>解析代码,主体如下:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main(String[] args) throws Exception {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document document = builder.parse(new InputSource(new FileInputStream("staffs.xml")));
forEachNode(document);
}
</code></pre></div></div>
<p><strong>其中,对Document对象的处理方法如下:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>private static void forEachNode(Document doc) {
NodeList nodeList = doc.getDocumentElement().getChildNodes();
for(int i=0; i<nodeList.getLength(); ++i) {
Node staff = nodeList.item(i);
if (staff instanceof Element) {
NodeList props = staff.getChildNodes();
for (int j=0; j<props.getLength(); ++j) {
Node prop = props.item(j) ;
if (prop instanceof Element) {
String content = prop.getLastChild().getTextContent().trim();
switch(prop.getNodeName()) {
case "firstname":
System.out.println("firstname:"+content);
break;
case "lastname":
System.out.println("lastname:"+content);
break;
case "nickname":
System.out.println("nickname:"+content);
break;
case "salary":
System.out.println("salary:"+content);
break;
}
}
}
System.out.println();
}
}
}
</code></pre></div></div>
<h3 id="33-sax的使用示例">3.3 SAX的使用示例</h3>
<p>SAX的使用,在于需要自定义一个<code class="language-plaintext highlighter-rouge">org.xml.sax.ContentHandler</code>处理器。</p>
<p><strong>解析的主体代码如下:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main(String[] args) throws Exception {
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware( true );
XMLReader xmlReader = spf.newSAXParser().getXMLReader();
xmlReader.setContentHandler(new StaffHandler());
xmlReader.parse(new InputSource(new FileInputStream("staffs.xml")));
}
</code></pre></div></div>
<p><strong>自定义的处理器 StaffHandler:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class StaffHandler extends DefaultHandler {
boolean bfname = false;
boolean blname = false;
boolean bnname = false;
boolean bsalary = false;
public void startElement (String uri, String localName,
String qName, Attributes attributes) throws SAXException {
System.out.println("Start Element :" + qName);
if (qName.equalsIgnoreCase("FIRSTNAME")) {
bfname = true;
}
if (qName.equalsIgnoreCase("LASTNAME")) {
blname = true;
}
if (qName.equalsIgnoreCase("NICKNAME")) {
bnname = true;
}
if (qName.equalsIgnoreCase("SALARY")) {
bsalary = true;
}
}
public void endElement(String uri, String localName,
String qName) throws SAXException {
System.out.println("End Element :" + qName);
}
public void characters(char ch[], int start, int length) throws SAXException {
if (bfname) {
System.out.println("First Name : " + new String(ch, start, length));
bfname = false;
}
if (blname) {
System.out.println("Last Name : " + new String(ch, start, length));
blname = false;
}
if (bnname) {
System.out.println("Nick Name : " + new String(ch, start, length));
bnname = false;
}
if (bsalary) {
System.out.println("Salary : " + new String(ch, start, length));
bsalary = false;
}
}
}
</code></pre></div></div>
<h2 id="四关于-saxparserfactory-和-documentbuilderfactory">四、关于 SAXParserFactory 和 DocumentBuilderFactory</h2>
<h3 id="41-相似的代码相同的设计模式">4.1 相似的代码,相同的设计模式</h3>
<p>对比前面的代码,可以发现两者主体部分代码,十分相似,使用了相同的设计模式。</p>
<p>再看一下:</p>
<p><strong>DOM:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//获取工厂实例
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
//由工厂实例,获得DocumentBuilder
DocumentBuilder builder = factory.newDocumentBuilder();
//解析ing
Document document = builder.parse(new InputSource(new FileInputStream("staffs.xml")));
</code></pre></div></div>
<p><strong>SAX:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//获取工厂实例
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware( true );
//由工厂实例,获得XMLReader
XMLReader xmlReader = spf.newSAXParser().getXMLReader();
xmlReader.setContentHandler(...);
//解析ing
xmlReader.parse(new InputSource(new FileInputStream("staffs.xml")));
</code></pre></div></div>
<p>在这里,我想探讨一下两者的工厂实例是如何获取的。</p>
<p>查了下jdk 1.8 源码。</p>
<h3 id="42-两者工厂实例的相同实现">4.2 两者工厂实例的相同实现</h3>
<p>两者工厂实例,都是由<code class="language-plaintext highlighter-rouge">FactoryFinder.find</code>方法实现</p>
<p><strong>javax.xml.parsers.DocumentBuilderFactory:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static DocumentBuilderFactory newInstance() {
return FactoryFinder.find(
/* The default property name according to the JAXP spec */
DocumentBuilderFactory.class, // "javax.xml.parsers.DocumentBuilderFactory"
/* The fallback implementation class name */
"com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl");
}
</code></pre></div></div>
<p><strong>javax.xml.parsers.SAXParserFactory:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static SAXParserFactory newInstance() {
return FactoryFinder.find(
/* The default property name according to the JAXP spec */
SAXParserFactory.class,
/* The fallback implementation class name */
"com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl");
}
</code></pre></div></div>
<h3 id="43-factoryfinderfind-的逻辑">4.3 FactoryFinder.find 的逻辑</h3>
<p>继续阅读源码,获知<code class="language-plaintext highlighter-rouge">FactoryFinder.find</code>的逻辑包括两个部分:</p>
<ol>
<li>查找定位工厂类</li>
<li>根据查到的工厂类,生成最终实例</li>
</ol>
<p>如何查找定位工厂类?按下面顺序,使用找到的第一个:</p>
<ol>
<li>使用系统属性。对DocumentBuilderFactory而言,是<code class="language-plaintext highlighter-rouge">javax.xml.parsers.DocumentBuilderFactory</code>;对SAXParserFactory而言,是<code class="language-plaintext highlighter-rouge">javax.xml.parsers.SAXParserFactory</code>。所以,你可以在启动java时,使用<code class="language-plaintext highlighter-rouge">-Djavax.xml.parsers.DocumentBuilderFactory=...</code>来设置DocmentBuilder的工厂类,SAXParser类似。</li>
<li>读取JRE目录下的属性文件 “lib/jaxp.properties”,其中属性名分别为<code class="language-plaintext highlighter-rouge">javax.xml.parsers.DocumentBuilderFactory</code>和<code class="language-plaintext highlighter-rouge">javax.xml.parsers.SAXParserFactory</code>。</li>
<li>使用jar包的<code class="language-plaintext highlighter-rouge">Service Provider</code>机制,加载工厂类。也就是说,查找所有加载的jar包中<code class="language-plaintext highlighter-rouge">META-INF/services</code>目录下的配置文件,文件名分别为<code class="language-plaintext highlighter-rouge">javax.xml.parsers.DocumentBuilderFactory</code>和<code class="language-plaintext highlighter-rouge">javax.xml.parsers.SAXParserFactory</code>。文件的内容就是该jar包内提供的类实例。比如<code class="language-plaintext highlighter-rouge">xercesImple-2.x.x.jar</code>提供的<code class="language-plaintext highlighter-rouge">org.apache.xerces.jaxp.SAXParserFactoryImpl</code>.</li>
<li>使用系统默认的工厂类。分别为:<code class="language-plaintext highlighter-rouge">com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl</code>和<code class="language-plaintext highlighter-rouge">com.sun.org.apache.xerces.internal.jaxp.SAXParserFactoryImpl</code>。</li>
</ol>
<h2 id="五总结">五、总结</h2>
<p>好了,至此,你已经掌握了JDK中自带的DOM和SAX的基本使用方法了。</p>
<p>本文,所使用的示例代码,可以从<a href="/attachment/src-jaxp20150504.zip">这里</a>获取。</p>
<h2 id="八参考文献">八、参考文献</h2>
<ol>
<li><a href="https://www.ibm.com/developerworks/cn/java/j-lo-jse67/">https://www.ibm.com/developerworks/cn/java/j-lo-jse67/</a></li>
<li><a href="http://www.cnblogs.com/evanliu/p/3665113.html">http://www.cnblogs.com/evanliu/p/3665113.html</a></li>
<li><a href="http://www.mkyong.com/java/how-to-read-xml-file-in-java-sax-parser/">http://www.mkyong.com/java/how-to-read-xml-file-in-java-sax-parser/</a></li>
<li><a href="http://www.javacodegeeks.com/2013/05/parsing-xml-using-dom-sax-and-stax-parser-in-java.html">http://www.javacodegeeks.com/2013/05/parsing-xml-using-dom-sax-and-stax-parser-in-java.html</a></li>
</ol>
Solr 应用(三)-查询数据
2015-05-04T00:00:00+00:00
http://www.blogways.net/blog/2015/05/04/solr-usage-3
<p>前两节主要介绍了一些基本的概念以及简单的查询演示,大家可能有一些大概的印象了,这个时候想要进一步了解一下,尤其是那个 web 控制台查询操作的时候太混乱了,每个条件都代表什么意思都不知道,这节主要介绍查询数据相关的一些概念以及运算、语法。</p>
<h3 id="常用查询参数说明">常用查询参数说明</h3>
<table class="table table-bordered table-striped table-condensed">
<thead>
<th width="20%">Field</td>
<th width="80%">Description</td>
</thead>
<tr>
<td>Request-handler(qt)</td>
<td>指定请求的查询处理。如果没有指定一个查询处理,Solr进程将以标准的查询处理响应。</td>
</tr>
<tr>
<td>q</td>
<td>查询字符串,这个是必须的。如果查询所有*:* ,根据指定字段查询(Name:张三 AND Address:北京)</td>
</tr>
<tr>
<td>fq</td>
<td>(filter query)过虑查询,作用:在q查询符合结果中同时是fq查询符合的,例如:q=Name:张三&fq=CreateDate:[20081001 TO 20091031],找关键字mm,并且CreateDate是20081001</td>
</tr>
<tr>
<td>fl</td>
<td>指定返回那些字段内容,用逗号或空格分隔多个。</td>
</tr>
<tr>
<td>start</td>
<td>返回第一条记录在完整找到结果中的偏移位置,0开始,一般分页用。</td>
</tr>
<tr>
<td>rows</td>
<td>指定返回结果最多有多少条记录,配合start来实现分页。</td>
</tr>
<tr>
<td>sort</td>
<td>排序,格式:sort=<field name="">+<desc|asc>[,<field name="">+<desc|asc>]… 。示例:(score desc, price asc)表示先 “score” 降序, 再 “price” 升序,默认是相关性降序。 </td>
</tr>
<tr>
<td>wt</td>
<td>(writer type)指定输出格式,可以有 xml, json, php, phps。 </td>
</tr>
<tr>
<td>indent</td>
<td>是否需要将请求响应的结果以缩进的方式显示出来,使结果更具可读性,类似格式化显示 xml、json 等功能</td>
</tr>
<tr>
<td>debugQuery</td>
<td>是否需要显示调试信息,包括每个文档返回的“解释信息”,以便理解和管理程序的执行过程</td>
</tr>
<tr>
<td>dismax</td>
<td>是否启用 Dismax 查询解析器</td>
</tr>
<tr>
<td>edismax</td>
<td>是否启用扩展查询解析器</td>
</tr>
<tr>
<td>fl</td>
<td>表示索引显示那些field( *表示所有field,如果想查询指定字段用逗号或空格隔开(如:Name,SKU,ShortDescription或Name SKU ShortDescription【注:字段是严格区分大小写的】)) </td>
</tr>
<tr>
<td>q.op</td>
<td>表示q 中 查询语句的 各条件的逻辑操作 AND(与) OR(或)</td>
</tr>
<tr>
<td>hl</td>
<td>是否高亮 ,如hl=true</td>
</tr>
<tr>
<td>hl.fl</td>
<td>高亮field ,hl.fl=Name,SKU</td>
</tr>
<tr>
<td>hl.snippets</td>
<td>默认是1,这里设置为3个片段</td>
</tr>
<tr>
<td>hl.simple.pre</td>
<td>高亮前面的格式</td>
</tr>
<tr>
<td>hl.simple.post</td>
<td>高亮后面的格式</td>
</tr>
<tr>
<td>facet</td>
<td>是否启动统计</td>
</tr>
<tr>
<td>facet.field</td>
<td>统计field</td>
</tr>
<tr>
<td>spatial</td>
<td>是否启用位置数据,该功能在地理或者空间搜索中使用</td>
</tr>
<tr>
<td>spellcheck</td>
<td>是否启用拼写检查,提供在线的模糊匹配或者专业术语匹配等建议</td>
</tr>
</table>
### Solr运算符
(1) “:” 指定字段查指定值,如返回所有值*:*
(2) “?” 表示单个任意字符的通配
(3) “*” 表示多个任意字符的通配(不能在检索的项开始使用*或者?符号)
(4) “~” 表示模糊检索,如检索拼写类似于”roam”的项这样写:roam~将找到形如foam和roams的单词;roam~0.8,检索返回相似度在0.8以上的记录。
(5) 邻近检索,如检索相隔10个单词的”apache”和”jakarta”,”jakarta apache”~10
(6) “^” 控制相关度检索,如检索jakarta apache,同时希望去让”jakarta”的相关度更加好,那么在其后加上”^”符号和增量值,即jakarta^4 apache
(7) 布尔操作符AND、||
(8) 布尔操作符OR、&&
(9) 布尔操作符NOT、!、- (排除操作符不能单独与项使用构成查询)
(10) “+” 存在操作符,要求符号”+”后的项必须在文档相应的域中存在
(11) ( ) 用于构成子查询
(12) [] 包含范围检索,如检索某时间段记录,包含头尾,date:[200707 TO 200710]
(13) {} 不包含范围检索,如检索某时间段记录,不包含头尾,date:{200707 TO 200710}
(14) / 转义操作符,特殊字符包括+ - && || ! ( ) { } [ ] ^ ” ~ * ? : /
*注:①“+”和”-“表示对单个查询单元的修饰,and 、or 、 not 是对两个查询单元是否做交集或者做差集还是取反的操作的符号*
比如:AB:china +AB:america ,表示的是AB:china忽略不计可有可无,必须满足第二个条件才是对的,而不是你所认为的必须满足这两个搜索条件
如果输入:AB:china AND AB:america ,解析出来的结果是两个条件同时满足,即+AB:china AND +AB:america或+AB:china +AB:america
总而言之,查询语法: 修饰符 字段名:查询关键词 AND/OR/NOT 修饰符 字段名:查询关键词
### Solr查询语法
(1) 最普通的查询,比如查询姓张的人( Name:张),如果是精准性搜索相当于SQL SERVER中的LIKE搜索这需要带引号(""),比如查询含有北京的(Address:"北京")
(2) 多条件查询,注:如果是针对单个字段进行搜索的可以用(Name:搜索条件加运算符(OR、AND、NOT) Name:搜索条件),比如模糊查询( Name:张 OR Name:李 )单个字段多条件搜索不建议这样写,一般建议是在单个字段里进行条件筛选,如( Name:张 OR 李),多个字段查询(Name:张 + Address:北京 )
(3) 排序,比如根据姓名升序(Name asc),降序(Name desc)
本文参考了 `http://blog.csdn.net/cgddm/article/details/44303203` 这篇文章中的 `Solr查询数据` 部分来写的,参数部分增加了一些官方文档里面多出来参数
</field></field></td></tr></th></th></thead></table>
MongoDB 安装与分布式部署
2015-05-04T00:00:00+00:00
http://www.blogways.net/blog/2015/05/04/mongodb-install-and-distribution-deploy
<h2 id="一概述">一、概述</h2>
<p>NoSQL数据库与传统的关系型数据库相比,它具有操作简单、完全免费、源码公开、随时下载等特点,并可以用于各种商业目的。这使NoSQL产品广泛应用于各种大型门户网站和专业网站,大大降低了运营成本。</p>
<p>MongoDB的文档模型自由灵活,可以让你在开发过程中畅顺无比。对于大数据量、高并发、弱事务的互联网应用,MongoDB可以应对自如。MongoDB内置的水平扩展机制提供了从百万到十亿级别的数据量处理能力,完全可以满足Web2.0和移动互联网的数据存储需求,其开箱即用的特性也大大降低了中小型网站的运维成本。</p>
<h2 id="二安装mongodb">二、安装MongoDB</h2>
<p>到官网下载对应的版本<a href="http://www.mongodb.org/downloads">http://www.mongodb.org/downloads</a>,下载完成后解压即可使用。也可以下载源码自己编译<a href="https://github.com/mongodb/mongo">https://github.com/mongodb/mongo</a>。</p>
<p>下载编译好的文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/tools
wget -c https://fastdl.mongodb.org/osx/mongodb-osx-x86_64-3.0.2.tgz
# 等待下载完成
tar zxf mongodb-osx-x86_64-3.0.2
ln -s mongodb-osx-x86_64-3.0.2 mongodb
echo "export MONGODB_HOME=~/tools/mongodb" >> ~/.bash_profile && source ~/.bash_profile 命令行启动 MongoDB 后台驻留程序:
mongod --dbpath /data/mongodb/db --logpath /data/mongodb/logs/mongod.log --nojournal --fork --port 27037 后台mongodb 主流程序,监听本机 27037 号端口,数据库文件保存在`/data/mongodb/db`目录下,日志文件输出到`/data/mongodb/logs/mongod.log`文件下,并禁用`journaling`( 启用 journaling 后,会将数据库相关操作等以日志的形式保存下来,会占用较大的硬盘空间和虚拟内存,64位mongodb,默认开启;32位版本默认关闭 )。
</code></pre></div></div>
<p>通过 <code class="language-plaintext highlighter-rouge">mongo</code> 连接 MongoDB 服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#mongo --help 可以查看mongo命令介绍
mongo test # 连接本地,27017端口的test数据库
mongo 192.168.20.1/test # 连接192.168.20.1主机,27017端口的数据库test
mongo 192.168.20.1:66666/test #连接192.168.20.1主角,66666端口的数据库test
</code></pre></div></div>
<h3 id="三常用命令">三、常用命令</h3>
<p>一般数据操作都有一些常用的客户端命令( 此处为:mongo shell ),MongoDB 的基本存储单元是集合 (collection),想到于关系数据库mysql、oracle或sql server的表;</p>
<h4 id="查询数据库">查询数据库</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>> show dbs # or show databases
admin (empty)
local 0.078GB
test 0.078GB #### 切换数据库
> use test
switched to db test #### 查看数据集合
> show collections
system.indexes
users #### 集合操作 集合的基本操作命令如下所示:
db.<collectionName>.<operate>(<argument>)
</code></pre></div></div>
<p>常用的集合操作有:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>查找记录 ( find )
查询记录数 ( count )
插入记录 ( insert )
删除记录 ( remove )
更新尽量 ( update ) 这所有的集合操作接受的参数都是通过 JSON 的形式传递的:
#查询所有记录
db.users.find()
# 查找 users 集合中,姓名为 小王的记录
db.users.find({name: '小王'}) 因为MongoDB是 NoSQL 数据库,因此不需要预定义表结构,直接通过 insert 插入数据即可。
</code></pre></div></div>
<h3 id="四分布式部署">四、分布式部署</h3>
<p>本机部署3个shard、1个mongos和3个 configsvr;</p>
<ul>
<li>Shard1:27037</li>
<li>Shard2:27038</li>
<li>Shard3:27039</li>
<li>Mongos:27017</li>
<li>Config1:27027</li>
<li>Config2:27028</li>
<li>COnfig3:27029</li>
</ul>
<p>在<code class="language-plaintext highlighter-rouge">/data/mongodb</code>目录下创建文件夹<code class="language-plaintext highlighter-rouge">shard0001</code>,<code class="language-plaintext highlighter-rouge">shard0002</code>,<code class="language-plaintext highlighter-rouge">shard0003</code>,<code class="language-plaintext highlighter-rouge">config0001</code>,<code class="language-plaintext highlighter-rouge">config0002</code><code class="language-plaintext highlighter-rouge">config0003</code>和<code class="language-plaintext highlighter-rouge">logs</code>,分别用以保持分片数据、配置服务器数据和日志文件</p>
<h4 id="启动配置服务器">启动配置服务器</h4>
<p>首先启动3个配置服务器,测试环境使用一个配置服务器也可以,但是生产环境必须是3个,MongoDB最多支持3个配置服务器;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongod --configsvr --port 27027 --dbpath /data/mongodb/config0001 --logpath /data/mongodb/logs/config0001.log --fork
mongod --configsvr --port 27028 --dbpath /data/mongodb/config0001 --logpath /data/mongodb/logs/config0001.log --fork
mongod --configsvr --port 27029 --dbpath /data/mongodb/config0001 --logpath /data/mongodb/logs/config0001.log --fork 运行上诉命令将启动3个配置服务器,其中的参数介绍:
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">--configsvr</code>,指定启动的是分配操作的配置服务器,该参数仅仅指定了默认的端口号( 27019 )和默认数据存放位置( /data/configdb ),并没有其它的任何用途,可以显示指定以覆盖默认设置</li>
<li><code class="language-plaintext highlighter-rouge">--port [端口号]</code>,指定该配置服务器监听的端口号,如果设置了<code class="language-plaintext highlighter-rouge">--configsvr</code>参数则默认为<code class="language-plaintext highlighter-rouge">27019</code>;</li>
<li><code class="language-plaintext highlighter-rouge">--dbpath [数据存放路径]</code>,指定配置服务器存放文件目录,如果设置了<code class="language-plaintext highlighter-rouge">--configsvr</code>参数则默认为<code class="language-plaintext highlighter-rouge">/data/configdb</code>;</li>
<li><code class="language-plaintext highlighter-rouge">--logpath [日志存放路径]</code>,指定配置服务器的日志存放地;</li>
<li><code class="language-plaintext highlighter-rouge">--fork</code>,指定该配置服务器位后台驻留程序;</li>
</ul>
<h4 id="启动路由服务器">启动路由服务器</h4>
<p>路由服务器可以根据需求启动一个或多个,启动个数没有限制,作为MongoDB集群的访问点,提供与未分配中<code class="language-plaintext highlighter-rouge">mongod</code>类似的功能,是MongoDB集群对外的“接口”;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongos --port 27017 --configdb localhost:27027,localhost:27028,localhost:27029 logpath /data/mongodb/logs/mongos.log --fork 命令、参数介绍:
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">mongos</code>,此处启动的命令是<strong><em>mongos</em></strong>;</li>
<li><code class="language-plaintext highlighter-rouge">--configdb</code>,指定配置服务器的主机/IP地址 + 端口号,只能是3个或1个,多个之间通过 <strong><em><code class="language-plaintext highlighter-rouge">,</code></em></strong>分割;</li>
</ul>
<h4 id="启动分片">启动分片</h4>
<p>分片实例其实就是通过命令<code class="language-plaintext highlighter-rouge">mongod</code>启动的 MongoDB 实例;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongod --port 27037 --dbpath /data/mongodb/shard0001 --logpath /data/mongodb/logs/shard0001.log --fork
mongod --port 27038 --dbpath /data/mongodb/shard0002 --logpath /data/mongodb/logs/shard0002.log --fork
mongod --port 27039 --dbpath /data/mongodb/shard0003 --logpath /data/mongodb/logs/shard0003.log --fork 至此所有分布式启动的后台程序都已启动完毕,后面就是分片操作了。
</code></pre></div></div>
<h4 id="mongo-shell-分片">Mongo Shell 分片</h4>
<p>在按照上诉顺序启动完所有MongoDB 实例后,打开一个命令行参数,想前面说的那样,通过<code class="language-plaintext highlighter-rouge">mongo</code>命令连接路由服务器;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongo localhost:27017/admin
# or
$ mongo admin
MongoDB shell version: 2.6.7
connecting to: admin
mongos> 添加分片,MongoDB 会将启动的分片的信息保持到配置服务器上;
mongos> sh.addShard('localhost:27037')
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> sh.addShard('localhost:27038')
{ "shardAdded" : "shard0001", "ok" : 1 }
mongos> sh.addShard('localhost:27039')
{ "shardAdded" : "shard0002", "ok" : 1 } 通过`sh.status()` 查看分片结果:
mongos> sh.status()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 4,
"minCompatibleVersion" : 4,
"currentVersion" : 5,
"clusterId" : ObjectId("55471025f722cd42a930ecee")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:27037" }
{ "_id" : "shard0001", "host" : "localhost:27038" }
{ "_id" : "shard0002", "host" : "localhost:27039" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" } 启动数据库分片;
# 格式 : sh.enableSharding(<databaseName>)
mongos> sh.enableSharding('test')
{ "ok" : 1 } 当启动数据库*** test ***的分片过后,再通过`sh.status()`命令查看,可发现相应的改变:
# ... 前面相同
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" } 发现在***databases***一项中,对应的多了一向分片的*** test ***数据库,其中:
</code></pre></div></div>
<ul>
<li><code class="language-plaintext highlighter-rouge">_id</code>,表示分片的数据库名,此处为<em>** test **</em>;</li>
<li><code class="language-plaintext highlighter-rouge">partitioned</code>,表示是否启动分片,为 <em>** true **</em>表示启用分片;</li>
<li><code class="language-plaintext highlighter-rouge">primary</code>,表示数据库存放的“主片”,一旦主片存放满了,或达到了分片设置的阈值,才会将数据存放到其它片,否则将一直存放于该主片上。</li>
</ul>
<p>至此 MongoDB 的分布式部署完成。</p>
<h3 id="五集合分片">五、集合分片</h3>
<p>在集合分片之前,说明单片( 不分片 )的局限性:</p>
<ol>
<li>单台主机的硬盘资源限制,一台主机的硬盘空间是一定的,当存放数据量达到硬盘的容量极限后,就只能为主机增加硬盘空间;</li>
<li>入库速度的限制,如果某个功能实现每分每秒有大量的数据插入 (如:日志收集、网页抓取等),单台主机入库的速度存在则极限( 后面会介绍 ),一旦入库请求操作此极限,入库速度会急剧下降;</li>
</ol>
<p>由此,分片的好处就不言而喻了,它可以进行水平扩展,一旦现行的设备无法满足需求之后,只需要添加分片即可,添加之后 MongoDB 会根据设置<strong><em>自动平衡</em></strong>数据( 将数据移动到新插入的分片中 );</p>
<p>还有就是如果集合分片之后,入库将会是分布式方式入库,<em>**mongos **</em>会将所有的入库请求,根据设置分配到各个分片上,降低耽搁分片入库的极限,使得一些对入库要求很高的的程序能稳定运行;</p>
<p>有得必有失,分片增加的入库速度,相应的查询更新速度就有所降低,如果不合理的查询( 没有提供分配字段 ),将导致遍历整个数据集合,这里的不合理查询一般都是查询条件中不包含分片的字段,MongoDB 则无法定位查询在哪个分配上进行,进而导致遍历整个集合。</p>
<h4 id="range-分片">Range 分片</h4>
<p>根据给定的列( 如:age ),按分片数的范围分片,如此处分为三片,则分配范围:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>minKey ~ 30 30 ~ 60 60 ~ maxKey
shard0001 shard0002 shard0003 年龄在`minKey ~ 30`之间的信息,将会保存在分片***shard0001***上,`30 ~ 60`的则会保持在***shard0002***上,依次类推。
</code></pre></div></div>
<p>分片方式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># sh.shardCollection('<database>.<collection>', {'colName' : 1/-1})
sh.shardCollection('test.users', {'age': 1})
{ "collectionsharded" : "test.users", "ok" : 1 } 可以通过`db.users.stats()`来查看分片过后集合的状态信息,因为是按范围分布的,相同范围之内的存放于同一个分片中,要提升入库速度的话,必须选择合适的分片依据,防止同一时间段内入口同一范围的数据;
</code></pre></div></div>
<p>可以通过设置多个分片健来解决,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sh.shardCollections('test.users', {'age': 1, 'name': 1}) 所以合理选择分片Key很重要
</code></pre></div></div>
<h4 id="hash-分片">Hash 分片</h4>
<p>顾名思义,Hash 分片就是将数据根据分配字段 Hash 分布到各个片中,好处是即使同一时刻分配字段连续递增,入库结果基本也是均衡分配的;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Hash Function
_________________|_____________________
| | |
shard0001 shard0002 shard0003 入库记录会经过***mongos***的Hash 函数的处理,将不同的记录分发到各个分片中入库。
</code></pre></div></div>
<p>分片方式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># sh.shardCollection('<database>.<collection>', {'age': 'hashed'})
mongos> sh.shardCollection('test.info', {'phone': 'hashed'})
{ "collectionsharded" : "test.info", "ok" : 1 } 分片信息`db.info.stats()`:
mongos> db.info.stats()
{
"sharded" : true,
"systemFlags" : 1,
"userFlags" : 1,
"ns" : "test.info",
"count" : 0,
"numExtents" : 3,
"size" : 0,
"storageSize" : 24576,
"totalIndexSize" : 49056,
"indexSizes" : {
"_id_" : 24528,
"phone_hashed" : 24528
},
"avgObjSize" : 0,
"nindexes" : 2,
"nchunks" : 6,
"shards" : {
"shard0000" : {
"ns" : "test.info",
"count" : 0,
"size" : 0,
"storageSize" : 8192,
"numExtents" : 1,
"nindexes" : 2,
"lastExtentSize" : 8192,
"paddingFactor" : 1,
"systemFlags" : 1,
"userFlags" : 1,
"totalIndexSize" : 16352,
"indexSizes" : {
"_id_" : 8176,
"phone_hashed" : 8176
},
"ok" : 1
},
"shard0001" : {
...
},
"shard0002" : {
...
}
},
"ok" : 1
} 现在再去插入一些记录看下,所有的记录分别被插入到了不同的分当中去了。
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p></br></p>
<p><strong><em>分片部分完成,后续还有 MongoDB 分片等的,根据官方文档的详细介绍。</em></strong></p>
Solr 应用(二)-初次应用体验
2015-04-30T00:00:00+00:00
http://www.blogways.net/blog/2015/04/30/solr-usage-2
<h2 id="solr-初次应用体验">Solr 初次应用体验</h2>
<p>前一节主要介绍了 Solr5.0 的简介以及安装部署,本次主要介绍如何使用 Solr 做一些简单的搜索任务</p>
<h3 id="创建搜索实例">创建搜索实例</h3>
<hr />
<h4 id="命令创建实例">命令创建实例</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bin/solr create -c test
</code></pre></div></div>
<h4 id="查看创建可选参数">查看创建可选参数</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bin/solr create -help
</code></pre></div></div>
<hr />
<p>Web 控制台创建</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Core Admin / Add Core
</code></pre></div></div>
<p><img src="/images/solr-2-1.png" alt="Web 控制台创建索引" /></p>
<p>这里我们创建一个 test 测试实例</p>
<hr />
<h3 id="导入测试文档">导入测试文档</h3>
<p>导入一些 solr 自带的 json 测试文档数据</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bin/post -c test example/exampledocs/*.json
SimplePostTool version 5.0.0
Posting files to [base] url http://localhost:8983/solr/gettingstarted/update...
Entering auto mode. File endings considered are
xml,json,csv,pdf,doc,docx,ppt,pptx,xls,xlsx,odt,odp,ods,ott,otp,ots,rtf,htm,html,txt,log
POSTing file books.json (application/json) to [base]
1 files indexed.
COMMITting Solr index changes to http://localhost:8983/solr/gettingstarted/update...
Time spent: 0:00:00.450
</code></pre></div></div>
<h3 id="web-控制台搜索体验">Web 控制台搜索体验</h3>
<p>例如:查询作者为 <code class="language-plaintext highlighter-rouge">Rick Riordan</code> 所有的书</p>
<p><img src="/images/solr-2-2.png" alt="Web 控制台 query 测试" /></p>
<p>http://localhost:8983/solr/test/select?q=author%3A%22Rick+Riordan%22&wt=json&indent=true</p>
<p>返回结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"responseHeader":{
"status":0,
"QTime":1,
"params":{
"indent":"true",
"q":"author:\"Rick Riordan\"",
"wt":"json"}},
"response":{"numFound":2,"start":0,"docs":[
{
"id":"978-0641723445",
"cat":["book",
"hardcover"],
"name":["The Lightning Thief"],
"author":["Rick Riordan"],
"series_t":["Percy Jackson and the Olympians"],
"sequence_i":1,
"genre_s":"fantasy",
"inStock":[true],
"price":[12.5],
"pages_i":384,
"_version_":1497865383114178560},
{
"id":"978-1423103349",
"cat":["book",
"paperback"],
"name":["The Sea of Monsters"],
"author":["Rick Riordan"],
"series_t":["Percy Jackson and the Olympians"],
"sequence_i":2,
"genre_s":"fantasy",
"inStock":[true],
"price":[6.49],
"pages_i":304,
"_version_":1497865383259930624}]
}}
</code></pre></div></div>
<p>当然,这个只是简单的查询示例,可以只输入一部分查询,或者按价钱区间来查询(例:查询 10-20 这个价钱区间的书就可以写成 <code class="language-plaintext highlighter-rouge">price:[ 10 TO 20 ]</code>),也可以组合条件查询,当然也可以限制查询结果只返回需要的字段,或者高亮显示匹配部分等等。</p>
Solr 应用(一)-初次体验
2015-04-29T00:00:00+00:00
http://www.blogways.net/blog/2015/04/29/solr-usage-1
<h2 id="solr-的简介以及安装">Solr 的简介以及安装</h2>
<p>我们要学习一个新东西的时候总是仍不住会问,这是个什么东西,干什么用的,能帮我们解决什么问题,然后才是如何应用,如何优化等等。本次我们要讨论的是 Solr,它是一个开源的企业级搜索服务器,可以说是 Apache Lucene 搜索引擎的企业级应用的实现。下面几节将简单介绍下 solr 的应用(使用 Solr5.0 版本)。</p>
<h3 id="solr-简介">Solr 简介</h3>
<p>Solr 是一个高性能,采用 java5 开发,基于 Lucene 的全文搜索服务器。提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展并对查询性能进行了优化,并且提供了一个完善的功能管理界面,拥有强大的全文检索功能,高亮显示检索结果,动态集群,数据库接口和电子文档(Word ,PDF 等)的处理。而且Solr 具有高度的可扩展,支持分布搜索和索引的复制。是一款非常优秀的全文搜索引擎。</p>
<h3 id="安装">安装</h3>
<h4 id="环境参数">环境参数</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jdk1.7+(下载地址 http://www.oracle.com/technetwork/java/javase/downloads/index.html)
Tomcat7(Solr5.0 自带 Jetty 服务器,可以直接启动)
Solr5.0(下载地址 http://lucene.apache.org/solr/)
</code></pre></div></div>
<h4 id="安装步骤">安装步骤</h4>
<hr />
<h5 id="linux">Linux</h5>
<p>这里直接使用自带的 Jetty 服务器测试</p>
<ul>
<li>
<p>直接解压 Solr</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ cd ~/
$ tar zxf solr-5.0.0.tgz
</code></pre></div> </div>
</li>
<li>
<p>启动 Solr</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr start
</code></pre></div> </div>
</li>
<li>
<p>脚本帮助</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr -help
$ bin/solr start -help
</code></pre></div> </div>
</li>
<li>
<p>前台启动 Solr</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr start -f
</code></pre></div> </div>
</li>
<li>
<p>指定端口启动</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr start -p 8984
</code></pre></div> </div>
</li>
<li>
<p>停止 Solr</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr stop -p 8983
</code></pre></div> </div>
</li>
<li>
<p>启动一个特殊示例配置的 Solr</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr -e techproducts
</code></pre></div> </div>
</li>
<li>
<p>检查 Solr 运行状态</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ bin/solr status
</code></pre></div> </div>
</li>
</ul>
<p>至此,Solr 就算成功启动了,可以去 web 控制台验证下 <code class="language-plaintext highlighter-rouge">http://localhost:8983/solr/</code></p>
<hr />
<h5 id="windows">Windows</h5>
<p>这里我们用自己的服务器 Tomcat7 来部署运行</p>
<ul>
<li>解压 Solr && 部署</li>
</ul>
<p>在目录 <code class="language-plaintext highlighter-rouge">solr-5.0.0/server/webapps</code> 目录下面有个 solr.war 包,把这个拷贝到 tomcat webapp 目录下,启动 tomcat,自动解压 solr 工程</p>
<ul>
<li>
<p>修改 web.xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <env-entry>
<env-entry-name>solr/home</env-entry-name>
<env-entry-value>E:\tools\solr-5.0.0\server\solr</env-entry-value>
<env-entry-type>java.lang.String</env-entry-type>
</env-entry>
</code></pre></div> </div>
<p>将 <code class="language-plaintext highlighter-rouge">env-entry-value</code> 节点值改成 solr 的目录</p>
</li>
<li>
<p>添加 jar 包</p>
<p>将 <code class="language-plaintext highlighter-rouge">solr-5.0.0\server\lib\ext</code> 目录下所有的 jar 包拷贝到 <code class="language-plaintext highlighter-rouge">apache-tomcat-7.0.42\webapps\solr\WEB-INF\lib</code> 下面</p>
</li>
<li>
<p>添加 log4j 配置</p>
<p>将 <code class="language-plaintext highlighter-rouge">solr-5.0.0\server\resources\log4j.properties</code> 拷贝到 <code class="language-plaintext highlighter-rouge">apache-tomcat-7.0.42\webapps\solr\WEB-INF\classes</code> 目录下(classes 目录需要手动创建)</p>
</li>
</ul>
<p>启动 tomcat,打开控制台可以看到跟上面效果一样的</p>
<hr />
<h4 id="web-控制台">Web 控制台</h4>
<p>搞了半天,看下控制台长什么样子~</p>
<p><img src="/images/solr-1-1.png" alt="Solr5.0 Web 控制台" /></p>
使用Redis实现TOP500排行功能( 列表和有序集合 )
2015-04-29T00:00:00+00:00
http://www.blogways.net/blog/2015/04/29/rank-by-redis
<h2 id="一背景介绍">一、背景介绍</h2>
<p>通过 node.js 将系统产生的日志入库到 mongodb,并统计最大耗时的TOP500,因为 node.js 操作 mongodb 都是通过异步调用的回调函数来完成的,所以在获取表中记录数的时候操作起来特别的烦琐,还有一个原因就是启动多个 node.js 实例来入库数据的时候,并发请求太多,可能多个 node.js 删除的时同一条记录,导致在实际测试过程中,统计表中的记录条数不是 500,而可能时几十、几百万条数据。</p>
<h2 id="二数据结构">二、数据结构</h2>
<p>通过采集系统的日志文件发送到 node.js,经过分析过后的数据:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var obj = {
"MAX" : 123214,
"MIN" : 234,
"AVERAGE" : 3245,
...
} 根据 MAX 或 MIN 排序;
</code></pre></div></div>
<h2 id="三实现">三、实现</h2>
<p>TOP500 统计有两个阶段:</p>
<ol>
<li>表中记录数不满 500 条时,在此阶段任何新插入的数据都是TOP500,直接插入数据即可</li>
<li>表中数据大于 500 条时,在此阶段若插入一条数据,就需要删除一条数据;</li>
</ol>
<p>上述第二阶段时,一般数据库的操作方式就是找到并删除 <code class="language-plaintext highlighter-rouge">MAX 最小</code>( <strong>当按MAX排行时,只要比表中最小的大,就是TOP500</strong> )或<code class="language-plaintext highlighter-rouge">MIN 最大</code>( <strong>当按MIN排行时,只要比表中最大的大,就是TOP500</strong> )的一条记录,然后将新纪录插入数据库即可。</p>
<h3 id="有序集合-sorted-sets-">有序集合( Sorted sets )</h3>
<p>在 node.js 中连接 redis 的所有操作都是异步回调的,而在判断表中记录数的时候需要等待结果,然后才能进行后续的操作,此处需要同步等待,因而使用 async 来实现:</p>
<p>因为有序集合会将插入的数据的值自动排序,所有表中第一条、或最后一条记录即是最小、最大值,操作不复杂,使用起来也很方便;操作有两种实现方式:</p>
<ol>
<li>手动判断数据个数,在表中记录不满500条时插入数据,超过500条之后根据排行榜功能返回表中第一条或最后一条记录,与当前值比较判断是否插入并删除取出的数据;</li>
<li>不管表中有多少条数据,先讲记录插入到表中,然后删除500条之后的记录、或倒数500条之前的记录;</li>
</ol>
<h4 id="实现方式一">实现方式一</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var env = process.env.NODE_ENV || 'development',
config = require('../../config/config')[env];
logger = require('../../log').logger,
redis = require('redis'),
client = redis.createClient(config.redis.port,config.redis.host),
async = require('async');
... 省略中间代码 ...
var value = obj[field];
async.auto({
step1: function (callback) {
client.zcard(['test'], callback);
},
step2: ['step1', function (callback, result) {
if (result.step1 < 500) {
client.zadd(['test', value, JSON.stringify(obj)], function (err, rest){
if (err) {
logger.error(err);
} else {
callback('redis insert successfully!')
}
})
} else {
if (type == 'max') {
client.zrange(['test', 0, 0, 'withscores'], callback)
} else {
client.zrange(['test', -1, -1, 'withscores'], callback)
}
}
}],
step3: ['step2', function (callback, result) {
if ( (type == 'max' && value > result.step2[1]) ||
(type == 'min' && obj.MIN < result.step2[1]) ) {
client.rem(['test', result.step2[0]], redis.print);
client.zadd(['test', value, JSON.stringify(obj)], redis.print);
}
}]
}, function(err, results) {
client.quit();
}); 这种方法很符合传统的思路,比较容易理解,但是很明显操作比较多,还使用了一些插件、判断等,效率应该不是很高。
</code></pre></div></div>
<h4 id="实现方式二">实现方式二</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var env = process.env.NODE_ENV || 'development',
config = require('../../config/config')[env];
logger = require('../../log').logger,
redis = require('redis'),
client = redis.createClient(config.redis.port,config.redis.host);
... 省略中间代码 ...
var value = obj[field];
client.zadd(['test', value, JSON.stringify(obj)], redis.print);
if (type == 'MAX') {
client.zremrangebyrank(['test', 0, -500], redis.print);
} else {
client.zremrangebyrank(['test', 500, -1], redis.print);
} 第二种实现方式代码量很少,所有的实现都是调用的 redis 提供的接口方法来实现,因而运行效率比方式一要高很多,比较推荐此种方式。
</code></pre></div></div>
<p>因为按 MAX 字段统计排行榜时,要保留MAX最大的TOP500,而表按照数值升序排序的,所以需要保留表中后500跳记录( 删除表中后500条之前的数据,即第一条记录到倒数第500条之间的数据:<code class="language-plaintext highlighter-rouge">zremrangebyrank(['test', 0, -500], redis.print)</code> );</p>
<p>同理,按照 MIN 字段统计排行榜时,只需要删除500条之后的数据( 升序排序,则第一条到第500条即为TOP500,<code class="language-plaintext highlighter-rouge">zremrangebyrank(['test', 500, -1], redis.print)</code> )。</p>
<h3 id="使用到的方法介绍">使用到的方法介绍</h3>
<p>redis 对 node.js 的所有接口方法都有两个参数,第一个为一个数组( <code class="language-plaintext highlighter-rouge">[]</code> ),数组中的参数即为在<code class="language-plaintext highlighter-rouge">redis-cli</code>客户端执行命令时的参数,第二个参数为一个回调函数,通常为 <code class="language-plaintext highlighter-rouge">function (err, result){ ... }</code>,方法执行的结果保存在 result 中。</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">client.zcard([ tabname ], callback)</code>:同客户端命令 <code class="language-plaintext highlighter-rouge">ZCARD</code>,得到的有序集合成员的数量;</li>
<li><code class="language-plaintext highlighter-rouge">client.zadd([ tabname, value, key ], callback)</code>:同客户端命令 <code class="language-plaintext highlighter-rouge">ZADD</code>,添加一个或多个成员到有序集合,或者如果它已经存在更新其数据值;添加多个记录:<code class="language-plaintext highlighter-rouge">client.zadd(['test', 1, 'a1', 2, 'a2'.....], callback)</code>;</li>
<li><code class="language-plaintext highlighter-rouge">client.zrange([ tabname, start stop, 'withscores' ], callback)</code>:同客户端命令 <code class="language-plaintext highlighter-rouge">ZRANGE</code>,由索引返回一个成员范围的有序集合,如果有<code class="language-plaintext highlighter-rouge">withscores</code>参数,则对于 key 的值也会返回,如:<code class="language-plaintext highlighter-rouge">[ 'a1', '1' ]</code>;</li>
<li><code class="language-plaintext highlighter-rouge">client.rem([ tabname, key], callback)</code>:同客户端命令<code class="language-plaintext highlighter-rouge">ZREM</code>,从有序集合中删除一个或多个成员;</li>
<li><code class="language-plaintext highlighter-rouge">client.zremrangebyrank([ tabname, start, stop ], callback)</code>,同客户端命令 <code class="language-plaintext highlighter-rouge">ZREMRANGEBYRANK</code>,在给定的索引之内删除所有成员的有序集合,下标从 0 开始,-1 表示最后一条记录,-500 表示倒数第500条记录。</li>
</ul>
<h3 id="列表">列表</h3>
<p>因为列表只有一个key,不存在 value的说法,因而通过 Lists 来实现的时候,需要将比较字段通过一些处理,并放到转换为字符串的前面,如在按照如下对象的 MAX 字段统计时:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var obj = {
"MAX" : 123214,
"MIN" : 234,
"AVERAGE" : 3245,
...
} 转化成的字符串需为:`"{\"KEY\":\"00000000002.342424\",\"MAX\":2.342424,\"MIN\":0.023212,\"AVERAGE\":0.065464, ....}"`,因为没有 value的说法,所以只能按照字符串的ASCII的方式来比较,所以比较字段需要将位数统一。
</code></pre></div></div>
<p>redis 的 <code class="language-plaintext highlighter-rouge">sort</code>命令能按照指定的顺序排序( desc,降序;asc,升序 ),同时可以截取排序结果并保存为一个单独的表( 或覆盖原来的表 ),如此则可以使用有序集合实现方式二的思想来实现:</p>
<ul>
<li>按 MAX 统计排行榜时,按降序排序,则前500条记录即为TOP500;</li>
<li>按 MIN 统计排行榜时,按升序排序,则钱500条记录即为TOP500;</li>
</ul>
<p>代码实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var env = process.env.NODE_ENV || 'development',
config = require('../../config/config')[env];
logger = require('../../log').logger,
redis = require('redis'),
client = redis.createClient(config.redis.port,config.redis.host);
... 省略中间代码 ...
var value = obj[field],
adesc = type == "max" ? "desc" : "asc",
tmpstr = JSON.stringify(obj),
tmpval = Array((11-(''+Math.floor(value)).length+1)).join(0)+value, //整数部分统一到 11 位
multi = client.multi();
var str = tmpstr.replace('{','{"KEY":"'+tmpval+'",'); // 将 KEY 部分添加到 字符串中
multi.rpush([tabname, str], redis.print);
multi.sort([tabname, 'limit', 0, count, adesc, 'alpha', 'store', tabname], redis.print);
multi.exec(function(err,rest){
if(err){
logger.error(err);
}else{
logger.debug(rest);
}
}); 代码也很简洁,不过实际效率情况的高低就不得而知了,其中需要注意的就是要将统计字段取出做等长处理( `MAX: 1.23` 和 `MAX: 11.3`,前者比后者小,但是转换为字符串之后,前者比后者大,将它们的整数位等长到11位或更多后,可以解决这个问题);
</code></pre></div></div>
<p>这种实现方式的思路:将新的记录插入到表中,然后通过<code class="language-plaintext highlighter-rouge">sort</code>命令排序,并截取前500条记录覆盖原来的表,虽然每次都有排序,但是实际情况没有想象中那么糟糕,因为每次插入数据钱的数据都是排序好了的,插入一条数据的排序花销可能不是很大( 具体要看redis的排序实现方式:如果是通过快速排序来实现的话,效率会很低;如果是通过冒泡方式来实现的话效率会高不少 )。</p>
<p>这种方法只是一种探究,基本不会使用到生产环境中,推荐使用有序集合的实现方式二。</p>
<p>使用的方法说明:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">multi = client.multi();</code>,顾名思义,相当于一个client的命令序列,先将要执行的命令放入其中,带启动时顺序执行;</li>
<li><code class="language-plaintext highlighter-rouge">rpush([tabname, key], callback);</code>,同客户端命令 <code class="language-plaintext highlighter-rouge">RPUSH</code>,添加一个或多个值到列表右端( 尾部 );</li>
<li><code class="language-plaintext highlighter-rouge">sort([tabname, 'limit', start, stop, 'asc'/'desc', 'alpha', 'store', tabname1], callback)</code>:同客户端命令 <code class="language-plaintext highlighter-rouge">sort</code>,将表 <code class="language-plaintext highlighter-rouge">tabname</code>的key 按字母( 默认会将key 转换为 double类型再做排序 )的升序或降序排列,并截取下标<code class="language-plaintext highlighter-rouge">start</code> 到 <code class="language-plaintext highlighter-rouge">stop</code>之间的元素,保存到 <code class="language-plaintext highlighter-rouge">tabname1</code>表中;</li>
<li><code class="language-plaintext highlighter-rouge">multi.exec(callback)</code>,按先后顺序运行<code class="language-plaintext highlighter-rouge">multi</code>中的命令;</li>
</ul>
<p>大概的通过 Redis 实现排行榜的就是这样,如果有什么好的想法也可以跟我联系,一起交流进步!</p>
Apache Flume-NG 介绍[5] 之 自定义组件
2015-04-29T00:00:00+00:00
http://www.blogways.net/blog/2015/04/29/Apache-Flume-NG-Introduction-5
<h2 id="一custom-source">一、Custom Source</h2>
<p>实现 Source 接口即可定义Custom Sources,在启动Flume Agent的时候,用户自定义源的类及其依赖必须包括在agent 的 <code class="language-plaintext highlighter-rouge">classpath</code>中。Custom Source 的类型是它的全限定性类名。</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>FQCN ( 全限定性类名,如:org.example.MyCustomSource )</code></td></tr>
<tr><td width="15%">selector.type</td><td width="10%"></td><td><code>replicating</code> 或 <code>multiplexing</code></td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td>依赖selector.type的值</td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p>实例:<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = org.example.MyCustomSource
a1.sources.r1.channels = c1
</code></pre></div></div>
<h3 id="自定义-source">自定义 Source</h3>
<ol>
<li>
<p>创建工程:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn archetype:create -DgroupId=org.apache.flume -DartifactId=flume-ng-db-source -DpackageName=com.ai.flume -Dversion=1.0
</code></pre></div> </div>
</li>
<li>
<p>编辑 <code class="language-plaintext highlighter-rouge">pom.xml</code> 添加依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> </dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.5.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.35</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc14</artifactId>
<version>10.2.0.1.0</version>
</dependency>
</dependencies>
</code></pre></div> </div>
</li>
<li>
<p>创建一个类,继承 <code class="language-plaintext highlighter-rouge">AbstractSource</code>,并实现接口 <code class="language-plaintext highlighter-rouge">Configurable</code>和<code class="language-plaintext highlighter-rouge">PollableSource</code>,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package com.ai.flume.source;
public class FlumeNGDBSource extends AbstractSource implements Configurable, PollableSource {
private static final Logger logger = LoggerFactory.getLogger(FlumeNGDBSource.class);
public void configure(Context context) {
// 处理配置文件( 如:conf/example.conf )中的属性
}
@Override
public synchronized void start() {
// Source 启动时的初始化、开启进程等
}
public Status process() throws EventDeliveryException {
// Source 真正的工作进程
// 获取data,封装到event中,发送到channel
}
@Override
public synchronized void stop() {
// Source 结束时的变量释放、进程结束等
}
}
</code></pre></div> </div>
</li>
<li>
<p>配置文件创建和使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> private String sqlType;
private String sqlHost;
private int sqlPort;
private String Db;
private String sqlUser;
private String sqlPwd;
private String sql;
... 省略中间代码 ...
public void configure(Context context) {
// 在此方法中,通过context.getString("property", defaultValue)
// context.getInteger("property", defaultValue)来读取配置文件
sqlType = context.getString("sql-type", "oracle");
sqlHost = context.getString("sql-host", "localhost");
sqlPort = context.getInteger("sql-port", 3306);
sqlDb = context.getString("sql-db", "test");
sqlUser = context.getString("sql-user", null);
sqlPwd = context.getString("sql-pwd", null);
sql = context.getString("sql", "select * from info;");
}
</code></pre></div> </div>
<p>上述文件的配置文件可以如下设置,<code class="language-plaintext highlighter-rouge">mydb.conf</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> a1.sources = r1
a1.channels = c1
a1.sources.r1.type = com.ai.flume.FlumeNGDBSource
a1.sources.r1.sql-type = mysql
a1.sources.r1.sql-host = 127.0.0.1
# 这里的SQL不需要加双引号或单引号
a1.sources.r1.sql = select * from users;
# 使用默认端口 3306,默认数据库 test,默认没有用户和密码
</code></pre></div> </div>
</li>
<li>
<p>数据封装为 Event,并发送到 Channel:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> private ChannelProcessor channelProcessor = null;
// 用于存放需要发送的队列
// 从数据库中取出,然后放到队列中
private LinkedBlockingQueue<String> queue = null;
... 省略中间代码 ...
public Status process() throws EventDeliveryException {
Status status = Status.READY;
// 获取连接Channel的对象
channelProcessor = getChannelProcessor();
try {
// 取出一条要发送的数据
String line = queue.take();
// 通过logger以INFO的级别输出到Source的日志文件中
logger.info(line);
// 调用EventBuilder.withBody(String body, Charset set),
// 将要发送的数据封装为一个 event
Event e = EventBuilder.withBody(line, Charset.forName("UTF8"));
// 将封装后的 event发送到连接的 Channel
channelProcessor.processEvent(e);
} catch (Exception e) {
// 出现错误,返回Status.BACKOFF,告诉Source 发送失败
// 当自定义 Sink 的时候,这里需要注意,详见后面
status = Status.BACKOFF;
logger.error("flume-ng mysql source error!", e);
throw new EventDeliveryException(e);
}
</code></pre></div> </div>
return status;
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> } 到这基本自定义 Source已经完成了,其它要做的就是通过代码实现数据的生成或抓取,然后放到上述的`queue`当中。
</code></pre></div> </div>
</li>
<li>
<p>通过<code class="language-plaintext highlighter-rouge">Timer</code>定时从数据库中取数据放到<code class="language-plaintext highlighter-rouge">queue</code>中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> private Timer scannerTimer;
... 省略中间代码 ...
@Override
public synchronized void start() {
super.start();
queue = new LinkedBlockingQueue<String>();
</code></pre></div> </div>
scannerTimer = new Timer(“FlumeNG_Scanner_Timer_Thread”, true);
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> scannerTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
// 调用操作数据库方法,执行sql,返回数据
list = ibd.selectAll(sql);
if (list.size() <= 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
logger.error("[ Error ]:", e);
}
}
String res = "";
for (int i = 0; i < list.size(); i++) {
if ("" == res) {
res += list.get(i);
} else {
res += "&data=" + list.get(i); }
}
// 将结果缓存到queue当中
queue.offer(res);
}
}, 0, runSpeed); // runSpeed为配置文件当中的查询间隔
}
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">stop</code>方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Override
public synchronized void stop() {
super.stop();
// 关闭与Channel的连接
channelProcessor.close();
// 结束数据获取线程
if (scannerTimer != null) {
try {
scannerTimer.cancel();
} catch (Exception e) {
} finally {
scannerTimer = null;
}
}
queue = null;
} 至此,自定义Source已经完成。
</code></pre></div> </div>
</li>
<li>
<p>生成<code class="language-plaintext highlighter-rouge">jar</code>包:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn clean package 在上面命令运行成功后,复制 `target/flume-ng-db-source-1.0.jar`到`$FLUME_HOME/lib`目录即可。
cp target/flume-ng-db-source-1.0.jar $FLUME_HOME/lib
</code></pre></div> </div>
</li>
</ol>
<h2 id="二custom-sink">二、Custom Sink</h2>
<p>实现 Sink 接口即可定义Custom Sink,在启动Flume Agent的时候,用户自定义Sink的类及其依赖必须包括在agent 的 <code class="language-plaintext highlighter-rouge">classpath</code>中。Custom Sink 的类型是它的全限定性类名。</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>FQCN ( 全限定性类名,如:org.example.MyCustomSink )</code></td></tr>
<tr><td width="15%">selector.type</td><td width="10%"></td><td><code>replicating</code> 或 <code>multiplexing</code></td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td>依赖selector.type的值</td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p>实例:<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = org.example.MyCustomSink
a1.sources.r1.channels = c1
</code></pre></div></div>
<h3 id="自定义-sink">自定义 Sink</h3>
<p>此 Sink从 Channel 获取 events,然后通过HTTP POST发送到远程的主机端口上。</p>
<ol>
<li>
<p>同样的,通过maven创建工程:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn archetype:create -DgroupId=org.apache.flume -DartifactId=flume-ng-http-source -DpackageName=com.ai.flume -Dversion=1.0
</code></pre></div> </div>
</li>
<li>
<p>编辑 <code class="language-plaintext highlighter-rouge">pom.xml</code> 添加依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> </dependencies>
<dependency>
<groupId>org.apache.flume</groupId>
<artifactId>flume-ng-core</artifactId>
<version>1.5.2</version>
</dependency>
</dependencies>
</code></pre></div> </div>
</li>
<li>
<p>创建一个类,继承 <code class="language-plaintext highlighter-rouge">AbstractSource</code>,并实现接口 <code class="language-plaintext highlighter-rouge">Configurable</code>和<code class="language-plaintext highlighter-rouge">PollableSource</code>,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package com.ai.flume.source;
public class FlumeNGHttpSink extends AbstractSink implements Configurable {
private static final Logger logger = LoggerFactory.getLogger(FlumeNGHttpSink.class);
public void configure(Context context) {
// 处理配置文件( 如:conf/example.conf )中的属性
}
public Status process() throws EventDeliveryException {
// Source 真正的工作进程
// 获取data,封装到event中,发送到channel
}
public String post(String url, String param) {
// http发送post请求
}
}
</code></pre></div> </div>
</li>
<li>
<p>配置文件创建和使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> private String url;
private String host;
private String type;
... 省略中间代码 ...
public void configure(Context context) {
// 在此方法中,通过context.getString("property", defaultValue)
// context.getInteger("property", defaultValue)来读取配置文件
url = context.getString("node-url", "http://127.0.0.1:5000/receive");
host = context.getString("node-host", "127.0.0.1");
type = context.getString("node-type", "TuxState");
}
</code></pre></div> </div>
<p>上述文件的配置文件可以如下设置,<code class="language-plaintext highlighter-rouge">mydb.conf</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> a1.sources = r1
a1.channels = c1
a1.sources.r1.type = com.ai.flume.FlumeNGHttpSink
a1.sources.r1.node-url = http://localhost:3000/receive
a1.sources.r1.node-host = 127.0.0.1
a1.sources.r1.node-type = TuxState
</code></pre></div> </div>
</li>
<li>
<p>从 Channel 获取 Event,并取出其中包含的数据:</p>
public Status process() throws EventDeliveryException {
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // TODO(ai) Auto-generated method stub
Status status = Status.READY;
Channel channel = getChannel();
Transaction txn = null;
try {
txn = channel.getTransaction();
txn.begin(); // 开始从Channel获取Event的事务处理
Event e = channel.take(); // 从Channel取一个Event
if (null != e) {
String line = EventHelper.dumpEvent(e);
logger.info(line);
// 取出 Event 中包含的内容,然后转换为需要的类型,此处为String
byte[] body = e.getBody();
String data = new String(body);
//logger.info(data);
String str = "localhost=" + host + "&type=" + type + "&data=" + data;
//将配置文件的信息和数据组合成post方法的param,调用编写的post方法发送到目的地
logger.info(post(url, str));
} else {
status = Status.BACKOFF;
}
</code></pre></div> </div>
txn.commit();
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> } catch (Exception e) {
// 前面提及过,在处理这的时候需要注意
// 若发送错误的原因是数据处理的问题,则可能会出现死循环
// 出错,而不执行txn.commit(),则出错的Event并不会从 Channel中删除
// 下一次获取的仍然是出错的 Event
logger.error("can't process events, drop it!", e);
if (txn != null) {
txn.commit(); //出现BUG,丢弃当前Event,防止出现死循环
}
throw new EventDeliveryException(e);
} finally {
if (null != txn) {
txn.close();
}
}
</code></pre></div> </div>
return status;
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> } HTTP的post方法此处不再给出,到这基本自定义 Sink已经完成了。
</code></pre></div> </div>
</li>
<li>
<p>生成<code class="language-plaintext highlighter-rouge">jar</code>包:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> mvn clean package 在上面命令运行成功后,复制 `target/flume-ng-http-sink-1.0.jar`到`$FLUME_HOME/lib`目录即可。
cp target/flume-ng-http-sink-1.0.jar $FLUME_HOME/lib
</code></pre></div> </div>
</li>
</ol>
<h2 id="三custom-channel">三、Custom Channel</h2>
<p>…</p>
<p></br></p>
<p>===</p>
<p><strong><em>未完待续。。。</em></strong></p>
Apache Flume-NG 介绍[4] 之 Channel
2015-04-29T00:00:00+00:00
http://www.blogways.net/blog/2015/04/29/Apache-Flume-NG-Introduction-4
<h2 id="一常用channel介绍">一、常用Channel介绍</h2>
<p>Channels 是一个 Agent上存储 events 的仓库,Source 向其中添加 events,而 Sink从中取走移除 events。</p>
<p>此处介绍的 Channel 有:Memory Channel、File Channel 和 Spillable Memory Channel。</p>
<h3 id="memory-channel">Memory Channel</h3>
<p>Source 添加的 events 都暂存在内存队列中,它非常适合那些需要更高吞吐量的数据流,但代价是一旦一个 agent 失败后,其中存储的events数据将会丢失。其必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>memory</code></td></tr>
<tr><td width="15%">capacity</td><td width="10%">100</td><td>存储在 Channel 当中的最大 events 数</td></tr>
<tr><td width="15%">transactionCapacity</td><td width="10%">100</td><td>同时刻从Source 获取,或发送到 Sink 的最大 events 数</td></tr>
<tr><td width="15%">keep-alive</td><td width="10%">3</td><td>添加或删除一个 event 超时的秒数</td></tr>
<tr><td width="15%">byteCapacityBufferPercentage</td><td width="10%">20</td><td><i><b>详见表后的链接</b></i></td></tr>
<tr><td width="15%">byteCapacity</td><td width="10%">20000</td><td><i><b>详见表后的链接</b></i></td></tr></table>
<p>详情见:<a href="http://flume.apache.org/FlumeUserGuide.html#memory-channel">http://flume.apache.org/FlumeUserGuide.html#memory-channel</a></p>
<p>实例 <code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.channels.c1.type = memory
a1.channels.c1.capacity = 10000
a1.channels.c1.transactionCapacity = 10000
a1.channels.c1.byteCapacityBufferPercentage = 20
a1.channels.c1.byteCapacity = 800000
</code></pre></div></div>
<h3 id="file-channel">File Channel</h3>
<p>必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>file</code></td></tr>
<tr><td width="15%">hostname</td><td width="10%">-</td><td>绑定的主机名或者 IP 地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>绑定的端口号</td></tr>
<tr><td width="15%">batch-size</td><td width="10%">100</td><td>一次同时发送的 event 数</td></tr>
<tr><td width="15%">connect-timeout</td><td width="10%">20000</td><td>第一次握手请求时允许的时长。( ms )</td></tr>
<tr><td width="15%">request-timeout</td><td width="10%">20000</td><td>第一次过后,后续请求允许的时长 ( ms )</td></tr>
<tr><td width="15%">ireset-connection-interval</td><td width="10%">none</td><td></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.channels.c1.type = file
a1.channels.c1.checkpointDir = /mnt/flume/checkpoint
a1.channels.c1.dataDirs = /mnt/flume/data
</code></pre></div></div>
<h3 id="spillable-memory-channel">Spillable Memory Channel</h3>
<p>Logs Sink 属于 INFO 级别的,通常用作测试或调试目的,其属性:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>SPILLABLEMEMORY</code></td></tr>
<tr><td width="15%">memoryCapacity</td><td width="10%">10000</td><td>存储在内存队列中的最大 events 数,设置为 <code>0</code>,则禁用缓存到内存队列</td></tr>
<tr><td width="15%">overflowCapacity</td><td width="10%">100000000</td><td>存储在本地磁盘的最大 events 数,设置为 <code>0</code>,则禁用缓存到本地文件</td></tr>
<tr><td width="15%">overflowTimeout</td><td width="10%">3</td><td>当内存队列溢出后,启用本地磁盘缓存的超时时间</td></tr>
<tr><td width="15%">byteCapacityBufferPercentage</td><td width="10%">见描述</td><td><i><b>详见表后的链接</b></i></td></tr>
<tr><td width="15%">byteCapacity</td><td width="10%">20</td><td><i><b>详见表后的链接</b></i></td></tr>
<tr><td width="15%">avgEventSize</td><td width="10%">500</td><td>估计将要缓存到 Channel 当中的 events 的平均大小 (单位:字节)</td></tr>
<tr><td width="15%"><file channel properties></td><td width="10%">见描述</td><td><i><b>详见表后的链接</b></i></td></tr>
</table>
<p>详情见:<a href="http://flume.apache.org/FlumeUserGuide.html#spillable-memory-channel">http://flume.apache.org/FlumeUserGuide.html#spillable-memory-channel</a></p>
<p>如果 <code class="language-plaintext highlighter-rouge">memoryCapacity</code>或<code class="language-plaintext highlighter-rouge">byteCapacity</code>被设置为 0,则 Flume 理解为内存队列已经满了。</p>
<p>实例,<code class="language-plaintext highlighter-rouge">Agnet a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.channels.c1.type = SPILLABLEMEMORY
a1.channels.c1.memoryCapacity = 10000
a1.channels.c1.overflowCapacity = 1000000
a1.channels.c1.byteCapacity = 800000
a1.channels.c1.checkpointDir = /mnt/flume/checkpoint
a1.channels.c1.dataDirs = /mnt/flume/data
</code></pre></div></div>
<p>禁用缓存 events 到内存队列,<code class="language-plaintext highlighter-rouge">memoryCapacity</code>属性设为 0,则此 Channel 就像一个 File Channel:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.channels.c1.type = SPILLABLEMEMORY
a1.channels.c1.memoryCapacity = 0
a1.channels.c1.overflowCapacity = 1000000
a1.channels.c1.checkpointDir = /mnt/flume/checkpoint
a1.channels.c1.dataDirs = /mnt/flume/data 禁用缓存 events 到本地磁盘,`overflowCapacity`属性设为 0,则此 Channel 就像一个纯粹的 Memory Channel:
a1.channels = c1
a1.channels.c1.type = SPILLABLEMEMORY
a1.channels.c1.memoryCapacity = 100000
a1.channels.c1.overflowCapacity = 0
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong><em>未完待续。。。</em></strong></p>
Apache Flume-NG 介绍[3] 之 Sink
2015-04-29T00:00:00+00:00
http://www.blogways.net/blog/2015/04/29/Apache-Flume-NG-Introduction-3
<h2 id="一常用sink介绍">一、常用Sink介绍</h2>
<p>此处介绍的 Sinks 有:HDFS Sink、Logger Sink、Avro/Thrift Sink、HBase Sink。</p>
<h3 id="avro-sink">Avro Sink</h3>
<p>Avro Sink 为 Flume 的层次连接提供了一半的支持( 另一半为 Avro Source ),发送到此 Sink 的 Flume 被转换为 Avro 事件,然后被发送到对应的主机:端口上。其必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>avro</code></td></tr>
<tr><td width="15%">hostname</td><td width="10%">-</td><td>绑定的主机名或者 IP 地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>绑定的端口号</td></tr>
<tr><td width="15%">batch-size</td><td width="10%">100</td><td>一次同时发送的 event 数</td></tr>
<tr><td width="15%">connect-timeout</td><td width="10%">20000</td><td>第一次握手请求时允许的时长。( ms )</td></tr>
<tr><td width="15%">request-timeout</td><td width="10%">20000</td><td>第一次过后,后续请求允许的时长 ( ms )</td></tr>
<tr><td width="15%">ireset-connection-interval</td><td width="10%">none</td><td></td></tr>
<tr><td width="15%">compression-type</td><td width="10%">none</td><td>可选项为“none”或“deflate”,compression-type必须符合匹配AvroSource的compression-type</td></tr>
<tr><td width="15%">compression-level</td><td width="10%">6</td><td>压缩 event 的压缩级别,0 为不压缩,1 - 9 为压缩,数字越大则压缩率越大。</td></tr>
<tr><td width="15%">ssl</td><td width="10%">false</td><td>设置为<code>true</code>启用SSL加密,同时可以选择性设置“truststore”,“truststore-password”,“truststore-type”,并且指定是否打开“trust-all-certs”</td></tr>
<tr><td width="15%">trust-all-certs</td><td width="10%">false</td><td>如果设置为 <code>true</code>,则远程服务( Avro Source )的 SSL 服务证书将不会进行校验,因而生产环境不能设置为 <code>true</code></td></tr>
<tr><td width="15%">truststore</td><td width="10%">-</td><td>Java truststore文件的路径,需要启用SSL加密</td></tr>
<tr><td width="15%">truststore-password</td><td width="10%">-</td><td>Java truststore的密码,需要启用SSl加密</td></tr>
<tr><td width="15%">truststore-type</td><td width="10%">JKS</td><td>Java truststore的类型,可选项为:“JSK” 或其它支持的Java truststore 类型</td></tr>
<tr><td width="15%">exclude-protocols</td><td width="10%">SSLv2Hello SSLv3</td><td>Space-separated list of SSL/TLS protocols to exclude</td></tr>
<tr><td width="15%">maxIoWorkers</td><td width="10%">2 * 当前机器上可用的处理器数</td><td>I/O 处理线程的最大数,在 <code>NettyAvroRpcClient</code> 和 <code>NioClientSocketChannelFactory</code> 上被加载。</td></tr>
</table>
<p>实例 <code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.sinks = k1
a1.sinks.k1.type = avro
a1.sinks.k1.channel = c1
a1.sinks.k1.hostname = 10.10.10.10
a1.sinks.k1.port = 4545
</code></pre></div></div>
<h3 id="thrift-sink">Thrift Sink</h3>
<p>Thrift Sink 为 Flume 的层次连接提供了一半的支持( 另一半为 Thrift Source ),发送到此 Sink 的 Flume 被转换为 Thrift 事件,然后被发送到对应的主机:端口上。其必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>thrift</code></td></tr>
<tr><td width="15%">hostname</td><td width="10%">-</td><td>绑定的主机名或者 IP 地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>绑定的端口号</td></tr>
<tr><td width="15%">batch-size</td><td width="10%">100</td><td>一次同时发送的 event 数</td></tr>
<tr><td width="15%">connect-timeout</td><td width="10%">20000</td><td>第一次握手请求时允许的时长。( ms )</td></tr>
<tr><td width="15%">request-timeout</td><td width="10%">20000</td><td>第一次过后,后续请求允许的时长 ( ms )</td></tr>
<tr><td width="15%">ireset-connection-interval</td><td width="10%">none</td><td></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.sinks = k1
a1.sinks.k1.type = thrift
a1.sinks.k1.channel = c1
a1.sinks.k1.hostname = 10.10.10.10
a1.sinks.k1.port = 4545
</code></pre></div></div>
<h3 id="hdfs-sink">HDFS Sink</h3>
<p>HDFS Sink 将 events 写入到 Hadoop 分布式文件系统( HDFS )当中,目前它支持创建文本文件和二进制序列化文件( SequenceFile ),这两种文件类型都支持压缩文件。数据库文件能够周期性地,在运行时间、数据大小或event数量的基础上轮转( 关闭当前文件,并创建一个新文件 )。It also buckets/partitions data by attributes like timestamp or machine where the event originated. The HDFS directory path may contain formatting escape sequences that will replaced by the HDFS sink to generate a directory/file name to store the events. 使用 HDFS Sink 需要安装 hadoop,如此 Flume 便能够通过 hadoop jars 连接 HDFS 集群,注意,Hadoop 的版本必须支持<code class="language-plaintext highlighter-rouge">sync()</code>调用。</p>
<p>下面是支持的转义序列:</p>
<table width="100%">
<tr><th width="20%">别名</th><th>描述</th></tr>
<tr><td width="20%">%{host}</td><td></td></tr>
<tr><td width="20%">%t</td><td>Unix 时间的秒数</td></tr>
<tr><td width="20%">%a</td><td>本地的星期缩写名称 (Mon, Tue, ...)</td></tr>
<tr><td width="20%">%A</td><td>本地的星期完整名称 (Monday, Tuesday, ...)</td></tr>
<tr><td width="20%">%b</td><td>本地的月份缩写名称 (Jan, Feb, ...)</td></tr>
<tr><td width="20%">%B</td><td>本地的月份完整名称 (January, February, ...)</td></tr>
<tr><td width="20%">%c</td><td>本地的日期和时间 (Thu Mar 3 23:05:25 2005)</td></tr>
<tr><td width="20%">%d</td><td>某月中的某天 (01)</td></tr>
<tr><td width="20%">%D</td><td>日期,与 <code>%m/%d/%y</code> 一样</td></tr>
<tr><td width="20%">%H</td><td>24 小时制的小时,<b><i>补齐两位</i></b> (00..23)</td></tr>
<tr><td width="20%">%I</td><td>12 小时制的小时,<b><i>补齐两位</i></b> (01..12)</td></tr>
<tr><td width="20%">%j</td><td>某年中的某天 (001..366)</td></tr>
<tr><td width="20%">%k</td><td>24 小时制的小时,<b><i>不补齐两位</i></b> ( 0..23)</td></tr>
<tr><td width="20%">%m</td><td>月份 (01..12)</td></tr>
<tr><td width="20%">%M</td><td>分钟数 (00..59)</td></tr>
<tr><td width="20%">%p</td><td>本地上午或下午 (am, pm)</td></tr>
<tr><td width="20%">%s</td><td>从 <code>1970-01-01 00:00:00 UTC</code> 到现在的秒数</td></tr>
<tr><td width="20%">%S</td><td>秒数 (00..59)</td></tr>
<tr><td width="20%">%y</td><td>年份的后两位数字 (00..99)</td></tr>
<tr><td width="20%">%Y</td><td>年份 (2015)</td></tr>
<tr><td width="20%">%z</td><td>+hhmm numeric timezone (for example, -0400)</td></tr>
</table>
<p>正在读取的文件的文件名会以 <code class="language-plaintext highlighter-rouge">.tmp</code> 结尾,一旦文件被关闭,则扩展部分会被移除,这使排除目录中部分完成的文件称为可能。其config属性如下:</p>
<p><strong><em>注意:对于所有与时间相关的转义系列,一个包含 “timestamp” 的header必须在 event 的所有headers中存在( 除非 <code class="language-plaintext highlighter-rouge">hdfs.useLocalTimeStamp</code> 被设置为 <code class="language-plaintext highlighter-rouge">true</code> )。设置自动添加的一种方式是使用 <code class="language-plaintext highlighter-rouge">TimestampInterceptor</code>。</em></strong></p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>hdfs</code></td></tr>
<tr><td width="15%">hdfs.path</td><td width="10%">-</td><td>HDFS 目录路径 (如 <code>hdfs://namenode/flume/webdata/</code>)</td></tr>
<tr><td width="15%">hdfs.filePrefix</td><td width="10%">FlumeData</td><td>在 HDFS 目录中,Flume 创建的文件的前缀</td></tr>
<tr><td width="15%">hdfs.fileSuffix</td><td width="10%">-</td><td>文件的后缀 (如 <code>.avro</code> - 注意:时间不是自动添加的)</td></tr>
<tr><td width="15%">hdfs.inUsePrefix</td><td width="10%">-</td><td>Flume 正在写入的临时文件的的前缀</td></tr>
<tr><td width="15%">hdfs.inUseSuffix</td><td width="10%"><code>.tmp</code></td><td>Flume 正在写入的临时文件的后缀</td></tr>
</table>
<p>更多属性配置详见:<a href="http://flume.apache.org/FlumeUserGuide.html#hdfs-sink">http://flume.apache.org/FlumeUserGuide.html#hdfs-sink</a></p>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.sinks = k1
a1.sinks.k1.type = hdfs
a1.sinks.k1.channel = c1
a1.sinks.k1.hdfs.path = /flume/events/%y-%m-%d/%H%M/%S
a1.sinks.k1.hdfs.filePrefix = events-
a1.sinks.k1.hdfs.round = true
a1.sinks.k1.hdfs.roundValue = 10
a1.sinks.k1.hdfs.roundUnit = minute
</code></pre></div></div>
<p>上面的配置将时间戳四舍五入到最近的10分钟,如:一个 event 的时间戳是 <code class="language-plaintext highlighter-rouge">11:54:34 AM, June 12, 2012</code>,那么映射到 hdfs 路径则为 <code class="language-plaintext highlighter-rouge">/flume/events/2012-06-12/1150/00</code></p>
<h3 id="hbase-sink">HBase Sink</h3>
<p>具体介绍等,详见:<a href="http://flume.apache.org/FlumeUserGuide.html#hbasesink">http://flume.apache.org/FlumeUserGuide.html#hbasesink</a></p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>hbase</code></td></tr>
<tr><td width="15%">table</td><td width="10%">-</td><td>HBase 中写入数据的表</td></tr>
<tr><td width="15%">columnFamily</td><td width="10%">-</td><td>HBase 中写入数据列簇</td></tr>
<tr><td width="15%">zookeeperQuorum</td><td width="10%">-</td><td>The quorum spec. This is the value for the property hbase.zookeeper.quorum in hbase-site.xml</td></tr>
<tr><td width="15%">znodeParent</td><td width="10%">/hbase</td><td>The base path for the znode for the -ROOT- region. Value of zookeeper.znode.parent in hbase-site.xml</td></tr>
<tr><td width="15%">batchSize</td><td width="10%">100</td><td>每次事务处理写入的 event数</td></tr>
<tr><td width="15%">coalesceIncrements</td><td width="10%">false</td><td>是否添加一个保存文件 <code>basename</code> 的Header</td></tr>
<tr><td width="15%">serializer</td><td width="10%">org.apache.flume.sink.hbase.SimpleHbaseEventSerializer</td><td>Default increment column = “iCol”, payload column = “pCol”</td></tr>
<tr><td width="15%">serializer.*</td><td width="10%">-</td><td>Properties to be passed to the serializer.</td></tr>
<tr><td width="15%">kerberosPrincipal</td><td width="10%">-</td><td>Kerberos user principal for accessing secure HBase</td></tr>
<tr><td width="15%">kerberosKeytab</td><td width="10%">-</td><td>Kerberos keytab for accessing secure HBase</td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent agent-1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.sinks = k1
a1.sinks.k1.type = hbase
a1.sinks.k1.table = foo_table
a1.sinks.k1.columnFamily = bar_cf
a1.sinks.k1.serializer = org.apache.flume.sink.hbase.RegexHbaseEventSerializer
a1.sinks.k1.channel = c1
</code></pre></div></div>
<h3 id="logger-sink">Logger Sink</h3>
<p>Logs Sink 属于 INFO 级别的,通常用作测试或调试目的,其属性:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>logger</code></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agnet a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.channels = c1
a1.sinks = k1
a1.sinks.k1.type = logger
a1.sinks.k1.channel = c1
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong><em>未完待续。。。</em></strong></p>
Spring NoSuchBeanDefinitionException
2015-04-28T00:00:00+00:00
http://www.blogways.net/blog/2015/04/28/spring-exception
<p>对于 Java 开发的 web 项目,Spring 成了小伙伴们的首选,几乎成了 JavaEE 的标配,在开发、测试的过程中免不了会碰到很多相关的错误,其中比较常见的一个错误就是 NoSuchBeanDefinitionException,下面来讨论一下常见的几种情况, 本文着重介绍 bootstrap 项目注解实例化 Bean,至于 xml 配置部分逻辑性比较好查,应该更容易定位问题,在这里就不介绍了。</p>
<h3 id="1-概述">1. 概述</h3>
<p>在这篇文章中,我们讨论一下 Spring <code class="language-plaintext highlighter-rouge">BeanFactory</code> 在试图创建一个未在 Spring 上下文中定义的 Bean 时抛出的常见的异常 <code class="language-plaintext highlighter-rouge">org.springframework.beans.factory.NoSuchBeanDefinitionException</code>。</p>
<p>我们将在这里讨论下导致这个问题的可能的原因以及可用的解决方案。</p>
<h3 id="2-cause-no-qualifying-bean-of-type--found-for-dependency">2. Cause: No qualifying bean of type […] found for dependency</h3>
<p>导致这个异常的最常见的原因是企图注入一个未被定义的 bean。例如-在 BeanA 中注入 BeanB:
@Component
public class BeanA {</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @Autowired
private BeanB dependency;
...
}
</code></pre></div></div>
<p>但如果 BeanB 没有在 Spring 的上下文中定义依赖关系,bootstrap 进程会终止并抛出 <code class="language-plaintext highlighter-rouge">NoSuchBeanDefinitionException</code> 异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.baeldung.packageB.BeanB] found for dependency:
expected at least 1 bean which qualifies as autowire candidate for this dependency.
Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
</code></pre></div></div>
<p>Spring 已经明确指出:“至少需要一个 bean 作为 autowire 依赖注入”</p>
<p>一个原因 BeanB 可能不在上下文中,需要 bean 在 classpath 扫描时自动加载,并且被成功注解为其中的一个 bean (@Component, @Repository, @Service, @Controller, etc),那么很可能 BeanB 所在的包没有被 Spring 扫描到:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package org.baeldung.packageB;
@Component
public class BeanB { ...}
While the classpath scanning may be configured as follows:
@Configuration
@ComponentScan("org.baeldung.packageA")
public class ContextWithJavaConfig {
...
}
</code></pre></div></div>
<p>如果 bean 所在的目录没有配置扫描,那么 BeanB 也就不会定义在当前的 Spring 上下文中。</p>
<h3 id="3-cause-no-qualifying-bean-of-type--is-defined">3. Cause: No qualifying bean of type […] is defined</h3>
<p>另外一种情况就是上下文中存在重复的 bean 定义,不唯一。例如,假如有一个接口 <code class="language-plaintext highlighter-rouge">IBeanB</code> 被两个 bean (<code class="language-plaintext highlighter-rouge">BeanB1</code>和<code class="language-plaintext highlighter-rouge">BeanB2</code>) 实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component
public class BeanB1 implements IBeanB {
//
}
@Component
public class BeanB2 implements IBeanB {
//
}
</code></pre></div></div>
<p>当 BeanA 依赖注入这个接口时,Spring 不知道到底注入哪个实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component
public class BeanA {
@Autowired
private IBeanB dependency;
...
}
</code></pre></div></div>
<p>结果是 <code class="language-plaintext highlighter-rouge">BeanFactory</code> 再次抛出了异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type [org.baeldung.packageB.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
Similarly, Spring clearly indicates the reason for the wiring failure: “expected single matching bean but found 2″.
</code></pre></div></div>
<p>但是注意,这次抛出的异常不是 <code class="language-plaintext highlighter-rouge">NoSuchBeanDefinitionException</code>,而是它的子类 <code class="language-plaintext highlighter-rouge">NoUniqueBeanDefinitionException</code>。这个新的异常在 Spring 3.2.1 有介绍,具体的原因就是在上下文中存在重复的 bean 定义。</p>
<p>如果不做指定,就会报如下的异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type [org.baeldung.packageB.IBeanB] is defined:
expected single matching bean but found 2: beanB1,beanB2
One solution to this problem is to use the @Qualifier annotation to specify exactly the name of the bean we want to wire:
</code></pre></div></div>
<p>-</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component
public class BeanA {
@Autowired
@Qualifier("beanB2")
private IBeanB dependency;
...
}
</code></pre></div></div>
<p>通过指定 <code class="language-plaintext highlighter-rouge">Qualifier</code> 具体的注入 bean,Spring 将能决定使用哪个实现类来注入。</p>
<h3 id="4-cause-no-bean-named--is-defined">4. Cause: No Bean Named […] is defined</h3>
<p>当 Spring 的上下文中不存在指定名称的 bean 时同样会抛出 <code class="language-plaintext highlighter-rouge">NoSuchBeanDefinitionException</code> 异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Component
public class BeanA implements InitializingBean {
@Autowired
private ApplicationContext context;
@Override
public void afterPropertiesSet() {
context.getBean("someBeanName");
}
}
</code></pre></div></div>
<p>在这个例子中,不存在 <code class="language-plaintext highlighter-rouge">someBeanName</code> 名称定义的 bean 将会导致下面的异常:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No bean named 'someBeanName' is defined
Again, Spring clearly and concisely indicates the reason for the failure: “No bean named X is defined“.
</code></pre></div></div>
<h3 id="5-cause-proxied-beans">5. Cause: Proxied Beans</h3>
<p>当上下文中一个 bean 使用 JDK 的动态代理机制代理时,那么这个代理类不需要扩展目标 bean (然而它将实现相同的接口)</p>
<p>正因为如此,如果一个接口被注入,它将被正确接入。然而如果一个 bean 被实际类注入,Spring 将找不到匹配的类的 bean 定义,由于代理类实际上不扩展该类。</p>
<p>一个很常见的代理是 Spring 的事务支持,即被 <code class="language-plaintext highlighter-rouge">@Transactional</code> 注解的 bean。</p>
<p>例如,如果 ServiceA 注入 ServiceB,两个服务都有事务,通过类定义注入将无法工作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private ServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
</code></pre></div></div>
<p>同样的两个服务,这次通过接口注入就 OK:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Service
@Transactional
public class ServiceA implements IServiceA{
@Autowired
private IServiceB serviceB;
...
}
@Service
@Transactional
public class ServiceB implements IServiceB{
...
}
</code></pre></div></div>
<h3 id="6-结论">6. 结论</h3>
<p>本文讨论了几种可能导致 <code class="language-plaintext highlighter-rouge">NoSuchBeanDefinitionException</code> 的情况,重点是如何在实践中解决这些异常。</p>
Apache Flume-NG 介绍[2] 之 Source
2015-04-28T00:00:00+00:00
http://www.blogways.net/blog/2015/04/28/Apache-Flume-NG-Introduction-2
<h2 id="一常用sources介绍">一、常用Sources介绍</h2>
<p>此处介绍的 Sources 有:Avro Source、Thrift Source、Exec Source、Spooling Directory Source。</p>
<h3 id="avro-source">Avro Source</h3>
<p>监听 Avro 端口,接收外部 Avro 客户端发来的 Event 是流,当和另一个Agent (Event流上,前面一个) 的 Avro Sink 连接配对时,能够将两个 Agent 连接形成一个Event 链。其必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>avro</code></td></tr>
<tr><td width="15%">bind</td><td width="10%">-</td><td>监听的主机名或者 IP 地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>监听的端口号</td></tr>
<tr><td width="15%">threads</td><td width="10%">-</td><td>能生成的工作线程的最大数</td></tr>
<tr><td width="15%">selector.type</td><td width="10%"></td><td></td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td></td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
<tr><td width="15%">compression-type</td><td width="10%">none</td><td>可选项为“none”或“deflate”,compression-type必须符合匹配AvroSource的compression-type</td></tr>
<tr><td width="15%">ssl</td><td width="10%">false</td><td>设置为<code>true</code>启用SSL加密,同时必须制定一个“keystore”和一个“keystore-password”</td></tr>
<tr><td width="15%">keystore</td><td width="10%">-</td><td>Java keystore文件的路径,需要启用SSL加密</td></tr>
<tr><td width="15%">keystore-password</td><td width="10%">-</td><td>Java keystore的密码,需要启用SSl加密</td></tr>
<tr><td width="15%">keystore-type</td><td width="10%">JKS</td><td>Java keystore的类型,可选项为:“JSK” 和 “PKCS12”</td></tr>
<tr><td width="15%">exclude-protocols</td><td width="10%">SSLv3</td><td>Space-separated list of SSL/TLS protocols to exclude. SSLv3 will always be excluded in addition to the protocols specified.</td></tr>
<tr><td width="15%">ipFilter</td><td width="10%">false</td><td>设置为<code>true</code>启用ip过滤</td></tr>
<tr><td width="15%">ipFilter.rules</td><td width="10%">-</td><td>通过此配置,定义ip过滤的表达式规则</td></tr>
</table>
<p>实例 <code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = avro
a1.sources.r1.bind = localhost
a1.sources.r1.port = 4141
a1.sources.r1.ipFilter = true
a1.sources.r1.ipFilter.rules = allow:ip:127.*,allow:name:localhost,deny:ip:*
a1.sources.r1.channels = c1
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">ipFilter.rules</code>定义格式如下 :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><allow or deny>:<ip or name for computer name>:<pattern> 或
allow/deny:ip/name:pattern
</code></pre></div></div>
<h3 id="thrift-source">Thrift Source</h3>
<p>监听 Thrift 端口,接收外部 Thrift 客户端发来的 Event 是流,当和另一个Agent (Event流上,前面一个) 的 Thrift Sink 连接配对时,能够将两个 Agent 连接形成一个Event 链。其必须的属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>thrift</code></td></tr>
<tr><td width="15%">bind</td><td width="10%">-</td><td>监听的主机名或者 IP 地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>监听的端口号</td></tr>
<tr><td width="15%">threads</td><td width="10%">-</td><td>能生成的工作线程的最大数</td></tr>
<tr><td width="15%">selector.type</td><td width="10%"></td><td></td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td></td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = thrift
a1.sources.r1.channels = c1
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.port = 4141
</code></pre></div></div>
<h3 id="exec-source">Exec Source</h3>
<p>Exec Source 运行一个给定的 Unix 命令,此命令需要在启动后,进程能不断的产生数据到标准输出( 除非将 <code class="language-plaintext highlighter-rouge">logStdErr</code> 设置为 <code class="language-plaintext highlighter-rouge">true</code>,否则标准错误输出stderr将会被抛弃 )。如果 Unix 命令进程意外退出了,Exec Source 也会退出,不会再产生数据。这意味着配置如 <code class="language-plaintext highlighter-rouge">cat [named pipe]</code> 或 <code class="language-plaintext highlighter-rouge">tail -F [file]</code> 命令的时候将会产生期望的结果,而使用 <code class="language-plaintext highlighter-rouge">date</code> 命令的时候不会,前面两个命令会产生数据流,但是后面一个任务只会产生一个单一的 Event,然后马上退出。其config属性如下:</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>exec</code></td></tr>
<tr><td width="15%">command</td><td width="10%">-</td><td>需要执行的命令</td></tr>
<tr><td width="15%">shell</td><td width="10%">-</td><td>调用来运行命令的shell,如:<code>/bin/sh -c</code>,Required only for commands relying on shell features like wildcards, back ticks, pipes etc.</td></tr>
<tr><td width="15%">restartThrottle</td><td width="10%">10000</td><td>重启之前的等待时间(ms
)</td></tr>
<tr><td width="15%">resstart</td><td width="10%">false</td><td>设置是否重启命令,如果命令进程死了</td></tr>
<tr><td width="15%">logStdErr</td><td width="10%">false</td><td>设置是否命令的标准错误输出会被发送</td></tr>
<tr><td width="15%">batchSize</td><td width="10%">20</td><td>同时读取和发送的最大行数</td></tr>
<tr><td width="15%">selector.type</td><td width="10%">replicating</td><td>replicating 或 multiplexing</td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td>依赖selector.type的值</td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p><strong><em>注意:可以使用Exec Source 模仿Flume 0.9x ( flume-og )中的 Tail Source,只要使用 Unix 命令<code class="language-plaintext highlighter-rouge">tail -F /full/path/to/your/file</code>,在此情况下,参数 <code class="language-plaintext highlighter-rouge">-F</code>比 <code class="language-plaintext highlighter-rouge">-f</code>要更好,因为它会根据文件轮询。</em></strong></p>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = exec
a1.sources.r1.command = tail -F /var/log/secure
a1.sources.r1.channels = c1
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">shell</code>配置来执行<code class="language-plaintext highlighter-rouge">command</code>,通过一个命令行脚本( 如 Bash 或 PowerShell ),</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>agent_foo.sources.tailsource-1.type = exec
agent_foo.sources.tailsource-1.shell = /bin/bash -c
agent_foo.sources.tailsource-1.command = for i in /path/*.txt; do cat $i; done
</code></pre></div></div>
<h3 id="spooling-directory-source">Spooling Directory Source</h3>
<p>SpoolDir Source 支持从磁盘“spooling”文件夹读取数据文件,此源会监控指定的文件夹的新增文件,一旦有新文件出现,SpoolDir Source 会将其解析为 Event发送,这个 Event 解析逻辑时插件化的。在一个文件被全部读入到 Channel 之后, 该文件会被重命名标记完成( 或选择性的删除 )。</p>
<p>不同于 Exec Source,这个源是可靠的,即使 Flume 进程重启或是被杀掉都不会丢失数据。作为可靠性的交换,只有不变的且命名唯一的文件才能被放入源监控的目录,Flume 会检测这些问题条件,如果违反了,Flume 会报错:</p>
<ol>
<li>如果一个文件在移动到 SpoolDir Source 监控目录下之后被更改过,Flume 会在日志文件中输出错误信息,并停止 Flume 进程;</li>
<li>如果一个文件名在一段时间后被重复使用,Flume 会在日志文件中输出错误信息,并停止 Flume 进程。</li>
</ol>
<p>为了避免上述问题,比较好的方法是:在日志文件被移动到监控目录时,给日志文件用唯一标示符来命名( 例如:时间戳,timestamp );</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>spooldir</code></td></tr>
<tr><td width="15%">spoolDir</td><td width="10%">-</td><td>源监控的目录路径</td></tr>
<tr><td width="15%">fileSuffix</td><td width="10%">.COMPLETED</td><td>文件被读入完成后添加的标示符后缀</td></tr>
<tr><td width="15%">deletePolicy</td><td width="10%">never</td><td>是否删除完成读入的文件,可选项:<code>never</code> 或 <code>immediate</code></td></tr>
<tr><td width="15%">fileHeader</td><td width="10%">false</td><td>是否添加一个保存文件绝对路径的 Header</td></tr>
<tr><td width="15%">fileHeaderKey</td><td width="10%">file</td><td>当给 event header 添加绝对路径名的时候使用</td></tr>
<tr><td width="15%">basenameHeader</td><td width="10%">false</td><td>是否添加一个保存文件 <code>basename</code> 的Header</td></tr>
<tr><td width="15%">basenameHeaderKey</td><td width="10%">basename</td><td>当给 event header 添加 <code>basename</code> 的时候使用</td></tr>
<tr><td width="15%">ignorePattern</td><td width="10%">^$</td><td>忽略正则表达式指定的文件</td></tr>
<tr><td width="15%">trackerDir</td><td width="10%">.flumespool</td><td>保存跟进程文件相关元数据的目录,如果此路径不是一个绝对路径,就会解释为一个相对于 <code>spoolDir</code> 的路径。</td></tr>
<tr><td width="15%">consumeOrder</td><td width="10%">oldest</td><td>监控目录下的文件被读取的顺序,可选项为:<code>oldest</code>、<code>youngest</code>、<code>random</code></td></tr>
<tr><td width="15%">maxBackoff</td><td width="10%">4000 ( ms )</td><td>当 Channel 满了之后,连续尝试往 Channel 传送数据的最大时间间隔。SpoolDir Source 开始会启动一个很低的 <code>maxBackoff</code>,一旦 Channel 抛出一个 <code>ChannelException</code> 的时候,就会增加此 <code>maxBackoff</code> 值,直到达到指定的最大值。</td></tr>
<tr><td width="15%">batchSize</td><td width="10%">100</td><td>数据被传送到 Channel 的粒度。</td></tr>
<tr><td width="15%">inputCharset</td><td width="10%">UTF-8</td><td>将输入当做文本文档解析时候使用的字符集,即监控文件的字符集</td></tr>
<tr><td width="15%">decodeErrorPolicy</td><td width="10%"><code>FAIL</code></td><td>当发现一个无法解析字符集的输入文件时,需要做的处理:<code>FAIL</code>,抛出一个异常并标记解析失败;<code>REPLACE</code>,使用 “replacement character” 字符重复解析错误解析字符,例如 Unicode U+FFFD;<code>IGNORE</code>,删除无法解析的字符串序列。</td></tr>
<tr><td width="15%">deserializer</td><td width="10%"><code>LINE</code></td><td>指定将文件解析为 Event 的解析器,默认解析每行为一个 Event,指定的限定性类名必须实现接口 <code>EventDeserializer.Builder</code></td></tr>
<tr><td width="15%">deserializer.*</td><td width="10%"></td><td>Varies per event deserializer.</td></tr>
<tr><td width="15%">bufferMaxLines</td><td width="10%">-</td><td>该选项已经被忽略</td></tr>
<tr><td width="15%">bufferMaxLineLength</td><td width="10%">5000</td><td>(Deprecated) 提交缓存中一行的最大长度,使用 <code>deserializer.maxLineLength</code> 代替。</td></tr>
<tr><td width="15%">selector.type</td><td width="10%">replicating</td><td>replicating 或 multiplexing</td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td>依赖selector.type的值</td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agent agent-1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>agent-1.channels = ch-1
agent-1.sources = src-1
agent-1.sources.src-1.type = spooldir
agent-1.sources.src-1.channels = ch-1
agent-1.sources.src-1.spoolDir = /var/log/apache/flumeSpool
agent-1.sources.src-1.fileHeader = true
</code></pre></div></div>
<h3 id="netcat-source">NetCat Source</h3>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">channels</td><td width="10%">-</td><td></td></tr>
<tr><td width="15%">type</td><td width="10%">-</td><td>组件类型名称必须是<code>netcat</code></td></tr>
<tr><td width="15%">bind</td><td width="10%">-</td><td>监听主机名或ip地址</td></tr>
<tr><td width="15%">port</td><td width="10%">-</td><td>监听端口号</td></tr>
<tr><td width="15%">max-line-length</td><td width="10%">512</td><td>每个 event 内容的最大行数 ( 单位:字节 )</code></td></tr>
<tr><td width="15%">ack-every-event</td><td width="10%">true</td><td>每接收到一个 event 则回发一个 OK</td></tr>
<tr><td width="15%">selector.type</td><td width="10%">replicating</td><td>replicating 或 multiplexing</td></tr>
<tr><td width="15%">selector.*</td><td width="10%"></td><td>依赖selector.type的值</td></tr>
<tr><td width="15%">interceptors</td><td width="10%">-</td><td>Space-separated list of interceptors</td></tr>
<tr><td width="15%">interceptors.*</td><td width="10%"></td><td></td></tr>
</table>
<p>实例,<code class="language-plaintext highlighter-rouge">Agnet a1</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1
a1.channels = c1
a1.sources.r1.type = netcat
a1.sources.r1.bind = 0.0.0.0
a1.sources.r1.bind = 6666
a1.sources.r1.channels = c1
</code></pre></div></div>
<h2 id="二event-deserializers">二、Event Deserializers</h2>
<p>以下是 Flume 附带的 Event 解析器:Line、Avro、BlobDeserializer。</p>
<h3 id="line">Line</h3>
<p>此 deserializer 对应文本输入的每一行生成一个 event。</p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">deserializer.maxLineLength</td><td width="10%">2048</td><td>单个 event 能包含字符的最大数,如果一行超过了这个长度,将会被截断,该行中截断后剩余的字符会出现在后续的 event 中</td></tr>
<tr><td width="15%">deserializer.outputCharset</td><td width="10%">UTF-8</td><td>发送到 Channel 的每个 event 的编码字符集</td></tr>
</table>
<h3 id="avro">AVRO</h3>
<p>详情见<strong><em><a href="http://flume.apache.org/FlumeUserGuide.html#avro" title="http://flume.apache.org/FlumeUserGuide.html#avro">AVRO Deserializer</a></em></strong></p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">deserializer.schemaType</td><td width="10%">HASH</td><td>How the schema is represented. By default, or when the value HASH is specified, the Avro schema is hashed and the hash is stored in every event in the event header “flume.avro.schema.hash”. If LITERAL is specified, the JSON-encoded schema itself is stored in every event in the event header “flume.avro.schema.literal”. Using LITERAL mode is relatively inefficient compared to HASH mode.</td></tr>
</table>
<h3 id="blobdeserializer">BlobDeserializer</h3>
<p>详情见<strong><em><a href="http://flume.apache.org/FlumeUserGuide.html#blobdeserializer" title="http://flume.apache.org/FlumeUserGuide.html#blobdeserializer">Blob Deserializer</a></em></strong></p>
<table width="100%">
<tr><th width="15%">属性名</th><th width="10%">默认值</th><th>描述</th></tr>
<tr><td width="15%">deserializer</td><td width="10%">-</td><td>这个类的全限定性类名: <code>org.apache.flume.sink.solr.morphline.BlobDeserializer$Builder</code></td></tr>
<tr><td width="15%">deserializer.maxBlobLength</td><td width="10%">100000000</td><td>一个给定请求读取和缓存的最大字节数</td></tr>
</table>
<p></br></p>
<p>===</p>
<p><strong><em>未完待续。。。</em></strong></p>
Apache Flume-NG 介绍[1] 之 配置安装
2015-04-28T00:00:00+00:00
http://www.blogways.net/blog/2015/04/28/Apache-Flume-NG-Introduction-1
<h2 id="一介绍">一、介绍</h2>
<h3 id="11-概要">1.1 概要</h3>
<p>Flume 是 Cloudera 提供的日志收集系统,具有分布式、高可靠、高可用性等特点,对海量日志采集、聚合和传输,Flume 支持在日志系统中定制各类数据发送方,同时,Flume提供对数据进行简单处理,并写到各种数据接受方的能力。</p>
<p>Flume 包括 0.9.x 和 1.x 两个版本,分别为 Flume-OG ( Flume Original Generation ) 和 Flume-NG ( Flume Next Generation ),Flume-OG 是一个分布式日志收集系统,有 Mater 概念,依赖于 zookeeper,其架构图如下所示:</p>
<p><img src="/images/post/flume-og-construction.png" alt="" /></p>
<p>Agent 用于采集数据,agent 是 flume 中产生数据流的地方,同时,agent 会将产生的数据流传输到 collector。对应的,collector 用于对数据进行聚合,往往会产生一个更大的流。</p>
<p>而 Flume-NG,它摒弃了 Master和zookeeper,collector也没有了,web配置台也没有了,只剩下 source,sink和channel,此时一个agent的概念<strong>包括source,channel和sink</strong>,完全由一个分布式系统变成了传输工具。不同机器之间的数据传输不再是OG那样由agent到collector,而是由一个agent端的sink流向另一个agent的source,其架构图如下所示:</p>
<p><img src="/images/post/flume-ng-construction.png" alt="" /></p>
<p>agent的source端,直接抓取数据或接收从前一个 agent 传送过来的数据,并将其转发到<strong>一个或多个</strong> channel 进行缓存;</p>
<p>agent的 sink 端,从 channel 获取一个Event ( Flume-NG 的最小传输单元 ),然后传送到序列化端、下一个或多个agent 的source端。</p>
<p>Event 就像上图中箭头所示方向流动, chennel 的功能就像一个队列,暂时保存所有的 Event,为了保证传输一定成功,在 Event 送到目的地之前,会先缓存数据,待数据真正到达目的地后,删除自己缓存的数据。</p>
<ul>
<li>官方网站:<a href="http://flume.apache.org/">http://flume.apache.org/</a></li>
<li>用户文档:<a href="http://flume.apache.org/FlumeUserGuide.html">http://flume.apache.org/FlumeUserGuide.html</a></li>
<li>开发文档:<a href="http://flume.apache.org/FlumeDeveloperGuide.html">http://flume.apache.org/FlumeDeveloperGuide.html</a></li>
</ul>
<h3 id="12-系统要求">1.2 系统要求</h3>
<ol>
<li>Flume 使用 java 编写,其需要运行在 Java1.6 或更高版本之上;( 推荐 Java1.7 )</li>
<li>保证有足够的内存空间让 Flume 的 source、 channel和 sink 使用;</li>
<li>Flume 的 channel 和 sinks 组件会有文件缓存到物理硬盘中,需要足够的硬盘空间;</li>
<li>Flume 会读取文件或目录,需要 agent 相关的文件目录的 <strong><em>读写</em></strong> 权限。</li>
</ol>
<h2 id="二基本组件">二、基本组件</h2>
<h3 id="21-agent-之-source">2.1 Agent 之 Source</h3>
<p>source 可以接收外部源发送过来的数据,不同的 source,可以接受不同的数据格式,如:</p>
<ul>
<li>目录池数据源(Spooling Directory Source),可以监控指定文件夹中的新文件变化,如果目录中有文件产生,就会立刻读取其内容;</li>
<li>命令行数据源(Exec Source),可以运行指定的shell命令(如:<code class="language-plaintext highlighter-rouge">tail -f [ file ]</code> )等,将产生的标准输出(或标准错误输出,需要配置)封装到 Event 中,向指定的 channel 发送;</li>
<li>自定义源(Custom Source),用户自定义的源,会重点介绍。</li>
</ul>
<h3 id="22-agent-之-channel">2.2 Agent 之 Channel</h3>
<p>channel 是一个存储地,接收 source 的输出,直到有 sink 消费掉 channel 中的数据。channel 中的数据直到进入到下一个channel中或者进入终端才会被删除。当 sink 写入失败后,可以自动重启,不会造成数据丢失,因此很可靠,常用的有:</p>
<ul>
<li>Memory Channel,将从 source 得到的 Event 都缓存到内存当中,存取速度快、高效,但是一旦 agent 崩了,缓存的数据无法恢复,可靠性不是太好;</li>
<li>File Channel,跟Memory Channel相对的,此处将得到的 Event 都缓存到本地磁盘的文件当中,存取访问速度没有内存高效,但是可靠性强,一旦 agent 进程死掉重启后,所有缓存的 Event 可以全部恢复;</li>
<li>Spillable Memory Channel, 前面两者的结合使用,首先发过来的 Event 都缓存到内存当中,一旦内存空间不够用了,就缓存到本地文件中,主要应用于内存空间不足,而硬盘空间充足的情况。</li>
</ul>
<h3 id="23-agent-之-sink">2.3 Agent 之 Sink</h3>
<p>sink 会消费 channel 中的数据,然后送给外部源或者其他 source。如:</p>
<ul>
<li>HDFS Sink/HBase Sink,根据配置将 Event 所包含的信息保存到本地 HDFS/HBase 中;</li>
<li>Logger Sink,顾名思义,将接收到的信息日志输出,主要用于测试和调试;</li>
<li>Avro Sink,说句实话,百度得知 Avro 是一个 Apache 的数据序列化的系统,具体是什么不需要关注,只需要知道 Avro Sink 是将接收到的数据转换为 Avro, 然后发送到下一个 Agent 的 Avro Source,多用于Agent 与 Agent之间的数据连接;</li>
<li>用户自定义(Custom Sink),重点介绍。</li>
</ul>
<h2 id="三安装与运行">三、安装与运行</h2>
<p>到官方网站下载,<a href="http://flume.apache.org/download.html">http://flume.apache.org/download.html</a>,下载完成解压完成即可使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/tools
wget -c http://mirrors.hust.edu.cn/apache/flume/1.5.2/apache-flume-1.5.2-bin.tar.gz
# 等待下载完成
tar -xvf apache-flume-1.5.2-bin.tar.gz
ln -s apache-flume-1.5.2-bin flume-ng ( 将 ~/tools/flume-ng加入到环境变量FLUME_HOME中 ) ====> 个人习惯,可不做
cd flume-ng
bin/flume-ng help # 查看flume-ng命令
</code></pre></div></div>
<p>或通过编译源码的方式安装:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ~/tools
wget - http://mirrors.hust.edu.cn/apache/flume/1.5.2/apache-flume-1.5.2-src.tar.gz
tar -xf apache-flume-1.5.2-src.tar.gz
cd apache-flume-1.5.2-src
# 跳过测试
mvn clean install -DskipTests
</code></pre></div></div>
<h3 id="30-配置文件说明">3.0 配置文件说明</h3>
<p>Flume 的 Agent的各个部件都是通过配置文件来实现的,Agent、Sources、Channels和Sinks的定义,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources = r1 r2
a1.sinks = k1 k2
a1.channels = c1 c2 其中`a1`是定义的 Agent 的名字,也是启动命令时`--name <agent>`中的`<agent>`为 `a1`;上代码Sources、Channels和 Sinks分别定义了两个分别为r1, r2, c1, c2, k1, k2;
</code></pre></div></div>
<p>定义每个Sources的类型,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sources.r1.type = avro
a1.sources.r1.bind = localhost
a1.sources.r1.port = 33333
a1.sources.r2.type = netcat
a1.sources.r2.bind = localhost
a1.sources.r2.port = 44444 其中 `Source r1`定义为 Avro Source,监听 `http://localhost:33333`,`Source r2`定义为 NetCat Source;定义Channels 和 Sinks的方式跟 Sources类似;
</code></pre></div></div>
<p>为Sources 和 Sinks 绑定 Channels,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 为sink绑定时,为channels
a1.sources.r1.channels = c1
a1.sources.r2.channels = c2
# 为sink绑定时,为channel
a1.sinks.k1.channel = c1
a1.sinks.k2.channel = c2 注意:其中 Sources绑定 Channels时,为 `<agent>.sources.<source>`***`.channels`***;而 Sinks则为 `<agent>.sinks.<sink>`***`.channel`***。
</code></pre></div></div>
<h3 id="31-启动-agent">3.1 启动 Agent</h3>
<p>在 <code class="language-plaintext highlighter-rouge">flume-ng/conf</code> 目录下新建一个 <code class="language-plaintext highlighter-rouge">mytest.conf</code>做一个测试,内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># 定义了一个agent( a1 ), 一个source( r1 ), 一个channel( c1 ), 一个sink( k1 )
a1.sources = r1
a1.sinks = k1
a1.channels = c1
# 定义source( r1 )为Netcat Source,监控localhost:44444
a1.sources.r1.type = netcat
a1.sources.r1.bind = localhost
a1.sources.r1.port = 44444
# 定义sink( k1 )为Logger Sink,将接收的数据显示到命令行
a1.sinks.k1.type = logger
# 用内存作为 channel 的缓存
a1.channels.c1.type = memory
a1.channels.c1.capacity = 1000
a1.channels.c1.transactionCapacity = 100
# 将source 和 sink 绑定到 channel
a1.sources.r1.channels = c1
a1.sinks.k1.channel = c1
</code></pre></div></div>
<p>在 <code class="language-plaintext highlighter-rouge">~/tools/flume-ng</code> 目录下执行如下命令启动 Agent :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin/flume-ng agent -n a1 -c conf -f conf/mytest.conf -Dflume.root.logger=INFO,console
</code></pre></div></div>
<p>或</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin/flume-ng agent --name a1 --conf conf --conf-file conf/mytest.conf -Dflume.root.logger=INFO,console
</code></pre></div></div>
<p>参数说明:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-n</code> 指定 Agent 名称</li>
<li><code class="language-plaintext highlighter-rouge">-c</code> 指定配置文件目录</li>
<li><code class="language-plaintext highlighter-rouge">-f</code> 指定配置文件</li>
<li><code class="language-plaintext highlighter-rouge">-Dflume.root.logger=INFO,console</code> 设置日志等级</li>
</ul>
<p>注:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">-Dflume.root.logger=INFO,console</code> 表示在控制台输出日志,用作测试或调试,真是生产缓存不用;</li>
<li>使用logger,<code class="language-plaintext highlighter-rouge">flume-ng/conf</code> 目录下必须有 <code class="language-plaintext highlighter-rouge">log4j.properties</code> 项,否则会报错;</li>
<li>在实际运行中,有时候就算 conf 目录下有 <code class="language-plaintext highlighter-rouge">log4j.properties</code> 运行还是报错,如果你用的时第二条启动命令的话,换第一条启动命令试试;</li>
</ul>
<p>在 Agent 成功启动后,新打开一个命令行,输入 <code class="language-plaintext highlighter-rouge">telnet localhost 44444</code>,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ telnet localhost 44444
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello World!
OK 控制台输出:
2015-04-28 11:50:23,091 (lifecycleSupervisor-1-0) [INFO - org.apache.flume.source.NetcatSource.start(NetcatSource.java:150)] Source starting
2015-04-28 11:50:23,150 (lifecycleSupervisor-1-0) [INFO - org.apache.flume.source.NetcatSource.start(NetcatSource.java:164)] Created serverSocket:sun.nio.ch.ServerSocketChannelImpl[/127.0.0.1:44444]
2015-04-28 11:51:57,185 (SinkRunner-PollingRunner-DefaultSinkProcessor) [INFO - org.apache.flume.sink.LoggerSink.process(LoggerSink.java:70)] Event: { headers:{} body: 48 65 6C 6C 6F 20 57 6F 72 6C 64 21 0D Hello World!. } 如果显示如上所示,则 Flume-NG 安装成功。
</code></pre></div></div>
<h3 id="32-第三方插件">3.2 第三方插件</h3>
<h4 id="pluginsd-目录">plugins.d 目录</h4>
<p>plugins.d 目录位于 <code class="language-plaintext highlighter-rouge">$FLUME_HOME/plugins.d</code>,在flume-ng 启动的时候,flume-ng 启动脚本会遍历 plugins.d 目录中的插件,当通过 java 启动的时候将它们嵌入到合适的路径,其目录结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>plugins.d
|- lib 插件的jar包
|- libext 插件依赖的jar包
|- native 必须的本地库,如:*.so文件
如:
plugins.d/
plugins.d/custom-source-1/
plugins.d/custom-source-1/lib/my-source.jar
plugins.d/custom-source-1/libext/spring-core-2.5.6.jar
plugins.d/custom-source-2/
plugins.d/custom-source-2/lib/custom.jar
plugins.d/custom-source-2/native/gettext.so
</code></pre></div></div>
<h2 id="四数据获取">四、数据获取</h2>
<p>Flume 支持大量从外部源获取数据的机制。</p>
<h3 id="41-rpc">4.1 RPC</h3>
<p>一个Flume中部署的 Avro 客户端,能够通过 avro RPC 机制,将一个指定的文件发送给 Flume 的 Avro Source:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ bin/flume-ng avro-client -H localhost -p 41414 -F /usr/logs/log.10 上面所示命令将会把 `/usr/logs/log.10` 文件的内容发送到 Flume Source 监听的端口上。 ### 4.2 Executing commands
</code></pre></div></div>
<p>还有就是前面提及过的 Exec Source,通过执行一个指定的命令,将输出结果中的一行( 文本后跟回车<code class="language-plaintext highlighter-rouge">\r</code>或换行符<code class="language-plaintext highlighter-rouge">\n</code>,或两者同时出现 ),作为数据发送出去。</p>
<p><strong><em>注意:Flume 不支持 tail 作为一个Source,但可以通过在 exec source 中使用 tail 命令将文件转换为数据流。</em></strong></p>
<h3 id="43-network-streams">4.3 Network streams</h3>
<p>Flume 支持下面的机制从常用的日志数据流类型中读取数据,例如:</p>
<ol>
<li>Avro</li>
<li>Thrift</li>
<li>Syslog</li>
<li>Netcat</li>
</ol>
<p>下面可以启动一个 avro-client 客户端生产数据:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bin/flume-ng avro-client -c conf -H localhost -p 41414 -F /etc/passwd -Dflume.root.logger=DEBUG,console
</code></pre></div></div>
<h2 id="五配置多-agent-数据流动">五、配置多 Agent 数据流动</h2>
<p>通过 Agent 的Avro Source 和Avro Sink 可以实现多个 Agent 之间的数据传输,</p>
<p><img src="/images/post/multi-flow-by-avro.png" alt="" /></p>
<p>agent foo 的配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># agent foo
foo.sources = r1
foo.channels = c1
foo.sinks = k1
foo.sources.r1.type = netcat
foo.sources.r1.bind = localhost
foo.sources.r1.port = 44444
foo.channels.c1.type = memory
foo.channels.c1.capacity = 1000
foo.channels.c1.transactionCapacity = 100
foo.sinks.k1.type = avro
foo.sinks.k1.hostname = localhost
foo.sinks.k1.port = 44445
foo.sources.r1.channels = c1
foo.sinks.k1.channel = c1
</code></pre></div></div>
<p>agent bar 的配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code># agent bar
bar.sources = r1
bar.channels = c1
bar.sinks = k1
bar.sources.r1.type = avro
bar.sources.r1.bind = localhost
bar.sources.r1.port = 44445
bar.channels.c1.type = memory
bar.channels.c1.capacity = 1000
bar.channels.c1.transactionCapacity = 100
bar.sinks.k1.type = logger
bar.sources.r1.channels = c1
bar.sinks.k1.channel = c1 注意:Avro Source 中指定ip地址的是`<agent>.sources.<source>.bind = ip`,而 Avro Sink 则为`<agent>.sinks.<sink>.hostname = ip`!
</code></pre></div></div>
<h3 id="52-合并-agent">5.2 合并 Agent</h3>
<p>在日志收集当中,一个非常常见的情况是:大量的客户端将产生的日志数据发送到少量的收集 Agent 上,这些 Agent 再将接收到的日志存储到链接的存储系统上。如:</p>
<p>将数百台web servers 产生的日志发送到12个 Agent 上,然后写入到 HDFS 集群中。</p>
<p><img src="/images/post/consolidation.png" alt="" /></p>
<p>上图所示可以通过在 Flume 中配置3个使用 Avro Sink 的Agent,将3个节点都连接到一个 Avro Source的Agent( 同样的也可以使用 Thrift Sources/Sinks/Client来实现 )。图示 Agent4 收集接收到的 events,缓存到一个单一的 channel中,最后通过一个 sink将其中的数据发送到目的地( 如图所示的 HDFS )</p>
<h3 id="53-多路-agent">5.3 多路 Agent</h3>
<p>Flume 支持多路传输 event到一个或多个目的地。能够通过定义一个多选器( 能够通过路由,复制或选择的将一个Event发送到一个或多个 channels上 )来实现,其 Agent 结构如下所示:</p>
<p><img src="/images/post/multiplexing.png" alt="" /></p>
<p>这种模式,有两种方式:</p>
<ul>
<li>
<p>一种是用来复制(Replication),Replication方式可以将最前端的数据源复制多份,分别传递到多个channel中,每个channel接收到的数据都是相同的,配置格式如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # List the sources, sinks and channels for the agent
replicatAgent.sources = r1
replicatAgent.sinks = k1 k2 k3
replicatAgent.channels = c1 c2 c3
replicatAgent.sources.r1.type = netcat
replicatAgent.sources.r1.bind = localhost
replicatAgent.sources.r1.port = 44444
replicatAgent.sources.r1.selector.type = replicating
replicatAgent.sinks.k1.type = HDFS
#...
replicatAgent.sinks.k2.type = JMS
#...
replicatAgent.sinks.k3.type = avro
#...
# set channel for sinks
replicatAgent.sinks.k1.channel = c1
replicatAgent.sinks.k2.channel = c2
replicatAgent.sinks.k3.channel = c3
# set list of channels for source (separated by space)
replicatAgent.sources.r1.channels = c1 c2 c3 上面指定了 selector 的 type 的值为 replication,使用的Replication方式,`Source r1`会将数据分别存储到 `Channel c1 c2 c3`,这三个 channel 里面存储的数据是相同的,然后数据被传递到 `Sink k1 k2 k3`。
</code></pre></div> </div>
</li>
<li>
<p>另一种是用来分流(Multiplexing),selector可以根据header的值来确定数据传递到哪一个channel,配置格式,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> multiplexAgent.sources = r1
multiplexAgent.sinks = k1 k2 k3
multiplexAgent.channels = c1 c2 c3
# Mapping for multiplexing selector
multiplexAgent.sources.r1.selector.type = multiplexing
multiplexAgent.sources.r1.selector.header = state
multiplexAgent.sources.r1.selector.mapping.CZ = c1
multiplexAgent.sources.r1.selector.mapping.US = c1 c3
multiplexAgent.sources.r1.selector.mapping.TW = c3
#...
multiplexAgent.sources.r1.selector.default = c2
#... 其余配置不再给出
</code></pre></div> </div>
</li>
</ul>
<h3 id="54-load_balance">5.4 load_balance</h3>
<p><img src="/images/post/flume-load-balance-agents.png" alt="" /></p>
<p>Load balancing Sink Processor能够实现load balance功能,上图Agent1是一个路由节点,负责将Channel暂存的Event均衡到对应的多个Sink组件上,而每个Sink组件分别连接到一个独立的Agent上,示例配置,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sinkgroups = g1
a1.sinkgroups.g1.sinks = k1 k2 k3
a1.sinkgroups.g1.processor.type = load_balance
a1.sinkgroups.g1.processor.backoff = true
a1.sinkgroups.g1.processor.selector = round_robin
a1.sinkgroups.g1.processor.selector.maxTimeOut=10000
</code></pre></div></div>
<h3 id="55-failover">5.5 failover</h3>
<p>Failover Sink Processor能够实现failover功能,具体流程类似load balance,但是内部处理机制与load balance完全不同:Failover Sink Processor维护一个优先级Sink组件列表,只要有一个Sink组件可用,Event就被传递到下一个组件。如果一个Sink能够成功处理Event,则会加入到一个Pool中,否则会被移出Pool并计算失败次数,设置一个惩罚因子,示例配置如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a1.sinkgroups = g1
a1.sinkgroups.g1.sinks = k1 k2 k3
a1.sinkgroups.g1.processor.type = failover
a1.sinkgroups.g1.processor.priority.k1 = 5
a1.sinkgroups.g1.processor.priority.k2 = 7
a1.sinkgroups.g1.processor.priority.k3 = 6
a1.sinkgroups.g1.processor.maxpenalty = 20000
</code></pre></div></div>
<p><br /></p>
<p>===</p>
<p><strong><em>未完待续。。。</em></strong></p>
使用Jenkins搭建博客和Java源码管理的集成环境
2014-10-08T00:00:00+00:00
http://www.blogways.net/blog/2014/10/08/Jenkins-Blogways-Maven
<h2 id="一-概述">一、 概述</h2>
<p>在搭建博客系统的集成环境时,需要用到的有Jenkins、git和jekyll运行环境,本博客有相关的安装教程:</p>
<p>1、<strong><em>Jenkins安装</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://www.blogways.net/blog/2013/04/17/jenkins-git-maven-junit.html
http://www.blogways.net/blog/2013/04/23/jenkins-git-maven-junit-2.html
</code></pre></div></div>
<p>2、<strong><em>git服务器部署及使用</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://wanzhou.github.io/blog/2013/04/13/git.html
</code></pre></div></div>
<p>3、<strong><em>Windows和MAC OS X下安装jekyll</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://www.blogways.net/categories/jekyll/
</code></pre></div></div>
<p>上面是关于博客系统的运行环境搭建,由于我是在服务器上搭建集成环境,本身并没有ruby环境,需要自己去安装,因而说一下ruby和gem的安装。</p>
<h2 id="二-rubygem安装">二、 ruby、gem安装</h2>
<p>因为jekyll是的ruby语言编写的,需要ruby运行环境!</p>
<p>1、<strong><em>安装ruby</em></strong></p>
<p>首先,去ruby官网下载离线安装包,我下载的是<code class="language-plaintext highlighter-rouge">ruby-2.1.3.tar.gz</code>,然后运行如下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd ruby-2.1.3.tar.gz目录 (如:cd /Users/xxx/Downloads)
tar -xzvf ruby-2.1.3.tar.gz
cd ruby-2.1.3
./configure --prefix=安装ruby的目录 (如:/Users/xxx/App/ruby)
make && make install
</code></pre></div></div>
<p>安装好以后,修改操作系统的PATH路径,一般是修改<code class="language-plaintext highlighter-rouge">~/.bashrc</code>或者<code class="language-plaintext highlighter-rouge">~/.bash_profile</code>文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>RUBY_HOME=/Users/xxx/App/ruby (安装ruby的目录)
export RUBY_HOME
PTAH=$RUBY_HOME/bin:$PATH
export PATH
</code></pre></div></div>
<p>2、<strong><em>安装ruby</em></strong></p>
<p>同样,首先下载rubygem安装文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://rubygems.org/pages/download/
</code></pre></div></div>
<p>其中有很多种安装文件,按照自己的需求去下载,我下载的是<code class="language-plaintext highlighter-rouge">rubygems-2.4.1.tgz</code>,然后运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd rubygems-2.4.1.tgz目录
tar -xzvf rubygems-2.4.1.tgz
cd rubygems-2.4.1
ruby setup.rb
</code></pre></div></div>
<p>到此,ruby和gem安装完成了,后面需要安装jekyll时,只要运行命令<code class="language-plaintext highlighter-rouge">gem install jekyll</code>即可。</p>
<h2 id="三-博客集成系统核心实现">三、 博客集成系统核心实现</h2>
<p>1、jenkins任务创建,详情可以参考前面给出的网站。</p>
<ul>
<li>新建一个jenkins任务,按如下设置,并确定!</li>
</ul>
<p><img src="/images/post/blogtest.png" alt="" /></p>
<ul>
<li>设置源码管理,第一个框中填仓库路径(远程仓库和本地仓库都可以),如:<code class="language-plaintext highlighter-rouge">/home/git/blogtestgit</code>;第二个框填构建的分支,一般为<code class="language-plaintext highlighter-rouge">master</code>,可以直接填master也可以像下图所示填写:</li>
</ul>
<p><img src="/images/post/jkgit.png" alt="" /></p>
<ul>
<li>填写触发条件,此处为每天的12时和20时触发,</li>
</ul>
<p><img src="/images/post/blog-trigger.png" alt="" /></p>
<ul>
<li>编写构建步骤,</li>
</ul>
<p><img src="/images/post/blog-build.png" alt="" /></p>
<p>2、最主要的就是通过<code class="language-plaintext highlighter-rouge">Execute shell</code>来实现博客系统的创建和发布:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bash /home/spdev/tools/jenkins.shell.scripts/deployjekyll.sh $WORKSPACE
</code></pre></div></div>
<p><img src="/images/post/blogtest.png" alt="" /></p>
<p>在该<code class="language-plaintext highlighter-rouge">.sh</code>文件中的命令如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll_pid=$(ps aux | grep 'jekyll' | grep -v 'grep' | awk '{print $2}')
[ -z "$jekyll_pid" ]
if [ "$(echo $?)" == "1" ] ; then
echo "-------------------------------------"
kill -9 $jekyll_pid
echo "-- Log: -- Kill Previous Jekyll Serve successfully! --"
echo "-------------------------------------"
fi
BUILD_ID=dontKillMe /usr/bin/jekyll serve --detach &
if [ "$(echo $?)" != "0" ] ; then
echo "-------------------------------------"
echo "-- Log: -- An Exception Has Happened In The Source ! --"
echo "-------------------------------------"
exit 1
else
echo "-------------------------------------"
echo "-- Log: -- Update The Blogways ! --"
echo "-------------------------------------"
exit 0
fi
exit 0
</code></pre></div></div>
<p>1、首先,判断包含<code class="language-plaintext highlighter-rouge">jekyll</code>的进程( 即博客的发布经常 )是否存在,若存在则结束此进程;</p>
<p>2、然后,运行<code class="language-plaintext highlighter-rouge">jekyll serve --detach</code>,生成新的博客文件,并发布到<code class="language-plaintext highlighter-rouge">4000</code>端口,如果是在服务器上运行,那么就能在相应的端口访问博客,如<code class="language-plaintext highlighter-rouge">http://192.168.11.34:4000/</code>或<code class="language-plaintext highlighter-rouge">http://localhost:4000/</code>;</p>
<p>3、最后,监听上条语句,即<code class="language-plaintext highlighter-rouge">jekyll serve --detach</code>执行成功与否,若执行成功,则说明此处生成并发布新博客成功,以<code class="language-plaintext highlighter-rouge">exit 0</code>正常退出,告诉jenkins此次构建成功;否则说明生成或发布失败,以<code class="language-plaintext highlighter-rouge">exit 1</code>异常退出,告诉jenkins此次构建失败。</p>
<h2 id="四-java源码编译测试集成系统">四、 Java源码编译测试集成系统</h2>
<p>java源码的编译、测试及发布等,都是通过maven来实现的,因此需要maven环境,关于maven的使用可以参考:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://www.blogways.net/blog/2013/04/23/maven.html
</code></pre></div></div>
<p>jenkins提供了maven插件,一般都是默认安装的,java源码开发的集成系统的核心实现跟博客稍有区别,它及可以通过<code class="language-plaintext highlighter-rouge">Execute shell</code>来实现,也可以通过jenkins的maven插件来实现。</p>
<p>1、<strong><em>Execute shell</em></strong></p>
<p>如果没有特殊的要求,一条语句就能完成:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn install
</code></pre></div></div>
<p>如下图所示,图示显示的设置等同于<code class="language-plaintext highlighter-rouge">mvn package</code>:</p>
<p><img src="/images/post/maven-package.png" alt="" /></p>
<p>运行该命令后,会自动的完成编译、测试、打包、安装(安装到本地mavne库)等操作,如有任何一个阶段运行失败,就会结束运行,并告诉jenkins运行失败,jenkins会处理信息,按照设置的邮箱发送给最近代码提交者,代码有BUG。</p>
<p>2、<strong><em>maven插件</em></strong></p>
<p>只需要在<code class="language-plaintext highlighter-rouge">配置 -> Build -> Goals and options</code>项中,添加<code class="language-plaintext highlighter-rouge">install</code>即可完成跟<code class="language-plaintext highlighter-rouge">Execute shell</code>相同的功能。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bash /home/spdev/tools/jenkins.shell.scripts/mavenjava.sh $WORKSPACE
</code></pre></div></div>
在Chrome Extension中使用RequireJS
2014-09-24T00:00:00+00:00
http://www.blogways.net/blog/2014/09/24/requirejs-in-chrome-extensions
<p>RequireJS,谁用谁知道。</p>
<p>在浏览器环境下使用<code class="language-plaintext highlighter-rouge">requireJS</code>,其加载js文件的方式,采用的是通过创建<code class="language-plaintext highlighter-rouge">script</code>节点,及设置其<code class="language-plaintext highlighter-rouge">src</code>属性来实现。但在chrome extensions中,这项做法被认为是不安全的,而被默认禁止,这样就导致了<code class="language-plaintext highlighter-rouge">RequireJS</code>无法生效。</p>
<p>要想解决这个问题,有两个方案。</p>
<h2 id="方案一使用cajon替代requirejs">方案一:使用cajon替代requirejs</h2>
<h3 id="1-关于cajon的简介">1. 关于cajon的简介</h3>
<p><a href="https://github.com/requirejs/cajon">cajon</a>和<code class="language-plaintext highlighter-rouge">RequireJS</code>是相同的<a href="https://github.com/jrburke">作者</a>.</p>
<p>cajon的工作原理:基于<code class="language-plaintext highlighter-rouge">RequireJS</code>,而重写了<code class="language-plaintext highlighter-rouge">requirejs.load</code>方法。重写的<code class="language-plaintext highlighter-rouge">requirejs.load</code>方法,默认判断逻辑,依赖相同网站的js文件,则通过异步<code class="language-plaintext highlighter-rouge">XHR</code>请求方式获取,并通过<code class="language-plaintext highlighter-rouge">eval</code>方法使之生效。如果依赖的是其他网站js文件,则仍用原来加载方式(生成一个<code class="language-plaintext highlighter-rouge">script</code>标签)进行加载。</p>
<p>cajon还支持自定义哪种规则的依赖文件,需要采用<code class="language-plaintext highlighter-rouge">XHR+eval</code>方式加载。</p>
<h3 id="2-具体解决方案">2. 具体解决方案</h3>
<p>直接将原来<code class="language-plaintext highlighter-rouge">require.js</code>文件替换为<code class="language-plaintext highlighter-rouge">cajon.js</code>文件。</p>
<p>示例:</p>
<p>原来使用<code class="language-plaintext highlighter-rouge">requirejs</code>的代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script data-main="js/main" src="js/require/require.js" type="text/javascript"></script>
</code></pre></div></div>
<p>使用<code class="language-plaintext highlighter-rouge">cajon.js</code>替换为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><script data-main="js/main" src="js/require/cajon.js" type="text/javascript"></script>
</code></pre></div></div>
<p>Ok了,是不是很简单?!</p>
<h2 id="方案二设置content-security-policy参数">方案二:设置content security policy参数</h2>
<h3 id="1-关于content-security-policy的简介">1. 关于content security policy的简介</h3>
<p>这个,可以看看我的另外一篇博文,原文见:www.blogways.net。</p>
<h3 id="2-具体解决方案-1">2. 具体解决方案</h3>
<p>在<code class="language-plaintext highlighter-rouge">manifest.json</code>文件中添加如下代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"content_security_policy": "script-src 'self' 'unsafe-eval'; object-src 'self'"
</code></pre></div></div>
<p>是的,这样就搞定了!这里,关键参数是<code class="language-plaintext highlighter-rouge">unsafe-eval</code>。</p>
<p>试试吧!</p>
Java项目代码编写规范
2014-09-17T00:00:00+00:00
http://www.blogways.net/blog/2014/09/17/checkStyle
<h2 id="一checkclipse插件安装">一、Checkclipse插件安装</h2>
<p>Checkclipse是一个Eclipse插件,它集成了Checkstyle的样式检查器的编码准则到Eclipse中。所有的Java风格的违规行为将被立即报告错误标记。可以为每个项目单独配置编码准则。</p>
<p>1、下载checkclipse jar 包文件<a href="http://sourceforge.jp/projects/sfnet_checkclipse/" title="checkclipse.jar">http://sourceforge.jp/projects/sfnet_checkclipse/</a></p>
<p>2、安装Checkclipse</p>
<p>这里我通过Help->Software Updates->Find and Insta方式安装了好几次没有成功,最终选择下载插件jar包方式安装,我下载的是上面链接打开后列表中的de.mvmsoft.checkclipse_3.0.0.b201310301757.jar,下载后将jar包放到Eclipse安装目录plugins目录下,重新启动Eclipse在Windows—>preferences下找到checkclipse,如图:</p>
<p><img src="/images/checkclipse.jpg" alt="" /></p>
<h2 id="二-配置eclipse-java-google-stylexml">二、 配置eclipse-java-google-style.xml</h2>
<p>这里eclipse-java-google-style.xml对Google原始Eclipse Formatter文件进行了部分修改,所以大家不要下载Google原始文件,就用本文提供的eclipse-java-google-style.xml。</p>
<p>1、eclipse-java-google-style.xml内容:<a href="/xml/eclipse-java-google-style.xml" title="eclipse-java-google-style.xml">eclipse-java-google-style.xml</a></p>
<p>2、配置eclipse-java-google-style.xml到Eclipse中</p>
<p><img src="/images/eclipse-java-google-style.xml.png" alt="" /></p>
<h2 id="三-配置java默认生成模板">三、 配置Java默认生成模板</h2>
<p>1、在Windows->preferences->Java->Code Style->Code Templates下新增文件默认生成模板:</p>
<p><img src="/images/JavaFileTemplate.png" alt="" /></p>
<p>2、新建Java类时选择Generater comments</p>
<p><img src="/images/testTemplate.png" alt="" /></p>
<h2 id="四-checkstyle文件编写及配置">四、 checkStyle文件编写及配置</h2>
<h3 id="1checkstyle文件编写">1、checkStyle文件编写</h3>
<p>这里提供已经编写好的文件,内容<a href="/xml/checkstyle.xml" title="checkstyle.xml">checkstyle.xml</a></p>
<h3 id="2checkstyle文件配置">2、checkStyle文件配置</h3>
<p>a、Checkclipse配置</p>
<p><img src="/images/checkclispeSet.png" alt="" /></p>
<p>勾选Set Project Dir as Checkjstyle Basedir,CheckStyle Configuration File选择上面编写的checkStyle.xml文件</p>
<p>b、选择你要进行checkstyle的项目文件,选择Project->properties</p>
<p><img src="/images/CheckProject.png" alt="" /></p>
<p>勾选Enable CheckStyle、Set Project ClassLoader。</p>
<p>c、ok,可以进行Java代码编写了,此时如果没有按照checkStyle.xml里配置的规范要求编写代码,Eclipse中将给出错误提示:</p>
<p><img src="/images/CheckStyleTest.png" alt="" /></p>
<h2 id="四-checkstyle-maven-插件使用">四、 checkStyle Maven 插件使用</h2>
<p>checkStyle的maven插件名为maven-checkstyle-plugin,用于执行CheckStyle task,以下列出具体使用方法:</p>
<h3 id="1maven-pom-文件配置">1、maven pom 文件配置</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.10</version>
<configuration>
<configLocation>D:\codingStandards\checkstyle.xml</configLocation>
</configuration>
<executions>
<execution>
<id>checkstyle</id>
<phase>validate</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<failOnViolation>true</failOnViolation>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</code></pre></div></div>
<p></build></p>
<p>其中D:\codingStandards\checkstyle.xml即为上面我们编写的checkstyle规范文件</p>
<h3 id="2运行checkstyle检查">2、运行checkstyle检查</h3>
<p>命令行下执行mvn checkstyle:checkstyle 或直接通过Eclipse插件中 Maven test等执行方法,我用的Maven test</p>
<h3 id="3检查checkstyle结果">3、检查checkstyle结果</h3>
<p>运行maven命令后可以在console里查看checkstyle运行结果。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 22.926s
[INFO] Finished at: Thu Sep 18 09:28:04 CST 2014
[INFO] Final Memory: 5M/9M
[INFO] ------------------------------------------------------------------------
[ERROR] Failed to execute goal org.apache.maven.plugins:maven-checkstyle-plugin:2.10:check
(checkstyle) on project maven-script-test: You have 1 Checkstyle violation. -> [Help 1]
[ERROR]
[ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch.
[ERROR] Re-run Maven using the -X switch to enable full debug logging.
[ERROR]
[ERROR] For more information about the errors and possible solutions, please read the following articles:
[ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
</code></pre></div></div>
<p>checkstye的详细结果信息被存放在target/checkstyle-result.xml中。下面是具体信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0" encoding="UTF-8"?>
<checkstyle version="5.6">
<file name="D:\workspace\maven-script-test\src\main\java\javassisttest\Test1.java">
<error line="22" severity="error" message="Line is longer than 120 characters (found 124)." source="
com.puppycrawl.tools.checkstyle.checks.sizes.LineLengthCheck"/>
</file>
<file name="D:\workspace\maven-script-test\src\main\java\mveltest\GetNameTest.java">
</file>
<file name="D:\workspace\maven-script-test\src\main\java\mveltest\MvelTest.java">
<error line="9" severity="warning" message="Wrong order for &apos;org.mvel2.MVEL&apos; import." source=
"com.puppycrawl.tools.checkstyle.checks.imports.ImportOrderCheck"/>
</file>
</checkstyle>
</code></pre></div></div>
<p>从中我们可以看出 Test1.java 22行有一个行字符数超过120的错误,MvelTest.java 9行有个提示。</p>
<h3 id="4对指定文件不检查">4、对指定文件不检查</h3>
<p>对上面例子中行超过了120字符。如果我们不想修复这个错误怎么办那?可以将其suppress掉。
方法是建立一个checkstyle-suppressions.xml文件。其中加入下述内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><?xml version="1.0"?>
<!DOCTYPE suppressions PUBLIC
"-//Puppy Crawl//DTD Suppressions 1.0//EN"
"http://www.puppycrawl.com/dtds/suppressions_1_0.dtd">
<suppressions>
<suppress checks="LineLengthCheck"
files="Test1.java"
/>
</suppressions>
</code></pre></div></div>
<p>然后在pom文件<configuration>节点内checkstyle.xml配置下面加入checkstyle-suppressions.xml配置,如:</configuration></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><configuration>
<configLocation>D:\codingStandards\checkstyle.xml</configLocation>
<suppressionsLocation>D:\codingStandards\checkstyle-suppressions.xml</suppressionsLocation>
</configuration>
</code></pre></div></div>
<p>现在再运行看看:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[INFO] --- maven-surefire-plugin:2.10:test (default-test) @ maven-script-test ---
[INFO] Surefire report directory: D:\workspace\maven-script-test\target\surefire-reports
-------------------------------------------------------
T E S T S
-------------------------------------------------------
Results :
Tests run: 0, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 3.956s
[INFO] Finished at: Thu Sep 18 10:08:12 CST 2014
[INFO] Final Memory: 6M/11M
[INFO] ------------------------------------------------------------------------
</code></pre></div></div>
<p>OK!运行没有异常了,刚刚的行限制被跳过了。</p>
java 动态脚本之groovy、mvel and javascript
2014-09-15T00:00:00+00:00
http://www.blogways.net/blog/2014/09/15/groovy-mvel-javascript
<h2 id="一groovy">一、groovy</h2>
<h3 id="-groovy介绍">. groovy介绍</h3>
<p>Groovy 是 用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。</p>
<p>Groovy是JVM的一个替代语言(替代是指可以用 Groovy 在Java平台上进行 Java 编程),使用方式基本与使用 Java代码的方式相同,该语言特别适合与Spring的动态语言支持一起使用,设计时充分考虑了Java集成,这使 Groovy 与 Java 代码的互操作很容易。(注意:不是指Groovy替代java,而是指Groovy和java很好的结合编程。</p>
<h3 id="-groovy语法">. groovy语法</h3>
<p>groovy语法和java语法类似,具体请参阅<a href="http://beta.groovy-lang.org/docs/groovy-2.3.1/html/documentation/" title="官方文档">http://beta.groovy-lang.org/docs/groovy-2.3.1/html/documentation/</a>,这里不做介绍。</p>
<h3 id="-java中使用groovy">. java中使用groovy</h3>
<p>Java中调用Groovy情况:</p>
<p>1、Eval</p>
<p>Eval很容易的通过me方法执行一段逻辑,但是Eval不支持多行逻辑,并且没有对执行的script脚本缓存,效率非常低。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("Eval begin:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").
format(new Date()));
for (int i = 0; i < 1000; i++) {
Eval.xy(i, i + 1, "x*y");
}
System.out.println("Eval end:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").
format(new Date()));
输出:
Eval begin:2014-09-17 14:36:43:218
Eval end: 2014-09-17 14:36:52:203
</code></pre></div></div>
<p>2、GroovyShell:</p>
<p>GroovyShell支持简单脚本及Groovy文件的解析执行, GroovyShell支持script脚本的缓存,执行效率相对Eval高多了(下面例子100000次循环比Eval1000次循环还快),但要注意GroovyShell中shell.parse解析脚本程序不能放在大循环中或被频繁的调用,否则会大大降低性能,并且还有PermGen space异常风险。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("GroovyShell begin:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
Binding binding = new Binding();
GroovyShell shell = new GroovyShell(binding);
Script script = shell.parse("def mul(x, y) { return x * y }\n mul(x1, y1)");
for (int i = 0; i < 100000; i++) {
binding.setProperty("x1", i);
binding.setProperty("y1", i + 1);
script.run();
}
System.out.println("GroovyShell end:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
GroovyShell begin:2014-09-17 15:50:04:945
GroovyShell end:2014-09-17 15:50:06:389
</code></pre></div></div>
<p>3、GroovyClassLoader:适用于复杂逻辑的整个groovy文件,这里引入了Groovy2.0新增的静态编译,效率有进一步提升。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("GroovyClassLoader begin " + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
GroovyClassLoader loader = new GroovyClassLoader();
Class groovyClass = loader.parseClass(new File("src/extendTest/Test.groovy"));
GroovyObject object = (GroovyObject) groovyClass.newInstance();
for (int i = 0; i < 100000; i++) {
object.invokeMethod("num", new int[]{i, i + 1});
}
System.out.println("GroovyClassLoader end" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
GroovyClassLoader begin 2014-09-17 16:23:06:156
GroovyClassLoader end 2014-09-17 16:23:06:411
</code></pre></div></div>
<p>4、GroovyScriptEngine:GroovyScriptEngine类似GroovyShell,但存在互关联的多个脚本时,使用GroovyScriptEngine会更好些,但是GroovyScriptEngine效率也不理想。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("SimpleScript begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
for (int i = 0; i < 100000; i++) {
binding1.setVariable("x", i);
binding1.setVariable("y", i + 1);
engine.run("src/extendTest/SimpleScript.groovy", binding1);
}
System.out.println("SimpleScript end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
SimpleScript begin:2014-09-17 18:48:48:506
SimpleScript end:2014-09-17 18:49:13:796
</code></pre></div></div>
<p>5、Bean Scripting Framework:Bean Scripting Framework是一组Java Classes提供了在Java应用程序内对脚本语言的支持,通过脚本语言可以访问java的对象和方法,Groovy的BSF引擎是继承org.codehaus.groovy.bsf.GroovyEngine,其效率也达不到GroovyClassLoader效率,这里不做实例分析。</p>
<p>6、JSR 223是Java 6提供的一种从Java内部执行脚本编写语言的方便、标准的方式,并提供从脚本内部访问Java 资源和类的功能。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("JSR 223 begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine1 = factory.getEngineByName("groovy");
String testScript = "def num(int[] a) {return a[0] * a[1]}";
engine1.eval(testScript);
Invocable inv = (Invocable) engine1;
for (int i = 0; i < 100000; i++) {
inv.invokeFunction("num", new int[]{i, i + 1});
}
System.out.println("JSR 223 end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
JSR 223 begin:2014-09-17 18:55:10:665
JSR 223 end:2014-09-17 18:55:10:716
</code></pre></div></div>
<p>以上一个简单的乘法运算测试发现JSR 223的效率比GroovyClassLoader要高。怀疑是不是GroovyClassLoader加载Groovy文件及编译文件耗时比较长,将开始打印日志移至for循环上面一行,同时将JSR 223测试代码打印日志也移至for循环上面一行,测试后发现JSR 223效率任然高于GroovyClassLoader,于是将JSR 223测试程序改读取Groovy文件,并使用Groovy静态编译,效率降低,稍高于GroovyClassLoader方式(GroovyClassLoader不读取文件方式高于GroovyClassLoader读取文件,稍低于JSR 223不读取文件方式):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ScriptEngineManager factory = new ScriptEngineManager();
ScriptEngine engine1 = factory.getEngineByName("groovy");
//String testScript = "def num(int[] a) {return a[0] * a[1]}";
engine1.eval(new FileReader("D:/workspace/extendTest/src/extendTest/Test.groovy"));
//engine1.eval(testScript);
Invocable inv = (Invocable) engine1;
System.out.println("JSR 223 begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
for (int i = 0; i < 100000; i++) {
inv.invokeFunction("num", new int[]{i, i + 1});
}
System.out.println("JSR 223 end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
JSR 223 begin:2014-09-17 18:49:13:847
JSR 223 end:2014-09-17 18:49:13:969
</code></pre></div></div>
<p>注:虽然JSR 223调用方式性能大大提升,但是其性能与java相比还相差很多,以上简单的乘法运算,用java实现,100000次循环仅仅需5毫秒,所以对于实时性要求很高的功能,Groovy并不合适。</p>
<h2 id="二mvel">二、MVEL</h2>
<h3 id="-mvel介绍">. MVEL介绍</h3>
<p>MVEL为 MVFLEX Expression Language(MVFLEX表达式语言)的缩写,它是一种动态/静态的可嵌入的表达式语言和为Java平台提供Runtime(运行时)的语言。最初是作为一个应用程序框架实用程序的语言开始,该项目现已发展完全独立。MVEL通常用于执行用户(程序员)通过配置XML文件或注释等定义的基本逻辑。它也可以用来解析简单的JavaBean表达式。Runtime(运行时)允许MVEL表达式通过解释执行或者预编译生成字节码后执行。</p>
<h3 id="-mvel语法">. MVEL语法</h3>
<p>MVEL语法具体请参阅 <a href="http://mvel.codehaus.org/Language+Guide+for+2.0" title="MVEL">http://mvel.codehaus.org/Language+Guide+for+2.0</a>,这里不做介绍。</p>
<h3 id="-java中使用mvel">. java中使用MVEL</h3>
<p>Java中调用MVEL情况:</p>
<p>1、解释执行</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("interpreted begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
Map<String, Object> params = new HashMap<String, Object>();
for (int i = 0; i < 100000; i++) {
params.put("x", i);
params.put("y", i + 1);
MVEL.eval("x*y", params);
}
System.out.println("interpreted end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
interpreted begin:2014-09-18 10:54:01:162
interpreted end:2014-09-18 10:54:01:879
</code></pre></div></div>
<p>2、编译后执行(加快执行)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>System.out.println("compiled begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
ExpressionCompiler compiler = new ExpressionCompiler("x * y");
CompiledExpression exp = compiler.compile();
Map<String, Object> params1 = new HashMap<String, Object>();
for (int i = 0; i < 100000; i++) {
params.put("x", i);
params.put("y", i + 1);
MVEL.executeExpression(exp, params);
}
System.out.println("compiled end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
compiled begin:2014-09-18 10:57:50:471
compiled end:2014-09-18 10:57:50:632
</code></pre></div></div>
<p>以上执行结果看出,编译后执行效率明显高于解释执行。</p>
<h2 id="三groovymveljavascript-调用性能分析">三、Groovy、MVEL、javascript 调用性能分析</h2>
<p>1、java中调用javascript测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("javascript");
String str = "function num (x,y) { return x*y }";
try {
engine.eval(str);
} catch (ScriptException e) {
e.printStackTrace();
}
Invocable invoke = (Invocable) engine;
System.out.println("javascript begin:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
try {
for (int i = 0; i < 100000; i++) {
invoke.invokeFunction("num", i, i + 1);
}
} catch (ScriptException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println("javascript end:"+ new SimpleDateFormat("yyyy-MM-dd HH:mm:ss:SSS").format(new Date()));
输出:
javascript begin:2014-09-18 11:28:11:661
javascript end:2014-09-18 11:28:16:348
</code></pre></div></div>
<p>2、性能分析</p>
<p>从以上对Java中调用Groovy、MVEL、javascript实现的一个简单的乘法运算100000次循环的结果来看:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Groovy JSR 223调用方式:51毫秒
MVEL编译后执行:161毫秒
javascript调用:4秒多
</code></pre></div></div>
<p>MVEL官网给出性能报告显示MVEL性能高于Groovy,不知道其什么数据测试场景,看官网报告测试的Groovy版本是1.5.7,低于本文测试的Groovy2.0版,从Groovy1.6开始才开始引入Cache功能,并且到了2.0才加入静态编译,所以MVEL官网的测试报告我觉得已经不能够正确说明Groovy的实际性能,至少从本文例子中看出Groovy2.0性能是明显优于MVEL2.2的。</p>
<h2 id="四总结">四、总结</h2>
<p>动态语言给我们实际编程中带来很多方便,比如常用的规则引擎,然而性能与功能不能同时得到满足,从本文测试中看出无论是Groovy还是MVEL都是实现动态逻辑配置的不错选择,但是遇到高实时性需求时,可能两者就不是特别适合,因为两者的性能与Java本身相比还是有数倍的差距。</p>
jQuery源码解读[14] -- AJAX(2)
2014-08-27T00:00:00+00:00
http://www.blogways.net/blog/2014/08/27/jQuery-source-analysis-ajax(2)
<h2 id="四ajax示例">四、Ajax示例</h2>
<p>前面已经对ajax的源码结构和API有了一些介绍,下面就介绍一些ajax方法的应用;</p>
<p>前面已经介绍过了,Ajax中最最核心的方法就是<code class="language-plaintext highlighter-rouge">$.ajax( url, options )</code>,其它的方法都是在这个方法的基础上修改options参数实现的,下面来看一下ajax支持的options参数:</p>
<table width="100%">
<tr><th width="15%">参数名</th><th width="10%">类型</th><th>说明</th></tr>
<tr><td>url</td><td>String</td><td>发送请求的地址(默认值: 当前页地址)。</td></tr>
<tr><td>type</td><td>String</td><td>请求方式 ("POST" 或 "GET"), 默认为 "GET"。注意:其它 HTTP 请求方法,如 PUT 和 DELETE 也可以使用,但仅部分浏览器支持</td></tr>
<tr><td>global</td><td>Boolean</td><td>是否触发全局 AJAX 事件(默认: true)。设置为 false 将不会触发全局 AJAX 事件,如 ajaxStart 或 ajaxStop 。可用于控制不同的Ajax事件</td></tr>
<tr><td>processData</td><td>Boolean</td><td>(默认: true) 默认情况下,发送的数据将被转换为对象(技术上讲并非字符串) 以配合默认内容类型 "application/x-www-form-urlencoded"。如果要发送 DOM 树信息或其它不希望转换的信息,请设置为 false。</td></tr>
<tr><td>async</td><td>Boolean</td><td>默认值: true。默认设置下,所有请求均为异步请求。如果需要发送同步请求,请将此选项设置为 false。
注意,同步请求将锁住浏览器,用户其它操作必须等待请求完成才可以执行。</td></tr>
<tr><td>contentType</td><td>String</td><td>发送信息至服务器时内容编码类型(默认值: "application/x-www-form-urlencoded")</td></tr>
<tr><td>timeout</td><td>Number</td><td>设置请求超时时间(毫秒),此设置将覆盖全局设置</td></tr>
<tr><td>data</td><td>Object</br>String</td><td>发送到服务器的数据。将自动转换为请求字符串格式。GET 请求中将附加在 URL 后。查看 processData 选项说明以禁止此自动转换。必须为 Key/Value 格式。如果为数组,jQuery 将自动为不同值对应同一个名称。如 {foo:["bar1", "bar2"]} 转换为 '&foo=bar1&foo=bar2'。</td></tr>
<tr><td>dataType</td><td>String</td><td>预期服务器返回的数据类型。如果不指定,jQuery 将自动根据 HTTP 包 MIME 信息返回 responseXML 或 responseText,并作为回调函数参数传递,可用值:</br>"xml": 返回 XML 文档,可用 jQuery 处理;</br>"html": 返回纯文本 HTML 信息;包含 script 元素;</br>"script": 返回纯文本 JavaScript 代码。不会自动缓存结果。;</br>"json": 返回 JSON 数据 ;</br>"jsonp": JSONP 格式,使用 JSONP 形式调用函数时,如 "myurl?callback=?" jQuery 将自动替换 ? 为正确的函数名,以执行回调函数。</td></tr>
<tr><td>username</td><td>String</td><td>用于响应 HTTP 访问认证请求的用户名</td></tr>
<tr><td>password</td><td>String</td><td>用于响应 HTTP 访问认证请求的密码</td></tr>
<tr><td>cache</td><td>Boolean</td><td>dataType 为 script 和 jsonp 时默认为 false;设置为 false 将不缓存此页面</td></tr>
<tr><td>traditional</td><td>Boolean</td><td>如果你想要用传统的方式来序列化数据,那么就设置为 true</td></tr>
<tr><td>beforeSend</td><td>Function</td><td>发送请求前可修改 XMLHttpRequest 对象的函数,如添加自定义 HTTP 头;</br>beforeSend(XMLHttpRequest) </td></tr>
<tr><td>complete</td><td>Function</td><td>请求完成后回调函数 (请求成功或失败时均调用)。参数: XMLHttpRequest 对象,成功信息字符串;</br>complete(XMLHttpRequest, textStatus)</td></tr>
<tr><td>error</td><td>Function</td><td>请求失败时将调用此方法。这个方法有三个参数:XMLHttpRequest 对象,错误信息,(可能)捕获的错误对象;</br>
error(XMLHttpRequest, textStatus, errorThrown)</td></tr>
<tr><td>success</td><td>Function</td><td>请求成功后回调函数。这个方法有两个参数:服务器返回数据,返回状态</br>
success(data, textStatus) </td></tr>
<tr><td>xhr</td><td>Function</td><td>需要返回一个 XMLHttpRequest 对象,默认在 IE 下是 ActiveXObject 而其他情况下是 XMLHttpRequest 。用于重写或者提供一个增强的 XMLHttpRequest 对象</td></tr>
</table>
<p><strong><em>.load( url, data, callback )</em></strong></p>
<p>html:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><button>Load HTML one!</button>
<button>Load HTML two!</button>
<button>Load HTML three!</button>
<div id='ajax_content'></div>
</code></pre></div></div>
<p>js:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('button').click(function(){
var val = $(this).text();
$('#ajax_content').load(
'/echo/html/',{
html: "<p>" + val + "</p>"
},function(text){
console.log(text)
});
});
</code></pre></div></div>
<p>上面的代码功能很简单,向<code class="language-plaintext highlighter-rouge">/echo/html/</code>发送一个ajax请求;请求数据为<code class="language-plaintext highlighter-rouge"><p></code>元素包装的按钮的文本内容;回调函数callback仅仅接收一个参数text(即请求返回的文本内容),将其显示到控制台console上,该回调函数会在加载完响应之后执行。</p>
<p>当单击某个按钮过后,<code class="language-plaintext highlighter-rouge"><div></code>元素里面的内容会是下面的某一个:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><p>Load HTML one!</p>
<p>Load HTML two!</p>
<p>Load HTML three!</p>
</code></pre></div></div>
<p>前面介绍过,<code class="language-plaintext highlighter-rouge">.load()</code>方法的功能是,将请求url响应直接插入到匹配元素中。可以直接将上面代码复制到<a href="http://jsfiddle.net" title="JSFiddle.net">JSFiddle.net</a>上进行测试!</p>
<p><strong><em>$.getJSON( url, data, callback )</em></strong></p>
<p>在介绍<code class="language-plaintext highlighter-rouge">$.getJSON()</code>之前,首先说一下JSON(JavaScript Object Notation,JavaScript对象表示法),</p>
<p>JavaScript对象是由一些“key-value”对组成的,可以使用“{}”来定义,而数组则可以使用“[]”来定义,JSON就是将这两种语法组合起来,通过字面量的方式来表示数据,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
"key1": "value1",
"key2': [
"array_elem1",
"array_elem2",
"array_elem3"
]
}
</code></pre></div></div>
<p>如是的JSON有很强的表达能力,能使用很少的空间大小来表示很多的数据,JSON规定了,所有的对象键及其键值都必须包含在双引号(““)中,而且函数不是有效的JSON值。</p>
<p>现在有如下一个a.json文件,其内容如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[
{
"name" : "Tom",
"birthday": "1991-01-23",
"hobbies": ["篮球","羽毛球","看电影"]
},
{
"name" : "Jhon",
"birthday": "1987-09-06",
"hobbies": ["网球","看书","听音乐"]
},
{
"name" : "James",
"birthday": "1989-10-23",
"hobbies": ["篮球","旅游","看书","玩游戏"]
}
]
</code></pre></div></div>
<p>在页面加载时,将这些信息显示到页面中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.getJSON('a.json', function(data){
var html = '';
$.each(data, function( index, item ){
html += '<div class="item">';
html += '<h2>' + item.name + '</h2>';
html += '<div class="birthday">' + item.birthday + '</div>';
html += '<ul>';
for(var i in item.hobbies){
html += '<li>' + item.hobbies[i] + '</li>';
}
html += '</ul>';
html += '</div>';
});
$('#content').html(html);
});
</code></pre></div></div>
<p>运行结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div id="content">
<div class="item">
<h2>Tom</h2>
<div class="birthday">1991-01-23</div>
<ul>
<li>篮球</li>
<li>羽毛球</li>
<li>看电影</li>
</ul>
</div>
<div class="item">
<h2>Jhon</h2>
<div class="birthday">1987-09-06</div>
<ul>
<li>网球</li>
<li>看书</li>
<li>听音乐</li>
</ul>
</div>
<div class="item">
<h2>James</h2>
<div class="birthday">1989-10-23</div>
<ul>
<li>篮球</li>
<li>旅游</li>
<li>看书</li>
<li>玩游戏</li>
</ul>
</div>
</div>
</code></pre></div></div>
<p><strong><em>$.getScript( url, callback )</em></strong></p>
<p>a.js文件,其内容如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var data = [
{
"name" : "Tom",
"birthday": "1991-01-23",
"hobbies": ["篮球","羽毛球","看电影"]
},
{
"name" : "Jhon",
"birthday": "1987-09-06",
"hobbies": ["网球","看书","听音乐"]
},
{
"name" : "James",
"birthday": "1989-10-23",
"hobbies": ["篮球","旅游","看书","玩游戏"]
}
];
var html = '';
$.each(data, function( index, item ){
html += '<div class="item">';
html += '<h2>' + item.name + '</h2>';
html += '<div class="birthday">' + item.birthday + '</div>';
html += '<ul>';
for(var i in item.hobbies){
html += '<li>' + item.hobbies[i] + '</li>';
}
html += '</ul>';
html += '</div>';
});
$('#content').html(html);
</code></pre></div></div>
<p>页面的script代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.getScript('/a.js',function(){
console.log('ajax get script done!');
});
</code></pre></div></div>
<p>在上面上面的ajax请求script成功之后,会直接执行响应的a.js文件,结果跟<code class="language-plaintext highlighter-rouge">$.getJSON</code>的结果显示一样。</p>
<p>前面也介绍过<code class="language-plaintext highlighter-rouge">$.getJSON</code>,<code class="language-plaintext highlighter-rouge">$.geScript</code>两个方法,调用最底层的ajax方法,然后为其指定了optioins,就成了这两个方法。</p>
<p>上面两个方法使用<code class="language-plaintext highlighter-rouge">$.get</code>和<code class="language-plaintext highlighter-rouge">$.ajax</code>来实现的话,其请求操作如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// $.get
$.get( 'a.json', function(data){...}, 'json' );
$.get( 'b.js', underfined, function(){
console.log('ajax get script done!');
}, 'script' );
// $.ajax
$.ajax({
url: 'a.json',
type: 'get',
dataType: 'json',
data: null,
success: function(data){...}
});
$.ajax({
url: 'b.js',
type: 'get',
dataType: 'script',
data: null,
success: function(){
console.log('ajax get script done!');
}
});
</code></pre></div></div>
<p><strong><em>$.get()、$.post()</em></strong></p>
<p>前面介绍<code class="language-plaintext highlighter-rouge">$.get()</code>和<code class="language-plaintext highlighter-rouge">$.post()</code>的源码中,可以看出它们都是通过<code class="language-plaintext highlighter-rouge">$.ajax()</code>方法来实现的,唯一不同的是type(请求方式)不同,其它的都是一样的。</p>
<p>实际上get和post请求最大的区别是get请求把查询字符串放到url中,作为url的一部分;而post请求则不是,但是在jQuery的实现中,这种方式是没有体现出来的,都是直接通过传递一个data参数,在jQuery的底层实现中会将其转换成各自不同的实现,而我们是不需要去管它是怎么实现的。</p>
<p>jQuery中get和post的原型前面也介绍过,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.get( url, data, callback )
$.post( url, data, callback )
</code></pre></div></div>
<p>可以看出其调用方式是一样的,只不过具体使用哪一种请求方式:(1)遵照服务器端代码的约定;(2)传输数据量–get方法对传输的数据量有更严格的限制。</p>
<p><strong><em>序列化表单</em></strong></p>
<p>向服务器发送数据常常会涉及到用户填写的表单,查询量较小的时候,可以手动的设置查询字符串,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.get( 'c.html', {'name': $('input[name='name']').val()}, function(data){
console.log(data);
});
</code></pre></div></div>
<p>但是当涉及到的表单数量较多的时候,手动的去序列化就不现实了,在这种情况下就可以使用jQuery提供的辅助方法<code class="language-plaintext highlighter-rouge">.serialize()</code>,该方法作用于一个jQuery对象,将匹配的DOM元素转换层能够随Ajax请求传递的查询字符串,例如前面的可以一般化为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('form').submit(function(event){
event.preventDefault();
var formValues = $(this).serialize();
$.get( 'c.html', formValues, function(){
$('#content').html(data);
});
});
</code></pre></div></div>
<p>下面我们举例说明这个序列化的结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<form action='#'>
姓名:<input type='text' name='name'></br>
年龄:<input type='text' name='age'></br>
密码:<input type='password' name='password'></br>
<input type='submit'/>
</form>
// js
$('form').submit(function(evnet){
event.preventDefault();
var va = $(this).serialize();
$('#content').html(data);
});
</code></pre></div></div>
<p>当用户填写<code class="language-plaintext highlighter-rouge">tom,20,tom123456</code>,然后单击提交,就会看到控制台输出了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>name=tom&age=20&password=tom123456
</code></pre></div></div>
<p><strong><em>.ajaxStart / .ajaxStop</em></strong></p>
<p>前面我么说的一直都是在处理事件的响应,其实很多的时候,在调用ajax之前或调用过程中也需要处理一些事件,比如说:为了增强用户体验,在发送请求之间在页面显示一个<code class="language-plaintext highlighter-rouge">Loading</code>,在请求成功之后再将<code class="language-plaintext highlighter-rouge">Loading</code>去掉,再显示请求结果。</p>
<p>jQuery为我们提供了实现这样功能的函数,其中<code class="language-plaintext highlighter-rouge">.ajaxStart()</code>是在ajax尚未进行其它ajax时调用该函数,而<code class="language-plaintext highlighter-rouge">.ajaxStop()</code>是在最后一次请求结束之后再调用。jQuery提供的这些功能函数是全局性的,无论代码注册的位置在哪,只要满足了执行的条件,它都会运行。</p>
<p>例如实现上面说的<code class="language-plaintext highlighter-rouge">Loading</code>,先在html需要的位置插入一个显示加载中的显示元素,如<code class="language-plaintext highlighter-rouge"><div id='loading'>Loading</div></code>,并将其设置为默认不显示,即添加一个样式style,<code class="language-plaintext highlighter-rouge">display: none;</code>,则html如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div id='loading' style='display: none;'>
Loading!
</div>
</code></pre></div></div>
<p>然后在js代码中需要的地方添加<code class="language-plaintext highlighter-rouge">.ajaxStart</code>即可,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ajaxStart(function(){
$('#loading').show();
}).ajaxStop(function(){
$('#loading').hide();
});
</code></pre></div></div>
<p>前面一篇介绍列出了所有jQuery提供的监听这些事件的方法,可以用来完成各种需要的操作,感兴趣的可以去尝试一下。</p>
<p><strong><em>ajax中的Deferred对象</em></strong></p>
<p>对于<code class="language-plaintext highlighter-rouge">$.get/post</code>,<code class="language-plaintext highlighter-rouge">.load</code>等快捷的Ajax方法来说,并没有提供错误回调函数,只是提供了一个请求成功的回调函数,因此在错误发生的时候的处理函数需要通过其它方式来完成,比如说前面介绍过的Deferred延迟对象,它可以通过<code class="language-plaintext highlighter-rouge">.done()</code>,<code class="language-plaintext highlighter-rouge">.fail()</code>或<code class="language-plaintext highlighter-rouge">.always()</code>来为ajax请求添加相应的事件回调函数即可。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.get( 'c.html', formValues, function(){
$('#content').html(data);
}).fail(function(jqXHR){
$('#content')
.html('An error occurred:' + jqXHR.status)
.append(jqXHR.responseText);
}).always(function(){
console.log('ajax stop!');
});
</code></pre></div></div>
<p>更多的相关内容可以参考前面关于Deferred对象的讲解。</p>
<p><strong><em>修改默认选项</em></strong></p>
<p>使用<code class="language-plaintext highlighter-rouge">$.ajaxSetup()</code>方法可以修改调用Ajax方法是,每个选项的默认值,接受与<code class="language-plaintext highlighter-rouge">$.ajax()</code>相同的对象参数选项,如本文开头的表格所示,之后的所有Ajax请求都将使用传递给该函数的选项,除非用户显示的指定同名参数(将会覆盖默认设置)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 修改默认选项
$.ajaxSetup({
url: 'd.html',
type: 'post',
dataType: 'html'
});
// 再次调用ajax
$.ajax({
type: 'get',
success: function(data){
$('#content').html(data);
}
});
</code></pre></div></div>
<p>前面修改默认选项时,为ajax方法指定了默认的url(<code class="language-plaintext highlighter-rouge">d.html</code>)和dataType,后面调用时不需要在明确指定,如果再设置了与前面同名的选项,会覆盖前面的,就像这里的type,默认是post方式,而此处显示的指定了get方式,那么在本次的ajax请求会使用get方式去请求,如果下次没有显示指定请求方式,仍然会使用post方式去请求。</p>
<p><strong><em>部分加载页面</em></strong></p>
<p>前面介绍过的<code class="language-plaintext highlighter-rouge">.load()</code>方法,会将指定的文档<strong>全部</strong>添加到匹配元素中,而实际上可能只需要添加一部分。比如在加载一个html文档时,需要的只是这个html文档当中的<code class="language-plaintext highlighter-rouge">class='part'</code>的元素,不需要去遍历查找,直接传递url时附带一个jQuery选择符表达式即可,jQuery会帮我们完成必要的操作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('#content').load('c.html .part');
</code></pre></div></div>
<p>实际的操作语句就只有一句,非常的简洁,感兴趣的可以去试试看,你会发现与jQuery选择符表达式无关的信息都被剔除掉了,只将需要的信息加载到了匹配元素中。</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[13] -- AJAX(1)
2014-08-26T00:00:00+00:00
http://www.blogways.net/blog/2014/08/26/jQuery-source-analysis-ajax(1)
<h2 id="一前言">一、前言</h2>
<p>AJAX,异步JavaScript和XML,涉及到如下技术:</p>
<p>(1)异步JavaScript,由于JavaScript中引入的回调函数的概念,所有后台的JavaScript代码中,后面的任务<strong>不需要</strong>在等待前面的任务执行完,每个任务执行完毕后不是去执行另一个任务,而是执行其绑定的一个或多个回调函数,因而程序的执行顺序与任务的排列顺序是不一致的、异步的。</p>
<p>一般耗时较长的任务都是使用异步操作,以避免浏览器长时间执行某一无意义的任务(如死循环等)失去响应。</p>
<p>(2)XMLHTTPRequest,在不中断浏览器其他任务的情况下,向服务器发送请求</p>
<p>(3)向服务器请求的数据类型,及预期服务器返回的数据类型。</p>
<h2 id="二源码结构">二、源码结构</h2>
<p>首先,看一下jQuery源码中,Ajax这块的方法及函数的扩展实现,总体结构如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.parseJSON = function( data ) {};
jQuery.parseXML = function( data ) {};
jQuery.parseHTML = function( data, context, keepScripts ) {};
function addToPrefiltersOrTransports( structure ) {};
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {};
function ajaxHandleResponses( s, jqXHR, responses ) {};
function ajaxConvert( s, response, jqXHR, isSuccess ) {};
jQuery.extend({
ajaxSettings: {...},
ajaxSetup: function( target, settings ) {},
ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
ajaxTransport: addToPrefiltersOrTransports( transports ),
// 主要的方法
ajax: function( url, options ) {},
getJSON: function( url, data, callback ) {
return jQuery.get( url, data, callback, "json" );
},
getScript: function( url, callback ) {
return jQuery.get( url, undefined, callback, "script" );
}
});
</code></pre></div></div>
<p>在上面的这些源码中,首先直接通过<code class="language-plaintext highlighter-rouge">jQuery.funcName</code>扩展jQuery全局函数,将jQuery提供的3个字符串转换为相应JSON、XML或HTML的API;</p>
<p>接着定义了一些ajax内部使用的函数,用于后面实现功能时调用;然后通过<code class="language-plaintext highlighter-rouge">jQuery.extend</code>将一些功能函数扩展到jQuery全局对象中去,这些函数的功能后面会有介绍,此处不详细说明。其中最主要的一个方法就是<code class="language-plaintext highlighter-rouge">ajax( url, options )</code>,ajax模块中的所有其他的ajax功能函数都是在此函数的基础上来实现的,就拿getJSON和getScript来说,它们的源代码很简单,就一行代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// getJSON
return jQuery.get( url, data, callback, "json" );
// getScript
return jQuery.get( url, undefined, callback, "script" );
</code></pre></div></div>
<p>可以看出,它们都是调用了<code class="language-plaintext highlighter-rouge">jQuery.get()</code>方法,它们本质上就是一个get请求,只不过认为的为其指定了<strong><em>预期回的数据类型</em></strong>( “json”和”script” ),那么这个get方法又是怎么实现的呢,下面请看get/post方法的实现源码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.each( [ "get", "post" ], function( i, method ) {
jQuery[ method ] = function( url, data, callback, type ) {
// shift arguments if data argument was omitted
if ( jQuery.isFunction( data ) ) {
type = type || callback;
callback = data;
data = undefined;
}
return jQuery.ajax({
url: url,
type: method,
dataType: type,
data: data,
success: callback
});
};
});
</code></pre></div></div>
<p>这里使用<code class="language-plaintext highlighter-rouge">jQuery.each()</code>遍历一个数组<code class="language-plaintext highlighter-rouge">[ "get", "post"]</code>,通过<code class="language-plaintext highlighter-rouge">jQuery[ method ] = function...</code>,将get/post方法扩展到了jQuery的全局对象中,然后来看一这两个方法的原型:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery[ method ] = function( url, data, callback, type ) {}
</code></pre></div></div>
<p>然后是其实现的代码,从中可以看出这两个方法实现的实质也是调用了<code class="language-plaintext highlighter-rouge">ajax( url, options )</code>方法,只不过是将其中的一些事先确定的参数传递给ajax方法而已。</p>
<p>接下来我们来看一下jQuery提供的一些默认的ajax设置,都保存在<code class="language-plaintext highlighter-rouge">jQuery.ajaxSettings</code>对象中,其对象结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ajaxSettings: {
url: ajaxLocation,
type: "GET",
isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
global: true,
processData: true,
async: true,
contentType: "application/x-www-form-urlencoded; charset=UTF-8",
accepts: {
"*": allTypes,
text: "text/plain",
html: "text/html",
xml: "application/xml, text/xml",
json: "application/json, text/javascript"
},
contents: {
xml: /xml/,
html: /html/,
json: /json/
},
responseFields: {
xml: "responseXML",
text: "responseText",
json: "responseJSON"
},
// 数据转换
converters: {
"* text": String,
"text html": true,
"text json": jQuery.parseJSON,
"text xml": jQuery.parseXML
},
flatOptions: {
url: true,
context: true
}
},
</code></pre></div></div>
<p>列出了ajax默认的一些设置,如果用户使用的ajax请求参数,都基本确定无需要很大的更改,则可以根据自己的需求更改这些默认的ajax设置,就不需要每次在使用ajax请求时都要指定这些参数,而用于修改ajax默认请求参数的方法源码如下,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ajaxSetup: function( target, settings ) {
return settings ?
// 根据默认设置,构建一个行的settings对象,
// 然后将修改的参数扩展到默认设置里面
ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
// 扩展ajaxSettings
ajaxExtend( jQuery.ajaxSettings, target );
}
</code></pre></div></div>
<p>其中的<code class="language-plaintext highlighter-rouge">ajaxExtend()</code>是一个ajax请求参数options的特殊扩展方法,只是将用户自定义的设置扩展到了jQuery的默认设置中去,当然同名的会发生覆盖,其基本功能跟<code class="language-plaintext highlighter-rouge">jQuery.extend</code>方法类似,感兴趣的可以到前面的讲解中去看一下相关介绍,可以看一个解析YAML的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.ajaxSetup({
accepts: {
yaml: 'application/x-yaml, text/yaml'
},
contents: {
yaml: /yaml/
},
converters: {
'text yaml': function(text){
alert(text);
return '';
}
}
});
</code></pre></div></div>
<p>这样就完成了一个ajax参数默认值的修改,一旦修改之后,后面的所有ajax都将会受到该次修改的影响:后面只要请求的数据类型是一个yaml文件,那么就会按照上面定义的这些参数来运行及转换,调用方式如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.ajax({
url: 'helloworld.yaml'
dataType: 'yaml'
});
</code></pre></div></div>
<p>其中的converters的<code class="language-plaintext highlighter-rouge">'text yaml'</code>是告诉jQuery,这个转换函数以text格式接受数据,然后以yaml格式重新解析,后面指定的就是起解析函数,此处的转换只是将内容通过警示框输出。</p>
<p>然后来看一下jQuery中ajax提供的一些监听方法,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 通过jQuery.each方法遍历数组中需要添加监听的名称,
// 使用jQuery.fn[ type ]将这些监听方法扩展到jQuery的实例对象中去,
// 在其具体实现中,通过调用当前匹配元素(jQuery实例对象)的on方法,
// 将指定的事件处理程序绑定到特定的ajax事件上
jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete",
"ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
jQuery.fn[ type ] = function( fn ) {
return this.on( type, fn );
};
});
</code></pre></div></div>
<p>从中可以看出,ajax一共提供了6个ajax事件监听,分别为:ajaxStart、ajaxStop、ajaxComplete、ajaxError、ajaxSuccess和ajaxSend,这些监听方法的功能介绍后面再做说明。</p>
<p>后面通过<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>将<code class="language-plaintext highlighter-rouge">.seriallize()</code>和<code class="language-plaintext highlighter-rouge">.seriallizeArray()</code>方法扩展到jQuery的实例对象中去,其代码实现不在做讲解,感兴趣的可以自己看看。</p>
<p>前面讲解到了ajaxSetup方法扩展一个yaml文件的ajax请求,下面来看一下jQuery源码中关于script的ajax请求的扩展,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 增加script数据类型
jQuery.ajaxSetup({
accepts: {
script: "text/javascript, application/javascript,
application/ecmascript, application/x-ecmascript"
},
contents: {
script: /(?:java|ecma)script/
},
converters: {
"text script": function( text ) {
jQuery.globalEval( text );
return text;
}
}
});
</code></pre></div></div>
<p>是不是跟我们举得例子基本是一样的吧,这里accepts中的属性会添加发送到服务器的头部信息,声明我们的脚本可以理解的特定的MIME类型,此处为</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
</code></pre></div></div>
<p>当请求的为这些MIME类型时,将其理解为script;</p>
<p>而contents属性处理数据交换的另一方,它提供一个与响应的MIME类型进行匹配的正则表达式,以尝试自动检测这个元数据当中的数据类型,此处表示会匹配script、javascript或ecmascript;</p>
<p>最后的converters中包含解析返回数据的函数,此处是调用<code class="language-plaintext highlighter-rouge">jQuery.globalEval()</code>方法在全局上下文中执行给定的JavaScript字符串,并返回此JavaScript字符串。</p>
<p>接下来的源码结构,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 定义script的预过滤器
jQuery.ajaxPrefilter( "script", function( s ) {...});
// 扩展script的传输机制
jQuery.ajaxTransport( "script", function(s) {...});
// 默认的json设置
jQuery.ajaxSetup({
jsonp: "callback",
jsonpCallback: function() {
var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
this[ callback ] = true;
return callback;
}
});
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {...});
// 通过ajax请求,将指定url响应的元素加入到匹配元素中
jQuery.fn.load = function( url, params, callback ) {};
</code></pre></div></div>
<p>这里的<code class="language-plaintext highlighter-rouge">jQuery.ajaxPrefilter()</code>函数可以添加<strong>预过滤器</strong>(即一些回调函数),在发送ajax请求之前对请求进行过滤。预过滤器会在<code class="language-plaintext highlighter-rouge">$.ajax()</code>修改或者使用它的任何选项之前调用,因此通过预过滤器可以修改这些选项或者基于新的、自定义的选项发送ajax请求。</p>
<p>在定义了特定功能的预过滤器之后,就不需要我们在ajax请求中,明确的去定义数据类型。</p>
<h2 id="三ajax方法">三、Ajax方法</h2>
<p>在看完ajax模块的源码结构之后,下面是对ajax中提供的一些方法做了一下总结:</p>
<p><strong><em>发送请求</em></strong></p>
<table width="100%">
<tr><th width="40%">Ajax方法</th><th>说 明</th></tr>
<tr><td>$.ajax( url, options )</td><td>使用传入的options生成一次ajax请求</td></tr>
<tr><td>.load( url, params, callback )</td><td>根据url生成一次ajax请求,将响应放到匹配的元素中</td></tr>
<tr><td>$.get( url, data, callback, type )</td><td>使用get方法向指定url发送一次ajax请求</td></tr>
<tr><td>$.post( url, data, callback, type )</td><td>使用post方法向指定url发送一次ajax请求</td></tr>
<tr><td>$.getJSON( url, data, callback )</td><td>向url发送一次ajax请求,<b>解析</b>预期返回的JSON数据</td></tr>
<tr><td>$.getScript( url, callback )</td><td>向url发送一次ajax请求,<b>执行</b>预期返回的JavaScript代码</td></tr>
</table>
<p><strong><em>监听请求</em></strong></p>
<table width="100%">
<tr><th width="40%">Ajax方法</th><th>说 明</th></tr>
<tr><td>.ajaxStart( fn )</td><td>绑定当任意Ajax事务开始,但没有其它Ajax事务活动时执行的处理程序</td></tr>
<tr><td>.ajaxStop( fn )</td><td>绑定当任意Ajax事务结束,但没有其它Ajax事务仍然在活动时执行的处理程序</td></tr>
<tr><td>.ajaxComplete( fn )</td><td>绑定当任意Ajax事务执行过程中,完成时执行的处理程序(无论执行失败还是成功都执行)</td></tr>
<tr><td>.ajaxError( fn )</td><td>绑定当任意Ajax事务执行过程中,发生错误时执行的处理程序</td></tr>
<tr><td>.ajaxSuccess( fn )</td><td>绑定当任意Ajax事务执行过程中,成功完成时执行的处理程序</td></tr>
<tr><td>.ajaxSend( fn )</td><td>绑定当任意Ajax事务执行过程中,开始执行时执行的处理程序</td></tr>
</table>
<p><strong><em>配置信息</em></strong></p>
<table width="100%">
<tr><th width="40%">Ajax方法</th><th>说 明</th></tr>
<tr><td>$.ajaxSettings</td><td>jQuery提供的ajax默认设置选项</td></tr>
<tr><td>$.ajaxSetup</td><td>为后续的ajax事务设置默认选项</td></tr>
<tr><td>$.ajaxPrefilter( prefilters )</td><td>在$.ajax()处理每个请求之前,会对每个Ajax请求选项做预过滤</td></tr>
<tr><td>$.ajaxTransport( transports )</td><td>为Ajax定义一个新的传输机制</td></tr>
</table>
<p><strong><em>辅助方法</em></strong></p>
<table width="100%">
<tr><th width="40%">Ajax方法</th><th>说 明</th></tr>
<tr><td>.seriallize()</td><td>将表单控件的值序列化为一个查询字符串</td></tr>
<tr><td>.seriallizeArray()</td><td>将表单空间的值序列化为一个JSON字符串</td></tr>
<tr><td>$.param( obj )</td><td>将obj对象转换为一个查询字符串</td></tr>
<tr><td>$.globalEval( code )</td><td>在全部上下文中执行给定是JavaScript字符串</td></tr>
<tr><td>$.parseJSON( json )</td><td>将给定的json字符串转换为JavaScript对象</td></tr>
<tr><td>$.parseXML( xml )</td><td>将给定的xml字符串转换为XML文档</td></tr>
<tr><td>$.parseHTML( html )</td><td>将给定的html字符串转换为DOM元素</td></tr>
</table>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[12] -- 特性属性操作
2014-08-25T00:00:00+00:00
http://www.blogways.net/blog/2014/08/25/jQuery-source-analysis-attribute
<h2 id="一前言">一、前言</h2>
<p>属性操作主要分为attr特性和prop属性操作,而一般使用的方法就三个:<code class="language-plaintext highlighter-rouge">.attr()</code>、<code class="language-plaintext highlighter-rouge">.prop</code>和<code class="language-plaintext highlighter-rouge">.val()</code>。其中<code class="language-plaintext highlighter-rouge">.attr()</code>用于操作attr特性,而<code class="language-plaintext highlighter-rouge">.prop()</code>主要用于操作prop属性,最后<code class="language-plaintext highlighter-rouge">.val()</code>则是用于操作元素的value属性值。</p>
<p>提到attr特性和prop属性,很多人都会很疑惑,它们之间有什么不同呢?</p>
<p>(1)<strong><em>attr特性</em></strong>,直接写在标签上的属性,可以通过浏览器原生的API:setAttribute、getAttribute进行设置、读取,如下所示的id,type,checked都是attr特性:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><input id='incb' type='checkbox' checked='checked'/>
</code></pre></div></div>
<p>attr特性,主要是通过<code class="language-plaintext highlighter-rouge">name='value'</code>的形式,通过NameNodeMap保存在元素节点当中,其自身作为<code class="language-plaintext highlighter-rouge">Node.ATTRIBUTE_NODE(2)</code>节点,上述的<code class="language-plaintext highlighter-rouge"><input></code>元素对应的jQuery对象结构,如下图所示:</p>
<p><img src="/images/attr.png" alt="attr 特性值 NameNodeMap" /></p>
<p>从图中可以看到attr特性在元素节点中是以NameNodeMap类型保持的。</p>
<p>(2)<strong><em>prop属性</em></strong>,prop属性和attr特性最明显的差异修饰保持方式和访问设置方式,attr要通过一些元素的API来访问设置,但是prop属性则是直接通过<code class="language-plaintext highlighter-rouge">.</code>号来进行访问和设置属性;prop属性是作为元素节点的实例对象属性来存储的。</p>
<p>就像上面attr特性值中的图片所示,其中的<code class="language-plaintext highlighter-rouge">checked: true</code>,<code class="language-plaintext highlighter-rouge">accept:""</code>,<code class="language-plaintext highlighter-rouge">autofocus: false</code>等都是prop属性值,因为作为元素节点的属性,可以直接访问:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var $incb = $('#incb');
$incb.accept = 'Test prop value!';
console.log($incb.accept);
</code></pre></div></div>
<p>结果会显示<code class="language-plaintext highlighter-rouge">Test prop value!</code>,可见上面通过对<code class="language-plaintext highlighter-rouge">.accept = '...'</code>直接赋值用于设置了prop属性,而通过<code class="language-plaintext highlighter-rouge">.accept</code>直接返回了设置的prop属性。</p>
<p>而<code class="language-plaintext highlighter-rouge">.val()</code>则是操作的是元素的value属性。</p>
<h2 id="二源码分析">二、源码分析</h2>
<p>首先,看一下通过<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>扩展到jQuery实例对象中的<code class="language-plaintext highlighter-rouge">.attr()</code>和<code class="language-plaintext highlighter-rouge">.prop()</code>方法,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attr: function( name, value ) {
return access( this, jQuery.attr, name, value, arguments.length > 1 );
}
prop: function( name, value ) {
return access( this, jQuery.prop, name, value, arguments.length > 1 );
}
</code></pre></div></div>
<p>由上面的源码可以看出,jQuery实例对象中的两个方法都是调用了access,在参数传递方面除了在第二个参数不同之外,其余都是一样的,那么让我们来看一下access函数是何方神圣,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
length = elems.length,
bulk = key == null;
// 传递的过来的key值是个对象,递归调用处理多值
if ( jQuery.type( key ) === "object" ) {
chainable = true;
for ( i in key ) {
jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
}
// 传递的value值有意义,处理单值
} else if ( value !== undefined ) {
chainable = true;
if ( !jQuery.isFunction( value ) ) {
raw = true;
}
if ( bulk ) {
// Bulk operations run against the entire set
// key值不为空,即传递过来的key值有意义
if ( raw ) {
// 如果传递过来的value不是一个函数,
// 则直接使用key和value值,调用传递过来的函数fn
fn.call( elems, value );
fn = null;
} else {
bulk = fn;
fn = function( elem, key, value ) {
return bulk.call( jQuery( elem ), value );
};
}
}
if ( fn ) { // 运行fn
for ( ; i < length; i++ ) {
fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
}
}
}
// chainable 为 arguments.length > 1 的结果
return chainable ? // 返回操作以后的数据
elems :
bulk ?
fn.call( elems ) :
length ? fn( elems[0], key ) : emptyGet;
};
</code></pre></div></div>
<p>从中可以看出,access方法没有太多代码,仅仅只是讲传递过来的参数做一定的修改,修改完成之后执行指定的函数(第二个参数所指定的函数),而主要的参数修改,就是将多值参数分解为单值操作,然后分别执行相应的函数。</p>
<p><code class="language-plaintext highlighter-rouge">.attr()</code>和<code class="language-plaintext highlighter-rouge">.prop()</code>方法在为access传递的第二个参数,分别传值为<code class="language-plaintext highlighter-rouge">jQuery.attr</code>和<code class="language-plaintext highlighter-rouge">jQuery.prop</code>,由此可见,attr特性与prop属性的get/set方法具体实现应该在这两个传值函数当中。</p>
<p><strong><em>attr: function( elem, name, value )</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>attr: function( elem, name, value ) {
var hooks, ret,
nType = elem.nodeType;
// 忽略文本、注释和属性节点的attr特性操作(get/set)
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
// 当不支持浏览器原生的getAttribute时,
// 调用prop属性设置方法来实现
if ( typeof elem.getAttribute === strundefined ) {
return jQuery.prop( elem, name, value );
}
// 所有的attr特性名都是小写
if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
name = name.toLowerCase(); // 将特性名转换为小写样式
hooks = jQuery.attrHooks[ name ] || // 如果hook已被定义,则直接抓取
( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
}
if ( value !== undefined ) { // 特性值已定义
if ( value === null ) { // 特性值为空,这删除对应属性
jQuery.removeAttr( elem, name );
} else if ( hooks && "set" in hooks &&
(ret = hooks.set( elem, value, name )) !== undefined ) {
return ret;
} else {
elem.setAttribute( name, value + "" );
return value;
}
// 如果有对应的hooks,且其中包含get方法,则调用hooks的get方法
} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
return ret;
} else {
ret = jQuery.find.attr( elem, name );
// 不存在对应特性返回undefined
return ret == null ?
undefined :
ret;
}
}
</code></pre></div></div>
<p>由源码可知,通过对参数的一些判断和修正,实现来对<code class="language-plaintext highlighter-rouge">.attr()</code>方法的重载,首先,value值是否定义,来判别是get/set,value的类型(可以是function),或者传入一个key-value值对象,都能实现类似<code class="language-plaintext highlighter-rouge">C/C++</code>中的函数重载:</p>
<p>(1)<code class="language-plaintext highlighter-rouge">.attr( key )</code>,get方法,取得指定key值(特性名)对应的特性值;</p>
<p>(2)<code class="language-plaintext highlighter-rouge">.attr( key, value )</code>,set方法,设置指定key值所示特性对应的特征值;</p>
<p>(3)<code class="language-plaintext highlighter-rouge">.attr( key, fn )</code>,set方法,设置对应key特性的特性值,为调用fn之后的返回结果(每个匹配元素单独调用);</p>
<p>(4)<code class="language-plaintext highlighter-rouge">.attr( obj )</code>,set方法,根据传入的key-value对象设置特性</p>
<p>(5)<code class="language-plaintext highlighter-rouge">.attr( key, null )</code>/<code class="language-plaintext highlighter-rouge">.removeAttr(key)</code>,删除指定key值对应的特性</p>
<p><strong><em>prop: function( elem, name, value )</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>prop: function( elem, name, value ) {
var ret, hooks, notxml,
nType = elem.nodeType;
// 忽略文本、注释和属性节点的attr特性操作(get/set)
if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
return;
}
notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
if ( notxml ) {
// 修正属性名和的绑定的hooks
name = jQuery.propFix[ name ] || name;
hooks = jQuery.propHooks[ name ];
}
// 与attr一样,通过判断是否传递value,来鉴别get/set,
// 属性的设置与获取都是通过hooks来实现
if ( value !== undefined ) { // value值已定义,相当于set
return hooks && "set" in hooks
&& (ret = hooks.set( elem, value, name )) !== undefined ?
ret :
( elem[ name ] = value );
} else { // value未定义,相当于get
return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
ret :
elem[ name ];
}
}
</code></pre></div></div>
<p>从源代码可以看出,<code class="language-plaintext highlighter-rouge">.attr()</code>和<code class="language-plaintext highlighter-rouge">.prop()</code>中并没有给出最底层调用原生API的实现,多数都是通过调用对应的hooks来实现对特性与属性的设置,由于笔者对于这个hooks也有些迷惑,还在学习中,此处不在说明。</p>
<p>虽然具体底层实现没有给出,但是源码中可以看出,通过判断value参数值是否定义,用于判断实现get/set方法。</p>
<p>(1)<code class="language-plaintext highlighter-rouge">.prop( key )</code>,get方法,取得指定key值(属性名名)对应的属性值;</p>
<p>(2)<code class="language-plaintext highlighter-rouge">.prop( key, value )</code>,set方法,设置指定key值所示属性对应的属性值;</p>
<p>(3)<code class="language-plaintext highlighter-rouge">.prop( key, fn )</code>,set方法,设置对应key属性的属性值,为调用fn之后的返回结果(每个匹配元素单独调用);</p>
<p>(4)<code class="language-plaintext highlighter-rouge">.prop( obj )</code>,set方法,根据传入的key-value对象设置属性</p>
<p>(5)<code class="language-plaintext highlighter-rouge">.removeProp(key)</code>,删除指定key值对应的属性</p>
<h3 id="附加">附加</h3>
<p>要说attr特性和prop属性的实际应用,其中attr特性中使用的最多的要算class特性来,而在一些表单元素中使用最多的属性则是value属性来,面对这些常用的特性与属性,jQuery专门为之提供来操作的API,方便开发。</p>
<h4 id="addclassremoveclasstoggleclasshasclass">addClass、removeClass、toggleClass、hasClass</h4>
<p>其中的元素的class特性无意是使用率最高的,因为在CSS层叠样式表中,用匹配符来设计样式时,基本都是使用的class,因而class成为来最常要使用的特性。</p>
<p>由于class包含很多通过空白分隔的若干个特性值,对于某些class特性值的修改非常的不容易,以前需要去编写循环操作,jQuery考虑到这些,专门提供来这几个方法来加快开发。</p>
<p><strong><em>addClass( class )</em></strong></p>
<p>为每个匹配的元素添加传入的类class</p>
<p><strong><em>removeClass( class )</em></strong></p>
<p>从每个匹配的元素中,删除传入的类class</p>
<p><strong><em>toggleClass( class )</em></strong></p>
<p>为每个匹配的元素执行后面的操作:如果传入的类class在匹配元素中已经存在,则删除此class;如果匹配的元素中不存在传入的类class,则在匹配的元素中添加类class</p>
<p><strong><em>hasClass( class )</em></strong></p>
<p>如果所有的匹配元素中,至少有一个元素包含类class则返回true,否则返回false</p>
<h4 id="valval-value-">val()、val( value )</h4>
<p><strong><em>.val()</em></strong></p>
<p>返回所有匹配元素中,第一个匹配元素的value属性值</p>
<p><strong><em>.val( val )</em></strong></p>
<p>设置所有匹配元素的value属性值为传入的val值</p>
<h2 id="三示例">三、示例</h2>
<p><strong><em>attr特性操作</em></strong></p>
<p>html:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><input id ='ic' class='hover highlight other' type='button' value='提交' />
<input id ='ic1' class='myclass' type='button' value='重置' />
</code></pre></div></div>
<p>测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var $inbtn = $('#ic');
$inbtn.attr('class'); // 去的'class'特性值,'hover highlight other'
$inbtn.attr('type', 'input'); // 将'type'设置为'input',即按钮变成来输入框
$inbtn.attr('class', null); // 删除'class'属性,及所有'class'特性值
// 将上面两部操作合并到一个对象,一次性实现
$inbtn.attr({'type': 'input','class': null});
$inbtn.removeAttr('class'); // 完成与$inbtn.attr('class', null)一样的操作
</code></pre></div></div>
<p><strong><em>prop属性操作</em></strong></p>
<p>首先看一下一些prop属性,如下图所示:</p>
<p><img src="/images/prop.png" alt="部分prop 属性" /></p>
<p>首先要说一下的是,很多特性都有一个与之想对应的属性,就拿上面<code class="language-plaintext highlighter-rouge"><input></code>元素的value特性来说,像html中那样编写,它明明是一个特性,但是在图中可以看出,在元素的属性中,也有一个与之对应的属性value。</p>
<p>它们之间的修改是关联的,修改来属性,特性值也会跟着改变,同样修改来特性值,属性值也会跟着改变。</p>
<p>测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$inbtn.prop('value'); // 返回属性value的值,此处为'提交'
$inbtn.prop('value', '修改'); // 将'value'属性修改为'修改'
$inbtn.prop('value', null ); // 删除'value'属性及其属性值
// 将上面两步操作合并到了一个对象中,一次性实现,结果是删除来'value'属性,
// 因为最后一个操作是删除'value'属性及其属性值,所以前面对'value'值的修改不会体现出来
$inbtn.prop({'value': '修改', 'value': null });
$inbtn.removeProp('value'); // 实现与$inbtn.prop('value', null )一样的操作
</code></pre></div></div>
<p><strong><em>jQuery操作class</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$inbtn.addClass('hello world other'); // 为<input>元素添加三个类hello, world, other
// 如果添加来已存在的类,会默认忽略,
// 修改后的类为hover, highlight, other, hello, world
$inbtn.removeClass('hover other'); // 删除<input>元素的两个类hover, other
// 修改后的类为highlight
$inbtn.toggleClass('hightlight bold');
// 修改后的类hover, other, bold
$inbtn.hasClass('myclass'); // 返回false,$inbtn只包含第一个<input>元素,没有myclass类
$('input').hasClass('myclass'); // 返回true,$('input')包含两个<input>元素,
// 第二个包含myclass类
</code></pre></div></div>
<p><strong><em>.val()、.val( val )</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$inbtn.val(); // 返回属性value的值,此处为'提交'
$inbtn.val('修改'); // 实现跟$inbtn.prop('value', '修改')一样的操作
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[11] -- 选择符表达式
2014-08-22T00:00:00+00:00
http://www.blogways.net/blog/2014/08/22/jQuery-source-analysis-selector-expr
<h2 id="测试用html">测试用html:##</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><body>
<span class='article'>
<h2>测试标题一</h2>
<p id='p1'>段落一,测试段落,仅供测试之用!!!<a href='#'>linkage</a></p>
<a href='#'>read more!</a>
</span>
<div class='article'>
<h2>测试标题二</h2>
<p id='p2'>段落二,测试段落,仅供测试之用!!!<a href='#'>linkage</a></p>
<a href='#'>read more!</a>
</div>
<span class='article'>
<h2>测试标题三</h2>
<p id='p3'>段落三,测试段落,仅供测试之用!!!<a href='#'>linkage</a></p>
<a href='#'>read more!</a>
</div>
<span class='article'>
<h2>测试标题四</h2>
<p id='p4'>段落四,测试段落,仅供测试之用!!!<a href='#'>linkage</a></p>
<a href='#'>read more!</a>
</div>
... // 后面省略的全部为<div class='article'>...</div>元素
<input type='button' class='article'>提交</input>
</body>
</code></pre></div></div>
<h2 id="一简单的css选择符">一、简单的CSS选择符</h2>
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>*</td><td>匹配所有元素</td></tr>
<tr><td>#id</td><td>匹配带有给定id的元素</td></tr>
<tr><td>element</td><td>给定元素类型的所有元素</td></tr>
<tr><td>.class</td><td>匹配给定类的所有元素</td></tr>
<tr><td>a, b</td><td>与a或b匹配的元素</td></tr>
<tr><td>a b</td><td>a的所有后代元素中,与b匹配的元素</td></tr>
<tr><td>a > b</td><td>作为a子元素的所以b元素</td></tr>
<tr><td>a + b</td><td>a之后的第一个与b匹配的兄弟元素</td></tr>
<tr><td>a - b</td><td>a之后的所有兄弟元素中与b匹配的元素</td></tr>
</table>
<p>测试代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('*') // 匹配上面html文档中的所有元素
$('#p1') // 匹配html文档中id='p1'的元素,即为段落一的<p>元素
$('p') // 匹配html文档中所有的<p>元素,即所有的段落
$('.article') // 匹配所有class='article'的元素
$('h2,a') // 匹配所有的<h2>或<a>元素
$('div a') // 匹配<div>元素所有后代中的,所有的<a>元素,此处为html中所有的<a>元素
$('div > a') // 匹配<div>元素的子元素中的<a>元素,此处为所有<a href='#'>read more!</a>
</code></pre></div></div>
<p></br></p>
<p>===</p>
<h2 id="二同辈元素间定位">二、同辈元素间定位</h2>
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>:nth-child(index)</td>
<td>匹配父元素的第index个子元素(从1开始计数)</td></tr>
<tr><td>:nth-child(even)</td>
<td>匹配父元素的下标(索引)为<b><i>偶数的</b></i>子元素(从1开始计数)</td></tr>
<tr><td>:nth-child(odd)</td>
<td>匹配父元素的下标(索引)为<b><i>奇数的</b></i>子元素(从1开始计数)</td></tr>
<tr><td>:nth-child(formula)</td>
<td>匹配父元素的第n个子元素(从1开始计数),formula(公式)格式为an+b,a和b为整数</td></tr>
<tr><td>:nth-last-child()</td>
<td>与:nth-child()相同(从最后一个元素,由1开始向前计数)</td></tr>
<tr><td>:first-child</td>
<td>匹配父元素的第一个子元素</td></tr>
<tr><td>:last-child</td>
<td>匹配父元素的最后一个子元素</td></tr>
<tr><td>:only-child</td>
<td>匹配父元素唯一一个子元素,若有多个元素,匹配为空</td></tr>
<tr><td>:nth-of-type()</td>
<td>与:nth-child()的功能相同,只是此方法只计相同元素</td></tr>
<tr><td>:nth-last-of-type()</td>
<td>与:nth-of-type()的功能相同,但是计数从最后一个元素开始</td></tr>
<tr><td>:first-of-type</td>
<td>同名的元素中的第一个元素</td></tr>
<tr><td>:last-of-type</td>
<td>同名元素中的最后一个元素</td></tr>
<tr><td>:only-of-type</td>
<td>没有同名的同辈元素的元素</td></tr>
</table>
测试代码:
$('.article:nth-child(1)') // 匹配第一个class='article'元素,
// 无论<div>,<span>,<input />都行,(以后简称.article元素)
$('.article:nth-child(even)') // 匹配偶数位置(2,4,6,8...)的.article元素
$('.article:nth-child(odd)') // 匹配奇数位置(1,3,5,7...)的.article元素
$('.article:nth-child(2n+1)') // 匹配奇数位置(1,3,5,7...)的.article元素
// 即当n为0,1,2,3...时,表达式2n+1的值即为匹配元素
$('.article:nth-last-child(2)') // 匹配倒数第2个.article元素
$('.article:first-child') // 匹配第一个.article元素
$('.article:last-child') // 匹配最后一个.article元素
$('.article:only-child') // 因为匹配元素中不止一个.article元素,匹配为空
$('.article:nth-of-type(2)') // 匹配同类元素中的第二个元素,
// 首先会找到含有class='article'所有元素,然后根据元素名称分类,
// 最后匹配不同分类中的第二个元素(如果存在的话)
// 此处匹配段落二和段落四所在的元素
$('.article:nth-last-of-type(1)') // 分别匹配同类元素中倒数第一个元素
// 此处匹配段落二和段落四,还有最后一个<input />元素
$('.article:first-of-type') // 分别匹配同类元素中的第一个元素
// 此处匹配段落一、段落二所在元素,和最后一个<input />元素
$('.article:last-of-type') // 分别匹配同来元素中的最后一个元素
// 雨nth-last-of-type(1)匹配结果相同
$('.article:only-of-type') // 匹配当前jQuery对象元素中,没有同名同辈元素的元素
// 如当前jQuery对象元素中,仅仅包含一个<input class="article" />...</input>元素,
// 那么此元素即为匹配元素
// 此处匹配最后一个<input />元素
</br>
===
## 三、匹配元素间定位 ##
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>:first</td>
<td>结果集中的第一个元素</td></tr>
<tr><td>:last</td>
<td>结果集中的最后一个元素</td></tr>
<tr><td>:not(selector)</td>
<td>结果集中与selector不匹配的所有元素</td></tr>
<tr><td>:even</td>
<td>结果集中的偶数元素(从0开始计数)</td></tr>
<tr><td>:odd</td>
<td>结果集中的奇数元素(从0开始计数)</td></tr>
<tr><td>:eq(index)</td>
<td>结果集中下标(索引)为index的元素(从0开始计数)</td></tr>
<tr><td>:gt(index)</td>
<td>结果集中位于给定下标(索引)之后的所有元素(从0开始计数)</td></tr>
<tr><td>:lt(index)</td>
<td>结果集中位于给定下标(索引)之前的所有元素(从0开始计数)</td></tr>
</table>
测试代码:
:first :last 与 :first-child :last-child结果一样
由于匹配元素间定位计数都是从0开始,而同辈元素间匹配是从1开始,
:even 与 :nth-child(odd) 结果一样;
:odd 与 nth-child(even) 结果一样
相同的或者类似的此处不再介绍,
$('.article:not(div,span)') // 匹配jQuery包含class='article'的不为<dib>和<span>的元素
// 此处匹配最后一个<input />元素
$('.article:eq(0)') // 匹配第一个.article元素,:eq(index)功能与nth-child(index)一样,
// 只不过:eq(index)从下标从0开始计数
$('.article:gt(3)') // 匹配从第4个.article元素之后的所有元素(不包括第四个)
$('.article:lt(3)') // 匹配从第4个.article元素之前的所有元素(不包括第四个),
// 此处匹配前三个.article元素
</br>
===
## 四、属性匹配 ##
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>[attr]</td>
<td>匹配带有属性attr的元素</td></tr>
<tr><td>[attr='val']</td>
<td>匹配attr属性的值为val的元素</td></tr>
<tr><td>[attr!='val']</td>
<td>匹配attr属性值不为val的元素</td></tr>
<tr><td>[attr^='val']</td>
<td>匹配attr属性值以val开头的元素</td></tr>
<tr><td>[attr$='val']</td>
<td>匹配attr属性值以val结尾的元素</td></tr>
<tr><td>[attr*='val']</td>
<td>匹配attr属性值包含字符串val的元素</td></tr>
<tr><td>[attr~='val']</td>
<td>匹配attr属性值是多个空格分开的字符串,其中一个为val的元素</td></tr>
<tr><td>[attr|='val']</td>
<td>匹配attr属性值为val,或者以val开头后面跟一个连接符(-)的元素</td></tr>
</table>
测试代码:
$('article > [href]') // 匹配包含class='article'元素的子元素中,带有href属性的元素
// 此处为所有的a元素,如果在<p>元素中也包含a元素,将不会匹配在内
$('article [href]') // 匹配包含class='article'元素所有后代元素中,带有href属性的元素
// 与上面一个一样,匹配所有a元素,包含在<p>元素中的<a href="">元素
$('.article [href='#']') // 匹配包含class='article'元素所有后代元素中,带有href属性的元素
// 且属性值为'#'的元素
[href!='www.baidu.com'] // 匹配属性href不为‘www.baidu.com’的元素
[href^='www'] // 匹配所有包含属性href,且href属性值以‘www’开头的元素
[href$='com'] // 匹配所有包含属性href,且href属性值以‘com’结尾的元素
[href*='baidu'] // 匹配所有包含属性href,且href属性值中包含‘baidu’的元素
[class~='hover'] // 匹配class='hover article blsf sflsj'的元素
[class|='my'] // 匹配class='my'或class='my-xxx'的元素
</br>
===
## 五、表单匹配 ##
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>:input</td>
<td>匹配所有<input>、<select>、<textarea>、<button>的元素</td></tr>
<tr><td>:text</td>
<td>匹配type='text'的<input>元素</td></tr>
<tr><td>:password</td>
<td>匹配type='password'的<input>元素</td></tr>
<tr><td>:file</td>
<td>匹配type='file'的<input>元素</td></tr>
<tr><td>:radio</td>
<td>匹配type='radio'的<input>元素</td></tr>
<tr><td>:checkbox</td>
<td>匹配type='checkbox'的<input>元素</td></tr>
<tr><td>:submit</td>
<td>匹配type='submit'的<input>元素</td></tr>
<tr><td>:image</td>
<td>匹配type='image'的<input>元素</td></tr>
<tr><td>:reset</td>
<td>匹配type='reset'的<input>元素</td></tr>
<tr><td>:button</td>
<td>匹配type='button'的<input>元素</td></tr>
<tr><td>:enabled</td>
<td>匹配启用的表单元素</td></tr>
<tr><td>:disabled</td>
<td>匹配禁用的表单元素</td></tr>
<tr><td>:checked</td>
<td>匹配选中的复选框和单选按钮元素</td></tr>
<tr><td>:selected</td>
<td>匹配选中的<option>元素</td></tr>
</table>
</br>
===
## 六、自定义选择符 ##
<table width="100%">
<tr><th width="40%">选择符</th><th>匹配</th></tr>
<tr><td>:root</td>
<td>文档的根节点</td></tr>
<tr><td>:header</td>
<td>标题元素(如<h1>、<h2>等)</td></tr>
<tr><td>:animated</td>
<td>匹配当前jQuery对象元素中,其动画正在播放的元素</td></tr>
<tr><td>:contains(text)</td>
<td>匹配当前jQuery对象元素中,包含给定文本text的元素</td></tr>
<tr><td>:empty</td>
<td>匹配当前jQuery对象元素中,不包含子节点的元素</td></tr>
<tr><td>:has(selector)</td>
<td>匹配当前jQuery对象元素中,后代元素中有和selector匹配的元素</td></tr>
<tr><td>:parent</td>
<td>匹配当前jQuery对象元素中,包含子节点的元素</td></tr>
<tr><td>:hidden</td>
<td>匹配当前jQuery对象元素中,隐藏的元素,包括通过css隐藏的元素和<input type='hidden' /></td></tr>
<tr><td>:visible</td>
<td>匹配当前jQuery对象元素中,显示的元素(即与:hidden相反的元素)</td></tr>
<tr><td>:focus</td>
<td>匹配当前jQuery对象元素中,获得焦点的元素</td></tr>
<tr><td>:lang(language)</td>
<td>匹配当前jQuery对象元素中,具有指定语言代码的元素(即指定了lang属性,或在<meta>标签中申明的)</td></tr>
<tr><td>:target</td>
<td>匹配URI标识符指向的目标元素</td></tr>
</table>
</br>
===
**未完待续。。。**
</a></p></p></span></dib></span></div></b></td></tr></b></td></tr></table>
jQuery源码解读[10] -- CSS样式应用
2014-08-22T00:00:00+00:00
http://www.blogways.net/blog/2014/08/22/jQuery-source-analysis-css
<h2 id="一css样式">一、CSS样式</h2>
<p>首先,看一下jQuery中提供的修改CSS样式的方法:(1)<code class="language-plaintext highlighter-rouge">.css( key )</code>;(2)<code class="language-plaintext highlighter-rouge">.css( key, value )</code>;(3)<code class="language-plaintext highlighter-rouge">.css( obj )</code>,都是用来修改html的样式的。</p>
<p><strong><em>css( key )</em></strong></p>
<p>取得css属性key的值,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.css('background-color');
</code></pre></div></div>
<p>取得匹配元素的<code class="language-plaintext highlighter-rouge">background-color</code>元素的值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.css(['background-color', 'font-size']);
</code></pre></div></div>
<p>取得匹配元素的<code class="language-plaintext highlighter-rouge">background-color</code>和<code class="language-plaintext highlighter-rouge">font-size</code>属性的值。</p>
<p><strong><em>css( key, value )</em></strong></p>
<p>设置css属性key的只为传入的value,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.css( 'background-color', '#0000ff' );
</code></pre></div></div>
<p>将匹配元素的<code class="language-plaintext highlighter-rouge">background-color</code>背景颜色设置为<code class="language-plaintext highlighter-rouge">#0000ff</code>蓝色。</p>
<p><strong><em>css( obj )</em></strong></p>
<p>根据传入的key-value参数设置css属性值,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.css({
background-color: '#0000ff',
font-size: 15px,
float: left;
});
</code></pre></div></div>
<p>闯入参数为一个<strong>属性 - 值</strong>一一对应的css属性对象,jQuery会为其一一对应的设置对应的css值。</p>
<p>利用两个按钮,调整某段落文字的大小:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<p class='p1'>段落一,测试文字!!修改CSS样式!</p>
<p class='p2'>段落二,测试文字!!修改CSS样式!</p>
<button id='bigger'>变大</button>
<button id='smaller'>变小</button>
// script
var $p = $('p');
$('button').on('click', functiono(){
var num = parseFloat($p.css('font-size'));
var tmp = this.id == 'bigger' ? num * 1.1 : num / 1.1;
$p.css('font-size',num);
});
</code></pre></div></div>
<p>这段代码很简单,不难,此处不再说明。</p>
<h2 id="二效果方法">二、效果方法</h2>
<h3 id="1预定义效果">1、预定义效果</h3>
<p><strong><em>显示和隐藏元素</em></strong></p>
<p>基本的<code class="language-plaintext highlighter-rouge">.hide()</code>和<code class="language-plaintext highlighter-rouge">.show()</code>不带任何参数,跟使用<code class="language-plaintext highlighter-rouge">.css('display', 'none')</code>或<code class="language-plaintext highlighter-rouge">.css('display', 'block等其它')</code>类似,用于显示和隐藏匹配元素,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<p class='p1'>段落一,测试文字!!修改CSS样式!</p>
<p class='p2' style='display: none;'>段落二,测试文字!!修改CSS样式!</p>
<p class='p3'>段落三,测试文字!!修改CSS样式!</p>
// script
$('.p1').hide(); // 隐藏段落一
</code></pre></div></div>
<p>上面的script代码,运行后会隐藏段落一,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.p1').show(); // 显示段落二
</code></pre></div></div>
<p>在段落二的html代码中,通过style属性为其指定了一个内联的CSS属性<code class="language-plaintext highlighter-rouge">display</code>,并将其设置为none,那么默认情况下,段落二将不会显示出来(默认是隐藏状态),在调用了上面的代码之后,段落二将会显示到界面上。</p>
<p>当为<code class="language-plaintext highlighter-rouge">.show()</code>和<code class="language-plaintext highlighter-rouge">.hide()</code>传递参数时,就会产生像动画一样的,持续性效果。对于jQuery提供的任何效果方法,都可以为其指定两种预设的速度参数:slow和fast。</p>
<p>使用<code class="language-plaintext highlighter-rouge">.show('slow')</code>会在600毫秒(0.6秒)内完成显示隐藏元素的效果,而传递参数为<code class="language-plaintext highlighter-rouge">'fast'</code>时的时间为200毫秒(0.2秒),而如果不显示的指定速度的参数,jQuery会默认在400毫秒(0.4秒)内完成执行的效果。</p>
<p>而如果想要指定自己需要的运行速度参数,直接传递一个数值参数即可:<code class="language-plaintext highlighter-rouge">.hide(999)</code>。</p>
<p>当然jQuery在隐藏显示元素中,不仅仅只为我们提供了这两个方法,还有一个<code class="language-plaintext highlighter-rouge">toggle()</code>方法,这个方法会显示或隐藏匹配的元素:当隐藏的元素调用此方法后会显示出来,而显示的元素调用此方法后会被隐藏。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('<button>toggle</button>')
.appendTo('body')
.click(function(){
$('p').toggle('fast');
});
</code></pre></div></div>
<p>上面代码在<code class="language-plaintext highlighter-rouge"><body></code>元素后面添加一个按钮,绑定一个单击事件,点击显示或隐藏所有段落。</p>
<p>而上面介绍的3个方法在接受一个数值作为显示、隐藏元素速度的同时,还可以传递第二个参数,作为一个回调函数,即当完成了显示、隐藏的任务后,调用该回调函数,原型入下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.show('fast', function(){...});
.hide('slow', function(){...});
.toggle(1000, function(){...});
</code></pre></div></div>
<p><strong><em>淡入、淡出</em></strong></p>
<p>jQuery除了提供简单的隐藏显示的方法之外,还提供了另一些不同持续效果的隐藏显示方法,这里要将的是淡入和淡出。</p>
<p>跟前面的隐藏显示元素一样,提供了三个方法,都能接收数值作为速度参数,同样也可以接收一个函数作为回调函数,原型如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.fadeIn([speed], [callback])
.fadeOut([speed], [callback])
.fadeToggle([speed], [callback])
</code></pre></div></div>
<p>其使用方法跟前面一样,不再详细给出。</p>
<p>其中还涉及到一个<code class="language-plaintext highlighter-rouge">.fadeTo(speed, opacity, [callback])</code>,这个方法可以调整匹配元素的不透明度,感兴趣的可以尝试一下。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('<button>fadeTo</button>')
.appendTo('body')
.click(function(){
$('p').fadeTo(1000, 0.2);
});
</code></pre></div></div>
<p>上面的代码,通过想html中添加一个带事件click处理程序的按钮,在其单击事件中,通过<code class="language-plaintext highlighter-rouge">.fadeTo</code>改变所有段落的不透明度(在1秒内,将不透明度由1 -> 0.2)</p>
<p><strong><em>滑入、滑出</em></strong></p>
<p>滑入和滑出是jQuery提供的另一种显示、隐藏元素的方法,接收的参数跟前面两种方法完全一样,只是其持续效果不同,原型如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.slideDown([speed], [callback])
.slideUp([speed], [callback])
.slideToggle([speed], [callback])
</code></pre></div></div>
<p>使用方法不在给出!</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[9] -- DOM操作方法应用
2014-08-21T00:00:00+00:00
http://www.blogways.net/blog/2014/08/21/jQuery-source-analysis-dom
<h2 id="一插入新元素">一、插入新元素</h2>
<p>首先,操作DOM要分为两类,(1)操作属性;(2)操作元素;而此处暂时只分析操作DOM树中的元素。</p>
<p>在学习jQuery以来,我们使用最多的要数<code class="language-plaintext highlighter-rouge">$()</code>了,通过传递document判断文档是否加载完成,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(....);
</code></pre></div></div>
<p>通过传递一个CSS选择符来选择DOM树中的元素,生成一个包含所选元素的jQuery对象,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div') // 选择DOM树中所有类名为“div”的元素,即class='div'
</code></pre></div></div>
<p>现在再向大家介绍<code class="language-plaintext highlighter-rouge">$()</code>的另一个用法,就是创建新的DOM元素,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('<div class="create by jquery">create by jquery</div>');
</code></pre></div></div>
<p>就像上面代码所示,只要为<code class="language-plaintext highlighter-rouge">$()</code>传递一个html的字符串,jQuery将会创建一个对应的DOM元素,当然这仅仅是创建了一个DOM元素,没有添加到DOM中,因而无法显示出来!</p>
<p>而jQuery中将创建元素插入到DOM树的方法有八个,它们分别是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.append / .prepend / .after / .before
.appendTo / .prependTo / .insertAfter / .insertBefore
</code></pre></div></div>
<p>它们的功能都是插入元素,但是其插入方式有比较大的差异。</p>
<p><strong><em>.append( content )和.appendTo( selector )</em></strong></p>
<p>append,在所以匹配元素的<strong>内部</strong>的<strong>末尾</strong>插入content,匹配元素就是调用此方法的jQuery对象中所包含的DOM元素,</p>
<p>appendTo,将匹配的元素插入到selector选择器匹配的元素的<strong>内部</strong>的<strong>末尾</strong>中,当前这里匹配的元素跟上面的是一样的,可能唯一有些区别的是,这里的匹配元素可以使用上面所说的方法创建的jQuery对象。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('body').append('<div class="create by jquery">create by jquery</div>');
$('<div class="create by jquery">create by jquery</div>').appendTo('body');
</code></pre></div></div>
<p>上面所示两行代码,虽然书写方式不同,但是其含义与实现的功能都是一样的,就是将html代码,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class="create by jquery">create by jquery</div>
</code></pre></div></div>
<p>添加到<code class="language-plaintext highlighter-rouge"><body></code>元素的末尾,而实际上也真是这样,可以将代码复制到<a href="http://jsfiddle.net/" title="JSFiddle.net">JSFiddle.net</a>进行测试,看是否将元素插入到了DOM树中。</p>
<p><strong><em>.prepend( content )和.prependTo( selector )</em></strong></p>
<p>这两个函数与前面的append与appendTo类似,只不过这两个函数是将元素插入匹配元素<strong>内部</strong>的<strong>开始</strong>,而不是末尾,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('body').prepend('<div class="create by jquery">create by jquery</div>');
$('<div class="create by jquery">create by jquery</div>').prependTo('body');
</code></pre></div></div>
<p>运行结果就不再过多的描述了,感兴趣的可以去<a href="http://jsfiddle.net/" title="JSFiddle.net">JSFiddle.net</a>进行测试!</p>
<p><strong><em>.after( content )和.insertAfter( selector )</em></strong></p>
<p>after,在每个匹配元素<em>外部</em>的后面插入content,</p>
<p>insertAfter,将匹配的元素插入到selector选择符匹配的元素的<strong>外部</strong>的后面,</p>
<p>这里要说一下的是,这里的<strong>外部</strong>和前面的<strong>内部</strong>,将的是元素的外部或内部,其内在含义就是:</p>
<p>(1)、在内部插入元素,表示将插入的元素作为自己的内容插入,即对某个<code class="language-plaintext highlighter-rouge"><div></code>元素调用内部插入元素方法,那么结果就是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div>(原来的内容)+(添加的内容)</div>
</code></pre></div></div>
<p>原来的内容加上新添加的内容作为行的<code class="language-plaintext highlighter-rouge"><div></code>元素的内容。</p>
<p>(2)、在外部插入元素,表示将新插入的元素灬内容作为自动的兄弟结点,例如对class为div的元素进行<strong>外部</strong>插入一个class为after的<code class="language-plaintext highlighter-rouge"><div></code>元素,结果就如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class='div'> content </div>
<div class='after'> content 1 </div>
</code></pre></div></div>
<p><strong><em>.before( content )和.insertBefore( selector )</em></strong></p>
<p>这两个方法与after和insertAfter类似,都是将内容插入到匹配元素<strong>外部</strong>,而不同的是这两个方法是将内容插入匹配元素的<strong>外部</strong>的<strong>前面</strong>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').before('<div class="before"> content </div>');
$('<div class="before"> content </div>').insertBefore('.div');
</code></pre></div></div>
<p>这两行代码的含义是一样的,将<code class="language-plaintext highlighter-rouge"><div class='before'> content </div></code>作为所以包含class为div的元素兄长结点插入,即将该html插入到匹配结点之前,结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class='before'> content </div>
<div class='div'> content </div>
</code></pre></div></div>
<h2 id="二移动元素">二、移动元素</h2>
<p>用于测试的html文档:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<body>
<div class='div'>
<span class='span'>&lt;span&gt;<a class='a' href='#'>&lt;a&gt;</a></span>
<p class='p'>&lt;p&gt;</p>
</div>
</body>
</html>
</code></pre></div></div>
<p>前面通过<code class="language-plaintext highlighter-rouge">$()</code>创建DOM元素,然后通过8个方法将其添加到DOM树中,其中</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.appendTo / .prependTo / .insertAfter / .insertBefore
</code></pre></div></div>
<p>这四个方法调用对象为jQuery实例对象,即这四个方法运行的上下文环境是一个jQuery对象实例,而接收的参数为一个CSS选择符,因而它们能完成一项感觉跟它们完全没关系的功能。</p>
<p>它们可以作为插入节点元素的方法,但是这四个方法同样可以移动DOM中元素节点的位置;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.a').appendTo('body');
</code></pre></div></div>
<p>执行上面的jQuery代码过后,可以看到如下的html:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<body>
<div class='div'>
<span class='span'>&lt;span&gt;</span>
<p class='p'>&lt;p&gt;</p>
</div>
<a class='a' href='#'>&lt;a&gt;</a>
</body>
</html>
</code></pre></div></div>
<p>可以看出,<code class="language-plaintext highlighter-rouge">.appendTo</code>和<code class="language-plaintext highlighter-rouge">.prependTo</code>继承了它们是在匹配元素<strong>内部</strong>操作的,那么由此可见<code class="language-plaintext highlighter-rouge">insertAfter</code>和<code class="language-plaintext highlighter-rouge">insertBefore</code>将会是在匹配元素<strong>外部</strong>操作的,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.a').insertAfter('body');
</code></pre></div></div>
<p>结果如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<body>
<div class='div'>
<span class='span'>&lt;span&gt;</span>
<p class='p'>&lt;p&gt;</p>
</div>
</body>
<a class='a' href='#'>&lt;a&gt;</a>
</html>
</code></pre></div></div>
<p>有了这四个方法,就可以根据需求在DOM树中,将需要的节点移动到另一个位置。</p>
<h2 id="三包装元素">三、包装元素</h2>
<p>有时候,需要给很多个段落(即,<code class="language-plaintext highlighter-rouge"><p></code>元素的内容)编号时,有人可能会想用循环为每个元素编号,或者高大上一点通过each遍历来实现,在学习jQuery之前我可能跟你一样,在学了jQuery之后,再也不需要那么麻烦了,jQuery想到了这一块,为我们提供了相应的方法,首先来看下一下效果吧,html如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><body>
<p>段落一,这仅仅是一些测试文字!</p>
<p>段落二,这仅仅是一些测试文字!</p>
<p>段落三,这仅仅是一些测试文字!</p>
<p>段落四,这仅仅是一些测试文字!</p>
<p>段落五,这仅仅是一些测试文字!</p>
</body>
</code></pre></div></div>
<p>jQuery中提供了相应的包装元素的方法:<code class="language-plaintext highlighter-rouge">.wrap( content )</code>、<code class="language-plaintext highlighter-rouge">.wrapAll( content )</code>以及<code class="language-plaintext highlighter-rouge">.wrapInner( content )</code>方法;</p>
<p><strong><em>wrap( content )</em></strong></p>
<p>将匹配的每个元素包装在content中,</p>
<p><strong><em>wrapAll( content )</em></strong></p>
<p>将匹配的每个元素作为一个党员包装在content中,</p>
<p><strong><em>wrapInner( content )</em></strong></p>
<p>将匹配的每个元素<strong>内部的内容</strong>包装在content中。</p>
<p>有了这3个方法,相信实现起来不难了吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('p').wrapAll('<ol></ol>')
.wrap('<li></li>')
.wrapInner('<i></i>');
</code></pre></div></div>
<p>首先用一对闭合的<code class="language-plaintext highlighter-rouge"><ol></ol></code>(<code class="language-plaintext highlighter-rouge"><ol></code>元素用于有序编号,<code class="language-plaintext highlighter-rouge"><ul></code>元素用于无序编号),通过<code class="language-plaintext highlighter-rouge">.wrapAll()</code>方法,将所有的段落均包含在内,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ol>
<p>段落一,这仅仅是一些测试文字!</p>
<p>段落二,这仅仅是一些测试文字!</p>
<p>段落三,这仅仅是一些测试文字!</p>
<p>段落四,这仅仅是一些测试文字!</p>
<p>段落五,这仅仅是一些测试文字!</p>
</ol>
</code></pre></div></div>
<p>因为调用<code class="language-plaintext highlighter-rouge">.wrapAll()</code>方法返回的,是一个包装<code class="language-plaintext highlighter-rouge"><ol></code>元素之后的对所有段落元素的引用的jQuery对象,可以继续对其调用jQuery实例方法,<code class="language-plaintext highlighter-rouge">.wrap()</code>将匹配的每一个元素包装在参数所给的内容中,则结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><ol>
<li><p>段落一,这仅仅是一些测试文字!</p></li>
<li><p>段落二,这仅仅是一些测试文字!</p></li>
<li><p>段落三,这仅仅是一些测试文字!</p></li>
<li><p>段落四,这仅仅是一些测试文字!</p></li>
<li><p>段落五,这仅仅是一些测试文字!</p></li>
</ol>
</code></pre></div></div>
<p>这就完成了对所有段落的有序编号,只有两行代码,比自己写循环要简单非常非常的多,使用起来也非常的方便。</p>
<p>在看到了两个包装方法的应用后,再来看下最后一个<code class="language-plaintext highlighter-rouge">.wrapInner()</code>,将匹配元素每个元素<strong>内部的内容</strong>包装在所给参数值,其含义很明显了,就是将段落的内容保存到常熟中,其运行结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// <i>:倾斜
<ol>
<li><p><i>段落一,这仅仅是一些测试文字!</i></p></li>
<li><p><i>段落二,这仅仅是一些测试文字!</i></p></li>
<li><p><i>段落三,这仅仅是一些测试文字!</i></p></li>
<li><p><i>段落四,这仅仅是一些测试文字!</i></p></li>
<li><p><i>段落五,这仅仅是一些测试文字!</i></p></li>
</ol>
</code></pre></div></div>
<h2 id="四替换">四、替换</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><p><b>段落一,这仅仅是一些测试文字!</b></p>
<p>段落二,这仅仅是一些测试文字!</p>
<p>段落三,这仅仅是一些测试文字!</p>
</code></pre></div></div>
<p>在jQuery的DOM操作方法中,jQuery为我们提供了两个替换方面的方法:<code class="language-plaintext highlighter-rouge">.replaceWith( content )</code>和<code class="language-plaintext highlighter-rouge">.replaceAll( selector )</code>。还有两个设置匹配元素值的方法,也可以类似的看成是替换:<code class="language-plaintext highlighter-rouge">html()</code>和<code class="language-plaintext highlighter-rouge">text()</code>方法,</p>
<p><strong><em>replaceWith( content )</em></strong></p>
<p>将匹配的元素替换为content,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('p').replaceWith('by replaceWith ! ');
// 结果
by replaceWith !
by replaceWith !
by replaceWith !
</code></pre></div></div>
<p><strong><em>replaceAll( selector )</em></strong></p>
<p>将selector选择符匹配的元素替换为匹配的元素,</p>
<p>这里要说一下的就是,这里有两个匹配的元素,前面一个是selector(即参数)所选中的匹配元素,后面的一个是调用此方法的jQuery对象中包含的元素,即运行此方法上下文所包含的元素。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('<p>无段落序号,这仅仅是一些替代文字!</p>').replaceAll('p');
// 结果
<p>无段落序号,这仅仅是一些替代文字!</p>
<p>无段落序号,这仅仅是一些替代文字!</p>
<p>无段落序号,这仅仅是一些替代文字!</p>
</code></pre></div></div>
<p><strong><em>html( [content] )</em></strong></p>
<p>函数参数content可省略,省略后调用此方法,会返回所有匹配元素中<strong>第一个元素</strong>的HTML内容;若不省略content,会将每个匹配的元素的HTML内容读设置为content。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('p').html(); // <p>段落一,这仅仅是一些测试文字!</p>
$('p').first().html('<h2>设置HTML</h2>');
// 结果
<p><h2>设置HTML</h2></p>
<p>段落二,这仅仅是一些测试文字!</p>
<p>段落三,这仅仅是一些测试文字!</p>
</code></pre></div></div>
<p><strong><em>text( [content] )</em></strong></p>
<p>跟<code class="language-plaintext highlighter-rouge">html()</code>一样,省略content,则会返回<strong>所有匹配元素</strong>的文本内容,返回一个字符串;若不省略content,则设置每个匹配元素的文本内容为传入值content。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('p').text();
// 段落一,这仅仅是一些测试文字!段落二,这仅仅是一些测试文字!段落三,这仅仅是一些测试文字!
$('p').text('<b>无段落序号,这仅仅是一些替代文字!</b>');
// 结果
<p>&lt;b&gt;无段落序号,这仅仅是一些替代文字!&lt;/b&gt;</p>
<p>&lt;b&gt;无段落序号,这仅仅是一些替代文字!&lt;/b&gt;</p>
<p>&lt;b&gt;无段落序号,这仅仅是一些替代文字!&lt;/b&gt;</p>
</code></pre></div></div>
<h2 id="五复制元素">五、复制元素</h2>
<p>复制元素jQuery中只提供了一个方法<code class="language-plaintext highlighter-rouge">clone( [ boolean ] )</code>方法,</p>
<p>默认情况下,<code class="language-plaintext highlighter-rouge">.clone()</code>方法不会复制匹配的元素或其后代元素中绑定的事件,不过,当为此函数传递一个true的boolean值时,就可以连同事件一起赋值,即<code class="language-plaintext highlighter-rouge">.clone( true )</code>。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<button id='btn1'>测试复制</button>
// js
$('#btn1').click(function(){
alert('测试复制事件!');
});
$('#btn1').clone(true).appendTo('body');
</code></pre></div></div>
<p>测试结果是,点击复制的按钮也会弹出警示框显示<code class="language-plaintext highlighter-rouge">测试复制事件!</code>,而没有设置参数的时候,点击复制后的按钮不会有任何相应。</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[8] -- Event事件处理(2)
2014-08-20T00:00:00+00:00
http://www.blogways.net/blog/2014/08/20/jQuery-source-analysis-event(2)
<h2 id="二源码分析">二、源码分析</h2>
<h3 id="1源码结构">1、源码结构</h3>
<p>event的源码实现结构如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function returnTrue() {};
function returnFalse() {};
// 返回当前获得焦点的元素
function safeActiveElement() {};
jQuery.event = {
global: {},
// 绑定事件处理
add: function( elem, types, handler, data, selector ) {},
// 移除事件处理
remove: function( elem, types, handler, selector, mappedTypes ) {},
// 触发事件
trigger: function( event, data, elem, onlyHandlers ) {},
// 分派(执行)事件处理程序
dispatch: function( event ) {},
// 组装事件处理队列
handlers: function( event, handlers ) {},
// 封装jQuery.Event原始对象,修正event事件属性
fix: function( event ) {},
// 事件属性
props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey "
+"relatedTarget shiftKey target timeStamp view which".split(" "),
fixHooks: {},
keyHooks: {},
mouseHooks: {},
// 事件特例,如load、click、focus、blur、beforeunload等
special: {},
// 模拟事件触发
simulate: function( type, elem, event, bubble ) {}
};
// jQuery事件对象,模拟实现部分W3C标准的DOM 3级别事件模型,统一了事件的属性
jQuery.Event = function( src, props ) {};
// jQuery事件对象原型
jQuery.Event.prototype = {
// 是否已经阻止元素默认行为
isDefaultPrevented: returnFalse,
// 是否已经停止了事件传播
isPropagationStopped: returnFalse,
// 是否已经立即停止了事件传播
isImmediatePropagationStopped: returnFalse,
// 阻止元素的浏览器默认行为
preventDefault: function() {},
// 停止事件传播
stopPropagation: function() {},
// 立即停止事件传播
stopImmediatePropagation: function() {}
};
// 如果不支持submit事件冒泡(IE),则submit事件委托
if ( !support.submitBubbles ) {};
// IE change事件委托,及checkbox/radio事件修正
if ( !support.changeBubbles ) {};
// 如果不支持focusin事件冒泡,则转为focus实现(focusin -> focus, focusout -> blur)
if ( !support.focusinBubbles ) {};
jQuery.fn.extend({
// 完成一些参数调整,调用内部add方法完成事件绑定
on: function( types, selector, data, fn, /*INTERNAL*/ one ) {},
// 元素只能运行一次事件处理器函数
one: function( types, selector, data, fn ) {},
// 解除绑定:删除一个之前附加的事件句柄
off: function( types, selector, fn ) {},
// 执行事件处理函数和默认行为
trigger: function( type, data ) {},
// 执行事件处理函数,不执行默认行为,只触发匹配的第一个元素,不返回jQuery对象
triggerHandler: function( type, data ) {}
});
jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu")
.split(" "), function( i, name ) {
// 处理事件绑定
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
jQuery.fn.extend({
// 便捷方法,在匹配的元素上绑定两个事件处理函数,
// 鼠标移入时执行handlerIn,移出时执行handlerOut
hover: function( fnOver, fnOut ) {},
// 绑定
bind: function( types, data, fn ) {},
// 解绑定:删除一个之前附加的事件处理程序handler
unbind: function( types, fn ) {},
// 事件委托,调用on方法实现
delegate: function( selector, types, data, fn ) {},
// 删除事件委托,调用off实现
undelegate: function( selector, types, fn ) {}
});
</code></pre></div></div>
<p>jQuery对事件的绑定和委托及接触是通过on和off方法来实现,而on和off方法则是通过调用jQuery.event的内部方法add来remove实现,而add和remove这是调用浏览器的原生事件addEventListener/removeEventListener或attachEvent/detachEvent来实现处理指定和删除事件处理程序,其实现的方向如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>bind,delegate/unbind,undelegate -> on/off -> add/remove -> addEventListener,attachEvent/removeEventListener,detachEvent
</code></pre></div></div>
<p>一般情况下,jQuery中实现添加事件处理程序都是通过on或者事件特例(click,load,focus等)来实现的,相应的移除事件处理程序则是通过使用off方法来实现。</p>
<p><strong><em>.bind( types,data, fn )</em></strong></p>
<p><code class="language-plaintext highlighter-rouge">.bind()</code>方法用于直接附加一个事件处理程序到元素上。</p>
<p>处理程序附加到jQuery对象中当前选中的元素,所以,在<code class="language-plaintext highlighter-rouge">.bind()</code>绑定事件的时候,这些元素必须已经存在,很明显就是直接调用的,没利用委托机制。</p>
<p><strong><em>.delegate( selector, types, data, fn )</em></strong></p>
<p>delegate事件委托其实质就是调用了on方法,将此方法的参数传递给on方法,让其代调用更具体的内部函数来实现事件委托,其源码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>delegate: function( selector, types, data, fn ) {
return this.on( types, selector, data, fn );
}
</code></pre></div></div>
<p>由此可见,delegate就是调用了on方法,并将参数传递过去,自己本身并没有做任何的处理,因而可以变相的将dalegate视为on方法的别名。例如,将一个<code class="language-plaintext highlighter-rouge"><a></code>元素的事件处理程序委托到祖先元素<code class="language-plaintext highlighter-rouge"><div></code>元素上,html请参考上一篇开头的html文档<a href="/blog/2014/08/19/jQuery-source-analysis-event(1).html" title="jQuery源码解读[7] -- Event事件处理(1)">Event事件处理(1)</a>,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').on('click', '.a', function(){
alert('delegate by on()');
});
$('.div').delegate('.a', 'click', function(){
alert('delegate by delegate()');
});
</code></pre></div></div>
<p>上面两种方法实现的功能都是一样的,将<code class="language-plaintext highlighter-rouge"><a></code>元素的click事件委托给<code class="language-plaintext highlighter-rouge"><div></code>元素。</p>
<p>任何时候只要有事件冒泡到<code class="language-plaintext highlighter-rouge">$('.div')</code>上,它就查看该事件是否是click事件,以及该事件的目标元素是否与CCS选择器相匹配。如果两种检查的结果都为真的话,它就执行函数。</p>
<h3 id="2源码分析">2、源码分析</h3>
<p><strong><em>add( elem, types, handler, data, selector )</em></strong></p>
<p>elem:事件绑定的元素名称</p>
<p>types:事件处理名称,如:click、bulr、mouseout等</p>
<p>selector: 一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素</p>
<p>data:当一个事件被触发时,要传递给事件处理函数的</p>
<p>handler:事件被触发时,执行的函数</p>
<p>其部分源码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add: function( elem, types, handler, data, selector ) {
// ...
// 尝试取出事件的namespace,如click.bbb.ccc中的bbb.ccc
tmp = rtypenamespace.exec( types[t] ) || [];
// 取出事件处理类型,如click
type = origType = tmp[1];
// 取出事件命名空间,如bbb.ccc,并根据"."分隔成数组
namespaces = ( tmp[2] || "" ).split( "." ).sort();
// 事件是否会改变当前状态,如果会则使用特殊事件
special = jQuery.event.special[ type ] || {};
// 根据是否已定义selector,决定使用哪个特殊事件api,如果没有非特殊事件,则用type
type = ( selector ? special.delegateType : special.bindType ) || type;
// 更具状态改变后的特殊事件
special = jQuery.event.special[ type ] || {};
// 组装用于特殊事件处理的对象
handleObj = jQuery.extend({
type: type,
origType: origType,
data: data,
handler: handler,
guid: handler.guid,
selector: selector,
needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
namespace: namespaces.join(".")
}, handleObjIn );
// 第一次添加事件处理时,初始化事件处理函数队列
if ( !(handlers = events[ type ]) ) {
handlers = events[ type ] = [];
handlers.delegateCount = 0;
// 如果特殊事件处理程序函数返回false,使用addEventListener/attachEvent添加事件
if ( !special.setup || special.setup.call( elem, data, namespaces,
eventHandle ) === false ) {
// 为元素绑定全局事件处理函数
if ( elem.addEventListener ) {
elem.addEventListener( type, eventHandle, false );
} else if ( elem.attachEvent ) {
elem.attachEvent( "on" + type, eventHandle );
}
}
}
// 通过特殊事件add处理事件
if ( special.add ) {
// 添加事件
special.add.call( elem, handleObj );
// 设置处理函数的ID
if ( !handleObj.handler.guid ) {
handleObj.handler.guid = handler.guid;
}
}
// 将事件处理函数推入处理列表
if ( selector ) {
handlers.splice( handlers.delegateCount++, 0, handleObj );
} else {
handlers.push( handleObj );
}
// ...
}
</code></pre></div></div>
<p>从这里的源代码看,对于没有特殊事件特有监听方法和普通事件都用addEventListener来添加事件。而有特有监听方法的特殊事件,则调用相应的<code class="language-plaintext highlighter-rouge">special.add.call</code>来添加事件。总之而言,此方法是jQuery中为元素添加事件监听器的。</p>
<p><strong><em>on( types, selector, data, fn, one )</em></strong></p>
<p>types:事件处理程序名称</p>
<p>selector:一个选择器字符串,用于过滤出被选中的元素中能触发事件的后代元素</p>
<p>data:当一个事件被触发时,要传递给事件处理函数的</p>
<p>fn:事件触发时执行的程序</p>
<p>one:近在内部使用,用于标识是否该事件只能被触发一次</p>
<p>其源码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
var type, origFn;
// types可以是一个由types/handlers组成的map对象
if ( typeof types === "object" ) {
// 如果selector不是字符串
// 则将传参由( types-Object, selector, data )变成( types-Object, data )
if ( typeof selector !== "string" ) {
data = data || selector;
selector = undefined;
}
// 遍历所有types,递归调用将事件绑定到当前元素上
for ( type in types ) {
this.on( type, selector, data, types[ type ], one );
}
return this;
}
// 如果data为空,且fn为空
if ( data == null && fn == null ) {
// 则传参由( types, selector )变成( types, fn )
fn = selector;
data = selector = undefined;
// 否则如果只是fn为空
} else if ( fn == null ) {
// 且selector为字符串
if ( typeof selector === "string" ) {
// 则传参从( types, selector, data )变成( types, selector, fn )
fn = data;
data = undefined;
} else {
// 否则传参从( type, selector, data )变成( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
//如果fn为false则变成一个return false的函数
if ( fn === false ) {
fn = returnFalse;
} else if ( !fn ) {
//如果fn现在还不存在,则直接return this
return this;
}
if ( one === 1 ) { // 如果one为1
origFn = fn; // 保存,并重定义fn
fn = function( event ) {
// 这个事件只触发一次,触发完成就用off取消掉
jQuery().off( event );
return origFn.apply( this, arguments );
};
// 使用相同的ID,为了未来好删除事件
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
}
// 对所有用jQuery.event.add来添加事件
return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
}
</code></pre></div></div>
<p>从上面的代码可以看出,其实on()方法大部分的代码都是在模拟重载on()方法的重载,还有一小段代码提供了one()方法的实现,若为某个事件标识了one(即只会触发一次),那么在on()方法中就会备份别改写一个回调函数(事件触发时执行的函数),</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>origFn = fn; // 保存,并重定义fn
fn = function( event ) {
// 这个事件只触发一次,触发完成就用off取消掉
jQuery().off( event );
return origFn.apply( this, arguments );
};
// 使用相同的guid,为了未来好删除事件
fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
</code></pre></div></div>
<p>通过回调函数传递的事件对象参数, 使用off()方法将该触发事件取消绑定,达到不会在触发下一次,即仅触发了本次一次的目的,在改写的回调函数中,通过</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>origFn.apply( this, arguments );
</code></pre></div></div>
<p>调用未改写前的回调函数来实现触发后的事件处理,最后还讲副本及改写函数都设置一个统一的guid,以便未来好删除事件。</p>
<p>而实际上on()方法最核心的代码其实就只有两行,就是最后两行代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>return this.each( function() {
jQuery.event.add( this, types, fn, data, selector );
});
</code></pre></div></div>
<p>首先此处的this是一个jQuery对象,引用的是希望被绑定事件的元素,就像</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').on('click mouseleave', '.p', function(){....});
</code></pre></div></div>
<p>所示,当上面的代码运行到on()方法最后两行时,this指的就是<code class="language-plaintext highlighter-rouge">$('.div')</code>所表示的jQuery对象。</p>
<p>on()方法源码中,对当前jQuery对象调用each方法,调用<code class="language-plaintext highlighter-rouge">jQuery.event</code>的内部函数add()方法,为当前jQuery对象所包含的所有DOM元素添加事件。由此可以看出,这些源码中最最核心的还是add/remove方法,其它的各种样式的方法都在在这两个基础的方法上扩展实现的。</p>
<p>相应的事件的触发,最核心的也是<code class="language-plaintext highlighter-rouge">jQuery.event</code>中的trigger()方法。</p>
<p>从源码中可以看出,通过<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>扩展到jQuery实例对象中的所有事件方法,都是通过直接调用on,off,trigger方法,间接调用add,remove,trigger,fix等一系列<code class="language-plaintext highlighter-rouge">jQuery.event</code>中的方法来实现实现的绑定、移除和触发的,当然前面也说过,最终将其添加到浏览器DOM上还是通过浏览器原生的addEventListener/attachEvent,removeEventListener/detachEvent来实现的。</p>
<p>而jQuery中所做的就是在这些浏览器元素的添加事件监听方法的基础上,通过<code class="language-plaintext highlighter-rouge">jQuery.event</code>对象,将原生的事件对象进行扩展与增强,然后实现功能更加强大的事件处理,最后将这些增强过后的事件处理接口(如:on, delegate, bind, off 等)扩展到jQuery实例对象中,暴露给用户使用。</p>
<p><strong><em>简写绑定</em></strong></p>
<p>前面说的都是一些标准的,通用的事件处理,下面我们来看一下一些简写的事件处理,就像如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').click(function(){....}); or $('.div').change(function(){....});
</code></pre></div></div>
<p>这些方法不用通过on()方法,并为其传递事件处理类型名称,而是直接通过类型名称来创建事件处理,不需要在像下面这样创建事件处理,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').on('change', function(){....});
</code></pre></div></div>
<p>相比之下,简写事件处理显得更加语意明确,书写简单,一看就能知道需要触发的条件是什么。其实现源代码如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
"change select submit keydown keypress keyup error contextmenu")
.split(" "), function( i, name ) {
// 处理事件绑定
jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
});
</code></pre></div></div>
<p>首先通过<code class="language-plaintext highlighter-rouge">.split</code>将所有的简写事件处理分为一个数组,然后使用<code class="language-plaintext highlighter-rouge">jQuery.each</code>遍历这些简写事件处理,通过</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.fn[ name ] = function( data, fn ) {
return arguments.length > 0 ?
this.on( name, null, data, fn ) :
this.trigger( name );
};
</code></pre></div></div>
<p>将其扩展到jQuery的实例对象当中去,万变不离其宗,虽然在创建事件处理的时候是通过这些简写事件名称来创建的,实际上这些事件的添加到元素、触发,还是通过<code class="language-plaintext highlighter-rouge">jQuery.event</code>中基础的on()和trigger()方法来实现的。</p>
<h2 id="三示例">三、示例</h2>
<p>由于在前面一篇博文,介绍事件处理的一些概念的时候,已经应用了很多的示例,此处不在给出,如果需要查看,可以跳转!<a href="/blog/2014/08/19/jQuery-source-analysis-event(1).html" title="jQuery源码解读[7] -- Event事件处理(1)">Event事件处理(1)</a></p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[7] -- Event事件处理(1)
2014-08-19T00:00:00+00:00
http://www.blogways.net/blog/2014/08/19/jQuery-source-analysis-event(1)
<h2 id="一前言">一、前言</h2>
<h3 id="1事件传播">1、事件传播</h3>
<p>当页面上发生一个事件时,每个层次上的DOM元素都有机会处理借个时间,如一下html为例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><div class='div'>&lt;div&gt;
<span class='span'>&lt;span&gt;
<a class='a'>&lt;a&gt;</a>
</span>
<p class='p'>&lt;p&gt;</p>
</div>
</code></pre></div></div>
<p>当使用图形表示上面的html,其结构如下图所示:</p>
<p><img src="/images/div.png" alt="对应的html图形化结构图" /></p>
<p>从上面可以看出,<code class="language-plaintext highlighter-rouge"><a></code>元素位于<code class="language-plaintext highlighter-rouge"><span></code>,<code class="language-plaintext highlighter-rouge"><div></code>及更外层的元素内,那么在鼠标单击<code class="language-plaintext highlighter-rouge"><a></code>元素所在的区域时,明显可以看出同时也单击了其所有的父辈元素,如span、div等。也就是说,在鼠标点击内部元素时,内部元素所有的直接父辈元素也应该能获得响应这次单击的机会。</p>
<p>允许多个元素响应单击事件的策略叫做<strong><em>事件捕获</em></strong>,在事件捕获的工程中,最外层元素最先获得事件,然后向内将事件交给范围更小一级的可以捕获当前事件的元素,例如当点击<code class="language-plaintext highlighter-rouge"><a></code>元素时,其捕获过程如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>document -> 外层元素 -> div -> span -> a
</code></pre></div></div>
<p>这里的外层元素就是div元素的所有直接父辈元素,值得注意的是,<code class="language-plaintext highlighter-rouge">document</code>是所有DOM元素的父辈,即无论任何事件在捕获过程中,事件首先会交给<code class="language-plaintext highlighter-rouge">document</code>,然后在向跟具体的触发事件元素传递。</p>
<p>两外一种相反的策略叫做<strong><em>事件冒泡</em></strong>,即当事件发生时,会首先将事件传递给最具体的元素,此处为<code class="language-plaintext highlighter-rouge"><a></code>元素,然后在逐级向外传递事件,直到停止事件传播或传递到了<code class="language-plaintext highlighter-rouge">document</code>,理出当点击<code class="language-plaintext highlighter-rouge"><a></code>元素时,其冒泡过程如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>a -> span -> div -> 外层元素 -> document
</code></pre></div></div>
<p>由于不同的浏览器采用的不同的事件策略,因此在DOM标准规定了同事使用两种策略:首先,事件从一般元素到具体元素逐层<strong>捕获</strong>,然后,事件再通过<strong>冒泡</strong>返回DOM树的顶层,事件处理程序可以注册到这个过程中的任何一个阶段,而jQuery中,如无特殊说明,默认将处理程序注册到<strong>冒泡</strong>阶段。</p>
<p>虽然默认是将处理程序注册到<strong>冒泡</strong>阶段,但是事件冒泡可能会导致一些无法预料的问题,例如当我们为<code class="language-plaintext highlighter-rouge"><div></code>元素添加一个mouseout时间处理程序,当用户鼠标退出<code class="language-plaintext highlighter-rouge"><div></code>元素时,会按照预期的一样触发事件处理程序,因为事件处理程序和触发事件元素都为<code class="language-plaintext highlighter-rouge"><div></code>所以不会有其他元素响应这个事件,但是当用户鼠标指针在<code class="language-plaintext highlighter-rouge"><a></code>元素内移动到<code class="language-plaintext highlighter-rouge"><a></code>元素外时,会触发一个mouseout事件,然后向外传递到<code class="language-plaintext highlighter-rouge"><div></code>时,会触发为<code class="language-plaintext highlighter-rouge"><div></code>元素指定的事件处理程序,这种结果显然不是所期望的。</p>
<h3 id="2事件对象">2、事件对象</h3>
<p>要解决前面所说的事件<strong>冒泡</strong>所产生的问题,就需要说到事件对象了,事件对象是一种DOM结构,它会在元素获得处理事件的机会时传递给被调用的事件的处理程序,就拿前面的例子来说,当<code class="language-plaintext highlighter-rouge"><a></code>元素触发mouseout事件并冒泡到<code class="language-plaintext highlighter-rouge"><div></code>元素时,event对象对传递给<code class="language-plaintext highlighter-rouge"><div></code>元素上mouseout事件处理程序。</p>
<p><strong><em>event.target属性</em></strong></p>
<p>jQuery中,扩展了事件对象中的event.target属性(保存着发生事件的目标元素),使其在所有的浏览器中都能够是用这个属性,通过event.target,可以确定DOM中首先接收到事件的元素(即实际被单击的元素),而在事件处理程序中的this指针,是对处理事件的DOM元素的引用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function(){
$('.div').on( 'mouseout', function(event){
if( event.target == this ){
alert('mouseout div');
}
});
});
</code></pre></div></div>
<p>上面的代码就是利用了enevt事件对象,很好的解决了前面所说的mouseout事件在冒泡阶段会导致的问题。</p>
<p><strong><em>event.stopPropagation()方法</em></strong></p>
<p>同时事件对象还提供了一个.stopPropagation()方法,用于完全阻止事件冒泡,与target类似,这个方法也是DOM标准的基本方法,但是在IE8及更早版本中无法使用,而在jQuery中扩展了这个方法,可以放心的使用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function(){
$('.div').click(function(){
alert('div click');
});
$('.span').click(function(){
alert('span click');
});
$('.a').click(function(event){
alert('a click');
event.stopPropagation();
});
});
</code></pre></div></div>
<p>为<code class="language-plaintext highlighter-rouge"><div></code>、<code class="language-plaintext highlighter-rouge"><span></code>和<code class="language-plaintext highlighter-rouge"><a></code>同时添加单击事件分别显示消息,同时在<code class="language-plaintext highlighter-rouge"><a></code>元素内调用<code class="language-plaintext highlighter-rouge">event.stopPropagation();</code>,执行上面代码,当单击<code class="language-plaintext highlighter-rouge"><span></code>元素时,会弹出<code class="language-plaintext highlighter-rouge">span click</code>和<code class="language-plaintext highlighter-rouge">div click</code>,但是在单击<code class="language-plaintext highlighter-rouge"><a></code>元素时,只会弹出<code class="language-plaintext highlighter-rouge">a click</code>,由此可见stopPropagation方法成功阻止了事件的冒泡,事件处理程序只会被当前元素中执行,且事件不会继续冒泡,那么其它元素也就不会再触发此事件处理程序了。</p>
<p><strong><em>event.preventDefault()方法</em></strong></p>
<p>很多元素都会有默认的事件触发动作,例如:当点击一个<code class="language-plaintext highlighter-rouge"><a></code>元素时,会默认跳转到指定了连接;当单击表单中的submit按钮,会默认提交当前表单。在实际操作中,我们可能会需要验证某些信息,而不希望这些元素执行默认操作,通常我们的做法是在JavaScript代码中<code class="language-plaintext highlighter-rouge">return false;</code>,返回false这种做法实际上就是组合使用了.stopPropagation和.preventDefault(),阻止事件传播,阻止元素默认触发动作。</p>
<h3 id="3事件委托">3、事件委托</h3>
<p>事件冒泡虽然可能会导致一些问题,但是也为我们带来了很多的好处,而<strong>事件委托</strong>就是利用冒泡策略来实现的,顾名思义,事件委托就是将事件处理程序委托给其它元素,让其代为处理某些元素的事件处理,当然此处的<em>其它元素</em>是指其所有直接父辈元素。</p>
<p>一种委托方式是通过<code class="language-plaintext highlighter-rouge">event.target</code>来判断是否触发特定元素的委托事件,例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function(){
$('.div').click(function(event){
if($(event.target).is('.p')){
alert('delegate p to div')
} else {
alert('div click');
}
});
});
</code></pre></div></div>
<p>如上代码所示,鼠标点击<code class="language-plaintext highlighter-rouge"><p></code>元素会弹出<code class="language-plaintext highlighter-rouge">delegate p to div</code>,<code class="language-plaintext highlighter-rouge"><p></code>元素的事件处理程序委托给了<code class="language-plaintext highlighter-rouge"><div></code>元素,当用户单击<code class="language-plaintext highlighter-rouge"><p></code>元素时,本身并无事件处理程序,因此向外冒泡,触发<code class="language-plaintext highlighter-rouge"><p></code>的父元素<code class="language-plaintext highlighter-rouge"><div></code>元素的事件处理程序,其中通过<code class="language-plaintext highlighter-rouge">event.target</code>判断目标元素是<code class="language-plaintext highlighter-rouge"><p></code>元素,因而调用相应的事件处理,弹出<code class="language-plaintext highlighter-rouge">dalegate p to div</code>。</p>
<p>首先要说明的是,<code class="language-plaintext highlighter-rouge">event.target</code>引用的是触发事件元素的html文档,而不是一个jQuery对象,可以在传递了事件对象的处理程序中通过如下代码显示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>console.log( event.target );
</code></pre></div></div>
<p>当单击了<code class="language-plaintext highlighter-rouge"><span></code>元素时,输出结果如下图所示:</p>
<p><img src="/images/eventtarget.png" alt="span的event.target" /></p>
<p>因而使用<code class="language-plaintext highlighter-rouge">$(html标签)</code>创建一个对应html标签的jQuery对象,然后通过<code class="language-plaintext highlighter-rouge">is()</code>选中元素是否是希望的元素,如果是则执行委托事件处理,否则就执行其默认处理。</p>
<p>而另一种事件委托方式,就是通过jQuery内置的事件委托,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.div').on( 'click mouseleave', '.p', function(event){
if( event.type == 'click' ){
alert('delegate p\'s click to div');
} else if(event.type == 'mouseleave' ){
alert('delegate p\'s mouseleave to div');
}
});
</code></pre></div></div>
<p>上面代码所示,将<code class="language-plaintext highlighter-rouge"><p></code>元素的click和mouseleave事件委托给了<code class="language-plaintext highlighter-rouge"><div></code>元素,当单击<code class="language-plaintext highlighter-rouge"><p></code>元素时,会弹出<code class="language-plaintext highlighter-rouge">delegate p's click to div</code>,相应的当鼠标离开<code class="language-plaintext highlighter-rouge"><p></code>元素区域时,会弹出<code class="language-plaintext highlighter-rouge">delegate p's mouseleave to div</code>。</p>
<p>由上面可知,可以将某元素的事件处理程序委托给其所有直接父辈元素中的某一个元素,即冒泡阶段中,某元素之后的任何事件传递元素,而document作为所有页面元素的祖先元素,将事件委托给document很方便,但是也可能因为DOM嵌套层数太多,事件冒泡阶段的传递较多,导致事件处理效率不高,所以应该尽可能选择具体的委托元素,以减少不必要的开销。</p>
<p><strong><em>早委托</em></strong></p>
<p>如果我们需要在某个页面中,处理某个链接的单击事件,并阻止其默认事件(即,单击立即跳转到相应的链接),如果我们等到文档就绪之后在为其绑定单击事件,那么可能在绑定事件处理程序之前,该链接已经被点击,然后跳转到另一个页面了。把事件处理程序绑定到document上,不再等到加载完整的DOM结构之前就运行,即将事件委托作为一个IIFE放入<code class="language-plaintext highlighter-rouge"><head></code>中,一旦加载玩绑定函数立即绑定,那么后面加载html内容时,单击触发的所有事件都会冒泡到document元素,执行相应的功能,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(function($){
$(document).on( 'click mouseleave', '.a', function(event){
alert('a click');
event.preventDefault;
});
})(jQuery);
</code></pre></div></div>
<h3 id="3自定义事件">3、自定义事件</h3>
<p>由浏览器的DOM实现自然触发的事件对任何WEB应用来说都是至关重要的,但是jQuery代码不没有局限于此,jQuery中支持对事件的自定义,即我们可以手动为DOM元素添加一些<strong><em>自定义事件</em></strong>。</p>
<p>由于是自定义事件,因而其触发必须是手动的方式来触发,其应用方式就像函数的定义与调用一样,自定义事件就像创建一个完成某项功能的的函数,在需要完成某个功能时,通过触发方式来触发自定义事件,就像调用函数一样,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function(){
$(document).on( 'AlertHello', function(event){
alert('Hello World!');
} );
$('.p').click(function(){
$(this).trigger('AlertHello');
});
}); 当点击`<p>`元素时,成功弹出`Hello World!`,当然也可以对自定义事件传递自定义参数,如:
$(document).on( 'AlertStr', function( event, str ){
var s = str || "Hello World!";
alert(s);
}
$('.p').click(function(){
$(this).trigger( 'AlertStr', 'jQuery');
});
</code></pre></div></div>
<p>而此时单击<code class="language-plaintext highlighter-rouge"><p></code>元素时,如你所想的,弹出的是<code class="language-plaintext highlighter-rouge">jQuery</code>。</p>
<h3 id="4移除事件处理程序">4、移除事件处理程序</h3>
<p>有绑定事件处理程序,当然也有移除绑定,在jQuery中,移除事件处理程序通常是用.off()方法来实现,如移除<code class="language-plaintext highlighter-rouge"><span></code>元素的单击事件,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.span').click(function(){
alert('span click');
});
$('.span').off('click');
</code></pre></div></div>
<p>再单击<code class="language-plaintext highlighter-rouge"><span></code>元素时,不会弹出任何警示框了,开始为其绑定了一个单击事件,然后移除了单击事件click的事件处理程序,之后单击都不会弹出提示。</p>
<p>当然此处通过代码</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('.span').off('click');
</code></pre></div></div>
<p>是将<code class="language-plaintext highlighter-rouge"><span></code>元素的所有单击事件都移除,言外一致就是只要是click事件的处理程序就被移除,不在响应click事件,而现实中我们通常只是希望移除某个或某些特定的事件,这就要用到事件处理的命名空间了。</p>
<p>通过命名空间可以让.off()方法更具有针对性,避免移除仍需要的事件处理程序,而事件处理的命名空间,事件绑定事件处理程序的时候,附带传入的信息,用以表示一个或者一类的事件处理程序,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$(document).ready(function(){
$('.p').on( 'click.alerthello', function(){
alert('Hello!');
});
$('.p').on( 'click.alerthello', function(){
alert('Hello World!');
});
$('.p').on( 'click.alertjquery', function(){
alert('Hello jQuery!');
});
$('.p').off('click.alerthello');
});
</code></pre></div></div>
<p>单击<code class="language-plaintext highlighter-rouge"><p></code>元素时,只弹出警示框显示<code class="language-plaintext highlighter-rouge">Hello jQuery!</code>,而另外两个没有显示,说明成功移除了click单击事件中,<code class="language-plaintext highlighter-rouge">alerthello</code>命名空间下所有的事件处理程序,而<code class="language-plaintext highlighter-rouge">alertjquery</code>命名空间下的事件处理程序仍然保留。</p>
<h3 id="5dom2级及ie-事件处理程序">5、DOM2级及IE 事件处理程序</h3>
<p>DOM2级事件定义了两个方法,addEventListener()和removeEventListener(),用于处理指定和删除事件处理程序操作,这两个方法接收三个参数:事件处理程序名称、事件处理程序函数和一个布尔值(用于表示是否在捕获阶段调用事件处理程序,默认值为false,即在冒泡阶段调用事件处理程序)。</p>
<p>IE中实现了与DOM中类似的两个方法:attachEvent()和detachEvent(),由于IE8及更早版本只支持冒泡阶段,因而这两个方法只接收两个参数:事件处理程序名称和事件处理程序函数。</p>
<p>jQuery中的为DOM元素添加和删除事件处理程序中,最底层的操作也是通过这四个方法来实现的,实现了对IE及其它浏览器的事件处理程序的兼容。</p>
<p><strong><em>addEventListener( type, listener, capture )</em></strong></p>
<ul>
<li>type 事件处理程序类型,即要监听的事件名称,例如click mouseout mouseleave等;</li>
<li>listener 事件处理程序的具体实现函数,当规定的事件发生时,执行该函数;</li>
<li>capture 如果为true,表示在事件捕获阶段调用事件处理程序。</li>
</ul>
<p>addEventListener()可能被调用多次,在同一个节点上为同一种类型的事件注册多个事件句柄。但要注意,DOM不能确定多个事件句柄被调用的顺序。</p>
<p><strong><em>removeEventListener( type, listener, capture )</em></strong></p>
<ul>
<li>type 要删除事件处理程序的类型</li>
<li>listener 要删除的事件程序的函数</li>
<li>capture 如果要删除是捕获阶段的事件处理程序,则为true;如果要删除的是冒泡阶段的事件处理程序,则为false</li>
</ul>
<p><strong><em>attachEvent( type, listener )</em></strong></p>
<ul>
<li>type 事件处理程序名称,带有一个“on”前缀,例如onclick onmouseout onmouseleave等;</li>
<li>listener 事件处理程序的具体实现函数,当规定的事件发生时,执行该函数;</li>
</ul>
<p>这个方法是一个特定与IE的事件注册方法。它和标准的addEventListener()方法(IE不支持它)具有相同的作用,只是两者传递的参数不同,而且IE中不支持<strong><em>事件捕获</em></strong>。</p>
<p><strong><em>detachEvent( type, listener )</em></strong></p>
<ul>
<li>type 要删除的事件监听器所针对的事件的类型,带有一个on前缀。</li>
<li>listener 要删除事件处理程序函数</li>
</ul>
<p>这个方法解除掉由attachEvent()方法所执行的事件句柄函数注册。它是removeEventListener()方法的特定与IE的替代。要为一个元素删除一个事件函数句柄,只需要使用你最初传递attachEvent()的相同参数来调用detachEvent()。</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[6] -- Queue队列
2014-08-15T00:00:00+00:00
http://www.blogways.net/blog/2014/08/15/jQuery-source-analysis-queue
<h2 id="一前言">一、前言</h2>
<p>队列是一种特殊的线性表,它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作。进行插入操作的端称为队尾,进行删除操作的端称为队头.最先插入在元素将是最先被删除;反之最后插入的元素将最后被删除,因此队列又称为“<strong><em>先进先出FIFO</em></strong>”(First In First Out)的线性表。</p>
<p>jQuery提供了<code class="language-plaintext highlighter-rouge">jQuery.queue/dequeue</code>和<code class="language-plaintext highlighter-rouge">jQuery.fn.queue/dequeue</code>,实现对队列的入队、出队操作,不同于队列定义的是,<code class="language-plaintext highlighter-rouge">jQuery.queue</code>和<code class="language-plaintext highlighter-rouge">jQuery.fn.queue</code>不仅执行出队操作,返回队头元素,还会自动执行返回的队头元素。</p>
<p><strong><em>有一点要注意的是,在jQuery中,队列Queue只应用于动画模块!</em></strong></p>
<h2 id="二源码分析">二、源码分析</h2>
<h3 id="1函数原型介绍">1、函数原型介绍</h3>
<p>jQuery中,队列的实际实现方式很简单,代码量也不是很大,总共就70~80行代码左右,直接通过<code class="language-plaintext highlighter-rouge">jQuery.extend</code>和<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>分别将其实现扩展到jQuery的全局对象和实例对象中去,其函数原型,如下所示:</p>
<p><strong><em>jQuery.extend扩展全局对象:</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
queue: function( elem, type, data ) {},
dequeue: function( elem, type ) {},
_queueHooks: function( elem, type ) {}
});
</code></pre></div></div>
<p><strong><em>jQuery.fn.extend扩展实例对象:</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.fn.extend({
queue: function( type, data ) {},
dequeue: function( type ) {},
clearQueue: function( type ) {},
// Get a promise resolved when queues of a certain type
// are emptied (fx is the type by default)
// 当队列中指定类型的函数执行完毕后,(fx是默认的类型)
// 返回一个解决延迟对象并完成回调函数后的promise对象,
promise: function( type, obj ) {}
});
</code></pre></div></div>
<p>从上面的代码原型可以看出,全局对象对外提供了两个API:<code class="language-plaintext highlighter-rouge">queue/dequeue</code>,这两个方法有两个作用,它们既是setter,又是getter,实例对象提供了四个API:<code class="language-plaintext highlighter-rouge">queue、dequeue、clearQueue、promise</code>。</p>
<p>其中实例对象的方法都是在调用全局对象的queue/dequeue方法的基础上实现的。而<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>中扩展了promise方法,实现了对延迟对象的支持,通过在promise中创建一个Deferred实例,修改相应的方法并返回承诺(promise)。</p>
<h3 id="2jqueryqueuejquerydequeue-源码分析">2、jQuery.queue、jQuery.dequeue 源码分析</h3>
<p><strong><em>queue( elem, type, data )</em></strong></p>
<p>其源码,如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>queue: function( elem, type, data ) {
var queue;
if ( elem ) { // elem必须存在,不然将没有含义
// 改名,每个都要加上queue,用以标识不同的队列
type = ( type || "fx" ) + "queue";
// 取出缓存数据,即缓存队列
queue = jQuery._data( elem, type );
// 如果data存在,才会进行后边转换数组、入队等操作,可以加速取出整个队列
// 如果data不存在,直接跳到执行return queue || [],
// 就相当于一个getter方法
if ( data ) {
// 如果队列不存在,或者队列存在且data是一个数组
if ( !queue || jQuery.isArray(data) ) {
// 通过创建新的数组来实现队列
queue = jQuery._data( elem, type, jQuery.makeArray(data) );
} else {
// 如果队列存在,这直接将传递的数据加入队列
// queue实际是一个数组
queue.push( data );
}
}
// 返回队列(即入队的同时,返回整个队列)
// 简洁实用的避免空引用的技巧
// 当queue存在时,直接返回queue,否则返回[]
return queue || [];
}
}
</code></pre></div></div>
<p>第一个参数elem是DOM元素,第二个参数type是字符串,第三个参数data可以是function或数组。前提提过它既是setter,有事getter,</p>
<ul>
<li>当传递三个参数的时候( elem,type,data ),它就是一个setter,将传递过来的数据加到队列;</li>
<li>当传递两个参数的时候( elem,type ),他就是一个getter,将type所指定的队列数据返回。</li>
</ul>
<p>从中可以看出,队列Queue中的数据是通过缓存数据Cache来实现的,而在实现时,调用的是<code class="language-plaintext highlighter-rouge">jQuery._data( elem, type, data )</code>来创建缓存数据Cache,通过<code class="language-plaintext highlighter-rouge">jQuery._data( elem, type )</code>来返回/读取缓存的数据,当queue存在,直接调用<code class="language-plaintext highlighter-rouge">queue.push( data )</code>将数据data加入队列。</p>
<p>由<code class="language-plaintext highlighter-rouge">type = ( type || "fx" ) + "queue";</code>可见,队列Queue在jQuery中是专职处理fx动画的。</p>
<p>===</p>
<p><strong><em>dequeue( elem, type )</em></strong></p>
<p>匹配的元素上执行队列中的下一个函数,其源码,如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 出队并执行
// 调用jQuery.queue取得整个队列,在调用shift取出第一个元素
dequeue: function( elem, type ) {
type = type || "fx";
var queue = jQuery.queue( elem, type ), // 取得队列
startLength = queue.length,
fn = queue.shift(), // 取出队列中的第一个元素
hooks = jQuery._queueHooks( elem, type ),
next = function() {
jQuery.dequeue( elem, type );
};
// 如果取出的fn是一个正在执行中标准动画fx,抛弃执行哨兵(inprogress),再取一个
if ( fn === "inprogress" ) {
fn = queue.shift();
startLength--;
}
if ( fn ) {
// 如果是标准动画,则在队列头部增加处理中标记属性,阻止fx自动处理
if ( type === "fx" ) {
// 在队列头部增加inprogress标记
queue.unshift( "inprogress" );
}
// clear up the last queue stop function
delete hooks.stop;
// 执行取出的fn,并传入回调函数jQuery.dequeue
// 可以看到fn必须是函数,否则会出错
fn.call( elem, next, hooks );
}
// 此时的队列成为空队列,实质是一个空数组,
if ( !startLength && hooks ) {
// 调用队列里面的hooks(通过jQuery._removeData)
// 删除type对应的空数组,完成队列数据的清理工作
hooks.empty.fire();
}
}
</code></pre></div></div>
<p>源码中先从缓存Cache中取出队列数据,再判断队列的长度,然后通过<code class="language-plaintext highlighter-rouge">queue.shift();</code>从队列中取出队头元素,然后做好一个预处理生成下一个的next,经判断修改最后调用相应的<code class="language-plaintext highlighter-rouge">.call()</code>方法执行对头函数,在队列中所有Callbacks都执行完毕后,即<code class="language-plaintext highlighter-rouge">!startLength</code>为真时,调用hooks完成清理工作中。</p>
<h2 id="三示例">三、示例</h2>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function fn1(){
alert('test 1');
};
function fn2(){
alert('test 2');
};
</code></pre></div></div>
<p><strong><em>queue( elem, type, data )</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// set
var vbody = $('body');
$.queue( vbody, 'test', fn1 );
$.queue( vbody, 'test', fn2 );
// get
var q = $.queue( vbody, 'test' );
console.log( q ); // 输出[function, function] 在Chrome的JavaScript控制台中显示,如下图所示:
</code></pre></div></div>
<p><img src="/images/queue_get.png" alt="Queue get" /></p>
<p>上面是将elem当作一个参数传递给queue,其实还可以不用这样写,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('body').queue( 'test', fn1 );
$('body').queue( 'test', fn2 );
$('body').queue( 'test' );
</code></pre></div></div>
<p>这种写法跟是那个面显示设置elem,所实现的功能是完全一样的!</p>
<p></br></p>
<p>===</p>
<p><strong><em>dequeue( elem, type)</em></strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.dequeue( vbody, 'test' ); // test 1
$.dequeue( vbody, 'test' ); // test 2
</code></pre></div></div>
<p>而在实际的应用当中,我们不可能为队列中每个元素进行手动的通过dequeue来调用,前面介绍dequeue的时候说过,<strong>做好一个预处理生成下一个的next</strong>,next方法可以让队列中中断的地方连续起来执行,不需要在手动的去允许,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function fn3( next ){
alert('test 3');
next();
};
$.queue( vbody, 'test', fn3 );
$.queue( vbody, 'test', fn2 );
$.dequeue( vbody, 'test' ); // test 3 , test 2
</code></pre></div></div>
<p>像上面这样,只要在传入的函数的参数当中,增加一个next方法变量,然后在当前函数功能执行完成后,调用next方法,队列动画就会继续向后面进行,例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var $div = $('div.testqueue');
$div.css({postition: 'relative'})
.fadeTo('fast', 0.5)
.fadeTo('slow', 1.0)
.slideUp('slow')
.queue(function(next){
$div.css({background: '#f00'});
next();
})
.slideDown('slow');
</code></pre></div></div>
<p>像上面这样传递一个回调函数,<code class="language-plaintext highlighter-rouge">.queue()</code>方法会把该函数添加到相应元素的效果队列当中,在这个回调函数当中,改变了相应元素的背景颜色,然后调用next继续执行下面的动画,将在队列中中断的地方连接起来,如果此处不调用next方法,这动画会在此处中断,有兴趣的话可以自己去尝试一下。</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[5] -- Cache数据缓存
2014-08-14T00:00:00+00:00
http://www.blogways.net/blog/2014/08/14/jQuery-source-analysis-cache
<h2 id="一前言">一、前言</h2>
<p>Cache数据缓存系统现在广泛应用于DOM元素、动画、事件等方面,消除了将动画队列都存储到各DOM元素的自定义属性中所带来的隐患,另外如果给DOM元素添加过多的自定义的属性或数据可能会引起内存泄漏,其实质是使用了一种<strong><em>低耦合的方式</em></strong>将DOM和缓存数据联系起来。</p>
<p>说到内存泄漏,相信大家都不会陌生,那么内存泄漏的定义是什么呢?</p>
<ul>
<li>
<p>内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元,直到程序结束(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏;</p>
</li>
<li>
<p>内存泄漏形象的比喻是“操作系统可提供给所有进程的存储空间正在被某个进程榨干”,最终结果是程序运行时间越长,占用存储空间越来越多,最终用尽全部存储空间,整个系统崩溃。</p>
</li>
</ul>
<p>在<code class="language-plaintext highlighter-rouge">C/C++</code>中,内存的分配和回收都是手动完成的,因而内存泄漏是经常发生的事情,而<code class="language-plaintext highlighter-rouge">JavaScript</code>使用的是一种称为<strong><em>垃圾收集</em></strong>的技术来管理分配给它的内存,当<code class="language-plaintext highlighter-rouge">JavaScript</code>代码生成一个需要使用新内存的项(如:创建一个对象或一个函数)时,系统就会为这个项留出一块内存空间。因为此对象或函数会被进行各种传递或引用,所以很多代码都会指向这块内存空间,<code class="language-plaintext highlighter-rouge">JavaScript</code>会跟踪这些对内存空间的引用,当某块内存空间没有再被任何其他代码引用时,这个对象占用的内存就会被释放。</p>
<p>但是由于浏览器的差异,这些<code class="language-plaintext highlighter-rouge">JavaScript</code>的自动垃圾回收方法的实现并不一样,而且回收方法可能还存在着BUG,因而还是会导致内存泄露,<code class="language-plaintext highlighter-rouge">JavaScript</code>中会导致内存泄露的情况:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 循环引用
2. `JavaScript`闭包
3. DOM插入顺序
</code></pre></div></div>
<p>循环引用,两个或两个以上的对象或方法相互之间引用,形成一个<strong><em>闭合环状引用</em></strong>,则会导致内存泄露,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var A = document.getElementById('mImage'),
B = new Object(); or A.relative = A;
A.relative = B;
B.relative = A;
</code></pre></div></div>
<h2 id="二源码分析">二、源码分析</h2>
<h3 id="1函数原型介绍">1、函数原型介绍</h3>
<p>首先,在<code class="language-plaintext highlighter-rouge">jQuery</code>源码中,一条不变的实现方式就是通过<code class="language-plaintext highlighter-rouge">jQuery.extend</code>、<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>将Cache功能扩展给<code class="language-plaintext highlighter-rouge">jQuery</code>。</p>
<p>在扩展Cache功能的时候,其代码量并不多,实际的功能实现都是通过调用内部的私有函数来实现的,下面看下这些私有函数及扩展功能函数的函数原型:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 用来判断该元素是否能接受数据,返回true或false
jQuery.acceptData = function( elem ) {};
// 支持HTML5的data-属性
// 如果在指定元素elem没有找到key对应的数据data,就尝试读取HTML5的data属性
function dataAttr( elem, key, data ) {};
// 检测一个Cache对象是否为空
function isEmptyDataObject( obj ) {};
// jQuery内部实现Cache实际函数,存储缓存数据
function internalData( elem, name, data, pvt /* Internal Use Only */ ) {};
function internalRemoveData( elem, name, pvt ) {}; // 移除缓存数据
jQuery.extend({ // 扩展jQuery全局对象
cache: {}, // 缓存对象,用于保存缓存数据
// 如果你试图对下面的元素添加expando属性,
// 它们会抛出不可捕捉的异常
// 意思就是,以下元素没有Data:embed和applet,除了Flash之外的object
noData: {
"applet ": true,
"embed ": true,
"object ": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
},
// 用来判断HTMLElement或JS对象是否具有数据。返回true或false。
// 即,如果调用了jQuery.data方法添加了属性,则返回true。
hasData: function( elem ) {},
// jQuery对外的缓存数据接口,用于存储数据
data: function( elem, name, data ) {
return internalData( elem, name, data );
},
// jQuery对外的缓存数据接口,用于删除数据
removeData: function( elem, name ) {
return internalRemoveData( elem, name );
},
// 私有函数,仅在内部使用
_data: function( elem, name, data ) {
return internalData( elem, name, data, true );
},
// 私有函数
_removeData: function( elem, name ) {
return internalRemoveData( elem, name, true );
}
});
jQuery.fn.extend({ // 扩展jQuery实例对象
// 在匹配的元素上存储任意数据,解决了循环引用和内存泄漏
data: function( key, value ) {},
// 在匹配的元素上移除给定key值的数据
removeData: function( key ) {}
});
</code></pre></div></div>
<h3 id="2internaldatainternalremovedata源码分析">2、internalData、internalRemoveData源码分析</h3>
<p><strong><em>$internalData( elem, name, data, pvt )</em></strong></p>
<p>其源码,如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function internalData( elem, name, data, pvt /* Internal Use Only */ ) {
if ( !jQuery.acceptData( elem ) ) {
// 是否可以附加数据,不可以则直接返回
return;
}
var ret, thisCache,
internalKey = jQuery.expando,
// 必须区分处理DOM元素和JS对象,
// IE6-7不能垃圾回收对象跨DOM对象和JS对象进行的引用属性
isNode = elem.nodeType,
// 如果是DOM元素,则使用全局的jQuery.Cache
// 如果是JS对象,则直接附加到实例对象上
cache = isNode ? jQuery.cache : elem,
// Only defining an ID for JS objects if its cache already exists allows
// the code to shortcut on the same path as a DOM node with no cache
// 如果JS对象的cache已经存在,则需要为JS对象定义一个ID
// 如果是DOM元素,则直接通过elem[ internalKey ]返回id,
// 如果是JS对象,且JS对象的属性internalKey存在,返回internalKey
id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
// 尝试在一个没有任何数据的对象上获取数据时,避免做更多的不必要工作
if ( (!id || !cache[id] || (!pvt && !cache[id].data))
&& data === undefined && typeof name === "string" ) {
return;
}
if ( !id ) {
// 只有DOM节点需要一个唯一的ID,因为DOM元素的数据存储在全局的cache中
if ( isNode ) {
id = elem[ internalKey ] = deletedIds.pop() || jQuery.guid++;
} else {
id = internalKey;
}
}
if ( !cache[ id ] ) {
// 当对象使用JSON.stringify被序列化时,避免将jQuery元数据暴露在一个纯JS对象上
cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
}
// 可以为jQuery.data传递一个对象或函数作为参数,而不必须是key/value的方式
// 将参数浅拷贝到存在的缓存数据中
if ( typeof name === "object" || typeof name === "function" ) {
if ( pvt ) {
cache[ id ] = jQuery.extend( cache[ id ], name );
} else {
cache[ id ].data = jQuery.extend( cache[ id ].data, name );
}
}
thisCache = cache[ id ];
// jQuery内部数据存在一个独立的对象(thisCache[ internalKey ])上,
// 是为了避免内部数据和用户定义数据冲突
// 如果是私有数据
if ( !pvt ) {
if ( !thisCache.data ) {
thisCache.data = {}; // 存放私有数据的对象不存在,则创建一个{}
}
thisCache = thisCache.data; // 使用私有数据对象替换thisCache
}
// 如果data不是undefined,表示传入了data参数,则存储data到name属性上
if ( data !== undefined ) {
thisCache[ jQuery.camelCase( name ) ] = data;
}
// 如果一个数据属性是被限定的,
// 则检测转换为驼峰表示法和未转换的数据属性的name
if ( typeof name === "string" ) {
// 首先尝试找到as-is属性数据
ret = thisCache[ name ];
// 如果ret为null或者undefined,尝试没有或者违背定义属性数据
if ( ret == null ) {
// 尝试找到骆驼拼写法属性(因为有可能之前name被驼峰化了)
ret = thisCache[ jQuery.camelCase( name ) ];
}
} else {
ret = thisCache;
}
return ret;
}
</code></pre></div></div>
<p>在源码中很多地方都出现了<code class="language-plaintext highlighter-rouge">expando</code>和<code class="language-plaintext highlighter-rouge">jQuery.expando</code>,那到底它们是何方神圣?首先看一下<code class="language-plaintext highlighter-rouge">jQuery.expando</code>的源码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
// 为了区别不同的jQuery实例存储的数据,使用前缀“jQuery”+jQuery版本号+随机数作为Key
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
// other code
});
</code></pre></div></div>
<p>可以看出<code class="language-plaintext highlighter-rouge">jQuery.expando</code>实际上是一个字符串,使用<code class="language-plaintext highlighter-rouge">Math.random()</code>生成,去掉了非数字字符,它作为HTMLElement或JavaScript对象的属性名,用以标识不同的HTML元素和JavaScript对象。</p>
<p>前面提过,<code class="language-plaintext highlighter-rouge">internalData( elem, name, data, pvt )</code>是<code class="language-plaintext highlighter-rouge">jQuery</code>中实际实现存储缓存数据的方法,每次调用(插入一个缓存数据)都会调用<code class="language-plaintext highlighter-rouge">jQuery.expando</code>来生成一个key值,用以给插入的数据做一个标识,以便以后通过key值来访问,而在此方法中使用<code class="language-plaintext highlighter-rouge">internalKey = jQuery.expando;</code>,将key值保存在internalKey中用以内部使用。</p>
<p>===</p>
<p><strong><em>internalRemoveData( elem, name, pvt )</em></strong></p>
<p>其源代码,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function internalRemoveData( elem, name, pvt ) {
var thisCache, i,
isNode = elem.nodeType,
cache = isNode ? jQuery.cache : elem,
id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
// 缓存数据中没有指定要删除的数据,直接return
if ( !cache[ id ] ) { return; }
if ( name ) {
// 取得实际要操作的缓存数据
thisCache = pvt ? cache[ id ] : cache[ id ].data;
if ( thisCache ) {
// Support array or space separated string names for data keys
// 支持数组或空格分割的字符串name作为数据的键值
if ( !jQuery.isArray( name ) ) {
// 在进行任何操作之前,尝试将整个字符串作为一个键值
if ( name in thisCache ) {
name = [ name ];
} else {
// split the camel cased version by spaces
// unless a key with the spaces exists
// 将name转换为骆驼表示法
name = jQuery.camelCase( name );
// 判断尝试转换后的name是否在缓存数据中
if ( name in thisCache ) {
name = [ name ]; // 在,
} else {
name = name.split(" "); // 不在,则将其用空格符分割
}
}
} else {
// 如果“name”是一个key值的数组,
// 当数据最初被创建时,通过 ("key", "val") 签名,
// key值将会通过jQuery.map()被转换为骆驼表示法。
// 由于没办法 告知 key是如何被添加,原始的和骆驼表示法的key值将会被移除
// Since there is no way to tell _how_ a key was added, remove
// both plain key and camelCase key. #12786
// This will only penalize the array argument path.
name = name.concat( jQuery.map( name, jQuery.camelCase ) );
}
i = name.length;
while ( i-- ) {
delete thisCache[ name[i] ];
}
// 如果当前缓存中的数据被全部删除, 直接返回return
if ( pvt ? !isEmptyDataObject(thisCache)
: !jQuery.isEmptyObject(thisCache) ) {
return;
}
}
}
if ( !pvt ) {
delete cache[ id ].data;
// 不销毁父缓存,除非在缓存中之剩下内部数据对象
if ( !isEmptyDataObject( cache[ id ] ) ) {
return;
}
}
// 销毁缓存
if ( isNode ) {
jQuery.cleanData( [ elem ], true );
// Use delete when supported for expandos or `cache` is not a window per isWindow
// 当支持suport.deleteExpando或cache不是。。。时,(反正就是支持用delete来删除缓存)
// 使用delete销毁缓存
} else if ( support.deleteExpando || cache != cache.window ) {
delete cache[ id ];
// 当所有条件都不满足时,直接置null
} else {
cache[ id ] = null;
}
}
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">internalRemoveData</code>方法中,大部分源码就是在通过判断,分析和处理找到需要删除数据的name,并没有太难理解的地方那个,此处不再详细讲解。</p>
<h2 id="三示例">三、示例</h2>
<p><strong><em>jQuery.data( elem, name, data )</em></strong></p>
<p>jQuery.data 这是提供给客户端程序员使用的方法,它同时是setter/getter。</p>
<ul>
<li>
<p>传一个参数,返回附加在指定元素的所有数据,即</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> thisCachejQuery.data(elem); // thisCache
</code></pre></div> </div>
</li>
<li>
<p>传二个参数,返回指定的属性值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> jQuery.data(elem, 'name'); 或 $.data(elem, 'name');
</code></pre></div> </div>
</li>
<li>
<p>传三个参数,设置属性及属性值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> jQuery.data(elem, 'name', 'Jack'); 或 $.data(elem, 'name', 'Jack');
jQuery.data(elem, 'oName', {}); 或 $.data(elem, 'oName', {});
</code></pre></div> </div>
</li>
<li>
<p>传四个参数,第四个参数pvt仅提供给jQuery库自身使用,即<code class="language-plaintext highlighter-rouge">jQuery._data</code>方法中传<code class="language-plaintext highlighter-rouge">true</code>,因为jQuery的事件模块严重依赖于jQuery.data,为避免人为的不小心重写,所以在这个版本中加入的。</p>
</li>
</ul>
<p>为JavaScript对象提供缓存,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var oMyJs = {};
$.data(oMyJs, 'info', 'Hello World');
$.data(oMyJs, 'info'); // Hello World
</code></pre></div></div>
<p>为HTMLElement提供缓存,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<div id="mdiv"></div>
// js
var elem = $('mdiv');
$.data(elem, 'info', 'Hello World');
$.data(elem, 'info'); // Hello World
</code></pre></div></div>
<p>如果你使用的时Chrome,你可以打开JavaScript控制台,并添上<code class="language-plaintext highlighter-rouge">console.log(oMyJs/elem);</code>,运行你就能看到元素上附加的数据了,如下图所示:</p>
<p><img src="/images/oMyJs.png" alt="$.data( oMyJs )" />
<img src="/images/elem.png" alt="$.data( elem )" /></p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[4] -- Callbacks和Deferred介绍
2014-08-13T00:00:00+00:00
http://www.blogways.net/blog/2014/08/13/jQuery-source-analysis-callback-deferred
<h2 id="一前言">一、前言</h2>
<h3 id="1jquerycallbacks">1、jQuery.Callbacks</h3>
<p>首先,来考虑一个问题,我们想让某些函数按照先后顺序执行,你最先想到的是什么?队列?那让我们来看一下用队列函数来实现,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function fnLikeQueue( fnList, callback){
var fnTask;
while(fnList.length > 0){
fnTask = fnList.shift();
fnTask(); // 执行函数
}
callback();
};
fnLikeQueue( [function(){
alert('one');
}, function(){
alert('two');
}, function(){
alert('three');
}], function(){
alert('I\'m a callback');
});
</code></pre></div></div>
<p>运行上面代码将会顺序弹出警示框,显示,</p>
one,two,three,i’m a callback
<p>这种方法要判断函数序列的长度,每次运行还要取出一个函数在执行,而且向函数序列中添加新的函数也不是很方便,直观感觉不是很Fashion,现在来试试<code class="language-plaintext highlighter-rouge">jQuery.Callbacks</code>,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var callbacks = $.Callbacks();
callbacks.add(function() {
alert('one');
});
callbacks.add(function() {
alert('two');
});
callbacks.fire(); // 显示 'one', 'two'
</code></pre></div></div>
<p>使用起来要便捷很多,代码又很清晰,所以<code class="language-plaintext highlighter-rouge">jQuery.Callbacks</code>是一个多用途的回调函数列表对象,提供了一种强大的方法来管理回调函数队列。</p>
<h3 id="2deferred">2、Deferred</h3>
<p>实际开发过程中,我们经常遇到某些耗时很长的JavaScript操作,其中既有异步的操作(比如ajax读取服务器数据),也有同步的操作(比如遍历一个大型数组),我们不可能等待这些操作完成,然后再继续后面的操作。</p>
<p>通常的做法是,为它们指定回调函数(Callback),即规定当操作执行完毕后,应该执行的某些动作。</p>
<p>但是,在回调函数方面,jQuery的功能非常弱。为了改变这一点,jQuery开发团队就设计了Deferred对象,简单说,Deferred对象就是jQuery的<strong>回调函数解决方案</strong>,其含义是”延迟”到未来某个点再执行。</p>
<p>通过调用<code class="language-plaintext highlighter-rouge">$.Deferred()</code>构造函数可以创建一个新的延迟对象,每个延迟对象都会向其它代码<strong>承诺</strong>(promise)提供数据,对于任何延迟对象,调用它的<code class="language-plaintext highlighter-rouge">.promise()</code>方法可以取得其承诺对象,通过调用其承诺对象的相应方法,可以添加承诺兑现时调用的处理程序:</p>
<ul>
<li>通过<code class="language-plaintext highlighter-rouge">.done()</code>方法添加的处理程序会在延迟对象被 <strong>成功解决</strong> 之后调用;</li>
<li>通过<code class="language-plaintext highlighter-rouge">.fail()</code>方法添加的处理程序会在延迟对象被 <strong>拒绝</strong> 之后调用;</li>
<li>通过<code class="language-plaintext highlighter-rouge">.always()</code>方法添加的处理程序会在延迟对象完成其任务(<strong>无论解决还是拒绝</strong>)时调用。</li>
</ul>
<p>首先,看下jQuery中Ajax的传统写法,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.ajax({
url: 'index.html',
success: function(){
alert('Done!');
},
error:function(){
alert('Fail!');
}
}); 其中success为请求成功后调用的操作,而error是请求失败后执行的操作,而在有了Deferred对象之后,就不需要像上面一样,再在ajax的请求选项中编写很多很长的操作,如:
$.ajax('index.html')
.done(function(){ alert('Done!'); })
.fail(function(){ alert('Fail!'); });
</code></pre></div></div>
<p>可以看到,done()相当于success方法,fail()相当于error方法。采用连缀写法以后,大大提高了代码的可读性。</p>
<h2 id="二jquerycallbacks源码分析">二、jQuery.Callbacks源码分析</h2>
<h3 id="1函数原型介绍">1、函数原型介绍</h3>
<p>在下面的代码中,可以看懂Callbacks的原型中需要传递一个参数options,一个由空格分开选项的可选项列表,常用的参数:</p>
<ul>
<li><strong>once</strong>: 确保这个回调列表只执行( .fire() )一次(像一个递延 Deferred).</li>
<li><strong>memory</strong>: 保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调 (像一个递延 Deferred).</li>
<li><strong>unique</strong>: 确保一次只能添加一个回调(所以在列表中没有重复的回调).</li>
<li><strong>stopOnFalse</strong>: 当一个回调函数返回false 时中断调用</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">jQuery.Callbacks</code>是在jQuery内部使用,如为<code class="language-plaintext highlighter-rouge">.ajax</code>,<code class="language-plaintext highlighter-rouge">$.Deferred</code>等组件提供<strong>基础功能</strong>的函数,在jQuery引入了Deferred对象(异步列队)之后,jQuery内部基本所有有异步的代码都被promise所转化成同步代码执行。</p>
<p><code class="language-plaintext highlighter-rouge">jQuery.Callbacks</code>的函数原型,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.Callbacks = function( options ) {
// 在需要的情况下,将字符串格式选项转换成对象格式,
// 在转换时会优先检测缓存
options = typeof options === "string" ?
( optionsCache[ options ] || createOptions( options ) ) :
jQuery.extend( {}, options );
var
firing, // 标记当前Callbacks列表是否正在运行
// Last fire value (for non-forgettable lists)
//
memory,
fired, // 标记是否Callbacks列表是否已经执行
firingLength, // Callbacks运行时,循环结束位置
firingIndex, // 当前正在运行的Callbacks的索引(下标)
// Callbacks列表运行时,开始循环的第一个回调函数
// 供add和fireWith方法使用
firingStart,
list = [], // 实际的回调函数列表
// 只有在选项没有设置为once时,stack才存在
// stack用来存储参数信息(此时函数列表已经处于firing状态,
// 必须将其他地方调用fire时的参数存储,之后再至此执行fire
stack = !options.once && [],
// 用给定的参数调用所有的回调函数
fire = function( data ) {},
// 实际的 Callbacks 对象
self = {
// 回调列表中添加一个回调函数或回调函数的集合
add: function() {},
// 从回调列表中的删除一个回调函数或回调函数集合
remove: function() {},
// 返回是否列表中已经拥有一个相同的回调函数
has: function( fn ) {},
// 从列表中删除所有的回调函数
empty: function() {},
// 禁用列表中的回调函数
disable: function() {},
// 确定列表是否已被禁用
disabled: function() {},
// 锁定当前状态的回调函数列表
lock: function() {},
// 确定回调函数列表是否已被锁定
locked: function() {},
// 访问给定的上下文和参数列表中的所有回调函数
fireWith: function( context, args ) {},
// 用给定的参数调用所有的回调函数
fire: function() {},
// 判断回调函数是否被已经被调用了至少一次
fired: function() {}
};
return self;
};
</code></pre></div></div>
<h3 id="2addfire源码分析">2、add、fire源码分析</h3>
<p><code class="language-plaintext highlighter-rouge">$.Callbacks().add( callbacks )</code>的源码如下所示,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>add: function() {
if ( list ) {
var start = list.length; // 保存当前list长度
(function add( args ) {
jQuery.each( args, function( _, arg ) {
var type = jQuery.type( arg );
// 如果传递过来的参数是函数,
// 没有设置‘unique’,则将传递过来的回调函数直接push到列表中
// 如果设置了‘unique’,则判断现在列表中是否已存在,
// 若不存在,则直接push到Callbacks列表
if ( type === "function" ) {
if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
}
} else if ( arg && arg.length && type !== "string" ) {
// 如果传递过来的时一个数组,则递归调用add实现回调函数的添加
add( arg );
}
});
})( arguments );
// 当回调函数正在执行时,则修改firingLength,确保当前添加的回调函数能够被执行
if ( firing ) {
firingLength = list.length;
// 如果不是firing状态,并且设置了memory
//(肯定是在fired状态时才会执行这一步,因为memory是在fire一次后才会被赋值)
// 此时memory已经是上次fire是传递的参数,
// 那么将会直接执行刚添加的函数集,而无需fire
} else if ( memory ) {
firingStart = start;
fire( memory );
}
}
return this;
}
</code></pre></div></div>
<p>当开发人员通过<code class="language-plaintext highlighter-rouge">.add( callbacks )</code>向回调函数列表添加回调函数时,在函数内直接将参数传递给一个<code class="language-plaintext highlighter-rouge">立即调用函数表达式(IIFE)</code>,根据传递参数的类型,采取不同的方式将其添加到回调列表中去。</p>
<p>这里需要注意的一处是,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if ( !options.unique || !self.has( arg ) ) {
list.push( arg );
} 它隐含的表达了两个判断,
</code></pre></div></div>
<ol>
<li>是否设置了“unique”;</li>
<li>在设置了“unique”的前提下,判断是否在回调列表中存在,</li>
</ol>
<p>使用一般的函数语句,通常是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if( !options.unique ){
// push
}else if(!self.has( arg )){
push
}
</code></pre></div></div>
<p>前面也有提到,像源码中的这种书写方式,看起来非常的简洁,唯一的不好就是阅读起来不是很方便。
</br></p>
<p>===</p>
<p><strong><code class="language-plaintext highlighter-rouge">fire</code></strong>方法,外部调用此方法是,jQuery内部的调用方向为,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>self.fire --> self.fireWith --> file
</code></pre></div></div>
<p>在前面的Callbacks函数原型中介绍过,self是真正的Callbacks对象,也就是我们使用是调用的Callbacks对象就是self,那么这里的self.fire和self.fireWith只是Callbacks对外提供的方法,而实际上实现fire功能的是Callbacks内部私有的<code class="language-plaintext highlighter-rouge">fire</code>方法,也就是此处要讲的源码,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// 运行回调函数列表
fire = function( data ) {
// 如果参数memory为true,则记录data
memory = options.memory && data;
fired = true; // 标记运行回调函数
firingIndex = firingStart || 0;
firingStart = 0;
firingLength = list.length;
firing = true; // 标记正在运行回调函数
for ( ; list && firingIndex < firingLength; firingIndex++ ) {
if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false
&& options.stopOnFalse ) {
memory = false; // 阻止未来可能由于add所产生的回调
break; // 由于参数options设置了stopOnFalse,
// 所有当有回调函数运行结果为false时,退出循环
}
}
// 标记结束运行回调
firing = false;
if ( list ) { // 如果Callbacks列表存在
if ( stack ) { // stack存在
// stack不为空,即stack中存有参数信息,
// 当firing在运行时,通过add添加的Callbacks都将保存到stack中
if ( stack.length ) {
//从stack中取出,递归fire执行stack中的Callbacks
fire( stack.shift() );
}
} else if ( memory ) {
list = [];
} else {
// 阻止回调列表中的回调
self.disable();
}
}
}
</code></pre></div></div>
<p>其中需要注意的是,在未参数memory赋值时,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>memory = options.memory && data;
</code></pre></div></div>
<p>执行此语句后,当<code class="language-plaintext highlighter-rouge">options.memory</code>的值为<code class="language-plaintext highlighter-rouge">true</code>时,memory的值是data中的值,这跟<code class="language-plaintext highlighter-rouge">C/C++</code>、<code class="language-plaintext highlighter-rouge">JAVA
</code>等语言不同,在<code class="language-plaintext highlighter-rouge">C/C++</code>、<code class="language-plaintext highlighter-rouge">JAVA</code>中,执行上面语句过后的值是一个<code class="language-plaintext highlighter-rouge">boolean</code>类型的值(true 或 false)。</p>
<p>在<code class="language-plaintext highlighter-rouge">JavaScript</code>中,它能完成非常‘完美’的功能,</p>
<ol>
<li>当options.memory存在时,该语句就相当于一条赋值语句<code class="language-plaintext highlighter-rouge">memory = data;</code>;</li>
<li>当options.memory不存在时,memory值为<code class="language-plaintext highlighter-rouge">false</code>。</li>
</ol>
<p>像这样写之后,就再也不用为写一些复杂的判断语句了,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if( options.memory ){
memory = data;
} else { 或者 memory = options.memory ? data : false;
memory = false;
}
</code></pre></div></div>
<p>而且看上去非常的“<strong>优雅</strong>”不是么? 当然还有<code class="language-plaintext highlighter-rouge">||</code>操作也一样,以前我们这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if( options.memory ){
memory = true;
} else { 或者 memory = options.memory ? true : data;
memory = data;
}
</code></pre></div></div>
<p>现在我们可以这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>memory = options.memory || data;
</code></pre></div></div>
<p>你可以复制下面代码到<a href=""http://jsfiddle.net/"" title="http://jsfiddle.net/">JSFiddle.net</a>去测试一下!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var options = true,
data = 'I\'m a String',
ret;
ret = options && data; //!options && data; //options || data; //!options || data;
alert(ret);
</code></pre></div></div>
<p>当然,这都只能在<code class="language-plaintext highlighter-rouge">JavaScript</code>中这么写,你要是在<code class="language-plaintext highlighter-rouge">C/C++</code>、<code class="language-plaintext highlighter-rouge">JAVA</code>中也这么写,<strong>恭喜你,慢慢调BUG吧~~</strong></p>
<p>在<code class="language-plaintext highlighter-rouge">fire</code>源码中,真正最终执行回调函数的代码是,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>list[ firingIndex ].apply( data[ 0 ], data[ 1 ] )
</code></pre></div></div>
<p>关于<code class="language-plaintext highlighter-rouge">apply</code>的使用,可以参考<a href=""/blog/2014/07/22/somethings-of-array-and-function.html"" title="http://www.blogways.net/blog/2014/07/22/somethings-of-array-and-function.html">JavaScript中,Array和Function的那些事儿</a>!</p>
<h3 id="3示例">3、示例</h3>
<p>下面是两个函数fn1和fn2:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function fn1( value ) {
alert( value );
}
function fn2( value ) {
fn1("fn2 says: " + value);
return false;
} ***$.Callbacks( 'once' )***
</code></pre></div></div>
<p>确保这个回调列表只执行( .fire() )一次。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var callbacks = $.Callbacks('once');
callbacks.add( fn1 );
callbacks.fire( 'hello' );
callbacks.add( fn2 );
callbacks.fire( 'world' );
</code></pre></div></div>
<p>只会显示“hello”,而不会显示“world”,因为在创建Callbacks实例对象的时候,传递了参数“once”,仅运行一次,因而后面的<code class="language-plaintext highlighter-rouge">callbacks.fire( 'world' );</code>是不会执行的。</p>
<p></br></p>
<p>===</p>
<p><strong><em>$.Callbacks( ‘memory’ )</em></strong></p>
<p>保持以前的值,将添加到这个列表的后面的最新的值立即执行调用任何回调函数。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var callbacks = $.Callbacks('memory');
callbacks.add(function() {
alert('f1');
});
callbacks.fire(); //输出 'f1',这时函数列表已经执行完毕!
callbacks.add(function() {
alert('f2');
}); //memory作用在这里,没有fire,一样有结果: f2
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong><em>$.Callbacks( ‘unique’ )</em></strong></p>
<p>确保一次只能添加一个回调函数(所以在列表中没有重复的回调函数)</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var callbacks = $.Callbacks('unique');
callbacks.add( fn1 ); // 添加成功
callbacks.add( fn1 ); // 添加失败
// 显示结果hello world
callbacks.fire( 'hello world' );
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong><em>$.Callbacks( ‘stopOnFlase’ )</em></strong></p>
<p>当一个回调函数返回false 时,中断调用</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var callbacks = $.Callbacks('stopOnFalse');
callbacks.add(f1);
callbacks.add(function(){
return false;
});
callbacks.add(f2);
// 只显示 hello world!
callbacks.fire( 'hello world!' );
</code></pre></div></div>
<p></br></p>
<h2 id="三deferred源码分析">三、Deferred源码分析</h2>
<h3 id="1函数原型介绍-1">1、函数原型介绍</h3>
<p><code class="language-plaintext highlighter-rouge">通过jQuery.extend</code>将Deferred扩展到jQuery<strong>全局对象</strong>中去,扩展原理前面已经讲过,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
Deferred: function( fnc ){},
// Deferred 帮助
when: function( subordinate /* , ..., subordinateN */ ) {}
});
</code></pre></div></div>
<p>首先来看下Deferred的代码结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Deferred: function( func ) {
var tuples = [
// 动作,添加监听器,处理程序列表(回调函数列表),最终状态
// 创建了三个$.Callbacks对象,分别表示成功、失败、处理中三种状态
[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
[ "notify", "progress", jQuery.Callbacks("memory") ]
],
state = "pending",
promise = {
state: function() {},
always: function() {},
then: function( /* fnDone, fnFail, fnProgress */ ) {},
// 为当前deferred,返回一个promise对象
// 如果传递参数obj对象给此方法,则promise将被扩展到此obj对象
promise: function( obj ) {}
},
deferred = {};
// 增加一组特定的方法
jQuery.each( tuples, function( i, tuple ) {
deferred[ tuple[0] + "With" ] = list.fireWith;
}
return deferred;
}
</code></pre></div></div>
<p>Deferred实例的创建,跟Callbacks的雷士,调用一个函数,然后返回的是内部构建的Deferred对象,创建了一个promise对象,具有state、always、then、primise方法,扩展primise对象生成最终的Deferred对象,返回该对象。</p>
<h3 id="2源码分析">2、源码分析</h3>
<p>Deferred部分源码,如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.each( tuples, function( i, tuple ) {
var list = tuple[ 2 ],
stateString = tuple[ 3 ];
// 通过下面的语句,实现对应的将$.Callbacks实例对象,
// 绑定到promise对象上
promise[ tuple[1] ] = list.add;
// 处理状态
if ( stateString ) {
list.add(function() {
// state = [ resolved | rejected ]
state = stateString;
// 默认会预先向doneList,failList中的list添加三个回调函数
}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
}
// deferred[ resolve | reject | notify ]
deferred[ tuple[0] ] = function() {
deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
return this;
};
deferred[ tuple[0] + "With" ] = list.fireWith;
});
// 将promise对象合并到deferred对象中
// 使用的是promise.promise( obj ),通过调用jQuery.extend扩展
// promise( obj ){
// return obj != null ? jQuery.extend( obj, promise ) : promise;
// }
promise.promise( deferred );
return deferred;
</code></pre></div></div>
<p>源码中通过<code class="language-plaintext highlighter-rouge">promise[ tuple[1] ] = list.add;</code>将回调函数绑定到相应的promise对象上,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>promise.done = $.Callbacks("once memory").add
promise.fail = $.Callbacks("once memory").add
promise.progress = $.Callbacks("memory").add
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">i ^ 1</code>按位异或运算,实际上第二个传参数是1、0索引对调了,所以取值是<code class="language-plaintext highlighter-rouge">failList.disable</code>与<code class="language-plaintext highlighter-rouge">doneList.disable</code>。</p>
<h3 id="3示例-1">3、示例</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var d = $.Deferred();
setTimeout(function(){
d.resolve( 'hello world' );
},0);
d.then( function( value ){
console.log( value );
});
</code></pre></div></div>
<p>当延迟对象被 resolved 时,任何通过<code class="language-plaintext highlighter-rouge">deferred.then</code>或<code class="language-plaintext highlighter-rouge">deferred.done</code>添加的处理函数,都会被调用。回调函数的执行顺序和它们被添加的顺序是一样的。传递给<code class="language-plaintext highlighter-rouge">deferred.resolve()</code>的args参数,会传给每个回调函数。当延迟对象进入<code class="language-plaintext highlighter-rouge">resolved</code>状态后,再添加的任何处理函数,当它们被添加时,就会被立刻执行,并带上传入给<code class="language-plaintext highlighter-rouge">.resolve()</code>的参数。</p>
<p>调用d.resolve(22) 就等于是调用,匿名函数并传入参数值”hello world”:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function(val){
console.log(val); // 显示hello world
} 更多关于Deferred对象的例子及讲解,请参考[jQuery的deferred对象详解][]!
</code></pre></div></div>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[3] -- 简单DOM遍历方法
2014-08-12T00:00:00+00:00
http://www.blogways.net/blog/2014/08/12/jQuery-source-analysis-dom-traversal
<h2 id="一前言">一、前言</h2>
<p>DOM遍历有两个核心的函数,其它的遍历方法都通过调用这两个方法来间接实现相应的功能,这两个核心函数的函数原型,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>dir: function( elem, dir, until ) {},
sibling: function( n, elem ) {} `dir( elem, dir, until )`,从一个元素出发,迭代检索某个方向上的所有元素并记录,直到与遇到 document 对象或遇到 until 匹配的元素;
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">sibling( n, elem )</code>,返回 n 元素的所有后续兄弟元素,包含 n,不包含 elem, 返回 n 的兄弟节点(把 n, elem 设为相同元素时,则不返回本身).</p>
<p>然后通过<code class="language-plaintext highlighter-rouge">jQuery.extend()</code>方法将两个核心函数扩展到<code class="language-plaintext highlighter-rouge">jQuery</code>全局对象中去,以便后面需要的时候直接通过<code class="language-plaintext highlighter-rouge">jQuery.dir/sibling</code>调用。</p>
<h3 id="1dir-elem-dir-until-和sibling-n-elem-的实现">1、dir( elem, dir, until )和sibling( n, elem )的实现</h3>
<p>在<code class="language-plaintext highlighter-rouge">jquery-1.11.1.js</code>中,其源码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
// elem 起始元素
// dir 迭代方向,可选值:parentNode nextSibling previousSibling
// until 选择器表达式,如果遇到until匹配的元素,迭代终止
dir: function( elem, dir, until ) {
var matched = [], // 保存匹配元素
// 根据dir从elem取出一个元素作为匹配的开始节点,
// cur表示匹配开始节点,为当前节点在dir迭代方向的下一个节点,
// 因此匹配结果不包含本节点
cur = elem[ dir ];
// 通过while循环 、 cur = cur[dir](根据迭代方向,向后移动一个节点),
// 实现向迭代方向的遍历,
// 当遍历完,或遇到document(cur.nodeType === 9),
// until匹配的元素( jQuery( cur ).is( until ) )时,结束遍历,返回结果
while ( cur && cur.nodeType !== 9 && (until === undefined
|| cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
if ( cur.nodeType === 1 ) {
// 将匹配的Element元素压入matched结果集中
matched.push( cur );
}
cur = cur[dir]; // 像dir方向,往后一个节点
}
return matched;
},
// n 起始元素(包含在返回结果中)
// elem 剔除元素(不包含在结果集中)
sibling: function( n, elem ) {
var r = [];
// 将 n 是否存在作为判断循环是否继续的依据,
// 先判断 n 的存在与否,再移动当前节点,
// 因此结果集中包含 n
for ( ; n; n = n.nextSibling ) {
// 当元素类型为Element 且 节点不为 elem 时,
// 将当前元素压入结果集,
if ( n.nodeType === 1 && n !== elem ) {
r.push( n );
}
}
return r;
}
}); 从上面的代码可以发现,虽然两个核心函数的代码量很少,但是实际上用它们能实现的功能是非常强大的。就拿`dir`函数来说,它支持3个迭代方向,这意味着它能实现至少3个函数的功能,再加上`until`是选择器表达式,所有它能遍历筛选的元素是非常多的。
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">dir( elem, dir, until )</code>函数中判断遇到<code class="language-plaintext highlighter-rouge">until</code>匹配元素结束迭代的判断语句:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until ) 其隐含的表达出了执行最后一个判断语句(即遇到`until`匹配元素结束迭代)的执行条件是:
until !== undefined && cur.nodeType === 1 即`until`必须存在,`cur`节点必须是`Element`元素,这种写法虽然阅读和维护都不是很方便,但是看上去比较简洁,同时节约了很多的代码,在`jQuery`中很多地方都使用了这种写法,有兴趣可以自己去看看。
</code></pre></div></div>
<h3 id="2hasclosestindexaddaddback扩展">2、has、closest、index、add、addBack扩展</h3>
<p>在向<code class="language-plaintext highlighter-rouge">jQuery</code>扩展两个核心函数的同时,也通过<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>向<strong>实例对象</strong>扩展了遍历方法,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.fn.extend({
// 判断当前元素集合中,是否包含target选择符指定的元素
// 当前元素集合指调用has的实例对象当中的元素
has: function( target ) {},
// 与选择符selectors匹配的第一个元素,遍历路径从选中元素开始,
// 沿DOM树向上在其中祖先节点中查找
closest: function( selectors, context ) {},
// 在当前元素集合中,返回给定elem元素所在的索引位置
index: function( elem ) {},
// 为选中的元素(当前匹配元素集合),加上与给定选择符selector匹配的元素
add: function( selector, context ) {},
// 为选中的元素,加上内部jQuery栈中与给定选择符selector匹配的元素
addBack: function( selector ) {}
});
</code></pre></div></div>
<h2 id="二遍历方法">二、遍历方法</h2>
<h3 id="1遍历方法的函数原型">1、遍历方法的函数原型</h3>
<p>首先调用两个核心函数,实现相应的遍历方法,然后将这些方法一起包装到一个对象<code class="language-plaintext highlighter-rouge">{}</code>当中,最后通过<code class="language-plaintext highlighter-rouge">jQuery.each</code>遍历这个方法对象,并在其回调函数中通过<code class="language-plaintext highlighter-rouge">jQuery.fn[ name ] = function9){}</code>添加到<strong>实例对象</strong>中,其函数原型如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.each({
parent: function( elem ) { // 父元素
var parent = elem.parentNode;
// 有父元素,且父元素不为DocumentFragment时,返回父元素
// 否则返回null
return parent && parent.nodeType !== 11 ? parent : null;
},
parents: function( elem ) { // 祖先元素
// 检索所有祖先元素,直到document
return jQuery.dir( elem, "parentNode" );
},
// 每个选中元素的所有祖先元素,直到但不包含util的祖先元素,
parentsUntil: function( elem, i, until ) {
// 检索所有祖先元素,直到遇到与until匹配的元素
return jQuery.dir( elem, "parentNode", until );
},
next: function( elem ) { // 每个选中元素紧邻的下一个同辈元素
return sibling( elem, "nextSibling" );
},
prev: function( elem ) { // 每个选中元素紧邻的上一个同辈元素
return sibling( elem, "previousSibling" );
},
nextAll: function( elem ) { // 每个选中元素之后的所有同辈元素
return jQuery.dir( elem, "nextSibling" );
},
prevAll: function( elem ) { // 每个选中元素之前的所有同辈元素
return jQuery.dir( elem, "previousSibling" );
},
// 匹配每个选中元素之后的所有同辈元素,
// 直到遇到与until匹配的元素,不包含until
nextUntil: function( elem, i, until ) {
return jQuery.dir( elem, "nextSibling", until );
},
prevUntil: function( elem, i, until ) {
return jQuery.dir( elem, "previousSibling", until );
},
// 给定节点的所有同辈元素
siblings: function( elem ) {
// elem父元素的第一个子节点的所有兄弟元素,排除当前节点
return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
},
// 子节点
children: function( elem ) {
// elem的第一个子节点的所有兄弟元素,即为elem的所有子节点
return jQuery.sibling( elem.firstChild );
},
// 所有的子节点,包含Element、Text、Comment
contents: function( elem ) {
return jQuery.nodeName( elem, "iframe" ) ?
elem.contentDocument || elem.contentWindow.document :
jQuery.merge( [], elem.childNodes );
}
}, function( name, fn ) {
// 将遍历对象中的方法扩展到jQuery实例对象中去
jQuery.fn[ name ] = function( until, selector ) {
// 将当前匹配集合中的元素,用fn处理,
// 然后用until过滤处理结果,最后返回匹配结果
var ret = jQuery.map( this, fn, until );
// 不过函数名不以Until结尾
if ( name.slice( -5 ) !== "Until" ) {
// 不需要参数until,只有一个参数selector,util只到这里为止
selector = until;
}
if ( selector && typeof selector === "string" ) {
// 对ret数组用selector进行过滤,只留下匹配的元素
// jQuery.filter会调用jQuery.find.matches > Sizzle.matches
// > Sizzle,Sizzle查找、过滤的结果已经经过排序、去重
ret = jQuery.filter( selector, ret );
}
if ( this.length > 1 ) {
// 去除重复
if ( !guaranteedUnique[ name ] ) {
ret = jQuery.unique( ret );
}
// parent或prev的遍历matched应该反转,使matched顺序更符合逻辑
if ( rparentsprev.test( name ) ) {
// 倒序
ret = ret.reverse();
}
}
// 根据操作结果,构造新的jQuery对象并返回用以后续操作
return this.pushStack( ret );
};
});
</code></pre></div></div>
<h3 id="应用">应用:###</h3>
<p>HTML:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// html
<h2>Shakespeare's Plays</h2>
<table>
<tr>
<td>As You Like It</td>
<td>Comedy</td>
<td></td>
</tr>
<tr>
<td>All's Well that Ends Well</td>
<td>Comedy</td>
<td>1601</td>
</tr>
<tr>
<td>Hamlet</td>
<td>Tragedy</td>
<td>1604</td>
</tr>
<tr>
<td>Macbeth</td>
<td>Tragedy</td>
<td>1606</td>
</tr>
<tr>
<td>Romeo and Juliet</td>
<td>Tragedy</td>
<td>1595</td>
</tr>
<tr>
<td>Henry IV, Part I</td>
<td>History</td>
<td>1596</td>
</tr>
<tr>
<td>Henry V</td>
<td>History</td>
<td>1599</td>
</tr>
</table>
</code></pre></div></div>
<p>CSS:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.highlight {
font-size: large;
font-family: monospace;
font-weight: bold;
font-style: italic;
}
</code></pre></div></div>
<h3 id="1next">1、next()</h3>
<p><code class="language-plaintext highlighter-rouge">next()</code>,每个选中元素紧邻的下一个同辈元素,给表格中包含Henry的邻近单元格加一个高亮,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('td:contains("Henry")').next().addClass('highlight');
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">td:contains(Henry)</code>筛选出包含Henry内容的单元格(两处),则此时的选中元素是</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><td>Henry IV, Part I</td> 和 <td>Henry V</td>,
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">next()</code>为选中元素的下一个同辈元素,所以在<code class="language-plaintext highlighter-rouge">next()</code>过后的选中元素是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><td>History</td> 和 <td>History</td>
</code></pre></div></div>
<p>将代码复制到<a href="http://jsfiddle.net">JSFiddle</a>测试显示结果,表格里面的两个History显示为hightlight类的样式了。</p>
<p></br></p>
<h3 id="2nextall">2、nextAll()</h3>
<p><code class="language-plaintext highlighter-rouge">nextALl()</code>,选中元素之后的所有同辈元素,给表格包含Henry的之后所有单元格加高亮,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('td:contains("Henry")').nextAll().addClass('highlight');
</code></pre></div></div>
<p>其它与前面是一样的,不一样的时,在执行了<code class="language-plaintext highlighter-rouge">nextAll()</code>之后,选中的元素是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><td>Tragedy</td>、<td>1595</td>、<td>History</td> 和 <td>1599</td>
</code></pre></div></div>
<p>测试运行过后,可以看到两个包含Henry行的当前但与昂之后的所有单元格均显示为hightlight类的样式了。</p>
<p></br></p>
<h3 id="3addbackparentchildren">3、addBack()、parent()、children()</h3>
<p><code class="language-plaintext highlighter-rouge">addBack()</code>,选中的元素,加上内部<code class="language-plaintext highlighter-rouge">jQuery</code>栈中之前选中的那一组元素,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('td:contains("Henry")').nextAll().addBack().addClass('highlight');
</code></pre></div></div>
<p>在执行<code class="language-plaintext highlighter-rouge">addBack()</code>之后,相应行中所有单元格都显示为hightlight类的样式了。事实上,要选择同一组元素,可以采用的方法很多,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('td:contains("Henry")').parent().children().addClass('highlight');
</code></pre></div></div>
<p></br></p>
<h3 id="4siblings">4、siblings()</h3>
<p><code class="language-plaintext highlighter-rouge">siblings()</code>,当前选中节点的所有同辈元素,剔除当前选中元素,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$('td:contains("Tragedy")').siblings().addClass('highlight');
</code></pre></div></div>
<p>在执行<code class="language-plaintext highlighter-rouge">siblings()</code>之前的选中元素为表格中包含“Tragedy”的三个单元格<code class="language-plaintext highlighter-rouge"><td></td></code>元素,而执行了<code class="language-plaintext highlighter-rouge">siblings()</code>之后,选中的元素为前面选中元素的所有同辈元素,即:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><td>Hamlet</td>
<td>1604</td>
<td>Macbeth</td>
<td>1606</td>
<td>Romeo and Juliet</td>
<td>1595</td>
</code></pre></div></div>
<p>运行测试过后,这几项就会显示为highlight类的样式了。</p>
<p>测试网站推荐<a href="http://jsfiddle.net">JSFiddle</a>!!</p>
<p></br></p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[2] -- 基础工具函数
2014-08-10T00:00:00+00:00
http://www.blogways.net/blog/2014/08/10/jQuery-source-analysis-two
<h2 id="一前言">一、前言</h2>
<p>前面一次讲了在<code class="language-plaintext highlighter-rouge">jQuery</code>源码中,是通过<code class="language-plaintext highlighter-rouge">jQuery.extend</code>和<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>将后续的大部分功能进行扩展的。<code class="language-plaintext highlighter-rouge">jQuery.extend</code>扩展<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>全局对象</strong>,<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>扩展<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>实例对象</strong>。</p>
<p>要了解<code class="language-plaintext highlighter-rouge">jQuery</code>工具函数的扩展,首先要了解<code class="language-plaintext highlighter-rouge">jQuery.extend</code>和<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>的实现,便于了解其工作原理,这样在后面的学习中才不会有疑惑的地方。</p>
<h3 id="1jqueryextend和jqueryfnextend的实现">1、jQuery.extend和jQuery.fn.extend的实现</h3>
<p>在<code class="language-plaintext highlighter-rouge">jquery-1.11.1.js</code>中,其源码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// .extend( [ boolean, ] target, src1, src2, ... )
// .fn.extend( target)
// 上面为两个函数的原型,其中extend包含一个可选的boolean型参数,其含义为是否进行深度扩展
// 此处的target为 接受扩展 的对象
jQuery.extend = jQuery.fn.extend = function() {
var src, copyIsArray, copy, name, options, clone,
// 若无特殊情况,参数中的第一个为target,若没有传递参数,默认为{}
target = arguments[0] || {}, i = 1,
length = arguments.length,
// 是否进行深度复制的flag,即为参数中[ boolean ]所传递过来的值,默认为false
deep = false;
// 处理深度复制的情况,若设置了[ boolean ],则其位置必为arguments中第一个(arguments[0])
if ( typeof target === "boolean" ) {
deep = target;
// 跳过 [ boolean ] 参数,取得target扩展对象
target = arguments[ i ] || {};
i++;
}
// 处理target是一个string或其他什么(可能发生在深度复制中)
if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
target = {};
}
// 如果只有一个参数被传递过来,则扩展jQuery自己,即将target值设为this
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// 只处理不为空或undefined值
if ( (options = arguments[ i ]) != null ) {
// 扩展基础对象
for ( name in options ) {
src = target[ name ];
copy = options[ name ];
// 防止死循环
if ( target === copy ) {
continue; // 当传递过来的参数中的所有属性及方法,
}
// 如果进行深度扩展,且我们正在合并的是一个纯对象或者数组,就递归调用
if ( deep && copy && ( jQuery.isPlainObject(copy)
|| (copyIsArray = jQuery.isArray(copy)) ) ) {
if ( copyIsArray ) {
copyIsArray = false;
clone = src && jQuery.isArray(src) ? src : [];
} else {
clone = src && jQuery.isPlainObject(src) ? src : {};
}
// 别直接修改原始对象,而是使用他们的一个副本
target[ name ] = jQuery.extend( deep, clone, copy );
// 排除参数中的undefined值
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// 返回修改过后的对象
return target;
}; 源码中通过`target[ name ] = jQuery.extend( deep, clone, copy );`和`target[ name ] = copy;`来具体实现目标对象的扩展,前者在选择了深度扩展且扩展对象为数组或纯对象时,通过递归调用`jQuery.extend`实现,后者是直接赋值。
</code></pre></div></div>
<p>两者相同一点就是同名属性或方法的直接赋值,这就使得后面的属性或方法会<strong>覆盖</strong>前面或者<code class="language-plaintext highlighter-rouge">target</code>中的同名属性或方法。而当<code class="language-plaintext highlighter-rouge">jQuery.extend</code>只有一个参数的时候,会将其扩展到<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>全局对象</strong>或<strong>实例对象</strong>当中去,我们可以利用这一特性,扩展<code class="language-plaintext highlighter-rouge">jQuery</code>的功能,而实际上我们也是这么做的,这个会在后面谈到,此处不再详细说明。</p>
<h2 id="二工具函数">二、工具函数</h2>
<h3 id="1工具函数的函数原型">1、工具函数的函数原型</h3>
<p><code class="language-plaintext highlighter-rouge">jQuery.extend</code>中扩展的<strong>全局对象</strong>工具函数,其函数原型如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.extend({
// 在页面中生成一个唯一标识每一个副本jQuery
expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
// 在没有ready模块时,假定jQuery准备完成
isReady: true,
// 抛出指定的错误信息
error: function( msg ) { throw new Error( msg ); },
// 空操作
noop: function() {},
isFunction: function( obj ) {}, // 判断参数是否为一个函数
// 当Array存在判断函数时,只做一个简单的赋值引用,若无则手动创建一个
isArray: Array.isArray || function( obj ) {},
isWindow: function( obj ) {}, // 判断参数是否为浏览器窗口对象window
isNumeric: function( obj ) {}, // 判断参数是不是数值
isEmptyObject: function( obj ) {}, // 判断参数是否是一个空对象
// 判断参数obj是不是通过对象字面量或new Object创建的
isPlainObject: function( obj ) {},
type: function( obj ) {}, // 返回obj类型
globalEval: function( data ) {}, // 在全局上下文中,求给定的JavaScript字符串数据的值
// 将参数字符串转换为骆驼表示法,如camelCase,nodeName等,
// 基本上jQuery中所有的方法都是用的骆驼表示法
camelCase: function( string ) {},
nodeName: function( elem, name ) {}, // 返回指定元素的节点名称与给定的名字是否一样
// args 仅在each内部使用,对obj执行规定运行的函数callback
each: function( obj, callback, args ) {},
trim: function( text ) {}, // 去除参数text末尾中的空白符,包括回车、空格、制表符、换行符
// results 仅在内部使用,转换一个类似数组的对象成为真正的JavaScript数组
makeArray: function( arr, results ) { },
// 确定第一个参数在数组中的位置(如果没有找到则返回 -1 )
inArray: function( elem, arr, i ) {},
merge: function( first, second ) {}, // 将两个参数合并,并返回合并后的值
grep: function( elems, callback, invert ) {}, // 数组元素过滤筛选
// arg 仅在内部使用,对当前集合elems中的每个元素调用callback,
// 将返回结果作为一个新的jQuery对象
map: function( elems, callback, arg ) {},
// 一个对象的全局的GUID计数器
guid: 1,
// optionally partially applying any arguments
// 创建一个新的,在指定上下文中执行的函数
proxy: function( fn, context ) {},
now: function() { return +( new Date() ); }, // 返回当前时间
// jQuery.support is not used in Core but other projects attach their
// properties to it so it needs to exist.
support: support
});
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>中扩展的<strong>实例对象</strong>工具函数,其函数原型如下代码所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.fn.extend({
// 返回与给定selector选择符匹配的后代元素
find: function( selector ) {},
// 与给定的选择符selector匹配的选中元素
filter: function( selector ) {},
not: function( selector ) {}, // 选中给定元素集中与给定选择符不匹配的元素
// 根据选择符来检测匹配元素集合,如果这些元素中至少有一个元素匹配给定的参数,则返回 true
is: function( selector ) {}
}); ## 三、源码分析 ## ### 1、each( obj, callback )遍历 ### `jQuery`中,`each`的源码中关键代码如下所示:
var value,
i = 0,
length = obj.length,
isArray = isArraylike( obj );
if ( isArray ) {
for ( ; i < length; i++ ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
} else {
for ( i in obj ) {
value = callback.call( obj[ i ], i, obj[ i ] );
if ( value === false ) {
break;
}
}
}
return obj; 首先通过**数组**和**对象**两种方式来遍历参数`obj`,对`obj`中的每个元素调用`callback.call`将每个元素绑定到回调函数上执行,并返回执行结果。
</code></pre></div></div>
<p>当回调函数返回<code class="language-plaintext highlighter-rouge">false</code>,则停止循环。在其源码中还涉及到<code class="language-plaintext highlighter-rouge">callback.apply</code>的方式来调用回调函数,其作用于<code class="language-plaintext highlighter-rouge">call</code>一样,都是将函数绑定到另外一个对象上去运行。关于<code class="language-plaintext highlighter-rouge">call</code>与<code class="language-plaintext highlighter-rouge">apply</code>的详细说明,可以跳转到[JavaScript中,Array和Function的那些事儿][]查看详细信息!
[JavaScript中,Array和Function的那些事儿]: http://www.blogways.net/blog/2014/07/22/somethings-of-array-and-function.html “JavaScript中,Array和Function的那些事儿”</p>
<h3 id="示例">示例:###</h3>
<p>测试代码如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><html>
<head>
<script type="text/javascript" src="/jquery/jquery.js"></script>
<script type="text/javascript">
$(document).ready(function(){
$("button").click(function(){
$("li").each(function(){
alert($(this).text())
});
});
});
</script>
</head>
<body>
<button>输出每个列表项的值</button>
<ul>
<li>Coffee</li>
<li>Milk</li>
<li>Soda</li>
</ul>
</body>
</html> 运行上面代码,会依次弹出三个警示框,分别显示`<li></li>`中的值。你可以到[$.each()遍历][]运行测试样例!
</code></pre></div></div>
<p>其中<code class="language-plaintext highlighter-rouge">function(){}</code>就是<code class="language-plaintext highlighter-rouge">each( obj, callback )</code>中的回调函数<code class="language-plaintext highlighter-rouge">callback</code>,此处只是显示列表项的值,不需要指定特定的<code class="language-plaintext highlighter-rouge">index</code>和<code class="language-plaintext highlighter-rouge">element</code>,在某些情况下需要用之来辨别当前所遍历的元素,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var info = [
{ "name":"aaa", "age":22, "hobby":["a","b","c"] },
{ "name":"bbb", "age":23, "hobby":["a","b","d"] },
{ "name":"ccc", "age":22, "hobby":["a","c","d"] }
];
$.each( info, function( index, item ){
var name = item.name,
age = item.age,
hobby = item.hobby;
alert("My name is " + name + ",I am " + age +
" years old,my hobbies are " + hobby);
}); 上面的代码会逐条输出`info`中的信息,你可以将上面代码复制到[JSFiddle][]运行测试!
</code></pre></div></div>
<p>其中<code class="language-plaintext highlighter-rouge">index</code>为当前元素在<code class="language-plaintext highlighter-rouge">info</code>中的索引,<code class="language-plaintext highlighter-rouge">item</code>是对当前遍历元素的引用,因此在函数体内使用<code class="language-plaintext highlighter-rouge">item.name/age/hobby</code>即可得到当前遍历元素的值,然后通过<code class="language-plaintext highlighter-rouge">alert()</code>显示到界面。</p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
jQuery源码解读[1] -- 总体结构及构造函数
2014-08-10T00:00:00+00:00
http://www.blogways.net/blog/2014/08/10/jQuery-source-analysis-one
<h2 id="一前言">一、前言</h2>
<p>首先了解一下<strong>块级作用域</strong>与<strong>函数作用域</strong>:</p>
<p><strong>块级作用域</strong>:任何一对花括号{}中的语句集都属于一个块,在这之中定义的所有变量在代码块外都是不可见的。</p>
<p><strong>函数作用域</strong>:定义在函数中的参数和变量在函数外部是不可见的。</p>
<p>大家都知道<code class="language-plaintext highlighter-rouge">Javascript</code>中是没有块级作用域的,那么作为<code class="language-plaintext highlighter-rouge">Javascript</code>的一个框架,<code class="language-plaintext highlighter-rouge">jQuery</code>要应用到各种环境中,要怎么解决命名空间冲突的问题是一个关键。</p>
<h3 id="1立即调用函数表达式iife">1、立即调用函数表达式(IIFE)</h3>
<p><code class="language-plaintext highlighter-rouge">jQuery</code>源码中,你会看到如下的代码结构:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(function( global, factory){
//code
}(typeof window !== "undefined" ? window : this, function( window, noGlobal){
//code
if ( typeof noGlobal === strundefined ) {
window.jQuery = window.$ = jQuery;
}
return jQuery;
})); 1. 这是一个`立即调用函数表达式(IIFE)`,创建一个匿名函数`function( global, factory){}`,创建完成后立即传递参数并运行。 2. 在函数名后面直接加一对`()`表示调用该函数,
function fn( a, b ){};
fn(); //调用函数
而IIFE只是将分开的两步作为一步来书写,
( function( a, b ){
//code
}( x, y ) );
这就叫做`立即调用函数表达式(IIFE)`。此外,还有另一种书写方式:
(functioin( a, b ){
//code
})( x, y ); 3. 通过定义一个`IIFE`,相当于创建了一个“私有”的命名空间,该命名空间中的**所有变量和方法**只为自己所有,如不进行特殊处理,函数外无法访问这些变量及方法,不会破坏全局的命名空间,达到了与`块级作用域`一样的效果。 4. 提及`立即调用函数表达式()IIFE)`,你会发现一个非常有趣的事实,测试代码如下:
$(document).ready(function(){
(function(){
undefined = "now it's defined";
alert(undefined);
alert(typeof undefined);
})();
//会弹出警示框,now it's defined ; string
});
感兴趣的话可以将代码代码复制到**[JSFiddle.net][]**自己测试一下。结果是只有firefox的测试结果为`undefined`,其他主流浏览器都显示为`now it's defined`。
鉴于上面出现的问题,所以在`jQuery`源码中用了`strundefined = typeof undefined;`在未来得及更改`undefined`之前为其创建一个不可变的常数副本。
</code></pre></div></div>
<ol>
<li><code class="language-plaintext highlighter-rouge">window.jQuery = window.$ = jQuery;</code>通过此代码,将<code class="language-plaintext highlighter-rouge">jQuery</code>和<code class="language-plaintext highlighter-rouge">$</code>标示符暴露给<code class="language-plaintext highlighter-rouge">window</code>,最后<code class="language-plaintext highlighter-rouge">return jQuery;</code>返回jQuery实例供外部使用。</li>
</ol>
<h2 id="二代码结构">二、代码结构</h2>
<h3 id="1jquery源码结构">1、jQuery源码结构</h3>
<p><code class="language-plaintext highlighter-rouge">jQuery</code>源码中,从前到后代码实现的功能如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>(function( global, factory){
//code
}(typeof window !== "undefined" ? window : this, function( window, noGlobal){
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
// Code
};
jQuery.extend = jQuery.fn.extend = function() {
// Code
};
jQuery.extend({
//Code
});
// jQuery选择器引擎
var Sizzle = (function( window ){
// Code
// 使用立即调用函数表达式(IIFE),生成Sizzle
})( window );
init = jQuery.fn.init = function( selector, context ) {
// Code
}
// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;
// DOM遍历方法
// Callback及Deferred,回调函数及延迟方法
// Support 浏览器测试
// Data 数据缓存;
// Queue 队列操作;
// Event 事件处理;浏览器兼容处理
// DOM 操作方法;DOM节点插入方法;
// CSS
// FX 动画
// Attr 特性与属性(attr、prop、class)
// 异步请求 AJAX
// 位置坐标、窗口视口大小
return jQuery;
})); 以上为`jQuery`源码的大致的代码结构,从中可以看出代码结构非常清晰、条理明确,以上为`jquery-1.11.1.js`版本当中的代码结构。
</code></pre></div></div>
<h2 id="三源码分析">三、源码分析</h2>
<h3 id="1构造jquery对象">1、构造jQuery对象</h3>
<p>在我们使用<code class="language-plaintext highlighter-rouge">jQuery</code>的时候,并没有像<code class="language-plaintext highlighter-rouge">javascript</code>一样,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// js // jquery
var jq = function(){ $(document).ready(...);
// constructor 构造器 $.getJSON(...);
}; $.ajax(...);
jq.prototype = {
// prototype 原型
find: function(){},
show: function(){}
};
var jq1 = new jq();
jq1.find(); `jQuery`没有通过`new`来创建实例,按照我们的书写方式,那么`$()`应该返回的是一个`jQuery`的实例对象,源码中的实现方式如下:
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context );
};
()
jQuery.fn = jQuery.prototype = {
// Code
}; 通过将`jQuery`类当作一个工厂方法来创建实例,将该创建方法放到`prototype`原型当中,那么在我们调用的时候就不必通过`new`关键字来创建了,直接调用`jQuery( selector, context )`。
</code></pre></div></div>
<p>如果直接将创建方法<code class="language-plaintext highlighter-rouge">init</code>放到<code class="language-plaintext highlighter-rouge">prototype</code>当中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery = function( selector, context ) {
return new jQuery.prototype.init( selector, context );
};
jQuery.fn = jQuery.prototype = {
init: function(){
this.age = 23;
return this; // 返回jQuery实例对象
},
age: 18
// code
};
jQuery().age // 23 如上所示,因为使用的时工厂模式来创建并返回一个`jQuery`的实例,那么`init`中`return this;`的`this`就表示当前实例(`jQuery`对象的实例),这久导致了一个严重的问题,`init`方法当中指像直接的`this`没有了。
</code></pre></div></div>
<p>实际的情况是,内部的<code class="language-plaintext highlighter-rouge">this</code>会覆盖上传的,因此返回的对象不是一个代表<code class="language-plaintext highlighter-rouge">jQuery</code>的实例,而是一个<code class="language-plaintext highlighter-rouge">init</code>的实例,所以<code class="language-plaintext highlighter-rouge">jQuery.age</code>的值不是18,而是23.</p>
<p>因为<code class="language-plaintext highlighter-rouge">init</code>和<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>作用域相同</strong>(都为<code class="language-plaintext highlighter-rouge">jQuery.prototype.init</code>)才会导致上面情况的发生,在源码中的解决方式是将<code class="language-plaintext highlighter-rouge">jQuery</code>的作用域挂载到<code class="language-plaintext highlighter-rouge">jQuery.fn.init</code>当中,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jQuery.fn = jQuery.prototype = {
// code
};
init = jQuery.fn.init = function( selector, context ) {
// Code
}
init.prototype = jQuery.fn; 首先执行`jQuery.fn = jQuery.prototype`,再执行`(jQuery.fn.)init.prototype = jQuery.fn;`,在执行那个这些语句后,挂载到`jQuery.fn.init`上就相当于挂载到了`jQuery.prototype.init`,即挂载到了`jQuery`函数上。
</code></pre></div></div>
<p>最后的结果是挂载到了我们最终使用的<code class="language-plaintext highlighter-rouge">jQuery</code>对象实例上,<code class="language-plaintext highlighter-rouge">jQuery.fn.init</code>是实际上创建<code class="language-plaintext highlighter-rouge">jQuery</code>实例对象的地方。</p>
<h3 id="2jqueryextend和jqueryfnextend">2、jQuery.extend和jQuery.fn.extend</h3>
<p>合并两个或更多对象的属性到第一个对象中,<code class="language-plaintext highlighter-rouge">jQuery</code>中后续的<strong>大部分功能</strong>都时通过该函数进行扩展,通过<code class="language-plaintext highlighter-rouge">jQuery.fn.extend</code>扩展的函数,大部分都会调用通过<code class="language-plaintext highlighter-rouge">jQuery.extend</code>扩展的同名函数。函数原型如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.extend( target, object1, object2, ... )
.fn.extend( target, object1, object2, ... )
</code></pre></div></div>
<ol>
<li>如果传入两个或多个对象,所有对象的属性会被添加到第一个对象<code class="language-plaintext highlighter-rouge">target</code>中,</li>
<li>如果只传入一个对象,则将对象的属性添加到<code class="language-plaintext highlighter-rouge">jQuery</code>对象中。</li>
</ol>
<p>用这种方式,我们可以为<code class="language-plaintext highlighter-rouge">jQuery</code>命名空间增加新的方法。可以用于编写<code class="language-plaintext highlighter-rouge">jQuery</code>插件,如果不想改变传入的对象,可以传入一个空对象:<code class="language-plaintext highlighter-rouge">$.extend({}, object1, object2, ... );</code>.</p>
<ul>
<li>默认合并操作是不迭代的,即便<code class="language-plaintext highlighter-rouge">target</code>的某个属性是对象或属性,也会被完全覆盖而不是合并</li>
<li>第一个参数是<code class="language-plaintext highlighter-rouge">true</code>,则会迭代合并</li>
<li>从<code class="language-plaintext highlighter-rouge">object</code>原型继承的属性会被拷贝</li>
<li><code class="language-plaintext highlighter-rouge">undefined</code>值不会被拷贝</li>
<li>因为性能原因,<code class="language-plaintext highlighter-rouge">JavaScript</code>自带类型的属性不会合并</li>
</ul>
<p>更详细讲解请参考[jQuery.extend 函数详解][] !
[jQuery.extend 函数详解]: http://www.cnblogs.com/RascallySnake/archive/2010/05/07/1729563.html</p>
<h3 id="3jqueryextend-示例">3、jQuery.extend 示例</h3>
<h4 id="1extendobject--extend-target-obj1-obj2--">1)$.extend(object) / $.extend( target, obj1, obj2, … )####</h4>
<p><code class="language-plaintext highlighter-rouge">$.extend( object )</code>方法就是将<code class="language-plaintext highlighter-rouge">object</code>合并到<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>全局对象</strong>中去,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$.extend({
sum: function( a, b ){
return a + b;
}
}); 将`sum`方法扩展到`jQuery`的全局方法中去,类似于`C/C++`、`JAVA`当中的静态方法。
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">$.extend( target, obj1, obj2, ... )</code>方法将<code class="language-plaintext highlighter-rouge">obj1, obj2, ...</code>合并到<code class="language-plaintext highlighter-rouge">target</code>当中,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var result = $.extend( {}, { name: 'A', age: 20}, {name: 'B', gender: 'female'} );
// result = { name: 'B', age: 20, gender: 'female'};
// 此处target为 {} ,将obj1,obj2合并到一个新的对象中
// 在不希望改变target的情况下使用
</code></pre></div></div>
<h4 id="2-fnextendobj">2) $.fn.extend(obj)</h4>
<p><code class="language-plaintext highlighter-rouge">$.fn.extend( object )</code>方法是讲<code class="language-plaintext highlighter-rouge">object</code>合并到<code class="language-plaintext highlighter-rouge">jQuery</code>的<strong>实例对象</strong>中去。</p>
<p>类似<code class="language-plaintext highlighter-rouge">C/C++</code>、<code class="language-plaintext highlighter-rouge">JAVA</code>中<strong>类的方法</strong>,只有<code class="language-plaintext highlighter-rouge">jQuery</code>的实例可以调用!</p>
<p>===</p>
<p><strong>未完待续。。。</strong></p>
JavaScript中,Array和Function的那些事儿
2014-07-22T00:00:00+00:00
http://www.blogways.net/blog/2014/07/22/somethings-of-array-and-function
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol type="I">
<li><a href="#1">概述</a></li>
<li>
<a href="#2">Function的那些事儿</a>
<ol type="1">
<li>arguments不是Array,但可以构造出一个Array对象来!</li>
<li>相同功能,不同调用形式的两方法——apply和call</li>
<li>去优雅地使用apply和call吧!</li>
<li>使用bind方法,再造函数!</li>
<li>uncurryThis,你知道吗?</li>
</ol>
</li>
<li>
<a href="#3">Array的那些事儿</a>
<ol type="1">
<li>可以浅度复制数组的slice方法</li>
<li>不仅仅作用于数组的堆栈操作,四方法:push/pop/shift/unshift</li>
<li>可删可插入的splice方法</li>
<li>forEach/map/reduce的实现与效率!</li>
<li>其他:join/concat/sort/reverse/...</li>
</ol>
</li>
<li><a href="#4">结束</a></li>
</ol>
</dd>
</dl>
</div>
<p><a name="1"></a></p>
<h3 id="一概述">一、概述</h3>
<p>在JavaScript中,内置对象Array和Function本身提供了不少方法,有些方法为人所熟知,有些方法则不被注意。而有些方法虽然被人所熟悉,却又有不被重视的使用场景,去实现一些妙用。</p>
<p>本文结合当下自己的使用心得,一方面做个分享,一方面也是个备忘,哈哈!</p>
<p><a name="2"></a></p>
<h3 id="二function的那些事儿">二、Function的那些事儿</h3>
<h4 id="21-arguments不是array">2.1 arguments不是Array!</h4>
<p><code class="language-plaintext highlighter-rouge">arguments</code>是函数被执行时,传入的实参集合。他直接在函数里面被类似于一个数组进行访问。</p>
<p>在Chrome浏览器的控制台执行下面代码(<em>本文代码都可以在Chrome浏览器的控制台中运行</em>):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function testFunc() {
console.log(arguments);
for(var i=0; i<arguments.length; ++i) {
console.log(arguments[i]);
}
for(var idx in arguments) {
console.log(arguments[idx]);
}
}
</code></pre></div></div>
<p>传入参数运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>testFunc(1,2,3,4)
</code></pre></div></div>
<p>执行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[1, 2, 3, 4]
1
2
3
4
1
2
3
4
</code></pre></div></div>
<p>从上面例子中,我们可以访问<code class="language-plaintext highlighter-rouge">arguments</code>的<code class="language-plaintext highlighter-rouge">length</code>属性,来确定实参的个数,以及通过下标对<code class="language-plaintext highlighter-rouge">arguments</code>进行访问,获取我们需要的某个参数。</p>
<p><strong><span style="color:red">注意</span>:arguments不是一个Array!</strong></p>
<p>示例代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var testFunc = function() {
console.log("[] 是一个 Array?"+Array.isArray([]));
console.log("arguments 是一个Array?"+Array.isArray(arguments));
console.log("arguments toString:" + arguments.toString());
}
</code></pre></div></div>
<p>传入参数运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>testFunc(1,2,3,4)
</code></pre></div></div>
<p>执行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[] 是一个 Array?true
arguments 是一个Array?false
arguments toString:[object Arguments]
</code></pre></div></div>
<p><strong><span style="color:red">提示</span>:我们可以基于arguments构造一个数组对象</strong></p>
<p>示例代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var testFunc = function() {
var args = Array.prototype.slice.apply(arguments);
console.log("args 是一个 Array?"+Array.isArray(args));
console.log(args);
}
</code></pre></div></div>
<p>运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>testFunc(1,2,3,4)
</code></pre></div></div>
<p>结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>args 是一个 Array?true
[1, 2, 3, 4]
</code></pre></div></div>
<p>当然,你也可以在构造的同时,对实参进行裁剪。参考代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var testFunc = function() {
var args = Array.prototype.slice.call(arguments, 1);
console.log(args);
}
</code></pre></div></div>
<h4 id="22-相同功能不同参数形式的两方法apply和call">2.2 相同功能,不同参数形式的两方法——apply和call</h4>
<p>这两个方法的功能相同,只是定义参数方式不同:
它们的作用都是将函数绑定到另外一个对象上去运行,两者仅在定义参数方式有所区别:</p>
<blockquote>
<p>Function.apply(thisArg,argArray);</p>
</blockquote>
<blockquote>
<p>Function.call(thisArg[,arg1,arg2…]);</p>
</blockquote>
<p>他们的功能,都是将函数绑定到一个指定的对象上去运行,即所有函数内部的this指针都会被赋值为thisArg。</p>
<p>这种功能,可以实现将函数作为特定对象的方法,进行执行的目的。</p>
<p>为什么会有这两种不同的形式呢?是为满足需求,而决定的!</p>
<p><code class="language-plaintext highlighter-rouge">Function.call</code>的形式很自然,调用起来很方便,就像<code class="language-plaintext highlighter-rouge">2.1</code>小节最后那个例子那样。在这个例子中,你肯定不愿意使用<code class="language-plaintext highlighter-rouge">Function.apply</code>,是不是?</p>
<p>但是,它存在一个缺点:当函数的参数个数是动态的,只能在运行过程中才能确定下来,那么使用<code class="language-plaintext highlighter-rouge">Function.call</code>就不合适了,就只能使用<code class="language-plaintext highlighter-rouge">Function.apply</code>了。在运行过程中,将动态的参数都放到数组中去,然后把数组作为一个参数,传给<code class="language-plaintext highlighter-rouge">Function.apply</code>。这样就完美的解决问题了!</p>
<h4 id="23-去优雅地使用apply和call吧">2.3 去优雅地使用apply和call吧</h4>
<p>有一个动态数组:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var numbers=[]
for(var i=0; i<100; ++i) {
numbers[i] = Math.floor(Math.random()*10000);
}
</code></pre></div></div>
<p>要求:从中找到最大数和最小数。</p>
<p>先看看常规的方法吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>max = -Infinity, min = +Infinity;
numbers.forEach(function(item) {
if (item > max) max = item;
if (item < min) min = item;
})
</code></pre></div></div>
<p>那么,如何优雅地使用<code class="language-plaintext highlighter-rouge">apply</code>去编码呢?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var max = Math.max.apply(null, numbers);
var min = Math.min.apply(null, numbers);
</code></pre></div></div>
<p>就执行效率而言,后者也比前者快!</p>
<p>当然,就解决这个题目而言,还有更快的方案,有兴趣地话,可以看看笔者的测试:<a href="http://jsperf.com/apply-vs-loop-for-max/2"><code class="language-plaintext highlighter-rouge">http://jsperf.com/apply-vs-loop-for-max/2</code></a>。这个测试的结果,可能会颠覆你的认知,让你惊讶的。^_^</p>
<h4 id="24-使用bind方法定制函数">2.4 使用bind方法,定制函数!</h4>
<p>先看定义:</p>
<blockquote>
<p>fun.bind(thisArg[, arg1[, arg2[, …]]])</p>
</blockquote>
<p>如果说,前面的<code class="language-plaintext highlighter-rouge">apply</code>和<code class="language-plaintext highlighter-rouge">call</code>方法是将函数绑定到特定的对象上去执行。那么,<code class="language-plaintext highlighter-rouge">bind</code>方法,就是只绑定,不执行。</p>
<p>它生成一个新的方法,它可以给已知的函数<code class="language-plaintext highlighter-rouge">fun</code>绑定执行的对象<code class="language-plaintext highlighter-rouge">thisArg</code>,还可以绑定执行的参数<code class="language-plaintext highlighter-rouge">[, arg1[, arg2[, ...]]]</code>,绑定的参数可以是部分,也可以是全部。</p>
<p>所以,就实现的功能而言,<code class="language-plaintext highlighter-rouge">bind</code>方法也可以借助<code class="language-plaintext highlighter-rouge">apply</code>方法,用下面代码模拟:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Function.prototype.bind = function(ctx) {
var fn = this;
var args = Array.prototype.slice.call(arguments, 1);
return function() {
fn.apply(ctx, args.concat(Array.prototype.slice.call(arguments)));
};
}
</code></pre></div></div>
<p>那么,<code class="language-plaintext highlighter-rouge">bind</code>有哪些作用,适合用在哪些场合呢?</p>
<p>看下面这段代码:</p>
<p>背景设定:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>this.x = 9;
var module = {
x: 81,
getX: function() { return this.x; }
};
</code></pre></div></div>
<p>调用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>module.getX(); // 81
</code></pre></div></div>
<p>如你所愿,执行结果是 <code class="language-plaintext highlighter-rouge">81</code>.</p>
<p>有时,你可能不经意间,进行赋值:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var getX = module.getX;
</code></pre></div></div>
<p>再运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>getX(); // 9, because in this case, "this" refers to the global object
</code></pre></div></div>
<p>可能,你希望结果是 <code class="language-plaintext highlighter-rouge">81</code>,但却是<code class="language-plaintext highlighter-rouge">9</code>.</p>
<p>怎么才能让结果仍然是 <code class="language-plaintext highlighter-rouge">81</code>呢?</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// create a new function with 'this' bound to module
var boundGetX = getX.bind(module);
boundGetX(); // 81
</code></pre></div></div>
<p>是的,如上使用<code class="language-plaintext highlighter-rouge">bind</code>就可以了!</p>
<h4 id="25-uncurrythis你知道吗">2.5 uncurryThis,你知道吗?</h4>
<p><code class="language-plaintext highlighter-rouge">uncurryThis</code>话题,来自于<code class="language-plaintext highlighter-rouge">Brendan Eich</code>(<code class="language-plaintext highlighter-rouge">JavaScript</code>之父)的一个<a href="http://twitter.com/BrendanEich/status/128975787448741891">tweet</a>.</p>
<p><code class="language-plaintext highlighter-rouge">uncurryThis</code>的最重要的用途,就是<strong>将对象的方法变为函数去使用</strong>。</p>
<p>特殊一点,可以将A对象的方法a使用到B对象上去。在前面<code class="language-plaintext highlighter-rouge">2.1</code>小节中,我们就用到了这个技巧,在<code class="language-plaintext highlighter-rouge">Arguments</code>对象上使用了<code class="language-plaintext highlighter-rouge">Array</code>对象的方法<code class="language-plaintext highlighter-rouge">slice</code>。很有用,不是吗?</p>
<p>那么怎么实现<code class="language-plaintext highlighter-rouge">uncurryThis</code>呢?</p>
<ul>
<li>
<p>扩展Function原型去实现(Brendan Eich写的实现代码):</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Function.prototype.uncurryThis = function () {
var f = this;
return function () {
var a = arguments;
return f.apply(a[0], [].slice.call(a, 1));
};
};
</code></pre></div> </div>
<p>使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var toUpperCase = String.prototype.toUpperCase.uncurryThis();
[ "foo", "bar", "baz" ].map(toUpperCase)
</code></pre></div> </div>
<p>运行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 'FOO', 'BAR', 'BAZ' ]
</code></pre></div> </div>
</li>
<li>
<p>独立的函数实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var uncurryThis = function(f) {
var call = Function.call;
return function() {
return call.apply(f, arguments);
};
};
</code></pre></div> </div>
<p>使用:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var toUpperCase = uncurryThis(String.prototype.toUpperCase);
[ "foo", "bar", "baz" ].map(toUpperCase)
</code></pre></div> </div>
<p>运行结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[ 'FOO', 'BAR', 'BAZ' ]
</code></pre></div> </div>
<p><a name="3"></a></p>
<h3 id="三array的那些事儿">三、Array的那些事儿</h3>
</li>
</ul>
<p>Array是个很常用的内置对象,在Javascript规范的逐步完善中,其内置方法在不知不觉中已经提供了很多了。本文仅介绍一些常用的方法。</p>
<h4 id="31-可以浅度复制数组的slice方法">3.1 可以浅度复制数组的slice方法</h4>
<p>定义:</p>
<blockquote>
<p>arr.slice(begin[, end])</p>
</blockquote>
<p>说明:返回一个新的浅度复制数组,包含从 <code class="language-plaintext highlighter-rouge">begin</code> 到 <code class="language-plaintext highlighter-rouge">end</code> (不包括该元素)的 <code class="language-plaintext highlighter-rouge">arr</code> 中的元素。(<em>原数组内容不发生变化</em>)</p>
<p><code class="language-plaintext highlighter-rouge">begin</code>和<code class="language-plaintext highlighter-rouge">end</code>表示在原数组上,选取的范围。如果<code class="language-plaintext highlighter-rouge">end</code>省略,表示原数组的结尾。 如果<code class="language-plaintext highlighter-rouge">begin</code>省略,表示从0位开始。也可以是负数,表示从原数组的尾部开始计算位置。</p>
<p>举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var arr = [1,2,3,4,5,6,7,8];
arr.slice(2,4); // [3, 4]
arr.slice(4); // [5, 6, 7, 8]
arr.slice(-3); // [6, 7, 8]
arr.slice(3, -2); // [4, 5, 6]
arr.slice(-3, -1); // [6, 7]
arr.slice(); // [1,2,3,4,5,6,7,8]
</code></pre></div></div>
<p><strong><span style="color:red">提示</span>:利用<code class="language-plaintext highlighter-rouge">Array.prototype.slice</code>,我们可以将一个类似array对象,转换为array对象</strong></p>
<p><code class="language-plaintext highlighter-rouge">arguments</code>不是数组,但可以转换为数组。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var args = Array.prototype.slice.call(arguments);
</code></pre></div></div>
<p>我们还可以看一个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function a() { return {length:2, 0:"hello", 1:"world"}}
var t = a();
var z = Array.prototype.slice.call(t);
t instanceof Array; // false
z instanceof Array // true
console.log(z); // ["hello", "world"]
</code></pre></div></div>
<h4 id="32-不仅仅作用于数组的堆栈操作四方法pushpopshiftunshift">3.2 不仅仅作用于数组的堆栈操作,四方法:push/pop/shift/unshift</h4>
<p>先看定义:</p>
<blockquote>
<p>arr.push(element1, …, elementN)</p>
</blockquote>
<blockquote>
<p>arr.pop()</p>
</blockquote>
<blockquote>
<p>arr.shift()</p>
</blockquote>
<blockquote>
<p>arr.unshift(element1, …, elementN)</p>
</blockquote>
<p>说明:</p>
<ul>
<li>
<p><code class="language-plaintext highlighter-rouge">push</code>将n个元素<code class="language-plaintext highlighter-rouge">element1, ..., elementN</code>追加到数组的尾部,其返回值为调用后数组的长度;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var sports = ["soccer", "baseball"];
var total = sports.push("football", "swimming");
console.log(sports); // ["soccer", "baseball", "football", "swimming"]
console.log(total); // 4
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">pop</code>将数组的最后一个对象删除,其返还值就是这个删除的元素。如果是个空数组,则返回值为<code class="language-plaintext highlighter-rouge">undefined</code>;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var myFish = ["angel", "clown", "mandarin", "surgeon"];
var popped = myFish.pop();
console.log(myFish); // ["angel", "clown", "mandarin"]
console.log(popped); // "surgeon"
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">shift</code> 删除数组的第一个数,并且数组内剩余的值,坐标依次前移。返回值为删掉的元素。如果是个空数组,那么返回值为<code class="language-plaintext highlighter-rouge">undefined</code>;</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var myFish = ["angel", "clown", "mandarin", "surgeon"];
var shifted = myFish.shift();
console.log(myFish); // ["clown", "mandarin", "surgeon"]
console.log(shifted); // "angel"
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">unshift</code>将n个元素<code class="language-plaintext highlighter-rouge">element1, ..., elementN</code>插入道数组的头部。其返回值为调用后数组的长度。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var arr = [1, 2];
arr.unshift(0); // result of call is 3, the new array length
// arr is [0, 1, 2]
arr.unshift(-2, -1); // = 5
// arr is [-2, -1, 0, 1, 2]
arr.unshift( [-3] );
// arr is [[-3], -2, -1, 0, 1, 2]
</code></pre></div> </div>
</li>
</ul>
<p>通过上面这四个方法,可以对数组进行堆栈操作了。</p>
<p><strong><span style="color:red">注意</span>:这四个用作堆栈操作的方法,其实还支持非数组对象。不过,支持的对象需要类似数组。从某种角度而言,这也是支持你实现自定义的具有堆栈功能的对象。</strong></p>
<p>所谓的类似数组,就是有<code class="language-plaintext highlighter-rouge">length</code>属性,可以通过<code class="language-plaintext highlighter-rouge">0...n</code>下标去访问元素。仅此,即可!</p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var myQueue = function() {return {length:0}};
var tz = new myQueue();
Array.prototype.push.call(tz, "hello", "world"); // 2
console.log(Array.prototype.slice.call(tz)); //["hello", "world"]
Array.prototype.shift.call(tz); // "hello"
console.log(Array.prototype.slice.call(tz)); //["world"]
Array.prototype.unshift.call(tz, "hello"); // 2
console.log(Array.prototype.slice.call(tz)); //["hello", "world"]
Array.prototype.pop.call(tz); // "world"
console.log(Array.prototype.slice.call(tz)); //["hello"]
</code></pre></div></div>
<p>神奇吧!</p>
<h4 id="33-可删可插入的splice方法">3.3 可删可插入的splice方法</h4>
<p>先看定义:</p>
<blockquote>
<p>array.splice(index , howMany[, element1[, …[, elementN]]])</p>
</blockquote>
<blockquote>
<p>array.splice(index) // SpiderMonkey/Firefox/Chrome extension</p>
</blockquote>
<p>其中, <code class="language-plaintext highlighter-rouge">index</code>表示数组中操作所开始的位置。如果大于数组的长度,那就重定位为数组的尾端。如果是负数,那就从数组的尾端向前移。<code class="language-plaintext highlighter-rouge">howMany</code>,表示要删除的个数,如果是0,则表示不删除。<code class="language-plaintext highlighter-rouge">[, element1[, ...[, elementN]]]</code>,表示要插入的个数。 该方法的返回值为:删除的元素数组。</p>
<p>看实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var myFish = ["angel", "clown", "mandarin", "surgeon"];
//removes 0 elements from index 2, and inserts "drum"
var removed = myFish.splice(2, 0, "drum");
//myFish is ["angel", "clown", "drum", "mandarin", "surgeon"]
//removed is [], no elements removed
//removes 1 element from index 3
removed = myFish.splice(3, 1);
//myFish is ["angel", "clown", "drum", "surgeon"]
//removed is ["mandarin"]
//removes 1 element from index 2, and inserts "trumpet"
removed = myFish.splice(2, 1, "trumpet");
//myFish is ["angel", "clown", "trumpet", "surgeon"]
//removed is ["drum"]
//removes 2 elements from index 0, and inserts "parrot", "anemone" and "blue"
removed = myFish.splice(0, 2, "parrot", "anemone", "blue");
//myFish is ["parrot", "anemone", "blue", "trumpet", "surgeon"]
//removed is ["angel", "clown"]
//removes 2 elements from index 3
removed = myFish.splice(3, Number.MAX_VALUE);
//myFish is ["parrot", "anemone", "blue"]
//removed is ["trumpet", "surgeon"]
</code></pre></div></div>
<p><strong><span style="color:red">提示</span>:我们可以用splice方法来实现数组的堆栈操作:push/pop/shift/unshift方法</strong></p>
<ul>
<li>函数方式实现:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var push = function(arr) {
var args = Array.prototype.slice.call(arguments,1);
args = [arr.length, 0].concat(args);
Array.prototype.splice.apply(arr, args);
return arr.length;
}
var pop = function(arr) {
if (arr.length==0) return undefined;
var el = Array.prototype.splice.call(arr, arr.length-1);
return el[0];
}
var shift = function(arr) {
if (arr.length==0) return undefined;
var el = Array.prototype.splice.call(arr, 0, 1);
return el[0];
}
var unshift = function(arr) {
var args = Array.prototype.slice.call(arguments,1);
args = [0, 0].concat(args);
Array.prototype.splice.apply(arr, args);
return arr.length;
}
</code></pre></div></div>
<ul>
<li>原型方式实现:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Array.prototype.push = function() {
var self = this;
var args = Array.prototype.slice.call(arguments);
args = [self.length, 0].concat(args);
Array.prototype.splice.apply(self, args);
return self.length;
}
Array.prototype.pop = function() {
var self = this;
if (self.length==0) return undefined;
var el = Array.prototype.splice.call(self, self.length-1);
return el[0];
}
Array.prototype.shift = function() {
var self = this;
if (self.length==0) return undefined;
var el = Array.prototype.splice.call(self, 0, 1);
return el[0];
}
Array.prototype.unshift = function() {
var self = this;
var args = Array.prototype.slice.call(arguments);
args = [0, 0].concat(args);
Array.prototype.splice.apply(self, args);
return self.length;
}
</code></pre></div></div>
<p>由于是使用<code class="language-plaintext highlighter-rouge">splice</code>方法来替代实现的,所以,这些方法不能用于类似数组对象。</p>
<p>笔者做了下性能对比测试:原生的最快,原型形式模拟的次之,函数形式模拟的最慢。性能测试见:<a href="http://jsperf.com/splice-vs-push-pop-shift-unshift">http://jsperf.com/splice-vs-push-pop-shift-unshift</a></p>
<h4 id="34-foreachmapreduce的实现与效率">3.4 forEach/map/reduce的实现与效率!</h4>
<p>先看定义:</p>
<blockquote>
<p>arr.forEach(callback[, thisArg])</p>
<p>arr.reduce(callback,[initialValue])</p>
<p>arr.map(callback[, thisArg])</p>
</blockquote>
<p>给使用示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var numbers=[]
for(var i=0; i<100; ++i) {
numbers[i] = i;
}
var sum = 0;
numbers.forEach(function(item) {
sum += item;
})
console.log(sum); //4950
var sum = numbers.reduce(function(sum, item){
return sum+item;
}, 0);
console.log(sum); //4950
numbers.slice(1,4).map(function(item){return Math.pow(item,2)}); //[1, 4, 9]
</code></pre></div></div>
<p>不太想写新手入门教程。写到这里,想说的是<span style="color:red">注意</span>下面几点:</p>
<ul>
<li>使用<code class="language-plaintext highlighter-rouge">reduce</code>可以替代<code class="language-plaintext highlighter-rouge">forEach</code>的实现,并且效率略高一点点。实现和测试可见:<a href="http://jsperf.com/apply-vs-loop-for-max/2"><code class="language-plaintext highlighter-rouge">http://jsperf.com/apply-vs-loop-for-max/2</code></a>;</li>
<li>无论<code class="language-plaintext highlighter-rouge">forEach</code>还是<code class="language-plaintext highlighter-rouge">reduce</code>,都没有手写的<code class="language-plaintext highlighter-rouge">loop</code>循环效率高。具体测试可见<a href="http://jsperf.com/apply-vs-loop-for-max/2"><code class="language-plaintext highlighter-rouge">http://jsperf.com/apply-vs-loop-for-max/2</code></a>;</li>
<li>无论<code class="language-plaintext highlighter-rouge">forEach</code>还是<code class="language-plaintext highlighter-rouge">map</code>,其中的<code class="language-plaintext highlighter-rouge">callback</code>都是同步执行的,<code class="language-plaintext highlighter-rouge">async.js</code>框架中提供了对应的异步实现。</li>
</ul>
<h4 id="35-其他joinconcatsortreverseeverysome">3.5 其他:join/concat/sort/reverse/every/some…</h4>
<p>Array内置了很多方法,下面在给出几个使用较多的方法的定义及使用示例。</p>
<p>定义:</p>
<blockquote>
<p>str = arr.join(separator)</p>
</blockquote>
<blockquote>
<p>arr.concat(value1, value2, …, valueN)</p>
<p>arr.sort([compareFunction])</p>
<p>arr.reverse()</p>
<p>arr.every(callback[, thisArg])</p>
<p>arr.some(callback[, thisArg])</p>
</blockquote>
<p>使用示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var a = new Array("Wind","Rain","Fire");
var myVar1 = a.join(); // assigns "Wind,Rain,Fire" to myVar1
var myVar2 = a.join(", "); // assigns "Wind, Rain, Fire" to myVar2
var myVar3 = a.join(" + "); // assigns "Wind + Rain + Fire" to myVar3
var alpha = ['a', 'b', 'c'];
var alphaNumeric = alpha.concat(1, [2, 3]); //["a", "b", "c", 1, 2, 3]
alphaNumeric = alpha.concat(1,[2,3],[[4]]); //["a", "b", "c", 1, 2, 3, [4]]
var scores = [1, 2, 10, 21];
scores.sort(); // [1, 10, 2, 21]
scores.sort(function(a,b){return a-b}); //[1, 2, 10, 21]
var myArray = ["one", "two", "three"];
myArray.reverse(); // ["three", "two", "one"]
function isBigEnough(element, index, array) {
return (element >= 10);
}
var passed = [12, 5, 8, 130, 44].every(isBigEnough); //false
passed = [12, 54, 18, 130, 44].every(isBigEnough); //true
passed = [2, 5, 8, 1, 4].some(isBigEnough); //false
passed = [12, 5, 8, 1, 4].some(isBigEnough); //true
</code></pre></div></div>
<p><strong><span style="color:red">注意</span>:<code class="language-plaintext highlighter-rouge">concat</code>是浅层复制,从上面例子中也可以看出来。</strong>,如果想实现深层复制,可以参考<code class="language-plaintext highlighter-rouge">underscore.js</code>框架中的<code class="language-plaintext highlighter-rouge">_.flatten</code>,或者参考<code class="language-plaintext highlighter-rouge">prototype.js</code>框架中的<code class="language-plaintext highlighter-rouge">Array#flatten()</code>。</p>
<p><a name="4"></a></p>
<h3 id="四结束">四、结束</h3>
<p>非常高兴,你能耐心看完这篇文章,希望能给你带来帮助!欢迎讨论!</p>
Express4.x API 翻译[4] -- Router
2014-06-16T00:00:00+00:00
http://www.blogways.net/blog/2014/06/16/node-express4.x-api-4
<h2 id="express4x-api-翻译4--router">Express4.x API 翻译[4] – Router</h2>
<h3 id="router">Router()</h3>
<p>路由器是一个孤立的中间件和路由实例。路由器被看作是唯一能胜任中间件和路由的迷你应用。每个express应用都包含一个内置的路由器。</p>
<p>路由器行为像中间件本身,可以在应用或者其他路由里使用。</p>
<p>使用”express.Router()”创建一个新的路由器。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var router = express.Router([options]);
</code></pre></div></div>
<p>改变路由行为的可选项:</p>
<ul>
<li>caseSensitive 开启大小写敏感,默认不开启,”/Foo”和”/foo”同样处理。</li>
<li>
<p>Strict 开启严格路由,默认”/foo”和”/foo/”指向同一个路由。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // invoked for any requests passed to this router
router.use(function(req, res, next) {
// .. some logic here .. like any other middleware
next();
});
// will handle any request that ends in /events
// depends on where the router is "use()'d"
router.get('/events', function(req, res, next) {
// ..
});
</code></pre></div> </div>
</li>
</ul>
<p>然后你可以使用一个特定的根url路由器,像这样分离路由到多个文件或者迷你应用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// only requests to /calendar/* will be sent to our "router"
app.use('/calendar', router); ### router.use([path], function)
</code></pre></div></div>
<p>使用中间件功能,可配置挂载路径,默认挂到”/”根路径。</p>
<p>中间件像一个管道,请求从第一个你定义的中间件开始,然后沿着这个线路一直向下,匹配每一个中间件堆栈中符合的路由。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
var router = express.Router();
// simple logger for this router's requests
// all requests to this router will first hit this middleware
router.use(function(req, res, next) {
console.log('%s %s %s', req.method, req.url, req.path);
next();
});
// this will only be invoked if the path ends in /bar
router.use('/bar', function(req, res, next) {
// ... maybe some additional /bar logging ...
next();
});
// always invoked
router.use(function(req, res, next) {
res.send('Hello World');
});
app.use('/foo', router);
app.listen(3000);
</code></pre></div></div>
<p>挂载路径被剥离,是不可见的中间件功能。此功能的主要用途是挂载中间件的操作不需要根据它的前缀路径来修改代码。</p>
<p>使用router.user()定义中间件的顺序非常重要,他们依次被调用,因此这个决定中间件的优先级。例如通常logger是第一个需要用到的中间件,用来记录每个请求:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var logger = require('morgan');
router.use(logger());
router.use(express.static(__dirname + '/public'));
router.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>现在假设你想忽略静态文件请求的日志,但在logger()之间继续记录路径跟中间件日志,你只需要将static()移到前面:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.use(express.static(__dirname + '/public'));
router.use(logger());
router.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>另一个具体的例子是来自多个文件目录的文件服务,优先从”./public”中查找:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads')); ### router.param([name], callback)
</code></pre></div></div>
<p>路由参数映射逻辑。例如当一个路由中包含:user,加载逻辑会自动提供req.user给路由,或者执行参数输入验证。</p>
<p>下面的代码说明了如果回调,很像中间件,从而支持异步操作,但多了一个id参数。当执行加载用户时,验证req.user,不成功抛出一个错误到next(err)。</p>
<p>要注意,触发一个命名参数函数来运行路由,仅仅在next在没有被参数处理错误调用的情况下执行。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
return next(err);
}
else if (!user) {
return next(new Error('failed to load user'));
}
req.user = user;
next();
});
});
// this route uses the ":user" named parameter
// which will cause the 'user' param callback to be triggered
router.get('/users/:user', function(req, res, next) {
// req.user WILL be defined here
// if there was an error, normal error handling will be triggered
// and this function will NOT execute
});
</code></pre></div></div>
<p>另外你可能只传递一个回调函数,在这种情况下你有机会修改router.param()API。例如express_params定义的回调函数允许你限制参数为给定的正则表达式。</p>
<p>这个例子有点超前,检查当第二个参数为正则表达式时,返回类似”user”参数示例的回调函数。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
</code></pre></div></div>
<p>该方法现在被用来有效的验证参数,或者解析他们提供分组:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.param('id', /^\d+$/);
router.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
router.param('range', /^(\w+)\.\.(\w+)?$/);
router.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
});
</code></pre></div></div>
<p>router.user()方法也支持命名参数,使其他路由提供的挂载点能使用命名参数预加载。</p>
<h3 id="routerroutepathxl">router.route(path)xl</h3>
<p>返回一个可以用来处理HTTP请求带有可选中间件的中间件路由。推荐使用router.route()避免重复路由定义和拼写错误。</p>
<p>根据前面所学建立route.param()示例,我们看到router.route()可以让我们轻松的应对各种HTTP请求处理。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var router = express.Router();
router.param('user_id', function(req, res, next, id) {
// sample user, would actually fetch from DB, etc...
req.user = {
id: id,
name: 'TJ'
};
next();
});
router.route('/users/:user_id')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware!
})
.get(function(req, res, next) {
res.json(req.user);
})
.put(function(req, res, next) {
// just an example of maybe updating the user
req.user.name = req.params.name;
// save user ... etc
res.json(req.user);
})
.post(function(req, res, next) {
next(new Error('not implemented'));
})
.delete(function(req, res, next) {
next(new Error('not implemented'));
})
</code></pre></div></div>
<p>这个方法重新使用单’/users/:user_id’路径,添加对各种HTTP请求的处理。</p>
<h3 id="routerverbpath-callback-callback">router.VERB(path, [callback…], callback)</h3>
<p>Express中router.VERB()方法提供路由功能,其中WERB属于HTTP请求,例如router.post()。可以有多个回调,所有回调同等对待,行为很像中间件,遇到异常时这些回调会调用next(‘route’)不再执行剩余的回调。这种机制可以用来执行有先决条件的路由,然后将控制权移交到其他没有限制的路由。</p>
<p>下面的代码演示了最简单路由定义。Express将这些路径转换成正则表达式,内部用来匹配即将到来的请求。在执行路由匹配时查询字符串不用考虑,例如”GET /”匹配的路由与”GET /?name=tobi”是一致的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.get('/', function(req, res){
res.send('hello world');
});
</code></pre></div></div>
<p>正则表达式也可以使用,当你有非常特殊的限制的时候是很有用的,例如”GET /commits/71dbb9c”能很好的匹配路由”GET /commits/71dbb9c..4c084f9”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>router.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
</code></pre></div></div>
Express4.x API 翻译[3] -- Response
2014-06-13T00:00:00+00:00
http://www.blogways.net/blog/2014/06/13/node-express4.x-api-3
<h2 id="express4x-api-翻译3--response">Express4.x API 翻译[3] – Response</h2>
<h3 id="resstatuscode">res.status(code)</h3>
<p>node <code class="language-plaintext highlighter-rouge">res.statusCode=</code>可链接的别名</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.status(404).sendfile('path/to/404.png'); ### res.set(field, [value]) 设置响应头内字段值,或者通过一个对象一次设置多个字段。
res.set('Content-Type', 'text/plain');
res.set({
'Content-Type': 'text/plain',
'Content-Length': '123',
'ETag': '12345'
})
</code></pre></div></div>
<p>res.header(field, [value])别名。</p>
<h3 id="resgetfield">res.get(field)</h3>
<p>获取响应头内字段值,不区分大小写。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.get('Content-Type');
// => "text/plain" ### res.cookie(name, value, [options]) 设置cookie名称和值,可以是字符串或者对象转换成的JSON。路径选项默认为"/"。
res.cookie('name', 'tobi', { domain: '.example.com', path: '/admin', secure: true });
res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true });
</code></pre></div></div>
<p>maxAge选项可以很方便的设置从当前时间开始以毫秒为单位的过期时间。下面的写法等同于上一个例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true })
</code></pre></div></div>
<p>一个对象可以通过序列化成JSON传递,它由bodyParser()中间件自动解析。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('cart', { items: [1,2,3] });
res.cookie('cart', { items: [1,2,3] }, { maxAge: 900000 });
</code></pre></div></div>
<p>这种方法也支持签名cookie。添加一个简单的signed选项。res.cookie()将隐藏传递给cookieParser(secret)对值签名。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('name', 'tobi', { signed: true });
</code></pre></div></div>
<p>然后你可以使用req.signedCookie来访问这个值。</p>
<h3 id="resclearcookiename-options">res.clearCookie(name, [options])</h3>
<p>删除cookie里面值。默认路径为”/”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.cookie('name', 'tobi', { path: '/admin' });
res.clearCookie('name', { path: '/admin' }); ### res.redirect([status], url)
</code></pre></div></div>
<p>重定向到给定的url,可选状态编码默认为302”Found”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('/foo/bar');
res.redirect('http://example.com');
res.redirect(301, 'http://example.com');
res.redirect('../login');
</code></pre></div></div>
<p>Express支持几种形式的重定向,首先一个完整合格的URI重定向到不同的域名:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('http://google.com');
</code></pre></div></div>
<p>第二种形式是相对路径的重定向,例如你正在http://example.com/admin/post/new,接着重定向到/admin,你将会登录http://example.com/admin:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('/admin');
</code></pre></div></div>
<p>其次相对于应用程序挂载点的相对重定向。例如你有一个博客应用程序挂载在/blog下,理论上来说并不知道它挂载在哪边,因此重定向到/admin/post/new将会跳转到http://example.com/admin/post/new,相对挂载点的重定向将会跳转到http://example.com/blog/admin/post/new:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('admin/post/new');
</code></pre></div></div>
<p>当然相对路径的重定向也是支持的。如果你在http://example.com/admin/post/new,下面的重定向转跳转到http://example.com/admin/post:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('..');
</code></pre></div></div>
<p>最后一个特殊情况是back重定向,重定向到Referer(或Refererer),找不到默认为/。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.redirect('back');
</code></pre></div></div>
<h3 id="reslocation">res.location</h3>
<p>设置location头。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.location('/foo/bar');
res.location('foo/bar');
res.location('http://example.com');
res.location('../login');
res.location('back');
</code></pre></div></div>
<p>你可以使用res.redirect()相同的urls。</p>
<p>例如你的应用挂载在/blog下,使用下面的代码设置location头为/blog/admin:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.location('admin') ### res.send([body|status], [body]) 发送一个响应。
res.send(new Buffer('whoop'));
res.send({ some: 'json' });
res.send('
some html
');
res.send(404, 'Sorry, we cannot find that!');
res.send(500, { error: 'something blew up' });
res.send(200);
</code></pre></div></div>
<p>此方法适用于执行大量的简单非流式的响应任务,例如在未提前定义和提供自动HEAD和HTTP缓存刷新支持的情况下自动设定Content-Length。
当传入的内容为Buffer,那么Content-Type会被设置为”application/octet-stream”,除非预先定义如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.set('Content-Type', 'text/html');
res.send(new Buffer('
some html
'));
</code></pre></div></div>
<p>当发送字符串时Content-Type设置默认为”text/html”:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.send('
some html
');
</code></pre></div></div>
<p>当发送数组或者对象时Express将会转换成JSON格式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.send({ user: 'tobi' })
res.send([1,2,3])
</code></pre></div></div>
<p>最后如果返回的是一个数字,没有前面提到的任何一个响应体,Express会为你设置一个响应字符串。例如200将会响应文本”OK”,400响应”Not Found”等等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.send(200)
res.send(404)
res.send(500) ### res.json([status|body], [body])
</code></pre></div></div>
<p>发送一个JSON返回。当返回对象或者数组时该方法与res.send()相同,然而它可以用来将非对象(null, undefined, 等等)转换成精准的JSON,尽管严格来说这些并不是有效的JSON。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.json(null)
res.json({ user: 'tobi' })
res.json(500, { error: 'message' }) ### res.jsonp([status|body], [body])
</code></pre></div></div>
<p>使用JSONP发送JSON响应。该方法与res.json()相同,但多了对JSONP回调的支持。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.jsonp(null)
// => null
res.jsonp({ user: 'tobi' })
// => { "user": "tobi" }
res.jsonp(500, { error: 'message' })
// => { "error": "message" }
</code></pre></div></div>
<p>默认JSONP回调函数名是callback,但你可以通过修改jsonp callback name参数重新定义。以下是JSONP响应的一些例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// ?callback=foo
res.jsonp({ user: 'tobi' })
// => foo({ "user": "tobi" })
app.set('jsonp callback name', 'cb');
// ?cb=foo
res.jsonp(500, { error: 'message' })
// => foo({ "error": "message" }) ### res.type(type)
</code></pre></div></div>
<p>设置Content-Type类型为mime的类型,或者当”/”存在时Content-Type被简单的设置成该类型。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.type('.html');
res.type('html');
res.type('json');
res.type('application/json');
res.type('png'); ### res.format(object)
</code></pre></div></div>
<p>执行请求时存在请求Accept头上下文转换。该方法使用req.accepted,这是一个按可接受类型重要性排序的数组,否则第一个回调函数被调用。当没有匹配的回调函数执行时服务器返回406 “Not Acceptable”,或者调用默认的回调函数。</p>
<p>设置Content-Type为你选择一个回调函数,但你可以在回调函数中使用res.set()或者res.type()等修改。</p>
<p>下例当Accept头字段设置成”application/json”或”<em>/json”时响应{ “message”: “hey” },但如果设置成”</em>/*“时将会响应”hey”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.format({
'text/plain': function(){
res.send('hey');
},
'text/html': function(){
res.send('
hey
');
},
'application/json': function(){
res.send({ message: 'hey' });
}
});
</code></pre></div></div>
<p>除了规范化的MIME类型你还可以使用扩展名映射这些类型,提供一个稍微不那么详细的实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.format({
text: function(){
res.send('hey');
},
html: function(){
res.send('
hey
');
},
json: function(){
res.send({ message: 'hey' });
}
}); ### res.attachment([filename])
</code></pre></div></div>
<p>设置Content-Disposition头字段为”attachment”。如果给定一个文件名,那么Content-Type将会通过res.type()自动设置成基于扩展名的类型,Content-Disposition的”filename=”参数同时也被设置。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.attachment();
// Content-Disposition: attachment
res.attachment('path/to/logo.png');
// Content-Disposition: attachment; filename="logo.png"
// Content-Type: image/png ### res.sendfile(path, [options], [fn]])
</code></pre></div></div>
<p>传输文件到给定的路径。</p>
<p>自动设置默认基于文件扩展名的Content-Type响应头。当传输发生错误时fn(err)回调函数被调用。</p>
<p>选项:</p>
<ul>
<li>maxAge 以毫秒为单位默认为0</li>
<li>root 相对文件名根目录</li>
</ul>
<p>在下例中该方法为文件服务提供细粒度支持:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/user/:uid/photos/:file', function(req, res){
var uid = req.params.uid
, file = req.params.file;
req.user.mayViewFilesFrom(uid, function(yes){
if (yes) {
res.sendfile('/uploads/' + uid + '/' + file);
} else {
res.send(403, 'Sorry! you cant see that.');
}
});
});
</code></pre></div></div>
<p>如有任何问题或者疑问请参阅send附加文档。</p>
<h3 id="resdownloadpath-filename-fn">res.download(path, [filename], [fn])</h3>
<p>传输路径中的文件作为附件,通常浏览器会提醒用户下载。Content-Disposition “filename=”参数,也就是显示在浏览器对话框的默认文件名,你也可以提供一个自定义文件名。</p>
<p>当传输完成或者中途发生错误时将会调用fn回调函数,该方法使用res.sendfile()来传输文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.download('/report-12345.pdf');
res.download('/report-12345.pdf', 'report.pdf');
res.download('/report-12345.pdf', 'report.pdf', function(err){
if (err) {
// handle error, keep in mind the response may be partially-sent
// so check res.headersSent
} else {
// decrement a download credit etc
}
}); ### res.links(links) 加入给定的链接来填充"Link"响应头字段。
res.links({
next: 'http://api.example.com/users?page=2',
last: 'http://api.example.com/users?page=5'
}); 处理后:
Link: <http://api.example.com/users?page=2>; rel="next",
<http://api.example.com/users?page=5>; rel="last" ### res.locals
</code></pre></div></div>
<p>响应本地化变量作用域为request,因此只适用于在该request/response周期内呈现的视图,如果有的话。其实该API跟app.locals是等同的。</p>
<p>这个对象适用于的request级别的信息,例如request路径,用户认证,用户设置等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(function(req, res, next){
res.locals.user = req.user;
res.locals.authenticated = ! req.user.anonymous;
next();
}); ### res.render(view, [locals], callback)
</code></pre></div></div>
<p>渲染一个视图,同时向回调函数传递渲染后的字符串。发生错误时内部调用next(err)。回调函数传入可能发生的错误以及渲染后的页面,这样就不会自动执行响应了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>res.render('index', function(err, html){
// ...
});
res.render('user', { name: 'Tobi' }, function(err, html){
// ...
});
</code></pre></div></div>
Express4.x API 翻译[2] -- Request
2014-06-11T00:00:00+00:00
http://www.blogways.net/blog/2014/06/11/node-express4.x-api-2
<h2 id="express4x-api-翻译2--request">Express4.x API 翻译[2] – Request</h2>
<h3 id="reqparams">req.params</h3>
<p>此属性是一个包含映射路由”parameters”的对象。例如你使用/user/:name路由,那么”name”属性对你来说就是一个req.params.name变量。该对象默认为{}。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /user/tj
req.params.name
// => "tj"
</code></pre></div></div>
<p>当在定义路由规则时使用了正则表达式,使用req.params[N]获取所有参数匹配数组,其中N表示数组的第几个。此规则适用于包含未定义的通配符的路由字符串,例如<code class="language-plaintext highlighter-rouge">/file/*</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /file/javascripts/jquery.js
req.params[0]
// => "javascripts/jquery.js" ### req.query
</code></pre></div></div>
<p>此属性是一个包含解析查询字符串的对象,默认为{}。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /search?q=tobi+ferret
req.query.q
// => "tobi ferret"
// GET /shoes?order=desc&shoe[color]=blue&shoe[type]=converse
req.query.order
// => "desc"
req.query.shoe.color
// => "blue"
req.query.shoe.type
// => "converse" ### req.param(name)
</code></pre></div></div>
<p>返回当前name参数的值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// ?name=tobi
req.param('name')
// => "tobi"
// POST name=tobi
req.param('name')
// => "tobi"
// /user/tobi for /user/:name
req.param('name')
// => "tobi"
</code></pre></div></div>
<p>查找优先级如下:</p>
<ul>
<li>req.params</li>
<li>req.body</li>
<li>req.query</li>
</ul>
<p>直接使用req.body,req.params,和req.query应该更新清晰,除非你确实需要接收每个对象的输入。</p>
<h3 id="reqroute">req.route</h3>
<p>当前匹配的路由包含多个属性,如路由的原始路径字符串以及转换后的正则表达式等。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/user/:id?', function(req, res){
console.log(req.route);
});
</code></pre></div></div>
<p>上面的代码输出结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{ path: '/user/:id?',
keys: [ { name: 'id', optional: true } ],
regexp: /^\/user(?:\/([^\/]+?))?\/?$/i,
params: [ id: '12' ] } ### req.cookies
</code></pre></div></div>
<p>当cookieParser()中间件使用时该对象默认为{},除此之外还包含由用户代理发送的cookies。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Cookie: name=tj
req.cookies.name
// => "tj"
</code></pre></div></div>
<p>如有任何问题或者疑问请参阅cookie-parser附加文档。</p>
<h3 id="reqsignedcookies">req.signedCookies</h3>
<p>当cookieParser(secret)中间件使用该对象默认为{},还包括用户代理发送的签名cookies,未签名以及准备使用的。签名cookies存放于一个单独的对象,以显示开发者的意图,否则可以通过在req.cookie设置值发起恶意攻击,从而很轻易的欺骗。需要注意的是签名的cookie并不意味着它是隐藏的或者是加密的,这个防止篡改的秘密只是简单的将签名私有化。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Cookie: user=tobi.CP7AWaXDfAKIRfH49dQzKJx7sKzzSoPq7/AcBBRVwlI3
req.signedCookies.user
// => "tobi" 如有任何问题或者疑问请参阅cookie-parser附加文档。
</code></pre></div></div>
<h3 id="reqgetfield">req.get(field)</h3>
<p>获取请求头内的field字段,不区分大小写。Referrer和Referer字段可以互换。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.get('Content-Type');
// => "text/plain"
req.get('content-type');
// => "text/plain"
req.get('Something');
// => undefined 别名为req.header(field)。
</code></pre></div></div>
<h3 id="reqacceptstypes">req.accepts(types)</h3>
<p>检查给定的types是不是可以接受的,当结果为true时返回最佳匹配,否则返回undefined,在这种情况下你应该返回406”Not Acceptable”。</p>
<p>type可以是单一的mine类型的字符串,比如”application/json”,扩展名如”json”,也可以是以逗号分隔的列表或者数组。当为列表或数组时将返回最佳匹配。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Accept: text/html
req.accepts('html');
// => "html"
// Accept: text/*, application/json
req.accepts('html');
// => "html"
req.accepts('text/html');
// => "text/html"
req.accepts('json, text');
// => "json"
req.accepts('application/json');
// => "application/json"
// Accept: text/*, application/json
req.accepts('image/png');
req.accepts('png');
// => undefined
// Accept: text/*;q=.5, application/json
req.accepts(['html', 'json']);
req.accepts('html, json');
// => "json"
</code></pre></div></div>
<p>如有任何问题或者疑问,请参阅accepts附加文档。</p>
<h3 id="reqacceptscharsetcharset">req.acceptsCharset(charset)</h3>
<p>检查给定的字符集是否可以支持。</p>
<p>如有任何问题或者疑问,请参阅accepts附加文档。</p>
<h3 id="reqacceptslanguagelang">req.acceptsLanguage(lang)</h3>
<p>检查给定的lang是否支持。</p>
<p>如有任何问题或者疑问,请参阅accepts附加文档。</p>
<h3 id="reqistype">req.is(type)</h3>
<p>检查传入请求字符串是否包含了”Content-Type”头字段,并且给出匹配的mine类型。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// With Content-Type: text/html; charset=utf-8
req.is('html');
req.is('text/html');
req.is('text/*');
// => true
// When Content-Type is application/json
req.is('json');
req.is('application/json');
req.is('application/*');
// => true
req.is('html');
// => false
</code></pre></div></div>
<p>如有任何问题或者疑问,请参阅type-is附加文档。</p>
<h3 id="reqip">req.ip</h3>
<p>返回远程地址,或者当信任代理已启用时返回代理地址。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.ip
// => "127.0.0.1" ### req.ips
</code></pre></div></div>
<p>当信任代理为true时,解析”X-Forwarded-For”ip地址列表返回一个数组,否则返回一个空数组。例如当值为”client, proxy1, proxy2”时你会获得[“client”, “proxy1”, “proxy2”]数组,其中”proxy2”是最远的下游地址。</p>
<h3 id="reqpath">req.path</h3>
<p>返回请求的URL路径名。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// example.com/users?sort=desc
req.path
// => "/users" ### req.host Returns the hostname from the "Host" header field (void of portno).
</code></pre></div></div>
<p>返回从”Host”头字段内取出的主机名(不包含端口)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// Host: "example.com:3000"
req.host
// => "example.com" ### req.fresh
</code></pre></div></div>
<p>检查请求是否刷新,通过对Last-Modified和/或ETag进行匹配,表明资源是不是最新的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.fresh
// => true 如有任何问题或者疑问,请参阅fresh附加文档。
</code></pre></div></div>
<h3 id="reqstale">req.stale</h3>
<p>检查请求是否过期,如果Last-Modified和/或ETag不匹配,表有资源是过期的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.stale
// => true ### req.xhr
</code></pre></div></div>
<p>检查请求头里是否包含”X-Requested-With”字段并且值为”XMLHttpRequest”(jQuery等)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>req.xhr
// => true ### req.protocol 当使用TLS请求时返回"http"或"https"协议字符串。当信任路由设置为开启时"X-Forwarded-Proto"头字段将被信任。如果你正在运行一个支持https协议的反向代理,那么这个是支持的。
req.protocol
// => "http" ### req.secure
</code></pre></div></div>
<p>检查TLS连接是否建立。这是一个简写:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>'https' == req.protocol; ### req.subdomains Return subdomains as an array. 返回子域数组。
// Host: "tobi.ferrets.example.com"
req.subdomains
// => ["ferrets", "tobi"] ### req.originalUrl
</code></pre></div></div>
<p>此属性很像req.url,但它保留了原始请求的url,允许你在做内部路由时自由重写req.url。例如app.use()中间件将重写req.url重新定义挂载点。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /search?q=something
req.originalUrl
// => "/search?q=something"
</code></pre></div></div>
node 插件化开发
2014-06-10T00:00:00+00:00
http://www.blogways.net/blog/2014/06/10/node-plugin
<h2 id="一非插件化开发在node-express下的实现">一、非插件化开发在node-express下的实现</h2>
<p>以下为我们日志分析系统中添加一个查询明细页面要添加的代码:</p>
<p>1、建立明细页面调转逻辑控制文件detail.js,添加代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exports.qrydetail = function(req, res) {
res.render('query/detail',{
layout: false,
errors: req.flash('error')
})
} 2、建立登录页面detail.html;
</code></pre></div></div>
<p>3、在路由控制文件routes.js引入detail.js:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var detailModule = require('../app/controllers/detail')
</code></pre></div></div>
<p>4、在路由控制文件routes.js中添加路由:app.get(‘/detail.html’, detailModule.qrydetail);</p>
<p>访问:http://localhost:3000/detail.html</p>
<p>缺点:</p>
<p>1) 每次添加链接都需要修改routes.js,时间久了文件很大不便于维护;</p>
<p>2) 多人协作routes.js会同时被多人修改;</p>
<p>3) 无法支持不改动代码多模块的安装卸载</p>
<h2 id="二插件化模块添加">二、插件化模块添加</h2>
<p>1、plugin安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ npm install plugin
</code></pre></div></div>
<p>2、与express集成</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var plugin = require('plugin');
//Plug-in technology
plugin(app).require(config.root+'/app/controllers/plugin').load();
</code></pre></div></div>
<p>以上代码含义为:node服务启动时会加载’/app/controllers/plugin’目录下所有js文件作为项目插件(当然文件内容有一定格式)</p>
<p>3、添加一个查询明细页面</p>
<p>1) 建立明细查询模块页面detail.js,代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exports.plugin = function(server) {
server.get('/detail.html', function(req, res) {
res.render('query/detail',{
layout: false,
errors: req.flash('error')
})
});
}
</code></pre></div></div>
<p>2) 建立登录页面detail.html;</p>
<p>访问:http://localhost:3000/detail.html</p>
<p>优点:</p>
<p>a) 无需修改routes.js;</p>
<p>b) 支持不改动代码多模块的安装卸载(卸载将’/app/controllers/plugin’目录下detail.js文件删除即可)</p>
node 日志管理log4js
2014-06-09T00:00:00+00:00
http://www.blogways.net/blog/2014/06/09/node-log4js
<h2 id="一默认的控制台输出">一、默认的控制台输出</h2>
<p>我们使用express框架时,开发模式用node或者supervisor启动nodejs应用时,控制台都是显示如下的日志。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GET /css/bootstrap.min.css 304 1ms
GET /css/my.css 304 0ms
GET /js/bootstrap.min.js 304 4ms
GET /js/jquery-1.9.1.min.js 304 6ms
GET /js/holder.js 304 3ms
GET /cat/json/latest 200 6ms
GET /cat/json/master 200 4ms
GET /cat/json/classic 200 2ms
GET /about 200 6ms
GET /css/bootstrap.min.css 304 2ms
GET /css/my.css 304 2ms
GET /js/bootstrap.min.js 304 2ms
GET /js/jquery-1.9.1.min.js 304 1ms
GET /js/holder.js 304 1ms
GET /js/bootstrap.min.js 304 1ms
GET / 304 6ms
GET /js/jquery-1.9.1.min.js 304 2ms
GET /css/my.css 304 1ms
GET /css/bootstrap.min.css 304 1ms
GET /js/bootstrap.min.js 304 2ms
GET /js/holder.js 304 2ms
GET /cat/json/latest 200 3ms
GET /cat/json/master 200 2ms
GET /cat/json/classic 200 2ms
GET /admin/ 304 13ms
GET /css/bootstrap.min.css 304 3ms
GET /js/jquery-1.9.1.min.js 304 2ms
GET /css/my.css 304 2ms
GET /js/bootstrap.min.js 304 1ms
GET /js/holder.js 304 2ms
</code></pre></div></div>
<p>我们也可以在代码中,用console.log()打印一些控制台日志。</p>
<p>修改routes/index.js</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exports.index = function(req, res){
console.log("This is an index page!");
res.render('index', {
title:'首页|moive.me',
page:'index'
});
};
</code></pre></div></div>
<p>访问页面,结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>This is an index page!
GET / 304 19ms
GET /css/bootstrap.min.css 304 4ms
GET /css/my.css 304 2ms
GET /js/jquery-1.9.1.min.js 304 38ms
GET /js/holder.js 304 29ms
GET /js/bootstrap.min.js 304 28ms
</code></pre></div></div>
<p>这样的输出的结果,都是在控制台显示,一旦server重启日志就丢失了。对于程序开发来说,这样的输出已经够用了。但是在生产环境上,我们希望能把控制台的输出,保存到文件中,而且需要更多的信息,不仅仅是默认的简化的日志信息。</p>
<p>由于express框架没有日志功能,我们需要引入log4js包来完成这个功能。</p>
<h2 id="二配置log4js与express框架集成">二、配置log4js与express框架集成</h2>
<p>1、安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>npm install log4js
</code></pre></div></div>
<p>2、修改项目入口配置文件,如日志分析项目express.js</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var log4js = require('log4js');
log4js.configure({
appenders: [
{ type: 'console' }, //控制台输出
{
type: 'file', //文件输出
filename: 'logs/log.log',
maxLogSize: 1024,
backups:3,
category: 'normal'
}
]
});
var logger = log4js.getLogger('normal');
logger.setLevel('INFO');
...
app.use(log4js.connectLogger(logger, {level:log4js.levels.INFO}));
app.use(app.router);
</code></pre></div></div>
<p>需要在express.js中进行log4js的配置。
appenders中配置了两个输出,一个是控制台输出,一个是文件输出。</p>
<p>appenders.type=file的对象,指定文件输出位置及文件大小,当超过maxLogSize大小时,会自动生成一个新文件。logs的文件目录要动手创建。
level:log4js.levels.INFO, 设置默认日志输出级别是INFO。</p>
<p>log4js的输出级别6个: trace, debug, info, warn, error, fatal
logger.trace(‘Entering cheese testing’);
logger.debug(‘Got cheese.’);
logger.info(‘Cheese is Gouda.’);
logger.warn(‘Cheese is quite smelly.’);
logger.error(‘Cheese is too ripe!’);
logger.fatal(‘Cheese was breeding ground for listeria.’);</p>
<p>如果输出级别是INFO,则不会打印出低于info级别的日志trace,debug,只打印info,warn,error,fatal。这样做的好处在于,在生产环境中我们可能只关心异常和错误,并不关心调试信息。从而大大减少日志的输出,能减少磁盘写入。而在开发环境中,我们可以需要打印非常多的信息,帮助开发人员定位错误,调试代码。</p>
<p>还有一个好处就是,代码中可以混有各种的日志打印代码。我们只要在一个配置文件中,修改输出级别,日志输出就会发生变化,不用修改所有的代码。如果所有地方都是console.log(),那么上线的时候,改动这个东西就要花很多时间。</p>
<h2 id="三根据项目配置log4js">三、根据项目配置log4js</h2>
<ol>
<li>
<p>增加replaceConsole代替console.log()</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var log4js = require('log4js');
log4js.configure({
appenders: [
{ type: 'console' },{
type: 'file',
filename: 'logs/log.log',
maxLogSize: 1024,
backups:4,
category: 'normal'
}
],
replaceConsole: true
});
</code></pre></div> </div>
</li>
<li>
<p>调整日志输出的格式</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> app.use(log4js.connectLogger(logger, {level:
level:log4js.levels.INFO, format:':method :url'}));
</code></pre></div> </div>
</li>
<li>
<p>自动调整日志输出级别</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 日志级别对应规则:
http responses 3xx, level = WARN
http responses 4xx & 5xx, level = ERROR
else, level = INFO 设置level为auto:
app.use(log4js.connectLogger(logger, {level: 'auto', format:':method :url'}));
</code></pre></div> </div>
</li>
</ol>
<h2 id="四调整log4js结构">四、调整log4js结构</h2>
<p>我们在配置log4js时会有一个问题。就是以上所有配置信息都是在express.js中做的,logger也是在这里直接定义的。如果在控制器(routes)想用log4js进行输出,我们现在拿不到logger的句柄。</p>
<p>新建立log.js</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var log4js = require('log4js');
log4js.configure({
appenders: [
{
type: 'console',
category: "console"
}, //控制台输出
{
type: "file",
filename: 'logs/log.log',
pattern: "_yyyy-MM-dd",
maxLogSize: 20480,
backups: 3,
category: 'dateFileLog'
}//日期文件格式
],
replaceConsole: true, //替换console.log
levels:{
dateFileLog: 'debug',
console: 'debug'
}
});
var dateFileLog = log4js.getLogger('dateFileLog');
var consoleLog = log4js.getLogger('console');
exports.logger = consoleLog;
exports.use = function(app) {
app.use(log4js.connectLogger(consoleLog, {level:'INFO', format:':method :url'}));
}
</code></pre></div></div>
<p>我们把logger单独定义出来,并且做为API暴露出来,此处是开发调试,没有使用文件输出。
这样在其他模块中使用logger输出日志只需如下操作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var logger = require('../../log').logger;
logger.debug("collectTime=%s",collectTime);
</code></pre></div></div>
<p>这样我们就已经玩转log4js了,如果部署生产需要文件输出只要修改log.js中dateFileLog级别,然后设置exports.logger=dateFileLog即可。</p>
Express4.x API 翻译[1] -- Application
2014-06-09T00:00:00+00:00
http://www.blogways.net/blog/2014/06/09/node-express4.x-api-1
<h2 id="express4x-api-翻译1--application">Express4.x API 翻译[1] – Application</h2>
<p>之前参与过一个node的项目,使用express框架,感觉这种异步IO以及事件驱动的架构设计用在一些高并发的场景还是大有可为的,决定深入学习一下。刚开始写几个例子就发现问题了,以前用的是3.5的版本,可以很好的集成在webstorm工具里面使用,到4.x版本的时候就一堆问题,工具已经无法创建新的项目,单独用命令生成,发现启动方法也跟以前不一样,4.x是用npm去启动bin下面的www文件,一些以前的写法现在也用不了,本想回到之前的版本,但看4.x的版本更新很快,应该也是以后的趋势,而且专注高性能,有必要直接学习之。从哪下手比较好呢,网上4.x的例子跟文档都很少,索性还是从API看起吧,刚好英文比较差,算是一起学了。</p>
<h3 id="express">express()</h3>
<p>创建一个 express 应用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('hello world');
});
app.listen(3000);
</code></pre></div></div>
<h2 id="application">Application</h2>
<h3 id="settings">settings</h3>
<p>提供以下设置用来改变Express行为:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">env</code> 环境模式,默认为process.env.NODE_ENV (NODE_ENV 环境变量) 或者 “development”</li>
<li><code class="language-plaintext highlighter-rouge">trust proxy</code> 启用反向代理,默认disabled</li>
<li><code class="language-plaintext highlighter-rouge">jsonp callback name</code> 通过 ?callback= 更新默认回调函数的名称</li>
<li><code class="language-plaintext highlighter-rouge">json replacer</code> JSON replacer callback,默认为null</li>
<li><code class="language-plaintext highlighter-rouge">case sensitive routing</code> 路由启用区分大小写,默认为disabled,”/Foo”和”/foo”默认是同一个地址</li>
<li><code class="language-plaintext highlighter-rouge">strict routing</code> 启用严谨路由,默认情况下”/foo”和”/foo/”被解析成同一个路由</li>
<li><code class="language-plaintext highlighter-rouge">view cache</code> 启用视图模板编译缓存,产品模式下默认enabled</li>
<li><code class="language-plaintext highlighter-rouge">view engine</code> 缺省状态下默认模板引擎</li>
<li><code class="language-plaintext highlighter-rouge">views</code> 视图目录路径,默认”process.cwd()+’/views’”</li>
<li><code class="language-plaintext highlighter-rouge">x-powered-by</code> 启用X-Powered-By: Express HTTP header,默认enabled</li>
</ul>
<h3 id="appsetname-value">app.set(name, value)</h3>
<p>设置指定name的值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('title', 'My Site');
app.get('title');
// => "My Site"
</code></pre></div></div>
<h3 id="appgetname">app.get(name)</h3>
<p>获取对应name的值</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('title');
// => undefined
app.set('title', 'My Site');
app.get('title');
// => "My Site"
</code></pre></div></div>
<h3 id="appenablename">app.enable(name)</h3>
<p>设置name值为true</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.enable('trust proxy');
app.get('trust proxy');
// => true ### app.disable(name) 设置name值为false
app.disable('trust proxy');
app.get('trust proxy');
// => false ### app.enabled(name) 检查name对应的值是否为true
app.enabled('trust proxy');
// => false
app.enable('trust proxy');
app.enabled('trust proxy');
// => true ### app.disabled(name) 检查name对应的值是否为false
app.disabled('trust proxy');
// => true
app.enable('trust proxy');
app.disabled('trust proxy');
// => false ### app.use([path], function) 使用给定的中间件function,可选择挂载path,默认"/"
var express = require('express');
var app = express();
// simple logger
app.use(function(req, res, next){
console.log('%s %s', req.method, req.url);
next();
});
// respond
app.use(function(req, res, next){
res.send('Hello World');
});
app.listen(3000);
</code></pre></div></div>
<p>挂载路径被剥离出来,对于中间件函数来说是不可见的。这么设计是为了让中间件在不用修改任何代码的情况下就可以在任意前缀的路径下执行。</p>
<p>这里有一个具体的例子,通过express.static()方法使用./public来管理文件服务用例的中间件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /javascripts/jquery.js
// GET /style.css
// GET /favicon.ico
app.use(express.static(__dirname + '/public'));
</code></pre></div></div>
<p>例如你想为自有的静态文件增加前缀’/static’,你可以使用’mounting’功能。挂载的中间件函数不会被调用,除非req.url包含这个前缀,当函数被调用时,前缀是被剥离出去的。当然这只会影响到这个函数,挂载好后随后的中间件还是会通过包含<code class="language-plaintext highlighter-rouge">/static</code>的<code class="language-plaintext highlighter-rouge">req.url</code>查看到。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// GET /static/javascripts/jquery.js
// GET /static/style.css
// GET /static/favicon.ico
app.use('/static', express.static(__dirname + '/public'));
</code></pre></div></div>
<p>中间件使用app.use()定义的顺序是非常重要的,它们依次被调用,因此这个决定了中间件的优先级。例如,一般来说日志中间件是你要用到的第一个中间件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var logger = require('morgan');
app.use(logger());
app.use(express.static(__dirname + '/public'));
app.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>现在假设你想忽略静态文件的请求日志,但又想在logger()定义之后继续使用日志路由,你只需要将static()移动前面就可以了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.static(__dirname + '/public'));
app.use(logger());
app.use(function(req, res){
res.send('Hello');
});
</code></pre></div></div>
<p>另一个具体的例子是从众多的目录文件服务中,给予”./public”最高的优先级:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use(express.static(__dirname + '/public'));
app.use(express.static(__dirname + '/files'));
app.use(express.static(__dirname + '/uploads')); ### app.engine(ext, callback)
</code></pre></div></div>
<p>注册给定的模板引擎的callback默认用来处理扩展名为ext的文件。例如,如果你试图渲染一个”foo.jade”文件,Express将在内部调用以下代码,并缓存require()给后续调用以提高性能。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.engine('jade', require('jade').__express);
</code></pre></div></div>
<p>引擎没有提供._express渲染方法,或者你想映射一个不一样的扩展名在模板引擎上,你可以用这个方法。例如映射EJS模板引擎来渲染”.html”文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.engine('html', require('ejs').renderFile);
</code></pre></div></div>
<p>在这种情况下,EJS提供.renderFile()方法使用Express定义的参数:(path, options, callback),但要注意这个方法是在内部给ejs._express取一个别名,如果你使用”.ejs”可以什么都不要做。</p>
<p>有些模板引擎并不遵循这一规则,consolidate.js库的建立是为了映射所有的node流行模板引擎遵循这一规则,从而使得他们在Express内无缝工作。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var engines = require('consolidate');
app.engine('haml', engines.haml);
app.engine('html', engines.hogan); ### app.param([name], callback)
</code></pre></div></div>
<p>映射路由参数规则。例如当:user存在于一个路由路径中,你需要自动提供req.user给路由映射启动逻辑,或者执行输入参数验证。</p>
<p>下面的代码说明了如果callback很像中间件,从而支持异常操作,但却增加了一个参数,这里命名为id。然后尝试执行加载用户信息,赋值给req.user,否则传递一个错误到next(err)。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.param('user', function(req, res, next, id){
User.find(id, function(err, user){
if (err) {
next(err);
} else if (user) {
req.user = user;
next();
} else {
next(new Error('failed to load user'));
}
});
});
</code></pre></div></div>
<p>另外,你可能只传递一个回调函数,在这种情况下你有机会改变app.param()API。例如express-params定义了下面的回调函数,它允许你使用给定的正则表达式限制参数。</p>
<p>这个例子有点更先进,检查当第二个参数是正则表达式,返回回调函数很像”user”参数例子。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.param(function(name, fn){
if (fn instanceof RegExp) {
return function(req, res, next, val){
var captures;
if (captures = fn.exec(String(val))) {
req.params[name] = captures;
next();
} else {
next('route');
}
}
}
});
</code></pre></div></div>
<p>该方法可以被用来有效的验证参数,或者解析提供匹配分组:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.param('id', /^\d+$/);
app.get('/user/:id', function(req, res){
res.send('user ' + req.params.id);
});
app.param('range', /^(\w+)\.\.(\w+)?$/);
app.get('/range/:range', function(req, res){
var range = req.params.range;
res.send('from ' + range[1] + ' to ' + range[2]);
}); ### app.VERB(path, [callback...], callback)
</code></pre></div></div>
<p>Express中App.WEB()方法提供了路由功能,其中VERB是一个HTTP动作,跟app.post()类似。可提供多个回调函数,都是一视同仁,表现跟中间件一样,唯一不一样的是通过调用next(‘route’)来继续其余的路由回调。这个机制可以用来执行路由的前提条件,然后将控制权传递给随后的路由,没有理由进行路由的匹配。</p>
<p>下面的代码说明了多个简单路由定义的可行性。Express将路径字符串转换成正由表达式,用来在内部匹配到来的请求。在执行这些匹配时查询字符串不考虑,例如”GET /”将匹配以下的路由,如”GET /?name=tobi”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/', function(req, res){
res.send('hello world');
});
</code></pre></div></div>
<p>正由表达式也可以使用,如果你有非常特殊的限制可能是有用的,例如下面的”GET /commits/71dbb9c”表达式将很好的匹配”GET /commits/71dbb9c..4c084f9”。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get(/^\/commits\/(\w+)(?:\.\.(\w+))?$/, function(req, res){
var from = req.params[0];
var to = req.params[1] || 'HEAD';
res.send('commit range ' + from + '..' + to);
});
</code></pre></div></div>
<p>可以传递一些回调函数,对于利用中间件加载资源、执行验证等很有用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/user/:id', user.load, function(){
// ...
})
</code></pre></div></div>
<p>如果你有多个共同的中间件路由,可以使用路由api的all。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var middleware = [loadForum, loadThread];
app.route('/forum/:fid/thread/:tid')
.all(loadForum)
.all(loadThread)
.get(function() { //... });
.post(function() { //... });
</code></pre></div></div>
<p>两个中间件将用来处理GET和POST请求。</p>
<h3 id="appallpath-callback-callback">app.all(path, [callback…], callback)</h3>
<p>此方法功能就像app.VERB()方法,但它匹配所有HTTP的动作。
该方法用于映射具体的路径前缀的”global”逻辑或者任意匹配非常有用。例如如果你将下面的路由放在其他路由前面定义,这将导致从这个规则起所有的请求都需要身份验证,并自动加载一个用户。请记住这些回调函数不应该被当作终点,loadUser可以当作一个任务,然后调用next()继续匹配随后的路由。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('*', requireAuthentication, loadUser); 等价于:
app.all('*', requireAuthentication)
app.all('*', loadUser);
</code></pre></div></div>
<p>另一个很好的例子是白名单”global”功能。下面的例子跟之前很像,但只限制”/api”为前缀的路径:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.all('/api/*', requireAuthentication); ### app.route(path)
</code></pre></div></div>
<p>返回一个路由的实例用来处理可选的中间件HTTP动作。推荐使用app.route()方法用来避免路由重复命名以及由此导致的错误。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var app = express();
app.route('/events')
.all(function(req, res, next) {
// runs for all HTTP verbs first
// think of it as route specific middleware!
})
.get(function(req, res, next) {
res.json(...);
})
.post(function(req, res, next) {
// maybe add a new event...
}) ### app.locals
</code></pre></div></div>
<p>应用本地变量提供给所有在这个应用程序内渲染的模板。这是一个非常有用的模板函数,就像应用程序级别数据一样。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.locals.title = 'My App';
app.locals.strftime = require('strftime');
app.locals.email = 'me@myapp.com';
</code></pre></div></div>
<p>该app.locals对象是JavaScript对象。添加在它上面的属性被当成应用程序内部的本地变量。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.locals.title
// => 'My App'
app.locals.email
// => 'me@myapp.com'
</code></pre></div></div>
<p>默认情况下Express只有一个应用程序级别的局部变量,那就是settings。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set('title', 'My App');
// use settings.title in a view ### app.render(view, [options], callback)
</code></pre></div></div>
<p>使用回调函数返回的渲染字符串渲染视图。这是res.render()的应用程序级别的版本,它们的行为是一样的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.render('email', function(err, html){
// ...
});
app.render('email', { name: 'Tobi' }, function(err, html){
// ...
}); ### app.listen()
</code></pre></div></div>
<p>绑定并监听给定主机和端口的连接,该方法和node的http.Server#listener()方法是一致的。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var app = express();
app.listen(3000);
</code></pre></div></div>
<p>express()返回的app实际上是一个JavaScript函数,目的是传递给node的http服务器作为回调处理请求。这使得你可以轻松的为你的应用程序提供HTTP和HTTPS版本相同的代码库,app并不从HTTP或者HTTPS继承,就是一个简单的回调函数:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require('express');
var https = require('https');
var http = require('http');
var app = express();
http.createServer(app).listen(80);
https.createServer(options, app).listen(443);
</code></pre></div></div>
<p>该app.listen()方法定义成一个简单方便的方法,如果你想使用HTTPS或者同时使用,使用上面的方法。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
</code></pre></div></div>
node Express 框架搭建
2014-06-06T00:00:00+00:00
http://www.blogways.net/blog/2014/06/06/node-express
<h2 id="一express安装">一、Express安装</h2>
<p>Express是一个node.js模块,采用npm全局模块。</p>
<p>npm install -g express</p>
<h3 id="二新建项目">二、新建项目</h3>
<p>创建一个项目express testExpress,会自动生成目录。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> create : testExpress
create : testExpress/package.json
create : testExpress/app.js
create : testExpress/public
create : testExpress/public/images
create : testExpress/views
create : testExpress/views/layout.jade
create : testExpress/views/index.jade
create : testExpress/routes
create : testExpress/routes/index.js
create : testExpress/routes/user.js
create : testExpress/public/stylesheets
create : testExpress/public/stylesheets/style.css
create : testExpress/public/javascripts
</code></pre></div></div>
<p>运行node app.js (运行程序,默认地址是http://localhost:3000)
如果打开页面出错,可能你没有安装jade模块,那就输入npm install jade进行安装,在我们
日志分析系统中没有用jade模板,用的ejs模板。</p>
<h3 id="三express项目目录文件介绍">三、express项目目录文件介绍</h3>
<p>Express目录介绍:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>目录/文件 说明
package.json npm依赖配置文件, java Maven中的pom.xml文件
app.js 项目的入口文件
routes/ 用于存放路由文件
public/ 静态文件
javascript/ js
stylesheets/ css
images/ 图片
views/ 模板文件, express默认采用jade
node_modules/ 存放npm安装到本地依赖包,依赖包在package.json文件
中声明,使用npm install指令安装
</code></pre></div></div>
<p>申明:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>以上目录文件介绍仅对于express框架自动生成目录,框架没有除了package.json文件和 node_modules目录,
没有限定其他文件名称和目录结构,大家在实际开发中可以根据项目需要重新定义目录结构。
</code></pre></div></div>
<h3 id="四运行原理">四、运行原理</h3>
<p>4.1 底层:http模块</p>
<p>Express框架建立在node.js内置的http模块上。http模块生成服务器的原始代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var http = require("http");
var app = http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.end("Hello world!\n");
});
app.listen(3000, "localhost");
console.log("Server running at http://localhost:3000/");
</code></pre></div></div>
<p>上面代码的关键是http模块的createServer方法,表示生成一个HTTP服务器实例。该方法接受一个回调函数,该回调函数的参数,分别为代表HTTP请求和HTTP回应的request对象和response对象。</p>
<p>4.2 对http模块的再包装</p>
<p>Express框架的核心是对http模块的再包装。上面的代码用Express改写如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require("express");
var http = require("http");
var app = express();
app.use(function(request, response) {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Hello world!\n");
});
http.createServer(app).listen(3000);
</code></pre></div></div>
<p>比较两段代码,可以看到它们非常接近,唯一的差别是createServer方法的参数,从一个回调函数变成了一个Epress对象的实例。而这个实例使用了use方法,加载了与上一段代码相同的回调函数。</p>
<p>Express框架等于在http模块之上,加了一个中间层,而use方法则相当于调用中间件。</p>
<p>4.3 中间件</p>
<p>简单说,中间件(middleware)就是处理HTTP请求的函数,用来完成各种特定的任务,比如检查用户是否登录、分析数据、以及其他在需要最终将数据发送给用户之前完成的任务。它最大的特点就是,一个中间件处理完,再传递给下一个中间件。</p>
<p>node.js的内置模块http的createServer方法,可以生成一个服务器实例,该实例允许在运行过程中,调用一系列函数(也就是中间件)。当一个HTTP请求进入服务器,服务器实例会调用第一个中间件,完成后根据设置,决定是否再调用下一个中间件。中间件内部可以使用服务器实例的response对象(ServerResponse,即回调函数的第二个参数),以及一个next回调函数(即第三个参数)。每个中间件都可以对HTTP请求(request对象)做出回应,并且决定是否调用next方法,将request对象再传给下一个中间件。</p>
<p>一个不进行任何操作、只传递request对象的中间件,大概是下面这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function uselessMiddleware(req, res, next) {
next();
}
</code></pre></div></div>
<p>上面代码的next为中间件的回调函数。如果它带有参数,则代表抛出一个错误,参数为错误文本。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>function uselessMiddleware(req, res, next) {
next('出错了!');
} 抛出错误以后,后面的中间件将不再执行,直到发现一个错误处理函数为止。
</code></pre></div></div>
<p>4.4 use方法</p>
<p>use是express调用中间件的方法,它返回一个函数。下面是一个连续调用两个中间件的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require("express");
var http = require("http");
var app = express();
app.use(function(request, response, next) {
console.log("In comes a " + request.method + " to " + request.url);
next();
});
app.use(function(request, response) {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Hello world!\n");
});
http.createServer(app).listen(3000);
</code></pre></div></div>
<p>上面代码先调用第一个中间件,在控制台输出一行信息,然后通过next方法,调用第二个中间件,输出HTTP回应。由于第二个中间件没有调用next方法,所以不再request对象就不再向后传递了。</p>
<p>使用use方法,可以根据请求的网址,返回不同的网页内容。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require("express");
var http = require("http");
var app = express();
app.use(function(request, response, next) {
if (request.url == "/") {
response.writeHead(200, { "Content-Type": "text/plain" });
response.end("Welcome to the homepage!\n");
} else {
next();
}
});
app.use(function(request, response, next) {
if (request.url == "/about") {
response.writeHead(200, { "Content-Type": "text/plain" });
} else {
next();
}
});
app.use(function(request, response) {
response.writeHead(404, { "Content-Type": "text/plain" });
response.end("404 error!\n");
});
http.createServer(app).listen(3000);
</code></pre></div></div>
<p>上面代码通过request.url属性,判断请求的网址,从而返回不同的内容。</p>
<p>除了在回调函数内部,判断请求的网址,Express也允许将请求的网址写在use方法的第一个参数。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.use('/', someMiddleware);
</code></pre></div></div>
<p>上面代码表示,只对根目录的请求,调用某个中间件。</p>
<h3 id="五express的方法">五、Express的方法</h3>
<p>5.1 all方法和HTTP动词方法</p>
<p>针对不同的请求,Express提供了use方法的一些别名。比如,上面代码也可以用别名的形式来写。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var express = require("express");
var http = require("http");
var app = express();
app.all("*", function(request, response, next) {
response.writeHead(200, { "Content-Type": "text/plain" });
next();
});
app.get("/", function(request, response) {
response.end("Welcome to the homepage!");
});
app.get("/about", function(request, response) {
response.end("Welcome to the about page!");
});
app.get("*", function(request, response) {
response.end("404!");
});
http.createServer(app).listen(3000);
</code></pre></div></div>
<p>上面代码的all方法表示,所有请求都必须通过该中间件,参数中的“*”表示对所有路径有效。get方法则是只有GET动词的HTTP请求通过该中间件,它的第一个参数是请求的路径。由于get方法的回调函数没有调用next方法,所以只要有一个中间件被调用了,后面的中间件就不会再被调用了。</p>
<p>除了get方法以外,Express还提供post、put、delete方法,即HTTP动词都是Express的方法。</p>
<p>这些方法的第一个参数,都是请求的路径。除了绝对匹配以外,Express允许模式匹配:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get("/hello/:who", function(req, res) {
res.end("Hello, " + req.params.who + ".");
});
</code></pre></div></div>
<p>上面代码将匹配“/hello/alice”网址,网址中的alice将被捕获,作为req.params.who属性的值。需要注意的是,捕获后需要对网址进行检查,过滤不安全字符,上面的写法只是为了演示,生产中不应这样直接使用用户提供的值。</p>
<p>如果在模式参数后面加上问号,表示该参数可选:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get('/hello/:who?',function(req,res) {
if(req.params.id) {
res.end("Hello, " + req.params.who + ".");
}
else {
res.send("Hello, Guest.");
}
});
</code></pre></div></div>
<p>5.2 set方法</p>
<p>set方法用于指定变量的值。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.set("views", __dirname + "/views");
app.set("view engine", "jade");
</code></pre></div></div>
<p>上面代码使用set方法,为系统变量“views”和“view engine”指定值。</p>
<p>5.3 response对象</p>
<p>(1)response.redirect方法</p>
<p>response.redirect方法允许网址的重定向。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>response.redirect("/hello/anime");
response.redirect("http://www.example.com");
response.redirect(301, "http://www.example.com");
</code></pre></div></div>
<p>(2)response.sendFile方法</p>
<p>response.sendFile方法用于发送文件。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>response.sendFile("/path/to/anime.mp4");
</code></pre></div></div>
<p>(3)response.render方法</p>
<p>response.render方法用于渲染网页模板。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app.get("/", function(request, response) {
response.render("index", { message: "Hello World" });
});
</code></pre></div></div>
<p>上面代码使用render方法,将message变量传入index模板,渲染成HTML网页。</p>
<p>5.4 requst对象</p>
<p>(1)request.ip</p>
<p>request.ip属性用于获得HTTP请求的IP地址。</p>
<p>(2)request.files</p>
<p>request.files用于获取上传的文件。</p>
<h3 id="六日志系统部分配置">六、日志系统部分配置</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// should be placed before express.static
app.use(express.compress({
filter: function (req, res) {
return /json|text|javascript|css/.test(res.getHeader('Content-Type'))
},
level: 9
}))
app.use(express.favicon('public/favicon.ico'))
app.use(express.static(config.root + '/public'))
// don't use logger for test env
if (process.env.NODE_ENV !== 'test') {
app.use(express.logger('dev'))
}
// set views path, template engine and default layout
app.engine('html', require('ejs').__express)
app.set('views', config.root + '/app/views')
app.set('view engine', 'html')
app.use(partials());
app.use(pjax());
// cookieParser should be above session
app.use(express.cookieParser())
// bodyParser should be above methodOverride
app.use(express.bodyParser())
app.use(express.methodOverride())
//log4js
log.use(app);
app.use(function(req, res, next){
res.on('header', function() {
if (!req.session) return;
if (req.session.cookie.expires==null) return;
req.session.cookie.expires = new Date(Date.now() + 1000*60*60*24*14)
})
next()
});
// express/mongo session storage
app.use(express.session({
secret: 'logAnalyse-pangu',
cookie: { maxAge: 900000 }, //15 minute
store: new mongoStore({
url: config.db,
collection : 'sessions'
})
}))
// connect flash for flash messages - should be declared after sessions
app.use(flash())
// adds CSRF support
if (process.env.NODE_ENV !== 'test') {
app.use(express.csrf())
}
app.use(function(req, res, next){
//if(req.url != "/faye")
res.locals.csrf_token = req.csrfToken();
next()
})
//menu Plug-in technology
var menus = [];
require('../app/controllers/menu').loadMenu(config.root+'/app/controllers/menu',
function(m){menus = m;});
//Public Response Information
app.use(function(req, res, next){
if (req.session.user){
res.locals.menus = menus;
res.locals.current_user = req.session.user;
}else{
if(req.url != "/login.html" && req.url != "/auth.html" && req.url != "/logout"
&& req.url != "/register" && req.url != "/registerAction"){
if (req.url == "/getInbox.html"){
ContentType = "text/plain";
res.StatusCode =500;
res.write("会话超时,请重新登录!");
res.end();
return;
}
return res.redirect('/login.html')
}
}
return next();
});
//Plug-in technology
plugin(app).require(config.root+'/app/controllers/plugin').load();
// routes should be at the last
app.use(app.router)
</code></pre></div></div>
EventProxy在node的应用
2014-06-05T00:00:00+00:00
http://www.blogways.net/blog/2014/06/05/node-eventproxy
<h2 id="一eventproxy简介">一、EventProxy简介</h2>
<p>EventProxy作者 田永强,新浪微博@朴灵,前端工程师,曾就职于SAP,现就职于淘宝,花名朴灵,致力于NodeJS和Mobile Web App方面的研发工作。EventProxy 仅仅是一个很轻量的工具,但是能够带来一种事件式编程的思维变化。有以下几个特点:</p>
<p>1.利用事件机制解耦复杂业务逻辑;</p>
<p>2.移除被广为诟病的深度callback嵌套问题;</p>
<p>3.将串行等待变成并行等待,提升多异步协作场景下的执行效率;</p>
<p>4.友好的Error handling;</p>
<p>5.无平台依赖,适合前后端,能用于浏览器和Node.js;</p>
<p>6.兼容CMD,AMD以及CommonJS模块环境。</p>
<h3 id="二安装">二、安装</h3>
<p>通过NPM安装即可使用:$ npm install eventproxy</p>
<p>调用:var EventProxy = require(‘eventproxy’);</p>
<h3 id="三使用">三、使用</h3>
<p>这里只简单介绍EventProxy在node环境中的应用,至于前端及其他环境使用见:https://github.com/JacksonTian/eventproxy。</p>
<p>1.过去异步的I/O操作时,很容易会写成回调函数深度嵌套,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var add= function (v1, v2, v3){
console.log(v1+v2+v3+'');
};
var value1,value2,value3
clinet.get("key1", function (err, data) {
// do something
value1 = data
clinet.get("key2", function (err, data) {
// do something
value2=data
clinet.get("key3", function (err, data) {
//do something
value3 = data
add(value1, value2, value3);
});
});
});
</code></pre></div></div>
<p>2.使用EventProxy后可以更多关心业务,去掉深度嵌套,并且在一些情况下显著提高效率</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var EventProxy = require('./eventproxy');
var proxy = new EventProxy();
var add= function (v1, v2, v3){
console.log(v1+v2+v3+'');
};
proxy.assign("v1", "v2", "v3", add);
clinet1.get("key1", function (err, data) {
//do something
proxy.trigger("v1", data);
});
clinet2.get("data", function (err, data) {
//do something
proxy.trigger("v2", data);
});
clinet3.get("l10n", function (err, data) {
//do something
proxy.trigger("v3", data);
});
</code></pre></div></div>
<p>3.EventProxy在日志分析系统中的应用</p>
<p>以下为日志系统中服务启动加载菜单的程序:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>var fs = require('fs'),
EventProxy = require('eventproxy').EventProxy;
exports.loadMenu = function(dir,cb){
var proxy = new EventProxy();
proxy.assign('menus', cb);
fs.readdir(dir, function(err, files) {
var menusTemp = [];
if(err) {
throw new Error(err);
}
files.forEach(function(filename) {
if(filename.substring(filename.length-3) == '.js'){
var filepath = [ dir, filename ].join('/');
var tmp = require(filepath);
for (m in tmp){
menusTemp.push(tmp[m]);
}
}
});
proxy.trigger('menus', menusTemp);
});
}
</code></pre></div></div>
<p>其中loadMenu是在服务启动是被调用,传入的参数为菜单文件路径、回调函数,程序首先通过
proxy.assign(‘menus’, cb);监听menus,在程序读取完菜单文件后调用 proxy.trigger(‘menus’, menusTemp);触发cb回调函数,参数为menusTemp。</p>
Apache Commons 系列简介 之 Lang
2014-03-10T00:00:00+00:00
http://www.blogways.net/blog/2014/03/10/ApacheCommonsLang
<h3 id="一概述">一、概述###</h3>
<p><code class="language-plaintext highlighter-rouge">Apache Commons Lang</code>库提供了标准Java库函数里所没有提供的Java核心类的操作方法。<code class="language-plaintext highlighter-rouge">Apache Commons Lang</code>为java.lang API提供了大量的辅助工具,尤其是在String操作方法,基础数值方法,对象引用,并发行,创建及序列化,系统属性方面。</p>
<p>Lang3.0及其后续版本使用的包名为<code class="language-plaintext highlighter-rouge">org.apache.commons.lang3</code>,而之前的版本为<code class="language-plaintext highlighter-rouge">org.apache.commons.lang</code>,允许其在被使用的同时作为一个较早的版本。</p>
<p><code class="language-plaintext highlighter-rouge">Apache Commons Lang 3.3 API</code>包列表:</p>
<blockquote>
<ul>
<li>org.apache.commons.lang3</li>
<li>org.apache.commons.lang3.builder</li>
<li>org.apache.commons.lang3.concurrent</li>
<li>org.apache.commons.lang3.event</li>
<li>org.apache.commons.lang3.exception</li>
<li>org.apache.commons.lang3.math</li>
<li>org.apache.commons.lang3.mutable</li>
<li>org.apache.commons.lang3.reflect</li>
<li>org.apache.commons.lang3.text</li>
<li>org.apache.commons.lang3.text.translate</li>
<li>org.apache.commons.lang3.time</li>
<li>org.apache.commons.lang3.tuple</li>
</ul>
</blockquote>
<h3 id="二下载">二、下载###</h3>
<p>官方下载页:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://http://commons.apache.org/proper/commons-lang/download_lang.cgi 源码:
svn checkout http://svn.apache.org/repos/asf/commons/proper/pool/trunk commons-pool2
</code></pre></div></div>
<p>Maven工程依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.3</version>
</dependency>
</code></pre></div></div>
<h3 id="三使用说明">三、使用说明###</h3>
<h4 id="31-orgapachecommonslang3">3.1 org.apache.commons.lang3####</h4>
<p>此包提供了高度可重用静态的工具方法,主要是对<code class="language-plaintext highlighter-rouge">java.lang</code>类的一些补充。</p>
<p>由于此包中方法绝大多数都为静态的,因此__不需要创建实例化相应的对象__,而是通过类名__直接调用__需要的方法。</p>
<p><code class="language-plaintext highlighter-rouge">ArrayUtils</code>是一个对数组进行特殊处理的类。当然 <code class="language-plaintext highlighter-rouge">jdk</code>中的<code class="language-plaintext highlighter-rouge">Arrays</code>是有一些功能的,<code class="language-plaintext highlighter-rouge">Array</code>也提供了一些动态访问 <code class="language-plaintext highlighter-rouge">java</code>数组的方法,这里的<code class="language-plaintext highlighter-rouge">ArrayUtils</code>扩展提供了更多的功能。</p>
<p>下面是<code class="language-plaintext highlighter-rouge">indexOf</code>方法的具体实现,用以从指定的<code class="language-plaintext highlighter-rouge">startIndex</code>开始,从数组<code class="language-plaintext highlighter-rouge">array</code>中返回第一个值为<code class="language-plaintext highlighter-rouge">valueToFind</code>的下标。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static int indexOf(final double[] array, final double valueToFind, int startIndex) {
if (ArrayUtils.isEmpty(array)) {
return INDEX_NOT_FOUND;
}
if (startIndex < 0) {
startIndex = 0;
}
for (int i = startIndex; i < array.length; i++) {
if (valueToFind == array[i]) {
return i;
}
}
return INDEX_NOT_FOUND;
}
</code></pre></div></div>
<p>在使用此方法的时候__不应该__:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ArrayUtils au = new ArrayUtils();
au.indexOf(array,valueToFind,startIndex);
</code></pre></div></div>
<p><strong>正确的使用方式</strong>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ArrayUtils.indexOf(array,valueToFind,startIndex);
</code></pre></div></div>
<p>一个比较完整的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package wz.lang3.test;
import org.apache.commons.lang3.ArrayUtils;
public class arrayutilstest
{
public static void main(String[] args)
{
double[] array = {1.23,2.34,3.45,4.56,5.67,6.78,7.89,8.90};
int result = ArrayUtils.indexOf(array, 5.67, 3);
System.out.println(result);
}
}
//输出结果:4
</code></pre></div></div>
<p>以下是网络实例:
<strong>[ArrayUtils实例][]</strong>!
<strong>[StringUtils实例][]</strong>!
[ArrayUtils实例]: http://www.blogjava.net/sean/archive/2005/07/30/8775.html “ArrayUtils实例”
[StringUtils实例]: http://www.blogjava.net/sean/archive/2005/07/30/8776.html “StringUtils实例”</p>
<p>其他的一些类的用途:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">AnnotationUtils</code>用于辅助处理注释实例。</li>
<li><code class="language-plaintext highlighter-rouge">CharSetUtils</code>用于操作字符集实例。</li>
<li><code class="language-plaintext highlighter-rouge">CharUtils</code>用于操作字符基本类型及字符类对象。</li>
<li><code class="language-plaintext highlighter-rouge">StringUtils</code>用于实现对字符串的操作,处理null输入。</li>
<li><strong>[其他类][OtherClass]</strong>。
[OtherClass]: http://commons.apache.org/proper/commons-lang/javadocs/api-release/index.html “Org.apache.commons.lang3类列表”</li>
</ul>
<h4 id="32-orgapachecommonslang3builder">3.2 org.apache.commons.lang3.builder####</h4>
<p>辅助实现<code class="language-plaintext highlighter-rouge">equals(Object)</code>,<code class="language-plaintext highlighter-rouge">toString()</code>,<code class="language-plaintext highlighter-rouge">hashCode()</code>, 和 <code class="language-plaintext highlighter-rouge">compareTo(Object)</code>方法,
在这个包里面一共有7个类:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">CompareToBuilder</code> : 用于辅助实现<code class="language-plaintext highlighter-rouge">Comparable.compareTo(Object)</code>方法;</li>
<li><code class="language-plaintext highlighter-rouge">EqualsBuilder</code> : 用于辅助实现<code class="language-plaintext highlighter-rouge">Object.equals(Object)</code>方法;</li>
<li><code class="language-plaintext highlighter-rouge">HashCodeBuilder</code> : 用于辅助实现<code class="language-plaintext highlighter-rouge">Object.hashCode()</code>方法;</li>
<li><code class="language-plaintext highlighter-rouge">ToStringBuilder</code> : 用于辅助实现<code class="language-plaintext highlighter-rouge">Object.toString()</code>方法;</li>
<li><code class="language-plaintext highlighter-rouge">ReflectionToStringBuilder</code> : 使用反射机制辅助实现<code class="language-plaintext highlighter-rouge">Object.toString()</code>方法;</li>
<li><code class="language-plaintext highlighter-rouge">ToStringStyle</code> : 辅助<code class="language-plaintext highlighter-rouge">ToStringBuilder</code>控制输出格式;</li>
<li><code class="language-plaintext highlighter-rouge">StandardToStringStyle</code> : 辅助<code class="language-plaintext highlighter-rouge">ToStringBuilder</code>控制标准格式。</li>
</ul>
<p>在我们的日常编码过程当中,经常会使用到比较两个对象是否相等、比较大小、取hash、获取对象信息等。但是在实现这些方法的具体代码当中,稍微有点不注意就会出现一些BUG,而且有些往往还非常难以发现,因此<code class="language-plaintext highlighter-rouge">org.apache.commons.lang3.builder</code>中提供的这些用于辅助实现上述功能的方法就比较好了,有了这些类,就可以更好、更快、更方便的实现上述方法。</p>
<p>以下例子来自网络:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//利用反射机制的版本自动化实现需要的功能
//比较两个对象
public int compareTo(Object o) {
return CompareToBuilder.reflectionCompare(this, o);
}
//判断相等
public boolean equals(Object o) {
return EqualsBuilder.reflectionEquals(this, o);
}
//取hash
public int hashCode() {
return HashCodeBuilder.reflectionHashCode(this);
}
//获取基本信息
public String toString() {
return ReflectionToStringBuilder.toString(this);
}
</code></pre></div></div>
<p>详细例子__[请参考][]__!
[请参考]: http://www.blogjava.net/sean/archive/2005/07/30/8781.html “org.apache.commons.lang.builder”</p>
<h4 id="33-orgapachecommonslang3time">3.3 org.apache.commons.lang3.time####</h4>
<p>用于提供操作时间(Date)和日期(Duration)的方法和类,在这个包里面一共有7个类:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">DateFormatUtils</code> : 提供格式化日期和时间的功能及相关常量,</li>
<li><code class="language-plaintext highlighter-rouge">DateUtils</code> : 在Calendar和Date的基础上提供更方便的访问,</li>
<li><code class="language-plaintext highlighter-rouge">DurationFormatUtils</code> : 提供格式化时间跨度的功能及相关常量,</li>
<li><code class="language-plaintext highlighter-rouge">FastDateFormat</code> : 为java.text.SimpleDateFormat提供一个的线程安全的替代类,</li>
<li><code class="language-plaintext highlighter-rouge">FastDateParser</code> : 为java.text.SimpleDateFormat提供一个的线程安全的替代类,</li>
<li><code class="language-plaintext highlighter-rouge">FastDatePrinter</code> : 为java.text.SimpleDateFormat提供一个的线程安全的替代类,</li>
<li><code class="language-plaintext highlighter-rouge">StopWatch</code> : 提供一套方便的计时器的API。</li>
</ul>
<p>这些包除了<code class="language-plaintext highlighter-rouge">StopWatch</code>,其他的因为都是不可变的,所以是__线程安全__的,此包包含了一些操作时间的基础工具。更<code class="language-plaintext highlighter-rouge">Apache Commons Lang</code>中的其他的大部分类一样,此包中的方法基本均为<code class="language-plaintext highlighter-rouge">static</code>方法,应该__直接使用类名调用__相应的方法予以实现相应的功能。</p>
<p>以下例子来自网络:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package sean.study.jakarta.commons.lang;
import java.util.Calendar;
import java.util.Date;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.time.*;
public class DateTimeUsage
{
public static void main(String[] args)
{
demoDateUtils();
demoStopWatch();
}
public static void demoDateUtils()
{
System.out.println(StringUtils.center(" demoDateUtils ", 30, "="));
Date date = new Date();
String isoDateTime = DateFormatUtils.ISO_DATETIME_FORMAT.format(date);
String isoTime = DateFormatUtils.ISO_TIME_NO_T_FORMAT.format(date);
FastDateFormat fdf = FastDateFormat.getInstance("yyyy-MM");
String customDateTime = fdf.format(date);
System.out.println("ISO_DATETIME_FORMAT: " + isoDateTime);
System.out.println("ISO_TIME_NO_T_FORMAT: " + isoTime);
System.out.println("Custom FastDateFormat: " + customDateTime);
System.out.println("Default format: " + date);
System.out.println("Round HOUR: " + DateUtils.round(date, Calendar.HOUR));
System.out.println("Truncate HOUR: " + DateUtils.truncate(date, Calendar.HOUR));
System.out.println();
}
public static void demoStopWatch()
{
System.out.println(StringUtils.center(" demoStopWatch ", 30, "="));
StopWatch sw = new StopWatch();
sw.start();
operationA();
sw.stop();
System.out.println("operationA used " + sw.getTime() + " milliseconds.");
System.out.println();
}
public static void operationA()
{
try {
Thread.sleep(999);
}
catch (InterruptedException e) {
// do nothing
}
}
}
</code></pre></div></div>
<p>输出结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>======= demoDateUtils ========
ISO_DATETIME_FORMAT: 2005-08-01T12:41:51
ISO_TIME_NO_T_FORMAT: 12:41:51
Custom FastDateFormat: 2005-08
Default format: Mon Aug 01 12:41:51 CST 2005
Round HOUR: Mon Aug 01 13:00:00 CST 2005
Truncate HOUR: Mon Aug 01 12:00:00 CST 2005
======= demoStopWatch ========
operationA used 1000 milliseconds.
</code></pre></div></div>
Apache Commons 系列简介 之 Pool
2014-01-15T00:00:00+00:00
http://www.blogways.net/blog/2014/01/15/apache-commons-pool
<h3 id="一概述">一、概述</h3>
<p><code class="language-plaintext highlighter-rouge">Apache Commons Pool</code>库提供了一整套用于实现对象池化的API,以及若干种各具特色的对象池实现。2.0版本,并非是对1.x的简单升级,而是一个完全重写的对象池的实现,显著的提升了性能和可伸缩性,并且包含可靠的实例跟踪和池监控。第二版要求JDK1.6+。</p>
<h3 id="二下载">二、下载</h3>
<p>官方下载页:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://commons.apache.org/proper/commons-pool/download_pool.cgi
</code></pre></div></div>
<p>源码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svn checkout http://svn.apache.org/repos/asf/commons/proper/pool/trunk commons-pool2
</code></pre></div></div>
<p>Maven工程依赖</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
<version>2.0</version>
</dependency>
</code></pre></div></div>
<h3 id="三使用说明">三、使用说明</h3>
<h4 id="31-创建池化对象">3.1 创建池化对象</h4>
<p>创建池化对象很简单,只要实现<code class="language-plaintext highlighter-rouge">commons-pool</code>的<code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>工厂接口就行了。</p>
<p><code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>是一个池化对象工厂接口,定义了生成对象、激活对象、钝化对象、销毁对象的方法,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface PooledObjectFactory<T> {
PooledObject<T> makeObject();
void activateObject(PooledObject<T> obj);
void passivateObject(PooledObject<T> obj);
boolean validateObject(PooledObject<T> obj);
void destroyObject(PooledObject<T> obj);
}
</code></pre></div></div>
<p>它创建并管理<code class="language-plaintext highlighter-rouge">PooledObject</code>。<code class="language-plaintext highlighter-rouge">PooledObject</code>包含了池化的对象实例,以及这些实例的池化属性,比如创建时间、最后使用时间等等。</p>
<p>如果需要使用<code class="language-plaintext highlighter-rouge">Commons-Pool</code>,那么你就需要提供一个<code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>接口的具体实现。一个比较简单的办法就是,继承<code class="language-plaintext highlighter-rouge">BasePooledObjectFactory</code>这个抽象类。而继承这个抽象类,只需要实现两个方法:<code class="language-plaintext highlighter-rouge">create()</code>和<code class="language-plaintext highlighter-rouge">wrap(T obj)</code>。</p>
<p>实现<code class="language-plaintext highlighter-rouge">create()</code>方法很简单,而实现<code class="language-plaintext highlighter-rouge">wrap(T obj)</code>也有捷径,可以使用类<code class="language-plaintext highlighter-rouge">DefaultPooledObject </code>,代码可以参考如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Override
public PooledObject<Foo> wrap(Foo foo) {
return new DefaultPooledObject<Foo>(foo);
}
</code></pre></div></div>
<p>比如,一个完整的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package test.test;
import org.apache.commons.pool2.BasePooledObjectFactory;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.impl.DefaultPooledObject;
public class StringBufferFactory extends BasePooledObjectFactory<StringBuffer> {
@Override
public StringBuffer create() throws Exception {
return new StringBuffer();
}
@Override
public PooledObject<StringBuffer> wrap(StringBuffer obj) {
return new DefaultPooledObject<StringBuffer>(obj);
}
}
</code></pre></div></div>
<p>有时候,单用对池内所有对象一视同仁的对象池,并不能解决问题。例如,有时需要通过key来获取不同的对象,这样,就有可能取出不合用的对象的麻烦。当然,可以通过为每一组参数相同的同类对象建立一个单独的对象池来解决这个问题。但是,如果使用普通的<code class="language-plaintext highlighter-rouge">ObjectPool</code>来实施这个计策的话,因为普通的<code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>只能生产出大批设置完全一致的对象,就需要为每一组参数相同的对象编写一个单独的<code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>,工作量相当可观。这种时候就可以使用<code class="language-plaintext highlighter-rouge">BaseKeyedPooledObjectFactory</code>来替代<code class="language-plaintext highlighter-rouge">BasePooledObjectFactory</code>.这个类,实现的是<code class="language-plaintext highlighter-rouge">KeyedPooledObjectFactory</code>接口,和<code class="language-plaintext highlighter-rouge">PooledObjectFactory</code>接口类似,只是在相关的方法中多了<code class="language-plaintext highlighter-rouge">Key</code>参数,定义如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface KeyedPoolableObjectFactory<K,V> {
PooledObject<V> makeObject(K key);
void activateObject(K key, PooledObject<V> obj);
void passivateObject(K key, PooledObject<V> obj);
boolean validateObject(K key, PooledObject<V> obj);
void destroyObject(K key, PooledObject<V> obj);
}
</code></pre></div></div>
<h4 id="32-创建对象池">3.2 创建对象池</h4>
<p>在<code class="language-plaintext highlighter-rouge">org.apache.commons.pool2.impl</code>中预设了三个可以直接使用的对象池:<code class="language-plaintext highlighter-rouge">GenericObjectPool</code>、<code class="language-plaintext highlighter-rouge">GenericKeyedObjectPool</code>和<code class="language-plaintext highlighter-rouge">SoftReferenceObjectPool</code>。</p>
<p>通常使用<code class="language-plaintext highlighter-rouge">GenericObjectPool</code>来创建对象池,如果是对象池是<code class="language-plaintext highlighter-rouge">Keyed</code>的,那么可以使用<code class="language-plaintext highlighter-rouge">GenericKeyedObjectPool</code>来创建对象池。这两个类都提供了丰富的配置选项。这两个对象池的特点是可以设置对象池中的对象特征,包括LIFO方式、最大空闲数、最小空闲数、是否有效性检查等等。两者的区别如前面所述,后者支持<code class="language-plaintext highlighter-rouge">Keyed</code>。</p>
<p>而<code class="language-plaintext highlighter-rouge">SoftReferenceObjectPool</code>对象池,它利用一个<code class="language-plaintext highlighter-rouge">java.util.ArrayList</code>对象来保存对象池里的对象。不过它并不在对象池里直接保存对象本身,而是保存它们的“软引用”(<code class="language-plaintext highlighter-rouge">Soft Reference</code>)。这种对象池的特色是:可以保存任意多个对象,不会有容量已满的情况发生;在对象池已空的时候,调用它的<code class="language-plaintext highlighter-rouge">borrowObject</code>方法,会自动返回新创建的实例;可以在初始化同时,在池内预先创建一定量的对象;当内存不足的时候,池中的对象可以被Java虚拟机回收。</p>
<p>举个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>new GenericObjectPool<StringBuffer>(new StringBufferFactory());
</code></pre></div></div>
<p>我们也可以使用<code class="language-plaintext highlighter-rouge">GenericObjectPoolConfig</code>来对上面创建的对象池进行一些参数配置,创建的Config参数,可以使用<code class="language-plaintext highlighter-rouge">setConfig</code>方法传给对象池,也可以在对象池的构造方法中作为参数传入。</p>
<p>举个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>GenericObjectPoolConfig conf = new GenericObjectPoolConfig();
conf.setMaxTotal(20);
conf.setMaxIdle(10);
...
GenericObjectPool<StringBuffer> pool = new GenericObjectPool<StringBuffer>(new StringBufferFactory(), conf);
</code></pre></div></div>
<h4 id="33-使用对象池">3.3 使用对象池</h4>
<p>对象池使用起来很方便,简单一点就是使用<code class="language-plaintext highlighter-rouge">borrowObject</code>和<code class="language-plaintext highlighter-rouge">returnObject</code>两个方法,直接给参考代码吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>StringBuffer buf = null;
try {
buf = pool.borrowObject();
...
} catch(IOException e) {
throw e;
} catch(Exception e) {
throw new RuntimeException("Unable to borrow buffer from pool" +
e.toString());
} finally {
try {
if(null != buf) {
pool.returnObject(buf);
}
} catch(Exception e) {
// ignored
}
}
</code></pre></div></div>
Apache Commons 系列简介 之 CLI
2014-01-15T00:00:00+00:00
http://www.blogways.net/blog/2014/01/15/apache-commons-cli
<p><code class="language-plaintext highlighter-rouge">Apache Commons</code>的主要目的就是,创建和维护一个可重用的java组件库集合。这样Apache社区的开发者,就可以使用相同的基础组件库来开发不同的Apache项目了。</p>
<p>而<code class="language-plaintext highlighter-rouge">Apache Commons</code>的开发者们,将尽量减少这些组件对其他外部库的影响,来确保这些组件可以很容易地进行部署。另外,这些组件将尽可能地保证接口稳定,以便Apache用户(包括Apache项目)可以实现这些组件,而不必担心未来发生变化。</p>
<p>本文将介绍Commons系列中的CLI组件库。</p>
<h3 id="一commons-cli-概述">一、Commons CLI 概述</h3>
<p><code class="language-plaintext highlighter-rouge">Apache Commons CLI</code> 库提供API,可以帮助程序去解析传递给程序的命令行参数。他也可以打印帮助信息,来说明可以运用于命令行的有效参数。</p>
<p>CLI库支持不同格式的选项:</p>
<ol>
<li>POSIX格式的选项(比如:<code class="language-plaintext highlighter-rouge">tar -zxvf foo.tar.gz</code>)</li>
<li>GNU格式的长参数选项(比如:<code class="language-plaintext highlighter-rouge">du --human-readable --max-depth=1</code>)</li>
<li>Java格式的属性(比如:<code class="language-plaintext highlighter-rouge">java -Djava.awt.headless=true -Djava.net.useSystemProxies=true Foo</code>)</li>
<li>带值的单选项(比如:<code class="language-plaintext highlighter-rouge">gcc -O2 foo.c</code>)</li>
<li>单<code class="language-plaintext highlighter-rouge">-</code>号的长参数选项(比如:<code class="language-plaintext highlighter-rouge">ant -projecthelp</code>)</li>
</ol>
<p>CLI库可以提供的帮助信息,类似如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usage: ls
-A,--almost-all do not list implied . and ..
-a,--all do not hide entries starting with .
-B,--ignore-backups do not list implied entried ending with ~
-b,--escape print octal escapes for nongraphic characters
--block-size <SIZE> use SIZE-byte blocks
-c with -lt: sort by, and show, ctime (time of last
modification of file status information) with
-l:show ctime and sort by name otherwise: sort
by ctime
-C list entries by columns
</code></pre></div></div>
<h3 id="二commons-cli-下载">二、Commons CLI 下载</h3>
<p>截止本文撰写时,CLI的最新发布版本为<code class="language-plaintext highlighter-rouge">1.2</code>。</p>
<p>官方下载地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://commons.apache.org/proper/commons-cli/download_cli.cgi
</code></pre></div></div>
<p>源码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svn checkout http://svn.apache.org/repos/asf/commons/proper/cli/trunk/ commons-cli
</code></pre></div></div>
<p>在Maven工程中添加依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.2</version>
</dependency>
</code></pre></div></div>
<h3 id="三使用场景">三、使用场景</h3>
<h3 id="31-布尔选项">3.1 布尔选项</h3>
<p>布尔选项是命令行最常见的选项,作为开关使用,不带参数。如果命令行中存在该选项,那么选项值就为<code class="language-plaintext highlighter-rouge">true</code>,否则其值为<code class="language-plaintext highlighter-rouge">false</code>。</p>
<p>举例,如果程序需要布尔选项<code class="language-plaintext highlighter-rouge">-t</code>,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// create Options object
Options options = new Options();
// add t option
options.addOption("t", false, "display current time");
</code></pre></div></div>
<p>如上,必须创建<code class="language-plaintext highlighter-rouge">Options</code>选项,然后为其添加<code class="language-plaintext highlighter-rouge">Option</code>对象。</p>
<p>上例中,<code class="language-plaintext highlighter-rouge">addOption</code>方法有三个参数:第一个参数类型为<code class="language-plaintext highlighter-rouge">String</code>,给出参数的名字;第二个参数类型为<code class="language-plaintext highlighter-rouge">boolean</code>,用来标记该选项是否需要参数,在上面例子中,布尔选项不需要参数,所以设置为<code class="language-plaintext highlighter-rouge">false</code>;第三个参数是选项的描述信息,该描述信息在打印命令行帮助信息时,会显示出来。</p>
<p>另外,<code class="language-plaintext highlighter-rouge">addOption</code>还存在一个四个参数的调用方式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>addOption(String opt, String longOpt, boolean hasArg, String description)
</code></pre></div></div>
<p>在这里,多了一个长选项参数,在后面的例子中,我们可以看到具体的使用。</p>
<h3 id="32-解析命令行参数">3.2 解析命令行参数</h3>
<p><code class="language-plaintext highlighter-rouge">CommandLineParser</code>提供的方法<code class="language-plaintext highlighter-rouge">parse</code>,就是用来解析命令行中的参数。接口<code class="language-plaintext highlighter-rouge">CommandLineParser</code>存在多种实现类,比如<code class="language-plaintext highlighter-rouge">BasicParser</code>、<code class="language-plaintext highlighter-rouge">PosixParser</code>和<code class="language-plaintext highlighter-rouge">GnuParser</code>,可以根据实际需求选择使用.</p>
<p>其中,<code class="language-plaintext highlighter-rouge">PosixParser</code>与<code class="language-plaintext highlighter-rouge">GnuParser</code>,顾名思义,其区别在于,前者把形如<code class="language-plaintext highlighter-rouge">-log</code>的选项作为三个选项(<code class="language-plaintext highlighter-rouge">l</code>、<code class="language-plaintext highlighter-rouge">o</code>和<code class="language-plaintext highlighter-rouge">g</code>)处理,而后者作为一个选项处理。</p>
<p>具体代码,参考如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>CommandLineParser parser = new GnuParser();
CommandLine cmd = parser.parse( options, args);
</code></pre></div></div>
<p>如果,我们要检查命令行中<code class="language-plaintext highlighter-rouge">t</code>选项是否被列出,可以使用<code class="language-plaintext highlighter-rouge">hasOption</code>方法。例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if(cmd.hasOption("t")) {
// 存在t选项的处理
}
else {
// 不存在t选项的处理
}
</code></pre></div></div>
<h3 id="32-带参数选项">3.2 带参数选项</h3>
<p>除了布尔选项外,还有些选项是需要参数的。比如<code class="language-plaintext highlighter-rouge">c</code>选项需要参数,那么可以如下设置:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// add c option
options.addOption("c", true, "country code");
</code></pre></div></div>
<p>并且,可以通过<code class="language-plaintext highlighter-rouge">getOptionValue</code>方法,获取命令行传入的参数:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// get c option value
String countryCode = cmd.getOptionValue("c");
if(countryCode == null) {
// print default date
}
else {
// print date for country specified by countryCode
}
</code></pre></div></div>
<h3 id="33-ant命令行实例">3.3 Ant命令行实例</h3>
<p>在这里,我们使用一个被普遍使用的java应用程序Ant来解释如果使用CLI库的。</p>
<h4 id="331-先看看ant的命令帮助">3.3.1 先看看Ant的命令帮助</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant [options] [target [target2 [target3] ...]]
Options:
-help print this message
-projecthelp print project help information
-version print the version information and exit
-quiet be extra quiet
-verbose be extra verbose
-debug print debugging information
-emacs produce logging information without adornments
-logfile <file> use given file for log
-logger <classname> the class which is to perform logging
-listener <classname> add an instance of class as a project listener
-buildfile <file> use given buildfile
-D<property>=<value> use value for given property
-find <file> search for buildfile towards the root of the
filesystem and use it
</code></pre></div></div>
<h4 id="332-创建布尔选项">3.3.2 创建布尔选项</h4>
<p>为了代码清晰,在这里我们使用<code class="language-plaintext highlighter-rouge">Option</code>的构造方法来创建。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Option help = new Option( "help", "print this message" );
Option projecthelp = new Option( "projecthelp", "print project help information" );
Option version = new Option( "version", "print the version information and exit" );
Option quiet = new Option( "quiet", "be extra quiet" );
Option verbose = new Option( "verbose", "be extra verbose" );
Option debug = new Option( "debug", "print debugging information" );
Option emacs = new Option( "emacs",
"produce logging information without adornments" );
</code></pre></div></div>
<h4 id="333-创建带参数的选项">3.3.3 创建带参数的选项</h4>
<p>我们使用<code class="language-plaintext highlighter-rouge">OptionBuilder</code>来创建:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Option logfile = OptionBuilder.withArgName( "file" )
.hasArg()
.withDescription( "use given file for log" )
.create( "logfile" );
Option logger = OptionBuilder.withArgName( "classname" )
.hasArg()
.withDescription( "the class which it to perform "
+ "logging" )
.create( "logger" );
Option listener = OptionBuilder.withArgName( "classname" )
.hasArg()
.withDescription( "add an instance of class as "
+ "a project listener" )
.create( "listener");
Option buildfile = OptionBuilder.withArgName( "file" )
.hasArg()
.withDescription( "use given buildfile" )
.create( "buildfile");
Option find = OptionBuilder.withArgName( "file" )
.hasArg()
.withDescription( "search for buildfile towards the "
+ "root of the filesystem and use it" )
.create( "find" );
</code></pre></div></div>
<h4 id="334-创建java属性选项">3.3.4 创建java属性选项</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Option property = OptionBuilder.withArgName( "property=value" )
.hasArgs(2)
.withValueSeparator()
.withDescription( "use value for given property" )
.create( "D" );
</code></pre></div></div>
<h4 id="335-创建options">3.3.5 创建Options</h4>
<p>上面已经创建了每个选项,下面我们需要创建<code class="language-plaintext highlighter-rouge">Options</code>,然后继续使用<code class="language-plaintext highlighter-rouge">addOption</code>方法,向其中添加每个选项,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Options options = new Options();
options.addOption( help );
options.addOption( projecthelp );
options.addOption( version );
options.addOption( quiet );
options.addOption( verbose );
options.addOption( debug );
options.addOption( emacs );
options.addOption( logfile );
options.addOption( logger );
options.addOption( listener );
options.addOption( buildfile );
options.addOption( find );
options.addOption( property );
</code></pre></div></div>
<p>**说明:可以通过<code class="language-plaintext highlighter-rouge">Option</code>的<code class="language-plaintext highlighter-rouge">setRequired</code>方法来设置,选项是否为必输项,默认不是必输项。 **</p>
<h4 id="336-解析命令行参数">3.3.6 解析命令行参数</h4>
<p>我们需要创建一个<code class="language-plaintext highlighter-rouge">CommandLineParser</code>,并用它根据之前设置的<code class="language-plaintext highlighter-rouge">Options</code>来解析命令行参数,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public static void main( String[] args ) {
// create the parser
CommandLineParser parser = new GnuParser();
try {
// parse the command line arguments
CommandLine line = parser.parse( options, args );
}
catch( ParseException exp ) {
// oops, something went wrong
System.err.println( "Parsing failed. Reason: " + exp.getMessage() );
}
}
</code></pre></div></div>
<h4 id="337-获取命令行参数">3.3.7 获取命令行参数</h4>
<p>使用<code class="language-plaintext highlighter-rouge">hasOption</code>方法来检查命令行是否传入选项,使用<code class="language-plaintext highlighter-rouge">getOptionValue</code>来获取传入的参数值。</p>
<p>代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// has the buildfile argument been passed?
if( line.hasOption( "buildfile" ) ) {
// initialise the member variable
this.buildfile = line.getOptionValue( "buildfile" );
}
</code></pre></div></div>
<h4 id="338-设置程序用例帮助信息">3.3.8 设置程序用例/帮助信息</h4>
<p>CLI库还可以根据<code class="language-plaintext highlighter-rouge">Options</code>来自动显示程序的用例/帮助信息。代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// automatically generate the help statement
HelpFormatter formatter = new HelpFormatter();
formatter.printHelp( "ant", options, true );
</code></pre></div></div>
<p>执行后生成下面信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>usage: ant
-D <property=value> use value for given property
-buildfile <file> use given buildfile
-debug print debugging information
-emacs produce logging information without adornments
-file <file> search for buildfile towards the root of the
filesystem and use it
-help print this message
-listener <classname> add an instance of class as a project listener
-logger <classname> the class which it to perform logging
-projecthelp print project help information
-quiet be extra quiet
-verbose be extra verbose
-version print the version information and exit
</code></pre></div></div>
<h3 id="34-再来一个ls实例">3.4 再来一个<code class="language-plaintext highlighter-rouge">ls</code>实例</h3>
<p>下面是帮助信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Usage: ls [OPTION]... [FILE]...
List information about the FILEs (the current directory by default).
Sort entries alphabetically if none of -cftuSUX nor --sort.
-a, --all do not hide entries starting with .
-A, --almost-all do not list implied . and ..
-b, --escape print octal escapes for nongraphic characters
--block-size=SIZE use SIZE-byte blocks
-B, --ignore-backups do not list implied entries ending with ~
-c with -lt: sort by, and show, ctime (time of last
modification of file status information)
with -l: show ctime and sort by name
otherwise: sort by ctime
-C list entries by columns
</code></pre></div></div>
<p>下面是代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>// create the command line parser
CommandLineParser parser = new GnuParser();
// create the Options
Options options = new Options();
options.addOption( "a", "all", false, "do not hide entries starting with ." );
options.addOption( "A", "almost-all", false, "do not list implied . and .." );
options.addOption( "b", "escape", false, "print octal escapes for nongraphic "
+ "characters" );
options.addOption( OptionBuilder.withLongOpt( "block-size" )
.withDescription( "use SIZE-byte blocks" )
.hasArg()
.withArgName("SIZE")
.create() );
options.addOption( "B", "ignore-backups", false, "do not list implied entried "
+ "ending with ~");
options.addOption( "c", false, "with -lt: sort by, and show, ctime (time of last "
+ "modification of file status information) with "
+ "-l:show ctime and sort by name otherwise: sort "
+ "by ctime" );
options.addOption( "C", false, "list entries by columns" );
String[] args = new String[]{ "--block-size=10" };
try {
// parse the command line arguments
CommandLine line = parser.parse( options, args );
// validate that block-size has been set
if( line.hasOption( "block-size" ) ) {
// print the value of block-size
System.out.println( line.getOptionValue( "block-size" ) );
}
}
catch( ParseException exp ) {
System.out.println( "Unexpected exception:" + exp.getMessage() );
}
</code></pre></div></div>
Apache Commons 系列简介 之 BeanUtils
2014-01-15T00:00:00+00:00
http://www.blogways.net/blog/2014/01/15/apache-commons-beanutils
<h3 id="一概述">一、概述</h3>
<p>大部分Java开发人员都会安装JavaBeans的命名规范为属性创建getter和setter方法,我们可以直接通过getXxx和setXxx方法直接进行调用。但是,也有一些场合我们必须动态访问java对象的属性,比如:</p>
<ul>
<li>为和java对象模型进行交互,而创建的脚本语言(如:<code class="language-plaintext highlighter-rouge">Bean Scripting Framework</code>)</li>
<li>为处理WEB展示或者类似需求,而创建的模板语言(如:<code class="language-plaintext highlighter-rouge">JSP</code>和<code class="language-plaintext highlighter-rouge">Velocity</code>)</li>
<li>为JSP和XSP环境创建自定义tag库(如:<code class="language-plaintext highlighter-rouge">Apache Taglibs</code>、<code class="language-plaintext highlighter-rouge">Struts</code>和<code class="language-plaintext highlighter-rouge">Cocoon</code>)</li>
<li>对一些基于XML配置的资源的处理(如:<code class="language-plaintext highlighter-rouge">Ant</code>构建脚本,web应用部署配置文件,Tomcat的<code class="language-plaintext highlighter-rouge">server.xml</code>文件等等)</li>
</ul>
<p>Java语言提供了反射(<code class="language-plaintext highlighter-rouge">Reflection</code>)和内省(<code class="language-plaintext highlighter-rouge">Introspection</code><sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>)API(见<code class="language-plaintext highlighter-rouge">java.lang.reflect</code>和<code class="language-plaintext highlighter-rouge">java.beans</code>这两个包说明),但这些API很难理解并加以应用。而BeanUtils库的目的,就是针对这些能力提供了易用的包装。</p>
<h3 id="二下载及源码">二、下载及源码</h3>
<p>下载地址:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>http://commons.apache.org/proper/commons-beanutils/download_beanutils.cgi
</code></pre></div></div>
<p>源码获取:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>svn checkout http://svn.apache.org/repos/asf/commons/proper/beanutils/trunk/ commons-beanutils
</code></pre></div></div>
<p>Github镜像:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>https://github.com/apache/commons-beanutils
</code></pre></div></div>
<p>Maven依赖:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.0</version>
</dependency>
</code></pre></div></div>
<h3 id="三使用说明">三、使用说明</h3>
<p>自从<code class="language-plaintext highlighter-rouge">1.7.0</code>版开始,BeanUtils发布三个jar包,供应用调用:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">commons-beanutils.jar</code> 包含所有功能</li>
<li><code class="language-plaintext highlighter-rouge">commons-beanutils-core.jar</code> 包含除<code class="language-plaintext highlighter-rouge">Bean Collections</code>类外的所有功能</li>
<li><code class="language-plaintext highlighter-rouge">commons-beanutils-bean-collections.jar</code> 只包含<code class="language-plaintext highlighter-rouge">Bean Collections</code>类</li>
</ul>
<p>应用可以根据需求,选择使用。</p>
<p>在运行时,<code class="language-plaintext highlighter-rouge">commons-beanutils</code>包依赖<code class="language-plaintext highlighter-rouge">commons-logging</code>包,另外,如果使用了下面几个类,则还需要依赖<code class="language-plaintext highlighter-rouge">commons-collections</code>包:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">org.apache.commons.beanutils.BeanMap</code></li>
<li><code class="language-plaintext highlighter-rouge">org.apache.commons.beanutils.BeanPredicate</code></li>
<li><code class="language-plaintext highlighter-rouge">org.apache.commons.beanutils.BeanPropertyValueChangeClosure</code></li>
<li><code class="language-plaintext highlighter-rouge">org.apache.commons.beanutils.BeanPropertyValueEqualsPredicate</code></li>
<li><code class="language-plaintext highlighter-rouge">org.apache.commons.beanutils.BeanToPropertyValueTransformer</code></li>
</ul>
<h4 id="31-标准javabeans">3.1 标准JavaBeans</h4>
<p>在介绍之前,我们先给一个对象的定义,后面的实例将结合这个对象给出。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Employee {
public Address getAddress(String type);
public void setAddress(String type, Address address);
public Employee getSubordinate(int index);
public void setSubordinate(int index, Employee subordinate);
public String getFirstName();
public void setFirstName(String firstName);
public String getLastName();
public void setLastName(String lastName);
}
</code></pre></div></div>
<h4 id="311-基本属性读写">3.1.1 基本属性读写</h4>
<p>首先,我们介绍<code class="language-plaintext highlighter-rouge">PropertyUtils</code>类,其作用:</p>
<ul>
<li>
<p>可以很容易的读取或者设置简单的属性值</p>
<p>所谓“简单”的属性,是指:属性只是一个简单的可被读写的值。这个属性的类型,可能是java语言原生类型(如:<code class="language-plaintext highlighter-rouge">int</code>或一个简单对象<code class="language-plaintext highlighter-rouge">java.lang.String</code>等),也可能是一个由程序或者类库定义的复杂的对象</p>
<p>对其操作的API有:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //读操作
PropertyUtils.getSimpleProperty(Object, String)
//写操作
PropertyUtils.setSimpleProperty(Object, String, Object)
</code></pre></div> </div>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Employee employee = ...;
String firstName = (String)
PropertyUtils.getSimpleProperty(employee, "firstName");
String lastName = (String)
PropertyUtils.getSimpleProperty(employee, "lastName");
... manipulate the values ...
PropertyUtils.setSimpleProperty(employee, "firstName", firstName);
PropertyUtils.setSimpleProperty(employee, "lastName", lastName);
</code></pre></div> </div>
</li>
<li>
<p>针对带序列的属性,支持两种操作方式。</p>
<p>所谓“带序列”的属性,是指:一组有序的对象集合,可以通过自然数下标单独访问。或者可以如同数组那样进行读写。如果,属性的类型是<code class="language-plaintext highlighter-rouge">java.util.List</code>也可以。</p>
<ul>
<li>
<p>一种方式:将下标使用方括号括起来,然后跟在属性名后,当做属性名使用。</p>
<p>其API为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //读操作
PropertyUtils.getIndexedProperty(Object, String)
//取操作
PropertyUtils.setIndexedProperty(Object, String, Object)
</code></pre></div> </div>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Employee employee = ...;
int index = ...;
String name = "subordinate[" + index + "]";
Employee subordinate = (Employee)
PropertyUtils.getIndexedProperty(employee, name);
</code></pre></div> </div>
</li>
<li>
<p>另外一种方式:将下标作为一个单独的参数使用。</p>
<p>其API:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PropertyUtils.getIndexedProperty(Object, String, int)
PropertyUtils.setIndexedProperty(Object, String, int, Object)
</code></pre></div> </div>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Employee employee = ...;
int index = ...;
Employee subordinate = (Employee)
PropertyUtils.getIndexedProperty(employee, "subordinate", index);
</code></pre></div> </div>
</li>
</ul>
</li>
<li>
<p>针对可以Maped的属性,支持两种操作方式,类似上面的带序列属性的读写操作。</p>
<p>所谓“可以Maped”的属性值,是指属性类型为<code class="language-plaintext highlighter-rouge">java.util.Map</code>。可以通过String型的键,对其进行读写。</p>
<ul>
<li>
<p>一种方式:将键值用圆括号括起来,跟在属性名后面,当做属性名使用。</p>
<p>其API:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //读操作
PropertyUtils.getMappedProperty(Object, String)
//写操作
PropertyUtils.setMappedProperty(Object, String, Object)
</code></pre></div> </div>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address(home)", address);
</code></pre></div> </div>
</li>
<li>
<p>另外一种方式:将键值作为一个单独的参数使用。</p>
<p>其API:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> //读操作
PropertyUtils.getMappedProperty(Object, String, String)
//写操作
PropertyUtils.setMappedProperty(Object, String, String, Object)
</code></pre></div> </div>
<p>例如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Employee employee = ...;
Address address = ...;
PropertyUtils.setMappedProperty(employee, "address", "home", address);
</code></pre></div> </div>
</li>
</ul>
</li>
</ul>
<h4 id="312-嵌套属性读写">3.1.2 嵌套属性读写</h4>
<p>结合上面的例子,如果我们要访问雇员家庭地址中的城市信息,按常规编程方式,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String city = employee.getAddress("home").getCity();
</code></pre></div></div>
<p>要使用<code class="language-plaintext highlighter-rouge">PropertyUtils</code>来访问类似上面的多层属性,可以通过<code class="language-plaintext highlighter-rouge">.</code>符号将属性名串联起来作为访问路径。使用的API为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PropertyUtils.getNestedProperty(Object, String)
PropertyUtils.setNestedProperty(Object, String, Object)
</code></pre></div></div>
<p>上面实例的等效代码为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String city = (String) PropertyUtils.getNestedProperty(employee, "address(home).city");
</code></pre></div></div>
<p><strong>这里再给出一组更简单的常用的API:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>//读操作
PropertyUtils.getProperty(Object, String)
//写操作
PropertyUtils.setProperty(Object, String, Object)
</code></pre></div></div>
<p>使用举例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Employee employee = ...;
String city = (String) PropertyUtils.getProperty(employee,
"subordinate[3].address(home).city");
</code></pre></div></div>
<h4 id="32-动态-beans">3.2 动态 Beans</h4>
<p>上面介绍的<code class="language-plaintext highlighter-rouge">PropertyUtils</code>类,可以轻松地对已经存在的JavaBean的属性进行访问。但是,有些场合,你需要通过动态计算出来的属性集,来确定一个JavaBean,而不必实际编写某个确定的JavaBean。</p>
<p>为此目的,BeanUtils提供<code class="language-plaintext highlighter-rouge">DynaBean</code>接口和与之相关的<code class="language-plaintext highlighter-rouge">DynaClass</code>接口。</p>
<p>我们看看代码,如何使用<code class="language-plaintext highlighter-rouge">DynaBean</code>来实现前面<code class="language-plaintext highlighter-rouge">Employee</code>类的功能:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>DynaBean employee = ...; // 这里的代码,要根据你使用的具体的BynaBean实现来定了
String firstName = (String) employee.get("firstName");
Address homeAddress = (Address) employee.get("address", "home");
Object subordinate = employee.get("subordinate", 2);
</code></pre></div></div>
<p>由于<code class="language-plaintext highlighter-rouge">DynaBean</code>和<code class="language-plaintext highlighter-rouge">DynaClass</code>仅仅是接口,所以可以有不同的实现。可以根据不同的场景,开发不同的实现。下面我们来介绍几个<code class="language-plaintext highlighter-rouge">BeanUtils</code>内置的实现,你也可以根据你的具体需求,来定制自己的实现。</p>
<h4 id="321-basicdynabean和basicdynaclass">3.2.1 <code class="language-plaintext highlighter-rouge">BasicDynaBean</code>和<code class="language-plaintext highlighter-rouge">BasicDynaClass</code></h4>
<p>我们结合前面的<code class="language-plaintext highlighter-rouge">Employee</code>例子,来介绍<code class="language-plaintext highlighter-rouge">BasicDynaBean</code>和<code class="language-plaintext highlighter-rouge">BasicDynaClass</code>的使用步骤及实例代码如下:</p>
<ol>
<li>
<p>创建属性集</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaProperty[] props = new DynaProperty[]{
new DynaProperty("address", java.util.Map.class),
new DynaProperty("subordinate", Employee[].class),
new DynaProperty("firstName", String.class),
new DynaProperty("lastName", String.class)
};
</code></pre></div> </div>
</li>
<li>
<p>创建<code class="language-plaintext highlighter-rouge">BasicDynaClass</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> BasicDynaClass dynaClass = new BasicDynaClass("employee", null, props);
</code></pre></div> </div>
<p>这里<code class="language-plaintext highlighter-rouge">BasicDynaClass</code>构造方法接收的第二个参数是<code class="language-plaintext highlighter-rouge">null</code>,它内部会作为<code class="language-plaintext highlighter-rouge">BasicDynaBean.class</code>来处理。</p>
</li>
<li>
<p>获取<code class="language-plaintext highlighter-rouge">DynaBean</code>实例并操作</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaBean employee = dynaClass.newInstance();
employee.set("address", new HashMap());
employee.set("subordinate", new Employee[0]);
employee.set("firstName", "Fred");
employee.set("lastName", "Flintstone");
</code></pre></div> </div>
<p>这里,<code class="language-plaintext highlighter-rouge">dynaClass.newInstance()</code>的返回值的类型为<code class="language-plaintext highlighter-rouge">DynaBean</code>而不是上文中设置的<code class="language-plaintext highlighter-rouge">BasicDynaBean</code>,这是因为,一般情况下,程序是不关心具体实现,而只需要根据<code class="language-plaintext highlighter-rouge">BynaBean</code>的接口进行访问就行了。</p>
</li>
</ol>
<h4 id="322-resultsetdynaclass使用dynabeans去包装resultset">3.2.2 <code class="language-plaintext highlighter-rouge">ResultSetDynaClass</code>(使用<code class="language-plaintext highlighter-rouge">DynaBeans</code>去包装<code class="language-plaintext highlighter-rouge">ResultSet</code>)</h4>
<p>将一系列有关系的,但自身又不是JavaBean的数据集合,包装起来,是<code class="language-plaintext highlighter-rouge">DynaBean</code>API的一个最普遍的使用方式。这其中一个经典应用就是,使用<code class="language-plaintext highlighter-rouge">DynaBean</code>去包装,用<code class="language-plaintext highlighter-rouge">JDBC</code>执行一个<code class="language-plaintext highlighter-rouge">SELECT</code>语句返还的结果<code class="language-plaintext highlighter-rouge">java.sql.ResultSet</code>对象。</p>
<p>看下面的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Connection conn = ...;
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery
("select account_id, name from customers");
Iterator rows = (new ResultSetDynaClass(rs)).iterator();
while (rows.hasNext()) {
DynaBean row = (DynaBean) rows.next();
System.out.println("Account number is " +
row.get("account_id") +
" and name is " + row.get("name"));
}
rs.close();
stmt.close();
</code></pre></div></div>
<h4 id="323-rowsetdynaclass将离线的resultset包装成dynabeans">3.2.3 <code class="language-plaintext highlighter-rouge">RowSetDynaClass</code>(将离线的<code class="language-plaintext highlighter-rouge">ResultSet</code>包装成<code class="language-plaintext highlighter-rouge">DynaBeans</code>)</h4>
<p>尽管使用<code class="language-plaintext highlighter-rouge">ResultSetDynaClass</code>很方便,但是它要求查询的结果集在数据整个处理过程中一直保持<code class="language-plaintext highlighter-rouge">open</code>状态。然而,有时我们需要先将结果集关闭后,再去处理查询结果的内容。不过这种方式的缺点是,我们需要足够的内存存储查询结果,以及存储过程中所需的性能损耗。如果,你能接受这点,那就可以使用<code class="language-plaintext highlighter-rouge">RowSetDynaClass</code>.</p>
<p>看下面的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Connection conn = ...; // 从连接池获取连接
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT ...");
RowSetDynaClass rsdc = new RowSetDynaClass(rs);
rs.close();
stmt.close();
...; // 将连接放回连接池
List rows = rsdc.getRows();
...; // 处理记录
</code></pre></div></div>
<p>使用<code class="language-plaintext highlighter-rouge">RowSetDynaClass</code>有个额外的好处,由于<code class="language-plaintext highlighter-rouge">RowSetDynaClass</code>实现了<code class="language-plaintext highlighter-rouge">java.io.Serializable</code>,所以可以很容易的序列化和反序列化。这样,使用<code class="language-plaintext highlighter-rouge">RowSetDynaClass</code>就可以很容易地将SQL语句查询结果传输给远程的java端应用。</p>
<h4 id="324-wrapdynabean和wrapdynaclass">3.2.4 <code class="language-plaintext highlighter-rouge">WrapDynaBean</code>和<code class="language-plaintext highlighter-rouge">WrapDynaClass</code></h4>
<p>如果,你习惯使用<code class="language-plaintext highlighter-rouge">DynaBeans</code>去通过<code class="language-plaintext highlighter-rouge">set</code>和<code class="language-plaintext highlighter-rouge">get</code>方法去对<code class="language-plaintext highlighter-rouge">DynaBeans</code>进行存取属性。而标准的JavaBean肯定不具备这样的方法。没关系,你可以使用<code class="language-plaintext highlighter-rouge">WrapDynaBean</code>让现有的标准JavaBean也可以变得这样更易访问。</p>
<p>看例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>MyBean bean = ...;
DynaBean wrapper = new WrapDynaBean(bean);
String firstName = wrapper.get("firstName");
</code></pre></div></div>
<h4 id="325-lazy-dynabeans">3.2.5 <code class="language-plaintext highlighter-rouge">Lazy DynaBeans</code></h4>
<p>包含有下面几个类:</p>
<ul>
<li>LazyDynaBean 一个“懒”动态Bean</li>
<li>LazyDynaMap 一个轻量级的可以转换为Map的DynaBean。</li>
<li>LazyDynaList 一个可以存放<code class="language-plaintext highlighter-rouge">DynaBean</code>、<code class="language-plaintext highlighter-rouge">java.util.Map</code>或者<code class="language-plaintext highlighter-rouge">POJO</code>的”懒”列表</li>
<li>LazyDynaClass <code class="language-plaintext highlighter-rouge">MutableDynaClass</code>接口的一个实现</li>
</ul>
<p><code class="language-plaintext highlighter-rouge">Lazy DynaBeans</code>具备下面的特性:</p>
<ul>
<li>可以通过<code class="language-plaintext highlighter-rouge">set(name, value)</code>方法自动添加属性</li>
<li><code class="language-plaintext highlighter-rouge">List</code>或<code class="language-plaintext highlighter-rouge">Array</code>的序列不够时,可以自动增长</li>
<li>自动实例化,在调用<code class="language-plaintext highlighter-rouge">setter/getter</code>方法中,会根据上下文进行实例化,创建对应的<code class="language-plaintext highlighter-rouge">Bean</code>、<code class="language-plaintext highlighter-rouge">List</code>或者<code class="language-plaintext highlighter-rouge">Map</code>等实例。</li>
</ul>
<p>简单举例如下:</p>
<ol>
<li>
<p><code class="language-plaintext highlighter-rouge">LazyDynaBean</code></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaBean dynaBean = new LazyDynaBean();
dynaBean.set("foo", "bar"); // simple
dynaBean.set("customer", "title", "Mr"); // mapped
dynaBean.set("customer", "surname", "Smith"); // mapped
dynaBean.set("address", 0, addressLine1); // indexed
dynaBean.set("address", 1, addressLine2); // indexed
dynaBean.set("address", 2, addressLine3); // indexed
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">LazyDynaMap</code></p>
<p>如果你想将动态Bean转换为Map,可以这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaBean dynaBean = new LazyDynaMap(); // create DynaBean
dynaBean.set("foo", "bar"); // simple
dynaBean.set("customer", "title", "Mr"); // mapped
dynaBean.set("address", 0, addressLine1); // indexed
Map myMap = dynaBean.getMap() // retrieve the Map
</code></pre></div> </div>
<p>如果你想将Map转换为动态Bean,可以这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Map myMap = .... // exisitng Map
DynaBean dynaBean = new LazyDynaMap(myMap); // wrap Map in DynaBean
dynaBean.set("foo", "bar"); // set properties
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">LazyDynaList</code></p>
<ul>
<li>你可以将任意一个<code class="language-plaintext highlighter-rouge">java.util.Map[]</code>数值放到<code class="language-plaintext highlighter-rouge">LazyDynaList</code>里面去:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> TreeMap[] myArray = .... // your Map[]
List lazyList = new LazyDynaList(myArray);
</code></pre></div> </div>
<ul>
<li><code class="language-plaintext highlighter-rouge">get(index)</code>方法将自动增长list的序列:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaBean newElement = (DynaBean)lazyList.get(lazyList.size());
newElement.put("someProperty", "someValue");
</code></pre></div> </div>
<ul>
<li>操作结束后,可以再转换为map:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> TreeMap[] myArray = (TreeMap[])lazyList.toArray());
</code></pre></div> </div>
<ul>
<li>你也可以创建空的列表,指定其中元素的类,<code class="language-plaintext highlighter-rouge">LazyDynaList</code>可以根据指定的类自动填充元素:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> List lazyList = new LazyDynaList(TreeMap.class);
List lazyList = new LazyDynaList(MyPojo.class);
List lazyList = new LazyDynaList(MyDynaBean.class);
DynaClass dynaClass = new LazyDynaMap(new HashMap());
List lazyList = new LazyDynaList(dynaClass);
DynaClass dynaClass = (new WrapDynaBean(myPojo)).getDynaClass();
List lazyList = new LazyDynaList(dynaClass);
DynaClass dynaClass = new BasicDynaClass(properties);
List lazyList = new LazyDynaList(dynaClass);
</code></pre></div> </div>
<p>上面之所以使用<code class="language-plaintext highlighter-rouge">DynaClass</code>来替代普通的<code class="language-plaintext highlighter-rouge">Class</code>,是因为有些DynaBean的实现没有默认的空参数的构造方法,而<code class="language-plaintext highlighter-rouge">DynaClass</code>提供了<code class="language-plaintext highlighter-rouge">DynaClass.newInstance()</code>方法。</p>
<ul>
<li>当然,也可以使用<code class="language-plaintext highlighter-rouge">setElementType(Class)</code>或者<code class="language-plaintext highlighter-rouge">setElementDynaClass(DynaClass)</code>方法来设置元素类型,然后使用普通的<code class="language-plaintext highlighter-rouge">java.util.List</code>接口提供的方法(比如:<code class="language-plaintext highlighter-rouge">set/add/addAll</code>)来填充元素:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // Create a new LazyDynaList (100 element capacity)
LazyDynaList lazyList = new LazyDynaList(100);
// Either Set the element type...
lazyList.setElementType(TreeMap.class);
// ...or the element DynaClass...
lazyList.setElementDynaClass(new MyCustomDynaClass());
// Populate from a collection
lazyList.addAll(myCollection);
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">LazyDynaClass</code></p>
<p><code class="language-plaintext highlighter-rouge">LazyDynaClass</code>继承于<code class="language-plaintext highlighter-rouge">BasicDynaClass</code>,实现了<code class="language-plaintext highlighter-rouge">MutableDynaClass</code>接口。我们在使用<code class="language-plaintext highlighter-rouge">LazyDynaBean</code>时,有时不需要关系其内部结构,这样就不需要操作<code class="language-plaintext highlighter-rouge">DynaClass</code>,但有时,我们需要强制其内部成员结构,这样就需要设置<code class="language-plaintext highlighter-rouge">DynaClass</code>。做这种强制内部结构的操作,有两种方式:</p>
<ul>
<li>我们可以先创建<code class="language-plaintext highlighter-rouge">LazyDynaClass</code>,设置结构,然后再生成<code class="language-plaintext highlighter-rouge">LazyDynaBean</code>:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> MutableDynaClass dynaClass = new LazyDynaClass(); // create DynaClass
dynaClass.add("amount", java.lang.Integer.class); // add property
dynaClass.add("orders", OrderBean[].class); // add indexed property
dynaClass.add("orders", java.util.TreeMapp.class); // add mapped property
DynaBean dynaBean = new LazyDynaBean(dynaClass); // Create DynaBean with associated DynaClass
</code></pre></div> </div>
<ul>
<li>我们也可以先生成<code class="language-plaintext highlighter-rouge">LazyDynaBean</code>,再获取<code class="language-plaintext highlighter-rouge">DynaClass</code>设置结构:</li>
</ul>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> DynaBean dynaBean = new LazyDynaBean(); // Create LazyDynaBean
MutableDynaClass dynaClass =
(MutableDynaClass)dynaBean.getDynaClass(); // get DynaClass
dynaClass.add("amount", java.lang.Integer.class); // add property
dynaClass.add("myBeans", myPackage.MyBean[].class); // add 'array' indexed property
dynaClass.add("myMap", java.util.TreeMapp.class); // add mapped property
</code></pre></div> </div>
</li>
</ol>
<h4 id="33-数据类型转换">3.3 数据类型转换</h4>
<p>BeanUtils包提供了一系列API和设计模式来解决数据类型转换问题。</p>
<p>看下面的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>HttpServletRequest request = ...;
MyBean bean = ...;
BeanUtils.populate(bean, request.getParameterMap());
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">BeanUtils</code>是依赖<code class="language-plaintext highlighter-rouge">ConvertUtils</code>提供的方法进行数据转换的,<code class="language-plaintext highlighter-rouge">ConvertUtils</code>不推荐直接使用,因为以后的版本中可能会被废弃。</p>
<p>你也可以定制自己的转换器,方法很简单,两个步骤:</p>
<ul>
<li>实现<code class="language-plaintext highlighter-rouge">Converter</code>接口,在其中的<code class="language-plaintext highlighter-rouge">convert</code>方法中实现你的转换规则</li>
<li>使用<code class="language-plaintext highlighter-rouge">ConvertUtils.register()</code>方法注册你自己的转换器</li>
</ul>
<p>举一个简单的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class Person{
private Date birthday;
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
...
}
//
DateConverter dc = new DateConverter();
dc.setPattern("yyyy-mm-dd");
ConvertUtils.register(dc, java.util.Date.class);
//
Person person = new Person();
String bd = "2014-01-15";
BeanUtils.setProperty(person, "birthday", bd);
</code></pre></div></div>
<p>在上面的例子中,如果不定制转换器,那么生日的属性设置会报错。</p>
<h4 id="34-集合">3.4 集合</h4>
<p>与<code class="language-plaintext highlighter-rouge">Apache Commons Collections</code>包配合,去实现一些对集合的操作。比如:</p>
<ul>
<li>
<p>根据集合中bean的某个属性值,对集合中得bean进行排序</p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Collections.sort(peopleCollection, new BeanComparator("firstName"));
</code></pre></div> </div>
<p>上面代码,会根据bean中<code class="language-plaintext highlighter-rouge">firstName</code>属性对集合<code class="language-plaintext highlighter-rouge">peopleCollection</code>进行排序。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Collections.sort(peopleCollection, new BeanComparator("firstName", new ReverseComparator(new ComparableComparator())));
</code></pre></div> </div>
<p>上面代码是做逆序。</p>
</li>
<li>
<p>修改集合中Bean的某属性值</p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // create the closure
BeanPropertyValueChangeClosure closure =
new BeanPropertyValueChangeClosure( "activeEmployee", Boolean.TRUE );
// update the Collection
CollectionUtils.forAllDo( peopleCollection, closure );
</code></pre></div> </div>
<p>上面代码,会将集合<code class="language-plaintext highlighter-rouge">peopleCollection</code>内所有bean的属性<code class="language-plaintext highlighter-rouge">activeEmployee</code>都设置为<code class="language-plaintext highlighter-rouge">Boolean.TRUE</code>.</p>
</li>
<li>
<p>通过Bean中某属性值是否相等来对集合进行过滤</p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // create the predicate
BeanPropertyValueEqualsPredicate predicate =
new BeanPropertyValueEqualsPredicate( "activeEmployee", Boolean.FALSE );
// filter the Collection
CollectionUtils.filter( peopleCollection, predicate );
</code></pre></div> </div>
<p>上面代码,会将集合<code class="language-plaintext highlighter-rouge">peopleCollection</code>中属性<code class="language-plaintext highlighter-rouge">activeEmployee</code>不为false的bean都删除。</p>
</li>
<li>
<p>通过Bean的属性路径来获取数据</p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> // create the transformer
BeanToPropertyValueTransformer transformer = new BeanToPropertyValueTransformer( "person.address.city" );
// transform the Collection
Collection peoplesCities = CollectionUtils.collect( peopleCollection, transformer );
</code></pre></div> </div>
<p>上面代码,会将集合<code class="language-plaintext highlighter-rouge">peopleCollection</code>中所有bean的<code class="language-plaintext highlighter-rouge">person.address.city</code>属性值都收集到集合<code class="language-plaintext highlighter-rouge">peoplesCities</code>中去。</p>
</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p>Java程序可以在运行是加载Class,获取其构造方法的定义,并生成其对象实体、或对其fields设值、或唤起其methods。这种“透视Class”的能力(<code class="language-plaintext highlighter-rouge">the ability of the program to examine itself</code>),被称为<code class="language-plaintext highlighter-rouge">Introspection</code> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>
Nginx配置及应用场景之lua
2013-10-23T00:00:00+00:00
http://www.blogways.net/blog/2013/10/23/nginx-4
<h2 id="一说明">一、说明</h2>
<p>这里不对lua语言本身及其编译器运行环境等做介绍,以下所有介绍前提对lua相关有所了解。</p>
<h2 id="二ngx_lua介绍">二、ngx_lua介绍</h2>
<h3 id="原理">原理</h3>
<p>ngx_lua将Lua嵌入Nginx,可以让Nginx执行Lua脚本,并且高并发、非阻塞的处理各种请求。Lua内建协程,这样就可以很好的将异步回调转换成顺序调用的形式。ngx_lua在Lua中进行的IO操作都会委托给Nginx的事件模型,从而实现非阻塞调用。开发者可以采用串行的方式编写程序,ngx_lua会自动的在进行阻塞的IO操作时中断,保存上下文;然后将IO操作委托给Nginx事件处理机制,在IO操作完成后,ngx_lua会恢复上下文,程序继续执行,这些操作都是对用户程序透明的。</p>
<p>每个NginxWorker进程持有一个Lua解释器或者LuaJIT实例,被这个Worker处理的所有请求共享这个实例。每个请求的Context会被Lua轻量级的协程分割,从而保证各个请求是独立的。</p>
<p>ngx_lua采用“one-coroutine-per-request”的处理模型,对于每个用户请求,ngx_lua会唤醒一个协程用于执行用户代码处理请求,当请求处理完成这个协程会被销毁。每个协程都有一个独立的全局环境(变量空间),继承于全局共享的、只读的“comman data”。所以,被用户代码注入全局空间的任何变量都不会影响其他请求的处理,并且这些变量在请求处理完成后会被释放,这样就保证所有的用户代码都运行在一个“sandbox”(沙箱),这个沙箱与请求具有相同的生命周期。</p>
<p>得益于Lua协程的支持,ngx_lua在处理10000个并发请求时只需要很少的内存。根据测试,ngx_lua处理每个请求只需要2KB的内存,如果使用LuaJIT则会更少。所以ngx_lua非常适合用于实现可扩展的、高并发的服务。</p>
<p><strong>协程</strong></p>
<p>协程类似一种多线程,与多线程的区别有:</p>
<ol>
<li>协程并非os线程,所以创建、切换开销比线程相对要小。</li>
<li>协程与线程一样有自己的栈、局部变量等,但是协程的栈是在用户进程空间模拟的,所以创建、切换开销很小。</li>
<li>多线程程序是多个线程并发执行,也就是说在一瞬间有多个控制流在执行。而协程强调的是一种多个协程间协作的关系,只有当一个协程主动放弃执行权,另一个协程才能获得执行权,所以在某一瞬间,多个协程间只有一个在运行。</li>
<li>由于多个协程时只有一个在运行,所以对于临界区的访问不需要加锁,而多线程的情况则必须加锁。</li>
<li>多线程程序由于有多个控制流,所以程序的行为不可控,而多个协程的执行是由开发者定义的所以是可控的。</li>
</ol>
<p>Nginx的每个Worker进程都是在epoll或kqueue这样的事件模型之上,封装成协程,每个请求都有一个协程进行处理。这正好与Lua内建协程的模型是一致的,所以即使ngx_lua需要执行Lua,相对C有一定的开销,但依然能保证高并发能力。</p>
<h2 id="二ngx_lua安装">二、ngx_lua安装</h2>
<p>Nginx中安装ngx_lua需要安装LuaJIT,ngx_devel_kit,ngx_lua等安装文件,我们这里用的OpenResty,内部已经集成ngx_lua,无需再安装任何模块。</p>
<h2 id="三ngx_lua用法">三、ngx_lua用法</h2>
<p><strong>嵌套lua脚本</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location /lua {
set $test "hello, world";
content_by_lua '
ngx.header.content_type = "text/plain";
ngx.say(ngx.var.test);
';
}
</code></pre></div></div>
<p>$ curl ‘http://134.32.28.134:8888/lua’,输出 hello, world。</p>
<p><strong>include lua文件</strong></p>
<p>Nginx中include lua的脚本文件方式,如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> location /mytest {
content_by_lua_file conf/alcache.lua;
}
</code></pre></div></div>
<p>其中在alcache.lua中编写lua脚本即可。</p>
<h2 id="四实际运用中通过lua结合分布式缓存对session的处理">四、实际运用中通过lua结合分布式缓存对session的处理</h2>
<p>这里redis与memcache的支持不是调用Nginx自带redis与memcache模块,都是调用OpenResty内部集成的第三方模块</p>
<p><strong>nginx.conf部分配置</strong></p>
location /login { <br />
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> content_by_lua_file conf/alcache.lua;
}
</code></pre></div></div>
<p><strong>alcache.lua配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>local key = tostring(ngx.var.arg_username)
local val = tostring(ngx.var.arg_password)
local passLogin = tostring(ngx.var.arg_passLoginFlag)
local flags = tostring(ngx.var.arg_flags or 0)
local exptime = tostring(ngx.var.arg_exptime or 0)
local sessionId = tostring(ngx.var.cookie_JSESSIONID)
ngx.say("sessionId:",sessionId)
ngx.say("key:",key)
ngx.say("val:",val)
if (key == nil and val == nil) then return end
--if (passLogin == nil or sessionId == nil) then return end
local memcached = require("resty.memcached")
--local redis = require("resty.redis")
local cache,err = memcached:new()
--local cache,err = redis.new()
if not cache then
ngx.say("failed to instantiate cache: ",err)
return
end
cache:set_timeout(1000)
local ok,err = cache:connect("134.32.28.134",11211)
--local ok,err = cache:connect("134.32.28.134",6379)
if not ok then
ngx.say("failed to connect: ",err)
return
end
local res,flags,err = cache:get(key)
if err then
ngx.say("failed to get ",key," : ",err)
return
end
if res and tostring(res) ~= sessionId then
cache:delete(key)
cache:set(key,sessionId,exptime,flags)
else
cache:set(key,sessionId,exptime,flags)
end
local ok, err = cache:close()
if not ok then
ngx.say("failed to close:", err)
return
end
local url = ngx.var.uri
local res = ngx.location.capture("/proxy")
</code></pre></div></div>
Nginx配置及应用场景之高级配置
2013-10-22T00:00:00+00:00
http://www.blogways.net/blog/2013/10/22/nginx-3
<h2 id="一nginx反向代理">一、Nginx反向代理</h2>
<p>反向代理(Reverse Proxy)方式是指以代理服务器来接受Internet上的连接请求,然后将请求转发给内部网络上的服务器;并将从服务器上得到的结果返回给Internet上请求连接的客户端,此时代理服务器对外就表现为一个服务器。</p>
<p>通常的代理服务器,只用于代理内部网络对Internet的连接请求,客户机必须指定代理服务器,并将本来要直接发送到Web服务器上的http请求发送到代理服务器中。当一个代理服务器能够代理外部网络上的主机,访问内部网络时,这种代理服务的方式称为反向代理服务。</p>
<p>一个通过HttpProxy模块实现反向代理的简单配置:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> server {
listen 8888;
server_name 134.32.28.134;
location / {
proxy_pass http://134.32.28.134:8090;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
} 此配置实现在ie中输入http://134.32.28.134:8888即会调转到134.32.28.134:8090中
</code></pre></div></div>
<p>Upstream模块配置实现:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
}
server {
listen 8888;
server_name 134.32.28.134;
location / {
proxy_pass http://appserver;
}
}
</code></pre></div></div>
<p>HttpRewrite模块配置实现</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> server {
listen 8888;
server_name 134.32.28.134;
rewrite http://134.32.28.134:8090/ permanent;
}
</code></pre></div></div>
<h2 id="二nginx负载均衡">二、Nginx负载均衡</h2>
<p>Nginx本身提供轮询(round robin)、用户IP哈希(ip_hash)和指定权重三种方式负载均衡策略,
另外也出现第三方负载均衡模块fair和url_hash,默认情况下,Nginx以轮询作为负载均衡策略。</p>
<p><strong>轮询与指定权重策略</strong></p>
<p>简单配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090 weight=5;
server 134.32.28.134:8091 weight=2;
}
</code></pre></div></div>
<p>weight是设置权重,用于后端服务器性能不均的情况。
轮询策略的缺点是某一时段内的一连串访问可能都是由同一个用户A发起的,那么第一次A的请求可能是 8090,而下一次是 8091,然后是 8090、8091…… 在大多数应用场景中,这样并不高效,并且如果后台服务器没有实现对session的共享,会导致session没有共享的。当然,也正因如此,Nginx 为你提供了一个按照IP来hash的方式,这样每个客户端的访问请求都会被甩给同一个后端服务器。</p>
<p><strong>IP哈希策略</strong></p>
<p>配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
ip_hash;
server 134.32.28.134:8090;
server 134.32.28.134:8091;
}
</code></pre></div></div>
<p>这种策略中,用于进行hash运算的key是客户端的IP地址。这样的方式保证一个客户端每次请求都将到达同一个后台主机。当然,如果所hash到的后台服务器当前不可用,则请求会被转移到其他服务器。</p>
<p><strong>down机、重试策略及备份</strong></p>
<p>当某个一个服务器暂时性的宕机(down)时,你可以使用“down”来标示出来,并且这样被标示的 、服务器就不会接受请求去处理。如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
server 134.32.28.134:8091 down;
}
</code></pre></div></div>
<p>可以为每个 backend 指定最大的重试次数,和重试时间间隔。所使用的关键字是 max_fails 和 fail_timeout。如下所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
server 134.32.28.134:8091 max_fails=3 fail_timeout=30s;
}
</code></pre></div></div>
<p>可以使用“backup”关键字。当所有的非备机(non-backup)都宕机(down)或者繁忙(busy)的时候,就只使用由 backup 标注的备机。backup不能和ip_hash关键字一起使用。举例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
server 134.32.28.134:8091;
server 134.32.28.134:8092 backup;
}
</code></pre></div></div>
<p><strong>fair(Nginx需安装第三方模块,OpenResty已经集成)</strong></p>
<p>fair按后端服务器的响应时间来分配请求,响应时间短的优先分配,配置如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
server 134.32.28.134:8091;
fair;
}
</code></pre></div></div>
<p><strong>url_hash(Nginx需安装第三方模块)</strong></p>
<p>url_hash按访问URL的hash结果来分配请求,使每个URL定向到同一个后端服务器,后端服务器为缓存时比较适用。另外,在upstream中加入hash语句后,server语句不能写入weight等其他参数。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> upstream appserver {
server 134.32.28.134:8090;
server 134.32.28.134:8091;
hash $request_uri;
hash_method crc32;
}
</code></pre></div></div>
<h2 id="三nginx静态缓存">三、Nginx静态缓存</h2>
<p>Nginx过Proxy Cache可以使其对静态资源进行缓存。其原理就是把静态资源按照一定的规则存在本地硬盘,并且会在内存中缓存常用的资源,从而加快静态资源的响应。</p>
<p><strong>http段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>proxy_connect_timeout 600; #nginx跟后端服务器连接超时时间(代理连接超时)
proxy_read_timeout 600; #连接成功后,后端服务器响应时间(代理接收超时)
proxy_send_timeout 600; #后端服务器数据回传时间(代理发送超时)
proxy_buffer_size 32k; #设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 4 32k;#proxy_buffers缓冲区,网页平均在32k以下的话,这样设置
proxy_busy_buffers_size 64k; #高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 64k; #设定缓存文件夹大小,大于这个值,将从upstream服务器传
proxy_temp_path /home/spdev/nginx/openresty/local/nginx/proxy_temp;
proxy_cache_path /home/spdev/nginx/openresty/local/nginx/cache levels=1:2
keys_zone=cache_one:200m inactive=1d max_size=30g;
#levels设置目录层次,keys_zone设置缓存名字和共享内存大小
#inactive在指定时间内没人访问则被删除在这里是1天,max_size最大缓存空间
</code></pre></div></div>
<p><strong>server段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> location ~* \.(gif|jpg|png|htm|html|css|js|flv|ico|swf)$ {
proxy_pass http://appserver; #动态不缓存
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 1h; #哪些状态缓存多长时间
proxy_cache_valid 301 1d;
proxy_cache_valid any 1m; #其他的缓存多长时间
expires 30d; #置失期时间,为30天
}
</code></pre></div></div>
<h2 id="四ip并发限制带宽限制">四、IP并发限制、带宽限制</h2>
<p>nginx可以通过HttpLimitReqModul和HttpLimitZoneModule配置来限制ip在同一时间段的访问次数来防cc攻击。
HttpLimitReqModul用来限制连单位时间内连接数的模块,使用limit_req_zone和limit_req指令配合使用来达到限制。
一旦并发连接超过指定数量,就会返回503错误。HttpLimitConnModul用来限制单个ip的并发连接数,使用limit_zone和limit_conn指令这两个模块的区别前一个是对一段时间内的连接数限制,后者是对同一时刻的连接数限制。</p>
<h3 id="设置httplimitreqmodul限制某一段时间内同一ip访问数">设置HttpLimitReqModul限制某一段时间内同一ip访问数</h3>
<p><strong>http段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #限制同一ip每秒访问20次
limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
</code></pre></div></div>
<p>这里定义一个名为allips的limit_req_zone用来存储session,大小是10M内存,以$binary_remote_addr 为key,限制平均每秒的请求为20个,1M能存储16000个状态,rate的值必须为整数,如果限制两秒钟一个请求,可以设置成30r/m。</p>
<p><strong>server段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> limit_req zone=allips burst=5 nodelay;
#brust的意思就是,如果第1秒、2,3,4秒请求为19个,第5秒的请求为25个是被允许的。
#但是如果你第1秒就25个请求后面的5个请求就会被限制,返回503错误。
#nodelay,如果不设置该选项,严格使用平均速率限制请求数,
#也就是说如果你设置rate=120r/m,相当于每秒只允许处理2个请求
</code></pre></div></div>
<h3 id="设置httplimitzonemodule-限制并发连接数">设置HttpLimitZoneModule 限制并发连接数</h3>
<p><strong>http段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #ip限制并发数
limit_conn_zone $binary_remote_addr zone=addr:10m;
#服务器限制并发总数
limit_conn_zone $server_name zone=perserver:10m;
</code></pre></div></div>
<p><strong>server段设置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #连接数限制
limit_conn addr 10;
limit_conn perserver 50000;
#连接限速
limit_rate 500k;
#限制ip
allow 132.35.75.0/24;
#deny all;
</code></pre></div></div>
<p>其中allow、deny是HTTP Access模块对应的指令,以下需注意:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1、deny 一定要加一个ip,否则直接跳转到403,不往下执行了;如果403默认页是
同一域名下,会造成死循环访问;
2、allow的ip段从允许访问的段位从小到大排列,如127.0.0.0/24 下面才能是
10.10.0.0/16
24表示子网掩码:255.255.255.0
16表示子网掩码:255.255.0.0
8表示子网掩码:255.0.0.0;
3、deny all;结尾 表示除了上面allow的其他都禁止
如:
deny 192.168.1.1;
allow 127.0.0.0/24;
allo w 192.168.0.0/16;
allow 10.10.0.0/16;
deny all;
</code></pre></div></div>
<h2 id="五第三方合并静态文件模块的使用">五、第三方合并静态文件模块的使用</h2>
<p>这里介绍的是淘宝开发的nginx_concat_module针对nginx的文件合并模块,主要用于合并前端代码减少http请求数。对于此模块的安装前面已经有过说明,这里不再详细解说。</p>
<p><strong>nginx_concat_module模块配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # nginx_concat_module 主开关
concat on;
# 最大合并文件数
concat_max_files 10;
# 只允许同类型文件合并
concat_unique on;
# 允许合并的文件类型,多个以逗号分隔。如:application/x-javascript, text/css
concat_types text/html;
</code></pre></div></div>
<p>举例如:</p>
<p>http://主机地址/test/??1.css,2.css,3.css…10.css,这里会将1.css,2.css…10.css 是个css文件合并为一个文件从而只需一次请求。</p>
<h2 id="六实际应用中配置实例">六、实际应用中配置实例</h2>
<p><strong>nginx.conf部分配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> include common.conf;
include proxy_cache.conf;
#设定负载均衡的服务器列表
upstream appserver {
server 134.32.28.134:8090 weight=5;
server 134.32.28.134:8091 weight=2;
}
include limit_common.conf;
# server虚拟主机配置
server {
listen 8888;
server_name 134.32.28.134;
#设置网页的默认编码格式
#charset utf8;
#根据访问域名生成对应的访问日志
access_log logs/host.access.log main;
lua_code_cache on;
location / {
proxy_pass http://appserver;
include user_agent.conf;
include limit_info.conf;
}
location /mytest {
content_by_lua_file conf/alcache.lua;
}
location /proxy {
include user_agent.conf;
include limit_info.conf;
proxy_pass http://appserver$uri;
}
#静态文件缓存
location ~* \.(gif|jpg|png|htm|html|css|js|flv|ico|swf)$ {
#防盗链
#valid_referers none blocked 134.32.28.134;
#if ($invalid_referer) {
#return 403;
#}
proxy_pass http://appserver;
proxy_redirect off;
proxy_set_header Host $host;
proxy_cache cache_one;
proxy_cache_valid 200 302 1h;
proxy_cache_valid 301 1d;
proxy_cache_valid any 1m;
expires 30d;
}
</code></pre></div></div>
<p>此处nginx.conf中include了limit_common.conf、user_agent.conf、limit_info.conf与proxy_cache.conf,其中user_agent.conf中配置了客户端信息判断及文件合并等信息,limit_common.conf配置了ip限制相关公用信息,limit_info.conf配置了实际server中限制要求,proxy_cache.conf中配置了静态缓存相关信息,其中静态文件缓存部分为server运用中的配置。</p>
<p><strong>user_agent.conf配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#客户端判断
#if ($http_user_agent ~* "MSIE") { proxy_pass http://appserver; }
if ($http_user_agent ~* "Nokia") { rewrite . /404.html break; }
if ($http_user_agent ~* "Mobile") { rewrite . /404.html break; }
if ($http_user_agent ~* "SAMSUNG") { rewrite . /404.html break; }
if ($http_user_agent ~* "SonyEricsson") { rewrite . /404.html break; }
if ($http_user_agent ~* "MOT") { rewrite . /404.html break; }
if ($http_user_agent ~* "BlackBerry") { rewrite . /404.html break; }
if ($http_user_agent ~* "LG") { rewrite . /404.html break; }
if ($http_user_agent ~* "HTC") { rewrite . /404.html break; }
if ($http_user_agent ~* "J2ME") { rewrite . /404.html break; }
if ($http_user_agent ~* "Opera Mini") { rewrite . /404.html break; }
if ($http_user_agent ~* "ipad") { proxy_pass http://appserver; }
if ($http_user_agent ~* "iphone") { proxy_pass http://appserver; }
if ($http_user_agent ~* "android") { proxy_pass http://appserver; }
#只允许访问get、head、post方法
if ($request_method !~* ^(GET|HEAD|POST)$ ) {
return 403;
}
#js、css文件合并
concat on;
#concat_max_files 10;
#只允许同类型文件合并
#concat_unique on;
#允许合并的文件类型,多个以逗号分隔。如:application/x-javascript, text/css
#concat_types text/html
</code></pre></div></div>
<p><strong>limit_common.conf配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#限制同一ip每秒访问20次
limit_req_zone $binary_remote_addr zone=allips:10m rate=20r/s;
#ip限制并发数
limit_conn_zone $binary_remote_addr zone=addr:10m;
#服务器限制并发总数
limit_conn_zone $server_name zone=perserver:10m;
</code></pre></div></div>
<p><strong>limit_info.conf配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#brust的意思就是,如果第1秒、2,3,4秒请求为19个,第5秒的请求为25个是被允许的。
#但是如果你第1秒就25个请求后面的5个请求就会被限制,返回503错误。
#nodelay,如果不设置该选项,严格使用平均速率限制请求数,
#也就是说如果你设置rate=120r/m,相当于每秒只允许处理2个请求
limit_req zone=allips burst=5 nodelay;
#连接数限制
limit_conn addr 10;
limit_conn perserver 50000;
#连接限速
limit_rate 500k;
#限制ip
allow 132.35.75.0/24;
#deny all;
</code></pre></div></div>
<p><strong>proxy_cache.conf配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>proxy_connect_timeout 5;
proxy_read_timeout 60;
proxy_send_timeout 5;
proxy_buffer_size 16k;
proxy_buffers 4 64k;
proxy_busy_buffers_size 128k;
proxy_temp_file_write_size 128k;
proxy_temp_path /home/spdev/nginx/openresty/local/nginx/proxy_temp;
proxy_cache_path /home/spdev/nginx/openresty/local/nginx/cache levels=1:2
keys_zone=cache_one:200m inactive=1d max_size=30g;
</code></pre></div></div>
Nginx配置及应用场景之基本配置
2013-10-21T00:00:00+00:00
http://www.blogways.net/blog/2013/10/21/nginx-2
<h3 id="一nginxconf配置说明">一、nginx.conf配置说明</h3>
<p><strong>主模块常用指令</strong></p>
<ol>
<li>
<p>daemon</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:设置是否以守护进程模式运行
语法:daemon on|off
缺省:on
示例:daemon off
注意:生产环境(production mode)中不要使用daemon指令,这些选项仅
用于开发测试(development mode)。
</code></pre></div> </div>
</li>
<li>
<p>debug_points</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:断点调试
语法:debug_points [stop|abort]
缺省:none
示例:debug_points stop;
注意:在Nginx内有一些assert断言,这些断言允许Nginx,配合调试器中断程序运行、
停止或创建core文件。
</code></pre></div> </div>
</li>
<li>
<p>master_process</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:设置是否启用主进程
语法:master_process on|off
缺省:on
示例:master_process off;
注意:
不要在生产环境(production mode)中使用master_process指令,
这些选项仅用于开发测试(development mode)。
</code></pre></div> </div>
</li>
<li>
<p>error_log</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:指定错误日志文件
语法:error_log file [debug|info|notice|warn|error|crit]
缺省:${prefix}/logs/error.log
示例:
error_log /data/nginx/logs/error.log debug
注意:
该命令并非只有在测试(或称为开发)模式下才可以使用,而是在编译时添加了--with-debug参数时,
则可以使用error_log指令的额外参数,即:
error_log file [debug_core|debug_alloc|debug_mutex|debug_event|
debug_http|debug_imap];
</code></pre></div> </div>
</li>
<li>
<p>include</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:指定所要包含的Nginx配置文件
语法:include <file|*>
缺省:none
示例:include vhosts/*.conf 或 include /home/michael/nginx/conf/nginx-main.conf
注意:
(1)include命令可以指定包含一个文件,比如第二个示例。也可以指定包含一个目录下的所有文件,
比如第一个示例。
(2)指定的文件路径的基路径,由编译选项--prefix决定,如果编译时没有指定,
则默认的路径是/usr/local/nginx。
</code></pre></div> </div>
</li>
<li>
<p>pid</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:指定存储进程ID(即PID)的文件。
语法:pid <file>
缺省:compile-time option Example
示例:pid /var/log/nginx.pid;
注意:可以使用命令kill -HUP cat /var/log/nginx.pid\ 对Nginx进行进程ID文件的重新加载。
</code></pre></div> </div>
</li>
<li>
<p>user</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:指定可以使用Nginx的用户
语法:user <user> [group]
缺省:nobody nobody(第一个nobody是user,第二个nobody是group)
示例:user spd spdev;
</code></pre></div> </div>
</li>
<li>
<p>worker_processes</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:指定worker进程数
语法:worker_processes <number>
缺省:1
示例:worker_processes 4;
注意:最大用户连接数=worker进程数×worker连接数,
即max_clients=worker_processes*worker_connections。
</code></pre></div> </div>
</li>
<li>
<p>worker_cpu_affinity</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 含义:为worker进程绑定CPU。
语法:worker_cpu_affinity cpumask [cpumask...]
缺省:none
示例:
(1)如果有4个CPU,并且指定4个worker进程,则:
worker_processes 4;
worker_cpu_affinity 0001 0010 0100 1000;
(2)如果有4个CPU,并且指定2个worker进程,则:
worker_processes 2;
worker_cpu_affinity 0101 1010;
</code></pre></div> </div>
<p>注意:只有Linux平台上才可以使用该指令。</p>
</li>
<li>
<p>worker_rlimit_nofile</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:worker进程的file descriptor可以打开的最大文件数,最好与与ulimit -n的值保持一致。
语法:worker_rlimit_nofile <number>;
</code></pre></div> </div>
</li>
</ol>
<p><strong>事件模块(events)常用指令</strong></p>
<ol>
<li>
<p>use</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>语法:use [kqueue | rtsig | epoll | /dev/poll | select | poll | eventport];
注意:如果在./configure的时候指定了不止一种事件模型,那么可以设置其中一个,告诉Nginx使用
哪种事件模型。默认情况下,Nginx会在./configure时找出最适合系统的事件模型。
</code></pre></div> </div>
</li>
<li>
<p>worker_connections</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>语法:worker_connection <number>;
最大连接数的计算公式如下:
max_clients = worker_processes * worker_connections;
</code></pre></div> </div>
</li>
<li>
<p>accept_mutex</p>
<p>含义:设置是否使用连接互斥锁进行顺序的accept()系统调用。
语法:accept_mutex <on|off>;
缺省:on
示例:accept_mutex off;</p>
</li>
<li>
<p>accept_mutex_delay</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置获得互斥锁的最少延迟时间。
语法:accpet_mutex_delay <number of millisecs>
缺省:500ms
示例:accpet_mutex_delay 1000ms;
</code></pre></div> </div>
</li>
<li>
<p>debug_connection</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置指定的clients产生debug日志。
语法:debug_connection [ip|CIDR];
缺省:none
示例:debug_connection 172.16.44.96;
一段较完整的事件模块代码如下:
error_log /data/nginx/log/error.log;
events {
debug_connection172.16.44.96;
}
</code></pre></div> </div>
</li>
</ol>
<p><strong>HTTP模块常用指令</strong></p>
<ol>
<li>
<p>alias</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:指定location使用的路径,与root类似,但不改变文件的跟路径,仅适用文件系统的路径。
语法:alias <file-path | directory-path>
缺省:N/A
作用域:http.server.location
示例:
location /i/ {
alias /home/michael/web/i/;
}
则请求 /i/logo.png 则返回 /home/michael/web/i/logo.png。
注意:
(1)替换路径时,可以使用变量。
(2)alias无法在正则的location中使用。如果有这种需求,则必须使用rewrite和root。
</code></pre></div> </div>
</li>
<li>
<p>client_body_in_file_only</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:指定是否将用户请求体存储到一个文件里。
语法:client_body_in_file_only <on | off>
缺省:off
作用域:http.server.location
示例:client_body_in_file_only on;
注意:
(1)该指令为on时,用户的请求体会被存储到一个文件中,但是请求结束后,该文件也不会被删除;
(2)该指令一般在调试的时候使用。
</code></pre></div> </div>
</li>
<li>
<p>client_body_buffer_size</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:指定用户请求体所使用的buffer的最大值
语法:client_body_buffer_size <size>
缺省:两个page的大小,一般为8k或16k
作用域:http.server.location
示例:client_body_buffer_size 512k;
注意:如果用户请求体超过了buffer的大小,则将全部内容或部分内容存储到一个临时文件中。
</code></pre></div> </div>
</li>
<li>
<p>client_body_temp_path</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置存储用户请求体的文件的目录路径
语法:client_body_temp_path <directory path> [level1 | level2 | level3]
作用域:http.server.location
示例:client_body_temp_path /spool/nginx/client_temp 1 2;
</code></pre></div> </div>
</li>
<li>
<p>client_body_timeout</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置用户请求体的超时时间。
语法:client_body_timeout <time>
作用域:http.server.location
示例:client_body_timeout 120s;
注意:只有请求体需要被1次以上读取时,该超时时间才会被设置。且如果这个时间后用户什么都没发,
nginx会返回requests time out 408.
</code></pre></div> </div>
</li>
<li>
<p>client_header_buffer_size</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置用户请求头所使用的buffer大小
语法:client_header_buffer_size <size>
缺省:1k
作用域:http.server
示例:client_header_buffer_size 2k;
注意:
(1)对绝大多数请求来说,1k足以满足请求头所需的buffer;
(2)对于携带有较大cookie或来自于wap用户的请求头来说,1k的buffer一般不够,这时可以使用指令
large_client_header_buffers。
</code></pre></div> </div>
</li>
<li>
<p>client_header_timeout</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置用户请求头的超时时间。
语法:client_header_timeout <time>
缺省:1m
作用域:http.server.location
示例:client_header_timeout 3m;
注意:只有请求头需要被1次以上读取时,该超时时间才会被设置。且如果这个时间后用户什么都没发,
nginx会返回requests time out 408.
</code></pre></div> </div>
</li>
<li>
<p>client_max_body_size</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:设置所能接收的最大请求体的大小
语法:client_max_body_size <size>
缺省:1m
作用域:http.server.location
示例:client_max_body_size 2m;
注意:根据请求头中的Content-Length来判断请求体大小是否允许。如果大于设定值,则返回
“ Request Entity Too Large”(413)错误。不过要注意的是,浏览器一般并不对这个错误进行特殊显示。
</code></pre></div> </div>
</li>
<li>
<p>send_timeout</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:指定响应客户端的超时时间,单位:秒,默认值为60
语法:send_timeout <time>
</code></pre></div> </div>
</li>
<li>
<p>keepalive_timeout</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:保持连接时间,单位:秒,超过该时间,服务器会关闭连接
</code></pre></div> </div>
</li>
<li>
<p>tcp_nopush</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:用于控制TCP链接是否推送,默认值是on
</code></pre></div> </div>
</li>
<li>
<p>tcp_nodelay</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>含义:用于控制TCP链接是否延迟,默认值是on,将tcp_nopush和tcp_nodelay两个
指令设置为on用于防止网络阻塞
</code></pre></div> </div>
</li>
</ol>
<p><strong>HTTP模块Location相关指令</strong></p>
<ol>
<li>
<p>基本语法</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>语法:location [= | ~ | ~* | ^~] </uri/> {...}
缺省:N/A
作用域:server
</code></pre></div> </div>
</li>
<li>
<p>匹配规则</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1. 四种匹配方式
= 精确匹配
~ 大小写敏感正则匹配
~* 大小写不敏感正则匹配
^~ 前缀匹配
2. location匹配指令的执行顺序
首先:= 精确匹配;
其次:^~ 前缀匹配;
再次:~* 和 ~ 正则匹配,顺序依据出现顺序;
最后:如果出现正则匹配成功,则采用该正则匹配;如果无可匹配正则,
则采用前缀匹配结果。
</code></pre></div> </div>
</li>
</ol>
<p>如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> location = / { # 只匹配"/".}
location / {# 匹配任何请求,因为所有请求都是以"/"开始
# 但是更长字符匹配或者正则表达式匹配会优先匹配
}
location ^~ /images/ {
# 匹配任何以 /images/ 开始的请求,并停止匹配
其它location
}
location ~* \.(gif|jpg|jpeg)$ {
# 匹配以 gif, jpg, or jpeg结尾的请求.
# 但是所有 /images/ 目录的请求将由上面localtion处理.
}
</code></pre></div></div>
<p><strong>压缩(gzip)模块相关指令</strong></p>
<p>gzip(GNU-ZIP)是一种压缩技术。经过gzip压缩后页面大小可以变为原来的30%甚至更小,这样,
用户浏览页面的时候速度会块得多。gzip的压缩页面需要浏览器和服务器双方都支持,实际上就
是服务器端压缩,传到浏览器后浏览器解压并解析。浏览器那里不需要我们担心,因为目前的巨
大多数浏览器都支持解析gzip过的页面。Nginx的压缩输出有一组gzip压缩指令来实现。相关指
令位于http{….}两个大括号之间。</p>
<ol>
<li>
<p>gzip on</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>该指令用于开启或关闭gzip模块(on/off)
</code></pre></div> </div>
</li>
<li>
<p>gzip_min_length 1k
设置允许压缩的页面最小字节数,页面字节数从header头得content-length中进行获取。
默认值是0,不管页面多大都压缩。建议设置成大于1k的字节数,小于1k可能会越压越大。</p>
</li>
<li>
<p>gzip_buffers 4 16k
设置系统获取几个单位的缓存用于存储gzip的压缩结果数据流。4 16k代表以16k为单位,
安装原始数据大小以16k为单位的4倍申请内存。</p>
</li>
<li>
<p>gzip_http_version 1.1</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>识别http的协议版本(1.0/1.1)
</code></pre></div> </div>
</li>
<li>
<p>gzip_comp_level 2</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gzip压缩比,1压缩比最小处理速度最快,9压缩比最大但处理速度最慢(传输快但比较消耗cpu)
</code></pre></div> </div>
</li>
<li>
<p>gzip_types text/plain application/x-javascript text/css application/xml</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>匹配mime类型进行压缩,无论是否指定,”text/html”类型总是会被压缩的。
</code></pre></div> </div>
</li>
<li>
<p>gzip_vary on</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>和http头有关系,加个vary头,给代理服务器用的,有的浏览器支持压缩,有的不支持,
所以避免浪费不支持的也压缩,所以根据客户端的HTTP头来判断,是否需要压缩
</code></pre></div> </div>
</li>
<li>
<p>gzip_disable “MSIE [1-6].”</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>指定版本浏览器不压缩
</code></pre></div> </div>
</li>
</ol>
<h3 id="二实际应用中配置实例">二、实际应用中配置实例</h3>
<p><strong>nginx.conf部分配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> #user nobody;
worker_processes 48;
worker_rlimit_nofile 1024;
error_log logs/error.log notice;
#pid logs/nginx.pid;
events {
use epoll;
worker_connections 1024;
}
# HTTP服务配置
http {
include common.conf;
</code></pre></div></div>
<p>此处nginx.conf中include了common.conf,其中common.conf中配置了http模块中公用信息</p>
<p><strong>common.conf部分配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> include mime.types;
default_type application/octet-stream;
log_format main '|$remote_addr|- |$http_cookie| - |$remote_user| [$time_local]| '
'"$request" |$status| $body_bytes_sent| "$http_referer"| '
'|"$http_user_agent"| |"$http_x_forwarded_for"|';
#access_log off
access_log logs/access.log main buffer=32K;
server_tokens off;
client_max_body_size 20m;
client_header_buffer_size 32K;
large_client_header_buffers 4 32K;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
client_header_timeout 30;
client_body_timeout 30;
send_timeout 30;
keepalive_timeout 65;
# HttpGzip模块配置
include gzip.conf;
</code></pre></div></div>
<p>此处common.conf中include了gzip.conf,其中gzip.conf中配置了HttpGzip模块中公用信息</p>
<p><strong>其中gzip.conf部分配置</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # HttpGzip模块配置,这个模块支持在线实时压缩输出数据流
gzip on;
gzip_min_length 1k;
gzip_proxied any;
gzip_buffers 4 16k;
gzip_http_version 1.1;
gzip_comp_level 1;
gzip_types text/plain application/x-javascript text/css application/xml;
gzip_vary on;
gzip_disable "MSIE [1-6]\.";
</code></pre></div></div>
Nginx服务器介绍及扩展
2013-10-18T00:00:00+00:00
http://www.blogways.net/blog/2013/10/18/nginx-1
<h2 id="一概述">一、概述</h2>
<h3 id="架构">架构</h3>
<p>nginx在启动后,在unix系统中会以daemon的方式在后台运行,后台进程包含一个master进程和多个worker进程。我们也可以手动地关掉后台模式,让nginx在前台运行,nginx是以多进程的方式来工作的,当然nginx也是支持多线程的方式的,只是我们主流的方式还是多进程的方式。</p>
<p>nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,包含:接收来自外界的信号,向各worker进程发送信号,监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。而基本的网络事件,则是放在worker进程中来处理了。多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。worker进程的个数是可以设置的,一般我们会设置与机器cpu核数一致,这里面的原因与nginx的进程模型以及事件处理模型是分不开的。nginx的进程模型,可以由下图来表示:</p>
<p><img src="/images/chapter-2-1.png" alt="" /></p>
<h3 id="基本概念">基本概念</h3>
<p><strong>connection</strong></p>
<p>在nginx中connection就是对tcp连接的封装,其中包括连接的socket,读事件,写事件。利用nginx封装的connection,我们可以很方便的使用nginx来处理与连接相关的事情,比如,建立连接,发送与接受数据等。而nginx中的http请求的处理就是建立在connection之上的,所以nginx不仅可以作为一个web服务器,也可以作为邮件服务器。当然,利用nginx提供的connection,我们可以与任何后端服务打交道。</p>
<p>在nginx中,每个进程会有一个连接数的最大上限,这个上限与系统对fd的限制不一样。在操作系统中,通过ulimit -n,我们可以得到一个进程所能够打开的fd的最大数,即nofile,因为每个socket连接会占用掉一个fd,所以这也会限制我们进程的最大连接数,当然也会直接影响到我们程序所能支持的最大并发数,当fd用完后,再创建socket时,就会失败。不过,这里我要说的nginx对连接数的限制,与nofile没有直接关系,可以大于nofile,也可以小于nofile。nginx通过设置worker_connectons来设置每个进程可使用的连接最大值。nginx在实现时,是通过一个连接池来管理的,每个worker进程都有一个独立的连接池,连接池的大小是worker_connections。这里的连接池里面保存的其实不是真实的连接,它只是一个worker_connections大小的一个ngx_connection_t结构的数组。并且,nginx会通过一个链表free_connections来保存所有的空闲ngx_connection_t,每次获取一个连接时,就从空闲连接链表中获取一个,用完后,再放回空闲连接链表里面。</p>
<p>一个nginx能建立的最大连接数,应该是worker_connections * worker_processes。这里说的是最大连接数,对于HTTP请求本地资源来说,能够支持的最大并发数量是worker_connections * worker_processes,而如果是HTTP作为反向代理来说,最大并发数量应该是worker_connections * worker_processes/2。因为作为反向代理服务器,每个并发会建立与客户端的连接和与后端服务的连接,会占用两个连接。</p>
<p><strong>request</strong></p>
<p>request具体到nginx中的数据结构是ngx_http_request_t。ngx_http_request_t是对一个http请求的封装。以下为nginx一个http请求的生命周期图:</p>
<p><img src="/images/chapter-2-2.png" alt="" /></p>
<h3 id="nginx指令">Nginx指令</h3>
<p>nginx的配置系统由一个主配置文件和其他一些辅助的配置文件构成。这些配置文件均是纯文本文件,全部位于nginx安装目录下的conf目录下。配置文件中以#开始的行,或者是前面有若干空格或者TAB,然后再跟#的行,都被认为是注释,也就是只对编辑查看文件的用户有意义,程序在读取这些注释行的时候,其实际的内容是被忽略的。在nginx.conf中,包含若干配置项。每个配置项由配置指令和指令参数2个部分构成。指令参数也就是配置指令对应的配置值。</p>
<p>配置指令是一个字符串,可以用单引号或者双引号括起来,也可以不括。但是如果配置指令包含空格,一定要引起来。指令的参数使用一个或者多个空格或者TAB字符与指令分开。指令的参数有一个或者多个TOKEN串组成。TOKEN串之间由空格或者TAB键分隔。TOKEN串分为简单字符串或者是复合配置块。复合配置块即是由大括号括起来的一堆内容。一个复合配置块中可能包含若干其他的配置指令。</p>
<p>如果一个配置指令的参数全部由简单字符串构成,也就是不包含复合配置块,那么我们就说这个配置指令是一个简单配置项,否则称之为复杂配置项。例如下面这个是一个简单配置项:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>error_page 500 502 503 504 /50x.html;
</code></pre></div></div>
<p>对于简单配置,配置项的结尾使用分号结束。对于复杂配置项,包含多个TOKEN串的,一般都是简单TOKEN串放在前面,复合配置块一般位于最后,而且其结尾,并不需要再添加分号。例如下面这个复杂配置项</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>location / {
root /home/jizhao/nginx-book/build/html;
index index.html index.htm;
}
</code></pre></div></div>
<h3 id="nginx模块概述">Nginx模块概述</h3>
<p>nginx的内部结构是由核心部分和一系列的功能模块所组成。这样划分是为了使得每个模块的功能相对简单,便于开发,同时也便于对系统进行功能扩展。nginx将各功能模块组织成一条链,当有请求到达的时候,请求依次经过这条链上的部分或者全部模块,进行处理。每个模块实现特定的功能。例如,实现对请求解压缩的模块,实现SSI的模块,实现与上游服务器进行通讯的模块。</p>
<p><strong>模块分类</strong></p>
<p>nginx的模块根据其功能基本上可以分为以下几种类型:</p>
<p><strong>event module</strong>: 搭建了独立于操作系统的事件处理机制的框架,及提供了各具体事件的处理。包括ngx_events_module, ngx_event_core_module和ngx_epoll_module等。nginx具体使用何种事件处理模块,这依赖于具体的操作系统和编译选项。</p>
<p><strong>phase handler</strong>: 此类型的模块也被直接称为handler模块。主要负责处理客户端请求并产生待响应内容,比如ngx_http_static_module模块,负责客户端的静态页面请求处理并将对应的磁盘文件准备为响应内容输出。</p>
<p><strong>output filter</strong>: 也称为filter模块,主要是负责对输出的内容进行处理,可以对输出进行修改。例如,可以实现对输出的所有html页面增加预定义的footbar一类的工作,或者对输出的图片的URL进行替换之类的工作。</p>
<p><strong>upstream</strong>: upstream模块实现反向代理的功能,将真正的请求转发到后端服务器上,并从后端服务器上读取响应,发回客户端。upstream模块是一种特殊的handler,只不过响应内容不是真正有自己产生的,而是从后端服务器上读取的。</p>
<p><strong>load-balancer</strong>: 负载均衡模块,实现特定的算法,在众多的后端服务器中,选择一个服务器出来作为某个请求的转发服务器。</p>
<p><strong>handler模块</strong></p>
<p>Handler模块就是接受来自客户端的请求并产生输出的模块,目前第三方开发模块最可能开发的就是三种类型的模块,即handler,filter和load-balancer。</p>
<h2 id="二openresty介绍及安装">二、OpenResty介绍及安装</h2>
<h3 id="openresty介绍">OpenResty介绍</h3>
<p>OpenResty (也称为 ngx_openresty)是一个全功能的 Web 应用服务器,它打包了标准的 Nginx 核心,很多的常用的第三方模块,以及它们的大多数依赖项。OpenResty
通过汇聚各种设计精良的 Nginx 模块。从而将 Nginx 有效的变成一个强大的 Web 应用服务器,这样, Web 开发人员可以使用 Lua 脚本语言调动 Nginx 支持的各种C以及Lua 模块。
OpenResty 的目标是让你的Web服务直接跑在 Nginx 服务内部,充分利用 Nginx 的非阻塞 I/O 模型,不仅仅对 HTTP 客户端请求,甚至于对远程后端诸如MySQL,PostgreSQL,
Memcache 以及 ~Redis 等都进行一致的高性能响应。</p>
<h3 id="openresty安装">OpenResty安装</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1、下载OpenResty http://openresty.org/download/ngx_openresty-1.4.2.8.tar.gz
2、解压openresty gunzip -c ngx_openresty-1.4.2.8.tar.gz | tar xvf -
3、./configure \
--prefix=/home/spdev/nginx/openresty/local \
--sbin-path=/home/spdev/nginx/openresty/sbin \
--user=spdev\
--group=spd\
--with-debug \
--with-http_dav_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_addition_module \
--with-http_flv_module \
--without-http_memcached_module \
--without-http_redis2_module \
--without-http_redis_module \
--without-lua_redis_parser \
--without-mail_pop3_module \
--without-mail_imap_module \
--without-mail_smtp_module \
make
make install
其中--with是自定义安装需要模块,--without是不安装默认安装的模块。
</code></pre></div></div>
<h3 id="第三方模块安装">第三方模块安装</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1、服务器新建目录如:/home/spdev/nginx/tb_module
2、下载第三方模块,如淘宝合并js模块
(svn checkout http://code.taobao.org/svn/nginx_concat_module/trunk/
$NGINX_CONCAT_MODULE)。
3、./configure 中增加参数,--add-module=$NGINX_CONCAT_MODULE
./configure \
--prefix=/home/spdev/nginx/openresty/local \
--sbin-path=/home/spdev/nginx/openresty/sbin \
--user=spdev\
--group=spd\
--with-debug \
--with-http_dav_module \
--with-http_ssl_module \
--with-http_stub_status_module \
--with-http_addition_module \
--with-http_flv_module \
--without-http_memcached_module \
--without-http_redis2_module \
--without-http_redis_module \
--without-lua_redis_parser \
--without-mail_pop3_module \
--without-mail_imap_module \
--without-mail_smtp_module \
--add-module=/home/spdev/nginx/tb_module/$NGINX_CONCAT_MODULE \
4、make make install
</code></pre></div></div>
<h3 id="三nginx简单命令">三、Nginx简单命令</h3>
<p>Nginx默认配置文件nginx.conf位于nginx安装目录conf目录下,执行命令需切换至对应目录。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>nginx 启动Nginx
nginx -c </path/to/config> Nginx 指定一个配置文件,来代替缺省的。
nginx -t 不运行,仅仅测试配置文件语法的正确性。
nginx -v 显示 nginx 的版本。
nginx -V 显示 nginx 的版本,编译器版本和配置参数。
nginx -s reload 更改了配置后无需重启Nginx,平滑重启。
nginx -s stop 停止Nginx
</code></pre></div></div>
Sencha Touch 实例分析(自定义列表)
2013-09-21T00:00:00+00:00
http://www.blogways.net/blog/2013/09/21/senchatouch-5
<h2 id="自定义列表分析">自定义列表分析</h2>
<h4 id="这是一个应用分类软件的截图">这是一个应用分类软件的截图</h4>
<p><img src="/images/st-7.png" alt="应用程序下载页面截图" /></p>
<h4 id="看看-st-里面如何来设计这个界面">看看 ST 里面如何来设计这个界面</h4>
<p><img src="/images/st-8.png" alt="ST 视图设计" /></p>
<p>简单来划分,这个视图分为三块,最外面的 TabPanel(id: tp),面板上面的 TitleBar(id: tb)以及下面主要数据展示的 List(id: ll),下面来看看每个模块的代码:</p>
<h4 id="tabpanelid-tp">TabPanel(id: tp)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('myapp.view.MyList', {
extend: 'Ext.TabPanel',
xtype: 'mylist',
config: {
tabBarPosition: 'bottom',
items: [{
title: '首页',
iconCls: 'home'
},
{
title: '分类',
iconCls: 'info'
},
{
title: '达人',
iconCls: 'locate'
},
{
title: '排行',
iconCls: 'user'
},
{
title: '管理',
iconCls: 'settings'
}]
}
})
</code></pre></div></div>
<h4 id="titlebarid-tb">TitleBar(id: tb)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'label',
html: '应用分类'
},
{
iconCls: 'search',
ui: 'plain',
align: 'right'
}
]
}
</code></pre></div></div>
<h4 id="listid-ll">List(id: ll)</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
xtype: 'list',
store: 'MyListStore',
itemTpl: ['<div class="mylist">',
'<div class="mylist-1"><image src="{img}"/></div>',
'<div class="mylist-2"><div class="mylist-2-1">{title}</div>',
'<div class="mylist-2-2">{subtitle}</div></div>',
'<div class="mylist-3">{download}</div>',
'</div>'].join(''),
flex: 1
}
</code></pre></div></div>
<p>既然是 list 肯定是有数据源的,这个示例中定义的数据源名是 MyListStore,看下其如何实现的,为了简便起见 model 我没有单独写,跟 store 合到一起了。</p>
<h4 id="myappstoremyliststore">myapp.store.MyListStore</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('myapp.store.MyListStore', {
extend: 'Ext.data.Store',
config: {
// model: 'myapp.model.MyListModel',
fields: ['title', 'subtitle', 'img', 'download'],
data: [
{ title: '游戏', subtitle: '体育、战略、休闲', img: 'resources/icons/Icon.png', download: '22889'},
{ title: '电子书', subtitle: '小说、笑话、资料', img: 'resources/icons/Icon.png', download: '18621'},
{ title: '影音播放', subtitle: 'Adobe Flash 播放器', img: 'resources/icons/Icon.png', download: '2088'},
{ title: '交通导航', subtitle: 'Google 地图,高级地图', img: 'resources/icons/Icon.png', download: '685'},
{ title: '生活娱乐', subtitle: '大众点评,我查查', img: 'resources/icons/Icon.png', download: '9184'}
]
}
});
</code></pre></div></div>
<p>看下最后的效果图:</p>
<p><img src="/images/st-9.png" alt="ST 自定义列表效果图" /></p>
<p>最后看下合并后整个视图的代码</p>
<h4 id="myappviewmylist">myapp.view.MyList</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('myapp.view.MyList', {
extend: 'Ext.TabPanel',
xtype: 'mylist',
requires: [
'Ext.Label',
'Ext.dataview.List'
],
config: {
tabBarPosition: 'bottom',
items: [{
title: '首页',
iconCls: 'home',
layout: 'vbox',
items: [{
xtype: 'titlebar',
docked: 'top',
items: [
{
xtype: 'label',
html: '应用分类'
},
{
iconCls: 'search',
ui: 'plain',
align: 'right'
}
]
}, {
xtype: 'list',
store: 'MyListStore',
itemTpl: ['<div class="mylist">',
'<div class="mylist-1"><image src="{img}"/></div>',
'<div class="mylist-2"><div class="mylist-2-1">{title}</div>',
'<div class="mylist-2-2">{subtitle}</div></div>',
'<div class="mylist-3">{download}</div>',
'</div>'].join(''),
flex: 1
}]
},
{
title: '分类',
iconCls: 'info'
},
{
title: '达人',
iconCls: 'locate'
},
{
title: '排行',
iconCls: 'user'
},
{
title: '管理',
iconCls: 'settings'
}]
}
})
</code></pre></div></div>
<p>自定义列表样式</p>
<h4 id="maincss">main.css</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.mylist {
height: 60px;
}
.mylist-1 {
display: inline-block;
float: left;
}
.mylist-2 {
margin-top: 10px;
padding-left: 10px;
display: inline-block;
}
.mylist-2-1 {
font-size: 1.2em;
}
.mylist-2-2 {
margin-top: 5px;
font-size: 0.6em;
}
.mylist-3 {
display: inline-block;
float: right;
padding-top: 20px;
padding-right: 5px;
font-size: 0.6em;
}
.x-list .x-list-item .x-list-item-body, .x-list .x-list-item.x-list-item-tpl .x-innerhtml {
padding: 5px 5px;
}
</code></pre></div></div>
<p>这个示例的选择相对比较简洁,大家有时候会看到更加复杂的列表,或者自己在开发的时候需要展示的数据更多,样式要求更复杂,其实原理都是一样的,通过定义 itemTpl 节点元素来实现自定义的列表项展示。</p>
Sencha Touch 实例分析(功能导航)
2013-09-20T00:00:00+00:00
http://www.blogways.net/blog/2013/09/20/senchatouch-4
<h2 id="功能导航页面分析">功能导航页面分析</h2>
<p>本次分析的是支付宝的一个功能导航页面</p>
<h4 id="下面是支付宝导航页面截图">下面是支付宝导航页面截图</h4>
<p><img src="/images/st-4.png" alt="支付宝功能导航页面" /></p>
<h4 id="我们来看看在-st-里面如何设计实现整个页面的部局">我们来看看在 ST 里面如何设计实现整个页面的部局</h4>
<p><img src="/images/st-5.png" alt="支付宝功能导航页面设计" /></p>
<p>整个页面主要是一个 TabPanel(id: tp),里面包含了一个 Container(id: con1)和一个 Carousel(id: car),car 里面包含了一个 Container(id:con2)。</p>
<h4 id="下面来看看每个模块如何实现">下面来看看每个模块如何实现:</h4>
<h5 id="tabpanelid-tp">TabPanel(id: tp)</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('myapp.view.Paypal', {
extend: 'Ext.TabPanel',
xtype: 'paypal',
requires: [],
config: {
tabBarPosition: 'bottom',
items: [{
title: '支付宝',
iconCls: 'home'
},
{
title: '帐单',
iconCls: 'info'
},
{
title: '我的帐单',
iconCls: 'locate'
},
{
title: '安全',
iconCls: 'user'
}]
}
})
</code></pre></div></div>
<h5 id="containerid-con1">Container(id: con1)</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
xtype: 'container',
padding: '15px 5px 5px 10px',
style: 'background-color: #5E99CC',
html: ['<div class="title"><div class="title-1">',
'<image src="resources/icons/Icon.png"/></div>',
'<div class="title-2"><div class="title-2-1">4.10 元</div>',
'<div class="title-2-2">备注说明</div></div></div>'].join('')
}
</code></pre></div></div>
<h5 id="carouselid-car">Carousel(id: car)</h5>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{
xtype: 'carousel',
flex: 1,
layout: 'fit',
defaults: {
styleHtmlContent: true
},
items: [
{
html : 'Item 1'
},
{
html : 'Item 2'
},
{
html : ['<div class="nav"><ul>',
'<li><image src="resources/icons/Icon.png"></image><a>转帐</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>信用卡还款</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>手机充值</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>水电煤</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>扫码</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>iReader</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>更多</a></li>',
'</ul></div>'].join('')
}
]
}
</code></pre></div></div>
<h4 id="看下总体的效果图">看下总体的效果图</h4>
<p><img src="/images/st-6.png" alt="ST 模仿效果图" /></p>
<p>当然我并没有完全按原样的界面去做,毕竟图片跟样式不是我们这节的重点,这个完全是按简化版来设计的,也没有添加任何事件,给大家一个参考,在实际运用的时候大家可以把两部分分成两个单独的文件来开发,通过 xtype 再结合起来。另外当中的九宫图也是比较经典的部分,我是通过 html 的无序列表来实现的,我们知道很多经典的菜单都是通过 ul+css 来实现的,这个地方也不例外,无序列表配合 css 用来实现导航功能还是蛮强大的,当然也可以通 div 来实现九宫格,样式理解起来可能更容易一些,或者直接使用 ST 的 toolbar 来实现也是可以的,这三种方式都是可以动态添加创建的,这个地方不作讨论,如果有需要的话以后来写下这三种动态实现方式。</p>
<h4 id="下面来看下完整的代码">下面来看下完整的代码</h4>
<p>view</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('myapp.view.Paypal', {
extend: 'Ext.TabPanel',
xtype: 'paypal',
requires: [],
config: {
tabBarPosition: 'bottom',
items: [{
title: '支付宝',
iconCls: 'home',
layout: 'vbox',
items: [{
xtype: 'container',
padding: '15px 5px 5px 10px',
style: 'background-color: #5E99CC',
html: ['<div class="title"><div class="title-1">',
'<image src="resources/icons/Icon.png"/></div>',
'<div class="title-2"><div class="title-2-1">4.10 元</div>',
'<div class="title-2-2">备注说明</div></div></div>'].join('')
}, {
xtype: 'carousel',
flex: 1,
layout: 'fit',
defaults: {
styleHtmlContent: true
},
items: [
{
html : 'Item 1'
},
{
html : 'Item 2'
},
{
html : ['<div class="nav"><ul>',
'<li><image src="resources/icons/Icon.png"></image><a>转帐</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>信用卡还款</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>手机充值</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>水电煤</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>扫码</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>iReader</a></li>',
'<li><image src="resources/icons/Icon.png"></image><a>更多</a></li>',
'</ul></div>'].join('')
}
]
}]
},
{
title: '帐单',
iconCls: 'info'
},
{
title: '我的帐单',
iconCls: 'locate'
},
{
title: '安全',
iconCls: 'user'
}]
}
})
</code></pre></div></div>
<p>css</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.title {
}
.title-1 {
float: left;
display: inline-block;
}
.title-2 {
display: inline-block;
margin: 5px;
padding-top: 5px;
}
.title-2-1 {
font-size: 1.2em;
}
.title-2-2 {
font-size: 0.6em;
}
.nav {
margin: 0;
padding: 0;
font-size: 0.6em;
}
.nav {
margin: 0px;
padding: 0px;
}
.nav ul {
margin: 0px;
padding: 0px;
}
.nav ul li {
width: 33%;
height: 80px;
float: left;
display: inline;
text-align: center;
}
.nav ul li img {
width: 48px;
height: 48px;
}
.nav ul li a {
display: block;
text-decoration: none;
}
</code></pre></div></div>
<p>方便起见,我把代码合到一个 view 文件里面,如果你还不知道怎么看的话,见意回头看看基础概念,别急于求成。</p>
构建基于 Sencha Touch MVC 的 web 应用
2013-09-20T00:00:00+00:00
http://www.blogways.net/blog/2013/09/20/senchatouch-3
<h2 id="构建基于-sencha-touch-mvc-的-web-应用">构建基于 Sencha Touch MVC 的 web 应用</h2>
<h3 id="mvc-程序结构解析">MVC 程序结构解析</h3>
<p>就算以前有 EXT 的编程经验,刚接触 ST MVC 的时候,你可能会被一个个文件夹跟一堆堆的 js 搞得头昏脑涨,建一个视图需要几个文件,每个文件该如何创建,相互之间如何调用。所以开始实践之前有必要来了解下 MVC 的整个程序结构。</p>
<p>一个应用程序其实就是Models,Views,Controllers,Stores和Profiles的集合,只不过附加了一些特殊的元素,例如程序的图标或者加载图片等等。</p>
<p><img src="/images/st-3.png" alt="ST 应用程序架构" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Model:在应用程序中代表了一个对象类型,简单理解便是数据模型。
View:利用Sencha Touch内部组件,负责向用户显示数据,简单解释便是视图。
Controller:处理应用程序的交互,监听事件并作出响应,简单理解就是控制器。
Store:负责将数据加载到我们的应用程序当中。
Profile:为的是在尽可能多的通用代码下,为手机和平板电脑定制UI。简单理解就是配置文件,如果是手机应该加载怎样的UI,平板电脑则加载什么样的UI。
</code></pre></div></div>
<p>通常我们在Sencha Touch的应用程序中都会这样编写application:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
name: 'MyApp',
models: ['User', 'Product', 'nested.Order'],
views: ['OrderList', 'OrderDetail', 'Main'],
controllers: ['Orders'],
launch: function() {
Ext.create('MyApp.view.Main');
}
});
</code></pre></div></div>
<p>那么我们就对以上代码进行简单分析:</p>
<p>name:便是我们定义的命名空间,我们的所有代码都在该空间下编写,类似Java中的顶级包名。在上面的代码中,我们我们知道,在view文件夹下有Main.js文件,就等价于该文件的存在路径为MyApp.view.Main。</p>
<p>我们通过models,views和controllers来加载相应目录下的文件。</p>
<p>Controllers</p>
<p>controller是程序的中心,它把程序的各部分有机的结合在一起,并统一运行控制,使我们的程序正常运行。比如,view中存放的仅仅是页面布局等代码,关于页面逻辑处理的代码几乎都存放在controller中了,实现逻辑代码的统一管理。</p>
<p>A simple example</p>
<p>接下来的例子展示给我们如何快速的定义控制器。在这里我们使用两个比较常用的控制器配置项:refs和control。通过refs,我们可以轻松的在一个页面中找到任何的组件。在本例中,我们将匹配xtype为formpanel的组件,并将第一个匹配的作为loginForm,并在doLogin函数之后使用该属性。</p>
<p>第二件事情便是建立起一个control配置项。就像refs,它使用一个组件查询器来找到所有的formpanel。看看下面具体的代码再说吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('MyApp.controller.Sessions', {
extend: 'Ext.app.Controller',
config: {
refs: {
loginForm: 'formpanel'
},
control: {
'formpanel button': {
tap: 'doLogin'
}
}
},
doLogin: function() {
var form = this.getLoginForm(),
values = form.getValues();
MyApp.authenticate(values);
}
});
</code></pre></div></div>
<p>doLogin函数本身是很简单的,因为在定义一个loginForm的refs的时候,控制器会自动为我们生成一个getLoginForm的函数,返回我们需要的那个formpanel。简单理解上面代码的作用便是:refs是我们声明的组件的引用,control中是我们对这些组件需要监听的事件以及触发的函数。refs是指向,control是控制。</p>
<p>Stores</p>
<p>Stores是Sencha Touch中特别重要的部分,并且大多数的窗体都会绑定数据的。简单来说,store其实就像是model的数据实体。例如List和DataView这些组件,渲染的都是store中的model的实体。因为model的实体被添加或者从store中移除时,都会触发这些组件所绑定的数据监听,达到更新或者其他操作的目的。</p>
<p>Device Profiles</p>
<p>我们知道,Sencha Touch程序会运行在不同设备上,这些设备有着不同的功能或者屏幕分辨率。例如一个用户界面可能比较适合于平板电脑,但是在手机中就显得不是那么合适了,反之亦然。然而我们在编写应用程序的时候,并不想为每一设备都单独写一个程序,那样太麻烦了,我们更倾向于写好的程序,本身能够在手机或者平板电脑中运行并根据设备进行不同的加载,显示不同的UI风格,这是我们便要用好profile文件了。profile文件只是简单的告诉程序,如何针对不同的设备进行不同的加载,我们通常在程序开始的时候便声明它:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
name: 'MyApp',
profiles: ['Phone', 'Tablet'],
//as before
});
</code></pre></div></div>
<p>一旦我们像上面这段代码一样,声明了我们的profile文件,那么程序在加载的过程当中,就会去加载我们app/profile目录下的相应JS文件了。我们可以看看下面这段代码是如何定义一个平板电脑的配置文件的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('MyApp.profile.Tablet', {
extend: 'Ext.app.Profile',
config: {
controllers: ['Groups'],
views: ['GroupAdmin'],
models: ['MyApp.model.Group']
},
isActive: function() {
return Ext.os.is.Tablet;
}
});
</code></pre></div></div>
<p>只要Sencha Touch认为我们的应用程序是运行在平板电脑上的时候,isActive函数便会返回true。其实这个判断并不是很准确的,因为目前的平板电脑和手机并没有很明显的界限,所有Sencha Touch在进行判断的时候,只识别ipad,对于其他的平板电脑都会返回false的,也就是说除了ipad,其他的都看做是手机。不过你也可在isActive函数中进行细化,来实现你需要的功能。
我们在编写代码的时候,一定要注意,只能有一个true从profile文件中返回,否则的话程序只会识别第一个返回的true,其他的将不会被识别或者被忽略。这时我们应用程序会被设定在当前的配置文件下,并且可以被随时查看。</p>
<p>如果检测到我们当前的配置文件,定义了额外的models,views,controllers或者stores,这些都会被自动加载的。但是名字可不是随便写的,这些都是有一定关联的,看下面的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>views: ['GroupAdmin'] will load app/view/tablet/GroupAdmin.js
controllers: ['Groups'] will load app/controller/tablet/Groups.js
models: ['MyApp.model.Group'] will load app/model/Group.js
</code></pre></div></div>
<p>如果没有完整的名字的话,该文件必须存放在相应目录下的tablet子目录下,例如所有的视图文件都必须存放在app/view/tablet目录下,但是如果指定了完整的路径的话,只要该文件存在于该路径下就OK了。</p>
<p>大多数情况下,我们只会在profile中定义一些额外的视图或者控制器,共享我们应用程序的数据。</p>
<p>Launch Process</p>
<p>我们可以为每一个应用程序定义一个launch函数,负责应用程序的加载,同时这里也是我们设定应用程序启动逻辑,创建主视图的最好位置。除了在该位置之外,我们还有两个地方可以设定我们程序的启动逻辑。首先是每一个控制器中,我们可以定义一个init函数,该函数会在launch函数之前被调用。另一个便是,如果我们使用了设备配置文件,那么每一个profile中都能定义launch函数,该函数会在控制器的init之后,launch之前被调用。</p>
<p>例如我们为设备定义了不同的profile文件,phone和tablet,并在一个平板电脑中运行我们的应用程序,那么启动顺序如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.控制器的init函数被调用。
2.Profile中的launch函数会被调用。
3.应用程序的launch函数会被调用。
4.控制器中的launch函数会被调用。
</code></pre></div></div>
<p>当我们使用Profile文件的时候,通常会把启动的逻辑顺序放在Profile的launch中,因为我们会根据不同设备,建立不同的视图启动顺序。</p>
<h2 id="管理-mvc-的依赖项">管理 MVC 的依赖项</h2>
<p>ST2应用程序用来定义依赖项的地方主要有两个,application本身和应用程序内部类。本指南将给出一些关于应用程序如何放置和在哪里放置依赖项的建议。</p>
<h4 id="应用程序依赖项">应用程序依赖项</h4>
<p>当你创建一个MVC应用程序时,Ext.application会提供一个直观的方式来设置应用程序会用到的数据模型、视图、控制器、数据存储器和配置文件等,如下例所示:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
name: 'MyApp',
views: ['Login'],
models: ['User'],
controllers: ['Users'],
stores: ['Products'],
profiles: ['Phone', 'Tablet']
});
</code></pre></div></div>
<p>这5个配置项是用来加载应用程序常用文件(数据模型、视图、控制器、存储器、配置文件)的快捷方式。如上的配置意味着应用程序会自动加载下列文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app/view/Login.js
app/model/User.js
app/controller/Users.js
app/store/Products.js
app/profile/Phone.js
app/profile/Tablet.js
</code></pre></div></div>
<p>就加载文件而言,上面的例子跟下面的定义是等价的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.require([
'MyApp.view.Login',
'MyApp.model.User',
'MyApp.controller.Users',
'MyApp.store.Products',
'MyApp.profile.Phone',
'MyApp.profile.Tablet'
]);
</code></pre></div></div>
<p>在你需要加载更多的类文件情况下,这种配置方式就会更有用,它能避免你为每一个文件都拼写又臭又长的完整类名。除了把依赖文件加载进来之外,这几个配置还会做更多的事情:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>profiles
配置文件 – 实例化每一个Profle并判断哪一个当前可用。当前可用的那个profile中所有依赖项也将被加载
controllers
控制器 – 加载完成后实例化每一个控制器
stores
存储器 – 实例化每一个存储器,没有指定id的存储器会被指定一个默认id
</code></pre></div></div>
<p>这意味着,如果你要享受MVC带给你的便利,那么载你定义应用程序依赖项的时候,最好使用配置选项这种方式。</p>
<h4 id="配置文件指定的依赖项">配置文件指定的依赖项</h4>
<p>当你使用设备配置的时候,可能会有一些类是仅在特定设备上使用的。例如,平板电脑版本的应用程序可能包含比手机版本更多的功能,这当然意味着要加载更多的类。每个Profile都可以在内部定义额外的依赖项。</p>
<p>Ext.define(‘MyApp.profile.Tablet’, {
extend: ‘Ext.app.Profile’,
config: {
views: [‘SpecialView’],
controllers: [‘Main’],
models: [‘MyApp.model.SuperUser’]
},
isActive: function() {
return Ext.os.is.Tablet;
}
});</p>
<p>然后每个profile中定义的依赖项都会被加载,不管这个profile是否active,不过尽管都被加载,但应用程序不会去做类似实例化非active状态profile指定的控制器这样的无用功。</p>
<p>这听起来有点不合常规,为什么要下载那些不会用到的类文件呢?这么做的原因是产生一个可以在任何设备上运行的通用程序版本,然后检测哪一个profile应该被使用,接着从这个profile启动应用程序。与之相对的选择是为每个profile创建一个应用版本,然后启动一个微型加载器来检测哪个profile该被选择,然后去下载该profile需要的代码文件。</p>
<p>的确这种通用架构的程序会在每个设备上都下载一些根本用不到的代码文件,不过对于绝大多数应用程序来说,你多下载的这点文件影响实在是微乎其微。而对于比较庞大的应用程序来说,这个问题可能更值得注意,所以我们可能在2.0的后续版本对它进行处理。</p>
<h4 id="级联依赖">级联依赖</h4>
<p>大一些应用通常会把数据模型、视图、控制器分别存储在不同子文件夹下,这样可以让整个项目看起来更清晰明了一些。对于视图来说尤其如此,大型应用拥有上百个独立的视图类并非天方夜谭,因此分文件夹存储几乎不可避免。</p>
<p>指定子文件夹中的依赖项只需要使用“.”来分割文件夹路径即可:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
name: 'MyApp',
controllers: ['Users', 'nested.MyController'],
views: ['products.Show', 'products.Edit', 'user.Login']
});
</code></pre></div></div>
<p>上例中将会加载下列5个文件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>app/controller/Users.js
app/controller/nested/MyController.js
app/view/products/Show.js
app/view/products/Edit.js
app/view/user/Login.js
</code></pre></div></div>
<p>我们可以混合使用两种方式来定义每个数据模型、视图、控制器、配置文件和存储器:快捷路径方式(符合mvc推荐原则的类只写最后的类名即可)和完整路径方式(自定义路径的类则写完整路径加类名)。</p>
<h4 id="外部依赖项">外部依赖项</h4>
<p>我们可以通过指定想要加载的完整类名方式来定义应用程序之外的类作为依赖项。这种情况最常见的用途就是在多个应用之间共享认证逻辑。我们可能会有好几个应用程序都要到同一个用户数据库进行验证并实现登录,这时我们当然希望它们能够共享用户登录的代码。比较容易的方式就是在应用程序文件夹之外创建一个单独的文件夹然后把其中的内容作为依赖项添加到应用程序中去。</p>
<p>我们假定共享的登录代码包含一个login控制器,一个用户model和一个login表单视图。我们要在应用程序中把它们全部用上:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.Loader.setPath({
'Auth': 'Auth'
});
Ext.application({
views: ['Auth.view.LoginForm', 'Welcome'],
controllers: ['Auth.controller.Sessions', 'Main'],
models: ['Auth.model.User']
});
</code></pre></div></div>
<p>上述代码将加载以下的文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Auth/view/LoginForm.js
Auth/controller/Sessions.js
Auth/model/User.js
app/view/Welcome.js
app/controller/Main.js
</code></pre></div></div>
<p>前面三个文件加载自应用程序外部,后两个则来自应用程序内部。同样我们可以混合调用内外部依赖项。</p>
<p>想要启用外部依赖项加载,我们只需告诉Loader到哪里可以找到这些文件即可,Ext.Loader.setPath就是干这个的。上例中我们告诉Loader所有以Auth命名空间中的文件都可以到Auth这个文件夹中找到。这样我们就能把应用程序文件夹之外的通用验证代码都拽进来了,其他的事情由ST框架来处理。</p>
<p>依赖项应该放在哪里</p>
<p>决定在哪里声明依赖项的一个基本原则就是保证你的类文件完整的内部包含。例如,你有一个视图A包含了几个其他的视图,你就应该在这个A视图内部声明它的依赖项,而不是在application中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.define('MyApp.view.Main', {
extend: 'Ext.Container',
requires: [
'MyApp.view.Navigation',
'MyApp.view.MainList'
],
config: {
items: [
{
xtype: 'navigation'
},
{
xtype: 'mainlist'
}
]
}
});
</code></pre></div></div>
<p>App.js中这么写:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
views: ['Main']
});
</code></pre></div></div>
<p>这才是依赖项的最佳声明方式。两个原因:1、确保app.js干净;2、让你知道主程序依赖MyApp.view.Main就已经足够。不好的方式就是下面这样把视图都罗列在app.js里:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Ext.application({
views: ['Main', 'Navigation', 'MainList']
});
</code></pre></div></div>
<p>换种方式来描述这个问题,app.js只需要包含最顶级的视图即可。你在应用程序内部通过Ext.create(‘MyApp.view.SomeView’)来创建的视图就可以视作顶级视图。其他那些仅仅被作为某些视图内部子视图的(比如例子中的MyApp.view.Navigation和MyApp.view.MainList)就不应该出现在app.js里面。</p>
Sencha Touch 体验
2013-09-18T00:00:00+00:00
http://www.blogways.net/blog/2013/09/18/senchatouch-2
<h2 id="sencha-touch-体验">Sencha Touch 体验</h2>
<h3 id="下载安装">下载&&安装</h3>
<p>Sencha Touch 2 SDK :
http://www.sencha.com/products/touch/</p>
<p>SDK Tool:
http://www.sencha.com/products/sdk-tools/download</p>
<p>解压 SDK 后是不能直接打开帮助文档的,需要部署到 web 服务器,通过 WebKit 的浏览器(chrome或者safari等)才能查看自带的帮助文档以及 Demo。</p>
<p>安装好 SDK Tool,在命令窗口输入 sencha 可以查询到 st cmd 相关信息。</p>
<h3 id="创建第一个-sencha-touch-应用">创建第一个 Sencha Touch 应用</h3>
<p>命令行格式:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sencha generate app 应用的命名空间 app路径
</code></pre></div></div>
<p>示例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E:\program\download\Html5\sencha\touch-2.2.1>sencha generate app myapp ../myapp
Sencha Cmd v3.1.2.342
[INF]
[INF] init-plugin:
[INF]
[INF] -before-generate-workspace:
[INF]
[INF] cmd-root-plugin.init-properties:
[INF]
[INF] init-properties:
[INF]
[INF] init-sencha-command:
[INF]
[INF] init:
[INF]
[INF] generate-workspace-impl:
[INF] [echo] generating into E:\program\download\Html5\sencha\touch-2.2.1\..\myapp from D:\program\Sencha\Cmd\3.1.2.342/templates/workspace
[INF] [mkdir] Created dir: E:\program\download\Html5\sencha\myapp\packages
[INF]
[INF] cmd-root-plugin.copy-framework-to-workspace-impl:
[INF] [propertyfile] Updating property file: E:\program\download\Html5\sencha\myapp\.sencha\workspace\sencha.cfg
[INF]
[INF] copy-framework-to-workspace-impl:
[INF] [copy] Copying 1862 files to E:\program\download\Html5\sencha\myapp\touch
[INF] [copy] Copied 218 empty directories to 3 empty directories under E:\program\download\Html5\sencha\myapp\touch
[INF] [copy] Copying 1 file to E:\program\download\Html5\sencha\myapp\touch
[INF] [copy] Copying 1 file to E:\program\download\Html5\sencha\myapp\touch
[INF] [propertyfile] Updating property file: E:\program\download\Html5\sencha\myapp\.sencha\workspace\sencha.cfg
[INF]
[INF] copy-framework-to-workspace:
[INF]
[INF] generate-workspace:
[INF]
[INF] -after-generate-workspace:
[INF]
[INF] init-plugin:
[INF]
[INF] cmd-root-plugin.init-properties:
[INF]
[INF] init-properties:
[INF]
[INF] init-sencha-command:
[INF]
[INF] init:
[INF]
[INF] before-upgrade:
[INF]
[INF] generate-app-impl:
[INF]
[INF] generate-starter-app:
[INF] [mkdir] Created dir: E:\program\download\Html5\sencha\myapp\app\profile
[INF]
[INF] copy-sdk:
[INF] [copy] Copying 1 file to E:\program\download\Html5\sencha\myapp\resources\css
[INF] [copy] Copying 4 files to E:\program\download\Html5\sencha\myapp\resources\sass\stylesheets
[INF] [x-property-file] Updating property file: E:\program\download\Html5\sencha\myapp\.sencha\app\sencha.cfg
[INF]
[INF] after-upgrade:
[INF]
[INF] generate-app:
[INF]
[INF] -after-generate-app:
[INF] [x-property-file] Updating property file: E:\program\download\Html5\sencha\myapp\.sencha\app\sencha.cfg
</code></pre></div></div>
<p>将产生的 myapp 项目部署到 web 服务器,通过 chrome 浏览器查看应用是否创建成功。</p>
<p><img src="/images/st-1.png" alt="第一个 Sencha Touch 应用" /></p>
<h3 id="项目结构分析">项目结构分析</h3>
<p>将生成的项目使用 IDE 打开</p>
<p><img src="/images/st-2.png" alt="Sencha Touch 项目结构" /></p>
<p>app - 目录,MVC 相关程序文件,下一章会做详细介绍
app.js - 应用的 js 入口文件
app.json - 应用配置文件,ST 采用动态加载 js 以及 css 文件机制,在 html 文件里面只需要加载 <code class="language-plaintext highlighter-rouge">touch/microloader/development.js</code> js 文件就可以了,其他的都配在这个文件里面
index.html - 应用入口 index 文件
packager.json - 打包原生程序的配置文件
resources - 目录,项目资源文件
touch - Sencha Touch SDK的副本</p>
<h3 id="编译打包">编译打包</h3>
<p>这个时候的项目虽然都能正常运行,但项目明显太大了,因为整个 ST sdk 的东西都包含在里面,而且目录也很多,通过打包将代码都打到一个文件夹里面,去除不必要的项目文件,形成最简洁的项目文件。</p>
<p>命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sencha app build package
</code></pre></div></div>
<p>进入项目根目录下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E:\program\download\Html5\sencha\myapp>sencha app build package
[INF] Saving certificate as D:\Program Files\Sencha\cmd\Sencha\Cmd\repo\pkgs\cer
t.json
[INF] Saving private key as D:\Program Files\Sencha\cmd\Sencha\Cmd\repo\.sencha\
repo\private-key.json
Sencha Cmd v3.1.2.342
[INF]
[INF] init-plugin:
[INF]
[INF] cmd-root-plugin.init-properties:
[INF]
[INF] init-properties:
[INF]
[INF] init-sencha-command:
[INF]
[INF] init:
[INF]
[INF] app-build-impl:
[INF]
[INF] -before-init-local:
[INF]
[INF] -init-local:
[INF]
[INF] -after-init-local:
[INF]
[INF] init-local:
[INF]
[INF] find-cmd:
[INF]
[INF] -before-init:
[INF]
[INF] -init:
[INF] Initializing Sencha Cmd ant environment
[INF] Adding antlib taskdef for com/sencha/command/compass/ant/antlib.xml
[INF] [x-load-properties] Loading optional properties file E:\program\download\H
tml5\sencha\myapp\.sencha\app\package.properties
[INF] [x-load-properties] Loading required properties file E:\program\download\H
tml5\sencha\myapp\.sencha\app\build.properties
[INF]
[INF] -after-init:
[INF]
[INF] -before-init-default:
[INF]
[INF] -init-default:
[INF]
[INF] -after-init-default:
[INF]
[INF] init:
[INF]
[INF] -before-build:
[INF]
[INF] sass:
[INF]
[INF] -before-sass:
[INF]
[INF] -sass:
[INF] executing compass using system installed ruby runtime
Error loading gem paths on load path in gem_prelude
can't modify frozen string
<internal:gem_prelude>:69:in `force_encoding'
<internal:gem_prelude>:69:in `set_home'
<internal:gem_prelude>:38:in `dir'
<internal:gem_prelude>:76:in `set_paths'
<internal:gem_prelude>:47:in `path'
<internal:gem_prelude>:286:in `push_all_highest_version_gems_on_load_path'
<internal:gem_prelude>:355:in `<compiled>'
remove ../css/app.css
create ../css/app.css
[INF]
[INF] -after-sass:
[INF]
[INF] page:
[INF]
[INF] -before-page:
[INF]
[INF] -page:
[INF] building application
[INF] Deploying your application to E:\program\download\Html5\sencha\myapp\build
\myapp\package
[INF] Copied E:\program\download\Html5\sencha\myapp\app.js to E:\program\downloa
d\Html5\sencha\myapp\build\myapp\package\app.js
[INF] Copied E:\program\download\Html5\sencha\myapp\resources\css\app.css to E:\
program\download\Html5\sencha\myapp\build\myapp\package\resources\css\app.css
[WRN] File or folder E:\program\download\Html5\sencha\myapp\resources\images not
found
[INF] Copied E:\program\download\Html5\sencha\myapp\resources\icons
[INF] Copied E:\program\download\Html5\sencha\myapp\resources\startup
[INF] Resolving your application dependencies (file:///E:/program/download/Html5
/sencha/myapp/index.html)
[INF] Compiling app.js and dependencies
[INF] Loading classpath entry E:\program\download\Html5\sencha\myapp\touch\src
[INF] Loading classpath entry E:\program\download\Html5\sencha\myapp\app.js
[INF] Loading classpath entry E:\program\download\Html5\sencha\myapp\app
[INF] Concatenating output to file E:\program\download\Html5\sencha\myapp\build\
myapp\package\app.js
[INF] Completed compilation.
[INF] Processed remote file touch/sencha-touch.js
[INF] Processed local file app.js
[INF] Minified app.js
[INF] Generated app.json
[INF] Embedded microloader into index.html
[INF] Successfully deployed your application to E:\program\download\Html5\sencha
\myapp\build\myapp\package
[INF]
[INF] -after-page:
[INF]
[INF] run:
[INF]
[INF] -build:
[INF]
[INF] -after-build:
[INF]
[INF] build:
[INF]
[INF] app-build:
</code></pre></div></div>
<p>执行有报错,貌似也没什么多大的影响,查看根目录下 build 文件里面会生成打包后的项目文件。</p>
<p>注意,这个地方的打包并不是说将 web 应用打包成终端的 native 应用,ST 的 cmd 虽然提供打包功能,但打出来的包问题太多,目前并不推荐使用,打包本地应用在后面章节再做介绍,如果实在想看效果的话,网上有几个在线打包工具可以试下。</p>
Sencha Touch 框架简介
2013-09-17T00:00:00+00:00
http://www.blogways.net/blog/2013/09/17/senchatouch-1
<h2 id="sencha-touch-框架简介">Sencha Touch 框架简介</h2>
<p>Sencha Touch(下称ST) 框架是第一个基于 HTML5 的 Mobile App 框架,是 Ext 整合 JQTouch 和 Raphael 而推出的适用于最前沿 Touch Web 的框架,完全基于 HTML5+CSS3 的最新标准,全面兼容 Android 、 Apple IOS 和 BlackBerry 等设备。ST 继承了 Ext 的界面风格,可以让你的 Web App 看起来更像 Native App,其丰富的界面组件、强大的数据管理以及跨浏览器兼容的矢量图将 ST 打造成 Mobile 跨平台开发利器,易扩展,足够应付绝大部分开发需求。</p>
<p>由于苹果对 Flash 的封杀,使得 Flash 无法进入 IOS 的平台,虽然 Flash 对 Android 系统支持得也不错,但对开发者来说还是有些遗憾,Flash 引以为豪的跨平台特性被中止,随着 Webkit 在移动设备上的流行,越来越多的人开始看好 Html5,后来居上的 ST 得到快速的发展。</p>
<h3 id="下面是官方列出的几大特性">下面是官方列出的几大特性:</h3>
<h4 id="1基于最新的-web-标准html5css3javascript">1、基于最新的 web 标准——HTML5,CSS3,JavaScript</h4>
<p>整个库压缩和 gzip 后大约 120KB,通过禁用一些组件还会使它更小。</p>
<h4 id="2支持目前世界上最好的移动设备">2、支持目前世界上最好的移动设备</h4>
<p>Sencha Touch 目前支持 Apple iOS,Android 和 BlackBerry 等目前 3G 市场上最流行的移动设备,它为不同的设备定制了不同的主题,用户可以通过使用这些主题使 web 应用在移动设备上的展现更华丽。</p>
<h4 id="3增强的触摸事件">3、增强的触摸事件</h4>
<p>除了 touchstart,touchend 等标准事件基础外,Sencha Touch 增加了一组自定义触摸事件,如 tap、double tap、swipe、tap and hold、pinch、rotate 等。</p>
<h4 id="4数据集成">4、数据集成</h4>
<p>Sencha Touch 提供了强大的数据包,通过 Ajax、JSONP、YQL 等方式用户可以很容易的从各种各样的数据源拿到数据并绑定到组件模板,写入本地离线存储。</p>
<h3 id="st-功能模块介绍">ST 功能模块介绍:</h3>
<h4 id="1用户界面组件容器和布局组件">1、用户界面组件,容器和布局组件</h4>
<p>Sencha Touch 提供了丰富的用户界面组件,包括常用的按钮,单选框,复选框,文本框,日期选择控件,表格,列表等等,通过运用 Sencha Touch 定制的样式和主题,这些控件在移动设备上看起来和本地应用的 UI 组件没有什么区别。在容器和布局方面,Sencha Touch 也可以和 Adobe 的 Flex 相媲美,不仅提供了基础的 HBoxLayout,VBoxLayout 还提供了 DockLayout,CardLayout,FieldLayout 等更适合开发支持触屏设备的 Mobile web 应用的组件,关于每个组件详细的内容可以参考参考资源中列出的 Sencha Touch 的 API 文档。</p>
<h4 id="2webkitcss3-样式技巧增强">2、WebKit/CSS3 样式技巧增强</h4>
<p>Sencha Touch 充分运用了 CSS3 的新特性使基于 webKit 浏览器运行的 Sencha Touch 应用更炫更酷。它支持并增强了对 Animations(动画),Transitions(转换效果),Gradients(渐变),shadows(阴影效果),Font Face(用户自定义字库),Marquee(文字移动效果),Multiple Backgrounds(多背景),RGBA(高清色彩显示通道)等样式效果的展示。</p>
<h4 id="3数据包">3、数据包</h4>
<p>Sencha Touch 的一个最大特性就是提供了功能强大的数据包。在 Ext.data 包中提供了丰富的 API 实现对 AJAX,JSONP,YQL 等数据访问方式的支持,并提供了 API 供用户更简单方便的操作和展现 JSON 数据, XML 数据等。用户还可以扩展基础接口实现对更复杂数据的操作和访问。Ext.data 数据包中最基础的是 Ext.data.Model,它就像 Java 的 Object 类一样是定义所有对象的基础类,代表应用程序中的数据类型:用户,产品,销售等任何东西。</p>
<h4 id="4数据验证和数据关联">4、数据验证和数据关联</h4>
<p>Sencha Touch 提供了五种基本的数据验证方式,用户可以将验证方式的定义直接和对象的定义绑定,就像定义数据库表的列约束一样方便和简洁。这五种验证方式是:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Presence:验证数据不能为空值。
Length:验证数据的长度,可以定义最大长度和最小长度。
Format:Format验证数据的格式是否符合预定义的格式,比如定义某个时间属性的格式是“yyyy-mm-dd”,那么“20110324”这样的值就被认为是不合法的。
Inclusion:验证数据是否属于某个预定义的范围,该范围可以是一个闭合区间,也可以是一些可能值的集合。
Exclusion:验证数据是否不属于某个预定义的范围。
</code></pre></div></div>
<h3 id="st-的缺点">ST 的缺点:</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1、相比较而言入门门槛较高,有些习惯了 jquery 开发的人不太习惯 st 的编程方式;
2、框架类库文件体积大;
3、执行效率低;
4、有些组件响应有延时;
</code></pre></div></div>
<p>总之,ST 开发简单、兼容性好、支持标准化,提供丰富的界面组件以及强大的数据操作 API,是目前 Mobile Web 开发最成熟的框架,虽然存在一些缺点,相信随着版本的升级会越来越好!</p>
关于express用到的日志库debug的知识点滴
2013-08-24T00:00:00+00:00
http://www.blogways.net/blog/2013/08/24/debug-in-express
<p>使用过<code class="language-plaintext highlighter-rouge">connect</code>或者<code class="language-plaintext highlighter-rouge">express</code>的同学必须要知道<code class="language-plaintext highlighter-rouge">debug</code>。这三个的主要作者都是同一个人。</p>
<p><code class="language-plaintext highlighter-rouge">debug</code>模块使用起来很方便,可以分为下面三步:</p>
<ol>
<li>
<p><strong>在程序中引入<code class="language-plaintext highlighter-rouge">debug</code>时,需要配置一个日志名字空间。</strong>如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> var debug = require('debug')('namespace')
</code></pre></div> </div>
</li>
<li>
<p><strong>代码中使用<code class="language-plaintext highlighter-rouge">debug</code>打印日志。</strong>
debug的内核是使用<code class="language-plaintext highlighter-rouge">console.error</code>来打印日志的。所以,<code class="language-plaintext highlighter-rouge">console</code>支持的通配符<code class="language-plaintext highlighter-rouge">debug</code>都可以使用,比如<code class="language-plaintext highlighter-rouge">'%s'</code>、<code class="language-plaintext highlighter-rouge">'%j'</code>等等。打印<code class="language-plaintext highlighter-rouge">json</code>数据就可以使用通配符<code class="language-plaintext highlighter-rouge">%j</code>,比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> debug("obj:%j", {name:'test'})
</code></pre></div> </div>
<p>但是,使用通配符<code class="language-plaintext highlighter-rouge">%j</code>打印出来的<code class="language-plaintext highlighter-rouge">json</code>格式不太漂亮,看不出缩进。如果为了方便阅读,也可以使用<code class="language-plaintext highlighter-rouge">nodeJS</code>的自带模块<code class="language-plaintext highlighter-rouge">util</code>,举例如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> util = require('util')
debug("object:%s", util.inspect(obj))
</code></pre></div> </div>
<p>这种方式打印出来的<code class="language-plaintext highlighter-rouge">json</code>对象,其格式缩进有度,很容易查看。</p>
</li>
<li>
<p><strong>配置环境变量<code class="language-plaintext highlighter-rouge">DEBUG</code>.</strong>如果仅做了上面两步,运行时默认是没有日志的,必须配置环境变量<code class="language-plaintext highlighter-rouge">DEBUG</code>.比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> export DEBUG=connect*,express*
export DEBUG=*,-send,-connect:dispatcher
</code></pre></div> </div>
<p>需要简单说明一下,多个日志名字空间可以使用<code class="language-plaintext highlighter-rouge">,</code>号或空格分隔,也支持使用<code class="language-plaintext highlighter-rouge">*</code>号来进行通配。日志名字空间前加<code class="language-plaintext highlighter-rouge">-</code>号,标记不打印该类型日志。</p>
</li>
</ol>
<h3 id="上面内容是不是很简单那就试试吧">上面内容是不是很简单,那就试试吧!</h3>
MongoDB 应用架构之路
2013-07-22T00:00:00+00:00
http://www.blogways.net/blog/2013/07/22/mongodb
<h2 id="mongodb-应用架构之路">MongoDB 应用架构之路</h2>
<p>随着大数据时代的到来,传统的关系型数据库已经无法满足不同的存储需求,短短几年的时间NoSQL大行其道,大有后来居上之势。NoSQL根据不同的数据库类型又可以分为:文件存储、键值存储、列存储、图数据库、RTF存储等。本文我们一起来探索下基于文件存储最流行的NoSQL-MongoDB在实际架构中的应用。</p>
<p>MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库中功能最丰富、最像关系数据库的。他支持的数据库结构非常松散,是类似json的bjson格式,可以存储比较复杂的数据类型。Mongo完全遵循javascript语法,语法比较灵活,支持的查询功能非常强大,几乎可以实现关系数据单表查询的绝大部分功能,并且支持对表建立索引。</p>
<h3 id="安装部署">安装部署</h3>
<h4 id="下载">下载</h4>
<p>根据自己的操作系统从官网下载对应的MongoDB版本(32bit/64bit),32位的最大只能存放2G的数据,64位的则没有限制。下载后解压到安装目录。</p>
<h4 id="启动">启动</h4>
<p>到bin目录下执行<code class="language-plaintext highlighter-rouge">mongod --dbpath=..\db</code>,其中dbpath参数为指定创建数据库文件的目录,该目录一定要存在,否则无法启动,如果嫌每次都输入参数太麻烦的话可以在bin目录下创建一个mongodb.config文件,将参数写到文件里面,下次启动的时候就可以直接执行<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config</code>就可以了。</p>
<p>在浏览器中输入<code class="language-plaintext highlighter-rouge">http://localhost:28017/</code>可以查看mongodb的管理信息。</p>
<h4 id="基本数据操作">基本数据操作</h4>
<p>在bin目录下执行mongo打开mongodb的客户端,默认连接的是test数据库。注意操作都是js的语法,可以根据自己的习惯来写。</p>
<h5 id="insert操作">insert操作</h5>
<p>db.user.insert({name:’Jack’,age:30})</p>
<h5 id="select操作">select操作</h5>
<p>db.user.find({name:’Jack’})</p>
<h5 id="update操作">update操作</h5>
<p>db.user.update({name:’Jack’},{name:’Jack’,age:20})</p>
<h5 id="remove操作">remove操作</h5>
<p>db.user.remove({name:’Jack’)}</p>
<p>这些都是最基本的操作,千万别以为就操作就这么简单,要知道js的语法是非常灵活的,要不然怎么实现关系型数据库的单表的大部分功能,但灵活的代价就是语句写起来比较复杂,高级的增删改查、索引、聚合、分类统计、MapReduce、游标等功能就不逐一介绍了,需要大家自己去多实践。</p>
<p>本文主要介绍MongoDB在应用架构中很重要的两个特性:主从复制、分片技术。</p>
<h2 id="架构应用">架构应用</h2>
<p>既然是做应用数据库,我们肯定不希望把鸡蛋全放到一个篮子里面,单点部署是不应该被采用的,要知道如果碰到数据库宕机或者硬盘问题数据被永久破坏影响可想而知。幸好MongoDB可以做到读写分离、双机热备份和集群部署。下面来具体看下如何操作。</p>
<h4 id="一主从复制">一、主从复制</h4>
<h5 id="1模型图">1、模型图</h5>
<p><img src="/images/nosql-1.png" alt="主从复制模型" /></p>
<h5 id="2从上面的模型中我们可以分析出这种架构有如下好处">2、从上面的模型中我们可以分析出这种架构有如下好处:</h5>
<p>1) 数据备份;</p>
<p>2) 数据恢复;</p>
<p>3) 读写分离;</p>
<h5 id="3实践">3、实践</h5>
<p>1) 我们把前面下载的安装文件再拷贝两份到其他的目录,模拟多服务器情况;</p>
<p>2) 启动一个作为主服务的mongodb,启动主服务器命令<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config --master</code>,原来单启服务加了一个master参数,其他都没有变化,包括端口;</p>
<p>3) 启动另外一个mongodb服务作为从服务器,可以启多个,注意同一台机器需要换个端口,启动从服务器命令<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config --port=8888 --slave --source=127.0.0.1:27017</code>;</p>
<p>4) 下面可以在两个数据库随便做点操作,我们可以看到数据在两个服务器上是同步更新的;</p>
<h5 id="4读写分离">4、读写分离</h5>
<p>这种手段在大一点的架构中都有实现,在mongodb中其实很简单,在默认的情况下,从属数据库不支持数据的读取,但是没关系,在驱动中给我们提供了一个叫做“slaveOkay”来让我们可以显示的读取从属数据库来减轻主数据库的性能压力,这里就不演示了。</p>
<h4 id="二副本集">二、副本集</h4>
<h5 id="1这个也是很牛的主从集群不过跟上面的主从集群还是有点区别">1、这个也是很牛的主从集群,不过跟上面的主从集群还是有点区别:</h5>
<p>1) 该集群没有特定的主数据库;</p>
<p>2) 如果主数据库宕机了,集群中会推选出一个从属数据库作为主数据库顶上,这就具备了自动故障恢复功能;</p>
<h5 id="2实践">2、实践</h5>
<p>下面的操作模拟三个mongodb的服务器,为了方便描述,这边起三个名字A(端口2222)、B(端口3333)、C(端口4444),其中设计A为集群主服务器、B为从服务器,C为仲裁服务器,所有操作都是在当前安装路径bin目录下执行的,开始操作如下:</p>
<p>1) 建立A服务器,<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config --port 2222 --replSet ailk/127.0.0.1:3333</code>,replSet参数表示让服务器知道还有其他的数据库,指定端口3333的B服务器为下一个数据库;</p>
<p>2) 建立B服务器,<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config --port 3333 --replSet ailk/127.0.0.1:2222</code>,解释同上;</p>
<p>3) 工作还没有完成,这个时候看下启动日志发现不停的在报<code class="language-plaintext highlighter-rouge">replSet can't get local.system.replSet ...</code>的错误,我们需要初始化一下“副本集”,随便连一个服务器,进入admin集合。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>D:\Program Files\mongodb-win32-x86_64-2.4.5\bin>mongo 127.0.0.1:2222/admin
MongoDB shell version: 2.4.5
connecting to: 127.0.0.1:2222/admin
> db.runCommand({replSetInitiate:{
... _id:'ailk',
... members:[
... {
... _id:1,
... host:'127.0.0.1:2222'
... },
... {
... _id:2,
... host:'127.0.0.1:3333'
... }
... ]}})
{
"info" : "Config now saved locally. Should come online in about a minute.",
"ok" : 1
}
</code></pre></div></div>
<p>这时候再看日志已经可以发现主从服务器都已经自动创建好了;</p>
<p>4) 创建仲裁服务器 C,<code class="language-plaintext highlighter-rouge">mongod --config mongodb.config --port 4444 --replSet ailk/127.0.0.1:2222</code>,在A admin集合中执行rs.addArb()追加即可。这里面需要说明下,仲裁服务器只参与投票选举。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>> rs.addArb('127.0.0.1:4444')
{ "ok" : 1 }
</code></pre></div></div>
<p>5) 现在我们使用rs.status()来看下集群服务器的状态</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ailk:PRIMARY> rs.status()
{
"set" : "ailk",
"date" : ISODate("2013-07-23T08:29:54Z"),
"myState" : 1,
"members" : [
{
"_id" : 1,
"name" : "127.0.0.1:2222",
"health" : 1,
"state" : 1,
"stateStr" : "PRIMARY",
"uptime" : 1360,
"optime" : Timestamp(1374568146, 1),
"optimeDate" : ISODate("2013-07-23T08:29:06Z"),
"self" : true
},
{
"_id" : 2,
"name" : "127.0.0.1:3333",
"health" : 1,
"state" : 2,
"stateStr" : "SECONDARY",
"uptime" : 189,
"optime" : Timestamp(1374568146, 1),
"optimeDate" : ISODate("2013-07-23T08:29:06Z"),
"lastHeartbeat" : ISODate("2013-07-23T08:29:53Z"),
"lastHeartbeatRecv" : ISODate("2013-07-23T08:29:52Z"),
"pingMs" : 0,
"syncingTo" : "127.0.0.1:2222"
},
{
"_id" : 3,
"name" : "127.0.0.1:4444",
"health" : 1,
"state" : 7,
"stateStr" : "ARBITER",
"uptime" : 48,
"lastHeartbeat" : ISODate("2013-07-23T08:29:54Z"),
"lastHeartbeatRecv" : ISODate("2013-07-23T08:29:53Z"),
"pingMs" : 0
}
],
"ok" : 1
}
</code></pre></div></div>
<p>看下stateStr节点就可以看出谁是主服务器、谁是从服务器、谁是仲裁服务器。</p>
<p>6) 最后来看下自动故障恢复功能,我们把主服务器 A 停掉,进入 B admin 集合执行 rs.status(),可以发现 B 服务器现在变成了主服务器了。</p>
<h3 id="三分片">三、分片</h3>
<p>有时候数据量比较大的时候我们需要把数据进行拆分,再把拆分后的数据分摊到每一个片上,这个地方有个“片键”的概念,也就是说拆分集合的依据是什么,按照什么键值进行拆分集合?</p>
<h4 id="1-模型图">1、 模型图</h4>
<p><img src="/images/nosql-2.png" alt="分片服务器模型" /></p>
<p>说明下:</p>
<p>mongos:就是一个路由服务器,它会根据管理员设置的“片键”将数据分摊到自己管理的mongod集群,数据和片的对应关系以及相应的配置信息保存在”config服务器”上;</p>
<p>mongod:就是一个普通的数据库实例。</p>
<h4 id="2-实践">2、 实践</h4>
<p>根据上图可以看出来,这里需要用到4个mongodb的服务器,mogos服务器、config服务器、两个mogod单服务器;</p>
<p>1) 开启config服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongod --config mongodb.config --port 2222
</code></pre></div></div>
<p>2) 开启mongos服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongos --port 3333 --configdb=127.0.0.1:2222
</code></pre></div></div>
<p>3) 启动mogod服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongod --config mongodb.config --port 4444
mongod --config mongodb.config --port 5555
</code></pre></div></div>
<p>4) mogos服务器配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>E:\mongodb-win32-x86_64-2.4.5\bin>mongo 127.0.0.1:3333/admin
MongoDB shell version: 2.4.5
connecting to: 127.0.0.1:3333/admin
mongos> db.runCommand({addshard : "localhost:4444", allowLocal : true})
{ "shardAdded" : "shard0000", "ok" : 1 }
mongos> db.runCommand({addshard : "localhost:5555", allowLocal : true})
{ "shardAdded" : "shard0001", "ok" : 1 }
</code></pre></div></div>
<p>这个时候已经为mongos服务器成功添加了两个mogod节点。</p>
<p>5) 开启数据库分片功能,指定集合中分片的片键</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongos> db.runCommand({enablesharding:'test'})
{ "ok" : 1 }
mongos> db.runCommand({shardcollection:'test.person',key:{name:1}})
{ "collectionsharded" : "test.person", "ok" : 1 }
</code></pre></div></div>
<p>6) 测试分片效果,插入10w条记录</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mongos> use test
switched to db test
mongos> for(var i = 0; i < 100000; i++) {
... db.person.insert({name:'jacky'+i,age:i})
... }
mongos> db.printShardingStatus()
--- Sharding Status ---
sharding version: {
"_id" : 1,
"version" : 3,
"minCompatibleVersion" : 3,
"currentVersion" : 4,
"clusterId" : ObjectId("51ee5bc0cdc1e6433d02a9a6")
}
shards:
{ "_id" : "shard0000", "host" : "localhost:4444" }
{ "_id" : "shard0001", "host" : "localhost:5555" }
databases:
{ "_id" : "admin", "partitioned" : false, "primary" : "config" }
{ "_id" : "test", "partitioned" : true, "primary" : "shard0000" }
test.person
shard key: { "name" : 1 }
chunks:
shard0000 2
shard0001 1
{ "name" : { "$minKey" : 1 } } -->> { "name" : "jacky0" } on : shard0000 Timestamp(2, 1)
{ "name" : "jacky0" } -->> { "name" : "jacky9999" } on : shard0000 Timestamp(1, 3)
{ "name" : "jacky9999" } -->> { "name" : { "$maxKey" : 1 } } on : shard0001 Timestamp(2, 0)
</code></pre></div></div>
<p>可以看出shards已经是两个分片了,另外默认集合被分成了三段:无穷小->jacky0;jacky0-jacky9999;jacky9999-无穷大。但这个结果并不理想,因为他是按字母来排序的,也就是2库只分到了11条记录,绝大部分被分到了一库,不过分片的原理就是这样,具体的应用还是根据实际的需求来规划数据分键。</p>
<h3 id="总结">总结</h3>
<p>大概也讲了不少了,NoSQL的强大也可见一斑,相比较以往关系型数据库而言NoSQL确实有很多优势,存储灵活、操作简单、性能高、易扩展等,但不是不要以为就可以完全替换关系型数据库,毕竟二者的应用场景不一样,没有最好的,只有最合适的。</p>
<p>另外MongoDB也是有自身的缺点的,下面大概说下他几个主要的缺点:</p>
<h4 id="1-mongodb占用存储空间大">1) mongodb占用存储空间大。</h4>
<p>这主要是由三个因素决定的。第一,mongodb的空间预分配方式,这样会让mongodb最多浪费不超过2G+2G文件大小的空间。第二,mongodb的字段名占用,即使是相同的字段,mongodb也会在每一个文档中都存储,这里会浪费极大的空间。第三,mongodb删除数据并不会释放空间,而只是将空间记录为删除状态以便重用。</p>
<h4 id="2-mongodb没有事务模式">2) mongodb没有事务模式</h4>
<p>所以事务要求严格的系统慎用。</p>
<h4 id="3-mongodb没有join">3) mongodb没有join</h4>
<p>毕竟数据都是有关联的,虽然也能通过一定的方式变向的实现一些简单的连接功能,但对于业务复杂的场景也不适合。</p>
hp-ux下编译64位Lua
2013-07-18T00:00:00+00:00
http://www.blogways.net/blog/2013/07/18/hp-ux-lua-64
<p>在之前的一篇<a href="../../06/20/aix-64-lua.html">文章</a>中,我们介绍了IBM AIX下编译64为Lua代码。在这里,我们介绍如何在HP-UX下编译64为Lua源码。</p>
<p>目前,官方默认支持的编译平台是<code class="language-plaintext highlighter-rouge">aix ansi bsd freebsd generic linux macosx mingw posix solaris</code></p>
<p>没关系,我们来修改官方的<code class="language-plaintext highlighter-rouge">Makefile</code>文件,让他支持<code class="language-plaintext highlighter-rouge">hp-ux</code>。</p>
<p>修改一:增加支持的平台代码 <code class="language-plaintext highlighter-rouge">hp</code></p>
<p>源码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris
</code></pre></div></div>
<p>修改为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>PLATS= aix ansi bsd freebsd generic linux macosx mingw posix solaris hp
</code></pre></div></div>
<p>修改二:增加编译命令</p>
<p>在源码中增加:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hp:
$(MAKE) all CC="aCC -AC99 +DD64 -z +Z +DSblended" CFLAGS="-O -DLUA_USE_POSIX -DLUA_USE_DLOPEN" MYLIBS="-ldl" MYLDFLAGS="-Wl,+s -Wl,+n"
</code></pre></div></div>
<p><strong>Ok啦,可以编译了!</strong></p>
<p>编译吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>make hp
</code></pre></div></div>
jBPM5 vs Actitivi
2013-07-16T00:00:00+00:00
http://www.blogways.net/blog/2013/07/16/activiti-jbpm-compare
<h2 id="jbpm5-vs-actitivi">jBPM5 vs Actitivi</h2>
<p>jBPM是目前市场上主流开源工作引擎之一,在创建者Tom Baeyens离开JBoss后,jBPM的下一个版本jBPM5完全放弃了jBPM4的基础代码,基于Drools Flow重头来过,目前官网已经推出了jBPM6的beta版本;Tom Baeyens加入Alfresco后很快推出了新的基于jBPM4的开源工作流系统Activiti。由此可以推测JBoss内部对jBPM未来版本的架构实现产生了严重的意见分歧。本文试着对二者做一些比较。</p>
<p>在比较之前先看下两者的安装部署过程:</p>
<h3 id="jbpm5安装及开发环境配置">jBPM5安装及开发环境配置</h3>
<p>1、安装JBPM之前,要求本机已安装了<code class="language-plaintext highlighter-rouge">JDk1.5+</code>版本和<code class="language-plaintext highlighter-rouge">ANT1.7+</code>版本。</p>
<p>2、JDK和ANT都安装完毕之后,到<code class="language-plaintext highlighter-rouge">http://sourceforge.net/projects/jbpm/files/</code>下载JBPM-installer,下载完之后,解压到安装目录,jbpm-installer文件夹里有个install.html,里面有英文版的安装教程,可以作为参考。</p>
<p>3、在CMD下进入刚才的\jbpm-installer目录,运行<code class="language-plaintext highlighter-rouge">ant install.demo</code>,该命令会执行下面一系列的操作:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>下载JBoss AS
下载Eclipse
安装Drools Guvnor 到JBoss AS
安装Oryx Designer 到JBoss AS
安装jBPM Console 到JBoss AS
安装jBPM Eclipse 插件
安装Drools Eclipse 插件
</code></pre></div></div>
<p>注意:这边下载的东西比较多,有好几百兆,如果本地网络不怎么好或者有些东西机上已经有了的话就可以单独下载需要的</p>
<p>例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant install.jBPM.runtime
ant install.guvnor.into.jboss
ant install.designer.into.jboss
ant install.jBPM-gwt-console.into.jboss
ant install.droolsjbpm-eclipse.into.eclipse
</code></pre></div></div>
<p>4、下面可以准备启动JBPM了。CMD到\jbpm-installer目录下,依次运行以下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ant start.h2 (启动h2数据库)
ant start.jboss (启动JBoss AS)
ant start.human.task (启动 task service)
</code></pre></div></div>
<p>5、Jboss启动之后,可以在http://localhost:8080/访问,jbpm自带的web控制台 http://localhost:8080/jbpm-console,登录的用户名、密码均为krisv,在web控制台中可启动一个新流程、查看正在执行流程的当前状态、查看当前登录人待办任务以及并可以以报表形式查看、跟踪流程状态。
Drools Guvnor的访问地址为:http://localhost:8080/drools-guvnor。</p>
<p>6、把eclipse目录下的features和plugins中的内容copy到eclipse的相应目录中。启动Eclipse之后,现在可以使用eclipse导入jbpm自带的一个流程。方法为依次点击File -> Import ,在General category下选择“Existing Projects into Workspace”,找到位于jbpm安装根目录/sample/evaluation文件夹,将该项目导入。</p>
<h3 id="activiti安装及开发环境配置">Activiti安装及开发环境配置</h3>
<p>1、 到<code class="language-plaintext highlighter-rouge">http://www.activiti.org/download.html</code>下载activiti-5.13.zip,解压到安装目录,页面上有个<code class="language-plaintext highlighter-rouge">The User Guide</code>,这个教程比较详细,如需要可深入学习下。</p>
<p>2、 打开解压目录,\wars下面有两个war包,把<code class="language-plaintext highlighter-rouge">activiti-explorer.war</code>部署到应用服务器中,里面默认的数据源是h2的内存数据库,如需要改成自己的数据库;</p>
<p>3、 安装完成后可以在<code class="language-plaintext highlighter-rouge">http://localhost:8080/activiti-explorer</code>处访问 Activiti Explorer web 应用程序,id/pwd: kermit/Kermit(这个账号是administrator),这个程序是流程引擎的用户接口,用户可以使用这个工具来执行启动新流程,分配用户任务,浏览或领取任务等操作。还可以用来执行 Activiti 引擎的管理工作;</p>
<p>4、 Activiti 提供了基于 Eclipse 插件的开发工具和流程设计工具 ( 需要 Eclipse 的版本为 Helios 或 Indigo,如果尚未安装 Eclipse,请从 http://www.eclipse.org/downloads/下载安装最新版本的 Eclipse 集成开发环境。)。这些工具可以使用 Eclipse 的”Install new software”功能在线安装,安装方法如下:</p>
<p>在 Eclipse 的 Help 菜单中选择 Install New Software 选项,在弹出菜单中,点击 Add Repository 按钮添加新的远程 Software Repository,如图 3 所示,在 Location 中添加 http://activiti.org/designer/update/ 作为 Repository 的远程地址。当新的 Repository 添加完成后,Eclipse 会自动获取 Repository 中的软件列表。</p>
<p>5、 现在我们开始创建工作流。右键点击项目根目录,选择new -> others,选择Activiti -> Activiti Diagram。</p>
<p>流程开发跟部署就不在这边说了,都是界面化的开发工具,两个都可以保存成同样的格式,也可以用同一个插件来开发。</p>
<h3 id="jbpm5与activiti5比较">jBPM5与Activiti5比较</h3>
<h4 id="主要相似之处">主要相似之处:</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>都是BPMN2过程建模和执行环境。
都是BPM系统(符合BPM规范)。
都是开源项目-遵循ASL协议( Apache的 软件许可)。
都源自JBoss(Activiti5是jBPM4的衍生,jBPM5则基于Drools Flow)。
都很成熟,从无到有,双方开始约始于2年半前。
都有对人工任务的生命周期管理。 Activiti5和jBPM5唯一的区别是jBPM5基于WebService - HumanTask标准来描述人工任务和管理生命周期。 如有兴趣了解这方面的标准及其优点,可参阅WS - HT规范介绍 。
都使用了不同风格的 Oryx 流程编辑器对BPMN2建模。 jBPM5采用的是 Intalio 维护的开源项目分支。 Activiti5则使用了Signavio维护的分支。
</code></pre></div></div>
<h4 id="activiti5与jbpm5技术组成对比">Activiti5与jBPM5技术组成对比</h4>
<table>
<tbody>
<tr><td><em>序号</em></td><td><em>技术组成</em></td><td><em>Activiti</em></td><td><em>jBPM5</em></td></tr>
<tr><td>1</td><td>数据库持久层ORM</td><td>MyBatis3</td><td>Hibernate3</td></tr>
<tr><td>2</td><td>持久化标准</td><td>无</td><td>JPA规范</td></tr>
<tr><td>3</td><td>事务管理</td><td>MyBatis机制/Spring事务控制</td><td>Bitronix,基于JTA事务管理</td></tr>
<tr><td>4</td><td>数据库连接方式</td><td>Jdbc/DataSource</td><td>Jdbc/DataSource</td></tr>
<tr><td>5</td><td>支持数据库</td><td>Oracle、SQL Server、MySQL等多数数据库</td><td>Oracle、SQL Server、MySQL等多数数据库</td></tr>
<tr><td>6</td><td>设计模式</td><td>Command模式、观察者模式等</td><td></td></tr>
<tr><td>7</td><td>内部服务通讯</td><td>Service间通过API调用</td><td>基于Apache Mina异步通讯</td></tr>
<tr><td>8</td><td>集成接口</td><td>SOAP、Mule、RESTful</td><td>消息通讯</td></tr>
<tr><td>9</td><td>支持的流程格式</td><td>BPMN2、xPDL、jPDL等</td><td>目前仅只支持BPMN2 xml</td></tr>
<tr><td>10</td><td>引擎核心</td><td>PVM(流程虚拟机)</td><td>Drools</td></tr>
<tr><td>11</td><td>技术前身</td><td>jBPM3、jBPM4</td><td>Drools Flow</td></tr>
<tr><td>12</td><td>所属公司</td><td>Alfresco</td><td>jBoss.org</td></tr>
</tbody>
</table>
<p>Activiti5使用Spring进行引擎配置以及各个Bean的管理,综合使用IoC和AOP技术,使用CXF作为Web Services实现的基础,使用MyBatis进行底层数据库ORM的管理,预先提供Bundle化包能较容易的与OSGi进行集成,通过与Mule ESB的集成和对外部服务(Web Service、RESTful等)的接口可以构建全面的SOA应用;jBPM5使用jBoss.org社区的大多数组件,以Drools Flow为核心组件作为流程引擎的核心构成,以Hibernate作为数据持久化ORM实现,采用基于JPA/JTA的可插拔的持久化和事务控制规范,使用Guvnor作为流程管理仓库,能够与Seam、Spring、OSGi等集成。</p>
<p>需要指出的是Activiti5是在jBPM3、jBPM4的基础上发展而来的,是原jBPM的延续,而jBPM5则与之前的jBPM3、jBPM4没有太大关联,且舍弃了备受推崇的PVM(流程虚拟机)思想,转而选择jBoss自身产品Drools Flow作为流程引擎的核心实现,工作流最为重要的“人机交互”任务(类似于审批活动)则由单独的一块“Human Task Service”附加到Drools Flow上实现,任务的查询、处理等行为通过Apache Mina异步通信机制完成。</p>
<h4 id="优劣对比">优劣对比:</h4>
<p>从技术组成来看,Activiti最大的优势是采用了PVM(流程虚拟机),支持除了BPMN2.0规范之外的流程格式,与外部服务有良好的集成能力,延续了jBPM3、jBPM4良好的社区支持,服务接口清晰,链式API更为优雅;劣势是持久化层没有遵循JPA规范。</p>
<p>jBPM最大的优势是采用了Apache Mina异步通信技术,采用JPA/JTA持久化方面的标准,以功能齐全的Guvnor作为流程仓库,有RedHat(jBoss.org被红帽收购)的专业化支持;但其劣势也很明显,对自身技术依赖过紧且目前仅支持BPMN2。</p>
<h3 id="总结">总结</h3>
<p>虽然是比较,但不一定要有胜负,只有适合自己的才是最好的,要针对具体的项目区别对待。对我们自己的项目,其实我更关注的是流程引擎的执行效率以及性能,每小时几十万甚至上百万的流程需要执行,需要多少个服务,集群、负载的策略是什么,会不会有冲突?目前这方面的资料还是比较少的,很多问题只有实际遇用到的时候才会去想办法解决。不过就我个人的感觉而言,Activiti上手比较快,界面也比较简洁、直观,值得一试,不过jBPM6的beta版也已经出来了,不知道会有什么变化,有兴趣的也可以试下。</p>
<table>
<tbody>
<tr>
<td>参考</td>
<td>推荐文章:</td>
</tr>
</tbody>
</table>
<p><code class="language-plaintext highlighter-rouge">http://blog.csdn.net/howareyoutodaysoft/article/details/8070068 </code> «BPMN2,activiti,jbpm5学习资料»</p>
<p><code class="language-plaintext highlighter-rouge">http://www.infoq.com/cn/articles/rh-jbpm5-activiti5</code> «纵观jBPM:从jBPM3到jBPM5以及Activiti5»</p>
AIX下安装cppunit记
2013-06-26T00:00:00+00:00
http://www.blogways.net/blog/2013/06/26/aix-cppunit
<h3 id="一下载">一、下载</h3>
<p>从官网下载cppunit,笔者当前使用版本为1.12.1。</p>
<p>下载地址为:<a href="http://downloads.sourceforge.net/project/cppunit/cppunit/1.12.1/cppunit-1.12.1.tar.gz">http://downloads.sourceforge.net/project/cppunit/cppunit/1.12.1/cppunit-1.12.1.tar.gz</a></p>
<h3 id="二解压">二、解压</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gzip -d cppunit-1.12.1.tar.gz
tar -xvf cppunit-1.12.1.tar
</code></pre></div></div>
<h3 id="三编译">三、编译</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#进入解压后的源码目录
cd cppunit-1.12.1
#在aix下面编译,目前不支持生成动态库,仅生成的是静态库。
./configure --disable-shared
#也可以添加prefix参数,指定编译后需要安装的目录
./configure --disable-shared --prefix=……
#编译
make
#安装
make install
</code></pre></div></div>
<p>在执行<code class="language-plaintext highlighter-rouge">configure</code>时,如果出现了<code class="language-plaintext highlighter-rouge">configure: error: C compiler cannot create executables</code>这种错误,需要检查一下<code class="language-plaintext highlighter-rouge">config.log</code>文件,看看是不是参数啥的配置错误了。</p>
<p><strong>总的来说,<code class="language-plaintext highlighter-rouge">cppunit</code>在<code class="language-plaintext highlighter-rouge">aix</code>下面安装还是相当顺利的。</strong></p>
<p>如果想在<code class="language-plaintext highlighter-rouge">aix</code>上编译生成64位的目标,配置命令如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure --disable-shared "LDFLAGS=-q64" "CFLAGS=-q64" "CXXFLAGS=-q64" "AR_FLAGS=-X64 cru"
</code></pre></div></div>
<p>如果是在hp下安装,相关命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>./configure --enable-hpuxshl CC=cc CXX=aCC CXXFLAGS="-AA"
</code></pre></div></div>
AIX下编译redis代码
2013-06-21T00:00:00+00:00
http://www.blogways.net/blog/2013/06/21/aix-redis
<p>在IBM AIX平台下编译redis还是比较顺利的. 我没有直接使用官方的<code class="language-plaintext highlighter-rouge">Makefile</code>,参考了下官方<code class="language-plaintext highlighter-rouge">Makefile</code>中的编译依赖关系,按照平时常用的格式自己写了个<code class="language-plaintext highlighter-rouge">Makefile</code>文件。</p>
<p>首先,编译<code class="language-plaintext highlighter-rouge">hiredis</code>时,主要在编译命令中定义了三个宏:<code class="language-plaintext highlighter-rouge">__HIREDIS_FMACRO_H</code>、<code class="language-plaintext highlighter-rouge">_BSD_SOURCE</code>、<code class="language-plaintext highlighter-rouge">AF_LOCAL=AF_UNIX</code>,编译就顺利通过了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-D__HIREDIS_FMACRO_H -D_BSD_SOURCE -DAF_LOCAL=AF_UNIX
</code></pre></div></div>
<p>编译服务器代码,稍微麻烦点,因为编译过程中会提示</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>"redis.h", line 502.9: 1506-275 (S) Unexpected text integer constant encountered.
</code></pre></div></div>
<p>这是因为,<code class="language-plaintext highlighter-rouge">redis.h</code>中结构体<code class="language-plaintext highlighter-rouge">redisServer</code>用到了一个名为<code class="language-plaintext highlighter-rouge">hz</code>的成员,而<code class="language-plaintext highlighter-rouge">hz</code>这玩意在aix平台下被定义过。至此,编译就报错了。</p>
<p>我在文件中加了<code class="language-plaintext highlighter-rouge">#undef hz</code>,解决了。</p>
<p>同样,编译服务器代码,采用相同的思路,这次添加了四个宏定义,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>-D_XOPEN_SOURCE_EXTENDED=1 -D_ISOC99_SOURCE -D_ALL_SOURCE -DAF_LOCAL=AF_UNIX
</code></pre></div></div>
<p>以上修改后,redis的服务器代码和客户端代码的编译顺利通过!</p>
AIX下编译64位Lua
2013-06-20T00:00:00+00:00
http://www.blogways.net/blog/2013/06/20/aix-64-lua
<p>Lua是一个非常有名的脚本语言,是使用标准C编写而成的,目前官方提供了其在众多平台下的编译的<code class="language-plaintext highlighter-rouge">Makefile</code>文件,非常强大!</p>
<p>支持编译的平台有<code class="language-plaintext highlighter-rouge">aix ansi bsd freebsd generic linux macosx mingw posix solaris</code></p>
<p>我在AIX下小试了一下,非常顺利地就编译通过了。</p>
<p>不过默认编译出来的是32位的,其库文件不能被64位的程序调用,需要修改一下其<code class="language-plaintext highlighter-rouge">Makefile</code>文件,就可以编译出64位版本。</p>
<p>修改<code class="language-plaintext highlighter-rouge">Makefile</code>需要考虑其原有的结构,不影响其他平台下的命令的执行。</p>
<p>故此,编辑<code class="language-plaintext highlighter-rouge">Makefile</code>:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cd lua-5.2.2/src
vi Makefile
</code></pre></div></div>
<p>修改其中<code class="language-plaintext highlighter-rouge">aix</code>小节如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>aix:
$(MAKE) $(ALL) CC="xlc -q64" AR="ar -X64 rcu" CFLAGS="-O2 -DLUA_USE_POSIX -DLUA_USE_DLOPEN" SYSLIBS="-ldl" SYSLDFLAGS="-brtl -bexpall"
</code></pre></div></div>
<p>编译出来的目标文件就是64位的了!</p>
<p>后面我们会介绍如何在<a href="../../07/18/hp-ux-lua-64.html"><code class="language-plaintext highlighter-rouge">hp-ux</code></a>环境下编译Lua源码。</p>
Junit4测试类中测试方法的执行顺序
2013-06-02T00:00:00+00:00
http://www.blogways.net/blog/2013/06/02/junit-test-execution-order
<p>我正在使用的Junit的版本为4.11,其中测试类的执行顺序有三种指定方式:默认、按方法名顺序和JVM顺序。</p>
<p>如果你不做任何指定,那么就是由默认顺序来执行,那么默认顺序是怎么确定的?他是由方法名的hash值的大小来确定,如果hash值大小一致,则按名字顺序确定。</p>
<p>看下面Junit的实现代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/**
* DEFAULT sort order
*/
public static Comparator<Method> DEFAULT = new Comparator<Method>() {
public int compare(Method m1, Method m2) {
int i1 = m1.getName().hashCode();
int i2 = m2.getName().hashCode();
if (i1 != i2) {
return i1 < i2 ? -1 : 1;
}
return NAME_ASCENDING.compare(m1, m2);
}
};
</code></pre></div></div>
<p>除了默认顺序,我们可以指定按方法的名字顺序来执行。指定方法是在测试类上加一个注释<code class="language-plaintext highlighter-rouge">FixMethodOrder(MethodSorters.NAME_ASCENDING)</code>,代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class MyTest {
@Test
public void test2() {
...
}
@Test
public void test1() {
…
}
}
</code></pre></div></div>
<p>这样执行顺序就是,先<code class="language-plaintext highlighter-rouge">test1</code>再<code class="language-plaintext highlighter-rouge">test2</code>。</p>
<p>除了上述两种顺序,还有<code class="language-plaintext highlighter-rouge">JVM</code>顺序,<code class="language-plaintext highlighter-rouge">JVM</code>顺序使之按调用反射API的顺序来执行。什么意思?看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Method[] methods = testClass.getDeclaredMethods();
</code></pre></div></div>
<p>也即是说,通过上面语句获取测试类的方法,返回的顺序就是测试的顺序。这种顺序,会由JVM的实现不同而顺序不同。一般情况下,是一个未明确的但是固定的顺序。</p>
<p>使用时,也是在测试类上加一个注释<code class="language-plaintext highlighter-rouge">FixMethodOrder(MethodSorters.JVM)</code>,方法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@FixMethodOrder(MethodSorters.JVM)
public class MyTest {
@Test
public void test2() {
...
}
@Test
public void test1() {
…
}
}
</code></pre></div></div>
<p>在我的机器上,上面代码执行顺序是:先<code class="language-plaintext highlighter-rouge">test2</code>再<code class="language-plaintext highlighter-rouge">test1</code>。</p>
<p>通过上面的了解,如果你想指定你的测试类中方法的执行顺序,那么最好使用名字顺序(<code class="language-plaintext highlighter-rouge">MethodSorters.NAME_ASCENDING</code>),这样最方便最可靠。</p>
Redis的Java客户端Jedis的八种调用方式(事务、管道、分布式…)介绍
2013-06-02T00:00:00+00:00
http://www.blogways.net/blog/2013/06/02/jedis-demo
<p>redis是一个著名的key-value存储系统,而作为其官方推荐的java版客户端jedis也非常强大和稳定,支持事务、管道及有jedis自身实现的分布式。</p>
<p>在这里对jedis关于事务、管道和分布式的调用方式做一个简单的介绍和对比:</p>
<h3 id="一普通同步方式">一、普通同步方式</h3>
<p>最简单和基础的调用方式,</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test1Normal() {
Jedis jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = jedis.set("n" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
}
</code></pre></div></div>
<p>很简单吧,每次<code class="language-plaintext highlighter-rouge">set</code>之后都可以返回结果,标记是否成功。</p>
<h3 id="二事务方式transactions">二、事务方式(Transactions)</h3>
<p>redis的事务很简单,他主要目的是保障,一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。</p>
<p>看下面例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test2Trans() {
Jedis jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
Transaction tx = jedis.multi();
for (int i = 0; i < 100000; i++) {
tx.set("t" + i, "t" + i);
}
List<Object> results = tx.exec();
long end = System.currentTimeMillis();
System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
}
</code></pre></div></div>
<p>我们调用<code class="language-plaintext highlighter-rouge">jedis.watch(…)</code>方法来监控key,如果调用后key值发生变化,则整个事务会执行失败。另外,事务中某个操作失败,并不会回滚其他操作。这一点需要注意。还有,我们可以使用<code class="language-plaintext highlighter-rouge">discard()</code>方法来取消事务。</p>
<h3 id="三管道pipelining">三、管道(Pipelining)</h3>
<p>有时,我们需要采用异步方式,一次发送多个指令,不同步等待其返回结果。这样可以取得非常好的执行效率。这就是管道,调用方法如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test3Pipelined() {
Jedis jedis = new Jedis("localhost");
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("p" + i, "p" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
}
</code></pre></div></div>
<h3 id="四管道中调用事务">四、管道中调用事务</h3>
<p>就Jedis提供的方法而言,是可以做到在管道中使用事务,其代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test4combPipelineTrans() {
jedis = new Jedis("localhost");
long start = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
pipeline.multi();
for (int i = 0; i < 100000; i++) {
pipeline.set("" + i, "" + i);
}
pipeline.exec();
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds");
jedis.disconnect();
}
</code></pre></div></div>
<p>但是经测试(见本文后续部分),发现其效率和单独使用事务差不多,甚至还略微差点。</p>
<h3 id="五分布式直连同步调用">五、分布式直连同步调用</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test5shardNormal() {
List<JedisShardInfo> shards = Arrays.asList(
new JedisShardInfo("localhost",6379),
new JedisShardInfo("localhost",6380));
ShardedJedis sharding = new ShardedJedis(shards);
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = sharding.set("sn" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds");
sharding.disconnect();
}
</code></pre></div></div>
<p>这个是分布式直接连接,并且是同步调用,每步执行都返回执行结果。类似地,还有异步管道调用。</p>
<h3 id="六分布式直连异步调用">六、分布式直连异步调用</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test6shardpipelined() {
List<JedisShardInfo> shards = Arrays.asList(
new JedisShardInfo("localhost",6379),
new JedisShardInfo("localhost",6380));
ShardedJedis sharding = new ShardedJedis(shards);
ShardedJedisPipeline pipeline = sharding.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sp" + i, "p" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds");
sharding.disconnect();
}
</code></pre></div></div>
<h3 id="七分布式连接池同步调用">七、分布式连接池同步调用</h3>
<p>如果,你的分布式调用代码是运行在线程中,那么上面两个直连调用方式就不合适了,因为直连方式是非线程安全的,这个时候,你就必须选择连接池调用。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test7shardSimplePool() {
List<JedisShardInfo> shards = Arrays.asList(
new JedisShardInfo("localhost",6379),
new JedisShardInfo("localhost",6380));
ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);
ShardedJedis one = pool.getResource();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = one.set("spn" + i, "n" + i);
}
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds");
pool.destroy();
}
</code></pre></div></div>
<p>上面是同步方式,当然还有异步方式。</p>
<h3 id="八分布式连接池异步调用">八、分布式连接池异步调用</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void test8shardPipelinedPool() {
List<JedisShardInfo> shards = Arrays.asList(
new JedisShardInfo("localhost",6379),
new JedisShardInfo("localhost",6380));
ShardedJedisPool pool = new ShardedJedisPool(new JedisPoolConfig(), shards);
ShardedJedis one = pool.getResource();
ShardedJedisPipeline pipeline = one.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sppn" + i, "n" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds");
pool.destroy();
}
</code></pre></div></div>
<h3 id="九需要注意的地方">九、需要注意的地方</h3>
<ol>
<li>
<p>事务和管道都是异步模式。在事务和管道中不能同步查询结果。比如下面两个调用,都是不允许的:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Transaction tx = jedis.multi();
for (int i = 0; i < 100000; i++) {
tx.set("t" + i, "t" + i);
}
System.out.println(tx.get("t1000").get()); //不允许
List<Object> results = tx.exec();
…
…
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("p" + i, "p" + i);
}
System.out.println(pipeline.get("p1000").get()); //不允许
List<Object> results = pipeline.syncAndReturnAll();
</code></pre></div> </div>
</li>
<li>事务和管道都是异步的,个人感觉,在管道中再进行事务调用,没有必要,不如直接进行事务模式。</li>
<li>
<p>分布式中,连接池的性能比直连的性能略好(见后续测试部分)。</p>
</li>
<li>
<p>分布式调用中不支持事务。</p>
<p>因为事务是在服务器端实现,而在分布式中,每批次的调用对象都可能访问不同的机器,所以,没法进行事务。</p>
</li>
</ol>
<h3 id="十测试">十、测试</h3>
<p>运行上面的代码,进行测试,其结果如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Simple SET: 5.227 seconds
Transaction SET: 0.5 seconds
Pipelined SET: 0.353 seconds
Pipelined transaction: 0.509 seconds
Simple@Sharing SET: 5.289 seconds
Pipelined@Sharing SET: 0.348 seconds
Simple@Pool SET: 5.039 seconds
Pipelined@Pool SET: 0.401 seconds
</code></pre></div></div>
<p>另外,经测试分布式中用到的机器越多,调用会越慢。上面是2片,下面是5片:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Simple@Sharing SET: 5.494 seconds
Pipelined@Sharing SET: 0.51 seconds
Simple@Pool SET: 5.223 seconds
Pipelined@Pool SET: 0.518 seconds
</code></pre></div></div>
<p>下面是10片:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Simple@Sharing SET: 5.9 seconds
Pipelined@Sharing SET: 0.794 seconds
Simple@Pool SET: 5.624 seconds
Pipelined@Pool SET: 0.762 seconds
</code></pre></div></div>
<p>下面是100片:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Simple@Sharing SET: 14.055 seconds
Pipelined@Sharing SET: 8.185 seconds
Simple@Pool SET: 13.29 seconds
Pipelined@Pool SET: 7.767 seconds
</code></pre></div></div>
<p>分布式中,连接池方式调用不但线程安全外,根据上面的测试数据,也可以看出连接池比直连的效率更好。</p>
<h3 id="十一完整的测试代码">十一、完整的测试代码</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.example.nosqlclient;
import java.util.Arrays;
import java.util.List;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.JedisShardInfo;
import redis.clients.jedis.Pipeline;
import redis.clients.jedis.ShardedJedis;
import redis.clients.jedis.ShardedJedisPipeline;
import redis.clients.jedis.ShardedJedisPool;
import redis.clients.jedis.Transaction;
import org.junit.FixMethodOrder;
import org.junit.runners.MethodSorters;
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class TestJedis {
private static Jedis jedis;
private static ShardedJedis sharding;
private static ShardedJedisPool pool;
@BeforeClass
public static void setUpBeforeClass() throws Exception {
List<JedisShardInfo> shards = Arrays.asList(
new JedisShardInfo("localhost",6379),
new JedisShardInfo("localhost",6379)); //使用相同的ip:port,仅作测试
jedis = new Jedis("localhost");
sharding = new ShardedJedis(shards);
pool = new ShardedJedisPool(new JedisPoolConfig(), shards);
}
@AfterClass
public static void tearDownAfterClass() throws Exception {
jedis.disconnect();
sharding.disconnect();
pool.destroy();
}
@Test
public void test1Normal() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = jedis.set("n" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test2Trans() {
long start = System.currentTimeMillis();
Transaction tx = jedis.multi();
for (int i = 0; i < 100000; i++) {
tx.set("t" + i, "t" + i);
}
//System.out.println(tx.get("t1000").get());
List<Object> results = tx.exec();
long end = System.currentTimeMillis();
System.out.println("Transaction SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test3Pipelined() {
Pipeline pipeline = jedis.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("p" + i, "p" + i);
}
//System.out.println(pipeline.get("p1000").get());
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test4combPipelineTrans() {
long start = System.currentTimeMillis();
Pipeline pipeline = jedis.pipelined();
pipeline.multi();
for (int i = 0; i < 100000; i++) {
pipeline.set("" + i, "" + i);
}
pipeline.exec();
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined transaction: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test5shardNormal() {
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = sharding.set("sn" + i, "n" + i);
}
long end = System.currentTimeMillis();
System.out.println("Simple@Sharing SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test6shardpipelined() {
ShardedJedisPipeline pipeline = sharding.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sp" + i, "p" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
System.out.println("Pipelined@Sharing SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test7shardSimplePool() {
ShardedJedis one = pool.getResource();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
String result = one.set("spn" + i, "n" + i);
}
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Simple@Pool SET: " + ((end - start)/1000.0) + " seconds");
}
@Test
public void test8shardPipelinedPool() {
ShardedJedis one = pool.getResource();
ShardedJedisPipeline pipeline = one.pipelined();
long start = System.currentTimeMillis();
for (int i = 0; i < 100000; i++) {
pipeline.set("sppn" + i, "n" + i);
}
List<Object> results = pipeline.syncAndReturnAll();
long end = System.currentTimeMillis();
pool.returnResource(one);
System.out.println("Pipelined@Pool SET: " + ((end - start)/1000.0) + " seconds");
}
}
</code></pre></div></div>
在服务器上创建 git 裸仓库
2013-05-28T00:00:00+00:00
http://www.blogways.net/blog/2013/05/28/git-bare-repo-sever
<p>git裸仓库,就是指没有工作目录的仓库。简单点说,裸仓库就是你工作目录下面的 <code class="language-plaintext highlighter-rouge">.git</code> 子目录里面的内容。</p>
<p>远程仓库,一般不需要工作目录,所以通常都是裸仓库。</p>
<p>如何在服务器上创建裸仓库?很简单,跟我来!</p>
<p>如果你还没有代码,直接在服务器上创建裸仓库很简单,一个命令就够了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git init --bare
</code></pre></div></div>
<p>但是如果在本机,你已经有了一些代码,如何把这些代码部署到服务器上,并且仅仅部署成一个裸仓库呢?其实,也很简单,因为我们了解了 git 裸仓库实际上就是你工作目录下的 <code class="language-plaintext highlighter-rouge">.git</code> 子目录的内容,拷过去就行了。</p>
<p>所以,下面有三个思路,都可以实现:</p>
<h3 id="思路一在本机生成裸仓库把裸仓库部署到服务器上">思路一:在本机生成裸仓库,把裸仓库部署到服务器上</h3>
<p>具体步骤:</p>
<ol>
<li>
<p>本机生成裸仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git clone --bare my_project my_project.git
$ cp -Rf my_project/.git my_project.git
</code></pre></div> </div>
<p>上面两个命令结果一样,都可以根据现有的仓库生成一个裸仓库。按喜欢选择一个即可。</p>
</li>
<li>
<p>部署到服务器上</p>
<p>可以用工具部署到远程服务器上,也可以用命令,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ scp -r my_project.git user@git.example.com:/opt/git
</code></pre></div> </div>
</li>
<li>
<p>大功告成了</p>
<p>可以测试一下,获取远程服务器上的版本</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git clone user@git.example.com:/opt/git/my_project.git
</code></pre></div> </div>
</li>
</ol>
<h3 id="思路二把本机的git目录部署到服务器上然后改成裸仓库">思路二:把本机的<code class="language-plaintext highlighter-rouge">.git</code>目录部署到服务器上,然后改成裸仓库</h3>
<ol>
<li>
<p>将<code class="language-plaintext highlighter-rouge">.git</code>目录部署到服务器上</p>
<p>可以用工具部署到远程服务器上,也可以用命令,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ scp -r my_project/.git user@git.example.com:/opt/git
</code></pre></div> </div>
</li>
<li>
<p>将服务器<code class="language-plaintext highlighter-rouge">.git</code>目录改成裸仓库</p>
<p>在服务器上执行命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cd /opt/git
mv .git my_project.git
</code></pre></div> </div>
</li>
<li>
<p>大功告成了</p>
<p>可以测试一下,获取远程服务器上的版本</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git clone user@git.example.com:/opt/git/my_project.git
</code></pre></div> </div>
</li>
</ol>
<h3 id="思路三git-push-到远程仓库">思路三:git push 到远程仓库</h3>
<ol>
<li>
<p>先在远程主机上建个裸仓库</p>
$ mkdir my_project.git
$ cd my_project.git
$ git init –bare
</li>
<li>
<p>给本地仓库添加一个远程仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> # git remote add <远程仓库名字> <地址>
$ git remote add ball git@xxx.xxx.xxx.xxx:/path/to/my_project.git
</code></pre></div> </div>
</li>
<li>
<p>将本地仓库内容上传远程仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git push ball master
</code></pre></div> </div>
</li>
</ol>
<p><strong>注意:</strong>clone版本库的时候,所使用的远程主机自动被Git命名为origin。如果想用其他的主机名,需要用git clone命令的-o选项指定。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ git clone -o jQuery https://github.com/jquery/jquery.git
</code></pre></div></div>
<h3 id="注意">注意:</h3>
<p>按思路一、思路二创建的远程仓库,如果需要支持其他人<code class="language-plaintext highlighter-rouge">push</code>数据,需要修改仓库下配置文件<code class="language-plaintext highlighter-rouge">config</code>,添加如下内容:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[receive]
denyCurrentBranch = ignore
</code></pre></div></div>
<p>否则,可能会遇到在客户端不被允许向裸仓库<code class="language-plaintext highlighter-rouge">push</code>数据。</p>
在Eclipse中创建Maven多模块工程的例子[20150428更新]
2013-05-13T00:00:00+00:00
http://www.blogways.net/blog/2013/05/13/maven-multi-modules-demo
<p>如果,你需要创建多个项目,项目之间即独立又有关系,那么创建一个Maven多模块项目是个非常好的选择,也非常cool!怎么在Eclipse里面创建多模块工程,以及需要注意哪些地方,我在这里做个简单的介绍。</p>
<h3 id="一准备">一、准备</h3>
<p>若想在Eclipse里面做这些的话,那么在做这一切前,请确认你是否已经在eclipse里面安装了maven插件。如果没有装插件,那只能通过命令行去做了。</p>
<p>好,现在假设已经在Eclipse里面装了maven插件,那么我们一起用Eclipse来创建Maven多模块项目吧!</p>
<h3 id="二先创建父项目">二、先创建父项目</h3>
<ol>
<li>在Eclipse里面New -> <code class="language-plaintext highlighter-rouge">Maven Project</code>;</li>
<li>在弹出界面中选择“Create a simple project”</li>
<li>
<p>设置工程的参数,见下图<br /><img src="/images/post/maven-modules1.png" alt="Params Settings" /></p>
<ul>
<li>Group Id: com.example</li>
<li>Artifact Id: multi-modules-demo</li>
<li><span style="color:red">Packaging: pom</span></li>
<li>Name: Multi Modules Demo</li>
</ul>
</li>
<li>点击完成</li>
</ol>
<p>这样,我们就按常规模版创建了一个Maven工程。我们还需要对这个工程进行修改。</p>
<p>因为,这是一个父项目,不需要有什么源码,那么,我们在Eclipse中将这个工程下的不用的目录都删除,仅留下<code class="language-plaintext highlighter-rouge">pom.xml</code>文件就行了。</p>
<h3 id="三创建子项目">三、创建子项目</h3>
<ol>
<li>选中刚建的父项目,在弹出菜单中点击 New -> <code class="language-plaintext highlighter-rouge">Maven Module</code>;</li>
<li>如图配置<br /><img src="/images/post/maven-modules3.png" alt="child settings" /></li>
<li>使用默认的Archetype(默认:GroupId:org.apache.maven.archetypes,Artifact Id:maven-archetype-quickstart)</li>
<li>完成工程配置,见下图<br /><img src="/images/post/maven-modules4.png" alt="Params Settings" /></li>
<li>点击完成</li>
</ol>
<p>这样一个子项目就创建完成了,在文件系统中,子项目会建在父项目的目录中。在父目录中运行<code class="language-plaintext highlighter-rouge">mvn test</code>等命令,所有的子项目都会按顺序执行。</p>
<p>细心一点的人,可能会发现,通过这个步骤创建子项目的同时,会修改父项目的<code class="language-plaintext highlighter-rouge">pom.xml</code>,增加了类似下面的信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><modules>
<module>module-children1-demo</module>
</modules>
</code></pre></div></div>
<p>这个信息,就是标记有哪些子模块。</p>
<p>重复创建子项目的步骤,可以创建多个子项目。</p>
<h3 id="四优化配置">四、优化配置</h3>
<p>虽然上面的步骤,可以完成多模块的创建,但是创建出来的多模块,在一个程序员的眼里,还是挺别扭的,怎么回事呢?对,存在重复。那让我们重构吧。</p>
<p>按上面步骤创建的子项目,在<code class="language-plaintext highlighter-rouge">pom.xml</code>中有个<code class="language-plaintext highlighter-rouge">parent</code>节点,所以,他可以继承父项目的相关信息。没错,父子项目中存在继承关系。</p>
<p>在子项目的<code class="language-plaintext highlighter-rouge">pom.xml</code>中,子项目的<code class="language-plaintext highlighter-rouge">groupId</code>和<code class="language-plaintext highlighter-rouge">version</code>一般和父项目相同,那么可以把子项目的这两个参数删除,这样会自动继承父项目的取值。</p>
<p>同样,如果其他的一些属性,所有子项目都是一样的,那么可以上移到父项目中设置,子项目中无需重复设置。比如:<code class="language-plaintext highlighter-rouge"><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding></code>可以仅在父项目中设置一次。</p>
<p>除了这种情况以外,还有一种情况,就是依赖和插件。依赖和插件的情况是这样,某一个依赖或插件可能会被大部分子项目所使用,但是也可能有些子项目不需要使用,这样使用上述的方式,简简单单地进行继承就不合适了。</p>
<p>Manen提供<code class="language-plaintext highlighter-rouge">dependencyManagement</code>和<code class="language-plaintext highlighter-rouge">pluginManagement</code>两个标签。使用这两个标签,可以在父项目中统一管理依赖和插件的配置参数,比如版本号啥的。而在子项目中,仅需列出需要使用的依赖和插件的<code class="language-plaintext highlighter-rouge">groupId</code>和<code class="language-plaintext highlighter-rouge">artifactId</code>就可以了,其他信息会自动从父项目管理的信息里面获取。</p>
<p>看例子,父项目中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependencyManagement>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.5</version>
</dependency>
</dependencies>
</dependencyManagement>
</code></pre></div></div>
<p>在子项目中:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
</code></pre></div></div>
<h3 id="四命令行创建">四、命令行创建</h3>
<p>上面就是在Eclipse里面创建多模块的步骤,和一些优化配置。</p>
<p>其中,具体的步骤可以根据实际情况进行适当的修改,比如选择<code class="language-plaintext highlighter-rouge">Archetype</code>时,可以根据需要,选择适当的<code class="language-plaintext highlighter-rouge">Archetype</code>。</p>
<p>上述步骤中的一些环节,也可以先通过命令行来生成雏形,然后再修改<code class="language-plaintext highlighter-rouge">pom.xml</code>来实现。</p>
<p>相关命令为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mvn archetype:generate -DarchetypeCatalog=internal -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart
</code></pre></div></div>
<p>工程创建后需要修改<code class="language-plaintext highlighter-rouge">pom.xml</code>.修改方式,可以参考上面说到的内容。</p>
Redis Linux 数据类型
2013-05-05T00:00:00+00:00
http://www.blogways.net/blog/2013/05/05/redis-usage
<h2 id="数据类型">数据类型</h2>
<h3 id="keys">keys</h3>
<p>redis本质上还是一个key-value db,所以我们首先来看看他的key。</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>exists key 测试指定的 key 是否存在,真1|假0
del key1 key2 ... keyn 返回删除的 key 的数目,0表示不存在
type key 返回 key 的 value 类型
keys pattern 返回匹配指定模式的所有 key
randomkey 随机返回一个 key
rename oldkey newkey 重命名一个 key,如果 newkey 存在会被覆盖
renamenx oldkey newkey 同上,如果 newkey 存在返回失败
dbsize 返回当前数据库的 key 数量
expire key seconds 为 key 指定过期时间,单位是秒
ttl key 返回设置过期时间的 key 的剩余过期时间
select db-index 通过索引选择数据库,默认连接的是数据库0,默认数据库数是16个
move key db-index 将 key 从当前数据库移动到指定数据库
flushdb 删除当前数据库里面所有的 key
flushall 删除所有数据库中所有的 key
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> set test 1
OK
redis 127.0.0.1:6479> set tast 2
OK
redis 127.0.0.1:6479> set tist 3
OK
redis 127.0.0.1:6479> exists test
(integer) 1
redis 127.0.0.1:6479> del tist
(integer) 1
redis 127.0.0.1:6479> type test
string
redis 127.0.0.1:6479> keys *
1) "tast"
2) "test"
3) "tist"
redis 127.0.0.1:6479> keys t*
1) "tast"
2) "test"
3) "tist"
redis 127.0.0.1:6479> keys t[ia]st
1) "tast"
2) "tist"
redis 127.0.0.1:6479> keys t?st
1) "tast"
2) "test"
3) "tist"
redis 127.0.0.1:6479> randomkey
"test"
redis 127.0.0.1:6479> rename test tt
OK
redis 127.0.0.1:6479> dbsize
(integer) 2
redis 127.0.0.1:6479> expire tt 30
(integer) 1
redis 127.0.0.1:6479> ttl tt
(integer) 24
redis 127.0.0.1:6479> select 10
OK
redis 127.0.0.1:6479[10]>
</code></pre></div></div>
<h3 id="string">string</h3>
<p>string 是 redis 最基本的类型,而且 string 类型是二进制安全的,可以包含任何数据,包括图片和序列化的对象,最大可存1G字节。</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>set key value 设置 key 对应的 string 类型的值
setnx key value 同上,如果 key 已经存在返回 0
get key 获取 key 对应的 string 值,不存在返回 nil
getset key 设置 key 的新值,并返回旧值
mget key1 key2 ... keyn 一次获取多个 key 值
mset key1 value1 key2 value2 ... keyn valuen 一次设置多个值
incr key 对 key 做 ++ 操作,如果 value 不是 int 类型会返回错误
decr kdy 对 key 做 -- 操作,可以为负
incrby key integer 对 key 加指定值
decrby key integer 对 key 减指定值
append key value 对 key 的值追加字符串
substr key start end 截取指定 key 的字符串值,下标从0开始
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> flushdb
OK
redis 127.0.0.1:6479> set k1 a k2 b k3 c
(error) ERR syntax error
redis 127.0.0.1:6479> mset k1 a k2 b k3 c
OK
redis 127.0.0.1:6479> mget k1 k2 k3 k4
1) "a"
2) "b"
3) "c"
4) (nil)
redis 127.0.0.1:6479> incr k4
(integer) 1
redis 127.0.0.1:6479> decrby k4 5
(integer) -4
redis 127.0.0.1:6479> set k1 hello
OK
redis 127.0.0.1:6479> append k1 ' world'
(integer) 11
redis 127.0.0.1:6479> mget k1 k2 k3 k4
1) "hello world"
2) "b"
3) "c"
4) "-4"
redis 127.0.0.1:6479> substr k1 2 8
"llo wor"
</code></pre></div></div>
<h3 id="list">list</h3>
<p>redis的list类型其实就是一个每个子元素都是string类型的双向链表。所以[lr]push和[lr]pop命令的算法时间复杂度都是O(1)
另外list会记录链表的长度。所以llen操作也是O(1).链表的最大长度是(2的32次方-1)。我们可以通过push,pop操作从链表的头部
或者尾部添加删除元素。这使得list既可以用作栈,也可以用作队列。有意思的是list的pop操作还有阻塞版本的。当我们[lr]pop一个
list对象是,如果list是空,或者不存在,会立即返回nil。但是阻塞版本的b[lr]pop可以则可以阻塞,当然可以加超时时间,超时后也会返回nil
。为什么要阻塞版本的pop呢,主要是为了避免轮询。举个简单的例子如果我们用list来实现一个工作队列。执行任务的thread可以调用阻塞版本的pop去
获取任务这样就可以避免轮询去检查是否有任务存在。当任务来时候工作线程可以立即返回,也可以避免轮询带来的延迟。</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>lpush key string 在 key 对应的 list 头部添加字符串元素,如果 key 不是 list 类型返回错误
rpush key string 同上,在尾部添加
llen key 返回 key 对应的 list 的长度,key 不存在返回0,key 不是 list 类型返回错误
lrange key start end 返回指定区间内的元素,下标从0开始,负数表示从后面开始
ltrim key start end 截取 list,保留指定区间内的元素
lset key index value 设置 list 指定下标的元素值
lrem key count value 从 key 对应的 list 中删除 count 个 和 value 元素相同的元素,count 为0时删除全部
lpop key 从 list 头部删除元素,并返回删除元素
rpop key 同上,从 list 尾部删除
blpop key1 key2 ... keyn timeout 从左到右返回一个非空 list 进行 lpop 操作并返回,如果所有的 list 都为空或者不存在,则会等待 timeout 秒,等待期间有 list 的 push 操作则立即返回,超时则返回 nil
brpop key1 key2 ... keyn timeout 同上,从尾部删除
rpoplpush srckey destkey 从 srckey 对应的 list 的尾部移动元素添加到 destkey 对应的 list 头部,最后返回被移除的元素,srckey 为空或者不存在返回 nil
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> lpush list1 aaa
(integer) 1
redis 127.0.0.1:6479> rpush list1 bbb
(integer) 2
redis 127.0.0.1:6479> lpush list1 ccc
(integer) 3
redis 127.0.0.1:6479> lpush list1 ddd
(integer) 4
redis 127.0.0.1:6479> llen list1
(integer) 4
redis 127.0.0.1:6479> lrange list1 0 2
1) "ddd"
2) "ccc"
3) "aaa"
redis 127.0.0.1:6479> ltrim list1 0 2
OK
redis 127.0.0.1:6479> lrange list1 0 3
1) "ddd"
2) "ccc"
3) "aaa"
redis 127.0.0.1:6479> lset list1 1 eeee
OK
redis 127.0.0.1:6479> lpop list1
"ddd"
redis 127.0.0.1:6479> rpop list1
"aaa"
</code></pre></div></div>
<h3 id="set">set</h3>
<p>redis的set是string类型的无序集合。set元素最大可以包含(2的32次方-1)个元素。set的是通过hash table实现的,所以添加,删除,查找的复杂度都是O(1)。hash table会随着添加或者删除自动的调整大小。需要注意的是调整hash table大小时候需要同步(获取写锁)会阻塞其他读写操作。可能不久后就会改用跳表(skip list)来实现
跳表已经在sorted set中使用了。关于set集合类型除了基本的添加删除操作,其他有用的操作还包含集合的取并集(union),交集(intersection),
差集(difference)。通过这些操作可以很容易的实现sns中的好友推荐和blog的tag功能。</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sadd key member 添加一个 string 元素到 key 对应的 set 集合中
srem key member 从 key 对应的 set 中移除给定元素
spop key 删除并返回 key 对应的 set 随机的一个元素
srandmember key 随机获取 set 中的一个元素,不删除
smove srckey destkey member 从 srckey 对应的 set 中移除 member 并添加到 destkey 对应的 set 中
scard key 返回 set 的元素个数
sismember key member 判断 member 是否在 set 中
sinter key1 key2 ... keyn 返回所有 set 的交集
sinterstore destkey key1 key2 ... keyn 同上,并保存交集到 destkey 对应的 set 下
sunion key1 key2 ... keyn 返回所有给定 set 的并集
sunion destkey key1 key2 ... keyn 同上,并同时保存并集到 destkey 对应的 set 下
sdiff key1 key2 ... keyn 返回所有 set 的差集
sdiffstore destkey key1 key2 ... keyn 同上,并保存差集到 destkey 对应的 set 下
smembers key 返回 key 对应的所有元素,结果是无序的
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> sadd set1 aaa
(integer) 1
redis 127.0.0.1:6479> sadd set1 bbb
(integer) 1
redis 127.0.0.1:6479> sadd set1 ccc
(integer) 1
redis 127.0.0.1:6479> srem set1 bbb
(integer) 1
redis 127.0.0.1:6479> spop set1
"aaa"
redis 127.0.0.1:6479> scard set1
(integer) 1
redis 127.0.0.1:6479> smembers set1
1) "ccc"
redis 127.0.0.1:6479> sadd set2 aaa
(integer) 1
redis 127.0.0.1:6479> sadd set2 bbb
(integer) 1
redis 127.0.0.1:6479> sadd set2 ccc
(integer) 1
redis 127.0.0.1:6479> sinterstore set3 set1 set2
(integer) 1
redis 127.0.0.1:6479> smembers set3
1) "ccc"
</code></pre></div></div>
<h3 id="sorted-set">sorted set</h3>
<p>和set一样sorted set也是string类型元素的集合,不同的是每个元素都会关联一个double类型的score。sorted set的实现是skip list和hash table的混合体
当元素被添加到集合中时,一个元素到score的映射被添加到hash table中,所以给定一个元素获取score的开销是O(1),另一个score到元素的映射被添加到skip list
并按照score排序,所以就可以有序的获取集合中的元素。添加,删除操作开销都是O(log(N))和skip list的开销一致,redis的skip list实现用的是双向链表,这样就
可以逆序从尾部取元素。sorted set最经常的使用方式应该是作为索引来使用.我们可以把要排序的字段作为score存储,对象的id当元素存储。</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>zadd key score member 添加元素互集合,元素存在只更新对应的 scroe
zrem key member 删除指定元素
zincrby key incr member 增加对应 member 的 scroe 值
zrank key member 返回指定元素在集合中的下标,默认按 score 从小到大排序
zrevrank key member 同上,按 scroe 从大到小排序
zrange key start end 返回指定区间的元素
zrevrange key start end 同上,返回结果按 score 逆序
zrangebystore key min max 返回集合中 score 在给定区间的元素
zcount key min max 返回集合中 score 在给定区间的数量
zcard key 返回集合中元素个数
zscore key element 返回给定元素对应的 score
zremrangebyrank key min max 删除集合中排名在给定区间的元素
zremrangebyscore key min max 删除集合中 score 在给定区间的元素
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> zadd ss1 10000 abc
(integer) 1
redis 127.0.0.1:6479> zadd ss1 20000 bbb
(integer) 1
redis 127.0.0.1:6479> zadd ss1 30000 ccc
(integer) 1
redis 127.0.0.1:6479> zadd ss1 44000 ddd
(integer) 1
redis 127.0.0.1:6479> zrank ss1 bbb
(integer) 1
redis 127.0.0.1:6479> zrevrank ss1 bbb
(integer) 2
redis 127.0.0.1:6479> zcard ss1
(integer) 4
redis 127.0.0.1:6479> zrangebyscore ss1 20000 40000
1) "bbb"
2) "ccc"
</code></pre></div></div>
<h3 id="hash">hash</h3>
<p>redis hash是一个string类型的field和value的映射表.它的添加,删除操作都是O(1)(平均).hash特别适合用于存储对象。相较于将对象的每个字段存成
单个string类型。将一个对象存储在hash类型中会占用更少的内存,并且可以更方便的存取整个对象。省内存的原因是新建一个hash对象时开始是用zipmap(又称为small hash)来存储的。这个zipmap其实并不是hash table,但是zipmap相比正常的hash实现可以节省不少hash本身需要的一些元数据存储开销。尽管zipmap的添加,删除,查找都是O(n),但是由于一般对象的field数量都不太多。所以使用zipmap也是很快的,也就是说添加删除平均还是O(1)。如果field或者value的大小超出一定限制后,redis会在内部自动将zipmap替换成正常的hash实现. 这个限制可以在配置文件中指定
hash-max-zipmap-entries 64 #配置字段最多64个
hash-max-zipmap-value 512 #配置value最大为512字节</p>
<p>相关命令</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>hset key field value 设置 hash field 指定值
hget key field 获取指定的 hash field 值
hmget key field1 field2 ... fieldn 获取全部指定的值
hmset key field1 value1 field2 value2 ... fieldn valuen 同时设定多个 field 值
hincrby key field integer 将指定的 field 加上给定的值
hexists key field 判断指定的 field 是否存在
hdel key field 删除指定的 field
hlen key 返回指定的 field 数量
hkeys key 返回所有的 field
hvals key 返回所有的 value
hgetall 返回所有的 field 和 value
</code></pre></div></div>
<p>示例</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> hmset hash1 field1 aaa field2 bbb field3 ccc
OK
redis 127.0.0.1:6479> hmget hash1 field1 field2 field3
1) "aaa"
2) "bbb"
3) "ccc"
redis 127.0.0.1:6479> hexists hash1 field2
(integer) 1
redis 127.0.0.1:6479> hlen hash1
(integer) 3
redis 127.0.0.1:6479> hkeys hash1
1) "field1"
2) "field2"
3) "field3"
redis 127.0.0.1:6479> hvals hash1
1) "aaa"
2) "bbb"
3) "ccc"
redis 127.0.0.1:6479> hgetall hash1
1) "field1"
2) "aaa"
3) "field2"
4) "bbb"
5) "field3"
6) "ccc"
</code></pre></div></div>
Redis Linux Pipeline && 分布式
2013-05-05T00:00:00+00:00
http://www.blogways.net/blog/2013/05/05/redis-usage-2
<h2 id="事务">事务</h2>
<p>redis对事务的支持目前还比较简单。redis只能保证一个client发起的事务中的命令可以连续的执行,而中间不会插入其他client的命令。 由于redis是单线程来处理所有client的请求的所以做到这点是很容易的。一般情况下redis在接受到一个client发来的命令后会立即处理并 返回处理结果,但是当一个client在一个连接中发出multi命令有,这个连接会进入一个事务上下文,该连接后续的命令并不是立即执行,而是先放到一 个队列中。当从此连接受到exec命令后,redis会顺序的执行队列中的所有命令。并将所有命令的运行结果打包到一起返回给client.然后此连接就 结束事务上下文。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> multi
OK
redis 127.0.0.1:6479> incr a
QUEUED
redis 127.0.0.1:6479> incr b
QUEUED
redis 127.0.0.1:6479> get a
QUEUED
redis 127.0.0.1:6479> get b
QUEUED
redis 127.0.0.1:6479> exec
1) (integer) 1
2) (integer) 1
3) "1"
4) "1"
redis 127.0.0.1:6479> get a
"1"
redis 127.0.0.1:6479> get b
"1"
</code></pre></div></div>
<p>批量操作效率的确的很大的提高,但同时也会带来其他的问题,因为 redis 本身并不提供同步锁机制,如果在批量执行的过程中另一个客户端对批量里面的元素进行操作,当调用 exec 执行的时候发现结果已经不是我们想要的结果了,还好 redis2.1 后添加了 watch 命令,可以用来实现乐观锁,通过对元素的监控来判断在等待的过程中元素的值有没有发生变化,如果有则执行失败。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> watch a
OK
redis 127.0.0.1:6479> get a
"5"
redis 127.0.0.1:6479> multi
OK
redis 127.0.0.1:6479> set a 2
QUEUED
redis 127.0.0.1:6479> exec
(nil)
</code></pre></div></div>
<p>redis 的事务是如此简单,当然也会存在一些问题,首先 redis 只能保证每个命令连续执行,如果事务中一个命令失败了,并不回滚其他的命令,这样就会导致的事务的完整性无法得到保证。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> set a 5
OK
redis 127.0.0.1:6479> lpush b 5
(integer) 1
redis 127.0.0.1:6479> multi
OK
redis 127.0.0.1:6479> incr a
QUEUED
redis 127.0.0.1:6479> incr b
QUEUED
redis 127.0.0.1:6479> exec
1) (integer) 6
2) (error) ERR Operation against a key holding the wrong kind of value
</code></pre></div></div>
<p>还有一个十分罕见的问题是 当事务的执行过程中,如果redis意外的挂了。很遗憾只有部分命令执行了,后面的也就被丢弃了。当然如果我们使用的append-only file方式持久化,redis会用单个write操作写入整个事务内容。即是是这种方式还是有可能只部分写入了事务到磁盘。发生部分写入事务的情况 下,redis重启时会检测到这种情况,然后失败退出。可以使用redis-check-aof工具进行修复,修复会删除部分写入的事务内容。修复完后就 能够重新启动了。</p>
<h2 id="pipeline">pipeline</h2>
<p>redis是一个cs模式的tcp server,使用和http类似的请求响应协议。一个client可以通过一个socket连接发起多个请求命令。每个请求命令发出后client通常 会阻塞并等待redis服务处理,redis处理完后请求命令后会将结果通过响应报文返回给client。我们还可以利用pipeline的方式从client打包多条命令一起发出,不需要等待单条命令的响应返回,而redis服务端会处理完多条命令后会将多条命令的处理结果打包到一起返回给客户端。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>String host = "127.0.0.1";
int port = 6479, timeout = 30000;
Jedis jedis = new Jedis(host, port, timeout);
Pipeline p = jedis.pipelined();
for (int i = 1; i <= 500000; i++) {
String key = "comppara_" + i;
String value = "{param1: " + i + ", param2: " + i + ", param3: " + i + "}";
p.set(key, value);
}
p.sync();
</code></pre></div></div>
<p>通过 pipeline 模式,set 跟 get 效率有了明显的提交,前面测试的50W数据导入只需要不到3秒种,读2秒左右。</p>
<h2 id="redis持久化">redis持久化</h2>
<p>redis是一个支持持久化的内存数据库,也就是说redis需要经常将内存中的数据同步到磁盘来保证持久化。redis支持两种持久化方式,一种是 Snapshotting(快照)也是默认方式,另一种是Append-only file(缩写aof)的方式。下面分别介绍</p>
<p>Snapshotting
快照是默认的持久化方式。这种方式是就是将内存中数据以快照的方式写入到二进制文件中,默认的文件名为dump.rdb。可以通过配置设置自动做快照持久 化的方式。我们可以配置redis在n秒内如果超过m个key被修改就自动做快照,下面是默认的快照保存配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>save 900 1 #900秒内如果超过1个key被修改,则发起快照保存
save 300 10 #300秒内容如超过10个key被修改,则发起快照保存
save 60 10000
</code></pre></div></div>
<h2 id="分布式">分布式</h2>
<p>在jedis的源码里发现了两种hash算法(MD5,MURMUR Hash(默认)),也可以自己实现redis.clients.util.Hashing接口扩展。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>List<JedisShardInfo> hosts = new ArrayList<JedisShardInfo>();
JedisShardInfo host1 = new JedisShardInfo("127.0.0.1", 6479, 3000);
JedisShardInfo host2 = new JedisShardInfo("192.168.4.17", 6479, 3000);
hosts.add(host1);
hosts.add(host2);
ShardedJedis jedis = new ShardedJedis(hosts);
ShardedJedisPipeline p = jedis.pipelined();
for (int i = 1; i <= 10000; i++) {
String key = "shard_" + i;
String value = "value_" + i;
p.set(key, value);
}
p.sync();
</code></pre></div></div>
Redis Linux 安装 && 测试
2013-05-02T00:00:00+00:00
http://www.blogways.net/blog/2013/05/02/redis
<h2 id="redis-linux-安装">Redis Linux 安装</h2>
<p>由于 Redis 并没有发布 windows 的官方版本,windows 的安装使用不作介绍,只介绍 Linux 下的安装使用。
下载地址:<code class="language-plaintext highlighter-rouge">https://github.com/dmajkic/redis/downloads</code> 下载最新版本</p>
<p>然后tar, make,即可。(make前,如果确认自己的测试机是32位linux,在src/Makefile文件中的头部加上 <code class="language-plaintext highlighter-rouge">CFLAGS= -march=i686</code></p>
<p>redis 2.6.9 安装报错</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Jimmy 2013-01-21 11:53
zmalloc.h:50:31: error: jemalloc/jemalloc.h: No such file or directory
zmalloc.h:55:2: error: #error "Newer version of jemalloc required"
make[1]: *** [adlist.o] Error 1
make[1]: Leaving directory `/data0/src/redis-2.6.2/src'
make: *** [all] Error 2
解决办法是:
make MALLOC=libc
</code></pre></div></div>
<p>启动 server:
根目录下执行 <code class="language-plaintext highlighter-rouge">nohup src/redis-server redis.conf &</code></p>
<p>如果端口有冲突改下 redis.conf 里面的 port 配置</p>
<p>启动客户端
<code class="language-plaintext highlighter-rouge">src/redis-cli</code>
端口有变化
<code class="language-plaintext highlighter-rouge">src/redis-cli -p 6479</code></p>
<p>测试下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>redis 127.0.0.1:6479> set test hello
OK
redis 127.0.0.1:6479> get test
"hello"
</code></pre></div></div>
<p>与你熟悉的关系型数据库一致,Redis有着相同的数据库基本概念,即一个数据库包含一组数据。典型的数据库应用案例是,将一个程序的所有数据组织起来,使之与另一个程序的数据保持独立。
在Redis里,数据库简单的使用一个数字编号来进行辨认,默认数据库的数字编号是0。如果你想切换到一个不同的数据库,你可以使用select命令来实现。在命令行界面里键入<code class="language-plaintext highlighter-rouge">select 1</code>,Redis应该会回复一条OK的信息,然后命令行界面里的提示符会变成类似<code class="language-plaintext highlighter-rouge">redis 127.0.0.1:6379[1]></code>这样。如果你想切换回默认数据库,只要在命令行界面键入<code class="language-plaintext highlighter-rouge">select 0</code>即可。</p>
<blockquote>
<h2 id="redis-客户端测试">Redis 客户端测试</h2>
<p>Java 官方推荐客户端
Jedis</p>
</blockquote>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import java.util.Date;
import redis.clients.jedis.Jedis;
public class RedisTest {
public static void main(String[] args) {
long start = System.currentTimeMillis();
String host = "127.0.0.1";
int port = 6479, timeout = 30000;
Jedis jedis = new Jedis(host, port, timeout);
for (int i = 1; i <= 500000; i++) {
String key = "comppara_" + i;
String value = "{param1: " + i + ", param2: " + i + ", param3: " + i + "}";
jedis.set(key, value);
}
long end = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " insert times " + (end - start));
long s = System.currentTimeMillis();
for (int i = 0; i <= 500000; i++) {
String key = "comppara_" + i;
jedis.get(key);
}
long e = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + " read times " + (e - s));
}
}
</code></pre></div></div>
<p>C 官方推荐客户端
hiredis</p>
<p>C 也不怎么会用,就改的 hiredis 自带的 example.c 示例,编译完了直接执行 ./hiredis-example</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "time.h"
#include "hiredis.h"
void get_time() {
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
printf ( "\007The current date/time is: %s", asctime (timeinfo) );
}
int main(void) {
unsigned int j;
redisContext *c;
redisReply *reply;
struct timeval timeout = { 1, 500000 }; // 1.5 seconds
c = redisConnectWithTimeout((char*)"127.0.0.1", 6479, timeout);
if (c == NULL || c->err) {
if (c) {
printf("Connection error: %s\n", c->errstr);
redisFree(c);
} else {
printf("Connection error: can't allocate redis context\n");
}
exit(1);
}
get_time();
printf("test_bach start");
for (int i = 1; i <= 500000; i++) {
redisCommand(c,"SET param_%d {param1:param%d,param2:param%d,param3:param%d,param4:param%d}", i, i, i, i, i);
}
printf("test_bach end");
get_time();
for (int j = 1; j <= 500000; j++) {
redisCommand(c,"GET param_%d", j);
}
get_time();
return 0;
}
</code></pre></div></div>
<p>测试主机配置:8CPU32G内存,测试结果 C 插入 50W 记录大概在31秒左右,JAVA 34秒左右;读 50W 的速度 C 大概需要 29 s 左右,JAVA 需要 31s 左右。如果是远程机器调用JAVA速度基本跟C相差无几。</p>
MAC OSX 环境下搭建 memcached 环境
2013-05-01T00:00:00+00:00
http://www.blogways.net/blog/2013/05/01/demo-libmemcached-at-mac
<p>在MAC OSX下搭建 memcached 环境,那是轻松的一塌糊涂啊。整个过程几分钟就搞定了,让我再次感叹,MacBook 就是为 *nux 下程序员量身定制的!</p>
<p>我是使用 brew 来安装的,让我们再回顾一下整个过程吧。如果你没有装 brew ,先看步骤一,否则直接看步骤二。</p>
<h3 id="步骤一安装-homebrew">步骤一:安装 Homebrew</h3>
<p>先看看是否满足下面条件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Intel 的 CPU
OS X 10.5 或者更高
安装了XCode 或者 XCode命令行工具
</code></pre></div></div>
<p>满足了,就可以安装 Homebrew,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
</code></pre></div></div>
<p>打开Terminal, 粘贴上面的语句.该脚本首先将会解释它要做什么, 然后暂停下来, 直到您确认继续. 更多的安装选项在<a href="https://github.com/mxcl/homebrew/wiki/Installation">这里</a>可以看到 .</p>
<h3 id="步骤二安装-memcached">步骤二:安装 memcached</h3>
<p>安装前,可以先查找一下,看看有没有:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew search memcache
</code></pre></div></div>
<p>返回结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>libmemcached memcache-top memcached memcacheq
</code></pre></div></div>
<p>说明和关键字<code class="language-plaintext highlighter-rouge">memcache</code>相关的有上面这四个,这样就确认了,有我们需要的东西,第一个是客户端,第三个是服务器。</p>
<p>那么安装吧!</p>
<p>先装服务器:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install memcached
</code></pre></div></div>
<p>安装日志:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>==> Installing memcached dependency: libevent
==> Downloading https://github.com/downloads/libevent/libevent/libevent-2.0.21-s
######################################################################## 100.0%
==> ./configure --disable-debug-mode --prefix=/usr/local/Cellar/libevent/2.0.21
==> make
==> make install
🍺 /usr/local/Cellar/libevent/2.0.21: 48 files, 1.8M, built in 84 seconds
==> Installing memcached
==> Downloading http://memcached.googlecode.com/files/memcached-1.4.15.tar.gz
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/memcached/1.4.15 --disable-coverage
==> make install
==> Caveats
To have launchd start memcached at login:
ln -sfv /usr/local/opt/memcached/*.plist ~/Library/LaunchAgents
Then to load memcached now:
launchctl load ~/Library/LaunchAgents/homebrew.mxcl.memcached.plist
Or, if you don't want/need launchctl, you can just run:
/usr/local/opt/memcached/bin/memcached
==> Summary
🍺 /usr/local/Cellar/memcached/1.4.15: 10 files, 176K, built in 8 seconds
</code></pre></div></div>
<p>从上面安装日志,可以看出:</p>
<ol>
<li>安装 memcached 前,先安装了其所依赖的 libevent 库</li>
<li>下载的libevent和memcached,被安装到/usr/local/Cellar下面,但是又自动在/usr/local/bin下面建立了软连接,方便使用。</li>
</ol>
<p>安装后可以查看安装的结果:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ which memcached
/usr/local/bin/memcached
$ memcached -h
memcached 1.4.15
...
</code></pre></div></div>
<h3 id="步骤二安装-libmemcached">步骤二:安装 libmemcached</h3>
<p>继续安装客户端库:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ brew install libmemcached
==> Downloading https://launchpad.net/libmemcached/1.0/1.0.16/+download/libmemca
######################################################################## 100.0%
==> ./configure --prefix=/usr/local/Cellar/libmemcached/1.0.16
==> make install
🍺 /usr/local/Cellar/libmemcached/1.0.16: 110 files, 1.4M, built in 108 seconds
</code></pre></div></div>
<h3 id="步骤三启动服务器">步骤三:启动服务器</h3>
<p>先默认参数启动吧:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ /usr/local/bin/memcached -d
</code></pre></div></div>
<h3 id="步骤四编写客户端测试程序并运行">步骤四:编写客户端测试程序并运行</h3>
<p>编写程序文件 <code class="language-plaintext highlighter-rouge">example.cpp</code> :</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>#include <libmemcached/memcached.h>
#include <string.h>
#include <stdio.h>
#include <sys/time.h>
#define TEST_NUM 500000
void printNowTime() {
struct timeval current;
gettimeofday(& current, NULL);
struct tm * mtm = localtime(& current.tv_sec);
printf("[%04d-%02d-%02d %02d:%02d:%02d.%03d] ", mtm->tm_year+1900, mtm->tm_mon + 1, mtm->tm_mday, mtm->tm_hour, mtm->tm_min, mtm->tm_sec, current.tv_usec / 1000);
}
int main() {
const char *config_string = "--SERVER=localhost";
memcached_st *memc= memcached(config_string, strlen(config_string));
//const char *keys[]= {"key1", "key2", "key3","key4"};
const size_t key_length[]= {4, 4, 4, 4};
const char *values[] = {"This is 1 first value", "This is 2 second value", "This is 3 third value","this is 4 forth value"};
size_t val_length[]= {21, 22, 21, 21};
char keys[TEST_NUM][10];
printNowTime();
printf("start init keys.\n");
for(int i=0; i<TEST_NUM; ++i) {
sprintf(keys[i], "key%06d", i);
}
printNowTime();
printf("end init keys.\n\n");
memcached_return_t rc;
printNowTime();
printf("start set value.\n");
for (int i=0; i < TEST_NUM; i++)
{
rc = memcached_set(memc, keys[i], 9, values[i%4], val_length[i%4], (time_t)180,(uint32_t)0);
//printf("key: %s rc:%s\n", keys[i], memcached_strerror(memc, rc)); // 输出状态
}
printNowTime();
printf("end set value.\n\n");
char * result;
uint32_t flags;
size_t value_length;
printNowTime();
printf("start read value.\n");
for(int i=0; i < TEST_NUM; i++)
{
result = memcached_get(memc, keys[i], 9, &value_length, &flags, &rc);
//if (i%10000 == 0)
// printf("key: %s, value: %s.\n", keys[i], result);
}
printNowTime();
printf("end read value.\n");
memcached_free(memc);
return 0;
}
</code></pre></div></div>
<p>编写 <code class="language-plaintext highlighter-rouge">Makefile</code> 文件:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>example: example.cpp
gcc -o example example.cpp -lmemcached
clean:
rm example
</code></pre></div></div>
<p>编译:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ make
</code></pre></div></div>
<p>运行测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ./example
[2013-05-01 20:20:39.500] start init keys.
[2013-05-01 20:20:39.593] end init keys.
[2013-05-01 20:20:39.593] start set value.
[2013-05-01 20:21:04.527] end set value.
[2013-05-01 20:21:04.527] start read value.
[2013-05-01 20:21:26.959] end read value.
</code></pre></div></div>
<p>整个过程结束!</p>
<p>确实很简单吧!</p>
TDD 读后心得
2013-04-28T00:00:00+00:00
http://www.blogways.net/blog/2013/04/28/tdd-impression
<p>在拜读Kent Beck(也是JUnit的联合作者)的大作《Test-Driven Development By Example》(测试驱动开发)之后,内心颇为震撼。也可以算是对自己原有认知的一种思想层面的颠覆。震撼之余,感觉很有必要把自己的一些心得分享出来。</p>
<p>书中表述的信息量很大,并且配合了丰富的代码实例,然而由于鄙人理解有限,一遍阅读之后,能够摄取的知识点可能也就是书中内容的三四成。</p>
<p>下面仅将最震撼<a href="http://www.blogways.net">自己</a>的几点内容分享如下:</p>
<h3 id="心得一先写测试再写实现">心得一:先写测试,再写实现</h3>
<p>对,你没看错。</p>
<p>按我们的常规,可能都是先写代码,再写测试。至少我本人以前这么干过。也因为以前是先写代码,再写测试,所以,常常感觉没必要写测试。因为代码是怎么实现的,心里明镜似的,写出来的测试代码,意义也不大。除非是开发中有些拿不准的地方,需要写测试来验证,除此之外,基本就不需要写测试。</p>
<p>然而,TDD的精华就是先写测试,再写代码。</p>
<p>只所以这样,我觉得和TDD内在的思想有关,他包含“简单设计”这么一个思想。</p>
<h3 id="心得二简单设计">心得二:简单设计</h3>
<p>我们之前做的设计,基本都是大而全的设计,这个不是TDD所提倡的。TDD所提倡的是“简单设计”,即设计以致用,或者说测试以致用。</p>
<p>你目前需要什么功能,就做怎样的设计,或者说体现为写怎样的测试代码。</p>
<p>这样,也就有了“先写测试,再写实现”,所以,从某种角度上来说,我觉得先写的测试,其实上是设计的一种变相表现。</p>
<p>对,我是这么理解的,“测试”是一种“设计”。并且,这种设计不是大而全的设计,而是一种“设以致用”的“简单设计”。</p>
<p>那么,怎么去做到“简单设计”呢?他需要我们,“积跬步,以至千里”。</p>
<h3 id="心得三积跬步以至千里">心得三:积跬步,以至千里</h3>
<p>关于“积跬步,以至千里”,书中给出了一个非常详细的实践例子。这个例子,给我的感觉,也是相当震撼的,原来居然可以这样来…</p>
<p>我想,这应该也是敏捷开发的一个思想吧,回顾前段时间学习的“scrum”方法,其实,也是对这种“积跬步,以至千里”的思想进行的实践。</p>
<p>但是,怎么去做到“积跬步,以至千里”,我觉得这也是一个技术活,需要好好体会书中的例子,并在实践中加以运用。运用好了,才能把测试当做设计。</p>
<p>当然了,在“积跬步,以至千里”的过程中,必不可少的就是“重构”。</p>
<h3 id="心得四重构">心得四:重构</h3>
<p>在“积跬步”的过程中,必须不断的做重构,否则代码就会凌乱。而且,不仅需要不断地重构实现代码,也需要不断地重构测试代码。这一点,书中也有实例,很好地进行了佐证。</p>
<p>“简单设计”(或者说测试体现设计)——“积跬步”——“重构”,这是一条线,是一个整体。线上的每个环节,都缺一不可。这条整线,我理解就是本书的最大精华了。</p>
<p>除了这些外,书中还讲了一些方法论,这些方法论包括:“可运行模式”、“测试模式”、“设计模式”、“重构”,这些方法论中,给我感触最深的是“可运行模式”和“测试模式”。这两个模式,也是在“测试”中不可少的两个环节。</p>
<p>要真正做到前面所说的——“简单设计、积跬步”,必须要了解“可运行模式”。</p>
<h3 id="心得五可运行模式">心得五:可运行模式</h3>
<p>可运行模式中包含四种方法:“伪实现”、“三角法”、“显明实现”、“从一到多”。个人认为,虽然这些内容很简单,但是,这些都是必须要掌握的细节。</p>
<p>这有掌握了这些,并在实践中加以应用,才能做的“积跬步,以至千里”。</p>
<h3 id="心得六测试模式">心得六:测试模式</h3>
<p>测试模式实际上就是测试方法,其实,不用看书,我们或多或少,都已经在之前的实践中进行应用了,只是作者做了一个相对来说,比较全面的总结。</p>
<p>如果,你想做好测试,这些模式也需要掌握,至少其中的部分,需要成为我们以后测试中的利器。</p>
<p>这些方法,包含有:“子测试”、“模拟对象”、“自分流”、“日志字符串”、“清扫测试死角”、“不完整测试”等等。</p>
<h3 id="总结">总结</h3>
<p>以上六点,是我读后的一些心得,记之以分享!</p>
<p>BTW:关于《测试驱动开发》这本书,我读的是网上流传的一个pdf版的中文译本。可能不是最新版,也可能由于是译本,对作者的本意存在理解偏差。不当之处,请指教。</p>
memcache NOSQL服务器安装及使用
2013-04-27T00:00:00+00:00
http://www.blogways.net/blog/2013/04/27/memcache
<h3 id="一概述">一、概述</h3>
<p>Memcache是danga的一个项目,最早是LiveJournal 服务的,最初为了加速 LiveJournal 访问速度而开发的,后来被很多大型的网站采用。Memcached是以守护程序方式运行于一个或多个服务器中,随时会接收客户端的连接和操作。</p>
<p>在 Memcached中可以保存的item数据量是没有限制的,只要内存足够。Memcached单进程最大使用内存为2G,要使用更多内存,可以分多个端口开启多个Memcached进程,最大30天的数据过期时间,设置为永久的也会在这个时间过期,常量REALTIME_MAXDELTA
60<em>60</em>24*30控制,最大键长为250字节,大于该长度无法存储,常量KEY_MAX_LENGTH 250控制,单个item最大数据是1MB,超过1MB数据不予存储,常量POWER_BLOCK 1048576进行控制(可以修改slabs.c:POWER_BLOCK的值,然后重新编译memcached),它是默认的slab大小,最大同时连接数是200,通过 conn_init()中的freetotal进行控制,最大软连接数是1024,通过settings.maxconns=1024 进行控制,跟空间占用相关的参数:settings.factor=1.25, settings.chunk_size=48,影响slab的数据占用和步进方式。memcached是一种无阻塞的socket通信方式服务,基于libevent库,由于无阻塞通信,对内存读写速度非常之快。memcached分服务器端和客户端,可以配置多个服务器端和客户端,应用于分布式的服务非常广泛。memcached作为小规模的数据分布式平台是十分有效果的。</p>
<p>memcached是键值一一对应,key默认最大不能超过128个字 节,value默认大小是1M,也就是一个slabs,如果要存2M的值(连续的),不能用两个slabs,因为两个slabs不是连续的,无法在内存中 存储,故需要修改slabs的大小,多个key和value进行存储时,即使这个slabs没有利用完,那么也不会存放别的数据。</p>
<p>目前memcached支持C/C++、Perl、PHP、Python、Ruby、Java、C#、Postgres、Chicken Scheme、Lua、MySQL和Protocol等语言客户端。</p>
<h3 id="二memcache和memcached">二、Memcache和memcached</h3>
<p>其实Memcache是这个项目的名称,而memcached是它服务器端的主程序文件名,知道我的意思了吧。一个是项目名称,一个是主程序文件名,在网上看到了很多人不明白,于是混用了。</p>
<h3 id="三memcached-安装">三、Memcached 安装</h3>
<p>1.在Linux环境下应用Memcache时,Memcache用到了libevent这个库,用于Socket的处理,所以还需要安装libevent。这里用的libevent的版本是libevent-1.4.9。下载地址<a href="http://www.monkey.org/~provos/libevent/">http://www.monkey.org/~provos/libevent/</a>(如果你的系统已经安装了libevent,可以不用安装)。</p>
<p>2.Memcached 下载地址<a href="https://code.google.com/p/memcached/">https://code.google.com/p/memcached/</a></p>
<p>3.这里我运用了Magent作为Memcached代理服务器软件,它可以搭建高可用性的集群应用的Memcached服务,magent采用的是:Consistent Hashing原理,Consistent Hashing如下所示:首先求出memcached服务器(节点)的哈希值, 并将其配置到0~232的圆(continuum)上。然后用同样的方法求出存储数据的键的哈希值,并映射到圆上。然后从数据映射到的位置开始顺时针查找,将数据保存到找到的第一个服务器上。 如果超过232仍然找不到服务器,就会保存到第一台memcached服务器上,magent下载地址为:<a href="http://code.google.com/p/memagent/">http://code.google.com/p/memagent/</a>。</p>
<p>4.编译安装libevent</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar zxvf libevent-1.4.9-stable.tar.gz
cd libevent-1.4.9-stable/
./configure --prefix=/usr
make && make install
</code></pre></div></div>
<p>5.编译安装Memcached</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>tar zxvf memcached-1[1].4.5.tar.gz
cd memcached-1.4.5/
./configure --with-libevent=/usr
make && make install
</code></pre></div></div>
<p>6.编译安装magent</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>mkdir magent
cd magent/
tar zxvf magent-0.5.tar.gz
/sbin/ldconfig
sed -i "s#LIBS = -levent#LIBS = -levent -lm#g" Makefile
make
cp magent /usr/bin/magent
</code></pre></div></div>
<p>在安装magent过程中可能遇到一些问题,下面列举几个可能出现的问题及解决方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gcc -Wall -g -O2 -I/usr/local/include -m64 -c -o magent.o magent.c
magent.c: In function 'writev_list':
magent.c:729: error: 'SSIZE_MAX' undeclared (first use in this function)
magent.c:729: error: (Each undeclared identifier is reported only once
magent.c:729: error: for each function it appears in.)
make: *** [magent.o] Error 1
解决办法:
[spdev@slave2 magent]# vi ketama.h
#在开头加入
#ifndef SSIZE_MAX
#define SSIZE_MAX 32767
#endif
gcc -Wall -g -O2 -I/usr/local/include -m64 -c -o magent.o magent.c
gcc -Wall -g -O2 -I/usr/local/include -m64 -c -o ketama.o ketama.c
gcc -Wall -g -O2 -I/usr/local/include -m64 -o magent magent.o ketama.o
/usr/lib64/libevent.a /usr/lib64/libm.a
gcc: /usr/lib64/libevent.a: No such file or directory
gcc: /usr/lib64/libm.a: No such file or directory
解决办法:
[spdev@slave2 magent]# ln -s /usr/lib/libevent* /usr/lib64/
gcc -Wall -g -O2 -I/usr/local/include -m64 -o magent magent.o ketama.o
/usr/lib64/libevent.a /usr/lib64/libm.a
gcc: /usr/lib64/libm.a: No such file or directory
make: *** [magent] Error 1
解决办法:
[spdev@slave2 magent]# cp /usr/lib64/libm.so /usr/lib64/libm.a
gcc -Wall -g -O2 -I/usr/local/include -m64 -o magent magent.o ketama.o
/usr/lib64/libevent.a /usr/lib64/libm.a
/usr/lib64/libevent.a(event.o): In function `detect_monotonic':
event.c:(.text+0xc79): undefined reference to `clock_gettime'
/usr/lib64/libevent.a(event.o): In function `gettime':
event.c:(.text+0xd60): undefined reference to `clock_gettime'
collect2: ld returned 1 exit status
make: *** [magent] Error 1
解决办法:
[spdev@slave2 magent]#vi Makefile
CFLAGS = -Wall -g -O2 -I/usr/local/include $(M64)
改为:
CFLAGS = -lrt -Wall -g -O2 -I/usr/local/include $(M64)
</code></pre></div></div>
<h3 id="四启动和结束服务">四、启动和结束服务</h3>
<p>1.启动一个Memcache的服务器端:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>memcached -d -m 10 -u spdev -l 192.168.4.19 -p 11211 -c 256 -P /tmp/memcached.pid
? -d 选项是启动一个守护进程,
? -m 是分配给Memcache使用的内存数量,单位是MB,我这里是10MB,
? -u 是运行Memcache的用户,我这里是spdev,
? -l 是监听的服务器IP地址,我这里指定了服务器的IP地址192.168.4.19,
? -p 是设置Memcache监听的端口,我这里设置了11211,最好是1024以上的端口,
? -c 是最大运行的并发连接数,默认1024,这里设置了256,按照服务器的负载量来设定,
? -P 是设置保存Memcache的pid文件,我这里是保存在/tmp/memcached.pid。
</code></pre></div></div>
<p>2.结束一个Memcache进程</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>如果要结束Memcache进程,执行:
# kill `cat /tmp/memcached.pid`
? 注意,上面命令中的符号是 `,不是单引号’
</code></pre></div></div>
<p>3.启动Magent代理</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>magent -u spdev -n 51200 -l 192.168.4.19 -p 12000 -s 192.168.4.19:11211
-s 192.168.4.19:11212 -b 192.168.4.19:11213
-h this message
-u uid
-g gid
-p port, default is 11211. (0 to disable tcp support)
-s ip:port, set memcached server ip and port
-b ip:port, set backup memcached server ip and port
-l ip, local bind ip address, default is 0.0.0.0
-n number, set max connections, default is 4096
-D don't go to background
-k use ketama key allocation algorithm
-f file, unix socket path to listen on. default is off
-i number, max keep alive connections for one memcached server, default is 20
-v verbose
启动magent代理服务器,端口为12000.代理服务器ip端口192.168.4.19:11211、
192.168.4.19:11212,备份主机ip端口为192.168.4.19:11213
</code></pre></div></div>
<h3 id="五telnet-memcache服务器及magent代理服务器测试">五、telnet memcache服务器及magent代理服务器测试</h3>
<ol>
<li>
<p>telnet memcache 192.168.4.19:11211服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> C:\Users\Administrator>telnet 192.168.4.19:11211
set key 0 0 8
88888888
STORED
quit
遗失对主机的连接。
</code></pre></div> </div>
</li>
<li>
<p>telnet magent 192.168.4.19:12000代理服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> C:\Users\Administrator>telnet 192.168.4.19:12000
get key
VALUE key 0 8
88888888
END
quit
</code></pre></div> </div>
遗失对主机的连接。
</li>
</ol>
<p>这里可以发现在服务器上set的值,在代理服务器上可以取到。</p>
<h3 id="六运用libmemcached-c客户端编写客户端测试程序">六、运用libmemcached c++客户端编写客户端测试程序</h3>
<p>libmemcached下载地址:</p>
<p><a href="https://launchpad.net/libmemcached/+download/">https://launchpad.net/libmemcached/+download/</a></p>
<p>此处要注意,libmemcached 1.0以上版本需要gcc4.2以上版本才能支持,这里我用的是最新版libmemcached-1.0.17,而16主机上gcc版本为4.12,所以我升级了gcc,升级gcc步骤这里就不做介绍</p>
<ol>
<li>
<p>libmemcached客户端程序连接magent代理服务器测试</p>
<ul>
<li>
<p>循环500000次向memcache服务器set 500000条值,本次测试是ip为
192.168.4.16的主机调ip为192.168.4.19的memcache服务器,具体
调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Save data:begin
2013-04-29 15:39:32.129
2013-04-29 15:41:43.406
Save data:end 从上面数据看出set 500000数据用了2分多钟
</code></pre></div> </div>
</li>
<li>
<p>循环10000次,每次随机取1-500000中任意一个随机数,然后从上面set的值中get对应
数据,具体调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 2013-04-29 15:41:43.406
2013-04-29 15:41:45.899 由此看出随机取10000条数据用了两秒多的时间
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<p>2.libmemcached客户端程序直接连接memcache服务器测试</p>
<ul>
<li>
<p>循环500000次向memcache服务器set 500000条值,本次测试是ip为
192.168.4.16的主机调ip为192.168.4.19的memcache服务器,具体
调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Save data:begin
2013-04-29 20:28:42.439
2013-04-29 20:30:10.462
Save data:end 从上面数据看出set 500000数据用了1分多钟
</code></pre></div> </div>
</li>
<li>
<p>循环10000次,每次随机取1-500000中任意一个随机数,然后从上面set的值中get对应
数据,具体调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 2013-04-29 20:30:10.462
2013-04-29 20:30:12.085 由此看出随机取10000条数据用了1秒多的时间
</code></pre></div> </div>
</li>
</ul>
<h3 id="七运用memcached-client-for-java客户端编写客户端测试程序">七、运用memcached client for java客户端编写客户端测试程序</h3>
<p>memcached client for java下载地址为:</p>
<p><a href="http://github.com/gwhalin/Memcached-Java-Client/">http://github.com/gwhalin/Memcached-Java-Client/</a></p>
<ol>
<li>
<p>java客户端程序连接magent代理服务器测试</p>
<ul>
<li>
<p>循环500000次向memcache服务器set 500000条值,本次测试是ip为
192.168.4.16的主机调ip为192.168.4.19的memcache服务器,具体
调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> save begin:2013-04-29 10:09:04:0790
save end:2013-04-29 10:13:11:0721 从上面数据看出set 500000数据用了4分多钟
</code></pre></div> </div>
</li>
<li>
<p>循环10000次,每次随机取1-500000中任意一个随机数,然后从上面set的值中get对应
数据,具体调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> get begin:2013-04-29 10:13:11:0722
get end:2013-04-29 10:13:16:0678 由此看出随机取10000条数据用了4秒多的时间
</code></pre></div> </div>
</li>
</ul>
</li>
</ol>
<p>2.java客户端程序直接连接memcache服务器测试</p>
<ul>
<li>
<p>循环500000次向memcache服务器set 500000条值,本次测试是ip为
192.168.4.16的主机调ip为192.168.4.19的memcache服务器,具体
调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> save begin:2013-04-29 20:21:46:0195
save end:2013-04-29 20:25:06:0365 从上面数据看出set 500000数据用了3分多钟
</code></pre></div> </div>
</li>
<li>
<p>循环10000次,每次随机取1-500000中任意一个随机数,然后从上面set的值中get对应
数据,具体调用时间如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> get begin:2013-04-29 20:25:06:0366
get end:2013-04-29 20:25:10:0357 由此看出随机取10000条数据用了接近4秒的时间
</code></pre></div> </div>
</li>
</ul>
<h3 id="八说明">八、说明</h3>
<ol>
<li>
<p>从步骤七中测试结果可以看出,c++客户端调用memcache服务器在性能上明显优于java客户端,同时运用magent代理服务器时性能上还是有所降低</p>
</li>
<li>
<p>至于redis与memcache性能差异本文没有做对比,可参照redis文档中列出的测试数据加以对比,这里列下在网上看到一些大拿们列出的对比:</p>
<ul>
<li>
<p>性能对比:由于Redis只使用单核,而Memcached可以使用多核,所以平均每一个核上Redis在存储小数据时比Memcached性能更高。而在100k以上的数据中,Memcached性能要高于Redis,虽然Redis最近也在存储大数据的性能上进行优化,但是比起Memcached,还是稍有逊色</p>
</li>
<li>
<p>内存使用效率对比:使用简单的key-value存储的话,Memcached的内存利用率更高,而如果Redis采用hash结构来做key-value存储,由于其组合式的压缩,其内存利用率会高于Memcached</p>
</li>
<li>
<p>Redis支持服务器端的数据操作:Redis相比Memcached来说,拥有更多的数据结构和并支持更丰富的数据操作,通常在Memcached里,你需要将数据拿到客户端来进行类似的修改再set回去。这大大增加了网络IO的次数和数据体积。在Redis中,这些复杂的操作通常和一般的GET/SET一样高效。所以,如果需要缓存能够支持更复杂的结构和操作,那么Redis会是不错的选择</p>
</li>
</ul>
</li>
</ol>
2013年待研究学习事项
2013-04-25T00:00:00+00:00
http://www.blogways.net/blog/2013/04/25/2013-todo-list
<h3 id="列表">列表:</h3>
<table>
<thead>
<tr>
<th> 序号 </th>
<th> 研究内容 </th>
<th> 子内容 </th>
<th> 优先级 </th>
<th> 添加人 </th>
</tr>
</thead>
<tbody>
<tr>
<td> 1 </td>
<td> 敏捷开发环境搭建 </td>
<td> </td>
<td> 100 </td>
<td> </td>
</tr>
<tr>
<td> 1.1 </td>
<td> </td>
<td> GIT环境搭建 </td>
<td> 100</td>
<td></td>
</tr>
<tr>
<td> 1.2 </td>
<td> </td>
<td> Maven环境搭建 </td>
<td>100</td>
<td></td>
</tr>
<tr>
<td> 1.3 </td>
<td> </td>
<td> jenkins环境搭建 </td>
<td> 100</td>
<td></td>
</tr>
<tr>
<td> 1.4 </td>
<td> </td>
<td> 测试驱动开发(TDD)学习</td>
<td> 100</td>
<td></td>
</tr>
<tr>
<td> 1.5</td>
<td> </td>
<td> Scrum方法学习</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td> 2 </td>
<td> NoSQL研究 </td>
<td></td>
<td>100</td>
<td> </td>
</tr>
<tr>
<td>2.1</td>
<td></td>
<td>memcached研究</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>2.2</td>
<td></td>
<td>redis研究</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td>2.3</td>
<td></td>
<td>mongodb研究</td>
<td>100</td>
<td></td>
</tr>
<tr>
<td> 3 </td>
<td> 手机应用研究 </td>
<td></td>
<td>100</td>
<td> </td>
</tr>
<tr>
<td> 3.1 </td>
<td> </td>
<td> Sencha框架研究 </td>
<td>100</td>
<td> </td>
</tr>
<tr>
<td> 4 </td>
<td> 云化 </td>
<td></td>
<td>100</td>
<td> </td>
</tr>
<tr>
<td> 4.1 </td>
<td> </td>
<td> U-Cloud研究学习 </td>
<td>100</td>
<td></td>
</tr>
<!-- 在这里继续添加内容 BEGIN -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td>待添加</td>
</tr>
<!-- 在这里继续添加内容 END -->
<tr>
<td>9</td>
<td>杂记</td>
<td></td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.1</td>
<td></td>
<td>OSGi学习</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.2</td>
<td></td>
<td>工作流引擎——jBPM5/Activiti</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.3</td>
<td></td>
<td>规则引擎——drools</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.4</td>
<td></td>
<td>EAI/ESB组件研究</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.4</td>
<td></td>
<td>HTML5/CSS3特性学习</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.5</td>
<td></td>
<td>去 IOE 之 关系数据库MySQL化</td>
<td>80</td>
<td></td>
</tr>
<tr>
<td>9.6</td>
<td></td>
<td>实时/准实时数据同步实现</td>
<td>50</td>
<td></td>
</tr>
<tr>
<td>9.7</td>
<td></td>
<td>ArcGIS学习和研究</td>
<td>50</td>
<td></td>
</tr>
<!-- 在这里继续添加内容 BEGIN -->
<tr>
<td></td>
<td></td>
<td></td>
<td></td>
<td>待添加</td>
</tr>
<!-- 在这里继续添加内容 END -->
</tbody>
</table>
<h3 id="说明">说明</h3>
<ul>
<li>上表为大家共同维护,发现有什么好的东西就向里面添加。根据工作要求,定期讨论后续研究内容的优先级。</li>
<li>序号 9 开始为杂记,根据工作要求,定期调整 9 中的内容至序号 1-8 内。</li>
<li>优先级说明:100为分界线。100及其以上为需优先研究学习内容。</li>
</ul>
Maven 环境搭建
2013-04-23T00:00:00+00:00
http://www.blogways.net/blog/2013/04/23/maven
<h2 id="maven-安装-简介">Maven 安装 简介</h2>
<ul>
<li>
<p>Maven 安装</p>
<p>下载安装包
地址:http://maven.apache.org/download.html</p>
<p>Windows 安装:
本机jdk必需是1.5或者以上版本</p>
<p>解压安装包</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 目录结构:
bin Maven的运行脚本
boot Maven自己的类装载器
conf 该目录下包含了全局行为定制文件setting.xml
lib Maven运行时所需的类库
</code></pre></div> </div>
</li>
<li>
<p>配置环境变量</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> M2_HOME=安装目录
在path变量中增加%M2_HOME%\bin
检查安装是否正确
Mvn –v
能看到Maven和JDK的版本号为安装正确
试运行maven命令
mvn help:system
该命令将会下载help插件并运行它,且打印出Java系统属性和环境变量
</code></pre></div> </div>
</li>
<li>
<p>Maven名词解释</p>
<ol>
<li>Project:任何您想build的事物,Maven都可以认为它们是工程。这些工程被定义为工程对象模型(POM,Poject Object Model)。一个工程可以依赖其它的工程;一个工程也可以由多个子工程构成。</li>
<li>POM:POM(pom.xml)是Maven的核心文件,它是指示Maven如何工作的元数据文件,类似于Ant中的build.xml文件。POM文件位于每个工程的根目录中。</li>
<li>GroupId:groupId是一个工程的在全局中唯一的标识符,一般地,它就是工程名。groupId有利于使用一个完全的包名,将一个工程从其它有类似名称的工程里区别出来。</li>
<li>Artifact:artifact 是工程将要产生或需要使用的文件,它可以是jar文件,源文件,二进制文件,war文件,甚至是pom文件。每个artifact都由groupId和 artifactId组合的标识符唯一识别。需要被使用(依赖)的artifact都要放在仓库(见Repository)中,否则Maven无法找到 (识别)它们。</li>
<li>Dependency:为了能够build或运行,一个典型的Java工程会依赖其它的包。在Maven中,这些被依赖的包就被称为dependency。dependency一般是其它工程的artifact。</li>
<li>Plug-in:Maven是由插件组织的,它的每一个功能都是由插件提供的。插件提供goal(类似于Ant中的target),并根据在POM中找到的元数据去完成工作。主要的Maven插件要是由Java写成的,但它也支持用Beanshell或Ant脚本写成的插件。</li>
<li>Repository:仓库。</li>
</ol>
</li>
<li>
<p>配置文件</p>
<p>$user.home/.m2/repository/setting.xml
为用户范围的配置文件
$M2_HOME/conf/setting.xml
为全局范围的配置文件,修改后将影响本机所有用户的配置
建议:只修改用户级别的配置,既不影响其它用户,也不影响后期升级。</p>
</li>
<li>
<p>配置介绍</p>
<ol>
<li>localRepository: 自定义本地库路径,默认在$user.home/.m2中</li>
<li>interactiveMode:</li>
<li>offline:是否每次编译都去查找远程中心库</li>
<li>pluginGroups:插件组,例如org.mortbay.jetty</li>
<li>proxies:通过代理访问外部库</li>
<li>servers:集成认证服务,例如集成Tomcat</li>
<li>mirrors:镜像库,可以指定内部中心库</li>
<li>profiles:个性配置,需要在Activation标签中激活</li>
<li>activeProfiles:表示激活的profile</li>
</ol>
</li>
<li>
<p>maven仓库</p>
<ol>
<li>远程公用仓库
Maven内置了远程公用仓库:http://repo1.maven.org/maven2这个公共仓库是由Maven自己维护,里面有大量的常用类库,并包含了世界上大部分流行的开源项目构件。目前是以java为主。</li>
<li>内部中心仓库
也称私有共享仓库(私服)。一般是由公司自己设立的,只为本公司内部共享使用。它既可以作为公司内部构件协作和存档,也可作为公用类库镜像缓存,减少在外部访问和下载的频率。Nexus和Artifactory均可搭建仓库服务器。但后者支持LDAP认证,这样就可以将私有仓库的认证集成到公司已经有的LDAP认证服务器。内部中心库又可以连接第三方库,例如Jboss中心库、Spring中心库,以随时获得最新版本的第三方构件。</li>
<li>本地仓库
Maven会将工程中依赖的构件(Jar包)从远程下载到本机一个目录下管理,通常默认在$user.home/.m2/repository下。
修改本地库位置:在$M2_HOME/conf/setting.xml文件的<localRepository>元素中指定路径,例如:<localRepository>D:/my_repository</localRepository></localRepository></li>
</ol>
</li>
<li>
<p>Maven常用命令</p>
<ol>
<li>检测Maven、JDK版本
mvn –v 或者 mvn -version</li>
<li>获取帮助选项
mvn –h 或者 mvn –help</li>
<li>显示详细错误信息
mvn –e</li>
<li>创建Java项目
mvn archetype:create -DgroupId=${groupId} -DartifactId=${artifactId}
示例:mvn archetype:create -DgroupId=com.howsun -DartifactId=myApp-Dversion=0.1</li>
<li>创建Web项目
mvn archetype:create -DgroupId=${packageName} -DartifactId=${webappName} -DarchetypeArtifactId=maven-archetype-webapp</li>
<li>创建其它项目(例如SSH、JPA、JSF、Seam…)
mvn archetype:generate然后根据提示选择项目骨架、groupid、artifactid、版本号…Maven3已有上百个项目骨架</li>
<li>转换成Eclipse工程
mvn eclipse:eclipse
mvn eclipse:clean //清除Eclipse设置信息
转换成idea项目:mvn idea:ide</li>
<li>编译
mvn compile</li>
<li>编译测试代码
mvn test-compile</li>
<li>产生Site:
mvn site</li>
<li>测试
mvn test //运行测试
mvn test -Dtest=${类名} //单独运行测试类</li>
<li>清除
mvn clean //将清除原来编译的结果</li>
<li>打包
mvn packagemvn package –Dmaven.test.skip=true //打包时不执行测试</li>
<li>发布
mvn install //将项目打包成构件安装到本地仓库
mvn deploy //发布到本地仓库或服务器(例如Tomcat、Jboss)</li>
<li>手动添加构件到仓库
mvn install:install-file -Dfile=${jar包文件位置} -DgroupId=${groupId} -DartifactId=${artifactId} -Dversion=${版本号} -Dpackaging=jar -DgeneratePom=${是否同时创建pom文件}</li>
<li>复制依赖构件到相应目录
mvn dependency:copy-dependencies -DoutputDirectory=${目标目录} -DexcludeScope=${scope} -Dsilent=true
示例:
mvn dependency:copy-dependencies
-DoutputDirectory=WebRoot/WEB-INF/lib
-Dsilent=true
-DincludeScope=runtime</li>
<li>显示一个插件的详细信息(configuration, goals等):
mvn help:describe -Dplugin=pluginName –Ddetail</li>
</ol>
</li>
<li>
<p>工程配置文件 pom.xml</p>
<p>是Maven项目的核心配置文件,位于每个工程的根目录,指示Maven工作的元数据文件。
节点介绍</p>
<ol>
<li>
<project> :文件的根节点 .
</project>
</li>
<li>
<modelversion> : pom.xml使用的对象模型版本 .
</modelversion>
</li>
<li>
<groupId> :创建项目的组织或团体的唯一 Id.
</groupId>
</li>
<li>
<artifactId> :项目的唯一 Id, 可视为项目名 .
</artifactId>
</li>
<li>
<packaging> :打包类型,一般有JAR,WAR,EAR 等
</packaging>
</li>
<li>
<version> :产品的版本号 .
</version>
</li>
<li>
<name> :项目的显示名,常用于 Maven 生成的文档。
</name>
</li>
<li>
<url> :组织的站点,常用于 Maven 生成的文档。
</url>
</li>
<li>
<description> :项目描述,常用于 Maven 生成的文档。
</description>
</li>
<li>
<dependencies>:构件依赖<parent>:模型继承
</parent></dependencies>
</li>
<li>
<dependencyManagement>:依赖管理
</dependencyManagement>
</li>
<li>
<reporting>:创建报告
</reporting>
</li>
<li>
<build>:构建
</build>
</li>
<li>
<repositories>:引用第三方仓库
</repositories>
</li>
<li>
<licenses>:许可
</licenses>
</li>
</ol>
</li>
<li>
<p>Eclipse中使用maven</p>
<ol>
<li>安装M2Eclipse插件
Help -> InstallNewSoftware -> Work with -> Add
核心组件:</li>
<li>Name:m2e
Location:http://m2eclipse.sonatype.org/sites/m2e</li>
<li>扩展组件:
Name: m2e-extras
Location:http://m2eclipse.sonatype.org/sites/m2e-extras</li>
</ol>
</li>
</ul>
<h2 id="示例">示例</h2>
<ul>
<li>
<p>命令行创建java工程</p>
<ol>
<li>
<p>command 窗口执行</p>
<p>F:\maven>mvn archetype:create -DgroupId=com.ailk.test -DartifactId=hello -DpackageName=com.ailk.test -Dversion=1.0
目录会下生成一个 hello 的文件夹
Hello/src/main/java 是源文件目录
Hello/src/test/java 是测试文件目录
Hello/target 是编译目标文件夹
Hello/pom.xml 是工程的 maven 配置文件</p>
</li>
<li>
<p>Setting 配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <?xml version="1.0" encoding="UTF-8"?>
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<localRepository>F:\maven\repo</localRepository>
<pluginGroups>
</pluginGroups>
<proxies>
</proxies>
<servers>
<server>
<id>sphost</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>spsnap</id>
<username>admin</username>
<password>admin123</password>
</server>
</servers>
<mirrors>
<mirror>
<id>nexus-public</id>
<mirrorOf>public</mirrorOf>
<!--url>http://127.0.0.1:8081/nexus/content/groups/mygroup</url-->
<url>http://192.168.4.19:8081/nexus/content/groups/spgroup/</url>
</mirror>
</mirrors>
<profiles>
<profile>
<id>nexus</id>
<repositories>
<repository>
<id>nexus</id>
<name>local private nexus</name>
<url>http://192.168.4.19:8081/nexus/content/groups/spgroup/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>nexus</id>
<name>local private nexus</name>
<url>http://192.168.4.19:8081/nexus/content/groups/spgroup/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</profile>
</profiles>
<activeProfiles>
<activeProfile>nexus</activeProfile>
</activeProfiles>
</settings>
</code></pre></div> </div>
</li>
<li>
<p>Pom 配置</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ailk.test</groupId>
<artifactId>hello</artifactId>
<version>1.0</version>
<packaging>jar</packaging>
<name>hello</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.5</source>
<target>1.5</target>
</configuration>
</plugin>
</plugins>
</pluginManagement>
<extensions>
<extension>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ftp</artifactId>
<version>1.0-alpha-6</version>
</extension>
</extensions>
</build>
<!-- 配置远程发布到私服,mvn deploy -->
<distributionManagement>
<repository>
<id>sphost</id>
<name>Nexus Release Repository</name>
<url>http://192.168.4.19:8081/nexus/content/repositories/sphost/</url>
</repository>
<snapshotRepository>
<id>spsnap</id>
<name>Nexus Snapshot Repository</name>
<url>http://192.168.4.19:8081/nexus/content/repositories/spsnap/</url>
</snapshotRepository>
</distributionManagement>
</project>
</code></pre></div> </div>
</li>
<li>
<p>清除、编译、测试、打包、安装到本机、发布到远程仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> F:\maven>cd hello
F:\maven\hello>mvn clean compile test package install deploy
</code></pre></div> </div>
</li>
</ol>
</li>
<li>
<p>Eclipse创建web工程</p>
<ol>
<li>
<p>创建 maven web 工程</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> File/new/project/maven project/next/next
选择创建maven web项目(maven-archetype-webapp)
Group id: com.ailk.web.test
Artiface id: hello
Version: 0.0.1-SNAPSHOT
Package: com.ailk.web.test.hello
创建完按下面目录添加serverlet、struts.xml、web.xml以及一些页面
src/main/java java源文件
src/main/resources struts等配置文件目录
src/main/webapp 页面以及web.xml文件目录
target 编译和打包目录
</code></pre></div> </div>
</li>
<li>
<p>setting.xml 配置</p>
<p>添加一个 tomcat server</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <servers>
<server>
<id>sphost</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>spsnap</id>
<username>admin</username>
<password>admin123</password>
</server>
<server>
<id>tomcat</id>
<username>admin</username>
<password>admin</password>
</server>
</servers>
</code></pre></div> </div>
</li>
<li>
<p>pom.xml 配置</p>
<p>添加编译 tomcat 插件</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <build>
<finalName>hello</finalName>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>tomcat-maven-plugin</artifactId>
<version>1.0-beta-1</version>
<configuration>
<url>http://localhost:8080/manager/text</url>
<server>tomcat</server>
</configuration>
</plugin>
</plugins>
</build>
</code></pre></div> </div>
</li>
<li>
<p>清除、编译、测试、打包、安装本地仓库、发布远程仓库、部署 tomcat 服务器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 项目右键/run as/maven build.../goals:clean compile test package install deploy site/run
</code></pre></div> </div>
</li>
</ol>
</li>
</ul>
Jenkins+git+maven+junit 二
2013-04-23T00:00:00+00:00
http://www.blogways.net/blog/2013/04/23/jenkins-git-maven-junit-2
<h3 id="一概述">一、概述</h3>
<p>对于可持续集成描述及Jenkins介绍及安装在前面文档中有所介绍,本文就不做说明,本文只介绍如何实现Jenkins如何自动取git上web工程代码,maven实现编译及打包,最后Jenkins负责将war包发布到tomcat中</p>
<h3 id="二jenkins配置及使用">二、Jenkins配置及使用</h3>
<p>tomcat中tomcat-users.xml文件添加如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><role rolename="manager-gui"/>
<role rolename="manager-script"/>
<user username="tomcat" password="tomcat" roles="manager-gui,
manager-script"/> 更改git仓库中pom.xml 文件,添加如下内容:
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat6-maven-plugin</artifactId>
<version>2.0</version>
<configuration>
<url>http://localhost:8080/manager</url>
<server>tomcat</server>
<!-- <uriEncoding>utf-8</uriEncoding> -->
<warSourceDirectory>WebContent</warSourceDirectory>
</configuration>
</plugin> Maven安装目录下settings.xml文件添加如下:
<server>
<id>tomcat</id>
<username>tomcat</username>
<password>tomcat</password>
</server> 其中<id>tomcat</id> 与pom.xml 文件中<server>tomcat</server> 要一致
</code></pre></div></div>
<ul>
<li>
<p>进入“系统管理”->“插件管理”</p>
<p>1.选择“可选插件”tab页,找到“Deploy to container Plugin”插件并安装,为
Jenkins部署tomcat工程所用</p>
</li>
<li>回到Jenkins首页,点击“新Job”,输入job名称如:MavenWeb,选择“构建一个maven2/3项目”</li>
<li>选择任务“MavenWeb”,点击“配置”进入任务配置界面,源代码管理中选择git,并输入仓库路径如:/home/git/MavenWeb/mytest,Branches to build根据自己需要输入,这里我取得主分支代码,输入的为master</li>
<li>
<p>“构建触发器”选择项下我选择的为“Poll SCM”,含义为在指定作业时间内有代码更新的时候去取代码,这里”Build periodically“含义为不管是否有代码更新在指定作业时间内都去取代码,”Build whenever a SNAPSHOT dependency is built“含义为当构建成功后,项目jar包会发到maven二方库上去
-”日程表“我这里配的是<em>/1 * * * *,代表每隔一分钟取一次,这里五个 * 号从左到右分别
分 时 日 月 年,相同 * 号段内用”,“号隔开,如:</em> * 8,20 * * *含义为每天8点、20点取代码
-”构建后操作“Editable Email NOtification” 其中Project Recipient List
可以配置收件人信箱,点击“高级”,“Add a Trigger”中可以选择对应触发器,我这里用到了构建失败(Failure),和测试报错(系统不稳定Unstable)两项,收件人选择了“Send To Recipient List”和提交者“Send To Committers”</p>
</li>
<li>“Deploy war/ear to a container” 中“WAR/EAR files”输入target/mytest.war,“Context path”输入MyApp(为项目访问contextPath),“Container”我这里用的tomcat6,所以选择tomcat6,“Tomcat URL”中输入tomcat访问地址如:http://192.168.4.19:8001</li>
<li>git上建立仓库将本地现有Maven+junit web项目push到git仓库,打开Jenkins主页进入MavenPrj任务,几分钟后就能看到构建任务及结果,</li>
<li>构建成功后输入http://192.168.4.19:8001/MyApp即可访问项目首页</li>
</ul>
Nexus 环境搭建
2013-04-22T00:00:00+00:00
http://www.blogways.net/blog/2013/04/22/nexus
<h2 id="安装">安装</h2>
<p>下载:http://nexus.sonatype.org/downloads/</p>
<p>解压到服务器上</p>
<ol>
<li>
<p>unix 启动:</p>
<p>目录:./nexus-2.3.1-01-bundle/nexus-2.3.1-01/bin/jsw/linux-x86-64</p>
<p>启动 nexus 服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> [spdev@slave2 linux-x86-64]$ sh nexus start
Starting Nexus OSS...
Started Nexus OSS
</code></pre></div> </div>
<p>从控制台可以看到 nexus 已经启动成功,可以在浏览器中访问</p>
<p>http://192.168.4.19:8081/nexus/index.html#view-repositories</p>
<p>login:admin/admin123</p>
<p>里面自带一些已经创建好的仓库供参考</p>
</li>
<li>
<p>window 启动:</p>
<p>目录:\nexus-2.3.1-01-bundle\nexus-2.3.1-01\bin\jsw\windows-x86-64</p>
<p>启动 nexus 服务:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> start-nexus.bat
wrapper | The nexus service is already running with status: RUNNING
</code></pre></div> </div>
<p>访问同上,nexus 默认端口是 8081 ,如果有冲突可以在 \nexus-2.3.1-01-bundle\nexus-2.3.1-01\conf\nexus.properties 配置文件里面修改</p>
</li>
</ol>
<h2 id="创建我们自己的仓库">创建我们自己的仓库</h2>
<ol>
<li>
<p>创建主机仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Add/Hosted Repository
Repository ID: Sphost
Repository Name: SP Host
Repository Policy: Release
</code></pre></div> </div>
</li>
<li>
<p>创建代理仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Add/Proxy Repository
Repository ID: spproxy
Repository Name: SP Proxy
Remote Storage Location: http://repo1.maven.org/maven2/
Download remote indexes: true
</code></pre></div> </div>
</li>
<li>
<p>创建快照仓库</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Add/Hosted Repository
Repository ID: Spsnap
Repository Name: SP Snap
Repository Policy: snapshot
</code></pre></div> </div>
</li>
<li>
<p>创建仓库组</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> add/Repository Group
Group ID:spgroup
Group Name: SP Group
Publish URL: TRUE
Ordered group repositories: SP Host、SP Proxy、SP Snap
</code></pre></div> </div>
<p>每个仓库后面都对应一个 repository path,这个地址在配 maven 的 setting.xml 跟项目的 pom.xml 文件里面的地址要对应上,否则会下载不走主机仓库或者没法发布到主机仓库。由于现在主机连不上网,代理仓库无法连到网上下载,本地maven从代理仓库下不了的时候会默认自动从中心库上下载所需要的jar包。</p>
</li>
</ol>
windows 下安装jekyll
2013-04-18T00:00:00+00:00
http://www.blogways.net/blog/2013/04/18/install-jekyll-on-windows
<h3 id="一ruby安装">一、ruby安装</h3>
<ol>
<li>下载 <a href="http://www.ruby-lang.org/en/">ruby</a></li>
<li>配置环境变量,path中添加ruby安装环境变量</li>
<li>执行 ruby –version 检查ruby是否安装成功</li>
</ol>
<h3 id="二devkit">二、DevKit</h3>
<ol>
<li>下载 <a href="https://github.com/oneclick/rubyinstaller/downloads/">DevKit</a></li>
<li>解压DevKit,命令行下到DevKit目录,执行ruby dk.rb init ruby dk.rb install</li>
</ol>
<h3 id="三安装jekyll">三、安装jekyll</h3>
<ol>
<li>执行gem install jekyll</li>
<li>执行gem install rdiscount</li>
<li>
<p>打开命令窗口到博客工程根目录下执行jekyll –server,如果此处报字符集错误,请先设置环境变量执行
set LC_ALL=en_US.UTF-8,set LANG=en_US.UTF-8,2.0及以后版本此方法不行,需修将ruby安装
目录下lib\ruby\gems\2.0.0\gems\jekyll-1.2.0\lib\jekyll下convertible.rb文件中
self.content = File.read(File.join(base, name))改为
self.content = File.read(File.join(base, name), :encoding => “utf-8”),lib\ruby\gems\2.0.0\gems\jekyll-1.2.0\lib\jekyll\tags下include.rb文件中
source = File.read(File.join(includes_dir, @file))改为
source = File.read(File.join(includes_dir, @file), :encoding => “utf-8”),然后再执行jekyll –server</p>
</li>
<li>服务启动成功后,在浏览器访问<code class="language-plaintext highlighter-rouge">localhost:4000</code>,显示博客列表</li>
</ol>
Jenkins+git+maven+junit 一
2013-04-17T00:00:00+00:00
http://www.blogways.net/blog/2013/04/17/jenkins-git-maven-junit
<h3 id="一持续集成概述">一、持续集成概述</h3>
<p>随着软件开发复杂度的不断提高,团队开发成员间如何更好地协同工作以确保软件开发的质量已经慢慢成为开发过程中不可回避的问题。尤其是近些年来,敏捷(Agile) 在软件工程领域越来越红火,如何能再不断变化的需求中快速适应和保证软件的质量也显得尤其的重要。
持续集成正是针对这一类问题的一种软件开发实践。它倡导团队开发成员必须经常集成他们的工作,甚至每天都可能发生多次集成。而每次的集成都是通过自动化的构建来验证,包括自动编译、发布和测试,从而尽快地发现集成错误,让团队能够更快的开发内聚的软件。
持续集成的核心价值在于:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.持续集成中的任何一个环节都是自动完成的,无需太多的人工干预,有利于减少重复过程以节省时间、
费用和工作量;
2.持续集成保障了每个时间点上团队成员提交的代码是能成功集成的。换言之,任何时间点都能第一时
间发现软件的集成问题,使任意时间发布可部署的软件成为了可能;
3.持续集成还能利于软件本身的发展趋势,这点在需求不明确或是频繁性变更的情景中尤其重要,持
续集成的质量能帮助团队进行有效决策,同时建立团队对开发产品的信心。
</code></pre></div></div>
<h3 id="二持续集成的原则">二、持续集成的原则</h3>
<p>业界普遍认同的持续集成的原则包括:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.需要版本控制软件保障团队成员提交的代码不会导致集成失败。常用的版本控制软件有 CVS、
Subversion、git 等,本文中用的是git;
2.开发人员必须及时向版本控制库中提交代码,也必须经常性地从版本控制库中更新代码到本地;
3.需要有专门的集成服务器来执行集成构建。根据项目的具体实际,集成构建可以被软件的修改来
直接触发,也可以定时启动,如每半个小时构建一次;
4.必须保证构建的成功。如果构建失败,修复构建过程中的错误是优先级最高的工作。一旦修复,
需要手动启动一次构建。
</code></pre></div></div>
<h3 id="三持续集成系统的组成">三、持续集成系统的组成</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>1.一个自动构建过程,包括自动编译、分发、部署和测试等,本文中用的是maven+junit。
2.一个代码存储库,即需要版本控制软件来保障代码的可维护性,同时作为构建过程的素材库,
本文中用的是git。
3.一个持续集成服务器。本文中用到的是jenkins。
</code></pre></div></div>
<h3 id="四jenkins简介及安装">四、Jenkins简介及安装</h3>
<p>git、maven、junit本博客其他篇章中都有所介绍,所以今天我们主要介绍Jenkins的应用:
Jenkins 是一个开源项目,提供了一种易于使用的持续集成系统,使开发者从繁杂的集成中解脱出来,
专注于更为重要的业务逻辑实现上。同时 Jenkins 能实施监控集成中存在的错误,提供详细的日志文
件和提醒功能,还能用图表的形式形象地展示项目构建的趋势和稳定性。</p>
<ul>
<li>下载Jenkins,<a href="http://jenkins-ci.org/">http://jenkins-ci.org/ </a></li>
<li>
<p>Jenkins 安装:本文介绍安装在linux主机上通过命令行安装</p>
<p>1.将下载的jenkins.war文件上传到linux主机安装目录下
如:/home/spdev
2.执行java -jar jenkins.war
3.打开ie输入地址 http://hoestname:8080 (hostname为主机 ip)即能访问Jenkins,如自己设定端口可执行:java -jar jenkins.war –httpPort= port</p>
</li>
</ul>
<h3 id="五jenkins配置及使用">五、Jenkins配置及使用</h3>
<ul>
<li>
<p>进入“系统管理”->“插件管理”</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1.选择“已安装”tab页查看发下maven插件已经安装,git、junit插件没有安装
2.选择“可选插件”tab页,在Filter输入框输入git,过滤条件后将git相关插件安装,同样
操作安装junit插件
3.选择“可选插件”tab页,找到“Jenkins Email Extension Plugin”插件并安装,此插件
是后面发布及测试法邮件使用
</code></pre></div> </div>
</li>
<li>
<p>进入“系统管理”->“系统设置”</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> 1.选择“JDK”安装,定义别名,JAVA_HOME输入JDK安装目录
2.选择“Git”安装,定义别名,安装目录输入Git安装目录
3.选择“Maven”安装,定义别名,安装目录输入MAVEN_HOME安装目录
4.“System Admin e-mail address”输入系统管理员邮箱地址
5.“Extended E-mail Notification”标签为插件“Jenkins Email Extension Plugin“里
内容,为邮件通知设置,这里我的配置如下图: <img src="/images/post/jenkins-setting.jpg" width="400" height="300" alt="image"/>
</code></pre></div> </div>
</li>
<li>回到Jenkins首页,点击“新Job”,输入job名称如:MavenPrj,选择“构建一个maven2/3项目”</li>
<li>选择任务“MavenPrj”,点击“配置”进入任务配置界面,源代码管理中选择git,并输入仓库路径如:/home/git/MavenPrj/mytest,Branches to build根据自己需要输入,这里我取得主分支代码,输入的为master</li>
<li>“构建触发器”选择项下我选择的为“Poll SCM”,含义为在指定作业时间内有代码更新的时候去取代码,这里”Build periodically“含义为不管是否有代码更新在指定作业时间内都去取代码,”Build whenever a SNAPSHOT dependency is built“含义为当构建成功后,项目jar包会发到maven二方库上去</li>
<li>
<p>”日程表“我这里配的是<em>/1 * * * *,代表每隔一分钟取一次,这里五个 * 号从左到右分别
分 时 日 月 年,相同 * 号段内用”,“号隔开,如:</em> * 8,20 * * *含义为每天8点、20点取代码
-”构建后操作“Editable Email NOtification” 其中Project Recipient List
可以配置收件人信箱,点击“高级”,“Add a Trigger”中可以选择对应触发器,我这里用到了构建失败(Failure),和测试报错(系统不稳定Unstable)两项,收件人选择了“Send To Recipient List”和提交者“Send To Committers”</p>
</li>
<li>git上建立仓库将本地现有Maven+junit java项目push到git仓库,打开Jenkins主页进入MavenPrj任务,几分钟后就能看到构建任务及测试结果了,下面是构建完的效果图:</li>
</ul>
<p><img src="/images/post/jenkins-setting2.jpg" width="400" height="300" alt="image" /></p>
JUnit4 使用进阶五
2013-04-15T00:00:00+00:00
http://www.blogways.net/blog/2013/04/15/junit-usage-5
<p>在<a href="junit-usage-2.html">进阶二</a>中,我们介绍了<code class="language-plaintext highlighter-rouge">@Rule</code>的两个用法,对异常进行检查和对超时时间的设定。其实,Rule还有很多用途,本文将做进一步介绍。</p>
<h3 id="rule的应用">Rule的应用</h3>
<p>Rule将在框架的很多方面提供解决方案。下面一一举例:</p>
<ol>
<li>
<p><strong>TemporaryFolder</strong></p>
<p><code class="language-plaintext highlighter-rouge">TemporaryFolder</code>作为Rule,可以运行在测试过程中创建临时文件或者临时目录,当测试结束后,框架会自动删除。</p>
<p>见实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public static class HasTempFolder {
@Rule
public TemporaryFolder folder = new TemporaryFolder();
@Test
public void testUsingTempFolder() throws IOException {
File createdFile = folder.newFile("myfile.txt");
File createdFolder = folder.newFolder("subfolder");
// ...
}
}
</code></pre></div> </div>
<ul>
<li><code class="language-plaintext highlighter-rouge">TemporaryFolder#newFolder(String... folderNames)</code>可以根据输入的参数创建目录。如果是多级目录,可以递归创建。</li>
<li><code class="language-plaintext highlighter-rouge">TemporaryFolder#newFile()</code>可以创建一个随机名字的临时文件;</li>
<li><code class="language-plaintext highlighter-rouge">TemporaryFolder##newFolder()</code> 可以创建一个随机名字的临时目录。</li>
</ul>
</li>
<li>
<p><strong>ExternalResource</strong></p>
<p><code class="language-plaintext highlighter-rouge">ExternalResource</code>可以设置测试前后需要做的事情(比如:文件、socket、服务、数据库的连接与关闭)。这个我们在之前的<a href="junit-usage-2.html">进阶二</a>中稍有提及。</p>
<p>见实例:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public static class UsesExternalResource {
Server myServer = new Server();
@Rule
public ExternalResource resource = new ExternalResource() {
@Override
protected void before() throws Throwable {
myServer.connect();
};
@Override
protected void after() {
myServer.disconnect();
};
};
@Test
public void testFoo() {
new Client().run(myServer);
}
}
</code></pre></div> </div>
<ul>
<li><code class="language-plaintext highlighter-rouge">ExternalResource#before</code>会在每个测试之前处理;<code class="language-plaintext highlighter-rouge">#after</code>会在每个测试之后处理;</li>
<li>关于<code class="language-plaintext highlighter-rouge">ExternalResource</code>与<code class="language-plaintext highlighter-rouge">@Before</code>已经<code class="language-plaintext highlighter-rouge">@After</code>等标记步骤的执行顺序,我们会在本文后面部分介绍。</li>
</ul>
</li>
<li>
<p><strong>ErrorCollector</strong></p>
<p><code class="language-plaintext highlighter-rouge">ErrorCollector</code>这个Rule,在出现一个错误后,还可以让测试继续进行下去。</p>
<p>他提供三个方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> checkThat(final T value, Matcher<T> matcher)
checkSucceeds(Callable<Object> callable)
addError(Throwable error)
</code></pre></div> </div>
<p>前面两个是用来处理断言的,最后一个是添加错误至错误列表中。</p>
<p>看下面例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package mytest;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import java.util.concurrent.Callable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ErrorCollector;
public class JUnitCoreErrorControllerRuleTest {
private final int multiplesOf2[] = { 0, 2, 4, 7, 8, 11, 12 };
@Rule
public ErrorCollector errorCollector = new ErrorCollector();
/*
* 下面这个测试,会报告两个failures。这一点和下面的checkSucceeds测试不同
*/
@Test
public void testMultiplesOf2() {
int multiple = 0;
for (int multipleOf2 : multiplesOf2) {
// Will count the number of issues in this list
// - 3*2 = 6 not 7, 5*2 = 10 not 11 : 2 Failures
errorCollector.checkThat(2 * multiple, is(multipleOf2));
multiple++;
}
}
/*
* 下面代码中有两个断言会失败,但每次运行JUnit框架只会报告一个。这一点和上面的checkThat测试不同,可以对比一下。
*/
@Test
public void testCallableMultiples() {
errorCollector.checkSucceeds(new Callable<Object>() {
public Object call() throws Exception {
assertThat(2 * 2, is(5));
assertThat(2 * 3, is(6));
assertThat(2 * 4, is(8));
assertThat(2 * 5, is(9));
return null;
}
});
}
/*
* 下面运行时,会报告2个错误
*/
@Test
public void testAddingAnError() {
assertThat(2 * 2, is(4));
errorCollector.addError(new Throwable("Error Collector added an error"));
assertThat(2 * 3, is(6));
errorCollector.addError(new Throwable(
"Error Collector added a second error"));
}
}
</code></pre></div> </div>
<p>运行结果,类似下面:</p>
Failed tests:
testCallableMultiples(mytest.JUnitCoreErrorControllerRuleTest):
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Expected: is <5>
but: was <4>
</code></pre></div> </div>
</li>
</ol>
testMultiplesOf2(mytest.JUnitCoreErrorControllerRuleTest):
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Expected: is <7>
but: was <6>
</code></pre></div></div>
testMultiplesOf2(mytest.JUnitCoreErrorControllerRuleTest):
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Expected: is <11>
but: was <10>
Tests in error:
testAddingAnError(tangzhi.mytest.JUnitCoreErrorControllerRuleTest): Error Collector added an error
</code></pre></div></div>
testAddingAnError(tangzhi.mytest.JUnitCoreErrorControllerRuleTest): Error Collector added a second error
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>从这个例子,可以看出:
* `ErrorCollector#checkThat` 会报告测试中的每一个failures
* `ErrorCollector#checkSucceeds` 只会检查是否成功,如果不成功,只报告第一个导致不成功的failure
* `ErrorCollector#addError` 是添加一个错误(error)。
</code></pre></div></div>
<ol>
<li>
<p><strong>Verifier</strong></p>
<p>如果,你想在每个测试之后,甚至是在<code class="language-plaintext highlighter-rouge">@After</code>之后,想检查些什么,就可以使用<code class="language-plaintext highlighter-rouge">Verifier</code>这个Rule.</p>
<p>看例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> private static String sequence;
public static class UsesVerifier {
@Rule
public Verifier collector = new Verifier() {
@Override
protected void verify() {
sequence += " verify ";
}
};
@Test
public void example() {
sequence += "test";
}
@Test
public void example2() {
sequence += "test2";
}
@After
public void after() {
sequence += " after";
}
}
@Test
public void verifierRunsAfterTest() {
sequence = "";
assertThat(testResult(UsesVerifier.class), isSuccessful());
assertEquals("test after verify test2 after verify ", sequence);
}
</code></pre></div> </div>
<p>从上面例子可以看出:<code class="language-plaintext highlighter-rouge">Verifier#verify</code>针对每个测试都会运行一次,并且运行在<code class="language-plaintext highlighter-rouge">@After</code>步骤之后。</p>
<p>需要说明:如果某测试出现失败(fail),那么这个测试之后就不会做<code class="language-plaintext highlighter-rouge">verify</code>,这一点,可以结合下面的例子看出。</p>
</li>
<li>
<p><strong>TestWatcher</strong></p>
<p>对测试的每个步骤进行监控。</p>
<p>看例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> package tangzhi.mytest;
import static org.junit.Assert.*;
import static org.hamcrest.CoreMatchers.*;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.rules.Verifier;
import org.junit.runner.Description;
import org.junit.runners.model.Statement;
public class WatchmanTest {
private static String watchedLog;
@Rule
public TestRule watchman = new TestWatcher() {
@Override
public Statement apply(Statement base, Description description) {
Statement s = super.apply(base, description);
watchedLog="";
System.out.println("watch apply.");
return s;
}
@Override
protected void succeeded(Description description) {
watchedLog += description.getDisplayName() + " " + "success!";
System.out.println("watch succeed:"+watchedLog);
}
@Override
protected void failed(Throwable e, Description description) {
watchedLog += description.getDisplayName() + " " + e.getClass().getSimpleName();
System.out.println("watch failed:"+watchedLog);
}
@Override
protected void starting(Description description) {
super.starting(description);
System.out.println("watch starting.");
}
@Override
protected void finished(Description description) {
super.finished(description);
System.out.println("watch finished.");
}
};
@Rule
public Verifier collector = new Verifier() {
@Override
protected void verify() {
System.out.println("@Verify:"+watchedLog);
}
};
@Test
public void fails() {
System.out.println("in fails");
assertThat("ssss", is("sss"));
}
@Test
public void succeeds() {
System.out.println("in succeeds");
}
@After
public void after() {
System.out.println("@After");
}
}
</code></pre></div> </div>
<p>运行后,日志如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> watch apply.
watch starting.
in succeeds
@After
watch succeed:succeeds(tangzhi.mytest.WatchmanTest) success!
watch finished.
@Verify:succeeds(tangzhi.mytest.WatchmanTest) success!
watch apply.
watch starting.
in fails
@After
watch failed:fails(tangzhi.mytest.WatchmanTest) AssertionError
watch finished.
</code></pre></div> </div>
</li>
<li>
<p><strong>TestName</strong></p>
<p><code class="language-plaintext highlighter-rouge">TestName</code>可以获取当前测试方法的名字。</p>
<p>看例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public class NameRuleTest {
@Rule
public TestName name = new TestName();
@Test
public void testA() {
assertEquals("testA", name.getMethodName());
}
@Test
public void testB() {
assertEquals("testB", name.getMethodName());
}
}
</code></pre></div> </div>
<p>如果,是在参数化测试(Parameterized)中,使用了<code class="language-plaintext highlighter-rouge">@Parameters</code>,那么其<code class="language-plaintext highlighter-rouge">name</code>属性定义的方法名也将会被<code class="language-plaintext highlighter-rouge">TestName#getMethodName</code>获取。</p>
</li>
<li>
<p><strong>Timeout</strong></p>
<p>这个我们在前面介绍过,可以设置某个测试类,所有测试方法的超时时间。详见<a href="junit-usage-2.html">进阶二</a>。</p>
</li>
<li>
<p><strong>ExpectedException</strong></p>
<p>这个也在<a href="junit-usage-2.html">进阶二</a>中有介绍。</p>
</li>
<li>
<p><strong>ClassRule</strong></p>
<p>注释<code class="language-plaintext highlighter-rouge">@ClassRule</code>是类级别的,而不是方法级别的。</p>
<p>见下面例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> @RunWith(Suite.class)
@SuiteClasses({A.class, B.class, C.class})
public class UsesExternalResource {
public static Server myServer= new Server();
@ClassRule
public static ExternalResource resource= new ExternalResource() {
@Override
protected void before() throws Throwable {
myServer.connect();
};
@Override
protected void after() {
myServer.disconnect();
};
};
}
</code></pre></div> </div>
<p>在Suite所打包的几个类测试前后,会执行一遍<code class="language-plaintext highlighter-rouge">ClassRule</code>。</p>
</li>
<li>
<p><strong>RuleChain</strong></p>
</li>
</ol>
见例子:
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> public static class UseRuleChain {
@Rule
public TestRule chain= RuleChain
.outerRule(new LoggingRule("outer rule")
.around(new LoggingRule("middle rule")
.around(new LoggingRule("inner rule");
@Test
public void example() {
assertTrue(true);
}
}
</code></pre></div></div>
执行后,日志如下:
starting outer rule
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> starting middle rule
starting inner rule
finished inner rule
finished middle rule
finished outer rule
</code></pre></div></div>
JUnit4 使用进阶四
2013-04-15T00:00:00+00:00
http://www.blogways.net/blog/2013/04/15/junit-usage-4
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">Suite测试</a></li>
<li><a href="#2">Parameterized测试</a></li>
<li><a href="#3">Categories测试</a></li>
<li><a href="#4">Theories测试</a></li>
<li><a href="#5">第三方的Runner</a></li>
<li><a href="#6">小结</a></li>
</ol>
</dd>
</dl>
</div>
<p>在<a href="junit-usage-1.html">进阶一</a>,我们介绍了默认的JUnit4默认的运行器是<code class="language-plaintext highlighter-rouge">JUnit4</code>。本文,我们将继续向大家介绍JUnit4框架提供的另外几个非常实用的运行器(Runner):<code class="language-plaintext highlighter-rouge">Suite</code>、<code class="language-plaintext highlighter-rouge">Parameterized</code>、<code class="language-plaintext highlighter-rouge">Categories</code>、<code class="language-plaintext highlighter-rouge">Enclosed</code>和<code class="language-plaintext highlighter-rouge">Theories</code>。</p>
<p>在测试中,可以使用注释<code class="language-plaintext highlighter-rouge">@RunWith</code>来指定这些运行器,比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RunWith(Parameterized.class)
public class MyTest { …
</code></pre></div></div>
<p>好吧,下面我们来一一介绍吧!</p>
<h3 id="一suite测试"><a name="1"></a>一、Suite测试</h3>
<p>进行<code class="language-plaintext highlighter-rouge">Suite</code>测试可以将多个待测试的类,打包(Suite)一起测试。在入口测试类上加两个注释:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RunWith(Suite.class)
@SuiteClasses(TestClass1.class, ...)
</code></pre></div></div>
<p>当你运行这个入口测试类,框架就会把打包在一起的所有待测试类都测试一遍。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import org.junit.runner.RunWith;
import org.junit.runners.Suite;
@RunWith(Suite.class)
@Suite.SuiteClasses({
TestFeatureLogin.class,
TestFeatureLogout.class,
TestFeatureNavigate.class,
TestFeatureUpdate.class
})
public class FeatureTestSuite {
// the class remains empty,
// used only as a holder for the above annotations
}
</code></pre></div></div>
<p>框架运行那些待测试的类,是按他们在<code class="language-plaintext highlighter-rouge">@Suite.SuiteClasses</code>中罗列的顺序开始测试。</p>
<h3 id="二parameterized测试"><a name="2"></a>二、Parameterized测试</h3>
<p>很多时候,需要用很多测试数据进行多次测试。怎么办?通过复制粘贴代码来实现?累…</p>
<p>针对这种情况,JUnit4框架提供<code class="language-plaintext highlighter-rouge">Parameterized</code>测试器(<code class="language-plaintext highlighter-rouge">Runner</code>)来实现这种需求。</p>
<p>看下面这个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package mytest;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
</code></pre></div></div>
<p>很简单,很方便是吧!</p>
<ol>
<li>用<code class="language-plaintext highlighter-rouge">@Parameters</code>来标记,我们为测试准备的数据</li>
<li>定义一个构造函数,构造函数的参数顺序和准备数据的顺序一致</li>
<li>写你需要的测试方法,用<code class="language-plaintext highlighter-rouge">@Test</code>注释</li>
<li>运行起来,每个数据都会测试一遍</li>
</ol>
<p>如果,你不习惯去写那样一个构造函数,也可以用下面的方法:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 2 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
@Parameter(0)
public int fInput;
@Parameter(1)
public int fExpected;
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
</code></pre></div></div>
<p>运行起来是不是很爽!</p>
<p>可能,你是一个完美主义者,对运行后报错信息不是很满意,现在报错时反馈的信息可能是这样:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test[3](mytest.FibonacciTest): expected:<2> but was:<0>
</code></pre></div></div>
<p>你想报错时显示测试的输入数据,希望报错类似如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>test[the 3 test, input:3,2](mytest.FibonacciTest): expected:<2> but was:<0>
</code></pre></div></div>
<p>要实现这点很简单,给<code class="language-plaintext highlighter-rouge">@Parameters</code>加个参数<code class="language-plaintext highlighter-rouge">name</code>,整个代码如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package mytest;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
@RunWith(Parameterized.class)
public class FibonacciTest {
@Parameters(name="the {index} test, input:{0},{1}")
public static Iterable<Object[]> data() {
return Arrays.asList(new Object[][] { { 0, 0 }, { 1, 1 }, { 2, 1 },
{ 3, 2 }, { 4, 3 }, { 5, 5 }, { 6, 8 } });
}
private int fInput;
private int fExpected;
public FibonacciTest(int input, int expected) {
fInput= input;
fExpected= expected;
}
@Test
public void test() {
assertEquals(fExpected, Fibonacci.compute(fInput));
}
}
</code></pre></div></div>
<p>在这里需要稍微解释一下<code class="language-plaintext highlighter-rouge">name</code>中的几个参数的含义:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>{index} 表示序号,测试数据在整个数据列表中的序号
{0} 表示第一个参数
{1} 表示第二个参数
...
{n} 表示第n+1个参数
</code></pre></div></div>
<h3 id="三categories测试"><a name="3"></a>三、Categories测试</h3>
<p>一个测试类里面包含很多待测试的方法,很多时候,我们需要把这些待测试的方法分类,某些时候测试某类方法,那么需要怎么做呢?</p>
<p>看下面这个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public interface FastTests { /* category marker */ }
public interface SlowTests { /* category marker */ }
public class A {
@Test
public void a() {
fail();
}
@Category(SlowTests.class)
@Test
public void b() {
}
}
@Category({SlowTests.class, FastTests.class})
public class B {
@Test
public void c() {
}
}
@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
// Will run A.b and B.c, but not A.a
}
</code></pre></div></div>
<p>在上面的例子中,将会测试<code class="language-plaintext highlighter-rouge">A.b</code>和<code class="language-plaintext highlighter-rouge">B.c</code>两个方法,不会测试<code class="language-plaintext highlighter-rouge">A.a</code>。</p>
<p>再看下面:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RunWith(Categories.class)
@IncludeCategory(SlowTests.class)
@ExcludeCategory(FastTests.class)
@SuiteClasses( { A.class, B.class }) // Note that Categories is a kind of Suite
public class SlowTestSuite {
// Will run A.b, but not A.a or B.c
}
</code></pre></div></div>
<p>这次,只会运行<code class="language-plaintext highlighter-rouge">A.b</code>,而不会运行<code class="language-plaintext highlighter-rouge">A.a</code>和<code class="language-plaintext highlighter-rouge">B.c</code>。</p>
<h3 id="四theories测试"><a name="4"></a>四、Theories测试</h3>
<p>结合前面<a href="junit-usage-3.html">进阶三</a>中介绍的假设(assumeThat),我们可以对大量的测试数据做一些理论测试。</p>
<p>先看例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@RunWith(Theories.class)
public class UserTest {
@DataPoint
public static String GOOD_USERNAME = "optimus";
@DataPoint
public static String USERNAME_WITH_SLASH = "optimus/prime";
@Theory
public void filenameIncludesUsername(String username) {
assumeThat(username, not(containsString("/")));
assertThat(new User(username).configFileName(), containsString(username));
}
}
</code></pre></div></div>
<p>是不是看得有点苦涩难懂,没关系,我来给你一一讲解:</p>
<ol>
<li><code class="language-plaintext highlighter-rouge">@RunWith(Theories.class)</code> 是告诉框架,下面的测试类将要做理论测试;</li>
<li><code class="language-plaintext highlighter-rouge">@DataPoint</code> 告诉框架,标注的这些数据都是准备用来测试的数据;</li>
<li><code class="language-plaintext highlighter-rouge">@Theory</code>标注的方法,是需要进行理论测试的方法;</li>
<li><code class="language-plaintext highlighter-rouge">assumeThat</code> 是对待测试的数据(<code class="language-plaintext highlighter-rouge">@DataPoint</code>标注的数据)进行检查,符合的数据继续往下走,不符合的数据忽略掉.如果,所有的数据都不符合,那么<code class="language-plaintext highlighter-rouge">@Theory</code>标注的测试,则算失败(fail)。</li>
</ol>
<p>关于理论测试,也可以扩展更多的功能,网上已经有人做了一些扩展,比如<a href="http://web.archive.org/web/20071012143326/popper.tigris.org/tutorial.html">这里</a>.</p>
<h3 id="五第三方的runner"><a name="5"></a>五、第三方的Runner</h3>
<p>我们也可以使用<code class="language-plaintext highlighter-rouge">@RunWith</code>来标注一些第三方的Runner,比如:</p>
<ol>
<li>
<p>SpringJUnit4ClassRunner</p>
<ul>
<li><a href="http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.html">http://static.springsource.org/spring/docs/3.0.x/javadoc-api/org/springframework/test/context/junit4/SpringJUnit4ClassRunner.html</a></li>
</ul>
</li>
<li>
<p>MockitoJUnitRunner</p>
<ul>
<li><a href="http://docs.mockito.googlecode.com/hg/latest/org/mockito/runners/MockitoJUnitRunner.html">http://docs.mockito.googlecode.com/hg/latest/org/mockito/runners/MockitoJUnitRunner.html</a></li>
</ul>
</li>
</ol>
<h3 id="六小结"><a name="6"></a>六、小结</h3>
<p>让我们回顾一下,本文介绍了JUnit4里面内置的几个特色运行器(Runner),包括:将多个测试类打包一起测试的Suite、可以实现参数化测试的Parameterized、可以实现分类测试的Categories、可以实现理论测试的Theories,还有网络上的一些第三方Runner。</p>
<p>根据实际情况的需要,合理的利用这些Runner,可以达到事半功倍的效果。</p>
JUnit4 使用进阶三
2013-04-15T00:00:00+00:00
http://www.blogways.net/blog/2013/04/15/junit-usage-3
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">不得不说的`assertThat`方法</a></li>
<li><a href="#2">还有个`assumeThat`方法</a></li>
</ol>
</dd>
</dl>
</div>
<p>在<a href="junit-usage-1.html">JUnit4 使用进阶一</a>中,我们介绍了JUnit4中断言的基本用法,在本文我们要做深入的介绍。当前JUnit4框架已经引入了 Hamcrest 匹配机制,使得程序员在编写单元测试的 assert 语句时,可以具有更强的可读性,而且也更加灵活。</p>
<h3 id="一不得不说的assertthat方法"><a name="1"></a>一、不得不说的<code class="language-plaintext highlighter-rouge">assertThat</code>方法</h3>
<p>JUnit4 结合 Hamcrest 提供了一个全新的断言语法——<code class="language-plaintext highlighter-rouge">assertThat</code>。程序员可以只使用 <code class="language-plaintext highlighter-rouge">assertThat</code> 一个断言语句,结合 Hamcrest 提供的匹配符,就可以表达全部的测试思想。</p>
<p>其基本语法为:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assertThat([message,] value, matcher-statement);
</code></pre></div></div>
<p>其中:</p>
<ol>
<li>第一个参数<code class="language-plaintext highlighter-rouge">message</code>,为可选参数,为出错是的提示信息;</li>
<li><code class="language-plaintext highlighter-rouge">value</code>为想要测试的变量;</li>
<li><code class="language-plaintext highlighter-rouge">matcher-statement</code>为使用 Hamcrest 匹配符来表达的对前面变量所期望的值的声明,如果 value 值与 <code class="language-plaintext highlighter-rouge">matcher-statement</code> 所表达的期望值相符,则测试成功,否则测试失败。</li>
</ol>
<p>开发人员可以通过实现 Matcher 接口,定制自己想要的匹配符。当开发人员发现自己的某些测试代码在不同的测试中重复出现,经常被使用,这时用户就可以自定义匹配符,将这些代码绑定在一个断言语句中,从而可以达到减少重复代码并且更加易读的目的。</p>
<p>目前,JUnit提供的匹配符,定义在两个类里面:<code class="language-plaintext highlighter-rouge">org.junit.matchers.JUnitMatchers</code>和<code class="language-plaintext highlighter-rouge">org.hamcrest.CoreMatchers</code>。</p>
<p><code class="language-plaintext highlighter-rouge">org.junit.matchers.JUnitMatchers</code>提供的匹配符,目前有两个:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>isThrowable(Matcher<T> throwableMatcher);
isException(Matcher<T> exceptionMatcher);
</code></pre></div></div>
<p>而<code class="language-plaintext highlighter-rouge">org.hamcrest.CoreMatchers</code>提供的匹配符就很多了,常用的有:</p>
<ol>
<li>
<p>核心</p>
<ul>
<li>anything - 总是匹配,如果你不关心测试下的对象是什么是有用的</li>
<li>describedAs - 添加一个定制的失败表述装饰器</li>
<li>is - 改进可读性的装饰器</li>
</ul>
</li>
<li>
<p>逻辑</p>
<ul>
<li>allOf - 如果所有匹配器都匹配才匹配, short circuits (很难懂的一个词,意译是短路,感觉不对,就没有翻译)(像 Java &&)</li>
<li>
<table>
<tbody>
<tr>
<td>anyOf - 如果任何匹配器匹配就匹配, short circuits (像 Java</td>
<td> </td>
<td>)</td>
</tr>
</tbody>
</table>
</li>
<li>not - 如果包装的匹配器不匹配器时匹配,反之亦然</li>
</ul>
</li>
<li>
<p>对象</p>
<ul>
<li>equalTo - 测试对象相等使用Object.equals方法</li>
<li>hasToString - 测试Object.toString方法</li>
<li>instanceOf, isCompatibleType - 测试类型</li>
<li>notNullValue, nullValue - 测试null</li>
<li>sameInstance - 测试对象实例</li>
</ul>
</li>
<li>
<p>Beans</p>
<ul>
<li>hasProperty - 测试JavaBeans属性</li>
</ul>
</li>
<li>集合
<ul>
<li>array - 测试一个数组元素test an array’s elements against an array of matchers</li>
<li>hasEntry, hasKey, hasValue - 测试一个Map包含一个实体,键或者值</li>
<li>hasItem, hasItems - 测试一个集合包含一个元素</li>
<li>hasItemInArray - 测试一个数组包含一个元素</li>
</ul>
</li>
<li>数字
<ul>
<li>closeTo - 测试浮点值接近给定的值</li>
<li>greaterThan, greaterThanOrEqualTo, lessThan, lessThanOrEqualTo - 测试次序</li>
</ul>
</li>
<li>文本
<ul>
<li>equalToIgnoringCase - 测试字符串相等忽略大小写</li>
<li>equalToIgnoringWhiteSpace - 测试字符串忽略空白</li>
<li>containsString, endsWith, startsWith - 测试字符串匹配</li>
</ul>
</li>
</ol>
<p><strong>如何自定义一个匹配符呢?</strong></p>
<p>看代码:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package mytest;
import org.hamcrest.Description;
import org.hamcrest.Factory;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;
public class IsNotANumber extends TypeSafeMatcher<Double> {
@Override
public boolean matchesSafely(Double number) {
return number.isNaN();
}
public void describeTo(Description description) {
description.appendText("not a number");
}
@Factory
public static <T> Matcher<Double> notANumber() {
return new IsNotANumber();
}
}
</code></pre></div></div>
<p>可以这样用于测试:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test
public void testSquareRootOfMinusOneIsNotANumber() {
assertThat(Math.sqrt(-1), is(notANumber()));
}
</code></pre></div></div>
<h3 id="一还有个assumethat方法"><a name="1"></a>一、还有个<code class="language-plaintext highlighter-rouge">assumeThat</code>方法</h3>
<p>理想情况下,写测试用例的开发人员可以明确的知道所有导致他们所写的测试用例不通过的地方,但是有的时候,这些导致测试用例不通过的地方并不是很容易的被发现,可能隐藏得很深,从而导致开发人员在写测试用例时很难预测到这些因素,而且往往这些因素并不是开发人员当初设计测试用例时真正目的,他们的测试点是希望测试出被测代码中别的出错地方。</p>
<p>比如,一个测试用例运行的 locale(如:Locale.US)与之前开发人员设计该测试用例时所设想的不同(如:Locale.UK),这样会导致测试不通过,但是这可能并不 是开发人员之前设计测试用例时所设想的测试出来的有用的失败结果(测试点并不是此,比如测试的真正目的是想判断函数的返回值是否为 true,返回 false 则测试失败),这时开发人员可以通过编写一些额外的代码来消除这些影响(比如将 locale 作为参数传入到测试用例中,每次运行测试用例时,明确指定 locale),但是花费时间和精力来编写这些不是测试用例根本目的的额外代码其实是种浪费,这时就可以使用 Assumption 假设机制来轻松达到额外代码的目的。编写该测试用例时,首先假设 locale 必须是 Locale.UK,如果运行时 locale 是 Locale.UK,则继续执行该测试用例函数,如果是其它的 locale,则跳过该测试用例函数,执行该测试用例函数以外的代码,这样就不会因为 locale 的问题导致测试出错。</p>
<p>JUnit4 结合 Hamcrest 库提供了 assumeThat 语句,开发人员可以使用其配合匹配符 Matcher 设计所有的假设条件(语法和 assertThat 一样)。</p>
<p>看下面例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import static org.junit.Assume.*
@Test public void filenameIncludesUsername() {
assumeThat(File.separatorChar, is('/'));
assertThat(new User("optimus").configFileName(), is("configfiles/optimus.cfg"));
}
@Test public void correctBehaviorWhenFilenameIsNull() {
assumeTrue(bugFixed("13356")); // bugFixed is not included in JUnit
assertThat(parse(null), is(new NullDocument()));
}
</code></pre></div></div>
<p>JUnit自带的运行器(Runner)会忽略那些假设不成立的测试数据,而自定义运行器的处理逻辑可能会不一样。</p>
<p>上面这个例子,也可以结合<a href="junit-usage-4.html">进阶四</a>中的理论测试来一起理解。</p>
<p>另外,假设也可以用在<code class="language-plaintext highlighter-rouge">@Before</code> 或者 <code class="language-plaintext highlighter-rouge">@BeforeClass</code>这两个步骤里面。</p>
JUnit4 使用进阶二
2013-04-15T00:00:00+00:00
http://www.blogways.net/blog/2013/04/15/junit-usage-2
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">忽略某个测试</a></li>
<li><a href="#2">对异常的测试</a></li>
<li><a href="#3">测试的超时时间</a></li>
<li><a href="#4">测试前后</a></li>
<li><a href="#5">小结</a></li>
</ol>
</dd>
</dl>
</div>
<p>在<a href="junit-usage-1.html">JUnit4 使用进阶一</a>中,我们介绍了JUnit4的下载安装,简单调用及运行测试方法,在本文中将继续对JUnit4提供的一些常用功能(忽略某个测试、对异常进行测试、设置超时时间、测试前后及顺序)进行介绍。</p>
<h3 id="一忽略某个测试"><a name="1"></a>一、忽略某个测试</h3>
<p>在测试过程中,我们可能需要临时禁止某个方法或者某个测试类的测试,比如:由于没完全准备好或者平台差异。这时,我们需要有一个方法可以告诉JUnit4框架,不要对这些方法或者类进行测试。</p>
<p>基于这个需要,JUnit4提供了一个注释<code class="language-plaintext highlighter-rouge">@Ignore</code>。</p>
<p>举个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Ignore
@Test
public void something() { ...
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">@Ignore</code>也可以添加一个可选的字符串参数,来说明为什么要忽略这个测试,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Ignore("还没准备好")
@Test
public void something() { ...
</code></pre></div></div>
<p>当然,<code class="language-plaintext highlighter-rouge">@Ignore</code>也可以直接作用在一个测试类上,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Ignore
public class IgnoreMe {
@Test
public void test1() { ... }
@Test
public void test2() { ... }
}
</code></pre></div></div>
<h3 id="二对异常的测试"><a name="2"></a>二、对异常的测试</h3>
<p>程序是否会按照我们所期待的,在运行过程中抛出异常呢?比如下面这个语句:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>new ArrayList<Object>().get(0);
</code></pre></div></div>
<p>上面的代码将会抛出<code class="language-plaintext highlighter-rouge">IndexOutOfBoundsException</code>异常,<code class="language-plaintext highlighter-rouge">@Test</code>注释有一个可选参数<code class="language-plaintext highlighter-rouge">expected</code>,这个参数的取值是<code class="language-plaintext highlighter-rouge">Throwable</code>的子类。如果我们想判断上面的代码是否抛出正确的异常,测试代码可以这样写:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test(expected= IndexOutOfBoundsException.class)
public void empty() {
new ArrayList<Object>().get(0);
}
</code></pre></div></div>
<p>JUnit4框架会对上面<code class="language-plaintext highlighter-rouge">expected</code>参数值进行检查,被测试的方法中如果抛出<code class="language-plaintext highlighter-rouge">IndexOutOfBoundsException</code>异常,那么测试就通过了。</p>
<p>一般,对异常的简单的测试,使用上面的方法就够了,但是有时,我们需要检查异常所包含的提示信息,那么我们就需要使用<code class="language-plaintext highlighter-rouge">ExpectedException</code>规则,来帮助我们实现了。</p>
<p>看下面这个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void shouldTestExceptionMessage() throws IndexOutOfBoundsException {
List<Object> list = new ArrayList<Object>();
thrown.expect(IndexOutOfBoundsException.class);
thrown.expectMessage("Index: 0, Size: 0");
list.get(0); // execution will never get past this line
}
</code></pre></div></div>
<p><strong>说明:</strong></p>
<ol>
<li>利用<code class="language-plaintext highlighter-rouge">@Rule</code>,我们可以对异常的提示信息进行检查。</li>
<li>
<p><code class="language-plaintext highlighter-rouge">expectMessage</code>方法还支持使用<code class="language-plaintext highlighter-rouge">CoreMatchers.containsString</code>来进行提示信息的匹配判断,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> thrown.expectMessage(CoreMatchers.containsString("Size: 0"));
</code></pre></div> </div>
</li>
</ol>
<h3 id="三测试的超时时间"><a name="3"></a>三、测试的超时时间</h3>
<p>有时,我们需要控制程序的执行时间,当超出预设的超时时间,那么就判断测试失败。JUnit4框架也提供了这种检查,看下面例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@Test(timeout=1000)
public void testWithTimeout() {
...
}
</code></pre></div></div>
<p>通过<code class="language-plaintext highlighter-rouge">@Test</code>注释的一个可选参数<code class="language-plaintext highlighter-rouge">timeout</code>的数值(单位毫秒),我们可以告诉框架,预设的超时时间是多少。当测试运行中,执行时间超出了这个预设值,框架就会抛出<code class="language-plaintext highlighter-rouge">TimeoutException</code>异常,标记这个测试失败了。</p>
<p>我们也可以使用规则,来为整个测试类里面所有测试方法设置一个统一的超时时间,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>public class HasGlobalTimeout {
public static String log;
@Rule
public Timeout globalTimeout = new Timeout(10000); // 10 seconds max per method tested
@Test
public void testInfiniteLoop1() {
log += "ran1";
for (;;) {
}
}
@Test
public void testInfiniteLoop2() {
log += "ran2";
for (;;) {
}
}
}
</code></pre></div></div>
<h3 id="四测试前后"><a name="4"></a>四、测试前后</h3>
<p>每个测试之前,我们可能需要做一些数据准备操作,再每个测试之后,我们可能需要将测试数据进行恢复。那么,JUnit4框架如何提供什么样的方式,来实现我们的需求呢?</p>
<p>没错,JUnit4提供了四个注释,来标注测试类中的某些方法,是用来做测试前后的准备或者恢复操作的。这四个注释分别为:<code class="language-plaintext highlighter-rouge">@Before</code>、<code class="language-plaintext highlighter-rouge">@After</code>、<code class="language-plaintext highlighter-rouge">@BeforeClass</code>和<code class="language-plaintext highlighter-rouge">@AfterClass</code>。</p>
<p>看下面这个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package test;
import java.io.Closeable;
import java.io.IOException;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
public class TestFixturesExample {
static class ExpensiveManagedResource implements Closeable {
@Override
public void close() throws IOException {}
}
static class ManagedResource implements Closeable {
@Override
public void close() throws IOException {}
}
@BeforeClass
public static void setUpClass() {
System.out.println("@BeforeClass setUpClass");
MyExpensiveManagedResource = new ExpensiveManagedResource();
}
@AfterClass
public static void tearDownClass() throws IOException {
System.out.println("@AfterClass tearDownClass");
MyExpensiveManagedResource.close();
MyExpensiveManagedResource = null;
}
private ManagedResource myManagedResource;
private static ExpensiveManagedResource MyExpensiveManagedResource;
private void println(String string) {
System.out.println(string);
}
@Before
public void setUp() {
this.println("@Before setUp");
this.myManagedResource = new ManagedResource();
}
@After
public void tearDown() throws IOException {
this.println("@After tearDown");
this.myManagedResource.close();
this.myManagedResource = null;
}
@Test
public void test1() {
this.println("@Test test1()");
}
@Test
public void test2() {
this.println("@Test test2()");
}
}
</code></pre></div></div>
<p>这个例子的执行结果,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>@BeforeClass setUpClass
@Before setUp
@Test test2()
@After tearDown
@Before setUp
@Test test1()
@After tearDown
@AfterClass tearDownClass
</code></pre></div></div>
<p><strong>说明:</strong></p>
<ol>
<li><code class="language-plaintext highlighter-rouge">@Before</code>和<code class="language-plaintext highlighter-rouge">@After</code>定义的方法,会在每个测试方法运行的前后执行一遍;</li>
<li><code class="language-plaintext highlighter-rouge">@BeforeClass</code>和<code class="language-plaintext highlighter-rouge">@AfterClass</code>定义的方法,会在整个测试类运行的开始和结束执行且仅执行一遍。</li>
<li>一般来说,<code class="language-plaintext highlighter-rouge">@Before</code>、<code class="language-plaintext highlighter-rouge">@After</code>、<code class="language-plaintext highlighter-rouge">@BeforeClass</code>和<code class="language-plaintext highlighter-rouge">@AfterClass</code>提供的方法,是在某个测试类里面所使用的,无法被另外的测试类所公用,如果你想写一个类来处理数据,并被几个测试类所使用,那么你可以使用规则来实现。关于这一点,你可以看看我们后面的教程。</li>
</ol>
<h3 id="五小结"><a name="5"></a>五、小结</h3>
<p>在本文中,我们进一步介绍了JUnit4框架提供的一些功能,在继续的教程中,我们将介绍几个有特色的运行器,来提高测试效率。</p>
JUnit4 使用进阶一
2013-04-15T00:00:00+00:00
http://www.blogways.net/blog/2013/04/15/junit-usage-1
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">简介</a></li>
<li><a href="#2">下载安装</a></li>
<li><a href="#3">一个简单的模版</a></li>
<li><a href="#4">运行测试</a></li>
<li><a href="#5">JUnit4的核心之一是断言</a></li>
<li><a href="#6">小结</a></li>
</ol>
</dd>
</dl>
</div>
<h3 id="一简介"><a name="1"></a>一、简介</h3>
<p>JUnit是一个Java语言的单元测试框架。它由Kent Beck和Erich Gamma建立,逐渐成为源于Kent Beck的sUnit的xUnit家族中为最成功的一个。来自JUnit的体验对测试驱动开发是很重要的,所以一些 JUnit知识经常 和测试驱动开发的讨论融合在一起。可以参考Kent Beck的 <a href="http://book.douban.com/subject/1771049/">《Test-Driven Development: By Example》</a>一书(有中文版和影印版)。</p>
<p>本文,对JUnit4的下载安装、基本测试方法及运行,配合实例进行基础介绍。帮助读者看完之后,就可以将JUnit4运用到单元测试中去。而JUnit4的更多功能可以参考后续的<a href="junit-usage-2.html">JUnit4使用进阶二</a>。</p>
<h3 id="二下载安装"><a name="2"></a>二、下载安装</h3>
<p>JUnit当前版本是4.11,如果,你的工程是使用Maven进行管理构建,那么只需要在工程的pom.xml文件中添加如下依赖信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</code></pre></div></div>
<p>否则,在你的测试classpath中放置下面两个jar:<code class="language-plaintext highlighter-rouge">junit.jar</code>和<code class="language-plaintext highlighter-rouge">hamcrest-core.jar</code>,这两个jar可以在<a href="http://search.maven.org/">这里</a>找到并下载。</p>
<h3 id="三一个简单的模版"><a name="3"></a>三、一个简单的模版</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package com.example.foo;
import static org.junit.Assert.assertEquals;
import org.junit.Test;
import org.junit.Ignore;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
/**
* Tests for {@link Foo}.
*
* @author user@example.com (John Doe)
*/
@RunWith(JUnit4.class)
public class FooTest {
@Test
public void thisAlwaysPasses() {
}
@Test
@Ignore
public void thisIsIgnored() {
}
}
</code></pre></div></div>
<p>这个模版是不是很简单!</p>
<ul>
<li>
<p>需要测试的方法,只需要通过<code class="language-plaintext highlighter-rouge">@Test</code>标注出来就可以了。</p>
</li>
<li>
<p>测试类只需要添加<code class="language-plaintext highlighter-rouge">@RunWith</code>注释,不再需要继承<code class="language-plaintext highlighter-rouge">junit.framework.TestCase</code>这个父类了。</p>
</li>
</ul>
<p>当然了,JUnit4还提供了很多特色功能,后面我们会一一介绍。</p>
<h3 id="四运行测试"><a name="4"></a>四、运行测试</h3>
<ul>
<li>
<p>在命令行,对写好的测试类,进行测试很简单,只需要下面一个命令就ok了:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> java -cp .:/usr/share/java/junit.jar org.junit.runner.JUnitCore [test class name]
</code></pre></div> </div>
</li>
<li>
<p>在IDE里面进行测试就更简单了,<code class="language-plaintext highlighter-rouge">Netbeans</code>, <code class="language-plaintext highlighter-rouge">Eclipse</code> 和 <code class="language-plaintext highlighter-rouge">IntelliJ Idea</code>都内置了图形化的测试运行器。</p>
</li>
</ul>
<p>测试类如何运行,是由<code class="language-plaintext highlighter-rouge">@RunWith</code>注释来决定。JUnit4当前版本的默认基本测试方式是<code class="language-plaintext highlighter-rouge">JUnit4.class</code>。除此之外,还有一些其他特殊的,我们将在<a href="junit-usage-4.html">JUnit4 使用进阶四</a>中进行介绍。</p>
<h3 id="五junit4的核心之一是断言"><a name="5"></a>五、JUnit4的核心之一是断言</h3>
<p>JUnit4框架主要是通过断言来判断运行结果正确与否,针对Java的原生类型(long,boolean,float…)或者Objects或者数组,JUnit都提供了对应的断言方法。</p>
<p>下面,我们先来看个例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.both;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.junit.matchers.JUnitMatchers.everyItem;
import static org.junit.matchers.JUnitMatchers.hasItems;
import java.util.Arrays;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
public class AssertTests {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
org.junit.Assert.assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertEquals() {
org.junit.Assert.assertEquals("failure - strings not same", 5l, 5l);
}
@Test
public void testAssertFalse() {
org.junit.Assert.assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
org.junit.Assert.assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
org.junit.Assert.assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
org.junit.Assert.assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
org.junit.Assert.assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
org.junit.Assert.assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThathasItemsContainsString() {
org.junit.Assert.assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
org.junit.Assert.assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
}
</code></pre></div></div>
<p><strong>注意:</strong></p>
<ol>
<li><code class="language-plaintext highlighter-rouge">assertEquals</code>和<code class="language-plaintext highlighter-rouge">assertSame</code> 的区别在于,前者是调用<code class="language-plaintext highlighter-rouge">期待值</code>的<code class="language-plaintext highlighter-rouge">equals</code>方法来判断<code class="language-plaintext highlighter-rouge">真实值</code>(<code class="language-plaintext highlighter-rouge">expected.equals(actual)</code>),而后者是判断<code class="language-plaintext highlighter-rouge">期待值</code>和<code class="language-plaintext highlighter-rouge">真实值</code>是否是同一个对象(<code class="language-plaintext highlighter-rouge">expected == actual</code>)。</li>
<li>例子中,这样调用<code class="language-plaintext highlighter-rouge">org.junit.Assert.assertEquals("failure - strings not same", 5l, 5l);</code>是不是觉得有点累,没关系,我们可以通过JDK1.5中的静态导入(<code class="language-plaintext highlighter-rouge">import static</code>)来简化这一切,看下面的代码。</li>
</ol>
<p>简化后的例子:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>package tangzhi.mytest;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.CoreMatchers.anyOf;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.sameInstance;
import static org.hamcrest.CoreMatchers.startsWith;
import static org.junit.Assert.assertThat;
import static org.junit.matchers.JUnitMatchers.both;
import static org.junit.matchers.JUnitMatchers.containsString;
import static org.junit.matchers.JUnitMatchers.everyItem;
import static org.junit.matchers.JUnitMatchers.hasItems;
import static org.junit.Assert.*;
import java.util.Arrays;
import org.hamcrest.core.CombinableMatcher;
import org.junit.Test;
public class AppTest {
@Test
public void testAssertArrayEquals() {
byte[] expected = "trial".getBytes();
byte[] actual = "trial".getBytes();
assertArrayEquals("failure - byte arrays not same", expected, actual);
}
@Test
public void testAssertEquals() {
assertEquals("failure - strings not same", 5l, 5l);
}
@Test
public void testAssertFalse() {
assertFalse("failure - should be false", false);
}
@Test
public void testAssertNotNull() {
assertNotNull("should not be null", new Object());
}
@Test
public void testAssertNotSame() {
assertNotSame("should not be same Object", new Object(), new Object());
}
@Test
public void testAssertNull() {
assertNull("should be null", null);
}
@Test
public void testAssertSame() {
Integer aNumber = Integer.valueOf(768);
assertSame("should be same", aNumber, aNumber);
}
// JUnit Matchers assertThat
@Test
public void testAssertThatBothContainsString() {
assertThat("albumen", both(containsString("a")).and(containsString("b")));
}
@Test
public void testAssertThathasItemsContainsString() {
assertThat(Arrays.asList("one", "two", "three"), hasItems("one", "three"));
}
@Test
public void testAssertThatEveryItemContainsString() {
assertThat(Arrays.asList(new String[] { "fun", "ban", "net" }), everyItem(containsString("n")));
}
// Core Hamcrest Matchers with assertThat
@Test
public void testAssertThatHamcrestCoreMatchers() {
assertThat("good", allOf(equalTo("good"), startsWith("good")));
assertThat("good", not(allOf(equalTo("bad"), equalTo("good"))));
assertThat("good", anyOf(equalTo("bad"), equalTo("good")));
assertThat(7, not(CombinableMatcher.<Integer> either(equalTo(3)).or(equalTo(4))));
assertThat(new Object(), not(sameInstance(new Object())));
}
}
</code></pre></div></div>
<p>在上面的代码中,我们静态导入了<code class="language-plaintext highlighter-rouge">import static org.junit.Assert.*;</code>后,在测试类中就可以直接使用<code class="language-plaintext highlighter-rouge">assertEquals</code>这些方法了。是不是很方便!</p>
<p><strong>常用的断言方法有:</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>assertTrue([message ,] condition);
assertFalse([message ,] condition);
assertEquals([message ,] expected, actual);
assertNotEquals([message ,] first, second);
assertArrayEquals([message ,] expecteds, actuals);
assertNotNull([message ,] object);
assertNull([message ,] object);
assertSame([message ,] expected, actual);
assertNotSame([message ,] unexpected, actual);
assertThat([message ,] actual, matcher);
</code></pre></div></div>
<p><strong>从上面的列表可以看出:</strong></p>
<ol>
<li>
<p>大部分断言方法的参数顺序都是:<code class="language-plaintext highlighter-rouge">[Message] 期待值 真实值</code>,第一个参数是个可选字符串,出错时的描述信息。第二个参数是期待值,第三个参数是真实值。</p>
</li>
<li>
<p>只有一个断言方法<code class="language-plaintext highlighter-rouge">assertThat</code>的参数顺序例外:可选的出错提示信息、真实值和一个<code class="language-plaintext highlighter-rouge">Matcher</code>对象。它参数中的期待值与真实值的顺序,与其他断言方法的参数顺序正好相反。</p>
</li>
</ol>
<p>这个<code class="language-plaintext highlighter-rouge">assertThat</code>方法是断言中的神器,后面我们会在<a href="junit-usage-3.html">进阶三</a>介绍!敬请期待吧!</p>
<h3 id="六小结"><a name="6"></a>六、小结</h3>
<p>前面我们介绍了JUnit4的基本知识。至此,你已经可以使用JUnit4进行代码测试了。如果你想知道更多信息,可以继续看看<a href="junit-usage-2.html">JUnit 使用进阶二</a>。</p>
jekyll 教程入门
2013-04-13T00:00:00+00:00
http://www.blogways.net/blog/2013/04/13/jekyll-usage
<h2 id="jekyll-教程入门">jekyll 教程入门</h2>
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">安装jekyll</a></li>
<li><a href="#2">搭建网站框架</a></li>
<li><a href="#3">编写博文</a></li>
<li><a href="#4">一些Jekyll网站例子及源码</a></li>
</ol>
</dd>
</dl>
</div>
<p><a href="https://github.com/mojombo/jekyll/wiki">jekyll</a> 是一款简单的博客系统,也可以说是一个静态网站生成器。她有一个模版目录,存放整个静态网站的模版文件,可以通过<a href="https://github.com/shopify/liquid/wiki">Liquid</a>处理模版文件,把使用标记语言<a href="http://en.wikipedia.org/wiki/Textile">Textile</a>或<a href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>编写的内容文件,按照模版格式,转换成最终的静态网站页面。大名鼎鼎的GitHub Pages就是通过她实现的。</p>
<p>在这里,我们将告诉你如果使用jekyll搭建一个简单的博客网站。</p>
<h3 id="一安装jekyll"><a name="1"></a>一、安装jekyll</h3>
<hr />
<p>使用<a href="http://rubygems.org/">RubyGems</a>安装jekyll。安装了Ruby之后,默认会自动安装RubyGems,也可以单独安装RubyGems.</p>
<p>使用RubyGems安装jekyll很简单,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install jekyll
</code></pre></div></div>
<p>如果使用的标记语言是Markdown,则需要另外安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install rdiscount
</code></pre></div></div>
<p>如果使用的标记语言是Textile,则需要另外安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install RedCloth
</code></pre></div></div>
<p>上面三个可以一次性安装,如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install jekyll rdiscount RedCloth
</code></pre></div></div>
<p>另外,如需安装和github pages相同版本的jekyll,那么不需要安装上面这几个,直接用下面命令安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install github-pages
</code></pre></div></div>
<p>如果是Mac环境下面,可能会出现版本问题,无法安装,可以参考我的另外一篇博文——<a href="/blog/2013/04/08/install-jekyll-on-mac.html">MAC OS X 10.8 下安装jekyll</a> 进行安装。</p>
<h3 id="二搭建博客网站框架"><a name="2"></a>二、搭建博客网站框架</h3>
<p>jekyll 的内核实际上就是一个格式文本转换引擎,她允许你使用标记语言Markdown或者Textile再或者原始的html语言,编写内容。她通过模版文件,将这些内容转换成最终的静态网页。</p>
<p>一个简单的jekyll网站结构如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>.
|-- _config.yml
|-- _includes
|-- _layouts
| |-- default.html
| `-- post.html
|-- _posts
| |-- 2007-10-29-why-every-programmer-should-play-nethack.textile
| `-- 2009-04-26-barcamp-boston-4-roundup.textile
|-- _site
`-- index.html
</code></pre></div></div>
<p>在这基本结构中,有几个关键的文件或者目录,其作用如下:</p>
<ol>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">_config.yml</code></strong></p>
<p>这是jekyll的主要配置文件。这些配置选项,大部分都可以在启动jekyll时,作为命令行参数执行。只不过,把这些选项配置在文件中,就省去记住他们,且每次执行jekyll时命令也简单一点。</p>
<p>关于jekyll的参数,其实系统已经设置了<a href="https://github.com/mojombo/jekyll/wiki/Configuration">默认值</a>,如无特殊需要,空文件也可以。</p>
<p>个人推荐,可以添加两个参数:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> markdown: rdiscount
pygments: true
</code></pre></div> </div>
<p><strong><code class="language-plaintext highlighter-rouge">rdiscount</code></strong>是markdown标记语言的一个转换引擎,比默认的<code class="language-plaintext highlighter-rouge">maruku</code>性能要好一些。</p>
<p><strong><code class="language-plaintext highlighter-rouge">pygments</code></strong>是支持语法高亮显示的工具。设置开通后,可以通过 <code class="language-plaintext highlighter-rouge">{\% highlight \%}</code> 标签在博文中嵌入语法高亮的代码。</p>
</li>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">_includes</code></strong></p>
<p>这个目录的存在是区分高手和普通用户的,嘿嘿。在网站设计中,页面的共有部分,可以存储成一个单独的文件。这样设计可以方便以后的维护。而这个单独的公用文件就存放在这个目录里面。这里面的公用文件,可以被<code class="language-plaintext highlighter-rouge">_layouts</code>和<code class="language-plaintext highlighter-rouge">_post</code>目录下面的文件嵌入。其嵌入方法,采用的是Liquid标签实现。比如:<code class="language-plaintext highlighter-rouge">{\% include file.ext \%}</code>,就指在文件中嵌入公用文件<code class="language-plaintext highlighter-rouge">_includes/file.ext</code>中的内容。</p>
<p>是不是很方便!?哈哈!</p>
</li>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">_layouts</code></strong></p>
<p>这个目录下存放的是整个网站的模版文件,可以是一个文件,也可以是一套文件。这个要看整个网站是怎么规划了。</p>
</li>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">_posts</code></strong></p>
<p>这个目录是存放你的博文的,整个网站搭建完成后,以后每次添加博文,只需要提交到这个目录下就可以了。</p>
<p>不过,编写的博文,必须符合一定的规则。见下面<a href="#3">编写博文</a>小节内容。</p>
</li>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">_site</code></strong></p>
<p>这个目录,是jekyll运行之后生成的。存放着整个网站的最终静态页面。其中的内容,不用去关心。</p>
</li>
<li>
<p><strong><code class="language-plaintext highlighter-rouge">index.html和其他的 HTML/Markdown/Textile文件</code></strong></p>
<p>这些文件如果包含<code class="language-plaintext highlighter-rouge">YAML</code>信息块,那么jekyll运行时就会自动对他进行转换。你网站根目录,以及任何不在上述目录下的文件(包括<code class="language-plaintext highlighter-rouge">.html</code>/<code class="language-plaintext highlighter-rouge">.markdown</code>/<code class="language-plaintext highlighter-rouge">.md</code>/<code class="language-plaintext highlighter-rouge">texitle</code>文件,但不仅这些文件)。</p>
</li>
<li>
<p><strong>其他文件和目录</strong></p>
<p>除了上述文件和目录,你还可以添加一些其他的文件或者目录。比如,添加css目录,存放网站的css文件;添加images目录,存放网站涉及到的图片资源。噢,对了,你还可以在网站的根目录下一个网站图标文件<code class="language-plaintext highlighter-rouge">favicon.ico</code>,这样别人访问你的网站时,体验会更好一点。设计优秀的网站图标,能帮助访问者,更容易记住你的网站哦!</p>
</li>
</ol>
<h3 id="三编写博文"><a name="3"></a>三、编写博文</h3>
<p>从上一小节的内容,我们知道<code class="language-plaintext highlighter-rouge">_posts</code>目录下面存放的是我们的博文,这些博文必须满足一定的规则。这里,我将告诉大家相关的规则。</p>
<ol>
<li>博文的命名是有讲究的,必须符合<code class="language-plaintext highlighter-rouge">YEAR-MONTH-DAY-title.MARKUP</code>这个格式。</li>
<li>
<p>博文的永久链接(PermaLink)生成规则很灵活,可以配置(在<code class="language-plaintext highlighter-rouge">_config.yml</code>中)。系统内置三种格式:</p>
<table>
<thead>
<tr>
<th> 模版名 </th>
<th> 模版内容 </th>
</tr>
</thead>
<tbody>
<tr>
<td> date </td>
<td> /:categories/:year/:month/:day/:title/ </td>
</tr>
<tr>
<td> pretty </td>
<td> /:categories/:year/:month/:day/:title/ </td>
</tr>
<tr>
<td> none </td>
<td> /:categories/:title.html </td>
</tr>
</tbody>
</table>
<p>目前系统默认格式是<code class="language-plaintext highlighter-rouge">date</code>。你也可以修改,比如,在<code class="language-plaintext highlighter-rouge">_config.yml</code>中配置<code class="language-plaintext highlighter-rouge">permalink: pretty</code>。这样就会生成类似<code class="language-plaintext highlighter-rouge">/2009/04/29/slap-chop/index.html</code>这样的文件url.除了内置的格式,也可以自定义格式,比如<code class="language-plaintext highlighter-rouge">permalink: /blog/:month-:day-:year/:title.html</code>。是不是很酷?哈哈!</p>
</li>
<li>
<p>博文可以是用标记语言Markdown或者Textile再或者原始的html语言编写的文本。但是,博文的开头必须包含一个<code class="language-plaintext highlighter-rouge">YAML</code>信息块。在这个<code class="language-plaintext highlighter-rouge">YAML</code>信息块,包含一些信息。比如使用的模版,博文的标题等等。给个简单的例子吧,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> ---
layout: post
title: jekyll 教程入门
---
</code></pre></div> </div>
</li>
<li>到此,基本内容已经讲完了。</li>
</ol>
<h3 id="四运行jekyll生成网站"><a name="4"></a>四、运行jekyll生成网站</h3>
<p>在网站根目录下,运行<code class="language-plaintext highlighter-rouge">jekyll</code>命令,就可以生成最终的静态网站内容目录<code class="language-plaintext highlighter-rouge">_site</code>。如果,你还想通过浏览器访问,可以运行下面命令。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll --server
</code></pre></div></div>
<p>这样,通过<code class="language-plaintext highlighter-rouge">http://localhost:4000</code>就可以访问你做出来的网站了。</p>
<p>如果你的博客网站是类似<code class="language-plaintext highlighter-rouge">http://localhost:4000/blog/index.html</code>这样的路径。那么,在运行时,需要添加参数<code class="language-plaintext highlighter-rouge">base-url</code>来实现,比如:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>jekyll --base-url '/blog' --server
</code></pre></div></div>
<h3 id="五一些jekyll网站例子及源码"><a name="5"></a>五、一些Jekyll网站例子及源码</h3>
<p>下面给一些建在GitHub上面的jekyll博客,及其源码。</p>
<ol>
<li><a href="http://tom.preston-werner.com/">tom.preston-werner.com</a>——<a href="http://github.com/mojombo/mojombo.github.io">源码</a></li>
<li><a href="http://gitready.com/">gitready.com</a>——<a href="http://github.com/gitready/gitready">源码</a></li>
<li><a href="http://zen.id.au/">zen.id.au</a>——<a href="https://github.com/zensavona/zensavona.github.com">源码</a></li>
</ol>
git服务器部署及使用
2013-04-13T00:00:00+00:00
http://www.blogways.net/blog/2013/04/13/git
<h3 id="一概述">一、概述</h3>
<p>Git是用于Linux内核开发的版本控制工具。与CVS、Subversion一类的集中式版本控制工具不同,它采用了分布式版本库的作法,不需要服务器端软件,就可以运作版本控制,使得源代码的发布和交流极其方便。Git的速度很快,这对于诸如Linux kernel这样的大项目来说自然很重要。Git最为出色的是它的合并追踪(merge tracing)能力。</p>
<p>实际上内核开发团队决定开始开发和使用Git来作为内核开发的版本控制系统的时候,世界开源社群的反对声音不少,最大的理由是Git太艰涩难懂,从Git的内部工作机制来说,的确是这样。但是随着开发的深入,Git的正常使用都由一些友善的命令稿来执行,使Git变得非常好用。现在,越来越多的著名项目采用Git来管理项目开发。</p>
<h3 id="二实现原理">二、实现原理</h3>
<p>Git和其他版本控制系统(如CVS)有不少的差别,Git本身关心档案的整体性是否有改变,但多数的CVS,或Subversion系统则在乎档案内容的差异。因此Git更像一个档案系统,直接在本机上取得资料,不必连线到host端取资料回来。</p>
<h3 id="三安装git">三、安装git</h3>
<ol>
<li>
<p>linux上安装:</p>
<p>linux上安装git可以源代码安装和预编译好的Git二进制安装包安装两种方式,
本次安装是在redHat上采用源码安装的:</p>
<ul>
<li>主机上建立一个用户git</li>
<li>
<p>root用户登录,修改/ect目录下sudoers文件,将git用户加入sudo列表</p>
</li>
<li>
<p>下载源代码
<a href="http://git-scm.com/download">http://git-scm.com/download</a></p>
</li>
<li>
<p>上传redHat主机解压编译并安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $tar -zxf git-1.7.2.2.tar.gz
$ cd git-1.7.2.2
$ make prefix=/usr/local all
$ sudo make prefix=/usr/local install 其中/usr/local为安装目录
</code></pre></div> </div>
</li>
<li>
<p>修改git用户下 ~/.bash_profile文件(没有新建),添加PATH=$PATH:/usr/local/bin</p>
</li>
<li>
<p>修改git用户下.bashrc文件(没有新建),添加 PATH=$HOME/bin:$PATH</p>
</li>
<li>$git –version,显示git版本git version 1.8.1.6,表示git安装成功</li>
</ul>
</li>
<li>
<p>windows上安装</p>
<ul>
<li>下载msysGit安装包
<a href="http://code.google.com/p/msysgit"> http://code.google.com/p/msysgit </a></li>
<li>安装msysGit,右键运行git Bash,输入git –version,
显示git version 1.7.11.msysgit.1,windows上git安装成功</li>
</ul>
</li>
</ol>
<h3 id="四git配置">四、git配置</h3>
<ol>
<li>
<p>linux 上git服务器配置:</p>
<p>首先我们都需要先配置下自己的 Git 工作环境。配置工作只需一次,以后升级时还会沿用现在的配置。当然,如果需要,你随时可以用相同的命令修改已有的配置。
Git 提供了一个叫做 git config 的工具,专门用来配置或读取相应的工作环境变量。
而正是由这些环境变量,决定了 Git 在各个环节的具体工作方式和行为。
这些变量可以存放在以下三个不同的地方:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> • /etc/gitconfig 文件:系统中对所有用户都普遍适用的配置。若使用git config 时用
--system 选项,读写的就是这个文件
• ~/.gitconfig 文件:用户目录下的配置文件只适用于该用户。若使用git config 时用
--global 选项,读写的就是这个文件
• 当前项目的 git 目录中的配置文件(也就是工作目录中的 .git/config 文件):所以
.git/config 里的配置会覆盖/etc/ gitconfig 中的同名变量
</code></pre></div> </div>
<ul>
<li>
<p>配置的是你个人的用户名称和电子邮件地址</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git config --global user.name "tangsz"
$ git config --global user.email "tangsz@asiainfo-linkage.com"
</code></pre></div> </div>
</li>
<li>
<p>文本编辑器</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git config --global core.editor vi
</code></pre></div> </div>
</li>
<li>
<p>查看配置信息 git config –list</p>
</li>
</ul>
</li>
<li>
<p>windows 上msysGit配置</p>
<ul>
<li>
<p>生成公钥
右键打开git Bash,输入ssh-keygen –C “你的email地址 “ –t rsa 就会为你生成一个 SSH Key,
然后会询问一些保存文件的位置,设置密码神马的,直接回车,回车,回车,默认的就可以了,SSH 公
钥默认储存在账户的主目录下的 ~/.ssh 目录something 和 something.pub 来命名的一对文件,
这个 something 通常就是 id_dsa 或 id_rsa,有 .pub 后缀的文件就是公钥</p>
</li>
<li>
<p>将公钥文件放到主机git用户.ssh目录下(没有此目录就新建),将公钥文件内容追加到 文件authorized_keys文件(没有就新建)尾部即可:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cat /tmp/id_rsa.pub >> ~/.ssh/authorized_keys
</code></pre></div> </div>
</li>
</ul>
这样后面windows与服务器交互clone、pull等操作时就不用频繁输入密码了
</li>
</ol>
<h3 id="五服务器上部署git">五、服务器上部署git</h3>
<p>服务器上部署git仓库有很多种方式,这里简单介绍两种:</p>
<ul>
<li>
<p>直接在服务器上需建立仓库目录执行 git init,通常我们在服务器上建立的仓库都是裸仓库-即一个不含工作目录的仓库,所以我们这里执行git –bare init建立裸仓库,–bare表示建立的是裸仓库</p>
</li>
<li>
<p>本机clone一个裸仓库移到服务器上,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> $ git clone --bare my_project my_project.git
$ scp -r my_project.git user@192.168.4.19:/home/git
</code></pre></div> </div>
<p>user为服务器上建立的git用户,192.168.4.19为主机地址,/home/git为裸仓库存放目录,这样所有对该服务器有 SSH 访问权限,并可读取 /home/git 目录的用户都可以用下面的命令克隆该项目</p>
</li>
</ul>
<p>注:裸仓库建立完后,更改裸仓库目录下config文件添加</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>[receive]
denyCurrentBranch = ignore)
</code></pre></div></div>
<p>否则本机push操作会报master branch is currently checked out</p>
<h3 id="六git操作">六、git操作</h3>
<p>git的操作需在实践中慢慢熟悉,这里简单介绍一些平时常用的:</p>
<ol>
<li>
<p>本地仓库操作</p>
<p>windows下命令行操作仓库在安装msysGit后只需右键执行git Bash就可以操作了,
也可以下载TortoiseGit通过可视化操作本地及远程仓库,此处就简单介绍命令行操作仓库,不做TortoiseGit操作介绍</p>
<ul>
<li>
<p>克隆一个仓库:git有git、ssh、http(s)几种协议,这里用的是ssh git clone git:192.168.4.19:/home/git/my_project.git git为服务器用户名、192.168.4.19为服务器地址、/home/git/my_project.git为服务器仓库目录</p>
</li>
<li>
<p>git status –检查文件</p>
</li>
<li>
<p>git add 文件名 –新增文件</p>
</li>
<li>
<p>git commit –提交操作</p>
</li>
<li>
<p>git log –查看日志</p>
</li>
<li>
<p>git commit –amend –撤销提交</p>
</li>
<li>
<p>git mv file_from file_to –文件改名</p>
</li>
<li>
<p>git diff –查看working tree与index file的差别</p>
</li>
<li>
<p>git rm –删除文件</p>
</li>
</ul>
</li>
<li>
<p>远程仓库操作</p>
<ul>
<li>
<p>git remote -v –查看远程仓库</p>
</li>
<li>
<p>git fetch [remote-name] –获取remote-name库数据不merge</p>
</li>
<li>
<p>git push [remote-name] [branch-name] –上传数据都remote-name库branch-name分支</p>
</li>
<li>
<p>git remote show origin – 查看origin库信息</p>
</li>
<li>
<p>git remote rename pb paul —把 pb 改成 paul</p>
</li>
<li>
<p>git pull –从远程获取最新版本并merge到本地,相当于git fetch 和 git merge</p>
</li>
</ul>
</li>
</ol>
创建SSH密钥,并连接GitHub
2013-04-10T00:00:00+00:00
http://www.blogways.net/blog/2013/04/10/generating-ssh-keys-4-github
<div class="code fl">
<dl>
<dt>目录</dt>
<dd>
<ol>
<li><a href="#1">Windows环境下生成SSH Keys,并连接GitHub</a></li>
<li><a href="#2">Mac环境下生成SSH Keys,并连接GitHub</a></li>
<li><a href="#3">单机如何控制不同的SSH Keys连不同的Git环境?</a></li>
</ol>
</dd>
</dl>
</div>
<p>我们可以使用SSH Keys在本机和GitHub之间建立一个安全的连接。下面,我们将手把手教您如何创建SSH Keys并将公钥加到你的GitHub账户中。</p>
<div class="clr"></div>
<h3 id="一windows环境下生成ssh-key且连接github"><a name="1"></a>一、Windows环境下生成SSH key且连接GitHub</h3>
<hr />
<h4 id="第一步看看是否存在ssh密钥keys">第一步、看看是否存在SSH密钥(keys)</h4>
<p>首先,我们需要看看是否看看本机是否存在SSH keys,打开Git Bash,并运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd ~/.ssh
# 检查你本机用户home目录下是否存在.ssh目录
</code></pre></div></div>
<p>如果,不存在此目录,则进行第二步操作,否则,你本机已经存在ssh公钥和私钥,可以略过第二步,直接进入第三步操作。</p>
<h4 id="第二步创建一对新的ssh密钥keys">第二步、创建一对新的SSH密钥(keys)</h4>
<p>输入如下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ssh-keygen -t rsa -C "your_email@example.com"
# 这将按照你提供的邮箱地址,创建一对密钥
Generating public/private rsa key pair.
Enter file in which to save the key (/c/Users/you/.ssh/id_rsa): [Press enter]
</code></pre></div></div>
<p>直接回车,则将密钥按默认文件进行存储。此时也可以输入特定的文件名,比如<code class="language-plaintext highlighter-rouge">/c/Users/you/.ssh/github_rsa</code></p>
<p>接着,根据提示,你需要输入密码和确认密码。相关提示如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter passphrase (empty for no passphrase): [Type a passphrase]
Enter same passphrase again: [Type passphrase again]
</code></pre></div></div>
<p>输入完成之后,屏幕会显示如下信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your identification has been saved in /c/Users/you/.ssh/id_rsa.
Your public key has been saved in /c/Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com
</code></pre></div></div>
<h4 id="第三步在github账户中添加你的公钥">第三步、在GitHub账户中添加你的公钥</h4>
<p>运行如下命令,将公钥的内容复制到系统粘贴板(clipboard)中。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>clip < ~/.ssh/id_rsa.pub
</code></pre></div></div>
<p>接着:</p>
<ol>
<li>登陆GitHub,进入你的Account Settings. <br /><img src="/images/post/userbar-account-settings.png" alt="Account Settings" /></li>
<li>在左边菜单,点击”SSH Keys”. <br /> <img src="/images/post/settings-sidebar-ssh-keys.png" alt="SSH Keys" /></li>
<li>点击”Add SSH key”按钮.<br /> <img src="/images/post/ssh-add-ssh-key.png" alt="Add SSH key" /></li>
<li>粘贴你的密钥到key输入框中.<br /> <img src="/images/post/ssh-key-paste.png" alt="Paste key" /></li>
<li>点击”Add Key”按钮。<br /> <img src="/images/post/ssh-add-key.png" alt="Add Key" /></li>
<li>再弹出窗口,输入你的GitHub密码,点击确认按钮。</li>
<li>到此,大功告成了!</li>
</ol>
<h4 id="第四步测试">第四步、测试</h4>
<p>为了确认我们可以通过SSH连接GitHub,我们输入下面命令。输入后,会要求我们提供验证密码,输入之前创建的密码就ok了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ssh -T git@github.com
</code></pre></div></div>
<p>你可能会看到告警信息,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The authenticity of host 'github.com (207.97.227.239)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?
</code></pre></div></div>
<p>不用担心,直接输入yes。</p>
<p>如果看到下面信息,就说明一切完美!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hi username! You've successfully authenticated, but GitHub does not provide shell access.
</code></pre></div></div>
<h3 id="二mac环境下生成ssh-key且连接github"><a name="2"></a>二、Mac环境下生成SSH key且连接GitHub</h3>
<hr />
<h4 id="第一步看看是否存在ssh密钥keys-1">第一步、看看是否存在SSH密钥(keys)</h4>
<p>首先,我们需要看看是否看看本机是否存在SSH keys,打开终端(Terminal),并运行:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$cd ~/.ssh
# 检查你本机用户home目录下是否存在.ssh目录
</code></pre></div></div>
<p>如果,不存在此目录,则进行第二步操作,否则,你本机已经存在ssh公钥和私钥,可以略过第二步,直接进入第三步操作。</p>
<h4 id="第二步创建一对新的ssh密钥keys-1">第二步、创建一对新的SSH密钥(keys)</h4>
<p>输入如下命令:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ssh-keygen -t rsa -C "your_email@example.com"
# 这将按照你提供的邮箱地址,创建一对密钥
Generating public/private rsa key pair.
Enter file in which to save the key (/Users/you/.ssh/id_rsa): [Press enter]
</code></pre></div></div>
<p>直接回车,则将密钥按默认文件进行存储。此时也可以输入特定的文件名,比如<code class="language-plaintext highlighter-rouge">/Users/you/.ssh/github_rsa</code></p>
<p>接着,根据提示,你需要输入密码和确认密码。相关提示如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Enter passphrase (empty for no passphrase): [Type a passphrase]
Enter same passphrase again: [Type passphrase again]
</code></pre></div></div>
<p>输入完成之后,屏幕会显示如下信息:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Your identification has been saved in /Users/you/.ssh/id_rsa.
Your public key has been saved in /Users/you/.ssh/id_rsa.pub.
The key fingerprint is:
01:0f:f4:3b:ca:85:d6:17:a1:7d:f0:68:9d:f0:a2:db your_email@example.com
</code></pre></div></div>
<h4 id="第三步在github账户中添加你的公钥-1">第三步、在GitHub账户中添加你的公钥</h4>
<p>运行如下命令,将公钥的内容复制到系统粘贴板(clipboard)中。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pbcopy < ~/.ssh/id_rsa.pub
</code></pre></div></div>
<p>接着:</p>
<ol>
<li>登陆GitHub,进入你的Account Settings. <br /><img src="/images/post/userbar-account-settings.png" alt="Account Settings" /></li>
<li>在左边菜单,点击”SSH Keys”. <br /> <img src="/images/post/settings-sidebar-ssh-keys.png" alt="SSH Keys" /></li>
<li>点击”Add SSH key”按钮.<br /> <img src="/images/post/ssh-add-ssh-key.png" alt="Add SSH key" /></li>
<li>粘贴你的密钥到key输入框中.<br /> <img src="/images/post/ssh-key-paste.png" alt="Paste key" /></li>
<li>点击”Add Key”按钮。<br /> <img src="/images/post/ssh-add-key.png" alt="Add Key" /></li>
<li>再弹出窗口,输入你的GitHub密码,点击确认按钮。</li>
<li>到此,大功告成了!</li>
</ol>
<h4 id="第四步测试-1">第四步、测试</h4>
<p>为了确认我们可以通过SSH连接GitHub,我们输入下面命令。输入后,会要求我们提供验证密码,输入之前创建的密码就ok了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh -T git@github.com
</code></pre></div></div>
<p>你可能会看到告警信息,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>The authenticity of host 'github.com (207.97.227.239)' can't be established.
RSA key fingerprint is 16:27:ac:a5:76:28:2d:36:63:1b:56:4d:eb:df:a6:48.
Are you sure you want to continue connecting (yes/no)?
</code></pre></div></div>
<p>不用担心,直接输入yes。</p>
<p>如果看到下面信息,就说明一切完美!</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Hi username! You've successfully authenticated, but GitHub does not provide shell access.
</code></pre></div></div>
<p>####【注意】</p>
<p>如果前面没有将生成的密钥存放在默认的文件<code class="language-plaintext highlighter-rouge">id_rsa</code>中(而是<code class="language-plaintext highlighter-rouge">my_rsa</code>中),那么<code class="language-plaintext highlighter-rouge">ssh -T git@github.com</code>命令就需要添加参数来执行。如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ ssh -T -i my_rsa git@github.com
</code></pre></div></div>
<h3 id="三单机如何控制不同的ssh-keys连不同的git环境"><a name="3"></a>三、单机如何控制不同的SSH Keys连不同的Git环境?</h3>
<hr />
<p>其实,一套SSH密钥是可以用在不同的SSH环境的.</p>
<p>但是如果由于某种要求,需要用不同的SSH密钥连接不同的Git环境。假设具体场景是,已经建了密钥github_rsa,还需要创建work_rsa连接工作环境git仓库,那么,可以按下面操作进行:</p>
<h4 id="1-创建另一对密钥work_rsa">1. 创建另一对密钥work_rsa.</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ssh-keygen -t rsa -C "work@mail.com"
#保存密钥为work_rsa
</code></pre></div></div>
<h4 id="2-添加新身份信息">2. 添加新身份信息</h4>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>$ssh-add ~/.ssh/work_rsa
</code></pre></div></div>
<h4 id="3-配置sshconfig">3. 配置.ssh/config</h4>
<p>我们需要通过Host别名,将不同的账号区分开来。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Host me.github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/github_rsa
Host work.comp.com
HostName comp.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/work_rsa
</code></pre></div></div>
<p>参考文档:GitHub官方文档<a href="https://help.github.com/articles/generating-ssh-keys">Generating SSH Keys</a></p>
MAC OS X 10.8 下安装jekyll
2013-04-08T00:00:00+00:00
http://www.blogways.net/blog/2013/04/08/install-jekyll-on-mac
<h3 id="一概述">一、概述</h3>
<p><a href="https://github.com/mojombo/jekyll/wiki">jekyll</a> 是一款简单的博客系统,静态网站生成器。她有一个模版目录,存放整个静态网站的模版文件,可以通过<a href="https://github.com/shopify/liquid/wiki">Liquid</a>处理模版文件,把使用标记语言<a href="http://en.wikipedia.org/wiki/Textile">Textile</a>或<a href="http://en.wikipedia.org/wiki/Markdown">Markdown</a>编写的内容文件,按照模版格式,转换成最终的静态网站页面。大名鼎鼎的GitHub Pages就是通过她实现的。</p>
<p>本想在本机Macbook pro上装个jekyll,可是本机xcode自动安装了ruby 1.8.7版,导致无法通过当前版本的gem安装jekyll。使用 <code class="language-plaintext highlighter-rouge">gem update --system</code> 命令对gem进行升级,可是貌似没有升级成功。</p>
<p>所以,我先装了个brew,通过brew安装了最新的ruby,最后通过新版gem成功安装了jekyll.整个过程记录如下。</p>
<h3 id="二安装homebrew">二、安装Homebrew</h3>
<p><a href="http://mxcl.github.io/homebrew/index_zh-cn.html">Homebrew</a>落户于gitHub上,安装是否简单,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby -e "$(curl -fsSL https://raw.github.com/mxcl/homebrew/go)"
</code></pre></div></div>
<p>打开Terminal, 粘贴上面的语句.该脚本首先将会解释它要做什么, 然后暂停下来, 直到您确认继续. 更多的安装选项在<a href="https://github.com/mxcl/homebrew/wiki/Installation">这里</a>可以看到 (需要10.5).</p>
<h3 id="三安装最新版ruby">三、安装最新版ruby</h3>
<p>Homebrew安装完成之后,通过她安装最新版ruby.命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>brew install ruby
</code></pre></div></div>
<p>最新版ruby安装完成之后,会提示你最新版本安装在<code class="language-plaintext highlighter-rouge">/usr/local/opt/ruby/bin</code>目录下面。原来的旧版仍然在<code class="language-plaintext highlighter-rouge">/usr/bin</code>下面.</p>
<p>可以修改环境变量PATH的值,将新版本的路径在查找路径中前置。修改<code class="language-plaintext highlighter-rouge">~/.bash_profile</code>文件,如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>export PATH=/usr/local/opt/ruby/bin:$PATH
</code></pre></div></div>
<p>修改后<code class="language-plaintext highlighter-rouge">source ~/.bash_profile</code>或者重新打开一个Terminal,新版Ruby就生效了。</p>
<p>可以通过<code class="language-plaintext highlighter-rouge">ruby --version</code>查看版本号,我的新版信息如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ruby 2.0.0p0 (2013-02-24 revision 39474) [x86_64-darwin12.3.0]
</code></pre></div></div>
<p>如果,版本不对,就使用<code class="language-plaintext highlighter-rouge">which ruby</code>看看,当前生效的ruby是否在<code class="language-plaintext highlighter-rouge">/usr/local/opt/ruby/bin</code>下,不对,就修改环境变量PATH,如上。</p>
<h3 id="四安装jekyll">四、安装jekyll</h3>
<p>有了最新版的ruby,安装jekyll就简单了。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install jekyll
</code></pre></div></div>
<p>如果使用的标记语言是Markdown,则需要另外安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install rdiscount
</code></pre></div></div>
<p>如果使用的标记语言是Textile,则需要另外安装</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install RedCloth
</code></pre></div></div>
<p>上面三个可以一次性安装,如下</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install jekyll rdiscount RedCloth
</code></pre></div></div>
<p>说明:这里安装的jekyll、rdiscount、redcloth都安装在本机的<code class="language-plaintext highlighter-rouge">/usr/local/opt/ruby/bin</code>目录下面。</p>
<p>另外,如果想和github提供的page环境保持相同的版本,可以安装<code class="language-plaintext highlighter-rouge">github-pages</code>,这个会保持与github的page版本一致。</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>gem install github-pages
</code></pre></div></div>
<h3 id="五运行例子jekyll作者提供的例子tpw">五、运行例子——jekyll作者提供的例子tpw</h3>
<ol>
<li>
<p>获取源码,并运行jekyll,命令如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> cd ~
mkdir webroot
cd webroot
git clone https://github.com/mojombo/tpw.git
cd tpw
jekyll --server
</code></pre></div> </div>
<p>界面显示启动信息,提示端口为4000</p>
</li>
<li>在浏览器访问<code class="language-plaintext highlighter-rouge">localhost:4000</code>,显示博客列表。</li>
<li>相当完美!</li>
<li>[说明]新版本的jekyll的命令发生了变化。<code class="language-plaintext highlighter-rouge">jekyll build</code>用来编译程序;<code class="language-plaintext highlighter-rouge">jekyll serve</code>用来编译并启动一个web服务。</li>
</ol>