假如你有个地域性很强的导购网站,在北京有个子站,在杭州又有一个,两个子站在后台对应单个JAVA应用。这时你会怎么设计URL的结构以区别不同的子站? 用hangzhou.hello.com/xxx代表杭州,beijing.hello.com/xxx代表北京;或者是hello.com/hangzhou/xxx代表杭州,hello.com/beijing/xxx代表北京;或者干脆都用统一的hello.com/xxx,让服务端记住用户在首页的选择然后根据这个信息来判断?
这些方案各有利弊。我最推荐子域名区隔方案(hangzhou.hello.com/xxx),因为它在代码容错方面有突出的优势。
对开发人员来说,多子站系统面临的问题是:
1. 处理用户请求如何判断用户当前所在的子站?是否要通过URL里的子站标识来判断?
2. 如果判断机制依赖URL,那么站内的所有链接应该都要带上子站标识,这样实现起来会不会很麻烦?
对产品经理来说, URL的长度、结构对用户体验也会有些影响。
不同的方案解决这些问题的能力也不同。
方案:用户在首页或者合适的地方显式地选择子站,然后后台将子站标识存入session,后续的所有访问都依赖于session里的子站标识,URL全站统一。
这种方案的缺点很大:
1. 大多数系统中,业务逻辑相关代码可以直接操纵session, 一旦代码写错或者漏写,很容易出现问题(尤其是新人接手时)。典型的例子:
a) 可能出现空白的页面区块、以及空指针错误。原因:在用户登出后,整个session对象被消灭,导致子站标识从session里也找不到。
b) 杭州站某条导购信息的页面中可能出现北京站的logo。 原因:代码里只知道展现导购信息,忘了更新session对象中的站点标识;当用户离开北京站来到杭州站时,就有可能出错。
2. 所有请求都是有状态的请求。
a) URL无法代表完整的请求。 在杭州站搜索完后把链接发给外地的朋友,朋友打开后可能看到的是北京的搜索结果
b) 某个请求一旦弄错了Session,后续请求都会弄错。
这种方案也有些好处:
1. 统一、简洁的URL,美观大方
2. 开发人员写站内链接时不必带上子站标识。直接写hello.com/xxx即可,不必写hello.com/hangzhou/xxx . (后文可以看出,这个很重要)
方案: 每个请求都是这种风格:hello.com/hangzhou/xxx 或 hello.com/xxx?site=hangzhou ; 后端服务器通过一个拦截器(如struts2中的interceptor)拦截所有请求,根据URL中的子站标识确定当前子站,并把它作为attribute存到当前request对象中 (request.setAttribute(“site”, “hangzhou”)。
优点:不依赖session,不存在前一种方案说的各种问题。
缺点:
1. 每个请求都要拦截,可能会有性能浪费。在一般情况下,这种性能损失都很小或者可以通过技术手段来规避。
2. URL冗长丑陋。尤其是xxx?site=hangzhou这种,有点影响心情。
3. (致命的) 所有站内链接都必须带上站点标识。
a) 你的jsp/Freemarker/velocity总是要加上一串:/yyy?site=${site}, 按我的经验和观察,这很容易遗漏(尤其是项目紧的时候),导致站点老是出现小毛病。
b) 另一种解决方案是使用自定义的jsp/freemarket/velocity View Tag, 如 <@mylink href=”/yyy”/>. 这样做的后果也很严重:它导致前端工程师在设计页面时无法直接使用原生的html <a/>标签,这对前后端分工有很大影响。
方案:使用hangzhou.hello.com/xxx这种URL
优点:
1. 解决了前一个方案中关于站内链接的问题。站内链接使用相对链接,如果当前站点是北京站,则用户点击/xxx时会浏览器会自然地进入beijing.hello.com/xxx,保持了一致性。
2. URL的美观度上也比前一个方案好一些,而且用户可以直接记住子域名,不太专业地说,这对培养用户的子站意识也有好处。
以上三种方案中,子域名方案是最值得推荐的,在最近的一个项目中我使用了这种方案并且效果不错。
不管你用那种方案,都必须注意一点:子站区隔机制最好要单一、并且必须集中在单一的API接口中供业务代码调用。我曾经经历过这样一个系统:既可以通过URL判断子站、又可以通过域名判断、又把子站标识放进了session,而且分别有一个API可以让你随意调用。在这种机制下,程序员非常非常容易犯错误,对机制进行微调时也经常有遗漏,因为这个引起的bug可以说是家常便饭……