Authored by 沈志敏

Merge branch 'master' of git.yoho.cn:fe/yohobuywap-node into feature/featureTemplate

@@ -51,6 +51,11 @@ app.set('etag', false); @@ -51,6 +51,11 @@ app.set('etag', false);
51 51
52 app.enable('trust proxy'); 52 app.enable('trust proxy');
53 53
  54 +// 请求限制中间件
  55 +if (!app.locals.devEnv) {
  56 + app.use(require('./doraemon/middleware/limiter'));
  57 +}
  58 +
54 // 指定libray目录 59 // 指定libray目录
55 global.utils = path.resolve('./utils'); 60 global.utils = path.resolve('./utils');
56 61
  1 +'use strict';
  2 +const _ = require('lodash');
  3 +const cache = global.yoho.cache.master;
  4 +
  5 +exports.index = (req, res) => {
  6 + res.render('check', {
  7 + width750: true,
  8 + localCss: true
  9 + });
  10 +};
  11 +
  12 +exports.submit = (req, res) => {
  13 + let captchaCode = _.get(req.session, 'captcha');
  14 + let remoteIp = req.get('X-Forwarded-For') || req.ip;
  15 +
  16 + if (remoteIp.indexOf(',') > 0) {
  17 + let arr = remoteIp.split(',');
  18 +
  19 + remoteIp = arr[0];
  20 + }
  21 +
  22 + if (req.body.captcha === captchaCode) {
  23 + let key = `pc:limiter:${remoteIp}`;
  24 + cache.delAsync(key).then(() => {
  25 + return res.json({
  26 + code: 200
  27 + });
  28 + }).catch(() => {
  29 + return res.json({
  30 + code: 400
  31 + });
  32 + });
  33 + } else {
  34 + return res.json({
  35 + code: 400
  36 + });
  37 + }
  38 +
  39 +};
@@ -9,9 +9,12 @@ @@ -9,9 +9,12 @@
9 const router = require('express').Router(); // eslint-disable-line 9 const router = require('express').Router(); // eslint-disable-line
10 const cRoot = './controllers'; 10 const cRoot = './controllers';
11 const ads = require(`${cRoot}/ads`); 11 const ads = require(`${cRoot}/ads`);
  12 +const check = require(`${cRoot}/check`);
12 13
13 // routers 14 // routers
14 15
15 router.get('/ads', ads.index); 16 router.get('/ads', ads.index);
  17 +router.get('/check', check.index);
  18 +router.post('/check/submit', check.submit);
16 19
17 module.exports = router; 20 module.exports = router;
  1 +<div class="check-page">
  2 + <div class="title">请输入正确的验证码,继续访问</div>
  3 + <div id="js-img-check"></div>
  4 + <div class="submit">
  5 + 确认
  6 + </div>
  7 +</div>
@@ -181,6 +181,7 @@ exports.orderSub = (req, res, next) => { @@ -181,6 +181,7 @@ exports.orderSub = (req, res, next) => {
181 let yohoCoin = req.body.yohoCoin || 0; 181 let yohoCoin = req.body.yohoCoin || 0;
182 let skuList = req.body.skuList || ''; 182 let skuList = req.body.skuList || '';
183 let orderInfo; 183 let orderInfo;
  184 + let isWechat = req.yoho.isWechat;
184 185
185 try { 186 try {
186 orderInfo = JSON.parse(req.cookies['order-info']); 187 orderInfo = JSON.parse(req.cookies['order-info']);
@@ -260,11 +261,11 @@ exports.orderSub = (req, res, next) => { @@ -260,11 +261,11 @@ exports.orderSub = (req, res, next) => {
260 261
261 result = yield cartModel.orderSub(uid, addressId, 'bundle', deliveryTimeId, 262 result = yield cartModel.orderSub(uid, addressId, 'bundle', deliveryTimeId,
262 deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode, 263 deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode,
263 - yohoCoin, null, unionKey, userAgent, times, activityInfo, ip); 264 + yohoCoin, null, unionKey, userAgent, times, activityInfo, ip, isWechat);
264 } else { 265 } else {
265 result = yield cartModel.orderSub(uid, addressId, cartType, deliveryTimeId, 266 result = yield cartModel.orderSub(uid, addressId, cartType, deliveryTimeId,
266 deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode, 267 deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode,
267 - yohoCoin, skuList, unionKey, userAgent, null, null, ip); 268 + yohoCoin, skuList, unionKey, userAgent, null, null, ip, isWechat);
268 } 269 }
269 270
270 // 提交成功清除Cookie 271 // 提交成功清除Cookie
@@ -178,12 +178,13 @@ exports.ticketsOrderCompute = (uid, productSku, buyNumber, yohoCoin) => { @@ -178,12 +178,13 @@ exports.ticketsOrderCompute = (uid, productSku, buyNumber, yohoCoin) => {
178 * @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息 178 * @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息
179 * @param int $times 179 * @param int $times
180 * @param null $activityInfo 套餐数据 180 * @param null $activityInfo 套餐数据
  181 + * @param isWechat 是否是微信商城
181 * @return array 接口返回的数据 182 * @return array 接口返回的数据
182 */ 183 */
183 exports.orderSub = (uid, addressId, cartType, deliveryTime, 184 exports.orderSub = (uid, addressId, cartType, deliveryTime,
184 deliveryWay, invoices, paymentId, paymentType, remark, 185 deliveryWay, invoices, paymentId, paymentType, remark,
185 couponCode, yohoCoin, skuList, qhyUnio, 186 couponCode, yohoCoin, skuList, qhyUnio,
186 - userAgent, times, activityInfo, ip) => { 187 + userAgent, times, activityInfo, ip, isWechat) => {
187 if (!qhyUnio) { 188 if (!qhyUnio) {
188 qhyUnio = ''; 189 qhyUnio = '';
189 } 190 }
@@ -215,7 +216,7 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime, @@ -215,7 +216,7 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime,
215 return shoppingAPI.orderSub(uid, addressId, cartType, deliveryTime, 216 return shoppingAPI.orderSub(uid, addressId, cartType, deliveryTime,
216 deliveryWay, invoices, paymentId, paymentType, 217 deliveryWay, invoices, paymentId, paymentType,
217 remark, couponCode, yohoCoin, skuList, qhyUnio, 218 remark, couponCode, yohoCoin, skuList, qhyUnio,
218 - userAgent, times, activityInfo, ip).then(orderSubRes => { 219 + userAgent, times, activityInfo, ip, isWechat).then(orderSubRes => {
219 let finalResult = {}; 220 let finalResult = {};
220 221
221 if (orderSubRes && orderSubRes.data && orderSubRes.data.is_hint === 'Y') { 222 if (orderSubRes && orderSubRes.data && orderSubRes.data.is_hint === 'Y') {
@@ -7,6 +7,7 @@ @@ -7,6 +7,7 @@
7 const _ = require('lodash'); 7 const _ = require('lodash');
8 const api = global.yoho.API; 8 const api = global.yoho.API;
9 const helpers = global.yoho.helpers; 9 const helpers = global.yoho.helpers;
  10 +const utils = require('../../../utils');
10 11
11 const genderMap = { 12 const genderMap = {
12 boys: '1,3', 13 boys: '1,3',
@@ -33,27 +34,27 @@ let _processCateData = (list, channel) => { @@ -33,27 +34,27 @@ let _processCateData = (list, channel) => {
33 // 如果有二级菜单,二级菜单跳转,否则一级菜单跳转 34 // 如果有二级菜单,二级菜单跳转,否则一级菜单跳转
34 if (firstItem.sub && firstItem.sub.length) { 35 if (firstItem.sub && firstItem.sub.length) {
35 _.map(firstItem.sub, function(secondItem) { 36 _.map(firstItem.sub, function(secondItem) {
36 - secondItem.url = helpers.urlFormat('/', { 37 + secondItem.url = helpers.urlFormat('/', utils.mapSort({
37 sort: _.get(secondItem, 'relation_parameter.sort'), 38 sort: _.get(secondItem, 'relation_parameter.sort'),
38 sort_name: secondItem.category_name, 39 sort_name: secondItem.category_name,
39 gender: genderMap[key] || '' 40 gender: genderMap[key] || ''
40 - }, 'list'); 41 + }), 'list');
41 }); 42 });
42 43
43 firstItem.sub.unshift({ 44 firstItem.sub.unshift({
44 category_name: `全部${firstItem.category_name}`, 45 category_name: `全部${firstItem.category_name}`,
45 - url: helpers.urlFormat('/', { 46 + url: helpers.urlFormat('/', utils.mapSort({
46 sort: _.get(firstItem, 'relation_parameter.sort'), 47 sort: _.get(firstItem, 'relation_parameter.sort'),
47 sort_name: firstItem.category_name, 48 sort_name: firstItem.category_name,
48 gender: genderMap[key] || '' 49 gender: genderMap[key] || ''
49 - }, 'list') 50 + }), 'list')
50 }); 51 });
51 } else { 52 } else {
52 - firstItem.url = helpers.urlFormat('/', { 53 + firstItem.url = helpers.urlFormat('/', utils.mapSort({
53 sort: _.get(firstItem, 'relation_parameter.sort'), 54 sort: _.get(firstItem, 'relation_parameter.sort'),
54 sort_name: firstItem.category_name, 55 sort_name: firstItem.category_name,
55 gender: genderMap[key] || '' 56 gender: genderMap[key] || ''
56 - }, 'list'); 57 + }), 'list');
57 } 58 }
58 }); 59 });
59 }); 60 });
@@ -103,7 +103,7 @@ const editorRedirect = (req, res, next) => { @@ -103,7 +103,7 @@ const editorRedirect = (req, res, next) => {
103 param = '?' + param; 103 param = '?' + param;
104 } 104 }
105 redirectUrl += `-${id}/${param}`; 105 redirectUrl += `-${id}/${param}`;
106 - res.redirect(redirectUrl); 106 + res.redirect(301, redirectUrl);
107 } else { 107 } else {
108 return next(); 108 return next();
109 } 109 }
@@ -469,7 +469,7 @@ const indexRedirect = (req, res, next) => { @@ -469,7 +469,7 @@ const indexRedirect = (req, res, next) => {
469 param = '?' + param; 469 param = '?' + param;
470 } 470 }
471 redirectUrl += `${id}.html${param}`; 471 redirectUrl += `${id}.html${param}`;
472 - res.redirect(redirectUrl); 472 + res.redirect(301, redirectUrl);
473 } else { 473 } else {
474 return next(); 474 return next();
475 } 475 }
@@ -111,7 +111,9 @@ const local = { @@ -111,7 +111,9 @@ const local = {
111 res.render('login', { 111 res.render('login', {
112 width750: true, 112 width750: true,
113 loginIndex: true, // 模板中使用JS的标识 113 loginIndex: true, // 模板中使用JS的标识
114 - captchaShow: _.get(req.session, 'login.errorCount') <= 0, 114 +
  115 + // captchaShow: _.get(req.session, 'login.errorCount') <= 0,
  116 + captchaShow: true, // 170306 因为暴力破解密码问题,要求每次都展示验证码
115 117
116 // 返回的URL链接 118 // 返回的URL链接
117 backUrl: 'javascript:history.go(-1)', // eslint-disable-line 119 backUrl: 'javascript:history.go(-1)', // eslint-disable-line
@@ -162,7 +164,9 @@ const local = { @@ -162,7 +164,9 @@ const local = {
162 // 返回的URL链接 164 // 返回的URL链接
163 backUrl: 'javascript:history.go(-1)', // eslint-disable-line 165 backUrl: 'javascript:history.go(-1)', // eslint-disable-line
164 loginInternational: true, // 模板中使用JS的标识 166 loginInternational: true, // 模板中使用JS的标识
165 - captchaShow: _.get(req.session, 'login.errorCount') <= 0, 167 +
  168 + // captchaShow: _.get(req.session, 'login.errorCount') <= 0,
  169 + captchaShow: true, // 170306 因为暴力破解密码问题,要求每次都展示验证码
166 isPassportPage: true, // 模板中模块标识 170 isPassportPage: true, // 模板中模块标识
167 headerText: '登录', 171 headerText: '登录',
168 areaCode: '+86', // 默认区号 172 areaCode: '+86', // 默认区号
@@ -173,6 +177,10 @@ const local = { @@ -173,6 +177,10 @@ const local = {
173 }); 177 });
174 }, 178 },
175 login: (req, res, next) => { 179 login: (req, res, next) => {
  180 +
  181 + // 170306 因为暴力破解密码问题,要求每次都校验验证码
  182 + _.set(req.session, 'login.errorCount', 0);
  183 +
176 let count = _.get(req.session, 'login.errorCount'); 184 let count = _.get(req.session, 'login.errorCount');
177 185
178 if (count == null) { // eslint-disable-line 186 if (count == null) { // eslint-disable-line
@@ -419,16 +419,19 @@ let setPassword = (req, res, next) => { @@ -419,16 +419,19 @@ let setPassword = (req, res, next) => {
419 419
420 RegService.regMobileAes(area, mobile, password, shoppingKey, smsCode, isFromMy).then((result) => { 420 RegService.regMobileAes(area, mobile, password, shoppingKey, smsCode, isFromMy).then((result) => {
421 if (!result.code || result.code !== 200) { 421 if (!result.code || result.code !== 200) {
422 - return Promise.reject(result); 422 + return res.send(result);
423 } 423 }
424 if (!result.data || !result.data.uid) { 424 if (!result.data || !result.data.uid) {
425 - return Promise.reject(result); 425 + return res.send(result);
426 } 426 }
427 427
428 resultCopy = result; 428 resultCopy = result;
429 429
430 return AuthHelper.syncUserSession(result.data.uid, req, res); 430 return AuthHelper.syncUserSession(result.data.uid, req, res);
431 }).then(() => { 431 }).then(() => {
  432 + if (!resultCopy) {
  433 + return;
  434 + }
432 // 返回跳转到来源页面 435 // 返回跳转到来源页面
433 let refer = req.cookies.refer; 436 let refer = req.cookies.refer;
434 437
@@ -86,7 +86,7 @@ const newDetail = { @@ -86,7 +86,7 @@ const newDetail = {
86 reject(); 86 reject();
87 } 87 }
88 }).then(skn => { 88 }).then(skn => {
89 - return res.redirect(`/product/${skn}.html${param}`); 89 + return res.redirect(301, `/product/${skn}.html${param}`);
90 }, () => { 90 }, () => {
91 return next(); 91 return next();
92 }); 92 });
@@ -142,8 +142,8 @@ router.get('/search/fuzzyDatas', search.fuzzyDatas); @@ -142,8 +142,8 @@ router.get('/search/fuzzyDatas', search.fuzzyDatas);
142 router.get('/search/search', search.search); 142 router.get('/search/search', search.search);
143 143
144 // 品类 144 // 品类
145 -router.get('/index/index', list.category);  
146 -router.get('/list/index', list.category); // 兼容 PC 的链接 145 +router.get('/index/index', rewrite.sortParams, list.category);
  146 +router.get('/list/index', rewrite.sortParams, list.category); // 兼容 PC 的链接
147 147
148 // 品牌 | 店铺 148 // 品牌 | 店铺
149 router.get('/index/shopAppCookie', list.shopAppCookie); 149 router.get('/index/shopAppCookie', list.shopAppCookie);
@@ -153,11 +153,12 @@ exports.checkTickets = (uid, productSku, buyNumber, useYohoCoin, yohoCoinMode) = @@ -153,11 +153,12 @@ exports.checkTickets = (uid, productSku, buyNumber, useYohoCoin, yohoCoinMode) =
153 * @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息 153 * @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息
154 * @param $times 154 * @param $times
155 * @param null $activityInfo 套餐信息 155 * @param null $activityInfo 套餐信息
  156 + * @param isWechat 是否是微信商城
156 * @return array 接口返回的数据 157 * @return array 接口返回的数据
157 */ 158 */
158 exports.orderSub = (uid, addressId, cartType, deliveryTime, 159 exports.orderSub = (uid, addressId, cartType, deliveryTime,
159 deliveryWay, invoices, paymentId, paymentType, remark, couponCode, 160 deliveryWay, invoices, paymentId, paymentType, remark, couponCode,
160 - yohoCoin, skuList, qhyUnion, userAgent, times, activityInfo, ip) => { 161 + yohoCoin, skuList, qhyUnion, userAgent, times, activityInfo, ip, isWechat) => {
161 if (!activityInfo) { 162 if (!activityInfo) {
162 activityInfo = null; 163 activityInfo = null;
163 } 164 }
@@ -226,6 +227,11 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime, @@ -226,6 +227,11 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime,
226 params.qhy_union = qhyUnion; 227 params.qhy_union = qhyUnion;
227 } 228 }
228 229
  230 + // 是否是微信商城
  231 + if (isWechat) {
  232 + params.client_type = 'wechat';
  233 + }
  234 +
229 return api.post('', params, { 235 return api.post('', params, {
230 headers: { 236 headers: {
231 'X-Forwarded-For': ip || '', 237 'X-Forwarded-For': ip || '',
@@ -72,7 +72,6 @@ module.exports = { @@ -72,7 +72,6 @@ module.exports = {
72 udp: { // send by udp 72 udp: { // send by udp
73 measurement: 'yohobuy_wap_node_log', 73 measurement: 'yohobuy_wap_node_log',
74 level: 'error', // logger level 74 level: 'error', // logger level
75 - host: 'influxdblog.web.yohoops.org', // influxdb host  
76 port: '4444' // influxdb port 75 port: '4444' // influxdb port
77 }, 76 },
78 console: { 77 console: {
@@ -107,7 +106,8 @@ module.exports = { @@ -107,7 +106,8 @@ module.exports = {
107 key: '7e6f3307b64cc87c79c472814b88f7fb', 106 key: '7e6f3307b64cc87c79c472814b88f7fb',
108 appSecret: 'ce21ae4a3f93852279175a167e54509b', 107 appSecret: 'ce21ae4a3f93852279175a167e54509b',
109 notifyUrl: domains.service + 'payment/weixin_notify', 108 notifyUrl: domains.service + 'payment/weixin_notify',
110 - } 109 + },
  110 + maxQps: 1200
111 }; 111 };
112 112
113 if (isProduction) { 113 if (isProduction) {
  1 +'use strict';
  2 +const limiter = require('../middleware/limiter/index');
  3 +
  4 +module.exports = (req, res, next) => {
  5 + return limiter(req, res, next);
  6 +};
  1 +'use strict';
  2 +
  3 +const _ = require('lodash');
  4 +const logger = global.yoho.logger;
  5 +const ip = require('./rules/ip-list');
  6 +const userAgent = require('./rules/useragent');
  7 +const qpsLimiter = require('./rules/qps-limit');
  8 +const fakerLimiter = require('./rules/faker-limit');
  9 +const captchaPolicy = require('./policies/captcha');
  10 +const reporterPolicy = require('./policies/reporter');
  11 +
  12 +const IP_WHITE_LIST = [
  13 + // '106.38.38.146',
  14 + // '218.94.75.58'
  15 +];
  16 +
  17 +const limiter = (rule, policy, context) => {
  18 + return rule(context, policy);
  19 +};
  20 +
  21 +module.exports = (req, res, next) => {
  22 + let remoteIp = req.get('X-Forwarded-For') || req.connection.remoteAddress;
  23 + logger.debug('request remote ip: ', remoteIp);
  24 +
  25 + if (remoteIp.indexOf(',') > 0) {
  26 + let arr = remoteIp.split(',');
  27 +
  28 + remoteIp = arr[0];
  29 + }
  30 +
  31 + const excluded = _.includes(IP_WHITE_LIST, remoteIp);
  32 + const enabled = !_.get(req.app.locals, 'wap.sys.noLimiter');
  33 +
  34 + // 判断获取remoteIp成功,并且开关未关闭
  35 + if (enabled && remoteIp && !excluded) {
  36 + const context = {
  37 + req: req,
  38 + res: res,
  39 + next: next,
  40 + remoteIp: remoteIp
  41 + };
  42 +
  43 + Promise.all([
  44 + limiter(userAgent, captchaPolicy, context),
  45 + limiter(ip, captchaPolicy, context),
  46 + limiter(qpsLimiter, captchaPolicy, context),
  47 + //limiter(fakerLimiter, reporterPolicy, context)
  48 + ]).then((results) => {
  49 + let allPass = true, exclusion = false, policy = null;
  50 +
  51 + logger.debug('limiter result: ' + JSON.stringify(results));
  52 +
  53 + _.forEach(results, (result) => {
  54 + if (typeof result === 'object' && !exclusion) {
  55 + exclusion = result.exclusion;
  56 + }
  57 +
  58 + if (!excluded && typeof result === 'function') {
  59 + allPass = false;
  60 + }
  61 +
  62 + if (typeof result === 'function') {
  63 + policy = result;
  64 + }
  65 + });
  66 +
  67 + if (exclusion) {
  68 + return next();
  69 + } else if (!allPass && policy) {
  70 + policy(req, res, next);
  71 + } else {
  72 + return next();
  73 + }
  74 +
  75 + }).catch((err) => {
  76 + logger.error(err);
  77 + return next();
  78 + });
  79 + } else {
  80 + return next();
  81 + }
  82 +};
  1 +'use strict';
  2 +
  3 +const helpers = global.yoho.helpers;
  4 +const _ = require('lodash');
  5 +
  6 +const WHITE_LIST = [
  7 + '/3party/check',
  8 + '/passport/imagesNode',
  9 + '/passport/cert/headerTip',
  10 + '/passport/captcha/get',
  11 + '/3party/check/submit'
  12 +];
  13 +
  14 +module.exports = (req, res, next) => {
  15 + let refer = req.method === 'GET' ? req.get('Referer') : '';
  16 + let limitAPI = helpers.urlFormat('/3party/check', {refer: refer});
  17 + let limitPage = helpers.urlFormat('/3party/check', {refer: req.protocol + '://' + req.get('host') + req.originalUrl});
  18 +
  19 + if (_.indexOf(WHITE_LIST, req.path) >= 0) {
  20 + return next();
  21 + }
  22 +
  23 + if (req.xhr) {
  24 + return res.json({
  25 + code: 400,
  26 + data: {refer: limitAPI}
  27 + });
  28 + }
  29 +
  30 + return res.redirect(limitPage);
  31 +};
  1 +'use strict';
  2 +
  3 +module.exports = (req, res, next) => {
  4 + return next();
  5 +};
  1 +'use strict';
  2 +
  3 +const logger = global.yoho.logger;
  4 +const cache = global.yoho.cache.master;
  5 +const ONE_DAY = 60 * 60 * 24;
  6 +
  7 +module.exports = (limiter, policy) => {
  8 + const req = limiter.req,
  9 + res = limiter.res,
  10 + next = limiter.next;
  11 +
  12 + const key = `pc:limiter:faker:${limiter.remoteIp}`;
  13 +
  14 + if (req.header('X-Requested-With') === 'XMLHttpRequest') {
  15 + cache.decrAsync(key, 1);
  16 + }
  17 +
  18 + res.on('render', function() {
  19 + cache.incrAsync(key, 1);
  20 + });
  21 +
  22 + return cache.getAsync(key).then((result) => {
  23 + if (result) {
  24 + if (result > 100) {
  25 + return Promise.resolve(policy);//policy(req, res, next);
  26 + } else {
  27 + return Promise.resolve(true);
  28 + }
  29 + } else {
  30 + cache.setAsync(key, 1, ONE_DAY); // 设置key,1m失效
  31 + return Promise.resolve(true);
  32 + }
  33 + });
  34 +};
  1 +'use strict';
  2 +
  3 +const cache = global.yoho.cache.master;
  4 +const _ = require('lodash');
  5 +
  6 +module.exports = (limiter) => {
  7 + const key = `pc:limiter:${limiter.remoteIp}`;
  8 +
  9 + return cache.getAsync(key).then((result) => {
  10 + if (result && _.isNumber(result)) {
  11 + return Promise.resolve({
  12 + exclusion: result === -1
  13 + });
  14 + } else {
  15 + return Promise.resolve(true);
  16 + }
  17 + });
  18 +};
  1 +'use strict';
  2 +
  3 +const logger = global.yoho.logger;
  4 +const cache = global.yoho.cache.master;
  5 +const config = global.yoho.config;
  6 +const ONE_DAY = 60 * 60 * 24;
  7 +const MAX_QPS = config.maxQps;
  8 +const _ = require('lodash');
  9 +
  10 +const PAGES = {
  11 + '/product/\\/pro_([\\d]+)_([\\d]+)\\/(.*)/': 5,
  12 + '/product/list/index': 5
  13 +};
  14 +
  15 +function urlJoin(a, b) {
  16 + if (_.endsWith(a, '/') && _.startsWith(b, '/')) {
  17 + return a + b.substring(1, b.length);
  18 + } else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) {
  19 + return a + '/' + b;
  20 + } else {
  21 + return a + b;
  22 + }
  23 +}
  24 +
  25 +module.exports = (limiter, policy) => {
  26 + const req = limiter.req,
  27 + res = limiter.res,
  28 + next = limiter.next;
  29 +
  30 + const key = `pc:limiter:${limiter.remoteIp}`;
  31 +
  32 + res.on('render', function() {
  33 + let route = req.route ? req.route.path : '';
  34 + let appPath = req.app.mountpath;
  35 +
  36 + if (_.isArray(route) && route.length > 0) {
  37 + route = route[0];
  38 + }
  39 +
  40 + let pageKey = urlJoin(appPath, route.toString()); // route may be a regexp
  41 + let pageIncr = PAGES[pageKey] || 0;
  42 +
  43 + if (/^\/p([\d]+)/.test(req.path)) {
  44 + pageIncr = 5;
  45 + }
  46 +
  47 + if (pageIncr > 0) {
  48 + cache.incrAsync(key, pageIncr, (err) => {});
  49 + }
  50 + });
  51 +
  52 + return cache.getAsync(key).then((result) => {
  53 + logger.debug('qps limiter: ' + key + '@' + result + ' max: ' + MAX_QPS);
  54 +
  55 + if (result && _.isNumber(result)) {
  56 +
  57 + if (result === -1) {
  58 + return Promise.resolve(true);
  59 + }
  60 +
  61 + if (result > MAX_QPS) { // 判断 qps
  62 + cache.touch(key, ONE_DAY);
  63 + logger.debug('req limit', key);
  64 +
  65 + return Promise.resolve(policy);
  66 + } else {
  67 + cache.incrAsync(key, 1); // qps + 1
  68 + return Promise.resolve(true);
  69 +
  70 + }
  71 + } else {
  72 + cache.setAsync(key, 1, 60); // 设置key,1m失效
  73 + return Promise.resolve(true);
  74 + }
  75 + });
  76 +};
  1 +'use strict';
  2 +
  3 +const cache = global.yoho.cache.master;
  4 +const _ = require('lodash');
  5 +const logger = global.yoho.logger;
  6 +
  7 +
  8 +module.exports = (limiter, policy) => {
  9 + const req = limiter.req,
  10 + res = limiter.res,
  11 + next = limiter.next;
  12 + const blackKey = 'pc:limiter:ua:black',
  13 + whiteKey = 'pc:limiter:ua:white';
  14 +
  15 + const ua = limiter.req.header('User-Agent');
  16 +
  17 + return Promise.all([
  18 + cache.getAsync(blackKey),
  19 + cache.getAsync(whiteKey)
  20 + ]).then((args) => {
  21 + const blacklist = args[0] || [], whitelist = args[1] || [];
  22 +
  23 + if (blacklist.length === 0 && whitelist.length === 0) {
  24 + return Promise.resolve(true);
  25 + }
  26 +
  27 + const test = (list) => {
  28 + let result = false;
  29 +
  30 + _.each(list, (item) => {
  31 + let regexp;
  32 +
  33 + try {
  34 + regexp = new RegExp(item);
  35 + } catch (e) {
  36 + logger.error(e);
  37 + }
  38 +
  39 + if (regexp.test(ua)) {
  40 + result = true;
  41 + }
  42 + });
  43 +
  44 + return result;
  45 + };
  46 +
  47 + if (test(blacklist)) {
  48 + return Promise.resolve(policy);
  49 + } else if (test(whitelist)) {
  50 + return Promise.resolve({
  51 + exclusion: true
  52 + });
  53 + } else {
  54 + return Promise.resolve(true);
  55 + }
  56 + });
  57 +
  58 +};
@@ -7,7 +7,13 @@ @@ -7,7 +7,13 @@
7 7
8 const typeLib = require('../../config/type-lib'); 8 const typeLib = require('../../config/type-lib');
9 const _ = require('lodash'); 9 const _ = require('lodash');
  10 +const utils = require('../../utils');
  11 +const helpers = global.yoho.helpers;
10 12
  13 +
  14 +/**
  15 + * 解析url规则中的参数
  16 + */
11 const resolve = (req, res, next) => { 17 const resolve = (req, res, next) => {
12 let path, 18 let path,
13 params = { 19 params = {
@@ -44,6 +50,9 @@ const resolve = (req, res, next) => { @@ -44,6 +50,9 @@ const resolve = (req, res, next) => {
44 next(); 50 next();
45 }; 51 };
46 52
  53 +/**
  54 + * 简介channel参数
  55 + */
47 const channel = (req, res, next) => { 56 const channel = (req, res, next) => {
48 let channelName; 57 let channelName;
49 58
@@ -79,7 +88,26 @@ const channel = (req, res, next) => { @@ -79,7 +88,26 @@ const channel = (req, res, next) => {
79 next(); 88 next();
80 }; 89 };
81 90
  91 +/**
  92 + * 参数排序
  93 + */
  94 +const sortParams = (req, res, next) => {
  95 + let sorts = utils.mapSort(req.query);
  96 + let queryKeys = _.keys(req.query);
  97 + let index = 0;
  98 + let matched = _.map(sorts, (val, key) => {
  99 + return key === queryKeys[index++];
  100 + });
  101 +
  102 + if (_.every(matched, match => match)) {
  103 + return next();
  104 + } else {
  105 + return res.redirect(helpers.urlFormat('/', sorts, 'list'));
  106 + }
  107 +};
  108 +
82 module.exports = { 109 module.exports = {
83 resolve, 110 resolve,
84 - channel 111 + channel,
  112 + sortParams
85 }; 113 };
1 { 1 {
2 "name": "m-yohobuy-node", 2 "name": "m-yohobuy-node",
3 - "version": "5.4.25", 3 + "version": "5.4.30",
4 "private": true, 4 "private": true,
5 "description": "A New Yohobuy Project With Express", 5 "description": "A New Yohobuy Project With Express",
6 "repository": { 6 "repository": {
@@ -48,7 +48,7 @@ @@ -48,7 +48,7 @@
48 "xml2js": "^0.4.17", 48 "xml2js": "^0.4.17",
49 "yoho-express-session": "^2.0.0", 49 "yoho-express-session": "^2.0.0",
50 "yoho-node-lib": "^0.2.8", 50 "yoho-node-lib": "^0.2.8",
51 - "yoho-zookeeper": "^1.0.6" 51 + "yoho-zookeeper": "^1.0.8"
52 }, 52 },
53 "devDependencies": { 53 "devDependencies": {
54 "autoprefixer": "^6.7.4", 54 "autoprefixer": "^6.7.4",
  1 +require('3party/check.page.css');
  2 +require('../common');
  3 +// 图片验证码
  4 +let ImgCheck = require('plugin/img-check');
  5 +
  6 +let imgCheck = new ImgCheck('#js-img-check', {
  7 + useREM: {
  8 + rootFontSize: 40,
  9 + picWidth: 150
  10 + }
  11 +});
  12 +
  13 +imgCheck.init();
  14 +
  15 +$(function() {
  16 + $('.submit').on('click', function() {
  17 + $.ajax({
  18 + method: 'POST',
  19 + url: '/3party/check/submit',
  20 + data: {
  21 + captcha: $.trim(imgCheck.getResults())
  22 + },
  23 + success: function(ret) {
  24 + if (ret.code === 200) {
  25 + window.location.href = decodeURIComponent(window.queryString.refer) || '//m.yohobuy.com';
  26 + } else {
  27 + imgCheck.refresh();
  28 + }
  29 + }
  30 + });
  31 + });
  32 +});
@@ -18,9 +18,9 @@ require('activity/promotion/promotion.page.css'); @@ -18,9 +18,9 @@ require('activity/promotion/promotion.page.css');
18 18
19 var share = require('../common/share'); 19 var share = require('../common/share');
20 share({ 20 share({
21 - title: 'YOHO!BUY有货邀请同学认证赠好礼!立享【学生专属】4大特权!', 21 + title: '【有货】学生认证看过来!专享特价,任性分期,立即走起→',
22 link: location.href, 22 link: location.href,
23 - desc: '每邀请1名同学完成认证立赠300有货币!上不封顶!', 23 + desc: '邀请同学认证更有惊喜好礼相送~',
24 imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png' 24 imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png'
25 }); 25 });
26 26
@@ -303,9 +303,9 @@ $( @@ -303,9 +303,9 @@ $(
303 var shareUrl = location.href.replace('userUid=', 'oldUid='); 303 var shareUrl = location.href.replace('userUid=', 'oldUid=');
304 304
305 share({ 305 share({
306 - title: 'YOHO!BUY有货邀请同学认证赠好礼!立享【学生专属】4大特权!', 306 + title: '【有货】学生认证看过来!专享特价,任性分期,立即走起→',
307 link: shareUrl, 307 link: shareUrl,
308 - desc: '每邀请1名同学完成认证立赠300有货币!上不封顶!', 308 + desc: '邀请同学认证更有惊喜好礼相送~',
309 imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png' 309 imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png'
310 }); 310 });
311 } 311 }
@@ -87,7 +87,9 @@ if (canOpenApp()) { @@ -87,7 +87,9 @@ if (canOpenApp()) {
87 }, 2000); 87 }, 2000);
88 88
89 if (isiOS) { 89 if (isiOS) {
90 - window.location.href = appPath; 90 + setTimeout(function() {
  91 + window.location.href = appPath;
  92 + }, 2000);
91 } else { 93 } else {
92 ifr = document.createElement('iframe'); 94 ifr = document.createElement('iframe');
93 ifr.src = appPath; 95 ifr.src = appPath;
@@ -46,7 +46,7 @@ function switchLoginBtnStatus() { @@ -46,7 +46,7 @@ function switchLoginBtnStatus() {
46 } 46 }
47 47
48 function resetForm() { 48 function resetForm() {
49 - $pwd.val('').focus(); 49 + // $pwd.val('').focus();
50 $loginBtn.text('登录').addClass('disable'); 50 $loginBtn.text('登录').addClass('disable');
51 } 51 }
52 52
@@ -123,7 +123,7 @@ $loginBtn.on('touchstart', function() { @@ -123,7 +123,7 @@ $loginBtn.on('touchstart', function() {
123 type: 'POST', 123 type: 'POST',
124 url: '/passport/login/auth', 124 url: '/passport/login/auth',
125 data, 125 data,
126 - success: function(data) { 126 + success: function(data) { //eslint-disable-line
127 var res, 127 var res,
128 LOGI_TYPE; 128 LOGI_TYPE;
129 129
@@ -101,8 +101,12 @@ function setPassword() { @@ -101,8 +101,12 @@ function setPassword() {
101 showErrTip(data.message); 101 showErrTip(data.message);
102 } 102 }
103 }, 103 },
104 - error: function() { 104 + error: function(data) {
105 $btnSure.removeClass('disable'); 105 $btnSure.removeClass('disable');
  106 +
  107 + if (data && data.responseJSON && data.responseJSON.message) {
  108 + showErrTip(data.message);
  109 + }
106 } 110 }
107 }); 111 });
108 } 112 }
  1 +@import "layout/img-check";
  2 +
  3 +.check-page {
  4 + margin: 20px auto;
  5 + width: 700px;
  6 +
  7 + .submit {
  8 + width: 100%;
  9 + height: 100px;
  10 + line-height: 100px;
  11 + text-align: center;
  12 + font-size: 32px;
  13 + color: #fff;
  14 + background: #5cb85c;
  15 + border-radius: 10px;
  16 + }
  17 +}
@@ -312,14 +312,14 @@ @@ -312,14 +312,14 @@
312 id: id 312 id: id
313 }, resultData => { 313 }, resultData => {
314 314
315 - // /* 结果返回 */  
316 - // if (resultData.length < 1) {  
317 - // areaCode.val(id);  
318 - // let returnTitle = this.returnTitle();  
319 -  
320 - // area.val(returnTitle);  
321 - // this.show = false;  
322 - // } 315 + /* 结果返回 */
  316 + if (resultData.length < 1) {
  317 + areaCode.val(id);
  318 + let returnTitle = this.returnTitle();
  319 +
  320 + area.val(returnTitle);
  321 + this.show = false;
  322 + }
323 323
324 /* 数据绑定 */ 324 /* 数据绑定 */
325 switch ((id + '').length) { 325 switch ((id + '').length) {
@@ -30,3 +30,19 @@ exports.refererLimit = (referer, blacklist) => { // eslint-disable-line @@ -30,3 +30,19 @@ exports.refererLimit = (referer, blacklist) => { // eslint-disable-line
30 30
31 return result; 31 return result;
32 }; 32 };
  33 +
  34 +/**
  35 + * 对象字段排序
  36 + */
  37 +exports.mapSort = obj => {
  38 + if (!obj) {
  39 + return {};
  40 + }
  41 + let data = {};
  42 +
  43 + Object.keys(obj).sort().forEach(k => {
  44 + data[k] = obj[k];
  45 + });
  46 +
  47 + return data;
  48 +};