Authored by 周少峰

Merge branch 'feature/tdk'

@@ -3,4 +3,4 @@ @@ -3,4 +3,4 @@
3 "parserOptions": { 3 "parserOptions": {
4 "sourceType": "module" 4 "sourceType": "module"
5 } 5 }
6 -} 6 +}
@@ -35,6 +35,10 @@ const pkg = require('./package.json'); @@ -35,6 +35,10 @@ const pkg = require('./package.json');
35 const app = express(); 35 const app = express();
36 const helpers = global.yoho.helpers; 36 const helpers = global.yoho.helpers;
37 37
  38 +// tdk
  39 +global.yoho.redis = require('./doraemon/middleware/redis');
  40 +const tdk = require('./utils/getTDK');
  41 +
38 // NOTE: 这里修改了图片质量的参数 42 // NOTE: 这里修改了图片质量的参数
39 helpers.image = _.flow(helpers.image, fp.replace(/\/quality\/\d*$/, '/quality/90')); 43 helpers.image = _.flow(helpers.image, fp.replace(/\/quality\/\d*$/, '/quality/90'));
40 44
@@ -163,6 +167,24 @@ app.use((req, res, next) => { @@ -163,6 +167,24 @@ app.use((req, res, next) => {
163 next(); 167 next();
164 }); 168 });
165 169
  170 +// redis seo
  171 +app.use((req, res, next) => {
  172 + if (!req.xhr) {
  173 + tdk('url', `${req.hostname}${req.originalUrl}`, req).then(TDKObj =>{
  174 + if (TDKObj[0]) {
  175 + req.tdk = {
  176 + title: TDKObj[1],
  177 + keywords: TDKObj[2],
  178 + description: TDKObj[3]
  179 + };
  180 + }
  181 + next();
  182 + });
  183 + } else {
  184 + return next();
  185 + }
  186 +});
  187 +
166 const logger = global.yoho.logger; 188 const logger = global.yoho.logger;
167 189
168 // dispatcher 190 // dispatcher
@@ -13,6 +13,7 @@ const headerModel = require('../../../doraemon/models/header'); @@ -13,6 +13,7 @@ const headerModel = require('../../../doraemon/models/header');
13 const ghelper = require('../models/guang-helper'); 13 const ghelper = require('../models/guang-helper');
14 const urlHelper = require('../models/url-helper'); 14 const urlHelper = require('../models/url-helper');
15 const querystring = require('querystring'); 15 const querystring = require('querystring');
  16 +const tdk = require('../../../utils/getTDK');
16 17
17 /** 18 /**
18 * 首页文章列表 类型列表 19 * 首页文章列表 类型列表
@@ -252,7 +253,8 @@ exports.detail = (req, res, next) => { @@ -252,7 +253,8 @@ exports.detail = (req, res, next) => {
252 reqCtx.getArticleBaseInfo(id, uid, udid), 253 reqCtx.getArticleBaseInfo(id, uid, udid),
253 reqCtx.getArticleRelateBrand(id), 254 reqCtx.getArticleRelateBrand(id),
254 reqCtx.getRecoArticles(gender, 1, 10, channel), 255 reqCtx.getRecoArticles(gender, 1, 10, channel),
255 - reqCtx.getAds(channel, isAdDegrade) 256 + reqCtx.getAds(channel, isAdDegrade),
  257 + tdk('article', id, req)
256 ]; 258 ];
257 259
258 if (info.authorId) { 260 if (info.authorId) {
@@ -275,6 +277,24 @@ exports.detail = (req, res, next) => { @@ -275,6 +277,24 @@ exports.detail = (req, res, next) => {
275 res.set('Cache-Control', 'no-cache'); 277 res.set('Cache-Control', 'no-cache');
276 } 278 }
277 279
  280 + let title, keywords, description;
  281 +
  282 + if (ret[8][0]) {
  283 + req.tdk = {
  284 + title: ret[8][1],
  285 + keywords: ret[8][2],
  286 + description: ret[8][3]
  287 + };
  288 + }
  289 +
  290 + title = `${info.title} | YOHO!BUY有货`;
  291 + keywords = info.tag.length > 0 ? `${info.tag}` :
  292 + ['Yoho! 有货,潮流,时尚,流行,购物,B2C,正品,购物网站,网上购物,货到付款,品牌服饰,男士护肤,',
  293 + '黑框眼镜,匡威,板鞋,i.t,izzue,5cm,eastpak,vans,lylescott,g-shock,new balance,lacoste,melissa,',
  294 + 'casio,卡西欧手表,舒雅,jasonwood,odm,AAAA,香港购物,日本潮流'].join('');
  295 + description = info.desc.length > 0 ? `${info.desc}` :
  296 + '潮流商品搜索,上衣,衬衫,TEE,卫衣,冲锋衣,风衣,羽绒服,裤子,休闲鞋,板鞋,配饰,复古眼镜';
  297 +
278 res.render('guang/detail', Object.assign({ 298 res.render('guang/detail', Object.assign({
279 module: 'guang', 299 module: 'guang',
280 page: 'detail', 300 page: 'detail',
@@ -305,13 +325,9 @@ exports.detail = (req, res, next) => { @@ -305,13 +325,9 @@ exports.detail = (req, res, next) => {
305 commentFirstPageUrl: '?pageSize=10' 325 commentFirstPageUrl: '?pageSize=10'
306 } 326 }
307 }, { 327 }, {
308 - title: `${info.title} | YOHO!BUY有货`,  
309 - keywords: info.tag.length > 0 ? `${info.tag}` :  
310 - ['Yoho! 有货,潮流,时尚,流行,购物,B2C,正品,购物网站,网上购物,货到付款,品牌服饰,男士护肤,',  
311 - '黑框眼镜,匡威,板鞋,i.t,izzue,5cm,eastpak,vans,lylescott,g-shock,new balance,lacoste,melissa,',  
312 - 'casio,卡西欧手表,舒雅,jasonwood,odm,AAAA,香港购物,日本潮流'].join(''),  
313 - description: info.desc.length > 0 ? `${info.desc}` :  
314 - '潮流商品搜索,上衣,衬衫,TEE,卫衣,冲锋衣,风衣,羽绒服,裤子,休闲鞋,板鞋,配饰,复古眼镜', 328 + title: title,
  329 + keywords: keywords,
  330 + description: description,
315 webNavHeader: channel 331 webNavHeader: channel
316 })); 332 }));
317 }); 333 });
@@ -31,7 +31,7 @@ const showMain = (req, res, next) => { @@ -31,7 +31,7 @@ const showMain = (req, res, next) => {
31 saveInCookies: null 31 saveInCookies: null
32 }; 32 };
33 33
34 - return service.showMainAsync(Object.assign({ 34 + return service.showMainAsync(req, Object.assign({
35 skn: skn, 35 skn: skn,
36 channel: channel, 36 channel: channel,
37 gender: gender 37 gender: gender
@@ -9,6 +9,7 @@ const list = require(`${mRoot}/list`); @@ -9,6 +9,7 @@ const list = require(`${mRoot}/list`);
9 const listSeoMap = require(`${global.middleware}/seo/listSeoMap`); 9 const listSeoMap = require(`${global.middleware}/seo/listSeoMap`);
10 const helpers = global.yoho.helpers; 10 const helpers = global.yoho.helpers;
11 const _ = require('lodash'); 11 const _ = require('lodash');
  12 +const tdk = require('../../../utils/getTDK');
12 13
13 // 搜索相关接口 14 // 搜索相关接口
14 const searchApi = require(`${mRoot}/search-api`); 15 const searchApi = require(`${mRoot}/search-api`);
@@ -22,58 +23,71 @@ const shop = (shopId, req, res, next, brandInfo) => { @@ -22,58 +23,71 @@ const shop = (shopId, req, res, next, brandInfo) => {
22 shopId = parseInt(shopId, 10); 23 shopId = parseInt(shopId, 10);
23 Object.assign(params, {shopId: shopId}); 24 Object.assign(params, {shopId: shopId});
24 25
25 - list.getShopInfo(shopId, req.user.uid).then(shopInfo => {  
26 - let pjax = params._pjax;  
27 -  
28 - // 获取不到店铺信息跳转至首页  
29 - if (!shopInfo || _.isEmpty(shopInfo)) {  
30 - return res.redirect(helpers.urlFormat('', null, ''));  
31 - }  
32 -  
33 - // 比较品牌域名与店铺域名是否一致,不一致跳转至店铺域名  
34 - if (!pjax && shopInfo.domain && domain && domain !== _.toLower(shopInfo.domain)) {  
35 - res.redirect(helpers.urlFormat('', params, shopInfo.domain));  
36 - return; 26 + tdk('shop', shopId, req).then(TDKObj => {
  27 + if (TDKObj[0]) {
  28 + req.tdk = {
  29 + title: TDKObj[1],
  30 + keywords: TDKObj[2],
  31 + description: TDKObj[3]
  32 + };
37 } 33 }
  34 + return list.getShopInfo(shopId, req.user.uid).then(shopInfo => {
  35 + let pjax = params._pjax;
38 36
39 - if (+shopInfo.shopTemplateType === 2) { // 经典模板 37 + // 获取不到店铺信息跳转至首页
  38 + if (!shopInfo || _.isEmpty(shopInfo)) {
  39 + return res.redirect(helpers.urlFormat('', null, ''));
  40 + }
40 41
41 - if (pjax) {  
42 - list.getShopGoodsData(shopId, req.yoho.channel, params, shopInfo).then(result => {  
43 - Object.assign(result, {  
44 - shopId: shopId,  
45 - layout: false  
46 - });  
47 - res.render('list/goods-list', result);  
48 - }).catch(next); 42 + // 比较品牌域名与店铺域名是否一致,不一致跳转至店铺域名
  43 + if (!pjax && shopInfo.domain && domain && domain !== _.toLower(shopInfo.domain)) {
  44 + res.redirect(helpers.urlFormat('', params, shopInfo.domain));
49 return; 45 return;
50 } 46 }
51 47
52 - list.getShopData(shopId, req.yoho.channel, params, shopInfo).then(result => {  
53 - Object.assign(result, {  
54 - page: 'shop',  
55 - shopId: shopId  
56 - });  
57 -  
58 - // 店铺装修为空则不cache  
59 - if (!result.shopTopBanner) {  
60 - res.set('Cache-Control', 'no-cache'); 48 + if (+shopInfo.shopTemplateType === 2) { // 经典模板
  49 +
  50 + if (pjax) {
  51 + list.getShopGoodsData(shopId, req.yoho.channel, params, shopInfo).then(result => {
  52 + Object.assign(result, {
  53 + shopId: shopId,
  54 + layout: false
  55 + });
  56 + res.render('list/goods-list', result);
  57 + }).catch(next);
  58 + return;
61 } 59 }
62 - res.render('list/shop-index', result);  
63 - }).catch(next);  
64 - } else { // 基础模板  
65 - list.getBaseShopData(params, Object.assign({uid: req.user.uid}, brandInfo),  
66 - req.yoho.channel, shopId).then(result => {  
67 - Object.assign(result, {page: 'list'});  
68 -  
69 - // 基础店铺装修为空则不cache  
70 - if (!result.brand || !result.brand.shopBanner) { 60 +
  61 + list.getShopData(shopId, req.yoho.channel, params, shopInfo).then(result => {
  62 + Object.assign(result, {
  63 + page: 'shop',
  64 + shopId: shopId
  65 + });
  66 +
  67 + // 店铺装修为空则不cache
  68 + if (!result.shopTopBanner) {
71 res.set('Cache-Control', 'no-cache'); 69 res.set('Cache-Control', 'no-cache');
72 } 70 }
73 - res.render('list/brand', result); 71 +
  72 + res.render('list/shop-index', result);
74 }).catch(next); 73 }).catch(next);
75 - } 74 + } else { // 基础模板
  75 + list.getBaseShopData(params, Object.assign({uid: req.user.uid}, brandInfo),
  76 + req.yoho.channel, shopId).then(result => {
  77 + Object.assign(result, {page: 'list'});
  78 +
  79 + // 基础店铺装修为空则不cache
  80 + if (!result.brand || !result.brand.shopBanner) {
  81 + res.set('Cache-Control', 'no-cache');
  82 + }
  83 +
  84 + res.render('list/brand', result);
  85 + }).catch(next);
  86 + }
  87 + });
76 }).catch(next); 88 }).catch(next);
  89 +
  90 +
77 }; 91 };
78 92
79 /** 93 /**
@@ -35,6 +35,7 @@ const HeaderModel = require('../../../doraemon/models/header'); @@ -35,6 +35,7 @@ const HeaderModel = require('../../../doraemon/models/header');
35 const BLANK_STR = ' '; 35 const BLANK_STR = ' ';
36 const BUNDLE_PRODUCE = 2; // 量贩 36 const BUNDLE_PRODUCE = 2; // 量贩
37 const BUNDLE_PACKAGE = 1; // 套餐 37 const BUNDLE_PACKAGE = 1; // 套餐
  38 +const tdk = require('../../../utils/getTDK');
38 39
39 // 展览票 40 // 展览票
40 const YOHOOD_TICKET = 51335912; 41 const YOHOOD_TICKET = 51335912;
@@ -1514,7 +1515,7 @@ const _removeSalePrice = (productInfo) => { @@ -1514,7 +1515,7 @@ const _removeSalePrice = (productInfo) => {
1514 /** 1515 /**
1515 * 获取某一个商品详情主页面 1516 * 获取某一个商品详情主页面
1516 */ 1517 */
1517 -const showMainAsync = (data) => { 1518 +const showMainAsync = (req, data) => {
1518 return co(function * () { 1519 return co(function * () {
1519 // 获取商品基本信息 1520 // 获取商品基本信息
1520 let productData = yield productAPI.getProductAsync({skn: data.skn}, data.uid, data.isStudent, data.vipLevel); 1521 let productData = yield productAPI.getProductAsync({skn: data.skn}, data.uid, data.isStudent, data.vipLevel);
@@ -1537,7 +1538,8 @@ const showMainAsync = (data) => { @@ -1537,7 +1538,8 @@ const showMainAsync = (data) => {
1537 _getSortNavAsync(smallSortId, data.gender), // 面包屑导航 1538 _getSortNavAsync(smallSortId, data.gender), // 面包屑导航
1538 HeaderModel.requestHeaderData(data.channel), // 通用头部数据 1539 HeaderModel.requestHeaderData(data.channel), // 通用头部数据
1539 _getProductIntroAsync(productId, productSkn), // 商品详细介绍 1540 _getProductIntroAsync(productId, productSkn), // 商品详细介绍
1540 - curUserProduct(productData) // 商品详细价格 1541 + curUserProduct(productData), // 商品详细价格
  1542 + tdk('skn', data.skn, req) // seo
1541 ]); 1543 ]);
1542 1544
1543 let smallSortNavigator = requestData[0]; 1545 let smallSortNavigator = requestData[0];
@@ -1545,6 +1547,14 @@ const showMainAsync = (data) => { @@ -1545,6 +1547,14 @@ const showMainAsync = (data) => {
1545 let productDescription = requestData[2]; 1547 let productDescription = requestData[2];
1546 let productInfo = requestData[3]; 1548 let productInfo = requestData[3];
1547 1549
  1550 + if (requestData[4][0]) {
  1551 + req.tdk = {
  1552 + title: requestData[4][1],
  1553 + keywords: requestData[4][2],
  1554 + description: requestData[4][3]
  1555 + };
  1556 + }
  1557 +
1548 // 拼装数据 1558 // 拼装数据
1549 let result = {}; 1559 let result = {};
1550 1560
@@ -17,18 +17,18 @@ module.exports = { @@ -17,18 +17,18 @@ module.exports = {
17 cookieDomain: '.yohobuy.com', 17 cookieDomain: '.yohobuy.com',
18 domains: { 18 domains: {
19 // test3 19 // test3
20 - singleApi: 'http://api-test3.yohops.com:9999/',  
21 - api: 'http://api-test3.yohops.com:9999/',  
22 - service: 'http://service-test3.yohops.com:9999/',  
23 - serviceNotify: 'http://service-test3.yohops.com:9999/',  
24 - global: 'http://global-test-soa.yohops.com:9999/', 20 + // singleApi: 'http://api-test3.yohops.com:9999/',
  21 + // api: 'http://api-test3.yohops.com:9999/',
  22 + // service: 'http://service-test3.yohops.com:9999/',
  23 + // serviceNotify: 'http://service-test3.yohops.com:9999/',
  24 + // global: 'http://global-test-soa.yohops.com:9999/',
25 25
26 // prod 26 // prod
27 - // singleApi: 'http://single.yoho.cn/',  
28 - // api: 'http://api.yoho.cn/',  
29 - // service: 'http://service.yoho.cn/',  
30 - // serviceNotify: 'http://service.yoho.cn/',  
31 - // global: 'http://api-global.yohobuy.com/', 27 + singleApi: 'http://single.yoho.cn/',
  28 + api: 'http://api.yoho.cn/',
  29 + service: 'http://service.yoho.cn/',
  30 + serviceNotify: 'http://service.yoho.cn/',
  31 + global: 'http://api-global.yohobuy.com/',
32 32
33 // gray 33 // gray
34 // singleApi: 'http://single.gray.yohops.com/', 34 // singleApi: 'http://single.gray.yohops.com/',
@@ -131,7 +131,26 @@ module.exports = { @@ -131,7 +131,26 @@ module.exports = {
131 }, 131 },
132 zookeeperServer: '192.168.102.168:2188', 132 zookeeperServer: '192.168.102.168:2188',
133 maxQps: 1200, 133 maxQps: 1200,
134 - sessionMemcachedPrefix: 'yohobuy_session:' 134 + sessionMemcachedPrefix: 'yohobuy_session:',
  135 + redis: {
  136 + connect: {
  137 + host: '127.0.0.1',
  138 + port: '6379',
  139 + retry_strategy(options) {
  140 + if (options.error && options.error.code === 'ECONNREFUSED') {
  141 + console.log('redis连接不成功');
  142 + }
  143 + if (options.total_retry_time > 1000 * 60 * 60 * 6) {
  144 + console.log('redis连接超时');
  145 + return;
  146 + }
  147 + if (options.attempt > 10) {
  148 + return 1000 * 60 * 60 * 0.5;
  149 + }
  150 + return Math.min(options.attempt * 100, 1000);
  151 + }
  152 + }
  153 + }
135 }; 154 };
136 155
137 if (isProduction) { 156 if (isProduction) {
@@ -165,7 +184,26 @@ if (isProduction) { @@ -165,7 +184,26 @@ if (isProduction) {
165 open: false, 184 open: false,
166 url: 'http://123.206.2.55/strategy' 185 url: 'http://123.206.2.55/strategy'
167 }, 186 },
168 - zookeeperServer: 'web.zookeeper.yohoops.org:2181' 187 + zookeeperServer: 'web.zookeeper.yohoops.org:2181',
  188 + redis: {
  189 + connect: {
  190 + host: 'web.redis.yohoops.org'
  191 + },
  192 + port: '6379',
  193 + retry_strategy(options) {
  194 + if (options.error && options.error.code === 'ECONNREFUSED') {
  195 + console.log('redis连接不成功');
  196 + }
  197 + if (options.total_retry_time > 1000 * 60 * 60 * 6) {
  198 + console.log('redis连接超时');
  199 + return;
  200 + }
  201 + if (options.attempt > 10) {
  202 + return 1000 * 60 * 60 * 0.5;
  203 + }
  204 + return Math.min(options.attempt * 100, 1000);
  205 + }
  206 + }
169 }); 207 });
170 } else if (isTest) { 208 } else if (isTest) {
171 Object.assign(module.exports, { 209 Object.assign(module.exports, {
@@ -74,7 +74,6 @@ module.exports = [ @@ -74,7 +74,6 @@ module.exports = [
74 { 74 {
75 type: TYPE.rewrite, 75 type: TYPE.rewrite,
76 origin: (req) => { 76 origin: (req) => {
77 - console.log(req.path);  
78 return req.path === '/erp2goods'; 77 return req.path === '/erp2goods';
79 }, 78 },
80 target: '/common/erp2goods' 79 target: '/common/erp2goods'
  1 +const redis = require('redis');
  2 +const bluebird = require('bluebird');
  3 +const config = require('../../config/common');
  4 +let client;
  5 +
  6 +try {
  7 + client = redis.createClient(config.redis.connect);
  8 +
  9 + bluebird.promisifyAll(redis.RedisClient.prototype);
  10 + bluebird.promisifyAll(redis.Multi.prototype);
  11 +
  12 + client.on('error', function() {
  13 + global.yoho.redis = '';
  14 + });
  15 +
  16 + client.on('connect', function() {
  17 + global.yoho.redis = client;
  18 + });
  19 +} catch (e) {
  20 + global.yoho.redis = '';
  21 +}
  22 +
  23 +
  24 +
  25 +module.exports = client;
@@ -44,15 +44,15 @@ @@ -44,15 +44,15 @@
44 "passport-sina": "^0.1.0", 44 "passport-sina": "^0.1.0",
45 "passport-strategy": "1.x.x", 45 "passport-strategy": "1.x.x",
46 "passport-weixin": "^0.1.0", 46 "passport-weixin": "^0.1.0",
  47 + "redis": "^2.7.1",
47 "request": "^2.81.0", 48 "request": "^2.81.0",
48 "request-ip": "^1.2.2", 49 "request-ip": "^1.2.2",
49 "request-promise": "^3.0.0", 50 "request-promise": "^3.0.0",
50 "serve-favicon": "^2.3.0", 51 "serve-favicon": "^2.3.0",
51 - "sitemap": "^1.12.0",  
52 "urlencode": "^1.1.0", 52 "urlencode": "^1.1.0",
53 "uuid": "^2.0.2", 53 "uuid": "^2.0.2",
54 "yoho-express-session": "^2.0.0", 54 "yoho-express-session": "^2.0.0",
55 - "yoho-node-lib": "^0.2.23", 55 + "yoho-node-lib": "^0.2.28",
56 "yoho-zookeeper": "^1.0.8" 56 "yoho-zookeeper": "^1.0.8"
57 }, 57 },
58 "devDependencies": { 58 "devDependencies": {
  1 +const md5 = require('md5');
  2 +const redis = global.yoho.redis;
  3 +const _ = require('lodash');
  4 +
  5 +module.exports = (type, query, req) => {
  6 + query = type === 'url' ? md5(query) : query;
  7 +
  8 + if (redis && _.get(req.app.locals.pc, 'ci.tdk', false)) {
  9 + let arr = [];
  10 +
  11 + arr.push(redis.multi([
  12 + ['exists', `tdk:${type}:${query}`],
  13 + ['hmget', `tdk:${type}:${query}`, 'key', 'title', 'keywords', 'description']
  14 + ]).execAsync());
  15 +
  16 + arr.push(new Promise((resolve)=>{
  17 + setTimeout(resolve, 500, []);
  18 + }));
  19 +
  20 + return Promise.race(arr).then(function(res) {
  21 + if (res.length) {
  22 + return res[1];
  23 + } else {
  24 + return [];
  25 + }
  26 + });
  27 + } else {
  28 + return Promise.resolve([]);
  29 + }
  30 +
  31 +};