Toggle navigation
Toggle navigation
This project
Loading...
Sign in
fe
/
yohobuy-node
·
Commits
Go to a project
GitLab
Go to group
Project
Activity
Files
Commits
Pipelines
0
Builds
0
Graphs
Milestones
Issues
1
Merge Requests
0
Members
Labels
Wiki
Forks
Network
Create a new issue
Download as
Plain Diff
Browse Files
Authored by
郝肖肖
7 years ago
Commit
f5a76873d24b2ed0fc25b930b2ebfb6d4a80fbab
2 parents
c7c7a35e
3c1623ef
Merge branch 'master' into feature/cartCtx
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
88 additions
and
126 deletions
app.js
apps/3party/models/robot-check-service.js
config/common.js
doraemon/middleware/limiter/index.js
doraemon/middleware/limiter/rules/ip-list.js
doraemon/middleware/limiter/rules/qps-limit.js
app.js
View file @
f5a7687
...
...
@@ -59,11 +59,6 @@ if (config.zookeeperServer) {
app
.
enable
(
'trust proxy'
);
// 请求限制中间件
if
(
!
app
.
locals
.
devEnv
)
{
app
.
use
(
require
(
'./doraemon/middleware/limiter'
));
}
app
.
set
(
'subdomain offset'
,
2
);
// 添加请求上下文
...
...
@@ -149,6 +144,12 @@ try {
app
.
use
(
mobileRefer
());
app
.
use
(
mobileCheck
());
app
.
use
(
user
());
// 请求限制中间件
if
(
!
app
.
locals
.
devEnv
)
{
app
.
use
(
require
(
'./doraemon/middleware/limiter'
));
}
app
.
use
(
seo
());
app
.
use
(
setPageInfo
());
app
.
use
(
layoutTools
());
...
...
apps/3party/models/robot-check-service.js
View file @
f5a7687
...
...
@@ -3,6 +3,8 @@
const
cache
=
global
.
yoho
.
cache
.
master
;
const
Promise
=
require
(
'bluebird'
);
const
co
=
Promise
.
coroutine
;
const
config
=
global
.
yoho
.
config
;
const
_
=
require
(
'lodash'
);
const
HeaderModel
=
require
(
'../../../doraemon/models/header'
);
...
...
@@ -15,24 +17,13 @@ const index = co(function* (channel) {
});
const
removeBlack
=
(
remoteIp
)
=>
{
let
key
=
`
pc
:
limiter
:
$
{
remoteIp
}
`
,
key10m
=
`
pc
:
limiter
:
10
m
:
$
{
remoteIp
}
`
,
keyMax
=
`
pc
:
limiter
:
max
:
$
{
remoteIp
}
`
,
key10mMax
=
`
pc
:
limiter
:
10
m
:
max
:
$
{
remoteIp
}
`
,
synchronizeKey
=
`
pc
:
limiter
:
synchronize
:
$
{
remoteIp
}
`
,
asynchronousKey
=
`
pc
:
limiter
:
asynchronous
:
$
{
remoteIp
}
`
,
spiderKey
=
`
pc
:
limiter
:
spider
:
$
{
remoteIp
}
`
;
return
Promise
.
all
([
cache
.
delAsync
(
key
),
cache
.
delAsync
(
key10m
),
cache
.
delAsync
(
keyMax
),
cache
.
delAsync
(
key10mMax
),
cache
.
delAsync
(
synchronizeKey
),
cache
.
delAsync
(
asynchronousKey
),
cache
.
delAsync
(
spiderKey
)
]);
let
operations
=
[
cache
.
delAsync
(
`
$
{
config
.
app
}
:
limiter
:
$
{
remoteIp
}
`
)];
_
.
forEach
(
config
.
REQUEST_LIMIT
,
(
val
,
key
)
=>
{
operations
.
push
(
cache
.
delAsync
(
`
$
{
config
.
app
}
:
limiter
:
$
{
key
}
:
max
:
$
{
remoteIp
}
`
));
});
return
Promise
.
all
(
operations
);
};
module
.
exports
=
{
...
...
config/common.js
View file @
f5a7687
...
...
@@ -141,8 +141,6 @@ module.exports = {
cache
:
false
},
zookeeperServer
:
'192.168.102.168:2188'
,
maxQps
:
1200
,
maxQps10m
:
2500
,
sessionMemcachedPrefix
:
'yohobuy_session:'
,
redis
:
{
connect
:
{
...
...
@@ -162,6 +160,17 @@ module.exports = {
return
Math
.
min
(
options
.
attempt
*
100
,
1000
);
}
}
},
REQUEST_LIMIT
:
{
// 30s 最多访问15次
30
:
15
,
// 60s 最多访问15次
60
:
20
,
// 100s 最多访问15次
600
:
100
}
};
...
...
@@ -174,7 +183,7 @@ if (isProduction) {
service
:
'http://api.yoho.yohoops.org/'
,
search
:
'http://search.yohoops.org/yohosearch/'
,
global
:
'http://api-global.yohobuy.com/'
,
serviceNotify
:
'http://
api.yoho.yohoops.org
/'
,
serviceNotify
:
'http://
service.yoho.cn
/'
,
imSocket
:
'wss://imsocket.yohobuy.com:443'
,
imCs
:
'https://imhttp.yohobuy.com/api'
,
platformApi
:
'http://api.platform.yohoops.org'
,
...
...
doraemon/middleware/limiter/index.js
View file @
f5a7687
...
...
@@ -5,8 +5,8 @@ const logger = global.yoho.logger;
const
ip
=
require
(
'./rules/ip-list'
);
const
userAgent
=
require
(
'./rules/useragent'
);
const
qpsLimiter
=
require
(
'./rules/qps-limit'
);
const
asynchronous
=
require
(
'./rules/asynchronous'
);
// const asynchronous = require('./rules/asynchronous');
// const fakerLimiter = require('./rules/faker-limit');
const
captchaPolicy
=
require
(
'./policies/captcha'
);
...
...
@@ -15,7 +15,16 @@ const captchaPolicy = require('./policies/captcha');
const
IP_WHITE_LIST
=
[
'106.38.38.146'
,
'218.94.75.58'
,
'218.94.75.50'
'218.94.75.50'
,
'218.94.77.166'
];
const
PATH_WHITE_LIST
=
[
'/3party/check'
,
'/passport/images.png'
,
'/passport/cert/headerTip'
,
'/common/getbanner'
,
'/common/suggestfeedback'
];
const
limiter
=
(
rule
,
policy
,
context
)
=>
{
...
...
@@ -37,7 +46,9 @@ module.exports = (req, res, next) => {
remoteIp
=
req
.
get
(
'X-Real-IP'
);
}
const
excluded
=
_
.
includes
(
IP_WHITE_LIST
,
remoteIp
);
// 排除条件:ip白名单/路径白名单/异步请求/登录用户
const
excluded
=
_
.
includes
(
IP_WHITE_LIST
,
remoteIp
)
||
_
.
includes
(
PATH_WHITE_LIST
,
req
.
path
)
||
req
.
xhr
||
!
_
.
isEmpty
(
_
.
get
(
req
,
'user.uid'
));
const
enabled
=
!
_
.
get
(
req
.
app
.
locals
,
'pc.sys.noLimiter'
);
logger
.
info
(
`
request
remote
ip
:
$
{
remoteIp
};
excluded
:
$
{
excluded
};
enabled
:
$
{
enabled
}
`
);
...
...
@@ -54,9 +65,9 @@ module.exports = (req, res, next) => {
Promise
.
all
([
limiter
(
userAgent
,
captchaPolicy
,
context
),
limiter
(
ip
,
captchaPolicy
,
context
),
limiter
(
qpsLimiter
,
captchaPolicy
,
context
),
limiter
(
asynchronous
,
captchaPolicy
,
context
)
limiter
(
qpsLimiter
,
captchaPolicy
,
context
)
// limiter(asynchronous, captchaPolicy, context)
// limiter(fakerLimiter, reporterPolicy, context)
]).
then
((
results
)
=>
{
let
allPass
=
true
,
exclusion
=
false
,
policy
=
null
;
...
...
@@ -68,11 +79,8 @@ module.exports = (req, res, next) => {
exclusion
=
result
.
exclusion
;
}
if
(
!
excluded
&&
typeof
result
===
'function'
)
{
allPass
=
false
;
}
if
(
typeof
result
===
'function'
)
{
allPass
=
false
;
policy
=
result
;
}
});
...
...
doraemon/middleware/limiter/rules/ip-list.js
View file @
f5a7687
...
...
@@ -2,15 +2,17 @@
const
cache
=
global
.
yoho
.
cache
.
master
;
const
_
=
require
(
'lodash'
);
const
config
=
global
.
yoho
.
config
;
const
logger
=
global
.
yoho
.
logger
;
module
.
exports
=
(
limiter
)
=>
{
const
key
=
`
pc
:
limiter
:
$
{
limiter
.
remoteIp
}
`
;
module
.
exports
=
(
limiter
,
policy
)
=>
{
const
key
=
`
$
{
config
.
app
}
:
limiter
:
$
{
limiter
.
remoteIp
}
`
;
return
cache
.
getAsync
(
key
).
then
((
result
)
=>
{
logger
.
debug
(
key
,
result
);
if
(
result
&&
_
.
isNumber
(
result
))
{
return
Promise
.
resolve
({
exclusion
:
result
===
-
1
});
return
Promise
.
resolve
(
policy
);
}
else
{
return
Promise
.
resolve
(
true
);
}
...
...
doraemon/middleware/limiter/rules/qps-limit.js
View file @
f5a7687
/**
* 限制页面访问次数,如超过限制次数,返回相应策略(目前是ip加入黑名单,跳转图形验证码页面,解除限制)
* 当前规则只针对未登录用户
*/
'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
MAX_QPS_10m
=
config
.
maxQps10m
;
// eslint-disable-line
const
_
=
require
(
'lodash'
);
const
PAGES
=
{
'/product/^\\/([\\d]+)(.*)/'
:
5
,
'/product/list/index'
:
5
,
'/product/search/index'
:
5
};
// 超出访问限制ip限制访问1小时
const
limiterIpTime
=
3600
;
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
;
}
}
// 页面访问限制
const
MAX_TIMES
=
config
.
REQUEST_LIMIT
;
module
.
exports
=
(
limiter
,
policy
)
=>
{
const
req
=
limiter
.
req
,
res
=
limiter
.
res
,
next
=
limiter
.
next
;
// eslint-disable-line
const
key
=
`
pc
:
limiter
:
$
{
limiter
.
remoteIp
}
`
;
const
keyMax
=
`
pc
:
limiter
:
max
:
$
{
limiter
.
remoteIp
}
`
;
const
key10m
=
`
pc
:
limiter
:
10
m
:
$
{
limiter
.
remoteIp
}
`
;
const
key10mMax
=
`
pc
:
limiter
:
10
m
:
max
:
$
{
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
;
// 存储规则的cache keys
let
ruleKeys
=
{};
let
getOp
=
{};
if
(
pageIncr
>
0
)
{
cache
.
incr
(
key
,
pageIncr
,
(
err
)
=>
{});
// eslint-disable-line
cache
.
incr
(
key10m
,
pageIncr
,
(
err
)
=>
{});
// eslint-disable-line
}
_
.
forEach
(
MAX_TIMES
,
(
val
,
key
)
=>
{
ruleKeys
[
key
]
=
`
$
{
config
.
app
}:
limiter
:
$
{
key
}
:
max
:
$
{
limiter
.
remoteIp
}
`
;
getOp
[
key
]
=
cache
.
getAsync
(
ruleKeys
[
key
]);
});
return
cache
.
getMultiAsync
([
key
,
key10m
,
keyMax
,
key10mMax
]).
then
((
results
)
=>
{
let
result
=
results
[
key
];
let
result10m
=
results
[
key10m
];
logger
.
debug
(
'qps limiter: '
+
key
+
'@'
+
result
+
' max: '
+
MAX_QPS
);
logger
.
debug
(
'qps limiter:10m '
+
key10m
+
'@'
+
result10m
+
' max: '
+
MAX_QPS_10m
);
// eslint-disable-line
// 达到1分钟或是10分钟的访问限制,禁止访问
if
(
results
[
keyMax
]
===
1
||
results
[
key10mMax
]
===
1
)
{
return
Promise
.
resolve
(
policy
);
}
// 默认数据设置
if
(
!
result
&&
!
_
.
isNumber
(
result
))
{
cache
.
setAsync
(
key
,
1
,
60
);
// 设置key,1m失效
}
if
(
!
result10m
&&
!
_
.
isNumber
(
result10m
))
{
cache
.
setAsync
(
key10m
,
1
,
600
);
// 设置key,10m失效
}
return
Promise
.
props
(
getOp
).
then
((
results
)
=>
{
// 第一次访问,都没计数,直接过
if
(
!
result
&&
!
_
.
isNumber
(
result
)
&&
!
result10m
&&
!
_
.
isNumber
(
result10m
))
{
return
Promise
.
resolve
(
true
);
}
logger
.
debug
(
MAX_TIMES
);
logger
.
debug
(
_
.
values
(
ruleKeys
));
logger
.
debug
(
results
);
if
(
result
===
-
1
||
result10m
===
-
1
)
{
return
Promise
.
resolve
(
true
);
}
// 遍历限制规则,若满足返回相应处理策略, 否则页面访问次数加1
let
operation
=
[];
// 判断 qps 10分钟
if
(
result10m
===
9999
)
{
res
.
statusCode
=
403
;
return
Promise
.
resolve
(
policy
);
}
else
if
(
result10m
>
MAX_QPS_10m
)
{
// eslint-disable-line
cache
.
setAsync
(
key10mMax
,
1
,
ONE_DAY
);
logger
.
debug
(
'req limit'
,
key10m
);
_
.
forEach
(
MAX_TIMES
,
(
val
,
key
)
=>
{
let
cacheKey
=
ruleKeys
[
key
];
return
Promise
.
resolve
(
policy
);
}
if
(
!
results
[
key
])
{
operation
.
push
(
cache
.
setAsync
(
cacheKey
,
1
,
+
key
));
}
else
if
(
+
results
[
key
]
>
+
val
)
{
// 判断 qps 1分钟
if
(
result
===
9999
)
{
res
.
statusCode
=
403
;
return
Promise
.
resolve
(
policy
);
}
else
if
(
result
>
MAX_QPS
)
{
// 判断 qps
cache
.
setAsync
(
keyMax
,
1
,
ONE_DAY
);
logger
.
debug
(
'req limit'
,
key
);
// ip限制1小时
operation
.
push
(
cache
.
setAsync
(
`
$
{
config
.
app
}:
limiter
:
$
{
limiter
.
remoteIp
}
`
,
1
,
limiterIpTime
));
return
Promise
.
resolve
(
policy
);
}
else
{
operation
.
push
(
cache
.
incrAsync
(
cacheKey
,
1
));
}
});
return
Promise
.
resolve
(
policy
);
}
Promise
.
all
(
operation
);
cache
.
incrAsync
(
key
,
1
);
// qps + 1
cache
.
incrAsync
(
key10m
,
1
);
// qps + 1
// 不满足任何限制规则,继续访问
return
Promise
.
resolve
(
true
);
}).
catch
(
err
=>
{
logger
.
error
(
err
);
});
};
...
...
Please
register
or
login
to post a comment