Authored by ccbikai(👎🏻🍜)

Merge branch 'master' into gray

... ... @@ -51,6 +51,11 @@ app.set('etag', false);
app.enable('trust proxy');
// 请求限制中间件
if (!app.locals.devEnv) {
app.use(require('./doraemon/middleware/limiter'));
}
// 指定libray目录
global.utils = path.resolve('./utils');
... ...
'use strict';
const _ = require('lodash');
const cache = global.yoho.cache.master;
exports.index = (req, res) => {
res.render('check', {
width750: true,
localCss: true
});
};
exports.submit = (req, res) => {
let captchaCode = _.get(req.session, 'captcha');
let remoteIp = req.get('X-Forwarded-For') || req.ip;
if (remoteIp.indexOf(',') > 0) {
let arr = remoteIp.split(',');
remoteIp = arr[0];
}
if (req.body.captcha === captchaCode) {
let key = `pc:limiter:${remoteIp}`;
cache.delAsync(key).then(() => {
return res.json({
code: 200
});
}).catch(() => {
return res.json({
code: 400
});
});
} else {
return res.json({
code: 400
});
}
};
... ...
... ... @@ -9,9 +9,12 @@
const router = require('express').Router(); // eslint-disable-line
const cRoot = './controllers';
const ads = require(`${cRoot}/ads`);
const check = require(`${cRoot}/check`);
// routers
router.get('/ads', ads.index);
router.get('/check', check.index);
router.post('/check/submit', check.submit);
module.exports = router;
... ...
<div class="check-page">
<div class="title">请输入正确的验证码,继续访问</div>
<div id="js-img-check"></div>
<div class="submit">
确认
</div>
</div>
... ...
... ... @@ -181,6 +181,7 @@ exports.orderSub = (req, res, next) => {
let yohoCoin = req.body.yohoCoin || 0;
let skuList = req.body.skuList || '';
let orderInfo;
let isWechat = req.yoho.isWechat;
try {
orderInfo = JSON.parse(req.cookies['order-info']);
... ... @@ -260,11 +261,11 @@ exports.orderSub = (req, res, next) => {
result = yield cartModel.orderSub(uid, addressId, 'bundle', deliveryTimeId,
deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode,
yohoCoin, null, unionKey, userAgent, times, activityInfo, ip);
yohoCoin, null, unionKey, userAgent, times, activityInfo, ip, isWechat);
} else {
result = yield cartModel.orderSub(uid, addressId, cartType, deliveryTimeId,
deliveryId, invoices, paymentTypeId, paymentType, msg, couponCode,
yohoCoin, skuList, unionKey, userAgent, null, null, ip);
yohoCoin, skuList, unionKey, userAgent, null, null, ip, isWechat);
}
// 提交成功清除Cookie
... ...
... ... @@ -178,12 +178,13 @@ exports.ticketsOrderCompute = (uid, productSku, buyNumber, yohoCoin) => {
* @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息
* @param int $times
* @param null $activityInfo 套餐数据
* @param isWechat 是否是微信商城
* @return array 接口返回的数据
*/
exports.orderSub = (uid, addressId, cartType, deliveryTime,
deliveryWay, invoices, paymentId, paymentType, remark,
couponCode, yohoCoin, skuList, qhyUnio,
userAgent, times, activityInfo, ip) => {
userAgent, times, activityInfo, ip, isWechat) => {
if (!qhyUnio) {
qhyUnio = '';
}
... ... @@ -215,7 +216,7 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime,
return shoppingAPI.orderSub(uid, addressId, cartType, deliveryTime,
deliveryWay, invoices, paymentId, paymentType,
remark, couponCode, yohoCoin, skuList, qhyUnio,
userAgent, times, activityInfo, ip).then(orderSubRes => {
userAgent, times, activityInfo, ip, isWechat).then(orderSubRes => {
let finalResult = {};
if (orderSubRes && orderSubRes.data && orderSubRes.data.is_hint === 'Y') {
... ...
... ... @@ -111,7 +111,9 @@ const local = {
res.render('login', {
width750: true,
loginIndex: true, // 模板中使用JS的标识
captchaShow: _.get(req.session, 'login.errorCount') <= 0,
// captchaShow: _.get(req.session, 'login.errorCount') <= 0,
captchaShow: true, // 170306 因为暴力破解密码问题,要求每次都展示验证码
// 返回的URL链接
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
... ... @@ -162,7 +164,9 @@ const local = {
// 返回的URL链接
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
loginInternational: true, // 模板中使用JS的标识
captchaShow: _.get(req.session, 'login.errorCount') <= 0,
// captchaShow: _.get(req.session, 'login.errorCount') <= 0,
captchaShow: true, // 170306 因为暴力破解密码问题,要求每次都展示验证码
isPassportPage: true, // 模板中模块标识
headerText: '登录',
areaCode: '+86', // 默认区号
... ... @@ -173,6 +177,10 @@ const local = {
});
},
login: (req, res, next) => {
// 170306 因为暴力破解密码问题,要求每次都校验验证码
_.set(req.session, 'login.errorCount', 0);
let count = _.get(req.session, 'login.errorCount');
if (count == null) { // eslint-disable-line
... ...
... ... @@ -153,11 +153,12 @@ exports.checkTickets = (uid, productSku, buyNumber, useYohoCoin, yohoCoinMode) =
* @param string|null $userAgent 联盟过来用户下单时需要的User-Agent信息
* @param $times
* @param null $activityInfo 套餐信息
* @param isWechat 是否是微信商城
* @return array 接口返回的数据
*/
exports.orderSub = (uid, addressId, cartType, deliveryTime,
deliveryWay, invoices, paymentId, paymentType, remark, couponCode,
yohoCoin, skuList, qhyUnion, userAgent, times, activityInfo, ip) => {
yohoCoin, skuList, qhyUnion, userAgent, times, activityInfo, ip, isWechat) => {
if (!activityInfo) {
activityInfo = null;
}
... ... @@ -226,6 +227,11 @@ exports.orderSub = (uid, addressId, cartType, deliveryTime,
params.qhy_union = qhyUnion;
}
// 是否是微信商城
if (isWechat) {
params.client_type = 'wechat';
}
return api.post('', params, {
headers: {
'X-Forwarded-For': ip || '',
... ...
... ... @@ -72,7 +72,6 @@ module.exports = {
udp: { // send by udp
measurement: 'yohobuy_wap_node_log',
level: 'error', // logger level
host: 'influxdblog.web.yohoops.org', // influxdb host
port: '4444' // influxdb port
},
console: {
... ... @@ -107,7 +106,8 @@ module.exports = {
key: '7e6f3307b64cc87c79c472814b88f7fb',
appSecret: 'ce21ae4a3f93852279175a167e54509b',
notifyUrl: domains.service + 'payment/weixin_notify',
}
},
maxQps: 1200
};
if (isProduction) {
... ...
'use strict';
const limiter = require('../middleware/limiter/index');
module.exports = (req, res, next) => {
return limiter(req, res, next);
};
... ...
'use strict';
const _ = require('lodash');
const logger = global.yoho.logger;
const ip = require('./rules/ip-list');
const userAgent = require('./rules/useragent');
const qpsLimiter = require('./rules/qps-limit');
const fakerLimiter = require('./rules/faker-limit');
const captchaPolicy = require('./policies/captcha');
const reporterPolicy = require('./policies/reporter');
const IP_WHITE_LIST = [
// '106.38.38.146',
// '218.94.75.58'
];
const limiter = (rule, policy, context) => {
return rule(context, policy);
};
module.exports = (req, res, next) => {
let remoteIp = req.get('X-Forwarded-For') || req.connection.remoteAddress;
logger.debug('request remote ip: ', remoteIp);
if (remoteIp.indexOf(',') > 0) {
let arr = remoteIp.split(',');
remoteIp = arr[0];
}
const excluded = _.includes(IP_WHITE_LIST, remoteIp);
const enabled = !_.get(req.app.locals, 'wap.sys.noLimiter');
// 判断获取remoteIp成功,并且开关未关闭
if (enabled && remoteIp && !excluded) {
const context = {
req: req,
res: res,
next: next,
remoteIp: remoteIp
};
Promise.all([
limiter(userAgent, captchaPolicy, context),
limiter(ip, captchaPolicy, context),
limiter(qpsLimiter, captchaPolicy, context),
//limiter(fakerLimiter, reporterPolicy, context)
]).then((results) => {
let allPass = true, exclusion = false, policy = null;
logger.debug('limiter result: ' + JSON.stringify(results));
_.forEach(results, (result) => {
if (typeof result === 'object' && !exclusion) {
exclusion = result.exclusion;
}
if (!excluded && typeof result === 'function') {
allPass = false;
}
if (typeof result === 'function') {
policy = result;
}
});
if (exclusion) {
return next();
} else if (!allPass && policy) {
policy(req, res, next);
} else {
return next();
}
}).catch((err) => {
logger.error(err);
return next();
});
} else {
return next();
}
};
... ...
'use strict';
const helpers = global.yoho.helpers;
const _ = require('lodash');
const WHITE_LIST = [
'/3party/check',
'/passport/imagesNode',
'/passport/cert/headerTip',
'/passport/captcha/get',
'/3party/check/submit'
];
module.exports = (req, res, next) => {
let refer = req.method === 'GET' ? req.get('Referer') : '';
let limitAPI = helpers.urlFormat('/3party/check', {refer: refer});
let limitPage = helpers.urlFormat('/3party/check', {refer: req.protocol + '://' + req.get('host') + req.originalUrl});
if (_.indexOf(WHITE_LIST, req.path) >= 0) {
return next();
}
if (req.xhr) {
return res.json({
code: 400,
data: {refer: limitAPI}
});
}
return res.redirect(limitPage);
};
... ...
'use strict';
module.exports = (req, res, next) => {
return next();
};
... ...
'use strict';
const logger = global.yoho.logger;
const cache = global.yoho.cache.master;
const ONE_DAY = 60 * 60 * 24;
module.exports = (limiter, policy) => {
const req = limiter.req,
res = limiter.res,
next = limiter.next;
const key = `pc:limiter:faker:${limiter.remoteIp}`;
if (req.header('X-Requested-With') === 'XMLHttpRequest') {
cache.decrAsync(key, 1);
}
res.on('render', function() {
cache.incrAsync(key, 1);
});
return cache.getAsync(key).then((result) => {
if (result) {
if (result > 100) {
return Promise.resolve(policy);//policy(req, res, next);
} else {
return Promise.resolve(true);
}
} else {
cache.setAsync(key, 1, ONE_DAY); // 设置key,1m失效
return Promise.resolve(true);
}
});
};
... ...
'use strict';
const cache = global.yoho.cache.master;
const _ = require('lodash');
module.exports = (limiter) => {
const key = `pc:limiter:${limiter.remoteIp}`;
return cache.getAsync(key).then((result) => {
if (result && _.isNumber(result)) {
return Promise.resolve({
exclusion: result === -1
});
} else {
return Promise.resolve(true);
}
});
};
... ...
'use strict';
const logger = global.yoho.logger;
const cache = global.yoho.cache.master;
const config = global.yoho.config;
const ONE_DAY = 60 * 60 * 24;
const MAX_QPS = config.maxQps;
const _ = require('lodash');
const PAGES = {
'/product/\\/pro_([\\d]+)_([\\d]+)\\/(.*)/': 5,
'/product/list/index': 5
};
function urlJoin(a, b) {
if (_.endsWith(a, '/') && _.startsWith(b, '/')) {
return a + b.substring(1, b.length);
} else if (!_.endsWith(a, '/') && !_.startsWith(b, '/')) {
return a + '/' + b;
} else {
return a + b;
}
}
module.exports = (limiter, policy) => {
const req = limiter.req,
res = limiter.res,
next = limiter.next;
const key = `pc:limiter:${limiter.remoteIp}`;
res.on('render', function() {
let route = req.route ? req.route.path : '';
let appPath = req.app.mountpath;
if (_.isArray(route) && route.length > 0) {
route = route[0];
}
let pageKey = urlJoin(appPath, route.toString()); // route may be a regexp
let pageIncr = PAGES[pageKey] || 0;
if (/^\/p([\d]+)/.test(req.path)) {
pageIncr = 5;
}
if (pageIncr > 0) {
cache.incrAsync(key, pageIncr, (err) => {});
}
});
return cache.getAsync(key).then((result) => {
logger.debug('qps limiter: ' + key + '@' + result + ' max: ' + MAX_QPS);
if (result && _.isNumber(result)) {
if (result === -1) {
return Promise.resolve(true);
}
if (result > MAX_QPS) { // 判断 qps
cache.touch(key, ONE_DAY);
logger.debug('req limit', key);
return Promise.resolve(policy);
} else {
cache.incrAsync(key, 1); // qps + 1
return Promise.resolve(true);
}
} else {
cache.setAsync(key, 1, 60); // 设置key,1m失效
return Promise.resolve(true);
}
});
};
... ...
'use strict';
const cache = global.yoho.cache.master;
const _ = require('lodash');
const logger = global.yoho.logger;
module.exports = (limiter, policy) => {
const req = limiter.req,
res = limiter.res,
next = limiter.next;
const blackKey = 'pc:limiter:ua:black',
whiteKey = 'pc:limiter:ua:white';
const ua = limiter.req.header('User-Agent');
return Promise.all([
cache.getAsync(blackKey),
cache.getAsync(whiteKey)
]).then((args) => {
const blacklist = args[0] || [], whitelist = args[1] || [];
if (blacklist.length === 0 && whitelist.length === 0) {
return Promise.resolve(true);
}
const test = (list) => {
let result = false;
_.each(list, (item) => {
let regexp;
try {
regexp = new RegExp(item);
} catch (e) {
logger.error(e);
}
if (regexp.test(ua)) {
result = true;
}
});
return result;
};
if (test(blacklist)) {
return Promise.resolve(policy);
} else if (test(whitelist)) {
return Promise.resolve({
exclusion: true
});
} else {
return Promise.resolve(true);
}
});
};
... ...
{
"name": "m-yohobuy-node",
"version": "5.4.25",
"version": "5.4.29",
"private": true,
"description": "A New Yohobuy Project With Express",
"repository": {
... ...
require('3party/check.page.css');
require('../common');
// 图片验证码
let ImgCheck = require('plugin/img-check');
let imgCheck = new ImgCheck('#js-img-check', {
useREM: {
rootFontSize: 40,
picWidth: 150
}
});
imgCheck.init();
$(function() {
$('.submit').on('click', function() {
$.ajax({
method: 'POST',
url: '/3party/check/submit',
data: {
captcha: $.trim(imgCheck.getResults())
},
success: function(ret) {
if (ret.code === 200) {
window.location.href = decodeURIComponent(window.queryString.refer) || '//m.yohobuy.com';
} else {
imgCheck.refresh();
}
}
});
});
});
... ...
... ... @@ -18,9 +18,9 @@ require('activity/promotion/promotion.page.css');
var share = require('../common/share');
share({
title: 'YOHO!BUY有货邀请同学认证赠好礼!立享【学生专属】4大特权!',
title: '【有货】学生认证看过来!专享特价,任性分期,立即走起→',
link: location.href,
desc: '每邀请1名同学完成认证立赠300有货币!上不封顶!',
desc: '邀请同学认证更有惊喜好礼相送~',
imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png'
});
... ... @@ -303,9 +303,9 @@ $(
var shareUrl = location.href.replace('userUid=', 'oldUid=');
share({
title: 'YOHO!BUY有货邀请同学认证赠好礼!立享【学生专属】4大特权!',
title: '【有货】学生认证看过来!专享特价,任性分期,立即走起→',
link: shareUrl,
desc: '每邀请1名同学完成认证立赠300有货币!上不封顶!',
desc: '邀请同学认证更有惊喜好礼相送~',
imgUrl: 'http://img11.static.yhbimg.com/taobaocms/2017/01/17/16/0108d724a0ef1f001d3ee6010aa79d960e.png'
});
}
... ...
... ... @@ -46,7 +46,7 @@ function switchLoginBtnStatus() {
}
function resetForm() {
$pwd.val('').focus();
// $pwd.val('').focus();
$loginBtn.text('登录').addClass('disable');
}
... ... @@ -123,7 +123,7 @@ $loginBtn.on('touchstart', function() {
type: 'POST',
url: '/passport/login/auth',
data,
success: function(data) {
success: function(data) { //eslint-disable-line
var res,
LOGI_TYPE;
... ...
... ... @@ -101,8 +101,12 @@ function setPassword() {
showErrTip(data.message);
}
},
error: function() {
error: function(data) {
$btnSure.removeClass('disable');
if (data && data.responseJSON && data.responseJSON.message) {
showErrTip(data.message);
}
}
});
}
... ...
@import "layout/img-check";
.check-page {
margin: 20px auto;
width: 700px;
.submit {
width: 100%;
height: 100px;
line-height: 100px;
text-align: center;
font-size: 32px;
color: #fff;
background: #5cb85c;
border-radius: 10px;
}
}
... ...