diff --git a/.eslintrc b/.eslintrc index 5a39e4a..1d43459 100644 --- a/.eslintrc +++ b/.eslintrc @@ -10,4 +10,4 @@ "camelcase": "off" } -} +} \ No newline at end of file diff --git a/app.js b/app.js index 49b338d..86ef08e 100644 --- a/app.js +++ b/app.js @@ -43,6 +43,9 @@ yohoLib.global(config); const logger = global.yoho.logger; +// tdk +global.yoho.redis = require('./doraemon/middleware/redis'); + // zookeeper if (config.zookeeperServer) { require('yoho-zookeeper')(config.zookeeperServer, 'wap', app.locals.wap = {}, global.yoho.cache); @@ -176,8 +179,11 @@ app.use((req, res, next) => { // 添加请求上下文 app.use(global.yoho.httpCtx()); +// redis seo + // dispatcher try { + const tdkUrl = require('./doraemon/middleware/redis-url'); const user = require('./doraemon/middleware/user'); const reqParamsFilter = require('./doraemon/middleware/req-params-filter'); const urlRewrite = require('./doraemon/middleware/url-rewrite'); @@ -192,6 +198,7 @@ try { const pageCache = require('./doraemon/middleware/page-cache'); // YOHO 前置中间件 + app.use(tdkUrl()); app.use(reqParamsFilter()); app.use(urlRewrite()); app.use(subDomain()); diff --git a/apps/guang/controllers/detail.js b/apps/guang/controllers/detail.js index fe1d1be..019fb5c 100644 --- a/apps/guang/controllers/detail.js +++ b/apps/guang/controllers/detail.js @@ -14,6 +14,7 @@ const stringProcess = require(`${global.utils}/string-process`); const guangProcess = require(`${global.utils}/guang-process`); const headerModel = require('../../../doraemon/models/header'); // 头部model const aboutModel = require('../../../doraemon/models/about'); +const tdk = require('../../../utils/getTDK'); const qs = require('querystring'); const channels = { boys: 1, @@ -167,6 +168,15 @@ const index = (req, res, next) => { isShare = isWeixin || isqq || isWeibo ? true : false; co(function* () { + let ret = yield tdk('article', id, req); + + if (ret[0]) { + req.tdk = { + title: ret[1], + keywords: ret[2], + description: ret[3] + }; + } let detail = yield req.ctx(DetailModel).packageData(id, isApp, isWeixin, channel, isShare); let commentsTotal = yield req.ctx(DetailModel).commentsTotal({article_id: id, udid: udid}); let praise = yield req.ctx(DetailModel).getArticlePraiseAndFavor({ diff --git a/apps/product/controllers/new-detail.js b/apps/product/controllers/new-detail.js index 44e1a66..4d99eee 100644 --- a/apps/product/controllers/new-detail.js +++ b/apps/product/controllers/new-detail.js @@ -9,6 +9,7 @@ const newDetailModel = require('../models/new-detail'); const listModel = require('../models/list'); const headerModel = require('../../../doraemon/models/header'); // 头部model const qs = require('querystring'); +const tdk = require('../../../utils/getTDK'); const helpers = global.yoho.helpers; const newDetail = { @@ -32,11 +33,21 @@ const newDetail = { navTitle: '商品详情' }); - newDetailModel.getProductData({ - id: id, - goodsId: goodsId, - productSkn: productSkn, - ua: req.get('user-agent') || '' + tdk('skn', productSkn, req).then(ret => { + if (ret[0]) { + req.tdk = { + title: ret[1], + keywords: ret[2], + description: ret[3] + }; + } + + return newDetailModel.getProductData({ + id: id, + goodsId: goodsId, + productSkn: productSkn, + ua: req.get('user-agent') || '' + }); }).then((result) => { if (_.isEmpty(result)) { return next(); diff --git a/apps/product/controllers/new-shop.js b/apps/product/controllers/new-shop.js index 862c4fe..741c32e 100644 --- a/apps/product/controllers/new-shop.js +++ b/apps/product/controllers/new-shop.js @@ -15,6 +15,7 @@ const helpers = global.yoho.helpers; const productProcess = require(`${utils}/product-process`); const redShopPrcs = require(`${utils}/redshop-process`); const co = require('bluebird').coroutine; +const tdk = require('../../../utils/getTDK'); /** * 从 useragent 获取 uid @@ -61,101 +62,114 @@ const shop = { brandLogo = result; - title = brandLogo.name; + return co(function*() { + title = brandLogo.name; - if (brandLogo && brandLogo.id) { - brandId = brandLogo.id; - } + if (brandLogo && brandLogo.id) { + brandId = brandLogo.id; + } + if (brandLogo && brandLogo.shopId) { + let TDKObj = yield tdk('shop', brandLogo.shopId, req); + + if (TDKObj[0]) { + req.tdk = { + title: TDKObj[1], + keywords: TDKObj[2], + description: TDKObj[3] + }; + } + } - params.brand = brandId; + params.brand = brandId; - // 唤起 APP 的路径 - res.locals.appPath = `yohobuy://yohobuy.com/goapp?openby:yohobuy={"action":"go.brand","params":{"brand_id":"${brandId}"}}`; + // 唤起 APP 的路径 + res.locals.appPath = `yohobuy://yohobuy.com/goapp?openby:yohobuy={"action":"go.brand","params":{"brand_id":"${brandId}"}}`; - let searchParam = Object.assign({ - isApp: req.yoho.isApp, - brand: brandId, - type: 'default', - order: '0', - page: 1, - limit: 4, - }, params); + let searchParam = Object.assign({ + isApp: req.yoho.isApp, + brand: brandId, + type: 'default', + order: '0', + page: 1, + limit: 4, + }, params); - if (uid) { - searchParam.uid = uid; - } + if (uid) { + searchParam.uid = uid; + } - if (req.query.from !== 'search' && brandLogo.type === '2' && brandLogo.shopId) { - req.query.shop_id = brandLogo.shopId; - shop.shop(req, res, next); - return false; - } else if (req.query.from === 'search') { - return Promise.all([ - listModel.getBrandShops(brandLogo.brandDomain, req), - searchModel.getSearchData(searchParam) - ]).then(shopResult => { - let brandShop = shopResult[0]; - let newGoods = shopResult[1]; - - params.newGoods = newGoods.list || []; - params.suggestion = newGoods.suggestion || []; - - // 推荐词条件判断 redmine: 18567 - if (params.suggestion && params.suggestion.isNeedSuggestion && - params.suggestion.termsSuggestion.length) { - params.newquery = params.suggestion.termsSuggestion[0].name; - } + if (req.query.from !== 'search' && brandLogo.type === '2' && brandLogo.shopId) { + req.query.shop_id = brandLogo.shopId; + shop.shop(req, res, next); + return false; + } else if (req.query.from === 'search') { + return Promise.all([ + listModel.getBrandShops(brandLogo.brandDomain, req), + searchModel.getSearchData(searchParam) + ]).then(shopResult => { + let brandShop = shopResult[0]; + let newGoods = shopResult[1]; - if (brandId === 0) { - params.query = domain; - } + params.newGoods = newGoods.list || []; + params.suggestion = newGoods.suggestion || []; - if (req.query.app_type && parseInt(req.query.app_type, 10) === 1) { - shopEnter = false; - } else { - shopEnter = true; - } + // 推荐词条件判断 redmine: 18567 + if (params.suggestion && params.suggestion.isNeedSuggestion && + params.suggestion.termsSuggestion.length) { + params.newquery = params.suggestion.termsSuggestion[0].name; + } + + if (brandId === 0) { + params.query = domain; + } - if (brandShop.length > 0 || brandLogo && shopEnter) { + if (req.query.app_type && parseInt(req.query.app_type, 10) === 1) { + shopEnter = false; + } else { + shopEnter = true; + } - params = _.assign({ - brandWay: _.isEmpty(brandShop) ? brandLogo : brandShop, - search: { - default: req.query.query || req.query.domain, - url: helpers.urlFormat('', null, 'search') - } - }, params); - } + if (brandShop.length > 0 || brandLogo && shopEnter) { - return true; - }); - } else { - params.brandHome = { - title: result.name, - id: result.id - }; - return Promise.all([ - listModel.getBrandBanner(brandId), - searchModel.getSearchData(searchParam) - ]).then(brandResult => { - let brandBanner = brandResult[0]; - let newGoods = brandResult[1]; - - if (!brandBanner || !newGoods) { - res.set('Cache-Control', 'no-cache'); - } + params = _.assign({ + brandWay: _.isEmpty(brandShop) ? brandLogo : brandShop, + search: { + default: req.query.query || req.query.domain, + url: helpers.urlFormat('', null, 'search') + } + }, params); + } - // 品牌没有 Banner 整个头部不显示 - if (!brandBanner) { - params.brandHome = null; - } else { - params.brandHome.banner = brandBanner; - } + return true; + }); + } else { + params.brandHome = { + title: result.name, + id: result.id + }; + return Promise.all([ + listModel.getBrandBanner(brandId), + searchModel.getSearchData(searchParam) + ]).then(brandResult => { + let brandBanner = brandResult[0]; + let newGoods = brandResult[1]; - params.newGoods = newGoods.list; - return true; - }); - } + if (!brandBanner || !newGoods) { + res.set('Cache-Control', 'no-cache'); + } + + // 品牌没有 Banner 整个头部不显示 + if (!brandBanner) { + params.brandHome = null; + } else { + params.brandHome.banner = brandBanner; + } + + params.newGoods = newGoods.list; + return true; + }); + } + })(); }).then((isBrand) => { if (isBrand) { params.isBrand = isBrand; diff --git a/apps/product/controllers/seckill-detail.js b/apps/product/controllers/seckill-detail.js index 384900d..0355db9 100644 --- a/apps/product/controllers/seckill-detail.js +++ b/apps/product/controllers/seckill-detail.js @@ -9,6 +9,7 @@ const seckillDetailModel = require('../models/seckill-detail'); const newDetailModel = require('../models/new-detail'); const listModel = require('../models/list'); const headerModel = require('../../../doraemon/models/header'); // 头部model +const tdk = require('../../../utils/getTDK'); const helpers = global.yoho.helpers; const newDetail = { @@ -30,11 +31,20 @@ const newDetail = { navTitle: '商品详情' }); - newDetailModel.getProductData({ - id: id, - goodsId: goodsId, - productSkn: productSkn, - ua: req.get('user-agent') || '' + tdk('skn', productSkn, req).then(ret => { + if (ret[0]) { + req.tdk = { + title: ret[1], + keywords: ret[2], + description: ret[3] + }; + } + return newDetailModel.getProductData({ + id: id, + goodsId: goodsId, + productSkn: productSkn, + ua: req.get('user-agent') || '' + }); }).then((result) => { if (_.isEmpty(result)) { return next(); diff --git a/config/common.js b/config/common.js index 5415028..4952d56 100644 --- a/config/common.js +++ b/config/common.js @@ -100,7 +100,26 @@ module.exports = { }, maxQps: 1200, geetestJs: '//static.geetest.com/static/tools/gt.js', - jsSdk: '//cdn.yoho.cn/js-sdk/1.2.2/jssdk.js' + jsSdk: '//cdn.yoho.cn/js-sdk/1.2.2/jssdk.js', + redis: { + connect: { + host: '127.0.0.1', + port: '6379', + retry_strategy(options) { + if (options.error && options.error.code === 'ECONNREFUSED') { + console.log('redis连接不成功'); + } + if (options.total_retry_time > 1000 * 60 * 60 * 6) { + console.log('redis连接超时'); + return; + } + if (options.attempt > 10) { + return 1000 * 60 * 60 * 0.5; + } + return Math.min(options.attempt * 100, 1000); + } + } + } }; if (isProduction) { @@ -152,6 +171,25 @@ if (isProduction) { key: '7e6f3307b64cc87c79c472814b88f7fb', appSecret: 'ce21ae4a3f93852279175a167e54509b', notifyUrl: 'http://service.yoho.cn/payment/weixin_notify', + }, + redis: { + connect: { + host: 'web.redis.yohoops.org' + }, + port: '6379', + retry_strategy(options) { + if (options.error && options.error.code === 'ECONNREFUSED') { + console.log('redis连接不成功'); + } + if (options.total_retry_time > 1000 * 60 * 60 * 6) { + console.log('redis连接超时'); + return; + } + if (options.attempt > 10) { + return 1000 * 60 * 60 * 0.5; + } + return Math.min(options.attempt * 100, 1000); + } } }); } else if (isTest) { diff --git a/doraemon/middleware/redis-url.js b/doraemon/middleware/redis-url.js new file mode 100644 index 0000000..ff67f50 --- /dev/null +++ b/doraemon/middleware/redis-url.js @@ -0,0 +1,21 @@ +'use strict'; +const tdk = require('../../utils/getTDK'); + +module.exports = () => { + return (req, res, next) => { + if (!req.xhr) { + tdk('url', `${req.hostname}${req.originalUrl}`, req).then(TDKObj =>{ + if (TDKObj[0]) { + req.tdk = { + title: TDKObj[1], + keywords: TDKObj[2], + description: TDKObj[3] + }; + } + next(); + }); + } else { + return next(); + } + }; +}; diff --git a/doraemon/middleware/redis.js b/doraemon/middleware/redis.js new file mode 100644 index 0000000..ffd5879 --- /dev/null +++ b/doraemon/middleware/redis.js @@ -0,0 +1,25 @@ +const redis = require('redis'); +const bluebird = require('bluebird'); +const config = require('../../config/common'); +let client; + +try { + client = redis.createClient(config.redis.connect); + + bluebird.promisifyAll(redis.RedisClient.prototype); + bluebird.promisifyAll(redis.Multi.prototype); + + client.on('error', function() { + global.yoho.redis = ''; + }); + + client.on('connect', function() { + global.yoho.redis = client; + }); +} catch (e) { + global.yoho.redis = ''; +} + + + +module.exports = client; diff --git a/package.json b/package.json index 53bb08f..953838e 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "passport-sina": "^0.1.0", "passport-strategy": "^1.0.0", "passport-weixin": "^0.1.0", + "redis": "^2.7.1", "postcss-calc": "^5.3.1", "request": "^2.81.0", "request-promise": "^4.2.1", @@ -55,7 +56,7 @@ "xml2js": "^0.4.17", "yoho-express-session": "^2.0.0", "yoho-md5": "^2.0.0", - "yoho-node-lib": "=0.2.27", + "yoho-node-lib": "=0.2.28", "yoho-zookeeper": "^1.0.8" }, "devDependencies": { diff --git a/utils/getTDK.js b/utils/getTDK.js new file mode 100644 index 0000000..27174b0 --- /dev/null +++ b/utils/getTDK.js @@ -0,0 +1,31 @@ +const md5 = require('md5'); +const redis = global.yoho.redis; +const _ = require('lodash'); + +module.exports = (type, query, req) => { + query = type === 'url' ? md5(query) : query; + + if (redis && _.get(req.app.locals.pc, 'ci.tdk', false)) { + let arr = []; + + arr.push(redis.multi([ + ['exists', `tdk:${type}:${query}`], + ['hmget', `tdk:${type}:${query}`, 'key', 'title', 'keywords', 'description'] + ]).execAsync()); + + arr.push(new Promise((resolve)=>{ + setTimeout(resolve, 500, []); + })); + + return Promise.race(arr).then(function(res) { + if (res.length) { + return res[1]; + } else { + return []; + } + }); + } else { + return Promise.resolve([]); + } + +};