RSS是一种网页内容联合格式(web content sydication format)。
它的名字是Really Simple Syndication的缩写。
RSS是XML的一种。所有的RSS文档都遵循XML 1.0规范,该规范发布在W3C网站上。
在一个RSS文档的开头是一个<rss>节点和一个规定的属性version,该属性规定了该文档将以RSS的哪个版本表示。如果该文档以这个规范来表示,那么它的version属性就必须等于2.0。
在<rss>节点的下一级是一个独立的<channel>节点,该节点包含关于channel的信息和内容。
关于本文档
该文档是在2002年秋天撰写的,当时的RSS版本为2.0.1。
它包含从RSS 0.91规范(2000年)开始的所有的修改和添加,以及包含在RSS 0.92(2000年12月)和RSS 0.94(2002年8月)中的新的特性。
必需的频道节点
下面有一份必须包含的频道(channel)节点的列表,每一个都有一个简单的描述、一个例子、应该出现的位置和更详细描述的超链接。
元素 | 描述 | 范例 |
title | 频道(channel)名称。它可以告诉别人如何访问你的服务。如果你有一个与你的RSS文件内容一致的HTML网站,你的title元素值应该与你的网站的标题相同。 | GoUpstate.com News Headings |
link | 响应该频道的网站的URL | http://www.goupstate.com/ |
description | 关于该频道的描述 | The latest news from GoUpstate.com, a Spartanburg Herald-Joural Web Site |
可选的频道元素
下面是可选的频道元素列表
节点 | 描述 | 范例 |
language | 使用的语言。这允许聚合器对所有的意大利语站点分组。 | en-us |
copyright | 版权声明 | Copyright 2002, Spartanburg Herald-Journal |
managingEditor | 内容负责人的Email | geo@herald.com (George Matesky) |
webMaster | 技术人员的Email | betty@herald.com (Betty Guernsey) |
pubDate | 内容的发布时间 | Sat, 07 Sep 2002 00:00:01 GMT |
lastBuildDate | 最后更新时间 | Sat, 07 Sep 2002 09:42:31 GMT |
category | 指定该频道所属的一个或多个分类。遵循与item级category元素相同的规则。 | <category>Newspapers</category> |
generator | 生成该频道的程序名称 | MightyInHouse Content System v2.3 |
docs | 指向rss格式文档的url地址? | http://blogs.law.harvard.edu/tech/rss |
cloud | 允许所有进程注册一个cloud用于获得频道的更新通知,并为rss种子实现一个轻量级的发布订阅协议。 | <cloud domain="rpc.sys.com" port="80" path="/RPC2" registerProcedure="pingMe" protocol="soap"/> |
ttl | ttl是Time to live的缩写。它指示cache的有效保存时间。 | <ttl>60</ttl> |
image | 与频道一起显示的图片地址 | |
rating | 该频道的统计图片地址 | |
textInput | 指定一个textbox与该频道一起显示 | |
skipHours | 告诉使用者哪些时段是可以忽略的 | |
skipDays | 告诉使用着哪些天是可以忽略的 |
<channel>子节点<image>
<image>是一个可选的<channel>子节点,该节点包含三个必需的子元素和三个可选的子元素。
<url>是GIF、JPEG或PNG图像文件的URL地址,该图像代表整个频道
<title>用于描述上面的图像,等同于HTML语言中的<img>的alt属性
<link>是要连接的站点的url,当显示频道时,图像的连接指向该站点。
<title>和<link>应该与频道的<title>和<link>有相同的值
可选的节点包括<width>和<height>,它们是数字类型,指定图像的宽度和高度,单位为像素
<description>就是link的TITLE属性中文本,它将在调用网页时显示出来。
图像宽度的最大值为144,默认值为88
图像高度的最大值为400,默认值为31
<channel>子节点<cloud>
<cloud>是一个可选的<channel>子节点。
它指定一个可以支持rssCloud接口的web服务,rssCloud接口可以用HTTP-POST,XML-RPC或SOAP1.1实现。
它的目的是允许通知注册为cloud的进程频道被更新,从而实现一个轻量级的发布订阅协议。
<channel>子节点<ttl>
<ttl>是一个可选的<channel>子节点。
ttl是time to live的缩写。它表示频道在被刷新前应该被缓存的时间。这使得rss源可以被一个支持文件共享的网络所管理,例如Gnutella
例如:<ttl>60</ttl>
<channel>子节点<textInput>
<textInput>是<channel>的可选的子节点,<textInput>包含四个子节点。
<title>--提交按钮的标签
<description>--该文本输入区的描述
<name>--文本输入区的名称
<link>--处理文本输入的CGI脚本的URL
使用<textInput>的目的有些神秘(?)。你可以用它提供一个搜索引擎输入框,或让读者提供反馈信息。许多聚合器忽略该节点。
<item>的节点
一个频道可以包含许多项目(item)节点。一个项目可以代表一个故事——比如说一份报纸或杂志上的故事,如果是这样的话,那么项目的描述则是故事的概要,项目的链接则指向整个故事的存放位置。项目的所有节点都是可选的,但是至少要包含至少一个标题(title)和描述(description)。
节点 | 描述 | 范例 |
title | item的标题 | Venice Film Festival Tries to Quit Sinking |
link | item的URL | http://www.nytimes.com/2002/09/07/movies/07FEST.html |
description | item概要 | Some of the most heated chatter at the Venice Film Festival this week was about the way that the arrival of the stars at the Palazzo del Cinema was being staged. |
author | 作者的email地址 | oprah@oxygen.net |
category | item可以包含在一个或多个分类中 | Simpsons Characters |
comments | 与item相关的评论的地址 | http://www.myblog.org/cgi-local/mt/mt-comments.cgi?entry_id=290 |
enclosure | 附加的媒体对象 | |
guid | 可以唯一确定item的字符串 | http://inessential.com/2002/09/01.php#a2 |
pubDate | item发布的时间 | Sun, 19 May 2002 15:21:36 GMT |
source | rss频道来源 | Quotes of the Day |
<item>子节点<source>
<source>是<item>的可选节点。
它的值是item来自的rss频道的名称,从item的title衍生而来。它有一个必须包含的属性url, 该属性链接到XML序列化源。
<item>子节点<enclosure>
<enclosure>是<item>的可选节点。
它有三个必要的属性。url属性指示enclosure的位置,length指出它的字节大小,type属性指出它的标准MIME类型
url必须为一个http url。
<item>子节点<category>
<category>是<item>的可选节点。
它有一个可选属性或域,该属性是一个用来定义分类法的字符串。
该节点的值是一个正斜杠分割的字符串,它用来在指定的分类法中识别一个分级位置(hierarchic location)。处理器可以为分类的识别建立会话。(Processors may establish conventions for the interpretation of categories)下面有两个例子:
<category domain="http://www.fool.com/cusips">MSFT</category>
<item>子节点<pubDate>
<pubDate>是<item>的可选节点。
它的值是item发布的日期。如果它是一个没有到达的日期,聚合器在日期到达之前可以选择不显示该item。
<item>子节点<guid>
<guid>是<item>的可选节点。
guid是globally unique identifier的缩写。它是一个可以唯一识别item的字符串。当item发布之后,聚合器可以选择使用该字符串判断该item是否是新的。
<guid>http://some.server.com/weblogItem3207</guid>
guid没有特定的语法规则,聚合器必须将他们看作一个字符串。生成具有唯一性的字符串guid取决于种子的源头。
如果guid节点有isPermaLink属性,并且值为真,读取器就会认为它是item的permalink。permalink是一个可在web浏览器中打开的url链接,它指向<item>节点所描述的全部item。
<guid isPermaLink="true">http://inessential.com/2002/09/01.php#a2</guid>
isPermaLink是可选属性,默认值为真。如果值为假,guid将不会被认为是一个url或指向任何对象的url。
<item>子节点<comment>
<comment>是<item>的可选节点。
如果出现,它指向该item评论的url
<comments>http://rateyourmusic.com/yaccs/commentsn/blogId=705245&itemId=271</comments>
<item>子节点<author>
<author>是<item>的可选节点。
它是item的作者的email。对于通过rss传播的报纸和杂志,作者可能是写该item所描述的文章的人。对于聚集型webblogs,作者可能不是责任编辑或站长。对于个人维护的webblog,忽略<author>节点是有意义的。
<author>lawyer@boyer.net (Lawyer Boyer)</author>
默认情况下,PHP会话(session)是通过文件来保存的。这样做有以下几个缺点:
- 会话文件一般都很小,但文件数却很多,在文件系统中保存许多这样的小文件非常浪费空间,且效率不高。
- 分布式的站点难以利用会话文件来共享会话。
- 会话文件方式不利于统计在线用户的会话信息。
为解决以上问题,我们可以考虑用数据库来保存会话信息。
对于 PHP 开发来说,保存会话用 MySQL 是一个非常不错的选择。MySQL 提供一种建立在内存中的表类型 Heap,如果每条会话数据量很小的话,可以考虑用这种类型的表来进一步优化性能。但是 Heap 类型的表有许多限制,例如它不支持 text 类型的字段,因此如果在无法预测会话数据记录长度的情况下,选择 MyISAM 是比较合适的,这种类型的表没有事物处理开销,对于基于磁盘的表可以得到最优性能。
下面是 sessions 表的结构:
- DROP TABLE IF EXISTS `sessions`;
- CREATE TABLE `sessions` (
- `session_id` varchar(32) NOT NULL default '',
- `user_id` int(10) unsigned NOT NULL default '0',
- `data_value` text NOT NULL,
- `last_visit` timestamp(14) NOT NULL,
- PRIMARY KEY (`session_id`),
- KEY `user_id` (`user_id`)
- ) TYPE=MyISAM;
PHP 支持用户会话模块,可以通过 session_set_save_handler 来设置自定义的会话处理函数。因为默认的处理模块是 files,因此要在用 session_set_save_handler 设置会话处理函数之前,先用 session_module_name(’user’) 来告诉 PHP 使用用户会话模块, 而session_set_save_handler 必须要在 session_start 之前执行。
用户会话数据在会话处理函数中都是序列化之后的,要取出其中的某个会话变量,可以对其进行反序列化,默认是 php 序列化方式,可以用 session::unserialize 函数来反序列化。
下面的代码定义了一个用 MySQL 来处理 PHP 会话的类,其中所使用的 class_mysql.php 请参见 《超级简单但超级实用的 PHP 的 mysql 类》 。
- <?php
- /**
- * @author 马秉尧
- * @copyright (C) 2005 CoolCode.CN
- */
- require_once("class_mysql.php");
- class session {
- var $db;
- function session(&$db) {
- $this->db = &$db;
- session_module_name('user');
- session_set_save_handler(
- array(&$this, 'open'),
- array(&$this, 'close'),
- array(&$this, 'read'),
- array(&$this, 'write'),
- array(&$this, 'destroy'),
- array(&$this, 'gc')
- );
- session_start();
- }
- function unserialize($data_value) {
- $vars = preg_split(
- '/([a-zA-Z_x7f-xff][a-zA-Z0-9_x7f-xff]*)|/',
- $data_value, -1, PREG_SPLIT_NO_EMPTY |
- PREG_SPLIT_DELIM_CAPTURE
- );
- for ($i = 0; $vars[$i]; $i++) {
- $result[$vars[$i++]] = unserialize($vars[$i]);
- }
- return $result;
- }
- function open($path, $name) {
- return true;
- }
- function close() {
- return true;
- }
- function read($session_id) {
- $session_id = $this->db->escape_string($session_id);
- if ($row = $this->db->query("select * from `sessions` where `session_id` = '$session_id' limit 1")) {
- return $row['data_value'];
- }
- else {
- $this->db->query("insert into `sessions` set `session_id` = '$session_id'");
- return "";
- }
- }
- function write($session_id, $data_value) {
- $data = $this->unserialize($data_value);
- $session_id = $this->db->escape_string($session_id);
- $data_value = $this->db->escape_string($data_value);
- $this->db->query("update `sessions` set "
- . "`user_id` = '{$data['user_id']}', "
- . "`data_value` = '$data_value', "
- . "`last_visit` = null "
- . "where `session_id` = '$session_id'");
- return true;
- }
- function destroy($session_id) {
- $session_id = $this->db->escape_string($session_id);
- $this->db->query("delete from `sessions` where `session_id` = '$session_id'");
- return true;
- }
- function gc($lifetime) {
- $this->db->query("delete from `sessions` where unix_timestamp(now()) - unix_timestamp(`last_visit`) > $lifetime");
- return true;
- }
- // get sessions by user_id
- function get($user_id) {
- $user_id = $this->db->escape_string($user_id);
- return $this->db->query("select * from `sessions` where `user_id` = '$user_id'");
- }
- // get sessions list
- function lists($page, $rows) {
- if ($page == 0) {
- return $this->db->query("select * from `sessions` order by `user_id`");
- }
- else {
- $start = ($page - 1) * $rows;
- return $this->db->query("select * from `sessions` order by `user_id` limit $start, $rows");
- }
- }
- }
- ?>
这个类的使用很简单,在原来使用 session_start 的地方,替换成 $session = new session($db) 就可以了。$db 表示 sessions 表所在的数据库。
另外可以用 get 方法来获取某个用户的所有会话信息,通过 lists 方法来得到所有用户会话列表。这样就可以方便的管理用户会话了。
第一部分、较轻微的错误
一、Printf(),
该函数主要用来格式化显示数据。当你要改变某个数据的显示格式时才使用。
例如以不同的精度来显示PI(3.1415926)的值。
/*
* The three faces of Π
*/
printf ("Pi is: %.2fn
n", M_PI);
printf ("Pi is also: %.3fn
n", M_PI);
printf ("Pi is also: %.4fn
n", M_PI);
?>
但许多程序员仅仅为显示一些变量值和函数返回值使用该函数。因为Printf()在显示数据前要先格式化该数据以速度较慢,因此,仅为了显示数据时应用print和echo,以提高速度。
二、语意检查
PHP是一种弱类型语言,也就是说在使用一个变量前不用定义,这样给编程带来了很大的方便和灵活,但你自己必须知道该变量到底应该是哪种类型,因为该变量在运行时仍实际对应着某一种类型(各种类型之间可以自由互相转换),没有类型的变量是不存在的。有可能PHP并不能检查出你的语意错误,但由于变量类型的变化,会导致一些潜在的问题的发生。另外一个值得注意的问题是变量的范围,它也可能会导致一些潜在的问题的发生。
在PHP中有以下几种基本变量:
Boolean, resource, integer, double, string, array and object。
三、临时变量的使用
临时变量的滥用会导致程序运行效率的降低。何时使用临时变量可基于以下两点考虑:
1、该变量是否至少使用两次。
2、该变量的使用是否会显著提高程序的可读性。
如果一条也不满足,则省略该变量的使用。例如:
$tmp = date ("F d, h:i a"); /* ie January 3, 2:30 pm */
print $tmp;
?>
就应该改成:
print date ("F d, h:i a");
?>
又如:
// string reverse_characters(string str)
// Reverse all of the characters in a string.
function reverse_characters ($str)
{
return implode ("", array_reverse (preg_split("//", $str)));
}
?>
的可读性不强,可改成:
// string reverse_characters(string str)
// Reverse all of the characters in a string.
function reverse_characters ($str)
{
$characters = preg_split ("//", $str);
$characters = array_reverse ($characters);
return implode ("", $characters);
}
?>
四、客户端和服务器端代码的分离
客户端和服务器端代码的在PHP程序中实际上就是HTML代码和PHP语言代码,很多人把HTML和PHP语句混合在一个文件里,使得这文件很大,这种风格对程序的维护和再开发很不利,不适合大型站点的开发。一般有两种方法把HTML和PHP语句分开:
1、编写专用API,例如:
index.php ? The Client side
| |
site.lib ? The server side code
$dbh = mysql_connect ("localhost", "sh", "pass")
or die (sprintf ("Cannot connect to MySQL [%s]: %s",
mysql_errno (), mysql_error ()));
@mysql_select_db ("MainSite")
or die (sprintf ("Cannot select database [%s]: %s",
mysql_errno (), mysql_error ()));
$sth = @mysql_query ("SELECT * FROM site", $dbh)
or die (sprintf ("Cannot execute query [%s]: %s",
mysql_errno (), mysql_error ()));
$site_info = mysql_fetch_object ($sth);
function print_header ()
{
global $site_info;
print $site_info->header;
}
function print_body ()
{
global $site_info;
print nl2br ($site_info->body);
}
function print_links ()
{
global $site_info;
$links = explode ("n", $site_info->links);
$names = explode ("n", $site_info->link_names);
for ($i = 0; $i < count ($links); $i++)
{
print "ttt
$names[$i]
n
n";
}
}
?>
这种方法使得程序看起来比较简洁,而且执行速度也较快。
2、使用模板的方法
这种方法使得程序看起来更简洁,同样实现上面的功能,可用以下代码:
%%PAGE_TITLE%%
%%PAGE_LINKS%% | %%PAGE_CONTENT%% |
用占位符代替要动态生成的内容,然后用一解析程序分析该模板文件,把占位符用际的内容替换。种方法使得即使不会使用PHP的页面制作人员也能修改模板文件。这种方法的缺点是执行效率不高,因为要解释模板文件。同时实现起来也比较复杂。
注: www.thewebmasters.net的 FastTemplate class可方便的实现以上功能。
五、不要用过时的函数
作为一种自由软件,PHP发展很快,其中的很多函数都已过时,例如:
while (1):
print "5";
if ($idx++ == 5):
break;
endif;
endwhile;
虽然还能用但效率肯定不高,而且可能在以后的版本中会禁用,导致程序不能运行。因此要经常对照最新PHP手册检查那些函数已过时及时修正
<?
//asx的格式:
function channelprogramlist($filename){
$str="";
$str.="<ASX version="3.0">rn";
$str.="t<ENTRY>rn";
$str.="tt<AUTHOR>".$filename; //节目名
$str.="</AUTHOR>rn";
$filename=urlencode($filename); //把节目名转换
$filepath="http://".192.168.0.1"/".$filename; //192.168.0.1是服务器的ip
$str.= "tt<REF HREF="$filepath" />rn";
$str.= "t</ENTRY>rn";
}
$str.= "</ASX>";
return $str;
}
//写asx文件
function writeFile($str){
$file="mms.asx";
$fp = fopen($file,"w+");
fwrite($fp,$str);
fclose($fp);
return $file;
}
//控制mediaplayer每次从新读取文件
function PlayChannel($urlstr){
echo "<script language="javascript">n";
echo "function change(){n";
echo "t"."document.mediaPlayer.file=".$urlstr.";n";
echo "}n";
echo "</script>n";
}
?>
<html>
<head>
<title>播放器</title>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<?php
$filename="神话";
PlayChannel(writeFile(channelprogramlist($filename)));
?>
</head>
<body leftmargin="0" topmargin="0" onLoad="change()" bgcolor="#000000">
<object id="mediaPlayer" width="554" height="380" classid="clsid:22D6F312-B0F6-11D0-94AB-0080C74C7E95" standby="Loading Microsoft Media Player components..." type="application/x-oleobject">
<PARAM NAME="animationatStart" VALUE="true">
<PARAM NAME="transparentatStart" VALUE="true">
<PARAM NAME="autoStart" VALUE="true">
<PARAM NAME="CanSeek" VALUE="FALSE">
<PARAM NAME="showControls" VALUE="FALSE">
<param name="FileName" value="mms.asx">
<embed type="application/x-mplayerO" pluginspage="http://www.microsoft.com/Windows/Downloads/Contents/Products/MediaPlayer/" autostart="1" width="500" height="400">
</embed>
</object>
</body>
</html>
项目过程
1、项目启动
1)、项目组成立(公司成员、客户成员)
2)、制定项目预期目标
3)、制定项目计划周期
4)、建立好项目组成员沟通机制
2、需求调研
1)、创建调研计划、协调调研时间
2)、收集客户资料,获取客户需求
所有的资料都需要保留一份,资料中存疑的需要及时询问
3)、编写需求文档
重点描述出客户的业务流程和性能要求。
采用Word、Excel、Rose等形式。
4)、需求变更记录
5)、确定开发环境和运行环境
6)、扩展性要求
7)、与旧系统的接驳要求。
8)、估算出项目工作量
本阶段需要一套需求管理系统来进行需求的管理。
本阶段的需求文档也是用户测试的依据。
3、系统设计/详细设计
一个系统可以分为基础平台和应用模块两部分。
1)、选择基础平台,无论是采用第三方平台还是自行开发平台,都需要深入了解,查看是否符合要求。
2)、应用模块设计(针对业务流程)
3)、中间件的采用或自行开发,需要深入了解。
4)、用户界面的设计
如果用户界面设计完毕并确认,即可初步写出用户使用手册、管理员使用手册。
5)、变更记录
本阶段的系统设计是集成测试的依据。
4、程序开发
创建开发任务计划表、开发计划日程表
1)、优先编写测试用例
2)、按照编码规范编写代码
3)、按照文档注释规范注释
以上形成开发文档。
本阶段需要一套版本管理系统。
本阶段的测试用例也是单元测试的依据。
如果能做到,最好每日构建。
5、测试
本阶段需要一套Bug管理系统,形成需求、设计、开发、测试互动。
1)、编写测试计划和测试方案
2)、功能测试
单元测试、集成测试
3)、性能测试
集成测试、压力测试
如果能做到,最好能进行自动化测试。
如果能做到,做分析统计工作。
最后形成测试报告。
6、试用、培训、维护
本阶段需要解决:
1)、解决异地修改和公司修改的同步问题。
2)、用户测试中的Bug修改问题,按照级别分为
a)、程序Bug
b)、设计变更
c)、需求变更
尽量按照a b c的顺序来进行修改,尽量避免b、c级的修改。
最后形成安装手册、维护记录。
项目成员组成
根据以上过程,一个项目组中,需要:
1、需求工程师,其要求
善于与客户沟通,能快速了解客户的需求,对客户所在的行业比较熟悉。
善于学习新知识。
熟悉Word、Excel、Rose等工具的使用。
熟悉开发语言和开发框架
熟悉已积累的产品的功能、性能等。
2、系统分析师/设计师,其要求
精通开发语言和开发框架,部分需要精通数据库
精通已积累的产品的功能、性能等
深入了解客户行业特点
能根据客户的要求分析出其实质
能做出优秀的设计
熟悉Word、Excel、Rose等工具的使用
3、开发工程师,其要求
熟悉开发语言,熟悉开发要求和注释规范,部分需要熟悉数据库。
熟悉单元测试。
能根据设计做出良好的编码,保证功能和性能。
部分需要有一定的设计要求,因为涉及到将来的维护。
4、测试工程师,其要求
熟悉测试工作,能按照测试计划进行测试。
熟悉开发语言,能协助开发工程师找错。
能独立完成黑、白盒测试。
如果是高级测试人员,还要能够对系统能深入进行分析并能制定出优秀的测试方案。
5、管理人员
一般由以上人员兼任,主要有
项目经理:负责整个项目
开发经理:负责系统设计、开发工作
测试经理:负责测试工作
6、其他人员
一些项目涉及到其他人员,如页面设计人员、页面制作人员。
部分大的项目,还有专门的维护人员。
由于目前国内很多公司并没有严格这么区分,如果项目小的话,可以一人兼任多项职位