1、因为我们使用了主从,所以需要给server起一个名字如server1、server2;否则分片算法默认根据ip:port:weight,这样就会主从数据的分片算法不一致;
复制第六章的nutcracker.init,帮把配置文件改为usr/chapter7/nutcracker.yml。然后通过/usr/chapter7/nutcracker.init start启动Twemproxy。
动态服务实现
因为真实数据是从多个子系统获取,很难模拟这么多子系统交互,所以此处我们使用假数据来进行实现。
项目搭建
我们使用Maven搭建Web项目,Maven知识请自行学习。
项目依赖
本文将最小化依赖,即仅依赖我们需要的servlet、jackson、guava、jedis。
<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>17.0</version>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.5.2</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.3.3</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.3.3</version>
</dependency>
</dependencies>
guava是类似于apache commons的一个基础类库,用于简化一些重复操作,可以参考http://ifeve.com/google-guava/。
核心代码
com.github.zhangkaitao.chapter7.servlet.ProductServiceServlet
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String type = req.getParameter("type");
String content = null;
try {
if("basic".equals(type)) {
content = getBasicInfo(req.getParameter("skuId"));
} else if("desc".equals(type)) {
content = getDescInfo(req.getParameter("skuId"));
} else if("other".equals(type)) {
content = getOtherInfo(req.getParameter("ps3Id"), req.getParameter("brandId"));
}
} catch (Exception e) {
e.printStackTrace();
resp.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
return;
}
if(content != null) {
resp.setCharacterEncoding("UTF-8");
resp.getWriter().write(content);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_FOUND);
}
}
根据请求参数type来决定调用哪个服务获取数据。
基本信息服务
private String getBasicInfo(String skuId) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
//商品编号
map.put("skuId", skuId);
//名称
map.put("name", "苹果(Apple)iPhone 6 (A1586) 16GB 金色 移动联通电信4G手机");
//一级二级三级分类
map.put("ps1Id", 9987);
map.put("ps2Id", 653);
map.put("ps3Id", 655);
//品牌ID
map.put("brandId", 14026);
//图片列表
map.put("imgs", getImgs(skuId));
//上架时间
map.put("date", "2014-10-09 22:29:09");
//商品毛重
map.put("weight", "400");
//颜色尺码
map.put("colorSize", getColorSize(skuId));
//扩展属性
map.put("expands", getExpands(skuId));
//规格参数
map.put("propCodes", getPropCodes(skuId));
map.put("date", System.currentTimeMillis());
String content = objectMapper.writeValueAsString(map);
//实际应用应该是发送MQ
asyncSetToRedis(basicInfoJedisPool, "p:" + skuId + ":", content);
return objectMapper.writeValueAsString(map);
}
private List<String> getImgs(String skuId) {
return Lists.newArrayList(
"jfs/t277/193/1005339798/768456/29136988/542d0798N19d42ce3.jpg",
"jfs/t352/148/1022071312/209475/53b8cd7f/542d079bN3ea45c98.jpg",
"jfs/t274/315/1008507116/108039/f70cb380/542d0799Na03319e6.jpg",
"jfs/t337/181/1064215916/27801/b5026705/542d079aNf184ce18.jpg"
);
}
private List<Map<String, Object>> getColorSize(String skuId) {
return Lists.newArrayList(
makeColorSize(1217499, "金色", "公开版(16GB ROM)"),
makeColorSize(1217500, "深空灰", "公开版(16GB ROM)"),
makeColorSize(1217501, "银色", "公开版(16GB ROM)"),
makeColorSize(1217508, "金色", "公开版(64GB ROM)"),
makeColorSize(1217509, "深空灰", "公开版(64GB ROM)"),
makeColorSize(1217509, "银色", "公开版(64GB ROM)"),
makeColorSize(1217493, "金色", "移动4G版 (16GB)"),
makeColorSize(1217494, "深空灰", "移动4G版 (16GB)"),
makeColorSize(1217495, "银色", "移动4G版 (16GB)"),
makeColorSize(1217503, "金色", "移动4G版 (64GB)"),
makeColorSize(1217503, "金色", "移动4G版 (64GB)"),
makeColorSize(1217504, "深空灰", "移动4G版 (64GB)"),
makeColorSize(1217505, "银色", "移动4G版 (64GB)")
);
}
private Map<String, Object> makeColorSize(long skuId, String color, String size) {
Map<String, Object> cs1 = Maps.newHashMap();
cs1.put("SkuId", skuId);
cs1.put("Color", color);
cs1.put("Size", size);
return cs1;
}
private List<List<?>> getExpands(String skuId) {
return Lists.newArrayList(
(List<?>)Lists.newArrayList("热点", Lists.newArrayList("超薄7mm以下", "支持NFC")),
(List<?>)Lists.newArrayList("系统", "苹果(IOS)"),
(List<?>)Lists.newArrayList("系统", "苹果(IOS)"),
(List<?>)Lists.newArrayList("购买方式", "非合约机")
);
}
private Map<String, List<List<String>>> getPropCodes(String skuId) {
Map<String, List<List<String>>> map = Maps.newHashMap();
map.put("主体", Lists.<List<String>>newArrayList(
Lists.<String>newArrayList("品牌", "苹果(Apple)"),
Lists.<String>newArrayList("型号", "iPhone 6 A1586"),
Lists.<String>newArrayList("颜色", "金色"),
Lists.<String>newArrayList("上市年份", "2014年")
));
map.put("存储", Lists.<List<String>>newArrayList(
Lists.<String>newArrayList("机身内存", "16GB ROM"),
Lists.<String>newArrayList("储存卡类型", "不支持")
));
map.put("显示", Lists.<List<String>>newArrayList(
Lists.<String>newArrayList("屏幕尺寸", "4.7英寸"),
Lists.<String>newArrayList("触摸屏", "Retina HD"),
Lists.<String>newArrayList("分辨率", "1334 x 750")
));
return map;
}
本例基本信息提供了如商品名称、图片列表、颜色尺码、扩展属性、规格参数等等数据;而为了简化逻辑大多数数据都是List/Map数据结构。
商品介绍服务
private String getDescInfo(String skuId) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
map.put("content", "<div><img data-lazyload='http://img30.360buyimg.com/jgsq-productsoa/jfs/t448/127/574781110/103911/b3c80634/5472ba22N45400f4e.jpg' alt='' /><img data-lazyload='http://img30.360buyimg.com/jgsq-productsoa/jfs/t802/133/19465528/162152/e463e43/54e2b34aN11bceb70.jpg' alt='' height='386' width='750' /></div>");
map.put("date", System.currentTimeMillis());
String content = objectMapper.writeValueAsString(map);
//实际应用应该是发送MQ
asyncSetToRedis(descInfoJedisPool, "d:" + skuId + ":", content);
return objectMapper.writeValueAsString(map);
}
其他信息服务
private String getOtherInfo(String ps3Id, String brandId) throws Exception {
Map<String, Object> map = new HashMap<String, Object>();
//面包屑
List<List<?>> breadcrumb = Lists.newArrayList();
breadcrumb.add(Lists.newArrayList(9987, "手机"));
breadcrumb.add(Lists.newArrayList(653, "手机通讯"));
breadcrumb.add(Lists.newArrayList(655, "手机"));
//品牌
Map<String, Object> brand = Maps.newHashMap();
brand.put("name", "苹果(Apple)");
brand.put("logo", "BrandLogo/g14/M09/09/10/rBEhVlK6vdkIAAAAAAAFLXzp-lIAAHWawP_QjwAAAVF472.png");
map.put("breadcrumb", breadcrumb);
map.put("brand", brand);
//实际应用应该是发送MQ
asyncSetToRedis(otherInfoJedisPool, "s:" + ps3Id + ":", objectMapper.writeValueAsString(breadcrumb));
asyncSetToRedis(otherInfoJedisPool, "b:" + brandId + ":", objectMapper.writeValueAsString(brand));
return objectMapper.writeValueAsString(map);
}
本例中其他信息只使用了面包屑和品牌数据。
辅助工具
private ObjectMapper objectMapper = new ObjectMapper();
private JedisPool basicInfoJedisPool = createJedisPool("127.0.0.1", 1111);
private JedisPool descInfoJedisPool = createJedisPool("127.0.0.1", 1113);
private JedisPool otherInfoJedisPool = createJedisPool("127.0.0.1", 1115);
private JedisPool createJedisPool(String host, int port) {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(100);
return new JedisPool(poolConfig, host, port);
}
private ExecutorService executorService = Executors.newFixedThreadPool(10);
private void asyncSetToRedis(final JedisPool jedisPool, final String key, final String content) {
executorService.submit(new Runnable() {
@Override
public void run() {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
jedis.set(key, content);
} catch (Exception e) {
e.printStackTrace();
jedisPool.returnBrokenResource(jedis);
} finally {
jedisPool.returnResource(jedis);
}
}
});
}
本例使用Jackson进行JSON的序列化;Jedis进行Redis的操作;使用线程池做异步更新(实际应用中可以使用MQ做实现)。
web.xml配置
<servlet>
<servlet-name>productServiceServlet</servlet-name>
<servlet-class>com.github.zhangkaitao.chapter7.servlet.ProductServiceServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>productServiceServlet</servlet-name>
<url-pattern>/info</url-pattern>
</servlet-mapping>
打WAR包
cd D:\workspace\chapter7
mvn clean package
此处使用maven命令打包,比如本例将得到chapter7.war,然后将其上传到服务器的/usr/chapter7/webapp,然后通过unzip chapter6.war解压。
配置Tomcat
复制第六章使用的tomcat实例:
cd /usr/servers/
cp -r tomcat-server1 tomcat-chapter7/
vim /usr/servers/tomcat-chapter7/conf/Catalina/localhost/ROOT.xml
<!-- 访问路径是根,web应用所属目录为/usr/chapter7/webapp -->
<Context path="" docBase="/usr/chapter7/webapp"></Context>
指向第七章的web应用路径。
测试
启动tomcat实例。
/usr/servers/tomcat-chapter7/bin/startup.sh
访问如下URL进行测试。
http://192.168.1.2:8080/info?type=basic&skuId=1
http://192.168.1.2:8080/info?type=desc&skuId=1
http://192.168.1.2:8080/info?type=other&ps3Id=1&brandId=1
nginx配置
vim /usr/chapter7/nginx_chapter7.conf
upstream backend {
server 127.0.0.1:8080 max_fails=5 fail_timeout=10s weight=1;
check interval=3000 rise=1 fall=2 timeout=5000 type=tcp default_down=false;
keepalive 100;
}
server {
listen 80;
server_name item2015.jd.com item.jd.com d.3.cn;
location ~ /backend/(.*) {
#internal;
keepalive_timeout 30s;
keepalive_requests 1000;
#支持keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
rewrite /backend(/.*) $1 break;
proxy_pass_request_headers off;
#more_clear_input_headers Accept-Encoding;
proxy_next_upstream error timeout;
proxy_pass http://backend;
}
}
此处server_name 我们指定了item.jd.com(商品详情页)和d.3.cn(商品介绍)。其他配置可以参考第六章内容。另外实际生产环境要把#internal打开,表示只有本nginx能访问。
vim /usr/servers/nginx/conf/nginx.conf
include /usr/chapter7/nginx_chapter7.conf;
#为了方便测试,注释掉example.conf
include /usr/chapter6/nginx_chapter6.conf;
#lua模块路径,其中”;;”表示默认搜索路径,默认到/usr/servers/nginx下找
lua_package_path "/usr/chapter7/lualib/?.lua;;"; #lua 模块
lua_package_cpath "/usr/chapter7/lualib/?.so;;"; #c模块
lua模块从/usr/chapter7目录加载,因为我们要写自己的模块使用。
重启nginx
/usr/servers/nginx/sbin/nginx -s reload
绑定hosts
192.168.1.2 item.jd.com
192.168.1.2 item2015.jd.com
192.168.1.2 d.3.cn
访问如http://item.jd.com/backend/info?type=basic&skuId=1即看到结果。
前端展示实现
我们分为三部分实现:基础组件、商品介绍、前端展示部分。
基础组件
首先我们进行基础组件的实现,商品介绍和前端展示部分都需要读取Redis和Http服务,因此我们可以抽取公共部分出来复用。
vim /usr/chapter7/lualib/item/common.lua
local redis = require("resty.redis")
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local function close_redis(red)
if not red then
return
end
--释放连接(连接池实现)
local pool_max_idle_time = 10000 --毫秒
local pool_size = 100 --连接池大小
local ok, err = red:set_keepalive(pool_max_idle_time, pool_size)
if not ok then
ngx_log(ngx_ERR, "set redis keepalive error : ", err)
end
end
local function read_redis(ip, port, keys)
local red = redis:new()
red:set_timeout(1000)
local ok, err = red:connect(ip, port)
if not ok then
ngx_log(ngx_ERR, "connect to redis error : ", err)
return close_redis(red)
end
local resp = nil
if #keys == 1 then
resp, err = red:get(keys[1])
else
resp, err = red:mget(keys)
end
if not resp then
ngx_log(ngx_ERR, "get redis content error : ", err)
return close_redis(red)
end
--得到的数据为空处理
if resp == ngx.null then
resp = nil
end
close_redis(red)
return resp
end
local function read_http(args)
local resp = ngx.location.capture("/backend/info", {
method = ngx.HTTP_GET,
args = args
})
if not resp then
ngx_log(ngx_ERR, "request error")
return
end
if resp.status ~= 200 then
ngx_log(ngx_ERR, "request error, status :", resp.status)
return
end
return resp.body
end
local _M = {
read_redis = read_redis,
read_http = read_http
}
return _M
整个逻辑和第六章类似;只是read_redis根据参数keys个数支持get和mget。 比如read_redis(ip, port, {"key1"})则调用get而read_redis(ip, port, {"key1", "key2"})则调用mget。
商品介绍
核心代码
vim /usr/chapter7/desc.lua
local common = require("item.common")
local read_redis = common.read_redis
local read_http = common.read_http
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_re_match = ngx.re.match
local ngx_var = ngx.var
local descKey = "d:" .. skuId .. ":"
local descInfoStr = read_redis("127.0.0.1", 1114, {descKey})
if not descInfoStr then
ngx_log(ngx_ERR, "redis not found desc info, back to http, skuId : ", skuId)
descInfoStr = read_http({type="desc", skuId = skuId})
end
if not descInfoStr then
ngx_log(ngx_ERR, "http not found basic info, skuId : ", skuId)
return ngx_exit(404)
end
ngx_print("showdesc(")
ngx_print(descInfoStr)
ngx_print(")")
通过复用逻辑后整体代码简化了许多;此处读取商品介绍从集群;另外前端展示使用JSONP技术展示商品介绍。
nginx配置
vim /usr/chapter7/nginx_chapter7.conf
location ~^/desc/(\d+)$ {
if ($host != "d.3.cn") {
return 403;
}
default_type application/x-javascript;
charset utf-8;
lua_code_cache on;
set $skuId $1;
content_by_lua_file /usr/chapter7/desc.lua;
}
因为item.jd.com和d.3.cn复用了同一个配置文件,此处需要限定只有d.3.cn域名能访问,防止恶意访问。
重启nginx后,访问如http://d.3.cn/desc/1即可得到JSONP结果。
前端展示
核心代码
vim /usr/chapter7/item.lua
local common = require("item.common")
local item = require("item")
local read_redis = common.read_redis
local read_http = common.read_http
local cjson = require("cjson")
local cjson_decode = cjson.decode
local ngx_log = ngx.log
local ngx_ERR = ngx.ERR
local ngx_exit = ngx.exit
local ngx_print = ngx.print
local ngx_var = ngx.var
local skuId = ngx_var.skuId
--获取基本信息
local basicInfoKey = "p:" .. skuId .. ":"
local basicInfoStr = read_redis("127.0.0.1", 1112, {basicInfoKey})
if not basicInfoStr then
ngx_log(ngx_ERR, "redis not found basic info, back to http, skuId : ", skuId)
basicInfoStr = read_http({type="basic", skuId = skuId})
end
if not basicInfoStr then
ngx_log(ngx_ERR, "http not found basic info, skuId : ", skuId)
return ngx_exit(404)
end
local basicInfo = cjson_decode(basicInfoStr)
local ps3Id = basicInfo["ps3Id"]
local brandId = basicInfo["brandId"]
--获取其他信息
local breadcrumbKey = "s:" .. ps3Id .. ":"
local brandKey = "b:" .. brandId ..":"
local otherInfo = read_redis("127.0.0.1", 1116, {breadcrumbKey, brandKey}) or {}
local breadcrumbStr = otherInfo[1]
local brandStr = otherInfo[2]
if breadcrumbStr then
basicInfo["breadcrumb"] = cjson_decode(breadcrumbStr)
end
if brandStr then
basicInfo["brand"] = cjson_decode(brandStr)
end
if not breadcrumbStr and not brandStr then
ngx_log(ngx_ERR, "redis not found other info, back to http, skuId : ", brandId)
local otherInfoStr = read_http({type="other", ps3Id = ps3Id, brandId = brandId})
if not otherInfoStr then
ngx_log(ngx_ERR, "http not found other info, skuId : ", skuId)
else
local otherInfo = cjson_decode(otherInfoStr)
basicInfo["breadcrumb"] = otherInfo["breadcrumb"]
basicInfo["brand"] = otherInfo["brand"]
end
end
local name = basicInfo["name"]
--name to unicode
basicInfo["unicodeName"] = item.utf8_to_unicode(name)
--字符串截取,超长显示...
basicInfo["moreName"] = item.trunc(name, 10)
--初始化各分类的url
item.init_breadcrumb(basicInfo)
--初始化扩展属性
item.init_expand(basicInfo)
--初始化颜色尺码
item.init_color_size(basicInfo)
local template = require "resty.template"
template.caching(true)
template.render("item.html", basicInfo)
整个逻辑分为四部分:1、获取基本信息;2、根据基本信息中的关联关系获取其他信息;3、初始化/格式化数据;4、渲染模板。
初始化模块
vim /usr/chapter7/lualib/item.lua
local bit = require("bit")
local utf8 = require("utf8")
local cjson = require("cjson")
local cjson_encode = cjson.encode
local bit_band = bit.band
local bit_bor = bit.bor
local bit_lshift = bit.lshift
local string_format = string.format
local string_byte = string.byte
local table_concat = table.concat
--utf8转为unicode
local function utf8_to_unicode(str)
if not str or str == "" or str == ngx.null then
return nil
end
local res, seq, val = {}, 0, nil
for i = 1, #str do
local c = string_byte(str, i)
if seq == 0 then
if val then
res[#res + 1] = string_format("%04x", val)
end
seq = c < 0x80 and 1 or c < 0xE0 and 2 or c < 0xF0 and 3 or
c < 0xF8 and 4 or --c < 0xFC and 5 or c < 0xFE and 6 or
0
if seq == 0 then
ngx.log(ngx.ERR, 'invalid UTF-8 character sequence' .. ",,," .. tostring(str))
return str
end
val = bit_band(c, 2 ^ (8 - seq) - 1)
else
val = bit_bor(bit_lshift(val, 6), bit_band(c, 0x3F))
end
seq = seq - 1
end
if val then
res[#res + 1] = string_format("%04x", val)
end
if #res == 0 then
return str
end
return "\\u" .. table_concat(res, "\\u")
end
--utf8字符串截取
local function trunc(str, len)
if not str then
return nil
end
if utf8.len(str) > len then
return utf8.sub(str, 1, len) .. "..."
end
return str
end
--初始化面包屑
local function init_breadcrumb(info)
local breadcrumb = info["breadcrumb"]
if not breadcrumb then
return
end
local ps1Id = breadcrumb[1][1]
local ps2Id = breadcrumb[2][1]
local ps3Id = breadcrumb[3][1]
--此处应该根据一级分类查找url
local ps1Url = "http://shouji.jd.com/"
local ps2Url = "http://channel.jd.com/shouji.html"
local ps3Url = "http://list.jd.com/list.html?cat=" .. ps1Id .. "," .. ps2Id .. "," .. ps3Id
breadcrumb[1][3] = ps1Url
breadcrumb[2][3] = ps2Url
breadcrumb[3][3] = ps3Url
end
--初始化扩展属性
local function init_expand(info)
local expands = info["expands"]
if not expands then
return
end
for _, e in ipairs(expands) do
if type(e[2]) == "table" then
e[2] = table_concat(e[2], ",")
end
end
end
--初始化颜色尺码
local function init_color_size(info)
local colorSize = info["colorSize"]
--颜色尺码JSON串
local colorSizeJson = cjson_encode(colorSize)
--颜色列表(不重复)
local colorList = {}
--尺码列表(不重复)
local sizeList = {}
info["colorSizeJson"] = colorSizeJson
info["colorList"] = colorList
info["sizeList"] = sizeList
local colorSet = {}
local sizeSet = {}
for _, cz in ipairs(colorSize) do
local color = cz["Color"]
local size = cz["Size"]
if color and color ~= "" and not colorSet[color] then
colorList[#colorList + 1] = {color = color, url = "http://item.jd.com/" ..cz["SkuId"] .. ".html"}
colorSet[color] = true
end
if size and size ~= "" and not sizeSet[size] then
sizeList[#sizeList + 1] = {size = size, url = "http://item.jd.com/" ..cz["SkuId"] .. ".html"}
sizeSet[size] = ""
end
end
end
local _M = {
utf8_to_unicode = utf8_to_unicode,
trunc = trunc,
init_breadcrumb = init_breadcrumb,
init_expand = init_expand,
init_color_size = init_color_size
}
return _M
比如utf8_to_unicode代码之前已经见过了,其他的都是一些逻辑代码。
模板html片段
var pageConfig = {
compatible: true,
product: {
skuid: {* skuId *},
name: '{* unicodeName *}',
skuidkey:'AFC266E971535B664FC926D34E91C879',
href: 'http://item.jd.com/{* skuId *}.html',
src: '{* imgs[1] *}',
cat: [{* ps1Id *},{* ps2Id *},{* ps3Id *}],
brand: {* brandId *},
tips: false,
pType: 1,
venderId:0,
shopId:'0',
specialAttrs:["HYKHSP-0","isDistribution","isHaveYB","isSelfService-0","isWeChatStock-0","packType","IsNewGoods","isCanUseDQ","isSupportCard","isCanUseJQ","isOverseaPurchase-0","is7ToReturn-1","isCanVAT"],
videoPath:'',
desc: 'http://d.3.cn/desc/{* skuId *}'
}
};
var warestatus = 1;
{% if colorSizeJson then %} var ColorSize = {* colorSizeJson *};{% end %}
{-raw-}
try{(function(flag){ if(!flag){return;} if(window.location.hash == '#m'){var exp = new Date();exp.setTime(exp.getTime() + 30 * 24 * 60 * 60 * 1000);document.cookie = "pcm=1;expires=" + exp.toGMTString() + ";path=/;domain=jd.com";return;}else{var cook=document.cookie.match(new RegExp("(^| )pcm=([^;]*)(;|$)"));if(cook&&cook.length>2&&unescape(cook[2])=="2"){flag=false;}} var userAgent = navigator.userAgent; if(userAgent){ userAgent = userAgent.toUpperCase();if(userAgent.indexOf("PAD")>-1){return;} var mobilePhoneList = ["IOS","IPHONE","ANDROID","WINDOWS PHONE"];for(var i=0,len=mobilePhoneList.length;i<len;i++){ if(userAgent.indexOf(mobilePhoneList[i])>-1){var url="http://m.jd.com/product/"+pageConfig.product.skuid+".html";if(flag){window.showtouchurl=true;}else{window.location.href = url;}break;}}}})((function(){var json={"6881":3,"1195":3,"10011":3,"6980":3,"12360":3};if(json[pageConfig.product.cat[0]+""]==1||json[pageConfig.product.cat[1]+""]==2||json[pageConfig.product.cat[2]+""]==3){return false;}else{return true;}})());}catch(e){}
{-raw-}
{* var *}输出变量,{% code %} 写代码片段,{-raw-} 不进行任何处理直接输出。
面包屑
<div class="breadcrumb">
<strong><a href='{* breadcrumb[1][3] *}'>{* breadcrumb[1][2] *}</a></strong>
<span>
>
<a href='{* breadcrumb[2][3] *}'>{* breadcrumb[2][2] *}</a>
>
<a href='{* breadcrumb[3][3] *}'>{* breadcrumb[3][2] *}</a>
>
</span>
<span>
{% if brand then %}
<a href='http://www.jd.com/pinpai/{* ps3Id *}-{* brandId *}.html'>{* brand['name'] *}</a>
>
{% end %}
<a href='http://item.jd.com/{* skuId *}.html'>{* moreName *}</a>
</span>
</div>
图片列表
<div id="spec-n1" class="jqzoom" onclick="window.open('http://www.jd.com/bigimage.aspx?id={* skuId *}')" clstag="shangpin|keycount|product|spec-n1">
<img data-img="1" width="350" height="350" src="http://img14.360buyimg.com/n1/{* imgs[1] *}" alt="{* name *}"/>
</div>
<div id="spec-list" clstag="shangpin|keycount|product|spec-n5">
<a href="javascript:;" class="spec-control" id="spec-forward"></a>
<a href="javascript:;" class="spec-control" id="spec-backward"></a>
<div class="spec-items">
<ul class="lh">
{% for _, img in ipairs(imgs) do %}
<li><img class='img-hover' alt='{* name *}' src='http://img14.360buyimg.com/n5/{* img *}' data-url='{* img *}' data-img='1' width='50' height='50'></li>
{% end %}
</ul>
</div>
</div>
颜色尺码选择
<div class="dt">选择颜色:</div>
<div class="dd">
{% for _, color in ipairs(colorList) do %}
<div class="item"><b></b><a href="{* color['url'] *}" title="{* color['color'] *}"><i>{* color['color'] *}</i></a></div>
{% end %}
</div>
</div>
<div id="choose-version" class="li">
<div class="dt">选择版本:</div>
<div class="dd">
{% for _, size in ipairs(sizeList) do %}
<div class="item"><b></b><a href="{* size['url'] *}" title="{* size['size'] *}">{* size['size'] *}</a></div>
{% end %}
</div>
</div>
扩展属性
<ul id="parameter2" class="p-parameter-list">
<li title='{* name *}'>商品名称:{* name *}</li>
<li title='{* skuId *}'>商品编号:{* skuId *}</li>
{% if brand then %}
<li title='{* brand["name"] *}'>品牌: <a href='http://www.jd.com/pinpai/{* ps3Id *}-{* brandId *}.html' target='_blank'>{* brand["name"] *}</a></li>
{% end %}
{% if date then %}
<li title='{* date *}'>上架时间:{* date *}</li>
{% end %}
{% if weight then %}
<li title='{* weight *}'>商品毛重:{* weight *}</li>
{% end %}
{% for _, e in pairs(expands) do %}
<li title='{* e[2] *}'>{* e[1] *}:{* e[2] *}</li>
{% end %}
</ul>
规格参数
<table cellpadding="0" cellspacing="1" width="100%" border="0" class="Ptable">
{% for group, pc in pairs(propCodes) do %}
<tr><th class="tdTitle" colspan="2">{* group *}</th><tr>
{% for _, v in pairs(pc) do %}
<tr><td class="tdTitle">{* v[1] *}</td><td>{* v[2] *}</td></tr>
{% end %}
{% end %}
</table>
nginx配置
vim /usr/chapter7/nginx_chapter7.conf
#模板加载位置
set $template_root "/usr/chapter7";
location ~ ^/(\d+).html$ {
if ($host !~ "^(item|item2015)\.jd\.com$") {
return 403;
}
default_type 'text/html';
charset utf-8;
lua_code_cache on;
set $skuId $1;
content_by_lua_file /usr/chapter7/item.lua;
}
测试
重启nginx,访问http://item.jd.com/1217499.html可得到响应内容,本例和京东的商品详情页的数据是有些出入的,输出的页面可能是缺少一些数据的。
优化
local cache
对于其他信息,对数据一致性要求不敏感,而且数据量很少,完全可以在本地缓存全量;而且可以设置如5-10分钟的过期时间是完全可以接受的;因此可以lua_shared_dict全局内存进行缓存。具体逻辑可以参考
local nginx_shared = ngx.shared
--item.jd.com配置的缓存
local local_cache = nginx_shared.item_local_cache
local function cache_get(key)
if not local_cache then
return nil
end
return local_cache:get(key)
end
local function cache_set(key, value)
if not local_cache then
return nil
end
return local_cache:set(key, value, 10 * 60) --10分钟
end
local function get(ip, port, keys)
local tables = {}
local fetchKeys = {}
local resp = nil
local status = STATUS_OK
--如果tables是个map #tables拿不到长度
local has_value = false
--先读取本地缓存
for i, key in ipairs(keys) do
local value = cache_get(key)
if value then
if value == "" then
value = nil
end
tables[key] = value
has_value = true
else
fetchKeys[#fetchKeys + 1] = key
end
end
--如果还有数据没获取 从redis获取
if #fetchKeys > 0 then
if #fetchKeys == 1 then
status, resp = redis_get(ip, port, fetchKeys[1])
else
status, resp = redis_mget(ip, port, fetchKeys)
end
if status == STATUS_OK then
for i = 1, #fetchKeys do
local key = fetchKeys[i]
local value = nil
if #fetchKeys == 1 then
value = resp
else
value = get_data(resp, i)
end
tables[key] = value
has_value = true
cache_set(key, value or "", ttl)
end
end
end
--如果从缓存查到 就认为ok
if has_value and status == STATUS_NOT_FOUND then
status = STATUS_OK
end
return status, tables
end
nginx proxy cache
为了防止恶意刷页面/热点页面访问频繁,我们可以使用nginx proxy_cache做页面缓存,当然更好的选择是使用CDN技术,如通过Apache Traffic Server、Squid、Varnish。
1、nginx.conf配置
proxy_buffering on;
proxy_buffer_size 8k;
proxy_buffers 256 8k;
proxy_busy_buffers_size 64k;
proxy_temp_file_write_size 64k;
proxy_temp_path /usr/servers/nginx/proxy_temp;
#设置Web缓存区名称为cache_one,内存缓存空间大小为200MB,1分钟没有被访问的内容自动清除,硬盘缓存空间大小为30GB。
proxy_cache_path /usr/servers/nginx/proxy_cache levels=1:2 keys_zone=cache_item:200m inactive=1m max_size=30g;
增加proxy_cache的配置,可以通过挂载一块内存作为缓存的存储空间。更多配置规则请参考
http://nginx.org/cn/docs/http/ngx_http_proxy_module.html。
2、nginx_chapter7.conf配置
与server指令配置同级
############ 测试时使用的动态请求
map $host $item_dynamic {
default "0";
item2015.jd.com "1";
}
即如果域名为item2015.jd.com则item_dynamic=1。
location ~ ^/(\d+).html$ {
set $skuId $1;
if ($host !~ "^(item|item2015)\.jd\.com$") {
return 403;
}
expires 3m;
proxy_cache cache_item;
proxy_cache_key $uri;
proxy_cache_bypass $item_dynamic;
proxy_no_cache $item_dynamic;
proxy_cache_valid 200 301 3m;
proxy_cache_use_stale updating error timeout invalid_header http_500 http_502 http_503 http_504;
proxy_pass_request_headers off;
proxy_set_header Host $host;
#支持keep-alive
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_pass http://127.0.0.1/proxy/$skuId.html;
add_header X-Cache '$upstream_cache_status';
}
location ~ ^/proxy/(\d+).html$ {
allow 127.0.0.1;
deny all;
keepalive_timeout 30s;
keepalive_requests 1000;
default_type 'text/html';
charset utf-8;
lua_code_cache on;
set $skuId $1;
content_by_lua_file /usr/chapter7/item.lua;
}
相关推荐
**Flask Web开发实战——Python Web开发的轻量级选择** Flask,作为一个轻量级的Python Web框架,自2010年开源以来,已经在开发者社区中积累了极高的声誉。与Django这样的全功能框架相比,Flask以其灵活性、简单性...
《Go Web编程实战派从入门到精通》是一本专注于Go语言在Web开发领域的专著,其开源的随书源码库名为"Go语言-Web-goWebActualCombat",压缩包内的主要内容是goWebActualCombat-master目录,这代表了一个完整的Go语言...
2. Web技术基础 - IP地址是网络中设备的位置标识,URL是网络资源的定位符,域名则是更容易记忆的网络主机名称。 - Web工作原理基于客户端-服务器模型,浏览器作为客户端发出请求,服务器响应请求并返回网页内容。 ...
Flask web开发实战视频教程+源代码+课件-Python框架。 Flask是一个使用 Python 编写的轻量级 Web 应用框架,扩展性非常良好。 是web端流行框架之一, Flask web开发实战+源代码+课件, 度盘自取 ======== Flask ...
《Java Web开发实战1200例(第2卷)》附带有配套DVD光盘,光盘中提供有书中全部实例的源代码,这些源代码都是经过作者精心调试通过的,保证能够在Windows XP、Windows 2003及Windows 7操作系统下编译和运行。...
Python高效开发实战——Django、Tornado、Flask、Twisted 本书源代码 Python高效开发 实战 也许你听说过全栈工程师,他们善于设计系统架构,精通数据库建模、通用网络协议、后端并发处理、前端界面设计,在学术研究...
第7章 JavaBean 7.1、JavaBean简介 7.2、在JSP中使用JavaBean 7.2.1、WEB开发的标准目录结构 7.2.2、使用JSP的page指令导入所需要的JavaBean 7.2.3、使用指令 7.3、JavaBean与表单 7.4、设置属性: 7.4.1、...
FlaskWeb开发:基于Python的Web应用开发实战
《MLDN——Java Web开发实战经典源码笔记(基础篇)》是一本专注于Java Web开发的实战教程,旨在帮助读者从零开始掌握Web应用的开发技术。这本书通过深入浅出的讲解和丰富的实例代码,全面介绍了Java Web开发的基础...
第7章 JavaBean 7.1、JavaBean简介 7.2、在JSP中使用JavaBean 7.2.1、WEB开发的标准目录结构 7.2.2、使用JSP的page指令导入所需要的JavaBean 7.2.3、使用指令 7.3、JavaBean与表单 7.4、设置属性: 7.4.1、...
Java Web整合开发实战--基于Struts 2+Hibernate+Spring.pdf 1章 Web的工作机制 2章 搭建Java Web开发环境 3章 JSP及其相关技术 2篇 表现层框架Struts技术 4章 Struts快速上手 5章 解密Struts之核心文件 6章 ...
第7章 良好的学习开端——Android基本组件介绍之友好地互动交流——对话框(Dialog)介绍与实例-2 第7章 良好的学习开端——Android基本组件介绍之温馨的提醒——Toast和Notification应用 第8章 移动信息仓库——...
《Go Web编程》这本书主要涵盖了使用Go语言进行Web开发的各种核心概念和技术。Go语言,也称为Golang,是由Google开发的一种静态类型、编译型、并发型且具有垃圾回收功能的编程语言,特别适合构建高性能的Web服务。...
这份文档名为《Java EE 框架整合开发入门到实战——Spring+Spring MVC+MyBatis(微课版)课后习题答案.pdf》,它显然是关于Java EE中流行的三个框架整合使用的教程。这三个框架分别是Spring、Spring MVC和MyBatis,...
**Delphi Web前端开发教程——基于TMS WEB Core框架** Delphi Web前端开发教程是针对想要使用TMS WEB Core框架创建Web应用程序的开发者,无论你是初学者还是高级开发者,本教程都提供了丰富的学习资源。TMS WEB ...
本书紧密结合互联网行业发展... 第7章CSS+DIV基础; 第8章CSS+DIV高级应用; 第9章表格; 第10章框架; 第11章表单; 第12章JavaScript基础; 第13章JavaScript事件分析; 第14章DOM和BOM; 第15章课程网站构建与发布。
《Java开发实战1200例(第1卷)》以开发人员在项目开发中经常遇到的问题和必须掌握的技术为中心,介绍了应用Java进行桌面程序开发各个方面的知识和技巧,主要包括Java语法与面向对象技术、Java高级应用、窗体与控件...
在本项目"web实战——包含完整购物车的web项目"中,我们将会探索一个基于C/S(客户端/服务器)架构的网上书店应用的实现。这个项目涵盖了多种关键的Web开发技术,包括HTML、CSS、JavaScript以及AJAX。下面将详细讨论...
在这个实战教程中,作者李刚聚焦于Struts 2、Spring和Hibernate这三个核心组件的整合,它们是Java EE领域中非常流行的开源框架,用于构建现代化的Web应用程序。 Struts 2是一个强大的MVC(模型-视图-控制器)框架,...