如何玩转Zeus,一个攻城狮 程序猿 测试豹的技术笔记

查看: 399|回复: 8
主题标签Tag
今日重磅推荐Recommend No.1
【】最新CCNA题库 更新175题解析社区更新Forums
随机图赏Gallery
SPOTO CCNA知识框架解读视频 60分钟实用视频 多个企业常用拓扑模型 教你企业实际应用思科官方 英文原版视频教程 新一代统一监控管理软件Prime Infrastruct 1.2 18集(高清)极客JavaWeb工程师全套视频教程 (初级+中级+高级) 一共485集 送面试辅导乾颐堂 现任明教教主 秦柯 近10年讲过的所有安全CCNA视频汇总下载(补档奖励1000金币)传智博客Java就业班面试资料(程序猿面试技巧+常见笔试题分析) 六讲慕课网—安卓Android零基础到实战编程视频教程 两门主课 122集120本PDF电子书打包下载 电脑黑客攻防解密 新手集训营Python自动化开发基础编程视频破解版17天课程完整版 共254集最新2016河南省H3C计算机网络应用省赛赛前培训视频教程+历年真题+学习资料包2016最新乾颐堂 安德华为入门HCNA 8班VIP视频录像39集2015最新乾颐堂CCIE DC V2马海波数据中心课程四门全套GNS3打包虚拟机/vGNS3下载及说明
【ADAM的网络笔记】---第八讲 DHCP技术详解2
查看: 399|回复: 8
本帖最后由 孤帆江上 于
17:10 编辑
首先非常感谢张同学在上一讲里为ADAM指出的错误,在此表示由衷感谢!【欢迎大家对ADAM的网络笔记中不经意的细节错误做出指正批评,我们会积极勘误,为提出意见的同学加上100-300不等的积分!】
c70bc59.png (109.25 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:07 上传
& & 高亮部分中的“当服务器来请求IP地址时”改写为“当客户端请求IP地址时”,好了,下面赶紧开始第二讲啦!在第一讲里,我们描述了关于DHCP最基本的分配地址的功能,今天我们来讨论DHCP的其它一些问题吧!
分配WLC(控制器)的IP——ADAM首先啊,我们来说说DHCP除了可以分配主机的IP地址之外,我们最常用到的其它一些功能有哪些呢?
分配网关的IP
分配DNS服务器IP
分配TFTP服务器IP / 分配CALL MANAGER(管理软件)IP
& &&&以上这些啊,就是我们比较常见的DHCP其它的一些功能了,那么这些功能是怎么实现的呢??相信自己做DHCP实验抓包的同学一定发现了,原来在DHCP的报文中有很多很多的OPTION字段!而要实现上述功能,都是通过在DHCP报文中的OPTION字段填写相关信息来完成的!DHCP的OPTION字段最少必需要添加1个,最多的话,可以携带255个。
a4f54521.png (187.82 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:09 上传
如图1-1,这就是一个DHCP报文了,在这里要说明几点:
dhcp 4层采用的是udp协议,并且呢,使用了2个端口号分别是67和68(大家在进行ACL和其它策略定义时,注意要对这两个端口号进行放行哦!)分别对应bootps(67) 和 bootpc(68),bootps对应DHCP服务器端的端口号,而bootpc对应DHCP客户端的端口号。
红色高亮的OPTION字段有:
& && & OPTION 1& & 携带了子网掩码信息& && & OPTION 3& & 携带了默认网关的地址& && & OPTION 6& & 携带了DNS服务器的地址& && & OPTION 51& &携带了租期(DHCP的地址不是永远分配的,当租期到期时,& && & 客户端的地址将不能再使用,且服务器将会把这个IP地址重新添加进地址池& && & 中去,cisco默认的时间是1天)
& && & 那么除了在这个保重携带的,我们还要知道的OPTION有:& && & OPTION 43& &携带WLC的IP地址
好了,说到这里的话,大家对OPTION应该有个基本认知了,接下来我们来讨论另一个问题
a59d2a01.png (28.54 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:09 上传
& && & 如图1-2,在这个环境中,R1作为DHCP&&SERVER 提供IP地址的分配,HOST2使用VM1虚拟网卡和R2进行桥接,作为客户端进行地址的动态获取,并且我们已经完成了基本IP地址的配置及R1的DHCP服务器配置,那么此时我们能不能通过DHCP的方式,动态的让主机获得IP地址呢?
a6147c3d.png (26.54 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:09 上传
& && & 我们用IPCONFIG命令查看后,发现我们VM1的网卡地址是169.254开头的一个B类地址,当你无法通过DHCP方式获得IP地址时,微软操作系统会为你分配这一个地址。这个地址称为APIPA地址(automatic private ip addressing),这是为了让设备在无法通过DHCP方式获得地址时,任然有ip地址可以通讯。当你获得APIPA地址后,设备任然会每隔5分钟进行一次DHCP请求,如若仍然没有获得地址,那么会继续使用APIPA地址,这个地址你可以修改哦!
a695273a.png (58.92 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:10 上传
& && & 如图1-4,你可以在ipv4地址配置中找到备用配置选项,这里的自动专用IP地址选项就是APIPA地址啦!默认用的就是169.254开头的B类地址,所以大家发现,如果两台电脑直连后,其实不配地址也可以通讯哦!当然你也可以选择用户配置,手工地配置这个地址。& && & 好了,回到正题,也就是说,到现在为止,我们并没有通过DHCP服务器获得IP地址,而采用了系统分配APIPA的地址,那为什么我们现在无法通过DHCP方式获得IP地址呢?& && & 这是因为DHCP的所有数据包都是广播的啊!& && & 回想第一讲的内容,整个请求的流程是客户端先发送一个DHCP&&DISCOVERY的消息,而在图1-2的例子中,我们的HOST2发出的DHCPDISCOVERY消息到达R2后,R2发现目标地址为255.255.255.255的广播地址会帮他转发么?当然不会!路由器默认并不会对目标IP地址为广播地址的数据进行转发哦!所以啊,连第一个包都到不了DHCP SERVER(也就是R1)上!怎么能够解析到地址呢。& && & 因此在这里就有了DHCP中继这个技术了!& && & DHCP中继技术的原理非常简单,就是当R2收到了广播的DHCP报文后,把它转换成单播的数据消息,发送给R1,那这件事不就解决了么!就是这么简单!& && & 在R2的接收端口F1 / 0接口,使用命令
(config-if)#ip helper address 12.12.12.1(R1对于R2可达的地址即可),这样啊,这个DHCP&&DISCOVERY消息的源目IP地址就会变为R2的F1 / 0接口地址,而目标地址是12.12.12.1!如图1-5所示:
a71f2b60.png (67.81 KB, 下载次数: 0)
【ADAM的网络笔记】---第八讲 DHCP技术详解2
17:10 上传
& && & 所以啊,对于R1来说这就是个单播包了!所以R1就可以进行OFFER包的响应了。然后啊,R2收到这个单播包后,会把这个包转换成目标地址为广播地址的数据了,因为这个时候主机还没有地址!
& && & 但是在这里,千万要注意一个问题!!R1要回应这个单播包,R1必须首先要有去23.23.23.254这个地址的路由啊!
所有&ADAM的网络笔记&汇总
width:100%">
享有帖子相关版权3、其他单位或个人使用、转载或引用本文时必须同时征得该帖子作者和的同意4、帖子作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意7、管理员和版主有权不事先通知发贴者而删除本文', this.href);">论坛版权
渗透无处不再!
width:100%">
这是什么东东啊
width:100%">
支持一下:lol
width:100%">
好好 学习了 确实不错
width:100%">
路过,支持一下啦
width:100%">
支持一下:lol
width:100%">
我抢、我抢、我抢沙发~
width:100%">
感谢楼主分享,学习了
width:100%">
Powered by
Designed by当前位置: &
5,484 次阅读 -
【Zeus3简介】
Zeus3是一个完整的Hadoop的作业平台,是基于Zeus的一个二次开发项目,从Hadoop任务的调试运行到生产任务的周期调度,宙斯支持任务的整个生命周期从功能上来说,支持:
HadoopMapReduce任务的调试运行
Hive任务的调试运行
Shell任务的运行
Hive元数据的可视化查询与数据预览
Hadoop任务的自动调度
完整的文档管理
【作者简介】
蔡畅奇(Andy.cai),Java工程师 一直从事软件研发工作, 从2010年开始从事大数据的工作,做过ERP, 车联网, 爬虫, 电商系统的大数据体系架构. 专研过Java分布式编程以及传统、机器学习算法。,
Andy.cai利用业余时间在阿里Zeus/Zeus2的基础上做二次开发,历时2周,并完善使用文档贡献给大家。
【文章正文】
改造Zeus的由来还要从项目组上Hive离线挖掘说起。当初项目组上Hive数据仓库需要调度各种Hive-SQL与Linux-Shell脚本,最开始的时候是通过Linux终端直接在生产环境上部署,并通过Linux的crontab调度。
生产环境上第一个脚本调度程序没有感觉,上多个后发现问题太多了:
[a]每个脚本程序运行情况不好监控,程序运行情况无法可视化查看
[b]每个服务器上跑了多少调度脚本不清楚
[c]运维同事生产发版更乱,无法简单地掌握全部调度情况
[d]复杂的workflow调度只能通过shell里面调用子shell的方式,耦合太深,不利于模块化。
[e]缺乏脚本调度的统计,多少成功,多少失败,多少延迟等
为了解决这个问题,需要采用一套调度系统,由于生产集群有安全控制需要,所以最好能是可以Web显示的系统。
我之前调研过Oozie调度系统,Oozie使用的时候有以下不便:
[a]Oozie调度的Workflow只能使用XML文件配置
[b]启动调度只能通过命令行
[c]无法通过Oozie界面调试调度脚本
[d]Oozie无法可视化调试脚本时候
[e]无法分组,权限管理等
同时调研过Kettle系统,Kettle更多关注在关系型DB之间的调度。由于Kettle不支持web页面,并且通过Hive Server调度Hive做Hive计算不太灵活。
因此视线转移到了阿里的Zeus大数据调度系统[1],但是阿里的Zeus调度系统距离现在已经2年没有维护。说到Zeus调度系统,我本人一年多前面试杭州阿里的推荐引擎大部门算法专家职位的时候,面试我的主管还特地问过我例如:Zeus是干啥的之类的问题,并给我介绍Zeus是他们团队的作品。加上Zeus是用java开发,我这几年一直研究阿里的java开源系统与java中间件开发,这样更加增加了我对Zeus调度系统改造的兴趣。
首先介绍下Github上的Zeus调度系统[1],Github上的Zeus系统刚刚上线时候很多人关注但是由于长期缺少人维护。由于时隔2年,支持的还是Hadoop1.X,我通读了里面的代码,
Zeus基于GWT开发。Google Web Toolkit的缩写,有了 GWT可以使用 Java 编程语言编写 AJAX 前端,然后 GWT 会交叉编译到优化的JavaScript 中,而 JavaScript 可以自动在所有主要浏览器上运行。GWT允许开发人员使用 Java 编程语言快速构建和维护复杂但性能高的 JavaScript 前端应用程序,从而降低了开发难度,尤其是与 Eclipse Google 插件结合使用时,优势更明显。Zeus通过MySQL存储各项调度任务,使用Dos2Unix转换脚本格式,通过读取(hive,shell以及自定义的变量)环境变量,使用Processbuilder调度。另外,使用CronTrigger定义定时调度。
Zeus能支持主要4种调度:
(1) Hive调度,将页面上的hive脚本转换为sql,然后通过hive -f x.sql执行调度。
(2)Shell调度,将页面上的shell脚本转为x.sh 通过sh x.sh调度。
(3)mapreduce调度,将页面上的jar文件通过hadoop 执行调度。
(4)调度java-jar包
Zeus的工程由以下几部分组成:
Zeus3的使用:
对一个软件系统的了解,先是了解如何使用这个系统,有哪些功能,开发的技术是啥,用了哪些套路,就像学骑车一样,先学会骑,然后再考虑如何改进与二次开发。下面再介绍Zeus3的使用场景:
Zeus3的安装与部署
[1]配置目录权限
mkdir -p /data/zeus/log
sudo -u hdfs hadoop fs -mkdir /zeus
sudo -u hdfs hadoop fs -mkdir /zeus/hdfs-upload-dir
mkdir -p /data/zeus/users/zhoufang/zeus/run_job_dir
mkdir -p /data/zeus/group/tbdataapplication/zhoufang
mkdir -p /data/zeus/users/zhoufang/zeus/logs/
[2]配置文件修改
2.1 zeus-web.properties //新增加的配置文件
2.2 persistence.xml
2.3 log4j.xml
2.4 broadcast-applicationContext.xml
[3]zeus的table创建文件
先创建zeus3数据库
通过tomcat起来,tomcat的内存请开大,如:12G。
另外,多次启动的时候,有可能需要kill掉之前的tomcat进程。
Zeus只支持firefox,chrome不支持IE
[6]自动发邮件设置
zeus-web.properties
mail.host=
mail.smtp.auth=false
mail.smtp.timeout=25000
mail.username=
mail.password=xxx
Zeus3的使用:
通过界面调试Hive脚本:
调试Hive的结果界面:
[2]调试Shell
调试页面,大体同调试Hive的界面:
[3]调度Shell
场景1:shell_task_1.sh 内部调用shell_task_yy.sh
Step1:上传shell_task_yy.sh作为资源
Step2:编辑调用
#!/bin/bash
sh ./shell_task_yy.sh
echo ‘shell_task_1’
Step3:执行
Step4:执行结果
场景2:定时调度任务,每隔1分钟调度一次,不断循环
0 0/1 * * * ?
场景3:依赖调度,先调度A再调度B
0 0/1 * * * ?
场景3:依赖调度,先调度A再调度B
具体配置细节:
(1)A任务调度配置自动一分钟一次:0 0/1 * * * ?
(2)B任务调度要配置依赖A任务,然后选择启动
(3)启动A任务
(4)查看workflow的调度结果
整个workflow由A触发
场景1-3:直接调用Hive-SQL,与SHELL类似
Zeus3的二次开发与改进之处:
[1] zeus-schedule工程中实现报警邮件发送,通过JavaMailSender实现
Java代码:
public class MailAlarm extends AbstractZeusAlarm{
private JavaMailSender mailS
public void alarm(List&String& users, String title, String content) throws Exception {
MimeMessage mailMessage = mailSender.createMimeMessage();
// 设置utf-8或GBK编码,否则邮件会有乱码
MimeMessageHelper messageHelper = new MimeMessageHelper(mailMessage, true, “utf-8”);
messageHelper.setTo(Global.getConfig(“mail.sendtolist”));
// 邮件接受者
messageHelper.setFrom(Global.getConfig(“mail.username”)); // 邮件发送者
messageHelper.setSubject(title); // 主题
// 邮件内容,注意加参数true,表示启用html格式
String htmlString =
messageHelper.setText(htmlString, true);
mailSender.send(mailMessage);
} catch (Exception e) {
e.printStackTrace();
XML配置:applicationContext-mail.xml
&?xml version=&#” encoding=“UTF-8”?&
&beans xmlns=“http://www.springframework.org/schema/beans”
xmlns:xsi=“http://www.w3.org/2001/XMLSchema-instance” xmlns:aop=“http://www.springframework.org/schema/aop”
xsi:schemaLocation=“http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd”&
&bean id=“mailSender” class=“org.springframework.mail.javamail.JavaMailSenderImpl”&
&property name=“host”&
&value&&/value&
&/property&
&property name=“javaMailProperties”&
&prop key=“mail.smtp.auth”&false&/prop&
&prop key=“mail.smtp.timeout”&25000&/prop&
&/property&
&property name=“username”&
&value&&/value&
&/property&
&property name=“password”&
&value&xxx#&/value&
&/property&
配置读取修改:DailyConf.java, OnlineConf.java
set(“fs.default.name”,Global.getConfig(“fs.default.name”));
set(“mapred.working.dir”,Global.getConfig(“mapred.working.dir”));
通过:Global读取配置参数
Global的读取配置实现:
Global.java
public class Global {
* 保存全局属性值
private static Map&String, String& map = Maps.newHashMap();
* 属性文件加载对象
private static PropertiesLoader propertiesLoader = new PropertiesLoader(“zeus-web.properties”);
* 获取配置
public static String getConfig(String key) {
String value = map.get(key);
if (value == null){
value = propertiesLoader.getProperty(key);
map.put(key, value);
PropertiesLoader.java,装载配置文件
public class PropertiesLoader {
private static Logger logger = LoggerFactory.getLogger(PropertiesLoader.class);
private static ResourceLoader resourceLoader = new DefaultResourceLoader();
private final P
public PropertiesLoader(String… resourcesPaths) {
properties = loadProperties(resourcesPaths);
public Properties getProperties() {
* 取出Property,但以System的Property优先,取不到返回空字符串.
private String getValue(String key) {
String systemProperty = System.getProperty(key);
if (systemProperty != null) {
return systemP
if (properties.containsKey(key)) {
return properties.getProperty(key);
return “”;
* 取出String类型的Property,但以System的Property优先,如果都为Null则抛出异常.
public String getProperty(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
* 取出String类型的Property,但以System的Property优先.如果都为Null则返回Default值.
public String getProperty(String key, String defaultValue) {
String value = getValue(key);
return value != null ? value : defaultV
* 取出Integer类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常.
public Integer getInteger(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
return Integer.valueOf(value);
* 取出Integer类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常
public Integer getInteger(String key, Integer defaultValue) {
String value = getValue(key);
return value != null ? Integer.valueOf(value) : defaultV
* 取出Double类型的Property,但以System的Property优先.如果都为Null或内容错误则抛出异常.
public Double getDouble(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
return Double.valueOf(value);
* 取出Double类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容错误则抛出异常
public Double getDouble(String key, Integer defaultValue) {
String value = getValue(key);
return value != null ? Double.valueOf(value) : defaultV
* 取出Boolean类型的Property,但以System的Property优先.如果都为Null抛出异常,如果内容不是true/false则返回false.
public Boolean getBoolean(String key) {
String value = getValue(key);
if (value == null) {
throw new NoSuchElementException();
return Boolean.valueOf(value);
* 取出Boolean类型的Property,但以System的Property优先.如果都为Null则返回Default值,如果内容不为true/false则返回false.
public Boolean getBoolean(String key, boolean defaultValue) {
String value = getValue(key);
return value != null ? Boolean.valueOf(value) : defaultV
* 载入多个文件, 文件路径使用Spring Resource格式.
private Properties loadProperties(String… resourcesPaths) {
Properties props = new Properties();
for (String location : resourcesPaths) {
InputStream is = null;
Resource resource = resourceLoader.getResource(location);
is = resource.getInputStream();
props.load(is);
} catch (IOException ex) {
logger.info(“Could not load properties from path:” + location + “, ” + ex.getMessage());
} finally {
IOUtils.closeQuietly(is);
这样,配置就能采用key-value的形式自由定义在:zeus-web.properties中:
————-zeus-web.properties文件内容—————–
fs.default.name=hdfs://hmaster1:8020
mapred.working.dir=/group/tbdataapplication/zhoufang
mapred.job.tracker=hmaster1:8021
supers=andy.cai
name=\u\u5947
uid=andy.cai
mail.host=
mail.smtp.auth=false
mail.smtp.timeout=25000
mail.sendtolist=
mail.title=Zeus_Warning_Email
mail.username=
mail.password=xxx#
实际上这个项目可以配合Diamond[2],dubbo[3],flash-dog[3]来实现,由于考虑到部署的复杂度,所以还是精简下,配置文件这块采用Global读取配置参数的形式,日志管理暂时去掉,服务调用采用GWT-RPC调用。
另外,ProcessJob.java, ConfUtil.java里面读取hive,hadoop等的配置文件,现在采用CDH版本的路径,后续可以改为通过配置文件读取,之前设计的是直接读取系统环境变量发现实际部署的时候有诸多不便。
以上Zeus3的工程代码下载
文中引用:
[1]Zeus调度系统
[2]diamond集中式配置管理系统
[3]dubbo RPC-SOA框架
[4]flash-dog 日志收集系统
注:转载文章均来自于公开网络,仅供学习使用,不会用于任何商业用途,如果侵犯到原作者的权益,请您与我们联系删除或者授权事宜,联系邮箱:contact@dataunion.org。转载数盟网站文章请注明原文章作者,否则产生的任何版权纠纷与数盟无关。
相关文章!
不用想啦,马上 发表自已的想法.
做最棒的数据科学社区
扫描二维码,加微信公众号
联系我们:  关于knockout的文章,园里已经有很多大神写过了,而且都写得很好。其实knockout学习起来还是很容易的,看看官网的demo和园里的文章,练习练习就可以上手了(仅限使用,不包含研究源码)。之所以想写这个系列,主要是想记录自己的学习和应用过程,也希望能给初学者一点帮助。
  既然是学习过程就一步一步来,从最开始的解决方案,到优化过程,到最后的实现方案。有了思考和对比,才会更加明白这个东西有什么好处,为什么使用它、什么情况要使用它。ok, 官网学习链接为?:
准备例子  
  过程是这样的:前台发送ajax请求,后台返回json字符串,前台生成html,插入到dom。这个过程我们再熟悉不过了,接下来我们就用多种方式完成这个例子。
  先用jquery简单写一个发送请求的方法,如下:
window.Tester = {
callback: function(fn) {
url: "../Handlers/GetCourse.ashx",
success: function(data) {
data = $.parseJSON(data);
  后台对应的实体对象,如下:
public class CourseInfo
public string CourseID { }
public string IconPath { }
public string CourseName { }
public string TeacherName { }
public string CreatedDate { }
public int StudyNumber { }
  html如下:
&ul id="course"&
&a href="/Default.aspx?courseID=001"&
&div class="course-img"&
&img src="../Image/1.jpg" /&
&div class="course-info"&
&div class="names"&
&span&jquery源码解析&/span&
&span class="fr"&李老师&/span&
&div class="pros"&
&span&&/span&
&span class="fr"&100人学习&/span&
  界面效果:
一、拼接字符串
  相信很多人开始都用过拼接字符串来生成dom元素,然后越写越多,越写越乱...,写到自己都看不太懂了,最后干脆挥挥手留给别人去看。我们都不希望这样做,有代码洁癖的朋友,看到这些应该会发狂。  
  我们来看一下实现上面的效果,用拼接字符串是怎么样的,代码如下:
Tester.callback(function(data) {
for (var i = 0; i & data. i++) {
var courseImg = "&div class='course-img'&&img src='" + data[i].IconPath + "' alt='" + data[i].CourseName + "'/&&/div&";
var names = "&div class='names'&&span&" + data[i].CourseName + "&/span&&span class='fr'&" + data[i].TeacherName + "&/span&&/div&";
var pros = "&div class='pros'&&span&" + data[i].CreatedDate + "&/span&&span class='fr'&" + data[i].StudyNumber + "人学习&/span&&/div&";
var item = "&li&&a target='_blank' href='Default.aspx?courseID=" + data[i].CourseID + "'&" + courseImg + "&div class='course-info'&" + names + pros + "&/div&&/a&&/li&";
$("#course").append(item);
  可以很快得出下面几点:1.拼接写起来很麻烦& 2.不能给人清晰的dom结构 3.到处都是字符串修改起来很麻烦。实际项目中,我们应该尽量避免这种情况。
二、clone dom
  为了解决上面的缺点,我们可以把html模板先写好,并隐藏。等到需要时,再clone一份,生成html。代码如下:  
&div id="tmp" class="noen"&
&li id="tmpItem"&
&div class="course-img"&
&div class="course-info"&
&div class="names"&
&span&&/span&
&span class="fr"&&/span&
&div class="pros"&
&span&&/span&
&span class="fr"&&/span&
Tester.callback(function(data) {
for (var i = 0; i & data. i++) {
var item = $("#tmpItem").clone();
item.find("a").attr("href", "Default.aspx?CourseID=" + data[i].CourseID);
item.find(".course-img&img").attr({ "src": data[i].IconPath, "alt": data[i].CourseName });
item.find(".names&span:eq(0)").text(data[i].CourseName);
item.find(".names&span:eq(1)").text(data[i].TeacherName);
item.find(".pros&span:eq(0)").text(data[i].CreatedDate);
item.find(".pros&span:eq(1)").text(data[i].StudyNumber + "人学习");
$("#course").append(item);
&  看起来比拼接字符串好多了。这里我们提到了&模板&的概念,但它还不是真正意义上的模板,所谓模板应该是:基础内容准备好了,就差数据,只要把数据传递过来,就可以生成完整内容。可以看到,我们上面还是自己去解析数据,然后生成内容,而不是自动化的过程。如果可以这样生成html就最好了:var html = template("#tmpID",data); tmpID 表示模板的id,data 是数据,这样生成html,不用自己去for遍历。没错,这就是大多数模板引擎的实现思路。
三、模板引擎
  关于js模板引擎有很多,我也会在下一篇文章单独介绍。不过在这里我不想马上就用现成的,我们自己先实现试试看!
3.1 基础版
  首先我们需要找到字符串中真实数据的位置,这通常是通过&占位符&来实现的,例如:${ $};然后再将占位符替换为真实的数据。查找占位符可以用正则表达式实现,替换占位符用字符串操作即可。
  例如字符串:my name is ${name$}, i am ${year$} years old。 数据为:{name : "tom", year : 18}。我们希望生成最后的结果是: my name is tom, i am 18 years old。
  先编写匹配占位符的正则表达式:/\${((?:.(?!\$}))*.)?\$}/g (说明:正则水平一般,卡了好久...,厉害的朋友在回复写出更好的!)。实现代码如下:
var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var str = "my name is ${name$}, i am ${year$} years old";
var data = {
name : "tom",
while (match = reg.exec(str)) {
str = str.replace(match[0], data[match[1]]);
console.log(str);//my name is tom, i am 18 years old
  简单解释一下:核心是exec方法,它返回的是一个数组,包括匹配到字符串的值,和其位置等。match[0] 是占位符;match[1] 是占位为内的内容(如name)。这样通过一个循环,就可以将所有匹配找到。
&3.2 改进版
  上面例子实在太简单了,看一个稍微复杂点的结构。字符串是:my name is ${name$}, i am ${info.age$} years old。数据为:{name: "tom", info: {age:18}}。按上面的做法就不能得到正确的结果了,因为匹配后 match[1] 为 &info.age&,而 data["info.age"] 显然不能获取到18。如果可以在字符串里写js呢,例如:this.name或.age,运行时this由我们传递并执行,这样问题就解决了。这里有两个问题:1. 如何在字符串里写js代码?& 2.this 如何动态决定?
  要在字符串里写代码执行,Function 就可以实现。Function接收字符串类型的参数,前面的是函数的参数,最后一个是函数的执行体。例如:var fn = new Function("arg1","arg2","return arg1 + arg2;"); fn 就是一个函数,接收两个参数。可以执行得到结果:console.log(fn(1,2)); //3。那么 this 如何由我们动态决定呢?答案就是:对象冒充。js 的 call, apply 就是用来实现对象冒充的。
  解决了这两个问题,实现起来就轻松多了,如下:
var code = "return 'my name is ' + this.name + ', i am ' + .age + ' years old';";
var fn = new Function(code).apply(data);
console.log(fn);
  这里我们创建一个函数,函数执行体就是code,this指向了data对象。注意,这里 this.name 不能加'',否则就作为普通字符串进行拼接了。字符串拼接太麻烦了,在网上看到一种很好的做法,通过数组实现,代码如下:
var code = "var result = [];"
code += "result.push('my name is ');";
code += "result.push(this.name);";
code += "result.push(' i am ');";
code += "result..age);";
code += "result.push(' years old');";
code += "return result.join('');";
var fn = new Function(code).apply(data);
console.log(fn());
&  同样,数据部分不能加''。这种方式很巧妙,fn 执行时,会从 var& result = []; 开始执行,this 就是 data 对象,最后生成字符串返回。这里我们简单封装一下:
var str = "my name is ${this.name$}, i am ${.age$} years old";
var data = {
name: "tom",
info: {age:18}
function template(html, data) {
if (!html) {
var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var cursor = 0;
var code = "var result = [];\n";
while (match = reg.exec(html)) {
code += "result.push('" + html.substring(cursor, match.index) + "');\n";
code += "result.push(" + match[1] + ");\n";
cursor = match.index + match[0].
code += "result.push('" + html.substring(cursor) + "');\n";
code += "return result.join('')";
//console.log(code);
return new Function(code.replace(/\n/g,"")).apply(data);
console.log(template(str, data));
3.3 最终版
  许多时候后台返回的是json数组字符串,这时需用使用逻辑判断和循环来处理。这里需要一个正则:/(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g 用来匹配判断循环关键字。需要注意的是,当遇到这些关键字的时候,就不能push到数组里了,而应该是作为程序的一部分执行,例如:
  var result = [];
  for(var i=0;i&10;i++){
    result.push(this.name);
  结合上面的,封装一个最终版,如下:
function template(id, data) {
if (!id) {
throw new Error("模板id不能为空!");
var jTmpl = $(id);
if(jTmpl.length &= 0){
throw new Error("找不到id为:"+id+"的模板");
var html = jTmpl.html();
if(!html){
html = html.replace(/\"/g,"\\\"");
var reg = /\${((?:.(?!\$}))*.)?\$}/g;
var logicReg = /(^( )?(if|for|else|switch|case|break|{|}))(.*)?/g;
var cursor = 0;
var code = "var result = [];\n";
while (match = reg.exec(html)) {
code += "result.push('" + html.substring(cursor, match.index) + "');\n";
code += match[1].match(logicReg) ? match[1] : "result.push(" + match[1] + ");";
code += "\n";
cursor = match.index + match[0].
code += "result.push('" + html.substring(cursor) + "');\n";
code += "return result.join('')";
//console.log(code.replace(/\n/g, ""));
return new Function(code.replace(/\n/g, "")).apply(data);
&  我们试着用这个模板完成上面拼接字符串和clone dom 相同的功能。先定义模板:
&script type="text/tmpl" id="courseTmpl"&
${for(var i=0,length=this.i&i++){$}
&a href="Default.aspx?courseID=${this[i].CourseID$}"&
&div class="course-img"&
&img src="${this[i].IconPath$}" alt="${this[i].CourseName$}"/&
&div class="course-info"&
&div class="names"&
&span&${this[i].TeacherName$}&/span&
&span class="fr"&${this[i].CourseName$}&/span&
&div class="pros"&
&span&${this[i].CreatedDate$}&/span&
&span class="fr"&${this[i].StudyNumber$}人学习&/span&
&  模板定义好后,执行代码就只有一行了!如下:
Tester.callback(function(data) {
$("#course").html(template("#courseTmpl",data));
  通过使用模板引擎,我只需要定义好模板,传递数据,渲染工作就由模板引擎自动完成了。
  这里还有一个小知识点,script的type属性设置为:text/tmpl,这个属性是浏览器不认识的。如果script的type是浏览器支持的(如text/javascript),就会当做脚本执行或通过src属性请求下载脚本再执行,如果是浏览器不支持的,就会忽略。所以这里可以用来存储数据,大多数模板也都是定义在这个地方。
  上面的模板引擎很简单,只有30行左右,但它其实已经可以解决一些简单的问题了。实际它还有许多问题没考虑,书写起来还是比较复杂的,也不可能针对多变的需求都适用,所以还是建议用于简单的应用或学习。很好的是,它让我们明白了整个解决思路和模板运行的过程。
  实际上现成的模板引擎已经很多了,接下来一篇就将介绍其中一个。
阅读(...) 评论()}

我要回帖

更多关于 攻城狮 程序猿 测试豹 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信