使用HTML5创建小说网站

在项目开始前,先要了解下HTML5规范包含的一些有用的新的语义标签,用于提供HTML页面的各个区域或部分的意义,例如页眉、页脚、导航栏、边栏等等。在以前的HTML版本中,这些部分通常使用<div>标签来创建,通过id或class属性来区分。

HTML5引入的主要标签包括:
<header>
此标签用于定义Web页面的某些部分的页眉,可以是整个页面、<article>标签或<section>标签。

<nav>
这是Web页面上主要导航链接的容器。此标签不应用于所有链接组,而是应仅用于主要导航块。如果您有一个<footer>标签包含导航链接,不需要将这些链接封装在<nav>标签中,因为<footer>标签将可以独自包括这些链接。

<footer>
此标签定义页面的某些部分的页脚。页脚不一定是在页面、文章或区域的结尾,但是它通常是在那个位置。

<article>
定义文档或页面上的独立区块,例如新闻、杂志、博文或评论。

<section>
此标签表示文档的一部分,例如,文章或教程的一章或一节。该标签通常具有一个页眉,虽然严格来说是不需要的。

<aside>
用于标记边栏或一些将认为与其周围内容有点无关的内容。此项的一个例子就是广告块。

<hgroup>
在某些情况下,页面、文章或区域可能需要多个标题,例如,您有一个标题和一个副标题。您可以在<hgroup>标签中封装这些标题,使用<h1>标签表示主标题,<h2>标签表示副标题。

这些标签的基础结构遵循以下大纲:

header
  +hgroup
nav
article
  +header
  +section
    ++header
footer

html5-novel-layout
上面是网站的布局设计,主要有header、navigation、footer和main四个区块,实现代码如下:

<!-- HTML5的DOCTYPE声明模式,它可以向后兼容,
     再也不用记忆XHTML中复杂的DOCTYPE了!-->
<!DOCTYPE html>
<html>
<head>
  <!-- 指定文档字符编码的写法,该写法在所有浏览器上都有效。-->
  <meta charset="utf-8" />
  <title>HTML5 + CSS3 Demo</title>
  <!-- link和script标签也无需提供type属性(No More Types for Scripts and Links),
       因为CSS和JavaScript是目前惟一受支持的样式表和脚本类型 -->
  <link href="style.css" rel="stylesheet" />
  <!-- 因为IE浏览器(甚至版本8)不支持新的HTML5标签,处理此问题的一个已知方法是使用
       JavaScript函数document.createElement()为每个标记创建虚拟标签。html5.js文件将
       为每个新的HTML标签进行此操作 -->
  <!--[if lt IE 9]>
    <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
  <![endif]-->
</head>
<body>
  <header>
    <hgroup>
      <h1><a href="#">Logo</a></h1>
      <h2>slogan</h2>
    </hgroup>
  </header>
  <nav>
    <ul>
      <li><a href="#">Catalog one</a></li>
      <li><a href="#">Catalog two</a></li>
      <li><a href="#">Catalog three</a></li>
    </ul>
  </nav>
  <!-- main block begin -->
  <!-- main block end -->
  <footer>&copy; 2013, codemany.com</footer>
</body>
</html>
* {
  font-family: Lucida Sans, Arial, Helvetica, sans-serif;
}
body {
  width: 768px;
  margin: 0px auto;
}
header h1 {
  font-size: 36px;
  margin: 0px;
}
header h2 {
  font-size: 18px;
  margin: 0px;
  color: #888;
  font-style: italic;
}
nav ul {
  list-style: none;
  padding: 0px;
  display: block;
  background-color: #666;
  padding-left: 4px;
  height: 24px;
}
nav ul li {
  display: inline;
  padding: 0px 20px 5px 10px;
  height: 24px;
  border-right: 1px solid #ccc;
}
nav ul li a {
  color: #efd3d3;
  text-decoration: none;
  font-size: 13px;
  font-weight: bold;
}
nav ul li a:hover {
  color: #fff;
}
footer {
  border-top: 1px solid #ccc;
  text-align: center;
  font-size: 12px;
  color: #888;
  margin-top: 24px;
}

整个网站主要有这么几个页面:首页、分类页、书目页、内容页,这些页面共用一个布局,主要区别在于main区块的不同。首页、分类页和书目页相似,都是由列表组成。

首页是多个无序列表:

<h3>Catalog 1</h3>
<ul>
  <li>Book's title</li>
  <li>Book's title</li>
</ul>
<h3>Catalog 2</h3>
<ul>
  <li>Book's title</li>
  <li>Book's title</li>
</ul>

分类页是单个无序列表:

<h3>Catalog 1</h3>
<ul>
  <li>Book's title</li>
  <li>Book's title</li>
</ul>

书目页是单个有序列表:

<h3>Book's title</h3>
<ol>
  <li>Chapter 1</li>
  <li>Chapter 2</li>
  <li>Chapter 3</li>
</ol>

html5-novel-article
内容页,顾名思义就是显示小说具体内容的页面。文章内容用<article>标签表示,其中的标题、作者、发表时间等信息被包含在<header>标签中。主要代码如下:

<article>
  <header>
    <h1><a href="#">Title</a></h1>
    <p>By <a href="#">Peter</a> on <time>2011-11-12 14:54</time></p>
  </header>
  <p>Lorem ipsum...</p>
  <section>
    <header>
      <h1>This is a section heading</h1>
    </header>
    <p>Lorem ipsum...</p>
  </section>
  <footer>
    <a href="#">Back</a>
    <a href="#">List</a>
    <a href="#">Forward</a>
  </footer>
</article>

在上面的代码里,<header>中我们仅使用了<h1>标签,这是因为HTML5会根据上下文计算出heading元素的层级,因此会有:
<body><h1> // 相当于heading 1
<body> … <section><h1> // 相当于heading 2
<body> … <section><section><h1> // 相当于heading 3

最后顺便说一句,HTML5支持已存在的各种写法:xhtml1.0、xhtml1.1和html4.0,但建议使用xhtml1.1规范,即:
1. 所有标签/属性都使用小写字母;
2. 所有属性值都必须加引号;
3. 使用闭合标签。

敏捷开发走下坡路了吗?

英文原文:http://thatextramile.be/blog/2008/11/agile-development-going-downhill

James Shore(非常精彩的书《The Art Of Agile Development》的作者)在他的博客上写了一篇很有趣的帖子:The Decline And Fall。你绝对应该读读它。

我想我同意James的观点。在过去的几年里,我已经听到了很多的人说他们在做敏捷开发,实际上,他们几乎都不是。引用James的话:

But guess which part people adopt? That’s right–Sprints and Scrums. Rapid cycles, but none of the good stuff that makes rapid cycles sustainable.

不幸的是,这是非常真实的。现在许多团队都在进行短迭代工作,很多团队也在做每日例会,或Scrum,或站立会议。但是,有多少人事实上致力于使敏捷开发成功的技术实践和原则呢?老实说,我一个也没见过。

我是一个真正的敏捷开发的铁杆迷,但即使在我现在的工作中,我最近的两个团队也没有完全正确地实现它。我们的研究结果虽然还不错,但我认为我们仍然可以做得更好。我逐渐尝试引入更多的原则和实践,但它确实需要一些时间。但是,所有这些关于敏捷开发的误解,所以很多人(开发人员,项目经理等)有没有真正帮助。在我当前的工作中,这些误解是非常小的,他们并不真正有不良影响。以前在客户那里,我确实注意到这些误解是如何导致异常低效的情况。其实这是非常伤心的,因为迟早经理可能会对敏捷方法持怀疑态度。如果这导致人们放弃一些技术实践和原则,对这个事业来说将会是一个相当大的损失。

我觉得有更多的理由去读James的那本出色的书,。如果我可以合法地迫使人们去阅读这本书,我会的:)

部署Qianbao到Heroku时遇到的一些问题

Heroku现在已经是纯粹的只读PaaS了,也就是说以前还支持的SQlite现在也不能使用了。因此部署到Heroku上的Rails应用需要把使用的数据库改成PostgreSQL,并且要关闭assets的预编译功能。

修改Gemfile,将

gem 'sqlite3'

改成

gem 'pg'

在config/application.rb中添加:

config.assets.initialize_on_precompile = false

股票功能需要导入交割单文件。因为导入后的文本文件不再使用,所以可以把上传路径由public/uploads改为tmp。这样就避免了不能写文件到public目录的问题。

应用上传后运行时出现异常,使用heroku logs -t命令查看日志发现有如下错误:

Error: column "stocks.share_name" must appear in the GROUP BY clause or be used in an aggregate function

这是因为在controller中有这么一行代码:

current_user.stocks.select("share_code, share_name, sum(actual_amount) as amount").group("share_code")

在PostgreSQL中这会有问题。比如下面的数据表:
qianbao-stocks-table

执行上面的SQL语句后,share_name的值到底是取Ruby呢还是ST Ruby?解决这个问题的方法是使用aggregate函数。

current_user.stocks.select("share_code, max(share_name) as share_name, sum(actual_amount) as amount").group("share_code")

PostgreSQL还有个问题,就是decimal类型的字段,取出来的值是字符串类型。例如:

if stock.amount < 0

它会报以下错误:

ArgumentError (comparison of String with 0 failed)

这个可以通过to_f函数解决。

if stock.amount.to_f < 0

识别图书ISBN实现藏书管理

每个程序猿家里都有一堆技术书籍,偶也不例外,因此想写个Android应用来管理自己的藏书以及想买的书籍。在网上找到marshal的《识别图书ISBN号并输出查询结果的示例》《完善图书查询原型,增加收藏夹功能》两篇文章,写的非常不错,还提供源代码。下载代码研究后发现已基本具备了想要的功能,决定在它的基础上做些修改供自己使用。

把原来uses-sdk的minSdkVersion改成了9,增加android:targetSdkVersion=”17″。然后使用Nexus测试程序时发现,在连接网络时后台会抛出了android.os.NetworkOnMainThreadException异常,并且应用崩溃打不开。通过查阅相关资料了解到,自从Android 2.3之后,系统增加了一个类StrictMode。这个类对网络的访问方式进行了一定的改变。官方文档给出了这个类设置的目的:

StrictMode is a developer tool which detects things you might be doing by accident and brings them to your attention so you can fix them.

StrictMode is most commonly used to catch accidental disk or network access on the application’s main thread, where UI operations are received and animations take place. Keeping disk and network operations off the main thread makes for much smoother, more responsive applications. By keeping your application’s main thread responsive, you also prevent ANR dialogs from being shown to users.

Note that even though an Android device’s disk is often on flash memory, many devices run a filesystem on top of that memory with very limited concurrency. It’s often the case that almost all disk accesses are fast, but may in individual cases be dramatically slower when certain I/O is happening in the background from other processes. If possible, it’s best to assume that such things are not fast.

因为marshal把访问网络的代码直接放到UI线程中,造成和主线程的首要工作——UI交互——相矛盾。解决这类问题很容易,把访问网络的代码放到AsyncTask中就行了。官方有个NetworkUsage例子是个不错的参考。

接着发现豆瓣API查询返回的是500错误,在浏览器上访问却又正常,后来给HttpClient加上Agent头就没问题了,不知道是不是期间豆瓣的API在实现上作了改变。

HttpClient client = new DefaultHttpClient();
String agent = System.getProperty("http.agent");
client.getParams().setParameter(CoreProtocolPNames.USER_AGENT, agent);

解析豆瓣XML查询结果的代码:

XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
factory.setNamespaceAware(true);
XmlPullParser parser = factory.newPullParser();
parser.setInput(inputStream, "UTF-8");
Book book = new Book();
int eventType = parser.getEventType();
while (eventType != XmlPullParser.END_DOCUMENT) {
    switch (eventType) {
    case XmlPullParser.START_TAG:
        if ("link".equals(parser.getName())
                && "image".equals(parser.getAttributeValue(null, "rel"))) {
            book.setImageUrl(parser.getAttributeValue(null, "href"));
            eventType = parser.next();
        } else if ("summary".equals(parser.getName())) {
            book.setSummary(parser.nextText());
        } else if ("attribute".equals(parser.getName())) {
            String name = parser.getAttributeValue(null, "name");
            if ("title".equals(name)) {
                book.setTitle(parser.nextText());
            } else if ("author".equals(name)) {
                book.setAuthor(parser.nextText());
            } else if ("isbn10".equals(name)) {
                book.setIsbn10(parser.nextText());
            } else if ("isbn13".equals(name)) {
                book.setIsbn13(parser.nextText());
            } else if ("publisher".equals(name)) {
                book.setPublisher(parser.getText());
            }
        }
        break;
    case XmlPullParser.END_TAG:
        break;
    }
    eventType = parser.next();
}

然后,然后就是结果页面不显示图书信息。想要找到原因,肿么办?看来要调试WebView了!http://developer.android.com/guide/webapps/debugging.html告诉了我们如何调试。
在WebView上设置setWebChromeClient方法:

webView.setWebChromeClient(new WebChromeClient() {
    public void onConsoleMessage(String message,
            int lineNumber, String sourceID) {
        Log.d(TAG, message + " -- From line "
                + lineNumber + " of " + sourceID);
    }
});

然后在JavaScript脚本中使用以下方法就可以在logcat中看到调试信息了。

console.log(String)
console.info(String)
console.warn(String)
console.error(String)

重新运行程序,果然在logcat中看到报如下错误:

Uncaught TypeError: Object [object Object] has no method...

搜索后在Stack Overflow找到了问题的答案(Stack Overflow真的非常不错,问题的回答都非常详尽,怎么国内就没有这样的社区呢?)。这里是Android官方文档的解释。

解决方法就是在要被JavaScript调用的方法上加@JavascriptInterface注解:

public class Book {
    ...
	@JavascriptInterface
	public String getAuthor() {
		return author;
	}
	public void setAuthor(String author) {
		if (this.author != null) {
			this.author += ", " + author;
		} else {
			this.author = author;
		}
	}
    ...
}

绘制圆角的正确方法

英文原文:http://www.usabilitypost.com/2009/01/26/the-proper-way-to-draw-rounded-corners/

在网络上,我已经注意到当人们在他们的设计中实现圆角时犯了很多同样的错误。因为某些原因,当有另外一个圆角在它里面的时候,这个圆角会带来一个问题—无论是有一个围绕周围的边界,还是另一个圆角形状坐落在一个圆角形状内。

这里是我要说的:
bad_corner

我看到了很多这种类型的圆角。您可以看到圆角的半径和内圆角的是相同的。这是错误的,因为如果你保持相同半径,两个拐角之间的空间量不会自始至终相等。

下面是相等间距看起来的样子:
good_corner

这是正确的做法。有什么不同?内角的半径被减小了。好吧,但你怎么知道半径应该有多大?这很简单—如果你把外面的圆角想象成一个圆形,你可以看到它的圆心在哪里。
corner_center

把这个圆心同样地作为内角的圆心,你就会得到内角的半径。使用这种方法将确保两个形状之间的完美契合。
good_corner_ruler

“现在你知道了吧?这是做多个圆角彼此包含的正确办法。当然,不需要你使用精确的圆心—有时为了设计良好还需要你移动一点点。但请不要仅仅把相同的圆角向内移动—这是绝对错误的。

使用SiteMesh做网页布局

SiteMesh是一个基于GoF的Decorator模式的,用于页面布局的框架。能帮助我们在由大量页面构成的项目中创建一致的页面布局和外观。

这里我们将会把它整合到JBookShelf里去。要和Struts2整合,先在pom.xml添加以下插件,该插件会将依赖的SiteMesh也一并包含到项目中。

<dependency>
    <groupId>org.apache.struts</groupId>
    <artifactId>struts2-sitemesh-plugin</artifactId>
    <version>2.3.12</version>
</dependency>

将web.xml配置中原来的

<filter>
    <filter-name>struts2</filter-name>
    <filter-class>
      org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>struts2</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

改成

<filter>
    <filter-name>struts-prepare</filter-name>
    <filter-class>
      org.apache.struts2.dispatcher.ng.filter.StrutsPrepareFilter
    </filter-class>
</filter>
<filter>
    <filter-name>sitemesh</filter-name>
    <filter-class>
      com.opensymphony.sitemesh.webapp.SiteMeshFilter
    </filter-class>
</filter>
<filter>
    <filter-name>struts-execute</filter-name>
    <filter-class>
      org.apache.struts2.dispatcher.ng.filter.StrutsExecuteFilter
    </filter-class>
</filter>
<filter-mapping>
    <filter-name>struts-prepare</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>sitemesh</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
<filter-mapping>
    <filter-name>struts-execute</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

这里要注意过滤器的位置:SiteMesh过滤器必须在StrutsPrepareFilter之后和StrutsExecuteFilter之前,否则在SiteMesh的修饰器页面中将访问不到ActionContext。这是因为Struts2的所有值是保存在Stack Context或者ValueStack中的,默认情况下,某个过滤器一旦访问了该Stack Context或ValueStack,里面对应的值会被清洗掉。如果先使用Struts2的StrutsPrepareAndExecuteFilter来过滤用户请求,则SiteMesh的过滤器将无法取得Stack Context或者ValueStack中的数据。为了解决这个问题,Struts2提供了StrutsPrepareFilter和StrutsExecuteFilter类(在2.1.3版本前是ActionContextCleanUp和FilterDispatcher)。通过它们协同来确保SiteMesh正常工作。

在WEB-INF下添加decorators.xml文档:

<?xml version="1.0" encoding="UTF-8"?>
<decorators defaultdir="/layouts">
    <excludes>
        <pattern>/stylesheets/*</pattern>
        <pattern>/javascripts/*</pattern>
        <pattern>/images/*</pattern>
    </excludes>
    <decorator name="application" page="application.jsp">
        <pattern>/*</pattern>
    </decorator>
</decorators>

stylesheets、javascripts、images目录下的内容是不需要被修饰的,可以把它们放到中排除掉。

新建/layouts/application.jsp模版页:

<%@ taglib uri="/struts-tags" prefix="s" %>
<%@ taglib uri="http://www.opensymphony.com/sitemesh/decorator" prefix="decorator" %>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><decorator:title default="JBookShelf" /></title>
<decorator:head />
</head>
<body>
    <div>
        <s:if test="#session.user_session_key != null">
        <s:a action="listBook">All Books</s:a>
        Welcome, you have logined.
        <s:a action="logout">Logout</s:a>
        </s:if>
        <s:else>
        <s:a action="login!input">Login</s:a> |
        <s:a action="register!input">Register</s:a>
        </s:else>
    </div>
    <hr />
    <div>Navigation</div>
    <hr />
    <decorator:body />
    <hr />
    <div>Footer</div>
</body>
</html>

代码下载:https://github.com/dohkoos/JBookShelf

PowerBuilder生成可执行文件时的小技巧

使用资源文件(.pbr)引用资源
在资源相同目录下建立.pbr文件,其中列出在应用中动态引用的资源文件,一行列出一个资源,格式如:
appico.ico
appbmp1.bmp

appbmp9.bmp

在.pbr文件中指定的文件名必须与在脚本中引用的资源匹配,若引用时包含路径,则在.pbr文件中也必须包含同一路径,否则因PowerBuilder在执行时只是简单地进行字符串比较而导致无法发现该资源。

设置可执行文件图标
应用 -> 属性 -> General -> Additional Properties按钮 -> Icon页签 -> Icon Name

可执行文件所需的环境DLL
最简便的方式是使用自带的打包程序PowerBuilder Runtime Packager。这个应用会自动把脱离环境运行所需的DLL等文件筛选出来。
Sybase -> PowerBuilder 9.0 -> PowerBuilder Runtime Packager

运行后会生成一个MSI应用,执行下就可以解压出需要的DLL文件了。

使用ichartjs画2D条形图

以前的文章中使用了OFC2来画2D条形图。现在Flash快要不行了,因为有了更好的HTML5。好的程序员也要紧随潮流,使用新的技术来改进和增强他的代码。这里就尝试使用HTML5图形库来替换OFC2。比较已有的一些图形库,最后选定国产的ichartjs

实现2D条形图真的很简单:

<script type="text/javascript" src="ichart.1.1.min.js"></script>
<script type="text/javascript">
$(function() {
  var data = [
    {name: 'IE', value: 35.75, color: '#a5c2d5'},
    {name: 'Chrome', value: 29.84, color: '#cbab4f'},
    {name: 'Firefox', value: 24.88, color: '#76a871'},
    {name: 'Safari',value: 6.77, color: '#9f7961'},
    {name: 'Opera',value:2.02, color: '#a56f8f'},
    {name: 'Other',value: 0.73, color: '#6f83a5'}
  ];
  new iChart.Bar2D({
    render: 'canvasDiv',
    data: data,
    title: 'Top 5 Browsers from 1 to 29 Feb 2012',
    showpercent: true,
    decimalsnum: 2,
    width: 800,
    height: 400,
    coordinate: {
      scale: [{
        position: 'bottom',
        start_scale: 0,
        end_scale: 40,
        scale_space: 8,
        listeners: {
          parseText: function(t, x, y) {
            return {text: t + "%"}
          }
        }
      }]
    }
  }).draw();
});
</script>
<div id="canvasDiv"></div>

逻辑题-老师的生日

小明和小强都是某老师的学生,老师的生日是M月N日,他们都知道老师的生日是以下十组中的一个。老师把M告诉了小明,把N告诉了小强,然后问他们是否知道自己的生日。小明说“如果我不知道小强也不知道”,小强说“本来我不知道,你说了这话我就知道了”,小明说“那我也知道了 ”。问老师的生日是以下的哪个?
3/4, 3/5, 3/8, 6/4, 6/7, 9/1, 9/5, 12/1, 12/2, 12/8

按M排:
3/4, 3/5, 3/8
6/4, 6/7
9/1, 9/5
12/1, 12/2, 12/8

按N排:
9/1, 12/1
12/2
3/4, 6/4
3/5, 9/5
6/7
3/8, 12/8

条件一:小明说“如果我不知道小强也不知道”
分析:因为M都是重复的,所以小明一定不知道。小明知道M以后肯定小强不知道,说明N肯定也是重复,可以剔除12/2和6/7这两个日期。另外,还可以排队N为2与7所对应的月份,因为当老师生日的M为6或12时,小强是有可能知道的,与已知条件相违背。
结果:3/4, 3/5, 3/8, 9/1, 9/5

按M排:
3/4, 3/5, 3/8
9/1, 9/5

按N排:
3/4
3/5, 9/5
3/8
9/1

条件二:小强说“本来我不知道,你说了这话我就知道了”
分析:根据上面的结果和条件二可以知道,现在N一定不能是重复的,可以把3/5和9/5排除。
结果:3/4, 3/8, 9/1

按M排:
3/4, 3/8
9/1

按N排:
3/4
3/8
9/1

条件三:小明说“那我也知道了”
分析:综合条件二的结果和条件三可以判定现在M应该是单一的,所以只能是9。
结果:9/1

老师的生日为9/1。

htaccess实例详解

又一次迁移博客服务器。因为是迁移,就没有采取安装新的Wordpress,而是把以前的程序拷贝到服务器上。

打开首页的时候还好,访问具体文章时就出现问题了:

Not Found
The requested URL /sample-article.html was not found on this server.

百思不得其解。后来试着去后台重新做设置,然后就发现在根目录下多了.htaccess文件,内容如下:

# BEGIN WordPress
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.php$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]
</IfModule>
# END WordPress

以前也看过这个文件,总是模模糊糊不太明白。今天研究的时候发现一下子就清清楚楚了(这大概就是说的顿悟吧:-)不过它的发生应该还是建立在渐修的基础上)。

下面来说说这些内容的理解:
第1行就是打开重写引擎
第2行是设置URL前缀

Syntax: RewriteRule Pattern Substitution [flags]

- (dash)
A dash indicates that no substitution should be performed (the existing path is passed through untouched). This is used when a flag (see below) needs to be applied without changing the path.

last|L
Stop the rewriting process immediately and don’t apply any more rules. Especially note caveats for per-directory and .htaccess context.

第3行是完全匹配index.php的URI,但不做替换,并且匹配成功后就停止执行重写过程。
第6行就是把所有的访问请求重写,指给index.php,然后停止执行重写过程。

Syntax: RewriteCond TestString CondPattern

REQUEST_FILENAME
The full local filesystem path to the file or script matching the request, if this has already been determined by the server at the time REQUEST_FILENAME is referenced. Otherwise, such as when used in virtual host context, the same value as REQUEST_URI.

You can prefix the pattern string with a ‘!’ character (exclamation mark) to specify a non-matching pattern.

‘-d’ (is directory)
Treats the TestString as a pathname and tests whether or not it exists, and is a directory.
‘-f’ (is regular file)
Treats the TestString as a pathname and tests whether or not it exists, and is a regular file.

第4和第5行是不去测试访问URI是否是目录或文件。其实有了第6行这两行基本就是摆设了。

最后再多嘴一句,有问题直接查官方文档应该是最好的选择。官方文档链接:Apache Module mod_rewrite