Authored by 毕凯

合并 wap 项目的代码

{
"extends": "yoho"
"extends": "yoho",
"parserOptions": {
"sourceType": "module"
}
}
... ...
... ... @@ -17,8 +17,10 @@ const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const favicon = require('serve-favicon');
const session = require('express-session');
const memcached = require('connect-memcached');
const session = require('yoho-express-session');
const memcached = require('yoho-connect-memcached');
const uuid = require('uuid');
const _ = require('lodash');
const pkg = require('./package.json');
const app = express();
... ... @@ -40,17 +42,35 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({extended: false}));
app.use(cookieParser());
app.use(session({
secret: '3e5fec7deca0b8305cefe2ad9d90ff5e',
name: 'PHPSESSID',
prefix: 'yohobuy',
proxy: true,
resave: true,
resave: false,
saveUninitialized: true,
unset: 'destroy',
secret: 'nothing', // 兼容 PHP SESSION,sessionID 不加密
name: 'PHPSESSID', // 兼容 PHP SESSION
genid: () => {
return uuid.v4(); // 兼容 PHP SESSION
},
cookie: {
domain: 'yohobuy.com'
},
store: new MemcachedStore({
hosts: config.memcache.session
hosts: config.memcache.session,
prefix: 'qinsessionsession:', // 兼容 PHP SESSION
key: 'yohobuy_session' // 兼容 PHP SESSION
})
}));
app.use((req, res, next) => {
req.user = {};
// 从 PHP 写的 SESSION 中获取到当前登录用户的 UID
if (req.session && _.isNumber(req.session._LOGIN_UID)) {
req.user.uid = req.session._LOGIN_UID;
}
next();
});
// dispatcher
require('./dispatch')(app);
... ...
... ... @@ -12,12 +12,12 @@ const isTest = process.env.NODE_ENV === 'test';
module.exports = {
port: 6002,
domains: {
api: 'http://192.168.102.205:8080/gateway',
service: 'http://testservice.yoho.cn:28077',
api: 'http://192.168.102.205:8080/gateway/',
service: 'http://testservice.yoho.cn:28077/',
search: 'http://192.168.10.64:8080/yohosearch/'
},
useOneapm: false,
useCache: true,
useCache: false,
memcache: {
master: ['192.168.102.168:12580'],
slave: ['192.168.102.168:12580'],
... ... @@ -52,11 +52,13 @@ module.exports = {
if (isProduction) {
Object.assign(module.exports, {
appName: 'www.yohobuy.com',
useOneapm: true
useOneapm: true,
useCache: true
});
} else if (isTest) {
Object.assign(module.exports, {
appName: 'www.yohobuy.com for test',
useOneapm: true
useOneapm: true,
useCache: true
});
}
... ...
... ... @@ -19,12 +19,10 @@ const serviceApi = config.domains.service;
const searchApi = config.domains.search;
let ApiUrl;
class Http {
class API {
constructor() {
ApiUrl = api;
constructor(baseUrl) {
this.ApiUrl = baseUrl;
}
/**
... ... @@ -39,39 +37,46 @@ class API {
*/
_requestFromAPI(options, cacheOption, reqId) {
let timer = new Timer();
let method = options.method || 'get';
log.info(`${method} api: ${options.url}?${qs.stringify(options.qs)}`);
timer.put('getApi');// 统计时间开始
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);
// 数据校验
if (!result || !result.code) {
log.error('error: 接口返回的数据结构错误,非 JSON');
return Promise.reject({
statusCode: 500,
message: '接口返回内容格式错误'
});
}
// 数据校验无误,写缓存, 否则返回 Slave 缓存服务器的数据
if (result && result.code) {
let cacheTime = _.isNumber(cacheOption) ? cacheOption : 60;
// 写缓存, 否则返回 Slave 缓存服务器的数据
if (config.useCache && cacheOption) {
let cacheTime = _.isNumber(cacheOption) ? cacheOption : 60;
cache.set(`apiCache:${reqId}`, result, cacheTime);
cache.setSlave(`apiCache:${reqId}`, result, cacheTime);
} else {
return this._requestFromCache(options, true);
}
reqId = reqId || this._getReqId(options);
cache.set(`apiCache:${reqId}`, result, cacheTime);
cache.setSlave(`apiCache:${reqId}`, result, 86400); // 二级缓存存储一天
}
log.info(`get api success: use: ${duration}ms`);
return result;
}).catch((error)=>{
}).catch((err)=> {
let duration = timer.put('getApi');// 统计时间结束
log.error(`get api fail: use: ${duration}ms, statusCode: ${error.statusCode}, error: ${error.message}`);
log.error(`${method} api fail: use: ${duration}ms, code:${err.statusCode}, error: ${err.message}`);
log.error(`API: ${options.url}?${qs.stringify(options.qs)}`);
// 使用缓存的时候,读取二级缓存
if (config.useCache) {
if (config.useCache && cacheOption) {
return this._requestFromCache(options, true);
}
return Promise.reject({
error: '接口调用失败'
code: 500,
message: '接口调用失败'
});
});
}
... ... @@ -97,14 +102,22 @@ class API {
}
}
// 读取缓存失败,并且不是二级缓存的时候,调用 API
if (!slave) {
return this._requestFromAPI(options, true, reqId);
}
}).catch(() => {
log.error(slave ? 'get slave cache fail' : 'get master cache fail');
// 读取缓存失败,并且不是二级缓存的时候,调用 API
if (!slave) {
return this._requestFromAPI(options, true, reqId);
}
return Promise.reject({
code: 500,
message: '接口调用失败'
});
});
}
... ... @@ -117,7 +130,7 @@ class API {
*/
get(url, data, cacheOption) {
let options = {
url: `${ApiUrl}${url}`,
url: `${this.ApiUrl}${url}`,
qs: data,
json: true,
timeout: 3000
... ... @@ -138,7 +151,7 @@ class API {
*/
post(url, data) {
let options = {
url: `${ApiUrl}${url}`,
url: `${this.ApiUrl}${url}`,
form: data,
method: 'post',
json: true,
... ... @@ -151,22 +164,27 @@ class API {
all(list) {
if (_.isArray(list)) {
return Promise.all(list);
} else {
return Promise.reject(Error('the parameters of api all method should be Array!'));
}
throw Error('the parameters of api all method should be Array!');
}
}
class ServiceAPI extends API {
class API extends Http {
constructor() {
super(api);
}
}
class ServiceAPI extends Http {
constructor() {
super();
ApiUrl = serviceApi;
super(serviceApi);
}
}
class SearchAPI extends API {
class SearchAPI extends Http {
constructor() {
super();
ApiUrl = searchApi;
super(searchApi);
}
}
... ...
... ... @@ -26,12 +26,10 @@ camelCaseArray = (list) => {
};
camelCase = (data) => {
if (_.isObject(data)) {
data = camelCaseObject(data);
}
if (_.isArray(data)) {
data = camelCaseArray(data);
} else if (_.isObject(data)) {
data = camelCaseObject(data);
}
return data;
... ...
/**
* 获取 UID
* @param {[object]} req
* @return {[string]}
*/
exports.getUid = (req) => {
var _uid = 0,
cookie = req.cookies._UID,
cookieList;
if (req.isApp) {
return req.query.uid || 0;
}
if (cookie) {
cookieList = cookie.split('::');
if (cookieList[1] && !isNaN(cookieList[1])) {
_uid = cookieList[1];
}
}
return _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;
}
}
... ...
{
"name": "m-yohobuy-node",
"name": "yohobuy-node",
"version": "0.0.1",
"private": true,
"description": "A New Yohobuy Project With Express",
... ... @@ -11,33 +11,50 @@
"start": "node app.js",
"dev": "node_modules/.bin/nodemon -e js,hbs -i public/ app.js",
"online": "NODE_ENV=\"production\" node app.js",
"debug": "DEBUG=\"express:*\" node app.js",
"debug": "DEBUG=\"express:*\" node_modules/.bin/nodemon -e js,hbs -i public/ app.js",
"lint-js": "./node_modules/.bin/eslint -c .eslintrc --cache --fix .",
"lint-css": "./node_modules/.bin/stylelint --config .stylelintrc public/**/*.css",
"precommit": "node lint.js"
"lint-css": "./node_modules/.bin/stylelint --config .stylelintrc public/scss/**/*.css",
"precommit": "node lint.js",
"test": "NODE_ENV=test ./node_modules/.bin/nyc ./node_modules/.bin/ava",
"posttest": "./node_modules/.bin/nyc report --reporter=html"
},
"ava": {
"tap": true,
"require": [
"babel-register"
],
"babel": {
"presets": [
"es2015"
]
}
},
"license": "MIT",
"dependencies": {
"bluebird": "^3.3.5",
"bluebird": "^3.4.0",
"body-parser": "^1.15.0",
"connect-memcached": "^0.2.0",
"cookie-parser": "^1.4.1",
"express": "^4.13.1",
"express-handlebars": "^3.0.0",
"express-session": "^1.13.0",
"influxdb-winston": "^1.0.1",
"lodash": "^4.12.0",
"md5": "^2.1.0",
"memcached": "^2.2.1",
"memcached": "^2.2.2",
"morgan": "^1.7.0",
"oneapm": "^1.2.20",
"request-promise": "^3.0.0",
"serve-favicon": "^2.3.0",
"uuid": "^2.0.2",
"winston": "^2.2.0",
"winston-daily-rotate-file": "^1.0.1"
"winston-daily-rotate-file": "^1.0.1",
"yoho-connect-memcached": "^1.0.3",
"yoho-express-session": "^1.0.2"
},
"devDependencies": {
"autoprefixer": "^6.3.6",
"ava": "^0.14.0",
"babel-preset-es2015": "^6.9.0",
"babel-register": "^6.9.0",
"eslint": "^2.10.2",
"eslint-config-yoho": "^1.0.1",
"gulp": "^3.9.1",
... ... @@ -47,8 +64,8 @@
"gulp-sourcemaps": "^2.0.0-alpha",
"gulp-util": "^3.0.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",
... ... @@ -57,19 +74,23 @@
"postcss-crip": "^2.0.0",
"postcss-opacity": "^3.0.0",
"postcss-position": "^0.4.0",
"postcss-pxtorem": "^3.3.1",
"postcss-short": "^1.4.0",
"postcss-sprites": "^3.1.2",
"postcss-use": "^2.1.0",
"postcss-use": "^2.0.2",
"precss": "^1.4.0",
"rewire": "^2.5.1",
"shelljs": "^0.7.0",
"stylelint": "^6.3.3",
"stylelint": "^6.4.1",
"stylelint-config-yoho": "^1.2.3",
"webpack": "^1.13.0",
"webpack-dev-server": "^1.14.1",
"webpack-stream": "^3.1.0",
"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-jquery-lazyload": "^1.9.7",
"yoho-swiper": "^3.3.1"
}
}
... ...
/**
* http api 测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
'use strict';
const test = require('ava');
const sign = require('../../library/sign');
const API = require('../../library/api').API;
const ServiceAPI = require('../../library/api').ServiceAPI;
const SearchAPI = require('../../library/api').SearchAPI;
const getUrl = 'operations/api/v6/category/getCategory';
test('api constructor test', (t) => {
let api = new ServiceAPI();
let api2 = new API();
let api3 = new SearchAPI();
t.true(api !== null);
t.true(api2 !== null);
t.true(api3 !== null);
});
test('api get test', t => {
let api = new ServiceAPI();
return api.get(getUrl, sign.apiSign({})).then(result => {
if (result && result.code) {
t.pass();
} else {
t.fail();
}
});
});
test('api get test, api return an error', t => {
let api = new ServiceAPI();
return api.get(getUrl + '/error', sign.apiSign({})).catch(err => {
// 故意调用一个错误的接口
if (err && err.code === 500) {
t.pass();
} else {
t.fail();
}
});
});
test('api get use cache test', t => {
let api = new ServiceAPI();
return api.get(getUrl, sign.apiSign({}), true).then(result => {
if (result && result.code) {
t.pass();
} else {
t.fail();
}
});
});
test('api post test', t => {
let api = new ServiceAPI();
return api.post(getUrl, sign.apiSign({})).then(result => {
if (result && result.code) {
t.pass();
} else {
t.fail();
}
});
});
test('api multiple call test', (t) => {
let api = new ServiceAPI();
let multi = [api.get(getUrl, sign.apiSign({})), api.get(getUrl, sign.apiSign({}))];
return api.all(multi).then(result => {
if (result.length === 2) {
t.pass();
} else {
t.fail();
}
});
});
test('api multiple fail call test', (t) => {
let api = new ServiceAPI();
return api.all(1).catch((e) => {
if (e) {
t.pass();
} else {
t.fail();
}
});
});
... ...
/**
* cache 测试
*
* @author: jf<jeff.jiang@yoho.cn>
* @date: 2016/5/18
*/
'use strict';
import test from 'ava';
import cache from '../../library/cache';
let testKey = 'test_unit_key:' + (new Date()).getTime();
let testValue = 'anotherValue';
let anotherKey = 'test_unit_key2:' + (new Date()).getTime();
let anotherValue = {a: 1};
let slaveTestKey = 'test_unit_key3:' + (new Date()).getTime();
let slaveTestValue = 'anotherValue3';
test.before('set test key', (t) => {
cache.set(testKey, testValue);
cache.set(anotherKey, anotherValue);
t.pass();
});
test.after('del test key', (t) => {
cache.del(testKey);
cache.del(anotherKey);
t.pass();
});
test('cache get test', (t) => {
return cache.get(testKey).then((v) => {
t.is(v, testValue);
});
});
test('cache get multi test', (t) => {
cache.set(anotherKey, anotherValue);
return cache.getMulti([testKey, anotherKey]).then((values) => {
t.is(values[testKey], testValue);
t.is(values[anotherKey], JSON.stringify(anotherValue));
});
});
test('cache get from slave test', (t) => {
return cache.getFromSlave(testKey).then((v) => {
t.is(v, testValue);
});
});
test('cache get multi from slave test', (t) => {
cache.set(anotherKey, anotherValue);
return cache.getMultiFromSlave([testKey, anotherKey]).then((values) => {
t.is(values[testKey], testValue);
t.is(values[anotherKey], JSON.stringify(anotherValue));
});
});
test('cache set to slave', (t) => {
return cache.setSlave(slaveTestKey, {
value: slaveTestValue
}).then(() => {
return cache.getFromSlave(slaveTestKey);
}).then((v) => {
v = JSON.parse(v);
t.is(v.value, slaveTestValue);
cache.del(slaveTestKey);
});
});
test('cache get test, key is not a string', (t) => {
return cache.get(123).then((v) => {
t.notOk(v);
});
});
test('cache get multi test, key is not an array', (t) => {
return cache.getMulti(123).then((v) => {
t.notOk(v);
});
});
test('cache get from slave test, key is not a string', (t) => {
return cache.getFromSlave(123).then((v) => {
t.notOk(v);
});
});
test('cache get multi from slave test, key is not an array', (t) => {
return cache.getMultiFromSlave(123).then((v) => {
t.notOk(v);
});
});
test('cache set test, key is not a string', (t) => {
return cache.set(123).then((v) => {
t.notOk(v);
});
});
test('cache set multi test, key is not an array', (t) => {
return cache.setSlave(123).then((v) => {
t.notOk(v);
});
});
test('cache del test, key is not a string', (t) => {
return cache.del(123).then((v) => {
t.notOk(v);
});
});
... ...
/**
* 对象键名驼峰测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
import {test} from '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'
}, {
A_B: 'ab_cd'
}];
t.is(camelCase(arr)[1].aB, 'ab_cd');
});
... ...
/**
* 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 logger = require('../../library/logger');
test('logger error test', t => {
logger.error('error test', () => {
t.pass();
});
});
test('logger info test', t => {
logger.info('info test', () => {
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));
});
test('app sign test webSign', t => {
let params = {
uid: '123',
key: '3fc5a9fcea9fea49cce5432202a167ad'
};
t.true(sign.webSign(params));
});
... ...
let expect = require('expect.js');
let Timer = require('../../library/timer');
/**
* Timer 计时类测试
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/05/17
*/
const test = require('ava');
const Timer = require('../../library/timer');
describe('/library/timer', function() { // eslint-disable-line
it('延迟100ms,期望大于或等于100ms', function(done) {// eslint-disable-line
let t = new Timer();
const sleep = (timeout) => {
return new Promise((resolve) => {
setTimeout(() => {
resolve();
}, timeout);
});
};
test.cb('timer class ', t => {
let timer = new Timer();
t.put('aa');
setTimeout(function() {
let time = t.put('aa');
timer.put('test');
sleep(300).then(() => {
let diff = timer.put('test');
expect(Math.round(time) >= 100).to.be.ok();
done();
}, 100);
t.true(diff >= 300);
t.end();
});
});
... ...
require('./library/timer.test');