Authored by biao

resolve config

{
"extends": "yoho",
"rules": {
"no-unused-vars": [
"error",
{
"args": "after-used",
"vars": "all"
}
]
}
"extends": "yoho"
}
... ...
... ... @@ -139,3 +139,4 @@ public/css/*
public/bundle/*
.eslintcache
*.log.*
nbproject/*
... ...
... ... @@ -145,7 +145,7 @@ const getChannelResource = (params) => {
params.new_device = true; // eslint-disable-line
}
return api.get('operations/api/v5/resource/home', sign.apiSign(params)).then(result => {
return api.get('operations/api/v5/resource/home', sign.apiSign(params), 300).then(result => {
if (result && result.code === 200) {
return processFloor(result.data.list);
} else {
... ... @@ -162,7 +162,7 @@ const getChannelResource = (params) => {
const getLeftNav = (choosed) => {
choosed = choosed || 'all';
return api.get('operations/api/v6/category/getCategory', sign.apiSign({})).then(result => {
return api.get('operations/api/v6/category/getCategory', sign.apiSign({}), 300).then(result => {
if (result && result.code === 200) {
return processSideBar(result.data, choosed);
} else {
... ... @@ -179,7 +179,7 @@ const getLeftNav = (choosed) => {
exports.getChannelDate = (params) => {
var channelData = {};
return Promise.all([getChannelResource(params), getLeftNav(params.gender)]).then((data) => {
return api.all([getChannelResource(params), getLeftNav(params.gender)]).then((data) => {
channelData.content = data[0]; // 资源位数据
channelData.sideNav = data[1]; // 侧边栏数据
... ...
/**
* sale controller
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/5/17
*/
'use strict';
// const mRoot = '../models';
const headerModel = require('../../../doraemon/models/header');
// sale model
// const sale = require(`${mRoot}/sale`);
exports.index = (req, res) => {
res.render('sale', {
devEnv: true,
pageHeader: headerModel.setNavHeader('SALE'),
pageFooter: true
});
};
... ...
... ... @@ -6,13 +6,18 @@
'use strict';
const router = require('express').Router();
const router = require('express').Router(); // eslint-disable-line
const cRoot = './controllers';
// 商品详情controller
const detail = require(`${cRoot}/detail`);
// sale controller
const sale = require(`${cRoot}/sale`);
// routers
router.get('/detail/:id/:gid', detail.index);
router.get('/sale', sale.index);
module.exports = router;
... ...
<div class="discount-page yoho-page">
{{# headerBanner}}
{{> product/banner-swipe-and-single}}
{{/ headerBanner}}
</div>
\ No newline at end of file
... ...
{{#if list}}
<div class="banner-top">
<div class="banner-swiper swiper-container">
<ul class="swiper-wrapper">
{{# list}}
<li class="swiper-slide">
<a href="{{url}}">
<img class="swiper-lazy" data-src="{{img}}">
</a>
<div class="swiper-lazy-preloader"></div>
</li>
{{/ list}}
</ul>
</div>
<div class="swiper-pagination">
<div class="pagination-inner">
</div>
</div>
</div>
{{else}}
<div class="banner-top-single">
<a href={{url}}>
<img class="img" src="{{img}}">
</a>
</div>
{{/if}}
... ...
... ... @@ -28,12 +28,12 @@ module.exports = {
infoFile: {
name: 'info',
level: 'info',
filename: 'info.log'
filename: 'log/info.log'
},
errorFile: {
name: 'error',
level: 'error',
filename: 'error.log',
filename: 'log/error.log',
handleExceptions: true
},
udp: { // send by udp
... ...
... ... @@ -7,12 +7,16 @@
'use strict';
const rp = require('request-promise');
const qs = require('querystring');
const md5 = require('md5');
const _ = require('lodash');
const log = require('./logger');
const api = require('../config/common').domains.api;
const serviceApi = require('../config/common').domains.service;
const searchApi = require('../config/common').domains.search;
const cache = require('./cache');
const Timer = require('./timer');
const config = require('../config/common');
const api = config.domains.api;
const serviceApi = config.domains.service;
const searchApi = config.domains.search;
let ApiUrl;
... ... @@ -24,50 +28,107 @@ class API {
}
/**
* get
* @param url String
* @param data Obejct
* 获取请求 ID
*/
get(url, data) {
let options = {
url: `${ApiUrl}${url}`,
qs: data,
json: true
};
_getReqId(options) {
return md5(`${options.url}?${qs.stringify(options.qs || options.form)}`);
}
/**
* 调用接口
*/
_requestFromAPI(options, cacheOption, reqId) {
let timer = new Timer();
timer.put('getApi');// 统计时间开始
let ret = rp(options);
return ret.catch((error)=>{
let duration = timer.put('getApi');// 接口返回
log.error('API GET: %s, parms: %j , durationMs: %d ms error: %s , statusCode: %d',
options.url, options.qs, duration, error.message, error.statusCode);
log.info(`get api: ${options.url}?${qs.stringify(options.qs)}`);
return rp(options).then((result) => {
let duration = timer.put('getApi');// 统计时间结束
log.info(`get api success: use: ${duration}ms`);
if (config.useCache && cacheOption) {
reqId = reqId || this._getReqId(options);
// 数据校验无误,写缓存, 否则返回 Slave 缓存服务器的数据
if (result && result.code) {
let cacheTime = _.isNumber(cacheOption) ? cacheOption : 60;
cache.set(`apiCache:${reqId}`, result, cacheTime);
cache.setSlave(`apiCache:${reqId}`, result, cacheTime);
} else {
return this._requestFromCache(options, true);
}
}
return result;
}).catch((error)=>{
let duration = timer.put('getApi');// 统计时间结束
log.error(`get api fail: use: ${duration}ms, statusCode: ${error.statusCode}, error: ${error.message}`);
// 使用缓存的时候,读取二级缓存
if (config.useCache) {
return this._requestFromCache(options, true);
}
return Promise.reject({
error: '接口调用失败'
});
});
}
/**
* multi get
* @params: urls => Array[Object[url[string], data[object]]]
* 读取缓存
* @param {[object]} options
* @param {[boolean]} slave true: 读取二级缓存
* @return {[type]}
*/
multiGet(urls) {
var rps = [];
_.forEach(urls, function(el) {
rps.push(rp({
url: `${ApiUrl}${el.url}`,
qs: el.data,
json: true
}));
_requestFromCache(options, slave) {
let reqId = this._getReqId(options);
let getCache = slave ? cache.getFromSlave : cache.get;
log.info(`get cache: ${reqId}, url: ${options.url}?${qs.stringify(options.qs)}`);
return getCache(`apiCache:${reqId}`).then((result) => {
if (!_.isNil(result)) {
try {
result = JSON.parse(result);
} finally {
log.info(slave ? 'get slave cache success' : 'get master cache success');
return result;
}
}
if (!slave) {
return this._requestFromAPI(options, true, reqId);
}
}).catch(() => {
log.error(slave ? 'get slave cache fail' : 'get master cache fail');
if (!slave) {
return this._requestFromAPI(options, true, reqId);
}
});
}
return Promise.all(rps).then((d) => {
return d;
});
/**
* 使用 get 请求获取接口
* @param {[string]} url
* @param {[object]} data
* @param {[bool or number]} cacheOption 使用数字时,数字表示缓存时间
* @return {[type]}
*/
get(url, data, cacheOption) {
let options = {
url: `${ApiUrl}${url}`,
qs: data,
json: true,
timeout: 3000
};
// 从缓存获取数据
if (config.useCache && cacheOption) {
return this._requestFromCache(options);
}
return this._requestFromAPI(options, cacheOption);
}
/**
... ... @@ -76,12 +137,22 @@ class API {
* @param data Obejct
*/
post(url, data) {
return rp({
let options = {
url: `${ApiUrl}${url}`,
method: 'post',
form: data,
json: true
});
method: 'post',
json: true,
timeout: 3000
};
return this._requestFromAPI(options);
}
all(list) {
if (_.isArray(list)) {
return Promise.all(list);
}
throw Error('the parameters of api all method should be Array!');
}
}
... ...
/**
* 缓存封装
* 前期使用 memcache, 写方法的时候考虑一下如何转换为 redis
* @author bikai kai.bi@yoho.cn
* @date 2016/05/16
*/
'use strict';
const Promise = require('bluebird');
const Memcached = require('memcached');
const _ = require('lodash');
const log = require('./logger');
const config = require('../config/common');
let master = new Memcached(config.memcache.master, config.memcache);
let slave = new Memcached(config.memcache.slave, config.memcache);
master = Promise.promisifyAll(master);
slave = Promise.promisifyAll(slave);
/**
* 获取缓存
* @param {[string]} key 键
* @return {[type]}
*/
exports.get = (key) => {
if (_.isString(key)) {
return master.getAsync(key);
}
return Promise.resolve();
};
/**
* 批量获取缓存
* @param {[array]} list 字符串数组
* @return {[type]}
*/
exports.getMulti = (list) => {
if (_.isArray(list)) {
return master.getMultiAsync(list);
}
return Promise.resolve();
};
/**
* 获取缓存(从 Slave 服务器)
* @param {[string]} key 键
* @return {[type]}
*/
exports.getFromSlave = (key) => {
if (_.isString(key)) {
return slave.getAsync(key);
}
return Promise.resolve();
};
/**
* 批量获取缓存(从 Slave 服务器)
* @param {[array]} list 字符串数组
* @return {[type]}
*/
exports.getMultiFromSlave = (list) => {
if (_.isArray(list)) {
return slave.getMultiAsync(list);
}
return Promise.resolve();
};
/**
* 写缓存
* @param {[type]} key 键
* @param {[type]} value 值
* @param {[type]} lifetime 生命周期
* @return {[type]}
*/
exports.set = (key, value, lifetime) => {
lifetime = lifetime || 86400;
log.info(`write cache: ${key}`);
if (_.isObject(value)) {
value = JSON.stringify(value);
}
if (_.isString(key)) {
return master.setAsync(key, value, lifetime);
}
return Promise.resolve();
};
/**
* 写缓存(到 Slave 服务器)
* @param {[type]} key 键
* @param {[type]} value 值
* @param {[type]} lifetime 生命周期
* @return {[type]}
*/
exports.setSlave = (key, value, lifetime) => {
lifetime = lifetime || 86400;
if (_.isObject(value)) {
value = JSON.stringify(value);
}
if (_.isString(key)) {
return slave.setAsync(key, value, lifetime);
}
return Promise.resolve();
};
/**
* 删除缓存
* @param {[string]} key 键
* @return {[type]}
*/
exports.del = (key) => {
if (_.isString(key)) {
return master.delAsync(key);
}
return Promise.resolve();
};
... ...
... ... @@ -6,7 +6,7 @@
'use strict';
const _ = require('lodash');
const qs = require('querystring');
const md5 = require('md5');
const privateKey = {
... ... @@ -24,7 +24,7 @@ const privateKey = {
* @return {Object} 排序之后的参数对象
*/
const packageSort = argument => {
var newObj = {};
let newObj = {};
for (let k of Object.keys(argument).sort()) {
newObj[k] = argument[k];
... ... @@ -39,18 +39,11 @@ const packageSort = argument => {
* @return {string} 生成的签名字符串
*/
const makeSign = argument => {
var qs = [];
_.forEach(argument, function(value, key) {
qs.push(key + '=' + _.trim(value));
});
return md5(qs.join('&')).toLowerCase();
return md5(qs.stringify(argument)).toLowerCase();
};
// 生成API签名,调用后端接口的时候有私钥校验
exports.apiSign = (params) => {
/* eslint-disable */
var sign = packageSort(Object.assign({
client_type: 'h5',
... ... @@ -60,18 +53,18 @@ exports.apiSign = (params) => {
screen_size: '720x1280',
v: '7'
}, params));
/* eslint-enable */
return Object.assign(sign, {
sign = Object.assign(sign, {
client_secret: makeSign(sign) // eslint-disable-line camelcase
});
delete sign.private_key;
return sign;
};
// 检查签名,APP 访问 H5 页面的时候需要检查
exports.checkSign = (params) => {
var clientSecret = params.client_secret, // eslint-disable-line camelcase
let clientSecret = params.client_secret, // eslint-disable-line camelcase
sortedParams;
// 忽略部分参数
... ... @@ -88,7 +81,7 @@ exports.checkSign = (params) => {
// 检查签名,APP 访问 H5 页面的时候需要检查, 有可能不同于上边的签名方式
exports.webSign = (params) => {
var webPrivateKey = 'yohobuyapp';
const webPrivateKey = 'yohobuyapp';
return params.key === md5(md5(webPrivateKey) + params.uid);
};
... ...
... ... @@ -25,7 +25,7 @@ class Timer {
if (labelTime) {
let duration = process.hrtime(labelTime);
return this._round(duration[1]);
return this._round(duration[0], duration[1]);
} else {
this.timers[label] = process.hrtime();
}
... ... @@ -35,8 +35,8 @@ class Timer {
* 格式化成毫秒
* @param {Number} value 纳秒
*/
_round(value) {
return Math.round(value / 10000) / 100;
_round(seconds, nanoseconds) {
return Math.round((seconds * 1e9 + nanoseconds) / 10000) / 100;
}
}
... ...
... ... @@ -13,9 +13,9 @@
"online": "NODE_ENV=\"production\" node app.js",
"debug": "DEBUG=\"express:*\" node app.js",
"lint-js": "./node_modules/.bin/eslint -c .eslintrc --cache --fix .",
"lint-css": "./node_modules/.bin/stylelint --config .stylelintrc public/**/*.css",
"lint-css": "./node_modules/.bin/stylelint --config .stylelintrc public/scss/**/*.css",
"precommit": "node lint.js",
"test": "ava"
"test": "./node_modules/.bin/nyc ./node_modules/.bin/ava"
},
"ava": {
"tap": true,
... ... @@ -30,6 +30,7 @@
},
"license": "MIT",
"dependencies": {
"bluebird": "^3.3.5",
"body-parser": "^1.15.0",
"connect-memcached": "^0.2.0",
"cookie-parser": "^1.4.1",
... ... @@ -39,6 +40,7 @@
"influxdb-winston": "^1.0.1",
"lodash": "^4.12.0",
"md5": "^2.1.0",
"memcached": "^2.2.1",
"morgan": "^1.7.0",
"oneapm": "^1.2.20",
"request-promise": "^3.0.0",
... ... @@ -49,8 +51,10 @@
"devDependencies": {
"autoprefixer": "^6.3.6",
"ava": "^0.14.0",
"babel-cli": "^6.8.0",
"babel-preset-es2015": "^6.6.0",
"babel-register": "^6.8.0",
"eslint": "^2.9.0",
"eslint": "^2.10.2",
"eslint-config-yoho": "^1.0.1",
"gulp": "^3.9.1",
"gulp-cssnano": "^2.1.2",
... ... @@ -61,6 +65,7 @@
"husky": "^0.11.4",
"mocha": "^2.4.5",
"nodemon": "1.9.2",
"nyc": "^6.4.3",
"postcss-assets": "^4.0.1",
"postcss-cachebuster": "^0.1.2",
"postcss-calc": "^5.2.1",
... ... @@ -74,17 +79,17 @@
"postcss-sprites": "^3.1.2",
"postcss-use": "^2.0.2",
"precss": "^1.4.0",
"rewire": "^2.5.1",
"shelljs": "^0.7.0",
"stylelint": "^6.3.3",
"stylelint-config-yoho": "^1.2.2",
"stylelint-config-yoho": "^1.2.3",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"webpack-stream": "^3.1.0",
"yoho-fastclick": "0.0.1",
"yoho-hammer": "0.0.1",
"yoho-jquery": "0.0.3",
"yoho-jquery-lazyload": "0.0.2",
"yoho-swiper": "0.0.1"
"yoho-fastclick": "^1.0.6",
"yoho-hammer": "^2.0.7",
"yoho-handlebars": "^4.0.5",
"yoho-jquery": "^1.9.1",
"yoho-jquery-lazyload": "^1.9.7",
"yoho-swiper": "^3.3.1"
}
}
... ...
/**
* http api 测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
const test = require('ava');
const sign = require('../../library/sign');
const API = require('../../library/api').ServiceAPI;
const api = new API();
test('api get test', (t) => {
return api.get('operations/api/v6/category/getCategory', sign.apiSign({})).then(result => {
if (result && result.code === 200) {
t.pass();
} else {
t.fail();
}
});
});
... ...
/**
* 对象键名驼峰测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
const test = require('ava');
const camelCase = require('../../library/camel-case');
test('camel case object', t => {
let o = {
A_B: 'ab_cd'
};
t.is(camelCase(o).aB, 'ab_cd');
});
test('camel case array', t => {
let arr = [{
A_B: 'ab_cd'
}];
t.is(camelCase(arr)[0].aB, 'ab_cd');
});
... ...
const headerModel = require('../../doraemon/models/header');
const test = require('ava').test;
test('test setNavHeader method', t => {
const headerData = headerModel.setNavHeader('逛');
t.is(headerData.navTitle, '逛');
t.true(headerData.backUrl);
t.true(headerData.navBtn);
});
/**
* library helpers 类单元测试
* @author jeff.jiang<jeff.jiang@yoho.cn>
* @date 2016/05/17
*/
'use strict';
const test = require('ava');
const helpers = require('../../library/helpers');
test('qiniu image url handle', t => {
let url = 'http://img11.static.yhbimg.com/yhb-img01/2016/04/18/03/016d50b20cfdec5a91c614b68546bc9d72.jpg?imageView2/{mode}/w/{width}/h/{height}';
let expected = 'http://img11.static.yhbimg.com/yhb-img01/2016/04/18/03/016d50b20cfdec5a91c614b68546bc9d72.jpg?imageView2/2/w/400/h/300';
t.is(helpers.image(url, 400, 300), expected);
});
test('uri format', t => {
let uri = '/test';
let qs = { name: 'yoho' };
let mod = 'list';
let expected = '//list.m.yohobuy.com/test?name=yoho';
t.is(helpers.url(uri, qs, mod), expected);
});
test('upper char to lowercase', t => {
let str = 'ABc';
let expected = 'abc';
t.is(helpers.lowerCase(str), expected);
});
test('lower char to uppercase', t => {
let str = 'abc!';
let expected = 'ABC!';
t.is(helpers.upperCase(str), expected);
});
... ...
/**
* logger 工具类测试
*/
const test = require('ava');
const shelljs = require('shelljs');
const logger = require('../../library/logger');
// const today = () => {
// let now = new Date();
// let s = now.getFullYear();
// if (now.getMonth() < 10) {
// s += '-0' + now.getMonth();
// } else {
// s += now.getMonth();
// }
// if (now.getDay() < 10) {
// s += '-0' + now.getDay();
// } else {
// s += now.getDay();
// }
// };
test.cb('logger test', t => {
shelljs.rm('-f', 'log/*.log.*');
logger.info('xxx', () => {
shelljs.ls('info.log.*').forEach(s => console.log('generate log file:' + s));
t.end();
});
});
test.after('clean test log file ', t => {
shelljs.rm('-f', 'log/*.log.*');
t.pass();
});
... ...
/**
* 签名类测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
const test = require('ava');
const sign = require('../../library/sign');
test('app sign test', t => {
let params = {
client_type: 'h5', // eslint-disable-line
a: 1,
b: 'b'
};
let signedParams = sign.apiSign(params);
t.true(sign.checkSign(signedParams));
});
... ...
/**
* Timer 计时类测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
const test = require('ava');
const Timer = require('../../library/timer');
const sleep = (timeout) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
};
test.cb('timer class ', t => {
let timer = new Timer();
timer.put('test');
sleep(300).then(() => {
let diff = timer.put('test');
t.true(diff >= 300);
t.end();
});
});
... ...