Showing
11 changed files
with
253 additions
and
96 deletions
1 | 'use strict'; | 1 | 'use strict'; |
2 | 2 | ||
3 | const _ = require('lodash'); | 3 | const _ = require('lodash'); |
4 | +const headerModel = require('../../../doraemon/models/header'); | ||
5 | +const checkModel = require('..//models/check'); | ||
4 | const decodeURIComponent = require('../../../utils/string-process').decodeURIComponent; | 6 | const decodeURIComponent = require('../../../utils/string-process').decodeURIComponent; |
5 | const logger = global.yoho.logger; | 7 | const logger = global.yoho.logger; |
6 | const Geetest = require('geetest'); | 8 | const Geetest = require('geetest'); |
@@ -14,7 +16,12 @@ const captcha = new Geetest({ | @@ -14,7 +16,12 @@ const captcha = new Geetest({ | ||
14 | 16 | ||
15 | exports.index = (req, res) => { | 17 | exports.index = (req, res) => { |
16 | req.yoho.captchaShow = false; | 18 | req.yoho.captchaShow = false; |
17 | - res.locals.useGeetest = true; | 19 | + |
20 | + if (req.session.apiRiskValidate) { | ||
21 | + res.locals.useRiskImg = true; | ||
22 | + } else { | ||
23 | + res.locals.useGeetest = true; | ||
24 | + } | ||
18 | 25 | ||
19 | if (_.has(res, 'locals.loadJsBefore')) { | 26 | if (_.has(res, 'locals.loadJsBefore')) { |
20 | res.locals.loadJsBefore.push({ | 27 | res.locals.loadJsBefore.push({ |
@@ -27,89 +34,129 @@ exports.index = (req, res) => { | @@ -27,89 +34,129 @@ exports.index = (req, res) => { | ||
27 | } | 34 | } |
28 | ]; | 35 | ]; |
29 | } | 36 | } |
37 | + | ||
30 | res.render('check', { | 38 | res.render('check', { |
39 | + pageHeader: headerModel.setNav({ | ||
40 | + navTitle: '友情提醒' | ||
41 | + }), | ||
31 | width750: true, | 42 | width750: true, |
32 | localCss: true | 43 | localCss: true |
33 | }); | 44 | }); |
34 | }; | 45 | }; |
35 | 46 | ||
36 | -exports.submit = (req, res) => { | ||
37 | - co(function * () { | ||
38 | - let challenge = req.body.geetest_challenge, | ||
39 | - validate = req.body.geetest_validate, | ||
40 | - seccode = req.body.geetest_seccode; | ||
41 | - | ||
42 | - let errRes = { | ||
43 | - code: 400, | ||
44 | - message: '验证码错误', | ||
45 | - captchaShow: true, | ||
46 | - changeCaptcha: true | ||
47 | - }; | ||
48 | - | ||
49 | - if (!challenge || !validate || !seccode) { | ||
50 | - return res.json(errRes); | 47 | + |
48 | +const submitValidate = { | ||
49 | + errRes: { | ||
50 | + code: 400, | ||
51 | + message: '验证码错误', | ||
52 | + captchaShow: true, | ||
53 | + changeCaptcha: true | ||
54 | + }, | ||
55 | + clearLimitIp(req) { | ||
56 | + let remoteIp = req.yoho.clientIp; | ||
57 | + | ||
58 | + if (remoteIp.indexOf(',') > 0) { | ||
59 | + let arr = remoteIp.split(','); | ||
60 | + | ||
61 | + remoteIp = arr[0]; | ||
51 | } | 62 | } |
52 | 63 | ||
53 | - let geetestRes = yield captcha.validate({ | ||
54 | - challenge, | ||
55 | - validate, | ||
56 | - seccode | ||
57 | - }); | 64 | + // pc:limiter:IP 和PC端共用 |
65 | + let operations = [cache.delAsync(`pc:limiter:${remoteIp}`)]; | ||
58 | 66 | ||
59 | - if (geetestRes) { | ||
60 | - logger.info('geetest success'); | 67 | + // 验证码之后一小时之内不再限制qps |
68 | + if (req.session.apiLimitValidate || req.session.apiRiskValidate) { | ||
69 | + operations.push(cache.setAsync( | ||
70 | + `${config.app}:limiter:api:ishuman:${remoteIp}`, | ||
71 | + 1, | ||
72 | + config.LIMITER_IP_TIME | ||
73 | + )); | ||
74 | + } else { | ||
75 | + operations.push(cache.setAsync( | ||
76 | + `${config.app}:limiter:ishuman:${remoteIp}`, | ||
77 | + 1, | ||
78 | + config.LIMITER_IP_TIME | ||
79 | + )); | ||
80 | + } | ||
81 | + | ||
82 | + delete req.session.apiLimitValidate; | ||
83 | + delete req.session.apiRiskValidate; | ||
84 | + | ||
85 | + if (req.body.pid) { | ||
86 | + let riskPid = decodeURIComponent(req.body.pid) + ':' + _.get(req.yoho, 'clientIp', ''); | ||
87 | + | ||
88 | + operations.push(cache.delAsync(riskPid)); | ||
89 | + } | ||
90 | + | ||
91 | + _.forEach(config.REQUEST_LIMIT, (val, key) => { | ||
92 | + operations.push(cache.delAsync(`${config.app}:limiter:${key}:max:${remoteIp}`)); | ||
93 | + }); | ||
61 | 94 | ||
62 | - let remoteIp = req.yoho.clientIp; | 95 | + return Promise.all(operations); |
96 | + }, | ||
97 | + geetest(req, res) { | ||
98 | + const self = this; | ||
63 | 99 | ||
64 | - if (remoteIp.indexOf(',') > 0) { | ||
65 | - let arr = remoteIp.split(','); | 100 | + co(function * () { |
101 | + let challenge = req.body.geetest_challenge, | ||
102 | + validate = req.body.geetest_validate, | ||
103 | + seccode = req.body.geetest_seccode; | ||
66 | 104 | ||
67 | - remoteIp = arr[0]; | 105 | + if (!challenge || !validate || !seccode) { |
106 | + return res.json(self.errRes); | ||
68 | } | 107 | } |
69 | 108 | ||
70 | - // pc:limiter:IP 和PC端共用 | ||
71 | - let operations = [cache.delAsync(`pc:limiter:${remoteIp}`)]; | 109 | + let geetestRes = yield captcha.validate({ |
110 | + challenge, | ||
111 | + validate, | ||
112 | + seccode | ||
113 | + }); | ||
114 | + | ||
115 | + if (geetestRes) { | ||
116 | + logger.info('geetest success'); | ||
72 | 117 | ||
73 | - // 验证码之后一小时之内不再限制qps | ||
74 | - if (req.session.apiLimitValidate) { | ||
75 | - operations.push(cache.setAsync( | ||
76 | - `${config.app}:limiter:api:ishuman:${remoteIp}`, | ||
77 | - 1, | ||
78 | - config.LIMITER_IP_TIME | ||
79 | - )); | 118 | + yield self.clearLimitIp(req); |
119 | + | ||
120 | + return res.json({ | ||
121 | + code: 200 | ||
122 | + }); | ||
80 | } else { | 123 | } else { |
81 | - operations.push(cache.setAsync( | ||
82 | - `${config.app}:limiter:ishuman:${remoteIp}`, | ||
83 | - 1, | ||
84 | - config.LIMITER_IP_TIME | ||
85 | - )); | 124 | + logger.info('geetest faild'); |
125 | + return res.json(self.errRes); | ||
86 | } | 126 | } |
87 | 127 | ||
88 | - delete req.session.apiLimitValidate; | 128 | + })(); |
129 | + }, | ||
130 | + imgCheckRisk(req, res) { | ||
131 | + const self = this; | ||
89 | 132 | ||
90 | - if (req.body.pid) { | ||
91 | - let riskPid = decodeURIComponent(req.body.pid) + ':' + _.get(req.yoho, 'clientIp', ''); | 133 | + co(function * () { |
134 | + let result = yield req.ctx(checkModel).verifyImgCheckRisk(req.cookies.udid, req.body.captcha); | ||
92 | 135 | ||
93 | - operations.push(cache.delAsync(riskPid)); | ||
94 | - } | 136 | + if (result.code === 200) { |
137 | + yield self.clearLimitIp(req); | ||
95 | 138 | ||
96 | - _.forEach(config.REQUEST_LIMIT, (val, key) => { | ||
97 | - operations.push(cache.delAsync(`${config.app}:limiter:${key}:max:${remoteIp}`)); | ||
98 | - }); | 139 | + return res.json(result); |
140 | + } else { | ||
141 | + logger.info('api risk img verify faild'); | ||
142 | + return res.json(self.errRes); | ||
143 | + } | ||
144 | + })(); | ||
145 | + } | ||
146 | +}; | ||
99 | 147 | ||
100 | - yield Promise.all(operations); | 148 | +exports.submit = (req, res) => { |
149 | + let validateType = 'geetest'; | ||
101 | 150 | ||
102 | - return res.json({ | ||
103 | - code: 200 | ||
104 | - }); | ||
105 | - } else { | ||
106 | - logger.info('geetest faild'); | ||
107 | - return res.json(errRes); | ||
108 | - } | 151 | + if (req.session.apiRiskValidate && req.body.apiRiskValidate) { |
152 | + validateType = 'imgCheckRisk'; | ||
153 | + } | ||
109 | 154 | ||
110 | - })().catch(() => { | 155 | + try { |
156 | + return submitValidate[validateType](req, res); | ||
157 | + } catch (err) { | ||
111 | return res.json({ | 158 | return res.json({ |
112 | code: 400 | 159 | code: 400 |
113 | }); | 160 | }); |
114 | - }); | 161 | + } |
115 | }; | 162 | }; |
apps/3party/models/check.js
0 → 100644
1 | +const PAGE = 'H5'; | ||
2 | +const logger = global.yoho.logger; | ||
3 | + | ||
4 | +module.exports = class extends global.yoho.BaseModel { | ||
5 | + constructor(ctx) { | ||
6 | + super(ctx); | ||
7 | + } | ||
8 | + | ||
9 | + verifyImgCheckRisk(udid, degrees) { | ||
10 | + return this.get({ | ||
11 | + data: { | ||
12 | + method: 'app.graphic.verify', | ||
13 | + udid: udid, | ||
14 | + fromPage: PAGE, | ||
15 | + degrees: degrees | ||
16 | + } | ||
17 | + }).then(result => { | ||
18 | + logger.info(`app.graphic.verify result: ${JSON.stringify(result)}`); | ||
19 | + return result; | ||
20 | + }); | ||
21 | + } | ||
22 | +}; |
1 | <div class="check-page"> | 1 | <div class="check-page"> |
2 | - <div class="title">请确认之后,继续访问</div> | 2 | + <p class="wran-tip"> |
3 | + <i class="iconfont"></i> | ||
4 | + 您的操作太频繁了~请完成以下操作后继续 | ||
5 | + </p> | ||
3 | {{!--图片验证--}} | 6 | {{!--图片验证--}} |
4 | - <div data-geetest="{{useGeetest}}" id="js-img-check"></div> | 7 | + <div{{#if useGeetest}} data-geetest="true"{{/if}}{{#if useRiskImg}} data-riskimg="true"{{/if}} id="js-img-check"></div> |
5 | <div class="submit"> | 8 | <div class="submit"> |
6 | 确认 | 9 | 确认 |
7 | </div> | 10 | </div> |
@@ -38,6 +38,24 @@ exports.imgCheck = (req, res, next) => { | @@ -38,6 +38,24 @@ exports.imgCheck = (req, res, next) => { | ||
38 | }).catch(next); | 38 | }).catch(next); |
39 | }; | 39 | }; |
40 | 40 | ||
41 | +exports.imgCheckRisk = (req, res, next) => { | ||
42 | + if (!req.session.apiRiskValidate) { | ||
43 | + return next(); | ||
44 | + } | ||
45 | + | ||
46 | + return req.ctx(imgCheckServiceModel).getRiskCheckImg(req.cookies.udid).then(result => { | ||
47 | + return request({ | ||
48 | + url: result, | ||
49 | + headers: { | ||
50 | + 'X-request-ID': req.reqID || '', | ||
51 | + 'X-YOHO-IP': req.yoho.clientIp || '', | ||
52 | + 'X-Forwarded-For': req.yoho.clientIp || '', | ||
53 | + 'User-Agent': 'yoho/nodejs' | ||
54 | + } | ||
55 | + }).pipe(res); // eslint-disable-line | ||
56 | + }).catch(next); | ||
57 | +}; | ||
58 | + | ||
41 | /** | 59 | /** |
42 | * 验证img-check验证码 | 60 | * 验证img-check验证码 |
43 | */ | 61 | */ |
@@ -2,7 +2,10 @@ | @@ -2,7 +2,10 @@ | ||
2 | const PAGE = 'H5'; | 2 | const PAGE = 'H5'; |
3 | const logger = global.yoho.logger; | 3 | const logger = global.yoho.logger; |
4 | const serviceAPI = global.yoho.ServiceAPI.ApiUrl; | 4 | const serviceAPI = global.yoho.ServiceAPI.ApiUrl; |
5 | +const ApiUrl = global.yoho.API.ApiUrl; | ||
5 | const config = global.yoho.config; | 6 | const config = global.yoho.config; |
7 | +const sign = global.yoho.sign; | ||
8 | +const querystring = require('querystring'); | ||
6 | 9 | ||
7 | module.exports = class extends global.yoho.BaseModel { | 10 | module.exports = class extends global.yoho.BaseModel { |
8 | constructor(ctx) { | 11 | constructor(ctx) { |
@@ -57,4 +60,11 @@ module.exports = class extends global.yoho.BaseModel { | @@ -57,4 +60,11 @@ module.exports = class extends global.yoho.BaseModel { | ||
57 | return result; | 60 | return result; |
58 | }); | 61 | }); |
59 | } | 62 | } |
63 | + | ||
64 | + getRiskCheckImg(udid) { | ||
65 | + return Promise.resolve(`${ApiUrl}/passport/img-verify?${querystring.stringify(sign.apiSign({ | ||
66 | + udid, | ||
67 | + fromPage: PAGE | ||
68 | + }))}`); | ||
69 | + } | ||
60 | }; | 70 | }; |
@@ -133,6 +133,7 @@ let captcha = require('./controllers/captcha'); | @@ -133,6 +133,7 @@ let captcha = require('./controllers/captcha'); | ||
133 | 133 | ||
134 | router.get('/passport/captcha/get', captcha.get); | 134 | router.get('/passport/captcha/get', captcha.get); |
135 | router.get('/passport/img-check.jpg', captcha.imgCheck); | 135 | router.get('/passport/img-check.jpg', captcha.imgCheck); |
136 | +router.get('/passport/img-check-risk.jpg', captcha.imgCheckRisk); | ||
136 | 137 | ||
137 | /** | 138 | /** |
138 | * 注册 | 139 | * 注册 |
@@ -13,6 +13,9 @@ const routeEncode = require('./route-encode'); | @@ -13,6 +13,9 @@ const routeEncode = require('./route-encode'); | ||
13 | const pathWhiteList = require('./limiter/rules/path-white-list'); | 13 | const pathWhiteList = require('./limiter/rules/path-white-list'); |
14 | const _ = require('lodash'); | 14 | const _ = require('lodash'); |
15 | 15 | ||
16 | +const replaceKey = '${refer}'; | ||
17 | +const checkRefer = helpers.urlFormat('/3party/check', {refer: replaceKey}); | ||
18 | + | ||
16 | const forceNoCache = (res) => { | 19 | const forceNoCache = (res) => { |
17 | if (res && !res.finished) { | 20 | if (res && !res.finished) { |
18 | res.set({ | 21 | res.set({ |
@@ -163,35 +166,42 @@ exports.serverError = () => { | @@ -163,35 +166,42 @@ exports.serverError = () => { | ||
163 | refer: req.originalUrl | 166 | refer: req.originalUrl |
164 | })); | 167 | })); |
165 | } | 168 | } |
166 | - } else if (err.code === 9999991 || err.code === 9999992) { | ||
167 | - if (!_.includes(pathWhiteList(), req.path)) { | ||
168 | - let remoteIp = req.yoho.clientIp; | 169 | + } else if (err.apiRisk || err.code === 9999991 || err.code === 9999992) { |
170 | + if (!err.apiRisk && _.includes(pathWhiteList(), req.path)) { | ||
171 | + return _err510(req, res, 510, err); | ||
172 | + } | ||
169 | 173 | ||
170 | - const isHuman = await cache.getAsync(`${config.app}:limiter:api:ishuman:${remoteIp}`); | 174 | + let remoteIp = req.yoho.clientIp; |
171 | 175 | ||
172 | - if (!isHuman) { | ||
173 | - if (remoteIp.indexOf(',') > 0) { | ||
174 | - let arr = remoteIp.split(','); | 176 | + if (remoteIp.indexOf(',') > 0) { |
177 | + let arr = remoteIp.split(','); | ||
175 | 178 | ||
176 | - remoteIp = arr[0]; | ||
177 | - } | ||
178 | - cache.setAsync(`${config.app}:limiter:${remoteIp}`, 1, config.LIMITER_IP_TIME); | 179 | + remoteIp = arr[0]; |
180 | + } | ||
179 | 181 | ||
180 | - let limitAPI = helpers.urlFormat('/3party/check', {refer: req.get('Referer') || ''}); | ||
181 | - let limitPage = helpers.urlFormat('/3party/check', { | ||
182 | - refer: req.protocol + '://' + req.get('host') + req.originalUrl | ||
183 | - }); | 182 | + let sessionLimitKey; |
183 | + let isHuman; | ||
184 | + | ||
185 | + if (err.apiRisk) { | ||
186 | + sessionLimitKey = 'apiRiskValidate'; | ||
187 | + } else { | ||
188 | + sessionLimitKey = 'apiLimitValidate'; | ||
189 | + isHuman = await cache.getAsync(`${config.app}:limiter:api:ishuman:${remoteIp}`); | ||
190 | + } | ||
191 | + | ||
192 | + if (!isHuman) { | ||
193 | + cache.setAsync(`${config.app}:limiter:${remoteIp}`, 1, config.LIMITER_IP_TIME); | ||
184 | 194 | ||
185 | - req.session.apiLimitValidate = true; | ||
186 | - if (req.xhr) { | ||
187 | - return res.status(510).json({ | ||
188 | - code: err.code, | ||
189 | - data: {refer: limitAPI} | ||
190 | - }); | ||
191 | - } | 195 | + req.session[sessionLimitKey] = true; |
192 | 196 | ||
193 | - return res.redirect(limitPage); | 197 | + if (req.xhr) { |
198 | + return res.status(510).json({ | ||
199 | + code: err.code, | ||
200 | + data: {refer: checkRefer.replace(replaceKey, req.get('Referer') || '')} | ||
201 | + }); | ||
194 | } | 202 | } |
203 | + | ||
204 | + return res.redirect(checkRefer.replace(replaceKey, req.protocol + '://' + req.get('host') + req.originalUrl)); | ||
195 | } | 205 | } |
196 | 206 | ||
197 | return _err510(req, res, 510, err); | 207 | return _err510(req, res, 510, err); |
@@ -9,6 +9,7 @@ const DEFAULT_PATH_WHITE_LIST = [ | @@ -9,6 +9,7 @@ const DEFAULT_PATH_WHITE_LIST = [ | ||
9 | '/3party/check/submit', | 9 | '/3party/check/submit', |
10 | '/passport/captcha/get', | 10 | '/passport/captcha/get', |
11 | '/passport/img-check.jpg', | 11 | '/passport/img-check.jpg', |
12 | + '/passport/img-check-risk.jpg', | ||
12 | '/passport/geetest/register', | 13 | '/passport/geetest/register', |
13 | '/activity/individuation', | 14 | '/activity/individuation', |
14 | '/activity/individuation/coupon', | 15 | '/activity/individuation/coupon', |
1 | require('3party/check.page.css'); | 1 | require('3party/check.page.css'); |
2 | require('common'); | 2 | require('common'); |
3 | 3 | ||
4 | -// 图片验证码 | ||
5 | -let Validate = require('plugin/validata'); | 4 | +let $ = require('yoho-jquery'), |
5 | + Validate = require('plugin/validata'); | ||
6 | 6 | ||
7 | -let validate = new Validate('#js-img-check', { | 7 | +let $check = $('#js-img-check'); |
8 | + | ||
9 | +let baseInfo = {pid: window.queryString.pid}; | ||
10 | +let validateOptions = { | ||
8 | useREM: { | 11 | useREM: { |
9 | rootFontSize: 40, | 12 | rootFontSize: 40, |
10 | - picWidth: 150 | 13 | + picWidth: 140 |
11 | } | 14 | } |
12 | -}); | 15 | +}; |
16 | + | ||
17 | +if ($check.data('riskimg')) { | ||
18 | + validateOptions.imgSrc = '/passport/img-check-risk.jpg'; | ||
19 | + baseInfo.apiRiskValidate = true; | ||
20 | +} | ||
21 | + | ||
22 | +let validate = new Validate($check, validateOptions); | ||
13 | 23 | ||
14 | validate.init(); | 24 | validate.init(); |
15 | 25 | ||
16 | $(function() { | 26 | $(function() { |
17 | $('.submit').on('click', function() { | 27 | $('.submit').on('click', function() { |
18 | validate.getResults().then((result) => { | 28 | validate.getResults().then((result) => { |
19 | - $.extend(result, {pid: window.queryString.pid}); | 29 | + $.extend(result, baseInfo); |
20 | 30 | ||
21 | $.ajax({ | 31 | $.ajax({ |
22 | method: 'POST', | 32 | method: 'POST', |
@@ -148,8 +148,9 @@ ImgCheck.prototype = { | @@ -148,8 +148,9 @@ ImgCheck.prototype = { | ||
148 | */ | 148 | */ |
149 | refresh: function() { | 149 | refresh: function() { |
150 | const self = this; | 150 | const self = this; |
151 | + const imgSrc = self.imgSrc || '/passport/img-check.jpg'; | ||
151 | 152 | ||
152 | - self.render({imgSrc: `/passport/img-check.jpg?t=${Date.now()}`}); | 153 | + self.render({imgSrc: `${imgSrc}?t=${Date.now()}`}); |
153 | }, | 154 | }, |
154 | 155 | ||
155 | 156 |
@@ -2,16 +2,50 @@ | @@ -2,16 +2,50 @@ | ||
2 | 2 | ||
3 | .check-page { | 3 | .check-page { |
4 | margin: 20px auto; | 4 | margin: 20px auto; |
5 | - width: 700px; | 5 | + width: 590px; |
6 | + | ||
7 | + .wran-tip { | ||
8 | + font-size: 30px; | ||
9 | + color: #444; | ||
10 | + text-align: center; | ||
11 | + margin-bottom: 150px; | ||
12 | + margin-top: 60px; | ||
13 | + | ||
14 | + .iconfont { | ||
15 | + font-size: 110px; | ||
16 | + display: block; | ||
17 | + color: #bbb; | ||
18 | + } | ||
19 | + } | ||
20 | + | ||
21 | + .img-check-header { | ||
22 | + font-size: 24px; | ||
23 | + } | ||
24 | + | ||
25 | + .img-check-pics > li { | ||
26 | + width: 140px; | ||
27 | + height: 140px; | ||
28 | + background-size: 560px 560px; | ||
29 | + position: relative; | ||
30 | + | ||
31 | + &:before { | ||
32 | + content: ""; | ||
33 | + position: absolute; | ||
34 | + width: 100%; | ||
35 | + height: 100%; | ||
36 | + box-sizing: border-box; | ||
37 | + border: 1px solid #e8e8e8; | ||
38 | + } | ||
39 | + } | ||
6 | 40 | ||
7 | .submit { | 41 | .submit { |
8 | width: 100%; | 42 | width: 100%; |
9 | - height: 100px; | ||
10 | - line-height: 100px; | 43 | + height: 90px; |
44 | + line-height: 90px; | ||
11 | text-align: center; | 45 | text-align: center; |
12 | - font-size: 32px; | 46 | + font-size: 30px; |
13 | color: #fff; | 47 | color: #fff; |
14 | - background: #5cb85c; | ||
15 | - border-radius: 10px; | 48 | + background: #444; |
49 | + border-radius: 8px; | ||
16 | } | 50 | } |
17 | } | 51 | } |
-
Please register or login to post a comment