几个还算有用的Apache htaccess配置

开启Apache的.htaccess文件可以让站点进行1些个性化的应用配置。这里提供了几个不错的.htaccess片段,希望能够根据你的实际情况优化你的网站,包括重定向、性能、可用性等!

  1. 强制后缀反斜杠(在URL的尾部加上反斜杠似乎对SEO有利)
    <IfModule mod_rewrite.c>
    RewriteCond %{REQUEST_URI} /+[^\.]+$
    RewriteRule ^(.+[^/])$ %{REQUEST_URI}/ [R=301,L]
    </IfModule>
  2. 防盗链
    RewriteEngine On
    #Replace ?mysite\.com/ with your url
    RewriteCond %{HTTP_REFERER} !^http://(.+\.)?mysite\.com/ [NC]
    RewriteCond %{HTTP_REFERER} !^$
    #Replace /images/nohotlink.jpg with your "don't hotlink" image url
    RewriteRule .*\.(jpe?g|gif|bmp|png)$ /images/nohotlink.jpg [L]
  3. 重定向移动设备(如果你的网站支持移动设备访问的话,最好还是重定向移动设备的访问到专门定制的页面)
    RewriteEngine On
    RewriteCond %{REQUEST_URI} !^/m/.*$
    RewriteCond %{HTTP_ACCEPT} "text/vnd.wap.wml|application/vnd.wap.xhtml+xml" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "acs|alav|alca|amoi|audi|aste|avan|benq|bird|blac|blaz|brew|cell|cldc|cmd-" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "dang|doco|eric|hipt|inno|ipaq|java|jigs|kddi|keji|leno|lg-c|lg-d|lg-g|lge-" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT}  "maui|maxo|midp|mits|mmef|mobi|mot-|moto|mwbp|nec-|newt|noki|opwv" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "palm|pana|pant|pdxg|phil|play|pluc|port|prox|qtek|qwap|sage|sams|sany" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "sch-|sec-|send|seri|sgh-|shar|sie-|siem|smal|smar|sony|sph-|symb|t-mo" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "teli|tim-|tosh|tsm-|upg1|upsi|vk-v|voda|w3cs|wap-|wapa|wapi" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "wapp|wapr|webc|winw|winw|xda|xda-" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "up.browser|up.link|windowssce|iemobile|mini|mmp" [NC,OR]
    RewriteCond %{HTTP_USER_AGENT} "symbian|midp|wap|phone|pocket|mobile|pda|psp" [NC]
    #------------- The line below excludes the iPad
    RewriteCond %{HTTP_USER_AGENT} !^.*iPad.*$
    #-------------
    RewriteCond %{HTTP_USER_AGENT} !macintosh [NC] #*SEE NOTE BELOW
    RewriteRule ^(.*)$ /m/ [L,R=302]
  4. 强制浏览器下载指定的文件类型(你可以强制浏览器下载某些类型的文件,而不是读取并打开这些文件,例如txt)
    <Files *.txt>
    ForceType application/octet-stream
    Header set Content-Disposition attachment
    </Files>
  5. 火狐的跨域名字体嵌入(火狐不允许嵌入一个外站的字体,下面的.htaccess片段可以绕过这个限制)
    <FilesMatch "\.(ttf|otf|eot|woff)$">
    <IfModule mod_headers.c>
    Header set Access-Control-Allow-Origin "http://yourdomain.com"
    </IfModule>
    </FilesMatch>
  6. 使用.htaccess缓存(给网站提速!恐怕这个是最有用的代码片段了。这段代码能帮你极大的提高网站的速度)
    # 1 YEAR
    <FilesMatch "\.(ico|pdf|flv)$">
    Header set Cache-Control "max-age=29030400, public"
    </FilesMatch>
    # 1 WEEK
    <FilesMatch "\.(jpg|jpeg|png|gif|swf)$">
    Header set Cache-Control "max-age=604800, public"
    </FilesMatch>
    # 2 DAYS
    <FilesMatch "\.(xml|txt|css|js)$">
    Header set Cache-Control "max-age=172800, proxy-revalidate"
    </FilesMatch>
    # 1 MIN
    <FilesMatch "\.(html|htm|php)$">
    Header set Cache-Control "max-age=60, private, proxy-revalidate"
    </FilesMatch>
  7. 重定向不同的feed格式到统一的格式(很多年前,有很多不同的feed格式,例如RSS、Atom、RDF等等。但是现在RSS已经占了绝对的主导地位。下面这段代码可以让你重定向不同的feed格式到同一个feed)
    <IfModule mod_alias.c>
    RedirectMatch 301 /feed/(atom|rdf|rss|rss2)/?$ http://example.com/feed/
    RedirectMatch 301 /comments/feed/(atom|rdf|rss|rss2)/?$ http://example.com/comments/feed/
    </IfModule>
  8. 配置网站的HTML5视频(HTML5为我们带来了不用Flash的视频播放功能,但是你必须配置你的服务器来提供最新的HTML5视频播放功能)
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} !=/favicon.ico
    AddType video/ogg .ogv
    AddType video/ogg .ogg
    AddType video/mp4 .mp4
    AddType video/webm .webm
    AddType application/x-shockwave-flash swf
  9. 记录PHP错误(在页面上显示PHP错误是很尴尬的事情,也不安全,下面这段代码可以把PHP错误记录到.log文件中而不在页面显示)
    # display no errs to user
    php_flag display_startup_errors off
    php_flag display_errors off
    php_flag html_errors off
    # log to file
    php_flag log_errors on
    php_value error_log /location/to/php_error.log

在BS架构中控制浏览器直接打印

是的,今天的文章标题是《在BS架构中控制浏览器直接打印》,记得之前我说过最近在做1个叫《智慧街区》的项目,里面需要攻克的第2个难点就是,客户端浏览器直接操作打印机的问题。
在BS架构中,有的时候我们需要控制浏览器直接进行打印,而不是弹出打印对话框,使用window.print()在此时就无法满足我们的需求。
我Google了1下,有多种方法,在这之前我就思考过这样1种方法:我们知道xhtml里是可以创建object对象的,比如几年前我们通过object调用Realplay、Windows Media Player播放器,这种object对象,会有2个参数叫codebase和classid,当客户端没有相关activeX插件的时候会通过codebase指定的地址进行下载,而classid是该插件在客户端注册表中的唯一对象标识。下面的代码同样可以操作打印机,这是在网上抄的,仅支持IE哦,因为IE支持ActiveX。

<OBJECT  id=WebBrowser  classid=CLSID:8856F961-340A-11D0-A96B-00C04FD705A2  height=0  width=0>
</OBJECT>
<input  type=button  value="打印" onclick="document.all.WebBrowser.ExecWB(6,1)">
<input  type=button  value="直接打印"  onclick="document.all.WebBrowser.ExecWB(6,6)">
<input  type=button  value="页面设置" onclick="document.all.WebBrowser.ExecWB(8,1)">

我在实际使用中发现上面的第2个按钮“直接打印”并不起作用,依然弹出对话框。
不过我知道我的思路是正确的,肯定是需要在客户端上安装ActiveX插件,于是google出这样1个插件ScriptX,欣喜。
ScriptX官方网站:http://scriptx.meadroid.com/home.aspx
下载Resource Kit后进行解压安装,在xhtml代码中使用该对象进行操作,便支持直接打印,ScriptX的详细参数见:http://scriptx.meadroid.com/knowledge-bank/technical-reference.aspx
我这里做1些简单的示例:

<object id="factory" codebase="smsx.cab" classid="clsid:1663ed61-23eb-11d2-b92f-008048fdd814">
</object>
<input  type="button"   value="直接打印"   onclick="factory.printing.print(false)">
<input type="button" value="预览" onclick="factory.printing.preview()" />

OK,本次文章到此结束,其实我不太善于写文章的。

简要的Web缓存技术

最近在做1个项目,名叫《智慧街区》。简单进行1下项目介绍:与政府有关部门进行合作,将自助终端(其实就是1台PC机,只不过长的好看点)竖立在大街小巷,为市民提供各色服务,比如:公交、天气、商家、优惠、政策、新闻等等1些便民功能。
这样1个项目,按道理来说,是应该做成CS结构的,这样便于对终端设备上有关硬软件的控制,但无耐的是项目时间较短、用户体验要好,我们只能选择偷梁换柱,使用BS方式(C端编写1全屏置顶的小程序,嵌套web网站)来解决。
调研阶段,我们了解到该项目功能并不复杂,手上也有比较好的原型和模块代码,我们所需要解决的问题主要有2个:1是客户端缓存,因为终端是使用3G无线传输,速度和流量得不到保障,速度<200KB,按流量收费,我们只能在服务端有更新内容的时候,客户端才能更新有关数据,最大限度的节约带宽;2是控制客户端打印,因为使用JS控制打印机会弹出打印对话框的,而终端设备上我们是不允许用户进行这种操作的。
今天先说第1个问题,客户端缓存的问题,这方面我特别g了1下,也算了解到1些比较简洁比较有效的技术,当然以前也都或多或少的接触过。下面这篇文章不错,遂载,明天还有1篇文章会描述《Web缓存技术概述》。

这是一篇知识性的文档,主要目的是为了让Web缓存相关概念更容易被开发者理解并应用于实际的应用环境中。为了简要起见,某些实现方面的细节被简化或省略了。如果你更关心细节实现则完全不必耐心看完本文,后面参考文档和更多深入阅读部分可能是你更需要的内容。

目录:

  1. 什么是Web缓存,为什么要使用它?
  2. 缓存的类型:
    1. 浏览器缓存;
    2. 代理服务器缓存;
  3. Web缓存无害吗?为什么要鼓励缓存?
  4. Web缓存如何工作:
  5. 如何控制(控制不)缓存:
    1. HTML Meta标签 vs. HTTP头信息;
    2. Pragma HTTP头信息(为什么不起作用);
    3. 使用Expires(过期时间)HTTP头信息控制保鲜期;
    4. Cache-Control(缓存控制) HTTP头信息;
    5. 校验参数和校验;
  6. 创建利于缓存网站的窍门;
  7. 编写利于缓存的脚本;
  8. 常见问题解答;
  9. 缓存机制的实现:Web服务器端配置;
  10. 缓存机制的实现:服务器端脚本;

1.什么是Web缓存,为什么要使用它?

Web缓存位于Web服务器之间(1个或多个,内容源服务器)和客户端之间(1个或多个):缓存会根据进来的请求保存输出内容的副本,例如html 页面, 图片,文件(统称为副本),然后,当下一个请求来到的时候:如果是相同的URL,缓存直接使用副本响应访问请求,而不是向源服务器再次发送请求。

使用缓存主要有2大理由:

  • 减少相应延迟:因为请求从缓存服务器(离客户端更近)而不是源服务器被相应,这个过程耗时更少,让web服务器看上去相应更快;
  • 减少网络带宽消耗:当副本被重用时会减低客户端的带宽消耗;客户可以节省带宽费用,控制带宽的需求的增长并更易于管理。

2.缓存的类型

浏览器缓存

对于新一代的Web浏览器来说(例如:IE,Firefox):一般都能在设置对话框中发现关于缓存的设置,通过在你的电脑上僻处一块硬盘空间用于存储你已经看过的网站的副本。浏览器缓存根据非常简单的规则进行工作:在同一个会话过程中(在当前浏览器没有被关闭之前)会检查一次并确定缓存的副本足够新。这个 缓存对于用户点击“后退”或者点击刚访问过的链接特别有用,如果你浏览过程中访问到同一个图片,这些图片可以从浏览器缓存中调出而即时显现。

代理服务器缓存

Web代理服务器使用同样的缓存原理,只是规模更大。代理服务器群为成百上千用户服务使用同样的机制;大公司和ISP经常在他们的防火墙上架设代理缓存或者单独的缓存设备;

由 于带路服务器缓存并非客户端或者源服务器的一部分,而是位于原网络之外,请求必须路由到他们才能起作用。一个方法是手工设置你的浏览器:告诉浏览器使用 那个代理,另外一个是通过中间服务器:这个中间服务器处理所有的web请求,并将请求转发到后台网络,而用户不必配置代理,甚至不必知道代理的存在;

代理服务器缓存:是一个共享缓存,不只为一个用户服务,经常为大量用户使用,因此在减少相应时间和带宽使用方面很有效:因为同一个副本会被重用多次。

网关缓存

也被称为反向代理缓存或间接代理缓存,网关缓存也是一个中间服务器,和内网管理员部署缓存用于节省带宽不同:网关缓存一般是网站管理员自己部署:让他们的网站更容易扩展并获得更好的性能;
请求有几种方法被路由到网关缓存服务器上:其中典型的是让用一台或多台负载均衡服务器从客户端看上去是源服务器;

网络内容发布商  (Content delivery networks CDNs)分布网关缓存到整个(或部分)互联网上,并出售缓存服务给需要的网站,SpeederaAkamai就是典型的网络内容发布商(下文简称CDN)。

本问主要关注于浏览器和代理缓存,当然,有些信息对于网关缓存也同样有效;

3.Web缓存无害吗?为什么要鼓励缓存?

Web缓存在互联网上最容易被误解的技术之一:网站管理员经常怕对网站失去控制,由于代理缓存会“隐藏”他们的用户,让他们感觉难以监控谁在使用他们的网站。
不幸的是:就算不考虑Web缓存,互联网上也有很多网站使用非常多的参数以便管理员精确地跟踪用户如何使用他们的网站;如果这类问题也是你关心的,本文将告诉你如何获得精确的统计而不必将网站设计的非常缓存不友好。
另外一个抱怨是缓存会给用户过期或失效的数据;无论如何:本文可以告诉你怎样配置你的服务器来控制你的内容将被如何缓存。

CDN是另外一个有趣的方向,和其他代理缓存不同:CDN的网关缓存为希望被缓存的网站服务,没有以上顾虑。即使你使用了CDN,你也要考虑后续的代理服务器缓存和浏览器缓存问题。

另外一方面:如果良好地规划了你的网站,缓存会有助于网站服务更快,并节省服务器负载和互联网的链接请求。这个改善是显著的:一个难以缓存的网站可能需要几秒去载入页面,而对比有缓存的网站页面几乎是即时显现:用户更喜欢速度快的网站并更经常的访问;

这样想:很多大型互联网公司为全世界服务器群投入上百万资金,为的就是让用户访问尽可能快,客户端缓存也是这个目的,只不过更靠近用户一端,而且最好的一点是你甚至根本不用为此付费。

事实上,无论你是否喜欢,代理服务器和浏览器都回启用缓存。如果你没有配置网站正确的缓存,他们会按照缺省或者缓存管理员的策略进行缓存。

4.缓存如何工作

所有的缓存都用一套规则来帮助他们决定什么时候使用缓存中的副本提供服务(假设有副本可用的情况下);一些规则在协议中有定义(HTTP协议1.0和1.1),一些规则由缓存的管理员设置(浏览器的用户或者代理服务器的管理员);
一般说来:遵循以下基本的规则(不必担心,你不必知道所有的细节,细节将随后说明)

  1. 如果响应头信息:告诉缓存器不要保留缓存,缓存器就不会缓存相应内容;
  2. 如果请求信息是需要认证或者安全加密的,相应内容也不会被缓存;
  3. 如果在回应中不存在校验器(ETag或者Last-Modified头信息),缓存服务器会认为缺乏直接的更新度信息,内容将会被认为不可缓存。
  4. 一个缓存的副本如果含有以下信息:内容将会被认为是足够新的
    • 含有完整的过期时间和寿命控制头信息,并且内容仍在保鲜期内;
    • 浏览器已经使用过缓存副本,并且在一个会话中已经检查过内容的新鲜度;
    • 缓存代理服务器近期内已经使用过缓存副本,并且内容的最后更新时间在上次使用期之前;
    • 够新的副本将直接从缓存中送出,而不会向源服务器发送请求;
  5. 如果缓存的副本已经太旧了,缓存服务器将向源服务器发出请求校验请求,用于确定是否可以继续使用当前拷贝继续服务;

总之:新鲜度校验是确定内容是否可用的最重要途径:

如果副本足够新,从缓存中提取就立刻能用了;
而经缓存器校验后发现副本的原件没有变化,系统也会避免将副本内容从源服务器整个重新传输一遍。

5.如何控制(控制不)缓存

有很多工具可以帮助设计师和网站管理员调整缓存服务器对待网站的方式,这也许需要你亲自下手对服务器的配置进行一些调整,但绝对值得;了解如何使用这些工具请参考后面的实现章节;

HTML meta标签和HTTP 头信息

HTML的编写者会在文档的<HEAD>区域中加入描述文档的各种属性,这些META标签常常被用于标记文档不可以被缓存或者标记多长时间后过期;
META标签使用很简单:但是效率并不高,因为只有几种浏览器会遵循这个标记(那些真正会“读懂”HTML的浏览器),没有一种缓存代理服务器能遵循这个规则(因为它们几乎完全不解析文档中HTML内容);有时会在Web页面中增加:Pragma: no-cache这个META标记,如果要让页面保持刷新,这个标签其实完全没有必要。
如果你的网站托管在ISP机房中,并且机房可能不给你权限去控制HTTP的头信息(如:Expires和Cache-Control),大声控诉:这些机制对于你的工作来说是必须的;
另外一方面: HTTP头信息可以让你对浏览器和代理服务器如何处理你的副本进行更多的控制。他们在HTML代码中是看不见的,一般由Web服务器自动生成。但是,根据你使用的服务,你可以在某种程度上进行控制。在下文中:你将看到一些有趣的HTTP头信息,和如何在你的站点上应用部署这些特性。

HTTP头信息发送在HTML代码之前,只有被浏览器和一些中间缓存能看到,一个典型的HTTP 1.1协议返回的头信息看上去像这样:

HTTP/1.1 200 OK
Date: Fri, 30 Oct 1998 13:19:41 GMT
Server: Apache/1.3.3 (Unix)
Cache-Control: max-age=3600, must-revalidate
Expires: Fri, 30 Oct 1998 14:19:41 GMT
Last-Modified: Mon, 29 Jun 1998 02:28:12 GMT
ETag: “3e86-410-3596fbbc”
Content-Length: 1040
Content-Type: text/html

在头信息空一行后是HTML代码的输出,关于如何设置HTTP头信息请参考实现章节;

Pragma HTTP头信息 (为什么它不起作用)

很多人认为在HTTP头信息中设置了Pragma: no-cache后会让内容无法被缓存。但事实并非如此:HTTP的规范中,响应型头信息没有任何关于Pragma属性的说明,而讨论了的是请求型头信息 Pragma属性(头信息也由浏览器发送给服务器),虽然少数集中缓存服务器会遵循这个头信息,但大部分不会。用了Pragma也不起什么作用,要用就使用下列头信息:

使用Expires(过期时间)HTTP头信息来控制保鲜期

Expires(过期时间)属性是HTTP控制缓存的基本手段,这个属性告诉缓存器:相关副本在多长时间内是新鲜的。过了这个时间,缓存器就会向源服务器发送请求,检查文档是否被修改。几乎所有的缓存服务器都支持Expires(过期时间)属性;

大部分Web服务器支持你用几种方式设置Expires属性;一般的:可以设计一个绝对时间间隔:基于客户最后查看副本的时间(最后访问时间)或者根据服务器上文档最后被修改的时间;

Expires 头信息:对于设置静态图片文件(例如导航栏和图片按钮)可缓存特别有用;因为这些图片修改很少,你可以给它们设置一个特别长的过期时间,这会使你的网站对用户变得相应非常快;他们对于控制有规律改变的网页也很有用,例如:你每天早上6点更新新闻页,你可以设置副本的过期时间也是这个时间,这样缓存服务器就知道什么时候去取一个更新版本,而不必让用户去按浏览器的“刷新”按钮。

过期时间头信息属性值只能是HTTP格式的日期时间,其他的都会被解析成当前时间“之前”,副本会过期,记住:HTTP的日期时间必须是格林威治时间(GMT),而不是本地时间。举例:

Expires: Fri, 30 Oct 1998 14:19:41 GMT

所以使用过期时间属性一定要确认你的Web服务器时间设置正确,一个途径是通过网络时间同步协议(Network Time Protocol NTP),和你的系统管理员那里你可以了解更多细节。
虽然过期时间属性非常有用,但是它还是有些局限,首先:是牵扯到了日期,这样Web服务器的时间和缓存服务器的时间必须是同步的,如果有些不同步,要么是应该缓存的内容提前过期了,要么是过期结果没及时更新。
还有一个过期时间设置的问题也不容忽视:如果你设置的过期时间是一个固定的时间,如果你返回内容的时候又没有连带更新下次过期的时间,那么之后所有访问请求都会被发送给源Web服务器,反而增加了负载和响应时间;

Cache-Control(缓存控制) HTTP头信息

HTTP 1.1介绍了另外一组头信息属性:Cache-Control响应头信息,让网站的发布者可以更全面的控制他们的内容,并定位过期时间的限制。
有用的 Cache-Control响应头信息包括:

  • max-age=[秒] — 执行缓存被认为是最新的最长时间。类似于过期时间,这个参数是基于请求时间的相对时间间隔,而不是绝对过期时间,[秒]是一个数字,单位是秒:从请求时间开始到过期时间之间的秒数。
  • s-maxage=[秒] — 类似于max-age属性,除了他应用于共享(如:代理服务器)缓存
  • public — 标记认证内容也可以被缓存,一般来说: 经过HTTP认证才能访问的内容,输出是自动不可以缓存的;
  • no-cache — 强制每次请求直接发送给源服务器,而不经过本地缓存版本的校验。这对于需要确认认证应用很有用(可以和public结合使用),或者严格要求使用最新数据的应用(不惜牺牲使用缓存的所有好处);
  • no-store — 强制缓存在任何情况下都不要保留任何副本
  • must-revalidate — 告诉缓存必须遵循所有你给予副本的新鲜度的,HTTP允许缓存在某些特定情况下返回过期数据,指定了这个属性,你高速缓存,你希望严格的遵循你的规则。
  • proxy-revalidate — 和 must-revalidate类似,除了他只对缓存代理服务器起作用

举例:

Cache-Control: max-age=3600, must-revalidate

如果你计划试用Cache-Control属性,你应该看一下这篇HTTP文档,详见参考和深入阅读;

校验参数和校验

在Web缓存如何工作: 我们说过:校验是当副本已经修改后,服务器和缓存之间的通讯机制;使用这个机制:缓存服务器可以避免副本实际上仍然足够新的情况下重复下载整个原件。
校验参数非常重要,如果1个不存在,并且没有任何信息说明保鲜期(Expires或Cache-Control)的情况下,缓存将不会存储任何副本;
最常见的校验参数是文档的最后修改时间,通过最后Last-Modified头信息可以,当一份缓存包含Last-Modified信息,他基于此信息,通过添加一个If-Modified-Since请求参数,向服务器查询:这个副本从上次查看后是否被修改了。
HTTP 1.1介绍了另外一个校验参数: ETag,服务器是服务器生成的唯一标识符ETag,每次副本的标签都会变化。由于服务器控制了ETag如何生成,缓存服务器可以通过If-None-Match请求的返回没变则当前副本和原件完全一致。
所有的缓存服务器都使用Last-Modified时间来确定副本是否够新,而ETag校验正变得越来越流行;
所有新一代的Web服务器都对静态内容(如:文件)自动生成ETag和Last-Modified头信息,而你不必做任何设置。但是,服务器对于动态内容(例如:CGI,ASP或数据库生成的网站)并不知道如何生成这些信息,参考一下编写利于缓存的脚本章节;

6.创建利于缓存网站的窍门

除了使用新鲜度信息和校验,你还有很多方法使你的网站缓存友好。

  • 保持URL稳定: 这是缓存的金科玉律,如果你给在不同的页面上,给不同用户或者从不同的站点上提供相同的内容,应该使用相同的URL,这是使你的网站缓存友好最简单,也是 最高效的方法。例如:如果你在页面上使用 “/index.html” 做为引用,那么就一直用这个地址;
  • 使用一个共用的库存放每页都引用的图片和其他页面元素;
  • 对于不经常改变的图片/页面启用缓存,并使用Cache-Control: max-age属性设置一个较长的过期时间;
  • 对于定期更新的内容设置一个缓存服务器可识别的max-age属性或过期时间;
  • 如果数据源(特别是下载文件)变更,修改名称,这样:你可以让其很长时间不过期,并且保证服务的是正确的版本;而链接到下载文件的页面是一个需要设置较短过期时间的页面。
  • 万不得已不要改变文件,否则你会提供一个非常新的Last-Modified日期;例如:当你更新了网站,不要复制整个网站的所有文件,只上传你修改的文件。
  • 只在必要的时候使用Cookie,cookie是非常难被缓存的,而且在大多数情况下是不必要的,如果使用cookie,控制在动态网页上;
  • 减少试用SSL,加密的页面不会被任何共享缓存服务器缓存,只在必要的时候使用,并且在SSL页面上减少图片的使用;
  • 使用可缓存性评估引擎,这对于你实践本文的很多概念都很有帮助;

7.编写利于缓存的脚本

脚本缺省不会返回校验参数(返回Last-Modified或ETag头信息)或其他新鲜度信息(Expires或Cache-Control), 有些动 态脚本的确是动态内容(每次相应内容都不一样),但是更多(搜索引擎,数据库引擎网站)网站还是能从缓存友好中获益的。
一般说来,如果脚本生成的输出在未来一段时间(几分钟或者几天)都是可重复复制的,那么就是可缓存的。如果脚本输出内容只随URL变化而变化,也是可缓存的;但如果输出会根据cookie,认证信息或者其他外部条件变化,则还是不可缓存的。

  • 最利于缓存的脚本就是将内容改变时导出成静态文件,Web服务器可以将其当作另外一个网页并生成和试用校验参数,让一些都变得更简单,只需要写入文件即可,这样最后修改时间也有了;
  • 另外一个让脚本可缓存的方法是对一段时间内能保持较新的内容设置一个相对寿命的头信息,虽然通过Expires头信息也可以实现,但更容易的是用Cache-Control: max-age属性,它会让首次请求后一段时间内缓存保持新鲜;
  • 如 果以上做法你都做不到,你可以让脚本生成一个校验属性,并对 If-Modified-Since 和/或If-None-Match请求作出反应,这些属性可以从解析HTTP头信息得到,并对符合条件的内容返回304 Not Modified(内容未改变),可惜的是,这种做法比不上前2种高效;

其他窍门:

  • 尽量避免使用POST,除非万不得已,POST模式的返回内容不会被大部分缓存服务器保存,如果你发送内容通过URL和查询(通过GET模式)的内容可以缓存下来供以后使用;
  • 不要在URL中加入针对每个用户的识别信息:除非内容是针对每个用户不同的;
  • 不要统计一个用户来自一个地址的所有请求,因为缓存常常是一起工作的;
  • 生成并返回Content-Length头信息,如果方便的话,这个属性让你的脚本在可持续链接模式时:客户端可以通过一个TCP/IP链接同时请求多个副本,而不是为每次请求单独建立链接,这样你的网站相应会快很多;

具体定义请参考实现章节。

8.常见问题解答

让网站变得可缓存的要点是什么?

好的策略是确定那些内容最热门,大量的复制(特别是图片)并针对这些内容先部署缓存。

如何让页面通过缓存达到最快相应?

缓存最好的副本是那些可以长时间保持新鲜的内容;基于校验虽然有助于加快相应,但是它不得不和源服务器联系一次去检查内容是否够新,如果缓存服务器上就知道内容是新的,内容就可以直接相应返回了。

我理解缓存是好的,但是我不得不统计多少人访问了我的网站!

如果你必须知道每次页面访问的,选择【一】个页面上的小元素,或者页面本身,通过适当的头信息让其不可缓存,例如: 可以在每个页面上部署一个1×1像素的透明图片。Referer头信息会有包含这个图片的每个页面信息;
明确一点:这个并不会给你一个关于你用户精确度很高的统计,而且这对互联网和你的用户这都不太好,消耗了额外的带宽,强迫用户去访问无法缓存的内容。了解更多信息,参考访问统计资料。

我如何能看到HTTP头信息的内容?

很多浏览器在页面属性或类似界面中可以让你看到Expires 和Last-Modified信息;如果有的话:你会找到页面信息的菜单和页面相关的文件(如图片),并且包含他们的详细信息;
看到完整的头信息,你可以用telnet手工连接到Web服务器;
为此:你可能需要用一个字段指定端口(缺省是80),或者链接到www.example.com:80 或者 www.example.com 80(注意是空格),更多设置请参考一下telnet客户端的文档;
打开网站链接:请求一个查看链接,如果你想看到http://www.example.com/foo.html 连接到www.example.com的80端口后,键入:

GET /foo.html HTTP/1.1 [回车]
GET /foo.html HTTP/1.1 [return]
Host: www.example.com [回车][回车]
Host: www.example.com [return][return]

在[回车]处按键盘的回车键;在最后,要按2次回车,然后,就会输出头信息及完整页面,如果只想看头信息,将GET换成HEAD。

我的页面是密码保护的,代理缓存服务器如何处理他们?

缺省的,网页被HTTP认证保护的都是私密内容,它们不会被任何共享缓存保留。但是,你可以通过设置Cache-Control: public让认证页面可缓存,HTTP 1.1标准兼容的缓存服务器会认出它们可缓存。
如果你认为这些可缓存的页面,但是需要每个用户认证后才能看,可以组合使用Cache-Control: public和no-cache头信息,高速缓存必须在提供副本之前,将将新客户的认证信息提交给源服务器。设置就是这样:

Cache-Control: public, no-cache

无论如何:这是减少认证请求的最好方法,例如: 你的图片是不机密的,将它们部署在另外一个目录,并对此配置服务器不强制认证。这样,那些图片会缺省都缓存。

我们是否要担心用户通过cache访问我的站点?

代理服务器上SSL页面不会被缓存(不推荐被缓存),所以你不必为此担心。但是,由于缓存保存了非SSL请求和从他们抓取的URL,你要意识到没有安全保护的网站,可能被不道德的管理员可能搜集用户隐私,特别是通过URL。
实际上,位于服务器和客户端之间的管理员可以搜集这类信息。特别是通过CGI脚本在通过URL传递用户名和密码的时候会有很大问题;这对泄露用户名和密码是一个很大的漏洞;
如果你初步懂得互联网的安全机制,你不会对缓存服务器有任何。

我在寻找一个包含在Web发布系统解决方案,那些是比较有缓存意识的系统?

这很难说,一般说来系统越复杂越难缓存。最差就是全动态发布并不提供校验参数;你无发缓存任何内容。可以向系统提供商的技术人员了解一下,并参考后面的实现说明。

我的图片设置了1个月后过期,但是我现在需要现在更新。

过期时间是绕不过去的,除非缓存(浏览器或者代理服务器)空间不足才会删除副本,缓存副本在过期之间会被一直使用。
最好的办法是改变它们的链接,这样,新的副本将会从源服务器上重新下载。记住:引用它们的页面本身也会被缓存。因此,使用静态图片和类似内容是很容易缓存的,而引用他们的HTML页面则要保持非常更新;
如果你希望对指定的缓存服务器重新载入一个副本,你可以强制使用“刷新”(在FireFox中在reload的时候按住shift键:就会有前面提到恶Pragma: no-cache头信息发出)。或者你可以让缓存的管理员从他们的界面中删除相应内容;

我运行一个Web托管服务,如何让我的用户发布缓存友好的网页?

如果你使用apahe,可以考虑允许他们使用.htaccess文件并提供相应的文档;
另外一方面: 你也可以考虑在各种虚拟主机上建立各种缓存策略。例如: 你可以设置一个目录 /cache-1m 专门用于存放访问1个月的访问,另外一个 /no-cache目录则被用提供不可存储副本的服务。
无论如何:对于大量用户访问还是应该用缓存。对于大网站,这方面的节约很明显(带宽和服务器负载);

我标记了一些网页是可缓存的,但是浏览器仍然每次发送请求给服务。如何强制他们保存副本?

缓存服务器并不会总保存副本并重用副本;他们只是在特定情况下会不保存并使用副本。所有的缓存服务器都回基于文件的大小,类型(例如:图片 页面),或者服务器空间的剩余来确定如何缓存。你的页面相比更热门或者更大的文件相比,并不值得缓存。
所以有些缓存服务器允许管理员根据文件类型确定缓存副本的优先级,允许某些副本被永久缓存并长期有效;

9.缓存机制的实现 – Web服务器端配置

一般说来,应该选择最新版本的Web服务器程序来部署。不仅因为它们包含更多利于缓存的功能,新版本往往在性能和安全性方面都有很多的改善。

Apache HTTP服务器

Apache有些可选的模块来包含这些头信息: 包括Expires和Cache-Control。 这些模块在1.2版本以上都支持;
这些模块需要和apache一起编译;虽然他们已经包含在发布版本中,但缺省并没有启用。为了确定相应模块已经被启用:找到httpd程序并运行httpd -l 它会列出可用的模块,我们需要用的模块是mod_expires和mod_headers

  • 如果这些模块不可用,你需要联系管理员,重新编译并包含这些模块。这些模块有时候通过配置文件中把注释掉的配置启用,或者在编译的时候增加-enable -module=expires和-enable-module=headers选项(在apache 1.3和以上版本)。参考Apache发布版中的INSTALL文件;

Apache一旦启用了相应的模块,你就可以在.htaccess文件或者在服务器的access.conf文件中通过mod_expires设置副本什么时候过期。你可设置过期从访问时间或文件修改时间开始计算,并且应用到某种文件类型上或缺省设置,参考模块的文档获得更多信息,或者遇到问题的时候向你身边的apache专家讨教。
应用Cache-Control头信息,你需要使用mod_headers,它将允许你设置任意的HTTP头信息,参考mod_headers的文档可以获得更多资料;
这里有个例子说明如何使用头信息:

  • .htaccess文件允许web发布者使用命令只在配置文件中用到的命令。他影响到所在目录及其子目录;问一下你的服务器管理员确认这个功能是否启用了。
### 启用 mod_expires
ExpiresActive On
### 设置 .gif 在被访问过后1个月过期。
ExpiresByType image/gif A2592000
### 其他文件设置为最后修改时间1天后过期
### (用了另外的语法)
ExpiresDefault “modification plus 1 day”
### 在index.html文件应用 Cache-Control头属性
<Files index.html>
Header append Cache-Control “public, must-revalidate”
</Files>
  • 注意:在适当情况下mod_expires会自动计算并插入Cache-Control:max-age 头信息

Apache 2.0的配置和1.3类似,更多信息可以参考2.0的mod_expiresmod_headers文档

Microsoft IIS服务器

Microsoft的IIS可以非常容易的设置头信息,注意:这只针对IIS 4.0服务器,并且只能在NT服务器上运行。
为网站的一个区域设置头信息,先要到管理员工具界面中,然后设置属性。选择HTTP Header选单,你会看到2个有趣的区域:启用内容过期和定制HTTP头信息。头一个设置会自动配置,第二个可以用于设置Cache-Control头信息;
设置asp页面的头信息可以参考后面的ASP章节,也可以通过ISAPI模块设置头信息,细节请参考MSDN。

Netscape/iPlanet企业服务器

3.6版本以后,Netscape/iPlanet已经不能设置Expires头信息了,他从3.0版本开始支持HTTP 1.1的功能。这意味着HTTP 1.1的缓存(代理服务器/浏览器)优势都可以通过你对Cache-Control设置来获得。
使用Cache-Control头信息,在管理服务器上选择内容管理|缓存设置目录。然后:使用资源选择器,选择你希望设置头信息的目录。设置完头信息后,点击“OK”。更多信息请参考Netscape/iPlanet企业服务器的手册

10.缓存机制的实现 – 服务器端脚本

需要注意的一点是:也许服务器设置HTTP头信息比脚本语言更容易,但是两者你都应该使用。
因为服务器端的脚本主要是为了动态内容,他本身不产生可缓存的文件页面,即使内容实际是可以缓存的。如果你的内容经常改变,但是不是每次页面请求都改变, 考虑设置一个Cache-Control: max-age头信息;大部分用户会在短时间内多次访问同一页面。例如: 用户点击“后退”按钮,即使没有新内容,他们仍然要再次从服务器下载内容查看。

CGI程序

CGI脚本是生成内容最流行的方式之一,你可以很容易在发送内容之前的扩展HTTP头信息;大部分CGI实现都需要你写 Content-Type头信息,例如这个Perl脚本:

#!/usr/bin/perl
print “Content-type: text/html\n”;
print “Expires: Thu, 29 Oct 1998 17:04:19 GMT\n”;
print “\n”;
### 后面是内容体…

由于都是文本,你可以很容易通过内置函数生成Expires和其他日期相关的头信息。如果你使用Cache-Control: max-age;会更简单;

print “Cache-Control: max-age=600\n”;

这样脚本可以在被请求后缓存10分钟;这样用户如果按“后退”按钮,他们不会重新提交请求;
CGI的规范同时也允许客户端发送头信息,每个头信息都有一个‘HTTP_’的前缀;这样如果一个客户端发送一个If-Modified-Since请求,就是这样的:

HTTP_IF_MODIFIED_SINCE = Fri, 30 Oct 1998 14:19:41 GMT

参考一下cgi_buffer库,一个自动处理ETag的生成和校验的库,生成Content-Length属性和对内容进行gzip压缩。在Python脚本中也只需加入一行;

服务器端包含 Server Side Includes

SSI(经常使用.shtml扩展名)是网站发布者最早可以生成动态内容的方案。通过在页面中设置特别的标记,也成为一种嵌入HTML的脚本;
大部分SSI的实现无法设置校验器,于是无法缓存。但是Apache可以通过对特定文件的组执行权限设置实现允许用户设置那种SSI可以被缓存;结合XbitHack调整整个目录。更多文档请参考mod_include文档

PHP

PHP是一个内建在web服务器中的服务器端脚本语言,当做为HTML嵌入式脚本,很像SSI,但是有更多的选项,PHP可以在各种Web服务器上设置为CGI模式运行,或者做为Apache的模块;
缺省PHP生成副本没有设置校验器,于是也无法缓存,但是开发者可以通过Header()函数来生成HTTP的头信息;
例如:以下代码会生成一个Cache-Control头信息,并设置为3天以后过期的Expires头信息;

<?php
Header(“Cache-Control: must-revalidate”);$offset = 60 * 60 * 24 * 3;
$ExpStr = “Expires: ” . gmdate(“D, d M Y H:i:s”, time() + $offset) . ” GMT”;
Header($ExpStr);
?>

记住: Header()的输出必须先于所有其他HTML的输出;
正如你看到的:你可以手工创建HTTP日期;PHP没有为你提供专门的函数(新版本已经让这个越来越容易了,请参考PHP的日期相关函数文档),当然,最简单的还是设置Cache-Control: max-age头信息,而且对于大部分情况都比较适用;
更多信息,请参考header相关的文档
也请参考一下cgi_buffer库,自动处理ETag的生成和校验,Content-Length生成和内容的gzip压缩,PHP脚本只需包含1行代码;

Cold Fusion

Cold Fusion是Macromedia的商业服务器端脚本引擎,并且支持多种Windows平台,Linux平台和多种Unix平台。Cold Fusion通过CFHEADER标记设置HTTP头信息相对容易。可惜的是:以下的Expires头信息的设置有些容易误导;

<CFHEADER NAME=”Expires” VALUE=”#Now()#”>

它并不像你想像的那样工作,因为时间(本例中为请求发起的时间)并不会被转换成一个符合HTTP时间,而且打印出副本的Cold fusion的日期/时间对象,大部分客户端会忽略或者将其转换成1970年1月1日。
但是:Cold Fusion另外提供了一套日期格式化函数, GetHttpTimeSTring. 结合DateAdd函数,就很容易设置过期时间了,这里我们设置一个Header声明副本在1个月以后过期;

<cfheader name=”Expires” value=”#GetHttpTimeString(DateAdd(‘m’, 1, Now()))#”>

你也可以使用CFHEADER标签来设置Cache-Control: max-age等其他头信息;
记住:Web服务器也会将头信息设置转给Cold Fusion(做为CGI运行的时候),检查你的服务器设置并确定你是否可以利用服务器设置代替Cold Fusion。

ASP和ASP.NET

在asp中设置HTTP头信息是:确认Response方法先于HTML内容输出前被调用,或者使用 Response.Buffer暂存输出;同样的:注意某些版本的IIS缺省设置会输出Cache-Control: private 头信息,必须声明成public才能被共享缓存服务器缓存。
IIS的ASP和其他web服务器都允许你设置HTTP头信息,例如: 设置过期时间,你可以设置Response对象的属性;

<% Response.Expires=1440 %>

设置请求的副本在输出的指定分钟后过期,类似的:也可以设置绝对的过期时间(确认你的HTTP日期格式正确)

<% Response.ExpiresAbsolute=#May 31,1996 13:30:15 GMT# %>

Cache-Control头信息可以这样设置:

<% Response.CacheControl=”public” %>

在ASP.NET中,Response.Expires 已经不推荐使用了,正确的方法是通过Response.Cache设置Cache相关的头信息;

Response.Cache.SetExpires ( DateTime.Now.AddMinutes ( 60 ) ) ;
Response.Cache.SetCacheability ( HttpCacheability.Public ) ;

参考MSDN文档可以找到更多相关新闻;

服务器发送了意外的返回值(405 Method Not Allowed),在响应“MKCOL”的请求

刚刚在通过TortoiseSVN客户端往SVN Server提交数据的时候,出现了以下错误:
服务器发送了意外的返回值(405 Method Not Allowed),在响应 “MKCOL” 的请求
解释一下现场发生之前我的操作:
我删除了一个文件夹后又创建了一个同名文件夹,在删除文件夹时未进行提交
解决方法:
删除出现错误的文件夹
SVN Update
这时服务器上存在的文件夹会出现在本地
删除原有的文件夹
SVN Commit
重新创建文件夹
SVN Commit
OK!

比较使用Linq To Entity、自已编写ORM映射、以及使用缓存保存常用数据之间的性能

第1步 建立数据库

首先我们模拟1个需求环境:会员参加活动,生成活动记录,会员有性别、所属区域字段。
第1步,建立Sql Server数据库,Members表保存成员的基本信息,其中SexId、AreaId为外键,分别关联Sex性别表、Areas区域表;Activitys表保存活动的标题;ActivityRecords表保存成员参加活动的记录,其中MemberId、ActivityId为外键,分别关联Members成员表、Activitys活动表。

下面是创建表结构的Sql语句:
Continue reading “比较使用Linq To Entity、自已编写ORM映射、以及使用缓存保存常用数据之间的性能”

深入理解依赖注入(Dependency Injection)

本来想自己写1下对依赖注入(Dependency Injection,简称DI)的理解,发现这篇文章已经写的比较全面和深入,并且易懂,遂转,希望能够给各位带来1定的帮助。

目录

1. IGame游戏公司的故事

1.1 讨论会

1.2 实习生小李的实现方法

1.3 架构师的建议

1.4 小李的小结

2. 探究依赖注入

2.1 故事的启迪

2.2 正式定义依赖注入

3. 依赖注入那些事儿

3.1 依赖注入的类别

3.1.1 Setter注入

3.1.2 Construtor注入

3.1.3 依赖获取

3.2 反射与依赖注入

3.3 多态的活性与依赖注入

3.3.1 多态性的活性

3.3.2 不同活性多态性依赖注入的选择

4. IoC Container

4.1 IoC Container出现的必然性

4.2 IoC Container的分类

4.2.1 重量级IoC Container

4.2.2 轻量级IoC Container

4.3 .NET平台上典型IoC Container推介

4.3.1 Spring.NET

4.3.2 Unity

参考文献

1. IGame游戏公司的故事

1.1 讨论会

话说有一个叫IGame的游戏公司,正在开发一款ARPG游戏(动作&角色扮演类游戏,如魔兽世界、梦幻西游这一类的游戏)。一般这类游戏 都有一个基本的功能,就是打怪(玩家攻击怪物,借此获得经验、虚拟货币和虚拟装备),并且根据玩家角色所装备的武器不同,攻击效果也不同。这 天,IGame公司的开发小组正在开会对打怪功能中的某一个功能点如何实现进行讨论,他们面前的大屏幕上是这样一份需求描述的ppt:
Continue reading “深入理解依赖注入(Dependency Injection)”

自己写的1个简单但易懂实用的广告幻灯片切换代码

代码比较简单,可操作性较强,可以自己组合。主要使用的是jquery的自有功能实现,我把代码贴出来,有空的可以参考1下:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>无标题文档</title>
<script type="text/javascript" src="jquery-1.6.2.min.js"></script>

<style>
* {font-family: "Lucida Grande" , "微软雅黑" , "宋体" , "黑体" , "幼圆" , "隶书" , "华文行楷", Verdana, Lucida, Arial, Helvetica, sans-serif;}
body {
    font-size:12px;
    border:0px;
    font-variant: normal;
    color: #bcbcbc;
    text-decoration: none;
    margin: 0px;
    padding: 0px;
    background-color: #fff;
    width:100%;
}
ul,li,ol,dl,dt,dd,form{padding:0px;margin:0px;}
li{ list-style:none}
form,img { border:none;}
.left{ float:left;}
.right{ float:right}
.clear{ clear:both}
.buttons li a {width:100px;height:50px; background:#069; display:block}
.buttons li a:hover{ background:#CCC}
.buttons li a.mouseover{background:#CCC}  /* 定义鼠标移上的效果*/
</style>

<script type="text/javascript">
//焦点图功能
$(function(){
    var ad_img = $("#imgs"); //定义幻灯图片组的id,可以根据实际情况修改
    var ad_button = $("#buttons"); //定义幻灯按钮组的id,可以根据实际情况修改
    var ad_imgs = ad_img.find("li");  //图片组的li集合
    var ad_buttons = ad_button.find("li") //按钮组的li集合
    var ad_mouseoverCls="mouseover" //定义鼠标移上的class的名称,可以根据实际情况修改
    ad_imgs.hide() //默认将图片组隐藏
    ad_imgs.eq(0).show() //默认将第1张图片显示
    var ad_i = 1; //计数器,默认从第2张图片开始淡入
    ad_buttons.mouseover(function(){
        ad_i = ad_buttons.index(this); //鼠标移上的是第几个
        ad_imgs.hide()
        ieFilter(ad_imgs[ad_i]); //执行滤镜
        ad_imgs.eq(ad_i).show(); //将对应的图片显示
        ad_buttons.find("a").removeClass(ad_mouseoverCls);
        ad_buttons.eq(ad_i).find("a").addClass(ad_mouseoverCls);
        })

    //如果是ie浏览器则执行滤镜
    var ieFilter = function(o){
        if($.browser.msie){
            $(o).css("filter","progid:DXImageTransform.Microsoft.RevealTrans(Duration=1, Transition=23)") //设置滤镜
            o.filters[0].apply();
            o.filters[0].    transition=23; //每次出现不同样式
            o.filters[0].play();
            }
        }
    var ad_autoMarquee = function(){
        if(ad_i<ad_imgs.length){
            ad_imgs.hide()
            ieFilter(ad_imgs[ad_i]); //执行滤镜
            ad_imgs.eq(ad_i).show(); //将对应的图片显示
            ad_buttons.find("a").removeClass(ad_mouseoverCls);
            ad_buttons.eq(ad_i).find("a").addClass(ad_mouseoverCls);
            ad_i++; //将计数器增1
            }else{
                ad_i=0; //重置计数器
                }
        }
    var focusAd = setInterval(ad_autoMarquee,3000)
    })    
</script>
</head>

<body>
<div >
<div id="imgs">
    <ul>
    <li><img src="Chrysanthemum.jpg" /></li>
    <li><img src="Desert.jpg" /></li>
    <li><img src="Hydrangeas.jpg" /></li>
    </ul>
</div>
<div     class="right buttons" id="buttons">
    <ul>
    <li><a href="#">1</a></li>
    <li><a href="#">2</a></li>
    <li><a href="#">3</a></li>
    </ul>
</div>
</div>
<div     class="clear"></div>
<h1><a href="http://zhuoyue.me">卓越.Me</a></h1>
</body>
</html>

你也可以看1下在线演示的效果:http://demo.zhuoyue.me/js/Ad/zi-ji-bian-xie-de-jian-dan-yi-dong-de-huan-deng/
当然你可以把源代码和图片下载下来:自己编写的简单易懂实用的广告幻灯代码

使用Data URI scheme来编码你的小图片

今天很有意思,在对组员编写的导入excel功能进行产品功能测试的时候,由于偷懒直接将不太满意的地方使用QQ截图存储到剪辑板中,然后又到项目的web管理平台当中录入Bug,并且继续发扬偷懒精神,直接使用ctrl+v粘贴截图到Bug描述(使用KindEditor超文本编辑器)当中,居然发现1个奇怪的现象:那张Bug截图居然正常的显示在编辑器当中,它可是网页上的编辑器啊,不是Windows应用软件啊,我印象中这种事以前只发生在客户端的1些软件之间的啊。
是的,根据以往的经验,我不太相信这件事,认为它可能是使用的本地系统的绝对文件路径,形如:file://c:/windows/temp/…,但是抱着死猪不怕开水烫的态度,我还是使用html源码进行了查看,这不看不要紧,1看吓1跳,发现下面代码:

<img src="data:image/png;base64,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" alt="" />

Continue reading “使用Data URI scheme来编码你的小图片”

回顾1些在项目管理中遇到的问题和解决方式

从事软件开发很长很长1段时间了,除去在校时光,正式进行社会工作也已经4年光景了。记得最初的时候,我还是使用ASP,那会儿asp还很流行,.net framework1.1才出来不久,那会儿是2003年吧;那会儿你要既会vbscript也要会javascript;那会儿你要既会css也要会html4;那会儿你要既懂vb.net 2003,也要懂c++;总之那会儿,我接触到了很多语言、思想之类的东西。
工作4年了,大小遇到过的项目也有三四十个了吧,由于1直以来,从踏入社会的那1刻就从事项目管理和开发的工作,所以在实践的道路上遇到过很多问题,通常是通过自己翻阅资料,以及寻求互联网帮助来解决,现在也慢慢的喜欢上这种方法。
现在把1些近期项目中遇到的问题记录如下,另外也附带了自己当时的处理方法和思路:
1.信息的不对称和不及时,这个问题是所有问题的根源。打个比方,进入某个迭代的产品测试环节,产品告诉美术根据客户需求,需要将新闻和公告互换位置,美术将更改后的效果图发送至项目经理和产品进行确认,项目经理可能还不知道这样1件事,项目经理、产品、客户确认过后,美术将效果图发送至前端,前端可能还不知所以然,问:“这是什么呀?”,然后美术又需要将修改的地方告诉前端。
这种信息的不对称和不及时,在项目的开发过程中,会浪费很多时间和工作量,我们应该使用1种网络软件管理或电邮等方式,要让所有项目组的人及时清楚,以及明确各自的责任。这个环节我目前使用的是zentao来进行管理。
Continue reading “回顾1些在项目管理中遇到的问题和解决方式”

共青团苏州市沧浪区委“会员制”志愿者管理服务系统

这段时间1直在负责公司的1个项目,这也是为什么今天周六大家都没有休息的原因,按照计划25日就要项目结束的了,还有13天。我来简单谈谈这个项目吧,上周这个项目也获得了苏州市经信委市级加快信息化建设专项资金扶持。
项目的主办方是共青团苏州沧浪区委,他们1直在计划这样1个平台:大家可以看到现在每天早晚会有很多挽着“志愿者”标志的广大社会人士在苏州市交通要道指挥交通,这些人叫做志愿者,他们1直有志于回报社会,当然这是非常值得提倡和学习的,目前社会志愿者也日益成为我国现代社会管理的重要力量,团区委希望搭建这样1个平台,在这个平台上广大普通社会人士可以申请注册成为1名志愿者,当他的信息通过审核后,团区委会给他发送1张能够唯一标识他身份的会员卡片,并且他能够登陆这个平台,在平台上获取感兴趣的志愿活动信息,然后申请报名相应志愿活动,1旦活动报名通过,他就能够去现场参加这些志愿活动,比如:去照顾老人、带小孩、指挥交通等等,同时团区委为了发扬和扩大志愿者精神和队伍,也会对志愿者们进行小小的奖励,就是在他每参加完1次志愿活动后,会在平台系统里给他记录1定数量的积分,当志愿者的积分达到1定量的时候,可以去团区委的指定机构兑换1些纪念礼品。
本身这个项目是比较简单的,相对比较复杂的就是:由于志愿者在参加完活动后需要记录积分,那么就需要有联网的设备对会员卡片进行刷卡操作,以将会员卡号、活动编号发送到平台;由于志愿者在积分达到1定程度的时候,可以去指定机构兑换礼品,那么同样需要有联网的设备对会员卡片进行刷卡操作,以将会员卡号、礼品编号发送到平台。
这也是我们目前尚未解决的问题,我们将这个需求扩展以后,形成了以下这样2条需求:“某志愿者通过某终端刷卡机参加了某活动,获得了多少积分;某志愿者通过某终端刷卡机兑换了某礼品,消费了多少积分”。为了实现这2条需求,团委领导、公司老板及我先后接触了多家公司,第1家是建行苏州分行,我们计划使用建行的信用卡或者借记卡来实行会员卡的功能,刷卡终端采用银联POS机或建行其它设备来实现,但后来由于建行信用卡发卡需要较多的用户信息,并且信用卡需要年费,未成立人需要监护人签字等条件,建行借记卡不能零消费,还有小额管理费等条件,我们和建行的洽谈就结束了;第2家我们找的是中国移动苏州分公司,刚开始他们给我们介绍了1款叫做CUPMobile智能SD卡的产品,这款产品是第3方供应商提供的,叫做海瑞斯信息科技,这款产品大小和1般mini sdcard相仿,可以作为普通的sdcard插入手机sdcard插槽中,不同的是该sd卡是智能sd卡,带有可以和终端设备进行交互的芯片,能够进行编程罢了,该方案后来由于必须走银联通道而告终,因为走银联通道意味着每次刷卡银联就要收取手续费,并且银联也不允许零消费操作;第3家还是中移苏分,不过这次他们推荐的是他们自己的产品,1张带有天线的芯片卡,可以放进普通的手机里,如果放不进去的,也可以做成1张普通IC卡片,无奈该方案也不完美,就是该方案终端设备上相配套的终端软件需要独立开发,这是我们目前的方案1。
当前该项目即将要结束,却陷入僵局,没有较完美的终端刷卡方案。期间我也计划了另外1套方案,使用二维码技术,每次志愿者上网报名活动和兑换礼品,我们生成1个唯一的2维码图片给志愿者,他可以选择打印或者保存,我们也可以通过彩信通道将2维码图片发送至他的手机上,志愿者只要活动结束或者礼品兑换现场将2维码图片出示并在电脑或手机前1扫,我们就可以获得该二维码图片所代表的网址信息,我们将网址打开就实现了刷卡,这是我们目前的方案2。
这个项目采用的是PHP,使用的Zend Framework MVC方式,目前90%功能部分和页面已经完成,但是时间相当紧迫,辛苦了这帮弟兄们了。