# Created by https://www.gitignore.io/api/node,webstorm,netbeans,sublimetext,vim
### Node ###
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
### WebStorm ###
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
.idea/
# User-specific stuff:
.idea/workspace.xml
.idea/tasks.xml
.idea/dictionaries
.idea/vcs.xml
.idea/jsLibraryMappings.xml
# Sensitive or high-churn files:
.idea/dataSources.ids
.idea/dataSources.xml
.idea/dataSources.local.xml
.idea/sqlDataSources.xml
.idea/dynamic.xml
.idea/uiDesigner.xml
# Gradle:
.idea/gradle.xml
.idea/libraries
# Mongo Explorer plugin:
.idea/mongoSettings.xml
## File-based project format:
*.iws
## Plugin-specific files:
# IntelliJ
/out/
# mpeltonen/sbt-idea plugin
.idea_modules/
# JIRA plugin
atlassian-ide-plugin.xml
# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties
### WebStorm Patch ###
# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
# *.iml
# modules.xml
### NetBeans ###
nbproject/private/
nbbuild/
dist/
nbdist/
nbactions.xml
.nb-gradle/
### SublimeText ###
# cache files for sublime text
*.tmlanguage.cache
*.tmPreferences.cache
*.stTheme.cache
# workspace files are user-specific
*.sublime-workspace
# project files should be checked into the repository, unless a significant
# proportion of contributors will probably not be using SublimeText
# *.sublime-project
# sftp configuration file
sftp-config.json
### TSD ###
typings/
### Vim ###
# swap
[._]*.s[a-w][a-z]
[._]s[a-w][a-z]
# session
Session.vim
# temporary
.netrwhist
*~
# auto-generated tag files
tags
### TSD ###
typings/
### VS Code ###
.vscode/
### YOHO ###
dist
public/build/bundle/*
public/build/dist/*
public/css/*
public/bundle/*
.eslintcache
.stylelintcache
*.log.*
nbproject/*
.DS_Store
.devhost
.happypack/*
.scannerwork
\ No newline at end of file
... ...
# 基于Express的有货前端开发框架
## 规约
### js模块命令规范
* JS按大模块放置,比如:home或者guang
* 页面加载的JS使用`*.page.js`, 且page的JS只能放在模块下的一级目录
* 非页面加载的JS直接`*.js`
### 页面加载静态资源规范
* 传模块名`module`和页面名`page`,即模块的目录名和当前页面的JS文件名的前缀,以加载对应页面的JS
\ No newline at end of file
... ...
/**
* yoho-activity app
* @author: leo<qi.li@yoho.cn>
* @date: 2017/6/23
*/
'use strict';
const path = require('path');
const utils = require('./utils');
const express = require('express');
const config = require('./config');
const bluebird = require('bluebird');
const ynLib = require('yoho-node-lib');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
let logger;
const app = express();
// 全局注册library
ynLib.global(config);
logger = global.yoho.logger;
global.Promise = bluebird;
global.yoho.utils = utils;
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false
}));
app.use(cookieParser());
app.use(global.yoho.httpCtx());
app.use((req, res, next) => {
req.user = {}; // 全局的用户数据
next();
});
const middleware = require('./middleware');
try {
// 路由分发
require('./dispatch')(app);
// 错误处理
app.use(middleware.error);
} catch (err) {
logger.error(err);
}
app.listen(config.port, function() {
logger.info('yoho-activity-node started successfully!');
});
... ...
/**
* passport 验证策略注册
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/5/31
*/
'use strict';
const passport = require('passport');
const WeixinStrategy = require('passport-weixin');
const SinaStrategy = require('passport-sina').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const QQStrategy = require('passport-qq').Strategy;
const AlipayStrategy = require('./models/passport-alipay').Strategy;
const _ = require('lodash');
const config = global.yoho.config;
const logger = global.yoho.logger;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const AuthHelper = require('./models/auth-helper');
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
// 本地登录
passport.use(new LocalStrategy({
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
}, (req, username, password, done) => {
let area = req.body.areaCode || '86';
let clientIp = req.yoho.clientIp || '';
let isSkip = req.body.isskip;
if (isNaN(parseInt(area, 0)) || _.isEmpty(username) || _.isEmpty(password)) {
logger.info(`【Passport Loginbad params, area:${area} account:${username} password:${password}`);
return done('登录参数错误', null);
}
let verifyEmail = helpers.verifyEmail(username);
// 国际号码验证取消
let verifyMobile = area === '86' ? helpers.verifyAreaMobile(area + '-' + username) : true;
// 999 号段为有货专用测试号段
if (username.indexOf('999') === 0) {
verifyMobile = true;
}
if (!verifyEmail && !verifyMobile) {
logger.info(`【Passport Loginbad account, email:${verifyEmail} mobile:${verifyMobile}`);
return done('登录账号格式错误', null);
}
// let expire = req.cookies['LE' + md5('_LOGIN_EXPIRE')];
// if (_.isEmpty(expire) || expire < (new Date()).getTime() / 1000) {
// return done('页面停留时间过长,请刷新页面', null);
// }
let shoppingKey = cookie.getShoppingKey(req);
AuthHelper.signinAes(area, username, password, shoppingKey, clientIp, isSkip).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
done(null, result.data);
} else if (result.code && result.code === 4189) {
done({code: 4189}, null);
} else if (result.code && result.code === 510) {
done(null, Object.assign(result.data, {weakPassword: true}));
} else {
done('账号或密码不正确', null);
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}));
// wechat 登录
passport.use('weixin', new WeixinStrategy({
clientID: config.thirdLogin.wechat.appID,
clientSecret: config.thirdLogin.wechat.appSecret,
callbackURL: `${siteUrl}/passport/login/wechat/callback`,
requireState: true,
authorizationURL: 'https://open.weixin.qq.com/connect/oauth2/authorize',
scope: 'snsapi_userinfo'
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// sina 登录
passport.use('sina', new SinaStrategy({
clientID: '3739328910',
clientSecret: '9d44cded26d048e23089e5e975c93df1',
callbackURL: `${siteUrl}/passport/login/sina/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// qq 登录
passport.use('qq', new QQStrategy({
clientID: '100229394',
clientSecret: 'c0af9c29e0900813028c2ccb42021792',
callbackURL: `${siteUrl}/passport/login/qq/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// alipay 登录
passport.use('alipay', new AlipayStrategy({
partner: '2088701661478015',
key: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
return_url: `${siteUrl}/passport/login/alipay/callback`
}), (profile, done) => {
done(null, profile);
});
... ...
/**
* 服务条款
* @author: zxr<xiaoru.zhang@yoho.cn>
* @date: 2016/10/25
*/
'use strict';
const headerModel = require('../../../doraemon/models/header'); // 头部model
const aboutModel = require('../../../doraemon/models/about');
const privacy = (req, res) => {
res.render('agreement/privacy', {
title: '隐私条款',
width750: true
});
};
const newpower = (req, res) => {
res.render('agreement/newpower', {
title: '关于新力传媒',
width750: true
});
};
const aboutYoho = (req, res) => {
res.render('agreement/yohobuy', {
title: '关于YOHO!BUY有货',
width750: true
});
};
const agreement = (req, res) => {
let parameter = {};
if (req.yoho.isApp) {
parameter = {
appVersion: '1'
};
} else {
parameter = {
pageHeader: headerModel.setNav({
navTitle: 'YOHO!BUY有货app服务条款'
}),
};
}
res.render('agreement/agreement', Object.assign({
module: 'passport',
page: 'agreement',
title: 'YOHO!BUY有货app服务条款',
width750: true
}, parameter));
};
const about = (req, res, next) => {
let responseData = {
pageHeader: headerModel.setNav({
navTitle: '关于有货',
navBtn: false
}),
module: 'passport',
page: 'agreement',
title: '关于有货',
width750: true
};
aboutModel.about(req.yoho.isApp).then(result => {
res.render('agreement/about', Object.assign(responseData, {wxFooter: result}));
}).catch(next);
};
module.exports = {
privacy,
newpower,
aboutYoho,
agreement,
about
};
... ...
/**
* 找回密码
* Created by Tao.Huang on 2016/6/12.
*/
'use strict';
const _ = require('lodash');
const helpers = global.yoho.helpers;
const service = require('../models/back-service');
const captchaService = require('../models/captcha-service');
const SIGN_IN = helpers.urlFormat('/signin.html');
/**
* 通过邮箱找回密码页面
*/
const indexEmailPage = (req, res) => {
res.render('back/email', Object.assign(
{
module: 'passport',
page: 'back-email',
title: '找回密码-通过邮箱'
}, {
// backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backEmail: true
}
));
};
/**
* 发送验证码到邮箱
*/
const sendCodeToEmailAPI = (req, res) => {
let email = req.body.email || '';
const ERR = {
code: 400,
message: '邮箱格式不正确,请重新输入',
data: ''
};
if (!helpers.verifyEmail(email)) {
res.json(ERR);
return;
}
service.sendCodeToEmailAsync(email)
.then(result => {
if (result.code === 200) {
result.data = helpers.urlFormat('/passport/back/success', { email: email });
}
res.json(result);
})
.catch(() => {
res.json(ERR);
});
};
/**
* 重新发送验证码到邮箱
*/
const resendCodeToEmailAPI = (req, res) => {
let email = req.query.email || '';
service.sendCodeToEmailAsync(email)
.then(result => {
if (_.isEmpty(result)) {
return Promise.rejected('重新发邮件失败');
}
res.json(result);
})
.catch(err => {
res.json({
code: 400,
message: err
});
});
};
/**
* 邮箱找回密码-返回成功页面
*/
const backSuccessByEmailPage = (req, res) => {
let email = req.query.email || '';
if (!helpers.verifyEmail(email)) {
res.redirect(400);
}
let domain = email.split('@')[1];
let emailUrl = `http://${domain === 'gmail.com' ? 'mail.google.com' : 'mail.'}${domain}`;
res.render('back/email-success', Object.assign({
module: 'passport',
page: 'back-email-success',
title: '找回密码-通过邮箱'
}, {
// backUrl: helpers.urlFormat('/passport/back/email'),
headerText: '找回密码',
isPassportPage: true,
backEmailSuccess: true,
goEmail: emailUrl,
resendUrl: helpers.urlFormat('/passport/back/resendemail', { email: email })
}));
};
/**
* 根据邮箱修改密码
*/
const setNewPasswordByEmailAPI = (req, res) => {
let pwd = req.body.password || '';
let code = req.body.code || '';
let data = {
code: 200,
data: SIGN_IN
};
service.modifyPasswordByEmailAsyncAes(pwd, code)
.then(result => {
if (result.includes('history.back')) {
data.code = 400;
data.message = '修改失败';
}
res.json(data);
})
.catch(() => {
res.json(data);
});
};
/**
* 找回密码页面-通过手机号
*/
const indexMobilePage = (req, res, next) => {
_.set(req.session, 'backupCaptch.verifyResult', false);
if (req.session.captchaValidCount == null) { // eslint-disable-line
req.session.captchaValidCount = 5;
}
service.getAreaDataAsync()
.then(result => {
res.render('back/mobile', Object.assign({
width750: true,
module: 'passport',
page: 'back-mobile',
title: '找回密码-通过手机号'
}, {
// backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backMobile: true,
countrys: result.data,
areaCode: '+86',
verifySrc: helpers.urlFormat('/passport/back/generatecodeimg.png', {t: Date.now()})
}));
})
.catch(next);
};
/**
* 生成验证码
* @param req
* @param res
*/
const generateCodeImg = (req, res) => {
let verifyCodeImg = captchaService.generateCaptcha(109, 50, 4);
if (verifyCodeImg) {
if (req.session.backupCaptch) {
req.session.backupCaptch.code = verifyCodeImg.text;
req.session.backupCaptch.verifyResult = false;
} else {
req.session.backupCaptch = {
code: verifyCodeImg.text,
verifyResult: false
};
}
res.set('Cache-Control', 'no-cache').send(verifyCodeImg.image);
}
};
/**
* 发送手机验证码
*/
const sendCodeToMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let areaCode = req.body.areaCode || '86';
let ERR = {
code: 400,
message: '输入手机号码出错'
};
if (!helpers.verifyMobile(phoneNum)) {
return res.json(ERR);
}
let backCount = _.get(req.session, 'backupCaptch.count'); // 短信验证码 发送次数
if (!backCount) {
/* 如果设置了冻结时间,验证 */
let untilTime = (parseInt(req.session.backupCaptch.timeout, 10) -
parseInt(Date.now(), 10)) / 1000 / 60;
if (parseInt(Date.now(), 10) < parseInt(req.session.backupCaptch.timeout, 10)) {
return res.json({
code: 401,
message: '请' + (parseInt(untilTime, 10) + 1) + '分钟后尝试!'
});
} else {
_.set(req.session, 'backupCaptch.count', 5);
}
}
_.set(req.session, 'backupCaptch.verifyResult', true);
service.sendCodeToMobileAsync(phoneNum, areaCode)
.then(result => {
if (_.isEmpty(result) || result.code !== 200) {
ERR.message = '发送验证码出错';
res.json(ERR);
}
if (result.code === 200) {
--req.session.backupCaptch.count;
if (!req.session.backupCaptch.count) {
_.set(req.session, 'backupCaptch.timeout', Date.now() + 5 * 60 * 1000);
}
req.session.backupCaptchStep2 = true; // 允许跳到第二步
return res.json({
code: 200,
data: helpers.urlFormat('/passport/back/mobilecode', {
phoneNum: phoneNum,
areaCode: areaCode
})
});
}
})
.catch(next);
};
/**
* 校验验证码页面
*/
const verifyCodeByMobilePage = (req, res) => {
let phoneNum = req.query.phoneNum || '';
let areaCode = req.query.areaCode || '86';
/* 用户直接进入第二个页面的情况 */
if (!_.get(req, 'session.backupCaptch.verifyResult')) {
return res.redirect(helpers.urlFormat('/'));
}
res.render('back/mobile-code', Object.assign({
module: 'passport',
page: 'back-code',
title: '找回密码-通过手机号'
}, {
// backUrl: helpers.urlFormat('/passport/back/mobile'),
headerText: '找回密码',
isPassportPage: true,
backCode: true,
areaCode: areaCode,
phoneNum: phoneNum,
verifySrc: helpers.urlFormat('/passport/back/generatecodeimg.png', {t: Date.now()})
}));
};
/**
* 校验手机验证码
*/
const verifyCodeByMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let code = req.body.code || '';
let areaCode = req.body.areaCode || '86';
service.validateMobileCodeAsync(phoneNum, code, areaCode)
.then(result => {
if (result.code === 200 && result.data) {
req.session.backcode = {
phoneNum: phoneNum,
token: result.data.token,
areaCode: areaCode,
code: code
};
res.json({
code: 200,
data: helpers.urlFormat('/passport/back/backcode')
});
} else {
res.json({
code: 400,
message: '验证码失败'
});
}
})
.catch(next);
};
/**
* 找回密码页面,设置新密码页面-手机
*/
const setNewPasswordByMobilePage = (req, res) => {
let backcode = req.session.backcode;
if (!backcode || !(backcode.code || (backcode.token && helpers.verifyMobile(backcode.phoneNum)))) {
res.redirect(400);
return;
}
req.session.backcode = null;
res.render('back/new-password', Object.assign({
module: 'passport',
page: 'back-new-password',
title: '找回密码-输入新密码'
}, {
// backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backNewPwd: true,
phoneNum: backcode.phoneNum,
token: backcode.token,
areaCode: backcode.areaCode,
code: backcode.code
}));
};
/**
* 根据手机验证码修改密码
*/
const setNewPasswordByMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let token = req.body.token || '';
let areaCode = req.body.areaCode || '86';
let newPwd = req.body.password || '';
service.modifyPasswordByMobileAsyncAes(phoneNum, token, newPwd, areaCode)
.then(result => {
if (result.code === 200) {
res.json({
code: 200,
data: SIGN_IN
});
} else {
res.json({
code: 400,
message: '修改密码失败'
});
}
})
.catch(next);
};
/**
* 直接调用发短信接口的情况
*/
const verifySmsAllow = (req, res, next) => {
if (_.get(req, 'session.backupCaptch.verifyResult')) {
return next();
} else {
return res.json({
code: 400,
message: '非法请求'
});
}
};
module.exports = {
indexEmailPage,
sendCodeToEmailAPI,
resendCodeToEmailAPI,
backSuccessByEmailPage,
setNewPasswordByEmailAPI,
indexMobilePage,
sendCodeToMobileAPI,
verifyCodeByMobilePage,
verifyCodeByMobileAPI,
setNewPasswordByMobilePage,
setNewPasswordByMobileAPI,
generateCodeImg,
verifySmsAllow
};
... ...
/**
* 手机号绑定功能
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const helpers = global.yoho.helpers;
const RegService = require('../models/reg-service');
const BindService = require('../models/bind-service');
const AuthHelper = require('../models/auth-helper');
const _ = require('lodash');
const moment = require('moment');
const Sources = {
qq: 'QQ',
sina: '微博',
alipay: '支付宝',
wechat: '微信'
};
const bind = {
indexPage: (req, res) => {
// 如果没有originalUrl,判定为非法链接
if (req.session.originalUrl !== 'true') {
return res.redirect('//m.yohobuy.com');
}
let refer = req.get('Referer');
if (refer) {
res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
}
let openId = req.query.openId;
let sourceType = req.query.sourceType;
let serviceUrl = helpers.urlFormat('/service/im');
res.render('bind/index', {
bindIndex: true, // js标识
backUrl: helpers.urlFormat('/signin.html'), // 返回的URL链接
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
sourceType: sourceType, // 第三方登录来源
platform: Sources[sourceType],
isWechatLogin: sourceType === 'wechat',
openId: openId, // openId
areaCode: '+86', // 默认区号
countrys: RegService.getAreaData(), // 国别码
serviceUrl: serviceUrl, // 在线客服
module: 'passport',
page: 'bind',
title: '绑定手机号',
width750: true
});
},
codePage: (req, res) => {
let openId = req.query.openId;
let sourceType = req.query.sourceType;
let areaCode = req.query.areaCode || '86';
let isReg = req.query.isReg;
let phoneNum = req.query.phoneNum;
res.render('bind/code', {
backUrl: helpers.urlFormat('/signin.html'),
showHeaderImg: true,
isPassportPage: true,
sourceType: sourceType,
openId: openId,
isReg: isReg,
areaCode: areaCode,
phoneNum: phoneNum,
relateCode: isReg === '3',
bindCode: isReg !== '3',
module: 'passport',
page: isReg === '3' ? 'relate' : 'bind-code',
title: '验证手机'
});
},
bindCheck: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
if (_.isNumber(parseInt(phoneNum, 0)) && openId && areaCode && sourceType) {
BindService.bindCheck(phoneNum, openId, sourceType, areaCode).then(result => {
let data = {
code: result.code,
message: result.message,
data: {}
};
if (result.code === 200) {
let nextUrl = helpers.urlFormat('/passport/bind/code', {
isReg: result.data.is_register, // esline-disable-line
openId: openId,
sourceType: sourceType,
areaCode: areaCode,
phoneNum: phoneNum
});
data.data.isReg = result.data.is_register;
data.data.next = nextUrl;
_.set(req.session, 'bind.area', areaCode);
_.set(req.session, 'bind.phoneNum', phoneNum);
} else {
data.data = result.data;
}
res.json(data);
}).catch(next);
} else {
res.json({
code: 400,
message: '',
data: ''
});
}
},
// 防刷
sendBefore: (req, res, next) => {
let count = _.get(req.session, 'bind.count');
let interval = _.get(req.session, 'bind.interval');
let now = Date.now();
// // 重发次数用完了, 会冻结5min
// // 1. 过了冻结期, count 重设为 5次
// // 2. 没过冻结期, end
// // 没有用完, 判断是否请求太频繁
let during = moment.duration(interval - now, 'ms').minutes();
let message = `请${during}分钟后再试`;
if (!count) {
if (interval > now) {
return res.json({
code: 400,
message: message,
during: Math.ceil((interval - now) / 1000)
});
} else {
_.set(req.session, 'bind.count', 5);
}
} else if (interval > now) {
return res.json({
code: 429,
message: message
});
}
next();
},
sendBindMsg: (req, res, next) => {
let phoneNum = _.get(req.session, 'bind.phoneNum');
let areaCode = _.get(req.session, 'bind.area');
if (req.xhr && _.isNumber(parseInt(phoneNum, 0)) && areaCode) {
BindService.sendBindMsg(areaCode, phoneNum).then(result => {
if (result && result.code) {
if (result.code === 200) {
_.set(req.session, 'bind.area', areaCode);
_.set(req.session, 'bind.phoneNum', phoneNum);
--req.session.bind.count;
if (!req.session.bind.count) {
_.set(req.session, 'bind.interval', Date.now() + 5 * 60 * 1000);
} else {
_.set(req.session, 'bind.interval', Date.now() + 60 * 1000);
}
}
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
}
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
checkBindMsg: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let code = req.body.code;
let areaCode = req.body.areaCode;
if (_.isNumber(parseInt(phoneNum, 0)) && code && areaCode) {
BindService.checkBindMsg(areaCode, phoneNum, code).then(result => {
if (result && result.code) {
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
}
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
bindMobile: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
let code = req.body.code;
let password = req.body.password || '';
if (_.isNumber(parseInt(phoneNum, 0)) && openId && sourceType && areaCode && code) {
BindService.checkBindCode(areaCode, phoneNum, code).then(result => {
if (result && result.code && result.code === 200) {
return BindService.bindMobile(openId, sourceType, phoneNum, areaCode, password);
} else {
return { code: 400, message: '短信验证码错误', data: '' };
}
}).then(result => {
let refer = helpers.urlFormat('/passport/bind/success?type=bind');
if (result && result.code && result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res, result.data.session_key).then(() => {
result.data.refer = refer;
return result;
});
} else {
return { code: 400, message: '绑定失败', data: '' };
}
}).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
relateMobile: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
let code = req.body.code;
if (_.isNumber(parseInt(phoneNum, 0)) && openId && areaCode && sourceType && code) {
BindService.checkBindCode(areaCode, phoneNum, code).then(result => {
if (result && result.code && result.code === 200) {
return BindService.relateMobile(openId, sourceType, phoneNum, areaCode);
} else {
return { code: 400, message: '短信验证码错误', data: '' };
}
}).then(result => {
let refer = helpers.urlFormat('/passport/bind/success', { sourceType: sourceType });
if (result && result.code && result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res, result.data.session_key).then(() => {
result.data.refer = refer;
return result;
});
} else {
return { code: 400, message: '关联失败', data: '' };
}
}).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
passwordPage: (req, res) => {
let openId = req.query.openId;
let sourceType = req.query.sourceType;
let areaCode = req.query.areaCode || '86';
let phoneNum = req.query.phoneNum;
let code = req.query.code;
res.render('bind/password', {
module: 'passport',
page: 'bind-password',
bindPwd: true, // js标识
backUrl: helpers.urlFormat('/signin.html'), // 返回的URL链接
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
sourceType: sourceType, // 第三方登录来源
openId: openId, // openId
areaCode: areaCode, // 国别码
phoneNum: phoneNum, // 国别码
code: code // 验证码
});
},
successPage: (req, res) => {
let refer = req.cookies.refer;
let type = req.query.type;
if (!refer || /signin|login/.test(refer)) {
refer = helpers.urlFormat('/?go=1');
}
// 微信免单活动,判断来源地址 2.16.12.28 by jing.li@yoho.cn
res.cookie('bindUrl', 'http://m.yohobuy.com/passport/bind/success?type=bind');
res.render('bind/success', {
isPassportPage: true,
successTip: type === 'bind' ? '恭喜您,第三方账号绑定手机号码成功!' : '恭喜您,第三方账号关联手机号码成功!',
goUrl: refer,
module: 'passport',
page: 'bind-success',
title: '绑定手机号'
});
},
changeCheck: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
if (_.isNumber(parseInt(phoneNum, 0)) && areaCode) {
BindService.changeCheck(phoneNum, areaCode).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
changeMobile: (req, res, next) => {
let uid = req.user.uid;
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
let code = req.body.code;
if (_.isNumber(parseInt(phoneNum, 0)) && uid && areaCode && code) {
BindService.changeMobile(uid, phoneNum, areaCode, code).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
}
};
module.exports = bind;
... ...
'use strict';
const _ = require('lodash');
const logger = global.yoho.logger;
let imgCheckAPI = require('../models/imgcheck');
const request = require('request');
const uuid = require('uuid');
exports.get = (req, res, next) => {
let data = {
src: ''
};
return imgCheckAPI.gen().then(result => {
if (result.code === 200 && result.data) {
let codeStr = result.data.degrees.reduce((str, rotate) => {
return str.concat((4 - rotate / 90 % 4) % 4);
}, '');
req.session.captcha = codeStr;
req.session.captchaSrc = result.data.verifiedGraphicCode;
data.src = `/passport/img-check.jpg?t=${Date.now()}`;
return res.json(data);
}
next();
}).catch(next);
};
exports.imgCheck = (req, res, next) => {
return imgCheckAPI.gen().then(result => {
if (result.code === 200 && result.data) {
let codeStr = result.data.degrees.reduce((str, rotate) => {
return str.concat((4 - rotate / 90 % 4) % 4);
}, '');
req.session.captcha = codeStr;
req.session.captchaTimeout = new Date().getTime() + 1000 * 60;
req.session.captchaSrc = result.data.verifiedGraphicCode;
return request(`${result.data.verifiedGraphicCode}?imageView2/0/format/jpg/q/70|watermark/2/text/${uuid.v4()}/fontsize/120/dissolve/10`).pipe(res); // eslint-disable-line
}
next();
}).catch(next);
};
/**
* 验证img-check验证码
*/
exports.validate = (req, res, next) => {
let captchaInput = req.body.captcha;
let captchaCode = _.get(req.session, 'captcha');
let captchaTimeout = _.get(req.session, 'captchaTimeout');
if (new Date().getTime() > captchaTimeout) {
_.set(req.session, 'captchaValidCount', 5);
req.session.captcha = null;
return res.json({
code: 400,
message: '验证码超时,请重试',
changeCaptcha: true,
captchaShow: true
});
}
let errorCount = _.get(req.session, 'captchaValidCount'); // 初始1次 + 后续4次, 同一个验证码 共5次
let jsonData = {
code: 400,
message: '请将图片旋转到正确方向',
captchaShow: true
};
logger.info(`captcha validate result${(captchaInput && captchaInput.toString()) === captchaCode},user:${captchaInput};server:${captchaCode}`); // eslint-disable-line
_.set(req.session, 'captchaValidCount', errorCount - 1);
if (!errorCount) {
_.set(req.session, 'captchaValidCount', 5);
req.session.captcha = null;
jsonData.changeCaptcha = true;
}
if (!(captchaInput && captchaCode && captchaInput === captchaCode)) {
return res.json(jsonData);
}
req.session.captcha = null;
req.session.captchaValidCount = null;
req.session.useYohoCaptcha = null;
return next();
};
... ...
'use strict';
const Geetest = require('geetest');
const logger = global.yoho.logger;
const captcha = new Geetest({
geetest_id: 'bce95d796bc3058615fdf2ec2c0aef29',
geetest_key: '124c41d3a22aa48f36ae3cd609c51db5'
});
const geetest = {
register(req, res, next) {
captcha.register().then(function(data) {
// data 为一个对象,包含 gt, challenge, success, new_captcha 字段
// success 为 1 表示正常模式,为 0 表示宕机模式(failback, fallback)
// var body = {
// gt: data.geetest_id,
// challenge: data.challenge,
// success: data.success
// };
// 如果某次会话极验证服务不可用,使用自有图片验证
if (data.success) {
req.session.useYohoCaptcha = null;
} else {
req.session.useYohoCaptcha = true;
}
res.send(data);
}).catch(next);
},
validate(req, res, next) {
let challenge = req.body.geetest_challenge,
validate = req.body.geetest_validate,
seccode = req.body.geetest_seccode;
let errRes = {
code: 400,
message: '验证码错误',
captchaShow: true,
changeCaptcha: true
};
if (!challenge || !validate || !seccode) {
return res.json(errRes);
}
return captcha.validate({
challenge,
validate,
seccode
}).then(function(success) {
if (success) {
logger.info('geetest success');
return next();
}
logger.info('geetest faild');
return res.json(errRes);
});
}
};
module.exports = geetest;
... ...
/* eslint no-unused-vars: ["error", { "args": "none" }] */
/**
* 登录
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const _ = require('lodash');
const passport = require('passport');
// const md5 = require('yoho-md5');
const uuid = require('uuid');
const co = Promise.coroutine;
const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const cache = global.yoho.cache;
const utils = require(global.utils);
const RegService = require('../models/reg-service');
const AuthHelper = require('../models/auth-helper');
const loginPage = `${config.siteUrl}/signin.html`;
function doPassportCallback(openId, nickname, sourceType, req, res) {
let shoppingKey = cookie.getShoppingKey(req);
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/signin|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
refer = utils.refererLimit(refer);
if (openId && nickname) {
return AuthHelper.signinByOpenID(nickname, openId, sourceType, shoppingKey).then((result) => {
if (result.code !== 200) {
return Promise.reject(result);
}
if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
return helpers.urlFormat('/passport/bind/index', {
openId: openId,
sourceType: sourceType,
refer: refer
});
} else if (result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res, result.data.session_key).then(() => {
return refer;
});
}
}).then((redirectTo) => {
return res.redirect(redirectTo);
});
} else {
return Promise.reject('missing third party login openId or nickname');
}
}
const common = {
beforeLogin: (req, res, next) => {
if (req.session.passwordWeak) {
return res.redirect('/passport/password/resetpage');
}
let refer = req.query.refer;
if (!refer) {
refer = req.get('Referer') || req.cookies.refer;
}
refer = utils.refererLimit(refer);
refer && !/signin|login|passport/.test(refer) && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
if (req.yoho.isApp) {
return next({
code: 401,
message: 'weblogin',
refer
});
}
next();
},
weixinCheck: (req, res, next) => {
let passLogin = _.get(req, 'cookies._WX_PASS_LOGIN', false);
if (req.yoho.isWechat && !passLogin) {
return res.redirect(helpers.urlFormat('/passport/login/wechat', {
refer: req.query.refer || req.get('Referer') || '/'
}));
}
next();
},
clearCookie: (req, res, next) => {
res.clearCookie('_SESSION_KEY', {
domain: 'yohobuy.com'
});
res.clearCookie('_UID', {
domain: 'yohobuy.com'
});
res.clearCookie('_TOKEN', {
domain: 'yohobuy.com'
});
if (req.session2 && req.session2.reset) {
req.session2.reset();
}
if (req.session && req.session.regenerate) {
return req.session.regenerate(() => {
return next();
});
}
},
isLoginUser: (req, res, next) => {
// 微信里边已经登录的时候,不再跳转登录
if (req.user.uid) {
AuthHelper.profile(req.user.uid).then(function(result) {
if (result.code !== 200) {
return next();
}
let refer = req.query.refer || decodeURI(req.cookies.refer) || config.siteUrl;
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
refer = utils.refererLimit(refer);
return res.redirect(refer);
}).catch(() => {
return next();
});
} else {
return next();
}
},
check: (req, res, next) => {
let refer = req.query.refer;
// 短信推广的链接强制检查登录
if (req.user.uid) {
AuthHelper.profile(req.user.uid).then(function(result) {
if (result && result.code === 200) {
return res.redirect(refer);
}
return res.redirect(helpers.urlFormat('/signin.html', {
refer: refer
}));
}).catch(() => {
return res.redirect(helpers.urlFormat('/signin.html', {
refer: refer
}));
});
} else {
return res.redirect(helpers.urlFormat('/signin.html', {
refer: refer
}));
}
}
};
const local = {
loginPage: (req, res) => {
// 是否关闭账号登录
let closePassword = _.get(req.app.locals.wap, 'close.passwordLogin', false);
if (closePassword) {
return res.redirect(`/signin.html?refer=${req.query.refer || ''}`);
}
if (req.session.captchaValidCount == null) { // eslint-disable-line
req.session.captchaValidCount = 5;
}
res.render('login', {
width750: true,
loginIndex: true, // 模板中使用JS的标识
captchaShow: req.yoho.captchaShow,
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
smsLoginUrl: '/passport/sms_login',
registerUrl: '/passport/reg/index', // 注册的URL链接
aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
qqLoginUrl: '/passport/login/qq', // 腾讯QQ登录的URL链接
wechatLoginUrl: '/passport/login/wechat', // 微信登录的URL链接
internationalUrl: '/passport/international', // 国际号登录的URL链接
phoneRetriveUrl: '/passport/back/mobile', // 通过手机号找回密码的URL链接
emailRetriveUrl: '/passport/back/email', // 通过邮箱找回密码的URL链接
module: 'passport',
page: 'login',
title: '登录',
reg: true
});
},
international: (req, res) => {
// 是否关闭账号登录
let closePassword = _.get(req.app.locals.wap, 'close.passwordLogin', false);
if (closePassword) {
return res.redirect(`/signin.html?refer=${req.query.refer || ''}`);
}
if (req.session.captchaValidCount == null) { // eslint-disable-line
req.session.captchaValidCount = 5;
}
res.render('international', {
width750: true,
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
loginInternational: true, // 模板中使用JS的标识
captchaShow: req.yoho.captchaShow,
isPassportPage: true, // 模板中模块标识
headerText: '登录',
areaCode: '+86', // 默认区号
countrys: RegService.getAreaData(), // 地区信息列表
module: 'passport',
page: 'international',
title: '国际账号登录'
});
},
login: (req, res, next) => {
// 是否关闭账号登录
let closePassword = _.get(req.app.locals.wap, 'close.passwordLogin', false);
if (closePassword) {
return res.json({
code: 403,
message: '请使用快速登录'
});
}
passport.authenticate('local', (err, user) => {
if (err || !user) {
if (err.code === 4189) {
let obj = {
code: 4189,
message: err || '登录出错请重试',
url: '//m.yohobuy.com/passport/sms_login'
};
return res.json(obj);
} else {
let obj = {
code: 400,
message: err || '登录出错请重试',
data: '',
captchaShow: true
};
cache.set(`loginErrorIp:${req.yoho.clientIp}`, true, 3600).catch(log.error);
return res.json(obj);
}
} else {
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
refer = utils.refererLimit(refer);
user.session = refer;
user.href = refer;
res.cookie('_LOGIN_TYPE', 0, {
domain: 'm.yohobuy.com'
});
// 弱密码返回数据
let passwordWeakReturn;
if (user.weakPassword) {
req.session.passwordWeak = user;
passwordWeakReturn = {
code: 510,
url: '/passport/password/resetpage',
pwdTip: _.get(user, 'pwdTip', '密码应为6-20位字母、数字的组合'),
uid: _.get(user, 'uid', '')
};
}
// 不可以跳过,不登录用户
if (user.weakPassword && user.canSkip !== 'Y') {
return res.json(passwordWeakReturn);
}
AuthHelper.syncUserSession(user.uid, req, res, user.session_key).then(() => {
if (user.weakPassword) {
return res.json(passwordWeakReturn);
} else {
res.json({
code: 200,
data: user
});
}
}).catch(next);
}
})(req, res, next);
},
logout: (req, res) => {
res.clearCookie('_SPK');
res.cookie('_WX_PASS_LOGIN', true, {
domain: 'm.yohobuy.com'
});
let refer = req.get('Referer') || config.siteUrl;
refer = utils.refererLimit(refer);
res.redirect(refer);
}
};
const wechat = {
login: (req, res, next) => {
// 设置为原链接标识originalUrl
req.session.originalUrl = 'true';
req.session.authState = uuid.v4();
res.clearCookie('_WX_PASS_LOGIN', {
domain: 'm.yohobuy.com'
});
return passport.authenticate('weixin', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('weixin', (err, user) => {
if (err || !user) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user._json.nickname || user.displayName;
let openId = user._json.unionid || user.id;
res.cookie('_WX_OPENID', _.get(user, '_json.openid'), {
domain: 'm.yohobuy.com'
});
res.cookie('_WX_UNIONID', _.get(user, '_json.unionid'), {
domain: 'm.yohobuy.com'
});
res.cookie('_LOGIN_TYPE', 4, {
domain: 'm.yohobuy.com'
});
doPassportCallback(openId, nickname, 'wechat', req, res).catch(next);
})(req, res, next);
} else {
log.error('Auth State Mismatch:' + req.originalUrl);
return res.redirect(loginPage);
}
}
};
const sina = {
login: (req, res, next) => {
// 设置为原链接标识originalUrl
req.session.originalUrl = 'true';
req.session.authState = uuid.v4();
return passport.authenticate('sina', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('sina', (err, user) => {
if (err || !user) {
log.error(`sina authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.screen_name;
let openId = user.id;
res.cookie('_LOGIN_TYPE', 2, {
domain: 'm.yohobuy.com'
});
doPassportCallback(openId, nickname, 'sina', req, res).catch(next);
})(req, res, next);
} else {
log.error('Auth State Mismatch:' + req.originalUrl);
return res.redirect(loginPage);
}
}
};
const qq = {
login: (req, res, next) => {
// 设置为原链接标识originalUrl
req.session.originalUrl = 'true';
req.session.authState = uuid.v4();
return passport.authenticate('qq', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('qq', (err, user) => {
if (err) {
log.error(`qq authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.nickname;
let openId = user.id;
res.cookie('_LOGIN_TYPE', 1, {
domain: 'm.yohobuy.com'
});
doPassportCallback(openId, nickname, 'qq', req, res).catch(next);
})(req, res, next);
} else {
log.error('Auth State Mismatch:' + req.originalUrl);
return res.redirect(loginPage);
}
}
};
const alipay = {
login: (req, res, next) => {
// 设置为原链接标识originalUrl
req.session.originalUrl = 'true';
return passport.authenticate('alipay')(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('alipay', (err, user) => {
if (err || !user) {
log.error(`alipay authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.realName;
let openId = user.userId;
res.cookie('_LOGIN_TYPE', 3, {
domain: 'm.yohobuy.com'
});
doPassportCallback(openId, nickname, 'alipay', req, res).catch(next);
})(req, res, next);
}
};
exports.user = function(req, res, next) {
let result = {
code: 403,
message: '未登录',
data: ''
};
if (req.user.uid) {
result.code = 200;
result.message = '已登录';
result.data = req.user.uid.toString();
}
res.jsonp(result);
};
/**
* 中间件
* 根据用户登录是否成功决定是否展示验证码
*/
exports.loginShowCaptchaByIp = function(req, res, next) {
// 总开关状态
req.yoho.captchaShow = !_.get(req.app.locals.wap, 'close.loginValidation', false);
// 开关打开,不走任何验证逻辑
if (!req.yoho.captchaShow) {
return next();
} else {
req.yoho.captchaShow = false;
}
co(function*() {
let hasErrorLog = yield cache.get(`loginErrorIp:${req.yoho.clientIp}`);
log.info(`Pagerender clientip ${req.yoho.clientIp} status is ` + hasErrorLog);
if (hasErrorLog) {
req.yoho.captchaShow = true;
}
next();
})().catch(function(e) {
req.yoho.captchaShow = true;
next();
});
};
exports.common = common;
exports.local = local;
exports.wechat = wechat;
exports.sina = sina;
exports.qq = qq;
exports.alipay = alipay;
... ...
/**
* 注册
*
* @author Bi Kai<kai.bi@yoho.cn>
* @date 2016/06/23
*/
'use strict';
const _ = require('lodash');
const url = require('url');
const utils = require(global.utils);
const helpers = global.yoho.helpers;
const sign = global.yoho.sign;
const cookie = global.yoho.cookie;
const RegService = require('../models/reg-service');
const AuthHelper = require('../models/auth-helper');
/*
session 结构
phoneReg: {
step //当前步骤
captcha // step1 的校验码
count: 5 // 默认可以重发5次, 当count: 0, 冻结30min,之后解冻
expire // 解冻时间
}
*/
/**
* 步骤校验
* step: 预期步骤
*/
let guardStep = function(step) {
return (req, res, next) => {
let curStep = _.get(req.session, 'phoneReg.step');
if (curStep !== step) {
if (req.xhr) {
return res.json({
code: 400,
refer: '/reg.html',
message: '页面失效'
});
} else {
return res.redirect('/reg.html');
}
}
return next();
};
};
/**
* Step1: 输入手机号码 + 验证码
*/
let index = (req, res) => {
if (req.user.uid) {
return res.redirect(req.get('refer') || '/');
}
// 判断是否 来自 个人中心
if (!_.get(req.session, 'phoneReg.isFromMy')) {
let referer = req.get('Referer') || '';
let urlObj = url.parse(referer, true, true);
referer = _.get(urlObj, 'query.refer', '');
urlObj = url.parse(referer, true, true);
urlObj.path === '/home' && _.set(req.session, 'phoneReg.isFromMy', '1');
}
// 设置注册有效时间30分钟, 防机器刷
// req.session.REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
refer = utils.refererLimit(refer);
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
// session init
_.set(req.session, 'phoneReg.step', 1);
if (req.session.captchaValidCount == null) { // eslint-disable-line
req.session.captchaValidCount = 5;
}
if (req.session.phoneReg.count == null) { // eslint-disable-line
req.session.phoneReg.count = 5;
}
res.render('reg/index', {
width750: true,
module: 'passport',
page: 'reg',
title: '注册',
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
captchaUrl: helpers.urlFormat('/passport/reg/captcha.png', {t: Date.now()}),
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: '+86', // 默认的区号
countrys: RegService.getAreaData() // 地区信息列表
});
};
let verifyMobile = (req, res, next) => {
let data = {
code: 400,
message: '手机号已存在',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
// let expire = req.session.REG_EXPIRE;
// if (!expire || expire < Date.now()) {
// data.message = '非法请求';
// return res.json(data);
// }
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
if (result.code !== 200) {
data.message = result.message;
return res.json(data);
}
// 返回跳转到验证页面的链接
if (result.code === 200) {
let token = sign.makeToken(mobile);
_.set(req.session, 'phoneReg.step', 2); // go step 2
--req.session.phoneReg.count;
// count is 0, will freeze;
if (!req.session.phoneReg.count) {
_.set(req.session, 'phoneReg.expire', Date.now() + 5 * 60 * 1000);
}
result.data = helpers.urlFormat('/passport/reg/code', {
token: token,
phoneNum: mobile,
areaCode: area
});
}
return res.json(result);
}).catch(next);
};
/**
* Step2: 校验 手机验证码
*/
let codeAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
let area = +(req.query.areaCode || 86);
// 判断是否允许访问, 不允许则跳转到错误页面
if (!_.isString(token) || !_.isNumber(mobile) || !sign.verifyToken(mobile, token)) {
return next({
code: 403,
message: 'error token or mobile'
});
}
let serviceUrl = helpers.urlFormat('/service/im');
res.render('reg/code', {
page: 'code',
title: '注册-验证码',
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token, // 访问令牌
serviceUrl: serviceUrl // 在线客服
});
};
let sendCodeBusyBoy = (req, res, next) => {
let count = _.get(req.session, 'phoneReg.count');
let expire = _.get(req.session, 'phoneReg.expire');
if (count) {
return next();
} else {
/*
如果 count === 0
1. 没过解冻期
2. 过了解冻期, count reset to 5
*/
let now = Date.now();
if (now > expire) {
_.set(req.session, 'phoneReg.count', 5);
return next();
} else {
return res.json({
code: 400,
message: '发送次数太多, 5分钟稍后再试'
});
}
}
};
let sendCode = (req, res, next) => {
let data = {
code: 400,
message: '发送验证码失败',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
// let expire = req.session.REG_EXPIRE;
// if (!expire || expire < Date.now()) {
// data.message = '非法请求';
// return res.json(data);
// }
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
let code = _.get(result, 'code');
if (code) {
--req.session.phoneReg.count;
// count is 0, will freeze;
if (!req.session.phoneReg.count) {
_.set(req.session, 'phoneReg.expire', Date.now() + 5 * 60 * 1000);
}
return res.json(result);
} else {
return res.json(data);
}
}).catch(next);
};
let verifyCode = (req, res, next) => {
let data = {
code: 400,
message: '验证码错误',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
let code = +req.body.code;
/* 判断参数是否合法 */
if (!_.isNumber(mobile) || !_.isNumber(area) || !_.isNumber(code)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
// let expire = req.session.REG_EXPIRE;
// if (!expire || expire < Date.now()) {
// data.message = '非法请求';
// return res.json(data);
// }
// 验证注册的标识码是否有效
RegService.validMobileCode(area, mobile, code).then((result) => {
if (!result.code) {
return res.json(data);
}
let resultCode = _.get(result, 'code');
let token = sign.makeToken(mobile);
switch (resultCode) {
case 200:
_.set(req.session, 'phoneReg.step', 3); // go step 3
result.data = helpers.urlFormat('/passport/reg/password', {
token: token,
phoneNum: mobile,
areaCode: area,
smsCode: code
});
break;
case 404:
default:
result = data;
}
return res.json(result);
}).catch(next);
};
/**
* Step3: set Password
*/
let passwordAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
let area = +(req.query.areaCode || 86);
let smsCode = +req.query.smsCode;
// 判断是否允许访问, 不允许则跳转到错误页面
if (!smsCode || !_.isString(token) || !_.isNumber(mobile) ||
!_.isNumber(area) || !sign.verifyToken(mobile, token)) {
return next({
code: 403,
message: 'error token, mobile or verifyCode'
});
}
res.render('reg/password', {
page: 'password',
title: '注册-设置密码',
backUrl: '/?go=1', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token, // 访问令牌
smsCode: smsCode // 手机验证码
});
};
let setPassword = (req, res, next) => {
let data = {
code: 400,
message: '密码格式不正确',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
let password = req.body.password;
let token = req.body.token;
let smsCode = +req.body.smsCode;
let isFromMy = _.get(req.session, 'phoneReg.isFromMy', '0');
// 判断参数是否合法
if (!smsCode || !_.isString(token) || !_.isNumber(mobile) || !_.isNumber(area) || !password) {
data.message = '请求参数不合法';
return res.json(data);
}
// 判断是否允许访问
if (!sign.verifyToken(mobile, token)) {
data.message = '非法 token';
return res.json(data);
}
// 判断密码是否符合规则
if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/.test(password)) {
return res.json(data);
}
// 购物车key
let shoppingKey = cookie.getShoppingKey(req);
// 验证注册的标识码是否有效
let resultCopy = null;
RegService.regMobileAes(area, mobile, password, shoppingKey, smsCode, isFromMy).then((result) => {
if (!result.code || result.code !== 200) {
return res.send(result);
}
if (!result.data || !result.data.uid) {
return res.send(result);
}
resultCopy = result;
return AuthHelper.syncUserSession(result.data.uid, req, res, result.data.session_key);
}).then(() => {
if (!resultCopy) {
return;
}
// 返回跳转到来源页面
let refer = req.cookies.refer;
// isFromMy to 新人会场
if (resultCopy.data.newUserPage) {
refer = resultCopy.data.msgDelivery; // 来自个人中心,跳新人会场
} else {
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = '/home';
}
if (/sign|login/.test(refer)) {
refer = '/home';
}
refer = utils.refererLimit(refer);
}
delete req.session.phoneNum;
return res.json({
code: 200,
message: '注册成功',
data: {
session: refer,
href: refer
}
});
}).catch(next);
};
module.exports = {
guardStep,
sendCodeBusyBoy,
index,
verifyMobile,
code: codeAction,
sendCode,
verifyCode,
password: passwordAction,
setPassword
};
... ...
/*
* @Author: Targaryen
* @Date: 2017-04-13 10:21:07
* @Last Modified by: Targaryen
* @Last Modified time: 2017-04-24 10:52:08
*/
/* ********************
* 重置密码
**********************/
'use strict';
const config = global.yoho.config;
const utils = require(global.utils);
const reset = require('../models/reset');
const passwordResetPage = (req, res) => {
let passwordWeakObj = req.session.passwordWeak;
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
refer = utils.refererLimit(refer);
res.render('reset/password', {
width750: true,
module: 'passport',
page: 'reset',
pageStyle: 'passport-body',
title: '重置密码',
canSkip: (passwordWeakObj && passwordWeakObj.canSkip === 'Y') ? refer : false
});
};
/**
* 重置密码
* @param {*} req
* @param {*} res
* @param {*} next
*/
const passwordReset = (req, res, next) => {
let passwordWeakObj = req.session.passwordWeak;
let uid = (req.user && req.user.uid) || (passwordWeakObj && passwordWeakObj.uid);
if (!uid || !passwordWeakObj || !passwordWeakObj.token) {
return res.json({
code: 400,
massage: '非法请求'
});
}
req.session.passwordWeak = null;
reset.resetPassword({
uid: uid,
oldPwd: req.body.oldPwd,
newPwd: req.body.newPwd,
token: passwordWeakObj.token
}).then(result => {
res.json(result);
}).catch(next);
};
/**
* 重置密码成功
* @param {*} req
* @param {*} res
*/
const passwordResetOkPage = (req, res) => {
res.clearCookie('_SPK');
res.cookie('_WX_PASS_LOGIN', true, {
domain: 'm.yohobuy.com'
});
res.render('reset/reset-success', {
width750: true,
module: 'passport',
page: 'reset-success',
pageStyle: 'passport-body',
title: '重置密码成功',
});
};
module.exports = {
passwordResetPage,
passwordReset,
passwordResetOkPage
};
... ...
/* eslint no-unused-vars: ["error", { "args": "none" }] */
'use strict';
const _ = require('lodash');
const moment = require('moment');
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const EventEmitter = require('events');
const utils = require(global.utils);
const RegService = require('../models/reg-service');
const PhoneService = require('../models/phone-service');
const AuthHelper = require('../models/auth-helper');
// constrant
const CODE_REQUIRED = '请输入校验码';
const PASSWORD_REQUIRED = '请输入密码';
const PASSWORD_LENGTH_ERROR = '密码6-20位,请重新输入';
const BAD_PASSWORD = '密码格式不正确';
const LOGIN_SUCCSS = '登录成功';
const VERIFY_ERROR = '校验失败';
exports.beforeIn = (req, res, next) => {
res.set({
'Cache-Control': 'no-cache, no-store, must-revalidate',
Pragma: 'no-cache',
Expires: 0
});
let refer = utils.refererLimit(req.cookies.refer);
if (!req.xhr && req.user.uid) {
return res.redirect(refer);
}
next();
};
// 短信登录 第一步: 展现页面
const _step1 = (req, res, next) => {
_.set(req.session, 'smsLogin.step', 1);
if (req.session.captchaValidCount == null) { // eslint-disable-line
req.session.captchaValidCount = 5;
}
// 是否打开账号登录
let openPassword = !_.get(req.app.locals.wap, 'close.passwordLogin', false);
let template = 'sms/login';
let viewData = {
width750: true,
module: 'passport',
page: 'sms-login',
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
showHeaderImg: true, // 控制显示头部图片
title: '手机短信登录',
isPassportPage: true,
captchaUrl: helpers.urlFormat('/passport/sms_login/captcha.png', {t: Date.now()}),
smsLoginUrl: '/passport/sms_login',
registerUrl: '/passport/reg/index', // 注册的URL链接
aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
qqLoginUrl: '/passport/login/qq', // 腾讯QQ登录的URL链接
wechatLoginUrl: '/passport/login/wechat', // 微信登录的URL链接
internationalUrl: '/passport/international', // 国际号登录的URL链接
accountUrl: '/passport/login', // 账号密码登录URL链接
phoneRetriveUrl: '/passport/back/mobile', // 通过手机号找回密码的URL链接
emailRetriveUrl: '/passport/back/email', // 通过邮箱找回密码的URL链接
areaCode: '+86', // 默认的区号
countrys: RegService.getAreaData(), // 地区信息列表
openPassword,
reg: true
};
res.render(template, viewData);
};
// 短信登录 第二步: 输入 校验码
const _step2 = (req, res, next) => {
const mobile = req.session.smsLogin.mobile;
const area = req.session.smsLogin.area;
const interval = req.session.smsLogin.interval;
const template = 'sms/check';
const viewData = {
module: 'passport',
page: 'sms-check',
title: '手机短信登录',
isPassportPage: true,
headerText: '手机号码快捷登录',
countdown: Math.ceil((interval - Date.now()) / 1000),
mobile,
area
};
res.render(template, viewData);
};
// 短信登录 第三步: 设置密码 (针对 改手机未注册用户)
const _step3 = (req, res, next) => {
const template = 'sms/password';
const viewData = {
module: 'passport',
page: 'sms-password',
title: '设置密码',
isPassportPage: true,
headerText: '设置密码'
};
res.render(template, viewData);
};
// 短信 登录
exports.loginPage = (req, res, next) => {
let step = Number(req.query.step) || 1;
let smsLoginStep = _.get(req.session, 'smsLogin.step', 1);
if (step === 2 && smsLoginStep !== 2) {
return res.redirect(req.path);
}
if (step === 3 && smsLoginStep !== 3) {
return res.redirect(req.path);
}
switch (step) {
case 2:
_step2(req, res, next);
break;
case 3:
_step3(req, res, next);
break;
case 1:
default:
_step1(req, res, next);
}
};
/**
* step1 的表单提交验证
*/
exports.indexCheck = (req, res, next) => {
_.set(req.session, 'smsLogin.step', 1);
let area = req.body.area = (req.body.area || '').trim();
let mobile = req.body.mobile = (req.body.mobile || '').trim();
let errorData = {
code: 400,
message: ''
};
let em = new EventEmitter();
// 校验 成功
em.on('resolve', () => {
// 1. 将信息放入 session
_.set(req.session, 'smsLogin.area', area);
_.set(req.session, 'smsLogin.mobile', mobile);
_.set(req.session, 'smsLogin.step', 2);
req.session.captcha = null;
PhoneService.sendSMS(mobile, area, 1);
res.json({
code: 200,
redirect: '/passport/sms_login?step=2'
});
});
// 校验 失败
em.on('reject', error => {
_.set(req.session, 'smsLogin.step', 1);
res.json(error);
});
// 验证
if ([area, mobile].some(val => val === '')) {
return em.emit('reject', Object.assign(errorData, { message: '请填写手机号'}));
}
// congratulation~~
em.emit('resolve');
};
exports.tokenBefore = (req, res, next) => {
let step = _.get(req.session, 'smsLogin.step');
let count = _.get(req.session, 'smsLogin.count');
let interval = _.get(req.session, 'smsLogin.interval');
if (!req.xhr || step !== 2) {
return next(404);
}
let now = Date.now();
// 重发次数用完了, 回冻结5min
// 1. 过了冻结期, count 重设为 5次
// 2. 没过冻结期, end
// 没有用完, 判断是否请求太频繁
let during = moment.duration(interval - now, 'ms').minutes();
let message = `请${during}分钟后再试`;
if (!count) {
if (interval > now) {
return res.json({
code: 400,
message: message,
during: Math.ceil((interval - now) / 1000)
});
} else {
_.set(req.session, 'smsLogin.count', 5);
}
} else if (interval > now) {
return res.json({
code: 429,
message: message
});
}
next();
};
// AJAX 获取验证码
exports.token = (req, res, next) => {
let area = _.get(req.session, 'smsLogin.area');
let mobile = _.get(req.session, 'smsLogin.mobile');
PhoneService.sendSMS(mobile, area, 1).then(result => {
if (result.code === 200) {
_.set(req.session, 'smsLogin.step', 2);
_.set(req.session, 'smsLogin.area', area);
_.set(req.session, 'smsLogin.mobile', mobile);
--req.session.smsLogin.count;
if (!req.session.smsLogin.count) {
_.set(req.session, 'smsLogin.interval', Date.now() + 5 * 60 * 1000);
} else {
_.set(req.session, 'smsLogin.interval', Date.now() + 60 * 1000);
}
result.redirect = '/passport/sms_login?step=2';
res.json(result);
return;
}
res.json(result);
});
};
exports.checkBefore = (req, res, next) => {
let code = req.query.code = (req.query.code || '').trim();
let step = _.get(req.session, 'smsLogin.step');
if (!req.xhr && step !== 2) {
return next(404);
}
if (!code) {
return res.json({
code: 404,
message: CODE_REQUIRED
});
}
next();
};
// AJAX 校验验证码 in step2
exports.check = (req, res, next) => {
const code = req.query.code;
const mobile = _.get(req.session, 'smsLogin.mobile', '');
const area = _.get(req.session, 'smsLogin.area', '');
const shopping_key = cookie.getShoppingKey(req); // eslint-disable-line
if (!mobile || !area) {
res.json({
code: 401,
message: VERIFY_ERROR
});
return;
}
Promise.all([
PhoneService.checkUserPhoneExist(mobile, area),
PhoneService.verifySMS(mobile, area, code, 1)
])
.then(result => {
let r1 = result[0] || {};
let r2 = result[1] || {};
let redirect;
// 验证码 校验异常
if (r2.code !== 200) {
res.json(r2);
return;
}
// 检测 手机号 是否注册 异常
if (r1.code !== 200) {
res.json(r1);
return;
}
// 校验失败
if (r2.data.is_pass !== 'Y') {
res.json({
code: 401,
message: VERIFY_ERROR
});
return;
}
// 手机号码 没注册
if (r1.data.is_register !== 'Y') {
redirect = '/passport/sms_login?step=3';
_.set(req.session, 'smsLogin.step', 3);
res.json({
code: 200,
redirect,
newer: true,
registerCode: r1.data.code
});
return;
}
// 手机号码已注册 --> 直接登录
PhoneService.autoSignin({
profile: mobile,
code: r2.data.code,
area,
shopping_key
})
.then(info => {
if (info.code !== 200) {
return Promise.reject(info);
}
res.cookie('_LOGIN_TYPE', 5, {
domain: 'm.yohobuy.com'
});
return AuthHelper.syncUserSession(info.data.uid, req, res, info.data.session_key);
})
.then(() => {
res.json({
code: 200,
message: LOGIN_SUCCSS,
redirect: utils.refererLimit(req.cookies.refer)
});
delete req.session.smsLogin;
})
.catch(error => {
res.json(error);
});
})
.catch(next);
};
// AJAX 短信登录 设置密码 in step3
exports.password = (req, res, next) => {
let step = _.get(req.session, 'smsLogin.step');
if (step !== 3) {
return next();
}
let data = {
code: '400',
message: BAD_PASSWORD
};
let mobile = _.get(req.session, 'smsLogin.mobile');
let area = _.get(req.session, 'smsLogin.area');
let password = (req.body.password || '').trim();
let registerCode = req.body.registerCode || '';
if (!password) {
data.message = PASSWORD_REQUIRED;
return res.json(data);
}
if (password.length < 6 || password.length > 20) {
data.message = PASSWORD_LENGTH_ERROR;
return res.json(data);
}
if (!/^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/.test(password)) {
return res.json(data);
}
// 购物车key
let shoppingKey = cookie.getShoppingKey(req);
// 验证注册的标识码是否有效
let resultCopy = null;
RegService.regMobileAes(area, mobile, password, shoppingKey, registerCode).then(result => {
if (!result.code || result.code !== 200) {
return res.send(result);
}
if (!result.data || !result.data.uid) {
return res.send(result);
}
resultCopy = result;
return AuthHelper.syncUserSession(result.data.uid, req, res, result.data.session_key);
}).then(() => {
if (!resultCopy) {
return;
}
res.json({
code: 200,
message: LOGIN_SUCCSS,
redirect: utils.refererLimit(req.cookies.refer)
});
delete req.session.smsLogin;
}).catch(next);
};
/**
* 生成 校验码
*/
/*
exports.genCaptcha = (req, res) => {
let captcha = captchaService.generateCaptcha(90, 52, 4);
_.set(req.session, 'smsLogin.captcha', captcha.text);
res.type('png')
.set('Cache-Control', 'no-cache')
.status(200)
.send(captcha.image);
};
*/
... ...
'use strict';
exports.index = (req, res) => {
res.render('app-update', {
title: '更新提示',
width750: true,
});
};
... ...
/**
* 验证码中间件
* @author feng.chen<feng.chen@yoho.cn>
* @date 2017/03/23
*/
'use strict';
const _ = require('lodash');
const config = global.yoho.config;
const co = Promise.coroutine;
const cache = global.yoho.cache;
const log = global.yoho.logger;
const geetest = require('./geetest');
const captcha = require('./captcha');
/**
* 验证验证码
*/
const check = (req, res, next) => {
let testCode = req.body.yohobuy;
if (testCode === config.testCode) {
return next();
}
// 默认取配置总开关来决定是否展示验证码
req.yoho.captchaShow = !_.get(req.app.locals.wap, 'close.loginValidation', false);
co(function* () {
// 如果是账号密码登录,那么需要检查是否登录失败过,登录失败过展示验证码
if (req.path === '/passport/login/auth') {
let hasErrorLog = yield cache.get(`loginErrorIp:${req.yoho.clientIp}`);
log.info(`Check clientip ${req.yoho.clientIp} status is ` + hasErrorLog);
if (hasErrorLog) {
req.yoho.captchaShow = true;
} else {
req.yoho.captchaShow = false;
}
}
return req.yoho.captchaShow;
})().catch(function() {
// memcache 不可用,展示验证码
req.yoho.captchaShow = true;
return req.yoho.captchaShow;
}).then(function() {
// 不是账号密码登录,直接根据配置总开关决定是否需要展示验证码
if (!req.yoho.captchaShow) {
return next();
}
// 使用极验证
let useGeetest = !_.get(req.app.locals.wap, 'geetest.validation', false);
// 某次请求极验证调用注册失败,强制使用自有图形验证码
if (req.session.useYohoCaptcha) {
useGeetest = false;
}
return (useGeetest ? geetest : captcha).validate(req, res, next);
});
};
/**
* 加载验证码
*/
const load = (req, res, next) => {
res.locals.useGeetest = !_.get(req.app.locals.wap, 'geetest.validation', false); // 使用极验证
if (_.has(res, 'locals.loadJs')) {
res.locals.loadJs.push({
src: global.yoho.config.geetestJs
});
} else {
res.locals.loadJs = [
{
src: global.yoho.config.geetestJs
}
];
}
return next();
};
module.exports = {
check,
load
};
... ...
This diff could not be displayed because it is too large.
This diff could not be displayed because it is too large.
/**
* sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
var express = require('express'),
path = require('path');
var passport = require('passport');
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.use(global.yoho.hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: path.join(__dirname, 'views/partial'),
views: path.join(__dirname, 'views/action'),
helpers: global.yoho.helpers
}));
require('./auth');
app.use(passport.initialize());
app.use(passport.session());
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* 登录注册密码加密,uid加密
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/07/07
*/
'use strict';
const crypto = global.yoho.crypto;
/**
* 密码加密
**/
const aesPwd = (pwd) => {
return crypto.encryption('yoho9646yoho9646', pwd);
};
/**
* uid加密
**/
const encryptionUid = (uid) => {
return crypto.encryption('yoho9646abcdefgh', uid + '');
};
module.exports = {
aesPwd,
encryptionUid
};
... ...
'use strict';
const _ = require('lodash');
const aes = require('./aes-pwd');
const authcode = require('../../../utils/authcode');
const logger = global.yoho.logger;
const sign = global.yoho.sign;
const api = global.yoho.API;
const uuid = require('uuid');
class Auth {
static signin(area, profile, password, shoppingKey) {
let param = {
method: 'app.passport.signin',
area: area,
profile: profile,
password: password
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.post('', param);
}
static signinAes(area, profile, password, shoppingKey, ip, isSkip) {
let param = {
method: 'app.passport.signinAES',
area: area,
profile: profile,
password: aes.aesPwd(password),
isSkip: isSkip ? isSkip : 'N'
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
logger.info(`${profile}, login from ${ip}`);
return api.post('', param, {
headers: {
'user-agent': 'yoho/nodejs',
'X-YOHO-IP': ip,
'X-Forwarded-For': ip
}
});
}
static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
source_type: sourceType, // esline-disable-line
method: 'app.passport.signinByOpenID',
shoppingKey: shoppingKey
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.get('', param);
}
static profile(uid) {
let param = {
uid: uid,
method: 'app.passport.profile'
};
return api.get('', param);
}
static syncUserSession(uid, req, res, sessionKey) {
let userId = {
toString: () => {
return uid;
}
};
if (sessionKey) {
// 弃用
// global.yoho.cache.set(`java_session_key:${uid}`, sessionKey).catch(() => {
// global.yoho.logger.error('write session key fail');
// });
req.session.SESSION_KEY = sessionKey;
res.cookie('_SESSION_KEY', authcode(sessionKey, '_SESSION_KEY', 2592000000, 'encode'), {
domain: 'yohobuy.com',
expires: new Date(Date.now() + 2592000000) // 有效期一年
});
userId.sessionKey = sessionKey;
}
res.cookie('_LOGIN_IS_REPORT', false, {
domain: 'm.yohobuy.com',
path: '/'
});
return Auth.profile(userId).then((userInfo) => {
let salt = uuid.v4().substr(0, 8);
let saltedUid = uid + salt;
let saltedToken = sign.makeToken(saltedUid);
let publicToken = saltedToken + salt;
let data = userInfo.data;
let encryptionUid = aes.encryptionUid(uid);
if (data) {
data.profile_name = (data.profile_name || '').replace(/::/g, '');
let uidCookie =
`${data.profile_name}::${encryptionUid}::${data.vip_info && data.vip_info.title}::${saltedToken}`;
res.cookie('_UID', uidCookie, {
domain: 'yohobuy.com',
expires: new Date(Date.now() + 2592000000) // 有效期一年
});
req.session.AVATAR = data.head_ico;
_.set(req.session, 'USER.AVATAR', data.head_ico);
_.set(req.session, 'USER.NAME', data.profile_name);
}
req.session.TOKEN = publicToken;
req.session.LOGIN_UID = uid;
_.set(req.session, 'USER.ENCRYPTION_UID', encryptionUid);
res.cookie('_TOKEN', publicToken, {
httpOnly: true,
domain: 'yohobuy.com',
expires: new Date(Date.now() + 2592000000) // 有效期一年
});
});
}
}
module.exports = Auth;
... ...
/**
* Created by Tao.Huang on 2016/6/14.
*/
'use strict';
const api = global.yoho.API;
const logger = global.yoho.logger;
const aes = require('./aes-pwd');
const YOHOBUY_URL = 'http://www.yohobuy.com/';
/**
* 获取地区数据
*/
const getAreaDataAsync = () => {
return api.get('', {
method: 'app.passport.getArea'
}).then(result => {
if (result && result.code === 200) {
result.data = result.data.map(value => {
value.areaCode = `+${value.area}`;
if (value.areaCode === '+86') {
value.selected = true;
} else {
value.selected = false;
}
delete value.area;
return value;
});
return result;
} else {
logger.error('获取地区数据返回 code no 200');
return [];
}
});
};
/**
* 通过邮箱找回密码
*
* @param string mail 邮箱地址
*/
const sendCodeToEmailAsync = (email) => {
return api.get('', {
method: 'app.register.backpwdByEmail',
email: email
});
};
/**
* 根据邮箱验证码修改密码(调用www.yohobuy.com接口)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
const modifyPasswordByEmailAsync = (pwd, code) => {
const options = {
url: `${YOHOBUY_URL}passport/back/update`,
form: {
pwd: pwd,
're-input': pwd,
code: code
},
timeout: 3000
};
return api._requestFromAPI(options);
};
/**
* 根据邮箱验证码修改密码(调用新接口 采用AES密码加密)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
const modifyPasswordByEmailAsyncAes = (pwd, code) => {
return api.get('', {
code: code,
newPwd: aes.aesPwd(pwd),
method: 'app.register.resetPwdByCodeAES'
});
};
/**
* 通过手机找回密码
*
* @param string mobile 手机号
* @param integer area 地区码ID
*/
const sendCodeToMobileAsync = (mobile, area) => {
return api.get('', {
mobile: mobile,
area: area,
method: 'app.register.sendBackpwdCodeToMobile'
});
};
/**
* 校验密码修改手机验证码
*
* @param string mobile 手机号
* @param string code 验证码
* @param integer area 地区码ID
*/
const validateMobileCodeAsync = (mobile, code, area) => {
area = area || 86;
return api.get('', {
mobile: mobile,
code: code,
area: area,
method: 'app.register.validBackpwdCode'
});
};
/**
* 根据手机验证码修改密码
*
* @param string mobile 手机号
* @param string token 验证手机验证码返回的token
* @param integer area 地区码ID
*/
const modifyPasswordByMobileAsync = (mobile, token, newpwd, area) => {
return api.get('', {
mobile: mobile,
token: token,
newpwd: newpwd,
area: area,
method: 'app.register.changepwdByMobileCode'
});
};
const modifyPasswordByMobileAsyncAes = (mobile, token, newpwd, area) => {
return api.get('', {
mobile: mobile,
token: token,
newpwd: aes.aesPwd(newpwd),
area: area,
method: 'app.register.changepwdByMobileCodeAES'
});
};
module.exports = {
getAreaDataAsync,
sendCodeToEmailAsync,
modifyPasswordByEmailAsync,
modifyPasswordByEmailAsyncAes,
sendCodeToMobileAsync,
validateMobileCodeAsync,
modifyPasswordByMobileAsync,
modifyPasswordByMobileAsyncAes
};
... ...
/**
* Created by TaoHuang on 2016/6/14.
*/
'use strict';
const api = require('./back-api');
module.exports = api;
... ...
/**
* 注册数据接口
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const api = global.yoho.API;
const BindService = {
bindCheck(mobile, openId, sourceType, area) {
let params = {
method: 'app.passport.signCheck',
area: area,
mobile: mobile,
open_id: openId,
source_type: sourceType
};
return api.get('', params);
},
sendBindMsg(area, mobile) {
let params = {
method: 'app.passport.smsbind',
mobile: mobile,
area: area
};
return api.get('', params);
},
checkBindCode(area, mobile, code) {
return api.get('', {
method: 'app.register.validRegCode',
mobile: mobile,
area: area,
code: code
});
},
bindMobile(openId, sourceType, mobile, area, password, nickname) {
let params = {
method: 'app.passport.bind',
mobile: mobile,
open_id: openId,
source_type: sourceType,
area: area
};
if (password) {
params.password = password;
}
if (nickname) {
params.nickname = nickname;
}
return api.get('', params);
},
relateMobile(openId, sourceType, mobile, area) {
return api.get('', {
method: 'app.passport.relatedMobile',
mobile: mobile,
openId: openId,
source_type: sourceType,
area: area
});
},
changeCheck(mobile, area) {
return api.get('', {
method: 'app.passport.changeCheck',
mobile: mobile,
area: area
});
},
changeMobile(uid, mobile, area, code) {
return api.get('', {
method: 'app.passport.changeMobile',
mobile: mobile,
uid: uid,
code: code,
area: area
});
}
};
module.exports = BindService;
... ...
/**
* Created by TaoHuang on 2016/7/1.
*/
'use strict';
const _ = require('lodash');
const Captchapng = require('captchapng');
exports.generateCaptcha = (width, height, length) => {
let min = Math.pow(10, (length - 1 || 1));
let max = Math.pow(10, (length - 1 || 1)) * 9;
let token = '' + _.random(min, max);
let png = new Captchapng(width, height, token);//
png.color(0, 0, 0, 0); // First color: background (red, green, blue, alpha)
png.color(80, 80, 80, 255); // Second color: paint (red, green, blue, alpha)
return {
image: new Buffer(png.getBase64(), 'base64'),
text: token
};
};
... ...
'use strict';
const _ = require('lodash');
let captchaData = require('../data/captcha.json');
// let api = global.yoho.API;
/**
* 获取图形旋转验证码
* @return Promise
* {
* "alg": "SALT_MD5",
* "code": 200,
* "data":
* {
* "degrees":
* [
* 90,
* 270,
* 90,
* 0
* ],
* "verifiedGraphicCode": "http://img11.static.yhbimg.com/yhb-img01/2016/12/09/09/011c1ef761ab6f0cdb9fbd116f409a6e52.png"
* },
* "md5": "25d3e6b030a142c32d246322e6884080",
* "message": "操作成功"
*}
*/
exports.gen = () => {
// let params = {
// method: 'web.register.getVerifiedGraphicCode'
// };
// return api.get('', params);
let random = _.random(0, captchaData.length);
return Promise.resolve({
code: 200,
data: captchaData[random]
});
};
... ...
/**
* passport.js 支付宝登录插件
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const util = require('util');
const _ = require('lodash');
const md5 = require('yoho-md5');
const passport = require('passport-strategy');
// 支付宝网关地址
const ALIPAY_URL = 'https://mapi.alipay.com/gateway.do';
const defaultOptions = {
service: 'alipay.auth.authorize',
_input_charset: 'utf-8',
sign_type: 'MD5',
target_service: 'user.auth.quick.login'
};
/**
* 将参数排序,拼接成 "参数=参数值" 的格式
*
* @param {Object} params
*/
function paramsToRaw(params) {
let keys = Object.keys(params);
keys = keys.sort();
let string = '';
keys.forEach((key) => {
string += '&' + key + '=' + params[key];
});
string = string.substr(1);
return string;
}
function AlipayStrategy(options, verify) {
if (typeof options === 'function') {
verify = options;
options = {};
}
options = options || {};
passport.Strategy.call(this);
this.name = 'alipay';
this._verify = verify;
this._options = options;
}
util.inherits(AlipayStrategy, passport.Strategy);
AlipayStrategy.prototype.authenticate = function(req, options) {
if (req.query && req.query.is_success && req.query.sign && req.query.sign_type) {
let query = req.query;
let sign = query.sign;
let signType = query.sign_type;
delete query.sign_type;
delete query.sign;
let signString = paramsToRaw(query) + this._options.key;
if (signType === 'MD5' && sign !== md5(signString)) {
this.error('alipay callback sign check fail');
this.fail('alipay callback sign check fail');
}
if (req.query.is_success === 'T') {
let user = {
userId: req.query.user_id,
realName: req.query.real_name,
email: req.query.email
};
this.success(user, null);
} else {
this.error('alipay login fail');
this.fail(req.error_code);
}
} else {
let params = _.assign(defaultOptions, this._options, options);
let signType = params.sign_type;
let key = params.key;
delete params.sign_type;
delete params.sign;
delete params.key;
let signString = paramsToRaw(params) + key;
if (signType === 'MD5') {
params.sign = md5(signString);
params.sign_type = 'MD5';
}
this.redirect(ALIPAY_URL + '?' + paramsToRaw(params));
}
};
exports = module.exports = AlipayStrategy;
exports.Strategy = AlipayStrategy;
... ...
/* eslint no-unused-vars: ["error", { "args": "none" }] */
'use strict';
const API = global.yoho.API;
class PhoneService {
// 校验 手机 是否 已注册
// http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/校验是否是注册用户.md
static checkUserPhoneExist(mobile, area) {
return API.get('', {
method: 'app.passport.checkUserExist',
mobile,
area
});
}
// 手机号 自动登录
// http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/手机号自动登录.md
static autoSignin(param) {
return API.get('', {
method: 'app.passport.autoSignin',
profile: param.profile,
area: param.area,
code: param.code,
shopping_key: param.shopping_key
});
}
// 发送 验证码
// http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/发送验证码.md
static sendSMS(mobile, area, type) {
return API.get('', {
method: 'app.message.sendSms',
mobile,
area,
type
});
}
// 校验 验证码
// http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/验证验证码.md
static verifySMS(mobile, area, code, type) {
return API.get('', {
method: 'app.message.verifySmsCode',
mobile,
area,
code,
type
});
}
}
module.exports = PhoneService;
... ...
/**
* 注册数据接口
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const api = global.yoho.API;
const aes = require('./aes-pwd');
const RegService = {
getAreaData() {
return [
{
areaCode: '+61',
selected: false,
name: '澳大利亚'
}, {
areaCode: '+82',
selected: false,
name: '韩国'
}, {
areaCode: '+1',
selected: false,
name: '加拿大'
}, {
areaCode: '+60',
selected: false,
name: '马来西亚'
}, {
areaCode: '+1',
selected: false,
name: '美国'
}, {
areaCode: '+81',
selected: false,
name: '日本'
}, {
areaCode: '+65',
selected: false,
name: '新加坡'
}, {
areaCode: '+44',
selected: false,
name: '英国'
}, {
areaCode: '+86',
selected: true, // default choose
name: '中国'
}, {
areaCode: '+853',
selected: false,
name: '中国澳门'
}, {
areaCode: '+886',
selected: false,
name: '中国台湾'
}, {
areaCode: '+852',
selected: false,
name: '中国香港'
}];
},
sendCodeToMobile(area, mobile) {
let params = {
method: 'app.register.sendRegCodeToMobile',
area: area,
mobile: mobile
};
return api.post('', params);
},
validMobileCode(area, mobile, code) {
let params = {
method: 'app.register.validRegCode',
area: area,
mobile: mobile,
code: code
};
return api.post('', params);
},
regMobile(area, mobile, password, shoppingKey) {
let params = {
method: 'app.passport.register',
area: area,
profile: mobile,
password: password
};
if (shoppingKey) {
params.shopping_key = shoppingKey;
}
return api.post('', params);
},
regMobileAes(area, mobile, password, shoppingKey, smsCode, isFromMy) {
isFromMy = isFromMy || '0';
let params = {
method: 'app.passport.registerAES',
area: area,
profile: mobile,
password: aes.aesPwd(password),
verifyCode: smsCode,
isFromMy
};
if (shoppingKey) {
params.shopping_key = shoppingKey;
}
return api.post('', params);
}
};
module.exports = RegService;
... ...
/*
* @Author: Targaryen
* @Date: 2017-04-13 10:25:56
* @Last Modified by: Targaryen
* @Last Modified time: 2017-04-14 17:07:09
*/
/* ******************
* 重置密码
********************/
'use strict';
const api = global.yoho.API;
const aes = require('./aes-pwd');
/**
* 重置密码
* @param {*} params
*/
const resetPassword = (params) => {
return api.post('', {
method: 'app.password.modPwdByCode',
uid: params.uid,
oldPwd: aes.aesPwd(params.oldPwd),
newPwd: aes.aesPwd(params.newPwd),
token: params.token
});
};
module.exports = {
resetPassword
};
... ...
/**
* router of sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const express = require('express');
const login = require('./controllers/login');
const back = require('./controllers/back');
const bind = require('./controllers/bind');
const reg = require('./controllers/reg');
const reset = require('./controllers/reset');
const disableBFCahce = require('../../doraemon/middleware/disable-BFCache');
const smsLogin = require('./controllers/sms');
const update = require('./controllers/update');
const agreement = require('./controllers/app-agreement');
const geetest = require('./controllers/geetest');
const validateCode = require('./controllers/validateCode');
const router = express.Router(); // eslint-disable-line
// geetest
router.get('/passport/geetest/register', geetest.register);
// 兼容老的路由
router.get('/signin.html', login.common.weixinCheck, validateCode.load,
login.common.beforeLogin, login.common.clearCookie, smsLogin.loginPage);
router.get('/reg.html', validateCode.load, disableBFCahce, reg.index);
router.get('/login.html', validateCode.load,
login.common.beforeLogin, login.common.clearCookie, login.local.international);
router.get('/phoneback.html', back.indexMobilePage);
router.get('/emailback.html', back.indexEmailPage);
// 登出
router.get('/passport/signout/index', login.common.clearCookie, login.local.logout);
// 登录页面
router.get('/passport/login',
validateCode.load,
login.common.beforeLogin,
login.common.clearCookie,
login.loginShowCaptchaByIp,
login.local.loginPage
);
router.get('/passport/international',
validateCode.load,
login.common.beforeLogin,
login.common.clearCookie,
login.loginShowCaptchaByIp,
login.local.international
);
// 本地登录
router.post('/passport/login/auth', validateCode.check, login.local.login);
// 检查登录状态
router.get('/passport/login/check', login.common.check);
// SMS 短信
router.use('/passport/sms_login', login.common.beforeLogin, smsLogin.beforeIn);
router.get('/passport/sms_login', validateCode.load, smsLogin.loginPage);
router.post('/passport/sms_login/step1_check', validateCode.check, smsLogin.indexCheck);
router.get('/passport/sms_login/token.json',
smsLogin.tokenBefore,
smsLogin.token); // only ajax;
router.get('/passport/sms_login/check.json',
smsLogin.checkBefore,
smsLogin.check); // only ajax
router.post('/passport/sms_login/password.json', smsLogin.password);
// jsonp获取用户uid
router.get('/passport/login/user', login.user);
// 微信登录
router.get('/passport/login/wechat', login.common.beforeLogin, login.common.isLoginUser, login.wechat.login);
router.get('/passport/login/wechat/callback', login.wechat.callback);
// sina登录
router.get('/passport/login/sina', login.common.beforeLogin, login.sina.login);
router.get('/passport/login/sina/callback', login.sina.callback);
// qq登录
router.get('/passport/login/qq', login.common.beforeLogin, login.qq.login);
router.get('/passport/login/qq/callback', login.qq.callback);
// 支付宝登录
router.get('/passport/login/alipay', login.common.beforeLogin, login.alipay.login);
router.get('/passport/login/alipay/callback', login.alipay.callback);
// 登录绑定
router.get('/passport/bind/index', validateCode.load, bind.indexPage);
router.post('/passport/bind/bindCheck', validateCode.check, bind.bindCheck);
router.get('/passport/bind/code', bind.codePage);
router.post('/passport/bind/sendBindMsg',
bind.sendBefore,
bind.sendBindMsg);
router.post('/passport/bind/bindMobile', bind.bindMobile);
router.post('/passport/bind/relateMobile', bind.relateMobile);
router.get('/passport/bind/password', bind.passwordPage);
router.get('/passport/bind/success', bind.successPage);
router.post('/passport/bind/changeCheck', bind.changeCheck);
router.post('/passport/bind/changeMobile', bind.changeMobile);
/**
* 注册
*/
router.use('/passport/reg/*', disableBFCahce);
router.get('/passport/reg/index', validateCode.load, reg.index);
router.post('/passport/reg/verifymobile', validateCode.check, reg.sendCodeBusyBoy, reg.verifyMobile);
router.get('/passport/reg/code', reg.guardStep(2), reg.code);
router.post('/passport/reg/sendcode', reg.guardStep(2), reg.sendCodeBusyBoy, reg.sendCode);
router.post('/passport/reg/sendcodeagain', reg.guardStep(2), reg.sendCodeBusyBoy, reg.sendCode);
router.post('/passport/reg/verifycode', reg.guardStep(2), reg.verifyCode);
router.get('/passport/reg/password', reg.guardStep(3), reg.password);
router.post('/passport/reg/setpassword', reg.guardStep(3), reg.setPassword);
router.get('/passport/password/resetpage', reset.passwordResetPage); // 重置密码页面
router.post('/passport/password/reset', reset.passwordReset); // 重置密码
router.get('/passport/password/resetsuccess', reset.passwordResetOkPage); // 重置成功
/**
* 密码找回
*/
router.get('/passport/back/email', back.indexEmailPage);// 通过邮箱找回密码页面
router.post('/passport/back/sendemail', back.sendCodeToEmailAPI);// 发送邮箱验证码
router.get('/passport/back/resendemail', back.resendCodeToEmailAPI);// 重新发送邮箱验证码
router.get('/passport/back/success', back.backSuccessByEmailPage);// 邮箱找回密码-发送成功页面
router.get('/passport/back/mobile', validateCode.load, back.indexMobilePage);// 输入手机号找回密码页面
router.get('/passport/back/mobilecode', back.verifyCodeByMobilePage);// 输入手机验证码页面
router.get('/passport/back/generatecodeimg.png', back.generateCodeImg);// 生成图片验证码
router.post('/passport/back/sendcode', validateCode.check, back.sendCodeToMobileAPI);// 发送手机验证码
router.post('/passport/back/sendcodeagain', back.verifySmsAllow, back.sendCodeToMobileAPI);// 重新发送手机验证码
router.post('/passport/back/verifycode', back.verifyCodeByMobileAPI);// 校验手机验证码
router.get('/passport/back/backcode', back.setNewPasswordByMobilePage);// 设置新密码页面
router.post('/passport/back/passwordbyemail', back.setNewPasswordByEmailAPI);// 依据邮箱验证码修改密码
router.post('/passport/back/passwordbymobile', back.setNewPasswordByMobileAPI);// 依据手机验证码修改密码
/**
* 提示升级app
*/
router.get('/passport/update', update.index);
// 服务条款
router.get('/passport/privacy', agreement.privacy);// 隐私条款
router.get('/passport/newpower', agreement.newpower);// 新力传媒
router.get('/passport/yohobuy', agreement.aboutYoho);// 关于有货
router.get('/passport/agreement', agreement.agreement);// 服务条款
router.get('/passport/about', agreement.about);// 关注有货
// 验证码
let captcha = require('./controllers/captcha');
router.get('/passport/captcha/get', captcha.get);
router.get('/passport/img-check.jpg', captcha.imgCheck);
module.exports = router;
... ...
/**
* 投票
* @author: leo
* @date: 23/06/2017
*/
const redis = require('redis');
const redisCli = global.yoho.utils.redisCli;
function getAll(req, res) {
redisCli.hget('vote', 'total', function(err, reply) {
res.json({
code: 200,
data: {
total: +reply
}
})
});
}
function addOne(req, res) {
redisCli.hincrby('vote', 'total', 1, function (err, reply) {
res.json({
code: 200,
message: '投票成功。'
});
});
}
module.exports = {
getAll,
addOne
};
... ...
/**
* index.js
* @author: leo
* @date: 23/06/2017
*/
const express = require('express');
const app = express();
app.use(require('./router'));
module.exports = app;
... ...
/**
* index.js
* @author: leo
* @date: 23/06/2017
*/
const express = require('express');
const router = express.Router();
const vote = require('./controllers/vote');
router.get('/', vote.getAll);
router.post('/', vote.addOne);
// router.get('/:vote_id', vote.getOne);
// router.put('/:vote_id', vote.putOne);
// router.delete('/:vote_id', vote.deleteOne);
module.exports = router;
... ...
/**
* 系统配置
*
* @author hbomb qiqi.zhou@yoho.cn
* @date 2016/05/06
*/
const pkg = require('../package.json');
const isProduction = process.env.NODE_ENV === 'production';
const isTest = process.env.NODE_ENV === 'test';
const domains = {
api: 'http://api-test3.yohops.com:9999/',
service: 'http://service-test3.yohops.com:9999/',
singleApi: 'http://api-test3.yohops.com:9999/',
global: 'http://global-test-soa.yohops.com:9999',
liveApi: 'http://testapi.live.yohops.com:9999/',
imSocket: 'ws://socket.yohobuy.com:10240',
imCs: 'http://im.yohobuy.com/api',
platformApi: 'http://192.168.102.48:8088/'
};
module.exports = {
app: 'h5',
appVersion: '5.8.0', // 调用api的版本
port: 6001,
siteUrl: '//m.yohobuy.com',
assetUrl: '//127.0.0.1:5001',
testCode: 'yoho4946abcdef#$%&!@',
domains: domains,
subDomains: {
host: '.m.yohobuy.com',
default: '//m.yohobuy.com',
guang: '//guang.m.yohobuy.com',
list: '//list.m.yohobuy.com',
search: '//search.m.yohobuy.com',
huodong: '//huodong.m.yohobuy.com',
activity: '//activity.yohobuy.com',
index: '//m.yohobuy.com'
},
useCache: false,
memcache: {
master: ['127.0.0.1:11211'],
slave: ['127.0.0.1:11211'],
session: ['127.0.0.1:11211'],
timeout: 1000,
retries: 0
},
interfaceShunt: {
open: false
},
loggers: {
infoFile: {
close: true,
name: 'info',
level: 'error',
filename: 'logs/info.log',
maxFiles: 7
},
errorFile: {
name: 'error',
level: 'error',
filename: 'logs/error.log',
handleExceptions: true,
maxFiles: 7
},
console: {
level: 'info',
colorize: 'all',
prettyPrint: true
}
},
thirdLogin: {
wechat: {
appID: 'wx75e5a7c0c88e45c2',
appSecret: 'ce21ae4a3f93852279175a167e54509b'
}
},
zookeeperServer: '127.0.0.1:2181',
alipayConfig: {
payUrl: 'https://mapi.alipay.com/gateway.do',
service: 'alipay.wap.create.direct.pay.by.user',
partner: '2088701661478015',
inputCharset: 'utf-8',
notifyUrl: domains.service + 'payment/alipay_notify',
returnUrl: '/shopping/pay/aliwapreturn',
signType: 'MD5',
paymentType: '1',
alipayKey: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
sellerMail: 'zfb@yoho.cn',
merchantUrl: 'http://m.yohobuy.com/home/orderDetail?order_code='
},
WxPayConfig: {
appId: 'wx75e5a7c0c88e45c2',
mchId: '1227694201',
key: '7e6f3307b64cc87c79c472814b88f7fb',
appSecret: 'ce21ae4a3f93852279175a167e54509b',
notifyUrl: domains.service + 'payment/weixin_notify',
},
maxQps: 1200,
geetestJs: '//static.geetest.com/static/tools/gt.js',
jsSdk: '//cdn.yoho.cn/js-sdk/1.2.2/jssdk.js',
redis: {
connect: {
host: '127.0.0.1',
port: '6379',
retry_strategy(options) {
if (options.error && options.error.code === 'ECONNREFUSED') {
console.log('redis连接不成功');
}
if (options.total_retry_time > 1000 * 60 * 60 * 6) {
console.log('redis连接超时');
return;
}
if (options.attempt > 10) {
return 1000 * 60 * 60 * 0.5;
}
return Math.min(options.attempt * 100, 1000);
}
}
}
};
if (isProduction) {
Object.assign(module.exports, {
appName: 'm.yohobuy.com',
assetUrl: `//cdn.yoho.cn/m-yohobuy-node/${pkg.version}/`,
domains: {
api: 'http://api.yoho.yohoops.org/',
service: 'http://service.yoho.yohoops.org/',
global: 'http://api-global.yohobuy.com',
store: '', // 线上域名尚未确定
liveApi: 'http://api.live.yoho.cn/',
singleApi: 'http://single.yoho.cn/',
imSocket: 'wss://imsocket.yohobuy.com:443',
imCs: 'https://imhttp.yohobuy.com/api',
platformApi: 'http://api.platform.yohoops.org'
},
memcache: {
master: ['memcache1.yohoops.org:12111', 'memcache2.yohoops.org:12111', 'memcache3.yohoops.org:12111'],
slave: ['memcache1.yohoops.org:12112', 'memcache2.yohoops.org:12112', 'memcache3.yohoops.org:12112'],
session: ['memcache1.yohoops.org:12111', 'memcache2.yohoops.org:12111', 'memcache3.yohoops.org:12111'],
poolSize: 100,
reconnect: 5000,
timeout: 1000,
retries: 0
},
useCache: true,
interfaceShunt: {
open: false,
url: 'http://123.206.2.55/strategy'
},
zookeeperServer: 'web.zookeeper.yohoops.org:2181',
alipayConfig: {
payUrl: 'https://mapi.alipay.com/gateway.do',
service: 'alipay.wap.create.direct.pay.by.user',
partner: '2088701661478015',
inputCharset: 'utf-8',
notifyUrl: 'http://service.yoho.cn/payment/alipay_notify',
returnUrl: '/shopping/pay/aliwapreturn',
signType: 'MD5',
paymentType: '1',
alipayKey: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
sellerMail: 'zfb@yoho.cn',
merchantUrl: 'http://m.yohobuy.com/home/orderDetail?order_code='
},
WxPayConfig: {
appId: 'wx75e5a7c0c88e45c2',
mchId: '1227694201',
key: '7e6f3307b64cc87c79c472814b88f7fb',
appSecret: 'ce21ae4a3f93852279175a167e54509b',
notifyUrl: 'http://service.yoho.cn/payment/weixin_notify',
},
redis: {
connect: {
host: 'web.redis.yohoops.org'
},
port: '6379',
retry_strategy(options) {
if (options.error && options.error.code === 'ECONNREFUSED') {
console.log('redis连接不成功');
}
if (options.total_retry_time > 1000 * 60 * 60 * 6) {
console.log('redis连接超时');
return;
}
if (options.attempt > 10) {
return 1000 * 60 * 60 * 0.5;
}
return Math.min(options.attempt * 100, 1000);
}
}
});
} else if (isTest) {
Object.assign(module.exports, {
appName: 'm.yohobuy.com for test',
assetUrl: `//cdn.yoho.cn/m-yohobuy-node/${pkg.version}/`,
domains: {
api: process.env.TEST_API || 'http://api-test1.yohops.com:9999/',
service: process.env.TEST_SERVICE || 'http://service-test1.yohops.com:9999/',
global: process.env.TEST_GLOBAL || 'http://global-test-soa.yohops.com:9999/',
store: process.env.TEST_STORE || 'http://192.168.102.210:8080/portal-gateway/',
liveApi: process.env.TEST_LIVE || 'http://testapi.live.yohops.com:9999/',
singleApi: process.env.TEST_SINGLE || 'http://api-test1.yohops.com:9999/',
imSocket: process.env.TEST_IM_SOCKET || 'ws://socket.yohobuy.com:10240',
imCs: process.env.TEST_IM_CS || 'http://im.yohobuy.com/api',
platformApi: 'http://192.168.102.48:8088/'
},
memcache: {
master: ['127.0.0.1:12111'],
slave: ['127.0.0.1:12112'],
session: ['127.0.0.1:12111'],
timeout: 100,
reconnect: 5000,
retries: 0
},
useCache: true,
alipayConfig: {
payUrl: 'https://mapi.alipay.com/gateway.do',
service: 'alipay.wap.create.direct.pay.by.user',
partner: '2088701661478015',
inputCharset: 'utf-8',
notifyUrl: (process.env.TEST_SERVICE || 'http://service-test1.yohops.com:9999/') + 'payment/alipay_notify',
returnUrl: '/shopping/pay/aliwapreturn',
signType: 'MD5',
paymentType: '1',
alipayKey: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
sellerMail: 'zfb@yoho.cn',
merchantUrl: 'http://m.yohobuy.com/home/orderDetail?order_code='
},
WxPayConfig: {
appId: 'wx75e5a7c0c88e45c2',
mchId: '1227694201',
key: '7e6f3307b64cc87c79c472814b88f7fb',
appSecret: 'ce21ae4a3f93852279175a167e54509b',
notifyUrl: (process.env.TEST_SERVICE || 'http://service-test1.yohops.com:9999/') + 'payment/weixin_notify',
}
});
}
... ...
/**
* API路由分发
* @author: Leo<qi.li@yoho.cn>
* @date: 2017/6/23
*/
const vote = require('./apps/vote');
module.exports = app => {
// 投票
app.use('/votes', vote);
};
... ...
## 接口归纳
####【主页面】热门图片列表(考虑复用全部图片列表接口)
###【列表页、图片页】点赞
###【列表页】全部图片列表
###【发布页】上传图片
###【发布页】发送验证码
###【发布页】发布评价前验证通过手机号返回用户昵称和头像(区分老用户和新用户的不同规则)
###【发布页】发布评论
### 图片页】图片详情接口
###【分享页】??????
... ...
No preview for this file type
No preview for this file type
const shelljs = require('shelljs');
const path = require('path');
const ext = process.platform === 'win32' ? '.cmd' : ''; // Windows 平台需要加后缀
const lintPath = {
js: path.resolve('./node_modules/.bin/eslint'),
css: path.resolve('./node_modules/.bin/stylelint')
};
const jsfiles = ['.', 'public/vue'];
const cssfiles = ['public/scss/**/*.css', 'public/vue/**/*.vue'];
jsfiles.forEach(function(filepath) {
console.log(`JS ${filepath} 检查结果:`);
shelljs.exec(`${lintPath.js}${ext} -f table -c .eslintrc --cache ${filepath}`);
});
cssfiles.forEach(function(filepath) {
console.log(`CSS ${filepath} 检查结果:`);
shelljs.exec(`${lintPath.css}${ext} --syntax scss --cache --config .stylelintrc --custom-formatter ./node_modules/stylelint-formatter-table/index.js "${filepath}"`); // eslint-disable-line
});
... ...
const shelljs = require('shelljs');
const path = require('path');
const changeFiles = {
js: shelljs.exec('git diff --cached --name-only --diff-filter=ACM | grep .js$', {silent: true}).stdout,
css: shelljs.exec('git diff --cached --name-only --diff-filter=ACM | grep .css$', {silent: true}).stdout,
vue: shelljs.exec('git diff --cached --name-only --diff-filter=ACM | grep .vue$', {silent: true}).stdout,
};
const lintPath = {
js: path.resolve('./node_modules/.bin/eslint'),
css: path.resolve('./node_modules/.bin/stylelint')
};
const lintResult = {
js: {},
css: {},
vueScript: {},
vueStyle: {}
};
const ext = process.platform === 'win32' ? '.cmd' : ''; // Windows 平台需要加后缀
// 在执行检查脚本的时候,不显示 NPM 错误日志
if (!shelljs.grep('npm run -s', path.resolve('./.git/hooks/pre-commit')).stdout.trim()) {
shelljs.sed('-i', 'npm run', 'npm run -s', path.resolve('./.git/hooks/pre-commit'));
}
if (changeFiles.js) {
changeFiles.js = changeFiles.js.replace(/\n/g, ' ');
lintResult.js = shelljs.exec(`${lintPath.js}${ext} -c .eslintrc --cache ${changeFiles.js}`);
}
if (changeFiles.css) {
changeFiles.css = changeFiles.css.replace(/\n/g, ' ');
lintResult.css = shelljs.exec(`${lintPath.css}${ext} --syntax scss --config .stylelintrc ${changeFiles.css}`);
}
if (changeFiles.vue) {
changeFiles.vue = changeFiles.vue.replace(/\n/g, ' ');
lintResult.vueScript = shelljs.exec(`${lintPath.js}${ext} -c .eslintrc --cache ${changeFiles.vue}`);
lintResult.vueStyle = shelljs.exec(`${lintPath.css}${ext} --syntax scss --extract --config .stylelintrc ${changeFiles.vue}`); // eslint-disable-line
}
const errorCode = lintResult.js.code || lintResult.css.code || lintResult.vueScript.code || lintResult.vueStyle.code;
if (errorCode) {
process.exit(errorCode); // eslint-disable-line
}
... ...
/**
* 登录判断
* @author: leo<qi.li@yoho.cn>
* @date: 2017/6/23
*/
'use strict';
module.exports = (req, res, next) => {
if (!req.user.uid) {
return res.json({
code: 400,
message: '抱歉,您暂未登录!'
});
}
next();
};
... ...
/**
* error处理
* @author: leo<qi.li@yoho.cn>
* @date: 2017/06/23
*/
const logger = global.yoho.logger;
module.exports = (err, req, res, next) => { // eslint-disable-line
logger.error(err);
if (err.code === 401) {
return res.status(401).json({
code: 401,
message: '抱歉,您暂未登录!'
});
}
if (err.code === 404) {
return res.status(404).json({
code: 404,
message: 'Not Found'
});
}
return res.status(500).json({
code: 500,
message: '服务器错误!'
});
next();
};
... ...
/**
* 入口
* @author: qi.li<qi.li@yoho.cn>
* @date: 2017/06/23
*/
'use strict';
const auth = require('./auth');
const error = require('./error');
module.exports = {auth, error};
... ...
/**
* 设置 YOHO 数据
* @author: 赵彪<bill.zhao@yoho.cn>
* @date: 2016/6/16
*/
'use strict';
const _ = require('lodash');
const helpers = global.yoho.helpers;
const net = require('net');
/**
* 获取 IP
* @param {*} req
*/
const _getClientIp = req => {
let remoteIp = req.get('X-Forwarded-For') || req.get('X-Real-IP') || req.ip;
if (remoteIp.indexOf(',') > 0) {
let arr = remoteIp.split(',');
remoteIp = _.trim(arr[arr.length - 1]);
}
if (_.startsWith(remoteIp, '10.66.')) {
remoteIp = req.get('X-Real-IP');
}
remoteIp = _.trim(remoteIp);
if (!net.isIPv4(remoteIp)) {
let ipv6String = remoteIp.split(':');
remoteIp = ipv6String[ipv6String.length - 1];
}
return remoteIp;
};
module.exports = () => {
return (req, res, next) => {
let yoho = {
pageChannel: {}
};
let ua = (req.get('User-Agent') || '').toLowerCase();
const channel = req.query.channel || req.cookies._Channel || 'boys';
// IP 地址
yoho.clientIp = _getClientIp(req);
// 用于头部颜色控制
yoho.pageChannel[channel] = true;
// 当前频道设置
yoho.channel = channel;
// 判断请求是否来自app
yoho.isApp = (req.query.app_version && req.query.app_version !== 'false') ||
(req.query.appVersion && req.query.appVersion !== 'false') ||
req.cookies.app_version || /YohoBuy/i.test(req.get('User-Agent') || '');
yoho.isMobile = /(nokia|iphone|android|ipad|motorola|^mot\-|softbank|foma|docomo|kddi|up\.browser|up\.link|htc|dopod|blazer|netfront|helio|hosin|huawei|novarra|CoolPad|webos|techfaith|palmsource|blackberry|alcatel|amoi|ktouch|nexian|samsung|^sam\-|s[cg]h|^lge|ericsson|philips|sagem|wellcom|bunjalloo|maui|symbian|smartphone|midp|wap|phone|windows ce|iemobile|^spice|^bird|^zte\-|longcos|pantech|gionee|^sie\-|portalmmm|jig\s browser|hiptop|^ucweb|^benq|haier|^lct|opera\s*mobi|opera\*mini|320x320|240x320|176x220)/i.test(req.get('User-Agent') || ''); // eslint-disable-line
yoho.isWechat = /micromessenger/i.test(req.get('User-Agent') || '');
yoho.isWeibo = ua.indexOf('weibo') !== -1;
yoho.isqq = /MQQBrowser/i.test(req.get('User-Agent') || '');
Object.assign(res.locals, yoho);
Object.assign(req.yoho, yoho);
// App 内请求支持跨域
if (yoho.isApp) {
res.set('Access-Control-Allow-Origin', '*');
}
res.locals.cartUrl = helpers.urlFormat('/cart/index/index'); // 悬挂购物车
res.locals.indexUrl = helpers.urlFormat('/?go=1'); // 悬挂首页
res.locals.showHeader = true;
next();
};
};
... ...
'use strict';
const _ = require('lodash');
const cookie = global.yoho.cookie;
const authcode = require('../../utils/authcode');
const config = global.yoho.config;
module.exports = () => {
return (req, res, next) => {
if (!req.yoho.isApp) {
// 从 SESSION 中获取到当前登录用户的 UID
if (req.session && _.isNumber(req.session.LOGIN_UID)) {
// 调用接口传参时切勿使用toString获得字符串
req.user.uid = {
toString: () => {
return _.parseInt(req.session.LOGIN_UID);
},
sessionKey: req.session.SESSION_KEY
};
let userData = _.get(req.session, 'USER', {});
_.merge(req.user, userData);
}
// session 没有读取到的时候,从 cookie 读取 UID
if (!req.user.uid && req.cookies._UID) {
let sessionKey = req.cookies._SESSION_KEY &&
authcode(req.cookies._SESSION_KEY, '_SESSION_KEY', 2592000000);
// 调用接口传参时切勿使用toString获得字符串
req.user.uid = {
toString: () => {
return _.parseInt(cookie.getUid(req));
},
sessionKey
};
}
}
if (!req.user.uid &&
req.cookies.app_uid &&
req.cookies.app_uid !== '0' &&
req.cookies.app_session_key &&
req.cookies.app_version &&
req.cookies.app_client_type) {
// 调用接口传参时切勿使用toString获得字符串
req.user.uid = {
toString: () => {
return _.parseInt(req.cookies.app_uid);
},
sessionKey: req.cookies.app_session_key,
appVersion: req.query.app_version || req.cookies.app_version || config.appVersion,
appSessionType: req.cookies.app_client_type
};
}
if (!req.user.uid &&
(req.query.uid || (
req.cookies.app_uid &&
req.cookies.app_uid !== '0' &&
req.cookies.app_version &&
req.cookies.app_client_type
))
) {
let appUid = req.query.uid || req.cookies.app_uid;
let appVersion = req.query.app_version || req.cookies.app_version || config.appVersion;
let appSessionType = req.query.client_type || req.cookies.app_client_type;
req.query.uid = {
toString: () => {
return _.parseInt(appUid);
},
appVersion: appVersion,
appSessionType: appSessionType
};
res.cookie('app_uid', appUid.toString());
res.cookie('app_version', appVersion);
res.cookie('app_client_type', appSessionType);
}
next();
};
};
... ...
/**
* OneAPM agent configuration
*/
const commonConfig = require('./config/index');
exports.config = {
app_name: [commonConfig.appName],
license_key: 'BwEGA1dRDlQ6357HHQ1AD1xJVkbc9fNfWRtQUwhQG41c5QFWGFIDSQoHc0e8AgMaUlcUVw0=',
logging: {
level: 'info'
},
transaction_events: {
enabled: true
}
};
... ...
{
"name": "yoho-activity-platform",
"version": "1.0.0",
"private": true,
"description": "A New Yohobuy Project With Express",
"repository": {
"type": "git",
"url": "http://git.yoho.cn/fe/yoho-activity-platform.git"
},
"scripts": {
"start": "NODE_ENV=\"production\" node app.js",
"dev": "nodemon -e js,hbs -i public/ app.js",
"static": "webpack-dev-server --config ./public/build/webpack.dev.config.js",
"build": "webpack --config ./public/build/webpack.prod.config.js",
"debug": "DEBUG=\"express:*\" nodemon -e js,hbs -i public/ app.js",
"lint-js": "eslint -c .eslintrc --cache .",
"lint-css": "stylelint --syntax scss --cache --config .stylelintrc 'public/scss/**/*.css'",
"lint-vue-js": "eslint -c .eslintrc --cache public/vue",
"lint-vue-css": "stylelint --syntax scss --extract --cache --config .stylelintrc 'public/scss/**/*.vue'",
"lint-all": "node lint-all.js",
"precommit": "node lint-commit.js"
},
"license": "MIT",
"dependencies": {
"bluebird": "^3.4.7",
"body-parser": "^1.17.2",
"captchapng": "0.0.1",
"cheerio": "^0.22.0",
"client-sessions": "^0.8.0",
"compression": "^1.6.2",
"connect-memcached": "^0.2.0",
"connect-multiparty": "^2.0.0",
"cookie-parser": "^1.4.3",
"cssnano": "^3.10.0",
"express": "^4.15.3",
"fast-safe-stringify": "^1.2.0",
"feed": "^1.1.0",
"geetest": "^4.1.2",
"lodash": "^4.17.4",
"memory-cache": "^0.1.6",
"moment": "^2.18.1",
"oneapm": "^1.2.20",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"passport-qq": "0.0.3",
"passport-sina": "^0.1.0",
"passport-strategy": "^1.0.0",
"passport-weixin": "^0.1.0",
"redis": "^2.7.1",
"postcss-calc": "^5.3.1",
"request": "^2.81.0",
"request-promise": "^4.2.1",
"semver": "^5.3.0",
"sitemap": "^1.12.0",
"uuid": "^3.0.1",
"xml2js": "^0.4.17",
"yoho-express-session": "^2.0.0",
"yoho-md5": "^2.0.0",
"yoho-node-lib": "=0.2.28",
"yoho-zookeeper": "^1.0.8"
},
"devDependencies": {
"autoprefixer": "^7.0.1",
"babel-core": "^6.24.1",
"babel-loader": "^7.0.0",
"babel-polyfill": "^6.23.0",
"babel-preset-env": "^1.5.1",
"css-loader": "^0.28.4",
"eslint": "^3.19.0",
"eslint-config-yoho": "^1.0.1",
"eslint-loader": "^1.7.1",
"eslint-plugin-html": "^2.0.3",
"extract-text-webpack-plugin": "^2.1.0",
"handlebars-loader": "^1.5.0",
"happypack": "^3.1.0",
"husky": "^0.13.4",
"nodemon": "^1.11.0",
"postcss-assets": "^4.0.1",
"postcss-calc": "^6.0.0",
"postcss-center": "^1.0.0",
"postcss-clearfix": "^1.0.0",
"postcss-crip": "^2.0.1",
"postcss-import": "^10.0.0",
"postcss-loader": "^2.0.0",
"postcss-position": "^0.5.0",
"postcss-pxtorem": "^4.0.1",
"postcss-scss": "^1.0.0",
"postcss-short": "^4.1.0",
"postcss-sprites": "^4.2.1",
"postcss-use": "^2.3.0",
"precss": "^1.4.0",
"shelljs": "^0.7.6",
"style-loader": "^0.18.1",
"stylelint": "^7.10.1",
"stylelint-config-yoho": "^1.2.8",
"stylelint-formatter-table": "^1.0.2",
"stylelint-processor-html": "^1.0.0",
"stylelint-webpack-plugin": "^0.7.0",
"vue": "^2.3.3",
"vue-loader": "^12.2.1",
"vue-template-compiler": "^2.3.3",
"webpack": "^2.6.1",
"webpack-dev-server": "^2.4.5",
"webpack-uglify-parallel": "^0.1.3",
"yoho-cookie": "^1.2.0",
"yoho-fastclick": "^1.0.6",
"yoho-hammer": "^2.0.7",
"yoho-iscroll": "^5.2.0",
"yoho-jquery": "^2.2.4",
"yoho-jquery-lazyload": "^1.9.12",
"yoho-jquery-qrcode": "^0.14.0",
"yoho-mlellipsis": "0.0.3",
"yoho-qs": "^1.0.1",
"yoho-swiper": "^3.3.1",
"yoho-swiper2": "0.0.5"
}
}
... ...
{
"apps": [
{
"name": "yohobuywap-node",
"script": "app.js",
"instances": "3",
"exec_mode": "cluster",
"merge_logs": true,
"log_date_format": "YYYY-MM-DD HH:mm:ss Z",
"error_file": "/Data/logs/node/yohobuywap-node-err.log",
"out_file": "/Data/logs/node/yohobuywap-node-out.log",
"env": {
"TZ": "Asia/Shanghai",
"PORT": 6001
}
}
]
}
... ...
/**
* utils 入口
* @author: qi.li<qi.li@yoho.cn>
* @date: 2017/06/23
*/
'use strict';
const redisCli = require('./redis');
module.exports = {
redisCli
};
... ...
const redis = require('redis');
const bluebird = require('bluebird');
const config = require('../config/index');
let client;
try {
client = redis.createClient(config.redis.connect);
bluebird.promisifyAll(redis.RedisClient.prototype);
bluebird.promisifyAll(redis.Multi.prototype);
client.on('error', function() {
global.yoho.redis = '';
});
client.on('connect', function() {
global.yoho.redis = client;
});
} catch (e) {
global.yoho.redis = '';
}
module.exports = client;
... ...