Authored by 514335620@qq.com

合并develop 分支代码

@@ -12,13 +12,25 @@ if (config.useOneapm) { @@ -12,13 +12,25 @@ if (config.useOneapm) {
12 require('oneapm'); 12 require('oneapm');
13 } 13 }
14 14
15 -let express = require('express'),  
16 - path = require('path'),  
17 - bodyParser = require('body-parser'),  
18 - cookieParser = require('cookie-parser'),  
19 - favicon = require('serve-favicon'); 15 +const express = require('express');
  16 +const path = require('path');
  17 +const bodyParser = require('body-parser');
  18 +const cookieParser = require('cookie-parser');
  19 +const favicon = require('serve-favicon');
  20 +const session = require('express-session');
  21 +const memcached = require('connect-memcached');
  22 +const pkg = require('./package.json');
20 23
21 -let app = express(); 24 +const app = express();
  25 +const MemcachedStore = memcached(session);
  26 +
  27 +
  28 +// 向模板注入变量
  29 +app.locals.devEnv = app.get('env') === 'development';
  30 +app.locals.version = pkg.version;
  31 +
  32 +// 指定libray目录
  33 +global.library = path.resolve('./library');
22 34
23 app.set('view engine', '.hbs'); 35 app.set('view engine', '.hbs');
24 36
@@ -27,11 +39,22 @@ app.use(express.static(path.join(__dirname, 'public'))); @@ -27,11 +39,22 @@ app.use(express.static(path.join(__dirname, 'public')));
27 app.use(bodyParser.json()); 39 app.use(bodyParser.json());
28 app.use(bodyParser.urlencoded({extended: false})); 40 app.use(bodyParser.urlencoded({extended: false}));
29 app.use(cookieParser()); 41 app.use(cookieParser());
  42 +app.use(session({
  43 + secret: '3e5fec7deca0b8305cefe2ad9d90ff5e',
  44 + name: 'PHPSESSID',
  45 + prefix: 'yohobuy',
  46 + proxy: true,
  47 + resave: true,
  48 + saveUninitialized: true,
  49 + store: new MemcachedStore({
  50 + hosts: config.memcache.session
  51 + })
  52 +}));
30 53
31 // dispatcher 54 // dispatcher
32 require('./dispatch')(app); 55 require('./dispatch')(app);
33 56
34 // listener 57 // listener
35 -app.listen(6002, function() { 58 +app.listen(config.port, function() {
36 console.log('yohobuy start'); 59 console.log('yohobuy start');
37 }); 60 });
  1 +/**
  2 + * index controller
  3 + * @author: bikai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +exports.special = (req, res) => {
  10 + let id = req.params[0] || 0;
  11 +
  12 + res.send(id);
  13 +};
  1 +/**
  2 + * sub app index
  3 + * @author: bikai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
  10 +
  11 +var app = express();
  12 +
  13 +// set view engin
  14 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
  15 +
  16 +app.set('views', path.join(__dirname, 'views/action'));
  17 +app.engine('.hbs', hbs({
  18 + extname: '.hbs',
  19 + defaultLayout: 'layout',
  20 + layoutsDir: doraemon,
  21 + partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
  22 + helpers: 'helpers'
  23 +}));
  24 +
  25 +// router
  26 +app.use(require('./router'));
  27 +
  28 +module.exports = app;
  1 +/**
  2 + * router of sub app index
  3 + * @author: bikai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const express = require('express');
  10 +const router = express.Router(); // eslint-disable-line
  11 +const cRoot = './controllers';
  12 +
  13 +const specialController = require(`${cRoot}/special`);
  14 +
  15 +// 专题活动
  16 +router.get(/^\/special\/(\d+)_(.*)\.html$/, specialController.special);
  17 +
  18 +module.exports = router;
  1 +<div class="yoho-page center-content">
  2 + <p>body</p>
  3 +</div>
  1 +/**
  2 + * girls controller
  3 + * @author: 赵彪<bill.zhao@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const headerModel = require('../../../doraemon/models/header');
  10 +
  11 +// const channelModel = require('../models/index');
  12 +
  13 +exports.boysIndex = (req, res) => {
  14 + headerModel.requestHeaderData()
  15 + .then(response => {
  16 + let data = headerModel.setHeaderData(response.data, 'boys');
  17 +
  18 + data.module = 'index';
  19 + data.page = 'index';
  20 + data.footerTop = true;
  21 +
  22 + res.render('boys', data);
  23 + })
  24 + .catch(() => {
  25 + res.render('error', {devEnv: true, pageErr: true});
  26 + });
  27 +};
  28 +
  29 +exports.girlsIndex = (req, res) => {
  30 + headerModel.requestHeaderData()
  31 + .then(response => {
  32 + let data = headerModel.setHeaderData(response.data, 'girls');
  33 +
  34 + data.module = 'index';
  35 + data.page = 'index';
  36 + data.footerTop = true;
  37 +
  38 + res.render('girls', data);
  39 + })
  40 + .catch(() => {
  41 + res.render('error', {devEnv: true, pageErr: true});
  42 + });
  43 +
  44 +};
  1 +/**
  2 + * sub app girls
  3 + * @author: biao<bill.zhao@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +var express = require('express'),
  8 + path = require('path'),
  9 + hbs = require('express-handlebars');
  10 +
  11 +var app = express();
  12 +
  13 +// set view engin
  14 +var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
  15 +
  16 +app.on('mount', function(parent) {
  17 + delete parent.locals.settings; // 不继承父 App 的设置
  18 + Object.assign(app.locals, parent.locals);
  19 +});
  20 +
  21 +app.set('views', path.join(__dirname, 'views/action'));
  22 +app.engine('.hbs', hbs({
  23 + extname: '.hbs',
  24 + defaultLayout: 'layout',
  25 + layoutsDir: doraemon,
  26 + partialsDir: ['./views/partial', `${doraemon}/partial`],
  27 + helpers: 'helpers'
  28 +}));
  29 +
  30 +// router
  31 +app.use(require('./router'));
  32 +
  33 +module.exports = app;
  1 +/**
  2 + * girls model
  3 + * @author: 赵彪<bill.zhao@yoho.cn>
  4 + * @date: 2016/05/17
  5 + */
  6 +'use strict';
  7 +
  8 +// const _ = require('lodash');
  9 +
  10 +const ServiceAPI = require(`${global.library}/api`).ServiceAPI;
  11 +const sign = require(`${global.library}/sign`);
  12 +
  13 +const serviceApi = new ServiceAPI();
  14 +
  15 +/**
  16 + * 获取公共配置
  17 + * @param undefined
  18 + * @return {Object} 配置对象
  19 + */
  20 +// const commonConfig = () => ({
  21 +// title: 'girls'
  22 +// });
  23 +
  24 +exports.getContent = () => {
  25 + let data = sign.apiSign({
  26 +
  27 + /* eslint-disable */
  28 + client_type: 'web'
  29 + /* eslint-enable */
  30 + });
  31 +
  32 + serviceApi.get('/operations/api/v5/resource/home', data).then(response => {
  33 + console.log(response);
  34 + });
  35 +};
  1 +/**
  2 + * router of sub app girls
  3 + * @author: biao<bill.zhao@yoho.cn>
  4 + * @date: 2016/05/16
  5 + */
  6 +
  7 +'use strict';
  8 +
  9 +const router = require('express').Router(); // eslint-disable-line
  10 +const cRoot = './controllers';
  11 +
  12 +// Your controller here
  13 +const channelController = require(`${cRoot}/index`);
  14 +
  15 +router.get('/boys', channelController.boysIndex);
  16 +
  17 +module.exports = router;
@@ -9,19 +9,17 @@ @@ -9,19 +9,17 @@
9 const headerModel = require('../../../doraemon/models/header'); 9 const headerModel = require('../../../doraemon/models/header');
10 10
11 exports.index = (req, res) => { 11 exports.index = (req, res) => {
12 - headerModel.getHeaderData()  
13 - .then(function(modelRes) {  
14 - let response = JSON.parse(modelRes);  
15 - let data = headerModel.getAllHeaderData(response.data, 'boy'); 12 + headerModel.requestHeaderData()
  13 + .then(response => {
  14 + let data = headerModel.setHeaderData(response.data, 'boys');
16 15
17 - data.headerData.navbars[0].active = true;  
18 data.module = 'index'; 16 data.module = 'index';
19 data.page = 'index'; 17 data.page = 'index';
20 data.footerTop = true; 18 data.footerTop = true;
21 19
22 res.render('index', data); 20 res.render('index', data);
23 }) 21 })
24 - .catch(function() { 22 + .catch(() => {
25 res.render('index', {devEnv: true, pageErr: true}); 23 res.render('index', {devEnv: true, pageErr: true});
26 }); 24 });
27 }; 25 };
@@ -10,12 +10,20 @@ const isProduction = process.env.NODE_ENV === 'production'; @@ -10,12 +10,20 @@ const isProduction = process.env.NODE_ENV === 'production';
10 const isTest = process.env.NODE_ENV === 'test'; 10 const isTest = process.env.NODE_ENV === 'test';
11 11
12 module.exports = { 12 module.exports = {
  13 + port: 6002,
13 domains: { 14 domains: {
14 api: 'http://192.168.102.205:8080/gateway', 15 api: 'http://192.168.102.205:8080/gateway',
15 service: 'http://testservice.yoho.cn:28077', 16 service: 'http://testservice.yoho.cn:28077',
16 search: 'http://192.168.10.64:8080/yohosearch/' 17 search: 'http://192.168.10.64:8080/yohosearch/'
17 }, 18 },
18 useOneapm: false, 19 useOneapm: false,
  20 + useCache: true,
  21 + memcache: {
  22 + master: ['192.168.102.168:12580'],
  23 + slave: ['192.168.102.168:12580'],
  24 + session: ['192.168.102.168:12580'],
  25 + timeout: 5000
  26 + },
19 loggers: { 27 loggers: {
20 infoFile: { 28 infoFile: {
21 name: 'info', 29 name: 'info',
@@ -8,7 +8,13 @@ module.exports = app => { @@ -8,7 +8,13 @@ module.exports = app => {
8 8
9 // 公共服务 9 // 公共服务
10 10
  11 + // 专题活动等活动页面
  12 + app.use(require('./apps/activity'));
  13 +
11 // 业务模块 14 // 业务模块
12 app.use('/', require('./apps/index')); 15 app.use('/', require('./apps/index'));
13 app.use('/product', require('./apps/product')); 16 app.use('/product', require('./apps/product'));
  17 +
  18 + // 频道页
  19 + app.use('/', require('./apps/channel'));
14 }; 20 };
@@ -7,10 +7,9 @@ @@ -7,10 +7,9 @@
7 'use strict'; 7 'use strict';
8 8
9 const _ = require('lodash'); 9 const _ = require('lodash');
10 -const lRoot = '../../library/';  
11 10
12 -const ServiceAPI = require(`${lRoot}/api`).ServiceAPI;  
13 -const sign = require(`${lRoot}/sign`); 11 +const ServiceAPI = require(`${global.library}/api`).ServiceAPI;
  12 +const sign = require(`${global.library}/sign`);
14 13
15 const serviceApi = new ServiceAPI(); 14 const serviceApi = new ServiceAPI();
16 15
@@ -20,11 +19,22 @@ const serviceApi = new ServiceAPI(); @@ -20,11 +19,22 @@ const serviceApi = new ServiceAPI();
20 * @return {Object} 配置对象 19 * @return {Object} 配置对象
21 */ 20 */
22 const commonConfig = () => ({ 21 const commonConfig = () => ({
23 - title: 'home',  
24 - devEnv: true,  
25 - version: '0.0.1' 22 + title: 'home'
26 }); 23 });
27 24
  25 +const getChannelIndex = (type) => {
  26 + const channelMap = {
  27 + boys: 0,
  28 + girls: 1,
  29 + kids: 2,
  30 + lifestyle: 3
  31 + };
  32 +
  33 + const index = channelMap[type];
  34 +
  35 + return _.isNil(index) ? 0 : index;
  36 +};
  37 +
28 /** 38 /**
29 * 获取菜单 39 * 获取菜单
30 * @param undefined 40 * @param undefined
@@ -138,26 +148,7 @@ const getThirdNav = (data) => { @@ -138,26 +148,7 @@ const getThirdNav = (data) => {
138 const getSubNav = (data, type) => { 148 const getSubNav = (data, type) => {
139 let subNav = []; 149 let subNav = [];
140 150
141 - let index;  
142 -  
143 - switch (type) {  
144 - case 'boy':  
145 - index = 0;  
146 - break;  
147 - case 'girl':  
148 - index = 1;  
149 - break;  
150 - case 'kids':  
151 - index = 2;  
152 - break;  
153 - case 'lifestyle':  
154 - index = 3;  
155 - break;  
156 - default:  
157 - index = 0;  
158 - }  
159 -  
160 - _.forEach(data[index].sub, function(item) { 151 + _.forEach(data[getChannelIndex(type)].sub, function(item) {
161 let obj = {}; 152 let obj = {};
162 153
163 obj.link = item.sort_url; 154 obj.link = item.sort_url;
@@ -177,23 +168,28 @@ const getSubNav = (data, type) => { @@ -177,23 +168,28 @@ const getSubNav = (data, type) => {
177 }; 168 };
178 169
179 170
  171 +
  172 +
180 /** 173 /**
181 * 处理接口返回的数据 174 * 处理接口返回的数据
182 * @param {object} 接口返回的对象 175 * @param {object} 接口返回的对象
  176 + * @param {String} 指定页面类型为boys,girls,kids,lifestyle
183 * @return {object} 头部数据 177 * @return {object} 头部数据
184 */ 178 */
185 -exports.getAllHeaderData = function(resData, type) { 179 +exports.setHeaderData = (resData, type) => {
186 let config = commonConfig(); 180 let config = commonConfig();
187 let data = { 181 let data = {
188 headerData: { 182 headerData: {
189 header: true, 183 header: true,
190 - type: type, 184 + headType: type,
191 yohoGroup: getMenuData(), 185 yohoGroup: getMenuData(),
192 navbars: getNavBar(resData), 186 navbars: getNavBar(resData),
193 subNav: getSubNav(resData, type) 187 subNav: getSubNav(resData, type)
194 } 188 }
195 }; 189 };
196 190
  191 + data.headerData.navbars[getChannelIndex(type)].active = true;
  192 +
197 return _.merge(config, data); 193 return _.merge(config, data);
198 }; 194 };
199 195
@@ -202,15 +198,13 @@ exports.getAllHeaderData = function(resData, type) { @@ -202,15 +198,13 @@ exports.getAllHeaderData = function(resData, type) {
202 * @param undefined 198 * @param undefined
203 * @return {promise} 199 * @return {promise}
204 */ 200 */
205 -exports.getHeaderData = function() { 201 +exports.requestHeaderData = () => {
206 let data = sign.apiSign({ 202 let data = sign.apiSign({
207 203
208 /* eslint-disable */ 204 /* eslint-disable */
209 - client_type: 'web', 205 + client_type: 'web'
210 /* eslint-enable */ 206 /* eslint-enable */
211 }); 207 });
212 208
213 - return serviceApi.get('/operations/api/v6/category/getCategory', data); 209 + return serviceApi.get('/operations/api/v6/category/getCategory', data, true);
214 }; 210 };
215 -  
216 -  
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 <link rel="dns-prefetch" href="//img12.static.yhbimg.com"> 15 <link rel="dns-prefetch" href="//img12.static.yhbimg.com">
16 <link rel="dns-prefetch" href="//img13.static.yhbimg.com"> 16 <link rel="dns-prefetch" href="//img13.static.yhbimg.com">
17 {{#if devEnv}} 17 {{#if devEnv}}
18 - <link rel="stylesheet" href="//localhost:5000/css/index.css"> 18 + <link rel="stylesheet" href="//localhost:5002/css/index.css">
19 {{^}} 19 {{^}}
20 <link rel="stylesheet" href="//cdn.yoho.cn/m-yohobuy-node/{{version}}/index.css"> 20 <link rel="stylesheet" href="//cdn.yoho.cn/m-yohobuy-node/{{version}}/index.css">
21 {{/if}} 21 {{/if}}
@@ -29,7 +29,7 @@ @@ -29,7 +29,7 @@
29 {{/if}} 29 {{/if}}
30 {{> footer}} 30 {{> footer}}
31 {{#if devEnv}} 31 {{#if devEnv}}
32 - <script src="//localhost:5000/{{module}}.{{page}}.js"></script> 32 + <script src="//localhost:5002/{{module}}.{{page}}.js"></script>
33 {{^}} 33 {{^}}
34 <script src="//cdn.yoho.cn/m-yohobuy-node/{{version}}/{{module}}.{{page}}.js"></script> 34 <script src="//cdn.yoho.cn/m-yohobuy-node/{{version}}/{{module}}.{{page}}.js"></script>
35 {{/if}} 35 {{/if}}
1 {{# headerData}} 1 {{# headerData}}
2 - <div class="yoho-header {{headtype}}"> 2 + <div class="yoho-header {{headType}}">
3 <div class="tool-wrapper clearfix"> 3 <div class="tool-wrapper clearfix">
4 <div class="center-content"> 4 <div class="center-content">
5 <div class="yoho-group-map left"> 5 <div class="yoho-group-map left">
@@ -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,54 +28,107 @@ class API { @@ -24,54 +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 - }; 33 + _getReqId(options) {
  34 + return md5(`${options.url}?${qs.stringify(options.qs || options.form)}`);
  35 + }
37 36
  37 + /**
  38 + * 调用接口
  39 + */
  40 + _requestFromAPI(options, cacheOption, reqId) {
38 let timer = new Timer(); 41 let timer = new Timer();
39 42
40 timer.put('getApi');// 统计时间开始 43 timer.put('getApi');// 统计时间开始
41 44
42 - let ret = rp(options);  
43 -  
44 - ret.then((body)=>{  
45 - let duration = timer.put('getApi');// 接口返回  
46 -  
47 - log.info('API GET: %s, parms: %j , durationMs: %d ms , body: %s', options.url, options.qs, duration, body); 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;
48 }).catch((error)=>{ 64 }).catch((error)=>{
49 - let duration = timer.put('getApi');// 接口返回 65 + let duration = timer.put('getApi');// 统计时间结束
50 66
51 - log.error('API GET: %s, parms: %j , durationMs: %d ms error: %s , statusCode: %d',  
52 - options.url, options.qs, duration, error.message, error.statusCode);  
53 - }); 67 + log.error(`get api fail: use: ${duration}ms, statusCode: ${error.statusCode}, error: ${error.message}`);
54 68
55 - return ret; 69 + // 使用缓存的时候,读取二级缓存
  70 + if (config.useCache) {
  71 + return this._requestFromCache(options, true);
  72 + }
  73 + return Promise.reject({
  74 + error: '接口调用失败'
  75 + });
  76 + });
56 } 77 }
57 78
58 /** 79 /**
59 - * multi get  
60 - * @params: urls => Array[Object[url[string], data[object]]] 80 + * 读取缓存
  81 + * @param {[object]} options
  82 + * @param {[boolean]} slave true: 读取二级缓存
  83 + * @return {[type]}
61 */ 84 */
62 - multiGet(urls) {  
63 - var rps = [];  
64 -  
65 - _.forEach(urls, function(el) {  
66 - rps.push(rp({  
67 - url: `${ApiUrl}${el.url}`,  
68 - qs: el.data  
69 - })); 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 + }
70 }); 108 });
  109 + }
71 110
72 - return Promise.all(rps).then((d) => {  
73 - return d;  
74 - }); 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);
75 } 132 }
76 133
77 /** 134 /**
@@ -80,11 +137,22 @@ class API { @@ -80,11 +137,22 @@ class API {
80 * @param data Obejct 137 * @param data Obejct
81 */ 138 */
82 post(url, data) { 139 post(url, data) {
83 - return rp({ 140 + let options = {
84 url: `${ApiUrl}${url}`, 141 url: `${ApiUrl}${url}`,
  142 + form: data,
85 method: 'post', 143 method: 'post',
86 - form: data  
87 - }); 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!');
88 } 156 }
89 } 157 }
90 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 +};
  1 +/**
  2 + * 对象键名驼峰化
  3 + * @author: Bi Kai<kai.bi@yoho.cn>
  4 + * @date: 2016/05/09
  5 + */
  6 +'use strict';
  7 +const _ = require('lodash');
  8 +
  9 +let camelCase,
  10 + camelCaseObject,
  11 + camelCaseArray;
  12 +
  13 +camelCaseObject = (obj) => {
  14 + _.forEach(Object.keys(obj), (k) => {
  15 + obj[k] = camelCase(obj[k]);
  16 + obj[_.camelCase(k)] = obj[k];
  17 + });
  18 + return obj;
  19 +};
  20 +
  21 +camelCaseArray = (list) => {
  22 + _.forEach(list, (k) => {
  23 + k = camelCase(k);
  24 + });
  25 + return list;
  26 +};
  27 +
  28 +camelCase = (data) => {
  29 + if (_.isObject(data)) {
  30 + data = camelCaseObject(data);
  31 + }
  32 +
  33 + if (_.isArray(data)) {
  34 + data = camelCaseArray(data);
  35 + }
  36 +
  37 + return data;
  38 +};
  39 +
  40 +module.exports = camelCase;
1 -/*  
2 - * @Author: Targaryen  
3 - * @Date: 2016-05-10 10:11:34  
4 - * @Last Modified by: Targaryen  
5 - * @Last Modified time: 2016-05-12 11:25:53 1 +/**
  2 + * Handlebars helpers
  3 + * bikai kai.bi@yoho.cn
  4 + * 2016-05-10
6 */ 5 */
7 -/* -----------------------------------------  
8 - * 实现 PHP 的 Helpers 函数  
9 - * -----------------------------------------  
10 - */  
11 -  
12 'use strict'; 6 'use strict';
13 -  
14 -const SUB_DOMAIN = '.yohobuy.com';  
15 -const SITE_MAIN = 'http://www.yohobuy.com'; 7 +const querystring = require('querystring');
16 const _ = require('lodash'); 8 const _ = require('lodash');
  9 +const config = require('../config/common');
17 10
18 -class HELPERS {  
19 -  
20 - /**  
21 - * 构建网站的URL  
22 - * @param uri String  
23 - * @param param Array  
24 - * @param module String  
25 - */  
26 - url(uri, param, module) {  
27 - let url = '';  
28 -  
29 - if (module === null) {  
30 - module = 'index';  
31 - }  
32 - switch (module) {  
33 - case 'default':  
34 - url = 'http://m.yohobuy.com';  
35 - break;  
36 - case 'guang':  
37 - url = 'http://guang' + SUB_DOMAIN;  
38 - break;  
39 - case 'list':  
40 - url = 'http://list' + SUB_DOMAIN;  
41 - break;  
42 - case 'search':  
43 - url = 'http://search' + SUB_DOMAIN;  
44 - break;  
45 - case 'index':  
46 - url = SITE_MAIN;  
47 - break;  
48 - case '':  
49 - break;  
50 - case undefined:  
51 - break;  
52 - default:  
53 - url = 'http://' + module + SUB_DOMAIN;  
54 - break;  
55 - } 11 +/**
  12 + * 七牛图片路径处理
  13 + * @param {[tring]} url
  14 + * @param {[tring]} width
  15 + * @param {[tring]} height
  16 + * @param {[tring]} mode
  17 + * @return {[tring]}
  18 + */
  19 +exports.image = (url, width, height, mode) => {
  20 + mode = _.isNumber(mode) ? mode : 2;
  21 + url = url || '';
  22 + return url.replace(/{width}/g, width).replace(/{height}/g, height).replace(/{mode}/g, mode);
  23 +};
56 24
57 - url += uri;  
58 - if (param !== null && param !== undefined) {  
59 - url += '?';  
60 - _.forEach(param, function(value, key) {  
61 - url += (key + '=' + value + '&');  
62 - });  
63 - }  
64 - if (url.substr(-1, 1) === '&') {  
65 - return url.substr(0, url.length - 1);  
66 - } else {  
67 - return url;  
68 - } 25 +/**
  26 + * 站内地址格式化
  27 + * @param {[string]} uri 路径
  28 + * @param {[object]} qs 查询字符串
  29 + * @param {[string]} module 模块
  30 + * @return {[string]}
  31 + */
  32 +exports.url = (uri, qs, module) => {
  33 + const subDomain = '.m.yohobuy.com';
  34 + const subName = {
  35 + default: config.siteUrl,
  36 + guang: '//guang' + subDomain,
  37 + list: '//list' + subDomain,
  38 + search: '//search' + subDomain,
  39 + huodong: '//huodong' + subDomain,
  40 + activity: '//activity.yohobuy.com',
  41 + index: config.siteUrl
  42 + };
  43 + let url;
69 44
  45 + module = module || 'default';
  46 + if (subName[module]) {
  47 + url = subName[module];
  48 + } else {
  49 + url = '//' + module + subDomain; // 规则没匹配到就把模块当作子域名
70 } 50 }
71 51
72 - /**  
73 - * 根据尺寸获得图片url  
74 - * @param uri String  
75 - * @param param Array  
76 - * @param module String  
77 - */  
78 - getImageUrl(url, width, height, mode) {  
79 - if (mode === null) {  
80 - mode = 2;  
81 - }  
82 - return url.replace(/{width}/g, width)  
83 - .replace(/{height}/g, height)  
84 - .replace(/{mode}/g, mode); 52 + url += uri;
  53 + if (qs) {
  54 + url += '?' + querystring.stringify(qs);
85 } 55 }
86 -}  
87 56
88 -module.exports = HELPERS; 57 + return url;
  58 +};
  59 +
  60 +
  61 +/**
  62 + * 大写转小写处理
  63 + * @param {[string]} str 转换字符
  64 + */
  65 +exports.lowerCase = (str) => {
  66 + str = str || '';
  67 + return str.toLowerCase();
  68 +};
  69 +
  70 +/**
  71 + * 小写转大写处理
  72 + * @param {[string]} str 转换字符
  73 + */
  74 +exports.upperCase = (str) => {
  75 + str = str || '';
  76 + return str.toUpperCase();
  77 +};
@@ -5,7 +5,6 @@ @@ -5,7 +5,6 @@
5 */ 5 */
6 6
7 'use strict'; 7 'use strict';
8 -  
9 const _ = require('lodash'); 8 const _ = require('lodash');
10 const md5 = require('md5'); 9 const md5 = require('md5');
11 10
@@ -24,7 +23,7 @@ const privateKey = { @@ -24,7 +23,7 @@ const privateKey = {
24 * @return {Object} 排序之后的参数对象 23 * @return {Object} 排序之后的参数对象
25 */ 24 */
26 const packageSort = argument => { 25 const packageSort = argument => {
27 - var newObj = {}; 26 + let newObj = {};
28 27
29 for (let k of Object.keys(argument).sort()) { 28 for (let k of Object.keys(argument).sort()) {
30 newObj[k] = argument[k]; 29 newObj[k] = argument[k];
@@ -39,7 +38,7 @@ const packageSort = argument => { @@ -39,7 +38,7 @@ const packageSort = argument => {
39 * @return {string} 生成的签名字符串 38 * @return {string} 生成的签名字符串
40 */ 39 */
41 const makeSign = argument => { 40 const makeSign = argument => {
42 - var qs = []; 41 + let qs = [];
43 42
44 _.forEach(argument, function(value, key) { 43 _.forEach(argument, function(value, key) {
45 qs.push(key + '=' + _.trim(value)); 44 qs.push(key + '=' + _.trim(value));
@@ -50,11 +49,10 @@ const makeSign = argument => { @@ -50,11 +49,10 @@ const makeSign = argument => {
50 49
51 // 生成API签名,调用后端接口的时候有私钥校验 50 // 生成API签名,调用后端接口的时候有私钥校验
52 exports.apiSign = (params) => { 51 exports.apiSign = (params) => {
53 -  
54 - var clientType = params.client_type || 'h5'; 52 + const clientType = params.client_type || 'web';
55 53
56 /* eslint-disable */ 54 /* eslint-disable */
57 - var sign = packageSort(Object.assign({ 55 + let sign = packageSort(Object.assign({
58 client_type: clientType, 56 client_type: clientType,
59 private_key: privateKey[clientType], 57 private_key: privateKey[clientType],
60 app_version: '3.8.2', 58 app_version: '3.8.2',
@@ -62,18 +60,18 @@ exports.apiSign = (params) => { @@ -62,18 +60,18 @@ exports.apiSign = (params) => {
62 screen_size: '720x1280', 60 screen_size: '720x1280',
63 v: '7' 61 v: '7'
64 }, params)); 62 }, params));
65 -  
66 /* eslint-enable */ 63 /* eslint-enable */
67 64
68 - return Object.assign(sign, {  
69 - 65 + sign = Object.assign(sign, {
70 client_secret: makeSign(sign) // eslint-disable-line camelcase 66 client_secret: makeSign(sign) // eslint-disable-line camelcase
71 }); 67 });
  68 + delete sign.private_key;
  69 + return sign;
72 }; 70 };
73 71
74 // 检查签名,APP 访问 H5 页面的时候需要检查 72 // 检查签名,APP 访问 H5 页面的时候需要检查
75 exports.checkSign = (params) => { 73 exports.checkSign = (params) => {
76 - var clientSecret = params.client_secret, // eslint-disable-line camelcase 74 + let clientSecret = params.client_secret, // eslint-disable-line camelcase
77 sortedParams; 75 sortedParams;
78 76
79 // 忽略部分参数 77 // 忽略部分参数
@@ -90,7 +88,7 @@ exports.checkSign = (params) => { @@ -90,7 +88,7 @@ exports.checkSign = (params) => {
90 88
91 // 检查签名,APP 访问 H5 页面的时候需要检查, 有可能不同于上边的签名方式 89 // 检查签名,APP 访问 H5 页面的时候需要检查, 有可能不同于上边的签名方式
92 exports.webSign = (params) => { 90 exports.webSign = (params) => {
93 - var webPrivateKey = 'yohobuyapp'; 91 + const webPrivateKey = 'yohobuyapp';
94 92
95 return params.key === md5(md5(webPrivateKey) + params.uid); 93 return params.key === md5(md5(webPrivateKey) + params.uid);
96 }; 94 };
@@ -18,13 +18,17 @@ @@ -18,13 +18,17 @@
18 }, 18 },
19 "license": "MIT", 19 "license": "MIT",
20 "dependencies": { 20 "dependencies": {
  21 + "bluebird": "^3.3.5",
21 "body-parser": "^1.15.0", 22 "body-parser": "^1.15.0",
  23 + "connect-memcached": "^0.2.0",
22 "cookie-parser": "^1.4.1", 24 "cookie-parser": "^1.4.1",
23 "express": "^4.13.1", 25 "express": "^4.13.1",
24 "express-handlebars": "^3.0.0", 26 "express-handlebars": "^3.0.0",
  27 + "express-session": "^1.13.0",
25 "influxdb-winston": "^1.0.1", 28 "influxdb-winston": "^1.0.1",
26 "lodash": "^4.12.0", 29 "lodash": "^4.12.0",
27 "md5": "^2.1.0", 30 "md5": "^2.1.0",
  31 + "memcached": "^2.2.1",
28 "morgan": "^1.7.0", 32 "morgan": "^1.7.0",
29 "oneapm": "^1.2.20", 33 "oneapm": "^1.2.20",
30 "request-promise": "^3.0.0", 34 "request-promise": "^3.0.0",
@@ -34,7 +38,7 @@ @@ -34,7 +38,7 @@
34 }, 38 },
35 "devDependencies": { 39 "devDependencies": {
36 "autoprefixer": "^6.3.6", 40 "autoprefixer": "^6.3.6",
37 - "eslint": "^2.9.0", 41 + "eslint": "^2.10.2",
38 "eslint-config-yoho": "^1.0.1", 42 "eslint-config-yoho": "^1.0.1",
39 "gulp": "^3.9.1", 43 "gulp": "^3.9.1",
40 "gulp-cssnano": "^2.1.2", 44 "gulp-cssnano": "^2.1.2",
@@ -55,14 +59,17 @@ @@ -55,14 +59,17 @@
55 "postcss-position": "^0.4.0", 59 "postcss-position": "^0.4.0",
56 "postcss-short": "^1.4.0", 60 "postcss-short": "^1.4.0",
57 "postcss-sprites": "^3.1.2", 61 "postcss-sprites": "^3.1.2",
58 - "postcss-use": "^2.0.2", 62 + "postcss-use": "^2.1.0",
59 "precss": "^1.4.0", 63 "precss": "^1.4.0",
60 "rewire": "^2.5.1", 64 "rewire": "^2.5.1",
61 "shelljs": "^0.7.0", 65 "shelljs": "^0.7.0",
62 "stylelint": "^6.3.3", 66 "stylelint": "^6.3.3",
63 - "stylelint-config-yoho": "^1.2.2", 67 + "stylelint-config-yoho": "^1.2.3",
64 "webpack": "^1.13.0", 68 "webpack": "^1.13.0",
65 "webpack-dev-server": "^1.14.1", 69 "webpack-dev-server": "^1.14.1",
66 - "webpack-stream": "^3.1.0" 70 + "webpack-stream": "^3.1.0",
  71 + "yoho-handlebars": "^4.0.5",
  72 + "yoho-jquery": "^1.9.1",
  73 + "yoho-jquery-lazyload": "^1.9.7"
67 } 74 }
68 } 75 }
@@ -167,16 +167,19 @@ gulp.task('webpack-dev-server', () => { @@ -167,16 +167,19 @@ gulp.task('webpack-dev-server', () => {
167 167
168 new WebpackDevServer(webpack(devConfig), { 168 new WebpackDevServer(webpack(devConfig), {
169 contentBase: '.', 169 contentBase: '.',
170 - publicPath: '//localhost:5000', 170 + publicPath: '//localhost:5002/',
171 hot: true, 171 hot: true,
172 stats: { 172 stats: {
173 colors: true 173 colors: true
  174 + },
  175 + headers: {
  176 + 'Access-Control-Allow-Origin': '*'
174 } 177 }
175 - }).listen(5000, 'localhost', (err) => { 178 + }).listen(5002, 'localhost', (err) => {
176 if (err) { 179 if (err) {
177 throw new gutil.PluginError('webpack-dev-server', err); 180 throw new gutil.PluginError('webpack-dev-server', err);
178 } 181 }
179 - gutil.log('[webpack-serve]', 'http://localhost:5000/'); 182 + gutil.log('[webpack-serve]', 'http://localhost:5002/');
180 }); 183 });
181 }); 184 });
182 185