Authored by 王水玲

Merge branch 'feature/sale' of http://git.dev.yoho.cn/web/yohobuywap-node into feature/sale

@@ -139,3 +139,4 @@ public/css/* @@ -139,3 +139,4 @@ public/css/*
139 public/bundle/* 139 public/bundle/*
140 .eslintcache 140 .eslintcache
141 *.log.* 141 *.log.*
  142 +nbproject/*
@@ -145,7 +145,7 @@ const getChannelResource = (params) => { @@ -145,7 +145,7 @@ const getChannelResource = (params) => {
145 params.new_device = true; // eslint-disable-line 145 params.new_device = true; // eslint-disable-line
146 } 146 }
147 147
148 - return api.get('operations/api/v5/resource/home', sign.apiSign(params)).then(result => { 148 + return api.get('operations/api/v5/resource/home', sign.apiSign(params), 300).then(result => {
149 if (result && result.code === 200) { 149 if (result && result.code === 200) {
150 return processFloor(result.data.list); 150 return processFloor(result.data.list);
151 } else { 151 } else {
@@ -162,7 +162,7 @@ const getChannelResource = (params) => { @@ -162,7 +162,7 @@ const getChannelResource = (params) => {
162 const getLeftNav = (choosed) => { 162 const getLeftNav = (choosed) => {
163 choosed = choosed || 'all'; 163 choosed = choosed || 'all';
164 164
165 - return api.get('operations/api/v6/category/getCategory', sign.apiSign({})).then(result => { 165 + return api.get('operations/api/v6/category/getCategory', sign.apiSign({}), 300).then(result => {
166 if (result && result.code === 200) { 166 if (result && result.code === 200) {
167 return processSideBar(result.data, choosed); 167 return processSideBar(result.data, choosed);
168 } else { 168 } else {
@@ -179,7 +179,7 @@ const getLeftNav = (choosed) => { @@ -179,7 +179,7 @@ const getLeftNav = (choosed) => {
179 exports.getChannelDate = (params) => { 179 exports.getChannelDate = (params) => {
180 var channelData = {}; 180 var channelData = {};
181 181
182 - return Promise.all([getChannelResource(params), getLeftNav(params.gender)]).then((data) => { 182 + return api.all([getChannelResource(params), getLeftNav(params.gender)]).then((data) => {
183 channelData.content = data[0]; // 资源位数据 183 channelData.content = data[0]; // 资源位数据
184 channelData.sideNav = data[1]; // 侧边栏数据 184 channelData.sideNav = data[1]; // 侧边栏数据
185 185
@@ -15,6 +15,13 @@ module.exports = { @@ -15,6 +15,13 @@ module.exports = {
15 api: 'http://192.168.102.207:8080/gateway/', // http://devapi.yoho.cn:58078/ http://testapi.yoho.cn:28078/ 15 api: 'http://192.168.102.207:8080/gateway/', // http://devapi.yoho.cn:58078/ http://testapi.yoho.cn:28078/
16 service: 'http://devapi.yoho.cn:58078/' 16 service: 'http://devapi.yoho.cn:58078/'
17 }, 17 },
  18 + useCache: true,
  19 + memcache: {
  20 + master: ['192.168.102.168:12580'],
  21 + slave: ['192.168.102.168:12580'],
  22 + session: ['192.168.102.168:12580'],
  23 + timeout: 5000
  24 + },
18 loggers: { 25 loggers: {
19 infoFile: { 26 infoFile: {
20 name: 'info', 27 name: 'info',
@@ -7,12 +7,16 @@ @@ -7,12 +7,16 @@
7 'use strict'; 7 'use strict';
8 8
9 const rp = require('request-promise'); 9 const rp = require('request-promise');
  10 +const qs = require('querystring');
  11 +const md5 = require('md5');
10 const _ = require('lodash'); 12 const _ = require('lodash');
11 const log = require('./logger'); 13 const log = require('./logger');
12 -const api = require('../config/common').domains.api;  
13 -const serviceApi = require('../config/common').domains.service;  
14 -const searchApi = require('../config/common').domains.search; 14 +const cache = require('./cache');
15 const Timer = require('./timer'); 15 const Timer = require('./timer');
  16 +const config = require('../config/common');
  17 +const api = config.domains.api;
  18 +const serviceApi = config.domains.service;
  19 +const searchApi = config.domains.search;
16 20
17 21
18 let ApiUrl; 22 let ApiUrl;
@@ -24,50 +28,107 @@ class API { @@ -24,50 +28,107 @@ class API {
24 } 28 }
25 29
26 /** 30 /**
27 - * get  
28 - * @param url String  
29 - * @param data Obejct 31 + * 获取请求 ID
30 */ 32 */
31 - get(url, data) {  
32 -  
33 - let options = {  
34 - url: `${ApiUrl}${url}`,  
35 - qs: data,  
36 - json: true  
37 - }; 33 + _getReqId(options) {
  34 + return md5(`${options.url}?${qs.stringify(options.qs || options.form)}`);
  35 + }
38 36
  37 + /**
  38 + * 调用接口
  39 + */
  40 + _requestFromAPI(options, cacheOption, reqId) {
39 let timer = new Timer(); 41 let timer = new Timer();
40 42
41 timer.put('getApi');// 统计时间开始 43 timer.put('getApi');// 统计时间开始
42 44
43 - let ret = rp(options);  
44 -  
45 - return ret.catch((error)=>{  
46 - let duration = timer.put('getApi');// 接口返回  
47 -  
48 - log.error('API GET: %s, parms: %j , durationMs: %d ms error: %s , statusCode: %d',  
49 - options.url, options.qs, duration, error.message, error.statusCode); 45 + log.info(`get api: ${options.url}?${qs.stringify(options.qs)}`);
  46 + return rp(options).then((result) => {
  47 + let duration = timer.put('getApi');// 统计时间结束
  48 +
  49 + log.info(`get api success: use: ${duration}ms`);
  50 + if (config.useCache && cacheOption) {
  51 + reqId = reqId || this._getReqId(options);
  52 +
  53 + // 数据校验无误,写缓存, 否则返回 Slave 缓存服务器的数据
  54 + if (result && result.code) {
  55 + let cacheTime = _.isNumber(cacheOption) ? cacheOption : 60;
  56 +
  57 + cache.set(`apiCache:${reqId}`, result, cacheTime);
  58 + cache.setSlave(`apiCache:${reqId}`, result, cacheTime);
  59 + } else {
  60 + return this._requestFromCache(options, true);
  61 + }
  62 + }
  63 + return result;
  64 + }).catch((error)=>{
  65 + let duration = timer.put('getApi');// 统计时间结束
  66 +
  67 + log.error(`get api fail: use: ${duration}ms, statusCode: ${error.statusCode}, error: ${error.message}`);
  68 +
  69 + // 使用缓存的时候,读取二级缓存
  70 + if (config.useCache) {
  71 + return this._requestFromCache(options, true);
  72 + }
  73 + return Promise.reject({
  74 + error: '接口调用失败'
  75 + });
50 }); 76 });
51 } 77 }
52 78
53 /** 79 /**
54 - * multi get  
55 - * @params: urls => Array[Object[url[string], data[object]]] 80 + * 读取缓存
  81 + * @param {[object]} options
  82 + * @param {[boolean]} slave true: 读取二级缓存
  83 + * @return {[type]}
56 */ 84 */
57 - multiGet(urls) {  
58 - var rps = [];  
59 -  
60 - _.forEach(urls, function(el) {  
61 - rps.push(rp({  
62 - url: `${ApiUrl}${el.url}`,  
63 - qs: el.data,  
64 - json: true  
65 - })); 85 + _requestFromCache(options, slave) {
  86 + let reqId = this._getReqId(options);
  87 + let getCache = slave ? cache.getFromSlave : cache.get;
  88 +
  89 + log.info(`get cache: ${reqId}, url: ${options.url}?${qs.stringify(options.qs)}`);
  90 + return getCache(`apiCache:${reqId}`).then((result) => {
  91 + if (!_.isNil(result)) {
  92 + try {
  93 + result = JSON.parse(result);
  94 + } finally {
  95 + log.info(slave ? 'get slave cache success' : 'get master cache success');
  96 + return result;
  97 + }
  98 + }
  99 +
  100 + if (!slave) {
  101 + return this._requestFromAPI(options, true, reqId);
  102 + }
  103 + }).catch(() => {
  104 + log.error(slave ? 'get slave cache fail' : 'get master cache fail');
  105 + if (!slave) {
  106 + return this._requestFromAPI(options, true, reqId);
  107 + }
66 }); 108 });
  109 + }
67 110
68 - return Promise.all(rps).then((d) => {  
69 - return d;  
70 - }); 111 + /**
  112 + * 使用 get 请求获取接口
  113 + * @param {[string]} url
  114 + * @param {[object]} data
  115 + * @param {[bool or number]} cacheOption 使用数字时,数字表示缓存时间
  116 + * @return {[type]}
  117 + */
  118 + get(url, data, cacheOption) {
  119 + let options = {
  120 + url: `${ApiUrl}${url}`,
  121 + qs: data,
  122 + json: true,
  123 + timeout: 3000
  124 + };
  125 +
  126 + // 从缓存获取数据
  127 + if (config.useCache && cacheOption) {
  128 + return this._requestFromCache(options);
  129 + }
  130 +
  131 + return this._requestFromAPI(options, cacheOption);
71 } 132 }
72 133
73 /** 134 /**
@@ -76,12 +137,22 @@ class API { @@ -76,12 +137,22 @@ class API {
76 * @param data Obejct 137 * @param data Obejct
77 */ 138 */
78 post(url, data) { 139 post(url, data) {
79 - return rp({ 140 + let options = {
80 url: `${ApiUrl}${url}`, 141 url: `${ApiUrl}${url}`,
81 - method: 'post',  
82 form: data, 142 form: data,
83 - json: true  
84 - }); 143 + method: 'post',
  144 + json: true,
  145 + timeout: 3000
  146 + };
  147 +
  148 + return this._requestFromAPI(options);
  149 + }
  150 +
  151 + all(list) {
  152 + if (_.isArray(list)) {
  153 + return Promise.all(list);
  154 + }
  155 + throw Error('the parameters of api all method should be Array!');
85 } 156 }
86 } 157 }
87 158
  1 +/**
  2 + * 缓存封装
  3 + * 前期使用 memcache, 写方法的时候考虑一下如何转换为 redis
  4 + * @author bikai kai.bi@yoho.cn
  5 + * @date 2016/05/16
  6 + */
  7 +
  8 +'use strict';
  9 +const Promise = require('bluebird');
  10 +const Memcached = require('memcached');
  11 +const _ = require('lodash');
  12 +const log = require('./logger');
  13 +const config = require('../config/common');
  14 +
  15 +let master = new Memcached(config.memcache.master, config.memcache);
  16 +let slave = new Memcached(config.memcache.slave, config.memcache);
  17 +
  18 +master = Promise.promisifyAll(master);
  19 +slave = Promise.promisifyAll(slave);
  20 +
  21 +/**
  22 + * 获取缓存
  23 + * @param {[string]} key 键
  24 + * @return {[type]}
  25 + */
  26 +exports.get = (key) => {
  27 + if (_.isString(key)) {
  28 + return master.getAsync(key);
  29 + }
  30 +
  31 + return Promise.resolve();
  32 +};
  33 +
  34 +/**
  35 + * 批量获取缓存
  36 + * @param {[array]} list 字符串数组
  37 + * @return {[type]}
  38 + */
  39 +exports.getMulti = (list) => {
  40 + if (_.isArray(list)) {
  41 + return master.getMultiAsync(list);
  42 + }
  43 + return Promise.resolve();
  44 +};
  45 +
  46 +/**
  47 + * 获取缓存(从 Slave 服务器)
  48 + * @param {[string]} key 键
  49 + * @return {[type]}
  50 + */
  51 +exports.getFromSlave = (key) => {
  52 + if (_.isString(key)) {
  53 + return slave.getAsync(key);
  54 + }
  55 + return Promise.resolve();
  56 +};
  57 +
  58 +/**
  59 + * 批量获取缓存(从 Slave 服务器)
  60 + * @param {[array]} list 字符串数组
  61 + * @return {[type]}
  62 + */
  63 +exports.getMultiFromSlave = (list) => {
  64 + if (_.isArray(list)) {
  65 + return slave.getMultiAsync(list);
  66 + }
  67 + return Promise.resolve();
  68 +};
  69 +
  70 +/**
  71 + * 写缓存
  72 + * @param {[type]} key 键
  73 + * @param {[type]} value 值
  74 + * @param {[type]} lifetime 生命周期
  75 + * @return {[type]}
  76 + */
  77 +exports.set = (key, value, lifetime) => {
  78 + lifetime = lifetime || 86400;
  79 +
  80 + log.info(`write cache: ${key}`);
  81 + if (_.isObject(value)) {
  82 + value = JSON.stringify(value);
  83 + }
  84 +
  85 + if (_.isString(key)) {
  86 + return master.setAsync(key, value, lifetime);
  87 + }
  88 + return Promise.resolve();
  89 +};
  90 +
  91 +/**
  92 + * 写缓存(到 Slave 服务器)
  93 + * @param {[type]} key 键
  94 + * @param {[type]} value 值
  95 + * @param {[type]} lifetime 生命周期
  96 + * @return {[type]}
  97 + */
  98 +exports.setSlave = (key, value, lifetime) => {
  99 + lifetime = lifetime || 86400;
  100 +
  101 + if (_.isObject(value)) {
  102 + value = JSON.stringify(value);
  103 + }
  104 +
  105 + if (_.isString(key)) {
  106 + return slave.setAsync(key, value, lifetime);
  107 + }
  108 + return Promise.resolve();
  109 +};
  110 +
  111 +/**
  112 + * 删除缓存
  113 + * @param {[string]} key 键
  114 + * @return {[type]}
  115 + */
  116 +exports.del = (key) => {
  117 + if (_.isString(key)) {
  118 + return master.delAsync(key);
  119 + }
  120 + return Promise.resolve();
  121 +};
@@ -30,6 +30,7 @@ @@ -30,6 +30,7 @@
30 }, 30 },
31 "license": "MIT", 31 "license": "MIT",
32 "dependencies": { 32 "dependencies": {
  33 + "bluebird": "^3.3.5",
33 "body-parser": "^1.15.0", 34 "body-parser": "^1.15.0",
34 "cookie-parser": "^1.4.1", 35 "cookie-parser": "^1.4.1",
35 "express": "^4.13.1", 36 "express": "^4.13.1",
@@ -37,6 +38,7 @@ @@ -37,6 +38,7 @@
37 "influxdb-winston": "^1.0.1", 38 "influxdb-winston": "^1.0.1",
38 "lodash": "^4.12.0", 39 "lodash": "^4.12.0",
39 "md5": "^2.1.0", 40 "md5": "^2.1.0",
  41 + "memcached": "^2.2.1",
40 "morgan": "^1.7.0", 42 "morgan": "^1.7.0",
41 "oneapm": "^1.2.20", 43 "oneapm": "^1.2.20",
42 "request-promise": "^3.0.0", 44 "request-promise": "^3.0.0",